Repository: Kermalis/PokemonBattleEngine Branch: master Commit: e733883abb04 Files: 242 Total size: 1.7 MB Directory structure: gitextract_erdgdp2y/ ├── .gitattributes ├── .gitignore ├── LICENSE.md ├── PokemonBattleEngine/ │ ├── Battle/ │ │ ├── Battle.cs │ │ ├── BattleActions.cs │ │ ├── BattleDamage.cs │ │ ├── BattleEffects.cs │ │ ├── BattleEffects_HitLogic.cs │ │ ├── BattleEnums.cs │ │ ├── BattleEvents.cs │ │ ├── BattleInventory.cs │ │ ├── BattleMoveset.cs │ │ ├── BattlePokemon.cs │ │ ├── BattleReplay.cs │ │ ├── BattleTargets.cs │ │ ├── BattleTeam.cs │ │ ├── BattleTrainer.cs │ │ ├── BattleUtils.cs │ │ ├── TrainerInfo.cs │ │ └── TypeEffectiveness.cs │ ├── Data/ │ │ ├── DataEnums.cs │ │ ├── DataProvider.cs │ │ ├── Interfaces/ │ │ │ ├── ItemData.cs │ │ │ ├── LocalizedString.cs │ │ │ ├── MoveData.cs │ │ │ ├── MovesetInterfaces.cs │ │ │ ├── PokemonData.cs │ │ │ ├── PokemonInterfaces.cs │ │ │ └── StatInterfaces.cs │ │ ├── Legality/ │ │ │ ├── LegalEffortValues.cs │ │ │ ├── LegalIndividualValues.cs │ │ │ ├── LegalMoveset.cs │ │ │ ├── LegalPokemon.cs │ │ │ └── LegalPokemonCollection.cs │ │ ├── PBEAlphabeticalList.cs │ │ ├── PBEList.cs │ │ ├── ReadOnlyLocalizedString.cs │ │ ├── ReadOnlyMoveset.cs │ │ ├── ReadOnlyPokemon.cs │ │ ├── ReadOnlyPokemonCollection.cs │ │ ├── Settings.cs │ │ ├── StatCollection.cs │ │ └── Utils/ │ │ ├── DataUtils_Effects.cs │ │ ├── DataUtils_Forms.cs │ │ ├── DataUtils_Items.cs │ │ ├── DataUtils_Moves.cs │ │ ├── DataUtils_Stats.cs │ │ └── DataUtils_Validate.cs │ ├── Network/ │ │ ├── Client.cs │ │ ├── Encryption.cs │ │ ├── NetworkUtils.cs │ │ ├── Server.cs │ │ └── ServerClient.cs │ ├── Packets/ │ │ ├── ActionsRequestPacket.cs │ │ ├── ActionsResponsePacket.cs │ │ ├── AutoCenterPacket.cs │ │ ├── BattlePacket.cs │ │ ├── BattleResultPacket.cs │ │ ├── FleeResponsePacket.cs │ │ ├── HazePacket.cs │ │ ├── MatchCancelledPacket.cs │ │ ├── PartyRequestPacket.cs │ │ ├── PartyResponsePacket.cs │ │ ├── PlayerJoinedPacket.cs │ │ ├── ResponsePacket.cs │ │ ├── SwitchInRequestPacket.cs │ │ ├── SwitchInResponsePacket.cs │ │ ├── TurnBeganPacket.cs │ │ ├── _AbilityPacket.cs │ │ ├── _AbilityReplacedPacket.cs │ │ ├── _BattleStatusPacket.cs │ │ ├── _CapturePacket.cs │ │ ├── _FleeFailedPacket.cs │ │ ├── _IllusionPacket.cs │ │ ├── _ItemPacket.cs │ │ ├── _ItemTurnPacket.cs │ │ ├── _MoveCritPacket.cs │ │ ├── _MoveLockPacket.cs │ │ ├── _MovePPChangedPacket.cs │ │ ├── _MoveResultPacket.cs │ │ ├── _MoveUsedPacket.cs │ │ ├── _PkmnEXPChangedPacket.cs │ │ ├── _PkmnEXPEarnedPacket.cs │ │ ├── _PkmnFaintedPacket.cs │ │ ├── _PkmnFormChangedPacket.cs │ │ ├── _PkmnHPChangedPacket.cs │ │ ├── _PkmnLevelChangedPacket.cs │ │ ├── _PkmnStatChangedPacket.cs │ │ ├── _PkmnSwitchInPacket.cs │ │ ├── _PkmnSwitchOutPacket.cs │ │ ├── _PsychUpPacket.cs │ │ ├── _ReflectTypePacket.cs │ │ ├── _SpecialMessagePacket.cs │ │ ├── _Status1Packet.cs │ │ ├── _Status2Packet.cs │ │ ├── _TeamStatusDamagePacket.cs │ │ ├── _TeamStatusPacket.cs │ │ ├── _TransformPacket.cs │ │ ├── _TypeChangedPacket.cs │ │ ├── _WeatherDamagePacket.cs │ │ ├── _WeatherPacket.cs │ │ ├── _WildPkmnAppearedPacket.cs │ │ ├── __Packet.cs │ │ └── __PacketProcessor.cs │ ├── PokemonBattleEngine.csproj │ └── Utils/ │ ├── EmptyCollections.cs │ ├── Random.cs │ └── Utils.cs ├── PokemonBattleEngine.DefaultData/ │ ├── AI/ │ │ ├── AI.cs │ │ ├── AIDecisions.cs │ │ └── WildAI.cs │ ├── Data/ │ │ ├── BerryData.cs │ │ ├── BerryData_Data.cs │ │ ├── EXPTables.cs │ │ ├── EventPokemon.cs │ │ ├── EventPokemon_Data.cs │ │ ├── ItemData.cs │ │ ├── ItemData_Data.cs │ │ ├── LegalityChecker.cs │ │ ├── MoveData.cs │ │ ├── MoveData_Data.cs │ │ └── PokemonData.cs │ ├── DefaultDataProvider.cs │ ├── Enums.cs │ ├── IPokemonDataExtended.cs │ ├── LocalizedString.cs │ ├── PokemonBattleEngine.DefaultData.csproj │ └── RandomTeamGenerator.cs ├── PokemonBattleEngine.sln ├── PokemonBattleEngineClient/ │ ├── App.xaml │ ├── App.xaml.cs │ ├── Clients/ │ │ ├── ActionsBuilder.cs │ │ ├── BattleClient.cs │ │ ├── NetworkClient.cs │ │ ├── NonLocalClient.cs │ │ ├── ReplayClient.cs │ │ ├── SinglePlayerClient.cs │ │ └── SwitchesBuilder.cs │ ├── Infrastructure/ │ │ ├── BetterWrapPanel.cs │ │ ├── Converters.cs │ │ ├── StringRenderer.cs │ │ ├── Utils.cs │ │ └── WriteableBitmapSurface.cs │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ ├── Models/ │ │ ├── MoveInfo.cs │ │ ├── PokemonInfo.cs │ │ ├── SwitchInfo.cs │ │ ├── TargetInfo.cs │ │ └── TeamInfo.cs │ ├── PokemonBattleEngineClient.csproj │ └── Views/ │ ├── ActionsView.xaml │ ├── ActionsView.xaml.cs │ ├── BattleView.xaml │ ├── BattleView.xaml.cs │ ├── FieldView.xaml │ ├── FieldView.xaml.cs │ ├── HPBarView.xaml │ ├── HPBarView.xaml.cs │ ├── MainView.xaml │ ├── MainView.xaml.cs │ ├── MessageView.xaml │ ├── MessageView.xaml.cs │ ├── PokemonView.xaml │ ├── PokemonView.xaml.cs │ ├── TeamBuilderView.xaml │ └── TeamBuilderView.xaml.cs ├── PokemonBattleEngineClient.Android/ │ ├── Assets/ │ │ └── AboutAssets.txt │ ├── MainActivity.cs │ ├── PokemonBattleEngineClient.Android.csproj │ ├── Properties/ │ │ ├── AndroidManifest.xml │ │ └── AssemblyInfo.cs │ └── Resources/ │ ├── AboutResources.txt │ ├── Resource.Designer.cs │ ├── layout/ │ │ └── Main.axml │ └── values/ │ └── Strings.xml ├── PokemonBattleEngineClient.Desktop/ │ ├── PokemonBattleEngineClient.Desktop.csproj │ └── Program.cs ├── PokemonBattleEngineClient.iOS/ │ ├── AppDelegate.cs │ ├── Entitlements.plist │ ├── Info.plist │ ├── Main.cs │ ├── PokemonBattleEngineClient.iOS.csproj │ ├── Properties/ │ │ └── AssemblyInfo.cs │ └── Resources/ │ └── LaunchScreen.xib ├── PokemonBattleEngineDiscord/ │ ├── BattleContext.cs │ ├── BattleContext_Constants.cs │ ├── BotCommands.cs │ ├── ChannelHandler.cs │ ├── Matchmaking.cs │ ├── PokemonBattleEngineDiscord.csproj │ ├── Program.cs │ ├── ReactionHandler.cs │ ├── ReplaySaver.cs │ └── Utils.cs ├── PokemonBattleEngineExtras/ │ ├── AIBattleDemo.cs │ ├── LocalizationDumper.cs │ ├── NARCTextDumper.cs │ ├── PokemonBattleEngineExtras.csproj │ ├── PokemonDataDumper.cs │ ├── PokemonDataDumper_Data.cs │ ├── PokemonDataDumper_DreamWorld.cs │ ├── Program.cs │ └── Utils.cs ├── PokemonBattleEngineServer/ │ ├── BattleServer.cs │ ├── Player.cs │ ├── PokemonBattleEngineServer.csproj │ └── Properties/ │ └── launchSettings.json ├── PokemonBattleEngineTests/ │ ├── Abilities/ │ │ ├── AntiStatusAbilityTests.cs │ │ ├── IllusionTests.cs │ │ ├── IntimidateTests.cs │ │ ├── NaturalCureTests.cs │ │ └── PoisonHealTests.cs │ ├── ActionsTests.cs │ ├── AutoCenterTests.cs │ ├── BattleResultTests.cs │ ├── BehaviorTests.cs │ ├── Forms/ │ │ ├── CastformCherrimTests.cs │ │ └── ShayminTests.cs │ ├── Items/ │ │ └── GemTests.cs │ ├── Moves/ │ │ ├── BellyDrumTests.cs │ │ ├── CamouflageTests.cs │ │ ├── HelpingHandTests.cs │ │ ├── MultiStrikeTests.cs │ │ ├── ProtectionTests.cs │ │ ├── RoostTests.cs │ │ ├── SecretPowerTests.cs │ │ ├── TeleportTests.cs │ │ └── WhirlwindTests.cs │ ├── PokemonBattleEngineTests.csproj │ ├── Statuses/ │ │ ├── ConfusionTests.cs │ │ ├── PowerTrickTests.cs │ │ └── SubstituteTests.cs │ ├── TestUtils.cs │ └── ThrowTests.cs ├── README.md ├── Shared Assets/ │ └── PKMN/ │ ├── FemaleMinispriteLookup.txt │ └── FemaleSpriteLookup.txt ├── To Do Abilities.txt ├── To Do Items.txt ├── To Do Moves.txt └── nuget.config ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ ############################################################################### # Set default behavior to automatically normalize line endings. ############################################################################### * text eol=lf ############################################################################### # Set default behavior for command prompt diff. # # This is need for earlier builds of msysgit that does not have it on by # default for csharp files. # Note: This is only used by command line ############################################################################### #*.cs diff=csharp ############################################################################### # Set the merge driver for project and solution files # # Merging from the command prompt will add diff markers to the files if there # are conflicts (Merging from VS is not affected by the settings below, in VS # the diff markers are never inserted). Diff markers may cause the following # file extensions to fail to load in VS. An alternative would be to treat # these files as binary and thus will always conflict and require user # intervention with every merge. To do so, just uncomment the entries below ############################################################################### #*.sln merge=binary #*.csproj merge=binary #*.vbproj merge=binary #*.vcxproj merge=binary #*.vcproj merge=binary #*.dbproj merge=binary #*.fsproj merge=binary #*.lsproj merge=binary #*.wixproj merge=binary #*.modelproj merge=binary #*.sqlproj merge=binary #*.wwaproj merge=binary ############################################################################### # behavior for image files # # image files are treated as binary by default. ############################################################################### #*.jpg binary #*.png binary #*.gif binary ############################################################################### # diff behavior for common document formats # # Convert binary document formats to text before diffing them. This feature # is only available from the command line. Turn it on by uncommenting the # entries below. ############################################################################### #*.doc diff=astextplain #*.DOC diff=astextplain #*.docx diff=astextplain #*.DOCX diff=astextplain #*.dot diff=astextplain #*.DOT diff=astextplain #*.pdf diff=astextplain #*.PDF diff=astextplain #*.rtf diff=astextplain #*.RTF diff=astextplain ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. PokemonBattleEngineExtras/DumpedData # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json project.fragment.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted #*.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # NuGet v3's project.json files produces more ignoreable files *.nuget.props *.nuget.targets # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings node_modules/ orleans.codegen.cs # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # JetBrains Rider .idea/ *.sln.iml # CodeRush .cr/ # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc ================================================ FILE: LICENSE.md ================================================ MIT License Copyright (c) 2022 Kermalis Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: PokemonBattleEngine/Battle/Battle.cs ================================================ using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngine.Packets; using Kermalis.PokemonBattleEngine.Utils; using System; using System.Collections.Generic; using System.Linq; namespace Kermalis.PokemonBattleEngine.Battle; /// Represents a specific Pokémon battle. public sealed partial class PBEBattle { public delegate void BattleStateChangedEvent(PBEBattle battle); public event BattleStateChangedEvent? OnStateChanged; private PBEBattleState _battleState; public PBEBattleState BattleState { get => _battleState; private set { if (value != _battleState) { _battleState = value; OnStateChanged?.Invoke(this); } } } public ushort TurnNumber { get; set; } public PBEBattleResult? BattleResult { get; set; } private readonly PBERandom _rand; public bool IsLocallyHosted { get; } public PBEBattleType BattleType { get; } public PBEBattleTerrain BattleTerrain { get; } public PBEBattleFormat BattleFormat { get; } public PBESettings Settings { get; } public PBETeams Teams { get; } public PBETrainers Trainers { get; } public List ActiveBattlers { get; } = new(6); private readonly List _turnOrder; public PBEWeather Weather { get; set; } public byte WeatherCounter { get; set; } public PBEBattleStatus BattleStatus { get; set; } public byte TrickRoomCount { get; set; } public List Events { get; } = new(); // Trainer battle private PBEBattle(PBEBattleFormat battleFormat, PBESettings settings, IReadOnlyList ti0, IReadOnlyList ti1, PBEBattleTerrain battleTerrain, PBEWeather weather, int? randomSeed) { if (battleFormat >= PBEBattleFormat.MAX) { throw new ArgumentOutOfRangeException(nameof(battleFormat)); } if (battleTerrain >= PBEBattleTerrain.MAX) { throw new ArgumentOutOfRangeException(nameof(battleTerrain)); } if (weather >= PBEWeather.MAX) { throw new ArgumentOutOfRangeException(nameof(weather)); } settings.ShouldBeReadOnly(nameof(settings)); _rand = new PBERandom(randomSeed ?? PBEDataProvider.GlobalRandom.RandomInt()); IsLocallyHosted = true; BattleType = PBEBattleType.Trainer; BattleTerrain = battleTerrain; BattleFormat = battleFormat; Settings = settings; Weather = weather; Teams = new PBETeams(this, ti0, ti1, out PBETrainers trainers); Trainers = trainers; _turnOrder = new List(6); QueueUpPokemon(); } // Wild battle private PBEBattle(PBEBattleFormat battleFormat, PBESettings settings, IReadOnlyList ti, PBEWildInfo wi, PBEBattleTerrain battleTerrain, PBEWeather weather, int? randomSeed) { if (battleFormat >= PBEBattleFormat.MAX) { throw new ArgumentOutOfRangeException(nameof(battleFormat)); } if (battleTerrain >= PBEBattleTerrain.MAX) { throw new ArgumentOutOfRangeException(nameof(battleTerrain)); } if (weather >= PBEWeather.MAX) { throw new ArgumentOutOfRangeException(nameof(weather)); } settings.ShouldBeReadOnly(nameof(settings)); _rand = new PBERandom(randomSeed ?? PBEDataProvider.GlobalRandom.RandomInt()); IsLocallyHosted = true; BattleType = PBEBattleType.Wild; BattleTerrain = battleTerrain; BattleFormat = battleFormat; Settings = settings; Weather = weather; Teams = new PBETeams(this, ti, wi, out PBETrainers trainers); Trainers = trainers; _turnOrder = new List(6); QueueUpPokemon(); } // Remote battle private PBEBattle(PBEBattlePacket packet) { IsLocallyHosted = false; BattleType = packet.BattleType; BattleFormat = packet.BattleFormat; BattleTerrain = packet.BattleTerrain; Weather = packet.Weather; Settings = packet.Settings; Teams = new PBETeams(this, packet, out PBETrainers trainers); Trainers = trainers; // These two will never be used in a non-local battle _rand = null!; _turnOrder = null!; } public static PBEBattle CreateTrainerBattle(PBEBattleFormat battleFormat, PBESettings settings, PBETrainerInfo ti0, PBETrainerInfo ti1, PBEBattleTerrain battleTerrain = PBEBattleTerrain.Plain, PBEWeather weather = PBEWeather.None, int? randomSeed = null) { return new PBEBattle(battleFormat, settings, new[] { ti0 }, new[] { ti1 }, battleTerrain, weather, randomSeed); } public static PBEBattle CreateTrainerBattle(PBEBattleFormat battleFormat, PBESettings settings, IReadOnlyList ti0, IReadOnlyList ti1, PBEBattleTerrain battleTerrain = PBEBattleTerrain.Plain, PBEWeather weather = PBEWeather.None, int? randomSeed = null) { return new PBEBattle(battleFormat, settings, ti0, ti1, battleTerrain, weather, randomSeed); } public static PBEBattle CreateWildBattle(PBEBattleFormat battleFormat, PBESettings settings, PBETrainerInfo ti0, PBEWildInfo wi, PBEBattleTerrain battleTerrain = PBEBattleTerrain.Plain, PBEWeather weather = PBEWeather.None, int? randomSeed = null) { return new PBEBattle(battleFormat, settings, new[] { ti0 }, wi, battleTerrain, weather, randomSeed); } public static PBEBattle CreateWildBattle(PBEBattleFormat battleFormat, PBESettings settings, IReadOnlyList ti0, PBEWildInfo wi, PBEBattleTerrain battleTerrain = PBEBattleTerrain.Plain, PBEWeather weather = PBEWeather.None, int? randomSeed = null) { return new PBEBattle(battleFormat, settings, ti0, wi, battleTerrain, weather, randomSeed); } public static PBEBattle CreateRemoteBattle(PBEBattlePacket packet) { return new PBEBattle(packet); } private void QueueUp(PBETeam team, PBEFieldPosition pos, ref PBETrainer? tr, ref int i) { // See which trainer owns this spot PBETrainer t = team.GetTrainer(pos); // If it's not the previous trainer, we start at their first PKMN if (tr != t) { i = 0; tr = t; } PBEList party = t.Party; // If the check index is valid, try to send out a non-fainted non-ignore PKMN tryget: if (i < party.Count) { PBEBattlePokemon p = party[i]; // If we should ignore this PKMN, try to get the one in the next index if (!p.CanBattle) { i++; goto tryget; } // Valid PKMN, send it out p.Trainer.SwitchInQueue.Add((p, pos)); // Wild PKMN should be out already if (team.IsWild) { p.FieldPosition = pos; ActiveBattlers.Add(p); } // Next slot to check i++; } } internal void QueueUpPokemon() { switch (BattleFormat) { case PBEBattleFormat.Single: { foreach (PBETeam team in Teams) { PBETrainer? t = null; int i = 0; QueueUp(team, PBEFieldPosition.Center, ref t, ref i); } break; } case PBEBattleFormat.Double: { foreach (PBETeam team in Teams) { PBETrainer? t = null; int i = 0; QueueUp(team, PBEFieldPosition.Left, ref t, ref i); QueueUp(team, PBEFieldPosition.Right, ref t, ref i); } break; } case PBEBattleFormat.Triple: { foreach (PBETeam team in Teams) { PBETrainer? t = null; int i = 0; QueueUp(team, PBEFieldPosition.Left, ref t, ref i); QueueUp(team, PBEFieldPosition.Center, ref t, ref i); QueueUp(team, PBEFieldPosition.Right, ref t, ref i); } break; } case PBEBattleFormat.Rotation: { foreach (PBETeam team in Teams) { PBETrainer? t = null; int i = 0; QueueUp(team, PBEFieldPosition.Center, ref t, ref i); QueueUp(team, PBEFieldPosition.Left, ref t, ref i); QueueUp(team, PBEFieldPosition.Right, ref t, ref i); } break; } default: throw new ArgumentOutOfRangeException(nameof(BattleFormat)); } BattleState = PBEBattleState.ReadyToBegin; } private void CheckLocal() { if (!IsLocallyHosted) { throw new InvalidOperationException("This battle is not locally hosted"); } } /// Begins the battle. /// Thrown when is not . public void Begin() { CheckLocal(); if (_battleState != PBEBattleState.ReadyToBegin) { throw new InvalidOperationException($"{nameof(BattleState)} must be {PBEBattleState.ReadyToBegin} to begin the battle."); } BattleState = PBEBattleState.Processing; BroadcastBattle(); // The first packet sent is PBEBattlePacket which replays rely on // Wild Pokémon appearing if (BattleType == PBEBattleType.Wild) { PBETeam team = Teams[1]; PBETrainer trainer = team.Trainers[0]; int count = trainer.SwitchInQueue.Count; var appearances = new PBEPkmnAppearedInfo[count]; for (int i = 0; i < count; i++) { appearances[i] = new PBEPkmnAppearedInfo(trainer.SwitchInQueue[i].Pkmn); } trainer.SwitchInQueue.Clear(); BroadcastWildPkmnAppeared(appearances); } SwitchesOrActions(); } /// Runs a turn. /// Thrown when is not . public void RunTurn() { CheckLocal(); if (_battleState != PBEBattleState.ReadyToRunTurn) { throw new InvalidOperationException($"{nameof(BattleState)} must be {PBEBattleState.ReadyToRunTurn} to run a turn."); } BattleState = PBEBattleState.Processing; FleeCheck(); if (EndCheck()) { return; } DetermineTurnOrder(); RunActionsInOrder(); TurnEnded(); } public void RunSwitches() { CheckLocal(); if (_battleState != PBEBattleState.ReadyToRunSwitches) { throw new InvalidOperationException($"{nameof(BattleState)} must be {PBEBattleState.ReadyToRunSwitches} to run switches."); } BattleState = PBEBattleState.Processing; FleeCheck(); if (EndCheck()) { return; } SwitchesOrActions(); } /// Sets to and clears and . Does not touch . public void SetEnded() { if (_battleState != PBEBattleState.Ended) { BattleState = PBEBattleState.Ended; OnNewEvent = null; OnStateChanged = null; } } private bool EndCheck() { if (BattleResult is not null) { BroadcastBattleResult(BattleResult.Value); foreach (PBEBattlePokemon pkmn in ActiveBattlers) { pkmn.ApplyNaturalCure(); // Natural Cure happens at the end of the battle. Pokémon should be copied when BattleState is set to "Ended", not upon battle result. } SetEnded(); return true; } return false; } private void SwitchesOrActions() { // Checking SwitchInQueue count since SwitchInsRequired is set to 0 after submitting switches PBETrainer[] trainersWithSwitchIns = Trainers.Where(t => t.SwitchInQueue.Count > 0).ToArray(); if (trainersWithSwitchIns.Length > 0) { var list = new List(6); foreach (PBETrainer trainer in trainersWithSwitchIns) { int count = trainer.SwitchInQueue.Count; var switches = new PBEPkmnAppearedInfo[count]; for (int i = 0; i < count; i++) { (PBEBattlePokemon pkmn, PBEFieldPosition pos) = trainer.SwitchInQueue[i]; pkmn.FieldPosition = pos; switches[i] = CreateSwitchInInfo(pkmn); PBETrainer.SwitchTwoPokemon(pkmn, pos); // Swap after Illusion ActiveBattlers.Add(pkmn); // Add before broadcast list.Add(pkmn); } BroadcastPkmnSwitchIn(trainer, switches); } DoSwitchInEffects(list); } foreach (PBETrainer trainer in Trainers) { int available = trainer.NumConsciousPkmn - trainer.NumPkmnOnField; trainer.SwitchInsRequired = 0; trainer.SwitchInQueue.Clear(); if (available > 0) { switch (BattleFormat) { case PBEBattleFormat.Single: { if (!trainer.IsSpotOccupied(PBEFieldPosition.Center)) { trainer.SwitchInsRequired = 1; } break; } case PBEBattleFormat.Double: { if (trainer.OwnsSpot(PBEFieldPosition.Left) && !trainer.IsSpotOccupied(PBEFieldPosition.Left)) { available--; trainer.SwitchInsRequired++; } if (available > 0 && trainer.OwnsSpot(PBEFieldPosition.Right) && !trainer.IsSpotOccupied(PBEFieldPosition.Right)) { trainer.SwitchInsRequired++; } break; } case PBEBattleFormat.Rotation: case PBEBattleFormat.Triple: { if (trainer.OwnsSpot(PBEFieldPosition.Left) && !trainer.IsSpotOccupied(PBEFieldPosition.Left)) { available--; trainer.SwitchInsRequired++; } if (available > 0 && trainer.OwnsSpot(PBEFieldPosition.Center) && !trainer.IsSpotOccupied(PBEFieldPosition.Center)) { available--; trainer.SwitchInsRequired++; } if (available > 0 && trainer.OwnsSpot(PBEFieldPosition.Right) && !trainer.IsSpotOccupied(PBEFieldPosition.Right)) { trainer.SwitchInsRequired++; } break; } default: throw new ArgumentOutOfRangeException(nameof(BattleFormat)); } } } trainersWithSwitchIns = Trainers.Where(t => t.SwitchInsRequired > 0).ToArray(); if (trainersWithSwitchIns.Length > 0) { BattleState = PBEBattleState.WaitingForSwitchIns; foreach (PBETrainer trainer in trainersWithSwitchIns) { BroadcastSwitchInRequest(trainer); } } else { if (EndCheck()) { return; } foreach (PBEBattlePokemon pkmn in ActiveBattlers) { pkmn.HasUsedMoveThisTurn = false; pkmn.TurnAction = null; pkmn.SpeedBoost_AbleToSpeedBoostThisTurn = pkmn.Ability == PBEAbility.SpeedBoost; if (pkmn.Status2.HasFlag(PBEStatus2.Flinching)) { BroadcastStatus2(pkmn, pkmn, PBEStatus2.Flinching, PBEStatusAction.Ended); } if (pkmn.Status2.HasFlag(PBEStatus2.HelpingHand)) { BroadcastStatus2(pkmn, pkmn, PBEStatus2.HelpingHand, PBEStatusAction.Ended); } if (pkmn.Status2.HasFlag(PBEStatus2.LockOn)) { if (--pkmn.LockOnTurns == 0) { pkmn.LockOnPokemon = null; BroadcastStatus2(pkmn, pkmn, PBEStatus2.LockOn, PBEStatusAction.Ended); } } if (pkmn.Protection_Used) { pkmn.Protection_Counter++; pkmn.Protection_Used = false; if (pkmn.Status2.HasFlag(PBEStatus2.Protected)) { BroadcastStatus2(pkmn, pkmn, PBEStatus2.Protected, PBEStatusAction.Ended); } } else { pkmn.Protection_Counter = 0; } if (pkmn.Status2.HasFlag(PBEStatus2.Roost)) { pkmn.EndRoost(); BroadcastStatus2(pkmn, pkmn, PBEStatus2.Roost, PBEStatusAction.Ended); } } foreach (PBETeam team in Teams) { if (team.TeamStatus.HasFlag(PBETeamStatus.QuickGuard)) { BroadcastTeamStatus(team, PBETeamStatus.QuickGuard, PBETeamStatusAction.Ended); } if (team.TeamStatus.HasFlag(PBETeamStatus.WideGuard)) { BroadcastTeamStatus(team, PBETeamStatus.WideGuard, PBETeamStatusAction.Ended); } } foreach (PBETrainer trainer in Trainers) { trainer.ActionsRequired.Clear(); trainer.ActionsRequired.AddRange(trainer.ActiveBattlersOrdered); } // #318 - We check pkmn on the field instead of conscious pkmn because of multi-battles // It still works if there's only one trainer on the team since we check for available switch-ins above if (BattleFormat == PBEBattleFormat.Triple && Teams.All(t => t.NumPkmnOnField == 1)) { PBEBattlePokemon pkmn0 = ActiveBattlers[0], pkmn1 = ActiveBattlers[1]; if ((pkmn0.FieldPosition == PBEFieldPosition.Left && pkmn1.FieldPosition == PBEFieldPosition.Left) || (pkmn0.FieldPosition == PBEFieldPosition.Right && pkmn1.FieldPosition == PBEFieldPosition.Right)) { PBEFieldPosition pkmn0OldPos = pkmn0.FieldPosition, pkmn1OldPos = pkmn1.FieldPosition; pkmn0.FieldPosition = PBEFieldPosition.Center; pkmn1.FieldPosition = PBEFieldPosition.Center; BroadcastAutoCenter(pkmn0, pkmn0OldPos, pkmn1, pkmn1OldPos); } } TurnNumber++; BroadcastTurnBegan(); foreach (PBETeam team in Teams) { bool old = team.MonFaintedThisTurn; // Fire events in a specific order team.MonFaintedThisTurn = false; team.MonFaintedLastTurn = old; } BattleState = PBEBattleState.WaitingForActions; foreach (PBETrainer trainer in Trainers.Where(t => t.NumConsciousPkmn > 0)) { BroadcastActionsRequest(trainer); } } } private IEnumerable GetActingOrder(IEnumerable pokemon, bool ignoreItemsThatActivate) { var evaluated = new List<(PBEBattlePokemon Pokemon, float Speed)>(); // TODO: Full Incense, Lagging Tail, Stall, Quick Claw foreach (PBEBattlePokemon pkmn in pokemon) { float speed = pkmn.Speed * GetStatChangeModifier(pkmn.SpeedChange, false); switch (pkmn.Item) { case PBEItem.ChoiceScarf: { speed *= 1.5f; break; } case PBEItem.MachoBrace: case PBEItem.PowerAnklet: case PBEItem.PowerBand: case PBEItem.PowerBelt: case PBEItem.PowerBracer: case PBEItem.PowerLens: case PBEItem.PowerWeight: { speed *= 0.5f; break; } case PBEItem.QuickPowder: { if (pkmn.OriginalSpecies == PBESpecies.Ditto && !pkmn.Status2.HasFlag(PBEStatus2.Transformed)) { speed *= 2.0f; } break; } } if (ShouldDoWeatherEffects()) { if (Weather == PBEWeather.HarshSunlight && pkmn.Ability == PBEAbility.Chlorophyll) { speed *= 2.0f; } else if (Weather == PBEWeather.Rain && pkmn.Ability == PBEAbility.SwiftSwim) { speed *= 2.0f; } else if (Weather == PBEWeather.Sandstorm && pkmn.Ability == PBEAbility.SandRush) { speed *= 2.0f; } } switch (pkmn.Ability) { case PBEAbility.QuickFeet: { if (pkmn.Status1 != PBEStatus1.None) { speed *= 1.5f; } break; } case PBEAbility.SlowStart: { if (pkmn.SlowStart_HinderTurnsLeft > 0) { speed *= 0.5f; } break; } } if (pkmn.Ability != PBEAbility.QuickFeet && pkmn.Status1 == PBEStatus1.Paralyzed) { speed *= 0.25f; } if (pkmn.Team.TeamStatus.HasFlag(PBETeamStatus.Tailwind)) { speed *= 2.0f; } (PBEBattlePokemon Pokemon, float Speed) tup = (pkmn, speed); if (evaluated.Count == 0) { evaluated.Add(tup); } else { int pkmnTiedWith = evaluated.FindIndex(t => t.Speed == speed); if (pkmnTiedWith != -1) // Speed tie - randomly go before or after the Pokémon it tied with { if (_rand.RandomBool()) { if (pkmnTiedWith == evaluated.Count - 1) { evaluated.Add(tup); } else { evaluated.Insert(pkmnTiedWith + 1, tup); } } else { evaluated.Insert(pkmnTiedWith, tup); } } else { int pkmnToGoBefore = evaluated.FindIndex(t => BattleStatus.HasFlag(PBEBattleStatus.TrickRoom) ? t.Speed > speed : t.Speed < speed); if (pkmnToGoBefore == -1) { evaluated.Add(tup); } else { evaluated.Insert(pkmnToGoBefore, tup); } } } } return evaluated.Select(t => t.Pokemon); } private void DetermineTurnOrder() { static int GetMovePrio(PBEBattlePokemon p) { IPBEMoveData mData = PBEDataProvider.Instance.GetMoveData(p.TurnAction!.FightMove); int priority = mData.Priority; if (p.Ability == PBEAbility.Prankster && mData.Category == PBEMoveCategory.Status) { priority++; } return priority; } _turnOrder.Clear(); //const int PursuitPriority = +7; const int SwitchRotatePriority = +6; const int WildFleePriority = -7; List pkmnUsingItem = ActiveBattlers.FindAll(p => p.TurnAction?.Decision == PBETurnDecision.Item); List pkmnSwitchingOut = ActiveBattlers.FindAll(p => p.TurnAction?.Decision == PBETurnDecision.SwitchOut); List pkmnFighting = ActiveBattlers.FindAll(p => p.TurnAction?.Decision == PBETurnDecision.Fight); List wildFleeing = ActiveBattlers.FindAll(p => p.TurnAction?.Decision == PBETurnDecision.WildFlee); // Item use happens first: _turnOrder.AddRange(GetActingOrder(pkmnUsingItem, true)); // Get move/switch/rotate/wildflee priority sorted IOrderedEnumerable> prios = pkmnSwitchingOut.Select(p => (p, SwitchRotatePriority)) .Concat(pkmnFighting.Select(p => (p, GetMovePrio(p)))) // Get move priority .Concat(wildFleeing.Select(p => (p, WildFleePriority))) .GroupBy(t => t.Item2, t => t.p) .OrderByDescending(t => t.Key); foreach (IGrouping bracket in prios) { bool ignoreItemsThatActivate = bracket.Key == SwitchRotatePriority || bracket.Key == WildFleePriority; _turnOrder.AddRange(GetActingOrder(bracket, ignoreItemsThatActivate)); } } private void RunActionsInOrder() { foreach (PBEBattlePokemon pkmn in _turnOrder.ToArray()) // Copy the list so a faint or ejection does not cause a collection modified exception { if (BattleResult is not null) // Do not broadcast battle result by calling EndCheck() in here; do it in TurnEnded() { return; } else if (ActiveBattlers.Contains(pkmn)) { switch (pkmn.TurnAction!.Decision) { case PBETurnDecision.Fight: { UseMove(pkmn, pkmn.TurnAction.FightMove, pkmn.TurnAction.FightTargets); break; } case PBETurnDecision.Item: { UseItem(pkmn, pkmn.TurnAction.UseItem); break; } case PBETurnDecision.SwitchOut: { SwitchTwoPokemon(pkmn, pkmn.Trainer.GetPokemon(pkmn.TurnAction.SwitchPokemonId)); break; } case PBETurnDecision.WildFlee: { WildFleeCheck(pkmn); break; } default: throw new ArgumentOutOfRangeException(nameof(pkmn.TurnAction.Decision)); } } } } private void TurnEnded() { if (EndCheck()) { return; } // Verified: Effects before LightScreen/LuckyChant/Reflect/Safeguard/TrickRoom DoTurnEndedEffects(); if (EndCheck()) { return; } // Verified: LightScreen/LuckyChant/Reflect/Safeguard/TrickRoom are removed in the order they were added foreach (PBETeam team in Teams) { if (team.TeamStatus.HasFlag(PBETeamStatus.LightScreen)) { team.LightScreenCount--; if (team.LightScreenCount == 0) { BroadcastTeamStatus(team, PBETeamStatus.LightScreen, PBETeamStatusAction.Ended); } } if (team.TeamStatus.HasFlag(PBETeamStatus.LuckyChant)) { team.LuckyChantCount--; if (team.LuckyChantCount == 0) { BroadcastTeamStatus(team, PBETeamStatus.LuckyChant, PBETeamStatusAction.Ended); } } if (team.TeamStatus.HasFlag(PBETeamStatus.Reflect)) { team.ReflectCount--; if (team.ReflectCount == 0) { BroadcastTeamStatus(team, PBETeamStatus.Reflect, PBETeamStatusAction.Ended); } } if (team.TeamStatus.HasFlag(PBETeamStatus.Safeguard)) { team.SafeguardCount--; if (team.SafeguardCount == 0) { BroadcastTeamStatus(team, PBETeamStatus.Safeguard, PBETeamStatusAction.Ended); } } if (team.TeamStatus.HasFlag(PBETeamStatus.Tailwind)) { team.TailwindCount--; if (team.TailwindCount == 0) { BroadcastTeamStatus(team, PBETeamStatus.Tailwind, PBETeamStatusAction.Ended); } } } // Trick Room if (BattleStatus.HasFlag(PBEBattleStatus.TrickRoom)) { TrickRoomCount--; if (TrickRoomCount == 0) { BroadcastBattleStatus(PBEBattleStatus.TrickRoom, PBEBattleStatusAction.Ended); } } SwitchesOrActions(); } } ================================================ FILE: PokemonBattleEngine/Battle/BattleActions.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngine.Data.Utils; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; namespace Kermalis.PokemonBattleEngine.Battle; public sealed class PBETurnAction { public byte PokemonId { get; } public PBETurnDecision Decision { get; } public PBEMove FightMove { get; } public PBETurnTarget FightTargets { get; internal set; } // Internal set because of PBEMoveTarget.RandomFoeSurrounding (TODO: Shouldn't this happen at runtime?) public PBEItem UseItem { get; } public byte SwitchPokemonId { get; } internal PBETurnAction(EndianBinaryReader r) { PokemonId = r.ReadByte(); Decision = r.ReadEnum(); switch (Decision) { case PBETurnDecision.Fight: { FightMove = r.ReadEnum(); FightTargets = r.ReadEnum(); break; } case PBETurnDecision.Item: { UseItem = r.ReadEnum(); break; } case PBETurnDecision.SwitchOut: { SwitchPokemonId = r.ReadByte(); break; } case PBETurnDecision.WildFlee: break; default: throw new InvalidDataException(nameof(Decision)); } } // Fight public PBETurnAction(PBEBattlePokemon pokemon, PBEMove fightMove, PBETurnTarget fightTargets) : this(pokemon.Id, fightMove, fightTargets) { } public PBETurnAction(byte pokemonId, PBEMove fightMove, PBETurnTarget fightTargets) { PokemonId = pokemonId; Decision = PBETurnDecision.Fight; FightMove = fightMove; FightTargets = fightTargets; } // Item public PBETurnAction(PBEBattlePokemon pokemon, PBEItem item) : this(pokemon.Id, item) { } public PBETurnAction(byte pokemonId, PBEItem item) { PokemonId = pokemonId; Decision = PBETurnDecision.Item; UseItem = item; } // Switch public PBETurnAction(PBEBattlePokemon pokemon, PBEBattlePokemon switchPokemon) : this(pokemon.Id, switchPokemon.Id) { } public PBETurnAction(byte pokemonId, byte switchPokemonId) { PokemonId = pokemonId; Decision = PBETurnDecision.SwitchOut; SwitchPokemonId = switchPokemonId; } // Internal wild flee internal PBETurnAction(PBEBattlePokemon pokemon) : this(pokemon.Id) { } internal PBETurnAction(byte pokemonId) { PokemonId = pokemonId; Decision = PBETurnDecision.WildFlee; } internal void ToBytes(EndianBinaryWriter w) { w.WriteByte(PokemonId); w.WriteEnum(Decision); switch (Decision) { case PBETurnDecision.Fight: { w.WriteEnum(FightMove); w.WriteEnum(FightTargets); break; } case PBETurnDecision.Item: { w.WriteEnum(UseItem); break; } case PBETurnDecision.SwitchOut: { w.WriteByte(SwitchPokemonId); break; } case PBETurnDecision.WildFlee: break; default: throw new InvalidDataException(nameof(Decision)); } } } public sealed class PBESwitchIn { public byte PokemonId { get; } public PBEFieldPosition Position { get; } internal PBESwitchIn(EndianBinaryReader r) { PokemonId = r.ReadByte(); Position = r.ReadEnum(); } public PBESwitchIn(PBEBattlePokemon pokemon, PBEFieldPosition position) : this(pokemon.Id, position) { } public PBESwitchIn(byte pokemonId, PBEFieldPosition position) { PokemonId = pokemonId; Position = position; } internal void ToBytes(EndianBinaryWriter w) { w.WriteByte(PokemonId); w.WriteEnum(Position); } } public sealed partial class PBEBattle { internal static bool AreActionsValid(PBETrainer trainer, IReadOnlyCollection actions, [NotNullWhen(false)] out string? invalidReason) { if (trainer.Battle._battleState != PBEBattleState.WaitingForActions) { throw new InvalidOperationException($"{nameof(BattleState)} must be {PBEBattleState.WaitingForActions} to validate actions."); } if (trainer.ActionsRequired.Count == 0) { invalidReason = "Actions were already submitted"; return false; } if (actions.Count != trainer.ActionsRequired.Count) { invalidReason = $"Invalid amount of actions submitted; required amount is {trainer.ActionsRequired.Count}"; return false; } var verified = new List(trainer.ActionsRequired.Count); var standBy = new List(trainer.ActionsRequired.Count); var items = new Dictionary(trainer.ActionsRequired.Count); foreach (PBETurnAction action in actions) { if (!trainer.TryGetPokemon(action.PokemonId, out PBEBattlePokemon? pkmn)) { invalidReason = $"Invalid Pokémon ID ({action.PokemonId})"; return false; } if (!trainer.ActionsRequired.Contains(pkmn)) { invalidReason = $"Pokémon {action.PokemonId} not looking for actions"; return false; } if (verified.Contains(pkmn)) { invalidReason = $"Pokémon {action.PokemonId} was multiple actions"; return false; } switch (action.Decision) { case PBETurnDecision.Fight: { if (Array.IndexOf(pkmn.GetUsableMoves(), action.FightMove) == -1) { invalidReason = $"{action.FightMove} is not usable by Pokémon {action.PokemonId}"; return false; } if (action.FightMove == pkmn.TempLockedMove && action.FightTargets != pkmn.TempLockedTargets) { invalidReason = $"Pokémon {action.PokemonId} must target {pkmn.TempLockedTargets}"; return false; } if (!AreTargetsValid(pkmn, action.FightMove, action.FightTargets)) { invalidReason = $"Invalid move targets for Pokémon {action.PokemonId}'s {action.FightMove}"; return false; } break; } case PBETurnDecision.Item: { if (pkmn.TempLockedMove != PBEMove.None) { invalidReason = $"Pokémon {action.PokemonId} must use {pkmn.TempLockedMove}"; return false; } if (!trainer.Inventory.TryGetValue(action.UseItem, out PBEBattleInventory.PBEBattleInventorySlot? slot)) { invalidReason = $"Trainer \"{trainer.Name}\" does not have any {action.UseItem}"; // Handles wild Pokémon return false; } bool used = items.TryGetValue(action.UseItem, out int amtUsed); if (!used) { amtUsed = 0; } long newAmt = slot.Quantity - amtUsed; if (newAmt <= 0) { invalidReason = $"Tried to use too many {action.UseItem}"; return false; } if (trainer.Battle.BattleType == PBEBattleType.Wild && trainer.Team.OpposingTeam.ActiveBattlers.Count > 1 && PBEDataUtils.AllBalls.Contains(action.UseItem)) { invalidReason = $"Cannot throw a ball at multiple wild Pokémon"; return false; } amtUsed++; if (used) { items[action.UseItem] = amtUsed; } else { items.Add(action.UseItem, amtUsed); } break; } case PBETurnDecision.SwitchOut: { if (!pkmn.CanSwitchOut()) { invalidReason = $"Pokémon {action.PokemonId} cannot switch out"; return false; } if (!trainer.TryGetPokemon(action.SwitchPokemonId, out PBEBattlePokemon? switchPkmn)) { invalidReason = $"Invalid switch Pokémon ID ({action.PokemonId})"; return false; } if (switchPkmn.HP == 0) { invalidReason = $"Switch Pokémon {action.PokemonId} is fainted"; return false; } if (switchPkmn.PBEIgnore) { invalidReason = $"Switch Pokémon {action.PokemonId} cannot battle"; return false; } if (switchPkmn.FieldPosition != PBEFieldPosition.None) { invalidReason = $"Switch Pokémon {action.PokemonId} is already on the field"; return false; } if (standBy.Contains(switchPkmn)) { invalidReason = $"Switch Pokémon {action.PokemonId} was asked to be switched in multiple times"; return false; } standBy.Add(switchPkmn); break; } default: { invalidReason = $"Invalid turn decision ({action.Decision})"; return false; } } verified.Add(pkmn); } invalidReason = null; return true; } internal static bool SelectActionsIfValid(PBETrainer trainer, IReadOnlyCollection actions, [NotNullWhen(false)] out string? invalidReason) { if (!AreActionsValid(trainer, actions, out invalidReason)) { return false; } trainer.ActionsRequired.Clear(); foreach (PBETurnAction action in actions) { PBEBattlePokemon pkmn = trainer.GetPokemon(action.PokemonId); if (action.Decision == PBETurnDecision.Fight && pkmn.GetMoveTargets(action.FightMove) == PBEMoveTarget.RandomFoeSurrounding) { switch (trainer.Battle.BattleFormat) { case PBEBattleFormat.Single: case PBEBattleFormat.Rotation: { action.FightTargets = PBETurnTarget.FoeCenter; break; } case PBEBattleFormat.Double: { action.FightTargets = trainer.Battle._rand.RandomBool() ? PBETurnTarget.FoeLeft : PBETurnTarget.FoeRight; break; } case PBEBattleFormat.Triple: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { action.FightTargets = trainer.Battle._rand.RandomBool() ? PBETurnTarget.FoeCenter : PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { PBETeam oppTeam = trainer.Team.OpposingTeam; int r; // Keep randomly picking until a non-fainted foe is selected roll: r = trainer.Battle._rand.RandomInt(0, 2); if (r == 0) { if (oppTeam.IsSpotOccupied(PBEFieldPosition.Left)) { action.FightTargets = PBETurnTarget.FoeLeft; } else { goto roll; } } else if (r == 1) { if (oppTeam.IsSpotOccupied(PBEFieldPosition.Center)) { action.FightTargets = PBETurnTarget.FoeCenter; } else { goto roll; } } else { if (oppTeam.IsSpotOccupied(PBEFieldPosition.Right)) { action.FightTargets = PBETurnTarget.FoeRight; } else { goto roll; } } } else { action.FightTargets = trainer.Battle._rand.RandomBool() ? PBETurnTarget.FoeLeft : PBETurnTarget.FoeCenter; } break; } default: throw new InvalidDataException(nameof(trainer.Battle.BattleFormat)); } } pkmn.TurnAction = action; } if (trainer.Battle.Trainers.All(t => t.ActionsRequired.Count == 0)) { trainer.Battle.BattleState = PBEBattleState.ReadyToRunTurn; } return true; } internal static bool AreSwitchesValid(PBETrainer trainer, IReadOnlyCollection switches, [NotNullWhen(false)] out string? invalidReason) { if (trainer.Battle._battleState != PBEBattleState.WaitingForSwitchIns) { throw new InvalidOperationException($"{nameof(BattleState)} must be {PBEBattleState.WaitingForSwitchIns} to validate switches."); } if (trainer.SwitchInsRequired == 0) { invalidReason = "Switches were already submitted"; return false; } if (switches.Count != trainer.SwitchInsRequired) { invalidReason = $"Invalid amount of switches submitted; required amount is {trainer.SwitchInsRequired}"; return false; } var verified = new List(trainer.SwitchInsRequired); foreach (PBESwitchIn s in switches) { if (s.Position == PBEFieldPosition.None || s.Position >= PBEFieldPosition.MAX || !trainer.OwnsSpot(s.Position)) { invalidReason = $"Invalid position ({s.PokemonId})"; return false; } if (!trainer.TryGetPokemon(s.PokemonId, out PBEBattlePokemon? pkmn)) { invalidReason = $"Invalid Pokémon ID ({s.PokemonId})"; return false; } if (pkmn.HP == 0) { invalidReason = $"Pokémon {s.PokemonId} is fainted"; return false; } if (pkmn.PBEIgnore) { invalidReason = $"Pokémon {s.PokemonId} cannot battle"; return false; } if (pkmn.FieldPosition != PBEFieldPosition.None) { invalidReason = $"Pokémon {s.PokemonId} is already on the field"; return false; } if (verified.Contains(pkmn)) { invalidReason = $"Pokémon {s.PokemonId} was asked to be switched in multiple times"; return false; } verified.Add(pkmn); } invalidReason = null; return true; } internal static bool SelectSwitchesIfValid(PBETrainer trainer, IReadOnlyCollection switches, [NotNullWhen(false)] out string? invalidReason) { if (!AreSwitchesValid(trainer, switches, out invalidReason)) { return false; } trainer.SwitchInsRequired = 0; foreach (PBESwitchIn s in switches) { trainer.SwitchInQueue.Add((trainer.GetPokemon(s.PokemonId), s.Position)); } if (trainer.Battle.Trainers.All(t => t.SwitchInsRequired == 0)) { trainer.Battle.BattleState = PBEBattleState.ReadyToRunSwitches; } return true; } internal static bool IsFleeValid(PBETrainer trainer, [NotNullWhen(false)] out string? invalidReason) { if (trainer.Battle.BattleType != PBEBattleType.Wild) { throw new InvalidOperationException($"{nameof(BattleType)} must be {PBEBattleType.Wild} to flee."); } switch (trainer.Battle._battleState) { case PBEBattleState.WaitingForActions: { if (trainer.ActionsRequired.Count == 0) { invalidReason = "Actions were already submitted"; return false; } PBEBattlePokemon pkmn = trainer.ActiveBattlersOrdered.First(); if (pkmn.TempLockedMove != PBEMove.None) { invalidReason = $"Pokémon {pkmn.Id} must use {pkmn.TempLockedMove}"; return false; } break; } case PBEBattleState.WaitingForSwitchIns: { if (trainer.SwitchInsRequired == 0) { invalidReason = "Switches were already submitted"; return false; } break; } default: throw new InvalidOperationException($"{nameof(BattleState)} must be {PBEBattleState.WaitingForActions} or {PBEBattleState.WaitingForSwitchIns} to flee."); } invalidReason = null; return true; } internal static bool SelectFleeIfValid(PBETrainer trainer, [NotNullWhen(false)] out string? invalidReason) { if (!IsFleeValid(trainer, out invalidReason)) { return false; } trainer.RequestedFlee = true; if (trainer.Battle._battleState == PBEBattleState.WaitingForActions) { trainer.ActionsRequired.Clear(); if (trainer.Battle.Trainers.All(t => t.ActionsRequired.Count == 0)) { trainer.Battle.BattleState = PBEBattleState.ReadyToRunTurn; } } else // WaitingForSwitches { trainer.SwitchInsRequired = 0; if (trainer.Battle.Trainers.All(t => t.SwitchInsRequired == 0)) { trainer.Battle.BattleState = PBEBattleState.ReadyToRunSwitches; } } return true; } } public sealed partial class PBETrainer { public bool AreActionsValid([NotNullWhen(false)] out string? invalidReason, params PBETurnAction[] actions) { return PBEBattle.AreActionsValid(this, actions, out invalidReason); } public bool AreActionsValid(IReadOnlyCollection actions, [NotNullWhen(false)] out string? invalidReason) { return PBEBattle.AreActionsValid(this, actions, out invalidReason); } public bool SelectActionsIfValid([NotNullWhen(false)] out string? invalidReason, params PBETurnAction[] actions) { return PBEBattle.SelectActionsIfValid(this, actions, out invalidReason); } public bool SelectActionsIfValid(IReadOnlyCollection actions, [NotNullWhen(false)] out string? invalidReason) { return PBEBattle.SelectActionsIfValid(this, actions, out invalidReason); } public bool AreSwitchesValid([NotNullWhen(false)] out string? invalidReason, params PBESwitchIn[] switches) { return PBEBattle.AreSwitchesValid(this, switches, out invalidReason); } public bool AreSwitchesValid(IReadOnlyCollection switches, [NotNullWhen(false)] out string? invalidReason) { return PBEBattle.AreSwitchesValid(this, switches, out invalidReason); } public bool SelectSwitchesIfValid([NotNullWhen(false)] out string? invalidReason, params PBESwitchIn[] switches) { return PBEBattle.SelectSwitchesIfValid(this, switches, out invalidReason); } public bool SelectSwitchesIfValid(IReadOnlyCollection switches, [NotNullWhen(false)] out string? invalidReason) { return PBEBattle.SelectSwitchesIfValid(this, switches, out invalidReason); } public bool IsFleeValid([NotNullWhen(false)] out string? invalidReason) { return PBEBattle.IsFleeValid(this, out invalidReason); } public bool SelectFleeIfValid([NotNullWhen(false)] out string? invalidReason) { return PBEBattle.SelectFleeIfValid(this, out invalidReason); } } ================================================ FILE: PokemonBattleEngine/Battle/BattleDamage.cs ================================================ using Kermalis.PokemonBattleEngine.Data; using System; using System.Linq; namespace Kermalis.PokemonBattleEngine.Battle; public sealed partial class PBEBattle { /// Gets the influence a stat change has on a stat. /// The stat change. /// True if the stat is or . public static float GetStatChangeModifier(sbyte change, bool forMissing) { float baseVal = forMissing ? 3 : 2; float numerator = Math.Max(baseVal, baseVal + change); float denominator = Math.Max(baseVal, baseVal - change); return numerator / denominator; } // Verified: Sturdy and Substitute only activate on damaging attacks (so draining HP or liquid ooze etc can bypass sturdy) private ushort DealDamage(PBEBattlePokemon culprit, PBEBattlePokemon victim, int hp, bool ignoreSubstitute = true, bool ignoreSturdy = true) { if (hp < 1) { hp = 1; } if (!ignoreSubstitute && victim.Status2.HasFlag(PBEStatus2.Substitute)) { ushort oldSubHP = victim.SubstituteHP; victim.SubstituteHP = (ushort)Math.Max(0, victim.SubstituteHP - hp); ushort damageAmt = (ushort)(oldSubHP - victim.SubstituteHP); BroadcastStatus2(victim, culprit, PBEStatus2.Substitute, PBEStatusAction.Damage); return damageAmt; } ushort oldHP = victim.HP; float oldPercentage = victim.HPPercentage; victim.HP = (ushort)Math.Max(0, victim.HP - hp); bool sturdyHappened = false, focusBandHappened = false, focusSashHappened = false; if (!ignoreSturdy && victim.HP == 0) { // TODO: Endure if (oldHP == victim.MaxHP && victim.Ability == PBEAbility.Sturdy && !culprit.HasCancellingAbility()) { sturdyHappened = true; victim.HP = 1; } else if (victim.Item == PBEItem.FocusBand && _rand.RandomBool(10, 100)) { focusBandHappened = true; victim.HP = 1; } else if (oldHP == victim.MaxHP && victim.Item == PBEItem.FocusSash) { focusSashHappened = true; victim.HP = 1; } } victim.UpdateHPPercentage(); BroadcastPkmnHPChanged(victim, oldHP, oldPercentage); if (sturdyHappened) { BroadcastAbility(victim, culprit, PBEAbility.Sturdy, PBEAbilityAction.Damage); BroadcastEndure(victim); } else if (focusBandHappened) { BroadcastItem(victim, culprit, PBEItem.FocusBand, PBEItemAction.Damage); } else if (focusSashHappened) { BroadcastItem(victim, culprit, PBEItem.FocusSash, PBEItemAction.Consumed); } return (ushort)(oldHP - victim.HP); } /// Restores HP to and broadcasts the HP changing if it changes. /// The Pokémon receiving the HP. /// The amount of HP will try to gain. /// The amount of HP restored. private ushort HealDamage(PBEBattlePokemon pkmn, int hp) { if (hp < 1) { hp = 1; } ushort oldHP = pkmn.HP; float oldPercentage = pkmn.HPPercentage; pkmn.HP = (ushort)Math.Min(pkmn.MaxHP, pkmn.HP + hp); // Always try to heal at least 1 HP ushort healAmt = (ushort)(pkmn.HP - oldHP); if (healAmt > 0) { pkmn.UpdateHPPercentage(); BroadcastPkmnHPChanged(pkmn, oldHP, oldPercentage); } return healAmt; } 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; } 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; } private float CalculateAttack(PBEBattlePokemon user, PBEBattlePokemon target, PBEType moveType, float initialAttack) { float attack = initialAttack; if (user.Ability == PBEAbility.HugePower || user.Ability == PBEAbility.PurePower) { attack *= 2.0f; } if (user.Item == PBEItem.ThickClub && (user.OriginalSpecies == PBESpecies.Cubone || user.OriginalSpecies == PBESpecies.Marowak)) { attack *= 2.0f; } if (user.Item == PBEItem.LightBall && user.OriginalSpecies == PBESpecies.Pikachu) { attack *= 2.0f; } if (moveType == PBEType.Bug && user.Ability == PBEAbility.Swarm && user.HP <= user.MaxHP / 3) { attack *= 1.5f; } if (moveType == PBEType.Fire && user.Ability == PBEAbility.Blaze && user.HP <= user.MaxHP / 3) { attack *= 1.5f; } if (moveType == PBEType.Grass && user.Ability == PBEAbility.Overgrow && user.HP <= user.MaxHP / 3) { attack *= 1.5f; } if (moveType == PBEType.Water && user.Ability == PBEAbility.Torrent && user.HP <= user.MaxHP / 3) { attack *= 1.5f; } if (user.Ability == PBEAbility.Hustle) { attack *= 1.5f; } if (user.Ability == PBEAbility.Guts && user.Status1 != PBEStatus1.None) { attack *= 1.5f; } if (user.Item == PBEItem.ChoiceBand) { attack *= 1.5f; } if (!user.HasCancellingAbility() && ShouldDoWeatherEffects() && Weather == PBEWeather.HarshSunlight && user.Team.ActiveBattlers.FindIndex(p => p.Ability == PBEAbility.FlowerGift) != -1) { attack *= 1.5f; } if ((moveType == PBEType.Fire || moveType == PBEType.Ice) && target.Ability == PBEAbility.ThickFat && !user.HasCancellingAbility()) { attack *= 0.5f; } if (user.Ability == PBEAbility.Defeatist && user.HP <= user.MaxHP / 2) { attack *= 0.5f; } if (user.Ability == PBEAbility.SlowStart && user.SlowStart_HinderTurnsLeft > 0) { attack *= 0.5f; } return attack; } private static float CalculateDefense(PBEBattlePokemon user, PBEBattlePokemon target, float initialDefense) { float defense = initialDefense; if (target.Item == PBEItem.MetalPowder && target.OriginalSpecies == PBESpecies.Ditto && !target.Status2.HasFlag(PBEStatus2.Transformed)) { defense *= 2.0f; } if (target.Ability == PBEAbility.MarvelScale && target.Status1 != PBEStatus1.None && !user.HasCancellingAbility()) { defense *= 1.5f; } if (target.Item == PBEItem.Eviolite && PBEDataProvider.Instance.HasEvolutions(target.OriginalSpecies, target.RevertForm)) { defense *= 1.5f; } return defense; } private float CalculateSpAttack(PBEBattlePokemon user, PBEBattlePokemon target, PBEType moveType, float initialSpAttack) { float spAttack = initialSpAttack; if (user.Item == PBEItem.DeepSeaTooth && user.OriginalSpecies == PBESpecies.Clamperl) { spAttack *= 2.0f; } if (user.Item == PBEItem.LightBall && user.OriginalSpecies == PBESpecies.Pikachu) { spAttack *= 2.0f; } if (moveType == PBEType.Bug && user.Ability == PBEAbility.Swarm && user.HP <= user.MaxHP / 3) { spAttack *= 1.5f; } if (moveType == PBEType.Fire && user.Ability == PBEAbility.Blaze && user.HP <= user.MaxHP / 3) { spAttack *= 1.5f; } if (moveType == PBEType.Grass && user.Ability == PBEAbility.Overgrow && user.HP <= user.MaxHP / 3) { spAttack *= 1.5f; } if (moveType == PBEType.Water && user.Ability == PBEAbility.Torrent && user.HP <= user.MaxHP / 3) { spAttack *= 1.5f; } if (ShouldDoWeatherEffects() && Weather == PBEWeather.HarshSunlight && user.Ability == PBEAbility.SolarPower) { spAttack *= 1.5f; } if (user.Item == PBEItem.SoulDew && (user.OriginalSpecies == PBESpecies.Latias || user.OriginalSpecies == PBESpecies.Latios)) { spAttack *= 1.5f; } if (user.Item == PBEItem.ChoiceSpecs) { spAttack *= 1.5f; } if ((user.Ability == PBEAbility.Minus || user.Ability == PBEAbility.Plus) && user.Team.ActiveBattlers.FindIndex(p => p != user && (p.Ability == PBEAbility.Minus || p.Ability == PBEAbility.Plus)) != -1) { spAttack *= 1.5f; } if ((moveType == PBEType.Fire || moveType == PBEType.Ice) && target.Ability == PBEAbility.ThickFat && !user.HasCancellingAbility()) { spAttack *= 0.5f; } if (user.Ability == PBEAbility.Defeatist && user.HP <= user.MaxHP / 2) { spAttack *= 0.5f; } return spAttack; } private float CalculateSpDefense(PBEBattlePokemon user, PBEBattlePokemon target, float initialSpDefense) { float spDefense = initialSpDefense; if (target.Item == PBEItem.DeepSeaScale && target.OriginalSpecies == PBESpecies.Clamperl) { spDefense *= 2.0f; } if (target.Item == PBEItem.SoulDew && (target.OriginalSpecies == PBESpecies.Latias || target.OriginalSpecies == PBESpecies.Latios)) { spDefense *= 1.5f; } if (target.Item == PBEItem.Eviolite && PBEDataProvider.Instance.HasEvolutions(target.OriginalSpecies, target.RevertForm)) { spDefense *= 1.5f; } if (ShouldDoWeatherEffects()) { if (Weather == PBEWeather.Sandstorm && target.HasType(PBEType.Rock)) { spDefense *= 1.5f; } if (!user.HasCancellingAbility() && Weather == PBEWeather.HarshSunlight && target.Team.ActiveBattlers.FindIndex(p => p.Ability == PBEAbility.FlowerGift) != -1) { spDefense *= 1.5f; } } return spDefense; } private int CalculateDamage(PBEBattlePokemon user, float a, float d, float basePower) { float damage; damage = (2 * user.Level / 5) + 2; damage = damage * a * basePower / d; damage /= 50; damage += 2; return (int)(damage * ((100f - _rand.RandomInt(0, 15)) / 100)); } private int CalculateConfusionDamage(PBEBattlePokemon pkmn) { // Verified: Unaware has no effect on confusion damage float m = GetStatChangeModifier(pkmn.AttackChange, false); float a = CalculateAttack(pkmn, pkmn, PBEType.None, pkmn.Attack * m); m = GetStatChangeModifier(pkmn.DefenseChange, false); float d = CalculateDefense(pkmn, pkmn, pkmn.Defense * m); return CalculateDamage(pkmn, a, d, 40); } 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); } } ================================================ FILE: PokemonBattleEngine/Battle/BattleEffects.cs ================================================ using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngine.Data.Utils; using Kermalis.PokemonBattleEngine.Packets; using System; using System.Collections.Generic; using System.IO; using System.Linq; namespace Kermalis.PokemonBattleEngine.Battle; public sealed partial class PBEBattle { private bool _calledFromOtherMove = false; private void DoSwitchInEffects(IEnumerable battlers, PBEBattlePokemon? forcedInBy = null) { // Set EXP Seens first foreach (PBEBattlePokemon pkmn in battlers) { PBETeam opTeam = pkmn.Team.OpposingTeam; foreach (PBEBattlePokemon op in opTeam.ActiveBattlers) { op.AddEXPPokemon(pkmn); pkmn.AddEXPPokemon(op); } } IEnumerable order = GetActingOrder(battlers, true); foreach (PBEBattlePokemon pkmn in order) { bool grounded = pkmn.IsGrounded(forcedInBy) == PBEResult.Success; // Verified: (Spikes/StealthRock/ToxicSpikes in the order they were applied) before ability if (grounded && pkmn.Team.TeamStatus.HasFlag(PBETeamStatus.Spikes)) { BroadcastTeamStatusDamage(pkmn.Team, PBETeamStatus.Spikes, pkmn); DealDamage(pkmn, pkmn, (int)(pkmn.MaxHP / (10.0 - (2 * pkmn.Team.SpikeCount)))); if (FaintCheck(pkmn)) { continue; } LowHPBerryCheck(pkmn, forcedToEatBy: forcedInBy); } if (pkmn.Team.TeamStatus.HasFlag(PBETeamStatus.StealthRock)) { BroadcastTeamStatusDamage(pkmn.Team, PBETeamStatus.StealthRock, pkmn); DealDamage(pkmn, pkmn, (int)(pkmn.MaxHP * PBETypeEffectiveness.GetStealthRockMultiplier(pkmn.Type1, pkmn.Type2))); if (FaintCheck(pkmn)) { continue; } LowHPBerryCheck(pkmn, forcedToEatBy: forcedInBy); } if (grounded && pkmn.Team.TeamStatus.HasFlag(PBETeamStatus.ToxicSpikes)) { if (pkmn.HasType(PBEType.Poison)) { pkmn.Team.ToxicSpikeCount = 0; BroadcastTeamStatus(pkmn.Team, PBETeamStatus.ToxicSpikes, PBETeamStatusAction.Cleared); } else if (pkmn.IsPoisonPossible(forcedInBy, ignoreSubstitute: true) == PBEResult.Success) { if (pkmn.Team.ToxicSpikeCount == 1) { pkmn.Status1 = PBEStatus1.Poisoned; } else { pkmn.Status1 = PBEStatus1.BadlyPoisoned; pkmn.Status1Counter = 1; } BroadcastStatus1(pkmn, pkmn, pkmn.Status1, PBEStatusAction.Added); // Immunity activates in ActivateAbility() below } } ActivateAbility(pkmn, true); } // Verified: Castform/Cherrim transformation goes last. Even if multiple weather abilities activate, they will not change until every ability has been activated CastformCherrimCheck(order); } private void DoPostHitEffects(PBEBattlePokemon user, PBEBattlePokemon victim, IPBEMoveData mData, PBEType moveType) { IllusionBreak(victim, user); // Verified: Illusion before Rocky Helmet if (victim.HP > 0 && victim.Ability == PBEAbility.Justified && moveType == PBEType.Dark) // Verified: Justified before Rocky Helmet { BroadcastAbility(victim, user, PBEAbility.Justified, PBEAbilityAction.Damage); ApplyStatChangeIfPossible(victim, victim, PBEStat.Attack, +1); } if (victim.HP > 0 && victim.Ability == PBEAbility.Rattled && (moveType == PBEType.Bug || moveType == PBEType.Dark || moveType == PBEType.Ghost)) // Verified: Rattled before Rocky Helmet { BroadcastAbility(victim, user, PBEAbility.Rattled, PBEAbilityAction.Damage); ApplyStatChangeIfPossible(victim, victim, PBEStat.Speed, +1); } if (victim.HP > 0 && victim.Ability == PBEAbility.WeakArmor && mData.Category == PBEMoveCategory.Physical) // Verified: Weak Armor before Rocky Helmet { BroadcastAbility(victim, user, PBEAbility.WeakArmor, PBEAbilityAction.Damage); ApplyStatChangeIfPossible(victim, victim, PBEStat.Defense, -1); ApplyStatChangeIfPossible(victim, victim, PBEStat.Speed, +1); } if (mData.Flags.HasFlag(PBEMoveFlag.MakesContact)) { if (user.HP > 0 && victim.Ability == PBEAbility.Mummy && user.Ability != PBEAbility.Multitype && user.Ability != PBEAbility.Mummy && user.Ability != PBEAbility.ZenMode) { BroadcastAbility(victim, user, PBEAbility.Mummy, PBEAbilityAction.Damage); SetAbility(victim, user, PBEAbility.Mummy); } if (user.HP > 0 && (victim.Ability == PBEAbility.IronBarbs || victim.Ability == PBEAbility.RoughSkin)) { BroadcastAbility(victim, user, victim.Ability, PBEAbilityAction.Damage); DealDamage(victim, user, user.MaxHP / 8); if (!FaintCheck(user)) { LowHPBerryCheck(user); } } // Verified: Cute Charm can activate when victim is about to faint if (user.HP > 0 && victim.Ability == PBEAbility.CuteCharm && user.IsAttractionPossible(victim) == PBEResult.Success && GetManipulableChance(victim, 30)) { BroadcastAbility(victim, user, PBEAbility.CuteCharm, PBEAbilityAction.ChangedStatus); CauseInfatuation(user, victim); } if (user.HP > 0 && victim.Ability == PBEAbility.EffectSpore && user.Status1 == PBEStatus1.None) { // Commented in case the Rainbow affects Effect Spore //int randomNum = PBERandom.RandomInt(0, 99); if (GetManipulableChance(victim, 30)) { // Spaghetti code taken from the assembly in generation 5 games PBEStatus1 status = PBEStatus1.None; int randomNum = _rand.RandomInt(0, 29); if (randomNum <= 20) { if (randomNum > 10) // 11-20 (10%) { // TODO: Can it really not paralyze electric? I thought that's gen 6+ if (!user.HasType(PBEType.Electric) && user.IsParalysisPossible(victim) == PBEResult.Success) { status = PBEStatus1.Paralyzed; } } else // 0-10 (11%) { if (user.IsSleepPossible(victim) == PBEResult.Success) { status = PBEStatus1.Asleep; } } } else // 21-29 (9%) { if (user.IsPoisonPossible(victim) == PBEResult.Success) { status = PBEStatus1.Poisoned; } } if (status != PBEStatus1.None) { BroadcastAbility(victim, user, PBEAbility.EffectSpore, PBEAbilityAction.ChangedStatus); user.Status1 = status; if (status == PBEStatus1.Asleep) { SetSleepTurns(user, Settings.SleepMinTurns, Settings.SleepMaxTurns); } user.Status1Counter = 0; BroadcastStatus1(user, victim, status, PBEStatusAction.Added); AntiStatusAbilityCheck(user); } } } if (user.HP > 0 && victim.Ability == PBEAbility.FlameBody && user.IsBurnPossible(victim) == PBEResult.Success && GetManipulableChance(victim, 30)) { BroadcastAbility(victim, user, PBEAbility.FlameBody, PBEAbilityAction.ChangedStatus); user.Status1 = PBEStatus1.Burned; BroadcastStatus1(user, victim, PBEStatus1.Burned, PBEStatusAction.Added); AntiStatusAbilityCheck(user); } if (user.HP > 0 && victim.Ability == PBEAbility.PoisonPoint && user.IsPoisonPossible(victim) == PBEResult.Success && GetManipulableChance(victim, 30)) { BroadcastAbility(victim, user, PBEAbility.PoisonPoint, PBEAbilityAction.ChangedStatus); user.Status1 = PBEStatus1.Poisoned; BroadcastStatus1(user, victim, PBEStatus1.Poisoned, PBEStatusAction.Added); AntiStatusAbilityCheck(user); } if (user.HP > 0 && victim.Ability == PBEAbility.Static && user.IsParalysisPossible(victim) == PBEResult.Success && GetManipulableChance(victim, 30)) { BroadcastAbility(victim, user, PBEAbility.Static, PBEAbilityAction.ChangedStatus); user.Status1 = PBEStatus1.Paralyzed; BroadcastStatus1(user, victim, PBEStatus1.Paralyzed, PBEStatusAction.Added); AntiStatusAbilityCheck(user); } // Verified: Above abilities before Rocky Helmet if (user.HP > 0 && victim.Item == PBEItem.RockyHelmet) { BroadcastItem(victim, user, PBEItem.RockyHelmet, PBEItemAction.Damage); DealDamage(victim, user, user.MaxHP / 6); if (!FaintCheck(user)) { LowHPBerryCheck(user); } } } } private void DoPostAttackedEffects(PBEBattlePokemon user, List allies, List foes, bool doLifeOrb, int? recoilDamage = null, PBEType colorChangeType = PBEType.None) { #region User if (user.HP > 0) { // Verified: Recoil before LifeOrb // Verified: Recoil calls berry check directly, and both can faint here if (recoilDamage is not null) { BroadcastRecoil(user); DealDamage(user, user, recoilDamage.Value); if (!FaintCheck(user)) { LowHPBerryCheck(user); } } if (user.HP > 0 && doLifeOrb && user.Item == PBEItem.LifeOrb) { BroadcastItem(user, user, PBEItem.LifeOrb, PBEItemAction.Damage); DealDamage(user, user, user.MaxHP / 10); FaintCheck(user); // No berry check because we are holding Life Orb } } #endregion #region Victims void DoColorChange(IEnumerable order) { foreach (PBEBattlePokemon pkmn in order) { if (pkmn.Ability == PBEAbility.ColorChange && !pkmn.HasType(colorChangeType)) { BroadcastAbility(pkmn, pkmn, PBEAbility.ColorChange, PBEAbilityAction.ChangedAppearance); BroadcastTypeChanged(pkmn, colorChangeType, PBEType.None); } } } IEnumerable a = allies.Select(v => v.Pkmn).Where(p => p.HP > 0); IEnumerable f = foes.Select(v => v.Pkmn).Where(p => p.HP > 0); // Verified: ColorChange (foes, allies) before Berry if (colorChangeType != PBEType.None) { DoColorChange(f); DoColorChange(a); } // Verified: Berry (allies, foes) before AntiStatusAbility LowHPBerryCheck(a, forcedToEatBy: user); LowHPBerryCheck(f, forcedToEatBy: user); // Verified: AntiStatusAbility (allies, foes) AntiStatusAbilityCheck(a); // Heal a status that was given with the user's Mold Breaker AntiStatusAbilityCheck(f); #endregion } private void DoTurnEndedEffects() { IEnumerable order = GetActingOrder(ActiveBattlers, true); // Verified: Weather stops before doing damage if (Weather != PBEWeather.None && WeatherCounter > 0) { WeatherCounter--; if (WeatherCounter == 0) { PBEWeather w = Weather; Weather = PBEWeather.None; BroadcastWeather(w, PBEWeatherAction.Ended); CastformCherrimCheck(order); } } // Verified: Hailstorm/Sandstorm/IceBody/RainDish/SolarPower before all if (Weather != PBEWeather.None && ShouldDoWeatherEffects()) { foreach (PBEBattlePokemon pkmn in order) { if (pkmn.HP == 0) { continue; } switch (Weather) { case PBEWeather.Hailstorm: { if (pkmn.Ability == PBEAbility.IceBody) { if (pkmn.HP < pkmn.MaxHP) { BroadcastAbility(pkmn, pkmn, pkmn.Ability, PBEAbilityAction.RestoredHP); HealDamage(pkmn, pkmn.MaxHP / Settings.IceBodyHealDenominator); } } else if (!pkmn.HasType(PBEType.Ice) && pkmn.Ability != PBEAbility.Overcoat && pkmn.Ability != PBEAbility.SnowCloak) { BroadcastWeatherDamage(PBEWeather.Hailstorm, pkmn); DealDamage(pkmn, pkmn, pkmn.MaxHP / Settings.HailDamageDenominator); if (!FaintCheck(pkmn)) { LowHPBerryCheck(pkmn); } } break; } case PBEWeather.HarshSunlight: { if (pkmn.Ability == PBEAbility.SolarPower) { BroadcastAbility(pkmn, pkmn, pkmn.Ability, PBEAbilityAction.Damage); DealDamage(pkmn, pkmn, pkmn.MaxHP / 8); if (!FaintCheck(pkmn)) { LowHPBerryCheck(pkmn); } } break; } case PBEWeather.Rain: { if (pkmn.Ability == PBEAbility.RainDish && pkmn.HP < pkmn.MaxHP) { BroadcastAbility(pkmn, pkmn, pkmn.Ability, PBEAbilityAction.RestoredHP); HealDamage(pkmn, pkmn.MaxHP / 16); } break; } case PBEWeather.Sandstorm: { if (!pkmn.HasType(PBEType.Rock) && !pkmn.HasType(PBEType.Ground) && !pkmn.HasType(PBEType.Steel) && pkmn.Ability != PBEAbility.Overcoat && pkmn.Ability != PBEAbility.SandForce && pkmn.Ability != PBEAbility.SandRush && pkmn.Ability != PBEAbility.SandVeil && !pkmn.Status2.HasFlag(PBEStatus2.Underground) && !pkmn.Status2.HasFlag(PBEStatus2.Underwater)) { BroadcastWeatherDamage(PBEWeather.Sandstorm, pkmn); DealDamage(pkmn, pkmn, pkmn.MaxHP / Settings.SandstormDamageDenominator); if (!FaintCheck(pkmn)) { LowHPBerryCheck(pkmn); } } break; } } } } // Verified: Healer/ShedSkin/BlackSludge/Leftovers before LeechSeed foreach (PBEBattlePokemon pkmn in order) { if (pkmn.HP == 0) { continue; } switch (pkmn.Ability) { case PBEAbility.Healer: { foreach (PBEBattlePokemon ally in GetRuntimeSurrounding(pkmn, true, false)) { // TODO: #265 if (ally.Status1 != PBEStatus1.None && GetManipulableChance(pkmn, 30)) { BroadcastAbility(pkmn, ally, pkmn.Ability, PBEAbilityAction.ChangedStatus); PBEStatus1 status1 = ally.Status1; ally.Status1 = PBEStatus1.None; ally.Status1Counter = 0; ally.SleepTurns = 0; BroadcastStatus1(ally, pkmn, status1, PBEStatusAction.Cleared); if (status1 == PBEStatus1.Asleep) { CureNightmare(ally, pkmn); } } } break; } case PBEAbility.ShedSkin: { if (pkmn.Status1 != PBEStatus1.None && GetManipulableChance(pkmn, 30)) { BroadcastAbility(pkmn, pkmn, pkmn.Ability, PBEAbilityAction.ChangedStatus); PBEStatus1 status1 = pkmn.Status1; pkmn.Status1 = PBEStatus1.None; pkmn.Status1Counter = 0; pkmn.SleepTurns = 0; BroadcastStatus1(pkmn, pkmn, status1, PBEStatusAction.Cleared); if (status1 == PBEStatus1.Asleep) { CureNightmare(pkmn, pkmn); } } break; } } switch (pkmn.Item) { case PBEItem.BlackSludge: { if (!pkmn.HasType(PBEType.Poison)) { BroadcastItem(pkmn, pkmn, pkmn.Item, PBEItemAction.Damage); DealDamage(pkmn, pkmn, pkmn.MaxHP / Settings.BlackSludgeDamageDenominator); FaintCheck(pkmn); // No need to call HealingBerryCheck() because if you are holding BlackSludge you are not holding a healing berry } else if (pkmn.HP < pkmn.MaxHP) { BroadcastItem(pkmn, pkmn, pkmn.Item, PBEItemAction.RestoredHP); HealDamage(pkmn, pkmn.MaxHP / Settings.BlackSludgeHealDenominator); } break; } case PBEItem.Leftovers: { if (pkmn.HP < pkmn.MaxHP) { BroadcastItem(pkmn, pkmn, pkmn.Item, PBEItemAction.RestoredHP); HealDamage(pkmn, pkmn.MaxHP / Settings.LeftoversHealDenominator); } break; } } } // Verified: LeechSeed before Status1/PoisonHeal foreach (PBEBattlePokemon pkmn in order) { if (pkmn.HP == 0 || !pkmn.Status2.HasFlag(PBEStatus2.LeechSeed)) { continue; } if (!pkmn.SeededTeam!.TryGetPokemon(pkmn.SeededPosition, out PBEBattlePokemon? sucker)) { continue; } BroadcastStatus2(pkmn, sucker, PBEStatus2.LeechSeed, PBEStatusAction.Damage); int restoreAmt = DealDamage(sucker, pkmn, pkmn.MaxHP / Settings.LeechSeedDenominator); // In the games, the pkmn faints after taking damage (before liquid ooze/heal) // We cannot have it faint and then still broadcast ability like the games, similar to why we can't faint before Explosion // The faint order should be maintained, though, so the correct winner can be chosen ApplyBigRoot(pkmn, ref restoreAmt); if (pkmn.Ability == PBEAbility.LiquidOoze) { BroadcastAbility(pkmn, sucker, PBEAbility.LiquidOoze, PBEAbilityAction.Damage); DealDamage(pkmn, sucker, restoreAmt); if (!FaintCheck(pkmn)) { LowHPBerryCheck(pkmn); } if (!FaintCheck(sucker)) { LowHPBerryCheck(sucker); } } else { if (!FaintCheck(pkmn)) { LowHPBerryCheck(pkmn); } HealDamage(sucker, restoreAmt); } } // Verified: Status1/PoisonHeal before Curse foreach (PBEBattlePokemon pkmn in order) { if (pkmn.HP == 0) { continue; } switch (pkmn.Status1) { case PBEStatus1.BadlyPoisoned: case PBEStatus1.Poisoned: { if (pkmn.Ability != PBEAbility.PoisonHeal) { BroadcastStatus1(pkmn, pkmn, pkmn.Status1, PBEStatusAction.Damage); int damage = pkmn.Status1 == PBEStatus1.BadlyPoisoned ? pkmn.MaxHP * pkmn.Status1Counter / Settings.ToxicDamageDenominator : pkmn.MaxHP / Settings.PoisonDamageDenominator; DealDamage(pkmn, pkmn, damage); if (!FaintCheck(pkmn)) { LowHPBerryCheck(pkmn); } } else if (pkmn.HP < pkmn.MaxHP) { BroadcastAbility(pkmn, pkmn, PBEAbility.PoisonHeal, PBEAbilityAction.RestoredHP); HealDamage(pkmn, pkmn.MaxHP / 8); } if (pkmn.Status1 == PBEStatus1.BadlyPoisoned) { pkmn.Status1Counter++; // Counter still increments if PoisonHeal exists } break; } case PBEStatus1.Burned: { BroadcastStatus1(pkmn, pkmn, pkmn.Status1, PBEStatusAction.Damage); int damage = pkmn.MaxHP / Settings.BurnDamageDenominator; if (pkmn.Ability == PBEAbility.Heatproof) { damage /= 2; } DealDamage(pkmn, pkmn, damage); if (!FaintCheck(pkmn)) { LowHPBerryCheck(pkmn); } break; } } } // Verified: Nightmare before Curse, not same loop foreach (PBEBattlePokemon pkmn in order) { if (pkmn.HP == 0 || !pkmn.Status2.HasFlag(PBEStatus2.Nightmare)) { continue; } BroadcastStatus2(pkmn, pkmn, PBEStatus2.Nightmare, PBEStatusAction.Damage); DealDamage(pkmn, pkmn, pkmn.MaxHP / 4); if (!FaintCheck(pkmn)) { LowHPBerryCheck(pkmn); } } // Verified: Curse before MagnetRise foreach (PBEBattlePokemon pkmn in order) { if (pkmn.HP == 0 || !pkmn.Status2.HasFlag(PBEStatus2.Cursed)) { continue; } BroadcastStatus2(pkmn, pkmn, PBEStatus2.Cursed, PBEStatusAction.Damage); DealDamage(pkmn, pkmn, pkmn.MaxHP / Settings.CurseDenominator); if (!FaintCheck(pkmn)) { LowHPBerryCheck(pkmn); } } // Verified: MagnetRise before Abilities/Orbs foreach (PBEBattlePokemon pkmn in order) { if (pkmn.HP == 0 || !pkmn.Status2.HasFlag(PBEStatus2.MagnetRise) || pkmn.MagnetRiseTurns == 0) { continue; } pkmn.MagnetRiseTurns--; if (pkmn.MagnetRiseTurns == 0) { BroadcastStatus2(pkmn, pkmn, PBEStatus2.MagnetRise, PBEStatusAction.Ended); } } // Verified: BadDreams/Moody/SlowStart/SpeedBoost before Orbs, but activate together foreach (PBEBattlePokemon pkmn in order) { if (pkmn.HP == 0) { continue; } // Ability before Orb switch (pkmn.Ability) { case PBEAbility.BadDreams: { foreach (PBEBattlePokemon victim in GetRuntimeSurrounding(pkmn, false, true).Where(p => p.Status1 == PBEStatus1.Asleep)) { BroadcastAbility(pkmn, victim, PBEAbility.BadDreams, PBEAbilityAction.Damage); DealDamage(pkmn, victim, pkmn.MaxHP / 8); if (!FaintCheck(victim)) { LowHPBerryCheck(victim); } } break; } case PBEAbility.Moody: { List statsThatCanGoUp = PBEDataUtils.MoodyStats.FindAll(s => pkmn.GetStatChange(s) < Settings.MaxStatChange); PBEStat? upStat = statsThatCanGoUp.Count == 0 ? null : _rand.RandomElement(statsThatCanGoUp); List statsThatCanGoDown = PBEDataUtils.MoodyStats.FindAll(s => pkmn.GetStatChange(s) > -Settings.MaxStatChange); if (upStat is not null) { statsThatCanGoDown.Remove(upStat.Value); } PBEStat? downStat = statsThatCanGoDown.Count == 0 ? null : _rand.RandomElement(statsThatCanGoDown); if (upStat is not null || downStat is not null) { BroadcastAbility(pkmn, pkmn, pkmn.Ability, PBEAbilityAction.Stats); if (upStat is not null) { ApplyStatChangeIfPossible(pkmn, pkmn, upStat.Value, +2); } if (downStat is not null) { ApplyStatChangeIfPossible(pkmn, pkmn, downStat.Value, -1); } } break; } case PBEAbility.SlowStart: { if (pkmn.SlowStart_HinderTurnsLeft > 0) { pkmn.SlowStart_HinderTurnsLeft--; if (pkmn.SlowStart_HinderTurnsLeft == 0) { BroadcastAbility(pkmn, pkmn, PBEAbility.SlowStart, PBEAbilityAction.SlowStart_Ended); } } break; } case PBEAbility.SpeedBoost: { if (pkmn.SpeedBoost_AbleToSpeedBoostThisTurn && pkmn.SpeedChange < Settings.MaxStatChange) { BroadcastAbility(pkmn, pkmn, pkmn.Ability, PBEAbilityAction.Stats); ApplyStatChangeIfPossible(pkmn, pkmn, PBEStat.Speed, +1); } break; } } // Orb switch (pkmn.Item) { case PBEItem.FlameOrb: { if (pkmn.IsBurnPossible(null, ignoreSubstitute: true, ignoreSafeguard: true) == PBEResult.Success) { pkmn.Status1 = PBEStatus1.Burned; BroadcastItem(pkmn, pkmn, pkmn.Item, PBEItemAction.Announced); BroadcastStatus1(pkmn, pkmn, PBEStatus1.Burned, PBEStatusAction.Added); } break; } case PBEItem.ToxicOrb: { if (pkmn.IsPoisonPossible(null, ignoreSubstitute: true, ignoreSafeguard: true) == PBEResult.Success) { pkmn.Status1 = PBEStatus1.BadlyPoisoned; pkmn.Status1Counter = 1; BroadcastItem(pkmn, pkmn, pkmn.Item, PBEItemAction.Announced); BroadcastStatus1(pkmn, pkmn, PBEStatus1.BadlyPoisoned, PBEStatusAction.Added); } break; } } } } public bool ShouldDoWeatherEffects() { // If HP is needed to be above 0, use HPPercentage so clients can continue to use this // However, I see no instance of this getting called where an ActiveBattler has 0 hp return ActiveBattlers.FindIndex(p => p.Ability == PBEAbility.AirLock || p.Ability == PBEAbility.CloudNine) == -1; } public bool WillLeafGuardActivate() { return ShouldDoWeatherEffects() && Weather == PBEWeather.HarshSunlight; } private void FleeCheck() { foreach (PBETrainer trainer in Trainers) { if (!trainer.RequestedFlee) { continue; } if (trainer.IsWild) { PBEBattlePokemon wildPkmn = trainer.ActiveBattlersOrdered.First(); wildPkmn.TurnAction = new PBETurnAction(wildPkmn); // Convert into a WildFlee turn action for the first Pokémon trainer.RequestedFlee = false; continue; } PBEBattlePokemon? pkmn = trainer.ActiveBattlersOrdered.FirstOrDefault(); if (pkmn is not null) { // Verified: RunAway before SmokeBall if (pkmn.Ability == PBEAbility.RunAway) { BroadcastAbility(pkmn, pkmn, PBEAbility.RunAway, PBEAbilityAction.Announced); SetEscaped(pkmn); return; } if (pkmn.Item == PBEItem.SmokeBall) { BroadcastItem(pkmn, pkmn, PBEItem.SmokeBall, PBEItemAction.Announced); SetEscaped(pkmn); return; } } else { pkmn = trainer.Party[0]; // Use the first fainted Pokémon's speed if there's no active battler } // TODO: I'm using the gen 3/4 formula below. // TODO: Figure out the gen 5 formula, as well as what to use in a double wild battle int a = pkmn.Speed; int b = (int)trainer.Team.OpposingTeam.ActiveBattlers.Average(p => p.Speed); int c = ++trainer.Team.NumTimesTriedToFlee; // Increment even if guaranteed bool success; if (a > b) { success = true; } else { int f = ((a * 128 / b) + (30 * c)) % 256; success = _rand.RandomInt(0, 255) < f; } if (success) { SetEscaped(pkmn); return; } else { BroadcastFleeFailed(pkmn); } trainer.RequestedFlee = false; } } private void WildFleeCheck(PBEBattlePokemon pkmn) { // TODO: Trapping effects SetEscaped(pkmn); } private void CalcEXP(PBEBattlePokemon loser) { IPBEPokemonData loserPData = PBEDataProvider.Instance.GetPokemonData(loser); float modTrainer = loser.IsWild ? 1 : 1.5f; int expYield = loserPData.BaseEXPYield; int levelLoser = loser.Level; float modPassPower = PBEDataProvider.Instance.GetEXPModifier(this); int amtParticipated = loser.EXPPokemon.Count(pk => pk.Trainer.GainsEXP && pk.HP > 0); int amtEXPShare = loser.EXPPokemon.Count(pk => pk.Trainer.GainsEXP && pk.Item == PBEItem.ExpShare); foreach (PBEBattlePokemon victor in loser.EXPPokemon) { if (!victor.Trainer.GainsEXP || victor.HP == 0 || victor.Level >= Settings.MaxLevel) { continue; } int levelVictor = victor.Level; float modParticipators; if (amtEXPShare == 0) { modParticipators = amtParticipated; } else if (victor.Item == PBEItem.ExpShare) { modParticipators = 2 * amtEXPShare; } else { modParticipators = 2 * amtParticipated; } float modTraded = PBEDataProvider.Instance.GetEXPTradeModifier(victor); float modLuckyEgg = victor.Item == PBEItem.LuckyEgg ? 1.5f : 1; float result1H = modTrainer * expYield * levelLoser; float result1L = 5 * modParticipators; float result1 = result1H / result1L; float result2H = MathF.Pow(2 * levelLoser + 10, 2.5f); float result2L = MathF.Pow(levelLoser + levelVictor + 10, 2.5f); float result2 = result2H / result2L; float combined = result1 * result2 + 1; float final = combined * modTraded * modLuckyEgg * modPassPower; GiveEXP(victor, (uint)final); } } private void GiveEXP(PBEBattlePokemon victor, uint amount) { // TODO: Should we allow remote battles with learning moves? No packets right now BroadcastPkmnEXPEarned(victor, amount); PBEGrowthRate growthRate = PBEDataProvider.Instance.GetPokemonData(victor).GrowthRate; top: uint oldEXP = victor.EXP; uint nextLevelAmt = PBEDataProvider.Instance.GetEXPRequired(growthRate, (byte)(victor.Level + 1)); if (oldEXP + amount >= nextLevelAmt) { victor.EXP = nextLevelAmt; BroadcastPkmnEXPChanged(victor, oldEXP); victor.Level++; victor.SetStats(true, false); BroadcastPkmnLevelChanged(victor); // BUG: PBEStatus2.PowerTrick is not cleared when leveling up, even though the stats are replaced (meaning it can still be baton passed) if (Settings.BugFix && victor.Status2.HasFlag(PBEStatus2.PowerTrick)) { BroadcastStatus2(victor, victor, PBEStatus2.PowerTrick, PBEStatusAction.Ended); } if (victor.Level == Settings.MaxLevel) { return; } uint grewBy = nextLevelAmt - oldEXP; amount -= grewBy; if (amount > 0) { goto top; // Keep gaining and leveling } } else { victor.EXP = oldEXP + amount; BroadcastPkmnEXPChanged(victor, oldEXP); } } private static float PokedexCountTable(int count, float g600, float g450, float g300, float g150, float g30, float ge0) { if (count > 600) { return g600; } if (count > 450) { return g450; } if (count > 300) { return g300; } if (count > 150) { return g150; } if (count > 30) { return g30; } return ge0; } private void GenerateCapture(PBEBattlePokemon user, PBEBattlePokemon wildPkmn, PBEItem ball, out byte shakes, out bool success, out bool isCriticalCapture) { if (PBEDataProvider.Instance.IsGuaranteedCapture(this, wildPkmn.OriginalSpecies, wildPkmn.RevertForm)) { shakes = 3; success = true; isCriticalCapture = false; return; } IPBEPokemonData pData = PBEDataProvider.Instance.GetPokemonData(wildPkmn.OriginalSpecies, wildPkmn.RevertForm); float rate = pData.CatchRate * PBEDataProvider.Instance.GetCatchRateModifier(this); float bonusBall = 1; switch (ball) { case PBEItem.GreatBall: case PBEItem.SafariBall: case PBEItem.SportBall: bonusBall = 1.5f; break; case PBEItem.UltraBall: bonusBall = 2; break; case PBEItem.MasterBall: case PBEItem.ParkBall: bonusBall = 255; break; case PBEItem.FastBall: { if (wildPkmn.Speed >= 100) { rate *= 4; } break; } case PBEItem.LevelBall: { int wl = wildPkmn.Level; int ul = user.Level; if (ul > wl * 4) { rate *= 8; } else if (ul > wl * 2) { rate *= 4; } else if (ul > wl) { rate *= 2; } break; } case PBEItem.LureBall: { if (PBEDataProvider.Instance.IsFishing(this)) { rate *= 3; } break; } case PBEItem.HeavyBall: { float weight = pData.Weight; if (weight >= 409.6f) { rate += 40; } else if (weight >= 307.2f) { rate += 30; } else if (weight >= 204.8f) { rate += 20; } else { rate -= 20; } break; } case PBEItem.LoveBall: { if (user.Species == wildPkmn.Species && user.Gender.IsOppositeGender(wildPkmn.Gender)) { rate *= 8; } break; } case PBEItem.MoonBall: { if (PBEDataProvider.Instance.IsMoonBallFamily(wildPkmn.OriginalSpecies, wildPkmn.RevertForm)) { rate *= 4; } break; } case PBEItem.NetBall: { if (wildPkmn.HasType(PBEType.Bug) || wildPkmn.HasType(PBEType.Water)) { bonusBall = 3; } break; } case PBEItem.NestBall: { bonusBall = Math.Max(1, (41 - wildPkmn.Level) / 10); break; } case PBEItem.RepeatBall: { if (PBEDataProvider.Instance.IsRepeatBallSpecies(wildPkmn.OriginalSpecies)) { bonusBall = 3; } break; } case PBEItem.TimerBall: { bonusBall = Math.Min(4, 1 + (TurnNumber * 0.3f)); break; } case PBEItem.DiveBall: { if (PBEDataProvider.Instance.IsFishing(this) || PBEDataProvider.Instance.IsSurfing(this) || PBEDataProvider.Instance.IsUnderwater(this)) { bonusBall = 3.5f; } break; } case PBEItem.DuskBall: { if (PBEDataProvider.Instance.IsDuskBallSetting(this)) { bonusBall = 3.5f; } break; } case PBEItem.QuickBall: { if (TurnNumber == 1) { bonusBall = 5; } break; } } rate = Math.Clamp(rate, 1, 255); float bonusStatus; switch (wildPkmn.Status1) { case PBEStatus1.Asleep: case PBEStatus1.Frozen: bonusStatus = 2.5f; break; case PBEStatus1.None: bonusStatus = 1; break; default: bonusStatus = 1.5f; break; } float pkmnFactor = (3 * wildPkmn.MaxHP) - (2 * wildPkmn.HP); int pkmnCaught = PBEDataProvider.Instance.GetSpeciesCaught(); if (PBEDataProvider.Instance.IsDarkGrass(this)) { pkmnFactor *= PokedexCountTable(pkmnCaught, 1, 0.9f, 0.8f, 0.7f, 0.5f, 0.3f); } float a = pkmnFactor * rate * bonusBall / (3 * wildPkmn.MaxHP) * bonusStatus; float c = a * PokedexCountTable(pkmnCaught, 2.5f, 2, 1.5f, 1, 0.5f, 0); // Critical capture modifier isCriticalCapture = _rand.RandomInt(0, 0xFF) < c / 6; byte numShakes = isCriticalCapture ? (byte)1 : (byte)3; if (a >= 0xFF) { shakes = numShakes; // Skip shake checks success = true; return; } float b = 0x10000 / MathF.Sqrt(MathF.Sqrt(0xFF / a)); for (shakes = 0; shakes < numShakes; shakes++) { if (_rand.RandomInt(0, 0xFFFF) >= b) { break; // Shake check fails } } success = shakes == numShakes; if (shakes == 2) { shakes = 3; // If there are only 2 shakes and a failure, shake three times and still fail } } private void UseItem(PBEBattlePokemon user, PBEItem item) { BroadcastItemTurn(user, item, PBEItemTurnAction.Attempt); if (PBEDataUtils.AllBalls.Contains(item)) { user.Trainer.Inventory.Remove(item); if (BattleType != PBEBattleType.Wild) { goto fail; } PBEBattlePokemon wildPkmn = user.Team.OpposingTeam.ActiveBattlers.Single(); GenerateCapture(user, wildPkmn, item, out byte numShakes, out bool success, out bool critical); BroadcastCapture(wildPkmn, item, numShakes, success, critical); if (success) { wildPkmn.CaughtBall = wildPkmn.KnownCaughtBall = item; BattleResult = PBEBattleResult.WildCapture; } return; } else { switch (item) { case PBEItem.FluffyTail: case PBEItem.PokeDoll: case PBEItem.PokeToy: { user.Trainer.Inventory.Remove(item); if (BattleType == PBEBattleType.Wild) { SetEscaped(user); return; } goto fail; } } } fail: BroadcastItemTurn(user, item, PBEItemTurnAction.NoEffect); } private void UseMove(PBEBattlePokemon user, PBEMove move, PBETurnTarget requestedTargets) { // Cancel the semi-invulnerable move if the user is affected by its status1 if (!_calledFromOtherMove && PreMoveStatusCheck(user, move)) { if (user.Status2.HasFlag(PBEStatus2.Airborne)) { BroadcastMoveLock_Temporary(user, PBEMove.None, PBETurnTarget.None); BroadcastStatus2(user, user, PBEStatus2.Airborne, PBEStatusAction.Ended); } if (user.Status2.HasFlag(PBEStatus2.ShadowForce)) { BroadcastMoveLock_Temporary(user, PBEMove.None, PBETurnTarget.None); BroadcastStatus2(user, user, PBEStatus2.ShadowForce, PBEStatusAction.Ended); } if (user.Status2.HasFlag(PBEStatus2.Underground)) { BroadcastMoveLock_Temporary(user, PBEMove.None, PBETurnTarget.None); BroadcastStatus2(user, user, PBEStatus2.Underground, PBEStatusAction.Ended); } if (user.Status2.HasFlag(PBEStatus2.Underwater)) { BroadcastMoveLock_Temporary(user, PBEMove.None, PBETurnTarget.None); BroadcastStatus2(user, user, PBEStatus2.Underwater, PBEStatusAction.Ended); } return; } IPBEMoveData mData = PBEDataProvider.Instance.GetMoveData(move); PBEBattlePokemon[] targets = GetRuntimeTargets(user, requestedTargets, user.GetMoveTargets(mData) == PBEMoveTarget.SingleNotSelf, _rand); switch (mData.Effect) { case PBEMoveEffect.Acrobatics: case PBEMoveEffect.Brine: case PBEMoveEffect.ChipAway: case PBEMoveEffect.CrushGrip: case PBEMoveEffect.Eruption: case PBEMoveEffect.Facade: case PBEMoveEffect.Flail: case PBEMoveEffect.FoulPlay: case PBEMoveEffect.Frustration: case PBEMoveEffect.GrassKnot: case PBEMoveEffect.HeatCrash: case PBEMoveEffect.Hex: case PBEMoveEffect.HiddenPower: case PBEMoveEffect.Hit: case PBEMoveEffect.Judgment: case PBEMoveEffect.Magnitude: case PBEMoveEffect.Payback: case PBEMoveEffect.Psyshock: case PBEMoveEffect.Punishment: case PBEMoveEffect.Retaliate: case PBEMoveEffect.Return: case PBEMoveEffect.StoredPower: case PBEMoveEffect.TechnoBlast: case PBEMoveEffect.Venoshock: case PBEMoveEffect.WeatherBall: Ef_Hit(user, targets, move, mData); break; case PBEMoveEffect.Attract: Ef_TryForceStatus2(user, targets, move, mData, PBEStatus2.Infatuated); break; case PBEMoveEffect.BellyDrum: Ef_BellyDrum(user, targets, move, mData); break; case PBEMoveEffect.Bounce: Ef_Bounce(user, targets, move, mData, requestedTargets); break; case PBEMoveEffect.BrickBreak: Ef_BrickBreak(user, targets, move, mData); break; case PBEMoveEffect.Burn: Ef_TryForceStatus1(user, targets, move, mData, PBEStatus1.Burned); break; case PBEMoveEffect.Camouflage: Ef_Camouflage(user, targets, move, mData); break; case PBEMoveEffect.ChangeTarget_ACC: Ef_ChangeTargetStats(user, targets, move, mData, new[] { (PBEStat.Accuracy, mData.EffectParam) }); break; case PBEMoveEffect.ChangeTarget_ATK: Ef_ChangeTargetStats(user, targets, move, mData, new[] { (PBEStat.Attack, mData.EffectParam) }); break; case PBEMoveEffect.ChangeTarget_DEF: Ef_ChangeTargetStats(user, targets, move, mData, new[] { (PBEStat.Defense, mData.EffectParam) }); break; case PBEMoveEffect.ChangeTarget_EVA: case PBEMoveEffect.Minimize: Ef_ChangeTargetStats(user, targets, move, mData, new[] { (PBEStat.Evasion, mData.EffectParam) }); break; case PBEMoveEffect.ChangeTarget_SPATK: Ef_ChangeTargetStats(user, targets, move, mData, new[] { (PBEStat.SpAttack, mData.EffectParam) }); break; case PBEMoveEffect.ChangeTarget_SPATK__IfAttractionPossible: Ef_ChangeTargetStats(user, targets, move, mData, new[] { (PBEStat.SpAttack, mData.EffectParam) }, requireAttraction: true); break; case PBEMoveEffect.ChangeTarget_SPDEF: Ef_ChangeTargetStats(user, targets, move, mData, new[] { (PBEStat.SpDefense, mData.EffectParam) }); break; case PBEMoveEffect.ChangeTarget_SPE: Ef_ChangeTargetStats(user, targets, move, mData, new[] { (PBEStat.Speed, mData.EffectParam) }); break; case PBEMoveEffect.Confuse: Ef_TryForceStatus2(user, targets, move, mData, PBEStatus2.Confused); break; case PBEMoveEffect.Conversion: Ef_Conversion(user, targets, move, mData); break; case PBEMoveEffect.Curse: Ef_Curse(user, targets, move, mData); break; case PBEMoveEffect.Dig: SemiInvulnerableChargeMove(user, targets, move, mData, requestedTargets, PBEStatus2.Underground); break; case PBEMoveEffect.Dive: SemiInvulnerableChargeMove(user, targets, move, mData, requestedTargets, PBEStatus2.Underwater); break; case PBEMoveEffect.Endeavor: Ef_Endeavor(user, targets, move, mData); break; case PBEMoveEffect.Entrainment: Ef_Entrainment(user, targets, move, mData); break; case PBEMoveEffect.Feint: Ef_Feint(user, targets, move, mData); break; case PBEMoveEffect.FinalGambit: Ef_FinalGambit(user, targets, move, mData); break; case PBEMoveEffect.Flatter: Ef_Flatter(user, targets, move, mData); break; case PBEMoveEffect.Fly: SemiInvulnerableChargeMove(user, targets, move, mData, requestedTargets, PBEStatus2.Airborne); break; case PBEMoveEffect.FocusEnergy: Ef_TryForceStatus2(user, targets, move, mData, PBEStatus2.Pumped); break; case PBEMoveEffect.Foresight: Ef_TryForceStatus2(user, targets, move, mData, PBEStatus2.Identified); break; case PBEMoveEffect.GastroAcid: Ef_SetOtherAbility(user, targets, move, mData, PBEAbility.None, false); break; case PBEMoveEffect.Growth: Ef_Growth(user, targets, move, mData); break; case PBEMoveEffect.Hail: Ef_TryForceWeather(user, move, mData, PBEWeather.Hailstorm); break; case PBEMoveEffect.Haze: Ef_Haze(user, targets, move, mData); break; case PBEMoveEffect.HelpingHand: Ef_HelpingHand(user, targets, move, mData); break; case PBEMoveEffect.Hit__2Times: Ef_MultiHit(user, targets, move, mData, 2); break; case PBEMoveEffect.Hit__2Times__MaybePoison: Ef_MultiHit(user, targets, move, mData, 2, status1: PBEStatus1.Poisoned, chanceToInflictStatus1: mData.EffectParam); break; case PBEMoveEffect.Hit__2To5Times: Ef_MultiHit_2To5(user, targets, move, mData); break; case PBEMoveEffect.Hit__MaybeBurn: Ef_Hit(user, targets, move, mData, status1: PBEStatus1.Burned, chanceToInflictStatus1: mData.EffectParam); break; case PBEMoveEffect.Hit__MaybeBurn__10PercentFlinch: Ef_Hit(user, targets, move, mData, status1: PBEStatus1.Burned, chanceToInflictStatus1: mData.EffectParam, status2: PBEStatus2.Flinching, chanceToInflictStatus2: 10); break; case PBEMoveEffect.Hit__MaybeBurnFreezeParalyze: Ef_Hit__MaybeBurnFreezeParalyze(user, targets, move, mData); break; case PBEMoveEffect.Hit__MaybeConfuse: Ef_Hit(user, targets, move, mData, status2: PBEStatus2.Confused, chanceToInflictStatus2: mData.EffectParam); break; case PBEMoveEffect.Hit__MaybeFlinch: Ef_Hit(user, targets, move, mData, status2: PBEStatus2.Flinching, chanceToInflictStatus2: mData.EffectParam); break; case PBEMoveEffect.Hit__MaybeFreeze: Ef_Hit(user, targets, move, mData, status1: PBEStatus1.Frozen, chanceToInflictStatus1: mData.EffectParam); break; case PBEMoveEffect.Hit__MaybeFreeze__10PercentFlinch: Ef_Hit(user, targets, move, mData, status1: PBEStatus1.Frozen, chanceToInflictStatus1: mData.EffectParam, status2: PBEStatus2.Flinching, chanceToInflictStatus2: 10); break; case PBEMoveEffect.Hit__MaybeLowerTarget_ACC_By1: Ef_Hit__MaybeChangeTargetStats(user, targets, move, mData, new[] { (PBEStat.Accuracy, -1) }, mData.EffectParam); break; case PBEMoveEffect.Hit__MaybeLowerTarget_ATK_By1: Ef_Hit__MaybeChangeTargetStats(user, targets, move, mData, new[] { (PBEStat.Attack, -1) }, mData.EffectParam); break; case PBEMoveEffect.Hit__MaybeLowerTarget_DEF_By1: Ef_Hit__MaybeChangeTargetStats(user, targets, move, mData, new[] { (PBEStat.Defense, -1) }, mData.EffectParam); break; case PBEMoveEffect.Hit__MaybeLowerTarget_SPATK_By1: Ef_Hit__MaybeChangeTargetStats(user, targets, move, mData, new[] { (PBEStat.SpAttack, -1) }, mData.EffectParam); break; case PBEMoveEffect.Hit__MaybeLowerTarget_SPDEF_By1: Ef_Hit__MaybeChangeTargetStats(user, targets, move, mData, new[] { (PBEStat.SpDefense, -1) }, mData.EffectParam); break; case PBEMoveEffect.Hit__MaybeLowerTarget_SPDEF_By2: Ef_Hit__MaybeChangeTargetStats(user, targets, move, mData, new[] { (PBEStat.SpDefense, -2) }, mData.EffectParam); break; case PBEMoveEffect.Hit__MaybeLowerTarget_SPE_By1: Ef_Hit__MaybeChangeTargetStats(user, targets, move, mData, new[] { (PBEStat.Speed, -1) }, mData.EffectParam); break; case PBEMoveEffect.Hit__MaybeParalyze: Ef_Hit(user, targets, move, mData, status1: PBEStatus1.Paralyzed, chanceToInflictStatus1: mData.EffectParam); break; case PBEMoveEffect.Hit__MaybeParalyze__10PercentFlinch: Ef_Hit(user, targets, move, mData, status1: PBEStatus1.Paralyzed, chanceToInflictStatus1: mData.EffectParam, status2: PBEStatus2.Flinching, chanceToInflictStatus2: 10); break; case PBEMoveEffect.Hit__MaybePoison: Ef_Hit(user, targets, move, mData, status1: PBEStatus1.Poisoned, chanceToInflictStatus1: mData.EffectParam); break; case PBEMoveEffect.Hit__MaybeLowerUser_ATK_DEF_By1: Ef_Hit__MaybeChangeUserStats(user, targets, move, mData, new[] { (PBEStat.Attack, -1), (PBEStat.Defense, -1) }, mData.EffectParam); break; case PBEMoveEffect.Hit__MaybeLowerUser_DEF_SPDEF_By1: Ef_Hit__MaybeChangeUserStats(user, targets, move, mData, new[] { (PBEStat.Defense, -1), (PBEStat.SpDefense, -1) }, mData.EffectParam); break; case PBEMoveEffect.Hit__MaybeLowerUser_SPATK_By2: Ef_Hit__MaybeChangeUserStats(user, targets, move, mData, new[] { (PBEStat.SpAttack, -2) }, mData.EffectParam); break; case PBEMoveEffect.Hit__MaybeLowerUser_SPE_By1: Ef_Hit__MaybeChangeUserStats(user, targets, move, mData, new[] { (PBEStat.Speed, -1) }, mData.EffectParam); break; case PBEMoveEffect.Hit__MaybeLowerUser_SPE_DEF_SPDEF_By1: Ef_Hit__MaybeChangeUserStats(user, targets, move, mData, new[] { (PBEStat.Speed, -1), (PBEStat.Defense, -1), (PBEStat.SpDefense, -1) }, mData.EffectParam); break; case PBEMoveEffect.Hit__MaybeRaiseUser_ATK_By1: Ef_Hit__MaybeChangeUserStats(user, targets, move, mData, new[] { (PBEStat.Attack, +1) }, mData.EffectParam); break; case PBEMoveEffect.Hit__MaybeRaiseUser_ATK_DEF_SPATK_SPDEF_SPE_By1: Ef_Hit__MaybeChangeUserStats(user, targets, move, mData, new[] { (PBEStat.Attack, +1), (PBEStat.Defense, +1), (PBEStat.SpAttack, +1), (PBEStat.SpDefense, +1), (PBEStat.Speed, +1) }, mData.EffectParam); break; case PBEMoveEffect.Hit__MaybeRaiseUser_DEF_By1: Ef_Hit__MaybeChangeUserStats(user, targets, move, mData, new[] { (PBEStat.Defense, +1) }, mData.EffectParam); break; case PBEMoveEffect.Hit__MaybeRaiseUser_SPATK_By1: Ef_Hit__MaybeChangeUserStats(user, targets, move, mData, new[] { (PBEStat.SpAttack, +1) }, mData.EffectParam); break; case PBEMoveEffect.Hit__MaybeRaiseUser_SPE_By1: Ef_Hit__MaybeChangeUserStats(user, targets, move, mData, new[] { (PBEStat.Speed, +1) }, mData.EffectParam); break; case PBEMoveEffect.Hit__MaybeToxic: Ef_Hit(user, targets, move, mData, status1: PBEStatus1.BadlyPoisoned, chanceToInflictStatus1: mData.EffectParam); break; case PBEMoveEffect.HPDrain: Ef_HPDrain(user, targets, move, mData); break; case PBEMoveEffect.HPDrain__RequireSleep: Ef_HPDrain(user, targets, move, mData, requireSleep: true); break; case PBEMoveEffect.LeechSeed: Ef_TryForceStatus2(user, targets, move, mData, PBEStatus2.LeechSeed); break; case PBEMoveEffect.LightScreen: Ef_TryForceTeamStatus(user, move, mData, PBETeamStatus.LightScreen); break; case PBEMoveEffect.LockOn: Ef_TryForceStatus2(user, targets, move, mData, PBEStatus2.LockOn); break; case PBEMoveEffect.LowerTarget_ATK_DEF_By1: Ef_ChangeTargetStats(user, targets, move, mData, new[] { (PBEStat.Attack, -1), (PBEStat.Defense, -1) }); break; case PBEMoveEffect.LowerTarget_DEF_SPDEF_By1_Raise_ATK_SPATK_SPE_By2: Ef_ChangeTargetStats(user, targets, move, mData, new[] { (PBEStat.Defense, -1), (PBEStat.SpDefense, -1), (PBEStat.Attack, +2), (PBEStat.SpAttack, +2), (PBEStat.Speed, +2) }); break; case PBEMoveEffect.LuckyChant: Ef_TryForceTeamStatus(user, move, mData, PBETeamStatus.LuckyChant); break; case PBEMoveEffect.MagnetRise: Ef_TryForceStatus2(user, targets, move, mData, PBEStatus2.MagnetRise); break; case PBEMoveEffect.Metronome: Ef_Metronome(user, move, mData); break; case PBEMoveEffect.MiracleEye: Ef_TryForceStatus2(user, targets, move, mData, PBEStatus2.MiracleEye); break; case PBEMoveEffect.Moonlight: Ef_Moonlight(user, targets, move, mData); break; case PBEMoveEffect.Nightmare: Ef_TryForceStatus2(user, targets, move, mData, PBEStatus2.Nightmare); break; case PBEMoveEffect.Nothing: Ef_Nothing(user, move, mData); break; case PBEMoveEffect.OneHitKnockout: Ef_OneHitKnockout(user, targets, move, mData); break; case PBEMoveEffect.PainSplit: Ef_PainSplit(user, targets, move, mData); break; case PBEMoveEffect.Paralyze: Ef_TryForceStatus1(user, targets, move, mData, PBEStatus1.Paralyzed); break; case PBEMoveEffect.PayDay: Ef_PayDay(user, targets, move, mData); break; case PBEMoveEffect.Poison: Ef_TryForceStatus1(user, targets, move, mData, PBEStatus1.Poisoned); break; case PBEMoveEffect.PowerTrick: Ef_TryForceStatus2(user, targets, move, mData, PBEStatus2.PowerTrick); break; case PBEMoveEffect.Protect: Ef_TryForceStatus2(user, targets, move, mData, PBEStatus2.Protected); break; case PBEMoveEffect.PsychUp: Ef_PsychUp(user, targets, move, mData); break; case PBEMoveEffect.Psywave: Ef_Psywave(user, targets, move, mData); break; case PBEMoveEffect.QuickGuard: Ef_TryForceTeamStatus(user, move, mData, PBETeamStatus.QuickGuard); break; case PBEMoveEffect.RainDance: Ef_TryForceWeather(user, move, mData, PBEWeather.Rain); break; case PBEMoveEffect.RaiseTarget_ATK_ACC_By1: Ef_ChangeTargetStats(user, targets, move, mData, new[] { (PBEStat.Attack, +1), (PBEStat.Accuracy, +1) }); break; case PBEMoveEffect.RaiseTarget_ATK_DEF_By1: Ef_ChangeTargetStats(user, targets, move, mData, new[] { (PBEStat.Attack, +1), (PBEStat.Defense, +1) }); break; case PBEMoveEffect.RaiseTarget_ATK_DEF_ACC_By1: Ef_ChangeTargetStats(user, targets, move, mData, new[] { (PBEStat.Attack, +1), (PBEStat.Defense, +1), (PBEStat.Accuracy, +1) }); break; case PBEMoveEffect.RaiseTarget_ATK_SPATK_By1: Ef_ChangeTargetStats(user, targets, move, mData, new[] { (PBEStat.Attack, +1), (PBEStat.SpAttack, +1) }); break; case PBEMoveEffect.RaiseTarget_ATK_SPE_By1: Ef_ChangeTargetStats(user, targets, move, mData, new[] { (PBEStat.Attack, +1), (PBEStat.Speed, +1) }); break; case PBEMoveEffect.RaiseTarget_DEF_SPDEF_By1: Ef_ChangeTargetStats(user, targets, move, mData, new[] { (PBEStat.Defense, +1), (PBEStat.SpDefense, +1) }); break; case PBEMoveEffect.RaiseTarget_SPATK_SPDEF_By1: Ef_ChangeTargetStats(user, targets, move, mData, new[] { (PBEStat.SpAttack, +1), (PBEStat.SpDefense, +1) }); break; case PBEMoveEffect.RaiseTarget_SPATK_SPDEF_SPE_By1: Ef_ChangeTargetStats(user, targets, move, mData, new[] { (PBEStat.SpAttack, +1), (PBEStat.SpDefense, +1), (PBEStat.Speed, +1) }); break; case PBEMoveEffect.RaiseTarget_SPE_By2_ATK_By1: Ef_ChangeTargetStats(user, targets, move, mData, new[] { (PBEStat.Speed, +2), (PBEStat.Attack, +1) }); break; case PBEMoveEffect.Recoil: Ef_Recoil(user, targets, move, mData); break; case PBEMoveEffect.Recoil__10PercentBurn: Ef_Recoil(user, targets, move, mData, status1: PBEStatus1.Burned, chanceToInflictStatus1: 10); break; case PBEMoveEffect.Recoil__10PercentParalyze: Ef_Recoil(user, targets, move, mData, status1: PBEStatus1.Paralyzed, chanceToInflictStatus1: 10); break; case PBEMoveEffect.Reflect: Ef_TryForceTeamStatus(user, move, mData, PBETeamStatus.Reflect); break; case PBEMoveEffect.ReflectType: Ef_ReflectType(user, targets, move, mData); break; case PBEMoveEffect.Refresh: Ef_Refresh(user, targets, move, mData); break; case PBEMoveEffect.Rest: Ef_Rest(user, move, mData); break; case PBEMoveEffect.RestoreTargetHP: Ef_RestoreTargetHP(user, targets, move, mData); break; case PBEMoveEffect.RolePlay: Ef_RolePlay(user, targets, move, mData); break; case PBEMoveEffect.Roost: Ef_Roost(user, targets, move, mData); break; case PBEMoveEffect.Safeguard: Ef_TryForceTeamStatus(user, move, mData, PBETeamStatus.Safeguard); break; case PBEMoveEffect.Sandstorm: Ef_TryForceWeather(user, move, mData, PBEWeather.Sandstorm); break; case PBEMoveEffect.SecretPower: Ef_SecretPower(user, targets, move, mData); break; case PBEMoveEffect.SeismicToss: Ef_SeismicToss(user, targets, move, mData); break; case PBEMoveEffect.Selfdestruct: Ef_Selfdestruct(user, targets, move, mData); break; case PBEMoveEffect.SetDamage: Ef_SetDamage(user, targets, move, mData); break; case PBEMoveEffect.ShadowForce: Ef_ShadowForce(user, targets, move, mData, requestedTargets); break; case PBEMoveEffect.SimpleBeam: Ef_SetOtherAbility(user, targets, move, mData, PBEAbility.Simple, true); break; case PBEMoveEffect.Sleep: Ef_TryForceStatus1(user, targets, move, mData, PBEStatus1.Asleep); break; case PBEMoveEffect.SmellingSalt: Ef_SmellingSalt(user, targets, move, mData); break; case PBEMoveEffect.Snore: Ef_Snore(user, targets, move, mData); break; case PBEMoveEffect.Soak: Ef_Soak(user, targets, move, mData); break; case PBEMoveEffect.Spikes: Ef_TryForceTeamStatus(user, move, mData, PBETeamStatus.Spikes); break; case PBEMoveEffect.StealthRock: Ef_TryForceTeamStatus(user, move, mData, PBETeamStatus.StealthRock); break; case PBEMoveEffect.Struggle: Ef_Struggle(user, targets, move, mData); break; case PBEMoveEffect.Substitute: Ef_TryForceStatus2(user, targets, move, mData, PBEStatus2.Substitute); break; case PBEMoveEffect.SuckerPunch: Ef_SuckerPunch(user, targets, move, mData); break; case PBEMoveEffect.SunnyDay: Ef_TryForceWeather(user, move, mData, PBEWeather.HarshSunlight); break; case PBEMoveEffect.SuperFang: Ef_SuperFang(user, targets, move, mData); break; case PBEMoveEffect.Swagger: Ef_Swagger(user, targets, move, mData); break; case PBEMoveEffect.Tailwind: Ef_TryForceTeamStatus(user, move, mData, PBETeamStatus.Tailwind); break; case PBEMoveEffect.Teleport: Ef_Teleport(user, move, mData); break; case PBEMoveEffect.ThunderWave: Ef_TryForceStatus1(user, targets, move, mData, PBEStatus1.Paralyzed, thunderWave: true); break; case PBEMoveEffect.Toxic: Ef_TryForceStatus1(user, targets, move, mData, PBEStatus1.BadlyPoisoned); break; case PBEMoveEffect.ToxicSpikes: Ef_TryForceTeamStatus(user, move, mData, PBETeamStatus.ToxicSpikes); break; case PBEMoveEffect.Transform: Ef_TryForceStatus2(user, targets, move, mData, PBEStatus2.Transformed); break; case PBEMoveEffect.TrickRoom: Ef_TryForceBattleStatus(user, move, mData, PBEBattleStatus.TrickRoom); break; case PBEMoveEffect.WakeUpSlap: Ef_WakeUpSlap(user, targets, move, mData); break; case PBEMoveEffect.Whirlwind: Ef_Whirlwind(user, targets, move, mData); break; case PBEMoveEffect.WideGuard: Ef_TryForceTeamStatus(user, move, mData, PBETeamStatus.WideGuard); break; case PBEMoveEffect.WorrySeed: Ef_SetOtherAbility(user, targets, move, mData, PBEAbility.Insomnia, true); break; default: throw new InvalidDataException(nameof(mData.Effect)); } } private bool PreMoveStatusCheck(PBEBattlePokemon user, PBEMove move) { IPBEMoveData mData = PBEDataProvider.Instance.GetMoveData(move); // Verified: Sleep and Freeze don't interact with Flinch unless they come out of the status // Sleep causes Confusion, Flinch, and Infatuation to activate if the user is trying to use Snore if (user.Status1 == PBEStatus1.Asleep) { user.Status1Counter++; if (user.Status1Counter > user.SleepTurns) { user.Status1 = PBEStatus1.None; user.Status1Counter = 0; user.SleepTurns = 0; BroadcastStatus1(user, user, PBEStatus1.Asleep, PBEStatusAction.Ended); CureNightmare(user, user); } else if (mData.Effect != PBEMoveEffect.Snore) { BroadcastStatus1(user, user, PBEStatus1.Asleep, PBEStatusAction.CausedImmobility); return true; } } else if (user.Status1 == PBEStatus1.Frozen) { if (mData.Flags.HasFlag(PBEMoveFlag.DefrostsUser) || _rand.RandomBool(20, 100)) { user.Status1 = PBEStatus1.None; BroadcastStatus1(user, user, PBEStatus1.Frozen, PBEStatusAction.Ended); } else { BroadcastStatus1(user, user, PBEStatus1.Frozen, PBEStatusAction.CausedImmobility); return true; } } // Verified: Flinch before Confusion, Infatuation, and Paralysis can do anything if (user.Status2.HasFlag(PBEStatus2.Flinching)) { BroadcastStatus2(user, user, PBEStatus2.Flinching, PBEStatusAction.CausedImmobility); if (user.Ability == PBEAbility.Steadfast && user.SpeedChange < Settings.MaxStatChange) { BroadcastAbility(user, user, PBEAbility.Steadfast, PBEAbilityAction.Stats); ApplyStatChangeIfPossible(user, user, PBEStat.Speed, +1); } return true; } // Verified: Confusion before Infatuation and Paralysis if (user.Status2.HasFlag(PBEStatus2.Confused)) { user.ConfusionCounter++; if (user.ConfusionCounter > user.ConfusionTurns) { user.ConfusionCounter = 0; user.ConfusionTurns = 0; BroadcastStatus2(user, user, PBEStatus2.Confused, PBEStatusAction.Ended); } else { BroadcastStatus2(user, user, PBEStatus2.Confused, PBEStatusAction.Announced); if (_rand.RandomBool(50, 100)) { int damage = CalculateConfusionDamage(user); DealDamage(user, user, damage, ignoreSturdy: false); BroadcastStatus2(user, user, PBEStatus2.Confused, PBEStatusAction.Damage); // BUG: Confusion damage does not activate these items if (!FaintCheck(user) && Settings.BugFix) { LowHPBerryCheck(user); } return true; } } } // Verified: Paralysis before Infatuation if (user.Status1 == PBEStatus1.Paralyzed && _rand.RandomBool(25, 100)) { BroadcastStatus1(user, user, PBEStatus1.Paralyzed, PBEStatusAction.CausedImmobility); return true; } // Infatuation if (user.Status2.HasFlag(PBEStatus2.Infatuated)) { BroadcastStatus2(user, user.InfatuatedWithPokemon!, PBEStatus2.Infatuated, PBEStatusAction.Announced); if (_rand.RandomBool(50, 100)) { BroadcastStatus2(user, user.InfatuatedWithPokemon!, PBEStatus2.Infatuated, PBEStatusAction.CausedImmobility); return true; } } return false; } private bool MissCheck(PBEBattlePokemon user, PBEBattlePokemon target, IPBEMoveData mData) { if (user == target) { return false; } // Verified: WideGuard happens before Protect if (target.Team.TeamStatus.HasFlag(PBETeamStatus.WideGuard) && mData.Category != PBEMoveCategory.Status && PBEDataUtils.IsSpreadMove(user.GetMoveTargets(mData))) { BroadcastTeamStatusDamage(target.Team, PBETeamStatus.WideGuard, target); return true; } // Feint ignores Quick Guard unless the target is an ally if (target.Team.TeamStatus.HasFlag(PBETeamStatus.QuickGuard) && mData.Priority > 0 && (mData.Effect != PBEMoveEffect.Feint || user.Team == target.Team)) { BroadcastTeamStatusDamage(target.Team, PBETeamStatus.QuickGuard, target); return true; } if (target.Status2.HasFlag(PBEStatus2.Protected) && mData.Flags.HasFlag(PBEMoveFlag.AffectedByProtect)) { BroadcastStatus2(target, user, PBEStatus2.Protected, PBEStatusAction.Damage); return true; } if (user.Status2.HasFlag(PBEStatus2.LockOn) && user.LockOnPokemon == target) { return false; } if (user.Ability == PBEAbility.NoGuard || target.Ability == PBEAbility.NoGuard) { return false; } if (target.Status2.HasFlag(PBEStatus2.Airborne) && !(mData.Flags.HasFlag(PBEMoveFlag.DoubleDamageAirborne) || mData.Flags.HasFlag(PBEMoveFlag.HitsAirborne))) { goto miss; } if (target.Status2.HasFlag(PBEStatus2.ShadowForce)) { goto miss; } if (target.Status2.HasFlag(PBEStatus2.Underground) && !(mData.Flags.HasFlag(PBEMoveFlag.DoubleDamageUnderground) || mData.Flags.HasFlag(PBEMoveFlag.HitsUnderground))) { goto miss; } if (target.Status2.HasFlag(PBEStatus2.Underwater) && !(mData.Flags.HasFlag(PBEMoveFlag.DoubleDamageUnderwater) || mData.Flags.HasFlag(PBEMoveFlag.HitsUnderwater))) { goto miss; } // These go after semi-invulnerable float chance = mData.Accuracy; if (chance == 0) // Moves that don't miss { return false; } if (ShouldDoWeatherEffects()) { if (Weather == PBEWeather.Hailstorm && mData.Flags.HasFlag(PBEMoveFlag.NeverMissHail)) { return false; } if (mData.Flags.HasFlag(PBEMoveFlag.NeverMissRain)) { if (Weather == PBEWeather.Rain) { return false; } if (Weather == PBEWeather.HarshSunlight) { chance = Math.Min(50, chance); } } } if (mData.Effect == PBEMoveEffect.OneHitKnockout) { chance = user.Level - target.Level + chance; if (chance < 1) { goto miss; } else { goto roll; // Skip all modifiers } } if (target.Ability == PBEAbility.WonderSkin && mData.Category == PBEMoveCategory.Status && !user.HasCancellingAbility()) { chance = Math.Min(50, chance); } bool ignoreA = mData.Category != PBEMoveCategory.Status && target.Ability == PBEAbility.Unaware && !user.HasCancellingAbility(); bool ignoreE = mData.Effect == PBEMoveEffect.ChipAway || (mData.Category != PBEMoveCategory.Status && user.Ability == PBEAbility.Unaware); float accuracy = ignoreA ? 1 : GetStatChangeModifier(user.AccuracyChange, true); float evasion; if (ignoreE) { evasion = 1; } else { bool ignorePositive = target.Status2.HasFlag(PBEStatus2.Identified) || target.Status2.HasFlag(PBEStatus2.MiracleEye); evasion = GetStatChangeModifier(ignorePositive ? Math.Min((sbyte)0, target.EvasionChange) : target.EvasionChange, true); } chance *= accuracy / evasion; if (user.Ability == PBEAbility.Compoundeyes) { chance *= 1.3f; } if (user.Team.ActiveBattlers.FindIndex(p => p.Ability == PBEAbility.VictoryStar) != -1) { chance *= 1.1f; } if (user.Ability == PBEAbility.Hustle && mData.Category == PBEMoveCategory.Physical) { chance *= 0.8f; } if (!user.HasCancellingAbility() && ShouldDoWeatherEffects()) { if (Weather == PBEWeather.Sandstorm && target.Ability == PBEAbility.SandVeil) { chance *= 0.8f; } if (Weather == PBEWeather.Hailstorm && target.Ability == PBEAbility.SnowCloak) { chance *= 0.8f; } } if (target.Item == PBEItem.BrightPowder) { chance *= 0.9f; } if (target.Item == PBEItem.LaxIncense) { chance *= 0.9f; } if (user.Item == PBEItem.WideLens) { chance *= 1.1f; } if (target.Ability == PBEAbility.TangledFeet && target.Status2.HasFlag(PBEStatus2.Confused) && !user.HasCancellingAbility()) { chance *= 0.5f; } roll: if (_rand.RandomBool((int)chance, 100)) { return false; } miss: BroadcastMoveResult(user, target, PBEResult.Missed); return true; } private bool AttackTypeCheck(PBEBattlePokemon user, PBEBattlePokemon target, PBEType moveType, out PBEResult result, out float damageMultiplier) { result = PBETypeEffectiveness.IsAffectedByAttack(user, target, moveType, out damageMultiplier); if (result == PBEResult.Ineffective_Ability) { BroadcastAbility(target, user, target.Ability, PBEAbilityAction.Damage); } if (result != PBEResult.NotVeryEffective_Type && result != PBEResult.Success && result != PBEResult.SuperEffective_Type) { BroadcastMoveResult(user, target, result); return false; } return true; } private bool CritCheck(PBEBattlePokemon user, PBEBattlePokemon target, IPBEMoveData mData) { if (((target.Ability == PBEAbility.BattleArmor || target.Ability == PBEAbility.ShellArmor) && !user.HasCancellingAbility()) || target.Team.TeamStatus.HasFlag(PBETeamStatus.LuckyChant)) { return false; } if (mData.Flags.HasFlag(PBEMoveFlag.AlwaysCrit)) { return true; } byte stage = 0; if (user.Status2.HasFlag(PBEStatus2.Pumped)) { stage += 2; } if (user.OriginalSpecies == PBESpecies.Chansey && user.Item == PBEItem.LuckyPunch) { stage += 2; } if (user.OriginalSpecies == PBESpecies.Farfetchd && user.Item == PBEItem.Stick) { stage += 2; } if (mData.Flags.HasFlag(PBEMoveFlag.HighCritChance)) { stage += 1; } if (user.Ability == PBEAbility.SuperLuck) { stage += 1; } if (user.Item == PBEItem.RazorClaw || user.Item == PBEItem.ScopeLens) { stage += 1; } float chance; switch (stage) { case 0: chance = 6.25f; break; case 1: chance = 12.5f; break; case 2: chance = 25; break; case 3: chance = 33.3f; break; default: chance = 50; break; } return _rand.RandomBool((int)(chance * 100), 100 * 100); } private void TrySetLoser(PBEBattlePokemon pkmn) { if (BattleResult is null && pkmn.Team.NumConsciousPkmn == 0) { BattleResult = pkmn.Team.Id == 0 ? PBEBattleResult.Team1Win : PBEBattleResult.Team0Win; } } private void SetEscaped(PBEBattlePokemon pkmn) { BattleResult = pkmn.IsWild ? PBEBattleResult.WildFlee : PBEBattleResult.WildEscape; } private bool FaintCheck(PBEBattlePokemon pkmn) { if (pkmn.HP == 0) { _turnOrder.Remove(pkmn); ActiveBattlers.Remove(pkmn); PBEFieldPosition oldPos = pkmn.FieldPosition; pkmn.ClearForFaint(); BroadcastPkmnFainted(pkmn, oldPos); RemoveInfatuationsAndLockOns(pkmn); CalcEXP(pkmn); pkmn.EXPPokemon.Clear(); pkmn.Team.MonFaintedThisTurn = true; TrySetLoser(pkmn); CastformCherrimCheckAll(); return true; } return false; } private bool GetManipulableChance(PBEBattlePokemon pkmn, int chance) { // TODO: Does the Rainbow affect abilities activating, such as CuteCharm/Static, Healer/ShedSkin, etc, and which side of the field would they activate from? Victim? // TODO: If it does affect abilities, does it affect Effect Spore? It uses its own weird rng if (pkmn.Ability == PBEAbility.SereneGrace) { chance *= 2; } return _rand.RandomBool(chance, 100); } private void ActivateAbility(PBEBattlePokemon pkmn, bool switchIn) { if (!switchIn) { CastformCherrimCheck(pkmn); // Switch-Ins check this after all Pokémon are sent out } AntiStatusAbilityCheck(pkmn); switch (pkmn.Ability) { case PBEAbility.AirLock: case PBEAbility.CloudNine: { if (switchIn) { BroadcastAbility(pkmn, pkmn, pkmn.Ability, PBEAbilityAction.Weather); } else { CastformCherrimCheckAll(); } break; } case PBEAbility.Anticipation: { foreach (PBEBattlePokemon opponent in pkmn.Team.OpposingTeam.ActiveBattlers) { foreach (PBEBattleMoveset.PBEBattleMovesetSlot moveSlot in opponent.Moves) { PBEMove move = moveSlot.Move; if (move != PBEMove.None) { IPBEMoveData mData = PBEDataProvider.Instance.GetMoveData(move); if (mData.Category != PBEMoveCategory.Status) { float d = PBETypeEffectiveness.GetEffectiveness(mData.Type, pkmn); if (d > 1) { BroadcastAbility(pkmn, pkmn, PBEAbility.Anticipation, PBEAbilityAction.Announced); goto bottomAnticipation; } } } } } bottomAnticipation: break; } case PBEAbility.Download: { List oppActive = pkmn.Team.OpposingTeam.ActiveBattlers; if (oppActive.Count != 0) { PBEStat stat = oppActive.Average(p => p.Defense * GetStatChangeModifier(p.DefenseChange, false)) < oppActive.Average(p => p.SpDefense * GetStatChangeModifier(p.SpDefenseChange, false)) ? PBEStat.Attack : PBEStat.SpAttack; if (pkmn.GetStatChange(stat) < Settings.MaxStatChange) { BroadcastAbility(pkmn, pkmn, PBEAbility.Download, PBEAbilityAction.Stats); ApplyStatChangeIfPossible(pkmn, pkmn, stat, +1); } } break; } case PBEAbility.Drizzle: { if (Weather != PBEWeather.Rain || WeatherCounter != 0) { BroadcastAbility(pkmn, pkmn, pkmn.Ability, PBEAbilityAction.Weather); SetWeather(PBEWeather.Rain, 0, switchIn); } break; } case PBEAbility.Drought: { if (Weather != PBEWeather.HarshSunlight || WeatherCounter != 0) { BroadcastAbility(pkmn, pkmn, pkmn.Ability, PBEAbilityAction.Weather); SetWeather(PBEWeather.HarshSunlight, 0, switchIn); } break; } case PBEAbility.Imposter: { PBEFieldPosition targetPos = GetPositionAcross(BattleFormat, pkmn.FieldPosition); if (pkmn.Team.OpposingTeam.TryGetPokemon(targetPos, out PBEBattlePokemon? target) && target.IsTransformPossible(pkmn) == PBEResult.Success) { BroadcastAbility(pkmn, target, pkmn.Ability, PBEAbilityAction.ChangedAppearance); DoTransform(pkmn, target); } break; } case PBEAbility.Intimidate: { // Verified: Do not announce if the positions are empty IReadOnlyList targets = GetRuntimeSurrounding(pkmn, false, true); if (targets.Count > 0) { // Verified: Announce even if nobody is lowered (due to Substitute, Minimized Attack, or Ability) BroadcastAbility(pkmn, pkmn, PBEAbility.Intimidate, PBEAbilityAction.Stats); foreach (PBEBattlePokemon target in GetActingOrder(targets, true)) { ApplyStatChangeIfPossible(pkmn, target, PBEStat.Attack, -1); // Verified: Substitute, Minimized Attack, and Ability are announced } } break; } case PBEAbility.MoldBreaker: case PBEAbility.Teravolt: case PBEAbility.Turboblaze: { BroadcastAbility(pkmn, pkmn, pkmn.Ability, PBEAbilityAction.Announced); break; } case PBEAbility.SandStream: { if (Weather != PBEWeather.Sandstorm || WeatherCounter != 0) { BroadcastAbility(pkmn, pkmn, pkmn.Ability, PBEAbilityAction.Weather); SetWeather(PBEWeather.Sandstorm, 0, switchIn); } break; } case PBEAbility.SlowStart: { pkmn.SlowStart_HinderTurnsLeft = 5; BroadcastAbility(pkmn, pkmn, PBEAbility.SlowStart, PBEAbilityAction.Announced); break; } case PBEAbility.SnowWarning: { if (Weather != PBEWeather.Hailstorm || WeatherCounter != 0) { BroadcastAbility(pkmn, pkmn, pkmn.Ability, PBEAbilityAction.Weather); SetWeather(PBEWeather.Hailstorm, 0, switchIn); } break; } } } private void CastformCherrimCheckAll() { CastformCherrimCheck(GetActingOrder(ActiveBattlers, true)); } private void CastformCherrimCheck(IEnumerable order) { foreach (PBEBattlePokemon pkmn in order) { CastformCherrimCheck(pkmn); } } private void CastformCherrimCheck(PBEBattlePokemon pkmn) { if (pkmn.HP == 0) { return; // #344 - Castform/Cherrim can change form while fainting from Explosion, if they kill someone with a weather-blocking ability } if (pkmn.Species == PBESpecies.Castform && pkmn.OriginalSpecies == PBESpecies.Castform) { PBEForm newForm = PBEForm.Castform; if (pkmn.Ability == PBEAbility.Forecast && ShouldDoWeatherEffects()) { switch (Weather) { case PBEWeather.Hailstorm: newForm = PBEForm.Castform_Snowy; break; case PBEWeather.HarshSunlight: newForm = PBEForm.Castform_Sunny; break; case PBEWeather.Rain: newForm = PBEForm.Castform_Rainy; break; } if (newForm != pkmn.Form) { BroadcastAbility(pkmn, pkmn, pkmn.Ability, PBEAbilityAction.ChangedAppearance); } } if (newForm != pkmn.Form) { BroadcastPkmnFormChanged(pkmn, newForm, pkmn.Ability, pkmn.KnownAbility, false); } } else if (pkmn.Species == PBESpecies.Cherrim && pkmn.OriginalSpecies == PBESpecies.Cherrim) { PBEForm newForm = PBEForm.Cherrim; if (pkmn.Ability == PBEAbility.FlowerGift && ShouldDoWeatherEffects()) { if (Weather == PBEWeather.HarshSunlight) { newForm = PBEForm.Cherrim_Sunshine; } if (newForm != pkmn.Form) { BroadcastAbility(pkmn, pkmn, pkmn.Ability, PBEAbilityAction.ChangedAppearance); } } if (newForm != pkmn.Form) { BroadcastPkmnFormChanged(pkmn, newForm, pkmn.Ability, pkmn.KnownAbility, false); } } } private void ShayminCheck(PBEBattlePokemon pkmn) { // If a Shaymin_Sky is given MagmaArmor and then Frozen, it will change to Shaymin and obtain Shaymin's ability, therefore losing MagmaArmor and as a result will not be cured of its Frozen status. if (pkmn.Species == PBESpecies.Shaymin && pkmn.OriginalSpecies == PBESpecies.Shaymin && pkmn.Form == PBEForm.Shaymin_Sky && pkmn.Status1 == PBEStatus1.Frozen) { const PBEForm newForm = PBEForm.Shaymin; PBEAbility newAbility = PBEDataProvider.Instance.GetPokemonData(PBESpecies.Shaymin, newForm).Abilities[0]; BroadcastPkmnFormChanged(pkmn, newForm, newAbility, PBEAbility.MAX, true); ActivateAbility(pkmn, false); } } private void IllusionBreak(PBEBattlePokemon pkmn, PBEBattlePokemon breaker) { if (pkmn.Status2.HasFlag(PBEStatus2.Disguised)) { pkmn.KnownGender = pkmn.Gender; pkmn.KnownCaughtBall = pkmn.CaughtBall; pkmn.KnownNickname = pkmn.Nickname; pkmn.KnownShiny = pkmn.Shiny; pkmn.KnownSpecies = pkmn.Species; pkmn.KnownType1 = pkmn.Type1; pkmn.KnownType2 = pkmn.Type2; BroadcastIllusion(pkmn); BroadcastAbility(pkmn, breaker, PBEAbility.Illusion, PBEAbilityAction.ChangedAppearance); BroadcastStatus2(pkmn, breaker, PBEStatus2.Disguised, PBEStatusAction.Ended); } } private void AntiStatusAbilityCheck(IEnumerable order) { foreach (PBEBattlePokemon pkmn in order) { AntiStatusAbilityCheck(pkmn); } } private void AntiStatusAbilityCheck(PBEBattlePokemon pkmn) { switch (pkmn.Ability) { case PBEAbility.Immunity: { if (pkmn.Status1 == PBEStatus1.BadlyPoisoned || pkmn.Status1 == PBEStatus1.Poisoned) { PBEStatus1 oldStatus1 = pkmn.Status1; pkmn.Status1 = PBEStatus1.None; pkmn.Status1Counter = 0; BroadcastAbility(pkmn, pkmn, pkmn.Ability, PBEAbilityAction.ChangedStatus); BroadcastStatus1(pkmn, pkmn, oldStatus1, PBEStatusAction.Cleared); } break; } case PBEAbility.Insomnia: case PBEAbility.VitalSpirit: { if (pkmn.Status1 == PBEStatus1.Asleep) { pkmn.Status1 = PBEStatus1.None; pkmn.Status1Counter = 0; pkmn.SleepTurns = 0; BroadcastAbility(pkmn, pkmn, pkmn.Ability, PBEAbilityAction.ChangedStatus); BroadcastStatus1(pkmn, pkmn, PBEStatus1.Asleep, PBEStatusAction.Cleared); CureNightmare(pkmn, pkmn); } break; } case PBEAbility.Limber: { if (pkmn.Status1 == PBEStatus1.Paralyzed) { pkmn.Status1 = PBEStatus1.None; BroadcastAbility(pkmn, pkmn, pkmn.Ability, PBEAbilityAction.ChangedStatus); BroadcastStatus1(pkmn, pkmn, PBEStatus1.Paralyzed, PBEStatusAction.Cleared); } break; } case PBEAbility.MagmaArmor: { if (pkmn.Status1 == PBEStatus1.Frozen) { pkmn.Status1 = PBEStatus1.None; BroadcastAbility(pkmn, pkmn, pkmn.Ability, PBEAbilityAction.ChangedStatus); BroadcastStatus1(pkmn, pkmn, PBEStatus1.Frozen, PBEStatusAction.Cleared); } break; } case PBEAbility.Oblivious: { if (pkmn.Status2.HasFlag(PBEStatus2.Infatuated)) { pkmn.InfatuatedWithPokemon = null; BroadcastAbility(pkmn, pkmn, pkmn.Ability, PBEAbilityAction.ChangedStatus); BroadcastStatus2(pkmn, pkmn, PBEStatus2.Infatuated, PBEStatusAction.Cleared); } break; } case PBEAbility.OwnTempo: { if (pkmn.Status2.HasFlag(PBEStatus2.Confused)) { pkmn.ConfusionCounter = 0; pkmn.ConfusionTurns = 0; BroadcastAbility(pkmn, pkmn, pkmn.Ability, PBEAbilityAction.ChangedStatus); BroadcastStatus2(pkmn, pkmn, PBEStatus2.Confused, PBEStatusAction.Cleared); } break; } case PBEAbility.WaterVeil: { if (pkmn.Status1 == PBEStatus1.Burned) { pkmn.Status1 = PBEStatus1.None; BroadcastAbility(pkmn, pkmn, pkmn.Ability, PBEAbilityAction.ChangedStatus); BroadcastStatus1(pkmn, pkmn, PBEStatus1.Burned, PBEStatusAction.Cleared); } break; } } } private void CauseConfusion(PBEBattlePokemon target, PBEBattlePokemon other) { target.ConfusionCounter = 0; target.ConfusionTurns = (byte)_rand.RandomInt(Settings.ConfusionMinTurns, Settings.ConfusionMaxTurns); BroadcastStatus2(target, other, PBEStatus2.Confused, PBEStatusAction.Added); AntiStatusAbilityCheck(target); } private void CauseInfatuation(PBEBattlePokemon target, PBEBattlePokemon other) { target.InfatuatedWithPokemon = other; BroadcastStatus2(target, other, PBEStatus2.Infatuated, PBEStatusAction.Added); if (target.Item == PBEItem.DestinyKnot && other.IsAttractionPossible(target) == PBEResult.Success) { BroadcastItem(target, other, PBEItem.DestinyKnot, PBEItemAction.Announced); other.InfatuatedWithPokemon = target; BroadcastStatus2(other, target, PBEStatus2.Infatuated, PBEStatusAction.Added); } AntiStatusAbilityCheck(target); } // TODO: Use & add packet handlers private void WhiteHerbCheck(PBEBattlePokemon pkmn) { if (pkmn.Item == PBEItem.WhiteHerb) { PBEStat[] negStats = pkmn.GetStatsLessThan(0); if (negStats.Length > 0) { foreach (PBEStat s in negStats) { pkmn.SetStatChange(s, 0); } BroadcastItem(pkmn, pkmn, PBEItem.WhiteHerb, PBEItemAction.Consumed); } } } private bool PowerHerbCheck(PBEBattlePokemon pkmn) { if (pkmn.Item == PBEItem.PowerHerb) { BroadcastItem(pkmn, pkmn, PBEItem.PowerHerb, PBEItemAction.Consumed); return true; } return false; } private void LowHPBerryCheck(IEnumerable order, PBEBattlePokemon? forcedToEatBy = null) { foreach (PBEBattlePokemon pkmn in order) { LowHPBerryCheck(pkmn, forcedToEatBy: forcedToEatBy); } } private void LowHPBerryCheck(PBEBattlePokemon pkmn, PBEBattlePokemon? forcedToEatBy = null) { forcedToEatBy ??= pkmn; void DoConfuseBerry(PBEFlavor flavor) { BroadcastItem(pkmn, forcedToEatBy, pkmn.Item, PBEItemAction.Consumed); HealDamage(pkmn, pkmn.MaxHP / 8); // Verified: Ignores Safeguard & Substitute, but not Own Tempo // Mold Breaker etc actually affect whether Own Tempo is ignored, which is what forcedToEatBy is for // I verified each of the times the Pokémon eats to check if Mold Breaker affected the outcome if (pkmn.Nature.GetRelationshipToFlavor(flavor) < 0 && pkmn.IsConfusionPossible(forcedToEatBy, ignoreSubstitute: true, ignoreSafeguard: true) == PBEResult.Success) { CauseConfusion(pkmn, forcedToEatBy); } } void DoHealItem(int hp) { BroadcastItem(pkmn, forcedToEatBy, pkmn.Item, PBEItemAction.Consumed); HealDamage(pkmn, hp); } void DoStatItem(PBEStat stat, int change) { // Verified: Mold Breaker affects Contrary/Simple here, unlike with Belly Drum if (pkmn.IsStatChangePossible(stat, forcedToEatBy, change, out sbyte oldValue, out sbyte newValue, ignoreSubstitute: true) == PBEResult.Success) { BroadcastItem(pkmn, forcedToEatBy, pkmn.Item, PBEItemAction.Consumed); SetStatAndBroadcast(pkmn, stat, oldValue, newValue); } } if (pkmn.HP <= pkmn.MaxHP / 4) { switch (pkmn.Item) { case PBEItem.ApicotBerry: DoStatItem(PBEStat.SpDefense, +1); break; case PBEItem.GanlonBerry: DoStatItem(PBEStat.Defense, +1); break; case PBEItem.LiechiBerry: DoStatItem(PBEStat.Attack, +1); break; case PBEItem.PetayaBerry: DoStatItem(PBEStat.SpAttack, +1); break; case PBEItem.SalacBerry: DoStatItem(PBEStat.Speed, +1); break; case PBEItem.StarfBerry: { // Verified: Starf Berry does not activate for Accuracy or Evasion, or if all other stats are maximized List statsThatCanGoUp = PBEDataUtils.StarfBerryStats.FindAll(s => pkmn.GetStatChange(s) < Settings.MaxStatChange); if (statsThatCanGoUp.Count > 0) { DoStatItem(_rand.RandomElement(statsThatCanGoUp), +2); } break; } } } if (pkmn.HP <= pkmn.MaxHP / 2) { switch (pkmn.Item) { case PBEItem.AguavBerry: DoConfuseBerry(PBEFlavor.Bitter); break; case PBEItem.BerryJuice: DoHealItem(20); break; case PBEItem.FigyBerry: DoConfuseBerry(PBEFlavor.Spicy); break; case PBEItem.IapapaBerry: DoConfuseBerry(PBEFlavor.Sour); break; case PBEItem.MagoBerry: DoConfuseBerry(PBEFlavor.Sweet); break; case PBEItem.OranBerry: DoHealItem(10); break; case PBEItem.SitrusBerry: DoHealItem(pkmn.MaxHP / 4); break; case PBEItem.WikiBerry: DoConfuseBerry(PBEFlavor.Dry); break; } } } private void SetAbility(PBEBattlePokemon user, PBEBattlePokemon target, PBEAbility ability) { // This func assumes new ability is different from current PBEAbility oldAbility = target.Ability; BroadcastAbilityReplaced(target, ability); switch (oldAbility) { case PBEAbility.Illusion: { IllusionBreak(target, user); break; } case PBEAbility.SlowStart: { target.SlowStart_HinderTurnsLeft = 0; break; } case PBEAbility.SpeedBoost: { target.SpeedBoost_AbleToSpeedBoostThisTurn = false; break; } } ActivateAbility(target, false); } private void SetWeather(PBEWeather weather, byte weatherCounter, bool switchIn) { Weather = weather; WeatherCounter = weatherCounter; BroadcastWeather(Weather, PBEWeatherAction.Added); if (!switchIn) { CastformCherrimCheckAll(); // Switch-Ins check this after all Pokémon are sent out } } private void RecordExecutedMove(PBEBattlePokemon user, PBEMove move, IPBEMoveData mData) { user.HasUsedMoveThisTurn = true; // Doesn't care if there is a Choice Locked move already. As long as the user knows it, it will become locked. (Metronome calling a move the user knows, Ditto transforming into someone else with transform) if ((user.Item == PBEItem.ChoiceBand || user.Item == PBEItem.ChoiceScarf || user.Item == PBEItem.ChoiceSpecs) && user.Moves.Contains(move)) { BroadcastMoveLock_ChoiceItem(user, move); } if (mData.Effect == PBEMoveEffect.Minimize) { user.Minimize_Used = true; } } private void PPReduce(PBEBattlePokemon pkmn, PBEMove move) { if (!_calledFromOtherMove) { const int amountToReduce = 1; // TODO: If target is not self and has pressure PBEBattleMoveset.PBEBattleMovesetSlot slot = pkmn.Moves[move]!; int oldPP = slot.PP; int newPP = Math.Max(0, oldPP - amountToReduce); int amountReduced = oldPP - newPP; slot.PP = newPP; pkmn.UpdateKnownPP(move, amountReduced); BroadcastMovePPChanged(pkmn, move, amountReduced); } } private static void ApplyBigRoot(PBEBattlePokemon pkmn, ref int restoreAmt) { if (pkmn.Item == PBEItem.BigRoot) { restoreAmt += (int)(restoreAmt * 0.3); } } private void CureNightmare(PBEBattlePokemon wakingUp, PBEBattlePokemon pokemon2) { if (wakingUp.Status2.HasFlag(PBEStatus2.Nightmare)) { BroadcastStatus2(wakingUp, pokemon2, PBEStatus2.Nightmare, PBEStatusAction.Ended); } } private void SetSleepTurns(PBEBattlePokemon pkmn, int minTurns, int maxTurns) { pkmn.SleepTurns = (byte)(_rand.RandomInt(minTurns, maxTurns) / (pkmn.Ability == PBEAbility.EarlyBird ? 2 : 1)); } private void DoTransform(PBEBattlePokemon user, PBEBattlePokemon target) { user.Transform(target); BroadcastTransform(user, target); BroadcastStatus2(user, target, PBEStatus2.Transformed, PBEStatusAction.Added); // Remove power trick (so it cannot be baton passed) if (user.Status2.HasFlag(PBEStatus2.PowerTrick)) { BroadcastStatus2(user, user, PBEStatus2.PowerTrick, PBEStatusAction.Ended); } if (!user.Moves.Contains(user.ChoiceLockedMove)) { BroadcastMoveLock_ChoiceItem(user, PBEMove.None); } } private PBEResult ApplyStatus1IfPossible(PBEBattlePokemon user, PBEBattlePokemon target, PBEStatus1 status, bool broadcastUnsuccessful) { PBEResult result; switch (status) { case PBEStatus1.Asleep: result = target.IsSleepPossible(user); break; case PBEStatus1.BadlyPoisoned: case PBEStatus1.Poisoned: result = target.IsPoisonPossible(user); break; case PBEStatus1.Burned: result = target.IsBurnPossible(user); break; case PBEStatus1.Frozen: result = target.IsFreezePossible(user); break; case PBEStatus1.Paralyzed: result = target.IsParalysisPossible(user); break; default: throw new ArgumentOutOfRangeException(nameof(status)); } if (result == PBEResult.Success) { target.Status1 = status; if (status == PBEStatus1.BadlyPoisoned) { target.Status1Counter = 1; } else if (status == PBEStatus1.Asleep) { SetSleepTurns(target, Settings.SleepMinTurns, Settings.SleepMaxTurns); target.Status1Counter = 0; } BroadcastStatus1(target, user, status, PBEStatusAction.Added); ShayminCheck(target); } else if (broadcastUnsuccessful) { if (result == PBEResult.Ineffective_Ability) { BroadcastAbility(target, user, target.Ability, PBEAbilityAction.PreventedStatus); } BroadcastMoveResult(user, target, result); } return result; } private PBEResult ApplyStatus2IfPossible(PBEBattlePokemon user, PBEBattlePokemon target, PBEStatus2 status, bool broadcastUnsuccessful) { PBEResult result; switch (status) { case PBEStatus2.Confused: { result = target.IsConfusionPossible(user); if (result == PBEResult.Success) { CauseConfusion(target, user); } break; } case PBEStatus2.Cursed: { if (!target.Status2.HasFlag(PBEStatus2.Cursed)) { BroadcastStatus2(target, user, PBEStatus2.Cursed, PBEStatusAction.Added); DealDamage(user, user, user.MaxHP / 2); if (!FaintCheck(user)) { LowHPBerryCheck(user); } result = PBEResult.Success; } else { result = PBEResult.Ineffective_Status; } break; } case PBEStatus2.Flinching: { result = target.IsFlinchPossible(user); if (result == PBEResult.Success) { target.Status2 |= PBEStatus2.Flinching; // No broadcast, not known } break; } case PBEStatus2.HelpingHand: { if (!target.HasUsedMoveThisTurn) { BroadcastStatus2(target, user, PBEStatus2.HelpingHand, PBEStatusAction.Added); result = PBEResult.Success; } else { result = PBEResult.InvalidConditions; } break; } case PBEStatus2.Identified: { if (!target.Status2.HasFlag(PBEStatus2.Identified)) { BroadcastStatus2(target, user, PBEStatus2.Identified, PBEStatusAction.Added); result = PBEResult.Success; } else { result = PBEResult.Ineffective_Status; } break; } case PBEStatus2.Infatuated: { result = target.IsAttractionPossible(user); if (result == PBEResult.Success) { CauseInfatuation(target, user); } break; } case PBEStatus2.LeechSeed: { result = target.IsLeechSeedPossible(); if (result == PBEResult.Success) { target.SeededPosition = user.FieldPosition; target.SeededTeam = user.Team; BroadcastStatus2(target, user, PBEStatus2.LeechSeed, PBEStatusAction.Added); } break; } case PBEStatus2.LockOn: { if (!user.Status2.HasFlag(PBEStatus2.LockOn)) { user.LockOnPokemon = target; user.LockOnTurns = 2; BroadcastStatus2(user, target, PBEStatus2.LockOn, PBEStatusAction.Added); result = PBEResult.Success; } else { result = PBEResult.Ineffective_Status; } break; } case PBEStatus2.MagnetRise: { result = target.IsMagnetRisePossible(); if (result == PBEResult.Success) { target.MagnetRiseTurns = 5; BroadcastStatus2(target, user, PBEStatus2.MagnetRise, PBEStatusAction.Added); } break; } case PBEStatus2.MiracleEye: { if (!target.Status2.HasFlag(PBEStatus2.MiracleEye)) { BroadcastStatus2(target, user, PBEStatus2.MiracleEye, PBEStatusAction.Added); result = PBEResult.Success; } else { result = PBEResult.Ineffective_Status; } break; } case PBEStatus2.Nightmare: { if (target.Status1 == PBEStatus1.Asleep && !target.Status2.HasFlag(PBEStatus2.Nightmare)) { BroadcastStatus2(target, user, PBEStatus2.Nightmare, PBEStatusAction.Added); result = PBEResult.Success; } else { result = PBEResult.Ineffective_Status; } break; } case PBEStatus2.PowerTrick: { target.ApplyPowerTrickChange(); BroadcastStatus2(target, user, PBEStatus2.PowerTrick, PBEStatusAction.Added); result = PBEResult.Success; break; } case PBEStatus2.Protected: { // TODO: If the user goes last, fail if (_rand.RandomBool(user.GetProtectionChance(), ushort.MaxValue)) { user.Protection_Used = true; BroadcastStatus2(user, user, PBEStatus2.Protected, PBEStatusAction.Added); result = PBEResult.Success; } else { result = PBEResult.InvalidConditions; } break; } case PBEStatus2.Pumped: { if (!target.Status2.HasFlag(PBEStatus2.Pumped)) { BroadcastStatus2(target, user, PBEStatus2.Pumped, PBEStatusAction.Added); result = PBEResult.Success; } else { result = PBEResult.Ineffective_Status; } break; } case PBEStatus2.Substitute: { result = target.IsSubstitutePossible(); if (result == PBEResult.Success) { int hpRequired = target.MaxHP / 4; DealDamage(user, target, hpRequired); LowHPBerryCheck(target); // Verified: Berry is eaten between damage and substitute target.SubstituteHP = (ushort)hpRequired; BroadcastStatus2(target, user, PBEStatus2.Substitute, PBEStatusAction.Added); } break; } case PBEStatus2.Transformed: { result = target.IsTransformPossible(user); if (result == PBEResult.Success) { DoTransform(user, target); } break; } default: throw new ArgumentOutOfRangeException(nameof(status)); } if (broadcastUnsuccessful && result != PBEResult.Success) { if (result == PBEResult.Ineffective_Ability) { BroadcastAbility(target, user, target.Ability, PBEAbilityAction.PreventedStatus); } BroadcastMoveResult(user, target, result); } return result; } private void ApplyStatChangeIfPossible(PBEBattlePokemon user, PBEBattlePokemon target, PBEStat stat, int change, bool isSecondaryEffect = false) { PBEResult result = target.IsStatChangePossible(stat, user, change, out sbyte oldValue, out sbyte newValue); bool broadcast; if (result == PBEResult.Success) { target.SetStatChange(stat, newValue); broadcast = true; } else { if (result == PBEResult.Ineffective_Ability) { if (!isSecondaryEffect) { BroadcastAbility(target, user, target.Ability, PBEAbilityAction.Stats); } return; } if (result == PBEResult.Ineffective_Substitute) { if (!isSecondaryEffect) { BroadcastMoveResult(user, target, PBEResult.Ineffective_Substitute); } return; } // Do not broadcast "could not be lowered!" for Mud-Slap, etc broadcast = !isSecondaryEffect; } if (broadcast) { BroadcastPkmnStatChanged(target, stat, oldValue, newValue); } } private void SetStatAndBroadcast(PBEBattlePokemon pkmn, PBEStat stat, sbyte oldValue, sbyte newValue) { pkmn.SetStatChange(stat, newValue); BroadcastPkmnStatChanged(pkmn, stat, oldValue, newValue); } private static PBEPkmnAppearedInfo CreateSwitchInInfo(PBEBattlePokemon pkmn) { if (pkmn.Ability == PBEAbility.Illusion) { PBEBattlePokemon? p = pkmn.GetPkmnWouldDisguiseAs(); if (p is not null) { pkmn.Status2 |= PBEStatus2.Disguised; // No broadcast, not known pkmn.KnownGender = p.Gender; pkmn.KnownCaughtBall = p.CaughtBall; pkmn.KnownNickname = p.Nickname; pkmn.KnownShiny = p.Shiny; pkmn.KnownSpecies = p.OriginalSpecies; pkmn.KnownForm = p.Form; IPBEPokemonData pData = PBEDataProvider.Instance.GetPokemonData(pkmn.KnownSpecies, pkmn.KnownForm); pkmn.KnownType1 = pData.Type1; pkmn.KnownType2 = pData.Type2; } } return new PBEPkmnAppearedInfo(pkmn); } private void SwitchTwoPokemon(PBEBattlePokemon pkmnLeaving, PBEBattlePokemon pkmnComing, PBEBattlePokemon? forcedByPkmn = null) { _turnOrder.Remove(pkmnLeaving); ActiveBattlers.Remove(pkmnLeaving); PBEFieldPosition pos = pkmnLeaving.FieldPosition; pkmnLeaving.ClearForSwitch(); BroadcastPkmnSwitchOut(pkmnLeaving, pos, forcedByPkmn); RemoveInfatuationsAndLockOns(pkmnLeaving); pkmnComing.FieldPosition = pos; var switches = new PBEPkmnAppearedInfo[] { CreateSwitchInInfo(pkmnComing) }; PBETrainer.SwitchTwoPokemon(pkmnLeaving, pkmnComing); // Swap after Illusion ActiveBattlers.Add(pkmnComing); // Add to active before broadcast BroadcastPkmnSwitchIn(pkmnComing.Trainer, switches, forcedByPkmn); if (forcedByPkmn is not null) { BroadcastDraggedOut(pkmnComing); } DoSwitchInEffects(new[] { pkmnComing }, forcedByPkmn); CastformCherrimCheckAll(); } private void RemoveInfatuationsAndLockOns(PBEBattlePokemon pkmnLeaving) { foreach (PBEBattlePokemon pkmn in ActiveBattlers) { if (pkmn.Status2.HasFlag(PBEStatus2.Infatuated) && pkmn.InfatuatedWithPokemon == pkmnLeaving) { pkmn.InfatuatedWithPokemon = null; BroadcastStatus2(pkmn, pkmn, PBEStatus2.Infatuated, PBEStatusAction.Ended); } if (pkmn.Status2.HasFlag(PBEStatus2.LockOn) && pkmn.LockOnPokemon == pkmnLeaving) { pkmn.LockOnPokemon = null; pkmn.LockOnTurns = 0; BroadcastStatus2(pkmn, pkmn, PBEStatus2.LockOn, PBEStatusAction.Ended); } } } private void SemiInvulnerableChargeMove(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData, PBETurnTarget requestedTargets, PBEStatus2 status2, Action? beforePostHit = null, Action? afterPostHit = null) { BroadcastMoveUsed(user, move); top: if (user.Status2.HasFlag(status2)) { BroadcastMoveLock_Temporary(user, PBEMove.None, PBETurnTarget.None); BroadcastStatus2(user, user, status2, PBEStatusAction.Ended); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { BasicHit(user, targets, mData, beforePostHit: beforePostHit, afterPostHit: afterPostHit); } RecordExecutedMove(user, move, mData); // Should only count as the last used move if it finishes charging } else { PPReduce(user, move); BroadcastMoveLock_Temporary(user, move, requestedTargets); BroadcastStatus2(user, user, status2, PBEStatusAction.Added); if (PowerHerbCheck(user)) { goto top; } } } private void Ef_TryForceStatus1(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData, PBEStatus1 status, bool thunderWave = false) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { foreach (PBEBattlePokemon target in targets) { if (!MissCheck(user, target, mData)) { if (thunderWave) { PBEResult result = PBETypeEffectiveness.ThunderWaveTypeCheck(user, target, move); if (result != PBEResult.Success) { BroadcastMoveResult(user, target, result); continue; } } ApplyStatus1IfPossible(user, target, status, true); AntiStatusAbilityCheck(target); } } } RecordExecutedMove(user, move, mData); return; } private void Ef_TryForceStatus2(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData, PBEStatus2 status) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { foreach (PBEBattlePokemon target in targets) { if (!MissCheck(user, target, mData)) { ApplyStatus2IfPossible(user, target, status, true); AntiStatusAbilityCheck(target); } } } RecordExecutedMove(user, move, mData); } private void Ef_TryForceBattleStatus(PBEBattlePokemon user, PBEMove move, IPBEMoveData mData, PBEBattleStatus status) { BroadcastMoveUsed(user, move); PPReduce(user, move); switch (status) { case PBEBattleStatus.TrickRoom: { if (!BattleStatus.HasFlag(PBEBattleStatus.TrickRoom)) { TrickRoomCount = 5; BroadcastBattleStatus(PBEBattleStatus.TrickRoom, PBEBattleStatusAction.Added); } else { TrickRoomCount = 0; BroadcastBattleStatus(PBEBattleStatus.TrickRoom, PBEBattleStatusAction.Cleared); } break; } default: throw new ArgumentOutOfRangeException(nameof(status)); } RecordExecutedMove(user, move, mData); } private void Ef_TryForceTeamStatus(PBEBattlePokemon user, PBEMove move, IPBEMoveData mData, PBETeamStatus status) { PBEResult result; BroadcastMoveUsed(user, move); PPReduce(user, move); switch (status) { case PBETeamStatus.LightScreen: { if (!user.Team.TeamStatus.HasFlag(PBETeamStatus.LightScreen)) { user.Team.LightScreenCount = (byte)(Settings.LightScreenTurns + (user.Item == PBEItem.LightClay ? Settings.LightClayTurnExtension : 0)); BroadcastTeamStatus(user.Team, PBETeamStatus.LightScreen, PBETeamStatusAction.Added); result = PBEResult.Success; } else { result = PBEResult.Ineffective_Status; } break; } case PBETeamStatus.LuckyChant: { if (!user.Team.TeamStatus.HasFlag(PBETeamStatus.LuckyChant)) { user.Team.LuckyChantCount = 5; BroadcastTeamStatus(user.Team, PBETeamStatus.LuckyChant, PBETeamStatusAction.Added); result = PBEResult.Success; } else { result = PBEResult.Ineffective_Status; } break; } case PBETeamStatus.QuickGuard: { if (!user.Team.TeamStatus.HasFlag(PBETeamStatus.QuickGuard) && _rand.RandomBool(user.GetProtectionChance(), ushort.MaxValue)) { user.Protection_Used = true; BroadcastTeamStatus(user.Team, PBETeamStatus.QuickGuard, PBETeamStatusAction.Added); result = PBEResult.Success; } else { result = PBEResult.Ineffective_Status; } break; } case PBETeamStatus.Reflect: { if (!user.Team.TeamStatus.HasFlag(PBETeamStatus.Reflect)) { user.Team.ReflectCount = (byte)(Settings.ReflectTurns + (user.Item == PBEItem.LightClay ? Settings.LightClayTurnExtension : 0)); BroadcastTeamStatus(user.Team, PBETeamStatus.Reflect, PBETeamStatusAction.Added); result = PBEResult.Success; } else { result = PBEResult.Ineffective_Status; } break; } case PBETeamStatus.Safeguard: { if (!user.Team.TeamStatus.HasFlag(PBETeamStatus.Safeguard)) { user.Team.SafeguardCount = 5; BroadcastTeamStatus(user.Team, PBETeamStatus.Safeguard, PBETeamStatusAction.Added); result = PBEResult.Success; } else { result = PBEResult.Ineffective_Status; } break; } case PBETeamStatus.Spikes: { if (user.Team.OpposingTeam.SpikeCount < 3) { user.Team.OpposingTeam.SpikeCount++; BroadcastTeamStatus(user.Team.OpposingTeam, PBETeamStatus.Spikes, PBETeamStatusAction.Added); result = PBEResult.Success; } else { result = PBEResult.Ineffective_Status; } break; } case PBETeamStatus.StealthRock: { if (!user.Team.OpposingTeam.TeamStatus.HasFlag(PBETeamStatus.StealthRock)) { BroadcastTeamStatus(user.Team.OpposingTeam, PBETeamStatus.StealthRock, PBETeamStatusAction.Added); result = PBEResult.Success; } else { result = PBEResult.Ineffective_Status; } break; } case PBETeamStatus.Tailwind: { if (!user.Team.TeamStatus.HasFlag(PBETeamStatus.Tailwind)) { user.Team.TailwindCount = 4; BroadcastTeamStatus(user.Team, PBETeamStatus.Tailwind, PBETeamStatusAction.Added); result = PBEResult.Success; } else { result = PBEResult.Ineffective_Status; } break; } case PBETeamStatus.ToxicSpikes: { if (user.Team.OpposingTeam.ToxicSpikeCount < 2) { user.Team.OpposingTeam.ToxicSpikeCount++; BroadcastTeamStatus(user.Team.OpposingTeam, PBETeamStatus.ToxicSpikes, PBETeamStatusAction.Added); result = PBEResult.Success; } else { result = PBEResult.Ineffective_Status; } break; } case PBETeamStatus.WideGuard: { if (!user.Team.TeamStatus.HasFlag(PBETeamStatus.WideGuard) && _rand.RandomBool(user.GetProtectionChance(), ushort.MaxValue)) { user.Protection_Used = true; BroadcastTeamStatus(user.Team, PBETeamStatus.WideGuard, PBETeamStatusAction.Added); result = PBEResult.Success; } else { result = PBEResult.Ineffective_Status; } break; } default: throw new ArgumentOutOfRangeException(nameof(status)); } if (result != PBEResult.Success) { BroadcastMoveResult(user, user, result); } RecordExecutedMove(user, move, mData); } private void Ef_TryForceWeather(PBEBattlePokemon user, PBEMove move, IPBEMoveData mData, PBEWeather weather) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (Weather == weather) { BroadcastMoveResult(user, user, PBEResult.Ineffective_Status); } else { byte turns; PBEItem extensionItem; byte itemTurnExtension; switch (weather) { case PBEWeather.Hailstorm: { turns = Settings.HailTurns; extensionItem = PBEItem.IcyRock; itemTurnExtension = Settings.IcyRockTurnExtension; break; } case PBEWeather.HarshSunlight: { turns = Settings.SunTurns; extensionItem = PBEItem.HeatRock; itemTurnExtension = Settings.HeatRockTurnExtension; break; } case PBEWeather.Rain: { turns = Settings.RainTurns; extensionItem = PBEItem.DampRock; itemTurnExtension = Settings.DampRockTurnExtension; break; } case PBEWeather.Sandstorm: { turns = Settings.SandstormTurns; extensionItem = PBEItem.SmoothRock; itemTurnExtension = Settings.SmoothRockTurnExtension; break; } default: throw new ArgumentOutOfRangeException(nameof(weather)); } SetWeather(weather, (byte)(turns + (user.Item == extensionItem ? itemTurnExtension : 0)), false); } RecordExecutedMove(user, move, mData); } private void Ef_Growth(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { int change = WillLeafGuardActivate() ? +2 : +1; Ef_ChangeTargetStats(user, targets, move, mData, new[] { (PBEStat.Attack, change), (PBEStat.SpAttack, change) }); } private void Ef_ChangeTargetStats(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData, (PBEStat, int)[] statChanges, bool requireAttraction = false) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { foreach (PBEBattlePokemon target in targets) { if (!MissCheck(user, target, mData)) { PBEResult result = requireAttraction ? target.IsAttractionPossible(user, ignoreCurrentStatus: true) : PBEResult.Success; if (result != PBEResult.Success) { BroadcastMoveResult(user, target, result); } else if (user != target && target.Status2.HasFlag(PBEStatus2.Substitute)) { BroadcastMoveResult(user, target, PBEResult.Ineffective_Substitute); } else { for (int i = 0; i < statChanges.Length; i++) { (PBEStat stat, int change) = statChanges[i]; ApplyStatChangeIfPossible(user, target, stat, change); } } } } } RecordExecutedMove(user, move, mData); } private void Ef_Hit__MaybeChangeTargetStats(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData, (PBEStat, int)[] statChanges, int chanceToChangeStats) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { void BeforePostHit(PBEBattlePokemon target, ushort damageDealt) { if (target.HP == 0 || !GetManipulableChance(user, chanceToChangeStats)) { return; } for (int i = 0; i < statChanges.Length; i++) { (PBEStat stat, int change) = statChanges[i]; ApplyStatChangeIfPossible(user, target, stat, change, isSecondaryEffect: true); } } BasicHit(user, targets, mData, beforePostHit: BeforePostHit); } RecordExecutedMove(user, move, mData); } private void Ef_Hit__MaybeChangeUserStats(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData, (PBEStat, int)[] statChanges, int chanceToChangeStats) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { void BeforePostHit(PBEBattlePokemon target, ushort damageDealt) { if (user.HP > 0 && GetManipulableChance(user, chanceToChangeStats)) { for (int i = 0; i < statChanges.Length; i++) { (PBEStat stat, int change) = statChanges[i]; ApplyStatChangeIfPossible(user, user, stat, change, isSecondaryEffect: true); } } } BasicHit(user, targets, mData, beforePostHit: BeforePostHit); } RecordExecutedMove(user, move, mData); } private void Ef_Entrainment(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { var blockedUserAbilities = new PBEAbility[] { PBEAbility.FlowerGift, PBEAbility.Forecast, PBEAbility.Illusion, PBEAbility.Imposter, PBEAbility.Trace }; BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { foreach (PBEBattlePokemon target in targets) { if (!MissCheck(user, target, mData)) { if (target.Ability == user.Ability || blockedUserAbilities.Contains(user.Ability)) { BroadcastMoveResult(user, target, PBEResult.InvalidConditions); } else if (target.Ability == PBEAbility.Multitype || target.Ability == PBEAbility.Truant) { BroadcastMoveResult(user, target, PBEResult.Ineffective_Ability); } else { SetAbility(user, target, user.Ability); // TODO: #234 - Reveal other Pokémon's Ability } } } } RecordExecutedMove(user, move, mData); } private void Ef_RolePlay(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { var blockedUserAbilities = new PBEAbility[] { PBEAbility.Imposter, PBEAbility.Multitype, PBEAbility.ZenMode }; var blockedTargetAbilities = new PBEAbility[] { PBEAbility.FlowerGift, PBEAbility.Forecast, PBEAbility.Illusion, PBEAbility.Imposter, PBEAbility.Multitype, PBEAbility.Trace, PBEAbility.WonderGuard, PBEAbility.ZenMode }; BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { foreach (PBEBattlePokemon target in targets) { if (!MissCheck(user, target, mData)) { if (target.Ability == user.Ability || blockedUserAbilities.Contains(user.Ability) || blockedTargetAbilities.Contains(target.Ability)) { BroadcastMoveResult(user, target, PBEResult.InvalidConditions); } else { SetAbility(user, user, target.Ability); // TODO: #234 - Reveal other Pokémon's Ability } } } } RecordExecutedMove(user, move, mData); } private void Ef_SetOtherAbility(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData, PBEAbility ability, bool blockedByTruant) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { foreach (PBEBattlePokemon target in targets) { if (!MissCheck(user, target, mData)) { if (target.Ability == ability) { BroadcastMoveResult(user, target, PBEResult.InvalidConditions); } else if (target.Ability == PBEAbility.Multitype || (blockedByTruant && target.Ability == PBEAbility.Truant)) { BroadcastMoveResult(user, target, PBEResult.Ineffective_Ability); } else { SetAbility(user, target, ability); } } } } RecordExecutedMove(user, move, mData); } private void Ef_Bounce(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData, PBETurnTarget requestedTargets) { void BeforePostHit(PBEBattlePokemon target, ushort damageDealt) { if (target.HP > 0 && GetManipulableChance(user, mData.EffectParam)) { ApplyStatus1IfPossible(user, target, PBEStatus1.Paralyzed, false); } } SemiInvulnerableChargeMove(user, targets, move, mData, requestedTargets, PBEStatus2.Airborne, beforePostHit: BeforePostHit); } private void Ef_ShadowForce(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData, PBETurnTarget requestedTargets) { void AfterPostHit(PBEBattlePokemon target) { if (target.HP > 0 && target.Status2.HasFlag(PBEStatus2.Protected)) { BroadcastStatus2(target, user, PBEStatus2.Protected, PBEStatusAction.Cleared); } if (target.Team.TeamStatus.HasFlag(PBETeamStatus.QuickGuard)) { BroadcastTeamStatus(target.Team, PBETeamStatus.QuickGuard, PBETeamStatusAction.Cleared); } if (target.Team.TeamStatus.HasFlag(PBETeamStatus.WideGuard)) { BroadcastTeamStatus(target.Team, PBETeamStatus.WideGuard, PBETeamStatusAction.Cleared); } } SemiInvulnerableChargeMove(user, targets, move, mData, requestedTargets, PBEStatus2.ShadowForce, afterPostHit: AfterPostHit); } private void Ef_BrickBreak(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { void BeforeDoingDamage(PBEBattlePokemon target) { // Verified: Reflect then Light Screen if (target.Team.TeamStatus.HasFlag(PBETeamStatus.Reflect)) { target.Team.ReflectCount = 0; BroadcastTeamStatus(target.Team, PBETeamStatus.Reflect, PBETeamStatusAction.Cleared); } if (target.Team.TeamStatus.HasFlag(PBETeamStatus.LightScreen)) { target.Team.LightScreenCount = 0; BroadcastTeamStatus(target.Team, PBETeamStatus.LightScreen, PBETeamStatusAction.Cleared); } } BasicHit(user, targets, mData, beforeDoingDamage: BeforeDoingDamage); } RecordExecutedMove(user, move, mData); } private void Ef_Feint(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { void BeforeDoingDamage(PBEBattlePokemon target) { if (target.HP > 0 && target.Status2.HasFlag(PBEStatus2.Protected)) { BroadcastStatus2(target, user, PBEStatus2.Protected, PBEStatusAction.Cleared); } if (target.Team == user.Team.OpposingTeam) { if (target.Team.TeamStatus.HasFlag(PBETeamStatus.QuickGuard)) { BroadcastTeamStatus(target.Team, PBETeamStatus.QuickGuard, PBETeamStatusAction.Cleared); } if (target.Team.TeamStatus.HasFlag(PBETeamStatus.WideGuard)) { BroadcastTeamStatus(target.Team, PBETeamStatus.WideGuard, PBETeamStatusAction.Cleared); } } } BasicHit(user, targets, mData, beforeDoingDamage: BeforeDoingDamage); } RecordExecutedMove(user, move, mData); } private void Ef_Hit(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData, PBEStatus1 status1 = PBEStatus1.None, int chanceToInflictStatus1 = 0, PBEStatus2 status2 = PBEStatus2.None, int chanceToInflictStatus2 = 0) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { void BeforePostHit(PBEBattlePokemon target, ushort damageDealt) { if (target.HP == 0) { return; } if (status1 != PBEStatus1.None && GetManipulableChance(user, chanceToInflictStatus1)) { ApplyStatus1IfPossible(user, target, status1, false); } if (status2 != PBEStatus2.None && GetManipulableChance(user, chanceToInflictStatus2)) { ApplyStatus2IfPossible(user, target, status2, false); } } BasicHit(user, targets, mData, beforePostHit: status1 != PBEStatus1.None || status2 != PBEStatus2.None ? BeforePostHit : null); } RecordExecutedMove(user, move, mData); } private void Ef_Hit__MaybeBurnFreezeParalyze(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { void BeforePostHit(PBEBattlePokemon target, ushort damageDealt) { if (target.HP == 0 || !GetManipulableChance(user, mData.EffectParam)) { return; } PBEStatus1 status1; int val = _rand.RandomInt(0, 2); if (val == 0) { status1 = PBEStatus1.Burned; } else if (val == 1) { status1 = PBEStatus1.Frozen; } else { status1 = PBEStatus1.Paralyzed; } ApplyStatus1IfPossible(user, target, status1, false); } BasicHit(user, targets, mData, beforePostHit: BeforePostHit); } RecordExecutedMove(user, move, mData); } private void Ef_MultiHit(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData, byte numHits, bool subsequentMissChecks = false, PBEStatus1 status1 = PBEStatus1.None, int chanceToInflictStatus1 = 0) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { void BeforePostHit(PBEBattlePokemon target) { if (target.HP > 0 && GetManipulableChance(user, chanceToInflictStatus1)) { ApplyStatus1IfPossible(user, target, status1, false); } } MultiHit(user, targets, mData, numHits, subsequentMissChecks: subsequentMissChecks, beforePostHit: status1 != PBEStatus1.None ? BeforePostHit : null); // Doesn't need to be its own func but neater } RecordExecutedMove(user, move, mData); } private void Ef_MultiHit_2To5(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { byte numHits; if (user.Ability == PBEAbility.SkillLink) { numHits = 5; } else { int val = _rand.RandomInt(0, 5); if (val < 2) { numHits = 2; } else if (val < 4) { numHits = 3; } else if (val < 5) { numHits = 4; } else { numHits = 5; } } Ef_MultiHit(user, targets, move, mData, numHits); } private void Ef_PayDay(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { void BeforeDoingDamage(PBEBattlePokemon target) { BroadcastPayDay(); } BasicHit(user, targets, mData, beforeDoingDamage: BeforeDoingDamage); } RecordExecutedMove(user, move, mData); } private void Ef_Recoil(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData, PBEStatus1 status1 = PBEStatus1.None, int chanceToInflictStatus1 = 0, PBEStatus2 status2 = PBEStatus2.None, int chanceToInflictStatus2 = 0) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { int? RecoilFunc(int totalDamageDealt) { return user.Ability == PBEAbility.RockHead || totalDamageDealt == 0 ? null : totalDamageDealt / mData.EffectParam; } void BeforePostHit(PBEBattlePokemon target, ushort damageDealt) { if (target.HP == 0) { return; } if (status1 != PBEStatus1.None && GetManipulableChance(user, chanceToInflictStatus1)) { ApplyStatus1IfPossible(user, target, status1, false); } if (status2 != PBEStatus2.None && GetManipulableChance(user, chanceToInflictStatus2)) { ApplyStatus2IfPossible(user, target, status2, false); } } BasicHit(user, targets, mData, recoilFunc: RecoilFunc, beforePostHit: status1 != PBEStatus1.None || status2 != PBEStatus2.None ? BeforePostHit : null); } RecordExecutedMove(user, move, mData); } private void Ef_SecretPower(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { void BeforePostHit(PBEBattlePokemon target, ushort damageDealt) { // BUG: SecretPower is unaffected by SereneGrace and the Rainbow if (target.HP > 0 && (Settings.BugFix ? GetManipulableChance(user, mData.EffectParam) : _rand.RandomBool(mData.EffectParam, 100)) ) { switch (BattleTerrain) { case PBEBattleTerrain.Cave: ApplyStatus2IfPossible(user, target, PBEStatus2.Flinching, false); break; case PBEBattleTerrain.Grass: ApplyStatus1IfPossible(user, target, PBEStatus1.Asleep, false); break; case PBEBattleTerrain.Plain: ApplyStatus1IfPossible(user, target, PBEStatus1.Paralyzed, false); break; case PBEBattleTerrain.Puddle: ApplyStatChangeIfPossible(user, target, PBEStat.Speed, -1, isSecondaryEffect: true); break; case PBEBattleTerrain.Sand: ApplyStatChangeIfPossible(user, target, PBEStat.Accuracy, -1, isSecondaryEffect: true); break; case PBEBattleTerrain.Snow: ApplyStatus1IfPossible(user, target, PBEStatus1.Frozen, false); break; case PBEBattleTerrain.Water: ApplyStatChangeIfPossible(user, target, PBEStat.Attack, -1, isSecondaryEffect: true); break; default: throw new InvalidDataException(nameof(BattleTerrain)); } } } BasicHit(user, targets, mData, beforePostHit: BeforePostHit); } RecordExecutedMove(user, move, mData); } private void Ef_Selfdestruct(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { // TODO: Damp BroadcastMoveUsed(user, move); PPReduce(user, move); // In gen 5, the user faints first (and loses if possible) // Due to technical limitations, we cannot faint first, but we should still make the user lose so it is the same behavior DealDamage(user, user, user.MaxHP); TrySetLoser(user); if (targets.Length == 0) // You still faint if there are no targets { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { BasicHit(user, targets, mData); } FaintCheck(user); // No berry check because we are always fainted RecordExecutedMove(user, move, mData); } private void Ef_SmellingSalt(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { void AfterPostHit(PBEBattlePokemon target) { if (target.HP > 0 && target.Status1 == PBEStatus1.Paralyzed) { target.Status1 = PBEStatus1.None; BroadcastStatus1(target, user, PBEStatus1.Paralyzed, PBEStatusAction.Cleared); } } BasicHit(user, targets, mData, afterPostHit: AfterPostHit); } RecordExecutedMove(user, move, mData); } private void Ef_Snore(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { // TODO: Snore etc fail in BasicHit? BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else if (user.Status1 != PBEStatus1.Asleep) { BroadcastMoveResult(user, user, PBEResult.Ineffective_Status); } else { void BeforePostHit(PBEBattlePokemon target, ushort damageDealt) { if (target.HP > 0 && GetManipulableChance(user, mData.EffectParam)) { ApplyStatus2IfPossible(user, target, PBEStatus2.Flinching, false); } } BasicHit(user, targets, mData, beforePostHit: BeforePostHit); } RecordExecutedMove(user, move, mData); } private void Ef_Struggle(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastStruggle(user); BroadcastMoveUsed(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { int? RecoilFunc(int totalDamageDealt) { return user.MaxHP / mData.EffectParam; } BasicHit(user, targets, mData, recoilFunc: RecoilFunc); } RecordExecutedMove(user, move, mData); } private void Ef_SuckerPunch(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { PBEResult FailFunc(PBEBattlePokemon target) { if (target.TurnAction is null // Just switched in, used item, etc || target.HasUsedMoveThisTurn || target.TurnAction.Decision != PBETurnDecision.Fight || PBEDataProvider.Instance.GetMoveData(target.TurnAction.FightMove).Category == PBEMoveCategory.Status) { const PBEResult result = PBEResult.InvalidConditions; BroadcastMoveResult(user, target, result); return result; } return PBEResult.Success; } BasicHit(user, targets, mData, failFunc: FailFunc); } RecordExecutedMove(user, move, mData); } private void Ef_WakeUpSlap(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { void AfterPostHit(PBEBattlePokemon target) { if (target.HP > 0 && target.Status1 == PBEStatus1.Asleep) { target.Status1 = PBEStatus1.None; target.Status1Counter = 0; target.SleepTurns = 0; BroadcastStatus1(target, user, PBEStatus1.Asleep, PBEStatusAction.Cleared); CureNightmare(target, user); } } BasicHit(user, targets, mData, afterPostHit: AfterPostHit); } RecordExecutedMove(user, move, mData); } private void Ef_Endeavor(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { int DamageFunc(PBEBattlePokemon target) { return target.HP - user.HP; } PBEResult FailFunc(PBEBattlePokemon target) { if (target.HP <= user.HP) { const PBEResult result = PBEResult.InvalidConditions; BroadcastMoveResult(user, target, result); return result; } return PBEResult.Success; } FixedDamageHit(user, targets, mData, DamageFunc, failFunc: FailFunc); } RecordExecutedMove(user, move, mData); } private void Ef_FinalGambit(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { int oldHP = user.HP; int DamageFunc(PBEBattlePokemon target) { // Only faint/deal damage first time if (user.HP > 0) { DealDamage(user, user, oldHP); FaintCheck(user); // No berry check because we are always fainted } return oldHP; } FixedDamageHit(user, targets, mData, DamageFunc); } RecordExecutedMove(user, move, mData); } private void Ef_OneHitKnockout(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { int DamageFunc(PBEBattlePokemon target) { return target.HP; } PBEResult FailFunc(PBEBattlePokemon target) { if (target.Level > user.Level) { const PBEResult result = PBEResult.Ineffective_Level; BroadcastMoveResult(user, target, result); return result; } else { return PBEResult.Success; } } void BeforePostHit() { // This Any is for Sturdy survivors if (Array.FindIndex(targets, p => p.HP == 0) != -1) { BroadcastOneHitKnockout(); } } FixedDamageHit(user, targets, mData, DamageFunc, failFunc: FailFunc, beforePostHit: BeforePostHit); } RecordExecutedMove(user, move, mData); } private void Ef_Psywave(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { int DamageFunc(PBEBattlePokemon target) { return user.Level * (_rand.RandomInt(0, 100) + 50) / 100; } FixedDamageHit(user, targets, mData, DamageFunc); } RecordExecutedMove(user, move, mData); } private void Ef_SeismicToss(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { int DamageFunc(PBEBattlePokemon target) { return user.Level; } FixedDamageHit(user, targets, mData, DamageFunc); } RecordExecutedMove(user, move, mData); } private void Ef_SetDamage(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { int DamageFunc(PBEBattlePokemon target) { return mData.EffectParam; } FixedDamageHit(user, targets, mData, DamageFunc); } RecordExecutedMove(user, move, mData); } private void Ef_SuperFang(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { static int DamageFunc(PBEBattlePokemon target) { return target.HP / 2; } FixedDamageHit(user, targets, mData, DamageFunc); } RecordExecutedMove(user, move, mData); } private void Ef_HPDrain(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData, bool requireSleep = false) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { PBEResult FailFunc(PBEBattlePokemon target) { if (target.Status1 != PBEStatus1.Asleep) { const PBEResult result = PBEResult.Ineffective_Status; BroadcastMoveResult(user, target, result); return result; } return PBEResult.Success; } void BeforePostHit(PBEBattlePokemon target, ushort damageDealt) { if (user.HP == 0) { return; } int restoreAmt = (int)(damageDealt * (mData.EffectParam / 100.0)); ApplyBigRoot(user, ref restoreAmt); if (target.Ability == PBEAbility.LiquidOoze) { // Verified: User faints first here, target faints at normal spot afterwards BroadcastAbility(target, user, PBEAbility.LiquidOoze, PBEAbilityAction.Damage); DealDamage(target, user, restoreAmt); if (!FaintCheck(user)) { LowHPBerryCheck(user); } } else if (HealDamage(user, restoreAmt) > 0) { BroadcastHPDrained(target); } } BasicHit(user, targets, mData, failFunc: requireSleep ? FailFunc : null, beforePostHit: BeforePostHit); } RecordExecutedMove(user, move, mData); } private void Ef_Moonlight(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { int denominator; if (ShouldDoWeatherEffects()) { switch (Weather) { case PBEWeather.None: denominator = 2; break; case PBEWeather.HarshSunlight: denominator = 3; break; default: denominator = 4; break; } } else { denominator = 2; } foreach (PBEBattlePokemon target in targets) { if (!MissCheck(user, target, mData)) { if (HealDamage(user, user.MaxHP / denominator) == 0) { BroadcastMoveResult(user, user, PBEResult.Ineffective_Stat); } } } } RecordExecutedMove(user, move, mData); } private void Ef_PainSplit(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { foreach (PBEBattlePokemon target in targets) { if (!MissCheck(user, target, mData)) { if (target.Status2.HasFlag(PBEStatus2.Substitute)) { BroadcastMoveResult(user, target, PBEResult.Ineffective_Substitute); } else { int total = user.HP + target.HP; int hp = total / 2; foreach (PBEBattlePokemon pkmn in new PBEBattlePokemon[] { user, target }) { if (hp > pkmn.HP) { HealDamage(pkmn, hp - pkmn.HP); } else if (hp < pkmn.HP) { DealDamage(user, pkmn, pkmn.HP - hp); } } BroadcastPainSplit(user, target); LowHPBerryCheck(user); LowHPBerryCheck(target, forcedToEatBy: user); // Verified: Berry is activated but no illusion breaking // Verified: ColorChange/LifeOrb is ignored } } } } RecordExecutedMove(user, move, mData); } private void Ef_Rest(PBEBattlePokemon user, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (user.Status1 == PBEStatus1.Asleep) { BroadcastMoveResult(user, user, PBEResult.Ineffective_Status); } else if (user.HP == user.MaxHP) { BroadcastMoveResult(user, user, PBEResult.Ineffective_Stat); } else { PBEResult result = user.IsSleepPossible(null, ignoreSubstitute: true, ignoreCurrentStatus: true, ignoreSafeguard: true); if (result == PBEResult.Ineffective_Ability) { BroadcastAbility(user, user, user.Ability, PBEAbilityAction.PreventedStatus); } if (result != PBEResult.Success) { BroadcastMoveResult(user, user, result); } else { user.Status1 = PBEStatus1.Asleep; SetSleepTurns(user, Settings.SleepMaxTurns, Settings.SleepMaxTurns); // Not a typo; Rest always sleeps for max turns user.Status1Counter = 0; BroadcastStatus1(user, user, PBEStatus1.Asleep, PBEStatusAction.Added); HealDamage(user, user.MaxHP); } } RecordExecutedMove(user, move, mData); } private void Ef_RestoreTargetHP(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { foreach (PBEBattlePokemon target in targets) { if (!MissCheck(user, target, mData)) { if (user != target && target.Status2.HasFlag(PBEStatus2.Substitute)) { BroadcastMoveResult(user, target, PBEResult.Ineffective_Substitute); } else { if (HealDamage(target, (int)(target.MaxHP * (mData.EffectParam / 100.0))) == 0) { BroadcastMoveResult(user, target, PBEResult.Ineffective_Stat); } } } } } RecordExecutedMove(user, move, mData); } private void Ef_Roost(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { foreach (PBEBattlePokemon target in targets) { if (!MissCheck(user, target, mData)) { if (target.Status2.HasFlag(PBEStatus2.Roost)) { BroadcastMoveResult(user, target, PBEResult.Ineffective_Status); } else { if (HealDamage(target, (int)(target.MaxHP * (mData.EffectParam / 100.0))) == 0) { BroadcastMoveResult(user, target, PBEResult.Ineffective_Stat); } else { target.StartRoost(); BroadcastStatus2(target, user, PBEStatus2.Roost, PBEStatusAction.Added); } } } } } RecordExecutedMove(user, move, mData); } private void Ef_BellyDrum(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { foreach (PBEBattlePokemon target in targets) { if (!MissCheck(user, target, mData)) { int requirement = target.MaxHP / 2; // BUG: The games do not check if the target has Contrary sbyte oldValue = 0, newValue = 0; if (target.HP <= requirement || (Settings.BugFix ? target.IsStatChangePossible(PBEStat.Attack, user, byte.MaxValue, out oldValue, out newValue) != PBEResult.Success : target.AttackChange == Settings.MaxStatChange) ) { BroadcastMoveResult(user, target, PBEResult.InvalidConditions); } else { DealDamage(user, target, requirement); if (Settings.BugFix) { SetStatAndBroadcast(target, PBEStat.Attack, oldValue, newValue); } else { ApplyStatChangeIfPossible(user, target, PBEStat.Attack, byte.MaxValue); // byte.MaxValue will work for all PBESettings } } } } } RecordExecutedMove(user, move, mData); } private void Ef_Camouflage(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { foreach (PBEBattlePokemon target in targets) { if (!MissCheck(user, target, mData)) { PBEType type; switch (BattleTerrain) { case PBEBattleTerrain.Cave: type = PBEType.Rock; break; case PBEBattleTerrain.Grass: type = PBEType.Grass; break; case PBEBattleTerrain.Plain: type = PBEType.Normal; break; case PBEBattleTerrain.Puddle: type = PBEType.Ground; break; case PBEBattleTerrain.Sand: type = PBEType.Ground; break; case PBEBattleTerrain.Snow: type = PBEType.Ice; break; case PBEBattleTerrain.Water: type = PBEType.Water; break; default: throw new InvalidDataException(nameof(BattleTerrain)); } // Verified: Works on dual-type, fails on single-type if (target.Type1 == type && target.Type2 == PBEType.None) { BroadcastMoveResult(user, target, PBEResult.InvalidConditions); } else { BroadcastTypeChanged(target, type, PBEType.None); } } } } RecordExecutedMove(user, move, mData); } private void Ef_Conversion(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { foreach (PBEBattlePokemon target in targets) { if (!MissCheck(user, target, mData)) { PBEBattleMoveset moves = target.Moves; int count = moves.Count; var available = new List(count); for (int i = 0; i < count; i++) { PBEMove m = moves[i].Move; if (m != PBEMove.None && m != move) { PBEType type = PBEDataProvider.Instance.GetMoveData(m).Type; if (!target.HasType(type)) { available.Add(type); } } } if (available.Count == 0) { BroadcastMoveResult(user, target, PBEResult.InvalidConditions); } else { BroadcastTypeChanged(target, _rand.RandomElement(available), PBEType.None); } } } } RecordExecutedMove(user, move, mData); } private void Ef_Curse(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { if (user.HasType(PBEType.Ghost)) { if (targets.Length == 1 && targets[0] == user) // Just gained the Ghost type after selecting the move, so get a random target { PBEFieldPosition prioritizedPos = GetPositionAcross(BattleFormat, user.FieldPosition); PBETurnTarget moveTarget = prioritizedPos == PBEFieldPosition.Left ? PBETurnTarget.FoeLeft : prioritizedPos == PBEFieldPosition.Center ? PBETurnTarget.FoeCenter : PBETurnTarget.FoeRight; targets = GetRuntimeTargets(user, moveTarget, false, _rand); } if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { foreach (PBEBattlePokemon target in targets) { if (!MissCheck(user, target, mData)) { ApplyStatus2IfPossible(user, target, PBEStatus2.Cursed, true); } } } } else { foreach (PBEBattlePokemon target in targets) { if (target.SpeedChange == -Settings.MaxStatChange && target.AttackChange == Settings.MaxStatChange && target.DefenseChange == Settings.MaxStatChange) { BroadcastMoveResult(user, target, PBEResult.Ineffective_Stat); } else { ApplyStatChangeIfPossible(user, target, PBEStat.Speed, -1); ApplyStatChangeIfPossible(user, target, PBEStat.Attack, +1); ApplyStatChangeIfPossible(user, target, PBEStat.Defense, +1); } } } } RecordExecutedMove(user, move, mData); } private void Ef_Flatter(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { foreach (PBEBattlePokemon target in targets) { if (!MissCheck(user, target, mData)) { if (target.Status2.HasFlag(PBEStatus2.Substitute)) { BroadcastMoveResult(user, target, PBEResult.Ineffective_Substitute); } else { ApplyStatChangeIfPossible(user, target, PBEStat.SpAttack, +1); ApplyStatus2IfPossible(user, target, PBEStatus2.Confused, true); } } } } RecordExecutedMove(user, move, mData); } private void Ef_Haze(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); foreach (PBEBattlePokemon pkmn in targets) { pkmn.ClearStatChanges(); } BroadcastHaze(); RecordExecutedMove(user, move, mData); } private void Ef_HelpingHand(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { foreach (PBEBattlePokemon target in targets) { // TODO: When triple battle shifting happens, all moves that can target allies but not the user will have to check if the user targetted itself due to shifting. // For now, I'll put this check here, because this is the only move that will attempt to target the user when the move cannot normally do so (single/rotation battle). if (target == user) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { ApplyStatus2IfPossible(user, target, PBEStatus2.HelpingHand, true); // No MissCheck because should be able to hit through protect, shadowforce, etc } } } RecordExecutedMove(user, move, mData); } private void Ef_Metronome(PBEBattlePokemon user, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); // Record before the called move is recorded RecordExecutedMove(user, move, mData); PBEMove calledMove = _rand.RandomElement(PBEDataUtils.MetronomeMoves); _calledFromOtherMove = true; UseMove(user, calledMove, GetRandomTargetForMetronome(user, calledMove, _rand)); _calledFromOtherMove = false; } private void Ef_Nothing(PBEBattlePokemon user, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); BroadcastNothingHappened(); RecordExecutedMove(user, move, mData); } private void Ef_PsychUp(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { foreach (PBEBattlePokemon target in targets) { if (!MissCheck(user, target, mData)) { BroadcastPsychUp(user, target); } } } RecordExecutedMove(user, move, mData); } private void Ef_ReflectType(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { foreach (PBEBattlePokemon target in targets) { if (!MissCheck(user, target, mData)) { // Fail if pure flying-type roosts if (target.Type1 == PBEType.None && target.Type2 == PBEType.None) { BroadcastMoveResult(user, target, PBEResult.InvalidConditions); } else { BroadcastReflectType(user, target); } } } } RecordExecutedMove(user, move, mData); } private void Ef_Refresh(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { foreach (PBEBattlePokemon target in targets) { if (!MissCheck(user, target, mData)) { PBEStatus1 status1 = target.Status1; if (status1 == PBEStatus1.None || status1 == PBEStatus1.Asleep || status1 == PBEStatus1.Frozen) { BroadcastMoveResult(user, target, PBEResult.InvalidConditions); } else { target.Status1 = PBEStatus1.None; target.Status1Counter = 0; BroadcastStatus1(target, user, status1, PBEStatusAction.Cleared); } } } } RecordExecutedMove(user, move, mData); } private void Ef_Soak(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { foreach (PBEBattlePokemon target in targets) { if (!MissCheck(user, target, mData)) { BroadcastTypeChanged(target, PBEType.Water, PBEType.None); } } } RecordExecutedMove(user, move, mData); } private void Ef_Swagger(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { foreach (PBEBattlePokemon target in targets) { if (!MissCheck(user, target, mData)) { if (target.Status2.HasFlag(PBEStatus2.Substitute)) { BroadcastMoveResult(user, target, PBEResult.Ineffective_Substitute); } else { ApplyStatChangeIfPossible(user, target, PBEStat.Attack, +2); ApplyStatus2IfPossible(user, target, PBEStatus2.Confused, true); } } } } RecordExecutedMove(user, move, mData); } private void Ef_Teleport(PBEBattlePokemon user, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); // TODO: Trapping effects, SmokeBall // In gen 5 there is a bug that prevents wild Pokémon holding a SmokeBall from escaping if they are affected by trapping effects if (BattleType == PBEBattleType.Wild && BattleFormat == PBEBattleFormat.Single) { SetEscaped(user); } else { BroadcastMoveResult(user, user, PBEResult.InvalidConditions); } RecordExecutedMove(user, move, mData); } private void Ef_Whirlwind(PBEBattlePokemon user, PBEBattlePokemon[] targets, PBEMove move, IPBEMoveData mData) { BroadcastMoveUsed(user, move); PPReduce(user, move); if (targets.Length == 0) { BroadcastMoveResult(user, user, PBEResult.NoTarget); } else { foreach (PBEBattlePokemon target in targets) { if (MissCheck(user, target, mData)) { continue; } // TODO: Trapping effects if (BattleType == PBEBattleType.Wild) { if (BattleFormat == PBEBattleFormat.Single) { // Wild single battle requires user's level to be >= target's level, then it'll end the battle if (user.Level < target.Level) { BroadcastMoveResult(user, target, PBEResult.Ineffective_Level); continue; } SetEscaped(target); break; } else { // Trainer using whirlwind in a wild double+ battle will cause it to fail (even if there's only one wild Pokémon left) if (!user.IsWild) { BroadcastMoveResult(user, target, PBEResult.InvalidConditions); continue; } // A wild Pokémon using it will cause it to switch the target out (as normal below) } } List possibleSwitcheroonies = target.Trainer.Party.FindAll(p => p.FieldPosition == PBEFieldPosition.None && p.CanBattle); if (possibleSwitcheroonies.Count == 0) { BroadcastMoveResult(user, target, PBEResult.InvalidConditions); } else { SwitchTwoPokemon(target, _rand.RandomElement(possibleSwitcheroonies), user); } } } RecordExecutedMove(user, move, mData); } } ================================================ FILE: PokemonBattleEngine/Battle/BattleEffects_HitLogic.cs ================================================ using Kermalis.PokemonBattleEngine.Data; using System; using System.Collections.Generic; namespace Kermalis.PokemonBattleEngine.Battle; // This constant looping order that's present in hitting as well as turn ended effects is very weird and unnecessary, but I mimic it for accuracy // That's why this file exists in favor of the order I had before public sealed partial class PBEBattle { private class PBEAttackVictim { public PBEBattlePokemon Pkmn { get; } public PBEResult Result { get; } public float TypeEffectiveness { get; } public bool Crit { get; set; } public ushort Damage { get; set; } public PBEAttackVictim(PBEBattlePokemon pkmn, PBEResult result, float typeEffectiveness) { Pkmn = pkmn; Result = result; TypeEffectiveness = typeEffectiveness; } } // TODO: TripleKick miss logic private void Hit_GetVictims(PBEBattlePokemon user, PBEBattlePokemon[] targets, IPBEMoveData mData, PBEType moveType, out List victims, Func? failFunc = null) { victims = new List(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; } // Outs are for hit targets that were not behind substitute private static void Hit_HitTargets(PBETeam user, Action> doSub, Action> doNormal, List victims, out List allies, out List foes) { List subAllies = victims.FindAll(v => { PBEBattlePokemon pkmn = v.Pkmn; return pkmn.Team == user && pkmn.Status2.HasFlag(PBEStatus2.Substitute); }); allies = victims.FindAll(v => { PBEBattlePokemon pkmn = v.Pkmn; return pkmn.Team == user && !pkmn.Status2.HasFlag(PBEStatus2.Substitute); }); List subFoes = victims.FindAll(v => { PBEBattlePokemon pkmn = v.Pkmn; return pkmn.Team != user && pkmn.Status2.HasFlag(PBEStatus2.Substitute); }); foes = victims.FindAll(v => { PBEBattlePokemon pkmn = v.Pkmn; return pkmn.Team != user && !pkmn.Status2.HasFlag(PBEStatus2.Substitute); }); doSub(subAllies); doNormal(allies); doSub(subFoes); doNormal(foes); } private void Hit_DoCrit(List victims) { foreach (PBEAttackVictim victim in victims) { if (victim.Crit) { BroadcastMoveCrit(victim.Pkmn); } } } private void Hit_DoMoveResult(PBEBattlePokemon user, List victims) { foreach (PBEAttackVictim victim in victims) { PBEResult result = victim.Result; if (result != PBEResult.Success) { BroadcastMoveResult(user, victim.Pkmn, result); } } } private void Hit_FaintCheck(List victims) { foreach (PBEAttackVictim victim in victims) { FaintCheck(victim.Pkmn); } } private void BasicHit(PBEBattlePokemon user, PBEBattlePokemon[] targets, IPBEMoveData mData, Func? failFunc = null, Action? beforeDoingDamage = null, Action? beforePostHit = null, Action? afterPostHit = null, Func? 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 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 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 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 allies, out List foes); DoPostAttackedEffects(user, allies, foes, true, recoilDamage: recoilFunc?.Invoke(totalDamageDealt), colorChangeType: moveType); } // None of these moves are multi-target private void FixedDamageHit(PBEBattlePokemon user, PBEBattlePokemon[] targets, IPBEMoveData mData, Func damageFunc, Func? 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 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 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 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 allies, out List foes); DoPostAttackedEffects(user, allies, foes, false, colorChangeType: moveType); } // None of these moves are multi-target private void MultiHit(PBEBattlePokemon user, PBEBattlePokemon[] targets, IPBEMoveData mData, byte numHits, bool subsequentMissChecks = false, Action? beforePostHit = null) { PBEType moveType = user.GetMoveType(mData); Hit_GetVictims(user, targets, mData, moveType, out List 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 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 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 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); } } ================================================ FILE: PokemonBattleEngine/Battle/BattleEnums.cs ================================================ using Kermalis.PokemonBattleEngine.Data; using System; namespace Kermalis.PokemonBattleEngine.Battle; /// Represents the battle's terrain. public enum PBEBattleTerrain : byte { Cave, // Rock, RockSlide, RockThrow Grass, // Grass, SeedBomb, NeedleArm /// Used for bridges, buildings, and link battles. Plain, // Normal, TriAttack, BodySlam Puddle, // Ground, MudBomb, MudShot Sand, // Ground, Earthquake, MudSlap Snow, // Ice, Blizzard, Avalanche Water, // Water, HydroPump, WaterPulse MAX } /// Represents the format of a specific battle. public enum PBEBattleFormat : byte { /// A 1v1 battle. Each Pokémon is able to use moves or switch out for another Pokémon. Single, /// A 2v2 battle where all Pokémon are able to use moves or switch out for another Pokémon. Double, /// A 3v3 battle where all Pokémon are able to use moves, shift positions with a teammate, or switch out for another Pokémon. Triple, /// A 3v3 battle where only the front Pokémon are able to force a team rotation, use a move, or switch out for another Pokémon. /// Team rotation does not take up a turn and can be done once per turn. Rotation, /// Invalid battle format. MAX } public enum PBEBattleType : byte { Trainer, Wild } /// Represents the current state of a specific battle. public enum PBEBattleState : byte { /// The battle is waiting for teams. WaitingForPlayers, /// The battle is ready to begin. ReadyToBegin, /// The battle is waiting for players to send actions. WaitingForActions, /// The battle is ready to run a turn. ReadyToRunTurn, /// The battle is processing. Processing, /// The battle is waiting for players to send switch-ins. WaitingForSwitchIns, /// The battle is ready to run switches. ReadyToRunSwitches, /// The battle ended. Ended } /// Represents the result of an ended battle. public enum PBEBattleResult : byte { /// Team 0 forfeited. Team0Forfeit, /// Team 0 defeated Team 1. Team0Win, /// Team 1 forfeited. Team1Forfeit, /// Team 1 defeated Team 0. Team1Win, /// A wild Pokémon was captured. WildCapture, /// The trainer(s) escaped from the wild Pokémon. WildEscape, /// A wild Pokémon escaped from the trainer(s). WildFlee } /// Represents the weather in a specific battle. public enum PBEWeather : byte { /// There is no weather. None, /// It is hailing. Hailstorm, /// The sunlight is harsh. HarshSunlight, /// It is raining. Rain, /// A sandstorm is brewing. Sandstorm, MAX } /// Represents a position on the battle field. public enum PBEFieldPosition : byte { /// A Pokémon is not on the field. None, /// The Pokémon to a player's left in a Double, Triple, or Rotation battle. Left, /// The Pokémon in the center of the field in a Single, Triple, or Rotation battle. Center, /// The Pokémon to a player's right in a Double, Triple, or Rotation battle. Right, MAX } /// Represents a 's targets. [Flags] public enum PBETurnTarget : byte { /// The Pokémon has not chosen any targets. None, /// The move is targetting the player's left Pokémon. AllyLeft = 1 << 0, /// The move is targetting the player's center Pokémon. AllyCenter = 1 << 1, /// The move is targetting the player's right Pokémon. AllyRight = 1 << 2, /// The move is targetting the opponent's left Pokémon. FoeLeft = 1 << 3, /// The move is targetting the opponent's center Pokémon. FoeCenter = 1 << 4, /// The move is targetting the opponent's right Pokémon. FoeRight = 1 << 5 } /// Represents a Pokémon's decision for a turn. public enum PBETurnDecision : byte { /// The Pokémon has not made a decision. None, /// The Pokémon has chosen to use a move. Fight, /// The Pokémon has chosen to use an item. Item, /// The Pokémon has chosen to switch out for another Pokémon. SwitchOut, /// The wild Pokémon has chosen to use flee. WildFlee } /// Represents a specific Pokémon's non-volatile status. public enum PBEStatus1 : byte { /// The Pokémon has no status. None, /// The Pokémon is asleep. Asleep, /// The Pokémon is badly poisoned. BadlyPoisoned, /// The Pokémon is burned. Burned, /// The Pokémon is frozen. Frozen, /// The Pokémon is paralyzed. Paralyzed, /// The Pokémon is poisoned. Poisoned, MAX } /// Represents a specific Pokémon's volatile status. [Flags] public enum PBEStatus2 : uint { /// The Pokémon has no status. None, /// The Pokémon is high up in the air. A move will miss against the Pokémon unless it has or either Pokémon has . Airborne = 1 << 0, /// The Pokémon is confused and may hurt itself instead of execute its chosen move. Confused = 1 << 1, /// The Pokémon is cursed and will take damage at the end of each turn. Cursed = 1 << 2, /// The Pokémon is disguised with . Disguised = 1 << 3, /// The Pokémon is flinching and will be unable to move this turn. Flinching = 1 << 4, /// The Pokémon will gain a power boost due to . HelpingHand = 1 << 5, Identified = 1 << 6, /// The Pokémon is infatuated with and may be unable to move this turn. Infatuated = 1 << 7, /// The Pokémon is seeded and HP will be stolen at the end of each turn. LeechSeed = 1 << 8, LockOn = 1 << 9, MagnetRise = 1 << 10, MiracleEye = 1 << 11, Nightmare = 1 << 12, /// The Pokémon's and are switched. PowerTrick = 1 << 13, /// The Pokémon is protected from moves this turn. Protected = 1 << 14, /// The Pokémon is under the effect of or and has a higher chance of landing critical hits. Pumped = 1 << 15, Roost = 1 << 16, ShadowForce = 1 << 17, /// The Pokémon is behind a substitute that will take damage on behalf of the Pokémon and prevent most moves from affecting the Pokémon. Substitute = 1 << 18, /// The Pokémon is transformed into another Pokémon. Transformed = 1 << 19, /// The Pokémon is underground. A move will miss against the Pokémon unless it has or either Pokémon has . Underground = 1 << 20, /// The Pokémon is underwater. A move will miss against the Pokémon unless it has or either Pokémon has . Underwater = 1 << 21 } /// Keeps track of which types changes to/from . [Flags] public enum PBERoostTypes : byte { None, KnownType1 = 1 << 0, KnownType2 = 1 << 1, Type1 = 1 << 2, Type2 = 1 << 3 } /// Represents a specific 's status. [Flags] public enum PBEBattleStatus : byte { /// The battle has no status. None, /// The acting order of Pokémon in this battle is reversed. TrickRoom = 1 << 0 // TODO: Full Incense, Lagging Tail, Stall, Quick Claw } /// Represents a specific 's status. [Flags] public enum PBETeamStatus : ushort { /// The team has no status. None, /// The team will take less damage from moves. LightScreen = 1 << 0, /// The team is shielded from critical hits. LuckyChant = 1 << 1, QuickGuard = 1 << 2, /// The team will take less damage from moves. Reflect = 1 << 3, Safeguard = 1 << 4, /// Grounded Pokémon that switch in will take damage. The amount of damage is based on . Spikes = 1 << 5, // TODO: Gravity, magic guard, iron ball, baton pass with ingrain, air balloon /// Pokémon that switch in will take damage. The amount of damage is based on the effectiveness of on the Pokémon. StealthRock = 1 << 6, // TODO: magic guard Tailwind = 1 << 7, /// Grounded Pokémon that switch in will be if is 1 or if it is 2. /// Grounded Pokémon will remove toxic spikes. ToxicSpikes = 1 << 8, // TODO: Gravity, magic guard, iron ball, baton pass with ingrain, air balloon, synchronize with roar/whirlwind /// The team is protected from spread moves for a turn. WideGuard = 1 << 9 } /// Represents an action regarding a . public enum PBEAbilityAction : byte { /// The ability is first announced. Announced = 0, /// The ability caused a Pokémon to change its appearance. ChangedAppearance = 1, /// The ability changed a Pokémon's or . ChangedStatus = 2, /// The ability was involved with damage. Damage = 3, /// The ability prevented a Pokémon from being inflicted with a or . PreventedStatus = 4, /// The ability restored a Pokémon's HP. RestoredHP = 5, /// ended. SlowStart_Ended = 6, /// The ability was involved with stats. Stats = 7, /// The ability was involved with weather. Weather = 8 } /// Represents an action regarding a . public enum PBEItemAction : byte { /// The item initiated its main effect. Announced = 0, /// The item was consumed by a Pokémon. Consumed = 1, /// The item was involved with damage. Damage = 2, /// The item restored HP to a Pokémon. RestoredHP = 3 } public enum PBEItemTurnAction : byte { NoEffect = 0, Attempt = 1, Success = 2 } /// Represents an action regarding a or . public enum PBEStatusAction : byte { /// The status was added to a Pokémon. /// The Pokémon became . Added = 0, /// The status activated its main effect. /// states the Pokémon is in love. Announced = 1, /// The status caused a Pokémon to be immobile. /// prevented movement. CausedImmobility = 2, /// The status was cured from a Pokémon. /// cured a Pokémon of . Cleared = 3, /// The status was involved with damage. /// A Pokémon's took damage. Damage = 4, /// The status has ended naturally. /// A Pokémon with regained its senses. Ended = 5 } public enum PBEBattleStatusAction : byte { Added = 0, Cleared = 1, Ended = 2 } /// Represents an action regarding a . public enum PBETeamStatusAction : byte { /// The status was added to a team. /// A team set up . Added = 0, /// The status was forcefully removed from a team. /// A Pokémon used and destroyed . Cleared = 1, /// The status ended naturally. /// wore off. Ended = 2 } /// Represents the result of an intention. public enum PBEResult : byte { /// No failure. Success = 0, /// Failure due to a . Ineffective_Ability = 1, /// Failure due to a . Ineffective_Gender = 2, /// Failure due to a Pokémon's level. Ineffective_Level = 3, /// Failure due to . Ineffective_MagnetRise = 4, /// Failure due to . Ineffective_Safeguard = 5, /// Failure due to a . Ineffective_Stat = 6, /// Failure due to a , , , , or . Ineffective_Status = 7, /// Failure due to . Ineffective_Substitute = 8, /// Failure due to a . Ineffective_Type = 9, /// Failure due to the intention's unmet special conditions. InvalidConditions = 10, /// Failure due to accuracy and/or evasion. Missed = 11, /// Failure due to having no available targets. NoTarget = 12, /// Limited success due to a Pokémon's . NotVeryEffective_Type = 13, /// Great success due to a Pokémon's . SuperEffective_Type = 14 } /// Represents an action regarding a . public enum PBEWeatherAction : byte { /// The weather was added to the battle. Added = 0, /// The weather was removed from the battle. Ended = 1 } public enum PBESpecialMessage : byte { DraggedOut = 0, Endure = 1, HPDrained = 2, Magnitude = 3, MultiHit = 4, NothingHappened = 5, OneHitKnockout = 6, PainSplit = 7, PayDay = 8, Recoil = 9, Struggle = 10 } public enum PBEMoveLockType : byte { ChoiceItem = 0, Temporary = 1 } ================================================ FILE: PokemonBattleEngine/Battle/BattleEvents.cs ================================================ using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngine.Data.Utils; using Kermalis.PokemonBattleEngine.Packets; using Kermalis.PokemonBattleEngine.Utils; using System; using System.IO; using System.Linq; namespace Kermalis.PokemonBattleEngine.Battle; public sealed partial class PBEBattle { public delegate void BattleEvent(PBEBattle battle, IPBEPacket packet); public event BattleEvent? OnNewEvent; private void Broadcast(IPBEPacket packet) { Events.Add(packet); OnNewEvent?.Invoke(this, packet); } private void BroadcastAbility(PBEBattlePokemon abilityOwner, PBEBattlePokemon pokemon2, PBEAbility ability, PBEAbilityAction abilityAction) { abilityOwner.Ability = ability; abilityOwner.KnownAbility = ability; Broadcast(new PBEAbilityPacket(abilityOwner, pokemon2, ability, abilityAction)); } private void BroadcastAbilityReplaced(PBEBattlePokemon abilityOwner, PBEAbility newAbility) { PBEAbility? oldAbility = newAbility == PBEAbility.None ? null : abilityOwner.Ability; // Gastro Acid does not reveal previous ability abilityOwner.Ability = newAbility; abilityOwner.KnownAbility = newAbility; Broadcast(new PBEAbilityReplacedPacket(abilityOwner, oldAbility, newAbility)); } private void BroadcastBattleStatus(PBEBattleStatus battleStatus, PBEBattleStatusAction battleStatusAction) { switch (battleStatusAction) { case PBEBattleStatusAction.Added: BattleStatus |= battleStatus; break; case PBEBattleStatusAction.Cleared: case PBEBattleStatusAction.Ended: BattleStatus &= ~battleStatus; break; default: throw new ArgumentOutOfRangeException(nameof(battleStatusAction)); } Broadcast(new PBEBattleStatusPacket(battleStatus, battleStatusAction)); } private void BroadcastCapture(PBEBattlePokemon pokemon, PBEItem ball, byte numShakes, bool success, bool critical) { Broadcast(new PBECapturePacket(pokemon, ball, numShakes, success, critical)); } private void BroadcastFleeFailed(PBEBattlePokemon pokemon) { Broadcast(new PBEFleeFailedPacket(pokemon)); } private void BroadcastHaze() { Broadcast(new PBEHazePacket()); } private void BroadcastIllusion(PBEBattlePokemon pokemon) { Broadcast(new PBEIllusionPacket(pokemon)); } private void BroadcastItem(PBEBattlePokemon itemHolder, PBEBattlePokemon pokemon2, PBEItem item, PBEItemAction itemAction) { switch (itemAction) { case PBEItemAction.Consumed: { itemHolder.Item = PBEItem.None; itemHolder.KnownItem = PBEItem.None; break; } default: { itemHolder.Item = item; itemHolder.KnownItem = item; break; } } Broadcast(new PBEItemPacket(itemHolder, pokemon2, item, itemAction)); } private void BroadcastItemTurn(PBEBattlePokemon itemUser, PBEItem item, PBEItemTurnAction itemAction) { Broadcast(new PBEItemTurnPacket(itemUser, item, itemAction)); } private void BroadcastMoveCrit(PBEBattlePokemon victim) { Broadcast(new PBEMoveCritPacket(victim)); } private void BroadcastMoveLock_ChoiceItem(PBEBattlePokemon moveUser, PBEMove lockedMove) { moveUser.ChoiceLockedMove = lockedMove; Broadcast(new PBEMoveLockPacket(moveUser, PBEMoveLockType.ChoiceItem, lockedMove)); } private void BroadcastMoveLock_Temporary(PBEBattlePokemon moveUser, PBEMove lockedMove, PBETurnTarget lockedTargets) { moveUser.TempLockedMove = lockedMove; moveUser.TempLockedTargets = lockedTargets; Broadcast(new PBEMoveLockPacket(moveUser, PBEMoveLockType.Temporary, lockedMove, lockedTargets)); } private void BroadcastMovePPChanged(PBEBattlePokemon moveUser, PBEMove move, int amountReduced) { Broadcast(new PBEMovePPChangedPacket(moveUser, move, amountReduced)); } private void BroadcastMoveResult(PBEBattlePokemon moveUser, PBEBattlePokemon pokemon2, PBEResult result) { Broadcast(new PBEMoveResultPacket(moveUser, pokemon2, result)); } private void BroadcastMoveUsed(PBEBattlePokemon moveUser, PBEMove move) { bool owned; if (!_calledFromOtherMove && moveUser.Moves.Contains(move)) { // Check if this move is known first. If you check for PBEMove.MAX then you will get multiple results if (!moveUser.KnownMoves.Contains(move)) { moveUser.KnownMoves[PBEMove.MAX]!.Move = move; } owned = true; } else { owned = false; } Broadcast(new PBEMoveUsedPacket(moveUser, move, owned)); } private void BroadcastPkmnEXPChanged(PBEBattlePokemon pokemon, uint oldEXP) { Broadcast(new PBEPkmnEXPChangedPacket(pokemon, oldEXP)); } private void BroadcastPkmnEXPEarned(PBEBattlePokemon pokemon, uint earned) { Broadcast(new PBEPkmnEXPEarnedPacket(pokemon, earned)); } private void BroadcastPkmnFainted(PBEBattlePokemon pokemon, PBEFieldPosition oldPosition) { Broadcast(new PBEPkmnFaintedPacket(pokemon, oldPosition)); } private void BroadcastPkmnFormChanged(PBEBattlePokemon pokemon, PBEForm newForm, PBEAbility newAbility, PBEAbility newKnownAbility, bool isRevertForm) { pokemon.Ability = newAbility; pokemon.KnownAbility = newKnownAbility; pokemon.Form = newForm; pokemon.KnownForm = newForm; if (isRevertForm) { pokemon.RevertForm = newForm; pokemon.RevertAbility = newAbility; } // This calcs all stats and then adds/removes HP based on the new MaxHP. So if the new MaxHP was 5 more than old, the mon would gain 5 HP. // This is the same logic a level-up and evolution would use when HP changes. pokemon.SetStats(true, false); IPBEPokemonData pData = PBEDataProvider.Instance.GetPokemonData(pokemon.Species, newForm); PBEType type1 = pData.Type1; pokemon.Type1 = type1; pokemon.KnownType1 = type1; PBEType type2 = pData.Type2; pokemon.Type2 = type2; pokemon.KnownType2 = type2; float weight = pData.Weight; // TODO: Is weight updated here? Bulbapedia claims in Autotomize's page that it is not pokemon.Weight = weight; pokemon.KnownWeight = weight; Broadcast(new PBEPkmnFormChangedPacket(pokemon, isRevertForm)); // BUG: PBEStatus2.PowerTrick is not cleared when changing form (meaning it can still be baton passed) if (Settings.BugFix && pokemon.Status2.HasFlag(PBEStatus2.PowerTrick)) { BroadcastStatus2(pokemon, pokemon, PBEStatus2.PowerTrick, PBEStatusAction.Ended); } } private void BroadcastPkmnHPChanged(PBEBattlePokemon pokemon, ushort oldHP, float oldHPPercentage) { Broadcast(new PBEPkmnHPChangedPacket(pokemon, oldHP, oldHPPercentage)); } private void BroadcastPkmnLevelChanged(PBEBattlePokemon pokemon) { Broadcast(new PBEPkmnLevelChangedPacket(pokemon)); } private void BroadcastPkmnStatChanged(PBEBattlePokemon pokemon, PBEStat stat, sbyte oldValue, sbyte newValue) { Broadcast(new PBEPkmnStatChangedPacket(pokemon, stat, oldValue, newValue)); } private void BroadcastPkmnSwitchIn(PBETrainer trainer, PBEPkmnAppearedInfo[] switchIns, PBEBattlePokemon? forcedByPokemon = null) { Broadcast(new PBEPkmnSwitchInPacket(trainer, switchIns, forcedByPokemon)); } private void BroadcastPkmnSwitchOut(PBEBattlePokemon pokemon, PBEFieldPosition oldPosition, PBEBattlePokemon? forcedByPokemon = null) { Broadcast(new PBEPkmnSwitchOutPacket(pokemon, oldPosition, forcedByPokemon)); } private void BroadcastPsychUp(PBEBattlePokemon user, PBEBattlePokemon target) { user.AttackChange = target.AttackChange; user.DefenseChange = target.DefenseChange; user.SpAttackChange = target.SpAttackChange; user.SpDefenseChange = target.SpDefenseChange; user.SpeedChange = target.SpeedChange; user.AccuracyChange = target.AccuracyChange; user.EvasionChange = target.EvasionChange; Broadcast(new PBEPsychUpPacket(user, target)); } private void BroadcastReflectType(PBEBattlePokemon user, PBEBattlePokemon target) { user.Type1 = user.KnownType1 = target.KnownType1 = target.Type1; user.Type2 = user.KnownType2 = target.KnownType2 = target.Type2; Broadcast(new PBEReflectTypePacket(user, target)); } private void BroadcastDraggedOut(PBEBattlePokemon pokemon) { Broadcast(new PBESpecialMessagePacket(PBESpecialMessage.DraggedOut, pokemon)); } private void BroadcastEndure(PBEBattlePokemon pokemon) { Broadcast(new PBESpecialMessagePacket(PBESpecialMessage.Endure, pokemon)); } private void BroadcastHPDrained(PBEBattlePokemon pokemon) { Broadcast(new PBESpecialMessagePacket(PBESpecialMessage.HPDrained, pokemon)); } private void BroadcastMagnitude(byte magnitude) { Broadcast(new PBESpecialMessagePacket(PBESpecialMessage.Magnitude, magnitude)); } private void BroadcastMultiHit(byte numHits) { Broadcast(new PBESpecialMessagePacket(PBESpecialMessage.MultiHit, numHits)); } private void BroadcastNothingHappened() { Broadcast(new PBESpecialMessagePacket(PBESpecialMessage.NothingHappened)); } private void BroadcastOneHitKnockout() { Broadcast(new PBESpecialMessagePacket(PBESpecialMessage.OneHitKnockout)); } private void BroadcastPainSplit(PBEBattlePokemon user, PBEBattlePokemon target) { Broadcast(new PBESpecialMessagePacket(PBESpecialMessage.PainSplit, user, target)); } private void BroadcastPayDay() { Broadcast(new PBESpecialMessagePacket(PBESpecialMessage.PayDay)); } private void BroadcastRecoil(PBEBattlePokemon pokemon) { Broadcast(new PBESpecialMessagePacket(PBESpecialMessage.Recoil, pokemon)); } private void BroadcastStruggle(PBEBattlePokemon pokemon) { Broadcast(new PBESpecialMessagePacket(PBESpecialMessage.Struggle, pokemon)); } private void BroadcastStatus1(PBEBattlePokemon status1Receiver, PBEBattlePokemon pokemon2, PBEStatus1 status1, PBEStatusAction statusAction) { Broadcast(new PBEStatus1Packet(status1Receiver, pokemon2, status1, statusAction)); } private void BroadcastStatus2(PBEBattlePokemon status2Receiver, PBEBattlePokemon pokemon2, PBEStatus2 status2, PBEStatusAction statusAction) { switch (statusAction) { case PBEStatusAction.Added: case PBEStatusAction.Announced: case PBEStatusAction.CausedImmobility: case PBEStatusAction.Damage: status2Receiver.Status2 |= status2; status2Receiver.KnownStatus2 |= status2; break; case PBEStatusAction.Cleared: case PBEStatusAction.Ended: status2Receiver.Status2 &= ~status2; status2Receiver.KnownStatus2 &= ~status2; break; default: throw new ArgumentOutOfRangeException(nameof(statusAction)); } Broadcast(new PBEStatus2Packet(status2Receiver, pokemon2, status2, statusAction)); } private void BroadcastTeamStatus(PBETeam team, PBETeamStatus teamStatus, PBETeamStatusAction teamStatusAction) { switch (teamStatusAction) { case PBETeamStatusAction.Added: team.TeamStatus |= teamStatus; break; case PBETeamStatusAction.Cleared: case PBETeamStatusAction.Ended: team.TeamStatus &= ~teamStatus; break; default: throw new ArgumentOutOfRangeException(nameof(teamStatusAction)); } Broadcast(new PBETeamStatusPacket(team, teamStatus, teamStatusAction)); } private void BroadcastTeamStatusDamage(PBETeam team, PBETeamStatus teamStatus, PBEBattlePokemon damageVictim) { team.TeamStatus |= teamStatus; Broadcast(new PBETeamStatusDamagePacket(team, teamStatus, damageVictim)); } private void BroadcastTransform(PBEBattlePokemon user, PBEBattlePokemon target) { Broadcast(new PBETransformPacket(user, target)); } private void BroadcastTypeChanged(PBEBattlePokemon pokemon, PBEType type1, PBEType type2) { pokemon.Type1 = type1; pokemon.KnownType1 = type1; pokemon.Type2 = type2; pokemon.KnownType2 = type2; Broadcast(new PBETypeChangedPacket(pokemon, type1, type2)); } private void BroadcastWeather(PBEWeather weather, PBEWeatherAction weatherAction) { Broadcast(new PBEWeatherPacket(weather, weatherAction)); } private void BroadcastWeatherDamage(PBEWeather weather, PBEBattlePokemon damageVictim) { Broadcast(new PBEWeatherDamagePacket(weather, damageVictim)); } private void BroadcastWildPkmnAppeared(PBEPkmnAppearedInfo[] appearances) { Broadcast(new PBEWildPkmnAppearedPacket(appearances)); } private void BroadcastActionsRequest(PBETrainer trainer) { Broadcast(new PBEActionsRequestPacket(trainer)); } private void BroadcastAutoCenter(PBEBattlePokemon pokemon0, PBEFieldPosition pokemon0OldPosition, PBEBattlePokemon pokemon1, PBEFieldPosition pokemon1OldPosition) { Broadcast(new PBEAutoCenterPacket(pokemon0, pokemon0OldPosition, pokemon1, pokemon1OldPosition)); } private void BroadcastBattle() { Broadcast(new PBEBattlePacket(this)); } private void BroadcastBattleResult(PBEBattleResult r) { Broadcast(new PBEBattleResultPacket(r)); } private void BroadcastSwitchInRequest(PBETrainer trainer) { Broadcast(new PBESwitchInRequestPacket(trainer)); } private void BroadcastTurnBegan() { Broadcast(new PBETurnBeganPacket(TurnNumber)); } public static string? GetDefaultMessage(PBEBattle battle, IPBEPacket packet, bool showRawHP = false, PBETrainer? userTrainer = null, Func? pkmnNameFunc = null, Func? trainerNameFunc = null, Func? teamNameFunc = null) { // This is not used by switching in or out or wild Pokémon appearing; those always use the known nickname string GetPkmnName(PBEBattlePokemon pkmn, bool firstLetterCapitalized) { if (pkmnNameFunc is not null) { return pkmnNameFunc(pkmn, firstLetterCapitalized); } if (pkmn.IsWild) { string wildPrefix = firstLetterCapitalized ? "The wild " : "the wild "; return wildPrefix + pkmn.KnownNickname; } // Replay/spectator always see prefix, but if you're battling a multi-battle, your Pokémon should still have no prefix if (userTrainer is null || (pkmn.Trainer != userTrainer && pkmn.Team.Trainers.Count > 1)) { return $"{GetTrainerName(pkmn.Trainer)}'s {pkmn.KnownNickname}"; } string ownerPrefix = string.Empty; string foePrefix = firstLetterCapitalized ? "The foe's " : "the foe's "; string prefix = pkmn.Trainer == userTrainer ? ownerPrefix : foePrefix; return prefix + pkmn.KnownNickname; } string GetTrainerName(PBETrainer trainer) { if (trainerNameFunc is not null) { return trainerNameFunc(trainer); } return trainer.Name; } string GetRawCombinedName(PBETeam team, bool firstLetterCapitalized) { if (team.IsWild) { string prefix = firstLetterCapitalized ? "The" : "the"; return prefix + " wild Pokémon"; } return team.CombinedName; } // This is not used by PBEBattleResultPacket; those use GetRawCombinedName() string GetTeamName(PBETeam team, bool firstLetterCapitalized) { if (teamNameFunc is not null) { return teamNameFunc(team, firstLetterCapitalized); } if (userTrainer is null) { return $"{GetRawCombinedName(team, firstLetterCapitalized)}'s"; } string ownerPrefix = firstLetterCapitalized ? "Your" : "your"; string foePrefix = firstLetterCapitalized ? "The opposing" : "the opposing"; return team == userTrainer.Team ? ownerPrefix : foePrefix; } string DoHiddenHP(PBEBattlePokemon pokemon, float percentageChange, float absPercentageChange) { return string.Format("{0} {1} {2:P2} of its HP!", GetPkmnName(pokemon, true), percentageChange <= 0 ? "lost" : "restored", absPercentageChange); } switch (packet) { case PBEAbilityPacket ap: { PBEBattlePokemon abilityOwner = ap.AbilityOwnerTrainer.GetPokemon(ap.AbilityOwner); PBEBattlePokemon pokemon2 = ap.AbilityOwnerTrainer.GetPokemon(ap.Pokemon2); bool abilityOwnerCaps = true, pokemon2Caps = true; string message; switch (ap.Ability) { case PBEAbility.AirLock: case PBEAbility.CloudNine: { switch (ap.AbilityAction) { case PBEAbilityAction.Weather: message = "{0}'s {2} causes the effects of weather to disappear!"; break; default: throw new InvalidDataException(nameof(ap.AbilityAction)); } break; } case PBEAbility.Anticipation: { switch (ap.AbilityAction) { case PBEAbilityAction.Announced: message = "{0}'s {2} made it shudder!"; break; default: throw new InvalidDataException(nameof(ap.AbilityAction)); } break; } case PBEAbility.BadDreams: { switch (ap.AbilityAction) { case PBEAbilityAction.Damage: message = "{1} is tormented by {0}'s {2}!"; abilityOwnerCaps = false; break; default: throw new InvalidDataException(nameof(ap.AbilityAction)); } break; } case PBEAbility.BigPecks: { switch (ap.AbilityAction) { case PBEAbilityAction.Stats: message = $"{{0}}'s {PBEDataProvider.Instance.GetStatName(PBEStat.Defense).English} was not lowered!"; break; default: throw new InvalidDataException(nameof(ap.AbilityAction)); } break; } case PBEAbility.ClearBody: case PBEAbility.WhiteSmoke: { switch (ap.AbilityAction) { case PBEAbilityAction.Stats: message = "{0}'s {2} prevents stat reduction!"; break; default: throw new InvalidDataException(nameof(ap.AbilityAction)); } break; } case PBEAbility.ColorChange: case PBEAbility.FlowerGift: case PBEAbility.Forecast: case PBEAbility.Imposter: { switch (ap.AbilityAction) { case PBEAbilityAction.ChangedAppearance: message = "{0}'s {2} activated!"; break; default: throw new InvalidDataException(nameof(ap.AbilityAction)); } break; } case PBEAbility.CuteCharm: case PBEAbility.EffectSpore: case PBEAbility.FlameBody: case PBEAbility.Healer: case PBEAbility.PoisonPoint: case PBEAbility.ShedSkin: case PBEAbility.Static: { switch (ap.AbilityAction) { case PBEAbilityAction.ChangedStatus: message = "{0}'s {2} activated!"; break; default: throw new InvalidDataException(nameof(ap.AbilityAction)); } break; } case PBEAbility.Download: case PBEAbility.Intimidate: { switch (ap.AbilityAction) { case PBEAbilityAction.Stats: message = "{0}'s {2} activated!"; break; default: throw new InvalidDataException(nameof(ap.AbilityAction)); } break; } case PBEAbility.Drizzle: case PBEAbility.Drought: case PBEAbility.SandStream: case PBEAbility.SnowWarning: { switch (ap.AbilityAction) { case PBEAbilityAction.Weather: message = "{0}'s {2} activated!"; break; default: throw new InvalidDataException(nameof(ap.AbilityAction)); } break; } case PBEAbility.HyperCutter: { switch (ap.AbilityAction) { case PBEAbilityAction.Stats: message = $"{{0}}'s {PBEDataProvider.Instance.GetStatName(PBEStat.Attack).English} was not lowered!"; break; default: throw new InvalidDataException(nameof(ap.AbilityAction)); } break; } case PBEAbility.IceBody: case PBEAbility.PoisonHeal: case PBEAbility.RainDish: { switch (ap.AbilityAction) { case PBEAbilityAction.RestoredHP: message = "{0}'s {2} activated!"; break; default: throw new InvalidDataException(nameof(ap.AbilityAction)); } break; } case PBEAbility.Illusion: { switch (ap.AbilityAction) { case PBEAbilityAction.ChangedAppearance: goto bottom; default: throw new InvalidDataException(nameof(ap.AbilityAction)); } } case PBEAbility.Immunity: case PBEAbility.Insomnia: case PBEAbility.Limber: case PBEAbility.MagmaArmor: case PBEAbility.Oblivious: case PBEAbility.OwnTempo: case PBEAbility.VitalSpirit: case PBEAbility.WaterVeil: { switch (ap.AbilityAction) { case PBEAbilityAction.ChangedStatus: case PBEAbilityAction.PreventedStatus: message = "{0}'s {2} activated!"; break; default: throw new InvalidDataException(nameof(ap.AbilityAction)); } break; } case PBEAbility.IronBarbs: case PBEAbility.Justified: case PBEAbility.Levitate: case PBEAbility.Mummy: case PBEAbility.Rattled: case PBEAbility.RoughSkin: case PBEAbility.SolarPower: case PBEAbility.Sturdy: case PBEAbility.WeakArmor: case PBEAbility.WonderGuard: { switch (ap.AbilityAction) { case PBEAbilityAction.Damage: message = "{0}'s {2} activated!"; break; default: throw new InvalidDataException(nameof(ap.AbilityAction)); } break; } case PBEAbility.KeenEye: { switch (ap.AbilityAction) { case PBEAbilityAction.Stats: message = $"{{0}}'s {PBEDataProvider.Instance.GetStatName(PBEStat.Accuracy).English} was not lowered!"; break; default: throw new InvalidDataException(nameof(ap.AbilityAction)); } break; } case PBEAbility.LeafGuard: { switch (ap.AbilityAction) { case PBEAbilityAction.PreventedStatus: message = "{0}'s {2} activated!"; break; default: throw new InvalidDataException(nameof(ap.AbilityAction)); } break; } case PBEAbility.LiquidOoze: { switch (ap.AbilityAction) { case PBEAbilityAction.Damage: message = "{1} sucked up the liquid ooze!"; break; default: throw new InvalidDataException(nameof(ap.AbilityAction)); } break; } case PBEAbility.MoldBreaker: { switch (ap.AbilityAction) { case PBEAbilityAction.Announced: message = "{0} breaks the mold!"; break; default: throw new InvalidDataException(nameof(ap.AbilityAction)); } break; } case PBEAbility.Moody: case PBEAbility.SpeedBoost: case PBEAbility.Steadfast: { switch (ap.AbilityAction) { case PBEAbilityAction.Stats: message = "{0}'s {2} activated!"; break; default: throw new InvalidDataException(nameof(ap.AbilityAction)); } break; } case PBEAbility.RunAway: { switch (ap.AbilityAction) { case PBEAbilityAction.Announced: message = "{0}'s {2} activated!"; break; default: throw new InvalidDataException(nameof(ap.AbilityAction)); } break; } case PBEAbility.SlowStart: { switch (ap.AbilityAction) { case PBEAbilityAction.Announced: message = "{0} can't get it going!"; break; case PBEAbilityAction.SlowStart_Ended: message = "{0} finally got its act together!"; break; default: throw new InvalidDataException(nameof(ap.AbilityAction)); } break; } case PBEAbility.Teravolt: { switch (ap.AbilityAction) { case PBEAbilityAction.Announced: message = "{0} is radiating a bursting aura!"; break; default: throw new InvalidDataException(nameof(ap.AbilityAction)); } break; } case PBEAbility.Turboblaze: { switch (ap.AbilityAction) { case PBEAbilityAction.Announced: message = "{0} is radiating a blazing aura!"; break; default: throw new InvalidDataException(nameof(ap.AbilityAction)); } break; } default: throw new InvalidDataException(nameof(ap.Ability)); } return string.Format(message, GetPkmnName(abilityOwner, abilityOwnerCaps), GetPkmnName(pokemon2, pokemon2Caps), PBEDataProvider.Instance.GetAbilityName(ap.Ability).English); } case PBEAbilityReplacedPacket arp: { PBEBattlePokemon abilityOwner = arp.AbilityOwnerTrainer.GetPokemon(arp.AbilityOwner); string message; switch (arp.NewAbility) { case PBEAbility.None: message = "{0}'s {1} was suppressed!"; break; default: message = "{0}'s {1} was changed to {2}!"; break; } return string.Format(message, GetPkmnName(abilityOwner, true), arp.OldAbility is null ? "Ability" : PBEDataProvider.Instance.GetAbilityName(arp.OldAbility.Value).English, PBEDataProvider.Instance.GetAbilityName(arp.NewAbility).English); } case PBEBattleStatusPacket bsp: { string message; switch (bsp.BattleStatus) { case PBEBattleStatus.TrickRoom: { switch (bsp.BattleStatusAction) { case PBEBattleStatusAction.Added: message = "The dimensions were twisted!"; break; case PBEBattleStatusAction.Cleared: case PBEBattleStatusAction.Ended: message = "The twisted dimensions returned to normal!"; break; default: throw new InvalidDataException(nameof(bsp.BattleStatusAction)); } break; } default: throw new InvalidDataException(nameof(bsp.BattleStatus)); } return message; } case PBECapturePacket cp: { PBEBattlePokemon pokemon = cp.PokemonTrainer.GetPokemon(cp.Pokemon); string ballEnglish = PBEDataProvider.Instance.GetItemName(cp.Ball).English; if (cp.Success) { return string.Format("Gotcha! {0} was caught with the {1} after {2} shake{3}!", pokemon.Nickname, ballEnglish, cp.NumShakes, cp.NumShakes == 1 ? string.Empty : "s"); } if (cp.NumShakes == 0) { return "The Pokémon broke free without shaking!"; } return string.Format("The Pokémon broke free after {0} shake{1}!", cp.NumShakes, cp.NumShakes == 1 ? string.Empty : "s"); } case PBEFleeFailedPacket ffp: { string name; if (ffp.Pokemon == PBEFieldPosition.None) { name = GetTrainerName(ffp.PokemonTrainer); } else { PBEBattlePokemon pokemon = ffp.PokemonTrainer.GetPokemon(ffp.Pokemon); name = GetPkmnName(pokemon, true); } return string.Format("{0} could not get away!", name); } case PBEHazePacket _: { return "All stat changes were eliminated!"; } case PBEItemPacket ip: { PBEBattlePokemon itemHolder = ip.ItemHolderTrainer.GetPokemon(ip.ItemHolder); PBEBattlePokemon pokemon2 = ip.Pokemon2Trainer.GetPokemon(ip.Pokemon2); bool itemHolderCaps = true, pokemon2Caps = false; string message; switch (ip.Item) { case PBEItem.AguavBerry: case PBEItem.BerryJuice: case PBEItem.FigyBerry: case PBEItem.IapapaBerry: case PBEItem.MagoBerry: case PBEItem.OranBerry: case PBEItem.SitrusBerry: case PBEItem.WikiBerry: { switch (ip.ItemAction) { case PBEItemAction.Consumed: message = "{0} restored its health using its {2}!"; break; default: throw new InvalidDataException(nameof(ip.ItemAction)); } break; } case PBEItem.ApicotBerry: case PBEItem.GanlonBerry: case PBEItem.LiechiBerry: case PBEItem.PetayaBerry: case PBEItem.SalacBerry: case PBEItem.StarfBerry: { switch (ip.ItemAction) { case PBEItemAction.Consumed: message = "{0} used its {2}!"; break; default: throw new InvalidDataException(nameof(ip.ItemAction)); } break; } case PBEItem.BugGem: case PBEItem.DarkGem: case PBEItem.DragonGem: case PBEItem.ElectricGem: case PBEItem.FightingGem: case PBEItem.FireGem: case PBEItem.FlyingGem: case PBEItem.GhostGem: case PBEItem.GrassGem: case PBEItem.GroundGem: case PBEItem.IceGem: case PBEItem.NormalGem: case PBEItem.PoisonGem: case PBEItem.PsychicGem: case PBEItem.RockGem: case PBEItem.SteelGem: case PBEItem.WaterGem: { switch (ip.ItemAction) { case PBEItemAction.Consumed: message = "The {2} strengthened {0}'s power!"; itemHolderCaps = false; break; default: throw new InvalidDataException(nameof(ip.ItemAction)); } break; } case PBEItem.BlackSludge: { switch (ip.ItemAction) { case PBEItemAction.Damage: message = "{0} is hurt by its {2}!"; break; case PBEItemAction.RestoredHP: message = "{0} restored a little HP using its {2}!"; break; default: throw new InvalidDataException(nameof(ip.ItemAction)); } break; } case PBEItem.DestinyKnot: { switch (ip.ItemAction) { case PBEItemAction.Announced: message = "{0}'s {2} activated!"; break; default: throw new InvalidDataException(nameof(ip.ItemAction)); } break; } case PBEItem.FlameOrb: { switch (ip.ItemAction) { case PBEItemAction.Announced: message = "{0} was burned by its {2}!"; break; default: throw new InvalidDataException(nameof(ip.ItemAction)); } break; } case PBEItem.FocusBand: { switch (ip.ItemAction) { case PBEItemAction.Damage: message = "{0} hung on using its {2}!"; break; default: throw new InvalidDataException(nameof(ip.ItemAction)); } break; } case PBEItem.FocusSash: { switch (ip.ItemAction) { case PBEItemAction.Consumed: message = "{0} hung on using its {2}!"; break; default: throw new InvalidDataException(nameof(ip.ItemAction)); } break; } case PBEItem.Leftovers: { switch (ip.ItemAction) { case PBEItemAction.RestoredHP: message = "{0} restored a little HP using its {2}!"; break; default: throw new InvalidDataException(nameof(ip.ItemAction)); } break; } case PBEItem.LifeOrb: { switch (ip.ItemAction) { case PBEItemAction.Damage: message = "{0} is hurt by its {2}!"; break; default: throw new InvalidDataException(nameof(ip.ItemAction)); } break; } case PBEItem.PowerHerb: { switch (ip.ItemAction) { case PBEItemAction.Consumed: message = "{0} became fully charged due to its {2}!"; break; default: throw new InvalidDataException(nameof(ip.ItemAction)); } break; } case PBEItem.RockyHelmet: { switch (ip.ItemAction) { case PBEItemAction.Damage: message = "{1} was hurt by the {2}!"; pokemon2Caps = true; break; default: throw new InvalidDataException(nameof(ip.ItemAction)); } break; } case PBEItem.SmokeBall: { switch (ip.ItemAction) { case PBEItemAction.Announced: message = "{0} used its {2}!"; break; default: throw new InvalidDataException(nameof(ip.ItemAction)); } break; } case PBEItem.ToxicOrb: { switch (ip.ItemAction) { case PBEItemAction.Announced: message = "{0} was badly poisoned by its {2}!"; break; default: throw new InvalidDataException(nameof(ip.ItemAction)); } break; } default: throw new InvalidDataException(nameof(ip.Item)); } return string.Format(message, GetPkmnName(itemHolder, itemHolderCaps), GetPkmnName(pokemon2, pokemon2Caps), PBEDataProvider.Instance.GetItemName(ip.Item).English); } case PBEItemTurnPacket itp: { PBEBattlePokemon itemUser = itp.ItemUserTrainer.GetPokemon(itp.ItemUser); string itemEnglish = PBEDataProvider.Instance.GetItemName(itp.Item).English; switch (itp.ItemAction) { case PBEItemTurnAction.Attempt: { string word; if (PBEDataUtils.AllBalls.Contains(itp.Item)) { word = "threw"; } else { word = "used"; } return string.Format("{0} {1} the {2}.", GetTrainerName(itemUser.Trainer), word, itemEnglish); } case PBEItemTurnAction.NoEffect: { if (PBEDataUtils.AllBalls.Contains(itp.Item)) { return "The trainer blocked the ball! Don't be a thief!"; } return string.Format("The {0} had no effect.", itemEnglish); } case PBEItemTurnAction.Success: { //string message; switch (itp.Item) { // No "success" items yet default: throw new InvalidDataException(nameof(itp.Item)); } //return string.Format(message, GetPkmnName(itemUser, true), itemEnglish); } default: throw new InvalidDataException(nameof(itp.ItemAction)); } } case PBEMoveCritPacket mcp: { PBEBattlePokemon victim = mcp.VictimTrainer.GetPokemon(mcp.Victim); return string.Format("A critical hit on {0}!", GetPkmnName(victim, false)); } case PBEMovePPChangedPacket mpcp: { PBEBattlePokemon moveUser = mpcp.MoveUserTrainer.GetPokemon(mpcp.MoveUser); return string.Format("{0}'s {1} {3} {2} PP!", GetPkmnName(moveUser, true), PBEDataProvider.Instance.GetMoveName(mpcp.Move).English, Math.Abs(mpcp.AmountReduced), mpcp.AmountReduced >= 0 ? "lost" : "gained"); } case PBEMoveResultPacket mrp: { PBEBattlePokemon moveUser = mrp.MoveUserTrainer.GetPokemon(mrp.MoveUser); PBEBattlePokemon pokemon2 = mrp.Pokemon2Trainer.GetPokemon(mrp.Pokemon2); bool pokemon2Caps = true; string message; switch (mrp.Result) { case PBEResult.Ineffective_Ability: message = "{1} is protected by its Ability!"; break; case PBEResult.Ineffective_Gender: message = "It doesn't affect {1}..."; pokemon2Caps = false; break; case PBEResult.Ineffective_Level: message = "{1} is protected by its level!"; break; case PBEResult.Ineffective_MagnetRise: message = $"{{1}} is protected by {PBEDataProvider.Instance.GetMoveName(PBEMove.MagnetRise).English}!"; break; case PBEResult.Ineffective_Safeguard: message = $"{{1}} is protected by {PBEDataProvider.Instance.GetMoveName(PBEMove.Safeguard).English}!"; break; case PBEResult.Ineffective_Stat: case PBEResult.Ineffective_Status: case PBEResult.InvalidConditions: message = "But it failed!"; break; case PBEResult.Ineffective_Substitute: message = $"{{1}} is protected by {PBEDataProvider.Instance.GetMoveName(PBEMove.Substitute).English}!"; break; case PBEResult.Ineffective_Type: message = "{1} is protected by its Type!"; break; case PBEResult.Missed: message = "{0}'s attack missed {1}!"; pokemon2Caps = false; break; case PBEResult.NoTarget: message = "But there was no target..."; break; case PBEResult.NotVeryEffective_Type: message = "It's not very effective on {1}..."; pokemon2Caps = false; break; case PBEResult.SuperEffective_Type: message = "It's super effective on {1}!"; pokemon2Caps = false; break; default: throw new InvalidDataException(nameof(mrp.Result)); } return string.Format(message, GetPkmnName(moveUser, true), GetPkmnName(pokemon2, pokemon2Caps)); } case PBEMoveUsedPacket mup: { PBEBattlePokemon moveUser = mup.MoveUserTrainer.GetPokemon(mup.MoveUser); return string.Format("{0} used {1}!", GetPkmnName(moveUser, true), PBEDataProvider.Instance.GetMoveName(mup.Move).English); } case PBEPkmnFaintedPacket pfp: { PBEBattlePokemon pokemon = pfp.PokemonTrainer.GetPokemon(pfp.Pokemon); return string.Format("{0} fainted!", GetPkmnName(pokemon, true)); } case PBEPkmnEXPEarnedPacket peep: { PBEBattlePokemon pokemon = peep.PokemonTrainer.GetPokemon(peep.Pokemon); return string.Format("{0} earned {1} EXP point(s)!", GetPkmnName(pokemon, true), peep.Earned); } case PBEPkmnFaintedPacket_Hidden pfph: { PBEBattlePokemon pokemon = pfph.PokemonTrainer.GetPokemon(pfph.OldPosition); return string.Format("{0} fainted!", GetPkmnName(pokemon, true)); } case IPBEPkmnFormChangedPacket pfcp: { PBEBattlePokemon pokemon = pfcp.PokemonTrainer.GetPokemon(pfcp.Pokemon); return string.Format("{0}'s new form is {1}!", GetPkmnName(pokemon, true), PBEDataProvider.Instance.GetFormName(pokemon.Species, pfcp.NewForm).English); } case PBEPkmnHPChangedPacket phcp: { PBEBattlePokemon pokemon = phcp.PokemonTrainer.GetPokemon(phcp.Pokemon); float percentageChange = phcp.NewHPPercentage - phcp.OldHPPercentage; float absPercentageChange = Math.Abs(percentageChange); if (showRawHP || userTrainer == pokemon.Trainer) // Owner should see raw values { int change = phcp.NewHP - phcp.OldHP; int absChange = Math.Abs(change); return string.Format("{0} {1} {2} ({3:P2}) HP!", GetPkmnName(pokemon, true), change <= 0 ? "lost" : "restored", absChange, absPercentageChange); } return DoHiddenHP(pokemon, percentageChange, absPercentageChange); } case PBEPkmnHPChangedPacket_Hidden phcph: { PBEBattlePokemon pokemon = phcph.PokemonTrainer.GetPokemon(phcph.Pokemon); float percentageChange = phcph.NewHPPercentage - phcph.OldHPPercentage; float absPercentageChange = Math.Abs(percentageChange); return DoHiddenHP(pokemon, percentageChange, absPercentageChange); } case PBEPkmnLevelChangedPacket plcp: { PBEBattlePokemon pokemon = plcp.PokemonTrainer.GetPokemon(plcp.Pokemon); return string.Format("{0} grew to level {1}!", GetPkmnName(pokemon, true), plcp.NewLevel); } case PBEPkmnStatChangedPacket pscp: { PBEBattlePokemon pokemon = pscp.PokemonTrainer.GetPokemon(pscp.Pokemon); string statName, message; switch (pscp.Stat) { case PBEStat.Accuracy: statName = "Accuracy"; break; case PBEStat.Attack: statName = "Attack"; break; case PBEStat.Defense: statName = "Defense"; break; case PBEStat.Evasion: statName = "Evasion"; break; case PBEStat.SpAttack: statName = "Special Attack"; break; case PBEStat.SpDefense: statName = "Special Defense"; break; case PBEStat.Speed: statName = "Speed"; break; default: throw new InvalidDataException(nameof(pscp.Stat)); } int change = pscp.NewValue - pscp.OldValue; switch (change) { case -2: message = "harshly fell"; break; case -1: message = "fell"; break; case +1: message = "rose"; break; case +2: message = "rose sharply"; break; default: { if (change == 0 && pscp.NewValue == -battle.Settings.MaxStatChange) { message = "won't go lower"; } else if (change == 0 && pscp.NewValue == battle.Settings.MaxStatChange) { message = "won't go higher"; } else if (change <= -3) { message = "severely fell"; } else if (change >= +3) { message = "rose drastically"; } else { throw new Exception(); } break; } } return string.Format("{0}'s {1} {2}!", GetPkmnName(pokemon, true), statName, message); } case IPBEPkmnSwitchInPacket psip: { if (!psip.Forced) { return string.Format("{1} sent out {0}!", psip.SwitchIns.Select(s => s.Nickname).ToArray().Andify(), GetTrainerName(psip.Trainer)); } goto bottom; } case PBEPkmnSwitchOutPacket psop: { if (!psop.Forced) { PBEBattlePokemon pokemon = psop.PokemonTrainer.GetPokemon(psop.Pokemon); return string.Format("{1} withdrew {0}!", pokemon.KnownNickname, GetTrainerName(psop.PokemonTrainer)); } goto bottom; } case PBEPkmnSwitchOutPacket_Hidden psoph: { if (!psoph.Forced) { PBEBattlePokemon pokemon = psoph.PokemonTrainer.GetPokemon(psoph.OldPosition); return string.Format("{1} withdrew {0}!", pokemon.KnownNickname, GetTrainerName(psoph.PokemonTrainer)); } goto bottom; } case PBEPsychUpPacket pup: { PBEBattlePokemon user = pup.UserTrainer.GetPokemon(pup.User); PBEBattlePokemon target = pup.TargetTrainer.GetPokemon(pup.Target); return string.Format("{0} copied {1}'s stat changes!", GetPkmnName(user, true), GetPkmnName(target, false)); } case PBEReflectTypePacket rtp: { PBEBattlePokemon user = rtp.UserTrainer.GetPokemon(rtp.User); PBEBattlePokemon target = rtp.TargetTrainer.GetPokemon(rtp.Target); string type1Str = PBEDataProvider.Instance.GetTypeName(rtp.Type1).English; return string.Format("{0} copied {1}'s {2}", GetPkmnName(user, true), GetPkmnName(target, false), rtp.Type2 == PBEType.None ? $"{type1Str} type!" : $"{type1Str} and {PBEDataProvider.Instance.GetTypeName(rtp.Type2).English} types!"); } case PBEReflectTypePacket_Hidden rtph: { PBEBattlePokemon user = rtph.UserTrainer.GetPokemon(rtph.User); PBEBattlePokemon target = rtph.TargetTrainer.GetPokemon(rtph.Target); return string.Format("{0} copied {1}'s types!", GetPkmnName(user, true), GetPkmnName(target, false)); } case PBESpecialMessagePacket smp: // TODO: Clean { string message; switch (smp.Message) { case PBESpecialMessage.DraggedOut: message = string.Format("{0} was dragged out!", GetPkmnName(((PBETrainer)smp.Params[0]).GetPokemon((PBEFieldPosition)smp.Params[1]), true)); break; case PBESpecialMessage.Endure: message = string.Format("{0} endured the hit!", GetPkmnName(((PBETrainer)smp.Params[0]).GetPokemon((PBEFieldPosition)smp.Params[1]), true)); break; case PBESpecialMessage.HPDrained: message = string.Format("{0} had its energy drained!", GetPkmnName(((PBETrainer)smp.Params[0]).GetPokemon((PBEFieldPosition)smp.Params[1]), true)); break; case PBESpecialMessage.Magnitude: message = string.Format("Magnitude {0}!", (byte)smp.Params[0]); break; case PBESpecialMessage.MultiHit: message = string.Format("Hit {0} time(s)!", (byte)smp.Params[0]); break; case PBESpecialMessage.NothingHappened: message = "But nothing happened!"; break; case PBESpecialMessage.OneHitKnockout: message = "It's a one-hit KO!"; break; case PBESpecialMessage.PainSplit: message = "The battlers shared their pain!"; break; case PBESpecialMessage.PayDay: message = "Coins were scattered everywhere!"; break; case PBESpecialMessage.Recoil: message = string.Format("{0} is damaged by recoil!", GetPkmnName(((PBETrainer)smp.Params[0]).GetPokemon((PBEFieldPosition)smp.Params[1]), true)); break; case PBESpecialMessage.Struggle: message = string.Format("{0} has no moves left!", GetPkmnName(((PBETrainer)smp.Params[0]).GetPokemon((PBEFieldPosition)smp.Params[1]), true)); break; default: throw new InvalidDataException(nameof(smp.Message)); } return message; } case PBEStatus1Packet s1p: { PBEBattlePokemon status1Receiver = s1p.Status1ReceiverTrainer.GetPokemon(s1p.Status1Receiver); string message; switch (s1p.Status1) { case PBEStatus1.Asleep: { switch (s1p.StatusAction) { case PBEStatusAction.Added: message = "{0} fell asleep!"; break; case PBEStatusAction.CausedImmobility: message = "{0} is fast asleep."; break; case PBEStatusAction.Cleared: case PBEStatusAction.Ended: message = "{0} woke up!"; break; default: throw new InvalidDataException(nameof(s1p.StatusAction)); } break; } case PBEStatus1.BadlyPoisoned: { switch (s1p.StatusAction) { case PBEStatusAction.Added: message = "{0} was badly poisoned!"; break; case PBEStatusAction.Cleared: message = "{0} was cured of its poisoning."; break; case PBEStatusAction.Damage: message = "{0} was hurt by poison!"; break; default: throw new InvalidDataException(nameof(s1p.StatusAction)); } break; } case PBEStatus1.Burned: { switch (s1p.StatusAction) { case PBEStatusAction.Added: message = "{0} was burned!"; break; case PBEStatusAction.Cleared: message = "{0}'s burn was healed."; break; case PBEStatusAction.Damage: message = "{0} was hurt by its burn!"; break; default: throw new InvalidDataException(nameof(s1p.StatusAction)); } break; } case PBEStatus1.Frozen: { switch (s1p.StatusAction) { case PBEStatusAction.Added: message = "{0} was frozen solid!"; break; case PBEStatusAction.CausedImmobility: message = "{0} is frozen solid!"; break; case PBEStatusAction.Cleared: case PBEStatusAction.Ended: message = "{0} thawed out!"; break; default: throw new InvalidDataException(nameof(s1p.StatusAction)); } break; } case PBEStatus1.Paralyzed: { switch (s1p.StatusAction) { case PBEStatusAction.Added: message = "{0} is paralyzed! It may be unable to move!"; break; case PBEStatusAction.CausedImmobility: message = "{0} is paralyzed! It can't move!"; break; case PBEStatusAction.Cleared: message = "{0} was cured of paralysis."; break; default: throw new InvalidDataException(nameof(s1p.StatusAction)); } break; } case PBEStatus1.Poisoned: { switch (s1p.StatusAction) { case PBEStatusAction.Added: message = "{0} was poisoned!"; break; case PBEStatusAction.Cleared: message = "{0} was cured of its poisoning."; break; case PBEStatusAction.Damage: message = "{0} was hurt by poison!"; break; default: throw new InvalidDataException(nameof(s1p.StatusAction)); } break; } default: throw new InvalidDataException(nameof(s1p.Status1)); } return string.Format(message, GetPkmnName(status1Receiver, true)); } case PBEStatus2Packet s2p: { PBEBattlePokemon status2Receiver = s2p.Status2ReceiverTrainer.GetPokemon(s2p.Status2Receiver); PBEBattlePokemon pokemon2 = s2p.Pokemon2Trainer.GetPokemon(s2p.Pokemon2); string message; bool status2ReceiverCaps = true, pokemon2Caps = false; switch (s2p.Status2) { case PBEStatus2.Airborne: { switch (s2p.StatusAction) { case PBEStatusAction.Added: message = "{0} flew up high!"; break; case PBEStatusAction.Ended: goto bottom; default: throw new InvalidDataException(nameof(s2p.StatusAction)); } break; } case PBEStatus2.Confused: { switch (s2p.StatusAction) { case PBEStatusAction.Added: message = "{0} became confused!"; break; case PBEStatusAction.Announced: message = "{0} is confused!"; break; case PBEStatusAction.Cleared: case PBEStatusAction.Ended: message = "{0} snapped out of its confusion."; break; case PBEStatusAction.Damage: message = "It hurt itself in its confusion!"; break; default: throw new InvalidDataException(nameof(s2p.StatusAction)); } break; } case PBEStatus2.Cursed: { switch (s2p.StatusAction) { case PBEStatusAction.Added: message = "{1} cut its own HP and laid a curse on {0}!"; status2ReceiverCaps = false; pokemon2Caps = true; break; case PBEStatusAction.Damage: message = "{0} is afflicted by the curse!"; break; default: throw new InvalidDataException(nameof(s2p.StatusAction)); } break; } case PBEStatus2.Disguised: { switch (s2p.StatusAction) { case PBEStatusAction.Ended: message = "{0}'s illusion wore off!"; break; default: throw new InvalidDataException(nameof(s2p.StatusAction)); } break; } case PBEStatus2.Flinching: { switch (s2p.StatusAction) { case PBEStatusAction.CausedImmobility: message = "{0} flinched and couldn't move!"; break; case PBEStatusAction.Ended: goto bottom; default: throw new InvalidDataException(nameof(s2p.StatusAction)); } break; } case PBEStatus2.Identified: case PBEStatus2.MiracleEye: { switch (s2p.StatusAction) { case PBEStatusAction.Added: message = "{0} was identified!"; break; default: throw new InvalidDataException(nameof(s2p.StatusAction)); } break; } case PBEStatus2.HelpingHand: { switch (s2p.StatusAction) { case PBEStatusAction.Added: message = "{1} is ready to help {0}!"; status2ReceiverCaps = false; pokemon2Caps = true; break; case PBEStatusAction.Ended: goto bottom; default: throw new InvalidDataException(nameof(s2p.StatusAction)); } break; } case PBEStatus2.Infatuated: { switch (s2p.StatusAction) { case PBEStatusAction.Added: message = "{0} fell in love with {1}!"; break; case PBEStatusAction.Announced: message = "{0} is in love with {1}!"; break; case PBEStatusAction.CausedImmobility: message = "{0} is immobilized by love!"; break; case PBEStatusAction.Cleared: case PBEStatusAction.Ended: message = "{0} got over its infatuation."; break; default: throw new InvalidDataException(nameof(s2p.StatusAction)); } break; } case PBEStatus2.LeechSeed: { switch (s2p.StatusAction) { case PBEStatusAction.Added: message = "{0} was seeded!"; break; case PBEStatusAction.Damage: message = "{0}'s health is sapped by Leech Seed!"; break; default: throw new InvalidDataException(nameof(s2p.StatusAction)); } break; } case PBEStatus2.LockOn: { switch (s2p.StatusAction) { case PBEStatusAction.Added: message = "{0} took aim at {1}!"; break; case PBEStatusAction.Ended: goto bottom; default: throw new InvalidDataException(nameof(s2p.StatusAction)); } break; } case PBEStatus2.MagnetRise: { switch (s2p.StatusAction) { case PBEStatusAction.Added: message = "{0} levitated with electromagnetism!"; break; case PBEStatusAction.Ended: message = "{0}'s electromagnetism wore off!"; break; default: throw new InvalidDataException(nameof(s2p.StatusAction)); } break; } case PBEStatus2.Nightmare: { switch (s2p.StatusAction) { case PBEStatusAction.Added: message = "{0} began having a nightmare!"; break; case PBEStatusAction.Damage: message = "{0} is locked in a nightmare!"; break; case PBEStatusAction.Ended: goto bottom; default: throw new InvalidDataException(nameof(s2p.StatusAction)); } break; } case PBEStatus2.PowerTrick: { switch (s2p.StatusAction) { case PBEStatusAction.Added: message = "{0} switched its Attack and Defense!"; break; case PBEStatusAction.Ended: goto bottom; default: throw new InvalidDataException(nameof(s2p.StatusAction)); } break; } case PBEStatus2.Protected: { switch (s2p.StatusAction) { case PBEStatusAction.Added: case PBEStatusAction.Damage: message = "{0} protected itself!"; break; case PBEStatusAction.Cleared: message = "{1} broke through {0}'s protection!"; status2ReceiverCaps = false; pokemon2Caps = true; break; case PBEStatusAction.Ended: goto bottom; default: throw new InvalidDataException(nameof(s2p.StatusAction)); } break; } case PBEStatus2.Pumped: { switch (s2p.StatusAction) { case PBEStatusAction.Added: message = "{0} is getting pumped!"; break; default: throw new InvalidDataException(nameof(s2p.StatusAction)); } break; } case PBEStatus2.Roost: { switch (s2p.StatusAction) { case PBEStatusAction.Added: case PBEStatusAction.Ended: goto bottom; default: throw new InvalidDataException(nameof(s2p.StatusAction)); } } case PBEStatus2.ShadowForce: { switch (s2p.StatusAction) { case PBEStatusAction.Added: message = "{0} vanished instantly!"; break; case PBEStatusAction.Ended: goto bottom; default: throw new InvalidDataException(nameof(s2p.StatusAction)); } break; } case PBEStatus2.Substitute: { switch (s2p.StatusAction) { case PBEStatusAction.Added: message = "{0} put in a substitute!"; break; case PBEStatusAction.Damage: message = "The substitute took damage for {0}!"; status2ReceiverCaps = false; break; case PBEStatusAction.Ended: message = "{0}'s substitute faded!"; break; default: throw new InvalidDataException(nameof(s2p.StatusAction)); } break; } case PBEStatus2.Transformed: { switch (s2p.StatusAction) { case PBEStatusAction.Added: message = "{0} transformed into {1}!"; break; default: throw new InvalidDataException(nameof(s2p.StatusAction)); } break; } case PBEStatus2.Underground: { switch (s2p.StatusAction) { case PBEStatusAction.Added: message = "{0} burrowed its way under the ground!"; break; case PBEStatusAction.Ended: goto bottom; default: throw new InvalidDataException(nameof(s2p.StatusAction)); } break; } case PBEStatus2.Underwater: { switch (s2p.StatusAction) { case PBEStatusAction.Added: message = "{0} hid underwater!"; break; case PBEStatusAction.Ended: goto bottom; default: throw new InvalidDataException(nameof(s2p.StatusAction)); } break; } default: throw new InvalidDataException(nameof(s2p.Status2)); } return string.Format(message, GetPkmnName(status2Receiver, status2ReceiverCaps), GetPkmnName(pokemon2, pokemon2Caps)); } case PBETeamStatusPacket tsp: { string message; bool teamCaps = true; switch (tsp.TeamStatus) { case PBETeamStatus.LightScreen: { switch (tsp.TeamStatusAction) { case PBETeamStatusAction.Added: message = "Light Screen raised {0} team's Special Defense!"; teamCaps = false; break; case PBETeamStatusAction.Cleared: case PBETeamStatusAction.Ended: message = "{0} team's Light Screen wore off!"; break; default: throw new InvalidDataException(nameof(tsp.TeamStatusAction)); } break; } case PBETeamStatus.LuckyChant: { switch (tsp.TeamStatusAction) { case PBETeamStatusAction.Added: message = "The Lucky Chant shielded {0} team from critical hits!"; teamCaps = false; break; case PBETeamStatusAction.Ended: message = "{0} team's Lucky Chant wore off!"; break; default: throw new InvalidDataException(nameof(tsp.TeamStatusAction)); } break; } case PBETeamStatus.QuickGuard: { switch (tsp.TeamStatusAction) { case PBETeamStatusAction.Added: message = "Quick Guard protected {0} team!"; teamCaps = false; break; case PBETeamStatusAction.Cleared: message = "{0} team's Quick Guard was destroyed!"; break; case PBETeamStatusAction.Ended: goto bottom; default: throw new InvalidDataException(nameof(tsp.TeamStatusAction)); } break; } case PBETeamStatus.Reflect: { switch (tsp.TeamStatusAction) { case PBETeamStatusAction.Added: message = "Reflect raised {0} team's Defense!"; teamCaps = false; break; case PBETeamStatusAction.Cleared: case PBETeamStatusAction.Ended: message = "{0} team's Reflect wore off!"; break; default: throw new InvalidDataException(nameof(tsp.TeamStatusAction)); } break; } case PBETeamStatus.Safeguard: { switch (tsp.TeamStatusAction) { case PBETeamStatusAction.Added: message = "{0} team became cloaked in a mystical veil!"; break; case PBETeamStatusAction.Ended: message = "{0} team is no longer protected by Safeguard!"; break; default: throw new InvalidDataException(nameof(tsp.TeamStatusAction)); } break; } case PBETeamStatus.Spikes: { switch (tsp.TeamStatusAction) { case PBETeamStatusAction.Added: message = "Spikes were scattered all around the feet of {0} team!"; teamCaps = false; break; //case PBETeamStatusAction.Cleared: message = "The spikes disappeared from around {0} team's feet!"; teamCaps = false; break; default: throw new InvalidDataException(nameof(tsp.TeamStatusAction)); } break; } case PBETeamStatus.StealthRock: { switch (tsp.TeamStatusAction) { case PBETeamStatusAction.Added: message = "Pointed stones float in the air around {0} team!"; teamCaps = false; break; //case PBETeamStatusAction.Cleared: message = "The pointed stones disappeared from around {0} team!"; teamCaps = false; break; default: throw new InvalidDataException(nameof(tsp.TeamStatusAction)); } break; } case PBETeamStatus.Tailwind: { switch (tsp.TeamStatusAction) { case PBETeamStatusAction.Added: message = "The tailwind blew from behind {0} team!"; teamCaps = false; break; case PBETeamStatusAction.Ended: message = "{0} team's tailwind petered out!"; break; default: throw new InvalidDataException(nameof(tsp.TeamStatusAction)); } break; } case PBETeamStatus.ToxicSpikes: { switch (tsp.TeamStatusAction) { case PBETeamStatusAction.Added: message = "Poison spikes were scattered all around {0} team's feet!"; break; case PBETeamStatusAction.Cleared: message = "The poison spikes disappeared from around {0} team's feet!"; break; default: throw new InvalidDataException(nameof(tsp.TeamStatusAction)); } break; } case PBETeamStatus.WideGuard: { switch (tsp.TeamStatusAction) { case PBETeamStatusAction.Added: message = "Wide Guard protected {0} team!"; break; case PBETeamStatusAction.Cleared: message = "{0} team's Wide Guard was destroyed!"; break; case PBETeamStatusAction.Ended: goto bottom; default: throw new InvalidDataException(nameof(tsp.TeamStatusAction)); } break; } default: throw new InvalidDataException(nameof(tsp.TeamStatus)); } return string.Format(message, GetTeamName(tsp.Team, teamCaps)); } case PBETeamStatusDamagePacket tsdp: { PBEBattlePokemon damageVictim = tsdp.DamageVictimTrainer.GetPokemon(tsdp.DamageVictim); string message; bool damageVictimCaps = false; switch (tsdp.TeamStatus) { case PBETeamStatus.QuickGuard: message = "Quick Guard protected {0}!"; break; case PBETeamStatus.Spikes: message = "{0} is hurt by the spikes!"; damageVictimCaps = true; break; case PBETeamStatus.StealthRock: message = "Pointed stones dug into {0}!"; break; case PBETeamStatus.WideGuard: message = "Wide Guard protected {0}!"; break; default: throw new InvalidDataException(nameof(tsdp.TeamStatus)); } return string.Format(message, GetPkmnName(damageVictim, damageVictimCaps)); } case PBETypeChangedPacket tcp: { PBEBattlePokemon pokemon = tcp.PokemonTrainer.GetPokemon(tcp.Pokemon); string type1Str = PBEDataProvider.Instance.GetTypeName(tcp.Type1).English; return string.Format("{0} transformed into the {1}", GetPkmnName(pokemon, true), tcp.Type2 == PBEType.None ? $"{type1Str} type!" : $"{type1Str} and {PBEDataProvider.Instance.GetTypeName(tcp.Type2).English} types!"); } case PBEWeatherPacket wp: { switch (wp.Weather) { case PBEWeather.Hailstorm: { switch (wp.WeatherAction) { case PBEWeatherAction.Added: return "It started to hail!"; case PBEWeatherAction.Ended: return "The hail stopped."; default: throw new InvalidDataException(nameof(wp.WeatherAction)); } } case PBEWeather.HarshSunlight: { switch (wp.WeatherAction) { case PBEWeatherAction.Added: return "The sunlight turned harsh!"; case PBEWeatherAction.Ended: return "The sunlight faded."; default: throw new InvalidDataException(nameof(wp.WeatherAction)); } } case PBEWeather.Rain: { switch (wp.WeatherAction) { case PBEWeatherAction.Added: return "It started to rain!"; case PBEWeatherAction.Ended: return "The rain stopped."; default: throw new InvalidDataException(nameof(wp.WeatherAction)); } } case PBEWeather.Sandstorm: { switch (wp.WeatherAction) { case PBEWeatherAction.Added: return "A sandstorm kicked up!"; case PBEWeatherAction.Ended: return "The sandstorm subsided."; default: throw new InvalidDataException(nameof(wp.WeatherAction)); } } default: throw new InvalidDataException(nameof(wp.Weather)); } } case PBEWeatherDamagePacket wdp: { PBEBattlePokemon damageVictim = wdp.DamageVictimTrainer.GetPokemon(wdp.DamageVictim); string message; switch (wdp.Weather) { case PBEWeather.Hailstorm: message = "{0} is buffeted by the hail!"; break; case PBEWeather.Sandstorm: message = "{0} is buffeted by the sandstorm!"; break; default: throw new InvalidDataException(nameof(wdp.Weather)); } return string.Format(message, GetPkmnName(damageVictim, true)); } case IPBEWildPkmnAppearedPacket wpap: { return string.Format("{0}{1} appeared!", wpap.Pokemon.Count == 1 ? "A wild " : "Oh! A wild ", wpap.Pokemon.Select(s => s.Nickname).ToArray().Andify()); } case PBEActionsRequestPacket arp: { return string.Format("{0} must submit actions for {1} Pokémon.", GetTrainerName(arp.Trainer), arp.Pokemon.Count); } case IPBEAutoCenterPacket _: { return "The battlers shifted to the center!"; } case PBEBattleResultPacket brp: { bool team0Caps = true; bool team1Caps = false; string message; switch (brp.BattleResult) { case PBEBattleResult.Team0Forfeit: message = "{0} forfeited."; break; case PBEBattleResult.Team0Win: message = "{0} defeated {1}!"; break; case PBEBattleResult.Team1Forfeit: message = "{1} forfeited."; team1Caps = true; break; case PBEBattleResult.Team1Win: message = "{1} defeated {0}!"; team0Caps = false; team1Caps = true; break; case PBEBattleResult.WildCapture: goto bottom; case PBEBattleResult.WildEscape: message = "{0} got away!"; break; case PBEBattleResult.WildFlee: message = "{1} got away!"; team1Caps = true; break; default: throw new InvalidDataException(nameof(brp.BattleResult)); } return string.Format(message, GetRawCombinedName(battle.Teams[0], team0Caps), GetRawCombinedName(battle.Teams[1], team1Caps)); } case PBESwitchInRequestPacket sirp: { return string.Format("{0} must send in {1} Pokémon.", GetTrainerName(sirp.Trainer), sirp.Amount); } case PBETurnBeganPacket tbp: { return string.Format("Turn {0} is starting.", tbp.TurnNumber); } } bottom: return null; } /// Writes battle events to in English. /// The battle that belongs to. /// The battle event packet. /// Thrown when or are null. public static void ConsoleBattleEventHandler(PBEBattle battle, IPBEPacket packet) { string? message = GetDefaultMessage(battle, packet, showRawHP: true); if (string.IsNullOrEmpty(message)) { return; } Console.WriteLine(message); } } ================================================ FILE: PokemonBattleEngine/Battle/BattleInventory.cs ================================================ using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngine.Packets; using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Kermalis.PokemonBattleEngine.Battle; public sealed class PBEBattleInventory : IReadOnlyDictionary { public sealed class PBEBattleInventorySlot { public PBEItem Item { get; } public uint Quantity { get; internal set; } internal PBEBattleInventorySlot(PBEItem item, uint quantity) { Item = item; Quantity = quantity; } } private readonly Dictionary _slots; public PBEBattleInventorySlot this[PBEItem key] => _slots[key]; public IEnumerable Keys => _slots.Keys; public IEnumerable Values => _slots.Values; public int Count => _slots.Count; internal PBEBattleInventory(IReadOnlyList<(PBEItem item, uint quantity)> items) { _slots = new Dictionary(items.Count); foreach ((PBEItem item, uint quantity) in items) { if (item == PBEItem.None || !Enum.IsDefined(item)) { throw new ArgumentOutOfRangeException(nameof(items), $"Invalid item returned: {item}"); } if (!_slots.TryGetValue(item, out PBEBattleInventorySlot? slot)) { slot = new PBEBattleInventorySlot(item, quantity); _slots.Add(item, slot); } else { slot.Quantity += quantity; } } } internal PBEBattleInventory(IReadOnlyList items) { _slots = new Dictionary(items.Count); foreach (PBEBattlePacket.PBETeamInfo.PBETrainerInfo.PBEInventorySlotInfo slot in items) { _slots.Add(slot.Item, new PBEBattleInventorySlot(slot.Item, slot.Quantity)); } } public bool ContainsKey(PBEItem key) { return _slots.ContainsKey(key); } public bool TryGetValue(PBEItem key, [NotNullWhen(true)] out PBEBattleInventorySlot? value) { return _slots.TryGetValue(key, out value); } public IEnumerator> GetEnumerator() { return _slots.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return _slots.GetEnumerator(); } private static readonly PBEBattleInventory _empty = new(Array.Empty<(PBEItem, uint)>()); internal static PBEBattleInventory Empty() { return _empty; } internal void Remove(PBEItem item) { _slots[item].Quantity--; } } ================================================ FILE: PokemonBattleEngine/Battle/BattleMoveset.cs ================================================ using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngine.Data.Utils; using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; namespace Kermalis.PokemonBattleEngine.Battle; public sealed class PBEBattleMoveset : IReadOnlyList { public sealed class PBEBattleMovesetSlot : INotifyPropertyChanged { private void OnPropertyChanged(string property) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); } public event PropertyChangedEventHandler? PropertyChanged; private PBEMove _move; public PBEMove Move { get => _move; set { if (_move != value) { _move = value; OnPropertyChanged(nameof(Move)); } } } private int _pp; public int PP { get => _pp; set { if (_pp != value) { _pp = value; OnPropertyChanged(nameof(PP)); } } } private int _maxPP; public int MaxPP { get => _maxPP; set { if (_maxPP != value) { _maxPP = value; OnPropertyChanged(nameof(MaxPP)); } } } internal PBEBattleMovesetSlot() { _move = PBEMove.MAX; } internal PBEBattleMovesetSlot(PBEMove move, int pp, int maxPP) { _move = move; _pp = pp; _maxPP = maxPP; } } private readonly PBEBattleMovesetSlot[] _list; public int Count => _list.Length; public PBEBattleMovesetSlot this[int index] { get { if (index >= _list.Length) { throw new ArgumentOutOfRangeException(nameof(index)); } return _list[index]; } } public PBEBattleMovesetSlot? this[PBEMove move] { get { for (int i = 0; i < _list.Length; i++) { PBEBattleMovesetSlot slot = _list[i]; if (slot.Move == move) { return slot; } } return null; } } internal PBEBattleMoveset(PBESettings settings) { int count = settings.NumMoves; _list = new PBEBattleMovesetSlot[count]; for (int i = 0; i < count; i++) { _list[i] = new PBEBattleMovesetSlot(); } } internal PBEBattleMoveset(PBESettings settings, PBEReadOnlyPartyMoveset moveset) { int count = moveset.Count; if (count != settings.NumMoves) { throw new ArgumentOutOfRangeException(nameof(moveset), $"Moveset count must be equal to \"{nameof(settings.NumMoves)}\" ({settings.NumMoves})."); } _list = new PBEBattleMovesetSlot[count]; for (int i = 0; i < count; i++) { PBEReadOnlyPartyMoveset.PBEReadOnlyPartyMovesetSlot slot = moveset[i]; PBEMove move = slot.Move; int maxPP = PBEDataUtils.CalcMaxPP(move, slot.PPUps, settings); _list[i] = new PBEBattleMovesetSlot(move, slot.PP, maxPP); } } internal PBEBattleMoveset(PBEBattleMoveset other) { int count = other._list.Length; _list = new PBEBattleMovesetSlot[count]; for (int i = 0; i < count; i++) { PBEBattleMovesetSlot oSlot = other._list[i]; _list[i] = new PBEBattleMovesetSlot(oSlot.Move, oSlot.PP, oSlot.MaxPP); } } public static int GetTransformPP(PBESettings settings, PBEMove move) { settings.ShouldBeReadOnly(nameof(settings)); if (move == PBEMove.None) { return 0; } if (move >= PBEMove.MAX) { throw new ArgumentOutOfRangeException(nameof(move)); } IPBEMoveData mData = PBEDataProvider.Instance.GetMoveData(move); if (!mData.IsMoveUsable()) { throw new ArgumentOutOfRangeException(nameof(move)); } return mData.PPTier == 0 ? 1 : settings.PPMultiplier; } public static int GetNonTransformPP(PBESettings settings, PBEMove move, byte ppUps) { settings.ShouldBeReadOnly(nameof(settings)); if (move == PBEMove.None) { return 0; } if (move >= PBEMove.MAX) { throw new ArgumentOutOfRangeException(nameof(move)); } IPBEMoveData mData = PBEDataProvider.Instance.GetMoveData(move); if (!mData.IsMoveUsable()) { throw new ArgumentOutOfRangeException(nameof(move)); } byte tier = mData.PPTier; return Math.Max(1, (tier * settings.PPMultiplier) + (tier * ppUps)); } internal static void DoTransform(PBEBattlePokemon user, PBEBattlePokemon target) { PBEBattleMoveset? targetKnownBackup = null; if (user.Trainer != target.Trainer) { targetKnownBackup = new PBEBattleMoveset(target.KnownMoves); } PBESettings settings = user.Battle.Settings; for (int i = 0; i < settings.NumMoves; i++) { PBEBattleMovesetSlot userMove = user.Moves._list[i]; PBEBattleMovesetSlot userKnownMove = user.KnownMoves._list[i]; PBEBattleMovesetSlot targetMove = target.Moves._list[i]; PBEBattleMovesetSlot targetKnownMove = target.KnownMoves._list[i]; PBEMove move; int pp; if (user.Trainer == target.Trainer) { move = targetMove.Move; pp = move == PBEMove.MAX ? 0 : GetTransformPP(settings, move); userMove.Move = move; userMove.PP = pp; userMove.MaxPP = pp; move = targetKnownMove.Move; pp = move == PBEMove.MAX ? 0 : GetTransformPP(settings, move); userKnownMove.Move = move; userKnownMove.PP = 0; userKnownMove.MaxPP = pp; } else { move = targetMove.Move; pp = move == PBEMove.MAX ? 0 : GetTransformPP(settings, move); userMove.Move = move; userMove.PP = pp; userMove.MaxPP = pp; targetKnownMove.Move = move; // Try to copy known PP from previous known moves PBEBattleMovesetSlot? bSlot = targetKnownBackup![move]; if (bSlot is null) // bSlot is null if the current move was not previously known { targetKnownMove.PP = 0; targetKnownMove.MaxPP = 0; } else { targetKnownMove.PP = bSlot.PP; targetKnownMove.MaxPP = bSlot.MaxPP; } userKnownMove.Move = move; userKnownMove.PP = 0; userKnownMove.MaxPP = pp; } } } internal ReadOnlyCollection ForTransformPacket() { var moves = new PBEMove[_list.Length]; for (int i = 0; i < _list.Length; i++) { moves[i] = _list[i].Move; } return new ReadOnlyCollection(moves); } // Reorders after one move is changed. It won't work if there are multiple culprit spots internal void Organize() { for (int i = 0; i < _list.Length - 1; i++) { PBEBattleMovesetSlot slot = _list[i]; if (slot.Move != PBEMove.None && slot.Move != PBEMove.MAX) { continue; // Skip populated slots } PBEBattleMovesetSlot nextSlot = _list[i + 1]; if (nextSlot.Move != PBEMove.None && nextSlot.Move != PBEMove.MAX) { _list[i] = nextSlot; _list[i + 1] = slot; // Swap slots since next slot has a move but current doesn't } } } internal void Reset(PBEBattleMoveset other) { for (int i = 0; i < _list.Length; i++) { PBEBattleMovesetSlot slot = _list[i]; PBEBattleMovesetSlot oSlot = other._list[i]; slot.Move = oSlot.Move; slot.PP = oSlot.PP; slot.MaxPP = oSlot.MaxPP; } } internal void SetUnknown() { for (int i = 0; i < _list.Length; i++) { PBEBattleMovesetSlot slot = _list[i]; slot.Move = PBEMove.MAX; slot.PP = 0; slot.MaxPP = 0; } } public bool Contains(PBEMove move) { return this[move] is not null; } public bool Contains(PBEMoveEffect effect) { for (int i = 0; i < _list.Length; i++) { PBEMove move = _list[i].Move; if (move != PBEMove.None && move != PBEMove.MAX && PBEDataProvider.Instance.GetMoveData(move).Effect == effect) { return true; } } return false; } // TODO: This is copied from PBEMovesetInterfaceExtensions public int CountMoves() { int num = 0; for (int i = 0; i < _list.Length; i++) { if (_list[i].Move != PBEMove.None) { num++; } } return num; } public IEnumerator GetEnumerator() { for (int i = 0; i < _list.Length; i++) { yield return _list[i]; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } ================================================ FILE: PokemonBattleEngine/Battle/BattlePokemon.cs ================================================ using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngine.Data.Utils; using Kermalis.PokemonBattleEngine.Packets; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; namespace Kermalis.PokemonBattleEngine.Battle; /// Represents a specific Pokémon during a battle. public sealed class PBEBattlePokemon : IPBEPokemonKnownTypes, IPBEPokemonTypes, IPBESpeciesForm { public PBEBattle Battle { get; } public PBETeam Team { get; } public PBETrainer Trainer { get; } public byte Id { get; } public bool IsWild => Team.IsWild; public bool IsLocallyHosted => Battle.IsLocallyHosted; public bool PBEIgnore { get; } public bool CanBattle => HP > 0 && !PBEIgnore; #region Basic Properties /// The Pokémon's current HP. public ushort HP { get; set; } /// The Pokémon's maximum HP. public ushort MaxHP { get; set; } /// The Pokémon's current HP as a percentage. public float HPPercentage { get; set; } /// The Pokémon's attack stat. public ushort Attack { get; set; } public sbyte AttackChange { get; set; } /// The Pokémon's defense stat. public ushort Defense { get; set; } public sbyte DefenseChange { get; set; } /// The Pokémon's special attack stat. public ushort SpAttack { get; set; } public sbyte SpAttackChange { get; set; } /// The Pokémon's special defense stat. public ushort SpDefense { get; set; } public sbyte SpDefenseChange { get; set; } /// The Pokémon's speed stat. public ushort Speed { get; set; } public sbyte SpeedChange { get; set; } public sbyte AccuracyChange { get; set; } public sbyte EvasionChange { get; set; } public PBEReadOnlyStatCollection? OriginalEffortValues { get; } public PBEStatCollection? EffortValues { get; } public PBEReadOnlyStatCollection? IndividualValues { get; } public byte Friendship { get; set; } public byte OriginalLevel { get; set; } /// The Pokémon's level. public byte Level { get; set; } public uint OriginalEXP { get; set; } public uint EXP { get; set; } /// The Pokémon's nature. public PBENature Nature { get; set; } /// The moveset the Pokémon had upon entering battle. public PBEReadOnlyPartyMoveset? OriginalMoveset { get; } /// The Pokémon's field position. public PBEFieldPosition FieldPosition { get; set; } /// The Pokémon's current ability. public PBEAbility Ability { get; set; } /// The ability the Pokémon is known to have. public PBEAbility KnownAbility { get; set; } /// The ability the Pokémon had upon entering battle. public PBEAbility OriginalAbility { get; set; } /// The ability the Pokémon will regain upon switching out, fainting, or the battle ending. public PBEAbility RevertAbility { get; set; } /// The Pokémon's gender. public PBEGender Gender { get; set; } /// The gender the Pokémon looks like (affected by transforming and disguising). public PBEGender KnownGender { get; set; } /// The Pokémon's current item. public PBEItem Item { get; set; } /// The item the Pokémon is known to have. public PBEItem KnownItem { get; set; } /// The item the Pokémon had upon entering battle. public PBEItem OriginalItem { get; set; } /// The Pokémon's current ball (affected by catching). public PBEItem CaughtBall { get; set; } /// The ball the Pokémon is known to be in (affected by disguising). public PBEItem KnownCaughtBall { get; set; } /// The ball the Pokémon was in upon entering battle. public PBEItem OriginalCaughtBall { get; set; } /// The moves the Pokémon currently has. public PBEBattleMoveset Moves { get; } /// The moves the Pokémon is known to have. public PBEBattleMoveset KnownMoves { get; } /// The nickname the Pokémon normally has. public string Nickname { get; set; } /// The nickname the Pokémon is known to have. public string KnownNickname { get; set; } /// The shininess the Pokémon normally has. public bool Shiny { get; set; } /// The shininess everyone sees the Pokémon has. public bool KnownShiny { get; set; } public bool Pokerus { get; set; } /// The current species of the Pokémon (affected by transforming and form changing). public PBESpecies Species { get; set; } /// The species everyone sees the Pokémon as (affected by transforming, disguising, and form changing). public PBESpecies KnownSpecies { get; set; } /// The species the Pokémon was upon entering battle. public PBESpecies OriginalSpecies { get; set; } public PBEForm Form { get; set; } public PBEForm KnownForm { get; set; } public PBEForm OriginalForm { get; set; } public PBEForm RevertForm { get; set; } public PBEStatus1 Status1 { get; set; } public PBEStatus1 OriginalStatus1 { get; set; } public PBEStatus2 Status2 { get; set; } public PBEStatus2 KnownStatus2 { get; set; } /// The Pokémon's first type. public PBEType Type1 { get; set; } /// The first type everyone believes the Pokémon has. public PBEType KnownType1 { get; set; } /// The Pokémon's second type. public PBEType Type2 { get; set; } /// The second type everyone believes the Pokémon has. public PBEType KnownType2 { get; set; } public float Weight { get; set; } public float KnownWeight { get; set; } #endregion #region Statuses /// The counter used for and . public byte Status1Counter { get; set; } /// The amount of turns the Pokémon will sleep for before waking. public byte SleepTurns { get; set; } /// The counter used for . public byte ConfusionCounter { get; set; } /// The amount of turns the Pokémon will be confused for before snapping out of it. public byte ConfusionTurns { get; set; } /// The Pokémon that is bound to. public PBEBattlePokemon? InfatuatedWithPokemon { get; set; } /// The amount of turns until ends. public byte MagnetRiseTurns { get; set; } /// The Pokémon that is bound to. public PBEBattlePokemon? LockOnPokemon { get; set; } public byte LockOnTurns { get; set; } /// The amount of times the Pokémon has successfully used , , and/or consecutively. public int Protection_Counter { get; set; } public bool Protection_Used { get; set; } public PBERoostTypes RoostTypes { get; set; } /// The position to return HP to on . public PBEFieldPosition SeededPosition { get; set; } /// The team responsible for . public PBETeam? SeededTeam { get; set; } /// The amount of HP the Pokémon's has left. public ushort SubstituteHP { get; set; } public PBEBattleMoveset TransformBackupMoves { get; } #endregion #region Actions /// if the Pokémon has successfully executed a move this turn. public bool HasUsedMoveThisTurn { get; set; } /// The action the Pokémon will try to perform when the turn is run. if the Pokémon just switched in, attempted to flee, etc. public PBETurnAction? TurnAction { get; set; } /// The move the Pokémon is forced to use by multi-turn moves. public PBEMove TempLockedMove { get; set; } // TODO: Tests - Does a pkmn lose its temp locked move if it runs out of pp on the move, all moves, or ever? (Some move can lower its pp while it's being used?) /// The targets the Pokémon is forced to target by multi-turn moves. public PBETurnTarget TempLockedTargets { get; set; } /// The move the Pokémon is forced to use by its choice item. public PBEMove ChoiceLockedMove { get; set; } // TODO: Tests - Does a pkmn lose its choice locked move if it runs out of pp on the move, all moves, or ever? #endregion #region Special Flags /// True if the Pokémon has successfully used which makes it succeptible to double damage from . public bool Minimize_Used { get; set; } /// The amount of turns left until a Pokémon with loses its hinderance. public byte SlowStart_HinderTurnsLeft { get; set; } /// True if the Pokémon was present at the start of the turn, which would allow to activate. public bool SpeedBoost_AbleToSpeedBoostThisTurn { get; set; } #endregion public List EXPPokemon { get; } = new(); #region Constructors private PBEBattlePokemon(PBETrainer trainer, byte id, PBESpecies species, PBEForm form, string nickname, byte level, uint exp, byte friendship, bool shiny, bool pokerus, PBEAbility ability, PBENature nature, PBEGender gender, PBEItem item, PBEItem caughtBall, PBEReadOnlyStatCollection evs, PBEReadOnlyStatCollection ivs, PBEReadOnlyPartyMoveset moves) { Battle = trainer.Battle; Team = trainer.Team; Trainer = trainer; Id = id; Species = OriginalSpecies = KnownSpecies = species; Form = OriginalForm = KnownForm = RevertForm = form; IPBEPokemonData pData = PBEDataProvider.Instance.GetPokemonData(species, form); KnownType1 = Type1 = pData.Type1; KnownType2 = Type2 = pData.Type2; KnownWeight = Weight = pData.Weight; Nickname = KnownNickname = nickname; Level = OriginalLevel = level; EXP = OriginalEXP = exp; Friendship = friendship; Shiny = KnownShiny = shiny; Pokerus = pokerus; Ability = OriginalAbility = RevertAbility = ability; KnownAbility = PBEAbility.MAX; Nature = nature; Gender = KnownGender = gender; Item = OriginalItem = item; KnownItem = (PBEItem)ushort.MaxValue; KnownCaughtBall = CaughtBall = OriginalCaughtBall = caughtBall; OriginalEffortValues = evs; EffortValues = new PBEStatCollection(evs); IndividualValues = new PBEReadOnlyStatCollection(ivs); SetStats(true, true); OriginalMoveset = moves; PBESettings settings = Battle.Settings; Moves = new PBEBattleMoveset(settings, moves); KnownMoves = new PBEBattleMoveset(settings); TransformBackupMoves = new PBEBattleMoveset(settings); } private PBEBattlePokemon(PBETrainer trainer, byte id, IPBEPokemon pkmn, PBEReadOnlyPartyMoveset moves) : this(trainer, id, pkmn.Species, pkmn.Form, pkmn.Nickname, pkmn.Level, pkmn.EXP, pkmn.Friendship, pkmn.Shiny, pkmn.Pokerus, pkmn.Ability, pkmn.Nature, pkmn.Gender, pkmn.Item, pkmn.CaughtBall, new PBEReadOnlyStatCollection(pkmn.EffortValues), new PBEReadOnlyStatCollection(pkmn.IndividualValues), moves) { PBEIgnore = pkmn.PBEIgnore; } internal PBEBattlePokemon(PBETrainer trainer, byte id, IPBEPokemon pkmn) : this(trainer, id, pkmn, new PBEReadOnlyPartyMoveset(trainer.Battle.Settings, pkmn.Moveset)) { trainer.Party.Add(this); } internal PBEBattlePokemon(PBETrainer trainer, byte id, IPBEPartyPokemon pkmn) : this(trainer, id, pkmn, new PBEReadOnlyPartyMoveset(pkmn.Moveset)) { ushort hp = pkmn.HP; if (hp > MaxHP) { throw new InvalidDataException(nameof(pkmn.HP)); } HP = hp; UpdateHPPercentage(); PBEStatus1 status1 = pkmn.Status1; if (status1 >= PBEStatus1.MAX) { throw new InvalidDataException(nameof(pkmn.Status1)); } if (status1 == PBEStatus1.BadlyPoisoned) { Status1Counter = 1; } Status1 = OriginalStatus1 = status1; byte sleepTurns = pkmn.SleepTurns; if (status1 != PBEStatus1.Asleep && sleepTurns != 0) { throw new InvalidDataException(nameof(pkmn.SleepTurns)); } SleepTurns = sleepTurns; trainer.Party.Add(this); } internal PBEBattlePokemon(PBETrainer trainer, PBEBattlePacket.PBETeamInfo.PBETrainerInfo.PBEBattlePokemonInfo info) : this(trainer, info.Id, info.Species, info.Form, info.Nickname, info.Level, info.EXP, info.Friendship, info.Shiny, info.Pokerus, info.Ability, info.Nature, info.Gender, info.Item, info.CaughtBall, info.EffortValues, info.IndividualValues, info.Moveset) { Status1 = info.Status1; if (Status1 == PBEStatus1.BadlyPoisoned) { Status1Counter = 1; } } private PBEBattlePokemon(PBETrainer trainer, IPBEPkmnAppearedInfo_Hidden info) { Battle = trainer.Battle; Team = trainer.Team; Trainer = trainer; FieldPosition = info.FieldPosition; HPPercentage = info.HPPercentage; Status1 = info.Status1; if (Status1 == PBEStatus1.BadlyPoisoned) { Status1Counter = 1; } Level = info.Level; KnownAbility = Ability = PBEAbility.MAX; KnownGender = Gender = info.Gender; KnownItem = Item = (PBEItem)ushort.MaxValue; Moves = new PBEBattleMoveset(Battle.Settings); // For Transform KnownMoves = new PBEBattleMoveset(Battle.Settings); TransformBackupMoves = new PBEBattleMoveset(Battle.Settings); // For Transform KnownNickname = Nickname = info.Nickname; KnownShiny = Shiny = info.Shiny; KnownSpecies = Species = info.Species; KnownForm = Form = info.Form; IPBEPokemonData pData = PBEDataProvider.Instance.GetPokemonData(KnownSpecies, KnownForm); KnownType1 = Type1 = pData.Type1; KnownType2 = Type2 = pData.Type2; KnownWeight = Weight = pData.Weight; trainer.Party.Add(this); Battle.ActiveBattlers.Add(this); } private PBEBattlePokemon(PBETrainer trainer, IPBEPkmnSwitchInInfo_Hidden info) : this(trainer, (IPBEPkmnAppearedInfo_Hidden)info) { KnownCaughtBall = CaughtBall = info.CaughtBall; } public PBEBattlePokemon(PBETrainer trainer, PBEPkmnSwitchInPacket_Hidden.PBEPkmnSwitchInInfo info) : this(trainer, (IPBEPkmnSwitchInInfo_Hidden)info) { } public PBEBattlePokemon(PBEBattle battle, PBEWildPkmnAppearedPacket_Hidden.PBEWildPkmnInfo info) : this(battle.Teams[1].Trainers[0], info) { } #endregion public void AddEXPPokemon(PBEBattlePokemon pkmn) { if (!EXPPokemon.Contains(pkmn)) { EXPPokemon.Add(pkmn); } } /// For use with level ups. Does not send any packets. public void LearnMove(PBEMove move, int index) { bool transformed = Status2.HasFlag(PBEStatus2.Transformed); PBEBattleMoveset moves = transformed ? TransformBackupMoves : Moves; PBEBattleMoveset.PBEBattleMovesetSlot slot = moves[index]; PBEMove oldMove = slot.Move; slot.Move = move; int pp = PBEDataUtils.CalcMaxPP(move, 0, Battle.Settings); slot.PP = pp; slot.MaxPP = pp; // Update known moves below if (!transformed && FieldPosition != PBEFieldPosition.None) { moves = KnownMoves; PBEBattleMoveset.PBEBattleMovesetSlot? slot2 = moves[oldMove]; if (slot2 is not null) // Check if move is known first { slot2.Move = PBEMove.MAX; // Make the move unknown if the old move was known slot2.PP = 0; moves.Organize(); } } } public void ApplyPowerTrickChange() { ushort a = Attack; Attack = Defense; Defense = a; } /// Applies effects that occur on switching out or escaping such as . public void ApplyNaturalCure() { if (!PBEIgnore && Ability == PBEAbility.NaturalCure) { Status1 = PBEStatus1.None; Status1Counter = 0; SleepTurns = 0; } } private void ResetSpecies() { Species = KnownSpecies = OriginalSpecies; Form = KnownForm = RevertForm; Ability = KnownAbility = RevertAbility; IPBEPokemonData pData = PBEDataProvider.Instance.GetPokemonData(this); KnownAbility = PBEAbility.MAX; KnownGender = Gender; KnownItem = (PBEItem)ushort.MaxValue; KnownMoves.SetUnknown(); KnownNickname = Nickname; KnownShiny = Shiny; KnownType1 = Type1 = pData.Type1; KnownType2 = Type2 = pData.Type2; } private void ResetVolatileStuff() { ConfusionCounter = 0; ConfusionTurns = 0; LockOnPokemon = null; LockOnTurns = 0; MagnetRiseTurns = 0; Protection_Counter = 0; Protection_Used = false; SeededPosition = PBEFieldPosition.None; SeededTeam = null; SubstituteHP = 0; if (Status2.HasFlag(PBEStatus2.Transformed)) { Moves.Reset(TransformBackupMoves); TransformBackupMoves.SetUnknown(); } Status2 = PBEStatus2.None; KnownStatus2 = PBEStatus2.None; HasUsedMoveThisTurn = false; TurnAction = null; TempLockedMove = PBEMove.None; TempLockedTargets = PBETurnTarget.None; ChoiceLockedMove = PBEMove.None; Minimize_Used = false; SlowStart_HinderTurnsLeft = 0; SpeedBoost_AbleToSpeedBoostThisTurn = false; } /// Sets and clears all information required for switching out. public void ClearForSwitch() { EXPPokemon.Clear(); FieldPosition = PBEFieldPosition.None; ApplyNaturalCure(); if (Ability == PBEAbility.Regenerator) { HP = Math.Clamp((ushort)(HP + (MaxHP / 3)), ushort.MinValue, MaxHP); UpdateHPPercentage(); } ResetSpecies(); ClearStatChanges(); if (Status1 == PBEStatus1.Asleep) { Status1Counter = 0; } else if (Status1 == PBEStatus1.BadlyPoisoned) { Status1Counter = 1; } ResetVolatileStuff(); SetStats(false, false); } /// Sets and clears all information required for fainting. public void ClearForFaint() { FieldPosition = PBEFieldPosition.None; ResetSpecies(); ClearStatChanges(); Status1 = PBEStatus1.None; ResetVolatileStuff(); SetStats(false, false); } // GameFreak are very inconsistent with how they handle Power Trick when recalculating stats // Form change, Transform, Level up public void SetStats(bool calculateHP, bool setMaxHPIfCalcHP) { IPBEPokemonData pData = PBEDataProvider.Instance.GetPokemonData(this); PBENature nature = Nature; PBEStatCollection evs = EffortValues!; PBEReadOnlyStatCollection ivs = IndividualValues!; byte level = Level; PBESettings settings = Battle.Settings; if (calculateHP) { ushort oldMaxHP = MaxHP; ushort hp = PBEDataUtils.CalculateStat(pData, PBEStat.HP, nature, evs.HP, ivs.HP, level, settings); MaxHP = hp; if (setMaxHPIfCalcHP) { HP = hp; } else if (HP != 0 && oldMaxHP != hp) // Don't change for a fainted mon { HP = (ushort)Math.Max(1, HP + (hp - oldMaxHP)); } UpdateHPPercentage(); } Attack = PBEDataUtils.CalculateStat(pData, PBEStat.Attack, nature, evs.Attack, ivs.Attack, level, settings); Defense = PBEDataUtils.CalculateStat(pData, PBEStat.Defense, nature, evs.Defense, ivs.Defense, level, settings); SpAttack = PBEDataUtils.CalculateStat(pData, PBEStat.SpAttack, nature, evs.SpAttack, ivs.SpAttack, level, settings); SpDefense = PBEDataUtils.CalculateStat(pData, PBEStat.SpDefense, nature, evs.SpDefense, ivs.SpDefense, level, settings); Speed = PBEDataUtils.CalculateStat(pData, PBEStat.Speed, nature, evs.Speed, ivs.Speed, level, settings); } /// Copies the , does not set . /// The Pokémon to transform into. public void Transform(PBEBattlePokemon target) { if (Trainer != target.Trainer) { KnownAbility = target.KnownAbility = Ability = target.Ability; KnownType1 = target.KnownType1 = Type1 = target.Type1; KnownType2 = target.KnownType2 = Type2 = target.Type2; KnownWeight = target.KnownWeight = Weight = target.Weight; } else { Ability = target.Ability; KnownAbility = target.KnownAbility; Type1 = target.Type1; KnownType1 = target.KnownType1; Type2 = target.Type2; KnownType2 = target.KnownType2; Weight = target.Weight; KnownWeight = target.KnownWeight; } KnownGender = target.KnownGender = target.Gender; KnownShiny = target.KnownShiny = target.Shiny; KnownSpecies = target.KnownSpecies = Species = target.Species; KnownForm = target.KnownForm = Form = target.Form; Attack = target.Attack; Defense = target.Defense; SpAttack = target.SpAttack; SpDefense = target.SpDefense; Speed = target.Speed; AttackChange = target.AttackChange; DefenseChange = target.DefenseChange; SpAttackChange = target.SpAttackChange; SpDefenseChange = target.SpDefenseChange; SpeedChange = target.SpeedChange; AccuracyChange = target.AccuracyChange; EvasionChange = target.EvasionChange; TransformBackupMoves.Reset(Moves); PBEBattleMoveset.DoTransform(this, target); } public void UpdateKnownPP(PBEMove move, int amountReduced) { if (move == PBEMove.None || move >= PBEMove.MAX || !PBEDataUtils.IsMoveUsable(move)) { throw new ArgumentOutOfRangeException(nameof(move)); } PBEBattleMoveset.PBEBattleMovesetSlot knownSlot = KnownMoves[move]!; knownSlot.PP += amountReduced; if (knownSlot.MaxPP == 0) { if (Status2.HasFlag(PBEStatus2.Transformed)) { knownSlot.MaxPP = PBEBattleMoveset.GetTransformPP(Battle.Settings, move); } else if (Battle.Settings.MaxPPUps == 0 || knownSlot.PP > PBEBattleMoveset.GetNonTransformPP(Battle.Settings, move, (byte)(Battle.Settings.MaxPPUps - 1))) { knownSlot.MaxPP = PBEBattleMoveset.GetNonTransformPP(Battle.Settings, move, Battle.Settings.MaxPPUps); } } } /// Divides by and places the result in . public void UpdateHPPercentage() { HPPercentage = (float)HP / MaxHP; } public void StartRoost() { PBERoostTypes t = PBERoostTypes.None; if (Type1 == PBEType.Flying) { if (Type2 == PBEType.None) { Type1 = PBEType.Normal; // Pure flying-type becomes Normal-type t |= PBERoostTypes.Type1; } else { Type1 = Type2; Type2 = PBEType.None; t |= PBERoostTypes.Type2; } } if (Type2 == PBEType.Flying) { Type2 = PBEType.None; t |= PBERoostTypes.Type2; } if (KnownType1 == PBEType.Flying) { if (KnownType2 == PBEType.None) { KnownType1 = PBEType.Normal; t |= PBERoostTypes.KnownType1; } else { KnownType1 = KnownType2; KnownType2 = PBEType.None; t |= PBERoostTypes.KnownType2; } } if (KnownType2 == PBEType.Flying) { KnownType2 = PBEType.None; t |= PBERoostTypes.KnownType2; } RoostTypes = t; } public void EndRoost() { PBERoostTypes t = RoostTypes; if (t.HasFlag(PBERoostTypes.Type1)) { Type1 = PBEType.Flying; } if (t.HasFlag(PBERoostTypes.Type2)) { Type2 = PBEType.Flying; } if (t.HasFlag(PBERoostTypes.KnownType1)) { KnownType1 = PBEType.Flying; } if (t.HasFlag(PBERoostTypes.KnownType2)) { KnownType2 = PBEType.Flying; } RoostTypes = PBERoostTypes.None; } public bool HasCancellingAbility(bool useKnownInfo = false) { PBEAbility kAbility = useKnownInfo ? KnownAbility : Ability; return kAbility == PBEAbility.MoldBreaker || kAbility == PBEAbility.Teravolt || kAbility == PBEAbility.Turboblaze; } public PBEStat[] GetChangedStats() { var list = new List(7); if (AttackChange != 0) { list.Add(PBEStat.Attack); } if (DefenseChange != 0) { list.Add(PBEStat.Defense); } if (SpAttackChange != 0) { list.Add(PBEStat.SpAttack); } if (SpDefenseChange != 0) { list.Add(PBEStat.SpDefense); } if (SpeedChange != 0) { list.Add(PBEStat.Speed); } if (AccuracyChange != 0) { list.Add(PBEStat.Accuracy); } if (EvasionChange != 0) { list.Add(PBEStat.Evasion); } return list.ToArray(); } public PBEStat[] GetStatsLessThan(int i) { var list = new List(7); if (AttackChange < i) { list.Add(PBEStat.Attack); } if (DefenseChange < i) { list.Add(PBEStat.Defense); } if (SpAttackChange < i) { list.Add(PBEStat.SpAttack); } if (SpDefenseChange < i) { list.Add(PBEStat.SpDefense); } if (SpeedChange < i) { list.Add(PBEStat.Speed); } if (AccuracyChange < i) { list.Add(PBEStat.Accuracy); } if (EvasionChange < i) { list.Add(PBEStat.Evasion); } return list.ToArray(); } public PBEStat[] GetStatsGreaterThan(int i) { var list = new List(7); if (AttackChange > i) { list.Add(PBEStat.Attack); } if (DefenseChange > i) { list.Add(PBEStat.Defense); } if (SpAttackChange > i) { list.Add(PBEStat.SpAttack); } if (SpDefenseChange > i) { list.Add(PBEStat.SpDefense); } if (SpeedChange > i) { list.Add(PBEStat.Speed); } if (AccuracyChange > i) { list.Add(PBEStat.Accuracy); } if (EvasionChange > i) { list.Add(PBEStat.Evasion); } return list.ToArray(); } public sbyte GetStatChange(PBEStat stat) { switch (stat) { case PBEStat.Attack: return AttackChange; case PBEStat.Defense: return DefenseChange; case PBEStat.SpAttack: return SpAttackChange; case PBEStat.SpDefense: return SpDefenseChange; case PBEStat.Speed: return SpeedChange; case PBEStat.Accuracy: return AccuracyChange; case PBEStat.Evasion: return EvasionChange; default: throw new ArgumentOutOfRangeException(nameof(stat)); } } public sbyte SetStatChange(PBEStat stat, int value) { sbyte maxStatChange = Battle.Settings.MaxStatChange; sbyte val = (sbyte)Math.Clamp(value, -maxStatChange, maxStatChange); switch (stat) { case PBEStat.Accuracy: AccuracyChange = val; break; case PBEStat.Attack: AttackChange = val; break; case PBEStat.Defense: DefenseChange = val; break; case PBEStat.Evasion: EvasionChange = val; break; case PBEStat.SpAttack: SpAttackChange = val; break; case PBEStat.SpDefense: SpDefenseChange = val; break; case PBEStat.Speed: SpeedChange = val; break; default: throw new ArgumentOutOfRangeException(nameof(stat)); } return val; } public PBEResult IsStatChangePossible(PBEStat stat, PBEBattlePokemon causer, int change, out sbyte oldValue, out sbyte newValue, bool useKnownInfo = false, bool ignoreSubstitute = false) { oldValue = GetStatChange(stat); if (causer != this && !ignoreSubstitute && (useKnownInfo ? KnownStatus2 : Status2).HasFlag(PBEStatus2.Substitute)) { newValue = oldValue; return PBEResult.Ineffective_Substitute; } // These abilities do not activate when the Pokémon changes its own stat if (causer != this && !causer.HasCancellingAbility()) { switch (useKnownInfo ? KnownAbility : Ability) { case PBEAbility.BigPecks: { if (change < 0 && stat == PBEStat.Defense) { newValue = oldValue; return PBEResult.Ineffective_Ability; } break; } case PBEAbility.ClearBody: case PBEAbility.WhiteSmoke: { if (change < 0) { newValue = oldValue; return PBEResult.Ineffective_Ability; } break; } case PBEAbility.HyperCutter: { if (change < 0 && stat == PBEStat.Attack) { newValue = oldValue; return PBEResult.Ineffective_Ability; } break; } case PBEAbility.KeenEye: { if (change < 0 && stat == PBEStat.Accuracy) { newValue = oldValue; return PBEResult.Ineffective_Ability; } break; } } } // Verified: Contrary/Simple are silent // These abilities activate when the Pokémon changes its own stat if (causer == this || !causer.HasCancellingAbility()) { switch (useKnownInfo ? KnownAbility : Ability) { case PBEAbility.Contrary: change *= -1; break; case PBEAbility.Simple: change *= 2; break; } } sbyte maxStatChange = Battle.Settings.MaxStatChange; newValue = (sbyte)Math.Clamp(oldValue + change, -maxStatChange, maxStatChange); if (oldValue == newValue) { return PBEResult.Ineffective_Stat; } return PBEResult.Success; } public void ClearStatChanges() { AttackChange = 0; DefenseChange = 0; SpAttackChange = 0; SpDefenseChange = 0; SpeedChange = 0; AccuracyChange = 0; EvasionChange = 0; } /// For use with and . public int GetPositiveStatTotal() { return GetStatsGreaterThan(0).Sum(s => GetStatChange(s)); } public PBEType GetMoveType(IPBEMoveData mData, bool useKnownInfo = false) { if (!mData.IsMoveUsable()) { throw new ArgumentOutOfRangeException(nameof(mData)); } switch (mData.Effect) { case PBEMoveEffect.HiddenPower: { if (!useKnownInfo) { return IndividualValues!.GetHiddenPowerType(); } break; } case PBEMoveEffect.Judgment: { switch (useKnownInfo ? KnownItem : Item) { case PBEItem.DracoPlate: return PBEType.Dragon; case PBEItem.DreadPlate: return PBEType.Dark; case PBEItem.EarthPlate: return PBEType.Ground; case PBEItem.FistPlate: return PBEType.Fighting; case PBEItem.FlamePlate: return PBEType.Fire; case PBEItem.IciclePlate: return PBEType.Ice; case PBEItem.InsectPlate: return PBEType.Bug; case PBEItem.IronPlate: return PBEType.Steel; case PBEItem.MeadowPlate: return PBEType.Grass; case PBEItem.MindPlate: return PBEType.Psychic; case PBEItem.SkyPlate: return PBEType.Flying; case PBEItem.SplashPlate: return PBEType.Water; case PBEItem.SpookyPlate: return PBEType.Ghost; case PBEItem.StonePlate: return PBEType.Rock; case PBEItem.ToxicPlate: return PBEType.Poison; case PBEItem.ZapPlate: return PBEType.Electric; } break; } case PBEMoveEffect.Struggle: { return PBEType.None; } case PBEMoveEffect.TechnoBlast: { switch (useKnownInfo ? KnownItem : Item) { case PBEItem.BurnDrive: return PBEType.Fire; case PBEItem.ChillDrive: return PBEType.Ice; case PBEItem.DouseDrive: return PBEType.Water; case PBEItem.ShockDrive: return PBEType.Electric; } break; } case PBEMoveEffect.WeatherBall: { if (Battle.ShouldDoWeatherEffects()) { switch (Battle.Weather) { case PBEWeather.Hailstorm: return PBEType.Ice; case PBEWeather.HarshSunlight: return PBEType.Fire; case PBEWeather.Rain: return PBEType.Water; case PBEWeather.Sandstorm: return PBEType.Rock; } } break; } } if ((useKnownInfo ? KnownAbility : Ability) == PBEAbility.Normalize) { return PBEType.Normal; } return mData.Type; } public PBEType GetMoveType(PBEMove move, bool useKnownInfo = false) { if (move == PBEMove.None || move >= PBEMove.MAX) { throw new ArgumentOutOfRangeException(nameof(move)); } return GetMoveType(PBEDataProvider.Instance.GetMoveData(move), useKnownInfo: useKnownInfo); } public PBEMoveTarget GetMoveTargets(IPBEMoveData mData) { if (!mData.IsMoveUsable()) { throw new ArgumentOutOfRangeException(nameof(mData)); } if (mData.Effect == PBEMoveEffect.Curse) { if (this.HasType(PBEType.Ghost)) { return PBEMoveTarget.SingleSurrounding; } return PBEMoveTarget.Self; } return mData.Targets; } /// Gets the possible targets that a move can target when used by this Pokémon. /// The move to check. /// Thrown when is invalid. public PBEMoveTarget GetMoveTargets(PBEMove move) { if (move == PBEMove.None || move >= PBEMove.MAX) { throw new ArgumentOutOfRangeException(nameof(move)); } return GetMoveTargets(PBEDataProvider.Instance.GetMoveData(move)); } /// Returns True if the Pokémon is only able to use . public bool IsForcedToStruggle() { if (TempLockedMove != PBEMove.None) // Temp locked moves deduct PP on the first turn and don't on the second, so having a temp locked move means it is supposed to be used again for the second turn { return false; } else if ((ChoiceLockedMove != PBEMove.None && Moves[ChoiceLockedMove]!.PP == 0) // If the choice locked move has 0 pp, it is forced to struggle || Moves.All(s => s.PP == 0) // If all moves have 0 pp, then the user is forced to struggle ) { return true; } else { return false; } } /// Returns True if the Pokémon can switch out. Does not check if the Pokémon is on the field or if there are available Pokémon to switch into. public bool CanSwitchOut() { return TempLockedMove == PBEMove.None; } public bool CanHitThroughSafeguard() { return Ability == PBEAbility.Infiltrator; } public PBEResult IsAttractionPossible(PBEBattlePokemon causer, bool useKnownInfo = false, bool ignoreCurrentStatus = false) { if (causer == this) { return PBEResult.InvalidConditions; } PBEStatus2 kStatus2 = useKnownInfo ? KnownStatus2 : Status2; if (!ignoreCurrentStatus && kStatus2.HasFlag(PBEStatus2.Infatuated)) { return PBEResult.Ineffective_Status; } PBEGender kGender = useKnownInfo ? KnownGender : Gender; if (!kGender.IsOppositeGender(causer.Gender)) { return PBEResult.Ineffective_Gender; } PBEAbility kAbility = useKnownInfo ? KnownAbility : Ability; if (!causer.HasCancellingAbility() && kAbility == PBEAbility.Oblivious) { return PBEResult.Ineffective_Ability; } return PBEResult.Success; } public PBEResult IsBurnPossible(PBEBattlePokemon? other, bool useKnownInfo = false, bool ignoreSubstitute = false, bool ignoreCurrentStatus = false, bool ignoreSafeguard = false) { PBEStatus2 kStatus2 = useKnownInfo ? KnownStatus2 : Status2; if (!ignoreSubstitute && kStatus2.HasFlag(PBEStatus2.Substitute)) { return PBEResult.Ineffective_Substitute; } if (!ignoreCurrentStatus && Status1 != PBEStatus1.None) { return PBEResult.Ineffective_Status; } if (!ignoreSafeguard && other?.CanHitThroughSafeguard() == true && Team.TeamStatus.HasFlag(PBETeamStatus.Safeguard)) { return PBEResult.Ineffective_Safeguard; } if (this.HasType(PBEType.Fire, useKnownInfo)) { return PBEResult.Ineffective_Type; } PBEAbility kAbility = useKnownInfo ? KnownAbility : Ability; if (other?.HasCancellingAbility() != true && (kAbility == PBEAbility.WaterVeil || (kAbility == PBEAbility.LeafGuard && Battle.WillLeafGuardActivate()))) { return PBEResult.Ineffective_Ability; } return PBEResult.Success; } public PBEResult IsConfusionPossible(PBEBattlePokemon? other, bool useKnownInfo = false, bool ignoreSubstitute = false, bool ignoreCurrentStatus = false, bool ignoreSafeguard = false) { PBEStatus2 kStatus2 = useKnownInfo ? KnownStatus2 : Status2; if (!ignoreSubstitute && kStatus2.HasFlag(PBEStatus2.Substitute)) { return PBEResult.Ineffective_Substitute; } if (!ignoreCurrentStatus && kStatus2.HasFlag(PBEStatus2.Confused)) { return PBEResult.Ineffective_Status; } if (!ignoreSafeguard && other?.CanHitThroughSafeguard() == true && Team.TeamStatus.HasFlag(PBETeamStatus.Safeguard)) { return PBEResult.Ineffective_Safeguard; } PBEAbility kAbility = useKnownInfo ? KnownAbility : Ability; if (other?.HasCancellingAbility() != true && kAbility == PBEAbility.OwnTempo) { return PBEResult.Ineffective_Ability; } return PBEResult.Success; } public PBEResult IsFreezePossible(PBEBattlePokemon? other, bool useKnownInfo = false, bool ignoreSubstitute = false, bool ignoreCurrentStatus = false, bool ignoreSafeguard = false) { PBEStatus2 kStatus2 = useKnownInfo ? KnownStatus2 : Status2; if (!ignoreSubstitute && kStatus2.HasFlag(PBEStatus2.Substitute)) { return PBEResult.Ineffective_Substitute; } if (!ignoreCurrentStatus && Status1 != PBEStatus1.None) { return PBEResult.Ineffective_Status; } if (!ignoreSafeguard && other?.CanHitThroughSafeguard() == true && Team.TeamStatus.HasFlag(PBETeamStatus.Safeguard)) { return PBEResult.Ineffective_Safeguard; } if (this.HasType(PBEType.Ice, useKnownInfo)) { return PBEResult.Ineffective_Type; } PBEAbility kAbility = useKnownInfo ? KnownAbility : Ability; if (other?.HasCancellingAbility() != true && (kAbility == PBEAbility.MagmaArmor || (kAbility == PBEAbility.LeafGuard && Battle.WillLeafGuardActivate()))) { return PBEResult.Ineffective_Ability; } return PBEResult.Success; } public PBEResult IsFlinchPossible(PBEBattlePokemon? other, bool useKnownInfo = false) { PBEStatus2 kStatus2 = useKnownInfo ? KnownStatus2 : Status2; if (kStatus2.HasFlag(PBEStatus2.Substitute)) { return PBEResult.Ineffective_Substitute; } if (kStatus2.HasFlag(PBEStatus2.Flinching)) { return PBEResult.Ineffective_Status; } PBEAbility kAbility = useKnownInfo ? KnownAbility : Ability; if (other?.HasCancellingAbility() != true && kAbility == PBEAbility.InnerFocus) { return PBEResult.Ineffective_Ability; } return PBEResult.Success; } public PBEResult IsGrounded(PBEBattlePokemon? other, bool useKnownInfo = false) { if (this.HasType(PBEType.Flying, useKnownInfo)) { return PBEResult.Ineffective_Type; } PBEAbility kAbility = useKnownInfo ? KnownAbility : Ability; if (other?.HasCancellingAbility() != true && kAbility == PBEAbility.Levitate) { return PBEResult.Ineffective_Ability; } PBEStatus2 kStatus2 = useKnownInfo ? KnownStatus2 : Status2; if (kStatus2.HasFlag(PBEStatus2.MagnetRise)) { return PBEResult.Ineffective_MagnetRise; } return PBEResult.Success; } public PBEResult IsLeechSeedPossible(bool useKnownInfo = false) { PBEStatus2 kStatus2 = useKnownInfo ? KnownStatus2 : Status2; if (kStatus2.HasFlag(PBEStatus2.Substitute)) { return PBEResult.Ineffective_Substitute; } if (kStatus2.HasFlag(PBEStatus2.LeechSeed)) { return PBEResult.Ineffective_Status; } if (this.HasType(PBEType.Grass, useKnownInfo)) { return PBEResult.Ineffective_Type; } return PBEResult.Success; } public PBEResult IsMagnetRisePossible(bool useKnownInfo = false) { PBEStatus2 kStatus2 = useKnownInfo ? KnownStatus2 : Status2; if (kStatus2.HasFlag(PBEStatus2.MagnetRise)) { return PBEResult.Ineffective_Status; } return PBEResult.Success; } public PBEResult IsParalysisPossible(PBEBattlePokemon? other, bool useKnownInfo = false, bool ignoreSubstitute = false, bool ignoreCurrentStatus = false, bool ignoreSafeguard = false) { PBEStatus2 kStatus2 = useKnownInfo ? KnownStatus2 : Status2; if (!ignoreSubstitute && kStatus2.HasFlag(PBEStatus2.Substitute)) { return PBEResult.Ineffective_Substitute; } if (!ignoreCurrentStatus && Status1 != PBEStatus1.None) { return PBEResult.Ineffective_Status; } if (!ignoreSafeguard && other?.CanHitThroughSafeguard() == true && Team.TeamStatus.HasFlag(PBETeamStatus.Safeguard)) { return PBEResult.Ineffective_Safeguard; } PBEAbility kAbility = useKnownInfo ? KnownAbility : Ability; if (other?.HasCancellingAbility() != true && (kAbility == PBEAbility.Limber || (kAbility == PBEAbility.LeafGuard && Battle.WillLeafGuardActivate()))) { return PBEResult.Ineffective_Ability; } return PBEResult.Success; } public PBEResult IsPoisonPossible(PBEBattlePokemon? other, bool useKnownInfo = false, bool ignoreSubstitute = false, bool ignoreCurrentStatus = false, bool ignoreSafeguard = false) { PBEStatus2 kStatus2 = useKnownInfo ? KnownStatus2 : Status2; if (!ignoreSubstitute && kStatus2.HasFlag(PBEStatus2.Substitute)) { return PBEResult.Ineffective_Substitute; } if (!ignoreCurrentStatus && Status1 != PBEStatus1.None) { return PBEResult.Ineffective_Status; } if (!ignoreSafeguard && other?.CanHitThroughSafeguard() == true && Team.TeamStatus.HasFlag(PBETeamStatus.Safeguard)) { return PBEResult.Ineffective_Safeguard; } if (this.HasType(PBEType.Poison, useKnownInfo) || this.HasType(PBEType.Steel, useKnownInfo)) { return PBEResult.Ineffective_Type; } PBEAbility kAbility = useKnownInfo ? KnownAbility : Ability; if (other?.HasCancellingAbility() != true && (kAbility == PBEAbility.Immunity || (kAbility == PBEAbility.LeafGuard && Battle.WillLeafGuardActivate()))) { return PBEResult.Ineffective_Ability; } return PBEResult.Success; } public PBEResult IsSleepPossible(PBEBattlePokemon? other, bool useKnownInfo = false, bool ignoreSubstitute = false, bool ignoreCurrentStatus = false, bool ignoreSafeguard = false) { PBEStatus2 kStatus2 = useKnownInfo ? KnownStatus2 : Status2; if (!ignoreSubstitute && kStatus2.HasFlag(PBEStatus2.Substitute)) { return PBEResult.Ineffective_Substitute; } if (!ignoreCurrentStatus && Status1 != PBEStatus1.None) { return PBEResult.Ineffective_Status; } if (!ignoreSafeguard && other?.CanHitThroughSafeguard() == true && Team.TeamStatus.HasFlag(PBETeamStatus.Safeguard)) { return PBEResult.Ineffective_Safeguard; } PBEAbility kAbility = useKnownInfo ? KnownAbility : Ability; if (other?.HasCancellingAbility() != true && (kAbility == PBEAbility.Insomnia || (kAbility == PBEAbility.LeafGuard && Battle.WillLeafGuardActivate()) || kAbility == PBEAbility.VitalSpirit)) { return PBEResult.Ineffective_Ability; } return PBEResult.Success; } public PBEResult IsSubstitutePossible(bool useKnownInfo = false, bool ignoreCurrentStatus = false) { PBEStatus2 kStatus2 = useKnownInfo ? KnownStatus2 : Status2; if (!ignoreCurrentStatus && kStatus2.HasFlag(PBEStatus2.Substitute)) { return PBEResult.Ineffective_Status; } int hpRequired = MaxHP / 4; if (hpRequired < 1 || HP <= hpRequired) { return PBEResult.Ineffective_Stat; } return PBEResult.Success; } public PBEResult IsTransformPossible(PBEBattlePokemon user, bool useKnownInfo = false) { if (user.Status2.HasFlag(PBEStatus2.Transformed)) { return PBEResult.Ineffective_Status; } PBEStatus2 kStatus2 = useKnownInfo ? KnownStatus2 : Status2; if (kStatus2.HasFlag(PBEStatus2.Substitute)) { return PBEResult.Ineffective_Substitute; } if (kStatus2.HasFlag(PBEStatus2.Disguised) || kStatus2.HasFlag(PBEStatus2.Transformed)) { return PBEResult.Ineffective_Status; } return PBEResult.Success; } /// Returns an array of moves the Pokémon can use. public PBEMove[] GetUsableMoves() { if (IsForcedToStruggle()) { return new PBEMove[1] { PBEMove.Struggle }; } else if (TempLockedMove != PBEMove.None) { return new PBEMove[1] { TempLockedMove }; } else if (ChoiceLockedMove != PBEMove.None) { return new PBEMove[1] { ChoiceLockedMove }; // IsForcedToStruggle() being false means the choice locked move still has PP } else { int numMoves = Moves.Count; var usableMoves = new List(numMoves); for (int i = 0; i < numMoves; i++) { PBEBattleMoveset.PBEBattleMovesetSlot slot = Moves[i]; if (slot.PP > 0) { usableMoves.Add(slot.Move); } } return usableMoves.ToArray(); } } /// Gets the chance of a protection move succeeding (based on ) out of . public ushort GetProtectionChance() { int count = Protection_Counter; return count == 0 ? ushort.MaxValue : (ushort)(ushort.MaxValue / (count * 2)); } public PBEBattlePokemon? GetPkmnWouldDisguiseAs() { PBEList party = Trainer.Party; for (int i = party.Count - 1; i >= 0; i--) { PBEBattlePokemon p = party[i]; // Does not copy eggs if (p.CanBattle) { // If this Pokémon is the "last" conscious one, it will go out as itself (loop breaks) // The only way to disguise as a Pokémon that's on the battlefield is the first turn of a Double/Triple/Rotation battle if (p.OriginalSpecies != OriginalSpecies) { return p; } break; } } return null; } /// Will only be accurate for the host public override string ToString() { var sb = new StringBuilder(); string formStr = PBEDataUtils.HasForms(Species, false) ? $" ({PBEDataProvider.Instance.GetFormName(this).English})" : string.Empty; sb.AppendLine($"{Nickname}/{PBEDataProvider.Instance.GetSpeciesName(Species).English}{formStr} {Gender.ToSymbol()} Lv.{Level}"); sb.AppendLine($"HP: {HP}/{MaxHP} ({HPPercentage:P2})"); sb.Append($"Types: {PBEDataProvider.Instance.GetTypeName(Type1).English}"); if (Type2 != PBEType.None) { sb.Append($"/{PBEDataProvider.Instance.GetTypeName(Type2).English}"); } sb.AppendLine(); sb.Append($"Known types: {PBEDataProvider.Instance.GetTypeName(KnownType1).English}"); if (KnownType2 != PBEType.None) { sb.Append($"/{PBEDataProvider.Instance.GetTypeName(KnownType2).English}"); } sb.AppendLine(); sb.AppendLine($"Position: {Team.CombinedName}'s {FieldPosition}"); sb.AppendLine($"Status1: {Status1}"); if (Status1 == PBEStatus1.Asleep) { sb.AppendLine($"Asleep turns: {Status1Counter}/{SleepTurns}"); } else if (Status1 == PBEStatus1.BadlyPoisoned) { sb.AppendLine($"Toxic counter: {Status1Counter}"); } sb.AppendLine($"Status2: {Status2}"); if (Status2.HasFlag(PBEStatus2.Confused)) { sb.AppendLine($"Confusion turns: {ConfusionCounter}/{ConfusionTurns}"); } if (Status2.HasFlag(PBEStatus2.Disguised)) { formStr = PBEDataUtils.HasForms(KnownSpecies, false) ? $" ({PBEDataProvider.Instance.GetFormName(KnownSpecies, KnownForm).English})" : string.Empty; sb.AppendLine($"Disguised as: {KnownNickname}/{PBEDataProvider.Instance.GetSpeciesName(KnownSpecies).English}{formStr} {KnownGender.ToSymbol()}"); } if (Battle.BattleFormat != PBEBattleFormat.Single) { if (Status2.HasFlag(PBEStatus2.Infatuated)) { sb.AppendLine($"Infatuated with: {InfatuatedWithPokemon!.Trainer.Name}'s {InfatuatedWithPokemon.Nickname}"); } if (Status2.HasFlag(PBEStatus2.LeechSeed)) { sb.AppendLine($"Seeded position: {SeededTeam!.CombinedName}'s {SeededPosition}"); } if (Status2.HasFlag(PBEStatus2.LockOn)) { sb.AppendLine($"Taking aim at: {LockOnPokemon!.Trainer.Name}'s {LockOnPokemon.Nickname}"); } } if (Status2.HasFlag(PBEStatus2.Substitute)) { sb.AppendLine($"Substitute HP: {SubstituteHP}"); } sb.AppendLine($"Stats: [A] {Attack}, [D] {Defense}, [SA] {SpAttack}, [SD] {SpDefense}, [S] {Speed}, [W] {Weight:0.0}"); PBEStat[] statChanges = GetChangedStats(); if (statChanges.Length > 0) { var statStrs = new List(7); if (Array.IndexOf(statChanges, PBEStat.Attack) != -1) { statStrs.Add($"[A] x{PBEBattle.GetStatChangeModifier(AttackChange, false):0.00}"); } if (Array.IndexOf(statChanges, PBEStat.Defense) != -1) { statStrs.Add($"[D] x{PBEBattle.GetStatChangeModifier(DefenseChange, false):0.00}"); } if (Array.IndexOf(statChanges, PBEStat.SpAttack) != -1) { statStrs.Add($"[SA] x{PBEBattle.GetStatChangeModifier(SpAttackChange, false):0.00}"); } if (Array.IndexOf(statChanges, PBEStat.SpDefense) != -1) { statStrs.Add($"[SD] x{PBEBattle.GetStatChangeModifier(SpDefenseChange, false):0.00}"); } if (Array.IndexOf(statChanges, PBEStat.Speed) != -1) { statStrs.Add($"[S] x{PBEBattle.GetStatChangeModifier(SpeedChange, false):0.00}"); } if (Array.IndexOf(statChanges, PBEStat.Accuracy) != -1) { statStrs.Add($"[AC] x{PBEBattle.GetStatChangeModifier(AccuracyChange, true):0.00}"); } if (Array.IndexOf(statChanges, PBEStat.Evasion) != -1) { statStrs.Add($"[E] x{PBEBattle.GetStatChangeModifier(EvasionChange, true):0.00}"); } sb.AppendLine($"Stat changes: {string.Join(", ", statStrs)}"); } sb.AppendLine($"Ability: {PBEDataProvider.Instance.GetAbilityName(Ability).English}"); sb.AppendLine($"Known ability: {(KnownAbility == PBEAbility.MAX ? "???" : PBEDataProvider.Instance.GetAbilityName(KnownAbility).English)}"); sb.AppendLine($"Item: {PBEDataProvider.Instance.GetItemName(Item).English}"); sb.AppendLine($"Known item: {(KnownItem == (PBEItem)ushort.MaxValue ? "???" : PBEDataProvider.Instance.GetItemName(KnownItem).English)}"); if (Moves.Contains(PBEMoveEffect.Frustration) || Moves.Contains(PBEMoveEffect.Return)) { sb.AppendLine($"Friendship: {Friendship} ({Friendship / byte.MaxValue:P2})"); } if (Moves.Contains(PBEMoveEffect.HiddenPower)) { PBEReadOnlyStatCollection ivs = IndividualValues!; sb.AppendLine($"{PBEDataProvider.Instance.GetMoveName(PBEMove.HiddenPower).English}: {PBEDataProvider.Instance.GetTypeName(ivs.GetHiddenPowerType()).English}|{ivs.GetHiddenPowerBasePower(Battle.Settings)}"); } sb.Append("Moves: "); for (int i = 0; i < Battle.Settings.NumMoves; i++) { PBEBattleMoveset.PBEBattleMovesetSlot slot = Moves[i]; PBEMove move = slot.Move; if (i > 0) { sb.Append(", "); } sb.Append(PBEDataProvider.Instance.GetMoveName(slot.Move).English); if (move != PBEMove.None) { sb.Append($" ({slot.PP}/{slot.MaxPP})"); } } sb.AppendLine(); sb.Append("Known moves: "); for (int i = 0; i < Battle.Settings.NumMoves; i++) { PBEBattleMoveset.PBEBattleMovesetSlot slot = 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).English); if (move != PBEMove.None && move != PBEMove.MAX) { sb.Append($" ({pp}{(maxPP == 0 ? ")" : $"/{maxPP})")}"); } } sb.AppendLine(); sb.Append($"Usable moves: {string.Join(", ", GetUsableMoves().Select(m => PBEDataProvider.Instance.GetMoveName(m).English))}"); return sb.ToString(); } } ================================================ FILE: PokemonBattleEngine/Battle/BattleReplay.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngine.Packets; using Kermalis.PokemonBattleEngine.Utils; using System; using System.IO; using System.Linq; using System.Security.Cryptography; namespace Kermalis.PokemonBattleEngine.Battle; public sealed partial class PBEBattle { private const ushort CUR_REPLAY_VERSION = 0; public string GetDefaultReplayFileName() { // "2020-12-30 23-59-59 - Team 1 vs Team 2.pbereplay" return PBEUtils.ToSafeFileName(new string(string.Format("{0:yyyy-MM-dd HH-mm-ss} - {1} vs {2}", DateTime.Now, Teams[0].CombinedName, Teams[1].CombinedName).Take(200).ToArray())) + ".pbereplay"; } private void CheckCanSaveReplay() { if (!IsLocallyHosted) { throw new InvalidOperationException("Can only save replays of locally hosted battles"); } if (_battleState != PBEBattleState.Ended) { throw new InvalidOperationException($"{nameof(BattleState)} must be {PBEBattleState.Ended} to save a replay."); } } public void SaveReplay() { CheckCanSaveReplay(); SaveReplay(GetDefaultReplayFileName()); } public void SaveReplayToFolder(string path) { CheckCanSaveReplay(); SaveReplay(Path.Combine(path, GetDefaultReplayFileName())); } public void SaveReplay(string path) { CheckCanSaveReplay(); using (var ms = new MemoryStream()) { var w = new EndianBinaryWriter(ms); w.WriteUInt16(CUR_REPLAY_VERSION); w.WriteInt32(_rand.Seed); int numEvents = Events.Count; w.WriteInt32(numEvents); for (int i = 0; i < numEvents; i++) { byte[] data = Events[i].Data.ToArray(); w.WriteUInt16((ushort)data.Length); w.WriteBytes(data); } ms.Position = 0; w.WriteBytes(MD5.HashData(ms)); File.WriteAllBytes(path, ms.ToArray()); } } public static PBEBattle LoadReplay(string path, PBEPacketProcessor packetProcessor) { byte[] fileBytes = File.ReadAllBytes(path); using (var s = new MemoryStream(fileBytes)) { var r = new EndianBinaryReader(s); byte[] hash; hash = MD5.HashData(fileBytes.AsSpan(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!; // The first packet should be a PBEBattlePacket int numEvents = r.ReadInt32(); if (numEvents < 1) { throw new InvalidDataException(); } for (int i = 0; i < numEvents; i++) { byte[] data = new byte[r.ReadUInt16()]; r.ReadBytes(data); IPBEPacket packet = packetProcessor.CreatePacket(data, b); if (packet is PBEBattlePacket bp) { if (i != 0) { throw new InvalidDataException(); } b = new PBEBattle(bp); } else { if (i == 0) { throw new InvalidDataException(); } if (packet is PBEWildPkmnAppearedPacket wpap) { PBETrainer wildTrainer = b.Teams[1].Trainers[0]; foreach (PBEPkmnAppearedInfo info in wpap.Pokemon) { PBEBattlePokemon pkmn = wildTrainer.GetPokemon(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; } } } ================================================ FILE: PokemonBattleEngine/Battle/BattleTargets.cs ================================================ using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngine.Data.Utils; using Kermalis.PokemonBattleEngine.Utils; using System; using System.Collections.Generic; using System.IO; using System.Linq; namespace Kermalis.PokemonBattleEngine.Battle; public sealed partial class PBEBattle { /// Gets the position across from the inputted position for a specific battle format. /// The battle format. /// The position. /// Thrown when is invalid or is invalid for . public static PBEFieldPosition GetPositionAcross(PBEBattleFormat battleFormat, PBEFieldPosition position) { switch (battleFormat) { case PBEBattleFormat.Single: case PBEBattleFormat.Rotation: { if (position == PBEFieldPosition.Center) { return PBEFieldPosition.Center; } else { throw new ArgumentOutOfRangeException(nameof(position)); } } case PBEBattleFormat.Double: { if (position == PBEFieldPosition.Left) { return PBEFieldPosition.Right; } else if (position == PBEFieldPosition.Right) { return PBEFieldPosition.Left; } else { throw new ArgumentOutOfRangeException(nameof(position)); } } case PBEBattleFormat.Triple: { if (position == PBEFieldPosition.Left) { return PBEFieldPosition.Right; } else if (position == PBEFieldPosition.Center) { return PBEFieldPosition.Center; } else if (position == PBEFieldPosition.Right) { return PBEFieldPosition.Left; } else { throw new ArgumentOutOfRangeException(nameof(position)); } } default: throw new ArgumentOutOfRangeException(nameof(battleFormat)); } } /// Gets the Pokémon surrounding . /// The Pokémon to check. /// True if allies should be included, False otherwise. /// True if foes should be included, False otherwise. /// Thrown when 's 's is invalid or 's is invalid for 's 's . public static IReadOnlyList GetRuntimeSurrounding(PBEBattlePokemon pkmn, bool includeAllies, bool includeFoes) { if (!includeAllies && !includeFoes) { throw new ArgumentException($"\"{nameof(includeAllies)}\" and \"{nameof(includeFoes)}\" were false."); } List allies = pkmn.Team.ActiveBattlers.FindAll(p => p != pkmn); List foes = pkmn.Team.OpposingTeam.ActiveBattlers; switch (pkmn.Battle.BattleFormat) { case PBEBattleFormat.Single: { if (pkmn.FieldPosition == PBEFieldPosition.Center) { if (includeFoes) { return foes.FindAll(p => p.FieldPosition == PBEFieldPosition.Center); } return Array.Empty(); } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEBattleFormat.Double: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { List ret = null!; if (includeAllies) { ret = allies.FindAll(p => p.FieldPosition == PBEFieldPosition.Right); if (!includeFoes) { return ret; } } if (includeFoes) { List f = foes.FindAll(p => p.FieldPosition == PBEFieldPosition.Left || p.FieldPosition == PBEFieldPosition.Right); if (!includeAllies) { return f; } ret.AddRange(f); return ret; } return Array.Empty(); } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { List ret = null!; if (includeAllies) { ret = allies.FindAll(p => p.FieldPosition == PBEFieldPosition.Left); if (!includeFoes) { return ret; } } if (includeFoes) { List f = foes.FindAll(p => p.FieldPosition == PBEFieldPosition.Left || p.FieldPosition == PBEFieldPosition.Right); if (!includeAllies) { return f; } ret.AddRange(f); return ret; } return Array.Empty(); } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEBattleFormat.Triple: case PBEBattleFormat.Rotation: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { List ret = null!; if (includeAllies) { ret = allies.FindAll(p => p.FieldPosition == PBEFieldPosition.Center); if (!includeFoes) { return ret; } } if (includeFoes) { List f = foes.FindAll(p => p.FieldPosition == PBEFieldPosition.Center || p.FieldPosition == PBEFieldPosition.Right); if (!includeAllies) { return f; } ret.AddRange(f); return ret; } return Array.Empty(); } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { List ret = null!; if (includeAllies) { ret = allies.FindAll(p => p.FieldPosition == PBEFieldPosition.Left || p.FieldPosition == PBEFieldPosition.Right); if (!includeFoes) { return ret; } } if (includeFoes) { List f = foes.FindAll(p => p.FieldPosition == PBEFieldPosition.Left || p.FieldPosition == PBEFieldPosition.Center || p.FieldPosition == PBEFieldPosition.Right); if (!includeAllies) { return f; } ret.AddRange(f); return ret; } return Array.Empty(); } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { List ret = null!; if (includeAllies) { ret = allies.FindAll(p => p.FieldPosition == PBEFieldPosition.Center); if (!includeFoes) { return ret; } } if (includeFoes) { List f = foes.FindAll(p => p.FieldPosition == PBEFieldPosition.Center || p.FieldPosition == PBEFieldPosition.Left); if (!includeAllies) { return f; } ret.AddRange(f); return ret; } return Array.Empty(); } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } default: throw new InvalidDataException(nameof(pkmn.Battle.BattleFormat)); } } private static void FindFoeLeftTarget(PBEBattlePokemon user, bool canHitFarCorners, List targets) { PBETeam ot = user.Team.OpposingTeam; if (!ot.TryGetPokemon(PBEFieldPosition.Left, out PBEBattlePokemon? pkmn)) { // Left not found; fallback to its teammate switch (user.Battle.BattleFormat) { case PBEBattleFormat.Double: { if (!ot.TryGetPokemon(PBEFieldPosition.Right, out pkmn)) { return; // Nobody left and nobody right; fail } break; } case PBEBattleFormat.Triple: { if (!ot.TryGetPokemon(PBEFieldPosition.Center, out pkmn)) { if (user.FieldPosition != PBEFieldPosition.Right || canHitFarCorners) { // Center fainted as well but the user can reach far right if (!ot.TryGetPokemon(PBEFieldPosition.Right, out pkmn)) { return; // Nobody left, center, or right; fail } } else { return; // Nobody left and nobody center; fail since we can't reach the right } } break; } default: throw new InvalidOperationException(); } } targets.Add(pkmn); } private static void FindFoeCenterTarget(PBEBattlePokemon user, bool canHitFarCorners, PBERandom rand, List targets) { PBETeam ot = user.Team.OpposingTeam; if (!ot.TryGetPokemon(PBEFieldPosition.Center, out PBEBattlePokemon? pkmn)) { switch (user.Battle.BattleFormat) { case PBEBattleFormat.Single: case PBEBattleFormat.Rotation: return; default: throw new InvalidOperationException(); case PBEBattleFormat.Triple: { // Center not found; fallback to its teammate switch (user.FieldPosition) { case PBEFieldPosition.Left: { if (!ot.TryGetPokemon(PBEFieldPosition.Right, out pkmn)) { if (canHitFarCorners) { if (!ot.TryGetPokemon(PBEFieldPosition.Left, out pkmn)) { return; // Nobody center, right, or left; fail } } else { return; // Nobody center and nobody right; fail since we can't reach the left } } break; } case PBEFieldPosition.Center: { if (!ot.TryGetPokemon(PBEFieldPosition.Left, out PBEBattlePokemon? left)) { if (!ot.TryGetPokemon(PBEFieldPosition.Right, out PBEBattlePokemon? right)) { return; // Nobody left or right; fail } pkmn = right; // Nobody left; pick right } else { if (!ot.TryGetPokemon(PBEFieldPosition.Right, out PBEBattlePokemon? right)) { pkmn = left; // Nobody right; pick left } else { pkmn = rand.RandomBool() ? left : right; // Left and right present; randomly select left or right } } break; } case PBEFieldPosition.Right: { if (!ot.TryGetPokemon(PBEFieldPosition.Left, out pkmn)) { if (canHitFarCorners) { if (!ot.TryGetPokemon(PBEFieldPosition.Right, out pkmn)) { return; // Nobody center, left, or right; fail } } else { return; // Nobody center and nobody left; fail since we can't reach the right } } break; } default: throw new InvalidDataException(); } break; } } } targets.Add(pkmn); } private static void FindFoeRightTarget(PBEBattlePokemon user, bool canHitFarCorners, List targets) { PBETeam ot = user.Team.OpposingTeam; if (!ot.TryGetPokemon(PBEFieldPosition.Right, out PBEBattlePokemon? pkmn)) { // Right not found; fallback to its teammate switch (user.Battle.BattleFormat) { case PBEBattleFormat.Double: { if (!ot.TryGetPokemon(PBEFieldPosition.Left, out pkmn)) { return; // Nobody right and nobody left; fail } break; } case PBEBattleFormat.Triple: { if (!ot.TryGetPokemon(PBEFieldPosition.Center, out pkmn)) { if (user.FieldPosition != PBEFieldPosition.Left || canHitFarCorners) { // Center fainted as well but the user can reach far left if (!ot.TryGetPokemon(PBEFieldPosition.Left, out pkmn)) { return; // Nobody right, center, or left; fail } } else { return; // Nobody right and nobody center; fail since we can't reach the left } } break; } default: throw new InvalidOperationException(); } } targets.Add(pkmn); } /// Gets all Pokémon that will be hit. /// The Pokémon that will act. /// The targets the Pokémon wishes to hit. /// Whether the move can hit far Pokémon in a triple battle. /// The random to use. private static PBEBattlePokemon[] GetRuntimeTargets(PBEBattlePokemon user, PBETurnTarget requestedTargets, bool canHitFarCorners, PBERandom rand) { var targets = new List(); // Foes first, then allies (since initial attack effects run that way) if (requestedTargets.HasFlag(PBETurnTarget.FoeLeft)) { FindFoeLeftTarget(user, canHitFarCorners, targets); } if (requestedTargets.HasFlag(PBETurnTarget.FoeCenter)) { FindFoeCenterTarget(user, canHitFarCorners, rand, targets); } if (requestedTargets.HasFlag(PBETurnTarget.FoeRight)) { FindFoeRightTarget(user, canHitFarCorners, targets); } PBETeam t = user.Team; if (requestedTargets.HasFlag(PBETurnTarget.AllyLeft)) { t.TryAddPokemonToCollection(PBEFieldPosition.Left, targets); } if (requestedTargets.HasFlag(PBETurnTarget.AllyCenter)) { t.TryAddPokemonToCollection(PBEFieldPosition.Center, targets); } if (requestedTargets.HasFlag(PBETurnTarget.AllyRight)) { t.TryAddPokemonToCollection(PBEFieldPosition.Right, targets); } return targets.Distinct().ToArray(); // Remove duplicate targets } /// Determines whether chosen targets are valid for a given move. /// The Pokémon that will act. /// The move the Pokémon wishes to use. /// The targets bitfield to validate. /// Thrown when , , 's , or 's 's is invalid. public static bool AreTargetsValid(PBEBattlePokemon pkmn, PBEMove move, PBETurnTarget targets) { if (move == PBEMove.None || move >= PBEMove.MAX) { throw new ArgumentOutOfRangeException(nameof(move)); } IPBEMoveData mData = PBEDataProvider.Instance.GetMoveData(move); if (!mData.IsMoveUsable()) { throw new ArgumentOutOfRangeException(nameof(move)); } return AreTargetsValid(pkmn, mData, targets); } public static bool AreTargetsValid(PBEBattlePokemon pkmn, IPBEMoveData mData, PBETurnTarget targets) { PBEMoveTarget possibleTargets = pkmn.GetMoveTargets(mData); switch (pkmn.Battle.BattleFormat) { case PBEBattleFormat.Single: { switch (possibleTargets) { case PBEMoveTarget.All: { if (pkmn.FieldPosition == PBEFieldPosition.Center) { return targets == (PBETurnTarget.AllyCenter | PBETurnTarget.FoeCenter); } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllFoes: case PBEMoveTarget.AllFoesSurrounding: case PBEMoveTarget.AllSurrounding: case PBEMoveTarget.SingleFoeSurrounding: case PBEMoveTarget.SingleNotSelf: case PBEMoveTarget.SingleSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Center) { return targets == PBETurnTarget.FoeCenter; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllTeam: case PBEMoveTarget.RandomFoeSurrounding: case PBEMoveTarget.Self: case PBEMoveTarget.SelfOrAllySurrounding: case PBEMoveTarget.SingleAllySurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Center) { return targets == PBETurnTarget.AllyCenter; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } default: throw new InvalidDataException(nameof(possibleTargets)); } } case PBEBattleFormat.Double: { switch (possibleTargets) { case PBEMoveTarget.All: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { return targets == (PBETurnTarget.AllyLeft | PBETurnTarget.AllyRight | PBETurnTarget.FoeLeft | PBETurnTarget.FoeRight); } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllFoes: case PBEMoveTarget.AllFoesSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { return targets == (PBETurnTarget.FoeLeft | PBETurnTarget.FoeRight); } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllTeam: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { return targets == (PBETurnTarget.AllyLeft | PBETurnTarget.AllyRight); } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return targets == (PBETurnTarget.AllyRight | PBETurnTarget.FoeLeft | PBETurnTarget.FoeRight); } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return targets == (PBETurnTarget.AllyLeft | PBETurnTarget.FoeLeft | PBETurnTarget.FoeRight); } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.RandomFoeSurrounding: case PBEMoveTarget.Self: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return targets == PBETurnTarget.AllyLeft; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return targets == PBETurnTarget.AllyRight; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.SelfOrAllySurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { return targets == PBETurnTarget.AllyLeft || targets == PBETurnTarget.AllyRight; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.SingleAllySurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return targets == PBETurnTarget.AllyRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return targets == PBETurnTarget.AllyLeft; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.SingleFoeSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { return targets == PBETurnTarget.FoeLeft || targets == PBETurnTarget.FoeRight; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.SingleNotSelf: case PBEMoveTarget.SingleSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return targets == PBETurnTarget.AllyRight || targets == PBETurnTarget.FoeLeft || targets == PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return targets == PBETurnTarget.AllyLeft || targets == PBETurnTarget.FoeLeft || targets == PBETurnTarget.FoeRight; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } default: throw new InvalidDataException(nameof(possibleTargets)); } } case PBEBattleFormat.Triple: { switch (possibleTargets) { case PBEMoveTarget.All: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { return targets == (PBETurnTarget.AllyLeft | PBETurnTarget.AllyCenter | PBETurnTarget.AllyRight | PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight); } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllFoes: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { return targets == (PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight); } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllFoesSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return targets == (PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight); } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { return targets == (PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight); } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return targets == (PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter); } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return targets == (PBETurnTarget.AllyCenter | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight); } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { return targets == (PBETurnTarget.AllyLeft | PBETurnTarget.AllyRight | PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight); } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return targets == (PBETurnTarget.AllyCenter | PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter); } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllTeam: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { return targets == (PBETurnTarget.AllyLeft | PBETurnTarget.AllyCenter | PBETurnTarget.AllyRight); } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.RandomFoeSurrounding: case PBEMoveTarget.Self: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return targets == PBETurnTarget.AllyLeft; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { return targets == PBETurnTarget.AllyCenter; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return targets == PBETurnTarget.AllyRight; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.SelfOrAllySurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return targets == PBETurnTarget.AllyLeft || targets == PBETurnTarget.AllyCenter; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { return targets == PBETurnTarget.AllyLeft || targets == PBETurnTarget.AllyCenter || targets == PBETurnTarget.AllyRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return targets == PBETurnTarget.AllyCenter || targets == PBETurnTarget.AllyRight; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.SingleAllySurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { return targets == PBETurnTarget.AllyCenter; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { return targets == PBETurnTarget.AllyLeft || targets == PBETurnTarget.AllyRight; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.SingleFoeSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return targets == PBETurnTarget.FoeCenter || targets == PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { return targets == PBETurnTarget.FoeLeft || targets == PBETurnTarget.FoeCenter || targets == PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return targets == PBETurnTarget.FoeLeft || targets == PBETurnTarget.FoeCenter; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.SingleNotSelf: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return targets == PBETurnTarget.AllyCenter || targets == PBETurnTarget.AllyRight || targets == PBETurnTarget.FoeLeft || targets == PBETurnTarget.FoeCenter || targets == PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { return targets == PBETurnTarget.AllyLeft || targets == PBETurnTarget.AllyRight || targets == PBETurnTarget.FoeLeft || targets == PBETurnTarget.FoeCenter || targets == PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return targets == PBETurnTarget.AllyLeft || targets == PBETurnTarget.AllyCenter || targets == PBETurnTarget.FoeLeft || targets == PBETurnTarget.FoeCenter || targets == PBETurnTarget.FoeRight; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.SingleSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return targets == PBETurnTarget.AllyCenter || targets == PBETurnTarget.FoeCenter || targets == PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { return targets == PBETurnTarget.AllyLeft || targets == PBETurnTarget.AllyRight || targets == PBETurnTarget.FoeLeft || targets == PBETurnTarget.FoeCenter || targets == PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return targets == PBETurnTarget.AllyCenter || targets == PBETurnTarget.FoeLeft || targets == PBETurnTarget.FoeCenter; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } default: throw new InvalidDataException(nameof(possibleTargets)); } } case PBEBattleFormat.Rotation: { switch (possibleTargets) { case PBEMoveTarget.All: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { return targets == (PBETurnTarget.AllyCenter | PBETurnTarget.FoeCenter); } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllFoes: case PBEMoveTarget.AllFoesSurrounding: case PBEMoveTarget.AllSurrounding: case PBEMoveTarget.SingleFoeSurrounding: case PBEMoveTarget.SingleNotSelf: case PBEMoveTarget.SingleSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { return targets == PBETurnTarget.FoeCenter; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllTeam: case PBEMoveTarget.RandomFoeSurrounding: case PBEMoveTarget.Self: case PBEMoveTarget.SelfOrAllySurrounding: case PBEMoveTarget.SingleAllySurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { return targets == PBETurnTarget.AllyCenter; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } default: throw new InvalidDataException(nameof(possibleTargets)); } } default: throw new InvalidDataException(nameof(pkmn.Battle.BattleFormat)); } } /// Gets a random target a move can hit when called by . /// The Pokémon using . /// The move being called. /// The random to use. /// Thrown when , 's , or 's 's is invalid. public static PBETurnTarget GetRandomTargetForMetronome(PBEBattlePokemon pkmn, PBEMove calledMove, PBERandom rand) { if (calledMove == PBEMove.None || calledMove >= PBEMove.MAX || !PBEDataUtils.IsMoveUsable(calledMove)) { throw new ArgumentOutOfRangeException(nameof(calledMove)); } IPBEMoveData mData = PBEDataProvider.Instance.GetMoveData(calledMove); if (!mData.IsMoveUsable()) { throw new ArgumentOutOfRangeException(nameof(calledMove)); } PBEMoveTarget possibleTargets = pkmn.GetMoveTargets(mData); switch (pkmn.Battle.BattleFormat) { case PBEBattleFormat.Single: { switch (possibleTargets) { case PBEMoveTarget.All: { if (pkmn.FieldPosition == PBEFieldPosition.Center) { return PBETurnTarget.AllyCenter | PBETurnTarget.FoeCenter; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllFoes: case PBEMoveTarget.AllFoesSurrounding: case PBEMoveTarget.AllSurrounding: case PBEMoveTarget.RandomFoeSurrounding: case PBEMoveTarget.SingleFoeSurrounding: case PBEMoveTarget.SingleNotSelf: case PBEMoveTarget.SingleSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Center) { return PBETurnTarget.FoeCenter; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllTeam: case PBEMoveTarget.Self: case PBEMoveTarget.SelfOrAllySurrounding: case PBEMoveTarget.SingleAllySurrounding: // Helping Hand cannot be called by Metronome anyway { if (pkmn.FieldPosition == PBEFieldPosition.Center) { return PBETurnTarget.AllyCenter; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } default: throw new InvalidDataException(nameof(possibleTargets)); } } case PBEBattleFormat.Double: { switch (possibleTargets) { case PBEMoveTarget.All: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.AllyLeft | PBETurnTarget.AllyRight | PBETurnTarget.FoeLeft | PBETurnTarget.FoeRight; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllFoes: case PBEMoveTarget.AllFoesSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.FoeLeft | PBETurnTarget.FoeRight; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllTeam: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.AllyLeft | PBETurnTarget.AllyRight; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return PBETurnTarget.AllyRight | PBETurnTarget.FoeLeft | PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.AllyLeft | PBETurnTarget.FoeLeft | PBETurnTarget.FoeRight; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.Self: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return PBETurnTarget.AllyLeft; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.AllyRight; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.SelfOrAllySurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { if (rand.RandomBool()) { return PBETurnTarget.AllyLeft; } else { return PBETurnTarget.AllyRight; } } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.SingleAllySurrounding: // Helping Hand cannot be called by Metronome anyway { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return PBETurnTarget.AllyRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.AllyLeft; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.RandomFoeSurrounding: case PBEMoveTarget.SingleFoeSurrounding: case PBEMoveTarget.SingleNotSelf: case PBEMoveTarget.SingleSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { if (rand.RandomBool()) { return PBETurnTarget.FoeLeft; } else { return PBETurnTarget.FoeRight; } } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } default: throw new InvalidDataException(nameof(possibleTargets)); } } case PBEBattleFormat.Triple: { switch (possibleTargets) { case PBEMoveTarget.All: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.AllyLeft | PBETurnTarget.AllyCenter | PBETurnTarget.AllyRight | PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllFoes: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllFoesSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { return PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return PBETurnTarget.AllyCenter | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { return PBETurnTarget.AllyLeft | PBETurnTarget.AllyRight | PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.AllyCenter | PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllTeam: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.AllyLeft | PBETurnTarget.AllyCenter | PBETurnTarget.AllyRight; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.Self: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return PBETurnTarget.AllyLeft; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { return PBETurnTarget.AllyCenter; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.AllyRight; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.SelfOrAllySurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { if (rand.RandomBool()) { return PBETurnTarget.AllyLeft; } else { return PBETurnTarget.AllyCenter; } } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { int val = rand.RandomInt(0, 2); if (val == 0) { return PBETurnTarget.AllyLeft; } else if (val == 1) { return PBETurnTarget.AllyCenter; } else { return PBETurnTarget.AllyRight; } } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { if (rand.RandomBool()) { return PBETurnTarget.AllyCenter; } else { return PBETurnTarget.AllyRight; } } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.SingleAllySurrounding: // Helping Hand cannot be called by Metronome anyway { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return PBETurnTarget.AllyCenter; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { if (rand.RandomBool()) { return PBETurnTarget.AllyLeft; } else { return PBETurnTarget.AllyRight; } } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.AllyCenter; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.RandomFoeSurrounding: case PBEMoveTarget.SingleFoeSurrounding: case PBEMoveTarget.SingleSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { if (rand.RandomBool()) { return PBETurnTarget.FoeCenter; } else { return PBETurnTarget.FoeRight; } } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { int val = rand.RandomInt(0, 2); if (val == 0) { return PBETurnTarget.FoeLeft; } else if (val == 1) { return PBETurnTarget.FoeCenter; } else { return PBETurnTarget.FoeRight; } } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { if (rand.RandomBool()) { return PBETurnTarget.FoeLeft; } else { return PBETurnTarget.FoeCenter; } } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.SingleNotSelf: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { int val = rand.RandomInt(0, 2); if (val == 0) { return PBETurnTarget.FoeLeft; } else if (val == 1) { return PBETurnTarget.FoeCenter; } else { return PBETurnTarget.FoeRight; } } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } default: throw new InvalidDataException(nameof(possibleTargets)); } } case PBEBattleFormat.Rotation: { switch (possibleTargets) { case PBEMoveTarget.All: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.AllyCenter | PBETurnTarget.FoeCenter; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllFoes: case PBEMoveTarget.AllFoesSurrounding: case PBEMoveTarget.AllSurrounding: case PBEMoveTarget.RandomFoeSurrounding: case PBEMoveTarget.SingleFoeSurrounding: case PBEMoveTarget.SingleNotSelf: case PBEMoveTarget.SingleSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.FoeCenter; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllTeam: case PBEMoveTarget.Self: case PBEMoveTarget.SelfOrAllySurrounding: case PBEMoveTarget.SingleAllySurrounding: // Helping Hand cannot be called by Metronome anyway { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.AllyCenter; } else { throw new InvalidDataException(nameof(pkmn.FieldPosition)); } } default: throw new InvalidDataException(nameof(possibleTargets)); } } default: throw new InvalidDataException(nameof(pkmn.Battle.BattleFormat)); } } } ================================================ FILE: PokemonBattleEngine/Battle/BattleTeam.cs ================================================ using Kermalis.PokemonBattleEngine.Packets; using Kermalis.PokemonBattleEngine.Utils; using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Text; namespace Kermalis.PokemonBattleEngine.Battle; public sealed class PBETeams : IReadOnlyList { private readonly PBETeam _team0; private readonly PBETeam _team1; public int Count => 2; public PBETeam this[int index] { get { switch (index) { case 0: return _team0; case 1: return _team1; default: throw new ArgumentOutOfRangeException(nameof(index)); } } } // Trainer battle internal PBETeams(PBEBattle battle, IReadOnlyList ti0, IReadOnlyList ti1, out PBETrainers trainers) { var allTrainers = new List(); _team0 = new PBETeam(battle, 0, ti0, allTrainers); _team1 = new PBETeam(battle, 1, ti1, allTrainers); _team0.OpposingTeam = _team1; _team1.OpposingTeam = _team0; trainers = new PBETrainers(allTrainers); } // Wild battle internal PBETeams(PBEBattle battle, IReadOnlyList ti, PBEWildInfo wi, out PBETrainers trainers) { var allTrainers = new List(); _team0 = new PBETeam(battle, 0, ti, allTrainers); _team1 = new PBETeam(battle, 1, wi, allTrainers); _team0.OpposingTeam = _team1; _team1.OpposingTeam = _team0; trainers = new PBETrainers(allTrainers); } // Remote battle internal PBETeams(PBEBattle battle, PBEBattlePacket packet, out PBETrainers trainers) { var allTrainers = new List(); _team0 = new PBETeam(battle, packet.Teams[0], allTrainers); _team1 = new PBETeam(battle, packet.Teams[1], allTrainers); _team0.OpposingTeam = _team1; _team1.OpposingTeam = _team0; trainers = new PBETrainers(allTrainers); } public bool All(Predicate match) { return match(_team0) && match(_team1); } public IEnumerator GetEnumerator() { yield return _team0; yield return _team1; } IEnumerator IEnumerable.GetEnumerator() { yield return _team0; yield return _team1; } } /// Represents a team in a specific . public sealed class PBETeam { /// The battle this team and its party belongs to. public PBEBattle Battle { get; } public PBETeam OpposingTeam { get; internal set; } public ReadOnlyCollection Trainers { get; } public byte Id { get; } public bool IsWild => Battle.BattleType == PBEBattleType.Wild && Id == 1; public string CombinedName { get; } public IEnumerable CombinedParty => Trainers.SelectMany(t => t.Party); public List ActiveBattlers => Battle.ActiveBattlers.FindAll(p => p.Team == this); public int NumConsciousPkmn => Trainers.Sum(t => t.NumConsciousPkmn); public int NumPkmnOnField => Trainers.Sum(t => t.NumPkmnOnField); public int NumTimesTriedToFlee { get; set; } public PBETeamStatus TeamStatus { get; set; } public byte LightScreenCount { get; set; } public byte LuckyChantCount { get; set; } public byte ReflectCount { get; set; } public byte SafeguardCount { get; set; } public byte SpikeCount { get; set; } public byte TailwindCount { get; set; } public byte ToxicSpikeCount { get; set; } public bool MonFaintedLastTurn { get; set; } public bool MonFaintedThisTurn { get; set; } // Trainer battle internal PBETeam(PBEBattle battle, byte id, IReadOnlyList ti, List allTrainers) { int count = ti.Count; if (!VerifyTrainerCount(battle.BattleFormat, count)) { throw new ArgumentException($"Illegal trainer count (Format: {battle.BattleFormat}, Team: {id}, Count: {count}"); } foreach (PBETrainerInfo t in ti) { if (!t.IsOkayForSettings(battle.Settings)) { throw new ArgumentOutOfRangeException(nameof(ti), "Team settings do not comply with battle settings."); } } Battle = battle; Id = id; var trainers = new PBETrainer[ti.Count]; for (int i = 0; i < ti.Count; i++) { trainers[i] = new PBETrainer(this, ti[i], allTrainers); } Trainers = new ReadOnlyCollection(trainers); CombinedName = GetCombinedName(); OpposingTeam = null!; // OpposingTeam is set in PBETeams after both are created } // Wild battle internal PBETeam(PBEBattle battle, byte id, PBEWildInfo wi, List allTrainers) { int count = wi.Party.Count; if (!VerifyWildCount(battle.BattleFormat, count)) { throw new ArgumentException($"Illegal wild Pokémon count (Format: {battle.BattleFormat}, Count: {count}"); } if (!wi.IsOkayForSettings(battle.Settings)) { throw new ArgumentOutOfRangeException(nameof(wi), "Team settings do not comply with battle settings."); } Battle = battle; Id = id; Trainers = new ReadOnlyCollection(new[] { new PBETrainer(this, wi, allTrainers) }); CombinedName = GetCombinedName(); OpposingTeam = null!; // OpposingTeam is set in PBETeams after both are created } // Remote battle internal PBETeam(PBEBattle battle, PBEBattlePacket.PBETeamInfo info, List allTrainers) { ReadOnlyCollection ti = info.Trainers; int count = ti.Count; if (!VerifyTrainerCount(battle.BattleFormat, count)) { throw new InvalidDataException(); } Battle = battle; Id = info.Id; var trainers = new PBETrainer[ti.Count]; for (int i = 0; i < trainers.Length; i++) { trainers[i] = new PBETrainer(this, ti[i], allTrainers); } Trainers = new ReadOnlyCollection(trainers); CombinedName = GetCombinedName(); OpposingTeam = null!; // OpposingTeam is set in PBETeams after both are created } private static bool VerifyWildCount(PBEBattleFormat format, int count) { switch (format) { case PBEBattleFormat.Single: return count == 1; case PBEBattleFormat.Double: return count >= 1 && count <= 2; case PBEBattleFormat.Rotation: case PBEBattleFormat.Triple: return count >= 1 && count <= 3; default: throw new ArgumentOutOfRangeException(nameof(format)); } } private static bool VerifyTrainerCount(PBEBattleFormat format, int count) { switch (format) { case PBEBattleFormat.Single: case PBEBattleFormat.Rotation: return count == 1; case PBEBattleFormat.Double: return count == 1 || count == 2; case PBEBattleFormat.Triple: return count == 1 || count == 3; default: throw new ArgumentOutOfRangeException(nameof(format)); } } private string GetCombinedName() { string[] names = new string[Trainers.Count]; for (int i = 0; i < names.Length; i++) { names[i] = Trainers[i].Name; } return names.Andify(); } public bool IsSpotOccupied(PBEFieldPosition pos) { foreach (PBEBattlePokemon p in ActiveBattlers) { if (p.FieldPosition == pos) { return true; } } return false; } public bool TryGetPokemon(PBEFieldPosition pos, [NotNullWhen(true)] out PBEBattlePokemon? pkmn) { foreach (PBEBattlePokemon p in ActiveBattlers) { if (p.FieldPosition == pos) { pkmn = p; return true; } } pkmn = null; return false; } public void TryAddPokemonToCollection(PBEFieldPosition pos, ICollection list) { if (TryGetPokemon(pos, out PBEBattlePokemon? pkmn)) { list.Add(pkmn); } } public override string ToString() { var sb = new StringBuilder(); sb.AppendLine($"Team {Id}:"); sb.AppendLine($"TeamStatus: {TeamStatus}"); //sb.AppendLine($"NumPkmn: {Party.Length}"); sb.AppendLine($"NumConsciousPkmn: {NumConsciousPkmn}"); sb.AppendLine($"NumPkmnOnField: {NumPkmnOnField}"); return sb.ToString(); } } ================================================ FILE: PokemonBattleEngine/Battle/BattleTrainer.cs ================================================ using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngine.Packets; using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Linq; namespace Kermalis.PokemonBattleEngine.Battle; public sealed class PBETrainers : IReadOnlyList { private readonly List _trainers; public int Count => _trainers.Count; public PBETrainer this[int index] => _trainers[index]; internal PBETrainers(List trainers) { _trainers = trainers; } public IEnumerator GetEnumerator() { return _trainers.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return _trainers.GetEnumerator(); } } public sealed partial class PBETrainer { public PBEBattle Battle { get; } public PBETeam Team { get; } public PBEList Party { get; } public string Name { get; } public bool GainsEXP { get; } public PBEBattleInventory Inventory { get; } public byte Id { get; } public bool IsWild => Team.IsWild; public List ActiveBattlers => Battle.ActiveBattlers.FindAll(p => p.Trainer == this); public IEnumerable ActiveBattlersOrdered => ActiveBattlers.OrderBy(p => p.FieldPosition); public int NumConsciousPkmn => Party.Count(p => p.CanBattle); public int NumPkmnOnField => Party.Count(p => p.FieldPosition != PBEFieldPosition.None); public bool RequestedFlee { get; set; } public List ActionsRequired { get; } = new(3); // PBEBattleState.WaitingForActions public byte SwitchInsRequired { get; set; } // PBEBattleState.WaitingForSwitchIns public List<(PBEBattlePokemon Pkmn, PBEFieldPosition Pos)> SwitchInQueue { get; } = new(3); // PBEBattleState.WaitingForSwitchIns // Trainer battle / wild battle private PBETrainer(PBETeam team, PBETrainerInfoBase ti, string name, ReadOnlyCollection<(PBEItem Item, uint Quantity)>? inventory, List trainers) { Battle = team.Battle; Team = team; Id = (byte)trainers.Count; Name = name; if (inventory is null || inventory.Count == 0) // Wild trainer { Inventory = PBEBattleInventory.Empty(); } else { Inventory = new PBEBattleInventory(inventory); } ReadOnlyCollection tiParty = ti.Party; Party = new PBEList(tiParty.Count); for (byte i = 0; i < tiParty.Count; i++) { IPBEPokemon pkmn = tiParty[i]; if (pkmn is IPBEPartyPokemon partyPkmn) { _ = new PBEBattlePokemon(this, i, partyPkmn); } else { _ = new PBEBattlePokemon(this, i, pkmn); } } trainers.Add(this); } // Trainer battle internal PBETrainer(PBETeam team, PBETrainerInfo ti, List trainers) : this(team, ti, ti.Name, ti.Inventory, trainers) { GainsEXP = ti.GainsEXP; } // Wild battle internal PBETrainer(PBETeam team, PBEWildInfo wi, List trainers) : this(team, wi, "The wild Pokémon", null, trainers) { } // Remote battle internal PBETrainer(PBETeam team, PBEBattlePacket.PBETeamInfo.PBETrainerInfo info, List trainers) { Battle = team.Battle; Team = team; Id = info.Id; Name = team.IsWild ? "The wild Pokémon" : info.Name; Inventory = info.Inventory.Count == 0 ? PBEBattleInventory.Empty() : new PBEBattleInventory(info.Inventory); Party = new PBEList(info.Party.Select(p => new PBEBattlePokemon(this, p))); trainers.Add(this); } public static void Remove(PBEBattlePokemon pokemon) { pokemon.Trainer.Party.Remove(pokemon); } public static void SwitchTwoPokemon(PBEBattlePokemon a, PBEFieldPosition pos) { if (pos == PBEFieldPosition.None || pos >= PBEFieldPosition.MAX) { throw new ArgumentOutOfRangeException(nameof(pos)); } PBETrainer t = a.Trainer; PBEBattlePokemon b = t.Party[t.GetFieldPositionIndex(pos)]; if (a != b) { t.Party.Swap(a, b); } } public static void SwitchTwoPokemon(PBEBattlePokemon a, PBEBattlePokemon b) { if (a != b) { PBETrainer t = a.Trainer; if (t != b.Trainer) { throw new ArgumentException(nameof(a.Trainer)); } t.Party.Swap(a, b); } } public bool IsSpotOccupied(PBEFieldPosition pos) { foreach (PBEBattlePokemon p in ActiveBattlers) { if (p.FieldPosition == pos) { return true; } } return false; } public bool TryGetPokemon(PBEFieldPosition pos, [NotNullWhen(true)] out PBEBattlePokemon? pkmn) { foreach (PBEBattlePokemon p in ActiveBattlers) { if (p.FieldPosition == pos) { pkmn = p; return true; } } pkmn = null; return false; } public bool TryGetPokemon(byte pkmnId, [NotNullWhen(true)] out PBEBattlePokemon? pkmn) { foreach (PBEBattlePokemon p in Party) { if (p.Id == pkmnId) { pkmn = p; return true; } } pkmn = null; return false; } public PBEBattlePokemon GetPokemon(PBEFieldPosition pos) { foreach (PBEBattlePokemon p in ActiveBattlers) { if (p.FieldPosition == pos) { return p; } } throw new InvalidOperationException(); } public PBEBattlePokemon GetPokemon(byte pkmnId) { foreach (PBEBattlePokemon p in Party) { if (p.Id == pkmnId) { return p; } } throw new InvalidOperationException(); } } ================================================ FILE: PokemonBattleEngine/Battle/BattleUtils.cs ================================================ using Kermalis.PokemonBattleEngine.Data; using System; using System.IO; namespace Kermalis.PokemonBattleEngine.Battle; public static class PBEBattleUtils { public static PBETurnTarget GetSpreadMoveTargets(PBEBattlePokemon pkmn, PBEMoveTarget targets) { switch (pkmn.Battle.BattleFormat) { case PBEBattleFormat.Single: { switch (targets) { case PBEMoveTarget.All: { if (pkmn.FieldPosition == PBEFieldPosition.Center) { return PBETurnTarget.AllyCenter | PBETurnTarget.FoeCenter; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllFoes: case PBEMoveTarget.AllFoesSurrounding: case PBEMoveTarget.AllSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Center) { return PBETurnTarget.FoeCenter; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllTeam: { if (pkmn.FieldPosition == PBEFieldPosition.Center) { return PBETurnTarget.AllyCenter; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } default: throw new ArgumentOutOfRangeException(nameof(targets)); } } case PBEBattleFormat.Double: { switch (targets) { case PBEMoveTarget.All: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.AllyLeft | PBETurnTarget.AllyRight | PBETurnTarget.FoeLeft | PBETurnTarget.FoeRight; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllFoes: case PBEMoveTarget.AllFoesSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.FoeLeft | PBETurnTarget.FoeRight; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllTeam: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.AllyLeft | PBETurnTarget.AllyRight; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return PBETurnTarget.AllyRight | PBETurnTarget.FoeLeft | PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.AllyLeft | PBETurnTarget.FoeLeft | PBETurnTarget.FoeRight; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } default: throw new ArgumentOutOfRangeException(nameof(targets)); } } case PBEBattleFormat.Triple: { switch (targets) { case PBEMoveTarget.All: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.AllyLeft | PBETurnTarget.AllyCenter | PBETurnTarget.AllyRight | PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllFoes: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllFoesSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { return PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return PBETurnTarget.AllyCenter | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { return PBETurnTarget.AllyLeft | PBETurnTarget.AllyRight | PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.AllyCenter | PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllTeam: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.AllyLeft | PBETurnTarget.AllyCenter | PBETurnTarget.AllyRight; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } default: throw new ArgumentOutOfRangeException(nameof(targets)); } } case PBEBattleFormat.Rotation: { switch (targets) { case PBEMoveTarget.All: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.AllyCenter | PBETurnTarget.FoeCenter; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllFoes: case PBEMoveTarget.AllFoesSurrounding: case PBEMoveTarget.AllSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.FoeCenter; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.AllTeam: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { return PBETurnTarget.AllyCenter; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } default: throw new ArgumentOutOfRangeException(nameof(targets)); } } default: throw new InvalidDataException(nameof(pkmn.Battle.BattleFormat)); } } public static PBETurnTarget[] GetPossibleTargets(PBEBattlePokemon pkmn, PBEMoveTarget targets) { switch (pkmn.Battle.BattleFormat) { case PBEBattleFormat.Single: { switch (targets) { case PBEMoveTarget.SingleFoeSurrounding: case PBEMoveTarget.SingleNotSelf: case PBEMoveTarget.SingleSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Center) { return new PBETurnTarget[] { PBETurnTarget.FoeCenter }; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.RandomFoeSurrounding: case PBEMoveTarget.Self: case PBEMoveTarget.SelfOrAllySurrounding: case PBEMoveTarget.SingleAllySurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Center) { return new PBETurnTarget[] { PBETurnTarget.AllyCenter }; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } default: throw new ArgumentOutOfRangeException(nameof(targets)); } } case PBEBattleFormat.Double: { switch (targets) { case PBEMoveTarget.RandomFoeSurrounding: case PBEMoveTarget.Self: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return new PBETurnTarget[] { PBETurnTarget.AllyLeft }; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return new PBETurnTarget[] { PBETurnTarget.AllyRight }; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.SelfOrAllySurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { return new PBETurnTarget[] { PBETurnTarget.AllyLeft, PBETurnTarget.AllyRight }; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.SingleAllySurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return new PBETurnTarget[] { PBETurnTarget.AllyRight }; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return new PBETurnTarget[] { PBETurnTarget.AllyLeft }; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.SingleFoeSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { return new PBETurnTarget[] { PBETurnTarget.FoeLeft, PBETurnTarget.FoeRight }; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.SingleNotSelf: case PBEMoveTarget.SingleSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return new PBETurnTarget[] { PBETurnTarget.AllyRight, PBETurnTarget.FoeLeft, PBETurnTarget.FoeRight }; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return new PBETurnTarget[] { PBETurnTarget.AllyLeft, PBETurnTarget.FoeLeft, PBETurnTarget.FoeRight }; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } default: throw new ArgumentOutOfRangeException(nameof(targets)); } } case PBEBattleFormat.Triple: { switch (targets) { case PBEMoveTarget.RandomFoeSurrounding: case PBEMoveTarget.Self: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return new PBETurnTarget[] { PBETurnTarget.AllyLeft }; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { return new PBETurnTarget[] { PBETurnTarget.AllyCenter }; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return new PBETurnTarget[] { PBETurnTarget.AllyRight }; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.SelfOrAllySurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return new PBETurnTarget[] { PBETurnTarget.AllyLeft, PBETurnTarget.AllyCenter }; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { return new PBETurnTarget[] { PBETurnTarget.AllyLeft, PBETurnTarget.AllyCenter, PBETurnTarget.AllyRight }; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return new PBETurnTarget[] { PBETurnTarget.AllyCenter, PBETurnTarget.AllyRight }; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.SingleAllySurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { return new PBETurnTarget[] { PBETurnTarget.AllyCenter }; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { return new PBETurnTarget[] { PBETurnTarget.AllyLeft, PBETurnTarget.AllyRight }; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.SingleFoeSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return new PBETurnTarget[] { PBETurnTarget.FoeCenter, PBETurnTarget.FoeRight }; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { return new PBETurnTarget[] { PBETurnTarget.FoeLeft, PBETurnTarget.FoeCenter, PBETurnTarget.FoeRight }; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return new PBETurnTarget[] { PBETurnTarget.FoeLeft, PBETurnTarget.FoeCenter }; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.SingleNotSelf: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return new PBETurnTarget[] { PBETurnTarget.AllyCenter, PBETurnTarget.AllyRight, PBETurnTarget.FoeLeft, PBETurnTarget.FoeCenter, PBETurnTarget.FoeRight }; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { return new PBETurnTarget[] { PBETurnTarget.AllyLeft, PBETurnTarget.AllyRight, PBETurnTarget.FoeLeft, PBETurnTarget.FoeCenter, PBETurnTarget.FoeRight }; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return new PBETurnTarget[] { PBETurnTarget.AllyLeft, PBETurnTarget.AllyCenter, PBETurnTarget.FoeLeft, PBETurnTarget.FoeCenter, PBETurnTarget.FoeRight }; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.SingleSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left) { return new PBETurnTarget[] { PBETurnTarget.AllyCenter, PBETurnTarget.FoeCenter, PBETurnTarget.FoeRight }; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { return new PBETurnTarget[] { PBETurnTarget.AllyLeft, PBETurnTarget.AllyRight, PBETurnTarget.FoeLeft, PBETurnTarget.FoeCenter, PBETurnTarget.FoeRight }; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { return new PBETurnTarget[] { PBETurnTarget.AllyCenter, PBETurnTarget.FoeLeft, PBETurnTarget.FoeCenter }; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } default: throw new ArgumentOutOfRangeException(nameof(targets)); } } case PBEBattleFormat.Rotation: { switch (targets) { case PBEMoveTarget.SingleFoeSurrounding: case PBEMoveTarget.SingleNotSelf: case PBEMoveTarget.SingleSurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { return new PBETurnTarget[] { PBETurnTarget.FoeCenter }; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } case PBEMoveTarget.RandomFoeSurrounding: case PBEMoveTarget.Self: case PBEMoveTarget.SelfOrAllySurrounding: case PBEMoveTarget.SingleAllySurrounding: { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { return new PBETurnTarget[] { PBETurnTarget.AllyCenter }; } else { throw new ArgumentException(nameof(pkmn.FieldPosition)); } } default: throw new ArgumentOutOfRangeException(nameof(targets)); } } default: throw new InvalidDataException(nameof(pkmn.Battle.BattleFormat)); } } internal static void VerifyPosition(PBEBattleFormat format, PBEFieldPosition pos) { if (pos != PBEFieldPosition.None && pos < PBEFieldPosition.MAX) { switch (format) { case PBEBattleFormat.Single: { switch (pos) { case PBEFieldPosition.Center: return; } break; } case PBEBattleFormat.Double: { switch (pos) { case PBEFieldPosition.Left: case PBEFieldPosition.Right: return; } break; } case PBEBattleFormat.Triple: case PBEBattleFormat.Rotation: { return; } } } throw new ArgumentOutOfRangeException(nameof(pos)); } public static int GetFieldPositionIndex(this PBETrainer trainer, PBEFieldPosition position) { if (!trainer.OwnsSpot(position)) { throw new ArgumentOutOfRangeException(nameof(position)); } PBEBattleFormat battleFormat = trainer.Battle.BattleFormat; int index = trainer.Team.Trainers.IndexOf(trainer); switch (battleFormat) { case PBEBattleFormat.Single: { switch (position) { case PBEFieldPosition.Center: return 0; } break; } case PBEBattleFormat.Double: { switch (position) { case PBEFieldPosition.Left: return 0; case PBEFieldPosition.Right: return index == 1 ? 0 : 1; } break; } case PBEBattleFormat.Triple: { switch (position) { case PBEFieldPosition.Left: return 0; case PBEFieldPosition.Center: return index == 1 ? 0 : 1; case PBEFieldPosition.Right: return index == 2 ? 0 : 2; } break; } case PBEBattleFormat.Rotation: { switch (position) { case PBEFieldPosition.Center: return 0; case PBEFieldPosition.Left: return 1; case PBEFieldPosition.Right: return 2; } break; } } throw new Exception(); } public static bool OwnsSpot(this PBETrainer trainer, PBEFieldPosition pos) { return GetTrainer(trainer.Team, pos) == trainer; } public static PBETrainer GetTrainer(this PBETeam team, PBEFieldPosition pos) { PBEBattleFormat format = team.Battle.BattleFormat; VerifyPosition(format, pos); int i = 0; if (team.Trainers.Count != 1) { switch (format) { case PBEBattleFormat.Double: i = pos == PBEFieldPosition.Left ? 0 : 1; break; case PBEBattleFormat.Triple: i = pos == PBEFieldPosition.Left ? 0 : pos == PBEFieldPosition.Center ? 1 : 2; break; } } return team.Trainers[i]; } } ================================================ FILE: PokemonBattleEngine/Battle/TrainerInfo.cs ================================================ using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngine.Data.Legality; using Kermalis.PokemonBattleEngine.Utils; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; namespace Kermalis.PokemonBattleEngine.Battle; public abstract class PBETrainerInfoBase { public ReadOnlyCollection Party { get; } private readonly PBESettings? _requiredSettings; protected PBETrainerInfoBase(IPBEPokemonCollection party) { if (party is IPBEPartyPokemonCollection ppc) { if (!ppc.Any(p => p.HP > 0 && !p.PBEIgnore)) { throw new ArgumentException("Party must have at least 1 conscious battler", nameof(party)); } } else { if (!party.Any(p => !p.PBEIgnore)) { throw new ArgumentException("Party must have at least 1 conscious battler", nameof(party)); } } if (party is PBELegalPokemonCollection lp) { _requiredSettings = lp.Settings; } Party = new ReadOnlyCollection(party.ToArray()); } public bool IsOkayForSettings(PBESettings settings) { settings.ShouldBeReadOnly(nameof(settings)); if (_requiredSettings is not null) { return settings.Equals(_requiredSettings); } if (this is PBETrainerInfo ti && ti.GainsEXP && settings.MaxLevel > 100) { throw new ArgumentException("Cannot start a battle with EXP enabled and a higher MaxLevel than 100. Not supported."); } return true; } } public sealed class PBETrainerInfo : PBETrainerInfoBase { public string Name { get; } public bool GainsEXP { get; } public ReadOnlyCollection<(PBEItem Item, uint Quantity)> Inventory { get; } public PBETrainerInfo(IPBEPokemonCollection party, string name, bool gainsEXP, IList<(PBEItem Item, uint Quantity)>? inventory = null) : base(party) { if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentOutOfRangeException(nameof(name)); } Name = name; GainsEXP = gainsEXP; if (inventory is null || inventory.Count == 0) { Inventory = PBEEmptyReadOnlyCollection<(PBEItem, uint)>.Value; } else { Inventory = new ReadOnlyCollection<(PBEItem, uint)>(inventory); } } } public sealed class PBEWildInfo : PBETrainerInfoBase { public PBEWildInfo(IPBEPokemonCollection party) : base(party) { } } ================================================ FILE: PokemonBattleEngine/Battle/TypeEffectiveness.cs ================================================ using Kermalis.PokemonBattleEngine.Data; using System; using System.Collections.Generic; namespace Kermalis.PokemonBattleEngine.Battle; public static class PBETypeEffectiveness { #region Static Collections /// The type effectiveness table. The first key is the attacking type and the second key is the defending type. private static readonly Dictionary> _table = new() { { PBEType.None, new Dictionary { { PBEType.None, 1.0f }, { PBEType.Bug, 1.0f }, { PBEType.Dark, 1.0f }, { PBEType.Dragon, 1.0f }, { PBEType.Electric, 1.0f }, { PBEType.Fighting, 1.0f }, { PBEType.Fire, 1.0f }, { PBEType.Flying, 1.0f }, { PBEType.Ghost, 1.0f }, { PBEType.Grass, 1.0f }, { PBEType.Ground, 1.0f }, { PBEType.Ice, 1.0f }, { PBEType.Normal, 1.0f }, { PBEType.Poison, 1.0f }, { PBEType.Psychic, 1.0f }, { PBEType.Rock, 1.0f }, { PBEType.Steel, 1.0f }, { PBEType.Water, 1.0f }, } }, { PBEType.Bug, new Dictionary { { PBEType.None, 1.0f }, { PBEType.Bug, 1.0f }, { PBEType.Dark, 2.0f }, { PBEType.Dragon, 1.0f }, { PBEType.Electric, 1.0f }, { PBEType.Fighting, 0.5f }, { PBEType.Fire, 0.5f }, { PBEType.Flying, 0.5f }, { PBEType.Ghost, 0.5f }, { PBEType.Grass, 2.0f }, { PBEType.Ground, 1.0f }, { PBEType.Ice, 1.0f }, { PBEType.Normal, 1.0f }, { PBEType.Poison, 0.5f }, { PBEType.Psychic, 2.0f }, { PBEType.Rock, 1.0f }, { PBEType.Steel, 0.5f }, { PBEType.Water, 1.0f }, } }, { PBEType.Dark, new Dictionary { { PBEType.None, 1.0f }, { PBEType.Bug, 1.0f }, { PBEType.Dark, 0.5f }, { PBEType.Dragon, 1.0f }, { PBEType.Electric, 1.0f }, { PBEType.Fighting, 0.5f }, { PBEType.Fire, 1.0f }, { PBEType.Flying, 1.0f }, { PBEType.Ghost, 2.0f }, { PBEType.Grass, 1.0f }, { PBEType.Ground, 1.0f }, { PBEType.Ice, 1.0f }, { PBEType.Normal, 1.0f }, { PBEType.Poison, 1.0f }, { PBEType.Psychic, 2.0f }, { PBEType.Rock, 1.0f }, { PBEType.Steel, 0.5f }, { PBEType.Water, 1.0f }, } }, { PBEType.Dragon, new Dictionary { { PBEType.None, 1.0f }, { PBEType.Bug, 1.0f }, { PBEType.Dark, 1.0f }, { PBEType.Dragon, 2.0f }, { PBEType.Electric, 1.0f }, { PBEType.Fighting, 1.0f }, { PBEType.Fire, 1.0f }, { PBEType.Flying, 1.0f }, { PBEType.Ghost, 1.0f }, { PBEType.Grass, 1.0f }, { PBEType.Ground, 1.0f }, { PBEType.Ice, 1.0f }, { PBEType.Normal, 1.0f }, { PBEType.Poison, 1.0f }, { PBEType.Psychic, 1.0f }, { PBEType.Rock, 1.0f }, { PBEType.Steel, 0.5f }, { PBEType.Water, 1.0f }, } }, { PBEType.Electric, new Dictionary { { PBEType.None, 1.0f }, { PBEType.Bug, 1.0f }, { PBEType.Dark, 1.0f }, { PBEType.Dragon, 0.5f }, { PBEType.Electric, 0.5f }, { PBEType.Fighting, 1.0f }, { PBEType.Fire, 1.0f }, { PBEType.Flying, 2.0f }, { PBEType.Ghost, 1.0f }, { PBEType.Grass, 0.5f }, { PBEType.Ground, 0.0f }, { PBEType.Ice, 1.0f }, { PBEType.Normal, 1.0f }, { PBEType.Poison, 1.0f }, { PBEType.Psychic, 1.0f }, { PBEType.Rock, 1.0f }, { PBEType.Steel, 1.0f }, { PBEType.Water, 2.0f }, } }, { PBEType.Fighting, new Dictionary { { PBEType.None, 1.0f }, { PBEType.Bug, 0.5f }, { PBEType.Dark, 2.0f }, { PBEType.Dragon, 1.0f }, { PBEType.Electric, 1.0f }, { PBEType.Fighting, 1.0f }, { PBEType.Fire, 1.0f }, { PBEType.Flying, 0.5f }, { PBEType.Ghost, 0.0f }, { PBEType.Grass, 1.0f }, { PBEType.Ground, 1.0f }, { PBEType.Ice, 2.0f }, { PBEType.Normal, 2.0f }, { PBEType.Poison, 0.5f }, { PBEType.Psychic, 0.5f }, { PBEType.Rock, 2.0f }, { PBEType.Steel, 2.0f }, { PBEType.Water, 1.0f }, } }, { PBEType.Fire, new Dictionary { { PBEType.None, 1.0f }, { PBEType.Bug, 2.0f }, { PBEType.Dark, 1.0f }, { PBEType.Dragon, 0.5f }, { PBEType.Electric, 1.0f }, { PBEType.Fighting, 1.0f }, { PBEType.Fire, 0.5f }, { PBEType.Flying, 1.0f }, { PBEType.Ghost, 1.0f }, { PBEType.Grass, 2.0f }, { PBEType.Ground, 1.0f }, { PBEType.Ice, 2.0f }, { PBEType.Normal, 1.0f }, { PBEType.Poison, 1.0f }, { PBEType.Psychic, 1.0f }, { PBEType.Rock, 0.5f }, { PBEType.Steel, 2.0f }, { PBEType.Water, 0.5f }, } }, { PBEType.Flying, new Dictionary { { PBEType.None, 1.0f }, { PBEType.Bug, 2.0f }, { PBEType.Dark, 1.0f }, { PBEType.Dragon, 1.0f }, { PBEType.Electric, 0.5f }, { PBEType.Fighting, 2.0f }, { PBEType.Fire, 1.0f }, { PBEType.Flying, 1.0f }, { PBEType.Ghost, 1.0f }, { PBEType.Grass, 2.0f }, { PBEType.Ground, 1.0f }, { PBEType.Ice, 1.0f }, { PBEType.Normal, 1.0f }, { PBEType.Poison, 1.0f }, { PBEType.Psychic, 1.0f }, { PBEType.Rock, 0.5f }, { PBEType.Steel, 0.5f }, { PBEType.Water, 1.0f }, } }, { PBEType.Ghost, new Dictionary { { PBEType.None, 1.0f }, { PBEType.Bug, 1.0f }, { PBEType.Dark, 0.5f }, { PBEType.Dragon, 1.0f }, { PBEType.Electric, 1.0f }, { PBEType.Fighting, 1.0f }, { PBEType.Fire, 1.0f }, { PBEType.Flying, 1.0f }, { PBEType.Ghost, 2.0f }, { PBEType.Grass, 1.0f }, { PBEType.Ground, 1.0f }, { PBEType.Ice, 1.0f }, { PBEType.Normal, 0.0f }, { PBEType.Poison, 1.0f }, { PBEType.Psychic, 2.0f }, { PBEType.Rock, 1.0f }, { PBEType.Steel, 0.5f }, { PBEType.Water, 1.0f }, } }, { PBEType.Grass, new Dictionary { { PBEType.None, 1.0f }, { PBEType.Bug, 0.5f }, { PBEType.Dark, 1.0f }, { PBEType.Dragon, 0.5f }, { PBEType.Electric, 1.0f }, { PBEType.Fighting, 1.0f }, { PBEType.Fire, 0.5f }, { PBEType.Flying, 0.5f }, { PBEType.Ghost, 1.0f }, { PBEType.Grass, 0.5f }, { PBEType.Ground, 2.0f }, { PBEType.Ice, 1.0f }, { PBEType.Normal, 1.0f }, { PBEType.Poison, 0.5f }, { PBEType.Psychic, 1.0f }, { PBEType.Rock, 2.0f }, { PBEType.Steel, 0.5f }, { PBEType.Water, 2.0f }, } }, { PBEType.Ground, new Dictionary { { PBEType.None, 1.0f }, { PBEType.Bug, 0.5f }, { PBEType.Dark, 1.0f }, { PBEType.Dragon, 1.0f }, { PBEType.Electric, 2.0f }, { PBEType.Fighting, 1.0f }, { PBEType.Fire, 2.0f }, { PBEType.Flying, 0.0f }, { PBEType.Ghost, 1.0f }, { PBEType.Grass, 0.5f }, { PBEType.Ground, 1.0f }, { PBEType.Ice, 1.0f }, { PBEType.Normal, 1.0f }, { PBEType.Poison, 2.0f }, { PBEType.Psychic, 1.0f }, { PBEType.Rock, 2.0f }, { PBEType.Steel, 2.0f }, { PBEType.Water, 1.0f }, } }, { PBEType.Ice, new Dictionary { { PBEType.None, 1.0f }, { PBEType.Bug, 1.0f }, { PBEType.Dark, 1.0f }, { PBEType.Dragon, 2.0f }, { PBEType.Electric, 1.0f }, { PBEType.Fighting, 1.0f }, { PBEType.Fire, 0.5f }, { PBEType.Flying, 2.0f }, { PBEType.Ghost, 1.0f }, { PBEType.Grass, 2.0f }, { PBEType.Ground, 2.0f }, { PBEType.Ice, 0.5f }, { PBEType.Normal, 1.0f }, { PBEType.Poison, 1.0f }, { PBEType.Psychic, 1.0f }, { PBEType.Rock, 1.0f }, { PBEType.Steel, 0.5f }, { PBEType.Water, 0.5f }, } }, { PBEType.Normal, new Dictionary { { PBEType.None, 1.0f }, { PBEType.Bug, 1.0f }, { PBEType.Dark, 1.0f }, { PBEType.Dragon, 1.0f }, { PBEType.Electric, 1.0f }, { PBEType.Fighting, 1.0f }, { PBEType.Fire, 1.0f }, { PBEType.Flying, 1.0f }, { PBEType.Ghost, 0.0f }, { PBEType.Grass, 1.0f }, { PBEType.Ground, 1.0f }, { PBEType.Ice, 1.0f }, { PBEType.Normal, 1.0f }, { PBEType.Poison, 1.0f }, { PBEType.Psychic, 1.0f }, { PBEType.Rock, 0.5f }, { PBEType.Steel, 0.5f }, { PBEType.Water, 1.0f }, } }, { PBEType.Poison, new Dictionary { { PBEType.None, 1.0f }, { PBEType.Bug, 1.0f }, { PBEType.Dark, 1.0f }, { PBEType.Dragon, 1.0f }, { PBEType.Electric, 1.0f }, { PBEType.Fighting, 1.0f }, { PBEType.Fire, 1.0f }, { PBEType.Flying, 1.0f }, { PBEType.Ghost, 0.5f }, { PBEType.Grass, 2.0f }, { PBEType.Ground, 0.5f }, { PBEType.Ice, 1.0f }, { PBEType.Normal, 1.0f }, { PBEType.Poison, 0.5f }, { PBEType.Psychic, 1.0f }, { PBEType.Rock, 0.5f }, { PBEType.Steel, 0.0f }, { PBEType.Water, 1.0f }, } }, { PBEType.Psychic, new Dictionary { { PBEType.None, 1.0f }, { PBEType.Bug, 1.0f }, { PBEType.Dark, 0.0f }, { PBEType.Dragon, 1.0f }, { PBEType.Electric, 1.0f }, { PBEType.Fighting, 2.0f }, { PBEType.Fire, 1.0f }, { PBEType.Flying, 1.0f }, { PBEType.Ghost, 1.0f }, { PBEType.Grass, 1.0f }, { PBEType.Ground, 1.0f }, { PBEType.Ice, 1.0f }, { PBEType.Normal, 1.0f }, { PBEType.Poison, 2.0f }, { PBEType.Psychic, 0.5f }, { PBEType.Rock, 1.0f }, { PBEType.Steel, 0.5f }, { PBEType.Water, 1.0f }, } }, { PBEType.Rock, new Dictionary { { PBEType.None, 1.0f }, { PBEType.Bug, 2.0f }, { PBEType.Dark, 1.0f }, { PBEType.Dragon, 1.0f }, { PBEType.Electric, 1.0f }, { PBEType.Fighting, 0.5f }, { PBEType.Fire, 2.0f }, { PBEType.Flying, 2.0f }, { PBEType.Ghost, 1.0f }, { PBEType.Grass, 1.0f }, { PBEType.Ground, 0.5f }, { PBEType.Ice, 2.0f }, { PBEType.Normal, 1.0f }, { PBEType.Poison, 1.0f }, { PBEType.Psychic, 1.0f }, { PBEType.Rock, 1.0f }, { PBEType.Steel, 0.5f }, { PBEType.Water, 1.0f }, } }, { PBEType.Steel, new Dictionary { { PBEType.None, 1.0f }, { PBEType.Bug, 1.0f }, { PBEType.Dark, 1.0f }, { PBEType.Dragon, 1.0f }, { PBEType.Electric, 0.5f }, { PBEType.Fighting, 1.0f }, { PBEType.Fire, 0.5f }, { PBEType.Flying, 1.0f }, { PBEType.Ghost, 1.0f }, { PBEType.Grass, 1.0f }, { PBEType.Ground, 1.0f }, { PBEType.Ice, 2.0f }, { PBEType.Normal, 1.0f }, { PBEType.Poison, 1.0f }, { PBEType.Psychic, 1.0f }, { PBEType.Rock, 2.0f }, { PBEType.Steel, 0.5f }, { PBEType.Water, 0.5f }, } }, { PBEType.Water, new Dictionary { { PBEType.None, 1.0f }, { PBEType.Bug, 1.0f }, { PBEType.Dark, 1.0f }, { PBEType.Dragon, 0.5f }, { PBEType.Electric, 1.0f }, { PBEType.Fighting, 1.0f }, { PBEType.Fire, 2.0f }, { PBEType.Flying, 1.0f }, { PBEType.Ghost, 1.0f }, { PBEType.Grass, 0.5f }, { PBEType.Ground, 2.0f }, { PBEType.Ice, 1.0f }, { PBEType.Normal, 1.0f }, { PBEType.Poison, 1.0f }, { PBEType.Psychic, 1.0f }, { PBEType.Rock, 2.0f }, { PBEType.Steel, 1.0f }, { PBEType.Water, 0.5f }, } } }; #endregion public static PBEResult IsAffectedByAttack(PBEBattlePokemon user, PBEBattlePokemon target, PBEType moveType, out float damageMultiplier, bool useKnownInfo = false) { if (moveType >= PBEType.MAX) { throw new ArgumentOutOfRangeException(nameof(moveType)); } PBEResult result; if (moveType == PBEType.Ground) { result = target.IsGrounded(user, useKnownInfo: useKnownInfo); if (result != PBEResult.Success) { damageMultiplier = 0; return result; } } bool ignoreGhost = user.Ability == PBEAbility.Scrappy || target.Status2.HasFlag(PBEStatus2.Identified), ignoreDark = target.Status2.HasFlag(PBEStatus2.MiracleEye); damageMultiplier = GetEffectiveness(moveType, target, useKnownInfo, ignoreGhost: ignoreGhost, ignoreDark: ignoreDark); if (damageMultiplier <= 0) // (-infinity, 0] { damageMultiplier = 0; return PBEResult.Ineffective_Type; } else if (damageMultiplier < 1) // (0, 1) { result = PBEResult.NotVeryEffective_Type; } else if (damageMultiplier == 1) // [1, 1] { result = PBEResult.Success; } else // (1, infinity) { return PBEResult.SuperEffective_Type; } PBEAbility kAbility = useKnownInfo ? target.KnownAbility : target.Ability; if (kAbility == PBEAbility.WonderGuard && !user.HasCancellingAbility()) { damageMultiplier = 0; result = PBEResult.Ineffective_Ability; } return result; } /// Checks if 's type affects the target, taking into account . public static PBEResult ThunderWaveTypeCheck(PBEBattlePokemon user, PBEBattlePokemon target, PBEMove move, bool useKnownInfo = false) { PBEType moveType = user.GetMoveType(move); float d = GetEffectiveness(moveType, target, useKnownInfo); if (d <= 0) { return PBEResult.Ineffective_Type; } return PBEResult.Success; } public static float GetEffectiveness(PBEType attackingType, PBEType defendingType, bool ignoreGhost = false, bool ignoreDark = false) { if (attackingType >= PBEType.MAX) { throw new ArgumentOutOfRangeException(nameof(attackingType)); } if (defendingType >= PBEType.MAX) { throw new ArgumentOutOfRangeException(nameof(defendingType)); } float d = _table[attackingType][defendingType]; if (d <= 0 && ((ignoreGhost && defendingType == PBEType.Ghost) || (ignoreDark && defendingType == PBEType.Dark))) { return 1; } return d; } public static float GetEffectiveness(PBEType attackingType, PBEType defendingType1, PBEType defendingType2, bool ignoreGhost = false, bool ignoreDark = false) { float d = GetEffectiveness(attackingType, defendingType1, ignoreGhost: ignoreGhost, ignoreDark: ignoreDark); d *= GetEffectiveness(attackingType, defendingType2, ignoreGhost: ignoreGhost, ignoreDark: ignoreDark); return d; } public static float GetEffectiveness(PBEType attackingType, IPBEPokemonTypes defendingTypes, bool ignoreGhost = false, bool ignoreDark = false) { return GetEffectiveness(attackingType, defendingTypes.Type1, defendingTypes.Type2, ignoreGhost: ignoreGhost, ignoreDark: ignoreDark); } public static float GetEffectiveness_Known(PBEType attackingType, IPBEPokemonKnownTypes defendingTypes, bool ignoreGhost = false, bool ignoreDark = false) { return GetEffectiveness(attackingType, defendingTypes.KnownType1, defendingTypes.KnownType2, ignoreGhost: ignoreGhost, ignoreDark: ignoreDark); } public static float GetEffectiveness(PBEType attackingType, T defendingTypes, bool useKnownInfo, bool ignoreGhost = false, bool ignoreDark = false) where T : IPBEPokemonTypes, IPBEPokemonKnownTypes { return GetEffectiveness(attackingType, useKnownInfo ? defendingTypes.KnownType1 : defendingTypes.Type1, useKnownInfo ? defendingTypes.KnownType2 : defendingTypes.Type2, ignoreGhost: ignoreGhost, ignoreDark: ignoreDark); } public static float GetStealthRockMultiplier(PBEType type1, PBEType type2) { if (type1 >= PBEType.MAX) { throw new ArgumentOutOfRangeException(nameof(type1)); } if (type2 >= PBEType.MAX) { throw new ArgumentOutOfRangeException(nameof(type2)); } float d = 0.125f; d *= _table[PBEType.Rock][type1]; d *= _table[PBEType.Rock][type2]; return d; } } ================================================ FILE: PokemonBattleEngine/Data/DataEnums.cs ================================================ using Kermalis.PokemonBattleEngine.Battle; using System; namespace Kermalis.PokemonBattleEngine.Data; /// Represents a language the engine supports. public enum PBELanguage : byte { English, French, German, Italian, Japanese_Kana, Japanese_Kanji, Korean, Spanish, MAX } /// Represents a specific Pokémon's gender. public enum PBEGender : byte { /// The Pokémon is female. Female, /// The Pokémon is genderless. Genderless, /// The Pokémon is male. Male, /// Invalid gender. MAX } /// Represents a Pokémon species' ratio. public enum PBEGenderRatio : byte { /// The species is 87.5% male, 12.5% female. M7_F1 = 0x1F, /// The species is 75% male, 25% female. M3_F1 = 0x3F, /// The species is 50% male, 50% female. M1_F1 = 0x7F, /// The species is 25% male, 75% female. M1_F3 = 0xBF, /// The species is 0% male, 100% female. M0_F1 = 0xFE, /// The species is genderless. M0_F0 = 0xFF, /// The species is 100% male, 0% female. M1_F0 = 0x00 } public enum PBEGrowthRate : byte { Erratic = 1, Fast = 4, Fluctuating = 2, MediumFast = 0, MediumSlow = 3, Slow = 5, MAX = 6 // 6 & 7 in-game are clones of MediumFast, but no Pokémon uses 6 or 7 } /// Represents a Pokémon stat. public enum PBEStat : byte { /// Hit points. HP, /// Attack. Attack, /// Defense. Defense, /// Special Attack. SpAttack, /// Special Defense. SpDefense, /// Speed. Speed, /// Accuracy. Accuracy, /// Evasion. Evasion } /// Represents a specific 's category. public enum PBEMoveCategory : byte { /// The move deals no damage. Status, /// The move deals physical damage using the Attack and Defense stats. Physical, /// The move deals special damage using the Special Attack and Special Defense stats. Special, /// Invalid category. MAX } /// Represents the various methods in which a Pokémon can learn a . public enum PBEType : byte { None, Bug, Dark, Dragon, Electric, Fighting, Fire, Flying, Ghost, Grass, Ground, Ice, Normal, Poison, Psychic, Rock, Steel, Water, MAX } /// Represents a specific Pokémon's nature. public enum PBENature : byte { /// The Pokémon favors attack and hinders special attack. Adamant = 3, /// The Pokémon doesn't favor any stat. Bashful = 18, /// The Pokémon favors defense and hinders attack. Bold = 5, /// The Pokémon favors attack and hinders speed. Brave = 2, /// The Pokémon favors special defense and hinders attack. Calm = 20, /// The Pokémon favors special defense and hinders special attack. Careful = 23, /// The Pokémon doesn't favor any stat. Docile = 6, /// The Pokémon favors special defense and hinders defense. Gentle = 21, /// The Pokémon doesn't favor any stat. Hardy = 0, /// The Pokémon favors speed and hinders defense. Hasty = 11, /// The Pokémon favors defense and hinders special attack. Impish = 8, /// The Pokémon favors speed and hinders special attack. Jolly = 13, /// The Pokémon favors defense and hinders special defense. Lax = 9, /// The Pokémon favors attack and hinders defense. Lonely = 1, /// The Pokémon favors special attack and hinders defense. Mild = 16, /// The Pokémon favors special attack and hinders attack. Modest = 15, /// The Pokémon favors speed and hinders special defense. Naive = 14, /// The Pokémon favors attack and hinders special defense. Naughty = 4, /// The Pokémon favors special attack and hinders speed. Quiet = 17, /// The Pokémon doesn't favor any stat. Quirky = 24, /// The Pokémon favors special attack and hinders special defense. Rash = 19, /// The Pokémon favors defense and hinders speed. Relaxed = 7, /// The Pokémon favors special defense and hinders speed. Sassy = 22, /// The Pokémon doesn't favor any stat. Serious = 12, /// The Pokémon favors speed and hinders attack. Timid = 10, /// Invalid nature. MAX = 25 } public enum PBEFlavor : byte { Bitter = 3, Dry = 2, Sour = 1, Spicy = 0, Sweet = 4, MAX = 5 } /// Represents a specific Pokémon's held item. public enum PBEItem : ushort { /// No item. None = 0, AbsorbBulb = 545, // TODO AdamantOrb = 135, AguavBerry = 162, AirBalloon = 541, // TODO /// No effect. AmuletCoin = 223, Antidote = 18, // TODO ApicotBerry = 205, /// No effect. ArmorFossil = 104, AspearBerry = 153, // TODO Awakening = 21, // TODO BabiriBerry = 199, // TODO /// No effect. BalmMushroom = 580, BelueBerry = 183, // TODO BerryJuice = 43, /// No effect. BigMushroom = 87, /// No effect. BigNugget = 581, /// No effect. BigPearl = 89, BigRoot = 296, BindingBand = 544, // TODO BlackBelt = 241, /// No effect. BlackFlute = 68, BlackGlasses = 240, BlackSludge = 281, /// No effect. BlkApricorn = 491, /// No effect. BluApricorn = 486, /// No effect. BlueFlute = 65, /// No effect. BlueScarf = 261, /// No effect. BlueShard = 73, BlukBerry = 165, // TODO /// No effect. BridgeMailD = 145, /// No effect. BridgeMailM = 148, /// No effect. BridgeMailS = 144, /// No effect. BridgeMailT = 146, /// No effect. BridgeMailV = 147, BrightPowder = 213, BugGem = 558, BurnDrive = 118, BurnHeal = 19, // TODO /// No effect. Calcium = 49, /// No effect. Carbos = 48, Casteliacone = 591, // TODO CellBattery = 546, // TODO Charcoal = 249, ChartiBerry = 195, // TODO CheriBerry = 149, // TODO CherishBall = 16, ChestoBerry = 150, // TODO ChilanBerry = 200, // TODO ChillDrive = 119, ChoiceBand = 220, ChoiceScarf = 287, ChoiceSpecs = 297, ChopleBerry = 189, // TODO /// No effect. ClawFossil = 100, /// No effect. CleanseTag = 224, /// No effect. CleverWing = 569, CobaBerry = 192, // TODO ColburBerry = 198, // TODO /// No effect. CometShard = 583, CornnBerry = 175, // TODO /// No effect. CoverFossil = 572, CustapBerry = 210, // TODO /// No effect. DampMulch = 96, DampRock = 285, DarkGem = 562, /// No effect. DawnStone = 109, DeepSeaScale = 227, DeepSeaTooth = 226, DestinyKnot = 280, DireHit = 56, // TODO DiveBall = 7, /// No effect. DomeFossil = 102, DouseDrive = 116, DracoPlate = 311, DragonFang = 250, DragonGem = 561, /// No effect. DragonScale = 235, DreadPlate = 312, DreamBall = 576, /// No effect. DubiousDisc = 324, DurinBerry = 182, // TODO DuskBall = 13, /// No effect. DuskStone = 108, EarthPlate = 305, EjectButton = 547, // TODO /// No effect. Electirizer = 322, ElectricGem = 550, Elixir = 40, // TODO EnergyPowder = 34, // TODO EnergyRoot = 35, // TODO EnigmaBerry = 208, // TODO /// No effect. EscapeRope = 78, Ether = 38, // TODO /// No effect. Everstone = 229, Eviolite = 538, ExpertBelt = 268, ExpShare = 216, FastBall = 492, /// No effect. FavoredMail = 138, FightingGem = 553, FigyBerry = 159, FireGem = 548, /// No effect. FireStone = 82, FistPlate = 303, /// The Pokémon contracts at the end of each turn if it has no other and it does not have . FlameOrb = 273, FlamePlate = 298, FloatStone = 539, // TODO FluffyTail = 64, FlyingGem = 556, FocusBand = 230, FocusSash = 275, FreshWater = 30, // TODO FriendBall = 497, FullHeal = 27, // TODO FullIncense = 316, // TODO FullRestore = 23, // TODO GanlonBerry = 202, /// No effect. GeniusWing = 568, GhostGem = 560, /// No effect. GooeyMulch = 98, GrassGem = 551, GreatBall = 3, /// No effect. GreenScarf = 263, /// No effect. GreenShard = 75, /// No effect. GreetMail = 137, GrepaBerry = 173, // TODO GripClaw = 286, // TODO GriseousOrb = 112, /// No effect. GrnApricorn = 488, GroundGem = 555, /// No effect. GrowthMulch = 95, GuardSpec = 55, // TODO HabanBerry = 197, // TODO HardStone = 238, HealBall = 14, HealPowder = 36, // TODO /// No effect. HealthWing = 565, /// No effect. HeartScale = 93, HeatRock = 284, HeavyBall = 495, /// No effect. HelixFossil = 101, HondewBerry = 172, // TODO /// No effect. Honey = 94, /// No effect. HPUp = 45, HyperPotion = 25, // TODO IapapaBerry = 163, IceGem = 552, IceHeal = 20, // TODO IciclePlate = 302, IcyRock = 282, /// No effect. InquiryMail = 141, InsectPlate = 308, /// No effect. Iron = 47, IronBall = 278, // TODO IronPlate = 313, JabocaBerry = 211, // TODO KasibBerry = 196, // TODO KebiaBerry = 190, // TODO KelpsyBerry = 170, // TODO KingsRock = 221, // TODO LaggingTail = 279, // TODO LansatBerry = 206, // TODO LavaCookie = 42, // TODO LaxIncense = 255, /// No effect. LeafStone = 85, Leftovers = 234, Lemonade = 32, // TODO LeppaBerry = 154, // TODO LevelBall = 493, LiechiBerry = 201, LifeOrb = 270, LightBall = 236, LightClay = 269, /// No effect. LikeMail = 142, LoveBall = 496, /// No effect. LuckIncense = 319, LuckyEgg = 231, LuckyPunch = 256, LumBerry = 157, // TODO LureBall = 494, LustrousOrb = 136, LuxuryBall = 11, MachoBrace = 215, /// No effect. Magmarizer = 323, Magnet = 242, MagoBerry = 161, MagostBerry = 176, // TODO MasterBall = 1, MaxElixir = 41, // TODO MaxEther = 39, // TODO MaxPotion = 24, // TODO /// No effect. MaxRepel = 77, MaxRevive = 29, // TODO MeadowPlate = 301, MentalHerb = 219, // TODO MetalCoat = 233, MetalPowder = 257, Metronome = 277, // TODO MicleBerry = 209, // TODO MindPlate = 307, MiracleSeed = 239, MoomooMilk = 33, // TODO MoonBall = 498, /// No effect. MoonStone = 81, MuscleBand = 266, /// No effect. MuscleWing = 566, MysticWater = 243, NanabBerry = 166, // TODO NestBall = 8, NetBall = 6, NeverMeltIce = 246, NomelBerry = 178, // TODO NormalGem = 564, /// No effect. Nugget = 92, OccaBerry = 184, // TODO OddIncense = 314, /// No effect. OddKeystone = 111, /// No effect. OldAmber = 103, OldGateau = 54, // TODO OranBerry = 155, /// No effect. OvalStone = 110, PamtreBerry = 180, // TODO ParalyzHeal = 22, // TODO ParkBall = 500, PasshoBerry = 185, // TODO /// No effect. PassOrb = 575, PayapaBerry = 193, // TODO /// No effect. Pearl = 88, /// No effect. PearlString = 582, PechaBerry = 151, // TODO PersimBerry = 156, // TODO PetayaBerry = 204, PinapBerry = 168, // TODO /// No effect. PinkScarf = 262, /// No effect. PlumeFossil = 573, /// No effect. PnkApricorn = 489, PoisonBarb = 245, PoisonGem = 554, PokeBall = 4, PokeDoll = 63, PokeToy = 577, PomegBerry = 169, // TODO Potion = 17, // TODO PowerAnklet = 293, PowerBand = 292, PowerBelt = 290, PowerBracer = 289, PowerHerb = 271, PowerLens = 291, PowerWeight = 294, /// No effect. PPMax = 53, /// No effect. PPUp = 51, PremierBall = 12, /// No effect. PrettyWing = 571, /// No effect. PrismScale = 537, /// No effect. Protector = 321, /// No effect. Protein = 46, PsychicGem = 557, /// No effect. PureIncense = 320, QualotBerry = 171, // TODO QuickBall = 15, QuickClaw = 217, // TODO QuickPowder = 274, RabutaBerry = 177, // TODO RageCandyBar = 504, // TODO /// No effect. RareBone = 106, /// No effect. RareCandy = 50, RawstBerry = 152, // TODO RazorClaw = 326, RazorFang = 327, // TODO RazzBerry = 164, // TODO /// No effect. ReaperCloth = 325, /// No effect. RedApricorn = 485, RedCard = 542, // TODO /// No effect. RedFlute = 67, /// No effect. RedScarf = 260, /// No effect. RedShard = 72, /// No effect. RelicBand = 588, /// No effect. RelicCopper = 584, /// No effect. RelicCrown = 590, /// No effect. RelicGold = 586, /// No effect. RelicSilver = 585, /// No effect. RelicStatue = 589, /// No effect. RelicVase = 587, RepeatBall = 9, /// No effect. Repel = 79, /// No effect. ReplyMail = 143, /// No effect. ResistWing = 567, RevivalHerb = 37, // TODO Revive = 28, // TODO RindoBerry = 187, // TODO RingTarget = 543, // TODO RockGem = 559, RockIncense = 315, RockyHelmet = 540, /// No effect. RootFossil = 99, RoseIncense = 318, RowapBerry = 212, // TODO /// No effect. RSVPMail = 139, SacredAsh = 44, // TODO SafariBall = 5, SalacBerry = 203, ScopeLens = 232, SeaIncense = 254, SharpBeak = 244, ShedShell = 295, // TODO ShellBell = 253, // TODO /// No effect. ShinyStone = 107, /// No effect. ShoalSalt = 70, /// No effect. ShoalShell = 71, ShockDrive = 117, ShucaBerry = 191, // TODO SilkScarf = 251, SilverPowder = 222, SitrusBerry = 158, /// No effect. SkullFossil = 105, SkyPlate = 306, SmokeBall = 228, SmoothRock = 283, SodaPop = 31, // TODO SoftSand = 237, /// No effect. SootheBell = 218, SoulDew = 225, SpellTag = 247, SpelonBerry = 179, // TODO SplashPlate = 299, SpookyPlate = 310, SportBall = 499, /// No effect. StableMulch = 97, /// No effect. Stardust = 90, StarfBerry = 207, /// No effect. StarPiece = 91, SteelGem = 563, Stick = 259, StickyBarb = 288, // TODO StonePlate = 309, /// No effect. SunStone = 80, SuperPotion = 26, // TODO /// No effect. SuperRepel = 76, SweetHeart = 134, // TODO /// No effect. SwiftWing = 570, TamatoBerry = 174, // TODO TangaBerry = 194, // TODO /// No effect. ThanksMail = 140, ThickClub = 258, /// No effect. Thunderstone = 83, TimerBall = 10, /// No effect. TinyMushroom = 86, /// The Pokémon contracts at the end of each turn if it has no other and it does not have or . ToxicOrb = 272, ToxicPlate = 304, TwistedSpoon = 248, UltraBall = 2, UpGrade = 252, WacanBerry = 186, // TODO WaterGem = 549, /// No effect. WaterStone = 84, WatmelBerry = 181, // TODO WaveIncense = 317, WepearBerry = 167, // TODO WhiteHerb = 214, // TODO /// No effect. WhiteFlute = 69, WideLens = 265, WikiBerry = 160, WiseGlasses = 267, /// No effect. WhtApricorn = 490, XAccuracy = 60, // TODO XAttack = 57, // TODO XDefend = 58, // TODO XSpecial = 61, // TODO XSpDef = 62, // TODO XSpeed = 59, // TODO YacheBerry = 188, // TODO /// No effect. YellowFlute = 66, /// No effect. YellowScarf = 264, /// No effect. YellowShard = 74, /// No effect. YlwApricorn = 487, ZapPlate = 300, /// No effect. Zinc = 52, ZoomLens = 276 // TODO } /// Represents a specific Pokémon's special ability. public enum PBEAbility : byte { /// The Pokémon's ability was suppressed with . None = 0, /// The Pokémon has a stronger same-type-attack-bonus. Adaptability = 91, Aftermath = 106, // TODO AirLock = 76, Analytic = 148, // TODO AngerPoint = 83, // TODO Anticipation = 107, ArenaTrap = 71, // TODO BadDreams = 123, /// The Pokémon takes no critical hits. BattleArmor = 4, BigPecks = 145, /// When the Pokémon has low HP, its Fire-type moves get a power boost. Blaze = 66, /// The Pokémon gets a speed boost in harsh sunlight. Chlorophyll = 34, ClearBody = 29, CloudNine = 13, ColorChange = 16, /// The Pokémon accuracy is boosted. Compoundeyes = 14, Contrary = 126, CursedBody = 130, // TODO CuteCharm = 56, Damp = 6, // TODO Defeatist = 129, Defiant = 128, // TODO Download = 88, /// The Pokémon changes the weather to infinite rain. Drizzle = 2, /// The Pokémon changes the weather to infinite harsh sunlight. Drought = 70, DrySkin = 87, // TODO EarlyBird = 48, EffectSpore = 27, /// The Pokémon takes less damage from incoming super-effective moves. Filter = 111, FlameBody = 49, FlareBoost = 138, FlashFire = 18, // TODO FlowerGift = 122, Forecast = 59, Forewarn = 108, // TODO (Also, does this activate when given/taken?) FriendGuard = 132, // TODO Frisk = 119, // TODO (Also, does this activate when given/taken?) Gluttony = 82, // TODO /// The Pokémon's attack is boosted when it is afflicted with a and the damage reduction from is not applied. Guts = 62, Harvest = 139, // TODO Healer = 131, /// The Pokémon takes less damage from Fire-type moves and from a burn. Heatproof = 85, HeavyMetal = 134, // TODO /// No effect in battle. HoneyGather = 118, /// The Pokémon's attack is boosted. HugePower = 37, /// The Pokémon's attack is boosted, but its accuracy is lower for physical moves. Hustle = 55, Hydration = 93, // TODO HyperCutter = 52, /// In a hailstorm, the Pokémon takes no damage from the hailstorm and restores HP at the end of each turn. IceBody = 115, /// No effect in battle. Illuminate = 35, Illusion = 149, Immunity = 17, /// The Pokémon transforms into the foe across from it when switching in. Imposter = 150, Infiltrator = 151, // TODO: Mist InnerFocus = 39, Insomnia = 15, Intimidate = 22, IronBarbs = 160, /// The power of moves with is increased. IronFist = 89, Justified = 154, KeenEye = 51, Klutz = 103, // TODO LeafGuard = 102, /// The Pokémon is immune to Ground-type moves and most entry hazards. Levitate = 26, LightMetal = 135, // TODO Lightningrod = 31, // TODO /// The Pokémon cannot be paralyzed. Limber = 7, LiquidOoze = 64, MagicBounce = 156, // TODO MagicGuard = 98, // TODO MagmaArmor = 40, MagnetPull = 42, // TODO /// The Pokémon's defense is boosted when it is afflicted with a . MarvelScale = 63, Minus = 58, MoldBreaker = 104, Moody = 141, MotorDrive = 78, // TODO Moxie = 153, // TODO Multiscale = 136, // TODO /// No effect in battle. Multitype = 121, Mummy = 152, NaturalCure = 30, /// The Pokémon will always hit and always get hit unless protection is used. NoGuard = 99, Normalize = 96, Oblivious = 12, /// The Pokémon takes no damage from a hailstorm or sandstorm. Overcoat = 142, /// When the Pokémon has low HP, its Grass-type moves get a power boost. Overgrow = 65, OwnTempo = 20, Pickpocket = 124, // TODO Pickup = 53, // TODO Plus = 57, PoisonHeal = 90, PoisonPoint = 38, PoisonTouch = 143, // TODO Prankster = 158, Pressure = 46, // TODO (Also, does this activate when given/taken?) /// The Pokémon's attack is boosted. PurePower = 74, QuickFeet = 95, /// In rain, the Pokémon restores HP at the end of each turn. RainDish = 44, Rattled = 155, Reckless = 120, Regenerator = 144, Rivalry = 79, // TODO RockHead = 69, RoughSkin = 24, RunAway = 50, /// In a sandstorm, the Pokémon takes no damage from the sandstorm and its Rock-, Ground-, and Steel-type moves get a power boost. SandForce = 159, /// The Pokémon gets a speed boost in a sandstorm. SandRush = 146, /// The Pokémon changes the weather to an infinite sandstorm. SandStream = 45, /// In a sandstorm, the Pokémon takes no damage from the sandstorm and gets a 20% evasion boost. SandVeil = 8, SapSipper = 157, // TODO Scrappy = 113, SereneGrace = 32, ShadowTag = 23, // TODO ShedSkin = 61, SheerForce = 125, // TODO /// The Pokémon takes no critical hits. ShellArmor = 75, ShieldDust = 19, // TODO /// The Pokémon's stat changes are doubled. Simple = 86, SkillLink = 92, // TODO: Effect on Triple Kick SlowStart = 112, /// The Pokémon deals more damage when landing critical hits. Sniper = 97, /// In a hailstorm, the Pokémon takes no damage from the hailstorm and gets a 20% evasion boost. SnowCloak = 81, /// The Pokémon changes the weather to an infinite hailstorm. SnowWarning = 117, /// In harsh sunlight, the Pokémon gets a special attack boost and takes damage at the end of each turn. SolarPower = 94, /// The Pokémon takes less damage from incoming super-effective moves. SolidRock = 116, Soundproof = 43, // TODO SpeedBoost = 3, Stall = 100, // TODO Static = 9, Steadfast = 80, Stench = 1, // TODO StickyHold = 60, // TODO StormDrain = 114, // TODO Sturdy = 5, SuctionCups = 21, // TODO /// The Pokémon is more likely to land critical hits. SuperLuck = 105, /// When the Pokémon has low HP, its Bug-type moves get a power boost. Swarm = 68, /// The Pokémon gets a speed boost in rain. SwiftSwim = 33, Synchronize = 28, // TODO TangledFeet = 77, Technician = 101, Telepathy = 140, // TODO Teravolt = 164, /// The Pokémon takes less damage from Ice- and Fire-type moves. ThickFat = 47, /// The Pokémon deals double damage with outgoing not-very-effective moves. TintedLens = 110, /// When the Pokémon has low HP, its Water-type moves get a power boost. Torrent = 67, ToxicBoost = 137, Trace = 36, // TODO (Also, does this activate when given/taken?) Truant = 54, // TODO Turboblaze = 163, Unaware = 109, Unburden = 84, // TODO Unnerve = 127, // TODO VictoryStar = 162, VitalSpirit = 72, VoltAbsorb = 10, // TODO WaterAbsorb = 11, // TODO WaterVeil = 41, WeakArmor = 133, WhiteSmoke = 73, /// The Pokémon is immune to all damaging moves except for moves that would deal super-effective damage. WonderGuard = 25, WonderSkin = 147, ZenMode = 161, // TODO (Also, does this activate when given/taken?) /// Invalid ability. MAX = 165, } // Official IDs for the forms #pragma warning disable CA1069 // Enums values should not be duplicated public enum PBEForm : byte { Arceus = 0, Arceus_Bug = 6, Arceus_Dark = 16, Arceus_Dragon = 15, Arceus_Electric = 12, Arceus_Fighting = 1, Arceus_Fire = 9, Arceus_Flying = 2, Arceus_Ghost = 7, Arceus_Grass = 11, Arceus_Ground = 4, Arceus_Ice = 14, Arceus_Poison = 3, Arceus_Psychic = 13, Arceus_Rock = 5, Arceus_Steel = 8, Arceus_Water = 10, Basculin_Blue = 1, Basculin_Red = 0, Burmy_Plant = 0, Burmy_Sandy = 1, Burmy_Trash = 2, Castform = 0, Castform_Rainy = 2, Castform_Snowy = 3, Castform_Sunny = 1, Cherrim = 0, Cherrim_Sunshine = 1, Darmanitan = 0, Darmanitan_Zen = 1, Deerling_Autumn = 2, Deerling_Spring = 0, Deerling_Summer = 1, Deerling_Winter = 3, Deoxys = 0, Deoxys_Attack = 1, Deoxys_Defense = 2, Deoxys_Speed = 3, Gastrodon_East = 1, Gastrodon_West = 0, Genesect = 0, Genesect_Burn = 3, Genesect_Chill = 4, Genesect_Douse = 1, Genesect_Shock = 2, Giratina = 0, Giratina_Origin = 1, Keldeo = 0, Keldeo_Resolute = 1, Kyurem = 0, Kyurem_Black = 2, Kyurem_White = 1, Landorus = 0, Landorus_Therian = 1, Meloetta = 0, Meloetta_Pirouette = 1, Rotom = 0, Rotom_Fan = 4, Rotom_Frost = 3, Rotom_Heat = 1, Rotom_Mow = 5, Rotom_Wash = 2, Sawsbuck_Autumn = 2, Sawsbuck_Spring = 0, Sawsbuck_Summer = 1, Sawsbuck_Winter = 3, Shaymin = 0, Shaymin_Sky = 1, Shellos_East = 1, Shellos_West = 0, Thundurus = 0, Thundurus_Therian = 1, Tornadus = 0, Tornadus_Therian = 1, Unown_A = 0, Unown_B = 1, Unown_C = 2, Unown_D = 3, Unown_E = 4, Unown_Exclamation = 26, Unown_F = 5, Unown_G = 6, Unown_H = 7, Unown_I = 8, Unown_J = 9, Unown_K = 10, Unown_L = 11, Unown_M = 12, Unown_N = 13, Unown_O = 14, Unown_P = 15, Unown_Q = 16, Unown_Question = 27, Unown_R = 17, Unown_S = 18, Unown_T = 19, Unown_U = 20, Unown_V = 21, Unown_W = 22, Unown_X = 23, Unown_Y = 24, Unown_Z = 25, Wormadam_Plant = 0, Wormadam_Sandy = 1, Wormadam_Trash = 2 #pragma warning restore CA1069 // Enums values should not be duplicated } /// Represents a specific Pokémon species. public enum PBESpecies : ushort { Abomasnow = 460, Abra = 63, Absol = 359, Accelgor = 617, Aerodactyl = 142, Aggron = 306, Aipom = 190, Alakazam = 65, Alomomola = 594, Altaria = 334, Ambipom = 424, Amoonguss = 591, Ampharos = 181, Anorith = 347, Arbok = 24, Arcanine = 59, Arceus = 493, Archen = 566, Archeops = 567, Ariados = 168, Armaldo = 348, Aron = 304, Articuno = 144, Audino = 531, Axew = 610, Azelf = 482, Azumarill = 184, Azurill = 298, Bagon = 371, Baltoy = 343, Banette = 354, Barboach = 339, Basculin = 550, Bastiodon = 411, Bayleef = 153, Beartic = 614, Beautifly = 267, Beedrill = 15, Beheeyem = 606, Beldum = 374, Bellossom = 182, Bellsprout = 69, Bibarel = 400, Bidoof = 399, Bisharp = 625, Blastoise = 9, Blaziken = 257, Blissey = 242, Blitzle = 522, Boldore = 525, Bonsly = 438, Bouffalant = 626, Braviary = 628, Breloom = 286, Bronzong = 437, Bronzor = 436, Budew = 406, Buizel = 418, Bulbasaur = 1, Buneary = 427, Burmy = 412, Butterfree = 12, Cacnea = 331, Cacturne = 332, Camerupt = 323, Carnivine = 455, Carracosta = 565, Carvanha = 318, Cascoon = 268, Castform = 351, Caterpie = 10, Celebi = 251, Chandelure = 609, Chansey = 113, Charizard = 6, Charmander = 4, Charmeleon = 5, Chatot = 441, Cherrim = 421, Cherubi = 420, Chikorita = 152, Chimchar = 390, Chimecho = 358, Chinchou = 170, Chingling = 433, Cinccino = 573, Clamperl = 366, Claydol = 344, Clefable = 36, Clefairy = 35, Cleffa = 173, Cloyster = 91, Cobalion = 638, Cofagrigus = 563, Combee = 415, Combusken = 256, Conkeldurr = 534, Corphish = 341, Corsola = 222, Cottonee = 546, Cradily = 346, Cranidos = 408, Crawdaunt = 342, Cresselia = 488, Croagunk = 453, Crobat = 169, Croconaw = 159, Crustle = 558, Cryogonal = 615, Cubchoo = 613, Cubone = 104, Cyndaquil = 155, Darkrai = 491, Darmanitan = 555, Darumaka = 554, Deerling = 585, Deino = 633, Delcatty = 301, Delibird = 225, Deoxys = 386, Dewgong = 87, Dewott = 502, Dialga = 483, Diglett = 50, Ditto = 132, Dodrio = 85, Doduo = 84, Donphan = 232, Dragonair = 148, Dragonite = 149, Drapion = 452, Dratini = 147, Drifblim = 426, Drifloon = 425, Drilbur = 529, Drowzee = 96, Druddigon = 621, Ducklett = 580, Dugtrio = 51, Dunsparce = 206, Duosion = 578, Durant = 632, Dusclops = 356, Dusknoir = 477, Duskull = 355, Dustox = 269, Dwebble = 557, Eelektrik = 603, Eelektross = 604, Eevee = 133, Ekans = 23, Electabuzz = 125, Electivire = 466, Electrike = 309, Electrode = 101, Elekid = 239, Elgyem = 605, Emboar = 500, Emolga = 587, Empoleon = 395, Entei = 244, Escavalier = 589, Espeon = 196, Excadrill = 530, Exeggcute = 102, Exeggutor = 103, Exploud = 295, Farfetchd = 83, Fearow = 22, Feebas = 349, Feraligatr = 160, Ferroseed = 597, Ferrothorn = 598, Finneon = 456, Flaaffy = 180, Flareon = 136, Floatzel = 419, Flygon = 330, Foongus = 590, Forretress = 205, Fraxure = 611, Frillish = 592, Froslass = 478, Furret = 162, Gabite = 444, Gallade = 475, Galvantula = 596, Garbodor = 569, Garchomp = 445, Gardevoir = 282, Gastly = 92, Gastrodon = 423, Genesect = 649, Gengar = 94, Geodude = 74, Gible = 443, Gigalith = 526, Girafarig = 203, Giratina = 487, Glaceon = 471, Glalie = 362, Glameow = 431, Gligar = 207, Gliscor = 472, Gloom = 44, Golbat = 42, Goldeen = 118, Golduck = 55, Golem = 76, Golett = 622, Golurk = 623, Gorebyss = 368, Gothita = 574, Gothitelle = 576, Gothorita = 575, Granbull = 210, Graveler = 75, Grimer = 88, Grotle = 388, Groudon = 383, Grovyle = 253, Growlithe = 58, Grumpig = 326, Gulpin = 316, Gurdurr = 533, Gyarados = 130, Happiny = 440, Hariyama = 297, Haunter = 93, Haxorus = 612, Heatmor = 631, Heatran = 485, Heracross = 214, Herdier = 507, Hippopotas = 449, Hippowdon = 450, Hitmonchan = 107, Hitmonlee = 106, Hitmontop = 237, HoOh = 250, Honchkrow = 430, Hoothoot = 163, Hoppip = 187, Horsea = 116, Houndoom = 229, Houndour = 228, Huntail = 367, Hydreigon = 635, Hypno = 97, Igglybuff = 174, Illumise = 314, Infernape = 392, Ivysaur = 2, Jellicent = 593, Jigglypuff = 39, Jirachi = 385, Jolteon = 135, Joltik = 595, Jumpluff = 189, Jynx = 124, Kabuto = 140, Kabutops = 141, Kadabra = 64, Kakuna = 14, Kangaskhan = 115, Karrablast = 588, Kecleon = 352, Keldeo = 647, Kingdra = 230, Kingler = 99, Kirlia = 281, Klang = 600, Klink = 599, Klinklang = 601, Koffing = 109, Krabby = 98, Kricketot = 401, Kricketune = 402, Krokorok = 552, Krookodile = 553, Kyogre = 382, Kyurem = 646, Lairon = 305, Lampent = 608, Landorus = 645, Lanturn = 171, Lapras = 131, Larvesta = 636, Larvitar = 246, Latias = 380, Latios = 381, Leafeon = 470, Leavanny = 542, Ledian = 166, Ledyba = 165, Lickilicky = 463, Lickitung = 108, Liepard = 510, Lileep = 345, Lilligant = 549, Lillipup = 506, Linoone = 264, Litwick = 607, Lombre = 271, Lopunny = 428, Lotad = 270, Loudred = 294, Lucario = 448, Ludicolo = 272, Lugia = 249, Lumineon = 457, Lunatone = 337, Luvdisc = 370, Luxio = 404, Luxray = 405, Machamp = 68, Machoke = 67, Machop = 66, Magby = 240, Magcargo = 219, Magikarp = 129, Magmar = 126, Magmortar = 467, Magnemite = 81, Magneton = 82, Magnezone = 462, Makuhita = 296, Mamoswine = 473, Manaphy = 490, Mandibuzz = 630, Manectric = 310, Mankey = 56, Mantine = 226, Mantyke = 458, Maractus = 556, Mareep = 179, Marill = 183, Marowak = 105, Marshtomp = 259, Masquerain = 284, Mawile = 303, Medicham = 308, Meditite = 307, Meganium = 154, Meloetta = 648, Meowth = 52, Mesprit = 481, Metagross = 376, Metang = 375, Metapod = 11, Mew = 151, Mewtwo = 150, Mienfoo = 619, Mienshao = 620, Mightyena = 262, Milotic = 350, Miltank = 241, MimeJr = 439, Minccino = 572, Minun = 312, Misdreavus = 200, Mismagius = 429, Moltres = 146, Monferno = 391, Mothim = 414, MrMime = 122, Mudkip = 258, Muk = 89, Munchlax = 446, Munna = 517, Murkrow = 198, Musharna = 518, Natu = 177, Nidoking = 34, Nidoqueen = 31, Nidoran_F = 29, Nidoran_M = 32, Nidorina = 30, Nidorino = 33, Nincada = 290, Ninetales = 38, Ninjask = 291, Noctowl = 164, Nosepass = 299, Numel = 322, Nuzleaf = 274, Octillery = 224, Oddish = 43, Omanyte = 138, Omastar = 139, Onix = 95, Oshawott = 501, Pachirisu = 417, Palkia = 484, Palpitoad = 536, Panpour = 515, Pansage = 511, Pansear = 513, Paras = 46, Parasect = 47, Patrat = 504, Pawniard = 624, Pelipper = 279, Persian = 53, Petilil = 548, Phanpy = 231, Phione = 489, Pichu = 172, Pidgeot = 18, Pidgeotto = 17, Pidgey = 16, Pidove = 519, Pignite = 499, Pikachu = 25, Piloswine = 221, Pineco = 204, Pinsir = 127, Piplup = 393, Plusle = 311, Politoed = 186, Poliwag = 60, Poliwhirl = 61, Poliwrath = 62, Ponyta = 77, Poochyena = 261, Porygon = 137, Porygon2 = 233, PorygonZ = 474, Primeape = 57, Prinplup = 394, Probopass = 476, Psyduck = 54, Pupitar = 247, Purrloin = 509, Purugly = 432, Quagsire = 195, Quilava = 156, Qwilfish = 211, Raichu = 26, Raikou = 243, Ralts = 280, Rampardos = 409, Rapidash = 78, Raticate = 20, Rattata = 19, Rayquaza = 384, Regice = 378, Regigigas = 486, Regirock = 377, Registeel = 379, Relicanth = 369, Remoraid = 223, Reshiram = 643, Reuniclus = 579, Rhydon = 112, Rhyhorn = 111, Rhyperior = 464, Riolu = 447, Roggenrola = 524, Roselia = 315, Roserade = 407, Rotom = 479, Rufflet = 627, Sableye = 302, Salamence = 373, Samurott = 503, Sandile = 551, Sandshrew = 27, Sandslash = 28, Sawk = 539, Sawsbuck = 586, Sceptile = 254, Scizor = 212, Scolipede = 545, Scrafty = 560, Scraggy = 559, Scyther = 123, Seadra = 117, Seaking = 119, Sealeo = 364, Seedot = 273, Seel = 86, Seismitoad = 537, Sentret = 161, Serperior = 497, Servine = 496, Seviper = 336, Sewaddle = 540, Sharpedo = 319, Shaymin = 492, Shedinja = 292, Shelgon = 372, Shellder = 90, Shellos = 422, Shelmet = 616, Shieldon = 410, Shiftry = 275, Shinx = 403, Shroomish = 285, Shuckle = 213, Shuppet = 353, Sigilyph = 561, Silcoon = 266, Simipour = 516, Simisage = 512, Simisear = 514, Skarmory = 227, Skiploom = 188, Skitty = 300, Skorupi = 451, Skuntank = 435, Slaking = 289, Slakoth = 287, Slowbro = 80, Slowking = 199, Slowpoke = 79, Slugma = 218, Smeargle = 235, Smoochum = 238, Sneasel = 215, Snivy = 495, Snorlax = 143, Snorunt = 361, Snover = 459, Snubbull = 209, Solosis = 577, Solrock = 338, Spearow = 21, Spheal = 363, Spinarak = 167, Spinda = 327, Spiritomb = 442, Spoink = 325, Squirtle = 7, Stantler = 234, Staraptor = 398, Staravia = 397, Starly = 396, Starmie = 121, Staryu = 120, Steelix = 208, Stoutland = 508, Stunfisk = 618, Stunky = 434, Sudowoodo = 185, Suicune = 245, Sunflora = 192, Sunkern = 191, Surskit = 283, Swablu = 333, Swadloon = 541, Swalot = 317, Swampert = 260, Swanna = 581, Swellow = 277, Swinub = 220, Swoobat = 528, Taillow = 276, Tangela = 114, Tangrowth = 465, Tauros = 128, Teddiursa = 216, Tentacool = 72, Tentacruel = 73, Tepig = 498, Terrakion = 639, Throh = 538, Thundurus = 642, Timburr = 532, Tirtouga = 564, Togekiss = 468, Togepi = 175, Togetic = 176, Torchic = 255, Torkoal = 324, Tornadus = 641, Torterra = 389, Totodile = 158, Toxicroak = 454, Tranquill = 520, Trapinch = 328, Treecko = 252, Tropius = 357, Trubbish = 568, Turtwig = 387, Tympole = 535, Tynamo = 602, Typhlosion = 157, Tyranitar = 248, Tyrogue = 236, Umbreon = 197, Unfezant = 521, Unown = 201, Ursaring = 217, Uxie = 480, Vanillish = 583, Vanillite = 582, Vanilluxe = 584, Vaporeon = 134, Venipede = 543, Venomoth = 49, Venonat = 48, Venusaur = 3, Vespiquen = 416, Vibrava = 329, Victini = 494, Victreebel = 71, Vigoroth = 288, Vileplume = 45, Virizion = 640, Volbeat = 313, Volcarona = 637, Voltorb = 100, Vullaby = 629, Vulpix = 37, Wailmer = 320, Wailord = 321, Walrein = 365, Wartortle = 8, Watchog = 505, Weavile = 461, Weedle = 13, Weepinbell = 70, Weezing = 110, Whimsicott = 547, Whirlipede = 544, Whiscash = 340, Whismur = 293, Wigglytuff = 40, Wingull = 278, Wobbuffet = 202, Woobat = 527, Wooper = 194, Wormadam = 413, Wurmple = 265, Wynaut = 360, Xatu = 178, Yamask = 562, Yanma = 193, Yanmega = 469, Zangoose = 335, Zapdos = 145, Zebstrika = 523, Zekrom = 644, Zigzagoon = 263, Zoroark = 571, Zorua = 570, Zubat = 41, Zweilous = 634, MAX = 650 } public enum PBEMoveTarget : byte { All, // Every battler (Ex. Perish Song) AllFoes, // Every foe (Ex. Stealth Rock) AllFoesSurrounding, // All foes surrounding (Ex. Growl) AllSurrounding, // All battlers surrounding (Ex. Earthquake) AllTeam, // User's entire team (Ex. Light Screen) RandomFoeSurrounding, // Randomly picks a surrounding foe (Ex. Outrage) Self, // Self (Ex. Growth) SelfOrAllySurrounding, // Self or adjacent ally (Ex. Acupressure) SingleAllySurrounding, // Adjacent ally (Ex. Helping Hand) SingleFoeSurrounding, // Single foe surrounding (Ex. Me First) SingleNotSelf, // Single battler except itself (Ex. Dark Pulse) SingleSurrounding, // Single battler surrounding (Ex. Tackle) Varies // Possible targets vary (Ex. Curse) } /// Represents a specific 's flags. [Flags] public enum PBEMoveFlag : ulong { /// The move has no flags. None, /// The move's power is boosted by . AffectedByIronFist = 1 << 0, AffectedByMagicCoat = 1 << 1, AffectedByMirrorMove = 1 << 2, AffectedByProtect = 1 << 3, /// The move's power is boosted by . AffectedByReckless = 1 << 4, AffectedBySnatch = 1 << 5, /// The move is blocked by . AffectedBySoundproof = 1 << 6, /// The move always lands a critical hit. AlwaysCrit = 1 << 7, BlockedByGravity = 1 << 8, BlockedFromAssist = 1 << 9, BlockedFromCopycat = 1 << 10, BlockedFromMeFirst = 1 << 12, BlockedFromMetronome = 1 << 13, BlockedFromMimic = 1 << 14, BlockedFromSketch = 1 << 15, BlockedFromSketchWhenSuccessful = 1 << 16, BlockedFromSleepTalk = 1 << 17, /// The move removes from the user. DefrostsUser = 1 << 18, DoubleDamageAirborne = 1 << 19, DoubleDamageMinimized = 1 << 20, DoubleDamageUnderground = 1 << 21, DoubleDamageUnderwater = 1 << 22, DoubleDamageUserDefenseCurl = 1 << 23, /// The move has a higher chance of landing a critical hit. HighCritChance = 1 << 24, /// The move can hit targets. HitsAirborne = 1 << 25, /// The move can hit targets. HitsUnderground = 1 << 26, /// The move can hit targets. HitsUnderwater = 1 << 27, /// The user makes contact with the target, causing it to take damage from the target's , , and . MakesContact = 1 << 28, NeverMissHail = 1 << 29, NeverMissRain = 1 << 30, UnaffectedByGems = 1uL << 31 // TODO } public enum PBEMoveEffect : byte { Acrobatics, Attract, BellyDrum, Bounce, BrickBreak, Brine, Burn, Camouflage, ChangeTarget_ACC, ChangeTarget_ATK, ChangeTarget_DEF, ChangeTarget_EVA, ChangeTarget_SPATK, ChangeTarget_SPATK__IfAttractionPossible, ChangeTarget_SPDEF, ChangeTarget_SPE, ChipAway, Confuse, Conversion, CrushGrip, Curse, Dig, Dive, Endeavor, Entrainment, Eruption, Facade, Feint, FinalGambit, Flatter, Flail, Fly, FocusEnergy, Foresight, FoulPlay, Frustration, GastroAcid, GrassKnot, Growth, Hail, Haze, HeatCrash, HelpingHand, Hex, HiddenPower, Hit, Hit__2Times, Hit__2Times__MaybePoison, Hit__2To5Times, Hit__MaybeBurn, Hit__MaybeBurn__10PercentFlinch, Hit__MaybeBurnFreezeParalyze, Hit__MaybeConfuse, Hit__MaybeFlinch, Hit__MaybeFreeze, Hit__MaybeFreeze__10PercentFlinch, Hit__MaybeLowerTarget_ACC_By1, Hit__MaybeLowerTarget_ATK_By1, Hit__MaybeLowerTarget_DEF_By1, Hit__MaybeLowerTarget_SPATK_By1, Hit__MaybeLowerTarget_SPDEF_By1, Hit__MaybeLowerTarget_SPDEF_By2, Hit__MaybeLowerTarget_SPE_By1, Hit__MaybeLowerUser_ATK_DEF_By1, Hit__MaybeLowerUser_DEF_SPDEF_By1, Hit__MaybeLowerUser_SPATK_By2, Hit__MaybeLowerUser_SPE_By1, Hit__MaybeLowerUser_SPE_DEF_SPDEF_By1, Hit__MaybeParalyze, Hit__MaybeParalyze__10PercentFlinch, Hit__MaybePoison, Hit__MaybeRaiseUser_ATK_By1, Hit__MaybeRaiseUser_ATK_DEF_SPATK_SPDEF_SPE_By1, Hit__MaybeRaiseUser_DEF_By1, Hit__MaybeRaiseUser_SPATK_By1, Hit__MaybeRaiseUser_SPE_By1, Hit__MaybeToxic, HPDrain, HPDrain__RequireSleep, Judgment, LeechSeed, LightScreen, LockOn, LowerTarget_ATK_DEF_By1, LowerTarget_DEF_SPDEF_By1_Raise_ATK_SPATK_SPE_By2, LuckyChant, MagnetRise, Magnitude, Metronome, Minimize, MiracleEye, Moonlight, Nightmare, Nothing, OneHitKnockout, PainSplit, Paralyze, Payback, PayDay, Poison, PowerTrick, Protect, // TODO: If the user goes last, fail PsychUp, Psyshock, Psywave, Punishment, QuickGuard, RainDance, RaiseTarget_ATK_ACC_By1, RaiseTarget_ATK_DEF_By1, RaiseTarget_ATK_DEF_ACC_By1, RaiseTarget_ATK_SPATK_By1, RaiseTarget_ATK_SPE_By1, RaiseTarget_DEF_SPDEF_By1, RaiseTarget_SPATK_SPDEF_By1, RaiseTarget_SPATK_SPDEF_SPE_By1, RaiseTarget_SPE_By2_ATK_By1, Recoil, Recoil__10PercentBurn, Recoil__10PercentParalyze, Reflect, ReflectType, Refresh, Rest, RestoreTargetHP, Retaliate, Return, RolePlay, Roost, Safeguard, Sandstorm, SecretPower, SeismicToss, Selfdestruct, SetDamage, ShadowForce, SimpleBeam, Sketch, // TODO Sleep, SmellingSalt, Snore, Soak, Spikes, StealthRock, StoredPower, Struggle, Substitute, SuckerPunch, SunnyDay, SuperFang, Swagger, Tailwind, TechnoBlast, Teleport, // TODO: Trapping effects & SmokeBall ThunderWave, Toxic, ToxicSpikes, Transform, TrickRoom, Venoshock, WakeUpSlap, WeatherBall, Whirlwind, // TODO: Trapping effects WideGuard, WorrySeed, TODOMOVE // Moves that are not added yet } public enum PBEMove : ushort { None = 0, Absorb = 71, Acid = 51, AcidArmor = 151, AcidSpray = 491, Acrobatics = 512, Acupressure = 367, // TODO AerialAce = 332, Aeroblast = 177, AfterYou = 495, // TODO Agility = 97, AirCutter = 314, AirSlash = 403, AllySwitch = 502, // TODO Amnesia = 133, AncientPower = 246, AquaJet = 453, AquaRing = 392, // TODO AquaTail = 401, ArmThrust = 292, Aromatherapy = 312, // TODO Assist = 274, // TODO Assurance = 372, // TODO Astonish = 310, AttackOrder = 454, Attract = 213, AuraSphere = 396, AuroraBeam = 62, Autotomize = 475, // TODO Avalanche = 419, // TODO Barrage = 140, Barrier = 112, BatonPass = 226, // TODO BeatUp = 251, // TODO BellyDrum = 187, Bestow = 516, // TODO Bide = 117, // TODO Bind = 20, // TODO Bite = 44, BlastBurn = 307, // TODO BlazeKick = 299, Blizzard = 59, Block = 335, // TODO BlueFlare = 551, BodySlam = 34, BoltStrike = 550, BoneClub = 125, Bonemerang = 155, BoneRush = 198, Bounce = 340, BraveBird = 413, BrickBreak = 280, Brine = 362, Bubble = 145, BubbleBeam = 61, BugBite = 450, // TODO BugBuzz = 405, BulkUp = 339, Bulldoze = 523, BulletPunch = 418, BulletSeed = 331, CalmMind = 347, Camouflage = 293, Captivate = 445, Charge = 268, // TODO ChargeBeam = 451, Charm = 204, Chatter = 448, ChipAway = 498, CircleThrow = 509, // TODO Clamp = 128, // TODO ClearSmog = 499, // TODO CloseCombat = 370, Coil = 489, CometPunch = 4, ConfuseRay = 109, Confusion = 93, Constrict = 132, Conversion = 160, Conversion2 = 176, // TODO Copycat = 383, // TODO CosmicPower = 322, CottonGuard = 538, CottonSpore = 178, Counter = 68, // TODO Covet = 343, // TODO Crabhammer = 152, CrossChop = 238, CrossPoison = 440, Crunch = 242, CrushClaw = 306, CrushGrip = 462, Curse = 174, Cut = 15, DarkPulse = 399, DarkVoid = 464, DefendOrder = 455, DefenseCurl = 111, Defog = 432, // TODO DestinyBond = 194, // TODO Detect = 197, Dig = 91, Disable = 50, // TODO Discharge = 435, Dive = 291, DizzyPunch = 146, DoomDesire = 353, // TODO DoubleEdge = 38, DoubleHit = 458, DoubleKick = 24, DoubleSlap = 3, DoubleTeam = 104, DracoMeteor = 434, DragonBreath = 225, DragonClaw = 337, DragonDance = 349, DragonPulse = 406, DragonRage = 82, DragonRush = 407, DragonTail = 525, // TODO DrainPunch = 409, DreamEater = 138, DrillPeck = 65, DrillRun = 529, DualChop = 530, DynamicPunch = 223, EarthPower = 414, Earthquake = 89, EchoedVoice = 497, // TODO EggBomb = 121, ElectroBall = 486, // TODO Electroweb = 527, Embargo = 373, // TODO Ember = 52, Encore = 227, // TODO Endeavor = 283, Endure = 203, // TODO EnergyBall = 412, Entrainment = 494, Eruption = 284, Explosion = 153, Extrasensory = 326, ExtremeSpeed = 245, Facade = 263, FaintAttack = 185, FakeOut = 252, // TODO FakeTears = 313, FalseSwipe = 206, // TODO FeatherDance = 297, Feint = 364, FieryDance = 552, FinalGambit = 515, FireBlast = 126, FireFang = 424, FirePledge = 519, // TODO FirePunch = 7, FireSpin = 83, // TODO Fissure = 90, Flail = 175, FlameBurst = 481, // TODO FlameCharge = 488, Flamethrower = 53, FlameWheel = 172, FlareBlitz = 394, Flash = 148, FlashCannon = 430, Flatter = 260, Fling = 374, // TODO Fly = 19, FocusBlast = 411, FocusEnergy = 116, FocusPunch = 264, // TODO FollowMe = 266, // TODO ForcePalm = 395, Foresight = 193, FoulPlay = 492, FreezeShock = 553, // TODO FrenzyPlant = 338, // TODO FrostBreath = 524, Frustration = 218, FuryAttack = 31, FuryCutter = 210, // TODO FurySwipes = 154, FusionBolt = 559, // TODO FusionFlare = 558, // TODO FutureSight = 248, // TODO GastroAcid = 380, // TODO: Magic Bounce, Magic Coat GearGrind = 544, GigaDrain = 202, GigaImpact = 416, // TODO Glaciate = 549, Glare = 137, GrassKnot = 447, GrassPledge = 520, // TODO GrassWhistle = 320, Gravity = 356, // TODO Growl = 45, Growth = 74, Grudge = 288, // TODO GuardSplit = 470, // TODO GuardSwap = 385, // TODO Guillotine = 12, GunkShot = 441, Gust = 16, GyroBall = 360, // TODO Hail = 258, HammerArm = 359, Harden = 106, Haze = 114, Headbutt = 29, HeadCharge = 543, HeadSmash = 457, HealBell = 215, // TODO HealBlock = 377, // TODO HealingWish = 361, // TODO HealOrder = 456, HealPulse = 505, HeartStamp = 531, HeartSwap = 391, // TODO HeatCrash = 535, HeatWave = 257, HeavySlam = 484, HelpingHand = 270, Hex = 506, HiddenPower = 237, HiJumpKick = 136, // TODO HoneClaws = 468, HornAttack = 30, HornDrill = 32, HornLeech = 532, Howl = 336, Hurricane = 542, HydroCannon = 308, // TODO HydroPump = 56, HyperBeam = 63, // TODO HyperFang = 158, HyperVoice = 304, Hypnosis = 95, IceBall = 301, // TODO IceBeam = 58, IceBurn = 554, // TODO IceFang = 423, IcePunch = 8, IceShard = 420, IcicleCrash = 556, IcicleSpear = 333, IcyWind = 196, Imprison = 286, // TODO Incinerate = 510, // TODO Inferno = 517, Ingrain = 275, // TODO IronDefense = 334, IronHead = 442, IronTail = 231, Judgment = 449, JumpKick = 26, // TODO KarateChop = 2, Kinesis = 134, KnockOff = 282, // TODO LastResort = 387, // TODO LavaPlume = 436, LeafBlade = 348, LeafStorm = 437, LeafTornado = 536, LeechLife = 141, LeechSeed = 73, Leer = 43, Lick = 122, LightScreen = 113, LockOn = 199, LovelyKiss = 142, LowKick = 67, LowSweep = 490, LuckyChant = 381, LunarDance = 461, // TODO LusterPurge = 295, MachPunch = 183, MagicalLeaf = 345, MagicCoat = 277, // TODO MagicRoom = 478, // TODO MagmaStorm = 463, // TODO MagnetBomb = 443, MagnetRise = 393, Magnitude = 222, MeanLook = 212, // TODO Meditate = 96, MeFirst = 382, // TODO & TODO: Sucker Punch MegaDrain = 72, Megahorn = 224, MegaKick = 25, MegaPunch = 5, Memento = 262, // TODO MetalBurst = 368, // TODO MetalClaw = 232, MetalSound = 319, MeteorMash = 309, Metronome = 118, MilkDrink = 208, Mimic = 102, // TODO MindReader = 170, Minimize = 107, MiracleEye = 357, MirrorCoat = 243, // TODO MirrorMove = 119, // TODO MirrorShot = 429, Mist = 54, // TODO & TODO: Infiltrator MistBall = 296, Moonlight = 236, MorningSun = 234, MudBomb = 426, MuddyWater = 330, MudShot = 341, MudSlap = 189, MudSport = 300, // TODO NastyPlot = 417, NaturalGift = 363, // TODO NaturePower = 267, // TODO NeedleArm = 302, NightDaze = 539, Nightmare = 171, NightShade = 101, NightSlash = 400, Octazooka = 190, OdorSleuth = 316, OminousWind = 466, Outrage = 200, // TODO Overheat = 315, PainSplit = 220, Payback = 371, // TODO: If the target used an item instead of a move PayDay = 6, Peck = 64, PerishSong = 195, // TODO PetalDance = 80, // TODO PinMissile = 42, Pluck = 365, // TODO PoisonFang = 305, PoisonGas = 139, PoisonJab = 398, PoisonPowder = 77, PoisonSting = 40, PoisonTail = 342, Pound = 1, PowderSnow = 181, PowerGem = 408, PowerSplit = 471, // TODO PowerSwap = 384, // TODO PowerTrick = 379, PowerWhip = 438, Present = 217, // TODO Protect = 182, Psybeam = 60, Psychic = 94, PsychoBoost = 354, PsychoCut = 427, PsychoShift = 375, // TODO PsychUp = 244, Psyshock = 473, Psystrike = 540, Psywave = 149, Punishment = 386, Pursuit = 228, // TODO Quash = 511, // TODO QuickAttack = 98, QuickGuard = 501, QuiverDance = 483, Rage = 99, // TODO RagePowder = 476, // TODO RainDance = 240, RapidSpin = 229, // TODO RazorLeaf = 75, RazorShell = 534, RazorWind = 13, // TODO Recover = 105, Recycle = 278, // TODO Reflect = 115, ReflectType = 513, Refresh = 287, RelicSong = 547, // TODO Rest = 156, // TODO: Uproar, Leaf Guard Retaliate = 514, Return = 216, Revenge = 279, // TODO Reversal = 179, Roar = 46, // TODO: Suction Cups, Soundproof, Ingrain RoarOfTime = 459, // TODO RockBlast = 350, RockClimb = 431, RockPolish = 397, RockSlide = 157, RockSmash = 249, RockThrow = 88, RockTomb = 317, RockWrecker = 439, // TODO RolePlay = 272, RollingKick = 27, Rollout = 205, // TODO Roost = 355, Round = 496, // TODO SacredFire = 221, SacredSword = 533, Safeguard = 219, SandAttack = 28, Sandstorm = 201, SandTomb = 328, // TODO Scald = 503, ScaryFace = 184, Scratch = 10, Screech = 103, SearingShot = 545, SecretPower = 290, SecretSword = 548, SeedBomb = 402, SeedFlare = 465, SeismicToss = 69, Selfdestruct = 120, ShadowBall = 247, ShadowClaw = 421, ShadowForce = 467, ShadowPunch = 325, ShadowSneak = 425, Sharpen = 159, SheerCold = 329, ShellSmash = 504, ShiftGear = 508, ShockWave = 351, SignalBeam = 324, SilverWind = 318, SimpleBeam = 493, Sing = 47, Sketch = 166, // TODO SkillSwap = 285, // TODO SkullBash = 130, // TODO SkyAttack = 143, // TODO SkyDrop = 507, // TODO SkyUppercut = 327, SlackOff = 303, Slam = 21, Slash = 163, SleepPowder = 79, SleepTalk = 214, // TODO & TODO: Moves such as ice ball/outrage that last multiple turns are not locked, even upon waking up (is this the same for metronome etc?) Sludge = 124, SludgeBomb = 188, SludgeWave = 482, SmackDown = 479, // TODO SmellingSalt = 265, Smog = 123, SmokeScreen = 108, Snarl = 555, Snatch = 289, // TODO Snore = 173, Soak = 487, Softboiled = 135, SolarBeam = 76, // TODO SonicBoom = 49, SpacialRend = 460, Spark = 209, SpiderWeb = 169, // TODO SpikeCannon = 131, Spikes = 191, Spite = 180, // TODO SpitUp = 255, // TODO Splash = 150, Spore = 147, StealthRock = 446, Steamroller = 537, SteelWing = 211, Stockpile = 254, // TODO Stomp = 23, StoneEdge = 444, StoredPower = 500, StormThrow = 480, Strength = 70, StringShot = 81, Struggle = 165, StruggleBug = 522, StunSpore = 78, Submission = 66, Substitute = 164, SuckerPunch = 389, SunnyDay = 241, SuperFang = 162, Superpower = 276, Supersonic = 48, Surf = 57, Swagger = 207, Swallow = 256, // TODO SweetKiss = 186, SweetScent = 230, Swift = 129, Switcheroo = 415, // TODO SwordsDance = 14, Synchronoise = 485, // TODO Synthesis = 235, Tackle = 33, TailGlow = 294, TailSlap = 541, TailWhip = 39, Tailwind = 366, TakeDown = 36, Taunt = 269, // TODO TechnoBlast = 546, TeeterDance = 298, Telekinesis = 477, // TODO Teleport = 100, Thief = 168, // TODO Thrash = 37, // TODO Thunder = 87, Thunderbolt = 85, ThunderFang = 422, ThunderPunch = 9, ThunderShock = 84, ThunderWave = 86, Tickle = 321, Torment = 259, // TODO Toxic = 92, ToxicSpikes = 390, Transform = 144, TriAttack = 161, Trick = 271, // TODO TrickRoom = 433, TripleKick = 167, // TODO TrumpCard = 376, // TODO Twineedle = 41, Twister = 239, Uproar = 253, // TODO Uturn = 369, // TODO VacuumWave = 410, VCreate = 557, Venoshock = 474, ViceGrip = 11, VineWhip = 22, VitalThrow = 233, VoltSwitch = 521, // TODO VoltTackle = 344, WakeUpSlap = 358, Waterfall = 127, WaterGun = 55, WaterPledge = 518, // TODO WaterPulse = 352, WaterSport = 346, // TODO WaterSpout = 323, WeatherBall = 311, Whirlpool = 250, // TODO Whirlwind = 18, // TODO: Suction Cups, Ingrain WideGuard = 469, WildCharge = 528, WillOWisp = 261, WingAttack = 17, Wish = 273, // TODO Withdraw = 110, WonderRoom = 472, // TODO WoodHammer = 452, WorkUp = 526, WorrySeed = 388, Wrap = 35, // TODO WringOut = 378, XScissor = 404, Yawn = 281, // TODO ZapCannon = 192, ZenHeadbutt = 428, /// Invalid move. MAX = 560 } ================================================ FILE: PokemonBattleEngine/Data/DataProvider.cs ================================================ using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data.Legality; using Kermalis.PokemonBattleEngine.Utils; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Kermalis.PokemonBattleEngine.Data; public abstract class PBEDataProvider { public static PBEDataProvider Instance { get; private set; } = null!; public static PBELanguage GlobalLanguage { get; private set; } = default; public static PBERandom GlobalRandom { get; private set; } = null!; protected PBEDataProvider(PBELanguage language, PBERandom rand) { if (language >= PBELanguage.MAX) { throw new ArgumentOutOfRangeException(nameof(language)); } GlobalLanguage = language; GlobalRandom = rand; Instance = this; } #region Data public abstract bool IsBerry(PBEItem item); public abstract IPBEBerryData GetBerryData(PBEItem item, bool cache = true); public abstract bool TryGetBerryData(PBEItem item, [NotNullWhen(true)] out IPBEBerryData? bData, bool cache = true); public abstract IPBEItemData GetItemData(PBEItem item, bool cache = true); public abstract IPBEMoveData GetMoveData(PBEMove move, bool cache = true); public abstract bool HasEvolutions(PBESpecies species, PBEForm form, bool cache = true); public virtual bool HasEvolutions(IPBESpeciesForm pkmn, bool cache = true) { return HasEvolutions(pkmn.Species, pkmn.Form, cache); } public abstract IPBEPokemonData GetPokemonData(PBESpecies species, PBEForm form, bool cache = true); public abstract int GetSpeciesCaught(); /// Used by . public abstract IReadOnlyCollection GetLegalMoves(PBESpecies species, PBEForm form, byte level); public virtual IPBEPokemonData GetPokemonData(IPBESpeciesForm pkmn, bool cache = true) { return GetPokemonData(pkmn.Species, pkmn.Form, cache: cache); } #endregion #region EXP public abstract uint GetEXPRequired(PBEGrowthRate type, byte level); public abstract byte GetEXPLevel(PBEGrowthRate type, uint exp); /// This is the boost to the EXP rate. In generation 5, Pass Powers boost the EXP rate. public abstract float GetEXPModifier(PBEBattle battle); /// In generation 5, this is 1 for ot, 1.5 for domestic trade, and 1.7 for international trade. public abstract float GetEXPTradeModifier(PBEBattlePokemon pkmn); #endregion #region Catching public abstract bool IsDarkGrass(PBEBattle battle); public abstract bool IsDuskBallSetting(PBEBattle battle); public abstract bool IsFishing(PBEBattle battle); public abstract bool IsGuaranteedCapture(PBEBattle battle, PBESpecies species, PBEForm form); public abstract bool IsMoonBallFamily(PBESpecies species, PBEForm form); public abstract bool IsRepeatBallSpecies(PBESpecies species); public abstract bool IsSurfing(PBEBattle battle); public abstract bool IsUnderwater(PBEBattle battle); /// This is the boost to the catch rate. In generation 5, Capture Powers boost the catch rate. public abstract float GetCatchRateModifier(PBEBattle battle); public virtual bool IsGuaranteedCapture(PBEBattle battle, IPBESpeciesForm pkmn) { return IsGuaranteedCapture(battle, pkmn.Species, pkmn.Form); } public virtual bool IsMoonBallFamily(IPBESpeciesForm pkmn) { return IsMoonBallFamily(pkmn.Species, pkmn.Form); } #endregion #region LocalizedString public abstract bool GetAbilityByName(string abilityName, [NotNullWhen(true)] out PBEAbility? ability); public abstract IPBEReadOnlyLocalizedString GetAbilityName(PBEAbility ability); public abstract bool GetFormByName(PBESpecies species, string formName, [NotNullWhen(true)] out PBEForm? form); public abstract IPBEReadOnlyLocalizedString GetFormName(PBESpecies species, PBEForm form); public abstract bool GetGenderByName(string genderName, [NotNullWhen(true)] out PBEGender? gender); public abstract IPBEReadOnlyLocalizedString GetGenderName(PBEGender gender); public abstract bool GetItemByName(string itemName, [NotNullWhen(true)] out PBEItem? item); public abstract IPBEReadOnlyLocalizedString GetItemName(PBEItem item); public abstract bool GetMoveByName(string moveName, [NotNullWhen(true)] out PBEMove? move); public abstract IPBEReadOnlyLocalizedString GetMoveName(PBEMove move); public abstract bool GetNatureByName(string natureName, [NotNullWhen(true)] out PBENature? nature); public abstract IPBEReadOnlyLocalizedString GetNatureName(PBENature nature); public abstract bool GetSpeciesByName(string speciesName, [NotNullWhen(true)] out PBESpecies? species); public abstract IPBEReadOnlyLocalizedString GetSpeciesName(PBESpecies species); public abstract bool GetStatByName(string statName, [NotNullWhen(true)] out PBEStat? stat); public abstract IPBEReadOnlyLocalizedString GetStatName(PBEStat stat); public abstract bool GetTypeByName(string typeName, [NotNullWhen(true)] out PBEType? type); public abstract IPBEReadOnlyLocalizedString GetTypeName(PBEType type); public virtual IPBEReadOnlyLocalizedString GetFormName(IPBESpeciesForm pkmn) { return GetFormName(pkmn.Species, pkmn.Form); } #endregion } ================================================ FILE: PokemonBattleEngine/Data/Interfaces/ItemData.cs ================================================ namespace Kermalis.PokemonBattleEngine.Data; public interface IPBEItemData { /// The power has when the user is holding this item. 0 will cause the move to fail. byte FlingPower { get; } } public interface IPBEBerryData { byte Bitterness { get; } byte Dryness { get; } byte Sourness { get; } byte Spicyness { get; } byte Sweetness { get; } /// The power has when the user is holding this item. byte NaturalGiftPower { get; } /// The type becomes when the user is holding this item. PBEType NaturalGiftType { get; } } ================================================ FILE: PokemonBattleEngine/Data/Interfaces/LocalizedString.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Globalization; namespace Kermalis.PokemonBattleEngine.Data; public interface IPBEReadOnlyLocalizedString { string English { get; } string French { get; } string German { get; } string Italian { get; } string Japanese_Kana { get; } string Japanese_Kanji { get; } string Korean { get; } string Spanish { get; } } public interface IPBELocalizedString : IPBEReadOnlyLocalizedString { new string English { get; set; } new string French { get; set; } new string German { get; set; } new string Italian { get; set; } new string Japanese_Kana { get; set; } new string Japanese_Kanji { get; set; } new string Korean { get; set; } new string Spanish { get; set; } } public static class PBELanguageExtensions { public static bool ToPBELanguage(this CultureInfo cultureInfo, [NotNullWhen(true)] out PBELanguage? lang) { string id = cultureInfo.TwoLetterISOLanguageName; switch (id) { case "en": lang = PBELanguage.English; return true; case "fr": lang = PBELanguage.French; return true; case "de": lang = PBELanguage.German; return true; case "it": lang = PBELanguage.Italian; return true; case "ja": lang = PBELanguage.Japanese_Kana; return true; case "ko": lang = PBELanguage.Korean; return true; case "es": lang = PBELanguage.Spanish; return true; } lang = default; return false; } public static CultureInfo ToCultureInfo(this PBELanguage language) { switch (language) { case PBELanguage.English: return CultureInfo.GetCultureInfo("en-US"); case PBELanguage.French: return CultureInfo.GetCultureInfo("fr-FR"); case PBELanguage.German: return CultureInfo.GetCultureInfo("de-DE"); case PBELanguage.Italian: return CultureInfo.GetCultureInfo("it-IT"); case PBELanguage.Japanese_Kana: case PBELanguage.Japanese_Kanji: return CultureInfo.GetCultureInfo("ja-JP"); case PBELanguage.Korean: return CultureInfo.GetCultureInfo("ko-KR"); case PBELanguage.Spanish: return CultureInfo.GetCultureInfo("es-ES"); default: throw new ArgumentOutOfRangeException(nameof(language)); } } public static string FromGlobalLanguage(this IPBEReadOnlyLocalizedString str) { return str.Get(PBEDataProvider.GlobalLanguage); } public static string Get(this IPBEReadOnlyLocalizedString str, PBELanguage lang) { switch (lang) { case PBELanguage.English: return str.English; case PBELanguage.French: return str.French; case PBELanguage.German: return str.German; case PBELanguage.Italian: return str.Italian; case PBELanguage.Japanese_Kana: return str.Japanese_Kana; case PBELanguage.Japanese_Kanji: return str.Japanese_Kanji; case PBELanguage.Korean: return str.Korean; case PBELanguage.Spanish: return str.Spanish; default: throw new ArgumentOutOfRangeException(nameof(lang)); } } } ================================================ FILE: PokemonBattleEngine/Data/Interfaces/MoveData.cs ================================================ using Kermalis.PokemonBattleEngine.Data.Utils; namespace Kermalis.PokemonBattleEngine.Data; public interface IPBEMoveData { PBEType Type { get; } PBEMoveCategory Category { get; } sbyte Priority { get; } /// 0 PPTier will become 1 PP (unaffected by pp ups) byte PPTier { get; } /// 0 power will show up as -- byte Power { get; } /// 0 accuracy will show up as -- byte Accuracy { get; } PBEMoveEffect Effect { get; } int EffectParam { get; } PBEMoveTarget Targets { get; } PBEMoveFlag Flags { get; } } public static class PBEMoveDataExtensions { public static bool HasSecondaryEffects(this IPBEMoveData mData, PBESettings settings) { return PBEDataUtils.HasSecondaryEffects(mData.Effect, settings); } public static bool IsHPDrainMove(this IPBEMoveData mData) { return PBEDataUtils.IsHPDrainMove(mData.Effect); } public static bool IsHPRestoreMove(this IPBEMoveData mData) { return PBEDataUtils.IsHPRestoreMove(mData.Effect); } public static bool IsMultiHitMove(this IPBEMoveData mData) { return PBEDataUtils.IsMultiHitMove(mData.Effect); } public static bool IsRecoilMove(this IPBEMoveData mData) { return PBEDataUtils.IsRecoilMove(mData.Effect); } public static bool IsSetDamageMove(this IPBEMoveData mData) { return PBEDataUtils.IsSetDamageMove(mData.Effect); } public static bool IsSpreadMove(this IPBEMoveData mData) { return PBEDataUtils.IsSpreadMove(mData.Targets); } public static bool IsWeatherMove(this IPBEMoveData mData) { return PBEDataUtils.IsWeatherMove(mData.Effect); } /// Temporary check to see if a move is usable, can be removed once all moves are added public static bool IsMoveUsable(this IPBEMoveData mData) { return PBEDataUtils.IsMoveUsable(mData.Effect); } } ================================================ FILE: PokemonBattleEngine/Data/Interfaces/MovesetInterfaces.cs ================================================ using Kermalis.EndianBinaryIO; using System.Collections.Generic; using System.Text.Json; namespace Kermalis.PokemonBattleEngine.Data; public interface IPBEMovesetSlot { PBEMove Move { get; } byte PPUps { get; } } public interface IPBEPartyMovesetSlot : IPBEMovesetSlot { int PP { get; } } public interface IPBEMoveset : IReadOnlyList where T : IPBEMovesetSlot { // } public interface IPBEMoveset : IPBEMoveset { // } public interface IPBEPartyMoveset : IReadOnlyList where T : IPBEPartyMovesetSlot { // } public interface IPBEPartyMoveset : IPBEPartyMoveset { // } public static class PBEMovesetInterfaceExtensions { public static int CountMoves(this IPBEMoveset moves) { int num = 0; for (int i = 0; i < moves.Count; i++) { if (moves[i].Move != PBEMove.None) { num++; } } return num; } internal static void ToBytes(this IPBEMoveset moveset, EndianBinaryWriter w) { byte count = (byte)moveset.Count; w.WriteByte(count); for (int i = 0; i < count; i++) { IPBEMovesetSlot slot = moveset[i]; w.WriteEnum(slot.Move); w.WriteByte(slot.PPUps); } } internal static void ToBytes(this IPBEPartyMoveset moveset, EndianBinaryWriter w) { byte count = (byte)moveset.Count; w.WriteByte(count); for (int i = 0; i < count; i++) { IPBEPartyMovesetSlot slot = moveset[i]; w.WriteEnum(slot.Move); w.WriteInt32(slot.PP); w.WriteByte(slot.PPUps); } } internal static void ToJson(this IPBEMoveset moveset, Utf8JsonWriter w) { w.WriteStartArray(); for (int i = 0; i < moveset.Count; i++) { IPBEMovesetSlot slot = moveset[i]; w.WriteStartObject(); w.WriteString(nameof(IPBEMovesetSlot.Move), slot.Move.ToString()); w.WriteNumber(nameof(IPBEMovesetSlot.PPUps), slot.PPUps); w.WriteEndObject(); } w.WriteEndArray(); } internal static void ToJson(this IPBEPartyMoveset moveset, Utf8JsonWriter w) { w.WriteStartArray(); for (int i = 0; i < moveset.Count; i++) { IPBEPartyMovesetSlot slot = moveset[i]; w.WriteStartObject(); w.WriteString(nameof(IPBEPartyMovesetSlot.Move), slot.Move.ToString()); w.WriteNumber(nameof(IPBEPartyMovesetSlot.PP), slot.PP); w.WriteNumber(nameof(IPBEPartyMovesetSlot.PPUps), slot.PPUps); w.WriteEndObject(); } w.WriteEndArray(); } } ================================================ FILE: PokemonBattleEngine/Data/Interfaces/PokemonData.cs ================================================ using System; using System.Collections.Generic; using System.Linq; namespace Kermalis.PokemonBattleEngine.Data; public interface IPBEPokemonData : IPBEPokemonTypes, IPBESpeciesForm { IPBEReadOnlyStatCollection BaseStats { get; } PBEGenderRatio GenderRatio { get; } PBEGrowthRate GrowthRate { get; } ushort BaseEXPYield { get; } byte CatchRate { get; } byte FleeRate { get; } /// Weight in Kilograms float Weight { get; } IReadOnlyList Abilities { get; } } public static class PBEPokemonDataExtensions { 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); } } ================================================ FILE: PokemonBattleEngine/Data/Interfaces/PokemonInterfaces.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data.Utils; using System; using System.Collections.Generic; using System.Text.Json; namespace Kermalis.PokemonBattleEngine.Data; // Not separating this into IPBEWildPokemon for these reasons: // 1: A lot of work to do that // 2: If someone wants to do pal park or catch released Pokémon etc, they'd need all these things // 3: If they want just some things (like effort values pre-seeded) they'd also need this public interface IPBEPokemon : IPBESpeciesForm { /// This marks the Pokémon to be ignored by the battle engine. The Pokémon will be treated like an egg or fainted Pokémon. /// Therefore, it won't be sent out, copied with , or count as a battler if the rest of the team faints. bool PBEIgnore { get; } PBEGender Gender { get; } string Nickname { get; } bool Shiny { get; } byte Level { get; } uint EXP { get; } bool Pokerus { get; } PBEItem Item { get; } byte Friendship { get; } PBEAbility Ability { get; } PBENature Nature { get; } PBEItem CaughtBall { get; } IPBEStatCollection EffortValues { get; } IPBEReadOnlyStatCollection IndividualValues { get; } IPBEMoveset Moveset { get; } } public interface IPBEPartyPokemon : IPBEPokemon { ushort HP { get; } PBEStatus1 Status1 { get; } byte SleepTurns { get; } new IPBEPartyMoveset Moveset { get; } } public interface IPBEPokemonCollection : IReadOnlyList where T : IPBEPokemon { } public interface IPBEPokemonCollection : IReadOnlyList { } public interface IPBEPartyPokemonCollection : IReadOnlyList where T : IPBEPartyPokemon { } public interface IPBEPartyPokemonCollection : IReadOnlyList { } public interface IPBEPokemonTypes { /// The Pokémon's first type. PBEType Type1 { get; } /// The Pokémon's second type. PBEType Type2 { get; } } public interface IPBEPokemonKnownTypes { /// The first type everyone believes the Pokémon has. PBEType KnownType1 { get; } /// The second type everyone believes the Pokémon has. PBEType KnownType2 { get; } } public interface IPBESpeciesForm { PBESpecies Species { get; } PBEForm Form { get; } } public static class PBEPokemonInterfaceExtensions { public static bool HasType(this IPBEPokemonTypes pkmn, PBEType type) { if (type >= PBEType.MAX) { throw new ArgumentOutOfRangeException(nameof(type)); } return pkmn.Type1 == type || pkmn.Type2 == type; } public static bool HasType_Known(this IPBEPokemonKnownTypes pkmn, PBEType type) { if (type >= PBEType.MAX) { throw new ArgumentOutOfRangeException(nameof(type)); } return pkmn.KnownType1 == type || pkmn.KnownType2 == type; } public static bool HasType(this T pkmn, PBEType type, bool useKnownInfo) where T : IPBEPokemonKnownTypes, IPBEPokemonTypes { return useKnownInfo ? HasType_Known(pkmn, type) : HasType(pkmn, type); } public static bool ReceivesSTAB(this IPBEPokemonTypes pkmn, PBEType type) { return type != PBEType.None && HasType(pkmn, type); } public static bool ReceivesSTAB_Known(this IPBEPokemonKnownTypes pkmn, PBEType type) { return type != PBEType.None && HasType_Known(pkmn, type); } public static bool ReceivesSTAB(this T pkmn, PBEType type, bool useKnownInfo) where T : IPBEPokemonKnownTypes, IPBEPokemonTypes { return useKnownInfo ? ReceivesSTAB_Known(pkmn, type) : ReceivesSTAB(pkmn, type); } internal static void ToBytes(this IPBEPokemon pkmn, EndianBinaryWriter w) { w.WriteEnum(pkmn.Species); w.WriteEnum(pkmn.Form); w.WriteChars_NullTerminated(pkmn.Nickname); w.WriteByte(pkmn.Level); w.WriteUInt32(pkmn.EXP); w.WriteByte(pkmn.Friendship); w.WriteBoolean(pkmn.Shiny); w.WriteBoolean(pkmn.Pokerus); w.WriteEnum(pkmn.Ability); w.WriteEnum(pkmn.Nature); w.WriteEnum(pkmn.CaughtBall); w.WriteEnum(pkmn.Gender); w.WriteEnum(pkmn.Item); pkmn.EffortValues.ToBytes(w); pkmn.IndividualValues.ToBytes(w); pkmn.Moveset.ToBytes(w); } internal static void ToJson(this IPBEPokemon pkmn, Utf8JsonWriter w) { w.WriteStartObject(); PBESpecies species = pkmn.Species; w.WriteString(nameof(IPBEPokemon.Species), species.ToString()); if (PBEDataUtils.HasForms(species, true)) { w.WriteString(nameof(IPBEPokemon.Form), PBEDataUtils.GetNameOfForm(species, pkmn.Form)); } w.WriteString(nameof(IPBEPokemon.Nickname), pkmn.Nickname); w.WriteNumber(nameof(IPBEPokemon.Level), pkmn.Level); w.WriteNumber(nameof(IPBEPokemon.EXP), pkmn.EXP); w.WriteNumber(nameof(IPBEPokemon.Friendship), pkmn.Friendship); w.WriteBoolean(nameof(IPBEPokemon.Shiny), pkmn.Shiny); w.WriteBoolean(nameof(IPBEPokemon.Pokerus), pkmn.Pokerus); w.WriteString(nameof(IPBEPokemon.Ability), pkmn.Ability.ToString()); w.WriteString(nameof(IPBEPokemon.Nature), pkmn.Nature.ToString()); w.WriteString(nameof(IPBEPokemon.CaughtBall), pkmn.CaughtBall.ToString()); w.WriteString(nameof(IPBEPokemon.Gender), pkmn.Gender.ToString()); w.WriteString(nameof(IPBEPokemon.Item), pkmn.Item.ToString()); w.WritePropertyName(nameof(IPBEPokemon.EffortValues)); pkmn.EffortValues.ToJson(w); w.WritePropertyName(nameof(IPBEPokemon.IndividualValues)); pkmn.IndividualValues.ToJson(w); w.WritePropertyName(nameof(IPBEPokemon.Moveset)); pkmn.Moveset.ToJson(w); w.WriteEndObject(); } internal static void ToBytes(this IPBEPokemonCollection party, EndianBinaryWriter w) { byte count = (byte)party.Count; w.WriteByte(count); for (int i = 0; i < count; i++) { party[i].ToBytes(w); } } } ================================================ FILE: PokemonBattleEngine/Data/Interfaces/StatInterfaces.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Data.Utils; using System; using System.Text.Json; namespace Kermalis.PokemonBattleEngine.Data; public interface IPBEReadOnlyStatCollection { byte HP { get; } byte Attack { get; } byte Defense { get; } byte SpAttack { get; } byte SpDefense { get; } byte Speed { get; } } public interface IPBEStatCollection : IPBEReadOnlyStatCollection { new byte HP { get; set; } new byte Attack { get; set; } new byte Defense { get; set; } new byte SpAttack { get; set; } new byte SpDefense { get; set; } new byte Speed { get; set; } } public static class PBEStatInterfaceExtensions { public static byte GetStat(this IPBEReadOnlyStatCollection stats, PBEStat stat) { switch (stat) { case PBEStat.HP: return stats.HP; case PBEStat.Attack: return stats.Attack; case PBEStat.Defense: return stats.Defense; case PBEStat.SpAttack: return stats.SpAttack; case PBEStat.SpDefense: return stats.SpDefense; case PBEStat.Speed: return stats.Speed; default: throw new ArgumentOutOfRangeException(nameof(stat)); } } public static void SetStat(this IPBEStatCollection stats, PBEStat stat, byte value) { switch (stat) { case PBEStat.HP: stats.HP = value; break; case PBEStat.Attack: stats.Attack = value; break; case PBEStat.Defense: stats.Defense = value; break; case PBEStat.SpAttack: stats.SpAttack = value; break; case PBEStat.SpDefense: stats.SpDefense = value; break; case PBEStat.Speed: stats.Speed = value; break; default: throw new ArgumentOutOfRangeException(nameof(stat)); } } public static byte GetHiddenPowerBasePower(this IPBEReadOnlyStatCollection stats, PBESettings settings) { return PBEDataUtils.GetHiddenPowerBasePower(stats.HP, stats.Attack, stats.Defense, stats.SpAttack, stats.SpDefense, stats.Speed, settings); } public static PBEType GetHiddenPowerType(this IPBEReadOnlyStatCollection stats) { return PBEDataUtils.GetHiddenPowerType(stats.HP, stats.Attack, stats.Defense, stats.SpAttack, stats.SpDefense, stats.Speed); } internal static void ToBytes(this IPBEReadOnlyStatCollection stats, EndianBinaryWriter w) { w.WriteByte(stats.HP); w.WriteByte(stats.Attack); w.WriteByte(stats.Defense); w.WriteByte(stats.SpAttack); w.WriteByte(stats.SpDefense); w.WriteByte(stats.Speed); } internal static void ToJson(this IPBEReadOnlyStatCollection stats, Utf8JsonWriter w) { w.WriteStartObject(); w.WriteNumber(nameof(IPBEReadOnlyStatCollection.HP), stats.HP); w.WriteNumber(nameof(IPBEReadOnlyStatCollection.Attack), stats.Attack); w.WriteNumber(nameof(IPBEReadOnlyStatCollection.Defense), stats.Defense); w.WriteNumber(nameof(IPBEReadOnlyStatCollection.SpAttack), stats.SpAttack); w.WriteNumber(nameof(IPBEReadOnlyStatCollection.SpDefense), stats.SpDefense); w.WriteNumber(nameof(IPBEReadOnlyStatCollection.Speed), stats.Speed); w.WriteEndObject(); } } ================================================ FILE: PokemonBattleEngine/Data/Legality/LegalEffortValues.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Utils; using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; using System.Text.Json.Nodes; namespace Kermalis.PokemonBattleEngine.Data.Legality; public sealed class PBELegalEffortValues : IPBEStatCollection, IEnumerable, INotifyPropertyChanged { public sealed class PBELegalEffortValue : INotifyPropertyChanged { private void OnPropertyChanged(string property) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); } public event PropertyChangedEventHandler? PropertyChanged; private readonly PBELegalEffortValues _parent; public PBEStat Stat { get; } private byte _value; public byte Value { get => _value; set { if (_value != value) { ushort oldTotal = _parent.StatTotal; int newTotal = oldTotal - _value + value; if (newTotal > _parent.Settings.MaxTotalEVs) { byte newValue = (byte)(value - (newTotal - _parent.Settings.MaxTotalEVs)); if (_value != newValue) { Update(newValue); } } else { Update(value); } } } } internal PBELegalEffortValue(PBELegalEffortValues parent, PBEStat stat, byte value) { _parent = parent; Stat = stat; _value = value; } private void Update(byte newValue) { _value = newValue; OnPropertyChanged(nameof(Value)); _parent.OnPropertyChanged(nameof(_parent.StatTotal)); } } private void OnPropertyChanged(string property) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); } public event PropertyChangedEventHandler? PropertyChanged; public PBESettings Settings { get; } private readonly PBELegalEffortValue[] _evs; public PBELegalEffortValue this[PBEStat stat] { get { int statIndex = (int)stat; if (statIndex >= 6) { throw new ArgumentOutOfRangeException(nameof(stat)); } return _evs[statIndex]; } } public ushort StatTotal { get { ushort total = 0; for (int i = 0; i < 6; i++) { total += _evs[i].Value; } return total; } } public byte HP { get => _evs[0].Value; set => _evs[0].Value = value; } public byte Attack { get => _evs[1].Value; set => _evs[1].Value = value; } public byte Defense { get => _evs[2].Value; set => _evs[2].Value = value; } public byte SpAttack { get => _evs[3].Value; set => _evs[3].Value = value; } public byte SpDefense { get => _evs[4].Value; set => _evs[4].Value = value; } public byte Speed { get => _evs[5].Value; set => _evs[5].Value = value; } internal PBELegalEffortValues(PBESettings settings, EndianBinaryReader r) { byte hp = r.ReadByte(); byte attack = r.ReadByte(); byte defense = r.ReadByte(); byte spAttack = r.ReadByte(); byte spDefense = r.ReadByte(); byte speed = r.ReadByte(); if (hp + attack + defense + spAttack + spDefense + speed > settings.MaxTotalEVs) { throw new InvalidDataException(); } Settings = settings; _evs = CreateEVs(hp, attack, defense, spAttack, spDefense, speed); } internal PBELegalEffortValues(PBESettings settings, JsonObject jObj) { byte hp = jObj.GetSafe(nameof(PBEStat.HP)).GetValue(); byte attack = jObj.GetSafe(nameof(PBEStat.Attack)).GetValue(); byte defense = jObj.GetSafe(nameof(PBEStat.Defense)).GetValue(); byte spAttack = jObj.GetSafe(nameof(PBEStat.SpAttack)).GetValue(); byte spDefense = jObj.GetSafe(nameof(PBEStat.SpDefense)).GetValue(); byte speed = jObj.GetSafe(nameof(PBEStat.Speed)).GetValue(); if (hp + attack + defense + spAttack + spDefense + speed > settings.MaxTotalEVs) { throw new InvalidDataException($"Effort values total must not exceed \"{nameof(settings.MaxTotalEVs)}\" ({settings.MaxTotalEVs})"); } Settings = settings; _evs = CreateEVs(hp, attack, defense, spAttack, spDefense, speed); } internal PBELegalEffortValues(PBELegalEffortValues other) { Settings = other.Settings; _evs = CreateEVs(other.HP, other.Attack, other.Defense, other.SpAttack, other.SpDefense, other.Speed); } public PBELegalEffortValues(PBESettings settings, bool randomize) { settings.ShouldBeReadOnly(nameof(settings)); Settings = settings; _evs = CreateEVs(0, 0, 0, 0, 0, 0); if (randomize) { Randomize(); } } private PBELegalEffortValue[] CreateEVs(byte hp, byte attack, byte defense, byte spAttack, byte spDefense, byte speed) { return new PBELegalEffortValue[6] { new PBELegalEffortValue(this, PBEStat.HP, hp), new PBELegalEffortValue(this, PBEStat.Attack, attack), new PBELegalEffortValue(this, PBEStat.Defense, defense), new PBELegalEffortValue(this, PBEStat.SpAttack, spAttack), new PBELegalEffortValue(this, PBEStat.SpDefense, spDefense), new PBELegalEffortValue(this, PBEStat.Speed, speed), }; } public void Clear() { for (int i = 0; i < 6; i++) { _evs[i].Value = 0; } } public void Equalize() { Clear(); for (int i = 0; i < 6; i++) { _evs[i].Value = (byte)(Settings.MaxTotalEVs / 6); } } public void Randomize() { if (Settings.MaxTotalEVs == 0) { return; } byte[] vals = new byte[6]; int[] a = Enumerable.Repeat(0, 6 - 1) .Select(x => PBEDataProvider.GlobalRandom.RandomInt(1, Settings.MaxTotalEVs - 1)) .Concat(new int[] { Settings.MaxTotalEVs }) .OrderBy(x => x) .ToArray(); ushort total = 0; for (int i = 0; i < 6; i++) { byte b = (byte)Math.Min(byte.MaxValue, a[i] - total); vals[i] = b; total += b; } // This "while" will fix the issue where the speed stat was supposed to be above 255 var notMax = new List(5); while (total != Settings.MaxTotalEVs) { notMax.Clear(); for (int i = 0; i < 6; i++) { if (vals[i] != byte.MaxValue) { notMax.Add(i); } } int index = PBEDataProvider.GlobalRandom.RandomElement(notMax); byte old = vals[index]; byte b = (byte)Math.Min(byte.MaxValue, old + (Settings.MaxTotalEVs - total)); vals[index] = b; total += (byte)(b - old); } for (int i = 0; i < 6; i++) { _evs[i].Value = vals[i]; } } public IEnumerator GetEnumerator() { for (int i = 0; i < 6; i++) { yield return _evs[i]; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } ================================================ FILE: PokemonBattleEngine/Data/Legality/LegalIndividualValues.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Data.Utils; using Kermalis.PokemonBattleEngine.Utils; using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Text.Json.Nodes; namespace Kermalis.PokemonBattleEngine.Data.Legality; public sealed class PBELegalIndividualValues : IPBEStatCollection, IEnumerable, INotifyPropertyChanged { public sealed class PBELegalIndividualValue : INotifyPropertyChanged { private void OnPropertyChanged(string property) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); } public event PropertyChangedEventHandler? PropertyChanged; private readonly PBELegalIndividualValues _parent; public PBEStat Stat { get; } private byte _value; public byte Value { get => _value; set { if (_value != value) { if (value > _parent.Settings.MaxIVs) { throw new ArgumentOutOfRangeException(nameof(value)); } _value = value; OnPropertyChanged(nameof(Value)); _parent.UpdateHiddenPower(); } } } internal PBELegalIndividualValue(PBELegalIndividualValues parent, PBEStat stat, byte value) { _parent = parent; Stat = stat; _value = value; } } private void OnPropertyChanged(string property) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); } public event PropertyChangedEventHandler? PropertyChanged; public PBESettings Settings { get; } private readonly PBELegalIndividualValue[] _ivs; public PBELegalIndividualValue this[PBEStat stat] { get { int statIndex = (int)stat; if (statIndex >= 6) { throw new ArgumentOutOfRangeException(nameof(stat)); } return _ivs[statIndex]; } } private PBEType _hiddenPowerType; public PBEType HiddenPowerType { get => _hiddenPowerType; private set { if (_hiddenPowerType != value) { _hiddenPowerType = value; OnPropertyChanged(nameof(HiddenPowerType)); } } } private byte _hiddenPowerBasePower; public byte HiddenPowerBasePower { get => _hiddenPowerBasePower; private set { if (_hiddenPowerBasePower != value) { _hiddenPowerBasePower = value; OnPropertyChanged(nameof(HiddenPowerBasePower)); } } } public byte HP { get => _ivs[0].Value; set => _ivs[0].Value = value; } public byte Attack { get => _ivs[1].Value; set => _ivs[1].Value = value; } public byte Defense { get => _ivs[2].Value; set => _ivs[2].Value = value; } public byte SpAttack { get => _ivs[3].Value; set => _ivs[3].Value = value; } public byte SpDefense { get => _ivs[4].Value; set => _ivs[4].Value = value; } public byte Speed { get => _ivs[5].Value; set => _ivs[5].Value = value; } internal PBELegalIndividualValues(PBESettings settings, EndianBinaryReader r) { void Validate(byte val) { if (val > settings.MaxIVs) { throw new InvalidDataException(); } } byte hp = r.ReadByte(); Validate(hp); byte attack = r.ReadByte(); Validate(attack); byte defense = r.ReadByte(); Validate(defense); byte spAttack = r.ReadByte(); Validate(spAttack); byte spDefense = r.ReadByte(); Validate(spDefense); byte speed = r.ReadByte(); Validate(speed); Settings = settings; _ivs = CreateIVs(hp, attack, defense, spAttack, spDefense, speed); UpdateHiddenPower(); } internal PBELegalIndividualValues(PBESettings settings, JsonObject jObj) { void Validate(byte val, string name) { if (val > settings.MaxIVs) { throw new InvalidDataException($"\"{name}\" individual value must not exceed \"{nameof(settings.MaxIVs)}\" ({settings.MaxIVs})"); } } byte hp = jObj.GetSafe(nameof(PBEStat.HP)).GetValue(); Validate(hp, nameof(PBEStat.HP)); byte attack = jObj.GetSafe(nameof(PBEStat.Attack)).GetValue(); Validate(attack, nameof(PBEStat.Attack)); byte defense = jObj.GetSafe(nameof(PBEStat.Defense)).GetValue(); Validate(defense, nameof(PBEStat.Defense)); byte spAttack = jObj.GetSafe(nameof(PBEStat.SpAttack)).GetValue(); Validate(spAttack, nameof(PBEStat.SpAttack)); byte spDefense = jObj.GetSafe(nameof(PBEStat.SpDefense)).GetValue(); Validate(spDefense, nameof(PBEStat.SpDefense)); byte speed = jObj.GetSafe(nameof(PBEStat.Speed)).GetValue(); Validate(speed, nameof(PBEStat.Speed)); Settings = settings; _ivs = CreateIVs(hp, attack, defense, spAttack, spDefense, speed); UpdateHiddenPower(); } internal PBELegalIndividualValues(PBELegalIndividualValues other) { Settings = other.Settings; _ivs = CreateIVs(other.HP, other.Attack, other.Defense, other.SpAttack, other.SpDefense, other.Speed); UpdateHiddenPower(); } public PBELegalIndividualValues(PBESettings settings, bool randomize) { settings.ShouldBeReadOnly(nameof(settings)); Settings = settings; _ivs = CreateIVs(0, 0, 0, 0, 0, 0); UpdateHiddenPower(); if (randomize) { Randomize(); } } private PBELegalIndividualValue[] CreateIVs(byte hp, byte attack, byte defense, byte spAttack, byte spDefense, byte speed) { return new PBELegalIndividualValue[6] { new PBELegalIndividualValue(this, PBEStat.HP, hp), new PBELegalIndividualValue(this, PBEStat.Attack, attack), new PBELegalIndividualValue(this, PBEStat.Defense, defense), new PBELegalIndividualValue(this, PBEStat.SpAttack, spAttack), new PBELegalIndividualValue(this, PBEStat.SpDefense, spDefense), new PBELegalIndividualValue(this, PBEStat.Speed, speed) }; } private void UpdateHiddenPower() { HiddenPowerType = PBEDataUtils.GetHiddenPowerType(HP, Attack, Defense, SpAttack, SpDefense, Speed); HiddenPowerBasePower = PBEDataUtils.GetHiddenPowerBasePower(HP, Attack, Defense, SpAttack, SpDefense, Speed, Settings); } public void Clear() { for (int i = 0; i < 6; i++) { _ivs[i].Value = 0; } } public void Maximize() { for (int i = 0; i < 6; i++) { _ivs[i].Value = Settings.MaxIVs; } } public void Randomize() { for (int i = 0; i < 6; i++) { _ivs[i].Value = (byte)PBEDataProvider.GlobalRandom.RandomInt(0, Settings.MaxIVs); } } public IEnumerator GetEnumerator() { for (int i = 0; i < 6; i++) { yield return _ivs[i]; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } ================================================ FILE: PokemonBattleEngine/Data/Legality/LegalMoveset.cs ================================================ using Kermalis.PokemonBattleEngine.Data.Utils; using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; namespace Kermalis.PokemonBattleEngine.Data.Legality; public sealed class PBELegalMoveset : IPBEMoveset, IPBEMoveset, INotifyPropertyChanged { public sealed class PBELegalMovesetSlot : IPBEMovesetSlot, INotifyPropertyChanged { private void OnPropertyChanged(string property) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); } public event PropertyChangedEventHandler? PropertyChanged; private readonly PBELegalMoveset _parent; private readonly int _index; public PBEAlphabeticalList Allowed { get; } private PBEMove _move; public PBEMove Move { get => _move; set { PBEMove old = _move; if (old != value) { if (value >= PBEMove.MAX || (value != PBEMove.None && !PBEDataUtils.IsMoveUsable(value))) { throw new ArgumentOutOfRangeException(nameof(value)); } if (!_isMoveEditable) { throw new InvalidOperationException($"Slot {_index}'s move cannot be changed because there is no move in slot {_index - 1}."); } if (!Allowed.Contains(value)) { throw new ArgumentOutOfRangeException(nameof(value), $"Slot {_index} does not allow {value}."); } if (value != PBEMove.None) { // If "move" is in another slot, place "slotIndex"'s old move at the other slot for (int i = 0; i < _parent.Settings.NumMoves; i++) { if (i != _index) { PBELegalMovesetSlot iSlot = _parent[i]; if (iSlot.Move == value) { // If slot 0 is Snore and slot 3 is None but is trying to become Snore, do nothing because the first Snore is in an earlier slot and swapping None to an earlier slot makes no sense if (old == PBEMove.None && i < _index) { goto finish; } else { UpdateMove(value); iSlot.UpdateMove(old); goto editables; } } } } } else { // If "move" is None and a slot after "slotIndex" is not None, then place None at the other slot instead and place the other slot's move at "slotIndex" for (int i = _parent.Settings.NumMoves - 1; i > _index; i--) { PBELegalMovesetSlot iSlot = _parent[i]; if (iSlot.Move != PBEMove.None) { UpdateMove(iSlot.Move); iSlot.UpdateMove(PBEMove.None); goto editables; } } } // This gets reached if: // "move" is not None and there is no other slot with "move" // "move" is None and there is no slot after "slotIndex" with a move UpdateMove(value); editables: _parent.SetEditables(); finish: ; } } } private bool _isMoveEditable; public bool IsMoveEditable { get => _isMoveEditable; internal set { if (_isMoveEditable != value) { _isMoveEditable = value; OnPropertyChanged(nameof(IsMoveEditable)); } } } private byte _ppUps; public byte PPUps { get => _ppUps; set { if (_ppUps != value) { if (value > _parent.Settings.MaxPPUps) { throw new ArgumentOutOfRangeException(nameof(value), $"\"{nameof(value)}\" cannot exceed \"{nameof(_parent.Settings.MaxPPUps)}\" ({_parent.Settings.MaxPPUps})."); } if (!IsPPUpsEditable) { throw new InvalidOperationException($"Slot {_index}'s PP-Ups cannot be changed because it has no move."); } UpdatePPUps(value); } } } private bool _isPPUpsEditable; public bool IsPPUpsEditable { get => _isPPUpsEditable; private set { if (_isPPUpsEditable != value) { _isPPUpsEditable = value; OnPropertyChanged(nameof(IsPPUpsEditable)); } } } private static readonly PBEMove[] _none = new PBEMove[1] { PBEMove.None }; internal PBELegalMovesetSlot(PBELegalMoveset parent, int index) { _parent = parent; _index = index; _isMoveEditable = index < 2; Allowed = new PBEAlphabeticalList(_none); } private void UpdateMove(PBEMove move) { if (_move != move) { _move = move; OnPropertyChanged(nameof(Move)); if (_move == PBEMove.None) { UpdatePPUps(0); } IsPPUpsEditable = _move != PBEMove.None; } } private void UpdatePPUps(byte ppUps) { if (_ppUps != ppUps) { _ppUps = ppUps; OnPropertyChanged(nameof(PPUps)); } } } private void OnPropertyChanged(string property) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); } public event PropertyChangedEventHandler? PropertyChanged; private PBESpecies _species; public PBESpecies Species { get => _species; set { if (_species != value) { PBEDataUtils.ValidateSpecies(value, 0, true); _species = value; _form = 0; OnPropertyChanged(nameof(Species)); SetAlloweds(); } } } private PBEForm _form; public PBEForm Form { get => _form; set { if (_form != value) { PBEDataUtils.ValidateSpecies(_species, value, true); _form = value; OnPropertyChanged(nameof(Form)); SetAlloweds(); } } } private byte _level; public byte Level { get => _level; set { if (_level != value) { PBEDataUtils.ValidateLevel(value, Settings); _level = value; OnPropertyChanged(nameof(Level)); SetAlloweds(); } } } public PBESettings Settings { get; } private readonly PBELegalMovesetSlot[] _list; public int Count => Settings.NumMoves; public PBELegalMovesetSlot this[int index] { get { if (index >= Settings.NumMoves) { throw new ArgumentOutOfRangeException(nameof(index)); } return _list[index]; } } IPBEMovesetSlot IReadOnlyList.this[int index] => this[index]; public PBELegalMovesetSlot? this[PBEMove move] { get { for (int i = 0; i < Settings.NumMoves; i++) { PBELegalMovesetSlot slot = _list[i]; if (slot.Move == move) { return slot; } } return null; } } internal PBELegalMoveset(PBELegalMoveset other) { _species = other._species; _form = other._form; _level = other._level; Settings = other.Settings; int count = Settings.NumMoves; _list = new PBELegalMovesetSlot[count]; for (int i = 0; i < count; i++) { _list[i] = new PBELegalMovesetSlot(this, i); } SetAlloweds(); for (int i = 0; i < count; i++) { PBELegalMovesetSlot slot = _list[i]; PBELegalMovesetSlot oSlot = other[i]; slot.Move = oSlot.Move; slot.PPUps = oSlot.PPUps; } } internal PBELegalMoveset(PBESpecies species, PBEForm form, byte level, PBESettings settings, IPBEMoveset other) { int count = other.Count; if (count != settings.NumMoves) { throw new InvalidDataException($"Moveset count must be equal to \"{nameof(settings.NumMoves)}\" ({settings.NumMoves})."); } _species = species; _form = form; _level = level; Settings = settings; _list = new PBELegalMovesetSlot[count]; for (int i = 0; i < count; i++) { _list[i] = new PBELegalMovesetSlot(this, i); } SetAlloweds(); for (int i = 0; i < count; i++) { IPBEMovesetSlot oSlot = other[i]; PBELegalMovesetSlot slot = _list[i]; PBEMove move = oSlot.Move; slot.Move = move; if (slot.Move != move) { throw new ArgumentOutOfRangeException(nameof(IPBEPokemon.Moveset), "Invalid moves."); } byte ppUps = oSlot.PPUps; slot.PPUps = ppUps; if (slot.PPUps != ppUps) { throw new ArgumentOutOfRangeException(nameof(IPBEPokemon.Moveset), "Invalid PP-Ups."); } } } /// Creates a new object with the specified traits. /// The species of the Pokémon that this moveset will be built for. /// The form of the Pokémon that this moveset will be built for. /// The level of the Pokémon that this moveset will be built for. /// The settings that will be used to evaluate the . /// True if should be called, False if the move slots use their default values. public PBELegalMoveset(PBESpecies species, PBEForm form, byte level, PBESettings settings, bool randomize) { settings.ShouldBeReadOnly(nameof(settings)); PBEDataUtils.ValidateSpecies(species, form, true); PBEDataUtils.ValidateLevel(level, settings); _level = level; _species = species; _form = form; Settings = settings; int count = settings.NumMoves; _list = new PBELegalMovesetSlot[count]; for (int i = 0; i < count; i++) { _list[i] = new PBELegalMovesetSlot(this, i); } SetAlloweds(); if (randomize) { Randomize(); } } private static readonly PBEAlphabeticalList secretSwordArray = new(new PBEMove[1] { PBEMove.SecretSword }); private void SetAlloweds() { // Set alloweds int i; IReadOnlyCollection legalMoves = PBEDataProvider.Instance.GetLegalMoves(_species, _form, _level); var allowed = new List(legalMoves.Count + 1); allowed.AddRange(legalMoves); if (_species == PBESpecies.Keldeo && _form == PBEForm.Keldeo_Resolute) { _list[0].Allowed.Reset(secretSwordArray); allowed.Remove(PBEMove.SecretSword); i = 1; } else { i = 0; } for (; i < Settings.NumMoves; i++) { if (i == 1) { allowed.Insert(0, PBEMove.None); } _list[i].Allowed.Reset(allowed); } // Remove unalloweds (slot.Move setter will automatically sort PBEMove.None) while (true) { int bad = Array.FindIndex(_list, s => !s.Allowed.Contains(s.Move)); if (bad == -1) { break; } else { PBELegalMovesetSlot slot = _list[bad]; slot.Move = slot.Allowed[0]; } } SetEditables(); } private void SetEditables() { for (int i = 2; i < Settings.NumMoves; i++) { _list[i].IsMoveEditable = _list[i - 1].Move != PBEMove.None; } } /// Sets every move slot excluding the first to with 0 PP-Ups. public void Clear() { for (int i = 1; i < Settings.NumMoves; i++) { _list[i].Move = PBEMove.None; } SetEditables(); } public bool Contains(PBEMove move) { return this[move] is not null; } /// Randomizes the move and PP-Ups in each slot without creating duplicate moves. public void Randomize() { var blacklist = new List(Settings.NumMoves) { PBEMove.None }; for (int i = 0; i < Settings.NumMoves; i++) { PBELegalMovesetSlot slot = _list[i]; PBEMove[] allowed = slot.Allowed.Except(blacklist).ToArray(); if (allowed.Length == 0) { for (int j = i; j < Settings.NumMoves; j++) { _list[j].Move = PBEMove.None; } break; } else { PBEMove move = PBEDataProvider.GlobalRandom.RandomElement(allowed); if (i < Settings.NumMoves - 1) { blacklist.Add(move); } slot.Move = move; slot.PPUps = (byte)PBEDataProvider.GlobalRandom.RandomInt(0, Settings.MaxPPUps); } } SetEditables(); } public IEnumerator GetEnumerator() { for (int i = 0; i < Settings.NumMoves; i++) { yield return _list[i]; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } ================================================ FILE: PokemonBattleEngine/Data/Legality/LegalPokemon.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Data.Utils; using Kermalis.PokemonBattleEngine.Utils; using System; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Text.Json.Nodes; namespace Kermalis.PokemonBattleEngine.Data.Legality; public sealed class PBELegalPokemon : IPBEPokemon, INotifyPropertyChanged { private void OnPropertyChanged(string property) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); } public event PropertyChangedEventHandler? PropertyChanged; public PBESettings Settings { get; } private IPBEPokemonData _pData; public PBEAlphabeticalList SelectableAbilities { get; } = new(); public PBEAlphabeticalList SelectableForms { get; } = new(); public PBEAlphabeticalList SelectableGenders { get; } = new(); public PBEAlphabeticalList SelectableItems { get; } = new(); public bool PBEIgnore => false; private PBESpecies _species; public PBESpecies Species { get => _species; set { if (_species != value) { PBEDataUtils.ValidateSpecies(value, 0, true); PBESpecies oldSpecies = _species; _species = value; _form = 0; OnPropertyChanged(nameof(Species)); OnSpeciesChanged(oldSpecies); OnPropertyChanged(nameof(Form)); } } } private PBEForm _form; public PBEForm Form { get => _form; set { if (_form != value) { PBEDataUtils.ValidateSpecies(_species, value, true); _form = value; OnPropertyChanged(nameof(Form)); OnFormChanged(); } } } private string _nickname; public string Nickname { get => _nickname; set { if (_nickname != value) { PBEDataUtils.ValidateNickname(value, Settings); _nickname = value; OnPropertyChanged(nameof(Nickname)); } } } private byte _level; public byte Level { get => _level; set { if (_level != value) { PBEDataUtils.ValidateLevel(value, Settings); _level = value; OnPropertyChanged(nameof(Level)); Moveset.Level = value; EXP = PBEDataProvider.Instance.GetEXPRequired(_pData.GrowthRate, value); } } } private uint _exp; public uint EXP { get => _exp; set { if (_exp != value) { _level = PBEDataProvider.Instance.GetEXPLevel(_pData.GrowthRate, value); _exp = value; OnPropertyChanged(nameof(EXP)); OnPropertyChanged(nameof(Level)); Moveset.Level = _level; } } } private byte _friendship; public byte Friendship { get => _friendship; set { if (value != _friendship) { _friendship = value; OnPropertyChanged(nameof(Friendship)); } } } private bool _shiny; public bool Shiny { get => _shiny; set { if (value != _shiny) { _shiny = value; OnPropertyChanged(nameof(Shiny)); } } } private bool _pokerus; public bool Pokerus { get => _pokerus; set { if (value != _pokerus) { _pokerus = value; OnPropertyChanged(nameof(Pokerus)); } } } private PBEAbility _ability; public PBEAbility Ability { get => _ability; set { if (value != _ability) { PBEDataUtils.ValidateAbility(SelectableAbilities, value); _ability = value; OnPropertyChanged(nameof(Ability)); } } } private PBENature _nature; public PBENature Nature { get => _nature; set { if (value != _nature) { PBEDataUtils.ValidateNature(value); _nature = value; OnPropertyChanged(nameof(Nature)); } } } private PBEItem _caughtBall; public PBEItem CaughtBall { get => _caughtBall; set { if (value != _caughtBall) { PBEDataUtils.ValidateCaughtBall(value); _caughtBall = value; OnPropertyChanged(nameof(CaughtBall)); } } } private PBEGender _gender; public PBEGender Gender { get => _gender; set { if (value != _gender) { PBEDataUtils.ValidateGender(SelectableGenders, value); _gender = value; OnPropertyChanged(nameof(Gender)); } } } private PBEItem _item; public PBEItem Item { get => _item; set { if (value != _item) { PBEDataUtils.ValidateItem(SelectableItems, value); _item = value; OnPropertyChanged(nameof(Item)); } } } public PBELegalEffortValues EffortValues { get; } IPBEStatCollection IPBEPokemon.EffortValues => EffortValues; public PBELegalIndividualValues IndividualValues { get; } IPBEReadOnlyStatCollection IPBEPokemon.IndividualValues => IndividualValues; public PBELegalMoveset Moveset { get; } IPBEMoveset IPBEPokemon.Moveset => Moveset; internal PBELegalPokemon(PBESettings settings, EndianBinaryReader r) { Settings = settings; PBESpecies species = r.ReadEnum(); PBEForm form = r.ReadEnum(); PBEDataUtils.ValidateSpecies(species, form, true); _species = species; _form = form; SetSelectable(); string nickname = r.ReadString_NullTerminated(); PBEDataUtils.ValidateNickname(nickname, Settings); _nickname = nickname; byte level = r.ReadByte(); PBEDataUtils.ValidateLevel(level, Settings); _level = level; uint exp = r.ReadUInt32(); PBEDataUtils.ValidateEXP(_pData!.GrowthRate, exp, level); _exp = exp; _friendship = r.ReadByte(); _shiny = r.ReadBoolean(); _pokerus = r.ReadBoolean(); PBEAbility ability = r.ReadEnum(); PBEDataUtils.ValidateAbility(SelectableAbilities, ability); _ability = ability; PBENature nature = r.ReadEnum(); PBEDataUtils.ValidateNature(nature); _nature = nature; PBEItem caughtBall = r.ReadEnum(); PBEDataUtils.ValidateCaughtBall(caughtBall); _caughtBall = caughtBall; PBEGender gender = r.ReadEnum(); PBEDataUtils.ValidateGender(SelectableGenders, gender); _gender = gender; PBEItem item = r.ReadEnum(); PBEDataUtils.ValidateItem(SelectableItems, item); _item = item; EffortValues = new PBELegalEffortValues(Settings, r); IndividualValues = new PBELegalIndividualValues(Settings, r); Moveset = new PBELegalMoveset(species, form, level, Settings, new PBEReadOnlyMoveset(r)); } internal PBELegalPokemon(PBESettings settings, JsonObject jObj) { Settings = settings; _friendship = jObj.GetSafe(nameof(Friendship)).GetValue(); _shiny = jObj.GetSafe(nameof(Shiny)).GetValue(); _pokerus = jObj.GetSafe(nameof(Pokerus)).GetValue(); byte level = jObj.GetSafe(nameof(Level)).GetValue(); PBEDataUtils.ValidateLevel(level, Settings); _level = level; string nickname = jObj.GetSafe(nameof(Nickname)).GetValue(); PBEDataUtils.ValidateNickname(nickname, Settings); _nickname = nickname; if (!PBEDataProvider.Instance.GetNatureByName(jObj.GetSafe(nameof(Nature)).GetValue(), out PBENature? nature)) { throw new InvalidDataException("Invalid nature"); } PBEDataUtils.ValidateNature(nature.Value); _nature = nature.Value; if (!PBEDataProvider.Instance.GetItemByName(jObj.GetSafe(nameof(CaughtBall)).GetValue(), out PBEItem? caughtBall)) { throw new InvalidDataException("Invalid caught ball"); } PBEDataUtils.ValidateCaughtBall(caughtBall.Value); _caughtBall = caughtBall.Value; if (!PBEDataProvider.Instance.GetSpeciesByName(jObj.GetSafe(nameof(Species)).GetValue(), out PBESpecies? species)) { throw new InvalidDataException("Invalid species"); } PBEForm form; if (PBEDataUtils.HasForms(species.Value, true)) { form = Enum.Parse(jObj.GetSafe(nameof(Form)).GetValue()); } else { form = 0; } PBEDataUtils.ValidateSpecies(species.Value, form, true); _species = species.Value; _form = form; SetSelectable(); uint exp = jObj.GetSafe(nameof(EXP)).GetValue(); PBEDataUtils.ValidateEXP(_pData.GrowthRate, exp, level); _exp = exp; if (!PBEDataProvider.Instance.GetAbilityByName(jObj.GetSafe(nameof(Ability)).GetValue(), out PBEAbility? ability)) { throw new InvalidDataException("Invalid ability"); } PBEDataUtils.ValidateAbility(SelectableAbilities, ability.Value); _ability = ability.Value; if (!PBEDataProvider.Instance.GetGenderByName(jObj.GetSafe(nameof(Gender)).GetValue(), out PBEGender? gender)) { throw new InvalidDataException("Invalid gender"); } PBEDataUtils.ValidateGender(SelectableGenders, gender.Value); _gender = gender.Value; if (!PBEDataProvider.Instance.GetItemByName(jObj.GetSafe(nameof(Item)).GetValue(), out PBEItem? item)) { throw new InvalidDataException("Invalid item"); } PBEDataUtils.ValidateItem(SelectableItems, item.Value); _item = item.Value; EffortValues = new PBELegalEffortValues(Settings, jObj.GetSafe(nameof(EffortValues)).AsObject()); IndividualValues = new PBELegalIndividualValues(Settings, jObj.GetSafe(nameof(IndividualValues)).AsObject()); Moveset = new PBELegalMoveset(_species, form, level, Settings, new PBEReadOnlyMoveset(jObj.GetSafe(nameof(Moveset)).AsArray())); } public PBELegalPokemon(PBESpecies species, PBEForm form, byte level, uint exp, PBESettings settings) { settings.ShouldBeReadOnly(nameof(settings)); PBEDataUtils.ValidateSpecies(species, form, true); PBEDataUtils.ValidateLevel(level, settings); PBEDataUtils.ValidateEXP(PBEDataProvider.Instance.GetPokemonData(species, form).GrowthRate, exp, level); Settings = settings; _species = species; _form = form; _level = level; _exp = exp; _friendship = (byte)PBEDataProvider.GlobalRandom.RandomInt(0, byte.MaxValue); _shiny = PBEDataProvider.GlobalRandom.RandomShiny(); _nature = PBEDataProvider.GlobalRandom.RandomElement(PBEDataUtils.AllNatures); _caughtBall = PBEDataProvider.GlobalRandom.RandomElement(PBEDataUtils.AllBalls); EffortValues = new PBELegalEffortValues(Settings, true); IndividualValues = new PBELegalIndividualValues(Settings, true); Moveset = new PBELegalMoveset(_species, _form, _level, Settings, true); SetSelectable(); _nickname = GetTruncatedNickname(); UpdateAbility(); UpdateGender(); UpdateItem(); } [MemberNotNull(nameof(_pData))] private void SetSelectable() { _pData = PBEDataProvider.Instance.GetPokemonData(_species, _form); SelectableAbilities.Reset(_pData.Abilities); SelectableForms.Reset(PBEDataUtils.GetForms(_species, true)); SelectableGenders.Reset(PBEDataUtils.GetValidGenders(_pData.GenderRatio)); SelectableItems.Reset(PBEDataUtils.GetValidItems(_species, _form)); } private void OnFormChanged() { SetSelectable(); if (!SelectableAbilities.Contains(_ability)) { Ability = PBEDataProvider.GlobalRandom.RandomElement(SelectableAbilities); } if (!SelectableItems.Contains(_item)) { Item = PBEDataProvider.GlobalRandom.RandomElement(SelectableItems); } Moveset.Form = _form; } private string GetTruncatedNickname() { string newNickname = PBEDataProvider.Instance.GetSpeciesName(_species).FromGlobalLanguage(); if (newNickname.Length > Settings.MaxPokemonNameLength) { newNickname = newNickname.Substring(0, Settings.MaxPokemonNameLength); } return newNickname; } private void UpdateAbility() { Ability = PBEDataProvider.GlobalRandom.RandomElement(SelectableAbilities); } private void UpdateGender() { Gender = PBEDataProvider.GlobalRandom.RandomGender(_pData.GenderRatio); } private void UpdateItem() { Item = PBEDataProvider.GlobalRandom.RandomElement(SelectableItems); } private void OnSpeciesChanged(PBESpecies oldSpecies) { SetSelectable(); if (_nickname == PBEDataProvider.Instance.GetSpeciesName(oldSpecies).FromGlobalLanguage()) { Nickname = GetTruncatedNickname(); } if (!SelectableAbilities.Contains(_ability)) { UpdateAbility(); } if (!SelectableGenders.Contains(_gender)) { UpdateGender(); } if (!SelectableItems.Contains(_item)) { UpdateItem(); } Moveset.Species = _species; Moveset.Form = _form; } } ================================================ FILE: PokemonBattleEngine/Data/Legality/LegalPokemonCollection.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Data.Utils; using Kermalis.PokemonBattleEngine.Utils; using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.IO; using System.Text.Json; using System.Text.Json.Nodes; namespace Kermalis.PokemonBattleEngine.Data.Legality; public sealed class PBELegalPokemonCollection : IPBEPokemonCollection, IPBEPokemonCollection, INotifyCollectionChanged, INotifyPropertyChanged { private void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { CollectionChanged?.Invoke(this, e); } private void OnPropertyChanged(string property) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); } public event NotifyCollectionChangedEventHandler? CollectionChanged; public event PropertyChangedEventHandler? PropertyChanged; private readonly List _list; public int Count => _list.Count; public PBELegalPokemon this[int index] { get { if (index >= _list.Count) { throw new ArgumentOutOfRangeException(nameof(index)); } return _list[index]; } set => ReplaceAt(value, index); } IPBEPokemon IReadOnlyList.this[int index] => this[index]; public PBESettings Settings { get; } internal PBELegalPokemonCollection(PBESettings settings, EndianBinaryReader r) { byte count = r.ReadByte(); if (count < 1 || count > settings.MaxPartySize) { throw new InvalidDataException(); } Settings = settings; _list = new List(Settings.MaxPartySize); for (int i = 0; i < count; i++) { InsertWithEvents(false, new PBELegalPokemon(Settings, r), i); } } public PBELegalPokemonCollection(string path) { JsonObject jObj = JsonNode.Parse(File.ReadAllText(path))!.AsObject(); Settings = new PBESettings(jObj.GetSafe(nameof(Settings)).GetValue()); Settings.MakeReadOnly(); JsonArray jArray = jObj.GetSafe("Party").AsArray(); if (jArray.Count < 1 || jArray.Count > Settings.MaxPartySize) { throw new InvalidDataException("Invalid party size."); } _list = new List(Settings.MaxPartySize); for (int i = 0; i < jArray.Count; i++) { InsertWithEvents(false, new PBELegalPokemon(Settings, jArray.GetSafe(i).AsObject()), i); } } public PBELegalPokemonCollection(PBESettings settings) { settings.ShouldBeReadOnly(nameof(settings)); Settings = settings; _list = new List(Settings.MaxPartySize); } public PBELegalPokemonCollection(PBESettings settings, int numPkmnToGenerate, bool setToMaxLevel) { settings.ShouldBeReadOnly(nameof(settings)); if (numPkmnToGenerate < 1 || numPkmnToGenerate > settings.MaxPartySize) { throw new ArgumentOutOfRangeException(nameof(numPkmnToGenerate)); } Settings = settings; _list = new List(Settings.MaxPartySize); for (int i = 0; i < numPkmnToGenerate; i++) { InsertRandom(setToMaxLevel, false, i); } } private void InsertRandom(bool setToMaxLevel, bool fireEvent, int index) { (PBESpecies species, PBEForm form) = PBEDataProvider.GlobalRandom.RandomSpecies(true); byte level = setToMaxLevel ? Settings.MaxLevel : PBEDataProvider.GlobalRandom.RandomLevel(Settings); Insert(species, form, level, PBEDataProvider.Instance.GetEXPRequired(PBEDataProvider.Instance.GetPokemonData(species, form).GrowthRate, level), fireEvent, index); } private void Insert(PBESpecies species, PBEForm form, byte level, uint exp, bool fireEvent, int index) { InsertWithEvents(fireEvent, new PBELegalPokemon(species, form, level, exp, Settings), index); } private void InsertWithEvents(bool fireEvent, PBELegalPokemon item, int index) { _list.Insert(index, item); if (fireEvent) { OnPropertyChanged(nameof(Count)); OnPropertyChanged("Item[]"); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); } } private void RemoveWithEvents(PBELegalPokemon item, int index) { _list.RemoveAt(index); NotifyCollectionChangedEventArgs e; if (_list.Count == 0) { InsertRandom(false, false, 0); e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, _list[0], item, 0); } else { OnPropertyChanged(nameof(Count)); e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index); } OnPropertyChanged("Item[]"); OnCollectionChanged(e); } private void ExceedException() { throw new InvalidOperationException($"Party size cannot exceed \"{nameof(Settings.MaxPartySize)}\" ({Settings.MaxPartySize})."); } public void AddRandom(bool setToMaxLevel) { if (_list.Count < Settings.MaxPartySize) { InsertRandom(setToMaxLevel, true, _list.Count); } else { ExceedException(); } } public void Add(PBESpecies species, PBEForm form, byte level, uint exp) { PBEDataUtils.ValidateSpecies(species, form, true); PBEDataUtils.ValidateLevel(level, Settings); PBEDataUtils.ValidateEXP(PBEDataProvider.Instance.GetPokemonData(species, form).GrowthRate, exp, level); if (_list.Count < Settings.MaxPartySize) { Insert(species, form, level, exp, true, _list.Count); } else { ExceedException(); } } public void Add(PBELegalPokemon item) { if (!Settings.Equals(item.Settings)) { throw new ArgumentException("Settings must be equal.", nameof(item)); } if (_list.Count < Settings.MaxPartySize) { InsertWithEvents(true, item, _list.Count); } else { ExceedException(); } } public void InsertRandom(bool setToMaxLevel, int index) { if (_list.Count < Settings.MaxPartySize) { InsertRandom(setToMaxLevel, true, index); } else { ExceedException(); } } public void Insert(PBESpecies species, PBEForm form, byte level, uint exp, int index) { PBEDataUtils.ValidateSpecies(species, form, true); PBEDataUtils.ValidateLevel(level, Settings); if (_list.Count < Settings.MaxPartySize) { Insert(species, form, level, exp, true, index); } else { ExceedException(); } } public void Insert(PBELegalPokemon item, int index) { if (!Settings.Equals(item.Settings)) { throw new ArgumentException("Settings must be equal.", nameof(item)); } if (_list.Count < Settings.MaxPartySize) { InsertWithEvents(true, item, index); } else { ExceedException(); } } public void Clear() { int oldCount = _list.Count; _list.Clear(); InsertRandom(false, false, 0); if (oldCount != 1) { OnPropertyChanged(nameof(Count)); } OnPropertyChanged("Item[]"); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } public bool Remove(PBELegalPokemon item) { int index = _list.IndexOf(item); bool b = index != -1; if (b) { RemoveWithEvents(item, index); } return b; } public void RemoveAt(int index) { if (index < 0 || index >= _list.Count) { throw new ArgumentOutOfRangeException(nameof(index)); } else { RemoveWithEvents(_list[index], index); } } public void ReplaceAt(PBELegalPokemon item, int index) { if (index >= _list.Count) { throw new ArgumentOutOfRangeException(nameof(index)); } if (!Settings.Equals(item.Settings)) { throw new ArgumentException("Settings must be equal.", nameof(item)); } PBELegalPokemon old = _list[index]; _list[index] = item; OnPropertyChanged("Item[]"); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, old, index)); } public bool Contains(PBELegalPokemon item) { return _list.IndexOf(item) != -1; } public int IndexOf(PBELegalPokemon item) { return _list.IndexOf(item); } public IEnumerator GetEnumerator() { return _list.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return _list.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return _list.GetEnumerator(); } public void ToJsonFile(string path) { using (FileStream fs = File.OpenWrite(path)) using (var w = new Utf8JsonWriter(fs, options: new JsonWriterOptions { Indented = true })) { w.WriteStartObject(); w.WriteString(nameof(Settings), Settings.ToString()); w.WriteStartArray("Party"); for (int i = 0; i < _list.Count; i++) { _list[i].ToJson(w); } w.WriteEndArray(); w.WriteEndObject(); } } } ================================================ FILE: PokemonBattleEngine/Data/PBEAlphabeticalList.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Linq; namespace Kermalis.PokemonBattleEngine.Data; public sealed class PBEAlphabeticalList : INotifyCollectionChanged, INotifyPropertyChanged, IReadOnlyList { private sealed class PBEAlphabeticalListEntry { public T Key { get; } public IPBEReadOnlyLocalizedString Value { get; } public PBEAlphabeticalListEntry(T key, object? parameter) { switch (key) { case PBEAbility ability: Value = PBEDataProvider.Instance.GetAbilityName(ability); break; case PBEForm form: Value = PBEDataProvider.Instance.GetFormName((PBESpecies)parameter!, form); break; case PBEGender gender: Value = PBEDataProvider.Instance.GetGenderName(gender); break; case PBEItem item: Value = PBEDataProvider.Instance.GetItemName(item); break; case PBEMove move: Value = PBEDataProvider.Instance.GetMoveName(move); break; case PBENature nature: Value = PBEDataProvider.Instance.GetNatureName(nature); break; case PBESpecies species: Value = PBEDataProvider.Instance.GetSpeciesName(species); break; case PBEStat stat: Value = PBEDataProvider.Instance.GetStatName(stat); break; case PBEType type: Value = PBEDataProvider.Instance.GetTypeName(type); break; default: throw new ArgumentOutOfRangeException(nameof(key)); } Key = key; } } private void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { CollectionChanged?.Invoke(this, e); } private void OnPropertyChanged(string property) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); } public event NotifyCollectionChangedEventHandler? CollectionChanged; public event PropertyChangedEventHandler? PropertyChanged; private PBEAlphabeticalListEntry[] _list; public int Count => _list.Length; public T this[int index] { get { if (index >= _list.Length) { throw new ArgumentOutOfRangeException(nameof(index)); } return _list[index].Key; } } internal PBEAlphabeticalList() { _list = Array.Empty(); } internal PBEAlphabeticalList(IEnumerable collection, object? parameter = null) { Reset(collection, parameter: parameter); } private void Sort(PBEAlphabeticalListEntry[]? old) { if (old is null || old == _list) { old = (PBEAlphabeticalListEntry[])_list.Clone(); } Array.Sort(_list, (x, y) => x.Value.FromGlobalLanguage().CompareTo(y.Value.FromGlobalLanguage())); if (!_list.SequenceEqual(old)) { OnPropertyChanged("Item[]"); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } [MemberNotNull(nameof(_list))] internal void Reset(IEnumerable collection, object? parameter = null) { PBEAlphabeticalListEntry[]? old = _list; if (collection is PBEAlphabeticalList other) { _list = (PBEAlphabeticalListEntry[])other._list.Clone(); } else { _list = collection.Select(t => new PBEAlphabeticalListEntry(t, parameter)).ToArray(); } if (old is not null && old.Length != _list.Length) { OnPropertyChanged(nameof(Count)); } Sort(old); } public bool Contains(T? item) { return IndexOf(item) != -1; } public List FindAll(Predicate match) { var results = new List(_list.Length); for (int i = 0; i < _list.Length; i++) { T key = _list[i].Key; if (match(key)) { results.Add(key); } } return results; } public int IndexOf(T? item) { if (item is not null) { for (int i = 0; i < _list.Length; i++) { if (item.Equals(_list[i].Key)) { return i; } } } return -1; } public IEnumerator GetEnumerator() { for (int i = 0; i < _list.Length; i++) { yield return _list[i].Key; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public ReadOnlyCollection AsReadOnly() { return ToList().AsReadOnly(); } public T[] ToArray() { var arr = new T[_list.Length]; for (int i = 0; i < _list.Length; i++) { arr[i] = _list[i].Key; } return arr; } public List ToList() { var list = new List(_list.Length); for (int i = 0; i < _list.Length; i++) { list.Add(_list[i].Key); } return list; } } ================================================ FILE: PokemonBattleEngine/Data/PBEList.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; namespace Kermalis.PokemonBattleEngine.Data; public sealed class PBEList : INotifyCollectionChanged, INotifyPropertyChanged, IReadOnlyList { private void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { CollectionChanged?.Invoke(this, e); } private void OnPropertyChanged(string property) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); } public event NotifyCollectionChangedEventHandler? CollectionChanged; public event PropertyChangedEventHandler? PropertyChanged; private readonly List _list; public int Count => _list.Count; public T this[int index] { get { if (index >= _list.Count) { throw new ArgumentOutOfRangeException(nameof(index)); } return _list[index]; } } internal PBEList() { _list = new List(); } internal PBEList(int capacity) { _list = new List(capacity); } internal PBEList(IEnumerable collection) { _list = new List(collection); } internal void Add(T item) { int index = _list.Count; _list.Insert(index, item); OnPropertyChanged(nameof(Count)); OnPropertyChanged("Item[]"); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); } internal void Insert(int index, T item) { _list.Insert(index, item); OnPropertyChanged(nameof(Count)); OnPropertyChanged("Item[]"); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); } internal bool Remove(T item) { int index = _list.IndexOf(item); bool b = index != -1; if (b) { _list.RemoveAt(index); OnPropertyChanged(nameof(Count)); OnPropertyChanged("Item[]"); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index)); } return b; } internal void RemoveAt(int index) { if (index < 0 || index >= _list.Count) { throw new ArgumentOutOfRangeException(nameof(index)); } else { T item = _list[index]; _list.RemoveAt(index); OnPropertyChanged(nameof(Count)); OnPropertyChanged("Item[]"); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index)); } } internal void Reset(IEnumerable collection) { int oldCount = _list.Count; if (!_list.SequenceEqual(collection)) { _list.Clear(); _list.AddRange(collection); if (oldCount != _list.Count) { OnPropertyChanged(nameof(Count)); } OnPropertyChanged("Item[]"); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } internal void Swap(T a, T b) { int aIndex = IndexOf(a); if (aIndex == -1) { throw new ArgumentOutOfRangeException(nameof(a)); } int bIndex = IndexOf(b); if (bIndex == -1) { throw new ArgumentOutOfRangeException(nameof(b)); } _list[aIndex] = b; _list[bIndex] = a; OnPropertyChanged("Item[]"); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, a, b, bIndex)); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, b, a, aIndex)); } public bool Contains(T item) { return _list.IndexOf(item) != -1; } public List FindAll(Predicate match) { return _list.FindAll(match); } public int IndexOf(T item) { return _list.IndexOf(item); } public T[] ToArray() { return _list.ToArray(); } public IEnumerator GetEnumerator() { for (int i = 0; i < _list.Count; i++) { yield return _list[i]; } } IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } ================================================ FILE: PokemonBattleEngine/Data/ReadOnlyLocalizedString.cs ================================================ namespace Kermalis.PokemonBattleEngine.Data; public sealed class PBEReadOnlyLocalizedString : IPBEReadOnlyLocalizedString { public string English { get; } public string French { get; } public string German { get; } public string Italian { get; } public string Japanese_Kana { get; } public string Japanese_Kanji { get; } public string Korean { get; } public string Spanish { get; } public PBEReadOnlyLocalizedString(IPBEReadOnlyLocalizedString other) { English = other.English; French = other.French; German = other.German; Italian = other.Italian; Japanese_Kana = other.Japanese_Kana; Japanese_Kanji = other.Japanese_Kanji; Korean = other.Korean; Spanish = other.Spanish; } public override string ToString() { return this.FromGlobalLanguage(); } } ================================================ FILE: PokemonBattleEngine/Data/ReadOnlyMoveset.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Data.Utils; using Kermalis.PokemonBattleEngine.Utils; using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Text.Json.Nodes; namespace Kermalis.PokemonBattleEngine.Data; public sealed class PBEReadOnlyMoveset : IPBEMoveset, IPBEMoveset { public sealed class PBEReadOnlyMovesetSlot : IPBEMovesetSlot { public PBEMove Move { get; } public byte PPUps { get; } internal PBEReadOnlyMovesetSlot(PBEMove move, byte ppUps) { Move = move; PPUps = ppUps; } } private readonly PBEReadOnlyMovesetSlot[] _list; public int Count => _list.Length; public PBEReadOnlyMovesetSlot this[int index] { get { if (index >= _list.Length) { throw new ArgumentOutOfRangeException(nameof(index)); } return _list[index]; } } IPBEMovesetSlot IReadOnlyList.this[int index] => this[index]; internal PBEReadOnlyMoveset(EndianBinaryReader r) { int count = r.ReadByte(); _list = new PBEReadOnlyMovesetSlot[count]; for (int i = 0; i < count; i++) { _list[i] = new PBEReadOnlyMovesetSlot(r.ReadEnum(), r.ReadByte()); } } internal PBEReadOnlyMoveset(JsonArray jArray) { int count = jArray.Count; _list = new PBEReadOnlyMovesetSlot[count]; for (int i = 0; i < count; i++) { JsonObject jObj = jArray.GetSafe(i).AsObject(); if (!PBEDataProvider.Instance.GetMoveByName(jObj.GetSafe(nameof(IPBEMovesetSlot.Move)).GetValue(), out PBEMove? move)) { throw new InvalidDataException("Invalid move"); } byte ppUps = jObj.GetSafe(nameof(IPBEMovesetSlot.PPUps)).GetValue(); _list[i] = new PBEReadOnlyMovesetSlot(move.Value, ppUps); } } public PBEReadOnlyMoveset(IPBEMoveset other) { int count = other.Count; _list = new PBEReadOnlyMovesetSlot[count]; for (int i = 0; i < count; i++) { IPBEMovesetSlot oSlot = other[i]; _list[i] = new PBEReadOnlyMovesetSlot(oSlot.Move, oSlot.PPUps); } } public IEnumerator GetEnumerator() { for (int i = 0; i < _list.Length; i++) { yield return _list[i]; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } public sealed class PBEReadOnlyPartyMoveset : IPBEPartyMoveset, IPBEPartyMoveset { public sealed class PBEReadOnlyPartyMovesetSlot : IPBEPartyMovesetSlot { public PBEMove Move { get; } public int PP { get; } public byte PPUps { get; } internal PBEReadOnlyPartyMovesetSlot(PBESettings settings, PBEMove move, byte ppUps) { Move = move; PP = PBEDataUtils.CalcMaxPP(move, ppUps, settings); PPUps = ppUps; } internal PBEReadOnlyPartyMovesetSlot(PBEMove move, int pp, byte ppUps) { Move = move; PP = pp; PPUps = ppUps; } } private readonly PBEReadOnlyPartyMovesetSlot[] _list; public int Count => _list.Length; public PBEReadOnlyPartyMovesetSlot this[int index] { get { if (index >= _list.Length) { throw new ArgumentOutOfRangeException(nameof(index)); } return _list[index]; } } IPBEPartyMovesetSlot IReadOnlyList.this[int index] => this[index]; internal PBEReadOnlyPartyMoveset(EndianBinaryReader r) { int count = r.ReadByte(); _list = new PBEReadOnlyPartyMovesetSlot[count]; for (int i = 0; i < count; i++) { _list[i] = new PBEReadOnlyPartyMovesetSlot(r.ReadEnum(), r.ReadInt32(), r.ReadByte()); } } internal PBEReadOnlyPartyMoveset(JsonArray jArray) { int count = jArray.Count; _list = new PBEReadOnlyPartyMovesetSlot[count]; for (int i = 0; i < count; i++) { JsonObject jObj = jArray.GetSafe(i).AsObject(); if (!PBEDataProvider.Instance.GetMoveByName(jObj.GetSafe(nameof(IPBEMovesetSlot.Move)).GetValue(), out PBEMove? move)) { throw new InvalidDataException("Invalid move"); } int pp = jObj.GetSafe(nameof(IPBEPartyMovesetSlot.PP)).GetValue(); byte ppUps = jObj.GetSafe(nameof(IPBEPartyMovesetSlot.PPUps)).GetValue(); _list[i] = new PBEReadOnlyPartyMovesetSlot(move.Value, pp, ppUps); } } public PBEReadOnlyPartyMoveset(PBESettings settings, IPBEMoveset other) { settings.ShouldBeReadOnly(nameof(settings)); int count = other.Count; _list = new PBEReadOnlyPartyMovesetSlot[count]; for (int i = 0; i < count; i++) { IPBEMovesetSlot oSlot = other[i]; _list[i] = new PBEReadOnlyPartyMovesetSlot(settings, oSlot.Move, oSlot.PPUps); } } public PBEReadOnlyPartyMoveset(IPBEPartyMoveset other) { int count = other.Count; _list = new PBEReadOnlyPartyMovesetSlot[count]; for (int i = 0; i < count; i++) { IPBEPartyMovesetSlot oSlot = other[i]; _list[i] = new PBEReadOnlyPartyMovesetSlot(oSlot.Move, oSlot.PP, oSlot.PPUps); } } public IEnumerator GetEnumerator() { for (int i = 0; i < _list.Length; i++) { yield return _list[i]; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } ================================================ FILE: PokemonBattleEngine/Data/ReadOnlyPokemon.cs ================================================ using Kermalis.EndianBinaryIO; namespace Kermalis.PokemonBattleEngine.Data; public sealed class PBEReadOnlyPokemon : IPBEPokemon { public bool PBEIgnore => false; public PBESpecies Species { get; } public PBEForm Form { get; } public PBEGender Gender { get; } public string Nickname { get; } public bool Shiny { get; } public byte Level { get; } public uint EXP { get; } public bool Pokerus { get; } public PBEItem Item { get; } public byte Friendship { get; } public PBEAbility Ability { get; } public PBENature Nature { get; } public PBEItem CaughtBall { get; } public IPBEStatCollection EffortValues { get; } public IPBEReadOnlyStatCollection IndividualValues { get; } public IPBEMoveset Moveset { get; } internal PBEReadOnlyPokemon(EndianBinaryReader r) { Species = r.ReadEnum(); Form = r.ReadEnum(); Nickname = r.ReadString_NullTerminated(); Level = r.ReadByte(); EXP = r.ReadUInt32(); Friendship = r.ReadByte(); Shiny = r.ReadBoolean(); Pokerus = r.ReadBoolean(); Ability = r.ReadEnum(); Nature = r.ReadEnum(); CaughtBall = r.ReadEnum(); Gender = r.ReadEnum(); Item = r.ReadEnum(); EffortValues = new PBEStatCollection(r); IndividualValues = new PBEReadOnlyStatCollection(r); Moveset = new PBEReadOnlyMoveset(r); } } ================================================ FILE: PokemonBattleEngine/Data/ReadOnlyPokemonCollection.cs ================================================ using Kermalis.EndianBinaryIO; using System; using System.Collections; using System.Collections.Generic; namespace Kermalis.PokemonBattleEngine.Data; public sealed class PBEReadOnlyPokemonCollection : IPBEPokemonCollection, IPBEPokemonCollection { private readonly PBEReadOnlyPokemon[] _list; public int Count => _list.Length; public PBEReadOnlyPokemon this[int index] { get { if (index >= _list.Length) { throw new ArgumentOutOfRangeException(nameof(index)); } return _list[index]; } } IPBEPokemon IReadOnlyList.this[int index] => this[index]; internal PBEReadOnlyPokemonCollection(EndianBinaryReader r) { byte count = r.ReadByte(); _list = new PBEReadOnlyPokemon[count]; for (int i = 0; i < count; i++) { _list[i] = new PBEReadOnlyPokemon(r); } } IEnumerator IEnumerable.GetEnumerator() { return _list.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)_list).GetEnumerator(); } public IEnumerator GetEnumerator() { return ((IEnumerable)_list).GetEnumerator(); } } ================================================ FILE: PokemonBattleEngine/Data/Settings.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using System; using System.ComponentModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Data; #pragma warning disable CS0618 // Type or member is obsolete /// The various engine settings. public sealed class PBESettings : INotifyPropertyChanged { private void OnPropertyChanged(string property) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); } /// Fires whenever a property changes. public event PropertyChangedEventHandler? PropertyChanged; private bool _isReadOnly; /// Gets a value that indicates whether this object is read-only. public bool IsReadOnly { get => _isReadOnly; private set { if (_isReadOnly != value) { _isReadOnly = value; OnPropertyChanged(nameof(IsReadOnly)); } } } /// The default settings used in official games. public static PBESettings DefaultSettings { get; } static PBESettings() { DefaultSettings = new PBESettings(); DefaultSettings.MakeReadOnly(); } #region Properties /// The default value of . public const byte DefaultMaxLevel = 100; private byte _maxLevel = DefaultMaxLevel; /// The maximum level a Pokémon can be. Not used in stat/damage calculation. public byte MaxLevel { get => _maxLevel; set { ShouldNotBeReadOnly(); if (_maxLevel != value) { if (value < _minLevel) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(MaxLevel)} must be at least {nameof(MinLevel)} ({_minLevel})."); } _maxLevel = value; OnPropertyChanged(nameof(MaxLevel)); } } } /// The default value of . public const byte DefaultMinLevel = 1; private byte _minLevel = DefaultMinLevel; /// The minimum level a Pokémon can be. public byte MinLevel { get => _minLevel; set { ShouldNotBeReadOnly(); if (_minLevel != value) { if (value < 1 || value > _maxLevel) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(MinLevel)} must be at least 1 and cannot exceed {nameof(MaxLevel)} ({_maxLevel})."); } _minLevel = value; OnPropertyChanged(nameof(MinLevel)); } } } /// The default value of . public const byte DefaultMaxPartySize = 6; private byte _maxPartySize = DefaultMaxPartySize; /// The maximum amount of Pokémon each trainer can bring into a battle. public byte MaxPartySize { get => _maxPartySize; set { ShouldNotBeReadOnly(); if (_maxPartySize != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(MaxPartySize)} must be at least 1."); } _maxPartySize = value; OnPropertyChanged(nameof(MaxPartySize)); } } } /// The default value of . public const byte DefaultMaxPokemonNameLength = 10; private byte _maxPokemonNameLength = DefaultMaxPokemonNameLength; /// The maximum amount of characters a Pokémon nickname can have. public byte MaxPokemonNameLength { get => _maxPokemonNameLength; set { ShouldNotBeReadOnly(); if (_maxPokemonNameLength != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(MaxPokemonNameLength)} must be at least 1."); } _maxPokemonNameLength = value; OnPropertyChanged(nameof(MaxPokemonNameLength)); } } } /// The default value of . This value is different in non-English games. public const byte DefaultMaxTrainerNameLength = 7; private byte _maxTrainerNameLength = DefaultMaxTrainerNameLength; /// The maximum amount of characters a trainer's name can have. [Obsolete("Currently not used anywhere.")] public byte MaxTrainerNameLength { get => _maxTrainerNameLength; set { ShouldNotBeReadOnly(); if (_maxTrainerNameLength != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(MaxTrainerNameLength)} must be at least 1."); } _maxTrainerNameLength = value; OnPropertyChanged(nameof(MaxTrainerNameLength)); } } } /// The default value of . public const ushort DefaultMaxTotalEVs = 510; private ushort _maxTotalEVs = DefaultMaxTotalEVs; /// The maximum sum of a Pokémon's EVs. public ushort MaxTotalEVs { get => _maxTotalEVs; set { const int max = byte.MaxValue * 6; ShouldNotBeReadOnly(); if (_maxTotalEVs != value) { if (value > max) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(MaxTotalEVs)} must not exceed {max}."); } _maxTotalEVs = value; OnPropertyChanged(nameof(MaxTotalEVs)); } } } /// The default value of . public const byte DefaultMaxIVs = 31; private byte _maxIVs = DefaultMaxIVs; /// The maximum amount of IVs Pokémon can have in each stat. Raising this will not affect . public byte MaxIVs { get => _maxIVs; set { ShouldNotBeReadOnly(); if (_maxIVs != value) { _maxIVs = value; OnPropertyChanged(nameof(MaxIVs)); } } } /// The default value of . public const float DefaultNatureStatBoost = 0.1f; private float _natureStatBoost = DefaultNatureStatBoost; /// The amount of influence a Pokémon's has on its stats. public float NatureStatBoost { get => _natureStatBoost; set { ShouldNotBeReadOnly(); if (_natureStatBoost != value) { if (value < 0) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(NatureStatBoost)} must be at least 0."); } _natureStatBoost = value; OnPropertyChanged(nameof(NatureStatBoost)); } } } /// The default value of . public const sbyte DefaultMaxStatChange = 6; private sbyte _maxStatChange = DefaultMaxStatChange; /// The maximum change a stat can have in the negative and positive direction. public sbyte MaxStatChange { get => _maxStatChange; set { ShouldNotBeReadOnly(); if (_maxStatChange != value) { _maxStatChange = value; OnPropertyChanged(nameof(MaxStatChange)); } } } /// The default value of . public const byte DefaultNumMoves = 4; private byte _numMoves = DefaultNumMoves; /// The maximum amount of moves a specific Pokémon can remember at once. public byte NumMoves { get => _numMoves; set { ShouldNotBeReadOnly(); if (_numMoves != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(NumMoves)} must be at least 1."); } _numMoves = value; OnPropertyChanged(nameof(NumMoves)); } } } /// The default value of . public const byte DefaultPPMultiplier = 5; private byte _ppMultiplier = DefaultPPMultiplier; /// This affects the base PP of each move and the boost PP-Ups give. The formulas that determine PP are at and . public byte PPMultiplier { get => _ppMultiplier; set { ShouldNotBeReadOnly(); if (_ppMultiplier != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(PPMultiplier)} must be at least 1."); } _ppMultiplier = value; OnPropertyChanged(nameof(PPMultiplier)); } } } /// The default value of . public const byte DefaultMaxPPUps = 3; private byte _maxPPUps = DefaultMaxPPUps; /// The maximum amount of PP-Ups that can be used on each of a Pokémon's moves. public byte MaxPPUps { get => _maxPPUps; set { ShouldNotBeReadOnly(); if (_maxPPUps != value) { _maxPPUps = value; OnPropertyChanged(nameof(MaxPPUps)); } } } /// The default value of . public const float DefaultCritMultiplier = 2.0f; private float _critMultiplier = DefaultCritMultiplier; /// The damage boost awarded by critical hits. public float CritMultiplier { get => _critMultiplier; set { ShouldNotBeReadOnly(); if (_critMultiplier != value) { _critMultiplier = value; OnPropertyChanged(nameof(CritMultiplier)); } } } /// The default value of . public const byte DefaultConfusionMaxTurns = 4; private byte _confusionMaxTurns = DefaultConfusionMaxTurns; /// The maximum amount of turns a Pokémon can be . public byte ConfusionMaxTurns { get => _confusionMaxTurns; set { ShouldNotBeReadOnly(); if (_confusionMaxTurns != value) { if (value < _confusionMinTurns) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(ConfusionMaxTurns)} must be at least {nameof(ConfusionMinTurns)} ({_confusionMinTurns})."); } _confusionMaxTurns = value; OnPropertyChanged(nameof(ConfusionMaxTurns)); } } } /// The default value of . public const byte DefaultConfusionMinTurns = 1; private byte _confusionMinTurns = DefaultConfusionMinTurns; /// The minimum amount of turns a Pokémon can be . public byte ConfusionMinTurns { get => _confusionMinTurns; set { ShouldNotBeReadOnly(); if (_confusionMinTurns != value) { if (value > _confusionMaxTurns) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(ConfusionMinTurns)} cannot exceed {nameof(ConfusionMaxTurns)} ({_confusionMaxTurns})."); } _confusionMinTurns = value; OnPropertyChanged(nameof(ConfusionMinTurns)); } } } /// The default value of . public const byte DefaultSleepMaxTurns = 3; private byte _sleepMaxTurns = DefaultSleepMaxTurns; /// The maximum amount of turns a Pokémon can be . will always sleep for turns. public byte SleepMaxTurns { get => _sleepMaxTurns; set { ShouldNotBeReadOnly(); if (_sleepMaxTurns != value) { if (value < _sleepMinTurns) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(SleepMaxTurns)} must be at least {nameof(SleepMinTurns)} ({_sleepMinTurns})."); } _sleepMaxTurns = value; OnPropertyChanged(nameof(SleepMaxTurns)); } } } /// The default value of . public const byte DefaultSleepMinTurns = 1; private byte _sleepMinTurns = DefaultSleepMinTurns; /// The minimum amount of turns a Pokémon can be . will ignore this value and always sleep for turns. public byte SleepMinTurns { get => _sleepMinTurns; set { ShouldNotBeReadOnly(); if (_sleepMinTurns != value) { if (value > _sleepMaxTurns) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(SleepMinTurns)} cannot exceed {nameof(SleepMaxTurns)} ({_sleepMaxTurns})."); } _sleepMinTurns = value; OnPropertyChanged(nameof(SleepMinTurns)); } } } /// The default value of . public const byte DefaultBurnDamageDenominator = 8; private byte _burnDamageDenominator = DefaultBurnDamageDenominator; /// A Pokémon with loses (1/this) of its HP at the end of every turn. public byte BurnDamageDenominator { get => _burnDamageDenominator; set { ShouldNotBeReadOnly(); if (_burnDamageDenominator != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(BurnDamageDenominator)} must be at least 1."); } _burnDamageDenominator = value; OnPropertyChanged(nameof(BurnDamageDenominator)); } } } /// The default value of . public const byte DefaultPoisonDamageDenominator = 8; private byte _poisonDamageDenominator = DefaultPoisonDamageDenominator; /// A Pokémon with loses (1/this) of its HP at the end of every turn. public byte PoisonDamageDenominator { get => _poisonDamageDenominator; set { ShouldNotBeReadOnly(); if (_poisonDamageDenominator != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(PoisonDamageDenominator)} must be at least 1."); } _poisonDamageDenominator = value; OnPropertyChanged(nameof(PoisonDamageDenominator)); } } } /// The default value of . public const byte DefaultToxicDamageDenominator = 16; private byte _toxicDamageDenominator = DefaultToxicDamageDenominator; /// A Pokémon with loses (/this) of its HP at the end of every turn. public byte ToxicDamageDenominator { get => _toxicDamageDenominator; set { ShouldNotBeReadOnly(); if (_toxicDamageDenominator != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(ToxicDamageDenominator)} must be at least 1."); } _toxicDamageDenominator = value; OnPropertyChanged(nameof(ToxicDamageDenominator)); } } } /// The default value of . public const byte DefaultLeechSeedDenominator = 8; private byte _leechSeedDenominator = DefaultLeechSeedDenominator; /// A Pokémon with loses (1/this) of its HP at the end of every turn and the Pokémon at on restores the lost HP. public byte LeechSeedDenominator { get => _leechSeedDenominator; set { ShouldNotBeReadOnly(); if (_leechSeedDenominator != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(LeechSeedDenominator)} must be at least 1."); } _leechSeedDenominator = value; OnPropertyChanged(nameof(LeechSeedDenominator)); } } } /// The default value of . public const byte DefaultCurseDenominator = 4; private byte _curseDenominator = DefaultCurseDenominator; /// A Pokémon with loses (1/this) of its HP at the end of every turn. public byte CurseDenominator { get => _curseDenominator; set { ShouldNotBeReadOnly(); if (_curseDenominator != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(CurseDenominator)} must be at least 1."); } _curseDenominator = value; OnPropertyChanged(nameof(CurseDenominator)); } } } /// The default value of . public const byte DefaultLeftoversHealDenominator = 16; private byte _leftoversHealDenominator = DefaultLeftoversHealDenominator; /// A Pokémon holding a restores (1/this) of its HP at the end of every turn. public byte LeftoversHealDenominator { get => _leftoversHealDenominator; set { ShouldNotBeReadOnly(); if (_leftoversHealDenominator != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(LeftoversHealDenominator)} must be at least 1."); } _leftoversHealDenominator = value; OnPropertyChanged(nameof(LeftoversHealDenominator)); } } } /// The default value of . public const byte DefaultBlackSludgeDamageDenominator = 8; private byte _blackSludgeDamageDenominator = DefaultBlackSludgeDamageDenominator; /// A Pokémon holding a without loses (1/this) of its HP at the end of every turn. public byte BlackSludgeDamageDenominator { get => _blackSludgeDamageDenominator; set { ShouldNotBeReadOnly(); if (_blackSludgeDamageDenominator != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(BlackSludgeDamageDenominator)} must be at least 1."); } _blackSludgeDamageDenominator = value; OnPropertyChanged(nameof(BlackSludgeDamageDenominator)); } } } /// The default value of . public const byte DefaultBlackSludgeHealDenominator = 16; private byte _blackSludgeHealDenominator = DefaultBlackSludgeHealDenominator; /// A Pokémon holding a with restores (1/this) of its HP at the end of every turn. public byte BlackSludgeHealDenominator { get => _blackSludgeHealDenominator; set { ShouldNotBeReadOnly(); if (_blackSludgeHealDenominator != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(BlackSludgeHealDenominator)} must be at least 1."); } _blackSludgeHealDenominator = value; OnPropertyChanged(nameof(BlackSludgeHealDenominator)); } } } /// The default value of . public const byte DefaultReflectTurns = 5; private byte _reflectTurns = DefaultReflectTurns; /// The amount of turns lasts. public byte ReflectTurns { get => _reflectTurns; set { ShouldNotBeReadOnly(); if (_reflectTurns != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(ReflectTurns)} must be at least 1."); } _reflectTurns = value; OnPropertyChanged(nameof(ReflectTurns)); } } } /// The default value of . public const byte DefaultLightScreenTurns = 5; private byte _lightScreenTurns = DefaultLightScreenTurns; /// The amount of turns lasts. public byte LightScreenTurns { get => _lightScreenTurns; set { ShouldNotBeReadOnly(); if (_lightScreenTurns != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(LightScreenTurns)} must be at least 1."); } _lightScreenTurns = value; OnPropertyChanged(nameof(LightScreenTurns)); } } } /// The default value of . public const byte DefaultLightClayTurnExtension = 3; private byte _lightClayTurnExtension = DefaultLightClayTurnExtension; /// The amount of turns added to and when the user is holding a . public byte LightClayTurnExtension { get => _lightClayTurnExtension; set { ShouldNotBeReadOnly(); if (_lightClayTurnExtension != value) { _lightClayTurnExtension = value; OnPropertyChanged(nameof(LightClayTurnExtension)); } } } /// The default value of . public const byte DefaultHailTurns = 5; private byte _hailTurns = DefaultHailTurns; /// The amount of turns lasts. For infinite turns, set to 0 first, then this to 0. public byte HailTurns { get => _hailTurns; set { ShouldNotBeReadOnly(); if (_hailTurns != value) { if (value == 0 && _icyRockTurnExtension != 0) { throw new ArgumentOutOfRangeException(nameof(value), $"For infinite turns, set {nameof(IcyRockTurnExtension)} to 0 first, then {nameof(HailTurns)} to 0."); } _hailTurns = value; OnPropertyChanged(nameof(HailTurns)); } } } /// The default value of . public const byte DefaultHailDamageDenominator = 16; private byte _hailDamageDenominator = DefaultHailDamageDenominator; /// A Pokémon in loses (1/this) of its HP at the end of every turn. public byte HailDamageDenominator { get => _hailDamageDenominator; set { ShouldNotBeReadOnly(); if (_hailDamageDenominator != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(HailDamageDenominator)} must be at least 1."); } _hailDamageDenominator = value; OnPropertyChanged(nameof(HailDamageDenominator)); } } } /// The default value of . public const byte DefaultIcyRockTurnExtension = 3; private byte _icyRockTurnExtension = DefaultIcyRockTurnExtension; /// The amount of turns added to when the user is holding a . If is 0 (infinite turns), this must also be 0. public byte IcyRockTurnExtension { get => _icyRockTurnExtension; set { ShouldNotBeReadOnly(); if (_icyRockTurnExtension != value) { if (value != 0 && _hailTurns == 0) { throw new ArgumentOutOfRangeException(nameof(value), $"If {nameof(HailTurns)} is 0 (infinite turns), {nameof(IcyRockTurnExtension)} must also be 0."); } _icyRockTurnExtension = value; OnPropertyChanged(nameof(IcyRockTurnExtension)); } } } /// The default value of . public const byte DefaultIceBodyHealDenominator = 16; private byte _iceBodyHealDenominator = DefaultIceBodyHealDenominator; /// A Pokémon with in restores (1/this) of its HP at the end of every turn. public byte IceBodyHealDenominator { get => _iceBodyHealDenominator; set { ShouldNotBeReadOnly(); if (_iceBodyHealDenominator != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(IceBodyHealDenominator)} must be at least 1."); } _iceBodyHealDenominator = value; OnPropertyChanged(nameof(IceBodyHealDenominator)); } } } /// The default value of . public const byte DefaultRainTurns = 5; private byte _rainTurns = DefaultRainTurns; /// The amount of turns lasts. For infinite turns, set to 0 first, then this to 0. public byte RainTurns { get => _rainTurns; set { ShouldNotBeReadOnly(); if (_rainTurns != value) { if (value == 0 && _dampRockTurnExtension != 0) { throw new ArgumentOutOfRangeException(nameof(value), $"For infinite turns, set {nameof(DampRockTurnExtension)} to 0 first, then {nameof(RainTurns)} to 0."); } _rainTurns = value; OnPropertyChanged(nameof(RainTurns)); } } } /// The default value of . public const byte DefaultDampRockTurnExtension = 3; private byte _dampRockTurnExtension = DefaultDampRockTurnExtension; /// The amount of turns added to when the user is holding a . If is 0 (infinite turns), this must also be 0. public byte DampRockTurnExtension { get => _dampRockTurnExtension; set { ShouldNotBeReadOnly(); if (_dampRockTurnExtension != value) { if (value != 0 && _rainTurns == 0) { throw new ArgumentOutOfRangeException(nameof(value), $"If {nameof(RainTurns)} is 0 (infinite turns), {nameof(DampRockTurnExtension)} must also be 0."); } _dampRockTurnExtension = value; OnPropertyChanged(nameof(DampRockTurnExtension)); } } } /// The default value of . public const byte DefaultSandstormTurns = 5; private byte _sandstormTurns = DefaultSandstormTurns; /// The amount of turns lasts. For infinite turns, set to 0 first, then this to 0. public byte SandstormTurns { get => _sandstormTurns; set { ShouldNotBeReadOnly(); if (_sandstormTurns != value) { if (value == 0 && _smoothRockTurnExtension != 0) { throw new ArgumentOutOfRangeException(nameof(value), $"For infinite turns, set {nameof(SmoothRockTurnExtension)} to 0 first, then {nameof(SandstormTurns)} to 0."); } _sandstormTurns = value; OnPropertyChanged(nameof(SandstormTurns)); } } } /// The default value of . public const byte DefaultSandstormDamageDenominator = 16; private byte _sandstormDamageDenominator = DefaultSandstormDamageDenominator; /// A Pokémon in loses (1/this) of its HP at the end of every turn. public byte SandstormDamageDenominator { get => _sandstormDamageDenominator; set { ShouldNotBeReadOnly(); if (_sandstormDamageDenominator != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(SandstormDamageDenominator)} must be at least 1."); } _sandstormDamageDenominator = value; OnPropertyChanged(nameof(SandstormDamageDenominator)); } } } /// The default value of . public const byte DefaultSmoothRockTurnExtension = 3; private byte _smoothRockTurnExtension = DefaultSmoothRockTurnExtension; /// The amount of turns added to when the user is holding a . If is 0 (infinite turns), this must also be 0. public byte SmoothRockTurnExtension { get => _smoothRockTurnExtension; set { ShouldNotBeReadOnly(); if (_smoothRockTurnExtension != value) { if (value != 0 && _sandstormTurns == 0) { throw new ArgumentOutOfRangeException(nameof(value), $"If {nameof(SandstormTurns)} is 0 (infinite turns), {nameof(SmoothRockTurnExtension)} must also be 0."); } _smoothRockTurnExtension = value; OnPropertyChanged(nameof(SmoothRockTurnExtension)); } } } /// The default value of . public const byte DefaultSunTurns = 5; private byte _sunTurns = DefaultSunTurns; /// The amount of turns lasts. For infinite turns, set to 0 first, then this to 0. public byte SunTurns { get => _sunTurns; set { ShouldNotBeReadOnly(); if (_sunTurns != value) { if (value == 0 && _heatRockTurnExtension != 0) { throw new ArgumentOutOfRangeException(nameof(value), $"For infinite turns, set {nameof(HeatRockTurnExtension)} to 0 first, then {nameof(SunTurns)} to 0."); } _sunTurns = value; OnPropertyChanged(nameof(SunTurns)); } } } /// The default value of . public const byte DefaultHeatRockTurnExtension = 3; private byte _heatRockTurnExtension = DefaultHeatRockTurnExtension; /// The amount of turns added to when the user is holding a . If is 0 (infinite turns), this must also be 0. public byte HeatRockTurnExtension { get => _heatRockTurnExtension; set { ShouldNotBeReadOnly(); if (_heatRockTurnExtension != value) { if (value != 0 && _sunTurns == 0) { throw new ArgumentOutOfRangeException(nameof(value), $"If {nameof(SunTurns)} is 0 (infinite turns), {nameof(HeatRockTurnExtension)} must also be 0."); } _heatRockTurnExtension = value; OnPropertyChanged(nameof(HeatRockTurnExtension)); } } } /// The default value of . public const byte DefaultHiddenPowerMax = 70; private byte _hiddenPowerMax = DefaultHiddenPowerMax; /// The maximum base power of . public byte HiddenPowerMax { get => _hiddenPowerMax; set { ShouldNotBeReadOnly(); if (_hiddenPowerMax != value) { if (value < _hiddenPowerMin) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(HiddenPowerMax)} must be at least {nameof(HiddenPowerMin)} ({_hiddenPowerMin})."); } _hiddenPowerMax = value; OnPropertyChanged(nameof(HiddenPowerMax)); } } } /// The default value of . public const byte DefaultHiddenPowerMin = 30; private byte _hiddenPowerMin = DefaultHiddenPowerMin; /// The minimum base power of . public byte HiddenPowerMin { get => _hiddenPowerMin; set { ShouldNotBeReadOnly(); if (_hiddenPowerMin != value) { if (value == 0 || value > _hiddenPowerMax) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(HiddenPowerMin)} must be at least 1 and cannot exceed {nameof(HiddenPowerMax)} ({_hiddenPowerMax})."); } _hiddenPowerMin = value; OnPropertyChanged(nameof(HiddenPowerMin)); } } } /// The default value of . public const bool DefaultBugFix = false; private bool _bugFix = DefaultBugFix; /// Whether bugfixes should be applied or not. public bool BugFix { get => _bugFix; set { ShouldNotBeReadOnly(); if (_bugFix != value) { _bugFix = value; OnPropertyChanged(nameof(BugFix)); } } } #endregion /// Creates a new object where every setting is pre-set to the values used in official games. public PBESettings() { } /// Creates a new object with the specified code . /// The code to use. public PBESettings(string code) { using (var ms = new MemoryStream(Convert.FromBase64String(code))) { FromBytes(new EndianBinaryReader(ms)); } } /// Creates a new object which copies the settings from the specified object. and are not copied. /// The object to copy settings from. public PBESettings(PBESettings other) { other.ShouldBeReadOnly(nameof(other)); MaxLevel = other._maxLevel; MinLevel = other._minLevel; MaxPartySize = other._maxPartySize; MaxPokemonNameLength = other._maxPokemonNameLength; MaxTrainerNameLength = other._maxTrainerNameLength; MaxTotalEVs = other._maxTotalEVs; MaxIVs = other._maxIVs; NatureStatBoost = other._natureStatBoost; MaxStatChange = other._maxStatChange; NumMoves = other._numMoves; PPMultiplier = other._ppMultiplier; MaxPPUps = other._maxPPUps; CritMultiplier = other._critMultiplier; ConfusionMaxTurns = other._confusionMaxTurns; ConfusionMinTurns = other._confusionMinTurns; SleepMaxTurns = other._sleepMaxTurns; SleepMinTurns = other._sleepMinTurns; BurnDamageDenominator = other._burnDamageDenominator; PoisonDamageDenominator = other._poisonDamageDenominator; ToxicDamageDenominator = other._toxicDamageDenominator; LeechSeedDenominator = other._leechSeedDenominator; CurseDenominator = other._curseDenominator; LeftoversHealDenominator = other._leftoversHealDenominator; BlackSludgeDamageDenominator = other._blackSludgeDamageDenominator; BlackSludgeHealDenominator = other._blackSludgeHealDenominator; ReflectTurns = other._reflectTurns; LightScreenTurns = other._lightScreenTurns; LightClayTurnExtension = other._lightClayTurnExtension; HailTurns = other._hailTurns; HailDamageDenominator = other._hailDamageDenominator; IcyRockTurnExtension = other._icyRockTurnExtension; IceBodyHealDenominator = other._iceBodyHealDenominator; RainTurns = other._rainTurns; DampRockTurnExtension = other._dampRockTurnExtension; SandstormTurns = other._sandstormTurns; SandstormDamageDenominator = other._sandstormDamageDenominator; SmoothRockTurnExtension = other._smoothRockTurnExtension; SunTurns = other._sunTurns; HeatRockTurnExtension = other._heatRockTurnExtension; HiddenPowerMax = other._hiddenPowerMax; HiddenPowerMin = other._hiddenPowerMin; BugFix = other._bugFix; } public PBESettings(EndianBinaryReader r) { FromBytes(r); } private void ShouldNotBeReadOnly() { if (_isReadOnly) { throw new InvalidOperationException($"This {nameof(PBESettings)} is marked as read-only."); } } public void ShouldBeReadOnly(string nameOf) { if (!_isReadOnly) { throw new ArgumentException("Settings must be read-only.", nameOf); } } /// Marks this object as read-only and clears . public void MakeReadOnly() { if (!_isReadOnly) { IsReadOnly = true; OnPropertyChanged(nameof(IsReadOnly)); PropertyChanged = null; } } public override int GetHashCode() { var hash = new HashCode(); hash.Add(_maxLevel); hash.Add(_minLevel); hash.Add(_maxPartySize); hash.Add(_maxPokemonNameLength); hash.Add(_maxTrainerNameLength); hash.Add(_maxTotalEVs); hash.Add(_maxIVs); hash.Add(_natureStatBoost); hash.Add(_maxStatChange); hash.Add(_numMoves); hash.Add(_ppMultiplier); hash.Add(_maxPPUps); hash.Add(_critMultiplier); hash.Add(_confusionMaxTurns); hash.Add(_confusionMinTurns); hash.Add(_sleepMaxTurns); hash.Add(_sleepMinTurns); hash.Add(_burnDamageDenominator); hash.Add(_poisonDamageDenominator); hash.Add(_toxicDamageDenominator); hash.Add(_leechSeedDenominator); hash.Add(_curseDenominator); hash.Add(_leftoversHealDenominator); hash.Add(_blackSludgeDamageDenominator); hash.Add(_blackSludgeHealDenominator); hash.Add(_reflectTurns); hash.Add(_lightScreenTurns); hash.Add(_lightClayTurnExtension); hash.Add(_hailTurns); hash.Add(_hailDamageDenominator); hash.Add(_icyRockTurnExtension); hash.Add(_iceBodyHealDenominator); hash.Add(_rainTurns); hash.Add(_dampRockTurnExtension); hash.Add(_sandstormTurns); hash.Add(_sandstormDamageDenominator); hash.Add(_smoothRockTurnExtension); hash.Add(_sunTurns); hash.Add(_heatRockTurnExtension); hash.Add(_hiddenPowerMax); hash.Add(_hiddenPowerMin); hash.Add(_bugFix); return hash.ToHashCode(); } /// Returns a value indicating whether a code or another object represents the same settings as this object. /// The code or the object to check for equality. public override bool Equals(object? obj) { if (obj is null) { return false; } if (ReferenceEquals(obj, this)) { return true; } if (obj is string str) { PBESettings ps; try { ps = new PBESettings(str); } catch { return false; } return ps.Equals(this); } if (obj is PBESettings other) { return other._maxLevel.Equals(_maxLevel) && other._minLevel.Equals(_minLevel) && other._maxPartySize.Equals(_maxPartySize) && other._maxPokemonNameLength.Equals(_maxPokemonNameLength) && other._maxTrainerNameLength.Equals(_maxTrainerNameLength) && other._maxTotalEVs.Equals(_maxTotalEVs) && other._maxIVs.Equals(_maxIVs) && other._natureStatBoost.Equals(_natureStatBoost) && other._maxStatChange.Equals(_maxStatChange) && other._numMoves.Equals(_numMoves) && other._ppMultiplier.Equals(_ppMultiplier) && other._maxPPUps.Equals(_maxPPUps) && other._critMultiplier.Equals(_critMultiplier) && other._confusionMaxTurns.Equals(_confusionMaxTurns) && other._confusionMinTurns.Equals(_confusionMinTurns) && other._sleepMaxTurns.Equals(_sleepMaxTurns) && other._sleepMinTurns.Equals(_sleepMinTurns) && other._burnDamageDenominator.Equals(_burnDamageDenominator) && other._poisonDamageDenominator.Equals(_poisonDamageDenominator) && other._toxicDamageDenominator.Equals(_toxicDamageDenominator) && other._leechSeedDenominator.Equals(_leechSeedDenominator) && other._curseDenominator.Equals(_curseDenominator) && other._leftoversHealDenominator.Equals(_leftoversHealDenominator) && other._blackSludgeDamageDenominator.Equals(_blackSludgeDamageDenominator) && other._blackSludgeHealDenominator.Equals(_blackSludgeHealDenominator) && other._reflectTurns.Equals(_reflectTurns) && other._lightScreenTurns.Equals(_lightScreenTurns) && other._lightClayTurnExtension.Equals(_lightClayTurnExtension) && other._hailTurns.Equals(_hailTurns) && other._hailDamageDenominator.Equals(_hailDamageDenominator) && other._icyRockTurnExtension.Equals(_icyRockTurnExtension) && other._iceBodyHealDenominator.Equals(_iceBodyHealDenominator) && other._rainTurns.Equals(_rainTurns) && other._dampRockTurnExtension.Equals(_dampRockTurnExtension) && other._sandstormTurns.Equals(_sandstormTurns) && other._sandstormDamageDenominator.Equals(_sandstormDamageDenominator) && other._smoothRockTurnExtension.Equals(_smoothRockTurnExtension) && other._sunTurns.Equals(_sunTurns) && other._heatRockTurnExtension.Equals(_heatRockTurnExtension) && other._hiddenPowerMax.Equals(_hiddenPowerMax) && other._hiddenPowerMin.Equals(_hiddenPowerMin) && other._bugFix.Equals(_bugFix); } return false; } private enum PBESettingID : ushort { MaxLevel, MinLevel, MaxPartySize, MaxPokemonNameLength, MaxTrainerNameLength, MaxTotalEVs, MaxIVs, NatureStatBoost, MaxStatChange, NumMoves, PPMultiplier, MaxPPUps, CritMultiplier, ConfusionMaxTurns, ConfusionMinTurns, SleepMaxTurns, SleepMinTurns, BurnDamageDenominator, PoisonDamageDenominator, ToxicDamageDenominator, LeechSeedDenominator, CurseDenominator, LeftoversHealDenominator, BlackSludgeDamageDenominator, BlackSludgeHealDenominator, ReflectTurns, LightScreenTurns, LightClayTurnExtension, HailTurns, HailDamageDenominator, IcyRockTurnExtension, IceBodyHealDenominator, RainTurns, DampRockTurnExtension, SandstormTurns, SandstormDamageDenominator, SmoothRockTurnExtension, SunTurns, HeatRockTurnExtension, HiddenPowerMax, HiddenPowerMin, BugFix } /// Converts this object into a unique code . public override string ToString() { return Convert.ToBase64String(ToBytes()); } public byte[] ToBytes() { byte[] data; ushort numChanged = 0; using (var ms = new MemoryStream()) { var w = new EndianBinaryWriter(ms); if (_maxLevel != DefaultMaxLevel) { w.WriteEnum(PBESettingID.MaxLevel); w.WriteByte(_maxLevel); numChanged++; } if (_minLevel != DefaultMinLevel) { w.WriteEnum(PBESettingID.MinLevel); w.WriteByte(_minLevel); numChanged++; } if (_maxPartySize != DefaultMaxPartySize) { w.WriteEnum(PBESettingID.MaxPartySize); w.WriteByte(_maxPartySize); numChanged++; } if (_maxPokemonNameLength != DefaultMaxPokemonNameLength) { w.WriteEnum(PBESettingID.MaxPokemonNameLength); w.WriteByte(_maxPokemonNameLength); numChanged++; } if (_maxTrainerNameLength != DefaultMaxTrainerNameLength) { w.WriteEnum(PBESettingID.MaxTrainerNameLength); w.WriteByte(_maxTrainerNameLength); numChanged++; } if (_maxTotalEVs != DefaultMaxTotalEVs) { w.WriteEnum(PBESettingID.MaxTotalEVs); w.WriteUInt16(_maxTotalEVs); numChanged++; } if (_maxIVs != DefaultMaxIVs) { w.WriteEnum(PBESettingID.MaxIVs); w.WriteByte(_maxIVs); numChanged++; } if (_natureStatBoost != DefaultNatureStatBoost) { w.WriteEnum(PBESettingID.NatureStatBoost); w.WriteSingle(_natureStatBoost); numChanged++; } if (_maxStatChange != DefaultMaxStatChange) { w.WriteEnum(PBESettingID.MaxStatChange); w.WriteSByte(_maxStatChange); numChanged++; } if (_numMoves != DefaultNumMoves) { w.WriteEnum(PBESettingID.NumMoves); w.WriteByte(_numMoves); numChanged++; } if (_ppMultiplier != DefaultPPMultiplier) { w.WriteEnum(PBESettingID.PPMultiplier); w.WriteByte(_ppMultiplier); numChanged++; } if (_maxPPUps != DefaultMaxPPUps) { w.WriteEnum(PBESettingID.MaxPPUps); w.WriteByte(_maxPPUps); numChanged++; } if (_critMultiplier != DefaultCritMultiplier) { w.WriteEnum(PBESettingID.CritMultiplier); w.WriteSingle(_critMultiplier); numChanged++; } if (_confusionMaxTurns != DefaultConfusionMaxTurns) { w.WriteEnum(PBESettingID.ConfusionMaxTurns); w.WriteByte(_confusionMaxTurns); numChanged++; } if (_confusionMinTurns != DefaultConfusionMinTurns) { w.WriteEnum(PBESettingID.ConfusionMinTurns); w.WriteByte(_confusionMinTurns); numChanged++; } if (_sleepMaxTurns != DefaultSleepMaxTurns) { w.WriteEnum(PBESettingID.SleepMaxTurns); w.WriteByte(_sleepMaxTurns); numChanged++; } if (_sleepMinTurns != DefaultSleepMinTurns) { w.WriteEnum(PBESettingID.SleepMinTurns); w.WriteByte(_sleepMinTurns); numChanged++; } if (_burnDamageDenominator != DefaultBurnDamageDenominator) { w.WriteEnum(PBESettingID.BurnDamageDenominator); w.WriteByte(_burnDamageDenominator); numChanged++; } if (_poisonDamageDenominator != DefaultPoisonDamageDenominator) { w.WriteEnum(PBESettingID.PoisonDamageDenominator); w.WriteByte(_poisonDamageDenominator); numChanged++; } if (_toxicDamageDenominator != DefaultToxicDamageDenominator) { w.WriteEnum(PBESettingID.ToxicDamageDenominator); w.WriteByte(_toxicDamageDenominator); numChanged++; } if (_leechSeedDenominator != DefaultLeechSeedDenominator) { w.WriteEnum(PBESettingID.LeechSeedDenominator); w.WriteByte(_leechSeedDenominator); numChanged++; } if (_curseDenominator != DefaultCurseDenominator) { w.WriteEnum(PBESettingID.CurseDenominator); w.WriteByte(_curseDenominator); numChanged++; } if (_leftoversHealDenominator != DefaultLeftoversHealDenominator) { w.WriteEnum(PBESettingID.LeftoversHealDenominator); w.WriteByte(_leftoversHealDenominator); numChanged++; } if (_blackSludgeDamageDenominator != DefaultBlackSludgeDamageDenominator) { w.WriteEnum(PBESettingID.BlackSludgeDamageDenominator); w.WriteByte(_blackSludgeDamageDenominator); numChanged++; } if (_blackSludgeHealDenominator != DefaultBlackSludgeHealDenominator) { w.WriteEnum(PBESettingID.BlackSludgeHealDenominator); w.WriteByte(_blackSludgeHealDenominator); numChanged++; } if (_reflectTurns != DefaultReflectTurns) { w.WriteEnum(PBESettingID.ReflectTurns); w.WriteByte(_reflectTurns); numChanged++; } if (_lightScreenTurns != DefaultLightScreenTurns) { w.WriteEnum(PBESettingID.LightScreenTurns); w.WriteByte(_lightScreenTurns); numChanged++; } if (_lightClayTurnExtension != DefaultLightClayTurnExtension) { w.WriteEnum(PBESettingID.LightClayTurnExtension); w.WriteByte(_lightClayTurnExtension); numChanged++; } if (_hailTurns != DefaultHailTurns) { w.WriteEnum(PBESettingID.HailTurns); w.WriteByte(_hailTurns); numChanged++; } if (_hailDamageDenominator != DefaultHailDamageDenominator) { w.WriteEnum(PBESettingID.HailDamageDenominator); w.WriteByte(_hailDamageDenominator); numChanged++; } if (_icyRockTurnExtension != DefaultIcyRockTurnExtension) { w.WriteEnum(PBESettingID.IcyRockTurnExtension); w.WriteByte(_icyRockTurnExtension); numChanged++; } if (_iceBodyHealDenominator != DefaultIceBodyHealDenominator) { w.WriteEnum(PBESettingID.IceBodyHealDenominator); w.WriteByte(_iceBodyHealDenominator); numChanged++; } if (_rainTurns != DefaultRainTurns) { w.WriteEnum(PBESettingID.RainTurns); w.WriteByte(_rainTurns); numChanged++; } if (_dampRockTurnExtension != DefaultDampRockTurnExtension) { w.WriteEnum(PBESettingID.DampRockTurnExtension); w.WriteByte(_dampRockTurnExtension); numChanged++; } if (_sandstormTurns != DefaultSandstormTurns) { w.WriteEnum(PBESettingID.SandstormTurns); w.WriteByte(_sandstormTurns); numChanged++; } if (_sandstormDamageDenominator != DefaultSandstormDamageDenominator) { w.WriteEnum(PBESettingID.SandstormDamageDenominator); w.WriteByte(_sandstormDamageDenominator); numChanged++; } if (_smoothRockTurnExtension != DefaultSmoothRockTurnExtension) { w.WriteEnum(PBESettingID.SmoothRockTurnExtension); w.WriteByte(_smoothRockTurnExtension); numChanged++; } if (_sunTurns != DefaultSunTurns) { w.WriteEnum(PBESettingID.SunTurns); w.WriteByte(_sunTurns); numChanged++; } if (_heatRockTurnExtension != DefaultHeatRockTurnExtension) { w.WriteEnum(PBESettingID.HeatRockTurnExtension); w.WriteByte(_heatRockTurnExtension); numChanged++; } if (_hiddenPowerMax != DefaultHiddenPowerMax) { w.WriteEnum(PBESettingID.HiddenPowerMax); w.WriteByte(_hiddenPowerMax); numChanged++; } if (_hiddenPowerMin != DefaultHiddenPowerMin) { w.WriteEnum(PBESettingID.HiddenPowerMin); w.WriteByte(_hiddenPowerMin); numChanged++; } if (_bugFix != DefaultBugFix) { w.WriteEnum(PBESettingID.BugFix); w.WriteBoolean(_bugFix); numChanged++; } data = ms.ToArray(); } byte[] ret = new byte[data.Length + 2]; EndianBinaryPrimitives.WriteInt16(ret.AsSpan(0, 2), (short)numChanged, Endianness.LittleEndian); Array.Copy(data, 0, ret, 2, data.Length); return ret; } private void FromBytes(EndianBinaryReader r) { ushort numChanged = r.ReadUInt16(); for (ushort i = 0; i < numChanged; i++) { switch (r.ReadEnum()) { case PBESettingID.MaxLevel: MaxLevel = r.ReadByte(); break; case PBESettingID.MinLevel: MinLevel = r.ReadByte(); break; case PBESettingID.MaxPartySize: MaxPartySize = r.ReadByte(); break; case PBESettingID.MaxPokemonNameLength: MaxPokemonNameLength = r.ReadByte(); break; case PBESettingID.MaxTrainerNameLength: MaxTrainerNameLength = r.ReadByte(); break; case PBESettingID.MaxTotalEVs: MaxTotalEVs = r.ReadUInt16(); break; case PBESettingID.MaxIVs: MaxIVs = r.ReadByte(); break; case PBESettingID.NatureStatBoost: NatureStatBoost = r.ReadSingle(); break; case PBESettingID.MaxStatChange: MaxStatChange = r.ReadSByte(); break; case PBESettingID.NumMoves: NumMoves = r.ReadByte(); break; case PBESettingID.PPMultiplier: PPMultiplier = r.ReadByte(); break; case PBESettingID.MaxPPUps: MaxPPUps = r.ReadByte(); break; case PBESettingID.CritMultiplier: CritMultiplier = r.ReadSingle(); break; case PBESettingID.ConfusionMaxTurns: ConfusionMaxTurns = r.ReadByte(); break; case PBESettingID.ConfusionMinTurns: ConfusionMinTurns = r.ReadByte(); break; case PBESettingID.SleepMaxTurns: SleepMaxTurns = r.ReadByte(); break; case PBESettingID.SleepMinTurns: SleepMinTurns = r.ReadByte(); break; case PBESettingID.BurnDamageDenominator: BurnDamageDenominator = r.ReadByte(); break; case PBESettingID.PoisonDamageDenominator: PoisonDamageDenominator = r.ReadByte(); break; case PBESettingID.ToxicDamageDenominator: ToxicDamageDenominator = r.ReadByte(); break; case PBESettingID.LeechSeedDenominator: LeechSeedDenominator = r.ReadByte(); break; case PBESettingID.CurseDenominator: CurseDenominator = r.ReadByte(); break; case PBESettingID.LeftoversHealDenominator: LeftoversHealDenominator = r.ReadByte(); break; case PBESettingID.BlackSludgeDamageDenominator: BlackSludgeDamageDenominator = r.ReadByte(); break; case PBESettingID.BlackSludgeHealDenominator: BlackSludgeHealDenominator = r.ReadByte(); break; case PBESettingID.ReflectTurns: ReflectTurns = r.ReadByte(); break; case PBESettingID.LightScreenTurns: LightScreenTurns = r.ReadByte(); break; case PBESettingID.LightClayTurnExtension: LightClayTurnExtension = r.ReadByte(); break; case PBESettingID.HailTurns: HailTurns = r.ReadByte(); break; case PBESettingID.HailDamageDenominator: HailDamageDenominator = r.ReadByte(); break; case PBESettingID.IcyRockTurnExtension: IcyRockTurnExtension = r.ReadByte(); break; case PBESettingID.IceBodyHealDenominator: IceBodyHealDenominator = r.ReadByte(); break; case PBESettingID.RainTurns: RainTurns = r.ReadByte(); break; case PBESettingID.DampRockTurnExtension: DampRockTurnExtension = r.ReadByte(); break; case PBESettingID.SandstormTurns: SandstormTurns = r.ReadByte(); break; case PBESettingID.SandstormDamageDenominator: SandstormDamageDenominator = r.ReadByte(); break; case PBESettingID.SmoothRockTurnExtension: SmoothRockTurnExtension = r.ReadByte(); break; case PBESettingID.SunTurns: SunTurns = r.ReadByte(); break; case PBESettingID.HeatRockTurnExtension: HeatRockTurnExtension = r.ReadByte(); break; case PBESettingID.HiddenPowerMax: HiddenPowerMax = r.ReadByte(); break; case PBESettingID.HiddenPowerMin: HiddenPowerMin = r.ReadByte(); break; case PBESettingID.BugFix: BugFix = r.ReadBoolean(); break; default: throw new InvalidDataException(); } } } } #pragma warning restore CS0618 // Type or member is obsolete ================================================ FILE: PokemonBattleEngine/Data/StatCollection.cs ================================================ using Kermalis.EndianBinaryIO; namespace Kermalis.PokemonBattleEngine.Data; public sealed class PBEReadOnlyStatCollection : IPBEReadOnlyStatCollection { public byte HP { get; } public byte Attack { get; } public byte Defense { get; } public byte SpAttack { get; } public byte SpDefense { get; } public byte Speed { get; } public PBEReadOnlyStatCollection(byte hp, byte attack, byte defense, byte spAttack, byte spDefense, byte speed) { HP = hp; Attack = attack; Defense = defense; SpAttack = spAttack; SpDefense = spDefense; Speed = speed; } internal PBEReadOnlyStatCollection(EndianBinaryReader r) : this(r.ReadByte(), r.ReadByte(), r.ReadByte(), r.ReadByte(), r.ReadByte(), r.ReadByte()) { } public PBEReadOnlyStatCollection(IPBEReadOnlyStatCollection stats) : this(stats.HP, stats.Attack, stats.Defense, stats.SpAttack, stats.SpDefense, stats.Speed) { } public byte this[PBEStat stat] => this.GetStat(stat); } public sealed class PBEStatCollection : IPBEStatCollection { public byte HP { get; set; } public byte Attack { get; set; } public byte Defense { get; set; } public byte SpAttack { get; set; } public byte SpDefense { get; set; } public byte Speed { get; set; } public PBEStatCollection(byte hp, byte attack, byte defense, byte spAttack, byte spDefense, byte speed) { HP = hp; Attack = attack; Defense = defense; SpAttack = spAttack; SpDefense = spDefense; Speed = speed; } internal PBEStatCollection(EndianBinaryReader r) : this(r.ReadByte(), r.ReadByte(), r.ReadByte(), r.ReadByte(), r.ReadByte(), r.ReadByte()) { } public PBEStatCollection(IPBEReadOnlyStatCollection stats) : this(stats.HP, stats.Attack, stats.Defense, stats.SpAttack, stats.SpDefense, stats.Speed) { } public byte this[PBEStat stat] { get => this.GetStat(stat); set => this.SetStat(stat, value); } } ================================================ FILE: PokemonBattleEngine/Data/Utils/DataUtils_Effects.cs ================================================ namespace Kermalis.PokemonBattleEngine.Data.Utils; public static partial class PBEDataUtils { #region Static Collections public static PBEAlphabeticalList MoodyStats { get; } = new(new[] { PBEStat.Attack, PBEStat.Defense, PBEStat.SpAttack, PBEStat.SpDefense, PBEStat.Speed, PBEStat.Accuracy, PBEStat.Evasion }); public static PBEAlphabeticalList StarfBerryStats { get; } = new(new[] { PBEStat.Attack, PBEStat.Defense, PBEStat.SpAttack, PBEStat.SpDefense, PBEStat.Speed }); #endregion } ================================================ FILE: PokemonBattleEngine/Data/Utils/DataUtils_Forms.cs ================================================ using Kermalis.PokemonBattleEngine.Utils; using System; using System.Collections.Generic; namespace Kermalis.PokemonBattleEngine.Data.Utils; public static partial class PBEDataUtils { #region Static Collections public static PBEAlphabeticalList AllSpecies { get; } = new(Enum.GetValues().ExceptOne(PBESpecies.MAX)); public static PBEAlphabeticalList FullyEvolvedSpecies { get; } = new(AllSpecies.FindAll(s => !PBEDataProvider.Instance.HasEvolutions(s, 0))); #region Forms private static readonly PBEAlphabeticalList _arceus = new(new[] { PBEForm.Arceus, PBEForm.Arceus_Bug, PBEForm.Arceus_Dark, PBEForm.Arceus_Dragon, PBEForm.Arceus_Electric, PBEForm.Arceus_Fighting, PBEForm.Arceus_Fire, PBEForm.Arceus_Flying, PBEForm.Arceus_Ghost, PBEForm.Arceus_Grass, PBEForm.Arceus_Ground, PBEForm.Arceus_Ice, PBEForm.Arceus_Poison, PBEForm.Arceus_Psychic, PBEForm.Arceus_Rock, PBEForm.Arceus_Steel, PBEForm.Arceus_Water }, PBESpecies.Arceus); private static readonly PBEAlphabeticalList _basculin = new(new[] { PBEForm.Basculin_Blue, PBEForm.Basculin_Red }, PBESpecies.Basculin); private static readonly PBEAlphabeticalList _burmy = new(new[] { PBEForm.Burmy_Plant, PBEForm.Burmy_Sandy, PBEForm.Burmy_Trash }, PBESpecies.Burmy); private static readonly PBEAlphabeticalList _castform = new(new[] { PBEForm.Castform, PBEForm.Castform_Rainy, PBEForm.Castform_Snowy, PBEForm.Castform_Sunny }, PBESpecies.Castform); private static readonly PBEAlphabeticalList _cherrim = new(new[] { PBEForm.Cherrim, PBEForm.Cherrim_Sunshine }, PBESpecies.Cherrim); private static readonly PBEAlphabeticalList _darmanitan = new(new[] { PBEForm.Darmanitan, PBEForm.Darmanitan_Zen }, PBESpecies.Darmanitan); private static readonly PBEAlphabeticalList _deerling = new(new[] { PBEForm.Deerling_Autumn, PBEForm.Deerling_Spring, PBEForm.Deerling_Summer, PBEForm.Deerling_Winter }, PBESpecies.Deerling); private static readonly PBEAlphabeticalList _deoxys = new(new[] { PBEForm.Deoxys, PBEForm.Deoxys_Attack, PBEForm.Deoxys_Defense, PBEForm.Deoxys_Speed }, PBESpecies.Deoxys); private static readonly PBEAlphabeticalList _gastrodon = new(new[] { PBEForm.Gastrodon_East, PBEForm.Gastrodon_West }, PBESpecies.Gastrodon); private static readonly PBEAlphabeticalList _genesect = new(new[] { PBEForm.Genesect, PBEForm.Genesect_Burn, PBEForm.Genesect_Chill, PBEForm.Genesect_Douse, PBEForm.Genesect_Shock }, PBESpecies.Genesect); private static readonly PBEAlphabeticalList _giratina = new(new[] { PBEForm.Giratina, PBEForm.Giratina_Origin }, PBESpecies.Giratina); private static readonly PBEAlphabeticalList _keldeo = new(new[] { PBEForm.Keldeo, PBEForm.Keldeo_Resolute }, PBESpecies.Keldeo); private static readonly PBEAlphabeticalList _kyurem = new(new[] { PBEForm.Kyurem, PBEForm.Kyurem_Black, PBEForm.Kyurem_White }, PBESpecies.Kyurem); private static readonly PBEAlphabeticalList _landorus = new(new[] { PBEForm.Landorus, PBEForm.Landorus_Therian }, PBESpecies.Landorus); private static readonly PBEAlphabeticalList _meloetta = new(new[] { PBEForm.Meloetta, PBEForm.Meloetta_Pirouette }, PBESpecies.Meloetta); private static readonly PBEAlphabeticalList _rotom = new(new[] { PBEForm.Rotom, PBEForm.Rotom_Fan, PBEForm.Rotom_Frost, PBEForm.Rotom_Heat, PBEForm.Rotom_Mow, PBEForm.Rotom_Wash }, PBESpecies.Rotom); private static readonly PBEAlphabeticalList _sawsbuck = new(new[] { PBEForm.Sawsbuck_Autumn, PBEForm.Sawsbuck_Spring, PBEForm.Sawsbuck_Summer, PBEForm.Sawsbuck_Winter }, PBESpecies.Sawsbuck); private static readonly PBEAlphabeticalList _shaymin = new(new[] { PBEForm.Shaymin, PBEForm.Shaymin_Sky }, PBESpecies.Shaymin); private static readonly PBEAlphabeticalList _shellos = new(new[] { PBEForm.Shellos_East, PBEForm.Shellos_West }, PBESpecies.Shellos); private static readonly PBEAlphabeticalList _thundurus = new(new[] { PBEForm.Thundurus, PBEForm.Thundurus_Therian }, PBESpecies.Thundurus); private static readonly PBEAlphabeticalList _tornadus = new(new[] { PBEForm.Tornadus, PBEForm.Tornadus_Therian }, PBESpecies.Tornadus); private static readonly PBEAlphabeticalList _unown = new(new[] { PBEForm.Unown_A, PBEForm.Unown_B, PBEForm.Unown_C, PBEForm.Unown_D, PBEForm.Unown_E, PBEForm.Unown_Exclamation, PBEForm.Unown_F, PBEForm.Unown_G, PBEForm.Unown_H, PBEForm.Unown_I, PBEForm.Unown_J, PBEForm.Unown_K, PBEForm.Unown_L, PBEForm.Unown_M, PBEForm.Unown_N, PBEForm.Unown_O, PBEForm.Unown_P, PBEForm.Unown_Q, PBEForm.Unown_Question, PBEForm.Unown_R, PBEForm.Unown_S, PBEForm.Unown_T, PBEForm.Unown_U, PBEForm.Unown_V, PBEForm.Unown_W, PBEForm.Unown_X, PBEForm.Unown_Y, PBEForm.Unown_Z }, PBESpecies.Unown); private static readonly PBEAlphabeticalList _wormadam = new(new[] { PBEForm.Wormadam_Plant, PBEForm.Wormadam_Sandy, PBEForm.Wormadam_Trash }, PBESpecies.Wormadam); #endregion #endregion public static bool CanChangeForm(PBESpecies species, bool requireUsableOutsideOfBattle) { if (species <= 0 || species >= PBESpecies.MAX) { throw new ArgumentOutOfRangeException(nameof(species)); } switch (species) { case PBESpecies.Arceus: case PBESpecies.Burmy: case PBESpecies.Deerling: case PBESpecies.Deoxys: case PBESpecies.Genesect: case PBESpecies.Giratina: case PBESpecies.Keldeo: case PBESpecies.Kyurem: case PBESpecies.Landorus: case PBESpecies.Rotom: case PBESpecies.Sawsbuck: case PBESpecies.Shaymin: case PBESpecies.Thundurus: case PBESpecies.Tornadus: return true; case PBESpecies.Castform: case PBESpecies.Cherrim: case PBESpecies.Darmanitan: case PBESpecies.Meloetta: return !requireUsableOutsideOfBattle; default: return false; } } public static bool HasForms(PBESpecies species, bool requireUsableOutsideOfBattle) { if (species <= 0 || species >= PBESpecies.MAX) { throw new ArgumentOutOfRangeException(nameof(species)); } switch (species) { case PBESpecies.Arceus: case PBESpecies.Basculin: case PBESpecies.Burmy: case PBESpecies.Deerling: case PBESpecies.Deoxys: case PBESpecies.Gastrodon: case PBESpecies.Genesect: case PBESpecies.Giratina: case PBESpecies.Keldeo: case PBESpecies.Kyurem: case PBESpecies.Landorus: case PBESpecies.Rotom: case PBESpecies.Sawsbuck: case PBESpecies.Shaymin: case PBESpecies.Shellos: case PBESpecies.Thundurus: case PBESpecies.Tornadus: case PBESpecies.Unown: case PBESpecies.Wormadam: return true; case PBESpecies.Castform: case PBESpecies.Cherrim: case PBESpecies.Darmanitan: case PBESpecies.Meloetta: return !requireUsableOutsideOfBattle; default: return false; } } public static string? GetNameOfForm(PBESpecies species, PBEForm form) { ValidateSpecies(species, form, false); string[] names = Enum.GetNames(); PBEForm[] forms = Enum.GetValues(); Dictionary combo = new(); for (int i = 0; i < names.Length; i++) { PBEForm f = forms[i]; string name = names[i]; if (name.StartsWith(species.ToString())) { combo.Add(f, name); } } if (combo.Count == 0) { return null; } return combo[form]; } public static IReadOnlyList GetForms(PBESpecies species, bool requireUsableOutsideOfBattle) { if (species <= 0 || species >= PBESpecies.MAX) { throw new ArgumentOutOfRangeException(nameof(species)); } switch (species) { case PBESpecies.Arceus: return _arceus; case PBESpecies.Basculin: return _basculin; case PBESpecies.Burmy: return _burmy; case PBESpecies.Castform: return requireUsableOutsideOfBattle ? Array.Empty() : _castform; case PBESpecies.Cherrim: return requireUsableOutsideOfBattle ? Array.Empty() : _cherrim; case PBESpecies.Darmanitan: return requireUsableOutsideOfBattle ? Array.Empty() : _darmanitan; case PBESpecies.Deerling: return _deerling; case PBESpecies.Deoxys: return _deoxys; case PBESpecies.Gastrodon: return _gastrodon; case PBESpecies.Genesect: return _genesect; case PBESpecies.Giratina: return _giratina; case PBESpecies.Keldeo: return _keldeo; case PBESpecies.Kyurem: return _kyurem; case PBESpecies.Landorus: return _landorus; case PBESpecies.Meloetta: return requireUsableOutsideOfBattle ? Array.Empty() : _meloetta; case PBESpecies.Rotom: return _rotom; case PBESpecies.Sawsbuck: return _sawsbuck; case PBESpecies.Shaymin: return _shaymin; case PBESpecies.Shellos: return _shellos; case PBESpecies.Thundurus: return _thundurus; case PBESpecies.Tornadus: return _tornadus; case PBESpecies.Unown: return _unown; case PBESpecies.Wormadam: return _wormadam; default: return Array.Empty(); } } public static IReadOnlyList GetValidItems(PBESpecies species, PBEForm form) { ValidateSpecies(species, form, false); switch (species) { case PBESpecies.Arceus: { switch (form) { case PBEForm.Arceus: return _arceusItems; case PBEForm.Arceus_Bug: return _arceusBugItems; case PBEForm.Arceus_Dark: return _arceusDarkItems; case PBEForm.Arceus_Dragon: return _arceusDragonItems; case PBEForm.Arceus_Electric: return _arceusElectricItems; case PBEForm.Arceus_Fighting: return _arceusFightingItems; case PBEForm.Arceus_Fire: return _arceusFireItems; case PBEForm.Arceus_Flying: return _arceusFlyingItems; case PBEForm.Arceus_Ghost: return _arceusGhostItems; case PBEForm.Arceus_Grass: return _arceusGrassItems; case PBEForm.Arceus_Ground: return _arceusGroundItems; case PBEForm.Arceus_Ice: return _arceusIceItems; case PBEForm.Arceus_Poison: return _arceusPoisonItems; case PBEForm.Arceus_Psychic: return _arceusPsychicItems; case PBEForm.Arceus_Rock: return _arceusRockItems; case PBEForm.Arceus_Steel: return _arceusSteelItems; case PBEForm.Arceus_Water: return _arceusWaterItems; default: throw new ArgumentOutOfRangeException(nameof(form)); } } case PBESpecies.Genesect: { switch (form) { case PBEForm.Genesect: return _genesectItems; case PBEForm.Genesect_Burn: return _genesectBurnItems; case PBEForm.Genesect_Chill: return _genesectChillItems; case PBEForm.Genesect_Douse: return _genesectDouseItems; case PBEForm.Genesect_Shock: return _genesectShockItems; default: throw new ArgumentOutOfRangeException(nameof(form)); } } case PBESpecies.Giratina: { switch (form) { case PBEForm.Giratina: return _giratinaItems; case PBEForm.Giratina_Origin: return _giratinaOriginItems; default: throw new ArgumentOutOfRangeException(nameof(form)); } } default: return AllItems; } } public static bool IsValidForm(PBESpecies species, PBEForm form, bool requireUsableOutsideOfBattle) { if (species <= 0 || species >= PBESpecies.MAX) { throw new ArgumentOutOfRangeException(nameof(species)); } switch (species) { case PBESpecies.Arceus: return form <= PBEForm.Arceus_Dark; case PBESpecies.Basculin: return form <= PBEForm.Basculin_Blue; case PBESpecies.Burmy: return form <= PBEForm.Burmy_Trash; case PBESpecies.Castform: return form <= (requireUsableOutsideOfBattle ? 0 : PBEForm.Castform_Snowy); case PBESpecies.Cherrim: return form <= (requireUsableOutsideOfBattle ? 0 : PBEForm.Cherrim_Sunshine); case PBESpecies.Darmanitan: return form <= (requireUsableOutsideOfBattle ? 0 : PBEForm.Darmanitan_Zen); case PBESpecies.Deerling: return form <= PBEForm.Deerling_Winter; case PBESpecies.Deoxys: return form <= PBEForm.Deoxys_Speed; case PBESpecies.Gastrodon: return form <= PBEForm.Gastrodon_East; case PBESpecies.Genesect: return form <= PBEForm.Genesect_Chill; case PBESpecies.Giratina: return form <= PBEForm.Giratina_Origin; case PBESpecies.Keldeo: return form <= PBEForm.Keldeo_Resolute; case PBESpecies.Kyurem: return form <= PBEForm.Kyurem_Black; case PBESpecies.Landorus: return form <= PBEForm.Landorus_Therian; case PBESpecies.Meloetta: return form <= (requireUsableOutsideOfBattle ? 0 : PBEForm.Meloetta_Pirouette); case PBESpecies.Rotom: return form <= PBEForm.Rotom_Mow; case PBESpecies.Sawsbuck: return form <= PBEForm.Sawsbuck_Winter; case PBESpecies.Shaymin: return form <= PBEForm.Shaymin_Sky; case PBESpecies.Shellos: return form <= PBEForm.Shellos_East; case PBESpecies.Thundurus: return form <= PBEForm.Thundurus_Therian; case PBESpecies.Tornadus: return form <= PBEForm.Tornadus_Therian; case PBESpecies.Unown: return form <= PBEForm.Unown_Question; case PBESpecies.Wormadam: return form <= PBEForm.Wormadam_Trash; default: return form <= 0; } } } ================================================ FILE: PokemonBattleEngine/Data/Utils/DataUtils_Items.cs ================================================ using Kermalis.PokemonBattleEngine.Utils; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; namespace Kermalis.PokemonBattleEngine.Data.Utils; public static partial class PBEDataUtils { #region Static Collections public static PBEAlphabeticalList AllItems { get; } = new(Enum.GetValues()); public static PBEAlphabeticalList AllBalls { get; } = new(new[] { PBEItem.MasterBall, PBEItem.UltraBall, PBEItem.GreatBall, PBEItem.PokeBall, PBEItem.SafariBall, PBEItem.NetBall, PBEItem.DiveBall, PBEItem.NestBall, PBEItem.RepeatBall, PBEItem.TimerBall, PBEItem.LuxuryBall, PBEItem.PremierBall, PBEItem.DuskBall, PBEItem.HealBall, PBEItem.QuickBall, PBEItem.CherishBall, PBEItem.FastBall, PBEItem.LevelBall, PBEItem.LureBall, PBEItem.HeavyBall, PBEItem.LoveBall, PBEItem.FriendBall, PBEItem.MoonBall, PBEItem.SportBall, PBEItem.ParkBall, PBEItem.DreamBall }); public static ReadOnlyDictionary TypeToGem { get; } = new(new Dictionary() { { PBEType.Bug, PBEItem.BugGem }, { PBEType.Dark, PBEItem.DarkGem }, { PBEType.Dragon, PBEItem.DragonGem }, { PBEType.Electric, PBEItem.ElectricGem }, { PBEType.Fighting, PBEItem.FightingGem }, { PBEType.Fire, PBEItem.FireGem }, { PBEType.Flying, PBEItem.FlyingGem }, { PBEType.Ghost, PBEItem.GhostGem }, { PBEType.Grass, PBEItem.GrassGem }, { PBEType.Ground, PBEItem.GroundGem }, { PBEType.Ice, PBEItem.IceGem }, { PBEType.Normal, PBEItem.NormalGem }, { PBEType.Poison, PBEItem.PoisonGem }, { PBEType.Psychic, PBEItem.PsychicGem }, { PBEType.Rock, PBEItem.RockGem }, { PBEType.Steel, PBEItem.SteelGem }, { PBEType.Water, PBEItem.WaterGem } }); #region Form Items private static readonly PBEAlphabeticalList _arceusItems = new(AllItems.Except(new[] { PBEItem.DracoPlate, PBEItem.DreadPlate, PBEItem.EarthPlate, PBEItem.FistPlate, PBEItem.FlamePlate, PBEItem.IciclePlate, PBEItem.InsectPlate, PBEItem.IronPlate, PBEItem.MeadowPlate, PBEItem.MindPlate, PBEItem.SkyPlate, PBEItem.SplashPlate, PBEItem.SpookyPlate, PBEItem.StonePlate, PBEItem.ToxicPlate, PBEItem.ZapPlate })); private static readonly PBEAlphabeticalList _arceusBugItems = new(new[] { PBEItem.InsectPlate }); private static readonly PBEAlphabeticalList _arceusDarkItems = new(new[] { PBEItem.DreadPlate }); private static readonly PBEAlphabeticalList _arceusDragonItems = new(new[] { PBEItem.DracoPlate }); private static readonly PBEAlphabeticalList _arceusElectricItems = new(new[] { PBEItem.ZapPlate }); private static readonly PBEAlphabeticalList _arceusFightingItems = new(new[] { PBEItem.FistPlate }); private static readonly PBEAlphabeticalList _arceusFireItems = new(new[] { PBEItem.FlamePlate }); private static readonly PBEAlphabeticalList _arceusFlyingItems = new(new[] { PBEItem.SkyPlate }); private static readonly PBEAlphabeticalList _arceusGhostItems = new(new[] { PBEItem.SpookyPlate }); private static readonly PBEAlphabeticalList _arceusGrassItems = new(new[] { PBEItem.MeadowPlate }); private static readonly PBEAlphabeticalList _arceusGroundItems = new(new[] { PBEItem.EarthPlate }); private static readonly PBEAlphabeticalList _arceusIceItems = new(new[] { PBEItem.IciclePlate }); private static readonly PBEAlphabeticalList _arceusPoisonItems = new(new[] { PBEItem.ToxicPlate }); private static readonly PBEAlphabeticalList _arceusPsychicItems = new(new[] { PBEItem.MindPlate }); private static readonly PBEAlphabeticalList _arceusRockItems = new(new[] { PBEItem.StonePlate }); private static readonly PBEAlphabeticalList _arceusSteelItems = new(new[] { PBEItem.IronPlate }); private static readonly PBEAlphabeticalList _arceusWaterItems = new(new[] { PBEItem.SplashPlate }); private static readonly PBEAlphabeticalList _genesectItems = new(AllItems.Except(new[] { PBEItem.BurnDrive, PBEItem.ChillDrive, PBEItem.DouseDrive, PBEItem.ShockDrive })); private static readonly PBEAlphabeticalList _genesectBurnItems = new(new[] { PBEItem.BurnDrive }); private static readonly PBEAlphabeticalList _genesectChillItems = new(new[] { PBEItem.ChillDrive }); private static readonly PBEAlphabeticalList _genesectDouseItems = new(new[] { PBEItem.DouseDrive }); private static readonly PBEAlphabeticalList _genesectShockItems = new(new[] { PBEItem.ShockDrive }); private static readonly PBEAlphabeticalList _giratinaItems = new(AllItems.ExceptOne(PBEItem.GriseousOrb)); private static readonly PBEAlphabeticalList _giratinaOriginItems = new(new[] { PBEItem.GriseousOrb }); #endregion #endregion } ================================================ FILE: PokemonBattleEngine/Data/Utils/DataUtils_Moves.cs ================================================ using System; using System.Collections.Generic; using System.Linq; namespace Kermalis.PokemonBattleEngine.Data.Utils; public static partial class PBEDataUtils { #region Static Collections public static PBEAlphabeticalList AllMoves { get; } = new(Enum.GetValues().Except(new[] { PBEMove.None, PBEMove.MAX })); public static PBEAlphabeticalList MetronomeMoves { get; } = new(GetMovesWithoutFlag(PBEMoveFlag.BlockedFromMetronome)); public static PBEAlphabeticalList SketchLegalMoves { get; } = new(GetMovesWithoutFlag(PBEMoveFlag.BlockedFromSketch, exception: PBEMoveEffect.Sketch)); #endregion private static List GetMovesWithoutFlag(PBEMoveFlag flag, PBEMoveEffect? exception = null) { return AllMoves.FindAll(m => { IPBEMoveData mData = PBEDataProvider.Instance.GetMoveData(m, cache: false); if (!mData.IsMoveUsable()) { return false; } if (exception is not null && mData.Effect == exception.Value) { return true; } return !mData.Flags.HasFlag(flag); }); } public static bool HasSecondaryEffects(PBEMoveEffect effect, PBESettings settings) { settings.ShouldBeReadOnly(nameof(settings)); switch (effect) { case PBEMoveEffect.Hit__MaybeBurn: case PBEMoveEffect.Hit__MaybeBurn__10PercentFlinch: case PBEMoveEffect.Hit__MaybeConfuse: case PBEMoveEffect.Hit__MaybeFlinch: case PBEMoveEffect.Hit__MaybeFreeze: case PBEMoveEffect.Hit__MaybeFreeze__10PercentFlinch: case PBEMoveEffect.Hit__MaybeLowerTarget_ACC_By1: case PBEMoveEffect.Hit__MaybeLowerTarget_ATK_By1: case PBEMoveEffect.Hit__MaybeLowerTarget_DEF_By1: case PBEMoveEffect.Hit__MaybeLowerTarget_SPATK_By1: case PBEMoveEffect.Hit__MaybeLowerTarget_SPDEF_By1: case PBEMoveEffect.Hit__MaybeLowerTarget_SPDEF_By2: case PBEMoveEffect.Hit__MaybeLowerTarget_SPE_By1: case PBEMoveEffect.Hit__MaybeLowerUser_ATK_DEF_By1: case PBEMoveEffect.Hit__MaybeLowerUser_DEF_SPDEF_By1: case PBEMoveEffect.Hit__MaybeLowerUser_SPATK_By2: case PBEMoveEffect.Hit__MaybeLowerUser_SPE_By1: case PBEMoveEffect.Hit__MaybeLowerUser_SPE_DEF_SPDEF_By1: case PBEMoveEffect.Hit__MaybeParalyze: case PBEMoveEffect.Hit__MaybeParalyze__10PercentFlinch: case PBEMoveEffect.Hit__MaybePoison: case PBEMoveEffect.Hit__MaybeRaiseUser_ATK_By1: case PBEMoveEffect.Hit__MaybeRaiseUser_ATK_DEF_SPATK_SPDEF_SPE_By1: case PBEMoveEffect.Hit__MaybeRaiseUser_DEF_By1: case PBEMoveEffect.Hit__MaybeRaiseUser_SPATK_By1: case PBEMoveEffect.Hit__MaybeRaiseUser_SPE_By1: case PBEMoveEffect.Hit__MaybeToxic: case PBEMoveEffect.Snore: return true; // BUG: SecretPower is unaffected by SereneGrace and the Rainbow case PBEMoveEffect.SecretPower: return settings.BugFix; default: return false; } } public static bool IsHPDrainMove(PBEMoveEffect effect) { switch (effect) { case PBEMoveEffect.HPDrain: case PBEMoveEffect.HPDrain__RequireSleep: return true; default: return false; } } public static bool IsHPRestoreMove(PBEMoveEffect effect) { switch (effect) { case PBEMoveEffect.Rest: case PBEMoveEffect.RestoreTargetHP: return true; default: return false; } } public static bool IsMultiHitMove(PBEMoveEffect effect) // TODO: TripleKick { switch (effect) { case PBEMoveEffect.Hit__2Times: case PBEMoveEffect.Hit__2Times__MaybePoison: case PBEMoveEffect.Hit__2To5Times: return true; default: return false; } } public static bool IsRecoilMove(PBEMoveEffect effect) // TODO: JumpKick/HiJumpKick { switch (effect) { case PBEMoveEffect.Recoil: case PBEMoveEffect.Recoil__10PercentBurn: case PBEMoveEffect.Recoil__10PercentParalyze: return true; default: return false; } } public static bool IsSetDamageMove(PBEMoveEffect effect) { switch (effect) { case PBEMoveEffect.Endeavor: case PBEMoveEffect.FinalGambit: case PBEMoveEffect.OneHitKnockout: case PBEMoveEffect.Psywave: case PBEMoveEffect.SeismicToss: case PBEMoveEffect.SetDamage: case PBEMoveEffect.SuperFang: return true; default: return false; } } public static bool IsSpreadMove(PBEMoveTarget targets) { switch (targets) { case PBEMoveTarget.All: case PBEMoveTarget.AllFoes: case PBEMoveTarget.AllFoesSurrounding: case PBEMoveTarget.AllSurrounding: case PBEMoveTarget.AllTeam: return true; default: return false; } } public static bool IsWeatherMove(PBEMoveEffect effect) { switch (effect) { case PBEMoveEffect.Hail: case PBEMoveEffect.RainDance: case PBEMoveEffect.Sandstorm: case PBEMoveEffect.SunnyDay: return true; default: return false; } } /// Temporary check to see if a move is usable, can be removed once all moves are added public static bool IsMoveUsable(PBEMove move) { return PBEDataProvider.Instance.GetMoveData(move, cache: false).IsMoveUsable(); } /// Temporary check to see if a move is usable, can be removed once all moves are added public static bool IsMoveUsable(PBEMoveEffect effect) { return effect != PBEMoveEffect.TODOMOVE && effect != PBEMoveEffect.Sketch; } } ================================================ FILE: PokemonBattleEngine/Data/Utils/DataUtils_Stats.cs ================================================ using Kermalis.PokemonBattleEngine.Utils; using System; using System.Collections.Generic; namespace Kermalis.PokemonBattleEngine.Data.Utils; public static partial class PBEDataUtils { #region Static Collections public static PBEAlphabeticalList AllNatures { get; } = new(Enum.GetValues().ExceptOne(PBENature.MAX)); public static PBEAlphabeticalList MoonStoneSpecies { get; } = new(new[] { PBESpecies.Nidoran_F, PBESpecies.Nidorina, PBESpecies.Nidoqueen, PBESpecies.Nidoran_M, PBESpecies.Nidorino, PBESpecies.Nidoking, PBESpecies.Cleffa, PBESpecies.Clefairy, PBESpecies.Clefable, PBESpecies.Igglybuff, PBESpecies.Jigglypuff, PBESpecies.Wigglytuff, PBESpecies.Skitty, PBESpecies.Delcatty, PBESpecies.Munna, PBESpecies.Musharna }); private static readonly Dictionary _natureBoosts = new() { { PBENature.Adamant, (PBEFlavor.Spicy, PBEFlavor.Dry) }, { PBENature.Bold, (PBEFlavor.Sour, PBEFlavor.Spicy) }, { PBENature.Brave, (PBEFlavor.Spicy, PBEFlavor.Sweet) }, { PBENature.Calm, (PBEFlavor.Bitter, PBEFlavor.Spicy) }, { PBENature.Careful, (PBEFlavor.Bitter, PBEFlavor.Dry) }, { PBENature.Gentle, (PBEFlavor.Bitter, PBEFlavor.Sour) }, { PBENature.Hasty, (PBEFlavor.Sweet, PBEFlavor.Sour) }, { PBENature.Impish, (PBEFlavor.Sour, PBEFlavor.Dry) }, { PBENature.Jolly, (PBEFlavor.Sweet, PBEFlavor.Dry) }, { PBENature.Lax, (PBEFlavor.Sour, PBEFlavor.Bitter) }, { PBENature.Lonely, (PBEFlavor.Spicy, PBEFlavor.Sour) }, { PBENature.Mild, (PBEFlavor.Dry, PBEFlavor.Sour) }, { PBENature.Modest, (PBEFlavor.Dry, PBEFlavor.Spicy) }, { PBENature.Naive, (PBEFlavor.Sweet, PBEFlavor.Bitter) }, { PBENature.Naughty, (PBEFlavor.Spicy, PBEFlavor.Bitter) }, { PBENature.Quiet, (PBEFlavor.Dry, PBEFlavor.Sweet) }, { PBENature.Rash, (PBEFlavor.Dry, PBEFlavor.Bitter) }, { PBENature.Relaxed, (PBEFlavor.Sour, PBEFlavor.Sweet) }, { PBENature.Sassy, (PBEFlavor.Bitter, PBEFlavor.Sweet) }, { PBENature.Timid, (PBEFlavor.Sweet, PBEFlavor.Spicy) } }; private static readonly PBEType[] _hiddenPowerTypes = new PBEType[] { PBEType.Fighting, // 7.8125 % PBEType.Flying, // 6.2500 % PBEType.Poison, // 6.2500 % PBEType.Ground, // 6.2500 % PBEType.Rock, // 6.2500 % PBEType.Bug, // 7.8125 % PBEType.Ghost, // 6.2500 % PBEType.Steel, // 6.2500 % PBEType.Fire, // 6.2500 % PBEType.Water, // 6.2500 % PBEType.Grass, // 7.8125 % PBEType.Electric, // 6.2500 % PBEType.Psychic, // 6.2500 % PBEType.Ice, // 6.2500 % PBEType.Dragon, // 6.2500 % PBEType.Dark // 1.5625 % }; #region Genders private static readonly PBEAlphabeticalList _genderless = new(new[] { PBEGender.Genderless }); private static readonly PBEAlphabeticalList _male = new(new[] { PBEGender.Male }); private static readonly PBEAlphabeticalList _female = new(new[] { PBEGender.Female }); private static readonly PBEAlphabeticalList _maleFemale = new(new[] { PBEGender.Male, PBEGender.Female }); #endregion #endregion public static sbyte GetRelationshipToFlavor(this PBENature nature, PBEFlavor flavor) { if (nature >= PBENature.MAX) { throw new ArgumentOutOfRangeException(nameof(nature)); } if (flavor >= PBEFlavor.MAX) { throw new ArgumentOutOfRangeException(nameof(flavor)); } if (_natureBoosts.TryGetValue(nature, out (PBEFlavor Favored, PBEFlavor Disliked) t)) { if (t.Favored == flavor) { return 1; } else if (t.Disliked == flavor) { return -1; } } return 0; } public static sbyte GetRelationshipToStat(this PBENature nature, PBEStat stat) { if (nature >= PBENature.MAX) { throw new ArgumentOutOfRangeException(nameof(nature)); } if (stat < PBEStat.Attack || stat > PBEStat.Speed) { throw new ArgumentOutOfRangeException(nameof(stat)); } return nature.GetRelationshipToFlavor((PBEFlavor)(stat - 1)); } public static PBEFlavor? GetLikedFlavor(this PBENature nature) { if (nature >= PBENature.MAX) { throw new ArgumentOutOfRangeException(nameof(nature)); } if (_natureBoosts.TryGetValue(nature, out (PBEFlavor Favored, PBEFlavor Disliked) t)) { return t.Favored; } return null; } public static PBEFlavor? GetDislikedFlavor(this PBENature nature) { if (nature >= PBENature.MAX) { throw new ArgumentOutOfRangeException(nameof(nature)); } if (_natureBoosts.TryGetValue(nature, out (PBEFlavor Favored, PBEFlavor Disliked) t)) { return t.Disliked; } return null; } public static PBEStat? GetLikedStat(this PBENature nature) { if (nature >= PBENature.MAX) { throw new ArgumentOutOfRangeException(nameof(nature)); } if (_natureBoosts.TryGetValue(nature, out (PBEFlavor Favored, PBEFlavor Disliked) t)) { return (PBEStat)(t.Favored + 1); } return null; } public static PBEStat? GetDislikedStat(this PBENature nature) { if (nature >= PBENature.MAX) { throw new ArgumentOutOfRangeException(nameof(nature)); } if (_natureBoosts.TryGetValue(nature, out (PBEFlavor Favored, PBEFlavor Disliked) t)) { return (PBEStat)(t.Disliked + 1); } return null; } public static bool IsNeutralNature(this PBENature nature) { if (nature >= PBENature.MAX) { throw new ArgumentOutOfRangeException(nameof(nature)); } return !_natureBoosts.ContainsKey(nature); } public static int CalcMaxPP(byte ppTier, byte ppUps, PBESettings settings) { settings.ShouldBeReadOnly(nameof(settings)); return Math.Max(1, (ppTier * settings.PPMultiplier) + (ppTier * ppUps)); } public static int CalcMaxPP(PBEMove move, byte ppUps, PBESettings settings) { settings.ShouldBeReadOnly(nameof(settings)); if (move >= PBEMove.MAX) { throw new ArgumentOutOfRangeException(nameof(move)); } if (move != PBEMove.None) { return CalcMaxPP(PBEDataProvider.Instance.GetMoveData(move).PPTier, ppUps, settings); } else { return 0; } } private static ushort CalcHP(PBESpecies species, IPBEReadOnlyStatCollection baseStats, byte evs, byte ivs, byte level) { return (ushort)(species == PBESpecies.Shedinja ? 1 : ((((2 * baseStats.HP) + ivs + (evs / 4)) * level / 100) + level + 10)); } private static ushort CalcOtherStat(IPBEReadOnlyStatCollection baseStats, PBEStat stat, sbyte statRelationship, byte evs, byte ivs, byte level, PBESettings settings) { float natureMultiplier = 1 + (statRelationship * settings.NatureStatBoost); return (ushort)(((((2 * baseStats.GetStat(stat)) + ivs + (evs / 4)) * level / 100) + 5) * natureMultiplier); } public static ushort CalculateStat(PBESpecies species, IPBEReadOnlyStatCollection baseStats, PBEStat stat, PBENature nature, byte evs, byte ivs, byte level, PBESettings settings) { settings.ShouldBeReadOnly(nameof(settings)); ValidateLevel(level, settings); if (ivs > settings.MaxIVs) { throw new ArgumentOutOfRangeException(nameof(ivs)); } switch (stat) { case PBEStat.HP: { return CalcHP(species, baseStats, evs, ivs, level); } case PBEStat.Attack: case PBEStat.Defense: case PBEStat.SpAttack: case PBEStat.SpDefense: case PBEStat.Speed: { return CalcOtherStat(baseStats, stat, nature.GetRelationshipToStat(stat), evs, ivs, level, settings); } default: throw new ArgumentOutOfRangeException(nameof(stat)); } } public static ushort CalculateStat(IPBEPokemonData pData, PBEStat stat, PBENature nature, byte evs, byte ivs, byte level, PBESettings settings) { return CalculateStat(pData.Species, pData.BaseStats, stat, nature, evs, ivs, level, settings); } public static ushort CalculateStat(IPBESpeciesForm pkmn, PBEStat stat, PBENature nature, byte evs, byte ivs, byte level, PBESettings settings) { return CalculateStat(pkmn.Species, pkmn.Form, stat, nature, evs, ivs, level, settings); } public static ushort CalculateStat(PBESpecies species, PBEForm form, PBEStat stat, PBENature nature, byte evs, byte ivs, byte level, PBESettings settings) { ValidateSpecies(species, form, false); return CalculateStat(PBEDataProvider.Instance.GetPokemonData(species, form), stat, nature, evs, ivs, level, settings); } public static void GetStatRange(PBESpecies species, IPBEReadOnlyStatCollection baseStats, PBEStat stat, byte level, PBESettings settings, out ushort low, out ushort high) { settings.ShouldBeReadOnly(nameof(settings)); ValidateLevel(level, settings); switch (stat) { case PBEStat.HP: { low = CalcHP(species, baseStats, 0, 0, level); high = CalcHP(species, baseStats, byte.MaxValue, settings.MaxIVs, level); break; } case PBEStat.Attack: case PBEStat.Defense: case PBEStat.SpAttack: case PBEStat.SpDefense: case PBEStat.Speed: { low = CalcOtherStat(baseStats, stat, -1, 0, 0, level, settings); high = CalcOtherStat(baseStats, stat, +1, byte.MaxValue, settings.MaxIVs, level, settings); break; } default: throw new ArgumentOutOfRangeException(nameof(stat)); } } public static void GetStatRange(IPBEPokemonData pData, PBEStat stat, byte level, PBESettings settings, out ushort low, out ushort high) { GetStatRange(pData.Species, pData.BaseStats, stat, level, settings, out low, out high); } public static void GetStatRange(IPBESpeciesForm pkmn, PBEStat stat, byte level, PBESettings settings, out ushort low, out ushort high) { GetStatRange(pkmn.Species, pkmn.Form, stat, level, settings, out low, out high); } public static void GetStatRange(PBESpecies species, PBEForm form, PBEStat stat, byte level, PBESettings settings, out ushort low, out ushort high) { ValidateSpecies(species, form, false); GetStatRange(PBEDataProvider.Instance.GetPokemonData(species, form), stat, level, settings, out low, out high); } public static PBEType GetHiddenPowerType(byte hpIV, byte attackIV, byte defenseIV, byte spAttackIV, byte spDefenseIV, byte speedIV) { int a = hpIV & 1, b = attackIV & 1, c = defenseIV & 1, d = speedIV & 1, e = spAttackIV & 1, f = spDefenseIV & 1; return _hiddenPowerTypes[(((1 << 0) * a) + ((1 << 1) * b) + ((1 << 2) * c) + ((1 << 3) * d) + ((1 << 4) * e) + ((1 << 5) * f)) * (_hiddenPowerTypes.Length - 1) / ((1 << 6) - 1)]; } public static byte GetHiddenPowerBasePower(byte hpIV, byte attackIV, byte defenseIV, byte spAttackIV, byte spDefenseIV, byte speedIV, PBESettings settings) { settings.ShouldBeReadOnly(nameof(settings)); int a = (hpIV & 2) == 2 ? 1 : 0, b = (attackIV & 2) == 2 ? 1 : 0, c = (defenseIV & 2) == 2 ? 1 : 0, d = (speedIV & 2) == 2 ? 1 : 0, e = (spAttackIV & 2) == 2 ? 1 : 0, f = (spDefenseIV & 2) == 2 ? 1 : 0; byte mininumBasePower = settings.HiddenPowerMin, maximumBasePower = settings.HiddenPowerMax; return (byte)(((((1 << 0) * a) + ((1 << 1) * b) + ((1 << 2) * c) + ((1 << 3) * d) + ((1 << 4) * e) + ((1 << 5) * f)) * (maximumBasePower - mininumBasePower) / ((1 << 6) - 1)) + mininumBasePower); } public static IReadOnlyList GetValidGenders(PBEGenderRatio genderRatio) { switch (genderRatio) { case PBEGenderRatio.M0_F0: return _genderless; case PBEGenderRatio.M1_F0: return _male; case PBEGenderRatio.M0_F1: return _female; default: return _maleFemale; } } public static bool IsOppositeGender(this PBEGender gender, PBEGender otherGender) { return gender != PBEGender.Genderless && otherGender != PBEGender.Genderless && gender != otherGender; } public static string ToSymbol(this PBEGender gender) { return gender == PBEGender.Female ? "♀" : gender == PBEGender.Male ? "♂" : string.Empty; } } ================================================ FILE: PokemonBattleEngine/Data/Utils/DataUtils_Validate.cs ================================================ using System; namespace Kermalis.PokemonBattleEngine.Data.Utils; public static partial class PBEDataUtils { public static void ValidateSpecies(PBESpecies species, PBEForm form, bool requireUsableOutsideOfBattle) { if (!IsValidForm(species, form, requireUsableOutsideOfBattle)) { throw new ArgumentOutOfRangeException(nameof(form)); } } public static void ValidateNickname(string value, PBESettings settings) { if (string.IsNullOrWhiteSpace(value)) { throw new ArgumentOutOfRangeException(nameof(value)); } if (value.Length > settings.MaxPokemonNameLength) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(value)} cannot have more than {nameof(settings.MaxPokemonNameLength)} ({settings.MaxPokemonNameLength}) characters."); } } public static void ValidateLevel(byte value, PBESettings settings) { if (value < settings.MinLevel || value > settings.MaxLevel) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(value)} must be at least {nameof(settings.MinLevel)} ({settings.MinLevel}) and cannot exceed {nameof(settings.MaxLevel)} ({settings.MaxLevel})."); } } public static void ValidateEXP(PBEGrowthRate type, uint value, byte level) { uint requiredForLevel = PBEDataProvider.Instance.GetEXPRequired(type, level); if (value < requiredForLevel) { throw new ArgumentOutOfRangeException(nameof(value)); } if (level < 100) { uint requiredForNextLevel = PBEDataProvider.Instance.GetEXPRequired(type, (byte)(level + 1)); if (value >= requiredForNextLevel) { throw new ArgumentOutOfRangeException(nameof(value)); } } } public static void ValidateAbility(PBEAlphabeticalList valid, PBEAbility value) { if (!valid.Contains(value)) { throw new ArgumentOutOfRangeException(nameof(value)); } } public static void ValidateNature(PBENature value) { if (!AllNatures.Contains(value)) { throw new ArgumentOutOfRangeException(nameof(value)); } } public static void ValidateGender(PBEAlphabeticalList valid, PBEGender value) { if (!valid.Contains(value)) { throw new ArgumentOutOfRangeException(nameof(value)); } } public static void ValidateItem(PBEAlphabeticalList valid, PBEItem value) { if (!valid.Contains(value)) { throw new ArgumentOutOfRangeException(nameof(value)); } } public static void ValidateCaughtBall(PBEItem value) { if (!AllBalls.Contains(value)) { throw new ArgumentOutOfRangeException(nameof(value)); } } } ================================================ FILE: PokemonBattleEngine/Network/Client.cs ================================================ /* EasyTcp * * Copyright (c) 2019 henkje * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Packets; using System; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Net; using System.Net.Sockets; namespace Kermalis.PokemonBattleEngine.Network; public sealed class PBEClient : IDisposable { public PBEBattle? Battle { get; set; } private Socket? _socket; private PBEEncryption? _encryption; private PBEPacketProcessor _packetProcessor = null!; // Set in Connect() private byte[]? _buffer; public IPEndPoint? RemoteIP { get; private set; } [MemberNotNullWhen(true, nameof(_socket), nameof(RemoteIP))] public bool IsConnected { get; private set; } public event EventHandler? Disconnected; public event EventHandler? Error; public event EventHandler? PacketReceived; public bool Connect(IPEndPoint ip, int millisecondsTimeout, PBEPacketProcessor packetProcessor, PBEEncryption? encryption = null) { if (millisecondsTimeout < -1) { throw new ArgumentException($"\"{nameof(millisecondsTimeout)}\" is invalid."); } Disconnect(true); _socket = new Socket(ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp); try { if (_socket.BeginConnect(ip, null, null).AsyncWaitHandle.WaitOne(millisecondsTimeout)) { IsConnected = true; RemoteIP = ip; _encryption = encryption; _packetProcessor = packetProcessor; BeginReceive(); return true; } } catch (Exception ex) { NotifyError(ex); } Disconnect(false); return false; } public void Disconnect(bool notify) { if (!IsConnected) { return; } IsConnected = false; RemoteIP = null; _encryption = null; try { _socket.Shutdown(SocketShutdown.Both); } catch (Exception ex) { NotifyError(ex); } _socket.Dispose(); _socket = null; if (notify) { Disconnected?.Invoke(this, EventArgs.Empty); } } public void Send(IPBEPacket packet) { if (!IsConnected) { throw new InvalidOperationException("Socket not connected."); } byte[] data = packet.Data.ToArray(); if (_encryption is not null) { data = _encryption.Encrypt(data); } PBENetworkUtils.Send(data, _socket); } private void BeginReceive() { _socket!.BeginReceive(_buffer = new byte[2], 0, 2, SocketFlags.None, OnReceiveLength, null); } private void OnReceiveLength(IAsyncResult ar) { if (!IsConnected) { return; } try { if (_socket.Poll(0, SelectMode.SelectRead) && _socket.Available <= 0) { Disconnect(true); } else { ushort dataLength = (ushort)(_buffer![0] | (_buffer[1] << 8)); if (dataLength <= 0) { Disconnect(true); } else { _socket.BeginReceive(_buffer = new byte[dataLength], 0, dataLength, SocketFlags.None, OnReceiveData, null); } } } catch (Exception ex) { NotifyError(ex); Disconnect(true); } } private void OnReceiveData(IAsyncResult ar) { if (!IsConnected) { return; } try { if (_socket.Poll(0, SelectMode.SelectRead) && _socket.Available <= 0) { Disconnect(true); } else { byte[] data = _buffer!; if (_encryption is not null) { data = _encryption.Decrypt(data); } PacketReceived?.Invoke(this, _packetProcessor.CreatePacket(data, Battle)); BeginReceive(); } } catch (Exception ex) { NotifyError(ex); Disconnect(true); } } private void NotifyError(Exception ex) { if (Error is not null) { Error.Invoke(this, ex); } else { throw ex; } } public void Dispose() { Disconnect(true); Disconnected = null; Error = null; PacketReceived = null; } } ================================================ FILE: PokemonBattleEngine/Network/Encryption.cs ================================================ /* EasyTcp * * Copyright (c) 2019 henkje * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ using System; using System.IO; using System.Security.Cryptography; namespace Kermalis.PokemonBattleEngine.Network; public sealed class PBEEncryption { private readonly SymmetricAlgorithm _algorithm; public PBEEncryption(SymmetricAlgorithm algorithm) { _algorithm = algorithm; } public byte[] Encrypt(byte[] data) { _algorithm.GenerateIV(); using (var ms = new MemoryStream()) { ms.Write(_algorithm.IV, 0, _algorithm.IV.Length); using (var cs = new CryptoStream(ms, _algorithm.CreateEncryptor(_algorithm.Key, _algorithm.IV), CryptoStreamMode.Write)) { cs.Write(data, 0, data.Length); cs.FlushFinalBlock(); } return ms.ToArray(); } } public byte[] Decrypt(byte[] data) { using (var ms = new MemoryStream(data)) { byte[] iv = new byte[_algorithm.IV.Length]; ms.Read(iv); _algorithm.IV = iv; using (var cs = new CryptoStream(ms, _algorithm.CreateDecryptor(_algorithm.Key, _algorithm.IV), CryptoStreamMode.Read)) { byte[] decrypted = new byte[data.Length]; int byteCount = cs.Read(decrypted, 0, decrypted.Length); byte[] ret = new byte[byteCount]; Array.Copy(decrypted, 0, ret, 0, byteCount); return ret; } } } } ================================================ FILE: PokemonBattleEngine/Network/NetworkUtils.cs ================================================ using Kermalis.EndianBinaryIO; using System; using System.Collections.Generic; using System.Net.Sockets; namespace Kermalis.PokemonBattleEngine.Network; internal static class PBENetworkUtils { public static void Send(byte[] data, Socket socket) { using (SocketAsyncEventArgs e = CreateArgs(data)) { socket.SendAsync(e); } } public static void Send(byte[] data, IEnumerable sockets) { using (SocketAsyncEventArgs e = CreateArgs(data)) { foreach (Socket socket in sockets) { socket.SendAsync(e); } } } private static SocketAsyncEventArgs CreateArgs(byte[] data) { int len = data.Length; if (len <= 0 || len > ushort.MaxValue) { throw new ArgumentException($"Data length must be greater than 0 bytes and must not exceed {ushort.MaxValue} bytes."); } byte[] message = new byte[len + 2]; EndianBinaryPrimitives.WriteInt16(message.AsSpan(0, 2), (short)len, Endianness.LittleEndian); Array.Copy(data, 0, message, 2, len); var e = new SocketAsyncEventArgs(); e.SetBuffer(message, 0, message.Length); return e; } } ================================================ FILE: PokemonBattleEngine/Network/Server.cs ================================================ /* EasyTcp * * Copyright (c) 2019 henkje * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Packets; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Net; using System.Net.Sockets; namespace Kermalis.PokemonBattleEngine.Network; public sealed class PBEServer : IDisposable { public PBEBattle? Battle { get; set; } private Socket? _listener; private PBEEncryption? _encryption; private PBEPacketProcessor _packetProcessor = null!; // Set in Start() private int _maxConnections; [MemberNotNullWhen(true, nameof(_listener))] public bool IsRunning => _listener is not null; private readonly HashSet _bannedIPs = new(); private readonly HashSet _connectedClients = new(); public event EventHandler? ClientConnected; public event EventHandler? ClientDisconnected; public event EventHandler? Error; public delegate void PBEClientRefusedEventHandler(object? sender, IPEndPoint refusedIP, bool refusedForBan); public event PBEClientRefusedEventHandler? ClientRefused; public void Start(IPEndPoint ip, int maxConnections, PBEPacketProcessor packetProcessor, PBEEncryption? encryption = null, bool dualMode = false) { if (IsRunning) { throw new InvalidOperationException("Server is already running."); } if (maxConnections <= 0) { throw new ArgumentException($"\"{nameof(maxConnections)}\" must be greater than 0."); } _listener = new Socket(ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp); if (ip.AddressFamily == AddressFamily.InterNetworkV6) { _listener.DualMode = dualMode; } _listener.Bind(ip); _encryption = encryption; _packetProcessor = packetProcessor; try { _maxConnections = maxConnections; _listener.Listen(maxConnections); _listener.BeginAccept(OnClientConnected, null); } catch (Exception ex) { NotifyError(ex); Stop(); } } public void Stop() { if (!IsRunning) { return; } try { _listener.Shutdown(SocketShutdown.Both); } catch (Exception ex) { NotifyError(ex); } _listener.Dispose(); _listener = null; } public void SendToAll(IPBEPacket packet) { lock (_connectedClients) { foreach (PBEServerClient client in _connectedClients) { client.Send(packet); } } } private void OnClientConnected(IAsyncResult ar) { if (!IsRunning) { return; } PBEServerClient? client = null; try { client = new PBEServerClient(_listener.EndAccept(ar), _encryption); bool isBanned; lock (_bannedIPs) { isBanned = _bannedIPs.Contains(client.IP); } if (isBanned) { RefuseClient(client, true); } else { int count; lock (_connectedClients) { count = _connectedClients.Count; } if (count >= _maxConnections) { RefuseClient(client, false); } else { lock (_connectedClients) { _connectedClients.Add(client); } client.IsConnected = true; ClientConnected?.Invoke(this, client); BeginReceive(client); } } } catch (Exception ex) { NotifyError(ex); if (client is not null) { DisconnectClient(client); } } _listener.BeginAccept(OnClientConnected, _listener); } private void BeginReceive(PBEServerClient client) { byte[] buffer = new byte[2]; client.Buffer = buffer; client.Socket.BeginReceive(buffer, 0, 2, SocketFlags.None, OnReceiveLength, client); } private void OnReceiveLength(IAsyncResult ar) { var client = (PBEServerClient)ar.AsyncState!; if (!client.IsConnected) { return; } try { if (client.Socket.Poll(0, SelectMode.SelectRead) && client.Socket.Available <= 0) { DisconnectClient(client); } else { ushort dataLength = (ushort)(client.Buffer![0] | (client.Buffer[1] << 8)); if (dataLength <= 0) { DisconnectClient(client); } else { client.Socket.BeginReceive(client.Buffer = new byte[dataLength], 0, dataLength, SocketFlags.None, OnReceiveData, client); } } } catch (Exception ex) { NotifyError(ex); DisconnectClient(client); } } private void OnReceiveData(IAsyncResult ar) { var client = (PBEServerClient)ar.AsyncState!; if (!client.IsConnected) { return; } try { if (client.Socket.Poll(0, SelectMode.SelectRead) && client.Socket.Available <= 0) { DisconnectClient(client); } else { byte[] data = client.Buffer!; if (_encryption is not null) { data = _encryption.Decrypt(data); } client.FirePacketReceived(_packetProcessor.CreatePacket(data, Battle)); BeginReceive(client); } } catch (Exception ex) { NotifyError(ex); DisconnectClient(client); } } private void RefuseClient(PBEServerClient client, bool isBanned) { client.Socket.Shutdown(SocketShutdown.Both); client.Socket.Dispose(); ClientRefused?.Invoke(this, client.IP, isBanned); } public bool DisconnectClient(PBEServerClient client) { bool b; lock (_connectedClients) { b = _connectedClients.Remove(client); } if (b) { client.IsConnected = false; client.Socket.Shutdown(SocketShutdown.Both); client.Socket.Dispose(); ClientDisconnected?.Invoke(this, client); } return b; } private void NotifyError(Exception ex) { if (Error is not null) { Error.Invoke(this, ex); } else { throw ex; } } public void Dispose() { Stop(); ClientConnected = null; ClientDisconnected = null; ClientRefused = null; Error = null; } } ================================================ FILE: PokemonBattleEngine/Network/ServerClient.cs ================================================ using Kermalis.PokemonBattleEngine.Packets; using System; using System.Linq; using System.Net; using System.Net.Sockets; namespace Kermalis.PokemonBattleEngine.Network; public sealed class PBEServerClient { internal readonly Socket Socket; internal byte[]? Buffer; private readonly PBEEncryption? _encryption; public IPEndPoint IP { get; } public bool IsConnected { get; internal set; } public event EventHandler? PacketReceived; internal PBEServerClient(Socket socket, PBEEncryption? encryption) { Socket = socket; IP = (IPEndPoint)socket.RemoteEndPoint!; _encryption = encryption; } public void Send(IPBEPacket packet) { byte[] data = packet.Data.ToArray(); if (_encryption is not null) { data = _encryption.Encrypt(data); } PBENetworkUtils.Send(data, Socket); } internal void FirePacketReceived(IPBEPacket packet) { PacketReceived?.Invoke(this, packet); } } ================================================ FILE: PokemonBattleEngine/Packets/ActionsRequestPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using System.Collections.ObjectModel; using System.IO; using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEActionsRequestPacket : IPBEPacket { public const ushort ID = 0x07; public ReadOnlyCollection Data { get; } public PBETrainer Trainer { get; } public ReadOnlyCollection Pokemon { get; } internal PBEActionsRequestPacket(PBETrainer trainer) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((Trainer = trainer).Id); byte count = (byte)(Pokemon = new ReadOnlyCollection(trainer.ActionsRequired.Select(p => p.FieldPosition).ToArray())).Count; w.WriteByte(count); for (int i = 0; i < count; i++) { w.WriteEnum(Pokemon[i]); } Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEActionsRequestPacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); Trainer = battle.Trainers[r.ReadByte()]; var pkmn = new PBEFieldPosition[r.ReadByte()]; for (int i = 0; i < pkmn.Length; i++) { pkmn[i] = r.ReadEnum(); } Pokemon = new ReadOnlyCollection(pkmn); } } ================================================ FILE: PokemonBattleEngine/Packets/ActionsResponsePacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEActionsResponsePacket : IPBEPacket { public const ushort ID = 0x08; public ReadOnlyCollection Data { get; } public ReadOnlyCollection Actions { get; } public PBEActionsResponsePacket(IList actions) { if (actions.Count == 0) { throw new ArgumentOutOfRangeException(nameof(actions)); } using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); byte count = (byte)(Actions = new ReadOnlyCollection(actions)).Count; w.WriteByte(count); for (int i = 0; i < count; i++) { Actions[i].ToBytes(w); } Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEActionsResponsePacket(byte[] data, EndianBinaryReader r) { Data = new ReadOnlyCollection(data); var actions = new PBETurnAction[r.ReadByte()]; for (int i = 0; i < actions.Length; i++) { actions[i] = new PBETurnAction(r); } Actions = new ReadOnlyCollection(actions); } } ================================================ FILE: PokemonBattleEngine/Packets/AutoCenterPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public interface IPBEAutoCenterPacket : IPBEPacket { PBETrainer Pokemon0Trainer { get; } PBEFieldPosition Pokemon0OldPosition { get; } PBETrainer Pokemon1Trainer { get; } PBEFieldPosition Pokemon1OldPosition { get; } } public interface IPBEAutoCenterPacket_0 : IPBEAutoCenterPacket { byte Pokemon0 { get; } } public interface IPBEAutoCenterPacket_1 : IPBEAutoCenterPacket { byte Pokemon1 { get; } } public sealed class PBEAutoCenterPacket : IPBEAutoCenterPacket_0, IPBEAutoCenterPacket_1 { public const ushort ID = 0x2A; public ReadOnlyCollection Data { get; } public PBETrainer Pokemon0Trainer { get; } public byte Pokemon0 { get; } public PBEFieldPosition Pokemon0OldPosition { get; } public PBETrainer Pokemon1Trainer { get; } public byte Pokemon1 { get; } public PBEFieldPosition Pokemon1OldPosition { get; } internal PBEAutoCenterPacket(PBEBattlePokemon pokemon0, PBEFieldPosition pokemon0OldPosition, PBEBattlePokemon pokemon1, PBEFieldPosition pokemon1OldPosition) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((Pokemon0Trainer = pokemon0.Trainer).Id); w.WriteByte(Pokemon0 = pokemon0.Id); w.WriteEnum(Pokemon0OldPosition = pokemon0OldPosition); w.WriteByte((Pokemon1Trainer = pokemon1.Trainer).Id); w.WriteByte(Pokemon1 = pokemon1.Id); w.WriteEnum(Pokemon1OldPosition = pokemon1OldPosition); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEAutoCenterPacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); Pokemon0Trainer = battle.Trainers[r.ReadByte()]; Pokemon0 = r.ReadByte(); Pokemon0OldPosition = r.ReadEnum(); Pokemon1Trainer = battle.Trainers[r.ReadByte()]; Pokemon1 = r.ReadByte(); Pokemon1OldPosition = r.ReadEnum(); } } public sealed class PBEAutoCenterPacket_Hidden0 : IPBEAutoCenterPacket_1 { public const ushort ID = 0x30; public ReadOnlyCollection Data { get; } public PBETrainer Pokemon0Trainer { get; } public PBEFieldPosition Pokemon0OldPosition { get; } public PBETrainer Pokemon1Trainer { get; } public byte Pokemon1 { get; } public PBEFieldPosition Pokemon1OldPosition { get; } public PBEAutoCenterPacket_Hidden0(PBEAutoCenterPacket other) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((Pokemon0Trainer = other.Pokemon0Trainer).Id); w.WriteEnum(Pokemon0OldPosition = other.Pokemon0OldPosition); w.WriteByte((Pokemon1Trainer = other.Pokemon1Trainer).Id); w.WriteByte(Pokemon1 = other.Pokemon1); w.WriteEnum(Pokemon1OldPosition = other.Pokemon1OldPosition); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEAutoCenterPacket_Hidden0(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); Pokemon0Trainer = battle.Trainers[r.ReadByte()]; Pokemon0OldPosition = r.ReadEnum(); Pokemon1Trainer = battle.Trainers[r.ReadByte()]; Pokemon1 = r.ReadByte(); Pokemon1OldPosition = r.ReadEnum(); } } public sealed class PBEAutoCenterPacket_Hidden1 : IPBEAutoCenterPacket_0 { public const ushort ID = 0x31; public ReadOnlyCollection Data { get; } public PBETrainer Pokemon0Trainer { get; } public byte Pokemon0 { get; } public PBEFieldPosition Pokemon0OldPosition { get; } public PBETrainer Pokemon1Trainer { get; } public PBEFieldPosition Pokemon1OldPosition { get; } public PBEAutoCenterPacket_Hidden1(PBEAutoCenterPacket other) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((Pokemon0Trainer = other.Pokemon0Trainer).Id); w.WriteByte(Pokemon0 = other.Pokemon0); w.WriteEnum(Pokemon0OldPosition = other.Pokemon0OldPosition); w.WriteByte((Pokemon1Trainer = other.Pokemon1Trainer).Id); w.WriteEnum(Pokemon1OldPosition = other.Pokemon1OldPosition); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEAutoCenterPacket_Hidden1(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); Pokemon0Trainer = battle.Trainers[r.ReadByte()]; Pokemon0 = r.ReadByte(); Pokemon0OldPosition = r.ReadEnum(); Pokemon1Trainer = battle.Trainers[r.ReadByte()]; Pokemon1OldPosition = r.ReadEnum(); } } public sealed class PBEAutoCenterPacket_Hidden01 : IPBEAutoCenterPacket { public const ushort ID = 0x32; public ReadOnlyCollection Data { get; } public PBETrainer Pokemon0Trainer { get; } public PBEFieldPosition Pokemon0OldPosition { get; } public PBETrainer Pokemon1Trainer { get; } public PBEFieldPosition Pokemon1OldPosition { get; } public PBEAutoCenterPacket_Hidden01(PBEAutoCenterPacket other) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((Pokemon0Trainer = other.Pokemon0Trainer).Id); w.WriteEnum(Pokemon0OldPosition = other.Pokemon0OldPosition); w.WriteByte((Pokemon1Trainer = other.Pokemon1Trainer).Id); w.WriteEnum(Pokemon1OldPosition = other.Pokemon1OldPosition); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEAutoCenterPacket_Hidden01(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); Pokemon0Trainer = battle.Trainers[r.ReadByte()]; Pokemon0OldPosition = r.ReadEnum(); Pokemon1Trainer = battle.Trainers[r.ReadByte()]; Pokemon1OldPosition = r.ReadEnum(); } } ================================================ FILE: PokemonBattleEngine/Packets/BattlePacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngine.Utils; using System.Collections.ObjectModel; using System.IO; using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEBattlePacket : IPBEPacket { public const ushort ID = 0x05; public ReadOnlyCollection Data { get; } public sealed class PBETeamInfo { public sealed class PBETrainerInfo { public sealed class PBEBattlePokemonInfo // SleepTurns would be too much info for a client to have { public byte Id { get; } public PBESpecies Species { get; } public PBEForm Form { get; } public string Nickname { get; } public byte Level { get; } public uint EXP { get; } public bool Pokerus { get; } public byte Friendship { get; } public bool Shiny { get; } public PBEAbility Ability { get; } public PBENature Nature { get; } public PBEGender Gender { get; } public PBEItem Item { get; } public PBEItem CaughtBall { get; } public PBEStatus1 Status1 { get; } public PBEReadOnlyStatCollection EffortValues { get; } public PBEReadOnlyStatCollection IndividualValues { get; } public PBEReadOnlyPartyMoveset Moveset { get; } internal PBEBattlePokemonInfo(PBEBattlePokemon pkmn) { Id = pkmn.Id; Species = pkmn.OriginalSpecies; Form = pkmn.OriginalForm; Nickname = pkmn.Nickname; Level = pkmn.OriginalLevel; EXP = pkmn.OriginalEXP; Friendship = pkmn.Friendship; Shiny = pkmn.Shiny; Pokerus = pkmn.Pokerus; Ability = pkmn.OriginalAbility; Nature = pkmn.Nature; Gender = pkmn.Gender; Item = pkmn.OriginalItem; CaughtBall = pkmn.OriginalCaughtBall; Status1 = pkmn.OriginalStatus1; EffortValues = pkmn.OriginalEffortValues!; IndividualValues = pkmn.IndividualValues!; Moveset = pkmn.OriginalMoveset!; } internal PBEBattlePokemonInfo(EndianBinaryReader r) { Id = r.ReadByte(); Species = r.ReadEnum(); Form = r.ReadEnum(); Nickname = r.ReadString_NullTerminated(); Level = r.ReadByte(); EXP = r.ReadUInt32(); Friendship = r.ReadByte(); Shiny = r.ReadBoolean(); Pokerus = r.ReadBoolean(); Ability = r.ReadEnum(); Nature = r.ReadEnum(); Gender = r.ReadEnum(); Item = r.ReadEnum(); CaughtBall = r.ReadEnum(); Status1 = r.ReadEnum(); EffortValues = new PBEReadOnlyStatCollection(r); IndividualValues = new PBEReadOnlyStatCollection(r); Moveset = new PBEReadOnlyPartyMoveset(r); } internal void ToBytes(EndianBinaryWriter w) { w.WriteByte(Id); w.WriteEnum(Species); w.WriteEnum(Form); w.WriteChars_NullTerminated(Nickname); w.WriteByte(Level); w.WriteUInt32(EXP); w.WriteByte(Friendship); w.WriteBoolean(Shiny); w.WriteBoolean(Pokerus); w.WriteEnum(Ability); w.WriteEnum(Nature); w.WriteEnum(Gender); w.WriteEnum(Item); w.WriteEnum(CaughtBall); w.WriteEnum(Status1); EffortValues.ToBytes(w); IndividualValues.ToBytes(w); Moveset.ToBytes(w); } } public sealed class PBEInventorySlotInfo { public PBEItem Item { get; } public uint Quantity { get; } internal PBEInventorySlotInfo(PBEBattleInventory.PBEBattleInventorySlot slot) { Item = slot.Item; Quantity = slot.Quantity; } internal PBEInventorySlotInfo(EndianBinaryReader r) { Item = r.ReadEnum(); Quantity = r.ReadUInt32(); } internal void ToBytes(EndianBinaryWriter w) { w.WriteEnum(Item); w.WriteUInt32(Quantity); } } public byte Id { get; } public string Name { get; } public ReadOnlyCollection Inventory { get; } public ReadOnlyCollection Party { get; } internal PBETrainerInfo(PBETrainer trainer) { Id = trainer.Id; if (trainer.IsWild) { Name = string.Empty; Inventory = PBEEmptyReadOnlyCollection.Value; } else { Name = trainer.Name; Inventory = trainer.Inventory.Count == 0 ? PBEEmptyReadOnlyCollection.Value : new ReadOnlyCollection(trainer.Inventory.Values.Select(s => new PBEInventorySlotInfo(s)).ToArray()); } Party = new ReadOnlyCollection(trainer.Party.Select(p => new PBEBattlePokemonInfo(p)).ToArray()); } internal PBETrainerInfo(EndianBinaryReader r) { Id = r.ReadByte(); Name = r.ReadString_NullTerminated(); int count = r.ReadUInt16(); if (count == 0) { Inventory = PBEEmptyReadOnlyCollection.Value; } else { var inv = new PBEInventorySlotInfo[count]; for (int i = 0; i < count; i++) { inv[i] = new PBEInventorySlotInfo(r); } Inventory = new ReadOnlyCollection(inv); } count = r.ReadByte(); if (count == 0) { Party = PBEEmptyReadOnlyCollection.Value; } else { var party = new PBEBattlePokemonInfo[count]; for (int i = 0; i < count; i++) { party[i] = new PBEBattlePokemonInfo(r); } Party = new ReadOnlyCollection(party); } } internal PBETrainerInfo(PBETrainerInfo other, byte? onlyForTrainer) { Id = other.Id; Name = other.Name; if (onlyForTrainer is not null && onlyForTrainer.Value == Id) { Inventory = other.Inventory; Party = other.Party; } else { Inventory = PBEEmptyReadOnlyCollection.Value; Party = PBEEmptyReadOnlyCollection.Value; } } internal void ToBytes(EndianBinaryWriter w) { w.WriteByte(Id); w.WriteChars_NullTerminated(Name); ushort icount = (ushort)Inventory.Count; w.WriteUInt16(icount); for (int i = 0; i < icount; i++) { Inventory[i].ToBytes(w); } byte pcount = (byte)Party.Count; w.WriteByte(pcount); for (int i = 0; i < pcount; i++) { Party[i].ToBytes(w); } } } public byte Id { get; } public ReadOnlyCollection Trainers { get; } internal PBETeamInfo(PBETeam team) { Id = team.Id; Trainers = new ReadOnlyCollection(team.Trainers.Select(t => new PBETrainerInfo(t)).ToArray()); } internal PBETeamInfo(EndianBinaryReader r) { Id = r.ReadByte(); var trainers = new PBETrainerInfo[r.ReadByte()]; for (int i = 0; i < trainers.Length; i++) { trainers[i] = new PBETrainerInfo(r); } Trainers = new ReadOnlyCollection(trainers); } internal PBETeamInfo(PBETeamInfo other, byte? onlyForTrainer) { Id = other.Id; Trainers = new ReadOnlyCollection(other.Trainers.Select(t => new PBETrainerInfo(t, onlyForTrainer)).ToArray()); } internal void ToBytes(EndianBinaryWriter w) { w.WriteByte(Id); byte count = (byte)Trainers.Count; w.WriteByte(count); for (int i = 0; i < count; i++) { Trainers[i].ToBytes(w); } } } public PBEBattleType BattleType { get; } public PBEBattleFormat BattleFormat { get; } public PBEBattleTerrain BattleTerrain { get; } public PBEWeather Weather { get; } public PBESettings Settings { get; } public ReadOnlyCollection Teams { get; } internal PBEBattlePacket(PBEBattle battle) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteEnum(BattleType = battle.BattleType); w.WriteEnum(BattleFormat = battle.BattleFormat); w.WriteEnum(BattleTerrain = battle.BattleTerrain); w.WriteEnum(Weather = battle.Weather); w.WriteBytes((Settings = battle.Settings).ToBytes()); Teams = new ReadOnlyCollection(battle.Teams.Select(t => new PBETeamInfo(t)).ToArray()); for (int i = 0; i < 2; i++) { Teams[i].ToBytes(w); } Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEBattlePacket(byte[] data, EndianBinaryReader r) { Data = new ReadOnlyCollection(data); BattleType = r.ReadEnum(); BattleFormat = r.ReadEnum(); BattleTerrain = r.ReadEnum(); Weather = r.ReadEnum(); Settings = new PBESettings(r); Settings.MakeReadOnly(); var teams = new PBETeamInfo[2]; for (int i = 0; i < 2; i++) { teams[i] = new PBETeamInfo(r); } Teams = new ReadOnlyCollection(teams); } public PBEBattlePacket(PBEBattlePacket other, byte? onlyForTrainer) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteEnum(BattleType = other.BattleType); w.WriteEnum(BattleFormat = other.BattleFormat); w.WriteEnum(BattleTerrain = other.BattleTerrain); w.WriteEnum(Weather = other.Weather); w.WriteBytes((Settings = other.Settings).ToBytes()); Teams = new ReadOnlyCollection(other.Teams.Select(t => new PBETeamInfo(t, onlyForTrainer)).ToArray()); for (int i = 0; i < 2; i++) { Teams[i].ToBytes(w); } Data = new ReadOnlyCollection(ms.ToArray()); } } } ================================================ FILE: PokemonBattleEngine/Packets/BattleResultPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEBattleResultPacket : IPBEPacket { public const ushort ID = 0x26; public ReadOnlyCollection Data { get; } public PBEBattleResult BattleResult { get; } internal PBEBattleResultPacket(PBEBattleResult battleResult) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteEnum(BattleResult = battleResult); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEBattleResultPacket(byte[] data, EndianBinaryReader r) { Data = new ReadOnlyCollection(data); BattleResult = r.ReadEnum(); } } ================================================ FILE: PokemonBattleEngine/Packets/FleeResponsePacket.cs ================================================ using Kermalis.EndianBinaryIO; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEFleeResponsePacket : IPBEPacket { public const ushort ID = 0x38; public ReadOnlyCollection Data { get; } public PBEFleeResponsePacket() { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEFleeResponsePacket(byte[] data) { Data = new ReadOnlyCollection(data); } } ================================================ FILE: PokemonBattleEngine/Packets/HazePacket.cs ================================================ using Kermalis.EndianBinaryIO; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEHazePacket : IPBEPacket { public const ushort ID = 0x0B; public ReadOnlyCollection Data { get; } internal PBEHazePacket() { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEHazePacket(byte[] data) { Data = new ReadOnlyCollection(data); } } ================================================ FILE: PokemonBattleEngine/Packets/MatchCancelledPacket.cs ================================================ using Kermalis.EndianBinaryIO; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEMatchCancelledPacket : IPBEPacket { public const ushort ID = 0x02; public ReadOnlyCollection Data { get; } public PBEMatchCancelledPacket() { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEMatchCancelledPacket(byte[] data) { Data = new ReadOnlyCollection(data); } } ================================================ FILE: PokemonBattleEngine/Packets/PartyRequestPacket.cs ================================================ using Kermalis.EndianBinaryIO; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEPartyRequestPacket : IPBEPacket { public const ushort ID = 0x03; public ReadOnlyCollection Data { get; } public byte BattleId { get; } public bool RequireLegal { get; } public PBEPartyRequestPacket(byte battleId, bool requireLegal) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte(BattleId = battleId); w.WriteBoolean(RequireLegal = requireLegal); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEPartyRequestPacket(byte[] data, EndianBinaryReader r) { Data = new ReadOnlyCollection(data); BattleId = r.ReadByte(); RequireLegal = r.ReadBoolean(); } } ================================================ FILE: PokemonBattleEngine/Packets/PartyResponsePacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngine.Data.Legality; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEPartyResponsePacket : IPBEPacket { public const ushort ID = 0x04; public ReadOnlyCollection Data { get; } public IPBEPokemonCollection Party { get; } public PBEPartyResponsePacket(IPBEPokemonCollection party) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); (Party = party).ToBytes(w); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEPartyResponsePacket(byte[] data, EndianBinaryReader r) { Data = new ReadOnlyCollection(data); Party = new PBEReadOnlyPokemonCollection(r); } } public sealed class PBELegalPartyResponsePacket : IPBEPacket { public const ushort ID = 0x2D; public ReadOnlyCollection Data { get; } public PBELegalPokemonCollection Party { get; } public PBELegalPartyResponsePacket(PBELegalPokemonCollection party) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteBytes(party.Settings.ToBytes()); (Party = party).ToBytes(w); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBELegalPartyResponsePacket(byte[] data, EndianBinaryReader r) { Data = new ReadOnlyCollection(data); var s = new PBESettings(r); s.MakeReadOnly(); Party = new PBELegalPokemonCollection(s, r); } } ================================================ FILE: PokemonBattleEngine/Packets/PlayerJoinedPacket.cs ================================================ using Kermalis.EndianBinaryIO; using System; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEPlayerJoinedPacket : IPBEPacket { public const ushort ID = 0x01; public ReadOnlyCollection Data { get; } public string TrainerName { get; } public PBEPlayerJoinedPacket(string trainerName) { if (string.IsNullOrWhiteSpace(trainerName)) { throw new ArgumentOutOfRangeException(nameof(trainerName)); } using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteChars_NullTerminated(TrainerName = trainerName); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEPlayerJoinedPacket(byte[] data, EndianBinaryReader r) { Data = new ReadOnlyCollection(data); TrainerName = r.ReadString_NullTerminated(); } } ================================================ FILE: PokemonBattleEngine/Packets/ResponsePacket.cs ================================================ using Kermalis.EndianBinaryIO; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEResponsePacket : IPBEPacket { public const ushort ID = 0x00; public ReadOnlyCollection Data { get; } public PBEResponsePacket() { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEResponsePacket(byte[] data) { Data = new ReadOnlyCollection(data); } } ================================================ FILE: PokemonBattleEngine/Packets/SwitchInRequestPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBESwitchInRequestPacket : IPBEPacket { public const ushort ID = 0x23; public ReadOnlyCollection Data { get; } public PBETrainer Trainer { get; } public byte Amount { get; } internal PBESwitchInRequestPacket(PBETrainer trainer) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((Trainer = trainer).Id); w.WriteByte(Amount = trainer.SwitchInsRequired); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBESwitchInRequestPacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); Trainer = battle.Trainers[r.ReadByte()]; Amount = r.ReadByte(); } } ================================================ FILE: PokemonBattleEngine/Packets/SwitchInResponsePacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBESwitchInResponsePacket : IPBEPacket { public const ushort ID = 0x24; public ReadOnlyCollection Data { get; } public ReadOnlyCollection Switches { get; } public PBESwitchInResponsePacket(IList switches) { if (switches.Count == 0) { throw new ArgumentOutOfRangeException(nameof(switches)); } using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); byte count = (byte)(Switches = new ReadOnlyCollection(switches)).Count; w.WriteByte(count); for (int i = 0; i < count; i++) { Switches[i].ToBytes(w); } Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBESwitchInResponsePacket(byte[] data, EndianBinaryReader r) { Data = new ReadOnlyCollection(data); var switches = new PBESwitchIn[r.ReadByte()]; for (int i = 0; i < switches.Length; i++) { switches[i] = new PBESwitchIn(r); } Switches = new ReadOnlyCollection(switches); } } ================================================ FILE: PokemonBattleEngine/Packets/TurnBeganPacket.cs ================================================ using Kermalis.EndianBinaryIO; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBETurnBeganPacket : IPBEPacket { public const ushort ID = 0x27; public ReadOnlyCollection Data { get; } public ushort TurnNumber { get; } internal PBETurnBeganPacket(ushort turnNumber) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteUInt16(TurnNumber = turnNumber); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBETurnBeganPacket(byte[] data, EndianBinaryReader r) { Data = new ReadOnlyCollection(data); TurnNumber = r.ReadUInt16(); } } ================================================ FILE: PokemonBattleEngine/Packets/_AbilityPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEAbilityPacket : IPBEPacket { public const ushort ID = 0x19; public ReadOnlyCollection Data { get; } public PBETrainer AbilityOwnerTrainer { get; } public PBEFieldPosition AbilityOwner { get; } public PBETrainer Pokemon2Trainer { get; } public PBEFieldPosition Pokemon2 { get; } public PBEAbility Ability { get; } public PBEAbilityAction AbilityAction { get; } internal PBEAbilityPacket(PBEBattlePokemon abilityOwner, PBEBattlePokemon pokemon2, PBEAbility ability, PBEAbilityAction abilityAction) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((AbilityOwnerTrainer = abilityOwner.Trainer).Id); w.WriteEnum(AbilityOwner = abilityOwner.FieldPosition); w.WriteByte((Pokemon2Trainer = pokemon2.Trainer).Id); w.WriteEnum(Pokemon2 = pokemon2.FieldPosition); w.WriteEnum(Ability = ability); w.WriteEnum(AbilityAction = abilityAction); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEAbilityPacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); AbilityOwnerTrainer = battle.Trainers[r.ReadByte()]; AbilityOwner = r.ReadEnum(); Pokemon2Trainer = battle.Trainers[r.ReadByte()]; Pokemon2 = r.ReadEnum(); Ability = r.ReadEnum(); AbilityAction = r.ReadEnum(); } } ================================================ FILE: PokemonBattleEngine/Packets/_AbilityReplacedPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEAbilityReplacedPacket : IPBEPacket { public const ushort ID = 0x2C; public ReadOnlyCollection Data { get; } public PBETrainer AbilityOwnerTrainer { get; } public PBEFieldPosition AbilityOwner { get; } public PBEAbility? OldAbility { get; } public PBEAbility NewAbility { get; } internal PBEAbilityReplacedPacket(PBEBattlePokemon abilityOwner, PBEAbility? oldAbility, PBEAbility newAbility) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((AbilityOwnerTrainer = abilityOwner.Trainer).Id); w.WriteEnum(AbilityOwner = abilityOwner.FieldPosition); w.WriteBoolean(oldAbility is not null); if (oldAbility is not null) { w.WriteEnum((OldAbility = oldAbility).Value); } w.WriteEnum(NewAbility = newAbility); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEAbilityReplacedPacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); AbilityOwnerTrainer = battle.Trainers[r.ReadByte()]; AbilityOwner = r.ReadEnum(); if (r.ReadBoolean()) { OldAbility = r.ReadEnum(); } NewAbility = r.ReadEnum(); } } ================================================ FILE: PokemonBattleEngine/Packets/_BattleStatusPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEBattleStatusPacket : IPBEPacket { public const ushort ID = 0x21; public ReadOnlyCollection Data { get; } public PBEBattleStatus BattleStatus { get; } public PBEBattleStatusAction BattleStatusAction { get; } internal PBEBattleStatusPacket(PBEBattleStatus battleStatus, PBEBattleStatusAction battleStatusAction) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteEnum(BattleStatus = battleStatus); w.WriteEnum(BattleStatusAction = battleStatusAction); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEBattleStatusPacket(byte[] data, EndianBinaryReader r) { Data = new ReadOnlyCollection(data); BattleStatus = r.ReadEnum(); BattleStatusAction = r.ReadEnum(); } } ================================================ FILE: PokemonBattleEngine/Packets/_CapturePacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBECapturePacket : IPBEPacket { public const ushort ID = 0x3B; public ReadOnlyCollection Data { get; } public PBETrainer PokemonTrainer { get; } public PBEFieldPosition Pokemon { get; } public PBEItem Ball { get; } public byte NumShakes { get; } public bool Success { get; } public bool Critical { get; } internal PBECapturePacket(PBEBattlePokemon pokemon, PBEItem ball, byte numShakes, bool success, bool critical) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((PokemonTrainer = pokemon.Trainer).Id); w.WriteEnum(Pokemon = pokemon.FieldPosition); w.WriteEnum(Ball = ball); w.WriteByte(NumShakes = numShakes); w.WriteBoolean(Success = success); w.WriteBoolean(Critical = critical); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBECapturePacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); PokemonTrainer = battle.Trainers[r.ReadByte()]; Pokemon = r.ReadEnum(); Ball = r.ReadEnum(); NumShakes = r.ReadByte(); Success = r.ReadBoolean(); Critical = r.ReadBoolean(); } } ================================================ FILE: PokemonBattleEngine/Packets/_FleeFailedPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEFleeFailedPacket : IPBEPacket { public const ushort ID = 0x39; public ReadOnlyCollection Data { get; } public PBETrainer PokemonTrainer { get; } public PBEFieldPosition Pokemon { get; } internal PBEFleeFailedPacket(PBEBattlePokemon pokemon) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((PokemonTrainer = pokemon.Trainer).Id); w.WriteEnum(Pokemon = pokemon.FieldPosition); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEFleeFailedPacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); PokemonTrainer = battle.Trainers[r.ReadByte()]; Pokemon = r.ReadEnum(); } } ================================================ FILE: PokemonBattleEngine/Packets/_IllusionPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEIllusionPacket : IPBEPacket { public const ushort ID = 0x25; public ReadOnlyCollection Data { get; } public PBETrainer PokemonTrainer { get; } public PBEFieldPosition Pokemon { get; } public PBEGender ActualGender { get; } public PBEItem ActualCaughtBall { get; } public bool ActualShiny { get; } public string ActualNickname { get; } public PBESpecies ActualSpecies { get; } public PBEForm ActualForm { get; } public PBEType ActualType1 { get; } public PBEType ActualType2 { get; } public float ActualWeight { get; } internal PBEIllusionPacket(PBEBattlePokemon pokemon) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((PokemonTrainer = pokemon.Trainer).Id); w.WriteEnum(Pokemon = pokemon.FieldPosition); w.WriteEnum(ActualGender = pokemon.Gender); w.WriteEnum(ActualCaughtBall = pokemon.CaughtBall); w.WriteChars_NullTerminated(ActualNickname = pokemon.Nickname); w.WriteBoolean(ActualShiny = pokemon.Shiny); w.WriteEnum(ActualSpecies = pokemon.Species); w.WriteEnum(ActualForm = pokemon.Form); w.WriteEnum(ActualType1 = pokemon.Type1); w.WriteEnum(ActualType2 = pokemon.Type2); w.WriteSingle(ActualWeight = pokemon.Weight); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEIllusionPacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); PokemonTrainer = battle.Trainers[r.ReadByte()]; Pokemon = r.ReadEnum(); ActualGender = r.ReadEnum(); ActualCaughtBall = r.ReadEnum(); ActualNickname = r.ReadString_NullTerminated(); ActualShiny = r.ReadBoolean(); ActualSpecies = r.ReadEnum(); ActualForm = r.ReadEnum(); ActualType1 = r.ReadEnum(); ActualType2 = r.ReadEnum(); ActualWeight = r.ReadSingle(); } } ================================================ FILE: PokemonBattleEngine/Packets/_ItemPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEItemPacket : IPBEPacket { public const ushort ID = 0x16; public ReadOnlyCollection Data { get; } public PBETrainer ItemHolderTrainer { get; } public PBEFieldPosition ItemHolder { get; } public PBETrainer Pokemon2Trainer { get; } public PBEFieldPosition Pokemon2 { get; } public PBEItem Item { get; } public PBEItemAction ItemAction { get; } internal PBEItemPacket(PBEBattlePokemon itemHolder, PBEBattlePokemon pokemon2, PBEItem item, PBEItemAction itemAction) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((ItemHolderTrainer = itemHolder.Trainer).Id); w.WriteEnum(ItemHolder = itemHolder.FieldPosition); w.WriteByte((Pokemon2Trainer = pokemon2.Trainer).Id); w.WriteEnum(Pokemon2 = pokemon2.FieldPosition); w.WriteEnum(Item = item); w.WriteEnum(ItemAction = itemAction); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEItemPacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); ItemHolderTrainer = battle.Trainers[r.ReadByte()]; ItemHolder = r.ReadEnum(); Pokemon2Trainer = battle.Trainers[r.ReadByte()]; Pokemon2 = r.ReadEnum(); Item = r.ReadEnum(); ItemAction = r.ReadEnum(); } } ================================================ FILE: PokemonBattleEngine/Packets/_ItemTurnPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEItemTurnPacket : IPBEPacket { public const ushort ID = 0x3A; public ReadOnlyCollection Data { get; } public PBETrainer ItemUserTrainer { get; } public PBEFieldPosition ItemUser { get; } public PBEItem Item { get; } public PBEItemTurnAction ItemAction { get; } internal PBEItemTurnPacket(PBEBattlePokemon itemUserHolder, PBEItem item, PBEItemTurnAction itemAction) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((ItemUserTrainer = itemUserHolder.Trainer).Id); w.WriteEnum(ItemUser = itemUserHolder.FieldPosition); w.WriteEnum(Item = item); w.WriteEnum(ItemAction = itemAction); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEItemTurnPacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); ItemUserTrainer = battle.Trainers[r.ReadByte()]; ItemUser = r.ReadEnum(); Item = r.ReadEnum(); ItemAction = r.ReadEnum(); } } ================================================ FILE: PokemonBattleEngine/Packets/_MoveCritPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEMoveCritPacket : IPBEPacket { public const ushort ID = 0x0F; public ReadOnlyCollection Data { get; } public PBETrainer VictimTrainer { get; } public PBEFieldPosition Victim { get; } internal PBEMoveCritPacket(PBEBattlePokemon victim) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((VictimTrainer = victim.Trainer).Id); w.WriteEnum(Victim = victim.FieldPosition); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEMoveCritPacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); VictimTrainer = battle.Trainers[r.ReadByte()]; Victim = r.ReadEnum(); } } ================================================ FILE: PokemonBattleEngine/Packets/_MoveLockPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEMoveLockPacket : IPBEPacket { public const ushort ID = 0x28; public ReadOnlyCollection Data { get; } public PBETrainer MoveUserTrainer { get; } public PBEFieldPosition MoveUser { get; } public PBEMoveLockType MoveLockType { get; } public PBEMove LockedMove { get; } public PBETurnTarget? LockedTargets { get; } internal PBEMoveLockPacket(PBEBattlePokemon moveUser, PBEMoveLockType moveLockType, PBEMove lockedMove, PBETurnTarget? lockedTargets = null) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((MoveUserTrainer = moveUser.Trainer).Id); w.WriteEnum(MoveUser = moveUser.FieldPosition); w.WriteEnum(MoveLockType = moveLockType); w.WriteEnum(LockedMove = lockedMove); w.WriteBoolean(lockedTargets is not null); if (lockedTargets is not null) { w.WriteEnum((LockedTargets = lockedTargets).Value); } Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEMoveLockPacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); MoveUserTrainer = battle.Trainers[r.ReadByte()]; MoveUser = r.ReadEnum(); MoveLockType = r.ReadEnum(); LockedMove = r.ReadEnum(); if (r.ReadBoolean()) { LockedTargets = r.ReadEnum(); } } } ================================================ FILE: PokemonBattleEngine/Packets/_MovePPChangedPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEMovePPChangedPacket : IPBEPacket { public const ushort ID = 0x17; public ReadOnlyCollection Data { get; } public PBETrainer MoveUserTrainer { get; } public PBEFieldPosition MoveUser { get; } public PBEMove Move { get; } public int AmountReduced { get; } internal PBEMovePPChangedPacket(PBEBattlePokemon moveUser, PBEMove move, int amountReduced) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((MoveUserTrainer = moveUser.Trainer).Id); w.WriteEnum(MoveUser = moveUser.FieldPosition); w.WriteEnum(Move = move); w.WriteInt32(AmountReduced = amountReduced); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEMovePPChangedPacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); MoveUserTrainer = battle.Trainers[r.ReadByte()]; MoveUser = r.ReadEnum(); Move = r.ReadEnum(); AmountReduced = r.ReadInt32(); } } ================================================ FILE: PokemonBattleEngine/Packets/_MoveResultPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEMoveResultPacket : IPBEPacket { public const ushort ID = 0x15; public ReadOnlyCollection Data { get; } public PBETrainer MoveUserTrainer { get; } public PBEFieldPosition MoveUser { get; } public PBETrainer Pokemon2Trainer { get; } public PBEFieldPosition Pokemon2 { get; } public PBEResult Result { get; } internal PBEMoveResultPacket(PBEBattlePokemon moveUser, PBEBattlePokemon pokemon2, PBEResult result) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((MoveUserTrainer = moveUser.Trainer).Id); w.WriteEnum(MoveUser = moveUser.FieldPosition); w.WriteByte((Pokemon2Trainer = pokemon2.Trainer).Id); w.WriteEnum(Pokemon2 = pokemon2.FieldPosition); w.WriteEnum(Result = result); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEMoveResultPacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); MoveUserTrainer = battle.Trainers[r.ReadByte()]; MoveUser = r.ReadEnum(); Pokemon2Trainer = battle.Trainers[r.ReadByte()]; Pokemon2 = r.ReadEnum(); Result = r.ReadEnum(); } } ================================================ FILE: PokemonBattleEngine/Packets/_MoveUsedPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEMoveUsedPacket : IPBEPacket { public const ushort ID = 0x09; public ReadOnlyCollection Data { get; } public PBETrainer MoveUserTrainer { get; } public PBEFieldPosition MoveUser { get; } public PBEMove Move { get; } public bool Owned { get; } internal PBEMoveUsedPacket(PBEBattlePokemon moveUser, PBEMove move, bool owned) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((MoveUserTrainer = moveUser.Trainer).Id); w.WriteEnum(MoveUser = moveUser.FieldPosition); w.WriteEnum(Move = move); w.WriteBoolean(Owned = owned); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEMoveUsedPacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); MoveUserTrainer = battle.Trainers[r.ReadByte()]; MoveUser = r.ReadEnum(); Move = r.ReadEnum(); Owned = r.ReadBoolean(); } } ================================================ FILE: PokemonBattleEngine/Packets/_PkmnEXPChangedPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEPkmnEXPChangedPacket : IPBEPacket { public const ushort ID = 0x3D; public ReadOnlyCollection Data { get; } public PBETrainer PokemonTrainer { get; } public byte Pokemon { get; } public uint OldEXP { get; } public uint NewEXP { get; } internal PBEPkmnEXPChangedPacket(PBEBattlePokemon pokemon, uint oldEXP) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((PokemonTrainer = pokemon.Trainer).Id); w.WriteByte(Pokemon = pokemon.Id); w.WriteUInt32(OldEXP = oldEXP); w.WriteUInt32(NewEXP = pokemon.EXP); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEPkmnEXPChangedPacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); PokemonTrainer = battle.Trainers[r.ReadByte()]; Pokemon = r.ReadByte(); OldEXP = r.ReadUInt32(); NewEXP = r.ReadUInt32(); } } ================================================ FILE: PokemonBattleEngine/Packets/_PkmnEXPEarnedPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEPkmnEXPEarnedPacket : IPBEPacket { public const ushort ID = 0x3E; public ReadOnlyCollection Data { get; } public PBETrainer PokemonTrainer { get; } public byte Pokemon { get; } public uint Earned { get; } internal PBEPkmnEXPEarnedPacket(PBEBattlePokemon pokemon, uint earned) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((PokemonTrainer = pokemon.Trainer).Id); w.WriteByte(Pokemon = pokemon.Id); w.WriteUInt32(Earned = earned); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEPkmnEXPEarnedPacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); PokemonTrainer = battle.Trainers[r.ReadByte()]; Pokemon = r.ReadByte(); Earned = r.ReadUInt32(); } } ================================================ FILE: PokemonBattleEngine/Packets/_PkmnFaintedPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public interface IPBEPkmnFaintedPacket : IPBEPacket { PBETrainer PokemonTrainer { get; } PBEFieldPosition OldPosition { get; } } public sealed class PBEPkmnFaintedPacket : IPBEPkmnFaintedPacket { public const ushort ID = 0x0E; public ReadOnlyCollection Data { get; } public PBETrainer PokemonTrainer { get; } public byte Pokemon { get; } public PBEFieldPosition OldPosition { get; } internal PBEPkmnFaintedPacket(PBEBattlePokemon pokemon, PBEFieldPosition oldPosition) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((PokemonTrainer = pokemon.Trainer).Id); w.WriteByte(Pokemon = pokemon.Id); w.WriteEnum(OldPosition = oldPosition); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEPkmnFaintedPacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); PokemonTrainer = battle.Trainers[r.ReadByte()]; Pokemon = r.ReadByte(); OldPosition = r.ReadEnum(); } } public sealed class PBEPkmnFaintedPacket_Hidden : IPBEPkmnFaintedPacket { public const ushort ID = 0x2F; public ReadOnlyCollection Data { get; } public PBETrainer PokemonTrainer { get; } public PBEFieldPosition OldPosition { get; } public PBEPkmnFaintedPacket_Hidden(PBEPkmnFaintedPacket other) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((PokemonTrainer = other.PokemonTrainer).Id); w.WriteEnum(OldPosition = other.OldPosition); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEPkmnFaintedPacket_Hidden(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); PokemonTrainer = battle.Trainers[r.ReadByte()]; OldPosition = r.ReadEnum(); } } ================================================ FILE: PokemonBattleEngine/Packets/_PkmnFormChangedPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public interface IPBEPkmnFormChangedPacket : IPBEPacket { PBETrainer PokemonTrainer { get; } PBEFieldPosition Pokemon { get; } float NewHPPercentage { get; } PBEAbility NewKnownAbility { get; } PBEForm NewForm { get; } PBEType NewType1 { get; } PBEType NewType2 { get; } float NewWeight { get; } bool IsRevertForm { get; } } public sealed class PBEPkmnFormChangedPacket : IPBEPkmnFormChangedPacket { public const ushort ID = 0x29; public ReadOnlyCollection Data { get; } public PBETrainer PokemonTrainer { get; } public PBEFieldPosition Pokemon { get; } public ushort NewHP { get; } public ushort NewMaxHP { get; } public float NewHPPercentage { get; } public ushort NewAttack { get; } public ushort NewDefense { get; } public ushort NewSpAttack { get; } public ushort NewSpDefense { get; } public ushort NewSpeed { get; } public PBEAbility NewAbility { get; } public PBEAbility NewKnownAbility { get; } public PBEForm NewForm { get; } public PBEType NewType1 { get; } public PBEType NewType2 { get; } public float NewWeight { get; } public bool IsRevertForm { get; } internal PBEPkmnFormChangedPacket(PBEBattlePokemon pokemon, bool isRevertForm) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((PokemonTrainer = pokemon.Trainer).Id); w.WriteEnum(Pokemon = pokemon.FieldPosition); w.WriteUInt16(NewHP = pokemon.HP); w.WriteUInt16(NewMaxHP = pokemon.MaxHP); w.WriteSingle(NewHPPercentage = pokemon.HPPercentage); w.WriteUInt16(NewAttack = pokemon.Attack); w.WriteUInt16(NewDefense = pokemon.Defense); w.WriteUInt16(NewSpAttack = pokemon.SpAttack); w.WriteUInt16(NewSpDefense = pokemon.SpDefense); w.WriteUInt16(NewSpeed = pokemon.Speed); w.WriteEnum(NewAbility = pokemon.Ability); w.WriteEnum(NewKnownAbility = pokemon.KnownAbility); w.WriteEnum(NewForm = pokemon.Form); w.WriteEnum(NewType1 = pokemon.Type1); w.WriteEnum(NewType2 = pokemon.Type2); w.WriteSingle(NewWeight = pokemon.Weight); w.WriteBoolean(IsRevertForm = isRevertForm); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEPkmnFormChangedPacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); PokemonTrainer = battle.Trainers[r.ReadByte()]; Pokemon = r.ReadEnum(); NewHP = r.ReadUInt16(); NewMaxHP = r.ReadUInt16(); NewHPPercentage = r.ReadSingle(); NewAttack = r.ReadUInt16(); NewDefense = r.ReadUInt16(); NewSpAttack = r.ReadUInt16(); NewSpDefense = r.ReadUInt16(); NewSpeed = r.ReadUInt16(); NewAbility = r.ReadEnum(); NewKnownAbility = r.ReadEnum(); NewForm = r.ReadEnum(); NewType1 = r.ReadEnum(); NewType2 = r.ReadEnum(); NewWeight = r.ReadSingle(); } } public sealed class PBEPkmnFormChangedPacket_Hidden : IPBEPkmnFormChangedPacket { public const ushort ID = 0x34; public ReadOnlyCollection Data { get; } public PBETrainer PokemonTrainer { get; } public PBEFieldPosition Pokemon { get; } public float NewHPPercentage { get; } public PBEAbility NewKnownAbility { get; } public PBEForm NewForm { get; } public PBEType NewType1 { get; } public PBEType NewType2 { get; } public float NewWeight { get; } public bool IsRevertForm { get; } public PBEPkmnFormChangedPacket_Hidden(PBEPkmnFormChangedPacket other) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((PokemonTrainer = other.PokemonTrainer).Id); w.WriteEnum(Pokemon = other.Pokemon); w.WriteSingle(NewHPPercentage = other.NewHPPercentage); w.WriteEnum(NewKnownAbility = other.NewKnownAbility); w.WriteEnum(NewForm = other.NewForm); w.WriteEnum(NewType1 = other.NewType1); w.WriteEnum(NewType2 = other.NewType2); w.WriteSingle(NewWeight = other.NewWeight); w.WriteBoolean(IsRevertForm = other.IsRevertForm); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEPkmnFormChangedPacket_Hidden(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); PokemonTrainer = battle.Trainers[r.ReadByte()]; Pokemon = r.ReadEnum(); NewHPPercentage = r.ReadSingle(); NewKnownAbility = r.ReadEnum(); NewForm = r.ReadEnum(); NewType1 = r.ReadEnum(); NewType2 = r.ReadEnum(); NewWeight = r.ReadSingle(); } } ================================================ FILE: PokemonBattleEngine/Packets/_PkmnHPChangedPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public interface IPBEPkmnHPChangedPacket : IPBEPacket { PBETrainer PokemonTrainer { get; } PBEFieldPosition Pokemon { get; } float OldHPPercentage { get; } float NewHPPercentage { get; } } public sealed class PBEPkmnHPChangedPacket : IPBEPkmnHPChangedPacket { public const ushort ID = 0x0A; public ReadOnlyCollection Data { get; } public PBETrainer PokemonTrainer { get; } public PBEFieldPosition Pokemon { get; } public ushort OldHP { get; } public ushort NewHP { get; } public float OldHPPercentage { get; } public float NewHPPercentage { get; } internal PBEPkmnHPChangedPacket(PBEBattlePokemon pokemon, ushort oldHP, float oldHPPercentage) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((PokemonTrainer = pokemon.Trainer).Id); w.WriteEnum(Pokemon = pokemon.FieldPosition); w.WriteUInt16(OldHP = oldHP); w.WriteUInt16(NewHP = pokemon.HP); w.WriteSingle(OldHPPercentage = oldHPPercentage); w.WriteSingle(NewHPPercentage = pokemon.HPPercentage); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEPkmnHPChangedPacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); PokemonTrainer = battle.Trainers[r.ReadByte()]; Pokemon = r.ReadEnum(); OldHP = r.ReadUInt16(); NewHP = r.ReadUInt16(); OldHPPercentage = r.ReadSingle(); NewHPPercentage = r.ReadSingle(); } } public sealed class PBEPkmnHPChangedPacket_Hidden : IPBEPkmnHPChangedPacket { public const ushort ID = 0x35; public ReadOnlyCollection Data { get; } public PBETrainer PokemonTrainer { get; } public PBEFieldPosition Pokemon { get; } public float OldHPPercentage { get; } public float NewHPPercentage { get; } public PBEPkmnHPChangedPacket_Hidden(PBEPkmnHPChangedPacket other) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((PokemonTrainer = other.PokemonTrainer).Id); w.WriteEnum(Pokemon = other.Pokemon); w.WriteSingle(OldHPPercentage = other.OldHPPercentage); w.WriteSingle(NewHPPercentage = other.NewHPPercentage); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEPkmnHPChangedPacket_Hidden(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); PokemonTrainer = battle.Trainers[r.ReadByte()]; Pokemon = r.ReadEnum(); OldHPPercentage = r.ReadSingle(); NewHPPercentage = r.ReadSingle(); } } ================================================ FILE: PokemonBattleEngine/Packets/_PkmnLevelChangedPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEPkmnLevelChangedPacket : IPBEPacket { public const ushort ID = 0x3F; public ReadOnlyCollection Data { get; } public PBETrainer PokemonTrainer { get; } public byte Pokemon { get; } public byte NewLevel { get; } internal PBEPkmnLevelChangedPacket(PBEBattlePokemon pokemon) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((PokemonTrainer = pokemon.Trainer).Id); w.WriteByte(Pokemon = pokemon.Id); w.WriteByte(NewLevel = pokemon.Level); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEPkmnLevelChangedPacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); PokemonTrainer = battle.Trainers[r.ReadByte()]; Pokemon = r.ReadByte(); NewLevel = r.ReadByte(); } } ================================================ FILE: PokemonBattleEngine/Packets/_PkmnStatChangedPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEPkmnStatChangedPacket : IPBEPacket { public const ushort ID = 0x10; public ReadOnlyCollection Data { get; } public PBETrainer PokemonTrainer { get; } public PBEFieldPosition Pokemon { get; } public PBEStat Stat { get; } public sbyte OldValue { get; } public sbyte NewValue { get; } internal PBEPkmnStatChangedPacket(PBEBattlePokemon pokemon, PBEStat stat, sbyte oldValue, sbyte newValue) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((PokemonTrainer = pokemon.Trainer).Id); w.WriteEnum(Pokemon = pokemon.FieldPosition); w.WriteEnum(Stat = stat); w.WriteSByte(OldValue = oldValue); w.WriteSByte(NewValue = newValue); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEPkmnStatChangedPacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); PokemonTrainer = battle.Trainers[r.ReadByte()]; Pokemon = r.ReadEnum(); Stat = r.ReadEnum(); OldValue = r.ReadSByte(); NewValue = r.ReadSByte(); } } ================================================ FILE: PokemonBattleEngine/Packets/_PkmnSwitchInPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public interface IPBEPkmnSwitchInInfo_Hidden : IPBEPkmnAppearedInfo_Hidden { PBEItem CaughtBall { get; } } public interface IPBEPkmnSwitchInPacket : IPBEPacket { PBETrainer Trainer { get; } IReadOnlyList SwitchIns { get; } bool Forced { get; } PBETrainer? ForcedByPokemonTrainer { get; } PBEFieldPosition ForcedByPokemon { get; } } public sealed class PBEPkmnSwitchInPacket : IPBEPkmnSwitchInPacket { public const ushort ID = 0x06; public ReadOnlyCollection Data { get; } public PBETrainer Trainer { get; } public ReadOnlyCollection SwitchIns { get; } IReadOnlyList IPBEPkmnSwitchInPacket.SwitchIns => SwitchIns; public bool Forced { get; } public PBETrainer? ForcedByPokemonTrainer { get; } public PBEFieldPosition ForcedByPokemon { get; } internal PBEPkmnSwitchInPacket(PBETrainer trainer, IList switchIns, PBEBattlePokemon? forcedByPokemon = null) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((Trainer = trainer).Id); byte count = (byte)(SwitchIns = new ReadOnlyCollection(switchIns)).Count; w.WriteByte(count); for (int i = 0; i < count; i++) { SwitchIns[i].ToBytes(w); } w.WriteBoolean(Forced = forcedByPokemon is not null); if (forcedByPokemon is not null) { w.WriteByte((ForcedByPokemonTrainer = forcedByPokemon.Trainer).Id); w.WriteEnum(ForcedByPokemon = forcedByPokemon.FieldPosition); } Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEPkmnSwitchInPacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); Trainer = battle.Trainers[r.ReadByte()]; var switches = new PBEPkmnAppearedInfo[r.ReadByte()]; for (int i = 0; i < switches.Length; i++) { switches[i] = new PBEPkmnAppearedInfo(r); } SwitchIns = new ReadOnlyCollection(switches); Forced = r.ReadBoolean(); if (Forced) { ForcedByPokemonTrainer = battle.Trainers[r.ReadByte()]; ForcedByPokemon = r.ReadEnum(); } } } public sealed class PBEPkmnSwitchInPacket_Hidden : IPBEPkmnSwitchInPacket { public const ushort ID = 0x36; public ReadOnlyCollection Data { get; } public sealed class PBEPkmnSwitchInInfo : IPBEPkmnSwitchInInfo_Hidden { public PBESpecies Species { get; } public PBEForm Form { get; } public string Nickname { get; } public byte Level { get; } public bool Shiny { get; } public PBEGender Gender { get; } public PBEItem CaughtBall { get; } public float HPPercentage { get; } public PBEStatus1 Status1 { get; } public PBEFieldPosition FieldPosition { get; } internal PBEPkmnSwitchInInfo(PBEPkmnAppearedInfo other) { Species = other.Species; Form = other.Form; Nickname = other.Nickname; Level = other.Level; Shiny = other.Shiny; Gender = other.Gender; CaughtBall = other.CaughtBall; HPPercentage = other.HPPercentage; Status1 = other.Status1; FieldPosition = other.FieldPosition; } internal PBEPkmnSwitchInInfo(EndianBinaryReader r) { Species = r.ReadEnum(); Form = r.ReadEnum(); Nickname = r.ReadString_NullTerminated(); Level = r.ReadByte(); Shiny = r.ReadBoolean(); Gender = r.ReadEnum(); CaughtBall = r.ReadEnum(); HPPercentage = r.ReadSingle(); Status1 = r.ReadEnum(); FieldPosition = r.ReadEnum(); } internal void ToBytes(EndianBinaryWriter w) { w.WriteEnum(Species); w.WriteEnum(Form); w.WriteChars_NullTerminated(Nickname); w.WriteByte(Level); w.WriteBoolean(Shiny); w.WriteEnum(Gender); w.WriteEnum(CaughtBall); w.WriteSingle(HPPercentage); w.WriteEnum(Status1); w.WriteEnum(FieldPosition); } } public PBETrainer Trainer { get; } public ReadOnlyCollection SwitchIns { get; } IReadOnlyList IPBEPkmnSwitchInPacket.SwitchIns => SwitchIns; public bool Forced { get; } public PBETrainer? ForcedByPokemonTrainer { get; } public PBEFieldPosition ForcedByPokemon { get; } public PBEPkmnSwitchInPacket_Hidden(PBEPkmnSwitchInPacket other) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((Trainer = other.Trainer).Id); var switchIns = new PBEPkmnSwitchInInfo[other.SwitchIns.Count]; for (int i = 0; i < switchIns.Length; i++) { switchIns[i] = new PBEPkmnSwitchInInfo(other.SwitchIns[i]); } byte count = (byte)(SwitchIns = new ReadOnlyCollection(switchIns)).Count; w.WriteByte(count); for (int i = 0; i < count; i++) { SwitchIns[i].ToBytes(w); } w.WriteBoolean(Forced = other.Forced); if (Forced) { w.WriteByte((ForcedByPokemonTrainer = other.ForcedByPokemonTrainer!).Id); w.WriteEnum(ForcedByPokemon = other.ForcedByPokemon); } Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEPkmnSwitchInPacket_Hidden(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); Trainer = battle.Trainers[r.ReadByte()]; var switches = new PBEPkmnSwitchInInfo[r.ReadByte()]; for (int i = 0; i < switches.Length; i++) { switches[i] = new PBEPkmnSwitchInInfo(r); } SwitchIns = new ReadOnlyCollection(switches); Forced = r.ReadBoolean(); if (Forced) { ForcedByPokemonTrainer = battle.Trainers[r.ReadByte()]; ForcedByPokemon = r.ReadEnum(); } } } ================================================ FILE: PokemonBattleEngine/Packets/_PkmnSwitchOutPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public interface IPBEPkmnSwitchOutPacket : IPBEPacket { PBETrainer PokemonTrainer { get; } PBEFieldPosition OldPosition { get; } bool Forced { get; } PBETrainer? ForcedByPokemonTrainer { get; } PBEFieldPosition ForcedByPokemon { get; } } public sealed class PBEPkmnSwitchOutPacket : IPBEPkmnSwitchOutPacket { public const ushort ID = 0x0C; public ReadOnlyCollection Data { get; } public PBETrainer PokemonTrainer { get; } public byte Pokemon { get; } public PBEFieldPosition OldPosition { get; } public bool Forced { get; } public PBETrainer? ForcedByPokemonTrainer { get; } public PBEFieldPosition ForcedByPokemon { get; } internal PBEPkmnSwitchOutPacket(PBEBattlePokemon pokemon, PBEFieldPosition oldPosition, PBEBattlePokemon? forcedByPokemon = null) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((PokemonTrainer = pokemon.Trainer).Id); w.WriteByte(Pokemon = pokemon.Id); w.WriteEnum(OldPosition = oldPosition); w.WriteBoolean(Forced = forcedByPokemon is not null); if (forcedByPokemon is not null) { w.WriteByte((ForcedByPokemonTrainer = forcedByPokemon.Trainer).Id); w.WriteEnum(ForcedByPokemon = forcedByPokemon.FieldPosition); } Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEPkmnSwitchOutPacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); PokemonTrainer = battle.Trainers[r.ReadByte()]; Pokemon = r.ReadByte(); OldPosition = r.ReadEnum(); Forced = r.ReadBoolean(); if (Forced) { ForcedByPokemonTrainer = battle.Trainers[r.ReadByte()]; ForcedByPokemon = r.ReadEnum(); } } } public sealed class PBEPkmnSwitchOutPacket_Hidden : IPBEPkmnSwitchOutPacket { public const ushort ID = 0x37; public ReadOnlyCollection Data { get; } public PBETrainer PokemonTrainer { get; } public PBEFieldPosition OldPosition { get; } public bool Forced { get; } public PBETrainer? ForcedByPokemonTrainer { get; } public PBEFieldPosition ForcedByPokemon { get; } public PBEPkmnSwitchOutPacket_Hidden(PBEPkmnSwitchOutPacket other) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((PokemonTrainer = other.PokemonTrainer).Id); w.WriteEnum(OldPosition = other.OldPosition); w.WriteBoolean(Forced = other.Forced); if (Forced) { w.WriteByte((ForcedByPokemonTrainer = other.ForcedByPokemonTrainer!).Id); w.WriteEnum(ForcedByPokemon = other.ForcedByPokemon); } Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEPkmnSwitchOutPacket_Hidden(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); PokemonTrainer = battle.Trainers[r.ReadByte()]; OldPosition = r.ReadEnum(); Forced = r.ReadBoolean(); if (Forced) { ForcedByPokemonTrainer = battle.Trainers[r.ReadByte()]; ForcedByPokemon = r.ReadEnum(); } } } ================================================ FILE: PokemonBattleEngine/Packets/_PsychUpPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEPsychUpPacket : IPBEPacket { public const ushort ID = 0x22; public ReadOnlyCollection Data { get; } public PBETrainer UserTrainer { get; } public PBEFieldPosition User { get; } public PBETrainer TargetTrainer { get; } public PBEFieldPosition Target { get; } public sbyte AttackChange { get; } public sbyte DefenseChange { get; } public sbyte SpAttackChange { get; } public sbyte SpDefenseChange { get; } public sbyte SpeedChange { get; } public sbyte AccuracyChange { get; } public sbyte EvasionChange { get; } internal PBEPsychUpPacket(PBEBattlePokemon user, PBEBattlePokemon target) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((UserTrainer = user.Trainer).Id); w.WriteEnum(User = user.FieldPosition); w.WriteByte((TargetTrainer = target.Trainer).Id); w.WriteEnum(Target = target.FieldPosition); w.WriteSByte(AttackChange = target.AttackChange); w.WriteSByte(DefenseChange = target.DefenseChange); w.WriteSByte(SpAttackChange = target.SpAttackChange); w.WriteSByte(SpDefenseChange = target.SpDefenseChange); w.WriteSByte(SpeedChange = target.SpeedChange); w.WriteSByte(AccuracyChange = target.AccuracyChange); w.WriteSByte(EvasionChange = target.EvasionChange); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEPsychUpPacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); UserTrainer = battle.Trainers[r.ReadByte()]; User = r.ReadEnum(); TargetTrainer = battle.Trainers[r.ReadByte()]; Target = r.ReadEnum(); AttackChange = r.ReadSByte(); DefenseChange = r.ReadSByte(); SpAttackChange = r.ReadSByte(); SpDefenseChange = r.ReadSByte(); SpeedChange = r.ReadSByte(); AccuracyChange = r.ReadSByte(); EvasionChange = r.ReadSByte(); } } ================================================ FILE: PokemonBattleEngine/Packets/_ReflectTypePacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEReflectTypePacket : IPBEPacket { public const ushort ID = 0x2E; public ReadOnlyCollection Data { get; } public PBETrainer UserTrainer { get; } public PBEFieldPosition User { get; } public PBETrainer TargetTrainer { get; } public PBEFieldPosition Target { get; } public PBEType Type1 { get; } public PBEType Type2 { get; } internal PBEReflectTypePacket(PBEBattlePokemon user, PBEBattlePokemon target) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((UserTrainer = user.Trainer).Id); w.WriteEnum(User = user.FieldPosition); w.WriteByte((TargetTrainer = target.Trainer).Id); w.WriteEnum(Target = target.FieldPosition); w.WriteEnum(Type1 = target.Type1); w.WriteEnum(Type2 = target.Type2); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEReflectTypePacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); UserTrainer = battle.Trainers[r.ReadByte()]; User = r.ReadEnum(); TargetTrainer = battle.Trainers[r.ReadByte()]; Target = r.ReadEnum(); Type1 = r.ReadEnum(); Type2 = r.ReadEnum(); } } public sealed class PBEReflectTypePacket_Hidden : IPBEPacket { public const ushort ID = 0x33; public ReadOnlyCollection Data { get; } public PBETrainer UserTrainer { get; } public PBEFieldPosition User { get; } public PBETrainer TargetTrainer { get; } public PBEFieldPosition Target { get; } public PBEReflectTypePacket_Hidden(PBEReflectTypePacket other) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((UserTrainer = other.UserTrainer).Id); w.WriteEnum(User = other.User); w.WriteByte((TargetTrainer = other.TargetTrainer).Id); w.WriteEnum(Target = other.Target); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEReflectTypePacket_Hidden(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); UserTrainer = battle.Trainers[r.ReadByte()]; User = r.ReadEnum(); TargetTrainer = battle.Trainers[r.ReadByte()]; Target = r.ReadEnum(); } } ================================================ FILE: PokemonBattleEngine/Packets/_SpecialMessagePacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBESpecialMessagePacket : IPBEPacket { public const ushort ID = 0x20; public ReadOnlyCollection Data { get; } public PBESpecialMessage Message { get; } public ReadOnlyCollection Params { get; } internal PBESpecialMessagePacket(PBESpecialMessage message, params object[] parameters) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteEnum(Message = message); var par = new List(); switch (Message) { case PBESpecialMessage.DraggedOut: case PBESpecialMessage.Endure: case PBESpecialMessage.HPDrained: case PBESpecialMessage.Recoil: case PBESpecialMessage.Struggle: { var p0 = (PBEBattlePokemon)parameters[0]; par.Add(p0.Trainer); par.Add(p0.FieldPosition); w.WriteByte(p0.Trainer.Id); w.WriteEnum(p0.FieldPosition); break; } case PBESpecialMessage.Magnitude: case PBESpecialMessage.MultiHit: { byte p0 = (byte)parameters[0]; par.Add(p0); w.WriteByte(p0); break; } case PBESpecialMessage.PainSplit: { var p0 = (PBEBattlePokemon)parameters[0]; var p1 = (PBEBattlePokemon)parameters[1]; par.Add(p0.Trainer); par.Add(p0.FieldPosition); par.Add(p1.Trainer); par.Add(p1.FieldPosition); w.WriteByte(p1.Trainer.Id); w.WriteEnum(p1.FieldPosition); w.WriteByte(p1.Trainer.Id); w.WriteEnum(p1.FieldPosition); break; } } Params = new ReadOnlyCollection(par); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBESpecialMessagePacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); Message = r.ReadEnum(); switch (Message) { case PBESpecialMessage.DraggedOut: case PBESpecialMessage.Endure: case PBESpecialMessage.HPDrained: case PBESpecialMessage.Recoil: case PBESpecialMessage.Struggle: { Params = new ReadOnlyCollection(new object[] { battle.Trainers[r.ReadByte()], r.ReadEnum() }); break; } case PBESpecialMessage.Magnitude: case PBESpecialMessage.MultiHit: { Params = new ReadOnlyCollection(new object[] { r.ReadByte() }); break; } case PBESpecialMessage.NothingHappened: case PBESpecialMessage.OneHitKnockout: case PBESpecialMessage.PayDay: { Params = new ReadOnlyCollection(Array.Empty()); break; } case PBESpecialMessage.PainSplit: { Params = new ReadOnlyCollection(new object[] { battle.Trainers[r.ReadByte()], r.ReadEnum(), battle.Trainers[r.ReadByte()], r.ReadEnum() }); break; } default: throw new InvalidDataException(); } } } ================================================ FILE: PokemonBattleEngine/Packets/_Status1Packet.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEStatus1Packet : IPBEPacket { public const ushort ID = 0x11; public ReadOnlyCollection Data { get; } public PBETrainer Status1ReceiverTrainer { get; } public PBEFieldPosition Status1Receiver { get; } public PBETrainer Pokemon2Trainer { get; } public PBEFieldPosition Pokemon2 { get; } public PBEStatus1 Status1 { get; } public PBEStatusAction StatusAction { get; } internal PBEStatus1Packet(PBEBattlePokemon status1Receiver, PBEBattlePokemon pokemon2, PBEStatus1 status1, PBEStatusAction statusAction) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((Status1ReceiverTrainer = status1Receiver.Trainer).Id); w.WriteEnum(Status1Receiver = status1Receiver.FieldPosition); w.WriteByte((Pokemon2Trainer = pokemon2.Trainer).Id); w.WriteEnum(Pokemon2 = pokemon2.FieldPosition); w.WriteEnum(Status1 = status1); w.WriteEnum(StatusAction = statusAction); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEStatus1Packet(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); Status1ReceiverTrainer = battle.Trainers[r.ReadByte()]; Status1Receiver = r.ReadEnum(); Pokemon2Trainer = battle.Trainers[r.ReadByte()]; Pokemon2 = r.ReadEnum(); Status1 = r.ReadEnum(); StatusAction = r.ReadEnum(); } } ================================================ FILE: PokemonBattleEngine/Packets/_Status2Packet.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEStatus2Packet : IPBEPacket { public const ushort ID = 0x12; public ReadOnlyCollection Data { get; } public PBETrainer Status2ReceiverTrainer { get; } public PBEFieldPosition Status2Receiver { get; } public PBETrainer Pokemon2Trainer { get; } public PBEFieldPosition Pokemon2 { get; } public PBEStatus2 Status2 { get; } public PBEStatusAction StatusAction { get; } internal PBEStatus2Packet(PBEBattlePokemon status2Receiver, PBEBattlePokemon pokemon2, PBEStatus2 status2, PBEStatusAction statusAction) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((Status2ReceiverTrainer = status2Receiver.Trainer).Id); w.WriteEnum(Status2Receiver = status2Receiver.FieldPosition); w.WriteByte((Pokemon2Trainer = pokemon2.Trainer).Id); w.WriteEnum(Pokemon2 = pokemon2.FieldPosition); w.WriteEnum(Status2 = status2); w.WriteEnum(StatusAction = statusAction); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEStatus2Packet(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); Status2ReceiverTrainer = battle.Trainers[r.ReadByte()]; Status2Receiver = r.ReadEnum(); Pokemon2Trainer = battle.Trainers[r.ReadByte()]; Pokemon2 = r.ReadEnum(); Status2 = r.ReadEnum(); StatusAction = r.ReadEnum(); } } ================================================ FILE: PokemonBattleEngine/Packets/_TeamStatusDamagePacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBETeamStatusDamagePacket : IPBEPacket { public const ushort ID = 0x41; public ReadOnlyCollection Data { get; } public PBETeam Team { get; } public PBETeamStatus TeamStatus { get; } public PBETrainer DamageVictimTrainer { get; } public PBEFieldPosition DamageVictim { get; } internal PBETeamStatusDamagePacket(PBETeam team, PBETeamStatus teamStatus, PBEBattlePokemon damageVictim) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((Team = team).Id); w.WriteEnum(TeamStatus = teamStatus); w.WriteByte((DamageVictimTrainer = damageVictim.Trainer).Id); w.WriteEnum(DamageVictim = damageVictim.FieldPosition); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBETeamStatusDamagePacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); Team = battle.Teams[r.ReadByte()]; TeamStatus = r.ReadEnum(); DamageVictimTrainer = battle.Trainers[r.ReadByte()]; DamageVictim = r.ReadEnum(); } } ================================================ FILE: PokemonBattleEngine/Packets/_TeamStatusPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBETeamStatusPacket : IPBEPacket { public const ushort ID = 0x13; public ReadOnlyCollection Data { get; } public PBETeam Team { get; } public PBETeamStatus TeamStatus { get; } public PBETeamStatusAction TeamStatusAction { get; } internal PBETeamStatusPacket(PBETeam team, PBETeamStatus teamStatus, PBETeamStatusAction teamStatusAction) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((Team = team).Id); w.WriteEnum(TeamStatus = teamStatus); w.WriteEnum(TeamStatusAction = teamStatusAction); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBETeamStatusPacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); Team = battle.Teams[r.ReadByte()]; TeamStatus = r.ReadEnum(); TeamStatusAction = r.ReadEnum(); } } ================================================ FILE: PokemonBattleEngine/Packets/_TransformPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBETransformPacket : IPBEPacket { public const ushort ID = 0x18; public ReadOnlyCollection Data { get; } public PBETrainer UserTrainer { get; } public PBEFieldPosition User { get; } public PBETrainer TargetTrainer { get; } public PBEFieldPosition Target { get; } public ushort TargetAttack { get; } public ushort TargetDefense { get; } public ushort TargetSpAttack { get; } public ushort TargetSpDefense { get; } public ushort TargetSpeed { get; } public sbyte TargetAttackChange { get; } public sbyte TargetDefenseChange { get; } public sbyte TargetSpAttackChange { get; } public sbyte TargetSpDefenseChange { get; } public sbyte TargetSpeedChange { get; } public sbyte TargetAccuracyChange { get; } public sbyte TargetEvasionChange { get; } public PBEAbility TargetAbility { get; } public PBESpecies TargetSpecies { get; } public PBEForm TargetForm { get; } public PBEType TargetType1 { get; } public PBEType TargetType2 { get; } public float TargetWeight { get; } public ReadOnlyCollection TargetMoves { get; } internal PBETransformPacket(PBEBattlePokemon user, PBEBattlePokemon target) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((UserTrainer = user.Trainer).Id); w.WriteEnum(User = user.FieldPosition); w.WriteByte((TargetTrainer = target.Trainer).Id); w.WriteEnum(Target = target.FieldPosition); w.WriteUInt16(TargetAttack = target.Attack); w.WriteUInt16(TargetDefense = target.Defense); w.WriteUInt16(TargetSpAttack = target.SpAttack); w.WriteUInt16(TargetSpDefense = target.SpDefense); w.WriteUInt16(TargetSpeed = target.Speed); w.WriteSByte(TargetAttackChange = target.AttackChange); w.WriteSByte(TargetDefenseChange = target.DefenseChange); w.WriteSByte(TargetSpAttackChange = target.SpAttackChange); w.WriteSByte(TargetSpDefenseChange = target.SpDefenseChange); w.WriteSByte(TargetSpeedChange = target.SpeedChange); w.WriteSByte(TargetAccuracyChange = target.AccuracyChange); w.WriteSByte(TargetEvasionChange = target.EvasionChange); w.WriteEnum(TargetAbility = target.Ability); w.WriteEnum(TargetSpecies = target.Species); w.WriteEnum(TargetForm = target.Form); w.WriteEnum(TargetType1 = target.Type1); w.WriteEnum(TargetType2 = target.Type2); w.WriteSingle(TargetWeight = target.Weight); TargetMoves = target.Moves.ForTransformPacket(); for (int i = 0; i < TargetMoves.Count; i++) { w.WriteEnum(TargetMoves[i]); } Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBETransformPacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); UserTrainer = battle.Trainers[r.ReadByte()]; User = r.ReadEnum(); TargetTrainer = battle.Trainers[r.ReadByte()]; Target = r.ReadEnum(); TargetAttack = r.ReadUInt16(); TargetDefense = r.ReadUInt16(); TargetSpAttack = r.ReadUInt16(); TargetSpDefense = r.ReadUInt16(); TargetSpeed = r.ReadUInt16(); TargetAttackChange = r.ReadSByte(); TargetDefenseChange = r.ReadSByte(); TargetSpAttackChange = r.ReadSByte(); TargetSpDefenseChange = r.ReadSByte(); TargetSpeedChange = r.ReadSByte(); TargetAccuracyChange = r.ReadSByte(); TargetEvasionChange = r.ReadSByte(); TargetAbility = r.ReadEnum(); TargetSpecies = r.ReadEnum(); TargetForm = r.ReadEnum(); TargetType1 = r.ReadEnum(); TargetType2 = r.ReadEnum(); TargetWeight = r.ReadSingle(); var moves = new PBEMove[battle.Settings.NumMoves]; for (int i = 0; i < moves.Length; i++) { moves[i] = r.ReadEnum(); } TargetMoves = new ReadOnlyCollection(moves); } } ================================================ FILE: PokemonBattleEngine/Packets/_TypeChangedPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBETypeChangedPacket : IPBEPacket { public const ushort ID = 0x2B; public ReadOnlyCollection Data { get; } public PBETrainer PokemonTrainer { get; } public PBEFieldPosition Pokemon { get; } public PBEType Type1 { get; } public PBEType Type2 { get; } internal PBETypeChangedPacket(PBEBattlePokemon pokemon, PBEType type1, PBEType type2) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteByte((PokemonTrainer = pokemon.Trainer).Id); w.WriteEnum(Pokemon = pokemon.FieldPosition); w.WriteEnum(Type1 = type1); w.WriteEnum(Type2 = type2); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBETypeChangedPacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); PokemonTrainer = battle.Trainers[r.ReadByte()]; Pokemon = r.ReadEnum(); Type1 = r.ReadEnum(); Type2 = r.ReadEnum(); } } ================================================ FILE: PokemonBattleEngine/Packets/_WeatherDamagePacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEWeatherDamagePacket : IPBEPacket { public const ushort ID = 0x40; public ReadOnlyCollection Data { get; } public PBEWeather Weather { get; } public PBETrainer DamageVictimTrainer { get; } public PBEFieldPosition DamageVictim { get; } internal PBEWeatherDamagePacket(PBEWeather weather, PBEBattlePokemon damageVictim) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteEnum(Weather = weather); w.WriteByte((DamageVictimTrainer = damageVictim.Trainer).Id); w.WriteEnum(DamageVictim = damageVictim.FieldPosition); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEWeatherDamagePacket(byte[] data, EndianBinaryReader r, PBEBattle battle) { Data = new ReadOnlyCollection(data); Weather = r.ReadEnum(); DamageVictimTrainer = battle.Trainers[r.ReadByte()]; DamageVictim = r.ReadEnum(); } } ================================================ FILE: PokemonBattleEngine/Packets/_WeatherPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public sealed class PBEWeatherPacket : IPBEPacket { public const ushort ID = 0x14; public ReadOnlyCollection Data { get; } public PBEWeather Weather { get; } public PBEWeatherAction WeatherAction { get; } internal PBEWeatherPacket(PBEWeather weather, PBEWeatherAction weatherAction) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); w.WriteEnum(Weather = weather); w.WriteEnum(WeatherAction = weatherAction); Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEWeatherPacket(byte[] data, EndianBinaryReader r) { Data = new ReadOnlyCollection(data); Weather = r.ReadEnum(); WeatherAction = r.ReadEnum(); } } ================================================ FILE: PokemonBattleEngine/Packets/_WildPkmnAppearedPacket.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public interface IPBEPkmnAppearedInfo_Hidden : IPBESpeciesForm { string Nickname { get; } byte Level { get; } bool Shiny { get; } PBEGender Gender { get; } float HPPercentage { get; } PBEStatus1 Status1 { get; } PBEFieldPosition FieldPosition { get; } } public interface IPBEWildPkmnAppearedPacket : IPBEPacket { IReadOnlyList Pokemon { get; } } public sealed class PBEPkmnAppearedInfo : IPBEPkmnSwitchInInfo_Hidden { public byte Pokemon { get; } public bool IsDisguised { get; } public PBESpecies Species { get; } public PBEForm Form { get; } public string Nickname { get; } public byte Level { get; } public bool Shiny { get; } public PBEGender Gender { get; } public PBEItem CaughtBall { get; } public ushort HP { get; } public ushort MaxHP { get; } public float HPPercentage { get; } public PBEStatus1 Status1 { get; } public PBEFieldPosition FieldPosition { get; } internal PBEPkmnAppearedInfo(PBEBattlePokemon pkmn) { Pokemon = pkmn.Id; IsDisguised = pkmn.Status2.HasFlag(PBEStatus2.Disguised); Species = pkmn.KnownSpecies; Form = pkmn.KnownForm; Nickname = pkmn.KnownNickname; Level = pkmn.Level; Shiny = pkmn.KnownShiny; Gender = pkmn.KnownGender; CaughtBall = pkmn.KnownCaughtBall; HP = pkmn.HP; MaxHP = pkmn.MaxHP; HPPercentage = pkmn.HPPercentage; Status1 = pkmn.Status1; FieldPosition = pkmn.FieldPosition; } internal PBEPkmnAppearedInfo(EndianBinaryReader r) { Pokemon = r.ReadByte(); IsDisguised = r.ReadBoolean(); Species = r.ReadEnum(); Form = r.ReadEnum(); Nickname = r.ReadString_NullTerminated(); Level = r.ReadByte(); Shiny = r.ReadBoolean(); Gender = r.ReadEnum(); CaughtBall = r.ReadEnum(); HP = r.ReadUInt16(); MaxHP = r.ReadUInt16(); HPPercentage = r.ReadSingle(); Status1 = r.ReadEnum(); FieldPosition = r.ReadEnum(); } internal void ToBytes(EndianBinaryWriter w) { w.WriteByte(Pokemon); w.WriteBoolean(IsDisguised); w.WriteEnum(Species); w.WriteEnum(Form); w.WriteChars_NullTerminated(Nickname); w.WriteByte(Level); w.WriteBoolean(Shiny); w.WriteEnum(Gender); w.WriteEnum(CaughtBall); w.WriteUInt16(HP); w.WriteUInt16(MaxHP); w.WriteSingle(HPPercentage); w.WriteEnum(Status1); w.WriteEnum(FieldPosition); } } public sealed class PBEWildPkmnAppearedPacket : IPBEWildPkmnAppearedPacket { public const ushort ID = 0x0D; public ReadOnlyCollection Data { get; } public ReadOnlyCollection Pokemon { get; } IReadOnlyList IPBEWildPkmnAppearedPacket.Pokemon => Pokemon; internal PBEWildPkmnAppearedPacket(IList pokemon) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); byte count = (byte)(Pokemon = new ReadOnlyCollection(pokemon)).Count; w.WriteByte(count); for (int i = 0; i < count; i++) { Pokemon[i].ToBytes(w); } Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEWildPkmnAppearedPacket(byte[] data, EndianBinaryReader r) { Data = new ReadOnlyCollection(data); var pokemon = new PBEPkmnAppearedInfo[r.ReadByte()]; for (int i = 0; i < pokemon.Length; i++) { pokemon[i] = new PBEPkmnAppearedInfo(r); } Pokemon = new ReadOnlyCollection(pokemon); } } public sealed class PBEWildPkmnAppearedPacket_Hidden : IPBEWildPkmnAppearedPacket { public const ushort ID = 0x3C; public ReadOnlyCollection Data { get; } public sealed class PBEWildPkmnInfo : IPBEPkmnAppearedInfo_Hidden { public PBESpecies Species { get; } public PBEForm Form { get; } public string Nickname { get; } public byte Level { get; } public bool Shiny { get; } public PBEGender Gender { get; } public float HPPercentage { get; } public PBEStatus1 Status1 { get; } public PBEFieldPosition FieldPosition { get; } internal PBEWildPkmnInfo(PBEPkmnAppearedInfo other) { Species = other.Species; Form = other.Form; Nickname = other.Nickname; Level = other.Level; Shiny = other.Shiny; Gender = other.Gender; HPPercentage = other.HPPercentage; Status1 = other.Status1; FieldPosition = other.FieldPosition; } internal PBEWildPkmnInfo(EndianBinaryReader r) { Species = r.ReadEnum(); Form = r.ReadEnum(); Nickname = r.ReadString_NullTerminated(); Level = r.ReadByte(); Shiny = r.ReadBoolean(); Gender = r.ReadEnum(); HPPercentage = r.ReadSingle(); Status1 = r.ReadEnum(); FieldPosition = r.ReadEnum(); } internal void ToBytes(EndianBinaryWriter w) { w.WriteEnum(Species); w.WriteEnum(Form); w.WriteChars_NullTerminated(Nickname); w.WriteByte(Level); w.WriteBoolean(Shiny); w.WriteEnum(Gender); w.WriteSingle(HPPercentage); w.WriteEnum(Status1); w.WriteEnum(FieldPosition); } } public ReadOnlyCollection Pokemon { get; } IReadOnlyList IPBEWildPkmnAppearedPacket.Pokemon => Pokemon; public PBEWildPkmnAppearedPacket_Hidden(PBEWildPkmnAppearedPacket other) { using (var ms = new MemoryStream()) { EndianBinaryWriter w = PBEPacketProcessor.WritePacketID(ms, ID); var pokemon = new PBEWildPkmnInfo[other.Pokemon.Count]; for (int i = 0; i < pokemon.Length; i++) { pokemon[i] = new PBEWildPkmnInfo(other.Pokemon[i]); } byte count = (byte)(Pokemon = new ReadOnlyCollection(pokemon)).Count; w.WriteByte(count); for (int i = 0; i < count; i++) { Pokemon[i].ToBytes(w); } Data = new ReadOnlyCollection(ms.ToArray()); } } internal PBEWildPkmnAppearedPacket_Hidden(byte[] data, EndianBinaryReader r) { Data = new ReadOnlyCollection(data); var pokemon = new PBEWildPkmnInfo[r.ReadByte()]; for (int i = 0; i < pokemon.Length; i++) { pokemon[i] = new PBEWildPkmnInfo(r); } Pokemon = new ReadOnlyCollection(pokemon); } } ================================================ FILE: PokemonBattleEngine/Packets/__Packet.cs ================================================ using System.Collections.ObjectModel; namespace Kermalis.PokemonBattleEngine.Packets; public interface IPBEPacket { ReadOnlyCollection Data { get; } } ================================================ FILE: PokemonBattleEngine/Packets/__PacketProcessor.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using System; using System.Diagnostics.CodeAnalysis; using System.IO; namespace Kermalis.PokemonBattleEngine.Packets; public class PBEPacketProcessor { protected static void CheckNull([NotNull] PBEBattle? battle) { if (battle is null) { throw new ArgumentNullException(nameof(battle)); } } public IPBEPacket CreatePacket(byte[] data, PBEBattle? battle) { if (data.Length < 2) { throw new InvalidDataException(); } using (var ms = new MemoryStream(data)) { var r = new EndianBinaryReader(ms); ushort id = r.ReadUInt16(); IPBEPacket? ret = TryCreatePacket(data, battle, r, id); if (ret is null) { throw new InvalidDataException($"Invalid packet ID ({id})"); } return ret; } } public static EndianBinaryWriter WritePacketID(MemoryStream ms, ushort id) { var w = new EndianBinaryWriter(ms); w.WriteUInt16(id); return w; } protected virtual IPBEPacket? TryCreatePacket(byte[] data, PBEBattle? battle, EndianBinaryReader r, ushort id) { switch (id) { case PBEResponsePacket.ID: return new PBEResponsePacket(data); case PBEPlayerJoinedPacket.ID: return new PBEPlayerJoinedPacket(data, r); case PBEMatchCancelledPacket.ID: return new PBEMatchCancelledPacket(data); case PBEPartyRequestPacket.ID: return new PBEPartyRequestPacket(data, r); case PBEPartyResponsePacket.ID: return new PBEPartyResponsePacket(data, r); case PBEBattlePacket.ID: return new PBEBattlePacket(data, r); case PBEPkmnSwitchInPacket.ID: CheckNull(battle); return new PBEPkmnSwitchInPacket(data, r, battle); case PBEActionsRequestPacket.ID: CheckNull(battle); return new PBEActionsRequestPacket(data, r, battle); case PBEActionsResponsePacket.ID: return new PBEActionsResponsePacket(data, r); case PBEMoveUsedPacket.ID: CheckNull(battle); return new PBEMoveUsedPacket(data, r, battle); case PBEPkmnHPChangedPacket.ID: CheckNull(battle); return new PBEPkmnHPChangedPacket(data, r, battle); case PBEHazePacket.ID: return new PBEHazePacket(data); case PBEPkmnSwitchOutPacket.ID: CheckNull(battle); return new PBEPkmnSwitchOutPacket(data, r, battle); case PBEWildPkmnAppearedPacket.ID: return new PBEWildPkmnAppearedPacket(data, r); case PBEPkmnFaintedPacket.ID: CheckNull(battle); return new PBEPkmnFaintedPacket(data, r, battle); case PBEMoveCritPacket.ID: CheckNull(battle); return new PBEMoveCritPacket(data, r, battle); case PBEPkmnStatChangedPacket.ID: CheckNull(battle); return new PBEPkmnStatChangedPacket(data, r, battle); case PBEStatus1Packet.ID: CheckNull(battle); return new PBEStatus1Packet(data, r, battle); case PBEStatus2Packet.ID: CheckNull(battle); return new PBEStatus2Packet(data, r, battle); case PBETeamStatusPacket.ID: CheckNull(battle); return new PBETeamStatusPacket(data, r, battle); case PBEWeatherPacket.ID: CheckNull(battle); return new PBEWeatherPacket(data, r); case PBEMoveResultPacket.ID: CheckNull(battle); return new PBEMoveResultPacket(data, r, battle); case PBEItemPacket.ID: CheckNull(battle); return new PBEItemPacket(data, r, battle); case PBEMovePPChangedPacket.ID: CheckNull(battle); return new PBEMovePPChangedPacket(data, r, battle); case PBETransformPacket.ID: CheckNull(battle); return new PBETransformPacket(data, r, battle); case PBEAbilityPacket.ID: CheckNull(battle); return new PBEAbilityPacket(data, r, battle); case PBESpecialMessagePacket.ID: CheckNull(battle); return new PBESpecialMessagePacket(data, r, battle); case PBEBattleStatusPacket.ID: return new PBEBattleStatusPacket(data, r); case PBEPsychUpPacket.ID: CheckNull(battle); return new PBEPsychUpPacket(data, r, battle); case PBESwitchInRequestPacket.ID: CheckNull(battle); return new PBESwitchInRequestPacket(data, r, battle); case PBESwitchInResponsePacket.ID: return new PBESwitchInResponsePacket(data, r); case PBEIllusionPacket.ID: CheckNull(battle); return new PBEIllusionPacket(data, r, battle); case PBEBattleResultPacket.ID: return new PBEBattleResultPacket(data, r); case PBETurnBeganPacket.ID: return new PBETurnBeganPacket(data, r); case PBEMoveLockPacket.ID: CheckNull(battle); return new PBEMoveLockPacket(data, r, battle); case PBEPkmnFormChangedPacket.ID: CheckNull(battle); return new PBEPkmnFormChangedPacket(data, r, battle); case PBEAutoCenterPacket.ID: CheckNull(battle); return new PBEAutoCenterPacket(data, r, battle); case PBETypeChangedPacket.ID: CheckNull(battle); return new PBETypeChangedPacket(data, r, battle); case PBEAbilityReplacedPacket.ID: CheckNull(battle); return new PBEAbilityReplacedPacket(data, r, battle); case PBELegalPartyResponsePacket.ID: return new PBELegalPartyResponsePacket(data, r); case PBEReflectTypePacket.ID: CheckNull(battle); return new PBEReflectTypePacket(data, r, battle); case PBEPkmnFaintedPacket_Hidden.ID: CheckNull(battle); return new PBEPkmnFaintedPacket_Hidden(data, r, battle); case PBEAutoCenterPacket_Hidden0.ID: CheckNull(battle); return new PBEAutoCenterPacket_Hidden0(data, r, battle); case PBEAutoCenterPacket_Hidden1.ID: CheckNull(battle); return new PBEAutoCenterPacket_Hidden1(data, r, battle); case PBEAutoCenterPacket_Hidden01.ID: CheckNull(battle); return new PBEAutoCenterPacket_Hidden01(data, r, battle); case PBEReflectTypePacket_Hidden.ID: CheckNull(battle); return new PBEReflectTypePacket_Hidden(data, r, battle); case PBEPkmnFormChangedPacket_Hidden.ID: CheckNull(battle); return new PBEPkmnFormChangedPacket_Hidden(data, r, battle); case PBEPkmnHPChangedPacket_Hidden.ID: CheckNull(battle); return new PBEPkmnHPChangedPacket_Hidden(data, r, battle); case PBEPkmnSwitchInPacket_Hidden.ID: CheckNull(battle); return new PBEPkmnSwitchInPacket_Hidden(data, r, battle); case PBEPkmnSwitchOutPacket_Hidden.ID: CheckNull(battle); return new PBEPkmnSwitchOutPacket_Hidden(data, r, battle); case PBEFleeResponsePacket.ID: return new PBEFleeResponsePacket(data); case PBEFleeFailedPacket.ID: CheckNull(battle); return new PBEFleeFailedPacket(data, r, battle); case PBEItemTurnPacket.ID: CheckNull(battle); return new PBEItemTurnPacket(data, r, battle); case PBECapturePacket.ID: CheckNull(battle); return new PBECapturePacket(data, r, battle); case PBEWildPkmnAppearedPacket_Hidden.ID: return new PBEWildPkmnAppearedPacket_Hidden(data, r); case PBEPkmnEXPChangedPacket.ID: CheckNull(battle); return new PBEPkmnEXPChangedPacket(data, r, battle); case PBEPkmnEXPEarnedPacket.ID: CheckNull(battle); return new PBEPkmnEXPEarnedPacket(data, r, battle); case PBEPkmnLevelChangedPacket.ID: CheckNull(battle); return new PBEPkmnLevelChangedPacket(data, r, battle); case PBEWeatherDamagePacket.ID: CheckNull(battle); return new PBEWeatherDamagePacket(data, r, battle); case PBETeamStatusDamagePacket.ID: CheckNull(battle); return new PBETeamStatusDamagePacket(data, r, battle); default: return null; } } } ================================================ FILE: PokemonBattleEngine/PokemonBattleEngine.csproj ================================================  net7.0 Library Kermalis.PokemonBattleEngine Kermalis Kermalis https://github.com/Kermalis/PokemonBattleEngine bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml 1591 enable ================================================ FILE: PokemonBattleEngine/Utils/EmptyCollections.cs ================================================ using System; using System.Collections.ObjectModel; namespace Kermalis.PokemonBattleEngine.Utils; internal static class PBEEmptyReadOnlyCollection { public static readonly ReadOnlyCollection Value = new(Array.Empty()); } ================================================ FILE: PokemonBattleEngine/Utils/Random.cs ================================================ using Kermalis.EndianBinaryIO; using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngine.Data.Utils; using System; using System.Collections.Generic; namespace Kermalis.PokemonBattleEngine.Utils; /// A random helper. This class is thread-safe. public class PBERandom { protected readonly object _randLockObj = new(); protected Random _rand; protected int _seed; /// Gets or sets the seed of this . The chain will be reset even if the seed is the same as the previous seed. public int Seed { get => _seed; set { lock (_randLockObj) { _rand = new Random(value); _seed = value; } } } public PBERandom() : this(Environment.TickCount) { } public PBERandom(int? seed) : this(seed ?? Environment.TickCount) { } public PBERandom(int seed) { _rand = new Random(seed); _seed = seed; } public PBEBattleTerrain RandomBattleTerrain() { return (PBEBattleTerrain)RandomInt(0, (int)PBEBattleTerrain.MAX - 1); } public bool RandomBool() { return RandomInt(0, 1) == 1; } public bool RandomBool(int chanceNumerator, int chanceDenominator) { if (chanceDenominator < 1) { throw new ArgumentOutOfRangeException(nameof(chanceDenominator), $"\"{nameof(chanceDenominator)}\" must be at least 1."); } if (chanceNumerator >= chanceDenominator) { return true; } if (chanceNumerator == 0) { return false; } return RandomInt(0, chanceDenominator - 1) < chanceNumerator; } public T RandomElement(IReadOnlyList source) { int count = source.Count - 1; if (count == 0) { return source[0]; } if (count < 0) { throw new ArgumentOutOfRangeException(nameof(source), $"\"{nameof(source)}\" must have at least one element."); } return source[RandomInt(0, count)]; } /// Returns a random for the given . /// Thrown when is invalid. public PBEGender RandomGender(PBEGenderRatio genderRatio) { switch (genderRatio) { case PBEGenderRatio.M7_F1: return RandomBool(875, 1000) ? PBEGender.Male : PBEGender.Female; case PBEGenderRatio.M3_F1: return RandomBool(750, 1000) ? PBEGender.Male : PBEGender.Female; case PBEGenderRatio.M1_F1: return RandomBool(500, 1000) ? PBEGender.Male : PBEGender.Female; case PBEGenderRatio.M1_F3: return RandomBool(250, 1000) ? PBEGender.Male : PBEGender.Female; case PBEGenderRatio.M0_F1: return PBEGender.Female; case PBEGenderRatio.M0_F0: return PBEGender.Genderless; case PBEGenderRatio.M1_F0: return PBEGender.Male; default: throw new ArgumentOutOfRangeException(nameof(genderRatio)); } } public int RandomInt() { return RandomInt(int.MinValue, int.MaxValue); } /// Returns a random value between the inclusive and inclusive . public int RandomInt(int minValue, int maxValue) { if (minValue == maxValue) { return minValue; } if (minValue > maxValue) { throw new ArgumentOutOfRangeException(nameof(minValue), $"\"{nameof(minValue)}\" cannot exceed \"{nameof(maxValue)}\"."); } Span bytes = stackalloc byte[sizeof(uint)]; uint scale; do { lock (_randLockObj) { _rand.NextBytes(bytes); } scale = EndianBinaryPrimitives.ReadUInt32_Unsafe(bytes, Endianness.LittleEndian); } while (scale == uint.MaxValue); // "d" should not be 1.0 float d = scale / (float)uint.MaxValue; return (int)(minValue + (((long)maxValue + 1 - minValue) * d)); // Remove "+ 1" for exclusive maxValue } /// Returns a random value that is between 's and . /// The object to use. public byte RandomLevel(PBESettings settings) { settings.ShouldBeReadOnly(nameof(settings)); return (byte)RandomInt(settings.MinLevel, settings.MaxLevel); } /// Returns a random value that represents shininess using shiny odds. public bool RandomShiny() { return RandomBool(8, 65536); } /// Returns a random with a random . public (PBESpecies, PBEForm) RandomSpecies(bool requireUsableOutsideOfBattle) { return RandomSpecies(PBEDataUtils.AllSpecies, requireUsableOutsideOfBattle); } public (PBESpecies, PBEForm) RandomSpecies(IReadOnlyList eligible, bool requireUsableOutsideOfBattle) { PBESpecies species = RandomElement(eligible); IReadOnlyList forms = PBEDataUtils.GetForms(species, requireUsableOutsideOfBattle); PBEForm form = forms.Count > 0 ? RandomElement(forms) : 0; return (species, form); } /// Shuffles the items in using the Fisher-Yates Shuffle algorithm. public void Shuffle(IList source) { int count = source.Count - 1; if (count < 0) { throw new ArgumentOutOfRangeException(nameof(source), $"\"{nameof(source)}\" must have at least one element."); } for (int a = 0; a < count; a++) { int b = RandomInt(a, count); (source[b], source[a]) = (source[a], source[b]); } } } ================================================ FILE: PokemonBattleEngine/Utils/Utils.cs ================================================ using System.Collections.Generic; using System.IO; using System.Text.Json.Nodes; namespace Kermalis.PokemonBattleEngine.Utils; /// A static class that provides utilities that are used throughout the battle engine. public static class PBEUtils { /// Returns a that combines 's elements' string representations using "and" with commas. /// The type of the elements of . /// An to create a string from. public static string Andify(this IReadOnlyList source) { string str = source[0]?.ToString() ?? string.Empty; for (int i = 1; i < source.Count; i++) { if (i == source.Count - 1) { if (source.Count > 2) { str += ','; } str += " and "; } else { str += ", "; } str += source[i]?.ToString() ?? string.Empty; } return str; } public static IEnumerable ExceptOne(this IEnumerable source, T one) { foreach (T t in source) { if (!Equals(t, one)) { yield return t; } } } /// Removes all invalid file name characters from . internal static string ToSafeFileName(string fileName) { char[] invalid = Path.GetInvalidFileNameChars(); for (int i = 0; i < invalid.Length; i++) { fileName = fileName.Replace(invalid[i], '-'); } return fileName; } internal static JsonNode GetSafe(this JsonArray j, int index) { JsonNode? ret = j[index]; if (ret is null) { throw new InvalidDataException($"JSON array index \"{index}\" was not found"); } return ret; } internal static JsonNode GetSafe(this JsonObject j, string key) { JsonNode? ret = j[key]; if (ret is null) { throw new InvalidDataException($"JSON object key \"{key}\" was not found"); } return ret; } } ================================================ FILE: PokemonBattleEngine.DefaultData/AI/AI.cs ================================================ using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using System; using System.Collections.Generic; namespace Kermalis.PokemonBattleEngine.DefaultData.AI; /// Creates valid decisions for a team in a battle. Decisions may not be valid for custom settings and/or move changes. public partial class PBEDDAI { // TODO: Control multiple trainers of a multi battle team public PBETrainer Trainer { get; } public PBEDDAI(PBETrainer trainer) { if (trainer.IsWild) { throw new ArgumentOutOfRangeException(nameof(trainer), $"Cannot use this AI type with a wild trainer. Use {nameof(PBEDDWildAI)} or another type of AI."); } Trainer = trainer; } /// Creates valid actions for a battle turn for a specific team. /// Thrown when has no active battlers or 's 's is not . /// Thrown when a Pokémon has no moves, the AI tries to use a move with invalid targets, or 's 's is invalid. public void CreateActions() { if (Trainer.Battle.BattleState != PBEBattleState.WaitingForActions) { throw new InvalidOperationException($"{nameof(Trainer.Battle.BattleState)} must be {PBEBattleState.WaitingForActions} to create actions."); } int count = Trainer.ActionsRequired.Count; var actions = new List(count); var standBy = new List(); for (int i = 0; i < count; i++) { PBEBattlePokemon user = Trainer.ActionsRequired[i]; // If a Pokémon is forced to struggle, it is best that it just stays in until it faints if (user.IsForcedToStruggle()) { actions.Add(new PBETurnAction(user, PBEMove.Struggle, PBEBattleUtils.GetPossibleTargets(user, user.GetMoveTargets(PBEMove.Struggle))[0])); continue; } // If a Pokémon has a temp locked move (Dig, Dive, ShadowForce) it must be used else if (user.TempLockedMove != PBEMove.None) { actions.Add(new PBETurnAction(user, user.TempLockedMove, user.TempLockedTargets)); continue; } // The Pokémon is free to switch or fight (unless it cannot switch due to Magnet Pull etc) PBETurnAction a = DecideAction(user, actions, standBy); // Action was chosen, finish up for this Pokémon if (a.Decision == PBETurnDecision.SwitchOut) { standBy.Add(Trainer.GetPokemon(a.SwitchPokemonId)); } actions.Add(a); } if (!Trainer.SelectActionsIfValid(actions, out string? valid)) { throw new Exception("AI created bad actions. - " + valid); } } /// Creates valid switches for a battle for a specific team. /// Thrown when does not require switch-ins or 's 's is not . /// Thrown when 's 's is invalid. public void CreateSwitches() { if (Trainer.Battle.BattleState != PBEBattleState.WaitingForSwitchIns) { throw new InvalidOperationException($"{nameof(Trainer.Battle.BattleState)} must be {PBEBattleState.WaitingForSwitchIns} to create switch-ins."); } if (Trainer.SwitchInsRequired == 0) { throw new InvalidOperationException($"{nameof(Trainer)} must require switch-ins."); } List available = Trainer.Party.FindAll(p => p.FieldPosition == PBEFieldPosition.None && p.CanBattle); PBEDataProvider.GlobalRandom.Shuffle(available); var availablePositions = new List(); switch (Trainer.Battle.BattleFormat) { case PBEBattleFormat.Single: { availablePositions.Add(PBEFieldPosition.Center); break; } case PBEBattleFormat.Double: { if (Trainer.OwnsSpot(PBEFieldPosition.Left) && !Trainer.IsSpotOccupied(PBEFieldPosition.Left)) { availablePositions.Add(PBEFieldPosition.Left); } if (Trainer.OwnsSpot(PBEFieldPosition.Right) && !Trainer.IsSpotOccupied(PBEFieldPosition.Right)) { availablePositions.Add(PBEFieldPosition.Right); } break; } case PBEBattleFormat.Triple: case PBEBattleFormat.Rotation: { if (Trainer.OwnsSpot(PBEFieldPosition.Left) && !Trainer.IsSpotOccupied(PBEFieldPosition.Left)) { availablePositions.Add(PBEFieldPosition.Left); } if (Trainer.OwnsSpot(PBEFieldPosition.Center) && !Trainer.IsSpotOccupied(PBEFieldPosition.Center)) { availablePositions.Add(PBEFieldPosition.Center); } if (Trainer.OwnsSpot(PBEFieldPosition.Right) && !Trainer.IsSpotOccupied(PBEFieldPosition.Right)) { availablePositions.Add(PBEFieldPosition.Right); } break; } default: throw new InvalidOperationException(nameof(Trainer.Battle.BattleFormat)); } var switches = new PBESwitchIn[Trainer.SwitchInsRequired]; for (int i = 0; i < Trainer.SwitchInsRequired; i++) { switches[i] = new PBESwitchIn(available[i], availablePositions[i]); } if (!Trainer.SelectSwitchesIfValid(out string? valid, switches)) { throw new Exception("AI created bad switches. - " + valid); } } } ================================================ FILE: PokemonBattleEngine.DefaultData/AI/AIDecisions.cs ================================================ using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngine.Data.Utils; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; namespace Kermalis.PokemonBattleEngine.DefaultData.AI; public partial class PBEDDAI { private PBETurnAction DecideAction(PBEBattlePokemon user, List actions, List standBy) { // Gather all options of switching and moves PBEMove[] usableMoves = user.GetUsableMoves(); var possibleActions = new List<(PBETurnAction Action, float 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(); if (possibleTarget.HasFlag(PBETurnTarget.AllyLeft)) { Trainer.Team.TryAddPokemonToCollection(PBEFieldPosition.Left, targets); } if (possibleTarget.HasFlag(PBETurnTarget.AllyCenter)) { Trainer.Team.TryAddPokemonToCollection(PBEFieldPosition.Center, targets); } if (possibleTarget.HasFlag(PBETurnTarget.AllyRight)) { Trainer.Team.TryAddPokemonToCollection(PBEFieldPosition.Right, targets); } if (possibleTarget.HasFlag(PBETurnTarget.FoeLeft)) { Trainer.Team.OpposingTeam.TryAddPokemonToCollection(PBEFieldPosition.Left, targets); } if (possibleTarget.HasFlag(PBETurnTarget.FoeCenter)) { Trainer.Team.OpposingTeam.TryAddPokemonToCollection(PBEFieldPosition.Center, targets); } if (possibleTarget.HasFlag(PBETurnTarget.FoeRight)) { Trainer.Team.OpposingTeam.TryAddPokemonToCollection(PBEFieldPosition.Right, targets); } float score = ScoreMove(targets, user, move, moveType, actions); possibleActions.Add((new PBETurnAction(user, move, possibleTarget), score)); } } if (user.CanSwitchOut()) { PBEBattlePokemon[] availableForSwitch = Trainer.Party.Except(standBy).Where(p => p.FieldPosition == PBEFieldPosition.None && p.CanBattle).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 float score = -10; possibleActions.Add((new PBETurnAction(user, switchPkmn), score)); } } IOrderedEnumerable<(PBETurnAction Action, float Score)> byScore = possibleActions.OrderByDescending(t => t.Score); Debug_LogGeneratedActions(user, byScore); float 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 } private void Debug_LogGeneratedActions(PBEBattlePokemon user, IOrderedEnumerable<(PBETurnAction Action, float Score)> byScore) { string ToDebugString((PBETurnAction Action, float 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.GetPokemon(t.Action.SwitchPokemonId).Nickname); } str += " [" + t.Score + "]}"; return str; } Debug.WriteLine("{0}'s possible actions: {1}", user.Nickname, "( " + string.Join(", ", byScore.Select(t => ToDebugString(t))) + " )"); } private float ScoreMove(List targets, PBEBattlePokemon user, PBEMove move, PBEType moveType, List actions) { if (targets.Count == 0) { return -100; } float score = 0; IPBEMoveData mData = PBEDataProvider.Instance.GetMoveData(move); if (!mData.IsMoveUsable()) { throw new ArgumentOutOfRangeException(nameof(move), $"{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 float 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 InvalidDataException(nameof(IPBEMoveData.Effect)); } return score; } private static void ScoreStatChange(PBEBattlePokemon user, PBEBattlePokemon target, PBEStat stat, int change, ref float score) { // TODO: Do we need the stat change? Physical vs special vs status users, and base stats/transform stats/power trick stats sbyte original = target.GetStatChange(stat); sbyte maxStatChange = user.Battle.Settings.MaxStatChange; change = Math.Max(-maxStatChange, Math.Min(maxStatChange, original + change)) - original; if (change != 0) { score += (user.Team == target.Team ? +1 : -1) * change * 10; score += HPAware(target.HPPercentage, -20, +10); } } private static bool IsTeammateUsingEffect(List actions, PBEMoveEffect effect) { return actions.FindIndex(a => a.Decision == PBETurnDecision.Fight && PBEDataProvider.Instance.GetMoveData(a.FightMove).Effect == effect) != -1; } private static float HPAware(float hpPercentage, float zeroPercentScore, float hundredPercentScore) { return ((-zeroPercentScore + hundredPercentScore) * hpPercentage) + zeroPercentScore; } } ================================================ FILE: PokemonBattleEngine.DefaultData/AI/WildAI.cs ================================================ using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using System; namespace Kermalis.PokemonBattleEngine.DefaultData.AI; // Wild Pokémon always select a random usable move (unless they are forced to use a move) // They will flee randomly based on their IPBEPokemonData.FleeRate only if it's a single battle and they are allowed to flee public sealed class PBEDDWildAI { public PBETrainer Trainer { get; } public PBEDDWildAI(PBETrainer trainer) { Trainer = trainer; } public void CreateActions(bool allowFlee) { 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 (allowFlee && Trainer.IsWild && Trainer.Battle.BattleFormat == PBEBattleFormat.Single && Trainer.IsFleeValid(out _)) { PBEBattlePokemon user = Trainer.ActionsRequired[0]; IPBEPokemonData pData = PBEDataProvider.Instance.GetPokemonData(user); if (PBEDataProvider.GlobalRandom.RandomBool(pData.FleeRate, byte.MaxValue)) { if (!Trainer.SelectFleeIfValid(out string? valid)) { throw new Exception("Wild AI tried to flee but couldn't. - " + valid); } return; } } // Select a move 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, so pick a random move PBEMove[] usableMoves = user.GetUsableMoves(); PBEMove move = PBEDataProvider.GlobalRandom.RandomElement(usableMoves); actions[i] = new PBETurnAction(user, move, PBEBattle.GetRandomTargetForMetronome(user, move, PBEDataProvider.GlobalRandom)); } if (!Trainer.SelectActionsIfValid(out string? valid2, actions)) { throw new Exception("Wild AI created bad actions. - " + valid2); } } } ================================================ FILE: PokemonBattleEngine.DefaultData/Data/BerryData.cs ================================================ using Kermalis.PokemonBattleEngine.Data; namespace Kermalis.PokemonBattleEngine.DefaultData.Data; public sealed partial class PBEDDBerryData : IPBEBerryData { public byte Bitterness { get; } public byte Dryness { get; } public byte Sourness { get; } public byte Spicyness { get; } public byte Sweetness { get; } public byte NaturalGiftPower { get; } public PBEType NaturalGiftType { get; } private PBEDDBerryData(byte naturalGiftPower, PBEType naturalGiftType, byte bitterness = 0, byte dryness = 0, byte sourness = 0, byte spicyness = 0, byte sweetness = 0) { Bitterness = bitterness; Dryness = dryness; Sourness = sourness; Spicyness = spicyness; Sweetness = sweetness; NaturalGiftPower = naturalGiftPower; NaturalGiftType = naturalGiftType; } } ================================================ FILE: PokemonBattleEngine.DefaultData/Data/BerryData_Data.cs ================================================ using Kermalis.PokemonBattleEngine.Data; using System.Collections.Generic; using System.Collections.ObjectModel; namespace Kermalis.PokemonBattleEngine.DefaultData.Data; public sealed partial class PBEDDBerryData { public static ReadOnlyDictionary Data { get; } = new(new Dictionary { { PBEItem.AguavBerry, new PBEDDBerryData(60, PBEType.Dragon, bitterness: 15) }, { PBEItem.ApicotBerry, new PBEDDBerryData(80, PBEType.Ground, spicyness: 10, dryness: 30, sourness: 30) }, { PBEItem.AspearBerry, new PBEDDBerryData(60, PBEType.Ice, sourness: 10) }, { PBEItem.BabiriBerry, new PBEDDBerryData(60, PBEType.Steel, spicyness: 25, dryness: 10) }, { PBEItem.BelueBerry, new PBEDDBerryData(80, PBEType.Electric, spicyness: 10, sourness: 30) }, { PBEItem.BlukBerry, new PBEDDBerryData(70, PBEType.Fire, dryness: 10, sweetness: 10) }, { PBEItem.ChartiBerry, new PBEDDBerryData(60, PBEType.Rock, spicyness: 10, dryness: 20) }, { PBEItem.CheriBerry, new PBEDDBerryData(60, PBEType.Fire, spicyness: 10) }, { PBEItem.ChestoBerry, new PBEDDBerryData(60, PBEType.Water, dryness: 10) }, { PBEItem.ChilanBerry, new PBEDDBerryData(60, PBEType.Normal, dryness: 25, sweetness: 10) }, { PBEItem.ChopleBerry, new PBEDDBerryData(60, PBEType.Fighting, spicyness: 15, bitterness: 10) }, { PBEItem.CobaBerry, new PBEDDBerryData(60, PBEType.Flying, dryness: 10, bitterness: 15) }, { PBEItem.ColburBerry, new PBEDDBerryData(60, PBEType.Dark, bitterness: 10, sourness: 20) }, { PBEItem.CornnBerry, new PBEDDBerryData(70, PBEType.Bug, dryness: 20, sweetness: 10) }, { PBEItem.CustapBerry, new PBEDDBerryData(80, PBEType.Ghost, sweetness: 40, bitterness: 10) }, { PBEItem.DurinBerry, new PBEDDBerryData(80, PBEType.Water, bitterness: 30, sourness: 10) }, { PBEItem.EnigmaBerry, new PBEDDBerryData(80, PBEType.Bug, spicyness: 40, dryness: 10) }, { PBEItem.FigyBerry, new PBEDDBerryData(60, PBEType.Bug, spicyness: 15) }, { PBEItem.GanlonBerry, new PBEDDBerryData(80, PBEType.Ice, dryness: 30, sweetness: 10, bitterness: 30) }, { PBEItem.GrepaBerry, new PBEDDBerryData(70, PBEType.Flying, dryness: 10, sweetness: 10, sourness: 10) }, { PBEItem.HabanBerry, new PBEDDBerryData(60, PBEType.Dragon, sweetness: 10, bitterness: 20) }, { PBEItem.HondewBerry, new PBEDDBerryData(70, PBEType.Ground, spicyness: 10, dryness: 10, bitterness: 10) }, { PBEItem.IapapaBerry, new PBEDDBerryData(60, PBEType.Dark, sourness: 15) }, { PBEItem.JabocaBerry, new PBEDDBerryData(80, PBEType.Dragon, bitterness: 40, sourness: 10) }, { PBEItem.KasibBerry, new PBEDDBerryData(60, PBEType.Ghost, dryness: 10, sweetness: 20) }, { PBEItem.KebiaBerry, new PBEDDBerryData(60, PBEType.Poison, dryness: 15, sourness: 10) }, { PBEItem.KelpsyBerry, new PBEDDBerryData(70, PBEType.Fighting, dryness: 10, bitterness: 10, sourness: 10) }, { PBEItem.LansatBerry, new PBEDDBerryData(80, PBEType.Flying, spicyness: 30, dryness: 10, sweetness: 30, bitterness: 10, sourness: 30) }, { PBEItem.LeppaBerry, new PBEDDBerryData(60, PBEType.Fighting, spicyness: 10, sweetness: 10, bitterness: 10, sourness: 10) }, { PBEItem.LiechiBerry, new PBEDDBerryData(80, PBEType.Grass, spicyness: 30, dryness: 10, sweetness: 30) }, { PBEItem.LumBerry, new PBEDDBerryData(60, PBEType.Flying, spicyness: 10, dryness: 10, sweetness: 10, bitterness: 10) }, { PBEItem.MagoBerry, new PBEDDBerryData(60, PBEType.Ghost, sweetness: 15) }, { PBEItem.MagostBerry, new PBEDDBerryData(70, PBEType.Rock, sweetness: 20, bitterness: 10) }, { PBEItem.MicleBerry, new PBEDDBerryData(80, PBEType.Rock, dryness: 40, sweetness: 10) }, { PBEItem.NanabBerry, new PBEDDBerryData(70, PBEType.Water, sweetness: 10, bitterness: 10) }, { PBEItem.NomelBerry, new PBEDDBerryData(70, PBEType.Dragon, spicyness: 10, sourness: 20) }, { PBEItem.OccaBerry, new PBEDDBerryData(60, PBEType.Fire, spicyness: 15, sweetness: 10) }, { PBEItem.OranBerry, new PBEDDBerryData(60, PBEType.Poison, spicyness: 10, dryness: 10, bitterness: 10, sourness: 10) }, { PBEItem.PamtreBerry, new PBEDDBerryData(70, PBEType.Steel, dryness: 30, sweetness: 10) }, { PBEItem.PasshoBerry, new PBEDDBerryData(60, PBEType.Water, dryness: 15, bitterness: 10) }, { PBEItem.PayapaBerry, new PBEDDBerryData(60, PBEType.Psychic, sweetness: 10, sourness: 15) }, { PBEItem.PechaBerry, new PBEDDBerryData(60, PBEType.Electric, sweetness: 10) }, { PBEItem.PersimBerry, new PBEDDBerryData(60, PBEType.Ground, spicyness: 10, dryness: 10, sweetness: 10, sourness: 10) }, { PBEItem.PetayaBerry, new PBEDDBerryData(80, PBEType.Poison, spicyness: 30, bitterness: 30, sourness: 10) }, { PBEItem.PinapBerry, new PBEDDBerryData(70, PBEType.Grass, spicyness: 10, sourness: 10) }, { PBEItem.PomegBerry, new PBEDDBerryData(70, PBEType.Ice, spicyness: 10, sweetness: 10, bitterness: 10) }, { PBEItem.QualotBerry, new PBEDDBerryData(70, PBEType.Poison, spicyness: 10, sweetness: 10, sourness: 10) }, { PBEItem.RabutaBerry, new PBEDDBerryData(70, PBEType.Ghost, bitterness: 20, sourness: 10) }, { PBEItem.RawstBerry, new PBEDDBerryData(60, PBEType.Grass, bitterness: 10) }, { PBEItem.RazzBerry, new PBEDDBerryData(60, PBEType.Steel, spicyness: 10, dryness: 10) }, { PBEItem.RindoBerry, new PBEDDBerryData(60, PBEType.Grass, spicyness: 10, bitterness: 15) }, { PBEItem.RowapBerry, new PBEDDBerryData(80, PBEType.Dark, spicyness: 10, sourness: 40) }, { PBEItem.SalacBerry, new PBEDDBerryData(80, PBEType.Fighting, sweetness: 30, bitterness: 10, sourness: 30) }, { PBEItem.ShucaBerry, new PBEDDBerryData(60, PBEType.Ground, spicyness: 10, sweetness: 15) }, { PBEItem.SitrusBerry, new PBEDDBerryData(60, PBEType.Psychic, dryness: 10, sweetness: 10, bitterness: 10, sourness: 10) }, { PBEItem.SpelonBerry, new PBEDDBerryData(70, PBEType.Dark, spicyness: 30, dryness: 10) }, { PBEItem.StarfBerry, new PBEDDBerryData(80, PBEType.Psychic, spicyness: 30, dryness: 10, sweetness: 30, bitterness: 10, sourness: 30) }, { PBEItem.TamatoBerry, new PBEDDBerryData(70, PBEType.Psychic, spicyness: 20, dryness: 10) }, { PBEItem.TangaBerry, new PBEDDBerryData(60, PBEType.Bug, spicyness: 20, sourness: 10) }, { PBEItem.WacanBerry, new PBEDDBerryData(60, PBEType.Electric, sweetness: 15, sourness: 10) }, { PBEItem.WatmelBerry, new PBEDDBerryData(80, PBEType.Fire, sweetness: 30, bitterness: 10) }, { PBEItem.WepearBerry, new PBEDDBerryData(70, PBEType.Electric, bitterness: 10, sourness: 10) }, { PBEItem.WikiBerry, new PBEDDBerryData(60, PBEType.Rock, dryness: 15) }, { PBEItem.YacheBerry, new PBEDDBerryData(60, PBEType.Ice, dryness: 10, sourness: 15) } }); } ================================================ FILE: PokemonBattleEngine.DefaultData/Data/EXPTables.cs ================================================ using Kermalis.PokemonBattleEngine.Data; using System; namespace Kermalis.PokemonBattleEngine.DefaultData.Data; public static class PBEDDEXPTables { #region Tables private static ReadOnlySpan ErraticTable => new uint[100] { 0, 15, 52, 122, 237, 406, 637, 942, 1_326, 1_800, 2_369, 3_041, 3_822, 4_719, 5_737, 6_881, 8_155, 9_564, 11_111, 12_800, 14_632, 16_610, 18_737, 21_012, 23_437, 26_012, 28_737, 31_610, 34_632, 37_800, 41_111, 44_564, 48_155, 51_881, 55_737, 59_719, 63_822, 68_041, 72_369, 76_800, 81_326, 85_942, 90_637, 95_406, 100_237, 105_122, 110_052, 115_015, 120_001, 125_000, 131_324, 137_795, 144_410, 151_165, 158_056, 165_079, 172_229, 179_503, 186_894, 194_400, 202_013, 209_728, 217_540, 225_443, 233_431, 241_496, 249_633, 257_834, 267_406, 276_458, 286_328, 296_358, 305_767, 316_074, 326_531, 336_255, 346_965, 357_812, 367_807, 378_880, 390_077, 400_293, 411_686, 423_190, 433_572, 445_239, 457_001, 467_489, 479_378, 491_346, 501_878, 513_934, 526_049, 536_557, 548_720, 560_922, 571_333, 583_539, 591_882, 600_000 }; private static ReadOnlySpan FastTable => new uint[100] { 0, 6, 21, 51, 100, 172, 274, 409, 583, 800, 1_064, 1_382, 1_757, 2_195, 2_700, 3_276, 3_930, 4_665, 5_487, 6_400, 7_408, 8_518, 9_733, 11_059, 12_500, 14_060, 15_746, 17_561, 19_511, 21_600, 23_832, 26_214, 28_749, 31_443, 34_300, 37_324, 40_522, 43_897, 47_455, 51_200, 55_136, 59_270, 63_605, 68_147, 72_900, 77_868, 83_058, 88_473, 94_119, 100_000, 106_120, 112_486, 119_101, 125_971, 133_100, 140_492, 148_154, 156_089, 164_303, 172_800, 181_584, 190_662, 200_037, 209_715, 219_700, 229_996, 240_610, 251_545, 262_807, 274_400, 286_328, 298_598, 311_213, 324_179, 337_500, 351_180, 365_226, 379_641, 394_431, 409_600, 425_152, 441_094, 457_429, 474_163, 491_300, 508_844, 526_802, 545_177, 563_975, 583_200, 602_856, 622_950, 643_485, 664_467, 685_900, 707_788, 730_138, 752_953, 776_239, 800_000 }; private static ReadOnlySpan MediumFastTable => new uint[100] { 0, 8, 27, 64, 125, 216, 343, 512, 729, 1_000, 1_331, 1_728, 2_197, 2_744, 3_375, 4_096, 4_913, 5_832, 6_859, 8_000, 9_261, 10_648, 12_167, 13_824, 15_625, 17_576, 19_683, 21_952, 24_389, 27_000, 29_791, 32_768, 35_937, 39_304, 42_875, 46_656, 50_653, 54_872, 59_319, 64_000, 68_921, 74_088, 79_507, 85_184, 91_125, 97_336, 103_823, 110_592, 117_649, 125_000, 132_651, 140_608, 148_877, 157_464, 166_375, 175_616, 185_193, 195_112, 205_379, 216_000, 226_981, 238_328, 250_047, 262_144, 274_625, 287_496, 300_763, 314_432, 328_509, 343_000, 357_911, 373_248, 389_017, 405_224, 421_875, 438_976, 456_533, 474_552, 493_039, 512_000, 531_441, 551_368, 571_787, 592_704, 614_125, 636_056, 658_503, 681_472, 704_969, 729_000, 753_571, 778_688, 804_357, 830_584, 857_375, 884_736, 912_673, 941_192, 970_299, 1_000_000 }; private static ReadOnlySpan MediumSlowTable => new uint[100] { 0, 9, 57, 96, 135, 179, 236, 314, 419, 560, 742, 973, 1_261, 1_612, 2_035, 2_535, 3_120, 3_798, 4_575, 5_460, 6_458, 7_577, 8_825, 10_208, 11_735, 13_411, 15_244, 17_242, 19_411, 21_760, 24_294, 27_021, 29_949, 33_084, 36_435, 40_007, 43_808, 47_846, 52_127, 56_660, 61_450, 66_505, 71_833, 77_440, 83_335, 89_523, 96_012, 102_810, 109_923, 117_360, 125_126, 133_229, 141_677, 150_476, 159_635, 169_159, 179_056, 189_334, 199_999, 211_060, 222_522, 234_393, 246_681, 259_392, 272_535, 286_115, 300_140, 314_618, 329_555, 344_960, 360_838, 377_197, 394_045, 411_388, 429_235, 447_591, 466_464, 485_862, 505_791, 526_260, 547_274, 568_841, 590_969, 613_664, 636_935, 660_787, 685_228, 710_266, 735_907, 762_160, 789_030, 816_525, 844_653, 873_420, 902_835, 932_903, 963_632, 995_030, 1_027_103, 1_059_860 }; private static ReadOnlySpan SlowTable => new uint[100] { 0, 10, 33, 80, 156, 270, 428, 640, 911, 1_250, 1_663, 2_160, 2_746, 3_430, 4_218, 5_120, 6_141, 7_290, 8_573, 10_000, 11_576, 13_310, 15_208, 17_280, 19_531, 21_970, 24_603, 27_440, 30_486, 33_750, 37_238, 40_960, 44_921, 49_130, 53_593, 58_320, 63_316, 68_590, 74_148, 80_000, 86_151, 92_610, 99_383, 106_480, 113_906, 121_670, 129_778, 138_240, 147_061, 156_250, 165_813, 175_760, 186_096, 196_830, 207_968, 219_520, 231_491, 243_890, 256_723, 270_000, 283_726, 297_910, 312_558, 327_680, 343_281, 359_370, 375_953, 393_040, 410_636, 428_750, 447_388, 466_560, 486_271, 506_530, 527_343, 548_720, 570_666, 593_190, 616_298, 640_000, 664_301, 689_210, 714_733, 740_880, 767_656, 795_070, 823_128, 851_840, 881_211, 911_250, 941_963, 973_360, 1_005_446, 1_038_230, 1_071_718, 1_105_920, 1_140_841, 1_176_490, 1_212_873, 1_250_000 }; private static ReadOnlySpan FluctuatingTable => new uint[100] { 0, 4, 13, 32, 65, 112, 178, 276, 393, 540, 745, 967, 1_230, 1_591, 1_957, 2_457, 3_046, 3_732, 4_526, 5_440, 6_482, 7_666, 9_003, 10_506, 12_187, 14_060, 16_140, 18_439, 20_974, 23_760, 26_811, 30_146, 33_780, 37_731, 42_017, 46_656, 50_653, 55_969, 60_505, 66_560, 71_677, 78_533, 84_277, 91_998, 98_415, 107_069, 114_205, 123_863, 131_766, 142_500, 151_222, 163_105, 172_697, 185_807, 196_322, 210_739, 222_231, 238_036, 250_562, 267_840, 281_456, 300_293, 315_059, 335_544, 351_520, 373_744, 390_991, 415_050, 433_631, 459_620, 479_600, 507_617, 529_063, 559_209, 582_187, 614_566, 639_146, 673_863, 700_115, 737_280, 765_275, 804_997, 834_809, 877_201, 908_905, 954_084, 987_754, 1_035_837, 1_071_552, 1_122_660, 1_160_499, 1_214_753, 1_254_796, 1_312_322, 1_354_652, 1_415_577, 1_460_276, 1_524_731, 1_571_884, 1_640_000 }; #endregion public static uint GetEXPRequired(PBEGrowthRate type, byte level) { if (type >= PBEGrowthRate.MAX) { throw new ArgumentOutOfRangeException(nameof(type)); } if (level < 1 || level > 100) { throw new ArgumentOutOfRangeException(nameof(level)); } return GetTable(type)[level - 1]; } public static byte GetEXPLevel(PBEGrowthRate type, uint exp) { if (type >= PBEGrowthRate.MAX) { throw new ArgumentOutOfRangeException(nameof(type)); } ReadOnlySpan table = GetTable(type); if (exp > table[100 - 1] || exp < table[1 - 1]) { throw new ArgumentOutOfRangeException(nameof(exp)); } for (byte i = 0; i < 99; i++) { uint cur = table[i]; uint next = table[i + 1]; if (exp >= cur && exp < next) { return (byte)(i + 1); } } return 100; } private static ReadOnlySpan GetTable(PBEGrowthRate type) { switch (type) { case PBEGrowthRate.Erratic: return ErraticTable; case PBEGrowthRate.Fast: return FastTable; case PBEGrowthRate.Fluctuating: return FluctuatingTable; case PBEGrowthRate.MediumFast: return MediumFastTable; case PBEGrowthRate.MediumSlow: return MediumSlowTable; case PBEGrowthRate.Slow: return SlowTable; default: throw new ArgumentOutOfRangeException(nameof(type)); } } } ================================================ FILE: PokemonBattleEngine.DefaultData/Data/EventPokemon.cs ================================================ using Kermalis.PokemonBattleEngine.Data; using System.Collections.Generic; using System.Collections.ObjectModel; namespace Kermalis.PokemonBattleEngine.DefaultData.Data; public sealed partial class PBEDDEventPokemon { public ReadOnlyCollection Generations { get; } public PBESpecies Species { get; } public byte Level { get; } /// means the Pokémon can be shiny or not shiny public bool? Shiny { get; } /// means the Pokémon can be male or female public PBEGender? Gender { get; } public ReadOnlyCollection PossibleAbilities { get; } public ReadOnlyCollection PossibleNatures { get; } public ReadOnlyCollection IndividualValues { get; } // A stat being "null" means that stat is random public ReadOnlyCollection Moves { get; } private PBEDDEventPokemon(IList generations, PBESpecies species, byte level, bool? shiny, PBEGender? gender, PBEAbility[] possibleAbilities, ReadOnlyCollection possibleNatures, byte?[] ivs, PBEMove[] moves) { Generations = new ReadOnlyCollection(generations); Species = species; Level = level; Shiny = shiny; Gender = gender; PossibleAbilities = new ReadOnlyCollection(possibleAbilities); PossibleNatures = possibleNatures; IndividualValues = new ReadOnlyCollection(ivs); Moves = new ReadOnlyCollection(moves); } private PBEDDEventPokemon(IList generations, PBESpecies species, byte level, bool? shiny, PBEGender? gender, PBEAbility[] possibleAbilities, PBENature[] possibleNatures, byte?[] ivs, PBEMove[] moves) : this(generations, species, level, shiny, gender, possibleAbilities, new ReadOnlyCollection(possibleNatures), ivs, moves) { // } private PBEDDEventPokemon(IList generations, PBESpecies species, byte level, bool? shiny, PBEGender? gender, PBEAbility[] possibleAbilities, PBEAlphabeticalList possibleNatures, byte?[] ivs, PBEMove[] moves) : this(generations, species, level, shiny, gender, possibleAbilities, possibleNatures.AsReadOnly(), ivs, moves) { // } } ================================================ FILE: PokemonBattleEngine.DefaultData/Data/EventPokemon_Data.cs ================================================ using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngine.Data.Utils; using System.Collections.Generic; using System.Collections.ObjectModel; namespace Kermalis.PokemonBattleEngine.DefaultData.Data; public sealed partial class PBEDDEventPokemon { public static ReadOnlyDictionary> Events { get; } = new(new Dictionary> { { PBESpecies.Absol, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // 5th anniversary egg ( new byte[] { 3 }, PBESpecies.Absol, 5, null, null, new PBEAbility[] { PBEAbility.Pressure }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Scratch, PBEMove.Leer, PBEMove.Spite, PBEMove.None } ), new PBEDDEventPokemon // 5th anniversary egg | Pokémon Stamp RS magazine raffle ( new byte[] { 3 }, PBESpecies.Absol, 5, null, null, new PBEAbility[] { PBEAbility.Pressure }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Scratch, PBEMove.Leer, PBEMove.Wish, PBEMove.None } ), new PBEDDEventPokemon // Pokémon Box promotion ( new byte[] { 3 }, PBESpecies.Absol, 35, false, null, new PBEAbility[] { PBEAbility.Pressure }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.RazorWind, PBEMove.Bite, PBEMove.SwordsDance, PBEMove.Spite } ), new PBEDDEventPokemon // Pokémon Box promotion ( new byte[] { 3 }, PBESpecies.Absol, 35, false, null, new PBEAbility[] { PBEAbility.Pressure }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.RazorWind, PBEMove.Bite, PBEMove.SwordsDance, PBEMove.Wish } ), new PBEDDEventPokemon // Journey across America | Party of the decade ( new byte[] { 3 }, PBESpecies.Absol, 70, false, null, new PBEAbility[] { PBEAbility.Pressure }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.DoubleTeam, PBEMove.Slash, PBEMove.FutureSight, PBEMove.PerishSong } ) }) }, { PBESpecies.Arceus, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Jewel of Life promotion ( new byte[] { 4 }, PBESpecies.Arceus, 100, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.Multitype }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Judgment, PBEMove.RoarOfTime, PBEMove.SpacialRend, PBEMove.ShadowForce } ), new PBEDDEventPokemon // Global Link promotion ( new byte[] { 5 }, PBESpecies.Arceus, 100, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.Multitype }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Recover, PBEMove.HyperBeam, PBEMove.PerishSong, PBEMove.Judgment } ) }) }, { PBESpecies.Audino, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // 2010 birthday ( new byte[] { 5 }, PBESpecies.Audino, 30, false, PBEGender.Female, new PBEAbility[] { PBEAbility.Healer }, new PBENature[] { PBENature.Calm }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.HealPulse, PBEMove.HelpingHand, PBEMove.Refresh, PBEMove.DoubleSlap } ), new PBEDDEventPokemon // 2011 birthday | 2012 birthday ( new byte[] { 5 }, PBESpecies.Audino, 30, false, PBEGender.Female, new PBEAbility[] { PBEAbility.Healer }, new PBENature[] { PBENature.Jolly, PBENature.Serious }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.HealPulse, PBEMove.HelpingHand, PBEMove.Refresh, PBEMove.Present } ) }) }, { PBESpecies.Axew, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Iris's Axew ( new byte[] { 5 }, PBESpecies.Axew, 1, null, PBEGender.Male, new PBEAbility[] { PBEAbility.MoldBreaker }, new PBENature[] { PBENature.Naive }, new byte?[] { null, null, null, null, null, 31 }, new PBEMove[] { PBEMove.Scratch, PBEMove.DragonRage, PBEMove.None, PBEMove.None } ), new PBEDDEventPokemon // Pokémon Searcher BW promotion ( new byte[] { 5 }, PBESpecies.Axew, 10, false, null, new PBEAbility[] { PBEAbility.MoldBreaker }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.DragonRage, PBEMove.Return, PBEMove.Endure, PBEMove.DragonClaw } ), new PBEDDEventPokemon // Best Wishes Iris's Axew ( new byte[] { 5 }, PBESpecies.Axew, 30, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Rivalry }, new PBENature[] { PBENature.Naive }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.DragonRage, PBEMove.Scratch, PBEMove.Outrage, PBEMove.GigaImpact } ) }) }, { PBESpecies.Azurill, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // PCNY ( new byte[] { 3 }, PBESpecies.Azurill, 5, false, null, new PBEAbility[] { PBEAbility.HugePower, PBEAbility.ThickFat }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Splash, PBEMove.Charm, PBEMove.None, PBEMove.None } ) }) }, { PBESpecies.Blastoise, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Journey across America | Party of the decade ( new byte[] { 3 }, PBESpecies.Blastoise, 70, false, null, new PBEAbility[] { PBEAbility.Torrent }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Protect, PBEMove.RainDance, PBEMove.SkullBash, PBEMove.HydroPump } ) }) }, { PBESpecies.Blaziken, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Journey across America | Party of the decade ( new byte[] { 3 }, PBESpecies.Blaziken, 70, false, null, new PBEAbility[] { PBEAbility.Blaze }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.BlazeKick, PBEMove.Slash, PBEMove.MirrorMove, PBEMove.SkyUppercut } ), new PBEDDEventPokemon // Train station ( new byte[] { 5 }, PBESpecies.Blaziken, 50, null, null, new PBEAbility[] { PBEAbility.Blaze }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.FlareBlitz, PBEMove.HiJumpKick, PBEMove.ThunderPunch, PBEMove.StoneEdge } ) }) }, { PBESpecies.Bulbasaur, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Gather more Pokémon ( new byte[] { 3 }, PBESpecies.Bulbasaur, 10, false, null, new PBEAbility[] { PBEAbility.Overgrow }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Tackle, PBEMove.Growl, PBEMove.LeechSeed, PBEMove.VineWhip } ), new PBEDDEventPokemon // Journey across America ( new byte[] { 3 }, PBESpecies.Bulbasaur, 70, false, null, new PBEAbility[] { PBEAbility.Overgrow }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.SweetScent, PBEMove.Growth, PBEMove.Synthesis, PBEMove.SolarBeam } ), new PBEDDEventPokemon // Kanto starter egg ( new byte[] { 5 }, PBESpecies.Bulbasaur, 1, null, null, new PBEAbility[] { PBEAbility.Overgrow }, PBEDataUtils.AllNatures, new byte?[] { null, null, 31, null, null, null }, new PBEMove[] { PBEMove.FalseSwipe, PBEMove.Block, PBEMove.FrenzyPlant, PBEMove.WeatherBall } ), new PBEDDEventPokemon // Global Link promotion ( new byte[] { 5 }, PBESpecies.Bulbasaur, 10, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Chlorophyll }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Tackle, PBEMove.Growl, PBEMove.LeechSeed, PBEMove.VineWhip } ) }) }, { PBESpecies.Chandelure, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Powerful Tag ( new byte[] { 5 }, PBESpecies.Chandelure, 50, false, PBEGender.Female, new PBEAbility[] { PBEAbility.FlashFire }, new PBENature[] { PBENature.Modest }, new byte?[] { null, null, null, 31, null, null }, new PBEMove[] { PBEMove.HeatWave, PBEMove.ShadowBall, PBEMove.EnergyBall, PBEMove.Psychic } ) }) }, { PBESpecies.Chansey, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // PCNY ( new byte[] { 3 }, PBESpecies.Chansey, 5, null, PBEGender.Female, new PBEAbility[] { PBEAbility.NaturalCure, PBEAbility.SereneGrace }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.SweetScent, PBEMove.Wish, PBEMove.None, PBEMove.None } ), new PBEDDEventPokemon // Gather more Pokémon ( new byte[] { 3 }, PBESpecies.Chansey, 10, false, PBEGender.Female, new PBEAbility[] { PBEAbility.NaturalCure, PBEAbility.SereneGrace }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Pound, PBEMove.Growl, PBEMove.TailWhip, PBEMove.Refresh } ) }) }, { PBESpecies.Charizard, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Journey across America | Top 10 distribution | Party of the decade ( new byte[] { 3 }, PBESpecies.Charizard, 70, false, null, new PBEAbility[] { PBEAbility.Blaze }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.WingAttack, PBEMove.Slash, PBEMove.DragonRage, PBEMove.FireSpin } ) }) }, { PBESpecies.Charmander, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Gather more Pokémon ( new byte[] { 3 }, PBESpecies.Charmander, 10, false, null, new PBEAbility[] { PBEAbility.Blaze }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Scratch, PBEMove.Growl, PBEMove.Ember, PBEMove.None } ), new PBEDDEventPokemon // 2007 birthday | 2008 birthday | 2009 birthday | 2010 birthday ( new byte[] { 4 }, PBESpecies.Charmander, 40, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Blaze }, new PBENature[] { PBENature.Hardy, PBENature.Mild, PBENature.Naive, PBENature.Naughty }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Return, PBEMove.HiddenPower, PBEMove.QuickAttack, PBEMove.Howl } ), new PBEDDEventPokemon // Kanto starter eggs ( new byte[] { 5 }, PBESpecies.Charmander, 1, null, null, new PBEAbility[] { PBEAbility.Blaze }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, 31 }, new PBEMove[] { PBEMove.FalseSwipe, PBEMove.Block, PBEMove.BlastBurn, PBEMove.Acrobatics } ), new PBEDDEventPokemon // Global Link promotion ( new byte[] { 5 }, PBESpecies.Charmander, 10, false, PBEGender.Male, new PBEAbility[] { PBEAbility.SolarPower }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Scratch, PBEMove.Growl, PBEMove.Ember, PBEMove.SmokeScreen } ) }) }, { PBESpecies.Chimchar, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // 2009 birthday | 2010 birthday ( new byte[] { 4 }, PBESpecies.Chimchar, 40, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Blaze }, new PBENature[] { PBENature.Hardy, PBENature.Mild }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Flamethrower, PBEMove.ThunderPunch, PBEMove.GrassKnot, PBEMove.HelpingHand } ), new PBEDDEventPokemon // Global Link promotion ( new byte[] { 5 }, PBESpecies.Chimchar, 10, false, PBEGender.Male, new PBEAbility[] { PBEAbility.IronFist }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Scratch, PBEMove.Leer, PBEMove.Ember, PBEMove.Taunt } ), new PBEDDEventPokemon // Global Link promotion ( new byte[] { 5 }, PBESpecies.Chimchar, 10, false, PBEGender.Male, new PBEAbility[] { PBEAbility.IronFist }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Leer, PBEMove.Ember, PBEMove.Taunt, PBEMove.FakeOut } ) }) }, { PBESpecies.Cradily, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // PCNY ( new byte[] { 3 }, PBESpecies.Cradily, 40, false, null, new PBEAbility[] { PBEAbility.SuctionCups }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Acid, PBEMove.Ingrain, PBEMove.ConfuseRay, PBEMove.Amnesia } ) }) }, { PBESpecies.Cresselia, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // World championships 2013 ( new byte[] { 5 }, PBESpecies.Cresselia, 68, false, PBEGender.Female, new PBEAbility[] { PBEAbility.Levitate }, new PBENature[] { PBENature.Modest }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.IceBeam, PBEMove.Psyshock, PBEMove.EnergyBall, PBEMove.HiddenPower } ) }) }, { PBESpecies.Crobat, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // World championships 2010 ( new byte[] { 4 }, PBESpecies.Crobat, 30, false, PBEGender.Male, new PBEAbility[] { PBEAbility.InnerFocus }, new PBENature[] { PBENature.Timid }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.HeatWave, PBEMove.AirSlash, PBEMove.SludgeBomb, PBEMove.SuperFang } ) }) }, { PBESpecies.Cubchoo, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Pokémon Smash! Cubchoo ( new byte[] { 5 }, PBESpecies.Cubchoo, 15, false, null, new PBEAbility[] { PBEAbility.SnowCloak }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.PowderSnow, PBEMove.Growl, PBEMove.Bide, PBEMove.IcyWind } ) }) }, { PBESpecies.Darkrai, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // The Rise of Darkrai promotion ( new byte[] { 4 }, PBESpecies.Darkrai, 50, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.BadDreams }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.RoarOfTime, PBEMove.SpacialRend, PBEMove.Nightmare, PBEMove.Hypnosis } ), new PBEDDEventPokemon // Almia Darkrai ( new byte[] { 4 }, PBESpecies.Darkrai, 50, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.BadDreams }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.DarkVoid, PBEMove.DarkPulse, PBEMove.ShadowBall, PBEMove.DoubleTeam } ), new PBEDDEventPokemon // Winter 2010 | Victini movie promotions | Winter 2011 | May 2012 ( new byte[] { 5 }, PBESpecies.Darkrai, 50, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.BadDreams }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.DarkVoid, PBEMove.OminousWind, PBEMove.FaintAttack, PBEMove.Nightmare } ) }) }, { PBESpecies.Deino, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Year of the dragon ( new byte[] { 5 }, PBESpecies.Deino, 1, true, null, new PBEAbility[] { PBEAbility.Hustle }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Tackle, PBEMove.DragonRage, PBEMove.None, PBEMove.None } ) }) }, { PBESpecies.Dialga, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // 2013 shiny creation trio ( new byte[] { 5 }, PBESpecies.Dialga, 100, true, PBEGender.Genderless, new PBEAbility[] { PBEAbility.Pressure }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.DragonPulse, PBEMove.DracoMeteor, PBEMove.RoarOfTime, PBEMove.AuraSphere } ) }) }, { PBESpecies.Druddigon, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Year of the dragon ( new byte[] { 5 }, PBESpecies.Druddigon, 1, true, null, new PBEAbility[] { PBEAbility.RoughSkin, PBEAbility.SheerForce }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Leer, PBEMove.Scratch, PBEMove.None, PBEMove.None } ) }) }, { PBESpecies.Eevee, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Eevee collection promotion ( new byte[] { 4 }, PBESpecies.Eevee, 10, false, PBEGender.Female, new PBEAbility[] { PBEAbility.Adaptability }, new PBENature[] { PBENature.Lonely }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Covet, PBEMove.Bite, PBEMove.HelpingHand, PBEMove.Attract } ), new PBEDDEventPokemon // World championships 2010 ( new byte[] { 4 }, PBESpecies.Eevee, 50, true, PBEGender.Male, new PBEAbility[] { PBEAbility.Adaptability }, new PBENature[] { PBENature.Hardy }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.IronTail, PBEMove.TrumpCard, PBEMove.Flail, PBEMove.QuickAttack } ), new PBEDDEventPokemon // Ikimono-gakari promotion ( new byte[] { 5 }, PBESpecies.Eevee, 50, false, PBEGender.Female, new PBEAbility[] { PBEAbility.Adaptability }, new PBENature[] { PBENature.Hardy }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Sing, PBEMove.Return, PBEMove.EchoedVoice, PBEMove.Attract } ) }) }, { PBESpecies.Emboar, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Pokémon Center 15th anniversary ( new byte[] { 5 }, PBESpecies.Emboar, 100, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Blaze }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.FlareBlitz, PBEMove.HammerArm, PBEMove.WildCharge, PBEMove.HeadSmash } ) }) }, { PBESpecies.Empoleon, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Pokémon Center 15th anniversary ( new byte[] { 5 }, PBESpecies.Empoleon, 100, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Torrent }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.HydroPump, PBEMove.IceBeam, PBEMove.AquaJet, PBEMove.GrassKnot } ) }) }, { PBESpecies.Espeon, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Journey across America | Party of the decade ( new byte[] { 3 }, PBESpecies.Espeon, 70, false, null, new PBEAbility[] { PBEAbility.Synchronize }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Psybeam, PBEMove.PsychUp, PBEMove.Psychic, PBEMove.MorningSun } ), new PBEDDEventPokemon // Global Link promotion ( new byte[] { 5 }, PBESpecies.Espeon, 10, false, PBEGender.Male, new PBEAbility[] { PBEAbility.MagicBounce }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.TailWhip, PBEMove.Tackle, PBEMove.HelpingHand, PBEMove.SandAttack } ) }) }, { PBESpecies.Farfetchd, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // PCNY ( new byte[] { 3 }, PBESpecies.Farfetchd, 5, null, null, new PBEAbility[] { PBEAbility.InnerFocus, PBEAbility.KeenEye }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Yawn, PBEMove.Wish, PBEMove.None, PBEMove.None } ) }) }, { PBESpecies.Flareon, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Global Link promotion ( new byte[] { 5 }, PBESpecies.Flareon, 10, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Guts }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.TailWhip, PBEMove.Tackle, PBEMove.HelpingHand, PBEMove.SandAttack } ) }) }, { PBESpecies.Garchomp, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Strongest class ( new byte[] { 5 }, PBESpecies.Garchomp, 100, false, PBEGender.Male, new PBEAbility[] { PBEAbility.SandVeil }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Outrage, PBEMove.Earthquake, PBEMove.SwordsDance, PBEMove.StoneEdge } ), new PBEDDEventPokemon // Global Link promotion ( new byte[] { 5 }, PBESpecies.Garchomp, 48, false, PBEGender.Male, new PBEAbility[] { PBEAbility.RoughSkin }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Slash, PBEMove.DragonClaw, PBEMove.Dig, PBEMove.Crunch } ) }) }, { PBESpecies.Genesect, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Plasma Genesect ( new byte[] { 5 }, PBESpecies.Genesect, 50, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.Download }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.TechnoBlast, PBEMove.MagnetBomb, PBEMove.SolarBeam, PBEMove.SignalBeam } ), new PBEDDEventPokemon // Genesect movie promotion ( new byte[] { 5 }, PBESpecies.Genesect, 100, true, PBEGender.Genderless, new PBEAbility[] { PBEAbility.Download }, new PBENature[] { PBENature.Hasty }, new byte?[] { null, 31, null, null, null, 31 }, new PBEMove[] { PBEMove.ExtremeSpeed, PBEMove.TechnoBlast, PBEMove.BlazeKick, PBEMove.ShiftGear } ) }) }, { PBESpecies.Giratina, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // 2013 shiny creation trio ( new byte[] { 5 }, PBESpecies.Giratina, 100, true, PBEGender.Genderless, new PBEAbility[] { PBEAbility.Pressure }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.DragonPulse, PBEMove.DragonClaw, PBEMove.AuraSphere, PBEMove.ShadowForce } ) }) }, { PBESpecies.Glaceon, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Global Link promotion ( new byte[] { 5 }, PBESpecies.Glaceon, 10, false, PBEGender.Male, new PBEAbility[] { PBEAbility.IceBody }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.TailWhip, PBEMove.Tackle, PBEMove.HelpingHand, PBEMove.SandAttack } ) }) }, { PBESpecies.Golurk, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Victini movies promotion ( new byte[] { 5 }, PBESpecies.Golurk, 70, true, PBEGender.Genderless, new PBEAbility[] { PBEAbility.IronFist }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.ShadowPunch, PBEMove.HyperBeam, PBEMove.GyroBall, PBEMove.HammerArm } ) }) }, { PBESpecies.Gorebyss, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // PCNY ( new byte[] { 3 }, PBESpecies.Gorebyss, 20, false, null, new PBEAbility[] { PBEAbility.SwiftSwim }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Whirlpool, PBEMove.Confusion, PBEMove.Agility, PBEMove.None } ) }) }, { PBESpecies.Haxorus, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Iris's Haxorus ( new byte[] { 5 }, PBESpecies.Haxorus, 59, false, PBEGender.Female, new PBEAbility[] { PBEAbility.MoldBreaker }, new PBENature[] { PBENature.Naive }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Earthquake, PBEMove.DualChop, PBEMove.XScissor, PBEMove.DragonDance } ) }) }, { PBESpecies.Huntail, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // PCNY ( new byte[] { 3 }, PBESpecies.Huntail, 20, false, null, new PBEAbility[] { PBEAbility.SwiftSwim }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Whirlpool, PBEMove.Bite, PBEMove.Screech, PBEMove.None } ) }) }, { PBESpecies.Hydreigon, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Victini movies promotion ( new byte[] { 5 }, PBESpecies.Hydreigon, 70, true, PBEGender.Male, new PBEAbility[] { PBEAbility.Levitate }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.HyperVoice, PBEMove.DragonBreath, PBEMove.Flamethrower, PBEMove.FocusBlast } ) }) }, { PBESpecies.Infernape, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Pokémon Center 15th anniversary ( new byte[] { 5 }, PBESpecies.Infernape, 100, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Blaze }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.FireBlast, PBEMove.CloseCombat, PBEMove.Uturn, PBEMove.GrassKnot } ) }) }, { PBESpecies.Jirachi, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Jirachi Wish Maker promotion | Wishmaker Jirachi | Channel Jirachi | 2004 Jirachi | 2005 Jirachi | 2006 Jirachi | 2007 Jirachi | 2008 Jirachi | 2010 (Korea) Jirachi ( new byte[] { 3, 4 }, PBESpecies.Jirachi, 5, null, PBEGender.Genderless, new PBEAbility[] { PBEAbility.SereneGrace }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Wish, PBEMove.Confusion, PBEMove.Rest, PBEMove.None } ), new PBEDDEventPokemon // PokéPark Jirachi ( new byte[] { 3 }, PBESpecies.Jirachi, 30, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.SereneGrace }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Wish, PBEMove.Psychic, PBEMove.HelpingHand, PBEMove.Rest } ), new PBEDDEventPokemon // 2009 Jirachi | 2010 (rest of the world) Jirachi ( new byte[] { 4 }, PBESpecies.Jirachi, 5, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.SereneGrace }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Wish, PBEMove.Confusion, PBEMove.Rest, PBEMove.DracoMeteor } ), new PBEDDEventPokemon // Decolora Jirachi ( new byte[] { 5 }, PBESpecies.Jirachi, 50, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.SereneGrace }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.HealingWish, PBEMove.Psychic, PBEMove.Swift, PBEMove.MeteorMash } ), new PBEDDEventPokemon // Character Fair Jirachi ( new byte[] { 5 }, PBESpecies.Jirachi, 50, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.SereneGrace }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Wish, PBEMove.HealingWish, PBEMove.CosmicPower, PBEMove.MeteorMash } ), new PBEDDEventPokemon // 2013 Jirachi ( new byte[] { 5 }, PBESpecies.Jirachi, 50, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.SereneGrace }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.DracoMeteor, PBEMove.MeteorMash, PBEMove.Wish, PBEMove.FollowMe } ), new PBEDDEventPokemon // Chilseok Jirachi ( new byte[] { 5 }, PBESpecies.Jirachi, 50, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.SereneGrace }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Wish, PBEMove.HealingWish, PBEMove.Swift, PBEMove.Return } ) }) }, { PBESpecies.Jolteon, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Global Link promotion ( new byte[] { 5 }, PBESpecies.Jolteon, 10, false, PBEGender.Male, new PBEAbility[] { PBEAbility.QuickFeet }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.TailWhip, PBEMove.Tackle, PBEMove.HelpingHand, PBEMove.SandAttack } ) }) }, { PBESpecies.Karrablast, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Trade for Evolution! ( new byte[] { 5 }, PBESpecies.Karrablast, 30, false, null, new PBEAbility[] { PBEAbility.ShedSkin, PBEAbility.Swarm }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.FuryAttack, PBEMove.Headbutt, PBEMove.FalseSwipe, PBEMove.BugBuzz } ), new PBEDDEventPokemon // Summer 2011 ( new byte[] { 5 }, PBESpecies.Karrablast, 50, false, null, new PBEAbility[] { PBEAbility.ShedSkin, PBEAbility.Swarm }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Megahorn, PBEMove.TakeDown, PBEMove.XScissor, PBEMove.Flail } ) }) }, { PBESpecies.Keldeo, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Kyurem VS. The Sword of Justice promotion ( new byte[] { 5 }, PBESpecies.Keldeo, 15, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.Justified }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.AquaJet, PBEMove.Leer, PBEMove.DoubleKick, PBEMove.BubbleBeam } ), new PBEDDEventPokemon // Winter 2013 ( new byte[] { 5 }, PBESpecies.Keldeo, 50, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.Justified }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.SacredSword, PBEMove.HydroPump, PBEMove.AquaJet, PBEMove.SwordsDance } ) }) }, { PBESpecies.Latias, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Journey across America | Top 10 distribution | Party of the decade ( new byte[] { 3 }, PBESpecies.Latias, 70, false, PBEGender.Female, new PBEAbility[] { PBEAbility.Levitate }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.MistBall, PBEMove.Psychic, PBEMove.Recover, PBEMove.Charm } ) }) }, { PBESpecies.Latios, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Journey across America | Top 10 distribution | Party of the decade ( new byte[] { 3 }, PBESpecies.Latios, 70, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Levitate }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.LusterPurge, PBEMove.Psychic, PBEMove.Recover, PBEMove.DragonDance } ) }) }, { PBESpecies.Leafeon, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Global Link promotion ( new byte[] { 5 }, PBESpecies.Leafeon, 10, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Chlorophyll }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.TailWhip, PBEMove.Tackle, PBEMove.HelpingHand, PBEMove.SandAttack } ) }) }, { PBESpecies.Liepard, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Se Jun's Liepard ( new byte[] { 5 }, PBESpecies.Liepard, 20, false, PBEGender.Female, new PBEAbility[] { PBEAbility.Prankster }, new PBENature[] { PBENature.Jolly }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.FakeOut, PBEMove.FoulPlay, PBEMove.Encore, PBEMove.Swagger } ) }) }, { PBESpecies.Lileep, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Pokémon Adventure Camp fossil Pokémon ( new byte[] { 5 }, PBESpecies.Lileep, 15, false, null, new PBEAbility[] { PBEAbility.SuctionCups }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Recover, PBEMove.RockSlide, PBEMove.Constrict, PBEMove.Acid } ) }) }, { PBESpecies.Lucario, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // PalCity Lucario ( new byte[] { 4 }, PBESpecies.Lucario, 50, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Steadfast }, new PBENature[] { PBENature.Modest }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.AuraSphere, PBEMove.DarkPulse, PBEMove.DragonPulse, PBEMove.WaterPulse } ), new PBEDDEventPokemon // World championships 2008 ( new byte[] { 4 }, PBESpecies.Lucario, 30, false, PBEGender.Male, new PBEAbility[] { PBEAbility.InnerFocus }, new PBENature[] { PBENature.Adamant }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.ForcePalm, PBEMove.BoneRush, PBEMove.SunnyDay, PBEMove.BlazeKick } ), new PBEDDEventPokemon // Global Link promotion ( new byte[] { 5 }, PBESpecies.Lucario, 10, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Justified }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Detect, PBEMove.MetalClaw, PBEMove.Counter, PBEMove.BulletPunch } ), new PBEDDEventPokemon // Powerful Tag ( new byte[] { 5 }, PBESpecies.Lucario, 50, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Justified }, new PBENature[] { PBENature.Naughty }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.BulletPunch, PBEMove.CloseCombat, PBEMove.StoneEdge, PBEMove.ShadowClaw } ) }) }, { PBESpecies.Magikarp, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // GTS Ryuuta ( new byte[] { 4 }, PBESpecies.Magikarp, 4, false, PBEGender.Male, new PBEAbility[] { PBEAbility.SwiftSwim }, new PBENature[] { PBENature.Modest }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Splash, PBEMove.None, PBEMove.None, PBEMove.None } ), new PBEDDEventPokemon // GTS Nana ( new byte[] { 4 }, PBESpecies.Magikarp, 5, false, PBEGender.Female, new PBEAbility[] { PBEAbility.SwiftSwim }, new PBENature[] { PBENature.Lonely }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Splash, PBEMove.None, PBEMove.None, PBEMove.None } ), new PBEDDEventPokemon // GTS Utz ( new byte[] { 4 }, PBESpecies.Magikarp, 5, false, PBEGender.Male, new PBEAbility[] { PBEAbility.SwiftSwim }, new PBENature[] { PBENature.Relaxed }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Splash, PBEMove.None, PBEMove.None, PBEMove.None } ), new PBEDDEventPokemon // GTS Ruirui ( new byte[] { 4 }, PBESpecies.Magikarp, 6, false, PBEGender.Female, new PBEAbility[] { PBEAbility.SwiftSwim }, new PBENature[] { PBENature.Rash }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Splash, PBEMove.None, PBEMove.None, PBEMove.None } ), new PBEDDEventPokemon // GTS Nory ( new byte[] { 4 }, PBESpecies.Magikarp, 7, false, PBEGender.Female, new PBEAbility[] { PBEAbility.SwiftSwim }, new PBENature[] { PBENature.Hardy }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Splash, PBEMove.None, PBEMove.None, PBEMove.None } ), new PBEDDEventPokemon // Pokémon Center relocation ( new byte[] { 5 }, PBESpecies.Magikarp, 99, true, null, new PBEAbility[] { PBEAbility.SwiftSwim }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Flail, PBEMove.HydroPump, PBEMove.Bounce, PBEMove.Splash } ) }) }, { PBESpecies.Meloetta, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Kyruem VS. The Sword of Justice promotion ( new byte[] { 5 }, PBESpecies.Meloetta, 15, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.SereneGrace }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Round, PBEMove.QuickAttack, PBEMove.Confusion, PBEMove.None } ), new PBEDDEventPokemon // Summer 2013 ( new byte[] { 5 }, PBESpecies.Meloetta, 50, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.SereneGrace }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Round, PBEMove.TeeterDance, PBEMove.Psychic, PBEMove.CloseCombat } ) }) }, { PBESpecies.Metagross, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Train station ( new byte[] { 5 }, PBESpecies.Metagross, 50, null, PBEGender.Genderless, new PBEAbility[] { PBEAbility.ClearBody }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.MeteorMash, PBEMove.Earthquake, PBEMove.BulletPunch, PBEMove.HammerArm } ), new PBEDDEventPokemon // Strongest class ( new byte[] { 5 }, PBESpecies.Metagross, 100, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.ClearBody }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.BulletPunch, PBEMove.ZenHeadbutt, PBEMove.HammerArm, PBEMove.IcePunch } ), new PBEDDEventPokemon // World championships 2013 ( new byte[] { 5 }, PBESpecies.Metagross, 45, true, PBEGender.Genderless, new PBEAbility[] { PBEAbility.ClearBody }, new PBENature[] { PBENature.Adamant }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.MeteorMash, PBEMove.Earthquake, PBEMove.ZenHeadbutt, PBEMove.Protect } ), new PBEDDEventPokemon // Global Link promotion ( new byte[] { 5 }, PBESpecies.Metagross, 45, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.LightMetal }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.IronDefense, PBEMove.Agility, PBEMove.HammerArm, PBEMove.DoubleEdge } ), new PBEDDEventPokemon // Global Link promotion ( new byte[] { 5 }, PBESpecies.Metagross, 45, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.LightMetal }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Psychic, PBEMove.MeteorMash, PBEMove.HammerArm, PBEMove.DoubleEdge } ), new PBEDDEventPokemon // Steven's Metagross ( new byte[] { 5 }, PBESpecies.Metagross, 58, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.ClearBody }, new PBENature[] { PBENature.Serious }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Earthquake, PBEMove.HyperBeam, PBEMove.Psychic, PBEMove.MeteorMash } ) }) }, { PBESpecies.Metang, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Gale of Darkness demo promotion ( new byte[] { 3 }, PBESpecies.Metang, 30, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.ClearBody }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.TakeDown, PBEMove.Confusion, PBEMove.MetalClaw, PBEMove.Refresh } ) }) }, { PBESpecies.Minun, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // PokéPark Minun ( new byte[] { 3 }, PBESpecies.Minun, 5, null, null, new PBEAbility[] { PBEAbility.Minus }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Growl, PBEMove.ThunderWave, PBEMove.MudSport, PBEMove.None } ), new PBEDDEventPokemon // Gather more Pokémon ( new byte[] { 3 }, PBESpecies.Minun, 10, false, null, new PBEAbility[] { PBEAbility.Minus }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Growl, PBEMove.ThunderWave, PBEMove.QuickAttack, PBEMove.None } ) }) }, { PBESpecies.Misdreavus, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Gather more Pokémon ( new byte[] { 3 }, PBESpecies.Misdreavus, 10, false, null, new PBEAbility[] { PBEAbility.Levitate }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Growl, PBEMove.Psywave, PBEMove.Spite, PBEMove.None } ) }) }, { PBESpecies.Ninetales, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Powerful Tag ( new byte[] { 5 }, PBESpecies.Ninetales, 50, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Drought }, new PBENature[] { PBENature.Bold }, new byte?[] { null, null, 31, null, null, null }, new PBEMove[] { PBEMove.HeatWave, PBEMove.SolarBeam, PBEMove.Psyshock, PBEMove.WillOWisp } ) }) }, { PBESpecies.Palkia, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // 2013 shiny creation trio ( new byte[] { 5 }, PBESpecies.Palkia, 100, true, PBEGender.Genderless, new PBEAbility[] { PBEAbility.Pressure }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.HydroPump, PBEMove.DracoMeteor, PBEMove.SpacialRend, PBEMove.AuraSphere } ) }) }, { PBESpecies.Pansage, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Cilan's Pansage ( new byte[] { 5 }, PBESpecies.Pansage, 1, null, PBEGender.Male, new PBEAbility[] { PBEAbility.Gluttony }, new PBENature[] { PBENature.Brave }, new byte?[] { null, 31, null, null, null, null }, new PBEMove[] { PBEMove.BulletSeed, PBEMove.Bite, PBEMove.SolarBeam, PBEMove.Dig } ), new PBEDDEventPokemon // Best Wishes Cilan's Pansage ( new byte[] { 5 }, PBESpecies.Pansage, 30, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Gluttony }, new PBENature[] { PBENature.Serious }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.BulletSeed, PBEMove.SolarBeam, PBEMove.RockTomb, PBEMove.Dig } ), new PBEDDEventPokemon // Global Link promotion ( new byte[] { 5 }, PBESpecies.Pansage, 10, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Overgrow }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Leer, PBEMove.Lick, PBEMove.VineWhip, PBEMove.LeafStorm } ) }) }, { PBESpecies.Pichu, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // 5th anniversary egg | Pokémon Stamp RS magazine raffle ( new byte[] { 3 }, PBESpecies.Pichu, 5, null, null, new PBEAbility[] { PBEAbility.Static }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.ThunderShock, PBEMove.Charm, PBEMove.TeeterDance, PBEMove.None } ), new PBEDDEventPokemon // 5th anniversary egg ( new byte[] { 3 }, PBESpecies.Pichu, 5, null, null, new PBEAbility[] { PBEAbility.Static }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.ThunderShock, PBEMove.Charm, PBEMove.Wish, PBEMove.None } ), new PBEDDEventPokemon // Pokémon Box bonus egg ( new byte[] { 3 }, PBESpecies.Pichu, 5, null, null, new PBEAbility[] { PBEAbility.Static }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.ThunderShock, PBEMove.Charm, PBEMove.Surf, PBEMove.None } ), new PBEDDEventPokemon // PokéPark Pichu ( new byte[] { 3 }, PBESpecies.Pichu, 5, null, null, new PBEAbility[] { PBEAbility.Static }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.ThunderShock, PBEMove.Charm, PBEMove.FollowMe, PBEMove.None } ), new PBEDDEventPokemon // Red and Green 12th anniversary ( new byte[] { 4 }, PBESpecies.Pichu, 1, null, null, new PBEAbility[] { PBEAbility.Static }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.VoltTackle, PBEMove.Thunderbolt, PBEMove.GrassKnot, PBEMove.Return } ), new PBEDDEventPokemon // Jewel of Life promotion ( new byte[] { 4 }, PBESpecies.Pichu, 30, true, PBEGender.Male, new PBEAbility[] { PBEAbility.Static }, new PBENature[] { PBENature.Jolly }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Charge, PBEMove.VoltTackle, PBEMove.Endeavor, PBEMove.Endure } ) }) }, { PBESpecies.Pidove, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Ash's Pidove ( new byte[] { 5 }, PBESpecies.Pidove, 1, null, PBEGender.Female, new PBEAbility[] { PBEAbility.SuperLuck }, new PBENature[] { PBENature.Hardy }, new byte?[] { null, 31, null, null, null, null }, new PBEMove[] { PBEMove.Gust, PBEMove.QuickAttack, PBEMove.AirCutter, PBEMove.None } ) }) }, { PBESpecies.Pikachu, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // PCNY stone promotion ( new byte[] { 3 }, PBESpecies.Pikachu, 50, false, null, new PBEAbility[] { PBEAbility.Static }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Thunderbolt, PBEMove.Agility, PBEMove.Thunder, PBEMove.LightScreen } ), new PBEDDEventPokemon // All Nippon Airways Pikachu (Gen 3) ( new byte[] { 3 }, PBESpecies.Pikachu, 10, false, null, new PBEAbility[] { PBEAbility.Static }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Fly, PBEMove.ThunderShock, PBEMove.TailWhip, PBEMove.ThunderWave } ), new PBEDDEventPokemon // Yokohama Pokémon Center opening ( new byte[] { 3 }, PBESpecies.Pikachu, 10, false, null, new PBEAbility[] { PBEAbility.Static }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.ThunderShock, PBEMove.Growl, PBEMove.ThunderWave, PBEMove.Surf } ), new PBEDDEventPokemon // GW Festival | Sapporo Pokémon Center opening ( new byte[] { 3 }, PBESpecies.Pikachu, 10, false, null, new PBEAbility[] { PBEAbility.Static }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Growl, PBEMove.TailWhip, PBEMove.ThunderWave, PBEMove.Fly } ), new PBEDDEventPokemon // Gather more Pokémon | Colosseum Pikachu ( new byte[] { 3 }, PBESpecies.Pikachu, 10, false, null, new PBEAbility[] { PBEAbility.Static }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.ThunderShock, PBEMove.Growl, PBEMove.TailWhip, PBEMove.ThunderWave } ), new PBEDDEventPokemon // Journey across America | Top 10 distribution ( new byte[] { 3 }, PBESpecies.Pikachu, 70, false, null, new PBEAbility[] { PBEAbility.Static }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Thunderbolt, PBEMove.Agility, PBEMove.Thunder, PBEMove.LightScreen } ), new PBEDDEventPokemon // Party of the decade ( new byte[] { 3 }, PBESpecies.Pikachu, 70, false, null, new PBEAbility[] { PBEAbility.Static }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Thunderbolt, PBEMove.Thunder, PBEMove.LightScreen, PBEMove.Fly } ), new PBEDDEventPokemon // Battle Revolution Pikachu ( new byte[] { 4 }, PBESpecies.Pikachu, 10, false, PBEGender.Female, new PBEAbility[] { PBEAbility.Static }, new PBENature[] { PBENature.Hardy }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.VoltTackle, PBEMove.Surf, PBEMove.TailWhip, PBEMove.ThunderWave } ), new PBEDDEventPokemon // TCG world championships 2007 ( new byte[] { 4 }, PBESpecies.Pikachu, 50, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Static }, new PBENature[] { PBENature.Hardy }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Surf, PBEMove.Thunderbolt, PBEMove.LightScreen, PBEMove.QuickAttack } ), new PBEDDEventPokemon // Nintendo Spot promotion | Nintendo Zone promotion | 7-Eleven Pikachu ( new byte[] { 4 }, PBESpecies.Pikachu, 20, false, null, new PBEAbility[] { PBEAbility.Static }, new PBENature[] { PBENature.Bashful, PBENature.Docile }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.QuickAttack, PBEMove.ThunderShock, PBEMove.TailWhip, PBEMove.Present } ), new PBEDDEventPokemon // Yokohama Pokémon Center reopening | 2008 birthday | 2009 birthday ( new byte[] { 4 }, PBESpecies.Pikachu, 40, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Static }, new PBENature[] { PBENature.Mild, PBENature.Modest }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Surf, PBEMove.Thunder, PBEMove.Protect, PBEMove.None } ), new PBEDDEventPokemon // Sleeping Pikachu Collection promotion ( new byte[] { 4 }, PBESpecies.Pikachu, 50, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Static }, new PBENature[] { PBENature.Relaxed }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Rest, PBEMove.SleepTalk, PBEMove.Yawn, PBEMove.Snore } ), new PBEDDEventPokemon // Character Fair Pikachu ( new byte[] { 4 }, PBESpecies.Pikachu, 30, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Static }, new PBENature[] { PBENature.Brave }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.VoltTackle, PBEMove.QuickAttack, PBEMove.Thunderbolt, PBEMove.IronTail } ), new PBEDDEventPokemon // Kyoto Cross Media Experience Pikachu ( new byte[] { 4 }, PBESpecies.Pikachu, 30, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Static }, new PBENature[] { PBENature.Naughty }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.LastResort, PBEMove.Present, PBEMove.Thunderbolt, PBEMove.QuickAttack } ), new PBEDDEventPokemon // Ario Pikachu ( new byte[] { 4 }, PBESpecies.Pikachu, 20, false, PBEGender.Female, new PBEAbility[] { PBEAbility.Static }, new PBENature[] { PBENature.Bashful }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Present, PBEMove.QuickAttack, PBEMove.ThunderWave, PBEMove.TailWhip } ), new PBEDDEventPokemon // Ash's Pikachu ( new byte[] { 4 }, PBESpecies.Pikachu, 50, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Static }, new PBENature[] { PBENature.Naughty }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.VoltTackle, PBEMove.IronTail, PBEMove.QuickAttack, PBEMove.Thunderbolt } ), new PBEDDEventPokemon // All Nippon Airways Pikachu (Gen 5) ( new byte[] { 5 }, PBESpecies.Pikachu, 50, false, null, new PBEAbility[] { PBEAbility.Static }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Fly, PBEMove.IronTail, PBEMove.ElectroBall, PBEMove.QuickAttack } ), new PBEDDEventPokemon // Singing Pikachu ( new byte[] { 5 }, PBESpecies.Pikachu, 30, false, PBEGender.Female, new PBEAbility[] { PBEAbility.Lightningrod }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Sing, PBEMove.TeeterDance, PBEMove.Encore, PBEMove.ElectroBall } ), new PBEDDEventPokemon // ExtremeSpeed Pikachu ( new byte[] { 5 }, PBESpecies.Pikachu, 50, null, PBEGender.Female, new PBEAbility[] { PBEAbility.Static }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.ExtremeSpeed, PBEMove.Thunderbolt, PBEMove.GrassKnot, PBEMove.BrickBreak } ), new PBEDDEventPokemon // Pikachu Festival | Pika Pika Carnival | Summer 2012 Pikachu | Strongest class ( new byte[] { 5 }, PBESpecies.Pikachu, 100, null, PBEGender.Female, new PBEAbility[] { PBEAbility.Static }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Thunder, PBEMove.VoltTackle, PBEMove.GrassKnot, PBEMove.QuickAttack } ), new PBEDDEventPokemon // World championships 2012 ( new byte[] { 5 }, PBESpecies.Pikachu, 50, false, PBEGender.Female, new PBEAbility[] { PBEAbility.Lightningrod }, new PBENature[] { PBENature.Timid }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Fly, PBEMove.Thunderbolt, PBEMove.GrassKnot, PBEMove.Protect } ), new PBEDDEventPokemon // Pokémon Center 15th anniversary ( new byte[] { 5 }, PBESpecies.Pikachu, 100, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Lightningrod }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.VoltTackle, PBEMove.QuickAttack, PBEMove.Feint, PBEMove.VoltSwitch } ), new PBEDDEventPokemon // Best Wishes Ash's Pikachu ( new byte[] { 5 }, PBESpecies.Pikachu, 50, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Static }, new PBENature[] { PBENature.Brave }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Thunderbolt, PBEMove.QuickAttack, PBEMove.IronTail, PBEMove.ElectroBall } ), new PBEDDEventPokemon // Global Link promotion ( new byte[] { 5 }, PBESpecies.Pikachu, 10, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Lightningrod }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.ThunderShock, PBEMove.TailWhip, PBEMove.ThunderWave, PBEMove.Headbutt } ) }) }, { PBESpecies.Piplup, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Pokémon Searcher BW promotion ( new byte[] { 5 }, PBESpecies.Piplup, 15, null, null, new PBEAbility[] { PBEAbility.Torrent }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.HydroPump, PBEMove.FeatherDance, PBEMove.WaterSport, PBEMove.Peck } ), new PBEDDEventPokemon // Dawn's Piplup ( new byte[] { 5 }, PBESpecies.Piplup, 15, false, null, new PBEAbility[] { PBEAbility.Torrent }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Sing, PBEMove.Round, PBEMove.FeatherDance, PBEMove.Peck } ), new PBEDDEventPokemon // Global Link promotion ( new byte[] { 5 }, PBESpecies.Piplup, 10, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Defiant }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Pound, PBEMove.Growl, PBEMove.Bubble, PBEMove.None } ), new PBEDDEventPokemon // Global Link promotion ( new byte[] { 5 }, PBESpecies.Piplup, 10, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Defiant }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Pound, PBEMove.Growl, PBEMove.Bubble, PBEMove.FeatherDance } ) }) }, { PBESpecies.Plusle, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // PokéPark Plusle ( new byte[] { 3 }, PBESpecies.Plusle, 5, null, null, new PBEAbility[] { PBEAbility.Plus }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Growl, PBEMove.ThunderWave, PBEMove.WaterSport, PBEMove.None } ), new PBEDDEventPokemon // Gather more Pokémon ( new byte[] { 3 }, PBESpecies.Plusle, 10, false, null, new PBEAbility[] { PBEAbility.Plus }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Growl, PBEMove.ThunderWave, PBEMove.QuickAttack, PBEMove.None } ) }) }, { PBESpecies.Politoed, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Yamamoto's Tournament ( new byte[] { 5 }, PBESpecies.Politoed, 50, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Drizzle }, new PBENature[] { PBENature.Calm }, new byte?[] { 31, 13, 31, 5, 31, 5 }, new PBEMove[] { PBEMove.Scald, PBEMove.IceBeam, PBEMove.PerishSong, PBEMove.Protect } ) }) }, { PBESpecies.Poliwag, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Egg Pokémon Present ( new byte[] { 3 }, PBESpecies.Poliwag, 5, null, null, new PBEAbility[] { PBEAbility.Damp, PBEAbility.WaterAbsorb }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Bubble, PBEMove.SweetKiss, PBEMove.None, PBEMove.None } ) }) }, { PBESpecies.Psyduck, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Pokémon Trade and Battle Day ( new byte[] { 3 }, PBESpecies.Psyduck, 27, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Damp }, new PBENature[] { PBENature.Lax }, new byte?[] { 31, 16, 12, 29, 31, 14 }, new PBEMove[] { PBEMove.TailWhip, PBEMove.Disable, PBEMove.Confusion, PBEMove.Screech } ), new PBEDDEventPokemon // PokéPark Psyduck ( new byte[] { 3 }, PBESpecies.Psyduck, 5, null, null, new PBEAbility[] { PBEAbility.CloudNine, PBEAbility.Damp }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.WaterSport, PBEMove.Scratch, PBEMove.TailWhip, PBEMove.MudSport } ), new PBEDDEventPokemon // GTS Psyducks ( new byte[] { 4 }, PBESpecies.Psyduck, 1, false, null, new PBEAbility[] { PBEAbility.CloudNine, PBEAbility.Damp }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Psychic, PBEMove.ConfuseRay, PBEMove.Yawn, PBEMove.MudBomb } ) }) }, { PBESpecies.Regirock, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Lucario and the Mystery of Mew promotion ( new byte[] { 3 }, PBESpecies.Regirock, 40, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.ClearBody }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Curse, PBEMove.Superpower, PBEMove.AncientPower, PBEMove.HyperBeam } ) }) }, { PBESpecies.Reshiram, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Victini movies promotion ( new byte[] { 5 }, PBESpecies.Reshiram, 100, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.Turboblaze }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.BlueFlare, PBEMove.FusionFlare, PBEMove.Mist, PBEMove.DracoMeteor } ) }) }, { PBESpecies.Riolu, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Pokémon Ranger ( new byte[] { 4 }, PBESpecies.Riolu, 40, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Steadfast }, new PBENature[] { PBENature.Serious }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.AuraSphere, PBEMove.ShadowClaw, PBEMove.BulletPunch, PBEMove.DrainPunch } ) }) }, { PBESpecies.Rotom, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Best Wishes Professor Oak's Rotom ( new byte[] { 5 }, PBESpecies.Rotom, 10, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.Levitate }, new PBENature[] { PBENature.Naughty }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Uproar, PBEMove.Astonish, PBEMove.Trick, PBEMove.ThunderShock } ) }) }, { PBESpecies.Samurott, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Pokémon Center 15th anniversary ( new byte[] { 5 }, PBESpecies.Samurott, 100, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Torrent }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.HydroPump, PBEMove.IceBeam, PBEMove.Megahorn, PBEMove.Superpower } ) }) }, { PBESpecies.Scrafty, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // World championships 2011 ( new byte[] { 5 }, PBESpecies.Scrafty, 50, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Moxie }, new PBENature[] { PBENature.Brave }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.FirePunch, PBEMove.Payback, PBEMove.DrainPunch, PBEMove.Substitute } ) }) }, { PBESpecies.Scraggy, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Ash's Scraggy ( new byte[] { 5 }, PBESpecies.Scraggy, 1, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Moxie }, new PBENature[] { PBENature.Adamant }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Leer, PBEMove.LowKick, PBEMove.Headbutt, PBEMove.HiJumpKick } ) }) }, { PBESpecies.Serperior, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Pokémon Center 15th anniversary ( new byte[] { 5 }, PBESpecies.Serperior, 100, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Torrent }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.LeafStorm, PBEMove.Substitute, PBEMove.GigaDrain, PBEMove.LeechSeed } ) }) }, { PBESpecies.Shedinja, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // PCNY Monster Week 1 ( new byte[] { 3 }, PBESpecies.Shedinja, 50, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.WonderGuard }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Spite, PBEMove.ConfuseRay, PBEMove.ShadowBall, PBEMove.Grudge } ) }) }, { PBESpecies.Shelmet, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Trade for Evolution! ( new byte[] { 5 }, PBESpecies.Shelmet, 30, false, null, new PBEAbility[] { PBEAbility.Hydration, PBEAbility.ShellArmor }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.StruggleBug, PBEMove.MegaDrain, PBEMove.Yawn, PBEMove.Protect } ), new PBEDDEventPokemon // Summer 2011 ( new byte[] { 5 }, PBESpecies.Shelmet, 50, false, null, new PBEAbility[] { PBEAbility.Hydration, PBEAbility.ShellArmor }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Encore, PBEMove.GigaDrain, PBEMove.BodySlam, PBEMove.BugBuzz } ) }) }, { PBESpecies.Skitty, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // PokéPark Skitty ( new byte[] { 3 }, PBESpecies.Skitty, 5, null, null, new PBEAbility[] { PBEAbility.CuteCharm }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Growl, PBEMove.Tackle, PBEMove.TailWhip, PBEMove.Rollout } ), new PBEDDEventPokemon // Gather more Pokémon ( new byte[] { 3 }, PBESpecies.Skitty, 10, false, null, new PBEAbility[] { PBEAbility.CuteCharm }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Growl, PBEMove.Tackle, PBEMove.TailWhip, PBEMove.Attract } ), new PBEDDEventPokemon // Pokémon Box bonus egg ( new byte[] { 3 }, PBESpecies.Skitty, 5, null, null, new PBEAbility[] { PBEAbility.CuteCharm }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Tackle, PBEMove.Growl, PBEMove.TailWhip, PBEMove.PayDay } ) }) }, { PBESpecies.Smeargle, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Gather more Pokémon ( new byte[] { 3 }, PBESpecies.Smeargle, 10, false, null, new PBEAbility[] { PBEAbility.OwnTempo }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Sketch, PBEMove.None, PBEMove.None, PBEMove.None } ), new PBEDDEventPokemon // World championships 2013 ( new byte[] { 5 }, PBESpecies.Smeargle, 50, false, PBEGender.Female, new PBEAbility[] { PBEAbility.Technician }, new PBENature[] { PBENature.Jolly }, new byte?[] { null, 31, null, null, null, 31 }, new PBEMove[] { PBEMove.FalseSwipe, PBEMove.Spore, PBEMove.OdorSleuth, PBEMove.MeanLook } ) }) }, { PBESpecies.Snivy, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Pokémon Center Tohoku Snivy ( new byte[] { 5 }, PBESpecies.Snivy, 5, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Overgrow }, new PBENature[] { PBENature.Hardy }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Growth, PBEMove.Synthesis, PBEMove.EnergyBall, PBEMove.Aromatherapy } ) }) }, { PBESpecies.Squirtle, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Gather more Pokémon ( new byte[] { 3 }, PBESpecies.Squirtle, 10, false, null, new PBEAbility[] { PBEAbility.Torrent }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Tackle, PBEMove.TailWhip, PBEMove.Bubble, PBEMove.Withdraw } ), new PBEDDEventPokemon // Kanto starter egg ( new byte[] { 5 }, PBESpecies.Squirtle, 1, null, null, new PBEAbility[] { PBEAbility.Torrent }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.FalseSwipe, PBEMove.Block, PBEMove.HydroCannon, PBEMove.FollowMe } ), new PBEDDEventPokemon // Global Link promotion ( new byte[] { 5 }, PBESpecies.Squirtle, 10, false, PBEGender.Male, new PBEAbility[] { PBEAbility.RainDish }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Tackle, PBEMove.TailWhip, PBEMove.Bubble, PBEMove.Withdraw } ) }) }, { PBESpecies.Thundurus, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Milos Island Pokémon ( new byte[] { 5 }, PBESpecies.Thundurus, 70, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Prankster }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Thunder, PBEMove.HammerArm, PBEMove.FocusBlast, PBEMove.WildCharge } ) }) }, { PBESpecies.Tirtouga, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Pokémon Adventure Camp fossil Pokémon ( new byte[] { 5 }, PBESpecies.Tirtouga, 15, false, null, new PBEAbility[] { PBEAbility.SolidRock, PBEAbility.Sturdy }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Bite, PBEMove.Protect, PBEMove.AquaJet, PBEMove.BodySlam } ) }) }, { PBESpecies.Torchic, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Gather more Pokémon ( new byte[] { 3 }, PBESpecies.Torchic, 10, false, null, new PBEAbility[] { PBEAbility.Blaze }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Scratch, PBEMove.Growl, PBEMove.FocusEnergy, PBEMove.Ember } ), new PBEDDEventPokemon // Global Link promotion ( new byte[] { 5 }, PBESpecies.Torchic, 10, false, PBEGender.Male, new PBEAbility[] { PBEAbility.SpeedBoost }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Scratch, PBEMove.Growl, PBEMove.FocusEnergy, PBEMove.Ember } ) }) }, { PBESpecies.Tornadus, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Milos Island Pokémon ( new byte[] { 5 }, PBESpecies.Tornadus, 70, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Prankster }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Hurricane, PBEMove.HammerArm, PBEMove.AirSlash, PBEMove.HiddenPower } ) }) }, { PBESpecies.Torterra, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Pokémon Center 15th anniversary ( new byte[] { 5 }, PBESpecies.Torterra, 100, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Overgrow }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.WoodHammer, PBEMove.Earthquake, PBEMove.Outrage, PBEMove.StoneEdge } ) }) }, { PBESpecies.Tropius, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // PCNY ( new byte[] { 3 }, PBESpecies.Tropius, 30, false, null, new PBEAbility[] { PBEAbility.Chlorophyll }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.RazorLeaf, PBEMove.Stomp, PBEMove.SweetScent, PBEMove.Whirlwind } ), new PBEDDEventPokemon // Pokémon Sunday promotion ( new byte[] { 4 }, PBESpecies.Tropius, 53, false, PBEGender.Female, new PBEAbility[] { PBEAbility.Chlorophyll }, new PBENature[] { PBENature.Jolly }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.AirSlash, PBEMove.Synthesis, PBEMove.SunnyDay, PBEMove.SolarBeam } ) }) }, { PBESpecies.Umbreon, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Journey across America | Party of the decade ( new byte[] { 3 }, PBESpecies.Umbreon, 70, false, null, new PBEAbility[] { PBEAbility.Synchronize }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.FaintAttack, PBEMove.MeanLook, PBEMove.Screech, PBEMove.Moonlight } ), new PBEDDEventPokemon // Global Link promotion ( new byte[] { 5 }, PBESpecies.Umbreon, 10, false, PBEGender.Male, new PBEAbility[] { PBEAbility.InnerFocus }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.TailWhip, PBEMove.Tackle, PBEMove.HelpingHand, PBEMove.SandAttack } ) }) }, { PBESpecies.Vaporeon, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Global Link promotion ( new byte[] { 5 }, PBESpecies.Vaporeon, 10, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Hydration }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.TailWhip, PBEMove.Tackle, PBEMove.HelpingHand, PBEMove.SandAttack } ) }) }, { PBESpecies.Victini, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // M14 promotion ( new byte[] { 5 }, PBESpecies.Victini, 50, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.VictoryStar }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.VCreate, PBEMove.FusionFlare, PBEMove.FusionBolt, PBEMove.SearingShot } ), new PBEDDEventPokemon // Pokémon Center Tohoku Victini ( new byte[] { 5 }, PBESpecies.Victini, 100, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.VictoryStar }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.VCreate, PBEMove.BlueFlare, PBEMove.BoltStrike, PBEMove.Glaciate } ) }) }, { PBESpecies.Volcarona, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Korean Nationals 2012 ( new byte[] { 5 }, PBESpecies.Volcarona, 100, true, PBEGender.Male, new PBEAbility[] { PBEAbility.FlameBody }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.QuiverDance, PBEMove.BugBuzz, PBEMove.FieryDance, PBEMove.MorningSun } ), new PBEDDEventPokemon // Alder's Volcarona ( new byte[] { 5 }, PBESpecies.Volcarona, 77, false, PBEGender.Male, new PBEAbility[] { PBEAbility.FlameBody }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.BugBuzz, PBEMove.Overheat, PBEMove.HyperBeam, PBEMove.QuiverDance } ) }) }, { PBESpecies.Vulpix, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Pokémon Trade and Battle Day ( new byte[] { 3 }, PBESpecies.Vulpix, 18, false, PBEGender.Female, new PBEAbility[] { PBEAbility.FlashFire }, new PBENature[] { PBENature.Quirky }, new byte?[] { 15, 6, 3, 25, 13, 22 }, new PBEMove[] { PBEMove.TailWhip, PBEMove.Roar, PBEMove.QuickAttack, PBEMove.WillOWisp } ) }) }, { PBESpecies.Whimsicott, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Powerful Tag ( new byte[] { 5 }, PBESpecies.Whimsicott, 50, false, PBEGender.Female, new PBEAbility[] { PBEAbility.Prankster }, new PBENature[] { PBENature.Timid }, new byte?[] { null, null, null, null, null, 31 }, new PBEMove[] { PBEMove.Swagger, PBEMove.GigaDrain, PBEMove.BeatUp, PBEMove.HelpingHand } ) }) }, { PBESpecies.Zekrom, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // Victini movies promotion ( new byte[] { 5 }, PBESpecies.Zekrom, 100, false, PBEGender.Genderless, new PBEAbility[] { PBEAbility.Teravolt }, PBEDataUtils.AllNatures, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.BoltStrike, PBEMove.FusionBolt, PBEMove.Haze, PBEMove.Outrage } ) }) }, { PBESpecies.Zoroark, new ReadOnlyCollection(new[] { new PBEDDEventPokemon // 2011 ( new byte[] { 5 }, PBESpecies.Zoroark, 50, false, PBEGender.Male, new PBEAbility[] { PBEAbility.Illusion }, new PBENature[] { PBENature.Quirky }, new byte?[] { null, null, null, null, null, null }, new PBEMove[] { PBEMove.Agility, PBEMove.Embargo, PBEMove.Punishment, PBEMove.Snarl } ) }) } }); } ================================================ FILE: PokemonBattleEngine.DefaultData/Data/ItemData.cs ================================================ using Kermalis.PokemonBattleEngine.Data; namespace Kermalis.PokemonBattleEngine.DefaultData.Data; public sealed partial class PBEDDItemData : IPBEItemData { public byte FlingPower { get; } private PBEDDItemData(byte flingPower = 0) { FlingPower = flingPower; } } ================================================ FILE: PokemonBattleEngine.DefaultData/Data/ItemData_Data.cs ================================================ using Kermalis.PokemonBattleEngine.Data; using System.Collections.Generic; using System.Collections.ObjectModel; namespace Kermalis.PokemonBattleEngine.DefaultData.Data; public sealed partial class PBEDDItemData { public static ReadOnlyDictionary Data { get; } = new(new Dictionary { { PBEItem.AbsorbBulb, new PBEDDItemData(30) }, { PBEItem.AdamantOrb, new PBEDDItemData(60) }, { PBEItem.AguavBerry, new PBEDDItemData(10) }, { PBEItem.AirBalloon, new PBEDDItemData(10) }, { PBEItem.AmuletCoin, new PBEDDItemData(30) }, { PBEItem.Antidote, new PBEDDItemData(30) }, { PBEItem.ApicotBerry, new PBEDDItemData(10) }, { PBEItem.ArmorFossil, new PBEDDItemData(100) }, { PBEItem.AspearBerry, new PBEDDItemData(10) }, { PBEItem.Awakening, new PBEDDItemData(30) }, { PBEItem.BabiriBerry, new PBEDDItemData(10) }, { PBEItem.BalmMushroom, new PBEDDItemData(30) }, { PBEItem.BelueBerry, new PBEDDItemData(10) }, { PBEItem.BerryJuice, new PBEDDItemData(30) }, { PBEItem.BigMushroom, new PBEDDItemData(30) }, { PBEItem.BigNugget, new PBEDDItemData(30) }, { PBEItem.BigPearl, new PBEDDItemData(30) }, { PBEItem.BigRoot, new PBEDDItemData(10) }, { PBEItem.BindingBand, new PBEDDItemData(30) }, { PBEItem.BlkApricorn, new PBEDDItemData() }, { PBEItem.BlackBelt, new PBEDDItemData(30) }, { PBEItem.BlackFlute, new PBEDDItemData(30) }, { PBEItem.BlackGlasses, new PBEDDItemData(30) }, { PBEItem.BlackSludge, new PBEDDItemData(30) }, { PBEItem.BluApricorn, new PBEDDItemData() }, { PBEItem.BlueFlute, new PBEDDItemData(30) }, { PBEItem.BlueScarf, new PBEDDItemData(10) }, { PBEItem.BlueShard, new PBEDDItemData(30) }, { PBEItem.BlukBerry, new PBEDDItemData(10) }, { PBEItem.BridgeMailD, new PBEDDItemData() }, { PBEItem.BridgeMailM, new PBEDDItemData() }, { PBEItem.BridgeMailS, new PBEDDItemData() }, { PBEItem.BridgeMailT, new PBEDDItemData() }, { PBEItem.BridgeMailV, new PBEDDItemData() }, { PBEItem.BrightPowder, new PBEDDItemData(10) }, { PBEItem.BugGem, new PBEDDItemData() }, { PBEItem.BurnDrive, new PBEDDItemData(70) }, { PBEItem.BurnHeal, new PBEDDItemData(30) }, { PBEItem.Calcium, new PBEDDItemData(30) }, { PBEItem.Carbos, new PBEDDItemData(30) }, { PBEItem.Casteliacone, new PBEDDItemData(30) }, { PBEItem.CellBattery, new PBEDDItemData(30) }, { PBEItem.Charcoal, new PBEDDItemData(30) }, { PBEItem.ChartiBerry, new PBEDDItemData(10) }, { PBEItem.CheriBerry, new PBEDDItemData(10) }, { PBEItem.CherishBall, new PBEDDItemData() }, { PBEItem.ChestoBerry, new PBEDDItemData(10) }, { PBEItem.ChilanBerry, new PBEDDItemData(10) }, { PBEItem.ChillDrive, new PBEDDItemData(70) }, { PBEItem.ChoiceBand, new PBEDDItemData(10) }, { PBEItem.ChoiceScarf, new PBEDDItemData(10) }, { PBEItem.ChoiceSpecs, new PBEDDItemData(10) }, { PBEItem.ChopleBerry, new PBEDDItemData(10) }, { PBEItem.ClawFossil, new PBEDDItemData(100) }, { PBEItem.CleanseTag, new PBEDDItemData(30) }, { PBEItem.CleverWing, new PBEDDItemData(20) }, { PBEItem.CobaBerry, new PBEDDItemData(10) }, { PBEItem.ColburBerry, new PBEDDItemData(10) }, { PBEItem.CometShard, new PBEDDItemData(30) }, { PBEItem.CornnBerry, new PBEDDItemData(10) }, { PBEItem.CoverFossil, new PBEDDItemData(100) }, { PBEItem.CustapBerry, new PBEDDItemData(10) }, { PBEItem.DampMulch, new PBEDDItemData(30) }, { PBEItem.DampRock, new PBEDDItemData(60) }, { PBEItem.DarkGem, new PBEDDItemData() }, { PBEItem.DawnStone, new PBEDDItemData(80) }, { PBEItem.DeepSeaScale, new PBEDDItemData(30) }, { PBEItem.DeepSeaTooth, new PBEDDItemData(90) }, { PBEItem.DestinyKnot, new PBEDDItemData(10) }, { PBEItem.DireHit, new PBEDDItemData(30) }, { PBEItem.DiveBall, new PBEDDItemData() }, { PBEItem.DomeFossil, new PBEDDItemData(100) }, { PBEItem.DouseDrive, new PBEDDItemData(70) }, { PBEItem.DracoPlate, new PBEDDItemData(90) }, { PBEItem.DragonFang, new PBEDDItemData(70) }, { PBEItem.DragonGem, new PBEDDItemData() }, { PBEItem.DragonScale, new PBEDDItemData(30) }, { PBEItem.DreadPlate, new PBEDDItemData(90) }, { PBEItem.DreamBall, new PBEDDItemData() }, { PBEItem.DubiousDisc, new PBEDDItemData(50) }, { PBEItem.DurinBerry, new PBEDDItemData(10) }, { PBEItem.DuskBall, new PBEDDItemData() }, { PBEItem.DuskStone, new PBEDDItemData(80) }, { PBEItem.EarthPlate, new PBEDDItemData(90) }, { PBEItem.EjectButton, new PBEDDItemData(30) }, { PBEItem.Electirizer, new PBEDDItemData(80) }, { PBEItem.ElectricGem, new PBEDDItemData() }, { PBEItem.Elixir, new PBEDDItemData(30) }, { PBEItem.EnergyPowder, new PBEDDItemData(30) }, { PBEItem.EnergyRoot, new PBEDDItemData(30) }, { PBEItem.EnigmaBerry, new PBEDDItemData(10) }, { PBEItem.EscapeRope, new PBEDDItemData(30) }, { PBEItem.Ether, new PBEDDItemData(30) }, { PBEItem.Everstone, new PBEDDItemData(30) }, { PBEItem.Eviolite, new PBEDDItemData(40) }, { PBEItem.ExpertBelt, new PBEDDItemData(10) }, { PBEItem.ExpShare, new PBEDDItemData(30) }, { PBEItem.FastBall, new PBEDDItemData() }, { PBEItem.FavoredMail, new PBEDDItemData() }, { PBEItem.FightingGem, new PBEDDItemData() }, { PBEItem.FigyBerry, new PBEDDItemData(10) }, { PBEItem.FireGem, new PBEDDItemData() }, { PBEItem.FireStone, new PBEDDItemData(30) }, { PBEItem.FistPlate, new PBEDDItemData(90) }, { PBEItem.FlameOrb, new PBEDDItemData(30) }, { PBEItem.FlamePlate, new PBEDDItemData(90) }, { PBEItem.FloatStone, new PBEDDItemData(30) }, { PBEItem.FluffyTail, new PBEDDItemData(30) }, { PBEItem.FlyingGem, new PBEDDItemData() }, { PBEItem.FocusBand, new PBEDDItemData(10) }, { PBEItem.FocusSash, new PBEDDItemData(10) }, { PBEItem.FreshWater, new PBEDDItemData(30) }, { PBEItem.FriendBall, new PBEDDItemData() }, { PBEItem.FullHeal, new PBEDDItemData(30) }, { PBEItem.FullIncense, new PBEDDItemData(10) }, { PBEItem.FullRestore, new PBEDDItemData(30) }, { PBEItem.GanlonBerry, new PBEDDItemData(10) }, { PBEItem.GeniusWing, new PBEDDItemData(20) }, { PBEItem.GhostGem, new PBEDDItemData() }, { PBEItem.GooeyMulch, new PBEDDItemData(30) }, { PBEItem.GrassGem, new PBEDDItemData() }, { PBEItem.GreatBall, new PBEDDItemData() }, { PBEItem.GrnApricorn, new PBEDDItemData() }, { PBEItem.GreenScarf, new PBEDDItemData(10) }, { PBEItem.GreenShard, new PBEDDItemData(30) }, { PBEItem.GreetMail, new PBEDDItemData() }, { PBEItem.GrepaBerry, new PBEDDItemData(10) }, { PBEItem.GripClaw, new PBEDDItemData(90) }, { PBEItem.GriseousOrb, new PBEDDItemData(60) }, { PBEItem.GroundGem, new PBEDDItemData() }, { PBEItem.GrowthMulch, new PBEDDItemData(30) }, { PBEItem.GuardSpec, new PBEDDItemData(30) }, { PBEItem.HabanBerry, new PBEDDItemData(10) }, { PBEItem.HardStone, new PBEDDItemData(100) }, { PBEItem.HealBall, new PBEDDItemData() }, { PBEItem.HealPowder, new PBEDDItemData(30) }, { PBEItem.HealthWing, new PBEDDItemData(20) }, { PBEItem.HeartScale, new PBEDDItemData(30) }, { PBEItem.HeatRock, new PBEDDItemData(60) }, { PBEItem.HeavyBall, new PBEDDItemData() }, { PBEItem.HelixFossil, new PBEDDItemData(100) }, { PBEItem.HondewBerry, new PBEDDItemData(10) }, { PBEItem.Honey, new PBEDDItemData(30) }, { PBEItem.HPUp, new PBEDDItemData(30) }, { PBEItem.HyperPotion, new PBEDDItemData(30) }, { PBEItem.IapapaBerry, new PBEDDItemData(10) }, { PBEItem.IceGem, new PBEDDItemData() }, { PBEItem.IceHeal, new PBEDDItemData(30) }, { PBEItem.IciclePlate, new PBEDDItemData(90) }, { PBEItem.IcyRock, new PBEDDItemData(40) }, { PBEItem.InquiryMail, new PBEDDItemData() }, { PBEItem.InsectPlate, new PBEDDItemData(90) }, { PBEItem.Iron, new PBEDDItemData(30) }, { PBEItem.IronBall, new PBEDDItemData(130) }, { PBEItem.IronPlate, new PBEDDItemData(90) }, { PBEItem.JabocaBerry, new PBEDDItemData(10) }, { PBEItem.KasibBerry, new PBEDDItemData(10) }, { PBEItem.KebiaBerry, new PBEDDItemData(10) }, { PBEItem.KelpsyBerry, new PBEDDItemData(10) }, { PBEItem.KingsRock, new PBEDDItemData(30) }, { PBEItem.LaggingTail, new PBEDDItemData(10) }, { PBEItem.LansatBerry, new PBEDDItemData(10) }, { PBEItem.LavaCookie, new PBEDDItemData(30) }, { PBEItem.LaxIncense, new PBEDDItemData(10) }, { PBEItem.LeafStone, new PBEDDItemData(30) }, { PBEItem.Leftovers, new PBEDDItemData(10) }, { PBEItem.Lemonade, new PBEDDItemData(30) }, { PBEItem.LeppaBerry, new PBEDDItemData(10) }, { PBEItem.LevelBall, new PBEDDItemData() }, { PBEItem.LiechiBerry, new PBEDDItemData(10) }, { PBEItem.LifeOrb, new PBEDDItemData(30) }, { PBEItem.LightBall, new PBEDDItemData(30) }, { PBEItem.LightClay, new PBEDDItemData(30) }, { PBEItem.LikeMail, new PBEDDItemData() }, { PBEItem.LoveBall, new PBEDDItemData() }, { PBEItem.LuckIncense, new PBEDDItemData(10) }, { PBEItem.LuckyEgg, new PBEDDItemData(30) }, { PBEItem.LuckyPunch, new PBEDDItemData(40) }, { PBEItem.LumBerry, new PBEDDItemData(10) }, { PBEItem.LureBall, new PBEDDItemData() }, { PBEItem.LustrousOrb, new PBEDDItemData(60) }, { PBEItem.LuxuryBall, new PBEDDItemData() }, { PBEItem.MachoBrace, new PBEDDItemData(60) }, { PBEItem.Magmarizer, new PBEDDItemData(80) }, { PBEItem.Magnet, new PBEDDItemData(30) }, { PBEItem.MagoBerry, new PBEDDItemData(10) }, { PBEItem.MagostBerry, new PBEDDItemData(10) }, { PBEItem.MasterBall, new PBEDDItemData() }, { PBEItem.MaxElixir, new PBEDDItemData(30) }, { PBEItem.MaxEther, new PBEDDItemData(30) }, { PBEItem.MaxPotion, new PBEDDItemData(30) }, { PBEItem.MaxRepel, new PBEDDItemData(30) }, { PBEItem.MaxRevive, new PBEDDItemData(30) }, { PBEItem.MeadowPlate, new PBEDDItemData(90) }, { PBEItem.MentalHerb, new PBEDDItemData(10) }, { PBEItem.MetalCoat, new PBEDDItemData(30) }, { PBEItem.MetalPowder, new PBEDDItemData(10) }, { PBEItem.Metronome, new PBEDDItemData(30) }, { PBEItem.MicleBerry, new PBEDDItemData(10) }, { PBEItem.MindPlate, new PBEDDItemData(90) }, { PBEItem.MiracleSeed, new PBEDDItemData(30) }, { PBEItem.MoomooMilk, new PBEDDItemData(30) }, { PBEItem.MoonBall, new PBEDDItemData() }, { PBEItem.MoonStone, new PBEDDItemData(30) }, { PBEItem.MuscleBand, new PBEDDItemData(10) }, { PBEItem.MuscleWing, new PBEDDItemData(20) }, { PBEItem.MysticWater, new PBEDDItemData(30) }, { PBEItem.NanabBerry, new PBEDDItemData(10) }, { PBEItem.NestBall, new PBEDDItemData() }, { PBEItem.NetBall, new PBEDDItemData() }, { PBEItem.NeverMeltIce, new PBEDDItemData(30) }, { PBEItem.NomelBerry, new PBEDDItemData(10) }, { PBEItem.NormalGem, new PBEDDItemData() }, { PBEItem.Nugget, new PBEDDItemData(30) }, { PBEItem.OccaBerry, new PBEDDItemData(10) }, { PBEItem.OddIncense, new PBEDDItemData(10) }, { PBEItem.OddKeystone, new PBEDDItemData(80) }, { PBEItem.OldAmber, new PBEDDItemData(100) }, { PBEItem.OldGateau, new PBEDDItemData(30) }, { PBEItem.OranBerry, new PBEDDItemData(10) }, { PBEItem.OvalStone, new PBEDDItemData(80) }, { PBEItem.PamtreBerry, new PBEDDItemData(10) }, { PBEItem.ParalyzHeal, new PBEDDItemData(30) }, { PBEItem.ParkBall, new PBEDDItemData() }, { PBEItem.PasshoBerry, new PBEDDItemData(10) }, { PBEItem.PassOrb, new PBEDDItemData(30) }, { PBEItem.PayapaBerry, new PBEDDItemData(10) }, { PBEItem.Pearl, new PBEDDItemData(30) }, { PBEItem.PearlString, new PBEDDItemData(30) }, { PBEItem.PechaBerry, new PBEDDItemData(10) }, { PBEItem.PersimBerry, new PBEDDItemData(10) }, { PBEItem.PetayaBerry, new PBEDDItemData(10) }, { PBEItem.PinapBerry, new PBEDDItemData(10) }, { PBEItem.PnkApricorn, new PBEDDItemData() }, { PBEItem.PinkScarf, new PBEDDItemData(10) }, { PBEItem.PlumeFossil, new PBEDDItemData(100) }, { PBEItem.PoisonBarb, new PBEDDItemData(70) }, { PBEItem.PoisonGem, new PBEDDItemData() }, { PBEItem.PokeBall, new PBEDDItemData() }, { PBEItem.PokeDoll, new PBEDDItemData(30) }, { PBEItem.PokeToy, new PBEDDItemData(30) }, { PBEItem.PomegBerry, new PBEDDItemData(10) }, { PBEItem.Potion, new PBEDDItemData(30) }, { PBEItem.PowerAnklet, new PBEDDItemData(70) }, { PBEItem.PowerBand, new PBEDDItemData(70) }, { PBEItem.PowerBelt, new PBEDDItemData(70) }, { PBEItem.PowerBracer, new PBEDDItemData(70) }, { PBEItem.PowerHerb, new PBEDDItemData(10) }, { PBEItem.PowerLens, new PBEDDItemData(70) }, { PBEItem.PowerWeight, new PBEDDItemData(70) }, { PBEItem.PPMax, new PBEDDItemData(30) }, { PBEItem.PPUp, new PBEDDItemData(30) }, { PBEItem.PremierBall, new PBEDDItemData() }, { PBEItem.PrettyWing, new PBEDDItemData(30) }, { PBEItem.PrismScale, new PBEDDItemData(30) }, { PBEItem.Protector, new PBEDDItemData(80) }, { PBEItem.Protein, new PBEDDItemData(30) }, { PBEItem.PsychicGem, new PBEDDItemData() }, { PBEItem.PureIncense, new PBEDDItemData(10) }, { PBEItem.QualotBerry, new PBEDDItemData(10) }, { PBEItem.QuickBall, new PBEDDItemData() }, { PBEItem.QuickClaw, new PBEDDItemData(80) }, { PBEItem.QuickPowder, new PBEDDItemData(10) }, { PBEItem.RabutaBerry, new PBEDDItemData(10) }, { PBEItem.RageCandyBar, new PBEDDItemData(30) }, { PBEItem.RareBone, new PBEDDItemData(100) }, { PBEItem.RareCandy, new PBEDDItemData(30) }, { PBEItem.RawstBerry, new PBEDDItemData(10) }, { PBEItem.RazorClaw, new PBEDDItemData(80) }, { PBEItem.RazorFang, new PBEDDItemData(30) }, { PBEItem.RazzBerry, new PBEDDItemData(10) }, { PBEItem.ReaperCloth, new PBEDDItemData(10) }, { PBEItem.RedApricorn, new PBEDDItemData() }, { PBEItem.RedCard, new PBEDDItemData(10) }, { PBEItem.RedFlute, new PBEDDItemData(30) }, { PBEItem.RedScarf, new PBEDDItemData(10) }, { PBEItem.RedShard, new PBEDDItemData(30) }, { PBEItem.RelicBand, new PBEDDItemData(30) }, { PBEItem.RelicCopper, new PBEDDItemData(30) }, { PBEItem.RelicCrown, new PBEDDItemData(30) }, { PBEItem.RelicGold, new PBEDDItemData(30) }, { PBEItem.RelicSilver, new PBEDDItemData(30) }, { PBEItem.RelicStatue, new PBEDDItemData(30) }, { PBEItem.RelicVase, new PBEDDItemData(30) }, { PBEItem.RepeatBall, new PBEDDItemData() }, { PBEItem.Repel, new PBEDDItemData(30) }, { PBEItem.ReplyMail, new PBEDDItemData() }, { PBEItem.ResistWing, new PBEDDItemData(20) }, { PBEItem.RevivalHerb, new PBEDDItemData(30) }, { PBEItem.Revive, new PBEDDItemData(30) }, { PBEItem.RindoBerry, new PBEDDItemData(10) }, { PBEItem.RingTarget, new PBEDDItemData(10) }, { PBEItem.RockGem, new PBEDDItemData() }, { PBEItem.RockIncense, new PBEDDItemData(10) }, { PBEItem.RockyHelmet, new PBEDDItemData(60) }, { PBEItem.RootFossil, new PBEDDItemData(100) }, { PBEItem.RoseIncense, new PBEDDItemData(10) }, { PBEItem.RowapBerry, new PBEDDItemData(10) }, { PBEItem.RSVPMail, new PBEDDItemData() }, { PBEItem.SacredAsh, new PBEDDItemData(30) }, { PBEItem.SafariBall, new PBEDDItemData() }, { PBEItem.SalacBerry, new PBEDDItemData(10) }, { PBEItem.ScopeLens, new PBEDDItemData(30) }, { PBEItem.SeaIncense, new PBEDDItemData(10) }, { PBEItem.SharpBeak, new PBEDDItemData(50) }, { PBEItem.ShedShell, new PBEDDItemData(10) }, { PBEItem.ShellBell, new PBEDDItemData(30) }, { PBEItem.ShinyStone, new PBEDDItemData(80) }, { PBEItem.ShoalSalt, new PBEDDItemData(30) }, { PBEItem.ShoalShell, new PBEDDItemData(30) }, { PBEItem.ShockDrive, new PBEDDItemData(70) }, { PBEItem.ShucaBerry, new PBEDDItemData(10) }, { PBEItem.SilkScarf, new PBEDDItemData(10) }, { PBEItem.SilverPowder, new PBEDDItemData(10) }, { PBEItem.SitrusBerry, new PBEDDItemData(10) }, { PBEItem.SkullFossil, new PBEDDItemData(100) }, { PBEItem.SkyPlate, new PBEDDItemData(90) }, { PBEItem.SmokeBall, new PBEDDItemData(30) }, { PBEItem.SmoothRock, new PBEDDItemData(10) }, { PBEItem.SodaPop, new PBEDDItemData(30) }, { PBEItem.SoftSand, new PBEDDItemData(10) }, { PBEItem.SootheBell, new PBEDDItemData(10) }, { PBEItem.SoulDew, new PBEDDItemData(30) }, { PBEItem.SpellTag, new PBEDDItemData(30) }, { PBEItem.SpelonBerry, new PBEDDItemData(10) }, { PBEItem.SplashPlate, new PBEDDItemData(90) }, { PBEItem.SpookyPlate, new PBEDDItemData(90) }, { PBEItem.SportBall, new PBEDDItemData() }, { PBEItem.StableMulch, new PBEDDItemData(30) }, { PBEItem.Stardust, new PBEDDItemData(30) }, { PBEItem.StarfBerry, new PBEDDItemData(10) }, { PBEItem.StarPiece, new PBEDDItemData(30) }, { PBEItem.SteelGem, new PBEDDItemData() }, { PBEItem.Stick, new PBEDDItemData(60) }, { PBEItem.StickyBarb, new PBEDDItemData(80) }, { PBEItem.StonePlate, new PBEDDItemData(90) }, { PBEItem.SunStone, new PBEDDItemData(30) }, { PBEItem.SuperPotion, new PBEDDItemData(30) }, { PBEItem.SuperRepel, new PBEDDItemData(30) }, { PBEItem.SweetHeart, new PBEDDItemData(30) }, { PBEItem.SwiftWing, new PBEDDItemData(20) }, { PBEItem.TamatoBerry, new PBEDDItemData(10) }, { PBEItem.TangaBerry, new PBEDDItemData(10) }, { PBEItem.ThanksMail, new PBEDDItemData() }, { PBEItem.ThickClub, new PBEDDItemData(90) }, { PBEItem.Thunderstone, new PBEDDItemData(30) }, { PBEItem.TimerBall, new PBEDDItemData() }, { PBEItem.TinyMushroom, new PBEDDItemData(30) }, { PBEItem.ToxicOrb, new PBEDDItemData(30) }, { PBEItem.ToxicPlate, new PBEDDItemData(90) }, { PBEItem.TwistedSpoon, new PBEDDItemData(30) }, { PBEItem.UltraBall, new PBEDDItemData() }, { PBEItem.UpGrade, new PBEDDItemData(30) }, { PBEItem.WacanBerry, new PBEDDItemData(10) }, { PBEItem.WaterGem, new PBEDDItemData() }, { PBEItem.WaterStone, new PBEDDItemData(30) }, { PBEItem.WatmelBerry, new PBEDDItemData(10) }, { PBEItem.WaveIncense, new PBEDDItemData(10) }, { PBEItem.WepearBerry, new PBEDDItemData(10) }, { PBEItem.WhtApricorn, new PBEDDItemData() }, { PBEItem.WhiteFlute, new PBEDDItemData(30) }, { PBEItem.WhiteHerb, new PBEDDItemData(10) }, { PBEItem.WideLens, new PBEDDItemData(10) }, { PBEItem.WikiBerry, new PBEDDItemData(10) }, { PBEItem.WiseGlasses, new PBEDDItemData(10) }, { PBEItem.XAccuracy, new PBEDDItemData(30) }, { PBEItem.XAttack, new PBEDDItemData(30) }, { PBEItem.XDefend, new PBEDDItemData(30) }, { PBEItem.XSpDef, new PBEDDItemData(30) }, { PBEItem.XSpecial, new PBEDDItemData(30) }, { PBEItem.XSpeed, new PBEDDItemData(30) }, { PBEItem.YacheBerry, new PBEDDItemData(10) }, { PBEItem.YlwApricorn, new PBEDDItemData() }, { PBEItem.YellowFlute, new PBEDDItemData(30) }, { PBEItem.YellowScarf, new PBEDDItemData(10) }, { PBEItem.YellowShard, new PBEDDItemData(30) }, { PBEItem.ZapPlate, new PBEDDItemData(90) }, { PBEItem.Zinc, new PBEDDItemData(30) }, { PBEItem.ZoomLens, new PBEDDItemData(10) } }); } ================================================ FILE: PokemonBattleEngine.DefaultData/Data/LegalityChecker.cs ================================================ using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngine.Data.Utils; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; namespace Kermalis.PokemonBattleEngine.DefaultData.Data; public static class PBEDDLegalityChecker { private static List<(PBESpecies, PBEForm)> GetSpecies(PBESpecies species, PBEForm form) { // Recursion BAYBEE // IDK what to name these functions so enjoy Add1 and Add2 var list = new List<(PBESpecies, PBEForm)>(); void Add1(PBESpecies s, PBEForm f) { // Do not take forms if unable to change into them (Wormadam) if (PBEDataUtils.CanChangeForm(s, true)) { foreach (PBEForm cf in PBEDataUtils.GetForms(s, true)) { Add2(s, cf); } } else { Add2(s, f); } } void Add2(PBESpecies s, PBEForm f) { foreach ((PBESpecies cs, PBEForm cf) in PBEDefaultDataProvider.Instance.GetPokemonDataExtended(s, f).PreEvolutions) { Add1(cs, cf); } list.Add((s, f)); } Add1(species, form); return list; } public static IReadOnlyCollection GetLegalMoves(PBESpecies species, PBEForm form, byte level) { List<(PBESpecies, PBEForm)> speciesToStealFrom = GetSpecies(species, form); var moves = new List(); foreach ((PBESpecies spe, PBEForm fo) in speciesToStealFrom) { IPBEDDPokemonDataExtended pData = PBEDefaultDataProvider.Instance.GetPokemonDataExtended(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 != PBEDDMoveObtainMethod.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 (PBEDDEventPokemon.Events.TryGetValue(spe, out ReadOnlyCollection? 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.FindIndex(m => PBEDataProvider.Instance.GetMoveData(m, cache: false).Effect == PBEMoveEffect.Sketch) != -1) { return PBEDataUtils.SketchLegalMoves; } } return moves.Distinct().Where(m => PBEDataUtils.IsMoveUsable(m)).ToArray(); } } ================================================ FILE: PokemonBattleEngine.DefaultData/Data/MoveData.cs ================================================ using Kermalis.PokemonBattleEngine.Data; using System; using System.Text; namespace Kermalis.PokemonBattleEngine.DefaultData.Data; public sealed partial class PBEDDMoveData : IPBEMoveData { public PBEType Type { get; } public PBEMoveCategory Category { get; } public sbyte Priority { get; } public byte PPTier { get; } public byte Power { get; } public byte Accuracy { get; } public PBEMoveEffect Effect { get; } public int EffectParam { get; } public PBEMoveTarget Targets { get; } public PBEMoveFlag Flags { get; } private PBEDDMoveData(PBEType type, PBEMoveCategory category, sbyte priority, byte ppTier, byte power, byte accuracy, PBEMoveEffect effect, int effectParam, PBEMoveTarget targets, PBEMoveFlag flags) { Type = type; Category = category; Priority = priority; PPTier = ppTier; Power = power; Accuracy = accuracy; Effect = effect; EffectParam = effectParam; Targets = targets; Flags = flags; } public override string ToString() { var sb = new StringBuilder(); sb.AppendLine($"Type: {Type}"); sb.AppendLine($"Category: {Category}"); sb.AppendLine($"Priority: {Priority}"); sb.AppendLine($"PP: {Math.Max(1, PPTier * PBESettings.DefaultPPMultiplier)}"); sb.AppendLine($"Power: {(Power == 0 ? "―" : Power.ToString())}"); sb.AppendLine($"Accuracy: {(Accuracy == 0 ? "―" : Accuracy.ToString())}"); sb.AppendLine($"Effect: {Effect}"); sb.AppendLine($"Effect Parameter: {EffectParam}"); sb.AppendLine($"Targets: {Targets}"); sb.Append($"Flags: {Flags}"); return sb.ToString(); } } ================================================ FILE: PokemonBattleEngine.DefaultData/Data/MoveData_Data.cs ================================================ using Kermalis.PokemonBattleEngine.Data; using System.Collections.Generic; using System.Collections.ObjectModel; namespace Kermalis.PokemonBattleEngine.DefaultData.Data; public sealed partial class PBEDDMoveData { public static ReadOnlyDictionary Data { get; } = new(new Dictionary { { PBEMove.Absorb, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Special, 0, 5, 20, 100, PBEMoveEffect.HPDrain, 50, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Acid, new PBEDDMoveData ( PBEType.Poison, PBEMoveCategory.Special, 0, 6, 40, 100, PBEMoveEffect.Hit__MaybeLowerTarget_SPDEF_By1, 10, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.AcidArmor, new PBEDDMoveData ( PBEType.Poison, PBEMoveCategory.Status, 0, 8, 0, 0, PBEMoveEffect.ChangeTarget_DEF, +2, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.AcidSpray, new PBEDDMoveData ( PBEType.Poison, PBEMoveCategory.Special, 0, 4, 40, 100, PBEMoveEffect.Hit__MaybeLowerTarget_SPDEF_By2, 100, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Acrobatics, new PBEDDMoveData ( PBEType.Flying, PBEMoveCategory.Physical, 0, 3, 55, 100, PBEMoveEffect.Acrobatics, 0, PBEMoveTarget.SingleNotSelf, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Acupressure, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 6, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleAllySurrounding, PBEMoveFlag.None ) }, { PBEMove.AerialAce, new PBEDDMoveData ( PBEType.Flying, PBEMoveCategory.Physical, 0, 4, 60, 0, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleNotSelf, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Aeroblast, new PBEDDMoveData ( PBEType.Flying, PBEMoveCategory.Special, 0, 1, 100, 95, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleNotSelf, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.HighCritChance ) }, { PBEMove.AfterYou, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 3, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.BlockedFromMetronome ) }, { PBEMove.Agility, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 6, 0, 0, PBEMoveEffect.ChangeTarget_SPE, +2, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.AirCutter, new PBEDDMoveData ( PBEType.Flying, PBEMoveCategory.Special, 0, 5, 55, 95, PBEMoveEffect.Hit, 0, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.HighCritChance ) }, { PBEMove.AirSlash, new PBEDDMoveData ( PBEType.Flying, PBEMoveCategory.Special, 0, 4, 75, 95, PBEMoveEffect.Hit__MaybeFlinch, 30, PBEMoveTarget.SingleNotSelf, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.AllySwitch, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, +1, 3, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.None ) }, { PBEMove.Amnesia, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.ChangeTarget_SPDEF, +2, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.AncientPower, new PBEDDMoveData ( PBEType.Rock, PBEMoveCategory.Special, 0, 1, 60, 100, PBEMoveEffect.Hit__MaybeRaiseUser_ATK_DEF_SPATK_SPDEF_SPE_By1, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.AquaJet, new PBEDDMoveData ( PBEType.Water, PBEMoveCategory.Physical, +1, 4, 40, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.AquaRing, new PBEDDMoveData ( PBEType.Water, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.AquaTail, new PBEDDMoveData ( PBEType.Water, PBEMoveCategory.Physical, 0, 2, 90, 90, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.ArmThrust, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, 0, 4, 15, 100, PBEMoveEffect.Hit__2To5Times, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Aromatherapy, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Status, 0, 1, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.AllTeam, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.Assist, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.Self, PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMetronome | PBEMoveFlag.BlockedFromSleepTalk ) }, { PBEMove.Assurance, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Physical, 0, 2, 50, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Astonish, new PBEDDMoveData ( PBEType.Ghost, PBEMoveCategory.Physical, 0, 3, 30, 100, PBEMoveEffect.Hit__MaybeFlinch, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.AttackOrder, new PBEDDMoveData ( PBEType.Bug, PBEMoveCategory.Physical, 0, 3, 90, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.HighCritChance ) }, { PBEMove.Attract, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 3, 0, 100, PBEMoveEffect.Attract, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.AuraSphere, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Special, 0, 4, 90, 0, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleNotSelf, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.AuroraBeam, new PBEDDMoveData ( PBEType.Ice, PBEMoveCategory.Special, 0, 4, 65, 100, PBEMoveEffect.Hit__MaybeLowerTarget_ATK_By1, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Autotomize, new PBEDDMoveData ( PBEType.Steel, PBEMoveCategory.Status, 0, 3, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.Avalanche, new PBEDDMoveData ( PBEType.Ice, PBEMoveCategory.Physical, -4, 2, 60, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Barrage, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 4, 15, 85, PBEMoveEffect.Hit__2To5Times, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Barrier, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 6, 0, 0, PBEMoveEffect.ChangeTarget_DEF, +2, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.BatonPass, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 8, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.Self, PBEMoveFlag.None ) }, { PBEMove.BeatUp, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Physical, 0, 2, 0, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.BellyDrum, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.BellyDrum, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.Bestow, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 3, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMetronome ) }, { PBEMove.Bide, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, +1, 2, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedFromSleepTalk | PBEMoveFlag.MakesContact ) }, { PBEMove.Bind, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 4, 15, 85, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Bite, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Physical, 0, 5, 60, 100, PBEMoveEffect.Hit__MaybeFlinch, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.BlastBurn, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Special, 0, 1, 150, 90, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.BlazeKick, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Physical, 0, 2, 85, 90, PBEMoveEffect.Hit__MaybeBurn, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.HighCritChance | PBEMoveFlag.MakesContact ) }, { PBEMove.Blizzard, new PBEDDMoveData ( PBEType.Ice, PBEMoveCategory.Special, 0, 1, 120, 70, PBEMoveEffect.Hit__MaybeFreeze, 10, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.NeverMissHail ) }, { PBEMove.Block, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 1, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove ) }, { PBEMove.BlueFlare, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Special, 0, 1, 130, 85, PBEMoveEffect.Hit__MaybeBurn, 20, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.BrickBreak, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, 0, 3, 75, 100, PBEMoveEffect.BrickBreak, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Brine, new PBEDDMoveData ( PBEType.Water, PBEMoveCategory.Special, 0, 2, 65, 100, PBEMoveEffect.Brine, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.BodySlam, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 3, 85, 100, PBEMoveEffect.Hit__MaybeParalyze, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.BoltStrike, new PBEDDMoveData ( PBEType.Electric, PBEMoveCategory.Physical, 0, 1, 130, 85, PBEMoveEffect.Hit__MaybeParalyze, 20, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.BoneClub, new PBEDDMoveData ( PBEType.Ground, PBEMoveCategory.Physical, 0, 4, 65, 85, PBEMoveEffect.Hit__MaybeFlinch, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Bonemerang, new PBEDDMoveData ( PBEType.Ground, PBEMoveCategory.Physical, 0, 2, 50, 90, PBEMoveEffect.Hit__2Times, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.BoneRush, new PBEDDMoveData ( PBEType.Ground, PBEMoveCategory.Physical, 0, 2, 25, 90, PBEMoveEffect.Hit__2To5Times, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Bounce, new PBEDDMoveData ( PBEType.Flying, PBEMoveCategory.Physical, 0, 1, 85, 85, PBEMoveEffect.Bounce, 30, PBEMoveTarget.SingleNotSelf, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedByGravity | PBEMoveFlag.BlockedFromSleepTalk | PBEMoveFlag.MakesContact ) }, { PBEMove.BraveBird, new PBEDDMoveData ( PBEType.Flying, PBEMoveCategory.Physical, 0, 3, 120, 100, PBEMoveEffect.Recoil, 3, PBEMoveTarget.SingleNotSelf, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedByReckless | PBEMoveFlag.MakesContact ) }, { PBEMove.Bubble, new PBEDDMoveData ( PBEType.Water, PBEMoveCategory.Special, 0, 6, 20, 100, PBEMoveEffect.Hit__MaybeLowerTarget_SPE_By1, 10, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.BubbleBeam, new PBEDDMoveData ( PBEType.Water, PBEMoveCategory.Special, 0, 4, 65, 100, PBEMoveEffect.Hit__MaybeLowerTarget_SPE_By1, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.BugBite, new PBEDDMoveData ( PBEType.Bug, PBEMoveCategory.Physical, 0, 4, 60, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.BugBuzz, new PBEDDMoveData ( PBEType.Bug, PBEMoveCategory.Special, 0, 2, 90, 100, PBEMoveEffect.Hit__MaybeLowerTarget_SPDEF_By1, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedBySoundproof ) }, { PBEMove.BulkUp, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.RaiseTarget_ATK_DEF_By1, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.Bulldoze, new PBEDDMoveData ( PBEType.Ground, PBEMoveCategory.Physical, 0, 4, 60, 100, PBEMoveEffect.Hit__MaybeLowerTarget_SPE_By1, 100, PBEMoveTarget.AllSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.BulletPunch, new PBEDDMoveData ( PBEType.Steel, PBEMoveCategory.Physical, +1, 6, 40, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByIronFist | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.BulletSeed, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Physical, 0, 6, 25, 100, PBEMoveEffect.Hit__2To5Times, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.CalmMind, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.RaiseTarget_SPATK_SPDEF_By1, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.Camouflage, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.Camouflage, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.Captivate, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 4, 0, 100, PBEMoveEffect.ChangeTarget_SPATK__IfAttractionPossible, -2, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Charge, new PBEDDMoveData ( PBEType.Electric, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.ChargeBeam, new PBEDDMoveData ( PBEType.Electric, PBEMoveCategory.Special, 0, 2, 50, 90, PBEMoveEffect.Hit__MaybeRaiseUser_SPATK_By1, 70, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Charm, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.ChangeTarget_ATK, -2, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Chatter, new PBEDDMoveData ( PBEType.Flying, PBEMoveCategory.Special, 0, 4, 60, 100, PBEMoveEffect.Hit__MaybeConfuse, 0, PBEMoveTarget.SingleNotSelf, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedBySoundproof | PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMeFirst | PBEMoveFlag.BlockedFromMetronome | PBEMoveFlag.BlockedFromSketch | PBEMoveFlag.BlockedFromMimic ) }, { PBEMove.ChipAway, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 4, 70, 100, PBEMoveEffect.ChipAway, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.CircleThrow, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, -6, 2, 60, 90, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.MakesContact ) }, { PBEMove.Clamp, new PBEDDMoveData ( PBEType.Water, PBEMoveCategory.Physical, 0, 3, 35, 85, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.ClearSmog, new PBEDDMoveData ( PBEType.Poison, PBEMoveCategory.Special, 0, 3, 50, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.CloseCombat, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, 0, 1, 120, 100, PBEMoveEffect.Hit__MaybeLowerUser_DEF_SPDEF_By1, 100, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Coil, new PBEDDMoveData ( PBEType.Poison, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.RaiseTarget_ATK_DEF_ACC_By1, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.CometPunch, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 3, 18, 85, PBEMoveEffect.Hit__2To5Times, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByIronFist | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.ConfuseRay, new PBEDDMoveData ( PBEType.Ghost, PBEMoveCategory.Status, 0, 2, 0, 100, PBEMoveEffect.Confuse, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Confusion, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Special, 0, 5, 50, 100, PBEMoveEffect.Hit__MaybeConfuse, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Constrict, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 7, 10, 100, PBEMoveEffect.Hit__MaybeLowerTarget_SPE_By1, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Conversion, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 6, 0, 0, PBEMoveEffect.Conversion, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.Conversion2, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 6, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.None ) }, { PBEMove.Copycat, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.Self, PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMetronome | PBEMoveFlag.BlockedFromSleepTalk ) }, { PBEMove.CosmicPower, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.RaiseTarget_DEF_SPDEF_By1, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.CottonGuard, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.ChangeTarget_DEF, +3, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.CottonSpore, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Status, 0, 8, 0, 100, PBEMoveEffect.ChangeTarget_SPE, -2, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Counter, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, -5, 4, 0, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMeFirst | PBEMoveFlag.BlockedFromMetronome | PBEMoveFlag.MakesContact ) }, { PBEMove.Covet, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 8, 60, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMeFirst | PBEMoveFlag.BlockedFromMetronome | PBEMoveFlag.MakesContact ) }, { PBEMove.Crabhammer, new PBEDDMoveData ( PBEType.Water, PBEMoveCategory.Physical, 0, 2, 90, 90, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.HighCritChance | PBEMoveFlag.MakesContact ) }, { PBEMove.CrossChop, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, 0, 1, 100, 80, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.HighCritChance | PBEMoveFlag.MakesContact ) }, { PBEMove.CrossPoison, new PBEDDMoveData ( PBEType.Poison, PBEMoveCategory.Physical, 0, 4, 70, 100, PBEMoveEffect.Hit__MaybePoison, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.HighCritChance | PBEMoveFlag.MakesContact ) }, { PBEMove.Crunch, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Physical, 0, 3, 80, 100, PBEMoveEffect.Hit__MaybeLowerTarget_DEF_By1, 20, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.CrushClaw, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 2, 75, 95, PBEMoveEffect.Hit__MaybeLowerTarget_DEF_By1, 50, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.CrushGrip, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 1, 120, 100, PBEMoveEffect.CrushGrip, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Curse, new PBEDDMoveData ( PBEType.Ghost, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.Curse, 0, PBEMoveTarget.Varies, PBEMoveFlag.None ) }, { PBEMove.Cut, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 6, 50, 95, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.DarkPulse, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Special, 0, 3, 80, 100, PBEMoveEffect.Hit__MaybeFlinch, 20, PBEMoveTarget.SingleNotSelf, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.DarkVoid, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Status, 0, 2, 0, 80, PBEMoveEffect.Sleep, 0, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.DefendOrder, new PBEDDMoveData ( PBEType.Bug, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.RaiseTarget_DEF_SPDEF_By1, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.DefenseCurl, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 8, 0, 0, PBEMoveEffect.ChangeTarget_DEF, +1, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.Defog, new PBEDDMoveData ( PBEType.Flying, PBEMoveCategory.Status, 0, 3, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.DestinyBond, new PBEDDMoveData ( PBEType.Ghost, PBEMoveCategory.Status, 0, 1, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.Self, PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMetronome ) }, { PBEMove.Detect, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Status, +4, 1, 0, 0, PBEMoveEffect.Protect, 0, PBEMoveTarget.Self, PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMetronome ) }, { PBEMove.Dig, new PBEDDMoveData ( PBEType.Ground, PBEMoveCategory.Physical, 0, 2, 80, 100, PBEMoveEffect.Dig, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedFromSleepTalk | PBEMoveFlag.MakesContact ) }, { PBEMove.Disable, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 4, 0, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Discharge, new PBEDDMoveData ( PBEType.Electric, PBEMoveCategory.Special, 0, 3, 80, 100, PBEMoveEffect.Hit__MaybeParalyze, 30, PBEMoveTarget.AllSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Dive, new PBEDDMoveData ( PBEType.Water, PBEMoveCategory.Physical, 0, 2, 80, 100, PBEMoveEffect.Dive, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedFromSleepTalk | PBEMoveFlag.MakesContact ) }, { PBEMove.DizzyPunch, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 2, 70, 100, PBEMoveEffect.Hit__MaybeConfuse, 20, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByIronFist | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.DoomDesire, new PBEDDMoveData ( PBEType.Steel, PBEMoveCategory.Special, 0, 1, 140, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.DoubleEdge, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 3, 120, 100, PBEMoveEffect.Recoil, 3, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedByReckless | PBEMoveFlag.MakesContact ) }, { PBEMove.DoubleHit, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 2, 35, 90, PBEMoveEffect.Hit__2Times, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.DoubleKick, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, 0, 6, 30, 100, PBEMoveEffect.Hit__2Times, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.DoubleSlap, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 2, 15, 85, PBEMoveEffect.Hit__2To5Times, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.DoubleTeam, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 3, 0, 0, PBEMoveEffect.ChangeTarget_EVA, +1, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.DracoMeteor, new PBEDDMoveData ( PBEType.Dragon, PBEMoveCategory.Special, 0, 1, 140, 90, PBEMoveEffect.Hit__MaybeLowerUser_SPATK_By2, 100, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.DragonBreath, new PBEDDMoveData ( PBEType.Dragon, PBEMoveCategory.Special, 0, 4, 60, 100, PBEMoveEffect.Hit__MaybeParalyze, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.DragonClaw, new PBEDDMoveData ( PBEType.Dragon, PBEMoveCategory.Physical, 0, 3, 80, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.DragonDance, new PBEDDMoveData ( PBEType.Dragon, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.RaiseTarget_ATK_SPE_By1, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.DragonPulse, new PBEDDMoveData ( PBEType.Dragon, PBEMoveCategory.Special, 0, 2, 90, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleNotSelf, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.DragonRage, new PBEDDMoveData ( PBEType.Dragon, PBEMoveCategory.Special, 0, 2, 0, 100, PBEMoveEffect.SetDamage, 40, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.DragonRush, new PBEDDMoveData ( PBEType.Dragon, PBEMoveCategory.Physical, 0, 2, 100, 75, PBEMoveEffect.Hit__MaybeFlinch, 20, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.DragonTail, new PBEDDMoveData ( PBEType.Dragon, PBEMoveCategory.Physical, -6, 2, 60, 90, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.MakesContact ) }, { PBEMove.DrainPunch, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, 0, 2, 75, 100, PBEMoveEffect.HPDrain, 50, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByIronFist | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.DreamEater, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Special, 0, 3, 100, 100, PBEMoveEffect.HPDrain__RequireSleep, 50, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.DrillPeck, new PBEDDMoveData ( PBEType.Flying, PBEMoveCategory.Physical, 0, 4, 80, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleNotSelf, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.DrillRun, new PBEDDMoveData ( PBEType.Ground, PBEMoveCategory.Physical, 0, 2, 80, 95, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.HighCritChance | PBEMoveFlag.MakesContact ) }, { PBEMove.DualChop, new PBEDDMoveData ( PBEType.Dragon, PBEMoveCategory.Physical, 0, 3, 40, 90, PBEMoveEffect.Hit__2Times, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.DynamicPunch, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, 0, 1, 100, 50, PBEMoveEffect.Hit__MaybeConfuse, 100, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByIronFist | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.EarthPower, new PBEDDMoveData ( PBEType.Ground, PBEMoveCategory.Special, 0, 2, 90, 100, PBEMoveEffect.Hit__MaybeLowerTarget_SPDEF_By1, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Earthquake, new PBEDDMoveData ( PBEType.Ground, PBEMoveCategory.Physical, 0, 2, 100, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.AllSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.DoubleDamageUnderground ) }, { PBEMove.EchoedVoice, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Special, 0, 3, 40, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedBySoundproof ) }, { PBEMove.EggBomb, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 2, 100, 75, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.ElectroBall, new PBEDDMoveData ( PBEType.Electric, PBEMoveCategory.Special, 0, 2, 0, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Electroweb, new PBEDDMoveData ( PBEType.Electric, PBEMoveCategory.Special, 0, 3, 55, 95, PBEMoveEffect.Hit__MaybeLowerTarget_SPE_By1, 100, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Embargo, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Status, 0, 3, 0, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Ember, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Special, 0, 5, 40, 100, PBEMoveEffect.Hit__MaybeBurn, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Encore, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 1, 0, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Endeavor, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 1, 0, 100, PBEMoveEffect.Endeavor, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Endure, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, +4, 2, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.Self, PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMetronome ) }, { PBEMove.EnergyBall, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Special, 0, 2, 80, 100, PBEMoveEffect.Hit__MaybeLowerTarget_SPDEF_By1, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Entrainment, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 3, 0, 100, PBEMoveEffect.Entrainment, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Eruption, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Special, 0, 1, 150, 100, PBEMoveEffect.Eruption, 0, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Explosion, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 1, 250, 100, PBEMoveEffect.Selfdestruct, 0, PBEMoveTarget.AllSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Extrasensory, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Special, 0, 6, 80, 100, PBEMoveEffect.Hit__MaybeFlinch, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.ExtremeSpeed, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, +2, 1, 80, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Facade, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 4, 70, 100, PBEMoveEffect.Facade, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.FaintAttack, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Physical, 0, 4, 60, 0, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.FakeOut, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, +3, 2, 40, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.FakeTears, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Status, 0, 4, 0, 100, PBEMoveEffect.ChangeTarget_SPDEF, -2, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.FalseSwipe, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 8, 40, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.FeatherDance, new PBEDDMoveData ( PBEType.Flying, PBEMoveCategory.Status, 0, 3, 0, 100, PBEMoveEffect.ChangeTarget_ATK, -2, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Feint, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, +2, 2, 30, 100, PBEMoveEffect.Feint, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMetronome ) }, { PBEMove.FieryDance, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Special, 0, 2, 80, 100, PBEMoveEffect.Hit__MaybeRaiseUser_SPATK_By1, 50, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.FinalGambit, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Special, 0, 1, 0, 100, PBEMoveEffect.FinalGambit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.FireBlast, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Special, 0, 1, 120, 85, PBEMoveEffect.Hit__MaybeBurn, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.FireFang, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Physical, 0, 3, 65, 95, PBEMoveEffect.Hit__MaybeBurn__10PercentFlinch, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.FirePledge, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Special, 0, 2, 50, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.UnaffectedByGems ) }, { PBEMove.FirePunch, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Physical, 0, 3, 75, 100, PBEMoveEffect.Hit__MaybeBurn, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByIronFist | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.FireSpin, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Special, 0, 3, 35, 85, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Fissure, new PBEDDMoveData ( PBEType.Ground, PBEMoveCategory.Physical, 0, 1, 0, 30, PBEMoveEffect.OneHitKnockout, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByProtect | PBEMoveFlag.HitsUnderground ) }, { PBEMove.Flail, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 3, 0, 100, PBEMoveEffect.Flail, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.FlameBurst, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Special, 0, 3, 70, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.FlameCharge, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Physical, 0, 4, 50, 100, PBEMoveEffect.Hit__MaybeRaiseUser_SPE_By1, 100, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Flamethrower, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Special, 0, 3, 95, 100, PBEMoveEffect.Hit__MaybeBurn, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.FlameWheel, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Physical, 0, 5, 60, 100, PBEMoveEffect.Hit__MaybeBurn, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.DefrostsUser | PBEMoveFlag.MakesContact ) }, { PBEMove.FlareBlitz, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Physical, 0, 3, 120, 100, PBEMoveEffect.Recoil__10PercentBurn, 3, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedByReckless | PBEMoveFlag.DefrostsUser | PBEMoveFlag.MakesContact ) }, { PBEMove.Flash, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 4, 0, 100, PBEMoveEffect.ChangeTarget_ACC, -1, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.FlashCannon, new PBEDDMoveData ( PBEType.Steel, PBEMoveCategory.Special, 0, 2, 80, 100, PBEMoveEffect.Hit__MaybeLowerTarget_SPDEF_By1, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Flatter, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Status, 0, 3, 0, 100, PBEMoveEffect.Flatter, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Fling, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Physical, 0, 2, 0, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Fly, new PBEDDMoveData ( PBEType.Flying, PBEMoveCategory.Physical, 0, 3, 90, 95, PBEMoveEffect.Fly, 0, PBEMoveTarget.SingleNotSelf, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedByGravity | PBEMoveFlag.BlockedFromSleepTalk | PBEMoveFlag.MakesContact ) }, { PBEMove.FocusBlast, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Special, 0, 1, 120, 70, PBEMoveEffect.Hit__MaybeLowerTarget_SPDEF_By1, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.FocusEnergy, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 6, 0, 0, PBEMoveEffect.FocusEnergy, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.FocusPunch, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, -3, 4, 150, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByIronFist | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMeFirst | PBEMoveFlag.BlockedFromMetronome | PBEMoveFlag.BlockedFromSleepTalk | PBEMoveFlag.MakesContact ) }, { PBEMove.FollowMe, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, +3, 4, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.Self, PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMetronome ) }, { PBEMove.ForcePalm, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, 0, 2, 60, 100, PBEMoveEffect.Hit__MaybeParalyze, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Foresight, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 8, 0, 0, PBEMoveEffect.Foresight, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.FoulPlay, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Physical, 0, 3, 95, 100, PBEMoveEffect.FoulPlay, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.FreezeShock, new PBEDDMoveData ( PBEType.Ice, PBEMoveCategory.Physical, 0, 1, 140, 90, PBEMoveEffect.TODOMOVE, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedFromSleepTalk | PBEMoveFlag.BlockedFromMetronome ) }, { PBEMove.FrenzyPlant, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Special, 0, 1, 150, 90, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.FrostBreath, new PBEDDMoveData ( PBEType.Ice, PBEMoveCategory.Special, 0, 2, 40, 90, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AlwaysCrit ) }, { PBEMove.Frustration, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 4, 0, 100, PBEMoveEffect.Frustration, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.FuryAttack, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 3, 15, 85, PBEMoveEffect.Hit__2To5Times, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.FuryCutter, new PBEDDMoveData ( PBEType.Bug, PBEMoveCategory.Physical, 0, 4, 20, 95, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.FurySwipes, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 3, 18, 85, PBEMoveEffect.Hit__2To5Times, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.FusionBolt, new PBEDDMoveData ( PBEType.Electric, PBEMoveCategory.Physical, 0, 1, 100, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.FusionFlare, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Special, 0, 1, 100, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.FutureSight, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Special, 0, 2, 100, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.None ) }, { PBEMove.GastroAcid, new PBEDDMoveData ( PBEType.Poison, PBEMoveCategory.Status, 0, 2, 0, 100, PBEMoveEffect.GastroAcid, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.GearGrind, new PBEDDMoveData ( PBEType.Steel, PBEMoveCategory.Physical, 0, 3, 50, 85, PBEMoveEffect.Hit__2Times, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.GigaDrain, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Special, 0, 2, 75, 100, PBEMoveEffect.HPDrain, 50, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.GigaImpact, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 1, 150, 90, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Glaciate, new PBEDDMoveData ( PBEType.Ice, PBEMoveCategory.Special, 0, 2, 65, 95, PBEMoveEffect.Hit__MaybeLowerTarget_SPE_By1, 100, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Glare, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 6, 0, 90, PBEMoveEffect.Paralyze, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.GrassKnot, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Special, 0, 4, 0, 100, PBEMoveEffect.GrassKnot, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.GrassPledge, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Special, 0, 2, 50, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.UnaffectedByGems ) }, { PBEMove.GrassWhistle, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Status, 0, 3, 0, 55, PBEMoveEffect.Sleep, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedBySoundproof ) }, { PBEMove.Gravity, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 1, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.All, PBEMoveFlag.None ) }, { PBEMove.Growl, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 8, 0, 100, PBEMoveEffect.ChangeTarget_ATK, -1, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedBySoundproof ) }, { PBEMove.Growth, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Status, 0, 8, 0, 0, PBEMoveEffect.Growth, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.Grudge, new PBEDDMoveData ( PBEType.Ghost, PBEMoveCategory.Status, 0, 1, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.Self, PBEMoveFlag.None ) }, { PBEMove.GuardSplit, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByProtect ) }, { PBEMove.GuardSwap, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Guillotine, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 1, 0, 30, PBEMoveEffect.OneHitKnockout, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.GunkShot, new PBEDDMoveData ( PBEType.Poison, PBEMoveCategory.Physical, 0, 1, 120, 70, PBEMoveEffect.Hit__MaybePoison, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Gust, new PBEDDMoveData ( PBEType.Flying, PBEMoveCategory.Special, 0, 7, 40, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleNotSelf, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.DoubleDamageAirborne ) }, { PBEMove.GyroBall, new PBEDDMoveData ( PBEType.Steel, PBEMoveCategory.Physical, 0, 1, 0, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Hail, new PBEDDMoveData ( PBEType.Ice, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.Hail, 0, PBEMoveTarget.All, PBEMoveFlag.None ) }, { PBEMove.HammerArm, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, 0, 2, 100, 90, PBEMoveEffect.Hit__MaybeLowerUser_SPE_By1, 100, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByIronFist | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Harden, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 6, 0, 0, PBEMoveEffect.ChangeTarget_DEF, +1, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.Haze, new PBEDDMoveData ( PBEType.Ice, PBEMoveCategory.Status, 0, 6, 0, 0, PBEMoveEffect.Haze, 0, PBEMoveTarget.All, PBEMoveFlag.None ) }, { PBEMove.Headbutt, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 3, 70, 100, PBEMoveEffect.Hit__MaybeFlinch, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.HeadCharge, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 3, 120, 100, PBEMoveEffect.Recoil, 4, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedByReckless | PBEMoveFlag.MakesContact ) }, { PBEMove.HeadSmash, new PBEDDMoveData ( PBEType.Rock, PBEMoveCategory.Physical, 0, 1, 150, 80, PBEMoveEffect.Recoil, 2, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedByReckless | PBEMoveFlag.MakesContact ) }, { PBEMove.HealBell, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 1, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.AllTeam, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.HealBlock, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 3, 0, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.HealingWish, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.HealOrder, new PBEDDMoveData ( PBEType.Bug, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.RestoreTargetHP, 50, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.HealPulse, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.RestoreTargetHP, 50, PBEMoveTarget.SingleNotSelf, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.HeartStamp, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Physical, 0, 5, 60, 100, PBEMoveEffect.Hit__MaybeFlinch, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.HeartSwap, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.HeatCrash, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Physical, 0, 2, 0, 100, PBEMoveEffect.HeatCrash, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.HeatWave, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Special, 0, 2, 100, 90, PBEMoveEffect.Hit__MaybeBurn, 10, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.HeavySlam, new PBEDDMoveData ( PBEType.Steel, PBEMoveCategory.Physical, 0, 2, 0, 100, PBEMoveEffect.HeatCrash, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.HelpingHand, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, +5, 4, 0, 0, PBEMoveEffect.HelpingHand, 0, PBEMoveTarget.SingleAllySurrounding, PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMetronome ) }, { PBEMove.Hex, new PBEDDMoveData ( PBEType.Ghost, PBEMoveCategory.Special, 0, 2, 50, 100, PBEMoveEffect.Hex, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.HiddenPower, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Special, 0, 3, 0, 100, PBEMoveEffect.HiddenPower, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.HiJumpKick, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, 0, 2, 130, 90, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedByReckless | PBEMoveFlag.BlockedByGravity | PBEMoveFlag.MakesContact ) }, { PBEMove.HoneClaws, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Status, 0, 3, 0, 0, PBEMoveEffect.RaiseTarget_ATK_ACC_By1, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.HornAttack, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 5, 65, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.HornDrill, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 1, 0, 30, PBEMoveEffect.OneHitKnockout, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.HornLeech, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Physical, 0, 2, 75, 100, PBEMoveEffect.HPDrain, 50, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Howl, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 8, 0, 0, PBEMoveEffect.ChangeTarget_ATK, +1, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.Hurricane, new PBEDDMoveData ( PBEType.Flying, PBEMoveCategory.Special, 0, 2, 120, 70, PBEMoveEffect.Hit__MaybeConfuse, 30, PBEMoveTarget.SingleNotSelf, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.HitsAirborne | PBEMoveFlag.NeverMissRain ) }, { PBEMove.HydroCannon, new PBEDDMoveData ( PBEType.Water, PBEMoveCategory.Special, 0, 1, 150, 90, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.HydroPump, new PBEDDMoveData ( PBEType.Water, PBEMoveCategory.Special, 0, 1, 120, 80, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.HyperBeam, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Special, 0, 1, 150, 90, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.HyperFang, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 3, 80, 90, PBEMoveEffect.Hit__MaybeFlinch, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.HyperVoice, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Special, 0, 2, 90, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedBySoundproof ) }, { PBEMove.Hypnosis, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 4, 0, 60, PBEMoveEffect.Sleep, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.IceBall, new PBEDDMoveData ( PBEType.Ice, PBEMoveCategory.Physical, 0, 4, 30, 90, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.DoubleDamageUserDefenseCurl | PBEMoveFlag.MakesContact ) }, { PBEMove.IceBeam, new PBEDDMoveData ( PBEType.Ice, PBEMoveCategory.Special, 0, 2, 95, 100, PBEMoveEffect.Hit__MaybeFreeze, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.IceBurn, new PBEDDMoveData ( PBEType.Ice, PBEMoveCategory.Special, 0, 1, 140, 90, PBEMoveEffect.TODOMOVE, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedFromSleepTalk | PBEMoveFlag.BlockedFromMetronome ) }, { PBEMove.IceFang, new PBEDDMoveData ( PBEType.Ice, PBEMoveCategory.Physical, 0, 3, 65, 95, PBEMoveEffect.Hit__MaybeFreeze__10PercentFlinch, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.IcePunch, new PBEDDMoveData ( PBEType.Ice, PBEMoveCategory.Physical, 0, 3, 75, 100, PBEMoveEffect.Hit__MaybeFreeze, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByIronFist | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.IceShard, new PBEDDMoveData ( PBEType.Ice, PBEMoveCategory.Physical, +1, 6, 40, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.IcicleCrash, new PBEDDMoveData ( PBEType.Ice, PBEMoveCategory.Physical, 0, 2, 85, 90, PBEMoveEffect.Hit__MaybeFlinch, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.IcicleSpear, new PBEDDMoveData ( PBEType.Ice, PBEMoveCategory.Physical, 0, 6, 25, 100, PBEMoveEffect.Hit__2To5Times, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.IcyWind, new PBEDDMoveData ( PBEType.Ice, PBEMoveCategory.Special, 0, 3, 55, 95, PBEMoveEffect.Hit__MaybeLowerTarget_SPE_By1, 100, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Imprison, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.Incinerate, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Special, 0, 3, 30, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Inferno, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Special, 0, 1, 100, 50, PBEMoveEffect.Hit__MaybeBurn, 100, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Ingrain, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.IronDefense, new PBEDDMoveData ( PBEType.Steel, PBEMoveCategory.Status, 0, 3, 0, 0, PBEMoveEffect.ChangeTarget_DEF, +2, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.IronHead, new PBEDDMoveData ( PBEType.Steel, PBEMoveCategory.Physical, 0, 3, 80, 100, PBEMoveEffect.Hit__MaybeFlinch, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.IronTail, new PBEDDMoveData ( PBEType.Steel, PBEMoveCategory.Physical, 0, 3, 100, 75, PBEMoveEffect.Hit__MaybeLowerTarget_DEF_By1, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Judgment, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Special, 0, 2, 100, 100, PBEMoveEffect.Judgment, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.JumpKick, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, 0, 2, 100, 95, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedByReckless | PBEMoveFlag.BlockedByGravity | PBEMoveFlag.MakesContact ) }, { PBEMove.KarateChop, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, 0, 5, 50, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.HighCritChance | PBEMoveFlag.MakesContact ) }, { PBEMove.Kinesis, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 3, 0, 80, PBEMoveEffect.ChangeTarget_ACC, -1, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.KnockOff, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Physical, 0, 4, 20, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.LastResort, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 1, 140, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.LavaPlume, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Special, 0, 3, 80, 100, PBEMoveEffect.Hit__MaybeBurn, 30, PBEMoveTarget.AllSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.LeafBlade, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Physical, 0, 3, 90, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.HighCritChance | PBEMoveFlag.MakesContact ) }, { PBEMove.LeafStorm, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Special, 0, 1, 140, 90, PBEMoveEffect.Hit__MaybeLowerUser_SPATK_By2, 100, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.LeafTornado, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Special, 0, 2, 65, 90, PBEMoveEffect.Hit__MaybeLowerTarget_ACC_By1, 50, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.LeechLife, new PBEDDMoveData ( PBEType.Bug, PBEMoveCategory.Physical, 0, 3, 20, 100, PBEMoveEffect.HPDrain, 50, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.LeechSeed, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Status, 0, 2, 0, 90, PBEMoveEffect.LeechSeed, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Leer, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 6, 0, 100, PBEMoveEffect.ChangeTarget_DEF, -1, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Lick, new PBEDDMoveData ( PBEType.Ghost, PBEMoveCategory.Physical, 0, 6, 20, 100, PBEMoveEffect.Hit__MaybeParalyze, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.LightScreen, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 6, 0, 0, PBEMoveEffect.LightScreen, 0, PBEMoveTarget.AllTeam, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.LockOn, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 1, 0, 0, PBEMoveEffect.LockOn, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.LovelyKiss, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 2, 0, 75, PBEMoveEffect.Sleep, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.LowKick, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, 0, 4, 0, 100, PBEMoveEffect.GrassKnot, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.LowSweep, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, 0, 4, 60, 100, PBEMoveEffect.Hit__MaybeLowerTarget_SPE_By1, 100, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.LuckyChant, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 6, 0, 0, PBEMoveEffect.LuckyChant, 0, PBEMoveTarget.AllTeam, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.LunarDance, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.LusterPurge, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Special, 0, 1, 70, 100, PBEMoveEffect.Hit__MaybeLowerTarget_SPDEF_By1, 50, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.MachPunch, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, +1, 6, 40, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByIronFist | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.MagicalLeaf, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Special, 0, 4, 60, 0, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.MagicCoat, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, +4, 3, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.Self, PBEMoveFlag.None ) }, { PBEMove.MagicRoom, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, -7, 2, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.All, PBEMoveFlag.AffectedByMirrorMove ) }, { PBEMove.MagmaStorm, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Special, 0, 1, 120, 75, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.MagnetBomb, new PBEDDMoveData ( PBEType.Steel, PBEMoveCategory.Physical, 0, 4, 60, 0, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.MagnetRise, new PBEDDMoveData ( PBEType.Electric, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.MagnetRise, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch | PBEMoveFlag.BlockedByGravity ) }, { PBEMove.Magnitude, new PBEDDMoveData ( PBEType.Ground, PBEMoveCategory.Physical, 0, 6, 0, 100, PBEMoveEffect.Magnitude, 0, PBEMoveTarget.AllSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.DoubleDamageUnderground ) }, { PBEMove.MeanLook, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 1, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove ) }, { PBEMove.Meditate, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 8, 0, 0, PBEMoveEffect.ChangeTarget_ATK, +1, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.MeFirst, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleFoeSurrounding, PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMetronome | PBEMoveFlag.BlockedFromSleepTalk ) }, { PBEMove.MegaDrain, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Special, 0, 3, 40, 100, PBEMoveEffect.HPDrain, 50, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Megahorn, new PBEDDMoveData ( PBEType.Bug, PBEMoveCategory.Physical, 0, 2, 120, 85, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.MegaKick, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 1, 120, 75, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.MegaPunch, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 4, 80, 85, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByIronFist | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Memento, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Status, 0, 2, 0, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.MetalBurst, new PBEDDMoveData ( PBEType.Steel, PBEMoveCategory.Physical, 0, 2, 0, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedFromMeFirst ) }, { PBEMove.MetalClaw, new PBEDDMoveData ( PBEType.Steel, PBEMoveCategory.Physical, 0, 7, 50, 95, PBEMoveEffect.Hit__MaybeRaiseUser_ATK_By1, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.MetalSound, new PBEDDMoveData ( PBEType.Steel, PBEMoveCategory.Status, 0, 8, 0, 85, PBEMoveEffect.ChangeTarget_SPDEF, -2, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedBySoundproof ) }, { PBEMove.MeteorMash, new PBEDDMoveData ( PBEType.Steel, PBEMoveCategory.Special, 0, 2, 100, 85, PBEMoveEffect.Hit__MaybeRaiseUser_ATK_By1, 20, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByIronFist | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Metronome, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.Metronome, 0, PBEMoveTarget.Self, PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMetronome | PBEMoveFlag.BlockedFromMimic | PBEMoveFlag.BlockedFromSketchWhenSuccessful | PBEMoveFlag.BlockedFromSleepTalk ) }, { PBEMove.MilkDrink, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.RestoreTargetHP, 50, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.Mimic, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMetronome | PBEMoveFlag.BlockedFromSketchWhenSuccessful | PBEMoveFlag.BlockedFromSleepTalk ) }, { PBEMove.MindReader, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 1, 0, 0, PBEMoveEffect.LockOn, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Minimize, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.Minimize, +2, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.MiracleEye, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 8, 0, 0, PBEMoveEffect.MiracleEye, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.MirrorCoat, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Special, -5, 4, 0, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMeFirst | PBEMoveFlag.BlockedFromMetronome ) }, { PBEMove.MirrorMove, new PBEDDMoveData ( PBEType.Flying, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMetronome | PBEMoveFlag.BlockedFromSleepTalk ) }, { PBEMove.MirrorShot, new PBEDDMoveData ( PBEType.Steel, PBEMoveCategory.Special, 0, 2, 65, 85, PBEMoveEffect.Hit__MaybeLowerTarget_ACC_By1, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Mist, new PBEDDMoveData ( PBEType.Ice, PBEMoveCategory.Status, 0, 6, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.AllTeam, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.MistBall, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Special, 0, 1, 70, 100, PBEMoveEffect.Hit__MaybeLowerTarget_SPATK_By1, 50, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Moonlight, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 1, 0, 0, PBEMoveEffect.Moonlight, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.MorningSun, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 1, 0, 0, PBEMoveEffect.Moonlight, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.MudBomb, new PBEDDMoveData ( PBEType.Ground, PBEMoveCategory.Special, 0, 2, 65, 85, PBEMoveEffect.Hit__MaybeLowerTarget_ACC_By1, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.MuddyWater, new PBEDDMoveData ( PBEType.Water, PBEMoveCategory.Special, 0, 2, 95, 85, PBEMoveEffect.Hit__MaybeLowerTarget_ACC_By1, 30, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.MudSlap, new PBEDDMoveData ( PBEType.Ground, PBEMoveCategory.Special, 0, 2, 20, 100, PBEMoveEffect.Hit__MaybeLowerTarget_ACC_By1, 100, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.MudSport, new PBEDDMoveData ( PBEType.Ground, PBEMoveCategory.Status, 0, 3, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.All, PBEMoveFlag.None ) }, { PBEMove.MudShot, new PBEDDMoveData ( PBEType.Ground, PBEMoveCategory.Special, 0, 3, 55, 95, PBEMoveEffect.Hit__MaybeLowerTarget_ACC_By1, 100, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.NastyPlot, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.ChangeTarget_SPATK, +2, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.NaturalGift, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 3, 0, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.NaturePower, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.BlockedFromMetronome ) }, { PBEMove.NeedleArm, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Physical, 0, 3, 60, 100, PBEMoveEffect.Hit__MaybeFlinch, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.NightDaze, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Special, 0, 2, 85, 95, PBEMoveEffect.Hit__MaybeLowerTarget_ACC_By1, 40, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Nightmare, new PBEDDMoveData ( PBEType.Ghost, PBEMoveCategory.Status, 0, 3, 0, 100, PBEMoveEffect.Nightmare, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.NightShade, new PBEDDMoveData ( PBEType.Ghost, PBEMoveCategory.Special, 0, 3, 0, 100, PBEMoveEffect.SeismicToss, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.NightSlash, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Physical, 0, 3, 70, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.HighCritChance | PBEMoveFlag.MakesContact ) }, { PBEMove.Octazooka, new PBEDDMoveData ( PBEType.Water, PBEMoveCategory.Special, 0, 2, 65, 85, PBEMoveEffect.Hit__MaybeLowerTarget_ACC_By1, 50, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.OdorSleuth, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 8, 0, 0, PBEMoveEffect.Foresight, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.OminousWind, new PBEDDMoveData ( PBEType.Ghost, PBEMoveCategory.Special, 0, 1, 60, 100, PBEMoveEffect.Hit__MaybeRaiseUser_ATK_DEF_SPATK_SPDEF_SPE_By1, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Outrage, new PBEDDMoveData ( PBEType.Dragon, PBEMoveCategory.Physical, 0, 2, 120, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.RandomFoeSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Overheat, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Special, 0, 1, 140, 90, PBEMoveEffect.Hit__MaybeLowerUser_SPATK_By2, 100, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.PainSplit, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.PainSplit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Payback, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Physical, 0, 2, 50, 100, PBEMoveEffect.Payback, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.PayDay, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 4, 40, 100, PBEMoveEffect.PayDay, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Peck, new PBEDDMoveData ( PBEType.Flying, PBEMoveCategory.Physical, 0, 7, 35, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleNotSelf, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.PerishSong, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 1, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.All, PBEMoveFlag.AffectedBySoundproof ) }, { PBEMove.PetalDance, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Special, 0, 2, 120, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.RandomFoeSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.PinMissile, new PBEDDMoveData ( PBEType.Bug, PBEMoveCategory.Physical, 0, 4, 14, 85, PBEMoveEffect.Hit__2To5Times, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Pluck, new PBEDDMoveData ( PBEType.Flying, PBEMoveCategory.Physical, 0, 4, 60, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleNotSelf, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.PoisonFang, new PBEDDMoveData ( PBEType.Poison, PBEMoveCategory.Physical, 0, 3, 50, 100, PBEMoveEffect.Hit__MaybeToxic, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.PoisonGas, new PBEDDMoveData ( PBEType.Poison, PBEMoveCategory.Status, 0, 8, 0, 80, PBEMoveEffect.Poison, 0, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.PoisonJab, new PBEDDMoveData ( PBEType.Poison, PBEMoveCategory.Physical, 0, 4, 80, 100, PBEMoveEffect.Hit__MaybePoison, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.PoisonPowder, new PBEDDMoveData ( PBEType.Poison, PBEMoveCategory.Status, 0, 7, 0, 75, PBEMoveEffect.Poison, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.PoisonSting, new PBEDDMoveData ( PBEType.Poison, PBEMoveCategory.Physical, 0, 7, 15, 100, PBEMoveEffect.Hit__MaybePoison, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.PoisonTail, new PBEDDMoveData ( PBEType.Poison, PBEMoveCategory.Physical, 0, 5, 50, 100, PBEMoveEffect.Hit__MaybePoison, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.HighCritChance | PBEMoveFlag.MakesContact ) }, { PBEMove.Pound, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 7, 40, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.PowderSnow, new PBEDDMoveData ( PBEType.Ice, PBEMoveCategory.Special, 0, 5, 40, 100, PBEMoveEffect.Hit__MaybeFreeze, 10, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.PowerGem, new PBEDDMoveData ( PBEType.Rock, PBEMoveCategory.Special, 0, 4, 70, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.PowerSplit, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByProtect ) }, { PBEMove.PowerSwap, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.PowerTrick, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.PowerTrick, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.PowerWhip, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Physical, 0, 2, 120, 85, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Present, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 3, 0, 90, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Protect, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, +4, 2, 0, 0, PBEMoveEffect.Protect, 0, PBEMoveTarget.Self, PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMetronome ) }, { PBEMove.Psybeam, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Special, 0, 4, 65, 100, PBEMoveEffect.Hit__MaybeConfuse, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Psychic, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Special, 0, 2, 90, 100, PBEMoveEffect.Hit__MaybeLowerTarget_SPDEF_By1, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.PsychoBoost, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Special, 0, 1, 140, 90, PBEMoveEffect.Hit__MaybeLowerUser_SPATK_By2, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.PsychoCut, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Physical, 0, 4, 70, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.HighCritChance ) }, { PBEMove.PsychoShift, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 2, 0, 90, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.PsychUp, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.PsychUp, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.None ) }, { PBEMove.Psyshock, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Special, 0, 2, 80, 100, PBEMoveEffect.Psyshock, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Psystrike, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Special, 0, 2, 100, 100, PBEMoveEffect.Psyshock, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Psywave, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Special, 0, 3, 0, 80, PBEMoveEffect.Psywave, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Punishment, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Physical, 0, 1, 60, 100, PBEMoveEffect.Punishment, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Pursuit, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Physical, 0, 4, 40, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Quash, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Status, 0, 3, 0, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedFromMetronome ) }, { PBEMove.QuickAttack, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, +1, 6, 40, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.QuickGuard, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Status, +3, 3, 0, 0, PBEMoveEffect.QuickGuard, 0, PBEMoveTarget.AllTeam, PBEMoveFlag.AffectedBySnatch | PBEMoveFlag.BlockedFromMetronome ) }, { PBEMove.QuiverDance, new PBEDDMoveData ( PBEType.Bug, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.RaiseTarget_SPATK_SPDEF_SPE_By1, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.Rage, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 4, 20, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.RagePowder, new PBEDDMoveData ( PBEType.Bug, PBEMoveCategory.Status, +3, 4, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.Self, PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMetronome ) }, { PBEMove.RainDance, new PBEDDMoveData ( PBEType.Water, PBEMoveCategory.Status, 0, 1, 0, 0, PBEMoveEffect.RainDance, 0, PBEMoveTarget.All, PBEMoveFlag.None ) }, { PBEMove.RapidSpin, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 8, 20, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.RazorLeaf, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Physical, 0, 5, 55, 95, PBEMoveEffect.Hit, 0, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.HighCritChance ) }, { PBEMove.RazorShell, new PBEDDMoveData ( PBEType.Water, PBEMoveCategory.Physical, 0, 2, 75, 95, PBEMoveEffect.Hit__MaybeLowerTarget_DEF_By1, 50, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.RazorWind, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Special, 0, 2, 80, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedFromSleepTalk | PBEMoveFlag.HighCritChance ) }, { PBEMove.Recover, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.RestoreTargetHP, 50, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.Recycle, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.Reflect, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.Reflect, 0, PBEMoveTarget.AllTeam, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.ReflectType, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 3, 0, 0, PBEMoveEffect.ReflectType, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Refresh, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.Refresh, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.RelicSong, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Special, 0, 2, 75, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedBySoundproof | PBEMoveFlag.BlockedFromMetronome ) }, { PBEMove.Rest, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.Rest, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.Retaliate, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 1, 70, 100, PBEMoveEffect.Retaliate, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Return, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 4, 0, 100, PBEMoveEffect.Return, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Revenge, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, -4, 2, 60, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Reversal, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, 0, 3, 0, 100, PBEMoveEffect.Flail, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Roar, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, -6, 4, 0, 100, PBEMoveEffect.Whirlwind, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedBySoundproof ) }, { PBEMove.RoarOfTime, new PBEDDMoveData ( PBEType.Dragon, PBEMoveCategory.Special, 0, 1, 150, 90, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.RockBlast, new PBEDDMoveData ( PBEType.Rock, PBEMoveCategory.Physical, 0, 2, 25, 90, PBEMoveEffect.Hit__2To5Times, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.RockClimb, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 4, 90, 85, PBEMoveEffect.Hit__MaybeConfuse, 20, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.RockPolish, new PBEDDMoveData ( PBEType.Rock, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.ChangeTarget_SPE, +2, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.RockSlide, new PBEDDMoveData ( PBEType.Rock, PBEMoveCategory.Physical, 0, 2, 75, 90, PBEMoveEffect.Hit__MaybeFlinch, 30, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.RockSmash, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, 0, 3, 40, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.RockThrow, new PBEDDMoveData ( PBEType.Rock, PBEMoveCategory.Physical, 0, 3, 50, 90, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.RockTomb, new PBEDDMoveData ( PBEType.Rock, PBEMoveCategory.Physical, 0, 2, 50, 80, PBEMoveEffect.Hit__MaybeLowerTarget_SPE_By1, 100, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.RockWrecker, new PBEDDMoveData ( PBEType.Rock, PBEMoveCategory.Physical, 0, 1, 150, 90, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.RolePlay, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.RolePlay, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.None ) }, { PBEMove.RollingKick, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, 0, 3, 60, 85, PBEMoveEffect.Hit__MaybeFlinch, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Rollout, new PBEDDMoveData ( PBEType.Rock, PBEMoveCategory.Physical, 0, 4, 30, 90, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.DoubleDamageUserDefenseCurl | PBEMoveFlag.MakesContact ) }, { PBEMove.Roost, new PBEDDMoveData ( PBEType.Flying, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.Roost, 50, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.Round, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Special, 0, 3, 60, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedBySoundproof ) }, { PBEMove.SacredFire, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Physical, 0, 1, 100, 95, PBEMoveEffect.Hit__MaybeBurn, 50, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.DefrostsUser ) }, { PBEMove.SacredSword, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, 0, 4, 90, 100, PBEMoveEffect.ChipAway, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Safeguard, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 5, 0, 0, PBEMoveEffect.Safeguard, 0, PBEMoveTarget.AllTeam, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.SandAttack, new PBEDDMoveData ( PBEType.Ground, PBEMoveCategory.Status, 0, 3, 0, 100, PBEMoveEffect.ChangeTarget_ACC, -1, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Sandstorm, new PBEDDMoveData ( PBEType.Rock, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.Sandstorm, 0, PBEMoveTarget.All, PBEMoveFlag.None ) }, { PBEMove.SandTomb, new PBEDDMoveData ( PBEType.Ground, PBEMoveCategory.Physical, 0, 3, 35, 85, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Scald, new PBEDDMoveData ( PBEType.Water, PBEMoveCategory.Special, 0, 3, 80, 100, PBEMoveEffect.Hit__MaybeBurn, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.DefrostsUser ) }, { PBEMove.ScaryFace, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 2, 0, 100, PBEMoveEffect.ChangeTarget_SPE, -2, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Scratch, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 8, 40, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Screech, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 8, 0, 85, PBEMoveEffect.ChangeTarget_DEF, -2, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedBySoundproof ) }, { PBEMove.SearingShot, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Special, 0, 1, 100, 100, PBEMoveEffect.Hit__MaybeBurn, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.SecretPower, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 4, 70, 100, PBEMoveEffect.SecretPower, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.SecretSword, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Special, 0, 2, 85, 100, PBEMoveEffect.Psyshock, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedFromMetronome ) }, { PBEMove.SeedBomb, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Physical, 0, 3, 80, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.SeedFlare, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Special, 0, 1, 120, 85, PBEMoveEffect.Hit__MaybeLowerTarget_SPDEF_By2, 40, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.SeismicToss, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, 0, 4, 0, 100, PBEMoveEffect.SeismicToss, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Selfdestruct, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 1, 200, 100, PBEMoveEffect.Selfdestruct, 0, PBEMoveTarget.AllSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.ShadowBall, new PBEDDMoveData ( PBEType.Ghost, PBEMoveCategory.Special, 0, 3, 80, 100, PBEMoveEffect.Hit__MaybeLowerTarget_SPDEF_By1, 20, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.ShadowClaw, new PBEDDMoveData ( PBEType.Ghost, PBEMoveCategory.Physical, 0, 3, 70, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.HighCritChance | PBEMoveFlag.MakesContact ) }, { PBEMove.ShadowForce, new PBEDDMoveData ( PBEType.Ghost, PBEMoveCategory.Physical, 0, 1, 120, 100, PBEMoveEffect.ShadowForce, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.BlockedFromSleepTalk | PBEMoveFlag.MakesContact ) }, { PBEMove.ShadowPunch, new PBEDDMoveData ( PBEType.Ghost, PBEMoveCategory.Physical, 0, 4, 60, 0, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByIronFist | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.ShadowSneak, new PBEDDMoveData ( PBEType.Ghost, PBEMoveCategory.Physical, +1, 6, 40, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Sharpen, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 6, 0, 0, PBEMoveEffect.ChangeTarget_ATK, +1, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.SheerCold, new PBEDDMoveData ( PBEType.Ice, PBEMoveCategory.Special, 0, 1, 0, 30, PBEMoveEffect.OneHitKnockout, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByProtect ) }, { PBEMove.ShellSmash, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 3, 0, 0, PBEMoveEffect.LowerTarget_DEF_SPDEF_By1_Raise_ATK_SPATK_SPE_By2, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.ShiftGear, new PBEDDMoveData ( PBEType.Steel, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.RaiseTarget_SPE_By2_ATK_By1, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.SignalBeam, new PBEDDMoveData ( PBEType.Bug, PBEMoveCategory.Special, 0, 3, 75, 100, PBEMoveEffect.Hit__MaybeConfuse, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.SilverWind, new PBEDDMoveData ( PBEType.Bug, PBEMoveCategory.Special, 0, 1, 60, 100, PBEMoveEffect.Hit__MaybeRaiseUser_ATK_DEF_SPATK_SPDEF_SPE_By1, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.SimpleBeam, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 3, 0, 100, PBEMoveEffect.SimpleBeam, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Sing, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 3, 0, 55, PBEMoveEffect.Sleep, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedBySoundproof ) }, { PBEMove.Sketch, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 0, 0, 0, PBEMoveEffect.Sketch, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMetronome | PBEMoveFlag.BlockedFromMimic | PBEMoveFlag.BlockedFromSketch | PBEMoveFlag.BlockedFromSleepTalk ) }, { PBEMove.SkillSwap, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.SkullBash, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 3, 100, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedFromSleepTalk | PBEMoveFlag.MakesContact ) }, { PBEMove.SkyAttack, new PBEDDMoveData ( PBEType.Flying, PBEMoveCategory.Physical, 0, 1, 140, 90, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleNotSelf, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedFromSleepTalk ) }, { PBEMove.SkyDrop, new PBEDDMoveData ( PBEType.Flying, PBEMoveCategory.Physical, 0, 2, 60, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleNotSelf, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedByGravity | PBEMoveFlag.BlockedFromSleepTalk | PBEMoveFlag.MakesContact ) }, { PBEMove.ShockWave, new PBEDDMoveData ( PBEType.Electric, PBEMoveCategory.Special, 0, 4, 60, 0, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.SkyUppercut, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, 0, 3, 85, 90, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByIronFist | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.HitsAirborne | PBEMoveFlag.MakesContact ) }, { PBEMove.SlackOff, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.RestoreTargetHP, 50, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.Slam, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 4, 80, 75, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Slash, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 4, 70, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.HighCritChance | PBEMoveFlag.MakesContact ) }, { PBEMove.SleepPowder, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Status, 0, 3, 0, 75, PBEMoveEffect.Sleep, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.SleepTalk, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.Self, PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMetronome | PBEMoveFlag.BlockedFromSleepTalk ) }, { PBEMove.Sludge, new PBEDDMoveData ( PBEType.Poison, PBEMoveCategory.Special, 0, 4, 65, 100, PBEMoveEffect.Hit__MaybePoison, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.SludgeBomb, new PBEDDMoveData ( PBEType.Poison, PBEMoveCategory.Special, 0, 2, 90, 100, PBEMoveEffect.Hit__MaybePoison, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.SmackDown, new PBEDDMoveData ( PBEType.Rock, PBEMoveCategory.Physical, 0, 3, 50, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.HitsAirborne ) }, { PBEMove.SmellingSalt, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 2, 60, 100, PBEMoveEffect.SmellingSalt, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.SludgeWave, new PBEDDMoveData ( PBEType.Poison, PBEMoveCategory.Special, 0, 2, 95, 100, PBEMoveEffect.Hit__MaybePoison, 10, PBEMoveTarget.AllSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Smog, new PBEDDMoveData ( PBEType.Poison, PBEMoveCategory.Special, 0, 4, 20, 70, PBEMoveEffect.Hit__MaybePoison, 40, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.SmokeScreen, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 4, 0, 100, PBEMoveEffect.ChangeTarget_ACC, -1, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Snarl, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Special, 0, 3, 55, 95, PBEMoveEffect.Hit__MaybeLowerTarget_SPATK_By1, 100, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedBySoundproof | PBEMoveFlag.BlockedFromMetronome ) }, { PBEMove.Snatch, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Status, +4, 2, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.Self, PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMetronome ) }, { PBEMove.Snore, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Special, 0, 3, 40, 100, PBEMoveEffect.Snore, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedBySoundproof | PBEMoveFlag.BlockedFromMetronome ) }, { PBEMove.Soak, new PBEDDMoveData ( PBEType.Water, PBEMoveCategory.Status, 0, 4, 0, 100, PBEMoveEffect.Soak, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Softboiled, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.RestoreTargetHP, 50, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.SolarBeam, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Special, 0, 2, 120, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedFromSleepTalk ) }, { PBEMove.SonicBoom, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Special, 0, 4, 0, 90, PBEMoveEffect.SetDamage, 20, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.SpacialRend, new PBEDDMoveData ( PBEType.Dragon, PBEMoveCategory.Special, 0, 1, 100, 95, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.HighCritChance ) }, { PBEMove.Spark, new PBEDDMoveData ( PBEType.Electric, PBEMoveCategory.Physical, 0, 4, 65, 100, PBEMoveEffect.Hit__MaybeParalyze, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.SpiderWeb, new PBEDDMoveData ( PBEType.Bug, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove ) }, { PBEMove.SpikeCannon, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 3, 20, 100, PBEMoveEffect.Hit__2To5Times, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Spikes, new PBEDDMoveData ( PBEType.Ground, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.Spikes, 0, PBEMoveTarget.AllFoes, PBEMoveFlag.AffectedByMagicCoat ) }, { PBEMove.Spite, new PBEDDMoveData ( PBEType.Ghost, PBEMoveCategory.Status, 0, 2, 0, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.SpitUp, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Special, 0, 2, 0, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Splash, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 8, 0, 0, PBEMoveEffect.Nothing, 0, PBEMoveTarget.Self, PBEMoveFlag.BlockedByGravity ) }, { PBEMove.Spore, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Status, 0, 3, 0, 100, PBEMoveEffect.Sleep, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.StealthRock, new PBEDDMoveData ( PBEType.Rock, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.StealthRock, 0, PBEMoveTarget.AllFoes, PBEMoveFlag.AffectedByMagicCoat ) }, { PBEMove.Steamroller, new PBEDDMoveData ( PBEType.Bug, PBEMoveCategory.Physical, 0, 4, 65, 100, PBEMoveEffect.Hit__MaybeFlinch, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.DoubleDamageMinimized | PBEMoveFlag.MakesContact ) }, { PBEMove.SteelWing, new PBEDDMoveData ( PBEType.Steel, PBEMoveCategory.Physical, 0, 5, 70, 90, PBEMoveEffect.Hit__MaybeRaiseUser_DEF_By1, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Stockpile, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.Stomp, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 4, 65, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.DoubleDamageMinimized | PBEMoveFlag.MakesContact ) }, { PBEMove.StoneEdge, new PBEDDMoveData ( PBEType.Rock, PBEMoveCategory.Physical, 0, 1, 100, 80, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.HighCritChance ) }, { PBEMove.StoredPower, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Special, 0, 2, 20, 100, PBEMoveEffect.StoredPower, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.StormThrow, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, 0, 2, 40, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AlwaysCrit | PBEMoveFlag.MakesContact ) }, { PBEMove.Strength, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 3, 80, 90, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.StringShot, new PBEDDMoveData ( PBEType.Bug, PBEMoveCategory.Status, 0, 8, 0, 95, PBEMoveEffect.ChangeTarget_SPE, -1, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Struggle, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 0, 50, 0, PBEMoveEffect.Struggle, 4, PBEMoveTarget.RandomFoeSurrounding, PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMeFirst | PBEMoveFlag.BlockedFromMetronome | PBEMoveFlag.BlockedFromMimic | PBEMoveFlag.BlockedFromSketch | PBEMoveFlag.MakesContact | PBEMoveFlag.UnaffectedByGems ) }, { PBEMove.StruggleBug, new PBEDDMoveData ( PBEType.Bug, PBEMoveCategory.Special, 0, 4, 30, 100, PBEMoveEffect.Hit__MaybeLowerTarget_SPATK_By1, 100, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.StunSpore, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Status, 0, 6, 0, 75, PBEMoveEffect.Paralyze, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Submission, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, 0, 5, 80, 80, PBEMoveEffect.Recoil, 4, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedByReckless | PBEMoveFlag.MakesContact ) }, { PBEMove.Substitute, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.Substitute, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.SuckerPunch, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Physical, +1, 1, 80, 100, PBEMoveEffect.SuckerPunch, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.SunnyDay, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Status, 0, 1, 0, 0, PBEMoveEffect.SunnyDay, 0, PBEMoveTarget.All, PBEMoveFlag.None ) }, { PBEMove.SuperFang, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 2, 0, 90, PBEMoveEffect.SuperFang, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Superpower, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, 0, 1, 120, 100, PBEMoveEffect.Hit__MaybeLowerUser_ATK_DEF_By1, 100, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Supersonic, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 4, 0, 55, PBEMoveEffect.Confuse, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedBySoundproof ) }, { PBEMove.Surf, new PBEDDMoveData ( PBEType.Water, PBEMoveCategory.Special, 0, 3, 95, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.AllSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.DoubleDamageUnderwater ) }, { PBEMove.Swagger, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 3, 0, 90, PBEMoveEffect.Swagger, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Swallow, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.SweetKiss, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 2, 0, 75, PBEMoveEffect.Confuse, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.SweetScent, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 5, 0, 100, PBEMoveEffect.ChangeTarget_EVA, -1, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Swift, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Special, 0, 4, 60, 0, PBEMoveEffect.Hit, 0, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Switcheroo, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Status, 0, 2, 0, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMetronome ) }, { PBEMove.SwordsDance, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 6, 0, 0, PBEMoveEffect.ChangeTarget_ATK, +2, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.Synchronoise, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Special, 0, 3, 70, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.AllSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Synthesis, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Status, 0, 1, 0, 0, PBEMoveEffect.Moonlight, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.Tackle, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 7, 50, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.TailGlow, new PBEDDMoveData ( PBEType.Bug, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.ChangeTarget_SPATK, +3, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.TailSlap, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 2, 25, 85, PBEMoveEffect.Hit__2To5Times, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.TailWhip, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 6, 0, 100, PBEMoveEffect.ChangeTarget_DEF, -1, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Tailwind, new PBEDDMoveData ( PBEType.Flying, PBEMoveCategory.Status, 0, 6, 0, 0, PBEMoveEffect.Tailwind, 0, PBEMoveTarget.AllTeam, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.TakeDown, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 4, 90, 85, PBEMoveEffect.Recoil, 4, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedByReckless | PBEMoveFlag.MakesContact ) }, { PBEMove.Taunt, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Status, 0, 4, 0, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.TechnoBlast, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Special, 0, 1, 85, 100, PBEMoveEffect.TechnoBlast, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedFromMetronome ) }, { PBEMove.TeeterDance, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 4, 0, 100, PBEMoveEffect.Confuse, 0, PBEMoveTarget.AllSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Telekinesis, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 3, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedByGravity ) }, { PBEMove.Teleport, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.Teleport, 0, PBEMoveTarget.Self, PBEMoveFlag.None ) }, { PBEMove.Thief, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Physical, 0, 2, 40, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMeFirst | PBEMoveFlag.BlockedFromMetronome | PBEMoveFlag.MakesContact ) }, { PBEMove.Thrash, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 2, 120, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.RandomFoeSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Thunder, new PBEDDMoveData ( PBEType.Electric, PBEMoveCategory.Special, 0, 2, 120, 70, PBEMoveEffect.Hit__MaybeParalyze, 30, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.HitsAirborne | PBEMoveFlag.NeverMissRain ) }, { PBEMove.Thunderbolt, new PBEDDMoveData ( PBEType.Electric, PBEMoveCategory.Special, 0, 3, 95, 100, PBEMoveEffect.Hit__MaybeParalyze, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.ThunderFang, new PBEDDMoveData ( PBEType.Electric, PBEMoveCategory.Physical, 0, 3, 65, 95, PBEMoveEffect.Hit__MaybeParalyze__10PercentFlinch, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.ThunderPunch, new PBEDDMoveData ( PBEType.Electric, PBEMoveCategory.Physical, 0, 3, 75, 100, PBEMoveEffect.Hit__MaybeParalyze, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByIronFist | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.ThunderShock, new PBEDDMoveData ( PBEType.Electric, PBEMoveCategory.Special, 0, 6, 40, 100, PBEMoveEffect.Hit__MaybeParalyze, 10, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.ThunderWave, new PBEDDMoveData ( PBEType.Electric, PBEMoveCategory.Status, 0, 4, 0, 100, PBEMoveEffect.ThunderWave, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Tickle, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 4, 0, 100, PBEMoveEffect.LowerTarget_ATK_DEF_By1, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Torment, new PBEDDMoveData ( PBEType.Dark, PBEMoveCategory.Status, 0, 3, 0, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Toxic, new PBEDDMoveData ( PBEType.Poison, PBEMoveCategory.Status, 0, 2, 0, 90, PBEMoveEffect.Toxic, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.ToxicSpikes, new PBEDDMoveData ( PBEType.Poison, PBEMoveCategory.Status, 0, 4, 0, 0, PBEMoveEffect.ToxicSpikes, 0, PBEMoveTarget.AllFoes, PBEMoveFlag.AffectedByMagicCoat ) }, { PBEMove.Transform, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.Transform, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMetronome | PBEMoveFlag.BlockedFromMimic | PBEMoveFlag.BlockedFromSketchWhenSuccessful ) }, { PBEMove.TriAttack, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Special, 0, 2, 80, 100, PBEMoveEffect.Hit__MaybeBurnFreezeParalyze, 20, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Trick, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, 0, 2, 0, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedFromAssist | PBEMoveFlag.BlockedFromCopycat | PBEMoveFlag.BlockedFromMetronome ) }, { PBEMove.TrickRoom, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, -7, 1, 0, 0, PBEMoveEffect.TrickRoom, 0, PBEMoveTarget.All, PBEMoveFlag.AffectedByMirrorMove ) }, { PBEMove.TripleKick, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, 0, 2, 10, 90, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.TrumpCard, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Special, 0, 1, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Twineedle, new PBEDDMoveData ( PBEType.Bug, PBEMoveCategory.Physical, 0, 4, 25, 100, PBEMoveEffect.Hit__2Times__MaybePoison, 20, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Twister, new PBEDDMoveData ( PBEType.Dragon, PBEMoveCategory.Special, 0, 4, 40, 100, PBEMoveEffect.Hit__MaybeFlinch, 20, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.DoubleDamageAirborne ) }, { PBEMove.Uproar, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Special, 0, 2, 90, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.RandomFoeSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedBySoundproof | PBEMoveFlag.BlockedFromSleepTalk ) }, { PBEMove.Uturn, new PBEDDMoveData ( PBEType.Bug, PBEMoveCategory.Physical, 0, 4, 70, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.VacuumWave, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Special, +1, 6, 40, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.VCreate, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Physical, 0, 1, 180, 95, PBEMoveEffect.Hit__MaybeLowerUser_SPE_DEF_SPDEF_By1, 100, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.BlockedFromMetronome | PBEMoveFlag.MakesContact ) }, { PBEMove.Venoshock, new PBEDDMoveData ( PBEType.Poison, PBEMoveCategory.Special, 0, 2, 65, 100, PBEMoveEffect.Venoshock, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.ViceGrip, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 6, 55, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.VineWhip, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Physical, 0, 3, 35, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.VitalThrow, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, -1, 2, 70, 0, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.VoltSwitch, new PBEDDMoveData ( PBEType.Electric, PBEMoveCategory.Special, 0, 4, 70, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.VoltTackle, new PBEDDMoveData ( PBEType.Electric, PBEMoveCategory.Physical, 0, 3, 120, 100, PBEMoveEffect.Recoil__10PercentParalyze, 3, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedByReckless | PBEMoveFlag.MakesContact ) }, { PBEMove.WakeUpSlap, new PBEDDMoveData ( PBEType.Fighting, PBEMoveCategory.Physical, 0, 2, 60, 100, PBEMoveEffect.WakeUpSlap, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Waterfall, new PBEDDMoveData ( PBEType.Water, PBEMoveCategory.Physical, 0, 3, 80, 100, PBEMoveEffect.Hit__MaybeFlinch, 20, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.WaterGun, new PBEDDMoveData ( PBEType.Water, PBEMoveCategory.Special, 0, 5, 40, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.WaterPledge, new PBEDDMoveData ( PBEType.Water, PBEMoveCategory.Special, 0, 2, 50, 100, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.UnaffectedByGems ) }, { PBEMove.WaterPulse, new PBEDDMoveData ( PBEType.Water, PBEMoveCategory.Special, 0, 4, 60, 100, PBEMoveEffect.Hit__MaybeConfuse, 20, PBEMoveTarget.SingleNotSelf, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.WaterSport, new PBEDDMoveData ( PBEType.Water, PBEMoveCategory.Status, 0, 3, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.All, PBEMoveFlag.None ) }, { PBEMove.WaterSpout, new PBEDDMoveData ( PBEType.Water, PBEMoveCategory.Special, 0, 1, 150, 100, PBEMoveEffect.Eruption, 0, PBEMoveTarget.AllFoesSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.WeatherBall, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Special, 0, 2, 50, 100, PBEMoveEffect.WeatherBall, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Whirlpool, new PBEDDMoveData ( PBEType.Water, PBEMoveCategory.Special, 0, 3, 35, 85, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.DoubleDamageUnderwater ) }, { PBEMove.Whirlwind, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, -6, 4, 0, 100, PBEMoveEffect.Whirlwind, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.WideGuard, new PBEDDMoveData ( PBEType.Rock, PBEMoveCategory.Status, +3, 2, 0, 0, PBEMoveEffect.WideGuard, 0, PBEMoveTarget.AllTeam, PBEMoveFlag.AffectedBySnatch | PBEMoveFlag.BlockedFromMetronome ) }, { PBEMove.WildCharge, new PBEDDMoveData ( PBEType.Electric, PBEMoveCategory.Physical, 0, 3, 90, 100, PBEMoveEffect.Recoil, 4, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedByReckless | PBEMoveFlag.MakesContact ) }, { PBEMove.WillOWisp, new PBEDDMoveData ( PBEType.Fire, PBEMoveCategory.Status, 0, 3, 0, 75, PBEMoveEffect.Burn, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.WingAttack, new PBEDDMoveData ( PBEType.Flying, PBEMoveCategory.Physical, 0, 7, 60, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleNotSelf, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Wish, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.Withdraw, new PBEDDMoveData ( PBEType.Water, PBEMoveCategory.Status, 0, 8, 0, 0, PBEMoveEffect.ChangeTarget_DEF, +1, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.WonderRoom, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Status, -7, 2, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.All, PBEMoveFlag.AffectedByMirrorMove ) }, { PBEMove.WoodHammer, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Physical, 0, 3, 120, 100, PBEMoveEffect.Recoil, 3, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.AffectedByReckless | PBEMoveFlag.MakesContact ) }, { PBEMove.WorkUp, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 6, 0, 0, PBEMoveEffect.RaiseTarget_ATK_SPATK_By1, 0, PBEMoveTarget.Self, PBEMoveFlag.AffectedBySnatch ) }, { PBEMove.WorrySeed, new PBEDDMoveData ( PBEType.Grass, PBEMoveCategory.Status, 0, 2, 0, 100, PBEMoveEffect.WorrySeed, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.Wrap, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Physical, 0, 4, 15, 90, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.WringOut, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Special, 0, 1, 120, 100, PBEMoveEffect.CrushGrip, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.XScissor, new PBEDDMoveData ( PBEType.Bug, PBEMoveCategory.Physical, 0, 3, 80, 100, PBEMoveEffect.Hit, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) }, { PBEMove.Yawn, new PBEDDMoveData ( PBEType.Normal, PBEMoveCategory.Status, 0, 2, 0, 0, PBEMoveEffect.TODOMOVE, 0, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMagicCoat | PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.ZapCannon, new PBEDDMoveData ( PBEType.Electric, PBEMoveCategory.Special, 0, 1, 120, 50, PBEMoveEffect.Hit__MaybeParalyze, 100, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect ) }, { PBEMove.ZenHeadbutt, new PBEDDMoveData ( PBEType.Psychic, PBEMoveCategory.Physical, 0, 3, 80, 90, PBEMoveEffect.Hit__MaybeFlinch, 20, PBEMoveTarget.SingleSurrounding, PBEMoveFlag.AffectedByMirrorMove | PBEMoveFlag.AffectedByProtect | PBEMoveFlag.MakesContact ) } }); } ================================================ FILE: PokemonBattleEngine.DefaultData/Data/PokemonData.cs ================================================ using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngine.Data.Utils; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; namespace Kermalis.PokemonBattleEngine.DefaultData.Data; public sealed class PBEDDPokemonData : IPBEDDPokemonDataExtended { public PBESpecies Species { get; } public PBEForm Form { get; } public PBEReadOnlyStatCollection BaseStats { get; } IPBEReadOnlyStatCollection IPBEPokemonData.BaseStats => BaseStats; public PBEType Type1 { get; } public PBEType Type2 { get; } public PBEGenderRatio GenderRatio { get; } public PBEGrowthRate GrowthRate { get; } public ushort BaseEXPYield { get; } public byte CatchRate { get; } public byte FleeRate { get; } /// Weight in Kilograms public float Weight { get; } public ReadOnlyCollection Abilities { get; } IReadOnlyList IPBEPokemonData.Abilities => Abilities; public ReadOnlyCollection<(PBESpecies Species, PBEForm Form)> PreEvolutions { get; } IReadOnlyList<(PBESpecies Species, PBEForm Form)> IPBEDDPokemonDataExtended.PreEvolutions => PreEvolutions; public ReadOnlyCollection<(PBESpecies Species, PBEForm Form)> Evolutions { get; } IReadOnlyList<(PBESpecies Species, PBEForm Form)> IPBEDDPokemonDataExtended.Evolutions => Evolutions; public ReadOnlyCollection<(PBEMove Move, byte Level, PBEDDMoveObtainMethod ObtainMethod)> LevelUpMoves { get; } IReadOnlyList<(PBEMove Move, byte Level, PBEDDMoveObtainMethod ObtainMethod)> IPBEDDPokemonDataExtended.LevelUpMoves => LevelUpMoves; public ReadOnlyCollection<(PBEMove Move, PBEDDMoveObtainMethod ObtainMethod)> OtherMoves { get; } IReadOnlyList<(PBEMove Move, PBEDDMoveObtainMethod ObtainMethod)> IPBEDDPokemonDataExtended.OtherMoves => OtherMoves; private PBEDDPokemonData(SearchResult result) { BaseStats = new PBEReadOnlyStatCollection(result); Type1 = (PBEType)result.Type1; Type2 = (PBEType)result.Type2; GenderRatio = (PBEGenderRatio)result.GenderRatio; GrowthRate = (PBEGrowthRate)result.GrowthRate; BaseEXPYield = result.BaseEXPYield; CatchRate = result.CatchRate; FleeRate = result.FleeRate; Weight = result.Weight; const char Split1Char = '+'; const char Split2Char = '|'; string[] split1 = result.PreEvolutions.Split(Split1Char, StringSplitOptions.RemoveEmptyEntries); var preEvolutions = new (PBESpecies, PBEForm)[split1.Length]; for (int i = 0; i < preEvolutions.Length; i++) { string[] split2 = split1[i].Split(Split2Char); preEvolutions[i] = (Enum.Parse(split2[0]), Enum.Parse(split2[1])); } PreEvolutions = new ReadOnlyCollection<(PBESpecies, PBEForm)>(preEvolutions); split1 = result.Evolutions.Split(Split1Char, StringSplitOptions.RemoveEmptyEntries); var evolutions = new (PBESpecies, PBEForm)[split1.Length]; for (int i = 0; i < evolutions.Length; i++) { string[] split2 = split1[i].Split(Split2Char); evolutions[i] = (Enum.Parse(split2[0]), Enum.Parse(split2[1])); } Evolutions = new ReadOnlyCollection<(PBESpecies, PBEForm)>(evolutions); split1 = result.Abilities.Split(Split1Char, StringSplitOptions.RemoveEmptyEntries); var abilities = new PBEAbility[split1.Length]; for (int i = 0; i < abilities.Length; i++) { abilities[i] = Enum.Parse(split1[i]); } Abilities = new ReadOnlyCollection(abilities); split1 = result.LevelUpMoves.Split(Split1Char, StringSplitOptions.RemoveEmptyEntries); var levelUpMoves = new (PBEMove, byte, PBEDDMoveObtainMethod)[split1.Length]; for (int i = 0; i < levelUpMoves.Length; i++) { string[] split2 = split1[i].Split(Split2Char); levelUpMoves[i] = (Enum.Parse(split2[0]), byte.Parse(split2[1]), Enum.Parse(split2[2])); } LevelUpMoves = new ReadOnlyCollection<(PBEMove, byte, PBEDDMoveObtainMethod)>(levelUpMoves); split1 = result.OtherMoves.Split(Split1Char, StringSplitOptions.RemoveEmptyEntries); var otherMoves = new (PBEMove, PBEDDMoveObtainMethod)[split1.Length]; for (int i = 0; i < otherMoves.Length; i++) { string[] split2 = split1[i].Split(Split2Char); otherMoves[i] = (Enum.Parse(split2[0]), Enum.Parse(split2[1])); } OtherMoves = new ReadOnlyCollection<(PBEMove, PBEDDMoveObtainMethod)>(otherMoves); } #region Database Querying #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. private class SearchResult : IPBEStatCollection { public string Species { get; set; } public string Form { get; set; } public byte HP { get; set; } public byte Attack { get; set; } public byte Defense { get; set; } public byte SpAttack { get; set; } public byte SpDefense { get; set; } public byte Speed { get; set; } public byte Type1 { get; set; } public byte Type2 { get; set; } public byte GenderRatio { get; set; } public byte GrowthRate { get; set; } public ushort BaseEXPYield { get; set; } public byte CatchRate { get; set; } public byte FleeRate { get; set; } public float Weight { get; set; } public string PreEvolutions { get; set; } public string Evolutions { get; set; } public string Abilities { get; set; } public string LevelUpMoves { get; set; } public string OtherMoves { get; set; } } #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. #pragma warning disable IDE0060 // Remove unused parameter public static PBEDDPokemonData GetData(PBESpecies species, PBEForm form, bool cache = true) #pragma warning restore IDE0060 // Remove unused parameter { PBEDataUtils.ValidateSpecies(species, form, false); List results = PBEDefaultDataProvider.Instance.QueryDatabase($"SELECT * FROM PokemonData WHERE Species='{species}' AND Form='{PBEDataUtils.GetNameOfForm(species, form) ?? "0"}'"); if (results.Count == 1) { return new PBEDDPokemonData(results[0]); } throw new InvalidDataException(); } #endregion } ================================================ FILE: PokemonBattleEngine.DefaultData/DefaultDataProvider.cs ================================================ using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngine.Data.Utils; using Kermalis.PokemonBattleEngine.DefaultData.Data; using Kermalis.PokemonBattleEngine.Utils; using Microsoft.Data.Sqlite; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Reflection; namespace Kermalis.PokemonBattleEngine.DefaultData; public class PBEDefaultDataProvider : PBEDataProvider { public static new PBEDefaultDataProvider Instance => (PBEDefaultDataProvider)PBEDataProvider.Instance; private readonly object _databaseConnectLockObj = new(); private readonly SqliteConnection _databaseConnection; protected PBEDefaultDataProvider(string databasePath, PBELanguage language, PBERandom rand) : base(language, rand) { SQLitePCL.Batteries_V2.Init(); _databaseConnection = new SqliteConnection($"Filename={Path.Combine(databasePath, "PokemonBattleEngine.db")};Mode=ReadOnly;"); _databaseConnection.Open(); _databaseConnection.CreateFunction("StrCmp", (Func)StrCmp); } public static void InitEngine(string databasePath, int? randomSeed = null) { var cultureInfo = CultureInfo.ReadOnly(CultureInfo.CurrentUICulture); if (!cultureInfo.ToPBELanguage(out PBELanguage? lang)) { lang = PBELanguage.English; } _ = new PBEDefaultDataProvider(databasePath, lang.Value, new PBERandom(randomSeed)); } public static void InitEngine(string databasePath, PBELanguage language, int? randomSeed = null) { if (language >= PBELanguage.MAX) { throw new ArgumentOutOfRangeException(nameof(language)); } _ = new PBEDefaultDataProvider(databasePath, language, new PBERandom(randomSeed)); } private static bool StrCmp(object arg0, object arg1) { if (Convert.IsDBNull(arg0) || Convert.IsDBNull(arg1)) { return false; } return string.Equals(Convert.ToString(arg0), Convert.ToString(arg1), StringComparison.InvariantCultureIgnoreCase); } // TODO: Keep this internal version and make a public version that only allows operations that retrieve data? internal List QueryDatabase(string commandText) where T : new() { var list = new List(); Type type = typeof(T); lock (_databaseConnectLockObj) { using (SqliteCommand cmd = _databaseConnection.CreateCommand()) { cmd.CommandText = commandText; using (SqliteDataReader r = cmd.ExecuteReader()) { while (r.Read()) { T obj = Activator.CreateInstance(); for (int i = 0; i < r.FieldCount; i++) { PropertyInfo? property = type.GetProperty(r.GetName(i)); property?.SetValue(obj, Convert.ChangeType(r.GetValue(i), property.PropertyType)); } list.Add(obj); } } } } return list; } #region Data public override bool IsBerry(PBEItem item) { return PBEDDBerryData.Data.ContainsKey(item); } public override IPBEBerryData GetBerryData(PBEItem item, bool cache = true) { return PBEDDBerryData.Data[item]; } public override bool TryGetBerryData(PBEItem item, [NotNullWhen(true)] out IPBEBerryData? bData, bool cache = true) { if (IsBerry(item)) { bData = GetBerryData(item, cache: cache); return true; } bData = null; return false; } public override IPBEItemData GetItemData(PBEItem item, bool cache = true) { return PBEDDItemData.Data[item]; } public override IPBEMoveData GetMoveData(PBEMove move, bool cache = true) { return PBEDDMoveData.Data[move]; } public override bool HasEvolutions(PBESpecies species, PBEForm form, bool cache = true) { return PBEDDPokemonData.GetData(species, form, cache: cache).HasEvolutions(); } public override IPBEPokemonData GetPokemonData(PBESpecies species, PBEForm form, bool cache = true) { return PBEDDPokemonData.GetData(species, form, cache: cache); } public virtual IPBEDDPokemonDataExtended GetPokemonDataExtended(PBESpecies species, PBEForm form, bool cache = true) { return PBEDDPokemonData.GetData(species, form, cache: cache); } public override int GetSpeciesCaught() { return 300; } public override IReadOnlyCollection GetLegalMoves(PBESpecies species, PBEForm form, byte level) { return PBEDDLegalityChecker.GetLegalMoves(species, form, level); } public virtual IPBEDDPokemonDataExtended GetPokemonDataExtended(IPBESpeciesForm pkmn, bool cache = true) { return GetPokemonDataExtended(pkmn.Species, pkmn.Form, cache: cache); } #endregion #region EXP public override uint GetEXPRequired(PBEGrowthRate type, byte level) { return PBEDDEXPTables.GetEXPRequired(type, level); } public override byte GetEXPLevel(PBEGrowthRate type, uint exp) { return PBEDDEXPTables.GetEXPLevel(type, exp); } public override float GetEXPModifier(PBEBattle battle) { return 1; } public override float GetEXPTradeModifier(PBEBattlePokemon pkmn) { return 1; } #endregion #region Catching public override bool IsDarkGrass(PBEBattle battle) { return false; } public override bool IsDuskBallSetting(PBEBattle battle) { return battle.BattleTerrain == PBEBattleTerrain.Cave; } public override bool IsFishing(PBEBattle battle) { return false; } public override bool IsGuaranteedCapture(PBEBattle battle, IPBESpeciesForm pkmn) { return IsGuaranteedCapture(battle, pkmn.Species, pkmn.Form); } public override bool IsGuaranteedCapture(PBEBattle battle, PBESpecies species, PBEForm form) { return false; } public override bool IsMoonBallFamily(IPBESpeciesForm pkmn) { return IsMoonBallFamily(pkmn.Species, pkmn.Form); } public override bool IsMoonBallFamily(PBESpecies species, PBEForm form) { return PBEDataUtils.MoonStoneSpecies.Contains(species); } public override bool IsRepeatBallSpecies(PBESpecies species) { return false; } public override bool IsSurfing(PBEBattle battle) { return battle.BattleTerrain == PBEBattleTerrain.Water; } public override bool IsUnderwater(PBEBattle battle) { return false; } public override float GetCatchRateModifier(PBEBattle battle) { return 1; } #endregion #region LocalizedString public override bool GetAbilityByName(string abilityName, [NotNullWhen(true)] out PBEAbility? ability) { return PBEDDLocalizedString.GetAbilityByName(abilityName, out ability); } public virtual IPBEReadOnlyLocalizedString GetAbilityDescription(PBEAbility ability) { return PBEDDLocalizedString.GetAbilityDescription(ability); } public override IPBEReadOnlyLocalizedString GetAbilityName(PBEAbility ability) { return PBEDDLocalizedString.GetAbilityName(ability); } public override bool GetFormByName(PBESpecies species, string formName, [NotNullWhen(true)] out PBEForm? form) { return PBEDDLocalizedString.GetFormByName(species, formName, out form); } public override IPBEReadOnlyLocalizedString GetFormName(PBESpecies species, PBEForm form) { return PBEDDLocalizedString.GetFormName(species, form); } public override bool GetGenderByName(string genderName, [NotNullWhen(true)] out PBEGender? gender) { return PBEDDLocalizedString.GetGenderByName(genderName, out gender); } public override IPBEReadOnlyLocalizedString GetGenderName(PBEGender gender) { return PBEDDLocalizedString.GetGenderName(gender); } public override bool GetItemByName(string itemName, [NotNullWhen(true)] out PBEItem? item) { return PBEDDLocalizedString.GetItemByName(itemName, out item); } public virtual IPBEReadOnlyLocalizedString GetItemDescription(PBEItem item) { return PBEDDLocalizedString.GetItemDescription(item); } public override IPBEReadOnlyLocalizedString GetItemName(PBEItem item) { return PBEDDLocalizedString.GetItemName(item); } public override bool GetMoveByName(string moveName, [NotNullWhen(true)] out PBEMove? move) { return PBEDDLocalizedString.GetMoveByName(moveName, out move); } public virtual IPBEReadOnlyLocalizedString GetMoveDescription(PBEMove move) { return PBEDDLocalizedString.GetMoveDescription(move); } public override IPBEReadOnlyLocalizedString GetMoveName(PBEMove move) { return PBEDDLocalizedString.GetMoveName(move); } public override bool GetNatureByName(string natureName, [NotNullWhen(true)] out PBENature? nature) { return PBEDDLocalizedString.GetNatureByName(natureName, out nature); } public override IPBEReadOnlyLocalizedString GetNatureName(PBENature nature) { return PBEDDLocalizedString.GetNatureName(nature); } public override bool GetSpeciesByName(string speciesName, [NotNullWhen(true)] out PBESpecies? species) { return PBEDDLocalizedString.GetSpeciesByName(speciesName, out species); } public virtual IPBEReadOnlyLocalizedString GetSpeciesCategory(PBESpecies species) { return PBEDDLocalizedString.GetSpeciesCategory(species); } public virtual IPBEReadOnlyLocalizedString GetSpeciesEntry(PBESpecies species) { return PBEDDLocalizedString.GetSpeciesEntry(species); } public override IPBEReadOnlyLocalizedString GetSpeciesName(PBESpecies species) { return PBEDDLocalizedString.GetSpeciesName(species); } public override bool GetStatByName(string statName, [NotNullWhen(true)] out PBEStat? stat) { return PBEDDLocalizedString.GetStatByName(statName, out stat); } public override IPBEReadOnlyLocalizedString GetStatName(PBEStat stat) { return PBEDDLocalizedString.GetStatName(stat); } public override bool GetTypeByName(string typeName, [NotNullWhen(true)] out PBEType? type) { return PBEDDLocalizedString.GetTypeByName(typeName, out type); } public override IPBEReadOnlyLocalizedString GetTypeName(PBEType type) { return PBEDDLocalizedString.GetTypeName(type); } #endregion } ================================================ FILE: PokemonBattleEngine.DefaultData/Enums.cs ================================================ using System; namespace Kermalis.PokemonBattleEngine.DefaultData; [Flags] public enum PBEDDMoveObtainMethod : ulong { /// There is no way to learn this move. None, /// The move can be learned by levelling up a Pokémon in Pokémon Ruby Version and Pokémon Sapphire Version. LevelUp_RSColoXD = 1uL << 0, /// The move can be learned by levelling up a Pokémon in Pokémon Fire Red Version. LevelUp_FR = 1uL << 1, /// The move can be learned by levelling up a Pokémon in Pokémon Leaf Green Version. LevelUp_LG = 1uL << 2, /// The move can be learned by levelling up a Pokémon in Pokémon Emerald Version. LevelUp_E = 1uL << 3, /// The move can be learned by levelling up a Pokémon in Pokémon Diamond Version and Pokémon Pearl Version. LevelUp_DP = 1uL << 4, /// The move can be learned by levelling up a Pokémon in Pokémon Platinum Version. LevelUp_Pt = 1uL << 5, /// The move can be learned by levelling up a Pokémon in Pokémon HeartGold Version and Pokémon SoulSilver Version. LevelUp_HGSS = 1uL << 6, /// The move can be learned by levelling up a Pokémon in Pokémon Black Version and Pokémon White Version. LevelUp_BW = 1uL << 7, /// The move can be learned by levelling up a Pokémon in Pokémon Black Version 2 and Pokémon White Version 2. LevelUp_B2W2 = 1uL << 8, /// The move can be learned by using a technical machine on a Pokémon in Pokémon Ruby Version, Pokémon Sapphire Version, Pokémon Fire Red Version, Pokémon Leaf Green Version, Pokémon Emerald Version, Pokémon Colosseum, and Pokémon XD: Gale of Darkness. TM_RSFRLGEColoXD = 1uL << 9, /// The move can be learned by using a technical machine on a Pokémon in Pokémon Diamond Version, Pokémon Pearl Version, and Pokémon Platinum Version. TM_DPPt = 1uL << 10, /// The move can be learned by using a technical machine on a Pokémon in Pokémon HeartGold Version and Pokémon SoulSilver Version. TM_HGSS = 1uL << 11, /// The move can be learned by using a technical machine on a Pokémon in Pokémon Black Version and Pokémon White Version. TM_BW = 1uL << 12, /// The move can be learned by using a technical machine on a Pokémon in Pokémon Black Version 2 and Pokémon White Version 2. TM_B2W2 = 1uL << 13, /// The move can be learned by using a hidden machine on a Pokémon in Pokémon Ruby Version, Pokémon Sapphire Version, Pokémon Fire Red Version, Pokémon Leaf Green Version, Pokémon Emerald Version, Pokémon Colosseum, and Pokémon XD: Gale of Darkness. HM_RSFRLGEColoXD = 1uL << 14, /// The move can be learned by using a hidden machine on a Pokémon in Pokémon Diamond Version, Pokémon Pearl Version, and Pokémon Platinum Version. HM_DPPt = 1uL << 15, /// The move can be learned by using a hidden machine on a Pokémon in Pokémon HeartGold Version and Pokémon SoulSilver Version. HM_HGSS = 1uL << 16, /// The move can be learned by using a hidden machine on a Pokémon in Pokémon Black Version, Pokémon White Version, Pokémon Black Version 2, and Pokémon White Version 2. HM_BWB2W2 = 1uL << 17, /// The move can be taught to a Pokémon by a move tutor in Pokémon Fire Red Version and Pokémon Leaf Green Version. MoveTutor_FRLG = 1uL << 18, /// The move can be taught to a Pokémon by a move tutor in Pokémon Emerald Version. MoveTutor_E = 1uL << 19, /// The move can be taught to a Pokémon by a move tutor in Pokémon XD: Gale of Darkness. MoveTutor_XD = 1uL << 20, /// The move can be taught to a Pokémon by a move tutor in Pokémon Diamond Version and Pokémon Pearl Version. MoveTutor_DP = 1uL << 21, /// The move can be taught to a Pokémon by a move tutor in Pokémon Platinum Version. MoveTutor_Pt = 1uL << 22, /// The move can be taught to a Pokémon by a move tutor in Pokémon HeartGold Version and Pokémon SoulSilver Version. MoveTutor_HGSS = 1uL << 23, /// The move can be taught to a Pokémon by a move tutor in Pokémon Black Version and Pokémon White Version. MoveTutor_BW = 1uL << 24, /// The move can be taught to a Pokémon by a move tutor in Pokémon Black Version 2 and Pokémon White Version 2. MoveTutor_B2W2 = 1uL << 25, /// The move can be learned by hatching a Pokémon egg in Pokémon Ruby Version, Pokémon Sapphire Version, Pokémon Fire Red Version, Pokémon Leaf Green Version, and Pokémon Emerald Version. EggMove_RSFRLGE = 1uL << 26, /// The move can be learned by hatching a Pokémon egg in Pokémon Diamond Version, Pokémon Pearl Version, and Pokémon Platinum Version. EggMove_DPPt = 1uL << 27, /// The move can be learned by hatching a Pokémon egg in Pokémon HeartGold Version and Pokémon SoulSilver Version. EggMove_HGSS = 1uL << 28, /// The move can be learned by hatching a Pokémon egg in Pokémon Black Version, Pokémon White Version, Pokémon Black Version 2, and Pokémon White Version 2. EggMove_BWB2W2 = 1uL << 29, /// The move is known by a Pokémon when found in the Dream World with Pokémon Black Version and Pokémon White Version. DreamWorld_BW = 1uL << 30, /// The move is known by a Pokémon when found in the Dream World with Pokémon Black Version 2 and Pokémon White Version 2. DreamWorld_B2W2 = 1uL << 31, /// The move can be learned by hatching a Pokémon egg under special conditions. EggMove_Special = 1uL << 32, /// The move is learned by a Pokémon when changing forms. The move cannot be used by other forms if this is the only flag or if the species cannot change forms. Form = 1uL << 33 } ================================================ FILE: PokemonBattleEngine.DefaultData/IPokemonDataExtended.cs ================================================ using Kermalis.PokemonBattleEngine.Data; using System.Collections.Generic; namespace Kermalis.PokemonBattleEngine.DefaultData; public interface IPBEDDPokemonDataExtended : IPBEPokemonData { IReadOnlyList<(PBESpecies Species, PBEForm Form)> PreEvolutions { get; } IReadOnlyList<(PBESpecies Species, PBEForm Form)> Evolutions { get; } IReadOnlyList<(PBEMove Move, byte Level, PBEDDMoveObtainMethod ObtainMethod)> LevelUpMoves { get; } IReadOnlyList<(PBEMove Move, PBEDDMoveObtainMethod ObtainMethod)> OtherMoves { get; } } public static class PBEDDPokemonDataExtensions { public static bool HasEvolutions(this IPBEDDPokemonDataExtended pData) { return pData.Evolutions.Count > 0; } } ================================================ FILE: PokemonBattleEngine.DefaultData/LocalizedString.cs ================================================ using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngine.Data.Utils; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; namespace Kermalis.PokemonBattleEngine.DefaultData; public static class PBEDDLocalizedString { #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. private sealed class SearchResult : IPBELocalizedString { public string Id { get; set; } public string English { get; set; } public string French { get; set; } public string German { get; set; } public string Italian { get; set; } public string Japanese_Kana { get; set; } public string Japanese_Kanji { get; set; } public string Korean { get; set; } public string Spanish { get; set; } } private sealed class FormNameSearchResult : IPBELocalizedString { public string Species { get; set; } public string Form { get; set; } public string English { get; set; } public string French { get; set; } public string German { get; set; } public string Italian { get; set; } public string Japanese_Kana { get; set; } public string Japanese_Kanji { get; set; } public string Korean { get; set; } public string Spanish { get; set; } } #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. private const string QueryText = "SELECT * FROM {0} WHERE StrCmp(English,'{1}') OR StrCmp(French,'{1}') OR StrCmp(German,'{1}') OR StrCmp(Italian,'{1}') OR StrCmp(Japanese_Kana,'{1}') OR StrCmp(Japanese_Kanji,'{1}') OR StrCmp(Korean,'{1}') OR StrCmp(Spanish,'{1}')"; private const string QueryId = "SELECT * FROM {0} WHERE Id='{1}'"; private const string QuerySpeciesAndText = "SELECT * FROM {0} WHERE (StrCmp(English,'{1}') OR StrCmp(French,'{1}') OR StrCmp(German,'{1}') OR StrCmp(Italian,'{1}') OR StrCmp(Japanese_Kana,'{1}') OR StrCmp(Japanese_Kanji,'{1}') OR StrCmp(Korean,'{1}') OR StrCmp(Spanish,'{1}')) AND (Species='{2}')"; private const string QuerySpecies = "SELECT * FROM {0} WHERE Species='{1}' AND Form='{2}'"; private static bool GetEnumValue(string value, [NotNullWhen(true)] out TEnum? result) where TEnum : struct, Enum { foreach (TEnum v in Enum.GetValues()) { if (v.ToString().Equals(value, StringComparison.InvariantCultureIgnoreCase)) { result = v; return true; } } result = null; return false; } public static bool GetAbilityByName(string abilityName, [NotNullWhen(true)] out PBEAbility? ability) { List results = PBEDefaultDataProvider.Instance.QueryDatabase(string.Format(QueryText, "AbilityNames", abilityName)); if (results.Count == 1) { ability = Enum.Parse(results[0].Id); } else if (!GetEnumValue(abilityName, out ability) || ability == PBEAbility.MAX) { ability = null; return false; } return true; } public static PBEReadOnlyLocalizedString GetAbilityDescription(PBEAbility ability) { if (ability >= PBEAbility.MAX) { throw new ArgumentOutOfRangeException(nameof(ability)); } List results = PBEDefaultDataProvider.Instance.QueryDatabase(string.Format(QueryId, "AbilityDescriptions", ability)); if (results.Count == 1) { return new PBEReadOnlyLocalizedString(results[0]); } throw new InvalidDataException(); } public static PBEReadOnlyLocalizedString GetAbilityName(PBEAbility ability) { if (ability >= PBEAbility.MAX) { throw new ArgumentOutOfRangeException(nameof(ability)); } List results = PBEDefaultDataProvider.Instance.QueryDatabase(string.Format(QueryId, "AbilityNames", ability)); if (results.Count == 1) { return new PBEReadOnlyLocalizedString(results[0]); } throw new InvalidDataException(); } public static bool GetFormByName(PBESpecies species, string formName, [NotNullWhen(true)] out PBEForm? form) { List results = PBEDefaultDataProvider.Instance.QueryDatabase(string.Format(QuerySpeciesAndText, "FormNames", formName, species)); if (results.Count == 1) { form = Enum.Parse(results[0].Form); } else if (!GetEnumValue(formName, out form)) { form = null; return false; } return true; } public static PBEReadOnlyLocalizedString GetFormName(PBESpecies species, PBEForm form) { PBEDataUtils.ValidateSpecies(species, form, false); List results = PBEDefaultDataProvider.Instance.QueryDatabase(string.Format(QuerySpecies, "FormNames", species, PBEDataUtils.GetNameOfForm(species, form) ?? "0")); if (results.Count == 1) { return new PBEReadOnlyLocalizedString(results[0]); } throw new InvalidDataException(); } public static bool GetGenderByName(string genderName, [NotNullWhen(true)] out PBEGender? gender) { List results = PBEDefaultDataProvider.Instance.QueryDatabase(string.Format(QueryText, "GenderNames", genderName)); if (results.Count == 1) { gender = Enum.Parse(results[0].Id); } else if (!GetEnumValue(genderName, out gender) || gender == PBEGender.MAX) { gender = null; return false; } return true; } public static PBEReadOnlyLocalizedString GetGenderName(PBEGender gender) { if (gender >= PBEGender.MAX) { throw new ArgumentOutOfRangeException(nameof(gender)); } List results = PBEDefaultDataProvider.Instance.QueryDatabase(string.Format(QueryId, "GenderNames", gender)); if (results.Count == 1) { return new PBEReadOnlyLocalizedString(results[0]); } throw new InvalidDataException(); } public static bool GetItemByName(string itemName, [NotNullWhen(true)] out PBEItem? item) { List results = PBEDefaultDataProvider.Instance.QueryDatabase(string.Format(QueryText, "ItemNames", itemName)); if (results.Count == 1) { item = Enum.Parse(results[0].Id); } else if (!GetEnumValue(itemName, out item)) { item = null; return false; } return true; } public static PBEReadOnlyLocalizedString GetItemDescription(PBEItem item) { if (!Enum.IsDefined(item)) { throw new ArgumentOutOfRangeException(nameof(item)); } List results = PBEDefaultDataProvider.Instance.QueryDatabase(string.Format(QueryId, "ItemDescriptions", item)); if (results.Count == 1) { return new PBEReadOnlyLocalizedString(results[0]); } throw new InvalidDataException(); } public static PBEReadOnlyLocalizedString GetItemName(PBEItem item) { if (!Enum.IsDefined(item)) { throw new ArgumentOutOfRangeException(nameof(item)); } List results = PBEDefaultDataProvider.Instance.QueryDatabase(string.Format(QueryId, "ItemNames", item)); if (results.Count == 1) { return new PBEReadOnlyLocalizedString(results[0]); } throw new InvalidDataException(); } public static bool GetMoveByName(string moveName, [NotNullWhen(true)] out PBEMove? move) { List results = PBEDefaultDataProvider.Instance.QueryDatabase(string.Format(QueryText, "MoveNames", moveName)); if (results.Count == 1) { move = Enum.Parse(results[0].Id); } else if (!GetEnumValue(moveName, out move) || move == PBEMove.MAX) { move = null; return false; } return true; } public static PBEReadOnlyLocalizedString GetMoveDescription(PBEMove move) { if (move >= PBEMove.MAX) { throw new ArgumentOutOfRangeException(nameof(move)); } List results = PBEDefaultDataProvider.Instance.QueryDatabase(string.Format(QueryId, "MoveDescriptions", move)); if (results.Count == 1) { return new PBEReadOnlyLocalizedString(results[0]); } throw new InvalidDataException(); } public static PBEReadOnlyLocalizedString GetMoveName(PBEMove move) { if (move >= PBEMove.MAX) { throw new ArgumentOutOfRangeException(nameof(move)); } List results = PBEDefaultDataProvider.Instance.QueryDatabase(string.Format(QueryId, "MoveNames", move)); if (results.Count == 1) { return new PBEReadOnlyLocalizedString(results[0]); } throw new InvalidDataException(); } public static bool GetNatureByName(string natureName, [NotNullWhen(true)] out PBENature? nature) { List results = PBEDefaultDataProvider.Instance.QueryDatabase(string.Format(QueryText, "NatureNames", natureName)); if (results.Count == 1) { nature = Enum.Parse(results[0].Id); } else if (!GetEnumValue(natureName, out nature) || nature == PBENature.MAX) { nature = null; return false; } return true; } public static PBEReadOnlyLocalizedString GetNatureName(PBENature nature) { if (nature >= PBENature.MAX) { throw new ArgumentOutOfRangeException(nameof(nature)); } List results = PBEDefaultDataProvider.Instance.QueryDatabase(string.Format(QueryId, "NatureNames", nature)); if (results.Count == 1) { return new PBEReadOnlyLocalizedString(results[0]); } throw new InvalidDataException(); } public static bool GetSpeciesByName(string speciesName, [NotNullWhen(true)] out PBESpecies? species) { List results = PBEDefaultDataProvider.Instance.QueryDatabase(string.Format(QueryText, "SpeciesNames", speciesName)); if (results.Count == 1) { species = Enum.Parse(results[0].Id); } else if (!GetEnumValue(speciesName, out species)) { species = null; return false; } return true; } public static PBEReadOnlyLocalizedString GetSpeciesCategory(PBESpecies species) { if (species <= 0 || species >= PBESpecies.MAX) { throw new ArgumentOutOfRangeException(nameof(species)); } List results = PBEDefaultDataProvider.Instance.QueryDatabase(string.Format(QueryId, "SpeciesCategories", species)); if (results.Count == 1) { return new PBEReadOnlyLocalizedString(results[0]); } throw new InvalidDataException(); } public static PBEReadOnlyLocalizedString GetSpeciesEntry(PBESpecies species) { if (species <= 0 || species >= PBESpecies.MAX) { throw new ArgumentOutOfRangeException(nameof(species)); } List results = PBEDefaultDataProvider.Instance.QueryDatabase(string.Format(QueryId, "SpeciesEntries", species)); if (results.Count == 1) { return new PBEReadOnlyLocalizedString(results[0]); } throw new InvalidDataException(); } public static PBEReadOnlyLocalizedString GetSpeciesName(PBESpecies species) { if (species <= 0 || species >= PBESpecies.MAX) { throw new ArgumentOutOfRangeException(nameof(species)); } List results = PBEDefaultDataProvider.Instance.QueryDatabase(string.Format(QueryId, "SpeciesNames", species)); if (results.Count == 1) { return new PBEReadOnlyLocalizedString(results[0]); } throw new InvalidDataException(); } public static bool GetStatByName(string statName, [NotNullWhen(true)] out PBEStat? stat) { List results = PBEDefaultDataProvider.Instance.QueryDatabase(string.Format(QueryText, "StatNames", statName)); if (results.Count == 1) { stat = Enum.Parse(results[0].Id); } else if (!GetEnumValue(statName, out stat)) { stat = null; return false; } return true; } public static PBEReadOnlyLocalizedString GetStatName(PBEStat stat) { if (!Enum.IsDefined(stat)) { throw new ArgumentOutOfRangeException(nameof(stat)); } List results = PBEDefaultDataProvider.Instance.QueryDatabase(string.Format(QueryId, "StatNames", stat)); if (results.Count == 1) { return new PBEReadOnlyLocalizedString(results[0]); } throw new InvalidDataException(); } public static bool GetTypeByName(string typeName, [NotNullWhen(true)] out PBEType? type) { List results = PBEDefaultDataProvider.Instance.QueryDatabase(string.Format(QueryText, "TypeNames", typeName)); if (results.Count == 1) { type = Enum.Parse(results[0].Id); } else if (!GetEnumValue(typeName, out type)) { type = null; return false; } return true; } public static PBEReadOnlyLocalizedString GetTypeName(PBEType type) { if (type >= PBEType.MAX) { throw new ArgumentOutOfRangeException(nameof(type)); } List results = PBEDefaultDataProvider.Instance.QueryDatabase(string.Format(QueryId, "TypeNames", type)); if (results.Count == 1) { return new PBEReadOnlyLocalizedString(results[0]); } throw new InvalidDataException(); } } ================================================ FILE: PokemonBattleEngine.DefaultData/PokemonBattleEngine.DefaultData.csproj ================================================  net7.0 Library Kermalis.PokemonBattleEngine.DefaultData Kermalis Kermalis https://github.com/Kermalis/PokemonBattleEngine bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml 1591 enable Always ================================================ FILE: PokemonBattleEngine.DefaultData/RandomTeamGenerator.cs ================================================ // This file is adapted from Pokémon Showdown (MIT License): https://github.com/smogon/pokemon-showdown/blob/master/data/mods/gen5/random-teams.ts // Those guys know what they're doing! using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngine.Data.Legality; using Kermalis.PokemonBattleEngine.Data.Utils; using System; using System.Collections.Generic; using System.Linq; namespace Kermalis.PokemonBattleEngine.DefaultData; public static class PBEDDRandomTeamGenerator { private sealed class TeamDetails { public bool Hail; public bool HarshSunlight; public bool Rain; public bool Sandstorm; public bool StealthRock; public bool ToxicSpikes; public bool RapidSpin; } private sealed class Counter { public int Damage; public int Recovery; public int STAB; public int Inaccurate; public int Priority; public int Recoil; public int Drain; public int Sound; public int PhysicalSetup; public int SpecialSetup; public int MixedSetup; public int SpeedSetup; public int PhysicalPool; public int SpecialPool; public int Hazards; public int DamagingMoves; public char SetupCategory; // 'N', 'M', 'P', 'S' - None, Mixed, Physical, Special private readonly Dictionary _categories = new(); private readonly Dictionary _types = new(); private readonly Dictionary _abilities = new(); public Counter() { for (PBEMoveCategory c = 0; c < PBEMoveCategory.MAX; c++) { _categories.Add(c, 0); } for (PBEType t = PBEType.None + 1; t < PBEType.MAX; t++) { _types.Add(t, 0); } foreach (PBEAbility a in _counterAbilities) { _abilities.Add(a, 0); } } public float this[PBEMoveCategory category] { get => _categories[category]; set => _categories[category] = value; } public int this[PBEType type] { get => _types[type]; set => _types[type] = value; } public int this[PBEAbility ability] { get => _abilities[ability]; set => _abilities[ability] = value; } } private static readonly PBEMove[] _setupExceptionMoves = new[] { PBEMove.CloseCombat, PBEMove.DracoMeteor, PBEMove.ExtremeSpeed, PBEMove.SuckerPunch, PBEMove.Superpower }; private static readonly PBEAbility[] _counterAbilities = new[] { PBEAbility.Adaptability, PBEAbility.Contrary, PBEAbility.Hustle, PBEAbility.IronFist, PBEAbility.SereneGrace, PBEAbility.SheerForce, PBEAbility.SkillLink, PBEAbility.Technician }; private static readonly PBEMove[] _recoveryMoves = new[] { PBEMove.HealOrder, PBEMove.MilkDrink, PBEMove.Moonlight, PBEMove.MorningSun, PBEMove.Recover, PBEMove.Roost, PBEMove.SlackOff, PBEMove.Softboiled, PBEMove.Synthesis }; private static readonly PBEMove[] _contraryMoves = new[] { PBEMove.CloseCombat, PBEMove.LeafStorm, PBEMove.Overheat, PBEMove.Superpower, PBEMove.VCreate }; private static readonly PBEMove[] _physicalSetupMoves = new[] { PBEMove.BellyDrum, PBEMove.BulkUp, PBEMove.Coil, PBEMove.Curse, PBEMove.DragonDance, PBEMove.HoneClaws, PBEMove.Howl, PBEMove.SwordsDance }; private static readonly PBEMove[] _specialSetupMoves = new[] { PBEMove.CalmMind, PBEMove.ChargeBeam, PBEMove.NastyPlot, PBEMove.QuiverDance, PBEMove.TailGlow }; private static readonly PBEMove[] _mixedSetupMoves = new[] { PBEMove.Growth, PBEMove.ShellSmash, PBEMove.WorkUp }; private static readonly PBEMove[] _speedSetupMoves = new[] { PBEMove.Agility, PBEMove.Autotomize, PBEMove.FlameCharge, PBEMove.RockPolish, PBEMove.ShiftGear }; private static readonly PBEMove[] _hazardMoves = new[] { PBEMove.Spikes, PBEMove.StealthRock, PBEMove.ToxicSpikes }; // Moves that shouldn't be the only STAB move private static readonly PBEMove[] _noSTABMoves = new[] { PBEMove.AquaJet, PBEMove.Bounce, PBEMove.Explosion, PBEMove.FakeOut, PBEMove.FlameCharge, PBEMove.IceShard, PBEMove.MachPunch, PBEMove.Pluck, PBEMove.Pursuit, PBEMove.QuickAttack, PBEMove.Selfdestruct, PBEMove.SuckerPunch, PBEMove.ClearSmog, PBEMove.Eruption, PBEMove.IcyWind, PBEMove.Incinerate, PBEMove.Snarl, PBEMove.VacuumWave, PBEMove.WaterSpout }; private static PBEAbility GetAbility(PBESpecies species, List moves, IPBEDDPokemonDataExtended pData, Counter counter, TeamDetails teamDs) { var abilityPool = new List(pData.Abilities); PBEAbility ability; do { bool reject = false; ability = PBEDataProvider.GlobalRandom.RandomElement(abilityPool); // Reasons to reject switch (ability) { case PBEAbility.AngerPoint: case PBEAbility.Gluttony: case PBEAbility.Moody: reject = true; break; case PBEAbility.Blaze: reject = counter[PBEType.Fire] == 0; break; case PBEAbility.Chlorophyll: reject = !teamDs.HarshSunlight && !moves.Contains(PBEMove.SunnyDay); break; case PBEAbility.Compoundeyes: case PBEAbility.NoGuard: reject = counter.Inaccurate == 0; break; case PBEAbility.Defiant: case PBEAbility.Moxie: reject = counter[PBEMoveCategory.Physical] == 0 && !moves.Contains(PBEMove.BatonPass); break; case PBEAbility.Hydration: case PBEAbility.RainDish: case PBEAbility.SwiftSwim: reject = !teamDs.Rain && !moves.Contains(PBEMove.RainDance); break; case PBEAbility.IceBody: case PBEAbility.SnowCloak: reject = !teamDs.Hail && !moves.Contains(PBEMove.Hail); break; // Zangoose case PBEAbility.Immunity: reject = pData.Abilities.Contains(PBEAbility.ToxicBoost); break; case PBEAbility.Lightningrod: reject = pData.HasType(PBEType.Ground); break; // Basculin case PBEAbility.MoldBreaker: reject = pData.Abilities.Contains(PBEAbility.Adaptability); break; case PBEAbility.Overgrow: reject = counter[PBEType.Grass] == 0; break; // Breloom case PBEAbility.PoisonHeal: reject = counter[PBEAbility.Technician] > 0 && pData.Abilities.Contains(PBEAbility.Technician); break; case PBEAbility.Prankster: reject = counter[PBEMoveCategory.Status] == 0; break; case PBEAbility.Reckless: case PBEAbility.RockHead: reject = counter.Recoil == 0; break; // Solosis, Duosion, Reuniclus case PBEAbility.Regenerator: reject = pData.Abilities.Contains(PBEAbility.MagicGuard); break; case PBEAbility.SandForce: case PBEAbility.SandRush: case PBEAbility.SandVeil: reject = !teamDs.Sandstorm && !moves.Contains(PBEMove.Sandstorm); break; case PBEAbility.SereneGrace: reject = species == PBESpecies.Blissey || species == PBESpecies.Togetic; break; // Timburr, Gurdurr, Conkeldurr case PBEAbility.SheerForce: reject = (counter[PBEAbility.IronFist] > counter[PBEAbility.SheerForce] && pData.Abilities.Contains(PBEAbility.IronFist)) || moves.Contains(PBEMove.FakeOut); break; case PBEAbility.Simple: case PBEAbility.WeakArmor: reject = counter.SetupCategory == 'N'; break; case PBEAbility.Sturdy: reject = counter.Recoil > 0 && counter.Recovery == 0; break; case PBEAbility.Swarm: reject = counter[PBEType.Bug] == 0; break; // Ambipom, Minccino, Cinccino case PBEAbility.Technician: reject = counter[PBEAbility.SkillLink] >= counter[PBEAbility.Technician] && pData.Abilities.Contains(PBEAbility.SkillLink); break; case PBEAbility.TintedLens: reject = counter.Damage >= counter.DamagingMoves || (counter[PBEMoveCategory.Status] > 2 && counter.SetupCategory == 'N'); break; case PBEAbility.Torrent: reject = counter[PBEType.Water] == 0; break; // Clefable case PBEAbility.Unaware: reject = counter[PBEMoveCategory.Status] < 2 && pData.Abilities.Contains(PBEAbility.MagicGuard); break; case PBEAbility.Unburden: reject = pData.BaseStats.Speed > 100; break; // Chinchou, Lanturn case PBEAbility.WaterAbsorb: reject = pData.Abilities.Contains(PBEAbility.VoltAbsorb); break; } // Reasons to always accept if (!reject) { if (_counterAbilities.Contains(ability)) { reject = counter[ability] == 0; if (!reject) // If we have an ability that the counter says has good moves, use the ability { break; } } else if (ability == PBEAbility.Prankster && counter[PBEMoveCategory.Status] > 1) { break; } else if (ability == PBEAbility.SwiftSwim && moves.Contains(PBEMove.RainDance)) { break; } else if ((ability == PBEAbility.Guts || ability == PBEAbility.QuickFeet) && moves.Contains(PBEMove.Facade)) { break; } } // Ability was rejected if (reject) { abilityPool.Remove(ability); ability = PBEAbility.None; } } while (ability == PBEAbility.None && abilityPool.Count > 0); return ability; } private static PBEItem GetItem(PBESpecies species, PBEForm form, PBEAbility ability, List moves, IPBEDDPokemonDataExtended pData, Counter counter, bool isLead) { PBEItem item; void GetRandomGem() { var list = new List(); foreach (PBEMove move in moves) { IPBEMoveData mData = PBEDataProvider.Instance.GetMoveData(move); //if (!move.basePower && !move.basePowerCallback) continue; // KERMALIS: Instead gonna check status for now if (mData.Category != PBEMoveCategory.Status) { list.Add(mData.Type); } } item = PBEDataUtils.TypeToGem[PBEDataProvider.GlobalRandom.RandomElement(list)]; } // First, the extra high-priority items if (species == PBESpecies.Marowak) { item = PBEItem.ThickClub; } else if (species == PBESpecies.Deoxys && form == PBEForm.Deoxys_Attack) { item = isLead && moves.Contains(PBEMove.StealthRock) ? PBEItem.FocusSash : PBEItem.LifeOrb; } else if (species == PBESpecies.Farfetchd) { item = PBEItem.Stick; } else if (species == PBESpecies.Pikachu) { item = PBEItem.LightBall; } else if (species == PBESpecies.Shedinja || species == PBESpecies.Smeargle) { item = PBEItem.FocusSash; } else if (species == PBESpecies.Unown) { item = PBEItem.ChoiceSpecs; } else if (species == PBESpecies.Wobbuffet && moves.Contains(PBEMove.DestinyBond) && PBEDataProvider.GlobalRandom.RandomBool()) { item = PBEItem.CustapBerry; } else if (ability == PBEAbility.Imposter) { item = PBEItem.ChoiceScarf; } else if ((moves.Contains(PBEMove.Switcheroo) || moves.Contains(PBEMove.Trick)) && moves.Contains(PBEMove.GyroBall)) { item = ability == PBEAbility.Levitate || pData.HasType(PBEType.Flying) ? PBEItem.MachoBrace : PBEItem.IronBall; } else if (moves.Contains(PBEMove.Switcheroo) || moves.Contains(PBEMove.Trick)) { if (pData.BaseStats.Speed >= 60 && pData.BaseStats.Speed <= 108) { item = PBEItem.ChoiceScarf; } else { item = counter[PBEMoveCategory.Physical] > counter[PBEMoveCategory.Special] ? PBEItem.ChoiceBand : PBEItem.ChoiceSpecs; } } else if (pData.HasEvolutions()) { item = PBEItem.Eviolite; } else if (moves.Contains(PBEMove.ShellSmash)) { item = PBEItem.WhiteHerb; } else if (ability == PBEAbility.Harvest || moves.Contains(PBEMove.BellyDrum)) { item = PBEItem.SitrusBerry; } else if ((ability == PBEAbility.MagicGuard || ability == PBEAbility.SheerForce) && counter.DamagingMoves > 1) { item = PBEItem.LifeOrb; } else if (ability == PBEAbility.PoisonHeal || ability == PBEAbility.ToxicBoost || moves.Contains(PBEMove.Facade)) { item = PBEItem.ToxicOrb; } else if (ability != PBEAbility.NaturalCure && ability != PBEAbility.ShedSkin && moves.Contains(PBEMove.Rest) && !moves.Contains(PBEMove.SleepTalk)) { item = PBEItem.ChestoBerry; } else if (moves.Contains(PBEMove.RainDance)) { item = PBEItem.DampRock; } else if (moves.Contains(PBEMove.SunnyDay)) { item = PBEItem.HeatRock; } else if (moves.Contains(PBEMove.LightScreen) || moves.Contains(PBEMove.Reflect)) { item = PBEItem.LightClay; } else if (moves.Contains(PBEMove.Acrobatics)) { item = PBEItem.FlyingGem; } else if ((ability == PBEAbility.Guts && !moves.Contains(PBEMove.SleepTalk)) || moves.Contains(PBEMove.PsychoShift)) { item = moves.Contains(PBEMove.DrainPunch) ? PBEItem.FlameOrb : PBEItem.ToxicOrb; } else if (ability == PBEAbility.Unburden && (counter[PBEMoveCategory.Physical] > 0 || counter[PBEMoveCategory.Special] > 0)) { // Give Unburden mons a random Gem of the type of one of their damaging moves GetRandomGem(); } // Medium priority else if (counter[PBEMoveCategory.Status] == 0 && (moves.Contains(PBEMove.Eruption) || moves.Contains(PBEMove.WaterSpout))) { item = PBEItem.ChoiceScarf; } else if (ability == PBEAbility.SpeedBoost && counter[PBEMoveCategory.Physical] + counter[PBEMoveCategory.Special] > 2 && !moves.Contains(PBEMove.Substitute)) { item = PBEItem.LifeOrb; } else if (counter[PBEMoveCategory.Physical] >= PBESettings.DefaultNumMoves && !moves.Contains(PBEMove.DragonTail) && !moves.Contains(PBEMove.FakeOut) && !moves.Contains(PBEMove.FlameCharge) && !moves.Contains(PBEMove.SuckerPunch) && (!moves.Contains(PBEMove.RapidSpin) || PBETypeEffectiveness.GetEffectiveness(PBEType.Rock, pData) < 1)) { item = (pData.BaseStats.Attack >= 100 || pData.Abilities.Contains(PBEAbility.HugePower)) && pData.BaseStats.Speed >= 60 && pData.BaseStats.Speed <= 108 && counter.Priority == 0 && PBEDataProvider.GlobalRandom.RandomBool(2, 3) ? PBEItem.ChoiceScarf : PBEItem.ChoiceBand; } else if (counter[PBEMoveCategory.Special] >= PBESettings.DefaultNumMoves && !moves.Contains(PBEMove.ClearSmog) && !moves.Contains(PBEMove.FieryDance)) { item = pData.BaseStats.SpAttack >= 100 && pData.BaseStats.Speed >= 60 && pData.BaseStats.Speed <= 108 && counter.Priority == 0 && PBEDataProvider.GlobalRandom.RandomBool(2, 3) ? PBEItem.ChoiceScarf : PBEItem.ChoiceSpecs; } else if (counter[PBEMoveCategory.Special] >= 3 && moves.Contains(PBEMove.Uturn)) { item = PBEItem.ChoiceSpecs; } else if (ability != PBEAbility.Levitate && PBETypeEffectiveness.GetEffectiveness(PBEType.Ground, pData) > 1 && !moves.Contains(PBEMove.MagnetRise)) { item = PBEItem.AirBalloon; } else if (moves.Contains(PBEMove.Substitute) && moves.Contains(PBEMove.Reversal)) { GetRandomGem(); } else if (ability != PBEAbility.Sturdy && (moves.Contains(PBEMove.Flail) || moves.Contains(PBEMove.Reversal))) { item = PBEItem.FocusSash; } else if (ability == PBEAbility.SlowStart || moves.Contains(PBEMove.Detect) || moves.Contains(PBEMove.Protect) || moves.Contains(PBEMove.SleepTalk) || moves.Contains(PBEMove.Substitute)) { item = PBEItem.Leftovers; } else if (ability == PBEAbility.IronBarbs) { item = PBEItem.RockyHelmet; } else if (species == PBESpecies.Palkia && (moves.Contains(PBEMove.DracoMeteor) || moves.Contains(PBEMove.SpacialRend)) && moves.Contains(PBEMove.HydroPump)) { item = PBEItem.LustrousOrb; } else if (pData.BaseStats.HP + pData.BaseStats.Defense + pData.BaseStats.SpDefense > 275) { item = PBEItem.Leftovers; } else if (ability != PBEAbility.Sturdy && counter[PBEMoveCategory.Physical] + counter[PBEMoveCategory.Special] >= 3 && counter.SetupCategory != 'N' && !moves.Contains(PBEMove.RapidSpin)) { item = moves.Contains(PBEMove.Outrage) ? PBEItem.LumBerry : PBEItem.LifeOrb; } else if (counter[PBEMoveCategory.Physical] + counter[PBEMoveCategory.Special] >= PBESettings.DefaultNumMoves) { item = counter[PBEType.Normal] > 0 ? PBEItem.LifeOrb : PBEItem.ExpertBelt; } else if (isLead && ability != PBEAbility.Regenerator && ability != PBEAbility.Sturdy && counter.Recoil == 0 && counter.Recovery == 0 && pData.BaseStats.HP + pData.BaseStats.Defense + pData.BaseStats.SpDefense <= 275) { item = PBEItem.FocusSash; } // This is the "REALLY can't think of a good item" cutoff else if (PBETypeEffectiveness.GetEffectiveness(PBEType.Rock, pData) >= 1 || moves.Contains(PBEMove.DragonTail)) { item = PBEItem.Leftovers; } else if (ability != PBEAbility.Sturdy && counter[PBEMoveCategory.Status] <= 1 && !moves.Contains(PBEMove.RapidSpin) && !moves.Contains(PBEMove.Uturn)) { item = PBEItem.LifeOrb; } else { item = PBEItem.Leftovers; } // For Trick / Switcheroo if (item == PBEItem.Leftovers && pData.HasType(PBEType.Poison)) { item = PBEItem.BlackSludge; } return item; } private static List GetMoves(PBESpecies species, PBEForm form, IPBEDDPokemonDataExtended pData, bool isLead, TeamDetails teamDs, out Counter counter) { var movePool = new List(PBEDataProvider.Instance.GetLegalMoves(species, form, PBESettings.DefaultMaxLevel)); var moves = new List(PBESettings.DefaultNumMoves); int startI; if (species == PBESpecies.Keldeo && form == PBEForm.Keldeo_Resolute) { moves.Add(PBEMove.SecretSword); movePool.Remove(PBEMove.SecretSword); startI = 1; } else { startI = 0; } var rejectedPool = new List(); do { // Choose next moves from learnset/viable moves and add them to moves list: while (moves.Count < PBESettings.DefaultNumMoves && movePool.Count > 0) { PBEMove move = PBEDataProvider.GlobalRandom.RandomElement(movePool); movePool.Remove(move); moves.Add(move); } while (moves.Count < PBESettings.DefaultNumMoves && rejectedPool.Count > 0) { PBEMove move = PBEDataProvider.GlobalRandom.RandomElement(rejectedPool); rejectedPool.Remove(move); moves.Add(move); } counter = QueryMoves(pData, moves, movePool); // If there are no more moves to choose, there's nothing to cull if (movePool.Count == 0 && rejectedPool.Count == 0) { break; } // Iterate through the moves again, this time to cull them: for (int i = startI; i < moves.Count; i++) { PBEMove move = moves[i]; IPBEMoveData mData = PBEDataProvider.Instance.GetMoveData(move); bool reject = false; bool isSetup = false; switch (move) { // Not very useful without their supporting moves case PBEMove.BatonPass: { if (counter.SetupCategory == 'N' && counter.SpeedSetup == 0 && !moves.Contains(PBEMove.Substitute) && !moves.Contains(PBEMove.Wish) && !pData.HasAbility(PBEAbility.SpeedBoost)) { reject = true; } break; } case PBEMove.FocusPunch: { if (!moves.Contains(PBEMove.Substitute) || counter.DamagingMoves < 2 || moves.Contains(PBEMove.SwordsDance)) { reject = true; } break; } case PBEMove.PerishSong: { if (!moves.Contains(PBEMove.Protect)) { reject = true; } break; } case PBEMove.Rest: { if (!movePool.Contains(PBEMove.SleepTalk)) { reject = true; } break; } case PBEMove.SleepTalk: { if (!moves.Contains(PBEMove.Rest)) { reject = true; } movePool.Remove(PBEMove.Rest); break; } case PBEMove.StoredPower: { if (counter.SetupCategory == 'N' && !moves.Contains(PBEMove.CosmicPower)) { reject = true; } break; } // Set up once and only if we have the moves for it case PBEMove.BellyDrum: case PBEMove.BulkUp: case PBEMove.Coil: case PBEMove.Curse: case PBEMove.DragonDance: case PBEMove.HoneClaws: case PBEMove.SwordsDance: { if (counter.SetupCategory != 'P' || counter.PhysicalSetup > 1) { reject = true; } else if (counter[PBEMoveCategory.Physical] + counter.PhysicalPool < 2 && !moves.Contains(PBEMove.BatonPass) && (!moves.Contains(PBEMove.Rest) || !moves.Contains(PBEMove.SleepTalk))) { reject = true; } isSetup = true; break; } case PBEMove.CalmMind: case PBEMove.NastyPlot: case PBEMove.QuiverDance: case PBEMove.TailGlow: { if (counter.SetupCategory != 'S' || counter.SpecialSetup > 1) { reject = true; } else if (counter[PBEMoveCategory.Special] + counter.SpecialPool < 2 && !moves.Contains(PBEMove.BatonPass) && (!moves.Contains(PBEMove.Rest) || !moves.Contains(PBEMove.SleepTalk))) { reject = true; } isSetup = true; break; } case PBEMove.Growth: case PBEMove.ShellSmash: case PBEMove.WorkUp: { if (counter.SetupCategory != 'M' || counter.MixedSetup > 1) { reject = true; } else if (counter.DamagingMoves + counter.PhysicalPool + counter.SpecialPool < 2 && !moves.Contains(PBEMove.BatonPass)) { reject = true; } else if (move == PBEMove.Growth && !moves.Contains(PBEMove.SunnyDay)) { reject = true; } isSetup = true; break; } case PBEMove.Agility: case PBEMove.Autotomize: case PBEMove.RockPolish: { if (counter.DamagingMoves < 2 && counter.SetupCategory == 'N' && !moves.Contains(PBEMove.BatonPass)) { reject = true; } else if (moves.Contains(PBEMove.Rest) && moves.Contains(PBEMove.SleepTalk)) { reject = true; } else if (counter.SetupCategory == 'N') { isSetup = true; } break; } // Bad after setup case PBEMove.BulletPunch: { if (counter.SpeedSetup > 0) { reject = true; } break; } case PBEMove.CircleThrow: case PBEMove.DragonTail: { if (counter.SetupCategory != 'N' && ((!moves.Contains(PBEMove.Rest) && !moves.Contains(PBEMove.SleepTalk)) || moves.Contains(PBEMove.StormThrow))) { reject = true; } else if (counter.SpeedSetup > 0 || moves.Contains(PBEMove.Encore) || moves.Contains(PBEMove.Roar) || moves.Contains(PBEMove.Whirlwind)) { reject = true; } break; } case PBEMove.FakeOut: { if (counter.SetupCategory != 'N' || moves.Contains(PBEMove.Substitute) || moves.Contains(PBEMove.Switcheroo) || moves.Contains(PBEMove.Trick)) { reject = true; } break; } case PBEMove.Haze: case PBEMove.MagicCoat: case PBEMove.Pursuit: case PBEMove.Selfdestruct: case PBEMove.Spikes: case PBEMove.WaterSpout: { if (counter.SetupCategory != 'N' || counter.SpeedSetup > 0 || (moves.Contains(PBEMove.Rest) && moves.Contains(PBEMove.SleepTalk))) { reject = true; } break; } case PBEMove.HealingWish: { if (counter.SetupCategory != 'N' || counter.Recovery > 0 || moves.Contains(PBEMove.Substitute)) { reject = true; } break; } case PBEMove.LeechSeed: case PBEMove.Roar: case PBEMove.Whirlwind: { if (counter.SetupCategory != 'N' || counter.SpeedSetup > 0 || moves.Contains(PBEMove.DragonTail)) { reject = true; } break; } case PBEMove.NightShade: case PBEMove.SeismicToss: case PBEMove.SuperFang: { if (counter.DamagingMoves > 1 || counter.SetupCategory != 'N') { reject = true; } break; } case PBEMove.Protect: { if (counter.SetupCategory != 'N' && (pData.HasAbility(PBEAbility.Guts) || pData.HasAbility(PBEAbility.SpeedBoost)) && !moves.Contains(PBEMove.BatonPass)) { reject = true; } else if (moves.Contains(PBEMove.Rest) || (moves.Contains(PBEMove.LightScreen) && moves.Contains(PBEMove.Reflect))) // A typo? light screen and reflect together? not ||? { reject = true; } break; } case PBEMove.RapidSpin: { if (moves.Contains(PBEMove.ShellSmash) || (counter.SetupCategory != 'N' && counter[PBEMoveCategory.Status] >= 2)) { reject = true; } break; } case PBEMove.StealthRock: { if (counter.SetupCategory != 'N' || counter.SpeedSetup > 0 || moves.Contains(PBEMove.Rest) || teamDs.StealthRock) { reject = true; } break; } case PBEMove.Switcheroo: case PBEMove.Trick: { if (counter[PBEMoveCategory.Physical] + counter[PBEMoveCategory.Special] < 3 || counter.Priority > 0 || moves.Contains(PBEMove.RapidSpin)) { reject = true; } break; } case PBEMove.ToxicSpikes: { if (counter.SetupCategory != 'N' || teamDs.ToxicSpikes) { reject = true; } break; } case PBEMove.TrickRoom: { if (counter.SetupCategory != 'N' || counter.SpeedSetup > 0 || counter.DamagingMoves < 2) { reject = true; } if (moves.Contains(PBEMove.LightScreen) || moves.Contains(PBEMove.Reflect)) { reject = true; } break; } case PBEMove.Uturn: { if (counter.SetupCategory != 'N' || counter.SpeedSetup > 0 || moves.Contains(PBEMove.BatonPass)) // KERMALIS: Should reject if they have volt switch? { reject = true; } break; } case PBEMove.VoltSwitch: { if (counter.SetupCategory != 'N' || counter.SpeedSetup > 0 || moves.Contains(PBEMove.BatonPass) || moves.Contains(PBEMove.MagnetRise) || moves.Contains(PBEMove.Uturn)) { reject = true; } break; } // Bit redundant to have both // Attacks: case PBEMove.BugBite: { if (moves.Contains(PBEMove.Uturn)) { reject = true; } break; } case PBEMove.Crunch: { if (!pData.HasType(PBEType.Dark) && moves.Contains(PBEMove.SuckerPunch)) { reject = true; } break; } case PBEMove.CloseCombat: { if (counter.SetupCategory != 'N' && moves.Contains(PBEMove.AuraSphere)) { reject = true; } break; } case PBEMove.DragonPulse: case PBEMove.SpacialRend: { if (moves.Contains(PBEMove.DracoMeteor) || moves.Contains(PBEMove.Outrage)) { reject = true; } break; } case PBEMove.Thunderbolt: { if (moves.Contains(PBEMove.WildCharge)) { reject = true; } break; } case PBEMove.AuraSphere: case PBEMove.HiJumpKick: { if (counter.SetupCategory != 'N' && moves.Contains(PBEMove.CloseCombat)) { reject = true; } break; } case PBEMove.DrainPunch: case PBEMove.FocusBlast: { if (moves.Contains(PBEMove.CloseCombat) || moves.Contains(PBEMove.CrossChop) || moves.Contains(PBEMove.HiJumpKick) || moves.Contains(PBEMove.LowKick)) { reject = true; } break; } case PBEMove.BlueFlare: case PBEMove.FlareBlitz: case PBEMove.FieryDance: case PBEMove.Flamethrower: case PBEMove.LavaPlume: { if (moves.Contains(PBEMove.FireBlast) || moves.Contains(PBEMove.Overheat) || moves.Contains(PBEMove.VCreate)) { reject = true; } break; } case PBEMove.AirSlash: case PBEMove.BraveBird: case PBEMove.Pluck: { if (moves.Contains(PBEMove.Acrobatics) || moves.Contains(PBEMove.Hurricane)) { reject = true; } break; } case PBEMove.GigaDrain: { if ((counter.SetupCategory != 'N' && moves.Contains(PBEMove.LeafStorm)) || moves.Contains(PBEMove.PetalDance) || moves.Contains(PBEMove.PowerWhip)) { reject = true; } break; } case PBEMove.SolarBeam: { if ((!pData.HasAbility(PBEAbility.Drought) && !moves.Contains(PBEMove.SunnyDay)) || moves.Contains(PBEMove.GigaDrain) || moves.Contains(PBEMove.LeafStorm)) { reject = true; } break; } case PBEMove.LeafStorm: { if (counter.SetupCategory != 'N' && moves.Contains(PBEMove.GigaDrain)) { reject = true; } break; } case PBEMove.Bonemerang: case PBEMove.EarthPower: { if (moves.Contains(PBEMove.Earthquake)) { reject = true; } break; } case PBEMove.Endeavor: { if (!isLead) { reject = true; } break; } case PBEMove.Facade: { if (moves.Contains(PBEMove.SuckerPunch) && !pData.HasType(PBEType.Normal)) { reject = true; } break; } case PBEMove.Judgment: { if (counter.SetupCategory != 'S' && counter.STAB > 1) { reject = true; } break; } case PBEMove.Return: { if (moves.Contains(PBEMove.BodySlam) || moves.Contains(PBEMove.DoubleEdge)) { reject = true; } break; } case PBEMove.WeatherBall: { if (!moves.Contains(PBEMove.SunnyDay)) { reject = true; } break; } case PBEMove.PoisonJab: { if (moves.Contains(PBEMove.GunkShot)) { reject = true; } break; } case PBEMove.Psychic: { if (moves.Contains(PBEMove.Psyshock)) { reject = true; } break; } case PBEMove.RockBlast: case PBEMove.RockSlide: { if (moves.Contains(PBEMove.HeadSmash) || moves.Contains(PBEMove.StoneEdge)) { reject = true; } break; } case PBEMove.StoneEdge: { if (moves.Contains(PBEMove.HeadSmash)) { reject = true; } break; } case PBEMove.Scald: case PBEMove.Surf: { if (moves.Contains(PBEMove.HydroPump)) { reject = true; } break; } case PBEMove.Waterfall: { if (moves.Contains(PBEMove.Scald) || (moves.Contains(PBEMove.Rest) && moves.Contains(PBEMove.SleepTalk))) { reject = true; } break; } // Status: case PBEMove.Encore: case PBEMove.IceShard: case PBEMove.SuckerPunch: { if (moves.Contains(PBEMove.Rest) && moves.Contains(PBEMove.SleepTalk)) { reject = true; } break; } case PBEMove.HealBell: { if (moves.Contains(PBEMove.MagicCoat)) { reject = true; } break; } case PBEMove.Moonlight: case PBEMove.PainSplit: case PBEMove.Recover: case PBEMove.Roost: case PBEMove.Softboiled: case PBEMove.Synthesis: { if (moves.Contains(PBEMove.LeechSeed) || moves.Contains(PBEMove.Rest) || moves.Contains(PBEMove.Wish)) { reject = true; } break; } case PBEMove.Substitute: { if ((moves.Contains(PBEMove.DoubleEdge) && !pData.HasAbility(PBEAbility.RockHead)) || moves.Contains(PBEMove.Pursuit) || moves.Contains(PBEMove.Rest) || moves.Contains(PBEMove.Superpower) || moves.Contains(PBEMove.Uturn) || moves.Contains(PBEMove.VoltSwitch)) { reject = true; } break; } case PBEMove.ThunderWave: { if (counter.SetupCategory != 'N' || counter.SpeedSetup > 0 || (moves.Contains(PBEMove.Rest) && moves.Contains(PBEMove.SleepTalk))) { reject = true; } if (moves.Contains(PBEMove.Discharge) || moves.Contains(PBEMove.TrickRoom)) { reject = true; } break; } case PBEMove.WillOWisp: { if (moves.Contains(PBEMove.LavaPlume) || (moves.Contains(PBEMove.Scald) && !pData.HasType(PBEType.Ghost))) // KERMALIS: why ghost check { reject = true; } break; } } // This move doesn't satisfy our setup requirements: if ((mData.Category == PBEMoveCategory.Physical && counter.SetupCategory == 'S') || (mData.Category == PBEMoveCategory.Special && counter.SetupCategory == 'P')) { // Reject STABs last in case the setup type changes later on int stabs = counter[pData.Type1]; if (pData.Type2 != PBEType.None) { stabs += counter[pData.Type2]; } if (!_setupExceptionMoves.Contains(move) && (!pData.HasType(mData.Type) || stabs > 1 || counter[mData.Category] < 2)) { reject = true; } } PBEMoveCategory c = counter.SetupCategory == 'P' ? PBEMoveCategory.Physical : PBEMoveCategory.Special; if (counter.SetupCategory != 'N' && !isSetup && counter.SetupCategory != 'M') { if (mData.Category != c && counter[c] < 2 && !moves.Contains(PBEMove.BatonPass) && (mData.Category != PBEMoveCategory.Status || !mData.IsHPRestoreMove()) && move != PBEMove.SleepTalk) { // Mono-attacking with setup and Rest/SleepTalk is allowed // Reject status moves only if there is nothing else to reject if (mData.Category != PBEMoveCategory.Status || (counter[c] + counter[PBEMoveCategory.Status] > 3 && counter.PhysicalSetup + counter.SpecialSetup < 2)) { reject = true; } } } if (counter.SetupCategory == 'S' && move == PBEMove.HiddenPower && pData.Type2 != PBEType.None && counter[PBEMoveCategory.Special] <= 2 && !pData.HasType(mData.Type) && counter[PBEMoveCategory.Physical] == 0 && counter.SpecialPool > 0) { // Hidden Power isn't good enough reject = true; } // Pokemon should have moves that benefit their Type/Ability/Weather, as well as moves required by its forme if (!reject) { if (( counter.PhysicalSetup + counter.SpecialSetup < 2 && (counter.SetupCategory == 'N' || counter.SetupCategory == 'M' || (mData.Category != PBEMoveCategory.Status && mData.Category != c) || counter[c] + counter[PBEMoveCategory.Status] > 3) ) && ( (counter.STAB == 0 && counter.Damage == 0 && (pData.Type2 != PBEType.None || (pData.Type1 != PBEType.Normal && pData.Type1 != PBEType.Psychic) || !moves.Contains(PBEMove.IceBeam) || pData.BaseStats.SpAttack >= pData.BaseStats.SpDefense)) || (pData.HasType(PBEType.Dark) && counter[PBEType.Dark] == 0) || (pData.HasType(PBEType.Dragon) && counter[PBEType.Dragon] == 0) || (pData.HasType(PBEType.Electric) && counter[PBEType.Electric] == 0) || (pData.HasType(PBEType.Fighting) && counter[PBEType.Fighting] == 0 && (pData.BaseStats.Attack >= 110 || pData.HasAbility(PBEAbility.Justified) || pData.HasAbility(PBEAbility.PurePower) || counter.SetupCategory != 'N' || counter[PBEMoveCategory.Status] == 0)) || (pData.HasType(PBEType.Fire) && counter[PBEType.Fire] == 0) || (pData.HasType(PBEType.Flying) && pData.HasType(PBEType.Normal) && counter[PBEType.Flying] == 0) || (pData.HasType(PBEType.Ground) && counter[PBEType.Ground] == 0 && !moves.Contains(PBEMove.Rest) && !moves.Contains(PBEMove.SleepTalk)) || (pData.HasType(PBEType.Ice) && counter[PBEType.Ice] == 0) || (pData.HasType(PBEType.Rock) && counter[PBEType.Rock] == 0 && pData.BaseStats.Attack >= 80) || (pData.HasType(PBEType.Steel) && pData.HasAbility(PBEAbility.Technician) && counter[PBEType.Steel] == 0) || (pData.HasType(PBEType.Water) && counter[PBEType.Water] == 0) || ( (pData.HasAbility(PBEAbility.Adaptability) && counter.SetupCategory == 'N' && pData.Type2 != PBEType.None && (counter[pData.Type1] == 0 || counter[pData.Type2] == 0)) || (pData.HasAbility(PBEAbility.BadDreams) && movePool.Contains(PBEMove.DarkVoid)) || (pData.HasAbility(PBEAbility.Contrary) && counter[PBEAbility.Contrary] == 0 && species != PBESpecies.Shuckle) || (pData.HasAbility(PBEAbility.Guts) && pData.HasType(PBEType.Normal) && movePool.Contains(PBEMove.Facade)) || (pData.HasAbility(PBEAbility.SlowStart) && movePool.Contains(PBEMove.Substitute)) || (counter.Recovery == 0 && counter.SetupCategory == 'N' && !moves.Contains(PBEMove.HealingWish) && ( movePool.Contains(PBEMove.Recover) || movePool.Contains(PBEMove.Roost) || movePool.Contains(PBEMove.Softboiled) ) && (counter[PBEMoveCategory.Status] > 1))) )) { // Reject Status or non-STAB if (!isSetup && !mData.IsWeatherMove() /*&& !move.damage*/ && (mData.Category != PBEMoveCategory.Status || !mData.IsHPRestoreMove()) && move != PBEMove.Judgment && move != PBEMove.SleepTalk) { if (mData.Category == PBEMoveCategory.Status || !pData.HasType(mData.Type) /*|| move.selfSwitch*/ || (mData.Power > 0 && mData.Power < 40 && !mData.IsMultiHitMove())) { reject = true; } } } } // Sleep Talk shouldn't be selected without Rest if (move == PBEMove.Rest && reject) { int sleeptalk = moves.IndexOf(PBEMove.SleepTalk); if (sleeptalk >= 0) { if (movePool.Count < 2) { reject = false; } else { movePool.RemoveAt(sleeptalk); } } } // Remove rejected moves from the move list if (reject) { // Let's say our movePool originally has 4 moves and we reject three, we have one move and three are looping in rejectedPool forever. // This if prevents them from looping through rejectedPool if (movePool.Count > 0) { rejectedPool.Add(move); } moves.RemoveAt(i); break; } // TODO: Hidden power IVs and types } } while (moves.Count < PBESettings.DefaultNumMoves); return moves; } private static Counter QueryMoves(IPBEDDPokemonDataExtended pData, List moves, List movePool) { var counter = new Counter(); if (moves.Count == 0) { return counter; } // Iterate through all moves we've chosen so far and keep track of what they do: foreach (PBEMove move in moves) { IPBEMoveData mData = PBEDataProvider.Instance.GetMoveData(move); PBEType moveType = move == PBEMove.Judgment ? pData.Type1 : mData.Type; // Moves that do a set amount of damage: if (mData.IsSetDamageMove()) { counter.Damage++; counter.DamagingMoves++; } else // Are Physical/Special/Status moves: { counter[mData.Category]++; } // Moves that have a low base power: if (move == PBEMove.LowKick || (mData.Power > 0 && mData.Power <= 60 && move != PBEMove.RapidSpin)) { counter[PBEAbility.Technician]++; } // Moves that hit up to 5 times: (KERMALIS: Also TripleKick) if (mData.IsMultiHitMove()) { counter[PBEAbility.SkillLink]++; } if (mData.IsRecoilMove()) { counter.Recoil++; } if (mData.IsHPDrainMove()) { counter.Drain++; } // Moves which have a base power, but aren't super-weak like RapidSpin if (move == PBEMove.NaturePower || mData.Power > 30 || mData.IsMultiHitMove()/* || mData.basePowerCallback*/) { counter[moveType]++; if (pData.HasType(moveType) || pData.Abilities.Contains(PBEAbility.Normalize)) { counter[PBEAbility.Adaptability]++; // STAB: // Certain moves aren't acceptable as a Pokémon's only STAB attack if (!_noSTABMoves.Contains(move) && (move != PBEMove.HiddenPower || pData.Type2 == PBEType.None)) { counter.STAB++; // Ties between Physical and Special setup should broken in favor of STABs counter[mData.Category] += 0.1f; } } if (mData.Flags.HasFlag(PBEMoveFlag.AffectedByIronFist)) { counter[PBEAbility.IronFist]++; } if (mData.Flags.HasFlag(PBEMoveFlag.AffectedBySoundproof)) { counter.Sound++; } counter.DamagingMoves++; } // Moves with secondary effects: if (mData.HasSecondaryEffects(PBESettings.DefaultSettings)) { counter[PBEAbility.SheerForce]++; if (mData.EffectParam >= 20 && mData.EffectParam < 100) { counter[PBEAbility.SereneGrace]++; } } // Moves with low accuracy: if (mData.Accuracy != 0 && mData.Accuracy < 90) { counter.Inaccurate++; } // Moves with non-zero priority: if (mData.Category != PBEMoveCategory.Status && mData.Priority != 0) { counter.Priority++; } // Moves that change stats: if (_recoveryMoves.Contains(move)) { counter.Recovery++; } if (_contraryMoves.Contains(move)) { counter[PBEAbility.Contrary]++; } if (_physicalSetupMoves.Contains(move)) { counter.PhysicalSetup++; counter.SetupCategory = 'P'; } else if (_specialSetupMoves.Contains(move)) { counter.SpecialSetup++; counter.SetupCategory = 'S'; } if (_mixedSetupMoves.Contains(move)) { counter.MixedSetup++; } if (_speedSetupMoves.Contains(move)) { counter.SpeedSetup++; } if (_hazardMoves.Contains(move)) { counter.Hazards++; } } // Keep track of available moves foreach (PBEMove move in movePool) { IPBEMoveData mData = PBEDataProvider.Instance.GetMoveData(move); if (!mData.IsSetDamageMove()) { if (mData.Category == PBEMoveCategory.Physical) { counter.PhysicalPool++; } else if (mData.Category == PBEMoveCategory.Special) { counter.SpecialPool++; } } } // Choose a setup type: if (counter.MixedSetup > 0) { counter.SetupCategory = 'M'; } else if (counter.PhysicalSetup > 0 && counter.SpecialSetup > 0) { float physical = counter[PBEMoveCategory.Physical] + counter.PhysicalPool; float special = counter[PBEMoveCategory.Special] + counter.SpecialPool; if (physical == special) { if (counter[PBEMoveCategory.Physical] > counter[PBEMoveCategory.Special]) { counter.SetupCategory = 'P'; } if (counter[PBEMoveCategory.Special] > counter[PBEMoveCategory.Physical]) { counter.SetupCategory = 'S'; } } else { counter.SetupCategory = physical > special ? 'P' : 'S'; } } else if (counter.SetupCategory == 'P') { if (counter[PBEMoveCategory.Physical] < 2 && (counter.STAB == 0 || counter.PhysicalPool == 0) && (!moves.Contains(PBEMove.Rest) || !moves.Contains(PBEMove.SleepTalk))) { counter.SetupCategory = 'N'; } } else if (counter.SetupCategory == 'S') { if (counter[PBEMoveCategory.Special] < 2 && (counter.STAB == 0 || counter.SpecialPool == 0) && (!moves.Contains(PBEMove.Rest) || !moves.Contains(PBEMove.SleepTalk))) { counter.SetupCategory = 'N'; } } counter[PBEMoveCategory.Physical] = (float)Math.Floor(counter[PBEMoveCategory.Physical]); counter[PBEMoveCategory.Special] = (float)Math.Floor(counter[PBEMoveCategory.Special]); return counter; } // TODO: Hidden power types private static void GetRandomSet(PBESpecies species, PBEForm form, IPBEDDPokemonDataExtended pData, bool isLead, TeamDetails teamDs, PBELegalPokemon pkmn) { pkmn.EffortValues.Equalize(); List moves = GetMoves(species, form, pData, isLead, teamDs, out Counter counter); // If Hidden Power has been removed, reset the IVs if (!moves.Contains(PBEMove.HiddenPower)) { pkmn.IndividualValues.Maximize(); } if (pkmn.SelectableAbilities.Count > 1) { PBEAbility a = GetAbility(species, moves, pData, counter, teamDs); if (a != PBEAbility.None) { pkmn.Ability = a; } } if (pkmn.SelectableItems.Count > 1) { pkmn.Item = GetItem(species, form, pkmn.Ability, moves, pData, counter, isLead); } // KERMALIS: This is where showdown does level scaling // Minimize confusion damage if (counter[PBEMoveCategory.Physical] == 0 && !moves.Contains(PBEMove.Transform)) { pkmn.EffortValues.Attack = 0; pkmn.IndividualValues.Attack = 0; } if (moves.Contains(PBEMove.GyroBall) || moves.Contains(PBEMove.MetalBurst) || moves.Contains(PBEMove.TrickRoom)) { pkmn.EffortValues.Speed = 0; pkmn.IndividualValues.Speed = 0; } pkmn.Friendship = moves.Contains(PBEMove.Frustration) ? byte.MinValue : byte.MaxValue; for (int i = 0; i < moves.Count; i++) { PBELegalMoveset.PBELegalMovesetSlot slot = pkmn.Moveset[i]; slot.Move = moves[i]; if (slot.IsPPUpsEditable) { slot.PPUps = PBESettings.DefaultMaxPPUps; } } } /// Creates a random team meant for . /// The amount of Pokémon to create in the team./> public static PBELegalPokemonCollection CreateRandomTeam(int numPkmn) { return CreateRandomTeam(numPkmn, PBEDataUtils.FullyEvolvedSpecies); } // TODO: Move Illusion out of the way instead of just yeeting it if it's last // TODO: Tiers?, level scaling, limit one type combination // TODO: Hidden Power types // TODO: Custom settings // TODO: Non-competitive moves (such as MegaDrain/GigaDrain together, HelpingHand), ban certain species (such as Wobbuffet & Unown, but add certain pre-evolutions such as Pikachu) // TODO: Prioritize correct attack stat for species, currently will give special moves to a physical attacker, etc /// Creates a random team meant for . /// The amount of Pokémon to create in the team./> /// The allowed species to consider. public static PBELegalPokemonCollection CreateRandomTeam(int numPkmn, IEnumerable allowedSpecies) { if (numPkmn < 1 || numPkmn > PBESettings.DefaultMaxPartySize) { throw new ArgumentOutOfRangeException(nameof(numPkmn)); } int maxShared = numPkmn / 3; var speciesPool = new List(allowedSpecies); var teamDs = new TeamDetails(); var usedTypes = new Dictionary(numPkmn - 1); var team = new PBELegalPokemonCollection(PBESettings.DefaultSettings); int currentIndex = 0; while (speciesPool.Count > 0) { (PBESpecies species, PBEForm form) = PBEDataProvider.GlobalRandom.RandomSpecies(speciesPool, true); speciesPool.Remove(species); // KERMALIS: Showdown limits {maxShared} per tier IPBEDDPokemonDataExtended pData = PBEDefaultDataProvider.Instance.GetPokemonDataExtended(species, form); if (ShouldDenyType(usedTypes, pData.Type1, maxShared) || ShouldDenyType(usedTypes, pData.Type2, maxShared)) { continue; } var pkmn = new PBELegalPokemon(species, form, PBESettings.DefaultMaxLevel, PBEDataProvider.Instance.GetEXPRequired(pData.GrowthRate, PBESettings.DefaultMaxLevel), PBESettings.DefaultSettings); GetRandomSet(species, form, pData, currentIndex == 0, teamDs, pkmn); // Illusion shouldn't be the last Pokémon of the team if (pkmn.Ability == PBEAbility.Illusion && currentIndex == numPkmn - 1) { continue; } // KERMALIS: Showdown limits one type combination, excluding weather ability users // KERMALIS: Showdown sets Illusion user's level to the level of the last in the party // Now that our Pokémon has passed all checks, we can increment our counters team.Add(pkmn); if (team.Count >= numPkmn || speciesPool.Count == 0) { break; } currentIndex++; // Increment our tier counter // Increment type counters AddTypeToDict(usedTypes, pData.Type1); AddTypeToDict(usedTypes, pData.Type2); // Team details if (pkmn.Ability == PBEAbility.SnowWarning || pkmn.Moveset.Contains(PBEMove.Hail)) { teamDs.Hail = true; } if (pkmn.Ability == PBEAbility.Drizzle || pkmn.Moveset.Contains(PBEMove.RainDance)) { teamDs.Rain = true; } if (pkmn.Ability == PBEAbility.SandStream || pkmn.Moveset.Contains(PBEMove.Sandstorm)) { teamDs.Sandstorm = true; } if (pkmn.Ability == PBEAbility.Drought || pkmn.Moveset.Contains(PBEMove.SunnyDay)) { teamDs.HarshSunlight = true; } if (pkmn.Moveset.Contains(PBEMove.StealthRock)) { teamDs.StealthRock = true; } if (pkmn.Moveset.Contains(PBEMove.ToxicSpikes)) { teamDs.ToxicSpikes = true; } if (pkmn.Moveset.Contains(PBEMove.RapidSpin)) { teamDs.RapidSpin = true; } } if (team.Count < numPkmn) { throw new Exception("Failed to create a random team"); } return team; } private static void AddTypeToDict(Dictionary dict, PBEType type) { if (type != PBEType.None) { if (dict.ContainsKey(type)) { dict[type]++; } else { dict.Add(type, 1); } } } private static bool ShouldDenyType(Dictionary dict, PBEType type, int maxShared) { return type != PBEType.None && dict.TryGetValue(type, out int value) && value >= maxShared; } } ================================================ FILE: PokemonBattleEngine.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29001.49 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PokemonBattleEngine", "PokemonBattleEngine\PokemonBattleEngine.csproj", "{AC469C92-3871-41DD-AA00-D3E36A5BDA2F}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PokemonBattleEngineServer", "PokemonBattleEngineServer\PokemonBattleEngineServer.csproj", "{BE8F0292-CE0E-4023-984A-17A4016518FA}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PokemonBattleEngineClient", "PokemonBattleEngineClient\PokemonBattleEngineClient.csproj", "{BFADDEA8-741D-439C-82DF-849CB665EF84}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PokemonBattleEngineDiscord", "PokemonBattleEngineDiscord\PokemonBattleEngineDiscord.csproj", "{CCBE5C1D-7C76-4774-B4D1-34659A5C856B}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PokemonBattleEngineExtras", "PokemonBattleEngineExtras\PokemonBattleEngineExtras.csproj", "{31B95673-DFD2-4613-BAD6-7892CB29F4B9}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PokemonBattleEngineClient.Desktop", "PokemonBattleEngineClient.Desktop\PokemonBattleEngineClient.Desktop.csproj", "{CEDCFABE-658C-42AB-BCE2-A96A7B17410E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PokemonBattleEngineTests", "PokemonBattleEngineTests\PokemonBattleEngineTests.csproj", "{2B15A32D-C213-4C60-981A-350E1569E5DA}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PokemonBattleEngine.DefaultData", "PokemonBattleEngine.DefaultData\PokemonBattleEngine.DefaultData.csproj", "{D49BA669-1AE2-4779-922A-D5C1FF21C2DE}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Ad-Hoc|Any CPU = Ad-Hoc|Any CPU Ad-Hoc|iPhone = Ad-Hoc|iPhone Ad-Hoc|iPhoneSimulator = Ad-Hoc|iPhoneSimulator AppStore|Any CPU = AppStore|Any CPU AppStore|iPhone = AppStore|iPhone AppStore|iPhoneSimulator = AppStore|iPhoneSimulator Debug|Any CPU = Debug|Any CPU Debug|iPhone = Debug|iPhone Debug|iPhoneSimulator = Debug|iPhoneSimulator Release|Any CPU = Release|Any CPU Release|iPhone = Release|iPhone Release|iPhoneSimulator = Release|iPhoneSimulator EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {AC469C92-3871-41DD-AA00-D3E36A5BDA2F}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU {AC469C92-3871-41DD-AA00-D3E36A5BDA2F}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU {AC469C92-3871-41DD-AA00-D3E36A5BDA2F}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU {AC469C92-3871-41DD-AA00-D3E36A5BDA2F}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU {AC469C92-3871-41DD-AA00-D3E36A5BDA2F}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU {AC469C92-3871-41DD-AA00-D3E36A5BDA2F}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU {AC469C92-3871-41DD-AA00-D3E36A5BDA2F}.AppStore|Any CPU.ActiveCfg = Release|Any CPU {AC469C92-3871-41DD-AA00-D3E36A5BDA2F}.AppStore|Any CPU.Build.0 = Release|Any CPU {AC469C92-3871-41DD-AA00-D3E36A5BDA2F}.AppStore|iPhone.ActiveCfg = Release|Any CPU {AC469C92-3871-41DD-AA00-D3E36A5BDA2F}.AppStore|iPhone.Build.0 = Release|Any CPU {AC469C92-3871-41DD-AA00-D3E36A5BDA2F}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU {AC469C92-3871-41DD-AA00-D3E36A5BDA2F}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU {AC469C92-3871-41DD-AA00-D3E36A5BDA2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AC469C92-3871-41DD-AA00-D3E36A5BDA2F}.Debug|Any CPU.Build.0 = Debug|Any CPU {AC469C92-3871-41DD-AA00-D3E36A5BDA2F}.Debug|iPhone.ActiveCfg = Debug|Any CPU {AC469C92-3871-41DD-AA00-D3E36A5BDA2F}.Debug|iPhone.Build.0 = Debug|Any CPU {AC469C92-3871-41DD-AA00-D3E36A5BDA2F}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU {AC469C92-3871-41DD-AA00-D3E36A5BDA2F}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {AC469C92-3871-41DD-AA00-D3E36A5BDA2F}.Release|Any CPU.ActiveCfg = Release|Any CPU {AC469C92-3871-41DD-AA00-D3E36A5BDA2F}.Release|Any CPU.Build.0 = Release|Any CPU {AC469C92-3871-41DD-AA00-D3E36A5BDA2F}.Release|iPhone.ActiveCfg = Release|Any CPU {AC469C92-3871-41DD-AA00-D3E36A5BDA2F}.Release|iPhone.Build.0 = Release|Any CPU {AC469C92-3871-41DD-AA00-D3E36A5BDA2F}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {AC469C92-3871-41DD-AA00-D3E36A5BDA2F}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {BE8F0292-CE0E-4023-984A-17A4016518FA}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU {BE8F0292-CE0E-4023-984A-17A4016518FA}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU {BE8F0292-CE0E-4023-984A-17A4016518FA}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU {BE8F0292-CE0E-4023-984A-17A4016518FA}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU {BE8F0292-CE0E-4023-984A-17A4016518FA}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU {BE8F0292-CE0E-4023-984A-17A4016518FA}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU {BE8F0292-CE0E-4023-984A-17A4016518FA}.AppStore|Any CPU.ActiveCfg = Release|Any CPU {BE8F0292-CE0E-4023-984A-17A4016518FA}.AppStore|Any CPU.Build.0 = Release|Any CPU {BE8F0292-CE0E-4023-984A-17A4016518FA}.AppStore|iPhone.ActiveCfg = Release|Any CPU {BE8F0292-CE0E-4023-984A-17A4016518FA}.AppStore|iPhone.Build.0 = Release|Any CPU {BE8F0292-CE0E-4023-984A-17A4016518FA}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU {BE8F0292-CE0E-4023-984A-17A4016518FA}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU {BE8F0292-CE0E-4023-984A-17A4016518FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BE8F0292-CE0E-4023-984A-17A4016518FA}.Debug|Any CPU.Build.0 = Debug|Any CPU {BE8F0292-CE0E-4023-984A-17A4016518FA}.Debug|iPhone.ActiveCfg = Debug|Any CPU {BE8F0292-CE0E-4023-984A-17A4016518FA}.Debug|iPhone.Build.0 = Debug|Any CPU {BE8F0292-CE0E-4023-984A-17A4016518FA}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU {BE8F0292-CE0E-4023-984A-17A4016518FA}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {BE8F0292-CE0E-4023-984A-17A4016518FA}.Release|Any CPU.ActiveCfg = Release|Any CPU {BE8F0292-CE0E-4023-984A-17A4016518FA}.Release|Any CPU.Build.0 = Release|Any CPU {BE8F0292-CE0E-4023-984A-17A4016518FA}.Release|iPhone.ActiveCfg = Release|Any CPU {BE8F0292-CE0E-4023-984A-17A4016518FA}.Release|iPhone.Build.0 = Release|Any CPU {BE8F0292-CE0E-4023-984A-17A4016518FA}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {BE8F0292-CE0E-4023-984A-17A4016518FA}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {BFADDEA8-741D-439C-82DF-849CB665EF84}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU {BFADDEA8-741D-439C-82DF-849CB665EF84}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU {BFADDEA8-741D-439C-82DF-849CB665EF84}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU {BFADDEA8-741D-439C-82DF-849CB665EF84}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU {BFADDEA8-741D-439C-82DF-849CB665EF84}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU {BFADDEA8-741D-439C-82DF-849CB665EF84}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU {BFADDEA8-741D-439C-82DF-849CB665EF84}.AppStore|Any CPU.ActiveCfg = Release|Any CPU {BFADDEA8-741D-439C-82DF-849CB665EF84}.AppStore|Any CPU.Build.0 = Release|Any CPU {BFADDEA8-741D-439C-82DF-849CB665EF84}.AppStore|iPhone.ActiveCfg = Release|Any CPU {BFADDEA8-741D-439C-82DF-849CB665EF84}.AppStore|iPhone.Build.0 = Release|Any CPU {BFADDEA8-741D-439C-82DF-849CB665EF84}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU {BFADDEA8-741D-439C-82DF-849CB665EF84}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU {BFADDEA8-741D-439C-82DF-849CB665EF84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BFADDEA8-741D-439C-82DF-849CB665EF84}.Debug|Any CPU.Build.0 = Debug|Any CPU {BFADDEA8-741D-439C-82DF-849CB665EF84}.Debug|iPhone.ActiveCfg = Debug|Any CPU {BFADDEA8-741D-439C-82DF-849CB665EF84}.Debug|iPhone.Build.0 = Debug|Any CPU {BFADDEA8-741D-439C-82DF-849CB665EF84}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU {BFADDEA8-741D-439C-82DF-849CB665EF84}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {BFADDEA8-741D-439C-82DF-849CB665EF84}.Release|Any CPU.ActiveCfg = Release|Any CPU {BFADDEA8-741D-439C-82DF-849CB665EF84}.Release|Any CPU.Build.0 = Release|Any CPU {BFADDEA8-741D-439C-82DF-849CB665EF84}.Release|iPhone.ActiveCfg = Release|Any CPU {BFADDEA8-741D-439C-82DF-849CB665EF84}.Release|iPhone.Build.0 = Release|Any CPU {BFADDEA8-741D-439C-82DF-849CB665EF84}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {BFADDEA8-741D-439C-82DF-849CB665EF84}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {CCBE5C1D-7C76-4774-B4D1-34659A5C856B}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU {CCBE5C1D-7C76-4774-B4D1-34659A5C856B}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU {CCBE5C1D-7C76-4774-B4D1-34659A5C856B}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU {CCBE5C1D-7C76-4774-B4D1-34659A5C856B}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU {CCBE5C1D-7C76-4774-B4D1-34659A5C856B}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU {CCBE5C1D-7C76-4774-B4D1-34659A5C856B}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU {CCBE5C1D-7C76-4774-B4D1-34659A5C856B}.AppStore|Any CPU.ActiveCfg = Release|Any CPU {CCBE5C1D-7C76-4774-B4D1-34659A5C856B}.AppStore|Any CPU.Build.0 = Release|Any CPU {CCBE5C1D-7C76-4774-B4D1-34659A5C856B}.AppStore|iPhone.ActiveCfg = Release|Any CPU {CCBE5C1D-7C76-4774-B4D1-34659A5C856B}.AppStore|iPhone.Build.0 = Release|Any CPU {CCBE5C1D-7C76-4774-B4D1-34659A5C856B}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU {CCBE5C1D-7C76-4774-B4D1-34659A5C856B}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU {CCBE5C1D-7C76-4774-B4D1-34659A5C856B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CCBE5C1D-7C76-4774-B4D1-34659A5C856B}.Debug|Any CPU.Build.0 = Debug|Any CPU {CCBE5C1D-7C76-4774-B4D1-34659A5C856B}.Debug|iPhone.ActiveCfg = Debug|Any CPU {CCBE5C1D-7C76-4774-B4D1-34659A5C856B}.Debug|iPhone.Build.0 = Debug|Any CPU {CCBE5C1D-7C76-4774-B4D1-34659A5C856B}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU {CCBE5C1D-7C76-4774-B4D1-34659A5C856B}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {CCBE5C1D-7C76-4774-B4D1-34659A5C856B}.Release|Any CPU.ActiveCfg = Release|Any CPU {CCBE5C1D-7C76-4774-B4D1-34659A5C856B}.Release|Any CPU.Build.0 = Release|Any CPU {CCBE5C1D-7C76-4774-B4D1-34659A5C856B}.Release|iPhone.ActiveCfg = Release|Any CPU {CCBE5C1D-7C76-4774-B4D1-34659A5C856B}.Release|iPhone.Build.0 = Release|Any CPU {CCBE5C1D-7C76-4774-B4D1-34659A5C856B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {CCBE5C1D-7C76-4774-B4D1-34659A5C856B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.AppStore|Any CPU.Build.0 = Debug|Any CPU {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.AppStore|iPhone.ActiveCfg = Debug|Any CPU {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.AppStore|iPhone.Build.0 = Debug|Any CPU {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.Debug|Any CPU.Build.0 = Debug|Any CPU {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.Debug|iPhone.ActiveCfg = Debug|Any CPU {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.Debug|iPhone.Build.0 = Debug|Any CPU {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.Release|Any CPU.ActiveCfg = Release|Any CPU {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.Release|Any CPU.Build.0 = Release|Any CPU {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.Release|iPhone.ActiveCfg = Debug|Any CPU {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.Release|iPhone.Build.0 = Debug|Any CPU {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.Release|iPhoneSimulator.ActiveCfg = Debug|Any CPU {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.Release|iPhoneSimulator.Build.0 = Debug|Any CPU {CEDCFABE-658C-42AB-BCE2-A96A7B17410E}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {CEDCFABE-658C-42AB-BCE2-A96A7B17410E}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {CEDCFABE-658C-42AB-BCE2-A96A7B17410E}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU {CEDCFABE-658C-42AB-BCE2-A96A7B17410E}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU {CEDCFABE-658C-42AB-BCE2-A96A7B17410E}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU {CEDCFABE-658C-42AB-BCE2-A96A7B17410E}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU {CEDCFABE-658C-42AB-BCE2-A96A7B17410E}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU {CEDCFABE-658C-42AB-BCE2-A96A7B17410E}.AppStore|Any CPU.Build.0 = Debug|Any CPU {CEDCFABE-658C-42AB-BCE2-A96A7B17410E}.AppStore|iPhone.ActiveCfg = Debug|Any CPU {CEDCFABE-658C-42AB-BCE2-A96A7B17410E}.AppStore|iPhone.Build.0 = Debug|Any CPU {CEDCFABE-658C-42AB-BCE2-A96A7B17410E}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU {CEDCFABE-658C-42AB-BCE2-A96A7B17410E}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU {CEDCFABE-658C-42AB-BCE2-A96A7B17410E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CEDCFABE-658C-42AB-BCE2-A96A7B17410E}.Debug|Any CPU.Build.0 = Debug|Any CPU {CEDCFABE-658C-42AB-BCE2-A96A7B17410E}.Debug|iPhone.ActiveCfg = Debug|Any CPU {CEDCFABE-658C-42AB-BCE2-A96A7B17410E}.Debug|iPhone.Build.0 = Debug|Any CPU {CEDCFABE-658C-42AB-BCE2-A96A7B17410E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU {CEDCFABE-658C-42AB-BCE2-A96A7B17410E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {CEDCFABE-658C-42AB-BCE2-A96A7B17410E}.Release|Any CPU.ActiveCfg = Release|Any CPU {CEDCFABE-658C-42AB-BCE2-A96A7B17410E}.Release|Any CPU.Build.0 = Release|Any CPU {CEDCFABE-658C-42AB-BCE2-A96A7B17410E}.Release|iPhone.ActiveCfg = Release|Any CPU {CEDCFABE-658C-42AB-BCE2-A96A7B17410E}.Release|iPhone.Build.0 = Release|Any CPU {CEDCFABE-658C-42AB-BCE2-A96A7B17410E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {CEDCFABE-658C-42AB-BCE2-A96A7B17410E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {2B15A32D-C213-4C60-981A-350E1569E5DA}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {2B15A32D-C213-4C60-981A-350E1569E5DA}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {2B15A32D-C213-4C60-981A-350E1569E5DA}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU {2B15A32D-C213-4C60-981A-350E1569E5DA}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU {2B15A32D-C213-4C60-981A-350E1569E5DA}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU {2B15A32D-C213-4C60-981A-350E1569E5DA}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU {2B15A32D-C213-4C60-981A-350E1569E5DA}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU {2B15A32D-C213-4C60-981A-350E1569E5DA}.AppStore|Any CPU.Build.0 = Debug|Any CPU {2B15A32D-C213-4C60-981A-350E1569E5DA}.AppStore|iPhone.ActiveCfg = Debug|Any CPU {2B15A32D-C213-4C60-981A-350E1569E5DA}.AppStore|iPhone.Build.0 = Debug|Any CPU {2B15A32D-C213-4C60-981A-350E1569E5DA}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU {2B15A32D-C213-4C60-981A-350E1569E5DA}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU {2B15A32D-C213-4C60-981A-350E1569E5DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2B15A32D-C213-4C60-981A-350E1569E5DA}.Debug|Any CPU.Build.0 = Debug|Any CPU {2B15A32D-C213-4C60-981A-350E1569E5DA}.Debug|iPhone.ActiveCfg = Debug|Any CPU {2B15A32D-C213-4C60-981A-350E1569E5DA}.Debug|iPhone.Build.0 = Debug|Any CPU {2B15A32D-C213-4C60-981A-350E1569E5DA}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU {2B15A32D-C213-4C60-981A-350E1569E5DA}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {2B15A32D-C213-4C60-981A-350E1569E5DA}.Release|Any CPU.ActiveCfg = Release|Any CPU {2B15A32D-C213-4C60-981A-350E1569E5DA}.Release|Any CPU.Build.0 = Release|Any CPU {2B15A32D-C213-4C60-981A-350E1569E5DA}.Release|iPhone.ActiveCfg = Release|Any CPU {2B15A32D-C213-4C60-981A-350E1569E5DA}.Release|iPhone.Build.0 = Release|Any CPU {2B15A32D-C213-4C60-981A-350E1569E5DA}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {2B15A32D-C213-4C60-981A-350E1569E5DA}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {D49BA669-1AE2-4779-922A-D5C1FF21C2DE}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {D49BA669-1AE2-4779-922A-D5C1FF21C2DE}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {D49BA669-1AE2-4779-922A-D5C1FF21C2DE}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU {D49BA669-1AE2-4779-922A-D5C1FF21C2DE}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU {D49BA669-1AE2-4779-922A-D5C1FF21C2DE}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU {D49BA669-1AE2-4779-922A-D5C1FF21C2DE}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU {D49BA669-1AE2-4779-922A-D5C1FF21C2DE}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU {D49BA669-1AE2-4779-922A-D5C1FF21C2DE}.AppStore|Any CPU.Build.0 = Debug|Any CPU {D49BA669-1AE2-4779-922A-D5C1FF21C2DE}.AppStore|iPhone.ActiveCfg = Debug|Any CPU {D49BA669-1AE2-4779-922A-D5C1FF21C2DE}.AppStore|iPhone.Build.0 = Debug|Any CPU {D49BA669-1AE2-4779-922A-D5C1FF21C2DE}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU {D49BA669-1AE2-4779-922A-D5C1FF21C2DE}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU {D49BA669-1AE2-4779-922A-D5C1FF21C2DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D49BA669-1AE2-4779-922A-D5C1FF21C2DE}.Debug|Any CPU.Build.0 = Debug|Any CPU {D49BA669-1AE2-4779-922A-D5C1FF21C2DE}.Debug|iPhone.ActiveCfg = Debug|Any CPU {D49BA669-1AE2-4779-922A-D5C1FF21C2DE}.Debug|iPhone.Build.0 = Debug|Any CPU {D49BA669-1AE2-4779-922A-D5C1FF21C2DE}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU {D49BA669-1AE2-4779-922A-D5C1FF21C2DE}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {D49BA669-1AE2-4779-922A-D5C1FF21C2DE}.Release|Any CPU.ActiveCfg = Release|Any CPU {D49BA669-1AE2-4779-922A-D5C1FF21C2DE}.Release|Any CPU.Build.0 = Release|Any CPU {D49BA669-1AE2-4779-922A-D5C1FF21C2DE}.Release|iPhone.ActiveCfg = Release|Any CPU {D49BA669-1AE2-4779-922A-D5C1FF21C2DE}.Release|iPhone.Build.0 = Release|Any CPU {D49BA669-1AE2-4779-922A-D5C1FF21C2DE}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {D49BA669-1AE2-4779-922A-D5C1FF21C2DE}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {26C7F5BF-9040-4BC5-B85A-579E7061A93B} EndGlobalSection EndGlobal ================================================ FILE: PokemonBattleEngineClient/App.xaml ================================================  ================================================ FILE: PokemonBattleEngineClient/App.xaml.cs ================================================ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; using Kermalis.PokemonBattleEngineClient.Models; using Kermalis.PokemonBattleEngineClient.Views; using System; namespace Kermalis.PokemonBattleEngineClient; public sealed class App : Application { public override void Initialize() { AvaloniaXamlLoader.Load(this); } public override void OnFrameworkInitializationCompleted() { MoveInfo.CreateBrushes(); HPBarView.CreateResources(); FieldView.CreateResources(); switch (ApplicationLifetime) { case null: break; case IClassicDesktopStyleApplicationLifetime desktop: desktop.MainWindow = new MainWindow(); break; case ISingleViewApplicationLifetime singleView: singleView.MainView = new MainView(); break; default: throw new PlatformNotSupportedException(); } base.OnFrameworkInitializationCompleted(); } } ================================================ FILE: PokemonBattleEngineClient/Clients/ActionsBuilder.cs ================================================ using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngineClient.Views; using System; using System.Linq; namespace Kermalis.PokemonBattleEngineClient.Clients; internal sealed class ActionsBuilder { private readonly BattleView _bv; private Action _onActionsReady; private int _index; private readonly PBEBattlePokemon[] _pkmn; private readonly PBETurnAction[] _actions; private readonly PBEBattlePokemon?[] _standBy; public ActionsBuilder(BattleView bv, PBETrainer trainer, Action onActionsReady) { _bv = bv; bv.Actions.ActionsBuilder = this; _onActionsReady = onActionsReady; _pkmn = trainer.ActiveBattlersOrdered.ToArray(); _actions = new PBETurnAction[_pkmn.Length]; _standBy = new PBEBattlePokemon?[_pkmn.Length]; ActionsLoop(); } public bool IsStandBy(PBEBattlePokemon p) { int i = Array.IndexOf(_standBy, p); return i != -1 && i < _index; } public void Pop() { _index--; ActionsLoop(); } public void PushMove(PBEMove move, PBETurnTarget targets) { PBEBattlePokemon pkmn = _pkmn[_index]; var a = new PBETurnAction(pkmn, move, targets); pkmn.TurnAction = a; _actions[_index] = a; _standBy[_index] = null; _index++; ActionsLoop(); } public void PushSwitch(PBEBattlePokemon switcher) { PBEBattlePokemon pkmn = _pkmn[_index]; var a = new PBETurnAction(pkmn, switcher); pkmn.TurnAction = a; _actions[_index] = a; _standBy[_index] = switcher; _index++; ActionsLoop(); } private void ActionsLoop() { if (_index == _pkmn.Length) { _bv.Actions.ActionsBuilder = null; _onActionsReady(_actions); _onActionsReady = null!; } else { PBEBattlePokemon pkmn = _pkmn[_index]; _bv.AddMessage($"What will {pkmn.Nickname} do?", messageLog: false); _bv.Actions.DisplayActions(_index, pkmn); } } } ================================================ FILE: PokemonBattleEngineClient/Clients/BattleClient.cs ================================================ using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Packets; using Kermalis.PokemonBattleEngineClient.Views; using System; namespace Kermalis.PokemonBattleEngineClient.Clients; internal abstract class BattleClient : IDisposable { protected const int WaitMilliseconds = 1750; public string Name { get; } public abstract PBEBattle Battle { get; } public abstract PBETrainer? Trainer { get; } public abstract BattleView BattleView { get; } public abstract bool HideNonOwned { get; } protected BattleClient(string name) { Name = name; } public bool ShouldUseKnownInfo(PBETrainer pkmnTrainer) { return pkmnTrainer != Trainer && HideNonOwned; } public abstract void Dispose(); protected void ShowAllPokemon() { foreach (PBEBattlePokemon pkmn in Battle.ActiveBattlers) { BattleView.Field.ShowPokemon(pkmn); } } #region Automatic packet processing // Returns true if the next packet should be run immediately protected virtual bool ProcessPacket(IPBEPacket packet) { switch (packet) { case PBEPkmnEXPChangedPacket _: case PBEMoveLockPacket _: case PBEMovePPChangedPacket _: case PBEIllusionPacket _: case PBETransformPacket _: case PBEBattlePacket _: case PBEActionsRequestPacket _: case PBESwitchInRequestPacket _: return true; /*case PBEPkmnEXPChangedPacket pecp: { PBEBattlePokemon pokemon = pecp.PokemonTrainer.GetPokemon(pecp.Pokemon); if (pokemon.FieldPosition != PBEFieldPosition.None) { BattleView.Field.UpdatePokemon(pokemon, true, false); } break; }*/ // Commented out because we don't have EXP bars case PBEPkmnFaintedPacket pfp: { PBEBattlePokemon pokemon = pfp.PokemonTrainer.GetPokemon(pfp.Pokemon); BattleView.Field.HidePokemon(pokemon, pfp.OldPosition); break; } case PBEPkmnFaintedPacket_Hidden pfph: { PBEBattlePokemon pokemon = pfph.PokemonTrainer.GetPokemon(pfph.OldPosition); BattleView.Field.HidePokemon(pokemon, pfph.OldPosition); break; } case IPBEPkmnFormChangedPacket pfcp: { PBEBattlePokemon pokemon = pfcp.PokemonTrainer.GetPokemon(pfcp.Pokemon); BattleView.Field.UpdatePokemon(pokemon, true, true); break; } case IPBEPkmnHPChangedPacket phcp: { PBEBattlePokemon pokemon = phcp.PokemonTrainer.GetPokemon(phcp.Pokemon); BattleView.Field.UpdatePokemon(pokemon, true, false); break; } case PBEPkmnLevelChangedPacket plcp: { PBEBattlePokemon pokemon = plcp.PokemonTrainer.GetPokemon(plcp.Pokemon); if (pokemon.FieldPosition != PBEFieldPosition.None) { BattleView.Field.UpdatePokemon(pokemon, true, false); } break; } case IPBEPkmnSwitchInPacket psip: { if (!psip.Forced) // TODO: Why not forced? bug? { foreach (IPBEPkmnSwitchInInfo_Hidden info in psip.SwitchIns) { BattleView.Field.ShowPokemon(psip.Trainer.GetPokemon(info.FieldPosition)); } } break; } case PBEPkmnSwitchOutPacket psop: { PBEBattlePokemon pokemon = psop.PokemonTrainer.GetPokemon(psop.Pokemon); BattleView.Field.HidePokemon(pokemon, psop.OldPosition); break; } case PBEPkmnSwitchOutPacket_Hidden psoph: { PBEBattlePokemon pokemon = psoph.PokemonTrainer.GetPokemon(psoph.OldPosition); BattleView.Field.HidePokemon(pokemon, psoph.OldPosition); break; } case PBEStatus1Packet s1p: { PBEBattlePokemon status1Receiver = s1p.Status1ReceiverTrainer.GetPokemon(s1p.Status1Receiver); BattleView.Field.UpdatePokemon(status1Receiver, true, false); break; } case PBEStatus2Packet s2p: { PBEBattlePokemon status2Receiver = s2p.Status2ReceiverTrainer.GetPokemon(s2p.Status2Receiver); switch (s2p.Status2) { case PBEStatus2.Airborne: BattleView.Field.UpdatePokemon(status2Receiver, false, true); break; case PBEStatus2.Disguised: { switch (s2p.StatusAction) { case PBEStatusAction.Ended: BattleView.Field.UpdatePokemon(status2Receiver, true, true); break; } break; } case PBEStatus2.ShadowForce: BattleView.Field.UpdatePokemon(status2Receiver, false, true); break; case PBEStatus2.Substitute: { switch (s2p.StatusAction) { case PBEStatusAction.Added: case PBEStatusAction.Ended: BattleView.Field.UpdatePokemon(status2Receiver, false, true); break; } break; } case PBEStatus2.Transformed: { switch (s2p.StatusAction) { case PBEStatusAction.Added: BattleView.Field.UpdatePokemon(status2Receiver, false, true); break; } break; } case PBEStatus2.Underground: BattleView.Field.UpdatePokemon(status2Receiver, false, true); break; case PBEStatus2.Underwater: BattleView.Field.UpdatePokemon(status2Receiver, false, true); break; } break; } case PBEWeatherPacket wp: { switch (wp.WeatherAction) { case PBEWeatherAction.Added: case PBEWeatherAction.Ended: BattleView.Field.UpdateWeather(); break; } break; } case IPBEWildPkmnAppearedPacket wpap: { PBETrainer wildTrainer = Battle.Teams[1].Trainers[0]; foreach (IPBEPkmnAppearedInfo_Hidden info in wpap.Pokemon) { BattleView.Field.ShowPokemon(wildTrainer.GetPokemon(info.FieldPosition)); } break; } case IPBEAutoCenterPacket acp: { PBEBattlePokemon pokemon0 = acp is IPBEAutoCenterPacket_0 acp0 ? acp.Pokemon0Trainer.GetPokemon(acp0.Pokemon0) : acp.Pokemon0Trainer.GetPokemon(acp.Pokemon0OldPosition); PBEBattlePokemon pokemon1 = acp is IPBEAutoCenterPacket_1 acp1 ? acp.Pokemon1Trainer.GetPokemon(acp1.Pokemon1) : acp.Pokemon1Trainer.GetPokemon(acp.Pokemon1OldPosition); BattleView.Field.MovePokemon(pokemon0, acp.Pokemon0OldPosition); BattleView.Field.MovePokemon(pokemon1, acp.Pokemon1OldPosition); break; } case PBETurnBeganPacket tbp: { BattleView.AddMessage($"Turn {tbp.TurnNumber}", messageBox: false); return true; } } string? message = PBEBattle.GetDefaultMessage(Battle, packet, showRawHP: !HideNonOwned, userTrainer: Trainer); if (string.IsNullOrEmpty(message)) { return true; } BattleView.AddMessage(message); return false; } #endregion } ================================================ FILE: PokemonBattleEngineClient/Clients/NetworkClient.cs ================================================ using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data.Legality; using Kermalis.PokemonBattleEngine.Network; using Kermalis.PokemonBattleEngine.Packets; using Kermalis.PokemonBattleEngineClient.Views; using System; using System.Diagnostics; using System.Net; namespace Kermalis.PokemonBattleEngineClient.Clients; internal sealed class NetworkClientConnection : IDisposable { private readonly PBEClient _client; private readonly PBELegalPokemonCollection _party; private readonly Action _action; private byte _battleId = byte.MaxValue; // Spectator by default public NetworkClientConnection(string host, ushort port, PBELegalPokemonCollection party, Action action) { _party = party; _action = action; _client = new PBEClient(); _client.Disconnected += OnDisconnected; _client.Error += OnError; _client.PacketReceived += OnPacketReceived; if (_client.Connect(new IPEndPoint(IPAddress.Parse(host), port), 10 * 1000, new PBEPacketProcessor())) { OnConnected(); } else { _client.Dispose(); action.Invoke(null); } } private void OnConnected() { Debug.WriteLine($"Connecting... connected to {_client.RemoteIP}"); _action.Invoke("Waiting for players..."); } private void OnDisconnected(object? sender, EventArgs e) { Debug.WriteLine("Connecting... disconnected from host"); _action.Invoke(null); } private void OnError(object? sender, Exception ex) { Debug.WriteLine($"Connecting... error: {ex}"); } private void OnPacketReceived(object? sender, IPBEPacket packet) { Debug.WriteLine($"Connecting... received \"{packet.GetType().Name}\""); switch (packet) { case PBEMatchCancelledPacket _: { _action.Invoke(null); break; } case PBEPartyRequestPacket prp: { _battleId = prp.BattleId; if (prp.RequireLegal) { Send(new PBELegalPartyResponsePacket(_party)); } else { Send(new PBEPartyResponsePacket(_party)); } break; } case PBEBattlePacket bp: { _client.Disconnected -= OnDisconnected; _client.Error -= OnError; _client.PacketReceived -= OnPacketReceived; _action.Invoke(Tuple.Create(_client, bp, _battleId)); // Response will be sent in NetworkClient constructor so the server doesn't send packets between threads break; } default: throw new ArgumentOutOfRangeException(nameof(packet)); } } private void Send(IPBEPacket packet) { if (_client.IsConnected) { _client.Send(packet); } } public void Dispose() { _client.Dispose(); // Unsubscribe events } } internal sealed class NetworkClient : NonLocalClient { private readonly PBEClient _client; public override PBEBattle Battle { get; } public override PBETrainer? Trainer { get; } public override BattleView BattleView { get; } public override bool HideNonOwned => true; public NetworkClient(PBEClient client, PBEBattlePacket bp, byte battleId, string name) : base(name) { var b = PBEBattle.CreateRemoteBattle(bp); Battle = b; if (battleId != byte.MaxValue) { Trainer = b.Trainers[battleId]; } BattleView = new BattleView(this); client.Battle = b; _client = client; client.Disconnected += OnDisconnected; client.Error += OnError; client.PacketReceived += OnPacketReceived; ShowAllPokemon(); Send(new PBEResponsePacket()); } private void OnDisconnected(object? sender, EventArgs e) { Debug.WriteLine($"{Name} disconnected from host"); BattleView.AddMessage("Disconnected from host.", messageBox: false); } private void OnError(object? sender, Exception ex) { Debug.WriteLine($"{Name} error: {ex}"); } private void OnPacketReceived(object? sender, IPBEPacket packet) { Debug.WriteLine($"{Name} received \"{packet.GetType().Name}\""); switch (packet) { case PBEMatchCancelledPacket _: { BattleView.AddMessage("Match cancelled!", messageBox: false); break; } case PBEPlayerJoinedPacket pjp: { BattleView.AddMessage(string.Format("{0} joined the game.", pjp.TrainerName), messageBox: false); Send(new PBEResponsePacket()); break; } case PBEActionsRequestPacket _: case PBESwitchInRequestPacket _: case PBEBattleResultPacket _: { Battle.Events.Add(packet); Send(new PBEResponsePacket()); StartPacketThread(); break; } default: { BattleView.AddMessage("Communicating...", messageLog: false); Battle.Events.Add(packet); Send(new PBEResponsePacket()); break; } } } private void OnActionsReady(PBETurnAction[] acts) { BattleView.AddMessage("Waiting for players...", messageLog: false); Send(new PBEActionsResponsePacket(acts)); } private void OnSwitchesReady(PBESwitchIn[] switches) { BattleView.AddMessage("Waiting for players...", messageLog: false); Send(new PBESwitchInResponsePacket(switches)); } private void Send(IPBEPacket packet) { if (_client.IsConnected) { _client.Send(packet); } } public override void Dispose() { base.Dispose(); _client.Dispose(); // Events unsubscribed } protected override bool ProcessPacket(IPBEPacket packet) { switch (packet) { case PBEMovePPChangedPacket mpcp: { PBEBattlePokemon moveUser = mpcp.MoveUserTrainer.GetPokemon(mpcp.MoveUser); if (moveUser.Trainer == Trainer) { moveUser.Moves[mpcp.Move]!.PP -= mpcp.AmountReduced; } break; } case PBEActionsRequestPacket arp: { if (arp.Trainer == Trainer) { _ = new ActionsBuilder(BattleView, Trainer, OnActionsReady); } else if (Trainer is null || Trainer.NumConsciousPkmn == 0) // Spectators/KO'd { BattleView.AddMessage("Waiting for players...", messageLog: false); } return true; } case PBESwitchInRequestPacket sirp: { PBETrainer t = sirp.Trainer; t.SwitchInsRequired = sirp.Amount; if (t == Trainer) { _ = new SwitchesBuilder(BattleView, sirp.Amount, OnSwitchesReady); } else if (BattleView.Actions.SwitchesBuilder?.SwitchesRequired == 0) // No need to switch/Spectators/KO'd { BattleView.AddMessage("Waiting for players...", messageLog: false); } return true; } case PBEPkmnFaintedPacket_Hidden pfph: { bool ret = base.ProcessPacket(packet); // Process before removal PBEBattlePokemon pokemon = pfph.PokemonTrainer.GetPokemon(pfph.OldPosition); Battle.ActiveBattlers.Remove(pokemon); pokemon.FieldPosition = PBEFieldPosition.None; PBETrainer.Remove(pokemon); return ret; } case PBEPkmnFormChangedPacket_Hidden pfcph: { PBEBattlePokemon pokemon = pfcph.PokemonTrainer.GetPokemon(pfcph.Pokemon); pokemon.HPPercentage = pfcph.NewHPPercentage; pokemon.KnownAbility = pfcph.NewKnownAbility; pokemon.KnownForm = pfcph.NewForm; pokemon.KnownType1 = pfcph.NewType1; pokemon.KnownType2 = pfcph.NewType2; pokemon.KnownWeight = pfcph.NewWeight; break; } case PBEPkmnHPChangedPacket_Hidden phcph: { PBEBattlePokemon pokemon = phcph.PokemonTrainer.GetPokemon(phcph.Pokemon); pokemon.HPPercentage = phcph.NewHPPercentage; break; } case PBEPkmnSwitchInPacket_Hidden psiph: { foreach (PBEPkmnSwitchInPacket_Hidden.PBEPkmnSwitchInInfo info in psiph.SwitchIns) { _ = new PBEBattlePokemon(psiph.Trainer, info); } break; } case PBEPkmnSwitchOutPacket_Hidden psoph: { bool ret = base.ProcessPacket(packet); // Process before removal PBEBattlePokemon pokemon = psoph.PokemonTrainer.GetPokemon(psoph.OldPosition); Battle.ActiveBattlers.Remove(pokemon); PBETrainer.Remove(pokemon); return ret; } case PBEReflectTypePacket_Hidden rtph: { PBEBattlePokemon user = rtph.UserTrainer.GetPokemon(rtph.User); PBEBattlePokemon target = rtph.TargetTrainer.GetPokemon(rtph.Target); user.Type1 = user.KnownType1 = target.KnownType1; // Set Type1 and Type2 so Transform works user.Type2 = user.KnownType2 = target.KnownType2; break; } case PBEWildPkmnAppearedPacket wpap: { PBETrainer wildTrainer = Battle.Teams[1].Trainers[0]; foreach (PBEPkmnAppearedInfo info in wpap.Pokemon) { PBEBattlePokemon pokemon = wildTrainer.GetPokemon(info.Pokemon); pokemon.FieldPosition = info.FieldPosition; Battle.ActiveBattlers.Add(pokemon); } break; } case PBEWildPkmnAppearedPacket_Hidden wpaph: { foreach (PBEWildPkmnAppearedPacket_Hidden.PBEWildPkmnInfo info in wpaph.Pokemon) { _ = new PBEBattlePokemon(Battle, info); } break; } } return base.ProcessPacket(packet); } } ================================================ FILE: PokemonBattleEngineClient/Clients/NonLocalClient.cs ================================================ using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngine.Packets; using System.IO; using System.Threading; namespace Kermalis.PokemonBattleEngineClient.Clients; internal abstract class NonLocalClient : BattleClient { protected NonLocalClient(string name) : base(name) { } public override void Dispose() { _stopPacketThread = true; } #region Automatic packet processing private int _currentPacket = -1; private Thread? _packetThread; private readonly object _packetThreadLockObj = new(); private bool _plsStartPacketThreadForMe = false; private bool _stopPacketThread = false; private void CreateThread__Unsafe() { _packetThread = new Thread(PacketThread) { Name = "Packet Thread" }; _packetThread.Start(); } protected void StartPacketThread() { lock (_packetThreadLockObj) { _stopPacketThread = false; if (_packetThread is null) { CreateThread__Unsafe(); } else { _plsStartPacketThreadForMe = true; } } } private void PacketThread() { while (!_stopPacketThread && _currentPacket < Battle.Events.Count - 1) { _plsStartPacketThreadForMe = false; _currentPacket++; if (!ProcessPacket(Battle.Events[_currentPacket])) { Thread.Sleep(WaitMilliseconds); } } lock (_packetThreadLockObj) { if (_plsStartPacketThreadForMe) { CreateThread__Unsafe(); } else { _packetThread = null; } } } private static 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; } } protected override bool ProcessPacket(IPBEPacket packet) { switch (packet) { case PBEAbilityPacket ap: { PBEBattlePokemon abilityOwner = ap.AbilityOwnerTrainer.GetPokemon(ap.AbilityOwner); abilityOwner.Ability = ap.Ability; abilityOwner.KnownAbility = ap.Ability; break; } case PBEAbilityReplacedPacket arp: { PBEBattlePokemon abilityOwner = arp.AbilityOwnerTrainer.GetPokemon(arp.AbilityOwner); abilityOwner.Ability = arp.NewAbility; abilityOwner.KnownAbility = arp.NewAbility; break; } case PBEBattleStatusPacket bsp: { switch (bsp.BattleStatusAction) { case PBEBattleStatusAction.Added: Battle.BattleStatus |= bsp.BattleStatus; break; case PBEBattleStatusAction.Cleared: case PBEBattleStatusAction.Ended: Battle.BattleStatus &= ~bsp.BattleStatus; break; default: throw new InvalidDataException(nameof(bsp.BattleStatusAction)); } break; } case PBECapturePacket cp: { if (cp.Success) { PBEBattlePokemon pokemon = cp.PokemonTrainer.GetPokemon(cp.Pokemon); pokemon.CaughtBall = cp.Ball; pokemon.KnownCaughtBall = cp.Ball; } break; } case PBEHazePacket _: { foreach (PBEBattlePokemon pkmn in Battle.ActiveBattlers) { pkmn.ClearStatChanges(); } break; } case PBEIllusionPacket ilp: { PBEBattlePokemon pokemon = ilp.PokemonTrainer.GetPokemon(ilp.Pokemon); pokemon.Ability = pokemon.KnownAbility = PBEAbility.Illusion; pokemon.Gender = pokemon.KnownGender = ilp.ActualGender; pokemon.CaughtBall = pokemon.KnownCaughtBall = ilp.ActualCaughtBall; pokemon.Nickname = pokemon.KnownNickname = ilp.ActualNickname; pokemon.Shiny = pokemon.KnownShiny = ilp.ActualShiny; pokemon.Species = pokemon.KnownSpecies = ilp.ActualSpecies; pokemon.Form = pokemon.KnownForm = ilp.ActualForm; pokemon.Type1 = pokemon.KnownType1 = ilp.ActualType1; pokemon.Type2 = pokemon.KnownType2 = ilp.ActualType2; pokemon.Weight = pokemon.KnownWeight = ilp.ActualWeight; return true; } case PBEItemPacket ip: { PBEBattlePokemon itemHolder = ip.ItemHolderTrainer.GetPokemon(ip.ItemHolder); switch (ip.ItemAction) { case PBEItemAction.Announced: case PBEItemAction.Damage: case PBEItemAction.RestoredHP: itemHolder.Item = itemHolder.KnownItem = ip.Item; break; case PBEItemAction.Consumed: itemHolder.Item = itemHolder.KnownItem = PBEItem.None; break; default: throw new InvalidDataException(nameof(ip.ItemAction)); } break; } case PBEMoveLockPacket mlp: { PBEBattlePokemon moveUser = mlp.MoveUserTrainer.GetPokemon(mlp.MoveUser); switch (mlp.MoveLockType) { case PBEMoveLockType.ChoiceItem: moveUser.ChoiceLockedMove = mlp.LockedMove; break; case PBEMoveLockType.Temporary: moveUser.TempLockedMove = mlp.LockedMove; break; default: throw new InvalidDataException(nameof(mlp.MoveLockType)); } if (mlp.LockedTargets is not null) { moveUser.TempLockedTargets = mlp.LockedTargets.Value; } return true; } case PBEMovePPChangedPacket mpcp: { PBEBattlePokemon moveUser = mpcp.MoveUserTrainer.GetPokemon(mpcp.MoveUser); moveUser.UpdateKnownPP(mpcp.Move, mpcp.AmountReduced); return true; } case PBEMoveUsedPacket mup: { PBEBattlePokemon moveUser = mup.MoveUserTrainer.GetPokemon(mup.MoveUser); if (mup.Owned && !moveUser.KnownMoves.Contains(mup.Move)) { moveUser.KnownMoves[PBEMove.MAX]!.Move = mup.Move; PBEBattleMoveset.PBEBattleMovesetSlot? slot = moveUser.Moves[PBEMove.MAX]; if (slot is not null) { slot.Move = mup.Move; // Copy to Moves as well so Transform doesn't break for spectators/allies } } break; } case PBEPkmnEXPChangedPacket pecp: { PBEBattlePokemon pokemon = pecp.PokemonTrainer.GetPokemon(pecp.Pokemon); pokemon.EXP = pecp.NewEXP; break; } case PBEPkmnFaintedPacket pfp: { bool ret = base.ProcessPacket(packet); // Process before removal PBEBattlePokemon pokemon = pfp.PokemonTrainer.GetPokemon(pfp.Pokemon); Battle.ActiveBattlers.Remove(pokemon); pokemon.ClearForFaint(); return ret; } case PBEPkmnFormChangedPacket pfcp: { PBEBattlePokemon pokemon = pfcp.PokemonTrainer.GetPokemon(pfcp.Pokemon); pokemon.HP = pfcp.NewHP; pokemon.MaxHP = pfcp.NewMaxHP; pokemon.HPPercentage = pfcp.NewHPPercentage; pokemon.Attack = pfcp.NewAttack; pokemon.Defense = pfcp.NewDefense; pokemon.SpAttack = pfcp.NewSpAttack; pokemon.SpDefense = pfcp.NewSpDefense; pokemon.Speed = pfcp.NewSpeed; pokemon.Ability = pfcp.NewAbility; pokemon.KnownAbility = pfcp.NewKnownAbility; pokemon.Form = pokemon.KnownForm = pfcp.NewForm; pokemon.Type1 = pokemon.KnownType1 = pfcp.NewType1; pokemon.Type2 = pokemon.KnownType2 = pfcp.NewType2; pokemon.Weight = pokemon.KnownWeight = pfcp.NewWeight; if (pfcp.IsRevertForm) { pokemon.RevertForm = pfcp.NewForm; pokemon.RevertAbility = pfcp.NewAbility; } break; } case PBEPkmnHPChangedPacket phcp: { PBEBattlePokemon pokemon = phcp.PokemonTrainer.GetPokemon(phcp.Pokemon); pokemon.HP = phcp.NewHP; pokemon.HPPercentage = phcp.NewHPPercentage; break; } case PBEPkmnLevelChangedPacket plcp: { PBEBattlePokemon pokemon = plcp.PokemonTrainer.GetPokemon(plcp.Pokemon); pokemon.Level = plcp.NewLevel; break; } case PBEPkmnStatChangedPacket pscp: { PBEBattlePokemon pokemon = pscp.PokemonTrainer.GetPokemon(pscp.Pokemon); pokemon.SetStatChange(pscp.Stat, pscp.NewValue); break; } case PBEPkmnSwitchInPacket psip: { foreach (PBEPkmnAppearedInfo info in psip.SwitchIns) { PBEBattlePokemon pokemon = psip.Trainer.GetPokemon(info.Pokemon); pokemon.FieldPosition = info.FieldPosition; PBETrainer.SwitchTwoPokemon(pokemon, info.FieldPosition); DoDisguisedAppearance(pokemon, info); Battle.ActiveBattlers.Add(pokemon); } break; } case PBEPkmnSwitchOutPacket psop: { bool ret = base.ProcessPacket(packet); // Process before removal PBEBattlePokemon pokemon = psop.PokemonTrainer.GetPokemon(psop.Pokemon); Battle.ActiveBattlers.Remove(pokemon); pokemon.ClearForSwitch(); return ret; } case PBEPsychUpPacket pup: { PBEBattlePokemon user = pup.UserTrainer.GetPokemon(pup.User); PBEBattlePokemon target = pup.TargetTrainer.GetPokemon(pup.Target); user.AttackChange = target.AttackChange = pup.AttackChange; user.DefenseChange = target.DefenseChange = pup.DefenseChange; user.SpAttackChange = target.SpAttackChange = pup.SpAttackChange; user.SpDefenseChange = target.SpDefenseChange = pup.SpDefenseChange; user.SpeedChange = target.SpeedChange = pup.SpeedChange; user.AccuracyChange = target.AccuracyChange = pup.AccuracyChange; user.EvasionChange = target.EvasionChange = pup.EvasionChange; break; } case PBEReflectTypePacket rtp: { PBEBattlePokemon user = rtp.UserTrainer.GetPokemon(rtp.User); PBEBattlePokemon target = rtp.TargetTrainer.GetPokemon(rtp.Target); user.Type1 = user.KnownType1 = target.KnownType1 = target.Type1 = rtp.Type1; user.Type2 = user.KnownType2 = target.KnownType2 = target.Type2 = rtp.Type2; break; } case PBEStatus1Packet s1p: { PBEBattlePokemon status1Receiver = s1p.Status1ReceiverTrainer.GetPokemon(s1p.Status1Receiver); switch (s1p.StatusAction) { case PBEStatusAction.Added: case PBEStatusAction.Announced: case PBEStatusAction.CausedImmobility: case PBEStatusAction.Damage: status1Receiver.Status1 = s1p.Status1; break; case PBEStatusAction.Cleared: case PBEStatusAction.Ended: status1Receiver.Status1 = PBEStatus1.None; break; default: throw new InvalidDataException(nameof(s1p.StatusAction)); } break; } case PBEStatus2Packet s2p: { PBEBattlePokemon status2Receiver = s2p.Status2ReceiverTrainer.GetPokemon(s2p.Status2Receiver); PBEBattlePokemon pokemon2 = s2p.Pokemon2Trainer.GetPokemon(s2p.Pokemon2); switch (s2p.StatusAction) { case PBEStatusAction.Added: case PBEStatusAction.Announced: case PBEStatusAction.CausedImmobility: case PBEStatusAction.Damage: status2Receiver.Status2 |= s2p.Status2; status2Receiver.KnownStatus2 |= s2p.Status2; break; case PBEStatusAction.Cleared: case PBEStatusAction.Ended: status2Receiver.Status2 &= ~s2p.Status2; status2Receiver.KnownStatus2 &= ~s2p.Status2; break; default: throw new InvalidDataException(nameof(s2p.StatusAction)); } switch (s2p.Status2) { case PBEStatus2.Infatuated: { switch (s2p.StatusAction) { case PBEStatusAction.Added: status2Receiver.InfatuatedWithPokemon = pokemon2; break; case PBEStatusAction.Cleared: case PBEStatusAction.Ended: status2Receiver.InfatuatedWithPokemon = null; break; } break; } case PBEStatus2.LeechSeed: { switch (s2p.StatusAction) { case PBEStatusAction.Added: status2Receiver.SeededPosition = pokemon2.FieldPosition; status2Receiver.SeededTeam = pokemon2.Team; break; } break; } case PBEStatus2.LockOn: { switch (s2p.StatusAction) { case PBEStatusAction.Added: status2Receiver.LockOnPokemon = pokemon2; break; case PBEStatusAction.Ended: status2Receiver.LockOnPokemon = null; break; } break; } case PBEStatus2.PowerTrick: { switch (s2p.StatusAction) { case PBEStatusAction.Added: status2Receiver.ApplyPowerTrickChange(); break; } break; } case PBEStatus2.Roost: { switch (s2p.StatusAction) { case PBEStatusAction.Added: status2Receiver.StartRoost(); break; case PBEStatusAction.Ended: status2Receiver.EndRoost(); break; } break; } case PBEStatus2.Transformed: { switch (s2p.StatusAction) { case PBEStatusAction.Added: status2Receiver.Transform(pokemon2); break; } break; } } break; } case PBETeamStatusPacket tsp: { PBETeam team = tsp.Team; switch (tsp.TeamStatusAction) { case PBETeamStatusAction.Added: case PBETeamStatusAction.Cleared: case PBETeamStatusAction.Ended: team.TeamStatus &= ~tsp.TeamStatus; break; default: throw new InvalidDataException(nameof(tsp.TeamStatusAction)); } switch (tsp.TeamStatus) { case PBETeamStatus.Spikes: { switch (tsp.TeamStatusAction) { case PBETeamStatusAction.Added: team.SpikeCount++; break; //case PBETeamStatusAction.Cleared: team.SpikeCount = 0; break; } break; } case PBETeamStatus.ToxicSpikes: { switch (tsp.TeamStatusAction) { case PBETeamStatusAction.Added: team.ToxicSpikeCount++; break; case PBETeamStatusAction.Cleared: team.ToxicSpikeCount = 0; break; } break; } } break; } case PBETeamStatusDamagePacket tsdp: { tsdp.Team.TeamStatus |= tsdp.TeamStatus; break; } case PBETransformPacket tp: { PBEBattlePokemon target = tp.TargetTrainer.GetPokemon(tp.Target); target.Attack = tp.TargetAttack; target.Defense = tp.TargetDefense; target.SpAttack = tp.TargetSpAttack; target.SpDefense = tp.TargetSpDefense; target.Speed = tp.TargetSpeed; target.AttackChange = tp.TargetAttackChange; target.DefenseChange = tp.TargetDefenseChange; target.SpAttackChange = tp.TargetSpAttackChange; target.SpDefenseChange = tp.TargetSpDefenseChange; target.SpeedChange = tp.TargetSpeedChange; target.AccuracyChange = tp.TargetAccuracyChange; target.EvasionChange = tp.TargetEvasionChange; target.Ability = target.KnownAbility = tp.TargetAbility; for (int i = 0; i < Battle.Settings.NumMoves; i++) { target.Moves[i].Move = tp.TargetMoves[i]; } target.Species = target.KnownSpecies = tp.TargetSpecies; target.Form = target.KnownForm = tp.TargetForm; target.Type1 = target.KnownType1 = tp.TargetType1; target.Type2 = target.KnownType2 = tp.TargetType2; target.Weight = target.KnownWeight = tp.TargetWeight; return true; } case PBETypeChangedPacket tcp: { PBEBattlePokemon pokemon = tcp.PokemonTrainer.GetPokemon(tcp.Pokemon); pokemon.Type1 = pokemon.KnownType1 = tcp.Type1; pokemon.Type2 = pokemon.KnownType2 = tcp.Type2; break; } case PBEWeatherPacket wp: { switch (wp.WeatherAction) { case PBEWeatherAction.Added: case PBEWeatherAction.Ended: Battle.Weather = PBEWeather.None; break; default: throw new InvalidDataException(nameof(wp.WeatherAction)); } break; } case PBEWeatherDamagePacket wdp: { Battle.Weather = wdp.Weather; break; } case PBEWildPkmnAppearedPacket wpap: { PBETrainer wildTrainer = Battle.Teams[1].Trainers[0]; foreach (PBEPkmnAppearedInfo info in wpap.Pokemon) { PBEBattlePokemon pokemon = wildTrainer.GetPokemon(info.Pokemon); DoDisguisedAppearance(pokemon, info); } break; } case IPBEAutoCenterPacket acp: { PBEBattlePokemon pokemon0 = acp.Pokemon0Trainer.GetPokemon(acp.Pokemon0OldPosition); PBEBattlePokemon pokemon1 = acp.Pokemon1Trainer.GetPokemon(acp.Pokemon1OldPosition); pokemon0.FieldPosition = PBEFieldPosition.Center; pokemon1.FieldPosition = PBEFieldPosition.Center; break; } case PBEBattleResultPacket brp: { Battle.BattleResult = brp.BattleResult; break; } case PBETurnBeganPacket tbp: { Battle.TurnNumber = tbp.TurnNumber; break; } } return base.ProcessPacket(packet); } #endregion } ================================================ FILE: PokemonBattleEngineClient/Clients/ReplayClient.cs ================================================ using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Packets; using Kermalis.PokemonBattleEngineClient.Views; namespace Kermalis.PokemonBattleEngineClient.Clients; internal sealed class ReplayClient : NonLocalClient { public override PBEBattle Battle { get; } public override PBETrainer? Trainer => null; public override BattleView BattleView { get; } public override bool HideNonOwned => false; public ReplayClient(string path, string name) : base(name) { Battle = PBEBattle.LoadReplay(path, new PBEPacketProcessor()); BattleView = new BattleView(this); ShowAllPokemon(); StartPacketThread(); } protected override bool ProcessPacket(IPBEPacket packet) { switch (packet) { case PBEMovePPChangedPacket mpcp: { PBEBattlePokemon moveUser = mpcp.MoveUserTrainer.GetPokemon(mpcp.MoveUser); moveUser.Moves[mpcp.Move]!.PP -= mpcp.AmountReduced; break; } case PBEActionsRequestPacket _: case PBESwitchInRequestPacket _: return true; } return base.ProcessPacket(packet); } } ================================================ FILE: PokemonBattleEngineClient/Clients/SinglePlayerClient.cs ================================================ using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.DefaultData.AI; using Kermalis.PokemonBattleEngine.Packets; using Kermalis.PokemonBattleEngineClient.Views; using System.Threading; namespace Kermalis.PokemonBattleEngineClient.Clients; internal sealed class SinglePlayerClient : BattleClient { public override PBEBattle Battle { get; } public override PBETrainer? Trainer { get; } public override BattleView BattleView { get; } public override bool HideNonOwned => true; private readonly PBEDDAI[] _ais; public SinglePlayerClient(PBEBattle b, string name) : base(name) { Battle = b; Trainer = b.Trainers[0]; BattleView = new BattleView(this); b.OnNewEvent += SinglePlayerBattle_OnNewEvent; b.OnStateChanged += SinglePlayerBattle_OnStateChanged; _ais = new PBEDDAI[b.Trainers.Count - 1]; for (int i = 0; i < _ais.Length; i++) { _ais[i] = new PBEDDAI(b.Trainers[i + 1]); } ShowAllPokemon(); CreateBattleThread(b.Begin); } private static void CreateBattleThread(ThreadStart start) { new Thread(start) { Name = "Battle Thread" }.Start(); } private void SinglePlayerBattle_OnNewEvent(PBEBattle battle, IPBEPacket packet) { if (!ProcessPacket(packet)) { Thread.Sleep(WaitMilliseconds); } } private void SinglePlayerBattle_OnStateChanged(PBEBattle battle) { switch (battle.BattleState) { case PBEBattleState.Ended: battle.SaveReplay("SinglePlayer Battle.pbereplay"); break; case PBEBattleState.ReadyToRunSwitches: CreateBattleThread(battle.RunSwitches); break; case PBEBattleState.ReadyToRunTurn: CreateBattleThread(battle.RunTurn); break; } } private void OnActionsReady(PBETurnAction[] acts) { CreateBattleThread(() => Trainer!.SelectActionsIfValid(out _, acts)); } private void OnSwitchesReady(PBESwitchIn[] switches) { CreateBattleThread(() => Trainer!.SelectSwitchesIfValid(out _, switches)); } private PBEDDAI GetAI(PBETrainer t) { return _ais[t.Id - 1]; } public override void Dispose() { Battle.SetEnded(); // Events unsubscribed } protected override bool ProcessPacket(IPBEPacket packet) { switch (packet) { case PBEActionsRequestPacket arp: { PBETrainer t = arp.Trainer; if (t == Trainer) { _ = new ActionsBuilder(BattleView, Trainer, OnActionsReady); } else { CreateBattleThread(GetAI(t).CreateActions); } return true; } case PBESwitchInRequestPacket sirp: { PBETrainer t = sirp.Trainer; if (t == Trainer) { _ = new SwitchesBuilder(BattleView, sirp.Amount, OnSwitchesReady); } else { CreateBattleThread(GetAI(t).CreateSwitches); } return true; } } return base.ProcessPacket(packet); } } ================================================ FILE: PokemonBattleEngineClient/Clients/SwitchesBuilder.cs ================================================ using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngineClient.Views; using System; namespace Kermalis.PokemonBattleEngineClient.Clients; internal sealed class SwitchesBuilder { private readonly BattleView _bv; private Action _onSwitchesReady; public readonly byte SwitchesRequired; private int _index; private readonly PBESwitchIn[] _switches; private readonly PBEBattlePokemon[] _standBy; private readonly PBEFieldPosition[] _positionStandBy; public SwitchesBuilder(BattleView bv, byte amount, Action onSwitchesReady) { _bv = bv; bv.Actions.SwitchesBuilder = this; _onSwitchesReady = onSwitchesReady; SwitchesRequired = amount; _switches = new PBESwitchIn[amount]; _standBy = new PBEBattlePokemon[amount]; _positionStandBy = new PBEFieldPosition[amount]; SwitchesLoop(); } public bool IsStandBy(PBEBattlePokemon p) { int i = Array.IndexOf(_standBy, p); return i != -1 && i < _index; } public bool IsStandBy(PBEFieldPosition p) { int i = Array.IndexOf(_positionStandBy, p); return i != -1 && i < _index; } public void Pop() { _index--; SwitchesLoop(); } public void Push(PBEBattlePokemon pkmn, PBEFieldPosition pos) { _switches[_index] = new PBESwitchIn(pkmn, pos); _standBy[_index] = pkmn; _positionStandBy[_index] = pos; _index++; SwitchesLoop(); } private void SwitchesLoop() { if (_index == SwitchesRequired) { _bv.Actions.SwitchesBuilder = null; _onSwitchesReady(_switches); _onSwitchesReady = null!; } else { _bv.AddMessage($"You must send in {SwitchesRequired - _index} Pokémon.", messageLog: false); _bv.Actions.DisplaySwitches(_index); } } } ================================================ FILE: PokemonBattleEngineClient/Infrastructure/BetterWrapPanel.cs ================================================ // This source file is adapted from the Avalonia project. // (https://github.com/AvaloniaUI/Avalonia) using Avalonia; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Layout; using Avalonia.Utilities; using static System.Math; namespace Kermalis.PokemonBattleEngineClient.Infrastructure; public sealed class BetterWrapPanel : Panel, INavigableContainer { public static readonly StyledProperty OrientationProperty = AvaloniaProperty.Register(nameof(Orientation), defaultValue: Orientation.Horizontal); public static readonly StyledProperty HorizontalContentAlignmentProperty = AvaloniaProperty.Register(nameof(HorizontalContentAlignment), defaultValue: HorizontalAlignment.Left); public static readonly StyledProperty VerticalContentAlignmentProperty = AvaloniaProperty.Register(nameof(VerticalContentAlignment), defaultValue: VerticalAlignment.Top); static BetterWrapPanel() { AffectsMeasure(OrientationProperty); AffectsArrange(HorizontalContentAlignmentProperty, VerticalContentAlignmentProperty); } public Orientation Orientation { get => GetValue(OrientationProperty); set => SetValue(OrientationProperty, value); } public HorizontalAlignment HorizontalContentAlignment { get => GetValue(HorizontalContentAlignmentProperty); set => SetValue(HorizontalContentAlignmentProperty, value); } public VerticalAlignment VerticalContentAlignment { get => GetValue(VerticalContentAlignmentProperty); set => SetValue(VerticalContentAlignmentProperty, value); } IInputElement INavigableContainer.GetControl(NavigationDirection direction, IInputElement from, bool wrap) { bool horiz = Orientation == Orientation.Horizontal; int index = Children.IndexOf((IControl)from); switch (direction) { case NavigationDirection.First: index = 0; break; case NavigationDirection.Last: index = Children.Count - 1; break; case NavigationDirection.Next: ++index; break; case NavigationDirection.Previous: --index; break; case NavigationDirection.Left: index = horiz ? index - 1 : -1; break; case NavigationDirection.Right: index = horiz ? index + 1 : -1; break; case NavigationDirection.Up: index = horiz ? -1 : index - 1; break; case NavigationDirection.Down: index = horiz ? -1 : index + 1; break; } if (index >= 0 && index < Children.Count) { return Children[index]; } else { return null!; // ? } } protected override Size MeasureOverride(Size constraint) { var curLineSize = new UVSize(Orientation); var panelSize = new UVSize(Orientation); var uvConstraint = new UVSize(Orientation, constraint.Width, constraint.Height); var childConstraint = new Size(constraint.Width, constraint.Height); for (int i = 0, count = Children.Count; i < count; i++) { IControl child = Children[i]; if (child is null) { continue; } //Flow passes its own constrint to children child.Measure(childConstraint); //this is the size of the child in UV space var sz = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height); if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvConstraint.U)) //need to switch to another line { panelSize.U = Max(curLineSize.U, panelSize.U); panelSize.V += curLineSize.V; curLineSize = sz; if (MathUtilities.GreaterThan(sz.U, uvConstraint.U)) //the element is wider then the constrint - give it a separate line { panelSize.U = Max(sz.U, panelSize.U); panelSize.V += sz.V; curLineSize = new UVSize(Orientation); } } else //continue to accumulate a line { curLineSize.U += sz.U; curLineSize.V = Max(sz.V, curLineSize.V); } } //the last line size, if any should be added panelSize.U = Max(curLineSize.U, panelSize.U); panelSize.V += curLineSize.V; //go from UV space to W/H space return new Size(panelSize.Width, panelSize.Height); } protected override Size ArrangeOverride(Size finalSize) { int firstInLine = 0; double accumulatedV = 0; var curLineSize = new UVSize(Orientation); var uvFinalSize = new UVSize(Orientation, finalSize.Width, finalSize.Height); for (int i = 0; i < Children.Count; i++) { IControl child = Children[i]; if (child is null) { continue; } var sz = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height); if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvFinalSize.U)) //need to switch to another line { ArrangeLine(finalSize, accumulatedV, curLineSize, firstInLine, i); accumulatedV += curLineSize.V; curLineSize = sz; if (MathUtilities.GreaterThan(sz.U, uvFinalSize.U)) //the element is wider then the constraint - give it a separate line { //switch to next line which only contain one element ArrangeLine(finalSize, accumulatedV, sz, i, ++i); accumulatedV += sz.V; curLineSize = new UVSize(Orientation); } firstInLine = i; } else //continue to accumulate a line { curLineSize.U += sz.U; curLineSize.V = Max(sz.V, curLineSize.V); } } //arrange the last line, if any if (firstInLine < Children.Count) { ArrangeLine(finalSize, accumulatedV, curLineSize, firstInLine, Children.Count); } return finalSize; } private void ArrangeLine(Size finalSize, double v, UVSize line, int start, int end) { double u; bool isHorizontal = Orientation == Orientation.Horizontal; if (isHorizontal) { switch (HorizontalContentAlignment) { case HorizontalAlignment.Center: u = (finalSize.Width - line.U) / 2; break; case HorizontalAlignment.Right: u = finalSize.Width - line.U; break; default: u = 0; break; } } else { switch (VerticalContentAlignment) { case VerticalAlignment.Center: u = (finalSize.Height - line.U) / 2; break; case VerticalAlignment.Bottom: u = finalSize.Height - line.U; break; default: u = 0; break; } } for (int i = start; i < end; i++) { IControl child = Children[i]; if (child is not null) { var childSize = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height); double layoutSlotU = childSize.U; child.Arrange(new Rect( isHorizontal ? u : v, isHorizontal ? v : u, isHorizontal ? layoutSlotU : line.V, isHorizontal ? line.V : layoutSlotU)); u += layoutSlotU; } } } private struct UVSize { internal UVSize(Orientation orientation, double width, double height) { U = V = 0d; _orientation = orientation; Width = width; Height = height; } internal UVSize(Orientation orientation) { U = V = 0d; _orientation = orientation; } internal double U; internal double V; private readonly Orientation _orientation; internal double Width { get => _orientation == Orientation.Horizontal ? U : V; set { if (_orientation == Orientation.Horizontal) { U = value; } else { V = value; } } } internal double Height { get => _orientation == Orientation.Horizontal ? V : U; set { if (_orientation == Orientation.Horizontal) { V = value; } else { U = value; } } } } } ================================================ FILE: PokemonBattleEngineClient/Infrastructure/Converters.cs ================================================ using Avalonia; using Avalonia.Data.Converters; using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngine.Data.Utils; using System; using System.Collections.Generic; using System.Globalization; namespace Kermalis.PokemonBattleEngineClient.Infrastructure; public sealed class FormToTextBitmapConverter : IMultiValueConverter { public static FormToTextBitmapConverter Instance { get; } = new(); public object? Convert(IList values, Type? targetType, object? parameter, CultureInfo? culture) { var species = (PBESpecies)values[0]; if (!PBEDataUtils.HasForms(species, true)) { return AvaloniaProperty.UnsetValue; } PBEForm form = true ? 0 : (PBEForm)values[1]; // TODO IPBEReadOnlyLocalizedString localized = PBEDataProvider.Instance.GetFormName(species, form); return StringRenderer.Render(localized.FromGlobalLanguage(), parameter?.ToString()); } } public sealed class ObjectToTextBitmapConverter : IValueConverter { public static ObjectToTextBitmapConverter Instance { get; } = new(); public object? Convert(object? value, Type? targetType, object? parameter, CultureInfo? culture) { if (value is null) { return AvaloniaProperty.UnsetValue; } IPBEReadOnlyLocalizedString? localized = null; switch (value) { case PBEAbility ability: localized = PBEDataProvider.Instance.GetAbilityName(ability); break; case PBEGender gender: localized = PBEDataProvider.Instance.GetGenderName(gender); break; case PBEItem item: localized = PBEDataProvider.Instance.GetItemName(item); break; case IPBEReadOnlyLocalizedString l: localized = l; break; case PBEMove move: localized = PBEDataProvider.Instance.GetMoveName(move); break; case PBENature nature: localized = PBEDataProvider.Instance.GetNatureName(nature); break; case PBESpecies species: localized = PBEDataProvider.Instance.GetSpeciesName(species); break; case PBEStat stat: localized = PBEDataProvider.Instance.GetStatName(stat); break; case PBEType type: localized = PBEDataProvider.Instance.GetTypeName(type); break; } return StringRenderer.Render(localized is null ? value.ToString() : localized.FromGlobalLanguage(), parameter?.ToString()) ?? AvaloniaProperty.UnsetValue; } public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } public sealed class SpeciesToMinispriteConverter : IValueConverter { public static SpeciesToMinispriteConverter Instance { get; } = new(); public object? Convert(object? value, Type? targetType, object? parameter, CultureInfo? culture) { PBESpecies species; PBEForm form; PBEGender gender; bool shiny; if (value is PBESpecies s) { species = s; form = 0; gender = PBEGender.Male; shiny = false; } else if (value is IPBEPokemon pkmn) { species = pkmn.Species; form = pkmn.Form; gender = pkmn.Gender; shiny = pkmn.Shiny; } else if (value is PBEBattlePokemon bpkmn) { bool useKnownInfo = parameter is bool b && b; species = useKnownInfo ? bpkmn.KnownSpecies : bpkmn.OriginalSpecies; form = useKnownInfo ? bpkmn.KnownForm : bpkmn.RevertForm; gender = useKnownInfo ? bpkmn.KnownGender : bpkmn.Gender; shiny = useKnownInfo ? bpkmn.KnownShiny : bpkmn.Shiny; } else { throw new ArgumentOutOfRangeException(nameof(value)); } return Utils.GetMinispriteBitmap(species, form, gender, shiny); } public object? ConvertBack(object? value, Type? targetType, object? parameter, CultureInfo? culture) { throw new NotImplementedException(); } } ================================================ FILE: PokemonBattleEngineClient/Infrastructure/StringRenderer.cs ================================================ using Avalonia; using Avalonia.Media.Imaging; using Avalonia.Platform; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Kermalis.PokemonBattleEngineClient.Infrastructure; internal static class StringRenderer { private interface IStringRenderFont { string FontId { get; } int FontHeight { get; } ConcurrentDictionary LoadedKeys { get; } (string OldKey, string NewKey)[] OverrideKeys { get; } // TODO: Cached text? } private class BattleHPFont : IStringRenderFont { public string FontId => "BattleHP"; public int FontHeight => 8; public static BattleHPFont Instance { get; } = new(); public ConcurrentDictionary LoadedKeys { get; } = new(); public (string OldKey, string NewKey)[] OverrideKeys { get; } = Array.Empty<(string OldKey, string NewKey)>(); } private class BattleLevelFont : IStringRenderFont { public string FontId => "BattleLevel"; public int FontHeight => 10; public static BattleLevelFont Instance { get; } = new(); public ConcurrentDictionary LoadedKeys { get; } = new(); public (string OldKey, string NewKey)[] OverrideKeys { get; } = new (string OldKey, string NewKey)[] { ("[LV]", "LV") }; } private class BattleNameFont : IStringRenderFont { public string FontId => "BattleName"; public int FontHeight => 13; public static BattleNameFont Instance { get; } = new(); public ConcurrentDictionary LoadedKeys { get; } = new(); public (string OldKey, string NewKey)[] OverrideKeys { get; } = new (string OldKey, string NewKey)[] { ("♂", "246D"), ("♀", "246E"), ("[PK]", "2486"), ("[MN]", "2487") }; } private class DefaultFont : IStringRenderFont { public string FontId => "Default"; public int FontHeight => 15; public static DefaultFont Instance { get; } = new(); public ConcurrentDictionary LoadedKeys { get; } = new(); public (string OldKey, string NewKey)[] OverrideKeys { get; } = new (string OldKey, string NewKey)[] { ("♂", "246D"), ("♀", "246E"), ("[PK]", "2486"), ("[MN]", "2487") }; } [return: NotNullIfNotNull("str")] public static unsafe Bitmap? Render(string? str, string? style) { if (string.IsNullOrWhiteSpace(str)) { return null; } IStringRenderFont font; switch (style) { case "BattleHP": font = BattleHPFont.Instance; break; case "BattleLevel": font = BattleLevelFont.Instance; break; case "BattleName": font = BattleNameFont.Instance; break; default: font = DefaultFont.Instance; break; } uint primary = 0xFFFFFFFF, secondary = 0xFF000000, tertiary = 0xFF808080; switch (style) { case "BattleHP": primary = 0xFFF7F7F7; secondary = 0xFF101010; tertiary = 0xFF9C9CA5; break; case "BattleLevel": case "BattleName": primary = 0xFFF7F7F7; secondary = 0xFF181818; break; //case "BattleWhite": secondary = 0xF0FFFFFF; break; // Looks horrible because of Avalonia's current issues case "MenuBlack": primary = 0xFF5A5252; secondary = 0xFFA5A5AD; break; default: secondary = 0xFF848484; break; } // Measure how large the string will end up var lineWidths = new List(20); int stringWidth = 0, stringHeight = font.FontHeight, curLineWidth = 0; void RecordLineWidth() { if (curLineWidth > stringWidth) { stringWidth = curLineWidth; } lineWidths.Add(curLineWidth); } int index = 0; var keys = new List<(string Key, Bitmap? Bitmap)>(); while (index < str.Length) { char c = str[index]; if (c == '\r') // Ignore { index++; } else if (c == '\n') { index++; stringHeight += font.FontHeight + 1; RecordLineWidth(); curLineWidth = 0; keys.Add(("\n", null)); } else { string? key = null; for (int i = 0; i < font.OverrideKeys.Length; i++) { (string oldKey, string newKey) = font.OverrideKeys[i]; if (index + oldKey.Length <= str.Length && str.Substring(index, oldKey.Length) == oldKey) { key = newKey; index += oldKey.Length; break; } } if (key is null) { key = ((ushort)str[index]).ToString("X4"); index++; } string resource = "FONT." + font.FontId + ".F_" + key + ".png"; if (!Utils.DoesResourceExist(resource)) { key = "003F"; // 003F is '?' } if (!font.LoadedKeys.TryGetValue(key, out Bitmap? bmp)) { bmp = new Bitmap(Utils.GetResourceStream(resource)); font.LoadedKeys.TryAdd(key, bmp); } curLineWidth += bmp.PixelSize.Width; keys.Add((key, bmp)); } } RecordLineWidth(); // Draw the string var dpi = new Vector(96, 96); var wb = new WriteableBitmap(new PixelSize(stringWidth, stringHeight), dpi, PixelFormat.Bgra8888, AlphaFormat.Premul); using (IRenderTarget rtb = Utils.RenderInterface.CreateRenderTarget(new[] { new WriteableBitmapSurface(wb) })) using (IDrawingContextImpl ctx = rtb.CreateDrawingContext(null)) { int x = 0, y = 0; for (int i = 0; i < keys.Count; i++) { (string key, Bitmap? bmp) = keys[i]; if (key == "\n") { y += font.FontHeight + 1; x = 0; } else { var size = new Size(bmp!.PixelSize.Width, bmp.PixelSize.Height); // TODO: Verify and/or try other options with different dpi ctx.DrawBitmap(bmp.PlatformImpl, 1d, new Rect(size), new Rect(new Point(x, y), size)); x += bmp.PixelSize.Width; } } } // Edit colors using (ILockedFramebuffer l = wb.Lock()) { long startAddress = l.Address.ToInt64(); for (int i = 0; i < lineWidths.Count; i++) { int w = lineWidths[i]; for (int x = 0; x < w; x++) { for (int y = 0; y < font.FontHeight; y++) { uint* address = (uint*)(startAddress + (x * sizeof(uint)) + ((y + ((font.FontHeight + 1) * i)) * l.RowBytes)); uint pixel = *address; if (pixel == 0xFFFFFFFF) { *address = primary; } else if (pixel == 0xFF000000) { *address = secondary; } else if (pixel == 0xFF808080) { *address = tertiary; } } } } } return wb; } } ================================================ FILE: PokemonBattleEngineClient/Infrastructure/Utils.cs ================================================ using Avalonia; using Avalonia.Media.Imaging; using Avalonia.Platform; using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngine.Data.Utils; using Kermalis.PokemonBattleEngine.DefaultData; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; namespace Kermalis.PokemonBattleEngineClient.Infrastructure; public static class Utils { private const string AssemblyPrefix = "Kermalis.PokemonBattleEngineClient."; private static readonly Assembly _assembly = Assembly.GetExecutingAssembly(); private static readonly string[] _resources = _assembly.GetManifestResourceNames(); private static IPlatformRenderInterface? _renderInterface = null; public static IPlatformRenderInterface RenderInterface { get { // This is done because the static constructor of Utils is called (by SetWorkingDirectory) before the Avalonia app is built if (_renderInterface is null) { _renderInterface = AvaloniaLocator.Current.GetService(); } return _renderInterface; } } static Utils() { _femaleMinispriteLookup = new List(); _femaleSpriteLookup = new List(); static void Add(string resource, List list) { using (var reader = new StreamReader(GetResourceStream(resource))) { string? line; while ((line = reader.ReadLine()) is not null) { if (!Enum.TryParse(line, out PBESpecies species)) { throw new InvalidDataException($"Failed to parse \"{resource}\""); } list.Add(species); } } } Add("PKMN.FemaleMinispriteLookup.txt", _femaleMinispriteLookup); Add("PKMN.FemaleSpriteLookup.txt", _femaleSpriteLookup); } private static readonly object _resourceExistsCacheLockObj = new(); private static readonly Dictionary _resourceExistsCache = new(); public static bool DoesResourceExist(string resource) { lock (_resourceExistsCacheLockObj) { if (!_resourceExistsCache.TryGetValue(resource, out bool value)) { value = Array.IndexOf(_resources, AssemblyPrefix + resource) != -1; _resourceExistsCache.Add(resource, value); } return value; } } public static Stream GetResourceStream(string resource) { Stream? ret = _assembly.GetManifestResourceStream(AssemblyPrefix + resource); if (ret is null) { throw new ArgumentOutOfRangeException(nameof(resource), "Resource not found: " + resource); } return ret; } public static Uri GetResourceUri(string resource) { return new Uri("resm:" + AssemblyPrefix + resource); } public static string WorkingDirectory { get; private set; } = null!; public static void SetWorkingDirectory(string workingDirectory) { PBEDefaultDataProvider.InitEngine(workingDirectory); WorkingDirectory = workingDirectory; } private static readonly List _femaleMinispriteLookup; private static readonly List _femaleSpriteLookup; private static bool HasFemaleSprite(PBESpecies species, bool minisprite) { return (minisprite ? _femaleMinispriteLookup : _femaleSpriteLookup).Contains(species); } public static Bitmap GetMinispriteBitmap(PBESpecies species, PBEForm form, PBEGender gender, bool shiny) { string speciesStr = PBEDataUtils.GetNameOfForm(species, form) ?? species.ToString(); string genderStr = gender == PBEGender.Female && HasFemaleSprite(species, true) ? "_F" : string.Empty; return new Bitmap(GetResourceStream("PKMN.PKMN_" + speciesStr + (shiny ? "_S" : string.Empty) + genderStr + ".png")); } public static Uri GetPokemonSpriteUri(PBEBattlePokemon pkmn, bool backSprite) { return GetPokemonSpriteUri(pkmn.KnownSpecies, pkmn.KnownForm, pkmn.KnownShiny, pkmn.KnownGender, pkmn.KnownStatus2.HasFlag(PBEStatus2.Substitute), backSprite); } public static Uri GetPokemonSpriteUri(IPBEPokemon pkmn) { return GetPokemonSpriteUri(pkmn.Species, pkmn.Form, pkmn.Shiny, pkmn.Gender, false, false); } public static Uri GetPokemonSpriteUri(PBESpecies species, PBEForm form, bool shiny, PBEGender gender, bool behindSubstitute, bool backSprite) { string orientation = backSprite ? "_B" : "_F"; if (behindSubstitute) { return GetResourceUri("PKMN.STATUS2_Substitute" + orientation + ".gif"); } else { string speciesStr = PBEDataUtils.GetNameOfForm(species, form) ?? species.ToString(); string genderStr = gender == PBEGender.Female && HasFemaleSprite(species, false) ? "_F" : string.Empty; return GetResourceUri("PKMN.PKMN_" + speciesStr + orientation + (shiny ? "_S" : string.Empty) + genderStr + ".gif"); } } 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(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).FromGlobalLanguage()})" : string.Empty; sb.AppendLine($"Disguised as: {pkmn.KnownNickname}/{PBEDataProvider.Instance.GetSpeciesName(pkmn.KnownSpecies).FromGlobalLanguage()}{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).FromGlobalLanguage()})" : string.Empty; sb.AppendLine($"{pkmn.KnownNickname}/{PBEDataProvider.Instance.GetSpeciesName(pkmn.KnownSpecies).FromGlobalLanguage()}{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).FromGlobalLanguage()}"); if (pkmn.KnownType2 != PBEType.None) { sb.Append($"/{PBEDataProvider.Instance.GetTypeName(pkmn.KnownType2).FromGlobalLanguage()}"); } 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).FromGlobalLanguage()))}"); } else { sb.AppendLine($"Known ability: {PBEDataProvider.Instance.GetAbilityName(pkmn.KnownAbility).FromGlobalLanguage()}"); } sb.AppendLine($"Known item: {(pkmn.KnownItem == (PBEItem)ushort.MaxValue ? "???" : PBEDataProvider.Instance.GetItemName(pkmn.KnownItem).FromGlobalLanguage())}"); 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).FromGlobalLanguage()); if (move != PBEMove.None && move != PBEMove.MAX) { sb.Append($" ({pp}{(maxPP == 0 ? ")" : $"/{maxPP})")}"); } } } else { string formStr = PBEDataUtils.HasForms(pkmn.Species, false) ? $" ({PBEDataProvider.Instance.GetFormName(pkmn).FromGlobalLanguage()})" : string.Empty; sb.AppendLine($"{pkmn.Nickname}/{PBEDataProvider.Instance.GetSpeciesName(pkmn.Species).FromGlobalLanguage()}{formStr} {pkmn.Gender.ToSymbol()} Lv.{pkmn.Level}"); sb.AppendLine($"HP: {pkmn.HP}/{pkmn.MaxHP} ({pkmn.HPPercentage:P2})"); sb.Append($"Types: {PBEDataProvider.Instance.GetTypeName(pkmn.Type1).FromGlobalLanguage()}"); if (pkmn.Type2 != PBEType.None) { sb.Append($"/{PBEDataProvider.Instance.GetTypeName(pkmn.Type2).FromGlobalLanguage()}"); } sb.AppendLine(); if (pkmn.FieldPosition != PBEFieldPosition.None) { sb.AppendLine($"Position: {pkmn.Team.CombinedName}'s {pkmn.FieldPosition}"); } AddStatus1(); if (pkmn.FieldPosition != PBEFieldPosition.None && pkmn.Status2 != PBEStatus2.None) { AddStatus2(pkmn.Status2); } sb.AppendLine($"Stats: [A] {pkmn.Attack}, [D] {pkmn.Defense}, [SA] {pkmn.SpAttack}, [SD] {pkmn.SpDefense}, [S] {pkmn.Speed}, [W] {pkmn.Weight:0.0}"); if (pkmn.FieldPosition != PBEFieldPosition.None) { AddStatChanges(); } sb.AppendLine($"Ability: {PBEDataProvider.Instance.GetAbilityName(pkmn.Ability).FromGlobalLanguage()}"); sb.AppendLine($"Item: {PBEDataProvider.Instance.GetItemName(pkmn.Item).FromGlobalLanguage()}"); if (pkmn.Moves.Contains(PBEMoveEffect.Frustration) || pkmn.Moves.Contains(PBEMoveEffect.Return)) { sb.AppendLine($"Friendship: {pkmn.Friendship} ({pkmn.Friendship / (float)byte.MaxValue:P2})"); } if (pkmn.Moves.Contains(PBEMoveEffect.HiddenPower)) { PBEReadOnlyStatCollection ivs = pkmn.IndividualValues!; sb.AppendLine($"{PBEDataProvider.Instance.GetMoveName(PBEMove.HiddenPower).FromGlobalLanguage()}: {PBEDataProvider.Instance.GetTypeName(ivs.GetHiddenPowerType()).FromGlobalLanguage()}|{ivs.GetHiddenPowerBasePower(pkmn.Battle.Settings)}"); } sb.Append("Moves: "); for (int i = 0; i < pkmn.Battle.Settings.NumMoves; i++) { PBEBattleMoveset.PBEBattleMovesetSlot slot = pkmn.Moves[i]; PBEMove move = slot.Move; if (i > 0) { sb.Append(", "); } sb.Append(PBEDataProvider.Instance.GetMoveName(slot.Move).FromGlobalLanguage()); if (move != PBEMove.None) { sb.Append($" ({slot.PP}/{slot.MaxPP})"); } } sb.AppendLine(); sb.Append($"Usable moves: {string.Join(", ", pkmn.GetUsableMoves().Select(m => PBEDataProvider.Instance.GetMoveName(m).FromGlobalLanguage()))}"); } return sb.ToString(); } } ================================================ FILE: PokemonBattleEngineClient/Infrastructure/WriteableBitmapSurface.cs ================================================ using Avalonia.Controls.Platform.Surfaces; using Avalonia.Media.Imaging; using Avalonia.Platform; namespace Kermalis.PokemonBattleEngineClient.Infrastructure; internal sealed class WriteableBitmapSurface : IFramebufferPlatformSurface { private readonly WriteableBitmap _bitmap; public WriteableBitmapSurface(WriteableBitmap bmp) { _bitmap = bmp; } public ILockedFramebuffer Lock() { return _bitmap.Lock(); } } ================================================ FILE: PokemonBattleEngineClient/MainWindow.xaml ================================================  ================================================ FILE: PokemonBattleEngineClient/MainWindow.xaml.cs ================================================ using Avalonia.Controls; using Avalonia.Markup.Xaml; using Kermalis.PokemonBattleEngineClient.Views; namespace Kermalis.PokemonBattleEngineClient; public sealed class MainWindow : Window { public MainWindow() { // TODO: iOS does not support dynamically loading assemblies // so we must refer to this resource DLL statically. For // now I am doing that here. But we need a better solution!! var theme = new Avalonia.Themes.Default.DefaultTheme(); theme.TryGetResource("Button", out _); AvaloniaXamlLoader.Load(this); } protected override bool HandleClosing() { this.FindControl("Main").HandleClosing(); return base.HandleClosing(); } } ================================================ FILE: PokemonBattleEngineClient/Models/MoveInfo.cs ================================================ using Avalonia.Media; using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngine.DefaultData; using ReactiveUI; using System; using System.Collections.Generic; using System.Reactive; using System.Text; namespace Kermalis.PokemonBattleEngineClient.Models; public sealed class MoveInfo { private static Dictionary _typeToBrush = null!; internal static void CreateBrushes() { _typeToBrush = new Dictionary { { PBEType.None, (new SolidColorBrush(Color.FromRgb(146, 154, 156)), new SolidColorBrush(Color.FromRgb(83, 89, 88))) }, { PBEType.Bug, (new SolidColorBrush(Color.FromRgb(162, 212, 56)), new SolidColorBrush(Color.FromRgb(87, 127, 12))) }, { PBEType.Dark, (new SolidColorBrush(Color.FromRgb(106, 122, 156)), new SolidColorBrush(Color.FromRgb(64, 79, 109))) }, { PBEType.Dragon, (new SolidColorBrush(Color.FromRgb(80, 136, 188)), new SolidColorBrush(Color.FromRgb(6, 83, 137))) }, { PBEType.Electric, (new SolidColorBrush(Color.FromRgb(246, 216, 48)), new SolidColorBrush(Color.FromRgb(173, 148, 24))) }, { PBEType.Fighting, (new SolidColorBrush(Color.FromRgb(244, 100, 138)), new SolidColorBrush(Color.FromRgb(153, 62, 86))) }, { PBEType.Fire, (new SolidColorBrush(Color.FromRgb(255, 152, 56)), new SolidColorBrush(Color.FromRgb(196, 86, 13))) }, { PBEType.Flying, (new SolidColorBrush(Color.FromRgb(80, 124, 212)), new SolidColorBrush(Color.FromRgb(36, 75, 153))) }, { PBEType.Ghost, (new SolidColorBrush(Color.FromRgb(94, 100, 208)), new SolidColorBrush(Color.FromRgb(49, 56, 137))) }, { PBEType.Grass, (new SolidColorBrush(Color.FromRgb(64, 208, 112)), new SolidColorBrush(Color.FromRgb(27, 135, 63))) }, { PBEType.Ground, (new SolidColorBrush(Color.FromRgb(232, 130, 68)), new SolidColorBrush(Color.FromRgb(150, 83, 45))) }, { PBEType.Ice, (new SolidColorBrush(Color.FromRgb(98, 204, 212)), new SolidColorBrush(Color.FromRgb(52, 128, 145))) }, { PBEType.Normal, (new SolidColorBrush(Color.FromRgb(146, 154, 156)), new SolidColorBrush(Color.FromRgb(83, 89, 88))) }, { PBEType.Poison, (new SolidColorBrush(Color.FromRgb(188, 82, 232)), new SolidColorBrush(Color.FromRgb(117, 52, 145))) }, { PBEType.Psychic, (new SolidColorBrush(Color.FromRgb(255, 136, 130)), new SolidColorBrush(Color.FromRgb(173, 81, 89))) }, { PBEType.Rock, (new SolidColorBrush(Color.FromRgb(196, 174, 112)), new SolidColorBrush(Color.FromRgb(114, 101, 66))) }, { PBEType.Steel, (new SolidColorBrush(Color.FromRgb(94, 160, 178)), new SolidColorBrush(Color.FromRgb(66, 105, 114))) }, { PBEType.Water, (new SolidColorBrush(Color.FromRgb(58, 176, 232)), new SolidColorBrush(Color.FromRgb(34, 106, 137))) } }; } public PBEMove Move { get; } public IBrush Brush { get; } public IBrush BorderBrush { get; } public string Description { get; } public ReactiveCommand SelectMoveCommand { get; } internal MoveInfo(PBEBattlePokemon pkmn, PBEMove move, Action clickAction) { Move = move; PBEType moveType = pkmn.GetMoveType(move); (Brush, BorderBrush) = _typeToBrush[moveType]; if (move == PBEMove.None) { Description = string.Empty; } else { IPBEMoveData mData = PBEDataProvider.Instance.GetMoveData(move); string s = $"Type: {PBEDataProvider.Instance.GetTypeName(mData.Type).FromGlobalLanguage()}"; if (mData.Type != moveType) { s += $" → {PBEDataProvider.Instance.GetTypeName(moveType).FromGlobalLanguage()}"; } var sb = new StringBuilder(); sb.AppendLine(s); sb.AppendLine($"Category: {mData.Category}"); PBEBattleMoveset.PBEBattleMovesetSlot? slot = pkmn.Moves[move]; if (slot is not null) // TempLocked move you do not own (like Struggle) { sb.AppendLine($"PP: {slot.PP}/{slot.MaxPP}"); } sb.AppendLine($"Priority: {mData.Priority}"); sb.AppendLine($"Power: {(mData.Power == 0 ? "―" : mData.Power.ToString())}"); sb.AppendLine($"Accuracy: {(mData.Accuracy == 0 ? "―" : mData.Accuracy.ToString())}"); s = $"Targets: {mData.Targets}"; PBEMoveTarget moveTargets = pkmn.GetMoveTargets(move); if (mData.Targets != moveTargets) { s += $" → {moveTargets}"; } sb.AppendLine(s); sb.AppendLine($"Flags: {mData.Flags}"); switch (mData.Effect) { case PBEMoveEffect.Recoil: sb.AppendLine($"Recoil: 1/{mData.EffectParam} damage dealt"); break; case PBEMoveEffect.Recoil__10PercentBurn: sb.AppendLine($"Recoil: 1/{mData.EffectParam} damage dealt"); break; // TODO: Burn chance case PBEMoveEffect.Recoil__10PercentParalyze: sb.AppendLine($"Recoil: 1/{mData.EffectParam} damage dealt"); break; // TODO: Paralyze chance case PBEMoveEffect.Struggle: sb.AppendLine("Recoil: 1/4 user's max HP"); break; } sb.AppendLine(); sb.Append(PBEDefaultDataProvider.Instance.GetMoveDescription(move).FromGlobalLanguage().Replace('\n', ' ')); Description = sb.ToString(); } SelectMoveCommand = ReactiveCommand.Create(() => clickAction(move)); } } ================================================ FILE: PokemonBattleEngineClient/Models/PokemonInfo.cs ================================================ using Avalonia.Media.Imaging; using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data.Utils; using Kermalis.PokemonBattleEngineClient.Clients; using Kermalis.PokemonBattleEngineClient.Infrastructure; namespace Kermalis.PokemonBattleEngineClient.Models; public sealed class PokemonInfo { public Bitmap? MiniSprite { get; } public string? Name { get; } internal PokemonInfo(PBEBattlePokemon pkmn, bool useKnownInfo) { MiniSprite = (Bitmap)SpeciesToMinispriteConverter.Instance.Convert(pkmn, typeof(Bitmap), useKnownInfo, null)!; Name = useKnownInfo ? pkmn.KnownNickname : pkmn.Nickname + (useKnownInfo && !pkmn.KnownStatus2.HasFlag(PBEStatus2.Transformed) ? pkmn.KnownGender : pkmn.Gender).ToSymbol(); } internal static PokemonInfo? From(BattleClient client, PBETeam team, PBEFieldPosition pos) { if (!team.TryGetPokemon(pos, out PBEBattlePokemon? pkmn)) { return null; } return new PokemonInfo(pkmn, client.ShouldUseKnownInfo(pkmn.Trainer)); } } ================================================ FILE: PokemonBattleEngineClient/Models/SwitchInfo.cs ================================================ using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngineClient.Infrastructure; using ReactiveUI; using System; using System.Reactive; namespace Kermalis.PokemonBattleEngineClient.Models; public sealed class SwitchInfo { public ReactiveCommand SelectPokemonCommand { get; } public bool Enabled { get; } public PokemonInfo Pokemon { get; } public string Description { get; } internal SwitchInfo(PBEBattlePokemon pkmn, bool locked, Action clickAction) { Pokemon = new PokemonInfo(pkmn, false); Description = Utils.CustomPokemonToString(pkmn, false); Enabled = !locked && pkmn.FieldPosition == PBEFieldPosition.None && pkmn.CanBattle; SelectPokemonCommand = ReactiveCommand.Create(() => clickAction(pkmn)); } } ================================================ FILE: PokemonBattleEngineClient/Models/TargetInfo.cs ================================================ using Kermalis.PokemonBattleEngine.Battle; using System.ComponentModel; namespace Kermalis.PokemonBattleEngineClient.Models; public sealed class TargetInfo : INotifyPropertyChanged { private void OnPropertyChanged(string property) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); } public event PropertyChangedEventHandler? PropertyChanged; private bool _enabled; public bool Enabled { get => _enabled; internal set { if (_enabled != value) { _enabled = value; OnPropertyChanged(nameof(Enabled)); } } } private PokemonInfo? _pokemon; public PokemonInfo? Pokemon { get => _pokemon; internal set { if (_pokemon != value) { _pokemon = value; OnPropertyChanged(nameof(Pokemon)); } } } private bool _lineRightVisible; public bool LineRightVisible { get => _lineRightVisible; internal set { if (_lineRightVisible != value) { _lineRightVisible = value; OnPropertyChanged(nameof(LineRightVisible)); } } } private bool _lineDownVisible; public bool LineDownVisible { get => _lineDownVisible; internal set { if (_lineDownVisible != value) { _lineDownVisible = value; OnPropertyChanged(nameof(LineDownVisible)); } } } internal PBETurnTarget Targets { get; set; } } ================================================ FILE: PokemonBattleEngineClient/Models/TeamInfo.cs ================================================ using Kermalis.PokemonBattleEngine.Data.Legality; namespace Kermalis.PokemonBattleEngineClient.Models; public sealed class TeamInfo { public string Name { get; } public PBELegalPokemonCollection Party { get; } internal TeamInfo(string name, PBELegalPokemonCollection party) { Name = name; Party = party; } } ================================================ FILE: PokemonBattleEngineClient/PokemonBattleEngineClient.csproj ================================================  net7.0 Library Kermalis.PokemonBattleEngineClient Kermalis Kermalis https://github.com/Kermalis/PokemonBattleEngine true false NU1605 enable %(Filename) Designer ================================================ FILE: PokemonBattleEngineClient/Views/ActionsView.xaml ================================================  ================================================ FILE: PokemonBattleEngineClient/Views/MainView.xaml.cs ================================================ using Avalonia.Controls; using Avalonia.Markup.Xaml; using Avalonia.Threading; using Kermalis.PokemonBattleEngine.Battle; using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngine.Data.Legality; using Kermalis.PokemonBattleEngine.DefaultData; using Kermalis.PokemonBattleEngine.Network; using Kermalis.PokemonBattleEngine.Packets; using Kermalis.PokemonBattleEngineClient.Clients; using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Threading; namespace Kermalis.PokemonBattleEngineClient.Views; public sealed class MainView : UserControl, INotifyPropertyChanged { private void OnPropertyChanged(string property) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); } public new event PropertyChangedEventHandler? PropertyChanged; private string _connectText; public string ConnectText { get => _connectText; private set { if (_connectText != value) { _connectText = value; OnPropertyChanged(nameof(ConnectText)); } } } private readonly List _battles = new(); private readonly TabControl _tabs; private readonly TeamBuilderView _teamBuilder; private readonly TextBox _ip; private readonly NumericUpDown _port; private readonly Button _connect; private readonly TextBox _name; private readonly CheckBox _multi; #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. public MainView() #pragma warning restore CS8618 // _connectText is set in ResetConnectButton() { DataContext = this; AvaloniaXamlLoader.Load(this); _tabs = this.FindControl("Tabs"); _teamBuilder = this.FindControl("TeamBuilder"); _ip = this.FindControl("IP"); _port = this.FindControl("Port"); _connect = this.FindControl