private int CalculateDamage(PBEBattlePokemon user, PBEBattlePokemon target, IPBEMoveData mData, PBEType moveType, float 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);
            float a, d;

            if (aCat == PBEMoveCategory.Physical)
            {
                float m = ignoreA ? 1 : GetStatChangeModifier(criticalHit ? Math.Max((sbyte)0, aPkmn.AttackChange) : aPkmn.AttackChange, false);
                a = CalculateAttack(user, target, moveType, aPkmn.Attack * m);
            }
            else
            {
                float 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)
            {
                float m = ignoreD ? 1 : GetStatChangeModifier(criticalHit ? Math.Min((sbyte)0, target.DefenseChange) : target.DefenseChange, false);
                d = CalculateDefense(user, target, target.Defense * m);
            }
            else
            {
                float 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));
        }
 private static IEnumerable <PBEMove> GetMovesWithoutFlag(PBEMoveFlag flag, PBEMoveEffect?exception = null)
 {
     return(AllMoves.Where(m =>
     {
         IPBEMoveData mData = PBEDataProvider.Instance.GetMoveData(m, cache: false);
         if (!mData.IsMoveUsable())
         {
             return false;
         }
         if (exception.HasValue && mData.Effect == exception.Value)
         {
             return true;
         }
         return !mData.Flags.HasFlag(flag);
     }));
 }
Example #3
0
            public async Task Info([Remainder] string moveName)
            {
                PBEMove?nMove = PBEDataProvider.Instance.GetMoveByName(moveName);

                if (!nMove.HasValue || nMove.Value == PBEMove.None)
                {
                    await Context.Channel.SendMessageAsync($"{Context.User.Mention} ― Invalid move!");
                }
                else
                {
                    PBEMove move = nMove.Value;
                    moveName = PBEDataProvider.Instance.GetMoveName(move).English;
                    IPBEMoveData mData = PBEDataProvider.Instance.GetMoveData(move);
                    EmbedBuilder embed = new EmbedBuilder()
                                         .WithAuthor(Context.User)
                                         .WithColor(Utils.TypeColors[mData.Type])
                                         .WithTitle(moveName)
                                         .WithUrl(Utils.URL)
                                         .WithDescription(PBEDataProvider.Instance.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());
                }
            }
        private float CalculateBasePower(PBEBattlePokemon user, PBEBattlePokemon[] targets, IPBEMoveData mData, PBEType moveType)
        {
            float basePower;

            #region Get move's base power
            switch (mData.Effect)
            {
            case PBEMoveEffect.CrushGrip:
            {
                basePower = Math.Max(1, targets.Select(t => (float)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.5f);
                break;
            }

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

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

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

            case PBEMoveEffect.Magnitude:
            {
                int  val = _rand.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 + (20f * t.GetPositiveStatTotal())).Average()));
                break;
            }

            case PBEMoveEffect.Return:
            {
                basePower = Math.Max(1, user.Friendship / 2.5f);
                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.5f;
            }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            case PBEType.None:
            {
                break;
            }

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

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

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

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

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

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

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

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

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

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

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

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

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

                case PBEItem.WaterGem:
                {
                    BroadcastItem(user, user, PBEItem.WaterGem, PBEItemAction.Consumed);
                    basePower *= 1.5f;
                    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.0f;
                }
                break;
            }

            case PBEMoveEffect.Brine:
            {
                if (Array.FindIndex(targets, t => t.HP <= t.HP / 2) != -1)
                {
                    basePower *= 2.0f;
                }
                break;
            }

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

            case PBEMoveEffect.Hex:
            {
                if (Array.FindIndex(targets, t => t.Status1 != PBEStatus1.None) != -1)
                {
                    basePower *= 2.0f;
                }
                break;
            }

            case PBEMoveEffect.Payback:
            {
                if (Array.FindIndex(targets, t => t.HasUsedMoveThisTurn) != -1)
                {
                    basePower *= 2.0f;
                }
                break;
            }

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

            case PBEMoveEffect.SmellingSalt:
            {
                if (Array.FindIndex(targets, t => t.Status1 == PBEStatus1.Paralyzed) != -1)
                {
                    basePower *= 2.0f;
                }
                break;
            }

            case PBEMoveEffect.Venoshock:
            {
                if (Array.FindIndex(targets, t => t.Status1 == PBEStatus1.Poisoned || t.Status1 == PBEStatus1.BadlyPoisoned) != -1)
                {
                    basePower *= 2.0f;
                }
                break;
            }

            case PBEMoveEffect.WakeUpSlap:
            {
                if (Array.FindIndex(targets, t => t.Status1 == PBEStatus1.Asleep) != -1)
                {
                    basePower *= 2.0f;
                }
                break;
            }

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

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

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

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

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

            return(basePower);
        }
Example #5
0
        private static PBETurnAction DecideAction(PBETrainer trainer, PBEBattlePokemon user, IEnumerable <PBETurnAction> actions, IEnumerable <PBEBattlePokemon> standBy)
        {
            // Gather all options of switching and moves
            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 = PBEDataUtils.IsSpreadMove(moveTargets)
                            ? new PBETurnTarget[] { PBEBattleUtils.GetSpreadMoveTargets(user, moveTargets) }
                            : PBEBattleUtils.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 <PBEBattlePokemon>();
                    if (possibleTarget.HasFlag(PBETurnTarget.AllyLeft))
                    {
                        targets.Add(trainer.TryGetPokemon(PBEFieldPosition.Left));
                    }
                    if (possibleTarget.HasFlag(PBETurnTarget.AllyCenter))
                    {
                        targets.Add(trainer.TryGetPokemon(PBEFieldPosition.Center));
                    }
                    if (possibleTarget.HasFlag(PBETurnTarget.AllyRight))
                    {
                        targets.Add(trainer.TryGetPokemon(PBEFieldPosition.Right));
                    }
                    if (possibleTarget.HasFlag(PBETurnTarget.FoeLeft))
                    {
                        targets.Add(trainer.Team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Left));
                    }
                    if (possibleTarget.HasFlag(PBETurnTarget.FoeCenter))
                    {
                        targets.Add(trainer.Team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Center));
                    }
                    if (possibleTarget.HasFlag(PBETurnTarget.FoeRight))
                    {
                        targets.Add(trainer.Team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Right));
                    }
                    double score;
                    if (targets.All(p => p == null))
                    {
                        score = -100;
                    }
                    else
                    {
                        score = 0;
                        targets.RemoveAll(p => p == null);
                        IPBEMoveData mData = PBEDataProvider.Instance.GetMoveData(move);
                        if (!mData.IsMoveUsable())
                        {
                            throw new ArgumentOutOfRangeException(nameof(trainer), $"{move} is not yet implemented in Pokémon Battle Engine.");
                        }
                        switch (mData.Effect)
                        {
                        case PBEMoveEffect.Acrobatics:
                        case PBEMoveEffect.Bounce:
                        case PBEMoveEffect.BrickBreak:
                        case PBEMoveEffect.Brine:
                        case PBEMoveEffect.ChipAway:
                        case PBEMoveEffect.CrushGrip:
                        case PBEMoveEffect.Dig:
                        case PBEMoveEffect.Dive:
                        case PBEMoveEffect.Eruption:
                        case PBEMoveEffect.Facade:
                        case PBEMoveEffect.Feint:
                        case PBEMoveEffect.Flail:
                        case PBEMoveEffect.Fly:
                        case PBEMoveEffect.FoulPlay:
                        case PBEMoveEffect.Frustration:
                        case PBEMoveEffect.GrassKnot:
                        case PBEMoveEffect.HeatCrash:
                        case PBEMoveEffect.Hex:
                        case PBEMoveEffect.HiddenPower:
                        case PBEMoveEffect.Hit:
                        case PBEMoveEffect.Hit__2Times:
                        case PBEMoveEffect.Hit__2Times__MaybePoison:
                        case PBEMoveEffect.Hit__2To5Times:
                        case PBEMoveEffect.Hit__MaybeBurn:
                        case PBEMoveEffect.Hit__MaybeBurn__10PercentFlinch:
                        case PBEMoveEffect.Hit__MaybeBurnFreezeParalyze:
                        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.Judgment:
                        case PBEMoveEffect.Magnitude:
                        case PBEMoveEffect.Payback:
                        case PBEMoveEffect.PayDay:
                        case PBEMoveEffect.Psyshock:
                        case PBEMoveEffect.Punishment:
                        case PBEMoveEffect.Recoil:
                        case PBEMoveEffect.Recoil__10PercentBurn:
                        case PBEMoveEffect.Recoil__10PercentParalyze:
                        case PBEMoveEffect.Retaliate:
                        case PBEMoveEffect.Return:
                        case PBEMoveEffect.SecretPower:
                        case PBEMoveEffect.ShadowForce:
                        case PBEMoveEffect.SmellingSalt:
                        case PBEMoveEffect.StoredPower:
                        case PBEMoveEffect.TechnoBlast:
                        case PBEMoveEffect.Venoshock:
                        case PBEMoveEffect.WakeUpSlap:
                        case PBEMoveEffect.WeatherBall:
                        {
                            foreach (PBEBattlePokemon 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)
                                PBETypeEffectiveness.IsAffectedByAttack(user, target, moveType, out double damageMultiplier, useKnownInfo: true);
                                if (damageMultiplier <= 0)     // (-infinity, 0.0] Ineffective
                                {
                                    score += target.Team == trainer.Team ? 0 : -60;
                                }
                                else if (damageMultiplier <= 0.25)     // (0.0, 0.25] NotVeryEffective
                                {
                                    score += target.Team == trainer.Team ? -5 : -30;
                                }
                                else if (damageMultiplier < 1)     // (0.25, 1.0) NotVeryEffective
                                {
                                    score += target.Team == trainer.Team ? -10 : -10;
                                }
                                else if (damageMultiplier == 1)     // [1.0, 1.0] Normal
                                {
                                    score += target.Team == trainer.Team ? -15 : +10;
                                }
                                else if (damageMultiplier < 4)     // (1.0, 4.0) SuperEffective
                                {
                                    score += target.Team == trainer.Team ? -20 : +25;
                                }
                                else     // [4.0, infinity) SuperEffective
                                {
                                    score += target.Team == trainer.Team ? -30 : +40;
                                }
                                if (user.ReceivesSTAB(moveType) && damageMultiplier > 0)
                                {
                                    score += (user.Ability == PBEAbility.Adaptability ? 7 : 5) * (target.Team == trainer.Team ? -1 : +1);
                                }
                            }
                            break;
                        }

                        case PBEMoveEffect.Attract:
                        {
                            foreach (PBEBattlePokemon target in targets)
                            {
                                // TODO: Destiny knot
                                if (target.IsAttractionPossible(user, useKnownInfo: true) == PBEResult.Success)
                                {
                                    score += target.Team == trainer.Team ? -20 : +40;
                                }
                                else
                                {
                                    score += target.Team == trainer.Team ? 0 : -60;
                                }
                            }
                            break;
                        }

                        case PBEMoveEffect.Burn:
                        {
                            foreach (PBEBattlePokemon target in targets)
                            {
                                // TODO: Heatproof, physical attacker
                                if (target.IsBurnPossible(user, useKnownInfo: true) == PBEResult.Success)
                                {
                                    score += target.Team == trainer.Team ? -20 : +40;
                                }
                                else
                                {
                                    score += target.Team == trainer.Team ? 0 : -60;
                                }
                            }
                            break;
                        }

                        case PBEMoveEffect.ChangeTarget_ACC:
                        {
                            foreach (PBEBattlePokemon target in targets)
                            {
                                ScoreStatChange(user, target, PBEStat.Accuracy, mData.EffectParam, ref score);
                            }
                            break;
                        }

                        case PBEMoveEffect.ChangeTarget_ATK:
                        {
                            foreach (PBEBattlePokemon target in targets)
                            {
                                ScoreStatChange(user, target, PBEStat.Attack, mData.EffectParam, ref score);
                            }
                            break;
                        }

                        case PBEMoveEffect.ChangeTarget_DEF:
                        {
                            foreach (PBEBattlePokemon target in targets)
                            {
                                ScoreStatChange(user, target, PBEStat.Defense, mData.EffectParam, ref score);
                            }
                            break;
                        }

                        case PBEMoveEffect.ChangeTarget_EVA:
                        case PBEMoveEffect.Minimize:
                        {
                            foreach (PBEBattlePokemon target in targets)
                            {
                                ScoreStatChange(user, target, PBEStat.Evasion, mData.EffectParam, ref score);
                            }
                            break;
                        }

                        case PBEMoveEffect.ChangeTarget_SPATK:
                        {
                            foreach (PBEBattlePokemon target in targets)
                            {
                                ScoreStatChange(user, target, PBEStat.SpAttack, mData.EffectParam, ref score);
                            }
                            break;
                        }

                        case PBEMoveEffect.ChangeTarget_SPDEF:
                        {
                            foreach (PBEBattlePokemon target in targets)
                            {
                                ScoreStatChange(user, target, PBEStat.SpDefense, mData.EffectParam, ref score);
                            }
                            break;
                        }

                        case PBEMoveEffect.ChangeTarget_SPE:
                        {
                            foreach (PBEBattlePokemon target in targets)
                            {
                                ScoreStatChange(user, target, PBEStat.Speed, mData.EffectParam, ref score);
                            }
                            break;
                        }

                        case PBEMoveEffect.Confuse:
                        case PBEMoveEffect.Flatter:
                        case PBEMoveEffect.Swagger:
                        {
                            foreach (PBEBattlePokemon 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 == trainer.Team ? -20 : +40;
                                }
                                else
                                {
                                    score += target.Team == trainer.Team ? 0 : -60;
                                }
                            }
                            break;
                        }

                        case PBEMoveEffect.Growth:
                        {
                            int change = trainer.Battle.WillLeafGuardActivate() ? +2 : +1;
                            foreach (PBEBattlePokemon target in targets)
                            {
                                ScoreStatChange(user, target, PBEStat.Attack, change, ref score);
                                ScoreStatChange(user, target, PBEStat.SpAttack, change, ref score);
                            }
                            break;
                        }

                        case PBEMoveEffect.LeechSeed:
                        {
                            foreach (PBEBattlePokemon target in targets)
                            {
                                if (target.IsLeechSeedPossible(useKnownInfo: true) == PBEResult.Success)
                                {
                                    score += target.Team == trainer.Team ? -20 : +40;
                                }
                                else
                                {
                                    score += target.Team == trainer.Team ? 0 : -60;
                                }
                            }
                            break;
                        }

                        case PBEMoveEffect.LightScreen:
                        {
                            score += trainer.Team.TeamStatus.HasFlag(PBETeamStatus.LightScreen) || IsTeammateUsingEffect(actions, PBEMoveEffect.LightScreen) ? -100 : +40;
                            break;
                        }

                        case PBEMoveEffect.LowerTarget_ATK_DEF_By1:
                        {
                            foreach (PBEBattlePokemon 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 (PBEBattlePokemon 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 += trainer.Team.TeamStatus.HasFlag(PBETeamStatus.LuckyChant) || IsTeammateUsingEffect(actions, PBEMoveEffect.LuckyChant) ? -100 : +40;
                            break;
                        }

                        case PBEMoveEffect.Moonlight:
                        case PBEMoveEffect.Rest:
                        case PBEMoveEffect.RestoreTargetHP:
                        case PBEMoveEffect.Roost:
                        {
                            foreach (PBEBattlePokemon target in targets)
                            {
                                if (target.Team == trainer.Team)
                                {
                                    score += HPAware(target.HPPercentage, +45, -15);
                                }
                                else
                                {
                                    score -= 100;
                                }
                            }
                            break;
                        }

                        case PBEMoveEffect.Nothing:
                        case PBEMoveEffect.Teleport:
                        {
                            score -= 100;
                            break;
                        }

                        case PBEMoveEffect.Paralyze:
                        case PBEMoveEffect.ThunderWave:
                        {
                            foreach (PBEBattlePokemon target in targets)
                            {
                                bool tw = mData.Effect != PBEMoveEffect.ThunderWave || PBETypeEffectiveness.ThunderWaveTypeCheck(user, target, move, useKnownInfo: true) == PBEResult.Success;
                                if (tw && target.IsParalysisPossible(user, useKnownInfo: true) == PBEResult.Success)
                                {
                                    score += target.Team == trainer.Team ? -20 : +40;
                                }
                                else
                                {
                                    score += target.Team == trainer.Team ? 0 : -60;
                                }
                            }
                            break;
                        }

                        case PBEMoveEffect.Poison:
                        case PBEMoveEffect.Toxic:
                        {
                            foreach (PBEBattlePokemon target in targets)
                            {
                                // TODO: Poison Heal
                                if (target.IsPoisonPossible(user, useKnownInfo: true) == PBEResult.Success)
                                {
                                    score += target.Team == trainer.Team ? -20 : +40;
                                }
                                else
                                {
                                    score += target.Team == trainer.Team ? 0 : -60;
                                }
                            }
                            break;
                        }

                        case PBEMoveEffect.RaiseTarget_ATK_ACC_By1:
                        {
                            foreach (PBEBattlePokemon 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 (PBEBattlePokemon 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 (PBEBattlePokemon 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 (PBEBattlePokemon 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 (PBEBattlePokemon 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 (PBEBattlePokemon 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 (PBEBattlePokemon 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 (PBEBattlePokemon 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 (PBEBattlePokemon target in targets)
                            {
                                ScoreStatChange(user, target, PBEStat.Speed, +2, ref score);
                                ScoreStatChange(user, target, PBEStat.Attack, +1, ref score);
                            }
                            break;
                        }

                        case PBEMoveEffect.Reflect:
                        {
                            score += trainer.Team.TeamStatus.HasFlag(PBETeamStatus.Reflect) || IsTeammateUsingEffect(actions, PBEMoveEffect.Reflect) ? -100 : +40;
                            break;
                        }

                        case PBEMoveEffect.Safeguard:
                        {
                            score += trainer.Team.TeamStatus.HasFlag(PBETeamStatus.Safeguard) || IsTeammateUsingEffect(actions, PBEMoveEffect.Safeguard) ? -100 : +40;
                            break;
                        }

                        case PBEMoveEffect.Sleep:
                        {
                            foreach (PBEBattlePokemon target in targets)
                            {
                                // TODO: Bad Dreams
                                if (target.IsSleepPossible(user, useKnownInfo: true) == PBEResult.Success)
                                {
                                    score += target.Team == trainer.Team ? -20 : +40;
                                }
                                else
                                {
                                    score += target.Team == trainer.Team ? 0 : -60;
                                }
                            }
                            break;
                        }

                        case PBEMoveEffect.Substitute:
                        {
                            foreach (PBEBattlePokemon target in targets)
                            {
                                if (target.IsSubstitutePossible() == PBEResult.Success)
                                {
                                    score += target.Team == trainer.Team ? HPAware(target.HPPercentage, -30, +50) : -60;
                                }
                                else
                                {
                                    score += target.Team == trainer.Team ? 0 : -20;
                                }
                            }
                            break;
                        }

                        case PBEMoveEffect.BellyDrum:
                        case PBEMoveEffect.Camouflage:
                        case PBEMoveEffect.ChangeTarget_SPATK__IfAttractionPossible:
                        case PBEMoveEffect.Conversion:
                        case PBEMoveEffect.Curse:
                        case PBEMoveEffect.Endeavor:
                        case PBEMoveEffect.Entrainment:
                        case PBEMoveEffect.FinalGambit:
                        case PBEMoveEffect.FocusEnergy:
                        case PBEMoveEffect.Foresight:
                        case PBEMoveEffect.GastroAcid:
                        case PBEMoveEffect.Hail:
                        case PBEMoveEffect.Haze:
                        case PBEMoveEffect.HelpingHand:
                        case PBEMoveEffect.HPDrain__RequireSleep:
                        case PBEMoveEffect.LockOn:
                        case PBEMoveEffect.MagnetRise:
                        case PBEMoveEffect.Metronome:
                        case PBEMoveEffect.MiracleEye:
                        case PBEMoveEffect.Nightmare:
                        case PBEMoveEffect.OneHitKnockout:
                        case PBEMoveEffect.PainSplit:
                        case PBEMoveEffect.PowerTrick:
                        case PBEMoveEffect.Protect:
                        case PBEMoveEffect.PsychUp:
                        case PBEMoveEffect.Psywave:
                        case PBEMoveEffect.QuickGuard:
                        case PBEMoveEffect.RainDance:
                        case PBEMoveEffect.ReflectType:
                        case PBEMoveEffect.Refresh:
                        case PBEMoveEffect.RolePlay:
                        case PBEMoveEffect.Sandstorm:
                        case PBEMoveEffect.SeismicToss:
                        case PBEMoveEffect.Selfdestruct:
                        case PBEMoveEffect.SetDamage:
                        case PBEMoveEffect.SimpleBeam:
                        case PBEMoveEffect.Sketch:
                        case PBEMoveEffect.Snore:
                        case PBEMoveEffect.Soak:
                        case PBEMoveEffect.Spikes:
                        case PBEMoveEffect.StealthRock:
                        case PBEMoveEffect.SuckerPunch:
                        case PBEMoveEffect.SunnyDay:
                        case PBEMoveEffect.SuperFang:
                        case PBEMoveEffect.Tailwind:
                        case PBEMoveEffect.ToxicSpikes:
                        case PBEMoveEffect.Transform:
                        case PBEMoveEffect.TrickRoom:
                        case PBEMoveEffect.Whirlwind:
                        case PBEMoveEffect.WideGuard:
                        case PBEMoveEffect.WorrySeed:
                        {
                            // TODO
                            break;
                        }

                        default: throw new ArgumentOutOfRangeException(nameof(IPBEMoveData.Effect));
                        }
                    }
                    possibleActions.Add((new PBETurnAction(user, move, possibleTarget), score));
                }
            }
            if (user.CanSwitchOut())
            {
                PBEBattlePokemon[] availableForSwitch = trainer.Party.Except(standBy).Where(p => p.FieldPosition == PBEFieldPosition.None && p.HP > 0).ToArray();
                for (int s = 0; s < availableForSwitch.Length; s++) // Score switches
                {
                    PBEBattlePokemon switchPkmn = availableForSwitch[s];
                    // TODO: Entry hazards
                    // TODO: Known moves of active battlers
                    // TODO: Type effectiveness
                    double score = -10d;
                    possibleActions.Add((new PBETurnAction(user, switchPkmn), 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}", trainer.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;

            return(PBEDataProvider.GlobalRandom.RandomElement(byScore.Where(t => t.Score == bestScore).ToArray()).Action); // Pick random action of the ones that tied for best score
        }
Example #6
0
 /// <summary>Temporary check to see if a move is usable, can be removed once all moves are added</summary>
 public static bool IsMoveUsable(this IPBEMoveData mData)
 {
     return(PBEDataUtils.IsMoveUsable(mData.Effect));
 }
Example #7
0
 public static bool IsWeatherMove(this IPBEMoveData mData)
 {
     return(PBEDataUtils.IsWeatherMove(mData.Effect));
 }
Example #8
0
 public static bool IsSpreadMove(this IPBEMoveData mData)
 {
     return(PBEDataUtils.IsSpreadMove(mData.Targets));
 }
Example #9
0
        private void BasicHit(PBEBattlePokemon user, PBEBattlePokemon[] targets, IPBEMoveData 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;
            }
            float basePower            = CalculateBasePower(user, targets, mData, moveType); // Gem activates here
            float initDamageMultiplier = victims.Count > 1 ? 0.75f : 1;
            int   totalDamageDealt     = 0;

            void CalcDamage(PBEAttackVictim victim)
            {
                PBEBattlePokemon target           = victim.Pkmn;
                PBEResult        result           = victim.Result;
                float            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(List <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(List <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 List <PBEAttackVictim> allies, out List <PBEAttackVictim> foes);
            DoPostAttackedEffects(user, allies, foes, true, recoilDamage: recoilFunc?.Invoke(totalDamageDealt), colorChangeType: moveType);
        }
Example #10
0
 public static bool IsMultiHitMove(this IPBEMoveData mData)
 {
     return(PBEDataUtils.IsMultiHitMove(mData.Effect));
 }
Example #11
0
 public static bool IsHPRestoreMove(this IPBEMoveData mData)
 {
     return(PBEDataUtils.IsHPRestoreMove(mData.Effect));
 }
Example #12
0
 public static bool HasSecondaryEffects(this IPBEMoveData mData, PBESettings settings)
 {
     return(PBEDataUtils.HasSecondaryEffects(mData.Effect, settings));
 }
Example #13
0
 // TODO: TripleKick miss logic
 private void Hit_GetVictims(PBEBattlePokemon user, PBEBattlePokemon[] targets, IPBEMoveData 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 float 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 is not 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;
 }
Example #14
0
        // None of these moves are multi-target
        private void MultiHit(PBEBattlePokemon user, PBEBattlePokemon[] targets, IPBEMoveData 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;
            }
            float basePower            = CalculateBasePower(user, targets, mData, moveType); // Verified: Gem boost applies to all hits
            float initDamageMultiplier = victims.Count > 1 ? 0.75f : 1;

            void CalcDamage(PBEAttackVictim victim)
            {
                PBEBattlePokemon target           = victim.Pkmn;
                PBEResult        result           = victim.Result;
                float            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(List <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(List <PBEAttackVictim> normals)
            {
                normals.RemoveAll(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;
            List <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.FindIndex(v => v.Pkmn.HP > 0) != -1);
            Hit_DoMoveResult(user, allies);
            Hit_DoMoveResult(user, foes);
            BroadcastMultiHit(hit);
            Hit_FaintCheck(allies);
            Hit_FaintCheck(foes);
            DoPostAttackedEffects(user, allies, foes, true, colorChangeType: moveType);
        }
Example #15
0
        // None of these moves are multi-target
        private void FixedDamageHit(PBEBattlePokemon user, PBEBattlePokemon[] targets, IPBEMoveData 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)
            {
                _ = 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(List <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(List <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 List <PBEAttackVictim> allies, out List <PBEAttackVictim> foes);
            DoPostAttackedEffects(user, allies, foes, false, colorChangeType: moveType);
        }
        private float CalculateDamageMultiplier(PBEBattlePokemon user, PBEBattlePokemon target, IPBEMoveData mData, PBEType moveType, PBEResult moveResult, bool criticalHit)
        {
            float damageMultiplier = 1;

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

            if (criticalHit)
            {
                damageMultiplier *= Settings.CritMultiplier;
                if (user.Ability == PBEAbility.Sniper)
                {
                    damageMultiplier *= 1.5f;
                }
            }
            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.5f;
                    }
                    else
                    {
                        damageMultiplier *= 0.66f;
                    }
                }
            }

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

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

            return(damageMultiplier);
        }
Example #17
0
 public static bool IsSetDamageMove(this IPBEMoveData mData)
 {
     return(PBEDataUtils.IsSetDamageMove(mData.Effect));
 }