private void ForceTurnInternal(RbyTurn turn) { int random = SYM["Random"] + 0x10; int playerTurnDone1 = SYM["MainInBattleLoop.playerMovesFirst"] + 0x3; int playerTurnDone2 = SYM["MainInBattleLoop.AIActionUsedEnemyFirst"] + 0xc; int playerTurnDone3 = SYM["HandlePlayerMonFainted"]; int enemyTurnDone1 = SYM["MainInBattleLoop.enemyMovesFirst"] + 0x11; int enemyTurnDone2 = SYM["MainInBattleLoop.playerMovesFirst"] + 0x27; int enemyTurnDone3 = SYM["HandleEnemyMonFainted"]; int ret; Joypad holdButton = Joypad.None; if ((CpuRead("wOptions") & 0x7) != 1) { holdButton = Joypad.A; } string[] sideEffects = { "FreezeBurnParalyzeEffect", "StatModifierDownEffect", "PoisonEffect", "ConfusionSideEffect", "FlinchSideEffect" }; while ((ret = ClearTextUntil(holdButton, random, playerTurnDone1, playerTurnDone2, playerTurnDone3, enemyTurnDone1, enemyTurnDone2, enemyTurnDone3)) == random) { int addr = CpuReadLE <ushort>(SP); if (addr > 0x4000) { addr |= CpuRead("hLoadedROMBank") << 16; } string address = SYM[addr]; RunUntil(addr); if (!address.StartsWith("VBlank")) { if (address.StartsWith("MoveHitTest")) // hit/miss { A = (turn.Flags & Miss) > 0 ? 0xff : 0x00; } else if (address.StartsWith("CriticalHitTest")) // crit { A = (turn.Flags & Crit) > 0 ? 0x00 : 0xff; } else if (address.StartsWith("RandomizeDamage")) // damage roll { int roll = turn.Flags & 0x3f; if (roll < 1) { roll = 1; } if (roll > 39) { roll = 39; } roll += 216; A = (byte)((roll << 1) | (roll >> 7)); // rotate left to counter a rrca instruction } else if (address == "StatModifierDownEffect+0021") // AI's 25% chance to miss { A = (turn.Flags & Miss) > 0 ? 0x00 : 0xff; } else if (sideEffects.Any(effect => address.StartsWith(effect))) // various side effects { A = (turn.Flags & SideEffect) > 0 ? 0x00 : 0xff; } else if (address.StartsWith("TrainerAI")) // trainer ai { if ((turn.Flags & AiItem) > 0) { A = 0x00; break; } else { A = 0xff; } } else if (address.StartsWith("ThrashPetalDanceEffect")) // thresh/petal dance length { A = (turn.Flags & ThreeTurn) > 0 ? 0 : 1; } else if (address.StartsWith("CheckPlayerStatusConditions.IsConfused") || address.StartsWith("CheckEnemyStatusConditions.IsConfused")) // confusion hit through { A = (turn.Flags & Hitself) > 0 ? 0xff : 0x00; } else if (address.StartsWith("CheckPlayerStatusConditions.ThrashingAboutCheck")) // confused for 2-5 turns after thrash/petal dance ends { A = 0; } else if (address.StartsWith("ApplyAttackToPlayerPokemon.loop")) // psywave damage { A = turn.Flags & 0x3f; } else if (address.StartsWith("DisableEffect.pickMoveToDisable")) // disable move { A = FindBattleMove(turn.Target) & 0x3; } else if (address.StartsWith("DisableEffect.playerTurnNotLinkBattle")) // disable number of turns (1-8) { int turns = turn.Flags / Turns; A = ((turns >= 1 ? turns : 8) - 1) & 0x7; } else if (address.StartsWith("BideEffect.bideEffect")) // bide number of turns (2-3) { int turns = turn.Flags / Turns; A = ((turns >= 2 ? turns : 3) - 2) & 0x7; } else if (address == "TrappingEffect.trappingEffect+000b" || address == "TwoToFiveAttacksEffect.setNumberOfHits+000e") // not needed { A = 0x3; } else if (address == "TrappingEffect.trappingEffect+0014" || address == "TwoToFiveAttacksEffect.setNumberOfHits+0017") // multi-attack number of hits (2-5) { int turns = turn.Flags / Turns; A = ((turns >= 2 ? turns : 5) - 2) & 0x3; } else if (address.StartsWith("MetronomePickMove")) { RbyMove move = Moves[turn.Target]; Debug.Assert(move != null, "Unable to find the move: " + turn.Target); A = move.Id; } else { Console.WriteLine("Unhandled Random call coming from " + address); } } } RunFor(1); }
public void BattleSwitch(string pokemon, RbyTurn enemyTurn) { ForceTurn(new RbyTurn(pokemon, Switch), enemyTurn); }
public void ForceTurn(RbyTurn playerTurn, RbyTurn enemyTurn = null, bool speedTieWin = true) { bool useItem = Items[playerTurn.Move] != null; if (useItem) { if (playerTurn.Target != null) { UseItem(playerTurn.Move, playerTurn.Target, playerTurn.Target2); } else { UseItem(playerTurn.Move, playerTurn.Flags); } } else if ((playerTurn.Flags & Switch) != 0) { BattleMenu(1, 0); ChooseMenuItem(FindPokemon(playerTurn.Move)); ChooseMenuItem(0); } else if (EnemyMon.UsingTrappingMove) { if (CpuRead("wTopMenuItemX") != 0x9 || CpuRead("wCurrentMenuItem") != 0) { MenuPress(Joypad.Left | Joypad.Up); } } else if (!BattleMon.ThrashingAbout && !BattleMon.Invulnerable) { if (CurrentMenuType != MenuType.Fight) { BattleMenu(0, 0); } int moveIndex = FindBattleMove(playerTurn.Move); // Reusing 'ChooseMenuItem' code, because the final AdvanceFrame advances past 'SelectEnemyMove.done', // and I don't have a good solution for this problem right now. RunUntil("_Joypad", "HandleMenuInput_.getJoypadState"); var scroll = CalcScroll(moveIndex, CpuRead("wCurrentMenuItem"), CpuRead("wNumMovesMinusOne"), true); for (int i = 0; i < scroll.Amount; i++) { MenuPress(scroll.Input); } if ((CpuRead("hJoyLast") & (byte)Joypad.A) != 0) { Press(Joypad.None); } Inject(Joypad.A); } if (!(EnemyMon.StoringEnergy | EnemyMon.ChargingUp | EnemyMon.UsingRage | EnemyMon.Frozen | EnemyMon.UsingTrappingMove)) { Hold(Joypad.A, SYM["SelectEnemyMove.done"]); A = enemyTurn != null && enemyTurn.Move != "" ? Moves[enemyTurn.Move].Id : 0; } bool playerFirst; int speedtie = Hold(Joypad.A, SYM["MainInBattleLoop.speedEqual"] + 9, SYM["MainInBattleLoop.enemyMovesFirst"], SYM["MainInBattleLoop.playerMovesFirst"]); if (speedtie == SYM["MainInBattleLoop.enemyMovesFirst"]) { playerFirst = false; } else if (speedtie == SYM["MainInBattleLoop.playerMovesFirst"]) { playerFirst = true; } else { A = speedTieWin ? 0x00 : 0xff; playerFirst = speedTieWin; } if (playerFirst) { if (!useItem) { ForceTurnInternal(playerTurn); } else { RunUntil(SYM["MainInBattleLoop.playerMovesFirst"] + 6); } if (enemyTurn != null) { ForceTurnInternal(enemyTurn); } } else { Debug.Assert(enemyTurn != null, "No enemy turn was specified even though the opponent moved first!"); ForceTurnInternal(enemyTurn); if (!useItem) { ForceTurnInternal(playerTurn); } } CurrentMenuType = MenuType.None; // Semi-terrible code to get around thrash. TODO: fix if (BattleMon.ThrashingAbout) { if (EnemyMon.HP == 0) { if (EnemyParty.Where(mon => mon.HP > 0).Count() > 1) { ClearTextUntil(Joypad.None, SYM["PlayCry"]); } else { ClearText(); } } } else if (!BattleMon.Invulnerable) { ClearText(); } }