From caec6383d45e92ce98dadff12954e903eed9150f Mon Sep 17 00:00:00 2001 From: Greg Edwards Date: Tue, 14 Apr 2015 21:14:34 -0400 Subject: [PATCH] Did some cleanup to the pokemon structure. --- library/Pokedex/Move.cs | 7 ++ library/Pokedex/Species.cs | 3 + library/Structures/Enums.cs | 11 ++ library/Structures/MoveSlot.cs | 28 ++++- library/Structures/Pokemon4.cs | 159 ++++++++----------------- library/Structures/PokemonBase.cs | 36 ++++-- library/Structures/PokemonPartyBase.cs | 114 +++++++++++++++--- 7 files changed, 219 insertions(+), 139 deletions(-) diff --git a/library/Pokedex/Move.cs b/library/Pokedex/Move.cs index 5d2bcf9b..2d65d837 100644 --- a/library/Pokedex/Move.cs +++ b/library/Pokedex/Move.cs @@ -65,5 +65,12 @@ namespace PkmnFoundations.Pokedex { get { return m_type_pair.Value; } } + + public static LazyKeyValuePair CreatePair(Pokedex pokedex) + { + return new LazyKeyValuePair( + k => k == 0 ? null : (pokedex == null ? null : pokedex.Moves(k)), + v => v == null ? 0 : v.ID); + } } } diff --git a/library/Pokedex/Species.cs b/library/Pokedex/Species.cs index 82a77fb9..db9a5d17 100644 --- a/library/Pokedex/Species.cs +++ b/library/Pokedex/Species.cs @@ -86,6 +86,9 @@ namespace PkmnFoundations.Pokedex public static LazyKeyValuePair 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( k => k == 0 ? null : (pokedex == null ? null : pokedex.Species(k)), v => v == null ? 0 : v.NationalDex); diff --git a/library/Structures/Enums.cs b/library/Structures/Enums.cs index 5c4eb6a6..24118ea8 100644 --- a/library/Structures/Enums.cs +++ b/library/Structures/Enums.cs @@ -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 + } } diff --git a/library/Structures/MoveSlot.cs b/library/Structures/MoveSlot.cs index 34448ecd..bff38992 100644 --- a/library/Structures/MoveSlot.cs +++ b/library/Structures/MoveSlot.cs @@ -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 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; } } } } diff --git a/library/Structures/Pokemon4.cs b/library/Structures/Pokemon4.cs index 191885a4..7c5413d3 100644 --- a/library/Structures/Pokemon4.cs +++ b/library/Structures/Pokemon4.cs @@ -35,6 +35,7 @@ namespace PkmnFoundations.Structures private void Initialize() { + UnknownRibbons = new HashSet(); } 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(); - UnknownRibbons = new HashSet(); + Ribbons.Clear(); + UnknownRibbons.Clear(); IDictionary 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 Ribbons { get; private set; } + /// + /// This allows preservation of unknown ribbon flags when saving. + /// public HashSet 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; diff --git a/library/Structures/PokemonBase.cs b/library/Structures/PokemonBase.cs index 3f273b4f..d315965f 100644 --- a/library/Structures/PokemonBase.cs +++ b/library/Structures/PokemonBase.cs @@ -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 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 diff --git a/library/Structures/PokemonPartyBase.cs b/library/Structures/PokemonPartyBase.cs index bf8ddca7..ab72307c 100644 --- a/library/Structures/PokemonPartyBase.cs +++ b/library/Structures/PokemonPartyBase.cs @@ -17,15 +17,59 @@ namespace PkmnFoundations.Structures private void Initialize() { + Ribbons = new HashSet(); + } + + 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. + + /// + /// this field decides whether or not its name gets reverted when it evolves. + /// + public bool HasNickname { get; set; } + + /// + /// aka. obedience flag. A few pokemon, eg. Mew, are disobedient unless this is set. + /// + 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 Ribbons { get; } + public HashSet 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; + } } }