diff --git a/PKHeX.Core/Saves/SAV4.cs b/PKHeX.Core/Saves/SAV4.cs index 032b44308..08f1834bf 100644 --- a/PKHeX.Core/Saves/SAV4.cs +++ b/PKHeX.Core/Saves/SAV4.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; namespace PKHeX.Core { @@ -29,6 +28,7 @@ public abstract class SAV4 : SaveFile protected abstract int StorageSize { get; } protected abstract int GeneralSize { get; } protected abstract int StorageStart { get; } + public abstract Zukan4 Dex { get; } /// public override bool GetFlag(int offset, int bitIndex) => FlagUtil.GetFlag(General, offset, bitIndex); @@ -573,286 +573,9 @@ protected override DataMysteryGift[] MysteryGiftCards } } - protected override void SetDex(PKM pkm) - { - if (pkm.Species == 0) - return; - if (pkm.Species > MaxSpeciesID) - return; - - const int brSize = 0x40; - int bit = pkm.Species - 1; - byte mask = (byte)(1 << (bit & 7)); - int ofs = PokeDex + (bit >> 3) + 0x4; - - /* 4 BitRegions with 0x40*8 bits - * Region 0: Caught (Captured/Owned) flags - * Region 1: Seen flags - * Region 2/3: Toggle for gender display - * 4 possible states: 00, 01, 10, 11 - * 00 - 1Seen: Male Only - * 01 - 2Seen: Male First, Female Second - * 10 - 2Seen: Female First, Male Second - * 11 - 1Seen: Female Only - * (bit1 ^ bit2) + 1 = forms in dex - * bit2 = male/female shown first toggle - */ - - // Set the Species Owned Flag - General[ofs + (brSize * 0)] |= mask; - - // Check if already Seen - if ((General[ofs + (brSize * 1)] & mask) == 0) // Not seen - { - General[ofs + (brSize * 1)] |= mask; // Set seen - int gr = pkm.PersonalInfo.Gender; - switch (gr) - { - case 255: // Genderless - case 0: // Male Only - General[ofs + (brSize * 2)] &= (byte)~mask; // unset - General[ofs + (brSize * 3)] &= (byte)~mask; // unset - break; - case 254: // Female Only - General[ofs + (brSize * 2)] |= mask; - General[ofs + (brSize * 3)] |= mask; - break; - default: // Male or Female - bool m = (General[ofs + (brSize * 2)] & mask) != 0; - bool f = (General[ofs + (brSize * 3)] & mask) != 0; - if (m || f) // bit already set? - break; - int gender = pkm.Gender & 1; - General[ofs + (brSize * 2)] &= (byte)~mask; // unset - General[ofs + (brSize * 3)] &= (byte)~mask; // unset - gender ^= 1; // Set OTHER gender seen bit so it appears second - General[ofs + (brSize * (2 + gender))] |= mask; - break; - } - } - - int FormOffset1 = PokeDex + 4 + (brSize * 4) + 4; - var forms = GetForms(pkm.Species); - if (forms.Length > 0) - { - if (pkm.Species == (int)Species.Unown) // Unown - { - for (int i = 0; i < 0x1C; i++) - { - byte val = General[FormOffset1 + 4 + i]; - if (val == pkm.Form) - break; // already set - if (val != 0xFF) - continue; // keep searching - - General[FormOffset1 + 4 + i] = (byte)pkm.Form; - break; // form now set - } - } - else if (pkm.Species == (int)Species.Pichu && HGSS) // Pichu (HGSS Only) - { - int form = pkm.Form == 1 ? 2 : pkm.Gender; - if (TryInsertForm(forms, form)) - SetForms(pkm.Species, forms); - } - else - { - if (TryInsertForm(forms, pkm.Form)) - SetForms(pkm.Species, forms); - } - } - - int dpl = 1 + Array.IndexOf(DPLangSpecies, pkm.Species); - if (DP && dpl <= 0) - return; - - // Set the Language - int PokeDexLanguageFlags = FormOffset1 + (HGSS ? 0x3C : 0x20); - int lang = GetGen4LanguageBitIndex(pkm.Language); - General[PokeDexLanguageFlags + (DP ? dpl : pkm.Species)] |= (byte)(1 << lang); - } - - private static readonly int[] DPLangSpecies = { 23, 25, 54, 77, 120, 129, 202, 214, 215, 216, 228, 278, 287, 315 }; - - private static int GetGen4LanguageBitIndex(int lang) => --lang switch - { - 3 => 4, // invert ITA/GER - 4 => 3, // invert ITA/GER - > 5 => 0, // Japanese - < 0 => 1, // English - _ => lang, - }; - - public override bool GetCaught(int species) - { - int bit = species - 1; - int bd = bit >> 3; // div8 - int bm = bit & 7; // mod8 - int ofs = PokeDex // Raw Offset - + 0x4; // Magic - return (1 << bm & General[ofs + bd]) != 0; - } - - public override bool GetSeen(int species) - { - const int brSize = 0x40; - - int bit = species - 1; - int bd = bit >> 3; // div8 - int bm = bit & 7; // mod8 - int ofs = PokeDex // Raw Offset - + 0x4; // Magic - - return (1 << bm & General[ofs + bd + (brSize * 1)]) != 0; - } - - public int[] GetForms(int species) - { - const int brSize = 0x40; - if (species == (int)Species.Deoxys) - { - uint val = (uint) (General[PokeDex + 0x4 + (1 * brSize) - 1] | General[PokeDex + 0x4 + (2 * brSize) - 1] << 8); - return GetDexFormValues(val, 4, 4); - } - - int FormOffset1 = PokeDex + 4 + (4 * brSize) + 4; - switch (species) - { - case (int)Species.Shellos: // Shellos - return GetDexFormValues(General[FormOffset1 + 0], 1, 2); - case (int)Species.Gastrodon: // Gastrodon - return GetDexFormValues(General[FormOffset1 + 1], 1, 2); - case (int)Species.Burmy: // Burmy - return GetDexFormValues(General[FormOffset1 + 2], 2, 3); - case (int)Species.Wormadam: // Wormadam - return GetDexFormValues(General[FormOffset1 + 3], 2, 3); - case (int)Species.Unown: // Unown - return General.Slice(FormOffset1 + 4, 0x1C).Select(i => (int)i).ToArray(); - } - if (DP) - return Array.Empty(); - - int PokeDexLanguageFlags = FormOffset1 + (HGSS ? 0x3C : 0x20); - int FormOffset2 = PokeDexLanguageFlags + 0x1F4; - return species switch - { - (int)Species.Rotom => GetDexFormValues(BitConverter.ToUInt32(General, FormOffset2), 3, 6), - (int)Species.Shaymin => GetDexFormValues(General[FormOffset2 + 4], 1, 2), - (int)Species.Giratina => GetDexFormValues(General[FormOffset2 + 5], 1, 2), - (int)Species.Pichu when HGSS => GetDexFormValues(General[FormOffset2 + 6], 2, 3), - _ => Array.Empty() - }; - } - - public void SetForms(int species, int[] forms) - { - const int brSize = 0x40; - switch (species) - { - case (int)Species.Deoxys: // Deoxys - uint newval = SetDexFormValues(forms, 4, 4); - General[PokeDex + 0x4 + (1 * brSize) - 1] = (byte) (newval & 0xFF); - General[PokeDex + 0x4 + (2 * brSize) - 1] = (byte) ((newval >> 8) & 0xFF); - break; - } - - int FormOffset1 = PokeDex + 4 + (4 * brSize) + 4; - switch (species) - { - case (int)Species.Shellos: // Shellos - General[FormOffset1 + 0] = (byte)SetDexFormValues(forms, 1, 2); - return; - case (int)Species.Gastrodon: // Gastrodon - General[FormOffset1 + 1] = (byte)SetDexFormValues(forms, 1, 2); - return; - case (int)Species.Burmy: // Burmy - General[FormOffset1 + 2] = (byte)SetDexFormValues(forms, 2, 3); - return; - case (int)Species.Wormadam: // Wormadam - General[FormOffset1 + 3] = (byte)SetDexFormValues(forms, 2, 3); - return; - case (int)Species.Unown: // Unown - int ofs = FormOffset1 + 4; - int len = forms.Length; - Array.Resize(ref forms, 0x1C); - for (int i = len; i < forms.Length; i++) - forms[i] = 0xFF; - Array.Copy(forms.Select(b => (byte)b).ToArray(), 0, General, ofs, forms.Length); - return; - } - - if (DP) - return; - - int PokeDexLanguageFlags = FormOffset1 + (HGSS ? 0x3C : 0x20); - int FormOffset2 = PokeDexLanguageFlags + 0x1F4; - switch (species) - { - case (int)Species.Rotom: // Rotom - BitConverter.GetBytes(SetDexFormValues(forms, 3, 6)).CopyTo(General, FormOffset2); - return; - case (int)Species.Shaymin: // Shaymin - General[FormOffset2 + 4] = (byte)SetDexFormValues(forms, 1, 2); - return; - case (int)Species.Giratina: // Giratina - General[FormOffset2 + 5] = (byte)SetDexFormValues(forms, 1, 2); - return; - case (int)Species.Pichu when HGSS: // Pichu - General[FormOffset2 + 6] = (byte)SetDexFormValues(forms, 2, 3); - return; - } - } - - private static int[] GetDexFormValues(uint Value, int BitsPerForm, int readCt) - { - int[] Forms = new int[readCt]; - int n1 = 0xFF >> (8 - BitsPerForm); - for (int i = 0; i < Forms.Length; i++) - { - int val = (int)(Value >> (i * BitsPerForm)) & n1; - if (n1 == val && BitsPerForm > 1) - Forms[i] = -1; - else - Forms[i] = val; - } - - // (BitsPerForm > 1) was already handled, handle (BitsPerForm == 1) - if (BitsPerForm == 1 && Forms[0] == Forms[1] && Forms[0] == 1) - Forms[0] = Forms[1] = -1; - - return Forms; - } - - private static uint SetDexFormValues(int[] Forms, int BitsPerForm, int readCt) - { - int n1 = 0xFF >> (8 - BitsPerForm); - uint Value = 0xFFFFFFFF << (readCt * BitsPerForm); - for (int i = 0; i < Forms.Length; i++) - { - int val = Forms[i]; - if (val == -1) - val = n1; - - Value |= (uint)(val << (BitsPerForm*i)); - if (i >= readCt) - throw new ArgumentException("Array count should be less than bitfield count", nameof(Forms)); - } - return Value; - } - - private static bool TryInsertForm(int[] forms, int form) - { - if (Array.IndexOf(forms, form) >= 0) - return false; // already in list - - // insert at first empty - var index = Array.IndexOf(forms, -1); - if (index < 0) - return false; // no free slots? - - forms[index] = form; - return true; - } + protected override void SetDex(PKM pkm) => Dex.SetDex(pkm); + public override bool GetCaught(int species) => Dex.GetCaught(species); + public override bool GetSeen(int species) => Dex.GetSeen(species); public int DexUpgraded { diff --git a/PKHeX.Core/Saves/SAV4DP.cs b/PKHeX.Core/Saves/SAV4DP.cs index e45c439b0..201375468 100644 --- a/PKHeX.Core/Saves/SAV4DP.cs +++ b/PKHeX.Core/Saves/SAV4DP.cs @@ -8,8 +8,20 @@ namespace PKHeX.Core /// public sealed class SAV4DP : SAV4Sinnoh { - public SAV4DP() => Initialize(); - public SAV4DP(byte[] data) : base(data) => Initialize(); + public SAV4DP() + { + Initialize(); + Dex = new Zukan4(this, PokeDex); + } + + public SAV4DP(byte[] data) : base(data) + { + Initialize(); + Dex = new Zukan4(this, PokeDex); + } + + public override Zukan4 Dex { get; } + protected override SAV4 CloneInternal4() => State.Exportable ? new SAV4DP(Data) : new SAV4DP(); public override PersonalTable Personal => PersonalTable.DP; public override IReadOnlyList HeldItems => Legal.HeldItems_DP; diff --git a/PKHeX.Core/Saves/SAV4HGSS.cs b/PKHeX.Core/Saves/SAV4HGSS.cs index 4e40782d5..2e428f237 100644 --- a/PKHeX.Core/Saves/SAV4HGSS.cs +++ b/PKHeX.Core/Saves/SAV4HGSS.cs @@ -8,8 +8,19 @@ namespace PKHeX.Core /// public sealed class SAV4HGSS : SAV4 { - public SAV4HGSS() => Initialize(); - public SAV4HGSS(byte[] data) : base(data) => Initialize(); + public SAV4HGSS() + { + Initialize(); + Dex = new Zukan4(this, PokeDex); + } + + public SAV4HGSS(byte[] data) : base(data) + { + Initialize(); + Dex = new Zukan4(this, PokeDex); + } + + public override Zukan4 Dex { get; } protected override SAV4 CloneInternal4() => State.Exportable ? new SAV4HGSS(Data) : new SAV4HGSS(); public override PersonalTable Personal => PersonalTable.HGSS; diff --git a/PKHeX.Core/Saves/SAV4Pt.cs b/PKHeX.Core/Saves/SAV4Pt.cs index fb651b30a..a8227b434 100644 --- a/PKHeX.Core/Saves/SAV4Pt.cs +++ b/PKHeX.Core/Saves/SAV4Pt.cs @@ -7,8 +7,19 @@ namespace PKHeX.Core /// public sealed class SAV4Pt : SAV4Sinnoh { - public SAV4Pt() => Initialize(); - public SAV4Pt(byte[] data) : base(data) => Initialize(); + public SAV4Pt() + { + Initialize(); + Dex = new Zukan4(this, PokeDex); + } + + public SAV4Pt(byte[] data) : base(data) + { + Initialize(); + Dex = new Zukan4(this, PokeDex); + } + + public override Zukan4 Dex { get; } protected override SAV4 CloneInternal4() => State.Exportable ? new SAV4Pt(Data) : new SAV4Pt(); public override PersonalTable Personal => PersonalTable.Pt; public override IReadOnlyList HeldItems => Legal.HeldItems_Pt; @@ -94,4 +105,4 @@ public override IReadOnlyList Inventory set => value.SaveAll(General); } } -} \ No newline at end of file +} diff --git a/PKHeX.Core/Saves/Substructures/PokeDex/Zukan4.cs b/PKHeX.Core/Saves/Substructures/PokeDex/Zukan4.cs new file mode 100644 index 000000000..ccc981aee --- /dev/null +++ b/PKHeX.Core/Saves/Substructures/PokeDex/Zukan4.cs @@ -0,0 +1,532 @@ +using System; +using System.Linq; + +namespace PKHeX.Core +{ + /// + /// Pokédex structure used by games. + /// + public sealed class Zukan4 : ZukanBase + { + private readonly byte[] Data; + private readonly int Offset; + + // General structure: u32 magic, 4*bitflags, u32 spinda, form flags, language flags, more form flags, upgrade flags + + /* 4 BitRegions with 0x40*8 bits + * Region 0: Caught (Captured/Owned) flags + * Region 1: Seen flags + * Region 2: First Seen Gender + * Region 3: Second Seen Gender + * When setting a newly seen species (first time), we set the gender bit to both First and Second regions. + * When setting an already-seen species, we set the Second region bit if the now-seen gender-bit is not equal to the first-seen bit. + * 4 possible states: 00, 01, 10, 11 + * 00 - 1Seen: Male Only + * 01 - 2Seen: Male First, Female Second + * 10 - 2Seen: Female First, Male Second + * 11 - 1Seen: Female Only + * assuming the species is seen, (bit1 ^ bit2) + 1 = genders in dex + */ + + public const string GENDERLESS = "Genderless"; + public const string MALE = "Male"; + public const string FEMALE = "Female"; + private const int SIZE_REGION = 0x40; + private const int COUNT_REGION = 4; + private const int OFS_SPINDA = sizeof(uint) + (COUNT_REGION * SIZE_REGION); + private const int OFS_FORM1 = OFS_SPINDA + sizeof(uint); + + private bool HGSS => SAV is SAV4HGSS; + private bool DP => SAV is SAV4DP; + + public Zukan4(SAV4 sav, int offset) : base(sav, offset) + { + Data = sav.General; + Offset = offset; + } + + public uint Magic { get => BitConverter.ToUInt32(Data, Offset); set => BitConverter.GetBytes(value).CopyTo(Data, Offset); } + + public override bool GetCaught(int species) => GetRegionFlag(0, species - 1); + public override bool GetSeen(int species) => GetRegionFlag(1, species - 1); + public int GetSeenGenderFirst(int species) => GetRegionFlag(2, species - 1) ? 1 : 0; + public int GetSeenGenderSecond(int species) => GetRegionFlag(3, species - 1) ? 1 : 0; + public bool GetSeenSingleGender(int species) => GetSeenGenderFirst(species) == GetSeenGenderSecond(species); + + private bool GetRegionFlag(int region, int index) + { + var ofs = Offset + 4 + (region * SIZE_REGION) + (index >> 3); + return FlagUtil.GetFlag(Data, ofs, index); + } + + public void SetCaught(int species, bool value = true) => SetRegionFlag(0, species - 1, value); + public void SetSeen(int species, bool value = true) => SetRegionFlag(1, species - 1, value); + public void SetSeenGenderFirst(int species, int value = 0) => SetRegionFlag(2, species - 1, value == 1); + public void SetSeenGenderSecond(int species, int value = 0) => SetRegionFlag(3, species - 1, value == 1); + + private void SetRegionFlag(int region, int index, bool value) + { + var ofs = Offset + 4 + (region * SIZE_REGION) + (index >> 3); + FlagUtil.SetFlag(Data, ofs, index, value); + } + + public uint SpindaPID { get => BitConverter.ToUInt32(Data, Offset + OFS_SPINDA); set => BitConverter.GetBytes(value).CopyTo(Data, Offset); } + + public static string[] GetFormNames4Dex(int species) + { + string[] formNames = FormConverter.GetFormList(species, GameInfo.Strings.types, GameInfo.Strings.forms, Array.Empty(), 4); + if (species == (int)Species.Pichu) + formNames = new[] { MALE, FEMALE, formNames[1] }; // Spiky + return formNames; + } + + public int[] GetForms(int species) + { + const int brSize = 0x40; + if (species == (int)Species.Deoxys) + { + uint val = (uint)(Data[Offset + 0x4 + (1 * brSize) - 1] | Data[Offset + 0x4 + (2 * brSize) - 1] << 8); + return GetDexFormValues(val, 4, 4); + } + + int FormOffset1 = Offset + 4 + (4 * brSize) + 4; + switch (species) + { + case (int)Species.Shellos: // Shellos + return GetDexFormValues(Data[FormOffset1 + 0], 1, 2); + case (int)Species.Gastrodon: // Gastrodon + return GetDexFormValues(Data[FormOffset1 + 1], 1, 2); + case (int)Species.Burmy: // Burmy + return GetDexFormValues(Data[FormOffset1 + 2], 2, 3); + case (int)Species.Wormadam: // Wormadam + return GetDexFormValues(Data[FormOffset1 + 3], 2, 3); + case (int)Species.Unown: // Unown + return Data.Slice(FormOffset1 + 4, 0x1C).Select(i => (int)i).ToArray(); + } + if (DP) + return Array.Empty(); + + int PokeDexLanguageFlags = FormOffset1 + (HGSS ? 0x3C : 0x20); + int FormOffset2 = PokeDexLanguageFlags + 0x1F4; + return species switch + { + (int)Species.Rotom => GetDexFormValues(BitConverter.ToUInt32(Data, FormOffset2), 3, 6), + (int)Species.Shaymin => GetDexFormValues(Data[FormOffset2 + 4], 1, 2), + (int)Species.Giratina => GetDexFormValues(Data[FormOffset2 + 5], 1, 2), + (int)Species.Pichu when HGSS => GetDexFormValues(Data[FormOffset2 + 6], 2, 3), + _ => Array.Empty() + }; + } + + public void SetForms(int species, int[] forms) + { + const int brSize = 0x40; + switch (species) + { + case (int)Species.Deoxys: // Deoxys + uint newval = SetDexFormValues(forms, 4, 4); + Data[Offset + 0x4 + (1 * brSize) - 1] = (byte)(newval & 0xFF); + Data[Offset + 0x4 + (2 * brSize) - 1] = (byte)((newval >> 8) & 0xFF); + break; + } + + int FormOffset1 = Offset + OFS_FORM1; + switch (species) + { + case (int)Species.Shellos: // Shellos + Data[FormOffset1 + 0] = (byte)SetDexFormValues(forms, 1, 2); + return; + case (int)Species.Gastrodon: // Gastrodon + Data[FormOffset1 + 1] = (byte)SetDexFormValues(forms, 1, 2); + return; + case (int)Species.Burmy: // Burmy + Data[FormOffset1 + 2] = (byte)SetDexFormValues(forms, 2, 3); + return; + case (int)Species.Wormadam: // Wormadam + Data[FormOffset1 + 3] = (byte)SetDexFormValues(forms, 2, 3); + return; + case (int)Species.Unown: // Unown + int ofs = FormOffset1 + 4; + int len = forms.Length; + Array.Resize(ref forms, 0x1C); + for (int i = len; i < forms.Length; i++) + forms[i] = 0xFF; + Array.Copy(forms.Select(b => (byte)b).ToArray(), 0, Data, ofs, forms.Length); + return; + } + + if (DP) + return; + + int PokeDexLanguageFlags = FormOffset1 + (HGSS ? 0x3C : 0x20); + int FormOffset2 = PokeDexLanguageFlags + 0x1F4; + switch (species) + { + case (int)Species.Rotom: // Rotom + BitConverter.GetBytes(SetDexFormValues(forms, 3, 6)).CopyTo(Data, FormOffset2); + return; + case (int)Species.Shaymin: // Shaymin + Data[FormOffset2 + 4] = (byte)SetDexFormValues(forms, 1, 2); + return; + case (int)Species.Giratina: // Giratina + Data[FormOffset2 + 5] = (byte)SetDexFormValues(forms, 1, 2); + return; + case (int)Species.Pichu when HGSS: // Pichu + Data[FormOffset2 + 6] = (byte)SetDexFormValues(forms, 2, 3); + return; + } + } + + private static int[] GetDexFormValues(uint Value, int BitsPerForm, int readCt) + { + int[] Forms = new int[readCt]; + int n1 = 0xFF >> (8 - BitsPerForm); + for (int i = 0; i < Forms.Length; i++) + { + int val = (int)(Value >> (i * BitsPerForm)) & n1; + if (n1 == val && BitsPerForm > 1) + Forms[i] = -1; + else + Forms[i] = val; + } + + // (BitsPerForm > 1) was already handled, handle (BitsPerForm == 1) + if (BitsPerForm == 1 && Forms[0] == Forms[1] && Forms[0] == 1) + Forms[0] = Forms[1] = -1; + + return Forms; + } + + private static uint SetDexFormValues(int[] Forms, int BitsPerForm, int readCt) + { + int n1 = 0xFF >> (8 - BitsPerForm); + uint Value = 0xFFFFFFFF << (readCt * BitsPerForm); + for (int i = 0; i < Forms.Length; i++) + { + int val = Forms[i]; + if (val == -1) + val = n1; + + Value |= (uint)(val << (BitsPerForm * i)); + if (i >= readCt) + throw new ArgumentException("Array count should be less than bitfield count", nameof(Forms)); + } + return Value; + } + + private static bool TryInsertForm(int[] forms, int form) + { + if (Array.IndexOf(forms, form) >= 0) + return false; // already in list + + // insert at first empty + var index = Array.IndexOf(forms, -1); + if (index < 0) + return false; // no free slots? + + forms[index] = form; + return true; + } + + public int GetUnownFormIndex(int form) + { + var ofs = Offset + OFS_FORM1 + 4; + for (int i = 0; i < 0x1C; i++) + { + byte val = Data[ofs + i]; + if (val == form) + return i; + if (val == 0xFF) + return -1; + } + return -1; + } + + public int GetUnownFormIndexNext(int form) + { + var ofs = Offset + OFS_FORM1 + 4; + for (int i = 0; i < 0x1C; i++) + { + byte val = Data[ofs + i]; + if (val == form) + return i; + if (val == 0xFF) + return i; + } + + return -1; + } + + public void ClearUnownForms() + { + var ofs = Offset + OFS_FORM1 + 4; + for (int i = 0; i < 0x1C; i++) + Data[ofs + i] = 0xFF; + } + + public bool GetUnownForm(int form) => GetUnownFormIndex(form) != -1; + + public void AddUnownForm(int form) + { + var index = GetUnownFormIndexNext(form); + if (index == -1) + return; + + var ofs = Offset + OFS_FORM1 + 4; + Data[ofs + index] = (byte)form; + } + + public override void SetDex(PKM pkm) + { + int species = pkm.Species; + if (species == 0) + return; + if (species > Legal.MaxSpeciesID_4) + return; + + var gender = pkm.Gender; + var form = pkm.Form; + var language = pkm.Language; + SetDex(species, gender, form, language); + } + + private void SetDex(int species, int gender, int form, int language) + { + SetCaught(species); + SetSeenGender(species, gender); + SetForms(species, form, gender); + SetLanguage(species, language); + } + + public void SetSeenGender(int species, int gender) + { + if (!GetSeen(species)) + SetSeenGenderNew(species, gender); + else if (GetSeenSingleGender(species)) + SetSeenGenderSecond(species, gender); + } + + public void SetSeenGenderNew(int species, int gender) + { + SetSeenGenderFirst(species, gender); + SetSeenGenderSecond(species, gender); + } + + public void SetSeenGenderNeither(int species) + { + SetSeenGenderFirst(species, 0); + SetSeenGenderSecond(species, 0); + } + + private void SetForms(int species, int form, int gender) + { + if (species == (int)Species.Unown) // Unown + { + AddUnownForm(form); + return; + } + + var forms = GetForms(species); + if (forms.Length == 0) + return; + + if (species == (int)Species.Pichu && HGSS) // Pichu (HGSS Only) + { + int formID = form == 1 ? 2 : gender; + if (TryInsertForm(forms, formID)) + SetForms(species, forms); + } + else + { + if (TryInsertForm(forms, form)) + SetForms(species, forms); + } + } + + public void SetLanguage(int species, int language, bool value = true) + { + int lang = GetGen4LanguageBitIndex(language); + SetLanguageBitIndex(species, lang, value); + } + + public bool GetLanguageBitIndex(int species, int lang) + { + int dpl = 1 + Array.IndexOf(DPLangSpecies, species); + if (DP && dpl < 0) + return false; + int FormOffset1 = Offset + OFS_FORM1; + int PokeDexLanguageFlags = FormOffset1 + (HGSS ? 0x3C : 0x20); + + var ofs = PokeDexLanguageFlags + (DP ? dpl : species); + return FlagUtil.GetFlag(Data, ofs, lang & 7); + } + + public void SetLanguageBitIndex(int species, int lang, bool value) + { + int dpl = 1 + Array.IndexOf(DPLangSpecies, species); + if (DP && dpl <= 0) + return; + int FormOffset1 = Offset + OFS_FORM1; + int PokeDexLanguageFlags = FormOffset1 + (HGSS ? 0x3C : 0x20); + + var ofs = PokeDexLanguageFlags + (DP ? dpl : species); + FlagUtil.SetFlag(Data, ofs, lang & 7, value); + } + + public bool HasLanguage(int species) => GetSpeciesLanguageByteIndex(species) >= 0; + + private int GetSpeciesLanguageByteIndex(int species) + { + if (DP) + return Array.IndexOf(DPLangSpecies, species); + return species; + } + + private static readonly int[] DPLangSpecies = { 23, 25, 54, 77, 120, 129, 202, 214, 215, 216, 228, 278, 287, 315 }; + + public static int GetGen4LanguageBitIndex(int lang) => --lang switch + { + 3 => 4, // invert ITA/GER + 4 => 3, // invert ITA/GER + > 5 => 0, // Japanese + < 0 => 1, // English + _ => lang, + }; + + [Flags] + public enum SetDexArgs + { + None, + SeenAll = 1 << 0, + + CaughtNone = 1 << 1, + CaughtAll = 1 << 2, + + SetNoLanguages = 1 << 3, + SetAllLanguages = 1 << 4, + SetSingleLanguage = 1 << 5, + + SetAllForms = 1 << 6, + + Complete = SeenAll | CaughtAll | SetAllLanguages | SetAllForms, + } + + public void ModifyAll(int species, SetDexArgs args, int lang = 0) + { + if (args == SetDexArgs.None) + { + ClearSeen(species); + return; + } + if ((args & SetDexArgs.SeenAll) != 0) + CompleteSeen(species); + + if ((args & SetDexArgs.CaughtNone) != 0) + { + SetCaught(species, false); + ClearLanguages(species); + } + else if ((args & SetDexArgs.CaughtAll) != 0) + { + SetCaught(species); + } + + if ((args & SetDexArgs.SetNoLanguages) != 0) + { + ClearLanguages(species); + } + if ((args & SetDexArgs.SetAllLanguages) != 0) + { + SetLanguages(species); + } + else if ((args & SetDexArgs.SetSingleLanguage) != 0) + { + SetLanguage(species, lang); + } + + if ((args & SetDexArgs.SetAllForms) != 0) + { + CompleteForms(species); + } + } + + private void CompleteForms(int species) + { + var forms = GetFormNames4Dex(species); + if (forms.Length <= 1) + return; + + var values = forms.Select((_, i) => i).ToArray(); + SetForms(species, values); + } + + private void CompleteSeen(int species) + { + SetSeen(species); + var pi = PersonalTable.HGSS[species]; + if (pi.IsDualGender) + { + SetSeenGenderFirst(species, 0); + SetSeenGenderSecond(species, 1); + } + else + { + SetSeenGender(species, pi.FixedGender & 1); + } + } + + public void ClearSeen(int species) + { + SetCaught(species, false); + SetSeen(species, false); + SetSeenGenderNeither(species); + + SetForms(species, Array.Empty()); + ClearLanguages(species); + } + + private const int LangCount = 6; + private void ClearLanguages(int species) + { + for (int i = 0; i < 8; i++) + SetLanguageBitIndex(species, i, false); + } + + private void SetLanguages(int species, bool value = true) + { + for (int i = 0; i < LangCount; i++) + SetLanguageBitIndex(species, i, value); + } + + // Bulk Manipulation + public override void CompleteDex(bool shinyToo = false) => IterateAll(z => ModifyAll(z, SetDexArgs.Complete)); + public override void SeenNone() => IterateAll(ClearSeen); + public override void CaughtNone() => IterateAll(z => SetCaught(z, false)); + public override void SeenAll(bool shinyToo = false) => IterateAll(CompleteSeen); + + public override void SetDexEntryAll(int species, bool shinyToo = false) => ModifyAll(species, SetDexArgs.Complete); + public override void ClearDexEntryAll(int species) => ModifyAll(species, SetDexArgs.None); + + private static void IterateAll(Action a) + { + for (int i = 1; i <= Legal.MaxSpeciesID_4; i++) + a(i); + } + + public override void SetAllSeen(bool value = true, bool shinyToo = false) + { + if (!value) + { + SeenNone(); + return; + } + IterateAll(CompleteSeen); + } + + public override void CaughtAll(bool shinyToo = false) + { + SeenAll(); + IterateAll(z => SetCaught(z)); + } + } +} diff --git a/PKHeX.WinForms/Subforms/Save Editors/Gen4/SAV_Pokedex4.cs b/PKHeX.WinForms/Subforms/Save Editors/Gen4/SAV_Pokedex4.cs index 858d48e8a..09306d691 100644 --- a/PKHeX.WinForms/Subforms/Save Editors/Gen4/SAV_Pokedex4.cs +++ b/PKHeX.WinForms/Subforms/Save Editors/Gen4/SAV_Pokedex4.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Windows.Forms; using PKHeX.Core; +using static PKHeX.Core.Zukan4; namespace PKHeX.WinForms { @@ -46,12 +47,12 @@ public SAV_Pokedex4(SaveFile sav) private readonly CheckBox[] CL; private bool editing; private int species = -1; - private const int brSize = 0x40; private const int LangCount = 6; // No Korean private void ChangeCBSpecies(object sender, EventArgs e) { - if (editing) return; + if (editing) + return; SetEntry(); editing = true; @@ -64,7 +65,8 @@ private void ChangeCBSpecies(object sender, EventArgs e) private void ChangeLBSpecies(object sender, EventArgs e) { - if (editing) return; + if (editing) + return; SetEntry(); editing = true; @@ -74,71 +76,45 @@ private void ChangeLBSpecies(object sender, EventArgs e) editing = false; } - private const string GENDERLESS = "Genderless"; - private const string MALE = "Male"; - private const string FEMALE = "Female"; - private static readonly int[] DPLangSpecies = { 23, 25, 54, 77, 120, 129, 202, 214, 215, 216, 228, 278, 287, 315 }; - private void GetEntry() { - // Load Bools for the data - int bit = species - 1; - byte mask = (byte)(1 << (bit & 7)); - int ofs = SAV.PokeDex + (bit >> 3) + 0x4; - int FormOffset1 = SAV.PokeDex + 4 + (brSize * 4) + 4; - int PokeDexLanguageFlags = FormOffset1 + (SAV.HGSS ? 0x3C : 0x20); - int l_ofs = !SAV.DP ? species : 1 + Array.IndexOf(DPLangSpecies, species); - if (l_ofs > 0) + var dex = SAV.Dex; + + CHK_Caught.Checked = dex.GetCaught(species); + CHK_Seen.Checked = dex.GetSeen(species); + + // Genders + LoadGenders(CHK_Seen.Checked); + + // Forms + LoadForms(); + + // Language + LoadLanguage(); + } + + private void LoadLanguage() + { + var dex = SAV.Dex; + if (dex.HasLanguage(species)) { - l_ofs += PokeDexLanguageFlags; for (int i = 0; i < LangCount; i++) - { - CL[i].Enabled = true; - CL[i].Checked = SAV.GetFlag(l_ofs, i); - } + CL[i].Checked = dex.GetLanguageBitIndex(species, i); } else { + GB_Language.Enabled = false; for (int i = 0; i < LangCount; i++) - CL[i].Enabled = CL[i].Checked = false; + CL[i].Checked = false; } + } - bool bit2 = (SAV.General[ofs + (brSize * 2)] & mask) != 0; - bool bit3 = (SAV.General[ofs + (brSize * 3)] & mask) != 0; - - CHK_Seen.Checked = (SAV.General[ofs + (brSize * 1)] & mask) != 0; - CHK_Caught.Checked = (SAV.General[ofs + (brSize * 0)] & mask) != 0; - - // Genders - LB_Gender.Items.Clear(); - LB_NGender.Items.Clear(); - - var active = CHK_Seen.Checked ? LB_Gender : LB_NGender; - var inactive = LB_NGender; - var other = bit2 ^ bit3 ? active : inactive; - - switch (SAV.Personal[species].Gender) - { - case 255: // Genderless - active.Items.Add(GENDERLESS); - break; - case 0: - active.Items.Add(MALE); - break; - case 254: - active.Items.Add(FEMALE); - break; - default: - active.Items.Add(bit2 ? FEMALE : MALE); - other.Items.Add(bit2 ? MALE : FEMALE); - break; - } - - // Forms + private void LoadForms() + { LB_Form.Items.Clear(); LB_NForm.Items.Clear(); - var forms = SAV.GetForms(species); + var forms = SAV.Dex.GetForms(species); if (forms.Length == 0) return; @@ -151,12 +127,33 @@ private void GetEntry() LB_NForm.Items.AddRange(not); } - private static string[] GetFormNames4Dex(int species) + private void LoadGenders(bool seen) { - string[] formNames = FormConverter.GetFormList(species, GameInfo.Strings.types, GameInfo.Strings.forms, Main.GenderSymbols, 4); - if (species == (int)Species.Pichu) - formNames = new[] { MALE, FEMALE, formNames[1] }; // Spiky - return formNames; + var dex = SAV.Dex; + var first = seen ? LB_Gender : LB_NGender; + var second = dex.GetSeenSingleGender(species) ? LB_NGender : first; + + LB_Gender.Items.Clear(); + LB_NGender.Items.Clear(); + var pi = SAV.Personal[species]; + var gr = pi.Gender; + switch (gr) + { + case 255: // Genderless + first.Items.Add(GENDERLESS); + break; + case 0: + first.Items.Add(MALE); + break; + case 254: + first.Items.Add(FEMALE); + break; + default: + var firstFem = dex.GetSeenGenderFirst(species) == 1; + first.Items.Add(firstFem ? FEMALE : MALE); + second.Items.Add(firstFem ? MALE : FEMALE); + break; + } } private void SetEntry() @@ -164,73 +161,34 @@ private void SetEntry() if (species < 0) return; - int bit = species - 1; - byte mask = (byte)(1 << (bit & 7)); - int ofs = SAV.PokeDex + (bit >> 3) + 0x4; - - // Check if already Seen - if (!CHK_Seen.Checked || LB_Gender.Items.Count == 0) + var dex = SAV.Dex; + dex.SetCaught(species, CHK_Caught.Checked); + dex.SetSeen(species, CHK_Seen.Checked); + dex.SetSeenGenderNeither(species); + if (LB_Gender.Items.Count != 0) { - SAV.General[ofs + (brSize * 0)] &= (byte)~mask; - SAV.General[ofs + (brSize * 1)] &= (byte)~mask; - SAV.General[ofs + (brSize * 2)] &= (byte)~mask; - SAV.General[ofs + (brSize * 3)] &= (byte)~mask; - } - else // Is Seen - { - // Set the Species Owned Flag - if (CHK_Caught.Checked) - SAV.General[ofs + (brSize * 0)] |= mask; - else - SAV.General[ofs + (brSize * 0)] &= (byte)~mask; - - SAV.General[ofs + (brSize * 1)] |= mask; - switch ((string)LB_Gender.Items[0]) - { - case GENDERLESS: - SAV.General[ofs + (brSize * 2)] &= (byte)~mask; - SAV.General[ofs + (brSize * 3)] &= (byte)~mask; - break; - case FEMALE: - SAV.General[ofs + (brSize * 2)] |= mask; // set - if (LB_Gender.Items.Count != 1) // Male present - SAV.General[ofs + (brSize * 3)] &= (byte)~mask; // unset - else - SAV.General[ofs + (brSize * 3)] |= mask; // set - break; - case MALE: - SAV.General[ofs + (brSize * 2)] &= (byte)~mask; // unset - if (LB_Gender.Items.Count != 1) // Female present - SAV.General[ofs + (brSize * 3)] |= mask; // set - else - SAV.General[ofs + (brSize * 3)] &= (byte)~mask; // unset - break; - default: - throw new ArgumentException("Invalid Gender???"); - } + var femaleFirst = LB_Gender.Items[0].ToString() == FEMALE; + var firstGender = femaleFirst ? 1 : 0; + dex.SetSeenGenderNew(species, firstGender); + if (LB_Gender.Items.Count != 1) + dex.SetSeenGenderSecond(species, firstGender ^ 1); } - int FormOffset1 = SAV.PokeDex + 4 + (4 * brSize) + 4; - int PokeDexLanguageFlags = FormOffset1 + (SAV.HGSS ? 0x3C : 0x20); - int l_ofs = !SAV.DP ? species : (1 + Array.IndexOf(DPLangSpecies, species)); - if (l_ofs > 0) + if (dex.HasLanguage(species)) { - l_ofs += PokeDexLanguageFlags; for (int i = 0; i < LangCount; i++) - SAV.SetFlag(l_ofs, i, CL[i].Checked); + dex.SetLanguageBitIndex(species, i, CL[i].Checked); } - var forms = SAV.GetForms(species); + var forms = SAV.Dex.GetForms(species); if (forms.Length > 0) { int[] arr = new int[LB_Form.Items.Count]; string[] formNames = GetFormNames4Dex(species); for (int i = 0; i < LB_Form.Items.Count; i++) arr[i] = Array.IndexOf(formNames, (string)LB_Form.Items[i]); - SAV.SetForms(species, arr); + SAV.Dex.SetForms(species, arr); } - - editing = false; } private void B_Cancel_Click(object sender, EventArgs e) @@ -250,21 +208,10 @@ private void B_Save_Click(object sender, EventArgs e) private void B_GiveAll_Click(object sender, EventArgs e) { - if (GB_Language.Enabled) - { - CHK_L1.Checked = - CHK_L2.Checked = - CHK_L3.Checked = - CHK_L4.Checked = - CHK_L5.Checked = - CHK_L6.Checked = ModifierKeys != Keys.Control; - } - CHK_Caught.Checked = CHK_Seen.Checked = ModifierKeys != Keys.Control; - - if (ModifierKeys == Keys.Control) - SeenNone(); - else - SeenAll(); + bool all = ModifierKeys != Keys.Control; + var args = all ? SetDexArgs.Complete : SetDexArgs.None; + SAV.Dex.ModifyAll(species, args, GetGen4LanguageBitIndex(SAV.Language)); + GetEntry(); } private void B_Modify_Click(object sender, EventArgs e) @@ -273,67 +220,28 @@ private void B_Modify_Click(object sender, EventArgs e) modifyMenu.Show(btn.PointToScreen(new Point(0, btn.Height))); } - private void SeenNone() - { - LB_NGender.Items.AddRange(LB_Gender.Items); - LB_Gender.Items.Clear(); - - LB_NForm.Items.AddRange(LB_Form.Items); - LB_Form.Items.Clear(); - CHK_Seen.Checked = false; - foreach (var c in CL) - c.Checked = false; - } - - private void SeenAll() - { - LB_Gender.Items.AddRange(LB_NGender.Items); - LB_NGender.Items.Clear(); - - LB_Form.Items.AddRange(LB_NForm.Items); - LB_NForm.Items.Clear(); - CHK_Seen.Checked = true; - } - private void ModifyAll(object sender, EventArgs e) { - int lang = SAV.Language - 1; - if (lang is < 0 or > 5) // KOR or Invalid - lang = 0; + SetEntry(); - bool seenA = sender == mnuSeenAll || sender == mnuCaughtAll || sender == mnuComplete; - bool seenN = sender == mnuSeenNone; - bool caughtA = sender == mnuCaughtAll || sender == mnuComplete; - bool caughtN = sender == mnuCaughtNone || sender == mnuSeenNone; + var lang = GetGen4LanguageBitIndex(SAV.Language); + SetDexArgs args; + if (sender == mnuComplete) + args = SetDexArgs.Complete; + else if (sender == mnuCaughtAll) + args = SetDexArgs.CaughtAll; + else if (sender == mnuCaughtNone) + args = SetDexArgs.CaughtNone; + else if (sender == mnuSeenAll) + args = SetDexArgs.SeenAll; + else + args = SetDexArgs.None; for (int i = 0; i < LB_Species.Items.Count; i++) - { - LB_Species.SelectedIndex = i; + SAV.Dex.ModifyAll(i + 1, args, lang); - if (seenN) // move all to none - SeenNone(); - else if (seenA) // move all to seen - SeenAll(); - - if (caughtA) - { - CHK_Caught.Checked = true; - for (int j = 0; j < CL.Length; j++) // set SAV language (and others if Complete) - CL[j].Checked = sender == mnuComplete || (mnuCaughtNone != sender && j == lang); - } - else if (caughtN) - { - CHK_Caught.Checked = false; - } - else if (!CHK_Seen.Checked) - { - foreach (CheckBox t in CL) - t.Checked = false; - } - } - - SetEntry(); GetEntry(); + System.Media.SystemSounds.Asterisk.Play(); } private void CHK_Seen_CheckedChanged(object sender, EventArgs e) @@ -342,8 +250,8 @@ private void CHK_Seen_CheckedChanged(object sender, EventArgs e) { if (!CHK_Seen.Checked) // move all to none { - CHK_Caught.Checked = false; - SeenNone(); + SAV.Dex.ClearSeen(species); + GetEntry(); } else if (LB_NGender.Items.Count > 0) {