Did some cleanup to the pokemon structure.

This commit is contained in:
Greg Edwards
2015-04-14 21:14:34 -04:00
parent 94f3a46f67
commit caec6383d4
7 changed files with 219 additions and 139 deletions

View File

@@ -65,5 +65,12 @@ namespace PkmnFoundations.Pokedex
{
get { return m_type_pair.Value; }
}
public static LazyKeyValuePair<int, Move> CreatePair(Pokedex pokedex)
{
return new LazyKeyValuePair<int, Move>(
k => k == 0 ? null : (pokedex == null ? null : pokedex.Moves(k)),
v => v == null ? 0 : v.ID);
}
}
}

View File

@@ -86,6 +86,9 @@ namespace PkmnFoundations.Pokedex
public static LazyKeyValuePair<int, Species> CreatePair(Pokedex pokedex)
{
// xxx: we need to return null when there's a KeyNotFoundException
// since we want the Pokemon4/5 ctor to succeed regardless of how
// broken the underlying data is.
return new LazyKeyValuePair<int, Species>(
k => k == 0 ? null : (pokedex == null ? null : pokedex.Species(k)),
v => v == null ? 0 : v.NationalDex);

View File

@@ -219,4 +219,15 @@ namespace PkmnFoundations.Structures
Infected,
Cured
}
[Flags]
public enum ShinyLeaves : byte
{
LeafA = 1,
LeafB = 2,
LeafC = 4,
LeafD = 8,
LeafE = 16,
Crown = 32
}
}

View File

@@ -3,27 +3,43 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using PkmnFoundations.Pokedex;
using PkmnFoundations.Support;
namespace PkmnFoundations.Structures
{
public struct MoveSlot
public class MoveSlot
{
public MoveSlot(Pokedex.Pokedex pokedex, int moveId, byte ppUps, byte remainingPp) : this()
public MoveSlot(Pokedex.Pokedex pokedex, int moveId, byte ppUps, byte remainingPp)
{
m_pokedex = pokedex;
Initialize();
MoveID = moveId;
PPUps = ppUps;
RemainingPP = remainingPp;
}
public void Initialize()
{
m_move_pair = Move.CreatePair(m_pokedex);
}
private Pokedex.Pokedex m_pokedex;
public int MoveID { get; set; }
private LazyKeyValuePair<int, Move> m_move_pair;
public int MoveID
{
get { return m_move_pair.Key; }
set { m_move_pair.Key = value; }
}
public Move Move
{
get { return m_move_pair.Value; }
set { m_move_pair.Value = value; }
}
public byte PPUps { get; set; } // todo: validate range
public byte RemainingPP { get; set; } // todo: validate range (against pokedex data and pp ups)
// todo: should have a MoveID/Move LazyKeyValuePair.
public Move Move { get { return MoveID == 0 ? null : m_pokedex.Moves(MoveID); } }
public int PP { get { return Move.PP * (5 + PPUps) / 5; } }
}
}

View File

