//;# MainWindow.tjs - KAG メインウィンドウ
//;# Copyright (C)2001-2008, W.Dee and contributors  改変・配布は自由です
//;<<'END_OF_TJS_SCRIPT';

// このスクリプトは有効な perl5 スクリプトでもある

var SKIP_NONE = 0;
var SKIP_CLICK = 1;
var SKIP_PAGE  = 2;
var SKIP_STOP  = 3;
var SKIP_FAST  = 4;
var SKIP_LABEL = 5;
var SKIP_CANCEL = 6;
var SKIP_FORCE  = 7;

var LINEMODE_NONE = 0;
var LINEMODE_PAGE = 1;
var LINEMODE_LINE = 2;
var LINEMODE_VN   = 3;
var LINEMODE_TEX  = 4;
var LINEMODE_FREE = 5;

try { Plugins.link("windowEx.dll"); } catch {}

class KAGWindow extends Window
{
	// KAG のウィンドウ クラス
	// KAG の動作のメインな部分はここに記述してある

	// 以下のうち、/*C*/ のつく変数は、末端の perl スクリプトによって
	// 自動的に栞にコピーされるコードが生成される変数

	var scWidth = 640; // 画面横幅
	var scHeight = 480; // 画面縦幅
	var exWidth, exHeight; // 拡張画面サイズ⇒sysbase/fore.base/back.baseレイヤサイズ
	var pxWidth, pxHeight; // 現在のサイズ（sc〜exまでの値）

	var aboutWidth = 320; // 「このソフトについて」ウィンドウの横幅
	var aboutHeight = 200; // 同縦幅
	var aboutStorage = "about.ks";

    var defaultName   = "名前"; // 標準名    ワールド拡張がボイス切り替えに利用
    var defaultFamily = "苗字"; // 標準苗字  ワールド拡張がボイス切り替えに利用
	var clearFaceImage; // 表情窓を消す画像の指定
    
	var isFirstProcess = true; // 一番最初の process の呼び出しかどうか

	var freeSaveDataMode = false; // 栞をメニューバーなどで管理せずに、個別のファイルとして管理する
	var saveThumbnail = false; // サムネイルを保存するかどうか
	var thumbnailWidth = 133; // サムネイルの横幅
	var thumbnailDepth = 8; // サムネイルのBMPモード。8か24
		// サムネイルの フルカラー化およびサイズフリー化のパッチは
		// ゆん氏からいただきました。
		// この場を借りてお礼申し上げます。

	var snapshotLayer = void; // 画像のスナップショットを一時的に保存するレイヤ
	var snapshotLockCount = 0; // ロックカウント
	var snapshotHideMessage = false; // スナップショット取得時にメッセージレイヤを一時消去するか

	var lastSaveDataNameGlobal = ""; // 最後に保存したフリーセーブモードでのファイル名
	/*C*/var lastSaveDataName = ""; // 最後に保存したフリーセーブモードでのファイル名

	var saveDataLocation = System.dataPath; // セーブデータ保存場所

	var saveDataID = "00000000-0000-0000-0000-000000000000"; // セーブデータの ID

	var readOnlyMode = false; // 読み込み専用モード(データをディスクに書かない)
	var dataName = "data"; // セーブデータ名
	var saveDataMode = ""; // データ保存モード( "c" で暗号化 )

    var recordHistoryOfStore = 0; // 通過履歴を記録するかどうか
		// 0 = 自動的には記録しない
        // 1 = 保存可能なラベルごと
		// 2 = 選択肢 ( @s タグ ) ごと
		// 3 = 選択肢 ( @select / @mselect タグ ) ごと
    
	var maxHistoryOfStore = 1; // 通過記録の最大数
	var historyOfStore = []; // 通過履歴データ
	var nextRecordHistory = false;
		// 次の保存可能なラベル通過時に現在の情報を保存するかどうか
    
	var stablePosibility = false;
		// 栞を保存可能なラベル間で stable になる可能性があるかどうか

	var fullScreened = false; // 現在フルスクリーンかどうか

	var isMain = true; // これがメインウィンドウかどうか

	var askOnClose = true; // 終了時に終了するかをユーザに聞くかどうか

	var helpFile = ""; // 「ヘルプ > 目次」で開くファイル

	var quakeTimer; // quake 用のタイマ
	var defaultQuakeTimeInChUnit = false;
    var quakeMessageLayer = false; // メッセージレイヤを quake対象にするか
    /*C*/var quaking = false; // 揺れているか
	/*C*/var quakeEndTick = 0; // 揺れを終了させる tick
	/*C*/var quakeHorzMax = 0; // 横振幅
	/*C*/var quakeVertMax = 0; // 縦振幅
	/*C*/var quakePhase = 0;

	var chDefaultAntialiased; // 文字にアンチエイリアスをかけるかどうか
	var chDefaultFace; // 文字のデフォルトのフォント
    var initialMessageLayerVisible = true;

	var _textWriteEnabled = true; // テキスト描画有効
	var _backVoiceMode = false; // バックボイス処理中。テキストを描画しない

	// テキスト書き出しが有効
	property textWriteEnabled {
		getter() {
			return _textWriteEnabled && !_backVoiceMode;
		}
	}
	
    var historyLayer; // メッセージ履歴レイヤ
	/*C*/var historyWriteEnabled = true; // メッセージレイヤに文字を出力するか
	/*C*/var historyEnabled = true; // メッセージ履歴レイヤを表示可能か
    var historyShowing = false; // メッセージ履歴レイヤを表示中か
	var historyLayerAbsolute = 4000000;
	var lastHistoryHiddenTick = 0; // 最後に履歴レイヤが非表示になったときの tick

	// 選択肢前のオート・スキップの状態
    var selectPrevAutoMode;
    var selectPrevSkipMode;
    var selectPrevTempSkip;
    
    var _selectLayer;           // 選択肢レイヤ
    var selectShowing = false; // 選択肢レイヤが表示中か
	var selectLayerAbsolute = 1000000;

    property selectLayer {
        getter() {
            if (_selectLayer === void) {
                // 選択肢レイヤの作成
                _selectLayer = new SelectLayer(this, fore.base);
                _selectLayer.name = "選択肢レイヤ";
                add(_selectLayer);
            }
            return _selectLayer;
        }
    }
    
    var _mapSelectLayer;           // マップ選択肢レイヤ
    var mapSelectShowing = false; // マップ選択肢レイヤが表示中か
	var mapSelectLayerAbsolute = 1000000;

    property mapSelectLayer {
        getter() {
            if (_mapSelectLayer === void) {
                // マップ選択肢レイヤの作成
                _mapSelectLayer = new MapSelectLayer(this, fore.base);
                _mapSelectLayer.name = "マップ選択肢レイヤ";
                add(_mapSelectLayer);
            }
            return _mapSelectLayer;
        }
    }
    
    var _transLayer; // 画面切り替え処理用レイヤ
    var transShowing = false; // 画面切り替えレイヤが表示中か

    property transLayer {
        getter() {
            if (_transLayer === void) {
                _transLayer = new TransLayer(this);
                add(_transLayer);
            }
            return _transLayer;
        }
    }
    
	/*C*/var numCharacterLayers = 0; // 前景レイヤの数
	/*C*/var numMessageLayers = 1; // メッセージレイヤの数

    /*C*/var messageFadeTime = 500; // メッセージレイヤの表示ON/OFF の時間

    var sysbase;  // ベースレイヤ
	var _primaryLayer;
	property primaryLayer { getter { return sysbase; } };
	var exEventEnabled = false; // registerExEvent されたかどうか

	var _debugwin; // デバッグ表示用レイヤ
	property debugwin {
		getter() {
			if (_debugwin === void) {
				// デバッグ表示用
				_debugwin = new Layer(this, primaryLayer);
				_debugwin.name = "デバッグ情報レイヤ";
				_debugwin.setSize(scWidth, 50);
				if (typeof global.debugwintop !== "undefined" && global.debugwintop) {
					_debugwin.setPos(0, 0);
				} else {
					_debugwin.setPos(0, scHeight - _debugwin.height);
				}
				_debugwin.visible = false;
				_debugwin.hitThreshold = 256;
            }
            return _debugwin;
        }
    }
    
    var fore = %[]; // 表レイヤ
	var back = %[]; // 裏レイヤ

	var scPositionX = %[]; // 立ち絵の中心座標(X)

	var tempLayer = void; // 一時的なレイヤ

	var lineBreak; // 行待ち用アニメーションレイヤ
	var pageBreak; // ページ待ち用グリフのアニメーションレイヤ
	var _clickWaiting = false; // クリック待ちかどうか
	property clickWaiting {
		getter { return _clickWaiting; }
		setter(v) {     _clickWaiting = v;
			fore.base.cursor = !v ? cursorDefault : !autoMode ? cursorWaitingClick : cursorWaitingAuto;
		}
	}

	var mainConductor; // メインのコンダクタ
	var extraConductor; // 予備のコンダクタ
	var conductor; // 現在のコンダクタ
	var usingExtraConductor = false; // 予備のコンダクタを使用中かどうか
	var onExtraConductorReturn; // extraConductor から通常のコンダクタに戻るときによぶ関数
	var forceDisableQuakeOnExtraConductor = true; // extraConductor使用中は強制quakeしないフラグ

	var tagHandlers; // タグのハンドラ群辞書配列

	var saveMacros = true; // マクロを栞に保存するかどうか

	var current; // 現在操作中のメッセージレイヤ
	/*C*/var currentNum; // 現在操作中のメッセージレイヤ番号
	/*C*/var currentPage; // 現在操作中のメッセージレイヤのページ(表0/裏1)
	/*C*/var currentWithBack = false; // 裏画面にも文字を描画するかどうか

	// 現在有効なボイス
	var currentVoice;

	// 現在有効なボイス再生スクリプト
	property currentVoiceScript {
		getter() {
			return getVoicePlayingScript(currentVoice, debugWindowEnabled); // デバッグ時はボイス再生でラベル反応する細工
		}
	}
	property hasCurrentVoice {
		getter { return currentVoice !== void && currentVoice.count > 0; }
	}

	// ボイス再生用のスクリプトを取得
	function getVoicePlayingScript(result, mode=false) {
		var ret = "";
		if (result !== void && result.count > 0) {
			ret += "kag.stopAllVoice();";
			for (var i=0;i<result.count;i++) {
				var info = result[i];
				if (info !== void) {
					// 全部通常ボイスとして再生
					ret += @'kag.playVoice("${info.name}","${info.voice}",0,false,${mode})if(kag.isChVoiceOn("${info.name}"));';
				}
			}
		}
		return ret;
	}
	
	var bgm; // BGM オブジェクト

    var numSEBuffers = 1; // 効果音バッファの数
	var numSysSEBuffers = 1; // システム効果音のバッファ数
	var sameSysSEVolume = true; // システム効果音のボリュームをsevolumeと同期する場合はtrue/syssevolumeを使用する場合はfalse
	var se = []; // 効果音バッファオブジェクト

	var numVoiceBuffers = 10;
	var voices = []; // ボイス音バッファオブジェクト

	// システムが使えるSEを返す（ロード時に破壊されない)
	property sysse {
		getter() {
            return se[numSEBuffers];
        }
    }
    
	var numMovies = 1; // ムービーオブジェクト数
//	var movie; // ムービーオブジェクト
	var movies = []; // ムービーオブジェクト
	var independentMovieAudioProperty = false; // ムービー音量を独立させたい場合はtrue, falseの時は bgmvolume に同期

    function isMoviePlaying() {
        for (var i=0;i<numMovies;i++) {
            if (movies[i].canWaitStop) {
                return true;
            }
        }
        return false;
    }

    // 次のテキスト表示の前に全ボイスをカット
	/*C*/var voicecut = false;
    // ボイスをカットのタイミングはページ消去時
    /*C*/var voicecutpage = false;
    // ボイス再生時に bgm をダウンさせる
    /*C*/var bgmdown = false;
    /*C*/var bgmdownlevel = 50; // 下げる率(%)
    
	var transCount; // 現在進行中のトランジションの数
	var moveCount; // 現在進行中の自動移動の数
    
	var chSpeeds = %[
		fast: 10, // 「高速」文字表示スピード
		normal: 30, // 「普通」文字表示スピード
		slow: 50, // 「遅い」文字表示スピード
		];

	var userChSpeed = 30; // ユーザの選んだ文字表示スピード
	var userCh2ndSpeed = -1; // ユーザの選んだ 既読部分の文字表示スピード
	var chNonStopToPageBreak = false; // ページ末まで一気に読み進むか ( l タグを無視するか )
	var ch2ndNonStopToPageBreak = false; // 既読の部分でページ末まで一気に読み進むか
	/*C*/var chUserMode = true; // 文字表示速度は現在ユーザの選んだものか
	/*C*/var chSpeed = 30; // 現在の文字表示スピード
	/*C*/var actualChSpeed = chSpeed; // 実際の文字表示スピード

	/*C*/var beforeNoWaitActualChSpeed; // nowait に入る前の actualChSpeed
	/*C*/var beforeNoWaitChUserMode; // nowait に入る前の chUserMode

	/*C*/var clickSkipEnabled = true; // クリックスキップが有効か
	/*C*/var nextSkipEnabled = true; // 次の選択肢(/未読)まで進むが有効か
	/*C*/var noeffectEnabled = true; // noeffect が有効か
	
	/*C*/var _allskip   = false; // 0:既読のみスキップ 1:全てスキップ
	var  _tempAllskip   = void;  // 非voidで一時的にallskipを上書きする(cancelskipで解除)
	property  allskip {
		getter { return _tempAllskip === void ? _allskip : _tempAllskip; }
		setter(v) {     _tempAllskip  =  void;  _allskip = v; }
	}
    /*C*/var afterauto = false; // 選択後オート   1:継続 0:解除
    /*C*/var afterskip = false; // 選択後スキップ 1:継続 0:解除
    /*C*/var nosewhenskip = false; // 1:スキップ時はSEをならさない

	/*C*/var cancelAutoWhenClick = true; // クリックでauto解除
	/*C*/var cancelSkipWhenClick = true; // クリックでskip解除
	
	var skipNoDispStartTime;
	var _skipNoDisp; // スキップ中に表示しない
	var skipNoDispWin;

	property skipNoDisp {
		getter() {
			return _skipNoDisp;
		}
		setter(v) {
			if (!_skipNoDisp == !v) return;
			if ((_skipNoDisp = !!v) != 0) {
				if (skipNoDispWin === void) {
					skipNoDispWin = new Layer(this, primaryLayer);
					skipNoDispWin.name = "スキップ情報レイヤ";
					skipNoDispWin.setSize(scWidth, 30);
					skipNoDispWin.setPos(0, scHeight - 30);
					skipNoDispWin.hitThreshold = 256;
				}
				dm("次の選択肢まで非表示スキップ開始");
				if (historyWriteEnabled) {
					historyLayer.clearAction();
					historyLayer.reline();
				}
				skipNoDispStartTime = System.getTickCount();
			} else {
				if (skipNoDispWin !== void) {
					invalidate skipNoDispWin;
					skipNoDispWin = void;
				}
				dm("スキップにかかった時間:" + (System.getTickCount() - skipNoDispStartTime) + "(ms)");
			}
			onSkipNoDispChanged(_skipNoDisp);
		}
	}
	var _clearMessageLayerNumOnSkipNoDispChanged = 0;
	function onSkipNoDispChanged(v) {
		if (current !== void && currentNum == _clearMessageLayerNumOnSkipNoDispChanged) {
			if(currentWithBack) current.comp.clear(true);
			current.clear(true);
			if(v && historyWriteEnabled) historyLayer.repage(true);
		}
		callHook("onSkipNoDispChanged", v);
	}

    var _skipMode = SKIP_NONE; // スキップのモード
	// SKIP_NONE スキップなし, SKIP_CLICK クリック待ち記号まで, SKIP_PAGE 改ページ待ち記号まで
	// SKIP_STOP 次の停止まで SKIP_FAST 早送り SKIP_LABEL 次のラベルまで強制スキップ
	// SKIP_CANCEL 次のスキップ解除まで強制スキップ
	// SKIP_FORCE 強制処理用
	property skipMode {
		getter() {
			return _skipMode;
		}
		setter(v) {
			_skipMode = v;
			if (v == SKIP_NONE && skipNoDisp) {
				skipNoDisp = false;
				// プラグインにシンクロ指示
				forEachEventHook('onSync');
				setMenuAccessibleAll();
			}
			skipToLabelStop = ""; // 強制リセット

			// autoLabelStore強制クリア
			autoLabelStore.clear() if (v >= SKIP_STOP && autoLabelStore !== void);
			updatePlayMode();
		}
	}

	var playMode;
	function updatePlayMode() {
		var newPlayMode = skipMode >= SKIP_STOP ? 2 : autoMode ? 1 : 0;
		if (playMode != newPlayMode) {
			forEachFunctionHook('onPlayModeChange', newPlayMode, playMode);
			playMode = newPlayMode;
		}
	}
	
	// 強制実行時の画面復帰用のカウンタ
	var skipCount = 0;
	
	var      checkSkipOnNextLabel   = false;
	/*C*/var enterSkipOnReadedLabel = false; // 既読ラベルで自動スキップ開始するフラグ(nextタグ直後にチェック)
	function checkSkipOnReadedLabel() {
		if (enterSkipOnReadedLabel && !skipMode && getCurrentRead()) {
			if (!callHook("enterSkipOnReadedLabel")) skipToStop(false, false);
		}
	}

	// beginskip 中のスキップモード
	var prevSkipMode;
	// 左クリックでの beginskip を有効にするかどうか
	var enableLeftBeginSkip;
	// SKIP_LABELでのラベル名停止指定
	var skipToLabelStop;

	var nowaitMode = false; // クリック待ちをしないもーどかどうか
	var nowaitModeWait = 350;
	var nowaitModeClickSkip = true; // nowaitmode状態の[p][l]タグ待ちでclickSkipEnabled==false時でも飛ばせるようにするかどうか

	var _autoMode = false; // 自動読みすすみモードかどうか
	property autoMode {
		getter() {
			return _autoMode;
		}
		setter(v) {
			_autoMode = v;
			updatePlayMode();
		}
	}

    var autoModePageWait = 350; // 自動読みすすみモード時の改ページ時のウェイト
	var autoModeLineWait = 50; // 自動読みすすみモード時の行クリック待ち時のウェイト
		// 上の二つは、ノーウェイトにしたい場合は 0 ではなくて -4 を指定すること

	var skipKeyRepeatEnabled = true; // return/space押しっぱなしでスキップ状態にするかどうか
	var skipKeyRepressed = false; // return あるいは space キー ( f キーなどではない )
								// が押されると true になる ( スキップ解除時に false

	var autoModePageWaits = %[
		fast: 400,  // 自動読みすすみモードの改ページ時 ウェイト「短い」
		faster: 700,  // 自動読みすすみモードの改ページ時 ウェイト「やや短い」
		medium: 1000,  // 自動読みすすみモードの改ページ時 ウェイト「普通」
		slower: 1300, // 自動読みすすみモードの改ページ時 ウェイト「やや遅い」
		slow: 2000, // 自動読みすすみモードの改ページ時 ウェイト「遅い」
		];

	var autoModeLineWaits = %[
		fast: 180,  // 自動読みすすみモードの行末 ウェイト「短い」
		faster: 240,  // 自動読みすすみモードの行末 ウェイト「やや短い」
		medium: 300,  // 自動読みすすみモードの行末 ウェイト「普通」
		slower: 360, // 自動読みすすみモードの行末 ウェイト「やや遅い」
		slow: 500, // 自動読みすすみモードの行末 ウェイト「遅い」
		];

	/*C*/var canCancelSkipByClick = true; // クリックによりスキップ状態のキャンセルができるか

	/*C*/var autoWCEnabled = false; // 自動ウェイトが有効かどうか
	/*C*/var autoWCChars = ""; // 自動ウェイトをかける文字
	var autoWCWaits = []; // 自動ウェイトのウェイト

	var timeOrigin; // resetWait で設定された時間原点
	var lastWaitTime; // wait mode=until で実際に待った時間

	var stableHandlers = []; // システムが安定(クリック待ち/停止)したときに呼ばれる
	var runHandlers = []; // システムが走り始めたときに呼ばれる
	var inStable = true; // 安定しているとき(走行中でないとき) true
	var inSleep = false; // s タグで停止しているとき true

	var updateBeforeCh = 0; // 文字を描画する前にいったん画面描画に入るかどうかのカウント

	var messageLayerHiding = false; // ユーザによりメッセージレイヤが隠されているか

	/*C*/var rightClickEnabled = true; // 右クリックが有効か
	/*C*/var rightClickCall = false; // 右クリックで特定のルーチンを呼ぶか
	/*C*/var rightClickJump = false; // 右クリックかで特定のラベルにジャンプするか
	/*C*/var rightClickTarget = ""; // 右クリックでの呼び出し先
	/*C*/var rightClickStorage = ""; // 右クリックでの呼び出し先
	/*C*/var rightClickName = "default"; // 右クリックのメニュー表示名
	/*C*/var rightClickCurrentMenuName = ""; // 右クリックのメニューに現在設定されている名前
	var rightClickDefaultName = ""; // 右クリックのデフォルトのメニュー表示名

	/*C*/var lastClickGlyphVisible; // extraConductor を使用する直前にクリック待ち記号が可視だったかどうか
	var lastClickGlyphMessagePage;
		// extraConductor を使用する直前のクリック待ち記号の表示されているメッセージレイヤのページ
	var lastClickGlyphMessageNum; // 〃 番号
	var lastClickGlyphWhich; // 〃 "page" か "line" か
	var inSleepBeforeExtraConductor; // extraConductor を使用する直前が inSleep だったか

	// 通常のマウスカーソル
	var cursorDefault = crArrow;  // 通常のマウスカーソル
	var cursorPointed = crHandPoint;  // ボタン、リンク等をポイントした時のマウスカーソル
	var cursorWaitingClick = crArrow;  // クリック待ちのマウスカーソル
	var cursorWaitingAuto = crArrow;  // crAppStart; // オート時の待ちのマウスカーソル
	var cursorDraggable = crSizeAll; // ドラッグ可能な場所用のマウスカーソル

	/*C*/var startAnchorEnabled = false; // 「最初に戻る」が使用可能かどうか

	/*C*/var storeEnabled = true; // ユーザが「栞をはさむ」メニューにアクセス可能かどうか
	/*C*/var restoreEnabled = true; // ユーザが「栞をたどる」メニューにアクセス可能かどうか
	var storeLabelPassed = false; // 保存可能なラベルを通過したかどうか
	var storeLabelPassedLastStorage; // 最後に保存可能なラベルを通過した時のストレージ
    /*C*/var currentStorage = ""; // 現在のラベル
    /*C*/var currentLabel = ""; // 現在のラベル
	/*C*/var _currentPageName = ""; // 現在のページ名

	property currentPageName {
		getter() {
			var ret = forEachFunctionHook("getCurrentPageName");
			if (ret !== void) {
				return ret;
			}
			return _currentPageName;
		}
		setter(v) {
			_currentPageName = v;
		}
	}
	
	var currentRecordName = ""; // 現在の記録名 ( trail_ストレージ_ラベル )
	var autoRecordPageShowing = false; // ラベル通過記録をするか

	var numBookMarks = 10; // メニューに用意する栞のサブメニュー項目の数
	var showBookMarkDate = false; // メニューに栞をはさんだ日付時刻を表示するか

    var bookMarkDateSecond = false; // 日付に秒までふくめるか

    // プレイ時間
    var playTime;
    var playStartTime;

    function clearPlayTime(initTime = 0) {
        playTime = initTime;
        playStartTime = (new Date()).getTime();
    }
@if (!OLDBOOKMARK)
	var bookMarkIndex = %[];
@endif
@if (OLDBOOKMARK)
	var bookMarkNames = []; // メニューに設定されている栞の名前
	var bookMarkDates = []; // 栞の日付
	var bookMarkProtectedStates = []; // 栞が保護されているかの情報
	var bookMarkStorages  = []; // 栞のシナリオ名
	var bookMarkPlayTimes = [];  // 栞のプレイ時間
	var bookMarkInfos = [];     // 栞の追加情報
@endif

	var showFixedPitchOnlyInFontSelector = false; // フォント選択で固定ピットフォントのみを表示するか

	var flags = %[]; // フラグ(ユーザ)
	var pflags = %[]; // 「保存可能なラベル」を通過した時点でのフラグ(ユーザ)
	var pcflags = %[]; // 〃 (コア)
	var sflags = %[]; // システム変数領域(ユーザ)
	var scflags = %[]; // システム変数領域(コア)
	var tflags = %[]; // 一時フラグ

	var tempBookMarks = []; // 一時的に保存した栞

	var clickCount = 0; // 左クリックカウント
	var lastMouseDownX; // 最後にクリックされた X 座標
	var lastMouseDownY; // 最後にクリックされた Y 座標

	var mouseKeyEnabledCount = 0; // マウスキーが有効かどうか

	var kagPlugins = []; // KAG プラグイン

	var keyDownHook = []; // キーが押されたときに呼び出される物
	var leftClickHook = []; // 右クリックされたときに呼び出される物
	var rightClickHook = []; // 左クリックされたときに呼び出される物
	var mouseMoveHook = []; // マウス移動で呼び出される物

	var padKeyMap; // パッドボタン -> ハンドラ(キーコード)。コンストラクタ内で設定
	var padKeyMapReverse; // キーコード -> パッドボタン。コンストラクタ内で設定


	var holdPeriodEventQueue = [];	// 保留にされたムービーのピリオドイベントキュー
	var isLeavePeriodEvent = false;	// ムービーのピリオドイベントを保留にするかどうか
	var isWaitPeriodEvent = false;	// ムービーのピリオドイベント待ち状態かどうか
	var waitedPeriodEventStorageName = void;	// ピリオドイベント待ちをコールしたストレージ名

    // ------------------------------------------------------ 自動ラベル処理

    var autoLabelMode; // true:自動ラベルモード
    var autoLabelType; // 0:間接ラベルロード 1:行番号指定ロード
    var autoLabelCount;  // 自動ラベルカウンタ
	var autoLabelCurrentRecordName; // 自動保存用記録名

	// デバッグ用機能
	var autoLabelStore; // ラベルで自動的に通過履歴データとして保存する

    // ------------------------------------------------------ 回想再生処理

	var _isRecollection = false;
    var recollectionEndStorage; // 回想終了を示すストレージ
    var recollectionEndLabel; // 回想終了を示すラベル
    var recollectionStorage;  // 回想終了時の遷移先ストレージ
    var recollectionTarget;   // 回想終了時の遷移先ターゲット

    /**
     * 回想中かどうか
     */
    property isRecollection {
        getter() {
            return _isRecollection;
        }
    }
    
    /**
     * 回想モード実行を開始する
     */
	function startRecollection(elm) {
		var doneStorage = elm.donestorage != "" ? elm.donestorage : elm.doneStorage;
		var doneTarget  = elm.donetarget  != "" ? elm.donetarget  : elm.doneTarget;
		var endStorage  = elm.endstorage  != "" ? elm.endstorage  : elm.endStorage;
		var endLabel    = elm.endlabel    != "" ? elm.endlabel    : elm.endLabel;
		if (elm.no !== void) throw new Exception("Bookmark方式の回想は廃止されました");
		else if (elm.storage != "" || elm.target != "") {
			process(elm.storage, elm.target);
			recollectionEndStorage = endStorage;
			recollectionEndLabel   = endLabel;
			recollectionStorage    = doneStorage;
			recollectionTarget     = doneTarget;
			_isRecollection = true;
		}
	}

    /**
     * 回想モード実行を終了する
     */
    function endRecollection() {
        // 回想終了処理
        if (isRecollection) {
            var ret = -1;
            if (recollectionStorage != '' || recollectionTarget != '') {
                // 実行を登録アドレスに移す
                if (recollectionStorage != '') {
                    conductor.loadScenario(recollectionStorage);
                }
                if (recollectionTarget != '') {
                    conductor.goToLabel(recollectionTarget);
                }
                inSleep = false;
                notifyRun();
                if (conductor.status != conductor.mRun) conductor.run();
                ret = 0;
            } else {
                // 実行停止
                stablePosibility = true;
                cancelSkip();
                isSleep = true;
                notifyStable();
            }
            stopRecollection();
            return ret;
        }
        return 0;
    }
    
    function stopRecollection() {
		recollectionEndStorage = void;
        recollectionEndLabel = void;
        recollectionStorage = void;
        recollectionTarget  = void;
		_isRecollection = false;
    }
    
	//------------------------------------------------------ ダイアログレイヤ処理 --

    var currentDialog;
	var currentDialogAbsolute = 9000000;
	var currentDialogFocusedRestore; // フォーカス状態を記録しておく
    
    function handleOpenDialog() {
		if (currentDialog !== void) {
			stopAllTransitions();
			cancelSkip();
			with (currentDialog) {
				.enabled = true;
				.setMode();
				.focus();
				.onOpen();
			}
		}
		setMenuAccessibleAll();
    }

    function handleCloseDialog() {
		if (!this isvalid) return;
		if (currentDialog !== void) {
			with (currentDialog) {
				.removeMode();
				.visible = false;
			}
			invalidate currentDialog;
			currentDialog = void;

			var restore = currentDialogFocusedRestore;
			if (typeof restore == "Object" && (restore == null || isvalid restore)) {
				try   { focusedLayer = restore; }
				catch { focusedLayer = null; }
			}
		}
		setMenuAccessibleAll() if (this isvalid);
	}

    /**
     * ダイアログを開く
     */
    function openDialog(dialog, trans) {
        if (currentDialog !== void &&
            currentDialog !== dialog) {
            invalidate currentDialog;
        }

		callHook("onOpenDialog", dialog);
        mainConductor.interrupted = true;
        extraConductor.interrupted = true;
        setMenuAccessibleAll();

		currentDialog = dialog;
		currentDialogFocusedRestore = focusedLayer;
		with (dialog) {
			.parent   = primaryLayer;
			.absolute = currentDialogAbsolute;
			.enabled = false;
			.visible = true;
		}
		var trig;
		if (typeof this.openDialogTrigger != "undefined") {
			trig = this.openDialogTrigger;
		} else {
			trig = this.openDialogTrigger = new AsyncTrigger(handleOpenDialog, '');
			trig.cached = true;
			add(trig);
		}
		var trig = this.openDialogTrigger;
		trig.trigger();
    }

    function closeDialog(dialog=void) {
        if (currentDialog !== void && (dialog === void || currentDialog === dialog)) {
            if (mainConductor.interrupted) {
                mainConductor.interrupted  = false;
            }
            if (extraConductor.interrupted) {
                extraConductor.interrupted = false;
            }
            setMenuAccessibleAll();
            
            if (currentDialog !== void) {
                global.closeDialogTrigger = new AsyncTrigger(handleCloseDialog, '');
                global.closeDialogTrigger.cached = true;
                global.closeDialogTrigger.trigger();
            }
        } else if (dialog !== void) {
            dialog.visible = false;
        }
    }

    //------------------------------------------------------ 特殊タイマ --

    /**
     * 画面更新処理
     */
	function onFlipTimerInterval(now) {
		var work = false;

		// タグ展開
		// これによりアクションや動画が生成される場合があるので先に行う
		work = true if updateDelay(now);
		
		// アクション更新
		work = true if updateAction(now);

		// GenericFlip更新
		if (updateGenericFlips(now)) work = true;

		// 画面切り替えレイヤ更新
		if (_transLayer !== void) {
			_transLayer.update(now);
			work = true if _transLayer.working;
		}

		if (!work) {
			flipStop();
		}
	}

	var flipStartFlag = false;
	
	function flipStart() {
		if (!flipStartFlag) {
			System.addContinuousHandler(onFlipTimerInterval);
			flipStartFlag = true;
		}
	}

	function flipStop() {
		if (flipStartFlag) {
			System.removeContinuousHandler(onFlipTimerInterval);
			flipStartFlag = false;
		}
	}
	
	//------------------------------------------------------ コンストラクタ --

	function KAGWindow(ismain = true, width = 0, height = 0, invisible = false)
	{
		// コンストラクタ
		// 引数 : ismain : メインウィンドウとして作成されるのかどうか
		super.Window(); // 親クラスのコンストラクタを呼ぶ

		// コンフィギュレーション
		isMain = ismain;
		if(ismain)
		{
			(KAGWindow_config incontextof this)();
			(KAGWindow_config_override incontextof this)()
				if typeof global.KAGWindow_config_override != "undefined";
		}
		
		// savedataid の上書き処理
		if (typeof global.ENV_GameId != "undefined") {
			saveDataID = global.ENV_GameId;
		}
		
		userChSpeed = chSpeed = actualChSpeed = chSpeeds.normal;
		autoModePageWait = autoModePageWaits.medium;
		autoModeLineWait = autoModeLineWaits.medium;

		askOnClose = false if !ismain;

		// saveDataLocation がフルパスでないようならば System.exePath を
		// 付け加える
		if(saveDataLocation.indexOf(":") == -1)
			saveDataLocation = System.exePath + saveDataLocation;

        // セーブ可能かどうかの確認
        checkSave();
        
		// ログの出力先を saveDataLocation にする
		if(ismain) Debug.logLocation = saveDataLocation;
        
		// メニューアイテムの作成
		if(ismain) (KAGWindow_createMenus incontextof this)();
		if(typeof this.rightClickMenuItem != "undefined")
			rightClickDefaultName = rightClickCurrentMenuName = rightClickMenuItem.caption;

		if(typeof this.autoModeMediumMenuItem != "undefined")
			autoModeMediumMenuItem.checked = true;
		if(typeof this.windowedMenuItem != "undefined")
			windowedMenuItem.checked = true;
		if(typeof this.chNormalMenuItem != "undefined")
			chNormalMenuItem.checked = true;
		if(typeof this.ch2ndNoChangeMenuItem != "undefined")
			ch2ndNoChangeMenuItem.checked = true;

		if(ismain) (Menu_visible_config incontextof this)();
		menuBarEnable = menu.visible; // メニューバー有効フラグはvisible_config後のvisible状態とする
		menuBarState  = menuBarEnable ? 1 : 0; // menuBarMode == 0 の時は設定されないのでここで確定しておく

		createBookMarkSubMenus();

		// ウィンドウ外見の調整
		if(ismain)
		{
			if (typeof this.registerExEvent != "undefined") {
				// windoeEx 拡張イベントを使用可能
				borderStyle = bsSizeable;
				innerSunken = false;
				registerExEvent();
				exEventEnabled = true;
				setWindowsClickEventHook();

				if (typeof       global.getSystemMouseCursor == "Object") {
					cursorPointed     = getSystemMouseCursor("Hand",        cursorPointed);
//					cursorWaitingAuto = getSystemMouseCursor("AppStarting", cursorWaitingAuto);
				}

				createMenuBarHideMenu();
			} else {
				// 拡張イベントなし
				borderStyle = bsSingle;
				innerSunken = true;
				menuBarMode = 0; // 強制固定
			}
		}
		else
		{
			borderStyle = bsDialog;
			innerSunken = false;
		}
		showScrollBars = false;
		if(ismain) caption = System.title;

		// システムタイトルをキャプションと同じに
		//if(ismain) System.title = caption;

		// ウィンドウサイズの調整
		if(width != 0 && height != 0)
		{
			// 与えられたサイズを適用
			scWidth = width;
			scHeight = height;
		}
		exWidth  = scWidth  if (exWidth  === void || exWidth  < scWidth );
		pxWidth  = scWidth  if (pxWidth  === void || pxWidth  < scWidth );
		pxWidth  = exWidth  if (                     pxWidth  > exWidth );
		exHeight = scHeight if (exHeight === void || exHeight < scHeight);
		pxHeight = scHeight if (pxHeight === void || pxHeight < scHeight);
		pxHeight = exHeight if (                     pxHeight > exHeight);

		// quake 用タイマの作成
		quakeTimer = new Timer(onQuakeTimerInterval, '');
		add(quakeTimer);
		quakeTimer.interval = 50;

		add(_primaryLayer = new Layer(this, null));
		with (_primaryLayer) {
			.name = "プライマリレイヤ";
			.setSize(exWidth, exHeight);
			.fillRect(0,0,exWidth, exHeight,0);
			.hasImage = false;
			.onMouseDown = function (x, y, btn) {
				switch (btn) {
				case mbLeft:  window.onPrimaryClick();      break;
				case mbRight: window.onPrimaryRightClick(); break;
				}
				return (global.Layer.onMouseDown incontextof this)(...);
			} incontextof _primaryLayer;
		}
		// ベースになるレイヤ
		sysbase = new Layer(this, _primaryLayer);
		with (sysbase) {
			.setSize(exWidth, exHeight);
			.name = "トップレイヤ";
			.fillRect(0,0,exWidth, exHeight,0);
			.type = ltOpaque;
			.visible = true;
		}
		add(sysbase);

        // 背景レイヤの作成
		fore.messages = [];
		back.messages = [];
		fore.layers = [];
		back.layers = [];
        fore.base = new BaseLayer(this, sysbase, "表-背景");
        add(fore.base);
        fore.base.setImageSize(exWidth, exHeight);
        fore.base.setSizeToImageSize();
        fore.base.visible = true;
        back.base = new BaseLayer(this, sysbase, "裏-背景");
        add(back.base);
        back.base.setImageSize(exWidth, exHeight);
		back.base.setSizeToImageSize();

        fore.base.setCompLayer(back.base);
		back.base.setCompLayer(fore.base);
		fore.base.freeImage();
		back.base.freeImage();

		fore.base.setDefaultCursor(cursorDefault);
		back.base.setDefaultCursor(cursorDefault);

		// メッセージ履歴レイヤの作成
        historyLayer = new HistoryLayer(this, fore.base);
        historyLayer.name = "履歴レイヤ";
		add(historyLayer);

		// 前景レイヤの作成
		allocateCharacterLayers(numCharacterLayers);

		// メッセージレイヤの作成
		allocateMessageLayers(numMessageLayers, false);
		current = fore.messages[0];
		currentNum = 0;
		currentPage = 0;
		currentWithBack = false;
		if(initialMessageLayerVisible)
		{
			fore.messages[0].visible = true;
			back.messages[0].visible = true;
		}

		chDefaultAntialiased = fore.messages[0].defaultAntialiased;
			// 文字にアンチエイリアスをかけるかどうか
		chDefaultFace = fore.messages[0].userFace;
			// 文字のデフォルトのフォント

		if(typeof this.chAntialiasMenuItem != "undefined")
			chAntialiasMenuItem.checked = chDefaultAntialiased;

		// 行待ち/ページ待ちアニメーションレイヤの作成
		lineBreak = new ClickGlyphLayer(this, fore.base);
		add(lineBreak);
		lineBreak.name = "行クリック待ち記号";
		pageBreak = new ClickGlyphLayer(this, fore.base);
		add(pageBreak);
		pageBreak.name = "ページ末クリック待ち記号";

		// タグハンドラ/コンダクタを作成
		tagHandlers = getHandlers();
        mainConductor = new Conductor(this, tagHandlers);
		add(mainConductor);
		conductor = mainConductor;
		extraConductor = new Conductor(this, tagHandlers);
		add(extraConductor);

        // コンダクタのデバッグレベルを指定
        debugLevel = _debugLevel;
        
		// BGM オブジェクトを作成
		bgm = new BGM(this);
		add(bgm);

		// 効果音オブジェクトを作成
		for(var i = 0, cnt = numSEBuffers + numSysSEBuffers; i < cnt; i++) {
			add(se[i] = new SESoundBuffer(this, i));
		}

		// ボイスオブジェクトを生成
		for(var i = 0, cnt = numVoiceBuffers; i < cnt; i++) {
			add(voices[i] = new VoiceSoundBuffer(this, i));
		}
		initLoopVoice();

		// ムービーオブジェクトを作成
		if(ismain)
		{
			for( var i = 0; i < numMovies; i++)
				add(movies[i] = new Movie(this,i));
		}

		// デフォルトのハンドラを追加
		stableHandlers.add(defaultStableHandler);
		runHandlers.add(defaultRunHandler);

		if(ismain) {
			// システム変数の読み込み
			loadSystemVariables();

			// システム変数を反映
			setSystemStateFromSystemVariables();
			setBookMarkMenuCaptions();
		}

		// メッセージレイヤのクリア
		clearMessageLayers(false);

		var forcefull = false; // 強制フルスクリーンフラグ
		with (System) {
			// 座標・サイズを決定
			var x = left,  y = top;          // ウィンドウ座標
			var w = width, h = height;       // ウィンドウサイズ
			var pw = pxWidth, ph = pxHeight; // 実サイズ
			var iw = pw, ih = ph;            // インナーサイズ
			var mw = 0, mh = 0;              // 最小補正サイズ
			var numer = scflags.zoomNumer;
			var denom = scflags.zoomDenom;
			var zoom  = (numer > 0 && denom > 0) ? (numer / denom) : 0;
			var max;

			if (ismain) {
				// ウィンドウ実サイズ
				iw = pw * zoom if (zoom > 0);
				ih = ph * zoom if (zoom > 0);

				// 位置を復帰
				if (System.getArgument("-restorewindowpos") != "no") with (scflags) {
					x = .windowLeft   if (.windowLeft    !== void);
					y = .windowTop    if (.windowTop     !== void);
					w = .windowWidth  if (.windowWidth   !== void);
					h = .windowHeight if (.windowHeight  !== void);
					max = .windowMaximized;
					// コンソール表示状態もついでに復帰
					if (debugWindowEnabled) with (Debug.console) try {
						var vis   = +scflags.consoleVisible;
						var place =  scflags.consolePlacement;
						var max   = (typeof place == "Object" && place.showCmd == 3/*SW_SHOWMAMIMIZED*/);
						var force = (System.getArgument("-consolerepos") == "yes");
						.visible = true if (force);
						.visible = vis;
						place = .getPlacement() if (typeof .getPlacement == "Object");
						if ((vis || force) && typeof place == "Object" && typeof .setPos == "Object") {
							var x, y, w, h;
							with (place) x=.normalLeft, y=.normalTop, w=.normalRight-x, h=.normalBottom-y;
							var mon = System.getMonitorInfo(true, x, y, w, h), setPos = .setPos;
							if (mon !== void) with (mon.work) {
								if (mon.monitor !== void) (x += .x - mon.monitor.x), (y += .y - mon.monitor.y);
								var resize = false;
								if (.w < w) w=.w, resize=true;
								if (.h < h) h=.h, resize=true;
								if (.x+.w < x+w\3) x=.x+.w-w;
								if (.y+.h < y+h\3) y=.y+.h-h;
								if (.x > x+w*2\3 ) x=.x;
								if (.y > y       ) y=.y;
								if (resize) setPos(x, y, w, h);
								else        setPos(x, y);
							}
						}
						.maximize() if (vis && max && typeof .maximize == "Object");
					} catch {}
				}
			}
			w = iw if (w <= 0);
			h = ih if (h <= 0);
			mw = w\3, mh = h\3 if (ismain);

			// モニタ情報の取得
			var info;
			var desktop = %[ /**/  x:.desktopLeft,   y:.desktopTop,
							 /**/  w:.desktopWidth,  h:.desktopHeight ];
			var screen  = %[ /**/  x: 0,             y: 0,
							 /**/  w:.screenWidth,   h:.screenHeight  ];

			// WindowExプラグインが使える場合はそちらを使用する
			if (typeof .getMonitorInfo != "undefined") {
				var info = .getMonitorInfo(true, x, y, w, h);
				if (typeof info == "Object") {
					(Dictionary.assign incontextof desktop)(info.work,    true);
					(Dictionary.assign incontextof screen )(info.monitor, true);
				}
			}

			// ズーム状態補正
			var sw = screen.w, sh = screen.h;
			var over = (iw > sw || ih >= sh);
			if (!ismain || over || !zoom) {
				numer = denom = 1;
				// 標準サイズでも画面からはみ出す場合
				if (ismain && over && (pw > sw || ph >= sh)) {
					// 3/4〜1/4 まで縮小できるか試す
					for (numer=3, denom=4; numer > 1; numer--) 
						if (pw*numer/denom < sw && ph*numer/denom < sh) break;
					// 強制でフルスクリーン
					forcefull = true;
				}
			}
			setZoom(numer, denom);

			// ウィンドウ位置の調節
			with (desktop) {
				x = .x+.w - w if (x   + mw > .x+.w);
				y = .y+.h - h if (y   + mh > .y+.h);
				x = .x        if (x+w - mw < .x   );
				y = .y        if (y/*+h-mh*/<.y   ); // タイトルバーが隠れると困る
			}
			setPos(x, y) if (left != x || top != y);
			maximize() if (max && typeof this.maximize != "undefined");
		}

		// パッドボタンハンドラの作成
		createPadKeyMap();

		// ウィンドウを表示
		if(ismain) {
			// フルスクリーン状態
			var fs = getInitialFullScreenState();

			visible = !invisible;

			// ログウィンドウ位置初期化（デバッグ時のみ）
			initLogPad(true) if (debugWindowEnabled && logMode && !fs);

			// 前回起動時にフルスクリーンだった場合はフルスクリーンにしてみる
			// または画面サイズがウィンドウサイズよりも小さい場合はフルスクリーンにしてみる
			onFullScreenMenuItemClick(this) if (visible && (!!fs || forcefull));

			// いったんシステム変数を書き出す
			saveSystemVariables();

			// !visible 時は scflags.fullScreen を復帰
			scflags.fullScreen = fs if (!visible);

			if (debugWindowEnabled) {
				// デバッグ時にはautoLabelStoreを有効にする
				autoLabelStore = [];
				// 外部メッセージ受信を有効にする
				try { this.messageEnable = true if (typeof this.registerUserMessageReceiver == "Object"); } catch {}
			}
		}

		// モーションリソースマネージャ
		if (ismain && typeof global.MotionResourceManager != "undefined") {
			this.motion_manager = new global.MotionResourceManager(this);
			add(motion_manager);
		}

		// extract処理実行用のトリガ
		extractTrigger = new AsyncTrigger(doExtractDelay, '');
		extractTrigger.cached = true;

		if (isMain) {
			loadCharacterVoiceInfo();
			loadSoundGainInfo();
			loadFileGainInfo();
		}
	}

	//------------------------------------------------------------- finalize --

	function finalize()
	{
		// finalize メソッド
		doneSquirrel();

		extractTrigger.cancel();
		invalidate extractTrigger;

		stopAllActions(true);
		flipStop();

        clearBgmStop();
        clearBgmLabel();
        
		// プラグインの無効化
		for(var i = 0; i < kagPlugins.count; i++) invalidate kagPlugins[i];

		// 前景、メッセージレイヤを無効化
		for(var i = 0; i< fore.layers.count; i++) invalidate fore.layers[i];
		for(var i = 0; i< back.layers.count; i++) invalidate back.layers[i];
		for(var i = 0; i< fore.messages.count; i++) invalidate fore.messages[i];
		for(var i = 0; i< back.messages.count; i++) invalidate back.messages[i];

		// snapshotLayer を無効化
		invalidate snapshotLayer if snapshotLayer !== void;

		// tempLayer を無効化
		invalidate tempLayer if tempLayer !== void;

		// スーパークラスの finalize を呼ぶ
		super.finalize(...);
	}

	//-------------------------------------------------- onCloseQuery/close --

    function onCloseYes() {
        shutdown();
    }

	function onCloseQuery()
	{
		saveSystemVariables();
        if(!askOnClose) {
			// コンソールを最大化で閉じないように
			with (Debug.console) .restoreMaximize() if (debugWindowEnabled && typeof .restoreMaximize != "undefined");
			return super.onCloseQuery(true);
		}
		if (!callHook("onCloseQuery") && currentDialog === void) {
            askYesNo("終了しますか？", "確認", onCloseYes);
        }
        super.onCloseQuery(false);
    }

	function close()
	{
		callHook("onCloseHook") if(!askOnClose);
		// ウィンドウを閉じる
		saveSystemVariables();
		super.close(...);
	}

	function shutdown()
	{
		// ウィンドウを閉じるが、終了確認を行わない
		// このメソッド内で close をすると、様々な
		// オブジェクトが無効化されてしまい、この
		// 関数から戻った先でエラーが発生する。
		// そのため、いったん AsyncTrigger を介して、
		// 全てのメソッドから帰ったあとにシャットダウン
		// 処理を行う。
		global.shutdownTrigger =
			new AsyncTrigger(handleShutdown, '');
		global.shutdownTrigger.cached = true;
		global.shutdownTrigger.trigger();
		if(conductor.status == conductor.mRun)
			conductor.interrupt();
			// コンダクタが走行中の時は
			// コンダクタに中断を伝える
	}

	function handleShutdown()
	{
		// shutdown() からのシャットダウン要求を
		// 実際に処理するメソッド
		var askOnClose_save = askOnClose;
		askOnClose = false;
		close();
		if(this isvalid) askOnClose = askOnClose_save;
	}

	function closeByScript(elm)
	{
		// ウィンドウを閉じる
		// ちょっと面倒くさいとをやっているのは
		// shutdown と同じ理由
		// 「いいえ」が選択されてウィンドウが閉じなかった場合は
		// 'not_closed' トリガが発行され、実行は再開する。
		var askOnClose_save = askOnClose;
		if(elm.ask !== void && !(+elm.ask)) askOnClose = false;
		global.shutdownTrigger =
			new AsyncTrigger(handleCloseByScript, '');
		global.shutdownTrigger.cached = true;
		global.shutdownTrigger.trigger();

		// closeByScript は、このハンドラが終わった直後に
		// 「終了しますか？」のダイアログを表示する。
		// 終了する場合はそのまま終了する。
		// 終了しない場合は 'not_closed' トリガが発行され、
		// 実行は継続する。
		conductor.wait(%[
			not_closed : askOnClose_save
			? (function
			{
				// 実際にこれが呼ばれることはないが、一応
				askOnClose = true;
			} incontextof this)
			: (function
			{
			} incontextof this)
			]);
	}

	function handleCloseByScript()
	{
		// shutdown() からのシャットダウン要求を
		// 実際に処理するメソッド
		close();
		if(this isvalid) // いいえ が選択された場合
			conductor.trigger('not_closed');
	}

	//------------------------------------------------------ プラグイン処理 --

	function forEachEventHook(method, func, arg)
	{
		// すべてのプラグインオブジェクトの method にたいして
		// func を実行する
		// func の引数には各要素と arg が渡される
		if(kagPlugins.count)
		{
			var array = [];
			array.assign(kagPlugins); // いったんコピーし、コピーした物に対して実行する
			var arraycount = array.count;
			if (func !== void) {
				for(var i =0; i<arraycount; i++)
				{
					var obj = array[i];
					if(typeof obj[method] != "undefined")
						func(obj[method], arg);
				}
			} else {
				for(var i =0; i<arraycount; i++)
				{
					var obj = array[i];
					if(typeof obj[method] != "undefined")
						obj[method](arg);
				}
			}
		}
	}

	function addPlugin(plugin)
	{
		// プラグインオブジェクトを追加
		kagPlugins.add(plugin);
	}

	function removePlugin(plugin)
	{
		// プラグインオブジェクトを削除
		kagPlugins.remove(plugin);
	}

	//---------------------------------------------------------- レイヤ機能呼び出し --

	/**
	 * 全レイヤのメソッド呼び出し
	 * param.command: メソッド名
	 * param.args: 引数
	 */
	function onLayerCommand(param) {
		var layers, messages;
		fore.base[param.command](param.args*);
		layers = fore.layers, messages = fore.messages;
		for(var i = layers.count-1; i >= 0; i--) layers[i][param.command](param.args*);
		for(var i = messages.count-1; i >= 0; i--) messages[i][param.command](param.args*);
		back.base[param.command](param.args*);
		layers = back.layers, messages = back.messages;
		for(var i = layers.count-1; i >= 0; i--) layers[i][param.command](param.args*);
		for(var i = messages.count-1; i >= 0; i--) messages[i][param.command](param.args*);
		forEachEventHook('onLayerCommand',,param);
	}
	
	//---------------------------------------------------------- フック処理 --

	var hookMap = %[ keyDown:this.keyDownHook, leftClick:this.leftClickHook, rightClick:this.rightClickHook, mouseMove:this.mouseMoveHook ];
	function callHook(tag, *)
	{
		// 文字列を渡されたときはテーブルから参照
		var array = (typeof tag == "String" && tag != "") ? hookMap[tag] : tag;
		if (typeof array != "Object" || array.count <= 0) return false;

		// 順番に呼び出し
		var ret = false, tmp = [];
		tmp.assign(array);
		for (var i = 0, cnt = tmp.count; i < cnt; i++) {
			var func = tmp[i];
			ret |= func(*) if (typeof func == "Object");
		}
		return !!ret;
	}
	function addHook(tag, func) {
		var array = tag;
		if (typeof tag == "String" && tag != "") {
			array = hookMap[tag];
			array = hookMap[tag] = [] if (array === void);
		}
		array.add(func) if (typeof array == "Object");
	}
	function removeHook(tag, func) {
		var array = (typeof tag == "String" && tag != "") ? hookMap[tag] : tag;
		array.remove(func, true) if (typeof array == "Object");
	}

	//------------------------------------------ パッドのキーマッピング作成 --

	var initialPadKeyMap = %[
		// パッド入力とそれに対応する動作のマッピング。
		// 標準では十字キーはキーボードのカーソルキーにマッピング、
		// ボタン1 は Return、ボタン2 は ESC、ボタン3 は メッセージ履歴の表示
		// にマッピングされる。
		// 関数を指定した場合はボタンを押したときにはその関数が呼ばれる。
		// 必要に応じて Config.tjs 中で書き換える。
		VK_PADLEFT =>		VK_LEFT,
		VK_PADRIGHT =>		VK_RIGHT,
		VK_PADUP =>			VK_UP,
		VK_PADDOWN =>		VK_DOWN,
		VK_PAD1 =>			VK_RETURN,
		VK_PAD2 =>			VK_ESCAPE,
		VK_PAD3 =>			showHistoryByKey, //#'R',
		];
	function createPadKeyMap()
	{
		padKeyMap = %[];
		(Dictionary.assign incontextof padKeyMap)(initialPadKeyMap, true);
		// オプション指定があれば書き換え
		for (var i = 1; i <= 10; i++) {
			if (typeof global["VK_PAD"+i] != "Integer") continue;
			var key  = global["VK_PAD"+i];
			var bind = System.getArgument("-padbtn"+i);
			if (bind == "" || bind == "default") continue;
			var tag = "VK_"+bind.toUpperCase();
			padKeyMap[key] = typeof global[tag] == "Integer" ? global[tag] : void;
		}
		padKeyMapReverse = %[];
		recreatePadKeyMap();
	}

	function recreatePadKeyMap() {
		// padKeyMapReverse を作成
		(Dictionary.clear incontextof padKeyMapReverse)();
		var ar = [];
		ar.assign(padKeyMap);

		for (var i = 0; i < ar.count; i += 2) {
			var pad = +ar[i], key = ar[i+1];
			if (typeof key != "Integer") continue;
			//dm("recreatePadKeyMap", pad, key);
			switch (typeof padKeyMapReverse[key]) {
			default:
				// 最初の登録
				padKeyMapReverse[key] = pad;
				break;
			case "Integer":
				// ２個目
				var first = padKeyMapReverse[key];
				padKeyMapReverse[key] = [ first, pad ];
				break;
			case "Object":
				// 以降
				padKeyMapReverse[key].add(pad);
				break;
			}
		}
	}

	function getKeyState(key)
	{
		// System.getKeyState 関数と同等だが、パッドのキーに関しても
		// 反応を返す
		var sg = System.getKeyState;
		if (sg(key)) return true;
		var pad_key = padKeyMapReverse[key];
		switch (typeof pad_key) {
		case "Integer":
			// キーコード -> パッドのキーコードの変換が見つかった
			return sg(pad_key);
		case "Object":
			// 複数登録されている場合
			for (var i = 0; i < pad_key.count; i++)
				if (sg(pad_key[i])) return true;
			break;
		}
		return false;
	}

	//-------------------------------------------------------------- action --

	function action(ev)
	{
		if(ev.target === this && (ev.type == 'onKeyDown' || ev.type == 'onKeyUp'))
		{
			// パッド入力に対応する処理
			var handler = padKeyMap[ev.key];
			if(handler !== void)
			{
				// ハンドラが見つかった
				if(typeof(handler) == "Object")
				{
					// ハンドラは関数
					(handler incontextof this)(ev) if (ev.type == 'onKeyDown');
				}
				else
				{
					// ハンドラはキーコード
					postInputEvent(ev.type, %[key: handler, shift:ev.shift ]);
				}
			}
		}
	}

	//------------------------------------------------------ tempLayer 関連 --

	property temporaryLayer
	{
		// ワークエリアとして一時的に使用できるレイヤを返す
		getter()
		{
			if(tempLayer === void)
			{
				tempLayer = new KAGLayer(this, primaryLayer);
				tempLayer.name = "一時ワークレイヤ";
			}
			// type/faceを初期化して返す（使いまわす場合にfaceが違ってハマることがあるため）
			with (tempLayer) {
				.type = ltAlpha;
				.face = dfAuto;
			}
			return tempLayer;
		}
	}

	//------------------------------------------ フルスクリーン・ズーム処理 --

	// 起動時のフルスクリーンステート取得
	function getInitialFullScreenState { return scflags.fullScreen; }

	property fullScreenMode { getter {
		var tag = System.getArgument("-fullscreenmode");
		var map = %[ auto:-1, primaryonly:0, usepseudo:1, pseudoall:3 ];
		return (tag != "" && typeof map[tag] != "undefined") ? map[tag] : -1;
	} }

	function isPseudoMode() {
		if (pseudoFullScreened) return true;
		var info, mode = fullScreenMode;
		mode = 0 if (!exEventEnabled);
		switch (mode) {
		case -1:
			// マルチモニタ環境の場合は擬似フルスクリーン
			mode = (typeof  System.getDisplayMonitors != "undefined" &&
					(info = System.getDisplayMonitors()) !== void &&
					(info.count > 1));
			break;
		case 1:
			// ウィンドウがセカンダリ以降なら擬似フルスクリーン
			mode = (typeof  System.getMonitorInfo != "undefined" &&
					(info = System.getMonitorInfo(true, this)) !== void &&
					!info.primary);
			break;
		}
		return !!mode;
	}

	var _pseudoFullScreen;
	property pseudoFullScreened { getter { return (_pseudoFullScreen !== void); } }

	var _lastWindowSize;
	property fullScreen {
		getter { return super.fullScreen || pseudoFullScreened; }
		setter(v) {
			if (!fullScreen == !v) return;
			if (typeof this.screenModeChangedTrigger == "undefined") {
				add(   this.screenModeChangedTrigger = new AsyncTrigger(this, "screenModeChanged"));
				with (      screenModeChangedTrigger) .cached = true, .mode = atmAtIdle, .trigged = false;
			}
			var trig =      screenModeChangedTrigger;
			with (trig) {
				.maximize = .resetIcon = .callResize = .windowPos = void;
				if (.trigged) throw new Exception("Cant set fullScreen, changed trigger not completed.");
			}

			// フック
			callHook("prepareChangeScreenMode", v);
			onMovieScreenChanging(v);

			// フルスクリーンモードチェック
			if (v) {
				var min = 1;
				_lastWindowSize = %[
					/**/window: ((typeof this.getNormalRect == "Object") ? getNormalRect() :
								 /**/%[ x:left, y:top, w:width, h:height ]) ];
				if (isPseudoMode()) {
					// 擬似フルスクリーン
					var max = (typeof this.maximized != "undefined") ? maximized : false;
					_pseudoFullScreen =
						%[	/**/x:left, y:top, w:width, h:height,
							/**/max:max, restmax:(System.getArgument("-restoremaximizebyf2w") == "yes"),
							/**/rect:getNormalRect(), bs:borderStyle, menu:menu.visible,
							/**/shortcut:(menuBarEnable ? makePseudoMenuTable(menu) : void) ];
					if (!max) {
						visible = false;
						trig.maximize = true;
					}
					menu.visible = false;
					borderStyle = bsNone;
				} else {
					// プライマリフルスクリーン
					setInnerSize(pxWidth, pxHeight);
					resetPrimaryPos();
					trig = void;
					menu.visible = menuBarEnable && menuBarState;
					super.fullScreen = true;
					min = -1;
					// 解像度が変わる場合があるので
					_lastWindowSize.fullscreen = %[ 
						/**/w:System.screenWidth, h:System.screenHeight, x:0, y:0 ];
				}
				updateMenuBarState();
				SetWindowControlMenu(this, min, 0, 1, 1);
			} else {
				super.fullScreen = false;
				if (pseudoFullScreened) with (_pseudoFullScreen) {
					visible = false;
					setPos (.x, .y);
					setSize(.w, .h);
					menu.visible  = .menu if (!menuBarHideModeEnable);
					borderStyle   = .bs;

					trig.callResize = true;
					if (.max) {
						trig.windowPos = .rect;
						trig.maximize = .restmax;;
					}
					showRestore();
					_pseudoFullScreen = void;
				} else {
					setZoom(origZoomNumer, origZoomDenom);
					trig.resetIcon = true;
				}

				// メニューを消す
				SetWindowControlMenu(this);
				updateMenuBarState();
				_lastWindowSize = void;
			}
			if (trig !== void) with (trig) {
				.trigged = true;
				.trigger();
			} else
				callHook("afterChangeScreenMode");
		}
	}
	function screenModeChanged() {
		with (screenModeChangedTrigger) {
			resetWindowIcon() if (.resetIcon && typeof this.resetWindowIcon != "undefined");
			if (.windowPos !== void) with (.windowPos) {
				setPos (.x, .y);
				setSize(.w, .h);
			}
			if (     .maximize  ) maximize();
			else if (.callResize) onResize();

			.maximize = .resetIcon = .callResize = .windowPos = void;
			.trigged = false;
		}
		visible = true if !visible;

		// フック
		callHook("afterChangeScreenMode");
	}
	function resetPrimaryPos(z = 0, w, h) {
		with (_primaryLayer) {
			if (z > 0) {
				var x = (innerWidth  - w) / (z*2);
				var y = (innerHeight - h) / (z*2);
				w = Math.ceil(innerWidth  / z);
				h = Math.ceil(innerHeight / z);
				sysbase.setPos(x, y, pxWidth, pxHeight);
				.hasImage = true;
				.setImageSize(w, h);
				.fillRect(0, 0, w, h, 0);
				.setSize(w, h);
			} else {
				sysbase.setPos(0, 0, pxWidth, pxHeight);
				.hasImage = false;
				.setSize(pxWidth, pxHeight);
			}
		}
		if (movieRestoreTimer !== void) with (movieRestoreTimer) {
			if (.enabled) {
				// 再ディレイ
				.enabled = false;
				.enabled = true;
				return;
			}
		}
		with (sysbase) onMoviePrimaryPosChanged(.left, .top);
	}

	var _zoomNumer, _zoomDenom;
	property origZoomNumer { setter(v) { super.zoomNumer = v; } getter { return tempZoomed ? _zoomNumer : super.zoomNumer; } }
	property origZoomDenom { setter(v) { super.zoomDenom = v; } getter { return tempZoomed ? _zoomDenom : super.zoomDenom; } }
	property tempZoomed    { getter { return _zoomNumer !== void; } }
	function setZoom(numer, denom, resize = true) {
		if (super.fullScreen) return;
		var zn = super.zoomNumer;
		var zd = super.zoomDenom;
		super.setZoom(numer, denom);
		var z = zoomNumer / zoomDenom;
		var w = pxWidth  * z;
		var h = pxHeight * z;
		if (resize) {
			setInnerSize(w, h);
			resetPrimaryPos();
			_zoomNumer = _zoomDenom = void;
		} else {
			resetPrimaryPos(z, w, h);
			if (!tempZoomed) {
				_zoomNumer = zn;
				_zoomDenom = zd;
			}
		}
//		top++, top--; // VCRなムービーが追従するようにおまじない XXX
	}

	var menuBarMode = 1; // 0:固定・変更不可，1:固定・変更可（menuBarState準拠）
	var menuBarEnable;   // メニューバー有効
	var menuBarState;  // 現在のメニューの表示状態(0:非表示，1:表示，-1:自動表示）
	var defaultMenuBarState = 1; // 初期状態
	var menuBarHideButtonVisible = true; // メニューバーに消す・復帰ボタンを表示する
	var menuBarHideAutoVisible   = true; // 自動表示の有効フラグ
	var menuBarRightClickEnabled = true; // メニューバー上の右クリックを有効
	var menuBarTempMenuEnabled   = true; // 一時メニュー表示を有効

	var menuBarHideMenuItem;
	var menuBarHidePopupMenu;
	var menuBarHideSysMenu;
	var menuBarHidePseudoShortcut;
	property menuBarHideModeEnable { getter { return menuBarMode && menuBarEnable; } }
	function updateMenuBarState() {
		if (!menuBarHideModeEnable) return;
		menuBarState = (int)menuBarState;
		menuBarState = -1 if (menuBarState < 0);
		menuBarState =  1 if (menuBarState > 0 || (!menuBarHideAutoVisible && menuBarState < 0));

		menu.visible = (menuBarState > 0) if (!fullScreen);
		var list = menuBarHidePopupMenu;
		for (var i = list.count-1; i >= 0; i--)
			 with (list[i]) .checked = (.state == menuBarState);
		if (!menuBarHideAutoVisible) {
			list = menuBarHideSysMenu;
			for (var i = list.count-1; i >= 0; i--)
				with (list[i]) if (typeof .state != "undefined") .checked = (.state == menuBarState);
		}
		if (menuBarHideMenuItem !== void) with (menuBarHideMenuItem) {
			if (fullScreen) .visible = false;
			else {
				.caption = (menuBarState > 0) ? "x" : "+";
				.visible = true;
//				.bmpItem = MenuItem[(menuBarState > 0) ? "biPopupClose" : "biPopupRestore"];
				.rightJustify = true;
			}
		}
	}
	function getMenuBarPseudoShortcut() {
		return (menuBarHideModeEnable && menuBarState <= 0) ? menuBarHidePseudoShortcut : void;
	}
	function changeMenuBarState(stat) {
		if (!menuBarHideModeEnable) return;
		menuBarState = stat;
		menuBarHidePseudoShortcut = makePseudoMenuTable(menu) if (menuBarState <= 0 && menuBarHidePseudoShortcut === void);
		updateMenuBarState();
		onResize();
	}
	function toggleMenuBarState(sender) {
		if (!menuBarHideModeEnable) return;
		var state = (menuBarState <= 0) ? 1 : (menuBarHideAutoVisible ? -1 : 0);
		state = sender.state if (sender !== void && typeof sender.state == "Integer");
		changeMenuBarState(state);
	}
	function createMenuBarHideMenu() {
		if (!menuBarHideModeEnable) return;
		if (menuBarHideAutoVisible) {
			menuBarHidePopupMenu = [
				%[  caption:"常に表示",   group:1, state: 1 ],
				%[  caption:"常に非表示", group:1, state: 0 ],
				%[  caption:"動的に表示", group:1, state:-1 ] ];
			menuBarHideSysMenu = [
				%[  caption:"-" ],
				%[  caption:"メニューバー", children:menuBarHidePopupMenu ] ];
		} else {
			menuBarHidePopupMenu = [
				%[  caption:"表示",   group:1, state: 1 ],
				%[  caption:"非表示", group:1, state: 0 ] ];
			menuBarHideSysMenu = [
				%[  caption:"-" ],
				%[  caption:"メニューバーを表示",   group:1, state: 1 ],
				%[  caption:"メニューバーを非表示", group:1, state: 0 ] ];
		}
		exSystemMenu = menuBarHideSysMenu;
		if (menuBarHideButtonVisible) {
			menu.add(menuBarHideMenuItem  = new KAGMenuItem(this, "x", 0, toggleMenuBarState, false));
			/**/     menuBarHideMenuItem.visible = false;
		}
	}
	function onExSystemMenuSelected(sel) {
		if (sel && typeof sel == "Object") changeMenuBarState(sel.state);
	}
	var _ncCaptionClick;
	function onNcMouseDown(x, y, btn, rg) {
		_ncCaptionClick = (btn == mbRight && rg == global.Window.nchtMenu);
		if (typeof this.toggleMenuBarState == "Object" && btn == mbMiddle && rg == global.Window.nchtCaption) {
			toggleMenuBarState();
			_ncCaptionMove = false;
		}
	}
	function onNcMouseUp(x, y, btn, rg, cb) {
		if (_ncCaptionClick && btn == mbRight && rg == global.Window.nchtMenu && menuBarRightClickEnabled) {
			var pop = MenuItem.popupEx(tpmRightButton, void, void, this, void, menuBarHidePopupMenu);
			if (pop !== void) {
				changeMenuBarState(pop.state);
				cb() if (cb !== void);
			}
		}
		_ncCaptionClick = void;
	}
	function onMouseLeave  () { checkMenuBarHideTrackPseudoMenu(); callHook("onMouseLeaveHook"); }
	function onNcMouseLeave() { checkMenuBarHideTrackPseudoMenu(); }
	function checkMenuBarHideTrackPseudoMenu() {
		if (_ncCaptionMove || trackTempMenuShowing > 0) {
			var cur = System.getCursorPos();
			if (trackTempMenuShowing > 0) {
				trackTempMenu(false) if (ncHitTest(cur.x, cur.y) != global.Window.nchtMenu);
			} else {
				var rect = getClientRect();
				if (cur.x >= rect.x        && cur.y >= rect.y &&
					cur.x <  rect.x+rect.w && cur.y <  rect.y+System.getSystemMetrics("CYMENU")) {
					trackMenuByMouse(true);
				}
			}
		}
		_ncCaptionMove = false;
	}
	var _ncCaptionMove;
	function onNcMouseMove(x, y, rg) {
		if (!menuBarHideModeEnable || menuBarState >= 0 || fullScreen) return;
		with (global.Window) {
			_ncCaptionMove = (rg == .nchtCaption || rg == .nchtSysMenu);
			if (trackTempMenuShowing > 0 && rg != .nchtMenu) trackTempMenu(false);
		}
	}
	function onMouseMove(x, y) {
		if (pseudoFullScreened && y == 0 && menuBarEnable && menuBarState) trackPseudoMenu();
		else if (trackTempMenuShowing) checkMenuBarHideTrackPseudoMenu();
		var hook = mouseMoveHook;
		if (hook.count > 0) with (primaryLayer) callHook(hook, .cursorX, .cursorY);
		return super.onMouseMove(...);
	}

	// MenuItem.shortcut からテーブルを作成
	function makePseudoMenuTable(menu, table) {
		var list = menu.children, first = (table === void);
		table = %[] if (first);
		for (var i = list.count-1; i >= 0; i--) {
			var item = list[i];
			if (item.children.count > 0) makePseudoMenuTable(item, table);
			else if (item.shortcut != "") {
				var sc = item.shortcut.split("+");
				var key = sc[sc.count-1], sft = 0, vk;
				if (key == "") continue;
				sc.erase(sc.count-1);
				key = key.toUpperCase();
				vk  = shortcutTextToVirtualKeyString[key];
				key = vk if (vk != "");
				if (typeof global["VK_"+key] == "undefined") continue;
				key =     +global["VK_"+key];
				table[key] = %[] if (table[key] === void);
				for (var j = sc.count-1; j >= 0; j--) {
					var tag = sc[j];
					tag = tag.toLowerCase() if (tag != "");
					switch (tag) {
					case "shift":   sft |= ssShift; break;
					case "ctrl":    sft |= ssCtrl;  break;
					case "alt":     sft |= ssAlt;   break;
					}
				}
				table[key][sft] = item.onClick;
			}
		}
		if (first && !exEventEnabled) {
			var f10 = table[VK_F10];
			f10 = table[VK_F10] = %[] if (f10 === void);
			f10[0] = trackMenuByKey if (f10[0] === void);
		}
		return table;
	}
	// shortcut ⇒ VK_* 例外変換用テーブル
	var shortcutTextToVirtualKeyString = (const)%[
		"DEL"=>"DELETE",
		"ENTER"=>"RETURN",
		"PAGEUP"=>"PRIOR",
		"PAGEDOWN"=>"NEXT"//,
		];
	// 擬似フルスクリーン時ショートカットキー処理
	function pseudoMenuShortcut(key, shift) {
		var table = pseudoFullScreened ? _pseudoFullScreen.shortcut : getMenuBarPseudoShortcut();
		if (table !== void) {
			shift &= ~ssRepeat if (shift & ssAlt); // [XXX] ALTでリピートフラグが正しく立たない
			var info = table[key], func;
			if ((info !== void) &&
				(func  = info[+shift]) !== void &&
				!(shift & ssRepeat))
			{
				func();
				return true;
			}
		}
		return false;
	}
	function onAccelKeyMenu(key, f) {
		var info, table = pseudoFullScreened ? _pseudoFullScreen.shortcut : getMenuBarPseudoShortcut();
		return  table !== void && (info = table[key]) !== void && info[+ssAlt] !== void;
	}
	function onStartKeyMenu(key) {
		if (key || menu.visible) return;
		return (trackMenuByKey(true) <= 0);
	}

	var ignoreTrackPseudoMenu = false, trackTempMenuShowing = 0;
	function trackPseudoMenu(bykey, rclick) {
		if ((!bykey && ignoreTrackPseudoMenu) || trackTempMenuShowing) return;
		rclick &= exEventEnabled;
		var popup = new PopupMenuWindow(this, bykey ? 1: -1,/*x*/,/*y*/,/*w*/, !rclick);
		if (rclick) with (popup) {
			._ncCaptionClick = void;
			.menuBarHidePopupMenu = menuBarHidePopupMenu;
			.menuBarRightClickEnabled = menuBarRightClickEnabled;
			.changeMenuBarState   = changeMenuBarState;
			.onNcMouseDown = (onNcMouseDown incontextof popup);
			.onNcMouseUp__ = (onNcMouseUp   incontextof popup);
			.onNcMouseUp   = function(x, y, btn, rg) { onNcMouseUp__(x, y, btn, rg, close); } incontextof popup;
			.showModal();
		}
		invalidate popup;
	}
	function closeTempMenu() { trackTempMenu(false,, true); }
	function trackTempMenu(vis, bykey, force) {
		if (vis && !bykey && ignoreTrackPseudoMenu) return;
		var old = trackTempMenuShowing;
		trackTempMenuShowing = !vis ? 0 : bykey ? -1 : 1;
		if (force || !old != !vis) {
			if  (vis) menu.visible = true;
			else {
				menu.visible = false if (pseudoFullScreened);
				updateMenuBarState();
			}
		}
	}
	function onExitMenuLoop() {
		if (trackTempMenuShowing) {
			trackTempMenuShowing = false;
			var trig;
			if (typeof this.closeTempMenuTrigger == "Object") {
				trig = this.closeTempMenuTrigger;
			} else {
				trig = this.closeTempMenuTrigger = new AsyncTrigger(closeTempMenu, "");
				trig.mode = atmAtIdle;
				trig.cached = true;
				add(trig);
			}
			trig.trigger();
		}
	}
	function trackMenuByMouse() {
		if (menuBarTempMenuEnabled) trackTempMenu(true);
		else trackPseudoMenu(false, menuBarRightClickEnabled);
	}
	function trackMenuByKey(delay) {
		var  fs = super.fullScreen;
		if (!fs && menuBarEnable && (menuBarState/* || pseudoFullScreened*/)) {
			if (menuBarTempMenuEnabled && !pseudoFullScreened) trackTempMenu(true, true);
			else {
				trackPseudoMenu(true);
				return -1;
			}
		} else return fs;
		return true;
	}

	// スクリーンセーバー抑制
	function onScreenSave()       { return true; }
	function onMonitorPower(mode) { return (mode > 0); }

	// Vista以降プレビューサムネ抑制[要WindowEx.dll]
	var     _appIconicPreview = false;
	property appIconicPreview {
		getter { return _appIconicPreview; }
		setter(v) {
			scflags.appIconicPreview = _appIconicPreview = !!v;
			internalSetAppIconicPreview();
		}
	}
	function internalSetAppIconicPreview(iconic = _appIconicPreview) {
		if (typeof System.setIconicPreview == "Object") {
			/**/   System.setIconicPreview(iconic);
		}
	}

	//-------------------------------------------------------- リサイズ処理 --

	property maximizeMode { getter {
		var tag = System.getArgument("-maximizemode");
		var map = %[ auto:-1, maximize:0, fullscreen:1 ];
		return (tag != "" && typeof map[tag] != "undefined") ? map[tag] : -1;
	} }

	function onMaximizeQuery() {
		if (pseudoFullScreened) return;
		var mode = maximizeMode;
		if (!mode || (mode < 0 && windowResizeable)) return;
		onFullScreenMenuItemClick();
		return true;
	}
	var _minimized = false;
	function changeMinimized(v) {
		callHook("onMinimizeChangedHook", v) if (_minimized != v);
		_minimized = v;
	}
	function onMinimize() { changeMinimized(true);  }
	function onHide()     { changeMinimized(true);  }
	function onShow()     {
		var refull = _minimized && super.fullScreen;
		changeMinimized(false);
		if (refull) {
			super.fullScreen = false;
			super.fullScreen = true;
		}
	}

	var _onResizeProcess = true;
	function onResize() {
		if (trackTempMenuShowing) return super.onResize();
//		dm("onResize", innerWidth, innerHeight);
		if (!_minimized && _onResizeProcess && _resizeSnapshot === void) {
			_onResizeProcess = false;
			var numer = innerWidth, denom = pxWidth;
			var max = (typeof this.maximized != "undefined") ? maximized : false;
			if (max) {
				var vert = (innerWidth/innerHeight > pxWidth/pxHeight);
				var zoom = System.getArgument("-fszoom");
				if (!pseudoFullScreened) {
					var mz = System.getArgument("-maximizezoom");
					zoom = mz if (mz != "fszoom");
				}
				switch (zoom) {
				case "outer": vert = !vert; break;
				case "no":    numer = denom = 1; vert = false; break;
				case "middle": // 中間拡大
					var per = System.getArgument("-mzpercent");
					per = (per == "") ? 50 : (int)(+per);
					if (per < 0) {
						numer = -per;
						denom = 100;
					} else {
						per = 100 if (per > 100);
						var iw = innerWidth, ih = innerHeight;
						var pw =    pxWidth, ph =    pxHeight;
						if (vert) iw <-> ih, pw <-> ph;
						numer = (int)(iw + (ih - iw) * per / 100);
						denom = (int)(pw + (ph - pw) * per / 100);
					}
					vert = false;
					break;
				}
				if (vert) numer = innerHeight, denom = pxHeight;
			}
			setZoom(numer, denom, !max);
			callResizeHook();
			_onResizeProcess = true;
		}
		updateResizeMenuItems();
		return super.onResize();
	}

	function updateResizeMenuItems() {
		var maxfull = fullScreened || (typeof this.maximized != "undefined" && maximized);
		var en = exEventEnabled && !maxfull;
		if (typeof this.resetWindowSizeMenuItem == "Object") with (resetWindowSizeMenuItem) {
			.enabled = en;
			.checked = en && (origZoomNumer == origZoomDenom);
		}
		if (typeof this.disableWindowResizeMenuItem == "Object") with (disableWindowResizeMenuItem) {
			.enabled = en;
			.checked = !windowResizeable;
		}
	}

	var _windowResizeable = true; // フレームを掴んでリサイズ可能かどうか
	property windowResizeable {
		getter { return _windowResizeable; }
		setter(v) {
			_windowResizeable = v;
			disableResize = !v if (exEventEnabled);
			updateResizeMenuItems();
		}
	}
	function onResizing(rect) {
		if (_resizeSnapshot === void) onMoveSizeBegin();
		if (!windowResizeable) {
			with (_resizeSnapshot.window) {
				rect.x = .x;
				rect.y = .y;
				rect.w = .w;
				rect.h = .h;
			}
			return true;
		}
		var w, h, dw, dh, cw, ch;
		with (_resizeSnapshot) dw=.dw, dh=.dh, cw=.cw, ch=.ch;
		with (rect) {
			w = .w - dw;
			h = .h - dh;
			var sel;
			switch (.type) {
			case 1: case 2: sel = true;  break;
			case 3: case 6: sel = false; break;
			default: sel = (w / h) > (pxWidth / pxHeight); break;
			}
			if (sel) h = w * pxHeight \ pxWidth;
			else     w = h * pxWidth  \ pxHeight;
			if (w < pxWidth\4) {
				w = pxWidth\4;
				h = w * pxHeight \ pxWidth;
			}
			w += dw;
			h += dh;
			const repos = function(type, p, o, n, sel) {
				switch (type % 3) {
				case 0:  return sel ? p+(o-n)\2 : p;
				case 1:  return p+o-n;
				default: return p;
				}
			};
			var x = repos(.type,   .x, .w, w, true);
			var y = repos(.type\3, .y, .h, h, false);
			if (.x == x && .y == y && .w == w && .h == h) return false;
			.x = x;
			.y = y;
			.w = w;
			.h = h;
		}
		return true;
	}
	var _resizeSnapshot = void;
	function onMoveSizeBegin() {
		_resizeSnapshot = %[
		window:      getWindowRect(),
		client:      getClientRect(),
		innerWidth:  innerWidth,
		innerHeight: innerHeight
			];
		with (_resizeSnapshot) {
			.dw = .window.w  - .innerWidth;
			.dh = .window.h - .innerHeight;
		}
	}
	function onMoveSizeEnd() {
		setZoom(innerWidth, pxWidth);
//		dm("onResizeEnd/zoomed", innerWidth, innerHeight);
		callHook("onMoveSizeEndHook", this, _resizeSnapshot);
		_resizeSnapshot = void;
		callResizeHook();
	}
	function callResizeHook() {
//		dm("callResizeHook");
		callHook("onWindowResizeHook", this, innerWidth, innerHeight);
	}
	function onDisplayChanged() {
//		dm("callDisplayChanged");
		callHook("onDisplayChangedHook", this);
	}

	//------------------------------------------------ メニューアイテム関連 --

	function onExitMenuItemClick(sender)
	{
		close();
	}

	function onRightClickMenuItemClick(sender)
	{
		onPrimaryRightClick(); // 右クリックの動作
	}

	function onShowHistoryMenuItemClick(sender)
	{
		if (historyShowing) hideHistory(); else showHistory();
	}

	function onSkipToNextStopMenuItemClick(sender)
	{
        if (skipMode) cancelSkip(); else skipToStop();
        setMenuAccessibleAll();
	}

	function onAutoModeMenuItemClick(sender)
	{
        if(autoMode) {
			cancelAutoMode();
        } else {
            enterAutoMode();
        }
        setMenuAccessibleAll();
    }

	function onAutoModeWaitMenuClick(sender)
	{
		sender.checked = true;
		autoModePageWait = sender.wait;
		autoModeLineWait = sender.lwait;
		callHook("onAutoSpeedChanged", autospeed);
	}

	function onBackToAutoLabelStoreMenuItemClick(sender)
	{
		goBackAutoLabelStore();
	}

	function onBackStartMenuItemClick(sender)
	{
		goBackHistory();
	}

	function onGoToStartMenuItemClick(sender)
	{
		goToStartWithAsk();
	}

    function onAllSkipMenuItemClick(sender)
    {
		sender.checked = true;
		_allskip = sender.skip;
		saveSystemVariables();
    }

    function onCancelAutoWhenClickMenuItemClick(sender)
    {
		sender.checked = true;
		cancelAutoWhenClick = sender.cancel;
		saveSystemVariables();
    }

    function onCancelSkipWhenClickMenuItemClick(sender)
    {
		sender.checked = true;
		cancelSkipWhenClick = sender.cancel;
		saveSystemVariables();
    }
	
	function onChSpeedMenuItemClick(sender)
	{
		sender.checked = true;
		userChSpeed = sender.speed;
		setUserSpeed();
		saveSystemVariables();
	}

	function onChNonStopToPageBreakItemClick(sender)
	{
		chNonStopToPageBreak = ! chNonStopToPageBreak;
		if(typeof this.chNonStopToPageBreakItem != "undefined")
			chNonStopToPageBreakItem.checked = chNonStopToPageBreak;
			saveSystemVariables();
	}

	function onCh2ndNonStopToPageBreakItemClick(sender)
	{
		ch2ndNonStopToPageBreak = ! ch2ndNonStopToPageBreak;
		if(typeof this.ch2ndNonStopToPageBreakItem != "undefined")
			ch2ndNonStopToPageBreakItem.checked = ch2ndNonStopToPageBreak;
			saveSystemVariables();
	}

	function onCh2ndSpeedMenuItemClick(sender)
	{
		sender.checked = true;
		userCh2ndSpeed = sender.speed;
		setUserSpeed();
		saveSystemVariables();
	}

	function onChAntialiasMenuItemClick(sender)
	{
		chDefaultAntialiased = !chDefaultAntialiased;
		if(typeof this.chAntialiasMenuItem != "undefined")
			chAntialiasMenuItem.checked = chDefaultAntialiased;
		setMessageLayerUserFont();
		saveSystemVariables();
	}

	function onChChangeFontMenuItem(sender)
	{
		selectFont();
		saveSystemVariables();
	}

	function onRestoreMenuClick(sender)
	{
		if(!freeSaveDataMode) return;
		loadBookMarkFromFileWithAsk();
	}

	function onStoreMenuClick(sender)
	{
		if(!freeSaveDataMode) return;
		saveBookMarkToFileWithAsk();
	}

	function onWindowedMenuItemClick(sender)
	{
		changeFullScreenOrWindow(false);
	}
	function onFullScreenMenuItemClick(sender)
	{
		changeFullScreenOrWindow(true);
	}
	function changeFullScreenOrWindow(isfull)
	{
		if ( !fullScreened == !isfull) return;
		try { fullScreen   =   isfull; }
		catch(e) {
			Debug.notice((isfull ? "フルスクリーン" : "ウィンドウ") + "モードに移行できませんでした : " + e.message);
		}
		var  menu = ((fullScreened = fullScreen) != 0) ? "fullScreenMenuItem" : "windowedMenuItem";
		this[menu].checked = true if (typeof this[menu] == "Object");
		updateResizeMenuItems();
		saveSystemVariables();
	}

	function onResetWindowSizeMenuItemClick()
	{
		setZoom(1, 1);
		updateResizeMenuItems();
	}
	function onDisableWinwodResizeMenuItemClick()
	{
		windowResizeable = !windowResizeable;
		updateResizeMenuItems();
	}

	function onHelpIndexMenuItemClick(sender)
	{
		// ヘルプファイルを開く
		System.shellExecute(Storages.getLocalName(System.exePath) + helpFile);
	}

	function onHelpAboutMenuItemClick(sender)
	{
		// 「このソフトについて」ウィンドウを表示
		var win = this.helpAboutWindow = new global.KAGWindow(false, aboutWidth, aboutHeight);
		win.debugLevel = tkdlNone;
		win.borderStyle = bsToolWindow;
		win.setInnerSize(aboutWidth, aboutHeight);
		win.setPos(left + ((width - win.width)>>1), top + ((height - win.height)>>1));
		win.rightClickMenuItem = new KAGMenuItem(this, "RightClick", 0, win.onRightClickMenuItemClick, false); // ESCで消せるように
		win.process(aboutStorage ,,, true); // about.ks を immediate で表示
		win.showModal(); // モード付きで表示
		invalidate win.rightClickMenuItem;
		invalidate win;
		delete this.helpAboutWindow;
	}

    // シナリオ実行ダイアログ呼び出し
    function onExecDialogMenuItemClick(sender)
    {
        execDialog();
    }

    function onChDebugLogMenuItemClick(sender) {
        logMode = !logMode;
		if (logMode) initLogPad(true);
		else if (logPad !== void) logPad.visible = false;
        if (typeof sender != "undefined") {
            sender.checked = logMode;
        }
		callHook("onChDebugLogMenuItemChanged", logMode);
        initLog();
    }

    function onOutputDebugLogMenuItemClick(sender) {
        outputLog();
    }
    
	function onReloadScenarioMenuItemClick(sender)
	{
		var fn = getBookMarkFileNameBySystem("reload");
		saveBookMarkToFile  (fn, false);
		loadBookMarkFromFile(fn);
	}

	function onShowConsoleMenuItemClick(sender)
	{
		Debug.console.visible = true;
	}

	function onShowContollerMenuItemClick(sender)
	{
		Debug.controller.visible = true;
	}

	function internalSetMenuAccessibleAll(menu, state)
	{
		// autoEnable が true のすべてのメニュー項目の accessible に値 state を
		// 設定する
		if(typeof menu.autoEnable != "undefined" && menu.autoEnable)
			menu.accessible = state;
		if(typeof menu.stopRecur == "undefined" || !menu.stopRecur)
		{
			var children = menu.children;
			for(var i = children.count -1; i >= 0; i--)
				internalSetMenuAccessibleAll(children[i], state); // 再帰
		}
	}

	function canStore()
	{
		return storeEnabled && storeLabelPassed && !isRecollection;
	}

	function canRestore()
	{
		return restoreEnabled && !isRecollection;
	}

	function setMenuAccessibleAll()
	{
		// メニュー項目の使用可/不可を設定する

		var modal = historyShowing || messageLayerHiding || currentDialog || panelModal;
		var state = inStable && !modal;
		var selShow = selectShowing ||  mapSelectShowing ||  transShowing ||  panelShowing;
		var canRun  = !modal && !inSleep  && !selShow;
		var canSkip = canRun && (inStable || skipMode) && nextSkipEnabled && (allskip || getCurrentRead());
		var canAuto = canRun && (inStable || autoMode) && !skipMode;

		// autoEnable が true のすべてのメニュー項目の accessible の
		// 値を設定する
		internalSetMenuAccessibleAll(menu, state);

        // その他のメニューの使用可/不可
        if(typeof this.skipToNextStopMenuItem != "undefined")
            skipToNextStopMenuItem.enabled = canSkip;

		if(typeof this.rightClickMenuItem != "undefined")
            rightClickMenuItem.enabled = inStable && !historyShowing && !currentDialog;

		if(typeof this.showHistoryMenuItem != "undefined")
            showHistoryMenuItem.enabled = inStable && ((!messageLayerHiding && !mapSelectShowing && !transShowing && !currentDialog && !panelShowing &&
				historyEnabled) || historyShowing);

        if(typeof this.autoModeMenuItem != "undefined")
            autoModeMenuItem.enabled = canAuto;

		if(typeof this.goBackMenuItem != "undefined")
			goBackMenuItem.enabled = state && isHistoryOfStoreAlive();

		if(typeof this.backToAutoLabelStoreMenuItem != "undefined")
			backToAutoLabelStoreMenuItem.enabled = state && autoLabelStore !== void && autoLabelStore.count > 0;

		if(typeof this.goToStartMenuItem != "undefined")
			goToStartMenuItem.enabled = state && startAnchorEnabled;

		if(typeof this.exitMenuItem != "undefined")
			exitMenuItem.enabled = !currentDialog;

		if(typeof this.storeMenu != "undefined")
		{
			var st = state && canStore();
			var children = storeMenu.children;
			if(freeSaveDataMode) storeMenu.enabled = st;
			for(var i = children.count - 1; i >= 0; i--)
			{
				var obj = children[i];
				obj.enabled = obj.orgEnabled && st;
			}
		}

		if(typeof this.restoreMenu != "undefined")
		{
			var st = state && canRestore();
			var children = restoreMenu.children;
			if(freeSaveDataMode) restoreMenu.enabled = st;
			for(var i = children.count - 1; i >= 0; i--)
			{
				var obj = children[i];
				obj.enabled = obj.orgEnabled && st;
			}
		}

        // 全制御
		if (this == global.Window.mainWindow && typeof global.onModeChange !== "undefined") {
			global.onModeChange(this, autoMode, skipMode, canAuto, canSkip, selShow, modal, canRun);
		}
		callHook("onModeChangeHook", autoMode, skipMode, canAuto, canSkip, selShow, modal, canRun);
    }

	//----------------------------------------------- マウスキーを有効にする --

	function enableMouseKey()
	{
		// マウスキーを有効にする
		if(mouseKeyEnabledCount == 0)
		{
			useMouseKey = true;
			mouseCursorState = mcsVisible;
		}
		mouseKeyEnabledCount++; // 参照カウンタ方式
	}

	function disableMouseKey()
	{
		// マウスキーを無効にする
		mouseKeyEnabledCount --;
		if(mouseKeyEnabledCount == 0) useMouseKey = false;
	}

	//----------------------------------------------------- システム変数関連 --

	/**
	 * 安全なevalStorage（外部からのevalによるhackを防ぐ）
	 */
	var disableSafeEvalStorage = false; // Config.tjsで抑制できるようにする
	function safeEvalStorage(fn, *) {
		if (!disableSafeEvalStorage) {
			var tmp = [].load(fn, *);
			// 冒頭に(const)があるかどうかで判断
			if (tmp[0] == "" || tmp[0].indexOf("(const)") != 0)
				throw new Exception("不正なデータです:"+Storages.extractStorageName(fn));
		}
		return Scripts.evalStorage(fn, *);
	}

    /**
     * 書き込みテストを行う
     */
    function checkSave()
    {
		if(!readOnlyMode) {
			
			var appName = (typeof global.ENV_Maker != "undefined") ? global.ENV_Maker + "/" + global.ENV_GameName : System.title;
/*
			// セーブ場所の差し替え
			var udp = Storages.chopStorageExt(System.exeName) + ".udp";
			if(Storages.isExistentStorage(udp)) {
				var vars = [];
				vars.load(udp);
				if (vars[0] == "document") {
					if (System.personalPath != System.exePath) {
						saveDataLocation = System.personalPath + appName;
					}
				} else if (vars[0] == "application") {
					if (System.appDataPath != System.exePath) {
						saveDataLocation = System.appDataPath + appName;
					}
				}
			}
*/
			// 書き込み権限テスト
			var dummy = ["test"];
			var fn = saveDataLocation + "/" + "savecheck";
			var saved = false;
			try {
				(Array.saveStruct incontextof dummy)(fn);
				saved = true;
			} catch(e) {
				Debug.notice("checkSave失敗 : " + e.message);
				if (System.personalPath != System.exePath) {
					errorInform("savemydoc");
					saveDataLocation = System.personalPath + appName;
				} else {
					readOnlyMode = true;
					errorInform("readonly");
				}
			}
			if (saved) try {
				var tmp = [].load(fn);
				// 冒頭に(const)がない場合はsafeEvalStorageチェックが使えない
				if (tmp[0] == "" || tmp[0].indexOf("(const)") != 0) {
					disableSafeEvalStorage = true;
					Debug.notice("saveStructによる(const)無効 : " + System.versionString);
				}
			} catch(e) {
				Debug.message("(const)check失敗 : " + e.message);
			}

			dm("セーブデータ保存場所:" + saveDataLocation );
		}
    }
    
	function loadSystemVariables()
	{
		// システム変数の読み込み
		try
		{
			var fn = saveDataLocation + "/" + dataName +
				"sc.ksd";
			if(Storages.isExistentStorage(fn))
			{
				scflags = safeEvalStorage(fn);
				scflags = %[] if scflags === void;
			}
			else
			{
				scflags = %[];
			}

			var fn = saveDataLocation + "/" + dataName +
				"su.ksd";
			if(Storages.isExistentStorage(fn))
			{
				sflags = safeEvalStorage(fn);
				sflags = %[] if sflags === void;
			}
			else
			{
				sflags = %[];
			}
		}
		catch(e)
		{
			errorInform("initexception", e.message);
			scflags = %[];
			sflags = %[];
		}
	}

	function setSystemStateFromSystemVariables()
	{
		// システム変数に基づいてシステムを設定
		// (フルスクリーン関連をのぞく)
		if(scflags.autoModePageWait !== void)
		{
			if(typeof this.autoModeWaitMenu !== "undefined")
			{
				var children = autoModeWaitMenu.children;
				for(var i = children.count-1; i >= 0; i--)
				{
					var item = children[i];
					if(typeof item.wait !== "undefined" && item.wait == scflags.autoModePageWait)
					{
						item.checked = true;
						break;
					}
				}
			}
		}

		if(scflags.userChSpeed !== void)
		{
			if(typeof this.chSpeedMenu !== "undefined")
			{
				var children = chSpeedMenu.children;
				for(var i = children.count-1; i >= 0; i--)
				{
					var item = children[i];
					if(typeof item.speed !== "undefined" && item.speed == scflags.userChSpeed)
					{
						item.checked = true;
						break;
					}
				}
			}
		}

		if(scflags.userCh2ndSpeed !== void)
		{
			if(typeof this.chSpeedMenu !== "undefined")
			{
				var children = ch2ndSpeedMenu.children;
				for(var i = children.count-1; i >= 0; i--)
				{
					var item = children[i];
					if(typeof item.speed !== "undefined" && item.speed == scflags.userCh2ndSpeed)
					{
						item.checked = true;
						break;
					}
				}
			}
		}

		_tempAllskip = void;
		if (scflags.allskip !== void) {
			allskip = scflags.allskip;
		}
		if (typeof this.allskipMenu !== "undefined") {
			var children = allskipMenu.children;
			for(var i = children.count-1; i >= 0; i--) {
				var item = children[i];
				if(typeof item.skip !== "undefined" && item.skip == allskip) {
					item.checked = true;
					break;
				}
			}
        }

		windowResizeable = scflags.windowResizeable !== void ? scflags.windowResizeable : _windowResizeable;

        voicecut = scflags.voicecut if scflags.voicecut !== void;
        voicecutpage = scflags.voicecutpage if scflags.voicecutpage !== void;
        bgmdown = scflags.bgmdown if scflags.bgmdown !== void;
        bgmdownlevel = scflags.bgmdownlevel if scflags.bgmdownlevel !== void;
        
        afterauto = scflags.afterauto if scflags.afterauto !== void;
        afterskip = scflags.afterskip if scflags.afterskip !== void;
		nosewhenskip = scflags.nosewhenskip if scflags.nosewhenskip !== void;
		cancelSkipWhenClick = scflags.cancelSkipWhenClick if scflags.cancelSkipWhenClick !== void;
		cancelAutoWhenClick = scflags.cancelAutoWhenClick if scflags.cancelAutoWhenClick !== void;
		enterSkipOnReadedLabel = scflags.enterSkipOnReadedLabel if scflags.enterSkipOnReadedLabel !== void;
		
		lastSaveDataNameGlobal = scflags.lastSaveDataNameGlobal if scflags.lastSaveDataNameGlobal !== void;

@if (!OLDBOOKMARK)
		bookMarkIndex = scflags.bookMarkIndex if scflags.bookMarkIndex !== void;
@endif
@if (OLDBOOKMARK)
		bookMarkNames = scflags.bookMarkNames if scflags.bookMarkNames !== void;
		bookMarkDates = scflags.bookMarkDates if scflags.bookMarkDates !== void;
		bookMarkProtectedStates = scflags.bookMarkProtectedStates if scflags.bookMarkProtectedStates !== void;
		bookMarkStorages = scflags.bookMarkStorages if scflags.bookMarkStorages !== void;
		bookMarkPlayTimes = scflags.bookMarkPlayTimes if scflags.bookMarkPlayTimes !== void;
		bookMarkInfos = scflags.bookMarkInfos if scflags.bookMarkInfos !== void;
@endif

		autoModePageWait = scflags.autoModePageWait if scflags.autoModePageWait !== void;
		autoModeLineWait = scflags.autoModeLineWait if scflags.autoModeLineWait !== void;
		userChSpeed = scflags.userChSpeed if scflags.userChSpeed !== void;
		userCh2ndSpeed = scflags.userCh2ndSpeed if scflags.userCh2ndSpeed !== void;

		setUserSpeed();

		chNonStopToPageBreak = scflags.chNonStopToPageBreak if scflags.chNonStopToPageBreak !== void;
		if(typeof this.chNonStopToPageBreakItem != "undefined")
			chNonStopToPageBreakItem.checked = chNonStopToPageBreak;

		ch2ndNonStopToPageBreak = scflags.ch2ndNonStopToPageBreak if scflags.ch2ndNonStopToPageBreak !== void;
		if(typeof this.ch2ndNonStopToPageBreakItem != "undefined")
			ch2ndNonStopToPageBreakItem.checked = ch2ndNonStopToPageBreak;

		chDefaultAntialiased = scflags.chDefaultAntialiased if scflags.chDefaultAntialiased !== void;
		if(typeof this.chAntialiasMenuItem != "undefined")
			chAntialiasMenuItem.checked = chDefaultAntialiased;

		chDefaultFace = scflags.chDefaultFace if scflags.chDefaultFace !== void;
		setMessageLayerUserFont();

		this.wavevolume = (scflags.waveVolume !== void) ? scflags.waveVolume : global.WaveSoundBuffer.globalVolume / 1000;
		this.waveenable = (scflags.waveEnable !== void) ? scflags.waveEnable : true;

		bgm.restoreSystemState(scflags);

        for(var i = 0; i<numSEBuffers + numSysSEBuffers; i++)
		{
			se[i].restoreSystemState(scflags);
		}

		updateVoiceSpeedMenu();

        if (scflags.logMode !== void) {
            logMode = scflags.logMode;
			initLogPad(false) if (logMode);
        }
        if (typeof this.chDebugLogMenuItem != "undefined") {
            chDebugLogMenuItem.checked = logMode;
        }
        
        if (scflags.debugLevel !== void) {
            debugLevel = scflags.debugLevel;
        }
        switch (debugLevel) {
        case tkdlNone:
            if(typeof this.debugLevelNoneMenuItem != "undefined")
                debugLevelNoneMenuItem.checked = true;
            break;
        case tkdlSimple:
            if(typeof this.debugLevelSimpleMenuItem != "undefined")
                debugLevelSimpleMenuItem.checked = true;
            break;
        case tkdlVerbose:
            if(typeof this.debugLevelVerboseMenuItem != "undefined")
                debugLevelVerboseMenuItem.checked = true;
            break;
        }

        if (scflags.debugwin !== void && scflags.debugwin) {
            debugwin.visible = true;
        }
        if (typeof this.debugWinMenuItem != "undefined" && _debugwin !== void) {
            debugWinMenuItem.checked = debugwin.visible;
        }

		_appIconicPreview = !!scflags.appIconicPreview if (scflags.appIconicPreview !== void);
		internalSetAppIconicPreview();

		updateResizeMenuItems();
		changeMenuBarState(scflags.menuBarState !== void ? scflags.menuBarState : defaultMenuBarState);
    }

	function clearSystemVariables()
	{
		// システム変数のクリア
		(Dictionary.clear incontextof sflags)();
	}

	function saveSystemVariables()
	{
		// システム変数の保存
		if(!isMain) return;

		// プラグインを呼ぶ
		forEachEventHook('onSaveSystemVariables');

		// フルスクリーン
		scflags.fullScreen = fullScreened;
		scflags.zoomNumer = origZoomNumer;
		scflags.zoomDenom = origZoomDenom;

		scflags.menuBarState = menuBarState;

		scflags.windowResizeable = windowResizeable;
		scflags.windowMaximized  = false;
		var rect;
		if (typeof this.maximized != "undefined" && maximized) {
			scflags.windowMaximized  = !fullScreen;
			if (_pseudoFullScreen !== void) with (_pseudoFullScreen) {
				rect = .rect;
				scflags.windowMaximized = .max && .restmax;
			} else if (typeof this.getNormalRect != "undefined")
				rect = getNormalRect();
		} else if (fullScreened && typeof _lastWindowSize == "Object") {
			rect = _lastWindowSize.window; //if (!pseudoFullScreened);
		}
		if (rect !== void) {
			scflags.windowLeft   = rect.x;
			scflags.windowTop    = rect.y;
			scflags.windowWidth  = rect.w;
			scflags.windowHeight = rect.h;
		} else {
			scflags.windowLeft   = left;
			scflags.windowTop    = top;
			scflags.windowWidth  = width;
			scflags.windowHeight = height;
		}
		// コンソール表示状態
		with (Debug.console) {
			scflags.consoleVisible = .visible;
			if (typeof      .getPlacement == "Object") {
				var place = .getPlacement();
				if (place !== void) scflags.consolePlacement = place;
			}
		}

		// 文字表示速度
		scflags.autoModePageWait = autoModePageWait;
		scflags.autoModeLineWait = autoModeLineWait;
		scflags.userChSpeed = userChSpeed;
		scflags.userCh2ndSpeed = userCh2ndSpeed;
		scflags.chDefaultAntialiased = chDefaultAntialiased;
		scflags.chDefaultFace = chDefaultFace;
		scflags.chNonStopToPageBreak = chNonStopToPageBreak;
		scflags.ch2ndNonStopToPageBreak = ch2ndNonStopToPageBreak;

		// ブックマーク名
@if (!OLDBOOKMARK)
		scflags.bookMarkIndex = bookMarkIndex;
@endif
@if (OLDBOOKMARK)
		scflags.bookMarkNames = bookMarkNames; // (コピーではなくて)参照で十分
		scflags.bookMarkDates = bookMarkDates;
		scflags.bookMarkProtectedStates = bookMarkProtectedStates;
		scflags.bookMarkStorages = bookMarkStorages;
		scflags.bookMarkPlayTimes = bookMarkPlayTimes;
		scflags.bookMarkInfos = bookMarkInfos;
@endif
		scflags.lastSaveDataNameGlobal = lastSaveDataNameGlobal;

        // ボイスカット処理設定
        scflags.voicecut = voicecut;
        scflags.voicecutpage = voicecutpage;
        scflags.bgmdown = bgmdown;
        scflags.bgmdownlevel = bgmdownlevel;
        
        // 全てスキップ
        scflags.allskip = _allskip;
        
        scflags.afterauto = afterauto;
        scflags.afterskip = afterskip;
        scflags.nosewhenskip = nosewhenskip;
		scflags.cancelAutoWhenClick = cancelAutoWhenClick;
		scflags.cancelSkipWhenClick = cancelSkipWhenClick;
		scflags.enterSkipOnReadedLabel = enterSkipOnReadedLabel;
	
        scflags.logMode = logMode;
        scflags.debugLevel = debugLevel;
        scflags.debugwin = _debugwin !== void && debugwin.visible;

		if (readOnlyMode) return;
		// ファイルに書き込む
		while (true) {
			try {
				var fn = saveDataLocation + "/" + dataName + "sc.ksd";
				(Dictionary.saveStruct incontextof scflags)(fn, saveDataMode);
				var fn = saveDataLocation + "/" + dataName + "su.ksd";
				(Dictionary.saveStruct incontextof sflags)(fn, saveDataMode);
			} catch (e) {
				Debug.notice("saveSystemVariables失敗 : " + e.message);
				if (callHook("onSaveSystemVariablesFailed", e.message))
					continue;
				readOnlyMode = true;
			}
			break;
		}
	}

	//------------------------------------------------------- ゲーム変数関連 --

    /**
     * 内部保存処理
     * @param f 記録先 loadFromLine 行番号ジャンプ指定
     */
	function internalStoreFlags(f, loadFromLine)
	{
		// f に現在の状態を記録する

		// KAGWindow に関するもの
		(Dictionary.clear incontextof f)(); // クリア
		// ここの [start_store_vars] から [end_store_vars] で囲まれた部分は
		// 末端の perl スクリプトによって自動的に処理されるので、このマークを消したり
		// このマークの間を編集したりしないこと。
		// [start_store_vars]
		f.lastSaveDataName = lastSaveDataName;
		f.quaking = quaking;
		f.quakeEndTick = quakeEndTick;
		f.quakeHorzMax = quakeHorzMax;
		f.quakeVertMax = quakeVertMax;
		f.quakePhase = quakePhase;
		f.textWriteEnabled = _textWriteEnabled;
		f.backVoiceMode = _backVoiceMode;
		f.historyWriteEnabled = historyWriteEnabled;
		f.historyEnabled = historyEnabled;
		f.numCharacterLayers = numCharacterLayers;
		f.numMessageLayers = numMessageLayers;
		f.currentNum = currentNum;
		f.currentPage = currentPage;
		f.currentWithBack = currentWithBack;
		f.chUserMode = chUserMode;
		f.chSpeed = chSpeed;
		f.actualChSpeed = actualChSpeed;
		f.beforeNoWaitActualChSpeed = beforeNoWaitActualChSpeed;
		f.beforeNoWaitChUserMode = beforeNoWaitChUserMode;
		f.clickSkipEnabled = clickSkipEnabled;
		f.noeffectEnabled = noeffectEnabled;
		f.nextSkipEnabled = nextSkipEnabled;
		f.canCancelSkipByClick = canCancelSkipByClick;
		f.autoWCEnabled = autoWCEnabled;
		f.autoWCChars = autoWCChars;
		f.rightClickEnabled = rightClickEnabled;
		f.rightClickCall = rightClickCall;
		f.rightClickJump = rightClickJump;
		f.rightClickTarget = rightClickTarget;
		f.rightClickStorage = rightClickStorage;
		f.rightClickName = rightClickName;
		f.rightClickCurrentMenuName = rightClickCurrentMenuName;
		f.lastClickGlyphVisible = lastClickGlyphVisible;
		f.startAnchorEnabled = startAnchorEnabled;
		f.storeEnabled = storeEnabled;
		f.restoreEnabled = restoreEnabled;
        f.currentStorage  = currentStorage;
        f.currentLabel    = currentLabel;
		f.currentPageName = currentPageName;
		// [end_store_vars]

		// perl スクリプトによって自動的に処理されないもの、いくつか。

		// マウスカーソル
		f.cursorDefault      = getSafeCursor(cursorDefault);
		f.cursorPointed      = getSafeCursor(cursorPointed);
		f.cursorWaitingClick = getSafeCursor(cursorWaitingClick);
		f.cursorWaitingAuto  = getSafeCursor(cursorWaitingAuto);
		f.cursorDraggable    = getSafeCursor(cursorDraggable);

		// 自動ウェイト
		f.autoWCWaits = [];
		f.autoWCWaits.assign(autoWCWaits); // 自動ウェイトのウェイト

		// bgm
		f.bgm = bgm.store();

		// 効果音
		f.se = [];
		for(var i = 0; i<numSEBuffers; i++)
		{
			f.se[i] = se[i].store();
		}

		// メッセージレイヤ
		f.foreMessageLayers = [];
		f.backMessageLayers = [];
		for(var i = 0; i < numMessageLayers; i++)
		{
			f.foreMessageLayers[i] = fore.messages[i].store();
			f.backMessageLayers[i] = back.messages[i].store();
		}

		// 前景レイヤ
		f.foreCharacterLayers = [];
		f.backCharacterLayers = [];
		for(var i = 0; i < numCharacterLayers; i++)
		{
			f.foreCharacterLayers[i] = fore.layers[i].store();
			f.backCharacterLayers[i] = back.layers[i].store();
		}

		// 背景レイヤ
		f.foreBaseLayer = fore.base.store();
		f.backBaseLayer = back.base.store();

		// ウィンドウキャプション
		f.caption = caption;

		// コンダクタ
		f.mainConductor = mainConductor.store();
		f.mainConductor.lineSaveMode = true if loadFromLine;
		if(!saveMacros) f.mainConductor.macros = void;
		// マクロ情報を void で上書きすると栞中のマクロ情報は無効になる

		// メッセージ履歴
		if(historyLayer.storeState)
		{
			f.historyData = historyLayer.save();
		}

		// ムービー
		if( isMain )
		{
			f.movies = [];
			for( var i = 0; i < numMovies; i++)
				f.movies[i] = movies[i].store();
		}

        f.erAfterPage = erAfterPage;
		storeVoiceMap(f);
		storeLoopVoice(f);
		
        f.lineMode  = lineMode;
        f.crAfterName = crAfterName;
        f.noCrOnce = noCrOnce;
        f.noErOnce = noErOnce;
        f.autoIndent = autoIndent;
        f.autoLabelCount = autoLabelCount;
        f.afterPage = afterPage;
        f.emptyLine = emptyLine;
		f.commandLine = commandLine;
        f.prevEmptyLine = prevEmptyLine;
        
		// storeHook
		forEachEventHook('onStore',
			function(handler, f) { handler(f.flags, f.options); } incontextof this,
			%[flags:f, options:void]);
	}

	function storeFlags(loadFromLine=false)
	{
		// pcflags, pflags に値を書き込む

		// flags -> pflags
		(Dictionary.assignStruct incontextof pflags)(flags);

		internalStoreFlags(pcflags, loadFromLine);
	}

	function internalRestoreFlags(f, clear = true, elm = void)
	{
		// f から情報を読み出す
		// clear が true ならばメッセージレイヤをクリアする
		// se, bgm がそれぞれ true ならばその情報も反映させる
		// backlay が true の場合は、表画面にロードすべきものを裏画面にロードする

		// KAGWindow に関するもの
		// ここの [start_restore_vars] から [end_restore_vars] で囲まれた部分は
		// (略)
		// [start_restore_vars]
		lastSaveDataName = f.lastSaveDataName if f.lastSaveDataName !== void;
		quaking = f.quaking if f.quaking !== void;
		quakeEndTick = f.quakeEndTick if f.quakeEndTick !== void;
		quakeHorzMax = f.quakeHorzMax if f.quakeHorzMax !== void;
		quakeVertMax = f.quakeVertMax if f.quakeVertMax !== void;
		quakePhase = f.quakePhase if f.quakePhase !== void;
		_textWriteEnabled = f.textWriteEnabled if f.textWriteEnabled !== void;
		_backVoiceMode = f.backVoiceMode if f.backVoiceMode !== void;
		historyWriteEnabled = f.historyWriteEnabled if f.historyWriteEnabled !== void;
		historyEnabled = f.historyEnabled if f.historyEnabled !== void;
		numCharacterLayers = f.numCharacterLayers if f.numCharacterLayers !== void;
		numMessageLayers = f.numMessageLayers if f.numMessageLayers !== void;
		currentNum = f.currentNum if f.currentNum !== void;
		currentPage = f.currentPage if f.currentPage !== void;
		currentWithBack = f.currentWithBack if f.currentWithBack !== void;
		chUserMode = f.chUserMode if f.chUserMode !== void;
		chSpeed = f.chSpeed if f.chSpeed !== void;
		actualChSpeed = f.actualChSpeed if f.actualChSpeed !== void;
		beforeNoWaitActualChSpeed = f.beforeNoWaitActualChSpeed if f.beforeNoWaitActualChSpeed !== void;
		beforeNoWaitChUserMode = f.beforeNoWaitChUserMode if f.beforeNoWaitChUserMode !== void;
		clickSkipEnabled = f.clickSkipEnabled if f.clickSkipEnabled !== void;
		noeffectEnabled = f.noeffectEnabled if f.noeffectEnabled !== void;
		nextSkipEnabled = f.nextSkipEnabled if f.nextSkipEnabled !== void;
		canCancelSkipByClick = f.canCancelSkipByClick if f.canCancelSkipByClick !== void;
		autoWCEnabled = f.autoWCEnabled if f.autoWCEnabled !== void;
		autoWCChars = f.autoWCChars if f.autoWCChars !== void;
		rightClickEnabled = f.rightClickEnabled if f.rightClickEnabled !== void;
		rightClickCall = f.rightClickCall if f.rightClickCall !== void;
		rightClickJump = f.rightClickJump if f.rightClickJump !== void;
		rightClickTarget = f.rightClickTarget if f.rightClickTarget !== void;
		rightClickStorage = f.rightClickStorage if f.rightClickStorage !== void;
		rightClickName = f.rightClickName if f.rightClickName !== void;
		rightClickCurrentMenuName = f.rightClickCurrentMenuName if f.rightClickCurrentMenuName !== void;
		lastClickGlyphVisible = f.lastClickGlyphVisible if f.lastClickGlyphVisible !== void;
		startAnchorEnabled = f.startAnchorEnabled if f.startAnchorEnabled !== void;
		storeEnabled = f.storeEnabled if f.storeEnabled !== void;
		restoreEnabled = f.restoreEnabled if f.restoreEnabled !== void;
        currentStorage = f.currentStorage if f.currentStorage !== void;
        currentLabel = f.currentLabel if f.currentLabel !== void;
		currentPageName = f.currentPageName if f.currentPageName !== void;
		// [end_restore_vars]

		// perl スクリプトによって自動的に処理されないもの、いくつか。

		// マウスカーソル
		var cur;
		if ((cur = getSafeCursor(f.cursorDefault))      !== void) cursorDefault      = cur;
		if ((cur = getSafeCursor(f.cursorPointed))      !== void) cursorPointed      = cur;
		if ((cur = getSafeCursor(f.cursorWaitingClick)) !== void) cursorWaitingClick = cur;
		if ((cur = getSafeCursor(f.cursorWaitingAuto))  !== void) cursorWaitingAuto  = cur;
		if ((cur = getSafeCursor(f.cursorDraggable))    !== void) cursorDraggable    = cur;
		fore.base.setDefaultCursor(cursorDefault);
		back.base.setDefaultCursor(cursorDefault);

		// 自動ウェイト
		autoWCWaits.assign(f.autoWCWaits) if f.autoWCWaits !== void;

        erAfterPage = f.erAfterPage;

		restoreVoiceMap(f);
		restoreLoopVoice(f);
		currentVoice = void; // [XXX] store/restore対象にするべき？

        switch (f.lineMode) {
        case LINEMODE_NONE:
            setLineMode();
            break;
        case LINEMODE_PAGE:
            setLineMode("page");
            break;
        case LINEMODE_LINE:
            setLineMode("line");
            break;
        case LINEMODE_VN:
            setLineMode("vn");
            break;
        case LINEMODE_TEX:
            setLineMode("tex");
            break;
        case LINEMODE_FREE:
            setLineMode("free");
            break;
        }
        crAfterName = f.crAfterName;
        noCrOnce    = f.noCrOnce;
        noErOnce    = f.noErOnce;
        autoIndent = f.autoIndent;
        autoLabelCount = f.autoLabelCount;
        afterPage = f.afterPage;
		emptyLine = f.emptyLine;
		commandLine = f.commandLine;
        prevEmptyLine = f.prevEmptyLine;
        
		// ムービー
		if( isMain )
		{
			for( var i = 0; i < numMovies; i++)
				movies[i].restore(f.movies[i]);
		}

		// 背景レイヤ
		var backlay = elm != void && elm.backlay != void && +elm.backlay;
		if(backlay)
		{
			back.base.restore(f.foreBaseLayer);
		}
		else
		{
			fore.base.restore(f.foreBaseLayer);
			back.base.restore(f.backBaseLayer);
		}

		// メッセージレイヤ
		allocateMessageLayers(numMessageLayers);
		if(backlay)
		{
			for(var i = 0; i < numMessageLayers; i++)
			{
				back.messages[i].restore(f.foreMessageLayers[i]);
			}
		}
		else
		{
			for(var i = 0; i < numMessageLayers; i++)
			{
				fore.messages[i].restore(f.foreMessageLayers[i]);
				back.messages[i].restore(f.backMessageLayers[i]);
			}
		}

		if(clear)
		{
			for(var i = 0; i < numMessageLayers; i++)
			{
				fore.messages[i].clear();
				back.messages[i].clear();
			}
			if(historyLayer.storeState)
			{
				historyLayer.load(f.historyData);
			}
			else
			{
				if(historyWriteEnabled)
				{
					if(historyLayer.everypage)
						historyLayer.repage();
					else
						historyLayer.reline(), historyLayer.reline();
				}
				historyLayer.clearAction();
			}
		}

		// 前景レイヤ
		allocateCharacterLayers(numCharacterLayers);
		if(backlay)
		{
			for(var i = 0; i < numCharacterLayers; i++)
			{
				back.layers[i].restore(f.foreCharacterLayers[i]);
			}
		}
		else
		{
			for(var i = 0; i < numCharacterLayers; i++)
			{
                try {
                    fore.layers[i].restore(f.foreCharacterLayers[i]);
                } catch (e) {
                    dm("画像の読み込みに失敗しました");
                } 
                try {
                    back.layers[i].restore(f.backCharacterLayers[i]);
                } catch (e) {
                    dm("画像の読み込みに失敗しました");
                } 
            }
		}

		// quake 関連
		restoreQuake();

		// bgm
		if(elm === void || elm.bgm === void || +elm.bgm)
		{
			bgm.restore(f.bgm);
		}

		// 効果音
		if(elm === void || elm.se === void || +elm.se)
		{
			for(var i = 0; i<numSEBuffers; i++)
			{
				se[i].restore(f.se[i]);
			}
		}

		// ウィンドウキャプション
		caption = f.caption;
		//System.title = caption;

		// current の設定し直し
		current = (currentPage?back:fore).messages[currentNum];

		// 右クリックメニュー名
		if(typeof this.rightClickMenuItem != "undefined")
			rightClickMenuItem.caption = rightClickCurrentMenuName;

		// [XXX] 顔を消す
		clearFace(0, 0);
		clearFace(0, 1);
		
		// restoreHook
		forEachEventHook('onRestore',
			function(handler, f) { handler(f.flags, f.clear, f.options); } incontextof this,
			%[flags:f, clear:clear, options:elm]);

	}

	function restoreFlags()
	{
		// pcflags, pflags から情報を読み出す

		callHook("onBeforeRestoreFlags");

		// ロード時、ムービーのピリオドイベント情報をクリアする
		purgeMoviePeriod();

		// スナップショットの破棄
		freeSnapshot();

		// トランジション、自動移動、アクションの停止
		stopAllTransitions(true);
		stopAllActions(true);
		stopAllMoves();

		// クリック待ち記号を隠す
		hideClickGlyphs();

		// メッセージ履歴を隠す
		hideHistory();

        // 選択肢を隠す
        hideSelect();
		hideMapSelect();
		hideTransLayer();
		hideBlackLayer();
		hidePanel();
        
		// スキップのキャンセル
        if (skipMode < SKIP_FORCE) {
            cancelSkip();
        }

		// pflags -> flags
		(Dictionary.assignStruct incontextof flags)(pflags);

		// 栞管理関連
		storeLabelPassed = true;
		nextRecordHistory = false;
		stablePosibility = false;

		// コンダクタ
		currentRecordName = "";
		autoLabelCurrentRecordName = "";
		extraConductor.clear();
		setConductorToMain();
		mainConductor.clear();
		mainConductor.restore(pcflags.mainConductor);
		storeLabelPassedLastStorage = mainConductor.curStorage;
		// XXX 順番入れ替え
		
		// 読み込み
		internalRestoreFlags(pcflags);

		callHook("onAfterRestoreFlags");

        // メニュー関連
		setMenuAccessibleAll();

        // 回想中止
        stopRecollection();
        
		// 実行開始
		processGo();
    }

	function clearVariables()
	{
		// ゲーム変数のクリア
		(Dictionary.clear incontextof flags)();
	}

	//--------------------------------------------------------- 通過記録管理 --

	function copyHistoryOfStore()
	{
		// 辞書配列に user と core をコピー
		var dic = %[ user:%[], core:%[] ];
		(Dictionary.assignStruct incontextof dic.user)(pflags);
		(Dictionary.assignStruct incontextof dic.core)(pcflags);
		return dic;
	}

	function pushHistoryOfStore()
	{
		//dm("通過記録実行");
		// 通過記録を行う
		// pflags, pcflags に情報を格納した後に呼ぶこと

		if(nextRecordHistory)
		{
			if(stablePosibility)
			{
				var ret = forEachFunctionHook("onRecordHistoryOfStore");
				if (ret === void) {
					// stablePosibility が false の場合は、
					// そこのラベルで通過記録を行っても
					// そこよりも前に戻るすべがないので通過記録をしない

					// dic を historyOfStore の先頭に挿入
					historyOfStore.unshift(copyHistoryOfStore());

					// はみ出た分を削除
					if(historyOfStore.count > maxHistoryOfStore)
						historyOfStore.count = maxHistoryOfStore;
				}
			}

			nextRecordHistory = false;
		}
		// autoLabelStoreに保存（デバッグ用）
		if (autoLabelStore !== void && stablePosibility && skipMode < SKIP_STOP) {
			var dic = copyHistoryOfStore();
			callHook("onAutoLabelStoreHook", dic);
			autoLabelStore.unshift(dic);
			if (autoLabelStore.count > 100) autoLabelStore.pop(); // 上限で切る
		}
	}

	function setToRecordHistory()
	{
		// 次の「保存可能なラベル」通過時に
		// 通過記録を行うように設定する
		// ( ただし、そのときに記録されるのは、現在の状態 )
		if (forEachFunctionHook("isSetToRecordHistory") == true || !isRecollection) {
			nextRecordHistory = true;
		}
	}

	function isHistoryOfStoreAlive()
	{
		// 通過記録が利用可能かどうかを返す
		var ret = forEachFunctionHook("isHistoryOfStoreAlive");
		return ret !== void ? ret : historyOfStore.count;
	}

	function goBackHistoryOfStore(target)
	{
		// 記録の先頭からデータを取得
		var dic = target.shift();
		if (dic === void) return; // データがない場合は何もしない

		// user と core を pflags, pcflags に戻す
		(Dictionary.assignStruct incontextof pflags )(dic.user);
		(Dictionary.assignStruct incontextof pcflags)(dic.core);

		if (historyWriteEnabled) {
			historyLayer.clearAction();
			historyLayer.reline();
			historyLayer.repage(true);
		}

		// データを元に、栞をたどる動作をする
		restoreFlags();
	}

	function goBackAutoLabelStore() { goBackHistoryOfStore(autoLabelStore) if (autoLabelStore !== void); }
	function goBackYes()            {
		var ret = forEachFunctionHook("onGoBackHistoryOfStore");
		if (ret === void) {
			goBackHistoryOfStore(historyOfStore);
		}
	}

	function getHistoryOfStorePage() {
		var ret = forEachFunctionHook("getHistoryOfStorePage");
		return ret !== void ? ret : historyOfStore[0].core.currentPageName;
	}

	function goBackHistory(ask = true)
	{
		// 通過記録をたどり、戻る

		if(!isHistoryOfStoreAlive())
			return false;

		var result;
		if(ask)
		{
			var prompt = "「"+  getHistoryOfStorePage() + "」まで戻りますか?";
            askYesNo(prompt, "確認", goBackYes);
		}
		else
		{
            goBackYes();
		}
	}

	//--------------------------------------------------------------- 栞管理 --

	function createBookMarkSubMenus()
	{
		// 「栞をたどる」「栞をはさむ」以下にサブメニュー項目を追加
		if(freeSaveDataMode) return; // フリーセーブモードではなにもしない
		if(typeof this.storeMenu !== "undefined" && storeMenu.visible)
		{
			for(var i = 0; i<numBookMarks; i++)
			{
				var item;
				storeMenu.add(item = new KAGMenuItem(this, string i, 0, onBookMarkStore,
					false));
				item.bmNum = i;
				item.orgEnabled = false;
			}
		}
		if(typeof this.restoreMenu !== "undefined" && restoreMenu.visible)
		{
			for(var i = 0; i<numBookMarks; i++)
			{
				var item;
				restoreMenu.add(item = new KAGMenuItem(this, string i, 0, onBookMarkRestore,
					false));
				item.bmNum = i;
				item.orgEnabled = false;
			}
		}
	}

	function setBookMarkMenuCaptions()
	{
		// 「栞をたどる」「栞をはさむ」以下のサブメニューに
		// キャプションを設定

		// 栞を挟む
		if(typeof this.storeMenu !== "undefined")
		{
			var children = storeMenu.children;
			for(var i = children.count - 1; i >= 0; i--)
			{
				var protect = getBookMarkProtectedState(i);
				var caption = getBookMarkMenuCaption(i);
				with (children[i]) {
					.caption = caption;
					.enabled = false;
					.orgEnabled = !protect;
				}
			}
		}

		// 栞をたどる
		if(typeof this.restoreMenu !== "undefined")
		{
			var children = restoreMenu.children;
			for(var i = children.count - 1; i >= 0; i--)
			{
				var caption = getBookMarkMenuCaption(i);
				var exist   = isExistentBookMark(i);
				with (children[i]) {
					.caption = caption;
					.enabled = false;
					.orgEnabled = exist;
				}
			}
		}
		setMenuAccessibleAll();
	}

	function onBookMarkStore(sender)
	{
		// 栞をはさむメニューが選択された
//		if(!sender.parent.accessEnabled) return;
		saveBookMarkWithAsk(sender.bmNum);
	}

	function onBookMarkRestore(sender)
	{
		// 栞をたどるメニューが選択された
//		if(!sender.parent.accessEnabled) return;
		loadBookMarkWithAsk(sender.bmNum);
	}


	function getBookMarkMenuCaption(num) {
		return (showBookMarkDate && isExistentBookMark(num) ? (getBookMarkDate(num)+" ") : "") + getBookMarkPageName(num);
	}
	//                                                                     Indexタグ         データが存在しない場合の値
	function getBookMarkPageName      (num) { return getBookMarkIndex(num, "Name",           "(未設定)"); } // 栞番号 num のブックマーク名を得る
	function getBookMarkDate          (num) { return getBookMarkIndex(num, "Date",           void);       } // 栞番号 num の日付を得る
	function getBookMarkStorage       (num) { return getBookMarkIndex(num, "Storage",        "");         } // 栞番号 num のストレージ名を得る
	function getBookMarkPlayTime      (num) { return getBookMarkIndex(num, "PlayTime",       0);          } // 栞番号 num のプレイ時間を得る
	function getBookMarkInfo          (num) { return getBookMarkIndex(num, "Info",           void);       } // 栞番号 num の拡張情報を得る
	function getBookMarkProtectedState(num) { return getBookMarkIndex(num, "ProtectedState", false);      } // 栞番号 num の保護フラグを得る

	// bookMarkIndex 操作関数群
@if (!OLDBOOKMARK)
	function isExistentBookMark(num) { return (numBookMarks < 0 || num < numBookMarks) && (typeof bookMarkIndex[(int)num] == "Object"); } // 栞番号 num が存在するかどうか
	function setBookMarkIndex(num, tag, value)  { bookMarkIndex[(int)num] = %[] if (bookMarkIndex[(int)num] === void); bookMarkIndex[(int)num][tag] = value; } // bookMarkIndex 情報を設定
	function getBookMarkIndex(num, tag, defval) { return isExistentBookMark(num) ?  bookMarkIndex[(int)num][tag] : defval; } // bookMarkIndex 情報を取得
	function clearBookMarkIndex(num) { delete bookMarkIndex[(int)num]; } // bookMarkIndex 情報をクリア
@endif
@if (OLDBOOKMARK)
	function isExistentBookMark(num) { return (numBookMarks < 0 || num < numBookMarks) ? (bookMarkDates[num] != "") : false; } // 栞番号 num が存在するかどうか
	function setBookMarkIndex(num, tag, value) { try { this["bookMark"+tag+"s"][num] = value; } catch {} } // bookMarkIndex 情報を設定
	function clearBookMarkIndex(num) { setBookMarkIndex(num, "Date", void); } // bookMarkIndex 情報をクリア
	function getBookMarkIndex(num, tag, defval) { // bookMarkIndex 情報を取得
		if (isExistentBookMark(num)) try {
			return this["bookMark"+tag+"s"][num]; // bookMark{Name,Date,Storage,...}s
		} catch {}
		return defval;
	}
@endif

	function setBookMarkProtectedState(num, s)
	{
		// n 番の栞の保護フラグを設定する
		// s = true ならば栞に書き込み保護をする
		setBookMarkIndex(num, "ProtectedState", s);
		setBookMarkMenuCaptions();
	}

	// 各種栞のファイル名を取得する関数群
	function getBookMarkFileName(tag)                   { return saveDataLocation + "/" + dataName + tag; }
	function getBookMarkFileNameAtNum(num)              { return getBookMarkFileName(/*"%04d".sprintf(*/num/*)*/) + (saveThumbnail?".bmp":".kdt"); }
	function getBookMarkFileNameAtLabel(storage, label) { return getBookMarkFileName("_auto_"+getRecordLabel(storage, label)) + ".ksd"; }
	function getBookMarkFileNameBySystem(tag)           { return getBookMarkFileName("_"+tag) + ".ksd"; }


	var _inCaptureSnapshot;
	function captureSnapshot(layer = snapshotLayer, w = scWidth, h = scHeight, hide = snapshotHideMessage) {
		with (layer) {
			_inCaptureSnapshot = true;
			.setImageSize(w, h);
			.face = dfAlpha;
			if (hide && !messageLayerHiding) setMessageLayerHiddenState(true);
			else hide = false;
			.piledCopy(0, 0, fore.base, 0, 0, w, h);
			if (hide) setMessageLayerHiddenState(false);
			_inCaptureSnapshot = false;
		}
	}

	function lockSnapshot()
	{
		// スナップショットをロックする
		// 初めてスナップショットがロックされた時点での画面を保存する
		if(snapshotLockCount == 0)
		{
			if(snapshotLayer === void) {
				snapshotLayer = new Layer(this, primaryLayer);
				snapshotLayer.name = "スナップショット用";
			}
			captureSnapshot(snapshotLayer);
		}
		snapshotLockCount ++;
	}

	function unlockSnapshot()
	{
		// スナップショットのロックを解除する
		if(snapshotLockCount == 0)
			throw new Exception("snapshotLockCount がアンダーフローしました");
		snapshotLockCount --;
		if(snapshotLockCount == 0)
		{
			if(snapshotLayer !== void)
				invalidate snapshotLayer, snapshotLayer = void;
		}
	}

	function calcThumbnailSize()
	{
		// サムネイルのサイズを計算する
		// 横幅は 133 に
		var ratio = scHeight / scWidth;
		var w = thumbnailWidth;
		var h = (int)(w * ratio);

		// サムネイル用ビットマップのサイズを計算
		// サムネイル用画像は 256 色 BMP または 24bit フルカラー BMP
		var size;
		if (thumbnailDepth == 8)
		  size = ((((w - 1) >> 2) + 1) << 2) * h + 1024 + 54;
		else
		  size = (((w * 3 + 3) >> 2) << 2) * h + 54;

		return %[width : w, height : h, size : size];
	}

	function freeSnapshot()
	{
		// スナップショットを強制的に破棄し、snapshotLockCount を 0 に設定する
		snapshotLockCount = 0;
		if(snapshotLayer !== void)
			invalidate snapshotLayer, snapshotLayer = void;
	}

    /**
     * 保存用追加情報の登録
     */
	function addBookMarkInfo(name, value) {
		pcflags.bookMarkInfo = %[] if (pcflags.bookMarkInfo === void);
		pcflags.bookMarkInfo[name] = value;
	}

	function saveBookMarkToFile(fn, savehist = true)
	{
        // ファイル fn に栞を保存する
        if(readOnlyMode) return false;
        pcflags.storeTime = (new Date()).getTime(); // 日付を保存
        pcflags.playTime = playTime + pcflags.storeTime - playStartTime;
        
		// セーブデータをまとめる
		var data = %[];
		data.id = saveDataID;
		data.core = pcflags;
		data.user = pflags;
		if(savehist) data.history = historyOfStore;

		if(saveThumbnail)
		{
			// サムネイルを保存
			lockSnapshot();
			try
			{
				// サムネイルのサイズまで縮小
				var size = calcThumbnailSize();
				var tmp = new Layer(this, primaryLayer);
				try
				{
					var sw = snapshotLayer.imageWidth, sh = snapshotLayer.imageHeight;
					var dw = size.width, dh = size.height;
					var isSameSize = (dw == sw   && dh == sh  );
					var isShrink   = (dw <= sw\2 && dh <= sh\2);
					//dm("thumbnail", isSameSize, isShrink);
					tmp.setImageSize(dw, dh);
					tmp.face = dfAlpha;
					if (isSameSize) {
						tmp.copyRect(0, 0, snapshotLayer, 0, 0, sw, sh);
					} else {
						var cp = (typeof tmp.shrinkCopy == "Object" && isShrink) ? tmp.shrinkCopy : tmp.stretchCopy;
						cp(0, 0, dw, dh, snapshotLayer, 0, 0, sw, sh, stLinear);
					}
					/*
					// サムネイル画像をセピア調にして保存する場合はコメントアウトを解除
					tmp.doGrayScale();
					tmp.adjustGamma(
									1.3, 0, 255,  // R gamma, floor, ceil
									1.0, 0, 255,  // G gamma, floor, ceil
									0.8, 0, 255); // B gamma, floor, ceil
					*/
					try
					{
						// サムネイルを保存
						tmp.saveLayerImage(fn, "bmp" + thumbnailDepth);

						// データを保存
						var mode = saveDataMode;
						mode += "o" + size.size; // モード文字列に 書き込みオフセットを指定
						(Dictionary.saveStruct incontextof data)(fn, mode);
					}
					catch(e)
					{
						Debug.notice("saveBookMarkToFile失敗 : " + e.message);
						invalidate tmp;
						unlockSnapshot();
						readOnlyMode = true;
						errorInform("savefail");
						return false;
					}
				}
				catch(e)
				{
					invalidate tmp;
					throw e;
				}
				invalidate tmp;
			}
			catch(e)
			{
				unlockSnapshot();
				throw e;
			}
			unlockSnapshot();
		}
		else
		{
			// 通常のファイルに保存
			try
			{
				(Dictionary.saveStruct incontextof data)(fn, saveDataMode);
			}
			catch(e)
			{
				readOnlyMode = true;
				errorInform("savefail");
				return false;
			}
		}

		return true;
	}
	

	function saveBookMark(num, savehist = true)
	{
		// 栞番号 num に栞を保存する
		if(readOnlyMode) return false;
		if(getBookMarkProtectedState(num)) return false;

        var ret = saveBookMarkToFile(getBookMarkFileNameAtNum(num), savehist);
		if(ret)
		{
            // メニュー / bookMarkIndex を更新
            getBookMarkInfoFromData(pcflags, num);
		}
		return ret;
	}

	function getBookMarkInfoFromData(dic, num)
	{
		// 辞書配列 dic から栞のページ名と日付を読み出し、bookMarkIndex に設定する
		if(numBookMarks < 0 || num < numBookMarks)
		{
			var set = setBookMarkIndex;
			set(num, "Name",     dic.currentPageName);
			set(num, "Storage",  dic.currentStorage);
			set(num, "PlayTime", dic.playTime);

			var date = new Date();
			var fmt  = "%04d/%02d/%02d %02d:%02d" + (bookMarkDateSecond ? ":%02d" : "");
			date.setTime(dic.storeTime);
			with (date) set(num, "Date", fmt.sprintf(
				.getYear(),  .getMonth() + 1, .getDate(),
				.getHours(), .getMinutes(),   .getSeconds()));

			// bookMarkInfoをコピーする
			var info;
			if (typeof dic.bookMarkInfo == "Object") {
				info = %[];
				(Dictionary.assignStruct incontextof info)(dic.bookMarkInfo);
			}
			set(num, "Info", info);

			setBookMarkMenuCaptions();
			saveSystemVariables();
		}
	}

	// bookMarkInfo を実データから読み直す
	function updateBookMarkInfo(num)
	{
		var data = readBookMarkFromFile(getBookMarkFileNameAtNum(num));
		getBookMarkInfoFromData(data.core, num) if (data !== void && data.core !== void && data.core.storeTime != "");
	}

	function rewriteBookMarkToFile(fn, data, thumbfile) {
		thumbfile = fn if (thumbfile == "");
		var mode = saveDataMode;
		if (saveThumbnail) {
			var sz = calcThumbnailSize().size;
			mode += "o" + sz;
			// 操作対象が同じファイルでサイズ切り詰め可能ならそちらを使用する
			if (typeof Storages.truncateFile == "Object" && fn == thumbfile) {
				/**/   Storages.truncateFile(fn, sz);
			} else {
				// そうでない場合はサムネを読み直して再セーブ
				var tmp = new Layer(this, primaryLayer);
				try {
					tmp.loadImages(thumbfile);
					tmp.saveLayerImage(fn, "bmp" + thumbnailDepth);
				} catch (e) {
					invalidate tmp;
					throw e;
				}
				invalidate tmp;
			}
		}
		(Dictionary.saveStruct incontextof data)(fn, mode);
	}

	// 実データと共に bookMarkInfo を上書きする
	function writeBookMarkInfo(num, ovInfo)
	{
		var fn = getBookMarkFileNameAtNum(num);
		var data = readBookMarkFromFile(fn);
		if (data !== void && data.core !== void &&
			data.core.storeTime != "" && data.id == saveDataID)
		{
			var info = data.core.bookMarkInfo, ov = %[];
			/**/info = data.core.bookMarkInfo = %[] if (typeof info != "Object");
			(Dictionary.assignStruct incontextof ov)(ovInfo);
			(Dictionary.assign incontextof info)(ov, false);
			getBookMarkInfoFromData(data.core, num);
			rewriteBookMarkToFile(fn, data);
		}
	}

	function evalBookMarkFromFile(fn) {
		var modestr = saveThumbnail ? ("o" + calcThumbnailSize().size) : void;
		return safeEvalStorage(fn, modestr);
	}

    function readBookMarkFromFile(fn)
    {
        try {
            if (Storages.isExistentStorage(fn)) {
				var data = evalBookMarkFromFile(fn);
				if (data.id == saveDataID) return data;
            }
        } catch(e) {
        }
        return %[];
    }

	// セーブデータIDが違う時の挙動を決定
	function loadOtherDataID(data) {
		var force = debugWindowEnabled;
		errorInform(force ? "loadforce" : "loadfail");
		return force;
	}

	function loadBookMarkFromFile(fn, loaduser = true)
	{
		// ファイル fn から栞を読み込む
		// loaduser が false の時は user を読み込まない
		try
		{
			if(!Storages.isExistentStorage(fn)) return false; //ファイルがない

			var data = evalBookMarkFromFile(fn);
			if(data.id != saveDataID && !loadOtherDataID(data)) return false;

			pcflags = data.core;
			pcflags = %[] if pcflags === void;
			if(loaduser)
			{
				pflags = data.user;
				pflags = %[] if pflags === void;
			}
			else
			{
				(Dictionary.assignStruct incontextof pflags)(flags);
			}
			historyOfStore = data.history;
			historyOfStore = [] if historyOfStore === void;
		}
		catch(e)
		{
			errorInform("loadexception", e.message);
			return false;
		}
		autoLabelStore.clear() if (autoLabelStore !== void);
		restoreFlags();
        clearPlayTime(pcflags.playTime);
        return true;
	}

	function loadBookMark(num, loaduser = true)
	{
		// 栞番号 num からデータを読み出す
		return loadBookMarkFromFile(getBookMarkFileNameAtNum(num), loaduser);
	}

    function readBookMark(num)
	{
        // 栞番号 num からデータのみを読み出す
        return readBookMarkFromFile(getBookMarkFileNameAtNum(num));
	}

	var     _saveBookMarkWithAskPrompt = "栞 ${index}${exist}に${curpage}をはさみますか?";
	function saveBookMarkWithAsk(num)
	{
		// 栞番号 num に栞を設定する
		// そのとき、設定するかどうかをたずねる
		if(readOnlyMode) return false;
		if(getBookMarkProtectedState(num)) return false;

		var prompt = applyInlineStringVariableExtract(_saveBookMarkWithAskPrompt, %[
			"index"   => (num < numBookMarks) ? (num + 1) : void,
			"exist"   => isExistentBookMark(num) ? "「"+getBookMarkPageName(num)+"」" : void,
			"curpage" => "「"+ pcflags.currentPageName + "」" ]);
		askYesNo(prompt, "確認", saveBookMark, void, num);
		return true;
	}

	var     _loadBookMarkWithAskPrompt = "栞 ${index}${pagename}をたどりますか?";
	function loadBookMarkWithAsk(num)
	{
		// 栞番号 num から栞を読み出す
		// そのとき、読み出すかどうかをたずねる
		if(!isExistentBookMark(num)) return false;
		var prompt = applyInlineStringVariableExtract(_loadBookMarkWithAskPrompt, %[
			"index"    => (num < numBookMarks) ? (num + 1) : void,
			"pagename" => "「"+getBookMarkPageName(num)+ "」" ]);
		askYesNo(prompt, "確認", loadBookMark, void, num);
		return true;
	}

	function saveBookMarkToFileWithAsk()
	{
		// 任意のファイルに栞を保存する
		// currentPageName をファイル名として適合するように
		// 変形する
		var invalid = "\\/:,;*?\"<>!.";
		var valid = "￥／：，；＊？”＜＞！．";

		var initialname = saveDataLocation + "/";
		var through = false;
		var orgname = currentPageName;

		if(lastSaveDataNameGlobal != "")
		{
			try
			{
				initialname = Storages.extractStoragePath(lastSaveDataNameGlobal);
			}
			catch(e)
			{
				initialname = saveDataLocation + "/";
			}
		}

		if(orgname == "")
		{
			// 栞の見出しがないので
			if(lastSaveDataName == "")
				orgname = System.title; // System.title を代わりに使う
			else
				initialname = lastSaveDataName, through = true;
		}

		if(!through)
		{
			var length = orgname.length;
			for(var i = 0; i < length; i++)
			{
				var ch = orgname[i];
					var ind = invalid.indexOf(ch);
				if(ind != -1)
					initialname += valid[ind];
				else if(#ch >= 32)
					initialname += ch;
			}
		}

		// 保存するファイル名を得る
		var selectdata = %[
			title:"栞をはさむ",
			filter: [saveThumbnail ?
					"サムネイル画像付き栞データ(*.bmp)|*.bmp" :
					"栞データ(*.kdt)|*.kdt"],
			filterIndex : 1,
			name : initialname,
			initialDir : "",
			defaultExt : saveThumbnail?"bmp":"kdt",
			save : true,
		];
		if(Storages.selectFile(selectdata))
		{
			// 保存
			saveBookMarkToFile(lastSaveDataName = lastSaveDataNameGlobal = selectdata.name);
			lastSaveDataName = Storages.chopStorageExt(lastSaveDataName);
		}
	}

	function loadBookMarkFromFileWithAsk()
	{
		// 任意のファイルから栞を読み込む
		var initialdir = "";
		if(lastSaveDataNameGlobal == "")
			initialdir = saveDataLocation + "/";

		var selectdata = %[
			title:"栞をたどる",
			filter: [saveThumbnail ?
					"サムネイル画像付き栞データ(*.bmp)|*.bmp" :
					"栞データ(*.kdt)|*.kdt"],
			filterIndex : 1,
			name : lastSaveDataNameGlobal,
			initialDir : initialdir,
			defaultExt : saveThumbnail?"bmp":"kdt",
			save : false,
		];
		if(Storages.selectFile(selectdata))
		{
			loadBookMarkFromFile(lastSaveDataName = lastSaveDataNameGlobal = selectdata.name);
			lastSaveDataName = Storages.chopStorageExt(lastSaveDataName);
		}
	}


	function copyBookMark(from, to)
	{
		// 栞番号 from から栞番号 to に栞をコピーする
		if(readOnlyMode) return false;
		if(getBookMarkProtectedState(to)) return false;

		var fn = getBookMarkFileNameAtNum(from);

		if(!Storages.isExistentStorage(fn)) return false; //ファイルがない

		var data = evalBookMarkFromFile(fn);

		rewriteBookMarkToFile(getBookMarkFileNameAtNum(to), data, fn);
		getBookMarkInfoFromData(data.core, to);
		return true;
	}

	function eraseBookMark(num)
	{
		// 栞を消す
		// num < numBookMarks の時にしか動作しないようになったので注意
		if(isExistentBookMark(num) && !getBookMarkProtectedState(num)) {
			clearBookMarkIndex(num);
			setBookMarkMenuCaptions();
		}
	}

	function tempDisableStore(elm)
	{
		// 栞を一時的に保存不可能にする
		storeEnabled = true;
		if(elm.store === void)
			storeLabelPassed = false;
		else
			storeLabelPassed = !(+elm.store);
		if(elm.restore == void)
			restoreEnabled = true;
		else
			restoreEnabled = !(+elm.restore);
		setMenuAccessibleAll();
	}

	function setStoreEnabled(enabled)
	{
		// 栞メニューの有効/無効の設定
		storeEnabled = enabled;
		restoreEnabled = enabled;
		setMenuAccessibleAll();
	}

	function setStartAnchorEnabled(enabled)
	{
		// 「最初に戻る」の有効/無効の設定
		startAnchorEnabled = enabled;
		if(enabled) {
			saveBookMarkToFile(getBookMarkFileNameBySystem("anchor"), false);
			debugSendMessageArgumentApply() if (debugWindowEnabled);
		}
		setMenuAccessibleAll();
	}

	function goToStart()
	{
		// 最初に戻る
		if(!startAnchorEnabled) return;
		loadBookMarkFromFile(getBookMarkFileNameBySystem("anchor"), false);
		startAnchorEnabled = true;
	}

	function goToStartWithAsk()
	{
		// 最初に戻る(確認あり)
        askYesNo("最初に戻ります。よろしいですか ?", "確認", goToStart);
	}

	function tempSave(num)
	{
		// tempBookMarks[num] に現在の状態を保存する
		tempBookMarks[num] = %[];
		internalStoreFlags(tempBookMarks[num]);
	}

	function tempLoad(num, elm)
	{
		// tempBookMarks[num] から状態を読み込む
		internalRestoreFlags(tempBookMarks[num], false, elm);
	}

	function restoreBookMark(num, ask = true)
	{
		return ask ? loadBookMarkWithAsk(num) : loadBookMark(num); // KAG 2.x 互換用
	}

	function storeBookMark(num, ask = true)
	{
		return ask ? saveBookMarkWithAsk(num) : saveBookMark(num); // KAG 2.x 互換用
	}

	//------------------------------------------------- 未読/既読/ラベル記録 --

    function getRecordLabel(storage, label) {
        if(label != '')	{
            if(label[0] == '*') label = label.substring(1);
        }
		var base = Storages.chopStorageExt(Storages.extractStorageName(storage)).toLowerCase();
		return base + '_' + label;
    }

	function setRecordLabel(storage, label)
	{
		// 現在のラベルを設定する
		if(autoRecordPageShowing)
		{
			if(label != '')
			{
				if(label[0] == '*') label = label.substring(1);
				if(label[1] == '-') return; // ローカルラベル
			}
			var base = Storages.chopStorageExt(Storages.extractStorageName(storage)).toLowerCase();
			currentRecordName = 'trail_' + base + '_' + label;
			autoLabelCurrentRecordName = "auto" + currentRecordName;
		}
	}

	function incRecordLabel(count)
	{
		// 自動ラベル状態を保存
		storeAutoLabel();

        // sflags[currentRecordName]++
		if(autoRecordPageShowing)
		{
			if(currentRecordName != "")
			{
				if(count)
				{
                    if(sflags[currentRecordName] === void)
						sflags[currentRecordName] = 0;
					sflags[currentRecordName]++;
				}
				currentRecordName = "";
				autoLabelCurrentRecordName  ="";
			}
		}
	}

	//------------------------------------------- システム全体に関係するもの --

	function setTitle(title)
	{
		// タイトルを設定
		//if(isMain) System.title = title;
		caption = title;
	}

	function setCursor(elm)
	{
		// マウスカーソルの設定
		var conv = function(variable, v)
		{
			var value = getSafeCursor(v);
			this[variable] = value if(value !== void);
		} incontextof this;

		conv('cursorDefault', elm['default']); 
		conv('cursorPointed', elm.pointed);
		conv('cursorWaitingClick', elm.click);
		conv('cursorWaitingAuto',  elm.auto);
		conv('cursorDraggable', elm.draggable);
		fore.base.setDefaultCursor(cursorDefault);
		back.base.setDefaultCursor(cursorDefault);
	}
	var _cursorConstants = [
		"crDefault",    "crNone",       "crArrow",      "crCross",      "crIBeam",
		"crHBeam",      "crSizeNESW",   "crSizeNS",     "crSizeNWSE",   "crSizeWE",
		"crUpArrow",    "crHourGlass",  "crDrag",       "crNoDrop",     "crHSplit",
		"crVSplit",     "crMultiDrag",  "crSQLWait",    "crNo",         "crAppStart",
		"crHelp",       "crHandPoint",  "crSizeAll" ];
	function getSafeCursor(cur) {
		switch (typeof cur) {
		case "Integer": return cur;
		case "String":
			try {
				if (_cursorConstants.find(cur) >= 0) return global[cur];
				var ext = Storages.extractStorageExt(cur).toLowerCase();
				if (ext != '.cur' && ext != '.ani') return (int)cur;
				else if (Storages.isExistentStorage(cur)) {
					var exe = System.exePath.toLowerCase();
					var path = Storages.getPlacedPath(cur).toLowerCase();
					if (path.substr(0, exe.length) == exe) return cur;
				}
			} catch {}
		}
		return void;
	}

	//---------------------------------------------------- トリガ管理(TJS用) --

	function waitTrigger(elm)
	{
		// elm.name で示されたトリガを待つ
		if((elm.canskip !== void && +elm.canskip) && clickSkipEnabled)
		{
			// スキップできる場合
			if(skipMode)
			{
				// スキップ動作中
				if(elm.onskip !== void) Scripts.eval(elm.onskip);
				return 0; // すぐに返る
			}
			conductor.wait(%[
				click : function(arg)
				{
					if(arg !== void) Scripts.eval(arg);
				} incontextof this,
				click_arg : elm.onskip,
				elm.name => function(arg)
				{
					return arg;
				} incontextof this,
				elm.name + "_arg" => +elm.wait
				]);
		}
		else
		{
			conductor.wait(%[
				elm.name => function(arg)
				{
					return arg;
				} incontextof this,
				elm.name + "_arg" => +elm.wait
				]);
		}
		return -2;
	}

	function trigger(name)
	{
		// name で示したトリガを発動する
		conductor.trigger(name);
	}

	//------------------------------------------------------- メッセージ履歴 --

	function showHistory()
	{
		if (historyShowing) return;
		// メッセージ履歴レイヤを表示する
		historyShowing = true;
		historyLayer.parent = fore.base; // メッセージ履歴レイヤの親も再設定
		historyLayer.absolute = historyLayerAbsolute;
		historyLayer.dispInit();

		if(typeof this.showHistoryMenuItem != "undefined")
			showHistoryMenuItem.checked = true;
		setMenuAccessibleAll();
	}

	function hideHistory()
	{
		if (!historyShowing) return;
		// メッセージ履歴レイヤを非表示にする
		historyShowing = false;
		historyLayer.dispUninit();

		if(typeof this.showHistoryMenuItem != "undefined")
			showHistoryMenuItem.checked = false;
		setMenuAccessibleAll();

		lastHistoryHiddenTick = System.getTickCount();
		conductor.trigger('history'); // 'history' を送る
	}

	function setHistoryOptions(elm)
	{
		// elm からメッセージ履歴レイヤのオプションを設定する
		historyWriteEnabled = +elm.output if elm.output !== void;
		historyEnabled = +elm.enabled if elm.enabled !== void;
		if(elm.enabled !== void && !historyEnabled)
			historyLayer.clearAction();
		historyLayer.setOptions(elm); // その他のオプション
		setMenuAccessibleAll();
	}

	function showHistoryByScenario(elm)
	{
		// メッセージ履歴をシナリオ中から表示させる
		showHistory();
		conductor.wait(%[ // コンダクタを待ちに
			history : function
			{
				// やることなし
			} incontextof this
			]);
		return -2; // break
	}

    //------------------------------------------------------- 選択肢 --

    function setSelectOptions(elm)
	{
        selectLayer.setOptions(elm);
	}

	function addSelect(elm) {
		if (f.selectInfos == void) {
			f.selectInfos = [];
		}
		if (f.selectDoneFlag) {
			f.selectInfos.clear();
			f.selDoneStorage = void;
			f.selDoneTarget  = void;
			f.selectDoneFlag = false;
			f.selectTotalCount = 0; // 非表示も含めたトータル個数
		}
		var show = true, subtag = (int)(f.selectTotalCount++);
		if (     elm.eval     != "") { show =  Scripts.eval(elm.eval); }   // 条件付表示
		else if (elm.onceflag != "") { show = !Scripts.eval(elm.onceflag); // 一度きり表示
			// フラグ立て処理を追加
			elm.exp = (elm.onceflag + "=true") + (elm.exp != "" ? (","+elm.exp) : "");
		}
		if (show) {
			var e = %[];
			(Dictionary.assign incontextof e)(elm,false);
			e.tag = (currentRecordName+":"+subtag+":"+e.text) if (e.tag == "");
			e.selected = +sflags["select_"+e.tag];
			//dm("addsel", e.tag, e.selected);
			f.selectInfos.add(e);
		}
	}

    /**
     * 選択肢の表示
     */
	function showSelect()
	{
		callHook("onShowSelectHook", f.selectInfos);
		selectLayer.clearButtons();
		for (var i=0;i<f.selectInfos.count;i++) {
			selectLayer.addSelect(f.selectInfos[i]);
		}
		var r = selectLayer.start(fore.base, selectLayerAbsolute);
		selectShowing = true;
		setMenuAccessibleAll();
		return r;
	}

    function hideSelect() {
        if (_selectLayer !== void) {
            selectLayer.done();
            selectShowing = false;
        }
    }
    
    function afterSelect() {
        if (erAfterPage) {
            // 選択肢後もテキスト消去
            tagHandlers.er(%[all:true]);
        }
        if (afterauto && selectPrevAutoMode == true) {
            if(typeof this.autoModeMenuItem !== "undefined")
                autoModeMenuItem.checked = true;
            autoMode = true;
        } else if (afterskip && selectPrevSkipMode >= SKIP_STOP) {
            if (selectPrevSkipMode == SKIP_STOP) {
                skipMode = SKIP_STOP;
				_tempAllskip  = selectPrevTempSkip;
            } else if (selectPrevSkipMode == SKIP_FAST) {
                if (getKeyState(VK_CONTROL)) {
                    skipMode = SKIP_FAST;
				}
            }
        }
        setMenuAccessibleAll();
    }

	function doneSelect(elm) {
		f.selectDoneFlag = true;
		var tag = (elm !== void) ? elm.tag : void;
		sflags["select_"+tag] = true if (tag != "");
		forEachEventHook("onSelect",,elm.text);
		if (elm.storage != '' || elm.target != '') {
			insertTag("next", %[storage:elm.storage, target:elm.target]);
		}
	}
	
	function invokeSelectLayerSetSelProcessLock() {
		if (selectShowing    && typeof selectLayer   .setSelProcessLock == "Object")
			/**/                       selectLayer   .setSelProcessLock(...);
		if (mapSelectShowing && typeof mapSelectLayer.setSelProcessLock == "Object")
			/**/                       mapSelectLayer.setSelProcessLock(...);
	}


    //------------------------------------------------------- マップ選択肢 --

    /**
     * マップ選択肢初期化
     */
    function initMapSelect(elm)
    {
        mapSelectLayer.init(elm);
	}

    /**
     * マップ選択肢オプション
     */
    function setMapSelectOptions(elm)
	{
        mapSelectLayer.setOptions(elm);
	}

    /**
     * ボタン登録
     */
    function addMapSelectButton(elm) {
		mapSelectLayer.addButton(elm);
    }

    /**
     * 場所登録
     */
    function addMapSelectPosition(elm) {
        mapSelectLayer.addPosition(elm);
    }

    /**
     * 選択肢登録
     */
    function addMapSelect(elm) {
		if ((elm.eval === void || elm.eval == "" || Scripts.eval(elm.eval))) {
			mapSelectLayer.addSelect(elm);
		}
    }
    
    function showMapSelect()
	{
        mapSelectLayer.start(fore.base, mapSelectLayerAbsolute);
        mapSelectShowing = true;
        setMenuAccessibleAll();
	}

    function hideMapSelect()
	{
        if (_mapSelectLayer !== void) {
            mapSelectLayer.done();
            mapSelectShowing = false;
        }
    }

    //------------------------------------------------------- シナリオ内分岐 --

	/**
	 * 分岐の追加
	 */
    function addBranch(elm) {
		if (f.branchInfos == void) {
			f.branchInfos = [];
		}
		delete f.branchDoneTarget;
		var e = %[];
		(Dictionary.assign incontextof e)(elm,false);
		f.branchInfos.add(e);
		return 0;
    }

	/**
	 * 分岐実行
	 */
	function doBranch(elm) {
		f.branchDoneTarget  = elm.target  if elm.target  !== void;
		for (var i=0;i<f.branchInfos.count;i++) {
			var elm = f.branchInfos[i];
			if (elm.target !== void && (elm.eval === void || elm.eval == "" || Scripts.eval(elm.eval))) {
				if (elm.exp !== void) {
					Scripts.eval(elm.exp);
				}
				insertTag("next", %[target:elm.target]);
				break;
			}
		}
		delete f.branchInfos;
		return 0;
    }

	/**
	 * 分岐終了
	 */
	function doneBranch(elm) {
		if (f.branchDoneTarget != "") {
			// 分岐
			insertTag("next", %[target:f.branchDoneTarget]);
		} else {
			// 強制停止
			return tagHandlers.s(elm);
		}
        return 0;
    }
	
    //------------------------------------------------------- 画面切り替え特殊レイヤ --

    function hideTransLayer() {
        if (_transLayer !== void) {
            transLayer.stop();
            transShowing = false;
        }
    }
    
    function layerTransBegin(elm)
	{
		hideTransLayer();
		if (elm.time === void) { elm.time = 1000; }
		if (elm.type === void) { elm.type = "trans"; }
		elm.time *= effectspeed;
		var waitParam  = %[];
		if((elm.canskip === void || +elm.canskip) && clickSkipEnabled) {
			if(skipMode && effectspeed == 0) {
				return 0; // すぐに返る
            }
            waitParam.click = function() {hideTransLayer();} incontextof this;
		}
		waitParam.layertrans = function(arg) { return arg; } incontextof this;
		waitParam.layertrans_arg = +elm.wait;
		transLayer.start(elm);
		flipStart();
		transShowing = true;
		conductor.wait(waitParam);
        return -2;
	}

    function layerTransEnd(elm) {
        if (!transShowing) {
            return 0;
        }
        var waitParam  = %[];
		if((elm.canskip === void || +elm.canskip) && clickSkipEnabled) {
			if(skipMode && effectspeed == 0) {
				hideTransLayer();
				return 0; // すぐに返る
            }
            waitParam.click = function() {hideTransLayer();} incontextof this;
        }
		waitParam.layertrans = function(arg) {hideTransLayer();return arg;} incontextof this;
		waitParam.layertrans_arg = +elm.wait;
		transLayer.start(elm, true);
		flipStart();
		conductor.wait(waitParam);
        return -2;
    }

    //------------------------------------------------------- 暗転トランジション

	var btLayer;

	function hideBlackLayer() {
		if (btLayer !== void) {
			invalidate btLayer;
			btLayer = void;
        }
    }
	
	// 暗転トランジション開き
	function blackTransBegin(elm) {
		hideBlackLayer();
		var waitParam  = %[];
		if((elm.canskip === void || +elm.canskip) && clickSkipEnabled) {
			if(skipMode && effectpeed==0) {
				updateBeforeCh = 1;
				return 0;
			}
			waitParam.click = function() {
				stopAllTransitions(true);
				hideBlackLayer();
				updateBeforeCh = 1;
			} incontextof this;
		}
		btLayer = new Layer(this, back.base);
		with (btLayer) {
			.width  = exWidth;
			.height = exHeight;
			.fillRect(0,0,.width,.height, 0xff000000 | +elm.color);
			.visible = true;
			.hitThreshold = 256;
		}
		elm.children = true;
		if (elm.method === void) {
			elm.method = "crossfade";
		}
		//forEachEventHook('onPreTransition');
		beginTransition(kag.fore.base, elm);
		waitParam.trans = function(arg) {updateBeforeCh = 1;return arg;} incontextof this;
		waitParam.trans_arg = +elm.wait;
		conductor.wait(waitParam);
		return -2;
    }
	
	// 暗転トランジション閉じ
	function blackTransEnd(elm) {
		stopAllTransitions(true);
		if (btLayer === void) {
			updateBeforeCh = 1;
			return 0;
		}
		var waitParam  = %[];
		if((elm.canskip === void || +elm.canskip) && clickSkipEnabled) {
			if(skipMode && effectpeed==0) {
				hideBlackLayer();
				updateBeforeCh = 1;
				return 0;
			}
			waitParam.click = function() {
				stopAllTransitions(true);
				hideBlackLayer();updateBeforeCh = 1;
			} incontextof this;
		}
		backupLayer(elm, true);
		elm.children = true;
		if (elm.method === void) {
			elm.method = "crossfade";
		}
		forEachEventHook('onPreTransition');
		beginTransition(kag.fore.base, elm);
		waitParam.trans = function(arg) {hideBlackLayer();updateBeforeCh = 1;return arg;} incontextof this;
		waitParam.trans_arg = +elm.wait;
		conductor.wait(waitParam);
		return -2;
    }
	
	//------------------------------------------------------- パネル処理 --

	// パネル用
	var panelLayer;
	var panelShowing = false;
	var panelLayerAbsolute = 8000000;
	var panelModal   = false; // modal表示モードかどうか
	function hidePanel() {
		if (panelLayer !== void) {
			panelLayer.removeMode();
			panelLayer.visible = false;
			invalidate panelLayer;
			panelLayer = void;
			panelModal = panelShowing = false;
        }
	}

	//-------------------------------------------------------------- process --

	function process(file, label, countpage = true, immediate = false)
	{
		forEachEventHook('onProcess',,usingExtraConductor);

		// 指定ファイル、指定ラベルから実行を開始する
        if(!usingExtraConductor) {
            incRecordLabel(countpage);
			autoLabelStore.clear() if (autoLabelStore !== void && file != "");
        }
		setUserSpeed();

		if(file != '')
		{
			if (!usingExtraConductor) {
				clearVoiceMap();
				clearLoopVoice();
			}
			// ファイルを読み込み
            conductor.loadScenario(file);
		}

		if(label != '')
		{
			// ラベルに移動する
			conductor.goToLabel(label);
		}

		if(isFirstProcess)
		{
			storeFlags(); // 一番最初の状態をストア
			isFirstProcess = false;
		}

        if (debugLevel >= tkdlSimple)
            dm("処理を開始します");
		inSleep = false;
		notifyRun();
		if(conductor.status != conductor.mRun) conductor.run(immediate); // 実行開始
    }

	function processGo(immediate = false)
	{
        // コンダクタを現在位置から実行開始させる
        if (debugLevel >= tkdlSimple)
            dm("処理を開始します");
		inSleep = false;
		notifyRun();
		conductor.run(immediate); // 実行開始
	}

	function processCall(file, label)
	{
		// 指定ファイル、指定ラベルを呼ぶ
		// incRecordLabel(); は呼ばないので注意

		if(file != '')
		{
			// ファイルを読み込み
			conductor.loadScenario(file);
		}

		inSleep = false;
		notifyRun();
		conductor.callLabel(label); // 実行開始
        if (debugLevel >= tkdlSimple)
            dm("処理を開始します");
		if(conductor.status != conductor.mRun) conductor.run();
	}

	//------------------------------------------------- コンダクタのイベント --

	function onConductorScenarioLoad(name)
	{
		prevSkipMode = void;
        // コンダクタが新しいシナリオファイルを読み込む前によばれる。
		// name は読み込もうとしたシナリオファイル。
		// 戻り値に文字列を返すと、それをシナリオとして
		// ファイルの代わりに使うようになるので、ここにフィルタを書くこ
		// とができる。
		// true を返すと通常のシナリオファイル読み込みとなる。
		return true;
	}


	function onConductorScenarioLoaded(name)
	{
		// コンダクタが新しいシナリオファイルを読み込んだ
		if(!usingExtraConductor) incRecordLabel(true);
        return true;
	}

	function checkSkipToLabel(label) {
		if (skipMode == SKIP_LABEL) {
			if (skipToLabelStop == "" || label == skipToLabelStop) {
				cancelSkip();
				this.onSkipToLabelStop(label, page) if (typeof this.onSkipToLabelStop != "undefined");
			}
		}
	}
	
	function onConductorLabel(label, page)
	{
		checkSkipToLabel(label);
        // コンダクタがラベルを通過した
		if(!usingExtraConductor)
		{
            if (label !== "*autoLabelLabel") {
				if (autoLabelMode) {
					autoLabelCount++;
				}
				incRecordLabel(true);
				autoLabelCount = 1;
			}
			setRecordLabel(conductor.curStorage, label);
		}
		setUserSpeed();
		if(!usingExtraConductor)
		{
			if (label !== "*autoLabelLabel") {
				if(!(allskip || getCurrentRead()) && skipMode < SKIP_FAST)
					cancelSkip(); // 未読なのでスキップを停止
				if (checkSkipOnNextLabel) {
					checkSkipOnNextLabel = false;
					checkSkipOnReadedLabel();
				}
				currentStorage = conductor.curStorage;
			}
			currentLabel = label;
			if (autoLabelMode) {
				autoLabelCount++;
				storeAutoLabel();
			}
		}
		if(page !== void && page !== '')
		{
			if(page[0] == '&') page = Scripts.eval((page.substring(1)));
			currentPageName = page;
		}
		if(page !== void)
		{
			pushHistoryOfStore();
			stablePosibility = false;
            if (debugLevel >= tkdlVerbose) {
				dm(conductor.curStorage + " : ラベル/ページ : " + label + "/" + currentPageName);
            }
			if(usingExtraConductor) throw new Exception("右クリックサブルーチン内/extraCondutor"
				"サブルーチン内では保存可能なラベルを記述できません");
			storeFlags(), storeLabelPassed = true, setMenuAccessibleAll();
			storeLabelPassedLastStorage = conductor.curStorage;
			if(recordHistoryOfStore == 1) // 1 : 保存可能なラベルごと
				setToRecordHistory();

        }

		// 回想終了チェック
		if (isRecollection && recollectionEndLabel != "" && recollectionEndLabel == label) {
//			incRecordLabel(true) if(!usingExtraConductor); // すぐ既読にする
			var end = recollectionEndStorage;
			var cur = Storages.extractStorageName(conductor.curStorage);
			endRecollection() if (end == "" || end.toLowerCase() == cur.toLowerCase());
		}
		return true;
	}

	function onConductorJump(elm)
	{
		// コンダクタで jump タグを処理するとき
        closeDialog();
        if(!usingExtraConductor) incRecordLabel(elm.countpage === void || +elm.countpage);
		return true;
	}

	function onConductorCall(elm)
	{
		// コンダクタが call タグを処理するとき
		if(!usingExtraConductor) incRecordLabel(elm.countpage !== void && +elm.countpage);
		return true;
	}

	function onConductorReturn(elm)
	{
		// コンダクタが return タグを処理するとき
		if(!usingExtraConductor) incRecordLabel(elm.countpage === void || +elm.countpage);
		if(conductor === extraConductor)
		{
			// extraConductor サブルーチン用のコンダクタから呼ばれている
			if(conductor.callStackDepth == 1)
			{
				// つまり、最終の return が実行されたと言うこと
				dm("extraConductor サブルーチンから戻ります ...");
				var run;
				if(elm.storage !== void || elm.target !== void) run = true; else run = false;
				returnExtraConductor(run);
				if(elm.storage !== void) conductor.loadScenario(elm.storage);
				if(elm.target !== void) conductor.goToLabel(elm.target);
				setRecordLabel(currentStorage = conductor.curStorage, currentLabel = conductor.curLabel);
				if(run)
				{
					notifyRun();
					conductor.run();
				}
				isLeavePeriodEvent = false;
				if(elm.storage !== void || elm.target !== void)
				{	// returnで元の位置以外に戻る時はピリオドイベントをクリアする。
					holdPeriodEventQueue.clear();
					isWaitPeriodEvent = false;
				}
				if( isWaitPeriodEvent == true )
				{	// [wp]でピリオドイベント待ちをしている時のみ、トリガーする
					fireMoviePeriodFromQueue();
				}
				return false; // return は実行しない
			}
		}
		return true;
	}
	function purgeMoviePeriod()
	{
		isLeavePeriodEvent = false;
		holdPeriodEventQueue.clear();
		isWaitPeriodEvent = false;
		waitedPeriodEventStorageName = void;
	}
	function fireMoviePeriodFromQueue()
	{
		var retVal = false;
		if( holdPeriodEventQueue.count > 0 )
		{
			if( waitedPeriodEventStorageName == conductor.curStorage && conductor == mainConductor )
			{
				for( var i = 0; i < holdPeriodEventQueue.count; i++ )
				{
					conductor.trigger( holdPeriodEventQueue[i] );
					retVal = true;
				}
			}
			holdPeriodEventQueue.clear();
		}
		return retVal;
	}

	function onConductorAfterReturn()
	{
		// コンダクタが return タグを実行した後
		if(!usingExtraConductor)
		{
			setRecordLabel(currentStorage = conductor.curStorage, currentLabel = conductor.curLabel);
		}
		setUserSpeed();
		if(!usingExtraConductor)
		{
            if(!(allskip || getCurrentRead()) && skipMode < SKIP_FAST)
                cancelSkip(); // 未読なのでスキップを停止
		}
    }

	function onConductorScript(script, scriptname, lineofs)
	{
		// iscript タグ
		try
		{
			Scripts.exec(script, scriptname, lineofs);
		}
		catch(e)
		{
			throw new Exception(scriptname + " の 行 " + lineofs + " から始まる"
				" iscript ブロックでエラーが発生しました。"
				"\n( 詳細はコンソールを参照してください )\n" + e.message);
		}
		return true;
	}
	
	// すべてのプラグインオブジェクトの method を呼び出して、
	// 最初に帰り値があったところで中断する
	function forEachFunctionHook(method, args*) {
		var ret;
		var count = kagPlugins.count;
		for (var i =0; i<count; i++) {
			var obj = kagPlugins[i];
			if (typeof obj[method] != "undefined") {
				if ((ret = obj[method](args*)) !== void) {
					break;
				}
			}
		}
		return ret;
	}

	function checkVoiceParam(tagname, elm, entryTag) {
		// ボイス指定の特殊展開
		if (characterVoiceInfo[tagname] !== void) {
			if (elm.voice !== void) {
				if (elm.voice != "true") {
					setCurrentVoice(tagname, elm.voice, elm.replace, elm.insert);
				}
				removeTagParam(elm, "voice");
				removeTagParam(elm, "replace");
				removeTagParam(elm, "insert");
			}
			if (elm.nextvoice !== void) {
				if (elm.nextvoice != "true") {
					setCurrentVoice(tagname, elm.nextvoice, elm.replace, elm.insert);
				}
				entryNextVoice(tagname);
				removeTagParam(elm, "nextvoice");
				removeTagParam(elm, "replace");
				removeTagParam(elm, "insert");
			}
			if (elm.backvoice !== void) {
				if (elm.backvoice != "true") {
					setCurrentVoice(tagname, elm.backvoice, elm.replace, elm.insert);
				}
				entryNextVoice(tagname, 1);
				removeTagParam(elm, "backvoice");
				removeTagParam(elm, "replace");
				removeTagParam(elm, "insert");
			}
			if (elm.playvoice !== void) {
				entryTag("playvoice", %[name:tagname, voice:elm.playvoice]);
				removeTagParam(elm, "playvoice");
			}
			var useloop = false;
			if (elm.bvoice !== void) {
				if (elm.bvoice == "") {
					entryTag("stopvoice", %[name:tagname, all:true]);
				} else {
					var loop = true;
					if (elm.loop !== void) {
						loop = +elm.loop;
						useloop = true;
					}
					entryTag("playvoice", %[name:tagname, voice:elm.bvoice, type:1, loop:loop]);
				}
				removeTagParam(elm, "bvoice");
			}
			if (elm.hvoice !== void) {
				if (elm.hvoice == "") {
					entryTag("stopvoice", %[name:tagname, all:true]);
				} else {
					var loop = true;
					if (elm.loop !== void) {
						loop = +elm.loop;
						useloop = true;
					}
					entryTag("playvoice", %[name:tagname, voice:elm.hvoice, type:2, loop:loop]);
				}
				removeTagParam(elm, "hvoice");
			}
			if (useloop) {
				removeTagParam(elm, "loop");
			}
			if (elm.waitvoice !== void) {
				entryTag("waitvoice", %[name:tagname]);
				removeTagParam(elm, "waitvoice");
			}
			if (elm.stopvoice !== void) {
				entryTag("stopvoice", %[name:tagname]);
				removeTagParam(elm, "stopvoice");
			}
			if (Scripts.getObjectCount(elm) <= 2) {
				// tagname と taglist
				return 0;
			}
		}
	}
	

	function onConductorUnknownTag(tagname, elm)
	{
		var ret = checkVoiceParam(tagname, elm, addFastTag);
		if (ret !== void) {
			// tagname と taglist
			return ret;
		}
		ret = forEachFunctionHook("onUnknown", tagname, elm);
		if (ret === void) {
			@if (KAG_UNKNOWNTAG_EXCEPTION)
			throw new Exception("タグ/マクロ \"" + tagname + "\" は存在しません");
			@endif
			errorCmd("未知のコマンド:"+tagname);
			return 0;
		} else {
			return ret; // この戻り値は、各タグハンドラが返す物とおなじ
		}
	}

	//----------------------------------------------------------- stable/run --

	function notifyStable()
	{
		// システムが安定(クリック待ち/停止)したときに、ハンドラを呼ぶ
		if(!inStable)
		{
			inStable = true;
			var handlers = stableHandlers;
			for(var i = handlers.count-1; i>=0; i--)
				handlers[i]();

			// stableHook
			forEachEventHook('onStableStateChanged',,true);
        }
	}

	function notifyRun()
	{
		// システムが走り始めたときに、ハンドラを呼ぶ
		if(inStable)
		{
			inStable = false;
			var handlers = runHandlers;
			for(var i = handlers.count-1; i>=0; i--)
				handlers[i]();

			// runHook
            forEachEventHook('onStableStateChanged',,false);
            
			if(autoMode) hideMouseCursor();
		}
	}

	function defaultStableHandler()
	{
		// デフォルトの stable ハンドラ
		setMenuAccessibleAll();
	}

	function defaultRunHandler()
	{
		// デフォルトの run ハンドラ
		hideHistory();
		hideClickGlyphs();
		showMessageLayerByUser();
		setMenuAccessibleAll();
	}

	//----------------------------------------------------------- 文字列入力 --

	var inputTemp;
	function inputString(elm)
	{
		// 文字列を入力する
		var name = elm.name;
		var initial = Scripts.eval(name);
		var res = System.inputString(elm.title, elm.prompt, initial);
		if(res !== void)
		{
			// name に res を代入する
			inputTemp = res;
			Scripts.eval(("(" + name + ") = kag.inputTemp"));
		}
	}

	//-------------------------------------------------- extraConductor 処理 --

	function callExtraConductor(storage, label, onreturn)
	{
		isLeavePeriodEvent = true; // 復帰時にmovie period eventを発砲する

		// extraConductor を使ってサブルーチンを呼び出す
		onExtraConductorReturn = onreturn;
		inSleepBeforeExtraConductor = inSleep; // inSleep 保存

		storeMessageLayerSelProcessLock(); // メッセージレイヤの storeSelProcessLock を呼ぶ
		lockSelectLayerFocus(true);

		conductor = extraConductor; // コンダクタを切り替える
		(Dictionary.assign incontextof extraConductor.macros)(mainConductor.macros);
			// マクロはコピー
		usingExtraConductor = true;
		if(storage == '')
		{
			// ストレージ指定がないので現在のストレージを読み込ませる
			storage = mainConductor.curStorage;
		}

		// 呼び出す
		conductor.clearCallStack();
		processCall(storage, label);
	}

	function returnExtraConductor(run)
	{
		// extraConductor のサブルーチンから戻る
		// run が true の場合は 待機状態の復帰は行わない

		conductor.sleep(); // 停止
		conductor.interrupt();
			// interrupt は コンダクタのイベント内でコンダクタの実行を
			// 停止させるためのメソッド
		conductor = mainConductor; // コンダクタを切り替え
		(Dictionary.assign incontextof mainConductor.macros)(extraConductor.macros);
			// マクロはコピー
		usingExtraConductor = false;
		if(!run)
		{	restoreClickGlyphState(); // クリック待ち記号の復帰
			inSleep = inSleepBeforeExtraConductor; // inSleep 復帰
			notifyStable();
		}

		lockSelectLayerFocus(false);
		restoreMessageLayerSelProcessLock(); // メッセージレイヤの restoreSelProcessLock を呼ぶ

		setMenuAccessibleAll();
		cancelSkip();

		if(onExtraConductorReturn !== void) onExtraConductorReturn();
	}

	// 選択肢をロック・アンロックする
	function lockSelectLayerFocus(lock) {
		var sel = lock ? "lockFocus" : "unlockFocus";
		selectLayer   [sel]() if (_selectLayer    !== void);
		mapSelectLayer[sel]() if (_mapSelectLayer !== void);
	}

	//------------------------------------------------------- 右クリック処理 --

	function setRightClickOptions(elm)
	{
		// 右クリックのオプションを設定する
		rightClickEnabled = +elm.enabled if elm.enabled !== void;
		if(elm.call !== void)
		{
			rightClickCall = +elm.call;
			if(rightClickCall) rightClickJump = false;
		}
		if(elm.jump !== void)
		{
			rightClickJump = +elm.jump;
			if(rightClickJump) rightClickCall = false;
		}
		rightClickTarget = elm.target if elm.target !== void;
		rightClickStorage = elm.storage if elm.storage !== void;
		if(elm.name !== void)
		{
			if(typeof this.rightClickMenuItem != "undefined")
			{
				rightClickName = elm.name;
				if(rightClickName == "default")
					rightClickMenuItem.caption = rightClickCurrentMenuName = rightClickDefaultName;
				else
					rightClickMenuItem.caption = rightClickCurrentMenuName = rightClickName;
			}
		}
	}

	function callRightClickSubRoutine()
	{
		isLeavePeriodEvent = true;

		// 右クリックサブルーチンを呼ぶ
		if(typeof this.rightClickMenuItem != "undefined")
		{
			rightClickMenuItem.caption = rightClickCurrentMenuName = rightClickDefaultName;
		}

		callExtraConductor(rightClickStorage, rightClickTarget, restoreFromRightClick);

		lockMessageLayerSelProcess(); // 選択肢ロック
	}

	function restoreFromRightClick()
	{
		// 右クリックサブルーチンから抜けるときに呼ばれる
		if(typeof this.rightClickMenuItem != "undefined")
		{
			if(rightClickName == "default")
				rightClickMenuItem.caption = rightClickCurrentMenuName = rightClickDefaultName;
			else
				rightClickMenuItem.caption = rightClickCurrentMenuName = rightClickName;
		}
	}

	function setConductorToMain()
	{
		// restore の時に呼ばれ、コンダクタを main に切り替える
		if(usingExtraConductor)
		{
			extraConductor.sleep();
			extraConductor.interrupt();
			conductor= mainConductor;
			usingExtraConductor = false;
			// [XXX] あとでなんとかする
			isWaitPeriodEvent = false;
			isLeavePeriodEvent = false;
		}
	}

	function jumpToRightClickTarget()
	{
		process(rightClickStorage, rightClickTarget);
	}

	function onPrimaryRightClick()
	{
		// プライマリレイヤで右クリックされたときに呼ばれる

		// beginskip処理中は最優先横取り
		if (prevSkipMode !== void) {
			skipMode = SKIP_CANCEL;
			conductor.trigger('click'); // スキップ待ち解除
			callHook(rightClickHook);   // フックしてるものがいたらそれは送る
			return;
		}
		if(!callHook(rightClickHook))
		{
			if(getKeyState(VK_LBUTTON))
			{
//				enterAutoMode();
				return;
			}
			// オート・スキップ中なら右クリックでキャンセル
			if (conductor.status != conductor.mStop) {
				// 条件に関してはonPrimaryClickを参照
				if (canCancelSkipByClick && skipMode && skipMode < SKIP_FAST) return cancelSkip();
				if (autoMode) return cancelAutoMode();
			}
			if(!rightClickEnabled) return;
			if(inStable)
			{
				if(rightClickJump)
					jumpToRightClickTarget();
				else if(rightClickCall && conductor == mainConductor)
					callRightClickSubRoutine();
				else
					switchMessageLayerHiddenByUser();
			}
			setMenuAccessibleAll();
		}
	}

	//------------------------------------------------------- 前景レイヤ処理 --

    function allocateCharacterLayers(num, reorder=true, level=0)
	{
		// 前景レイヤ数を num に設定する
		if(fore.layers.count > num)
		{
			// レイヤが減る
			for(var i = num; i<fore.layers.count; i++)
			{
				invalidate fore.layers[i];
				invalidate back.layers[i];
			}
			fore.layers.count = num;
			back.layers.count = num;
		}
		else if(fore.layers.count < num)
		{
            // レイヤが増える
            if (reorder) {
                for(var i = fore.layers.count; i<num; i++)
                {
                    fore.layers[i] = new CharacterLayer(this, fore.base, "表前景レイヤ" + i, i, level);
                    back.layers[i] = new CharacterLayer(this, back.base, "裏前景レイヤ" + i, i, level);
                    fore.layers[i].setCompLayer(back.layers[i]);
                    back.layers[i].setCompLayer(fore.layers[i]);
                }
                reorderLayers(true, false);
            } else {
                // 該当レベルで最大の index 値をさがす
                var foreindex = (level + 1) * 100000;
                var backindex = (level + 1) * 100000;
                for (var i=0; i<fore.layers.count;i++) {
                    if (fore.layers[i].level == level && fore.layers[i].absolute > foreindex) {
                        foreindex = fore.layers[i].absolute;
                    }
                    if (back.layers[i].level == level && back.layers[i].absolute > backindex) {
                        backindex = back.layers[i].absolute;
                    }
                }
                foreindex += 100;
                backindex += 100;
                
                for(var i = fore.layers.count; i<num; i++) {
                    fore.layers[i] = new CharacterLayer(this, fore.base, "表前景レイヤ" + i, i, level);
                    back.layers[i] = new CharacterLayer(this, back.base, "裏前景レイヤ" + i, i, level);
                    fore.layers[i].setCompLayer(back.layers[i]);
                    back.layers[i].setCompLayer(fore.layers[i]);
                    fore.layers[i].absolute = foreindex;
                    back.layers[i].absolute = backindex;
                    foreindex += 100;
                    backindex += 100;
                }
            }
        }
		numCharacterLayers = num;
	}

	//------------------------------------------------- メッセージレイヤ処理 --

	function allocateMessageLayers(num, setdefaultfont = true)
	{
		// メッセージレイヤ数を num に設定する
		if(fore.messages.count > num)
		{
			// レイヤが減る
			for(var i = num; i<fore.messages.count; i++)
			{
				if(current == fore.messages[i] || current == back.messages[i])
					current = fore.messages[0], currentNum = 0, currentPage = 0;
				invalidate fore.messages[i];
				invalidate back.messages[i];
			}
			fore.messages.count = num;
			back.messages.count = num;
		}
		else if(fore.messages.count < num)
		{
			// レイヤが増える
			for(var i = fore.messages.count; i<num; i++)
			{
				fore.messages[i] = new MessageLayer(this, fore.base, "表メッセージレイヤ" + i, i, true);
				back.messages[i] = new MessageLayer(this, back.base, "裏メッセージレイヤ" + i, i, true);
				fore.messages[i].setCompLayer(back.messages[i]);
				back.messages[i].setCompLayer(fore.messages[i]);
				fore.messages[i].clear();
				back.messages[i].clear();
			}
			reorderLayers(false, true);
			if(setdefaultfont) setMessageLayerUserFont();
		}
		numMessageLayers = num;
	}

	function setCurrentMessageLayer(elm)
	{
		// 現在のメッセージレイヤを設定
		var page = getMessageLayerPageFromElm(elm);
		var num = getMessageLayerNumberFromElm(elm);
		currentNum = num;
		currentPage = page;
		if(page) current = back.messages[num]; else current = fore.messages[num];
		currentWithBack = +elm.withback if elm.withback !== void;
	}

	function setMessageLayerPosition(elm)
	{
		// 現在のメッセージレイヤの位置、属性を設定
		var layer = getMessageLayerObjectFromElm(elm);
		elm.setPosition(elm);
	}

	function clearMessageLayers(resetcurrent)
	{
		// すべてのメッセージレイヤのクリア
		// ct タグから呼ばれる
		// resetcurrent が true の場合は現在のメッセージレイヤを
		// 表0に設定する

		var messages;
		messages = fore.messages;
		for(var i = messages.count-1; i >= 0; i--) messages[i].clear(true);
		messages = back.messages;
		for(var i = messages.count-1; i >= 0; i--) messages[i].clear(true);
		if(resetcurrent)
		{
			currentNum = 0;
			currentPage = 0;
			current = fore.messages[0];
			currentWithBack = false;
		}
	}

	function lockMessageLayerSelProcess()
	{
		// すべてのメッセージレイヤに 選択とprocessを禁止させる
		var messages;
		messages = fore.messages;
		for(var i = messages.count-1; i >= 0; i--) with(messages[i]) { .setSelProcessLock(true); .lockMotion(); }
		messages = back.messages;
		for(var i = messages.count-1; i >= 0; i--) with(messages[i]) { .setSelProcessLock(true); .lockMotion(); }

		invokeSelectLayerSetSelProcessLock(true);
	}

	function unlockMessageLayerSelProcess()
	{
		// すべてのメッセージレイヤの選択を許可する
		var messages;
		messages = fore.messages;
		for(var i = messages.count-1; i >= 0; i--) with(messages[i]) { .setSelProcessLock(false); .unlockMotion(); }
		messages = back.messages;
		for(var i = messages.count-1; i >= 0; i--) with(messages[i]) { .setSelProcessLock(false); .unlockMotion(); }

		invokeSelectLayerSetSelProcessLock(false);
	}

	function setMessageLayerUserFont()
	{
		// すべてのメッセージレイヤの defaultAntialiased と
		// userFace を設定する
		var messages;
		messages = fore.messages;
		for(var i = messages.count-1; i >= 0; i--)
			messages[i].defaultAntialiased = chDefaultAntialiased,
			messages[i].userFace = chDefaultFace,
			messages[i].resetNameLayerFont();
		messages = back.messages;
		for(var i = messages.count-1; i >= 0; i--)
			messages[i].defaultAntialiased = chDefaultAntialiased,
			messages[i].userFace = chDefaultFace,
			messages[i].resetNameLayerFont();
		if (typeof historyLayer.fontUpdate == "Object") {
			/**/   historyLayer.fontUpdate(historyShowing);
		}
		callHook("onSetMessageLayerUserFont", chDefaultFace);
	}

	function storeMessageLayerSelProcessLock()
	{
		// すべてのメッセージレイヤの storeSelProcessLock を呼び出す
		var messages;
		messages = fore.messages;
		for(var i = messages.count-1; i >= 0; i--) messages[i].storeSelProcessLock();
		messages = back.messages;
		for(var i = messages.count-1; i >= 0; i--) messages[i].storeSelProcessLock();
	}

	function restoreMessageLayerSelProcessLock()
	{
		// すべてのメッセージレイヤの restoreSelProcessLock を呼び出す
		var messages;
		messages = fore.messages;
		for(var i = messages.count-1; i >= 0; i--) messages[i].restoreSelProcessLock();
		messages = back.messages;
		for(var i = messages.count-1; i >= 0; i--) messages[i].restoreSelProcessLock();
	}

	function setMessageLayerHiddenState(b, page)
	{
		var layers, base = !page ? fore : back;
		layers = base.messages;
		for(var i = layers.count-1; i >= 0; i--) layers[i].setHiddenStateByUser(b);
		layers = base.layers;
		for(var i = layers.count-1; i >= 0; i--) layers[i].setHiddenStateByUser(b);

		selectLayer.setHiddenStateByUser(b);
		//mapSelectLayer.setHiddenStateByUser(b);

		// プラグインを呼ぶ
		forEachEventHook('onMessageHiddenStateChanged',,b, page);
	}

    // メッセージレイヤ状態を同期
    function syncMessageLayer() {
        var messages = back.messages;
        for(var i = messages.count-1; i >= 0; i--) messages[i].assignComp();
    }
    
	function hideMessageLayerByUser()
	{
        // メッセージレイヤを一時的に隠す
		if(messageLayerHiding) return;
		setMessageLayerHiddenState(true);
		if(typeof this.rightClickMenuItem !== "undefined")
			rightClickMenuItem.checked = true;
		messageLayerHiding = true;
		fore.base.cursor = cursorWaitingClick;
		setMenuAccessibleAll();
	}

	function showMessageLayerByUser()
	{
        // 一時的に隠されていたメッセージレイヤを元に戻す
		if(!messageLayerHiding) return;
        setMessageLayerHiddenState(false);
		if(typeof this.rightClickMenuItem !== "undefined")
			rightClickMenuItem.checked = false;
		messageLayerHiding = false;
		conductor.trigger('message'); // 'message' を送る
		clickWaiting = _clickWaiting; // マウスカーソル再設定
		setMenuAccessibleAll();
	}

	function switchMessageLayerHiddenByUser()
	{
		// メッセージレイヤの非表示/表示を切り替える
		if(messageLayerHiding) showMessageLayerByUser(); else hideMessageLayerByUser();
	}

	function hideMessageLayerByScenario(elm)
	{
		// シナリオからメッセージを一時的に隠す
		hideMessageLayerByUser();
		conductor.wait(%[ // コンダクタを待ちに
			message : function
			{
				// やることなし
			} incontextof this
			]);
        notifyStable();
		return -2; // break
	}

    function setCurrentMessageLayerVisibleFast(page, visible) {
		var base = page == 0 ? fore : back;
		var msg = base.messages[currentNum];
		if (visible) clearFace(0,1);
		msg.visible = visible;
		forEachEventHook('onCurrentMessageVisibleChanged',
						 function(handler, f) { handler(f.hidden, f.page, 0); } incontextof this,
						 %[hidden:!visible, page:page]);
    }

    /**
     * 現在のメッセージ窓の表示状態の変更
     * @param visible
     * @return 処理の待ちが必要な場合は true を返す
	 */
    function setCurrentMessageLayerVisible(visible, time) {

		if (visible) clearFace(0);
		
		if (fore.messages[currentNum].visible != visible) {
			var fadeTime = (time !== void ? +time : messageFadeTime) * effectspeed;
			if (fadeTime > 0) {
				
				// 裏は先に消しておく。そうしないと msgvisible がとんでしまって
				// せっかくの待ちが解除される
                back.messages[currentNum].visible = visible;
                
                // 処理待ち
                var layer = fore.messages[currentNum];
                layer.setVisibleTime(visible, fadeTime);

				conductor.wait(%[
				click : function(layer) {
					updateBeforeCh = 1;
					layer.fadeDone();
				} incontextof this,
					'click_arg' => layer,
				msgvisible : function() {
					updateBeforeCh = 1;
				} incontextof this
					]);
				forEachEventHook('onCurrentMessageVisibleChanged',
								 function(handler, f) { handler(f.hidden, f.page, f.time); } incontextof this,
								 %[hidden:!visible, page:0, time:fadeTime ]);
				return true;
            } else {
				fore.messages[currentNum].visible = visible;
				back.messages[currentNum].visible = visible;
				forEachEventHook('onCurrentMessageVisibleChanged',
								 function(handler, f) { handler(f.hidden, f.page, 0); } incontextof this,
								 %[hidden:!visible, page:0]);
			}
        }
        return false;
    }

	// ----------------------------------------------------------------------------
	// メッセージ窓用特殊インターフェース
	// ----------------------------------------------------------------------------

	// 名前を単純消去
	function clearName() {
		if (textWriteEnabled) {
			current.processName();
			if(currentWithBack) current.comp.processName();
		}
	}
	
	// 名前の描画処理。履歴にも登録
	function drawName(name) {
		// 名前加工処理
		if (typeof global.dispNameFilter !== 'undefined') {
			name = global.dispNameFilter(name);
		}
		if (textWriteEnabled) {
			current.processName(name);
			if(currentWithBack) current.comp.processName(name);
		}
		callHook("onDrawName", name);
	}

	/**
	 * 表情表示処理下請け
	 */
	function loadFaceLayer(layer, name) {
		try {
			layer.loadImages(name);
		} catch (e) {
			errorImage(name + ":表情画像がロードできません");
		}
		layer.visible = true;
	}

    /**
     * 表情消去処理下請け
	 */
	function clearFaceLayer(layer, clearImage) {
		if (clearImage !== void) {
			loadFaceLayer(layer, clearImage);
		} else {
			with (layer) {
				.fillRect(0,0,.width,.height,0);
				.visible = true;
			}
        }
    }

	/**
	 * 表情レイヤへの描画
	 * @param layer 描画先
	 * @param image 画像描画関数または画像そのもの
	 * @return 描画された場合はtrue
	 */
	function drawFaceLayer(layer, image, clearImage) {
		if (layer !== void) {
			if (typeof image == "Object") {
				layer.visible = true;
				if (image(layer)) {
					return true;
				}
				clearFaceLayer(layer, clearImage);
			} else if (image !== void) {
				loadFaceLayer(layer, image);
				return true;
			} else {
				clearFaceLayer(layer, clearImage);
			}
		}
	}
	
	/**
	 * 表情の消去
	 * @param time 消去時間指定
	 * @param page ページ指定（0:fore, 1:back, 省略時はcurrent）
	 */
	function clearFace(time, page = void) {
		var layer;
		if (page === void) layer = current.faceLayer;
		else layer = (page == 0 ? fore : back).messages[currentNum].faceLayer;
		if (layer !== void) {
			time *= effectspeed;
			if (layer.visible && time > 0) {
				layer.stopTransition(); // 副作用でfore/backが入れ替わる場合あり
				var fore = layer = current.faceLayer;
				var back =         current.comp.faceLayer;
				if (fore !== void && back !== void) {
					if (fore.visible) {
						clearFaceLayer(back, clearFaceImage);
						fore.syncSize(back);
						fore.beginTransition(%[method: "crossfade", time:time], back);
					}
				}
			} else {
				clearFaceLayer(layer, clearFaceImage);
			}
			if (typeof layer.onFaceCleared == "Object") layer.onFaceCleared();
		}
	}

	/**
	 * 表情の描画
	 * @param image 描画関数または画像ファイル名
	 * @param time 表示時間指定
	 */
	function drawFace(image, time) {
		var layer = current.faceLayer;
		if (layer !== void) {
			layer.stopTransition(); // 副作用でfore/backが入れ替わる場合あり
			var fore = current.faceLayer;
			var back = current.comp.faceLayer;
			var hasImage;
			time *= effectspeed;
			if (time > 0) {
				if (fore !== void && back !== void) {
					if (!fore.visible) {
						// 未表示ならとりあえず表示させておく
						fore.fillRect(0,0,fore.width,fore.height,0);
						fore.visible = true;
					}
					hasImage = drawFaceLayer(back, image, clearFaceImage);
					fore.syncSize(back);
					fore.beginTransition(%[method: "crossfade", time:time ], back);
				}
			} else {
				hasImage = drawFaceLayer(fore, image, clearFaceImage);
				back.assignImages(fore, true);
			}
			if (typeof fore.onFaceDrawed == "Object") fore.onFaceDrawed(hasImage);
		}
	}

	// ----------------------------------------------------------------------------
	
	property genericFontInstance { getter { return fore.base.font; } }
	function getFontFaceList() {
		return genericFontInstance.getList(...);
	}
	function selectFontDoUserSelect() {
		// フォントを選択する
		with (genericFontInstance) {
			.face = chDefaultFace;
			.height = -20;
			if (.doUserSelect(...)) return .face;
		}
	}
	function selectFont()
	{
		var flags = fsfSameCharSet | fsfNoVertical | fsfTrueTypeOnly | fsfUseFontFace;
		if(showFixedPitchOnlyInFontSelector) flags |= fsfFixedPitch;
		var sample = typeof this.selectFontSampleText == "String" ? selectFontSampleText : "ABCDEFGHIあいうえお亜胃宇絵御";
		var sel = selectFontDoUserSelect(flags, "フォントの選択", "フォントを選択してください", sample);
		if (sel != "")
		{
			chDefaultFace = sel;
			setMessageLayerUserFont();
		}
	}

	function mapPrerenderedFont(storage)
	{
		// レンダリング済みフォントを現在の操作対象のレイヤに選択
		// されているフォントにマッピングする
		current.decideSizeChange();
		current.lineLayer.font.mapPrerenderedFont(storage);
	}

    //------------------------------------------------- レイヤを正しい順序に --

    // 指定したレイヤを指定したレベルに移動する
    function toLevel(n, newlevel, base) {
        if (base === void) {
            base = fore;
        }
        if (n === void) {
            return;
        }
        n = +n;
        newlevel = +newlevel;
        if (n < base.layers.count) {
            
            var level = base.layers[n].level;
            if (level == newlevel) {
                return;
            }
            var index = base.layers[n].absolute;
            // もともといたレベルの調整
            for (var i = 0; i<base.layers.count; i++) {
                if (i != n && base.layers[i].level == level) {
                    if (base.layers[i].absolute > index) {
                        base.layers[i].absolute -= 100;
                    }
                }
            }
            // 新規レベルの一番上にもっていく
            var newindex = (newlevel + 1) * 100000;
            for (var i = 0; i<base.layers.count; i++) {
                if (i != n && base.layers[i].level == newlevel && base.layers[i].absolute > newindex) {
                    newindex = base.layers[i].absolute;
                }
            }
            newindex += 100;
            //dm("レベル設定:" + newlevel + ":" + newindex);
            base.layers[n].absolute = newindex;
            base.layers[n].level = newlevel;
        }
    }
    
    // 同じレベルのレイヤの中で最前列に移動
    function toFront(n, base) {
        if (base === void) {
            base = fore;
        }
        if (n !== void && n < base.layers.count) {
            var level = base.layers[n].level;
            var index = base.layers[n].absolute;
            var maxindex = index;
            for(var i = 0; i<base.layers.count; i++) {
                if (i != n && base.layers[i].level == level) {
                    if (base.layers[i].absolute > maxindex) {
                        maxindex = base.layers[i].absolute;
                    }
                    if (base.layers[i].absolute > index) {
                        base.layers[i].absolute -= 100;
                    }
                }
            }
            base.layers[n].absolute = maxindex;
        }
    }

    // レイヤを最後列に
    function toBack(n, base) {
        if (base === void) {
            base = fore;
        }
        if (n !== void && n < base.layers.count) {
            var level = base.layers[n].level;
            var index = base.layers[n].absolute;
            var minindex = index;
            for(var i = 0; i<base.layers.count; i++) {
                if (i != n && base.layers[i].level == level) {
                    if (base.layers[i].absolute < minindex) {
                        minindex = base.layers[i].absolute;
                    }
                    if (base.layers[i].absolute < index) {
                        base.layers[i].absolute += 100;
                    }
                }
            }
            base.layers[n].absolute = minindex;
        }
    }

    function reorderLayers(ch=true, msg=true)
	{
        // レイヤを正しい順序に並び替える
        if (ch) {
            var indexes = %[];
            for(var i = 0; i<fore.layers.count; i++)
            {
                var level = fore.layers[i].level;
                var index = indexes[level];
                if (index === void) {
                    index = (level + 1) * 100000;
                }
                fore.layers[i].absolute = index;
                back.layers[i].absolute = index;
                index += 100;
                indexes[level] = index;
            }
        }

        if (msg) {
            var index = 1000000;
            for(var i = 0; i<fore.messages.count; i++)
            {
                fore.messages[i].absolute = index;
                back.messages[i].absolute = index;
                index += 1000;
            }
        }

		historyLayer.absolute = historyLayerAbsolute;
	}

	//--------------------------------------------- 属性->レイヤオブジェクト --

	function getLayerFromElm(elm, prefix = '')
	{
		// elm に指定されている page と layer 属性から、該当する
		// オブジェクトを返す
		// prefix には、属性名の前につけるプレフィクスを指定する
		var base;
		if(elm[prefix + 'page'] == 'back') base = back; else base = fore;
		var layer = elm[prefix + 'layer'];
		if(layer == 'base') return base.base; // 背景
		if(layer[0] == 'm')
		{
			// message? ( ? = 数値 )
			// ここではあまり厳密にエラーチェックはしない
			if(layer == 'message') return base.messages[currentNum];
			return base.messages[+layer.substr(7)];
		}
		return base.layers[+layer];
	}

	function getLayerPageFromElm(elm, backlay)
	{
		// getLayerFromElm と似ているが、page 属性まではみない。
		// backlay が true のときは裏、false の時は表のレイヤを返す。
		// elm.layer が void の時は背景レイヤを帰す
		var base = backlay?back:fore;
		var layer = elm.layer;
		if(layer === void || layer == 'base') return base.base; // 背景
        if(layer[0] == 'm')
		{
			if(layer == 'message') return base.messages[currentNum];
			return base.messages[+layer.substr(7)];
		}
		return base.layers[+layer];
	}

	function getMessageLayerPageFromElm(elm)
	{
		// elm から該当する表/裏画面のメッセージレイヤ配列を返す
		if(elm.page == 'back') return 1; else return 0;
	}

	function getMessageLayerNumberFromElm(elm)
	{
		// elm の layer 属性の示すメッセージレイヤ番号を返す
		var layer = elm.layer;
		if(layer === void || layer == 'message') return currentNum;
		return +layer.substr(7);
	}

	function getMessageLayerObjectFromElm(elm)
	{
		// elm の layer 属性の示すメッセージレイヤを返す
		var page = elm.page;
		var layer = elm.layer;
		if(page === void && layer === void) return current;
		var base;
		if(page == 'back') base = back; else base = fore;
		if(layer === void || layer == 'message') return base.messages[currentNum];
		return base.messages[+layer.substr(7)];
	}

	function getMessageLayerObjectFromPageAndNumber(page, num)
	{
		return (page?back:fore).messages[num];
	}

	//----------------------------------------------------- レイヤコピー関連 --

	function backupLayer(elm, toback)
	{
        // レイヤの表←→裏間のコピーを行う
		// toback = true の場合は表→裏、false の場合は裏→表
		if(elm.layer !== void)
		{
			// レイヤ指定がある
			getLayerPageFromElm(elm, toback).assignComp(); // 対のレイヤの内容をコピー
		}
		else
		{
			// レイヤ指定が無いので全部のレイヤをコピー
			var base = toback ? back:fore;
			base.base.assignComp();
            var layers = base.layers, messages = base.messages;
			for(var i = layers.count-1; i >= 0; i--) layers[i].assignComp();
			for(var i = messages.count-1; i >= 0; i--) messages[i].assignComp();

			forEachEventHook('onCopyLayer',
				function(handler, f) { handler(f.toback, true); } incontextof this,
				%[toback:toback]);
		}
	}

	function copyLayer(elm)
	{
		// elm に従って同種のレイヤ間のコピーを行う
		var src = getLayerFromElm(elm, 'src');
		var dest = getLayerFromElm(elm, 'dest');
		dest.assign(src);
	}

	//--------------------------------------------------- アニメーション関連 --

	function onAnimationStopped(name, segment)
	{
		// アニメーションが停止した
		conductor.trigger('anim:' + name + ':' + segment);
	}

	function waitAnimation(elm, canskip)
	{
		// アニメーションの停止をまつ
		var target;
		if (elm.layer !== void) {
			target = getLayerFromElm(elm);
		} else if (typeof elm.target == "Object") {
			target = elm.target;
		} else if (typeof elm.target == "String") {
			target = Scripts.eval(elm.target);
		}
		if (target !== void) {
			if (typeof target == "Object" && target instanceof "AnimationLayer") {
				return waitLayerAnimation(layer, elm.canskip, +elm.wait, +elm.seg);
			} else {
				dm("アニメーション待ちはレイヤに指定する必要があります");
			}
		}
		return 0;
	}

	function waitLayerAnimation(layer, canskip, wait=0, seg=0)
	{
		// アニメーションの停止をまつ
		if(!layer.canWaitAnimStop(seg)) return 0; // アニメーションの停止を待つ
		//dm("アニメーションの終了待ち1");
		var name = 'anim:' + layer.name + ':' + seg;		
		if((canskip === void || +canskip) && clickSkipEnabled)
		{
			conductor.wait(%[
				click : function (layer)
				{
					layer.stopAnim(0); // 再生を終了する
				} incontextof this,
				'click_arg' => layer, // ハンドラへの引数
				name => function(arg) {
					return arg;
				} incontextof this,
				name + "_arg" => wait,
					]);
		} else {
			conductor.wait(%[
				name => function(arg) {
					return arg;
				} incontextof this,
				name + "_arg" => wait,
					]);
		}
		return -2;
	}

	// アニメーションレイヤの強制無効判定
	function isDisabledAnimationLayer(lay) {
		return callHook("isDisabledAnimationLayer", lay);
	}


	//--------------------------------------------------- トランジション関連 --

	function beginTransition(layer, elm) {
		if (layer == fore.base)  {
			if (selectShowing) {
				selectLayer.setParent(back.base, selectLayerAbsolute);
			}
			if (mapSelectShowing) {
				mapSelectLayer.setParent(back.base, mapSelectLayerAbsolute);
			}
		}
		layer.beginTransition(elm);
	}
	
	function onLayerTransitionCompleted(layer, dest, src)
	{
		// レイヤでトランジションが終了したときに呼ばれる
		conductor.trigger('trans_' + (string)layer);
		if (!isWorkingWaitAction(layer)) {
			conductor.trigger('acttrans_' + (string)layer);
		}
		if (transCount == 0) {
			// 全トランジション終了
			conductor.trigger('trans'); // 'trans' を送る
			forEachEventHook('onAllTransitionCompleted');
			if (!isWorkingWaitAction()) {
				conductor.trigger('acttrans');
			}
			if (typeof this.transCompletedHandler == "Object") {
				transCompletedHandler();
			}
		}
    }

	/**
	 * 単独レイヤのトランジションを待つ
	 */
	function waitSingleTransition(target, canskip=void, wait=0) {
		if (!target.inTransition) return 0; // アクションを待てない
		var name = 'trans_' + (string)target;
		if((canskip === void || +canskip) && clickSkipEnabled)
		{
			// スキップできる場合
			if(skipMode && effectspeed==0)
			{
				// スキップ動作中
				target.stopTransition(true);
				return 0; // 自動移動を停止させてすぐに返る
			}
			conductor.wait(%[
				click : function (target)
				{
                    updateBeforeCh = 1;
					if (isvalid target) {
						target.stopTransition(true); // アクションを停止
					}
				} incontextof this,
				'click_arg' => target, // ハンドラへの引数
				name => function(arg)
				{
					updateBeforeCh = 1;
					return arg;
				} incontextof this,
				name + "_arg" => wait
				]);
		}
		else
		{
			conductor.wait(%[
				name => function(arg)
				{
					updateBeforeCh = 1;
					return arg;
				} incontextof this,
				name + "_arg" => wait
				]);
		}
		return -2;
	}
	
	function waitAllTransition(elm)
	{
		// トランジションを待つ
		if(transCount == 0) return 0; // トランジションを待てない
		if((elm.canskip === void || +elm.canskip) && clickSkipEnabled)
		{
			// スキップできる場合
			if(skipMode && effectspeed==0)
			{
				// スキップ動作中
				stopAllTransitions(true);
				return 0; // トランジションを停止させてすぐに返る
			}
			conductor.wait(%[
				click : function
				{
					updateBeforeCh = 1;
					stopAllTransitions(true); // すべてのトランジションは停止
				} incontextof this,
				trans : function(arg)
				{
					updateBeforeCh = 1;
					return arg;
				} incontextof this,
				trans_arg : +elm.wait
				]);
		}
		else
		{
			conductor.wait(%[
				trans : function(arg)
				{
					updateBeforeCh = 1;
					return arg;
				} incontextof this,
				trans_arg : +elm.wait
				]);
		}
		return -2;
	}

	function waitTransition(elm) {
        // トランジションを待つ
		var target;
		if (elm.layer !== void) {
			target = getLayerFromElm(elm);
		} else if (typeof elm.target == "Object") {
			target = elm.target;
		} else if (typeof elm.target == "String") {
			target = Scripts.eval(elm.target);
		}
		if (target !== void) {
			if (isvalid target) {
				if (typeof target == "Object" && typeof target.inTransition != "undefined") {
					return waitSingleTransition(target, elm.canskip, +elm.wait);
				} else {
					dm("トランジション待ちはレイヤに指定する必要があります");
				}
			}
			return 0;
		} else {
			return waitAllTransition(elm);
		}
	}
	
	function stopAllTransitions(all)
	{
		// すべてのトランジションを停止させる
		onLayerCommand(%[command:"stopTransition", args:[all]]);
		transCount = 0; // 一応
	}

	function callExchangeInfo()
	{
		// すべての背景レイヤをのぞく表レイヤに対して
		// exchangeInfo を呼ぶ
        var layers = fore.layers, messages = fore.messages;
		for(var i = layers.count-1; i >= 0; i--) layers[i].exchangeInfo();
		for(var i = messages.count-1; i >= 0; i--) messages[i].exchangeInfo();
    }

	function callAssignTransSrc()
	{
		// すべての背景レイヤをのぞく表レイヤに対して
		// assignTransSrc を呼ぶ
        var layers = fore.layers, messages = fore.messages;
		for(var i = layers.count-1; i >= 0; i--) layers[i].assignTransSrc();
		for(var i = messages.count-1; i >= 0; i--) messages[i].assignTransSrc();
        forEachEventHook('onCopyLayer',,false);
    }

	function exchangeForeBack()
	{
        var tmp = fore;
		fore = back;
		back = tmp;
		current = (currentPage?back:fore).messages[currentNum]; // current は設定し直し
		forEachEventHook('onExchangeForeBack');

        // 選択肢復帰
        if (selectShowing) {
            selectLayer.setParent(fore.base, selectLayerAbsolute);
        }
        if (mapSelectShowing) {
            mapSelectLayer.setParent(fore.base, mapSelectLayerAbsolute);
        }

        // 強制表示変更
        fore.base.visible = true;
        back.base.visible = false;
    }

	function swapBaseLayer()
	{
        // 背景レイヤのみを取り替える
        var tmp = fore.base;
		fore.base = back.base;
		back.base = tmp;
        current = (currentPage?back:fore).messages[currentNum]; // current は設定し直し

    }

	function swapStageLayer(id)
	{
        // 前景レイヤの表と裏を取り替える
        var fl = fore[id], bl = back[id];
        var tmp = fl;
        fl = bl;
        bl = tmp;
	}
    
	function swapCharacterLayer(id)
	{
		// 前景レイヤの表と裏を取り替える
		var fl = fore.layers, bl = back.layers;
		var tmp = fl[id];
		fl[id] = bl[id];
		bl[id] = tmp;
	}

	function swapMessageLayer(id)
	{
		// メッセージレイヤの表と裏を取り替える
		var fl = fore.messages, bl = back.messages;
		var tmp = fl[id];
		fl[id] = bl[id];
		bl[id] = tmp;
		current = (currentPage?back:fore).messages[currentNum]; // current は設定し直し
	}

	//--------------------------------------------------------- 自動移動関連 --

	function onLayerMoveStop()
	{
		// レイヤの自動移動が終了した
		conductor.trigger('move');
	}

	function waitMove(elm)
	{
		// 自動移動を待つ
		if(moveCount == 0) return 0; // 自動移動を待てない
		if((elm.canskip === void || +elm.canskip) && clickSkipEnabled)
		{
			// スキップできる場合
			if(skipMode && effectspeed==0)
			{
				// スキップ動作中
				stopAllMoves();
				return 0; // 自動移動を停止させてすぐに返る
			}
			conductor.wait(%[
				click : function
				{
					updateBeforeCh = 1;
					stopAllMoves(); // すべてのトランジションは停止
				} incontextof this,
				move : function(arg)
				{
					updateBeforeCh = 1;
					return arg;
				} incontextof this,
				move_arg : +elm.wait
				]);
		}
		else
		{
			conductor.wait(%[
				move : function(arg)
				{
					updateBeforeCh = 1;
					return arg;
				} incontextof this,
				move_arg : +elm.wait
				]);
		}
		return -2;
	}

	function stopAllMoves()
	{
		// すべての自動移動を停止させる
		var layers, messages;
		fore.base.stopMove();
		back.base.stopMove();
        layers = fore.layers, messages = fore.messages;
		for(var i = layers.count-1; i >= 0; i--) layers[i].stopMove();
		for(var i = messages.count-1; i >= 0; i--) messages[i].stopMove();
		layers = back.layers, messages = back.messages;
		for(var i = layers.count-1; i >= 0; i--) layers[i].stopMove();
		for(var i = messages.count-1; i >= 0; i--) messages[i].stopMove();
		moveCount = 0; // 一応
	}

	//--------------------------------------------------------- アクション関連 --

    // アクション対象全部
    var allActions = %[];
	var actionQueue = [];

	// アクションが動作中
	function isWorkingAction(target) {
		if (target !== void) {
			var info = allActions[target];
			return info !== void  && info.working;
		} else {
			var names = Scripts.getObjectKeys(allActions);
			var count = names.count;
			for (var i=0;i<count;i++) {
				var name = names[i];
				var info = allActions[name];
				if (info.working) {
					return true;
				}
			}
			return false;
		}
	}
	
	// wait対象アクションが動作中
	function isWorkingWaitAction(target) {
		if (target !== void) {
			var info = allActions[target];
			return info !== void  && info.workingWait;
		} else {
			var names = Scripts.getObjectKeys(allActions);
			var count = names.count;
			for (var i=0;i<count;i++) {
				var name = names[i];
				var info = allActions[name];
				if (info.workingWait) {
					return true;
				}
			}
			return false;
		}
	}

	/**
	 * 指定したプロパティのアクションが実行中か判定
	 */
	function hasAction(target, propName) {
		if (target != "") {
			var info = allActions[target];
			return info !== void && info.hasAction(propName);
		}
		return false;
	}

	/**
	 * 指定したプロパティのアクションを破棄
	 */
	function delAction(target, propName) {
		if (target != "") {
			var info = allActions[target];
			if (info !== void) {
				var queue = [];
				info.delAction(propName, queue);
				execQueue(queue);
			}
		}
	}
	
	/**
	 * アクション情報をコピーする
	 * @param src コピー元
	 * @param dest コピー先
	 */
	function copyAction(src, dest) {
		//dm("アクション情報のコピー");
		stopAction(dest, true);
		var srcAction = allActions[src];
		if (srcAction !== void) {
			var destAction = srcAction.clone(dest);
			allActions[dest] = destAction;
		}
	}

	/**
	 * アクション実行結果を返す
	 * @param target ターゲットオブジェクト
	 */
	function getActionResult(target) {
		var info = allActions[target];
		return info !== void ? info.getResultValue() : void;
	}
	
    /**
	 * アクションを登録
	 * @param target ターゲットオブジェクト
	 * @param action アクション情報
	 * @param complete  完了処理上書き指定
	 * @param nowait 待ちなし上書き指定
	 */
	function beginAction(target, action, complete, nowait) {
		//dm(@"アクション開始:${target}:${nowait}");
		if (target != "") {
			var info = allActions[target];
			if (info === void) {
				info = new ActionSequense(target);
				allActions[target] = info;
			}
			info.beginAction(action, complete, nowait, actionQueue);
			flipStart();
			return info.getResultValue();
		}
	}

	// 個別アクション停止
	function internalActionComplete(info) {
		var target;
		with (info) try {
			target = .target;
		} catch (e) {
			Debug.message(e.message);
		}
		conductor.trigger('action_' + (string)target);
		if (isvalid target && typeof target.inTransition != "undefined" && !target.inTransition) {
			conductor.trigger('acttrans_' + (string)target);
		}
	}

	function execQueue(queue) {
		var func;
		while ((func = queue.shift()) !== void) {
			func();
		}
		queue.clear();
	}
	
	/**
     * アクションを停止
     * @param target ターゲットオブジェクト
	 * @param all nowaitのアクションも停止する
	 */
	function stopAction(target, all=false) {
		if (target != "") {
			var info = allActions[target];
			if (info !== void) {
				var queue = [];
				info.stopAction(all, queue);
				execQueue(queue);
			}
		}
	}

    /**
     * 全アクションの停止
	 * @param all nowaitのアクションも停止する
     */
	function stopAllActions(all=false) {
		var queue = [];
		var names = Scripts.getObjectKeys(allActions);
		var count = names.count;
		for (var i=0;i<count;i++) {
			var name = names[i];
			var info = allActions[name];
			if (info !== void) {
				info.stopAction(all, queue);
			}
		}
		execQueue(queue);
		forEachEventHook('onStopAction',,all);
	}
	
    /**
     * アクション状態更新
     * システム全体から呼び出す
	 * @param now 現在時刻
	 * @return 動作中アクションが残ってる
     */
	function updateAction(now) {
		var names = Scripts.getObjectKeys(allActions);
		var count = names.count;
		var preAllWorkingWait = false;
		var afterAllWorkingWait = false;
		var update = false;
		for (var i=0;i<count;i++) {
			var name = names[i];
			var info = allActions[name];
			if (info !== void) {
				var preWorkingWait = info.workingWait;
				if (!preAllWorkingWait) preAllWorkingWait = true if preWorkingWait;
				if (info.doAction(now, actionQueue)) {
					internalActionComplete(info);
					invalidate info;
					delete allActions[name];
				} else {
					var afterWorkingWait = info.workingWait;
					if (preWorkingWait && !afterWorkingWait) {
						internalActionComplete(info);
					}
					if (!afterAllWorkingWait) afterAllWorkingWait = true if afterWorkingWait;
					update = true;
				}
			}
		}
		if (preAllWorkingWait && !afterAllWorkingWait) {
			// 全アクション終了
			conductor.trigger('action');
			forEachEventHook('onAllActionCompleted');
			if (transCount == 0) {
				conductor.trigger('acttrans');
			}
			actionCompletedHandler() if (typeof this.actionCompletedHandler == "Object");
		}
		execQueue(actionQueue);
		return update;
    }
    
	/**
	 * アクション待ち
	 */
	function waitSingleAction(target, canskip=void, wait=0) {
		if (!isWorkingWaitAction(target)) return 0; // アクションを待てない
		var name = 'action_' + (string)target;
		if((canskip === void || +canskip) && clickSkipEnabled)
		{
			// スキップできる場合
			if(skipMode && effectspeed==0)
			{
				// スキップ動作中
                stopAction(target);
				return 0; // 自動移動を停止させてすぐに返る
			}
			conductor.wait(%[
				click : function (target)
				{
                    updateBeforeCh = 1;
                    stopAction(target); // アクションを停止
				} incontextof this,
				'click_arg' => target, // ハンドラへの引数
				name => function(arg)
				{
					updateBeforeCh = 1;
					return arg;
				} incontextof this,
				name + "_arg" => wait
				]);
		}
		else
		{
			conductor.wait(%[
				name => function(arg)
				{
					updateBeforeCh = 1;
					return arg;
				} incontextof this,
				name + "_arg" => wait
				]);
		}
		return -2;
	}

	function waitAllAction(canskip, wait=0)	{
		// アクションを待つ
		if (!isWorkingWaitAction()) return 0; // アクションを待てない
		if((canskip === void || +canskip) && clickSkipEnabled)
		{
			// スキップできる場合
			if(skipMode && effectspeed==0)
			{
				// スキップ動作中
				stopAllActions(false);
				return 0; // 自動移動を停止させてすぐに返る
			}
			conductor.wait(%[
				click : function
				{
					updateBeforeCh = 1;
					stopAllActions(false); // すべてのアクションは停止
				} incontextof this,
				action : function(arg)
				{
					updateBeforeCh = 1;
					return arg;
				} incontextof this,
				action_arg : wait
				]);
		}
		else
		{
			conductor.wait(%[
				action : function(arg)
				{
					updateBeforeCh = 1;
					return arg;
				} incontextof this,
				action_arg : wait
				]);
		}
		return -2;
	}
	
	function waitAction(elm) {
		var target;
		if (elm.layer !== void) {
			target = getLayerFromElm(elm);
		} else if (typeof elm.target == "Object") {
			target = elm.target;
		} else if (typeof elm.target == "String") {
			target = Scripts.eval(elm.target);
		}
		if (target !== void) {
			if (isvalid target) {
				if (typeof target == "Object") {
					return waitSingleAction(target, elm.canskip, +elm.wait);
				} else {
					dm("アクション待ちはオブジェクトに指定する必要があります");
				}
			}
			return 0;
		} else {
			return waitAllAction(elm.canskip, +elm.wait);
		}
		return 0;
	}
	
	// アクションとトランジションを同時に待つ
	function waitSingleActionAndTransition(target, canskip, wait=0)	{
		if (!isWorkingWaitAction(target) && !target.inTransition) return 0; // 待てない
		var name = 'acttrans_' + (string)target;
		if((canskip === void || +canskip) && clickSkipEnabled)
		{
			// スキップできる場合
			if(skipMode && effectspeed==0)
			{
				// スキップ動作中
				target.stopTransition(true);
				stopAction(target);
				return 0; // 自動移動を停止させてすぐに返る
			}
			conductor.wait(%[
				click : function(target)
				{
					updateBeforeCh = 1;
					if (isvalid target) {
						target.stopTransition(true);
						stopAction(target);
					}
				} incontextof this,
				'click_arg' => target, // ハンドラへの引数
				name => function(arg)
				{
					updateBeforeCh = 1;
					return arg;
				} incontextof this,
				name + "_arg" => wait
				]);
		}
		else
		{
			conductor.wait(%[
				name => function(arg)
				{
					updateBeforeCh = 1;
					return arg;
				} incontextof this,
				name + "_arg" => wait
				]);
		}
		return -2;
	}
	
	// アクションとトランジションが全て終わるのを待つ
	function waitAllActionAndTransition(canskip, wait=0)	{
		if (!isWorkingWaitAction() && transCount == 0) return 0;
		if((canskip === void || +canskip) && clickSkipEnabled)
		{
			// スキップできる場合
			if(skipMode && effectspeed==0)
			{
				// スキップ動作中
				stopAllActions(false);
				stopAllTransitions(false);
				return 0; // 自動移動を停止させてすぐに返る
			}
			conductor.wait(%[
				click : function
				{
					updateBeforeCh = 1;
					stopAllActions(false);
					stopAllTransitions(false);
				} incontextof this,
				acttrans : function(arg)
				{
					updateBeforeCh = 1;
					return arg;
				} incontextof this,
				acttrans_arg : wait,
				]);
		}
		else
		{
			conductor.wait(%[
				acttrans : function(arg)
				{
					updateBeforeCh = 1;
					return arg;
				} incontextof this,
				acttrans_arg : wait,
				]);
		}
		return -2;
	}

	function waitActionAndTransition(elm) {
		var target;
		if (elm.layer !== void) {
			target = getLayerFromElm(elm);
		} else if (typeof elm.target == "Object") {
			target = elm.target;
		} else if (typeof elm.target == "String") {
			target = Scripts.eval(elm.target);
		}
		if (target !== void) {
			if (isvalid target) {
				if (typeof target == "Object") {
					return waitSingleActionAndTransition(target, elm.canskip, +elm.wait);
				} else {
					dm("アクション待ちはオブジェクトに指定する必要があります");
				}
			}
			return 0;
		} else {
			return waitAllActionAndTransition(elm.canskip, +elm.wait);
		}
	}
	
	//------------------------------------------------ ディレイ/スキップ関連 --

	function setDelay(elm)
	{
		// delay タグの処理
		var speed = elm.speed;
		if(speed == 'nowait')
		{
			chSpeed = 0;
			chUserMode = false;
		}
		else if(speed == 'user')
		{
			chUserMode = true;
			setUserSpeed();
		}
		else if(typeof speed == "String" && speed != "" && speed.charAt(0) == "x") {
			chSpeed = userChSpeed * +speed.substr(1);
			chUserMode = false;
		}
		else
		{
			chSpeed = +speed;
			chUserMode = false;
		}
		actualChSpeed = chSpeed;
	}

	function getCurrentRead()
	{
		// プラグイン側判定
		var ret;
		if ((ret = forEachFunctionHook("getCurrentRead")) !== void) {
			return ret;
		}

        // 回想中は常に既読
		if (isRecollection) {
			return true;
        }
		
        // 自動ラベルモードでは行単位で判定する必要がある 
        if (autoLabelMode) {
			if (autoLabelCurrentRecordName != "") {
				//dm("ラベル:" + autoLabelCurrentRecordName);
				//dm("フラグ:" + sflags[autoLabelCurrentRecordName]);
				//dm("カウント:" + autoLabelCount);
				return (sflags[autoLabelCurrentRecordName] !== void &&
						sflags[autoLabelCurrentRecordName] > autoLabelCount);
            }
        }

        // 現在のシナリオ部分が既読かどうかを判定する
		return autoRecordPageShowing && currentRecordName != "" &&
				+sflags[currentRecordName] || !autoRecordPageShowing;
	}

	function setUserSpeed()
	{
		// ユーザの選択した文字表示スピードを設定
		// この関数を読んだ時点ですでに userChSpeed には
		// あたらしい値が設定されているとみなす。
		// あるいは、ラベルごとに、その区域が既読か未読かで
		// 表示スピードを変える目的で呼ばれる
		if(chUserMode)
		{
			if(getCurrentRead())
				chSpeed = userCh2ndSpeed==-1?userChSpeed:userCh2ndSpeed; // 既読
			else
				chSpeed = userChSpeed; // 未読
		}
		actualChSpeed = chSpeed;
	}

	function checkClick() {
		if (skipMode == SKIP_CLICK) {
			cancelSkip();
			return true;
		}
		return skipMode != SKIP_NONE;
	}

	function skipToClick()
	{
		// クリック待ち記号までスキップ
		skipMode = SKIP_CLICK;
	}

	function skipToPage()
	{
		// 改ページ待ち記号までスキップ
		skipMode = SKIP_PAGE;
	}

	function skipToStop(nodisp=false, tempallskip = void)
	{
		_tempAllskip = tempallskip;
		if (tempallskip !== void) callHook("onTempSkipToStop", tempallskip);

        // すべてスキップまたは既読の場合のみ
        if (allskip || getCurrentRead()) {
            // 次の停止までスキップ
            onPrimaryClick(true); // クリックの動作をエミュレートする
            skipMode = SKIP_STOP;
			callHook("onSkipToStop", allskip, nodisp);
			if (nodisp) {
				skipNoDisp = true;
				// XXX 暫定
				// delayrun の解除
				doneDelay(true);
				// ボイス停止
				stopAllVoice(0, true);
				// メッセージ窓消去
				setCurrentMessageLayerVisibleFast(0, false);
				
			}
		}
	}

	function skipToStop2()
	{
		// 次の停止までスキップ(早送りモード)
		showMessageLayerByUser() if(messageLayerHiding);
		onPrimaryClick(true);
		skipMode = SKIP_FAST;
		callHook("onSkipToStop2");
	}

	function skipToLabel(nodisp=false, stoplabel = "")
    {
        // デバッグ用: 次のラベルまで強制スキップ
		onPrimaryClick(true);
		skipMode = SKIP_LABEL;
		skipToLabelStop = stoplabel;
		if (nodisp) {
			skipNoDisp = true;
			if (stoplabel != "" && skipNoDispWin !== void) {
				invalidate skipNoDispWin;
				skipNoDispWin = void;
			}
		}
    }
    
	function cancelSkip()
	{
		// スキップ動作をキャンセル
		if (skipMode == SKIP_FAST) skipKeyRepressed = false;
		skipMode = SKIP_NONE;
		_tempAllskip = void;
		callHook("onCancelSkip");
	}

	function enterNoWait()
	{
		// nowait タグの処理
		beforeNoWaitActualChSpeed = actualChSpeed;
		beforeNoWaitChUserMode = chUserMode;
		actualChSpeed = 0;
	}

	function leaveNoWait()
	{
		// endnowait タグの処理
		actualChSpeed = beforeNoWaitActualChSpeed;
		chUserMode = beforeNoWaitChUserMode;
	}

	function setAutoWait(elm)
	{
		// 自動ウェイトを設定する
		autoWCEnabled = +elm.enabled if elm.enabled !== void;
		autoWCChars = elm.ch if elm.ch !== void;
		autoWCWaits = [].split(",", elm.time) if elm.time !== void;
	}

	function cancelAutoMode()
	{
        // 自動読みすすみモードのキャンセル
		if (autoMode) {
			if (typeof this.autoModeMenuItem !== "undefined") {
                autoModeMenuItem.checked = false;
            }
			// オート待ちをキャンセル
			if (clickWaiting) {
				conductor.trigger("cancelauto");
			}
			autoMode = false;
			setMenuAccessibleAll();
		}
	}

	function enterAutoMode()
	{
        // 自動読みすすみモードに入る
        if(typeof this.autoModeMenuItem !== "undefined")
            autoModeMenuItem.checked = true;
        // クリック待ちの場合の挙動
		if (clickWaiting) {
			conductor.trigger('click');
			if (checkVoiceWait() !== void) {
				var t = conductor.lastTagName;
				if (t == 'p' || t == 'p2'){
					insertTag("p2");
				}
				else if(t == 'l'){
					insertTag("l");
				}
			}
		}
		autoMode = true;
    }
        
	//--------------------------------------------------------- ウェイト関連 --

	function resetWait()
	{
		// 時間原点のリセット
        timeOrigin = System.getTickCount();
	}

	function waitTime(waittime, canskip)
	{
		// waittime 分待つ
		waittime *= waitspeed;
		if(waittime == 0) return 0;

		if(canskip)
		{
            conductor.waitWithTimeOut(%[
				click : function() {} incontextof this,
				timeout : function() {} incontextof this,
				], waittime);
		}
		else
		{
			// スキップできない場合
			conductor.waitWithTimeOut(%[
				timeout : function(arg)	{} incontextof this
				], waittime);
		}
		return -2; // break
		
	}

	function doWait(elm)
	{
		// wait タグの処理
		var waittime;
		if(elm.mode == 'until')
		{
			// until モード
			waittime = timeOrigin + +elm.time - System.getTickCount();
			if(waittime < 0) { lastWaitTime = 0; return 0; } // すでに時間が経過している
			lastWaitTime = waittime;
			if(waittime < 6) return 0; // あまりに待ち時間が短いので待たない
		}
		else
		{
			waittime = +elm.time;
        }
		return waitTime(waittime, (elm.canskip === void || +elm.canskip) && clickSkipEnabled);
	}

	// 特殊な待ち
	// 一度でもクリックでキャンセルされた場合は timeOrigin を無効化してしまう
	function waitUntil(elm) {
		// until モード
		var waittime = timeOrigin + +elm.time - System.getTickCount();
		if(waittime < 0) { lastWaitTime = 0; return 0; } // すでに時間が経過している
		lastWaitTime = waittime;
		if(waittime < 6) return 0; // あまりに待ち時間が短いので待たない
		var canskip = (elm.canskip === void || +elm.canskip) && clickSkipEnabled;
		if (canskip) {
			// スキップできる場合
			if(skipMode) {
				// スキップ中の場合
				return 0; // スキップ中の場合はなにもせずに返る
			}
			conductor.waitWithTimeOut(%[
			click : function() {timeOrigin = 0;} incontextof this,
			timeout : function(){} incontextof this,
				], waittime);
		} else {
			// スキップできない場合
			conductor.waitWithTimeOut(%[
			timeout : function(){} incontextof this,
				], waittime);
		}
		return -2; // break
	}
	
	function doWaitCh(elm)
	{
		// +elm.time のカウント分、待つ
		var t = elm.time;
		return waitTime(actualChSpeed * (t === void ? 1 : +t),
			(elm.canskip === void || +elm.canskip) && clickSkipEnabled);
	}

	//--------------------------------------------------------- delay 関連 --

	var delayLabels  = %[]; // 全delay
	var delayTimeList = []; // 時間系delay一覧(小さい順)

	// 時間系delay情報更新
	function updateDelay(tick) {
		while (delayTimeList.count > 0 && delayTimeList[0].time >= tick) {
			var info = delayTimeList.shift();
			extractDelay(info.name);
		}
		extractDelay(tick);
		return delayTimeList.count > 0 || conductor.hasDelay();
	}

	// 時間系delay情報を追加
	function entryDelayTime(name, time) {
		var count = delayTimeList.count;
		var i=0;
		for (var i=0;i<count;i++) {
			if (delayTimeList[i].time > time) {
				delayTimeList.insert(i, %[name:name, time:time]);
				return;
			}
		}
		delayTimeList.push(%[name:name, time:time]);
	}

	// delayコマンド登録
	function entryDelayCmd(elm) {
		var entry = false;
		var names = [];
		names.assign(elm);
		for (var i=0;i<names.count;i+=2) {
			var name = names[i];
			var time = names[i+1];
			if (name != "tagname" && name != "taglist") {
				entry = true;
				delayLabels[name] = true;
				entryDelayTime(name, System.getTickCount() + +time);
			}
		}
		if (entry) {
			flipStart();
		}
	}
	
	// 待ちラベル情報登録
	function entryDelay(labels) {
		if (typeof labels == "Object") {
			if (labels instanceof "Dictionary") {
				var names = [];
				names.assign(labels);
				for (var i=0;i<names.count; i+=2) {
					delayLabels[names[i]] = true;
				}
			} else if (labels instanceof "Array") {
				for (var i=0;i<labels.count; i++) {
					delayLabels[labels[i]] = true;
				}
			}
		} else if (typeof labels == "String") {
			delayLabels[labels] = true;
		}
	}

	var keywaitLabelCount = 0;
	
	function clearDelay() {
		(Dictionary.clear incontextof delayLabels)();
		keywaitLabelCount = 0;
	}

	// 待ちラベル破棄
	function cancelDelay() {
		conductor.cancelDelay();
		clearDelay();
	}

	/*
	 * ディレイ処理完了指令
	 * @param all 保留中のものも全部解放
	 */
	function doneDelay(all) {
		clearVoiceMode(all);
		delayQueue.clear();
		extractTrigger.cancel();
		trigger("delay_*");
		clearDelay();
		mainConductor.extractDelay(void, all);
	}
	
	var delayQueue = [];

	var extractTrigger;
	
	// 待ちラベル解放
	function extractDelay(name) {
		if (!isvalid this || !isvalid extractTrigger) return; //[XXX] 終了時にサウンド停止系からコールバックがかかる場合がある
		delayQueue.push(name);
		extractTrigger.trigger();
	}

	function doExtractDelay() {
		var name;
		while ((name = delayQueue.shift()) !== void) {
			if (name === void || name === "*") {
				trigger("delay_*");
				clearDelay();
			} else if (typeof name == "String") {
				if (delayLabels[name] !== void) {
					delete delayLabels[name];
				}
				trigger("delay_" + name);
			}
			mainConductor.extractDelay(name);
		}
	}

	function waitDelay(name, canskip=true)
	{
		//dm("delay待ち:" + name + ":" +delayLabels[name]);
		if (name === void || delayLabels[name] === void) return 0;
		if(canskip !== void && +canskip && clickSkipEnabled)
		{
			// スキップできる場合
			if(skipMode)
			{
				// スキップ中の場合
				return 0; // スキップ中の場合はなにもせずに返る
			}

			conductor.wait(%[
				click : function() { clearDelay(); } incontextof this,
				"delay_" + name => function(){} incontextof this,
				"delay_*" => function(){} incontextof this,
				]);
		}
		else
		{
			// スキップできない場合
            conductor.wait(%[
				"delay_" + name => function(){} incontextof this,
				"delay_*" => function(){} incontextof this,
				]);
		}
		return -2; // break
	}
	
	//------------------------------------------------------------ quake関連 --

	function doQuake(elm)
	{
		// elm に従って quake を開始
		if(elm.time !== void)
		{
			if(defaultQuakeTimeInChUnit)
			{
				if(elm.timemode == 'ms')
					quakeEndTick = System.getTickCount() + +elm.time;
				else
					quakeEndTick = System.getTickCount() + +elm.time * chSpeed;
			}
			else
			{
				if(elm.timemode == 'delay')
					quakeEndTick = System.getTickCount() + +elm.time * chSpeed;
				else
					quakeEndTick = System.getTickCount() + +elm.time;
			}
		}
		else
		{
			quakeEndTick = -1;
		}

		if(elm.hmax !== void) quakeHorzMax = +elm.hmax; else quakeHorzMax = 10;
		if(elm.vmax !== void) quakeVertMax = +elm.vmax; else quakeVertMax = 10;

		quakeTimer.enabled = true;
		quaking = true;
	}

	function restoreQuake()
	{
		// restore から呼ばれ、栞を保存したときに揺れていた場合は揺らす
		if(quaking && quakeEndTick == -1)
			quakeTimer.enabled =true;
	}

    /**
     * レイヤ位置の調整
     * メッセージレイヤの位置を自動的にずらす
     */
	function setBaseLayerPos(x, y) {
		(global.Layer.setPos incontextof fore.base)(x,y);
		(global.Layer.setPos incontextof back.base)(x,y);
		if (!quakeMessageLayer) {
			for(var i = 0; i< fore.messages.count; i++) fore.messages[i].setDiff(-x, -y);
			for(var i = 0; i< back.messages.count; i++) back.messages[i].setDiff(-x, -y);
        }
		forEachEventHook('setBaseLayerPos',
						 function(handler, info) { handler(info.x, info.y); } incontextof this, %[x:x, y:y]);
	}

    
	function stopQuake()
	{
		// 揺れを停止
		setBaseLayerPos(0, 0);
		quakeTimer.enabled = false;
		quaking = false;
		conductor.trigger('quake');
	}

	function getForceDisableQuakeState()
	{
		// 揺れが無効かどうか
		return historyShowing || selectShowing || mapSelectShowing || panelShowing || currentDialog !== void ||
			(forceDisableQuakeOnExtraConductor && usingExtraConductor);
	}

	function onQuakeTimerInterval()
	{
		// quakeTimer により呼ばれる
		if(quakeEndTick != -1 && System.getTickCount() > quakeEndTick) { stopQuake(); return; }
		if(getForceDisableQuakeState())
		{
			// メッセージ履歴レイヤ表示中はさすがに揺れていられない
			setBaseLayerPos(0, 0);
			return;
		}
		var x, y;
		if(quakeHorzMax == quakeVertMax)
		{
			// だいたい同じ
			x = int(Math.random() * quakeHorzMax *2 - quakeHorzMax);
			y = int(Math.random() * quakeVertMax *2 - quakeVertMax);
		}
		else if(quakeHorzMax < quakeVertMax)
		{
			// 縦揺れ
			x = int(Math.random() * quakeHorzMax *2 - quakeHorzMax);
			y = int((quakePhase ? Math.random() : -Math.random()) * quakeVertMax);
		}
		else
		{
			// 横揺れ
			x = int((quakePhase ? Math.random() : -Math.random()) * quakeHorzMax);
			y = int(Math.random() * quakeVertMax*2 - quakeVertMax);
		}
		quakePhase = !quakePhase;
		setBaseLayerPos(x, y);
	}

	function waitQuake(elm)
	{
		// 揺れが終了するまでまつ
		if(!quaking || quakeEndTick == -1) return 0; // 揺れていなければ待たない
		if(elm.canskip !== void && +elm.canskip && clickSkipEnabled)
		{
			// スキップできる場合
			if(skipMode && effectspeed==0)
			{
				// スキップ中の場合
				stopQuake();
				return 0; // スキップ中の場合は揺れを停止させて返る
			}
			conductor.wait(%[
				click : function
				{
					stopQuake(); // 揺れは停止する
				} incontextof this,
				quake : function(arg)
				{
					// やることなし
					return arg;
				} incontextof this,
				quake_arg : +elm.wait
				]);
		}
		else
		{
			// スキップできない場合
			conductor.wait(%[
				quake : function(arg)
				{
					// やることなし
					return arg;
				} incontextof this,
				quake_arg : +elm.wait
				]);
		}
		return -2;
	}

	//------------------------------------------------------------- クリック --

	function onPrimaryClick(force = false)
	{
        // プライマリレイヤで「クリックの動作」がなにもフィルタリングされなかった
		// とき、プライマリレイヤから呼ばれる。
		clickCount++ if (!force);
		if(force || !callHook(leftClickHook))
		{
			if(messageLayerHiding)
			{
				showMessageLayerByUser(); // メッセージレイヤを表示する
				if (!force) return;
			}
//          else
            {
				var st = conductor.status;
				var runst = conductor.mRun;
				var stopst = conductor.mStop;

                if(st != stopst && canCancelSkipByClick && skipMode && skipMode < SKIP_FAST && cancelSkipWhenClick)
				{
					// クリックによるスキップの解除が可能
					cancelSkip();
					return;
				}

				// beginskip 用処理
				// 普通はそのままスキップに入るが、enableLeftBeginSkip が有効でない場合は
				// 逆にスキップさせない
				if (prevSkipMode !== void) {
					if (enableLeftBeginSkip) {
						skipMode = SKIP_CANCEL;
						conductor.trigger('click');
					}
					return;
				}

                {
					// この時点でフィルタリングされないメッセージは待ち状態のクリアなので
					// conductor に 'click' を送り解除を伝える。
					if(!conductor.trigger('click')) // 待ち状態でない場合は単に無視される
					{
						// ハンドラが見つからないなど、処理されなかった場合
						if(st == runst && clickSkipEnabled && skipMode == SKIP_NONE)
						{
							// クリックによるスキップが可能
							skipToClick();
						}
                    }
				}

				// 順序をいれかえた。自動モード解除はクリックによるキャンセル処理の後にする
				if (st != stopst && autoMode && cancelAutoWhenClick) {
					cancelAutoMode();
                }
                
            }
		}
	}

	function onPrimaryClickByKey()
	{
		// キーが押されたときプライマリレイヤをクリックしたのと
		// 同じ動作をするが、さらに一時的にマウスカーソルを隠す
		onPrimaryClick();
		hideMouseCursor();
	}

	function waitClick(elm)
	{
		// beginskip/endskip中は強制で飛ばす
		if (skipMode >= SKIP_CANCEL) return  0;

		// クリックを待つ
		conductor.wait(%[
			click : function
			{
				inSleep = false;
				notifyRun();
			} incontextof this]);
		inSleep = true;
		notifyStable();
		return -2;
	}

	function onMouseDown(x, y)
	{
		lastMouseDownX = x;
		lastMouseDownY = y;
		super.onMouseDown(...);
	}

	function onMouseUp(x, y, btn)
	{
		conductor.trigger("mouseup" + btn);
		super.onMouseUp(...);
		// [XXX] ダイアログがモード状態で表示されている時，レイヤ範囲外のクリックを検知できない
		if (currentDialog !== void && btn == mbRight) with (sysbase) {
			var x = .cursorX, y = .cursorY, left = .left, top = .top;
			if (x < left || y < top || x >= left+.width || y >= top+.height) {
				onPrimaryRightClick(); // 右クリックの動作DialogLayerのフックが取得する
			}
		}
	}

	// ネイティブフルスクリーンでプライマリレイヤ外をクリックされた時の処理（要WinodwEX）
	var _windowsClickEventMessageMap = %[];
	function setWindowsClickEventHook() {
		if (typeof this.setMessageHook != "Object" ||
			typeof global.Window.getNotificationNum != "Object") return;
		var setMH = setMessageHook;
		var getMN = global.Window.getNotificationNum;
		var map   = _windowsClickEventMessageMap;
		var ev    = [ "LBUTTONDOWN", "RBUTTONDOWN", "LBUTTONDBLCLK", "RBUTTONDBLCLK" ];
		for (var i = 0; i < ev.count; i++) {
			var name = ev[i];
			setMH(true, name);
			map[name] = getMN(name);
		}
	}
	function onWindowsMessageHook(msg, wp, lp) {
		with (_windowsClickEventMessageMap) switch (msg) {
		case .LBUTTONDOWN: case .LBUTTONDBLCLK:
			//dm("outer click");
			onPrimaryClick();
			break;
		case .RBUTTONDOWN: case .RBUTTONDBLCLK:
			//dm("outer Rclick");
			onPrimaryRightClick();
			break;
		}
		return callHook("onWindowsMessageHook", msg, wp, lp);
	}

	//------------------------------------------------------- キーボード操作 --

	function processKeys(key, shift)
	{
		if (key == VK_BACK && (shift == ssShift) && autoLabelStore !== void
			&& typeof this.backToAutoLabelStoreMenuItem == "Object")
		{
			backToAutoLabelStoreMenuItem.click();
		}

        // CONTROL によるスキップ処理
        if(key == VK_CONTROL) {
			var sg = getKeyState;
			if(sg(VK_CONTROL)){
				if (conductor.status != conductor.mStop && !conductor.interrupted &&
					clickSkipEnabled && skipMode < SKIP_FAST &&
					(prevSkipMode === void || enableLeftBeginSkip) // beginskip で左クリック無効時はCTRLスキップも抑制
					) {
					skipToStop2(); // まだskipMode 4に入っていない場合は早送りモードに入る
				}
			}
        }

        if(checkProceedingKey(key, shift)) return;

		if(key == #'F')
		{
			// 次の選択肢/未読まで進む
			skipToNextStopByKey();
			return;
		}

		if(key == #'B')
		{
			// 前に戻る
			goBackByKey();
			return;
		}

		if(key == #'A')
		{
			// 自動的に読み進める
			switchAutoModeByKey();
			return;
		}
 
		if(freeSaveDataMode)
		{
			if(key == #'S')
			{
				// 栞をはさむ
				return menuItemClickByKey("storeMenu");
			}

			if(key == #'L')
			{
				// 栞をたどる
				return menuItemClickByKey("restoreMenu");
			}
		}

		if(key == #'R' || (shift == ssShift && key == VK_UP))
		{
			// メッセージ履歴を表示
			showHistoryByKey();
			return;
		}

		if(key == VK_ESCAPE)
		{
			// メッセージを消す
			return menuItemClickByKey("rightClickMenuItem");
		}
	}

	function preProcessKeys(key, shift)
	{
		return callHook(keyDownHook, key, shift);
	}

	function internalOnKeyDown(key, shift)
	{
		if(!preProcessKeys(key, shift)) processKeys(key, shift);
	}

	function checkProceedingKey(key, shift)
	{
        // key が読みすすみのキー ( スペースキーかReturnキー ) の場合は
		// キーを処理し、true を返す。そうでなければ false を返す
		if(key == VK_RETURN || key == VK_SPACE)
		{
			// キーがメッセージキューに溜まってる場合があるので
			// 実際にそのキーが押されているのかどうかを
			// getKeyState を用いて調べる
			var sg = getKeyState;
			if(sg(VK_RETURN) || sg(VK_SPACE))
			{
				// キーが押されてた
				if((shift & ssRepeat) && clickSkipEnabled)
				{
					// キーリピート
					if(skipMode < SKIP_FAST && skipKeyRepressed && skipKeyRepeatEnabled)
						skipToStop2(); // まだskipMode 4に入っていない場合は早送りモードに入る
					// skipKeyRepressed をチェックするのは
					// 連続してキーリピートが発生しているときに
					// cancelSkip 後にスキップに突入するのを防ぐため
				}
				else
				{
					skipKeyRepressed = true;
					onPrimaryClickByKey();
				}
				return true;
			}
		}

		return false;
	}

	function skipCancelKeyPressing()
	{
		// スキップを解除する要因となるキーあるいはマウスボタンが押されているか
		var sg = getKeyState;
		return sg(VK_RETURN) || sg(VK_SPACE) || sg(VK_LBUTTON);
	}

	function skipKeyPressing()
	{
		// VK_RETURN あるいは VK_SPACE が押されているかどうか
		var sg = getKeyState;
		return (skipKeyRepeatEnabled && (sg(VK_RETURN) || sg(VK_SPACE))) || sg(VK_CONTROL);
	}

	function releaseCapture() {
		// releaseCaptureのレイヤは何でも良い
		primaryLayer.releaseCapture();
	}

	// メニューアイテムをクリックする動作を
	function menuItemClickByKey(menuItem) {
		releaseCapture(); // マウスのキャプチャを外す
		// 文字列が渡された場合は自分自身の変数を参照してメニューアイテムとみなす
		if (typeof  menuItem == "String" && typeof this[menuItem] != "undefined") menuItem = this[menuItem];
		// オブジェクトがメニューアイテムであり有効であればクリック動作をエミュレート
		if (typeof  menuItem == "Object" && menuItem instanceof "MenuItem" &&
			isvalid menuItem && menuItem.enabled) menuItem.click();
	}

	function goBackByKey()
	{
		menuItemClickByKey("goBackMenuItem");
	}

	function skipToNextStopByKey()
	{
		menuItemClickByKey("skipToNextStopMenuItem");
	}

	function showHistoryByKey()
	{
		menuItemClickByKey("showHistoryMenuItem");
	}

	function switchAutoModeByKey()
	{
		menuItemClickByKey("autoModeMenuItem");
	}

	function onKeyDown(key, shift)
	{
		if(pseudoMenuShortcut(key, shift)) return;
		if(focusedLayer === null)
			internalOnKeyDown(key, shift);
		super.onKeyDown(...);
	}
	function onKeyUp(key, shift)
	{
		callHook("keyUp", key, shift);
		super.onKeyUp(...);
	}

	function onMouseWheel(shift, delta, x, y)
	{
		// ホイールが回転した
		super.onMouseWheel(...);

		// フック処理
		if (callHook("onMouseWheelHook", shift, delta, x, y)) return;

		// ダイアログ表示中なら常にそちらへ流す
		if (currentDialog !== void) {
			// ホイール処理があれば実行
			currentDialog.processWheel(...) if (currentDialog.hasWheel());
			return;
		}
		// パネルが受け取れる場合はそちらへ流す
		if (panelShowing && panelLayer.hasWheel()) {
			panelLayer.processWheel(...);
			return;
		}
		// メッセージ窓のホイール処理
		if (inSleep && current.hasWheel()) {
			current.processWheel(...);
			return;
		}
		// メッセージ履歴表示中ならそちらへイベントを垂れ流す
		if (historyShowing) {
			historyLayer.windowMouseWheel(...);
			return;
		}
		return internalOnMouseWheel(...);
	}

	function internalOnMouseWheel(shift, delta, x, y)
	{
		if (delta > 0) {
			// ホイール上回し
			showHistoryByKey(); // メッセージ履歴を表示
		} else if (delta < 0) {
			// ホイール下回し
			if (System.getTickCount() - lastHistoryHiddenTick > 150) onPrimaryClick(); // クリックをエミュレート
			// ↑ tick を比較しているのは、メッセージ履歴を隠す操作とホイールを
			// 手前に回す操作が連続した場合に勝手に読み進むのをある程度防ぐ仕掛け
		}
	}

	//------------------------------------------------- クリック待ち記号処理 --

	function _showClickGlyphs(layer, type)
	{
		if (forEachFunctionHook("onShowClickGlyph", layer, type) === void) {
			switch (type) {
			case "line":
				layer.showLineBreakGlyph(lineBreak);
				break;
			case "page":
				layer.showPageBreakGlyph(pageBreak);
				break;
			}
		}
	}

	function _hideClickGlyphs() {
		var layer;
		if(lastClickGlyphVisible){
			layer = getMessageLayerObjectFromPageAndNumber(lastClickGlyphMessagePage, lastClickGlyphMessageNum);
		}
		if (forEachFunctionHook("onHideClickGlyph", layer) === void) {
			lineBreak.visible = false;
			pageBreak.visible = false;
		}
	}

	function showClickGlyphs(type) {
		_showClickGlyphs(current, type);
		storeClickGlyphState(type);
	}
	
	function hideClickGlyphs()
	{
		// クリック待ち記号を非表示に
		_hideClickGlyphs();
		if(conductor == mainConductor)
		{
			// クリック待ち記号の状態を記録
			lastClickGlyphVisible = false;
		}
	}

	function storeClickGlyphState(which)
	{
		// クリック待ち記号の情報を一時的に待避
		// このデータは右クリックサブルーチンやextraConductorサブルーチンから戻るときに参照する
		if(conductor == mainConductor)
		{
			lastClickGlyphVisible = true;
			lastClickGlyphMessagePage = currentPage;
			lastClickGlyphMessageNum = currentNum;
			lastClickGlyphWhich = which;
		}
	}

	function restoreClickGlyphState()
	{
		// lastClickGlyph *** に一時的に待避したクリック待ち記号の情報
		// に基づいてクリック待ち記号を設定する
		if(lastClickGlyphVisible)
		{
			var layer = getMessageLayerObjectFromPageAndNumber
				(lastClickGlyphMessagePage, lastClickGlyphMessageNum);
			if(layer !== void)
			{
				_showClickGlyphs(layer, lastClickGlyphWhich);
			}
		}
	}

	function canIgnoreL()
	{
		// L タグを無視できるかどうか
		return chNonStopToPageBreak || (getCurrentRead() && ch2ndNonStopToPageBreak);
	}

	function waitAuto(waittime, tag, voice=false) {
		waittime *= actskipspeed;
		if (waittime <= 0) {
			waittime = 1;
        }
		var trigger = voice ? checkVoiceWait() : void;
		var waitfunc = trigger === void ? conductor.waitWithTimeOut : conductor.wait;
		trigger = "timeout" if (trigger === void);
		waitfunc(%[
			cancelauto : function (tag)
			{
				clickWaiting = false;
				hideClickGlyphs();
				if (tag !== void) {
					insertTag(tag);
				}
			} incontextof this,
			cancelauto_arg : tag,
            click : function (tag)
            {
				clickWaiting = false;
				hideClickGlyphs();
				if (cancelAutoWhenClick) {
					cancelAutoMode();
					if (tag !== void) {
						insertTag(tag);
					}
				}
            } incontextof this,
            click_arg : tag,
            trigger => function
            {
				clickWaiting = false;
                hideClickGlyphs();
            } incontextof this
                ], waittime);
		clickWaiting = true;
		return -2; // break
	}

	function waitAutoClear(waittime, voice=false) {
		waittime *= actskipspeed;
		if (waittime <= 0) {
			waittime = 1;
        }
		var trigger = voice ? checkVoiceWait() : void;
		var waitfunc = trigger === void ? conductor.waitWithTimeOut : conductor.wait;
		trigger = "timeout" if (trigger === void);
		waitfunc(%[
			cancelauto : function ()
			{
				clickWaiting = false;
				current.clear2();
			} incontextof this,
            click : function ()
            {
				clickWaiting = false;
				current.clear2();
				if (cancelAutoWhenClick) {
					cancelAutoMode();
				}
            } incontextof this,
            trigger => function
            {
				clickWaiting = false;
				current.clear2(); // clear2 を呼ぶ
            } incontextof this
        ], waittime);
		clickWaiting = true;
		return -3; // break
	}
	
	function showLineBreak(elm)
	{
		// 行処理後にdelay解除発動
		addFastTag("extractdelay", %[label:"kl" + (++keywaitLabelCount)]);

		// 現在のメッセージレイヤに行待ち記号を表示する
		stablePosibility = true;
		if(canIgnoreL())
		{
			// l タグの無視
			if(elm.canskip === void || !+elm.canskip)
				return (skipMode==SKIP_STOP || skipMode==SKIP_FAST) ? 0 : -4;
		}

		if(skipMode == SKIP_CLICK) cancelSkip();
		if(skipMode == SKIP_FAST && !skipKeyPressing()) cancelSkip();
		if(skipMode >= SKIP_FAST) return -4;
		if(skipMode) return skipCancelKeyPressing()?-4:0;
			// スキップ中(スキップをキャンセルするようなキーがあればスキップ解除
			// のためのイベント処理の機会を与える)

		if(nowaitMode)
			return ((nowaitModeClickSkip || clickSkipEnabled)
					? waitAuto(nowaitModeWait)
					: waitTime(nowaitModeWait, false));

		showClickGlyphs("line");

		if(autoMode) {
			return waitAuto(autoModeLineWait, "l", hasCurrentVoice && elm.voicewait);
		}

		if(!current.nodeVisible && textWriteEnabled)
		{
			if (!callHook("onInvisibleMessageBreak", "line"))
			dm("警告 : 非表示になっている" +
				(currentPage ? "裏" : "表") + "メッセージレイヤ" + currentNum +
				"で行クリック待ちになりました");
		}

		// conductor を 'click' まで待ち状態に
		conductor.wait(%[
			click : function
			{
				clickWaiting = false;
				notifyRun();
                hideClickGlyphs();
            } incontextof this
			]);
		clickWaiting = true;
		notifyStable();
		return -2;
	}

	function showPageBreak(elm)
	{
		// 現在のメッセージレイヤにページ待ち記号を表示する
		stablePosibility = true;
		if(skipMode == SKIP_CLICK || skipMode == SKIP_PAGE) cancelSkip();
		if(skipMode == SKIP_FAST && !skipKeyPressing()) cancelSkip();
		if(skipMode) return -4; // いったんイベントを処理

		if(nowaitMode)
			return ((nowaitModeClickSkip || clickSkipEnabled)
					? waitAuto(nowaitModeWait)
					: waitTime(nowaitModeWait, false));

		showClickGlyphs("page");
		
		if(autoMode) {
			return waitAuto(autoModePageWait, "p2", hasCurrentVoice);
		}

		if(!current.nodeVisible && textWriteEnabled)
		{
			if (!callHook("onInvisibleMessageBreak", "page"))
			dm("警告 : 非表示になっている" +
				(currentPage ? "裏" : "表") + "メッセージレイヤ" + currentNum +
				"でページクリック待ちになりました");
		}

		// conductor を 'click' まで待ち状態に
		conductor.wait(%[
			click : function
			{
				clickWaiting = false;
				notifyRun();
                hideClickGlyphs();
			} incontextof this
			]);
		clickWaiting = true;
		notifyStable();
		return -2;
	}

	function showPageBreakAndClear()
	{
		// メッセージレイヤが最終行まで達して自動改ページがされるときに
		// 呼ばれる。現在のメッセージレイヤにページ待ち記号を表示し、
		// 実行再開時には MessageLayer.clear2 を呼ぶ
		stablePosibility = true;
		if(skipMode == SKIP_CLICK || skipMode == SKIP_PAGE) cancelSkip();
		if(skipMode == SKIP_FAST && !skipKeyPressing()) cancelSkip();
		var lasttagname = conductor.lastTagName;
		if(!autoMode && ((!canIgnoreL() && lasttagname == 'l') || lasttagname == 'p'))
			{ current.clear2(); return -5; }// いったんイベントを処理(タグは後回し)
		if(skipMode) { current.clear2(); return -5; }// いったんイベントを処理(タグは後回し)

		if(!current.nodeVisible && textWriteEnabled)
		{
			if (!callHook("onInvisibleMessageBreak", "autopage"))
			dm("警告 : 非表示になっている" +
				(currentPage ? "裏" : "表") + "メッセージレイヤ" + currentNum +
				"で自動改ページクリック待ちになりました");
		}

		if(nowaitMode) {
			current.clear2(); // clear2 を呼ぶ
			return 0;
		}
		if(autoMode)
		{
			return waitAutoClear(autoModePageWait, hasCurrentVoice);
		}
		else
		{
			showClickGlyphs("page");

			// conductor を 'click' まで待ち状態に
			conductor.wait(%[
				click : function
				{
					clickWaiting = false;
					current.clear2(); // clear2 を呼ぶ
					notifyRun();
				} incontextof this
				]);
			clickWaiting = true;
			notifyStable();
			return -3;
		}
	}

	//------------------------------------------------------------- BGM 処理 --

	function onBGMFadeCompleted()
	{
		// BGM のフェードが完了した
		conductor.trigger('bgmfade');
	}

    // BGM停止処理
    var bgmStopExp;
    var bgmStopStorage;
    var bgmStopTarget;

    function handleBgmStop() {
        // BGM停止ハンドラ

        // パラメータを保持して破棄
        var exp     = bgmStopExp;
        var storage = bgmStopStorage;
        var target  = bgmStopTarget;
        clearBgmStop();
        
        // 処理実行
        if (exp !== void) {
            Scripts.eval(exp);
        }
        if (storage !== void || target !== void) {
            process(storage, target);
        }
    }
    
    function setBgmStop(elm) {
        // BGM停止時処理の登録
        bgmStopExp     = elm.exp;
        bgmStopStorage = elm.storage;
        bgmStopTarget  = elm.target;
    }
    
    function clearBgmStop() {
        // BGM停止時処理の解除
        bgmStopExp     = void;
        bgmStopStorage = void;
        bgmStopTarget  = void;
    }
    
    // BGM 停止時拡張ハンドラ
    var bgmStopHandler;

	function onBGMStart(labels)
	{
		entryDelay(labels);
	}
	
	function onBGMStop()
	{
        // BGM が停止した
        conductor.trigger('bgmstop');
        if (bgmStopHandler !== void) {
            bgmStopHandler();
        }
		forEachEventHook('onBGMStop');
        if (bgmStopExp !== void || bgmStopStorage !== void || bgmStopTarget !== void) {
            global.stopBgmTrigger = new AsyncTrigger(handleBgmStop, '');
            global.stopBgmTrigger.cached = true;
            global.stopBgmTrigger.trigger();
        }
    }

    // ------------------------------------------
    
    var bgmLabelInfos = %[];
    var bgmLabelInfo;

    function handleBgmLabel() {
        if (bgmLabelInfo !== void) {
            if (bgmLabelInfo.exp !== void) {
                Scripts.eval(bgmLabelInfo.exp);
            }
            if (bgmLabelInfo.storage !== void || bgmLabelInfo.target !== void) {
                process(bgmLabelInfo.storage, bgmLabelInfo.target);
            }
        }
    }

    // BGM 停止時拡張ハンドラ
    var bgmLabelHandler;

    function onBGMLabel(label) {
		// ディレイ実行展開
		extractDelay(label);
        // BGM のラベル通過処理
        conductor.trigger('bgmlabel_' + label);
        if (bgmLabelHandler !== void) {
            bgmLabelHandler(label);
        }
        var obj = bgmLabelInfos[label];
        if (obj !== void) {
            bgmLabelInfo = obj;
            global.labelBgmTrigger = new AsyncTrigger(handleBgmLabel, '');
            global.labelBgmTrigger.cached = true;
            global.labelBgmTrigger.trigger();
        }
    }

    /**
     * BGM ラベル処理の登録
     */
    function setBgmLabel(elm) {
        if (elm.name !== void) {
            var obj = %[];
            obj.storage = elm.storage;
            obj.target  = elm.target;
            obj.exp     = elm.exp;
            bgmLabelInfos[elm.name] = obj;
        }
    }

    /**
     * BGM ラベル処理の解除
     */
    function clearBgmLabel() {
        bgmLabelInfos = %[];
        bgmLabelInfo = void;
    }

    // ----------------------------------------------------------------------
    
	function waitBGMFade(elm)
	{
		// BGM のフェード終了を待つ
		if(!bgm.inFading) return 0; // フェード中でなければ待たない
		if(elm.canskip !== void && +elm.canskip && clickSkipEnabled)
		{
			// スキップできる場合
			if(skipMode)
			{
				// スキップ中の場合
				bgm.stopFade();
				return 0; // スキップ中の場合はフェードを停止させて返る
			}
			conductor.wait(%[
				click : function
				{
					bgm.stopFade(); // フェーディングは停止する
				} incontextof this,

				bgmfade : function(arg)
				{
					return arg;
				} incontextof this,
				bgmfade_arg : +elm.wait
				]);
		}
		else
		{
			// スキップできない場合
			conductor.wait(%[
				bgmfade : function(arg)
				{
					return arg;
				} incontextof this,
				bgmfade_arg : +elm.wait
				]);
		}
		return -2;
	}

	function waitBGMStop(elm)
	{
		// BGM の再生終了を待つ
		if(!bgm.canWaitStop) return 0; // BGM 再生終了を待てなければそのまま戻る
		if(elm.canskip !== void && +elm.canskip && clickSkipEnabled)
		{
			// スキップできる場合
			if(skipMode)
			{
				// スキップ中の場合
				bgm.stop();
				return 0; // スキップ中の場合は再生を停止させて返る
			}
			conductor.wait(%[
				click : function
				{
					bgm.stop(); // 再生を終了する
				} incontextof this,

				bgmstop : function(arg)
				{
					return arg;
				} incontextof this,
				bgmstop_arg : +elm.wait
				]);
		}
		else
		{
			// スキップできない場合
			conductor.wait(%[
				bgmstop : function(arg)
				{
					return arg;
				} incontextof this,
				bgmstop_arg : +elm.wait
				]);
		}
		return -2;
	}

	function waitBGMLabel(elm)
	{
		// BGM のラベル通過を待つ
		if(!bgm.canWaitLabel(elm.name)) return 0; // ラベルを待てなければそのまま戻る
		var name = "bgmlabel_" + elm.name;
		if(elm.canskip !== void && +elm.canskip && clickSkipEnabled)
		{
			// スキップできる場合
			if(skipMode) {
				return 0; // スキップ中の場合は再生を停止させて返る
			}
			conductor.wait(%[
				click : function {
				} incontextof this,
				name => function(arg) {
					return arg;
				} incontextof this,
				name + "_arg" => +elm.wait
				]);
		}
		else
		{
			// スキップできない場合
			conductor.wait(%[
				name => function(arg) {
					return arg;
				} incontextof this,
				name + "_arg" => +elm.wait
				]);
		}
		return -2;
	}
	
	//----------------------------------------------------------- 効果音処理 --

	function onSEStart(labels) {
		entryDelay(labels);
	}
	
    function onSELabel(label, id) {
		// ディレイ実行展開
		extractDelay(label);
		callHook("onSELabel", label, id);
	}
	
	function onSESoundBufferFadeCompleted(id)
	{
		// 効果音のフェードが終了した
		conductor.trigger('sefade' + id);
	}

	function onSESoundBufferStop(id) {
		// 効果音の再生が終了した
		conductor.trigger('sestop' + id);
		forEachEventHook('onSeStop',,id);
	}

	function waitSEFade(elm)
	{
		var id = +elm.buf;
		var buf = se[id];
		if(!buf.inFading) return 0; // フェード中でなければそのまま戻る
		var name = 'sefade'+id;
		if(elm.canskip !== void && +elm.canskip && clickSkipEnabled)
		{
			// スキップできる場合
			if(skipMode)
			{
				// スキップ中の場合
				buf.stopFade();
				return 0; // スキップ中の場合はフェードを停止させて返る
			}
			conductor.wait(%[
				click : function (id)
				{
					se[id].stopFade(); // フェードを終了する
				} incontextof this,

				click_arg : id, // ハンドラへの引数

				name => function (arg)
				{
					return arg;
				} incontextof this,
				name + "_arg" => +elm.wait
				]);
		}
		else
		{
			// スキップできない場合
			conductor.wait(%[
				name => function (arg)
				{
					return arg;
				} incontextof this,
				name + "_arg" => +elm.wait
				]);
		}
		return -2;
	}

	function waitSEStop(elm)
	{
		var id = +elm.buf;
		var buf = se[id];
		if(!buf.canWaitStop()) return 0; // 終了を待てなければそのまま返る
		var name = 'sestop'+id;
		if(elm.canskip !== void && +elm.canskip && clickSkipEnabled)
		{
			// スキップできる場合
			if(skipMode)
			{
				// スキップ中の場合
				buf.stop();
				return 0; // スキップ中の場合は再生を停止させて返る
			}
			conductor.wait(%[
				click : function (id)
				{
					se[id].stop(); // 再生を終了する
				} incontextof this,

				'click_arg' => id, // ハンドラへの引数

				name => function (arg)
				{
					return arg;
				} incontextof this,
				name + "_arg" => +elm.wait
				]);
		}
		else
		{
			// スキップできない場合
			conductor.wait(%[
				name => function (arg)
				{
					return arg;
				} incontextof this,
				name + "_arg" => +elm.wait
				]);
		}
		return -2;
	}

	// 効果音タグ共通処理
	function commonSEmethod(elm, method, *) {
		var bufs = [];
		if (elm.buf == "all") {
			for (var i = 0; i <= numSEBuffers; i++) bufs.add(i);
			if (elm.except != "") {
				var rem = elm.except.split(",");
				for (var i = 0; i < rem.count; i++) bufs.remove(+rem[i]);
			}
		} else {
			if (elm.buf != "" && elm.buf.indexOf(",") > 0) bufs = elm.buf.split(",");
			else bufs.add(+elm.buf);
		}
		for (var i = 0; i < bufs.count; i++) {
			var num = +bufs[i];
			if (num >= 0 && num <= numSEBuffers) {
				//dm("commonSEmethod", method, num);
				se[num][method](*);
			}
		}
		return 0;
	}

	//----------------------------------------------------------- ボイス処理 --

	function onVoiceStart(id, type, loop) {
		if (type == 0) {
			// 通常ボイスの場合はBGM音量下げ処理実行
			if (bgmdown) {
				beginAction(this, %["bgmdownvalue", %[handler:MoveAction, value:bgmdownlevel/100.0, time:300]], void, true);
			}
		}
	}
	
	function onVoiceLabelStart(labels) {
		extractDelay("vlstart");
		// wait 要のラベルを登録
		var names = [];
		names.assign(labels);
		// ↑WaveSoundBuffer.labels は特殊な EnumMembers が実装されており，
		//   必ずsamplePositionでソートされた状態assignされる（Dictionaryのように順序不定にならない）
		var entryDelay = kag.entryDelay;
		var n = 0;
		for (var i=0;i<names.count;i+=2) {
			entryDelay(names[i]);
			entryDelay("vl" + ++n);
		}
		entryDelay("vlstop");
	}
	
	function onVoiceLabel(label, count) {
		extractDelay(label);
		extractDelay("vl" + count);
	}
	
	function onVoiceLabelStop() {
		extractDelay("vlstop");
	}

	// ボイス停止通知
	function onVoiceStop(id, type, loop) {
		// 終了トリガ
		conductor.trigger("voicestop_" + id);
		// ループボイスを再開させる
		if (!loop) {
			loopVoiceTrigger.trigger();
		}

		// 全体判定
		var isPlayingNormal = false;
		var isPlayingAny = false;
		for(var i = 0; i<numVoiceBuffers; i++) {
			var voice = voices[i];
			if (voice.canWait()) {
				if (voice.type == 0) {
					isPlayingNormal = true;
				}
				isPlayingAny = true;
			}
		}
		// 全ボイス停止時処理
		if (!isPlayingAny) {
			conductor.trigger("voicestop");
			forEachFunctionHook('onVoiceStop');
		} else {
			forEachFunctionHook('onVoiceStop',voices[id].name);
		}
		// 通常ボイス停止時処理
		if (!isPlayingNormal) {
			conductor.trigger("normalvoicestop");
			if (bgmdownvalue < 1.0) {
				// BGM下げを戻す
				beginAction(this, %["bgmdownvalue", %[handler:MoveAction, value:1.0, time:300]], void, true);
			}
		}
	}

	function onVoiceFlip(id, level) {
		//dm(@"onVoiceFlip${voices[id].name}:${level}");
		forEachFunctionHook('onVoiceFlip',voices[id].name,level);
	}
	
	/**
	 * @param name キャラ名
	 * @param type ボイス種別 0:通常 1:bvoice 2:lvoice 
	 * @return ボイスが有効かどうか
	 */
	function isChVoiceOn(name, type=0) {
		var info = characterVoiceInfo[name];
		var config = info !== void ? info.config : "";
		if (getVoiceOn(config)) {
			switch (type) {
			case 0:
				return voiceenable;
			case 1:
				return bgvenable;
			case 2:
				return bgv2enable;
			}
		}
		return false;
	}

	/**
	 * @param name キャラ名
	 * @param type ボイス種別 0:通常 1:bvoice 2:lvoice 
	 * @return ボイスの実音量を取得
	 */
	function getChVoiceVolume(name, type) {
		var info = characterVoiceInfo[name];
		var config = info !== void ? info.config : "";
		var per    = info !== void ? info.per    : 100;
		var vol;
		switch (type) {
		case 0:
			vol = kag.voiceenable ? getVoiceVolume(config) : 0;
			break;
		case 1:
			vol = kag.bgvenable ? getBGVVolume(config) : 0;
			break;
		case 2:
			vol = kag.bgv2enable ? getBGV2Volume(config) : 0;
			break;
		}
		return vol * per / 100 * 1000;
	}

	// 通常ボイスが待てる場合はそのトリガ名を返す
	function checkVoiceWait() {
		for(var i = 0; i<numVoiceBuffers; i++) {
			var voice = voices[i];
			if (voice.canWait() && voice.type == 0) {
				return "normalvoicestop";
			}
		}
	}
	
	// -----------------------------------------------------------------------
	// ボイス用ファイル処理
	// -----------------------------------------------------------------------
	
	// 標準名かどうか
	function isDefaultName() {
		return !(flags.name !== void && (flags.name != defaultName || flags.family != defaultFamily));
	}
	
	function checkExistVoiceFilename(storage) {
		if (!isDefaultName()) {
			// 名前指定がある場合でデフォルト名でない場合は Nつきのファイル名で参照する
			var name = getExistVoice(storage + "N");
			if (name !== void) {
				return name;
			}
		}
		return getExistVoice(storage);
	}

	function getPlayVoiceFilename(name, voice) {
		var info = characterVoiceInfo[name];
		var config = info !== void ? info.config : "";
		sflags["voice_" + _voiceNameFilter(config)] = true;
		if (voice !== void) {
			var vfile = forEachFunctionHook('onCheckExistVoiceFilename', voice);
			if (vfile === void) {
				vfile = checkExistVoiceFilename(voice);
			}
			if (vfile !== void) {
				return vfile;
			} else {
				errorVoice(@"${name}:ボイスがロードできません:${voice}");
			}
		}
	}

	
	/**
	 * voice 定義を再生用のファイル名リストに変える
	 * @param voices ボイス定義一覧
	 * @return ボイスファイル名情報の配列
	 */
	function extractVoice(voices) {
		var store = [];
		if (voices !== void) {
			var count = voices.count;
			for (var i=0;i<count;i++) {
				var name  = voices[i].name;
				var voice = voices[i].voice;
				var type  = voices[i].type;
				var vfile = getPlayVoiceFilename(name, voice);
				if (vfile !== void) {
					store.add(%[name:name, voice:vfile, type:type]);
					//dm(@"${name}:再生ボイス:${vfile}");
				}
			}
		}
		return store;
	}

	/**
	 * 指定キャラのボイスを停止する
	 * @param name キャラ名
	 * @param fadetime フェード時間指定
	 * @param force バックボイスも停止
	 */
	function stopVoice(name, fadetime, force) {
		for(var i = 0; i<numVoiceBuffers; i++) {
			var voice = voices[i];
			if (voice.name == name && (force || voice.type == 0)) {
				voice.stop(fadetime);
			}
		}
		if (force) {
			stopLoopVoice(name);
		}
	}
	
    /**
	 * 全キャラのボイスを停止する
	 * @param fadetime フェード時間指定
	 * @param force バックボイス含めて停止
	 */
	function stopAllVoice(fadetime, force=false) {
		//dm(@"全ボイス停止:${force}");
		for(var i = 0; i<numVoiceBuffers; i++) {
			var voice = voices[i];
			//dm(@"停止チェック ${voice.name}:${voice.type}");
			if (force || voice.type == 0) {
				voice.stop(fadetime);
			}
		}
		if (force) {
			clearLoopVoice();
		}
    }

    /**
	 * 全ボイスの音量を調整
     */
	function resetAllVoiceVolume() {
		for(var i = 0; i<numVoiceBuffers; i++) {
			voices[i].resetVolume();
		}
	}

	/**
	 * 全ボイスの音量を調整
	 */
	function resetAllVoiceSpeed() {
		for(var i = 0; i<numVoiceBuffers; i++) {
			voices[i].speed = voicespeed;
		}
	}
	
	/**
	 * 全ボイスの delayrun モードを解除
	 */
	function clearVoiceMode(all) {
		for(var i = 0; i<numVoiceBuffers; i++) {
			var voice = voices[i];
			voice.clearMode(all);
		}
	}
	
	/**
	 * @param name キャラ名
	 * @param all モードに関係なく取得
	 * @return 該当キャラの再生中のサウンドトラックを返す
	 */
	function getPlayingVoiceTrack(name, all=false) {
		for (var i = 0; i<numVoiceBuffers; i++) {
			var voice = voices[i];
			if (voice.name == name && voice.status == "play" && (all || voice.mode)) {
				return voice;
			}
		}
	}

	/**
	 * @param name
	 * @return 該当キャラの再生用のサウンドトラックを返す
	 */
	function getVoiceTrack(name) {
		// 同名トラックを探す
		for (var i = 0; i<numVoiceBuffers; i++) {
			var voice = voices[i];
			if (voice.name == name) {
				return voice;
			}
		}
		// 使ってないものを探す
		for (var i = 0; i<numVoiceBuffers; i++) {
			var voice = voices[i];
			if (voice.name === void && voice.status == "stop") {
				return voice;
			}
		}
		// 一番古いものを探す
		var target = 0;
		var time   = voices[target].startTime;
		for (var i = 1; i<numVoiceBuffers; i++) {
			var voice = voices[i];
			if (voice.startTime < time) {
				target = i;
			}
		}
		return voices[target];
	}
	
    /*
	 * 指定したキャラで指定したボイスファイルを再生
	 * @param name キャラ名
	 * @param voicename ボイス名
	 * @param type ならすボイスの種別 0:通常 1:bgv 2:bgv2
	 * @param mode trueならラベル反応させる
	 */
	function playVoice(name, voicename, type=0, loop=false, mode=false) {
		//dm(@"${name}:playvoice:${voicename},${type},${mode}");
		var voice = getVoiceTrack(name);
		voice.start(name, voicename, type, loop, mode);
	}

	/**
	 * 履歴データのボイスから直接全ボイス再生
	 * @param voices ボイス定義情報
	 * @param mode trueならラベル反応させる
	 */
	function playAllVoice(voices, mode=false) {
		var list = extractVoice(voices);
		var count = list.count;
		for (var i=0;i<count;i++) {
			// 似たような処理が全体にちらばってるので直すこと XXX
			var info = list[i];
			if (info !== void && isChVoiceOn(info.name)) {
				playVoice(info.name, info.voice, info.type, false, mode);
			}
		}
	}

	/**
	 * テキストに付随したボイス再生処理の実行
	 * @param voices  ボイス定義情報
	 * @param play 実際に再生するかどうか
	 * @return currentVoice配列またはvoid
	 */
	function playLineVoice(voices, play = true) {

		// 現在のボイスとして登録
		currentVoice = extractVoice(voices);

		var  count = currentVoice.count;
		if (!count) return;

		if (play) {
			var playFlag = false;
			// ボイス再生判定
			for (var i=0;i<count;i++) {
				var info = currentVoice[i];
				if (info !== void && isChVoiceOn(info.name)) {
					playFlag = true;
					break;
				}
			}

			// ボイスの再生
			if (playFlag) {
				stopAllVoice(); // ボイス消去実行
				for (var i=0;i<count;i++) {
					var info = currentVoice[i];
					if (info !== void && isChVoiceOn(info.name)) {
						playVoice(info.name, info.voice, info.type, false, true);
					}
				}
			}
		}
		return currentVoice;
	}
	
    /**
     * ボイス待ち
     */
	function waitVoice(elm) {
		var voiceBuffer = getPlayingVoiceTrack(elm.name);
		if (voiceBuffer !== void && voiceBuffer.canWait()) {
			var canskip = elm.canskip !== void ? elm.canskip : true;
			var trigger = "voicestop_" + elm.name;
			if (canskip && clickSkipEnabled) {
				if (skipMode){
					stopVoice(elm.name);
					return 0;
				}
				conductor.wait(%[
				    click : function(arg)
					{
						stopVoice(arg);
					} incontextof this,
					click_arg : elm.name,
					trigger => function(arg)
					{
						return arg;
					} incontextof this,
					trigger + "_arg" => +elm.wait
						]);
			} else {
				conductor.wait(%[
					trigger => function(arg)
					{
						return arg;
					} incontextof this,
					trigger + "_arg" => +elm.wait
						]);
			}
			return -2;
		}
		return 0;
    }

    /**
     * ボイス待ち
     */
	function waitAllVoice(elm) {

		var canWait = false;
		for(var i = 0; i<numVoiceBuffers; i++) {
			var voice = voices[i];
			if (voice.canWait()) {
				canWait = true;
				break;
			}
		}
		if (canWait) {
			var canskip = elm.canskip !== void ? elm.canskip : true;
			if (canskip && clickSkipEnabled) {
				if (skipMode){
					stopAllVoice();
					return 0;
				}
				conductor.wait(%[
				    click : function(arg)
					{
						stopAllVoice();
					} incontextof this,
					voicestop : function(arg)
					{
						return arg;
					} incontextof this,
					voicestop__arg : +elm.wait
						]);
			} else {
				conductor.wait(%[
					voicestop : function(arg)
					{
						return arg;
					} incontextof this,
					voicestop_arg : +elm.wait
						]);
			}
			return -2;
		}
		return 0;
    }
	
	var talkWaitCount;
	
	/**
	 * 会話タイミング待ち
	 */
	function doTalkWait(elm) {
		talkWaitCount++;
		if (skipMode == 0) {
			return waitDelay("vl" + talkWaitCount, true);
		}
		return 0;
	}

	//--------------------------------------------------------- ムービー関連 --
	/**
	 * 全レイヤムービーの停止
	 */
	function stopAllLayerMovies() {
		stopAllGenericFlipsOneClass("movie");
	}

	/**
	 * レイヤムービーを待ってみる
	 */
	function waitLayerMovie(target, canskip, wait=0) {
		if (!target.moviePlaying) return 0;
		var name = 'lmstop'+target.movieObject;
		if((canskip === void || +canskip) && clickSkipEnabled)
		{
			// スキップできる場合
			if(skipMode)
			{
				// スキップ中の場合
				target.stopMovie();
				return 0; // スキップ中の場合は再生を停止させて返る
			}
			conductor.wait(%[
				click : function (target)
				{
					target.stopMovie(); // 再生を終了する
				} incontextof this,
				'click_arg' => target, // ハンドラへの引数
				name => function (arg)
				{
					return arg;
				} incontextof this,
				name + "_arg" => wait
				]);
		}
		else
		{
			// スキップできない場合
			conductor.wait(%[
				name =>	function (arg)
				{
					return arg;
				} incontextof this,
				name + "_arg" => wait
				]);
		}
		return -2;
	}

	function waitAllLayerMovies(canskip, wait=0)	{
		// アクションを待つ
		if (!getGenericFlipsInstanceCount("movie")) return 0;
		if((canskip === void || +canskip) && clickSkipEnabled)
		{
			// スキップできる場合
			if(skipMode)
			{
				// スキップ動作中
				stopAllLayerMovies(false);
				return 0; // 自動移動を停止させてすぐに返る
			}
			conductor.wait(%[
				click : function
				{
					updateBeforeCh = 1;
					stopAllLayerMovies(false); // すべてのレイヤムービーを停止
				} incontextof this,
				lmstop : function(arg)
				{
					updateBeforeCh = 1;
					return arg;
				} incontextof this,
				lmstop_arg : wait
				]);
		}
		else
		{
			conductor.wait(%[
				lmstop : function(arg)
				{
					updateBeforeCh = 1;
					return arg;
				} incontextof this,
				lmstop_arg : wait
				]);
		}
		return -2;
	}

	// スクリーン切り替え時にムービー再生を一時的に止めるロジック
	var movieRestoreTimer, ignoreMovieTrigger;
	function onMovieScreenChanging(full) {
		if (movieRestoreTimer === void) {
			movieRestoreTimer = new Timer(onMovieScreenChanged, "");
			with (movieRestoreTimer) .state=[], .enabled=false, .capacity=1, .interval=100, .mode=atmNormal;
		}
		with (movieRestoreTimer) {
			if (.enabled) {
				// 再ディレイ
				.enabled = false;
				.enabled = true;
				return;
			}
			ignoreMovieTrigger = true;
			.state.clear();
			for (var i=0;i<numMovies;i++) {
				var movie = movies[i], st = %[];
				movie.internalStore(st);
				movie.stop();
				.state.add(st);
			}
			.enabled = true;
		}
	}
	function onMovieScreenChanged() {
		if (movieRestoreTimer === void) return;
		var consvis = false;
		/**/consvis <-> Debug.console.visible;
		with (movieRestoreTimer) {
			.enabled = ignoreMovieTrigger = false;
			for (var i=0;i<numMovies;i++) {
				movies[i].internalRestore(.state[i]);
			}
			.state.clear();
		}
		if (consvis) with (Debug.console) {
			.visible = true;
			if (typeof this.bringTo == "Object") bringTo("top", true);
			else if (typeof .bringAfter == "Object") .bringAfter(this);
			else bringToFront();
		}
		with (sysbase) onMoviePrimaryPosChanged(.left, .top);
	}
	// プライマリの位置が変わった
	function onMoviePrimaryPosChanged(ox, oy) {
		if (!isMain) return;
		for (var i=0;i<numMovies;i++)
			with (movies[i]) .setOptions(%[ primox:ox, primoy:oy ]);
	}
	function onMovieStatusChanged(state, id)
	{
		// 拡張用スタブ（何もしない）
	}
	function onMoviePlay(id)
	{
		// ムービーの再生を開始した
		onMovieStatusChanged("play", id);
	}
	function onMovieStop(id)
	{
		// ムービーの再生が終了した
		conductor.trigger('moviestop'+id) if (!ignoreMovieTrigger);
		onMovieStatusChanged("stop", id);
	}

	function waitMovieStop(elm)
	{
		var id = +elm.slot;

		// ムービーの再生終了を待つ
		if(!movies[id].canWaitStop) return 0; // ムービー再生終了を待てなければそのまま戻る
		var name = 'moviestop'+id;
		if(elm.canskip !== void && +elm.canskip && clickSkipEnabled)
		{
			// スキップできる場合
			if(skipMode)
			{
				// スキップ中の場合
				movies[id].stop();
				return 0; // スキップ中の場合は再生を停止させて返る
			}
			conductor.wait(%[
				click : function (id)
				{
					movies[id].stop(); // 再生を終了する
				} incontextof this,

				'click_arg' => id, // ハンドラへの引数
				
				name =>	function (arg)
				{
					return arg;
				} incontextof this,
				name + "_arg" => +elm.wait
				]);
		}
		else
		{
			// スキップできない場合
			conductor.wait(%[
				name => function (arg)
				{
					return arg;
				} incontextof this,
				name + "_arg" => +elm.wait
				]);
		}
		return -2;
	}

	function onMoviePeriod(id,type)
	{
		// ムービーのピリオドイベントが発生した
		if( isLeavePeriodEvent != false )
		{
			holdPeriodEventQueue.add( 'movieperiod'+id+'_'+type );
		}
		else
		{
			conductor.trigger('movieperiod'+id+'_'+type);
		}
	}

	function waitMoviePeriod(elm)
	{
		isWaitPeriodEvent = true;
		waitedPeriodEventStorageName = conductor.curStorage;
		var id = +elm.slot;
		stablePosibility = true;

		var name;
		switch (elm.for) {
		case "loop":
			name = 'movieperiod'+id+'_'+perLoop;
			break;
		case "period":
			name = 'movieperiod'+id+'_'+perPeriod;
			break;
		case "prepare":
			name = 'movieperiod'+id+'_'+perPrepare;
			break;
		case "segloop":
			name = 'movieperiod'+id+'_'+perSegLoop;
			break;
		}
		
		if( holdPeriodEventQueue.count > 0 )
		{
			var	triggered = false;
			for (var i = 0; i < holdPeriodEventQueue.count; i++ ) {
				if (name === void || name == holdPeriodEventQueue[i] ) {
					triggered = true;
					break;
				}
			}
			holdPeriodEventQueue.clear();
			if( triggered == true )
			{
				isWaitPeriodEvent = false;
				return 0;
			}
		}

		// ムービーのピリオドイベントを待つ
		if(!movies[id].canWaitStop) return 0; // ムービーが再生中でなければそのまま戻る

		if (name !== void)
		{
			conductor.wait(%[
				name => function (arg) { notifyRun(); isWaitPeriodEvent = false; return arg; } incontextof this,
				name + "_arg" => +elm.wait
					]);
		}
		else
		{
			conductor.wait(%[
				'movieperiod'+id+'_'+perLoop => function (arg) { notifyRun(); isWaitPeriodEvent = false; return arg; } incontextof this,
				'movieperiod'+id+'_'+perLoop + "_arg" => +elm.wait,
				'movieperiod'+id+'_'+perPeriod => function (arg) { notifyRun(); isWaitPeriodEvent = false; return arg; } incontextof this,
				'movieperiod'+id+'_'+perPeriod + "_arg" => +elm.wait,
				'movieperiod'+id+'_'+perPrepare => function (arg) { notifyRun(); isWaitPeriodEvent = false; return arg; } incontextof this,
				'movieperiod'+id+'_'+perPrepare + "_arg" => +elm.wait,
				'movieperiod'+id+'_'+perSegLoop => function (arg) { notifyRun(); isWaitPeriodEvent = false; return arg; } incontextof this,
				'movieperiod'+id+'_'+perSegLoop + "_arg" => +elm.wait
				]);
		}
		notifyStable() if (!elm.nostable);
		return -2;
	}

	//--------------------------------------------------------- FireEffect関連 --

	/**
	 * 全レイヤムービーの停止
	 */
	function stopAllLayerFires() {
		stopAllGenericFlipsOneClass("fire");
	}

	//--------------------------------------------------------- 汎用Flip関連 --

	var genericFlipsInstances = %[];
	var activeGenericFlips = [];
	function createGenericFlip(type, id) {
		var cls = GenericFlip.GetClass(type), obj;
		if (cls === void) return;
		try { obj = new cls(this); } catch { return; }
		obj.flipType = type;
		obj.flipID = (id != "") ? id : System.createUUID();
		var list = genericFlipsInstances[type];
		/**/list = genericFlipsInstances[type] = [] if (list === void);
		list.add(obj);
		activeGenericFlips.add(type) if (activeGenericFlips.find(type) < 0);
		flipStart();
		return obj;
	}
	function updateGenericFlips(now) {
		var  cnt = activeGenericFlips.count;
		if (!cnt) return false;
		var remove = false;
		for (var i = 0; i < cnt; i++) {
			var gft = activeGenericFlips[i], list, del = false;
			/**/list = genericFlipsInstances[gft] if (gft != "");
			if (list === void) del = true;
			else {
				del = !updateGenericFlipsOneClass(now, list, gft);
			}
			if (del) {
				activeGenericFlips[i] = void;
				remove = true;
			}
		}
		if (remove) activeGenericFlips.remove(void, true);
		return      activeGenericFlips.count > 0;
	}
	function updateGenericFlipsOneClass(now, list, gft) {
		var  cnt = list.count;
		if (!cnt) return false;
		var trig = GenericFlip.GetTrigger(gft);
		for (var i  = cnt-1; i >= 0; i--) {
			var obj = list[i];
			if (obj === void || !obj.flipFrame(now)) {
				if (obj) {
					conductor.trigger(trig + obj) if (trig != "");
					invalidate obj;
				}
				list.erase(i);
			}
		}
		cnt = list.count;
		conductor.trigger(trig) if (!cnt && trig != "");
		return cnt > 0;
	}
	function stopAllGenericFlips() {
		for (var i  = activeGenericFlips.count-1; i >= 0; i--)
			stopAllGenericFlipsOneClass(activeGenericFlips[i]);
	}
	function stopAllGenericFlipsOneClass(gft) {
		if (gft == "") return;
		var list = genericFlipsInstances[gft];
		if (list === void) return;
		for (var i  = list.count-1; i >= 0; i--) {
			var obj = list[i];
			/**/obj.flipStop() if (obj !== void);
		}
	}
	function getGenericFlipsInstanceCount(gft) {
		if (gft != "") {
			var list = genericFlipsInstances[gft];
			return (list !== void) ? list.count : 0;
		} else {
			var ret = 0;
			for (var i = activeGenericFlips.count-1; i >= 0; i--)
				try { ret += genericFlipsInstances[activeGenericFlips[i]].count; } catch {}
			return ret;
		}
	}
	function findGenericFlipsInstance(gft, id) {
		if (gft == "") {
			for (var tag, i = activeGenericFlips.count-1; i >= 0; i--) {
				gft = activeGenericFlips[i];
				if (gft == "") continue;
				var r = findGenericFlipsInstance(gft);
				if (r !== void) return r;
			}
		} else {
			var list = genericFlipsInstances[gft];
			if (list === void) return;
			for (var i = list.count-1; i >= 0; i--) {
				if (list[i] !== void && list[i].flipID == id) return list[i];
			}
		}
	}

	//--------------------------------------------------------- モーション関連 --

	var motionCount = 0;

	function onMotionStart(layer)
	{
		motionCount++;
	}

	// レイヤでモーションが終了したときに呼ばれる
	function onMotionStop(layer) 
	{
		motionCount--;
		conductor.trigger('motion_' + (string)layer);
		if (motionCount == 0) {
			conductor.trigger('motion');
			// モーションのキャッシュデータをクリアしておく
			if (motion_manager !== void) {
				motion_manager.clearCache();
			}
		}
	}

	function onMotionAction(action) {
		// ディレイ実行展開
		extractDelay(label);
	}
	
	// 全モーションの停止
	function stopAllMotions() {
		onLayerCommand(%[command:"stopMotion", args:[]]);
	}

	// レイヤモーションを待つ
	function waitLayerMotion(target, canskip, wait=0) {
		if (!target.motionPlaying) return 0;
		var name = 'motion_'+ (string)target;
		if((canskip === void || +canskip) && clickSkipEnabled)
		{
			// スキップできる場合
			if(skipMode)
			{
				// スキップ中の場合
				target.stopMotion();
				return 0; // スキップ中の場合は再生を停止させて返る
			}
			conductor.wait(%[
				click : function (target)
				{
					target.stopMotion(); // 再生を終了する
				} incontextof this,
				'click_arg' => target, // ハンドラへの引数
				name => function (arg)
				{
					return arg;
				} incontextof this,
				name + "_arg" => wait
				]);
		}
		else
		{
			// スキップできない場合
			conductor.wait(%[
				name =>	function (arg)
				{
					return arg;
				} incontextof this,
				name + "_arg" => wait
				]);
		}
		return -2;
	}

	// 全モーションを待つ
	function waitAllMotions(canskip, wait=0)	{
		if (motionCount == 0) return 0;
		if((canskip === void || +canskip) && clickSkipEnabled)
		{
			// スキップできる場合
			if(skipMode)
			{
				// スキップ動作中
				stopAllMotions();
				return 0; // 自動移動を停止させてすぐに返る
			}
			conductor.wait(%[
				click : function
				{
					updateBeforeCh = 1;
					stopAllMotions();
				} incontextof this,
				motion : function(arg)
				{
					updateBeforeCh = 1;
					return arg;
				} incontextof this,
				motion_arg : wait
				]);
		}
		else
		{
			conductor.wait(%[
				motion : function(arg)
				{
					updateBeforeCh = 1;
					return arg;
				} incontextof this,
				motion_arg : wait
				]);
		}
		return -2;
	}

	// モーションを待つ
	function waitMotion(elm) {
		var target;
		if (elm.layer !== void) {
			target = getLayerFromElm(elm);
		} else if (typeof elm.target == "Object") {
			target = elm.target;
		} else if (typeof elm.target == "String") {
			target = Scripts.eval(elm.target);
		}
		if (target !== void) {
			if (typeof target == "Object" && typeof target.motionPlaying != "undefined") {
				return waitLayerMotion(target, elm.canskip, +elm.wait);
			} else {
				dm("モーション待ちはレイヤに指定する必要があります");
				return 0;
			}
		} else {
			return waitAllMotions(elm.canskip, +elm.wait);
		}
	}
	
	// ------------------------------------------------------ レイヤ全消去

	/**
	 * レイヤの全消去
	 */
	function clearLayers(base) {
		
		base.base.freeImage();
        for(var i = base.layers.count-1; i >= 0; i--) {
			base.layers[i].freeImage();
            base.layers[i].visible = false;
        }
        for(var i = base.messages.count-1; i >= 0; i--) {
            base.messages[i].clear(true);
            base.messages[i].visible = false;
        }
        // 選択肢窓消去
		hideSelect();
		hideMapSelect();
		hideTransLayer();
		hideBlackLayer();
		hidePanel();
		forEachEventHook('onClear',,base);
		setMenuAccessibleAll();

		// PSDレイヤ情報のキャッシュもクリア
		standPSDInfoList.clear();
	}
	
	// ------------------------------------------------------ メッセージ自動消去指定

    var erAfterPage; // 改ページ直後に画面消去するか
	
    /**
	 * @param erAfterPage ページ待ち直後に画面消去
	 */
	function setErAfterPage(erAfterPage) {
		this.erAfterPage = erAfterPage;
    }

    // ------------------------------------------------------ 改行特殊制御モード
	
    var lineMode;    // 改行処理モード
    var crAfterName; // 名前の後に改行をいれるか
    var noCrOnce;    // 改行処理を１回なしにする
    var noErOnce;    // 画面消去処理を１回なしにする
	var autoIndent;  // 「『で自動的にインデントする

	// 自動インデント用の括弧一覧（必ずBegin/Endと順序が合うように並べる）
	var autoIndentBeginCharacters = "「『（‘“〔［｛〈《";
	var autoIndentEndCharacters   = "」』）’”〕］｝〉》";

	var indentLevel; // 「のカウントを行う
	var indentCharacter; // インデントに使った文字

    var afterPage;   // 改ページ直後
	var doLine;        // 入力待ち実行指示
	var emptyLine;     // まだ行が空
	var commandLine;   // コマンドが実行された行
    var prevEmptyLine; // 前の空行状態

    var nameMode;    // 名前処理モード
    var afterName;   // 名前処理の直後
    var nameStr;     // 現在の名前
    var nameDisp;    // 現在の表示名
    
    /**
     * @param lineMode
     * 0: 通常
     * 1:行単位で [p]
     * 2:行単位で [l] 手動で [p]
     * 3:行単位で [l] 空行で [p]
     * 4:改行は無視。空行で [p]
     * 5:改行は [r] 。空行で [p]
     */
    function setLineMode(lineMode) {
		if (lineMode == "none" || lineMode == "") {
			//dm("ラインモード:なし");
			mainConductor.ignoreCR = global.ignoreCR;
			afterPage = true;
			doLine = false;
            emptyLine = true;
			commandLine = false;
			prevEmptyLine = true;
			this.lineMode = LINEMODE_NONE;
        } else if (lineMode == "page") {
            //dm("ラインモード:page");
            mainConductor.ignoreCR  = false;
			afterPage = true;
			doLine = false;
            emptyLine = true;
			commandLine = false;
            prevEmptyLine = true;
            this.lineMode = LINEMODE_PAGE;
        } else if (lineMode == "line") {
            //dm("ラインモード:line");
            mainConductor.ignoreCR  = false;
            afterPage = true;
			doLine = false;
            emptyLine = true;
			commandLine = false;
            prevEmptyLine = true;
			this.lineMode = LINEMODE_LINE;
        } else if (lineMode == "vn") {
            //dm("ラインモード:vn");
            mainConductor.ignoreCR  = false;
            afterPage = true;
			doLine = false;
            emptyLine = true;
			commandLine = false;
            prevEmptyLine = true;
            this.lineMode = LINEMODE_VN;
        } else if (lineMode == "tex") {
            //dm("ラインモード:tex");
            mainConductor.ignoreCR  = false;
            afterPage = true;
			doLine = false;
            emptyLine = true;
			commandLine = false;
            prevEmptyLine = true;
            this.lineMode = LINEMODE_TEX;
        } else if (lineMode == "free") {
            //dm("ラインモード:free");
            mainConductor.ignoreCR  = false;
            afterPage = true;
			doLine = false;
            emptyLine = true;
			commandLine = false;
            prevEmptyLine = true;
            this.lineMode = LINEMODE_FREE;
        }
    }

    /**
     * @param crAfterName 名前の後に改行をいれるかどうか
     */
    function setCrAfterName(crAfterName) {
        this.crAfterName = crAfterName;
        afterName = false;
    }

    /**
     * @param autoIndent 名前直後の「『で自動的にインデント処理
     */
    function setAutoIndent(autoIndent) {
        this.autoIndent = autoIndent;
    }

    /**
     * 自動ラベルモードの既読処理
     */
	function storeAutoLabel() {
		if (autoLabelMode) {
			if (autoLabelCurrentRecordName != "") {
				if (sflags[autoLabelCurrentRecordName] === void ||
					sflags[autoLabelCurrentRecordName] < autoLabelCount) {
					sflags[autoLabelCurrentRecordName] = autoLabelCount;
					callHook("onStoreAutoLabelUpdate");
				}
            }
        }
    }

    /**
     * 自動ラベリング設定
     */
    function setAutoLabel() {
		if (autoLabelMode) {
			// 自動ラベル処理をコンダクタにわりこませる
            insertTag("autolabel");
        }
    }

	/**
	 * 未読スキップ停止確認
	 */
	function checkAutoSkip() {
		if (autoLabelMode && autoLabelType == 1) {
			if(!(allskip || getCurrentRead()) && skipMode < SKIP_FAST) {
				cancelSkip(); // 未読なのでスキップを停止
			}
		}
	}

	/**
	 * 自動ラベル処理実行
	 */
	function doAutoLabel() {
		if (autoLabelMode) {
			// 自動ラベル処理用特殊タグ
			// 通常の KAG からよぶことはない。
			// 改ページ処理によって行頭に擬似的に挿入される
			switch (autoLabelType) {
			case 0:
				// 間接ラベル方式で保存
				conductor.callLabel("");
				conductor.loadScenario("autolabel.ks");
				conductor.goToLabel("*autoLabelLabel");
				break;
			case 1:
				// 行指定モードで保存処理
				pushHistoryOfStore();
				storeFlags(true);
				storeLabelPassed = true;
				checkAutoSkip();
				setMenuAccessibleAll();
				break;
			}
		}
	}

	/**
	 * 指定したタグを実行キューに追加する／次回に割り込み実行させる
	 */
	function addTag()        { conductor.enqueueTag(    copyTag(...)); }
	function insertTag()     { conductor.   pushTag(    copyTag(...)); }

	/**
	 * 最優先実行タグ登録（自己展開以外に使うと挙動がおかしくなるので注意）
	 */
	function addFastTag()    { conductor.enqueueFastTag(copyTag(...)); }
	function insertFastTag() { conductor.   pushFastTag(copyTag(...)); }

	/**
	 * タグの追加（パース処理用）
	 */
	function addParseTag(cd, name, elm) {
        var e = %[];
		var taglist = [];
		_copyTag(e, taglist, elm);
		e.tagname = name if name !== void;
		e.taglist = taglist;
		cd.pendings.add(e);
    }
	
	/**
	 * パースコマンドのパース
	 */
	function parseParse(cd, elm) {
		clearVoiceMap();
		if (elm.base !== void) {
			voiceBase = elm.base;
		}
		if (elm.voice !== void) {
			voiceDefault = +elm.voice;
		}
		addParseTag(cd, "parse");
    }

	// ------------------------------------------------
	// ボイスパース処理用
	// ------------------------------------------------

	// キャラ別ボイス定義
	var characterVoiceInfo = %[];
	var characterVoiceList = [];

	/**
	 * キャラ別ボイス定義情報の読み込み
	 * キャラ名,ファイル名,コンフィグ名,ボリューム制御
	 */
	function loadCharacterVoiceInfo(filename="charvoice.csv") {
		(Dictionary.clear incontextof characterVoiceInfo)();
		characterVoiceList.clear();
		var temp = [];
		var sep = (Storages.extractStorageExt(filename).toLowerCase() == ".txt") ? "\t" : ",";
		temp.load(filename);
		for (var i = 0; i < temp.count; i++) {
			var line = [];
			line.split(sep, temp[i]);
			var count = line.count;
			var name = line[0];
			if (count >= 2 && name.charAt(0) != '#') {
				var info = %[name:name, file:line[1]];
				info.config = count >= 3 ? line[2]  : "";
				info.per    = count >= 4 ? +line[3] : 100;
				info.alias  = count >= 5 ? line[4]  : "";
				characterVoiceInfo[name] = info;
				characterVoiceList.push(info);
			}
		}
	}

	function getAliasName(name) {
		var info = characterVoiceInfo[name];
		if (info !== void && info.alias != "") {
			return (info.alias.charAt(0) == "&" ? Scripts.eval(info.alias.substr(1)) : info.alias);
		}
		return name;
	}
	
	var voiceBase = "";
	var voiceDefault;
	var voiceMap = %[];
	var nextVoiceList = [];

	function clearVoiceMap() {
		//dm("ボイスマップクリア");
		nextVoiceList.clear();
		(Dictionary.clear incontextof voiceMap)();
	}

	function storeVoiceMap(f) {
		f.voiceBase = voiceBase;
		f.voiceDefault = voiceDefault;
		var map = f.voiceMap = %[];
		var names = [];
		names.assign(voiceMap);
		var count = names.count;
		for (var i=0;i<count;i+=2) {
			var name  = names[i];
			var value = names[i+1];
			var info = %[];
			(Dictionary.assign incontextof info)(value);
			//dm("保存値:" + name + ":" + value.voice);
			map[name] = info;
		}
	}

	function restoreVoiceMap(f) {
		voiceBase = f.voiceBase;
		voiceDefault = f.voiceDefault;
		clearVoiceMap();
		if (f.voiceMap !== void) {
			var names = [];
			names.assign(f.voiceMap);
			var count = names.count;
			for (var i=0;i<count;i+=2) {
				var name  = names[i];
				var value = names[i+1];
				var info = %[];
				(Dictionary.assign incontextof info)(value);
				//dm("復帰値:" + name + ":" + value.voice);
				voiceMap[name] = info;
			}
		}
	}

	// ----------------------------------------
	// ループボイス処理用
	// ----------------------------------------

	var loopVoiceList = [];
	var loopVoiceTrigger;

	function initLoopVoice() {
		loopVoiceTrigger = new AsyncTrigger(updateLoopVoice, '');
		loopVoiceTrigger.cached = true;
		add(loopVoiceTrigger);
	}
	
	function clearLoopVoice() {
		//dm("ループボイス情報クリア");
		loopVoiceList.clear();
	}

	/**
	 * ループボイス情報探索
	 * @param name キャラ名
	 */
	function findLoopVoiceInfo(name) {
		var count = loopVoiceList.count;
		for (var i=0;i<count;i++) {
			var info = loopVoiceList[i];
			if (info.name == name) {
				return info;
			}
		}
	}

	/**
	 * ループボイス再生指定
	 * @param name キャラ名
	 * @param file ファイル指定
	 */
	function startLoopVoice(name, voice, type) {
		var info = findLoopVoiceInfo(name);
		if (info !== void) {
			info.voice = voice;
			info.type = type;
		} else {
			info = %[name:name, voice:voice, type:type];
			loopVoiceList.push(info);
		}
		loopVoiceTrigger.trigger();
	}

	/**
	 * ループボイス再開指定
	 * @param name キャラ名
	 */
	function updateLoopVoice() {
		Scripts.foreach (loopVoiceList, function(id, info) {
			var voiceBuffer = getPlayingVoiceTrack(info.name, true);
			if (voiceBuffer === void || !voiceBuffer.canWait()) {
				playVoice(info.name, getPlayVoiceFilename(info.name, info.voice), info.type, true, false);
			}
		});
	}
	
	/**
	 * ループボイス停止指定
	 * @param name キャラ名
	 */
	function stopLoopVoice(name) {
		//dm("ループボイス停止:" + name);
		var count = loopVoiceList.count;
		for (var i=0;i<count;i++) {
			if (loopVoiceList[i].name == name) {
				loopVoiceList[i].erase(i);
				break;
			}
		}
	}

	/**
	 * ループボイス状態保存
	 */
	function storeLoopVoice(f) {
		if (loopVoiceList.count > 0) {
			//dm(@"ループボイス保存:${loopVoiceList.count}");
			f.loopVoiceList = [];
			f.loopVoiceList.push(loopVoiceList*);
		}
	}
	
	/**
	 * ループボイス状態復帰
	 */
	function restoreLoopVoice(f, trigger=true) {
		clearLoopVoice();
		if (f.loopVoiceList !== void) {
			//dm(@"ループボイス復帰:${f.loopVoiceList.count}");
			Scripts.foreach(f.loopVoiceList, function(id,value) { 
				loopVoiceList.push(Scripts.clone(value));
			});
		}
		if (trigger) {
			loopVoiceTrigger.trigger();
		}
	}

	// -----------------------------------------------------------
	
	/**
	 * 指定キャラのボイス指定を更新する
	 * @param name 話者名
	 * @param voice ボイス指定
	 * @param replace 差し替えならtrue
	 * @param insert 割り込みならtrue
	 */
	function setCurrentVoice(name, voice, replace, insert) {
		var info = voiceMap[name];
		if (info == void) {
			info = %[];
			voiceMap[name] = info;
		}
		if (voice.length > 0) {
			if (voice.charAt(0) == "@") {
				if (info.voice !== void) {
					info.voice += +voice.substr(1);
				} else {
					info.voice = +voice.substr(1);
				}
			} else if (voice == "clear") {
				delete info.strVoice;
				delete info.voice;
				delete info.incVoice;
			} else if (voice == "dec") {
				if (info.voice !== void) {
					info.voice--;
				}
			} else if (voice == "inc") {
				if (info.voice !== void) {
					info.voice++;
				}
			} else if (voice == "ignore") {
				info.strVoice = "ignore";
			} else if (reNumber.test(voice)) {
				// 数値指定の場合
				if (replace || insert) {
					info.strVoice = +voice;
				} else {
					info.voice = +voice;
				}
			} else {
				info.strVoice = voice;
			}
			if (replace) {
				info.incVoice = true;
			}
		}
		//dm("ボイス設定:%s:%s:%s".sprintf(name, info.voice, info.strVoice));
	}

	var reNumber = new RegExp("^[1-9][0-9]*$"); // 数値確認用
	var cutZero  = new RegExp("^0*", "g"); // 先頭の0の文字カット用（残すと８進数とみなされるので）
	
	/**
	 * 指定キャラの現在のボイス情報を返す
	 * @param name キャラ名
	 * @return ボイス指定名
	 */
	function getCurrentVoice(name) {
		//dm("ボイス取得処理:" + name);
		var info = voiceMap[name];
		if (info == void) {
			if (voiceDefault !== void && characterVoiceInfo[name] !== void) {
				info = %[];
				voiceMap[name] = info;
				info.voice = voiceDefault;
			}
		}
		var voice;
		if (info !== void) {
			if (info.strVoice !== void) {
				if (info.strVoice != "ignore") {
					voice = info.strVoice;
				}
				info.strVoice = void;
			} else if (info.voice !== void) {
				voice = info.voice;
				info.incVoice = true;
			}
			if (info.incVoice) {
				if (info.voice >= 0) {
					info.voice++;
				}
				delete info.incVoice;
			}
			//dm("ボイス取得:%s:%s".sprintf(name, voice));
		}
		if (voice !== void) {
			// ボイスの実ファイル名化
			if (typeof voice == "String" && reNumber.test(voice)) {
				voice = (int)(cutZero.replace(voice, ""));
			}
			if (typeof voice == "Integer") {
				var info = characterVoiceInfo[name];
				if (info !== void && info.file != "") {
					voice = info.file.sprintf(voiceBase !== void ? voiceBase : "", voice);
				}
			}
		}
		return voice;
	}

	/**
	 * 既にエントリされているか
	 */
	function isEntryVoice(name) {
		var count = nextVoiceList.count;
		for (var i=0;i<count;i++) {
			var info = nextVoiceList[i];
			if (info.name == name) {
				return true;
			}
		}
		return false;
	}

	/**
	 * 次に再生するボイスを登録する
	 */
	function entryNextVoice(name, type=0) {
		var voice = getCurrentVoice(name);
		//dm(@"${name}:entryNextVoice:${voice},${type}");
		if (voice !== void) {
			nextVoiceList.add(%[name:name, voice:voice, type:type]);
		}
	}

	// 該当ファイルの実ファイルの更新時刻取得
	function getStorageModifyTime(storage) {
		Plugins.link("fstat.dll") if (typeof Storages.dirlist == "undefined");
		var path = Storages.getPlacedPath(storage);
		if (path != "") {
			var time = Storages.getTime(path);
			if (time !== void && time.mtime !== void) {
				return time.mtime.getTime();
			}
		}
		return 0;
	}
	
	/**
	 * コンバートモード用のボイス情報取得の口
	 * ボイスの長さ情報を取得して格納しておく
	 * @param nameStr 現在の話者名
	 * @param nolip 現在の話者の心の声ならtrue (口ぱくさせない)
	 */
	function getNextVoiceListForConvertMode(nameStr, nolip=false) {
		if (nextVoiceList.count > 0) {
			var voice = nextVoiceList;
			nextVoiceList = [];
			var vb = voices[0];
			for (var i=0;i<voice.count;i++) {
				var v = voice[i];
				var filename = getPlayVoiceFilename(v.name, v.voice);
				if (false && filename !== void) {
					try {
						vb.open(filename);
						vb.paused = true;
						vb.play();

						// 時間情報
						v.time = vb.totalTime;
						
						// ラベル情報
						var labels = [];
						var names = [];
						labels.push(0, "vlstart");
						names.assign(vb.labels);
						for (var i=0;i<names.count;i+=2) {
							var lname  = names[i];
							var lframe = (int)(names[i+1].position * 60 / 1000);
							labels.push(lframe, lname);
							labels.push(lframe, @"vl${i+1}");
						}
						labels.push((int)(v.time * 60 / 1000), "vlstop");
						v.labels = labels;
						vb.stop();
						vb.paused = false;
						if (v.name != nameStr || !nolip) {
							// 口ぱく用音量情報登録
							var vfilename = filename + ".vol.csv";
							if (Storages.isExistentStorage(vfilename)) {
								try {
									var a = [];
									a.load(vfilename);
									if (a.count > 0) {
										var vols = a[0].split(",");
										var c = vols.count;
										for (var i=0;i<c;i++) {
											vols[i] = +vols[i];
										}
										v.vols = vols;
									}
								} catch(e) {
								errorVoice(@"${vfilename}:ボイス音量情報読み込み失敗:${e.message}");
								}
							}
						} 
					} catch (e) {
						errorVoice(@"${filename}:ボイス読み込み失敗:${e.message}");
					}
				} 
			}
			return voice;
		}
	}
	
	// ----------------------------------------------------------------

	// ボイスコマンド扱い
	var voiceCmds = %[ tagname:true, base:true, "default" => true, replace:true, insert:true, next:true, back:true ];

	// 引数をそれぞれ名前と見なしてボイス用のコマンドを処理する
	function parseVoice(cd, elm) {
		if (elm.base !== void) {
			clearVoiceMap();
			voiceBase = elm.base;
		}
		if (elm["default"] !== void) {
			voiceDefault = elm["default"];
		}
		var names = elm.taglist;
		var count = names !== void ? names.count : 0;
		for (var i=0;i<count;i++) {
			var name  = names[i];
			if (voiceCmds[name] !== void) {
				continue;
			}
			var value = elm[name];
			if (value != "true") {
				// 名前だけあった場合は無視
				setCurrentVoice(name, value, elm.replace, elm.insert);
			}
			if (elm.back) {
				entryNextVoice(name, 1);
			} else if (elm.next) {
				entryNextVoice(name);
			}
		}
	}

	function parseNextVoice(cd, elm) {
		elm.next = true;
		return parseVoice(cd, elm);
	}

	function parseBackVoice(cd, elm) {
		elm.back = true;
		return parseVoice(cd, elm);
	}
	
	// -------------------------------------------------
	
	function lineCheck(cd) {
		// 行頭でのラインモード処理
		// 改行指示がある
		if (doLine) {
			parseL(cd);
			doLine = false;
		}
	}
	
	function emptyCheck(cd) {
		// テキストクリア処理
		switch (lineMode) {
		case LINEMODE_PAGE:
			break;
		case LINEMODE_LINE:
		case LINEMODE_VN:
		case LINEMODE_FREE:
			if (afterPage) {
				afterPage = false;
			} else {
				if (!afterName && !skipNoDisp) {
					addParseTag(cd, "r");
				}
			}
			break;
		case LINEMODE_TEX: // tex
			if (afterPage) {
				afterPage = false;
			}
			break;
		}
		if (!afterName && (lineMode <= LINEMODE_LINE || prevEmptyLine)) {
			forEachFunctionHook("onParseDispname");
			addParseTag(cd, "dispname");
			afterName = true;
			indentCharacter = void;
			indentLevel = 0;
		}
	}
	
	/**
	 * テキストのパース処理
	 */
	function parseCh(cd, elm, isload) {

        // 名前表示専用処理
        //【キャラクタ名/表示名】を検出して名前表示処理を実行する
		if (nameMode > 0) {
            // 既に名前表示ロジックに入ってる
            // ここでは emptyLine の状態はありえない
			if (elm.text == "】") {
				nameMode = 0;
				//dm("名前処理:" + nameStr + ":" + nameDisp);
				if (!isload && !isEntryVoice(nameStr)) {
					// 重複登録しない。先に登録したほうが有効
					entryNextVoice(nameStr);
				}
				//dm("名前展開:" + nameStr + " / " + nameDisp);

				// バックボイス指定
				// 一つでも通常ボイスがあればバックボイス扱いにはしない
				var count = nextVoiceList.count;
				var backVoice = count > 0;
				for (var i=0;i<count;i++) {
					if (nextVoiceList[i].type == 0) {
						backVoice = false;
					}
				}
				_backVoiceMode = backVoice;
				var e = %[name:nameStr, disp:nameDisp];
				forEachFunctionHook("onParseDispname", e);
				addParseTag(cd, "dispname", e);
				if (nextVoiceList.count > 0) {
					addParseTag(cd, "linevoice", %[voice:nextVoiceList]); // 特殊処理
					nextVoiceList = [];
				}
				afterName = true;
				indentCharacter = void;
				indentLevel = 0;
                return;
            } else {
				if (nameMode == 1) {
					if (elm.text == "/") {
						nameMode = 2;
                        return 0;
                    } else {
                        nameStr += elm.text;
                    }
                } else {
                    nameDisp += elm.text;
                }
            }
            return;
		} else if (emptyLine) {
			lineCheck(cd);
			if (elm.text == "【") {
				nameMode = 1;
				nameStr  = "";
				nameDisp = "";
				return;
			} 
			emptyCheck(cd);
		}

		// 自動インデント文字チェック
		var indentBeginIdx = -1, indentEndIdx = -1;
		if (!skipNoDisp && autoIndent && elm.text != "") {
			var ch = ((string)elm.text).charAt(0);
			indentBeginIdx = autoIndentBeginCharacters.indexOf(ch);
			indentEndIdx   = autoIndentEndCharacters  .indexOf(ch);
		}

		// 文字登録
		if (!skipNoDisp) {
			if (autoIndent < 0 && (afterName || (emptyLine && !indentLevel)) && indentBeginIdx >= 0) {
				addParseTag(cd, "preindent", elm);
			}
			addParseTag(cd, "ch", elm);
		}
		emptyLine = false;
		afterPage = false;

		// 自動インデント処理
		if (indentBeginIdx >= 0) {
			if (afterName) {
				indentCharacter = autoIndentBeginCharacters.charAt(indentBeginIdx);
				addParseTag(cd, "indent");
			}
			indentLevel++;
		} else if (indentEndIdx >= 0) {
			if (indentLevel == 1) {
				var pair = autoIndentBeginCharacters.indexOf(indentCharacter);
				addParseTag(cd, "endindent") if (pair == indentEndIdx);
			}
			indentLevel--;
		}
		afterName = false;

        return;
    };

	/**
	 * 改行のパース処理
	 */
	function parseR(cd, elm)
	{
		if (!elm.eol) {
			// 改行指示がある
			if (doLine) {
				parseL(cd);
				doLine = false;
			}
			if (!skipNoDisp) {
				checkName(cd);
				addParseTag(cd, "r", elm);
			}
			return;
		}

		// 改行効果を一度だけ無視させる
        if (noCrOnce) {
			noCrOnce = false;
			prevEmptyLine = emptyLine;
			emptyLine = true;
			commandLine = false;
			return 0;
        }

		nameMode = 0;
		if (crAfterName && afterName) {
			commandLine = false;
			return 0;
		}
		
		switch (lineMode) {
        case LINEMODE_PAGE:
			//dm("page:改行");
			if (emptyLine) {
				if (!commandLine) {
					prevEmptyLine = emptyLine;
				}
            } else {
				prevEmptyLine = emptyLine;
				emptyLine = true;
				if (!skipNoDisp) {
					addParseTag(cd, "p", elm);
				} else {
					checkAutoSkip();
					setAutoLabel();
					addParseTag(cd, "delaydone", elm);
					addParseTag(cd, "afterpage", elm);
				}
			}
            break;
        case LINEMODE_LINE:
            //dm("line:改行");
			if (emptyLine) {
				if (!commandLine) {
					prevEmptyLine = emptyLine;
				}
            } else {
				prevEmptyLine = emptyLine;
				emptyLine = true;
				parseL(cd);
			}
			break;
        case LINEMODE_VN:
            //dm("vn:改行");
            if (emptyLine) {
				if (!prevEmptyLine) {
					if (!commandLine) {
						// 最初の空行で改ページ
						doLine = false;
						prevEmptyLine = emptyLine;
						parseP(cd, elm);
					}
				} else {
					// そうでない空行は無視する
                    prevEmptyLine = emptyLine;
                }
            } else {
                // 名前の直後の改行は無視する
				prevEmptyLine = emptyLine;
				emptyLine = true;
				if (!skipNoDisp) {
					doLine = true;
				}
			}
			break;

        case LINEMODE_TEX:
        case LINEMODE_FREE:
            //dm("tex/free:改行");
            if (emptyLine) {
				if (!commandLine) {
					if (!prevEmptyLine) {
						// 最初の空行で改ページ
						prevEmptyLine = emptyLine;
						parseP(cd, elm);
					} else {
						// そうでない空行は無視する
						prevEmptyLine = emptyLine;
					}
				}
            } else {
                // 名前の直後の改行は無視する
				prevEmptyLine = emptyLine;
				emptyLine = true;
            }
        }
		commandLine = false;
        return 0;
	}

	function parseL(cd) {
		if (!skipNoDisp) {
			addParseTag(cd, "l", %[voicewait:true]);
			addParseTag(cd, "afterline");
		}
	}

	function parseL2(cd, elm) {
		addParseTag(cd, "l", elm);
	}
	
	/**
	 * p タグの展開
	 */
    function parseP(cd, elm) {
		if (!skipNoDisp) {
			addParseTag(cd, "p", elm);
		} else {
			checkAutoSkip();
			setAutoLabel();
			addParseTag(cd, "delaydone", elm);
			addParseTag(cd, "afterpage", elm);
		}
		afterPage = true;
		emptyLine = true;
		commandLine = false;
		nameStr = "";
		nameDisp = "";
    }

	// 名前チェック
	function checkName(cd) {
		if (emptyLine) {
			lineCheck(cd);
			emptyCheck(cd);
		}
		emptyLine = false;
		afterPage = false;
		afterName = false;
	}

	function parseRuby(cd, elm) {
		checkName(cd);
		if (!skipNoDisp) {
			addParseTag(cd, "ruby",elm);
		}
	}

	// キャラ扱いだけど何も表示しないタグ
	function parseNul(cd, elm) {
		checkName(cd);
	}
	
	var textTags = %[
	ruby:parseRuby incontextof this,
	ch:parseCh incontextof this,
	nul:parseNul incontextof this,
	r:parseR incontextof this,
	p:parseP incontextof this,
	l:parseL2 incontextof this,
	nor: function(elm) { noCrOnce = true; return 0; } incontextof this,
	noer: function(elm) { noErOnce = true; return 0; } incontextof this,
		];
	
	var extractTags = %[
	parse:parseParse incontextof this,
	voice:parseVoice incontextof this,
	nextvoice:parseNextVoice incontextof this,
    backvoice:parseBackVoice incontextof this,
		];
	
    /*
     * タグ展開処理
     * lineMode に応じて特殊なタグ展開を行う
     */
	function checkTagExtract(cd, elm, isload) {
		// 自動保存用のラベルをカウント
		if (!isload && autoLabelMode && elm.tagname == "r" && elm.eol) {
			autoLabelCount++;
			storeAutoLabel();
		}
		// 展開処理
		if (lineMode != 0) {
			var func = textTags[elm.tagname];
			if (func !== void) {
				func(cd, elm, isload);
				return true;
			} else {
				// 改行指示がある場合はそれを実行
				if (doLine) {
					parseL(cd);
					doLine = false;
				}
				commandLine = true;
				var func = extractTags[elm.tagname];
				if (func !== void) {
					func(cd, elm);
					return true;
				}
			}
        }
		// 特殊ルビ処理
		var rb = checkRubyTag(elm.tagname);
		if (rb !== void) {
			checkName(cd);
			if (!skipNoDisp) {
				var len = rb.length, ruby = rb.ruby, text = rb.text;
				for (var i = 0; i < len; i++) {
					addParseTag(cd, "ruby", %[ text:ruby[i] ]) if (ruby[i] != "");
					addParseTag(cd, "ch",   %[ text:text[i] ]);
				}
			}
			return true;
		}
		if (forEachFunctionHook("onExtract", elm.tagname, elm, cd) !== void) {
			return true;
		}
		return false;
	}
	// 特殊ルビ用区切り文字（""にすると展開しない）
	var RubySplitNormal   = ",";
	var RubySplitDivision = ",";
	var RubySplitSimple   = "'";

	// ルビ展開するかどうか調べる
	function checkRubyTag(tag) {
		var p, n = RubySplitNormal, s = RubySplitSimple;
		return (n != "" && (p = tag.indexOf(n)) > 0) ? extractRubyTag(    tag.substring(0,p),        tag.substring(p+1))
			:  (s != "" && (p = tag.indexOf(s)) > 0) ? %[ length:1, text:[tag.substring(0,p)], ruby:[tag.substring(p+1)] ] : void;
	}
	// ルビ展開処理
	function extractRubyTag(text, ruby) {
		var tlen = text.length;
		var rlen = ruby.length;
		var tdiv = [], rdiv = [];
		var ret  = %[ length:tlen, text:tdiv, ruby:rdiv ];
		// テキストはそのまま分割して積む
		for (var i = 0; i < tlen; i++) tdiv.add(text.charAt(i));

		if (ruby != "" && RubySplitDivision != "" && ruby.indexOf(RubySplitDivision) >= 0) {
			// 「,」複数区切りの場合
			var div = ruby.split(RubySplitDivision);
			if (div.count != tlen) {
				error("ex-ruby: 個数が一致しません: " + text + "/" + ruby);
			}
			rdiv.assign(div);
		} else if (tlen == 1) {
			// 1文字の場合は問答無用
			rdiv.add(ruby);
		} else if (rlen == tlen) {
			// １文字単位わりあて
			for (var i = 0; i < rlen; i++) rdiv.add(ruby.charAt(i));
		} else if (rlen == tlen * 2) {
			// ２文字単位割り当て
			for (var i = 0; i < tlen; i++) rdiv.add(ruby.substring(i*2,2));
		} else if (rlen > tlen) {
			// ルビのが長い
			// 文字１文字あたりルビ数
			var n = (int)(rlen / tlen);
			if (n < 2) {
				// 2文字に満たない場合
				var start = (int)((tlen - rlen/2)/2);
				var rubyshift = "";
				if ((tlen - rlen / 2) % 2 == 1) {
					// 割り切れない場合はシフト処理
					rubyshift = "　　　";
				}
				for (var i = 0; i < start; i++) rdiv.add("");

				// 均等に配置。とりあえずてきとー
				for(var i = 0; i < rlen; i+=2) {
					rdiv.add(rubyshift + ruby.substring(i,2));
				}
			} else {
				// ルビのが長いので左右にはみでさせる
				var cnt = rlen - tlen * 2;
				var rubyshift = "";
				if (cnt % 2 == 1) {
					// 割り切れない分はシフト処理
					rubyshift = "　";
				}
				cnt = (int)((cnt+1) / 2);
				var firstruby = ruby.substring(0,cnt+2);
				if (cnt > 0) firstruby += "　".repeat(cnt);
				rdiv.add(rubyshift + firstruby);

				var r = cnt+2;
				for (var i = 1; i < tlen-1; i++,r+=2) {
					rdiv.add(rubyshift + ruby.substring(r,2));
				}
				var lastruby = ruby.substring(r);
				cnt = lastruby.length - 2;
				if (cnt > 0) lastruby = "　".repeat(cnt) + lastruby;

				rdiv.add(rubyshift + lastruby);
			}
		} else {
			// ルビのが短い
			// ルビ１文字あたり文字数
			var n = (int)(tlen / rlen);
			var start = (int)((tlen - rlen * n) / 2);
			var rubyshift = "";
			if ((tlen - rlen * n) % 2 == 1) {
				// 割り切れない場合はシフト処理
				rubyshift = "　　　";
			}
			for (var i = 0; i < start; i++) rdiv.add("");
			// 均等に配置。とりあえずてきとー
			for(var i = 0; i < rlen; i++) {
				rdiv.add(rubyshift + ruby.charAt(i));
				for (var k = 1; k < n; k++) rdiv.add("");
			}
		}
		return ret;
	}

	// 特殊待ち指定
	// sync または wait 指定があった場合は待ち処理を実行する
	function waitFunc(elm, func) {
		//dm("waitFunc呼び出し:" + elm.wait + ":" + elm.sync);
		if (elm.wait !== void || elm.sync !== void) {
			if (elm.wait === void) {
				elm.wait = 0;
			}
			return func(elm);
		}
		return 0;
	}

	//------------------------------------------------------- タグハンドラ群 --
    
	function getHandlers()
	{
		return %[ // 辞書配列オブジェクト

		/*
			タグハンドラ群は、名前とそれに対応する関数のペアを列挙するもので、
			関数名 : function(elm)
			{
				// 関数の中身
			} incontextof this,
			の書式を用いる。ただし、関数名が予約語の場合は、「関数名 : 」ではなく
			「"関数名" => 」を用いる。
			incontextof this は、関数が正しく このクラスの
			オブジェクトのコンテキスト上で動くようにするために必要。
		*/

	//--------------------------------------- タグハンドラ群(メッセージ操作) --
    parse : function(elm)
    {
        // 特殊パース処理
        conductor.parseAll();
        return 0;
	} incontextof this,

    embex : function(elm)
    {
        // 特殊パース処理
		if (elm.exp !== void) {
			try {
				var text = Scripts.eval(elm.exp);
				for (var i=0;i<text.length;i++) {
					tagHandlers.ch(%[text:text.charAt(i)]);
				}
			} catch(e) {
				error(@"embex:評価できません${elm.exp}");
			}
		}
        return 0;
	} incontextof this,
	
	// KNS wordwrapping begin
	nextword : function(elm)
	{
		//if (current.x+current.lineLayer.font.getTextWidth(elm.text) >= current.relinexpos)
		//	historyLayer.newline();
		if (current.nextWord(elm.text))
		{
			return showPageBreakAndClear();
		}
		return 0;
	} incontextof this,
	// KNS wordwrapping end

    ch : function(elm)
    {
		if (skipNoDisp) { return 0;	}

		if (lineMode && textWriteEnabled) {
			// 表示処理
            if (setCurrentMessageLayerVisible(true)) {
                return -3;
            }
        }

        // 文字表示
		var acs = actualChSpeed;
		if(updateBeforeCh)
		{
			if(flipStartFlag || acs) { updateBeforeCh--; return -5; } else { updateBeforeCh--; }
		}
		var text = elm.text, repage = void;
		@if (KAGHOOK_PROCESSCH)
		{
			var tmp = %[ text:text ];
			if (callHook("onProcessCh", tmp)) {
				text = tmp.text;
				if (text == "") return 0;
			}
		}
		@endif
		if (textWriteEnabled) {
			if(currentWithBack) current.comp.processCh(text);
			repage = current.processCh(text, chUserMode ? acs : 0);
			if(repage)
			{
				return showPageBreakAndClear();
			}
		}
		if(historyWriteEnabled)
		{
			historyLayer.autoReline() if (repage === false && typeof historyLayer.autoReline == "Object");
			historyLayer.store(text);
		}
		if(autoWCEnabled)
		{
			// 自動ウェイト
			var ind;
			if((ind = autoWCChars.indexOf(text)) != -1)
			{
				return int(acs * autoWCWaits[ind]);
			}
		}
		return acs * actskipspeed;
	} incontextof this,

    dispname : function(elm)
    {
		// [XXX] 後で何とかする
		currentVoice = void;

		if (skipNoDisp) { return 0;	}

		var name;
		if (elm !== void) {
			if      (elm.disp != "") name = elm.disp;
			else if (elm.name != "") {
				name = getAliasName(elm.name);
			}
		}

		// 名前表示が同じだった場合にフラグを立てる
		sflags["dispname_" + name] if (name == elm.name);
		
		var ret = 0;

		if (!_backVoiceMode) {
			// バックボイス時は名前表示処理を行わない
			
			// メッセージ窓表示処理
			if (kag.textWriteEnabled && kag.setCurrentMessageLayerVisible(true)) {
				return -3;
			}
			
			// 前のボイスの停止
			if (voicecut && !voicecutpage) {
				stopAllVoice();
			}
			
			// メッセージ窓消去処理
			if (!erAfterPage) {
				tagHandlers.er(%[]);
			}
			
			// 外部プラグイン処理
			ret = forEachFunctionHook("onDispname", elm);
			if (ret === void) {
				// 標準処理
				drawName(name);
				ret = 0;
			}
		}
		
		// ヒストリ登録
		if (historyWriteEnabled) {
			if (typeof historyLayer.storeName !== 'undefined') {
				historyLayer.storeName(name, elm);
			} else {
				if (name != "") {
					historyLayer.store(name + " ");
				}
			}
		}
		
		talkWaitCount = 0;
		return ret;

	} incontextof this,

	graph : function(elm)
	{
		if (skipNoDisp) { return 0;	}

		if (lineMode && textWriteEnabled) {
			// 表示処理
            if (setCurrentMessageLayerVisible(true)) {
                return -3;
            }
        }

		// グラフィックを文字として表示
		var acs = actualChSpeed;
		if(updateBeforeCh)
		{
			if(flipStartFlag || acs) { updateBeforeCh--; return -5; } else { updateBeforeCh--; }
		}
		var text = elm.alt, repage = void;
		if (textWriteEnabled) {
			if(currentWithBack) current.comp.processGraph(elm);
			repage = current.processGraph(elm);
			if(repage)
			{
				return showPageBreakAndClear();
			}
		}
		if(historyWriteEnabled)
		{
			historyLayer.autoReline() if (repage === false && typeof historyLayer.autoReline == "Object");
			historyLayer.store(text, true) if (text !== void);
		}
		return acs * actskipspeed;
	} incontextof this,

	hch : function(elm)
	{
		if (skipNoDisp) { return 0;	}

		// 縦中横
		var acs = actualChSpeed;
		if(updateBeforeCh)
		{
			if(acs) { updateBeforeCh--; return -5; } else { updateBeforeCh--; }
		}
		var text = elm.text;
		var expand = elm.expand !== void && +elm.expand;
		if (textWriteEnabled) {
			if(currentWithBack) current.comp.putHorizonCh(text, expand);
			if(current.putHorizonCh(text, expand))
			{
				return showPageBreakAndClear();
			}
		}
		if(historyWriteEnabled) historyLayer.store(text);
		return acs * actskipspeed;
	} incontextof this,

    linemode : function(elm) {
        setLineMode(elm.mode);
        return 0;
    } incontextof this,

    craftername : function(elm) {
        setCrAfterName(elm.mode);
        return 0;
    } incontextof this,

    erafterpage : function(elm) {
        setErAfterPage(elm.mode);
        return 0;
    } incontextof this,
                  
    autoindent : function(elm) {
        setAutoIndent(elm.mode);
        return 0;
    } incontextof this,
                  
	r : function(elm)
    {
		if (skipNoDisp) { return 0;	}

		// 改行
        if(historyWriteEnabled) historyLayer.reline();
		if (textWriteEnabled) {
			if(currentWithBack) current.comp.processReturn();
			if(current.processReturn())
			{
				var ret = showPageBreakAndClear();
				// 改行はpendingしない
				if(ret == -5)
					ret = -4;
				else if(ret == -3)
					ret = -2;
				return ret;
			}
		}
		return actualChSpeed * actskipspeed;
    } incontextof this,

	ruby : function(elm)
	{
		// 次の文字に対するルビ設定
		if (textWriteEnabled) {
			if(currentWithBack) current.comp.setRuby(elm.text);
			current.setRuby(elm.text);
		}
		return 0;
	} incontextof this,

	font : function(elm)
	{
		// フォント設定
		if(currentWithBack) current.comp.setFont(elm);
		current.setFont(elm);
		return 0;
	} incontextof this,

	deffont : function(elm)
	{
		// デフォルトのフォント設定
		if(currentWithBack) current.comp.setDefaultFont(elm);
		current.setDefaultFont(elm);
		return 0;
	} incontextof this,

	resetfont : function(elm)
	{
		// フォントのリセット
		if(currentWithBack) current.comp.resetFont();
		current.resetFont();
		return 0;
	} incontextof this,

	style : function(elm)
	{
		// スタイル設定
		if(currentWithBack) current.comp.setStyle(elm);
		current.setStyle(elm);
		return 0;
	} incontextof this,

	defstyle : function(elm)
	{
		// デフォルトのスタイル設定
		if(currentWithBack) current.comp.setDefaultStyle(elm);
		current.setDefaultStyle(elm);
		return 0;
	} incontextof this,

	resetstyle : function(elm)
	{
		// スタイルのリセット
		if(currentWithBack) current.comp.resetStyle();
		current.resetStyle();
		return 0;
	} incontextof this,

	link : function(elm)
	{
		// ハイパーリンクの開始
		if(currentWithBack) current.comp.beginHyperLink(elm);
		current.beginHyperLink(elm);
		return 0;
	} incontextof this,

	endlink : function(elm)
	{
		// ハイパーリンクの終了
		if(currentWithBack) current.comp.endHyperLink(elm);
		current.endHyperLink(elm);
		return 0;
	} incontextof this,

	button : function(elm)
	{
		// グラフィカルボタン
		if(currentWithBack) current.comp.addButton(elm);
		current.addButton(elm);
		return 0;
	} incontextof this,

	edit : function(elm)
	{
		// 単一行編集
		if(currentWithBack) current.comp.addEdit(elm);
		current.addEdit(elm);
		return 0;
	} incontextof this,

	checkbox : function(elm)
	{
		// チェックボックス
		if(currentWithBack) current.comp.addCheckBox(elm);
		current.addCheckBox(elm);
		return 0;
	} incontextof this,

	slider : function(elm)
	{
		// スライダ
		if(currentWithBack) current.comp.addSlider(elm);
		current.addSlider(elm);
		return 0;
	} incontextof this,
                  
	commit : function(elm)
	{
		// フォーム要素のコミット
		current.commit();
		return 0;
	} incontextof this,

    sysbutton : function(elm)
    {
        // システムボタンの追加
        if (currentWithBack) current.comp.addSystemButton(elm);
        current.addSystemButton(elm);
        return 0;
    } incontextof this,

    csysbutton : function(elm)
    {
        // システムボタンの消去
        if (currentWithBack) current.comp.clearSystemButtons();
        current.clearSystemButtons(elm);
        return 0;
    } incontextof this,

    timeout : function(elm)
    {
        // タイムアウト処理の登録(カレントのみ)
        current.addTimeout(elm);
        return 0;
    } incontextof this,

    ctimeout : function(elm)
    {
        // タイムアウト処理の解除（カレントのみ）
        current.clearTimeout();
        return 0;
    } incontextof this,

    wheel : function(elm)
    {
        // ホイール処理の登録(カレントのみ)
        current.addWheel(elm);
        return 0;
    } incontextof this,

    cwheel : function(elm)
    {
        // ホイール処理の解除（カレントのみ）
        current.clearWheel();
        return 0;
    } incontextof this,
                  
    click : function(elm)
    {
        // クリック処理の登録(カレントのみ)
        current.addClick(elm);
        return 0;
    } incontextof this,

    afterline : function(elm)
    {
        // ラインモードで行終了時に呼び出される処理
		if (!skipNoDisp) {
			if (historyWriteEnabled) {
				historyLayer.clearAction();
			}
        }
        return 0;
    } incontextof this,

	l : function(elm)
	{
		// 行クリック待ち
		return showLineBreak(elm);
	} incontextof this,

    afterpage : function(elm) {
		// ページ処理完了後ハンドラ
		if (!skipNoDisp) {
			if (voicecut && voicecutpage) {
				stopAllVoice();
			}
			if (historyWriteEnabled) {
				historyLayer.clearAction();
				historyLayer.reline();
			}
		}
		forEachEventHook('onAfterPage');
		stopAllActions();
		stopAllTransitions();
		if (skipNoDisp) {
			if (System.getTickCount() - kag.skipNoDispStartTime > 3000) {
				skipCount = (skipCount + 1) % 100;
				return skipCount == 0 ? -4 : 0;
			}
		}
		return 0;
    } incontextof this,

	p : function(elm)
	{
        // 自動ラベルモードは p タグにおいて有効

		if (!elm.nosave && !(erAfterPage && noErOnce)) {
			// 改ページ後消去が無効の場合は自動セーブしない
			// 選択肢などの前処理の部分で意味がある
			setAutoLabel();
		}

        // 自動インデント解除
        if (autoIndent) {
            tagHandlers.endindent(%[ pagebreak:true ]);
        }

        // ページクリック待ち
		if (!skipNoDisp) {
			if(historyWriteEnabled) historyLayer.reline(true);
		}
		var ret = _backVoiceMode ? 0 : showPageBreak(elm);
		_backVoiceMode = false;

		// ラインモード時のみ機能
		if (lineMode) {
			// ページ終了後に呼ばれるタグ
			insertTag("afterpage");
			// ページ待ち後に消去を強制投入
			if (erAfterPage) {
				if (!noErOnce) {
					insertTag("er", %[all:true]);
				}
				noErOnce = false;
			}
			// 遅延実行のキャンセル処理
			insertTag("delaydone");
		}
		
        return ret;

	} incontextof this,

	p2 : function(elm)
	{
		return showPageBreak(elm);
	} incontextof this,
				  
	current : function(elm)
	{
		// 操作対象のメッセージレイヤの指定
		setCurrentMessageLayer(elm);
		return 0;
	} incontextof this,

	position : function(elm)
	{
		// メッセージレイヤの位置、属性を設定
		getMessageLayerObjectFromElm(elm).setPosition(elm);
		return 0;
	} incontextof this,

	uiload : function(elm)
	{
		// メッセージレイヤにUIパーツを一括で読み込む
        if (elm.storage !== void) {
            global.uiload(getMessageLayerObjectFromElm(elm), elm);
        } else {
            error("uiload:ファイルが指定されていません");
        }
		return 0;
	} incontextof this,

	sysuiload : function(elm)
	{
		// メッセージレイヤにUIパーツを一括で読み込む
        if (elm.storage !== void) {
            global.uiloadSystem(getMessageLayerObjectFromElm(elm), elm);
        } else {
            error("uiload:ファイルが指定されていません");
        }
		return 0;
	} incontextof this,

	ct : function(elm)
	{
		// メッセージレイヤのリセット(すべてのメッセージレイヤのクリアと
		// current のリセット)
		if(historyWriteEnabled) historyLayer.repage();
		clearMessageLayers(true);
		return 0;
	} incontextof this,

	cm : function(elm)
	{
		// メッセージレイヤのリセットを行うが、ct のように
		// current のリセットは行わないもの
		if(historyWriteEnabled) historyLayer.repage();
		clearMessageLayers(false);
		return 0;
	} incontextof this,

	er : function(elm)
	{
		callHook("onEraceMessage", elm);
		if (!skipNoDisp) {
			// 現在のメッセージレイヤのクリア
			if(historyWriteEnabled) historyLayer.repage();
			if(currentWithBack) current.comp.clear(elm.all);
			current.clear(elm.all);
		}
		return 0;
	} incontextof this,

	preindent : function(elm)
	{
		if (!skipNoDisp) {
			var ch = elm.text;
			if (textWriteEnabled) {
				if(currentWithBack) current.comp.preIndent(ch);
				current.preIndent(ch);
			}
			if(historyWriteEnabled && typeof historyLayer.preIndent == "Object")
				historyLayer.preIndent();
		}
		return 0;
	} incontextof this,

	indent : function(elm)
	{
		if (!skipNoDisp) {
			// インデントの設定
			if (textWriteEnabled) {
				if(currentWithBack) current.comp.setIndent();
				current.setIndent();
			}
			if(historyWriteEnabled) historyLayer.beginIndent();
		}
		return 0;
	} incontextof this,

	endindent : function(elm)
	{
		if (!skipNoDisp) {
		// インデントの解除
			if (textWriteEnabled) {
				if(currentWithBack) current.comp.resetIndent();
				current.resetIndent();
			}
			if(historyWriteEnabled) historyLayer.endIndent(elm.pagebreak);
		}
		return 0;
	} incontextof this,

	delay : function(elm)
	{
		// 文字表示速度の指定
		setDelay(elm);
		return 0;
	} incontextof this,

	nowait : function(elm)
	{
		// 一時的にノーウェイトで実行
		enterNoWait();
		return 0;
	} incontextof this,

	endnowait : function(elm)
	{
		// nowait の解除
		leaveNoWait();
		return 0;
	} incontextof this,

	locate : function(elm)
	{
		// 文字表示位置を指定
		if(currentWithBack) current.comp.locate(elm.x, elm.y);
		current.locate(elm.x, elm.y);
		return 0;
	} incontextof this,

	glyph : function(elm)
	{
		// クリック待ち記号を指定
		current.setGlyph(elm);
		return 0;
	} incontextof this,

	locklink : function(elm)
	{
		// リンクのロック
		lockMessageLayerSelProcess();
		return 0;
	} incontextof this,

	unlocklink : function(elm)
	{
		// リンクのアンロック
		unlockMessageLayerSelProcess();
		return 0;
	} incontextof this,

	//----------------------------------------- タグハンドラ群(モーション操作) --

    // モーションの読み込み
	motionload : function(elm)
	{
		var message = getMessageLayerObjectFromElm(elm);
		if (elm.visible === void) {
			elm.visible = true;
		}
		message.loadMotion(elm);
		elm.target = message;
		return waitFunc(elm, waitMotion);
	} incontextof this,

    // モーション解放
    motiondone : function(elm)
	{
		var message = getMessageLayerObjectFromElm(elm);
		message.doneMotion();
		message.setPosition(%[visible:false]);
		return 0;
	} incontextof this,

    // モーションの状態変更
    motionopt : function(elm)
	{
		var message = getMessageLayerObjectFromElm(elm);
		message.setMotionOption(elm);
		elm.target = message;
		return waitFunc(elm, waitMotion);
	} incontextof this,

    // モーションの再描画
    motionredraw : function(elm)
	{
		var message = getMessageLayerObjectFromElm(elm);
		message.redrawMotion();
		return 0;
	} incontextof this,
				  
    // モーションの変数設定
    motionset : function(elm)
	{
		var message = getMessageLayerObjectFromElm(elm);
		message.setMotionVariable(elm.name, elm.value);
		return 0;
	} incontextof this,
				  
    // モーションにボタンを追加
    motionbutton : function(elm)
	{
		var message = getMessageLayerObjectFromElm(elm);
		message.addMotionButton(elm);
		return 0;
	} incontextof this,

	// モーション再生待ち
	wmotion : function(elm)
	{
		return waitMotion(elm);
	} incontextof this,

	//----------------------------------------- タグハンドラ群(システム操作) --

	loadplugin : function(elm)
	{
		// プラグインの読み込み
		Plugins.link(elm.module);
        if (debugLevel >= tkdlSimple)
            dm("プラグインを読み込みました : " + elm.module);
		return 0;
	} incontextof this,

	title : function(elm)
	{
		// タイトルの設定
		setTitle(elm.name);
		return 0;
	} incontextof this,

	s : function(elm)
	{
        // 実行停止
		stablePosibility = true;
		cancelSkip();
		if(!usingExtraConductor) incRecordLabel(true);
		inSleep = true;
		if(recordHistoryOfStore == 2) // 2 : 選択肢 ( @s タグ ) ごと
			setToRecordHistory();
		notifyStable();

        // メッセージレイヤタイムアウト呼び出し開始
        current.startTimeout();

        return -1;
	} incontextof this,

	clickskip : function(elm)
	{
        // クリックスキップの設定
		clickSkipEnabled = +elm.enabled;
		return 0;
	} incontextof this,

	noeffect : function(elm)
	{
        // noeffectの設定
		noeffectEnabled = +elm.enabled;
		return 0;
	} incontextof this,
				  
	nextskip : function(elm)
	{
		// 次の選択肢(/未読)まで進むの設定
		nextSkipEnabled = +elm.enabled;
		return 0;
	} incontextof this,

	cancelskip : function(elm)
	{
		// スキップの解除
		cancelSkip();
        setMenuAccessibleAll();
		return 0;
	} incontextof this,

	cancelautomode : function(elm)
	{
		// 「自動的に読み進む」の解除
		cancelAutoMode();
        setMenuAccessibleAll();
		return 0;
	} incontextof this,

	resetwait : function(elm)
	{
		// 時間原点の設定
		resetWait();
		return 0;
	} incontextof this,

	wait : function(elm)
	{
		// ウェイト
		return doWait(elm);
	} incontextof this,

	waituntil : function(elm)
	{
		// 特殊なウェイト
		return waitUntil(elm);
	} incontextof this,

	showwait : function(elm)
	{
		// ウェイト時間の表示
		dm("showwait:" + (System.getTickCount() - timeOrigin));
		return 0;
	} incontextof this,
				  
	td : function(elm)
	{
		// delay登録
		entryDelayCmd(elm);
		return 0;
	} incontextof this,

	wd : function(elm)
	{
		// delay待ち
		return waitDelay(elm.name, elm.canskip);
	} incontextof this,

	wc : function(elm)
	{
		// 指定文字分のウェイト
		return doWaitCh(elm);
	} incontextof this,

	waitclick : function(elm)
	{
		if (skipNoDisp) { return 0;	}
		// クリックを待つ
		return waitClick(elm);
	} incontextof this,

	rclick : function(elm)
	{
		// 右クリックの動作設定
		setRightClickOptions(elm);
		return 0;
	} incontextof this,

	history : function(elm)
	{
		// メッセージ履歴レイヤの設定
		setHistoryOptions(elm);
		return 0;
	} incontextof this,

	clearhistory : function(elm)
	{
        // メッセージ履歴レイヤの消去
        historyLayer.clear();
        historyOfStore.clear();
		nextRecordHistory = false;
        return 0;
	} incontextof this,
                  
	showhistory : function(elm)
	{
		// メッセージ履歴レイヤの表示
		return showHistoryByScenario(elm);
	} incontextof this,

	hr : function(elm)
	{
		// メッセージ履歴レイヤに改行を出力
		if(historyWriteEnabled)
		{
			if(elm.repage)
				historyLayer.repage();
			else if (!elm.pagebreak)
				historyLayer.reline();
			else {
				historyLayer.reline(true);
				historyLayer.repage();
			}
		}
		return 0;
	} incontextof this,

	hact : function(elm)
	{
		// メッセージ履歴にアクションを設定
		if(historyWriteEnabled)
			historyLayer.setNewAction(elm.exp);
		return 0;
	} incontextof this,

	endhact : function(elm)
	{
		// メッセージ履歴のアクションをクリア
		if(historyWriteEnabled)
			historyLayer.clearAction();
		return 0;
	} incontextof this,

	hidemessage : function(elm)
	{
		// メッセージを一時的に隠す
		return hideMessageLayerByScenario(elm);
	} incontextof this,

	quake : function(elm)
	{
		// 揺れ
		var ret = forEachFunctionHook("onQuake", elm);
		if (ret !== void) {
			return ret;
		}
		doQuake(elm);
		return waitFunc(elm, waitQuake);
	} incontextof this,

	stopquake : function(elm)
	{
		// 揺れの停止
		stopQuake();
		return 0;
	} incontextof this,

	wq : function(elm)
	{
		// 揺れの停止を待つ
		return waitQuake(elm);
	} incontextof this,

	autowc : function(elm)
	{
		// 自動ウェイト
		setAutoWait(elm);
		return 0;
	} incontextof this,

	cursor : function(elm)
	{
		// マウスカーソルの変更
		setCursor(elm);
		return 0;
	} incontextof this,

	close : function(elm)
	{
		// ウィンドウを閉じる
		closeByScript(elm);
		return -2;
	} incontextof this,

	copybookmark : function(elm)
	{
		// 栞をコピー
		copyBookMark(+elm.from, +elm.to);
		return 0;
	} incontextof this,

	erasebookmark : function(elm)
	{
		// 栞を削除
		eraseBookMark(+elm.place);
		return 0;
	} incontextof this,

	disablestore : function(elm)
	{
		// 栞を一時的に使用不可に
		tempDisableStore(elm);
		return 0;
	} incontextof this,

	store : function(elm)
	{
		// 栞の使用不可・使用可を設定する
		setStoreEnabled(+elm.enabled);
		return 0;
	} incontextof this,

	load : function(elm)
	{
		// 栞の読み込み
		if(elm.ask !== void && +elm.ask)
			loadBookMarkWithAsk(+elm.place);
		else
			loadBookMark(+elm.place);
		return -4;
	} incontextof this,

	save : function(elm)
	{
		// 栞の読み込み
		if(elm.ask !== void && +elm.ask)
			saveBookMarkWithAsk(+elm.place);
		else
			saveBookMark(+elm.place);
		return -4;
	} incontextof this,

	startanchor : function(elm)
	{
		// 「最初に戻る」の使用不可・使用可を設定する
		setStartAnchorEnabled(elm.enabled === void || +elm.enabled);
		return 0;
	} incontextof this,

	gotostart : function(elm)
	{
		// 「最初に戻る」
		if(elm.ask !== void && +elm.ask)
			goToStartWithAsk();
		else
			goToStart();
		return -4;
	} incontextof this,

	goback : function(elm)
	{
		// 通過記録を戻る
		if(elm.ask !== void && +elm.ask)
			goBackHistory(true);
		else
			goBackHistory(false);
		return -4;
	} incontextof this,

	record : function(elm)
	{
		// 通過記録をする
		setToRecordHistory();
		return 0;
	} incontextof this,

	tempsave : function(elm)
	{
		// 状態のメモリへの保存
		tempSave(+elm.place);
		return 0;
	} incontextof this,

	tempload : function(elm)
	{
		// 状態のメモリへの保存
		tempLoad(+elm.place, elm);
			//elm.se === void || +elm.se, elm.bgm === void || +elm.bgm,
			//elm.backlay !== void && +elm.backlay);
		return 0;
	} incontextof this,

	mappfont : function(elm)
	{
		// レンダリング済みフォントを現在のフォントにマッピング
		mapPrerenderedFont(elm.storage);
		return 0;
	} incontextof this,

	locksnapshot : function(elm)
	{
		// 画面のスナップショットをロックする
		lockSnapshot();
		return 0;
	} incontextof this,

	unlocksnapshot : function(elm)
	{
		// 画面のスナップショットのロックを解除する
		unlockSnapshot();
		return 0;
	} incontextof this,

	freesnapshot : function(elm)
	{
		// 画面のスナップショットのロックを強制解除する
		freeSnapshot();
		return 0;
	} incontextof this,

	//------------------------------------------- タグハンドラ群(レイヤ操作) --

	image : function(elm)
	{
		// 画像読み込み
		updateBeforeCh = 1;
		var start = System.getTickCount();
		getLayerFromElm(elm).loadImages(elm);
        if (debugLevel >= tkdlVerbose)
            dm(elm.storage + " の読み込みに " + (System.getTickCount() - start) + "ms かかりました");
		return 0;
	} incontextof this,

	img : function(elm)
	{
		// 画像読み込み(imageとおなじ)
		updateBeforeCh = 1;
		var start = System.getTickCount();
		getLayerFromElm(elm).loadImages(elm);
        if (debugLevel >= tkdlVerbose)
            dm(elm.storage + " の読み込みに " + (System.getTickCount() - start) + "ms かかりました");
		return 0;
	} incontextof this,

	pimage : function(elm)
	{
		// 部分追加画像読み込み
		getLayerFromElm(elm).loadPartialImage(elm);
		return 0;
	} incontextof this,

	ptext : function(elm)
	{
		// 背景/前景レイヤへの文字描画
		getLayerFromElm(elm).drawReconstructibleText(elm);
		return 0;
	} incontextof this,

    clearlayers : function(elm) {
        updateBeforeCh = 1;

		var base;
		if (elm !== void && elm.page == "back") {
            base   = back;
        } else {
            base   = fore;
        }
		
		clearLayers(base);
		
		return 0;
    } incontextof this,

    syncmsg : function(elm) {
        //dm("メッセージ窓シンクロ");
        // メッセージ窓だけ表から裏にコピー
        var messages = fore.messages;
        for(var i = messages.count-1; i >= 0; i--) messages[i].assignComp();
        return 0;
    } incontextof this,
                  
	freeimage : function(elm)
	{
		// 画像のクリア
		updateBeforeCh = 1;
		getLayerFromElm(elm).freeImage(elm);
		return 0;
	} incontextof this,

	animstart : function(elm)
	{
		// アニメーションの開始
		updateBeforeCh = 1;
		getLayerFromElm(elm).startAnim(elm);
		return waitFunc(elm, waitAnimation);
	} incontextof this,

	animstop : function(elm)
	{
		// アニメーションの停止
		updateBeforeCh = 1;
		getLayerFromElm(elm).stopAnim(+elm.seg);
		return 0;
	} incontextof this,

	wa : function(elm)
	{
		// アニメーションの停止待ち
		return waitAnimation(elm);
	} incontextof this,

	mapimage : function(elm)
	{
		// クリッカブルマップの領域画像を読み込む
		getLayerFromElm(elm).loadProvinceImage(elm.storage);
		return 0;
	} incontextof this,

	mapaction : function(elm)
	{
		// クリッカブルマップの領域アクション定義を読み込む
		getLayerFromElm(elm).loadProvinceActions(elm.storage);
		return 0;
	} incontextof this,

	mapdisable : function(elm)
	{
		// クリッカブルマップを無効にする
		getLayerFromElm(elm).clearProvinceActions();
		return 0;
	} incontextof this,

	backlay : function(elm)
	{
		// レイヤを裏画面にコピー
		updateBeforeCh = 1;
		backupLayer(elm, true);
		return 0;
	} incontextof this,

	forelay : function(elm)
	{
		// レイヤを表画面にコピー
		updateBeforeCh = 1;
		backupLayer(elm, false);
		return 0;
	} incontextof this,

	copylay : function(elm)
	{
		// 同種のレイヤ同士のコピー
		updateBeforeCh = 1;
		copyLayer(elm);
		return 0;
	} incontextof this,

	layopt : function(elm)
	{
		// レイヤのオプションを設定
		updateBeforeCh = 1;
        getLayerFromElm(elm).setOptions(elm);
		return 0;
	} incontextof this,

    laylevel : function(elm)
    {
        updateBeforeCh = 1;
        var base = (elm.page == 'back') ? back : fore;
		toLevel(elm.layer, elm.level, base);
		if (elm.front) {
			toFront(elm.layer, base);
		}
        return 0;
	} incontextof this,

    layfront : function(elm)
    {
        updateBeforeCh = 1;
        var base = (elm.page == 'back') ? back : fore;
        toFront(elm.layer, base);
        return 0;
	} incontextof this,

    layback : function(elm)
    {
        updateBeforeCh = 1;
        var base = (elm.page == 'back') ? back : fore;
        toBack(elm.layer, base);
        return 0;
	} incontextof this,

	trans : function(elm)
	{
		// トランジションの開始
		var layer = getLayerPageFromElm(elm, false);
		if (layer == fore.base)  {
			forEachEventHook('onPreTransition');
		}
		beginTransition(layer, elm);
		return waitFunc(elm, waitTransition);
	} incontextof this,

	wt : function(elm)
	{
		return waitTransition(elm);
	} incontextof this,

	stoptrans : function(elm)
	{
		// トランジションを停止する
		stopAllTransitions(true);
		return 0;
	} incontextof this,

	move : function(elm)
	{
		// 自動移動の開始
		getLayerFromElm(elm).beginMove(elm);
		return waitFunc(elm, waitMove);
	} incontextof this,

	wm : function(elm)
	{
		//  自動移動を待つ
		return waitMove(elm);
	} incontextof this,

	stopmove : function(elm)
	{
		// 自動移動を停止する
		stopAllMoves();
		return 0;
	} incontextof this,

	laycount : function(elm)
	{
		updateBeforeCh = 1;
		allocateCharacterLayers(+elm.layers) if elm.layers !== void;
		allocateMessageLayers(+elm.messages) if elm.messages !== void;
		return 0;
	} incontextof this,

    // アクション開始
    action : function(elm)
    {
		var target;
		if (elm.layer !== void) {
			target = getLayerFromElm(elm);
		} else if (typeof elm.target == "Object") {
			target = elm.target;
		} else if (typeof elm.target == "String") {
			target = Scripts.eval(elm.target);
		}
		if (typeof target == "Object") {
			var action;
			if (elm.action !== void) {
				// 直接アクション構造が指定されている場合
				if (elm.action instanceof "Dictionary") {
					action = %[];
					(Dictionary.assign incontextof action)(elm.action);
					action.hide   = elm.hide   if elm.hide   !== void;
					action.nowait = elm.nowait if elm.nowait !== void;
				} else {
					action = elm.action;
				}
			} else {
				// アクション構造をパラメータから生成
				action = %[];
				(Dictionary.assign incontextof action)(elm);
				delete action.tagname;
				delete action.layer;
				delete action.page;
				delete action.target;
			}
			if (typeof target.beginAction != "undefined") {
				target.beginAction(action);
			} else {
				beginAction(target, action, void, elm.nowait);
			}
		} else {
			error("action:アクションはオブジェクトに指定する必要があります");
		}
		return waitFunc(elm, waitAction);
    } incontextof this,

    // アクション停止
	stopaction : function(elm)
	{
		updateBeforeCh = 1;
        var target;
		if (elm.layer !== void) {
			target = getLayerFromElm(elm);
		} else if (typeof elm.target == "Object") {
			target = elm.target;
		} else if (typeof elm.target == "String") {
			target = Scripts.eval(elm.target);
		}
		if (target !== void) {
			if (typeof target == "Object") {
				stopAction(target);
			} else {
				error("stopaction:アクションはオブジェクトに指定する必要があります");
			}
		} else {
			stopAllActions(true);
		}
        return 0;
	} incontextof this,

	wact : function(elm)
	{
		return waitAction(elm);
	} incontextof this,

	wat : function(elm)
	{
		return waitActionAndTransition(elm);
	} incontextof this,

	//------------------------------ タグハンドラ群(効果音・BGM・ビデオ操作) --

	playbgm : function(elm)
	{
        // BGM の演奏
		bgm.play(elm);
        clearBgmStop();
        clearBgmLabel();
        return 0;
	} incontextof this,

	stopbgm : function(elm)
	{
		// BGM の停止
		bgm.stop();
		return 0;
	} incontextof this,

	pausebgm : function(elm)
	{
		// BGM の一時停止
		bgm.pause();
		return 0;
	} incontextof this,

	resumebgm : function(elm)
	{
		// BGM の再開
		bgm.resume();
		return 0;
	} incontextof this,

	fadeinbgm : function(elm)
	{
		// BGM のフェードイン
		bgm.fadeIn(elm);
		return waitFunc(elm, waitBGMFade);
	} incontextof this,

	fadeoutbgm : function(elm)
	{
		// BGM のフェードアウト
		bgm.fadeOut(elm);
		return waitFunc(elm, waitBGMStop);
	} incontextof this,

	fadepausebgm : function(elm)
	{
		// BGM のフェードポーズ
		bgm.fadePause(elm);
		return waitFunc(elm, waitBGMFade);
	} incontextof this,
                  
	fadebgm : function(elm)
	{
		// BGM の指定音量までのフェード
		bgm.fade(elm);
		return waitFunc(elm, waitBGMFade);
	} incontextof this,

	xchgbgm : function(elm)
	{
		// BGM の入れ替え/クロスフェード
		bgm.exchange(elm);
        clearBgmStop();
        clearBgmLabel();
		return waitFunc(elm, waitBGMFade);
	} incontextof this,

	bgmopt : function(elm)
	{
		// BGM のオプション設定
		bgm.setOptions(elm);
		return 0;
	} incontextof this,

    setbgmstop : function(elm)
	{
		// BGM の終了時コールバックの登録
        setBgmStop(elm);
        return 0;
	} incontextof this,

    clearbgmstop : function(elm)
	{
		// BGM の終了時コールバックの削除
        clearBgmStop();
        return 0;
	} incontextof this,

    setbgmlabel : function(elm) {
        // BGM のラベル時コールバックの登録
        setBgmLabel(elm);
        return 0;
    } incontextof this,

    clearbgmlabel : function(elm)
	{
        // BGM のラベル時コールバックの削除
        clearBgmLabel();
        return 0;
	} incontextof this,
                  
	wb : function(elm)
	{
		// BGM のフェード終了待ち
		return waitBGMFade(elm);
	} incontextof this,

	wl : function(elm)
	{
		// BGM の再生終了待ち
		return waitBGMStop(elm);
	} incontextof this,

	wbl : function(elm)
	{
		// BGM のラベル待ち
		return waitBGMLabel(elm);
	} incontextof this,
				  
	playse : function(elm)
	{
        // 効果音の再生
		se[+elm.buf].play(elm);
		return 0;
	} incontextof this,

	stopse : function(elm)
	{
		// 効果音の停止
		return commonSEmethod(elm, "stop");
	} incontextof this,

	fadeinse : function(elm)
	{
		// 効果音のフェードイン再生
		se[+elm.buf].fadeIn(elm);
		return waitFunc(elm, waitSEFade);
	} incontextof this,

	fadeoutse : function(elm)
	{
		// 効果音のフェードアウト
		commonSEmethod(elm, "fadeOut", elm);
		return waitFunc(elm, waitSEStop);
	} incontextof this,

	fadese : function(elm)
	{
		// 効果音のフェード
		return commonSEmethod(elm, "fade", elm);
		return waitFunc(elm, waitSEFade);
	} incontextof this,

	seopt : function(elm)
	{
		// 効果音のフェード
		return commonSEmethod(elm, "setOptions", elm);
	} incontextof this,

	wf : function(elm)
	{
		// 効果音のフェード終了待ち
		return waitSEFade(elm);
	} incontextof this,

	ws : function(elm)
	{
		// 効果音の再生終了待ち
		return waitSEStop(elm);
	} incontextof this,

	// -----------------------------------------------------

    playvoice : function(elm) {
		// ボイス直接再生指定
		var mode = elm.mode !== void ? +elm.mode : true;
		var type = +elm.type;
		var loop = +elm.loop;
		if (!skipMode || type != 0) {
			if (elm.name !== void) {
				var name = elm.name;
				var voice = elm.voice !== void ? elm.voice : getCurrentVoice(name);
				stopVoice(name);
				if (loop) {
					startLoopVoice(name, Scripts.clone(voice), type);
				} else {
					playVoice(name, getPlayVoiceFilename(name, voice), type, false, mode);
				}
			}
		}
		return waitFunc(elm, waitVoice);
	} incontextof this,

	stopvoice : function(elm) {
		// ボイスの停止
		if (elm.name !== void) {
			stopAllVoice(elm.time, elm.all);
		} else {
			stopVoice(elm.name, elm.time, elm.all);
		}
		return 0;
	} incontextof this,

	waitvoice : function(elm) {
		// ボイス再生待ち
		return elm.name !== void ? waitVoice(elm) : waitAllVoice(elm);
	} incontextof this,

    linevoice : function(elm) {
		var voices = playLineVoice(elm.voice, !skipMode);
		if (voices !== void && historyWriteEnabled) {
			historyLayer.setNewAction(getVoicePlayingScript(voices));
		}
		return 0;
	} incontextof this,

    "*" => function(elm) {
		return doTalkWait(elm);
	} incontextof this,

	// -----------------------------------------------------

	video : function(elm)
	{
		// ムービーのオプションを設定する
		movies[+elm.slot].setOptions(elm);
		return 0;
	} incontextof this,

	playvideo : function(elm)
	{
		// ムービーを再生する
		movies[+elm.slot].play(elm.storage);
		return waitFunc(elm, waitMovieStop);
	} incontextof this,

	stopvideo : function(elm)
	{
		// ムービーを停止する
		movies[+elm.slot].stop();
		return 0;
	} incontextof this,

	openvideo : function(elm)
	{
		// ムービー再生の準備をする
		movies[+elm.slot].open(elm.storage);
		return 0;
	} incontextof this,

	wv : function(elm)
	{
		var target;
		if (elm.layer !== void) {
			if (elm.layer == "all") {
				return waitAllLayerMovies(elm.canskip, +elm.wait);
			}
			target = getLayerFromElm(elm);
		} else if (typeof elm.target == "Object") {
			target = elm.target;
		} else if (typeof elm.target == "String") {
			target = Scripts.eval(elm.target);
		}
		if (target !== void) {
			if (typeof target == "Object" && typeof target.moviePlaying != "undefined") {
				if (target != fore.base && target != back.base) {
					return waitLayerMovie(target, elm.canskip, +elm.wait);
				}
			} else {
				dm("動画待ちはレイオブジェクトに指定する必要があります");
			}
		} else {
			// ムービーの再生終了を待つ
			return waitMovieStop(elm);
		}
		return 0;
	} incontextof this,

// Start: Add: T.Imoto
	wp : function(elm)
	{
		// ムービーのピリオドイベントを待つ
		return waitMoviePeriod(elm);
	} incontextof this,

	pausevideo : function(elm)
	{
		movies[+elm.slot].pause();
		return 0;
	} incontextof this,

	resumevideo : function(elm)
	{
		movies[+elm.slot].resume();
		return 0;
	} incontextof this,

	preparevideo : function(elm)
	{
		movies[+elm.slot].prepare();
		return 0;
	} incontextof this,

	rewindvideo : function(elm)
	{
		movies[+elm.slot].rewind();
		return 0;
	} incontextof this,

	videolayer : function(elm)
	{
		movies[+elm.slot].storeLayer( elm.layer, elm.page, elm.channel );
		movies[+elm.slot].setVideoLayer(getLayerFromElm(elm),elm);
		return 0;
	} incontextof this,

	clearvideolayer : function(elm)
	{
		movies[+elm.slot].cancelLayer( elm.channel );
		movies[+elm.slot].setVideoLayer(null,elm);
		return 0;
	} incontextof this,

	videosegloop : function(elm)
	{
		movies[+elm.slot].setSegment(elm);
		return 0;
	} incontextof this,

	cancelvideosegloop : function(elm)
	{
		movies[+elm.slot].cancelSegmentLoop();
		return 0;
	} incontextof this,

	videoevent : function(elm)
	{
		movies[+elm.slot].setPeriod(elm);
		return 0;
	} incontextof this,

	cancelvideoevent : function(elm)
	{
		movies[+elm.slot].cancelPeriodEvent();
		return 0;
	} incontextof this,
// End: Add: T.Imoto

	//--------------------------------------- タグハンドラ群(変数・TJS 操作) --

	eval : function(elm)
	{
		// 式の評価
		Scripts.eval(elm.exp);
		return 0;
	} incontextof this,

	set : function (elm) {
		// 変数の設定 ※文字列しか設定できないのに注意
		if (elm.name !== void && elm.value !== void) {
			var name  = elm.name;
			var value = elm.value;
			var l = name.length;
			if (l > 3 && name.substr(0,3) == "tf.") {
				tf[name.substr(3)] = value;
			} else if (l > 3 && name.substr(0,3) == "sf.") {
				sf[name.substr(3)] = value;
			} else if (l > 2 && name.substr(0,2) == "f.") {
				f[name.substr(2)] = value;
			}
		}
		return 0;
	} incontextof this,

	sflag : function (elm) {
		dm(@"システムフラグ設定:${elm.name}");
		var name = (elm.prefix !== void) ? elm.prefix + elm.name : elm.name;
		sf[name] = true;
		return 0;
	} incontextof this,
				  
	func : function (elm) {
		// 関数の呼び出し ※パラメータは全て文字列になるので注意
		if (elm.name !== void) {
			var func = Scripts.eval(elm.name);
			if (typeof func == "Object" && func instanceof "Function") {
				func(elm);
			}
		}
		return 0;
	} incontextof this,
				  
	trace : function(elm)
	{
		// 式のトレース表示
		var exp = elm.exp;
		var result = Scripts.eval(exp);
        
        if (result instanceof "Dictionary") {
            dm("▼[trace] expression=\"" + exp + "\"");
            var names = [];
            names.assign(result);
            for (var i=0; i<names.count; i+= 2) {
                var name = names[i];
                var value = names[i+1];
                dm("▼[trace] name=\"" + name + "\" type of value=" + typeof value + 
                   " value=" + value);
            }
        } else if (result instanceof "Array") {
            dm("▼[trace] expression=\"" + exp +  "\"");
            for (var i=0; i<result.count; i++) {
                var value = result[i];
                dm("▼[trace] idx=\"" + i + "\" type of value=" + typeof value + 
                   " value=" + value);
            }
        } else {
            dm("▼[trace] expression=\"" + exp + "\" type of result=" + typeof result + 
               " result=" + result);
        }
		return 0;
	} incontextof this,

	input : function(elm)
	{
		// 文字列の入力
		inputString(elm);
		return 0;
	} incontextof this,

	clearsysvar : function(elm)
	{
		// システム変数のクリア
		clearSystemVariables();
		return 0;
	} incontextof this,

	clearvar : function(elm)
	{
		// ゲーム変数のクリア
		clearVariables();
		return 0;
	} incontextof this,

	clearplaytime : function(elm)
	{
        // プレイ時間のクリア
		clearPlayTime();
		return 0;
	} incontextof this,

	waittrig : function(elm)
	{
		// トリガを待つ
		return waitTrigger(elm);
	} incontextof this,

	clearallmacro : function(elm)
	{
		// 全マクロのクリア
		(Dictionary.clear incontextof conductor.macros)();
		return 0;
	} incontextof this,

	//----------------------------------------- タグハンドラ群(選択肢) --

    seladd : function(elm)
    {
		if (f.selectInfos == void || f.selectInfos.count == 0) {
			forEachFunctionHook("onParseSelect");
		}
		addSelect(elm);
        return 0;
	} incontextof this,

    select : function(elm)
    {
		// 選択肢がひとつも何も無い場合はそのままスルー
		if (f.selectInfos === void || !f.selectInfos.count) {
			dm("選択肢がありませんでした。");
			f.selectDoneFlag = true;
			return 0;
		}

		// 選択肢が一つになる場合は force 指定がなければそのままスルーする
		if (f.selectInfos.count == 1 && !elm.force) {
			elm = f.selectInfos[0];
			if (elm.exp != "") {
				Scripts.eval(elm.exp);
			}
			doneSelect(elm);
			return 0;
		}
		
		with (selectLayer) {
			var msgoff = false, msgon = false;
			if      (elm.msgoff) msgoff = true;
			else if (elm.msgon ) msgon  = true;
			else {
				msgoff = typeof .msgoff != "undefined" && !!.msgoff;
				msgon  = typeof .msgon  != "undefined" && !!.msgon;
			}
			// メッセージ窓の状態を変更
			if ((msgoff && setCurrentMessageLayerVisible(false)) ||
				(msgon  && setCurrentMessageLayerVisible(true))) return -3;
		}

		// select開始時フック
		forEachEventHook("onSelectStart",,f.selectInfos);

		f.selectDoneFlag = true;

		if (skipNoDisp && skipMode < SKIP_FORCE) {
			cancelSkip();
		}
		
		if (recordHistoryOfStore == 3) { // 3 : 選択肢 ( @{m}select タグ ) ごと
			setToRecordHistory();
		}

		selectPrevAutoMode = autoMode;
		selectPrevSkipMode = skipMode;
		selectPrevTempSkip = _tempAllskip;

		if (skipMode < SKIP_FORCE) {
			cancelSkip();
        }
        cancelAutoMode();
        
        f.selDoneStorage = elm.storage if elm.storage !== void || elm.target !== void;
        f.selDoneTarget  = elm.target  if elm.target  !== void;

		var r = showSelect();
		if (typeof r == "Integer") return r;

		stablePosibility = true;
		notifyStable();
		conductor.sleep();
		conductor.interrupt();
		setMenuAccessibleAll();
		return -1;
	} incontextof this,

	selstop : function(elm) {
		selectLayer.onSelectStop() if (typeof selectLayer.onSelectStop == "Object");
		stablePosibility = true;
		notifyStable();
		conductor.sleep();
		conductor.interrupt();
		setMenuAccessibleAll();
		return -1;
	} incontextof this,

	selopt : function(elm)
	{
		setSelectOptions(elm);
		return 0;
	} incontextof this,

	seldone : function(elm)
	{
		if (autoLabelMode) {
			autoLabelCount++;
			storeAutoLabel();
		}
		if (f.selDoneStorage != "" || f.selDoneTarget != "") {
			insertTag("next", %[storage:f.selDoneStorage, target:f.selDoneTarget]);
			return 0;
		} else {
			return tagHandlers.s(elm);
		}
	} incontextof this,

	doneselect : function(elm)
	{
		hideSelect();
		afterSelect();
		doneSelect(elm);
		return 0;
	} incontextof this,

	//----------------------------------------- タグハンドラ群(選択肢) --

    mselinit : function(elm)
    {
        initMapSelect(elm);
        return 0;
	} incontextof this,

    mselbutton : function(elm)
    {
        addMapSelectButton(elm);
        return 0;
	} incontextof this,

    mselpos : function(elm)
    {
        addMapSelectPosition(elm);
        return 0;
	} incontextof this,

    mseladd : function(elm)
    {
        addMapSelect(elm);
        return 0;
	} incontextof this,

    mselect : function(elm)
    {
        if (mapSelectLayer.msgoff && setCurrentMessageLayerVisible(false)) {
            // 処理待ち
            return -3;
        }

		if (skipNoDisp && skipMode < SKIP_FORCE) {
			cancelSkip();
		}
		
		if (recordHistoryOfStore == 3) { // 3 : 選択肢 ( @{m}select タグ ) ごと
			setToRecordHistory();
		}

		selectPrevAutoMode = autoMode;
		selectPrevSkipMode = skipMode;
		selectPrevTempSkip = _tempAllskip;

        if (skipMode < SKIP_FORCE) {
            cancelSkip();
        }
        cancelAutoMode();

        stablePosibility = true;
        notifyStable();

        showMapSelect();

        conductor.sleep();
        conductor.interrupt();
		setMenuAccessibleAll();
        return -1;
	} incontextof this,

	mselopt : function(elm)
	{
        setMapSelectOptions(elm);
		return 0;
	} incontextof this,

	donemapselect : function(elm)
	{
        hideMapSelect();
		afterSelect();
		if (elm.storage != '' || elm.target != '') {
			insertTag("next", %[storage:elm.storage, target:elm.target]);
		}
		return 0;
	} incontextof this,

	//----------------------------------------- タグハンドラ群(シナリオ内分岐) --

    bradd : function(elm)
    {
		return addBranch(elm);
	} incontextof this,

    branch : function(elm)
    {
		return doBranch(elm);
	} incontextof this,

    brdone : function(elm)
    {
		return doneBranch(elm);
	} incontextof this,

	//----------------------------------------- タグハンドラ群(特殊分岐処理) --

	// タグの内部処理で行われる分岐はここに集約される
	next : function(elm)
	{
		if (autoLabelMode) {
			autoLabelCount++;
			storeAutoLabel();
		}
		//dm("next条件:" + elm.eval);
		if ((elm.eval == "" || Scripts.eval(elm.eval)) &&
			(elm.confirm == "" || System.confirm(elm.confirm, elm.caption != "" ? elm.caption : "確認", this))) {
			if (elm.exp !== void) {
				Scripts.eval(elm.exp);
			}
			if (elm.storage !== void || elm.target !== void) {
				if (forEachFunctionHook("onNext", elm.storage, elm.target) === void) {
					// プラグインで分岐が処理されなかった場合はKAGの遷移を行う
					checkSkipOnNextLabel = true; // 飛び先で既読ラベルの自動スキップチェックを行う
					process(elm.storage, elm.target);
				}
			}
		}
		return 0;
	} incontextof this,

    // storage指定時は next と原則等価だがプラグインに横取りされない。何も指定しなければ gotostart と等価
	exit : function(elm)
	{
		if (autoLabelMode) {
			autoLabelCount++;
			storeAutoLabel();
		}
		//dm("exit条件:" + elm.eval);
		if (elm.eval == "" || Scripts.eval(elm.eval)) {
			if (elm.exp !== void) {
				Scripts.eval(elm.exp);
			}
			if (elm.storage !== void || elm.target !== void) {
				process(elm.storage, elm.target);
				return 0;
			} else {
				goToStart();
				return -4;
			}
		}
		return 0;
	} incontextof this,

	//----------------------------------------- タグハンドラ群(特殊スキップ処理) --

    beginskip : function(elm)
	{
		if (clickSkipEnabled) {
			if (prevSkipMode !== void) {
				throw new Exception("beginskipは入れ子できません!!!");
			} else {
				// 現在のスキップモードを記録
				enableLeftBeginSkip = (elm.eval == "" || Scripts.eval(elm.eval));
				if (!enableLeftBeginSkip) {
					// 強制でキャンセル
					cancelSkip();
				}
				prevSkipMode = skipMode;
				if (skipMode) {
					// 既にスキップしてた場合は停止できないスキップ状態にする
					skipMode = SKIP_CANCEL;
				}
			}
		}
        return 0;
	} incontextof this,

    endskip : function(elm)
	{
		if (prevSkipMode !== void) {
			if (prevSkipMode == SKIP_NONE) {
				cancelSkip();
			} else {
				skipMode = prevSkipMode;
			}
			prevSkipMode = void;
		}
        return 0;
	} incontextof this,

	//----------------------------------------------- 自動ラベル処理 --

    autolabel : function(elm)
	{
		doAutoLabel();
		return 0;
	} incontextof this,

	//----------------------------------------------- 回想モード --

    recollection : function(elm)
	{
        startRecollection(elm);
        return 0;
	} incontextof this,

    stoprecollection : function(elm)
    {
        stopRecollection(elm);
        return 0;
    } incontextof this,
                  
    endrecollection : function(elm)
	{
        return endRecollection();
	} incontextof this,

	//----------------------------------------------- レイヤトランジション --

	ltbegin : function(elm)
	{
		return layerTransBegin(elm);
	} incontextof this,

	ltend : function(elm)
	{
		return layerTransEnd(elm);
	} incontextof this,

	//----------------------------------------------- 暗転特殊トランジション --

	btbegin : function(elm)
	{
		return blackTransBegin(elm);
	} incontextof this,

	btend : function(elm)
	{
		return blackTransEnd(elm);
	} incontextof this,


	//----------------------------------------- タグハンドラ群(選択肢) --

    panel : function(elm)
    {
		if (elm.class != "") {
			hidePanel();
			var clobj = Scripts.eval(elm.class);
			panelLayer = new clobj(this, elm.name !== void ? elm.name : "panel", elm);
			dm("パネルレイヤ表示");
			panelLayer.absolute = (elm.absolute !== void) ? +elm.absolute : panelLayerAbsolute;
			panelLayer.visible = true;
			panelLayer.setMode();
			panelLayer.focus();
			panelLayer.onOpen();
			panelShowing = true;
			panelModal   = panelLayer.modal;

			if (elm.nostop) {
				return 0;
			}

			stablePosibility = true;
			notifyStable();
			conductor.sleep();
			conductor.interrupt();
			setMenuAccessibleAll();
			return -1;
		} else if (panelShowing) {
			if (elm.action != "" && typeof panelLayer[elm.action] != "undefined" &&
				(elm.name == "" || elm.name == panelLayer.name))
				return (panelLayer[elm.action])(elm);
		}
		return 0;
	} incontextof this,

	donepanel : function(elm)
	{
		hidePanel();
		setMenuAccessibleAll();
		return 0;
	} incontextof this,

	nowaitmode : function(elm)
	{
		nowaitMode = true;
		nowaitModeWait = elm.wait !== void ? +elm.wait : autoModePageWait;
		_textWriteEnabled = elm.text if elm.text !== void;
		return 0;
	} incontextof this,

	notextmode : function(elm)
	{
		_textWriteEnabled = (elm.enabled !== void) ? !elm.enabled : false;
		return 0;
	} incontextof this,
				  
    cancelnowaitmode : function(elm)
	{
		nowaitMode = false;
		_textWriteEnabled = true;
		return 0;
	} incontextof this,
				  
	//----------------------------------------------- squirrel --

    sqexec : function(elm)
	{
		execSquirrel(elm);
		return 0;
	} incontextof this,
				  
    sqfork : function(elm)
	{
		if (forkSquirrel(elm) !== void) {
			return waitFunc(elm, waitSquirrel);
		}
		return 0;
	} incontextof this,

    stopsq : function(elm)
	{
		return stopSquirrel(elm);
	} incontextof this,
				  
    wsq : function(elm)
	{
		return waitSquirrel(elm);
	} incontextof this,

	//----------------------------------------------- タグハンドラ群の終わり --

		interrupt : function(elm) { return -2; } incontextof this ];
	}

	//------------------------------------------------------ 特殊変数処理 --
    // システム画面用に簡易参照できるメソッド・プロパティを追加

	/**
	 * グローバル WaveSound 音量の設定
	 * 100 段階設定
	 */
	property wavevolume {
		getter()  {
			return scflags.waveVolume !== void ? +scflags.waveVolume : global.WaveSoundBuffer.globalVolume / 1000;
		}
		setter(v) {
			v = +v;
			if      (v < 0)   v = 0;
			else if (v > 100) v = 100;
			scflags.waveVolume = v;
			global.WaveSoundBuffer.globalVolume = scflags.waveEnable ? v * 1000 : 0;
		}
	}

	/**
	 * グローバル WaveSound の有効値の設定
	 * true / false
	 */
	property waveenable {
		getter() {
			return scflags.waveEnable !== void ? +scflags.waveEnable : true;
		}
		setter(v) {
			scflags.waveEnable = +v;
			wavevolume = scflags.waveVolume;
		}
	}

	/**
	 * BGM 有効値の設定
	 * true / false
	 */
	property bgmenable {
		getter()  { return getSoundProperty("bgm", "enable", true); }
		setter(v) {        setSoundProperty("bgm", "enable", !!v);
			/**/           bgm.restoreSystemState(scflags); forEachEventHook('onBgmVolumeChange'); }
	}

	/**
	 * BGM ボリューム値の設定
	 * 100段階指定
	 */
	property bgmvolume {
		getter()  { return getSoundProperty("bgm", "globalVolume", 100000) / 1000; }
		setter(v) {        setSoundProperty("bgm", "globalVolume",      v  * 1000);
			/**/           bgm.restoreSystemState(scflags); forEachEventHook('onBgmVolumeChange'); }
	}

	/**
	 * 自動下げに対応したBGMボリューム値(読み取り専用)
	 */
	property downbgmenable {
		getter()  { return bgmenable; }
	}
	property downbgmvolume {
		getter()  { return bgmvolume * bgmdownvalue; }
	}

	// 下げ制御用プロパティ
	var _bgmdownvalue = 1.0;
	property bgmdownvalue  {
		getter() { return _bgmdownvalue; }
		setter(v) {
			if (v != _bgmdownvalue) {
				_bgmdownvalue = v;
				bgm.restoreSystemState(scflags); forEachEventHook('onBgmVolumeChange');
			}
		}
	}
	
	/**
	 * BGM 有効値の設定
	 * true / false
	 */
	property seenable {
		getter()  { return getSEGroupProperty("enable", true); }
		setter(v) {        setSEGroupProperty("enable", !!v); forEachEventHook('onSEVolumeChange'); }
	}

	/**
	 * SE ボリューム値の設定
	 * 100段階設定
	 */
	property sevolume {
		getter()  { return getSEGroupProperty("globalVolume", 100000) / 1000;  }
		setter(v) {        setSEGroupProperty("globalVolume",       v * 1000); forEachEventHook('onSEVolumeChange'); }
	}


	// システムSE有効/音量
	property _sysse_group { getter { return sameSysSEVolume ? void : "sysse"; } }
	property sysseenable {
		getter()  { return getSEGroupProperty("enable", true, _sysse_group); }
		setter(v) {        setSEGroupProperty("enable", !!v,  _sysse_group); }
	}
	property syssevolume {
		getter()  { return getSEGroupProperty("globalVolume", 100000,   _sysse_group) / 1000; }
		setter(v) {        setSEGroupProperty("globalVolume", v * 1000, _sysse_group);        }
	}

	function doSEGroup(grp, callback, *) {
		var flags = scflags.se;
		if (flags === void) scflags.se = flags = [];
		var from = 0, to = -1; // to : 終端+1
		switch (grp) {
		case void: case "":
			from = 0;
			to   = numSEBuffers;
			to  += numSysSEBuffers if (sameSysSEVolume);
			break;
		case "sysse":
			from = numSEBuffers;
			to   = numSEBuffers + numSysSEBuffers;
			break;
		default:
			// 拡張できるようにする？
		}
		if (from >= to) return void;
		//dm("doSEGroup", grp, from, to);
		for (var i = from; i < to; i++) {
			if (se[i] === void) continue;
			var dic = flags[i];
			if (dic === void) flags[i] = dic = %[];
			if (callback(se[i], dic, *)) break;
		}
	}

	function setSEGroupProperty(prop, value, grp) {
		doSEGroup(grp, function (se, flag, prop, value) {
			flag[prop] = value;
			se.restoreSystemState(scflags);
		} incontextof this, prop, value);
		callHook("onSoundPropertyChanged", (grp == "" ? "se" : grp), prop, value);
	}

	function getSEGroupProperty(prop, defval, grp) {
		var dic = %[];
		doSEGroup(grp, function (se, flag, prop, dic) {
			if (flag[prop] !== void) dic.result = flag[prop];
			return true;
		} incontextof this, prop, dic);
		return (typeof dic.result !== "undefined") ? dic.result : defval;
	}

	// サウンド関連の設定読み出し／変更
	function getSoundProperty(tag, prop, defval) {
		var table = scflags[tag];
		return (table !== void && table[prop] !== void) ? table[prop] : defval;
	}
	function setSoundProperty(tag, prop, value) {
		var table = scflags[tag];
		if (table === void) table = scflags[tag] = %[];
		table[prop] = value;
		callHook("onSoundPropertyChanged", tag, prop, value);
	}


	/**
	 * ボイス有効値の設定
	 * true / false
	 */
	property voiceenable {
		getter()  { return getSoundProperty("voice", "enable", true); }
		setter(v) {        setSoundProperty("voice", "enable", !!v);  }
	}

	/**
	 * ボイスの代表ボリューム
	 * 100段階設定
	 */
	property voicevolume {
		getter()  { return getSoundProperty("voice", "globalVolume", 100000) / 1000; }
		setter(v) {        setSoundProperty("voice", "globalVolume",      v  * 1000); resetAllVoiceVolume(); }
	}

	// {get/set}Voice{On/Volume} で渡されるnameを正規化
	function _voiceNameFilter(name) {
		@if (DEBUG)
			// scflags.voice を使いまわすので下記の名前が競合する（まず使わないだろうけど）
			if (name == "enable" || name == "globalVolume")
				errorCmd("{get/set}Voice{On/Volume}: ボイス名に予約語が指定されました");
		@endif
		return (name != "") ? name : "etc";
	}

	/**
	 * 指定された名前のキャラのボイスが有効かどうか
	 */
	function getVoiceOn(name) {
		return getSoundProperty("voiceon", _voiceNameFilter(name), true);
	}

	/**
	 * 指定された名前のキャラのボイスを有効にする
	 */
	function setVoiceOn(name, on=true) {
		setSoundProperty("voiceon", _voiceNameFilter(name), !!on);
	}

	/**
	 * 指定された名前のキャラのボイスの音量を取得する
	 * @param name キャラ名
	 * @param rawvalue trueならvoicevolumeの補正をしない
	 */
	function getVoiceVolume(name, rawvalue = false) {
		var vol = getSoundProperty("voice", _voiceNameFilter(name), 100000) / 1000;
		return rawvalue ? vol : (vol * voicevolume / 100);
	}

	/**
	 * 指定された名前のキャラのボイスの音量を変更する
	 */
	function setVoiceVolume(name, vol) {
		setSoundProperty("voice", _voiceNameFilter(name), vol * 1000);
		forEachEventHook('onVoiceVolumeUpdate');
		resetAllVoiceVolume();
	}

	/**
	 * BGV有効値の設定
	 * true / false
	 */
	property bgvenable {
		getter()  { return getSoundProperty("bgv", "enable", true); }
		setter(v) {        setSoundProperty("bgv", "enable", !!v);  }
	}
	property bgv2enable {
		getter()  { return getSoundProperty("bgv2", "enable", true); }
		setter(v) {        setSoundProperty("bgv2", "enable", !!v);  }
	}

	/**
	 * BGVの代表ボリューム
	 * 100段階設定
	 */
	property bgvvolume {
		getter()  { return getSoundProperty("bgv", "globalVolume", 100000) / 1000;  }
		setter(v) {        setSoundProperty("bgv", "globalVolume",      v  * 1000); resetAllVoiceVolume(); }
	}
	property bgv2volume {
		getter()  { return getSoundProperty("bgv2", "globalVolume", 100000) / 1000;  }
		setter(v) {        setSoundProperty("bgv2", "globalVolume",      v  * 1000); resetAllVoiceVolume(); }
	}

	/**
	 * 指定された名前のキャラのループボイスの音量を取得
	 */
	function getBGVVolume (name) { return bgvvolume  * getVoiceVolume(name, true) / 100; }
	function getBGV2Volume(name) { return bgv2volume * getVoiceVolume(name, true) / 100; }

	/**
	 * ムービー音声の有効値の設定
	 * true / false
	 */
	property movieAudioEnable {
		getter()  { return !independentMovieAudioProperty ? bgmenable :
			/**/    getSoundProperty("movie", "enable", true); }
		setter(v) { setSoundProperty("movie", "enable", !!v);  }
	}

	/**
	 * ムービー音声のボリューム
	 * 100段階設定
	 */
	property movieAudioVolume {
		getter()  { return !independentMovieAudioProperty ? bgmvolume :
			/**/    getSoundProperty("movie", "globalVolume", 100000) / 1000; }
		setter(v) { setSoundProperty("movie", "globalVolume",      v  * 1000); }
	}
	// [XXX] music object用alias
	property movieaudiovolume { getter { return movieAudioVolume; } setter(v) { movieAudioVolume = v; } }
	property movieaudioenable { getter { return movieAudioEnable; } setter(v) { movieAudioEnable = v; } }


    /**
     * テキスト速度制御
     * 0〜_textSpeedMax で指定 (_textSpeedMaxが最速)
     */
	var _textSpeedMax = 10;
    property textspeed {
        getter() {
			var max = _textSpeedMax;
            return max - (userChSpeed / (chSpeeds.slow / max));
        }
        setter(v) {
			var max = _textSpeedMax;
            userChSpeed = (max - v) * (chSpeeds.slow / max);
			callHook("onTextSpeedChanged", textspeed);
            if (userChSpeed <= chSpeeds.fast) {
                if (typeof this.chFastMenuItem != "undefined") {
                    this.chFastMenuItem.checked = true;
                }
            } else if (userChSpeed <= chSpeeds.normal) {
                if (typeof this.chNormalMenuItem != "undefined") {
                    this.chNormalMenuItem.checked = true;
                }
            } else {
				if (typeof this.chSlowMenuItem != "undefined") {
                    this.chSlowMenuItem.checked = true;
                }
            }
        }
    }

    /**
     * 自動再生時待ち時間制御
     * 0〜_autoSpeedMaxで指定 (_autoSpeedMaxが最速）
     */
	var _autoSpeedMax = 10;
    property autospeed {
        getter() {
			var max = _autoSpeedMax;
            return max - (autoModePageWait / (autoModePageWaits.slow / max));
        }
        setter(v) {
			var max = _autoSpeedMax;
            autoModePageWait = (int)((max - v) * (autoModePageWaits.slow / max));
            autoModeLineWait = (int)((max - v) * (autoModeLineWaits.slow / max));
			callHook("onAutoSpeedChanged", autospeed);
            if (autoModePageWait <= autoModePageWaits.fast) {
                if (typeof this.autoModeFastMenuItem != "undefined") {
                    this.autoModeFastMenuItem.checked = true;
                }
            } else if (autoModePageWait <= autoModePageWaits.faster) {
                if (typeof this.autoModeFasterMenuItem != "undefined") {
                    this.autoModeFasterMenuItem.checked = true;
                }
            } else if (autoModePageWait <= autoModePageWaits.medium) {
                if (typeof this.autoModeMediumMenuItem != "undefined") {
                    this.autoModeMediumMenuItem.checked = true;
                }
            } else if (autoModePageWait <= autoModePageWaits.slower) {
                if (typeof this.autoModeSlowerMenuItem != "undefined") {
                    this.autoModeSlowerMenuItem.checked = true;
                }
            } else {
                if (typeof this.autoModeSlowMenuItem != "undefined") {
                    this.autoModeSlowMenuItem.checked = true;
                }
            } 
        }
    }

	function updateEffectButton() {
		forEachEventHook('onUpdateEffect');
		if (noeffect) {
			if (typeof this.noeffectMenuItem != "undefined") {
				this.noeffectMenuItem.checked = true;
			}
		} else {
			if (typeof this.effectMenuItem != "undefined") {
				this.effectMenuItem.checked = true;
			}
		}
		if (typeof this.effectCheckMenuItem != "undefined") {
			this.effectCheckMenuItem.checked = !noeffect;
		}
	}
	
    /**
     * 描画速度 (トランジションの再生倍率を指定する）
     */
    property drawspeed {
        getter() {
			if (!noeffectEnabled || scflags.drawspeed === void) {
				return 1.0;
			}
			return scflags.drawspeed;
        }
        setter(v) {
			scflags.drawspeed = v;
			updateEffectButton();
        }
    }

    /**
	 * スキップ時の描画速度補正
	 */
	property skipspeed {
		getter() {
			if (scflags.skipspeed === void) {
				return 0;
			}
			return scflags.skipspeed;
		}
		setter(v) {
			scflags.skipspeed = v;
			updateEffectButton();
        }
    }

	property actskipspeed {
		getter() {
			return skipMode > SKIP_STOP ? 0 : skipMode ? skipspeed : 1.0;
		}
	}

	// 効果補正用
	property effectspeed {
		getter() {
			if (skipNoDisp) {
				return 0;
			} 
			var draw = noeffectEnabled ? (noeffect ? 0 : drawspeed) : 1.0;
			return draw * actskipspeed;
		}
	}

	// 時間補正用
	property waitspeed {
		getter() {
			if (skipNoDisp) {
				return 0;
			} 
			var draw = noeffectEnabled ? drawspeed : 1.0;
			return draw * actskipspeed;
		}
	}
	

	/**
     * エフェクトの有効・無効（トランジション補正と連動）
     */
    property noeffect {
        getter() {
			if (scflags.noeffect === void) {
				return false;
			}
			return scflags.noeffect;
        }
        setter(v) {
			scflags.noeffect = v;
			updateEffectButton();
		}
    }

    /**
     * ボイスの再生倍速
     */
    property voicespeed {
		getter() {
			if (scflags.voicespeed === void) {
                return 1.0;
            }
            return scflags.voicespeed;
        }
        setter(v) {
			if (scflags.voicespeed != v) {
				scflags.voicespeed = v;
				resetAllVoiceSpeed();
				updateVoiceSpeedMenu(v);
			}
        }
    }

	function updateVoiceSpeedMenu(value = this.voicespeed)
	{
		// ボイス用速度制御用メニューのチェックを更新
		if(typeof this.voiceSpeedMenu !== "undefined") {
			var children = voiceSpeedMenu.children;
			for (var i = children.count-1; i >= 0; i--) {
				var item = children[i];
				if (typeof item.speed !== "undefined" && item.speed == value) {
					item.checked = true;
					break;
				}
			}
		}
	}
	function onVoiceSpeedMenuItemClick(sender)
	{
		//sender.checked = true; //※updateVoiceSpeedMenuにて設定される
		voicespeed = sender.speed;
	}

    // ----------------------------------------------------------
    // エラーログ処理
    // ----------------------------------------------------------

    // デバッグレベル初期値
    var _debugLevel = tkdlNone;
    
    /**
     * デバッグレベル
     */
    property debugLevel {
        setter(v) {
            _debugLevel = v;
            if (mainConductor !== void) {
                mainConductor.debugLevel = v;
                extraConductor.debugLevel = v;
            }
        }
        getter() {
            return _debugLevel;
        }
    }

	function onDebugLevelNoneMenuItemClick(sender)
	{
        debugLevel = tkdlNone;
        if(typeof this.debugLevelNoneMenuItem != "undefined")
            debugLevelNoneMenuItem.checked = true;
        saveSystemVariables();
	}

	function onDebugLevelSimpleMenuItemClick(sender)
	{
        debugLevel = tkdlSimple;
        if(typeof this.debugLevelSimpleMenuItem != "undefined")
            debugLevelSimpleMenuItem.checked = true;
        saveSystemVariables();
	}

    function onDebugLevelVerboseMenuItemClick(sender)
	{
        debugLevel = tkdlVerbose;
        if(typeof this.debugLevelVerboseMenuItem != "undefined")
            debugLevelVerboseMenuItem.checked = true;
        saveSystemVariables();
	}
    
	var logMode = false;
	var logPad = void;
	var cmdLog = [];
	var imageLog = [];
	var soundLog = [];
	var voiceLog = [];

    function error(msg) {
        if (debugLevel >= tkdlSimple) dm(msg);
    }
    
	function errorLine(target, msg) {
		var info = getCurrentInfo();
		var filename = info.file;
		var line     = info.line;
		var shortfn  = Storages.extractStorageName(filename);
		var fullfn   = filename != "" ? Storages.getPlacedPath(filename) : void;
		if (fullfn != "") {
			var exename  = System.exePath.toLowerCase();
			if (fullfn.substring(0,exename.length).toLowerCase() == exename) {
				fullfn = fullfn.substring(exename.length);
			}
		}
		callHook("onErrorLineHook", target, msg, filename, line);
		var shortmsg = "%s:%d: %s".sprintf(shortfn, line, msg);
		if (debugLevel >= tkdlSimple) dm(shortmsg);
		if (logMode && target !== void) {
			var fullmsg  = "%s:%d: %s".sprintf(fullfn, line, msg);
			target.add(fullmsg);
			if (logPad !== void) with (logPad) {
				var stat = "上が最新ログ";
				.text = "" if (.statusText != stat);
				.text = shortmsg + "\n" +.text;
				.statusText = stat;
				.visible = true;
			}
		}
    }

    /**
	 * 未知の命令
     */
	function errorCmd(msg) {
		errorLine(cmdLog, msg);
    }

	
    /**
     * 画像ファイルがロードできない場合
     */
    function errorImage(msg) {
        errorLine(imageLog, msg);
    }

    /**
     * ボイスファイルがロードできない場合
     */
    function errorSound(msg) {
        errorLine(soundLog, msg);
    }

    /**
     * ボイスファイルがロードできない場合
     */
    function errorVoice(msg) {
        errorLine(voiceLog, msg);
    }

    /**
     * ログを初期化する
     */
    function initLog() {
		cmdLog.clear();
		imageLog.clear();
        soundLog.clear();
        voiceLog.clear();
    }
	function initLogPad(pos = false, noclear = false) {
		if (logPad === void) {
			logPad = new Pad();
			with (logPad) {
				.borderStyle = bsSizeToolWin;
				.color = 0;
				.fontColor = 0xFFFFFF;
				.fontFace = "ＭＳ ゴシック"; //chDefaultFace;
				.readOnly = false;
				.wordWrap = true;
				.showScrollBars = ssVertical;
				.height = 10;
				.title = "KAGEXログ";
				if (typeof .registerExEvent == "Object" &&
					typeof this.chDebugLogMenuItem == "Object") {
					.onClose = function {
						if (!isvalid this) return;
						onChDebugLogMenuItemClick(chDebugLogMenuItem) if (logMode);
					} incontextof this;
					.registerExEvent();
				}
			}
		}
		with (logPad) {
			if (pos) {
				.width  = width;
				.height = scHeight\2;
				.left   = left;
				.top    = top + height;
			} else {
				.width = scWidth;
				.height = scHeight\2;
			}
			.text = "" if (!noclear);
			.statusText = "上が最新ログ";
			.visible = true;
		}
	}

    /**
     * ログを出力する
     */
    function outputLog() {
        if (logMode) {
			cmdLog.save(Debug.logLocation + "/cmderror.log");
			imageLog.save(Debug.logLocation + "/imageerror.log");
			soundLog.save(Debug.logLocation + "/sounderror.log");
			voiceLog.save(Debug.logLocation + "/voiceerror.log");
			if (logPad !== void) with (logPad) {
				.text = (
					"■コマンドエラー\n" + cmdLog  .join("\n") + "\n\n" +
					"■イメージエラー\n" + imageLog.join("\n") + "\n\n" +
					"■サウンドエラー\n" + soundLog.join("\n") + "\n\n" +
					"■ボイスエラー\n"   + voiceLog.join("\n") + "\n\n");
				.statusText = "出力結果";
				.visible = true;
			}
			initLog();
        }
    }

    /**
     * ダイアログを表示する
     */
	var _errorInformMessageMap = %[
	savefail: "ファイルに保存できません (ファイルを開けないか、書き込み禁止です)",
	loadfail:  "他のシステムのデータを読み込もうとしました",
	loadforce:  %[ message:"セーブデータIDが違いますがデバッグモードのため強行して読み込みます", caption:"Warning" ],
	loadexception: %[ exp:function(mes) { return @"栞を読み込めないか、栞が壊れているか、あるいは他の形式の栞データです(${mes})"; } ],
	initexception: %[ exp:function(mes) { return @"システム変数データを読み込めないか、壊れています(${mes})\n全て初期化して起動します"; } ],
	savemydoc: %[ message:"書き込み権限がなくファイル保存できません。MyDocument以下にデータを保存します", caption:"Information" ],
	readonly:  %[ message:"書き込み権限がなくファイル保存できません。ReadOnlyMode で動作します",          caption:"Warning" ],
		];
	function errorInform(tag, *) {
		if (tag == "" || _errorInformMessageMap[tag] == "") return;
		var msg = _errorInformMessageMap[tag];
		var cap = "Error";
		if (typeof msg == "Object") with (msg) {
			cap = .caption if (typeof .caption != "undefined");
			msg = .exp(*)  if (typeof .exp     != "undefined");
			msg = .message if (typeof .message != "undefined");
		}
		internalErrorInform(msg, cap);
	}
	function internalErrorInform(msg, cap) {
		System.inform(msg, cap + ":" + System.title);
	}

    // -------------------------------------------------------------

	// 実行中情報の取得
	// 実行中ストレージ、行番号、ラベル、行文字列などなど
	function getCurrentInfo() {
		var info = forEachFunctionHook("getCurrentInfo");
		if (info === void) {
			with (mainConductor) {
				info = %[
				file:.curStorage,
				line:.curLine,
				label:.runLabel,
				lineStr:.runLineStr,
					];
			}
		}
		return info;
	}
	
    /**
     * デバッグ窓表示内容の更新
     */
	function updateDebugInfo() {
		if (_debugwin !== void && debugwin.visible) {
			var info = getCurrentInfo();
			var filename = info.file;
			var exename  = System.exePath;
			if (filename.substring(0,exename.length) == exename) {
				filename = filename.substring(exename.length);
			}
			var voicename = "";
			if (hasCurrentVoice) {
				for (var i=0;i<currentVoice.count;i++) {
					voicename += currentVoice[i].name + ":" + currentVoice[i].voice + ",";
				}
			}
			var title = "%06d:%s %s %s".sprintf(info.line, filename, info.label, voicename);
			debugwin.fillRect(0, 0, debugwin.width, debugwin.height, 0xa0000000);
			debugwin.drawText(10, 10, title , 0xffffff);
			debugwin.drawText(10, 30, info.lineStr, 0xffffff);
        }
		if (skipNoDispWin !== void) {
			skipNoDispWin.visible = true;
			var title = "次の選択肢までスキップ中:%s".sprintf(getSkipNoDispText());
			skipNoDispWin.fillRect(0, 0, skipNoDispWin.width, skipNoDispWin.height, 0xa0000000);
			skipNoDispWin.drawText(10, 10, title , 0xffffff);
		}
    }
	function getSkipNoDispText() { return currentPageName; }

    /**
     * デバッグ窓の ON/OFF
     */
    function onDebugWinMenuItemClick(sender)
	{
        debugwin.visible = !debugwin.visible;
		callHook("onDebugWinVisibleChanged", debugwin.visible);
        if(typeof this.debugWinMenuItem != "undefined")
            debugWinMenuItem.checked = debugwin.visible;
        if (debugwin.visible) {
            updateDebugInfo();
        }
        saveSystemVariables();
	}

    // -------------------------------------------------------------

	// 外部制御
	function onMessageReceived(key, mes) {
		if (key == System.title) {
			Debug.notice("外部制御メッセージを受信："+mes);
			distributeReceivedMessage(mes);
		}
	}
	var     _debugSendMessageArgumentApplyed;
	function debugSendMessageArgumentApply() {
		var mes = System.getArgument("-sendmessage");
		if (   !_debugSendMessageArgumentApplyed && mes != "") {
			/**/_debugSendMessageArgumentApplyed = true;
			Debug.notice("起動オプションから制御メッセージを受信："+mes);
			distributeReceivedMessage(mes);
		}
	}
	function distributeReceivedMessage(mes) {
		callHook("onMessageReceivedHook", mes);
	}

    
    // -------------------------------------------------------------
    
    // 全鑑賞モード （一部システムがフラグとしてのみ利用）
    var allseen = false;

    // -------------------------------------------------------------

	/**
	 * システムのリセット（不完全）
	 */
	function resetAll() {
		forEachEventHook('onResetAll');

		if (isMain) {
			loadCharacterVoiceInfo();
			loadSoundGainInfo();
			loadFileGainInfo();
		}
		
		(Dictionary.clear incontextof pflags)();
		if (startAnchorEnabled) {
			goToStart();
			startAnchorEnabled = true;
		} else  {
			clearLayers(fore);
			restoreFlags();
		}

		stablePosibility = true;
		cancelSkip();
		inSleep = true;
		notifyStable();

		conductor.sleep();
		conductor.interrupt();

		setMenuAccessibleAll();

		standPSDLevel = void;
	}

	// -------------------------------------------------------------
	// モーション出力用処理
	// -------------------------------------------------------------

	/**
	 * モーションを出力する
	 * @param elm パラメータ
	 *  storage モーションデータファイル
	 *  chara キャラ指定
	 *  motion モーション指定
	 *  store 格納ファイル名(省略時は元データに連番付与)
	 *  time 記録時間。省略時は終了まで
	 *  width 出力画像サイズ横幅
	 *  height 出力画像サイズ縦幅
	 *  diff 時間差分
	 *  speed 速度
	 *  start 開始時間
	 */
	function exportMotion(elm) {
		Plugins.link("layerExSave.dll") if (typeof global.Layer.saveLayerImageTlg5 != "Object");
		
		var player = new Motion.Player(motion_manager.resourceManager);
		var target = new global.Layer(this, primaryLayer);

		var width  = elm.width !== void ? +elm.width : scWidth;
		var height = elm.height !== void ? +elm.height : scHeight;
		with (target) {
			.setSize(width, height);
			.fillRect(0,0,.width,.height,.neutralColor);
		}
		motion_manager.load(elm.storage);
		player.completionType = stFastLinear;
		player.setCoord(target.width/2, target.height/2);
		if (elm.zoom !== void) {
			player.setZoom(+elm.zoom/100, +elm.zoom/100);
		}
		player.chara = elm.chara;
		player.play(elm.motion, elm.flags !== void ? +elm.flags : Motion.PlayFlagForce);
		player.speed = +elm.speed if elm.speed !== void;
		player.tickCount = +elm.start if elm.start !== void;

		var store = elm.store === void ? elm.motion : elm.store;
		store += "%04d.png";
		
		var n    = 0;
		var tick = 0;
		var last = elm.time !== void ? +elm.time : player.lastTime;
		var diff = elm.diff !== void ? +elm.diff : 100;

		player.progress(0);
		while (tick <= last) {
			//dm("tick:%d/%d".sprintf(tick, last));
			player.draw(target);
			target.saveLayerImagePng(store.sprintf(n));
			player.clear(target, target.neutralColor);
			player.progress(diff);
			tick += diff;
			n++;
		}
		motion_manager.unload(elm.storage);
		player.stop();
		invalidate player;
		invalidate target;
	}

	// -------------------------------------------------------------
	// squirrel制御系
	// -------------------------------------------------------------

	var prevSqTick;
	var squirrelThreads = [];

	// スレッド一覧からスレッドを探す
	function findSqThread(name) {
		for (var i=squirrelThreads.count-1;i>=0;i--) {
			var thinfo = squirrelThreads[i];
			if (thinfo.name == name) {
				return thinfo.th;
			}
		}
	}

	function execSquirrel(elm) {
		Plugins.link("squirrel.dll") if (typeof global.Scripts.execStorageSQ != "Object");
		if (elm.storage !== void) {
			Scripts.execStorageSQ(elm.storage, kag, elm);
		} else if (elm.exp !== void) {
			Scripts.execSQ(elm.exp, kag, elm);
		}
	}
	
	/**
	 * squirrelスレッドを実行する
	 * @param name 名前指定
	 * @param elm 引数情報
	 */
	function forkSquirrel(elm) {
		if (elm.name !== void) {
			Plugins.link("squirrel.dll") if (typeof global.Scripts.forkStorageSQ != "Object");
			var th;
			if (elm.storage !== void) {
				th = Scripts.forkStorageSQ(elm.storage, kag, elm);
			} else if (elm.exp !== void) {
				th = Scripts.forkSQ(elm.exp, kag, elm);
			}
			if (th !== void) {
				squirrelThreads.add(%[name:elm.name, th:th]);
				if (Scripts.getThreadCountSQ() == 1) {
					System.addContinuousHandler(driveSquirrel);
					prevSqTick = System.getTickCount();
				}
			}
			return th;
		}
	}

	// squirrelスレッド駆動処理
	function driveSquirrel(tick) {
		if (Scripts.driveSQ(tick - prevSqTick, onSquirrelThreadDone) == 0) {
			System.removeContinuousHandler(driveSquirrel);
		}
		prevSqTick = tick;
	}
	
	// squirrel 終了処理
	function doneSquirrel() {
		if (squirrelThreads.count > 0) {
			stopAllSquirrel();
			driveSquirrel(System.getTickCount());
		}
	}
	
	// 指定したsquirrelスレッドを終了させる
	// 実際に削除されるのは次の実行時
	function stopSingleSquirrel(name) {
		var th = findSqThread(name);
		if (th !== void) {
			th.exit(-1);
		}
	}

	// 全squirrelスレッドを終了させる
	function stopAllSquirrel() {
		for (var i=squirrelThreads.count-1;i>=0;i--) {
			var thinfo = squirrelThreads[i];
			thinfo.th.exit(-1);
		}
		squirrelThreads.clear();
	}

	// squirrel の実行を終了させる
	function stopSquirrel(elm) {
		if (elm.name !== void) {
			stopSingleSquirrel(elm.name);
		} else {
			stopAllSquirrel();
		}
		return 0;
	}
	
	/**
	 * squirrelのスレッド実行が終了したときに呼び出される処理
	 */
	function onSquirrelThreadDone(th) {
		for (var i=squirrelThreads.count-1;i>=0;i--) {
			var thinfo = squirrelThreads[i];
			if (Scripts.compareSQ(th, thinfo.th) == 0) {
				// 特定スレッド終了のトリガ
				trigger("sqthread:" + thinfo.name);
				squirrelThreads.erase(i);
			}
		}
		// 全スレッド終了
		if (squirrelThreads.count == 0) {
			trigger("sqthread");
		}
	}

	/**
	 * squirrelのスレッド実行を待つ
	 */
	function waitSingleSquirrel(target, canskip=false, wait=0) {
		var th = findSqThread(target);
		if (th === void || th.getStatus() == 0) {
			return 0;
		}
		var name = 'sqthread:' + target;
		if((canskip !== void && +canskip) && clickSkipEnabled)
		{
			// スキップできる場合
			if(skipMode)
			{
				// スキップ動作中
				stopSingleSquirrel(target);
				return 0; // 自動移動を停止させてすぐに返る
			}
			conductor.wait(%[
				click : function (target)
				{
                    updateBeforeCh = 1;
					stopSingleSquirrel(target);
				} incontextof this,
				'click_arg' => target, // ハンドラへの引数
				name => function(arg)
				{
					updateBeforeCh = 1;
					return arg;
				} incontextof this,
				name + "_arg" => wait
				]);
		}
		else
		{
			conductor.wait(%[
				name => function(arg)
				{
					updateBeforeCh = 1;
					return arg;
				} incontextof this,
				name + "_arg" => wait
				]);
		}
		return -2;
	}

	/**
	 * squirrelのスレッド実行を全て待つ
	 */
	function waitAllSquirrel(canskip=false, wait=0)	{
		if (squirrelThreads.count == 0) return 0;
		if((canskip !== void && +canskip) && clickSkipEnabled)
		{
			if(skipMode)
			{
				stopAllSquirrel();
				return 0;
			}
			conductor.wait(%[
				click : function
				{
					updateBeforeCh = 1;
					stopAllSquirrel();
				} incontextof this,
			    sqthread : function(arg)
				{
					updateBeforeCh = 1;
					return arg;
				} incontextof this,
				sqthread_arg : wait
				]);
		}
		else
		{
			conductor.wait(%[
				sqthread : function(arg)
				{
					updateBeforeCh = 1;
					return arg;
				} incontextof this,
				sqthread_arg : wait
				]);
		}
		return -2;
	}
	
	// squirrel の実行を待つ
	function waitSquirrel(elm) {
		if (elm.name !== void) {
			return waitSingleSquirrel(elm.name, elm.canskip, +elm.wait);
		} else {
			return waitAllSquirrel(elm.canskip, +elm.wait);
		}
		return 0;
	}

	// -------------------------------------------------------------
	// モーションパネル
	// -------------------------------------------------------------



	// -------------------------------------------------------------
	// standlayer用
	// -------------------------------------------------------------

	// 立ち絵PSD情報キャッシュ
	var standPSDInfoList = [];
	var standPSDLevel;

	// 立ち絵レベル情報取
	function getStandPSDLevel() {
		if (standPSDLevel === void) {
			standPSDLevel = Scripts.evalStorage("standlevel.tjs");
		}
		return standPSDLevel;
	}

	/**
	 * 立ち絵PSD情報一括キャッシュ処理
	 */
	function getStandPSDInfo(standInfo, storage) {
		var psdinfo;
		var count = standPSDInfoList.count;
		for (var i=0;i<count;i++) {
			var info = standPSDInfoList[i];
			if (info.info == standInfo && info.storage == storage) { // 既にロード済み
				psdinfo = info;
				if (i != 0) {
					// 先頭に移動させる
					standPSDInfoList.erase(i); standPSDInfoList.unshift(psdinfo);
				}
				break;
			}
		}
		if (psdinfo === void) {
			// 見つからない場合は新規に作成
			psdinfo = new StandPSDInfo(this, standInfo, storage);
			while (standPSDInfoList.count > 10) { // キャッシュは10まで
				standPSDInfoList.pop();
			}
			standPSDInfoList.unshift(psdinfo);
		}
		return psdinfo;
	}

	function getStandPSDLayer(standInfo, storage) {
		return new StandLayer(this, getStandPSDInfo(standInfo, storage));
	}

	// -------------------------------------------------------------
	// ゲイン補正用
	// -------------------------------------------------------------

	var _soundGainInfo = %[]; //< ファイルに対するゲイン
	var _soundGainConfig = %[]; //< 識別IDに対するゲイン

	// モジュールからの問いあわせ
	function querySoundGain(url) {
		try {
			var name;
			with (Storages) name = .chopStorageExt(.extractStorageName(((string)url).toLowerCase())); // 名前を正規化
			var info = _soundGainInfo[name];
			if (info !== void) {
				//dm(@"gain query:${name} gain:${info[1]}");
				return info[1];
			}
			return 0;
		} catch(e) {
			Debug.notice(e.message);
		}
	}

	/**
	 * ゲイン情報のコンフィグの読み込み
	 */
	function loadSoundGainInfo(filename="soundgain.csv") {
		(Dictionary.clear incontextof _soundGainConfig)();
		var temp = [];
		var sep = (Storages.extractStorageExt(filename).toLowerCase() == ".txt") ? "\t" : ",";
		temp.load(filename);
		for (var i = 0; i < temp.count; i++) {
			var line = [];
			line.split(sep, temp[i]);
			var count = line.count;
			var name = line[0];
			if (count >= 2 && name.charAt(0) != '#') {
				_soundGainConfig[name] = +line[1];
				//dm(@"ゲイン情報登録:${name}:${line[1]}");
			}
		}
	}

	function loadFileGainInfo(filename="filegain.csv") {
		var temp = [];
		var sep = (Storages.extractStorageExt(filename).toLowerCase() == ".txt") ? "\t" : ",";
		temp.load(filename);
		for (var i = 0; i < temp.count; i++) {
			var line = [];
			line.split(sep, temp[i]);
			var count = line.count;
			var name = line[0];
			if (count >= 2 && name.charAt(0) != '#') {
				// 複数回よまれるとカウント上書きで消えなくなるがそれはそれでOK
				var gain = +line[1];
				with (Storages) name = .chopStorageExt(.extractStorageName(((string)name).toLowerCase())); // 名前を正規化
				var info = _soundGainInfo[name];
				if (info === void) {
					_soundGainInfo[name] = [1,gain];
				} else {
					info[0]++;
					info[1] = gain;
				}
			}
		}
	}
	
	/**
	 * 指定のファイルのゲイン情報を登録
	 * @param filename ファイル名
	 * @param gain ゲイン値またはサウンドコンフィグ名
	 */
	function addFileSoundGain(filename, gain=0) {
		gain = _soundGainConfig[gain] if typeof gain == "String" && gain != "";
		gain = 0 if gain == "";
		var name;
		with (Storages) name = .chopStorageExt(.extractStorageName(((string)filename).toLowerCase())); // 名前を正規化
		var info = _soundGainInfo[name];
		if (info === void) {
			_soundGainInfo[name] = [1,gain];
			//dm(@"ファイルゲイン登録:${name}:${gain}");
		} else {
			// カウントだけあげる。初期状態の調整はいじらない
			info[0]++;
		}
	}

	/**
	 * 指定のファイルのゲイン情報を削除
	 * @param filename ファイル名
	 */
	function removeFileSoundGain(filename) {
		var name;
		with (Storages) name = .chopStorageExt(.extractStorageName(((string)filename).toLowerCase())); // 名前を正規化
		if (name != "") {
			var info = _soundGainInfo[name];
			if (info !== void) {
				if (--info[0] == 0) {
					delete _soundGainInfo[name];
					//dm(@"ファイルゲイン削除:${name}");
				}
			}
		}
	}

	function addVoiceGain(filename, name) {
		if (name != "" && filename != "") {
			if (_soundGainConfig[name] === void) {
				var info = characterVoiceInfo[name];
				if (info !== void) {
					name = info.config;
				}
			}
			addFileSoundGain(filename, name);
		}
	}

	function removeVoiceGain(filename) {
		removeFileSoundGain(filename);
	}
}


// TJS スクリプトはここで終わり
"
END_OF_TJS_SCRIPT
# "; /*

# assign でコピーすべき変数の再生成を行う perl スクリプト

open FH, "MainWindow.tjs" or die;
undef($/);
$content = <FH>;

$list_store = '';
$list_restore = '';
while($content =~ /\/\*C\*\/var\s+(\w+)/gs)
{
	$list_store .= "\t\tf.$1 = $1;\n";
	$list_restore .= "\t\t$1 = f.$1 if f.$1 !== void;\n";
}

$content =~
s/\t\t\/\/ \[start_store_vars\]\n.*?\t\t\/\/ \[end_store_vars\]/\t\t\/\/ \[start_store_vars\]\n$list_store\t\t\/\/ \[end_store_vars\]/s;
$content =~
s/\t\t\/\/ \[start_restore_vars\]\n.*?\t\t\/\/ \[end_restore_vars\]/\t\t\/\/ \[start_restore_vars\]\n$list_restore\t\t\/\/ \[end_restore_vars\]/s;

open FH, ">MainWindow.tjs" or die;
print FH $content;


# */
