function Action(fld, props){
	var field=fld;
	this.isRun=false;
	this.timeCritical=false;
	
	var talkStrs=[];
	var talkLine=-1;
	var talkWait=-1; // \w[5]
	var eventChara=null;//イベント発生のキャラクター
	var talkInterval=0;//アクション終了後のインターバル
	//var date=null;
	
	var aiWords=new AiWords();
	//var events=new Object();
	var aiEvents=new AiEvents();

	//var actions=new Array();

	for(var i=0,ac,baseurl=AJAX_BASE_URL+'koropokke/action/',f,o;f=props.actions[i];i++){
D.Out(1,'ActionLoad',baseurl,f);//return 0;

		//o=Dabten.Net.Load(baseurl+f, null, {async:false, force:true,overrideMimeType:'text/plain',headers:{Accept:'text/plain'}});
		o=Dabten.Net.Load(baseurl+f, null, {async:false, force:true});
D.Out(1, 'OK',baseurl);
D.Out(1, baseurl,o.client.status,o.client.responseText.substring(0,100));
		Isolate(o.client);
		o=null;
	}//for
	
	/****/
	this.Init=function(){
		
	};
	/****/
	this.Destroy=function(){
		aiEvents.Destroy();
		aiWords.Destroy();
	};
	
	/****/
	this.Stop=function(){
		if(talkStrs.length<=talkLine){
			talkInterval=0;
			talkStrs.length=0;
			talkLine=-1;
			this.isRun=false;
		}
		return this;
	};

	/****/
	this.Run=function(dt){
		var now=dt.getTime();
//D.Out(1, 'actionRun',talkLine, talkStrs.length, (now-talkInterval), now);
		if(talkLine==-1){
			return this;
		}else if(talkLine && talkStrs.length<=talkLine){
//D.Out('Run if1', talkStrs.length, talkLine, talkInterval, now, now-talkInterval-B_CLOSE_INTERVAL);
			if(talkInterval && ((now-talkInterval-B_CLOSE_INTERVAL)>0)){
//alert('Run'+talkInterval );
//D.Out('Run'+'if, '+talkInterval+', '+now+', '+(now-talkInterval)+', '+B_CLOSE_INTERVAL+';');
				talkInterval=0;
				talkStrs.length=0;
				talkLine=-1;
				for(var i=0,k;k=field.GetKoropokkeByNumber(i);i++){
					k.balloon.Close();
					k.SetPose(0);
				}
				eventChara=null;
				this.isRun=false;
			}else if(!talkInterval){
				talkInterval=now;
//D.Out('Run elseif'+', '+talkInterval+', '+now+', '+(now-talkInterval)+', '+B_CLOSE_INTERVAL+';');
			}else{
//D.Out('Run else'+', '+talkInterval+', '+now+', '+(now-talkInterval)+', '+B_CLOSE_INTERVAL+';');
			}
			this.timeCritical=false;
//D.Out('Run Finished', now);
			return this;
		}else if(!talkStrs.length){
			this.timeCritical=false;
			this.isRun=false;
			return this;
		}
		this.isRun=true;

//D.Out('Run1 '+talkInterval+' '+now+' '+talkStrs[talkLine]);
		if(talkInterval){
			var n=now-talkInterval;
			if(n<talkWait || n<B_LETTER_INTERVAL){
//D.Out('Run talkInterval return');
				return this;
			}
		}
		talkWait=0;
		talkInterval=now;
//D.Out(D.elms[0], talkStrs[talkLine],talkLine,eventChara.IsAnim());
		
		if(!eventChara)
			eventChara=field.GetKoropokkeByNumber(0);
		else if(eventChara.IsAnim())
			return this;
		
		var str=talkStrs[talkLine++];
//D.Out('Run2 str\n'+str+'\n'+talkStrs[talkLine]);
		if(!str)
			return this;

		var c=str.charAt(0);
		if(c==YEN){
			c=str.charAt(1);
			switch(c){
			case 'c':
				eventChara.balloon.Clear();
				return this;
			case 'e':
				talkStrs.length=0;
				return this;
			case 't':
				this.timeCritical=true;
				return this;
			case 'b':
				eventChara.balloon.Close();
				return this;
			case 'p':
				var tmp=str.match(/\[(\d+?)\]/);
				if(tmp)
					eventChara=field.GetKoropokkeByNumber(tmp[1]);
				return this;
			case 's':
				var tmp=str.match(/\[(.+?)\]/);
D.Out(0, 'YENS',tmp.join(','));
				if(tmp){
					eventChara.SetPose(tmp[1]);
					eventChara.Draw(field.field, dt);
				}
				return this;
			case 'r':
				var tmp=str.match(/\[(.+?)\]/)
				if(tmp)
					field.SetScene(tmp[1]);
				return this;
			case 'w':
				var tmp=str.match(/\[(\d+)\]/);
				if(tmp)
					talkWait=tmp[1]*50;
				return this;
			case '_':
				c=str.charAt(2);
				switch(c){
				case 'b':
					field.CloseAllBalloon();
					return this;
				case 'c':
					field.ClearAllBalloon();
					return this;
				case 'p':
					field.ResetAllPose(dt);
					return this;
				}//switch(c=str.charAt(2))
			case '+':
			case '=':
			case '*':
				var tmp=str.match(/\[(.+)\]/);
				if(tmp){
					var p=tmp[1].lastIndexOf(',');
					if(p==-1)
						field.ShowMessage(tmp[1], '', c);
					else
						field.ShowMessage(tmp[1].substring(0, p), tmp[1].substring(p+1), c);
				}
				return this;
			case '-':
				field.ShowMessage('', '', '-');
				return this;
			}//switch(c=str.charAt(1))
		}//if(c==YEN)
		
		eventChara.balloon.Show();
		eventChara.balloon.Talk(str, true);

		return this;
	};
	
	/****/
	this.FreeTalk=function(dt){
		this.GetEvent('OnAITalk', null, dt);
		return this;
	};
	
	/****/
	this.GetEvent=function(grp, cha, dt){
//alert('GetEvent'+cha+' '+grp+' '+this.timeCritical);
		if(this.timeCritical)
			return this;
//D.Out('GetEvent 1');
		field.ClearAllBalloon();
		field.CloseAllBalloon();
//D.Out('GetEvent 2');
		field.action.Stop();
//D.Out('GetEvent 3');
		this.timeCritical=true;
//D.Out(D.elms[0], 'GetEvent',grp,cha);
//		if(cha)
			eventChara=cha;
		if(!dt)
			dt=new Date();
		
		grp=grp.toLowerCase();
		//if(grp.indexOf('on')!=0)
		//	grp='on'+grp;
//D.Out('GetEvent 4');
		if(eventChara){
			talkStrs=this.SsToHtml(eventChara.aiEvents.Get(grp)||aiEvents.Get(grp)||eventChara.aiEvents.Get('onmenu')||aiEvents.Get('onmenu'), dt);
		}else{
			//talkStrs=this.SsToHtml(aiEvents.Get(grp)||aiEvents.Get('onmenu'), dt);
			talkStrs=this.SsToHtml(aiEvents.Get(grp), dt);
		}
//D.Out('GetEvent 5'+talkStrs);
//D.Out(D.elms[0], 'talkStrs',talkStrs.join(' '));
		talkInterval=talkLine=0;

		this.Run(dt||(new Date()));
		return this;
	};//

	/****/
	this.StartTalk=function(str, cha){
		if(this.timeCritical){
			talkInterval=0;
			talkStrs.length=0;
			talkLine=-1;
			for(var i=0,k;k=field.GetKoropokkeByNumber(i);i++){
				k.balloon.Close();
				k.SetPose(0);
			}
//			eventChara=null;
			this.isRun=false;
		}
		this.timeCritical=true;
		//field.action.Stop();
		this.Stop();
		field.CloseAllBalloon();

		var dt=new Date();
//D.Out(D.elms[0],'GetEvent',grp,cha)
		if(cha!=null)
			eventChara=field.GetKoropokkeByNumber(0);
		else
			eventChara=(typeof cha=='number')?field.GetKoropokkeByNumber(0):(typeof cha=='string')?field.GetKoropokkeById(cha):cha;
		
		talkStrs=this.SsToHtml(str, dt);
		talkInterval=talkLine=0;
		
		for(var i=0,k;k=field.GetKoropokkeByNumber(i);i++)
			k.balloon.Clear();

		this.Run(dt);
		return this;
	};//

	/******/
	this.SsToHtml=function(src, dt){
D.Out(D.elms[0],src,escape(src));
		var html=[],tmp,ars=src.split('');
		var reg=/[^\[0-9a-zA-Z]/;
//alert(src);
//alert('_'+' '+'_'.charCodeAt(0));
		for(var i=0,c,tmp;i<ars.length;i++){
			c=ars[i];
//alert(c+' '+c.charCodeAt(0));
//D.Out(D.elms[0],c.charCodeAt(0),(c==BSLASH));
			switch(c){
			case '<':
				tmp='';
				while(++i<ars.length){
					c=ars[i];
					if(c=='>')
						break;
					
					tmp+=c;
				}//while
				if(tmp)
					html.push(tmp);
				break;
			case '':
				break;
			case YEN:
			case BSLASH:
				
				c=ars[i+1];
				switch(c){
				case YEN: //print '\'
				case BSLASH: //print '\'
					html.push(YEN);
					i++;
					break;
				case '%': //print '%'
				case '$': //print '%'
					html.push(c);
					i++;
					break;
				case 'n': //print '\n'
					html.push('<br>');
					i++;
					break;
				case 'e': //end of script
				case 'c': //clear balloon
				case 'b': // close balloon
					html.push(YEN+c);
					i++;
					break;
				case 'd': //diverge expression
				case 'w': //wait
				case 'a': //url anchor
				case 'q': //selection
				case 'p': //charactor change
				case 's': //pose (surface) change
				case 'r': // scene change
				case 'l': //flip
				case '+': //add write board
				case '*': //insert write board
				case '=': //rewrite board
				case '-': //delete board
D.Out(D.elms[0],'YEN',c);
					tmp='';
					var flg=2; // 2:コマンド未オープン、1:コマンドオープン、0:コマンドクローズ
					var t='';
					while(flg && i<ars.length){
						if(t==YEN)
							t=(ars[i]==BSLASH)?YEN:ars[i];
						else{
							//t=ars[i];
							//if(t==BSLASH)
							//	t=YEN;
							t=(ars[i]==BSLASH)?YEN:ars[i];
							if(t=='[' || t==']')
								flg--;
						}
//						if(flg==0 && t=='[') // \s[1[ # 不正、後ろの '[' のみ残す
//							tmp='';
//						else if(flg==1 && reg.test(t)){ // \s[1 の後に ']' 以外の記号 # 不正、後ろの記号のみ残す
//							tmp='';
//							flg=0;
//						}
						i+=+!!flg;
						tmp+=t;
					}//while
//D.Out(D.elms[0],''+tmp);
					if(c=='d' && tmp!=''){ //selection, url anchor
//D.Out(D.elms[0],'c==d', c,tmp, field, eventChara);
						var tmpm=tmp.match('\\[(.+?)\\]');
						if(tmpm && tmpm[1].search(/r(\d+)/)!=-1){
//D.Out(D.elms[0],'tmpm',tmpm[1]].join('_'));
							tmpm[1]=Menu.references[RegExp.$1];
//D.Out(D.elms[0],'replace',eventChara, tmpm[1], escape(tmpm[1]), RegExp.$1);
							//tmpm=tmpm[1].split(',');
							//tmp=(eventChara)?eventChara.aiEvents.Get(tmpm[1])||eventChara.aiEvents.Get('onmenu'):aiEvents.Get(tmpm[1])||aiEvents.Get('onmenu');
							if(eventChara){
								tmp=eventChara.aiEvents.Get(tmpm[1])||aiEvents.Get(tmpm[1])||eventChara.aiEvents.Get('onmenu')||aiEvents.Get('onmenu');
							}else{
								tmp=aiEvents.Get(grp)||aiEvents.Get('onmenu');
							}

							if(tmp){
								tmp=this.SsToHtml(tmp, dt);
//D.Out(D.elms[0],'tmp',tmp);
								ars=ars.slice(0,i).concat(tmp).concat(ars.slice(i+1));
								i--;
								tmp='';
							}
						}
//D.Out(D.elms[0],escape(tmp));
					}else if((c=='q'||c=='a') && tmp!=''){ //selection, url anchor
//D.Out(D.elms[0],c,tmp, field, eventChara);
						var tmpm=tmp.match('\\[(.+?)\\]');
						if(tmpm){
							var refs=tmpm[1].split(',');
							
							if(c=='q'){
								tmpm[1]=refs.shift();
								for(var j=0;j<refs.length;j++)
									refs[j]=refs[j].replace(/'/g, '\\\''); //'
								tmp='<a onclick="Menu.Selection(\''+field.id+'\', \''+eventChara.id+'\', [\''+refs.join('\',\'')+'\']);">'+tmpm[1]+'</a>';
							}else if(c=='a'){
								if(refs.length==1)
									refs[1]=refs[0];
								tmp='<a href="'+refs[0]+'">'+refs[1]+'</a>';
							}
						}
					}//if(c=='k')
					if(tmp)
						html.push(tmp);
					break;
				case '_': // extra commands
//alert('start _ '+c);
					i++;
					c=ars[i+1];
//alert('next ars : '+c);
					switch(c){
					case 'b': //close all balloons
					case 'c': //clear all balloons
					case 'p': // reset all charas' pose
						html.push(YEN+'_'+c);
						i++;
						break;
					case 'g': // rotate radian
					case 'o': //move recover
						tmp='';
						var flg=2; // 2:コマンド未オープン、1:コマンドオープン、0:コマンドクローズ
						while(flg && i<ars.length){
							var t=ars[i];
							if(t==BSLASH)
								t=YEN;
							if(t=='[' || t==']')
								flg--;
							
	//						if(flg==0 && t=='[') // \s[1[ # 不正、後ろの '[' のみ残す
	//							tmp='';
	//						else if(flg==1 && reg.test(t)){ // \s[1 の後に ']' 以外の記号 # 不正、後ろの記号のみ残す
	//							tmp='';
	//							flg=0;
	//						}
							i+=+!!flg;
							tmp+=t;
						}//while
						if(tmp)
							html.push(tmp);
						break;
					case 'x': // wait click on balloon or time
						break;
					case 's': // syncronized session
						break;
					}//switch case '_' ... switch(c=ars[++i])
				} // case YEN: ... switch(c=ars[i+1])
				break;
			case '$':
			case '%':
				tmp='', s='';
				var flg=2; // 2:コマンド未オープン、1:コマンドオープン、0:コマンドクローズ
				while(flg && i<ars.length){
					s=ars[i];
					if(s=='[' || s==']')
						flg--;
					
					if(flg==0 && s=='[') // \s[1[ # 不正、後ろの '[' のみ残す
						tmp='';
					i+=+!!flg;
					if(flg==1 && s!='[')
						tmp+=s;
				}//while
				if(tmp){
					if(c=='$')
						tmp=aiWords.Get(tmp);
					else
						tmp=aiWords.System(tmp, dt);
					
					//ars[i]=tmp;
					tmp=this.SsToHtml(tmp, dt);
					//ars.splice(i , 1, tmp);
					ars=ars.slice(0,i).concat(tmp).concat(ars.slice(i+1));
					i--;
				}//if(tmp)
				break;
			default:
				html.push(c);
				break;
			}//switch(c)
		}//for(ars)
		
		return html;
	};//

/*********/
	function Isolate(client){
		if(client.status!=0 && client.status!=200 && client.status!=304){
			return 0;
		}
D.Out(D.elms[0],'Action Isolate');//return 0;
		var lines=client.responseText.split(LF);
		LINES:for(var l=0,elm,line,r={key:'',val:''},p,dir='',kid='',koroAi,reg=new RegExp('^#','');l<lines.length;l++){
			line=U.Trim(lines[l]);
			if(!line || line.charAt(0)=='#')
				continue LINES;

			if(reg.test(line)){//.test(/^<\/action>/)){
			//ディレクティブ終了
				if(dir.indexOf('word')==0)
					aiWords.Close();
				else if(kid)
					koroAi.Close();
				else
					aiEvents.Close();
				dir=kid='';
				//elm=null;
				continue LINES;
			}else if(dir.indexOf('word')==0){
			// 単語登録、使用するときは$[name]
				aiWords.Add(line);

			//}else if(dir.indexOf('on')==0){
			}else if(dir!=''){
			//イベント
//D.Out(1,dir, kid, line);
				if(kid)
					koroAi.Add(line);
				else
					aiEvents.Add(line);

			}else if(dir=='' && line.charAt(0)=='<'){
			//ディレクティブ
				if(line.charAt(1)=='/'){
				//ディレクティブ終了
					dir='';
				}else{//if(line.charAt(1)=='/')
				//ディレクティブ開始
					var tags=line.substring(1,line.length-1).split(' ');
					var tmp=null;
					//tags[0]=tags[0].toLowerCase();
					dir=tags[0].toLowerCase();//.substring(1);
					
					if(dir.indexOf('word')==0){
						if(tags[1])
							tmp=tags[1].match(/name="(.+?)"/);
						kid=tmp?tmp[1]:'freetalk';
						dir='word';
						aiWords.Create(kid);
					}else if(dir.indexOf('freetalk')==0){
						aiEvents.Create('onaitalk');
						dir='freetalk';
						kid='';
					//}else if(dir.indexOf('on')==0){
					}else{
						if(tags[1])
							tmp=tags[1].match(/name="(.+?)"/);
						kid=tmp?tmp[1]:'';
						
						if(kid){
							var koro=field.GetKoropokkeById(kid);
							if(koro){
								koroAi=koro.aiEvents;
								koroAi.Create(dir);
							}else{
								kid='';
								koroAi=null;
								aiEvents.Create(dir);
							}
						}else
							aiEvents.Create(dir);
//D.Out(1,'create', dir, kid, line);
					}
					reg=new RegExp('^'+YEN+'<'+YEN+'/'+dir+YEN+'>', 'i');
				}//if(line.charAt(1)=='/')
				continue LINES;
				
			}else{ //if(dir)
				// illegular
			}

		}//LINES:for
		return 0;
		
		/*************/
		function GetProp(line, r){
			var p=line.indexOf(' ');
			if(p==-1)
				p=line.indexOf('\t');
			r.key=line.substring(0, p);
			r.val=U.Trim(line.substring(p+1));
			if(r.val=='false')
				r.val=false;
			return !!r.key;
		}//GetProp
	}//Create

};//

/**************/
function AI(){
//	var sentences={};
//	var tmp=[];
//	var tmpgrp='';
	this.sentences={};
	this.tmp=[];
	this.tmpgrp='';
	this.tmpcid='';

	/****/
	this.Destroy=function(){
		for(var i in this.sentences)
			this.sentences[i]=null;
		this.tmp=this.tmpgrp=this.sentences=null;
		return this;
	};

	/****/
	this.Create=function(grp, cid){
		if(this.tmpgrp)
			this.Close();
		if(!grp)
			grp='onfree';
		this.tmpgrp=grp;
		this.tmpcid=cid;
		if(!this.sentences[this.tmpgrp])
			this.sentences[this.tmpgrp]=[];
		return this;
	};
	
	/****/
	this.Close=function(){
		if(this.tmpgrp)
			this.sentences[this.tmpgrp].push(this.tmp.join(YEN_ENU));
//D.Out(1,'Close',escape(this.tmpgrp), this.sentences[this.tmpgrp].length, this.sentences[this.tmpgrp]);

		this.tmp.length=0;
		this.tmpgrp='';
		return this;
	};
	/****/
	this.Add=function(line){
		line=line.replace(/\n$/);
		this.tmp.push(line);
		return this;
	};
	/****/
	this.Get=function(grp, cid){
		if(!grp)
			grp='freetalk';
		grp=grp.toLowerCase();
//D.Out(D.elms[0],'GET',grp, !!this.sentences[grp]);
		if(!this.sentences[grp] || this.sentences[grp].length==0)
			return '';
		var r=Math.floor(Math.random()*(this.sentences[grp].length-1));
//		if(grp=='onmenu')
//			return this.sentences[grp][r]+'\\n'+Dabten.GetHTML(Menu.tplid, {});
//		else
			return this.sentences[grp][r];
	};
}// AI


/*************/
function AiEvents(){
	AI.apply(this, arguments);
//	var sentences={};
//	var tmp=[];
//	var tmpgrp='';
	/****/
	this.Add=function(line){
		line=line.replace(/[\r\n]+$/, '').replace(/\\h/g, '\\p[0]').replace(/\\u/g, '\\p[1]');
		this.tmp.push(line);
		return this;
	};
}//
U.Inherit(AiEvents, AI);

/*************/
function AiWords(){
	AI.apply(this, arguments);
//	var sentences={};
//	var tmp=[];
//	var tmpgrp='';
	
	/****/
	this.Add=function(word){
		if(!this.sentences[this.tmpgrp]||word=='')
			return this;
		this.sentences[this.tmpgrp].push(word);
		return this;
	};
	
	/****/
	this.System=function(grp, dt){
		if(!grp)
			return '';
		grp=grp.toLowerCase();
		if(grp=='username')
			return Config.username;
		else if(grp=='year')
			return dt.getFullYear().toString();
		else if(grp=='month')
			return dt.getMonth().toString();
		else if(grp=='date')
			return dt.getDate().toString();
		else if(grp=='day')
			return ['日','月','火','水','木','金','土'][Math.floor(dt.getDay())];
		else if(grp=='exhm'){
			var now=(dt.getTime()-Config.startTime)*0.001;
			var h=Math.floor(now/3600);
			var m=Math.floor((now%3600)/60);
			return h+'時間'+m+'分';
		}else if(grp=='exm')
			return ''+Math.floor((dt.getTime()-Config.startTime)/60000);
		else if(grp=='exh')
			return ''+Math.floor((dt.getTime()-Config.startTime)/3600000);
		else
			return '';
	};
	
}
U.Inherit(AiWords, AI);

