// Conductor.tjs - KAG シナリオ進行処理
// Copyright (C)2001-2005, W.Dee and contributors 改変・配布は自由です
@if(kirikiriz)
Plugins.link("KAGParser.dll");
@endif
class ConductorException extends Exception
{
// ConductorException - Conductor がタグハンドラを処理中に発生した例外を
// 投げる時に使われる例外クラス
function ConductorException() { super.Exception(...); }
function finalize() { super.finalize(...); }
};
class BaseConductor extends KAGParser
{
// BaseConductor - シナリオ進行処理のベースクラス
var timer;
var oneshot;
var _interrupted = false; // 中断中か
var timerEnabled = false; // タイマが起動中か
var pendings; // 後回しにされたタグ
var inProcessing = false; // timerCallback を処理中かどうか
var reentered = false; // timerCallback 中に 再入したか
var nextTimerTick = 0; // 次にタイマーが発動されるはずの tick
function BaseConductor()
{
// コンストラクタ
super.KAGParser(...);
timer = new Timer(timerCallback, '');
// Timerの第二引数に空文字列を指定すると
// 第1引数に指定した関数を直接呼び出すようになる
oneshot = new AsyncTrigger(timerCallback, '');
// これも同様
oneshot.cached = true; // イベントのキャッシュを有効に
pendings = [];
}
function finalize()
{
// finalize()
invalidate timer;
invalidate oneshot;
super.finalize(...);
}
function clear()
{
// clear オーバーライド
pendings.clear();
super.clear();
}
function timerCallback()
{
// 次の要素を得る
nextTimerTick = timer.interval + System.getTickCount();
var obj;
try
{
if(inProcessing)
{
// 再入
reentered = true;
timer.interval = 0;
return;
}
inProcessing = true;
for(;
{
if(pendings.count > 0)
{
// 後回しにされたタグがある場合
obj = pendings[0];
pendings.erase(0);
}
else
{
// 後回しにされたタグがないので次のタグを得る
obj = getNextTag(); // 次のタグを得る
// getNextTag() の中で、pendings に追加された (iscript など)
if(pendings.count > 0)
{
pendings.add(obj);
continue;
}
}
if(obj === void)
{
// シナリオ終了
timer.enabled = false;
timerEnabled =false;
onStop();
inProcessing = false;
reentered = false;
return;
}
else
{
// onTag を呼ぶ
var step = onTag(obj);
if(step === void)
throw new Exception(__("onTag が void を返しました (%s)( おそらくタグハンドラの戻り値を返し忘れた )").sprintf(obj.tagname));
step = int step; // step を数値に
if(step == 0)
{
// ウェイトを掛けずに次へ
timer.interval = 0;
continue;
}
else if(step < 0)
{
switch(step)
{
case -5: // いったんイベントを処理(現在のタグは後回し)
pendings.insert(0, obj);
oneshot.mode = atmAtIdle;
oneshot.trigger(); // トリガ
timer.interval = 0; // タイマは停止
inProcessing = false;
reentered = false;
return;
case -4: // いったんイベントを処理
oneshot.mode = atmAtIdle;
oneshot.trigger(); // トリガ
timer.interval = 0; // タイマは停止
inProcessing = false;
reentered = false;
return;
case -3: // 後回ししてブレーク
pendings.insert(0, obj);
timer.interval = 0; // タイマは停止
inProcessing = false;
reentered = false;
return;
case -2: // ブレーク
timer.interval = 0; // タイマは停止
inProcessing = false;
reentered = false;
return;
case -1: // シナリオ終了
timer.interval = 0;
timer.enabled = false;
timerEnabled = false;
onStop();
inProcessing = false;
reentered = false;
return;
}
}
else
{
// 次へ
if(timer.interval != step)
{
timer.interval = step;
nextTimerTick = step + System.getTickCount();
}
inProcessing = false;
reentered = false;
return;
}
}
}
inProcessing = false;
reentered = false;
}
catch(e)
{
// Debug.logAsError();
timer.enabled = false;
timerEnabled =false;
onStop();
inProcessing = false;
var msg = "エラーが発生しました\n"
"ファイル : " + curStorage + " 行 : " + (curLine+1) + "\n"
"タグ : " + (obj === void ? "不明" : obj.tagname)
+ " ( ← エラーの発生した前後のタグを示している場合もあります )\n"
+ e.message;
if((typeof e.trace) != "undefined") dm("trace : " + e.trace);
dm(msg);
throw new ConductorException(msg);
// System.inform(msg, "エラー");
}
}
function onTag()
{
// オーバーライドすること
return -1;
}
function onStop()
{
// (シナリオの)停止時に呼ばれる。
// stop() から呼ばれるわけではない。
// オーバーライドすること。
}
function startProcess(immediate = false)
{
// シナリオ進行開始
// immediate = false の場合は非同期で実行を開始するので、
// このメソッド内でタグハンドラが呼ばれることはない
// 次のイベント配信のタイミングで最初のタグハンドラが呼ばれる。
// immediate = true の場合は、このメソッド内で初回のタグハンドラが
// 処理されるため、呼び出し側はこのメソッドの実行が終わったら
// すぐに吉里吉里に制御を戻す(すべての関数から抜ける)ようにするべき。
resetInterrupt();
timer.interval = 0; // 初期インターバル
timerEnabled = true;
if(!_interrupted)
{
timer.enabled = true; // タイマー開始
if(immediate)
{
timerCallback();
}
else
{
oneshot.mode = atmExclusive;
// イベントが配信されるまで他の非同期イベントをブロック
oneshot.trigger(); // トリガ
}
}
}
function start()
{
// タイマ開始
timerEnabled = true;
timer.enabled = true;
}
function stop()
{
// タイマ停止
timer.enabled = false;
timerEnabled = false;
}
property interrupted
{
getter() { return _interrupted; }
setter(x)
{
if(!x)
{
// enable
if(timerEnabled)
{
timer.interval = 0;
timer.enabled = true;
oneshot.mode = atmExclusive;
// イベントが配信されるまで他の非同期イベントをブロック
oneshot.trigger(); // トリガ
}
}
else
{
// disable
oneshot.cancel();
timer.enabled = false;
}
_interrupted = x;
}
}
function assign(src)
{
// src の状態をこのオブジェクトにコピー
var t = timer;
var st = src.timer;
t.enabled = false;
t.interval = st.interval;
nextTimerTick = src.nextTimerTick;
if(st.enabled && st.interval != 0)
{
// タイマ interval の調整
var delta = nextTimerTick - System.getTickCount();
if(delta > 0) t.interval = delta; else t.interval = 1;
}
t.enabled = st.enabled;
timerEnabled = src.timerEnabled;
_interrupted = src._interrupted;
if(src.pendings.count > 0)
pendings.assignStruct(src.pendings);
else
pendings.clear();
super.assign(src);
}
function store()
{
// store オーバーライド
return super.store(...);
}
function restore(dic)
{
// restore オーバーライド
super.restore(...);
pendings.clear();
}
function loadScenario(/*file*/)
{
// loadScenario オーバーライド
pendings.clear();
//dm("loadScenario: "+file);
super.loadScenario(...);
}
function goToLabel()
{
// goToLabel オーバーライド
pendings.clear();
super.goToLabel(...);
}
function enqueueTag(tag)
{
pendings.add(tag);
}
var dash_character = "―";
var block_character = "■";
// Blech. TJS2 "String" class is /supposed/ to have a .repeat() method.
function dashrepeat(count, character)
{
var dashes = "";
for (var i=0;i<count;i++) {
dashes += character;
}
return dashes;
}
function replaceLine(text, reg, character=dash_character) {
var results = reg.exec(text);
while(results.count == 2) {
var count;
if (results[1] == "") {
count = 1;
} else {
count = results[1];
}
text = text.replace(reg,dashrepeat(count, character));
results = reg.exec(text);
}
return text;
}
function cleanLine(line=curLineStr) {
if (isInQuiz()) {
return quizdata.question;
}
if (line.length > 0 && line[0] == "@") {
return "";
}
line = replaceLine(line,/\[line len=([0-9]+)]/); //Replace line-tags with dashes
line = replaceLine(line,/\[line([0-9]*)]/); //Replace slightly-different-line-tags with dashes
line = replaceLine(line,/\[block len=([0-9]+)]/,block_character);
line = line.replace(/\["[a-zA-Z0-9_-]*"]/g,""); //Remove arrays with string quotes indexes (not sure if possible but just in case)
line = line.replace(/\['[a-zA-Z0-9_-]*']/g,""); //Remove arrays with string apostrophes indexes (not sure if possible but just in case)
line = line.replace(/\[[0-9]*]/g, ""); //Remove arrays with number indexes (required or it will break when there is nested []s
line = line.replace(/\[[^]]*]/g, ""); //Remove tags
return line;
}
function isInQuiz() {
return curStorage == 'QuizSystem.ks' && owner.currentLabel == '*quiz_question';
}
var conductorPrevStorage;
var conductorPrevLine;
var conductorPrevLineStr;
var lastQuizdataQuestion;
function isNewLine() {
if (curStorage != conductorPrevStorage || curLine != conductorPrevLine || (isInQuiz() && lastQuizdataQuestion != quizdata.question)) {
conductorPrevStorage = curStorage;
conductorPrevLine = curLine;
conductorPrevLineStr = curLineStr;
if (isInQuiz() && lastQuizdataQuestion != quizdata.question) {
lastQuizdataQuestion = quizdata.question;
}
return true;
}
return false;
}
}
class Conductor extends BaseConductor
{
// Conductor - シナリオ進行処理
/*const*/ var mStop = 0; // 停止
/*const*/ var mRun = 1; // 動作中
/*const*/ var mWait = 2; // 待ち
var owner;
var handlers;
var status = mStop;
var timeOutTimer;
var waitUntil = %[];
var lastTagName = ''; // 直前のタグ名
function Conductor(owner, handlers)
{
// コンストラクタ
super.BaseConductor();
ignoreCR = global.ignoreCR;
debugLevel = tkdlVerbose;
this.owner = owner;
this.handlers = handlers;
timeOutTimer = new Timer(onTimeOut, '');
}
function finalize()
{
// finalize()
invalidate timeOutTimer;
super.finalize(...);
}
function run(immediate = false)
{
// 実行の開始
// immediate=true の場合は、
// このメソッドを実行したらすぐに吉里吉里に制御を戻す
// (すべての関数から戻る)こと
status = mRun;
startProcess(immediate);
}
function sleep()
{
// 実行の停止
status = mStop;
stop();
}
function wait(until)
{
// 待ち
// until = trigger で用いるシグナル名とコールバック関数の
// 辞書配列
status = mWait;
stop();
(Dictionary.assign incontextof waitUntil)(until);
}
function waitWithTimeOut(until, timeout)
{
// 待ちを行うが、タイムアウトがある
// タイムアウト時には 'timeout' がトリガされるので
// ハンドラを定義すること。
if(timeout == 0) timeout = 1; // timeout が 0 の場合は 1 に
status = mWait;
stop();
(Dictionary.assign incontextof waitUntil)(until);
timeOutTimer.interval = timeout;
timeOutTimer.enabled = true;
}
function onTimeOut()
{
// timeOutTimer がタイムアウトした
timeOutTimer.enabled = false;
trigger('timeout'); // 自分自身で timeout をトリガする
}
function trigger(name)
{
// waitUntil 内にシグナル名 name が存在すれば、実行再開、
// 同時に waitUntil に登録されたメソッド(リスタートハンドラ)を呼ぶ
// シグナル名に _arg がついたものが waitUntil 内にあれば、
// それを引数としてハンドラに渡す
// waitUntil はクリアされる
if(status != mWait) return false;
var func = waitUntil[name];
if(func !== void)
{
var arg = waitUntil[name + '_arg'];
if(arg !== void) func(arg); else func();
(Dictionary.clear incontextof waitUntil)();
run();
return true;
}
else
{
return false;
}
}
function onTag(elm)
{
// タグの処理
var tagname = elm.tagname;
var handler = handlers[tagname];
if(handler !== void)
{
var ret = handler(elm);
lastTagName = tagname;
return ret;
}
return onUnknownTag(tagname, elm);
}
function onStop()
{
// BaseConductor.onStop オーバーライド
// 停止時に呼ばれるのでステータスを mStop にする
status = mStop;
if(owner.conductor == this) handlers.s(); // ハンドラの s (停止) を呼ぶ
}
function onScript(script, scriptname, lineofs)
{
// scirpt を実行する
try
{
Scripts.exec(script, scriptname, lineofs);
}
catch(e)
{
throw new Exception(__("%s の 行 %s から始まる iscript ブロックでエラーが発生しました。\n( 詳細はコンソールを参照してください )\n%s").sprintf(scriptname, lineofs, e.message));
}
return true;
}
function store()
{
// store オーバーライド
return super.store(...);
}
function restore(dic)
{
// restore オーバーライド
super.restore(...);
lastTagName = '';
}
function onScenarioLoad()
{
conductorPrevStorage = void;
conductorPrevLine = void;
conductorPrevLineStr = void;
lastQuizdataQuestion = void;
return owner.onConductorScenarioLoad(...);
}
function onScenarioLoaded()
{
return owner.onConductorScenarioLoaded(...);
}
function onLabel()
{
return owner.onConductorLabel(...);
}
function onJump()
{
return owner.onConductorJump(...);
}
function onCall()
{
return owner.onConductorCall(...);
}
function onReturn()
{
return owner.onConductorReturn(...);
}
function onAfterReturn()
{
return owner.onConductorAfterReturn(...);
}
function onScript()
{
return owner.onConductorScript(...);
}
function onUnknownTag()
{
return owner.onConductorUnknownTag(...);
}
}