@@ -35,6 +35,7 @@ namespace PkmnFoundations.Structures
private void Initialize()
{
UnknownRibbons = new HashSet<int>();
}
protected override void Load(BinaryReader reader)
@@ -116,17 +117,15 @@ namespace PkmnFoundations.Structures
byte forme = block[24];
FatefulEncounter = (forme & 0x01) != 0;
Female = (forme & 0x02) != 0;
Genderless = (forme & 0x04) != 0;
m_female = (forme & 0x02) != 0;
m_genderless = (forme & 0x04) != 0;
FormID = (byte)(forme >> 3);
// todo: parse this in a meaningful way.
ShinyLeaves = block[25];
Unknown1 = BitConverter.ToUInt16(block, 26); // Probably unused?
ShinyLeaves = (ShinyLeaves)block[25];
Unknown1 = BitConverter.ToUInt16(block, 26);
// todo: doing trainer memos the right way is a pretty large task
// involving new database work.
TrainerMemoPlatinum = BitConverter.ToInt32(block, 28);
EggLocationID_Plat = BitConverter.ToUInt16(block, 28);
LocationID_Plat = BitConverter.ToUInt16(block, 30);
}
{
@@ -136,7 +135,7 @@ namespace PkmnFoundations.Structures
Unknown2 = block[22];
Version = (Versions)block[23];
ribbons3 = BitConverter.ToInt32(block, 24);
Unknown3 = BitConverter.ToInt32(block, 28);
Unknown3 = BitConverter.ToUInt32(block, 28);
}
{
@@ -144,13 +143,14 @@ namespace PkmnFoundations.Structures
TrainerNameEncoded = new EncodedString4(block, 0, 16);
// todo: parse dates
// todo: store as DateTime
EggDate = new byte[3];
Array.Copy(block, 16, EggDate, 0, 3);
Date = new byte[3];
Array.Copy(block, 19, Date, 0, 3);
TrainerMemo = BitConverter.ToInt32(block, 22);
EggLocationID = BitConverter.ToUInt16(block, 22);
LocationID = BitConverter.ToUInt16(block, 24);
byte pokerusStatus = block[26];
PokerusDaysLeft = (byte)(pokerusStatus & 0x0f);
PokerusStrain = (byte)(pokerusStatus >> 4);
@@ -158,7 +158,8 @@ namespace PkmnFoundations.Structures
byte encounter_level = block[28];
EncounterLevel = (byte)(encounter_level & 0x7f);
TrainerFemale = (encounter_level & 0x80) != 0;
bool trainerFemale = (encounter_level & 0x80) != 0;
TrainerGender = trainerFemale ? TrainerGenders.Female : TrainerGenders.Male;
EncounterType = block[29];
PokeBallID_Hgss = block[30];
@@ -195,29 +196,19 @@ namespace PkmnFoundations.Structures
Array.Copy(block, 76, Seals, 0, 24);
}
Ribbons1 = ribbons1;
Ribbons2 = ribbons2;
Ribbons3 = ribbons3;
// todo: Ribbons need to be tracked in the database and stored in a meaningful way.
// pkmncf_pokedex_ribbons
// id, value3, value4, value5, value6, Name_JA, etc, Description_JA, etc
// value >> 3 --> array offset.
// value & 0x07 --> bit position.
byte[] ribbons = new byte[12];
Array.Copy(BitConverter.GetBytes(ribbons1), 0, ribbons, 0, 4);
Array.Copy(BitConverter.GetBytes(ribbons2), 0, ribbons, 4, 4);
Array.Copy(BitConverter.GetBytes(ribbons3), 0, ribbons, 8, 4);
Ribbons = new HashSet<Ribbon>();
UnknownRibbons = new HashSet<int>();
Ribbons.Clear();
UnknownRibbons.Clear();
IDictionary<int, Ribbon> allRibbons = m_pokedex.Ribbons(Generations.Generation4);
for (int x = 0; x < 96; x++)
{
if (HasRibbon(ribbons, x))
if (PokemonPartyBase.HasRibbon(ribbons, x))
{
if (allRibbons.ContainsKey(x))
Ribbons.Add(allRibbons[x]);
@@ -227,16 +218,9 @@ namespace PkmnFoundations.Structures
}
}
bool HasRibbon(byte[] ribbons, int value)
{
if (value >= 96 || value < 0) throw new ArgumentOutOfRangeException();
int offset = value >> 3;
byte mask = (byte)(1 << (value & 0x07));
return (ribbons[offset] & mask) != 0;
}
protected override void Save(BinaryWriter writer)
{
// todo: implement save
throw new NotImplementedException();
}
@@ -245,30 +229,27 @@ namespace PkmnFoundations.Structures
get { return Generations.Generation4; }
}
public bool Female { get; set; }
public bool Genderless { get; set; }
private bool m_female;
private bool m_genderless;
public override Genders Gender
{
get
{
if (Genderless) return Genders.None;
if (Female) return Genders.Female;
if (m_genderless) return Genders.None;
if (m_female) return Genders.Female;
return Genders.Male;
}
set
{
Female = value == Genders.Female;
Genderless = value == Genders.None;
m_female = value == Genders.Female;
m_genderless = value == Genders.None;
}
}
// todo: parse shiny leaves data
public byte ShinyLeaves { get; set; }
public ushort Unknown1 { get; set; }
public ShinyLeaves ShinyLeaves { get; set; }
public ushort Unknown1 { get; set; } // appears just after a flags region storing gender, forme, shiny leaves, etc.
// this will require some database work.
public int TrainerMemoPlatinum { get; set; } // trainer memo for PtHGSS
public EncodedString4 NicknameEncoded { get; set; }
public EncodedString4 NicknameEncoded { get; set; } // public so trash bytes can be inspected/manipulated
public override string Nickname
{
get
@@ -282,8 +263,9 @@ namespace PkmnFoundations.Structures
else NicknameEncoded.Text = value;
}
}
public byte Unknown2 { get; set; }
public int Unknown3 { get; set; }
public byte Unknown2 { get; set; } // appears just before Version
public uint Unknown3 { get; set; } // appears just after the last ribbon block
public EncodedString4 TrainerNameEncoded { get; set; }
public override string TrainerName
{
@@ -298,40 +280,21 @@ namespace PkmnFoundations.Structures
else TrainerNameEncoded.Text = value;
}
}
// fixme: use DateTimes for these
// todo: Implement trainer memo structure
public byte[] EggDate { get; set; } // 3 bytes
public byte[] Date { get; set; } // 3 bytes
public int TrainerMemo { get; set; }
public ushort EggLocationID { get; set; }
public ushort LocationID { get; set; }
public ushort EggLocationID_Plat { get; set; }
public ushort LocationID_Plat { get; set; }
public byte EncounterLevel { get; set; }
private byte m_pokerus_days_left;
public override byte PokerusDaysLeft
{
get
{
return m_pokerus_days_left;
}
set
{
if (value > 15) throw new ArgumentOutOfRangeException();
m_pokerus_days_left = value;
}
}
// this is the notorious genIV encounter type flag, not used for much besides validation
public byte EncounterType { get; set; }
public byte Unknown4 { get; set; } // appears just after HGSS pokeball
private byte m_pokerus_strain;
public override byte PokerusStrain
{
get
{
return m_pokerus_strain;
}
set
{
if (value > 15) throw new ArgumentOutOfRangeException();
m_pokerus_strain = value;
}
}
// todo: need list of values and map them onto items
// todo: obtain complete list of Pokeball IDs in HGSS
public byte PokeBallID { get; set; }
public byte PokeBallID_Hgss { get; set; }
@@ -348,11 +311,7 @@ namespace PkmnFoundations.Structures
set
{
if (m_pokeball == value) return;
if (value == null)
{
m_pokeball = null;
return;
}
if (value == null) throw new ArgumentNullException();
if (value.PokeballValue == null) throw new ArgumentException("Item is not a valid Pokeball.");
if ((int)value.PokeballValue > 255 || (int)value.PokeballValue < 0)
@@ -362,12 +321,15 @@ namespace PkmnFoundations.Structures
bool is_hgss = IsHgss();
bool is_hgss_pokeball = IsHgssPokeball(pokeballId);
if (!is_hgss && is_hgss_pokeball) throw new NotSupportedException("Can't place an HGSS Pokeball on a DPPt Pokémon.");
// xxx: we can probably allow an hgss pokeball on a dppt pokemon
// although the structure won't be valid
// todo: fact check how hgss responds in this situation.
// todo: fact check these two values:
// 1. a pokemon in an HGSS ball has a DPPt value of 1
// 1. a pokemon in an HGSS ball has a DPPt value of 4
// 2. any pokemon from DPPt has an HGSS value of 0
// (investigating pokemon in the system should be enough)
PokeBallID = (byte)(is_hgss_pokeball ? 1 : pokeballId);
PokeBallID = (byte)(is_hgss_pokeball ? 4 : pokeballId);
PokeBallID_Hgss = (byte)(is_hgss ? pokeballId : 0);
}
}
@@ -377,33 +339,14 @@ namespace PkmnFoundations.Structures
return (Version == Versions.HeartGold || Version == Versions.SoulSilver);
}
private bool IsHgssPokeball(int pokeballId)
private static bool IsHgssPokeball(int pokeballId)
{
return pokeballId > 16;
}
public byte EncounterLevel { get; set; }
public bool TrainerFemale { get; set; }
public override TrainerGenders TrainerGender
{
get
{
return TrainerFemale ? TrainerGenders.Female : TrainerGenders.Male;
}
set
{
TrainerFemale = value == TrainerGenders.Female;
}
}
// this is the notorious genIV encounter type flag, not used for much besides validation
public byte EncounterType { get; set; }
public byte Unknown4 { get; set; }
public int Ribbons1 { get; set; }
public int Ribbons2 { get; set; }
public int Ribbons3 { get; set; }
public HashSet<Ribbon> Ribbons { get; private set; }
/// <summary>
/// This allows preservation of unknown ribbon flags when saving.
/// </summary>
public HashSet<int> UnknownRibbons { get; private set; }
// party-only stuff. (todo: put in derived class)
@@ -414,7 +357,7 @@ namespace PkmnFoundations.Structures
public byte CapsuleIndex { get; set; }
public ushort HP { get; set; }
//public IntStatValues Stats { get; set; } // cached stats (only refreshes per level on gen4)
public byte[] Unknown7 { get; set; }
public byte[] Unknown7 { get; set; } // 56 bytes
public byte[] Seals { get; set; }
private IntStatValues m_stats;

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
@@ -60,7 +61,6 @@ namespace PkmnFoundations.Structures
{
// changing species will cause the looked up Form to be
// incorrect so null it out.
// xxx: should really observer pattern this.
m_species_pair.Key = value;
m_form_pair.Invalidate();
}
@@ -79,12 +79,19 @@ namespace PkmnFoundations.Structures
public byte FormID
{
get { return m_form_pair.Key; }
set { m_form_pair.Key = value; }
set
{
m_form_pair.Key = value;
}
}
public Form Form
{
get { return m_form_pair.Value; }
set { m_form_pair.Value = value; }
set
{
SpeciesID = value.SpeciesID;
m_form_pair.Value = value;
}
}
public abstract Generations Generation { get; }
@@ -113,16 +120,16 @@ namespace PkmnFoundations.Structures
set { m_ability_pair.Value = value; }
}
public MoveSlot[] Moves { get { return m_moves; } }
public IList<MoveSlot> Moves { get { return m_moves; } }
public uint TrainerID { get; set; }
public uint Personality { get; set; }
public Natures Nature { get { return (Natures)(Personality % 25u); } }
public byte Level { get; set; }
public byte Happiness { get; set; }
public Languages Language { get; set; }
public IvStatValues IVs { get; set; }
public ByteStatValues EVs { get; set; }
public abstract byte Level { get; set; }
public abstract Genders Gender { get; set; }
public abstract String Nickname { get; set; }
@@ -131,12 +138,17 @@ namespace PkmnFoundations.Structures
get
{
// Gen3/4/5 formula. Gen6 must override.
uint step1 = Personality ^ TrainerID;
int step2 = (int)((step1 >> 16) ^ (step1 & 0xffffu));
return step2 >> 3 == 0;
return ShinyTest(Personality, TrainerID, 3);
}
}
public static bool ShinyTest(uint personality, uint trainerId, int trimBits)
{
uint step1 = personality ^ trainerId;
int step2 = (int)((step1 >> 16) ^ (step1 & 0xffffu));
return step2 >> trimBits == 0;
}
public Characteristic Characteristic
{
get
@@ -200,6 +212,7 @@ namespace PkmnFoundations.Structures
return result;
}
#region Experience formulas
public static int ExperienceAt(int level, GrowthRates gr)
{
if (level > 100 || level < 1) throw new ArgumentOutOfRangeException("level");
@@ -222,7 +235,7 @@ namespace PkmnFoundations.Structures
throw new ArgumentException("gr");
}
public static int LevelAt(int experience, GrowthRates gr)
public static byte LevelAt(int experience, GrowthRates gr)
{
if (experience < 0) throw new ArgumentOutOfRangeException("experience");
@@ -231,8 +244,8 @@ namespace PkmnFoundations.Structures
while (1 < 2)
{
if (maxExp <= experience) return maxLevel;
if (minLevel + 1 >= maxLevel) return minLevel;
if (maxExp <= experience) return (byte)maxLevel;
if (minLevel + 1 >= maxLevel) return (byte)minLevel;
int midLevel = (minLevel + maxLevel) >> 1;
int midExp = ExperienceAt(midLevel, gr);
@@ -301,6 +314,7 @@ namespace PkmnFoundations.Structures
else
return cube * (level / 2 + 32) / 50;
}
#endregion
}
public struct Characteristic

