diff --git a/PokemonBattleEngine/AI/AIDecisions.cs b/PokemonBattleEngine/AI/AIDecisions.cs index 3f7ac2f2f..62b63271d 100644 --- a/PokemonBattleEngine/AI/AIDecisions.cs +++ b/PokemonBattleEngine/AI/AIDecisions.cs @@ -7,53 +7,39 @@ namespace Kermalis.PokemonBattleEngine.AI { - /// - /// Creates valid decisions for a team in a battle. Decisions may not be valid for custom settings and/or move changes. - /// - public static partial class PBEAIManager + /// Creates valid decisions for a team in a battle. Decisions may not be valid for custom settings and/or move changes. + public static partial class PBEAI { - /// - /// Creates valid actions for a battle turn for a specific team. - /// + /// Creates valid actions for a battle turn for a specific team. /// The team to create actions for. /// 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 static IEnumerable CreateActions(PBETeam team) + public static PBETurnAction[] CreateActions(PBETeam team) { - if (team.Battle.BattleState != PBEBattleState.WaitingForActions) + if (team == null) { - throw new InvalidOperationException($"{nameof(team.Battle.BattleState)} must be {PBEBattleState.WaitingForActions} to create actions."); + throw new ArgumentNullException(nameof(team)); } - PBEPokemon[] active = team.ActiveBattlers; - if (active.Length == 0) + if (team.Battle.BattleState != PBEBattleState.WaitingForActions) { - throw new InvalidOperationException($"{nameof(team)} must have at least one active battler."); + throw new InvalidOperationException($"{nameof(team.Battle.BattleState)} must be {PBEBattleState.WaitingForActions} to create actions."); } - PBETeam opposingTeam = team == team.Battle.Teams[0] ? team.Battle.Teams[1] : team.Battle.Teams[0]; - var actions = new PBEAction[team.ActionsRequired.Count]; + var actions = new PBETurnAction[team.ActionsRequired.Count]; var standBy = new List(); for (int i = 0; i < actions.Length; i++) { PBEPokemon pkmn = team.ActionsRequired[i]; - if (Array.TrueForAll(pkmn.Moves, m => m == PBEMove.None)) - { - throw new ArgumentOutOfRangeException(nameof(pkmn.Moves), $"{pkmn.Nickname} has no moves."); - } // If a Pokémon is forced to struggle, it is best that it just stays in until it faints if (pkmn.IsForcedToStruggle()) { - actions[i].Decision = PBEDecision.Fight; - actions[i].FightMove = PBEMove.Struggle; - actions[i].FightTargets = GetPossibleTargets(pkmn, pkmn.GetMoveTargets(PBEMove.Struggle))[0]; // Seems a little nasty just to select "Self", since this will be RandomFoeSurrounding + actions[i] = new PBETurnAction(pkmn.Id, PBEMove.Struggle, GetPossibleTargets(pkmn, pkmn.GetMoveTargets(PBEMove.Struggle))[0]); } // If a Pokémon has a temp locked move (Dig, Dive, Shadow Force) it must be used else if (pkmn.TempLockedMove != PBEMove.None) { - actions[i].Decision = PBEDecision.Fight; - actions[i].FightMove = pkmn.TempLockedMove; - actions[i].FightTargets = pkmn.TempLockedTargets; + actions[i] = new PBETurnAction(pkmn.Id, pkmn.TempLockedMove, pkmn.TempLockedTargets); } // The Pokémon is free to switch or fight (unless it cannot switch due to Magnet Pull etc) else @@ -62,42 +48,42 @@ public static IEnumerable CreateActions(PBETeam team) PBEPokemon[] availableForSwitch = team.Party.Except(standBy).Where(p => p.FieldPosition == PBEFieldPosition.None && p.HP > 0).ToArray(); PBEMove[] usableMoves = pkmn.GetUsableMoves(); - var possibleActions = new List<(PBEAction Action, double Score)>(); + var possibleActions = new List<(PBETurnAction Action, double Score)>(); for (int m = 0; m < usableMoves.Length; m++) // Score moves { PBEMove move = usableMoves[m]; PBEType moveType = pkmn.GetMoveType(move); PBEMoveTarget moveTargets = pkmn.GetMoveTargets(move); - PBETarget[] possibleTargets = PBEMoveData.IsSpreadMove(moveTargets) - ? new PBETarget[] { GetSpreadMoveTargets(pkmn, moveTargets) } + PBETurnTarget[] possibleTargets = PBEMoveData.IsSpreadMove(moveTargets) + ? new PBETurnTarget[] { GetSpreadMoveTargets(pkmn, moveTargets) } : GetPossibleTargets(pkmn, moveTargets); - foreach (PBETarget possibleTarget in possibleTargets) + foreach (PBETurnTarget possibleTarget in possibleTargets) { // TODO: RandomFoeSurrounding (probably just account for the specific effects that use this target type) var targets = new List(); - if (possibleTarget.HasFlag(PBETarget.AllyLeft)) + if (possibleTarget.HasFlag(PBETurnTarget.AllyLeft)) { targets.Add(team.TryGetPokemon(PBEFieldPosition.Left)); } - if (possibleTarget.HasFlag(PBETarget.AllyCenter)) + if (possibleTarget.HasFlag(PBETurnTarget.AllyCenter)) { targets.Add(team.TryGetPokemon(PBEFieldPosition.Center)); } - if (possibleTarget.HasFlag(PBETarget.AllyRight)) + if (possibleTarget.HasFlag(PBETurnTarget.AllyRight)) { targets.Add(team.TryGetPokemon(PBEFieldPosition.Right)); } - if (possibleTarget.HasFlag(PBETarget.FoeLeft)) + if (possibleTarget.HasFlag(PBETurnTarget.FoeLeft)) { - targets.Add(opposingTeam.TryGetPokemon(PBEFieldPosition.Left)); + targets.Add(team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Left)); } - if (possibleTarget.HasFlag(PBETarget.FoeCenter)) + if (possibleTarget.HasFlag(PBETurnTarget.FoeCenter)) { - targets.Add(opposingTeam.TryGetPokemon(PBEFieldPosition.Center)); + targets.Add(team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Center)); } - if (possibleTarget.HasFlag(PBETarget.FoeRight)) + if (possibleTarget.HasFlag(PBETurnTarget.FoeRight)) { - targets.Add(opposingTeam.TryGetPokemon(PBEFieldPosition.Right)); + targets.Add(team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Right)); } double score = 0.0; @@ -121,11 +107,11 @@ public static IEnumerable CreateActions(PBETeam team) // TODO: Favor sleep with Bad Dreams (unless ally) if (target.Status1 != PBEStatus1.None) { - score += target.Team == opposingTeam ? -60 : 0; + score += target.Team == team.OpposingTeam ? -60 : 0; } else { - score += target.Team == opposingTeam ? +40 : -20; + score += target.Team == team.OpposingTeam ? +40 : -20; } } } @@ -191,31 +177,31 @@ public static IEnumerable CreateActions(PBETeam team) typeEffectiveness *= PBEPokemonData.TypeEffectiveness[moveType][target.KnownType2]; if (typeEffectiveness <= 0.0) // (-infinity, 0.0] Ineffective { - score += target.Team == opposingTeam ? -60 : -1; + score += target.Team == team.OpposingTeam ? -60 : -1; } else if (typeEffectiveness <= 0.25) // (0.0, 0.25] NotVeryEffective { - score += target.Team == opposingTeam ? -30 : -5; + score += target.Team == team.OpposingTeam ? -30 : -5; } else if (typeEffectiveness < 1.0) // (0.25, 1.0) NotVeryEffective { - score += target.Team == opposingTeam ? -10 : -10; + score += target.Team == team.OpposingTeam ? -10 : -10; } else if (typeEffectiveness == 1.0) // [1.0, 1.0] Normal { - score += target.Team == opposingTeam ? +10 : -15; + score += target.Team == team.OpposingTeam ? +10 : -15; } else if (typeEffectiveness < 4.0) // (1.0, 4.0) SuperEffective { - score += target.Team == opposingTeam ? +25 : -20; + score += target.Team == team.OpposingTeam ? +25 : -20; } else // [4.0, infinity) SuperEffective { - score += target.Team == opposingTeam ? +40 : -30; + score += target.Team == team.OpposingTeam ? +40 : -30; } if (pkmn.HasType(moveType) && typeEffectiveness > 0.0) // STAB { - score += (pkmn.Ability == PBEAbility.Adaptability ? 15 : 10) * (target.Team == opposingTeam ? +1 : -1); + score += (pkmn.Ability == PBEAbility.Adaptability ? 15 : 10) * (target.Team == team.OpposingTeam ? +1 : -1); } } } @@ -228,7 +214,7 @@ public static IEnumerable CreateActions(PBETeam team) case PBEMoveEffect.RestoreUserHP: { PBEPokemon target = targets[0]; - if (target == null || target.Team == opposingTeam) + if (target == null || target.Team == team.OpposingTeam) { score -= 100; } @@ -325,14 +311,9 @@ public static IEnumerable CreateActions(PBETeam team) // TODO Moves break; } + default: throw new ArgumentOutOfRangeException(nameof(PBEMoveData.Effect)); } - var mAction = new PBEAction - { - Decision = PBEDecision.Fight, - FightMove = move, - FightTargets = possibleTarget - }; - possibleActions.Add((mAction, score)); + possibleActions.Add((new PBETurnAction(pkmn.Id, move, possibleTarget), score)); } } if (pkmn.CanSwitchOut()) @@ -344,19 +325,14 @@ public static IEnumerable CreateActions(PBETeam team) // TODO: Known moves of active battlers // TODO: Type effectiveness double score = 0.0; - var sAction = new PBEAction - { - Decision = PBEDecision.SwitchOut, - SwitchPokemonId = switchPkmn.Id - }; - possibleActions.Add((sAction, score)); + possibleActions.Add((new PBETurnAction(pkmn.Id, switchPkmn.Id), score)); } } - string ToDebugString((PBEAction Action, double Score) t) + string ToDebugString((PBETurnAction Action, double Score) t) { string str = "{"; - if (t.Action.Decision == PBEDecision.Fight) + if (t.Action.Decision == PBETurnDecision.Fight) { str += string.Format("Fight {0} {1}", t.Action.FightMove, t.Action.FightTargets); } @@ -367,15 +343,14 @@ string ToDebugString((PBEAction Action, double Score) t) str += " [" + t.Score + "]}"; return str; } - IOrderedEnumerable<(PBEAction Action, double Score)> byScore = possibleActions.OrderByDescending(t => t.Score); + IOrderedEnumerable<(PBETurnAction Action, double Score)> byScore = possibleActions.OrderByDescending(t => t.Score); Debug.WriteLine("{0}'s possible actions: {1}", pkmn.Nickname, byScore.Select(t => ToDebugString(t)).Print()); double bestScore = byScore.First().Score; actions[i] = byScore.Where(t => t.Score == bestScore).ToArray().RandomElement().Action; // Pick random action of the ones that tied for best score } // Action was chosen, finish up for this Pokémon - actions[i].PokemonId = pkmn.Id; - if (actions[i].Decision == PBEDecision.SwitchOut) + if (actions[i].Decision == PBETurnDecision.SwitchOut) { standBy.Add(team.TryGetPokemon(actions[i].SwitchPokemonId)); } @@ -383,14 +358,16 @@ string ToDebugString((PBEAction Action, double Score) t) return actions; } - /// - /// Creates valid switches for a battle for a specific team. - /// + /// Creates valid switches for a battle for a specific team. /// The team to create switches for. /// Thrown when does not require switch-ins or 's 's is not . /// Thrown when 's 's is invalid. - public static IEnumerable<(byte PokemonId, PBEFieldPosition Position)> CreateSwitches(PBETeam team) + public static PBESwitchIn[] CreateSwitches(PBETeam team) { + if (team == null) + { + throw new ArgumentNullException(nameof(team)); + } if (team.Battle.BattleState != PBEBattleState.WaitingForSwitchIns) { throw new InvalidOperationException($"{nameof(team.Battle.BattleState)} must be {PBEBattleState.WaitingForSwitchIns} to create switch-ins."); @@ -399,7 +376,6 @@ string ToDebugString((PBEAction Action, double Score) t) { throw new InvalidOperationException($"{nameof(team)} must require switch-ins."); } - var switches = new List<(byte PokemonId, PBEFieldPosition Position)>(team.SwitchInsRequired); PBEPokemon[] available = team.Party.Where(p => p.FieldPosition == PBEFieldPosition.None && p.HP > 0).ToArray(); available.Shuffle(); var availablePositions = new List(); @@ -441,9 +417,10 @@ string ToDebugString((PBEAction Action, double Score) t) } default: throw new ArgumentOutOfRangeException(nameof(team.Battle.BattleFormat)); } + var switches = new PBESwitchIn[team.SwitchInsRequired]; for (int i = 0; i < team.SwitchInsRequired; i++) { - switches.Add((available[i].Id, availablePositions[i])); + switches[i] = new PBESwitchIn(available[i].Id, availablePositions[i]); } return switches; } diff --git a/PokemonBattleEngine/AI/AITargets.cs b/PokemonBattleEngine/AI/AITargets.cs index 4c2e7d4a3..907ad5fed 100644 --- a/PokemonBattleEngine/AI/AITargets.cs +++ b/PokemonBattleEngine/AI/AITargets.cs @@ -4,11 +4,11 @@ namespace Kermalis.PokemonBattleEngine.AI { - public static partial class PBEAIManager + public static partial class PBEAI { // TODO: Move these to battle targets and make them public; this file doesn't have to exist - private static PBETarget GetSpreadMoveTargets(PBEPokemon pkmn, PBEMoveTarget targets) + private static PBETurnTarget GetSpreadMoveTargets(PBEPokemon pkmn, PBEMoveTarget targets) { switch (pkmn.Team.Battle.BattleFormat) { @@ -20,7 +20,7 @@ private static PBETarget GetSpreadMoveTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return PBETarget.AllyCenter | PBETarget.FoeCenter; + return PBETurnTarget.AllyCenter | PBETurnTarget.FoeCenter; } else { @@ -33,7 +33,7 @@ private static PBETarget GetSpreadMoveTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return PBETarget.FoeCenter; + return PBETurnTarget.FoeCenter; } else { @@ -44,7 +44,7 @@ private static PBETarget GetSpreadMoveTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return PBETarget.AllyCenter; + return PBETurnTarget.AllyCenter; } else { @@ -62,7 +62,7 @@ private static PBETarget GetSpreadMoveTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.AllyLeft | PBETarget.AllyRight | PBETarget.FoeLeft | PBETarget.FoeRight; + return PBETurnTarget.AllyLeft | PBETurnTarget.AllyRight | PBETurnTarget.FoeLeft | PBETurnTarget.FoeRight; } else { @@ -74,7 +74,7 @@ private static PBETarget GetSpreadMoveTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.FoeLeft | PBETarget.FoeRight; + return PBETurnTarget.FoeLeft | PBETurnTarget.FoeRight; } else { @@ -85,7 +85,7 @@ private static PBETarget GetSpreadMoveTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.AllyLeft | PBETarget.AllyRight; + return PBETurnTarget.AllyLeft | PBETurnTarget.AllyRight; } else { @@ -96,11 +96,11 @@ private static PBETarget GetSpreadMoveTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return PBETarget.AllyRight | PBETarget.FoeLeft | PBETarget.FoeRight; + return PBETurnTarget.AllyRight | PBETurnTarget.FoeLeft | PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.AllyLeft | PBETarget.FoeLeft | PBETarget.FoeRight; + return PBETurnTarget.AllyLeft | PBETurnTarget.FoeLeft | PBETurnTarget.FoeRight; } else { @@ -118,7 +118,7 @@ private static PBETarget GetSpreadMoveTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.AllyLeft | PBETarget.AllyCenter | PBETarget.AllyRight | PBETarget.FoeLeft | PBETarget.FoeCenter | PBETarget.FoeRight; + return PBETurnTarget.AllyLeft | PBETurnTarget.AllyCenter | PBETurnTarget.AllyRight | PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight; } else { @@ -129,7 +129,7 @@ private static PBETarget GetSpreadMoveTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.FoeLeft | PBETarget.FoeCenter | PBETarget.FoeRight; + return PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight; } else { @@ -140,15 +140,15 @@ private static PBETarget GetSpreadMoveTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return PBETarget.FoeCenter | PBETarget.FoeRight; + return PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return PBETarget.FoeLeft | PBETarget.FoeCenter | PBETarget.FoeRight; + return PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.FoeLeft | PBETarget.FoeCenter; + return PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter; } else { @@ -159,15 +159,15 @@ private static PBETarget GetSpreadMoveTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return PBETarget.AllyCenter | PBETarget.FoeCenter | PBETarget.FoeRight; + return PBETurnTarget.AllyCenter | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return PBETarget.AllyLeft | PBETarget.AllyRight | PBETarget.FoeLeft | PBETarget.FoeCenter | PBETarget.FoeRight; + return PBETurnTarget.AllyLeft | PBETurnTarget.AllyRight | PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.AllyCenter | PBETarget.FoeLeft | PBETarget.FoeCenter; + return PBETurnTarget.AllyCenter | PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter; } else { @@ -178,7 +178,7 @@ private static PBETarget GetSpreadMoveTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.AllyLeft | PBETarget.AllyCenter | PBETarget.AllyRight; + return PBETurnTarget.AllyLeft | PBETurnTarget.AllyCenter | PBETurnTarget.AllyRight; } else { @@ -196,7 +196,7 @@ private static PBETarget GetSpreadMoveTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.AllyCenter | PBETarget.FoeCenter; + return PBETurnTarget.AllyCenter | PBETurnTarget.FoeCenter; } else { @@ -209,7 +209,7 @@ private static PBETarget GetSpreadMoveTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.FoeCenter; + return PBETurnTarget.FoeCenter; } else { @@ -220,7 +220,7 @@ private static PBETarget GetSpreadMoveTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.AllyCenter; + return PBETurnTarget.AllyCenter; } else { @@ -234,7 +234,7 @@ private static PBETarget GetSpreadMoveTargets(PBEPokemon pkmn, PBEMoveTarget tar } } - private static PBETarget[] GetPossibleTargets(PBEPokemon pkmn, PBEMoveTarget targets) + private static PBETurnTarget[] GetPossibleTargets(PBEPokemon pkmn, PBEMoveTarget targets) { switch (pkmn.Team.Battle.BattleFormat) { @@ -248,7 +248,7 @@ private static PBETarget[] GetPossibleTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return new PBETarget[] { PBETarget.FoeCenter }; + return new PBETurnTarget[] { PBETurnTarget.FoeCenter }; } else { @@ -262,7 +262,7 @@ private static PBETarget[] GetPossibleTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return new PBETarget[] { PBETarget.AllyCenter }; + return new PBETurnTarget[] { PBETurnTarget.AllyCenter }; } else { @@ -281,11 +281,11 @@ private static PBETarget[] GetPossibleTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return new PBETarget[] { PBETarget.AllyLeft }; + return new PBETurnTarget[] { PBETurnTarget.AllyLeft }; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return new PBETarget[] { PBETarget.AllyRight }; + return new PBETurnTarget[] { PBETurnTarget.AllyRight }; } else { @@ -296,7 +296,7 @@ private static PBETarget[] GetPossibleTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { - return new PBETarget[] { PBETarget.AllyLeft, PBETarget.AllyRight }; + return new PBETurnTarget[] { PBETurnTarget.AllyLeft, PBETurnTarget.AllyRight }; } else { @@ -307,11 +307,11 @@ private static PBETarget[] GetPossibleTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return new PBETarget[] { PBETarget.AllyRight }; + return new PBETurnTarget[] { PBETurnTarget.AllyRight }; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return new PBETarget[] { PBETarget.AllyLeft }; + return new PBETurnTarget[] { PBETurnTarget.AllyLeft }; } else { @@ -322,7 +322,7 @@ private static PBETarget[] GetPossibleTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { - return new PBETarget[] { PBETarget.FoeLeft, PBETarget.FoeRight }; + return new PBETurnTarget[] { PBETurnTarget.FoeLeft, PBETurnTarget.FoeRight }; } else { @@ -334,11 +334,11 @@ private static PBETarget[] GetPossibleTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return new PBETarget[] { PBETarget.AllyRight, PBETarget.FoeLeft, PBETarget.FoeRight }; + return new PBETurnTarget[] { PBETurnTarget.AllyRight, PBETurnTarget.FoeLeft, PBETurnTarget.FoeRight }; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return new PBETarget[] { PBETarget.AllyLeft, PBETarget.FoeLeft, PBETarget.FoeRight }; + return new PBETurnTarget[] { PBETurnTarget.AllyLeft, PBETurnTarget.FoeLeft, PBETurnTarget.FoeRight }; } else { @@ -357,15 +357,15 @@ private static PBETarget[] GetPossibleTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return new PBETarget[] { PBETarget.AllyLeft }; + return new PBETurnTarget[] { PBETurnTarget.AllyLeft }; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return new PBETarget[] { PBETarget.AllyCenter }; + return new PBETurnTarget[] { PBETurnTarget.AllyCenter }; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return new PBETarget[] { PBETarget.AllyRight }; + return new PBETurnTarget[] { PBETurnTarget.AllyRight }; } else { @@ -376,15 +376,15 @@ private static PBETarget[] GetPossibleTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return new PBETarget[] { PBETarget.AllyLeft, PBETarget.AllyCenter }; + return new PBETurnTarget[] { PBETurnTarget.AllyLeft, PBETurnTarget.AllyCenter }; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return new PBETarget[] { PBETarget.AllyLeft, PBETarget.AllyCenter, PBETarget.AllyRight }; + return new PBETurnTarget[] { PBETurnTarget.AllyLeft, PBETurnTarget.AllyCenter, PBETurnTarget.AllyRight }; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return new PBETarget[] { PBETarget.AllyCenter, PBETarget.AllyRight }; + return new PBETurnTarget[] { PBETurnTarget.AllyCenter, PBETurnTarget.AllyRight }; } else { @@ -395,11 +395,11 @@ private static PBETarget[] GetPossibleTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { - return new PBETarget[] { PBETarget.AllyCenter }; + return new PBETurnTarget[] { PBETurnTarget.AllyCenter }; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return new PBETarget[] { PBETarget.AllyLeft, PBETarget.AllyRight }; + return new PBETurnTarget[] { PBETurnTarget.AllyLeft, PBETurnTarget.AllyRight }; } else { @@ -410,15 +410,15 @@ private static PBETarget[] GetPossibleTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return new PBETarget[] { PBETarget.FoeCenter, PBETarget.FoeRight }; + return new PBETurnTarget[] { PBETurnTarget.FoeCenter, PBETurnTarget.FoeRight }; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return new PBETarget[] { PBETarget.FoeLeft, PBETarget.FoeCenter, PBETarget.FoeRight }; + return new PBETurnTarget[] { PBETurnTarget.FoeLeft, PBETurnTarget.FoeCenter, PBETurnTarget.FoeRight }; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return new PBETarget[] { PBETarget.FoeLeft, PBETarget.FoeCenter }; + return new PBETurnTarget[] { PBETurnTarget.FoeLeft, PBETurnTarget.FoeCenter }; } else { @@ -429,15 +429,15 @@ private static PBETarget[] GetPossibleTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return new PBETarget[] { PBETarget.AllyCenter, PBETarget.AllyRight, PBETarget.FoeLeft, PBETarget.FoeCenter, PBETarget.FoeRight }; + return new PBETurnTarget[] { PBETurnTarget.AllyCenter, PBETurnTarget.AllyRight, PBETurnTarget.FoeLeft, PBETurnTarget.FoeCenter, PBETurnTarget.FoeRight }; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return new PBETarget[] { PBETarget.AllyLeft, PBETarget.AllyRight, PBETarget.FoeLeft, PBETarget.FoeCenter, PBETarget.FoeRight }; + return new PBETurnTarget[] { PBETurnTarget.AllyLeft, PBETurnTarget.AllyRight, PBETurnTarget.FoeLeft, PBETurnTarget.FoeCenter, PBETurnTarget.FoeRight }; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return new PBETarget[] { PBETarget.AllyLeft, PBETarget.AllyCenter, PBETarget.FoeLeft, PBETarget.FoeCenter, PBETarget.FoeRight }; + return new PBETurnTarget[] { PBETurnTarget.AllyLeft, PBETurnTarget.AllyCenter, PBETurnTarget.FoeLeft, PBETurnTarget.FoeCenter, PBETurnTarget.FoeRight }; } else { @@ -448,15 +448,15 @@ private static PBETarget[] GetPossibleTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return new PBETarget[] { PBETarget.AllyCenter, PBETarget.FoeCenter, PBETarget.FoeRight }; + return new PBETurnTarget[] { PBETurnTarget.AllyCenter, PBETurnTarget.FoeCenter, PBETurnTarget.FoeRight }; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return new PBETarget[] { PBETarget.AllyLeft, PBETarget.AllyRight, PBETarget.FoeLeft, PBETarget.FoeCenter, PBETarget.FoeRight }; + return new PBETurnTarget[] { PBETurnTarget.AllyLeft, PBETurnTarget.AllyRight, PBETurnTarget.FoeLeft, PBETurnTarget.FoeCenter, PBETurnTarget.FoeRight }; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return new PBETarget[] { PBETarget.AllyCenter, PBETarget.FoeLeft, PBETarget.FoeCenter }; + return new PBETurnTarget[] { PBETurnTarget.AllyCenter, PBETurnTarget.FoeLeft, PBETurnTarget.FoeCenter }; } else { @@ -476,7 +476,7 @@ private static PBETarget[] GetPossibleTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { - return new PBETarget[] { PBETarget.FoeCenter }; + return new PBETurnTarget[] { PBETurnTarget.FoeCenter }; } else { @@ -490,7 +490,7 @@ private static PBETarget[] GetPossibleTargets(PBEPokemon pkmn, PBEMoveTarget tar { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { - return new PBETarget[] { PBETarget.AllyCenter }; + return new PBETurnTarget[] { PBETurnTarget.AllyCenter }; } else { diff --git a/PokemonBattleEngine/Battle/Battle.cs b/PokemonBattleEngine/Battle/Battle.cs index 29e0901ec..fc841e9ae 100644 --- a/PokemonBattleEngine/Battle/Battle.cs +++ b/PokemonBattleEngine/Battle/Battle.cs @@ -2,15 +2,22 @@ using Kermalis.PokemonBattleEngine.Data; using System; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.Linq; namespace Kermalis.PokemonBattleEngine.Battle { - // TODO: INPC + // TODO: Fully implement INPC /// Represents a specific Pokémon battle. - public sealed partial class PBEBattle + public sealed partial class PBEBattle : IDisposable, INotifyPropertyChanged { + private void OnPropertyChanged(string property) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); + } + public event PropertyChangedEventHandler PropertyChanged; + public delegate void BattleStateChangedEvent(PBEBattle battle); public event BattleStateChangedEvent OnStateChanged; public PBEBattleState BattleState { get; private set; } @@ -20,9 +27,9 @@ public sealed partial class PBEBattle public PBEBattleFormat BattleFormat { get; } public PBESettings Settings { get; } - public PBETeam[] Teams { get; } = new PBETeam[2]; + public PBETeams Teams { get; } public List ActiveBattlers { get; } = new List(6); - private readonly List turnOrder = new List(); + private readonly List _turnOrder = new List(); public PBEWeather Weather { get; set; } public byte WeatherCounter { get; set; } @@ -35,49 +42,59 @@ public sealed partial class PBEBattle /// The ID of the . public PBEPokemon TryGetPokemon(byte pkmnId) { + if (IsDisposed) + { + throw new ObjectDisposedException(null); + } return Teams.SelectMany(t => t.Party).SingleOrDefault(p => p.Id == pkmnId); } - private byte pkmnIdCounter; /// Creates a new object with the specified and teams. Each team must have equal settings. The battle's settings are set to a copy of the teams' settings. will be . /// The of the battle. - /// The object to use to create [0]. - /// The name of the trainer(s) on [0]. - /// The object to use to create [1]. - /// The name of the trainer(s) on [1]. - /// Thrown when or are null. - /// Thrown when and have unequal or when or are invalid. - public PBEBattle(PBEBattleFormat battleFormat, PBETeamShell team0Shell, string team0TrainerName, PBETeamShell team1Shell, string team1TrainerName) + /// The object to use to create [0]. + /// The name of the trainer(s) on [0]. + /// The object to use to create [1]. + /// The name of the trainer(s) on [1]. + /// Thrown when or are null. + /// Thrown when and have unequal or when or are invalid. + public PBEBattle(PBEBattleFormat battleFormat, PBETeamShell team1Shell, string team1TrainerName, PBETeamShell team2Shell, string team2TrainerName) { if (battleFormat >= PBEBattleFormat.MAX) { throw new ArgumentOutOfRangeException(nameof(battleFormat)); } - if (team0Shell == null) - { - throw new ArgumentNullException(nameof(team0Shell)); - } - if (string.IsNullOrWhiteSpace(team0TrainerName)) - { - throw new ArgumentOutOfRangeException(nameof(team0TrainerName)); - } if (team1Shell == null) { throw new ArgumentNullException(nameof(team1Shell)); } + if (team2Shell == null) + { + throw new ArgumentNullException(nameof(team2Shell)); + } if (string.IsNullOrWhiteSpace(team1TrainerName)) { throw new ArgumentOutOfRangeException(nameof(team1TrainerName)); } - if (!team0Shell.Settings.Equals(team1Shell.Settings)) + if (string.IsNullOrWhiteSpace(team2TrainerName)) { - throw new ArgumentOutOfRangeException(nameof(team0Shell.Settings), "Team settings must be equal to each other."); + throw new ArgumentOutOfRangeException(nameof(team2TrainerName)); + } + if (team1Shell.IsDisposed) + { + throw new ObjectDisposedException(nameof(team1Shell)); + } + if (team2Shell.IsDisposed) + { + throw new ObjectDisposedException(nameof(team2Shell)); + } + if (!team1Shell.Settings.Equals(team2Shell.Settings)) + { + throw new ArgumentOutOfRangeException(nameof(team1Shell.Settings), "Team settings must be equal to each other."); } BattleFormat = battleFormat; - Settings = new PBESettings(team0Shell.Settings); + Settings = new PBESettings(team1Shell.Settings); Settings.MakeReadOnly(); - Teams[0] = new PBETeam(this, 0, team0Shell, team0TrainerName, ref pkmnIdCounter); - Teams[1] = new PBETeam(this, 1, team1Shell, team1TrainerName, ref pkmnIdCounter); + Teams = new PBETeams(this, team1Shell, team1TrainerName, team2Shell, team2TrainerName); CheckForReadiness(); } /// Creates a new object with the specified and a copy of the specified . will be . @@ -96,16 +113,13 @@ public PBEBattle(PBEBattleFormat battleFormat, PBESettings settings) BattleFormat = battleFormat; Settings = new PBESettings(settings); Settings.MakeReadOnly(); - - Teams[0] = new PBETeam(this, 0); - Teams[1] = new PBETeam(this, 1); - + Teams = new PBETeams(this); BattleState = PBEBattleState.WaitingForPlayers; OnStateChanged?.Invoke(this); } private void CheckForReadiness() { - if (Array.TrueForAll(Teams, t => t.NumPkmnAlive > 0)) + if (Teams.All(t => t.NumPkmnAlive > 0)) { switch (BattleFormat) { @@ -113,8 +127,9 @@ private void CheckForReadiness() { foreach (PBETeam team in Teams) { - team.Party[0].FieldPosition = PBEFieldPosition.Center; - team.SwitchInQueue.Add(team.Party[0]); + PBEPokemon pkmn = team.Party[0]; + pkmn.FieldPosition = PBEFieldPosition.Center; + team.SwitchInQueue.Add(pkmn); } break; } @@ -122,12 +137,14 @@ private void CheckForReadiness() { foreach (PBETeam team in Teams) { - team.Party[0].FieldPosition = PBEFieldPosition.Left; - team.SwitchInQueue.Add(team.Party[0]); + PBEPokemon pkmn = team.Party[0]; + pkmn.FieldPosition = PBEFieldPosition.Left; + team.SwitchInQueue.Add(pkmn); if (team.Party.Count > 1) { - team.Party[1].FieldPosition = PBEFieldPosition.Right; - team.SwitchInQueue.Add(team.Party[1]); + pkmn = team.Party[1]; + pkmn.FieldPosition = PBEFieldPosition.Right; + team.SwitchInQueue.Add(pkmn); } } break; @@ -136,17 +153,20 @@ private void CheckForReadiness() { foreach (PBETeam team in Teams) { - team.Party[0].FieldPosition = PBEFieldPosition.Left; - team.SwitchInQueue.Add(team.Party[0]); + PBEPokemon pkmn = team.Party[0]; + pkmn.FieldPosition = PBEFieldPosition.Left; + team.SwitchInQueue.Add(pkmn); if (team.Party.Count > 1) { - team.Party[1].FieldPosition = PBEFieldPosition.Center; - team.SwitchInQueue.Add(team.Party[1]); + pkmn = team.Party[1]; + pkmn.FieldPosition = PBEFieldPosition.Center; + team.SwitchInQueue.Add(pkmn); } if (team.Party.Count > 2) { - team.Party[2].FieldPosition = PBEFieldPosition.Right; - team.SwitchInQueue.Add(team.Party[2]); + pkmn = team.Party[2]; + pkmn.FieldPosition = PBEFieldPosition.Right; + team.SwitchInQueue.Add(pkmn); } } break; @@ -155,17 +175,20 @@ private void CheckForReadiness() { foreach (PBETeam team in Teams) { - team.Party[0].FieldPosition = PBEFieldPosition.Center; - team.SwitchInQueue.Add(team.Party[0]); + PBEPokemon pkmn = team.Party[0]; + pkmn.FieldPosition = PBEFieldPosition.Center; + team.SwitchInQueue.Add(pkmn); if (team.Party.Count > 1) { - team.Party[1].FieldPosition = PBEFieldPosition.Left; - team.SwitchInQueue.Add(team.Party[1]); + pkmn = team.Party[1]; + pkmn.FieldPosition = PBEFieldPosition.Left; + team.SwitchInQueue.Add(pkmn); } if (team.Party.Count > 2) { - team.Party[2].FieldPosition = PBEFieldPosition.Right; - team.SwitchInQueue.Add(team.Party[2]); + pkmn = team.Party[2]; + pkmn.FieldPosition = PBEFieldPosition.Right; + team.SwitchInQueue.Add(pkmn); } } break; @@ -186,14 +209,6 @@ private void CheckForReadiness() /// Thrown when 's settings are unequal to 's battle's settings or when is invalid. public static void CreateTeamParty(PBETeam team, PBETeamShell teamShell, string teamTrainerName) { - if (team.Battle.BattleState != PBEBattleState.WaitingForPlayers) - { - throw new InvalidOperationException($"{nameof(BattleState)} must be {PBEBattleState.WaitingForPlayers} to set a team's party."); - } - if (team.Party.Count > 0) - { - throw new InvalidOperationException("This team already has its party set."); - } if (team == null) { throw new ArgumentNullException(nameof(team)); @@ -202,41 +217,82 @@ public static void CreateTeamParty(PBETeam team, PBETeamShell teamShell, string { throw new ArgumentNullException(nameof(teamShell)); } + if (string.IsNullOrEmpty(teamTrainerName)) + { + throw new ArgumentOutOfRangeException(nameof(teamTrainerName)); + } + if (team.IsDisposed) + { + throw new ObjectDisposedException(nameof(team)); + } + if (teamShell.IsDisposed) + { + throw new ObjectDisposedException(nameof(teamShell)); + } if (!teamShell.Settings.Equals(team.Battle.Settings)) { throw new ArgumentOutOfRangeException(nameof(teamShell), $"\"{nameof(teamShell)}\"'s settings must be equal to the battle's settings."); } - if (string.IsNullOrEmpty(teamTrainerName)) + if (team.Battle.BattleState != PBEBattleState.WaitingForPlayers) { - throw new ArgumentOutOfRangeException(nameof(teamTrainerName)); + throw new InvalidOperationException($"{nameof(BattleState)} must be {PBEBattleState.WaitingForPlayers} to set a team's party."); } - team.CreateParty(teamShell, teamTrainerName, ref team.Battle.pkmnIdCounter); + if (team.Party.Count > 0) + { + throw new InvalidOperationException("This team already has its party set."); + } + team.CreateParty(teamShell, teamTrainerName); team.Battle.CheckForReadiness(); } /// Begins the battle. /// Thrown when is not . public void Begin() { + if (IsDisposed) + { + throw new ObjectDisposedException(null); + } if (BattleState != PBEBattleState.ReadyToBegin) { throw new InvalidOperationException($"{nameof(BattleState)} must be {PBEBattleState.ReadyToBegin} to begin the battle."); } - SwitchesOrActions(); + lock (_disposeLockObj) + { + if (!IsDisposed) + { + foreach (PBETeam team in Teams) + { + BroadcastTeam(team); + } + SwitchesOrActions(); + } + } } /// Runs a turn. /// Thrown when is not . public void RunTurn() { + if (IsDisposed) + { + throw new ObjectDisposedException(null); + } if (BattleState != PBEBattleState.ReadyToRunTurn) { throw new InvalidOperationException($"{nameof(BattleState)} must be {PBEBattleState.ReadyToRunTurn} to run a turn."); } - BattleState = PBEBattleState.Processing; - OnStateChanged?.Invoke(this); - DetermineTurnOrder(); - RunActionsInOrder(); - TurnEnded(); + lock (_disposeLockObj) + { + if (!IsDisposed) + { + BattleState = PBEBattleState.Processing; + OnStateChanged?.Invoke(this); + DetermineTurnOrder(); + RunActionsInOrder(); + TurnEnded(); + } + } } + private bool WinCheck() { if (Winner != null) @@ -253,13 +309,13 @@ private void SwitchesOrActions() BattleState = PBEBattleState.Processing; OnStateChanged?.Invoke(this); - IEnumerable teamsWithSwitchIns = Teams.Where(t => t.SwitchInQueue.Count > 0); - if (teamsWithSwitchIns.Count() > 0) + PBETeam[] teamsWithSwitchIns = Teams.Where(t => t.SwitchInQueue.Count > 0).ToArray(); + if (teamsWithSwitchIns.Length > 0) { foreach (PBETeam team in teamsWithSwitchIns) { ActiveBattlers.AddRange(team.SwitchInQueue); - BroadcastPkmnSwitchIn(team, team.SwitchInQueue.Select(p => CreateSwitchInInfo(p)), false); + BroadcastPkmnSwitchIn(team, team.SwitchInQueue.Select(p => CreateSwitchInInfo(p)).ToArray(), false); } DoSwitchInEffects(teamsWithSwitchIns.SelectMany(t => t.SwitchInQueue)); } @@ -314,8 +370,8 @@ private void SwitchesOrActions() default: throw new ArgumentOutOfRangeException(nameof(BattleFormat)); } } - teamsWithSwitchIns = Teams.Where(t => t.SwitchInsRequired > 0); - if (teamsWithSwitchIns.Count() > 0) + teamsWithSwitchIns = Teams.Where(t => t.SwitchInsRequired > 0).ToArray(); + if (teamsWithSwitchIns.Length > 0) { BattleState = PBEBattleState.WaitingForSwitchIns; OnStateChanged?.Invoke(this); @@ -393,7 +449,7 @@ private void SwitchesOrActions() } } } - private IEnumerable GetActingOrder(IEnumerable pokemon, bool ignoreItemsThatActivate) + private PBEPokemon[] GetActingOrder(IEnumerable pokemon, bool ignoreItemsThatActivate) { var evaluated = new List<(PBEPokemon Pokemon, double Speed)>(); // TODO: Full Incense, Lagging Tail, Stall, Quick Claw foreach (PBEPokemon pkmn in pokemon) @@ -497,34 +553,34 @@ private IEnumerable GetActingOrder(IEnumerable pokemon, } Debug.WriteLine(evaluated.Select(t => $"{t.Pokemon.Team.Id} {t.Pokemon.Nickname} {t.Speed}").Print()); } - return evaluated.Select(t => t.Pokemon); + return evaluated.Select(t => t.Pokemon).ToArray(); } private void DetermineTurnOrder() { - turnOrder.Clear(); - IEnumerable pkmnSwitchingOut = ActiveBattlers.Where(p => p.SelectedAction.Decision == PBEDecision.SwitchOut); - IEnumerable pkmnFighting = ActiveBattlers.Where(p => p.SelectedAction.Decision == PBEDecision.Fight); + _turnOrder.Clear(); + IEnumerable pkmnSwitchingOut = ActiveBattlers.Where(p => p.TurnAction.Decision == PBETurnDecision.SwitchOut); + IEnumerable pkmnFighting = ActiveBattlers.Where(p => p.TurnAction.Decision == PBETurnDecision.Fight); // Switching happens first: - turnOrder.AddRange(GetActingOrder(pkmnSwitchingOut, true)); + _turnOrder.AddRange(GetActingOrder(pkmnSwitchingOut, true)); // Moves: sbyte GetPrio(PBEPokemon p) { - PBEMoveData mData = PBEMoveData.Data[p.SelectedAction.FightMove]; + PBEMoveData mData = PBEMoveData.Data[p.TurnAction.FightMove]; return (sbyte)PBEUtils.Clamp(mData.Priority + (p.Ability == PBEAbility.Prankster && mData.Category == PBEMoveCategory.Status ? 1 : 0), sbyte.MinValue, sbyte.MaxValue); } foreach (sbyte priority in pkmnFighting.Select(p => GetPrio(p)).Distinct().OrderByDescending(p => p)) { - IEnumerable pkmnWithThisPriority = pkmnFighting.Where(p => GetPrio(p) == priority); - if (pkmnWithThisPriority.Count() > 0) + PBEPokemon[] pkmnWithThisPriority = pkmnFighting.Where(p => GetPrio(p) == priority).ToArray(); + if (pkmnWithThisPriority.Length > 0) { Debug.WriteLine("Priority {0} bracket...", priority); - turnOrder.AddRange(GetActingOrder(pkmnWithThisPriority, false)); + _turnOrder.AddRange(GetActingOrder(pkmnWithThisPriority, false)); } } } private void RunActionsInOrder() { - foreach (PBEPokemon pkmn in turnOrder.ToArray()) // Copy the list so a faint or ejection does not cause a collection modified exception + foreach (PBEPokemon pkmn in _turnOrder.ToArray()) // Copy the list so a faint or ejection does not cause a collection modified exception { if (Winner != null) // Do not broadcast winner by calling WinCheck() in here; do it in TurnEnded() { @@ -532,19 +588,19 @@ private void RunActionsInOrder() } else if (ActiveBattlers.Contains(pkmn)) { - switch (pkmn.SelectedAction.Decision) + switch (pkmn.TurnAction.Decision) { - case PBEDecision.Fight: + case PBETurnDecision.Fight: { - UseMove(pkmn, pkmn.SelectedAction.FightMove, pkmn.SelectedAction.FightTargets); + UseMove(pkmn, pkmn.TurnAction.FightMove, pkmn.TurnAction.FightTargets); break; } - case PBEDecision.SwitchOut: + case PBETurnDecision.SwitchOut: { - SwitchTwoPokemon(pkmn, TryGetPokemon(pkmn.SelectedAction.SwitchPokemonId), false); + SwitchTwoPokemon(pkmn, TryGetPokemon(pkmn.TurnAction.SwitchPokemonId), false); break; } - default: throw new ArgumentOutOfRangeException(nameof(pkmn.SelectedAction.Decision)); + default: throw new ArgumentOutOfRangeException(nameof(pkmn.TurnAction.Decision)); } } } @@ -564,7 +620,7 @@ private void TurnEnded() return; } - // Verified: Reflect then Light Screen then Lucky Chant then Trick Room + // Reflect/Light Screen/Lucky Chant/Trick Room are removed in the order they were added, but who cares foreach (PBETeam team in Teams) { if (team.TeamStatus.HasFlag(PBETeamStatus.Reflect)) @@ -609,5 +665,23 @@ private void TurnEnded() SwitchesOrActions(); } + + private readonly object _disposeLockObj = new object(); + public bool IsDisposed { get; private set; } + public void Dispose() + { + lock (_disposeLockObj) + { + if (!IsDisposed) + { + IsDisposed = true; + OnPropertyChanged(nameof(IsDisposed)); + foreach (PBETeam team in Teams) + { + team.Dispose(); + } + } + } + } } } diff --git a/PokemonBattleEngine/Battle/BattleActions.cs b/PokemonBattleEngine/Battle/BattleActions.cs index 56954c69c..878a675cb 100644 --- a/PokemonBattleEngine/Battle/BattleActions.cs +++ b/PokemonBattleEngine/Battle/BattleActions.cs @@ -3,66 +3,130 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Runtime.InteropServices; namespace Kermalis.PokemonBattleEngine.Battle { - [StructLayout(LayoutKind.Explicit)] - public struct PBEAction + public sealed class PBETurnAction { - [FieldOffset(0)] - public byte PokemonId; - [FieldOffset(1)] - public PBEDecision Decision; - [FieldOffset(2)] - public PBEMove FightMove; - [FieldOffset(4)] - public PBETarget FightTargets; - [FieldOffset(4)] - public byte SwitchPokemonId; + public byte PokemonId { get; } + public PBETurnDecision Decision { get; } + public PBEMove FightMove { get; } + public PBETurnTarget FightTargets { get; internal set; } // Internal set because of PBEMoveTarget.RandomFoeSurrounding + public byte SwitchPokemonId { get; } - internal List ToBytes() + internal PBETurnAction(BinaryReader r) + { + PokemonId = r.ReadByte(); + Decision = (PBETurnDecision)r.ReadByte(); + switch (Decision) + { + case PBETurnDecision.Fight: + { + FightMove = (PBEMove)r.ReadUInt16(); + FightTargets = (PBETurnTarget)r.ReadByte(); + break; + } + case PBETurnDecision.SwitchOut: + { + SwitchPokemonId = r.ReadByte(); + break; + } + default: throw new ArgumentOutOfRangeException(nameof(Decision)); + } + } + public PBETurnAction(byte pokemonId, PBEMove fightMove, PBETurnTarget fightTargets) + { + PokemonId = pokemonId; + Decision = PBETurnDecision.Fight; + FightMove = fightMove; + FightTargets = fightTargets; + } + public PBETurnAction(byte pokemonId, byte switchPokemonId) + { + PokemonId = pokemonId; + Decision = PBETurnDecision.SwitchOut; + SwitchPokemonId = switchPokemonId; + } + + internal void ToBytes(List bytes) { - var bytes = new List(); bytes.Add(PokemonId); bytes.Add((byte)Decision); - bytes.AddRange(BitConverter.GetBytes((ushort)FightMove)); - bytes.Add(SwitchPokemonId); - return bytes; + switch (Decision) + { + case PBETurnDecision.Fight: + { + bytes.AddRange(BitConverter.GetBytes((ushort)FightMove)); + bytes.Add((byte)FightTargets); + break; + } + case PBETurnDecision.SwitchOut: + { + bytes.Add(SwitchPokemonId); + break; + } + throw new ArgumentOutOfRangeException(nameof(Decision)); + } } - internal static PBEAction FromBytes(BinaryReader r) + } + public sealed class PBESwitchIn + { + public byte PokemonId { get; } + public PBEFieldPosition Position { get; } + + internal PBESwitchIn(BinaryReader r) { - return new PBEAction - { - PokemonId = r.ReadByte(), - Decision = (PBEDecision)r.ReadByte(), - FightMove = (PBEMove)r.ReadUInt16(), - SwitchPokemonId = r.ReadByte() - }; + PokemonId = r.ReadByte(); + Position = (PBEFieldPosition)r.ReadByte(); + } + public PBESwitchIn(byte pokemonId, PBEFieldPosition position) + { + PokemonId = pokemonId; + Position = position; + } + + internal void ToBytes(List bytes) + { + bytes.Add(PokemonId); + bytes.Add((byte)Position); } } public sealed partial class PBEBattle { - /// - /// Determines whether chosen actions are valid. - /// + /// Determines whether chosen actions are valid. /// The team the inputted actions belong to. /// The actions the team wishes to execute. /// False if the team already chose actions or the actions are illegal, True otherwise. /// Thrown when is not . - public static bool AreActionsValid(PBETeam team, IEnumerable actions) + public static bool AreActionsValid(PBETeam team, IList actions) { + if (team == null) + { + throw new ArgumentNullException(nameof(team)); + } + if (actions == null) + { + throw new ArgumentNullException(nameof(actions)); + } + if (team.IsDisposed) + { + throw new ObjectDisposedException(nameof(team)); + } if (team.Battle.BattleState != PBEBattleState.WaitingForActions) { throw new InvalidOperationException($"{nameof(BattleState)} must be {PBEBattleState.WaitingForActions} to validate actions."); } - if (team.ActionsRequired.Count == 0 || actions.Count() != team.ActionsRequired.Count) + if (team.ActionsRequired.Count == 0 || actions.Count != team.ActionsRequired.Count) { return false; } var standBy = new List(); - foreach (PBEAction action in actions) + foreach (PBETurnAction action in actions) { + if (action == null) + { + return false; + } PBEPokemon pkmn = team.TryGetPokemon(action.PokemonId); if (!team.ActionsRequired.Contains(pkmn)) { @@ -70,7 +134,7 @@ public static bool AreActionsValid(PBETeam team, IEnumerable actions) } switch (action.Decision) { - case PBEDecision.Fight: + case PBETurnDecision.Fight: { if (Array.IndexOf(pkmn.GetUsableMoves(), action.FightMove) == -1 || (action.FightMove == pkmn.TempLockedMove && action.FightTargets != pkmn.TempLockedTargets) @@ -81,7 +145,7 @@ public static bool AreActionsValid(PBETeam team, IEnumerable actions) } break; } - case PBEDecision.SwitchOut: + case PBETurnDecision.SwitchOut: { if (!pkmn.CanSwitchOut()) { @@ -102,141 +166,161 @@ public static bool AreActionsValid(PBETeam team, IEnumerable actions) } break; } + default: return false; } } return true; } - /// - /// Selects actions if they are valid. Changes the battle state if both teams have selected valid actions. - /// + /// Selects actions if they are valid. Changes the battle state if both teams have selected valid actions. /// The team the inputted actions belong to. /// The actions the team wishes to execute. /// True if the actions are valid and were selected. /// Thrown when is not . - public static bool SelectActionsIfValid(PBETeam team, IEnumerable actions) + public static bool SelectActionsIfValid(PBETeam team, IList actions) { + if (team == null) + { + throw new ArgumentNullException(nameof(team)); + } + if (actions == null) + { + throw new ArgumentNullException(nameof(actions)); + } + if (team.IsDisposed) + { + throw new ObjectDisposedException(nameof(team)); + } if (team.Battle.BattleState != PBEBattleState.WaitingForActions) { throw new InvalidOperationException($"{nameof(BattleState)} must be {PBEBattleState.WaitingForActions} to select actions."); } if (AreActionsValid(team, actions)) { - team.ActionsRequired.Clear(); - foreach (PBEAction action in actions) + lock (team.Battle._disposeLockObj) { - PBEPokemon pkmn = team.TryGetPokemon(action.PokemonId); - pkmn.SelectedAction = action; - switch (pkmn.SelectedAction.Decision) + if (!team.Battle.IsDisposed) { - case PBEDecision.Fight: + team.ActionsRequired.Clear(); + foreach (PBETurnAction action in actions) { - switch (pkmn.GetMoveTargets(pkmn.SelectedAction.FightMove)) + PBEPokemon pkmn = team.TryGetPokemon(action.PokemonId); + if (action.Decision == PBETurnDecision.Fight && pkmn.GetMoveTargets(action.FightMove) == PBEMoveTarget.RandomFoeSurrounding) { - case PBEMoveTarget.RandomFoeSurrounding: + switch (team.Battle.BattleFormat) { - switch (team.Battle.BattleFormat) + case PBEBattleFormat.Single: + case PBEBattleFormat.Rotation: { - case PBEBattleFormat.Single: - case PBEBattleFormat.Rotation: - { - pkmn.SelectedAction.FightTargets = PBETarget.FoeCenter; - break; - } - case PBEBattleFormat.Double: + action.FightTargets = PBETurnTarget.FoeCenter; + break; + } + case PBEBattleFormat.Double: + { + action.FightTargets = PBEUtils.RandomBool() ? PBETurnTarget.FoeLeft : PBETurnTarget.FoeRight; + break; + } + case PBEBattleFormat.Triple: + { + if (pkmn.FieldPosition == PBEFieldPosition.Left) { - pkmn.SelectedAction.FightTargets = PBEUtils.RandomBool() ? PBETarget.FoeLeft : PBETarget.FoeRight; - break; + action.FightTargets = PBEUtils.RandomBool() ? PBETurnTarget.FoeCenter : PBETurnTarget.FoeRight; } - case PBEBattleFormat.Triple: + else if (pkmn.FieldPosition == PBEFieldPosition.Center) { - if (pkmn.FieldPosition == PBEFieldPosition.Left) - { - pkmn.SelectedAction.FightTargets = PBEUtils.RandomBool() ? PBETarget.FoeCenter : PBETarget.FoeRight; - } - else if (pkmn.FieldPosition == PBEFieldPosition.Center) + int r; // Keep randomly picking until a non-fainted foe is selected + roll: + r = PBEUtils.RandomInt(0, 2); + if (r == 0) { - PBETeam opposingTeam = team == team.Battle.Teams[0] ? team.Battle.Teams[1] : team.Battle.Teams[0]; - int r; // Keep randomly picking until a non-fainted foe is selected - roll: - r = PBEUtils.RandomInt(0, 2); - if (r == 0) + if (team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Left) != null) { - if (opposingTeam.TryGetPokemon(PBEFieldPosition.Left) != null) - { - pkmn.SelectedAction.FightTargets = PBETarget.FoeLeft; - } - else - { - goto roll; - } + action.FightTargets = PBETurnTarget.FoeLeft; } - else if (r == 1) + else + { + goto roll; + } + } + else if (r == 1) + { + if (team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Center) != null) { - if (opposingTeam.TryGetPokemon(PBEFieldPosition.Center) != null) - { - pkmn.SelectedAction.FightTargets = PBETarget.FoeCenter; - } - else - { - goto roll; - } + action.FightTargets = PBETurnTarget.FoeCenter; } else { - if (opposingTeam.TryGetPokemon(PBEFieldPosition.Right) != null) - { - pkmn.SelectedAction.FightTargets = PBETarget.FoeRight; - } - else - { - goto roll; - } + goto roll; } } else { - pkmn.SelectedAction.FightTargets = PBEUtils.RandomBool() ? PBETarget.FoeLeft : PBETarget.FoeCenter; + if (team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Right) != null) + { + action.FightTargets = PBETurnTarget.FoeRight; + } + else + { + goto roll; + } } - break; } + else + { + action.FightTargets = PBEUtils.RandomBool() ? PBETurnTarget.FoeLeft : PBETurnTarget.FoeCenter; + } + break; } - break; + default: throw new ArgumentOutOfRangeException(nameof(team.Battle.BattleFormat)); } } - break; + pkmn.TurnAction = action; } + if (team.Battle.Teams.All(t => t.ActionsRequired.Count == 0)) + { + team.Battle.BattleState = PBEBattleState.ReadyToRunTurn; + team.Battle.OnStateChanged?.Invoke(team.Battle); + } + return true; } } - if (Array.TrueForAll(team.Battle.Teams, t => t.ActionsRequired.Count == 0)) - { - team.Battle.BattleState = PBEBattleState.ReadyToRunTurn; - team.Battle.OnStateChanged?.Invoke(team.Battle); - } - return true; } return false; } - /// - /// Determines whether chosen switches are valid. - /// + /// Determines whether chosen switches are valid. /// The team the inputted switches belong to. /// The switches the team wishes to execute. /// False if the team already chose switches or the switches are illegal, True otherwise. /// Thrown when is not . - public static bool AreSwitchesValid(PBETeam team, IEnumerable<(byte PokemonId, PBEFieldPosition Position)> switches) + public static bool AreSwitchesValid(PBETeam team, IList switches) { + if (team == null) + { + throw new ArgumentNullException(nameof(team)); + } + if (switches == null) + { + throw new ArgumentNullException(nameof(switches)); + } + if (team.IsDisposed) + { + throw new ObjectDisposedException(nameof(team)); + } if (team.Battle.BattleState != PBEBattleState.WaitingForSwitchIns) { throw new InvalidOperationException($"{nameof(BattleState)} must be {PBEBattleState.WaitingForSwitchIns} to validate switches."); } - if (team.SwitchInsRequired == 0 || switches.Count() != team.SwitchInsRequired) + if (team.SwitchInsRequired == 0 || switches.Count != team.SwitchInsRequired) { return false; } - foreach ((byte PokemonId, PBEFieldPosition Position) in switches) + foreach (PBESwitchIn s in switches) { - PBEPokemon pkmn = team.TryGetPokemon(PokemonId); + if (s == null) + { + return false; + } + PBEPokemon pkmn = team.TryGetPokemon(s.PokemonId); if (pkmn == null || pkmn.HP == 0 || pkmn.FieldPosition != PBEFieldPosition.None) { return false; @@ -244,33 +328,49 @@ public static bool AreSwitchesValid(PBETeam team, IEnumerable<(byte PokemonId, P } return true; } - /// - /// Selects switches if they are valid. Changes the battle state if both teams have selected valid switches. - /// + /// Selects switches if they are valid. Changes the battle state if both teams have selected valid switches. /// The team the inputted switches belong to. /// The switches the team wishes to execute. /// True if the switches are valid and were selected. /// Thrown when is not . - public static bool SelectSwitchesIfValid(PBETeam team, IEnumerable<(byte PokemonId, PBEFieldPosition Position)> switches) + public static bool SelectSwitchesIfValid(PBETeam team, IList switches) { + if (team == null) + { + throw new ArgumentNullException(nameof(team)); + } + if (switches == null) + { + throw new ArgumentNullException(nameof(switches)); + } + if (team.IsDisposed) + { + throw new ObjectDisposedException(nameof(team)); + } if (team.Battle.BattleState != PBEBattleState.WaitingForSwitchIns) { throw new InvalidOperationException($"{nameof(BattleState)} must be {PBEBattleState.WaitingForSwitchIns} to select switches."); } if (AreSwitchesValid(team, switches)) { - team.SwitchInsRequired = 0; - foreach ((byte PokemonId, PBEFieldPosition Position) in switches) + lock (team.Battle._disposeLockObj) { - PBEPokemon pkmn = team.TryGetPokemon(PokemonId); - pkmn.FieldPosition = Position; - team.SwitchInQueue.Add(pkmn); - } - if (Array.TrueForAll(team.Battle.Teams, t => t.SwitchInsRequired == 0)) - { - team.Battle.SwitchesOrActions(); + if (!team.Battle.IsDisposed) + { + team.SwitchInsRequired = 0; + foreach (PBESwitchIn s in switches) + { + PBEPokemon pkmn = team.TryGetPokemon(s.PokemonId); + pkmn.FieldPosition = s.Position; + team.SwitchInQueue.Add(pkmn); + } + if (team.Battle.Teams.All(t => t.SwitchInsRequired == 0)) + { + team.Battle.SwitchesOrActions(); + } + return true; + } } - return true; } return false; } diff --git a/PokemonBattleEngine/Battle/BattleDamage.cs b/PokemonBattleEngine/Battle/BattleDamage.cs index 2a605447b..48a23e635 100644 --- a/PokemonBattleEngine/Battle/BattleDamage.cs +++ b/PokemonBattleEngine/Battle/BattleDamage.cs @@ -6,9 +6,7 @@ namespace Kermalis.PokemonBattleEngine.Battle { public sealed partial class PBEBattle { - /// - /// Gets the influence a stat change has on a stat. - /// + /// Gets the influence a stat change has on a stat. /// The stat change. /// True if the stat is or . public static double GetStatChangeModifier(sbyte change, bool forMissing) @@ -19,9 +17,7 @@ public static double GetStatChangeModifier(sbyte change, bool forMissing) return numerator / denominator; } - /// - /// Deals damage to and broadcasts the HP changing and substitute damage. - /// + /// Deals damage to and broadcasts the HP changing and substitute damage. /// The Pokémon responsible for the damage. /// The Pokémon receiving the damage. /// The amount of HP will try to lose. @@ -81,9 +77,7 @@ private ushort DealDamage(PBEPokemon culprit, PBEPokemon victim, ushort hp, bool return (ushort)(oldHP - victim.HP); } } - /// - /// Restores HP to and broadcasts the HP changing if it changes. - /// + /// 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. @@ -502,6 +496,10 @@ private double CalculateBasePower(PBEPokemon user, PBEPokemon[] targets, PBEMove break; } break; + case PBEType.None: + { + break; + } case PBEType.Normal: switch (user.Item) { @@ -613,6 +611,7 @@ private double CalculateBasePower(PBEPokemon user, PBEPokemon[] targets, PBEMove break; } break; + default: throw new ArgumentOutOfRangeException(nameof(moveType)); } // Move-specific power boosts diff --git a/PokemonBattleEngine/Battle/BattleEffects.cs b/PokemonBattleEngine/Battle/BattleEffects.cs index be76fe6f6..b80ae866b 100644 --- a/PokemonBattleEngine/Battle/BattleEffects.cs +++ b/PokemonBattleEngine/Battle/BattleEffects.cs @@ -8,13 +8,13 @@ namespace Kermalis.PokemonBattleEngine.Battle { public sealed partial class PBEBattle { - private bool calledFromOtherMove = false; + private static readonly PBEStat[] _moodyStats = Enum.GetValues(typeof(PBEStat)).Cast().Except(new[] { PBEStat.HP }).ToArray(); + + private bool _calledFromOtherMove = false; private void DoSwitchInEffects(IEnumerable battlers) { - IEnumerable order = GetActingOrder(battlers, true); - - foreach (PBEPokemon pkmn in order) + foreach (PBEPokemon pkmn in GetActingOrder(battlers, true)) { // Verified: (Spikes/StealthRock/ToxicSpikes in the order they were applied) before ability if (pkmn.Team.TeamStatus.HasFlag(PBETeamStatus.Spikes) && !pkmn.HasType(PBEType.Flying) && pkmn.Ability != PBEAbility.Levitate) @@ -101,8 +101,7 @@ private void DoSwitchInEffects(IEnumerable battlers) case PBEAbility.Imposter: { PBEFieldPosition targetPos = GetPositionAcross(BattleFormat, pkmn.FieldPosition); - PBETeam opposingTeam = pkmn.Team == Teams[0] ? Teams[1] : Teams[0]; - PBEPokemon target = opposingTeam.TryGetPokemon(targetPos); + PBEPokemon target = pkmn.Team.OpposingTeam.TryGetPokemon(targetPos); if (target != null && !target.Status2.HasFlag(PBEStatus2.Disguised) && !target.Status2.HasFlag(PBEStatus2.Substitute) @@ -138,9 +137,7 @@ private void DoSwitchInEffects(IEnumerable battlers) } } } - /// - /// Does effects that take place after hitting such as substitute breaking, rough skin, and victim eating its berry. - /// + /// Does effects that take place after hitting such as substitute breaking, rough skin, and victim eating its berry. /// The Pokémon who used . /// The Pokémon who was affected by . /// The move used. @@ -295,9 +292,7 @@ private void DoPostHitEffects(PBEPokemon user, PBEPokemon victim, PBEMove move, // TODO: King's Rock, Stench, etc // TODO?: Cell Battery } - /// - /// Does effects that take place after an attack is completely done such as recoil and life orb. - /// + /// Does effects that take place after an attack is completely done such as recoil and life orb. /// The Pokémon who used the attack. /// Whether should damage the user or not. /// The amount of recoil damage will take. @@ -324,7 +319,7 @@ private void DoPostAttackedEffects(PBEPokemon user, bool ignoreLifeOrb, ushort r } private void DoTurnEndedEffects() { - IEnumerable order = GetActingOrder(ActiveBattlers, true); + PBEPokemon[] order = GetActingOrder(ActiveBattlers, true); // Verified: Weather stops before doing damage if (WeatherCounter > 0) @@ -577,10 +572,9 @@ private void DoTurnEndedEffects() { case PBEAbility.Moody: { - IEnumerable allStats = Enum.GetValues(typeof(PBEStat)).Cast().Except(new[] { PBEStat.HP }); - PBEStat[] statsThatCanGoUp = allStats.Where(s => pkmn.GetStatChange(s) < Settings.MaxStatChange).ToArray(); + PBEStat[] statsThatCanGoUp = _moodyStats.Where(s => pkmn.GetStatChange(s) < Settings.MaxStatChange).ToArray(); PBEStat? upStat = statsThatCanGoUp.Length == 0 ? (PBEStat?)null : statsThatCanGoUp.RandomElement(); - var statsThatCanGoDown = allStats.Where(s => pkmn.GetStatChange(s) > -Settings.MaxStatChange).ToList(); + var statsThatCanGoDown = _moodyStats.Where(s => pkmn.GetStatChange(s) > -Settings.MaxStatChange).ToList(); if (upStat.HasValue) { statsThatCanGoDown.Remove(upStat.Value); @@ -648,19 +642,23 @@ private void DoTurnEndedEffects() } public bool ShouldDoWeatherEffects() { + if (IsDisposed) + { + throw new ObjectDisposedException(null); + } // 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.Any(p => p.Ability == PBEAbility.AirLock || p.Ability == PBEAbility.CloudNine); } - private void UseMove(PBEPokemon user, PBEMove move, PBETarget requestedTargets) + private void UseMove(PBEPokemon user, PBEMove move, PBETurnTarget requestedTargets) { - if (!calledFromOtherMove && PreMoveStatusCheck(user, move)) + if (!_calledFromOtherMove && PreMoveStatusCheck(user, move)) { if (user.Status2.HasFlag(PBEStatus2.Airborne)) { user.TempLockedMove = PBEMove.None; - user.TempLockedTargets = PBETarget.None; + user.TempLockedTargets = PBETurnTarget.None; BroadcastMoveLock(user, user.TempLockedMove, user.TempLockedTargets, PBEMoveLockType.Temporary); user.Status2 &= ~PBEStatus2.Airborne; BroadcastStatus2(user, user, PBEStatus2.Airborne, PBEStatusAction.Ended); @@ -668,7 +666,7 @@ private void UseMove(PBEPokemon user, PBEMove move, PBETarget requestedTargets) if (user.Status2.HasFlag(PBEStatus2.Underground)) { user.TempLockedMove = PBEMove.None; - user.TempLockedTargets = PBETarget.None; + user.TempLockedTargets = PBETurnTarget.None; BroadcastMoveLock(user, user.TempLockedMove, user.TempLockedTargets, PBEMoveLockType.Temporary); user.Status2 &= ~PBEStatus2.Underground; BroadcastStatus2(user, user, PBEStatus2.Underground, PBEStatusAction.Ended); @@ -676,7 +674,7 @@ private void UseMove(PBEPokemon user, PBEMove move, PBETarget requestedTargets) if (user.Status2.HasFlag(PBEStatus2.Underwater)) { user.TempLockedMove = PBEMove.None; - user.TempLockedTargets = PBETarget.None; + user.TempLockedTargets = PBETurnTarget.None; BroadcastMoveLock(user, user.TempLockedMove, user.TempLockedTargets, PBEMoveLockType.Temporary); user.Status2 &= ~PBEStatus2.Underwater; BroadcastStatus2(user, user, PBEStatus2.Underwater, PBEStatusAction.Ended); @@ -1504,14 +1502,14 @@ private void TrySetLoser(PBEPokemon pkmn) { if (Winner == null && pkmn.Team.NumPkmnAlive == 0) { - Winner = pkmn.Team == Teams[0] ? Teams[1] : Teams[0]; + Winner = pkmn.Team.OpposingTeam; } } private bool FaintCheck(PBEPokemon pkmn) { if (pkmn.HP == 0) { - turnOrder.Remove(pkmn); + _turnOrder.Remove(pkmn); ActiveBattlers.Remove(pkmn); PBEFieldPosition oldPos = pkmn.FieldPosition; pkmn.FieldPosition = PBEFieldPosition.None; @@ -1732,7 +1730,14 @@ private void HealingBerryCheck(PBEPokemon pkmn) private bool HasUsedMoveThisTurn(PBEPokemon pkmn) { - return pkmn.ExecutedMoves.Any(e => e.TurnNumber == TurnNumber); + for (int i = 0; i < pkmn.ExecutedMoves.Count; i++) + { + if (pkmn.ExecutedMoves[i].TurnNumber == TurnNumber) + { + return true; + } + } + return false; } private void RecordExecutedMove(PBEPokemon user, PBEMove move, PBEFailReason failReason, IList targets) { @@ -1741,19 +1746,22 @@ private void RecordExecutedMove(PBEPokemon user, PBEMove move, PBEFailReason fai if ((user.Item == PBEItem.ChoiceBand || user.Item == PBEItem.ChoiceScarf || user.Item == PBEItem.ChoiceSpecs) && user.Moves.Contains(move)) { user.ChoiceLockedMove = move; - BroadcastMoveLock(user, move, PBETarget.None, PBEMoveLockType.ChoiceItem); + BroadcastMoveLock(user, move, PBETurnTarget.None, PBEMoveLockType.ChoiceItem); } } private void PPReduce(PBEPokemon pkmn, PBEMove move) { - if (!calledFromOtherMove) + if (!_calledFromOtherMove) { - int moveIndex = Array.IndexOf(pkmn.Moves, move); - int amtToReduce = 1; + const int amountToReduce = 1; // TODO: If target is not self and has pressure - byte oldPP = pkmn.PP[moveIndex]; - pkmn.PP[moveIndex] = (byte)Math.Max(0, pkmn.PP[moveIndex] - amtToReduce); - BroadcastMovePPChanged(pkmn, move, oldPP, pkmn.PP[moveIndex]); + 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); } } @@ -2155,7 +2163,7 @@ private PBEPkmnSwitchInPacket.PBESwitchInInfo CreateSwitchInInfo(PBEPokemon pkmn private void SwitchTwoPokemon(PBEPokemon pkmnLeaving, PBEPokemon pkmnComing, bool forced) { PBEFieldPosition pos = pkmnLeaving.FieldPosition; - turnOrder.Remove(pkmnLeaving); + _turnOrder.Remove(pkmnLeaving); ActiveBattlers.Remove(pkmnLeaving); PBEPokemon disguisedAsPokemon = pkmnLeaving.Status2.HasFlag(PBEStatus2.Disguised) ? pkmnLeaving.DisguisedAsPokemon : pkmnLeaving; pkmnLeaving.ClearForSwitch(); @@ -2242,7 +2250,7 @@ private void BasicHit(PBEPokemon user, PBEPokemon[] targets, PBEMove move, ref L BroadcastEffectiveness(target, moveEffectiveness); if (success.CriticalHit) { - BroadcastMoveCrit(); + BroadcastMoveCrit(target); } hit++; @@ -2459,7 +2467,6 @@ private void Ef_TryForceTeamStatus(PBEPokemon user, PBEMove move, PBETeamStatus BroadcastMoveUsed(user, move); PPReduce(user, move); - PBETeam opposingTeam = user.Team == Teams[0] ? Teams[1] : Teams[0]; switch (status) { case PBETeamStatus.LightScreen: @@ -2509,11 +2516,11 @@ private void Ef_TryForceTeamStatus(PBEPokemon user, PBEMove move, PBETeamStatus } case PBETeamStatus.Spikes: { - if (opposingTeam.SpikeCount < 3) + if (user.Team.OpposingTeam.SpikeCount < 3) { - opposingTeam.TeamStatus |= PBETeamStatus.Spikes; - opposingTeam.SpikeCount++; - BroadcastTeamStatus(opposingTeam, PBETeamStatus.Spikes, PBETeamStatusAction.Added); + user.Team.OpposingTeam.TeamStatus |= PBETeamStatus.Spikes; + user.Team.OpposingTeam.SpikeCount++; + BroadcastTeamStatus(user.Team.OpposingTeam, PBETeamStatus.Spikes, PBETeamStatusAction.Added); failReason = PBEFailReason.None; } else @@ -2524,10 +2531,10 @@ private void Ef_TryForceTeamStatus(PBEPokemon user, PBEMove move, PBETeamStatus } case PBETeamStatus.StealthRock: { - if (!opposingTeam.TeamStatus.HasFlag(PBETeamStatus.StealthRock)) + if (!user.Team.OpposingTeam.TeamStatus.HasFlag(PBETeamStatus.StealthRock)) { - opposingTeam.TeamStatus |= PBETeamStatus.StealthRock; - BroadcastTeamStatus(opposingTeam, PBETeamStatus.StealthRock, PBETeamStatusAction.Added); + user.Team.OpposingTeam.TeamStatus |= PBETeamStatus.StealthRock; + BroadcastTeamStatus(user.Team.OpposingTeam, PBETeamStatus.StealthRock, PBETeamStatusAction.Added); failReason = PBEFailReason.None; } else @@ -2538,11 +2545,11 @@ private void Ef_TryForceTeamStatus(PBEPokemon user, PBEMove move, PBETeamStatus } case PBETeamStatus.ToxicSpikes: { - if (opposingTeam.ToxicSpikeCount < 2) + if (user.Team.OpposingTeam.ToxicSpikeCount < 2) { - opposingTeam.TeamStatus |= PBETeamStatus.ToxicSpikes; - opposingTeam.ToxicSpikeCount++; - BroadcastTeamStatus(opposingTeam, PBETeamStatus.ToxicSpikes, PBETeamStatusAction.Added); + user.Team.OpposingTeam.TeamStatus |= PBETeamStatus.ToxicSpikes; + user.Team.OpposingTeam.ToxicSpikeCount++; + BroadcastTeamStatus(user.Team.OpposingTeam, PBETeamStatus.ToxicSpikes, PBETeamStatusAction.Added); failReason = PBEFailReason.None; } else @@ -2831,7 +2838,7 @@ PBEFailReason BeforeDoingDamage(PBEPokemon target) } RecordExecutedMove(user, move, failReason, targetSuccess); } - private void Ef_Dig(PBEPokemon user, PBEPokemon[] targets, PBEMove move, PBETarget requestedTargets) + private void Ef_Dig(PBEPokemon user, PBEPokemon[] targets, PBEMove move, PBETurnTarget requestedTargets) { var targetSuccess = new List(); PBEFailReason failReason; @@ -2840,7 +2847,7 @@ private void Ef_Dig(PBEPokemon user, PBEPokemon[] targets, PBEMove move, PBETarg if (user.Status2.HasFlag(PBEStatus2.Underground)) { user.TempLockedMove = PBEMove.None; - user.TempLockedTargets = PBETarget.None; + user.TempLockedTargets = PBETurnTarget.None; BroadcastMoveLock(user, user.TempLockedMove, user.TempLockedTargets, PBEMoveLockType.Temporary); user.Status2 &= ~PBEStatus2.Underground; BroadcastStatus2(user, user, PBEStatus2.Underground, PBEStatusAction.Ended); @@ -2874,7 +2881,7 @@ private void Ef_Dig(PBEPokemon user, PBEPokemon[] targets, PBEMove move, PBETarg } RecordExecutedMove(user, move, failReason, targetSuccess); } - private void Ef_Dive(PBEPokemon user, PBEPokemon[] targets, PBEMove move, PBETarget requestedTargets) + private void Ef_Dive(PBEPokemon user, PBEPokemon[] targets, PBEMove move, PBETurnTarget requestedTargets) { var targetSuccess = new List(); PBEFailReason failReason; @@ -2883,7 +2890,7 @@ private void Ef_Dive(PBEPokemon user, PBEPokemon[] targets, PBEMove move, PBETar if (user.Status2.HasFlag(PBEStatus2.Underwater)) { user.TempLockedMove = PBEMove.None; - user.TempLockedTargets = PBETarget.None; + user.TempLockedTargets = PBETurnTarget.None; BroadcastMoveLock(user, user.TempLockedMove, user.TempLockedTargets, PBEMoveLockType.Temporary); user.Status2 &= ~PBEStatus2.Underwater; BroadcastStatus2(user, user, PBEStatus2.Underwater, PBEStatusAction.Ended); @@ -2924,7 +2931,7 @@ private void Ef_Fail(PBEPokemon user, PBEMove move) BroadcastMoveFailed(user, user, PBEFailReason.Default); RecordExecutedMove(user, move, PBEFailReason.Default, Array.Empty()); } - private void Ef_Fly(PBEPokemon user, PBEPokemon[] targets, PBEMove move, PBETarget requestedTargets) + private void Ef_Fly(PBEPokemon user, PBEPokemon[] targets, PBEMove move, PBETurnTarget requestedTargets) { var targetSuccess = new List(); PBEFailReason failReason; @@ -2933,7 +2940,7 @@ private void Ef_Fly(PBEPokemon user, PBEPokemon[] targets, PBEMove move, PBETarg if (user.Status2.HasFlag(PBEStatus2.Airborne)) { user.TempLockedMove = PBEMove.None; - user.TempLockedTargets = PBETarget.None; + user.TempLockedTargets = PBETurnTarget.None; BroadcastMoveLock(user, user.TempLockedMove, user.TempLockedTargets, PBEMoveLockType.Temporary); user.Status2 &= ~PBEStatus2.Airborne; BroadcastStatus2(user, user, PBEStatus2.Airborne, PBEStatusAction.Ended); @@ -3055,8 +3062,8 @@ private void Ef_SuckerPunch(PBEPokemon user, PBEPokemon[] targets, PBEMove move) failReason = PBEFailReason.None; PBEFailReason BeforeDoingDamage(PBEPokemon target) { - if (target.SelectedAction.Decision != PBEDecision.Fight - || PBEMoveData.Data[target.SelectedAction.FightMove].Category == PBEMoveCategory.Status + if (target.TurnAction.Decision != PBETurnDecision.Fight + || PBEMoveData.Data[target.TurnAction.FightMove].Category == PBEMoveCategory.Status || HasUsedMoveThisTurn(target)) { BroadcastMoveFailed(user, target, PBEFailReason.Default); @@ -3596,18 +3603,18 @@ private void Ef_Curse(PBEPokemon user, PBEPokemon[] targets, PBEMove move) if (target == user) // Just gained the Ghost type after selecting the move, so get a random target { PBEFieldPosition prioritizedPos = GetPositionAcross(BattleFormat, user.FieldPosition); - PBETarget moveTarget; + PBETurnTarget moveTarget; if (prioritizedPos == PBEFieldPosition.Left) { - moveTarget = PBETarget.FoeLeft; + moveTarget = PBETurnTarget.FoeLeft; } else if (prioritizedPos == PBEFieldPosition.Center) { - moveTarget = PBETarget.FoeCenter; + moveTarget = PBETurnTarget.FoeCenter; } else { - moveTarget = PBETarget.FoeRight; + moveTarget = PBETurnTarget.FoeRight; } PBEPokemon[] runtimeTargets = GetRuntimeTargets(user, moveTarget, false); @@ -3806,9 +3813,9 @@ private void Ef_Metronome(PBEPokemon user, PBEMove move) RecordExecutedMove(user, move, PBEFailReason.None, Array.Empty()); PBEMove calledMove = PBEMoveData.Data.Where(t => !t.Value.Flags.HasFlag(PBEMoveFlag.BlockedByMetronome)).Select(t => t.Key).ToArray().RandomElement(); - calledFromOtherMove = true; + _calledFromOtherMove = true; UseMove(user, calledMove, GetRandomTargetForMetronome(user, calledMove)); - calledFromOtherMove = false; + _calledFromOtherMove = false; } private void Ef_PsychUp(PBEPokemon user, PBEPokemon[] targets, PBEMove move) { diff --git a/PokemonBattleEngine/Battle/BattleEvents.cs b/PokemonBattleEngine/Battle/BattleEvents.cs index 012721bdc..9c62cdf66 100644 --- a/PokemonBattleEngine/Battle/BattleEvents.cs +++ b/PokemonBattleEngine/Battle/BattleEvents.cs @@ -2,7 +2,6 @@ using Kermalis.PokemonBattleEngine.Data; using Kermalis.PokemonBattleEngine.Packets; using System; -using System.Collections.Generic; using System.Linq; namespace Kermalis.PokemonBattleEngine.Battle @@ -50,9 +49,9 @@ private void BroadcastItem(PBEPokemon itemHolder, PBEPokemon pokemon2, PBEItem i Events.Add(p); OnNewEvent?.Invoke(this, p); } - private void BroadcastMoveCrit() + private void BroadcastMoveCrit(PBEPokemon victim) { - var p = new PBEMoveCritPacket(); + var p = new PBEMoveCritPacket(victim); Events.Add(p); OnNewEvent?.Invoke(this, p); } @@ -68,7 +67,7 @@ private void BroadcastMoveFailed(PBEPokemon moveUser, PBEPokemon pokemon2, PBEFa Events.Add(p); OnNewEvent?.Invoke(this, p); } - private void BroadcastMoveLock(PBEPokemon moveUser, PBEMove lockedMove, PBETarget lockedTargets, PBEMoveLockType moveLockType) + private void BroadcastMoveLock(PBEPokemon moveUser, PBEMove lockedMove, PBETurnTarget lockedTargets, PBEMoveLockType moveLockType) { var p = new PBEMoveLockPacket(moveUser, lockedMove, lockedTargets, moveLockType); Events.Add(p); @@ -80,18 +79,18 @@ private void BroadcastMoveMissed(PBEPokemon moveUser, PBEPokemon pokemon2) Events.Add(p); OnNewEvent?.Invoke(this, p); } - private void BroadcastMovePPChanged(PBEPokemon moveUser, PBEMove move, byte oldValue, byte newValue) + private void BroadcastMovePPChanged(PBEPokemon moveUser, PBEMove move, int amountReduced) { - var p = new PBEMovePPChangedPacket(moveUser.FieldPosition, moveUser.Team, move, oldValue, newValue); + var p = new PBEMovePPChangedPacket(moveUser.FieldPosition, moveUser.Team, move, amountReduced); Events.Add(p); OnNewEvent?.Invoke(this, p); } private void BroadcastMoveUsed(PBEPokemon moveUser, PBEMove move) { bool reveal; - if (!calledFromOtherMove && moveUser.Moves.Contains(move) && !moveUser.KnownMoves.Contains(move)) + if (!_calledFromOtherMove && moveUser.Moves.Contains(move) && !moveUser.KnownMoves.Contains(move)) { - moveUser.KnownMoves[Array.IndexOf(moveUser.KnownMoves, PBEMove.MAX)] = move; + moveUser.KnownMoves[PBEMove.MAX].Move = move; reveal = true; } else @@ -136,7 +135,7 @@ private void BroadcastPkmnStatChanged(PBEPokemon pokemon, PBEStat stat, sbyte ol Events.Add(p); OnNewEvent?.Invoke(this, p); } - private void BroadcastPkmnSwitchIn(PBETeam team, IEnumerable switchIns, bool forced) + private void BroadcastPkmnSwitchIn(PBETeam team, PBEPkmnSwitchInPacket.PBESwitchInInfo[] switchIns, bool forced) { var p = new PBEPkmnSwitchInPacket(team, switchIns, forced); Events.Add(p); @@ -246,6 +245,12 @@ private void BroadcastAutoCenter(byte pokemon1Id, PBEFieldPosition pokemon1Posit Events.Add(p); OnNewEvent?.Invoke(this, p); } + private void BroadcastTeam(PBETeam team) + { + var p = new PBETeamPacket(team); + Events.Add(p); + OnNewEvent?.Invoke(this, p); + } private void BroadcastSwitchInRequest(PBETeam team) { var p = new PBESwitchInRequestPacket(team); @@ -266,13 +271,25 @@ private void BroadcastWinner(PBETeam winningTeam) } - /// - /// Writes battle events to in English. - /// + /// 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, INetPacket packet) { + if (battle == null) + { + throw new ArgumentNullException(nameof(battle)); + } + if (packet == null) + { + throw new ArgumentNullException(nameof(packet)); + } + if (battle.IsDisposed) + { + throw new ObjectDisposedException(nameof(battle)); + } + string NameForTrainer(PBEPokemon pkmn) { return pkmn == null ? string.Empty : $"{pkmn.Team.TrainerName}'s {pkmn.KnownNickname}"; @@ -590,9 +607,10 @@ string NameForTrainer(PBEPokemon pkmn) Console.WriteLine(message, NameForTrainer(itemHolder), NameForTrainer(pokemon2), PBELocalizedString.GetItemName(ip.Item).English); break; } - case PBEMoveCritPacket _: + case PBEMoveCritPacket mcp: { - Console.WriteLine("A critical hit!"); + PBEPokemon victim = mcp.VictimTeam.TryGetPokemon(mcp.Victim); + Console.WriteLine("A critical hit on {0}!", NameForTrainer(victim)); break; } case PBEMoveEffectivenessPacket mep: @@ -602,12 +620,15 @@ string NameForTrainer(PBEPokemon pkmn) switch (mep.Effectiveness) { case PBEEffectiveness.Ineffective: message = "It doesn't affect {0}..."; break; - case PBEEffectiveness.NotVeryEffective: message = "It's not very effective..."; break; - case PBEEffectiveness.Normal: message = "It's normally effective."; break; - case PBEEffectiveness.SuperEffective: message = "It's super effective!"; break; + case PBEEffectiveness.NotVeryEffective: message = "It's not very effective on {0}..."; break; + case PBEEffectiveness.Normal: message = null; break; + case PBEEffectiveness.SuperEffective: message = "It's super effective on {0}!"; break; default: throw new ArgumentOutOfRangeException(nameof(mep.Effectiveness)); } - Console.WriteLine(message, NameForTrainer(victim)); + if (message != null) + { + Console.WriteLine(message, NameForTrainer(victim)); + } break; } case PBEMoveFailedPacket mfp: @@ -642,8 +663,7 @@ string NameForTrainer(PBEPokemon pkmn) case PBEMovePPChangedPacket mpcp: { PBEPokemon moveUser = mpcp.MoveUserTeam.TryGetPokemon(mpcp.MoveUser); - int change = mpcp.NewValue - mpcp.OldValue; - Console.WriteLine("{0}'s {1} {3} {2} PP!", NameForTrainer(moveUser), PBELocalizedString.GetMoveName(mpcp.Move).English, Math.Abs(change), change <= 0 ? "lost" : "gained"); + Console.WriteLine("{0}'s {1} {3} {2} PP!", NameForTrainer(moveUser), PBELocalizedString.GetMoveName(mpcp.Move).English, Math.Abs(mpcp.AmountReduced), mpcp.AmountReduced >= 0 ? "lost" : "gained"); break; } case PBEMoveUsedPacket mup: @@ -743,7 +763,7 @@ string NameForTrainer(PBEPokemon pkmn) { if (!psip.Forced) { - Console.WriteLine("{1} sent out {0}!", psip.SwitchIns.Select(s => s.Nickname).Andify(), psip.Team.TrainerName); + Console.WriteLine("{1} sent out {0}!", psip.SwitchIns.Select(s => s.Nickname).ToArray().Andify(), psip.Team.TrainerName); } break; } @@ -1196,12 +1216,12 @@ string NameForTrainer(PBEPokemon pkmn) } case PBETurnBeganPacket tbp: { - Console.WriteLine("Turn {0} is starting. ({1})", tbp.TurnNumber, tbp.Time); + Console.WriteLine("Turn {0} is starting.", tbp.TurnNumber); break; } case PBEWinnerPacket win: { - Console.WriteLine("{0} defeated {1}!", win.WinningTeam.TrainerName, (win.WinningTeam == battle.Teams[0] ? battle.Teams[1] : battle.Teams[0]).TrainerName); + Console.WriteLine("{0} defeated {1}!", win.WinningTeam.TrainerName, win.WinningTeam.OpposingTeam.TrainerName); break; } } diff --git a/PokemonBattleEngine/Battle/BattleMoveset.cs b/PokemonBattleEngine/Battle/BattleMoveset.cs new file mode 100644 index 000000000..8d3791b12 --- /dev/null +++ b/PokemonBattleEngine/Battle/BattleMoveset.cs @@ -0,0 +1,282 @@ +using Kermalis.PokemonBattleEngine.Data; +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) + { + _list = new PBEBattleMovesetSlot[settings.NumMoves]; + for (int i = 0; i < _list.Length; i++) + { + _list[i] = new PBEBattleMovesetSlot(); + } + } + internal PBEBattleMoveset(PBEMoveset moveset) + { + _list = new PBEBattleMovesetSlot[moveset.Settings.NumMoves]; + for (int i = 0; i < _list.Length; i++) + { + PBEMoveset.PBEMovesetSlot slot = moveset[i]; + PBEMove move = slot.Move; + int pp; + if (move != PBEMove.None) + { + byte tier = PBEMoveData.Data[move].PPTier; + pp = Math.Max(1, (tier * moveset.Settings.PPMultiplier) + (tier * slot.PPUps)); + } + else + { + pp = 0; + } + _list[i] = new PBEBattleMovesetSlot(move, pp, pp); + } + } + internal PBEBattleMoveset(PBEBattleMoveset other) + { + _list = new PBEBattleMovesetSlot[other._list.Length]; + for (int i = 0; i < _list.Length; i++) + { + PBEBattleMovesetSlot oSlot = other._list[i]; + _list[i] = new PBEBattleMovesetSlot(oSlot.Move, oSlot.PP, oSlot.MaxPP); + } + } + + public static int GetTransformPP(PBESettings settings, PBEMove move) + { + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + else if (move >= PBEMove.MAX || !Enum.IsDefined(typeof(PBEMove), move)) + { + throw new ArgumentOutOfRangeException(nameof(move)); + } + else if (move == PBEMove.None) + { + return 0; + } + else + { + return PBEMoveData.Data[move].PPTier == 0 ? 1 : settings.PPMultiplier; + } + } + public static int GetNonTransformPP(PBESettings settings, PBEMove move, byte ppUps) + { + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + else if (move >= PBEMove.MAX || !Enum.IsDefined(typeof(PBEMove), move)) + { + throw new ArgumentOutOfRangeException(nameof(move)); + } + else if (move == PBEMove.None) + { + return 0; + } + else + { + byte tier = PBEMoveData.Data[move].PPTier; + return Math.Max(1, (tier * settings.PPMultiplier) + (tier * ppUps)); + } + } + + internal static void DoTransform(PBEPokemon user, PBEPokemon target) + { + PBEBattleMoveset targetKnownBackup = null; + if (user.Team != target.Team) + { + targetKnownBackup = new PBEBattleMoveset(target.KnownMoves); + } + PBESettings settings = user.Team.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.Team == target.Team) + { + 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; + PBEBattleMovesetSlot bSlot = targetKnownBackup[move]; + if (bSlot == null) + { + 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); + } + 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] != null; + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < _list.Length; i++) + { + yield return _list[i]; + } + } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/PokemonBattleEngine/Battle/BattleReplay.cs b/PokemonBattleEngine/Battle/BattleReplay.cs index d2798f876..bb3726026 100644 --- a/PokemonBattleEngine/Battle/BattleReplay.cs +++ b/PokemonBattleEngine/Battle/BattleReplay.cs @@ -15,11 +15,23 @@ public sealed partial class PBEBattle public void SaveReplay() { + if (IsDisposed) + { + throw new ObjectDisposedException(null); + } // "12-30-2020 11-59-59 PM - Team 1 vs Team 2.pbereplay" SaveReplay(PBEUtils.ToSafeFileName(new string(string.Format("{0} - {1} vs {2}", DateTime.Now.ToLocalTime(), Teams[0].TrainerName, Teams[1].TrainerName).Take(200).ToArray())) + ".pbereplay"); } public void SaveReplay(string path) { + if (path == null) + { + throw new ArgumentNullException(nameof(path)); + } + if (IsDisposed) + { + throw new ObjectDisposedException(null); + } if (BattleState != PBEBattleState.Ended) { throw new InvalidOperationException($"{nameof(BattleState)} must be {PBEBattleState.Ended} to save a replay."); @@ -28,12 +40,9 @@ public void SaveReplay(string path) var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(CurrentReplayVersion)); - bytes.AddRange(Settings.ToBytes()); + Settings.ToBytes(bytes); bytes.Add((byte)BattleFormat); - bytes.AddRange(Teams[0].ToBytes()); - bytes.AddRange(Teams[1].ToBytes()); - bytes.AddRange(BitConverter.GetBytes(Events.Count)); for (int i = 0; i < Events.Count; i++) { @@ -76,20 +85,20 @@ private PBEBattle(BinaryReader r) Settings = new PBESettings(r); Settings.MakeReadOnly(); BattleFormat = (PBEBattleFormat)r.ReadByte(); - - Teams[0] = new PBETeam(this, 0); - Teams[0].FromBytes(r); - Teams[1] = new PBETeam(this, 1); - Teams[1].FromBytes(r); + Teams = new PBETeams(this); var packetProcessor = new PBEPacketProcessor(this); int numEvents = r.ReadInt32(); for (int i = 0; i < numEvents; i++) { INetPacket packet = packetProcessor.CreatePacket(r.ReadBytes(r.ReadInt16())); - if (packet is PBEWinnerPacket wp) + switch (packet) { - Winner = wp.WinningTeam; + case PBEWinnerPacket wp: + { + Winner = wp.WinningTeam; + break; + } } Events.Add(packet); } diff --git a/PokemonBattleEngine/Battle/BattleTargets.cs b/PokemonBattleEngine/Battle/BattleTargets.cs index fc0512034..cf56303fc 100644 --- a/PokemonBattleEngine/Battle/BattleTargets.cs +++ b/PokemonBattleEngine/Battle/BattleTargets.cs @@ -7,9 +7,7 @@ namespace Kermalis.PokemonBattleEngine.Battle { public sealed partial class PBEBattle { - /// - /// Gets the position across from the inputted position for a specific battle format. - /// + /// 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 . @@ -63,21 +61,27 @@ public static PBEFieldPosition GetPositionAcross(PBEBattleFormat battleFormat, P throw new ArgumentOutOfRangeException(nameof(position)); } } - default: throw new ArgumentOutOfRangeException(nameof(BattleFormat)); + default: throw new ArgumentOutOfRangeException(nameof(battleFormat)); } } - /// - /// Gets the Pokémon surrounding . - /// + /// 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 IEnumerable GetRuntimeSurrounding(PBEPokemon pkmn, bool includeAllies, bool includeFoes) + public static List GetRuntimeSurrounding(PBEPokemon pkmn, bool includeAllies, bool includeFoes) { + if (pkmn == null) + { + throw new ArgumentNullException(nameof(pkmn)); + } + if (!includeAllies && !includeFoes) + { + throw new ArgumentException($"\"{nameof(includeAllies)}\" and \"{nameof(includeFoes)}\" were false."); + } PBEPokemon[] allies = pkmn.Team.ActiveBattlers.Where(p => p != pkmn).ToArray(); - PBEPokemon[] foes = (pkmn.Team == pkmn.Team.Battle.Teams[0] ? pkmn.Team.Battle.Teams[1] : pkmn.Team.Battle.Teams[0]).ActiveBattlers; + PBEPokemon[] foes = pkmn.Team.OpposingTeam.ActiveBattlers; switch (pkmn.Team.Battle.BattleFormat) { case PBEBattleFormat.Single: @@ -180,53 +184,49 @@ public static IEnumerable GetRuntimeSurrounding(PBEPokemon pkmn, boo } } - /// - /// Gets all Pokémon that will be hit. - /// + /// 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. - private static PBEPokemon[] GetRuntimeTargets(PBEPokemon user, PBETarget requestedTargets, bool canHitFarCorners) + private static PBEPokemon[] GetRuntimeTargets(PBEPokemon user, PBETurnTarget requestedTargets, bool canHitFarCorners) { - PBETeam opposingTeam = user.Team == user.Team.Battle.Teams[0] ? user.Team.Battle.Teams[1] : user.Team.Battle.Teams[0]; - var targets = new List(); - if (requestedTargets.HasFlag(PBETarget.AllyLeft)) + if (requestedTargets.HasFlag(PBETurnTarget.AllyLeft)) { targets.Add(user.Team.TryGetPokemon(PBEFieldPosition.Left)); } - if (requestedTargets.HasFlag(PBETarget.AllyCenter)) + if (requestedTargets.HasFlag(PBETurnTarget.AllyCenter)) { targets.Add(user.Team.TryGetPokemon(PBEFieldPosition.Center)); } - if (requestedTargets.HasFlag(PBETarget.AllyRight)) + if (requestedTargets.HasFlag(PBETurnTarget.AllyRight)) { targets.Add(user.Team.TryGetPokemon(PBEFieldPosition.Right)); } - if (requestedTargets.HasFlag(PBETarget.FoeLeft)) + if (requestedTargets.HasFlag(PBETurnTarget.FoeLeft)) { - PBEPokemon pkmn = opposingTeam.TryGetPokemon(PBEFieldPosition.Left); + PBEPokemon pkmn = user.Team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Left); if (pkmn == null) { if (user.Team.Battle.BattleFormat == PBEBattleFormat.Double) { - pkmn = opposingTeam.TryGetPokemon(PBEFieldPosition.Right); + pkmn = user.Team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Right); } else if (user.Team.Battle.BattleFormat == PBEBattleFormat.Triple) { - pkmn = opposingTeam.TryGetPokemon(PBEFieldPosition.Center); + pkmn = user.Team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Center); // Center fainted as well and user can reach far right if (pkmn == null && (user.FieldPosition != PBEFieldPosition.Right || canHitFarCorners)) { - pkmn = opposingTeam.TryGetPokemon(PBEFieldPosition.Right); + pkmn = user.Team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Right); } } } targets.Add(pkmn); } - if (requestedTargets.HasFlag(PBETarget.FoeCenter)) + if (requestedTargets.HasFlag(PBETurnTarget.FoeCenter)) { - PBEPokemon pkmn = opposingTeam.TryGetPokemon(PBEFieldPosition.Center); + PBEPokemon pkmn = user.Team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Center); // Target fainted, fallback to its teammate if (pkmn == null) { @@ -234,26 +234,26 @@ private static PBEPokemon[] GetRuntimeTargets(PBEPokemon user, PBETarget request { if (user.FieldPosition == PBEFieldPosition.Left) { - pkmn = opposingTeam.TryGetPokemon(PBEFieldPosition.Right); + pkmn = user.Team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Right); // Right fainted as well and user can reach far left if (pkmn == null && (user.FieldPosition != PBEFieldPosition.Left || canHitFarCorners)) { - pkmn = opposingTeam.TryGetPokemon(PBEFieldPosition.Left); + pkmn = user.Team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Left); } } else if (user.FieldPosition == PBEFieldPosition.Right) { - pkmn = opposingTeam.TryGetPokemon(PBEFieldPosition.Left); + pkmn = user.Team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Left); // Left fainted as well and user can reach far right if (pkmn == null && (user.FieldPosition != PBEFieldPosition.Right || canHitFarCorners)) { - pkmn = opposingTeam.TryGetPokemon(PBEFieldPosition.Right); + pkmn = user.Team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Right); } } else // Center { - PBEPokemon oppLeft = opposingTeam.TryGetPokemon(PBEFieldPosition.Left), - oppRight = opposingTeam.TryGetPokemon(PBEFieldPosition.Right); + PBEPokemon oppLeft = user.Team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Left), + oppRight = user.Team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Right); // Left is dead but not right if (oppLeft == null && oppRight != null) { @@ -274,23 +274,23 @@ private static PBEPokemon[] GetRuntimeTargets(PBEPokemon user, PBETarget request } targets.Add(pkmn); } - if (requestedTargets.HasFlag(PBETarget.FoeRight)) + if (requestedTargets.HasFlag(PBETurnTarget.FoeRight)) { - PBEPokemon pkmn = opposingTeam.TryGetPokemon(PBEFieldPosition.Right); + PBEPokemon pkmn = user.Team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Right); // Target fainted, fallback to its teammate if (pkmn == null) { if (user.Team.Battle.BattleFormat == PBEBattleFormat.Double) { - pkmn = opposingTeam.TryGetPokemon(PBEFieldPosition.Left); + pkmn = user.Team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Left); } else if (user.Team.Battle.BattleFormat == PBEBattleFormat.Triple) { - pkmn = opposingTeam.TryGetPokemon(PBEFieldPosition.Center); + pkmn = user.Team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Center); // Center fainted as well and user can reach far left if (pkmn == null && (user.FieldPosition != PBEFieldPosition.Left || canHitFarCorners)) { - pkmn = opposingTeam.TryGetPokemon(PBEFieldPosition.Left); + pkmn = user.Team.OpposingTeam.TryGetPokemon(PBEFieldPosition.Left); } } } @@ -299,16 +299,18 @@ private static PBEPokemon[] GetRuntimeTargets(PBEPokemon user, PBETarget request return targets.Where(p => p != null).Distinct().ToArray(); // Remove duplicate targets } - /// - /// Determines whether chosen targets are valid for a given move. - /// + /// 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(PBEPokemon pkmn, PBEMove move, PBETarget targets) + public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETurnTarget targets) { - if (move == PBEMove.None || move >= PBEMove.MAX) + if (pkmn == null) + { + throw new ArgumentNullException(nameof(pkmn)); + } + if (move == PBEMove.None || move >= PBEMove.MAX || !Enum.IsDefined(typeof(PBEMove), move)) { throw new ArgumentOutOfRangeException(nameof(move)); } @@ -323,7 +325,7 @@ public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETarget targ { if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return targets == (PBETarget.AllyCenter | PBETarget.FoeCenter); + return targets == (PBETurnTarget.AllyCenter | PBETurnTarget.FoeCenter); } else { @@ -339,7 +341,7 @@ public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETarget targ { if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return targets == PBETarget.FoeCenter; + return targets == PBETurnTarget.FoeCenter; } else { @@ -354,7 +356,7 @@ public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETarget targ { if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return targets == PBETarget.AllyCenter; + return targets == PBETurnTarget.AllyCenter; } else { @@ -372,7 +374,7 @@ public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETarget targ { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { - return targets == (PBETarget.AllyLeft | PBETarget.AllyRight | PBETarget.FoeLeft | PBETarget.FoeRight); + return targets == (PBETurnTarget.AllyLeft | PBETurnTarget.AllyRight | PBETurnTarget.FoeLeft | PBETurnTarget.FoeRight); } else { @@ -384,7 +386,7 @@ public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETarget targ { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { - return targets == (PBETarget.FoeLeft | PBETarget.FoeRight); + return targets == (PBETurnTarget.FoeLeft | PBETurnTarget.FoeRight); } else { @@ -395,7 +397,7 @@ public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETarget targ { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { - return targets == (PBETarget.AllyLeft | PBETarget.AllyRight); + return targets == (PBETurnTarget.AllyLeft | PBETurnTarget.AllyRight); } else { @@ -406,11 +408,11 @@ public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETarget targ { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return targets == (PBETarget.AllyRight | PBETarget.FoeLeft | PBETarget.FoeRight); + return targets == (PBETurnTarget.AllyRight | PBETurnTarget.FoeLeft | PBETurnTarget.FoeRight); } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return targets == (PBETarget.AllyLeft | PBETarget.FoeLeft | PBETarget.FoeRight); + return targets == (PBETurnTarget.AllyLeft | PBETurnTarget.FoeLeft | PBETurnTarget.FoeRight); } else { @@ -422,11 +424,11 @@ public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETarget targ { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return targets == PBETarget.AllyLeft; + return targets == PBETurnTarget.AllyLeft; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return targets == PBETarget.AllyRight; + return targets == PBETurnTarget.AllyRight; } else { @@ -437,7 +439,7 @@ public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETarget targ { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { - return targets == PBETarget.AllyLeft || targets == PBETarget.AllyRight; + return targets == PBETurnTarget.AllyLeft || targets == PBETurnTarget.AllyRight; } else { @@ -448,11 +450,11 @@ public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETarget targ { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return targets == PBETarget.AllyRight; + return targets == PBETurnTarget.AllyRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return targets == PBETarget.AllyLeft; + return targets == PBETurnTarget.AllyLeft; } else { @@ -463,7 +465,7 @@ public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETarget targ { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { - return targets == PBETarget.FoeLeft || targets == PBETarget.FoeRight; + return targets == PBETurnTarget.FoeLeft || targets == PBETurnTarget.FoeRight; } else { @@ -475,11 +477,11 @@ public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETarget targ { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return targets == PBETarget.AllyRight || targets == PBETarget.FoeLeft || targets == PBETarget.FoeRight; + return targets == PBETurnTarget.AllyRight || targets == PBETurnTarget.FoeLeft || targets == PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return targets == PBETarget.AllyLeft || targets == PBETarget.FoeLeft || targets == PBETarget.FoeRight; + return targets == PBETurnTarget.AllyLeft || targets == PBETurnTarget.FoeLeft || targets == PBETurnTarget.FoeRight; } else { @@ -497,7 +499,7 @@ public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETarget targ { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { - return targets == (PBETarget.AllyLeft | PBETarget.AllyCenter | PBETarget.AllyRight | PBETarget.FoeLeft | PBETarget.FoeCenter | PBETarget.FoeRight); + return targets == (PBETurnTarget.AllyLeft | PBETurnTarget.AllyCenter | PBETurnTarget.AllyRight | PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight); } else { @@ -508,7 +510,7 @@ public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETarget targ { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { - return targets == (PBETarget.FoeLeft | PBETarget.FoeCenter | PBETarget.FoeRight); + return targets == (PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight); } else { @@ -519,15 +521,15 @@ public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETarget targ { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return targets == (PBETarget.FoeCenter | PBETarget.FoeRight); + return targets == (PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight); } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return targets == (PBETarget.FoeLeft | PBETarget.FoeCenter | PBETarget.FoeRight); + return targets == (PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight); } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return targets == (PBETarget.FoeLeft | PBETarget.FoeCenter); + return targets == (PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter); } else { @@ -538,15 +540,15 @@ public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETarget targ { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return targets == (PBETarget.AllyCenter | PBETarget.FoeCenter | PBETarget.FoeRight); + return targets == (PBETurnTarget.AllyCenter | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight); } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return targets == (PBETarget.AllyLeft | PBETarget.AllyRight | PBETarget.FoeLeft | PBETarget.FoeCenter | PBETarget.FoeRight); + return targets == (PBETurnTarget.AllyLeft | PBETurnTarget.AllyRight | PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight); } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return targets == (PBETarget.AllyCenter | PBETarget.FoeLeft | PBETarget.FoeCenter); + return targets == (PBETurnTarget.AllyCenter | PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter); } else { @@ -557,7 +559,7 @@ public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETarget targ { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { - return targets == (PBETarget.AllyLeft | PBETarget.AllyCenter | PBETarget.AllyRight); + return targets == (PBETurnTarget.AllyLeft | PBETurnTarget.AllyCenter | PBETurnTarget.AllyRight); } else { @@ -569,15 +571,15 @@ public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETarget targ { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return targets == PBETarget.AllyLeft; + return targets == PBETurnTarget.AllyLeft; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return targets == PBETarget.AllyCenter; + return targets == PBETurnTarget.AllyCenter; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return targets == PBETarget.AllyRight; + return targets == PBETurnTarget.AllyRight; } else { @@ -588,15 +590,15 @@ public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETarget targ { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return targets == PBETarget.AllyLeft || targets == PBETarget.AllyCenter; + return targets == PBETurnTarget.AllyLeft || targets == PBETurnTarget.AllyCenter; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return targets == PBETarget.AllyLeft || targets == PBETarget.AllyCenter || targets == PBETarget.AllyRight; + return targets == PBETurnTarget.AllyLeft || targets == PBETurnTarget.AllyCenter || targets == PBETurnTarget.AllyRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return targets == PBETarget.AllyCenter || targets == PBETarget.AllyRight; + return targets == PBETurnTarget.AllyCenter || targets == PBETurnTarget.AllyRight; } else { @@ -607,11 +609,11 @@ public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETarget targ { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { - return targets == PBETarget.AllyCenter; + return targets == PBETurnTarget.AllyCenter; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return targets == PBETarget.AllyLeft || targets == PBETarget.AllyRight; + return targets == PBETurnTarget.AllyLeft || targets == PBETurnTarget.AllyRight; } else { @@ -622,15 +624,15 @@ public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETarget targ { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return targets == PBETarget.FoeCenter || targets == PBETarget.FoeRight; + return targets == PBETurnTarget.FoeCenter || targets == PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return targets == PBETarget.FoeLeft || targets == PBETarget.FoeCenter || targets == PBETarget.FoeRight; + return targets == PBETurnTarget.FoeLeft || targets == PBETurnTarget.FoeCenter || targets == PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return targets == PBETarget.FoeLeft || targets == PBETarget.FoeCenter; + return targets == PBETurnTarget.FoeLeft || targets == PBETurnTarget.FoeCenter; } else { @@ -641,15 +643,15 @@ public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETarget targ { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return targets == PBETarget.AllyCenter || targets == PBETarget.AllyRight || targets == PBETarget.FoeLeft || targets == PBETarget.FoeCenter || targets == PBETarget.FoeRight; + return targets == PBETurnTarget.AllyCenter || targets == PBETurnTarget.AllyRight || targets == PBETurnTarget.FoeLeft || targets == PBETurnTarget.FoeCenter || targets == PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return targets == PBETarget.AllyLeft || targets == PBETarget.AllyRight || targets == PBETarget.FoeLeft || targets == PBETarget.FoeCenter || targets == PBETarget.FoeRight; + return targets == PBETurnTarget.AllyLeft || targets == PBETurnTarget.AllyRight || targets == PBETurnTarget.FoeLeft || targets == PBETurnTarget.FoeCenter || targets == PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return targets == PBETarget.AllyLeft || targets == PBETarget.AllyCenter || targets == PBETarget.FoeLeft || targets == PBETarget.FoeCenter || targets == PBETarget.FoeRight; + return targets == PBETurnTarget.AllyLeft || targets == PBETurnTarget.AllyCenter || targets == PBETurnTarget.FoeLeft || targets == PBETurnTarget.FoeCenter || targets == PBETurnTarget.FoeRight; } else { @@ -660,15 +662,15 @@ public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETarget targ { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return targets == PBETarget.AllyCenter || targets == PBETarget.FoeCenter || targets == PBETarget.FoeRight; + return targets == PBETurnTarget.AllyCenter || targets == PBETurnTarget.FoeCenter || targets == PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return targets == PBETarget.AllyLeft || targets == PBETarget.AllyRight || targets == PBETarget.FoeLeft || targets == PBETarget.FoeCenter || targets == PBETarget.FoeRight; + return targets == PBETurnTarget.AllyLeft || targets == PBETurnTarget.AllyRight || targets == PBETurnTarget.FoeLeft || targets == PBETurnTarget.FoeCenter || targets == PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return targets == PBETarget.AllyCenter || targets == PBETarget.FoeLeft || targets == PBETarget.FoeCenter; + return targets == PBETurnTarget.AllyCenter || targets == PBETurnTarget.FoeLeft || targets == PBETurnTarget.FoeCenter; } else { @@ -686,7 +688,7 @@ public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETarget targ { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { - return targets == (PBETarget.AllyCenter | PBETarget.FoeCenter); + return targets == (PBETurnTarget.AllyCenter | PBETurnTarget.FoeCenter); } else { @@ -702,7 +704,7 @@ public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETarget targ { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { - return targets == PBETarget.FoeCenter; + return targets == PBETurnTarget.FoeCenter; } else { @@ -717,7 +719,7 @@ public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETarget targ { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { - return targets == PBETarget.AllyCenter; + return targets == PBETurnTarget.AllyCenter; } else { @@ -731,14 +733,20 @@ public static bool AreTargetsValid(PBEPokemon pkmn, PBEMove move, PBETarget targ } } - /// - /// Gets a random target a move can hit when called by . - /// + /// Gets a random target a move can hit when called by . /// The Pokémon using . /// The move being called. /// Thrown when , 's , or 's 's is invalid. - public static PBETarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove calledMove) + public static PBETurnTarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove calledMove) { + if (pkmn == null) + { + throw new ArgumentNullException(nameof(pkmn)); + } + if (calledMove == PBEMove.None || calledMove >= PBEMove.MAX || !Enum.IsDefined(typeof(PBEMove), calledMove)) + { + throw new ArgumentOutOfRangeException(nameof(calledMove)); + } PBEMoveTarget possibleTargets = pkmn.GetMoveTargets(calledMove); switch (pkmn.Team.Battle.BattleFormat) { @@ -750,7 +758,7 @@ public static PBETarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove cal { if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return PBETarget.AllyCenter | PBETarget.FoeCenter; + return PBETurnTarget.AllyCenter | PBETurnTarget.FoeCenter; } else { @@ -767,7 +775,7 @@ public static PBETarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove cal { if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return PBETarget.FoeCenter; + return PBETurnTarget.FoeCenter; } else { @@ -781,7 +789,7 @@ public static PBETarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove cal { if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return PBETarget.AllyCenter; + return PBETurnTarget.AllyCenter; } else { @@ -799,7 +807,7 @@ public static PBETarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove cal { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.AllyLeft | PBETarget.AllyRight | PBETarget.FoeLeft | PBETarget.FoeRight; + return PBETurnTarget.AllyLeft | PBETurnTarget.AllyRight | PBETurnTarget.FoeLeft | PBETurnTarget.FoeRight; } else { @@ -811,7 +819,7 @@ public static PBETarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove cal { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.FoeLeft | PBETarget.FoeRight; + return PBETurnTarget.FoeLeft | PBETurnTarget.FoeRight; } else { @@ -822,7 +830,7 @@ public static PBETarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove cal { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.AllyLeft | PBETarget.AllyRight; + return PBETurnTarget.AllyLeft | PBETurnTarget.AllyRight; } else { @@ -833,11 +841,11 @@ public static PBETarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove cal { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return PBETarget.AllyRight | PBETarget.FoeLeft | PBETarget.FoeRight; + return PBETurnTarget.AllyRight | PBETurnTarget.FoeLeft | PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.AllyLeft | PBETarget.FoeLeft | PBETarget.FoeRight; + return PBETurnTarget.AllyLeft | PBETurnTarget.FoeLeft | PBETurnTarget.FoeRight; } else { @@ -848,11 +856,11 @@ public static PBETarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove cal { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return PBETarget.AllyLeft; + return PBETurnTarget.AllyLeft; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.AllyRight; + return PBETurnTarget.AllyRight; } else { @@ -865,11 +873,11 @@ public static PBETarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove cal { if (PBEUtils.RandomBool()) { - return PBETarget.AllyLeft; + return PBETurnTarget.AllyLeft; } else { - return PBETarget.AllyRight; + return PBETurnTarget.AllyRight; } } else @@ -881,11 +889,11 @@ public static PBETarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove cal { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return PBETarget.AllyRight; + return PBETurnTarget.AllyRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.AllyLeft; + return PBETurnTarget.AllyLeft; } else { @@ -901,11 +909,11 @@ public static PBETarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove cal { if (PBEUtils.RandomBool()) { - return PBETarget.FoeLeft; + return PBETurnTarget.FoeLeft; } else { - return PBETarget.FoeRight; + return PBETurnTarget.FoeRight; } } else @@ -924,7 +932,7 @@ public static PBETarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove cal { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.AllyLeft | PBETarget.AllyCenter | PBETarget.AllyRight | PBETarget.FoeLeft | PBETarget.FoeCenter | PBETarget.FoeRight; + return PBETurnTarget.AllyLeft | PBETurnTarget.AllyCenter | PBETurnTarget.AllyRight | PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight; } else { @@ -935,7 +943,7 @@ public static PBETarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove cal { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.FoeLeft | PBETarget.FoeCenter | PBETarget.FoeRight; + return PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight; } else { @@ -946,15 +954,15 @@ public static PBETarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove cal { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return PBETarget.FoeCenter | PBETarget.FoeRight; + return PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return PBETarget.FoeLeft | PBETarget.FoeCenter | PBETarget.FoeRight; + return PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.FoeLeft | PBETarget.FoeCenter; + return PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter; } else { @@ -965,15 +973,15 @@ public static PBETarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove cal { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return PBETarget.AllyCenter | PBETarget.FoeCenter | PBETarget.FoeRight; + return PBETurnTarget.AllyCenter | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return PBETarget.AllyLeft | PBETarget.AllyRight | PBETarget.FoeLeft | PBETarget.FoeCenter | PBETarget.FoeRight; + return PBETurnTarget.AllyLeft | PBETurnTarget.AllyRight | PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter | PBETurnTarget.FoeRight; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.AllyCenter | PBETarget.FoeLeft | PBETarget.FoeCenter; + return PBETurnTarget.AllyCenter | PBETurnTarget.FoeLeft | PBETurnTarget.FoeCenter; } else { @@ -984,7 +992,7 @@ public static PBETarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove cal { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.AllyLeft | PBETarget.AllyCenter | PBETarget.AllyRight; + return PBETurnTarget.AllyLeft | PBETurnTarget.AllyCenter | PBETurnTarget.AllyRight; } else { @@ -995,15 +1003,15 @@ public static PBETarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove cal { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return PBETarget.AllyLeft; + return PBETurnTarget.AllyLeft; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { - return PBETarget.AllyCenter; + return PBETurnTarget.AllyCenter; } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.AllyRight; + return PBETurnTarget.AllyRight; } else { @@ -1016,11 +1024,11 @@ public static PBETarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove cal { if (PBEUtils.RandomBool()) { - return PBETarget.AllyLeft; + return PBETurnTarget.AllyLeft; } else { - return PBETarget.AllyCenter; + return PBETurnTarget.AllyCenter; } } else if (pkmn.FieldPosition == PBEFieldPosition.Center) @@ -1028,26 +1036,26 @@ public static PBETarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove cal int val = PBEUtils.RandomInt(0, 2); if (val == 0) { - return PBETarget.AllyLeft; + return PBETurnTarget.AllyLeft; } else if (val == 1) { - return PBETarget.AllyCenter; + return PBETurnTarget.AllyCenter; } else { - return PBETarget.AllyRight; + return PBETurnTarget.AllyRight; } } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { if (PBEUtils.RandomBool()) { - return PBETarget.AllyCenter; + return PBETurnTarget.AllyCenter; } else { - return PBETarget.AllyRight; + return PBETurnTarget.AllyRight; } } else @@ -1059,22 +1067,22 @@ public static PBETarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove cal { if (pkmn.FieldPosition == PBEFieldPosition.Left) { - return PBETarget.AllyCenter; + return PBETurnTarget.AllyCenter; } else if (pkmn.FieldPosition == PBEFieldPosition.Center) { if (PBEUtils.RandomBool()) { - return PBETarget.AllyLeft; + return PBETurnTarget.AllyLeft; } else { - return PBETarget.AllyRight; + return PBETurnTarget.AllyRight; } } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.AllyCenter; + return PBETurnTarget.AllyCenter; } else { @@ -1089,11 +1097,11 @@ public static PBETarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove cal { if (PBEUtils.RandomBool()) { - return PBETarget.FoeCenter; + return PBETurnTarget.FoeCenter; } else { - return PBETarget.FoeRight; + return PBETurnTarget.FoeRight; } } else if (pkmn.FieldPosition == PBEFieldPosition.Center) @@ -1101,26 +1109,26 @@ public static PBETarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove cal int val = PBEUtils.RandomInt(0, 2); if (val == 0) { - return PBETarget.FoeLeft; + return PBETurnTarget.FoeLeft; } else if (val == 1) { - return PBETarget.FoeCenter; + return PBETurnTarget.FoeCenter; } else { - return PBETarget.FoeRight; + return PBETurnTarget.FoeRight; } } else if (pkmn.FieldPosition == PBEFieldPosition.Right) { if (PBEUtils.RandomBool()) { - return PBETarget.FoeLeft; + return PBETurnTarget.FoeLeft; } else { - return PBETarget.FoeCenter; + return PBETurnTarget.FoeCenter; } } else @@ -1135,15 +1143,15 @@ public static PBETarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove cal int val = PBEUtils.RandomInt(0, 2); if (val == 0) { - return PBETarget.FoeLeft; + return PBETurnTarget.FoeLeft; } else if (val == 1) { - return PBETarget.FoeCenter; + return PBETurnTarget.FoeCenter; } else { - return PBETarget.FoeRight; + return PBETurnTarget.FoeRight; } } else @@ -1162,7 +1170,7 @@ public static PBETarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove cal { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.AllyCenter | PBETarget.FoeCenter; + return PBETurnTarget.AllyCenter | PBETurnTarget.FoeCenter; } else { @@ -1179,7 +1187,7 @@ public static PBETarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove cal { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.FoeCenter; + return PBETurnTarget.FoeCenter; } else { @@ -1193,7 +1201,7 @@ public static PBETarget GetRandomTargetForMetronome(PBEPokemon pkmn, PBEMove cal { if (pkmn.FieldPosition == PBEFieldPosition.Left || pkmn.FieldPosition == PBEFieldPosition.Center || pkmn.FieldPosition == PBEFieldPosition.Right) { - return PBETarget.AllyCenter; + return PBETurnTarget.AllyCenter; } else { diff --git a/PokemonBattleEngine/Battle/ExecutedMove.cs b/PokemonBattleEngine/Battle/ExecutedMove.cs index 78c4a75fc..47751736c 100644 --- a/PokemonBattleEngine/Battle/ExecutedMove.cs +++ b/PokemonBattleEngine/Battle/ExecutedMove.cs @@ -16,6 +16,8 @@ public sealed class PBETargetSuccess public bool CriticalHit { get; set; } public PBEFailReason FailReason { get; set; } public bool Missed { get; set; } + + internal PBETargetSuccess() { } } public ushort TurnNumber { get; } @@ -23,7 +25,7 @@ public sealed class PBETargetSuccess public PBEFailReason FailReason { get; } public ReadOnlyCollection Targets { get; } - public PBEExecutedMove(ushort turnNumber, PBEMove move, PBEFailReason failReason, IList targets) + internal PBEExecutedMove(ushort turnNumber, PBEMove move, PBEFailReason failReason, IList targets) { TurnNumber = turnNumber; Move = move; diff --git a/PokemonBattleEngine/Battle/Pokemon.cs b/PokemonBattleEngine/Battle/Pokemon.cs index 05102745f..7eb537fdd 100644 --- a/PokemonBattleEngine/Battle/Pokemon.cs +++ b/PokemonBattleEngine/Battle/Pokemon.cs @@ -17,73 +17,47 @@ public sealed class PBEPokemon /// The Pokémon's ID in its battle. public byte Id { get; } - /// - /// The Pokémon's nickname with its gender attached. - /// + /// The Pokémon's with its attached. public string NameWithGender => Nickname + GenderSymbol; - /// - /// The Pokémon's gender as a string. - /// + /// The Pokémon's as a . public string GenderSymbol => Gender == PBEGender.Female ? "♀" : Gender == PBEGender.Male ? "♂" : string.Empty; - /// - /// The Pokémon's known nickname with its known gender attached. - /// + /// The Pokémon's with its attached. public string KnownNameWithKnownGender => KnownNickname + KnownGenderSymbol; - /// - /// The Pokémon's known gender as a string. - /// + /// The Pokémon's as a . public string KnownGenderSymbol => KnownGender == PBEGender.Female ? "♀" : KnownGender == PBEGender.Male ? "♂" : string.Empty; - /// - /// The Pokémon's current HP. - /// + /// The Pokémon's current HP. public ushort HP { get; set; } - /// - /// The Pokémon's maximum HP. - /// + /// The Pokémon's maximum HP. public ushort MaxHP { get; set; } - /// - /// The Pokémon's current HP as a percentage. - /// + /// The Pokémon's current HP as a percentage. public double HPPercentage { get; set; } - /// - /// The Pokémon's attack stat. - /// + /// The Pokémon's attack stat. public ushort Attack { get; set; } public sbyte AttackChange { get; set; } - /// - /// The Pokémon's defense stat. - /// + /// The Pokémon's defense stat. public ushort Defense { get; set; } public sbyte DefenseChange { get; set; } - /// - /// The Pokémon's special attack stat. - /// + /// The Pokémon's special attack stat. public ushort SpAttack { get; set; } public sbyte SpAttackChange { get; set; } - /// - /// The Pokémon's special defense stat. - /// + /// The Pokémon's special defense stat. public ushort SpDefense { get; set; } public sbyte SpDefenseChange { get; set; } - /// - /// The Pokémon's speed stat. - /// + /// 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 PBEEffortValueCollection EffortValues { get; set; } - public PBEIndividualValueCollection IndividualValues { get; set; } + public PBEEffortValues EffortValues { get; } + public PBEIndividualValues IndividualValues { get; } public byte Friendship { get; set; } /// The Pokémon's level. public byte Level { get; set; } /// The Pokémon's nature. public PBENature Nature { get; set; } - /// The moves the Pokémon had upon entering battle. - public PBEMove[] OriginalMoves { get; set; } - /// The PP-Ups the Pokémon had upon entering battle. - public byte[] OriginalPPUps { get; set; } + /// The moveset the Pokémon had upon entering battle. + public PBEMoveset OriginalMoveset { get; set; } /// The Pokémon's field position. public PBEFieldPosition FieldPosition { get; set; } @@ -104,11 +78,9 @@ public sealed class PBEPokemon /// The item the Pokémon is known to have. public PBEItem KnownItem { get; set; } /// The moves the Pokémon currently has. - public PBEMove[] Moves { get; set; } + public PBEBattleMoveset Moves { get; } /// The moves the Pokémon is known to have. - public PBEMove[] KnownMoves { get; set; } - public byte[] PP { get; set; } - public byte[] MaxPP { get; set; } + 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. @@ -137,79 +109,49 @@ public sealed class PBEPokemon public double KnownWeight { get; set; } #region Statuses - /// - /// The counter used for and . - /// + /// The counter used for and . public byte Status1Counter { get; set; } - /// - /// The amount of turns the Pokémon will sleep for before waking. - /// + /// The amount of turns the Pokémon will sleep for before waking. public byte SleepTurns { get; set; } - /// - /// The counter used for . - /// + /// 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. - /// + /// 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 disguised as. - /// + /// The Pokémon that is disguised as. public PBEPokemon DisguisedAsPokemon { get; set; } - /// - /// The Pokémon that is bound to. - /// + /// The Pokémon that is bound to. public PBEPokemon InfatuatedWithPokemon { get; set; } - /// - /// The position to return HP to on . - /// + /// The position to return HP to on . public PBEFieldPosition SeededPosition { get; set; } - /// - /// The team responsible for . - /// + /// The team responsible for . public PBETeam SeededTeam { get; set; } - /// - /// The amount of HP the Pokémon's has left. - /// + /// The amount of HP the Pokémon's has left. public ushort SubstituteHP { get; set; } - public PBEMove[] PreTransformMoves { get; set; } - public byte[] PreTransformPP { get; set; } - public byte[] PreTransformMaxPP { get; set; } + public PBEBattleMoveset TransformBackupMoves { get; } #endregion #region Actions - public PBEAction SelectedAction; // Must be a field + public PBETurnAction TurnAction { get; set; } public List ExecutedMoves { get; } = new List(); - /// - /// The move the Pokémon is forced to use by multi-turn moves. - /// + /// The move the Pokémon is forced to use by multi-turn moves. public PBEMove TempLockedMove { get; set; } - /// - /// The targets the Pokémon is forced to target by multi-turn moves. - /// - public PBETarget TempLockedTargets { get; set; } - /// - /// The move the Pokémon is forced to use by its choice item. - /// + /// 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; } #endregion #region Special Flags - /// - /// True if the Pokémon was originally but was , therefore forcing it to remain as when switching out. - /// + /// True if the Pokémon was originally but was , therefore forcing it to remain as when switching out. public bool Shaymin_CannotChangeBackToSkyForm { get; set; } - /// - /// True if the Pokémon was present at the start of the turn, which would allow to activate. - /// + /// 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 internal PBEPokemon(BinaryReader r, PBETeam team) { Team = team; - SelectedAction.PokemonId = Id = r.ReadByte(); + Id = r.ReadByte(); Species = OriginalSpecies = KnownSpecies = (PBESpecies)r.ReadUInt32(); var pData = PBEPokemonData.GetData(Species); KnownType1 = Type1 = pData.Type1; @@ -225,42 +167,21 @@ internal PBEPokemon(BinaryReader r, PBETeam team) Gender = KnownGender = (PBEGender)r.ReadByte(); Item = OriginalItem = (PBEItem)r.ReadUInt16(); KnownItem = (PBEItem)ushort.MaxValue; - EffortValues = new PBEEffortValueCollection(team.Battle.Settings, r.ReadByte(), r.ReadByte(), r.ReadByte(), r.ReadByte(), r.ReadByte(), r.ReadByte()); - IndividualValues = new PBEIndividualValueCollection(team.Battle.Settings, r.ReadByte(), r.ReadByte(), r.ReadByte(), r.ReadByte(), r.ReadByte(), r.ReadByte()); + EffortValues = new PBEEffortValues(Team.Battle.Settings, r); + IndividualValues = new PBEIndividualValues(Team.Battle.Settings, r); SetStats(); HP = MaxHP; HPPercentage = 1D; - OriginalMoves = new PBEMove[team.Battle.Settings.NumMoves]; - OriginalPPUps = new byte[team.Battle.Settings.NumMoves]; - for (int i = 0; i < team.Battle.Settings.NumMoves; i++) - { - OriginalMoves[i] = (PBEMove)r.ReadUInt16(); - OriginalPPUps[i] = r.ReadByte(); - } - Moves = (PBEMove[])OriginalMoves.Clone(); - KnownMoves = new PBEMove[Team.Battle.Settings.NumMoves]; - for (int i = 0; i < Team.Battle.Settings.NumMoves; i++) - { - KnownMoves[i] = PBEMove.MAX; - } - PP = new byte[team.Battle.Settings.NumMoves]; - MaxPP = new byte[team.Battle.Settings.NumMoves]; - for (int i = 0; i < team.Battle.Settings.NumMoves; i++) - { - PBEMove move = Moves[i]; - if (move != PBEMove.None) - { - byte tier = PBEMoveData.Data[move].PPTier; - PP[i] = MaxPP[i] = (byte)Math.Max(1, (tier * team.Battle.Settings.PPMultiplier) + (tier * OriginalPPUps[i])); - } - } - team.Party.Add(this); + OriginalMoveset = new PBEMoveset(Species, Level, Team.Battle.Settings, r); + Moves = new PBEBattleMoveset(OriginalMoveset); + KnownMoves = new PBEBattleMoveset(Team.Battle.Settings); + TransformBackupMoves = new PBEBattleMoveset(Team.Battle.Settings); + Team.Party.Add(this); } - // Stats & PP are set from the shell info internal PBEPokemon(PBETeam team, byte id, PBEPokemonShell shell) { Team = team; - SelectedAction.PokemonId = Id = id; + Id = id; Species = OriginalSpecies = KnownSpecies = shell.Species; var pData = PBEPokemonData.GetData(Species); KnownType1 = Type1 = pData.Type1; @@ -276,35 +197,28 @@ internal PBEPokemon(PBETeam team, byte id, PBEPokemonShell shell) Gender = KnownGender = shell.Gender; Item = OriginalItem = shell.Item; KnownItem = (PBEItem)ushort.MaxValue; - EffortValues = new PBEEffortValueCollection(team.Battle.Settings, shell.EffortValues); - IndividualValues = new PBEIndividualValueCollection(team.Battle.Settings, shell.IndividualValues); + EffortValues = new PBEEffortValues(shell.EffortValues); + IndividualValues = new PBEIndividualValues(shell.IndividualValues); SetStats(); HP = MaxHP; HPPercentage = 1D; - OriginalMoves = shell.Moveset.MoveSlots.Select(m => m.Move).ToArray(); - OriginalPPUps = shell.Moveset.MoveSlots.Select(m => m.PPUps).ToArray(); - Moves = (PBEMove[])OriginalMoves.Clone(); - KnownMoves = new PBEMove[Team.Battle.Settings.NumMoves]; - for (int i = 0; i < Team.Battle.Settings.NumMoves; i++) - { - KnownMoves[i] = PBEMove.MAX; - } - PP = new byte[team.Battle.Settings.NumMoves]; - MaxPP = new byte[team.Battle.Settings.NumMoves]; - for (int i = 0; i < team.Battle.Settings.NumMoves; i++) - { - PBEMove move = Moves[i]; - if (move != PBEMove.None) - { - byte tier = PBEMoveData.Data[move].PPTier; - PP[i] = MaxPP[i] = (byte)Math.Max(1, (tier * team.Battle.Settings.PPMultiplier) + (tier * OriginalPPUps[i])); - } - } + OriginalMoveset = new PBEMoveset(shell.Moveset); + Moves = new PBEBattleMoveset(OriginalMoveset); + KnownMoves = new PBEBattleMoveset(Team.Battle.Settings); + TransformBackupMoves = new PBEBattleMoveset(Team.Battle.Settings); team.Party.Add(this); } // This constructor is to define a remote Pokémon public PBEPokemon(PBETeam team, PBEPkmnSwitchInPacket.PBESwitchInInfo info) { + if (team == null) + { + throw new ArgumentNullException(nameof(team)); + } + if (info == null) + { + throw new ArgumentNullException(nameof(info)); + } Team = team; Id = info.PokemonId; FieldPosition = info.FieldPosition; @@ -316,14 +230,9 @@ public PBEPokemon(PBETeam team, PBEPkmnSwitchInPacket.PBESwitchInInfo info) KnownAbility = Ability = OriginalAbility = PBEAbility.MAX; KnownGender = Gender = info.Gender; KnownItem = Item = OriginalItem = (PBEItem)ushort.MaxValue; - Moves = new PBEMove[Team.Battle.Settings.NumMoves]; - KnownMoves = new PBEMove[Team.Battle.Settings.NumMoves]; - for (int i = 0; i < Team.Battle.Settings.NumMoves; i++) - { - KnownMoves[i] = Moves[i] = PBEMove.MAX; - } - PP = new byte[Team.Battle.Settings.NumMoves]; - MaxPP = new byte[Team.Battle.Settings.NumMoves]; + Moves = new PBEBattleMoveset(Team.Battle.Settings); + KnownMoves = new PBEBattleMoveset(Team.Battle.Settings); + TransformBackupMoves = new PBEBattleMoveset(Team.Battle.Settings); KnownNickname = Nickname = info.Nickname; KnownShiny = Shiny = info.Shiny; KnownSpecies = Species = OriginalSpecies = info.Species; @@ -377,10 +286,7 @@ public void ClearForSwitch() KnownAbility = PBEAbility.MAX; KnownGender = Gender; KnownItem = (PBEItem)ushort.MaxValue; - for (int i = 0; i < Team.Battle.Settings.NumMoves; i++) - { - KnownMoves[i] = PBEMove.MAX; - } + KnownMoves.SetUnknown(); KnownNickname = Nickname; KnownShiny = Shiny; KnownType1 = Type1 = pData.Type1; @@ -402,19 +308,15 @@ public void ClearForSwitch() SeededPosition = PBEFieldPosition.None; SeededTeam = null; SubstituteHP = 0; - if (Status2.HasFlag(PBEStatus2.Transformed)) + if (Id != byte.MaxValue && Status2.HasFlag(PBEStatus2.Transformed)) { - Moves = (PBEMove[])PreTransformMoves.Clone(); - PP = (byte[])PreTransformPP.Clone(); - MaxPP = (byte[])PreTransformMaxPP.Clone(); - PreTransformMoves = null; - PreTransformPP = null; - PreTransformMaxPP = null; + Moves.Reset(TransformBackupMoves); + TransformBackupMoves.SetUnknown(); } Status2 = PBEStatus2.None; TempLockedMove = ChoiceLockedMove = PBEMove.None; - TempLockedTargets = PBETarget.None; + TempLockedTargets = PBETurnTarget.None; ExecutedMoves.Clear(); @@ -431,6 +333,10 @@ public void ClearForSwitch() /// Frees the Pokémon of its . public void Transform(PBEPokemon target) { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } if (Team != target.Team) { KnownAbility = target.KnownAbility = Ability = target.Ability; @@ -464,36 +370,43 @@ public void Transform(PBEPokemon target) SpeedChange = target.SpeedChange; AccuracyChange = target.AccuracyChange; EvasionChange = target.EvasionChange; - PreTransformMoves = (PBEMove[])Moves.Clone(); - PreTransformPP = (byte[])PP.Clone(); - PreTransformMaxPP = (byte[])MaxPP.Clone(); - for (int i = 0; i < Team.Battle.Settings.NumMoves; i++) + TransformBackupMoves.Reset(Moves); + PBEBattleMoveset.DoTransform(this, target); + if (!Moves.Contains(ChoiceLockedMove)) { - if (Team != target.Team) - { - KnownMoves[i] = target.KnownMoves[i] = Moves[i] = target.Moves[i]; - } - else + ChoiceLockedMove = PBEMove.None; + } + Status2 |= PBEStatus2.Transformed; + } + public void UpdateKnownPP(PBEMove move, int amountReduced) + { + if (move == PBEMove.None || move >= PBEMove.MAX || !Enum.IsDefined(typeof(PBEMove), move)) + { + throw new ArgumentOutOfRangeException(nameof(move)); + } + PBEBattleMoveset.PBEBattleMovesetSlot knownSlot = KnownMoves[move]; + knownSlot.PP += amountReduced; + if (knownSlot.MaxPP == 0) + { + if (Status2.HasFlag(PBEStatus2.Transformed)) { - Moves[i] = target.Moves[i]; - KnownMoves[i] = target.KnownMoves[i]; + knownSlot.MaxPP = PBEBattleMoveset.GetTransformPP(Team.Battle.Settings, move); } - if (Id != byte.MaxValue) // Don't set PP if this is a client's unknown remote Pokémon + else if (Team.Battle.Settings.MaxPPUps == 0 || knownSlot.PP > PBEBattleMoveset.GetNonTransformPP(Team.Battle.Settings, move, (byte)(Team.Battle.Settings.MaxPPUps - 1))) { - PP[i] = MaxPP[i] = (byte)(Moves[i] == PBEMove.None ? 0 : PBEMoveData.Data[Moves[i]].PPTier == 0 ? 1 : Team.Battle.Settings.PPMultiplier); + knownSlot.MaxPP = PBEBattleMoveset.GetNonTransformPP(Team.Battle.Settings, move, Team.Battle.Settings.MaxPPUps); } } - if (!Moves.Contains(ChoiceLockedMove)) - { - ChoiceLockedMove = PBEMove.None; - } - Status2 |= PBEStatus2.Transformed; } /// Returns True if the Pokémon has , False otherwise. /// The type to check. public bool HasType(PBEType type) { + if (type >= PBEType.MAX) + { + throw new ArgumentOutOfRangeException(nameof(type)); + } return Type1 == type || Type2 == type; } public bool HasCancellingAbility() @@ -562,14 +475,12 @@ public sbyte SetStatChange(PBEStat stat, int value) default: throw new ArgumentOutOfRangeException(nameof(stat)); } } - /// - /// Gets the type that a move will become when used by this Pokémon. - /// + /// Gets the type that a move will become when used by this Pokémon. /// The move to check. /// Thrown when is invalid. public PBEType GetMoveType(PBEMove move) { - if (move == PBEMove.None || move >= PBEMove.MAX) + if (move == PBEMove.None || move >= PBEMove.MAX || !Enum.IsDefined(typeof(PBEMove), move)) { throw new ArgumentOutOfRangeException(nameof(move)); } @@ -640,14 +551,12 @@ public PBEType GetMoveType(PBEMove move) } } } - /// - /// Gets the possible targets that a move can target when used by this Pokémon. - /// + /// 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) + if (move == PBEMove.None || move >= PBEMove.MAX || !Enum.IsDefined(typeof(PBEMove), move)) { throw new ArgumentOutOfRangeException(nameof(move)); } @@ -667,17 +576,15 @@ public PBEMoveTarget GetMoveTargets(PBEMove move) return PBEMoveData.Data[move].Targets; } } - /// - /// Returns True if the Pokémon is only able to use , False otherwise. - /// + /// Returns True if the Pokémon is only able to use , False otherwise. 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 && PP[Array.IndexOf(Moves, ChoiceLockedMove)] == 0) // If the choice locked move has 0 pp, it is forced to struggle - || Array.TrueForAll(PP, p => p == 0) // If all moves have 0 pp, then the user is forced to struggle + 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; @@ -687,9 +594,7 @@ public bool IsForcedToStruggle() 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. - /// + /// 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; @@ -697,17 +602,29 @@ public bool CanSwitchOut() // TODO: Make different public versions that use Known*? AIs should not be able to cheat public bool CanBecomeBurnedBy(PBEPokemon other) { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } return Status1 == PBEStatus1.None && !HasType(PBEType.Fire) && !(Ability == PBEAbility.WaterVeil && !other.HasCancellingAbility()); } public bool CanBecomeConfusedBy(PBEPokemon other) { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } return !Status2.HasFlag(PBEStatus2.Confused) && !(Ability == PBEAbility.OwnTempo && !other.HasCancellingAbility()); } public bool CanBecomeFrozenBy(PBEPokemon other) { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } return Status1 == PBEStatus1.None && !HasType(PBEType.Ice) && !(Ability == PBEAbility.MagmaArmor && !other.HasCancellingAbility()); @@ -715,17 +632,29 @@ public bool CanBecomeFrozenBy(PBEPokemon other) /// Returns True if the Pokémon can become with . public bool CanBecomeInfatuatedWith(PBEPokemon other) { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } return !Status2.HasFlag(PBEStatus2.Infatuated) && ((Gender == PBEGender.Male && other.Gender == PBEGender.Female) || (Gender == PBEGender.Female && other.Gender == PBEGender.Male)) && !(Ability == PBEAbility.Oblivious && !other.HasCancellingAbility()); } public bool CanBecomeParalyzedBy(PBEPokemon other) { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } return Status1 == PBEStatus1.None && !(Ability == PBEAbility.Limber && !other.HasCancellingAbility()); } public bool CanBecomePoisonedBy(PBEPokemon other) { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } return Status1 == PBEStatus1.None && !HasType(PBEType.Poison) && !HasType(PBEType.Steel) @@ -733,11 +662,19 @@ public bool CanBecomePoisonedBy(PBEPokemon other) } public bool CanFallAsleepFrom(PBEPokemon other) { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } return Status1 == PBEStatus1.None && !(Ability == PBEAbility.Insomnia && !other.HasCancellingAbility()); } public bool CanFlinchFrom(PBEPokemon other) { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } return !Status2.HasFlag(PBEStatus2.Flinching) && !(Ability == PBEAbility.InnerFocus && !other.HasCancellingAbility()); } @@ -761,9 +698,10 @@ public PBEMove[] GetUsableMoves() { for (int i = 0; i < Team.Battle.Settings.NumMoves; i++) { - if (PP[i] > 0) + PBEBattleMoveset.PBEBattleMovesetSlot slot = Moves[i]; + if (slot.PP > 0) { - usableMoves.Add(Moves[i]); + usableMoves.Add(slot.Move); } } } @@ -788,12 +726,11 @@ public ushort GetProtectionChance() return chance; } - internal List ToBytes() + internal void ToBytes(List bytes) { - var bytes = new List(); bytes.Add(Id); bytes.AddRange(BitConverter.GetBytes((uint)OriginalSpecies)); - bytes.AddRange(PBEUtils.StringToBytes(Nickname)); + PBEUtils.StringToBytes(bytes, Nickname); bytes.Add(Level); bytes.Add(Friendship); bytes.Add((byte)(Shiny ? 1 : 0)); @@ -801,14 +738,9 @@ internal List ToBytes() bytes.Add((byte)Nature); bytes.Add((byte)Gender); bytes.AddRange(BitConverter.GetBytes((ushort)OriginalItem)); - bytes.AddRange(EffortValues.Select(ev => ev.Value)); - bytes.AddRange(IndividualValues.Select(iv => iv.Value)); - for (int i = 0; i < Team.Battle.Settings.NumMoves; i++) - { - bytes.AddRange(BitConverter.GetBytes((ushort)OriginalMoves[i])); - bytes.Add(OriginalPPUps[i]); - } - return bytes; + EffortValues.ToBytes(bytes); + IndividualValues.ToBytes(bytes); + OriginalMoveset.ToBytes(bytes); } // Will only be accurate for the host @@ -817,7 +749,18 @@ public override string ToString() var sb = new StringBuilder(); sb.AppendLine($"{Nickname}/{Species} {GenderSymbol} Lv.{Level}"); sb.AppendLine($"HP: {HP}/{MaxHP} ({HPPercentage:P2})"); - sb.AppendLine($"Types: {PBELocalizedString.GetTypeName(Type1).English}/{PBELocalizedString.GetTypeName(Type2).English}"); + sb.Append($"Types: {PBELocalizedString.GetTypeName(Type1).English}"); + if (Type2 != PBEType.None) + { + sb.Append($"/{PBELocalizedString.GetTypeName(Type2).English}"); + } + sb.AppendLine(); + sb.Append($"Known types: {PBELocalizedString.GetTypeName(KnownType1).English}"); + if (KnownType2 != PBEType.None) + { + sb.Append($"/{PBELocalizedString.GetTypeName(KnownType2).English}"); + } + sb.AppendLine(); sb.AppendLine($"Position: {Team.TrainerName}'s {FieldPosition}"); sb.AppendLine($"Status1: {Status1}"); if (Status1 == PBEStatus1.Asleep) @@ -888,23 +831,68 @@ public override string ToString() sb.AppendLine($"Known ability: {(KnownAbility == PBEAbility.MAX ? "???" : PBELocalizedString.GetAbilityName(KnownAbility).English)}"); sb.AppendLine($"Item: {PBELocalizedString.GetItemName(Item).English}"); sb.AppendLine($"Known item: {(KnownItem == (PBEItem)ushort.MaxValue ? "???" : PBELocalizedString.GetItemName(KnownItem).English)}"); - if (Array.IndexOf(Moves, PBEMove.Frustration) != -1 || Array.IndexOf(Moves, PBEMove.Return) != -1) + if (Moves.Contains(PBEMove.Frustration) || Moves.Contains(PBEMove.Return)) { - sb.AppendLine($"Friendship: {Friendship} ({Friendship / (double)byte.MaxValue:P2})"); + sb.AppendLine($"Friendship: {Friendship} ({Friendship / byte.MaxValue:P2})"); } - if (Array.IndexOf(Moves, PBEMove.HiddenPower) != -1) + if (Moves.Contains(PBEMove.HiddenPower)) { sb.AppendLine($"{PBELocalizedString.GetMoveName(PBEMove.HiddenPower).English}: {PBELocalizedString.GetTypeName(IndividualValues.HiddenPowerType).English}:{IndividualValues.HiddenPowerBasePower}"); } - string[] moveStrs = new string[Moves.Length]; - for (int i = 0; i < moveStrs.Length; i++) + sb.Append("Moves: "); + for (int i = 0; i < Team.Battle.Settings.NumMoves; i++) + { + PBEBattleMoveset.PBEBattleMovesetSlot slot = Moves[i]; + PBEMove move = slot.Move; + if (i > 0) + { + sb.Append(", "); + } + sb.Append(PBELocalizedString.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 < Team.Battle.Settings.NumMoves; i++) { - moveStrs[i] = $"{PBELocalizedString.GetMoveName(Moves[i]).English} {PP[i]}/{MaxPP[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 ? "???" : PBELocalizedString.GetMoveName(move).English); + if (move != PBEMove.None && move != PBEMove.MAX) + { + sb.Append($" ({pp}{(maxPP == 0 ? ")" : $"/{maxPP})")}"); + } } - sb.AppendLine($"Moves: {string.Join(", ", moveStrs)}"); - sb.AppendLine($"Usable moves: {string.Join(", ", GetUsableMoves().Select(m => PBELocalizedString.GetMoveName(m).English))}"); - sb.Append($"Known moves: {string.Join(", ", KnownMoves.Select(m => m == PBEMove.MAX ? "???" : PBELocalizedString.GetMoveName(m).English))}"); + sb.AppendLine(); + sb.Append($"Usable moves: {string.Join(", ", GetUsableMoves().Select(m => PBELocalizedString.GetMoveName(m).English))}"); return sb.ToString(); } + + internal bool IsDisposed { get; private set; } + internal void Dispose() + { + if (!IsDisposed) + { + IsDisposed = true; + if (Id != byte.MaxValue) + { + EffortValues.CanDispose = true; + EffortValues.Dispose(); + IndividualValues.CanDispose = true; + IndividualValues.Dispose(); + OriginalMoveset.CanDispose = true; + OriginalMoveset.Dispose(); + } + } + } } } diff --git a/PokemonBattleEngine/Battle/Team.cs b/PokemonBattleEngine/Battle/Team.cs index 356a5312c..2583287b9 100644 --- a/PokemonBattleEngine/Battle/Team.cs +++ b/PokemonBattleEngine/Battle/Team.cs @@ -1,4 +1,6 @@ using Kermalis.PokemonBattleEngine.Data; +using System; +using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; @@ -6,6 +8,50 @@ namespace Kermalis.PokemonBattleEngine.Battle { + public sealed class PBETeams : IReadOnlyList + { + private readonly PBETeam _team1; + private readonly PBETeam _team2; + + public int Count => 2; + public PBETeam this[int index] + { + get + { + switch (index) + { + case 0: return _team1; + case 1: return _team2; + default: throw new ArgumentOutOfRangeException(nameof(index)); + } + } + } + + internal PBETeams(PBEBattle battle, PBETeamShell team1Shell, string team1TrainerName, PBETeamShell team2Shell, string team2TrainerName) + { + _team1 = new PBETeam(battle, 0, team1Shell, team1TrainerName); + _team2 = new PBETeam(battle, 1, team2Shell, team2TrainerName); + _team1.OpposingTeam = _team2; + _team2.OpposingTeam = _team1; + } + internal PBETeams(PBEBattle battle) + { + _team1 = new PBETeam(battle, 0); + _team2 = new PBETeam(battle, 1); + _team1.OpposingTeam = _team2; + _team2.OpposingTeam = _team1; + } + + public IEnumerator GetEnumerator() + { + yield return _team1; + yield return _team2; + } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } // TODO: INPC /// Represents a team in a specific . public sealed class PBETeam @@ -14,7 +60,8 @@ public sealed class PBETeam public PBEBattle Battle { get; } public byte Id { get; } public string TrainerName { get; set; } // Setter is public because a client cannot submit the opponent's team - public List Party { get; private set; } + public PBEList Party { get; } + public PBETeam OpposingTeam { get; internal set; } public PBEPokemon[] ActiveBattlers => Battle.ActiveBattlers.Where(p => p.Team == this).OrderBy(p => p.FieldPosition).ToArray(); public int NumPkmnAlive => Party.Count(p => p.HP > 0); @@ -33,25 +80,25 @@ public sealed class PBETeam public bool MonFaintedLastTurn { get; set; } public bool MonFaintedThisTurn { get; set; } - internal PBETeam(PBEBattle battle, byte id, PBETeamShell shell, string trainerName, ref byte pkmnIdCounter) + internal PBETeam(PBEBattle battle, byte id, PBETeamShell shell, string trainerName) { Battle = battle; Id = id; - CreateParty(shell, trainerName, ref pkmnIdCounter); + Party = new PBEList(Battle.Settings.MaxPartySize); + CreateParty(shell, trainerName); } internal PBETeam(PBEBattle battle, byte id) { Battle = battle; Id = id; - Party = new List(Battle.Settings.MaxPartySize); + Party = new PBEList(Battle.Settings.MaxPartySize); } - internal void CreateParty(PBETeamShell shell, string trainerName, ref byte pkmnIdCounter) + internal void CreateParty(PBETeamShell shell, string trainerName) { TrainerName = trainerName; - Party = new List(Battle.Settings.MaxPartySize); for (int i = 0; i < shell.Count; i++) { - new PBEPokemon(this, pkmnIdCounter++, shell[i]); + new PBEPokemon(this, (byte)((Id * Battle.Settings.MaxPartySize) + i), shell[i]); } } @@ -59,22 +106,49 @@ internal void CreateParty(PBETeamShell shell, string trainerName, ref byte pkmnI /// The of the . public PBEPokemon TryGetPokemon(PBEFieldPosition pos) { + if (IsDisposed) + { + throw new ObjectDisposedException(null); + } return ActiveBattlers.SingleOrDefault(p => p.FieldPosition == pos); } /// Gets a specific by its ID. /// The ID of the . public PBEPokemon TryGetPokemon(byte pkmnId) { + if (IsDisposed) + { + throw new ObjectDisposedException(null); + } return Party.SingleOrDefault(p => p.Id == pkmnId); } - internal List ToBytes() + public bool Remove(PBEPokemon pokemon) + { + if (IsDisposed) + { + throw new ObjectDisposedException(null); + } + if (pokemon.IsDisposed) + { + throw new ObjectDisposedException(nameof(pokemon)); + } + bool b = Party.Remove(pokemon); + if (b) + { + pokemon.Dispose(); + } + return b; + } + + internal void ToBytes(List bytes) { - var bytes = new List(); - bytes.AddRange(PBEUtils.StringToBytes(TrainerName)); + PBEUtils.StringToBytes(bytes, TrainerName); bytes.Add((byte)Party.Count); - bytes.AddRange(Party.SelectMany(p => p.ToBytes())); - return bytes; + for (int i = 0; i < (sbyte)Party.Count; i++) + { + Party[i].ToBytes(bytes); + } } internal void FromBytes(BinaryReader r) { @@ -96,5 +170,18 @@ public override string ToString() sb.AppendLine($"NumPkmnOnField: {NumPkmnOnField}"); return sb.ToString(); } + + internal bool IsDisposed { get; private set; } + internal void Dispose() + { + if (!IsDisposed) + { + IsDisposed = true; + for (int i = 0; i < Party.Count; i++) + { + Party[i].Dispose(); + } + } + } } } diff --git a/PokemonBattleEngine/Data/EffortValueCollection.cs b/PokemonBattleEngine/Data/EffortValueCollection.cs deleted file mode 100644 index 9971296ef..000000000 --- a/PokemonBattleEngine/Data/EffortValueCollection.cs +++ /dev/null @@ -1,249 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; - -namespace Kermalis.PokemonBattleEngine.Data -{ - public sealed class PBEEffortValueCollection : IEnumerable, INotifyPropertyChanged - { - public sealed class PBEEffortValue : INotifyPropertyChanged - { - private void OnPropertyChanged(string property) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); - } - public event PropertyChangedEventHandler PropertyChanged; - - private readonly PBEEffortValueCollection parent; - - public PBEStat Stat { get; } - private byte value; - public byte Value - { - get => value; - set - { - if (this.value != value) - { - parent.Set(Stat, value); - } - } - } - - internal PBEEffortValue(PBEEffortValueCollection parent, PBEStat stat) - { - this.parent = parent; - Stat = stat; - } - - internal void Update(byte value) - { - this.value = value; - OnPropertyChanged(nameof(Value)); - } - } - - private void OnPropertyChanged(string property) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); - } - public event PropertyChangedEventHandler PropertyChanged; - - public PBESettings Settings { get; } - private PBEEffortValue[] evs; - - public ushort StatTotal - { - get - { - ushort total = 0; - for (int i = 0; i < 6; i++) - { - total += evs[i].Value; - } - return total; - } - } - - public PBEEffortValueCollection(PBESettings settings, bool randomize) - { - if (settings == null) - { - throw new ArgumentNullException(nameof(settings)); - } - Settings = settings; - Settings.PropertyChanged += OnSettingsChanged; - CreateEVs(); - if (randomize) - { - Randomize(); - } - } - public PBEEffortValueCollection(PBESettings settings, byte hp = 0, byte attack = 0, byte defense = 0, byte spAttack = 0, byte spDefense = 0, byte speed = 0) - { - if (settings == null) - { - throw new ArgumentNullException(nameof(settings)); - } - Settings = settings; - Settings.PropertyChanged += OnSettingsChanged; - CreateEVs(); - TrySet(hp, attack, defense, spAttack, spDefense, speed); - } - public PBEEffortValueCollection(PBESettings settings, PBEEffortValueCollection other) - { - if (settings == null) - { - throw new ArgumentNullException(nameof(settings)); - } - if (other == null) - { - throw new ArgumentNullException(nameof(other)); - } - Settings = settings; - Settings.PropertyChanged += OnSettingsChanged; - CreateEVs(); - TrySet(other[PBEStat.HP].Value, other[PBEStat.Attack].Value, other[PBEStat.Defense].Value, other[PBEStat.SpAttack].Value, other[PBEStat.SpDefense].Value, other[PBEStat.Speed].Value); - } - private void CreateEVs() - { - evs = new PBEEffortValue[6] - { - new PBEEffortValue(this, PBEStat.HP), - new PBEEffortValue(this, PBEStat.Attack), - new PBEEffortValue(this, PBEStat.Defense), - new PBEEffortValue(this, PBEStat.SpAttack), - new PBEEffortValue(this, PBEStat.SpDefense), - new PBEEffortValue(this, PBEStat.Speed) - }; - } - private void UpdateEV(PBEEffortValue ev, byte value) - { - if (ev.Value != value) - { - ev.Update(value); - OnPropertyChanged(nameof(StatTotal)); - } - } - private void TrySet(byte hp, byte attack, byte defense, byte spAttack, byte spDefense, byte speed) - { - Set(PBEStat.HP, hp); - Set(PBEStat.Attack, attack); - Set(PBEStat.Defense, defense); - Set(PBEStat.SpAttack, spAttack); - Set(PBEStat.SpDefense, spDefense); - Set(PBEStat.Speed, speed); - } - - public PBEEffortValue this[PBEStat stat] - { - get - { - int statIndex = (int)stat; - if (statIndex >= 6) - { - throw new ArgumentOutOfRangeException(nameof(stat)); - } - else - { - return evs[statIndex]; - } - } - } - - public void Set(PBEStat stat, byte value) - { - int statIndex = (int)stat; - if (statIndex >= 6) - { - throw new ArgumentOutOfRangeException(nameof(stat)); - } - PBEEffortValue ev = evs[statIndex]; - ushort oldTotal = StatTotal; - int newTotal = oldTotal - ev.Value + value; - if (newTotal > Settings.MaxTotalEVs) - { - UpdateEV(ev, (byte)(value - (newTotal - Settings.MaxTotalEVs))); - } - else - { - UpdateEV(ev, value); - } - } - public void Randomize() - { - if (Settings.MaxTotalEVs != 0) - { - byte[] vals = new byte[6]; - int[] a = Enumerable.Repeat(0, 6 - 1) - .Select(x => PBEUtils.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 = notMax.RandomElement(); - 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++) - { - UpdateEV(evs[i], vals[i]); - } - } - } - - public IEnumerator GetEnumerator() - { - for (int i = 0; i < 6; i++) - { - yield return evs[i]; - } - } - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - private void OnSettingsChanged(object sender, PropertyChangedEventArgs e) - { - switch (e.PropertyName) - { - case nameof(Settings.MaxTotalEVs): - { - ushort oldTotal = StatTotal; - if (oldTotal > Settings.MaxTotalEVs) - { - for (int i = 0; i < 6; i++) - { - PBEEffortValue ev = evs[i]; - UpdateEV(ev, (byte)(Settings.MaxTotalEVs * ((double)ev.Value / oldTotal))); - } - } - break; - } - } - } - } -} diff --git a/PokemonBattleEngine/Data/EffortValues.cs b/PokemonBattleEngine/Data/EffortValues.cs new file mode 100644 index 000000000..3cde1fac4 --- /dev/null +++ b/PokemonBattleEngine/Data/EffortValues.cs @@ -0,0 +1,295 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; + +namespace Kermalis.PokemonBattleEngine.Data +{ + public sealed class PBEEffortValues : IDisposable, IEnumerable, INotifyPropertyChanged + { + public sealed class PBEEffortValue : INotifyPropertyChanged + { + private void OnPropertyChanged(string property) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); + } + public event PropertyChangedEventHandler PropertyChanged; + + private readonly PBEEffortValues _parent; + + public PBEStat Stat { get; } + private byte _value; + public byte Value + { + get => _value; + set + { + if (_parent.IsDisposed) + { + throw new ObjectDisposedException(null); + } + 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 PBEEffortValue(PBEEffortValues 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 PBEEffortValue[] _evs; + public PBEEffortValue 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; + } + } + + internal PBEEffortValues(PBESettings settings, BinaryReader 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; + Settings.PropertyChanged += OnSettingsChanged; + CreateEVs(hp, attack, defense, spAttack, spDefense, speed); + } + internal PBEEffortValues(PBESettings settings, JToken jToken) + { + byte hp = jToken[nameof(PBEStat.HP)].Value(); + byte attack = jToken[nameof(PBEStat.Attack)].Value(); + byte defense = jToken[nameof(PBEStat.Defense)].Value(); + byte spAttack = jToken[nameof(PBEStat.SpAttack)].Value(); + byte spDefense = jToken[nameof(PBEStat.SpDefense)].Value(); + byte speed = jToken[nameof(PBEStat.Speed)].Value(); + if (hp + attack + defense + spAttack + spDefense + speed > settings.MaxTotalEVs) + { + throw new ArgumentOutOfRangeException(nameof(PBEPokemonShell.EffortValues), $"Effort values total must not exceed \"{nameof(settings.MaxTotalEVs)}\" ({settings.MaxTotalEVs})"); + } + Settings = settings; + Settings.PropertyChanged += OnSettingsChanged; + CreateEVs(hp, attack, defense, spAttack, spDefense, speed); + } + internal PBEEffortValues(PBEEffortValues other) + { + Settings = other.Settings; + Settings.PropertyChanged += OnSettingsChanged; + CreateEVs(other[PBEStat.HP].Value, other[PBEStat.Attack].Value, other[PBEStat.Defense].Value, other[PBEStat.SpAttack].Value, other[PBEStat.SpDefense].Value, other[PBEStat.Speed].Value); + } + public PBEEffortValues(PBESettings settings, bool randomize) + { + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + Settings = settings; + Settings.PropertyChanged += OnSettingsChanged; + _canDispose = true; + CreateEVs(0, 0, 0, 0, 0, 0); + if (randomize) + { + Randomize(); + } + } + + private void CreateEVs(byte hp, byte attack, byte defense, byte spAttack, byte spDefense, byte speed) + { + _evs = new PBEEffortValue[6] + { + new PBEEffortValue(this, PBEStat.HP, hp), + new PBEEffortValue(this, PBEStat.Attack, attack), + new PBEEffortValue(this, PBEStat.Defense, defense), + new PBEEffortValue(this, PBEStat.SpAttack, spAttack), + new PBEEffortValue(this, PBEStat.SpDefense, spDefense), + new PBEEffortValue(this, PBEStat.Speed, speed) + }; + } + private void OnSettingsChanged(object sender, PropertyChangedEventArgs e) + { + switch (e.PropertyName) + { + case nameof(Settings.MaxTotalEVs): + { + ushort oldTotal = StatTotal; + if (oldTotal > Settings.MaxTotalEVs) + { + for (int i = 0; i < 6; i++) + { + PBEEffortValue ev = _evs[i]; + ev.Value = (byte)(Settings.MaxTotalEVs * ((double)ev.Value / oldTotal)); + } + } + break; + } + } + } + + private bool _canDispose; + public bool CanDispose + { + get => _canDispose; + internal set + { + if (_canDispose != value) + { + _canDispose = value; + OnPropertyChanged(nameof(CanDispose)); + } + } + } + public bool IsDisposed { get; private set; } + public void Dispose() + { + if (!_canDispose) + { + throw new InvalidOperationException(); + } + if (!IsDisposed) + { + IsDisposed = true; + OnPropertyChanged(nameof(IsDisposed)); + Settings.PropertyChanged -= OnSettingsChanged; + } + } + + public void Randomize() + { + if (IsDisposed) + { + throw new ObjectDisposedException(null); + } + if (Settings.MaxTotalEVs != 0) + { + byte[] vals = new byte[6]; + int[] a = Enumerable.Repeat(0, 6 - 1) + .Select(x => PBEUtils.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 = notMax.RandomElement(); + 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(); + } + + internal void ToBytes(List bytes) + { + for (int i = 0; i < 6; i++) + { + bytes.Add(_evs[i].Value); + } + } + internal void ToJson(JsonTextWriter w) + { + w.WriteStartObject(); + for (int i = 0; i < 6; i++) + { + PBEEffortValue ev = _evs[i]; + w.WritePropertyName(ev.Stat.ToString()); + w.WriteValue(ev.Value); + } + w.WriteEndObject(); + } + } +} diff --git a/PokemonBattleEngine/Data/Enums.cs b/PokemonBattleEngine/Data/Enums.cs index 2cdfdf11b..7d6d4e971 100644 --- a/PokemonBattleEngine/Data/Enums.cs +++ b/PokemonBattleEngine/Data/Enums.cs @@ -15,696 +15,380 @@ public enum PBEGender : byte /// Invalid gender. MAX } - /// - /// Represents a Pokémon species' ratio. - /// + /// Represents a Pokémon species' ratio. public enum PBEGenderRatio : byte { - /// - /// The species is 87.5% male, 12.5% female. - /// + /// The species is 87.5% male, 12.5% female. M7_F1 = 0x1F, - /// - /// The species is 75% male, 25% female. - /// + /// The species is 75% male, 25% female. M3_F1 = 0x3F, - /// - /// The species is 50% male, 50% female. - /// + /// The species is 50% male, 50% female. M1_F1 = 0x7F, - /// - /// The species is 25% male, 75% female. - /// + /// The species is 25% male, 75% female. M1_F3 = 0xBF, - /// - /// The species is 0% male, 100% female. - /// + /// The species is 0% male, 100% female. M0_F1 = 0xFE, - /// - /// The species is genderless. - /// + /// The species is genderless. M0_F0 = 0xFF, - /// - /// The species is 100% male, 0% female. - /// + /// The species is 100% male, 0% female. M1_F0 = 0x00 } - /// - /// Represents a Pokémon stat. - /// + /// Represents a Pokémon stat. public enum PBEStat : byte { - /// - /// Hit points. - /// - HP = 0, - /// - /// Attack. - /// - Attack = 1, - /// - /// Defense. - /// - Defense = 2, - /// - /// Special Attack. - /// - SpAttack = 3, - /// - /// Special Defense. - /// - SpDefense = 4, - /// - /// Speed. - /// - Speed = 5, - /// - /// Accuracy. - /// + /// Hit points. + HP, + /// Attack. + Attack, + /// Defense. + Defense, + /// Special Attack. + SpAttack, + /// Special Defense. + SpDefense, + /// Speed. + Speed, + /// Accuracy. Accuracy, - /// - /// Evasion. - /// + /// Evasion. Evasion } - /// - /// Represents the effectiveness of a move against a target. - /// + /// Represents the effectiveness of a move against a target. public enum PBEEffectiveness : byte { - /// - /// The move does not affect the target. - /// + /// The move does not affect the target. Ineffective, - /// - /// The move does less damage to the target. - /// + /// The move does less damage to the target. NotVeryEffective, - /// - /// The move affects the target as usual. - /// + /// The move affects the target as usual. Normal, - /// - /// The move does more damage to the target. - /// + /// The move does more damage to the target. SuperEffective } - /// - /// Represents the format of a specific battle. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// Invalid battle format. MAX } - /// - /// Represents the current state of a specific battle. - /// + /// Represents the current state of a specific battle. public enum PBEBattleState : byte { - /// - /// The battle is waiting for team shells. - /// + /// The battle is waiting for team shells. WaitingForPlayers, - /// - /// The battle is ready to begin. - /// + /// The battle is ready to begin. ReadyToBegin, - /// - /// The battle is waiting for players to send actions. - /// + /// The battle is waiting for players to send actions. WaitingForActions, - /// - /// The battle is ready to run a turn. - /// + /// The battle is ready to run a turn. ReadyToRunTurn, - /// - /// The battle is processing. - /// + /// The battle is processing. Processing, - /// - /// The battle is waiting for players to send switch-ins. - /// + /// The battle is waiting for players to send switch-ins. WaitingForSwitchIns, - /// - /// The battle ended. - /// + /// The battle ended. Ended } - /// - /// Represents the weather in a specific battle. - /// + /// Represents the weather in a specific battle. public enum PBEWeather : byte { - /// - /// There is no weather. - /// + /// There is no weather. None, - /// - /// It is hailing. - /// + /// It is hailing. Hailstorm, - /// - /// The sunlight is harsh. - /// + /// The sunlight is harsh. HarshSunlight, - /// - /// It is raining. - /// + /// It is raining. Rain, - /// - /// A sandstorm is brewing. - /// + /// A sandstorm is brewing. Sandstorm } - /// - /// Represents a position on the battle field. - /// + /// Represents a position on the battle field. public enum PBEFieldPosition : byte { - /// - /// A Pokémon is not on the field. - /// + /// A Pokémon is not on the field. None, - /// - /// The Pokémon to a player's left in a Double, Triple, or Rotation battle. - /// + /// 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. - /// + /// 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. - /// + /// The Pokémon to a player's right in a Double, Triple, or Rotation battle. Right } - /// - /// Represents a 's targets. - /// + /// Represents a 's targets. [Flags] - public enum PBETarget : byte + public enum PBETurnTarget : byte { - /// - /// The Pokémon has not chosen any targets. - /// + /// The Pokémon has not chosen any targets. None, - /// - /// The move is targetting the player's left Pokémon. - /// + /// The move is targetting the player's left Pokémon. AllyLeft = 1 << 0, - /// - /// The move is targetting the player's center Pokémon. - /// + /// The move is targetting the player's center Pokémon. AllyCenter = 1 << 1, - /// - /// The move is targetting the player's right Pokémon. - /// + /// The move is targetting the player's right Pokémon. AllyRight = 1 << 2, - /// - /// The move is targetting the opponent's left Pokémon. - /// + /// The move is targetting the opponent's left Pokémon. FoeLeft = 1 << 3, - /// - /// The move is targetting the opponent's center Pokémon. - /// + /// The move is targetting the opponent's center Pokémon. FoeCenter = 1 << 4, - /// - /// The move is targetting the opponent's right Pokémon. - /// + /// The move is targetting the opponent's right Pokémon. FoeRight = 1 << 5 } - /// - /// Represents a Pokémon's decision for a turn. - /// - public enum PBEDecision : byte + /// Represents a Pokémon's decision for a turn. + public enum PBETurnDecision : byte { - /// - /// The Pokémon has not made a decision. - /// + /// The Pokémon has not made a decision. None, - /// - /// The Pokémon has chosen to use a move. - /// + /// The Pokémon has chosen to use a move. Fight, - /// - /// The Pokémon has chosen to switch out for another Pokémon. - /// + /// The Pokémon has chosen to switch out for another Pokémon. SwitchOut } - /// - /// Represents a specific 's category. - /// + /// Represents a specific 's category. public enum PBEMoveCategory : byte { - /// - /// The move deals no damage. - /// + /// The move deals no damage. Status, - /// - /// The move deals physical damage using the Attack and Defense stats. - /// + /// 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. - /// + /// The move deals special damage using the Special Attack and Special Defense stats. Special, - /// - /// Invalid category. - /// + /// Invalid category. MAX } - /// - /// Represents the various methods in which a Pokémon can learn a . - /// + /// Represents the various methods in which a Pokémon can learn a . [Flags] public enum PBEMoveObtainMethod : ulong { - /// - /// There is no way to learn this move. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// The move is known by a Pokémon when found in the Dream World. DreamWorld = 1uL << 30, - /// - /// The move can be learned by hatching a Pokémon egg under special conditions. - /// + /// The move can be learned by hatching a Pokémon egg under special conditions. EggMove_Special = 1uL << 31, - /// - /// The move is learned by a Pokémon when changing forms. - /// + /// The move is learned by a Pokémon when changing forms. Form = 1uL << 32 } - /// - /// Represents a specific Pokémon's non-volatile status. - /// + /// Represents a specific Pokémon's non-volatile status. public enum PBEStatus1 : byte { - /// - /// The Pokémon has no status. - /// + /// The Pokémon has no status. None, - /// - /// The Pokémon is asleep. - /// + /// The Pokémon is asleep. Asleep, - /// - /// The Pokémon is badly poisoned. - /// + /// The Pokémon is badly poisoned. BadlyPoisoned, - /// - /// The Pokémon is burned. - /// + /// The Pokémon is burned. Burned, - /// - /// The Pokémon is frozen. - /// + /// The Pokémon is frozen. Frozen, - /// - /// The Pokémon is paralyzed. - /// + /// The Pokémon is paralyzed. Paralyzed, - /// - /// The Pokémon is poisoned. - /// + /// The Pokémon is poisoned. Poisoned } - /// - /// Represents a specific Pokémon's volatile status. - /// + /// Represents a specific Pokémon's volatile status. [Flags] public enum PBEStatus2 : uint { - /// - /// The Pokémon has no status. - /// + /// 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 . + /// 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. - /// + /// 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. - /// + /// The Pokémon is cursed and will take damage at the end of each turn. Cursed = 1 << 2, - /// - /// The Pokémon is disguised as with . - /// + /// The Pokémon is disguised as with . Disguised = 1 << 3, - /// - /// The Pokémon is flinching and will be unable to move this turn. - /// + /// 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 . - /// + /// The Pokémon will gain a power boost due to . HelpingHand = 1 << 5, - /// - /// The Pokémon is infatuated with and may be unable to move this turn. - /// + /// The Pokémon is infatuated with and may be unable to move this turn. Infatuated = 1 << 6, - /// - /// The Pokémon is seeded and HP will be stolen at the end of each turn. - /// + /// The Pokémon is seeded and HP will be stolen at the end of each turn. LeechSeed = 1 << 7, - /// - /// The Pokémon is protected from moves this turn. - /// + /// The Pokémon is protected from moves this turn. Protected = 1 << 8, - /// - /// The Pokémon is under the effect of or and has a higher chance of landing critical hits. - /// + /// The Pokémon is under the effect of or and has a higher chance of landing critical hits. Pumped = 1 << 9, - /// - /// 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. - /// + /// 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 << 10, - /// - /// The Pokémon is unable to use the same move two times in a row. - /// + /// The Pokémon is unable to use the same move two times in a row. Tormented = 1 << 11, // TODO - /// - /// The Pokémon is transformed into another Pokémon. - /// + /// The Pokémon is transformed into another Pokémon. Transformed = 1 << 12, - /// - /// The Pokémon is underground. - /// A move will miss against the Pokémon unless it has or either Pokémon has . + /// The Pokémon is underground.A move will miss against the Pokémon unless it has or either Pokémon has . /// The Pokémon will take double damage from and . /// Underground = 1 << 13, - /// - /// The Pokémon is underwater. - /// A move will miss against the Pokémon unless it has or either Pokémon has . + /// The Pokémon is underwater.A move will miss against the Pokémon unless it has or either Pokémon has . /// The Pokémon will take double damage from and . /// Underwater = 1 << 14 } - /// - /// Represents a specific 's status. - /// + /// Represents a specific 's status. [Flags] public enum PBEBattleStatus : byte { - /// - /// The battle has no status. - /// + /// The battle has no status. None, - /// - /// The acting order of Pokémon in this battle is reversed. - /// + /// 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. - /// + /// Represents a specific 's status. [Flags] public enum PBETeamStatus : byte { - /// - /// The team has no status. - /// + /// The team has no status. None, - /// - /// The team will take less damage from moves. - /// + /// The team will take less damage from moves. LightScreen = 1 << 0, - /// - /// The team is shielded from critical hits. - /// + /// The team is shielded from critical hits. LuckyChant = 1 << 1, - /// - /// The team will take less damage from moves. - /// + /// The team will take less damage from moves. Reflect = 1 << 2, - /// - /// Grounded Pokémon that switch in will take damage. - /// The amount of damage is based on . + /// Grounded Pokémon that switch in will take damage.The amount of damage is based on . /// Spikes = 1 << 3, // TODO: Gravity, magnet rise, 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. + /// Pokémon that switch in will take damage.The amount of damage is based on the effectiveness of on the Pokémon. /// StealthRock = 1 << 4, // TODO: magic guard, castform transforms after taking damage - /// - /// Grounded Pokémon that switch in will be if is 1 or if it is 2. - /// Grounded Pokémon will remove toxic spikes. + /// 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 << 5, // TODO: Gravity, immunity, leaf guard, magic guard, iron ball, baton pass with ingrain, air balloon, synchronize with roar/whirlwind - /// - /// The team is protected from spread moves for a turn. - /// + /// The team is protected from spread moves for a turn. WideGuard = 1 << 6 } - /// - /// Represents an action regarding a . - /// + /// Represents an action regarding a . public enum PBEAbilityAction : byte { - /// - /// The ability was changed. - /// + /// The ability was changed. Changed = 0, - /// - /// The ability caused a Pokémon to change its appearance. - /// + /// The ability caused a Pokémon to change its appearance. ChangedAppearance = 1, - /// - /// The ability changed a Pokémon stats. - /// + /// The ability changed a Pokémon stats. ChangedStats = 2, - /// - /// The ability changed a Pokémon's or . - /// + /// The ability changed a Pokémon's or . ChangedStatus = 3, - /// - /// The ability was involved with damage. - /// + /// The ability was involved with damage. Damage = 4, - /// - /// The ability prevented a Pokémon from being inflicted with a or . - /// + /// The ability prevented a Pokémon from being inflicted with a or . PreventedStatus = 5, - /// - /// The ability restored a Pokémon's HP. - /// + /// The ability restored a Pokémon's HP. RestoredHP = 6, - /// - /// The ability was involved with weather. - /// + /// The ability was involved with weather. Weather = 7 } - /// - /// Represents an action regarding a . - /// + /// Represents an action regarding a . public enum PBEItemAction : byte { - /// - /// The item caused a Pokémon's or to change. - /// + /// The item caused a Pokémon's or to change. ChangedStatus = 0, - /// - /// The item was consumed by a Pokémon. - /// + /// The item was consumed by a Pokémon. Consumed = 1, - /// - /// The item was involved with damage. - /// + /// The item was involved with damage. Damage = 2, - /// - /// The item restored HP to a Pokémon. - /// + /// The item restored HP to a Pokémon. RestoredHP = 3 } - /// - /// Represents an action regarding a or . - /// + /// Represents an action regarding a or . public enum PBEStatusAction : byte { - /// - /// The status activated its main effect. - /// + /// The status activated its main effect. /// /// prevented movement. /// Activated = 0, - /// - /// The status was added to a Pokémon. - /// + /// The status was added to a Pokémon. /// /// The Pokémon became . /// Added = 1, - /// - /// The status caused a Pokémon to be immobile. - /// + /// The status caused a Pokémon to be immobile. CausedImmobility = 2, - /// - /// The status was cured from a Pokémon. - /// + /// The status was cured from a Pokémon. /// /// cured a Pokémon of . /// Cured = 3, - /// - /// The status was involved with damage. - /// + /// The status was involved with damage. /// /// A Pokémon's took damage. /// Damage = 4, - /// - /// The status has ended. - /// + /// The status has ended. /// /// A Pokémon with regained its senses. /// @@ -716,110 +400,66 @@ public enum PBEBattleStatusAction : byte Cleared = 1, Ended = 2 } - /// - /// Represents an action regarding a . - /// + /// Represents an action regarding a . public enum PBETeamStatusAction : byte { - /// - /// The status was added to a team. - /// + /// The status was added to a team. /// /// The team set up . /// Added = 0, - /// - /// The status was removed from a team. - /// + /// The status was removed from a team. /// /// An opponent used and destroyed . /// Cleared = 1, - /// - /// The status caused a Pokémon to take damage. - /// + /// The status caused a Pokémon to take damage. /// /// An Pokémon switched in and took damage from . /// Damage = 2, - /// - /// The status ended. - /// + /// The status ended. /// /// wore off. /// Ended = 3 } - /// - /// Represents the reason for a move failing. - /// + /// Represents the reason for a move failing. public enum PBEFailReason : byte { - /// - /// The move did not fail. - /// + /// The move did not fail. None = 0, - /// - /// The move failed because the target already has . - /// + /// The move failed because the target already has . AlreadyAsleep = 1, - /// - /// The move failed because the target already has . - /// + /// The move failed because the target already has . AlreadyBurned = 2, - /// - /// The move failed because the target already has . - /// + /// The move failed because the target already has . AlreadyConfused = 3, - /// - /// The move failed because the target already has . - /// + /// The move failed because the target already has . AlreadyParalyzed = 4, - /// - /// The move failed because the target already has or . - /// + /// The move failed because the target already has or . AlreadyPoisoned = 5, - /// - /// The move failed because the target already has . - /// + /// The move failed because the target already has . AlreadySubstituted = 6, - /// - /// General failure. - /// + /// General failure. Default = 7, - /// - /// The move tried to heal a Pokémon's HP when it was already full. - /// + /// The move tried to heal a Pokémon's HP when it was already full. HPFull = 8, - /// - /// The move failed because the Pokémon was immune to the move. - /// + /// The move failed because the Pokémon was immune to the move. Ineffective = 9, - /// - /// The move was used when there were no available targets to hit. - /// + /// The move was used when there were no available targets to hit. NoTarget = 10, - /// - /// The one-hit-knockout move failed because the target was a higher level than the user. - /// + /// The one-hit-knockout move failed because the target was a higher level than the user. OneHitKnockoutUnaffected = 11 } - /// - /// Represents an action regarding a . - /// + /// Represents an action regarding a . public enum PBEWeatherAction : byte { - /// - /// The weather was added to the battle. - /// + /// The weather was added to the battle. Added = 0, - /// - /// The weather caused a Pokémon to take damage. - /// + /// The weather caused a Pokémon to take damage. CausedDamage = 1, - /// - /// The weather was removed from the battle. - /// + /// The weather was removed from the battle. Ended = 2 } public enum PBESpecialMessage : byte @@ -857,7 +497,8 @@ public enum PBEType : byte Psychic, Rock, Steel, - Water + Water, + MAX } /// Represents a specific Pokémon's nature. public enum PBENature : byte @@ -915,132 +556,80 @@ public enum PBENature : byte /// Invalid nature. MAX = 25 } - /// - /// Represents a specific Pokémon's held item. - /// + /// Represents a specific Pokémon's held item. public enum PBEItem : ushort { - /// - /// No item. - /// + /// No item. None = 0, AbsorbBulb = 545, // TODO AdamantOrb = 135, AguavBerry = 162, // TODO AirBalloon = 541, // TODO - /// - /// No effect. - /// + /// No effect. AmuletCoin = 223, - /// - /// No effect. - /// + /// No effect. Antidote = 18, ApicotBerry = 205, // TODO - /// - /// No effect. - /// + /// No effect. ArmorFossil = 104, AspearBerry = 153, // TODO - /// - /// No effect. - /// + /// No effect. Awakening = 21, BabiriBerry = 199, // TODO - /// - /// No effect. - /// + /// No effect. BalmMushroom = 580, BelueBerry = 183, // TODO BerryJuice = 43, // TODO - /// - /// No effect. - /// + /// No effect. BigMushroom = 87, - /// - /// No effect. - /// + /// No effect. BigNugget = 581, - /// - /// No effect. - /// + /// No effect. BigPearl = 89, BigRoot = 296, BindingBand = 544, // TODO BlackBelt = 241, - /// - /// No effect. - /// + /// No effect. BlackFlute = 68, BlackGlasses = 240, BlackSludge = 281, - /// - /// No effect. - /// + /// No effect. BlkApricorn = 491, - /// - /// No effect. - /// + /// No effect. BluApricorn = 486, - /// - /// No effect. - /// + /// No effect. BlueFlute = 65, - /// - /// No effect. - /// + /// No effect. BlueScarf = 261, - /// - /// No effect. - /// + /// No effect. BlueShard = 73, BlukBerry = 165, // TODO - /// - /// No effect. - /// + /// No effect. BridgeMailD = 145, - /// - /// No effect. - /// + /// No effect. BridgeMailM = 148, - /// - /// No effect. - /// + /// No effect. BridgeMailS = 144, - /// - /// No effect. - /// + /// No effect. BridgeMailT = 146, - /// - /// No effect. - /// + /// No effect. BridgeMailV = 147, BrightPowder = 213, BugGem = 558, BurnDrive = 118, - /// - /// No effect. - /// + /// No effect. BurnHeal = 19, - /// - /// No effect. - /// + /// No effect. Calcium = 49, - /// - /// No effect. - /// + /// No effect. Carbos = 48, - /// - /// No effect. - /// + /// No effect. Casteliacone = 591, CellBattery = 546, // TODO Charcoal = 249, ChartiBerry = 195, // TODO CheriBerry = 149, // TODO - /// - /// No effect. - /// + /// No effect. CherishBall = 16, ChestoBerry = 150, // TODO ChilanBerry = 200, // TODO @@ -1049,264 +638,158 @@ public enum PBEItem : ushort ChoiceScarf = 287, ChoiceSpecs = 297, ChopleBerry = 189, // TODO - /// - /// No effect. - /// + /// No effect. ClawFossil = 100, - /// - /// No effect. - /// + /// No effect. CleanseTag = 224, - /// - /// No effect. - /// + /// No effect. CleverWing = 569, CobaBerry = 192, // TODO ColburBerry = 198, // TODO - /// - /// No effect. - /// + /// No effect. CometShard = 583, CornnBerry = 175, // TODO - /// - /// No effect. - /// + /// No effect. CoverFossil = 572, CustapBerry = 210, // TODO - /// - /// No effect. - /// + /// No effect. DampMulch = 96, DampRock = 285, DarkGem = 562, - /// - /// No effect. - /// + /// No effect. DawnStone = 109, DeepSeaScale = 227, DeepSeaTooth = 226, DestinyKnot = 280, - /// - /// No effect. - /// + /// No effect. DireHit = 56, - /// - /// No effect. - /// + /// No effect. DiveBall = 7, - /// - /// No effect. - /// + /// No effect. DomeFossil = 102, DouseDrive = 116, DracoPlate = 311, DragonFang = 250, DragonGem = 561, - /// - /// No effect. - /// + /// No effect. DragonScale = 235, DreadPlate = 312, - /// - /// No effect. - /// + /// No effect. DreamBall = 576, - /// - /// No effect. - /// + /// No effect. DubiousDisc = 324, DurinBerry = 182, // TODO - /// - /// No effect. - /// + /// No effect. DuskBall = 13, - /// - /// No effect. - /// + /// No effect. DuskStone = 108, EarthPlate = 305, EjectButton = 547, // TODO - /// - /// No effect. - /// + /// No effect. Electirizer = 322, ElectricGem = 550, - /// - /// No effect. - /// + /// No effect. Elixir = 40, - /// - /// No effect. - /// + /// No effect. EnergyPowder = 34, - /// - /// No effect. - /// + /// No effect. EnergyRoot = 35, EnigmaBerry = 208, // TODO - /// - /// No effect. - /// + /// No effect. EscapeRope = 78, - /// - /// No effect. - /// + /// No effect. Ether = 38, - /// - /// No effect. - /// + /// No effect. Everstone = 229, Eviolite = 538, ExpertBelt = 268, - /// - /// No effect. - /// + /// No effect. ExpShare = 216, - /// - /// No effect. - /// + /// No effect. FastBall = 492, - /// - /// No effect. - /// + /// No effect. FavoredMail = 138, FightingGem = 553, FigyBerry = 159, // TODO FireGem = 548, - /// - /// No effect. - /// + /// 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 . - /// + /// 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 - /// - /// No effect. - /// + /// No effect. FluffyTail = 64, FlyingGem = 556, FocusBand = 230, FocusSash = 275, - /// - /// No effect. - /// + /// No effect. FreshWater = 30, - /// - /// No effect. - /// + /// No effect. FriendBall = 497, - /// - /// No effect. - /// + /// No effect. FullHeal = 27, FullIncense = 316, // TODO - /// - /// No effect. - /// + /// No effect. FullRestore = 23, GanlonBerry = 202, // TODO - /// - /// No effect. - /// + /// No effect. GeniusWing = 568, GhostGem = 560, - /// - /// No effect. - /// + /// No effect. GooeyMulch = 98, GrassGem = 551, - /// - /// No effect. - /// + /// No effect. GreatBall = 3, - /// - /// No effect. - /// + /// No effect. GreenScarf = 263, - /// - /// No effect. - /// + /// No effect. GreenShard = 75, - /// - /// No effect. - /// + /// No effect. GreetMail = 137, GrepaBerry = 173, // TODO GripClaw = 286, // TODO GriseousOrb = 112, - /// - /// No effect. - /// + /// No effect. GrnApricorn = 488, GroundGem = 555, - /// - /// No effect. - /// + /// No effect. GrowthMulch = 95, - /// - /// No effect. - /// + /// No effect. GuardSpec = 55, HabanBerry = 197, // TODO HardStone = 238, - /// - /// No effect. - /// + /// No effect. HealBall = 14, - /// - /// No effect. - /// + /// No effect. HealPowder = 36, - /// - /// No effect. - /// + /// No effect. HealthWing = 565, - /// - /// No effect. - /// + /// No effect. HeartScale = 93, HeatRock = 284, - /// - /// No effect. - /// + /// No effect. HeavyBall = 495, - /// - /// No effect. - /// + /// No effect. HelixFossil = 101, HondewBerry = 172, // TODO - /// - /// No effect. - /// + /// No effect. Honey = 94, - /// - /// No effect. - /// + /// No effect. HPUp = 45, - /// - /// No effect. - /// + /// No effect. HyperPotion = 25, IapapaBerry = 163, // TODO IceGem = 552, - /// - /// No effect. - /// + /// No effect. IceHeal = 20, IciclePlate = 302, IcyRock = 282, - /// - /// No effect. - /// + /// No effect. InquiryMail = 141, InsectPlate = 308, - /// - /// No effect. - /// + /// No effect. Iron = 47, IronBall = 278, // TODO IronPlate = 313, @@ -1317,87 +800,53 @@ public enum PBEItem : ushort KingsRock = 221, // TODO LaggingTail = 279, // TODO LansatBerry = 206, // TODO - /// - /// No effect. - /// + /// No effect. LavaCookie = 42, LaxIncense = 255, - /// - /// No effect. - /// + /// No effect. LeafStone = 85, Leftovers = 234, - /// - /// No effect. - /// + /// No effect. Lemonade = 32, LeppaBerry = 154, // TODO - /// - /// No effect. - /// + /// No effect. LevelBall = 493, LiechiBerry = 201, // TODO LifeOrb = 270, LightBall = 236, LightClay = 269, - /// - /// No effect. - /// + /// No effect. LikeMail = 142, - /// - /// No effect. - /// + /// No effect. LoveBall = 496, - /// - /// No effect. - /// + /// No effect. LuckIncense = 319, - /// - /// No effect. - /// + /// No effect. LuckyEgg = 231, LuckyPunch = 256, LumBerry = 157, // TODO - /// - /// No effect. - /// + /// No effect. LureBall = 494, LustrousOrb = 136, - /// - /// No effect. - /// + /// No effect. LuxuryBall = 11, MachoBrace = 215, - /// - /// No effect. - /// + /// No effect. Magmarizer = 323, Magnet = 242, MagoBerry = 161, // TODO MagostBerry = 176, // TODO - /// - /// No effect. - /// + /// No effect. MasterBall = 1, - /// - /// No effect. - /// + /// No effect. MaxElixir = 41, - /// - /// No effect. - /// + /// No effect. MaxEther = 39, - /// - /// No effect. - /// + /// No effect. MaxPotion = 24, - /// - /// No effect. - /// + /// No effect. MaxRepel = 77, - /// - /// No effect. - /// + /// No effect. MaxRevive = 29, MeadowPlate = 301, MentalHerb = 219, // TODO @@ -1407,116 +856,70 @@ public enum PBEItem : ushort MicleBerry = 209, // TODO MindPlate = 307, MiracleSeed = 239, - /// - /// No effect. - /// + /// No effect. MoomooMilk = 33, - /// - /// No effect. - /// + /// No effect. MoonBall = 498, - /// - /// No effect. - /// + /// No effect. MoonStone = 81, MuscleBand = 266, - /// - /// No effect. - /// + /// No effect. MuscleWing = 566, MysticWater = 243, NanabBerry = 166, // TODO - /// - /// No effect. - /// + /// No effect. NestBall = 8, - /// - /// No effect. - /// + /// No effect. NetBall = 6, NeverMeltIce = 246, NomelBerry = 178, // TODO NormalGem = 564, - /// - /// No effect. - /// + /// No effect. Nugget = 92, OccaBerry = 184, // TODO OddIncense = 314, - /// - /// No effect. - /// + /// No effect. OddKeystone = 111, - /// - /// No effect. - /// + /// No effect. OldAmber = 103, - /// - /// No effect. - /// + /// No effect. OldGateau = 54, OranBerry = 155, // TODO - /// - /// No effect. - /// + /// No effect. OvalStone = 110, PamtreBerry = 180, // TODO - /// - /// No effect. - /// + /// No effect. ParalyzHeal = 22, - /// - /// No effect. - /// + /// No effect. ParkBall = 500, PasshoBerry = 185, // TODO - /// - /// No effect. - /// + /// No effect. PassOrb = 575, PayapaBerry = 193, // TODO - /// - /// No effect. - /// + /// No effect. Pearl = 88, - /// - /// No effect. - /// + /// No effect. PearlString = 582, PechaBerry = 151, // TODO PersimBerry = 156, // TODO PetayaBerry = 204, // TODO PinapBerry = 168, // TODO - /// - /// No effect. - /// + /// No effect. PinkScarf = 262, - /// - /// No effect. - /// + /// No effect. PlumeFossil = 573, - /// - /// No effect. - /// + /// No effect. PnkApricorn = 489, PoisonBarb = 245, PoisonGem = 554, - /// - /// No effect. - /// + /// No effect. PokeBall = 4, - /// - /// No effect. - /// + /// No effect. PokeDoll = 63, - /// - /// No effect. - /// + /// No effect. PokeToy = 577, PomegBerry = 169, // TODO - /// - /// No effect. - /// + /// No effect. Potion = 17, PowerAnklet = 293, PowerBand = 292, @@ -1525,158 +928,90 @@ public enum PBEItem : ushort PowerHerb = 271, PowerLens = 291, PowerWeight = 294, - /// - /// No effect. - /// + /// No effect. PPMax = 53, - /// - /// No effect. - /// + /// No effect. PPUp = 51, - /// - /// No effect. - /// + /// No effect. PremierBall = 12, - /// - /// No effect. - /// + /// No effect. PrettyWing = 571, - /// - /// No effect. - /// + /// No effect. PrismScale = 537, - /// - /// No effect. - /// + /// No effect. Protector = 321, - /// - /// No effect. - /// + /// No effect. Protein = 46, PsychicGem = 557, - /// - /// No effect. - /// + /// No effect. PureIncense = 320, QualotBerry = 171, // TODO - /// - /// No effect. - /// + /// No effect. QuickBall = 15, QuickClaw = 217, // TODO QuickPowder = 274, RabutaBerry = 177, // TODO - /// - /// No effect. - /// + /// No effect. RageCandyBar = 504, - /// - /// No effect. - /// + /// No effect. RareBone = 106, - /// - /// No effect. - /// + /// No effect. RareCandy = 50, RawstBerry = 152, // TODO RazorClaw = 326, RazorFang = 327, // TODO RazzBerry = 164, // TODO - /// - /// No effect. - /// + /// No effect. ReaperCloth = 325, - /// - /// No effect. - /// + /// No effect. RedApricorn = 485, RedCard = 542, // TODO - /// - /// No effect. - /// + /// No effect. RedFlute = 67, - /// - /// No effect. - /// + /// No effect. RedScarf = 260, - /// - /// No effect. - /// + /// No effect. RedShard = 72, - /// - /// No effect. - /// + /// No effect. RelicBand = 588, - /// - /// No effect. - /// + /// No effect. RelicCopper = 584, - /// - /// No effect. - /// + /// No effect. RelicCrown = 590, - /// - /// No effect. - /// + /// No effect. RelicGold = 586, - /// - /// No effect. - /// + /// No effect. RelicSilver = 585, - /// - /// No effect. - /// + /// No effect. RelicStatue = 589, - /// - /// No effect. - /// + /// No effect. RelicVase = 587, - /// - /// No effect. - /// + /// No effect. RepeatBall = 9, - /// - /// No effect. - /// + /// No effect. Repel = 79, - /// - /// No effect. - /// + /// No effect. ReplyMail = 143, - /// - /// No effect. - /// + /// No effect. ResistWing = 567, - /// - /// No effect. - /// + /// No effect. RevivalHerb = 37, - /// - /// No effect. - /// + /// No effect. Revive = 28, RindoBerry = 187, // TODO RingTarget = 543, // TODO RockGem = 559, RockIncense = 315, RockyHelmet = 540, - /// - /// No effect. - /// + /// No effect. RootFossil = 99, RoseIncense = 318, RowapBerry = 212, // TODO - /// - /// No effect. - /// + /// No effect. RSVPMail = 139, - /// - /// No effect. - /// + /// No effect. SacredAsh = 44, - /// - /// No effect. - /// + /// No effect. SafariBall = 5, SalacBerry = 203, // TODO ScopeLens = 232, @@ -1684,199 +1019,121 @@ public enum PBEItem : ushort SharpBeak = 244, ShedShell = 295, // TODO ShellBell = 253, // TODO - /// - /// No effect. - /// + /// No effect. ShinyStone = 107, - /// - /// No effect. - /// + /// No effect. ShoalSalt = 70, - /// - /// No effect. - /// + /// No effect. ShoalShell = 71, ShockDrive = 117, ShucaBerry = 191, // TODO SilkScarf = 251, SilverPowder = 222, SitrusBerry = 158, - /// - /// No effect. - /// + /// No effect. SkullFossil = 105, SkyPlate = 306, - /// - /// No effect. - /// + /// No effect. SmokeBall = 228, SmoothRock = 283, - /// - /// No effect. - /// + /// No effect. SodaPop = 31, SoftSand = 237, - /// - /// No effect. - /// + /// No effect. SootheBell = 218, SoulDew = 225, SpellTag = 247, SpelonBerry = 179, // TODO SplashPlate = 299, SpookyPlate = 310, - /// - /// No effect. - /// + /// No effect. SportBall = 499, - /// - /// No effect. - /// + /// No effect. StableMulch = 97, - /// - /// No effect. - /// + /// No effect. Stardust = 90, StarfBerry = 207, // TODO - /// - /// No effect. - /// + /// No effect. StarPiece = 91, SteelGem = 563, Stick = 259, StickyBarb = 288, // TODO StonePlate = 309, - /// - /// No effect. - /// + /// No effect. SunStone = 80, - /// - /// No effect. - /// + /// No effect. SuperPotion = 26, - /// - /// No effect. - /// + /// No effect. SuperRepel = 76, - /// - /// No effect. - /// + /// No effect. SweetHeart = 134, - /// - /// No effect. - /// + /// No effect. SwiftWing = 570, TamatoBerry = 174, // TODO TangaBerry = 194, // TODO - /// - /// No effect. - /// + /// No effect. ThanksMail = 140, ThickClub = 258, - /// - /// No effect. - /// + /// No effect. Thunderstone = 83, - /// - /// No effect. - /// + /// No effect. TimerBall = 10, - /// - /// No effect. - /// + /// 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 . - /// + /// 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, - /// - /// No effect. - /// + /// No effect. UltraBall = 2, UpGrade = 252, WacanBerry = 186, // TODO WaterGem = 549, - /// - /// No effect. - /// + /// No effect. WaterStone = 84, WatmelBerry = 181, // TODO WaveIncense = 317, WepearBerry = 167, // TODO WhiteHerb = 214, // TODO - /// - /// No effect. - /// + /// No effect. WhiteFlute = 69, WideLens = 265, WikiBerry = 160, // TODO WiseGlasses = 267, - /// - /// No effect. - /// + /// No effect. WhtApricorn = 490, - /// - /// No effect. - /// + /// No effect. XAccuracy = 60, - /// - /// No effect. - /// + /// No effect. XAttack = 57, - /// - /// No effect. - /// + /// No effect. XDefend = 58, - /// - /// No effect. - /// + /// No effect. XSpecial = 61, - /// - /// No effect. - /// + /// No effect. XSpDef = 62, - /// - /// No effect. - /// + /// No effect. XSpeed = 59, YacheBerry = 188, // TODO - /// - /// No effect. - /// + /// No effect. YellowFlute = 66, - /// - /// No effect. - /// + /// No effect. YellowScarf = 264, - /// - /// No effect. - /// + /// No effect. YellowShard = 74, - /// - /// No effect. - /// + /// No effect. YlwApricorn = 487, ZapPlate = 300, - /// - /// No effect. - /// + /// No effect. Zinc = 52, ZoomLens = 276 // TODO } - /// - /// Represents a specific Pokémon's special ability. - /// + /// Represents a specific Pokémon's special ability. public enum PBEAbility : byte { - /// - /// The Pokémon's ability was suppressed with . - /// + /// The Pokémon's ability was suppressed with . None = 0, - /// - /// The Pokémon has a stronger same-type-attack-bonus. - /// + /// The Pokémon has a stronger same-type-attack-bonus. Adaptability = 91, Aftermath = 106, // TODO AirLock = 76, @@ -1885,25 +1142,17 @@ public enum PBEAbility : byte Anticipation = 107, // TODO ArenaTrap = 71, // TODO BadDreams = 123, // TODO - /// - /// The Pokémon takes no critical hits. - /// + /// The Pokémon takes no critical hits. BattleArmor = 4, BigPecks = 145, // TODO - /// - /// When the Pokémon has low HP, its Fire-type moves get a power boost. - /// + /// 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. - /// + /// The Pokémon gets a speed boost in harsh sunlight. Chlorophyll = 34, ClearBody = 29, // TODO CloudNine = 13, ColorChange = 16, // TODO - /// - /// The Pokémon accuracy is boosted. - /// + /// The Pokémon accuracy is boosted. Compoundeyes = 14, Contrary = 126, // TODO CursedBody = 130, // TODO @@ -1912,20 +1161,14 @@ public enum PBEAbility : byte Defeatist = 129, Defiant = 128, // TODO Download = 88, // TODO - /// - /// The Pokémon changes the weather to infinite rain. - /// + /// The Pokémon changes the weather to infinite rain. Drizzle = 2, - /// - /// The Pokémon changes the weather to infinite harsh sunlight. - /// + /// 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. - /// + /// The Pokémon takes less damage from incoming super-effective moves. Filter = 111, FlameBody = 49, FlareBoost = 138, // TODO @@ -1936,76 +1179,52 @@ public enum PBEAbility : byte FriendGuard = 132, // TODO Frisk = 119, // TODO Gluttony = 82, // TODO - /// - /// The Pokémon's attack is boosted when it is afflicted with a and the damage reduction from is not applied. - /// + /// 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. - /// + /// The Pokémon takes less damage from Fire-type moves and from a burn. Heatproof = 85, HeavyMetal = 134, // TODO - /// - /// No effect in battle. - /// + /// No effect in battle. HoneyGather = 118, - /// - /// The Pokémon's attack is boosted. - /// + /// The Pokémon's attack is boosted. HugePower = 37, - /// - /// The Pokémon's attack is boosted, but its accuracy is lower for physical moves. - /// + /// The Pokémon's attack is boosted, but its accuracy is lower for physical moves. Hustle = 55, Hydration = 93, // TODO HyperCutter = 52, // TODO - /// - /// In a hailstorm, the Pokémon takes no damage from the hailstorm and restores HP at the end of each turn. - /// + /// 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. - /// + /// No effect in battle. Illuminate = 35, Illusion = 149, Immunity = 17, - /// - /// The Pokémon transforms into the foe across from it when switching in. - /// + /// The Pokémon transforms into the foe across from it when switching in. Imposter = 150, Infiltrator = 151, // TODO InnerFocus = 39, Insomnia = 15, Intimidate = 22, // TODO IronBarbs = 160, - /// - /// The power of moves with is increased. - /// + /// The power of moves with is increased. IronFist = 89, Justified = 154, KeenEye = 51, // TODO Klutz = 103, // TODO LeafGuard = 102, // TODO - /// - /// The Pokémon is immune to Ground-type moves and most entry hazards. - /// + /// 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. - /// + /// 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 . - /// + /// The Pokémon's defense is boosted when it is afflicted with a . MarvelScale = 63, Minus = 58, MoldBreaker = 104, // TODO @@ -2013,25 +1232,17 @@ public enum PBEAbility : byte MotorDrive = 78, // TODO Moxie = 153, // TODO Multiscale = 136, // TODO - /// - /// No effect in battle. - /// + /// No effect in battle. Multitype = 121, Mummy = 152, NaturalCure = 30, - /// - /// The Pokémon will always hit and always get hit unless protection is used. - /// + /// 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. - /// + /// 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. - /// + /// When the Pokémon has low HP, its Grass-type moves get a power boost. Overgrow = 65, OwnTempo = 20, Pickpocket = 124, // TODO @@ -2042,14 +1253,10 @@ public enum PBEAbility : byte PoisonTouch = 143, // TODO Prankster = 158, Pressure = 46, // TODO - /// - /// The Pokémon's attack is boosted. - /// + /// The Pokémon's attack is boosted. PurePower = 74, QuickFeet = 95, - /// - /// In rain, the Pokémon restores HP at the end of each turn. - /// + /// In rain, the Pokémon restores HP at the end of each turn. RainDish = 44, Rattled = 155, Reckless = 120, @@ -2057,25 +1264,15 @@ public enum PBEAbility : byte Rivalry = 79, // TODO RockHead = 69, RoughSkin = 24, - /// - /// No effect in battle. - /// + /// No effect in battle. 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. - /// + /// 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. - /// + /// The Pokémon gets a speed boost in a sandstorm. SandRush = 146, - /// - /// The Pokémon changes the weather to an infinite sandstorm. - /// + /// 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. - /// + /// 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, // TODO @@ -2083,36 +1280,22 @@ public enum PBEAbility : byte ShadowTag = 23, // TODO ShedSkin = 61, SheerForce = 125, // TODO - /// - /// The Pokémon takes no critical hits. - /// + /// The Pokémon takes no critical hits. ShellArmor = 75, ShieldDust = 19, // TODO - /// - /// The Pokémon's stat changes are doubled. - /// + /// The Pokémon's stat changes are doubled. Simple = 86, SkillLink = 92, // TODO SlowStart = 112, // TODO - /// - /// The Pokémon deals more damage when landing critical hits. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// 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. - /// + /// The Pokémon takes less damage from incoming super-effective moves. SolidRock = 116, Soundproof = 43, // TODO SpeedBoost = 3, @@ -2124,34 +1307,22 @@ public enum PBEAbility : byte StormDrain = 114, // TODO Sturdy = 5, SuctionCups = 21, // TODO - /// - /// The Pokémon is more likely to land critical hits. - /// + /// 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. - /// + /// 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. - /// + /// The Pokémon gets a speed boost in rain. SwiftSwim = 33, Synchronize = 28, // TODO TangledFeet = 77, Technician = 101, // TODO Telepathy = 140, // TODO Teravolt = 164, // TODO - /// - /// The Pokémon takes less damage from Ice- and Fire-type moves. - /// + /// 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. - /// + /// 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. - /// + /// When the Pokémon has low HP, its Water-type moves get a power boost. Torrent = 67, ToxicBoost = 137, Trace = 36, // TODO @@ -2167,20 +1338,14 @@ public enum PBEAbility : byte WaterVeil = 41, WeakArmor = 133, WhiteSmoke = 73, // TODO - /// - /// The Pokémon is immune to all damaging moves except for moves that would deal super-effective damage. - /// + /// The Pokémon is immune to all damaging moves except for moves that would deal super-effective damage. WonderGuard = 25, // TODO: Mold Breaker, Teravolt, Turboblaze, Role Play, Skill Swap WonderSkin = 147, ZenMode = 161, // TODO - /// - /// Invalid ability. - /// + /// Invalid ability. MAX = 165, } - /// - /// Represents a specific Pokémon species. - /// + /// Represents a specific Pokémon species. public enum PBESpecies : uint { Abomasnow = 460, @@ -2931,76 +2096,42 @@ public enum PBEMoveTarget : byte SingleSurrounding, // Single battler surrounding (Ex. Tackle) Varies // Possible targets vary (Ex. Curse) } - /// - /// Represents a specific 's flags. - /// + /// Represents a specific 's flags. [Flags] public enum PBEMoveFlag : ushort { - /// - /// The move has no flags. - /// + /// The move has no flags. None, - /// - /// The move's power is boosted by . - /// + /// The move's power is boosted by . AffectedByIronFist = 1 << 0, - /// - /// The move is blocked by and . - /// + /// The move is blocked by and . AffectedByMagicCoat = 1 << 1, - /// - /// The move can be copied by . - /// + /// The move can be copied by . AffectedByMirrorMove = 1 << 2, - /// - /// The move is blocked by , , and . - /// + /// The move is blocked by , , and . AffectedByProtect = 1 << 3, - /// - /// The move's power is boosted by . - /// + /// The move's power is boosted by . AffectedByReckless = 1 << 4, - /// - /// The move can be stolen by . - /// + /// The move can be stolen by . AffectedBySnatch = 1 << 5, - /// - /// The move always lands a critical hit. - /// + /// The move always lands a critical hit. AlwaysCrit = 1 << 6, BlockedByMetronome = 1 << 7, - /// - /// The move removes from the user. - /// + /// The move removes from the user. DefrostsUser = 1 << 8, - /// - /// The move has a higher chance of landing a critical hit. - /// + /// The move has a higher chance of landing a critical hit. HighCritChance = 1 << 9, - /// - /// The move can hit targets. - /// + /// The move can hit targets. HitsAirborne = 1 << 10, - /// - /// The move can hit targets. - /// + /// The move can hit targets. HitsUnderground = 1 << 11, - /// - /// The move can hit targets. - /// + /// The move can hit targets. HitsUnderwater = 1 << 12, - /// - /// The user makes contact with the target, causing it to take damage from the target's , , and . - /// + /// The user makes contact with the target, causing it to take damage from the target's , , and . MakesContact = 1 << 13, - /// - /// The move is blocked by . - /// + /// The move is blocked by . SoundBased = 1 << 14, - /// - /// The move does not consume a held gem. - /// + /// The move does not consume a held gem. UnaffectedByGems = 1 << 15 } public enum PBEMoveEffect : byte @@ -3497,9 +2628,7 @@ public enum PBEMove : ushort XScissor = 404, ZapCannon = 192, ZenHeadbutt = 428, - /// - /// Invalid move. - /// + /// Invalid move. MAX = 560 } } diff --git a/PokemonBattleEngine/Data/IndividualValueCollection.cs b/PokemonBattleEngine/Data/IndividualValueCollection.cs deleted file mode 100644 index a7bd774cf..000000000 --- a/PokemonBattleEngine/Data/IndividualValueCollection.cs +++ /dev/null @@ -1,213 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; - -namespace Kermalis.PokemonBattleEngine.Data -{ - public sealed class PBEIndividualValueCollection : IEnumerable, INotifyPropertyChanged - { - public sealed class PBEIndividualValue : INotifyPropertyChanged - { - private void OnPropertyChanged(string property) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); - } - public event PropertyChangedEventHandler PropertyChanged; - - private readonly PBEIndividualValueCollection parent; - - public PBEStat Stat { get; } - private byte value; - public byte Value - { - get => value; - set - { - if (this.value != value) - { - parent.Set(Stat, value); - } - } - } - - internal PBEIndividualValue(PBEIndividualValueCollection parent, PBEStat stat) - { - this.parent = parent; - Stat = stat; - } - - internal void Update(byte value) - { - this.value = value; - OnPropertyChanged(nameof(Value)); - } - } - - private void OnPropertyChanged(string property) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); - } - public event PropertyChangedEventHandler PropertyChanged; - - public PBESettings Settings { get; } - private PBEIndividualValue[] ivs; - - 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 PBEIndividualValueCollection(PBESettings settings, bool randomize) - { - if (settings == null) - { - throw new ArgumentNullException(nameof(settings)); - } - Settings = settings; - CreateIVs(); - if (randomize) - { - Randomize(); - } - } - public PBEIndividualValueCollection(PBESettings settings, byte hp = 0, byte attack = 0, byte defense = 0, byte spAttack = 0, byte spDefense = 0, byte speed = 0) - { - if (settings == null) - { - throw new ArgumentNullException(nameof(settings)); - } - Settings = settings; - Settings.PropertyChanged += OnSettingsChanged; - CreateIVs(); - TrySet(hp, attack, defense, spAttack, spDefense, speed); - } - public PBEIndividualValueCollection(PBESettings settings, PBEIndividualValueCollection other) - { - if (settings == null) - { - throw new ArgumentNullException(nameof(settings)); - } - if (other == null) - { - throw new ArgumentNullException(nameof(other)); - } - Settings = settings; - Settings.PropertyChanged += OnSettingsChanged; - CreateIVs(); - TrySet(other[PBEStat.HP].Value, other[PBEStat.Attack].Value, other[PBEStat.Defense].Value, other[PBEStat.SpAttack].Value, other[PBEStat.SpDefense].Value, other[PBEStat.Speed].Value); - } - private void CreateIVs() - { - ivs = new PBEIndividualValue[6] - { - new PBEIndividualValue(this, PBEStat.HP), - new PBEIndividualValue(this, PBEStat.Attack), - new PBEIndividualValue(this, PBEStat.Defense), - new PBEIndividualValue(this, PBEStat.SpAttack), - new PBEIndividualValue(this, PBEStat.SpDefense), - new PBEIndividualValue(this, PBEStat.Speed) - }; - UpdateHiddenPower(); - } - private void UpdateHiddenPower() - { - HiddenPowerType = PBEPokemonData.GetHiddenPowerType(this[PBEStat.HP].Value, this[PBEStat.Attack].Value, this[PBEStat.Defense].Value, this[PBEStat.SpAttack].Value, this[PBEStat.SpDefense].Value, this[PBEStat.Speed].Value); - HiddenPowerBasePower = PBEPokemonData.GetHiddenPowerBasePower(this[PBEStat.HP].Value, this[PBEStat.Attack].Value, this[PBEStat.Defense].Value, this[PBEStat.SpAttack].Value, this[PBEStat.SpDefense].Value, this[PBEStat.Speed].Value); - } - private void TrySet(byte hp, byte attack, byte defense, byte spAttack, byte spDefense, byte speed) - { - Set(PBEStat.HP, hp); - Set(PBEStat.Attack, attack); - Set(PBEStat.Defense, defense); - Set(PBEStat.SpAttack, spAttack); - Set(PBEStat.SpDefense, spDefense); - Set(PBEStat.Speed, speed); - } - - public PBEIndividualValue this[PBEStat stat] - { - get - { - int statIndex = (int)stat; - if (statIndex >= 6) - { - throw new ArgumentOutOfRangeException(nameof(stat)); - } - else - { - return ivs[statIndex]; - } - } - } - - public void Set(PBEStat stat, byte value) - { - int statIndex = (int)stat; - if (statIndex >= 6) - { - throw new ArgumentOutOfRangeException(nameof(stat)); - } - byte newVal = Math.Min(value, Settings.MaxIVs); - PBEIndividualValue iv = ivs[statIndex]; - if (iv.Value != newVal) - { - iv.Update(newVal); - UpdateHiddenPower(); - } - } - public void Randomize() - { - for (int i = 0; i < 6; i++) - { - ivs[i].Value = (byte)PBEUtils.RandomInt(0, Settings.MaxIVs); - } - } - - public IEnumerator GetEnumerator() - { - for (int i = 0; i < 6; i++) - { - yield return ivs[i]; - } - } - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - private void OnSettingsChanged(object sender, PropertyChangedEventArgs e) - { - switch (e.PropertyName) - { - case nameof(Settings.MaxIVs): - { - TrySet(this[PBEStat.HP].Value, this[PBEStat.Attack].Value, this[PBEStat.Defense].Value, this[PBEStat.SpAttack].Value, this[PBEStat.SpDefense].Value, this[PBEStat.Speed].Value); - break; - } - } - } - } -} diff --git a/PokemonBattleEngine/Data/IndividualValues.cs b/PokemonBattleEngine/Data/IndividualValues.cs new file mode 100644 index 000000000..1772f8515 --- /dev/null +++ b/PokemonBattleEngine/Data/IndividualValues.cs @@ -0,0 +1,283 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; + +namespace Kermalis.PokemonBattleEngine.Data +{ + public sealed class PBEIndividualValues : IDisposable, IEnumerable, INotifyPropertyChanged + { + public sealed class PBEIndividualValue : INotifyPropertyChanged + { + private void OnPropertyChanged(string property) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); + } + public event PropertyChangedEventHandler PropertyChanged; + + private readonly PBEIndividualValues _parent; + + public PBEStat Stat { get; } + private byte _value; + public byte Value + { + get => _value; + set + { + if (_parent.IsDisposed) + { + throw new ObjectDisposedException(null); + } + if (_value != value) + { + if (value > _parent.Settings.MaxIVs) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + _value = value; + OnPropertyChanged(nameof(Value)); + _parent.UpdateHiddenPower(); + } + } + } + + internal PBEIndividualValue(PBEIndividualValues 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 PBEIndividualValue[] _ivs; + public PBEIndividualValue 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)); + } + } + } + + internal PBEIndividualValues(PBESettings settings, BinaryReader 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; + Settings.PropertyChanged += OnSettingsChanged; + CreateIVs(hp, attack, defense, spAttack, spDefense, speed); + } + internal PBEIndividualValues(PBESettings settings, JToken jToken) + { + void Validate(byte val, string name) + { + if (val > settings.MaxIVs) + { + throw new ArgumentOutOfRangeException(nameof(PBEPokemonShell.IndividualValues), $"\"{name}\" individual value must not exceed \"{nameof(settings.MaxIVs)}\" ({settings.MaxIVs})"); + } + } + byte hp = jToken[nameof(PBEStat.HP)].Value(); + Validate(hp, nameof(PBEStat.HP)); + byte attack = jToken[nameof(PBEStat.Attack)].Value(); + Validate(attack, nameof(PBEStat.Attack)); + byte defense = jToken[nameof(PBEStat.Defense)].Value(); + Validate(defense, nameof(PBEStat.Defense)); + byte spAttack = jToken[nameof(PBEStat.SpAttack)].Value(); + Validate(spAttack, nameof(PBEStat.SpAttack)); + byte spDefense = jToken[nameof(PBEStat.SpDefense)].Value(); + Validate(spDefense, nameof(PBEStat.SpDefense)); + byte speed = jToken[nameof(PBEStat.Speed)].Value(); + Validate(speed, nameof(PBEStat.Speed)); + Settings = settings; + Settings.PropertyChanged += OnSettingsChanged; + CreateIVs(hp, attack, defense, spAttack, spDefense, speed); + } + internal PBEIndividualValues(PBEIndividualValues other) + { + Settings = other.Settings; + Settings.PropertyChanged += OnSettingsChanged; + CreateIVs(other[PBEStat.HP].Value, other[PBEStat.Attack].Value, other[PBEStat.Defense].Value, other[PBEStat.SpAttack].Value, other[PBEStat.SpDefense].Value, other[PBEStat.Speed].Value); + } + public PBEIndividualValues(PBESettings settings, bool randomize) + { + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + Settings = settings; + Settings.PropertyChanged += OnSettingsChanged; + _canDispose = true; + CreateIVs(0, 0, 0, 0, 0, 0); + if (randomize) + { + Randomize(); + } + } + + private void CreateIVs(byte hp, byte attack, byte defense, byte spAttack, byte spDefense, byte speed) + { + _ivs = new PBEIndividualValue[6] + { + new PBEIndividualValue(this, PBEStat.HP, hp), + new PBEIndividualValue(this, PBEStat.Attack, attack), + new PBEIndividualValue(this, PBEStat.Defense, defense), + new PBEIndividualValue(this, PBEStat.SpAttack, spAttack), + new PBEIndividualValue(this, PBEStat.SpDefense, spDefense), + new PBEIndividualValue(this, PBEStat.Speed, speed) + }; + UpdateHiddenPower(); + } + private void OnSettingsChanged(object sender, PropertyChangedEventArgs e) + { + switch (e.PropertyName) + { + case nameof(Settings.MaxIVs): + { + for (int i = 0; i < 6; i++) + { + PBEIndividualValue iv = _ivs[i]; + if (iv.Value > Settings.MaxIVs) + { + iv.Value = Settings.MaxIVs; + } + } + break; + } + } + } + private void UpdateHiddenPower() + { + HiddenPowerType = PBEPokemonData.GetHiddenPowerType(this[PBEStat.HP].Value, this[PBEStat.Attack].Value, this[PBEStat.Defense].Value, this[PBEStat.SpAttack].Value, this[PBEStat.SpDefense].Value, this[PBEStat.Speed].Value); + HiddenPowerBasePower = PBEPokemonData.GetHiddenPowerBasePower(this[PBEStat.HP].Value, this[PBEStat.Attack].Value, this[PBEStat.Defense].Value, this[PBEStat.SpAttack].Value, this[PBEStat.SpDefense].Value, this[PBEStat.Speed].Value); + } + + private bool _canDispose; + public bool CanDispose + { + get => _canDispose; + internal set + { + if (_canDispose != value) + { + _canDispose = value; + OnPropertyChanged(nameof(CanDispose)); + } + } + } + public bool IsDisposed { get; private set; } + public void Dispose() + { + if (!_canDispose) + { + throw new InvalidOperationException(); + } + if (!IsDisposed) + { + IsDisposed = true; + OnPropertyChanged(nameof(IsDisposed)); + Settings.PropertyChanged -= OnSettingsChanged; + } + } + + public void Randomize() + { + if (IsDisposed) + { + throw new ObjectDisposedException(null); + } + for (int i = 0; i < 6; i++) + { + _ivs[i].Value = (byte)PBEUtils.RandomInt(0, Settings.MaxIVs); + } + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < 6; i++) + { + yield return _ivs[i]; + } + } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + internal void ToBytes(List bytes) + { + for (int i = 0; i < 6; i++) + { + bytes.Add(_ivs[i].Value); + } + } + internal void ToJson(JsonTextWriter w) + { + w.WriteStartObject(); + for (int i = 0; i < 6; i++) + { + PBEIndividualValue iv = _ivs[i]; + w.WritePropertyName(iv.Stat.ToString()); + w.WriteValue(iv.Value); + } + w.WriteEndObject(); + } + } +} diff --git a/PokemonBattleEngine/Data/ItemData.cs b/PokemonBattleEngine/Data/ItemData.cs index 940fa0f9a..e07276e28 100644 --- a/PokemonBattleEngine/Data/ItemData.cs +++ b/PokemonBattleEngine/Data/ItemData.cs @@ -5,17 +5,11 @@ namespace Kermalis.PokemonBattleEngine.Data { public sealed class PBEItemData { - /// - /// The power has when the user is holding this item. 0 will cause the move to fail. - /// + /// The power has when the user is holding this item. 0 will cause the move to fail. public byte FlingPower { get; } - /// - /// The power has when the user is holding this item. 0 will cause the move to fail. - /// + /// The power has when the user is holding this item. 0 will cause the move to fail. public byte NaturalGiftPower { get; } - /// - /// The type becomes when the user is holding this item. - /// + /// The type becomes when the user is holding this item. public PBEType NaturalGiftType { get; } private PBEItemData(byte flingPower = 0, byte naturalGiftPower = 0, PBEType naturalGiftType = PBEType.None) diff --git a/PokemonBattleEngine/Data/LegalityChecker.cs b/PokemonBattleEngine/Data/LegalityChecker.cs index 4af858430..839de4f89 100644 --- a/PokemonBattleEngine/Data/LegalityChecker.cs +++ b/PokemonBattleEngine/Data/LegalityChecker.cs @@ -10,8 +10,10 @@ public static class PBELegalityChecker // TODO: Include generation? // TODO: Sketch // TODO: Same goals as MoveLegalityCheck - public static PBEMove[] GetLegalMoves(PBESpecies species, byte level) + public static PBEMove[] GetLegalMoves(PBESpecies species, byte level, PBESettings settings) { + PBEPokemonShell.ValidateSpecies(species); + PBEPokemonShell.ValidateLevel(level, settings); var evolutionChain = new List(); void AddPreEvolutions(PBESpecies sp) { @@ -43,48 +45,34 @@ void AddPreEvolutions(PBESpecies sp) // TODO: Check if HMs were transferred // TODO: Check events for moves // TODO: EggMove_Special - public static void MoveLegalityCheck(PBESpecies species, byte level, IEnumerable moves, PBESettings settings) + public static void MoveLegalityCheck(PBEMoveset moveset) { // Validate basic move rules first - if (moves == null || moves.Count() != settings.NumMoves) + if (moveset == null) { - throw new ArgumentOutOfRangeException(nameof(moves), $"A Pokémon must have exactly {settings.NumMoves} moves."); - } - if (moves.Any(m => moves.Count(m2 => m != PBEMove.None && m == m2) > 1)) - { - throw new ArgumentOutOfRangeException(nameof(moves), $"A Pokémon cannot have duplicate moves other than {PBEMove.None}."); - } - if (moves.All(m => m == PBEMove.None)) - { - throw new ArgumentOutOfRangeException(nameof(moves), $"A Pokémon must have at least one move other than {PBEMove.None}."); - } - if (species == PBESpecies.Keldeo_Resolute && !moves.Contains(PBEMove.SecretSword)) - { - throw new ArgumentOutOfRangeException(nameof(moves), $"{species} must have {PBEMove.SecretSword}."); + throw new ArgumentNullException(nameof(moveset)); } // Combine all moves from pre-evolutions - IEnumerable evolutionChain = PBEPokemonData.GetData(species).PreEvolutions.Concat(new[] { species }); + var evolutionChain = new List(); + void AddPreEvolutions(PBESpecies sp) + { + foreach (PBESpecies pkmn in PBEPokemonData.GetData(sp).PreEvolutions) + { + AddPreEvolutions(pkmn); + } + evolutionChain.Add(sp); + } + AddPreEvolutions(moveset.Species); var levelUp = new List<(PBEMove Move, byte Level, PBEMoveObtainMethod ObtainMethod)>(); var other = new List<(PBEMove Move, PBEMoveObtainMethod ObtainMethod)>(); foreach (PBESpecies pkmn in evolutionChain) { var pData = PBEPokemonData.GetData(pkmn); - levelUp.AddRange(pData.LevelUpMoves.Where(t => t.Level <= level)); + levelUp.AddRange(pData.LevelUpMoves.Where(t => t.Level <= moveset.Level)); other.AddRange(pData.OtherMoves); } - // TODO: - PBEMove[] allAsMoves = GetLegalMoves(species, level); - - // Check if there's a move it cannot possibly learn - foreach (PBEMove m in moves) - { - if (m != PBEMove.None && !allAsMoves.Contains(m)) - { - throw new ArgumentOutOfRangeException(nameof(moves), $"{species} cannot learn {m}."); - } - } // Check generational rules bool HasGen3Method(PBEMoveObtainMethod method) @@ -127,7 +115,7 @@ bool HasGen5Method(PBEMoveObtainMethod method) || method.HasFlag(PBEMoveObtainMethod.EggMove_BWB2W2); } - IEnumerable<(PBEMove Move, PBEMoveObtainMethod ObtainMethod)> movesAsObtainMethods = levelUp.Where(t => moves.Contains(t.Move)).Select(t => (t.Move, t.ObtainMethod)).Union(other.Where(t => moves.Contains(t.Move))); + IEnumerable<(PBEMove Move, PBEMoveObtainMethod ObtainMethod)> movesAsObtainMethods = levelUp.Where(t => moveset.Contains(t.Move)).Select(t => (t.Move, t.ObtainMethod)).Union(other.Where(t => moveset.Contains(t.Move))); // Check to see where the Pokémon DEFINITELY has been bool definitelyBeenInGeneration3 = false, diff --git a/PokemonBattleEngine/Data/LocalizedString.cs b/PokemonBattleEngine/Data/LocalizedString.cs index 10d6bf7f2..b89d2cf98 100644 --- a/PokemonBattleEngine/Data/LocalizedString.cs +++ b/PokemonBattleEngine/Data/LocalizedString.cs @@ -9,24 +9,24 @@ public sealed class PBELocalizedString public delegate void PBECultureChangedHandler(CultureInfo oldPBECultureInfo); public static event PBECultureChangedHandler PBECultureChanged; - private static CultureInfo pbeCulture; + private static CultureInfo _pbeCulture; public static CultureInfo PBECulture { - get => pbeCulture; + get => _pbeCulture; set { if (value == null) { throw new ArgumentNullException(nameof(value)); } - if (!pbeCulture.Equals(value)) + if (!_pbeCulture.Equals(value)) { if (!IsCultureValid(value)) { throw new ArgumentOutOfRangeException(nameof(value)); } - CultureInfo oldPBECultureInfo = pbeCulture; - pbeCulture = value; + CultureInfo oldPBECultureInfo = _pbeCulture; + _pbeCulture = value; PBECultureChanged?.Invoke(oldPBECultureInfo); } } @@ -35,7 +35,7 @@ public static CultureInfo PBECulture static PBELocalizedString() { CultureInfo cultureInfo = CultureInfo.CurrentUICulture; - pbeCulture = IsCultureValid(cultureInfo) ? cultureInfo : CultureInfo.GetCultureInfo("en-US"); + _pbeCulture = IsCultureValid(cultureInfo) ? cultureInfo : CultureInfo.GetCultureInfo("en-US"); } public string English { get; } @@ -61,6 +61,10 @@ private PBELocalizedString(SearchResult result) public string FromCultureInfo(CultureInfo cultureInfo) { + if (cultureInfo == null) + { + throw new ArgumentNullException(nameof(cultureInfo)); + } string id = cultureInfo.TwoLetterISOLanguageName; switch (id) { @@ -76,13 +80,17 @@ public string FromCultureInfo(CultureInfo cultureInfo) } public static bool IsCultureValid(CultureInfo cultureInfo) { + if (cultureInfo == null) + { + throw new ArgumentNullException(nameof(cultureInfo)); + } string id = cultureInfo.TwoLetterISOLanguageName; return id == "en" || id == "fr" || id == "de" || id == "it" || id == "ja" || id == "ko" || id == "es"; } public override string ToString() { - return FromCultureInfo(pbeCulture); + return FromCultureInfo(_pbeCulture); } #region Database Querying @@ -99,8 +107,8 @@ private class SearchResult public string Korean { get; set; } public string Spanish { get; set; } } - 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 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 static bool GetEnumValue(string value, out TEnum result) where TEnum : struct { foreach (TEnum v in Enum.GetValues(typeof(TEnum))) @@ -118,7 +126,7 @@ private static bool GetEnumValue(string value, out TEnum result) where TE public static PBEAbility? GetAbilityByName(string abilityName) { PBEAbility ability; - List results = PBEUtils.QueryDatabase(string.Format(queryText, "AbilityNames", abilityName)); + List results = PBEUtils.QueryDatabase(string.Format(QueryText, "AbilityNames", abilityName)); if (results.Count == 1) { ability = (PBEAbility)results[0].Id; @@ -135,7 +143,7 @@ public static PBELocalizedString GetAbilityDescription(PBEAbility ability) { throw new ArgumentOutOfRangeException(nameof(ability)); } - return new PBELocalizedString(PBEUtils.QueryDatabase(string.Format(queryId, "AbilityDescriptions", (byte)ability))[0]); + return new PBELocalizedString(PBEUtils.QueryDatabase(string.Format(QueryId, "AbilityDescriptions", (byte)ability))[0]); } public static PBELocalizedString GetAbilityName(PBEAbility ability) { @@ -143,12 +151,12 @@ public static PBELocalizedString GetAbilityName(PBEAbility ability) { throw new ArgumentOutOfRangeException(nameof(ability)); } - return new PBELocalizedString(PBEUtils.QueryDatabase(string.Format(queryId, "AbilityNames", (byte)ability))[0]); + return new PBELocalizedString(PBEUtils.QueryDatabase(string.Format(QueryId, "AbilityNames", (byte)ability))[0]); } public static PBEGender? GetGenderByName(string genderName) { PBEGender gender; - List results = PBEUtils.QueryDatabase(string.Format(queryText, "GenderNames", genderName)); + List results = PBEUtils.QueryDatabase(string.Format(QueryText, "GenderNames", genderName)); if (results.Count == 1) { gender = (PBEGender)results[0].Id; @@ -165,12 +173,12 @@ public static PBELocalizedString GetGenderName(PBEGender gender) { throw new ArgumentOutOfRangeException(nameof(gender)); } - return new PBELocalizedString(PBEUtils.QueryDatabase(string.Format(queryId, "GenderNames", (byte)gender))[0]); + return new PBELocalizedString(PBEUtils.QueryDatabase(string.Format(QueryId, "GenderNames", (byte)gender))[0]); } public static PBEItem? GetItemByName(string itemName) { PBEItem item; - List results = PBEUtils.QueryDatabase(string.Format(queryText, "ItemNames", itemName)); + List results = PBEUtils.QueryDatabase(string.Format(QueryText, "ItemNames", itemName)); if (results.Count == 1) { item = (PBEItem)results[0].Id; @@ -187,7 +195,7 @@ public static PBELocalizedString GetItemDescription(PBEItem item) { throw new ArgumentOutOfRangeException(nameof(item)); } - return new PBELocalizedString(PBEUtils.QueryDatabase(string.Format(queryId, "ItemDescriptions", (ushort)item))[0]); + return new PBELocalizedString(PBEUtils.QueryDatabase(string.Format(QueryId, "ItemDescriptions", (ushort)item))[0]); } public static PBELocalizedString GetItemName(PBEItem item) { @@ -195,12 +203,12 @@ public static PBELocalizedString GetItemName(PBEItem item) { throw new ArgumentOutOfRangeException(nameof(item)); } - return new PBELocalizedString(PBEUtils.QueryDatabase(string.Format(queryId, "ItemNames", (ushort)item))[0]); + return new PBELocalizedString(PBEUtils.QueryDatabase(string.Format(QueryId, "ItemNames", (ushort)item))[0]); } public static PBEMove? GetMoveByName(string moveName) { PBEMove move; - List results = PBEUtils.QueryDatabase(string.Format(queryText, "MoveNames", moveName)); + List results = PBEUtils.QueryDatabase(string.Format(QueryText, "MoveNames", moveName)); if (results.Count == 1) { move = (PBEMove)results[0].Id; @@ -217,7 +225,7 @@ public static PBELocalizedString GetMoveDescription(PBEMove move) { throw new ArgumentOutOfRangeException(nameof(move)); } - return new PBELocalizedString(PBEUtils.QueryDatabase(string.Format(queryId, "MoveDescriptions", (ushort)move))[0]); + return new PBELocalizedString(PBEUtils.QueryDatabase(string.Format(QueryId, "MoveDescriptions", (ushort)move))[0]); } public static PBELocalizedString GetMoveName(PBEMove move) { @@ -225,12 +233,12 @@ public static PBELocalizedString GetMoveName(PBEMove move) { throw new ArgumentOutOfRangeException(nameof(move)); } - return new PBELocalizedString(PBEUtils.QueryDatabase(string.Format(queryId, "MoveNames", (ushort)move))[0]); + return new PBELocalizedString(PBEUtils.QueryDatabase(string.Format(QueryId, "MoveNames", (ushort)move))[0]); } public static PBENature? GetNatureByName(string natureName) { PBENature nature; - List results = PBEUtils.QueryDatabase(string.Format(queryText, "NatureNames", natureName)); + List results = PBEUtils.QueryDatabase(string.Format(QueryText, "NatureNames", natureName)); if (results.Count == 1) { nature = (PBENature)results[0].Id; @@ -247,12 +255,12 @@ public static PBELocalizedString GetNatureName(PBENature nature) { throw new ArgumentOutOfRangeException(nameof(nature)); } - return new PBELocalizedString(PBEUtils.QueryDatabase(string.Format(queryId, "NatureNames", (byte)nature))[0]); + return new PBELocalizedString(PBEUtils.QueryDatabase(string.Format(QueryId, "NatureNames", (byte)nature))[0]); } public static PBESpecies? GetSpeciesByName(string speciesName) { PBESpecies species; - List results = PBEUtils.QueryDatabase(string.Format(queryText, "SpeciesNames", speciesName)); + List results = PBEUtils.QueryDatabase(string.Format(QueryText, "SpeciesNames", speciesName)); if (results.Count == 1) { species = (PBESpecies)results[0].Id; @@ -270,7 +278,7 @@ public static PBELocalizedString GetSpeciesCategory(PBESpecies species) { throw new ArgumentOutOfRangeException(nameof(species)); } - return new PBELocalizedString(PBEUtils.QueryDatabase(string.Format(queryId, "SpeciesCategories", speciesId))[0]); + return new PBELocalizedString(PBEUtils.QueryDatabase(string.Format(QueryId, "SpeciesCategories", speciesId))[0]); } public static PBELocalizedString GetSpeciesEntry(PBESpecies species) { @@ -279,7 +287,7 @@ public static PBELocalizedString GetSpeciesEntry(PBESpecies species) { throw new ArgumentOutOfRangeException(nameof(species)); } - return new PBELocalizedString(PBEUtils.QueryDatabase(string.Format(queryId, "SpeciesEntries", speciesId))[0]); + return new PBELocalizedString(PBEUtils.QueryDatabase(string.Format(QueryId, "SpeciesEntries", speciesId))[0]); } public static PBELocalizedString GetSpeciesName(PBESpecies species) { @@ -288,12 +296,12 @@ public static PBELocalizedString GetSpeciesName(PBESpecies species) { throw new ArgumentOutOfRangeException(nameof(species)); } - return new PBELocalizedString(PBEUtils.QueryDatabase(string.Format(queryId, "SpeciesNames", speciesId))[0]); + return new PBELocalizedString(PBEUtils.QueryDatabase(string.Format(QueryId, "SpeciesNames", speciesId))[0]); } public static PBEStat? GetStatByName(string statName) { PBEStat stat; - List results = PBEUtils.QueryDatabase(string.Format(queryText, "StatNames", statName)); + List results = PBEUtils.QueryDatabase(string.Format(QueryText, "StatNames", statName)); if (results.Count == 1) { stat = (PBEStat)results[0].Id; @@ -310,12 +318,12 @@ public static PBELocalizedString GetStatName(PBEStat stat) { throw new ArgumentOutOfRangeException(nameof(stat)); } - return new PBELocalizedString(PBEUtils.QueryDatabase(string.Format(queryId, "StatNames", (byte)stat))[0]); + return new PBELocalizedString(PBEUtils.QueryDatabase(string.Format(QueryId, "StatNames", (byte)stat))[0]); } public static PBEType? GetTypeByName(string typeName) { PBEType type; - List results = PBEUtils.QueryDatabase(string.Format(queryText, "TypeNames", typeName)); + List results = PBEUtils.QueryDatabase(string.Format(QueryText, "TypeNames", typeName)); if (results.Count == 1) { type = (PBEType)results[0].Id; @@ -328,11 +336,11 @@ public static PBELocalizedString GetStatName(PBEStat stat) } public static PBELocalizedString GetTypeName(PBEType type) { - if (!Enum.IsDefined(typeof(PBEType), type)) + if (type >= PBEType.MAX) { throw new ArgumentOutOfRangeException(nameof(type)); } - return new PBELocalizedString(PBEUtils.QueryDatabase(string.Format(queryId, "TypeNames", (byte)type))[0]); + return new PBELocalizedString(PBEUtils.QueryDatabase(string.Format(QueryId, "TypeNames", (byte)type))[0]); } #endregion diff --git a/PokemonBattleEngine/Data/Moveset.cs b/PokemonBattleEngine/Data/Moveset.cs new file mode 100644 index 000000000..3985a5fe2 --- /dev/null +++ b/PokemonBattleEngine/Data/Moveset.cs @@ -0,0 +1,640 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.IO; +using System.Linq; + +namespace Kermalis.PokemonBattleEngine.Data +{ + public sealed class PBEMoveset : IDisposable, INotifyCollectionChanged, INotifyPropertyChanged, IReadOnlyList + { + public sealed class PBEMovesetSlot : INotifyPropertyChanged + { + private void OnPropertyChanged(string property) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); + } + public event PropertyChangedEventHandler PropertyChanged; + + private readonly PBEMoveset _parent; + private readonly int _index; + + public PBEAlphabeticalList Allowed { get; } + private PBEMove _move; + public PBEMove Move + { + get => _move; + set + { + if (_parent.IsDisposed) + { + throw new ObjectDisposedException(null); + } + PBEMove old = _move; + if (old != value) + { + if (!Enum.IsDefined(typeof(PBEMove), 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) + { + PBEMovesetSlot 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--) + { + PBEMovesetSlot 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 (_parent.IsDisposed) + { + throw new ObjectDisposedException(null); + } + 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)); + } + } + } + + internal PBEMovesetSlot(PBEMoveset parent, int index) + { + _parent = parent; + _index = index; + _isMoveEditable = index < 2; + Allowed = new PBEAlphabeticalList(new[] { PBEMove.None }); + } + + private void UpdateMove(PBEMove move) + { + if (_move != move) + { + PBEMove old = _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 FireEvents(NotifyCollectionChangedEventArgs e) + { + OnPropertyChanged(nameof(Count)); + OnPropertyChanged("Item[]"); + OnCollectionChanged(e); + } + 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 PBESpecies _species; + public PBESpecies Species + { + get => _species; + set + { + if (IsDisposed) + { + throw new ObjectDisposedException(null); + } + if (_species != value) + { + PBEPokemonShell.ValidateSpecies(value); + _species = value; + OnPropertyChanged(nameof(Species)); + SetAlloweds(); + } + } + } + private byte _level; + public byte Level + { + get => _level; + set + { + if (IsDisposed) + { + throw new ObjectDisposedException(null); + } + if (_level != value) + { + PBEPokemonShell.ValidateLevel(value, Settings); + _level = value; + OnPropertyChanged(nameof(Level)); + SetAlloweds(); + } + } + } + public PBESettings Settings { get; } + private readonly List _list; + public int Count => _list.Count; + public PBEMovesetSlot this[int index] + { + get + { + if (index >= _list.Count) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + return _list[index]; + } + } + public PBEMovesetSlot this[PBEMove move] + { + get + { + for (int i = 0; i < Settings.NumMoves; i++) + { + PBEMovesetSlot slot = _list[i]; + if (slot.Move == move) + { + return slot; + } + } + return null; + } + } + + internal PBEMoveset(PBESpecies species, byte level, PBESettings settings, BinaryReader r) + { + if (r.ReadByte() != settings.NumMoves) + { + throw new InvalidDataException(); + } + _species = species; + _level = level; + Settings = settings; + _list = new List(Settings.NumMoves); + for (int i = 0; i < Settings.NumMoves; i++) + { + Add(new PBEMovesetSlot(this, i)); + } + SetAlloweds(); + for (int i = 0; i < Settings.NumMoves; i++) + { + PBEMovesetSlot slot = _list[i]; + var move = (PBEMove)r.ReadUInt16(); + slot.Move = move; + if (slot.Move != move) + { + throw new InvalidDataException(); + } + byte ppUps = r.ReadByte(); + slot.PPUps = ppUps; + if (slot.PPUps != ppUps) + { + throw new InvalidDataException(); + } + } + Settings.PropertyChanged += OnSettingsChanged; + } + internal PBEMoveset(PBESpecies species, byte level, PBESettings settings, JArray jArray) + { + if (jArray.Count != settings.NumMoves) + { + throw new ArgumentOutOfRangeException(nameof(PBEPokemonShell.Moveset), $"Moveset count must be equal to \"{nameof(settings.NumMoves)}\" ({settings.NumMoves})."); + } + _species = species; + _level = level; + Settings = settings; + _list = new List(Settings.NumMoves); + for (int i = 0; i < Settings.NumMoves; i++) + { + Add(new PBEMovesetSlot(this, i)); + } + SetAlloweds(); + for (int i = 0; i < Settings.NumMoves; i++) + { + PBEMovesetSlot slot = _list[i]; + JToken jToken = jArray[i]; + PBEMove move = PBELocalizedString.GetMoveByName(jToken[nameof(PBEMovesetSlot.Move)].Value()).Value; + slot.Move = move; + if (slot.Move != move) + { + throw new ArgumentOutOfRangeException(nameof(PBEPokemonShell.Moveset), "Invalid moves."); + } + byte ppUps = jToken[nameof(PBEMovesetSlot.PPUps)].Value(); + slot.PPUps = ppUps; + if (slot.PPUps != ppUps) + { + throw new ArgumentOutOfRangeException(nameof(PBEPokemonShell.Moveset), "Invalid PP-Ups."); + } + } + Settings.PropertyChanged += OnSettingsChanged; + } + internal PBEMoveset(PBEMoveset other) + { + _species = other._species; + _level = other._level; + Settings = other.Settings; + Settings.PropertyChanged += OnSettingsChanged; + _list = new List(Settings.NumMoves); + for (int i = 0; i < Settings.NumMoves; i++) + { + Add(new PBEMovesetSlot(this, i)); + } + SetAlloweds(); + for (int i = 0; i < Settings.NumMoves; i++) + { + PBEMovesetSlot slot = _list[i]; + PBEMovesetSlot oSlot = other[i]; + slot.Move = oSlot.Move; + slot.PPUps = oSlot.PPUps; + } + } + /// Creates a new object with the specified traits. + /// The species 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 PBEMoveset(PBESpecies species, byte level, PBESettings settings, bool randomize) + { + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + PBEPokemonShell.ValidateLevel(level, settings); + _level = level; + PBEPokemonShell.ValidateSpecies(species); + _species = species; + Settings = settings; + Settings.PropertyChanged += OnSettingsChanged; + _canDispose = true; + _list = new List(Settings.NumMoves); + for (int i = 0; i < Settings.NumMoves; i++) + { + Add(new PBEMovesetSlot(this, i)); + } + SetAlloweds(); + if (randomize) + { + Randomize(); + } + } + + private void Add(PBEMovesetSlot item) + { + int index = _list.Count; + _list.Insert(index, item); + FireEvents(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); + } + private bool Remove(PBEMovesetSlot item) + { + int index = _list.IndexOf(item); + bool b = index != -1; + if (b) + { + _list.RemoveAt(index); + FireEvents(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index)); + item.Allowed.Dispose(); + } + return b; + } + private void OnSettingsChanged(object sender, PropertyChangedEventArgs e) + { + switch (e.PropertyName) + { + case nameof(Settings.MaxLevel): + { + if (_level > Settings.MaxLevel) + { + Level = Settings.MaxLevel; + } + break; + } + case nameof(Settings.MaxPPUps): + { + for (int i = 0; i < Settings.NumMoves; i++) + { + PBEMovesetSlot slot = _list[i]; + if (slot.PPUps > Settings.MaxPPUps) + { + slot.PPUps = Settings.MaxPPUps; + } + } + break; + } + case nameof(Settings.MinLevel): + { + if (_level < Settings.MinLevel) + { + Level = Settings.MinLevel; + } + break; + } + case nameof(Settings.NumMoves): + { + int oldCount = _list.Count; + if (Settings.NumMoves != oldCount) + { + if (Settings.NumMoves > oldCount) + { + if (Settings.NumMoves > _list.Capacity) + { + _list.Capacity = Settings.NumMoves; + } + int numToAdd = Settings.NumMoves - oldCount; + for (int i = 0; i < numToAdd; i++) + { + Add(new PBEMovesetSlot(this, oldCount + i)); + } + } + else + { + int numToRemove = oldCount - Settings.NumMoves; + for (int i = 0; i < numToRemove; i++) + { + Remove(_list[oldCount - 1 - i]); + } + } + SetAlloweds(); + } + break; + } + } + } + + private void SetAlloweds() + { + int i; + PBEMove[] legalMoves = PBELegalityChecker.GetLegalMoves(_species, _level, Settings); + var allowed = new List(legalMoves.Length + 1); + allowed.AddRange(legalMoves); + if (_species == PBESpecies.Keldeo_Resolute) + { + PBEMovesetSlot slot = _list[0]; + slot.Allowed.Reset(new[] { PBEMove.SecretSword }); + if (slot.Move != PBEMove.SecretSword) + { + slot.Move = PBEMove.SecretSword; + } + allowed.Remove(PBEMove.SecretSword); + i = 1; + } + else + { + i = 0; + } + for (; i < Settings.NumMoves; i++) + { + if (i == 1) + { + allowed.Add(PBEMove.None); + } + PBEMovesetSlot slot = _list[i]; + slot.Allowed.Reset(allowed); + if (!allowed.Contains(slot.Move)) + { + PBEMove m = allowed[0]; + slot.Move = m; + } + } + SetEditables(); + } + private void SetEditables() + { + for (int i = 2; i < Settings.NumMoves; i++) + { + _list[i].IsMoveEditable = _list[i - 1].Move != PBEMove.None; + } + } + + private bool _canDispose; + public bool CanDispose + { + get => _canDispose; + internal set + { + if (_canDispose != value) + { + _canDispose = value; + OnPropertyChanged(nameof(CanDispose)); + } + } + } + public bool IsDisposed { get; private set; } + public void Dispose() + { + if (!_canDispose) + { + throw new InvalidOperationException(); + } + if (!IsDisposed) + { + IsDisposed = true; + OnPropertyChanged(nameof(IsDisposed)); + Settings.PropertyChanged -= OnSettingsChanged; + for (int i = 0; i < _list.Count; i++) + { + _list[i].Allowed.Dispose(); + } + } + } + + /// Sets every move slot excluding the first to with 0 PP-Ups. + public void Clear() + { + if (IsDisposed) + { + throw new ObjectDisposedException(null); + } + for (int i = 1; i < Settings.NumMoves; i++) + { + _list[i].Move = PBEMove.None; + } + SetEditables(); + } + public bool Contains(PBEMove move) + { + if (IsDisposed) + { + throw new ObjectDisposedException(null); + } + return this[move] != null; + } + /// Randomizes the move and PP-Ups in each slot without creating duplicate moves. + public void Randomize() + { + if (IsDisposed) + { + throw new ObjectDisposedException(null); + } + var blacklist = new List(Settings.NumMoves) { PBEMove.None }; + for (int i = 0; i < Settings.NumMoves; i++) + { + PBEMovesetSlot 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 = allowed.RandomElement(); + if (i < Settings.NumMoves - 1) + { + blacklist.Add(move); + } + slot.Move = move; + slot.PPUps = (byte)PBEUtils.RandomInt(0, Settings.MaxPPUps); + } + } + SetEditables(); + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < _list.Count; i++) + { + yield return _list[i]; + } + } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + internal void ToBytes(List bytes) + { + byte amt = (byte)_list.Count; + bytes.Add(amt); + for (int i = 0; i < amt; i++) + { + PBEMovesetSlot slot = _list[i]; + bytes.AddRange(BitConverter.GetBytes((ushort)slot.Move)); + bytes.Add(slot.PPUps); + } + } + internal void ToJson(JsonTextWriter w) + { + w.WriteStartArray(); + for (int i = 0; i < _list.Count; i++) + { + PBEMovesetSlot slot = _list[i]; + w.WriteStartObject(); + w.WritePropertyName(nameof(PBEMovesetSlot.Move)); + w.WriteValue(slot.Move.ToString()); + w.WritePropertyName(nameof(PBEMovesetSlot.PPUps)); + w.WriteValue(slot.PPUps); + w.WriteEndObject(); + } + w.WriteEndArray(); + } + } +} diff --git a/PokemonBattleEngine/Data/MovesetBuilder.cs b/PokemonBattleEngine/Data/MovesetBuilder.cs deleted file mode 100644 index 07ca6709b..000000000 --- a/PokemonBattleEngine/Data/MovesetBuilder.cs +++ /dev/null @@ -1,398 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; - -namespace Kermalis.PokemonBattleEngine.Data -{ - public sealed class PBEMovesetBuilder : INotifyPropertyChanged - { - public sealed class PBEMoveSlot : INotifyPropertyChanged - { - private void OnPropertyChanged(string property) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); - } - public event PropertyChangedEventHandler PropertyChanged; - - private readonly PBEMovesetBuilder parent; - private readonly int slotIndex; - - public PBEAlphabeticalList Allowed { get; } - private PBEMove move; - public PBEMove Move - { - get => move; - set => parent.Set(slotIndex, value, null); - } - public bool IsMoveEditable { get; private set; } - private byte ppUps; - public byte PPUps - { - get => ppUps; - set => parent.Set(slotIndex, null, value); - } - public bool IsPPUpsEditable => move != PBEMove.None; - - internal PBEMoveSlot(PBEMovesetBuilder parent, int slotIndex) - { - this.parent = parent; - this.slotIndex = slotIndex; - IsMoveEditable = slotIndex < 2; - Allowed = new PBEAlphabeticalList(new[] { PBEMove.None }); - } - - internal void Update(PBEMove? move, byte? ppUps) - { - if (move.HasValue && this.move != move.Value) - { - PBEMove old = this.move; - this.move = move.Value; - OnPropertyChanged(nameof(Move)); - if ((old == PBEMove.None && this.move != PBEMove.None) || (old != PBEMove.None && this.move == PBEMove.None)) - { - OnPropertyChanged(nameof(IsPPUpsEditable)); - } - } - if (ppUps.HasValue && this.ppUps != ppUps.Value) - { - this.ppUps = ppUps.Value; - OnPropertyChanged(nameof(PPUps)); - } - } - internal void SetEditable(bool value) - { - if (value != IsMoveEditable) - { - IsMoveEditable = value; - OnPropertyChanged(nameof(IsMoveEditable)); - } - } - internal void RemoveIfNotAllowed() - { - if (!Allowed.Contains(move)) - { - Move = Allowed[0]; - } - } - internal void RemoveIfNotAllowedSilent() - { - if (!Allowed.Contains(move)) - { - PBEMove m = Allowed[0]; - Update(m, m == PBEMove.None ? 0 : (byte?)null); - } - } - } - - 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) - { - PBEPokemonShell.ValidateSpecies(value); - species = value; - OnPropertyChanged(nameof(Species)); - SetAlloweds(); - } - } - } - private byte level; - public byte Level - { - get => level; - set - { - if (level != value) - { - PBEPokemonShell.ValidateLevel(value, Settings); - level = value; - OnPropertyChanged(nameof(Level)); - SetAlloweds(); - } - } - } - public PBESettings Settings { get; } - public PBEList MoveSlots { get; } - - /// Creates a new object with the specified traits. - /// The species 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 PBEMovesetBuilder(PBESpecies species, byte level, PBESettings settings, bool randomize) - { - if (settings == null) - { - throw new ArgumentNullException(nameof(settings)); - } - Settings = settings; - Settings.PropertyChanged += OnSettingsChanged; - PBEPokemonShell.ValidateLevel(level, Settings); - this.level = level; - PBEPokemonShell.ValidateSpecies(species); - this.species = species; - MoveSlots = new PBEList(Settings.NumMoves); - for (int i = 0; i < Settings.NumMoves; i++) - { - MoveSlots.Add(new PBEMoveSlot(this, i)); - } - SetAlloweds(); - if (randomize) - { - Randomize(); - } - } - - private void OnSettingsChanged(object sender, PropertyChangedEventArgs e) - { - switch (e.PropertyName) - { - case nameof(Settings.MaxLevel): - { - if (level > Settings.MaxLevel) - { - Level = Settings.MaxLevel; - } - break; - } - case nameof(Settings.MaxPPUps): - { - for (int i = 0; i < Settings.NumMoves; i++) - { - PBEMoveSlot slot = MoveSlots[i]; - if (slot.PPUps > Settings.MaxPPUps) - { - slot.Update(null, Settings.MaxPPUps); - } - } - break; - } - case nameof(Settings.MinLevel): - { - if (level < Settings.MinLevel) - { - Level = Settings.MinLevel; - } - break; - } - case nameof(Settings.NumMoves): - { - int oldCount = MoveSlots.Count; - if (Settings.NumMoves != oldCount) - { - if (Settings.NumMoves > oldCount) - { - int numToAdd = Settings.NumMoves - oldCount; - for (int i = 0; i < numToAdd; i++) - { - MoveSlots.Add(new PBEMoveSlot(this, oldCount + i)); - } - } - else - { - int numToRemove = oldCount - Settings.NumMoves; - for (int i = 0; i < numToRemove; i++) - { - MoveSlots.RemoveAt(oldCount - 1 - i); - } - } - SetAlloweds(); - SetEditables(); - } - break; - } - } - } - - private void SetAlloweds() - { - int i; - PBEMove[] legalMoves = PBELegalityChecker.GetLegalMoves(species, level); - var allowed = new List(legalMoves.Length + 1); - allowed.AddRange(legalMoves); - if (species == PBESpecies.Keldeo_Resolute) - { - PBEMoveSlot slot = MoveSlots[0]; - slot.Allowed.Reset(new[] { PBEMove.SecretSword }); - slot.RemoveIfNotAllowedSilent(); - allowed.Remove(PBEMove.SecretSword); - i = 1; - } - else - { - i = 0; - } - for (; i < Settings.NumMoves; i++) - { - if (i == 1) - { - allowed.Add(PBEMove.None); - } - PBEMoveSlot slot = MoveSlots[i]; - slot.Allowed.Reset(allowed); - slot.RemoveIfNotAllowedSilent(); - } - } - private void SetEditables() - { - for (int i = 2; i < Settings.NumMoves; i++) - { - MoveSlots[i].SetEditable(MoveSlots[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++) - { - MoveSlots[i].Update(PBEMove.None, 0); - } - SetEditables(); - } - /// 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++) - { - PBEMoveSlot slot = MoveSlots[i]; - PBEMove[] allowed = slot.Allowed.Except(blacklist).ToArray(); - if (allowed.Length == 0) - { - for (int j = i; j < Settings.NumMoves; j++) - { - MoveSlots[j].Update(PBEMove.None, 0); - } - break; - } - else - { - PBEMove move = allowed.RandomElement(); - if (i < Settings.NumMoves - 1) - { - blacklist.Add(move); - } - slot.Update(move, (byte)PBEUtils.RandomInt(0, Settings.MaxPPUps)); - } - } - SetEditables(); - } - /// Sets a specific move slot's move and/or PP-Ups. If using a for loop to set all values and the moveset is not already cleared, you should call first so that move indexes do not move behind your iterator as you change moves. - /// The index of the slot to change. - /// The move if it needs changing, null if the current move will remain unchanged. - /// The PP-Ups if it needs changing, null if the current PP-Ups will remain unchanged. - public void Set(int slotIndex, PBEMove? move, byte? ppUps) - { - if (slotIndex < 0 || slotIndex >= Settings.NumMoves) - { - throw new ArgumentOutOfRangeException(nameof(slotIndex)); - } - if (move.HasValue || ppUps.HasValue) - { - PBEMoveSlot slot = MoveSlots[slotIndex]; - if (move.HasValue) - { - PBEMove mVal = move.Value; - PBEMove old = slot.Move; - if (old != mVal) - { - if (!Enum.IsDefined(typeof(PBEMove), move)) - { - throw new ArgumentOutOfRangeException(nameof(move)); - } - if (!slot.IsMoveEditable) - { - throw new InvalidOperationException($"Slot {slotIndex}'s move cannot be changed because there is no move in slot {slotIndex - 1}."); - } - if (!slot.Allowed.Contains(mVal)) - { - throw new ArgumentOutOfRangeException(nameof(move), $"Slot {slotIndex} does not allow {mVal}."); - } - void UpdateSlot() - { - slot.Update(mVal, mVal == PBEMove.None ? 0 : (byte?)null); - } - if (mVal != PBEMove.None) - { - // If "move" is in another slot, place "slotIndex"'s old move at the other slot - for (int i = 0; i < Settings.NumMoves; i++) - { - if (i != slotIndex) - { - PBEMoveSlot iSlot = MoveSlots[i]; - if (iSlot.Move == mVal) - { - // 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 < slotIndex) - { - goto finish; - } - else - { - UpdateSlot(); - iSlot.Update(old, old == PBEMove.None ? 0 : (byte?)null); - 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 = Settings.NumMoves - 1; i > slotIndex; i--) - { - PBEMoveSlot iSlot = MoveSlots[i]; - if (iSlot.Move != PBEMove.None) - { - slot.Update(iSlot.Move, null); - iSlot.Update(PBEMove.None, 0); - 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 - UpdateSlot(); - editables: - SetEditables(); - finish: - ; - } - } - if (ppUps.HasValue) - { - byte pVal = ppUps.Value; - if (slot.PPUps != pVal) - { - if (pVal > Settings.MaxPPUps) - { - throw new ArgumentOutOfRangeException(nameof(ppUps), $"\"{nameof(ppUps)}\" cannot exceed \"{nameof(Settings.MaxPPUps)}\" ({Settings.MaxPPUps})."); - } - if (!slot.IsPPUpsEditable) - { - throw new InvalidOperationException($"Slot {slotIndex}'s PP-Ups cannot be changed because it has no move."); - } - slot.Update(null, pVal); - } - } - } - else - { - throw new ArgumentException($"\"{nameof(move)}\" or \"{nameof(ppUps)}\" has to have a value to set."); - } - } - } -} diff --git a/PokemonBattleEngine/Data/PBEAlphabeticalList.cs b/PokemonBattleEngine/Data/PBEAlphabeticalList.cs index c8ac2b4f8..f92fb4f5c 100644 --- a/PokemonBattleEngine/Data/PBEAlphabeticalList.cs +++ b/PokemonBattleEngine/Data/PBEAlphabeticalList.cs @@ -8,7 +8,7 @@ namespace Kermalis.PokemonBattleEngine.Data { - public sealed class PBEAlphabeticalList : IDisposable, INotifyCollectionChanged, INotifyPropertyChanged, IReadOnlyList + public sealed class PBEAlphabeticalList : INotifyCollectionChanged, INotifyPropertyChanged, IReadOnlyList { private sealed class PBEAlphabeticalListEntry { @@ -33,12 +33,6 @@ public PBEAlphabeticalListEntry(T key) } } - private void FireEvents(NotifyCollectionChangedEventArgs e) - { - OnPropertyChanged(nameof(Count)); - OnPropertyChanged("Item[]"); - OnCollectionChanged(e); - } private void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { CollectionChanged?.Invoke(this, e); @@ -50,13 +44,23 @@ private void OnPropertyChanged(string property) public event NotifyCollectionChangedEventHandler CollectionChanged; public event PropertyChangedEventHandler PropertyChanged; - private PBEAlphabeticalListEntry[] list; - public int Count => list.Length; - public T this[int index] => list[index].Key; + 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>(); + _list = Array.Empty>(); PBELocalizedString.PBECultureChanged += OnCultureChanged; } internal PBEAlphabeticalList(IEnumerable collection) @@ -69,30 +73,52 @@ private void OnCultureChanged(CultureInfo oldPBECultureInfo) { if (!oldPBECultureInfo.TwoLetterISOLanguageName.Equals(PBELocalizedString.PBECulture.TwoLetterISOLanguageName)) { - Sort(); + Sort(_list); } } - private void Sort() + private void Sort(PBEAlphabeticalListEntry[] old) { - Array.Sort(list, (x, y) => x.Value.ToString().CompareTo(y.Value.ToString())); - FireEvents(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + if (old == null || old == _list) + { + old = (PBEAlphabeticalListEntry[])_list.Clone(); + } + Array.Sort(_list, (x, y) => x.Value.ToString().CompareTo(y.Value.ToString())); + if (!_list.SequenceEqual(old)) + { + OnPropertyChanged("Item[]"); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } } + private bool _isDisposed = false; + internal void Dispose() + { + if (!_isDisposed) + { + _isDisposed = true; + PBELocalizedString.PBECultureChanged -= OnCultureChanged; + } + } internal void Reset(IEnumerable collection) { if (collection == null) { throw new ArgumentNullException(nameof(collection)); } + PBEAlphabeticalListEntry[] old = _list; if (collection is PBEAlphabeticalList other) { - list = (PBEAlphabeticalListEntry[])other.list.Clone(); + _list = (PBEAlphabeticalListEntry[])other._list.Clone(); } else { - list = collection.Select(t => new PBEAlphabeticalListEntry(t)).ToArray(); + _list = collection.Select(t => new PBEAlphabeticalListEntry(t)).ToArray(); + } + if (old != null && old.Length != _list.Length) + { + OnPropertyChanged(nameof(Count)); } - Sort(); + Sort(old); } public bool Contains(T item) @@ -103,9 +129,9 @@ public int IndexOf(T item) { if (item != null) { - for (int i = 0; i < list.Length; i++) + for (int i = 0; i < _list.Length; i++) { - if (item.Equals(list[i].Key)) + if (item.Equals(_list[i].Key)) { return i; } @@ -114,15 +140,11 @@ public int IndexOf(T item) return -1; } - public void Dispose() - { - PBELocalizedString.PBECultureChanged -= OnCultureChanged; - } public IEnumerator GetEnumerator() { - for (int i = 0; i < list.Length; i++) + for (int i = 0; i < _list.Length; i++) { - yield return list[i].Key; + yield return _list[i].Key; } } IEnumerator IEnumerable.GetEnumerator() diff --git a/PokemonBattleEngine/Data/PBEList.cs b/PokemonBattleEngine/Data/PBEList.cs index 0e9261930..05b4a39e6 100644 --- a/PokemonBattleEngine/Data/PBEList.cs +++ b/PokemonBattleEngine/Data/PBEList.cs @@ -3,17 +3,12 @@ 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 FireEvents(NotifyCollectionChangedEventArgs e) - { - OnPropertyChanged(nameof(Count)); - OnPropertyChanged("Item[]"); - OnCollectionChanged(e); - } private void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { CollectionChanged?.Invoke(this, e); @@ -25,52 +20,70 @@ private void OnPropertyChanged(string property) public event NotifyCollectionChangedEventHandler CollectionChanged; public event PropertyChangedEventHandler PropertyChanged; - private readonly List list; - public int Count => list.Count; - public T this[int index] => list[index]; + 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(); + _list = new List(); } internal PBEList(int capacity) { - list = new List(capacity); + _list = new List(capacity); } internal void Add(T item) { - int index = list.Count; - list.Insert(index, item); - FireEvents(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); + 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); - FireEvents(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); + _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); + int index = _list.IndexOf(item); bool b = index != -1; if (b) { - list.RemoveAt(index); - FireEvents(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index)); + _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) + if (index < 0 || index >= _list.Count) { throw new ArgumentOutOfRangeException(nameof(index)); } else { - T item = list[index]; - list.RemoveAt(index); - FireEvents(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index)); + T item = _list[index]; + _list.RemoveAt(index); + OnPropertyChanged(nameof(Count)); + OnPropertyChanged("Item[]"); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index)); } } internal void Reset(IEnumerable collection) @@ -79,27 +92,39 @@ internal void Reset(IEnumerable collection) { throw new ArgumentNullException(nameof(collection)); } - list.Clear(); - list.AddRange(collection); - FireEvents(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + 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)); + } } public bool Contains(T item) { - return list.IndexOf(item) != -1; + return _list.IndexOf(item) != -1; } public int IndexOf(T item) { - return list.IndexOf(item); + return _list.IndexOf(item); } public IEnumerator GetEnumerator() { - return list.GetEnumerator(); + for (int i = 0; i < _list.Count; i++) + { + yield return _list[i]; + } } IEnumerator IEnumerable.GetEnumerator() { - return list.GetEnumerator(); + return GetEnumerator(); } } } diff --git a/PokemonBattleEngine/Data/PokemonData.cs b/PokemonBattleEngine/Data/PokemonData.cs index dc005c1b0..c2ecdae5e 100644 --- a/PokemonBattleEngine/Data/PokemonData.cs +++ b/PokemonBattleEngine/Data/PokemonData.cs @@ -38,10 +38,18 @@ private PBEPokemonData(byte[] baseStats, public bool HasAbility(PBEAbility ability) { + if (ability >= PBEAbility.MAX) + { + throw new ArgumentOutOfRangeException(nameof(ability)); + } return Abilities.Contains(ability); } public bool HasType(PBEType type) { + if (type >= PBEType.MAX) + { + throw new ArgumentOutOfRangeException(nameof(type)); + } return Type1 == type || Type2 == type; } @@ -494,12 +502,23 @@ public bool HasType(PBEType type) PBEType.Dark // 1.5625 % }); - /// - /// Calculates a species's stats based on its level, IVs, EVs, and nature. - /// + /// Calculates a species's stats based on its level, IVs, EVs, and nature. /// Thrown when is invalid. public static ushort CalculateStat(PBEStat stat, PBESpecies species, PBENature nature, byte evs, byte ivs, byte level, PBESettings settings) { + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + if (!Enum.IsDefined(typeof(PBESpecies), species)) + { + throw new ArgumentOutOfRangeException(nameof(species)); + } + PBEPokemonShell.ValidateLevel(level, settings); + if (ivs > settings.MaxIVs) + { + throw new ArgumentOutOfRangeException(nameof(ivs)); + } switch (stat) { case PBEStat.HP: @@ -521,6 +540,15 @@ public static ushort CalculateStat(PBEStat stat, PBESpecies species, PBENature n } public static void GetStatRange(PBEStat stat, PBESpecies species, byte level, PBESettings settings, out ushort low, out ushort high) { + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + if (!Enum.IsDefined(typeof(PBESpecies), species)) + { + throw new ArgumentOutOfRangeException(nameof(species)); + } + PBEPokemonShell.ValidateLevel(level, settings); switch (stat) { case PBEStat.HP: @@ -595,6 +623,10 @@ private class SearchResult } public static PBEPokemonData GetData(PBESpecies species) { + if (!Enum.IsDefined(typeof(PBESpecies), species)) + { + throw new ArgumentOutOfRangeException(nameof(species)); + } string json = PBEUtils.QueryDatabase($"SELECT * FROM PokemonData WHERE Id={(uint)species}")[0].Json; using (var reader = new JsonTextReader(new StringReader(json))) { diff --git a/PokemonBattleEngine/Data/PokemonShell.cs b/PokemonBattleEngine/Data/PokemonShell.cs index 3462409d2..67d7c1fe0 100644 --- a/PokemonBattleEngine/Data/PokemonShell.cs +++ b/PokemonBattleEngine/Data/PokemonShell.cs @@ -8,9 +8,7 @@ namespace Kermalis.PokemonBattleEngine.Data { - // TODO: What should happen if you continue using this after it was removed from a PBETeamShell? - // This should be able to be used aside from team shells (which would help event pokemon be represented again) - public sealed class PBEPokemonShell : INotifyPropertyChanged + public sealed class PBEPokemonShell : IDisposable, INotifyPropertyChanged { public static PBEAlphabeticalList AllSpecies { get; } = new PBEAlphabeticalList( Enum.GetValues(typeof(PBESpecies)) @@ -38,292 +36,231 @@ private void OnPropertyChanged(string property) } public event PropertyChangedEventHandler PropertyChanged; - private readonly PBETeamShell parent; + public PBESettings Settings { get; } - private PBEPokemonData pData; + private PBEPokemonData _pData; public PBEAlphabeticalList SelectableAbilities { get; } = new PBEAlphabeticalList(); public PBEAlphabeticalList SelectableGenders { get; } = new PBEAlphabeticalList(); public PBEAlphabeticalList SelectableItems { get; } = new PBEAlphabeticalList(); - private PBESpecies species; + private PBESpecies _species; public PBESpecies Species { - get => species; + get => _species; set { - if (species != value) + if (_species != value) { ValidateSpecies(value); - PBESpecies old = species; - species = value; + PBESpecies old = _species; + _species = value; OnPropertyChanged(nameof(Species)); OnSpeciesChanged(old); } } } - private string nickname; + private string _nickname; public string Nickname { - get => nickname; + get => _nickname; set { - if (nickname != value) + if (_nickname != value) { - ValidateNickname(value, parent.Settings); - nickname = value; + ValidateNickname(value, Settings); + _nickname = value; OnPropertyChanged(nameof(Nickname)); } } } - private byte level; + private byte _level; public byte Level { - get => level; + get => _level; set { - if (level != value) + if (_level != value) { - ValidateLevel(value, parent.Settings); - level = value; + ValidateLevel(value, Settings); + _level = value; OnPropertyChanged(nameof(Level)); Moveset.Level = value; } } } - private byte friendship; + private byte _friendship; public byte Friendship { - get => friendship; + get => _friendship; set { - if (value != friendship) + if (value != _friendship) { - friendship = value; + _friendship = value; OnPropertyChanged(nameof(Friendship)); } } } - private bool shiny; + private bool _shiny; public bool Shiny { - get => shiny; + get => _shiny; set { - if (value != shiny) + if (value != _shiny) { - shiny = value; + _shiny = value; OnPropertyChanged(nameof(Shiny)); } } } - private PBEAbility ability; + private PBEAbility _ability; public PBEAbility Ability { - get => ability; + get => _ability; set { - if (value != ability) + if (value != _ability) { ValidateAbility(value); - ability = value; + _ability = value; OnPropertyChanged(nameof(Ability)); } } } - private PBENature nature; + private PBENature _nature; public PBENature Nature { - get => nature; + get => _nature; set { - if (value != nature) + if (value != _nature) { ValidateNature(value); - nature = value; + _nature = value; OnPropertyChanged(nameof(Nature)); } } } - private PBEGender gender; + private PBEGender _gender; public PBEGender Gender { - get => gender; + get => _gender; set { - if (value != gender) + if (value != _gender) { ValidateGender(value); - gender = value; + _gender = value; OnPropertyChanged(nameof(Gender)); } } } - private PBEItem item; + private PBEItem _item; public PBEItem Item { - get => item; + get => _item; set { - if (value != item) + if (value != _item) { ValidateItem(value); - item = value; + _item = value; OnPropertyChanged(nameof(Item)); } } } - public PBEEffortValueCollection EffortValues { get; private set; } - public PBEIndividualValueCollection IndividualValues { get; private set; } - public PBEMovesetBuilder Moveset { get; private set; } + public PBEEffortValues EffortValues { get; } + public PBEIndividualValues IndividualValues { get; } + public PBEMoveset Moveset { get; } - internal PBEPokemonShell(BinaryReader r, PBETeamShell parent) + internal PBEPokemonShell(PBESettings settings, BinaryReader r) { - this.parent = parent; + Settings = settings; var species = (PBESpecies)r.ReadUInt32(); ValidateSpecies(species); - this.species = species; + _species = species; SetSelectable(); string nickname = PBEUtils.StringFromBytes(r); - ValidateNickname(nickname, parent.Settings); - this.nickname = nickname; + ValidateNickname(nickname, Settings); + _nickname = nickname; byte level = r.ReadByte(); - ValidateLevel(level, parent.Settings); - this.level = level; - friendship = r.ReadByte(); - shiny = r.ReadBoolean(); + ValidateLevel(level, Settings); + _level = level; + _friendship = r.ReadByte(); + _shiny = r.ReadBoolean(); var ability = (PBEAbility)r.ReadByte(); ValidateAbility(ability); - this.ability = ability; + _ability = ability; var nature = (PBENature)r.ReadByte(); ValidateNature(nature); - this.nature = nature; + _nature = nature; var gender = (PBEGender)r.ReadByte(); ValidateGender(gender); - this.gender = gender; + _gender = gender; var item = (PBEItem)r.ReadUInt16(); ValidateItem(item); - this.item = item; - byte hpEV = r.ReadByte(); - byte attackEV = r.ReadByte(); - byte defenseEV = r.ReadByte(); - byte spAttackEV = r.ReadByte(); - byte spDefenseEV = r.ReadByte(); - byte speedEV = r.ReadByte(); - if (hpEV + attackEV + defenseEV + spAttackEV + spDefenseEV + speedEV > parent.Settings.MaxTotalEVs) - { - throw new ArgumentOutOfRangeException(nameof(EffortValues), $"Total must not exceed \"{nameof(parent.Settings.MaxTotalEVs)}\" ({parent.Settings.MaxTotalEVs})"); - } - EffortValues = new PBEEffortValueCollection(parent.Settings, hpEV, attackEV, defenseEV, spAttackEV, spDefenseEV, speedEV); - void ValidateIV(byte val, string name) - { - if (val > parent.Settings.MaxIVs) - { - throw new ArgumentOutOfRangeException(nameof(IndividualValues), $"\"{name}\" value must not exceed \"{nameof(parent.Settings.MaxIVs)}\" ({parent.Settings.MaxIVs})"); - } - } - byte hpIV = r.ReadByte(); - ValidateIV(hpIV, nameof(PBEStat.HP)); - byte attackIV = r.ReadByte(); - ValidateIV(attackIV, nameof(PBEStat.Attack)); - byte defenseIV = r.ReadByte(); - ValidateIV(defenseIV, nameof(PBEStat.Defense)); - byte spAttackIV = r.ReadByte(); - ValidateIV(spAttackIV, nameof(PBEStat.SpAttack)); - byte spDefenseIV = r.ReadByte(); - ValidateIV(spDefenseIV, nameof(PBEStat.SpDefense)); - byte speedIV = r.ReadByte(); - ValidateIV(speedIV, nameof(PBEStat.Speed)); - IndividualValues = new PBEIndividualValueCollection(parent.Settings, hpIV, attackIV, defenseIV, spAttackIV, spDefenseIV, speedIV); - Moveset = new PBEMovesetBuilder(species, level, parent.Settings, false); - for (int i = 0; i < parent.Settings.NumMoves; i++) - { - var move = (PBEMove)r.ReadUInt16(); - byte ppUps = r.ReadByte(); - Moveset.Set(i, move, ppUps); // "Set()" will throw its own exceptions for invalid moves and pp-ups - // The following check is for the case where identical moves were stored in the same moveset, therefore forcing "Set()" to overwrite one with PBEMove.None - PBEMovesetBuilder.PBEMoveSlot slot = Moveset.MoveSlots[i]; - if (slot.Move != move || slot.PPUps != ppUps) - { - throw new InvalidDataException("Invalid moveset."); - } - } + _item = item; + Settings.PropertyChanged += OnSettingsChanged; + EffortValues = new PBEEffortValues(Settings, r); + IndividualValues = new PBEIndividualValues(Settings, r); + Moveset = new PBEMoveset(species, level, Settings, r); } - internal PBEPokemonShell(JToken jObj, PBETeamShell parent) + internal PBEPokemonShell(PBESettings settings, JToken jToken) { - this.parent = parent; - friendship = jObj[nameof(Friendship)].Value(); - shiny = jObj[nameof(Shiny)].Value(); - byte level = jObj[nameof(Level)].Value(); - ValidateLevel(level, parent.Settings); - this.level = level; - string nickname = jObj[nameof(Nickname)].Value(); - ValidateNickname(nickname, parent.Settings); - this.nickname = nickname; - PBENature nature = PBELocalizedString.GetNatureByName(jObj[nameof(Nature)].Value()).Value; + Settings = settings; + _friendship = jToken[nameof(Friendship)].Value(); + _shiny = jToken[nameof(Shiny)].Value(); + byte level = jToken[nameof(Level)].Value(); + ValidateLevel(level, Settings); + _level = level; + string nickname = jToken[nameof(Nickname)].Value(); + ValidateNickname(nickname, Settings); + _nickname = nickname; + PBENature nature = PBELocalizedString.GetNatureByName(jToken[nameof(Nature)].Value()).Value; ValidateNature(nature); - this.nature = nature; - PBESpecies species = PBELocalizedString.GetSpeciesByName(jObj[nameof(Species)].Value()).Value; + _nature = nature; + PBESpecies species = PBELocalizedString.GetSpeciesByName(jToken[nameof(Species)].Value()).Value; ValidateSpecies(species); - this.species = species; + _species = species; SetSelectable(); - PBEAbility ability = PBELocalizedString.GetAbilityByName(jObj[nameof(Ability)].Value()).Value; + PBEAbility ability = PBELocalizedString.GetAbilityByName(jToken[nameof(Ability)].Value()).Value; ValidateAbility(ability); - this.ability = ability; - PBEGender gender = PBELocalizedString.GetGenderByName(jObj[nameof(Gender)].Value()).Value; + _ability = ability; + PBEGender gender = PBELocalizedString.GetGenderByName(jToken[nameof(Gender)].Value()).Value; ValidateGender(gender); - this.gender = gender; - PBEItem item = PBELocalizedString.GetItemByName(jObj[nameof(Item)].Value()).Value; + _gender = gender; + PBEItem item = PBELocalizedString.GetItemByName(jToken[nameof(Item)].Value()).Value; ValidateItem(item); - this.item = item; - JToken eiObj = jObj[nameof(EffortValues)]; - EffortValues = new PBEEffortValueCollection(parent.Settings, - eiObj[nameof(PBEStat.HP)].Value(), - eiObj[nameof(PBEStat.Attack)].Value(), - eiObj[nameof(PBEStat.Defense)].Value(), - eiObj[nameof(PBEStat.SpAttack)].Value(), - eiObj[nameof(PBEStat.SpDefense)].Value(), - eiObj[nameof(PBEStat.Speed)].Value() - ); - eiObj = jObj[nameof(IndividualValues)]; - IndividualValues = new PBEIndividualValueCollection(parent.Settings, - eiObj[nameof(PBEStat.HP)].Value(), - eiObj[nameof(PBEStat.Attack)].Value(), - eiObj[nameof(PBEStat.Defense)].Value(), - eiObj[nameof(PBEStat.SpAttack)].Value(), - eiObj[nameof(PBEStat.SpDefense)].Value(), - eiObj[nameof(PBEStat.Speed)].Value() - ); - var mObj = (JArray)jObj[nameof(Moveset)]; - Moveset = new PBEMovesetBuilder(species, level, parent.Settings, false); - for (int j = 0; j < parent.Settings.NumMoves; j++) - { - eiObj = mObj[j]; - Moveset.Set(j, - PBELocalizedString.GetMoveByName(eiObj[nameof(PBEMovesetBuilder.PBEMoveSlot.Move)].Value()).Value, - eiObj[nameof(PBEMovesetBuilder.PBEMoveSlot.PPUps)].Value() - ); - } + _item = item; + Settings.PropertyChanged += OnSettingsChanged; + EffortValues = new PBEEffortValues(Settings, jToken[nameof(EffortValues)]); + IndividualValues = new PBEIndividualValues(Settings, jToken[nameof(IndividualValues)]); + Moveset = new PBEMoveset(species, level, Settings, (JArray)jToken[nameof(Moveset)]); } - internal PBEPokemonShell(PBESpecies species, byte level, PBETeamShell parent) + public PBEPokemonShell(PBESpecies species, byte level, PBESettings settings) { - ValidateLevel(level, parent.Settings); + ValidateLevel(level, settings); ValidateSpecies(species); - this.parent = parent; - this.species = species; - this.level = level; - friendship = (byte)PBEUtils.RandomInt(0, byte.MaxValue); - shiny = PBEUtils.RandomShiny(); - nature = AllNatures.RandomElement(); - EffortValues = new PBEEffortValueCollection(parent.Settings, true); - IndividualValues = new PBEIndividualValueCollection(parent.Settings, true); + Settings = settings; + Settings.PropertyChanged += OnSettingsChanged; + _canDispose = true; + _species = species; + _level = level; + _friendship = (byte)PBEUtils.RandomInt(0, byte.MaxValue); + _shiny = PBEUtils.RandomShiny(); + _nature = AllNatures.RandomElement(); + EffortValues = new PBEEffortValues(Settings, true) { CanDispose = false }; + IndividualValues = new PBEIndividualValues(Settings, true) { CanDispose = false }; + Moveset = new PBEMoveset(_species, _level, Settings, true) { CanDispose = false }; OnSpeciesChanged(0); } private void SetSelectable() { - pData = PBEPokemonData.GetData(species); - SelectableAbilities.Reset(pData.Abilities); + _pData = PBEPokemonData.GetData(_species); + SelectableAbilities.Reset(_pData.Abilities); PBEGender[] selectableGenders; - switch (pData.GenderRatio) + switch (_pData.GenderRatio) { case PBEGenderRatio.M0_F0: selectableGenders = new[] { PBEGender.Genderless }; break; case PBEGenderRatio.M1_F0: selectableGenders = new[] { PBEGender.Male }; break; @@ -332,7 +269,7 @@ private void SetSelectable() } SelectableGenders.Reset(selectableGenders); IEnumerable selectableItems; - switch (species) + switch (_species) { case PBESpecies.Giratina: selectableItems = AllItems.Except(new[] { PBEItem.GriseousOrb }); break; case PBESpecies.Giratina_Origin: selectableItems = new[] { PBEItem.GriseousOrb }; break; @@ -371,62 +308,58 @@ private void SetSelectable() private void OnSpeciesChanged(PBESpecies oldSpecies) { SetSelectable(); - if (oldSpecies == 0 || nickname == PBELocalizedString.GetSpeciesName(oldSpecies).ToString()) + if (oldSpecies == 0 || _nickname == PBELocalizedString.GetSpeciesName(oldSpecies).ToString()) { - string newNickname = PBELocalizedString.GetSpeciesName(species).ToString(); - if (newNickname.Length > parent.Settings.MaxPokemonNameLength) + string newNickname = PBELocalizedString.GetSpeciesName(_species).ToString(); + if (newNickname.Length > Settings.MaxPokemonNameLength) { - newNickname = newNickname.Substring(0, parent.Settings.MaxPokemonNameLength); + newNickname = newNickname.Substring(0, Settings.MaxPokemonNameLength); } Nickname = newNickname; } - if (oldSpecies == 0 || !SelectableAbilities.Contains(ability)) + if (oldSpecies == 0 || !SelectableAbilities.Contains(_ability)) { Ability = SelectableAbilities.RandomElement(); } - if (oldSpecies == 0 || !SelectableGenders.Contains(gender)) + if (oldSpecies == 0 || !SelectableGenders.Contains(_gender)) { - Gender = PBEUtils.RandomGender(pData.GenderRatio); + Gender = PBEUtils.RandomGender(_pData.GenderRatio); } - if (oldSpecies == 0 || !SelectableItems.Contains(item)) + if (oldSpecies == 0 || !SelectableItems.Contains(_item)) { Item = SelectableItems.RandomElement(); } - if (oldSpecies == 0) + if (oldSpecies != 0) { - Moveset = new PBEMovesetBuilder(species, level, parent.Settings, true); - } - else - { - Moveset.Species = species; + Moveset.Species = _species; } } - internal void OnSettingsChanged(object sender, PropertyChangedEventArgs e) + private void OnSettingsChanged(object sender, PropertyChangedEventArgs e) { switch (e.PropertyName) { - case nameof(parent.Settings.MaxLevel): + case nameof(Settings.MaxLevel): { - if (level > parent.Settings.MaxLevel) + if (_level > Settings.MaxLevel) { - Level = parent.Settings.MaxLevel; + Level = Settings.MaxLevel; } break; } - case nameof(parent.Settings.MaxPokemonNameLength): + case nameof(Settings.MaxPokemonNameLength): { - if (nickname.Length > parent.Settings.MaxPokemonNameLength) + if (_nickname.Length > Settings.MaxPokemonNameLength) { - Nickname = nickname.Substring(0, parent.Settings.MaxPokemonNameLength); + Nickname = _nickname.Substring(0, Settings.MaxPokemonNameLength); } break; } - case nameof(parent.Settings.MinLevel): + case nameof(Settings.MinLevel): { - if (level < parent.Settings.MinLevel) + if (_level < Settings.MinLevel) { - Level = parent.Settings.MinLevel; + Level = Settings.MinLevel; } break; } @@ -487,77 +420,86 @@ private void ValidateItem(PBEItem value) } } - internal List ToBytes() + internal void ToBytes(List bytes) { - var bytes = new List(); - bytes.AddRange(BitConverter.GetBytes((uint)species)); - bytes.AddRange(PBEUtils.StringToBytes(nickname)); - bytes.Add(level); - bytes.Add(friendship); - bytes.Add((byte)(shiny ? 1 : 0)); - bytes.Add((byte)ability); - bytes.Add((byte)nature); - bytes.Add((byte)gender); - bytes.AddRange(BitConverter.GetBytes((ushort)item)); - bytes.AddRange(EffortValues.Select(ev => ev.Value)); - bytes.AddRange(IndividualValues.Select(iv => iv.Value)); - foreach (PBEMovesetBuilder.PBEMoveSlot slot in Moveset.MoveSlots) - { - bytes.AddRange(BitConverter.GetBytes((ushort)slot.Move)); - bytes.Add(slot.PPUps); - } - return bytes; + bytes.AddRange(BitConverter.GetBytes((uint)_species)); + PBEUtils.StringToBytes(bytes, _nickname); + bytes.Add(_level); + bytes.Add(_friendship); + bytes.Add((byte)(_shiny ? 1 : 0)); + bytes.Add((byte)_ability); + bytes.Add((byte)_nature); + bytes.Add((byte)_gender); + bytes.AddRange(BitConverter.GetBytes((ushort)_item)); + EffortValues.ToBytes(bytes); + IndividualValues.ToBytes(bytes); + Moveset.ToBytes(bytes); } internal void ToJson(JsonTextWriter w) { w.WriteStartObject(); w.WritePropertyName(nameof(Species)); - w.WriteValue(species.ToString()); + w.WriteValue(_species.ToString()); w.WritePropertyName(nameof(Nickname)); - w.WriteValue(nickname); + w.WriteValue(_nickname); w.WritePropertyName(nameof(Level)); - w.WriteValue(level); + w.WriteValue(_level); w.WritePropertyName(nameof(Friendship)); - w.WriteValue(friendship); + w.WriteValue(_friendship); w.WritePropertyName(nameof(Shiny)); - w.WriteValue(shiny); + w.WriteValue(_shiny); w.WritePropertyName(nameof(Ability)); - w.WriteValue(ability.ToString()); + w.WriteValue(_ability.ToString()); w.WritePropertyName(nameof(Nature)); - w.WriteValue(nature.ToString()); + w.WriteValue(_nature.ToString()); w.WritePropertyName(nameof(Gender)); - w.WriteValue(gender.ToString()); + w.WriteValue(_gender.ToString()); w.WritePropertyName(nameof(Item)); - w.WriteValue(item.ToString()); + w.WriteValue(_item.ToString()); w.WritePropertyName(nameof(EffortValues)); - w.WriteStartObject(); - foreach (PBEEffortValueCollection.PBEEffortValue ev in EffortValues) + EffortValues.ToJson(w); + w.WritePropertyName(nameof(IndividualValues)); + IndividualValues.ToJson(w); + w.WritePropertyName(nameof(Moveset)); + Moveset.ToJson(w); + w.WriteEndObject(); + } + + private bool _canDispose; + public bool CanDispose + { + get => _canDispose; + set { - w.WritePropertyName(ev.Stat.ToString()); - w.WriteValue(ev.Value); + if (_canDispose != value) + { + _canDispose = value; + OnPropertyChanged(nameof(CanDispose)); + } } - w.WriteEndObject(); - w.WritePropertyName(nameof(IndividualValues)); - w.WriteStartObject(); - foreach (PBEIndividualValueCollection.PBEIndividualValue iv in IndividualValues) + } + public bool IsDisposed { get; private set; } + public void Dispose() + { + if (!_canDispose) { - w.WritePropertyName(iv.Stat.ToString()); - w.WriteValue(iv.Value); + throw new InvalidOperationException(); } - w.WriteEndObject(); - w.WritePropertyName(nameof(Moveset)); - w.WriteStartArray(); - foreach (PBEMovesetBuilder.PBEMoveSlot slot in Moveset.MoveSlots) + if (!IsDisposed) { - w.WriteStartObject(); - w.WritePropertyName(nameof(PBEMovesetBuilder.PBEMoveSlot.Move)); - w.WriteValue(slot.Move.ToString()); - w.WritePropertyName(nameof(PBEMovesetBuilder.PBEMoveSlot.PPUps)); - w.WriteValue(slot.PPUps); - w.WriteEndObject(); + IsDisposed = true; + OnPropertyChanged(nameof(IsDisposed)); + Settings.PropertyChanged -= OnSettingsChanged; + SelectableAbilities.Dispose(); + SelectableGenders.Dispose(); + SelectableItems.Dispose(); + EffortValues.CanDispose = true; + EffortValues.Dispose(); + IndividualValues.CanDispose = true; + IndividualValues.Dispose(); + Moveset.CanDispose = true; + Moveset.Dispose(); } - w.WriteEndArray(); - w.WriteEndObject(); } } } diff --git a/PokemonBattleEngine/Data/Settings.cs b/PokemonBattleEngine/Data/Settings.cs index d53c9435b..cc7951958 100644 --- a/PokemonBattleEngine/Data/Settings.cs +++ b/PokemonBattleEngine/Data/Settings.cs @@ -16,16 +16,16 @@ private void OnPropertyChanged(string property) /// Fires whenever a property changes. public event PropertyChangedEventHandler PropertyChanged; - private bool isReadOnly; + private bool _isReadOnly; /// Gets a value that indicates whether this object is read-only. public bool IsReadOnly { - get => isReadOnly; + get => _isReadOnly; private set { - if (isReadOnly != value) + if (_isReadOnly != value) { - isReadOnly = value; + _isReadOnly = value; OnPropertyChanged(nameof(IsReadOnly)); } } @@ -42,805 +42,801 @@ static PBESettings() /// The default value of . public const byte DefaultMaxLevel = 100; - private byte maxLevel = DefaultMaxLevel; + private byte _maxLevel = DefaultMaxLevel; /// The maximum level a Pokémon can be. Not used in stat/damage calculation. public byte MaxLevel { - get => maxLevel; + get => _maxLevel; set { ReadOnlyCheck(); - if (maxLevel != value) + if (_maxLevel != value) { - if (value < minLevel) + if (value < _minLevel) { - throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(MaxLevel)} must be at least {nameof(MinLevel)} ({minLevel})."); + throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(MaxLevel)} must be at least {nameof(MinLevel)} ({_minLevel})."); } - maxLevel = value; + _maxLevel = value; OnPropertyChanged(nameof(MaxLevel)); } } } /// The default value of . public const byte DefaultMinLevel = 1; - private byte minLevel = DefaultMinLevel; + private byte _minLevel = DefaultMinLevel; /// The minimum level a Pokémon can be. public byte MinLevel { - get => minLevel; + get => _minLevel; set { ReadOnlyCheck(); - if (minLevel != value) + if (_minLevel != value) { - if (value < 1 || value > maxLevel) + if (value < 1 || value > _maxLevel) { - throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(MinLevel)} must be at least 1 and cannot exceed {nameof(MaxLevel)} ({maxLevel})."); + throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(MinLevel)} must be at least 1 and cannot exceed {nameof(MaxLevel)} ({_maxLevel})."); } - minLevel = value; + _minLevel = value; OnPropertyChanged(nameof(MinLevel)); } } } /// The default value of . public const sbyte DefaultMaxPartySize = 6; - private sbyte maxPartySize = DefaultMaxPartySize; + private sbyte _maxPartySize = DefaultMaxPartySize; /// The maximum amount of Pokémon each team can bring into a battle. public sbyte MaxPartySize { - get => maxPartySize; + get => _maxPartySize; set { ReadOnlyCheck(); - if (maxPartySize != value) + if (_maxPartySize != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(MaxPartySize)} must be at least 1."); } - maxPartySize = value; + _maxPartySize = value; OnPropertyChanged(nameof(MaxPartySize)); } } } /// The default value of . public const byte DefaultMaxPokemonNameLength = 10; - private byte maxPokemonNameLength = DefaultMaxPokemonNameLength; + private byte _maxPokemonNameLength = DefaultMaxPokemonNameLength; /// The maximum amount of characters a Pokémon nickname can have. public byte MaxPokemonNameLength { - get => maxPokemonNameLength; + get => _maxPokemonNameLength; set { ReadOnlyCheck(); - if (maxPokemonNameLength != value) + if (_maxPokemonNameLength != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(MaxPokemonNameLength)} must be at least 1."); } - maxPokemonNameLength = value; + _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; + 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; + get => _maxTrainerNameLength; set { ReadOnlyCheck(); - if (maxTrainerNameLength != value) + if (_maxTrainerNameLength != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(MaxTrainerNameLength)} must be at least 1."); } - maxTrainerNameLength = value; + _maxTrainerNameLength = value; OnPropertyChanged(nameof(MaxTrainerNameLength)); } } } /// The default value of . public const ushort DefaultMaxTotalEVs = 510; - private ushort maxTotalEVs = DefaultMaxTotalEVs; + private ushort _maxTotalEVs = DefaultMaxTotalEVs; /// The maximum sum of a Pokémon's EVs. public ushort MaxTotalEVs { - get => maxTotalEVs; + get => _maxTotalEVs; set { const int max = byte.MaxValue * 6; ReadOnlyCheck(); - if (maxTotalEVs != value) + if (_maxTotalEVs != value) { if (value > max) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(MaxTotalEVs)} must not exceed {max}."); } - maxTotalEVs = value; + _maxTotalEVs = value; OnPropertyChanged(nameof(MaxTotalEVs)); } } } /// The default value of . public const byte DefaultMaxIVs = 31; - private byte maxIVs = DefaultMaxIVs; + 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; + get => _maxIVs; set { ReadOnlyCheck(); - if (maxIVs != value) + if (_maxIVs != value) { - maxIVs = value; + _maxIVs = value; OnPropertyChanged(nameof(MaxIVs)); } } } /// The default value of . public const double DefaultNatureStatBoost = 0.1; - private double natureStatBoost = DefaultNatureStatBoost; + private double _natureStatBoost = DefaultNatureStatBoost; /// The amount of influence a Pokémon's has on its stats. public double NatureStatBoost { - get => natureStatBoost; + get => _natureStatBoost; set { ReadOnlyCheck(); - if (natureStatBoost != value) + if (_natureStatBoost != value) { if (value < 0) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(NatureStatBoost)} must be at least 0."); } - natureStatBoost = value; + _natureStatBoost = value; OnPropertyChanged(nameof(NatureStatBoost)); } } } /// The default value of . public const sbyte DefaultMaxStatChange = 6; - private sbyte maxStatChange = DefaultMaxStatChange; + private sbyte _maxStatChange = DefaultMaxStatChange; /// The maximum change a stat can have in the negative and positive direction. public sbyte MaxStatChange { - get => maxStatChange; + get => _maxStatChange; set { ReadOnlyCheck(); - if (maxStatChange != value) + if (_maxStatChange != value) { - maxStatChange = value; + _maxStatChange = value; OnPropertyChanged(nameof(MaxStatChange)); } } } /// The default value of . public const byte DefaultNumMoves = 4; - private byte numMoves = DefaultNumMoves; + private byte _numMoves = DefaultNumMoves; /// The maximum amount of moves a specific Pokémon can remember at once. public byte NumMoves { - get => numMoves; + get => _numMoves; set { ReadOnlyCheck(); - if (numMoves != value) + if (_numMoves != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(NumMoves)} must be at least 1."); } - numMoves = value; + _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. - /// - /// Growl is a tier 8 move, so the maximum PP will be 64. The formula: Max(1, ((tier * PPMultiplier) + (tier * PPUps))). - /// will change the max PP of each copied move to PPMultiplier (1 if the move is tier 0). - /// + 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; + get => _ppMultiplier; set { ReadOnlyCheck(); - if (ppMultiplier != value) + if (_ppMultiplier != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(PPMultiplier)} must be at least 1."); } - ppMultiplier = value; + _ppMultiplier = value; OnPropertyChanged(nameof(PPMultiplier)); } } } /// The default value of . public const byte DefaultMaxPPUps = 3; - private byte maxPPUps = DefaultMaxPPUps; + 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; + get => _maxPPUps; set { ReadOnlyCheck(); - if (maxPPUps != value) + if (_maxPPUps != value) { - maxPPUps = value; + _maxPPUps = value; OnPropertyChanged(nameof(MaxPPUps)); } } } /// The default value of . public const double DefaultCritMultiplier = 2.0; - private double critMultiplier = DefaultCritMultiplier; + private double _critMultiplier = DefaultCritMultiplier; /// The damage boost awarded by critical hits. public double CritMultiplier { - get => critMultiplier; + get => _critMultiplier; set { ReadOnlyCheck(); - if (critMultiplier != value) + if (_critMultiplier != value) { - critMultiplier = value; + _critMultiplier = value; OnPropertyChanged(nameof(CritMultiplier)); } } } /// The default value of . public const byte DefaultConfusionMaxTurns = 4; - private byte confusionMaxTurns = DefaultConfusionMaxTurns; + private byte _confusionMaxTurns = DefaultConfusionMaxTurns; /// The maximum amount of turns a Pokémon can be . public byte ConfusionMaxTurns { - get => confusionMaxTurns; + get => _confusionMaxTurns; set { ReadOnlyCheck(); - if (confusionMaxTurns != value) + if (_confusionMaxTurns != value) { - if (value < confusionMinTurns) + if (value < _confusionMinTurns) { - throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(ConfusionMaxTurns)} must be at least {nameof(ConfusionMinTurns)} ({confusionMinTurns})."); + throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(ConfusionMaxTurns)} must be at least {nameof(ConfusionMinTurns)} ({_confusionMinTurns})."); } - confusionMaxTurns = value; + _confusionMaxTurns = value; OnPropertyChanged(nameof(ConfusionMaxTurns)); } } } /// The default value of . public const byte DefaultConfusionMinTurns = 1; - private byte confusionMinTurns = DefaultConfusionMinTurns; + private byte _confusionMinTurns = DefaultConfusionMinTurns; /// The minimum amount of turns a Pokémon can be . public byte ConfusionMinTurns { - get => confusionMinTurns; + get => _confusionMinTurns; set { ReadOnlyCheck(); - if (confusionMinTurns != value) + if (_confusionMinTurns != value) { - if (value > confusionMaxTurns) + if (value > _confusionMaxTurns) { - throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(ConfusionMinTurns)} cannot exceed {nameof(ConfusionMaxTurns)} ({confusionMaxTurns})."); + throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(ConfusionMinTurns)} cannot exceed {nameof(ConfusionMaxTurns)} ({_confusionMaxTurns})."); } - confusionMinTurns = value; + _confusionMinTurns = value; OnPropertyChanged(nameof(ConfusionMinTurns)); } } } /// The default value of . public const byte DefaultSleepMaxTurns = 3; - private byte sleepMaxTurns = DefaultSleepMaxTurns; + private byte _sleepMaxTurns = DefaultSleepMaxTurns; /// The maximum amount of turns a Pokémon can be . public byte SleepMaxTurns { - get => sleepMaxTurns; + get => _sleepMaxTurns; set { ReadOnlyCheck(); - if (sleepMaxTurns != value) + if (_sleepMaxTurns != value) { - if (value < sleepMinTurns) + if (value < _sleepMinTurns) { - throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(SleepMaxTurns)} must be at least {nameof(SleepMinTurns)} ({sleepMinTurns})."); + throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(SleepMaxTurns)} must be at least {nameof(SleepMinTurns)} ({_sleepMinTurns})."); } - sleepMaxTurns = value; + _sleepMaxTurns = value; OnPropertyChanged(nameof(SleepMaxTurns)); } } } /// The default value of . public const byte DefaultSleepMinTurns = 1; - private byte sleepMinTurns = DefaultSleepMinTurns; + private byte _sleepMinTurns = DefaultSleepMinTurns; /// The minimum amount of turns a Pokémon can be . public byte SleepMinTurns { - get => sleepMinTurns; + get => _sleepMinTurns; set { ReadOnlyCheck(); - if (sleepMinTurns != value) + if (_sleepMinTurns != value) { - if (value > sleepMaxTurns) + if (value > _sleepMaxTurns) { - throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(SleepMinTurns)} cannot exceed {nameof(SleepMaxTurns)} ({sleepMaxTurns})."); + throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(SleepMinTurns)} cannot exceed {nameof(SleepMaxTurns)} ({_sleepMaxTurns})."); } - sleepMinTurns = value; + _sleepMinTurns = value; OnPropertyChanged(nameof(SleepMinTurns)); } } } /// The default value of . public const byte DefaultBurnDamageDenominator = 8; - private byte burnDamageDenominator = DefaultBurnDamageDenominator; + 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; + get => _burnDamageDenominator; set { ReadOnlyCheck(); - if (burnDamageDenominator != value) + if (_burnDamageDenominator != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(BurnDamageDenominator)} must be at least 1."); } - burnDamageDenominator = value; + _burnDamageDenominator = value; OnPropertyChanged(nameof(BurnDamageDenominator)); } } } /// The default value of . public const byte DefaultPoisonDamageDenominator = 8; - private byte poisonDamageDenominator = DefaultPoisonDamageDenominator; + 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; + get => _poisonDamageDenominator; set { ReadOnlyCheck(); - if (poisonDamageDenominator != value) + if (_poisonDamageDenominator != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(PoisonDamageDenominator)} must be at least 1."); } - poisonDamageDenominator = value; + _poisonDamageDenominator = value; OnPropertyChanged(nameof(PoisonDamageDenominator)); } } } /// The default value of . public const byte DefaultToxicDamageDenominator = 16; - private byte toxicDamageDenominator = DefaultToxicDamageDenominator; + private byte _toxicDamageDenominator = DefaultToxicDamageDenominator; /// A Pokémon with loses (/this) of its HP at the end of every turn. public byte ToxicDamageDenominator { - get => toxicDamageDenominator; + get => _toxicDamageDenominator; set { ReadOnlyCheck(); - if (toxicDamageDenominator != value) + if (_toxicDamageDenominator != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(ToxicDamageDenominator)} must be at least 1."); } - toxicDamageDenominator = value; + _toxicDamageDenominator = value; OnPropertyChanged(nameof(ToxicDamageDenominator)); } } } /// The default value of . public const byte DefaultLeechSeedDenominator = 8; - private byte leechSeedDenominator = DefaultLeechSeedDenominator; + 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; + get => _leechSeedDenominator; set { ReadOnlyCheck(); - if (leechSeedDenominator != value) + if (_leechSeedDenominator != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(LeechSeedDenominator)} must be at least 1."); } - leechSeedDenominator = value; + _leechSeedDenominator = value; OnPropertyChanged(nameof(LeechSeedDenominator)); } } } /// The default value of . public const byte DefaultCurseDenominator = 4; - private byte curseDenominator = DefaultCurseDenominator; + 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; + get => _curseDenominator; set { ReadOnlyCheck(); - if (curseDenominator != value) + if (_curseDenominator != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(CurseDenominator)} must be at least 1."); } - curseDenominator = value; + _curseDenominator = value; OnPropertyChanged(nameof(CurseDenominator)); } } } /// The default value of . public const byte DefaultLeftoversHealDenominator = 16; - private byte leftoversHealDenominator = DefaultLeftoversHealDenominator; + 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; + get => _leftoversHealDenominator; set { ReadOnlyCheck(); - if (leftoversHealDenominator != value) + if (_leftoversHealDenominator != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(LeftoversHealDenominator)} must be at least 1."); } - leftoversHealDenominator = value; + _leftoversHealDenominator = value; OnPropertyChanged(nameof(LeftoversHealDenominator)); } } } /// The default value of . public const byte DefaultBlackSludgeDamageDenominator = 8; - private byte blackSludgeDamageDenominator = DefaultBlackSludgeDamageDenominator; + 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; + get => _blackSludgeDamageDenominator; set { ReadOnlyCheck(); - if (blackSludgeDamageDenominator != value) + if (_blackSludgeDamageDenominator != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(BlackSludgeDamageDenominator)} must be at least 1."); } - blackSludgeDamageDenominator = value; + _blackSludgeDamageDenominator = value; OnPropertyChanged(nameof(BlackSludgeDamageDenominator)); } } } /// The default value of . public const byte DefaultBlackSludgeHealDenominator = 16; - private byte blackSludgeHealDenominator = DefaultBlackSludgeHealDenominator; + 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; + get => _blackSludgeHealDenominator; set { ReadOnlyCheck(); - if (blackSludgeHealDenominator != value) + if (_blackSludgeHealDenominator != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(BlackSludgeHealDenominator)} must be at least 1."); } - blackSludgeHealDenominator = value; + _blackSludgeHealDenominator = value; OnPropertyChanged(nameof(BlackSludgeHealDenominator)); } } } /// The default value of . public const byte DefaultReflectTurns = 5; - private byte reflectTurns = DefaultReflectTurns; + private byte _reflectTurns = DefaultReflectTurns; /// The amount of turns lasts. public byte ReflectTurns { - get => reflectTurns; + get => _reflectTurns; set { ReadOnlyCheck(); - if (reflectTurns != value) + if (_reflectTurns != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(ReflectTurns)} must be at least 1."); } - reflectTurns = value; + _reflectTurns = value; OnPropertyChanged(nameof(ReflectTurns)); } } } /// The default value of . public const byte DefaultLightScreenTurns = 5; - private byte lightScreenTurns = DefaultLightScreenTurns; + private byte _lightScreenTurns = DefaultLightScreenTurns; /// The amount of turns lasts. public byte LightScreenTurns { - get => lightScreenTurns; + get => _lightScreenTurns; set { ReadOnlyCheck(); - if (lightScreenTurns != value) + if (_lightScreenTurns != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(LightScreenTurns)} must be at least 1."); } - lightScreenTurns = value; + _lightScreenTurns = value; OnPropertyChanged(nameof(LightScreenTurns)); } } } /// The default value of . public const byte DefaultLightClayTurnExtension = 3; - private byte lightClayTurnExtension = DefaultLightClayTurnExtension; + private byte _lightClayTurnExtension = DefaultLightClayTurnExtension; /// The amount of turns added to and when the user is holding a . public byte LightClayTurnExtension { - get => lightClayTurnExtension; + get => _lightClayTurnExtension; set { ReadOnlyCheck(); - if (lightClayTurnExtension != value) + if (_lightClayTurnExtension != value) { - lightClayTurnExtension = value; + _lightClayTurnExtension = value; OnPropertyChanged(nameof(LightClayTurnExtension)); } } } /// The default value of . public const byte DefaultHailTurns = 5; - private byte hailTurns = DefaultHailTurns; + 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; + get => _hailTurns; set { ReadOnlyCheck(); - if (hailTurns != value) + if (_hailTurns != value) { - if (value == 0 && icyRockTurnExtension != 0) + 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; + _hailTurns = value; OnPropertyChanged(nameof(HailTurns)); } } } /// The default value of . public const byte DefaultHailDamageDenominator = 16; - private byte hailDamageDenominator = DefaultHailDamageDenominator; + 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; + get => _hailDamageDenominator; set { ReadOnlyCheck(); - if (hailDamageDenominator != value) + if (_hailDamageDenominator != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(HailDamageDenominator)} must be at least 1."); } - hailDamageDenominator = value; + _hailDamageDenominator = value; OnPropertyChanged(nameof(HailDamageDenominator)); } } } /// The default value of . public const byte DefaultIcyRockTurnExtension = 3; - private byte icyRockTurnExtension = DefaultIcyRockTurnExtension; + 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; + get => _icyRockTurnExtension; set { ReadOnlyCheck(); - if (icyRockTurnExtension != value) + if (_icyRockTurnExtension != value) { - if (value != 0 && hailTurns == 0) + 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; + _icyRockTurnExtension = value; OnPropertyChanged(nameof(IcyRockTurnExtension)); } } } /// The default value of . public const byte DefaultIceBodyHealDenominator = 16; - private byte iceBodyHealDenominator = DefaultIceBodyHealDenominator; + 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; + get => _iceBodyHealDenominator; set { ReadOnlyCheck(); - if (iceBodyHealDenominator != value) + if (_iceBodyHealDenominator != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(IceBodyHealDenominator)} must be at least 1."); } - iceBodyHealDenominator = value; + _iceBodyHealDenominator = value; OnPropertyChanged(nameof(IceBodyHealDenominator)); } } } /// The default value of . public const byte DefaultRainTurns = 5; - private byte rainTurns = DefaultRainTurns; + 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; + get => _rainTurns; set { ReadOnlyCheck(); - if (rainTurns != value) + if (_rainTurns != value) { - if (value == 0 && dampRockTurnExtension != 0) + 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; + _rainTurns = value; OnPropertyChanged(nameof(RainTurns)); } } } /// The default value of . public const byte DefaultDampRockTurnExtension = 3; - private byte dampRockTurnExtension = DefaultDampRockTurnExtension; + 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; + get => _dampRockTurnExtension; set { ReadOnlyCheck(); - if (dampRockTurnExtension != value) + if (_dampRockTurnExtension != value) { - if (value != 0 && rainTurns == 0) + 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; + _dampRockTurnExtension = value; OnPropertyChanged(nameof(DampRockTurnExtension)); } } } /// The default value of . public const byte DefaultSandstormTurns = 5; - private byte sandstormTurns = DefaultSandstormTurns; + 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; + get => _sandstormTurns; set { ReadOnlyCheck(); - if (sandstormTurns != value) + if (_sandstormTurns != value) { - if (value == 0 && smoothRockTurnExtension != 0) + 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; + _sandstormTurns = value; OnPropertyChanged(nameof(SandstormTurns)); } } } /// The default value of . public const byte DefaultSandstormDamageDenominator = 16; - private byte sandstormDamageDenominator = DefaultSandstormDamageDenominator; + 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; + get => _sandstormDamageDenominator; set { ReadOnlyCheck(); - if (sandstormDamageDenominator != value) + if (_sandstormDamageDenominator != value) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(SandstormDamageDenominator)} must be at least 1."); } - sandstormDamageDenominator = value; + _sandstormDamageDenominator = value; OnPropertyChanged(nameof(SandstormDamageDenominator)); } } } /// The default value of . public const byte DefaultSmoothRockTurnExtension = 3; - private byte smoothRockTurnExtension = DefaultSmoothRockTurnExtension; + 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; + get => _smoothRockTurnExtension; set { ReadOnlyCheck(); - if (smoothRockTurnExtension != value) + if (_smoothRockTurnExtension != value) { - if (value != 0 && sandstormTurns == 0) + 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; + _smoothRockTurnExtension = value; OnPropertyChanged(nameof(SmoothRockTurnExtension)); } } } /// The default value of . public const byte DefaultSunTurns = 5; - private byte sunTurns = DefaultSunTurns; + 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; + get => _sunTurns; set { ReadOnlyCheck(); - if (sunTurns != value) + if (_sunTurns != value) { - if (value == 0 && heatRockTurnExtension != 0) + 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; + _sunTurns = value; OnPropertyChanged(nameof(SunTurns)); } } } /// The default value of . public const byte DefaultHeatRockTurnExtension = 3; - private byte heatRockTurnExtension = DefaultHeatRockTurnExtension; + 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; + get => _heatRockTurnExtension; set { ReadOnlyCheck(); - if (heatRockTurnExtension != value) + if (_heatRockTurnExtension != value) { - if (value != 0 && sunTurns == 0) + 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; + _heatRockTurnExtension = value; OnPropertyChanged(nameof(HeatRockTurnExtension)); } } @@ -869,45 +865,45 @@ public PBESettings(PBESettings other) { throw new ArgumentNullException(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; + 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; } internal PBESettings(BinaryReader r) { @@ -916,7 +912,7 @@ internal PBESettings(BinaryReader r) private void ReadOnlyCheck() { - if (isReadOnly) + if (_isReadOnly) { throw new InvalidOperationException($"This {nameof(PBESettings)} is marked as read-only."); } @@ -924,17 +920,72 @@ private void ReadOnlyCheck() /// Marks this object as read-only. public void MakeReadOnly() { - if (!isReadOnly) + if (!_isReadOnly) { IsReadOnly = true; } } - /// Returns a value indicating whether a code or another object represent the same settings as this object. + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = (hash * 31) + _maxLevel.GetHashCode(); + hash = (hash * 31) + _minLevel.GetHashCode(); + hash = (hash * 31) + _maxPartySize.GetHashCode(); + hash = (hash * 31) + _maxPokemonNameLength.GetHashCode(); + hash = (hash * 31) + _maxTrainerNameLength.GetHashCode(); + hash = (hash * 31) + _maxTotalEVs.GetHashCode(); + hash = (hash * 31) + _maxIVs.GetHashCode(); + hash = (hash * 31) + _natureStatBoost.GetHashCode(); + hash = (hash * 31) + _maxStatChange.GetHashCode(); + hash = (hash * 31) + _numMoves.GetHashCode(); + hash = (hash * 31) + _ppMultiplier.GetHashCode(); + hash = (hash * 31) + _maxPPUps.GetHashCode(); + hash = (hash * 31) + _critMultiplier.GetHashCode(); + hash = (hash * 31) + _confusionMaxTurns.GetHashCode(); + hash = (hash * 31) + _confusionMinTurns.GetHashCode(); + hash = (hash * 31) + _sleepMaxTurns.GetHashCode(); + hash = (hash * 31) + _sleepMinTurns.GetHashCode(); + hash = (hash * 31) + _burnDamageDenominator.GetHashCode(); + hash = (hash * 31) + _poisonDamageDenominator.GetHashCode(); + hash = (hash * 31) + _toxicDamageDenominator.GetHashCode(); + hash = (hash * 31) + _leechSeedDenominator.GetHashCode(); + hash = (hash * 31) + _curseDenominator.GetHashCode(); + hash = (hash * 31) + _leftoversHealDenominator.GetHashCode(); + hash = (hash * 31) + _blackSludgeDamageDenominator.GetHashCode(); + hash = (hash * 31) + _blackSludgeHealDenominator.GetHashCode(); + hash = (hash * 31) + _reflectTurns.GetHashCode(); + hash = (hash * 31) + _lightScreenTurns.GetHashCode(); + hash = (hash * 31) + _lightClayTurnExtension.GetHashCode(); + hash = (hash * 31) + _hailTurns.GetHashCode(); + hash = (hash * 31) + _hailDamageDenominator.GetHashCode(); + hash = (hash * 31) + _icyRockTurnExtension.GetHashCode(); + hash = (hash * 31) + _iceBodyHealDenominator.GetHashCode(); + hash = (hash * 31) + _rainTurns.GetHashCode(); + hash = (hash * 31) + _dampRockTurnExtension.GetHashCode(); + hash = (hash * 31) + _sandstormTurns.GetHashCode(); + hash = (hash * 31) + _sandstormDamageDenominator.GetHashCode(); + hash = (hash * 31) + _smoothRockTurnExtension.GetHashCode(); + hash = (hash * 31) + _sunTurns.GetHashCode(); + hash = (hash * 31) + _heatRockTurnExtension.GetHashCode(); + return hash; + } + } + /// 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 string str) + if (ReferenceEquals(obj, null)) + { + return false; + } + else if (ReferenceEquals(obj, this)) + { + return true; + } + else if (obj is string str) { PBESettings ps; try @@ -949,45 +1000,45 @@ public override bool Equals(object obj) } else 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); + 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); } else { @@ -1041,249 +1092,250 @@ private enum PBESettingID : ushort /// Converts this object into a unique code . public override string ToString() { - return Convert.ToBase64String(ToBytes().ToArray()); + var list = new List(); + ToBytes(list); + return Convert.ToBase64String(list.ToArray()); } - internal List ToBytes() + internal void ToBytes(List bytes) { - var bytes = new List(); + int startIndex = bytes.Count; ushort numChanged = 0; - if (maxLevel != DefaultMaxLevel) + if (_maxLevel != DefaultMaxLevel) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.MaxLevel)); - bytes.Add(maxLevel); + bytes.Add(_maxLevel); numChanged++; } - if (minLevel != DefaultMinLevel) + if (_minLevel != DefaultMinLevel) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.MinLevel)); - bytes.Add(minLevel); + bytes.Add(_minLevel); numChanged++; } - if (maxPartySize != DefaultMaxPartySize) + if (_maxPartySize != DefaultMaxPartySize) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.MaxPartySize)); - bytes.Add((byte)maxPartySize); + bytes.Add((byte)_maxPartySize); numChanged++; } - if (maxPokemonNameLength != DefaultMaxPokemonNameLength) + if (_maxPokemonNameLength != DefaultMaxPokemonNameLength) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.MaxPokemonNameLength)); - bytes.Add(maxPokemonNameLength); + bytes.Add(_maxPokemonNameLength); numChanged++; } - if (maxTrainerNameLength != DefaultMaxTrainerNameLength) + if (_maxTrainerNameLength != DefaultMaxTrainerNameLength) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.MaxTrainerNameLength)); - bytes.Add(maxTrainerNameLength); + bytes.Add(_maxTrainerNameLength); numChanged++; } - if (maxTotalEVs != DefaultMaxTotalEVs) + if (_maxTotalEVs != DefaultMaxTotalEVs) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.MaxTotalEVs)); - bytes.AddRange(BitConverter.GetBytes(maxTotalEVs)); + bytes.AddRange(BitConverter.GetBytes(_maxTotalEVs)); numChanged++; } - if (maxIVs != DefaultMaxIVs) + if (_maxIVs != DefaultMaxIVs) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.MaxIVs)); - bytes.Add(maxIVs); + bytes.Add(_maxIVs); numChanged++; } - if (natureStatBoost != DefaultNatureStatBoost) + if (_natureStatBoost != DefaultNatureStatBoost) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.NatureStatBoost)); - bytes.AddRange(BitConverter.GetBytes(natureStatBoost)); + bytes.AddRange(BitConverter.GetBytes(_natureStatBoost)); numChanged++; } - if (maxStatChange != DefaultMaxStatChange) + if (_maxStatChange != DefaultMaxStatChange) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.MaxStatChange)); - bytes.Add((byte)maxStatChange); + bytes.Add((byte)_maxStatChange); numChanged++; } - if (numMoves != DefaultNumMoves) + if (_numMoves != DefaultNumMoves) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.NumMoves)); - bytes.Add(numMoves); + bytes.Add(_numMoves); numChanged++; } - if (ppMultiplier != DefaultPPMultiplier) + if (_ppMultiplier != DefaultPPMultiplier) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.PPMultiplier)); - bytes.Add(ppMultiplier); + bytes.Add(_ppMultiplier); numChanged++; } - if (maxPPUps != DefaultMaxPPUps) + if (_maxPPUps != DefaultMaxPPUps) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.MaxPPUps)); - bytes.Add(maxPPUps); + bytes.Add(_maxPPUps); numChanged++; } - if (critMultiplier != DefaultCritMultiplier) + if (_critMultiplier != DefaultCritMultiplier) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.CritMultiplier)); - bytes.AddRange(BitConverter.GetBytes(critMultiplier)); + bytes.AddRange(BitConverter.GetBytes(_critMultiplier)); numChanged++; } - if (confusionMaxTurns != DefaultConfusionMaxTurns) + if (_confusionMaxTurns != DefaultConfusionMaxTurns) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.ConfusionMaxTurns)); - bytes.Add(confusionMaxTurns); + bytes.Add(_confusionMaxTurns); numChanged++; } - if (confusionMinTurns != DefaultConfusionMinTurns) + if (_confusionMinTurns != DefaultConfusionMinTurns) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.ConfusionMinTurns)); - bytes.Add(confusionMinTurns); + bytes.Add(_confusionMinTurns); numChanged++; } - if (sleepMaxTurns != DefaultSleepMaxTurns) + if (_sleepMaxTurns != DefaultSleepMaxTurns) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.SleepMaxTurns)); - bytes.Add(sleepMaxTurns); + bytes.Add(_sleepMaxTurns); numChanged++; } - if (sleepMinTurns != DefaultSleepMinTurns) + if (_sleepMinTurns != DefaultSleepMinTurns) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.SleepMinTurns)); - bytes.Add(sleepMinTurns); + bytes.Add(_sleepMinTurns); numChanged++; } - if (burnDamageDenominator != DefaultBurnDamageDenominator) + if (_burnDamageDenominator != DefaultBurnDamageDenominator) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.BurnDamageDenominator)); - bytes.Add(burnDamageDenominator); + bytes.Add(_burnDamageDenominator); numChanged++; } - if (poisonDamageDenominator != DefaultPoisonDamageDenominator) + if (_poisonDamageDenominator != DefaultPoisonDamageDenominator) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.PoisonDamageDenominator)); - bytes.Add(poisonDamageDenominator); + bytes.Add(_poisonDamageDenominator); numChanged++; } - if (toxicDamageDenominator != DefaultToxicDamageDenominator) + if (_toxicDamageDenominator != DefaultToxicDamageDenominator) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.ToxicDamageDenominator)); - bytes.Add(toxicDamageDenominator); + bytes.Add(_toxicDamageDenominator); numChanged++; } - if (leechSeedDenominator != DefaultLeechSeedDenominator) + if (_leechSeedDenominator != DefaultLeechSeedDenominator) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.LeechSeedDenominator)); - bytes.Add(leechSeedDenominator); + bytes.Add(_leechSeedDenominator); numChanged++; } - if (curseDenominator != DefaultCurseDenominator) + if (_curseDenominator != DefaultCurseDenominator) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.CurseDenominator)); - bytes.Add(curseDenominator); + bytes.Add(_curseDenominator); numChanged++; } - if (leftoversHealDenominator != DefaultLeftoversHealDenominator) + if (_leftoversHealDenominator != DefaultLeftoversHealDenominator) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.LeftoversHealDenominator)); - bytes.Add(leftoversHealDenominator); + bytes.Add(_leftoversHealDenominator); numChanged++; } - if (blackSludgeDamageDenominator != DefaultBlackSludgeDamageDenominator) + if (_blackSludgeDamageDenominator != DefaultBlackSludgeDamageDenominator) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.BlackSludgeDamageDenominator)); - bytes.Add(blackSludgeDamageDenominator); + bytes.Add(_blackSludgeDamageDenominator); numChanged++; } - if (blackSludgeHealDenominator != DefaultBlackSludgeHealDenominator) + if (_blackSludgeHealDenominator != DefaultBlackSludgeHealDenominator) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.BlackSludgeHealDenominator)); - bytes.Add(blackSludgeHealDenominator); + bytes.Add(_blackSludgeHealDenominator); numChanged++; } - if (reflectTurns != DefaultReflectTurns) + if (_reflectTurns != DefaultReflectTurns) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.ReflectTurns)); - bytes.Add(reflectTurns); + bytes.Add(_reflectTurns); numChanged++; } - if (lightScreenTurns != DefaultLightScreenTurns) + if (_lightScreenTurns != DefaultLightScreenTurns) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.LightScreenTurns)); - bytes.Add(lightScreenTurns); + bytes.Add(_lightScreenTurns); numChanged++; } - if (lightClayTurnExtension != DefaultLightClayTurnExtension) + if (_lightClayTurnExtension != DefaultLightClayTurnExtension) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.LightClayTurnExtension)); - bytes.Add(lightClayTurnExtension); + bytes.Add(_lightClayTurnExtension); numChanged++; } - if (hailTurns != DefaultHailTurns) + if (_hailTurns != DefaultHailTurns) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.HailTurns)); - bytes.Add(hailTurns); + bytes.Add(_hailTurns); numChanged++; } - if (hailDamageDenominator != DefaultHailDamageDenominator) + if (_hailDamageDenominator != DefaultHailDamageDenominator) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.HailDamageDenominator)); - bytes.Add(hailDamageDenominator); + bytes.Add(_hailDamageDenominator); numChanged++; } - if (icyRockTurnExtension != DefaultIcyRockTurnExtension) + if (_icyRockTurnExtension != DefaultIcyRockTurnExtension) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.IcyRockTurnExtension)); - bytes.Add(icyRockTurnExtension); + bytes.Add(_icyRockTurnExtension); numChanged++; } - if (iceBodyHealDenominator != DefaultIceBodyHealDenominator) + if (_iceBodyHealDenominator != DefaultIceBodyHealDenominator) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.IceBodyHealDenominator)); - bytes.Add(iceBodyHealDenominator); + bytes.Add(_iceBodyHealDenominator); numChanged++; } - if (rainTurns != DefaultRainTurns) + if (_rainTurns != DefaultRainTurns) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.RainTurns)); - bytes.Add(rainTurns); + bytes.Add(_rainTurns); numChanged++; } - if (dampRockTurnExtension != DefaultDampRockTurnExtension) + if (_dampRockTurnExtension != DefaultDampRockTurnExtension) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.DampRockTurnExtension)); - bytes.Add(dampRockTurnExtension); + bytes.Add(_dampRockTurnExtension); numChanged++; } - if (sandstormTurns != DefaultSandstormTurns) + if (_sandstormTurns != DefaultSandstormTurns) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.SandstormTurns)); - bytes.Add(sandstormTurns); + bytes.Add(_sandstormTurns); numChanged++; } - if (sandstormDamageDenominator != DefaultSandstormDamageDenominator) + if (_sandstormDamageDenominator != DefaultSandstormDamageDenominator) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.SandstormDamageDenominator)); - bytes.Add(sandstormDamageDenominator); + bytes.Add(_sandstormDamageDenominator); numChanged++; } - if (smoothRockTurnExtension != DefaultSmoothRockTurnExtension) + if (_smoothRockTurnExtension != DefaultSmoothRockTurnExtension) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.SmoothRockTurnExtension)); - bytes.Add(smoothRockTurnExtension); + bytes.Add(_smoothRockTurnExtension); numChanged++; } - if (sunTurns != DefaultSunTurns) + if (_sunTurns != DefaultSunTurns) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.SunTurns)); - bytes.Add(sunTurns); + bytes.Add(_sunTurns); numChanged++; } - if (heatRockTurnExtension != DefaultHeatRockTurnExtension) + if (_heatRockTurnExtension != DefaultHeatRockTurnExtension) { bytes.AddRange(BitConverter.GetBytes((ushort)PBESettingID.HeatRockTurnExtension)); - bytes.Add(heatRockTurnExtension); + bytes.Add(_heatRockTurnExtension); numChanged++; } - bytes.InsertRange(0, BitConverter.GetBytes(numChanged)); - return bytes; + bytes.InsertRange(startIndex, BitConverter.GetBytes(numChanged)); } private void FromBytes(BinaryReader r) { diff --git a/PokemonBattleEngine/Data/TeamShell.cs b/PokemonBattleEngine/Data/TeamShell.cs index 4f08d2bb0..7f2e75f6a 100644 --- a/PokemonBattleEngine/Data/TeamShell.cs +++ b/PokemonBattleEngine/Data/TeamShell.cs @@ -9,14 +9,8 @@ namespace Kermalis.PokemonBattleEngine.Data { - public sealed class PBETeamShell : IEnumerable, INotifyCollectionChanged, INotifyPropertyChanged + public sealed class PBETeamShell : IDisposable, IEnumerable, INotifyCollectionChanged, INotifyPropertyChanged { - private void FireEvents(NotifyCollectionChangedEventArgs e) - { - OnPropertyChanged(nameof(Count)); - OnPropertyChanged("Item[]"); - OnCollectionChanged(e); - } private void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { CollectionChanged?.Invoke(this, e); @@ -28,41 +22,55 @@ private void OnPropertyChanged(string property) public event NotifyCollectionChangedEventHandler CollectionChanged; public event PropertyChangedEventHandler PropertyChanged; - private readonly List list; - public int Count => list.Count; - public PBEPokemonShell this[int index] => list[index]; + private readonly List _list; + public int Count => _list.Count; + public PBEPokemonShell this[int index] + { + get + { + if (index >= _list.Count) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + return _list[index]; + } + } public PBESettings Settings { get; } - internal PBETeamShell(BinaryReader r) + internal PBETeamShell(PBESettings settings, BinaryReader r) { - Settings = new PBESettings(r); - Settings.PropertyChanged += OnSettingsChanged; - list = new List(Settings.MaxPartySize); sbyte count = r.ReadSByte(); - if (count < 1 || count > Settings.MaxPartySize) + if (count < 1 || count > settings.MaxPartySize) { throw new InvalidDataException(); } + Settings = settings; + Settings.PropertyChanged += OnSettingsChanged; + _list = new List(Settings.MaxPartySize); for (int i = 0; i < count; i++) { - AddWithEvents(false, new PBEPokemonShell(r, this), i); + InsertWithEvents(false, new PBEPokemonShell(Settings, r), i); } } public PBETeamShell(string path) { + if (path == null) + { + throw new ArgumentNullException(nameof(path)); + } var json = JObject.Parse(File.ReadAllText(path)); Settings = new PBESettings(json[nameof(Settings)].Value()); - Settings.PropertyChanged += OnSettingsChanged; - var partyObj = (JArray)json["Party"]; - if (partyObj.Count < 1 || partyObj.Count > Settings.MaxPartySize) + var jArray = (JArray)json["Party"]; + if (jArray.Count < 1 || jArray.Count > Settings.MaxPartySize) { - throw new InvalidDataException("File has an invalid party size for the settings provided."); + throw new InvalidDataException("Invalid party size."); } - list = new List(Settings.MaxPartySize); - for (int i = 0; i < partyObj.Count; i++) + Settings.PropertyChanged += OnSettingsChanged; + _list = new List(Settings.MaxPartySize); + for (int i = 0; i < jArray.Count; i++) { - AddWithEvents(false, new PBEPokemonShell(partyObj[i], this), i); + InsertWithEvents(false, new PBEPokemonShell(Settings, jArray[i]), i); } } public PBETeamShell(PBESettings settings, int numPkmnToGenerate, bool setToMaxLevel) @@ -77,47 +85,48 @@ public PBETeamShell(PBESettings settings, int numPkmnToGenerate, bool setToMaxLe } Settings = settings; Settings.PropertyChanged += OnSettingsChanged; - list = new List(Settings.MaxPartySize); + _list = new List(Settings.MaxPartySize); for (int i = 0; i < numPkmnToGenerate; i++) { - Add(false, PBEUtils.RandomSpecies(), setToMaxLevel ? Settings.MaxLevel : PBEUtils.RandomLevel(Settings)); + Insert(PBEUtils.RandomSpecies(), setToMaxLevel ? Settings.MaxLevel : PBEUtils.RandomLevel(Settings), false, i); } } - private void AddRandom(bool fireEvent) + private void InsertRandom(bool fireEvent, int index) { - Add(fireEvent, PBEUtils.RandomSpecies(), PBEUtils.RandomLevel(Settings)); + Insert(PBEUtils.RandomSpecies(), PBEUtils.RandomLevel(Settings), fireEvent, index); } - private void Add(bool fireEvent, PBESpecies species, byte level) + private void Insert(PBESpecies species, byte level, bool fireEvent, int index) { - var item = new PBEPokemonShell(species, level, this); - int index = list.Count; - AddWithEvents(fireEvent, item, index); + InsertWithEvents(fireEvent, new PBEPokemonShell(species, level, Settings) { CanDispose = false }, index); } - private void AddWithEvents(bool fireEvent, PBEPokemonShell item, int index) + private void InsertWithEvents(bool fireEvent, PBEPokemonShell item, int index) { - Settings.PropertyChanged += item.OnSettingsChanged; - list.Insert(index, item); + _list.Insert(index, item); if (fireEvent) { - FireEvents(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); + OnPropertyChanged(nameof(Count)); + OnPropertyChanged("Item[]"); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); } } private void RemoveWithEvents(PBEPokemonShell item, int index) { - Settings.PropertyChanged -= item.OnSettingsChanged; - list.RemoveAt(index); + item.CanDispose = true; + _list.RemoveAt(index); NotifyCollectionChangedEventArgs e; - if (list.Count == 0) + if (_list.Count == 0) { - AddRandom(false); - e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, list[index], item, index); + InsertRandom(false, 0); + e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, _list[0], item, 0); } else { + OnPropertyChanged(nameof(Count)); e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index); } - FireEvents(e); + OnPropertyChanged("Item[]"); + OnCollectionChanged(e); } private void ExceedException() @@ -126,9 +135,13 @@ private void ExceedException() } public void AddRandom() { - if (list.Count < Settings.MaxPartySize) + if (IsDisposed) { - AddRandom(true); + throw new ObjectDisposedException(null); + } + if (_list.Count < Settings.MaxPartySize) + { + InsertRandom(true, _list.Count); } else { @@ -137,9 +150,47 @@ public void AddRandom() } public void Add(PBESpecies species, byte level) { - if (list.Count < Settings.MaxPartySize) + if (IsDisposed) { - Add(true, species, level); + throw new ObjectDisposedException(null); + } + PBEPokemonShell.ValidateSpecies(species); + PBEPokemonShell.ValidateLevel(level, Settings); + if (_list.Count < Settings.MaxPartySize) + { + Insert(species, level, true, _list.Count); + } + else + { + ExceedException(); + } + } + public void InsertRandom(int index) + { + if (IsDisposed) + { + throw new ObjectDisposedException(null); + } + if (_list.Count < Settings.MaxPartySize) + { + InsertRandom(true, index); + } + else + { + ExceedException(); + } + } + public void Insert(PBESpecies species, byte level, int index) + { + if (IsDisposed) + { + throw new ObjectDisposedException(null); + } + PBEPokemonShell.ValidateSpecies(species); + PBEPokemonShell.ValidateLevel(level, Settings); + if (_list.Count < Settings.MaxPartySize) + { + Insert(species, level, true, index); } else { @@ -148,17 +199,35 @@ public void Add(PBESpecies species, byte level) } public void Clear() { - for (int i = 0; i < list.Count; i++) + if (IsDisposed) { - Settings.PropertyChanged -= list[i].OnSettingsChanged; + throw new ObjectDisposedException(null); } - list.Clear(); - AddRandom(false); - FireEvents(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + int oldCount = _list.Count; + for (int i = 0; i < oldCount; i++) + { + _list[i].CanDispose = true; + } + _list.Clear(); + InsertRandom(false, 0); + if (oldCount != 1) + { + OnPropertyChanged(nameof(Count)); + } + OnPropertyChanged("Item[]"); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } public bool Remove(PBEPokemonShell item) { - int index = list.IndexOf(item); + if (IsDisposed) + { + throw new ObjectDisposedException(null); + } + if (item.IsDisposed) + { + throw new ObjectDisposedException(nameof(item)); + } + int index = _list.IndexOf(item); bool b = index != -1; if (b) { @@ -168,32 +237,44 @@ public bool Remove(PBEPokemonShell item) } public void RemoveAt(int index) { - if (index < 0 || index >= list.Count) + if (IsDisposed) + { + throw new ObjectDisposedException(null); + } + if (index < 0 || index >= _list.Count) { throw new ArgumentOutOfRangeException(nameof(index)); } else { - RemoveWithEvents(list[index], index); + RemoveWithEvents(_list[index], index); } } public bool Contains(PBEPokemonShell item) { - return list.IndexOf(item) != -1; + if (item.IsDisposed) + { + throw new ObjectDisposedException(nameof(item)); + } + return _list.IndexOf(item) != -1; } public int IndexOf(PBEPokemonShell item) { - return list.IndexOf(item); + if (item.IsDisposed) + { + throw new ObjectDisposedException(nameof(item)); + } + return _list.IndexOf(item); } public IEnumerator GetEnumerator() { - return list.GetEnumerator(); + return _list.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { - return list.GetEnumerator(); + return _list.GetEnumerator(); } private void OnSettingsChanged(object sender, PropertyChangedEventArgs e) @@ -202,38 +283,41 @@ private void OnSettingsChanged(object sender, PropertyChangedEventArgs e) { case nameof(Settings.MaxPartySize): { - if (list.Count > Settings.MaxPartySize) + if (_list.Count > Settings.MaxPartySize) { - int numToRemove = list.Count - Settings.MaxPartySize; + int numToRemove = _list.Count - Settings.MaxPartySize; var changedItems = new PBEPokemonShell[numToRemove]; for (int i = 0; i < numToRemove; i++) { - int index = list.Count - 1; - PBEPokemonShell item = list[index]; - Settings.PropertyChanged -= item.OnSettingsChanged; - list.RemoveAt(index); + int index = _list.Count - 1; + PBEPokemonShell item = _list[index]; + item.CanDispose = true; + _list.RemoveAt(index); changedItems[i] = item; } - FireEvents(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, changedItems, list.Count)); + OnPropertyChanged(nameof(Count)); + OnPropertyChanged("Item[]"); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, changedItems, _list.Count)); } break; } } } - internal List ToBytes() + internal void ToBytes(List bytes) { - var bytes = new List(); - bytes.AddRange(Settings.ToBytes()); - bytes.Add((byte)list.Count); - for (int i = 0; i < list.Count; i++) + bytes.Add((byte)_list.Count); + for (int i = 0; i < _list.Count; i++) { - bytes.AddRange(list[i].ToBytes()); + _list[i].ToBytes(bytes); } - return bytes; } public void ToJsonFile(string path) { + if (IsDisposed) + { + throw new ObjectDisposedException(null); + } using (var w = new JsonTextWriter(File.CreateText(path)) { Formatting = Formatting.Indented }) { w.WriteStartObject(); @@ -241,13 +325,30 @@ public void ToJsonFile(string path) w.WriteValue(Settings.ToString()); w.WritePropertyName("Party"); w.WriteStartArray(); - for (int i = 0; i < list.Count; i++) + for (int i = 0; i < _list.Count; i++) { - list[i].ToJson(w); + _list[i].ToJson(w); } w.WriteEndArray(); w.WriteEndObject(); } } + + public bool IsDisposed { get; private set; } + public void Dispose() + { + if (!IsDisposed) + { + IsDisposed = true; + OnPropertyChanged(nameof(IsDisposed)); + Settings.PropertyChanged -= OnSettingsChanged; + for (int i = 0; i < _list.Count; i++) + { + PBEPokemonShell item = _list[i]; + item.CanDispose = true; + item.Dispose(); + } + } + } } } diff --git a/PokemonBattleEngine/Packets/ActionsRequestPacket.cs b/PokemonBattleEngine/Packets/ActionsRequestPacket.cs index 3293538f1..8c77ab927 100644 --- a/PokemonBattleEngine/Packets/ActionsRequestPacket.cs +++ b/PokemonBattleEngine/Packets/ActionsRequestPacket.cs @@ -12,28 +12,34 @@ namespace Kermalis.PokemonBattleEngine.Packets public sealed class PBEActionsRequestPacket : INetPacket { public const short Code = 0x07; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public PBETeam Team { get; } public ReadOnlyCollection Pokemon { get; } - public PBEActionsRequestPacket(PBETeam team) + internal PBEActionsRequestPacket(PBETeam team) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); bytes.Add((Team = team).Id); - bytes.Add((byte)(Pokemon = Team.ActionsRequired.Select(p => p.FieldPosition).ToList().AsReadOnly()).Count); - bytes.AddRange(Pokemon.Select(p => (byte)p)); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.Add((byte)(Pokemon = new ReadOnlyCollection(Team.ActionsRequired.Select(p => p.FieldPosition).ToArray())).Count); + for (int i = 0; i < (byte)Pokemon.Count; i++) + { + bytes.Add((byte)Pokemon[i]); + } + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBEActionsRequestPacket(byte[] buffer, PBEBattle battle) + internal PBEActionsRequestPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { - using (var r = new BinaryReader(new MemoryStream(buffer))) + Buffer = buffer; + Team = battle.Teams[r.ReadByte()]; + var pkmn = new PBEFieldPosition[r.ReadByte()]; + for (int i = 0; i < pkmn.Length; i++) { - r.ReadInt16(); // Skip Code - Team = battle.Teams[r.ReadByte()]; - Pokemon = r.ReadBytes(r.ReadByte()).Select(b => (PBEFieldPosition)b).ToList().AsReadOnly(); + pkmn[i] = (PBEFieldPosition)r.ReadByte(); } + Pokemon = new ReadOnlyCollection(pkmn); } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/ActionsResponsePacket.cs b/PokemonBattleEngine/Packets/ActionsResponsePacket.cs index de07fadab..cddf96ba4 100644 --- a/PokemonBattleEngine/Packets/ActionsResponsePacket.cs +++ b/PokemonBattleEngine/Packets/ActionsResponsePacket.cs @@ -11,31 +11,43 @@ namespace Kermalis.PokemonBattleEngine.Packets public sealed class PBEActionsResponsePacket : INetPacket { public const short Code = 0x08; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } - public ReadOnlyCollection Actions { get; } + public ReadOnlyCollection Actions { get; } - public PBEActionsResponsePacket(IEnumerable actions) + public PBEActionsResponsePacket(IList actions) { + if (actions == null) + { + throw new ArgumentNullException(nameof(actions)); + } + if (actions.Count == 0) + { + throw new ArgumentOutOfRangeException(nameof(actions)); + } + if (actions.Any(a => a == null)) + { + throw new ArgumentNullException(nameof(actions)); + } var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); - bytes.Add((byte)(Actions = actions.ToList().AsReadOnly()).Count); - bytes.AddRange(Actions.SelectMany(a => a.ToBytes())); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.Add((byte)(Actions = new ReadOnlyCollection(actions)).Count); + for (int i = 0; i < (byte)Actions.Count; i++) + { + Actions[i].ToBytes(bytes); + } + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBEActionsResponsePacket(byte[] buffer, PBEBattle battle) + internal PBEActionsResponsePacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { Buffer = buffer; - using (var r = new BinaryReader(new MemoryStream(buffer))) + var actions = new PBETurnAction[r.ReadByte()]; + for (int i = 0; i < actions.Length; i++) { - r.ReadInt16(); // Skip Code - var actions = new PBEAction[r.ReadByte()]; - for (int i = 0; i < actions.Length; i++) - { - actions[i] = PBEAction.FromBytes(r); - } - Actions = Array.AsReadOnly(actions); + actions[i] = new PBETurnAction(r); } + Actions = new ReadOnlyCollection(actions); } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/AutoCenterPacket.cs b/PokemonBattleEngine/Packets/AutoCenterPacket.cs index f88cce1e3..a08f0204b 100644 --- a/PokemonBattleEngine/Packets/AutoCenterPacket.cs +++ b/PokemonBattleEngine/Packets/AutoCenterPacket.cs @@ -3,15 +3,15 @@ using Kermalis.PokemonBattleEngine.Data; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEAutoCenterPacket : INetPacket { public const short Code = 0x2A; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public byte Pokemon1Id { get; } public PBEFieldPosition Pokemon1Position { get; } @@ -20,7 +20,7 @@ public sealed class PBEAutoCenterPacket : INetPacket public PBEFieldPosition Pokemon2Position { get; } public PBETeam Pokemon2Team { get; } - public PBEAutoCenterPacket(byte pokemon1Id, PBEFieldPosition pokemon1Position, PBETeam pokemon1Team, byte pokemon2Id, PBEFieldPosition pokemon2Position, PBETeam pokemon2Team) + internal PBEAutoCenterPacket(byte pokemon1Id, PBEFieldPosition pokemon1Position, PBETeam pokemon1Team, byte pokemon2Id, PBEFieldPosition pokemon2Position, PBETeam pokemon2Team) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); @@ -30,21 +30,27 @@ public PBEAutoCenterPacket(byte pokemon1Id, PBEFieldPosition pokemon1Position, P bytes.Add(Pokemon2Id = pokemon2Id); bytes.Add((byte)(Pokemon2Position = pokemon2Position)); bytes.Add((Pokemon2Team = pokemon2Team).Id); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBEAutoCenterPacket(byte[] buffer, PBEBattle battle) + internal PBEAutoCenterPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { Buffer = buffer; - using (var r = new BinaryReader(new MemoryStream(buffer))) + Pokemon1Id = r.ReadByte(); + Pokemon1Position = (PBEFieldPosition)r.ReadByte(); + Pokemon1Team = battle.Teams[r.ReadByte()]; + Pokemon2Id = r.ReadByte(); + Pokemon2Position = (PBEFieldPosition)r.ReadByte(); + Pokemon2Team = battle.Teams[r.ReadByte()]; + } + + public PBEAutoCenterPacket MakeHidden(bool team0Hidden, bool team1Hidden) + { + if (!team0Hidden && !team1Hidden) { - r.ReadInt16(); // Skip Code - Pokemon1Id = r.ReadByte(); - Pokemon1Position = (PBEFieldPosition)r.ReadByte(); - Pokemon1Team = battle.Teams[r.ReadByte()]; - Pokemon2Id = r.ReadByte(); - Pokemon2Position = (PBEFieldPosition)r.ReadByte(); - Pokemon2Team = battle.Teams[r.ReadByte()]; + throw new ArgumentException(); } + return new PBEAutoCenterPacket(team0Hidden ? byte.MaxValue : Pokemon1Id, Pokemon1Position, Pokemon1Team, team1Hidden ? byte.MaxValue : Pokemon2Id, Pokemon2Position, Pokemon2Team); } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/MatchCancelledPacket.cs b/PokemonBattleEngine/Packets/MatchCancelledPacket.cs index adadb9d4d..a1c264ad0 100644 --- a/PokemonBattleEngine/Packets/MatchCancelledPacket.cs +++ b/PokemonBattleEngine/Packets/MatchCancelledPacket.cs @@ -1,16 +1,28 @@ using Ether.Network.Packets; using Kermalis.PokemonBattleEngine.Battle; +using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEMatchCancelledPacket : INetPacket { public const short Code = 0x02; - public IEnumerable Buffer { get; } = new byte[] { 0x02, 0x00, 0x02, 0x00 }; + public ReadOnlyCollection Buffer { get; } - public PBEMatchCancelledPacket() { } - public PBEMatchCancelledPacket(byte[] buffer, PBEBattle battle) { } + public PBEMatchCancelledPacket() + { + var bytes = new List(); + bytes.AddRange(BitConverter.GetBytes(Code)); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); + } + internal PBEMatchCancelledPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) + { + Buffer = buffer; + } public void Dispose() { } } diff --git a/PokemonBattleEngine/Packets/PartyRequestPacket.cs b/PokemonBattleEngine/Packets/PartyRequestPacket.cs index cb07a2e65..612084e93 100644 --- a/PokemonBattleEngine/Packets/PartyRequestPacket.cs +++ b/PokemonBattleEngine/Packets/PartyRequestPacket.cs @@ -1,16 +1,28 @@ using Ether.Network.Packets; using Kermalis.PokemonBattleEngine.Battle; +using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEPartyRequestPacket : INetPacket { public const short Code = 0x03; - public IEnumerable Buffer { get; } = new byte[] { 0x02, 0x00, 0x03, 0x00 }; + public ReadOnlyCollection Buffer { get; } - public PBEPartyRequestPacket() { } - public PBEPartyRequestPacket(byte[] buffer, PBEBattle battle) { } + public PBEPartyRequestPacket() + { + var bytes = new List(); + bytes.AddRange(BitConverter.GetBytes(Code)); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); + } + internal PBEPartyRequestPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) + { + Buffer = buffer; + } public void Dispose() { } } diff --git a/PokemonBattleEngine/Packets/PartyResponsePacket.cs b/PokemonBattleEngine/Packets/PartyResponsePacket.cs index e67502b23..f97cbac1a 100644 --- a/PokemonBattleEngine/Packets/PartyResponsePacket.cs +++ b/PokemonBattleEngine/Packets/PartyResponsePacket.cs @@ -3,33 +3,34 @@ using Kermalis.PokemonBattleEngine.Data; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEPartyResponsePacket : INetPacket { public const short Code = 0x04; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public PBETeamShell TeamShell { get; } public PBEPartyResponsePacket(PBETeamShell teamShell) { + if (teamShell == null) + { + throw new ArgumentNullException(nameof(teamShell)); + } var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); - bytes.AddRange((TeamShell = teamShell).ToBytes()); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + (TeamShell = teamShell).ToBytes(bytes); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBEPartyResponsePacket(byte[] buffer, PBEBattle battle) + internal PBEPartyResponsePacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { Buffer = buffer; - using (var r = new BinaryReader(new MemoryStream(buffer))) - { - r.ReadInt16(); // Skip Code - TeamShell = new PBETeamShell(r); // What happens if an exception occurs? Similar question to https://github.com/Kermalis/PokemonBattleEngine/issues/167 - } + TeamShell = new PBETeamShell(battle.Settings, r); // What happens if an exception occurs? Similar question to https://github.com/Kermalis/PokemonBattleEngine/issues/167 } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/PlayerJoinedPacket.cs b/PokemonBattleEngine/Packets/PlayerJoinedPacket.cs index 5e85d680d..0787da4ab 100644 --- a/PokemonBattleEngine/Packets/PlayerJoinedPacket.cs +++ b/PokemonBattleEngine/Packets/PlayerJoinedPacket.cs @@ -2,15 +2,15 @@ using Kermalis.PokemonBattleEngine.Battle; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEPlayerJoinedPacket : INetPacket { public const short Code = 0x01; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public bool IsMe { get; } public int BattleId { get; } @@ -18,23 +18,28 @@ public sealed class PBEPlayerJoinedPacket : INetPacket public PBEPlayerJoinedPacket(bool isMe, int battleId, string trainerName) { + if (trainerName == null) + { + throw new ArgumentNullException(nameof(trainerName)); + } + if (string.IsNullOrWhiteSpace(trainerName)) + { + throw new ArgumentOutOfRangeException(nameof(trainerName)); + } var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); bytes.Add((byte)((IsMe = isMe) ? 1 : 0)); bytes.AddRange(BitConverter.GetBytes(BattleId = battleId)); - bytes.AddRange(PBEUtils.StringToBytes(TrainerName = trainerName)); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + PBEUtils.StringToBytes(bytes, TrainerName = trainerName); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBEPlayerJoinedPacket(byte[] buffer, PBEBattle battle) + internal PBEPlayerJoinedPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { Buffer = buffer; - using (var r = new BinaryReader(new MemoryStream(buffer))) - { - r.ReadInt16(); // Skip Code - IsMe = r.ReadBoolean(); - BattleId = r.ReadInt32(); - TrainerName = PBEUtils.StringFromBytes(r); - } + IsMe = r.ReadBoolean(); + BattleId = r.ReadInt32(); + TrainerName = PBEUtils.StringFromBytes(r); } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/ResponsePacket.cs b/PokemonBattleEngine/Packets/ResponsePacket.cs index cdace986c..b00c4c598 100644 --- a/PokemonBattleEngine/Packets/ResponsePacket.cs +++ b/PokemonBattleEngine/Packets/ResponsePacket.cs @@ -1,16 +1,28 @@ using Ether.Network.Packets; using Kermalis.PokemonBattleEngine.Battle; +using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEResponsePacket : INetPacket { public const short Code = 0x00; - public IEnumerable Buffer { get; } = new byte[] { 0x02, 0x00, 0x00, 0x00 }; + public ReadOnlyCollection Buffer { get; } - public PBEResponsePacket() { } - public PBEResponsePacket(byte[] buffer, PBEBattle battle) { } + public PBEResponsePacket() + { + var bytes = new List(); + bytes.AddRange(BitConverter.GetBytes(Code)); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); + } + internal PBEResponsePacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) + { + Buffer = buffer; + } public void Dispose() { } } diff --git a/PokemonBattleEngine/Packets/SetPartyPacket.cs b/PokemonBattleEngine/Packets/SetPartyPacket.cs deleted file mode 100644 index 22883d844..000000000 --- a/PokemonBattleEngine/Packets/SetPartyPacket.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Ether.Network.Packets; -using Kermalis.PokemonBattleEngine.Battle; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; -using System.Linq; - -namespace Kermalis.PokemonBattleEngine.Packets -{ - public sealed class PBESetPartyPacket : INetPacket - { - public const short Code = 0x05; - public IEnumerable Buffer { get; } - - public PBETeam Team { get; } - public ReadOnlyCollection Party { get; } - - public PBESetPartyPacket(PBETeam team) - { - var bytes = new List(); - bytes.AddRange(BitConverter.GetBytes(Code)); - bytes.Add((Team = team).Id); - bytes.Add((byte)(Party = Team.Party.AsReadOnly()).Count); - bytes.AddRange(Party.SelectMany(p => p.ToBytes())); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); - } - public PBESetPartyPacket(byte[] buffer, PBEBattle battle) - { - Buffer = buffer; - using (var r = new BinaryReader(new MemoryStream(buffer))) - { - r.ReadInt16(); // Skip Code - Team = battle.Teams[r.ReadByte()]; - var party = new PBEPokemon[r.ReadByte()]; - for (int i = 0; i < party.Length; i++) - { - party[i] = new PBEPokemon(r, Team); - } - Party = Array.AsReadOnly(party); - } - } - - public void Dispose() { } - } -} diff --git a/PokemonBattleEngine/Packets/SwitchInRequestPacket.cs b/PokemonBattleEngine/Packets/SwitchInRequestPacket.cs index 22b305949..957d57131 100644 --- a/PokemonBattleEngine/Packets/SwitchInRequestPacket.cs +++ b/PokemonBattleEngine/Packets/SwitchInRequestPacket.cs @@ -2,35 +2,33 @@ using Kermalis.PokemonBattleEngine.Battle; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBESwitchInRequestPacket : INetPacket { public const short Code = 0x23; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public PBETeam Team { get; } public byte Amount { get; } - public PBESwitchInRequestPacket(PBETeam team) + internal PBESwitchInRequestPacket(PBETeam team) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); bytes.Add((Team = team).Id); bytes.Add(Amount = Team.SwitchInsRequired); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBESwitchInRequestPacket(byte[] buffer, PBEBattle battle) + internal PBESwitchInRequestPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { - using (var r = new BinaryReader(new MemoryStream(buffer))) - { - r.ReadInt16(); // Skip Code - Team = battle.Teams[r.ReadByte()]; - Amount = r.ReadByte(); - } + Buffer = buffer; + Team = battle.Teams[r.ReadByte()]; + Amount = r.ReadByte(); } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/SwitchInResponsePacket.cs b/PokemonBattleEngine/Packets/SwitchInResponsePacket.cs index bc6ad0bb4..d8199d273 100644 --- a/PokemonBattleEngine/Packets/SwitchInResponsePacket.cs +++ b/PokemonBattleEngine/Packets/SwitchInResponsePacket.cs @@ -1,6 +1,5 @@ using Ether.Network.Packets; using Kermalis.PokemonBattleEngine.Battle; -using Kermalis.PokemonBattleEngine.Data; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -12,31 +11,43 @@ namespace Kermalis.PokemonBattleEngine.Packets public sealed class PBESwitchInResponsePacket : INetPacket { public const short Code = 0x24; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } - public ReadOnlyCollection<(byte PokemonId, PBEFieldPosition Position)> Switches { get; } + public ReadOnlyCollection Switches { get; } - public PBESwitchInResponsePacket(IEnumerable<(byte PokemonId, PBEFieldPosition Position)> switches) + public PBESwitchInResponsePacket(IList switches) { + if (switches == null) + { + throw new ArgumentNullException(nameof(switches)); + } + if (switches.Count == 0) + { + throw new ArgumentOutOfRangeException(nameof(switches)); + } + if (switches.Any(s => s == null)) + { + throw new ArgumentNullException(nameof(switches)); + } var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); - bytes.Add((byte)(Switches = switches.ToList().AsReadOnly()).Count); - bytes.AddRange(Switches.SelectMany(s => new byte[] { s.PokemonId, (byte)s.Position })); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.Add((byte)(Switches = new ReadOnlyCollection(switches)).Count); + for (int i = 0; i < (byte)Switches.Count; i++) + { + Switches[i].ToBytes(bytes); + } + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBESwitchInResponsePacket(byte[] buffer, PBEBattle battle) + internal PBESwitchInResponsePacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { Buffer = buffer; - using (var r = new BinaryReader(new MemoryStream(buffer))) + var switches = new PBESwitchIn[r.ReadByte()]; + for (int i = 0; i < switches.Length; i++) { - r.ReadInt16(); // Skip Code - var switches = new (byte PokemonId, PBEFieldPosition Position)[r.ReadByte()]; - for (int i = 0; i < switches.Length; i++) - { - switches[i] = (r.ReadByte(), (PBEFieldPosition)r.ReadByte()); - } - Switches = Array.AsReadOnly(switches); + switches[i] = new PBESwitchIn(r); } + Switches = new ReadOnlyCollection(switches); } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/TeamPacket.cs b/PokemonBattleEngine/Packets/TeamPacket.cs new file mode 100644 index 000000000..5866c5585 --- /dev/null +++ b/PokemonBattleEngine/Packets/TeamPacket.cs @@ -0,0 +1,35 @@ +using Ether.Network.Packets; +using Kermalis.PokemonBattleEngine.Battle; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; + +namespace Kermalis.PokemonBattleEngine.Packets +{ + public sealed class PBETeamPacket : INetPacket + { + public const short Code = 0x05; + public ReadOnlyCollection Buffer { get; } + + public PBETeam Team { get; } + + internal PBETeamPacket(PBETeam team) + { + var bytes = new List(); + bytes.AddRange(BitConverter.GetBytes(Code)); + bytes.Add((Team = team).Id); + Team.ToBytes(bytes); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); + } + internal PBETeamPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) + { + Buffer = buffer; + Team = battle.Teams[r.ReadByte()]; + Team.FromBytes(r); + } + + public void Dispose() { } + } +} diff --git a/PokemonBattleEngine/Packets/TurnBeganPacket.cs b/PokemonBattleEngine/Packets/TurnBeganPacket.cs index ca83e04f4..fc5b9db2d 100644 --- a/PokemonBattleEngine/Packets/TurnBeganPacket.cs +++ b/PokemonBattleEngine/Packets/TurnBeganPacket.cs @@ -2,35 +2,30 @@ using Kermalis.PokemonBattleEngine.Battle; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBETurnBeganPacket : INetPacket { public const short Code = 0x27; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public ushort TurnNumber { get; } - public DateTime Time { get; } - public PBETurnBeganPacket(ushort turnNumber) + internal PBETurnBeganPacket(ushort turnNumber) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); bytes.AddRange(BitConverter.GetBytes(TurnNumber = turnNumber)); - bytes.AddRange(BitConverter.GetBytes((Time = DateTime.Now).ToBinary())); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBETurnBeganPacket(byte[] buffer, PBEBattle battle) + internal PBETurnBeganPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { - using (var r = new BinaryReader(new MemoryStream(buffer))) - { - r.ReadInt16(); // Skip Code - TurnNumber = r.ReadUInt16(); - Time = DateTime.FromBinary(r.ReadInt64()); - } + Buffer = buffer; + TurnNumber = r.ReadUInt16(); } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/WinnerPacket.cs b/PokemonBattleEngine/Packets/WinnerPacket.cs index 9ac3b05f4..82d054a4e 100644 --- a/PokemonBattleEngine/Packets/WinnerPacket.cs +++ b/PokemonBattleEngine/Packets/WinnerPacket.cs @@ -2,33 +2,30 @@ using Kermalis.PokemonBattleEngine.Battle; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEWinnerPacket : INetPacket { public const short Code = 0x26; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public PBETeam WinningTeam { get; } - public PBEWinnerPacket(PBETeam winningTeam) + internal PBEWinnerPacket(PBETeam winningTeam) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); bytes.Add((WinningTeam = winningTeam).Id); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBEWinnerPacket(byte[] buffer, PBEBattle battle) + internal PBEWinnerPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { Buffer = buffer; - using (var r = new BinaryReader(new MemoryStream(buffer))) - { - r.ReadInt16(); // Skip Code - WinningTeam = battle.Teams[r.ReadByte()]; - } + WinningTeam = battle.Teams[r.ReadByte()]; } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/_AbilityPacket.cs b/PokemonBattleEngine/Packets/_AbilityPacket.cs index e1e21ddf0..704f7e10d 100644 --- a/PokemonBattleEngine/Packets/_AbilityPacket.cs +++ b/PokemonBattleEngine/Packets/_AbilityPacket.cs @@ -3,15 +3,15 @@ using Kermalis.PokemonBattleEngine.Data; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEAbilityPacket : INetPacket { public const short Code = 0x19; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public PBEFieldPosition AbilityOwner { get; } public PBETeam AbilityOwnerTeam { get; } @@ -20,7 +20,7 @@ public sealed class PBEAbilityPacket : INetPacket public PBEAbility Ability { get; } public PBEAbilityAction AbilityAction { get; } - public PBEAbilityPacket(PBEPokemon abilityOwner, PBEPokemon pokemon2, PBEAbility ability, PBEAbilityAction abilityAction) + internal PBEAbilityPacket(PBEPokemon abilityOwner, PBEPokemon pokemon2, PBEAbility ability, PBEAbilityAction abilityAction) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); @@ -30,21 +30,18 @@ public PBEAbilityPacket(PBEPokemon abilityOwner, PBEPokemon pokemon2, PBEAbility bytes.Add((Pokemon2Team = pokemon2.Team).Id); bytes.Add((byte)(Ability = ability)); bytes.Add((byte)(AbilityAction = abilityAction)); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBEAbilityPacket(byte[] buffer, PBEBattle battle) + internal PBEAbilityPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { Buffer = buffer; - using (var r = new BinaryReader(new MemoryStream(buffer))) - { - r.ReadInt16(); // Skip Code - AbilityOwner = (PBEFieldPosition)r.ReadByte(); - AbilityOwnerTeam = battle.Teams[r.ReadByte()]; - Pokemon2 = (PBEFieldPosition)r.ReadByte(); - Pokemon2Team = battle.Teams[r.ReadByte()]; - Ability = (PBEAbility)r.ReadByte(); - AbilityAction = (PBEAbilityAction)r.ReadByte(); - } + AbilityOwner = (PBEFieldPosition)r.ReadByte(); + AbilityOwnerTeam = battle.Teams[r.ReadByte()]; + Pokemon2 = (PBEFieldPosition)r.ReadByte(); + Pokemon2Team = battle.Teams[r.ReadByte()]; + Ability = (PBEAbility)r.ReadByte(); + AbilityAction = (PBEAbilityAction)r.ReadByte(); } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/_BattleStatusPacket.cs b/PokemonBattleEngine/Packets/_BattleStatusPacket.cs index 4b4931936..d07361f06 100644 --- a/PokemonBattleEngine/Packets/_BattleStatusPacket.cs +++ b/PokemonBattleEngine/Packets/_BattleStatusPacket.cs @@ -3,35 +3,33 @@ using Kermalis.PokemonBattleEngine.Data; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEBattleStatusPacket : INetPacket { public const short Code = 0x21; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public PBEBattleStatus BattleStatus { get; } public PBEBattleStatusAction BattleStatusAction { get; } - public PBEBattleStatusPacket(PBEBattleStatus battleStatus, PBEBattleStatusAction battleStatusAction) + internal PBEBattleStatusPacket(PBEBattleStatus battleStatus, PBEBattleStatusAction battleStatusAction) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); bytes.Add((byte)(BattleStatus = battleStatus)); bytes.Add((byte)(BattleStatusAction = battleStatusAction)); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBEBattleStatusPacket(byte[] buffer, PBEBattle battle) + internal PBEBattleStatusPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { - using (var r = new BinaryReader(new MemoryStream(buffer))) - { - r.ReadInt16(); // Skip Code - BattleStatus = (PBEBattleStatus)r.ReadByte(); - BattleStatusAction = (PBEBattleStatusAction)r.ReadByte(); - } + Buffer = buffer; + BattleStatus = (PBEBattleStatus)r.ReadByte(); + BattleStatusAction = (PBEBattleStatusAction)r.ReadByte(); } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/_IllusionPacket.cs b/PokemonBattleEngine/Packets/_IllusionPacket.cs index d6a727b5f..a374740b7 100644 --- a/PokemonBattleEngine/Packets/_IllusionPacket.cs +++ b/PokemonBattleEngine/Packets/_IllusionPacket.cs @@ -3,15 +3,15 @@ using Kermalis.PokemonBattleEngine.Data; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEIllusionPacket : INetPacket { public const short Code = 0x25; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public PBEFieldPosition Pokemon { get; } public PBETeam PokemonTeam { get; } @@ -23,37 +23,34 @@ public sealed class PBEIllusionPacket : INetPacket public PBEType ActualType2 { get; } public double ActualWeight { get; } - public PBEIllusionPacket(PBEPokemon pokemon) + internal PBEIllusionPacket(PBEPokemon pokemon) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); bytes.Add((byte)(Pokemon = pokemon.FieldPosition)); bytes.Add((PokemonTeam = pokemon.Team).Id); bytes.Add((byte)(ActualGender = pokemon.Gender)); - bytes.AddRange(PBEUtils.StringToBytes(ActualNickname = pokemon.Nickname)); + PBEUtils.StringToBytes(bytes, ActualNickname = pokemon.Nickname); bytes.Add((byte)((ActualShiny = pokemon.Shiny) ? 1 : 0)); bytes.AddRange(BitConverter.GetBytes((ushort)(ActualSpecies = pokemon.Species))); bytes.Add((byte)(ActualType1 = pokemon.Type1)); bytes.Add((byte)(ActualType2 = pokemon.Type2)); bytes.AddRange(BitConverter.GetBytes(ActualWeight = pokemon.Weight)); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBEIllusionPacket(byte[] buffer, PBEBattle battle) + internal PBEIllusionPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { Buffer = buffer; - using (var r = new BinaryReader(new MemoryStream(buffer))) - { - r.ReadInt16(); // Skip Code - Pokemon = (PBEFieldPosition)r.ReadByte(); - PokemonTeam = battle.Teams[r.ReadByte()]; - ActualGender = (PBEGender)r.ReadByte(); - ActualNickname = PBEUtils.StringFromBytes(r); - ActualShiny = r.ReadBoolean(); - ActualSpecies = (PBESpecies)r.ReadUInt16(); - ActualType1 = (PBEType)r.ReadByte(); - ActualType2 = (PBEType)r.ReadByte(); - ActualWeight = r.ReadDouble(); - } + Pokemon = (PBEFieldPosition)r.ReadByte(); + PokemonTeam = battle.Teams[r.ReadByte()]; + ActualGender = (PBEGender)r.ReadByte(); + ActualNickname = PBEUtils.StringFromBytes(r); + ActualShiny = r.ReadBoolean(); + ActualSpecies = (PBESpecies)r.ReadUInt16(); + ActualType1 = (PBEType)r.ReadByte(); + ActualType2 = (PBEType)r.ReadByte(); + ActualWeight = r.ReadDouble(); } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/_ItemPacket.cs b/PokemonBattleEngine/Packets/_ItemPacket.cs index 552d749fc..2e51fcb51 100644 --- a/PokemonBattleEngine/Packets/_ItemPacket.cs +++ b/PokemonBattleEngine/Packets/_ItemPacket.cs @@ -3,15 +3,15 @@ using Kermalis.PokemonBattleEngine.Data; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEItemPacket : INetPacket { public const short Code = 0x16; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public PBEFieldPosition ItemHolder { get; } public PBETeam ItemHolderTeam { get; } @@ -20,7 +20,7 @@ public sealed class PBEItemPacket : INetPacket public PBEItem Item { get; } public PBEItemAction ItemAction { get; } - public PBEItemPacket(PBEPokemon itemHolder, PBEPokemon pokemon2, PBEItem item, PBEItemAction itemAction) + internal PBEItemPacket(PBEPokemon itemHolder, PBEPokemon pokemon2, PBEItem item, PBEItemAction itemAction) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); @@ -30,21 +30,18 @@ public PBEItemPacket(PBEPokemon itemHolder, PBEPokemon pokemon2, PBEItem item, P bytes.Add((Pokemon2Team = pokemon2.Team).Id); bytes.AddRange(BitConverter.GetBytes((ushort)(Item = item))); bytes.Add((byte)(ItemAction = itemAction)); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBEItemPacket(byte[] buffer, PBEBattle battle) + internal PBEItemPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { Buffer = buffer; - using (var r = new BinaryReader(new MemoryStream(buffer))) - { - r.ReadInt16(); // Skip Code - ItemHolder = (PBEFieldPosition)r.ReadByte(); - ItemHolderTeam = battle.Teams[r.ReadByte()]; - Pokemon2 = (PBEFieldPosition)r.ReadByte(); - Pokemon2Team = battle.Teams[r.ReadByte()]; - Item = (PBEItem)r.ReadUInt16(); - ItemAction = (PBEItemAction)r.ReadByte(); - } + ItemHolder = (PBEFieldPosition)r.ReadByte(); + ItemHolderTeam = battle.Teams[r.ReadByte()]; + Pokemon2 = (PBEFieldPosition)r.ReadByte(); + Pokemon2Team = battle.Teams[r.ReadByte()]; + Item = (PBEItem)r.ReadUInt16(); + ItemAction = (PBEItemAction)r.ReadByte(); } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/_MoveCritPacket.cs b/PokemonBattleEngine/Packets/_MoveCritPacket.cs index 762d9ddfd..1f5abe682 100644 --- a/PokemonBattleEngine/Packets/_MoveCritPacket.cs +++ b/PokemonBattleEngine/Packets/_MoveCritPacket.cs @@ -1,17 +1,36 @@ using Ether.Network.Packets; using Kermalis.PokemonBattleEngine.Battle; +using Kermalis.PokemonBattleEngine.Data; +using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; namespace Kermalis.PokemonBattleEngine.Packets { - // TODO: Include IDs public sealed class PBEMoveCritPacket : INetPacket { public const short Code = 0x0F; - public IEnumerable Buffer { get; } = new byte[] { 0x02, 0x00, 0x0F, 0x00 }; + public ReadOnlyCollection Buffer { get; } - public PBEMoveCritPacket() { } - public PBEMoveCritPacket(byte[] buffer, PBEBattle battle) { } + public PBEFieldPosition Victim { get; } + public PBETeam VictimTeam { get; } + + internal PBEMoveCritPacket(PBEPokemon victim) + { + var bytes = new List(); + bytes.AddRange(BitConverter.GetBytes(Code)); + bytes.Add((byte)(Victim = victim.FieldPosition)); + bytes.Add((VictimTeam = victim.Team).Id); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); + } + internal PBEMoveCritPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) + { + Buffer = buffer; + Victim = (PBEFieldPosition)r.ReadByte(); + VictimTeam = battle.Teams[r.ReadByte()]; + } public void Dispose() { } } diff --git a/PokemonBattleEngine/Packets/_MoveEffectivenessPacket.cs b/PokemonBattleEngine/Packets/_MoveEffectivenessPacket.cs index 048935ed1..dec97740a 100644 --- a/PokemonBattleEngine/Packets/_MoveEffectivenessPacket.cs +++ b/PokemonBattleEngine/Packets/_MoveEffectivenessPacket.cs @@ -3,39 +3,36 @@ using Kermalis.PokemonBattleEngine.Data; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEMoveEffectivenessPacket : INetPacket { public const short Code = 0x0B; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public PBEFieldPosition Victim { get; } public PBETeam VictimTeam { get; } public PBEEffectiveness Effectiveness { get; } - public PBEMoveEffectivenessPacket(PBEPokemon victim, PBEEffectiveness effectiveness) + internal PBEMoveEffectivenessPacket(PBEPokemon victim, PBEEffectiveness effectiveness) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); bytes.Add((byte)(Victim = victim.FieldPosition)); bytes.Add((VictimTeam = victim.Team).Id); bytes.Add((byte)(Effectiveness = effectiveness)); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBEMoveEffectivenessPacket(byte[] buffer, PBEBattle battle) + internal PBEMoveEffectivenessPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { Buffer = buffer; - using (var r = new BinaryReader(new MemoryStream(buffer))) - { - r.ReadInt16(); // Skip Code - Victim = (PBEFieldPosition)r.ReadByte(); - VictimTeam = battle.Teams[r.ReadByte()]; - Effectiveness = (PBEEffectiveness)r.ReadByte(); - } + Victim = (PBEFieldPosition)r.ReadByte(); + VictimTeam = battle.Teams[r.ReadByte()]; + Effectiveness = (PBEEffectiveness)r.ReadByte(); } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/_MoveFailedPacket.cs b/PokemonBattleEngine/Packets/_MoveFailedPacket.cs index 1c6809263..3b937dc51 100644 --- a/PokemonBattleEngine/Packets/_MoveFailedPacket.cs +++ b/PokemonBattleEngine/Packets/_MoveFailedPacket.cs @@ -3,15 +3,15 @@ using Kermalis.PokemonBattleEngine.Data; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEMoveFailedPacket : INetPacket { public const short Code = 0x15; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public PBEFieldPosition MoveUser { get; } public PBETeam MoveUserTeam { get; } @@ -19,7 +19,7 @@ public sealed class PBEMoveFailedPacket : INetPacket public PBETeam Pokemon2Team { get; } public PBEFailReason FailReason { get; } - public PBEMoveFailedPacket(PBEPokemon moveUser, PBEPokemon pokemon2, PBEFailReason failReason) + internal PBEMoveFailedPacket(PBEPokemon moveUser, PBEPokemon pokemon2, PBEFailReason failReason) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); @@ -28,20 +28,17 @@ public PBEMoveFailedPacket(PBEPokemon moveUser, PBEPokemon pokemon2, PBEFailReas bytes.Add((byte)(Pokemon2 = pokemon2.FieldPosition)); bytes.Add((Pokemon2Team = pokemon2.Team).Id); bytes.Add((byte)(FailReason = failReason)); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBEMoveFailedPacket(byte[] buffer, PBEBattle battle) + internal PBEMoveFailedPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { Buffer = buffer; - using (var r = new BinaryReader(new MemoryStream(buffer))) - { - r.ReadInt16(); // Skip Code - MoveUser = (PBEFieldPosition)r.ReadByte(); - MoveUserTeam = battle.Teams[r.ReadByte()]; - Pokemon2 = (PBEFieldPosition)r.ReadByte(); - Pokemon2Team = battle.Teams[r.ReadByte()]; - FailReason = (PBEFailReason)r.ReadByte(); - } + MoveUser = (PBEFieldPosition)r.ReadByte(); + MoveUserTeam = battle.Teams[r.ReadByte()]; + Pokemon2 = (PBEFieldPosition)r.ReadByte(); + Pokemon2Team = battle.Teams[r.ReadByte()]; + FailReason = (PBEFailReason)r.ReadByte(); } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/_MoveLockPacket.cs b/PokemonBattleEngine/Packets/_MoveLockPacket.cs index e012fade2..baf59b168 100644 --- a/PokemonBattleEngine/Packets/_MoveLockPacket.cs +++ b/PokemonBattleEngine/Packets/_MoveLockPacket.cs @@ -3,23 +3,23 @@ using Kermalis.PokemonBattleEngine.Data; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEMoveLockPacket : INetPacket { public const short Code = 0x28; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public PBEFieldPosition MoveUser { get; } public PBETeam MoveUserTeam { get; } public PBEMove LockedMove { get; } - public PBETarget LockedTargets { get; } + public PBETurnTarget LockedTargets { get; } public PBEMoveLockType MoveLockType { get; } - public PBEMoveLockPacket(PBEPokemon moveUser, PBEMove lockedMove, PBETarget lockedTargets, PBEMoveLockType moveLockType) + internal PBEMoveLockPacket(PBEPokemon moveUser, PBEMove lockedMove, PBETurnTarget lockedTargets, PBEMoveLockType moveLockType) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); @@ -28,20 +28,17 @@ public PBEMoveLockPacket(PBEPokemon moveUser, PBEMove lockedMove, PBETarget lock bytes.AddRange(BitConverter.GetBytes((ushort)(LockedMove = lockedMove))); bytes.Add((byte)(LockedTargets = lockedTargets)); bytes.Add((byte)(MoveLockType = moveLockType)); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBEMoveLockPacket(byte[] buffer, PBEBattle battle) + internal PBEMoveLockPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { Buffer = buffer; - using (var r = new BinaryReader(new MemoryStream(buffer))) - { - r.ReadInt16(); // Skip Code - MoveUser = (PBEFieldPosition)r.ReadByte(); - MoveUserTeam = battle.Teams[r.ReadByte()]; - LockedMove = (PBEMove)r.ReadUInt16(); - LockedTargets = (PBETarget)r.ReadByte(); - MoveLockType = (PBEMoveLockType)r.ReadByte(); - } + MoveUser = (PBEFieldPosition)r.ReadByte(); + MoveUserTeam = battle.Teams[r.ReadByte()]; + LockedMove = (PBEMove)r.ReadUInt16(); + LockedTargets = (PBETurnTarget)r.ReadByte(); + MoveLockType = (PBEMoveLockType)r.ReadByte(); } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/_MoveMissedPacket.cs b/PokemonBattleEngine/Packets/_MoveMissedPacket.cs index 373d35be3..b7635c630 100644 --- a/PokemonBattleEngine/Packets/_MoveMissedPacket.cs +++ b/PokemonBattleEngine/Packets/_MoveMissedPacket.cs @@ -3,22 +3,22 @@ using Kermalis.PokemonBattleEngine.Data; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEMoveMissedPacket : INetPacket { public const short Code = 0x0D; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public PBEFieldPosition MoveUser { get; } public PBETeam MoveUserTeam { get; } public PBEFieldPosition Pokemon2 { get; } public PBETeam Pokemon2Team { get; } - public PBEMoveMissedPacket(PBEPokemon moveUser, PBEPokemon pokemon2) + internal PBEMoveMissedPacket(PBEPokemon moveUser, PBEPokemon pokemon2) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); @@ -26,19 +26,16 @@ public PBEMoveMissedPacket(PBEPokemon moveUser, PBEPokemon pokemon2) bytes.Add((MoveUserTeam = moveUser.Team).Id); bytes.Add((byte)(Pokemon2 = pokemon2.FieldPosition)); bytes.Add((Pokemon2Team = pokemon2.Team).Id); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBEMoveMissedPacket(byte[] buffer, PBEBattle battle) + internal PBEMoveMissedPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { Buffer = buffer; - using (var r = new BinaryReader(new MemoryStream(buffer))) - { - r.ReadInt16(); // Skip Code - MoveUser = (PBEFieldPosition)r.ReadByte(); - MoveUserTeam = battle.Teams[r.ReadByte()]; - Pokemon2 = (PBEFieldPosition)r.ReadByte(); - Pokemon2Team = battle.Teams[r.ReadByte()]; - } + MoveUser = (PBEFieldPosition)r.ReadByte(); + MoveUserTeam = battle.Teams[r.ReadByte()]; + Pokemon2 = (PBEFieldPosition)r.ReadByte(); + Pokemon2Team = battle.Teams[r.ReadByte()]; } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/_MovePPChangedPacket.cs b/PokemonBattleEngine/Packets/_MovePPChangedPacket.cs index 487108b91..f5ceaf3db 100644 --- a/PokemonBattleEngine/Packets/_MovePPChangedPacket.cs +++ b/PokemonBattleEngine/Packets/_MovePPChangedPacket.cs @@ -3,45 +3,39 @@ using Kermalis.PokemonBattleEngine.Data; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEMovePPChangedPacket : INetPacket { public const short Code = 0x17; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public PBEFieldPosition MoveUser { get; } public PBETeam MoveUserTeam { get; } public PBEMove Move { get; } - public byte OldValue { get; } - public byte NewValue { get; } + public int AmountReduced { get; } - public PBEMovePPChangedPacket(PBEFieldPosition moveUser, PBETeam moveUserTeam, PBEMove move, byte oldValue, byte newValue) + internal PBEMovePPChangedPacket(PBEFieldPosition moveUser, PBETeam moveUserTeam, PBEMove move, int amountReduced) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); bytes.Add((byte)(MoveUser = moveUser)); bytes.Add((MoveUserTeam = moveUserTeam).Id); bytes.AddRange(BitConverter.GetBytes((ushort)(Move = move))); - bytes.Add(OldValue = oldValue); - bytes.Add(NewValue = newValue); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.AddRange(BitConverter.GetBytes(AmountReduced = amountReduced)); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBEMovePPChangedPacket(byte[] buffer, PBEBattle battle) + internal PBEMovePPChangedPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { Buffer = buffer; - using (var r = new BinaryReader(new MemoryStream(buffer))) - { - r.ReadInt16(); // Skip Code - MoveUser = (PBEFieldPosition)r.ReadByte(); - MoveUserTeam = battle.Teams[r.ReadByte()]; - Move = (PBEMove)r.ReadUInt16(); - OldValue = r.ReadByte(); - NewValue = r.ReadByte(); - } + MoveUser = (PBEFieldPosition)r.ReadByte(); + MoveUserTeam = battle.Teams[r.ReadByte()]; + Move = (PBEMove)r.ReadUInt16(); + AmountReduced = r.ReadInt32(); } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/_MoveUsedPacket.cs b/PokemonBattleEngine/Packets/_MoveUsedPacket.cs index 5b6c23f78..f5570412e 100644 --- a/PokemonBattleEngine/Packets/_MoveUsedPacket.cs +++ b/PokemonBattleEngine/Packets/_MoveUsedPacket.cs @@ -3,22 +3,22 @@ using Kermalis.PokemonBattleEngine.Data; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEMoveUsedPacket : INetPacket { public const short Code = 0x09; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public PBEFieldPosition MoveUser { get; } public PBETeam MoveUserTeam { get; } public PBEMove Move { get; } public bool Reveal { get; } - public PBEMoveUsedPacket(PBEPokemon moveUser, PBEMove move, bool reveal) + internal PBEMoveUsedPacket(PBEPokemon moveUser, PBEMove move, bool reveal) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); @@ -26,19 +26,16 @@ public PBEMoveUsedPacket(PBEPokemon moveUser, PBEMove move, bool reveal) bytes.Add((MoveUserTeam = moveUser.Team).Id); bytes.AddRange(BitConverter.GetBytes((ushort)(Move = move))); bytes.Add((byte)((Reveal = reveal) ? 1 : 0)); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBEMoveUsedPacket(byte[] buffer, PBEBattle battle) + internal PBEMoveUsedPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { Buffer = buffer; - using (var r = new BinaryReader(new MemoryStream(buffer))) - { - r.ReadInt16(); // Skip Code - MoveUser = (PBEFieldPosition)r.ReadByte(); - MoveUserTeam = battle.Teams[r.ReadByte()]; - Move = (PBEMove)r.ReadUInt16(); - Reveal = r.ReadBoolean(); - } + MoveUser = (PBEFieldPosition)r.ReadByte(); + MoveUserTeam = battle.Teams[r.ReadByte()]; + Move = (PBEMove)r.ReadUInt16(); + Reveal = r.ReadBoolean(); } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/_PkmnFaintedPacket.cs b/PokemonBattleEngine/Packets/_PkmnFaintedPacket.cs index 3b451342c..f80b7b7dd 100644 --- a/PokemonBattleEngine/Packets/_PkmnFaintedPacket.cs +++ b/PokemonBattleEngine/Packets/_PkmnFaintedPacket.cs @@ -3,39 +3,41 @@ using Kermalis.PokemonBattleEngine.Data; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEPkmnFaintedPacket : INetPacket { public const short Code = 0x0E; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public byte PokemonId { get; } public PBEFieldPosition PokemonPosition { get; } public PBETeam PokemonTeam { get; } - public PBEPkmnFaintedPacket(byte pokemonId, PBEFieldPosition pokemonPosition, PBETeam pokemonTeam) + internal PBEPkmnFaintedPacket(byte pokemonId, PBEFieldPosition pokemonPosition, PBETeam pokemonTeam) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); bytes.Add(PokemonId = pokemonId); bytes.Add((byte)(PokemonPosition = pokemonPosition)); bytes.Add((PokemonTeam = pokemonTeam).Id); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBEPkmnFaintedPacket(byte[] buffer, PBEBattle battle) + internal PBEPkmnFaintedPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { Buffer = buffer; - using (var r = new BinaryReader(new MemoryStream(buffer))) - { - r.ReadInt16(); // Skip Code - PokemonId = r.ReadByte(); - PokemonPosition = (PBEFieldPosition)r.ReadByte(); - PokemonTeam = battle.Teams[r.ReadByte()]; - } + PokemonId = r.ReadByte(); + PokemonPosition = (PBEFieldPosition)r.ReadByte(); + PokemonTeam = battle.Teams[r.ReadByte()]; + } + + public PBEPkmnFaintedPacket MakeHidden() + { + return new PBEPkmnFaintedPacket(byte.MaxValue, PokemonPosition, PokemonTeam); } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/_PkmnFormChangedPacket.cs b/PokemonBattleEngine/Packets/_PkmnFormChangedPacket.cs index bd0a44733..f8887db99 100644 --- a/PokemonBattleEngine/Packets/_PkmnFormChangedPacket.cs +++ b/PokemonBattleEngine/Packets/_PkmnFormChangedPacket.cs @@ -3,15 +3,15 @@ using Kermalis.PokemonBattleEngine.Data; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEPkmnFormChangedPacket : INetPacket { public const short Code = 0x29; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public PBEFieldPosition Pokemon { get; } public PBETeam PokemonTeam { get; } @@ -27,7 +27,8 @@ public sealed class PBEPkmnFormChangedPacket : INetPacket public PBEType NewType2 { get; } public double NewWeight { get; } - public PBEPkmnFormChangedPacket(PBEFieldPosition pokemonPosition, PBETeam pokemonTeam, ushort newAttack, ushort newDefense, ushort newSpAttack, ushort newSpDefense, ushort newSpeed, PBEAbility newAbility, PBEAbility newKnownAbility, PBESpecies newSpecies, PBEType newType1, PBEType newType2, double newWeight) + internal PBEPkmnFormChangedPacket(PBEFieldPosition pokemonPosition, PBETeam pokemonTeam, ushort newAttack, ushort newDefense, ushort newSpAttack, ushort newSpDefense, ushort newSpeed, + PBEAbility newAbility, PBEAbility newKnownAbility, PBESpecies newSpecies, PBEType newType1, PBEType newType2, double newWeight) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); @@ -44,28 +45,30 @@ public PBEPkmnFormChangedPacket(PBEFieldPosition pokemonPosition, PBETeam pokemo bytes.Add((byte)(NewType1 = newType1)); bytes.Add((byte)(NewType2 = newType2)); bytes.AddRange(BitConverter.GetBytes(NewWeight = newWeight)); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBEPkmnFormChangedPacket(byte[] buffer, PBEBattle battle) + internal PBEPkmnFormChangedPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { Buffer = buffer; - using (var r = new BinaryReader(new MemoryStream(buffer))) - { - r.ReadInt16(); // Skip Code - Pokemon = (PBEFieldPosition)r.ReadByte(); - PokemonTeam = battle.Teams[r.ReadByte()]; - NewAttack = r.ReadUInt16(); - NewDefense = r.ReadUInt16(); - NewSpAttack = r.ReadUInt16(); - NewSpDefense = r.ReadUInt16(); - NewSpeed = r.ReadUInt16(); - NewAbility = (PBEAbility)r.ReadByte(); - NewKnownAbility = (PBEAbility)r.ReadByte(); - NewSpecies = (PBESpecies)r.ReadUInt32(); - NewType1 = (PBEType)r.ReadByte(); - NewType2 = (PBEType)r.ReadByte(); - NewWeight = r.ReadDouble(); - } + Pokemon = (PBEFieldPosition)r.ReadByte(); + PokemonTeam = battle.Teams[r.ReadByte()]; + NewAttack = r.ReadUInt16(); + NewDefense = r.ReadUInt16(); + NewSpAttack = r.ReadUInt16(); + NewSpDefense = r.ReadUInt16(); + NewSpeed = r.ReadUInt16(); + NewAbility = (PBEAbility)r.ReadByte(); + NewKnownAbility = (PBEAbility)r.ReadByte(); + NewSpecies = (PBESpecies)r.ReadUInt32(); + NewType1 = (PBEType)r.ReadByte(); + NewType2 = (PBEType)r.ReadByte(); + NewWeight = r.ReadDouble(); + } + + public PBEPkmnFormChangedPacket MakeHidden() + { + return new PBEPkmnFormChangedPacket(Pokemon, PokemonTeam, ushort.MinValue, ushort.MinValue, ushort.MinValue, ushort.MinValue, ushort.MinValue, NewKnownAbility != PBEAbility.MAX ? NewAbility : PBEAbility.MAX, NewKnownAbility, NewSpecies, NewType1, NewType2, NewWeight); } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/_PkmnHPChangedPacket.cs b/PokemonBattleEngine/Packets/_PkmnHPChangedPacket.cs index 38fea5f65..42f68260d 100644 --- a/PokemonBattleEngine/Packets/_PkmnHPChangedPacket.cs +++ b/PokemonBattleEngine/Packets/_PkmnHPChangedPacket.cs @@ -3,15 +3,15 @@ using Kermalis.PokemonBattleEngine.Data; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEPkmnHPChangedPacket : INetPacket { public const short Code = 0x0A; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public PBEFieldPosition Pokemon { get; } public PBETeam PokemonTeam { get; } @@ -20,7 +20,7 @@ public sealed class PBEPkmnHPChangedPacket : INetPacket public double OldHPPercentage { get; } public double NewHPPercentage { get; } - public PBEPkmnHPChangedPacket(PBEFieldPosition pokemon, PBETeam pokemonTeam, ushort oldHP, ushort newHP, double oldHPPercentage, double newHPPercentage) + internal PBEPkmnHPChangedPacket(PBEFieldPosition pokemon, PBETeam pokemonTeam, ushort oldHP, ushort newHP, double oldHPPercentage, double newHPPercentage) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); @@ -30,21 +30,23 @@ public PBEPkmnHPChangedPacket(PBEFieldPosition pokemon, PBETeam pokemonTeam, ush bytes.AddRange(BitConverter.GetBytes(NewHP = newHP)); bytes.AddRange(BitConverter.GetBytes(OldHPPercentage = oldHPPercentage)); bytes.AddRange(BitConverter.GetBytes(NewHPPercentage = newHPPercentage)); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBEPkmnHPChangedPacket(byte[] buffer, PBEBattle battle) + internal PBEPkmnHPChangedPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { Buffer = buffer; - using (var r = new BinaryReader(new MemoryStream(buffer))) - { - r.ReadInt16(); // Skip Code - Pokemon = (PBEFieldPosition)r.ReadByte(); - PokemonTeam = battle.Teams[r.ReadByte()]; - OldHP = r.ReadUInt16(); - NewHP = r.ReadUInt16(); - OldHPPercentage = r.ReadDouble(); - NewHPPercentage = r.ReadDouble(); - } + Pokemon = (PBEFieldPosition)r.ReadByte(); + PokemonTeam = battle.Teams[r.ReadByte()]; + OldHP = r.ReadUInt16(); + NewHP = r.ReadUInt16(); + OldHPPercentage = r.ReadDouble(); + NewHPPercentage = r.ReadDouble(); + } + + public PBEPkmnHPChangedPacket MakeHidden() + { + return new PBEPkmnHPChangedPacket(Pokemon, PokemonTeam, ushort.MinValue, ushort.MinValue, OldHPPercentage, NewHPPercentage); } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/_PkmnStatChangedPacket.cs b/PokemonBattleEngine/Packets/_PkmnStatChangedPacket.cs index fe3a0abb9..eb4213681 100644 --- a/PokemonBattleEngine/Packets/_PkmnStatChangedPacket.cs +++ b/PokemonBattleEngine/Packets/_PkmnStatChangedPacket.cs @@ -3,15 +3,15 @@ using Kermalis.PokemonBattleEngine.Data; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEPkmnStatChangedPacket : INetPacket { public const short Code = 0x10; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public PBEFieldPosition Pokemon { get; } public PBETeam PokemonTeam { get; } @@ -19,7 +19,7 @@ public sealed class PBEPkmnStatChangedPacket : INetPacket public sbyte OldValue { get; } public sbyte NewValue { get; } - public PBEPkmnStatChangedPacket(PBEPokemon pokemon, PBEStat stat, sbyte oldValue, sbyte newValue) + internal PBEPkmnStatChangedPacket(PBEPokemon pokemon, PBEStat stat, sbyte oldValue, sbyte newValue) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); @@ -28,20 +28,17 @@ public PBEPkmnStatChangedPacket(PBEPokemon pokemon, PBEStat stat, sbyte oldValue bytes.Add((byte)(Stat = stat)); bytes.Add((byte)(OldValue = oldValue)); bytes.Add((byte)(NewValue = newValue)); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBEPkmnStatChangedPacket(byte[] buffer, PBEBattle battle) + internal PBEPkmnStatChangedPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { Buffer = buffer; - using (var r = new BinaryReader(new MemoryStream(buffer))) - { - r.ReadInt16(); // Skip Code - Pokemon = (PBEFieldPosition)r.ReadByte(); - PokemonTeam = battle.Teams[r.ReadByte()]; - Stat = (PBEStat)r.ReadByte(); - OldValue = r.ReadSByte(); - NewValue = r.ReadSByte(); - } + Pokemon = (PBEFieldPosition)r.ReadByte(); + PokemonTeam = battle.Teams[r.ReadByte()]; + Stat = (PBEStat)r.ReadByte(); + OldValue = r.ReadSByte(); + NewValue = r.ReadSByte(); } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/_PkmnSwitchInPacket.cs b/PokemonBattleEngine/Packets/_PkmnSwitchInPacket.cs index b7d5bac0d..1c6fc3170 100644 --- a/PokemonBattleEngine/Packets/_PkmnSwitchInPacket.cs +++ b/PokemonBattleEngine/Packets/_PkmnSwitchInPacket.cs @@ -5,14 +5,13 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEPkmnSwitchInPacket : INetPacket { public const short Code = 0x06; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public sealed class PBESwitchInInfo { @@ -29,7 +28,7 @@ public sealed class PBESwitchInInfo public PBEStatus1 Status1 { get; } public PBEFieldPosition FieldPosition { get; } - public PBESwitchInInfo(byte pkmnId, byte disguisedAsId, PBESpecies species, string nickname, byte level, bool shiny, PBEGender gender, ushort hp, ushort maxHP, double hpPercentage, PBEStatus1 status1, PBEFieldPosition fieldPosition) + internal PBESwitchInInfo(byte pkmnId, byte disguisedAsId, PBESpecies species, string nickname, byte level, bool shiny, PBEGender gender, ushort hp, ushort maxHP, double hpPercentage, PBEStatus1 status1, PBEFieldPosition fieldPosition) { PokemonId = pkmnId; DisguisedAsId = disguisedAsId; @@ -60,13 +59,12 @@ internal PBESwitchInInfo(BinaryReader r) FieldPosition = (PBEFieldPosition)r.ReadByte(); } - internal List ToBytes() + internal void ToBytes(List bytes) { - var bytes = new List(); bytes.Add(PokemonId); bytes.Add(DisguisedAsId); bytes.AddRange(BitConverter.GetBytes((uint)Species)); - bytes.AddRange(PBEUtils.StringToBytes(Nickname)); + PBEUtils.StringToBytes(bytes, Nickname); bytes.Add(Level); bytes.Add((byte)(Shiny ? 1 : 0)); bytes.Add((byte)Gender); @@ -75,7 +73,6 @@ internal List ToBytes() bytes.AddRange(BitConverter.GetBytes(HPPercentage)); bytes.Add((byte)Status1); bytes.Add((byte)FieldPosition); - return bytes; } } @@ -83,33 +80,42 @@ internal List ToBytes() public ReadOnlyCollection SwitchIns { get; } public bool Forced { get; } - public PBEPkmnSwitchInPacket(PBETeam team, IEnumerable switchIns, bool forced) + internal PBEPkmnSwitchInPacket(PBETeam team, IList switchIns, bool forced) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); bytes.Add((Team = team).Id); - bytes.Add((byte)(SwitchIns = switchIns.ToList().AsReadOnly()).Count); - foreach (PBESwitchInInfo info in SwitchIns) + bytes.Add((byte)(SwitchIns = new ReadOnlyCollection(switchIns)).Count); + for (int i = 0; i < (sbyte)SwitchIns.Count; i++) { - bytes.AddRange(info.ToBytes()); + SwitchIns[i].ToBytes(bytes); } bytes.Add((byte)((Forced = forced) ? 1 : 0)); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBEPkmnSwitchInPacket(byte[] buffer, PBEBattle battle) + internal PBEPkmnSwitchInPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { - using (var r = new BinaryReader(new MemoryStream(buffer))) + Buffer = buffer; + Team = battle.Teams[r.ReadByte()]; + var switches = new PBESwitchInInfo[r.ReadSByte()]; + for (int i = 0; i < switches.Length; i++) { - r.ReadInt16(); // Skip Code - Team = battle.Teams[r.ReadByte()]; - var switches = new PBESwitchInInfo[r.ReadByte()]; - for (int i = 0; i < switches.Length; i++) - { - switches[i] = new PBESwitchInInfo(r); - } - SwitchIns = Array.AsReadOnly(switches); - Forced = r.ReadBoolean(); + switches[i] = new PBESwitchInInfo(r); } + SwitchIns = new ReadOnlyCollection(switches); + Forced = r.ReadBoolean(); + } + + public PBEPkmnSwitchInPacket MakeHidden() + { + var hiddenSwitchIns = new PBESwitchInInfo[SwitchIns.Count]; + for (int i = 0; i < hiddenSwitchIns.Length; i++) + { + PBESwitchInInfo s = SwitchIns[i]; + hiddenSwitchIns[i] = new PBESwitchInInfo(byte.MaxValue, byte.MaxValue, s.Species, s.Nickname, s.Level, s.Shiny, s.Gender, ushort.MinValue, ushort.MinValue, s.HPPercentage, s.Status1, s.FieldPosition); + } + return new PBEPkmnSwitchInPacket(Team, hiddenSwitchIns, Forced); } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/_PkmnSwitchOutPacket.cs b/PokemonBattleEngine/Packets/_PkmnSwitchOutPacket.cs index 5c886aa42..40a8efdc4 100644 --- a/PokemonBattleEngine/Packets/_PkmnSwitchOutPacket.cs +++ b/PokemonBattleEngine/Packets/_PkmnSwitchOutPacket.cs @@ -3,15 +3,15 @@ using Kermalis.PokemonBattleEngine.Data; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEPkmnSwitchOutPacket : INetPacket { public const short Code = 0x0C; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public byte PokemonId { get; } public byte DisguisedAsPokemonId { get; } @@ -19,7 +19,7 @@ public sealed class PBEPkmnSwitchOutPacket : INetPacket public PBETeam PokemonTeam { get; } public bool Forced { get; } - public PBEPkmnSwitchOutPacket(byte pokemonId, byte disguisedAsPokemonId, PBEFieldPosition pokemonPosition, PBETeam pokemonTeam, bool forced) + internal PBEPkmnSwitchOutPacket(byte pokemonId, byte disguisedAsPokemonId, PBEFieldPosition pokemonPosition, PBETeam pokemonTeam, bool forced) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); @@ -28,20 +28,22 @@ public PBEPkmnSwitchOutPacket(byte pokemonId, byte disguisedAsPokemonId, PBEFiel bytes.Add((byte)(PokemonPosition = pokemonPosition)); bytes.Add((PokemonTeam = pokemonTeam).Id); bytes.Add((byte)((Forced = forced) ? 1 : 0)); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBEPkmnSwitchOutPacket(byte[] buffer, PBEBattle battle) + internal PBEPkmnSwitchOutPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { Buffer = buffer; - using (var r = new BinaryReader(new MemoryStream(buffer))) - { - r.ReadInt16(); // Skip Code - PokemonId = r.ReadByte(); - DisguisedAsPokemonId = r.ReadByte(); - PokemonPosition = (PBEFieldPosition)r.ReadByte(); - PokemonTeam = battle.Teams[r.ReadByte()]; - Forced = r.ReadBoolean(); - } + PokemonId = r.ReadByte(); + DisguisedAsPokemonId = r.ReadByte(); + PokemonPosition = (PBEFieldPosition)r.ReadByte(); + PokemonTeam = battle.Teams[r.ReadByte()]; + Forced = r.ReadBoolean(); + } + + public PBEPkmnSwitchOutPacket MakeHidden() + { + return new PBEPkmnSwitchOutPacket(byte.MaxValue, byte.MaxValue, PokemonPosition, PokemonTeam, Forced); } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/_PsychUpPacket.cs b/PokemonBattleEngine/Packets/_PsychUpPacket.cs index 3d75cdb7c..1af8e77a1 100644 --- a/PokemonBattleEngine/Packets/_PsychUpPacket.cs +++ b/PokemonBattleEngine/Packets/_PsychUpPacket.cs @@ -3,15 +3,15 @@ using Kermalis.PokemonBattleEngine.Data; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEPsychUpPacket : INetPacket { public const short Code = 0x22; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public PBEFieldPosition User { get; } public PBETeam UserTeam { get; } @@ -25,7 +25,7 @@ public sealed class PBEPsychUpPacket : INetPacket public sbyte AccuracyChange { get; } public sbyte EvasionChange { get; } - public PBEPsychUpPacket(PBEPokemon user, PBEPokemon target) + internal PBEPsychUpPacket(PBEPokemon user, PBEPokemon target) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); @@ -40,26 +40,23 @@ public PBEPsychUpPacket(PBEPokemon user, PBEPokemon target) bytes.Add((byte)(SpeedChange = target.SpeedChange)); bytes.Add((byte)(AccuracyChange = target.AccuracyChange)); bytes.Add((byte)(EvasionChange = target.EvasionChange)); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBEPsychUpPacket(byte[] buffer, PBEBattle battle) + internal PBEPsychUpPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { Buffer = buffer; - using (var r = new BinaryReader(new MemoryStream(buffer))) - { - r.ReadInt16(); // Skip Code - User = (PBEFieldPosition)r.ReadByte(); - UserTeam = battle.Teams[r.ReadByte()]; - Target = (PBEFieldPosition)r.ReadByte(); - TargetTeam = battle.Teams[r.ReadByte()]; - AttackChange = r.ReadSByte(); - DefenseChange = r.ReadSByte(); - SpAttackChange = r.ReadSByte(); - SpDefenseChange = r.ReadSByte(); - SpeedChange = r.ReadSByte(); - AccuracyChange = r.ReadSByte(); - EvasionChange = r.ReadSByte(); - } + User = (PBEFieldPosition)r.ReadByte(); + UserTeam = battle.Teams[r.ReadByte()]; + Target = (PBEFieldPosition)r.ReadByte(); + TargetTeam = battle.Teams[r.ReadByte()]; + AttackChange = r.ReadSByte(); + DefenseChange = r.ReadSByte(); + SpAttackChange = r.ReadSByte(); + SpDefenseChange = r.ReadSByte(); + SpeedChange = r.ReadSByte(); + AccuracyChange = r.ReadSByte(); + EvasionChange = r.ReadSByte(); } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/_SpecialMessagePacket.cs b/PokemonBattleEngine/Packets/_SpecialMessagePacket.cs index b46571809..c3ecb903c 100644 --- a/PokemonBattleEngine/Packets/_SpecialMessagePacket.cs +++ b/PokemonBattleEngine/Packets/_SpecialMessagePacket.cs @@ -5,19 +5,18 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBESpecialMessagePacket : INetPacket { public const short Code = 0x20; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public PBESpecialMessage Message { get; } public ReadOnlyCollection Params { get; } - public PBESpecialMessagePacket(PBESpecialMessage message, params object[] parameters) + internal PBESpecialMessagePacket(PBESpecialMessage message, params object[] parameters) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); @@ -56,43 +55,41 @@ public PBESpecialMessagePacket(PBESpecialMessage message, params object[] parame break; } } - Params = par.AsReadOnly(); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + Params = new ReadOnlyCollection(par); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBESpecialMessagePacket(byte[] buffer, PBEBattle battle) + internal PBESpecialMessagePacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { Buffer = buffer; - using (var r = new BinaryReader(new MemoryStream(buffer))) + Message = (PBESpecialMessage)r.ReadByte(); + switch (Message) { - r.ReadInt16(); // Skip Code - Message = (PBESpecialMessage)r.ReadByte(); - switch (Message) + case PBESpecialMessage.DraggedOut: + case PBESpecialMessage.Endure: + case PBESpecialMessage.HPDrained: + case PBESpecialMessage.Recoil: + case PBESpecialMessage.Struggle: + { + Params = new ReadOnlyCollection(new object[] { (PBEFieldPosition)r.ReadByte(), battle.Teams[r.ReadByte()] }); + break; + } + case PBESpecialMessage.Magnitude: { - case PBESpecialMessage.DraggedOut: - case PBESpecialMessage.Endure: - case PBESpecialMessage.HPDrained: - case PBESpecialMessage.Recoil: - case PBESpecialMessage.Struggle: - { - Params = Array.AsReadOnly(new object[] { (PBEFieldPosition)r.ReadByte(), battle.Teams[r.ReadByte()] }); - break; - } - case PBESpecialMessage.Magnitude: - { - Params = Array.AsReadOnly(new object[] { r.ReadByte() }); - break; - } - case PBESpecialMessage.PainSplit: - { - Params = Array.AsReadOnly(new object[] { (PBEFieldPosition)r.ReadByte(), battle.Teams[r.ReadByte()], (PBEFieldPosition)r.ReadByte(), battle.Teams[r.ReadByte()] }); - break; - } - default: // OneHitKnockout - { - Params = Array.AsReadOnly(Array.Empty()); - break; - } + Params = new ReadOnlyCollection(new object[] { r.ReadByte() }); + break; + } + case PBESpecialMessage.OneHitKnockout: + { + Params = new ReadOnlyCollection(Array.Empty()); + break; + } + case PBESpecialMessage.PainSplit: + { + Params = new ReadOnlyCollection(new object[] { (PBEFieldPosition)r.ReadByte(), battle.Teams[r.ReadByte()], (PBEFieldPosition)r.ReadByte(), battle.Teams[r.ReadByte()] }); + break; } + throw new InvalidDataException(); } } diff --git a/PokemonBattleEngine/Packets/_Status1Packet.cs b/PokemonBattleEngine/Packets/_Status1Packet.cs index e0efb3055..4dafe7a32 100644 --- a/PokemonBattleEngine/Packets/_Status1Packet.cs +++ b/PokemonBattleEngine/Packets/_Status1Packet.cs @@ -3,15 +3,15 @@ using Kermalis.PokemonBattleEngine.Data; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEStatus1Packet : INetPacket { public const short Code = 0x11; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public PBEFieldPosition Pokemon2 { get; } public PBETeam Pokemon2Team { get; } @@ -20,7 +20,7 @@ public sealed class PBEStatus1Packet : INetPacket public PBEStatus1 Status1 { get; } public PBEStatusAction StatusAction { get; } - public PBEStatus1Packet(PBEPokemon status1Receiver, PBEPokemon pokemon2, PBEStatus1 status1, PBEStatusAction statusAction) + internal PBEStatus1Packet(PBEPokemon status1Receiver, PBEPokemon pokemon2, PBEStatus1 status1, PBEStatusAction statusAction) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); @@ -30,21 +30,18 @@ public PBEStatus1Packet(PBEPokemon status1Receiver, PBEPokemon pokemon2, PBEStat bytes.Add((Pokemon2Team = pokemon2.Team).Id); bytes.Add((byte)(Status1 = status1)); bytes.Add((byte)(StatusAction = statusAction)); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBEStatus1Packet(byte[] buffer, PBEBattle battle) + internal PBEStatus1Packet(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { Buffer = buffer; - using (var r = new BinaryReader(new MemoryStream(buffer))) - { - r.ReadInt16(); // Skip Code - Status1Receiver = (PBEFieldPosition)r.ReadByte(); - Status1ReceiverTeam = battle.Teams[r.ReadByte()]; - Pokemon2 = (PBEFieldPosition)r.ReadByte(); - Pokemon2Team = battle.Teams[r.ReadByte()]; - Status1 = (PBEStatus1)r.ReadByte(); - StatusAction = (PBEStatusAction)r.ReadByte(); - } + Status1Receiver = (PBEFieldPosition)r.ReadByte(); + Status1ReceiverTeam = battle.Teams[r.ReadByte()]; + Pokemon2 = (PBEFieldPosition)r.ReadByte(); + Pokemon2Team = battle.Teams[r.ReadByte()]; + Status1 = (PBEStatus1)r.ReadByte(); + StatusAction = (PBEStatusAction)r.ReadByte(); } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/_Status2Packet.cs b/PokemonBattleEngine/Packets/_Status2Packet.cs index ee87c557a..1d438c560 100644 --- a/PokemonBattleEngine/Packets/_Status2Packet.cs +++ b/PokemonBattleEngine/Packets/_Status2Packet.cs @@ -3,15 +3,15 @@ using Kermalis.PokemonBattleEngine.Data; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEStatus2Packet : INetPacket { public const short Code = 0x12; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public PBEFieldPosition Status2Receiver { get; } public PBETeam Status2ReceiverTeam { get; } @@ -20,7 +20,7 @@ public sealed class PBEStatus2Packet : INetPacket public PBEStatus2 Status2 { get; } public PBEStatusAction StatusAction { get; } - public PBEStatus2Packet(PBEPokemon status2Receiver, PBEPokemon pokemon2, PBEStatus2 status2, PBEStatusAction statusAction) + internal PBEStatus2Packet(PBEPokemon status2Receiver, PBEPokemon pokemon2, PBEStatus2 status2, PBEStatusAction statusAction) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); @@ -30,21 +30,18 @@ public PBEStatus2Packet(PBEPokemon status2Receiver, PBEPokemon pokemon2, PBEStat bytes.Add((Pokemon2Team = pokemon2.Team).Id); bytes.AddRange(BitConverter.GetBytes((uint)(Status2 = status2))); bytes.Add((byte)(StatusAction = statusAction)); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBEStatus2Packet(byte[] buffer, PBEBattle battle) + internal PBEStatus2Packet(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { Buffer = buffer; - using (var r = new BinaryReader(new MemoryStream(buffer))) - { - r.ReadInt16(); // Skip Code - Status2Receiver = (PBEFieldPosition)r.ReadByte(); - Status2ReceiverTeam = battle.Teams[r.ReadByte()]; - Pokemon2 = (PBEFieldPosition)r.ReadByte(); - Pokemon2Team = battle.Teams[r.ReadByte()]; - Status2 = (PBEStatus2)r.ReadUInt32(); - StatusAction = (PBEStatusAction)r.ReadByte(); - } + Status2Receiver = (PBEFieldPosition)r.ReadByte(); + Status2ReceiverTeam = battle.Teams[r.ReadByte()]; + Pokemon2 = (PBEFieldPosition)r.ReadByte(); + Pokemon2Team = battle.Teams[r.ReadByte()]; + Status2 = (PBEStatus2)r.ReadUInt32(); + StatusAction = (PBEStatusAction)r.ReadByte(); } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/_TeamStatusPacket.cs b/PokemonBattleEngine/Packets/_TeamStatusPacket.cs index 196780c61..6be36914e 100644 --- a/PokemonBattleEngine/Packets/_TeamStatusPacket.cs +++ b/PokemonBattleEngine/Packets/_TeamStatusPacket.cs @@ -3,22 +3,22 @@ using Kermalis.PokemonBattleEngine.Data; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBETeamStatusPacket : INetPacket { public const short Code = 0x13; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public PBETeam Team { get; } public PBETeamStatus TeamStatus { get; } public PBETeamStatusAction TeamStatusAction { get; } public PBEFieldPosition DamageVictim { get; } // PBEFieldPosition.None means no victim - public PBETeamStatusPacket(PBETeam team, PBETeamStatus teamStatus, PBETeamStatusAction teamStatusAction, PBEPokemon damageVictim) + internal PBETeamStatusPacket(PBETeam team, PBETeamStatus teamStatus, PBETeamStatusAction teamStatusAction, PBEPokemon damageVictim) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); @@ -26,18 +26,16 @@ public PBETeamStatusPacket(PBETeam team, PBETeamStatus teamStatus, PBETeamStatus bytes.Add((byte)(TeamStatus = teamStatus)); bytes.Add((byte)(TeamStatusAction = teamStatusAction)); bytes.Add((byte)(DamageVictim = damageVictim == null ? PBEFieldPosition.None : damageVictim.FieldPosition)); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBETeamStatusPacket(byte[] buffer, PBEBattle battle) + internal PBETeamStatusPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { - using (var r = new BinaryReader(new MemoryStream(buffer))) - { - r.ReadInt16(); // Skip Code - Team = battle.Teams[r.ReadByte()]; - TeamStatus = (PBETeamStatus)r.ReadByte(); - TeamStatusAction = (PBETeamStatusAction)r.ReadByte(); - DamageVictim = (PBEFieldPosition)r.ReadByte(); - } + Buffer = buffer; + Team = battle.Teams[r.ReadByte()]; + TeamStatus = (PBETeamStatus)r.ReadByte(); + TeamStatusAction = (PBETeamStatusAction)r.ReadByte(); + DamageVictim = (PBEFieldPosition)r.ReadByte(); } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/_TransformPacket.cs b/PokemonBattleEngine/Packets/_TransformPacket.cs index e629d6e91..009299dea 100644 --- a/PokemonBattleEngine/Packets/_TransformPacket.cs +++ b/PokemonBattleEngine/Packets/_TransformPacket.cs @@ -5,14 +5,13 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBETransformPacket : INetPacket { public const short Code = 0x18; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public PBEFieldPosition User { get; } public PBETeam UserTeam { get; } @@ -37,7 +36,7 @@ public sealed class PBETransformPacket : INetPacket public double TargetWeight { get; } public ReadOnlyCollection TargetMoves { get; } - public PBETransformPacket(PBEPokemon user, PBEPokemon target) + internal PBETransformPacket(PBEPokemon user, PBEPokemon target) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); @@ -62,47 +61,44 @@ public PBETransformPacket(PBEPokemon user, PBEPokemon target) bytes.Add((byte)(TargetType1 = target.Type1)); bytes.Add((byte)(TargetType2 = target.Type2)); bytes.AddRange(BitConverter.GetBytes(TargetWeight = target.Weight)); - bytes.Add((byte)(TargetMoves = Array.AsReadOnly(target.Moves)).Count); - for (int i = 0; i < TargetMoves.Count; i++) + TargetMoves = target.Moves.ForTransformPacket(); + for (int i = 0; i < (byte)TargetMoves.Count; i++) { bytes.AddRange(BitConverter.GetBytes((ushort)TargetMoves[i])); } - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBETransformPacket(byte[] buffer, PBEBattle battle) + internal PBETransformPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { Buffer = buffer; - using (var r = new BinaryReader(new MemoryStream(buffer))) + User = (PBEFieldPosition)r.ReadByte(); + UserTeam = battle.Teams[r.ReadByte()]; + Target = (PBEFieldPosition)r.ReadByte(); + TargetTeam = battle.Teams[r.ReadByte()]; + 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 = (PBEAbility)r.ReadByte(); + TargetSpecies = (PBESpecies)r.ReadUInt16(); + TargetType1 = (PBEType)r.ReadByte(); + TargetType2 = (PBEType)r.ReadByte(); + TargetWeight = r.ReadDouble(); + var moves = new PBEMove[battle.Settings.NumMoves]; + for (int i = 0; i < moves.Length; i++) { - r.ReadInt16(); // Skip Code - User = (PBEFieldPosition)r.ReadByte(); - UserTeam = battle.Teams[r.ReadByte()]; - Target = (PBEFieldPosition)r.ReadByte(); - TargetTeam = battle.Teams[r.ReadByte()]; - 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 = (PBEAbility)r.ReadByte(); - TargetSpecies = (PBESpecies)r.ReadUInt16(); - TargetType1 = (PBEType)r.ReadByte(); - TargetType2 = (PBEType)r.ReadByte(); - TargetWeight = r.ReadDouble(); - var moves = new PBEMove[r.ReadByte()]; - for (int i = 0; i < moves.Length; i++) - { - moves[i] = (PBEMove)r.ReadUInt16(); - } - TargetMoves = Array.AsReadOnly(moves); + moves[i] = (PBEMove)r.ReadUInt16(); } + TargetMoves = new ReadOnlyCollection(moves); } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/_WeatherPacket.cs b/PokemonBattleEngine/Packets/_WeatherPacket.cs index b16adc4aa..711ccab31 100644 --- a/PokemonBattleEngine/Packets/_WeatherPacket.cs +++ b/PokemonBattleEngine/Packets/_WeatherPacket.cs @@ -3,22 +3,22 @@ using Kermalis.PokemonBattleEngine.Data; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEWeatherPacket : INetPacket { public const short Code = 0x14; - public IEnumerable Buffer { get; } + public ReadOnlyCollection Buffer { get; } public PBEWeather Weather { get; } public PBEWeatherAction WeatherAction { get; } public PBEFieldPosition DamageVictim { get; } // PBEFieldPosition.None means no victim public PBETeam DamageVictimTeam { get; } // null means no victim - public PBEWeatherPacket(PBEWeather weather, PBEWeatherAction weatherAction, PBEPokemon damageVictim) + internal PBEWeatherPacket(PBEWeather weather, PBEWeatherAction weatherAction, PBEPokemon damageVictim) { var bytes = new List(); bytes.AddRange(BitConverter.GetBytes(Code)); @@ -26,20 +26,17 @@ public PBEWeatherPacket(PBEWeather weather, PBEWeatherAction weatherAction, PBEP bytes.Add((byte)(WeatherAction = weatherAction)); bytes.Add((byte)(DamageVictim = damageVictim == null ? PBEFieldPosition.None : damageVictim.FieldPosition)); bytes.Add((DamageVictimTeam = damageVictim?.Team) == null ? byte.MaxValue : damageVictim.Team.Id); - Buffer = BitConverter.GetBytes((short)bytes.Count).Concat(bytes); + bytes.InsertRange(0, BitConverter.GetBytes((short)bytes.Count)); + Buffer = new ReadOnlyCollection(bytes); } - public PBEWeatherPacket(byte[] buffer, PBEBattle battle) + internal PBEWeatherPacket(ReadOnlyCollection buffer, BinaryReader r, PBEBattle battle) { Buffer = buffer; - using (var r = new BinaryReader(new MemoryStream(buffer))) - { - r.ReadInt16(); // Skip Code - Weather = (PBEWeather)r.ReadByte(); - WeatherAction = (PBEWeatherAction)r.ReadByte(); - DamageVictim = (PBEFieldPosition)r.ReadByte(); - byte teamId = r.ReadByte(); - DamageVictimTeam = teamId == byte.MaxValue ? null : battle.Teams[teamId]; - } + Weather = (PBEWeather)r.ReadByte(); + WeatherAction = (PBEWeatherAction)r.ReadByte(); + DamageVictim = (PBEFieldPosition)r.ReadByte(); + byte teamId = r.ReadByte(); + DamageVictimTeam = teamId == byte.MaxValue ? null : battle.Teams[teamId]; } public void Dispose() { } diff --git a/PokemonBattleEngine/Packets/__PacketProcessor.cs b/PokemonBattleEngine/Packets/__PacketProcessor.cs index def36ce07..be9c3c0dd 100644 --- a/PokemonBattleEngine/Packets/__PacketProcessor.cs +++ b/PokemonBattleEngine/Packets/__PacketProcessor.cs @@ -1,63 +1,72 @@ using Ether.Network.Packets; using Kermalis.PokemonBattleEngine.Battle; using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; namespace Kermalis.PokemonBattleEngine.Packets { public sealed class PBEPacketProcessor : IPacketProcessor { - private readonly PBEBattle battle; + private readonly PBEBattle _battle; public PBEPacketProcessor(PBEBattle battle) { - this.battle = battle; + _battle = battle; } /// - public INetPacket CreatePacket(byte[] buffer) + public INetPacket CreatePacket(byte[] bytes) { INetPacket packet; - short code = BitConverter.ToInt16(buffer, 0); - switch (code) + using (var r = new BinaryReader(new MemoryStream(bytes))) { - case PBEResponsePacket.Code: packet = new PBEResponsePacket(buffer, battle); break; - case PBEPlayerJoinedPacket.Code: packet = new PBEPlayerJoinedPacket(buffer, battle); break; - case PBEMatchCancelledPacket.Code: packet = new PBEMatchCancelledPacket(buffer, battle); break; - case PBEPartyRequestPacket.Code: packet = new PBEPartyRequestPacket(buffer, battle); break; - case PBEPartyResponsePacket.Code: packet = new PBEPartyResponsePacket(buffer, battle); break; - case PBESetPartyPacket.Code: packet = new PBESetPartyPacket(buffer, battle); break; - case PBEPkmnSwitchInPacket.Code: packet = new PBEPkmnSwitchInPacket(buffer, battle); break; - case PBEActionsRequestPacket.Code: packet = new PBEActionsRequestPacket(buffer, battle); break; - case PBEActionsResponsePacket.Code: packet = new PBEActionsResponsePacket(buffer, battle); break; - case PBEMoveUsedPacket.Code: packet = new PBEMoveUsedPacket(buffer, battle); break; - case PBEPkmnHPChangedPacket.Code: packet = new PBEPkmnHPChangedPacket(buffer, battle); break; - case PBEMoveEffectivenessPacket.Code: packet = new PBEMoveEffectivenessPacket(buffer, battle); break; - case PBEPkmnSwitchOutPacket.Code: packet = new PBEPkmnSwitchOutPacket(buffer, battle); break; - case PBEMoveMissedPacket.Code: packet = new PBEMoveMissedPacket(buffer, battle); break; - case PBEPkmnFaintedPacket.Code: packet = new PBEPkmnFaintedPacket(buffer, battle); break; - case PBEMoveCritPacket.Code: packet = new PBEMoveCritPacket(buffer, battle); break; - case PBEPkmnStatChangedPacket.Code: packet = new PBEPkmnStatChangedPacket(buffer, battle); break; - case PBEStatus1Packet.Code: packet = new PBEStatus1Packet(buffer, battle); break; - case PBEStatus2Packet.Code: packet = new PBEStatus2Packet(buffer, battle); break; - case PBETeamStatusPacket.Code: packet = new PBETeamStatusPacket(buffer, battle); break; - case PBEWeatherPacket.Code: packet = new PBEWeatherPacket(buffer, battle); break; - case PBEMoveFailedPacket.Code: packet = new PBEMoveFailedPacket(buffer, battle); break; - case PBEItemPacket.Code: packet = new PBEItemPacket(buffer, battle); break; - case PBEMovePPChangedPacket.Code: packet = new PBEMovePPChangedPacket(buffer, battle); break; - case PBETransformPacket.Code: packet = new PBETransformPacket(buffer, battle); break; - case PBEAbilityPacket.Code: packet = new PBEAbilityPacket(buffer, battle); break; - case PBESpecialMessagePacket.Code: packet = new PBESpecialMessagePacket(buffer, battle); break; - case PBEBattleStatusPacket.Code: packet = new PBEBattleStatusPacket(buffer, battle); break; - case PBEPsychUpPacket.Code: packet = new PBEPsychUpPacket(buffer, battle); break; - case PBESwitchInRequestPacket.Code: packet = new PBESwitchInRequestPacket(buffer, battle); break; - case PBESwitchInResponsePacket.Code: packet = new PBESwitchInResponsePacket(buffer, battle); break; - case PBEIllusionPacket.Code: packet = new PBEIllusionPacket(buffer, battle); break; - case PBEWinnerPacket.Code: packet = new PBEWinnerPacket(buffer, battle); break; - case PBETurnBeganPacket.Code: packet = new PBETurnBeganPacket(buffer, battle); break; - case PBEMoveLockPacket.Code: packet = new PBEMoveLockPacket(buffer, battle); break; - case PBEPkmnFormChangedPacket.Code: packet = new PBEPkmnFormChangedPacket(buffer, battle); break; - case PBEAutoCenterPacket.Code: packet = new PBEAutoCenterPacket(buffer, battle); break; - default: throw new ArgumentOutOfRangeException(nameof(code)); + var list = new List(bytes); + list.InsertRange(0, BitConverter.GetBytes((short)bytes.Length)); + var buffer = new ReadOnlyCollection(list); + short code = r.ReadInt16(); + switch (code) + { + case PBEResponsePacket.Code: packet = new PBEResponsePacket(buffer, r, _battle); break; + case PBEPlayerJoinedPacket.Code: packet = new PBEPlayerJoinedPacket(buffer, r, _battle); break; + case PBEMatchCancelledPacket.Code: packet = new PBEMatchCancelledPacket(buffer, r, _battle); break; + case PBEPartyRequestPacket.Code: packet = new PBEPartyRequestPacket(buffer, r, _battle); break; + case PBEPartyResponsePacket.Code: packet = new PBEPartyResponsePacket(buffer, r, _battle); break; + case PBETeamPacket.Code: packet = new PBETeamPacket(buffer, r, _battle); break; + case PBEPkmnSwitchInPacket.Code: packet = new PBEPkmnSwitchInPacket(buffer, r, _battle); break; + case PBEActionsRequestPacket.Code: packet = new PBEActionsRequestPacket(buffer, r, _battle); break; + case PBEActionsResponsePacket.Code: packet = new PBEActionsResponsePacket(buffer, r, _battle); break; + case PBEMoveUsedPacket.Code: packet = new PBEMoveUsedPacket(buffer, r, _battle); break; + case PBEPkmnHPChangedPacket.Code: packet = new PBEPkmnHPChangedPacket(buffer, r, _battle); break; + case PBEMoveEffectivenessPacket.Code: packet = new PBEMoveEffectivenessPacket(buffer, r, _battle); break; + case PBEPkmnSwitchOutPacket.Code: packet = new PBEPkmnSwitchOutPacket(buffer, r, _battle); break; + case PBEMoveMissedPacket.Code: packet = new PBEMoveMissedPacket(buffer, r, _battle); break; + case PBEPkmnFaintedPacket.Code: packet = new PBEPkmnFaintedPacket(buffer, r, _battle); break; + case PBEMoveCritPacket.Code: packet = new PBEMoveCritPacket(buffer, r, _battle); break; + case PBEPkmnStatChangedPacket.Code: packet = new PBEPkmnStatChangedPacket(buffer, r, _battle); break; + case PBEStatus1Packet.Code: packet = new PBEStatus1Packet(buffer, r, _battle); break; + case PBEStatus2Packet.Code: packet = new PBEStatus2Packet(buffer, r, _battle); break; + case PBETeamStatusPacket.Code: packet = new PBETeamStatusPacket(buffer, r, _battle); break; + case PBEWeatherPacket.Code: packet = new PBEWeatherPacket(buffer, r, _battle); break; + case PBEMoveFailedPacket.Code: packet = new PBEMoveFailedPacket(buffer, r, _battle); break; + case PBEItemPacket.Code: packet = new PBEItemPacket(buffer, r, _battle); break; + case PBEMovePPChangedPacket.Code: packet = new PBEMovePPChangedPacket(buffer, r, _battle); break; + case PBETransformPacket.Code: packet = new PBETransformPacket(buffer, r, _battle); break; + case PBEAbilityPacket.Code: packet = new PBEAbilityPacket(buffer, r, _battle); break; + case PBESpecialMessagePacket.Code: packet = new PBESpecialMessagePacket(buffer, r, _battle); break; + case PBEBattleStatusPacket.Code: packet = new PBEBattleStatusPacket(buffer, r, _battle); break; + case PBEPsychUpPacket.Code: packet = new PBEPsychUpPacket(buffer, r, _battle); break; + case PBESwitchInRequestPacket.Code: packet = new PBESwitchInRequestPacket(buffer, r, _battle); break; + case PBESwitchInResponsePacket.Code: packet = new PBESwitchInResponsePacket(buffer, r, _battle); break; + case PBEIllusionPacket.Code: packet = new PBEIllusionPacket(buffer, r, _battle); break; + case PBEWinnerPacket.Code: packet = new PBEWinnerPacket(buffer, r, _battle); break; + case PBETurnBeganPacket.Code: packet = new PBETurnBeganPacket(buffer, r, _battle); break; + case PBEMoveLockPacket.Code: packet = new PBEMoveLockPacket(buffer, r, _battle); break; + case PBEPkmnFormChangedPacket.Code: packet = new PBEPkmnFormChangedPacket(buffer, r, _battle); break; + case PBEAutoCenterPacket.Code: packet = new PBEAutoCenterPacket(buffer, r, _battle); break; + default: throw new ArgumentOutOfRangeException(nameof(code)); + } } return packet; } diff --git a/PokemonBattleEngine/PokemonBattleEngine.csproj b/PokemonBattleEngine/PokemonBattleEngine.csproj index 30f0ffa56..d2b856463 100644 --- a/PokemonBattleEngine/PokemonBattleEngine.csproj +++ b/PokemonBattleEngine/PokemonBattleEngine.csproj @@ -8,6 +8,7 @@ Kermalis https://github.com/Kermalis/PokemonBattleEngine bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml + 1591 @@ -17,9 +18,6 @@ ../Shared Dependencies\Ether.Network.dll - - - Always diff --git a/PokemonBattleEngine/PokemonBattleEngine.db b/PokemonBattleEngine/PokemonBattleEngine.db index e1c758f4f..61dbd430f 100644 Binary files a/PokemonBattleEngine/PokemonBattleEngine.db and b/PokemonBattleEngine/PokemonBattleEngine.db differ diff --git a/PokemonBattleEngine/Utils.cs b/PokemonBattleEngine/Utils.cs index f5ccd11b0..b6e03bb94 100644 --- a/PokemonBattleEngine/Utils.cs +++ b/PokemonBattleEngine/Utils.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Reflection; using System.Security.Cryptography; using System.Text; @@ -28,21 +27,16 @@ private static bool StrCmp(object arg0, object arg1) /// Creates a connection to PokemonBattleEngine.db. This must be called only once; before the database is used. /// The path of the folder containing PokemonBattleEngine.db. /// Thrown when is null. - /// Thrown when contains one or more of the invalid characters defined in . /// Thrown when a database connection has already been created. public static void CreateDatabaseConnection(string databasePath) { - if (databaseConnection != null) - { - throw new InvalidOperationException("Database connection was already created."); - } - else if (databasePath == null) + if (databasePath == null) { throw new ArgumentNullException(nameof(databasePath)); } - else if (databasePath.IndexOfAny(Path.GetInvalidPathChars()) != -1) + else if (databaseConnection != null) { - throw new ArgumentOutOfRangeException(nameof(databasePath)); + throw new InvalidOperationException("Database connection was already created."); } else { @@ -54,14 +48,14 @@ public static void CreateDatabaseConnection(string databasePath) } /// - /// When I used , it would only take 55 consecutive calls to with a collection of s to be able to predict future values. + /// When I used , it would only take 55 consecutive calls to with a collection of s to be able to predict future values. /// You would also be able to predict with more work if you called or . /// I do not see how that would help attackers because the host would be able to modify the game however it desires anyway. /// For these reasons, I decided to have the random functions use the private without the need to pass one in as a parameter. /// Although is slower and does not allow seeds for obvious reasons, I did not use seeds with the implementation, and Pokémon battles are turn based so the speed doesn't hurt much. /// I decided to switch from to because I did not need the better speed or the seeded constructor and because provides better random outputs. /// - private static readonly RNGCryptoServiceProvider rand = new RNGCryptoServiceProvider(); + private static readonly RNGCryptoServiceProvider _rand = new RNGCryptoServiceProvider(); internal static bool RandomBool() { return RandomInt(0, 1) == 1; @@ -106,7 +100,7 @@ internal static int RandomInt(int minValue, int maxValue) byte[] bytes = new byte[sizeof(uint)]; while (scale == uint.MaxValue) // "d" should not be 1.0 { - rand.GetBytes(bytes); + _rand.GetBytes(bytes); scale = BitConverter.ToUInt32(bytes, 0); } double d = scale / (double)uint.MaxValue; @@ -167,19 +161,18 @@ public static PBESpecies RandomSpecies() /// The type of the elements of . /// An to create a string from. /// Thrown when is null. - public static string Andify(this IEnumerable source) + public static string Andify(this IList source) { if (source == null) { throw new ArgumentNullException(nameof(source)); } - T[] array = source.ToArray(); - string str = array[0].ToString(); - for (int i = 1; i < array.Length; i++) + string str = source[0].ToString(); + for (int i = 1; i < source.Count; i++) { - if (i == array.Length - 1) + if (i == source.Count - 1) { - if (array.Length > 2) + if (source.Count > 2) { str += ','; } @@ -189,7 +182,7 @@ public static string Andify(this IEnumerable source) { str += ", "; } - str += array[i].ToString(); + str += source[i].ToString(); } return str; } @@ -266,13 +259,11 @@ internal static string ToSafeFileName(string fileName) return fileName; } - internal static List StringToBytes(string str) + internal static void StringToBytes(List bytes, string str) { - var bytes = new List(); byte[] nameBytes = Encoding.Unicode.GetBytes(str); bytes.Add((byte)nameBytes.Length); bytes.AddRange(nameBytes); - return bytes; } internal static string StringFromBytes(BinaryReader r) { diff --git a/PokemonBattleEngineClient.Android/MainActivity.cs b/PokemonBattleEngineClient.Android/MainActivity.cs index eddd7530c..3154ab8d8 100644 --- a/PokemonBattleEngineClient.Android/MainActivity.cs +++ b/PokemonBattleEngineClient.Android/MainActivity.cs @@ -9,7 +9,7 @@ namespace Kermalis.PokemonBattleEngineClient.Android { [Activity(Label = "Pokémon Battle Engine", Icon = "@drawable/icon", MainLauncher = true, LaunchMode = LaunchMode.SingleInstance)] - public class MainActivity : AvaloniaActivity + public sealed class MainActivity : AvaloniaActivity { protected override void OnCreate(Bundle savedInstanceState) { diff --git a/PokemonBattleEngineClient.Android/PokemonBattleEngineClient.Android.csproj b/PokemonBattleEngineClient.Android/PokemonBattleEngineClient.Android.csproj index 35eda2525..b0ce76b5b 100644 --- a/PokemonBattleEngineClient.Android/PokemonBattleEngineClient.Android.csproj +++ b/PokemonBattleEngineClient.Android/PokemonBattleEngineClient.Android.csproj @@ -30,14 +30,16 @@ True None True - False + false False armeabi-v7a;x86 Xamarin False - False - False + false + false False + NU1605 + false pdbonly @@ -58,6 +60,7 @@ False False False + NU1605 diff --git a/PokemonBattleEngineClient.Android/Properties/AndroidManifest.xml b/PokemonBattleEngineClient.Android/Properties/AndroidManifest.xml index b8a203a0c..ac8825430 100644 --- a/PokemonBattleEngineClient.Android/Properties/AndroidManifest.xml +++ b/PokemonBattleEngineClient.Android/Properties/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/PokemonBattleEngineClient.Desktop/PokemonBattleEngineClient.Desktop.csproj b/PokemonBattleEngineClient.Desktop/PokemonBattleEngineClient.Desktop.csproj index 717e9111e..6b2eeb54e 100644 --- a/PokemonBattleEngineClient.Desktop/PokemonBattleEngineClient.Desktop.csproj +++ b/PokemonBattleEngineClient.Desktop/PokemonBattleEngineClient.Desktop.csproj @@ -7,6 +7,8 @@ Kermalis Kermalis https://github.com/Kermalis/PokemonBattleEngine + + Kermalis.PokemonBattleEngineClient.Desktop.Program diff --git a/PokemonBattleEngineClient.Desktop/Program.cs b/PokemonBattleEngineClient.Desktop/Program.cs index 38d25801d..e6d4582d6 100644 --- a/PokemonBattleEngineClient.Desktop/Program.cs +++ b/PokemonBattleEngineClient.Desktop/Program.cs @@ -14,9 +14,7 @@ private static void Main() Utils.SetWorkingDirectory(string.Empty); BuildAvaloniaApp().Start(); } - /// - /// This method is needed for IDE previewer infrastructure - /// + /// This method is needed for IDE previewer infrastructure. public static AppBuilder BuildAvaloniaApp() { return AppBuilder.Configure() diff --git a/PokemonBattleEngineClient.NetCore/PokemonBattleEngineClient.NetCore.csproj b/PokemonBattleEngineClient.NetCore/PokemonBattleEngineClient.NetCore.csproj index 964147d8f..11bf4b1ba 100644 --- a/PokemonBattleEngineClient.NetCore/PokemonBattleEngineClient.NetCore.csproj +++ b/PokemonBattleEngineClient.NetCore/PokemonBattleEngineClient.NetCore.csproj @@ -8,6 +8,8 @@ Kermalis Kermalis https://github.com/Kermalis/PokemonBattleEngine + + Kermalis.PokemonBattleEngineClient.NetCore.Program diff --git a/PokemonBattleEngineClient.NetCore/Program.cs b/PokemonBattleEngineClient.NetCore/Program.cs index a33608d8e..c725ec6c8 100644 --- a/PokemonBattleEngineClient.NetCore/Program.cs +++ b/PokemonBattleEngineClient.NetCore/Program.cs @@ -14,9 +14,7 @@ private static void Main() Utils.SetWorkingDirectory(string.Empty); BuildAvaloniaApp().Start(); } - /// - /// This method is needed for IDE previewer infrastructure - /// + /// This method is needed for IDE previewer infrastructure. public static AppBuilder BuildAvaloniaApp() { return AppBuilder.Configure() diff --git a/PokemonBattleEngineClient.iOS/AppDelegate.cs b/PokemonBattleEngineClient.iOS/AppDelegate.cs index b391d111d..db664e9a2 100644 --- a/PokemonBattleEngineClient.iOS/AppDelegate.cs +++ b/PokemonBattleEngineClient.iOS/AppDelegate.cs @@ -13,7 +13,7 @@ namespace Kermalis.PokemonBattleEngineClient.iOS // User Interface of the application, as well as listening (and optionally responding) to // application events from iOS. [Register("AppDelegate")] - public partial class AppDelegate : UIApplicationDelegate + public sealed partial class AppDelegate : UIApplicationDelegate { public override UIWindow Window { get; set; } diff --git a/PokemonBattleEngineClient.iOS/Main.cs b/PokemonBattleEngineClient.iOS/Main.cs index 758cc63e3..6fba81a96 100644 --- a/PokemonBattleEngineClient.iOS/Main.cs +++ b/PokemonBattleEngineClient.iOS/Main.cs @@ -2,7 +2,7 @@ namespace Kermalis.PokemonBattleEngineClient.iOS { - public class Application + public sealed class Application { private static void Main(string[] args) { diff --git a/PokemonBattleEngineClient.iOS/PokemonBattleEngineClient.iOS.csproj b/PokemonBattleEngineClient.iOS/PokemonBattleEngineClient.iOS.csproj index b754494ab..3ad5c7e90 100644 --- a/PokemonBattleEngineClient.iOS/PokemonBattleEngineClient.iOS.csproj +++ b/PokemonBattleEngineClient.iOS/PokemonBattleEngineClient.iOS.csproj @@ -62,6 +62,7 @@ Entitlements.plist iPhone Developer true + NU1605 none @@ -73,6 +74,7 @@ ARMv7, ARM64 false iPhone Developer + NU1605 none @@ -86,6 +88,7 @@ True Automatic:AdHoc iPhone Distribution + NU1605 none @@ -98,6 +101,7 @@ Entitlements.plist Automatic:AppStore iPhone Distribution + NU1605 diff --git a/PokemonBattleEngineClient/App.xaml.cs b/PokemonBattleEngineClient/App.xaml.cs index 4dad85148..1f4ea95e4 100644 --- a/PokemonBattleEngineClient/App.xaml.cs +++ b/PokemonBattleEngineClient/App.xaml.cs @@ -3,7 +3,7 @@ namespace Kermalis.PokemonBattleEngineClient { - public class App : Application + public sealed class App : Application { public override void Initialize() { diff --git a/PokemonBattleEngineClient/BattleClient.cs b/PokemonBattleEngineClient/BattleClient.cs index 69141723c..c2771417b 100644 --- a/PokemonBattleEngineClient/BattleClient.cs +++ b/PokemonBattleEngineClient/BattleClient.cs @@ -12,11 +12,10 @@ using System.Linq; using System.Net.Sockets; using System.Threading; -using System.Timers; namespace Kermalis.PokemonBattleEngineClient { - public class BattleClient : NetClient + internal sealed class BattleClient : NetClient { private const int WaitMilliseconds = 2000; public enum ClientMode : byte @@ -26,15 +25,20 @@ public enum ClientMode : byte SinglePlayer } - private readonly PBEPacketProcessor packetProcessor; - public override IPacketProcessor PacketProcessor => packetProcessor; + public override IPacketProcessor PacketProcessor { get; } - public readonly PBEBattle Battle; - public readonly BattleView BattleView; - public readonly ClientMode Mode; - public int BattleId = int.MaxValue; - public bool ShowRawValues0, ShowRawValues1; - private readonly PBETeamShell teamShell; + public PBEBattle Battle { get; } + public BattleView BattleView { get; } + public ClientMode Mode { get; } + public int BattleId { get; private set; } = int.MaxValue; + public PBETeam Team { get; private set; } + public bool ShowRawValues0 { get; private set; } + public bool ShowRawValues1 { get; private set; } + private readonly PBETeamShell _teamShell; + + private int _currentPacket = -1; + private Thread _packetThread; + private readonly object _packetThreadLockObj = new object(); public BattleClient(string host, int port, PBEBattleFormat battleFormat, PBETeamShell teamShell) { @@ -44,18 +48,16 @@ public BattleClient(string host, int port, PBEBattleFormat battleFormat, PBETeam Mode = ClientMode.Online; Battle = new PBEBattle(battleFormat, teamShell.Settings); - packetProcessor = new PBEPacketProcessor(Battle); - this.teamShell = teamShell; - - packetTimer.Elapsed += PacketTimer_Elapsed; - packetTimer.Start(); BattleView = new BattleView(this); + PacketProcessor = new PBEPacketProcessor(Battle); + _teamShell = teamShell; } public BattleClient(PBEBattle battle, ClientMode mode) { Mode = mode; Battle = battle; - packetProcessor = new PBEPacketProcessor(Battle); + BattleView = new BattleView(this); + PacketProcessor = new PBEPacketProcessor(Battle); if (Mode == ClientMode.SinglePlayer) { BattleId = 0; @@ -66,25 +68,111 @@ public BattleClient(PBEBattle battle, ClientMode mode) } else // ClientMode.Replay { - ShowRawValues0 = ShowRawValues1 = true; - packetTimer.Elapsed += PacketTimer_Elapsed; - packetTimer.Start(); + ShowRawValues0 = true; + ShowRawValues1 = true; + StartPacketThread(); } - BattleView = new BattleView(this); } - private int currentPacket = -1; - private readonly System.Timers.Timer packetTimer = new System.Timers.Timer(WaitMilliseconds); + private readonly List _actions = new List(3); + public List StandBy { get; } = new List(3); + public void ActionsLoop(bool begin) + { + if (begin) + { + foreach (PBEPokemon pkmn in Team.Party) + { + pkmn.TurnAction = null; + } + _actions.Clear(); + _actions.AddRange(Team.ActiveBattlers); + StandBy.Clear(); + } + int i = _actions.FindIndex(p => p.TurnAction == null); + if (i == -1) + { + PBETurnAction[] acts = _actions.Select(p => p.TurnAction).ToArray(); + if (Mode == ClientMode.Online) + { + BattleView.AddMessage($"Waiting for {Team.OpposingTeam.TrainerName}...", true, false); + Send(new PBEActionsResponsePacket(acts)); + } + else // ClientMode.SinglePlayer + { + new Thread(() => PBEBattle.SelectActionsIfValid(Team, acts)) { Name = "Battle Thread" }.Start(); + } + } + else + { + BattleView.AddMessage($"What will {_actions[i].Nickname} do?", true, false); + BattleView.Actions.DisplayActions(_actions[i]); + } + } + public List Switches { get; } = new List(3); + private byte switchesRequired; + public List PositionStandBy { get; } = new List(3); + public void SwitchesLoop(bool begin) + { + if (begin) + { + Switches.Clear(); + StandBy.Clear(); + PositionStandBy.Clear(); + } + else + { + switchesRequired--; + } + if (switchesRequired == 0) + { + if (Mode == ClientMode.Online) + { + BattleView.AddMessage($"Waiting for {(Team.OpposingTeam.SwitchInsRequired > 0 ? Team.OpposingTeam.TrainerName : "server")}...", true, false); + Send(new PBESwitchInResponsePacket(Switches)); + } + else // ClientMode.SinglePlayer + { + new Thread(() => PBEBattle.SelectSwitchesIfValid(Team, Switches)) { Name = "Battle Thread" }.Start(); + } + } + else + { + BattleView.AddMessage($"You must send in {switchesRequired} Pokémon.", true, false); + BattleView.Actions.DisplaySwitches(); + } + } + + protected override void OnConnected() + { + Debug.WriteLine("Connected to {0}", Socket.RemoteEndPoint); + BattleView.AddMessage("Waiting for players...", false, true); + } + protected override void OnDisconnected() + { + Debug.WriteLine("Disconnected from server"); + BattleView.AddMessage("Disconnected from server.", false, true); + } + protected override void OnSocketError(SocketError socketError) + { + Debug.WriteLine("Socket Error: {0}", socketError); + } + public override void HandleMessage(INetPacket packet) { Debug.WriteLine($"Message received: \"{packet.GetType().Name}\""); switch (packet) { + case PBEMatchCancelledPacket _: + { + BattleView.AddMessage("Match cancelled!", false, true); + break; + } case PBEPlayerJoinedPacket pjp: { if (pjp.IsMe) { BattleId = pjp.BattleId; + Team = Battle.Teams[BattleId]; ShowRawValues0 = BattleId == 0; ShowRawValues1 = BattleId == 1; } @@ -101,12 +189,15 @@ public override void HandleMessage(INetPacket packet) } case PBEPartyRequestPacket _: { - Send(new PBEPartyResponsePacket(teamShell)); + Send(new PBEPartyResponsePacket(_teamShell)); break; } - case PBESetPartyPacket spp: + case PBEActionsRequestPacket _: + case PBESwitchInRequestPacket _: + case PBEWinnerPacket _: { - // Pokémon are set via internal methods in the packet + Battle.Events.Add(packet); + StartPacketThread(); Send(new PBEResponsePacket()); break; } @@ -118,18 +209,31 @@ public override void HandleMessage(INetPacket packet) } } } - private void PacketTimer_Elapsed(object sender, ElapsedEventArgs e) + private void StartPacketThread() { - bool runImmediately; - do + lock (_packetThreadLockObj) { - runImmediately = false; - if (currentPacket != Battle.Events.Count - 1) + if (_packetThread == null) { - currentPacket++; - runImmediately = ProcessPacket(Battle.Events[currentPacket]); + _packetThread = new Thread(PacketThread) { Name = "Packet Thread" }; + _packetThread.Start(); } - } while (runImmediately); + } + } + private void PacketThread() + { + while (_currentPacket < Battle.Events.Count - 1) + { + _currentPacket++; + if (!ProcessPacket(Battle.Events[_currentPacket])) + { + Thread.Sleep(WaitMilliseconds); + } + } + lock (_packetThreadLockObj) + { + _packetThread = null; + } } private void SinglePlayerBattle_OnNewEvent(PBEBattle battle, INetPacket packet) { @@ -348,7 +452,7 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) default: throw new ArgumentOutOfRangeException(nameof(ap.Ability)); } BattleView.AddMessage(string.Format(message, NameForTrainer(abilityOwner, true), NameForTrainer(pokemon2, true), PBELocalizedString.GetAbilityName(ap.Ability).ToString()), true, true); - break; + return false; } case PBEBattleStatusPacket bsp: { @@ -369,7 +473,7 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) default: throw new ArgumentOutOfRangeException(nameof(bsp.BattleStatus)); } BattleView.AddMessage(message, true, true); - break; + return false; } case PBEIllusionPacket ilp: { @@ -388,7 +492,7 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) pokemon.Weight = pokemon.KnownWeight = ilp.ActualWeight; } BattleView.Field.UpdatePokemon(pokemon); - break; + return false; } case PBEItemPacket ip: { @@ -398,8 +502,11 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) { switch (ip.ItemAction) { + case PBEItemAction.ChangedStatus: + case PBEItemAction.Damage: + case PBEItemAction.RestoredHP: itemHolder.Item = itemHolder.KnownItem = ip.Item; break; case PBEItemAction.Consumed: itemHolder.Item = itemHolder.KnownItem = PBEItem.None; break; - default: itemHolder.Item = itemHolder.KnownItem = ip.Item; break; + default: throw new ArgumentOutOfRangeException(nameof(ip.ItemAction)); } } bool itemHolderCaps = true, @@ -535,12 +642,13 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) default: throw new ArgumentOutOfRangeException(nameof(ip.Item)); } BattleView.AddMessage(string.Format(message, NameForTrainer(itemHolder, itemHolderCaps), NameForTrainer(pokemon2, pokemon2Caps), PBELocalizedString.GetItemName(ip.Item).ToString()), true, true); - break; + return false; } - case PBEMoveCritPacket _: + case PBEMoveCritPacket mcp: { - BattleView.AddMessage("A critical hit!", true, true); - break; + PBEPokemon victim = mcp.VictimTeam.TryGetPokemon(mcp.Victim); + BattleView.AddMessage(string.Format("A critical hit on {0}!", NameForTrainer(victim, false)), true, true); + return false; } case PBEMoveEffectivenessPacket mep: { @@ -549,13 +657,13 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) switch (mep.Effectiveness) { case PBEEffectiveness.Ineffective: message = "It doesn't affect {0}..."; break; - case PBEEffectiveness.NotVeryEffective: message = "It's not very effective..."; break; + case PBEEffectiveness.NotVeryEffective: message = "It's not very effective on {0}..."; break; case PBEEffectiveness.Normal: return true; - case PBEEffectiveness.SuperEffective: message = "It's super effective!"; break; + case PBEEffectiveness.SuperEffective: message = "It's super effective on {0}!"; break; default: throw new ArgumentOutOfRangeException(nameof(mep.Effectiveness)); } BattleView.AddMessage(string.Format(message, NameForTrainer(victim, false)), true, true); - break; + return false; } case PBEMoveFailedPacket mfp: { @@ -577,7 +685,7 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) default: throw new ArgumentOutOfRangeException(nameof(mfp.FailReason)); } BattleView.AddMessage(string.Format(message, NameForTrainer(moveUser, true), NameForTrainer(pokemon2, true)), true, true); - break; + return false; } case PBEMoveLockPacket mlp: { @@ -607,26 +715,30 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) PBEPokemon moveUser = mmp.MoveUserTeam.TryGetPokemon(mmp.MoveUser), pokemon2 = mmp.Pokemon2Team.TryGetPokemon(mmp.Pokemon2); BattleView.AddMessage(string.Format("{0}'s attack missed {1}!", NameForTrainer(moveUser, true), NameForTrainer(pokemon2, false)), true, true); - break; + return false; } case PBEMovePPChangedPacket mpcp: { if (Mode != ClientMode.SinglePlayer) { PBEPokemon moveUser = mpcp.MoveUserTeam.TryGetPokemon(mpcp.MoveUser); - moveUser.PP[Array.IndexOf(moveUser.Moves, mpcp.Move)] = mpcp.NewValue; + if (Mode != ClientMode.Online || mpcp.MoveUserTeam.Id == BattleId) + { + moveUser.Moves[mpcp.Move].PP -= mpcp.AmountReduced; + } + moveUser.UpdateKnownPP(mpcp.Move, mpcp.AmountReduced); } return true; } case PBEMoveUsedPacket mup: { PBEPokemon moveUser = mup.MoveUserTeam.TryGetPokemon(mup.MoveUser); - if (Mode == ClientMode.Online && mup.Reveal) + if (Mode != ClientMode.SinglePlayer && mup.Reveal) { - moveUser.KnownMoves[Array.IndexOf(moveUser.KnownMoves, PBEMove.MAX)] = mup.Move; + moveUser.KnownMoves[PBEMove.MAX].Move = mup.Move; } BattleView.AddMessage(string.Format("{0} used {1}!", NameForTrainer(moveUser, true), PBELocalizedString.GetMoveName(mup.Move).ToString()), true, true); - break; + return false; } case PBEPkmnFaintedPacket pfap: { @@ -637,12 +749,12 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) pokemon.FieldPosition = PBEFieldPosition.None; if (Mode == ClientMode.Online && pfap.PokemonTeam.Id != BattleId) { - pokemon.Team.Party.Remove(pokemon); + pokemon.Team.Remove(pokemon); } } BattleView.Field.UpdatePokemon(pokemon, pfap.PokemonPosition); BattleView.AddMessage(string.Format("{0} fainted!", NameForTrainer(pokemon, true)), true, true); - break; + return false; } case PBEPkmnFormChangedPacket pfcp: { @@ -667,7 +779,7 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) } BattleView.Field.UpdatePokemon(pokemon); BattleView.AddMessage(string.Format("{0} transformed!", NameForTrainer(pokemon, true)), true, true); - break; + return false; } case PBEPkmnHPChangedPacket phcp: { @@ -690,7 +802,7 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) { BattleView.AddMessage(string.Format("{0} {1} {2:P2} of its HP!", NameForTrainer(pokemon, true), percentageChange <= 0 ? "lost" : "restored", absPercentageChange), true, true); } - break; + return false; } case PBEPkmnStatChangedPacket pscp: { @@ -744,7 +856,7 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) } } BattleView.AddMessage(string.Format("{0}'s {1} {2}!", NameForTrainer(pokemon, true), statName, message), true, true); - break; + return false; } case PBEPkmnSwitchInPacket psip: { @@ -783,9 +895,9 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) } if (!psip.Forced) { - BattleView.AddMessage(string.Format("{1} sent out {0}!", PBEUtils.Andify(psip.SwitchIns.Select(s => s.Nickname)), psip.Team.TrainerName), true, true); + BattleView.AddMessage(string.Format("{1} sent out {0}!", PBEUtils.Andify(psip.SwitchIns.Select(s => s.Nickname).ToArray()), psip.Team.TrainerName), true, true); } - break; + return false; } case PBEPkmnSwitchOutPacket psop: { @@ -798,7 +910,7 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) if (Mode == ClientMode.Online && psop.PokemonTeam.Id != BattleId) { pokemon.FieldPosition = PBEFieldPosition.None; - pokemon.Team.Party.Remove(pokemon); + pokemon.Team.Remove(pokemon); } else if (Mode != ClientMode.SinglePlayer) { @@ -809,7 +921,7 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) { BattleView.AddMessage(string.Format("{1} withdrew {0}!", disguisedAsPokemon.Nickname, psop.PokemonTeam.TrainerName), true, true); } - break; + return false; } case PBEPsychUpPacket pup: { @@ -826,7 +938,7 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) user.EvasionChange = target.EvasionChange = pup.EvasionChange; } BattleView.AddMessage(string.Format("{0} copied {1}'s stat changes!", NameForTrainer(user, true), NameForTrainer(target, false)), true, true); - break; + return false; } case PBESpecialMessagePacket smp: { @@ -876,7 +988,7 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) default: throw new ArgumentOutOfRangeException(nameof(smp.Message)); } BattleView.AddMessage(message, true, true); - break; + return false; } case PBEStatus1Packet s1p: { @@ -885,9 +997,13 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) { switch (s1p.StatusAction) { - case PBEStatusAction.Added: status1Receiver.Status1 = s1p.Status1; break; + case PBEStatusAction.Activated: + case PBEStatusAction.Added: + case PBEStatusAction.CausedImmobility: + case PBEStatusAction.Damage: status1Receiver.Status1 = s1p.Status1; break; case PBEStatusAction.Cured: case PBEStatusAction.Ended: status1Receiver.Status1 = PBEStatus1.None; break; + default: throw new ArgumentOutOfRangeException(nameof(s1p.StatusAction)); } } BattleView.Field.UpdatePokemon(status1Receiver); @@ -965,7 +1081,7 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) default: throw new ArgumentOutOfRangeException(nameof(s1p.Status1)); } BattleView.AddMessage(string.Format(message, NameForTrainer(status1Receiver, true)), true, true); - break; + return false; } case PBEStatus2Packet s2p: { @@ -975,9 +1091,14 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) { switch (s2p.StatusAction) { - case PBEStatusAction.Added: status2Receiver.Status2 |= s2p.Status2; break; + case PBEStatusAction.Activated: + case PBEStatusAction.Added: + case PBEStatusAction.CausedImmobility: + case PBEStatusAction.Damage: status2Receiver.Status2 |= s2p.Status2; break; case PBEStatusAction.Cured: - case PBEStatusAction.Ended: status2Receiver.Status2 &= ~s2p.Status2; break; + case PBEStatusAction.Ended: + status2Receiver.Status2 &= ~s2p.Status2; break; + throw new ArgumentOutOfRangeException(nameof(s2p.StatusAction)); } } string message; @@ -1164,7 +1285,7 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) default: throw new ArgumentOutOfRangeException(nameof(s2p.Status2)); } BattleView.AddMessage(string.Format(message, NameForTrainer(status2Receiver, status2ReceiverCaps), NameForTrainer(pokemon2, pokemon2Caps)), true, true); - break; + return false; } case PBETeamStatusPacket tsp: { @@ -1172,9 +1293,11 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) { switch (tsp.TeamStatusAction) { - case PBETeamStatusAction.Added: tsp.Team.TeamStatus |= tsp.TeamStatus; break; + case PBETeamStatusAction.Added: + case PBETeamStatusAction.Damage: tsp.Team.TeamStatus |= tsp.TeamStatus; break; case PBETeamStatusAction.Cleared: case PBETeamStatusAction.Ended: tsp.Team.TeamStatus &= ~tsp.TeamStatus; break; + default: throw new ArgumentOutOfRangeException(nameof(tsp.TeamStatusAction)); } } PBEPokemon damageVictim = tsp.Team.TryGetPokemon(tsp.DamageVictim); @@ -1298,7 +1421,7 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) BattleId >= 2 ? $"{tsp.Team.TrainerName}'s" : tsp.Team.Id == BattleId ? "your" : "your foe's", NameForTrainer(damageVictim, damageVictimCaps) ), true, true); - break; + return false; } case PBETransformPacket tp: { @@ -1319,7 +1442,10 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) target.AccuracyChange = tp.TargetAccuracyChange; target.EvasionChange = tp.TargetEvasionChange; target.Ability = target.KnownAbility = tp.TargetAbility; - target.Moves = tp.TargetMoves.ToArray(); + for (int i = 0; i < Battle.Settings.NumMoves; i++) + { + target.Moves[i].Move = tp.TargetMoves[i]; + } target.Species = target.KnownSpecies = tp.TargetSpecies; target.Type1 = target.KnownType1 = tp.TargetType1; target.Type2 = target.KnownType2 = tp.TargetType2; @@ -1333,14 +1459,18 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) { switch (wp.WeatherAction) { - case PBEWeatherAction.Added: Battle.Weather = wp.Weather; break; + case PBEWeatherAction.Added: + case PBEWeatherAction.CausedDamage: Battle.Weather = wp.Weather; break; case PBEWeatherAction.Ended: Battle.Weather = PBEWeather.None; break; + default: throw new ArgumentOutOfRangeException(nameof(wp.WeatherAction)); } } switch (wp.WeatherAction) { case PBEWeatherAction.Added: case PBEWeatherAction.Ended: BattleView.Field.UpdateWeather(); break; + case PBEWeatherAction.CausedDamage: break; + default: throw new ArgumentOutOfRangeException(nameof(wp.WeatherAction)); } PBEPokemon damageVictim = wp.DamageVictimTeam?.TryGetPokemon(wp.DamageVictim); string message; @@ -1391,7 +1521,7 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) default: throw new ArgumentOutOfRangeException(nameof(wp.Weather)); } BattleView.AddMessage(string.Format(message, NameForTrainer(damageVictim, true)), true, true); - break; + return false; } case PBEActionsRequestPacket arp: { @@ -1417,7 +1547,7 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) } else { - new Thread(() => PBEBattle.SelectActionsIfValid(arp.Team, PBEAIManager.CreateActions(arp.Team))) { Name = "Battle Thread" }.Start(); + new Thread(() => PBEBattle.SelectActionsIfValid(arp.Team, PBEAI.CreateActions(arp.Team))) { Name = "Battle Thread" }.Start(); } break; } @@ -1442,7 +1572,7 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) BattleView.Field.UpdatePokemon(pokemon1, acp.Pokemon1Position); BattleView.Field.UpdatePokemon(pokemon2, acp.Pokemon2Position); BattleView.AddMessage("The battlers shifted to the center!", true, true); - break; + return false; } case PBESwitchInRequestPacket sirp: { @@ -1460,9 +1590,9 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) { BattleView.AddMessage("Waiting for players...", true, false); } - else if (Battle.Teams[BattleId].SwitchInsRequired == 0) // Don't display this message if we're in switchesloop because it'd overwrite the messages we need to see. + else if (Team.SwitchInsRequired == 0) // Don't display this message if we're in switchesloop because it'd overwrite the messages we need to see. { - BattleView.AddMessage($"Waiting for {Battle.Teams[BattleId == 0 ? 1 : 0].TrainerName}...", true, false); + BattleView.AddMessage($"Waiting for {Team.OpposingTeam.TrainerName}...", true, false); } break; } @@ -1475,13 +1605,17 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) } else { - new Thread(() => PBEBattle.SelectSwitchesIfValid(sirp.Team, PBEAIManager.CreateSwitches(sirp.Team))) { Name = "Battle Thread" }.Start(); + new Thread(() => PBEBattle.SelectSwitchesIfValid(sirp.Team, PBEAI.CreateSwitches(sirp.Team))) { Name = "Battle Thread" }.Start(); } } break; } return true; } + case PBETeamPacket _: + { + return true; + } case PBETurnBeganPacket tbp: { BattleView.AddMessage($"Turn {Battle.TurnNumber = tbp.TurnNumber}", false, true); @@ -1493,93 +1627,11 @@ string NameForTrainer(PBEPokemon pkmn, bool firstLetterCapitalized) { Battle.Winner = win.WinningTeam; } - BattleView.AddMessage(string.Format("{0} defeated {1}!", win.WinningTeam.TrainerName, (win.WinningTeam == Battle.Teams[0] ? Battle.Teams[1] : Battle.Teams[0]).TrainerName), true, true); - break; - } - } - return false; - } - - private readonly List actions = new List(3); - public List StandBy { get; } = new List(3); - public void ActionsLoop(bool begin) - { - if (begin) - { - foreach (PBEPokemon pkmn in Battle.Teams[BattleId].Party) - { - pkmn.SelectedAction.Decision = PBEDecision.None; - } - actions.Clear(); - actions.AddRange(Battle.Teams[BattleId].ActiveBattlers); - StandBy.Clear(); - } - int i = actions.FindIndex(p => p.SelectedAction.Decision == PBEDecision.None); - if (i == -1) - { - if (Mode == ClientMode.Online) - { - BattleView.AddMessage($"Waiting for {Battle.Teams[BattleId == 0 ? 1 : 0].TrainerName}...", true, false); - Send(new PBEActionsResponsePacket(actions.Select(p => p.SelectedAction))); - } - else // ClientMode.SinglePlayer - { - new Thread(() => PBEBattle.SelectActionsIfValid(Battle.Teams[BattleId], actions.Select(p => p.SelectedAction))) { Name = "Battle Thread" }.Start(); - } - } - else - { - BattleView.AddMessage($"What will {actions[i].Nickname} do?", true, false); - BattleView.Actions.DisplayActions(actions[i]); - } - } - public List<(byte PokemonId, PBEFieldPosition Position)> Switches { get; } = new List<(byte PokemonId, PBEFieldPosition Position)>(3); - private byte switchesRequired; - public List PositionStandBy { get; } = new List(3); - public void SwitchesLoop(bool begin) - { - if (begin) - { - Switches.Clear(); - StandBy.Clear(); - PositionStandBy.Clear(); - } - else - { - switchesRequired--; - } - if (switchesRequired == 0) - { - if (Mode == ClientMode.Online) - { - BattleView.AddMessage($"Waiting for {(Battle.Teams[BattleId == 0 ? 1 : 0].SwitchInsRequired > 0 ? Battle.Teams[BattleId == 0 ? 1 : 0].TrainerName : "server")}...", true, false); - Send(new PBESwitchInResponsePacket(Switches)); - } - else // ClientMode.SinglePlayer - { - new Thread(() => PBEBattle.SelectSwitchesIfValid(Battle.Teams[BattleId], Switches)) { Name = "Battle Thread" }.Start(); + BattleView.AddMessage(string.Format("{0} defeated {1}!", win.WinningTeam.TrainerName, win.WinningTeam.OpposingTeam.TrainerName), true, true); + return true; } + default: throw new ArgumentOutOfRangeException(nameof(packet)); } - else - { - BattleView.AddMessage($"You must send in {switchesRequired} Pokémon.", true, false); - BattleView.Actions.DisplaySwitches(); - } - } - - protected override void OnConnected() - { - Debug.WriteLine("Connected to {0}", Socket.RemoteEndPoint); - BattleView.AddMessage("Waiting for players...", false, true); - } - protected override void OnDisconnected() - { - Debug.WriteLine("Disconnected from server"); - BattleView.AddMessage("Disconnected from server.", false, true); - } - protected override void OnSocketError(SocketError socketError) - { - Debug.WriteLine("Socket Error: {0}", socketError); } } } diff --git a/PokemonBattleEngineClient/Infrastructure/ObjectToTextBitmapConverter.cs b/PokemonBattleEngineClient/Infrastructure/ObjectToTextBitmapConverter.cs index af3331b9b..5f730e8ad 100644 --- a/PokemonBattleEngineClient/Infrastructure/ObjectToTextBitmapConverter.cs +++ b/PokemonBattleEngineClient/Infrastructure/ObjectToTextBitmapConverter.cs @@ -5,7 +5,7 @@ namespace Kermalis.PokemonBattleEngineClient.Infrastructure { - public class ObjectToTextBitmapConverter : IValueConverter + public sealed class ObjectToTextBitmapConverter : IValueConverter { public static ObjectToTextBitmapConverter Instance { get; } = new ObjectToTextBitmapConverter(); public object Convert(object value, Type targetType, object parameter, CultureInfo culture) diff --git a/PokemonBattleEngineClient/Infrastructure/SpeciesToMinispriteConverter.cs b/PokemonBattleEngineClient/Infrastructure/SpeciesToMinispriteConverter.cs index d365f8aaf..808503053 100644 --- a/PokemonBattleEngineClient/Infrastructure/SpeciesToMinispriteConverter.cs +++ b/PokemonBattleEngineClient/Infrastructure/SpeciesToMinispriteConverter.cs @@ -5,7 +5,7 @@ namespace Kermalis.PokemonBattleEngineClient.Infrastructure { - public class SpeciesToMinispriteConverter : IValueConverter + public sealed class SpeciesToMinispriteConverter : IValueConverter { public static SpeciesToMinispriteConverter Instance { get; } = new SpeciesToMinispriteConverter(); public object Convert(object value, Type targetType, object parameter, CultureInfo culture) diff --git a/PokemonBattleEngineClient/Infrastructure/Utils.cs b/PokemonBattleEngineClient/Infrastructure/Utils.cs index cf4c0f8a4..a63467817 100644 --- a/PokemonBattleEngineClient/Infrastructure/Utils.cs +++ b/PokemonBattleEngineClient/Infrastructure/Utils.cs @@ -15,35 +15,35 @@ 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; + 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 == null) + if (_renderInterface == null) { - renderInterface = AvaloniaLocator.Current.GetService(); + _renderInterface = AvaloniaLocator.Current.GetService(); } - return renderInterface; + return _renderInterface; } } - private static readonly Dictionary resourceExistsCache = new Dictionary(); + private static readonly Dictionary _resourceExistsCache = new Dictionary(); public static bool DoesResourceExist(string resource) { - if (!resourceExistsCache.TryGetValue(resource, out bool value)) + if (!_resourceExistsCache.TryGetValue(resource, out bool value)) { - value = Array.IndexOf(resources, assemblyPrefix + resource) != -1; - resourceExistsCache.Add(resource, value); + value = Array.IndexOf(_resources, AssemblyPrefix + resource) != -1; + _resourceExistsCache.Add(resource, value); } return value; } public static Stream GetResourceStream(string resource) { - return assembly.GetManifestResourceStream(assemblyPrefix + resource); + return _assembly.GetManifestResourceStream(AssemblyPrefix + resource); } public static string WorkingDirectory; @@ -132,7 +132,12 @@ void AddStatChanges() { sb.AppendLine($"{pkmn.KnownNickname}/{pkmn.KnownSpecies} {(pkmn.Status2.HasFlag(PBEStatus2.Transformed) ? pkmn.GenderSymbol : pkmn.KnownGenderSymbol)} Lv.{pkmn.Level}"); sb.AppendLine($"HP: {pkmn.HPPercentage:P2}"); - sb.AppendLine($"Known types: {PBELocalizedString.GetTypeName(pkmn.KnownType1).ToString()}/{PBELocalizedString.GetTypeName(pkmn.KnownType2).ToString()}"); + sb.Append($"Known types: {PBELocalizedString.GetTypeName(pkmn.KnownType1).ToString()}"); + if (pkmn.KnownType2 != PBEType.None) + { + sb.Append($"/{PBELocalizedString.GetTypeName(pkmn.KnownType2).ToString()}"); + } + sb.AppendLine(); if (pkmn.FieldPosition != PBEFieldPosition.None) { sb.AppendLine($"Position: {pkmn.Team.TrainerName}'s {pkmn.FieldPosition}"); @@ -178,13 +183,34 @@ void AddStatChanges() sb.AppendLine($"Known ability: {PBELocalizedString.GetAbilityName(pkmn.KnownAbility).ToString()}"); } sb.AppendLine($"Known item: {(pkmn.KnownItem == (PBEItem)ushort.MaxValue ? "???" : PBELocalizedString.GetItemName(pkmn.KnownItem).ToString())}"); - sb.Append($"Known moves: {string.Join(", ", pkmn.KnownMoves.Select(m => m == PBEMove.MAX ? "???" : PBELocalizedString.GetMoveName(m).ToString()))}"); + sb.Append("Known moves: "); + for (int i = 0; i < pkmn.Team.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 ? "???" : PBELocalizedString.GetMoveName(move).ToString()); + if (move != PBEMove.None && move != PBEMove.MAX) + { + sb.Append($" ({pp}{(maxPP == 0 ? ")" : $"/{maxPP})")}"); + } + } } else { sb.AppendLine($"{pkmn.Nickname}/{pkmn.Species} {pkmn.GenderSymbol} Lv.{pkmn.Level}"); sb.AppendLine($"HP: {pkmn.HP}/{pkmn.MaxHP} ({pkmn.HPPercentage:P2})"); - sb.AppendLine($"Types: {PBELocalizedString.GetTypeName(pkmn.Type1).ToString()}/{PBELocalizedString.GetTypeName(pkmn.Type2).ToString()}"); + sb.Append($"Types: {PBELocalizedString.GetTypeName(pkmn.Type1).ToString()}"); + if (pkmn.Type2 != PBEType.None) + { + sb.Append($"/{PBELocalizedString.GetTypeName(pkmn.Type2).ToString()}"); + } + sb.AppendLine(); if (pkmn.FieldPosition != PBEFieldPosition.None) { sb.AppendLine($"Position: {pkmn.Team.TrainerName}'s {pkmn.FieldPosition}"); @@ -216,26 +242,36 @@ void AddStatChanges() } sb.AppendLine($"Ability: {PBELocalizedString.GetAbilityName(pkmn.Ability).ToString()}"); sb.AppendLine($"Item: {PBELocalizedString.GetItemName(pkmn.Item).ToString()}"); - if (Array.IndexOf(pkmn.Moves, PBEMove.Frustration) != -1 || Array.IndexOf(pkmn.Moves, PBEMove.Return) != -1) + if (pkmn.Moves.Contains(PBEMove.Frustration) || pkmn.Moves.Contains(PBEMove.Return)) { sb.AppendLine($"Friendship: {pkmn.Friendship} ({pkmn.Friendship / (double)byte.MaxValue:P2})"); } - if (Array.IndexOf(pkmn.Moves, PBEMove.HiddenPower) != -1) + if (pkmn.Moves.Contains(PBEMove.HiddenPower)) { sb.AppendLine($"{PBELocalizedString.GetMoveName(PBEMove.HiddenPower).ToString()}: {PBELocalizedString.GetTypeName(pkmn.IndividualValues.HiddenPowerType).ToString()}:{pkmn.IndividualValues.HiddenPowerBasePower}"); } - string[] moveStrs = new string[pkmn.Moves.Length]; - for (int i = 0; i < moveStrs.Length; i++) + sb.Append("Moves: "); + for (int i = 0; i < pkmn.Team.Battle.Settings.NumMoves; i++) { - moveStrs[i] = $"{PBELocalizedString.GetMoveName(pkmn.Moves[i]).ToString()} {pkmn.PP[i]}/{pkmn.MaxPP[i]}"; + PBEBattleMoveset.PBEBattleMovesetSlot slot = pkmn.Moves[i]; + PBEMove move = slot.Move; + if (i > 0) + { + sb.Append(", "); + } + sb.Append(PBELocalizedString.GetMoveName(slot.Move).ToString()); + if (move != PBEMove.None) + { + sb.Append($" ({slot.PP}/{slot.MaxPP})"); + } } - sb.AppendLine($"Moves: {string.Join(", ", moveStrs)}"); + sb.AppendLine(); sb.Append($"Usable moves: {string.Join(", ", pkmn.GetUsableMoves().Select(m => PBELocalizedString.GetMoveName(m).ToString()))}"); } return sb.ToString(); } - private static readonly Random rand = new Random(); + private static readonly Random _rand = new Random(); public static T RandomElement(this T[] source) { int count = source.Length; @@ -243,7 +279,7 @@ public static T RandomElement(this T[] source) { throw new ArgumentOutOfRangeException(nameof(source), $"\"{nameof(source)}\" must have at least one element."); } - return source[rand.Next(count)]; + return source[_rand.Next(count)]; } } } diff --git a/PokemonBattleEngineClient/Infrastructure/WriteableBitmapSurface.cs b/PokemonBattleEngineClient/Infrastructure/WriteableBitmapSurface.cs index 1da73420d..3acc3ea18 100644 --- a/PokemonBattleEngineClient/Infrastructure/WriteableBitmapSurface.cs +++ b/PokemonBattleEngineClient/Infrastructure/WriteableBitmapSurface.cs @@ -4,7 +4,7 @@ namespace Kermalis.PokemonBattleEngineClient.Infrastructure { - internal class WriteableBitmapSurface : IFramebufferPlatformSurface + internal sealed class WriteableBitmapSurface : IFramebufferPlatformSurface { private readonly WriteableBitmap _bitmap; public WriteableBitmapSurface(WriteableBitmap bmp) diff --git a/PokemonBattleEngineClient/MainView.xaml.cs b/PokemonBattleEngineClient/MainView.xaml.cs index 876795762..121b506d1 100644 --- a/PokemonBattleEngineClient/MainView.xaml.cs +++ b/PokemonBattleEngineClient/MainView.xaml.cs @@ -12,7 +12,7 @@ namespace Kermalis.PokemonBattleEngineClient { - public class MainView : UserControl, INotifyPropertyChanged + public sealed class MainView : UserControl, INotifyPropertyChanged { private void OnPropertyChanged(string property) { @@ -20,45 +20,48 @@ private void OnPropertyChanged(string property) } public new event PropertyChangedEventHandler PropertyChanged; - private string connectText = "Connect"; + private string _connectText = "Connect"; public string ConnectText { - get => connectText; - set + get => _connectText; + private set { - connectText = value; - OnPropertyChanged(nameof(ConnectText)); + if (_connectText != value) + { + _connectText = value; + OnPropertyChanged(nameof(ConnectText)); + } } } - private readonly List battles = new List(); + private readonly List _battles = new List(); - private readonly TabControl tabs; - private readonly TeamBuilderView teamBuilder; - private readonly TextBox ip; - private readonly NumericUpDown port; - private readonly Button connect; + private readonly TabControl _tabs; + private readonly TeamBuilderView _teamBuilder; + private readonly TextBox _ip; + private readonly NumericUpDown _port; + private readonly Button _connect; public MainView() { DataContext = this; AvaloniaXamlLoader.Load(this); - tabs = this.FindControl("Tabs"); + _tabs = this.FindControl("Tabs"); //teamBuilder = this.FindControl("TeamBuilder"); // teamBuilder will be null - teamBuilder = TemporaryFix("TeamBuilder"); - ip = this.FindControl("IP"); - port = this.FindControl("Port"); - connect = this.FindControl