public void Basic_Actions() { #region Setup PBERandom.SetSeed(0); PBESettings settings = PBESettings.DefaultSettings; var p0 = new TestPokemonCollection(2); p0[0] = new TestPokemon(settings, PBESpecies.Koffing, 0, 100, PBEMove.Selfdestruct); p0[1] = new TestPokemon(settings, PBESpecies.Magikarp, 0, 100, PBEMove.Splash); var p1 = new TestPokemonCollection(1); p1[0] = new TestPokemon(settings, PBESpecies.Darkrai, 0, 100, PBEMove.Protect); var battle = new PBEBattle(PBEBattleFormat.Single, settings, new PBETrainerInfo(p0, "Trainer 0"), new PBETrainerInfo(p1, "Trainer 1")); battle.OnNewEvent += PBEBattle.ConsoleBattleEventHandler; battle.Begin(); PBETrainer t0 = battle.Trainers[0]; PBETrainer t1 = battle.Trainers[1]; PBEBattlePokemon koffing = t0.Party[0]; PBEBattlePokemon magikarp = t0.Party[1]; PBEBattlePokemon darkrai = t1.Party[0]; #endregion #region Darkrai uses Protect, Koffing uses Selfdestruct and faints var a = new PBETurnAction(koffing, PBEMove.Selfdestruct, PBETurnTarget.FoeCenter); Assert.Throws <ArgumentNullException>(() => PBEBattle.SelectActionsIfValid(null, a)); // Throw for null team Assert.Throws <ArgumentNullException>(() => PBEBattle.SelectActionsIfValid(t0, (IReadOnlyList <PBETurnAction>)null)); // Throw for null collection Assert.Throws <ArgumentNullException>(() => PBEBattle.SelectActionsIfValid(t0, new PBETurnAction[] { null })); // Throw for null elements Assert.False(PBEBattle.SelectActionsIfValid(t0, a, a)); // False for too many actions Assert.True(PBEBattle.SelectActionsIfValid(t0, a)); // True for good actions // TODO: bad field position to switch into, bad move, bad targets, bad targets with templockedmove, battle status, bad pkmn id, wrong team pkmn id, duplicate pkmn id, can't switch out but tried, invalid switch mon (null hp pos), duplicate switch mon Assert.False(PBEBattle.SelectActionsIfValid(t0, a)); // False because actions were already submitted Assert.False(PBEBattle.SelectActionsIfValid(t0, Array.Empty <PBETurnAction>())); // False for 0 despite us now needing 0 additional actions Assert.True(PBEBattle.SelectActionsIfValid(t1, new PBETurnAction(darkrai, PBEMove.Protect, PBETurnTarget.AllyCenter))); // True for good actions battle.RunTurn(); #endregion #region More checks var s = new PBESwitchIn(magikarp, PBEFieldPosition.Center); Assert.Throws <ArgumentNullException>(() => PBEBattle.SelectSwitchesIfValid(null, s)); // Throw for null team Assert.Throws <ArgumentNullException>(() => PBEBattle.SelectSwitchesIfValid(t0, (IReadOnlyList <PBESwitchIn>)null)); // Throw for null collection Assert.Throws <ArgumentNullException>(() => PBEBattle.SelectSwitchesIfValid(t0, new PBESwitchIn[] { null })); // Throw for null elements Assert.False(PBEBattle.SelectSwitchesIfValid(t0, s, s)); // False for too many Assert.True(PBEBattle.SelectSwitchesIfValid(t0, s)); // True for good switches // Below two wouldn't work because of battle status lol //Assert.False(PBEBattle.SelectSwitchesIfValid(t0, s)); // False because switches were already submitted //Assert.False(PBEBattle.SelectSwitchesIfValid(t0, Array.Empty<PBESwitchIn>())); // False for 0 despite us now needing 0 additional actions //battle.RunSwitches(); #endregion #region Cleanup battle.OnNewEvent -= PBEBattle.ConsoleBattleEventHandler; #endregion }
public void Basic_Actions() { #region Setup PBEDataProvider.GlobalRandom.Seed = 0; PBESettings settings = PBESettings.DefaultSettings; var p0 = new TestPokemonCollection(2); p0[0] = new TestPokemon(settings, PBESpecies.Koffing, 0, 100, PBEMove.Selfdestruct); p0[1] = new TestPokemon(settings, PBESpecies.Magikarp, 0, 100, PBEMove.Splash); var p1 = new TestPokemonCollection(1); p1[0] = new TestPokemon(settings, PBESpecies.Darkrai, 0, 100, PBEMove.Protect); var battle = new PBEBattle(PBEBattleFormat.Single, settings, new PBETrainerInfo(p0, "Trainer 0"), new PBETrainerInfo(p1, "Trainer 1")); battle.OnNewEvent += PBEBattle.ConsoleBattleEventHandler; PBETrainer t0 = battle.Trainers[0]; PBETrainer t1 = battle.Trainers[1]; PBEBattlePokemon koffing = t0.Party[0]; PBEBattlePokemon magikarp = t0.Party[1]; PBEBattlePokemon darkrai = t1.Party[0]; battle.Begin(); #endregion #region Darkrai uses Protect, Koffing uses Selfdestruct and faints var a = new PBETurnAction(koffing, PBEMove.Selfdestruct, PBETurnTarget.FoeCenter); Assert.Throws <ArgumentNullException>(() => t0.SelectActionsIfValid((IReadOnlyList <PBETurnAction>)null)); // Throw for null collection Assert.Throws <ArgumentNullException>(() => t0.SelectActionsIfValid(new PBETurnAction[] { null })); // Throw for null elements Assert.NotNull(t0.SelectActionsIfValid(a, a)); // Too many actions Assert.Null(t0.SelectActionsIfValid(a)); // Good actions Assert.NotNull(t0.SelectActionsIfValid(a)); // Actions were already submitted Assert.NotNull(t0.SelectActionsIfValid(Array.Empty <PBETurnAction>())); // 0 despite us now needing 0 additional actions Assert.Null(t1.SelectActionsIfValid(new PBETurnAction(darkrai, PBEMove.Protect, PBETurnTarget.AllyCenter))); // True for good actions battle.RunTurn(); #endregion #region More checks var s = new PBESwitchIn(magikarp, PBEFieldPosition.Center); Assert.Throws <ArgumentNullException>(() => t0.SelectSwitchesIfValid((IReadOnlyList <PBESwitchIn>)null)); // Throw for null collection Assert.Throws <ArgumentNullException>(() => t0.SelectSwitchesIfValid(new PBESwitchIn[] { null })); // Throw for null elements Assert.NotNull(t0.SelectSwitchesIfValid(s, s)); // Too many Assert.Null(t0.SelectSwitchesIfValid(s)); // Good switches // Below two wouldn't work because of battle status lol //Assert.NotNull(t0.SelectSwitchesIfValid(s)); // Switches were already submitted //Assert.NotNull(t0.SelectSwitchesIfValid(Array.Empty<PBESwitchIn>())); // 0 despite us now needing 0 additional switches //battle.RunSwitches(); #endregion #region Cleanup battle.OnNewEvent -= PBEBattle.ConsoleBattleEventHandler; #endregion }
internal PBEActionsResponsePacket(ReadOnlyCollection <byte> buffer, BinaryReader r, PBEBattle battle) { Buffer = buffer; var actions = new PBETurnAction[r.ReadByte()]; for (int i = 0; i < actions.Length; i++) { actions[i] = new PBETurnAction(r); } Actions = new ReadOnlyCollection <PBETurnAction>(actions); }
internal PBEActionsResponsePacket(byte[] data, EndianBinaryReader r) { Data = new ReadOnlyCollection <byte>(data); var actions = new PBETurnAction[r.ReadByte()]; for (int i = 0; i < actions.Length; i++) { actions[i] = new PBETurnAction(r); } Actions = new ReadOnlyCollection <PBETurnAction>(actions); }
public void PushSwitch(PBEBattlePokemon switcher) { PBEBattlePokemon pkmn = _pkmn[_index]; var a = new PBETurnAction(pkmn, switcher); pkmn.TurnAction = a; _actions[_index] = a; _standBy[_index] = switcher; _index++; ActionsLoop(); }
public void PushMove(PBEMove move, PBETurnTarget targets) { PBEBattlePokemon pkmn = _pkmn[_index]; var a = new PBETurnAction(pkmn, move, targets); pkmn.TurnAction = a; _actions[_index] = a; _standBy[_index] = null; _index++; ActionsLoop(); }
// Wild Pokémon always select a random usable move (unless they are forced to use a move) // They will flee randomly based on their PBEPokemonData.FleeRate only if it's a single battle and they are allowed to flee public static void CreateWildAIActions(this PBETrainer trainer) { if (trainer is null) { throw new ArgumentNullException(nameof(trainer)); } if (trainer.Battle.BattleState != PBEBattleState.WaitingForActions) { throw new InvalidOperationException($"{nameof(trainer.Battle.BattleState)} must be {PBEBattleState.WaitingForActions} to create actions."); } // Try to flee if it's a single wild battle and the Pokémon is a runner if (trainer.IsWild && trainer.Battle.BattleFormat == PBEBattleFormat.Single && trainer.IsFleeValid() is null) { PBEBattlePokemon user = trainer.ActionsRequired[0]; IPBEPokemonData pData = PBEDataProvider.Instance.GetPokemonData(user); if (PBEDataProvider.GlobalRandom.RandomBool(pData.FleeRate, 255)) { string valid = trainer.SelectFleeIfValid(); if (valid != null) { throw new Exception("Wild AI tried to flee but couldn't. - " + valid); } return; } } var actions = new PBETurnAction[trainer.ActionsRequired.Count]; for (int i = 0; i < actions.Length; i++) { PBEBattlePokemon user = trainer.ActionsRequired[i]; // If a Pokémon is forced to struggle, it must use Struggle if (user.IsForcedToStruggle()) { actions[i] = new PBETurnAction(user, PBEMove.Struggle, PBEBattleUtils.GetPossibleTargets(user, user.GetMoveTargets(PBEMove.Struggle))[0]); continue; } // If a Pokémon has a temp locked move (Dig, Dive, ShadowForce) it must be used if (user.TempLockedMove != PBEMove.None) { actions[i] = new PBETurnAction(user, user.TempLockedMove, user.TempLockedTargets); continue; } // The Pokémon is free to fight PBEMove[] usableMoves = user.GetUsableMoves(); PBEMove move = PBEDataProvider.GlobalRandom.RandomElement(usableMoves); actions[i] = new PBETurnAction(user, move, PBEBattle.GetRandomTargetForMetronome(user, move, PBEDataProvider.GlobalRandom)); } string valid2 = trainer.SelectActionsIfValid(actions); if (valid2 != null) { throw new Exception("Wild AI created bad actions. - " + valid2); } }
// TODO: Control multiple trainers of a multi battle team /// <summary>Creates valid actions for a battle turn for a specific team.</summary> /// <param name="trainer">The trainer to create actions for.</param> /// <exception cref="InvalidOperationException">Thrown when <paramref name="trainer"/> has no active battlers or <paramref name="trainer"/>'s <see cref="PBETrainer.Battle"/>'s <see cref="PBEBattle.BattleState"/> is not <see cref="PBEBattleState.WaitingForActions"/>.</exception> /// <exception cref="ArgumentOutOfRangeException">Thrown when a Pokémon has no moves, the AI tries to use a move with invalid targets, or <paramref name="trainer"/>'s <see cref="PBETrainer.Battle"/>'s <see cref="PBEBattle.BattleFormat"/> is invalid.</exception> public static void CreateAIActions(this PBETrainer trainer) { if (trainer is null) { throw new ArgumentNullException(nameof(trainer)); } if (trainer.Battle.BattleState != PBEBattleState.WaitingForActions) { throw new InvalidOperationException($"{nameof(trainer.Battle.BattleState)} must be {PBEBattleState.WaitingForActions} to create actions."); } if (trainer.IsWild) { trainer.CreateWildAIActions(); return; } var actions = new PBETurnAction[trainer.ActionsRequired.Count]; var standBy = new List <PBEBattlePokemon>(); for (int i = 0; i < actions.Length; i++) { PBEBattlePokemon user = trainer.ActionsRequired[i]; // If a Pokémon is forced to struggle, it is best that it just stays in until it faints if (user.IsForcedToStruggle()) { actions[i] = new PBETurnAction(user, PBEMove.Struggle, PBEBattleUtils.GetPossibleTargets(user, user.GetMoveTargets(PBEMove.Struggle))[0]); continue; } // If a Pokémon has a temp locked move (Dig, Dive, ShadowForce) it must be used else if (user.TempLockedMove != PBEMove.None) { actions[i] = new PBETurnAction(user, user.TempLockedMove, user.TempLockedTargets); continue; } // The Pokémon is free to switch or fight (unless it cannot switch due to Magnet Pull etc) actions[i] = DecideAction(trainer, user, actions, standBy); // Action was chosen, finish up for this Pokémon if (actions[i].Decision == PBETurnDecision.SwitchOut) { standBy.Add(trainer.TryGetPokemon(actions[i].SwitchPokemonId)); } } string valid = trainer.SelectActionsIfValid(actions); if (valid != null) { throw new Exception("AI created bad actions. - " + valid); } }
/// <summary>Creates valid actions for a battle turn for a specific team.</summary> /// <exception cref="InvalidOperationException">Thrown when <see name="Trainer"/> has no active battlers or <see name="Trainer"/>'s <see cref="PBETrainer.Battle"/>'s <see cref="PBEBattle.BattleState"/> is not <see cref="PBEBattleState.WaitingForActions"/>.</exception> /// <exception cref="ArgumentOutOfRangeException">Thrown when a Pokémon has no moves, the AI tries to use a move with invalid targets, or <see name="Trainer"/>'s <see cref="PBETrainer.Battle"/>'s <see cref="PBEBattle.BattleFormat"/> is invalid.</exception> public void CreateActions() { if (Trainer.Battle.BattleState != PBEBattleState.WaitingForActions) { throw new InvalidOperationException($"{nameof(Trainer.Battle.BattleState)} must be {PBEBattleState.WaitingForActions} to create actions."); } int count = Trainer.ActionsRequired.Count; var actions = new List <PBETurnAction>(count); var standBy = new List <PBEBattlePokemon>(); for (int i = 0; i < count; i++) { PBEBattlePokemon user = Trainer.ActionsRequired[i]; // If a Pokémon is forced to struggle, it is best that it just stays in until it faints if (user.IsForcedToStruggle()) { actions.Add(new PBETurnAction(user, PBEMove.Struggle, PBEBattleUtils.GetPossibleTargets(user, user.GetMoveTargets(PBEMove.Struggle))[0])); continue; } // If a Pokémon has a temp locked move (Dig, Dive, ShadowForce) it must be used else if (user.TempLockedMove != PBEMove.None) { actions.Add(new PBETurnAction(user, user.TempLockedMove, user.TempLockedTargets)); continue; } // The Pokémon is free to switch or fight (unless it cannot switch due to Magnet Pull etc) PBETurnAction a = DecideAction(user, actions, standBy); // Action was chosen, finish up for this Pokémon if (a.Decision == PBETurnDecision.SwitchOut) { standBy.Add(Trainer.GetPokemon(a.SwitchPokemonId)); } actions.Add(a); } if (!Trainer.SelectActionsIfValid(actions, out string?valid)) { throw new Exception("AI created bad actions. - " + valid); } }
/// <summary>Creates valid actions for a battle turn for a specific team.</summary> /// <param name="team">The team to create actions for.</param> /// <exception cref="InvalidOperationException">Thrown when <paramref name="team"/> has no active battlers or <paramref name="team"/>'s <see cref="PBETeam.Battle"/>'s <see cref="PBEBattle.BattleState"/> is not <see cref="PBEBattleState.WaitingForActions"/>.</exception> /// <exception cref="ArgumentOutOfRangeException">Thrown when a Pokémon has no moves, the AI tries to use a move with invalid targets, or <paramref name="team"/>'s <see cref="PBETeam.Battle"/>'s <see cref="PBEBattle.BattleFormat"/> is invalid.</exception> public static PBETurnAction[] CreateActions(PBETeam team) { if (team == null) { throw new ArgumentNullException(nameof(team)); } if (team.IsDisposed) { throw new ObjectDisposedException(nameof(team)); } if (team.Battle.BattleState != PBEBattleState.WaitingForActions) { throw new InvalidOperationException($"{nameof(team.Battle.BattleState)} must be {PBEBattleState.WaitingForActions} to create actions."); } var actions = new PBETurnAction[team.ActionsRequired.Count]; var standBy = new List <PBEPokemon>(); for (int i = 0; i < actions.Length; i++) { PBEPokemon user = team.ActionsRequired[i]; // If a Pokémon is forced to struggle, it is best that it just stays in until it faints if (user.IsForcedToStruggle()) { actions[i] = new PBETurnAction(user.Id, PBEMove.Struggle, GetPossibleTargets(user, user.GetMoveTargets(PBEMove.Struggle))[0]); } // If a Pokémon has a temp locked move (Dig, Dive, Shadow Force) it must be used else if (user.TempLockedMove != PBEMove.None) { actions[i] = new PBETurnAction(user.Id, user.TempLockedMove, user.TempLockedTargets); } // The Pokémon is free to switch or fight (unless it cannot switch due to Magnet Pull etc) else { // Gather all options of switching and moves PBEPokemon[] availableForSwitch = team.Party.Except(standBy).Where(p => p.FieldPosition == PBEFieldPosition.None && p.HP > 0).ToArray(); PBEMove[] usableMoves = user.GetUsableMoves(); var possibleActions = new List <(PBETurnAction Action, double Score)>(); for (int m = 0; m < usableMoves.Length; m++) // Score moves { PBEMove move = usableMoves[m]; PBEType moveType = user.GetMoveType(move); PBEMoveTarget moveTargets = user.GetMoveTargets(move); PBETurnTarget[] possibleTargets = PBEMoveData.IsSpreadMove(moveTargets) ? new PBETurnTarget[] { GetSpreadMoveTargets(user, moveTargets) } : GetPossibleTargets(user, moveTargets); foreach (PBETurnTarget possibleTarget in possibleTargets) { // TODO: RandomFoeSurrounding (probably just account for the specific effects that use this target type) // TODO: Don't queue up to do the same thing (two trying to afflict the same target when there are multiple targets) var targets = new List <PBEPokemon>(); if (possibleTarget.HasFlag(PBETurnTarget.AllyLeft)) { targets.Add(team.TryGetPokemon(PBEFieldPosition.Left)); } if (possibleTarget.HasFlag(PBETurnTarget.AllyCenter)) { targets.Add(team.TryGetPokemon(PBEFieldPosition.Center)); } if (possibleTarget.HasFlag(PBETurnTarget.AllyRight)) { targets.Add(team.TryGetPokemon(PBEFieldPosition.Right)); } if (possibleTarget.HasFlag(PBETurnTarget.FoeLeft)) { targets.Add(team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Left)); } if (possibleTarget.HasFlag(PBETurnTarget.FoeCenter)) { targets.Add(team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Center)); } if (possibleTarget.HasFlag(PBETurnTarget.FoeRight)) { targets.Add(team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Right)); } double score; if (targets.All(p => p == null)) { score = -100; } else { score = 0d; targets.RemoveAll(p => p == null); PBEMoveData mData = PBEMoveData.Data[move]; switch (mData.Effect) { case PBEMoveEffect.Attract: { foreach (PBEPokemon target in targets) { // TODO: Destiny knot if (target.IsAttractionPossible(user, useKnownInfo: true) == PBEResult.Success) { score += target.Team == team ? -20 : +40; } else { score += target.Team == team ? 0 : -60; } } break; } case PBEMoveEffect.BrickBreak: case PBEMoveEffect.Dig: case PBEMoveEffect.Dive: case PBEMoveEffect.Fly: case PBEMoveEffect.Hit: case PBEMoveEffect.Hit__MaybeBurn: case PBEMoveEffect.Hit__MaybeBurn__10PercentFlinch: case PBEMoveEffect.Hit__MaybeConfuse: case PBEMoveEffect.Hit__MaybeFlinch: case PBEMoveEffect.Hit__MaybeFreeze: case PBEMoveEffect.Hit__MaybeFreeze__10PercentFlinch: case PBEMoveEffect.Hit__MaybeLowerTarget_ACC_By1: case PBEMoveEffect.Hit__MaybeLowerTarget_ATK_By1: case PBEMoveEffect.Hit__MaybeLowerTarget_DEF_By1: case PBEMoveEffect.Hit__MaybeLowerTarget_SPATK_By1: case PBEMoveEffect.Hit__MaybeLowerTarget_SPDEF_By1: case PBEMoveEffect.Hit__MaybeLowerTarget_SPDEF_By2: case PBEMoveEffect.Hit__MaybeLowerTarget_SPE_By1: case PBEMoveEffect.Hit__MaybeLowerUser_ATK_DEF_By1: case PBEMoveEffect.Hit__MaybeLowerUser_DEF_SPDEF_By1: case PBEMoveEffect.Hit__MaybeLowerUser_SPATK_By2: case PBEMoveEffect.Hit__MaybeLowerUser_SPE_By1: case PBEMoveEffect.Hit__MaybeLowerUser_SPE_DEF_SPDEF_By1: case PBEMoveEffect.Hit__MaybeParalyze: case PBEMoveEffect.Hit__MaybeParalyze__10PercentFlinch: case PBEMoveEffect.Hit__MaybePoison: case PBEMoveEffect.Hit__MaybeRaiseUser_ATK_By1: case PBEMoveEffect.Hit__MaybeRaiseUser_ATK_DEF_SPATK_SPDEF_SPE_By1: case PBEMoveEffect.Hit__MaybeRaiseUser_DEF_By1: case PBEMoveEffect.Hit__MaybeRaiseUser_SPATK_By1: case PBEMoveEffect.Hit__MaybeRaiseUser_SPE_By1: case PBEMoveEffect.Hit__MaybeToxic: case PBEMoveEffect.HPDrain: case PBEMoveEffect.Recoil: case PBEMoveEffect.Recoil__10PercentBurn: case PBEMoveEffect.Recoil__10PercentParalyze: case PBEMoveEffect.SuckerPunch: { foreach (PBEPokemon target in targets) { // TODO: Favor hitting ally with move if waterabsorb/voltabsorb etc // TODO: Liquid ooze // TODO: Check items // TODO: Stat changes and accuracy (even thunder/guillotine accuracy) // TODO: Check base power specifically against hp remaining (include spread move damage reduction) target.IsAffectedByMove(user, moveType, out double damageMultiplier, useKnownInfo: true); if (damageMultiplier <= 0) // (-infinity, 0.0] Ineffective { score += target.Team == team ? 0 : -60; } else if (damageMultiplier <= 0.25) // (0.0, 0.25] NotVeryEffective { score += target.Team == team ? -5 : -30; } else if (damageMultiplier < 1) // (0.25, 1.0) NotVeryEffective { score += target.Team == team ? -10 : -10; } else if (damageMultiplier == 1) // [1.0, 1.0] Normal { score += target.Team == team ? -15 : +10; } else if (damageMultiplier < 4) // (1.0, 4.0) SuperEffective { score += target.Team == team ? -20 : +25; } else // [4.0, infinity) SuperEffective { score += target.Team == team ? -30 : +40; } if (user.HasType(moveType) && damageMultiplier > 0) // STAB { score += (user.Ability == PBEAbility.Adaptability ? 7 : 5) * (target.Team == team ? -1 : +1); } } break; } case PBEMoveEffect.Burn: { foreach (PBEPokemon target in targets) { // TODO: Heatproof, physical attacker if (target.IsBurnPossible(user, useKnownInfo: true) == PBEResult.Success) { score += target.Team == team ? -20 : +40; } else { score += target.Team == team ? 0 : -60; } } break; } case PBEMoveEffect.ChangeTarget_ACC: { foreach (PBEPokemon target in targets) { ScoreStatChange(user, target, PBEStat.Accuracy, mData.EffectParam, ref score); } break; } case PBEMoveEffect.ChangeTarget_ATK: { foreach (PBEPokemon target in targets) { ScoreStatChange(user, target, PBEStat.Attack, mData.EffectParam, ref score); } break; } case PBEMoveEffect.ChangeTarget_DEF: { foreach (PBEPokemon target in targets) { ScoreStatChange(user, target, PBEStat.Defense, mData.EffectParam, ref score); } break; } case PBEMoveEffect.ChangeTarget_EVA: { foreach (PBEPokemon target in targets) { ScoreStatChange(user, target, PBEStat.Evasion, mData.EffectParam, ref score); } break; } case PBEMoveEffect.ChangeTarget_SPATK: { foreach (PBEPokemon target in targets) { ScoreStatChange(user, target, PBEStat.SpAttack, mData.EffectParam, ref score); } break; } case PBEMoveEffect.ChangeTarget_SPDEF: { foreach (PBEPokemon target in targets) { ScoreStatChange(user, target, PBEStat.SpDefense, mData.EffectParam, ref score); } break; } case PBEMoveEffect.ChangeTarget_SPE: { foreach (PBEPokemon target in targets) { ScoreStatChange(user, target, PBEStat.Speed, mData.EffectParam, ref score); } break; } case PBEMoveEffect.Confuse: case PBEMoveEffect.Flatter: case PBEMoveEffect.Swagger: { foreach (PBEPokemon target in targets) { // TODO: Only swagger/flatter if the opponent most likely won't use it against you if (target.IsConfusionPossible(user, useKnownInfo: true) == PBEResult.Success) { score += target.Team == team ? -20 : +40; } else { score += target.Team == team ? 0 : -60; } } break; } case PBEMoveEffect.Growth: { int change = team.Battle.WillLeafGuardActivate() ? +2 : +1; foreach (PBEPokemon target in targets) { ScoreStatChange(user, target, PBEStat.Attack, change, ref score); ScoreStatChange(user, target, PBEStat.SpAttack, change, ref score); } break; } case PBEMoveEffect.LeechSeed: { foreach (PBEPokemon target in targets) { if (target.IsLeechSeedPossible(useKnownInfo: true) == PBEResult.Success) { score += target.Team == team ? -20 : +40; } else { score += target.Team == team ? 0 : -60; } } break; } case PBEMoveEffect.LightScreen: { score += team.TeamStatus.HasFlag(PBETeamStatus.LightScreen) || IsTeammateUsingEffect(actions, PBEMoveEffect.LightScreen) ? -100 : +40; break; } case PBEMoveEffect.LowerTarget_ATK_DEF_By1: { foreach (PBEPokemon target in targets) { ScoreStatChange(user, target, PBEStat.Attack, -1, ref score); ScoreStatChange(user, target, PBEStat.Defense, -1, ref score); } break; } case PBEMoveEffect.LowerTarget_DEF_SPDEF_By1_Raise_ATK_SPATK_SPE_By2: { foreach (PBEPokemon target in targets) { ScoreStatChange(user, target, PBEStat.Defense, -1, ref score); ScoreStatChange(user, target, PBEStat.SpDefense, -1, ref score); ScoreStatChange(user, target, PBEStat.Attack, +2, ref score); ScoreStatChange(user, target, PBEStat.SpAttack, +2, ref score); ScoreStatChange(user, target, PBEStat.Speed, +2, ref score); } break; } case PBEMoveEffect.LuckyChant: { score += team.TeamStatus.HasFlag(PBETeamStatus.LuckyChant) || IsTeammateUsingEffect(actions, PBEMoveEffect.LuckyChant) ? -100 : +40; break; } case PBEMoveEffect.Moonlight: case PBEMoveEffect.Rest: case PBEMoveEffect.RestoreTargetHP: { foreach (PBEPokemon target in targets) { if (target.Team == team) { score += HPAware(target.HPPercentage, +45, -15); } else { score -= 100; } } break; } case PBEMoveEffect.Nothing: case PBEMoveEffect.Teleport: { score -= 100; break; } case PBEMoveEffect.Paralyze: { foreach (PBEPokemon target in targets) { // TODO: Effectiveness with thunder wave/glare if (target.IsParalysisPossible(user, useKnownInfo: true) == PBEResult.Success) { score += target.Team == team ? -20 : +40; } else { score += target.Team == team ? 0 : -60; } } break; } case PBEMoveEffect.Poison: case PBEMoveEffect.Toxic: { foreach (PBEPokemon target in targets) { // TODO: Poison Heal if (target.IsPoisonPossible(user, useKnownInfo: true) == PBEResult.Success) { score += target.Team == team ? -20 : +40; } else { score += target.Team == team ? 0 : -60; } } break; } case PBEMoveEffect.RaiseTarget_ATK_ACC_By1: { foreach (PBEPokemon target in targets) { ScoreStatChange(user, target, PBEStat.Attack, +1, ref score); ScoreStatChange(user, target, PBEStat.Accuracy, +1, ref score); } break; } case PBEMoveEffect.RaiseTarget_ATK_DEF_By1: { foreach (PBEPokemon target in targets) { ScoreStatChange(user, target, PBEStat.Attack, +1, ref score); ScoreStatChange(user, target, PBEStat.Defense, +1, ref score); } break; } case PBEMoveEffect.RaiseTarget_ATK_DEF_ACC_By1: { foreach (PBEPokemon target in targets) { ScoreStatChange(user, target, PBEStat.Attack, +1, ref score); ScoreStatChange(user, target, PBEStat.Defense, +1, ref score); ScoreStatChange(user, target, PBEStat.Accuracy, +1, ref score); } break; } case PBEMoveEffect.RaiseTarget_ATK_SPATK_By1: { foreach (PBEPokemon target in targets) { ScoreStatChange(user, target, PBEStat.Attack, +1, ref score); ScoreStatChange(user, target, PBEStat.SpAttack, +1, ref score); } break; } case PBEMoveEffect.RaiseTarget_ATK_SPE_By1: { foreach (PBEPokemon target in targets) { ScoreStatChange(user, target, PBEStat.Attack, +1, ref score); ScoreStatChange(user, target, PBEStat.Speed, +1, ref score); } break; } case PBEMoveEffect.RaiseTarget_DEF_SPDEF_By1: { foreach (PBEPokemon target in targets) { ScoreStatChange(user, target, PBEStat.Defense, +1, ref score); ScoreStatChange(user, target, PBEStat.SpDefense, +1, ref score); } break; } case PBEMoveEffect.RaiseTarget_SPATK_SPDEF_By1: { foreach (PBEPokemon target in targets) { ScoreStatChange(user, target, PBEStat.SpAttack, +1, ref score); ScoreStatChange(user, target, PBEStat.SpDefense, +1, ref score); } break; } case PBEMoveEffect.RaiseTarget_SPATK_SPDEF_SPE_By1: { foreach (PBEPokemon target in targets) { ScoreStatChange(user, target, PBEStat.SpAttack, +1, ref score); ScoreStatChange(user, target, PBEStat.SpDefense, +1, ref score); ScoreStatChange(user, target, PBEStat.Speed, +1, ref score); } break; } case PBEMoveEffect.RaiseTarget_SPE_By2_ATK_By1: { foreach (PBEPokemon target in targets) { ScoreStatChange(user, target, PBEStat.Speed, +2, ref score); ScoreStatChange(user, target, PBEStat.Attack, +1, ref score); } break; } case PBEMoveEffect.Reflect: { score += team.TeamStatus.HasFlag(PBETeamStatus.Reflect) || IsTeammateUsingEffect(actions, PBEMoveEffect.Reflect) ? -100 : +40; break; } case PBEMoveEffect.Safeguard: { score += team.TeamStatus.HasFlag(PBETeamStatus.Safeguard) || IsTeammateUsingEffect(actions, PBEMoveEffect.Safeguard) ? -100 : +40; break; } case PBEMoveEffect.Sleep: { foreach (PBEPokemon target in targets) { // TODO: Bad Dreams if (target.IsSleepPossible(user, useKnownInfo: true) == PBEResult.Success) { score += target.Team == team ? -20 : +40; } else { score += target.Team == team ? 0 : -60; } } break; } case PBEMoveEffect.Substitute: { foreach (PBEPokemon target in targets) { if (target.IsSubstitutePossible() == PBEResult.Success) { score += target.Team == team?HPAware(target.HPPercentage, -30, +50) : -60; } else { score += target.Team == team ? 0 : -20; } } break; } case PBEMoveEffect.ChangeTarget_SPATK__IfAttractionPossible: case PBEMoveEffect.Conversion: case PBEMoveEffect.Curse: case PBEMoveEffect.Endeavor: case PBEMoveEffect.FinalGambit: case PBEMoveEffect.FocusEnergy: case PBEMoveEffect.GastroAcid: case PBEMoveEffect.Hail: case PBEMoveEffect.Haze: case PBEMoveEffect.HelpingHand: case PBEMoveEffect.HPDrain__RequireSleep: case PBEMoveEffect.MagnetRise: case PBEMoveEffect.Metronome: case PBEMoveEffect.OneHitKnockout: case PBEMoveEffect.PainSplit: case PBEMoveEffect.PowerTrick: case PBEMoveEffect.Protect: case PBEMoveEffect.PsychUp: case PBEMoveEffect.Psywave: case PBEMoveEffect.RainDance: case PBEMoveEffect.Sandstorm: case PBEMoveEffect.SeismicToss: case PBEMoveEffect.Selfdestruct: case PBEMoveEffect.SetDamage: case PBEMoveEffect.SimpleBeam: case PBEMoveEffect.Snore: case PBEMoveEffect.Soak: case PBEMoveEffect.Spikes: case PBEMoveEffect.StealthRock: case PBEMoveEffect.SunnyDay: case PBEMoveEffect.SuperFang: case PBEMoveEffect.Tailwind: case PBEMoveEffect.ToxicSpikes: case PBEMoveEffect.Transform: case PBEMoveEffect.TrickRoom: case PBEMoveEffect.Whirlwind: case PBEMoveEffect.WideGuard: { // TODO Moves break; } default: throw new ArgumentOutOfRangeException(nameof(PBEMoveData.Effect)); } } possibleActions.Add((new PBETurnAction(user.Id, move, possibleTarget), score)); } } if (user.CanSwitchOut()) { for (int s = 0; s < availableForSwitch.Length; s++) // Score switches { PBEPokemon switchPkmn = availableForSwitch[s]; // TODO: Entry hazards // TODO: Known moves of active battlers // TODO: Type effectiveness double score = -10d; possibleActions.Add((new PBETurnAction(user.Id, switchPkmn.Id), score)); } } string ToDebugString((PBETurnAction Action, double Score) t) { string str = "{"; if (t.Action.Decision == PBETurnDecision.Fight) { str += string.Format("Fight {0} {1}", t.Action.FightMove, t.Action.FightTargets); } else { str += string.Format("Switch {0}", team.TryGetPokemon(t.Action.SwitchPokemonId).Nickname); } str += " [" + t.Score + "]}"; return(str); } IOrderedEnumerable <(PBETurnAction Action, double Score)> byScore = possibleActions.OrderByDescending(t => t.Score); Debug.WriteLine("{0}'s possible actions: {1}", user.Nickname, byScore.Select(t => ToDebugString(t)).Print()); double bestScore = byScore.First().Score; actions[i] = byScore.Where(t => t.Score == bestScore).ToArray().RandomElement().Action; // Pick random action of the ones that tied for best score } // Action was chosen, finish up for this Pokémon if (actions[i].Decision == PBETurnDecision.SwitchOut) { standBy.Add(team.TryGetPokemon(actions[i].SwitchPokemonId)); } } return(actions); }
/// <summary>Creates valid actions for a battle turn for a specific team.</summary> /// <param name="team">The team to create actions for.</param> /// <exception cref="InvalidOperationException">Thrown when <paramref name="team"/> has no active battlers or <paramref name="team"/>'s <see cref="PBETeam.Battle"/>'s <see cref="PBEBattle.BattleState"/> is not <see cref="PBEBattleState.WaitingForActions"/>.</exception> /// <exception cref="ArgumentOutOfRangeException">Thrown when a Pokémon has no moves, the AI tries to use a move with invalid targets, or <paramref name="team"/>'s <see cref="PBETeam.Battle"/>'s <see cref="PBEBattle.BattleFormat"/> is invalid.</exception> public static PBETurnAction[] CreateActions(PBETeam team) { if (team == null) { throw new ArgumentNullException(nameof(team)); } if (team.Battle.BattleState != PBEBattleState.WaitingForActions) { throw new InvalidOperationException($"{nameof(team.Battle.BattleState)} must be {PBEBattleState.WaitingForActions} to create actions."); } var actions = new PBETurnAction[team.ActionsRequired.Count]; var standBy = new List <PBEPokemon>(); for (int i = 0; i < actions.Length; i++) { PBEPokemon pkmn = team.ActionsRequired[i]; // If a Pokémon is forced to struggle, it is best that it just stays in until it faints if (pkmn.IsForcedToStruggle()) { actions[i] = new PBETurnAction(pkmn.Id, PBEMove.Struggle, GetPossibleTargets(pkmn, pkmn.GetMoveTargets(PBEMove.Struggle))[0]); } // If a Pokémon has a temp locked move (Dig, Dive, Shadow Force) it must be used else if (pkmn.TempLockedMove != PBEMove.None) { actions[i] = new PBETurnAction(pkmn.Id, pkmn.TempLockedMove, pkmn.TempLockedTargets); } // The Pokémon is free to switch or fight (unless it cannot switch due to Magnet Pull etc) else { // Gather all options of switching and moves PBEPokemon[] availableForSwitch = team.Party.Except(standBy).Where(p => p.FieldPosition == PBEFieldPosition.None && p.HP > 0).ToArray(); PBEMove[] usableMoves = pkmn.GetUsableMoves(); var possibleActions = new List <(PBETurnAction Action, double Score)>(); for (int m = 0; m < usableMoves.Length; m++) // Score moves { PBEMove move = usableMoves[m]; PBEType moveType = pkmn.GetMoveType(move); PBEMoveTarget moveTargets = pkmn.GetMoveTargets(move); PBETurnTarget[] possibleTargets = PBEMoveData.IsSpreadMove(moveTargets) ? new PBETurnTarget[] { GetSpreadMoveTargets(pkmn, moveTargets) } : GetPossibleTargets(pkmn, moveTargets); foreach (PBETurnTarget possibleTarget in possibleTargets) { // TODO: RandomFoeSurrounding (probably just account for the specific effects that use this target type) var targets = new List <PBEPokemon>(); if (possibleTarget.HasFlag(PBETurnTarget.AllyLeft)) { targets.Add(team.TryGetPokemon(PBEFieldPosition.Left)); } if (possibleTarget.HasFlag(PBETurnTarget.AllyCenter)) { targets.Add(team.TryGetPokemon(PBEFieldPosition.Center)); } if (possibleTarget.HasFlag(PBETurnTarget.AllyRight)) { targets.Add(team.TryGetPokemon(PBEFieldPosition.Right)); } if (possibleTarget.HasFlag(PBETurnTarget.FoeLeft)) { targets.Add(team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Left)); } if (possibleTarget.HasFlag(PBETurnTarget.FoeCenter)) { targets.Add(team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Center)); } if (possibleTarget.HasFlag(PBETurnTarget.FoeRight)) { targets.Add(team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Right)); } double score = 0.0; switch (PBEMoveData.Data[move].Effect) { case PBEMoveEffect.Burn: case PBEMoveEffect.Paralyze: case PBEMoveEffect.Poison: case PBEMoveEffect.Sleep: case PBEMoveEffect.Toxic: { foreach (PBEPokemon target in targets) { if (target == null) { // TODO: If all targets are null, this should give a bad score } else { // TODO: Effectiveness check // TODO: Favor sleep with Bad Dreams (unless ally) if (target.Status1 != PBEStatus1.None) { score += target.Team == team.OpposingTeam ? -60 : 0; } else { score += target.Team == team.OpposingTeam ? +40 : -20; } } } break; } case PBEMoveEffect.Hail: { if (team.Battle.Weather == PBEWeather.Hailstorm) { score -= 100; } break; } case PBEMoveEffect.BrickBreak: case PBEMoveEffect.Dig: case PBEMoveEffect.Dive: case PBEMoveEffect.Fly: case PBEMoveEffect.Hit: case PBEMoveEffect.Hit__MaybeBurn: case PBEMoveEffect.Hit__MaybeConfuse: case PBEMoveEffect.Hit__MaybeFlinch: case PBEMoveEffect.Hit__MaybeFreeze: case PBEMoveEffect.Hit__MaybeLowerTarget_ACC_By1: case PBEMoveEffect.Hit__MaybeLowerTarget_ATK_By1: case PBEMoveEffect.Hit__MaybeLowerTarget_DEF_By1: case PBEMoveEffect.Hit__MaybeLowerTarget_SPATK_By1: case PBEMoveEffect.Hit__MaybeLowerTarget_SPDEF_By1: case PBEMoveEffect.Hit__MaybeLowerTarget_SPDEF_By2: case PBEMoveEffect.Hit__MaybeLowerTarget_SPE_By1: case PBEMoveEffect.Hit__MaybeLowerUser_ATK_DEF_By1: case PBEMoveEffect.Hit__MaybeLowerUser_DEF_SPDEF_By1: case PBEMoveEffect.Hit__MaybeLowerUser_SPATK_By2: case PBEMoveEffect.Hit__MaybeLowerUser_SPE_By1: case PBEMoveEffect.Hit__MaybeLowerUser_SPE_DEF_SPDEF_By1: case PBEMoveEffect.Hit__MaybeParalyze: case PBEMoveEffect.Hit__MaybePoison: case PBEMoveEffect.Hit__MaybeRaiseUser_ATK_By1: case PBEMoveEffect.Hit__MaybeRaiseUser_ATK_DEF_SPATK_SPDEF_SPE_By1: case PBEMoveEffect.Hit__MaybeRaiseUser_DEF_By1: case PBEMoveEffect.Hit__MaybeRaiseUser_SPATK_By1: case PBEMoveEffect.Hit__MaybeRaiseUser_SPE_By1: case PBEMoveEffect.Hit__MaybeToxic: case PBEMoveEffect.HPDrain: case PBEMoveEffect.Recoil: case PBEMoveEffect.FlareBlitz: case PBEMoveEffect.SuckerPunch: case PBEMoveEffect.VoltTackle: { foreach (PBEPokemon target in targets) { if (target == null) { // TODO: If all targets are null, this should give a bad score } else { // TODO: Put type checking somewhere in PBEPokemon (levitate, wonder guard, etc) // TODO: Favor hitting ally with move if it absorbs it // TODO: Check items // TODO: Stat changes and accuracy // TODO: Check base power specifically against hp remaining (include spread move damage reduction) double typeEffectiveness = PBEPokemonData.TypeEffectiveness[moveType][target.KnownType1]; typeEffectiveness *= PBEPokemonData.TypeEffectiveness[moveType][target.KnownType2]; if (typeEffectiveness <= 0.0) // (-infinity, 0.0] Ineffective { score += target.Team == team.OpposingTeam ? -60 : -1; } else if (typeEffectiveness <= 0.25) // (0.0, 0.25] NotVeryEffective { score += target.Team == team.OpposingTeam ? -30 : -5; } else if (typeEffectiveness < 1.0) // (0.25, 1.0) NotVeryEffective { score += target.Team == team.OpposingTeam ? -10 : -10; } else if (typeEffectiveness == 1.0) // [1.0, 1.0] Normal { score += target.Team == team.OpposingTeam ? +10 : -15; } else if (typeEffectiveness < 4.0) // (1.0, 4.0) SuperEffective { score += target.Team == team.OpposingTeam ? +25 : -20; } else // [4.0, infinity) SuperEffective { score += target.Team == team.OpposingTeam ? +40 : -30; } if (pkmn.HasType(moveType) && typeEffectiveness > 0.0) // STAB { score += (pkmn.Ability == PBEAbility.Adaptability ? 15 : 10) * (target.Team == team.OpposingTeam ? +1 : -1); } } } break; } case PBEMoveEffect.Moonlight: case PBEMoveEffect.Rest: case PBEMoveEffect.RestoreTargetHP: case PBEMoveEffect.RestoreUserHP: { PBEPokemon target = targets[0]; if (target == null || target.Team == team.OpposingTeam) { score -= 100; } else // Ally { // 0% = +45, 25% = +30, 50% = +15, 75% = 0, 100% = -15 score -= (60 * target.HPPercentage) - 45; } break; } case PBEMoveEffect.RainDance: { if (team.Battle.Weather == PBEWeather.Rain) { score -= 100; } break; } case PBEMoveEffect.Sandstorm: { if (team.Battle.Weather == PBEWeather.Sandstorm) { score -= 100; } break; } case PBEMoveEffect.SunnyDay: { if (team.Battle.Weather == PBEWeather.HarshSunlight) { score -= 100; } break; } case PBEMoveEffect.Attract: case PBEMoveEffect.ChangeTarget_ACC: case PBEMoveEffect.ChangeTarget_ATK: case PBEMoveEffect.ChangeTarget_DEF: case PBEMoveEffect.ChangeTarget_EVA: case PBEMoveEffect.ChangeTarget_SPDEF: case PBEMoveEffect.ChangeTarget_SPE: case PBEMoveEffect.ChangeUser_ATK: case PBEMoveEffect.ChangeUser_DEF: case PBEMoveEffect.ChangeUser_EVA: case PBEMoveEffect.ChangeUser_SPATK: case PBEMoveEffect.ChangeUser_SPDEF: case PBEMoveEffect.ChangeUser_SPE: case PBEMoveEffect.Confuse: case PBEMoveEffect.Curse: case PBEMoveEffect.Endeavor: case PBEMoveEffect.Fail: case PBEMoveEffect.FinalGambit: case PBEMoveEffect.Flatter: case PBEMoveEffect.FocusEnergy: case PBEMoveEffect.GastroAcid: case PBEMoveEffect.Growth: case PBEMoveEffect.HelpingHand: case PBEMoveEffect.LeechSeed: case PBEMoveEffect.LightScreen: case PBEMoveEffect.LowerTarget_ATK_DEF_By1: case PBEMoveEffect.LowerUser_DEF_SPDEF_By1_Raise_ATK_SPATK_SPE_By2: case PBEMoveEffect.LuckyChant: case PBEMoveEffect.Metronome: case PBEMoveEffect.OneHitKnockout: case PBEMoveEffect.PainSplit: case PBEMoveEffect.Protect: case PBEMoveEffect.PsychUp: case PBEMoveEffect.Psywave: case PBEMoveEffect.RaiseUser_ATK_ACC_By1: case PBEMoveEffect.RaiseUser_ATK_DEF_By1: case PBEMoveEffect.RaiseUser_ATK_DEF_ACC_By1: case PBEMoveEffect.RaiseUser_ATK_SPATK_By1: case PBEMoveEffect.RaiseUser_ATK_SPE_By1: case PBEMoveEffect.RaiseUser_DEF_SPDEF_By1: case PBEMoveEffect.RaiseUser_SPATK_SPDEF_By1: case PBEMoveEffect.RaiseUser_SPATK_SPDEF_SPE_By1: case PBEMoveEffect.RaiseUser_SPE_By2_ATK_By1: case PBEMoveEffect.Reflect: case PBEMoveEffect.SeismicToss: case PBEMoveEffect.Selfdestruct: case PBEMoveEffect.SetDamage: case PBEMoveEffect.Snore: case PBEMoveEffect.Spikes: case PBEMoveEffect.StealthRock: case PBEMoveEffect.Substitute: case PBEMoveEffect.SuperFang: case PBEMoveEffect.Swagger: case PBEMoveEffect.ToxicSpikes: case PBEMoveEffect.Transform: case PBEMoveEffect.TrickRoom: case PBEMoveEffect.Whirlwind: case PBEMoveEffect.WideGuard: { // TODO Moves break; } default: throw new ArgumentOutOfRangeException(nameof(PBEMoveData.Effect)); } possibleActions.Add((new PBETurnAction(pkmn.Id, move, possibleTarget), score)); } } if (pkmn.CanSwitchOut()) { for (int s = 0; s < availableForSwitch.Length; s++) // Score switches { PBEPokemon switchPkmn = availableForSwitch[s]; // TODO: Entry hazards // TODO: Known moves of active battlers // TODO: Type effectiveness double score = 0.0; possibleActions.Add((new PBETurnAction(pkmn.Id, switchPkmn.Id), score)); } } string ToDebugString((PBETurnAction Action, double Score) t) { string str = "{"; if (t.Action.Decision == PBETurnDecision.Fight) { str += string.Format("Fight {0} {1}", t.Action.FightMove, t.Action.FightTargets); } else { str += string.Format("Switch {0}", team.TryGetPokemon(t.Action.SwitchPokemonId).Nickname); } str += " [" + t.Score + "]}"; return(str); } IOrderedEnumerable <(PBETurnAction Action, double Score)> byScore = possibleActions.OrderByDescending(t => t.Score); Debug.WriteLine("{0}'s possible actions: {1}", pkmn.Nickname, byScore.Select(t => ToDebugString(t)).Print()); double bestScore = byScore.First().Score; actions[i] = byScore.Where(t => t.Score == bestScore).ToArray().RandomElement().Action; // Pick random action of the ones that tied for best score } // Action was chosen, finish up for this Pokémon if (actions[i].Decision == PBETurnDecision.SwitchOut) { standBy.Add(team.TryGetPokemon(actions[i].SwitchPokemonId)); } } return(actions); }
public void Basic_Actions_Checks() { PBEUtils.CreateDatabaseConnection(string.Empty); PBESettings settings = PBESettings.DefaultSettings; var team1Shell = new PBETeamShell(settings, 2, true); PBEPokemonShell p = team1Shell[0]; p.Species = PBESpecies.Koffing; p.Item = PBEItem.None; p.Moveset[0].Move = PBEMove.Selfdestruct; var team2Shell = new PBETeamShell(settings, 1, true); p = team2Shell[0]; p.Species = PBESpecies.Darkrai; p.Item = PBEItem.None; p.Moveset[0].Move = PBEMove.Protect; var battle = new PBEBattle(PBEBattleFormat.Single, team1Shell, "Team 1", team2Shell, "Team 2"); team1Shell.Dispose(); team2Shell.Dispose(); battle.Begin(); PBETeam t = battle.Teams[0]; var a = new PBETurnAction(t.Party[0].Id, PBEMove.Selfdestruct, PBETurnTarget.FoeCenter); var a1 = new PBETurnAction[] { a }; Assert.Throws <ArgumentNullException>(() => PBEBattle.SelectActionsIfValid(null, a1)); // Throw for null team Assert.Throws <ArgumentNullException>(() => PBEBattle.SelectActionsIfValid(t, null)); // Throw for null collection Assert.Throws <ArgumentNullException>(() => PBEBattle.SelectActionsIfValid(t, new PBETurnAction[] { null })); // Throw for null elements Assert.False(PBEBattle.SelectActionsIfValid(t, new PBETurnAction[] { a, a })); // False for too many actions Assert.True(PBEBattle.SelectActionsIfValid(t, a1)); // True for good actions // TODO: bad move, bad targets, bad targets with templockedmove, battle status, bad pkmn id, wrong team pkmn id, duplicate pkmn id, can't switch out but tried, invalid switch mon (null hp pos), duplicate switch mon Assert.False(PBEBattle.SelectActionsIfValid(t, a1)); // False because actions were already submitted Assert.False(PBEBattle.SelectActionsIfValid(t, Array.Empty <PBETurnAction>())); // False for 0 despite us now needing 0 additional actions t = battle.Teams[1]; Assert.True(PBEBattle.SelectActionsIfValid(t, new PBETurnAction[] { new PBETurnAction(t.Party[0].Id, PBEMove.Protect, PBETurnTarget.AllyCenter) })); // True for good actions battle.RunTurn(); // Darkrai uses Protect, Koffing uses Selfdestruct, Koffing faints t = battle.Teams[0]; var s = new PBESwitchIn(t.Party[1].Id, PBEFieldPosition.Center); var s1 = new PBESwitchIn[] { s }; Assert.Throws <ArgumentNullException>(() => PBEBattle.SelectSwitchesIfValid(null, s1)); // Throw for null team Assert.Throws <ArgumentNullException>(() => PBEBattle.SelectSwitchesIfValid(t, null)); // Throw for null collection Assert.Throws <ArgumentNullException>(() => PBEBattle.SelectSwitchesIfValid(t, new PBESwitchIn[] { null })); // Throw for null elements Assert.False(PBEBattle.SelectSwitchesIfValid(t, new PBESwitchIn[] { s, s })); // False for too many Assert.True(PBEBattle.SelectSwitchesIfValid(t, s1)); // True for good switches // Below two wouldn't work because of battle status lol //Assert.False(PBEBattle.SelectSwitchesIfValid(t, s1)); // False because switches were already submitted //Assert.False(PBEBattle.SelectSwitchesIfValid(t, Array.Empty<PBESwitchIn>())); // False for 0 despite us now needing 0 additional actions battle.Dispose(); Assert.Throws <ObjectDisposedException>(() => PBEBattle.SelectActionsIfValid(t, a1)); // Throw for disposed battle Assert.Throws <ObjectDisposedException>(() => PBEBattle.SelectSwitchesIfValid(t, s1)); // Throw for disposed battle }