private void CheckForLearnMoves()
 {
     if (_learningMoves.Count != 0)
     {
         int index = _pkmn.Moveset.GetFirstEmptySlot();
         if (index == -1)
         {
             SetWantsToLearnMove();
         }
         else
         {
             Moveset.MovesetSlot slot = _pkmn.Moveset[index];
             PBEMove             move = _learningMoves.Dequeue(); // Remove from queue
             string moveStr           = PBELocalizedString.GetMoveName(move).English;
             slot.Move = move;
             PBEMoveData mData = PBEMoveData.Data[move];
             slot.PP    = PBEDataUtils.CalcMaxPP(mData.PPTier, 0, PkmnConstants.PBESettings);
             slot.PPUps = 0;
             CreateMessage(string.Format("{0} learned {1}!", _pkmn.Nickname, moveStr));
             _state = State.LearnMove_ForgotMsg;
         }
     }
     else
     {
         SetFadeOut();
     }
 }
Exemple #2
0
        private void DetermineTurnOrder()
        {
            _turnOrder.Clear();
            IEnumerable <PBEPokemon> pkmnSwitchingOut = ActiveBattlers.Where(p => p.TurnAction.Decision == PBETurnDecision.SwitchOut);
            IEnumerable <PBEPokemon> pkmnFighting     = ActiveBattlers.Where(p => p.TurnAction.Decision == PBETurnDecision.Fight);

            // Switching happens first:
            _turnOrder.AddRange(GetActingOrder(pkmnSwitchingOut, true));
            // Moves:
            sbyte GetPrio(PBEPokemon p)
            {
                PBEMoveData mData = PBEMoveData.Data[p.TurnAction.FightMove];

                return((sbyte)PBEUtils.Clamp(mData.Priority + (p.Ability == PBEAbility.Prankster && mData.Category == PBEMoveCategory.Status ? 1 : 0), sbyte.MinValue, sbyte.MaxValue));
            }

            foreach (sbyte priority in pkmnFighting.Select(p => GetPrio(p)).Distinct().OrderByDescending(p => p))
            {
                PBEPokemon[] pkmnWithThisPriority = pkmnFighting.Where(p => GetPrio(p) == priority).ToArray();
                if (pkmnWithThisPriority.Length > 0)
                {
                    Debug.WriteLine("Priority {0} bracket...", priority);
                    _turnOrder.AddRange(GetActingOrder(pkmnWithThisPriority, false));
                }
            }
        }
        private int CalculateDamage(PBEBattlePokemon user, PBEBattlePokemon target, PBEMoveData mData, PBEType moveType, double basePower, bool criticalHit)
        {
            PBEBattlePokemon aPkmn;
            PBEMoveCategory  aCat = mData.Category, dCat;

            switch (mData.Effect)
            {
            case PBEMoveEffect.FoulPlay:
            {
                aPkmn = target;
                dCat  = aCat;
                break;
            }

            case PBEMoveEffect.Psyshock:
            {
                aPkmn = user;
                dCat  = PBEMoveCategory.Physical;
                break;
            }

            default:
            {
                aPkmn = user;
                dCat  = aCat;
                break;
            }
            }

            bool   ignoreA = user != target && target.Ability == PBEAbility.Unaware && !user.HasCancellingAbility();
            bool   ignoreD = user != target && (mData.Effect == PBEMoveEffect.ChipAway || user.Ability == PBEAbility.Unaware);
            double a, d;

            if (aCat == PBEMoveCategory.Physical)
            {
                double m = ignoreA ? 1 : GetStatChangeModifier(criticalHit ? Math.Max((sbyte)0, aPkmn.AttackChange) : aPkmn.AttackChange, false);
                a = CalculateAttack(user, target, moveType, aPkmn.Attack * m);
            }
            else
            {
                double m = ignoreA ? 1 : GetStatChangeModifier(criticalHit ? Math.Max((sbyte)0, aPkmn.SpAttackChange) : aPkmn.SpAttackChange, false);
                a = CalculateSpAttack(user, target, moveType, aPkmn.SpAttack * m);
            }
            if (dCat == PBEMoveCategory.Physical)
            {
                double m = ignoreD ? 1 : GetStatChangeModifier(criticalHit ? Math.Min((sbyte)0, target.DefenseChange) : target.DefenseChange, false);
                d = CalculateDefense(user, target, target.Defense * m);
            }
            else
            {
                double m = ignoreD ? 1 : GetStatChangeModifier(criticalHit ? Math.Min((sbyte)0, target.SpDefenseChange) : target.SpDefenseChange, false);
                d = CalculateSpDefense(user, target, target.SpDefense * m);
            }

            return(CalculateDamage(user, a, d, basePower));
        }
            public async Task Info([Remainder] string moveName)
            {
                PBEMove?nMove = PBELocalizedString.GetMoveByName(moveName);

                if (!nMove.HasValue || nMove.Value == PBEMove.None)
                {
                    await Context.Channel.SendMessageAsync($"{Context.User.Mention} ― Invalid move!");
                }
                else
                {
                    PBEMove move = nMove.Value;
                    moveName = PBELocalizedString.GetMoveName(move).English;
                    PBEMoveData  mData = PBEMoveData.Data[move];
                    EmbedBuilder embed = new EmbedBuilder()
                                         .WithAuthor(Context.User)
                                         .WithColor(Utils.TypeColors[mData.Type])
                                         .WithTitle(moveName)
                                         .WithUrl(Utils.URL)
                                         .WithDescription(PBELocalizedString.GetMoveDescription(move).English.Replace('\n', ' '))
                                         .AddField("Type", Utils.TypeEmotes[mData.Type], true)
                                         .AddField("Category", mData.Category, true)
                                         .AddField("Priority", mData.Priority, true)
                                         .AddField("PP", Math.Max(1, mData.PPTier * PBESettings.DefaultPPMultiplier), true)
                                         .AddField("Power", mData.Power == 0 ? "―" : mData.Power.ToString(), true)
                                         .AddField("Accuracy", mData.Accuracy == 0 ? "―" : mData.Accuracy.ToString(), true)
                                         .AddField("Targets", mData.Targets, true)
                                         .AddField("Flags", mData.Flags, true);
                    switch (mData.Effect)
                    {
                    case PBEMoveEffect.Recoil: embed.AddField("Recoil", $"1/{mData.EffectParam} damage dealt"); break;

                    case PBEMoveEffect.Recoil__10PercentBurn: embed.AddField("Recoil", $"1/{mData.EffectParam} damage dealt"); break;     // TODO: Burn chance

                    case PBEMoveEffect.Recoil__10PercentParalyze: embed.AddField("Recoil", $"1/{mData.EffectParam} damage dealt"); break; // TODO: Paralyze chance

                    case PBEMoveEffect.Struggle: embed.AddField("Recoil", "1/4 user's max HP"); break;

                    case PBEMoveEffect.TODOMOVE: embed.AddField("**ATTENTION**", $"{moveName} is not yet implemented in Pokémon Battle Engine"); break;
                    }
                    await Context.Channel.SendMessageAsync(string.Empty, embed : embed.Build());
                }
            }
Exemple #5
0
            public async Task Info([Remainder] string moveName)
            {
                PBEMove            move      = 0;
                PBELocalizedString localized = PBEMoveLocalization.Names.Values.FirstOrDefault(l => l.Contains(moveName));

                if (localized != null)
                {
                    move = PBEMoveLocalization.Names.First(p => p.Value == localized).Key;
                }
                else
                {
                    Enum.TryParse(moveName, true, out move);
                }
                if (move != 0)
                {
                    if (move == PBEMove.None)
                    {
                        goto invalid;
                    }
                    PBEMoveData mData = PBEMoveData.Data[move];
                    var         embed = new EmbedBuilder()
                                        .WithColor(Utils.TypeToColor[mData.Type])
                                        .WithUrl("https://github.com/Kermalis/PokemonBattleEngine")
                                        .WithTitle(PBEMoveLocalization.Names[move].English)
                                        .WithAuthor(Context.User)
                                        .AddField("Type", mData.Type, true)
                                        .AddField("Category", mData.Category, true)
                                        .AddField("Priority", mData.Priority, true)
                                        .AddField("PP", mData.PPTier * PBESettings.DefaultSettings.PPMultiplier, true)
                                        .AddField("Power", mData.Power == 0 ? "--" : mData.Power.ToString(), true)
                                        .AddField("Accuracy", mData.Accuracy == 0 ? "--" : mData.Accuracy.ToString(), true)
                                        .AddField("Effect", mData.Effect, true)
                                        .AddField("Effect Param", mData.EffectParam, true)
                                        .AddField("Targets", mData.Targets, true)
                                        .AddField("Flags", mData.Flags, true);
                    await Context.Channel.SendMessageAsync(string.Empty, embed : embed.Build());

                    return;
                }
invalid:
                await Context.Channel.SendMessageAsync($"{Context.User.Mention} Invalid move!");
            }
            public async Task Info([Remainder] string moveName)
            {
                PBEMove?nMove = PBELocalizedString.GetMoveByName(moveName);

                if (!nMove.HasValue || nMove.Value == PBEMove.None)
                {
                    await Context.Channel.SendMessageAsync($"{Context.User.Mention} Invalid move!");
                }
                else
                {
                    PBEMove      move  = nMove.Value;
                    PBEMoveData  mData = PBEMoveData.Data[move];
                    EmbedBuilder embed = new EmbedBuilder()
                                         .WithAuthor(Context.User)
                                         .WithColor(Utils.TypeToColor[mData.Type])
                                         .WithTitle(PBELocalizedString.GetMoveName(move).English)
                                         .WithUrl(Utils.URL)
                                         .WithDescription(PBELocalizedString.GetMoveDescription(move).English.Replace('\n', ' '))
                                         .AddField("Type", mData.Type, true)
                                         .AddField("Category", mData.Category, true)
                                         .AddField("Priority", mData.Priority, true)
                                         .AddField("PP", Math.Max(1, mData.PPTier * PBESettings.DefaultSettings.PPMultiplier), true)
                                         .AddField("Power", mData.Power == 0 ? "--" : mData.Power.ToString(), true)
                                         .AddField("Accuracy", mData.Accuracy == 0 ? "--" : mData.Accuracy.ToString(), true);
                    switch (mData.Effect)
                    {
                    case PBEMoveEffect.FlareBlitz: embed.AddField("Recoil", "1/3 damage dealt", true); break;     // TODO: Burn chance

                    case PBEMoveEffect.Recoil: embed.AddField("Recoil", $"1/{mData.EffectParam} damage dealt", true); break;

                    case PBEMoveEffect.Struggle: embed.AddField("Recoil", "1/4 user's max HP", true); break;

                    case PBEMoveEffect.VoltTackle: embed.AddField("Recoil", "1/3 damage dealt", true); break;     // TODO: Paralyze chance
                    }
                    embed.AddField("Targets", mData.Targets, true)
                    .AddField("Flags", mData.Flags, true);
                    await Context.Channel.SendMessageAsync(string.Empty, embed : embed.Build());
                }
            }
