Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor PBESettings #195

Merged
merged 21 commits into from
Sep 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
2ebcc0e
Create default settings constants
Kermalis Jul 31, 2019
06730c3
Use settings constants where possible
Kermalis Jul 31, 2019
d856186
Settings ToBytes, FromBytes, ToString, FromString
Kermalis Aug 1, 2019
9b8e3e0
Store settings in replays
Kermalis Aug 1, 2019
2f1c320
Add null catches in battle constructors
Kermalis Aug 1, 2019
499c011
Fix MaxStatChange check
Kermalis Aug 2, 2019
f2c5f87
PBESettings.Equals()
Kermalis Aug 2, 2019
9285b65
Re-add PBETeamShell, include settings in team json files, move EventP…
Kermalis Aug 4, 2019
c46af9a
Add PBESettings.Equals(code)
Kermalis Sep 5, 2019
5d6dfc2
Do not use settings.MaxLevel in Psywave/Stat/Damage calculation
Kermalis Sep 5, 2019
7bd3530
PBESettings.IsReadOnly
Kermalis Sep 5, 2019
bbcd5d3
Require trainer names when submitting teams to the battle
Kermalis Sep 6, 2019
dc6b615
PBEUtils documentation update
Kermalis Sep 6, 2019
ba7b47a
Some fixes cuz I'm dumb
Kermalis Sep 6, 2019
3836b18
Listen for settings changes in PBEPokemonShell and PBETeamShell, list…
Kermalis Sep 6, 2019
47b470e
Listen for settings changes in PBEMovesetBuilder
Kermalis Sep 7, 2019
954b9e7
Update Settings.cs
Kermalis Sep 7, 2019
9162806
Load winner from battle replay
Kermalis Sep 7, 2019
99376ce
Listen for settings changes in PBEIndividualValueCollection
Kermalis Sep 7, 2019
54a936d
Use a copy of the default settings in TeamBuilderView so we can edit
Kermalis Sep 7, 2019
8249ce4
Listen for settings changes in PBEEffortValueCollection
Kermalis Sep 7, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 65 additions & 42 deletions PokemonBattleEngine/Battle/Battle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ public sealed partial class PBEBattle
public event BattleStateChangedEvent OnStateChanged;
public PBEBattleState BattleState { get; private set; }
public ushort TurnNumber { get; set; }
/// <summary>
/// The winner of the battle. Null if the battle is ongoing or the battle resulted in a draw.
/// </summary>
/// <summary>The winner of the battle. null if <see cref="BattleState"/> is not <see cref="PBEBattleState.Ended"/> or the battle resulted in a draw.</summary>
public PBETeam Winner { get; set; }

public PBEBattleFormat BattleFormat { get; }
Expand All @@ -33,61 +31,78 @@ public sealed partial class PBEBattle

public List<INetPacket> Events { get; } = new List<INetPacket>();

/// <summary>
/// Gets a specific Pokémon participating in this battle by its ID.
/// </summary>
/// <param name="pkmnId">The ID of the Pokémon you want to get.</param>
/// <returns>null if no Pokémon was found with <paramref name="pkmnId"/>; otherwise the <see cref="PBEPokemon"/>.</returns>
/// <summary>Gets a specific <see cref="PBEPokemon"/> participating in this battle by its ID.</summary>
/// <param name="pkmnId">The ID of the <see cref="PBEPokemon"/>.</param>
public PBEPokemon TryGetPokemon(byte pkmnId)
{
return Teams.SelectMany(t => t.Party).SingleOrDefault(p => p.Id == pkmnId);
}

