Esempio n. 1
0
        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);
        }
Esempio n. 5
0
        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();
        }
Esempio n. 6
0
        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();
        }
Esempio n. 7
0
        // 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);
            }
        }
Esempio n. 8
0
        // 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);
            }
        }
Esempio n. 9
0
        /// <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);
            }
        }
Esempio n. 10
0
        /// <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);
        }
Esempio n. 11
0
        /// <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);
        }
Esempio n. 12
0
        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
        }