Exemple #7
0
        public static IReadOnlyCollection <PBEMove> GetLegalMoves(PBESpecies species, PBEForm form, byte level, PBESettings settings)
        {
            if (settings == null)
            {
                throw new ArgumentNullException(nameof(settings));
            }
            if (!settings.IsReadOnly)
            {
                throw new ArgumentException("Settings must be read-only.", nameof(settings));
            }
            ValidateSpecies(species, form, true);
            ValidateLevel(level, settings);
            List <(PBESpecies, PBEForm)> speciesToStealFrom = GetSpecies(species, form);

            var moves = new List <PBEMove>();

            foreach ((PBESpecies spe, PBEForm fo) in speciesToStealFrom)
            {
                var pData = PBEPokemonData.GetData(spe, fo);
                // Disallow moves learned after the current level
                moves.AddRange(pData.LevelUpMoves.Where(t => t.Level <= level).Select(t => t.Move));
                // Disallow form-specific moves from other forms (Rotom)
                moves.AddRange(pData.OtherMoves.Where(t => (spe == species && fo == form) || t.ObtainMethod != PBEMoveObtainMethod.Form).Select(t => t.Move));
                // Event Pokémon checking is extremely basic and wrong, but the goal is not to be super restricting or accurate
                if (PBEEventPokemon.Events.TryGetValue(spe, out ReadOnlyCollection <PBEEventPokemon> events))
                {
                    // Disallow moves learned after the current level
                    moves.AddRange(events.Where(e => e.Level <= level).SelectMany(e => e.Moves).Where(m => m != PBEMove.None));
                }
                if (moves.Any(m => PBEMoveData.Data[m].Effect == PBEMoveEffect.Sketch))
                {
                    return(PBEDataUtils.SketchLegalMoves);
                }
            }
            return(moves.Distinct().Where(m => PBEMoveData.IsMoveUsable(m)).ToArray());
        }
Exemple #8
0
        private unsafe void DrawMovesPage(uint *bmpAddress, int bmpWidth, int bmpHeight)
        {
            const float winX         = 0.08f;
            const float winY         = 0.15f;
            const float winW         = 0.75f - winX;
            const float winH         = 0.9f - winY;
            const float moveColX     = winX + 0.03f;
            const float moveTextX    = moveColX + 0.02f;
            const float moveColW     = 0.69f - winX;
            const float itemSpacingY = winH / (PkmnConstants.NumMoves + 0.75f);
            const float moveX        = 0.21f;
            const float moveY        = 0.03f;
            const float ppX          = 0.12f;
            const float ppNumX       = 0.35f;
            const float ppY          = itemSpacingY / 2;
            const float cancelY      = winY + moveY + (PkmnConstants.NumMoves * itemSpacingY);

            RenderUtils.FillRoundedRectangle(bmpAddress, bmpWidth, bmpHeight, winX, winY, winX + winW, winY + winH, 15, RenderUtils.Color(250, 128, 120, 255));

            Font moveFont = Font.Default;

            uint[] moveColors = Font.DefaultWhite_DarkerOutline_I;
            uint[] ppColors   = Font.DefaultBlack_I;

            void Place(int i, PBEMove move, int pp, int maxPP)
            {
                PBEMoveData mData = PBEMoveData.Data[move];
                float       x     = moveTextX;
                float       y     = winY + moveY + (i * itemSpacingY);
                string      str   = PBELocalizedString.GetTypeName(mData.Type).English;

                moveFont.DrawString(bmpAddress, bmpWidth, bmpHeight, x, y, str, moveColors);
                x  += moveX;
                str = PBELocalizedString.GetMoveName(move).English;
                moveFont.DrawString(bmpAddress, bmpWidth, bmpHeight, x, y, str, moveColors);
                x   = moveTextX + ppX;
                y  += ppY;
                str = "PP";
                moveFont.DrawString(bmpAddress, bmpWidth, bmpHeight, x, y, str, ppColors);
                x   = moveTextX + ppNumX;
                str = string.Format("{0}/{1}", pp, maxPP);
                moveFont.MeasureString(str, out int strW, out _);
                moveFont.DrawString(bmpAddress, bmpWidth, bmpHeight, RenderUtils.GetCoordinatesForCentering(bmpWidth, strW, x), (int)(bmpHeight * y), str, ppColors);

                DrawSelection(i);
            }

            void DrawSelection(int i)
            {
                if (_selectingMove != i)
                {
                    return;
                }
                float x = moveColX;
                float y = winY + moveY + (i * itemSpacingY);
                float w = moveColW;
                float h = i == PkmnConstants.NumMoves ? itemSpacingY / 2 : itemSpacingY;

                RenderUtils.DrawRoundedRectangle(bmpAddress, bmpWidth, bmpHeight, x, y, x + w, y + h, 5, RenderUtils.Color(48, 180, 255, 200));
            }

            // Moves
            if (_pPkmn is not null)
            {
                Moveset moves = _pPkmn.Moveset;
                for (int m = 0; m < PkmnConstants.NumMoves; m++)
                {
                    Moveset.MovesetSlot slot = moves[m];
                    PBEMove             move = slot.Move;
                    if (move == PBEMove.None)
                    {
                        continue;
                    }
                    int pp    = slot.PP;
                    int maxPP = PBEDataUtils.CalcMaxPP(move, slot.PPUps, PkmnConstants.PBESettings);
                    Place(m, move, pp, maxPP);
                }
            }
            else if (_pcPkmn is not null)
            {
                BoxMoveset moves = _pcPkmn.Moveset;
                for (int m = 0; m < PkmnConstants.NumMoves; m++)
                {
                    BoxMoveset.BoxMovesetSlot slot = moves[m];
                    PBEMove move = slot.Move;
                    if (move == PBEMove.None)
                    {
                        continue;
                    }
                    int maxPP = PBEDataUtils.CalcMaxPP(move, slot.PPUps, PkmnConstants.PBESettings);
                    Place(m, move, maxPP, maxPP);
                }
            }
            else
            {
                PBEBattlePokemon bPkmn = _bPkmn.Pkmn;
                PBEBattleMoveset moves = bPkmn.Status2.HasFlag(PBEStatus2.Transformed) ? bPkmn.TransformBackupMoves : bPkmn.Moves;
                for (int m = 0; m < PkmnConstants.NumMoves; m++)
                {
                    PBEBattleMoveset.PBEBattleMovesetSlot slot = moves[m];
                    PBEMove move = slot.Move;
                    if (move == PBEMove.None)
                    {
                        continue;
                    }
                    int pp    = slot.PP;
                    int maxPP = slot.MaxPP;
                    Place(m, move, pp, maxPP);
                }
            }

            // Cancel or new move
            if (_learningMove != PBEMove.None)
            {
                uint[]      learnColors = Font.DefaultBlue_I;
                PBEMoveData mData       = PBEMoveData.Data[_learningMove];
                float       x           = moveTextX;
                string      str         = PBELocalizedString.GetTypeName(mData.Type).English;
                moveFont.DrawString(bmpAddress, bmpWidth, bmpHeight, x, cancelY, str, learnColors);
                x  += moveX;
                str = PBELocalizedString.GetMoveName(_learningMove).English;
                moveFont.DrawString(bmpAddress, bmpWidth, bmpHeight, x, cancelY, str, learnColors);
                DrawSelection(PkmnConstants.NumMoves);
            }
            else
            {
                if (_selectingMove != -1)
                {
                    string str = "Cancel";
                    moveFont.DrawString(bmpAddress, bmpWidth, bmpHeight, moveTextX, cancelY, str, moveColors);
                    DrawSelection(PkmnConstants.NumMoves);
                }
            }
        }
        private double CalculateDamageMultiplier(PBEBattlePokemon user, PBEBattlePokemon target, PBEMoveData mData, PBEType moveType, PBEResult moveResult, bool criticalHit)
        {
            double damageMultiplier = 1;

            if (target.Status2.HasFlag(PBEStatus2.Airborne) && mData.Flags.HasFlag(PBEMoveFlag.DoubleDamageAirborne))
            {
                damageMultiplier *= 2.0;
            }
            if (target.Minimize_Used && mData.Flags.HasFlag(PBEMoveFlag.DoubleDamageMinimized))
            {
                damageMultiplier *= 2.0;
            }
            if (target.Status2.HasFlag(PBEStatus2.Underground) && mData.Flags.HasFlag(PBEMoveFlag.DoubleDamageUnderground))
            {
                damageMultiplier *= 2.0;
            }
            if (target.Status2.HasFlag(PBEStatus2.Underwater) && mData.Flags.HasFlag(PBEMoveFlag.DoubleDamageUnderwater))
            {
                damageMultiplier *= 2.0;
            }

            if (criticalHit)
            {
                damageMultiplier *= Settings.CritMultiplier;
                if (user.Ability == PBEAbility.Sniper)
                {
                    damageMultiplier *= 1.5;
                }
            }
            else if (user.Ability != PBEAbility.Infiltrator)
            {
                if ((target.Team.TeamStatus.HasFlag(PBETeamStatus.Reflect) && mData.Category == PBEMoveCategory.Physical) ||
                    (target.Team.TeamStatus.HasFlag(PBETeamStatus.LightScreen) && mData.Category == PBEMoveCategory.Special))
                {
                    if (target.Team.NumPkmnOnField == 1)
                    {
                        damageMultiplier *= 0.5;
                    }
                    else
                    {
                        damageMultiplier *= 0.66;
                    }
                }
            }

            switch (moveResult)
            {
            case PBEResult.NotVeryEffective_Type:
            {
                if (user.Ability == PBEAbility.TintedLens)
                {
                    damageMultiplier *= 2.0;
                }
                break;
            }

            case PBEResult.SuperEffective_Type:
            {
                if ((target.Ability == PBEAbility.Filter || target.Ability == PBEAbility.SolidRock) && !user.HasCancellingAbility())
                {
                    damageMultiplier *= 0.75;
                }
                if (user.Item == PBEItem.ExpertBelt)
                {
                    damageMultiplier *= 1.2;
                }
                break;
            }
            }
            if (user.ReceivesSTAB(moveType))
            {
                if (user.Ability == PBEAbility.Adaptability)
                {
                    damageMultiplier *= 2.0;
                }
                else
                {
                    damageMultiplier *= 1.5;
                }
            }
            if (mData.Category == PBEMoveCategory.Physical && user.Status1 == PBEStatus1.Burned && user.Ability != PBEAbility.Guts)
            {
                damageMultiplier *= 0.5;
            }
            if (moveType == PBEType.Fire && target.Ability == PBEAbility.Heatproof && !user.HasCancellingAbility())
            {
                damageMultiplier *= 0.5;
            }

            return(damageMultiplier);
        }
