I suspect there's a compatibility issue between Yanfly Engine Plugins - Battle Engine Core and Yanfly Engine Plugins - Instant Cast.
When an actor uses an instant cast skill/item having Effects Common Event calling one that asks an enemy to make a force action, the enemy can end up trying to call Game_Actor.prototype.endInstantCast via BattleManager.endActorInstantCast upon action end.
The below's my analysis of the cause:
Spoiler
1. The below shows that the instant cast will let BattleManager.endActorInstantCast to be called upon action end:
BattleManager.performInstantCast = function() { console.log("YEP_InstantCast BattleManager.performInstantCast", this._phase, this._subject); if (Imported.YEP_BattleEngineCore) { this.stopAllSelection(); this.resetSelection(); } this._subject = BattleManager.actor(); this._instantCasting = true; this.startAction();};
Code:Yanfly.Instant.BattleManager_endAction = BattleManager.endAction;BattleManager.endAction = function() { console.log("YEP_InstantCast BattleManager.endAction", this._phase, this._subject); if (this._instantCasting) { this.endActorInstantCast(); } else { this.endEnemyInstantCastAction(); Yanfly.Instant.BattleManager_endAction.call(this); } this._startedInstantCasting = false;};
2. The below shows that the common event will be reserved at the same frame where a target's hit -
Spoiler
BattleManager.updateActionTargetList = function() { console.log("YEP_BattleEngineCore BattleManager.updateActionTargetList", this._phase, this._subject); for (; { this._actSeq = this._actionList.shift(); if (this._actSeq) { if (!this.actionConditionsMet(this._actSeq)) continue; var seqName = this._actSeq[0].toUpperCase(); if (!this.processActionSequence(seqName, this._actSeq[1])) { break; } } else if (this._individualTargets.length > 0) { this._individualTargets.shift(); if (this._individualTargets.length > 0) { this.setTargets([this._individualTargets[0]]); this._actionList = this._action.item().targetActions.slice(); } else { this._phase = 'phaseChange'; break; } } else { this._phase = 'phaseChange'; break; } }};
Code:BattleManager.processActionSequence = function(actionName, actionArgs) { // NO ACTION if (actionName === '') { return true; } // ACTION ANIMATION if (actionName === 'ACTION ANIMATION') { return this.actionActionAnimation(actionArgs); } // ACTION EFFECT if (actionName === 'ACTION COMMON EVENT') { return this.actionActionCommonEvent(); } // ACTION EFFECT if (actionName === 'ACTION EFFECT') { return this.actionActionEffect(actionArgs); } // ANI WAIT: frames if (['ANI WAIT', 'ANIWAIT', 'ANIMATION WAIT'].contains(actionName)) { return this.actionAniWait(actionArgs[0]); } // CAST ANIMATION if (actionName === 'CAST ANIMATION') { return this.actionCastAnimation(); } // CLEAR BATTLE LOG if (actionName === 'CLEAR BATTLE LOG') { return this.actionClearBattleLog(); } // DEATH BREAK if (actionName === 'DEATH BREAK') { return this.actionDeathBreak(); } // DISPLAY ACTION if (actionName === 'DISPLAY ACTION') { return this.actionDisplayAction(); } // IF condition if (actionName.match(/IF[ ](.*)/i)) { return this.actionIfConditions(actionName, actionArgs); } // IMMORTAL: targets, true/false if (actionName === 'IMMORTAL') { return this.actionImmortal(actionArgs); } // MOTION WAIT if (actionName === 'MOTION WAIT') { return this.actionMotionWait(actionArgs); } // PERFORM ACTION if (actionName === 'PERFORM ACTION') { return this.actionPerformAction(); } // PERFORM FINISH if (actionName === 'PERFORM FINISH') { return this.actionPerformFinish(); } // PERFORM START if (actionName === 'PERFORM START') { return this.actionPerformStart(); } // WAIT: frames if (actionName === 'WAIT') { return this.actionWait(actionArgs[0]); } // WAIT FOR ANIMATION if (actionName === 'WAIT FOR ANIMATION') { return this.actionWaitForAnimation(); } // WAIT FOR EFFECT if (actionName === 'WAIT FOR EFFECT') { return this.actionWaitForEffect(); } // WAIT FOR MOVEMENT if (actionName === 'WAIT FOR MOVEMENT') { return this.actionWaitForMovement(); } // WAIT FOR NEW LINE if (actionName === 'WAIT FOR NEW LINE') { return this.actionWaitForNewLine(); } // WAIT FOR POPUPS if (actionName === 'WAIT FOR POPUPS') { return this.actionWaitForPopups(); } return false;};
Code:BattleManager.actionActionCommonEvent = function() { this._action.item().effects.forEach(function(effect) { if (effect.code === Game_Action.EFFECT_COMMON_EVENT) { $gameTemp.reserveCommonEvent(effect.dataId); } }, this); return false;};
3. The below shows that the forced action with an enemy as the subject will be executed immediately afterwards and continue to execute until the force action ends -
Spoiler
BattleManager.forceAction = function(battler) { this._actionForcedBattler = battler; var index = this._actionBattlers.indexOf(battler); if (index >= 0) { this._actionBattlers.splice(index, 1); }};
Code:BattleManager.update = function() { if (!this.isBusy() && !this.updateEvent()) { switch (this._phase) { case 'start': this.startInput(); break; case 'turn': this.updateTurn(); break; case 'action': this.updateAction(); break; case 'phaseChange': this.updatePhase(); break; case 'actionList': this.updateActionList() break; case 'actionTargetList': this.updateActionTargetList() break; case 'turnEnd': this.updateTurnEnd(); break; case 'battleEnd': this.updateBattleEnd(); break; } }};
Code:BattleManager.updateEvent = function() { if (this._processingForcedAction) return false; switch (this._phase) { case 'start': case 'turn': case 'turnEnd': case 'actionList': case 'actionTargetList': if (this.isActionForced()) { this.processForcedAction(); return true; } else { return this.updateEventMain(); } } return this.checkAbort();};
Code:BattleManager.isActionForced = function() { return !!this._actionForcedBattler;};
Code:BattleManager.processForcedAction = function() { if (this._actionForcedBattler) { this._subject = this._actionForcedBattler; this._actionForcedBattler = null; this.startAction(); this._subject.removeCurrentAction(); }};
Now this._subject becomes that enemy.
4. The below shows that Game_Actor.prototype.endInstantCast will be called by that enemy:
Spoiler
Yanfly.Instant.BattleManager_endAction = BattleManager.endAction;BattleManager.endAction = function() { if (this._instantCasting) { this.endActorInstantCast(); } else { this.endEnemyInstantCastAction(); Yanfly.Instant.BattleManager_endAction.call(this); } this._startedInstantCasting = false;};BattleManager.endActorInstantCast = function() { var user = this._subject; if (Imported.YEP_BattleEngineCore) { if (this._processingForcedAction) this._phase = this._preForcePhase; this._processingForcedAction = false; } if (this.updateEventMain()) return; Yanfly.Instant.BattleManager_endAction.call(this); this._instantCasting = undefined; user.makeActions(); if (this.checkBattleEnd()) return; this._phase = 'input'; if (user.canMove()) { user.endInstantCast(); } else { this.selectNextCommand(); }};
Code:BattleManager.updateEventMain = function() { $gameTroop.updateInterpreter(); $gameParty.requestMotionRefresh(); if ($gameTroop.isEventRunning() || this.checkBattleEnd()) { return true; } $gameTroop.setupBattleEvent(); if ($gameTroop.isEventRunning() || SceneManager.isSceneChanging()) { return true; } return false;};
The root cause is that, under Yanfly Engine Plugins - Battle Engine Core, a force action can be completely executed before the originally executing action finishes its executions, causing the flow to the something like this:
1. Start original action
2. Start force action
3. End force action
4. End original action
Also, under Yanfly Engine Plugins - Instant Cast, an actor's instant cast will have a flow like this:
1. Raise the actor instant cast flag
2. Start the actor instant cast
3. End the actor instant cast
4. Call BattleManager.endActorInstantCast(
The only function resetting the actor instant cast flag) if the actor instant cast flag's raised
Combining both and assuming that the force action has an enemy as its subject will lead to something like this:
1. Raise the actor instant cast flag
2. Start the actor instant cast
3. Start enemy force action
4. End enemy force action
5.
Call BattleManager.endActorInstantCast if the actor instant cast flag's raised
6.
Call BattleManager.endActorInstantCast as the actor instant cast flag's raised
To fix this:
Spoiler
1. I'd suggest changes like these to Yanfly Engine Plugins - Battle Engine Core -
BattleManager.savePreForceActionSettings = function() { this._forceActionQueue.push(this.setPreForceActionSettings());};BattleManager.setPreForceActionSettings = function() { return { subject: this._subject, action: JsonEx.makeDeepCopy(this._action), actionList: JsonEx.makeDeepCopy(this._actionList), targets: this._targets.slice(), allTargets: this._allTargets.slice(), indTargets: this._individualTargets.slice(), phaseSteps: JsonEx.makeDeepCopy(this._phaseSteps), returnPhase: this._returnPhase, phase: this._phase, conditionFlags: JsonEx.makeDeepCopy(this._conditionFlags), trueFlags: JsonEx.makeDeepCopy(this._trueFlags) }};BattleManager.loadPreForceActionSettings = function() { console.log("YEP_BattleEngineCore BattleManager.loadPreForceActionSettings", this._phase, this._subject); var settings = this._forceActionQueue.shift(); if (settings) { this.resetPreForceActionSettings(settings); return true; } else { return false; } };BattleManager.resetPreForceActionSettings = function(settings) { this._subject = settings['subject']; this._action = settings['action']; this._actionList = settings['actionList']; this._targets = settings['targets']; this._allTargets = settings['allTargets']; this._individualTargets = settings['indTargets']; this._phaseSteps = settings['phaseSteps']; this._returnPhase = settings['returnPhase']; this._conditionFlags = settings['conditionFlags']; this._trueFlags = settings['trueFlags']; this._phase = settings['phase'];};
Now extending those functions by Yanfly Engine Plugins - Instant Cast will be much easier.
2. I'd suggest changes like these to Yanfly Engine Plugins - Instant Cast:
Spoiler
Yanfly.BEC.BattleManager_savePreForceActionSettings = BattleManager.savePreForceActionSettings;BattleManager.savePreForceActionSettings = function() { Yanfly.BEC.BattleManager_savePreForceActionSettings.call(this); this._instantCasting = false;};Yanfly.BEC.BattleManager_setPreForceActionSettings =BattleManager.setPreForceActionSettings;BattleManager.setPreForceActionSettings = function() { var settings = Yanfly.BEC.BattleManager_setPreForceActionSettings.call(this); settings['instantCasting'] = this._instantCasting; return settings;};Yanfly.BEC.BattleManager_resetPreForceActionSettings =BattleManager.resetPreForceActionSettings;BattleManager.resetPreForceActionSettings = function() { Yanfly.BEC.BattleManager_resetPreForceActionSettings.call(this); this._instantCasting = settings['instantCasting'];};
Now the actor instant cast flag will be reset upon the start of the force action execution and raised again upon the end of the force action execution.
You don't have to credit nor even mention me if you decided to use my fix. I just hope I've correctly grasped how your plugins work and my fix will work if it's needed at all
:)