Skip to content

Instantly share code, notes, and snippets.

@miahmie
Last active January 27, 2024 14:50
Show Gist options
  • Save miahmie/85c238e4c87d4cfe64b33a061cc193e9 to your computer and use it in GitHub Desktop.
Save miahmie/85c238e4c87d4cfe64b33a061cc193e9 to your computer and use it in GitHub Desktop.
吉里吉里Z(2)/KAG3向けザッピングシナリオ切り替えプラグイン

ザッピングシナリオ切り替えプラグイン

吉里吉里にて複数のシナリオを任意のタイミングで切り替えながら進める形式を実現するための簡易プラグインです。

  • 動作としてはシナリオの現在状態が保存されている kag.pcflags をザッピングパート毎に切り替えるイメージです
    • テキスト履歴(HistoryLayer)のログ内容も切り替わるため,パート別に内容が保持されることに注意してください
  • f.〇〇 のシナリオローカルフラグはザッピング前後で維持されます
  • それぞれのザッピングパートで独立したフラグ管理をしたい場合は,フラグ名の重複を避けてください
  • どうしても重複が避けられない場合は [zapping_option indepflags=~] を指定してください
    • 外部のプラグインなどで,現在の状態を f.〇〇 に保存する実装になっている時などが該当します
  • 2つ以上のパートのザッピングが可能な実装になっています(実装例では2つのパートの切り替え)
  • 各パート相互に状態が影響するシナリオ構成の場合,ザッピング切り替え直後に状態の齟齬が発生する場合があります
    • 例:片方のパートでブレーカーを落とす⇒ザッピング切り替え⇒電気が消えていないといけないのにザップ前の状態を保持しているので電気がついたまま,といったようなケース
    • 状態の再反映に関しては本プラグインの領分ではないので各々で工夫してください
    • テキストメッセージを送るごとに毎回フラグをチェックして変更があれば内容を反映,といった泥臭い方法が一番手っ取り早いと思われます
  • 動作チェックが甘いので何かしらの不備があるかもしれません
    • ザッピングシナリオ中のcallスタックの扱いなどに不安があります

使い方

起動直後のシナリオなどで zapping.ks を call してください。

各種オプションを定義する [zapping_option] と,実際にザッピングを実行する [zapping_jump] マクロが定義されます。

各マクロの詳細はリファレンスを参照してください。

実装例

first.ks等の初期化シナリオ中にプラグインをロードしてください:

; プラグインをロード
[call storage=zapping.ks]

タイトル画面等からゲーム開始直後のシナリオファイルで下記を記述します:

; 全初期化
[zapping_option init]
; パート名と初回起動シナリオを定義
[zapping_option scenario="a" storage="zaproot.ks" target=*first_a]
[zapping_option scenario="b" storage="zaproot.ks" target=*first_b]
; 独立したフラグが必要であればここに記述(zaproot.ks *init_flagsで初期化が必要なことに注意)
[zapping_option indepflags="flag_x:flag_y"]
; ザッピング切り替えUI用定義例
[sysbutton name=zap graphic=zapbutton ... exp='_button_zapping_call(true,"zaproot.ks","*zapbutton")']
; 初回パートにジャンプ
[zapping_jump jump="a"]

ザッピング管理シナリオ(上記の例では zaproot.ks)にフラグ初期化とジャンプ処理を記述します:

; ザッピング切り替え処理(extra conductor経由)
*zapbutton
; ~必要に応じてここに場面切り替えの演出を追加~
; シナリオパート切り替え
[zapping_jump swap="a:b"]
[return]

; 各パートの初期化と分岐(※ジャンプ先シナリオを必要に応じて調整)
*first_a|
[call target=*init_flags]
[jump storage="Aパート本編.ks" target=*label]

*first_b|
[call target=*init_flags]
[jump storage="Bパート本編.ks" target=*label]

; 共通の初期化処理
*init_flags
; 独立したフラグを指定した場合([zapping_option indepflags=~]),ここで必ず初期化すること
[eval exp="f.flag_x=void"]
[eval exp="f.flag_y=void"]
[return]