Exemple #10
0
        // None of these moves are multi-target
        private void FixedDamageHit(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMoveData mData, Func <PBEBattlePokemon, int> damageFunc,
                                    Func <PBEBattlePokemon, PBEResult> failFunc = null,
                                    Action beforePostHit = null)
        {
            PBEType moveType = user.GetMoveType(mData);

            // Endeavor fails if the target's HP is <= the user's HP
            // One hit knockout moves fail if the target's level is > the user's level
            Hit_GetVictims(user, targets, mData, moveType, out List <PBEAttackVictim> victims, failFunc: failFunc);
            if (victims.Count == 0)
            {
                return;
            }
            // BUG: Gems activate for these moves despite base power not being involved
            if (!Settings.BugFix)
            {
                double unused = CalculateBasePower(user, targets, mData, moveType);
            }
            void CalcDamage(PBEAttackVictim victim)
            {
                PBEBattlePokemon target = victim.Pkmn;

                // FinalGambit user faints here
                victim.Damage = DealDamage(user, target, damageFunc.Invoke(target));
            }

            void DoSub(IEnumerable <PBEAttackVictim> subs)
            {
                foreach (PBEAttackVictim victim in subs)
                {
                    CalcDamage(victim);
                    PBEBattlePokemon target = victim.Pkmn;
                    if (target.SubstituteHP == 0)
                    {
                        BroadcastStatus2(target, user, PBEStatus2.Substitute, PBEStatusAction.Ended);
                    }
                }
            }

            void DoNormal(IEnumerable <PBEAttackVictim> normals)
            {
                foreach (PBEAttackVictim victim in normals)
                {
                    CalcDamage(victim);
                }
                foreach (PBEAttackVictim victim in normals)
                {
                    PBEBattlePokemon target = victim.Pkmn;
                    // "It's a one-hit KO!"
                    beforePostHit?.Invoke();
                    DoPostHitEffects(user, target, mData, moveType);
                }
                Hit_FaintCheck(normals);
            }

            Hit_HitTargets(user.Team, DoSub, DoNormal, victims, out IEnumerable <PBEAttackVictim> allies, out IEnumerable <PBEAttackVictim> foes);
            DoPostAttackedEffects(user, allies, foes, false, colorChangeType: moveType);
        }
        private double CalculateBasePower(PBEPokemon user, PBEPokemon[] targets, PBEMove move, PBEType moveType)
        {
            PBEMoveData mData = PBEMoveData.Data[move];
            // Get move's base power
            double basePower;

            switch (move)
            {
            case PBEMove.CrushGrip:
            case PBEMove.WringOut:
            {
                PBEPokemon target = targets[0];
                basePower = Math.Max(1, 120 * target.HP / target.MaxHP);
                break;
            }

            case PBEMove.Eruption:
            case PBEMove.WaterSpout:
            {
                basePower = Math.Max(1, mData.Power * user.HP / user.MaxHP);
                break;
            }

            case PBEMove.Flail:
            case PBEMove.Reversal:
            {
                int val = 48 * user.HP / user.MaxHP;
                if (val < 2)
                {
                    basePower = 200;
                }
                else if (val < 4)
                {
                    basePower = 150;
                }
                else if (val < 8)
                {
                    basePower = 100;
                }
                else if (val < 16)
                {
                    basePower = 80;
                }
                else if (val < 32)
                {
                    basePower = 40;
                }
                else
                {
                    basePower = 20;
                }
                break;
            }

            case PBEMove.Frustration:
            {
                basePower = Math.Max(1, (byte.MaxValue - user.Friendship) / 2.5);
                break;
            }

            case PBEMove.GrassKnot:
            case PBEMove.LowKick:
            {
                PBEPokemon target = targets[0];
                if (target.Weight >= 200.0)
                {
                    basePower = 120;
                }
                else if (target.Weight >= 100.0)
                {
                    basePower = 100;
                }
                else if (target.Weight >= 50.0)
                {
                    basePower = 80;
                }
                else if (target.Weight >= 25.0)
                {
                    basePower = 60;
                }
                else if (target.Weight >= 10.0)
                {
                    basePower = 40;
                }
                else
                {
                    basePower = 20;
                }
                break;
            }

            case PBEMove.HeatCrash:
            case PBEMove.HeavySlam:
            {
                PBEPokemon target   = targets[0];
                double     relative = user.Weight / target.Weight;
                if (relative < 2)
                {
                    basePower = 40;
                }
                else if (relative < 3)
                {
                    basePower = 60;
                }
                else if (relative < 4)
                {
                    basePower = 80;
                }
                else if (relative < 5)
                {
                    basePower = 100;
                }
                else
                {
                    basePower = 120;
                }
                break;
            }

            case PBEMove.HiddenPower:
            {
                basePower = user.IndividualValues.HiddenPowerBasePower;
                break;
            }

            case PBEMove.Magnitude:
            {
                int  val = PBEUtils.RandomInt(0, 99);
                byte magnitude;
                if (val < 5)     // Magnitude 4 - 5%
                {
                    magnitude = 4;
                    basePower = 10;
                }
                else if (val < 15)     // Magnitude 5 - 10%
                {
                    magnitude = 5;
                    basePower = 30;
                }
                else if (val < 35)     // Magnitude 6 - 20%
                {
                    magnitude = 6;
                    basePower = 50;
                }
                else if (val < 65)     // Magnitude 7 - 30%
                {
                    magnitude = 7;
                    basePower = 70;
                }
                else if (val < 85)     // Magnitude 8 - 20%
                {
                    magnitude = 8;
                    basePower = 90;
                }
                else if (val < 95)     // Magnitude 9 - 10%
                {
                    magnitude = 9;
                    basePower = 110;
                }
                else     // Magnitude 10 - 5%
                {
                    magnitude = 10;
                    basePower = 150;
                }
                BroadcastMagnitude(magnitude);
                break;
            }

            case PBEMove.Punishment:
            {
                PBEPokemon target = targets[0];
                basePower = Math.Max(1, Math.Min(200, 60 + (20 * target.GetPositiveStatTotal())));
                break;
            }

            case PBEMove.Return:
            {
                basePower = Math.Max(1, user.Friendship / 2.5);
                break;
            }

            case PBEMove.StoredPower:
            {
                basePower = mData.Power + (20 * user.GetPositiveStatTotal());
                break;
            }

            default:
            {
                basePower = mData.Power;
                break;
            }
            }

            // Ability/Item-specific power boosts
            bool canUseGems = !mData.Flags.HasFlag(PBEMoveFlag.UnaffectedByGems);

            switch (moveType)
            {
            case PBEType.Bug:
            {
                if (user.Ability == PBEAbility.Swarm && user.HP <= user.MaxHP / 3)
                {
                    basePower *= 1.5;
                }
                switch (user.Item)
                {
                case PBEItem.InsectPlate:
                case PBEItem.SilverPowder:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.BugGem:
                {
                    if (canUseGems)
                    {
                        BroadcastItem(user, user, PBEItem.BugGem, PBEItemAction.Consumed);
                        basePower *= 1.5;
                    }
                    break;
                }
                }
                break;
            }

            case PBEType.Dark:
            {
                switch (user.Item)
                {
                case PBEItem.BlackGlasses:
                case PBEItem.DreadPlate:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.DarkGem:
                {
                    if (canUseGems)
                    {
                        BroadcastItem(user, user, PBEItem.DarkGem, PBEItemAction.Consumed);
                        basePower *= 1.5;
                    }
                    break;
                }
                }
                break;
            }

            case PBEType.Dragon:
            {
                switch (user.Item)
                {
                case PBEItem.AdamantOrb:
                {
                    if (user.OriginalSpecies == PBESpecies.Dialga)
                    {
                        basePower *= 1.2;
                    }
                    break;
                }

                case PBEItem.DracoPlate:
                case PBEItem.DragonFang:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.GriseousOrb:
                {
                    if (user.OriginalSpecies == PBESpecies.Giratina_Origin)
                    {
                        basePower *= 1.2;
                    }
                    break;
                }

                case PBEItem.LustrousOrb:
                {
                    if (user.OriginalSpecies == PBESpecies.Palkia)
                    {
                        basePower *= 1.2;
                    }
                    break;
                }

                case PBEItem.DragonGem:
                {
                    if (canUseGems)
                    {
                        BroadcastItem(user, user, PBEItem.DragonGem, PBEItemAction.Consumed);
                        basePower *= 1.5;
                    }
                    break;
                }
                }
                break;
            }

            case PBEType.Electric:
            {
                switch (user.Item)
                {
                case PBEItem.Magnet:
                case PBEItem.ZapPlate:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.ElectricGem:
                {
                    if (canUseGems)
                    {
                        BroadcastItem(user, user, PBEItem.ElectricGem, PBEItemAction.Consumed);
                        basePower *= 1.5;
                    }
                    break;
                }
                }
                break;
            }

            case PBEType.Fighting:
            {
                switch (user.Item)
                {
                case PBEItem.BlackBelt:
                case PBEItem.FistPlate:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.FightingGem:
                {
                    if (canUseGems)
                    {
                        BroadcastItem(user, user, PBEItem.FightingGem, PBEItemAction.Consumed);
                        basePower *= 1.5;
                    }
                    break;
                }
                }
                break;
            }

            case PBEType.Fire:
            {
                switch (user.Item)
                {
                case PBEItem.Charcoal:
                case PBEItem.FlamePlate:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.FireGem:
                {
                    if (canUseGems)
                    {
                        BroadcastItem(user, user, PBEItem.FireGem, PBEItemAction.Consumed);
                        basePower *= 1.5;
                    }
                    break;
                }
                }
                break;
            }

            case PBEType.Flying:
            {
                switch (user.Item)
                {
                case PBEItem.SharpBeak:
                case PBEItem.SkyPlate:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.FlyingGem:
                {
                    if (canUseGems)
                    {
                        BroadcastItem(user, user, PBEItem.FlyingGem, PBEItemAction.Consumed);
                        basePower *= 1.5;
                    }
                    break;
                }
                }
                break;
            }

            case PBEType.Ghost:
            {
                switch (user.Item)
                {
                case PBEItem.GriseousOrb:
                {
                    if (user.OriginalSpecies == PBESpecies.Giratina_Origin)
                    {
                        basePower *= 1.2;
                    }
                    break;
                }

                case PBEItem.SpellTag:
                case PBEItem.SpookyPlate:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.GhostGem:
                {
                    if (canUseGems)
                    {
                        BroadcastItem(user, user, PBEItem.GhostGem, PBEItemAction.Consumed);
                        basePower *= 1.5;
                    }
                    break;
                }
                }
                break;
            }

            case PBEType.Grass:
            {
                switch (user.Item)
                {
                case PBEItem.MeadowPlate:
                case PBEItem.MiracleSeed:
                case PBEItem.RoseIncense:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.GrassGem:
                {
                    if (canUseGems)
                    {
                        BroadcastItem(user, user, PBEItem.GrassGem, PBEItemAction.Consumed);
                        basePower *= 1.5;
                    }
                    break;
                }
                }
                break;
            }

            case PBEType.Ground:
            {
                switch (user.Item)
                {
                case PBEItem.EarthPlate:
                case PBEItem.SoftSand:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.GroundGem:
                {
                    if (canUseGems)
                    {
                        BroadcastItem(user, user, PBEItem.GroundGem, PBEItemAction.Consumed);
                        basePower *= 1.5;
                    }
                    break;
                }
                }
                break;
            }

            case PBEType.Ice:
            {
                switch (user.Item)
                {
                case PBEItem.IciclePlate:
                case PBEItem.NeverMeltIce:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.IceGem:
                {
                    if (canUseGems)
                    {
                        BroadcastItem(user, user, PBEItem.IceGem, PBEItemAction.Consumed);
                        basePower *= 1.5;
                    }
                    break;
                }
                }
                break;
            }

            case PBEType.None:
            {
                break;
            }

            case PBEType.Normal:
            {
                switch (user.Item)
                {
                case PBEItem.SilkScarf:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.NormalGem:
                {
                    if (canUseGems)
                    {
                        BroadcastItem(user, user, PBEItem.NormalGem, PBEItemAction.Consumed);
                        basePower *= 1.5;
                    }
                    break;
                }
                }
                break;
            }

            case PBEType.Poison:
            {
                switch (user.Item)
                {
                case PBEItem.PoisonBarb:
                case PBEItem.ToxicPlate:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.PoisonGem:
                {
                    if (canUseGems)
                    {
                        BroadcastItem(user, user, PBEItem.PoisonGem, PBEItemAction.Consumed);
                        basePower *= 1.5;
                    }
                    break;
                }
                }
                break;
            }

            case PBEType.Psychic:
            {
                switch (user.Item)
                {
                case PBEItem.MindPlate:
                case PBEItem.OddIncense:
                case PBEItem.TwistedSpoon:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.PsychicGem:
                {
                    if (canUseGems)
                    {
                        BroadcastItem(user, user, PBEItem.PsychicGem, PBEItemAction.Consumed);
                        basePower *= 1.5;
                    }
                    break;
                }
                }
                break;
            }

            case PBEType.Rock:
            {
                switch (user.Item)
                {
                case PBEItem.HardStone:
                case PBEItem.RockIncense:
                case PBEItem.StonePlate:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.RockGem:
                {
                    if (canUseGems)
                    {
                        BroadcastItem(user, user, PBEItem.RockGem, PBEItemAction.Consumed);
                        basePower *= 1.5;
                    }
                    break;
                }
                }
                break;
            }

            case PBEType.Steel:
            {
                switch (user.Item)
                {
                case PBEItem.AdamantOrb:
                {
                    if (user.OriginalSpecies == PBESpecies.Dialga)
                    {
                        basePower *= 1.2;
                    }
                    break;
                }

                case PBEItem.IronPlate:
                case PBEItem.MetalCoat:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.SteelGem:
                {
                    if (canUseGems)
                    {
                        BroadcastItem(user, user, PBEItem.SteelGem, PBEItemAction.Consumed);
                        basePower *= 1.5;
                    }
                    break;
                }
                }
                break;
            }

            case PBEType.Water:
            {
                switch (user.Item)
                {
                case PBEItem.LustrousOrb:
                {
                    if (user.OriginalSpecies == PBESpecies.Palkia)
                    {
                        basePower *= 1.2;
                    }
                    break;
                }

                case PBEItem.MysticWater:
                case PBEItem.SeaIncense:
                case PBEItem.SplashPlate:
                case PBEItem.WaveIncense:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.WaterGem:
                {
                    if (canUseGems)
                    {
                        BroadcastItem(user, user, PBEItem.WaterGem, PBEItemAction.Consumed);
                        basePower *= 1.5;
                    }
                    break;
                }
                }
                break;
            }

            default: throw new ArgumentOutOfRangeException(nameof(moveType));
            }

            // Move-specific power boosts
            switch (move)
            {
            case PBEMove.Acrobatics:
            {
                if (user.Item == PBEItem.None)
                {
                    basePower *= 2.0;
                }
                break;
            }

            case PBEMove.Brine:
            {
                PBEPokemon target = targets[0];
                if (target.HP <= target.HP / 2)
                {
                    basePower *= 2.0;
                }
                break;
            }

            case PBEMove.Facade:
            {
                if (user.Status1 == PBEStatus1.Burned || user.Status1 == PBEStatus1.Paralyzed || user.Status1 == PBEStatus1.Poisoned || user.Status1 == PBEStatus1.BadlyPoisoned)
                {
                    basePower *= 2.0;
                }
                break;
            }

            case PBEMove.Hex:
            {
                PBEPokemon target = targets[0];
                if (target.Status1 != PBEStatus1.None)
                {
                    basePower *= 2.0;
                }
                break;
            }

            case PBEMove.Retaliate:
            {
                if (user.Team.MonFaintedLastTurn)
                {
                    basePower *= 2.0;
                }
                break;
            }

            case PBEMove.Venoshock:
            {
                PBEPokemon target = targets[0];
                if (target.Status1 == PBEStatus1.Poisoned || target.Status1 == PBEStatus1.BadlyPoisoned)
                {
                    basePower *= 2.0;
                }
                break;
            }

            case PBEMove.WeatherBall:
            {
                if (ShouldDoWeatherEffects() && Weather != PBEWeather.None)
                {
                    basePower *= 2.0;
                }
                break;
            }
            }

            // Weather-specific power boosts
            if (ShouldDoWeatherEffects())
            {
                switch (Weather)
                {
                case PBEWeather.HarshSunlight:
                {
                    if (moveType == PBEType.Fire)
                    {
                        basePower *= 1.5;
                    }
                    else if (moveType == PBEType.Water)
                    {
                        basePower *= 0.5;
                    }
                    break;
                }

                case PBEWeather.Rain:
                {
                    if (moveType == PBEType.Water)
                    {
                        basePower *= 1.5;
                    }
                    else if (moveType == PBEType.Fire)
                    {
                        basePower *= 0.5;
                    }
                    break;
                }

                case PBEWeather.Sandstorm:
                {
                    if (user.Ability == PBEAbility.SandForce && (user.HasType(PBEType.Rock) || user.HasType(PBEType.Ground) || user.HasType(PBEType.Steel)))
                    {
                        basePower *= 1.3;
                    }
                    break;
                }
                }
            }

            // Other power boosts
            if (user.Status2.HasFlag(PBEStatus2.HelpingHand))
            {
                basePower *= 1.5;
            }
            if (user.Ability == PBEAbility.ToxicBoost && mData.Category == PBEMoveCategory.Physical && (user.Status1 == PBEStatus1.Poisoned || user.Status1 == PBEStatus1.BadlyPoisoned))
            {
                basePower *= 1.5;
            }
            if (user.Item == PBEItem.LifeOrb)
            {
                basePower *= 1.3;
            }
            if (user.Ability == PBEAbility.IronFist && mData.Flags.HasFlag(PBEMoveFlag.AffectedByIronFist))
            {
                basePower *= 1.2;
            }
            if (user.Ability == PBEAbility.Reckless && mData.Flags.HasFlag(PBEMoveFlag.AffectedByReckless))
            {
                basePower *= 1.2;
            }
            if (mData.Category == PBEMoveCategory.Physical && user.Item == PBEItem.MuscleBand)
            {
                basePower *= 1.1;
            }
            if (mData.Category == PBEMoveCategory.Special && user.Item == PBEItem.WiseGlasses)
            {
                basePower *= 1.1;
            }

            return(basePower);
        }