private byte pkmnIdCounter;
public PBEBattle(PBEBattleFormat battleFormat, PBESettings settings, IEnumerable<PBEPokemonShell> team0Party, IEnumerable<PBEPokemonShell> team1Party)
/// <summary>Creates a new <see cref="PBEBattle"/> object with the specified <see cref="PBEBattleFormat"/> and teams. Each team must have equal settings. The battle's settings are set to a copy of the teams' settings. <see cref="BattleState"/> will be <see cref="PBEBattleState.ReadyToBegin"/>.</summary>
/// <param name="battleFormat">The <see cref="PBEBattleFormat"/> of the battle.</param>
/// <param name="team0Shell">The <see cref="PBETeamShell"/> object to use to create <see cref="Teams"/>[0].</param>
/// <param name="team0TrainerName">The name of the trainer(s) on <see cref="Teams"/>[0].</param>
/// <param name="team1Shell">The <see cref="PBETeamShell"/> object to use to create <see cref="Teams"/>[1].</param>
/// <param name="team1TrainerName">The name of the trainer(s) on <see cref="Teams"/>[1].</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="team0Shell"/> or <paramref name="team1Shell"/> are null.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="team0Shell"/> and <paramref name="team1Shell"/> have unequal <see cref="PBETeamShell.Settings"/> or when <paramref name="team0TrainerName"/> or <paramref name="team1TrainerName"/> are invalid.</exception>
public PBEBattle(PBEBattleFormat battleFormat, PBETeamShell team0Shell, string team0TrainerName, PBETeamShell team1Shell, string team1TrainerName)
{
if (battleFormat >= PBEBattleFormat.MAX)
{
throw new ArgumentOutOfRangeException(nameof(battleFormat));
}
if (team0Party == null)
if (team0Shell == null)
{
throw new ArgumentNullException(nameof(team0Party));
throw new ArgumentNullException(nameof(team0Shell));
}
if (team1Party == null)
if (string.IsNullOrWhiteSpace(team0TrainerName))
{
throw new ArgumentNullException(nameof(team1Party));
throw new ArgumentOutOfRangeException(nameof(team0TrainerName));
}
if (team0Party.Count() == 0 || team0Party.Count() > settings.MaxPartySize)
if (team1Shell == null)
{
throw new ArgumentOutOfRangeException(nameof(team0Party));
throw new ArgumentNullException(nameof(team1Shell));
}
if (team1Party.Count() == 0 || team1Party.Count() > settings.MaxPartySize)
if (string.IsNullOrWhiteSpace(team1TrainerName))
{
throw new ArgumentOutOfRangeException(nameof(team1Party));
throw new ArgumentOutOfRangeException(nameof(team1TrainerName));
}
if (!team0Shell.Settings.Equals(team1Shell.Settings))
{
throw new ArgumentOutOfRangeException(nameof(team0Shell.Settings), "Team settings must be equal to each other.");
}
BattleFormat = battleFormat;
Settings = settings;
Teams[0] = new PBETeam(this, 0, team0Party, ref pkmnIdCounter);
Teams[1] = new PBETeam(this, 1, team1Party, ref pkmnIdCounter);
Settings = new PBESettings(team0Shell.Settings);
Settings.MakeReadOnly();
Teams[0] = new PBETeam(this, 0, team0Shell, team0TrainerName, ref pkmnIdCounter);
Teams[1] = new PBETeam(this, 1, team1Shell, team1TrainerName, ref pkmnIdCounter);
CheckForReadiness();
}
/// <summary>Creates a new <see cref="PBEBattle"/> object with the specified <see cref="PBEBattleFormat"/> and a copy of the specified <see cref="PBESettings"/>. <see cref="BattleState"/> will be <see cref="PBEBattleState.WaitingForPlayers"/>.</summary>
/// <param name="battleFormat">The <see cref="PBEBattleFormat"/> of the battle.</param>
/// <param name="settings">The <see cref="PBESettings"/> to copy for the battle to use.</param>
public PBEBattle(PBEBattleFormat battleFormat, PBESettings settings)
{
if (battleFormat >= PBEBattleFormat.MAX)
{
throw new ArgumentOutOfRangeException(nameof(battleFormat));
}
if (settings == null)
{
throw new ArgumentNullException(nameof(settings));
}
BattleFormat = battleFormat;
Settings = settings;
Settings = new PBESettings(settings);
Settings.MakeReadOnly();

Teams[0] = new PBETeam(this, 0);
Teams[1] = new PBETeam(this, 1);

BattleState = PBEBattleState.WaitingForPlayers;
OnStateChanged?.Invoke(this);
}
// Sets BattleState to PBEBattleState.ReadyToBegin
private void CheckForReadiness()
{
if (Array.TrueForAll(Teams, t => t.NumPkmnAlive > 0))
Expand Down Expand Up @@ -162,33 +177,44 @@ private void CheckForReadiness()
OnStateChanged?.Invoke(this);
}
}
/// <summary>
/// Sets a specific team's party. <see cref="BattleState"/> will change to <see cref="PBEBattleState.ReadyToBegin"/> if all teams have parties.
/// </summary>
/// <summary>Sets a specific team's party. <see cref="BattleState"/> will change to <see cref="PBEBattleState.ReadyToBegin"/> if all teams have parties.</summary>
/// <param name="team">The team which will have its party set.</param>
/// <param name="party">The Pokémon party <paramref name="team"/> will use.</param>
/// <exception cref="InvalidOperationException">Thrown when <see cref="BattleState"/> is not <see cref="PBEBattleState.WaitingForPlayers"/>.</exception>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="party"/> is null.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="party"/>'s size is invalid.</exception>
public static void CreateTeamParty(PBETeam team, IEnumerable<PBEPokemonShell> party)
/// <param name="teamShell">The information <paramref name="team"/> will use to create its party.</param>
/// <param name="teamTrainerName">The name of the trainer(s) on <paramref name="team"/>.</param>
/// <exception cref="InvalidOperationException">Thrown when <see cref="BattleState"/> is not <see cref="PBEBattleState.WaitingForPlayers"/> or <paramref name="team"/> already has its party set.</exception>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="team"/> or <paramref name="teamShell"/> is null.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="teamShell"/>'s settings are unequal to <paramref name="team"/>'s battle's settings or when <paramref name="teamTrainerName"/> is invalid.</exception>
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 (party == null)
if (team.Party.Count > 0)
{
throw new InvalidOperationException("This team already has its party set.");
}
if (team == null)
{
throw new ArgumentNullException(nameof(team));
}
if (teamShell == null)
{
throw new ArgumentNullException(nameof(teamShell));
}
if (!teamShell.Settings.Equals(team.Battle.Settings))
{
throw new ArgumentNullException(nameof(party));
throw new ArgumentOutOfRangeException(nameof(teamShell), $"\"{nameof(teamShell)}\"'s settings must be equal to the battle's settings.");
}
if (party.Count() == 0 || party.Count() > team.Battle.Settings.MaxPartySize)
if (string.IsNullOrEmpty(teamTrainerName))
{
throw new ArgumentOutOfRangeException(nameof(party));
throw new ArgumentOutOfRangeException(nameof(teamTrainerName));
}
team.CreateParty(party, ref team.Battle.pkmnIdCounter);
team.CreateParty(teamShell, teamTrainerName, ref team.Battle.pkmnIdCounter);
team.Battle.CheckForReadiness();
}
// Starts the battle
// Sets BattleState to PBEBattleState.Processing, then PBEBattleState.WaitingForActions
/// <summary>Begins the battle.</summary>
/// <exception cref="InvalidOperationException">Thrown when <see cref="BattleState"/> is not <see cref="PBEBattleState.ReadyToBegin"/>.</exception>
public void Begin()
{
if (BattleState != PBEBattleState.ReadyToBegin)
Expand All @@ -197,8 +223,8 @@ public void Begin()
}
SwitchesOrActions();
}
// Runs a turn
// Sets BattleState to PBEBattleState.Processing, then PBEBattleState.WaitingForActions/PBEBattleState.WaitingForSwitches/PBEBattleState.Ended
/// <summary>Runs a turn.</summary>
/// <exception cref="InvalidOperationException">Thrown when <see cref="BattleState"/> is not <see cref="PBEBattleState.ReadyToRunTurn"/>.</exception>
public void RunTurn()
{
if (BattleState != PBEBattleState.ReadyToRunTurn)
Expand All @@ -211,7 +237,6 @@ public void RunTurn()
RunActionsInOrder();
TurnEnded();
}
// Sets BattleState to PBEBattleState.Ended
private bool WinCheck()
{
if (Winner != null)
Expand All @@ -223,7 +248,6 @@ private bool WinCheck()
}
return false;
}
// Sets BattleState to PBEBattleState.Processing/PBEBattleState.WaitingForActions/PBEBattleState.WaitingForSwitches/PBEBattleState.Ended
private void SwitchesOrActions()
{
BattleState = PBEBattleState.Processing;
Expand Down Expand Up @@ -525,7 +549,6 @@ private void RunActionsInOrder()
}
}
}
// Sets BattleState to PBEBattleState.WaitingForActions/PBEBattleState.WaitingForSwitches/PBEBattleState.Ended
private void TurnEnded()
{
if (WinCheck())
Expand Down
4 changes: 2 additions & 2 deletions PokemonBattleEngine/Battle/BattleActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ public struct PBEAction
[FieldOffset(4)]
public byte SwitchPokemonId;

internal byte[] ToBytes()
internal List<byte> ToBytes()
{
var bytes = new List<byte>();
bytes.Add(PokemonId);
bytes.Add((byte)Decision);
bytes.AddRange(BitConverter.GetBytes((ushort)FightMove));
bytes.Add(SwitchPokemonId);
return bytes.ToArray();
return bytes;
}
internal static PBEAction FromBytes(BinaryReader r)
{
Expand Down
2 changes: 1 addition & 1 deletion PokemonBattleEngine/Battle/BattleEffects.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3184,7 +3184,7 @@ private void Ef_Psywave(PBEPokemon user, PBEPokemon[] targets, PBEMove move)
failReason = PBEFailReason.None;
ushort DamageFunc(PBEPokemon target)
{
return (ushort)(user.Level * (PBEUtils.RandomInt(0, Settings.MaxLevel) + (Settings.MaxLevel / 2)) / Settings.MaxLevel);
return (ushort)(user.Level * (PBEUtils.RandomInt(0, 100) + 50) / 100);
}
FixedDamageHit(user, targets, move, ref targetSuccess, DamageFunc);
}
Expand Down
80 changes: 38 additions & 42 deletions PokemonBattleEngine/Battle/BattleReplay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,17 @@ public void SaveReplay(string path)

var bytes = new List<byte>();
bytes.AddRange(BitConverter.GetBytes(CurrentReplayVersion));
bytes.Add((byte)BattleFormat);

bytes.AddRange(PBEUtils.StringToBytes(Teams[0].TrainerName));
bytes.Add((byte)Teams[0].Party.Count);
bytes.AddRange(Teams[0].Party.SelectMany(p => p.Shell.ToBytes()));
bytes.AddRange(Settings.ToBytes());
bytes.Add((byte)BattleFormat);

bytes.AddRange(PBEUtils.StringToBytes(Teams[1].TrainerName));
bytes.Add((byte)Teams[1].Party.Count);
bytes.AddRange(Teams[1].Party.SelectMany(p => p.Shell.ToBytes()));
bytes.AddRange(Teams[0].ToBytes());
bytes.AddRange(Teams[1].ToBytes());

foreach (INetPacket packet in Events)
bytes.AddRange(BitConverter.GetBytes(Events.Count));
for (int i = 0; i < Events.Count; i++)
{
bytes.AddRange(packet.Buffer);
bytes.AddRange(Events[i].Buffer);
}

using (var md5 = MD5.Create())
Expand All @@ -52,54 +50,52 @@ public void SaveReplay(string path)

public static PBEBattle LoadReplay(string path)
{
PBESettings settings = PBESettings.DefaultSettings;
byte[] fileBytes = File.ReadAllBytes(path);
using (var s = new MemoryStream(fileBytes))
using (var r = new BinaryReader(s))
{
byte[] hash;
using (var md5 = MD5.Create())
{
byte[] hash = md5.ComputeHash(fileBytes, 0, fileBytes.Length - 16);
for (int i = 0; i < 16; i++)
hash = md5.ComputeHash(fileBytes, 0, fileBytes.Length - 16);
}
for (int i = 0; i < 16; i++)
{
if (hash[i] != fileBytes[fileBytes.Length - 16 + i])
{
if (hash[i] != fileBytes[fileBytes.Length - 16 + i])
{
throw new InvalidDataException();
}
throw new InvalidDataException();
}
}
return new PBEBattle(r);
}
}
private PBEBattle(BinaryReader r)
{
ushort version = r.ReadUInt16();

ushort version = r.ReadUInt16();
Settings = new PBESettings(r);
Settings.MakeReadOnly();
BattleFormat = (PBEBattleFormat)r.ReadByte();

var battle = new PBEBattle((PBEBattleFormat)r.ReadByte(), settings);
Teams[0] = new PBETeam(this, 0);
Teams[0].FromBytes(r);
Teams[1] = new PBETeam(this, 1);
Teams[1].FromBytes(r);

battle.Teams[0].TrainerName = PBEUtils.StringFromBytes(r);
var party = new PBEPokemonShell[r.ReadSByte()];
for (int i = 0; i < party.Length; i++)
{
party[i] = PBEPokemonShell.FromBytes(r, settings);
}
battle.Teams[0].CreateParty(party, ref battle.pkmnIdCounter);

battle.Teams[1].TrainerName = PBEUtils.StringFromBytes(r);
party = new PBEPokemonShell[r.ReadSByte()];
for (int i = 0; i < party.Length; i++)
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)
{
party[i] = PBEPokemonShell.FromBytes(r, settings);
Winner = wp.WinningTeam;
}
battle.Teams[1].CreateParty(party, ref battle.pkmnIdCounter);

var packetProcessor = new PBEPacketProcessor(battle);
INetPacket packet;
do
{
byte[] buffer = r.ReadBytes(r.ReadInt16());
packet = packetProcessor.CreatePacket(buffer);
battle.Events.Add(packet);
} while (!(packet is PBEWinnerPacket));

return battle;
Events.Add(packet);
}

BattleState = PBEBattleState.Ended;
OnStateChanged?.Invoke(this);
}
}
}
Loading