リファレンス

初期化

[zapping_option init]

初回シナリオテーブルや,現在のザッピング保存状態など,すべて初期化します。 タイトルからスタートした直後などに実行し,そのあとで初回シナリオテーブルを記述すると良いでしょう。

デバッグモード

[zapping_option debug=true]
[zapping_option debug=false]

デバッグログ表示を有効にします。debug=trueでログ有り,debug=falseでログ無し(デフォルト)の動作です。

吉里吉里のコンソールログにザッピング動作のタイミングで「@zapping_debug xxx=...」のようなログが表示されます。

なお,デバッグ状態はセーブデータへは保存されず,[zapping_option init]の初期化の対象外です。ゲーム起動毎に設定する必要があります。

パート別初回シナリオ定義

[zapping_option scenario="scn1" storage="..." target="..."]

scenario= でパート名(管理しやすい名前を任意に指定/半角大文字小文字は区別されません)を指定し,storage, target でジャンプ先を指定します。

storage および target を指定しない場合は現在定義されているパートに関しての初回シナリオの情報をクリアします。

もしくは,

[zapping_option resetallscn]

にて,今まで定義したパートに関する初回シナリオの情報をすべてクリアします。

ザッピング状態情報の消去

[zapping_option clearstate="scn1:scn2:..."]

過去に切り替えた指定パートのザッピングの状態情報を消去します。:で区切って複数のパートを一括で消去することができます。

状態情報を消去することでセーブデータのサイズが小さくなるといった利点があります。 これ以降ザッピング機能を利用しないといった場合に使うと良いかもしれません。

[zapping_option init] を実行してもセーブデータは小さくなります。

パート別独立フラグの指定

[zapping_option indepflags="flag1:flag2:...:flagN"]

各パートで f.〇〇 系のフラグ管理を独立させたい場合に,そのフラグ名を : で区切って列挙します。

ザッピング時の状態保存データに列挙されたフラグの内容も一緒に保存し,戻す時にフラグを書き戻すという動作になります。

この実装の都合上,初回シナリオジャンプ時は上記でフラグを維持したまま開始されてしまうため, 本ドキュメント冒頭の実装例においては zaproot.ks*init_flags にてフラグを初期化するという処理を入れてあることに注意してください。

強制パート指定

[zapping_option current="scn1"]

ジャンプ処理を実行せずに,強制的に指定のパート内にいるものとして状態を設定します。 基本的には [zapping_jump] を使うため,この機能を使うことはないと思います。

指定パートへザッピング

[zapping_jump jump="scn1"]

指定したパートにザッピングします。extra conductor経由でも通常のシナリオスクリプトからでも使用可能です。

extra conductor内で呼び出された場合は return で本編に戻る瞬間にザッピングする動作になります。

なお,現在のパートがジャンプ指定と同じパートだった場合は何も起こりません。

切り替え式ザッピング

[zapping_jump swap="scn1:scn2"]

現在プレイ中のパートの次のパートにザッピングします。

  • swap=: で区切って複数のパートを列挙します
  • 現在のパートが列挙されたパートのいずれにも含まれない場合は先頭に列挙されたパートにジャンプします
  • 現在のパートが末尾に列挙されたパートだった場合も先頭のパートにジャンプします

2つのパートしか列挙しない場合は交互に切り替わる動作となります

ユーティリティ関数

/**
 * ザッピングボタンが押された時に利用するKAGシナリオコール関数
 * @param use_extra : kag.callExtraConductor経由で呼び出す
 * @param storage : コールするシナリオファイル
 * @param target : コールするラベル
 */
function _button_zapping_call(use_extra, storage, target);

[link_or_uibutton_definition exp=_buton_zapping_call(~)] のように使うことを想定しています。

/**
 * ザッピングボタンが押された時に直接ザッピングを実行する
 * @param elm : %[jump:"~"] もしくは %[swap:"~"] を指定。詳細は [zapping_jump] のオプションを参照のこと
 */
function _button_zapping_direct(elm);

