module yamalib.draw.scenariodraw;

private import std.utf;
private import std.file;
private import std.path;
private import std.string;	// atoi
private import std.conv;

private import y4d_draw.screen;
private import y4d_draw.drawbase;
private import y4d_draw.texture;
private import y4d_draw.textureloader;
private import y4d_draw.scenariodraw; 
private import y4d_draw.transbltter;
private import y4d_draw.fontrepository;
private import y4d_draw.sprite;

private import y4d_sound.sound;

private import y4d_aux.direnumerator;
private import y4d_aux.filesys;
private import y4d_aux.widestring;
private import y4d_aux.lineparser;

private import y4d_sound.soundloader;

private import y4d_input.mouse;
private import y4d_input.keyinputbase;

private import y4d_timer.fixtimer;

private import ytl.vector;
private import ytl.y4d_result;

private import kyojintati4d.val.kyojinconst;
private import kyojintati4d.bookmark;
private import kyojintati4d.userinfo;
private import kyojintati4d.taskoption;

private import yamalib.auxil.properties;
private import yamalib.draw.textdraw;
private import yamalib.draw.prmptcarsor;
private import yamalib.gui.dialog;
private import yamalib.gui.guibutton;
private import yamalib.character;
private import yamalib.counterfsp;
private import yamalib.log.log;


class ScenarioDrawEx {
	
	// エフェクトタイプの列挙
	static const char[][] effect = [
		"e01","e02","e03","e04","e05","e06","e07","e08","e09","e10",
		"e11","e12","e13","e14","e15","e16","e17","e18","e19","e20",
		"e21","e22","e23","e24"
	];

	// キャラ立ち位置のプリセットID
	static const char[][] presetPos = [
			"left",
			"center",
			"right",
	];
	
	/// Opstion情報の設定
	void setOptionInf(OptionInfo optionInfo_) {
		this.optionInfo = optionInfo_;
		setOptionInfo();
	}
	
	/// TextDrawContextの設定
	void setTextDrawContext( TextDrawContext con ) {
		context = con;
	}

	/// コンテクストを補足してシナリオ情報オブジェクトを設定する	
	void setScenarioInfo(ScenarioInfo si) {
	 	currentScenarioInfo = si;
	}
	
	/// TextDrawContextで設定している値からのオフセット値の設定
	void setOffset(int x, int y) {
		offsetx.set(x,x,0);
		offsety.set(y,y,0);
	}
	
	/// 背景とキャラクターのオフセット位置
	void setBgCharaOffset(int x, int y) {
		bgOffsetx.set(x,x,0);
		bgOffsety.set(y,y,0);
		foreach (inout CharaSprite c; charaSprite) {
			c.setOffset(x,y);
		}
	}
	
	/// BGテクスチャローダの設定
	void setBGLoader( TextureLoader tl_bg ) {
		this.tl_bg = tl_bg;
	}
	TextureLoader getBGLoader() {
		return this.tl_bg;
	}
	
	/// シルエットテクスチャローダの設定
	void setSilhouetteLoader( TextureLoader tl_shil ) {
		this.tl_shil = tl_shil;
	}
	TextureLoader getSilhouetteLoader() {
		return this.tl_shil;
	}
	
	/// BGMローダの設定
	void setBGMLoader( SoundLoader bgmLoader_) {
		this.bgmLoader = bgmLoader_;	
	}
	SoundLoader getBGMLoader() {
		return this.bgmLoader;
	}
	
	/// SEローダの設定
	void setSELoader(SoundLoader seLoader) {
		this.seLoader = seLoader;
	}
	SoundLoader getSELoader() {
		return this.seLoader;
	}
	
	/// VoiceLoaderの設定
	void setVoiceLoader(SoundLoader voiceLoader) {
		this.voiceLoader = voiceLoader;
	}
	SoundLoader getVoiceLoader() {
		return this.voiceLoader;
	}
	/// フォントローダの設定
	void setFontLoader(FontLoader fl, int textFontNo_, int rubiFontNo_) {
		this.fontLoader = fl;
		this.textFontNo = textFontNo_;
		this.rubiFontNo = rubiFontNo_;
	}
	FontLoader getFontLoader() {
		return this.fontLoader;
	}
	
	/// プロンプトカーソルを設定します
	void setPromptCarsor(Texture t) {
		promptCarsor = new PromptCarsor();
		promptCarsor.setTexture(t);
	}
	
	void setNewPageSprite(SpriteEx sprite) {
		this.newPageSprite = sprite;
	}
	
	/// マウスのセット
	void setMouse( MouseInput mouse_ ) {
		this.mouse = mouse_;
	}
	
	/// Keyのセット
	void setKey(KeyInputBase key_) {
		this.key = key_;
	}
	
	/// 背景暗幕フィルタを使用するかどうか
	void setBackFillter(bool b) {
		m_enableBackFillter = b;
	}
	bool isBackFillter() {
		return this.m_enableBackFillter;
	}
	/// 背景フィルターの設定
	void setBackFillter(Texture texture) {
		tFilter = texture;
	}
	
	void setUserReadPos(long userReadPos) {
		this.userReadPos = userReadPos;
	}
	
	// スキップするかどうかを設定する
	void setFastSkipToReadPos(bool b) {
		fastSkipToReadPos = b;
		fastSkipCounter.reset();
	}
	
	/// 読みスキップか？
	bool isFastSkipToReadPos() {
		return fastSkipToReadPos;
	}
	
	int[] getParamData() {
		return this.m_paramData;
	}
	
	/// シナリオ中で表示するダイアログにおいて使用するリソースを設定する
	void setDialogResources(DialogBoxResources dlgRes) {
		this.m_dialogResource = dlgRes;
	}
	
	/// キャラクターを表示するか
	void setDrawChara(bool flg) {
		this.drawChara = flg;
	}
	bool isDrawChara() {
		return this.drawChara;
	}
	/// テキストを描画するか	
	void setDrawText(bool flg) {
		this.drawText = flg;
	}
	bool isDrawText() {
		return this.drawText;
	}
	
	/// 選択待機中か？
	bool isSelectWait() {
		return this.selectWait;
	}
	
	/// 遷移中か
	bool isEffect() {
		return isEffectAll();
	}
	
	/// エフェクト待機中か？
	bool isWait() {
		return isWaitAll();
	}
	
	/// 停止中か？
	bool isReadStop() {
		return readstop;
	}
	
	/// バックログモードかどうか
	bool isBackLogMode() {
		return this.m_backLog;
	}

	/// シナリオビューを進める設定
	void setNext(bool b) {
		nextFlg = b;
	}
	
	/// 現在の文章をキャンセルします
	void setCancelNowText(bool b) {
		readSkip = b;
	}
	
	/// 現在のシナリオ情報を取得します
	ScenarioInfo getCurrentScenarioInfo() {
		return createScenarioInfo();
	}
	
	/// 現在のテキストを取得する
	wchar[] getCurrentText() {
		return sd.getText();
	}
	
	/// 現在のページ数を設定する
	void setPageCount(int value) {
		pageCount = value;
	}
	
	/// 現在のページ数を取得する
	int getPageCount() {
		return pageCount;
	}
	
	/// 指定シナリオが終了
	bool isEnd() {
		return isScenarioEnd;
	}
	
	/// ダイアログボックスを取得する
	DialogBox getSelectDialog() {
		return this.m_selDialog;
	}
	
