// Verified: Sturdy and Substitute only activate on damaging attacks (so draining HP or liquid ooze etc can bypass sturdy)
        private ushort DealDamage(PBEBattlePokemon culprit, PBEBattlePokemon victim, int hp, bool ignoreSubstitute = true, bool ignoreSturdy = true)
        {
            if (hp < 1)
            {
                hp = 1;
            }
            if (!ignoreSubstitute && victim.Status2.HasFlag(PBEStatus2.Substitute))
            {
                ushort oldSubHP = victim.SubstituteHP;
                victim.SubstituteHP = (ushort)Math.Max(0, victim.SubstituteHP - hp);
                ushort damageAmt = (ushort)(oldSubHP - victim.SubstituteHP);
                BroadcastStatus2(victim, culprit, PBEStatus2.Substitute, PBEStatusAction.Damage);
                return(damageAmt);
            }
            ushort oldHP         = victim.HP;
            double oldPercentage = victim.HPPercentage;

            victim.HP = (ushort)Math.Max(0, victim.HP - hp);
            bool sturdyHappened = false, focusBandHappened = false, focusSashHappened = false;

            if (!ignoreSturdy && victim.HP == 0)
            {
                // TODO: Endure
                if (oldHP == victim.MaxHP && victim.Ability == PBEAbility.Sturdy && !culprit.HasCancellingAbility())
                {
                    sturdyHappened = true;
                    victim.HP      = 1;
                }
                else if (victim.Item == PBEItem.FocusBand && PBERandom.RandomBool(10, 100))
                {
                    focusBandHappened = true;
                    victim.HP         = 1;
                }
                else if (oldHP == victim.MaxHP && victim.Item == PBEItem.FocusSash)
                {
                    focusSashHappened = true;
                    victim.HP         = 1;
                }
            }
            victim.UpdateHPPercentage();
            BroadcastPkmnHPChanged(victim, oldHP, oldPercentage);
            if (sturdyHappened)
            {
                BroadcastAbility(victim, culprit, PBEAbility.Sturdy, PBEAbilityAction.Damage);
                BroadcastEndure(victim);
            }
            else if (focusBandHappened)
            {
                BroadcastItem(victim, culprit, PBEItem.FocusBand, PBEItemAction.Damage);
            }
            else if (focusSashHappened)
            {
                BroadcastItem(victim, culprit, PBEItem.FocusSash, PBEItemAction.Consumed);
            }
            return((ushort)(oldHP - victim.HP));
        }
        /// <summary>Selects actions if they are valid. Changes the battle state if both teams have selected valid actions.</summary>
        /// <param name="trainer">The trainer the inputted actions belong to.</param>
        /// <param name="actions">The actions the team wishes to execute.</param>
        /// <returns>True if the actions are valid and were selected.</returns>
        /// <exception cref="InvalidOperationException">Thrown when <see cref="BattleState"/> is not <see cref="PBEBattleState.WaitingForActions"/>.</exception>
        public static bool SelectActionsIfValid(PBETrainer trainer, IReadOnlyList <PBETurnAction> actions)
        {
            if (AreActionsValid(trainer, actions))
            {
                trainer.ActionsRequired.Clear();
                foreach (PBETurnAction action in actions)
                {
                    PBEBattlePokemon pkmn = trainer.TryGetPokemon(action.PokemonId);
                    if (action.Decision == PBETurnDecision.Fight && pkmn.GetMoveTargets(action.FightMove) == PBEMoveTarget.RandomFoeSurrounding)
                    {
                        switch (trainer.Battle.BattleFormat)
                        {
                        case PBEBattleFormat.Single:
                        case PBEBattleFormat.Rotation:
                        {
                            action.FightTargets = PBETurnTarget.FoeCenter;
                            break;
                        }

                        case PBEBattleFormat.Double:
                        {
                            action.FightTargets = PBERandom.RandomBool() ? PBETurnTarget.FoeLeft : PBETurnTarget.FoeRight;
                            break;
                        }

                        case PBEBattleFormat.Triple:
                        {
                            if (pkmn.FieldPosition == PBEFieldPosition.Left)
                            {
                                action.FightTargets = PBERandom.RandomBool() ? PBETurnTarget.FoeCenter : PBETurnTarget.FoeRight;
                            }
                            else if (pkmn.FieldPosition == PBEFieldPosition.Center)
                            {
                                PBETeam oppTeam = trainer.Team.OpposingTeam;
                                int     r; // Keep randomly picking until a non-fainted foe is selected
roll:
                                r = PBERandom.RandomInt(0, 2);
                                if (r == 0)
                                {
                                    if (oppTeam.TryGetPokemon(PBEFieldPosition.Left) != null)
                                    {
                                        action.FightTargets = PBETurnTarget.FoeLeft;
                                    }
                                    else
                                    {
                                        goto roll;
                                    }
                                }
                                else if (r == 1)
                                {
                                    if (oppTeam.TryGetPokemon(PBEFieldPosition.Center) != null)
                                    {
                                        action.FightTargets = PBETurnTarget.FoeCenter;
                                    }
                                    else
                                    {
                                        goto roll;
                                    }
                                }
                                else
                                {
                                    if (oppTeam.TryGetPokemon(PBEFieldPosition.Right) != null)
                                    {
                                        action.FightTargets = PBETurnTarget.FoeRight;
                                    }
                                    else
                                    {
                                        goto roll;
                                    }
                                }
                            }
                            else
                            {
                                action.FightTargets = PBERandom.RandomBool() ? PBETurnTarget.FoeLeft : PBETurnTarget.FoeCenter;
                            }
                            break;
                        }

                        default: throw new ArgumentOutOfRangeException(nameof(trainer.Battle.BattleFormat));
                        }
                    }
                    pkmn.TurnAction = action;
                }
                if (trainer.Battle.Trainers.All(t => t.ActionsRequired.Count == 0))
                {
                    trainer.Battle.BattleState = PBEBattleState.ReadyToRunTurn;
                    trainer.Battle.OnStateChanged?.Invoke(trainer.Battle);
                }
                return(true);
            }
            return(false);
        }
