/// <summary>Deals damage to <paramref name="victim"/> and broadcasts the HP changing and substitute damage.</summary> /// <param name="culprit">The Pokémon responsible for the damage.</param> /// <param name="victim">The Pokémon receiving the damage.</param> /// <param name="hp">The amount of HP <paramref name="victim"/> will try to lose.</param> /// <param name="ignoreSubstitute">Whether the damage should ignore <paramref name="victim"/>'s <see cref="PBEStatus2.Substitute"/>.</param> /// <param name="ignoreSturdy">Whether the damage should ignore <paramref name="victim"/>'s <see cref="PBEAbility.Sturdy"/>, <see cref="PBEItem.FocusBand"/>, or <see cref="PBEItem.FocusSash"/>.</param> /// <returns>The amount of damage dealt.</returns> private ushort DealDamage(PBEPokemon culprit, PBEPokemon victim, int hp, bool ignoreSubstitute, bool ignoreSturdy = false) { if (hp < 1) { hp = 1; } if (!ignoreSubstitute && victim.Status2.HasFlag(PBEStatus2.Substitute)) { ushort oldHP = victim.SubstituteHP; victim.SubstituteHP = (ushort)Math.Max(0, victim.SubstituteHP - hp); ushort damageAmt = (ushort)(oldHP - victim.SubstituteHP); BroadcastStatus2(victim, culprit, PBEStatus2.Substitute, PBEStatusAction.Damage); return(damageAmt); } else { ushort oldHP = victim.HP; double oldPercentage = victim.HPPercentage; victim.HP = (ushort)Math.Max(0, victim.HP - hp); bool sturdyHappened = false, focusBandHappened = false, focusSashHappened = false; if (!ignoreSturdy && victim.HP == 0) { // TODO: Endure if (oldHP == victim.MaxHP && victim.Ability == PBEAbility.Sturdy && !culprit.HasCancellingAbility()) { sturdyHappened = true; victim.HP = 1; } else if (victim.Item == PBEItem.FocusBand && PBEUtils.RandomBool(10, 100)) { focusBandHappened = true; victim.HP = 1; } else if (oldHP == victim.MaxHP && victim.Item == PBEItem.FocusSash) { focusSashHappened = true; victim.HP = 1; } } victim.HPPercentage = (double)victim.HP / victim.MaxHP; BroadcastPkmnHPChanged(victim, oldHP, oldPercentage); if (sturdyHappened) { BroadcastAbility(victim, culprit, PBEAbility.Sturdy, PBEAbilityAction.Damage); BroadcastEndure(victim); } else if (focusBandHappened) { BroadcastItem(victim, culprit, PBEItem.FocusBand, PBEItemAction.Damage); } else if (focusSashHappened) { BroadcastItem(victim, culprit, PBEItem.FocusSash, PBEItemAction.Consumed); } return((ushort)(oldHP - victim.HP)); } }
private PBEPokemon[] GetActingOrder(IEnumerable <PBEPokemon> pokemon, bool ignoreItemsThatActivate) { var evaluated = new List <(PBEPokemon Pokemon, double Speed)>(); // TODO: Full Incense, Lagging Tail, Stall, Quick Claw foreach (PBEPokemon pkmn in pokemon) { double speed = pkmn.Speed * GetStatChangeModifier(pkmn.SpeedChange, false); switch (pkmn.Item) { case PBEItem.ChoiceScarf: { speed *= 1.5; break; } case PBEItem.MachoBrace: case PBEItem.PowerAnklet: case PBEItem.PowerBand: case PBEItem.PowerBelt: case PBEItem.PowerBracer: case PBEItem.PowerLens: case PBEItem.PowerWeight: { speed *= 0.5; break; } case PBEItem.QuickPowder: { if (pkmn.OriginalSpecies == PBESpecies.Ditto && !pkmn.Status2.HasFlag(PBEStatus2.Transformed)) { speed *= 2.0; } break; } } if (ShouldDoWeatherEffects()) { if (Weather == PBEWeather.HarshSunlight && pkmn.Ability == PBEAbility.Chlorophyll) { speed *= 2.0; } if (Weather == PBEWeather.Rain && pkmn.Ability == PBEAbility.SwiftSwim) { speed *= 2.0; } if (Weather == PBEWeather.Sandstorm && pkmn.Ability == PBEAbility.SandRush) { speed *= 2.0; } } if (pkmn.Ability == PBEAbility.QuickFeet) { if (pkmn.Status1 != PBEStatus1.None) { speed *= 1.5; } } else if (pkmn.Status1 == PBEStatus1.Paralyzed) { speed *= 0.25; } Debug.WriteLine("Team {0}'s {1}'s evaluated speed: {2}", pkmn.Team.Id, pkmn.Nickname, speed); (PBEPokemon Pokemon, double Speed)tup = (pkmn, speed); if (evaluated.Count == 0) { evaluated.Add(tup); } else { int pkmnTiedWith = evaluated.FindIndex(t => t.Speed == speed); if (pkmnTiedWith != -1) // Speed tie - randomly go before or after the Pokémon it tied with { if (PBEUtils.RandomBool()) { if (pkmnTiedWith == evaluated.Count - 1) { evaluated.Add(tup); } else { evaluated.Insert(pkmnTiedWith + 1, tup); } } else { evaluated.Insert(pkmnTiedWith, tup); } } else { int pkmnToGoBefore = evaluated.FindIndex(t => BattleStatus.HasFlag(PBEBattleStatus.TrickRoom) ? t.Speed > speed : t.Speed < speed); if (pkmnToGoBefore == -1) { evaluated.Add(tup); } else { evaluated.Insert(pkmnToGoBefore, tup); } } } Debug.WriteLine(evaluated.Select(t => $"{t.Pokemon.Team.Id} {t.Pokemon.Nickname} {t.Speed}").Print()); } return(evaluated.Select(t => t.Pokemon).ToArray()); }
/// <summary>Selects actions if they are valid. Changes the battle state if both teams have selected valid actions.</summary> /// <param name="team">The team the inputted actions belong to.</param> /// <param name="actions">The actions the team wishes to execute.</param> /// <returns>True if the actions are valid and were selected.</returns> /// <exception cref="InvalidOperationException">Thrown when <see cref="BattleState"/> is not <see cref="PBEBattleState.WaitingForActions"/>.</exception> public static bool SelectActionsIfValid(PBETeam team, IList <PBETurnAction> actions) { if (team == null) { throw new ArgumentNullException(nameof(team)); } if (actions == null) { throw new ArgumentNullException(nameof(actions)); } if (team.IsDisposed) { throw new ObjectDisposedException(nameof(team)); } if (team.Battle.BattleState != PBEBattleState.WaitingForActions) { throw new InvalidOperationException($"{nameof(BattleState)} must be {PBEBattleState.WaitingForActions} to select actions."); } if (AreActionsValid(team, actions)) { lock (team.Battle._disposeLockObj) { if (!team.Battle.IsDisposed) { team.ActionsRequired.Clear(); foreach (PBETurnAction action in actions) { PBEPokemon pkmn = team.TryGetPokemon(action.PokemonId); if (action.Decision == PBETurnDecision.Fight && pkmn.GetMoveTargets(action.FightMove) == PBEMoveTarget.RandomFoeSurrounding) { switch (team.Battle.BattleFormat) { case PBEBattleFormat.Single: case PBEBattleFormat.Rotation: { action.FightTargets = PBETurnTarget.FoeCenter; break; } case PBEBattleFormat.Double: { action.FightTargets = PBEUtils.RandomBool() ? PBETurnTarget.FoeLeft : PBETurnTarget.FoeRight; break; } case PBEBattleFormat.Triple: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { action.FightTargets = PBEUtils.RandomBool() ? PBETurnTarget.FoeCenter : PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { int r; // Keep randomly picking until a non-fainted foe is selected roll: r = PBEUtils.RandomInt(0, 2); if (r == 0) { if (team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Left) != null) { action.FightTargets = PBETurnTarget.FoeLeft; } else { goto roll; } } else if (r == 1) { if (team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Center) != null) { action.FightTargets = PBETurnTarget.FoeCenter; } else { goto roll; } } else { if (team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Right) != null) { action.FightTargets = PBETurnTarget.FoeRight; } else { goto roll; } } } else { action.FightTargets = PBEUtils.RandomBool() ? PBETurnTarget.FoeLeft : PBETurnTarget.FoeCenter; } break; } default: throw new ArgumentOutOfRangeException(nameof(team.Battle.BattleFormat)); } } pkmn.TurnAction = action; } if (team.Battle.Teams.All(t => t.ActionsRequired.Count == 0)) { team.Battle.BattleState = PBEBattleState.ReadyToRunTurn; team.Battle.OnStateChanged?.Invoke(team.Battle); } return(true); } } } return(false); }
/// <summary> /// Selects actions if they are valid. Changes the battle state if both teams have selected valid actions. /// </summary> /// <param name="team">The team the inputted actions belong to.</param> /// <param name="actions">The actions the team wishes to execute.</param> /// <returns>True if the actions are valid and were selected.</returns> /// <exception cref="InvalidOperationException">Thrown when <see cref="BattleState"/> is not <see cref="PBEBattleState.WaitingForActions"/>.</exception> public static bool SelectActionsIfValid(PBETeam team, IEnumerable <PBEAction> actions) { if (team.Battle.BattleState != PBEBattleState.WaitingForActions) { throw new InvalidOperationException($"{nameof(BattleState)} must be {PBEBattleState.WaitingForActions} to select actions."); } if (AreActionsValid(team, actions)) { team.ActionsRequired.Clear(); foreach (PBEAction action in actions) { PBEPokemon pkmn = team.TryGetPokemon(action.PokemonId); pkmn.SelectedAction = action; switch (pkmn.SelectedAction.Decision) { case PBEDecision.Fight: { switch (pkmn.GetMoveTargets(pkmn.SelectedAction.FightMove)) { case PBEMoveTarget.RandomFoeSurrounding: { switch (team.Battle.BattleFormat) { case PBEBattleFormat.Single: case PBEBattleFormat.Rotation: { pkmn.SelectedAction.FightTargets = PBETarget.FoeCenter; break; } case PBEBattleFormat.Double: { pkmn.SelectedAction.FightTargets = PBEUtils.RandomBool() ? PBETarget.FoeLeft : PBETarget.FoeRight; break; } case PBEBattleFormat.Triple: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { pkmn.SelectedAction.FightTargets = PBEUtils.RandomBool() ? PBETarget.FoeCenter : PBETarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { PBETeam opposingTeam = team == team.Battle.Teams[0] ? team.Battle.Teams[1] : team.Battle.Teams[0]; int r; // Keep randomly picking until a non-fainted foe is selected roll: r = PBEUtils.RandomInt(0, 2); if (r == 0) { if (opposingTeam.TryGetPokemon(PBEFieldPosition.Left) != null) { pkmn.SelectedAction.FightTargets = PBETarget.FoeLeft; } else { goto roll; } } else if (r == 1) { if (opposingTeam.TryGetPokemon(PBEFieldPosition.Center) != null) { pkmn.SelectedAction.FightTargets = PBETarget.FoeCenter; } else { goto roll; } } else { if (opposingTeam.TryGetPokemon(PBEFieldPosition.Right) != null) { pkmn.SelectedAction.FightTargets = PBETarget.FoeRight; } else { goto roll; } } } else { pkmn.SelectedAction.FightTargets = PBEUtils.RandomBool() ? PBETarget.FoeLeft : PBETarget.FoeCenter; } break; } } break; } } break; } } } if (Array.TrueForAll(team.Battle.Teams, t => t.ActionsRequired.Count == 0)) { team.Battle.BattleState = PBEBattleState.ReadyToRunTurn; team.Battle.OnStateChanged?.Invoke(team.Battle); } return(true); } return(false); }