View File

@@ -17,15 +17,59 @@ namespace PkmnFoundations.Structures
private void Initialize()
{
Ribbons = new HashSet<Ribbon>();
}
private int m_experience;
public int Experience
{
get
{
return m_experience;
}
set
{
if (m_experience == value) return;
m_experience = value;
m_level = null;
// xxx: if exp changes but level doesn't, this gets invalidated for no reason.
// (really needs observable)
m_stats = null;
}
}
private byte? m_level;
public override byte Level
{
get
{
if (m_level == null)
m_level = LevelAt(m_experience, Species.GrowthRate);
return (byte)m_level;
}
set
{
if (m_level == value) return;
m_experience = ExperienceAt(value, Species.GrowthRate);
m_level = value;
m_stats = null;
}
}
// todo: link experience with level
public int Experience { get; set; }
public Markings Markings { get; set; }
public ConditionValues ContestStats { get; set; }
public bool IsEgg { get; set; }
public bool HasNickname { get; set; } // this field decides whether or not its name gets reverted when it evolves.
public bool FatefulEncounter { get; set; } // aka. obedience flag. A few pokemon, eg. Mew, are disobedient unless this is set.
/// <summary>
/// this field decides whether or not its name gets reverted when it evolves.
/// </summary>
public bool HasNickname { get; set; }
/// <summary>
/// aka. obedience flag. A few pokemon, eg. Mew, are disobedient unless this is set.
/// </summary>
public bool FatefulEncounter { get; set; }
public Versions Version { get; set; }
// todo: create database-driven TrainerMemo class
@@ -33,30 +77,72 @@ namespace PkmnFoundations.Structures
//public abstract TrainerMemo TrainerMemo { get; }
public abstract String TrainerName { get; set; }
public abstract TrainerGenders TrainerGender { get; set; }
// todo: Add pokerus status (None, Infected, Cured) and pokerus days remaining
//public abstract PokerusStatus PokerusStatus { get; set; }
//public abstract int PokerusDaysLeft { get; set; }
public TrainerGenders TrainerGender { get; set; }
public abstract Item Pokeball { get; set; }
// todo: implement ribbons store (see Pokemon4.Load comment)
//public HashSet<Ribbon> Ribbons { get; }
public HashSet<Ribbon> Ribbons { get; private set; }
public abstract IntStatValues Stats { get; }
private IntStatValues m_stats = null;
public virtual IntStatValues Stats
{
get
{
if (m_stats == null)
{
// todo: stat formula
throw new NotImplementedException();
}
return m_stats;
}
}
public abstract byte PokerusDaysLeft { get; set; }
public abstract byte PokerusStrain { get; set; }
private byte m_pokerus_days_left = 0;
public virtual byte PokerusDaysLeft
{
get
{
return m_pokerus_days_left;
}
set
{
if (value > 15) throw new ArgumentOutOfRangeException();
m_pokerus_days_left = value;
}
}
private byte m_pokerus_strain = 0;
public virtual byte PokerusStrain
{
get
{
return m_pokerus_strain;
}
set
{
if (value > 15) throw new ArgumentOutOfRangeException();
m_pokerus_strain = value;
}
}
public Pokerus Pokerus
{
get
{
// note: "strain 0" is invalid and will cause the pokemon to
// lose pokerus entirely once its days left hits 0.
if (PokerusDaysLeft > 0) return Pokerus.Infected;
if (PokerusStrain > 0) return Pokerus.Cured;
return Pokerus.None;
}
}
public static bool HasRibbon(byte[] ribbons, int value)
{
if (value >= 96 || value < 0) throw new ArgumentOutOfRangeException();
int offset = value >> 3;
byte mask = (byte)(1 << (value & 0x07));
return (ribbons[offset] & mask) != 0;
}
}
}