Exemple #12
0
 // TODO: TripleKick miss logic
 private void Hit_GetVictims(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMoveData mData, PBEType moveType, out List <PBEAttackVictim> victims,
                             Func <PBEBattlePokemon, PBEResult> failFunc = null)
 {
     victims = new List <PBEAttackVictim>(targets.Length);
     foreach (PBEBattlePokemon target in targets)
     {
         if (!AttackTypeCheck(user, target, moveType, out PBEResult result, out double typeEffectiveness))
         {
             continue;
         }
         // Verified: These fails are after type effectiveness (So SuckerPunch will not affect Ghost types due to Normalize before it fails due to invalid conditions)
         if (failFunc != null && failFunc.Invoke(target) != PBEResult.Success)
         {
             continue;
         }
         victims.Add(new PBEAttackVictim(target, result, typeEffectiveness));
     }
     if (victims.Count == 0)
     {
         return;
     }
     victims.RemoveAll(t => MissCheck(user, t.Pkmn, mData));
     return;
 }
Exemple #13
0
        // None of these moves are multi-target
        private void MultiHit(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMoveData mData, byte numHits,
                              bool subsequentMissChecks = false,
                              Action <PBEBattlePokemon> beforePostHit = null)
        {
            PBEType moveType = user.GetMoveType(mData);

            Hit_GetVictims(user, targets, mData, moveType, out List <PBEAttackVictim> victims);
            if (victims.Count == 0)
            {
                return;
            }
            double basePower            = CalculateBasePower(user, targets, mData, moveType); // Verified: Gem boost applies to all hits
            double initDamageMultiplier = victims.Count > 1 ? 0.75 : 1d;

            void CalcDamage(PBEAttackVictim victim)
            {
                PBEBattlePokemon target           = victim.Pkmn;
                PBEResult        result           = victim.Result;
                double           damageMultiplier = initDamageMultiplier * victim.TypeEffectiveness;
                bool             crit             = CritCheck(user, target, mData);

                damageMultiplier *= CalculateDamageMultiplier(user, target, mData, moveType, result, crit);
                int damage = (int)(damageMultiplier * CalculateDamage(user, target, mData, moveType, basePower, crit));

                victim.Damage = DealDamage(user, target, damage, ignoreSubstitute: false, ignoreSturdy: false);
                victim.Crit   = crit;
            }

            void DoSub(IEnumerable <PBEAttackVictim> subs)
            {
                foreach (PBEAttackVictim victim in subs)
                {
                    CalcDamage(victim);
                    PBEBattlePokemon target = victim.Pkmn;
                    if (victim.Crit)
                    {
                        BroadcastMoveCrit(target);
                    }
                    if (target.SubstituteHP == 0)
                    {
                        BroadcastStatus2(target, user, PBEStatus2.Substitute, PBEStatusAction.Ended);
                    }
                }
            }

            void DoNormal(IEnumerable <PBEAttackVictim> normals)
            {
                normals = normals.Where(v => v.Pkmn.HP > 0); // Remove ones that fainted from previous hits
                foreach (PBEAttackVictim victim in normals)
                {
                    CalcDamage(victim);
                }
                Hit_DoCrit(normals);
                foreach (PBEAttackVictim victim in normals)
                {
                    PBEBattlePokemon target = victim.Pkmn;
                    // Twineedle has a chance to poison on each strike
                    beforePostHit?.Invoke(target);
                    DoPostHitEffects(user, target, mData, moveType);
                }
            }

            byte hit = 0;
            IEnumerable <PBEAttackVictim> allies, foes;

            do
            {
                Hit_HitTargets(user.Team, DoSub, DoNormal, victims, out allies, out foes);
                hit++;
            } while (hit < numHits && user.HP > 0 && user.Status1 != PBEStatus1.Asleep && victims.Any(v => v.Pkmn.HP > 0));
            Hit_DoMoveResult(user, allies);
            Hit_DoMoveResult(user, foes);
            BroadcastMultiHit(hit);
            Hit_FaintCheck(allies);
            Hit_FaintCheck(foes);
            DoPostAttackedEffects(user, allies, foes, true, colorChangeType: moveType);
        }
Exemple #14
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);
        }
        private void CB_Evolution()
        {
            switch (_state)
            {
            case State.FadeIn:
            {
                if (_fadeTransition.IsDone)
                {
                    _fadeTransition = null;
                    _stringWindow   = new Window(0, 0.79f, 1, 0.16f, RenderUtils.Color(255, 255, 255, 255));
                    CreateMessage(string.Format("{0} is evolving!", _oldNickname));
                    _state = State.IsEvolvingMsg;
                }
                return;
            }

            case State.IsEvolvingMsg:
            {
                if (ReadMessage())
                {
                    _stringPrinter.Close();
                    _stringPrinter  = null;
                    _fadeTransition = new FadeToColorTransition(1_000, RenderUtils.ColorNoA(200, 200, 200));
                    _state          = State.FadeToWhite;
                }
                return;
            }

            case State.FadeToWhite:
            {
                if (TryCancelEvolution())
                {
                    _fadeTransition = null;
                    CreateMessage(string.Format("{0} stopped evolving!", _oldNickname));
                    _state = State.CancelledMsg;
                    return;
                }
                if (_fadeTransition.IsDone)
                {
                    _fadeTransition = null;
                    if (_evo.Method == EvoMethod.Ninjask_LevelUp)
                    {
                        Evolution.TryCreateShedinja(_pkmn);
                    }
                    _pkmn.Evolve(_evo);
                    LoadPkmnImage();
                    _fadeTransition = new FadeFromColorTransition(1_000, RenderUtils.ColorNoA(200, 200, 200));
                    _state          = State.FadeToEvo;
                }
                return;
            }

            case State.FadeToEvo:
            {
                if (_fadeTransition.IsDone)
                {
                    _fadeTransition = null;
                    SoundControl.PlayCry(_pkmn.Species, _pkmn.Form);
                    CreateMessage(string.Format("{0} evolved into {1}!", _oldNickname, PBELocalizedString.GetSpeciesName(_pkmn.Species).English));
                    _state = State.EvolvedIntoMsg;
                }
                return;
            }

            case State.EvolvedIntoMsg:
            {
                if (ReadMessage())
                {
                    _stringPrinter.Close();
                    _stringPrinter = null;
                    // Check for moves to learn
                    _learningMoves = new Queue <PBEMove>(new LevelUpData(_pkmn.Species, _pkmn.Form).GetNewMoves(_pkmn.Level));
                    CheckForLearnMoves();
                }
                return;
            }

            case State.FadeOut:
            {
                if (_fadeTransition.IsDone)
                {
                    _fadeTransition = null;
                    OverworldGUI.Instance.ReturnToFieldWithFadeInAfterEvolutionCheck();
                }
                return;
            }

            case State.CancelledMsg:
            {
                if (ReadMessage())
                {
                    _stringPrinter.Close();
                    _stringPrinter = null;
                    SetFadeOut();
                }
                return;
            }

            // Learning moves
            case State.LearnMove_WantsToLearnMoveMsg:
            {
                if (ReadMessageEnded())
                {
                    TextGUIChoices.CreateStandardYesNoChoices(ShouldLearnMoveAction, out _textChoices, out _textChoicesWindow);
                    _state = State.LearnMove_WantsToLearnMoveChoice;
                }
                return;
            }

            case State.LearnMove_WantsToLearnMoveChoice:
            case State.LearnMove_GiveUpLearningChoice:
            {
                HandleMultichoice();
                return;
            }

            case State.LearnMove_FadeToSummary:
            {
                if (_fadeTransition.IsDone)
                {
                    _fadeTransition           = null;
                    _stringWindow.IsInvisible = true;
                    _textChoicesWindow.Close();
                    _textChoicesWindow = null;
                    _textChoices.Dispose();
                    _textChoices = null;
                    _stringPrinter.Close();
                    _stringPrinter = null;
                    _ = new SummaryGUI(_pkmn, SummaryGUI.Mode.LearnMove, OnSummaryClosed, learningMove: _learningMoves.Peek());
                }
                return;
            }

            case State.LearnMove_FadeFromSummary:
            {
                if (_fadeTransition.IsDone)
                {
                    // Give up on learning
                    if (_forgetMove == -1 || _forgetMove == PkmnConstants.NumMoves)
                    {
                        SetGiveUpLearningMove();
                    }
                    else
                    {
                        Moveset.MovesetSlot slot    = _pkmn.Moveset[_forgetMove];
                        PBEMove             oldMove = slot.Move;
                        string  oldMoveStr          = PBELocalizedString.GetMoveName(oldMove).English;
                        PBEMove move    = _learningMoves.Dequeue();  // Remove from queue
                        string  moveStr = PBELocalizedString.GetMoveName(move).English;
                        slot.Move = move;
                        PBEMoveData mData = PBEMoveData.Data[move];
                        slot.PP    = PBEDataUtils.CalcMaxPP(mData.PPTier, 0, PkmnConstants.PBESettings);
                        slot.PPUps = 0;
                        CreateMessage(string.Format("{0} forgot {1}\nand learned {2}!", _pkmn.Nickname, oldMoveStr, moveStr));
                        _state = State.LearnMove_ForgotMsg;
                    }
                }
                return;
            }

            case State.LearnMove_GiveUpLearningMsg:
            {
                if (ReadMessageEnded())
                {
                    TextGUIChoices.CreateStandardYesNoChoices(ShouldGiveUpMoveAction, out _textChoices, out _textChoicesWindow);
                    _state = State.LearnMove_GiveUpLearningChoice;
                }
                return;
            }

            case State.LearnMove_DidNotLearnMsg:
            case State.LearnMove_ForgotMsg:
            {
                if (ReadMessage())
                {
                    _stringPrinter.Close();
                    _stringPrinter = null;
                    CheckForLearnMoves();
                }
                return;
            }
            }
        }
        /// <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);
        }
        private double CalculateBasePower(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMoveData mData, PBEType moveType)
        {
            double basePower;

            #region Get move's base power
            switch (mData.Effect)
            {
            case PBEMoveEffect.CrushGrip:
            {
                basePower = Math.Max(1, targets.Select(t => mData.Power * t.HP / t.MaxHP).Average());
                break;
            }

            case PBEMoveEffect.Eruption:
            {
                basePower = Math.Max(1, mData.Power * user.HP / user.MaxHP);
                break;
            }

            case PBEMoveEffect.Flail:
            {
                int val = 48 * user.HP / user.MaxHP;
                if (val < 2)
                {
                    basePower = 200;
                }
                else if (val < 4)
                {
                    basePower = 150;
                }
                else if (val < 8)
                {
                    basePower = 100;
                }
                else if (val < 16)
                {
                    basePower = 80;
                }
                else if (val < 32)
                {
                    basePower = 40;
                }
                else
                {
                    basePower = 20;
                }
                break;
            }

            case PBEMoveEffect.Frustration:
            {
                basePower = Math.Max(1, (byte.MaxValue - user.Friendship) / 2.5);
                break;
            }

            case PBEMoveEffect.GrassKnot:
            {
                basePower = targets.Select(t =>
                    {
                        if (t.Weight >= 200.0)
                        {
                            return(120);
                        }
                        else if (t.Weight >= 100.0)
                        {
                            return(100);
                        }
                        else if (t.Weight >= 50.0)
                        {
                            return(80);
                        }
                        else if (t.Weight >= 25.0)
                        {
                            return(60);
                        }
                        else if (t.Weight >= 10.0)
                        {
                            return(40);
                        }
                        return(20);
                    }).Average();
                break;
            }

            case PBEMoveEffect.HeatCrash:
            {
                basePower = targets.Select(t =>
                    {
                        double relative = user.Weight / t.Weight;
                        if (relative < 2)
                        {
                            return(40);
                        }
                        else if (relative < 3)
                        {
                            return(60);
                        }
                        else if (relative < 4)
                        {
                            return(80);
                        }
                        else if (relative < 5)
                        {
                            return(100);
                        }
                        return(120);
                    }).Average();
                break;
            }

            case PBEMoveEffect.HiddenPower:
            {
                basePower = user.IndividualValues.GetHiddenPowerBasePower(Settings);
                break;
            }

            case PBEMoveEffect.Magnitude:
            {
                int  val = PBERandom.RandomInt(0, 99);
                byte magnitude;
                if (val < 5)     // Magnitude 4 - 5%
                {
                    magnitude = 4;
                    basePower = 10;
                }
                else if (val < 15)     // Magnitude 5 - 10%
                {
                    magnitude = 5;
                    basePower = 30;
                }
                else if (val < 35)     // Magnitude 6 - 20%
                {
                    magnitude = 6;
                    basePower = 50;
                }
                else if (val < 65)     // Magnitude 7 - 30%
                {
                    magnitude = 7;
                    basePower = 70;
                }
                else if (val < 85)     // Magnitude 8 - 20%
                {
                    magnitude = 8;
                    basePower = 90;
                }
                else if (val < 95)     // Magnitude 9 - 10%
                {
                    magnitude = 9;
                    basePower = 110;
                }
                else     // Magnitude 10 - 5%
                {
                    magnitude = 10;
                    basePower = 150;
                }
                BroadcastMagnitude(magnitude);
                break;
            }

            case PBEMoveEffect.Punishment:
            {
                basePower = Math.Max(1, Math.Min(200, targets.Select(t => mData.Power + (20 * t.GetPositiveStatTotal())).Average()));
                break;
            }

            case PBEMoveEffect.Return:
            {
                basePower = Math.Max(1, user.Friendship / 2.5);
                break;
            }

            case PBEMoveEffect.StoredPower:
            {
                basePower = mData.Power + (20 * user.GetPositiveStatTotal());
                break;
            }

            default:
            {
                basePower = Math.Max(1, (int)mData.Power);
                break;
            }
            }
            #endregion

            // Technician goes before any other power boosts
            if (user.Ability == PBEAbility.Technician && basePower <= 60)
            {
                basePower *= 1.5;
            }

            #region Item-specific power boosts
            switch (moveType)
            {
            case PBEType.Bug:
            {
                switch (user.Item)
                {
                case PBEItem.InsectPlate:
                case PBEItem.SilverPowder:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.BugGem:
                {
                    BroadcastItem(user, user, PBEItem.BugGem, PBEItemAction.Consumed);
                    basePower *= 1.5;
                    break;
                }
                }
                break;
            }

            case PBEType.Dark:
            {
                switch (user.Item)
                {
                case PBEItem.BlackGlasses:
                case PBEItem.DreadPlate:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.DarkGem:
                {
                    BroadcastItem(user, user, PBEItem.DarkGem, PBEItemAction.Consumed);
                    basePower *= 1.5;
                    break;
                }
                }
                break;
            }

            case PBEType.Dragon:
            {
                switch (user.Item)
                {
                case PBEItem.AdamantOrb:
                {
                    if (user.OriginalSpecies == PBESpecies.Dialga)
                    {
                        basePower *= 1.2;
                    }
                    break;
                }

                case PBEItem.DracoPlate:
                case PBEItem.DragonFang:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.GriseousOrb:
                {
                    if (user.OriginalSpecies == PBESpecies.Giratina && user.RevertForm == PBEForm.Giratina_Origin)
                    {
                        basePower *= 1.2;
                    }
                    break;
                }

                case PBEItem.LustrousOrb:
                {
                    if (user.OriginalSpecies == PBESpecies.Palkia)
                    {
                        basePower *= 1.2;
                    }
                    break;
                }

                case PBEItem.DragonGem:
                {
                    BroadcastItem(user, user, PBEItem.DragonGem, PBEItemAction.Consumed);
                    basePower *= 1.5;
                    break;
                }
                }
                break;
            }

            case PBEType.Electric:
            {
                switch (user.Item)
                {
                case PBEItem.Magnet:
                case PBEItem.ZapPlate:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.ElectricGem:
                {
                    BroadcastItem(user, user, PBEItem.ElectricGem, PBEItemAction.Consumed);
                    basePower *= 1.5;
                    break;
                }
                }
                break;
            }

            case PBEType.Fighting:
            {
                switch (user.Item)
                {
                case PBEItem.BlackBelt:
                case PBEItem.FistPlate:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.FightingGem:
                {
                    BroadcastItem(user, user, PBEItem.FightingGem, PBEItemAction.Consumed);
                    basePower *= 1.5;
                    break;
                }
                }
                break;
            }

            case PBEType.Fire:
            {
                switch (user.Item)
                {
                case PBEItem.Charcoal:
                case PBEItem.FlamePlate:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.FireGem:
                {
                    BroadcastItem(user, user, PBEItem.FireGem, PBEItemAction.Consumed);
                    basePower *= 1.5;
                    break;
                }
                }
                break;
            }

            case PBEType.Flying:
            {
                switch (user.Item)
                {
                case PBEItem.SharpBeak:
                case PBEItem.SkyPlate:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.FlyingGem:
                {
                    BroadcastItem(user, user, PBEItem.FlyingGem, PBEItemAction.Consumed);
                    basePower *= 1.5;
                    break;
                }
                }
                break;
            }

            case PBEType.Ghost:
            {
                switch (user.Item)
                {
                case PBEItem.GriseousOrb:
                {
                    if (user.OriginalSpecies == PBESpecies.Giratina && user.RevertForm == PBEForm.Giratina_Origin)
                    {
                        basePower *= 1.2;
                    }
                    break;
                }

                case PBEItem.SpellTag:
                case PBEItem.SpookyPlate:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.GhostGem:
                {
                    BroadcastItem(user, user, PBEItem.GhostGem, PBEItemAction.Consumed);
                    basePower *= 1.5;
                    break;
                }
                }
                break;
            }

            case PBEType.Grass:
            {
                switch (user.Item)
                {
                case PBEItem.MeadowPlate:
                case PBEItem.MiracleSeed:
                case PBEItem.RoseIncense:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.GrassGem:
                {
                    BroadcastItem(user, user, PBEItem.GrassGem, PBEItemAction.Consumed);
                    basePower *= 1.5;
                    break;
                }
                }
                break;
            }

            case PBEType.Ground:
            {
                switch (user.Item)
                {
                case PBEItem.EarthPlate:
                case PBEItem.SoftSand:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.GroundGem:
                {
                    BroadcastItem(user, user, PBEItem.GroundGem, PBEItemAction.Consumed);
                    basePower *= 1.5;
                    break;
                }
                }
                break;
            }

            case PBEType.Ice:
            {
                switch (user.Item)
                {
                case PBEItem.IciclePlate:
                case PBEItem.NeverMeltIce:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.IceGem:
                {
                    BroadcastItem(user, user, PBEItem.IceGem, PBEItemAction.Consumed);
                    basePower *= 1.5;
                    break;
                }
                }
                break;
            }

            case PBEType.None:
            {
                break;
            }

            case PBEType.Normal:
            {
                switch (user.Item)
                {
                case PBEItem.SilkScarf:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.NormalGem:
                {
                    BroadcastItem(user, user, PBEItem.NormalGem, PBEItemAction.Consumed);
                    basePower *= 1.5;
                    break;
                }
                }
                break;
            }

            case PBEType.Poison:
            {
                switch (user.Item)
                {
                case PBEItem.PoisonBarb:
                case PBEItem.ToxicPlate:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.PoisonGem:
                {
                    BroadcastItem(user, user, PBEItem.PoisonGem, PBEItemAction.Consumed);
                    basePower *= 1.5;
                    break;
                }
                }
                break;
            }

            case PBEType.Psychic:
            {
                switch (user.Item)
                {
                case PBEItem.MindPlate:
                case PBEItem.OddIncense:
                case PBEItem.TwistedSpoon:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.PsychicGem:
                {
                    BroadcastItem(user, user, PBEItem.PsychicGem, PBEItemAction.Consumed);
                    basePower *= 1.5;
                    break;
                }
                }
                break;
            }

            case PBEType.Rock:
            {
                switch (user.Item)
                {
                case PBEItem.HardStone:
                case PBEItem.RockIncense:
                case PBEItem.StonePlate:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.RockGem:
                {
                    BroadcastItem(user, user, PBEItem.RockGem, PBEItemAction.Consumed);
                    basePower *= 1.5;
                    break;
                }
                }
                break;
            }

            case PBEType.Steel:
            {
                switch (user.Item)
                {
                case PBEItem.AdamantOrb:
                {
                    if (user.OriginalSpecies == PBESpecies.Dialga)
                    {
                        basePower *= 1.2;
                    }
                    break;
                }

                case PBEItem.IronPlate:
                case PBEItem.MetalCoat:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.SteelGem:
                {
                    BroadcastItem(user, user, PBEItem.SteelGem, PBEItemAction.Consumed);
                    basePower *= 1.5;
                    break;
                }
                }
                break;
            }

            case PBEType.Water:
            {
                switch (user.Item)
                {
                case PBEItem.LustrousOrb:
                {
                    if (user.OriginalSpecies == PBESpecies.Palkia)
                    {
                        basePower *= 1.2;
                    }
                    break;
                }

                case PBEItem.MysticWater:
                case PBEItem.SeaIncense:
                case PBEItem.SplashPlate:
                case PBEItem.WaveIncense:
                {
                    basePower *= 1.2;
                    break;
                }

                case PBEItem.WaterGem:
                {
                    BroadcastItem(user, user, PBEItem.WaterGem, PBEItemAction.Consumed);
                    basePower *= 1.5;
                    break;
                }
                }
                break;
            }

            default: throw new ArgumentOutOfRangeException(nameof(moveType));
            }
            #endregion

            #region Move-specific power boosts
            switch (mData.Effect)
            {
            case PBEMoveEffect.Acrobatics:
            {
                if (user.Item == PBEItem.None)
                {
                    basePower *= 2.0;
                }
                break;
            }

            case PBEMoveEffect.Brine:
            {
                if (targets.Any(t => t.HP <= t.HP / 2))
                {
                    basePower *= 2.0;
                }
                break;
            }

            case PBEMoveEffect.Facade:
            {
                if (user.Status1 == PBEStatus1.Burned || user.Status1 == PBEStatus1.Paralyzed || user.Status1 == PBEStatus1.Poisoned || user.Status1 == PBEStatus1.BadlyPoisoned)
                {
                    basePower *= 2.0;
                }
                break;
            }

            case PBEMoveEffect.Hex:
            {
                if (targets.Any(t => t.Status1 != PBEStatus1.None))
                {
                    basePower *= 2.0;
                }
                break;
            }

            case PBEMoveEffect.Payback:
            {
                if (targets.Any(t => t.HasUsedMoveThisTurn))
                {
                    basePower *= 2.0;
                }
                break;
            }

            case PBEMoveEffect.Retaliate:
            {
                if (user.Team.MonFaintedLastTurn)
                {
                    basePower *= 2.0;
                }
                break;
            }

            case PBEMoveEffect.SmellingSalt:
            {
                if (targets.Any(t => t.Status1 == PBEStatus1.Paralyzed))
                {
                    basePower *= 2.0;
                }
                break;
            }

            case PBEMoveEffect.Venoshock:
            {
                if (targets.Any(t => t.Status1 == PBEStatus1.Poisoned || t.Status1 == PBEStatus1.BadlyPoisoned))
                {
                    basePower *= 2.0;
                }
                break;
            }

            case PBEMoveEffect.WakeUpSlap:
            {
                if (targets.Any(t => t.Status1 == PBEStatus1.Asleep))
                {
                    basePower *= 2.0;
                }
                break;
            }

            case PBEMoveEffect.WeatherBall:
            {
                if (ShouldDoWeatherEffects() && Weather != PBEWeather.None)
                {
                    basePower *= 2.0;
                }
                break;
            }
            }
            #endregion

            #region Weather-specific power boosts
            if (ShouldDoWeatherEffects())
            {
                switch (Weather)
                {
                case PBEWeather.HarshSunlight:
                {
                    if (moveType == PBEType.Fire)
                    {
                        basePower *= 1.5;
                    }
                    else if (moveType == PBEType.Water)
                    {
                        basePower *= 0.5;
                    }
                    break;
                }

                case PBEWeather.Rain:
                {
                    if (moveType == PBEType.Water)
                    {
                        basePower *= 1.5;
                    }
                    else if (moveType == PBEType.Fire)
                    {
                        basePower *= 0.5;
                    }
                    break;
                }

                case PBEWeather.Sandstorm:
                {
                    if (user.Ability == PBEAbility.SandForce && (moveType == PBEType.Rock || moveType == PBEType.Ground || moveType == PBEType.Steel))
                    {
                        basePower *= 1.3;
                    }
                    break;
                }
                }
            }
            #endregion

            #region Other power boosts
            if (user.Status2.HasFlag(PBEStatus2.HelpingHand))
            {
                basePower *= 1.5;
            }
            if (user.Ability == PBEAbility.FlareBoost && mData.Category == PBEMoveCategory.Special && user.Status1 == PBEStatus1.Burned)
            {
                basePower *= 1.5;
            }
            if (user.Ability == PBEAbility.ToxicBoost && mData.Category == PBEMoveCategory.Physical && (user.Status1 == PBEStatus1.Poisoned || user.Status1 == PBEStatus1.BadlyPoisoned))
            {
                basePower *= 1.5;
            }
            if (user.Item == PBEItem.LifeOrb)
            {
                basePower *= 1.3;
            }
            if (user.Ability == PBEAbility.IronFist && mData.Flags.HasFlag(PBEMoveFlag.AffectedByIronFist))
            {
                basePower *= 1.2;
            }
            if (user.Ability == PBEAbility.Reckless && mData.Flags.HasFlag(PBEMoveFlag.AffectedByReckless))
            {
                basePower *= 1.2;
            }
            if (user.Item == PBEItem.MuscleBand && mData.Category == PBEMoveCategory.Physical)
            {
                basePower *= 1.1;
            }
            if (user.Item == PBEItem.WiseGlasses && mData.Category == PBEMoveCategory.Special)
            {
                basePower *= 1.1;
            }
            #endregion

            return(basePower);
        }
        private double CalculateDamageMultiplier(PBEPokemon user, PBEPokemon target, PBEMove move, PBEType moveType, PBEResult moveResult, bool criticalHit)
        {
            PBEMoveData mData            = PBEMoveData.Data[move];
            double      damageMultiplier = 1;

            switch (move)
            {
            case PBEMove.Gust:
            case PBEMove.Twister:
            {
                if (target.Status2.HasFlag(PBEStatus2.Airborne))
                {
                    damageMultiplier *= 2.0;
                }
                break;
            }

            case PBEMove.Earthquake:
            case PBEMove.Magnitude:
            {
                if (target.Status2.HasFlag(PBEStatus2.Underground))
                {
                    damageMultiplier *= 2.0;
                }
                break;
            }

            case PBEMove.Steamroller:
            case PBEMove.Stomp:
            {
                if (target.Minimize_Used)
                {
                    damageMultiplier *= 2.0;
                }
                break;
            }

            case PBEMove.Surf:
            {
                if (target.Status2.HasFlag(PBEStatus2.Underwater))
                {
                    damageMultiplier *= 2.0;
                }
                break;
            }
            }

            if (criticalHit)
            {
                damageMultiplier *= Settings.CritMultiplier;
                if (user.Ability == PBEAbility.Sniper)
                {
                    damageMultiplier *= 1.5;
                }
            }
            else
            {
                if ((target.Team.TeamStatus.HasFlag(PBETeamStatus.Reflect) && mData.Category == PBEMoveCategory.Physical) ||
                    (target.Team.TeamStatus.HasFlag(PBETeamStatus.LightScreen) && mData.Category == PBEMoveCategory.Special))
                {
                    if (target.Team.NumPkmnOnField == 1)
                    {
                        damageMultiplier *= 0.5;
                    }
                    else
                    {
                        damageMultiplier *= 0.66;
                    }
                }
            }

            switch (moveResult)
            {
            case PBEResult.NotVeryEffective_Type:
            {
                if (user.Ability == PBEAbility.TintedLens)
                {
                    damageMultiplier *= 2.0;
                }
                break;
            }

            case PBEResult.SuperEffective_Type:
            {
                if ((target.Ability == PBEAbility.Filter || target.Ability == PBEAbility.SolidRock) && !user.HasCancellingAbility())
                {
                    damageMultiplier *= 0.75;
                }
                if (user.Item == PBEItem.ExpertBelt)
                {
                    damageMultiplier *= 1.2;
                }
                break;
            }
            }
            if (user.HasType(moveType))
            {
                if (user.Ability == PBEAbility.Adaptability)
                {
                    damageMultiplier *= 2.0;
                }
                else
                {
                    damageMultiplier *= 1.5;
                }
            }
            if (mData.Category == PBEMoveCategory.Physical && user.Status1 == PBEStatus1.Burned && user.Ability != PBEAbility.Guts)
            {
                damageMultiplier *= 0.5;
            }
            if (moveType == PBEType.Fire && target.Ability == PBEAbility.Heatproof && !user.HasCancellingAbility())
            {
                damageMultiplier *= 0.5;
            }

            return(damageMultiplier);
        }