exp式で直接ザッピングしたいケースで利用してください。

上記2関数とも,必要に応じて zapping.ks の上記関数のデフォルト引数を調整してください。

その他

  • 現在のパート名を調べたい場合は,TJS式にて kag.zappingScenarioPlugin.getCurrent() を実行してください

ライセンス

吉里吉里Zのライセンスに準拠してください。

;/*-------------------------------------------------------------
;// Simple Zapping Scenario KAG-Plugin
[iscript]
;KAGLoadScript("zapping.ks") if typeof kag.zappingScenarioPlugin == "undefined";
[endscript]
[macro name=zapping_jump ][eval exp="kag.zappingScenarioPlugin.command_jump (kag.conductor.mp)"][endmacro]
[macro name=zapping_option][eval exp="kag.zappingScenarioPlugin.command_option(kag.conductor.mp)"][endmacro]
[return]
;// zapping option sample
*sample_zapping
[zapping_option init]
[zapping_option scenario="a" storage="part_a.ks" target=*a_first]
[zapping_option scenario="b" storage="part_b.ks" target=*b_first]
[zapping_option indepflags="indep_a:indep_b"]
[return]
;// _button_zapping_call sample
*button_zapping
[zapping_jump swap="a:b"]
[return]
[if exp=false]
;*/
//--------------------------------------------------------------
// sample implementation
// indirect call ( [uibutton_definition exp=_buton_zapping_call()], etc.)
function _button_zapping_call(use_extra=true, storage="zapping.ks", target="*button_zapping") {
if (use_extra && !kag.usingExtraConductor) {
kag.callExtraConductor(storage, target);
} else with (kag.conductor) {
// pseudo-scenario call
.callLabel("");
.loadScenario(storage);
.goToLabel(target);
}
}
// direct execution ( [uibutton_definition exp=_buton_zapping_direct()], etc.)
function _buton_zapping_direct(elm = %[ "swap" => "a:b"/*default behavior*/ ]) {
kag.zappingScenarioPlugin.command_jump(...);
}
//--------------------------------------------------------------
kag. zappingScenarioPlugin
= new ZappingScenarioPlugin(kag);
class ZappingScenarioPlugin extends KAGPlugin {
function ZappingScenarioPlugin(kag) {
// super.KAGPlugin();
kag.addPlugin(this);
this.kag = kag;
}
function finalize {
this.kag.removePlugin(this) if (isvalid this.kag);
// super.finalize(...);
}
//------------------------------
var kag, debuglog;
var current;
var stateMap = %[]; // zapping states
var scenarioMap = %[]; // first scenarios
var indepFlags; // copy pflag keys
var _keyvalues = [
"current" => this._strvoid,
"stateMap" => this._dict,
"scenarioMap" => this._dict,
"indepFlags" => this._strvoid,
];
function _foreachKeyValues(list = this._keyvalues, cb, *) {
for (var i = 0, cnt = list.count; i < cnt; i+=2) {
var key = list[i], fix = list[i+1];
if (key != "") cb(key, fix, *);
}
}
function _strvoid(v) { return v != "" ? (string)v : void; }
function _dict(v) { return v instanceof "Dictionary" ? v : %[]; }
//------------------------------
// KAGPlugin callbacks
function onStore(f) {
_foreachKeyValues(void, function (key, fix, elm) { elm[key] = fix(this[key]); } incontextof this, f.zapping = %[]);
}
function onRestore(f) {
_foreachKeyValues(void, function (key, fix, elm) { this[key] = fix(elm[key]); } incontextof this, _dict(f.zapping));
}
//------------------------------
// util methods
function tagNormalize(st) { return ((string)st).toLowerCase(); }
function getCurrent() { return this.current; }
function hasState(st) { return st != "" && this.stateMap[tagNormalize(st)] !== void; }
function copyFlags(f) {
var div = ((string)this.indepFlags).split(":",,true);
if (!div.count) return null;
var r = %[];
while (div.count > 0) {
var key = div.pop();
if (key != "") r[key] = f[key];
}
return r;
}
//------------------------------
// [zapping_option init]
// [zapping_option scenario="scn1" storage="..." target="..."]
// [zapping_option resetallscn]
// [zapping_option clearstate="scn1:scn2:..."]
// [zapping_option indepflags="flag1:flag2:...:flagN"]
// [zapping_option current="scenario"]
// [zapping_option debug=true|false]
function command_option(elm) {
if (elm.debug !== void) this.debuglog = elm.debug ? Debug.message : void;
if (elm.init) this.onRestore(%[]);
if (elm.resetallscn) (Dictionary.clear incontextof this.scenarioMap)();
if (elm.scenario != "") {
var map = this.scenarioMap, tag = tagNormalize(elm.scenario);
if (elm.storage != "" || elm.target != "") map[tag] = elm.storage+"|"+elm.target;
else delete map[tag];
}
if (elm.clearstate != "") {
var div = ((string)elm.clearstate).split(":",, true);
for (var i = 0; i < div.count; ++i) {
var st = div[i];
if (st != "") delete this.stateMap[tagNormalize(st)];
}
}
if (elm.indepflags !== void) this.indepFlags = _strvoid(elm.indepflags);
if (elm.current !== void) this.current = _strvoid(elm.current);
return 0;
}
//------------------------------
// [zapping_jump swap="scn1:scn2:..." or jump="scnX"]
function command_jump(elm) {
if (elm.swap != "") swapStateOrJumpFirst (elm.swap);
else changeStateOrJumpFirst(elm.jump);
return 0;
}
function swapStateOrJumpFirst(swap) {
var div = ((string)swap).split(":",, true);
if (!div.count) throw new Exception("zapping: empty swap option");
var idx = div.find(this.current);
if (debuglog) debuglog("@zapping_debug swap="+swap, "current="+this.current, "index="+idx);
if (idx < 0) idx = 0;
else idx = (idx + 1) % div.count;
return changeStateOrJumpFirst(div[idx]);
}
function changeStateOrJumpFirst(to) {
if (to == this.current) {
if (debuglog) debuglog("@zapping_debug ignore="+to, "current="+this.current);
return;
}
if (changeState(to) || to == "") return;
var jumpto = this.scenarioMap[tagNormalize(to)];
if (debuglog) debuglog("@zapping_debug jump="+to, "current="+this.current, "jumpto="+jumpto);
if (jumpto == "") throw new Exception("zapping: empty initial scenario: "+to);
var div = ((string)jumpto).split("|");
var jump = function {
var self = this[0];
self.current = this[1];
self.kag.process(this[2], this[3]);
} incontextof [ this, tagNormalize(to), div[0], div[1] ];
if (this.kag.usingExtraConductor) {
this.kag.onExtraConductorReturn <-> jump;
if (jump !== void) throw new Exception("kag.onExtraConductorReturn already used");
} else {
jump();
}
}
function changeState(to) {
backupState(this.current);
return restoreState(this.current = tagNormalize(to));
}
function backupState(st) {
if (st == "") return false;
if (debuglog) debuglog("@zapping_debug backup="+st);
var backup = this.stateMap[tagNormalize(st)] = %[];
(Dictionary.assignStruct incontextof backup)(this.kag.pcflags);
delete backup.zapping;
backup.zapflags = copyFlags(this.kag.pflags);
return true;
}
function restoreState(to) {
var restore = to != "" ? this.stateMap[tagNormalize(to)] : void;
if (restore === void) return false;
if (debuglog) debuglog("@zapping_debug restore="+to);
var pcflags = this.kag.pcflags;
var pflags = this.kag.pflags;
(Dictionary.assignStruct incontextof pcflags)(restore);
var f = pcflags.zapflags;
delete pcflags.zapflags;
this.onStore(pcflags); // pcflags.zapping restored
(Dictionary.assignStruct incontextof pflags)(this.kag.flags); // copy flags->pflags
if (f) (Dictionary.assign incontextof pflags)(f, false); // override independent flags
// load zapping state data
this.kag.restoreFlags();
return true;
}
}
//[endif]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment