public static PBEBattle LoadReplay(string path) { byte[] fileBytes = File.ReadAllBytes(path); using (var s = new MemoryStream(fileBytes)) using (var r = new EndianBinaryReader(s, encoding: EncodingType.UTF16)) { byte[] hash; using (var md5 = MD5.Create()) { hash = md5.ComputeHash(fileBytes, 0, fileBytes.Length - 16); } for (int i = 0; i < 16; i++) { if (hash[i] != fileBytes[fileBytes.Length - 16 + i]) { throw new InvalidDataException(); } } ushort version = r.ReadUInt16(); // Unused for now int seed = r.ReadInt32(); // Unused for now PBEBattle b = null; int numEvents = r.ReadInt32(); for (int i = 0; i < numEvents; i++) { IPBEPacket packet = PBEPacketProcessor.CreatePacket(b, r.ReadBytes(r.ReadUInt16())); if (packet is PBEBattlePacket bp) { b = new PBEBattle(bp); } else if (packet is PBEWildPkmnAppearedPacket wpap) { PBETrainer wildTrainer = b.Teams[1].Trainers[0]; foreach (PBEPkmnAppearedInfo info in wpap.Pokemon) { PBEBattlePokemon pkmn = wildTrainer.TryGetPokemon(info.Pokemon); // Process disguise and position now pkmn.FieldPosition = info.FieldPosition; if (info.IsDisguised) { pkmn.Status2 |= PBEStatus2.Disguised; pkmn.KnownCaughtBall = info.CaughtBall; pkmn.KnownGender = info.Gender; pkmn.KnownNickname = info.Nickname; pkmn.KnownShiny = info.Shiny; pkmn.KnownSpecies = info.Species; pkmn.KnownForm = info.Form; IPBEPokemonData pData = PBEDataProvider.Instance.GetPokemonData(info); pkmn.KnownType1 = pData.Type1; pkmn.KnownType2 = pData.Type2; } b.ActiveBattlers.Add(pkmn); } } b.Events.Add(packet); } b.BattleState = PBEBattleState.Ended; return(b); } }
public static bool HasAbility(this IPBEPokemonData pData, PBEAbility ability) { if (ability == PBEAbility.None || ability >= PBEAbility.MAX) { throw new ArgumentOutOfRangeException(nameof(ability)); } return(pData.Abilities.Contains(ability)); }
public void Illusion_Works_Wild() { #region Setup PBEDataProvider.GlobalRandom.Seed = 0; PBESettings settings = PBESettings.DefaultSettings; var p0 = new TestPokemonCollection(1); p0[0] = new TestPokemon(settings, PBESpecies.Magikarp, 0, 1, PBEMove.Tackle); var p1 = new TestPokemonCollection(1); p1[0] = new TestPokemon(settings, PBESpecies.Zoroark, 0, 100, PBEMove.Splash) { Ability = PBEAbility.Illusion, CaughtBall = PBEItem.None }; var battle = PBEBattle.CreateWildBattle(PBEBattleFormat.Single, settings, new PBETrainerInfo(p0, "Trainer 0", false), new PBEWildInfo(p1)); battle.OnNewEvent += PBEBattle.ConsoleBattleEventHandler; PBETrainer t0 = battle.Trainers[0]; PBETrainer t1 = battle.Trainers[1]; PBEBattlePokemon magikarp = t0.Party[0]; PBEBattlePokemon zoroark = t1.Party[0]; zoroark.Status2 |= PBEStatus2.Disguised; zoroark.KnownGender = PBEGender.Genderless; zoroark.KnownCaughtBall = PBEItem.None; zoroark.KnownShiny = false; zoroark.KnownSpecies = PBESpecies.Entei; zoroark.KnownForm = 0; zoroark.KnownNickname = zoroark.KnownSpecies.ToString(); IPBEPokemonData pData = PBEDataProvider.Instance.GetPokemonData(zoroark.KnownSpecies, zoroark.KnownForm); zoroark.KnownType1 = pData.Type1; zoroark.KnownType2 = pData.Type2; battle.Begin(); #endregion #region Check that the disguise works Assert.True(zoroark.Status2.HasFlag(PBEStatus2.Disguised) && ((PBEWildPkmnAppearedPacket)battle.Events.Single(p => p is PBEWildPkmnAppearedPacket)).Pokemon[0].IsDisguised); #endregion #region Break the disguise and check Assert.True(t0.SelectActionsIfValid(out _, new PBETurnAction(magikarp, PBEMove.Tackle, PBETurnTarget.FoeCenter))); Assert.True(t1.SelectActionsIfValid(out _, new PBETurnAction(zoroark, PBEMove.Splash, PBETurnTarget.AllyCenter))); battle.RunTurn(); Assert.True(!zoroark.Status2.HasFlag(PBEStatus2.Disguised) && zoroark.KnownSpecies == PBESpecies.Zoroark); #endregion #region Cleanup battle.OnNewEvent -= PBEBattle.ConsoleBattleEventHandler; #endregion }
// Wild Pokémon always select a random usable move (unless they are forced to use a move) // They will flee randomly based on their PBEPokemonData.FleeRate only if it's a single battle and they are allowed to flee public static void CreateWildAIActions(this PBETrainer trainer) { if (trainer is null) { throw new ArgumentNullException(nameof(trainer)); } if (trainer.Battle.BattleState != PBEBattleState.WaitingForActions) { throw new InvalidOperationException($"{nameof(trainer.Battle.BattleState)} must be {PBEBattleState.WaitingForActions} to create actions."); } // Try to flee if it's a single wild battle and the Pokémon is a runner if (trainer.IsWild && trainer.Battle.BattleFormat == PBEBattleFormat.Single && trainer.IsFleeValid() is null) { PBEBattlePokemon user = trainer.ActionsRequired[0]; IPBEPokemonData pData = PBEDataProvider.Instance.GetPokemonData(user); if (PBEDataProvider.GlobalRandom.RandomBool(pData.FleeRate, 255)) { string valid = trainer.SelectFleeIfValid(); if (valid != null) { throw new Exception("Wild AI tried to flee but couldn't. - " + valid); } return; } } var actions = new PBETurnAction[trainer.ActionsRequired.Count]; for (int i = 0; i < actions.Length; i++) { PBEBattlePokemon user = trainer.ActionsRequired[i]; // If a Pokémon is forced to struggle, it must use Struggle if (user.IsForcedToStruggle()) { actions[i] = new PBETurnAction(user, PBEMove.Struggle, PBEBattleUtils.GetPossibleTargets(user, user.GetMoveTargets(PBEMove.Struggle))[0]); continue; } // If a Pokémon has a temp locked move (Dig, Dive, ShadowForce) it must be used if (user.TempLockedMove != PBEMove.None) { actions[i] = new PBETurnAction(user, user.TempLockedMove, user.TempLockedTargets); continue; } // The Pokémon is free to fight PBEMove[] usableMoves = user.GetUsableMoves(); PBEMove move = PBEDataProvider.GlobalRandom.RandomElement(usableMoves); actions[i] = new PBETurnAction(user, move, PBEBattle.GetRandomTargetForMetronome(user, move, PBEDataProvider.GlobalRandom)); } string valid2 = trainer.SelectActionsIfValid(actions); if (valid2 != null) { throw new Exception("Wild AI created bad actions. - " + valid2); } }
private void DoDisguisedAppearance(PBEBattlePokemon pkmn, PBEPkmnAppearedInfo info) { if (info.IsDisguised) { pkmn.Status2 |= PBEStatus2.Disguised; pkmn.KnownCaughtBall = info.CaughtBall; pkmn.KnownGender = info.Gender; pkmn.KnownNickname = info.Nickname; pkmn.KnownShiny = info.Shiny; pkmn.KnownSpecies = info.Species; pkmn.KnownForm = info.Form; IPBEPokemonData pData = PBEDataProvider.Instance.GetPokemonData(info); pkmn.KnownType1 = pData.Type1; pkmn.KnownType2 = pData.Type2; } }
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) { IPBEPokemonData pData = PBEDataProvider.Instance.GetPokemonData(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 => PBEDataProvider.Instance.GetMoveData(m, cache: false).Effect == PBEMoveEffect.Sketch)) { return(PBEDataUtils.SketchLegalMoves); } } return(moves.Distinct().Where(m => PBEDataUtils.IsMoveUsable(m)).ToArray()); }
public async Task Info([Remainder] string input) { // Inputs for forms should be like "Giratina (Origin Forme)" Match m = Regex.Match(input, @"^(\S+) \((.+)\)$"); string speciesName; string?formName; if (m.Success) { speciesName = m.Groups[1].Value; formName = m.Groups[2].Value; } else { speciesName = input; formName = null; } if (!PBEDataProvider.Instance.GetSpeciesByName(speciesName, out PBESpecies? nSpecies)) { await Context.Channel.SendMessageAsync($"{Context.User.Mention} ― Invalid species!"); return; } PBESpecies species = nSpecies.Value; speciesName = PBEDataProvider.Instance.GetSpeciesName(species).English; PBEForm form; if (formName is null) { form = 0; } else { if (!PBEDataProvider.Instance.GetFormByName(species, formName, out PBEForm? nForm)) { IReadOnlyList <PBEForm> forms = PBEDataUtils.GetForms(species, false); string str = $"{Context.User.Mention} ― Invalid form for {speciesName}"; if (forms.Count > 0) { str += ", valid forms are:\n**" + string.Join("\n", forms.Select(f => PBEDataProvider.Instance.GetFormName(species, f).English)) + "**"; } else { str += "! It has no forms!"; } await Context.Channel.SendMessageAsync(str); return; } form = nForm.Value; } formName = PBEDataUtils.HasForms(species, false) ? $" ({PBEDataProvider.Instance.GetFormName(species, form).English})" : string.Empty; IPBEPokemonData pData = PBEDataProvider.Instance.GetPokemonData(species, form); string types = $"{Utils.TypeEmotes[pData.Type1]}"; if (pData.Type2 != PBEType.None) { types += $" {Utils.TypeEmotes[pData.Type2]}"; } string ratio; switch (pData.GenderRatio) { case PBEGenderRatio.M7_F1: ratio = "87.5% Male, 12.5% Female"; break; case PBEGenderRatio.M3_F1: ratio = "75% Male, 25% Female"; break; case PBEGenderRatio.M1_F1: ratio = "50% Male, 50% Female"; break; case PBEGenderRatio.M1_F3: ratio = "25% Male, 75% Female"; break; case PBEGenderRatio.M0_F1: ratio = "100% Female"; break; case PBEGenderRatio.M1_F0: ratio = "100% Male"; break; case PBEGenderRatio.M0_F0: ratio = "Genderless Species"; break; default: throw new InvalidDataException(nameof(pData.GenderRatio)); } string weaknesses = string.Empty, resistances = string.Empty, immunities = string.Empty; for (PBEType atk = PBEType.None + 1; atk < PBEType.MAX; atk++) { float d = PBETypeEffectiveness.GetEffectiveness(atk, pData); if (d <= 0) { if (immunities != string.Empty) { immunities += ' '; } immunities += Utils.TypeEmotes[atk]; } else if (d < 1) { if (resistances != string.Empty) { resistances += ' '; } resistances += Utils.TypeEmotes[atk]; } if (d > 1) { if (weaknesses != string.Empty) { weaknesses += ' '; } weaknesses += Utils.TypeEmotes[atk]; } } if (weaknesses == string.Empty) { weaknesses = "No Weaknesses"; } if (resistances == string.Empty) { resistances = "No Resistances"; } if (immunities == string.Empty) { immunities = "No Immunities"; } EmbedBuilder embed = new EmbedBuilder() .WithAuthor(Context.User) .WithColor(Utils.GetColor(pData.Type1, pData.Type2)) .WithTitle($"{speciesName}{formName} ― {PBEDefaultDataProvider.Instance.GetSpeciesCategory(species).English}") .WithUrl(Utils.URL) .WithDescription(PBEDefaultDataProvider.Instance.GetSpeciesEntry(species).English.Replace('\n', ' ')) .AddField("Types", types, true) .AddField("Gender Ratio", ratio, true) .AddField("Weight", $"{pData.Weight:N1} kg", true) .AddField("Abilities", string.Join(", ", pData.Abilities.Select(a => PBEDataProvider.Instance.GetAbilityName(a).English)), false) .AddField("HP", pData.BaseStats.HP, true) .AddField("Attack", pData.BaseStats.Attack, true) .AddField("Defense", pData.BaseStats.Defense, true) .AddField("Special Attack", pData.BaseStats.SpAttack, true) .AddField("Special Defense", pData.BaseStats.SpDefense, true) .AddField("Speed", pData.BaseStats.Speed, true) .AddField("Type Weaknesses", weaknesses, true) .AddField("Type Resistances", resistances, true) .AddField("Type Immunities", immunities, true) .WithImageUrl(Utils.GetPokemonSprite(species, form, PBEDataProvider.GlobalRandom.RandomShiny(), PBEDataProvider.GlobalRandom.RandomGender(pData.GenderRatio), false, false)); await Context.Channel.SendMessageAsync(string.Empty, embed : embed.Build()); }
private string CreateKnownPokemonEmbed(PBEBattlePokemon pkmn) { IPBEPokemonData pData = PBEDataProvider.Instance.GetPokemonData(pkmn.KnownSpecies, pkmn.KnownForm); var sb = new StringBuilder(); string formStr = PBEDataUtils.HasForms(pkmn.KnownSpecies, false) ? $" ({PBEDataProvider.Instance.GetFormName(pkmn.KnownSpecies, pkmn.KnownForm).English})" : string.Empty; sb.AppendLine($"{GetTrainerName(pkmn.Trainer)}'s {pkmn.KnownNickname}/{pkmn.KnownSpecies}{formStr} {(pkmn.KnownStatus2.HasFlag(PBEStatus2.Transformed) ? pkmn.Gender.ToSymbol() : pkmn.KnownGender.ToSymbol())} Lv.{pkmn.Level}{(pkmn.KnownShiny ? $" {_shinyEmoji}" : string.Empty)}"); sb.AppendLine($"**HP:** {pkmn.HPPercentage:P2}"); sb.Append($"**Known types:** {Utils.TypeEmotes[pkmn.KnownType1]}"); if (pkmn.KnownType2 != PBEType.None) { sb.Append($" {Utils.TypeEmotes[pkmn.KnownType2]}"); } sb.AppendLine(); if (pkmn.Status1 != PBEStatus1.None) { sb.AppendLine($"**Main status:** {Utils.Status1Emotes[pkmn.Status1]}"); if (pkmn.Status1 == PBEStatus1.Asleep) { sb.AppendLine($"**{Utils.Status1Emotes[PBEStatus1.Asleep]} turns:** {pkmn.Status1Counter}"); } else if (pkmn.Status1 == PBEStatus1.BadlyPoisoned) { sb.AppendLine($"**{Utils.Status1Emotes[PBEStatus1.BadlyPoisoned]} counter:** {pkmn.Status1Counter}"); } } if (pkmn.KnownStatus2 != PBEStatus2.None) { sb.AppendLine($"**Volatile status:** {pkmn.KnownStatus2}"); if (pkmn.KnownStatus2.HasFlag(PBEStatus2.Confused)) { sb.AppendLine($"**Confusion turns:** {pkmn.ConfusionCounter}"); } } PBEDataUtils.GetStatRange(pData, PBEStat.HP, pkmn.Level, PBESettings.DefaultSettings, out ushort lowHP, out ushort highHP); PBEDataUtils.GetStatRange(pData, PBEStat.Attack, pkmn.Level, PBESettings.DefaultSettings, out ushort lowAttack, out ushort highAttack); PBEDataUtils.GetStatRange(pData, PBEStat.Defense, pkmn.Level, PBESettings.DefaultSettings, out ushort lowDefense, out ushort highDefense); PBEDataUtils.GetStatRange(pData, PBEStat.SpAttack, pkmn.Level, PBESettings.DefaultSettings, out ushort lowSpAttack, out ushort highSpAttack); PBEDataUtils.GetStatRange(pData, PBEStat.SpDefense, pkmn.Level, PBESettings.DefaultSettings, out ushort lowSpDefense, out ushort highSpDefense); PBEDataUtils.GetStatRange(pData, PBEStat.Speed, pkmn.Level, PBESettings.DefaultSettings, out ushort lowSpeed, out ushort highSpeed); sb.AppendLine($"**Stat range:** [HP] {lowHP}-{highHP}, [A] {lowAttack}-{highAttack}, [D] {lowDefense}-{highDefense}, [SA] {lowSpAttack}-{highSpAttack}, [SD] {lowSpDefense}-{highSpDefense}, [S] {lowSpeed}-{highSpeed}, [W] {pkmn.KnownWeight:0.0}"); AddStatChanges(pkmn, sb); if (pkmn.KnownAbility == PBEAbility.MAX) { sb.AppendLine($"**Possible abilities:** {string.Join(", ", pData.Abilities.Select(a => PBEDataProvider.Instance.GetAbilityName(a).English))}"); } else { sb.AppendLine($"**Known ability:** {PBEDataProvider.Instance.GetAbilityName(pkmn.KnownAbility).English}"); } sb.AppendLine($"**Known item:** {(pkmn.KnownItem == (PBEItem)ushort.MaxValue ? "???" : PBEDataProvider.Instance.GetItemName(pkmn.KnownItem).English)}"); sb.Append("**Known moves:** "); for (int i = 0; i < PBESettings.DefaultNumMoves; i++) { PBEBattleMoveset.PBEBattleMovesetSlot slot = pkmn.KnownMoves[i]; PBEMove move = slot.Move; if (move != PBEMove.None) { int pp = slot.PP; int maxPP = slot.MaxPP; if (i > 0) { sb.Append(", "); } if (move == PBEMove.MAX) { sb.Append("???"); } else { sb.Append($"{Utils.TypeEmotes[pkmn.GetMoveType(move, useKnownInfo: true)]} {PBEDataProvider.Instance.GetMoveName(move).English} ({pp}{(maxPP == 0 ? ")" : $"/{maxPP})")}"); } } }
public static string CustomPokemonToString(PBEBattlePokemon pkmn, bool useKnownInfo) { var sb = new StringBuilder(); string GetTeamNickname(PBEBattlePokemon p) { return($"{p.Trainer.Name}'s {(useKnownInfo ? p.KnownNickname : p.Nickname)}"); } void AddStatChanges() { PBEStat[] statChanges = pkmn.GetChangedStats(); if (statChanges.Length > 0) { var statStrs = new List <string>(7); if (Array.IndexOf(statChanges, PBEStat.Attack) != -1) { statStrs.Add($"[A] x{PBEBattle.GetStatChangeModifier(pkmn.AttackChange, false):0.00}"); } if (Array.IndexOf(statChanges, PBEStat.Defense) != -1) { statStrs.Add($"[D] x{PBEBattle.GetStatChangeModifier(pkmn.DefenseChange, false):0.00}"); } if (Array.IndexOf(statChanges, PBEStat.SpAttack) != -1) { statStrs.Add($"[SA] x{PBEBattle.GetStatChangeModifier(pkmn.SpAttackChange, false):0.00}"); } if (Array.IndexOf(statChanges, PBEStat.SpDefense) != -1) { statStrs.Add($"[SD] x{PBEBattle.GetStatChangeModifier(pkmn.SpDefenseChange, false):0.00}"); } if (Array.IndexOf(statChanges, PBEStat.Speed) != -1) { statStrs.Add($"[S] x{PBEBattle.GetStatChangeModifier(pkmn.SpeedChange, false):0.00}"); } if (Array.IndexOf(statChanges, PBEStat.Accuracy) != -1) { statStrs.Add($"[AC] x{PBEBattle.GetStatChangeModifier(pkmn.AccuracyChange, true):0.00}"); } if (Array.IndexOf(statChanges, PBEStat.Evasion) != -1) { statStrs.Add($"[E] x{PBEBattle.GetStatChangeModifier(pkmn.EvasionChange, true):0.00}"); } sb.AppendLine($"Stat changes: {string.Join(", ", statStrs)}"); } } void AddStatus1() { if (pkmn.Status1 != PBEStatus1.None) { sb.AppendLine($"Main status: {pkmn.Status1}"); if (pkmn.Status1 == PBEStatus1.Asleep) { sb.AppendLine($"Asleep turns: {pkmn.Status1Counter}"); } else if (pkmn.Status1 == PBEStatus1.BadlyPoisoned) { sb.AppendLine($"Toxic counter: {pkmn.Status1Counter}"); } } } void AddStatus2(PBEStatus2 status2) { status2 &= ~PBEStatus2.Flinching; // Don't show flinching sb.AppendLine($"Volatile status: {status2}"); if (status2.HasFlag(PBEStatus2.Disguised)) { string formStr = PBEDataUtils.HasForms(pkmn.KnownSpecies, false) ? $" ({PBEDataProvider.Instance.GetFormName(pkmn.KnownSpecies, pkmn.KnownForm).FromPBECultureInfo()})" : string.Empty; sb.AppendLine($"Disguised as: {pkmn.KnownNickname}/{PBEDataProvider.Instance.GetSpeciesName(pkmn.KnownSpecies).FromPBECultureInfo()}{formStr} {pkmn.KnownGender.ToSymbol()}"); } if (pkmn.Battle.BattleFormat != PBEBattleFormat.Single) { if (status2.HasFlag(PBEStatus2.Infatuated)) { sb.AppendLine($"Infatuated with: {GetTeamNickname(pkmn.InfatuatedWithPokemon)}"); } if (status2.HasFlag(PBEStatus2.LeechSeed)) { sb.AppendLine($"Seeded position: {pkmn.SeededTeam.CombinedName}'s {pkmn.SeededPosition}"); } if (status2.HasFlag(PBEStatus2.LockOn)) { sb.AppendLine($"Taking aim at: {GetTeamNickname(pkmn.LockOnPokemon)}"); } } } if (useKnownInfo) { IPBEPokemonData pData = PBEDataProvider.Instance.GetPokemonData(pkmn.KnownSpecies, pkmn.KnownForm); string formStr = PBEDataUtils.HasForms(pkmn.KnownSpecies, false) ? $" ({PBEDataProvider.Instance.GetFormName(pkmn.KnownSpecies, pkmn.KnownForm).FromPBECultureInfo()})" : string.Empty; sb.AppendLine($"{pkmn.KnownNickname}/{PBEDataProvider.Instance.GetSpeciesName(pkmn.KnownSpecies).FromPBECultureInfo()}{formStr} {(pkmn.KnownStatus2.HasFlag(PBEStatus2.Transformed) ? pkmn.Gender.ToSymbol() : pkmn.KnownGender.ToSymbol())} Lv.{pkmn.Level}"); sb.AppendLine($"HP: {pkmn.HPPercentage:P2}"); sb.Append($"Known types: {PBEDataProvider.Instance.GetTypeName(pkmn.KnownType1).FromPBECultureInfo()}"); if (pkmn.KnownType2 != PBEType.None) { sb.Append($"/{PBEDataProvider.Instance.GetTypeName(pkmn.KnownType2).FromPBECultureInfo()}"); } sb.AppendLine(); if (pkmn.FieldPosition != PBEFieldPosition.None) { sb.AppendLine($"Position: {pkmn.Team.CombinedName}'s {pkmn.FieldPosition}"); } AddStatus1(); if (pkmn.FieldPosition != PBEFieldPosition.None) { if (pkmn.KnownStatus2 != PBEStatus2.None) { AddStatus2(pkmn.KnownStatus2); } } PBEDataUtils.GetStatRange(pData, PBEStat.HP, pkmn.Level, pkmn.Battle.Settings, out ushort lowHP, out ushort highHP); PBEDataUtils.GetStatRange(pData, PBEStat.Attack, pkmn.Level, pkmn.Battle.Settings, out ushort lowAttack, out ushort highAttack); PBEDataUtils.GetStatRange(pData, PBEStat.Defense, pkmn.Level, pkmn.Battle.Settings, out ushort lowDefense, out ushort highDefense); PBEDataUtils.GetStatRange(pData, PBEStat.SpAttack, pkmn.Level, pkmn.Battle.Settings, out ushort lowSpAttack, out ushort highSpAttack); PBEDataUtils.GetStatRange(pData, PBEStat.SpDefense, pkmn.Level, pkmn.Battle.Settings, out ushort lowSpDefense, out ushort highSpDefense); PBEDataUtils.GetStatRange(pData, PBEStat.Speed, pkmn.Level, pkmn.Battle.Settings, out ushort lowSpeed, out ushort highSpeed); sb.AppendLine($"Stat range: [HP] {lowHP}-{highHP}, [A] {lowAttack}-{highAttack}, [D] {lowDefense}-{highDefense}, [SA] {lowSpAttack}-{highSpAttack}, [SD] {lowSpDefense}-{highSpDefense}, [S] {lowSpeed}-{highSpeed}, [W] {pkmn.KnownWeight:0.0}"); if (pkmn.FieldPosition != PBEFieldPosition.None) { AddStatChanges(); } if (pkmn.KnownAbility == PBEAbility.MAX) { sb.AppendLine($"Possible abilities: {string.Join(", ", pData.Abilities.Select(a => PBEDataProvider.Instance.GetAbilityName(a).FromPBECultureInfo()))}"); } else { sb.AppendLine($"Known ability: {PBEDataProvider.Instance.GetAbilityName(pkmn.KnownAbility).FromPBECultureInfo()}"); } sb.AppendLine($"Known item: {(pkmn.KnownItem == (PBEItem)ushort.MaxValue ? "???" : PBEDataProvider.Instance.GetItemName(pkmn.KnownItem).FromPBECultureInfo())}"); sb.Append("Known moves: "); for (int i = 0; i < pkmn.Battle.Settings.NumMoves; i++) { PBEBattleMoveset.PBEBattleMovesetSlot slot = pkmn.KnownMoves[i]; PBEMove move = slot.Move; int pp = slot.PP; int maxPP = slot.MaxPP; if (i > 0) { sb.Append(", "); } sb.Append(move == PBEMove.MAX ? "???" : PBEDataProvider.Instance.GetMoveName(move).FromPBECultureInfo()); if (move != PBEMove.None && move != PBEMove.MAX) { sb.Append($" ({pp}{(maxPP == 0 ? ")" : $"/{maxPP})")}"); } } }