Esempio n. 3
0
        private IEnumerable <PBEBattlePokemon> GetActingOrder(IEnumerable <PBEBattlePokemon> pokemon, bool ignoreItemsThatActivate)
        {
            var evaluated = new List <(PBEBattlePokemon Pokemon, double Speed)>(); // TODO: Full Incense, Lagging Tail, Stall, Quick Claw

            foreach (PBEBattlePokemon pkmn in pokemon)
            {
                double speed = pkmn.Speed * GetStatChangeModifier(pkmn.SpeedChange, false);

                switch (pkmn.Item)
                {
                case PBEItem.ChoiceScarf:
                {
                    speed *= 1.5;
                    break;
                }

                case PBEItem.MachoBrace:
                case PBEItem.PowerAnklet:
                case PBEItem.PowerBand:
                case PBEItem.PowerBelt:
                case PBEItem.PowerBracer:
                case PBEItem.PowerLens:
                case PBEItem.PowerWeight:
                {
                    speed *= 0.5;
                    break;
                }

                case PBEItem.QuickPowder:
                {
                    if (pkmn.OriginalSpecies == PBESpecies.Ditto && !pkmn.Status2.HasFlag(PBEStatus2.Transformed))
                    {
                        speed *= 2.0;
                    }
                    break;
                }
                }
                if (ShouldDoWeatherEffects())
                {
                    if (Weather == PBEWeather.HarshSunlight && pkmn.Ability == PBEAbility.Chlorophyll)
                    {
                        speed *= 2.0;
                    }
                    else if (Weather == PBEWeather.Rain && pkmn.Ability == PBEAbility.SwiftSwim)
                    {
                        speed *= 2.0;
                    }
                    else if (Weather == PBEWeather.Sandstorm && pkmn.Ability == PBEAbility.SandRush)
                    {
                        speed *= 2.0;
                    }
                }
                switch (pkmn.Ability)
                {
                case PBEAbility.QuickFeet:
                {
                    if (pkmn.Status1 != PBEStatus1.None)
                    {
                        speed *= 1.5;
                    }
                    break;
                }

                case PBEAbility.SlowStart:
                {
                    if (pkmn.SlowStart_HinderTurnsLeft > 0)
                    {
                        speed *= 0.5;
                    }
                    break;
                }
                }
                if (pkmn.Ability != PBEAbility.QuickFeet && pkmn.Status1 == PBEStatus1.Paralyzed)
                {
                    speed *= 0.25;
                }
                if (pkmn.Team.TeamStatus.HasFlag(PBETeamStatus.Tailwind))
                {
                    speed *= 2.0;
                }

                Debug.WriteLine("Team {0}'s {1}'s evaluated speed: {2}", pkmn.Team.Id, pkmn.Nickname, speed);
                (PBEBattlePokemon Pokemon, double Speed)tup = (pkmn, speed);
                if (evaluated.Count == 0)
                {
                    evaluated.Add(tup);
                }
                else
                {
                    int pkmnTiedWith = evaluated.FindIndex(t => t.Speed == speed);
                    if (pkmnTiedWith != -1) // Speed tie - randomly go before or after the Pokémon it tied with
                    {
                        if (PBERandom.RandomBool())
                        {
                            if (pkmnTiedWith == evaluated.Count - 1)
                            {
                                evaluated.Add(tup);
                            }
                            else
                            {
                                evaluated.Insert(pkmnTiedWith + 1, tup);
                            }
                        }
                        else
                        {
                            evaluated.Insert(pkmnTiedWith, tup);
                        }
                    }
                    else
                    {
                        int pkmnToGoBefore = evaluated.FindIndex(t => BattleStatus.HasFlag(PBEBattleStatus.TrickRoom) ? t.Speed > speed : t.Speed < speed);
                        if (pkmnToGoBefore == -1)
                        {
                            evaluated.Add(tup);
                        }
                        else
                        {
                            evaluated.Insert(pkmnToGoBefore, tup);
                        }
                    }
                }
                Debug.WriteLine(evaluated.Select(t => $"{t.Pokemon.Team.Id} {t.Pokemon.Nickname} {t.Speed}").Print());
            }
            return(evaluated.Select(t => t.Pokemon));
        }