	/// タスクリクエスト待ち行列の先頭要素を取得する
	int popTaskReq() {
		int taskId = -1;
		try {
			if ( 0 == vTaskReq.size() ) {
				return taskId;
			}
			
			vTaskReq.iterator it = vTaskReq.begin();
			taskId = it();
			
			vTaskReq.erase(it);
		} catch (Exception e) {
			Log.printFatal("Exception %s#popTaskReq :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
		
		return taskId;
	}

	/// 毎回呼び出すなり
	int onMove(Screen screen) {
		assert( !(context is null) );
		
		try {
			if (!init) {
				onInit();
				init = true;
			}
			
			bool bL,bR;	
			bL = nextFlg;
			bR = readSkip;
			
			// タグ解析を行う
			analyzeTag();
	 
			// For debug
			static int bLcount = 0;
			static bool bStopRead = false;
			static bool bAuto = true;
			if ( m_autoRead) {
				if (bL) {
					bAuto = cast(bool) !bAuto;
				}
				if (readstop && bAuto ) {
					bL = true;
				}
			}
			if (fastSkipToReadPos) {
				if (sd.getHeadTextOffset() < userReadPos) {
					if (readstop) {
						bL = true;
					} else if (optionInfo.TextSpeed.LINE != optionInfo.getTextSpeed()
						&& optionInfo.TextSpeed.PAGE != optionInfo.getTextSpeed()) {
						// 行単位、ページ単位でなければ、段階表示をキャンセルしてさらに高速にスキップ
						bR = true;
					}
					
					fastSkipCounter.inc();
					if (0 == fastSkipCounter.get() && textPage) {
						bL = true;
					}
					
					// キャラエフェクトを完了させる
					foreach (chara; charaSprite) {
						chara.effectFinish();
					}
					
				} else {
					fastSkipToReadPos = false;
				}
			}
			
			
//			static ulong clickRcounter = 0;
//			if (++clickRcounter % 3 == 0) {
//				bR = true;
//			}
			// end debug code
				
			if (selectWait) {
				onMoveSelect(screen);
				// 選択肢のときはこの判定以外での入力をキャンセル
				bL = false;
				bR = false;
			}
	
			// 表題タスクに遷移するのか
			if (-1 != invokeHeadline) {
			}
	
			// NextScene指定
			if (nextSceneID) {
	//			analyzeNextScene();
			}
	
			// ホイーリングを検出してαを調整する
			//filterAlphaSense();
			// TODO 評価版用
			onMoveBacklog(screen);
			
			if ( m_backLog && bR ) {
				m_backLog = false;
				m_logback = false;
				m_backLogDepth = 0;
				sd.setBackLogMode(false);
				bR = false;
			}
			
			// バックログ中であれば、入力を無視する
			if (m_backLog) {
				bL = bR = false;
			}
	
			int filterx = offsetx.get();
	
			wait = false;
			foreach (inout CharaSprite c; charaSprite) {
				bool b = false;
				if ( c.isUse() && c.wait ) {
					b = c.isEffect();
				}
				wait = cast(bool) (wait || b);
	
				if (wait) {
					break;
				}
			}

			// テキストを進めるか
			if (!readstop && !wait && !bg_wait) {
				
				// ボイス待ちがあれば再生
				if (voicePlayWait) {
					voicePlayWait = false;
					voiceLoader.playBGM(voiceNo);
				}
				
				// ボイス再生終了まちか？
				if (!voiceWait) {
					// オプションのテキストスピード設定に従って次ぎの位置へ
					nextTextPos();
				} else {
					if (!voiceLoader.get(voiceNo).isPlay()) {
						voiceWait = false;
					}
				}
			}
	
			// プロンプトカーソルの表示
			int tx,ty;
			if (readstop && readwait==-1) {
				int pos = textphase.get() - 1;
				if (drawchar.length > pos) {
					tx = offsetx.get() + cast(int)drawchar[pos].pos.x + 22;
					ty = offsety.get() + cast(int)drawchar[pos].pos.y;
					promptCarsor.setXY(tx,ty);
					newPageSprite.setPos(tx,ty);
				}
			}
	
			// 読み止まりタイマ
			if (readstop && readwait!=-1) {
				readWaitTimer.update();
	
				// 停止時間を過ぎたか？
				if (readWaitTimer.get() > readwait) {
					readstop = false;
					readwait = -1;
				}
			}
	
			// マウスが左クリックされたか
			if (bL) {  	// 左クリックされた
				Log.print("Input:NextText!");
	
				// ボイス再生終了まちか？
				if (voiceWait) {
					if ( useVoice ) {
						if (voiceLoader.get(voiceNo).isPlay()) {
							Log.print("Input#NextTextChanceled-NowVoicePlaying!\n");
							voiceWait = true;	// Fix ボイスウェイト中
						} else {
							voiceWait = false;
						}
					} else {
						voiceWait = false;
					}
				}
	
//				Log.print("check state %s, %s", readstop, readwait);
				// 文章再生へ
				if (readstop && -1==readwait) {
					readstop = false;
	
					// 再生しているボイスはとめる
					if (voiceNo!=-1) {
						voiceLoader.stopBGM();
						voiceNo = -1;
					}
				}
				
				// ページ終了
				if (textphase.isEnd()) {
					
					Log.print("TEXT_PHASE END. to nextPage...");
					
					// 次のページにいくってのに、最後にBRが隠れていたりすると
					// テキストの再生が開始されない
					readstop = false;

					// Fix
					// 文章は終わっていても後にタグがあるかもしれん。
					// 残りの全タグを解析させる
					prePos = 0;
					analyzeTag();
					
					updateText();
					readstop = false;
				}
			}
	
			// マウス右クリック:テキストスキップ処理
			if ( bR )  	// 右クリックされた
			{
				
				Log.print("Input:textSkip!");
				voicePlayWait = false;
				if (voiceWait) {
					if ( useVoice ) {
						voiceWait = false;
						// 再生しているボイスはとめる
						if (voiceNo!=-1) {
							voiceLoader.stopBGM();
							voiceNo = -1;
						}
					} else {
						voiceWait = false;
					}
				}
				
				if ( !readstop ) {
					int n = searchNextStop();
					if (n != -1) {
						prePos = textphase.get();
						//テキストを進める
						textphase.opAssign(n);
						skip = true;
						
						
						// ここで行うべきこと
						// まず、次のreadstopまで進み、そのreadstopは解析済みとしなければならない
						// そうしなければ１クリックその解析を惹起させるために見た目じょう何も起こらない
						// １クリックが必要になる。
						// そして、現在位置から、目標のreadstopのその間にある未処理のタグを実行する
						
						// tag解析を惹起させるために readstop フラグを下げる
						Log.print("-- START SKIPPING TAGS! --");
						readstop = false;
						analyzeTag();
						readstop = true;	//readstopはタグ解析時に設定してくれるけど...
						Log.print("-- END SKIPPING TAGS! --");

						bg_wait = false;

						auto promptImgX = offsetx.get() + cast(int) drawchar[n-1].pos.x + 22;
						int promptImgY = offsety.get() + cast(int) drawchar[n-1].pos.y;
						promptCarsor.setXY(promptImgX,promptImgY);
						newPageSprite.setPos(promptImgX,promptImgY);
						
						Log.print("prePos : %s, nextStopPos : %s", prePos, n);
					}
				}
	
			}
			
			
			// クリーンアップ処理
			onMoveCleanUp();
		} catch (Exception e) {
			Log.printFatal("Exception %s#onMove :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}

		return 0;
	}

	/// 毎回呼び出すなり
	int onDraw(Screen screen) {
		assert( !(context is null) );
		
		try {
			if (!init) {
				onInit();
				init = true;
			}
	
			// 背景の描画
			onDrawBG(screen);
			
			// キャラクタの描画
			if (drawChara) {
				onDrawChara(screen, true);
			}
			
			// wait中は非表示
			if (m_enableBackFillter) 
			{
				screen.setColor(255,255,255, filterAlpha.get());
				screen.blt(tFilter,bgOffsetx.get(), bgOffsety.get());
				screen.resetColor();
			}
			
			if ( m_backLog ) {
				onDrawBacklog(screen);	
			} else if (drawText) {
				// テキストの描画
				int tx,ty;
				for(int i = 0; i < textphase.get() && i < drawchar.length; ++i) {
					float size = drawchar[i].size; 
					// 影の描画
					screen.setColor(0,0,0);
					tx = offsetx.get() + cast(int) drawchar[i].pos.x + 2;
					ty = offsety.get() + cast(int) drawchar[i].pos.y + 1;
					
					// 拡大ありで描画
					if (1.0f != size) {
						screen.bltRotate(
							drawchar[i].texture,	// テクスチャ 
							tx, ty, 	// x,y 位置
							0, 			// 回転角
							size, 		// 拡大率
							0			// 描画ベース位置（左上）
							);
					} else {
						screen.blt(drawchar[i].texture, tx, ty);
					}
		
					// 本文字の描画
					screen.setColor( drawchar[i].color );
					tx = offsetx.get() + cast(int) drawchar[i].pos.x;
					ty = offsety.get() + cast(int) drawchar[i].pos.y;
					
					// 拡大ありで描画
					if (1.0f != size) {
						screen.bltRotate(
							drawchar[i].texture,	// テクスチャ 
							tx, ty, 	// x,y 位置
							0, 			// 回転角
							size, 		// 拡大率
							0			// 描画ベース位置（左上）
							);
					} else {
						screen.blt(drawchar[i].texture, tx, ty);
					}
		
					// ルビ影の描画
					screen.setColor( 0, 0, 0 );
					ScenarioTextDraw.DrawChar[] rubi = drawchar[i].rubi;
					foreach (inout ScenarioTextDraw.DrawChar c; rubi) {
						tx = offsetx.get() + cast(int) c.pos.x;
						ty = offsety.get() + cast(int) c.pos.y;
						screen.blt(c.texture, tx, ty);
					}
		
					// ルビの描画
					screen.setColor( 255, 255, 255 );
					foreach (inout ScenarioTextDraw.DrawChar c; rubi) {
						tx = offsetx.get() + cast(int) c.pos.x;
						ty = offsety.get() + cast(int) c.pos.y;
						screen.blt(c.texture, tx, ty);
					}
				}
			}
			screen.resetColor();
	
			// 選択肢の描画
			if (selectWait) {
				onDrawSelect(screen);
			}
	
			// プロンプトカーソルの表示
			if (readstop && readwait == -1 && !m_backLog ) {
				// テキスト描画ちゅうでなければカーソルも非表示
				if (drawText) {
					if ( textphase.isEnd() ) {
						int px,py;
						newPageSprite.getPos(px,py);
						newPageSprite.blt( screen, px, py );
					} else {
						promptCarsor.onDraw(screen);
					}
				}
			}
			
		} catch (Exception e) {
			Log.printFatal("ScenarioDrawEx#onDraw(Screen) : %s", e.toString());
		}
		
		return 0;
	}
	
	/// コンストラクタ　キャラクターマップ指定する場合
	this(char[] charaImgListName, char[] charaSdfListName) {
		this();
		// CharaMapの構築
		Chara.setCharMap(charaImgListName);
		CharaSprite.setCharMap(charaSdfListName);
	}
	
	/// コンストラクタ
	this() {
		// ScenarioDraw
		sd = new ScenarioTextDraw();
		
		// 画面移動用オフセット
		offsetx = new RootCounterS();
		offsety = new RootCounterS();

		bgOffsetx = new RootCounterS();
		bgOffsety = new RootCounterS();
		
		// テキスト進捗計測用
		textphase = new RootCounterS();
		
		// 読み止まり計測タイマ
		readWaitTimer = new FixTimer();
		
		// フィルタ明度計算用カウンタ
		filterAlpha = new RootCounterS(0, 255, 4);
		filterAlpha.opAssign(128);

		// シナリオスクリプト解析用パーサ
		lineparser = new LineParser();

		// 背景エフェクト進捗用カウンタ
		bgEffect = new RootCounterS();
		bgEffect.set(0,255,2);

		// キャラクターコンテナ生成
		charaSprite = new CharaSprite[4];
		foreach (inout CharaSprite c; charaSprite) {
			c = new WinkCharacter();
		}

		Chara.setCharMap("img/sil/list.lst");
		// CharaMapの構築
		CharaSprite.setCharMap("img/sil/chara_map.lst");
		
		pageCount = 1;
		
		vTaskReq = new vector!(int);
		
		// 呼び出しヘッドライン番号
		invokeHeadline = -1;
		
		// 描画フラグ
		drawText = true;
		drawChara = true;
		
		// skip counter
		fastSkipCounter = new RootCounter(0, 10, 1);

		m_enableBackFillter = true;
	}
	
	/// 情報の再読込
	void onLoad(ScenarioInfo si_, TextDrawContext con_, bool startTop) 
		in
		{
			assert( !(si_ is null) );
			assert( !(con_ is null) );
		}
		body
		{
			context = con_;
			setScenario(si_, startTop);
		}
	
	/// 指定したシナリオを呼び出します
	void nextScenario(int storyNo_=0) {
		try {
			Log.print("nextScenario!%s",storyNo);
			storyNo = storyNo_;
			if (storyNo < 0 || KyojinConst.scenarioFileName.length <= storyNo) {
				storyNo = 0;
			}
			setLoaders();
	
			char[] strText = cast(char[]) FileSys.read(KyojinConst.scenarioFileName[storyNo]);
			if (!strText) {
				Log.printError("Scenario data reading error!");
			}
			context.setText(toWCS(strText));
			sd.setTextDrawContext(context);
			sd.updateText();
			drawchar = sd.getDrawChar();
			tag = sd.getUnknownTagInfo();
			selectTag = sd.getSelectTagInfo();
	
			if (textPage) {
				textphase.set(drawchar.length,drawchar.length, textSpeed);
			} else {
				textphase.set(0,drawchar.length,textSpeed);
			}
			
			readstop = false;
	
			analyzedTag = 0;
			prePos = 0;
		} catch (Exception e) {
			Log.printFatal("Exception %s#nextScenario :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}

	}
	
	/// シナリオ情報を取得
	ScenarioInfo getScenarioInfo() {
		return this.currentScenarioInfo;
	}

	/// ルビを使用する
	void enableRubiText() {
		this.sd.enableRubiText();
	}
	
	/// ルビを使用しない
	void disenableRubiText() {
		this.sd.disenableRubiText();
	}

private:
	/// ボイスリストの一時書き出し
	static void writeVoiceList(char[] filename, char[] data) {
		try {
			char[] mkPath = std.path.getDirName(filename);
			char[][] dirs = std.string.split(mkPath, "\\");
			char[] wrkStr;
			for (int i = 0; i < dirs.length-1; ++i) {
				wrkStr ~= dirs[i] ~ "\\";
				if ( !std.file.exists(wrkStr) ) {
					printf("mkdir %*s\n", wrkStr);
					std.file.mkdir(wrkStr);
				}
			}
			FileSys.write(filename, data);
		} catch (Exception e) {
			Log.printFatal("Exception ScenarioDrawEx#writeVoiceList :[%s]:[%s]", e.toString(), e.msg);
		}
	}

	/// 初期化
	void onInit() {
		try {
			Log.print("ScenarioDrawEx onInit");
			
			// オブション情報の反映
	//		setOptionInfo();
	
			FontRepository fr = sd.getFontRepository();
			fr.setLoader(fontLoader, this.textFontNo);
			fr.setMax(300);
			
			// バックログ用フォントリポジトリ
			fr = sd.getBackLogFontRepository();
			fr.setLoader(fontLoader, this.textFontNo);
			fr.setMax(300);
			
			// ルビ用フォントローダの設定
			fr = sd.getRubiFontRepository();
			fr.setLoader(fontLoader, this.rubiFontNo);
			fr.setMax(300);
	
//			Log.print("FontRepository setting ok");
			
			// シナリオ読み込み
			setScenario(currentScenarioInfo);
	
			if (textPage) {
				textphase.set(drawchar.length, drawchar.length, textSpeed);
			} else {
				textphase.set(0, drawchar.length, textSpeed);
			}
	
			// For Debug
			setAutoRead();
	
			Log.print("ScenarioDrawEx onInit End");
		} catch (Exception e) {
			Log.printFatal("Exception %s#onInit :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
	}
	
	/// onMove処理の最後に呼び出される
	void onMoveCleanUp() {
		nextFlg = false;
		readSkip = false;
	}
	
	/// オートリード属性を設定する
	void setAutoRead() {
		try {
			debug 
			{
				Properties prop = Properties.getInstance(KyojinConst.C_USER_ENV_FILE);
//				prop.load( KyojinConst.C_USER_ENV_FILE );
				char[] flg = prop.getProperty("auto_read", "off");

				m_autoRead = cast(bool) ("on"==flg);
				if (m_autoRead) {
					Log.printLook("SENARIO AUTO READ ON");
				}
				
			}
//			m_autoRead = true;
		} catch (Exception e) {
			Log.printFatal("Exception %s#setAutoRead :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
	}
	
	/// シナリオ情報からシナリオタスクを再構築する
	void setScenario(ScenarioInfo sinfo,bool start=false) {
		assert( !(sinfo is null) );
		
		try {
			// フラグをリセットしておこう
			reset();
			bgEffectNo = -1;
			
			// seがなっていればとめる
			if ( seNo != -1 ) {
				seLoader.get(seNo).stop();
			}
	
			storyNo = sinfo.storyNo;	// シナリオ番号
			bgNo = sinfo.bgNo;
			bgNoOld = bgNo;
			bgmNo = sinfo.bgmNo;
			bgmVol = sinfo.bgmVol;
			bgmLoop = sinfo.bgmLoop;
			seNo = sinfo.seNo;
			seLoop = sinfo.seLoop;
			seVol = sinfo.seVol;
			pageCount = sinfo.page;
			
			Log.print("try loadScenario Data:setScenarioInfo");
	
			Log.print("storyNo:%s",		storyNo);
			Log.print("bgNo:%s",		bgNo);
			Log.print("bgmNo:%s",		bgmNo);
			Log.print("bgmLoop:%s",		bgmLoop);
			Log.print("seNo:%s",		seNo);
			Log.print("seLoop:%s",		seLoop);
			Log.print("pageCount:%s",	pageCount);
			Log.print("HeaderTextpos:%s",sinfo.pos);
			Log.print("textPos:%s",		sinfo.textPos);
			Log.print("start:%s",		start);
			Log.print("analyzedTag:%s",	sinfo.analyzedTag);
			
			for(int i=0; i < charaSprite.length; ++i) {
				
				charaSprite[i].setViewWidth( context.viewWidth );
				// 初期化しておく
				charaSprite[i].reset();
				// 続きからならば、復元
				if ( !start ) {
					charaSprite[i].pos = sinfo.charpos[i];
					charaSprite[i].preset_pos = sinfo.preset_pos[i];
					charaSprite[i].setCharId( sinfo.strCharId[i] );
					charaSprite[i].setSpriteDefFile( sinfo.strCharId[i] );
					charaSprite[i].setColor( Color4ub.valueOf(sinfo.vCharColor[i]) );
				}
			}
			
			// シナリオ情報から画面更新
			if (storyNo < 0 || KyojinConst.scenarioFileName.length 	<= storyNo) {
				storyNo = 0;
			}
	
			sd.setTextDrawContext(context);
			setLoaders();
	
			// シナリオの頭からよむんか
			if (start) {
				Log.print("Scenario START setting...");
				pageCount = 1;
				sd.setTextOffset(0);	// バッファポジション
				sd.updateText();
				drawchar = sd.getDrawChar();
				tag = sd.getUnknownTagInfo();
				selectTag = sd.getSelectTagInfo();
				analyzedTag = 0;
				analyzedTagList = null;
				prePos = 0;
				
				readstop = true;
				textphase.set(0,drawchar.length, 1);
				readstop = false;
				
				
				Log.print("Scenario START");
			} else {
				sd.setTextOffset(sinfo.pos);	// バッファポジション
				sd.updateText();
				drawchar = sd.getDrawChar();
				tag = sd.getUnknownTagInfo();
				selectTag = sd.getSelectTagInfo();
				analyzedTagList = null;
	
				// セーブデータの整合性チェック
				if (sinfo.textPos >= drawchar.length) {
					Log.printWarn("ScenarioDraaEx#setScenario Save data extraordinary. textPos = %s, drawchar.length = %s", sinfo.textPos, drawchar.length);
					sinfo.textPos = drawchar.length;
				}
	
				textphase.set(sinfo.textPos,drawchar.length, 1);
				analyzedTag = sinfo.analyzedTag;
				prePos = sinfo.textPos;
	
				bgmLoader.stopBGM();
				if (-1 != bgmNo) {
					y4d_result result;
					bgmLoader.get(bgmNo).setLoop(-1);
					result = bgmLoader.get(bgmNo).setVolume(cast(int)(bgmVol * bgmMasterVol));
					if ( y4d_result.no_error != result ) {
						Log.printError("Can't set bgm volume at continue: %s", getErrorName(result));
					}
					result = bgmLoader.playBGMFade(bgmNo,2500);
					if ( y4d_result.no_error != result ) {
						Log.printError("Can't play bgm at continue: %s", getErrorName(result));
					}
				}
	
				if (-1 != seNo && -1 == seLoop) {
					seLoader.get(seNo).setVolume(cast(int)(seVol * seMasterVol));
					seLoader.get(seNo).setLoop(-1);
					seLoader.get(seNo).play();
				}
				
				readstop = true;
				Log.print("Scenario CONTINUE");
			}
	
			setOptionInfo();
	
			voiceWait = false;
			bg_wait = false;
		} catch (Exception e) {
			Log.printFatal("Exception %s#setScenario :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
	}
	
	
	/// ストーリーナンバーから 各種ローダを設定する
	void setLoaders() {
		try {
			with (kyojintati4d.val.kyojinconst) {
				// ボイスを指定しないことも可能
				if ( KyojinConst.voicePath.length <= storyNo) {
					return;
				}

				y4d_result result;
				char[] filename = KyojinConst.voicePath[storyNo] ~ "voice.lst";
				/// このシナリオのvoice defListを作る
//				createVoiceDefList();
//				voiceLoader.releaseFileList();
				result = voiceLoader.loadDefRW( FileSys.read(filename) );
				if ( y4d_result.no_error != result ) {
					Log.printError("voiceLoader.loadDefRW : %s %s", filename, getErrorName(result));
				} else {
					Log.print("load voice list:%s", filename);
//					voiceLoader.print();
				}
				setVoiceMap(filename);
			}
		} catch (Exception e) {
			Log.printFatal("Exception %s#setLoaders :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
	}
	
	/// voice フォルダから合成したリストを作成する
	void createVoiceDefList() {
		static const char[] listFileName = "voice.lst";
		static const char[] dummyFileName = "dummy.dat";
/*
		Log.print("createVoiceDefList START");
		
		try {
			with (kyojintati4d.val.kyojinconst.KyojinConst) {
				
				char[] voiceDirDummy = FileSys.makeFullName(voicePath[storyNo] ~ dummyFileName);
				if (!voiceDirDummy) {
					Log.printFatal("voice directory not found!!" ~ voicePath[storyNo]);
					return;
				}
			
				char[] voiceDir = std.path.getDirName(voiceDirDummy);
				if ( voiceDir[length-1] != '\\' || voiceDir[length-1] != '/' ) {
					voiceDir ~= std.path.sep;
				}
				Log.print("Voice Dir : %s", voiceDir);
				
				// ここですでにあるリストは削除する
				if ( FileSys.isExist(voiceDir ~ listFileName) ) {
					std.file.remove(voiceDir ~ listFileName);
				}
				
				// 指定したディレクトリ以下の"lst"ファイルを検索しそのリストを合成する				
				DirEnumerator dir = new DirEnumerator;
				dir.setDir(voiceDir);
				char[] buffer;
				foreach (char[] file; dir) {
					// 拡張子がlstなら
					if (std.string.tolower(std.path.getExt(file)) == "lst") {
						
						Log.print("find voice list:%s",file);
						
						// フルパスに書き換える
						ubyte[] mem = cast(ubyte[])(FileSys.read(file));
						if (!mem) return; // 読み込みエラー

						std.stream.MemoryStream m = new std.stream.MemoryStream(mem);
						LineParser lp = new LineParser();
						while (!m.eof) {
							char[] tmp;
							char[] linebuf = m.readLine();
							lp.setLine(linebuf);
							char[] filename = lp.getStr();
							if (!filename) continue; // ダメやん
							
							tmp ~= filename;
		
							// オプションを読み出す
							while (!lp.isEnd()) {
								char[] str = lp.getStr();
								if(str)
									tmp ~= "," ~ str;
							}
							tmp ~= "\r\n";	// 改行処理
							
							buffer ~= tmp;
						}	// while
						
						m.close();
					}
				}
				
//				FileSys.write(voiceDir ~ listFileName, buffer);
				Log.print("%s#createVoiceDefList : create voice list %s",toString(), voiceDir ~ listFileName);
			}
		} catch (Exception e) {
			Log.printFatal("Exception %s#createVoiceDefList :[%s]:[%s]", this.toString(), e.toString(), e.msg);
		} catch {
			Log.printFatal("Error %s#createVoiceDefList", this.toString());
		}
*/		
		Log.print("createVoiceDefList END");
	}
	
	///	指定されたVoice定義ファイルより、採番したユニークＩＤとのマップを構築する
	void setVoiceMap(char[] filename) {
		
		assert( !(filename is null) );
		try {
			voiceMap = null;
			ubyte[] memory = cast(ubyte[]) FileSys.read(filename);
			if (!memory) {
				Log.printError("Can't read voice list : %s", filename);
				return;
			}
			
			std.stream.MemoryStream m = new std.stream.MemoryStream(memory);
			
			if ( m is null || !m.isOpen() || !m.readable) {
				Log.printError("FileOpenError %s#setVoiceMap : [%s]", this.toString(), filename);
				return;			
			}
			LineParser lineParser = new LineParser();
	
			int count = 0;
			while(!m.eof) {
				lineParser.setLine(m.readLine());
				// filename はすてる
				lineParser.getStr();
				// charID
				char[] str = lineParser.getStr();
				if (!str) continue;	// あかんやん
				voiceMap[str] = count++;
			}
			
			voiceMap = voiceMap.rehash;
			Log.print("Created voice map : size=%s", voiceMap.length);
			
			m.close();
		} catch (Exception e) {
			Log.printFatal("Exception %s#setVoiceMap :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
	}	

	/// オブション設定を反映させる
	void setOptionInfo() {
		try {
			// bgm の設定
			useBgm = optionInfo.bgmOn;
			bgmMasterVol = optionInfo.getBgmVol()/100.0f;
			Log.print("BGM_MASTER_VOL:%s\n",bgmMasterVol);
			Log.print("BGM_ON/OFF: %s\n", useBgm);
	
			bool preEnableSound = bgmLoader.isEnableSound();
			bgmLoader.enableSound(useBgm);
			// BGM ON →　BGM OFF
			if ( preEnableSound && !useBgm) {
				// ここで今なっている曲を退避する。
				// そしてサウンドを無効化する。→SoundLoaderが内部的にもっている曲番号もなくなる
				// そのあとに、再生要求をなげて、SoundLoaderが内部的にストアさせる
				// それによって、もう一度 EnableSound(true)をしたときに再生するようにする
				bgmLoader.playBGM( bgmNo );
			}
			// 音量設定
			if (-1 != bgmNo) {
				bgmLoader.get(bgmNo).setVolume(cast(int) (bgmVol*bgmMasterVol));
			}
			
			//seの設定
			useSe = optionInfo.seOn;
			seMasterVol = optionInfo.getSeVol()/100.0f;
			seLoader.enableSound(useSe);
			Log.print("SE_MASTER_VOL:%s\n",seMasterVol);
			// 音量設定
			if (-1 != seNo) {
				seLoader.get(seNo).setVolume(cast(int) (seVol*seMasterVol));
			}
	
			// voiceの設定
			useVoice = optionInfo.voiceOn;
			voiceMasterVol = optionInfo.getVoiceVol()/100.0f;
			//↓ そもそも、OFFにしないので、ここでON要求すると再生されてしまう
	//		voiceLoader.enableSound(useVoice);
			Log.print("VOICE_MASTER_VOL:%s\n",voiceMasterVol);
			// 音量設定
	//		if (-1 != voiceNo) {
	//			voiceLoader.get(voiceNo).setVolume(cast(int) (voiceVol*voiceMasterVol));
	//		}
	
			//textSpeed の設定
			textSpeed = optionInfo.getTextSpeed();
			int n = textphase.get();
	
			switch (textSpeed) {
				
				case OptionInfo.TextSpeed.NORMAL:
				case OptionInfo.TextSpeed.FAST:
					if (textPage) {
						if (useBgm && -1 != bgmNo) {
							// 再生中か？
							if ( !bgmLoader.get(bgmNo).isPlay() ) {
								// 新たに再生する
								bgmLoader.get(bgmNo).setLoop(-1);
								bgmLoader.playBGM(bgmNo);
								bgmLoader.get(bgmNo).setVolume(cast(int)(bgmVol*bgmMasterVol));
							}
						}
					}
	
					textLine = false;
					textPage = false;
					Log.print("TextSpeed:NORMAL or FAST");
					break;
				
				case OptionInfo.TextSpeed.LINE:	// 速い
					textLine = true;
					Log.print("TextSpeed:LINE\n");
					textPage = false;
					break;
	
				case OptionInfo.TextSpeed.PAGE:	// ページ
					textPage = true;
					Log.print("TextSpeed:PAGE\n");
					textLine = false;
					break;
				default:
					textLine = false;
					textPage = false;
					Log.printError("setOptionInfo# Unkown TextSpeed %s", textSpeed);
					break;
			}
	
			// 表示速度の設定
			textphase.set(n, drawchar.length ,textSpeed);
		} catch (Exception e) {
			Log.printFatal("Exception %s#setOptionInfo :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
	}
	
	/// セーブするためのシナリオ情報を構築
	ScenarioInfo createScenarioInfo() {
		try {
			ScenarioInfo scenarioInfo = new ScenarioInfo();
			// シナリオ番号
			scenarioInfo.storyNo = storyNo;	
			// 現在のページの先頭のバッファポジション
			scenarioInfo.pos = sd.getHeadTextOffset();
			// テキストの位置	
			scenarioInfo.textPos = textphase.get();
			// 解析タグリスト
			scenarioInfo.analyzedTag = analyzedTag;
			// ページ番号
			scenarioInfo.page = pageCount;
			// 再生ＢＧＭ番号
			scenarioInfo.bgNo = bgNo;
			// 旧ＢＧＭ番号
			scenarioInfo.bgNoOld = bgNoOld;
			// 再生ＢＧＭ番号
			scenarioInfo.bgmNo = bgmNo;
			// 再生ＢＧＭヴォリューム
			scenarioInfo.bgmVol = bgmVol;
			// ＢＧＭループフラグ
			scenarioInfo.bgmLoop = bgmLoop;
			// ＳＥ番号
			scenarioInfo.seNo = seNo;
			// ＳＥボリューム
			scenarioInfo.seVol = seVol;
			// ＳＥループフラグ
			scenarioInfo.seLoop = seLoop;
	
			/+  テスト +/
			Log.print( "Created Scenario Info" );
			Log.print( "storyNo : %s", scenarioInfo.storyNo);
			Log.print( "pos : %s", scenarioInfo.pos);
			Log.print( "textPos : %s", scenarioInfo.textPos);
			Log.print( "page : %s", scenarioInfo.page);
			Log.print( "bgNo : %s", scenarioInfo.bgNo);
			Log.print( "bgNoOld : %s", scenarioInfo.bgNoOld);
			Log.print( "bgmNo : %s", scenarioInfo.bgmNo);
			Log.print( "bgmVol : %s", scenarioInfo.bgmVol);
			Log.print( "bgmLoop : %s", scenarioInfo.bgmLoop);
			Log.print( "seNo : %s", scenarioInfo.seNo);
			Log.print( "seVol : %s", scenarioInfo.seVol);
			Log.print( "seLoop : %s", scenarioInfo.seLoop);
	
			for(int i=0; i < charaSprite.length; ++i) {
				scenarioInfo.charpos[i] = charaSprite[i].pos;
				scenarioInfo.preset_pos[i] = charaSprite[i].preset_pos;
				scenarioInfo.strCharId[i] = charaSprite[i].getCharId();
				scenarioInfo.vCharColor[i] = charaSprite[i].getColor().toString();
				Log.print( "charpos - %s, preset_pos - %s, strCharId - %s", 
					scenarioInfo.charpos[i], scenarioInfo.preset_pos[i], scenarioInfo.strCharId[i] );
			}
			
			return scenarioInfo;
		} catch (Exception e) {
			Log.printFatal("Exception %s#createScenarioInfo :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
	}
	
	/// バックログの動作を制御します
	void onMoveBacklog(Screen screen) 
		in
		{
			assert( !(screen is null) );
			assert( !(this.sd is null) );
		}
		body
		{
			try {
				//　ページ単位の場合は、readstop == false である
				if ( !textPage && (!readstop || readwait != -1) ) {
					return;
				}
				
				if ( this.mouse.isPress(MouseInput.button.wheel_up) ) {
					// 前に戻る
					if ( !m_backLog ) {
						m_backLog = true;
						sd.setBackLogMode(true);
						Log.printLook("BACK LOG ON");
					}
					
					updateBacklog(true);
					
				} else if ( this.mouse.isPress(MouseInput.button.wheel_down) ) {
					// 次に進む
					if ( !m_backLog ) {
						return;
					}
					
					updateBacklog(false);
				}
			} catch (Exception e) {
				Log.printFatal("Exception %s#onMoveBacklog :[%s]:[%s]", this.toString(), e.toString(), e.msg);
				throw e;
			}
			
		}
	
	/// バックログを現在の状態で初期化します
	bool updateBacklog(bool back) {
		
		try {
			// 規定位置より先にすすのはだめ！
			if ( !back && m_backLogDepth <= 1) {
				return false;			
			}
			
			// 前はバックだったけど、こんどは進むのとき
			if ( m_logback && !back ) {
				sd.updateText(back);
				m_logback = false;
			} else if ( !m_logback && back ) {
				sd.updateText(back);
				m_logback = true;
			}
			
			if ( sd.updateText(back) ) {
				m_drawcharLog = sd.getDrawChar();
				
				if ( back ) {
					m_backLogDepth++;
				} else {
					m_backLogDepth--;
				}
				
				Log.printLook("BACK LOG DEPTH:%d", m_backLogDepth);
				
				return true;
			}
		} catch (Exception e) {
			Log.printFatal("Exception %s#updateBacklog :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
		return false;
	}
	
	/// バックログを描画します
	void onDrawBacklog(Screen screen) 
		in
		{
			assert( !(screen is null) );
			assert( !(offsetx is null) );
			assert( !(offsety is null) );
		}
		body
		{
			if (m_drawcharLog is null) return;
			
			int tx,ty;
			try {
				for(int i = 0; i < m_drawcharLog.length; ++i) {
					float size = m_drawcharLog[i].size; 
					// 影の描画
					screen.setColor(0,0,0);
					tx = offsetx.get() + cast(int) m_drawcharLog[i].pos.x + 2;
					ty = offsety.get() + cast(int) m_drawcharLog[i].pos.y + 1;
					
					// 拡大ありで描画
					if (1.0f != size) {
						screen.bltRotate(
							m_drawcharLog[i].texture,	// テクスチャ 
							tx, ty, 	// x,y 位置
							0, 			// 回転角
							size, 		// 拡大率
							0			// 描画ベース位置（左上）
							);
					} else {
						screen.blt(m_drawcharLog[i].texture, tx, ty);
					}
		
					// 本文字の描画
					Color4ub col = m_drawcharLog[i].color;
					screen.setColor( col.r / 2, col.g / 2, col.b / 2 );
					tx = offsetx.get() + cast(int) m_drawcharLog[i].pos.x;
					ty = offsety.get() + cast(int) m_drawcharLog[i].pos.y;
					
					// 拡大ありで描画
					if (1.0f != size) {
						screen.bltRotate(
							drawchar[i].texture,	// テクスチャ 
							tx, ty, 	// x,y 位置
							0, 			// 回転角
							size, 		// 拡大率
							0			// 描画ベース位置（左上）
							);
					} else {
						screen.blt(m_drawcharLog[i].texture, tx, ty);
					}
		
					// ルビ影の描画
					screen.setColor( 0, 0, 0 );
					ScenarioTextDraw.DrawChar[] rubi = m_drawcharLog[i].rubi;
					foreach (inout ScenarioTextDraw.DrawChar c; rubi) {
						tx = offsetx.get() + cast(int) c.pos.x;
						ty = offsety.get() + cast(int) c.pos.y;
						screen.blt(c.texture, tx, ty);
					}
		
					// ルビの描画
					screen.setColor( 128, 128, 128 );
					foreach (inout ScenarioTextDraw.DrawChar c; rubi) {
						tx = offsetx.get() + cast(int) c.pos.x;
						ty = offsety.get() + cast(int) c.pos.y;
						screen.blt(c.texture, tx, ty);
					}
				}
			} catch (Exception e) {
				Log.printFatal("Exception %s#onDrawBacklog :[%s]:[%s]", this.toString(), e.toString(), e.msg);
				throw e;
			}
		}
	
	/// 次のページのテキストを読み込み表示変数を初期化します
	void updateText() {
		try {
			if ( sd.updateText() == false ) {
				// 解析終了
				isScenarioEnd = true;
			}
			
			drawchar = sd.getDrawChar();
			tag = sd.getUnknownTagInfo();
			selectTag = sd.getSelectTagInfo();
			
			Log.print("This page has %s tag(s)",tag.length);
			Log.print("-------------------PageTag ListUp----------------------");
			for(int i=0; i<tag.length; ++i) {
				Log.print("tag%s: %s:pos%s", i, tag[i].tag, tag[i].pos);
			}
			Log.print("-------------------Finished ListUp----------------------");
		
			analyzedTagList = null;
		
			// ページ単位なら表示を終了させる
			if (textPage) {
				textphase.set(drawchar.length,drawchar.length,textSpeed);
			} else {
				textphase.set(0,drawchar.length,textSpeed);
			}
			analyzedTag = 0;
			prePos = 0;
			pageCount++;	// ページを数える
		} catch (Exception e) {
			Log.printFatal("Exception %s#updateText :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
	}
	
		/// キャラクターのエフェクトを強制的に終了状態に持って行く
	void charaEffectFinish() {
		int end,start;
		try {
			foreach(inout CharaSprite c; charaSprite) {
				if (c.isUse()) {
					// エフェクト終わらせるで～
					c.effectFinish();
				}
			}
			// コンパクションしておき、空き状態がないことを保証する			
			compactionCharaArray();
		} catch (Exception e) {
			Log.printFatal("Exception %s#charaEffectFinish :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
		
	}
	
	/// 背景のエフェクトを終了させる
	void bgEffectFinish() {
		try {
			int end  = bgEffect.getEnd();
			bgEffect.opAssign(end);
			bg_wait = false;
			bgEffectNo = -1;
		} catch (Exception e) {
			Log.printFatal("Exception %s#bgEffectFinish :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
	}

	/// すべて（背景・キャラクター）の wait フラグをチェック
	bool isWaitAll() {
		try {
			// 背景
			if(bg_wait) return true;
			return isCharaWait();
			
		} catch (Exception e) {
			Log.printFatal("Exception %s#isWaitAll :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
		return false;
	}
	
	/// いずれかのキャラクターがWait中であるかどうか
	bool isCharaWait() {
		// キャラクタ
		foreach(inout CharaSprite c; charaSprite) {
			if (c.isUse()) {
				// エフェクト中か～
				if (c.wait) return true;
			}
		}
		return false;
	}

	/// すべて（背景・キャラ）のエフェクト状態をチェック
	bool isEffectAll() {
		try {
			if ( bgEffect.isEnd() && !bgDoubleTrans ) {
				foreach (inout CharaSprite c; charaSprite) {
					if ( c.isEffect() ) return true;
				}
			} else {
				return true;
			}
		} catch (Exception e) {
			Log.printFatal("Exception %s#isEffectAll :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}

		return false;
	}

	/// 背後暗幕フィルタ濃度設定（マウスホイールから情報取得）
	void filterAlphaSense() {
		try {
			if ( this.mouse.isPress(MouseInput.button.wheel_up) ) {
				filterAlpha++;
			} else if ( this.mouse.isPress(MouseInput.button.wheel_down) ) {
				filterAlpha--;
			}
		} catch (Exception e) {
			Log.printFatal("Exception %s#filterAlphaSense :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
	}
	
	/// 外部に公開するタスク要求待ち行列に追加する
	void pushTaskReq(int n) {
		vTaskReq.push_back(n);
	}

	/// テキスト表示速度の設定から次のテキストの位置を返す
	/// @return bool 次の停止位置が見つかればture でなければ false
	bool nextTextPos() {
		try {
			prePos = textphase.get();
			switch (textSpeed) {
				case optionInfo.TextSpeed.NORMAL:
				case optionInfo.TextSpeed.FAST:
					textphase++;
					
					// Fix ここで、readstop を見逃している
					int n = searchNextStopEx();
					if (-1 != n && n < textphase.get()) {
						textphase.opAssign(n);
					}
					
					return textphase.isEnd();
	
				case optionInfo.TextSpeed.LINE:
					int res = searchNextStop();
					if (-1 != res) {
						textphase.opAssign(res);
						return true;
					}
					return false;
	
				case optionInfo.TextSpeed.PAGE:
					if (!textphase.isEnd()) {
						textphase.set(textphase.getEnd(),textphase.getEnd(),1);
					}
					break;
				default:
					break;
			}
		} catch (Exception e) {
			Log.printFatal("Exception %s#nextTextPos :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
		return true;
	}

	/// 次のreadstpoを探す（ReadStop,timeを含まない）
	int searchNextStop() {
		int found = -1;
		try {
			for (int i=analyzedTag; i<tag.length; ++i) {
	
				lineparser.setLine(toMBS(tag[i].tag));
				char[] cc = lineparser.getStr();
	
				// タグは小文字化して探査しよう
				foreach (inout char c; cc) {
					c = std.ctype.tolower(c);
				}
				if (cc == "readstop") {
					if (lineparser.isEnd()) {
						found = i;
						break;
					} else {
						// これはreadstop,timeだ！
						continue;
					}
				}
			}
			
			Log.print("next readstop: %s", found);
	
			if (found == -1) {
				return -1;
			} else {
				return tag[found].pos;
			}
		} catch (Exception e) {
			Log.printFatal("Exception %s#searchNextStop :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
	}
	
	/// 次のreadstpoを探す（ReadStop,timeをむ）
	int searchNextStopEx() {
		int found = -1;

		try {
			for (int i=analyzedTag; i<tag.length; ++i) {
	
				lineparser.setLine(toMBS(tag[i].tag));
				char[] cc = lineparser.getStr();
	
				// タグは小文字化して探査しよう
				foreach (inout char c; cc) {
					c = std.ctype.tolower(c);
				}
				if (cc == "readstop") {
					return tag[i].pos;
				}
			}
		} catch (Exception e) {
			Log.printFatal("Exception %s#searchNextStopEx:[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
		
		// みつからんかった...
		return -1;
	}
		
	
	/// タグの解析を行います
	bool analyzeTag() {

		//	どのタグに該当するかを調べる
		static const char[][] tags = [
			"readstop",	 	// 0 .読み止まり
			"charain",		// 1. キャラクターイン
			"charaout",		// 2. キャラクターアウト
			"bg",			// 3. 背景描画
			"voice",		// 4. 音声再生
			"se",			// 5. ＳＥ再生
			"se_stop",		// 6. ＳＥ停止
			"bgm_play",		// 7. ＢＧＭの再生
			"bgm_stop",		// 8. ＢＧＭの停止
			"wait",			// 9. エフェクト処理が終わるまで待つ
			"voicewait",	//10. ボイス再生終了を待つ
			"headline",		//11. タイトル表示
			"nextscene",	//12. 指定したシーンへ移行
			"hr",			//13. 改ページ
		];
		
		try {
			if (textPage) {
				// 新しいページだがすでに音楽がなっていればフラグをたてる
				playingBgm = cast(bool) (bgmNo != -1);
				bool updateBGM = false;
				int bgmOld = bgmNo;
	
				// ページ単位表示なら全タグを解析し、最終的に有効なタグのみを実行する
				while (analyzedTag < tag.length) {
	
					// この位置にタグはあるんけ？
					if (selectTag && !selectWait) {
Log.print("FOUND select TAG");
						if (ScenarioTextDraw.SelectTagInfo.pos == prePos) {
							// ここで選択肢だ！
							selectWait = true;
							readstop = true;

							// 選択肢ダイアログ生成						
							m_selDialog = createSelection("img/dialog/dialog_select.txt");
							
							// マウス入力排他
							mouse.setGuardTime( 60 );
							Log.print("selecting pos");
							break;
						}
					}
	
					if (tag[analyzedTag].pos != prePos) {
						if (textphase.get() > prePos) {
							// 次の解析にかかる
							prePos++;
							continue;
						}
						break;
					}
	
					char[] strTag = toUTF8(tag[analyzedTag].tag );
					lineparser.setLine(strTag);
					char[] cc = lineparser.getStr();
					char[] strErorr;
	
					// タグは小文字化して探査しよう
					foreach (inout char c; cc) {
						c = std.ctype.tolower(c);
					}
	
					int found = -1; // not found marker
					for(int i=0;i<tags.length;++i){
						if (cc == tags[i]) {
							found = i;
							break;
						}
					}
	
					//	このタグの処理
					switch(found) {
					case -1:
						Log.printWarn("UnkownTag<%s>",cc);
						break;
					case 0:		// <readstop> 無効
						Log.print("<readstop>");
						break;
					case 1:		// <char_in>
						Log.print("<char_in>");
						tagCharaIn();
						charaEffectFinish();	// エフェクト中は使用中と見なされてしまう...
						break;
					case 2:		// <char_out>
						Log.print("<char_out>");
						tagCharaOutSprite();
						charaEffectFinish();	// エフェクト中は使用中と見なされてしまう...
						break;
					case 3:		// <bg>
						Log.print("<bg>");
						tagBG();
						bgEffectFinish();	// エフェクト中は使用中と見なされてしまう...
						break;
					case 4:		// <voice> 無効
						Log.print("<voice>");
						break;
					case 5:		// <se> ページ最初にあるの限り有効、以外は無視
						Log.print("<se>");
						if (analyzedTag==0) {
							tagSe();
						}
						break;
					case 6:		// <se_stop> 有効
						// 2005.8.17 Fix
						tagSeStop();
						Log.print("<se_stop>");
						break;
					case 7:		// <bgm_play>
						Log.print("<bgm_play>");
						tagBgmPlay(true);
						break;
					case 8:		// <bgm_stop>
						Log.print("<bgm_stop>");
						tagBgmStop(true);
						break;
					case 9:		// <wait> 無効
						Log.print("<wait>");
						break;
					case 10:	// <VoiceWait> 無効
						Log.print("<VoiceWait>");
						break;
					case 11:	// <HeadLine> 有効
						Log.print("<HeadLine>");
						tagHeadline();
						break;
					case 12:	// <e> 有効
						Log.print("<e>");
						tage();
						break;
					case 13:	// <hr> 無視
						// 無視
						break;
					default:
						Log.printError("Error:%s",found);
						break;
					}
	
					analyzedTag++;
					// 頁単位であると、一度だけこの解析が走る。
					// そのタイミングでＢＧＭを更新する
					updateBGM = true;
				}
	
				// 解析したＢＧＭタグの最終結果を再生する
				if (updateBGM 	// この頁の解析がなされたときである
					  && useBgm // BGM ON
					  && bgmOld != bgmNo	// 更新結果 BGM NO は変更になった
					  && bgmNo != -1 		// BGM 番号有効指定
					  && bgmVol != -1) {	// BGM ボリューム有効指定
					bgmLoader.get(bgmNo).setLoop(-1);
					if (bgmFadeTime==-1 || bgmFadeTime==0) {
						bgmLoader.playBGM(bgmNo);
					} else {
						bgmLoader.playBGMFade(bgmNo,bgmFadeTime);
					}
					bgmLoader.get(bgmNo).setVolume(cast(int)(bgmVol*bgmMasterVol));
	
					Log.print("TEXT SPEED UNNORMAL - PLAY BGM %s", bgmNo);
				}
	
			} else if (analyzedTag<tag.length && !readstop && !voiceWait) {
				
				// 前のテキストの位置から次の位置に進んだとき
				// その間にあった、タグのすべてを解析する
				while (tag[analyzedTag].pos >= prePos) {
//Log.print("tag[analyzedTag].pos=%s , prePos=%s", tag[analyzedTag].pos,prePos);
	
					// この位置にタグはあるんけ？
					if (selectTag) {
						if (ScenarioTextDraw.SelectTagInfo.pos == prePos) {
Log.print("FOUND select TAG");
							// ここで選択肢だ！
							selectWait = true;
							readstop = true;

							// 選択肢ダイアログ生成						
							m_selDialog = createSelection("img/dialog/dialog_select.txt");
							
							// マウス入力排他
							mouse.setGuardTime( 60 );
							Log.print("selecting pos");
							break;
						}
					}
	
//Log.print("TARGET TAG INDEX = %s : pos =%s, prePos = %s ", analyzedTag, tag[analyzedTag].pos, prePos);
					if (tag[analyzedTag].pos != prePos) {
						// todo
						if (textphase.get() > prePos) {
							// 次の解析にかかる
							prePos++;
							continue;
						}
//Log.print("BREAK ! tag[analyzedTag].pos != prePos");
						break;
					}
	
					char[] strTag = toMBS(tag[analyzedTag].tag);
					lineparser.setLine(strTag);
					char[] cc = lineparser.getStr();
					char[] strErorr;
	
					// タグは小文字化して探査しよう
					foreach (inout char c; cc) {
						c = std.ctype.tolower(c);
					}
	
					int found = -1; // not found marker
					for(int i=0;i<tags.length;++i){
						if ( cc == tags[i]) {
							found = i;
//Log.print("find tag : %s",i);
							break;
						}
					}
	
					//	このタグの処理
					switch(found) {
					case -1:
						Log.printWarn("UnkownTag<%s>",cc);
						if (textphase.get() > prePos) {
							// 次の解析にかかる;
							break;
						}
						break;
	
					case 0: // <ReadStop> : 読み止まり
						Log.print("<ReadStop>");
						tagReadStop();
						break;
	
					case 1: // <Chara_In> : キャライン
						Log.print("<Chara_In>:%s",strTag);
						tagCharaIn();
	
						// 解析終了
						// スキップだったらキャラのエフェクトを終わらせておく
						if (skip) {
							Log.print("CHARAIN EFFECT FINISH FORCE!!");
							charaEffectFinish();
						}
	
						break;
	
					case 2: // <Chara_Out> : キャラアウト
						Log.print("<Chara_Out>");
						tagCharaOutSprite();
	
						// 解析終了
						// スキップだったらキャラのエフェクトを終わらせておく
						if (skip) {
							Log.print("CHARAOUT EFFECT FINISH FORCE!!");
							charaEffectFinish();
						}
						break;
	
					case 3: // <BG> : 背景描画
						Log.print("<BG>");
						tagBG();
	
						// 解析終了
						if (skip) {
							bgEffectFinish();
						}
						break;
	
					case 4: // <Voice> : Voiceのプレイ
						Log.print("<Voice>");
						
						// 行単位の場合、複数再生される可能性があるのでキャンセル
						if (optionInfo.getTextSpeed() != optionInfo.TextSpeed.LINE ) {
							tagVoice();
						}
						break;
	
					case 5: // <SE> : ＳＥの再生
						Log.print("<SE>");
						tagSe();
						break;
	
					case 6: // <SE_Stop> : ＳＥ停止
						Log.print("<SE_Stop>");
						tagSeStop();
						break;
	
					case 7: // <BGM_Play> : ＢＧＭの再生
						Log.print("<BGM_Play>");
						tagBgmPlay();
						break;
	
					case 8: // <BGM_Stop> : ＢＧＭの停止
						Log.print("<BGM_Stop>");
						tagBgmStop();
						break;
	
					case 9: // <Wait> : エフェクト処理待機
						Log.print("<Wait>");
						wait = true;
						break;
	
					case 10:
						// Fix 右クリックスキップであればvoiceWaitは無効
						if (skip) break;
						// Fix 行単位の場合は、voiceは再生されない
						if (optionInfo.getTextSpeed() == optionInfo.TextSpeed.LINE ) break;
						
						Log.print("<VoiceWait>");
						voiceWait = true;
						voiceWaitPos = tag[analyzedTag].pos;
						Log.print("%s",tag[analyzedTag].pos);
						break;
	
					case 11:	// <HeadLine> 有効
						Log.print("<HeadLine>");
						tagHeadline();
						break;
	
					case 12:	// <e> 有効
						Log.print("<e>");
						tage();
						break;
					case 13:	// <hr> 無視
						// 無視
						break;
					default:
						assert(false);
					}
	
					// 解析済みリストを保持する
					if (found != -1) {
	//					analyzedTagList ~= tag[analyzedTag];
					}
	
					// 解析タグを進める
					analyzedTag++;
					if (analyzedTag>=tag.length) {
						Log.print("This page tags were analyzed!");
						break;
					}
	
					// readstopだったらこれ以上解析する必要はない
//					if (readstop && !skip) 
					if (readstop) {
//						Log.print("readstop break!%s",analyzedTag-1);
						break;
					}
					
					if (voiceWait) {
						Log.print("voiceWait break");
						break;
					}
				}	// while
	
				// スキップはここで終わりやん
				skip = false;

			}	// if


		} catch (Exception e) {
			Log.printFatal("Exception %s#analyzeTag:[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}

		return true;
	}


	/// タグ<ReadStop>を処理する
	/// @return 読み込みに成功したか
	bool tagReadStop() {

		readwait = -1;
		try {
			if (!lineparser.isEnd()) {
				// Fix スキップ時は ReadStop,Timeを無視
				if (skip) return true;
				readwait = cast(int) lineparser.getNum(-1);
				readWaitTimer.reset();
			} else {
				readwait = -1;
			}
	
			// 行単位出力
			if (textLine) {
				if (readwait != -1) {
					Log.print("textLine:<ReadStop,time>#skip!");
					readwait = -1;
					return true;
				}
			}
	
			// 行き過ぎた分巻き戻し
			if (!textPage) {
				textphase.opAssign(tag[analyzedTag].pos);
//				Log.print("readstop pos:%s",prePos);
			}
	
			readstop = true;
		} catch (Exception e) {
			Log.printFatal("Exception %s#tagReadStop :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}

		return true;
	}

	/// タグ<CharaIn>を処理する
	/// @return 読み込みに成功したか
	bool tagCharaIn() {
		return tagCharaInSprite();
	}

	/// タグ<CharaIn>を処理する
	/// @return 読み込みに成功したか
	bool tagCharaInSprite() {
		try {
			static final const int REPLACE_EFFECT_FORONT = 22;
			static final const int REPLACE_EFFECT_BACK = 23;

			char[] strCharNo,strCharNoOld = null;
			strCharNo = lineparser.getStr();
			char[] strPos = lineparser.getStr();
			int	   stand_pos;
			char[] strEffect;
			int    effectNo = -1;
			int    effectNoBack = -1;
			int	   slide = 0;
			bool charin_wait = false;
			int replacePreSpeed = 1; // 置換時の前画像のフェード速度（これは今表示するのと独立）

			// コンパクションしておき、空き状態がないことを保証する			
			compactionCharaArray();
	
			// スライドインの取得
			if ( !lineparser.isEnd() ) {
				slide = cast(int) lineparser.getNum(0);
			}
	
			// エフェクト番号の取得
			if ( !lineparser.isEnd() ) {
				strEffect = lineparser.getStr();
	
				// 小文字化して判定する
				foreach (inout char c; strEffect) {
					c = std.ctype.tolower(c);
				}
	
				for(int i=0; i < effect.length; ++i) {
					if (strEffect == effect[i]) {
						effectNo = i;
						break;
					}
				}
				if (effectNo == -1) {
					Log.printError("<Chara_In,EffectNo>param [%s] is invalid!",strEffect);
					return false;
				}
				
				effectNoBack = effectNo;
			}
	
			// エフェクトスピードの取得
			int effectspd;
			if ( !lineparser.isEnd() ) {
				effectspd = cast(int) lineparser.getNum(int.max);
				if (effectspd == int.max || effectspd == 0) {
					effectspd = 1;
				}
			} else {
				effectspd = 2;
			}
			
			// 20060305追加
			// キャラクターの描画色
			Color4ub color;
			if (!lineparser.isEnd() ) {		// 省略可能
				color = Color4ub.valueOf( lineparser.getStr() );
				Log.printLook("chara color setting : %s", color.toString());
			}

			/// 処理待ちウェイトの取得
			if ( !lineparser.isEnd() ) {
				charin_wait = (lineparser.getNum(0) == 0) ? false : true;
				if (charin_wait) {
					Log.print("CharaInWait!");
					textphase.opAssign(prePos);
				}
			}
	
			// 置き換えようキャラ番号の取得
			if ( !lineparser.isEnd() ) {
				strCharNoOld = lineparser.getStr();
			} else {
				strCharNoOld = null;
			}
	
			// 置き換えか？
			bool replace = (strCharNoOld is null) ? false : true;
			
			int found = -1;	// not faound marker
			if (replace) {
				
				// 表情変更か？
				if ( !isCharaPoseChange(strCharNo, strCharNoOld) ) {
					Log.print("### CHARA expression CHANGE ###");
					replacePreSpeed = 3;
					
					// TODO 20060408 にちのさんの背景ぼかし対応というか、表情入れかだったら
					// 特にきにせずさっといれかたら？
					{
						effectNo = REPLACE_EFFECT_FORONT;
						effectNoBack = REPLACE_EFFECT_BACK;
						replacePreSpeed = 1;	// 同速度
					}
				}
				
				// スタック状に積んであるので、逆巡回でなければならない
				// たとえば、同じキャラIDが存在するとき、置換命令は、直近のキャラに対してと考える
				for(int i = charaSprite.length-1; i >= 0; --i) {
					// 置換指定キャラを探す
					if ( !charaSprite[i].isUse() ) continue;
					if (charaSprite[i].getCharId() == strCharNoOld) {
						found = i;
						break;
					}
				}
			} else {
				for(int i = 0; i < charaSprite.length; ++i) {
					// あいているところに登録
					if ( !charaSprite[i].isUse() ) {
						found = i;
						break;
					}
				}
			}
	
			if (found==-1 && !replace) {
				Log.printError("ERROR CharaIn[] is FULL!");
				return false;
			} else if (found==-1 && replace){
				Log.printError("CharaInfo Not Find!");
				Log.printError("ERROR CharaID:%s is Not Found in CharaInfo[]!",strCharNoOld);
				return false;
			}
	
			// キャラ指定
			charaSprite[found].setCharId(strCharNo);
			charaSprite[found].setSpriteDefFile(strCharNo);
	
			// プリセットポジションの設定
			for(int j=0; j < presetPos.length; ++j) {
				if (strPos == presetPos[j]) {
					charaSprite[found].preset_pos = j;
					break;
				}
			}
	
			// 座標指定
			if (charaSprite[found].preset_pos == -1){
				charaSprite[found].pos = cast(int) atoi(strPos);
			}
	
			// スライドの設定
			charaSprite[found].slide = slide;
	
			// スライド位置の算出
			if (slide != 0) {
				Texture t = tl_shil.get( charaSprite[found].getTextureNo() );
				assert( !(t is null) );
				float tx = t.getWidth();
				int x;
	
				// 終着位置の割り出し
				if (charaSprite[found].preset_pos != -1) {
					switch (charaSprite[found].preset_pos) {
					case 0: x = cast(int)(160 - tx / 2); break;
					case 1: x = cast(int)(320 - tx / 2); break;
					case 2: x = cast(int)(480 - tx / 2); break;
					default:
						Log.printError("undefined pos : %s", charaSprite[found].preset_pos);
						assert(false);
					}
				} else {
					x = charaSprite[found].pos;
				}
	
				// スライド開始位置
				if (slide > 0) {
					int sx = cast(int)(x - (tx * 0.5) * slide);
					charaSprite[found].move_x = cast(int)-((tx * 0.5) * slide);
//					Log.print("TexNo %s width %s, slide %s move_x: %s ", charaSprite[found].getTextureNo(), tx, slide, charaSprite[found].move_x);
					charaSprite[found].charaMove.set(128,0,1);
				} else if (slide < 0) {
					slide = -slide;
					charaSprite[found].move_x = cast(int)((tx / 2) * slide);
					charaSprite[found].charaMove.set(128,0,1);
				}
			}
	
			charaSprite[found].wait = textPage ? false : charin_wait;

//			charaSprite[found].effectNo = effectNo;
//			charaSprite[found].setReplace(replace);
//			// 20060326 キャラクター置換速度を分離した
//			charaSprite[found].fadeIn(effectspd, replacePreSpeed);


		
			// 上の３つをこれで表現
			if (replace) {
				charaSprite[found].setEffectReplace(
					effectNo,effectspd,
					effectNoBack, effectspd);
			} else {
				charaSprite[found].setEffect(effectNo,effectspd);
			}
			
			
			
			charaSprite[found].setColor(color);
			
			// fordebug 
			dumpCharaState();
			
		} catch (Exception e) {
			Log.printFatal("Exception %s#tagCharaInSprite :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
		return true;
	}
	
	/// IDを認識しポーズが変更されたかを返却する
	/// キャラクター識別子(a-z,A-Z)＋キャラクターポーズ識別子(1-9)１文字＋表情識別子(1-9)２文字
	static bool isCharaPoseChange(char[] charNow, char[] charPre) {
		// キャラが違うのであれば、すべて違うはず
		if (getCharaIDName(charNow) != getCharaIDName(charPre)) {
			return true;
		}
		char[] nowNum = getCharaIDNumber(charNow);
		char[] preNum = getCharaIDNumber(charPre);
		
		// ポーズ識別子が別であるか？
		return nowNum[0] != preNum[0];
	}
	
	/// キャラクターIDよりID番号部分を返却する
	static char[] getCharaIDNumber(char[] charId) {
		char[] numBuffer;
		foreach (char c; charId) {
			if ( !isDigit(c) ) {
				continue;
			}
			numBuffer ~= c;
		}
		return numBuffer;
	}

	/// キャラクターIDよりID識別子部分を返却する
	static char[] getCharaIDName(char[] charId) {
		char[] nameBuffer;
		foreach (char c; charId) {
			if ( isDigit(c) ) {
				break;
			}
			nameBuffer ~= c;
		}
		return nameBuffer;
	}
	
	/// 引数で下キャラクターが数字かどうか
	static bool isDigit(char c) {
		foreach (inout char digit; std.string.digits) {
			if (digit == c) {
				return true;
			}
		}
		return false;
	}
	
	/// キャラクターの表示状態をダンプする
	void dumpCharaState() {
		foreach (inout CharaSprite c; charaSprite) {
			Log.print("ID : %s, USE : %s REPLACE : %s WAIT : %s, E_IN : %s, E_OUT : %s", 
				c.getCharId(), c.isUse(), c.isReplace, c.wait, c.isEffectIn, c.isEffectOut);
		}
	}
	
	/// キャラクターの配列の空き領域を詰めて、スタック状態を構築する
	void compactionCharaArray() {
		Log.print("EXECUTE CAHAR ARRAY COMPACTION");
		CharaSprite[] tmp = new CharaSprite[charaSprite.length];
		int i = 0;
		int j = charaSprite.length-1;
		foreach (inout CharaSprite c; charaSprite) {
			if (c.isUse()) {
				tmp[i++] = c;
			} else {
				tmp[j--] = c;
			}
		}
		charaSprite = tmp;
	}

	/**
	 タグ<CharaOut> を処理する 
	 */
	bool tagCharaOutSprite() {
		try {
			char[] strChar = lineparser.getStr();	// キャラＩＤ
			CharaSprite c = null;
			bool charin_wait = false;
			int slide = 0;
	
			// 立ちキャラが一人だけだったらそいつに決定
			int count;
			foreach(inout CharaSprite tmp; charaSprite) {
				if ( tmp.isUse() ) {
					count++;
					c = tmp;
				}
			}
			// 二人以上おるやん！
			if (count > 1) {
				// 指定キャラを探す
				// 同じキャラが同時に描画されている時には問題がある
				// コンテナ１にイン
				// コンテナ１にをアウトエフェクト
				// コンテナ２に同じキャラをイン
				// コンテナ２のキャラをアウト
				// このときコンテナ１のアウトエフェクトが終わっていないと
				// 二重にアウトしようとし、コンテナ２のキャラはずっと消えない
				// 従って、ここで、アウトエフェクトしているものは、対象としないべきだ				
				for(int i = 0; i < charaSprite.length; ++i) {
					if ( !charaSprite[i].isUse() || charaSprite[i].isEffectOut()) continue;
	
					if (charaSprite[i].getCharId() == strChar) {
						c = charaSprite[i];
						Log.print("CHAR OUT IDEX : %s", i);
						break;
					}
				}
			}
			if (!c) {
				Log.print("ERROR CharaOut:[%s] is not found!",strChar);
				Log.print("--------------DebugPrint-----------------");
					for(int i=0; i < charaSprite.length; ++i) {
						Log.print("%s:Char:%s",i, charaSprite[i].getCharId());
					}
				Log.print("--------------DebugPrint-----------------");
	
				return false;
			}
	
			// スライドインの取得
			if ( !lineparser.isEnd() ) {
				slide = cast(int) lineparser.getNum(0);
			}
	
			// エフェクト番号の取得
			int effectNo = -1;
			if ( !lineparser.isEnd() ) {
				char[] str = lineparser.getStr();
	
				// 小文字化して判定する
				foreach (inout char ch; str) {
					ch = std.ctype.tolower(ch);
				}
	
				for(int i=0; i < effect.length; ++i) {
					if (str == effect[i]) {
						effectNo = i;
						break;
					}
				}
				
				if (effectNo == -1) {
					Log.printError("EffectNo is invalid");
				}
			}
	
			// テクスチャの幅が欲しい
			Texture t = tl_shil.get( c.getTextureNo() );
			float tx = t.getWidth();
	
			// スライド開始位置
			if (slide > 0) {
				c.move_x = cast(int)( (tx * 0.5) * slide );
				c.charaMove.set(0, 128, 1);
			} else if (slide<0) {
				slide = -slide;
				c.move_x = cast(int)-( (tx / 2) * slide );
				c.charaMove.set(0, 128, 1);
			}
	
			// エフェクトスピードの取得
			int effectspd;
			if ( !lineparser.isEnd() ) {
				effectspd = cast(int) lineparser.getNum(int.max);
				if (effectspd == int.max || effectspd == 0) {
					effectspd = 2;
				}
			} else {
				effectspd = 2;
			}
			
			// カラーは捨てる...ダサー
			if ( !lineparser.isEnd() ) {
				lineparser.getStr();
			}
				
			/// 処理待ちウェイトの取得
			if ( !lineparser.isEnd() ) {
				charin_wait = (lineparser.getNum(0) == 0) ? false : true;
			}
	
			if (effectNo == -1) {
				c.reset();
			} else {
				c.wait = charin_wait;
				c.fadeOut(effectNo, effectspd);
			}
	
			// コンパクション実行
			compactionCharaArray();
			
			// fordebug 
			dumpCharaState();
			
			
		} catch (Exception e) {
			Log.printFatal("Exception %s#tagCharaOutSprite :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
		return true;
	}

	/// タグ<BG>を処理する
	/// @return 読み込みに成功したか
	bool tagBG() {
		bgNoOld = bgNo;
		try {
			try {		
				bgNo = std.conv.toInt( lineparser.getStr() );
			} catch (Error e) {
				Log.printFatal("invalied TAG <BG>!");
				return false;
			}
	
			if (bgNo == -1) {
				Log.printError("invalied TAG <BG>!");
				return false;
			}
	
			// エフェクト取得
			if (!lineparser.isEnd() && (!textLine||!textPage)) {
				char[] str = lineparser.getStr();
				foreach (inout char c; str) {
					c = std.ctype.tolower(c);
				}
				int effectNo = -1;
				for(int i=0; i < effect.length; ++i) {
					if (str == effect[i]) {
						effectNo = i;
						break;
					}
				}
				bgEffectNo = effectNo;
			} else {
				bgEffectNo = -1;
			}
	
			// エフェクトスピードの取得
			int effectspd;
			if ( !lineparser.isEnd() ) {
				effectspd = cast(int) lineparser.getNum(int.max);
				if ( (effectspd == int.max) || (effectspd == 0) ) {
					effectspd = 1;
				}
			} else {
				effectspd = 1;
			}
	
			// 待機フラグ取得
			if ( !lineparser.isEnd() ) {
				bg_wait = (lineparser.getNum(int.max) == 1 ? true : false);
			} else {
				bg_wait = false;
			}
			// エフェクトスピード取得
			bgEffect.set(0, 255, effectspd);
		} catch (Exception e) {
			Log.printFatal("Exception %s#tagBG :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
		
		return true;
	}

	/// タグ<BGM_Play>を処理する
	/// @param noAction 読み込みのみ行ってプレイはしないようにするか
	/// @return 読み込みに成功したか
	bool tagBgmPlay(bool noAction = false) {
		bgmNo = -1, bgmVol = -1;
		bgmFadeTime = -1;

		try {
			bgmNo = cast(int) lineparser.getNum(-1);
			bgmVol = cast(int) lineparser.getNum(-1);
	
			if ( (-1 == bgmNo) || (-1 == bgmVol) ) {
				Log.printError("invalied TAG <BGM_Play>");
				return false;
			}
	
			if (!lineparser.isEnd()) {
				bgmFadeTime = cast(int) lineparser.getNum(-1);
			}
	
			if (!noAction) {
				y4d_result result;

				bgmLoader.get(bgmNo);
				bgmLoader.get(bgmNo).setLoop(-1);

				if (useBgm) {
					result = bgmLoader.get(bgmNo).setVolume(cast(int)(bgmVol*bgmMasterVol));
				}
				if ( y4d_result.no_error != result ) {
					Log.printError("Can't set bgm volume : %s", getErrorName(result));
				}

				if ( (bgmFadeTime == -1) || (bgmFadeTime == 0) ) {
					result = bgmLoader.playBGM(bgmNo);
				} else {
					result = bgmLoader.playBGMFade(bgmNo, bgmFadeTime);
				}
				if ( y4d_result.no_error != result ) {
					Log.printError("Can't play bgm : %s", getErrorName(result));
				}
	
				Log.print("tagBgmPlay : NO=%s VOL=%s", bgmNo, cast(int)(bgmVol*bgmMasterVol));
			} else if (!bgmLoader.isEnableSound()) {
				y4d_result result = bgmLoader.playBGM(bgmNo);
				if ( y4d_result.no_error != result ) {
					Log.printError("Can't play bgm(sound disable) : %s", getErrorName(result));
				}
			}
	
			bgmLoop = true;
		} catch (Exception e) {
			Log.printFatal("Exception %s#tagBgmPlay :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
		return true;
	}

	/// タグ<BGM_STOP>を処理する
	/// @param noAction 読み込みのみ行ってストップはしないようにするか
	/// @return 読み込みに成功したか
	bool tagBgmStop(bool noAction=false) {
		bgmNo = 1;
		int fade = -1;

		try {
			bgmNo = cast(int) lineparser.getNum(-1);
	
			if (-1==bgmNo) {
				Log.printError("invalied Tag <BGM_STOP>");
				return false;
			}
	
			if ( !lineparser.isEnd() ) {
				fade = cast(int) lineparser.getNum(fade);
			}
	
			if (!noAction || playingBgm) {
				if (fade==0) {
					bgmLoader.stopBGM();
				} else {
					bgmLoader.stopBGMFade(fade);
					Log.print("BGM_STOP : no:%s, fade:%s", bgmNo, fade);
				}
				// 前のページから鳴っていたＢＧＭをとめた
				playingBgm = false;
			} else if (!bgmLoader.isEnableSound()) {
				bgmLoader.stopBGM();
			}
	
			bgmNo = -1;
			bgmVol = -1;
			bgmLoop = false;
		} catch (Exception e) {
			Log.printFatal("Exception %s#tagBgmStop :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}

		return true;
	}

	/// タグ<SE>を処理する
	/// @return 読み込みに成功したか
	bool tagSe() {
		int seNo = -1, vol = -1, time = -1;

		try {
			seNo = cast(int) lineparser.getNum(-1);
			vol = cast(int) lineparser.getNum(80);
	
			if (-1 == seNo) {
				Log.printError("invalied Tag<SE>");
				return false;
			}
	
			if ( !lineparser.isEnd() ) {
				time = cast(int) lineparser.getNum(time);
	
				if (time != -1) {
					--time;
				}
			}
	
			if (useSe) {
				seLoader.get(seNo).setLoop(time);
				seLoader.get(seNo).setVolume(cast(int)(vol * seMasterVol));
				seLoader.get(seNo).play();
			}
	
			Log.print("tagSe : NO=%s VOL=%s", seNo, cast(int)(vol * seMasterVol));
	
			this.seNo = seNo;
			seVol = vol;
			seLoop = time;
		} catch (Exception e) {
			Log.printFatal("Exception %s#tagSe :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}

		return true;
	}

	/// タグ<se_stop>を処理する
	/// @return 読み込みに成功したか
	bool tagSeStop() {
		seVol = -1;
		seLoop = -1;

		try {
			seNo = cast(int) lineparser.getNum(-1);
	
			if (-1==seNo) {
				Log.printError("invalied Tag<SeStop>");
				return false;
			}
	
			// 再生中であればとめる
			if (seLoader.get(seNo).isPlay()) {
				seLoader.get(seNo).stop();
			}
	
			seNo = -1;
			seVol = -1;
			seLoop = -1;
		} catch (Exception e) {
			Log.printFatal("Exception %s#tagSeStop :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}

		return true;
	}

	/// タグ<Voice>を処理する
	/// @return 読み込みに成功したか
	bool tagVoice() 
	in
	{
	}
	body
	{
		if (voiceLoader is null) {
			Log.print("unset voice loader");
			return true;
		}
		Log.print("tagVoice");
		try {
			voicePlayWait = false;	// 消しとく
			char[] voiceId = lineparser.getStr();
			if (voiceId is null) {
				Log.printError("tagVoice Param Error : voieId is null");
				return false;
			}
			
			int vol;
			if (voiceId in voiceMap) {
				voiceNo = voiceMap[voiceId];
				vol = cast(int) lineparser.getNum(128);
			} else {
				voiceNo = -1;
				Log.printError("Not Found Voice ID : %s", voiceId);
				
	//			char[][] keys = voiceMap.keys;
	//			foreach (char[] key; keys) {
	//				Log.print("dump voiceMap: [%s]", key);
	//			}
				
				return false;
			}
	
			// 未指定
			if (-1==voiceNo) {
				Log.printError("<Voice, voiceID, vol>!");
				return false;
			}
			
			int no = voiceNo;
			if ( useVoice && !fastSkipToReadPos) {
				y4d_result result;
				// voiceの再生
				voiceLoader.stopBGM();
				if (voiceLoader.getInfoList().size() <= voiceNo) {
					Log.printWarn("voicelist or voicemap is invalid:%s", voiceNo);
					voiceNo = -1;
					return false;
				}
				
				voiceLoader.get(voiceNo).setVolume( cast(int)(vol*voiceMasterVol) );
				
				if ( isCharaWait() ) {
					Log.printLook("VOICE PALY WAIT!!");
					voicePlayWait = true;
				} else {
					result = voiceLoader.playBGM(voiceNo);
					if ( y4d_result.no_error != result ) {
						Log.printError("Can't play voice : %s", getErrorName(result));
					}
				}
			}
	
			Log.print("VoicePlay : id=%s vol=%s innerId=%d",voiceId, cast(int)(vol*voiceMasterVol), no );
		} catch (Exception e) {
			Log.printFatal("Exception %s#tagVoice :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}

		return true;
	}
	
	/// タグ<e>を処理する
	/// @return 読み込みに成功したか
	bool tagHeadline() {
		try {
			invokeHeadline = cast(int) lineparser.getNum(-1);
		} catch (Exception e) {
			Log.printFatal("Exception %s#tagHeadline :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
		if (invokeHeadline==-1) return false;
		return true;
	}

	/// タグ<e>を処理する
	/// @return 読み込みに成功したか
	bool tage() {
		try {
			nextSceneID = lineparser.getStr();
			analyzeNextScene();
			Log.print("nextSceneID:%s", nextSceneID);
			if (nextSceneID is null) return false;
		} catch (Exception e) {
			Log.printFatal("Exception %s#tage :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
		return true;
	}
	
		/// <e>で指定された値を調べます
	void analyzeNextScene() {
		
		//	どのタグに該当するかを調べる
		static const char[][] tags = [
			"story01",	 	// 0 .ストーリー０１ 1-1
			"story02",		// 1. ストーリー０２ 2-0	
			"story03",		// 2. ストーリー０３ 2-1
			"story04",		// 3. ストーリー０４ 2-2
			"story05",		// 4. ストーリー０５ 3
			"story06",		// 5. ストーリー０６ 4
			"story07",		// 6. ストーリー０７ 5
			"story08",		// 7. ストーリー０８ 6
			"story09",		// 8. ストーリー０９
			"kyojin01",		// 9. 去人ストーリー０１
			"kyojin02",		//10. 去人ストーリー０２
			"kyojin03",		//11. 去人ストーリー０３
			"ending01",		//12. エンディング１
			"ending02",		//13. エンディング２
			"ending03",		//14. エンディング３
			"story2ch01",	//15. ２ｃｈストーリー０１
			"telop",		//16. テロップ
			"title",		//17. タイトル画面へ
		];

		try {
			// タグは小文字化して探査しよう
			foreach (inout char c; nextSceneID) {
				c = std.ctype.tolower(c);
			}
	
			int found = -1; // not found marker
			for(int i=0;i<tags.length;++i){
				if ( nextSceneID == tags[i]) {
					found = i;
					break;
				}
			}
			
// printf("found = %d id=[%*s] \n",found, nextSceneID );
	
			switch (found) {
			case -1:
				// もしリクエストが数字であれば、そのままリクエストとする
				try {
					int n = std.conv.toInt(nextSceneID);
					pushTaskReq( n );
				} catch (ConvError e) {
					// 数字ではなかった
					Log.printError("UnkownID:%s\n", nextSceneID);
				}
				break;
			case 0:
				pushTaskReq( KyojinConst.Task.Task_EyeCatche );
				break;
	
			case 1:
				pushTaskReq( KyojinConst.Task.Task_EyeCatche );
				m_paramData = new int[1];
				m_paramData[0] = 1;	// シナリオ２へ
				break;
	
			case 2:
				pushTaskReq( KyojinConst.Task.Task_EyeCatche );
				m_paramData = new int[1];
				m_paramData[0] = 2;	// シナリオ2-1へ
				break;
	
			case 3:
				pushTaskReq( KyojinConst.Task.Task_EyeCatche );
				m_paramData = new int[1];
				m_paramData[0] = 3;	// シナリオ2-2へ
				break;
	
			case 4:
				pushTaskReq( KyojinConst.Task.Task_EyeCatche );
				m_paramData = new int[1];
				m_paramData[0] = 4;	// へ
				break;
	
			case 5:
				pushTaskReq( KyojinConst.Task.Task_EyeCatche );
				m_paramData = new int[1];
				m_paramData[0] = 5;	// へ
				break;
	
			case 6:
				pushTaskReq( KyojinConst.Task.Task_EyeCatche );
				m_paramData = new int[1];
				m_paramData[0] = 6;	// へ
				break;
	
			case 7:
				pushTaskReq( KyojinConst.Task.Task_EyeCatche );
				m_paramData = new int[1];
				m_paramData[0] = 7;	// へ
				break;
	
			case 8:
				pushTaskReq( KyojinConst.Task.Task_EyeCatche );
				m_paramData = new int[1];
				m_paramData[0] = 8;	// へ
				break;
	
			case 9:
				pushTaskReq( KyojinConst.Task.Task_EyeCatche );
				m_paramData = new int[1];
				m_paramData[0] = 9;	// へ
				break;
	
			case 10:
				pushTaskReq( KyojinConst.Task.Task_EyeCatche );
				m_paramData = new int[1];
				m_paramData[0] = 10;	// へ
				break;
	
			case 11:
				pushTaskReq( KyojinConst.Task.Task_EyeCatche );
				m_paramData = new int[1];
				m_paramData[0] = 11;	// へ
				break;
	
			case 12:	// ending01
				pushTaskReq( KyojinConst.Task.Task_Ending1 );
				m_paramData = new int[1];
				m_paramData[0] = 12;	// へ
				break;
	
			case 13:	// ending02
				pushTaskReq( KyojinConst.Task.Task_Ending2 );
				m_paramData = new int[1];
				m_paramData[0] = 13;	// へ
				break;
	
			case 14:	// ending03
				pushTaskReq( KyojinConst.Task.Task_Ending3 );
				// 今表示している背景を破棄する
				bgNo = bgNoOld = KyojinConst.BGDef.BLACK;				
				break;
				
			case 15: 	// telop
				pushTaskReq( KyojinConst.Task.Task_Telop );
				// 今表示している背景を破棄する
				bgNo = bgNoOld = KyojinConst.BGDef.BLACK;				
				break;
	
			case 16: 	// telop
				pushTaskReq( KyojinConst.Task.Task_Telop );
				// 今表示している背景を破棄する
				bgNo = bgNoOld = KyojinConst.BGDef.BLACK;				
				break;
			case 17: 	// タイトル画面へ
				pushTaskReq( KyojinConst.Task.Task_EyeCatche );
				break;
	
			default:
				Log.printError("analyzeNextScene():%s",found);
				break;
			}
			nextSceneID = null;
		} catch (Exception e) {
			Log.printFatal("Exception %s#analyzeNextScene :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
	}

	/// 選択肢が選ばれたならそのindex番号が帰ります
	/// なにも選択されなければ -1 が返ります
	int onMoveSelect(Screen screen) {

		try {
			assert( !(m_selDialog is null) );
			m_selDialog.onMove(screen);
			GUIButton[] bts = m_selDialog.getButton();
			for (int i = 0; i < bts.length; ++i) {
				if ( bts[i].isLClick() ) {
					selectWait = false;
					nextSceneID = selectTag[i].nextSceneId;
					analyzeNextScene();
					Log.print("CLICK %s", i);
					return i;
				}
			}
		} catch (Exception e) {
			Log.printFatal("Exception %s#onMoveSelect :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
/*		
		int x,y;
		try {
			mouse.getPos(x,y);
			bool bL = mouse.isLButtonUp();
			
			Log.print("select");
			
			foreach (int i,inout ScenarioTextDraw.SelectTagInfo tagInfo; selectTag) {
				Rect rc;
				rc.left = offsetx.get()+tagInfo.x;
				rc.top = offsety.get()+tagInfo.y;
				rc.right = rc.left + tagInfo.width;
				rc.bottom = rc.top + tagInfo.height;
	
				if (x>=cast(int)rc.left && x<=cast(int)rc.right && y>=cast(int)rc.top && y<=cast(int)rc.bottom) {
					// TODO
					if (bL) {
						Log.print("select!: [%s] , [%s]\n",i, tagInfo.nextSceneId);
						selectWait = false;
						nextSceneID = tagInfo.nextSceneId;
						analyzeNextScene();
						return i;
					} else {
						Log.print("in Forcus!:%s\n",i);
						tagInfo.color.setColor(255,0,0);
					}
				} else {
					tagInfo.color.setColor(255,255,255,128);
				}
			}
	
		} catch (Exception e) {
			Log.printFatal("Exception %s#onMoveSelect :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
*/
		return -1;
	}

	/// 選択肢の描画
	void onDrawSelect(Screen screen) {
		try {
			m_selDialog.onDraw(screen);
		} catch (Exception e) {
			Log.printFatal("Exception %s#onDrawSelect :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
/*
		try {
			foreach (inout ScenarioTextDraw.SelectTagInfo tagInfo; selectTag) {
				screen.setColor(tagInfo.color);
				screen.blt(tagInfo.texture,offsetx.get()+tagInfo.x,offsety.get()+tagInfo.y);
			}
			screen.resetColor();
		} catch (Exception e) {
			Log.printError("Exception %s#onDrawSelect :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
*/		
	}

	/// 背景の描画
	void onDrawBG(Screen screen) {
		Texture t1,t2;

		try {
			if (bgDoubleTrans) {
				t1 = tl_bg.get(KyojinConst.BGDef.BLACK);
			} else {
				t1 = tl_bg.get(bgNo);
			}
			if (t1 is null) {
				Log.printWarn("TEXTURE LOAD FAILURE! %s",bgNo);
				return;
			}
	
			if( bgEffectNo == -1) {
				screen.blt(t1, bgOffsetx.get(), bgOffsety.get());
			} else {
				if (bgEffect.isEnd()) {
					if (bgDoubleTrans) {
						// もいっちょ
						bgNoOld = KyojinConst.BGDef.BLACK;
						bgEffect.reset();
						bgDoubleTrans = false;
					} else {
						screen.blt(t1,bgOffsetx.get(), bgOffsety.get());
						bg_wait = false;
						bgEffectNo = -1;
					}
				} else {
					bgEffect++;
					t2 = tl_bg.get(bgNoOld);
					if (t2) {
						screen.blt(t2,bgOffsetx.get(), bgOffsety.get());
					}
					if (bgEffectNo == 19) {
						Color4ub colorOrg = screen.getColor4ub();
						screen.setColor( 255, 255, 255, bgEffect.get() );
						screen.blt(t1,bgOffsetx.get(), bgOffsety.get());
						screen.setColor(colorOrg);
					} else {
						
						TransBltter.blt(bgEffectNo, screen,
							t1, bgOffsetx.get(), bgOffsety.get(), bgEffect.get());
					}
				}
			}
		} catch (Exception e) {
			Log.printFatal("Exception %s#onDrawBG :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
	}

	/// キャラクターの描画
	void onDrawChara(Screen screen, bool draw = true) {
		try {
			// 描画処理を呼び出すだけ
			foreach (inout CharaSprite c; charaSprite) {
				c.blt(screen, draw);
			}
		} catch (Exception e) {
			Log.printFatal("Exception %s#onDrawChara :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
	}
	
	/// フラグの初期化
	void resetFlags() {
		bgmLoop = false;
		voiceWait = false;
		bgmLoop = false;
		playingBgm = false;
		selectWait = false;
		skip = false;
		readstop = false;
		wait = false;
		bg_wait = false;
		selectWait = false;
	}

	/// キャラクタ状態のリセット
	void resetCharacter() {
		try {
			for(int i=0; i < charaSprite.length; ++i) {
				// 初期化しておく
				charaSprite[i].reset();
			}
			// コンパクションしておき、空き状態がないことを保証する			
			compactionCharaArray();
		} catch (Exception e) {
			Log.printFatal("Exception %s#resetCharacter :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}
	}
	
	/// 状態のリセット
	void reset() {
		try {
			if (-1 != bgmNo) {
				bgmLoader.stopBGM();
			}
			if (-1 != seNo) {
				seLoader.get(seNo).stop();
			}
			if (-1 != voiceNo) {
				voiceLoader.stopBGM();
//				voiceLoader.get(voiceNo).stop();
			}
			resetFlags();
		} catch (Exception e) {
			Log.printFatal("Exception %s#reset :[%s]:[%s]", this.toString(), e.toString(), e.msg);
			throw e;
		}

	}
	
	/// 指定のダイアログボックス定義ファイルより設定を読み込み、ダイアログを生成する
	DialogBox createSelection(char[] filename) {
		return DialogBox.createDialogBox(
				this.m_dialogResource,
				filename,
				this.mouse,
				this.key );
	}
	
	
private:
	static final const int GUARD_TIME = 20;
	
	MouseInput mouse;
	KeyInputBase key;
	OptionInfo optionInfo;	//!< ゲームオプション情報
	
	bool init;	//!< 初期化フラグ
	
	ScenarioTextDraw sd;			//!< シナリオビュー
	TextDrawContext context;		//!< シナリオデータモデル
	ScenarioInfo currentScenarioInfo;	//!< シナリオReadモデル
	
	RootCounterS offsetx, offsety;		//!< テキスト描画位置のオフセット
	RootCounterS bgOffsetx, bgOffsety;	//!< テキスト描画位置のオフセット

	// 背景ローダ
	TextureLoader tl_bg;
	// Shilhouetteローダ
	TextureLoader tl_shil;
	// BGMローダ
	SoundLoader bgmLoader;
	// seローダ
	SoundLoader seLoader;
	// ヴォイスローダ
	SoundLoader voiceLoader;
	// フォントローダ
	FontLoader fontLoader;
	int textFontNo;
	int rubiFontNo;
	
	// ダイアログ用のリソースセット
	DialogBoxResources m_dialogResource;

	// プロンプトカーソルテクスチャ
	PromptCarsor promptCarsor;
	SpriteEx	newPageSprite;	//!< 改ページ用認識用のスプライト
	Texture tFilter;			//!< 暗幕フィルタ
	bool m_enableBackFillter;   //!< 暗幕フィルタを使用するかしないか
	RootCounterS filterAlpha;

	// スクリプト解析用パーサ
	LineParser lineparser;
	
	// --- 外部との通信用変数 ---
	bool nextFlg;
	bool readSkip;
	bool fastSkipToReadPos;
	RootCounter fastSkipCounter;
	bool drawText;
	bool drawChara;
	long userReadPos;
	int[] m_paramData;
	
	
	// ---  オプション情報  ---
	bool textLine;
	bool textPage;

	// --- テキスト属性  ---
	ScenarioTextDraw.DrawChar[] drawchar;	//!< 描画する矩形情報
	ScenarioTextDraw.UnknownTagInfo[] tag;	//!< タグ情報
	ScenarioTextDraw.UnknownTagInfo[] analyzedTagList;
	ScenarioTextDraw.SelectTagInfo[] selectTag;
	int analyzedTag;		//!< 解析しているタグのインデックス
	int storyNo;
	int pageCount;			//!< ページ数
	RootCounterS textphase;	//!< 表示しているテキスト文字のインデックス
	int textSpeed;

	int prePos;
	bool skip;
	bool readstop;				//<!< 読み止まり中か
	bool wait;					//<! エフェクト待機中か
	bool bg_wait;				//<! ＢＧのエフェクト待機中か
	FixTimer readWaitTimer;		//!< 自動読み再開タイマ
	int readwait;				//!< 読み止まりwait時間[ms]
	bool selectWait;			//!< 選択肢選択待ち
	bool isScenarioEnd;			//!< 当該シナリオ解析終了
	
	int bgNo = -1;
	int bgNoOld = -1;
	int bgEffectNo;
	bool bgDoubleTrans;
	RootCounterS	  bgEffect;			//<! 背景エフェクトカウント
	
	bool useBgm;
	int bgmNo = -1;
	int bgmVol;
	int bgmFadeTime;
	float bgmMasterVol = 1.0f;
	bool bgmLoop;
	bool playingBgm;

	bool useSe;
	int seNo = -1;
	int seVol;
	float seMasterVol = 1.0f;
	int seLoop;

	// --- VOICE  ---
	bool useVoice;
	bool voiceWait;
	bool voicePlayWait;
	int voiceWaitPos;
	int voiceNo = -1;
	int[char[]]	voiceMap;			//!< ボイスと番号の連想マップ
	float voiceMasterVol = 1.0f;

	// --- 選択肢用 ----
	DialogBox m_selDialog;
	
	// --- キャラクタ ----
	CharaSprite[] charaSprite;	// キャラクタデータコンテナ
	
	// --- TASK_CONTROLE 属性  ---
	vector!(int) vTaskReq;
	
	int invokeHeadline;
	char[] nextSceneID;

	// --- バックログ ----
	bool m_backLog;			//!< バックログ中か
	int  m_backLogDepth;	//!< バックログの深さ
	bool m_logback;
	ScenarioTextDraw.DrawChar[] m_drawcharLog;	//!< 描画する矩形情報


	// --- デバッグ用 ----
	bool m_autoRead;

}
