SaveAnyware Plugin 「どこでもセーブプラグイン改」 Ver 0.2 2009/09/08 by KAICHO 1. はじめに 「どこでもセーブプラグイン改」は、KAGスクリプト中で明示的に セーブポイント(*label|commentね)を書かなくてもセーブが できるようになるプラグイン。同名で頒布されていたはずだが 最近入手困難になったらしい(※我輩はオリジナルを知らない)ので、 自分で作ってみた。オリジナルと違うのは以下の通り(…のハズ)。 1) 既読判定がちょっくら賢くなった 2) [call]タグに気持ち悪く対応 3) 右クリックサブルーチンなど、extraConductorの後も使用可能 4) 拡張call(後述)が使えるようになった 2. 使い方 SaveAnywhere.ks を data/scenario/ ディレクトリ以下に放り込み、 first.ksの頭で、以下のように読み込むことで使用可能になる。 [call storage="SaveAnywhere.ks"] 以降、[label]と書くと、その部分がセーブ可能ポイントとなる。実際には 改ページ毎にセーブさせたいだろうから、以下のようなマクロを書いて、 ; 改ページマクロ[np]。セーブポイントにもなる。 [macro name="np"] [p] [cm] [label] [cm] [endmacro] ↑[cm]が二つあるのは、セーブ前にテキストをクリアするため。  こうしないとロードした時に一瞬テキストが表示されて消去されたりする。 あとはゲームスクリプト中から以下のように使用するのが吉。 *start|開始 改ページします。改ページ直後でセーブ可能です。 [np] どうですかお客さん。 [np] [label]を含むタグ(ここでは[np])は、単独行に配置することをお勧め する。パッチ適用の前後でセーブデータが使えるようにするため(後述)。 3. 制限 ・extraConductor中でセーブはできない(想定してないのでどうなるか不明)。  どーしてもextraConductor中でセーブしたい人が居れば知らせて欲しい。 ・後述する拡張callを使用すると、既読スキップした時に、読んだページの  「次のページ」までスキップされることがある。具体的には、その  ページ中に[call]が使われており、前回それを呼んでいた時。いやなら  拡張callをOffにすること。 4. 拡張callとは 拡張callは、call直前に既読判定フラグを立て、直後に新しい既読 判定ラベルを挿入することで、「callの前後の既読判定をより自然に 行えるようにしたもの」。そんなことしていいのかという話は とりあえず無視。 以下のようなシナリオで具体例を述べる。興味ない人は読まなくてよし。 以下のシナリオでは、デフォルトKAGの場合、既読判定に使われるラベルが 保存される順番は、"*target"→"*label1"になる。[call]は呼び出し前の ラベル(ここでは*lable1)を、[call]が戻った次のラベルまで既読と 判断しないため。ついでに、この例では"*label2"は既読ラベルに入らない。 *label1|メイン abc[np] [call *target] cde[np] *label2|おわり hij [s] *target|サブルーチン efg[np] [return] これで困るのは、*target通過後"efg"の部分でセーブすると、*label1も targetも未読と判定されること。"cde"でセーブすると、targetは既読、 label1は未読となる。従って、"cde"でセーブした場合、既読スキップ しても"abc"で停止するが、その先"efg"はスキップされちゃうという ちょっと不思議な動作になる。 拡張callはこれをなんとかし、"cde"でセーブされても、無理矢理 label1を既読としてしまう。いいのか。本当にいいのか。 おまけとして、デフォルトKAGでは、"cde"でセーブされた時の kag.currentPageNameは"サブルーチン"になるが、より自然に、 ということで、"メイン"になるようにしてある。…いいのかなあ。 5. 四方山: オリジナルのどこでもセーブプラグインは、rclickを呼んだ後に kag.currentLabelやkag.currentRecordNameが壊れることがあったらしい けれど、この「改」では壊れない(はず)。オリジナルでは[return]が あったら必ずラベル末尾のカウンタをカウントアップしてたみたい。 それ困る。 そんかし、「改」では、ConductorクラスのonReturnやらonCallやらを フックしまくる気持ちの悪いプログラムになっている。良い子はまねを しないでね。 6. 内部のお話: KAGのセーブと既読判定は、実は全然別のものだったりする。 1) セーブは、セーブ可能ラベルを通過した時に、その状態を保存しておいて セーブ時にそれをガバンとファイルに書き出すことによって実現されてる 2) 既読判定は、sf[kag.currentRecordName]の真偽を判定している 2) incRecordLabel()で現在のkag.currentRecordNameをセーブする。 3) setRecordLabel( storage, label )で新しいkag.currentRecordNameを作成 セーブは、ラベルを書けばほっといてもセーブ可能と判断されるが、 既読判定は、kag.currentRecordName、kag.currentLabelなどが複雑に絡むため 結構難しい。しかも、onReturn()やonAfterReturn()なんかでことごとく KAG自身に上書きされちゃうので、そいつらをちゃんとフックしておかないと 自分が設定したい既読判定ができない。このあたりまだバグがあるかも しれないので、何かあればお知らせ頂けると我輩が喜びますかなり。 7. パッチ作成時の注意点 どこでもセーブプラグインを使うと、パッチを当てたり、シナリオファイルを 変更した場合に、変更後にセーブデータが使用できないことがある。 エラーが発生しました ファイル : first.ks 行 : ### タグ : 不明 ( ← エラーの発生した前後のタグを示している場合もあります ) シナリオファイルに変更があったため return の戻り先位置を特定できません こんなエラーメッセージを見たことがある方も居るはず。これは、どこでも セーブプラグインが、「call先のラベル」でセーブを実現しているため、 シナリオファイルが変更されてcall元の位置(ファイル中での行数及び 行頭からの文字数で記録されている)が変わってしまった場合に、[return]で どこに戻っていいかわからなくなるためだ。 逆に言えば、「call元の位置を変更しなければ」セーブデータはそのまま 流用できるはず。実際、パッチ適用の前後で、シナリオファイルについて、 以下の条件を守ることで、パッチ適用前後のセーブデータの一貫性を 保つことが可能になる。 #本来なら、合わせて、セーブ可能ラベルが[call][label]の前後で #変更にならないようにする必要があるが、拡張callを使うと不可能だし、 #そもそもその箇所を通過後のセーブデータを救う意味はあまりないので、 #ラベルが変更になることは許容する。許容すると、パッチ適用前後の #セーブデータ間で、「同じ場所でセーブしてるのに既読判定ラベル名が #違う」ことが起こる。まぁ…些細なことではなかろうか。 セーフな例([np]は上で定義したマクロとする): *start|スタート abc [np] bcd [np] [s] この↑シナリオをこのよう↓に変更してもセーフ *start|スタート abc[if eval="sf.フルコンプ"]フルコンプしてるやん。[endif] [np] [call target="*cde"]CDEを呼ぶように変更 [np] [s] *cde|CDE cde [np] [return] アウトな例: *start|スタート abc [np] bcd [np] [s] この↑シナリオをこのよう↓に変更したらアウト *start|スタート abc [np] 追加行 ← [label]が増えるのでダメ [np] bcd [np] [s] この例では、当然abcの後の[np]でセーブされているデータは、フルコンプ していようがいまいが、パッチ適用してもフルコンプメッセージは表示され ないし、bcdの後の[np]でセーブされているデータでは、どうあがいても *cdeは呼び出されないことに注意。