//=============================================================================
// SoR_EnemySymbolEncounter_MZ.js
// SoR License (C) 2020 蒼竜, REQUIRED User Registration on Dragon Cave
// http://dragonflare.blue/dcave/license.php
// ---------------------------------------------------------------------------
// Latest version v1.72 (2022/12/13)
//=============================================================================
/*:ja
@plugindesc ＜シンボルエンカウント総合＞ v1.72
@author 蒼竜
@target MZ
@url https://dragonflare.blue/dcave/
@help シンボルエンカウント方式のゲームデザインを実現するために
一般のコンシューマゲームのような挙動をモチーフに
次のような一通りの仕組みを単独プラグインで実装します。
- 接敵時の状態に応じた先制攻撃、不意打ちの判定
- 敵シンボルの経路探索および追跡
- 戦闘後の処理(プレイヤーの無敵時間,敵シンボルの復帰)

敵シンボルとするイベントを作成し、メモ欄に
<EnemySymbol>
と記述すると、このプラグインの効能を受けます。
シンボルごとの細かな挙動指定は、各イベント内における
イベントコマンドの「注釈」を用いて行います。

上のタグを記述するだけですぐに最低限の動作が可能ですが、
各製作者のゲームデザインにより沿った挙動をさせるには
様々なカスタマイズ(設定)が必要となります。
詳細な設定方法・応用例はpdfドキュメントを熟読して下さい。

@param -----一般設定-----
@param AutoDisable_DefaultEncounter
@desc true: 通常エンカウント方式の無効化設定をスクリプトが代行します (default: false)
@type boolean
@default false
@param FollowerAttack
@type select
@option 隊列歩行なし／先頭キャラクター以外エンカウント判定なし＆シンボルのフォロワーすり抜けなし
@value 0
@option 先頭キャラクター以外にもエンカウント(接触)判定を付ける
@value 1
@option 先頭キャラクター以外エンカウント判定なし＆シンボルのフォロワーすり抜けあり
@value 2
@option 先頭キャラクター以外エンカウント判定なし＆シンボルのフォロワーすり抜けなし＆フォロワー検知あり
@value 3
@option 先頭キャラクター以外にもエンカウント(接触)判定を付ける＆フォロワー検知あり
@value 4
@option 先頭キャラクター以外エンカウント判定なし＆シンボルのフォロワーすり抜けあり＆フォロワー検知あり
@value 5
@default 0
@desc 「隊列歩行」使用時の、フォロワー考慮に関する敵シンボル挙動設定。隊列歩行を使わない場合はそのままで可
@param InvincibleTime_AfterBattle
@desc 戦闘後の無敵(発見されない、接触しない)フレーム時間 (default: 300)
@default 300
@type number
@decimal
@param EnemySearchRange_scale
@desc 敵シンボル移動経路探索量倍率 (default: 2.0)
@default 2.0
@type number
@decimals 2
@param SymbolKeepOut_RegionID
@desc 敵シンボル通行不可リージョンID, -1で無効 (default: -1)
@default -1
@type number
@min -1

@param -リンクエンカウント-
@param Enable_SymbolLink
@desc true: エンカウント時に付近にシンボルが複数存在した場合、まとめて戦闘を行います。要68.「トループ増援システム」
@type boolean
@default false
@param SymbolInvolvementRange
@desc 敵シンボルをリンクエンカウントに巻き込む範囲。プレイヤー中心の半径 (default: 3)
@type number
@default 3
@min 1 
@param MaximumSymbolInvolvement
@desc リンクエンカウントに巻き込む周辺敵シンボルの最大数。0で制限なし
@type number
@default 0
@min 0

@param -ゲーム演出-
@param BalloonID_PlayerDetected
@desc プレイヤーを発見した時にシンボル頭上に表示するふきだしアイコン, -1で無効 (default: 1)
@default 1
@type number
@min -1
@param SE_PlayerDetected
@desc プレイヤーを発見した時に再生される効果音, 無指定で再生しない
@type struct<SEDATA>
@default {"name":"","volume":"100","pitch":"100","pan":"0"}
@dir audio/se/
@param BalloonID_PlayerFled
@desc プレイヤーを見失った時にシンボル頭上に表示するふきだしアイコン, -1で無効 (default: 6)
@default 6
@type number
@min -1
@param SE_PlayerFled
@desc プレイヤーを見失った時に再生される効果音, 無指定で再生しない
@type struct<SEDATA>
@default {"name":"","volume":"100","pitch":"100","pan":"0"}
@dir audio/se/
@param BalloonID_Evacuate
@desc プレイヤーを発見して，逃走する時にシンボル頭上に表示するふきだしアイコン, -1で無効 (default: 6)
@default 6
@type number
@min -1
@param SE_Evacuate
@desc プレイヤーを発見て，逃走する時に再生される効果音, 無指定で再生しない
@type struct<SEDATA>
@default {"name":"","volume":"100","pitch":"100","pan":"0"}
@dir audio/se/
@param BattleBGM_surprised
@desc 不意打ち状態で戦闘突入時の戦闘BGM, 無指定で変更なし
@dir audio/bgm/
@type struct<BGMDATA>
@default {"name":"","volume":"100","pitch":"100","pan":"0"}
@param -エンカウント発生後表現-
@param InvincibleStyle_Flash
@desc シンボルエンカウント発生後のプレイヤー無敵時間における表示方法(点滅) (default: true)
@type boolean
@default true
@param InvincibleStyle_Opaque
@desc シンボルエンカウント発生後のプレイヤー無敵時間における表示方法(透明) (default: false)
@type boolean
@default false
@param InvincibleStyle_Blend
@desc シンボルエンカウント発生後のプレイヤー無敵時間における表示方法(合成方法変更) (default: 0)
@type select
@option 通常
@value 0
@option 加算
@value 1
@option 乗算
@value 2
@option スクリーン
@value 3
@default 0
*/
/*:
@plugindesc <Symbol Encoutner System> v1.71
@author Soryu
@target MZ
@url https://dragonflare.blue/dcave/index_e.php
@help This introduces essencial and a highly functional suite 
of symbol encounter system which are inspired by existing console RPGs.

Indispensable features for symbol encoutner system such as 
- Preemptive/surprised encoutner based on the direction of players 
  and enemy symbol when they collide
- Route Search and Chase (Optimal or less-smart) for enemy symbols
- Invincible time for the player after battles with enemy symbols
  are implemented on your game with one plugin.

Fundamentaly, you just make an event and write a tag as 
<EnemySymbol> 
in the note to work as an enemy symbol.															  
We can specify the behavior of symbols in detail by writing command in "Comment" command. 
@param ---General---
@param AutoDisable_DefaultEncounter
@desc Set true so that the script disables regular encotner. (default: false)
@type boolean
@default false
@param FollowerAttack
@type select
@option No followers./No encounter except for the leader & Symbols does not pass through followers.
@value 0
@option Encoutner is occurred for all party characters touching to symbols.
@value 1
@option No encounter except for the leader & Symbols can pass through followers.
@value 2
@option No encounter except for the leader & Symbols does not pass through followers & Followers are also detected.
@value 3
@option Encoutner is occurred for all party characters & Followers are also detected.
@value 4
@option No encounter except for the leader & Symbols can pass through followers & Followers are also detected.
@value 5
@default 0
@desc Configuration for symbols in terms of collision with a party with followers.
@param InvincibleTime_AfterBattle
@desc Invincible (never detected and start battles) time (frame) after battle (default: 300)
@default 300
@type number
@param EnemySearchRange_scale
@desc Constant for route search length of enemy symbols (default: 2.0)
@default 2.0
@type number
@decimals 2
@param SymbolKeepOut_RegionID
@desc Region ID which Enemy symbols cannot pass, -1 to disable (default: -1)
@default -1
@type number
@min -1

@param -Link Encoutner-
@param Enable_SymbolLink
@desc If true, battles can involve enemies of multiple symbols around the player. Require 68. SoR_TroopWaveReinforcement_MZ.js
@type boolean
@default false
@param SymbolInvolvementRange
@desc The range, a radius, of enemy symbol involvement for link encounter assuming its center is the player position (default: 3)
@default 3
@min 1
@type number
@param MaximumSymbolInvolvement
@desc Maximum number of enemy symbols can be involved to a link encounter, set 0 for no limit.
@type number
@default 0
@min 0

@param -Screen Direction-
@param BalloonID_PlayerDetected
@desc Balloon icon appeared above the symbol when it detects player, -1 to disable (default: 1)
@default 1
@type number
@min -1
@param SE_PlayerDetected
@desc SE played when the symbol detects player, nothing specified to disable
@dir audio/se/
@type struct<SEDATA>
@default {"name":"","volume":"100","pitch":"100","pan":"0"}
@param BalloonID_PlayerFled
@desc Balloon icon appeared above the symbol when it loses player, -1 to disable (default: 6)
@default 6
@type number
@min -1
@param SE_PlayerFled
@desc SE played when the symbol loses player, nothing specified to disable 
@type struct<SEDATA>
@default {"name":"","volume":"100","pitch":"100","pan":"0"}
@dir audio/se/
@param BalloonID_Evacuate
@desc Balloon icon appeared above the symbol which is about to escape, -1 to disable (default: 6)
@default 6
@type number
@min -1
@param SE_Evacuate
@desc SE played when the symbol being about to escape, nothing specified to disable
@type struct<SEDATA>
@default {"name":"","volume":"100","pitch":"100","pan":"0"}
@dir audio/se/
@type file
@param BattleBGM_surprised
@desc Change battle BGM when players are surprised, nothing specified to disable
@dir audio/bgm/
@type struct<BGMDATA>
@default {"name":"","volume":"100","pitch":"100","pan":"0"}

@param ---Effect After Encoutner---
@param InvincibleStyle_Flash
@desc Drawing style of player during invincible time after symbol encoutners (flash)(default: true)
@type boolean
@default true
@param InvincibleStyle_Opaque
@desc Drawing style of player during invincible time after symbol encoutners (opaque)(default: false)
@type boolean
@default false
@param InvincibleStyle_Blend
@desc Drawing style of player during invincible time after symbol encoutners (blend)(default: 0)
@type select
@option Normal
@value 0
@option Add
@value 1
@option Multiply
@value 2
@option Screen
@value 3
@default 0
*/
/*~struct~BGMDATA:
@type string
@param name
@dir audio/bgm/
@type file
@desc BGM
@param volume
@desc 音量 [0...100]
@type number
@default 100
@min 0
@max 100
@param pitch
@desc ピッチ [50...150]
@type number
@default 100
@min 50
@max 150
@param pan
@desc パン(位相) [-50...50]
@type number
@default 0
@min -50
@max 50
*/
/*~struct~BGMDATAE:
@type string
@param name
@dir audio/bgm/
@type file
@desc BGM
@param volume
@desc Volume of BGM [0...100]
@type number
@default 100
@min 0
@max 100
@param pitch
@desc Pitch of BGM [50...150]
@type number
@default 100
@min 50
@max 150
@param pan
@desc Pan of BGM [-50...50]
@type number
@default 0
@min -50
@max 50
*/
/*~struct~SEDATA:
@type string
@param name
@dir audio/se/
@type file
@desc 効果音
@param volume
@desc 音量 [0...100]
@type number
@default 100
@min 0
@max 100
@param pitch
@desc ピッチ [50...150]
@type number
@default 100
@min 50
@max 150
@param pan
@desc パン(位相) [-50...50]
@type number
@default 0
@min -50
@max 50
*/
/*~struct~SEDATAE:
@type string
@param name
@dir audio/se/
@type file
@desc SE File
@param volume
@desc Voulme [0...100]
@type number
@default 100
@min 0
@max 100
@param pitch
@desc Pitch [50...150]
@type number
@default 100
@min 50
@max 150
@param pan
@desc Pan [-50...50]
@type number
@default 0
@min -50
@max 50
*/
(function() {
const Param = PluginManager.parameters('SoR_EnemySymbolEncounter_MZ');

const AutoDisable_DefaultEncounter = Boolean(Param['AutoDisable_DefaultEncounter'] === 'true') || false;
const FollowerAttack = Number(Param['FollowerAttack']) || 0; 
const InvinsibleDuration_AB = Number(Param['InvincibleTime_AfterBattle']) || 300; 
const SearchRange_scale = Number(Param['EnemySearchRange_scale']) || 2.0;
const SymbolKeepOut_RegionID = Number(Param['SymbolKeepOut_RegionID']) || -1;
const BalloonID_PlayerDetected = Number(Param['BalloonID_PlayerDetected']) || 1; 
const BalloonID_PlayerFled = Number(Param['BalloonID_PlayerFled']) || 6;

const SE_PlayerDetected = convertJsonSE(Param['SE_PlayerDetected']) || '';
const SE_PlayerFled = convertJsonSE(Param['SE_PlayerFled']) || '';
const BattleBGM_surprised = convertJsonSE(Param['BattleBGM_surprised']) || '';

const InvincibleStyle_Flash = Boolean(Param['InvincibleStyle_Flash'] === 'true') || false;
const InvincibleStyle_Opaque = Boolean(Param['InvincibleStyle_Opaque'] === 'true') || false;
const InvincibleStyle_Blend = Number(Param['InvincibleStyle_Blend']) || 0;
//v1.30
const BalloonID_Evacuate = Number(Param['BalloonID_Evacuate']) || 6;
const SE_Evacuate = convertJsonSE(Param['SE_Evacuate']) || '';

function convertJsonSE(param) {
    const obj = JSON.parse(param);
    obj.volume = Number(obj.volume);
    obj.pan = Number(obj.pan);
    obj.pitch = Number(obj.pitch);
    return obj;
}

//v1.70
const isTWR = PluginManager._scripts.includes("SoR_TroopWaveReinforcement_MZ") ? true : false;
const Enable_SymbolLink = Boolean(Param['Enable_SymbolLink'] === 'true') || false;
const isActive_SymbolLink = isTWR && Enable_SymbolLink;
const SymbolInvolvementRange = Number(Param['SymbolInvolvementRange']) || 5;
const MaximumSymbolInvolvement = Number(Param['MaximumSymbolInvolvement']) || 5;


//////////////////////////////////////////////////////////////////////////
//
// Encount Manager
//
//////////////////////////////////////////////////////////////////////////
	const SoR_ESE_BM_onEncounter = BattleManager.onEncounter;
    BattleManager.onEncounter = function() {
        SoR_ESE_BM_onEncounter.call(this);
		this._preemptive = $gameTemp.encflag==2? true : false;
		this._surprise = $gameTemp.encflag==1? true : false;
    }

	const SoR_ESE_GE_lock = Game_Event.prototype.lock;
	Game_Event.prototype.lock = function() {
		const ens = this.event().meta.EnemySymbol;
		if(ens){
			if (this._locked) return;
			this._prelockDirection = this.direction();
			$gameTemp.isEncounteredonMap = ens;
			this.EncountDir();

			if(isActive_SymbolLink==true) BattleManager.searchSEInvolvement(this._eventId); //v1.70
			$gameTemp.JustEncounterID = this._eventId; 
			this._locked = true;
		}
		else SoR_ESE_GE_lock.call(this);
    }
	
	Game_Event.prototype.EncountDir = function() {
		let player = $gamePlayer;
		if($gameTemp.collisionFollower != null) player = $gameTemp.collisionFollower
		
		const p = {x:player.x,y:player.y,dir:player._direction};
		const e = {x:this.x,y:this.y,dir:this._direction};
		$gameTemp.encflag = 0;
		if (p.dir == e.dir){
			AlignSymbolWithDirection(p, e);
			if(e.x > p.x) $gameTemp.encflag = 2;
			else $gameTemp.encflag = 1;
		}
	}
	
	function AlignSymbolWithDirection(p, e){
	    e.x-=p.x;
        e.y-=p.y;
        p.x-=p.x;
        p.y-=p.y;
		while(p.dir!=6){
			//Cyclic subgroup generated by 2 \in Group(Z_{10}-{0},*)	
			p.dir = (p.dir*2)%10;
			e.dir = (e.dir*2)%10;
			//rot90
			const tmp = -e.y;
			e.y = e.x;
			e.x = tmp;
		}
	}
	
	
	const SoR_ESE_SB_terminate = Scene_Battle.prototype.terminate;
    Scene_Battle.prototype.terminate = function() {
        SoR_ESE_SB_terminate.call(this);
		
		if($gameTemp.isEncounteredonMap){
			$gamePlayer.SetupInvinsibleEffectsAfterSE();
			$gameTemp.isEncounteredonMap = false;
			$gameTemp.JustAfterEncounter = true;
			$gameTemp.Encounter_BattleEndSwitch = true;
			if($gameTemp.encflag==1 && BattleBGM_surprised.name != ''){//surprised bgm
				$gameSystem.setBattleBgm($gameTemp.defaultBattleBGM);
			}
		}
		$gameTemp.encflag = 0;
		$gameTemp.collisionFollower = null;
    }
	
	const SoR_ESE_SM_update = Spriteset_Map.prototype.update;
	Spriteset_Map.prototype.update = function(){
	  SoR_ESE_SM_update.call(this);
	  this.updateCharacterForSymbolEnc();
	}
	
	Spriteset_Map.prototype.updateCharacterForSymbolEnc = function(){
		if($gameSystem.SoRSE_InvincibleExcept()){
		//invinsible time after battles
			if($gameTemp.invincibleAfterEnc>=0) $gamePlayer.UpdateInvinsibleEffectsAfterSE();
			else if($gameTemp.invincibleAfterEnc != -1) $gamePlayer.FinishInvinsibleEffectsAfterSE();
		}
	}

	const SoR_ESE_GP_clearTransferInfo = Game_Player.prototype.clearTransferInfo;
	Game_Player.prototype.clearTransferInfo = function() {
		SoR_ESE_GP_clearTransferInfo.call(this);
		this.FinishInvinsibleEffectsAfterSE();
	}



	const SoR_ESE_GE_start = Game_Event.prototype.start;
	Game_Event.prototype.start = function() {
		if(this.event().meta.EnemySymbol){
			const ES = this._SoRES;
			
			if(VehicleCheck(0) == false) return;// boat vs enemy on the ground ???
			if($gameTemp.invincibleAfterEnc != -1) return;
			else{ //For after event(battle)
				if(ES.Isreturn_afterwait){
					ES.IsDetectPlayer = false;
					ES.IsChasePlayer = false;
					ES.IsFleePlayer = true;
					ES.waitAC_count = ES.wait_afterchase;
					ES.IsStayAfterFlee = true;
				}
				else this.checkAllchaseFlags();
				this.setMoveSpeed(ES.default_speed);
			}
			
		}
		
		SoR_ESE_GE_start.call(this);
	}
	
	function VehicleCheck(v){
		if(v==0){
			if($gamePlayer.isInVehicle()) return false;
			else return true;
		}
		return true;
	}


	const SoR_ESE_GI_command301 = Game_Interpreter.prototype.command301;
    Game_Interpreter.prototype.command301 = function(params) {
		
		if($gameTemp.isEncounteredonMap == true && !$gameParty.inBattle()){
			let troopId;
			if (params[0] === 0) troopId = params[1]; // Direct designation
			else if (params[0] === 1) troopId = $gameVariables.value(params[1]); // Designation with a variable
			else troopId = $gamePlayer.makeEncounterTroopId(); // Same as Random Encounters

            if ($dataTroops[troopId]) {
                BattleManager.setup(troopId, params[2], params[3]);				
                BattleManager.onEncounter();
				if($gameTemp.encflag==1 && BattleBGM_surprised.name != ''){//surprised bgm
					$gameTemp.defaultBattleBGM = $gameSystem._battleBgm;
					$gameSystem.setBattleBgm(BattleBGM_surprised);
				}

                BattleManager.setEventCallback(function(n) {
                    this._branch[this._indent] = n;
                }.bind(this));
                $gamePlayer.makeEncounterCount();
                SceneManager.push(Scene_Battle);
            }
        	return true;
		}
		
		return SoR_ESE_GI_command301.call(this, ...arguments); //return for the interpreter
    }



	//v1.70
	BattleManager.searchSEInvolvement = function(mainEId){
		if($gameParty.inBattle()) return;
		if($dataMap.meta.SoR_NoLinkEncounter == true) return;
		
		const seEVs = $gameMap.events().filter(event => !!event._SoRES && event.findProperPageIndex()>=0 && calcDist(event, $gamePlayer)<=SymbolInvolvementRange);

		let n_involved = 0;
		$gameTemp.SoRSE_involvedEIDs = [];
		for(const ev of seEVs){
			if(MaximumSymbolInvolvement>0 && n_involved>=MaximumSymbolInvolvement) break;
			if(ev._eventId == mainEId) continue;
			
			 
			if(ev._SoRES.can_linkSymbols==true && BresenhamTest(ev)){
				const p_idx = ev.findProperPageIndex();
				const nL = ev.event().pages[p_idx].list.length;
				
				let isfound = false;
				for(let i=0; i<nL;i++){
					if(ev.event().pages[p_idx].list[i].code == 301){
						const com = ev.event().pages[p_idx].list[i].parameters;
						if(typeof $gameTemp.TroopWaves === "undefined") $gameTemp.TroopWaves = [];
						$gameTemp.TroopWaves.push(com[1]);
						n_involved++;
						isfound = true;
					}
					else if(isfound==true && Math.floor(ev.event().pages[p_idx].list[i].code/100)!=3){
						$gameTemp.SoRSE_involvedEIDs.push({evId: ev._eventId, idx: i+1});
						break;
					}
				}
			}
		}
		
		//if not mainEId
		//if With EnemySymbol tag
		//if Can pass bresenham test
		//if canSymbolInvolved_SE
		//-> involve (call troopWaveReinforcement)		
		// record EventID involved
	}
	
	function BresenhamTest(e){
		const p = $gamePlayer;
		if(e.x-p.x!=0) return Bresenham(e,p,true);
		return Bresenham90(e,p,true);
	}

	const SoR_ESE_GI_currentCommand = Game_Interpreter.prototype.currentCommand;
	Game_Interpreter.prototype.currentCommand = function() {
		if($gameTemp.Encounter_BattleEndSwitch == true){
			//trigger involved symbol events
			const data = {
				code: 911,
				indent: 0,
				parameters: []
			};
			return data;
		}
		
		return SoR_ESE_GI_currentCommand.call(this);
	}
	
	Game_Interpreter.prototype.command911 = function(params) {
		$gameTemp.Encounter_BattleEndSwitch = false;
		this._index--;
		if(typeof $gameTemp.SoRSE_involvedEIDs === "undefined") return true;
		
		const Events = $gameMap.events();
		for(const cand of $gameTemp.SoRSE_involvedEIDs){
			const fidx = Events.findIndex(x=>x._eventId==cand.evId);
			if(fidx==-1) continue;
			
			const ev = Events[fidx];
			ev.start();
			
			const elist = ev.list().slice(cand.idx+1);
			if(ev._interpreter==null) ev._interpreter = new Game_Interpreter();
			ev._interpreter.setup(elist, ev._eventId);
		}
		
		return true;
	}
	
	/*
	
Game_Map.prototype.setupStartingMapEvent = function() {
    for (const event of this.events()) {
        if (event.isStarting()) {
            event.clearStartingFlag();
            this._interpreter.setup(event.list(), event.eventId());
            return true;
        }
    }
    return false;
};*/

//////////////////////////////////////////////////////////////////////////
//
// Process Invinsible Effect for player Afeter Symbol Encounter
//
//////////////////////////////////////////////////////////////////////////

	Game_Player.prototype.SetupInvinsibleEffectsAfterSE = function() {
		$gameTemp.invincibleAfterEnc = InvinsibleDuration_AB;
		if(InvincibleStyle_Opaque) $gamePlayer.setOpacity(127);
		if(InvincibleStyle_Blend !=0) $gamePlayer.setBlendMode(InvincibleStyle_Blend);
		$gameTemp._flash_count = 0;
	}

	Game_Player.prototype.UpdateInvinsibleEffectsAfterSE = function() {
		$gameTemp.invincibleAfterEnc--;

		if(InvincibleStyle_Flash){
			let flash;
			if($gameTemp.invincibleAfterEnc>=InvinsibleDuration_AB*0.5) flash=12;
			else if($gameTemp.invincibleAfterEnc>=InvinsibleDuration_AB*0.25) flash=9;
			else if($gameTemp.invincibleAfterEnc>=InvinsibleDuration_AB*0.1) flash=7;
			else flash = 5;
			
			$gameTemp._flash_count = ($gameTemp._flash_count + 1) % flash;
			if($gameTemp._flash_count<=1) $gamePlayer.SetInvinsibleAfterSymbolEnc(true);
			else $gamePlayer.SetInvinsibleAfterSymbolEnc(false);

			if($gameTemp.invincibleAfterEnc==0) $gamePlayer.FinishInvinsibleEffectsAfterSE(false);
		}
		if(InvincibleStyle_Opaque && $gameTemp.invincibleAfterEnc==0) $gamePlayer.FinishInvinsibleEffectsAfterSE(false);
	}

	Game_Player.prototype.FinishInvinsibleEffectsAfterSE = function() {
		$gameTemp.invincibleAfterEnc = -1;
		$gameTemp._flash_count = 0;
		$gamePlayer.SetInvinsibleAfterSymbolEnc(false);
	}
	
	Game_Player.prototype.SetInvinsibleAfterSymbolEnc = function(val) {
		if(InvincibleStyle_Opaque) $gamePlayer.setOpacity(255);
		if(InvincibleStyle_Blend !=0) $gamePlayer.setBlendMode(0);
		if(InvincibleStyle_Flash){
			const v = val===true? (InvincibleStyle_Opaque===true? 127:0) :255;
			$gamePlayer.setOpacity(v);
		}
	}

//////////////////////////////////////////////////////////////////////////
//
// Player Observer on Map Scene (by enemy events)
//
//////////////////////////////////////////////////////////////////////////

const SoR_ESE_GE_setupPage = Game_Event.prototype.setupPage;
Game_Event.prototype.setupPage = function() {
    SoR_ESE_GE_setupPage.call(this);
    this.InitEnemySymbolBehavior();
}

Game_Event.prototype.InitEnemySymbolBehavior = function() {
	if(!this.event().meta.EnemySymbol) return;
	const entag = this.event().meta.EnemySymbol;
	
	let seinv_tag = null;	
	if (typeof entag === "string" || entag instanceof String) seinv_tag = entag.trim();
	else seinv_tag = entag;
	
	this._SoRES = {
		Is_symbolIgnore : false,
		can_linkSymbols: seinv_tag=="NotInvolved"? false : true,
		trails : [],
		trails_n : 0,
		see_distance  : 5,
		see_angle : 30*Math.PI/180+0.00001,
		serach_state : null,
		default_speed : this._moveSpeed,
		chase_speed : 4,
		chase_duration : 40,
		Dir8moveChase : false,
		waitAC_count : 180,
		bushrate : 0.2,
		IsDetectBalloon : false,
		IsDetectPlayer : false,
		IsChasePlayer : false,
		IsFleePlayer : false,
		Isreturn_afterwait : false,
		IsReturnOriginalPos : false,	
		detectedAlertDuration : 10,
		detectedAlert_count : 0,
		wait_afterchase : 150,
		updateRoute_interval : 5,
		Ischase_onVehicle : false,
		call_CommonEventID : [0,0,0,0,0],
		call_ScriptsInPhases : [null,null,null,null,null],
		SpontaneousEvacuate : false,
		bullmove : false,
		_bullcounter : 0,
		_bullaxis : -1,
		leapmove : false,
		leapanim : 0,
		leapduration : 180,
		EvacCond : "true",
		//Breakable Map Objects
		_BMOattacked : false,
		_BMOcanstan : true,
		_BMOstanned : false,
		_BMOstanwait : 0
	}

	this.default_x = this.x;
	this.default_y = this.y;

	const p_idx = this.findProperPageIndex();
	if(p_idx>=0){//do not process for invalid page (no unconditional pages)
		const nL = this.event().pages[p_idx].list.length;
		for(let i=0; i<nL;i++){
			if(this.event().pages[p_idx].list[i].code == 108 || this.event().pages[p_idx].list[i].code == 408){
			const com = this.event().pages[p_idx].list[i].parameters;
			this.SetSymbolTags(com[0]);
			}
		}
    }
}


Game_Event.prototype.canSymbolInvolved_SE = function() {
	if(!this._SoRES) return false;
	return this._SoRES.can_linkSymbols;
}


Game_Event.prototype.SetSymbolTags = function(com) {
	const ES = this._SoRES;
	if(com.match(/(?:distance):[ ]*(.*)/i)) ES.see_distance = parseInt(RegExp.$1);
	else if(com.match(/(?:angle):[ ]*(.*)/i)) ES.see_angle = parseInt(RegExp.$1)*Math.PI/180+0.00001;
	else if(com.match(/(?:speed):[ ]*(.*)/i)) ES.chase_speed = Number(RegExp.$1)!=NaN ? Number(RegExp.$1) : ES.chase_speed;
	else if(com.match(/(?:duration):[ ]*(.*)/i)) ES.chase_duration = parseInt(RegExp.$1);
	else if(com.match(/(?:wait):[ ]*(.*)/i)) ES.wait_afterchase = parseInt(RegExp.$1);
	else if(com.match(/(?:return)[ ]*/i)) ES.Isreturn_afterwait = true;
	else if(com.match(/(?:ignore)/i)) ES.Is_symbolIgnore = true;
	else if(com.match(/(?:bush_rate):[ ]*(.*)/i)) ES.bushrate = parseFloat(RegExp.$1);
	else if(com.match(/(?:alert_time):[ ]*(.*)/i)) ES.detectedAlertDuration = parseInt(RegExp.$1);
	else if(com.match(/(?:route_search):[ ]*(.*)/i)) ES.updateRoute_interval = parseInt(RegExp.$1);
	else if(com.match(/(?:chase_vehicle)[ ]*/i)) ES.Ischase_onVehicle = true;
	else if(com.match(/(?:script5):[ ]*(.*)/i)) ES.call_ScriptsInPhases[4] = String(RegExp.$1).trim();
	else if(com.match(/(?:script4):[ ]*(.*)/i)) ES.call_ScriptsInPhases[3] = String(RegExp.$1).trim();
	else if(com.match(/(?:script3):[ ]*(.*)/i)) ES.call_ScriptsInPhases[2] = String(RegExp.$1).trim();
	else if(com.match(/(?:script2):[ ]*(.*)/i)) ES.call_ScriptsInPhases[1] = String(RegExp.$1).trim();
	else if(com.match(/(?:script):[ ]*(.*)/i)) ES.call_ScriptsInPhases[0] = String(RegExp.$1).trim();
	else if(com.match(/(?:common_event5):[ ]*(.*)/i)) ES.call_CommonEventID[4] = parseInt(RegExp.$1);
	else if(com.match(/(?:common_event4):[ ]*(.*)/i)) ES.call_CommonEventID[3] = parseInt(RegExp.$1);
	else if(com.match(/(?:common_event3):[ ]*(.*)/i)) ES.call_CommonEventID[2] = parseInt(RegExp.$1);
	else if(com.match(/(?:common_event2):[ ]*(.*)/i)) ES.call_CommonEventID[1] = parseInt(RegExp.$1);
	else if(com.match(/(?:common_event):[ ]*(.*)/i)) ES.call_CommonEventID[0] = parseInt(RegExp.$1);
	else if(com.match(/(?:evacuate):[ ]*(.*)/i)){ES.SpontaneousEvacuate = true; ES.EvacCond = String(RegExp.$1).trim();}
	else if(com.match(/(?:evacuate)[ ]*/i)){ES.SpontaneousEvacuate = true;}
	else if(com.match(/(?:8dir)[ ]*/i)){ES.Dir8moveChase = true;}
	else if(com.match(/(?:bull)[ ]*/i)){ES.bullmove = true;}
	else if(com.match(/(?:leap):[ ]*(.*)/i)){ES.leapmove = true; ES.leapanim = parseInt(RegExp.$1);}
}


const SoR_ESE_GE_updateSelfMovement = Game_Event.prototype.updateSelfMovement;
Game_Event.prototype.updateSelfMovement = function() {
	if(this.event().meta.EnemySymbol){
		const ES = this._SoRES;
		if(this.reactBMO_checkStanned()) return;
		else if(!ES.Is_symbolIgnore && ES.serach_state != null){
			if(this._erased || this.findProperPageIndex()==-1) return;
			if($gameMap.isEventTriggered()) return;
			const state_bin = this.checkAllchaseFlags();

			const state = ES.serach_state;
			if(ES.IsChasePlayer){
				if(ES.SpontaneousEvacuate && SoR_Eval(ES.EvacCond)) this.EvacuateFromPlayer(state);
				else if(ES.bullmove) this.BullPlayerChaser(state);
				else if(ES.leapmove && state.dist>3) this.LeapPlayerChaser(state);
				else this.PlayerChaser(state);
			}
			else if(ES.IsFleePlayer) this.FleePlayer();
			else if(ES.IsReturnOriginalPos) this.ReturnOrigPoint(state.dist);
			if(state_bin != 0b00000) return;
		}
	}

	SoR_ESE_GE_updateSelfMovement.call(this);
}


const SoR_ESE_GE_update = Game_Event.prototype.update;
Game_Event.prototype.update = function() {
    SoR_ESE_GE_update.call(this);
	if(this.event().meta.EnemySymbol) this.updateSEprocess();
}


Game_Event.prototype.updateSEprocess = function() {
	const ES = this._SoRES;
	if($gameTemp.JustAfterEncounter && $gameTemp.JustEncounterID == this._eventId){//just after a battle
		if(ES.call_CommonEventID[3] != 0) $gameTemp.reserveCommonEvent(ES.call_CommonEventID[3]);
		if(ES.call_ScriptsInPhases[3] != null) this.SoR_ExecScript(ES.call_ScriptsInPhases[3]);
		$gameTemp.JustAfterEncounter = false;
		$gameTemp.JustEncounterID = -1;
	}

	if(this._erased || this.findProperPageIndex()==-1) return;
		
	if(ES.Is_symbolIgnore || this.isVehicleChase() ){
		this.clearAllchaseFlags();
		return;
	}

	if( $gameTemp.invincibleAfterEnc > -1){
		if(ES.IsChasePlayer){
			ES.IsChasePlayer = false;
			ES.IsFleePlayer = true;
		}
		ES.IsDetectPlayer = false;
	}
	else if($gameTemp.invincibleAfterEnc == -1 && !this.isMoving()){
		const state = this.serachPlayer();
		ES.serach_state = state;
		if(state.flag)	ES.IsDetectPlayer = true;
		if(ES.IsDetectPlayer && !ES.IsChasePlayer && !ES.IsFleePlayer)  this.DetectedPlayer();
	}	
}


Game_Event.prototype.isVehicleChase = function() {
	const ES = this._SoRES;
	let flag = $gamePlayer.isInVehicle();
	if(ES.Ischase_onVehicle) flag = false;
	return flag;
}

Game_Event.prototype.checkAllchaseFlags = function() {
	const ES = this._SoRES;
	let ret = 0b00000;
	
	if(ES.IsDetectPlayer)          ret += 0b000001;
	if(ES.IsDetectBalloon)          ret += 0b000010;
	if(ES.IsChasePlayer)           ret += 0b000100;
	if(ES.IsFleePlayer)            ret += 0b001000;
	if(ES.IsStayAfterFlee)         ret += 0b010000;
	if(ES.IsReturnOriginalPos)     ret += 0b100000;	
	return ret;
}

Game_Event.prototype.clearAllchaseFlags = function() {
	const ES = this._SoRES;
	ES.IsDetectPlayer = false;
	ES.IsDetectBalloon = false;
	ES.IsChasePlayer = false;
	ES.IsFleePlayer = false;
	ES.IsStayAfterFlee = false;
	ES.IsReturnOriginalPos = false;
	
	this.setMoveSpeed(ES.default_speed);
}


/////////////////////////////////////////////////////////////////
Game_Event.prototype.reactBMO_checkStanned = function() {
	const ES = this._SoRES;
    if(!PluginManager._scripts.includes("SoR_BreakableMapObjects_MZ")) return false;
	if(!ES._BMOattacked) return false;

	if(ES._BMOstanned){
		if(!this._balloonPlaying) $gameTemp.requestBalloon(this, 13);
	}

	ES._BMOstanwait--;
	
	if(ES._BMOstanwait>0) return true;
	else if(ES._BMOattacked){
		ES._BMOstanwait = 0;
		ES._BMOattacked = false;
		ES._BMOstanned = false;
		ES.IsDetectPlayer = true; // start to chase
		return false;
	}
	
	return false;
}
/////////////////////////////////////////////////////////////////






//v1.31
/////////////////////////////////////////////////////////////////////////
const SoR_ESE_GP_triggerButtonAction = Game_Player.prototype.triggerButtonAction;
Game_Player.prototype.triggerButtonAction = function() {
	$gameTemp._ESE_triggerFromOK = true;//////
    return SoR_ESE_GP_triggerButtonAction.call(this);
}

const SoR_ESE_GP_triggerTouchAction = Game_Player.prototype.triggerTouchAction;
Game_Player.prototype.triggerTouchAction = function() {
	$gameTemp._ESE_triggerFromOK = false;
    return SoR_ESE_GP_triggerTouchAction.call(this);
}

const SoR_ESE_GP_startMapEvent = Game_Player.prototype.startMapEvent;
Game_Player.prototype.startMapEvent = function(x, y, triggers, normal) {
	//avoid encounter with symbol on the ground while in the sky
	const cands = $gameMap.GetEventsXY_SoRESE(x, y);
	for(const ev of cands){
        if (ev.event().meta.EnemySymbol){
			if($gamePlayer.isInAirship()) return;
			if($gameTemp._ESE_triggerFromOK===true) return;
		}
	}
	SoR_ESE_GP_startMapEvent.call(this,x, y, triggers, normal);
}
/////////////////////////////////////////////////////////////////////////

Game_Event.prototype.serachPlayer = function() {
	const ES = this._SoRES;
	let flag = false;
	const p = {x:$gamePlayer.x,y:$gamePlayer.y,dir:$gamePlayer._direction};
	const e = {x:this.x,y:this.y,dir:this._direction};

	const bush_rate = $gameMap.isBush(p.x,p.y) == true ? ES.bushrate:1;
	const dist = calcDist(e,p);
	if(dist <= Math.ceil(ES.see_distance*bush_rate)){
		const ang = calcAngle(e,p);
		if(ang >= -ES.see_angle && ang <= ES.see_angle){
			if(e.x-p.x!=0) flag = Bresenham(e,p);
			else flag = Bresenham90(e,p);
		}
	}
	
	if(FollowerAttack >=3){// walking with followers
		const flws = this.AdditionalSerachForFollowers(e);
		flag = flag || flws.flag;
		if(flws.flag) dist = flws.dist;
	}

	return {dist: dist, flag: flag};
}

// for only followers
Game_Event.prototype.AdditionalSerachForFollowers = function(e) {
	const ES = this._SoRES;
	const flw = $gamePlayer._followers.visibleFollowers();

	let flag = false;
	let dist = 0;
	const n_flw = flw.length;
	for(let i=0; i<n_flw;i++){
		let p = {x:flw[i].x,y:flw[i].y,dir:flw[i]._direction};
		const bush_rate = $gameMap.isBush(p.x,p.y) == true ? ES.bushrate:1;
		dist = calcDist(e,p);
		if(dist <= Math.ceil(ES.see_distance*bush_rate)){
			const ang = calcAngle(e,p);
			if(ang >= -ES.see_angle && ang <= ES.see_angle){			 
				if(e.x-p.x!=0) flag = flag || Bresenham(e,p);
				else flag = flag || Bresenham90(e,p);
			}
		}
	}

	return {dist: dist, flag: flag};
}


function Bresenham(a1,a2,ignore){
	let flag = true;
	const dy = Math.abs(a2.y-a1.y);
	const dx = Math.abs(a2.x-a1.x);
	let err = dx-dy;

	const inc_x = a2.x > a1.x ? 1 : -1;
	const inc_y = a2.y > a1.y ? 1 : -1;
	
	let cx = a1.x;
	let cy = a1.y;
	
	const dir_x = dx>0? 6:4;
	const dir_y = dy>0? 2:8;

	while(1){
		if(!$gameMap.isPassable(cx, cy, dir_x) || !$gameMap.isPassable(cx, cy, dir_y) || $gameMap.regionId(cx, cy) == SymbolKeepOut_RegionID){
			flag = false;
			break;
		}
		if(cx==a2.x && cy==a2.y) break;
		
		if(!(cx == a1.x && cy == a1.y) && !(cx == a2.x  && cy == a2.y) && !isUnPassableEvent(cx,cy,ignore)){
			flag = false;
			break;
		}
		
		const e2 = err*2;
		if(e2 > -dy){
			err -= dy;
			cx += inc_x;
		}
		if(e2 < dx){
			err += dx;
			cy += inc_y;
		}
	}

	return flag;
}

function Bresenham90(a1,a2,ignore){
	let flag = true;
	const incre = a2.y > a1.y ? 1 : -1;
	const cx = a1.x;
	for(let cy = a1.y; ; cy+=incre){
		if(!$gameMap.isPassable(cx, cy, a1.dir)  || $gameMap.regionId(cx, cy) == SymbolKeepOut_RegionID){
			flag = false;
			break;
		}
		if(!(cx == a1.x && cy == a1.y) && !(cx == a2.x  && cy == a2.y) && !isUnPassableEvent(cx,cy,ignore)){
			flag = false;
			break;
		}
		if(cy==a2.y) break;
		
	}
	return flag;
}

function isUnPassableEvent(x,y,ignore){
	if(ignore==true) return true;//to ignore character events for bresenham
	
	let flag = true;
	$gameMap.GetEventsXY_SoRESE(x, y).forEach(function(event) {
        if (event.isNormalPriority() === true) {
            flag = false;
        }
    });
	
	return flag;
}	

Game_Event.prototype.DetectedPlayer = function() {
	const ES = this._SoRES;
	if(!ES.IsDetectBalloon){
		if(!ES.IsReturnOriginalPos){
			this.default_x = this.x;
			this.default_y = this.y;
		}
	
		ES.detectedAlert_count = ES.detectedAlertDuration;
		ES.IsDetectBalloon = true;
		ES.IsStayAfterFlee = false;
		ES.IsReturnOriginalPos = false;
		
		//balloon
		if(this.SpontaneousEvacuate && SoR_Eval(ES.EvacCond)){//evacuate
			if(BalloonID_Evacuate!=-1) $gameTemp.requestBalloon(this, BalloonID_Evacuate);
			if(SE_Evacuate.name!='')AudioManager.playSe(SE_Evacuate);
		}
		else{//regular
			if(BalloonID_PlayerDetected!=-1) $gameTemp.requestBalloon(this, BalloonID_PlayerDetected);
			if(SE_PlayerDetected.name!='')AudioManager.playSe(SE_PlayerDetected);
		}
		//common ev
		if(ES.call_CommonEventID[0] != 0) $gameTemp.reserveCommonEvent(ES.call_CommonEventID[0]);
		if(ES.call_ScriptsInPhases[0] != null) this.SoR_ExecScript(ES.call_ScriptsInPhases[0]);
	}
	
	ES.detectedAlert_count--;
	
	if(ES.detectedAlert_count < 0){
		ES.IsDetectBalloon = false;
		this.InitPlayerChase();
	}
}


Game_Event.prototype.InitPlayerChase = function() {
	const ES = this._SoRES;
	this.setMoveSpeed(ES.chase_speed);
	ES.IsChasePlayer = true;
	ES.chase_time = ES.chase_duration;
	ES.trails = [];
	if(ES.call_CommonEventID[1] != 0) $gameTemp.reserveCommonEvent(ES.call_CommonEventID[1]);
	if(ES.call_ScriptsInPhases[1] != null) this.SoR_ExecScript(ES.call_ScriptsInPhases[1]);

	if(!ES.SpontaneousEvacuate || !SoR_Eval(ES.EvacCond)){
		if(ES.bullmove){
			this.setMoveSpeed(1);
			this.setDirectionFix(true);
			ES._bullaxis=-1;
			ES._bullcounter=0;
		}
		else if(ES.leapmove){
			ES.leapduration = 180;
		}
	}
}


Game_Event.prototype.PlayerChaser = function(state) {
	const ES = this._SoRES;
	const d = state.dist
	let isChaseFeasible = true;
	if(ES.chase_time >= 0){
		//make route
	  if((ES.trails.length == 0 || ES.trails_n >= ES.updateRoute_interval)){
  	    ES.trails = this.AstarDir($gamePlayer.x, $gamePlayer.y, ES.see_distance *($gameMap.isBush($gamePlayer.x,$gamePlayer.y) == true ? ES.bushrate:1));///d
		ES.trails_n = 0;
	  }
	  if(ES.trails.length >= 1){// trail the player
			const direction = this.MakeDir(ES.trails[0]);
			ES.trails_n++;
			ES.trails.shift();
			if(ES.Dir8moveChase && ES.trails.length >= 1){
				const pattern = [8, 12, 32, 48];
				const dir2 = this.MakeDir2(direction,ES.trails[0]);
				if(d>=2 && pattern.some(x=> x==direction*dir2)){////8dir
					if(direction== 4 || direction == 6)	this.moveDiagonally(direction,dir2);
					else this.moveDiagonally(dir2,direction);
					ES.trails_n++;
					ES.trails.shift();
				}
				else if(direction > 0) this.moveStraight(direction);
			}
			else if(direction > 0) this.moveStraight(direction);
	  }
	  else isChaseFeasible = false; // player missing
	  ES.chase_time--;
	}
	
	if(ES.chase_time < 0 && isChaseFeasible && d <= ES.see_distance/3) ES.chase_time=10;
	if(ES.chase_time < 0 || !isChaseFeasible){
		ES.IsDetectPlayer = false;
		ES.IsChasePlayer = false;
	    ES.IsFleePlayer = true;
	}
}


Game_Event.prototype.BullPlayerChaser = function(state) {
	const ES = this._SoRES;
	const d = state.dist
	let isChaseFeasible = true;
	if(ES._bullcounter<90){//charge
		ES._bullaxis = Math.abs($gamePlayer.x-this.x) <= Math.abs($gamePlayer.y-this.y)? $gamePlayer.x>this.x? 1:2 : $gamePlayer.y>this.y? -1:-2;
		const destx = ES._bullaxis>0? $gamePlayer.x : this.x;// xe=xp
		const desty = ES._bullaxis<0? $gamePlayer.y : this.y;// ye=yp
		//make route
		if((ES.trails.length == 0 || ES.trails_n >= ES.updateRoute_interval)){
			ES.trails = this.AstarDir(destx, desty, 10);///d
			ES.trails_n = 0;
		}
		if(ES.trails.length >= 1){// trail the player
			const direction = this.MakeDir(ES.trails[0]);
			ES.trails_n++;
			ES.trails.shift();
			if(ES.Dir8moveChase && ES.trails.length >= 1){
				const pattern = [8, 12, 32, 48];
				const dir2 = this.MakeDir2(direction,ES.trails[0]);
				if(pattern.some(x=> x==direction*dir2)){////8dir
					if(direction== 4 || direction == 6)	this.moveDiagonally(direction,dir2);
					else this.moveDiagonally(dir2,direction);
					ES.trails_n++;
					ES.trails.shift();
				}
				else if(direction > 0) this.moveStraight(direction);
			}
			else if(direction > 0) this.moveStraight(direction);
		}
		else ES._bullcounter++; // go bull fast
		ES._bullcounter++;

		if(ES._bullcounter >= 90){
			ES.trails = [];
		}
	}
	else if(ES.chase_time >= 0){//bull
		//make route
		this.setDirectionFix(false);
		this.setMoveSpeed(ES.chase_speed);
		const destx = ES._bullaxis<0? $gamePlayer.x + ($gamePlayer.x>this.x?0:-0): this.x;
		const desty = ES._bullaxis>0? $gamePlayer.y + ($gamePlayer.y>this.y?0:-0) : this.y;

		if((ES.trails.length == 0 || ES.trails_n >= ES.updateRoute_interval)){
			ES.trails = this.AstarDir(destx, desty, 10);///d
			ES.trails_n = 0;
		}
		if(ES.trails.length >= 1){// trail the player
			const direction = this.MakeDir(ES.trails[0]);
			ES.trails_n++;
			ES.trails.shift();
			if(ES.Dir8moveChase && ES.trails.length >= 1){
				const pattern = [8, 12, 32, 48];
				const dir2 = this.MakeDir2(direction,ES.trails[0]);
				if(pattern.some(x=> x==direction*dir2)){////8dir
					if(direction== 4 || direction == 6)	this.moveDiagonally(direction,dir2);
					else this.moveDiagonally(dir2,direction);
					ES.trails_n++;
					ES.trails.shift();
				}
				else if(direction > 0) this.moveStraight(direction);
			}
			else if(direction > 0) this.moveStraight(direction);
		}
		else isChaseFeasible = false; // player missing
		ES.chase_time--;
	}


	if(ES.chase_time < 0 ){
		ES.IsDetectPlayer = false;
		ES.IsChasePlayer = false;
		ES.IsStayAfterFlee = false;
		ES.IsFleePlayer = true;

		if(ES.Isreturn_afterwait){
			ES.IsReturnOriginalPos = true;
			ES.trails = [];
			ES.trails_n = 0;
		}
	}
}


Game_Event.prototype.LeapPlayerChaser = function(state) {
	const ES = this._SoRES;
	const d = state.dist
	let isChaseFeasible = true;

	//leapduration
	ES.leapduration--;
	if(ES.chase_time >= 0){
		//make route
		if((ES.trails.length == 0 || ES.trails_n >= ES.updateRoute_interval)){
			ES.trails = this.AstarDir($gamePlayer.x, $gamePlayer.y, ES.see_distance);///d
			if(ES.trails.length>=3) ES.trails.pop();
			ES.trails_n = 0;
		}
		if(ES.trails.length >= 1){// trail the player
			if(ES.leapduration<=0){//wait leap
				let testx = this._x;
				let testy = this._y;

				$gameTemp.requestAnimation([this], ES.leapanim);
				while(ES.trails.length >= 1){
					const direction = this.MakeDirTest(ES.trails[0],testx,testy);
					ES.trails_n++;
					ES.trails.shift();
					if(ES.Dir8moveChase && ES.trails.length >= 1){
						const pattern = [8, 12, 32, 48];
						const dir2 = this.MakeDir2Test(direction,ES.trails[0],testx,testy);
						if(pattern.some(x=> x==direction*dir2)){////8dir
							if(direction== 4 || direction == 6){
								const test = this.moveDiagonalTest(direction,dir2,testx,testy);
								testx = test.x; testy = test.y;
							}
							else{
								const test = this.moveDiagonalTest(dir2,direction,testx,testy);
								testx = test.x; testy = test.y;
							}
							ES.trails_n++;
							ES.trails.shift();
						}
						else if(direction > 0){
							const test = this.moveStraightTest(direction,testx,testy);
							testx = test.x; testy = test.y;
						}
					}
					else if(direction > 0){
						const test = this.moveStraightTest(direction,testx,testy);
						testx = test.x; testy = test.y;
					}
				}
				this.setPosition(testx,testy);
				ES.leapduration=180;
			}
		}
		else isChaseFeasible = false; // player missing
		if(ES.leapduration<=0) ES.chase_time--;
	}
	
	if(ES.chase_time < 0 && isChaseFeasible && d <= ES.see_distance/3) ES.chase_time=10;
	if(ES.chase_time < 0 || !isChaseFeasible){
		ES.IsDetectPlayer = false;
		ES.IsChasePlayer = false;
	    ES.IsFleePlayer = true;
	}
}

Game_Event.prototype.EvacuateFromPlayer = function(state) {
	const ES = this._SoRES;
	const d = state.dist
	let isChaseFeasible = true;
	if(ES.chase_time >= 0){
		//make route
		if((ES.trails.length == 0 || ES.trails_n >= ES.updateRoute_interval)){
			ES.trails = this.calcEvacuateDestination();
			ES.trails_n = 0;
		}
		if(ES.trails.length >= 1){// trail the player
				const direction = this.MakeDir(ES.trails[0]);
				ES.trails_n++;
				ES.trails.shift();
				if(ES.Dir8moveChase && ES.trails.length >= 1){
					const pattern = [8, 12, 32, 48];
					const dir2 = this.MakeDir2(direction,ES.trails[0]);
					if(pattern.some(x=> x==direction*dir2)){////8dir
						if(direction== 4 || direction == 6)	this.moveDiagonally(direction,dir2);
						else this.moveDiagonally(dir2,direction);
						ES.trails_n++;
						ES.trails.shift();
					}
					else if(direction > 0) this.moveStraight(direction);
				}
				else if(direction > 0) this.moveStraight(direction);
		}

		ES.chase_time--;
	}
	
	if(ES.chase_time < 0 ){
		ES.IsDetectPlayer = false;
		ES.IsChasePlayer = false;
		ES.IsStayAfterFlee = false;
		ES.IsFleePlayer = true;

		if(ES.Isreturn_afterwait){
			ES.IsReturnOriginalPos = true;
			ES.trails = [];
			ES.trails_n = 0;
		}
	}
}


Game_Event.prototype.calcEvacuateDestination = function() {
//temporary (BFS&BFS => DP in the future work)
	const symbolbfs = [];
	const playerbfs = [];
	const candidates = [];
	const MaxSteps = 8;

	playerbfs.unshift({x: $gamePlayer.x, y: $gamePlayer.y});
	symbolbfs.unshift({x: this.x, y: this.y, dist: 0});
	const currentdist = this.AstarDirTest(symbolbfs[0].x, symbolbfs[0].y, playerbfs[0].x, playerbfs[0].y);
	symbolbfs[0].dist = currentdist.length;

	for(let steps=0; steps<MaxSteps; steps++){
		const n_plevel = playerbfs.length;
		for(let i=0;i<n_plevel;i++){
			const nexts = playerbfs[n_plevel-1];
			if(!nexts) continue;

			let shiftcandidate = false;
			for(let dir=2; dir<=8; dir+=2){
				const x2 = this.GetMovedirXPos_SoRESE(nexts.x, dir);
				const y2 = this.GetMovedirYPos_SoRESE(nexts.y, dir);
				if (!this.IsEnablePassAdjcentCells(nexts.x, nexts.y, x2, y2, dir)) continue;
				if(playerbfs.some((v)=>v.x==x2&&v.y==y2)) continue;

				shiftcandidate = true;
				playerbfs.unshift({x: x2, y: y2});
			}

			if(shiftcandidate == true) playerbfs.pop();
		}

		const n_elevel = symbolbfs.length;
		for(let i=0;i<n_plevel;i++){
			const nexts = symbolbfs[n_elevel-1];
			if(!nexts) continue;
			candidates.unshift({x: nexts.x, y: nexts.y, s: steps+1});

			let shiftcandidate = false;
			for(let dir=2; dir<=8; dir+=2){
				const x2 = this.GetMovedirXPos_SoRESE(nexts.x, dir);
				const y2 = this.GetMovedirYPos_SoRESE(nexts.y, dir);
				if (!this.IsEnablePassAdjcentCells(nexts.x, nexts.y, x2, y2, dir)) continue;
				if(symbolbfs.some((v)=>v.x==x2&&v.y==y2)) continue;
				if(playerbfs.some((v)=>v.x==x2&&v.y==y2)) continue;

				const n_plevel = playerbfs.length;
				let mindist = 999999;
				let minid = -1;
				let i=0;
				for(const p of playerbfs){
					const dist = calcDist(x2, y2, p.x, p.y);
					if(mindist > dist){
						mindist = dist;
						minid = i;
					}
					i++;
				}
				
				if(currentdist > mindist) continue;

				symbolbfs.unshift({x: x2, y: y2, dist: mindist});
				if(steps<MaxSteps-1) shiftcandidate = true;
			}

			if(shiftcandidate == true) candidates.shift();
			symbolbfs.pop();
		}
	}

	const ncand = candidates.length;
	let candid = 0;
	let candmax = candidates[0];
	for(i=1; i<ncand; i++){
		if(candmax < candidates[i] && Math.random()<0.7){
			candmax = candidates[i];
			candid = i;
		}
	}

	const cand = candidates[candid];
	if(cand) return this.AstarDir(cand.x, cand.y, cand.s);
	return this.AstarDir(this.x, this.y, 1);
}



Game_Event.prototype.FleePlayer = function() {
	const ES = this._SoRES;
	if(!ES.IsStayAfterFlee){
		ES.waitAC_count = ES.wait_afterchase;
		ES.IsStayAfterFlee = true;
		
		//balloon
		if(BalloonID_PlayerFled != -1) $gameTemp.requestBalloon(this, BalloonID_PlayerFled);
		if(SE_PlayerFled.se!='' && this.isNearTheScreen()) AudioManager.playSe(SE_PlayerFled);
		if(ES.call_CommonEventID[2] != 0) $gameTemp.reserveCommonEvent(ES.call_CommonEventID[2]);
		if(ES.call_ScriptsInPhases[2] != null) this.SoR_ExecScript(ES.call_ScriptsInPhases[2]);
		this.setMoveSpeed(ES.default_speed);
	}
	
	ES.waitAC_count--;
	
	if(ES.waitAC_count < 0){
		ES.IsStayAfterFlee = false;
		ES.IsFleePlayer = false;
		if(ES.Isreturn_afterwait){
			ES.IsReturnOriginalPos = true;
			ES.trails = [];
			ES.trails_n = 0;
		}
	}
}



Game_Event.prototype.ReturnOrigPoint = function(d) {
	const ES = this._SoRES;
	if((ES.trails.length == 0 || ES.trails_n >= ES.updateRoute_interval)){
		ES.trails = this.AstarDir(this.default_x, this.default_y, 99);
		ES.trails_n = 0;
	}
	
	if(ES.trails.length >= 1){// trail the player
		const direction = this.MakeDir(ES.trails[0]);
		ES.trails_n++;
		ES.trails.shift();
		if(ES.Dir8moveChase && ES.trails.length >= 1){
			const pattern = [8, 12, 32, 48];
			const dir2 = this.MakeDir2(direction,ES.trails[0]);
			if(pattern.some(x=> x==direction*dir2)){////8dir
				if(direction== 4 || direction == 6)	this.moveDiagonally(direction,dir2);
				else this.moveDiagonally(dir2,direction);
				ES.trails_n++;
				ES.trails.shift();
			}
			else if(direction > 0) this.moveStraight(direction);
		}
		else if(direction > 0) this.moveStraight(direction);
	}

	if(this.x == this.default_x && this.y == this.default_y){
	    ES.IsReturnOriginalPos = false;
		if(ES.call_CommonEventID[4] != 0) $gameTemp.reserveCommonEvent(ES.call_CommonEventID[4]);
		if(ES.call_ScriptsInPhases[4] != null) this.SoR_ExecScript(ES.call_ScriptsInPhases[4]);
	}
}


function calcDist(e,p){
	return Math.sqrt((e.x-p.x)*(e.x-p.x)+(e.y-p.y)*(e.y-p.y));
}

function calcAngle(e,p){
    const va = {x: p.x-e.x, y: p.y-e.y};
	
	let x = 2, n_op = 0;
	//Cyclic subgroup generated by 2 \in Group(Z_{10}-{0},*)
	while(x!=e.dir){
		x = (x*2)%10;
		n_op++;
	}
	const vec = {z: 0, c: 1};
	//(C,*) isomorphism to sub of Z[i] ~ rotate90
	for(let i=0; i<n_op;i++){
		const tmp = -vec.c;
		vec.c = vec.z;
		vec.z = tmp;
	}
	
	const theta = Math.acos((vec.z*va.x+vec.c*va.y)/Math.sqrt(va.x*va.x+va.y*va.y)); 
	if( vec.z*va.y - vec.c*va.x < 0) return -theta;
	else return theta;// -pi < theta < pi
}


Game_Character.prototype.AstarDir = function(goalX, goalY, dist) {
	const searchLimit = Math.floor(dist*SearchRange_scale);
    const mapWidth = $gameMap.width();
    let nodeList = [];
    let openList = [];
    let closedList = [];
    let start = {};
    let best = start;

    if (this.x === goalX && this.y === goalY) return []; 

    start.parent = null;
    start.x = this.x;
    start.y = this.y;
    start.g = 0;
    start.f = dist;
    nodeList.push(start);
    openList.push(start.y * mapWidth + start.x);

    while (nodeList.length > 0) {
        let bestIndex = 0;
        for (let i = 0; i < nodeList.length; i++) {
            if (nodeList[i].f < nodeList[bestIndex].f) {
                bestIndex = i;
            }
        }

        const current = nodeList[bestIndex];
        const x1 = current.x;
        const y1 = current.y;
        const pos1 = y1 * mapWidth + x1;
        const g1 = current.g;

        nodeList.splice(bestIndex, 1);
        openList.splice(openList.indexOf(pos1), 1);
        closedList.push(pos1);

        if (current.x === goalX && current.y === goalY){
            best = current;
            break;
        }
        if (g1 >= searchLimit) continue;

		
        for (let j = 0; j < 4; j++) {
            const direction = 2 + j * 2;
            const x2 = this.GetMovedirXPos_SoRESE(x1, direction);
            const y2 = this.GetMovedirYPos_SoRESE(y1, direction);
            const pos2 = y2 * mapWidth + x2;

            if (closedList.contains(pos2)) continue;
            if (!this.IsEnablePassAdjcentCells(x1, y1, x2, y2, direction)) continue;
			
            const g2 = g1 + 1;
            const index2 = openList.indexOf(pos2);

            if (index2 < 0 || g2 < nodeList[index2].g) {
                let neighbor;
                if (index2 >= 0) neighbor = nodeList[index2];
                else {
                    neighbor = {};
                    nodeList.push(neighbor);
                    openList.push(pos2);
                }
                neighbor.parent = current;
                neighbor.x = x2;
                neighbor.y = y2;
                neighbor.g = g2;
                neighbor.f = g2 + $gameMap.distance(x2, y2, goalX, goalY);
                if (!best || neighbor.f - neighbor.g < best.f - best.g) best = neighbor;
            }
        }
		
    }
	
	let trails = [];
	let goal = {x: goalX, y: goalY};
	if(calcDist(best,goal)==0){
		for(node = best; node.parent; node = node.parent){
			trails.unshift(node);
			if(node.parent === start) break;
		}
	}
	
	return trails;
}


Game_Character.prototype.AstarDirTest = function(startX, startY, goalX, goalY) {
	const searchLimit = 8;
    const mapWidth = $gameMap.width();
    let nodeList = [];
    let openList = [];
    let closedList = [];
    let start = {};
    let best = start;

    if (startX === goalX && startY === goalY) return 0;

    start.parent = null;
    start.x = startX;
    start.y = startY;
    start.g = 0;
    start.f = 4;
    nodeList.push(start);
    openList.push(start.y * mapWidth + start.x);

    while (nodeList.length > 0) {
        let bestIndex = 0;
        for (let i = 0; i < nodeList.length; i++) {
            if (nodeList[i].f < nodeList[bestIndex].f) {
                bestIndex = i;
            }
        }

        const current = nodeList[bestIndex];
        const x1 = current.x;
        const y1 = current.y;
        const pos1 = y1 * mapWidth + x1;
        const g1 = current.g;

        nodeList.splice(bestIndex, 1);
        openList.splice(openList.indexOf(pos1), 1);
        closedList.push(pos1);

        if (current.x === goalX && current.y === goalY){
            best = current;
            break;
        }
        if (g1 >= searchLimit) continue;

        for (let j = 0; j < 4; j++) {
            const direction = 2 + j * 2;
            const x2 = this.GetMovedirXPos_SoRESE(x1, direction);
            const y2 = this.GetMovedirYPos_SoRESE(y1, direction);
            const pos2 = y2 * mapWidth + x2;

            if (closedList.contains(pos2)) continue;
            if (!this.IsEnablePassAdjcentCells(x1, y1, x2, y2, direction)) continue;
			
            const g2 = g1 + 1;
            const index2 = openList.indexOf(pos2);

            if (index2 < 0 || g2 < nodeList[index2].g) {
                let neighbor;
                if (index2 >= 0) neighbor = nodeList[index2];
                else {
                    neighbor = {};
                    nodeList.push(neighbor);
                    openList.push(pos2);
                }
                neighbor.parent = current;
                neighbor.x = x2;
                neighbor.y = y2;
                neighbor.g = g2;
                neighbor.f = g2 + $gameMap.distance(x2, y2, goalX, goalY);
                if (!best || neighbor.f - neighbor.g < best.f - best.g) best = neighbor;
            }
        }
    }
	
	let trails = [];
	let goal = {x: goalX, y: goalY};
	if(calcDist(best,goal)==0){
		for(node = best; node.parent; node = node.parent){
			trails.unshift(node);
			if(node.parent === start) break;
		}
	}
	
	return trails.length;
}


//extention of canPass
Game_Event.prototype.IsEnablePassAdjcentCells = function(x, y, x2, y2, d) {
    if (!$gameMap.isValid(x2, y2))  return false;
	//keep out by plugin parameter
	if($gameMap.regionId(x, y) == SymbolKeepOut_RegionID) return false;
	if($gameMap.regionId(x2, y2) == SymbolKeepOut_RegionID)	return false;
    if (this.isThrough()) return true;
	if (!this.isMapPassable(x, y, d)) return false;
	if ( FollowerAttack % 3 == 0 && this.isCollidedWithFollowerCharacters(x,y)) return false;
    if (this.isCollidedWithEventObjects(x2, y2))  return false; // except for the player
	
    return true;
}

Game_Event.prototype.IsEnablePassDiagonallyAdjcentCells = function(x, y, horz, vert) {
    const x2 = $gameMap.roundXWithDirection(x, horz);
    const y2 = $gameMap.roundYWithDirection(y, vert);

    if (this.IsEnablePassAdjcentCells(x, y, x2, y2, vert) && this.IsEnablePassAdjcentCells(x, y, x2, y2, horz)) {
        return true;
    }
    if (this.IsEnablePassAdjcentCells(x, y, x2, y2, horz) && this.IsEnablePassAdjcentCells(x, y, x2, y2, vert)) {
        return true;
    }
    return false;
}



Game_Event.prototype.isCollidedWithEventObjects = function(x, y) {
    return Game_Character.prototype.isCollidedWithCharacters.call(this, x, y);
}

Game_Character.prototype.MakeDir = function(node) {
    const deltaX1 = $gameMap.deltaX(node.x, this.x);
    const deltaY1 = $gameMap.deltaY(node.y, this.y);
    if (deltaY1 == 1) return 2;
    else if (deltaX1 == -1) return 4;
    else if (deltaX1 == 1) return 6;
    else if (deltaY1 == -1) return 8;
	else return 0;
}

Game_Character.prototype.MakeDir2 = function(next,node) {
	let paddx = 0, paddy = 0;
	paddx = next==6? 1 : next==4? -1 : 0;
	paddy = next==2? 1 : next==8? -1 : 0;

    const deltaX1 = $gameMap.deltaX(node.x, this.x+paddx);
    const deltaY1 = $gameMap.deltaY(node.y, this.y+paddy);
    if (deltaY1 == 1) return 2;
    else if (deltaX1 == -1) return 4;
    else if (deltaX1 == 1) return 6;
    else if (deltaY1 == -1) return 8;
	else return 0;
}

Game_Map.prototype.isEventTriggered = function() {
    return this._interpreter.isRunning();
}

const SoR_ESE_GP_updateVehicleGetOn = Game_Player.prototype.updateVehicleGetOn;
Game_Player.prototype.updateVehicleGetOn = function() {
	//force quit invincinble for enemy symbol
	if($gameTemp.invincibleAfterEnc && $gameTemp.invincibleAfterEnc != -1) $gamePlayer.FinishInvinsibleEffectsAfterSE();
	SoR_ESE_GP_updateVehicleGetOn.call(this);
}


////////////////////////////////////////////////////
//
// Option for follower attack by enemy symbols
//
////////////////////////////////////////////////////
if(FollowerAttack%3 == 1){
	const SoR_ESE_GE_checkEventTriggerTouch = Game_Event.prototype.checkEventTriggerTouch;
	Game_Event.prototype.checkEventTriggerTouch = function(x, y) {
		if(this.event().meta.EnemySymbol){
			if (!$gameMap.isEventRunning()) {
							
				let flw = $gamePlayer._followers.visibleFollowers();
				let isF_collided = false;
				const n_flw = flw.length;
				for(let i=0; i<n_flw;i++){
					isF_collided = flw[i].pos(x,y);
					if(isF_collided==true){
						$gameTemp.collisionFollower = flw[i];
						break;
					}
				}
				
				if (this._trigger === 2 && isF_collided) {
					if (!this.isJumping() && this.isNormalPriority()) {
						this.start();
					}
				}
			}
		}
		
		SoR_ESE_GE_checkEventTriggerTouch.call(this, x, y);
	}
}
else if(FollowerAttack%3 == 2){
	const SoR_ESE_GE_isCollidedWithPlayerCharacters = Game_Event.prototype.isCollidedWithPlayerCharacters;
	Game_Event.prototype.isCollidedWithPlayerCharacters = function (x, y) {
		if(this.event().meta.EnemySymbol){
			return this.isNormalPriority() && !$gamePlayer.isThrough() && $gamePlayer.pos(x, y);
		}
	return SoR_ESE_GE_isCollidedWithPlayerCharacters.call(this,...arguments);
	}
}

Game_Event.prototype.isCollidedWithFollowerCharacters = function(x, y) {
	let flw = $gamePlayer._followers.visibleFollowers();
	let isF_collided = false;
	const n_flw = flw.length;
	for(let i=0; i<n_flw;i++){
		isF_collided = flw[i].pos(x,y);
		if(isF_collided==true){
			return true;
		}
	}
	return isF_collided;
}



///////////////////////////////////////////////////////////
//test for leap move
Game_Character.prototype.MakeDirTest = function(node,x,y) {
    const deltaX1 = $gameMap.deltaX(node.x, x);
    const deltaY1 = $gameMap.deltaY(node.y, y);
    if (deltaY1 == 1) return 2;
    else if (deltaX1 == -1) return 4;
    else if (deltaX1 == 1) return 6;
    else if (deltaY1 == -1) return 8;
	else return 0;
}
Game_Character.prototype.MakeDir2Test = function(next,node,x,y) {
	let paddx = 0, paddy = 0;
	paddx = next==6? 1 : next==4? -1 : 0;
	paddy = next==2? 1 : next==8? -1 : 0;

    const deltaX1 = $gameMap.deltaX(node.x, x+paddx);
    const deltaY1 = $gameMap.deltaY(node.y, y+paddy);
    if (deltaY1 == 1) return 2;
    else if (deltaX1 == -1) return 4;
    else if (deltaX1 == 1) return 6;
    else if (deltaY1 == -1) return 8;
	else return 0;
}

Game_CharacterBase.prototype.moveDiagonalTest = function(horz, vert , x, y) {
	let test = {x, y};
    if (this.IsEnablePassDiagonallyAdjcentCells(x, y, horz, vert)) {
        test.x = this.GetMovedirXPos_SoRESE(test.x, horz);
        test.y = this.GetMovedirYPos_SoRESE(test.y, vert);
    }

	return test;
}

Game_CharacterBase.prototype.moveStraightTest = function(d, x, y) {
	let test = {x, y};
    if (this.canPass(x, y, d)) {
        test.x = this.GetMovedirXPos_SoRESE(test.x, d);
        test.y = this.GetMovedirYPos_SoRESE(test.y, d);
    }

	return test;
}

///////////////////////////////////////////////////////
//
// Treatment for MenuSubCommand.js
//
///////////////////////////////////////////////////////

Game_System.prototype.SoRSE_InvincibleExcept = function(){
	return true;
}

//TODO 
//Find an isomorphic function {2,4,8,6} to {i,-1,-i,1}
////////////////////////////////////////////////////////

const SoR_SSE_DM_setupNewGame = DataManager.setupNewGame;
DataManager.setupNewGame = function() {
	SoR_SSE_DM_setupNewGame.call(this);
	if(AutoDisable_DefaultEncounter) $gameSystem.disableEncounter();
}

////////////////////////////////////////////////////////
Game_Party.prototype.AverageLv = function() {
	let avg = 0;
	const npt = $gameParty.size();
	for(x of $gameParty.members()){
		avg += x.level;
	}
	return Math.floor(avg/npt);
}




////////////////////////////////////////////////////////
// v1.61 Tri_HalfMove
////////////////////////////////////////////////////////
Game_Character.prototype.GetMovedirXPos_SoRESE = function(x,dir){
	return $gameMap.roundXWithDirection(x, dir);
}
Game_Character.prototype.GetMovedirYPos_SoRESE = function(y,dir){
	return $gameMap.roundYWithDirection(y, dir);
}
Game_Map.prototype.GetEventsXY_SoRESE = function(x,y){
	return $gameMap.eventsXy(x, y);
}


////////////////////////////////////////////////////////
function SoR_Eval(ev) {
    const sentence = "return (" + ev + ");";
    if(typeof $gameTemp.SoRTmp_script === "undefined") $gameTemp.SoRTmp_script = new Map();
    if(!$gameTemp.SoRTmp_script.has(sentence)){
        $gameTemp.SoRTmp_script.set(sentence, new Function(sentence));
    }     
    const res = $gameTemp.SoRTmp_script.get(sentence)();
    return res;
}

Game_Event.prototype.SoR_ExecScript = function(ev){
    let sentence = ev;
    sentence = sentence.replace(/EVID/g, this._eventId);
    sentence = sentence.replace(/MAPID/g, this._mapId);
    sentence = sentence.replace(/PAGEID/g, this._pageIndex);
    sentence = sentence.replace(/selfON\((.*)\)/g, (_, p1) => {
		const tx = "$gameSelfSwitches.setValue(["+ this._mapId +","+ this._eventId +",\""+ String(p1).trim() +"\"],"+ true + ")";
		return tx;
	});
    sentence = sentence.replace(/selfOFF\((.*)\)/g, (_, p1) => {
		const tx = "$gameSelfSwitches.setValue(["+ this._mapId +","+ this._eventId +",\""+ String(p1).trim() +"\"],"+ false + ")";
		return tx;
	});
    sentence = sentence.replace(/delEV\(\)/g, "$gameMap.eraseEvent("+ this._eventId + ")");

    if(typeof $gameTemp.SoRTmp_script === "undefined") $gameTemp.SoRTmp_script = new Map();
    if(!$gameTemp.SoRTmp_script.has(sentence)){
        $gameTemp.SoRTmp_script.set(sentence, new Function(sentence));
    }
    const res = $gameTemp.SoRTmp_script.get(sentence)();
    return res;
}

})();