public int[] CalcDamage(RbyPokemon attacker, RbyPokemon defender, RbyMove move, bool crit) { int[] ret = new int[39]; for (int i = 0; i < ret.Length; i++) { ret[i] = CalcDamage(attacker, defender, move, i + 217, crit); } return(ret); }
// Code ported from route one. (https://github.com/HRoll/poke-router/blob/master/src/DamageCalculator.java) public int CalcDamage(RbyPokemon attacker, RbyPokemon defender, RbyMove move, int damageRoll, bool crit) { damageRoll = Math.Clamp(damageRoll, 217, 255); if (move.Power == 0) { return(0); } bool special = move.Type.IsSpecial(); int attackUnmodified = special ? attacker.UnmodifiedSpecial : attacker.UnmodifiedAttack; int attack = special ? attacker.Special : attacker.Attack; int defenseUnmodified = special ? defender.UnmodifiedSpecial : defender.UnmodifiedDefense; int defense = special ? defender.Special : defender.Defense; if (move.Name == "SELFDESTRUCT" || move.Name == "EXPLOSION") { defenseUnmodified = Math.Max(defenseUnmodified / 2, 1); defense = Math.Max(defense / 2, 1); } bool stab = attacker.Species.Type1 == move.Type || attacker.Species.Type2 == move.Type; int damage = ((attacker.Level * (crit ? 2 : 1)) & 0xff) * 2 / 5 + 2; damage *= crit ? attackUnmodified : attack; damage *= move.Power; damage /= 50; damage /= crit ? defenseUnmodified : defense; damage += 2; if (stab) { damage = damage * 3 / 2; } damage = damage * move.Game.GetTypeEffectiveness(move.Type, defender.Species.Type1) / 10; damage = damage * move.Game.GetTypeEffectiveness(move.Type, defender.Species.Type2) / 10; if (damage == 0) { return(0); } damage *= damageRoll; damage /= 255; return(Math.Max(damage, 1)); }
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 float OneShotPercentage(RbyPokemon attacker, RbyPokemon defender, RbyMove move, bool crit) { int[] damageRolls = CalcDamage(attacker, defender, move, crit); return((float)damageRolls.Where(dmg => dmg >= defender.HP).Count() / (float)damageRolls.Length); }