Exemple #19
0
        private void BasicHit(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMoveData mData,
                              Func <PBEBattlePokemon, PBEResult> failFunc     = null,
                              Action <PBEBattlePokemon> beforeDoingDamage     = null,
                              Action <PBEBattlePokemon, ushort> beforePostHit = null,
                              Action <PBEBattlePokemon> afterPostHit          = null,
                              Func <int, int?> recoilFunc = null)
        {
            // Targets array is [FoeLeft, FoeCenter, FoeRight, AllyLeft, AllyCenter, AllyRight]
            // User can faint or heal with a berry at LiquidOoze, IronBarbs/RockyHelmet, and also at Recoil/LifeOrb
            // -------------Official order-------------
            // Setup   - [effectiveness/fail checks foes], [effectiveness/fail checks allies], [miss/protection checks foes] [miss/protection checks allies], gem,
            // Allies  - [sub damage allies, sub effectiveness allies, sub crit allies, sub break allies], [hit allies], [effectiveness allies], [crit allies], [posthit allies], [faint allies],
            // Foes    - [sub damage foes, sub effectiveness foes, sub crit foes, sub break foes], [hit foes], [effectiveness foes], [crit foes], [posthit foes], [faint foes],
            // Cleanup - recoil, lifeorb, [colorchange foes], [colorchange allies], [berry allies], [berry foes], [antistatusability allies], [antistatusability foes], exp

            PBEType moveType = user.GetMoveType(mData);

            // DreamEater checks for sleep before gem activates
            // SuckerPunch fails
            Hit_GetVictims(user, targets, mData, moveType, out List <PBEAttackVictim> victims, failFunc: failFunc);
            if (victims.Count == 0)
            {
                return;
            }
            double basePower            = CalculateBasePower(user, targets, mData, moveType); // Gem activates here
            double initDamageMultiplier = victims.Count > 1 ? 0.75 : 1d;
            int    totalDamageDealt     = 0;

            void CalcDamage(PBEAttackVictim victim)
            {
                PBEBattlePokemon target           = victim.Pkmn;
                PBEResult        result           = victim.Result;
                double           damageMultiplier = initDamageMultiplier * victim.TypeEffectiveness;

                // Brick Break destroys Light Screen and Reflect before doing damage (after gem)
                // Feint destroys protection
                // Pay Day scatters coins
                beforeDoingDamage?.Invoke(target);
                bool crit = CritCheck(user, target, mData);

                damageMultiplier *= CalculateDamageMultiplier(user, target, mData, moveType, result, crit);
                int damage = (int)(damageMultiplier * CalculateDamage(user, target, mData, moveType, basePower, crit));

                victim.Damage     = DealDamage(user, target, damage, ignoreSubstitute: false, ignoreSturdy: false);
                totalDamageDealt += victim.Damage;
                victim.Crit       = crit;
            }

            void DoSub(IEnumerable <PBEAttackVictim> subs)
            {
                foreach (PBEAttackVictim victim in subs)
                {
                    CalcDamage(victim);
                    PBEBattlePokemon target = victim.Pkmn;
                    PBEResult        result = victim.Result;
                    if (result != PBEResult.Success)
                    {
                        BroadcastMoveResult(user, target, result);
                    }
                    if (victim.Crit)
                    {
                        BroadcastMoveCrit(target);
                    }
                    if (target.SubstituteHP == 0)
                    {
                        BroadcastStatus2(target, user, PBEStatus2.Substitute, PBEStatusAction.Ended);
                    }
                }
            }

            void DoNormal(IEnumerable <PBEAttackVictim> normals)
            {
                foreach (PBEAttackVictim victim in normals)
                {
                    CalcDamage(victim);
                }
                Hit_DoMoveResult(user, normals);
                Hit_DoCrit(normals);
                foreach (PBEAttackVictim victim in normals)
                {
                    PBEBattlePokemon target = victim.Pkmn;
                    // Stats/statuses are changed before post-hit effects
                    // HP-draining moves restore HP
                    beforePostHit?.Invoke(target, victim.Damage); // TODO: LiquidOoze fainting/healing
                    DoPostHitEffects(user, target, mData, moveType);
                    // ShadowForce destroys protection
                    // SmellingSalt cures paralysis
                    // WakeUpSlap cures sleep
                    afterPostHit?.Invoke(target); // Verified: These happen before Recoil/LifeOrb
                }
                Hit_FaintCheck(normals);
            }

            Hit_HitTargets(user.Team, DoSub, DoNormal, victims, out IEnumerable <PBEAttackVictim> allies, out IEnumerable <PBEAttackVictim> foes);
            DoPostAttackedEffects(user, allies, foes, true, recoilDamage: recoilFunc?.Invoke(totalDamageDealt), colorChangeType: moveType);
        }