Improved auto fill (#254)

* Rename fields

Rename to `HatchedSpecies`
`Species` here is actually `ModelID`. To get Species `DexIndexNational` should be used

* Update interfaces

Use DexIndexRegional
Replace `RegionalFormIndex` with `Form`

Add national dex index

* Add debug assert

* Add set functions for easy assignment of personal info

* Update auto-fill

* All missing SWSH data is now filled based on USUM data
* Accounts for form data
* Ignores all forms that are not carried over to next gen
* Adds form number and national dex number

* Update PLA filler to account for forms and other missing entries
This commit is contained in:
JelleInfinity 2022-09-30 01:14:44 +02:00 committed by GitHub
parent 7f051d06ed
commit 9346f482da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 541 additions and 117 deletions

View File

@ -81,13 +81,13 @@ public PersonalInfo8LA(PersonalInfoLAfb fb)
public uint TR_D { get => FB.TR_D; set => FB.TR_D = value; }
public uint TypeTutor { get => FB.TypeTutor; set => FB.TypeTutor = value; }
public ushort HatchedSpecies { get => FB.HatchSpecies; set => FB.HatchSpecies = (ushort)value; }
public ushort HatchedSpecies { get => FB.HatchedSpecies; set => FB.HatchedSpecies = value; }
public ushort LocalFormIndex { get => FB.LocalFormIndex; set => FB.LocalFormIndex = value; }
public bool Field_45 { get => FB.Field_45; set => FB.Field_45 = value; }
public ushort Field_46 { get => FB.Field_46; set => FB.Field_46 = value; }
public byte Field_47 { get => FB.Field_47; set => FB.Field_47 = value; }
public ushort Species { get => FB.Species; set => FB.Species = value; }
public ushort ModelID { get => FB.Species; set => FB.Species = value; }
public ushort Form { get => FB.Form; set => FB.Form = value; }
public ushort DexIndexNational { get => FB.DexIndexOther; set => FB.DexIndexOther = value; }
public ushort DexIndexRegional { get => FB.DexIndexHisui; set => FB.DexIndexHisui = value; }

View File

@ -1,6 +1,7 @@
using pkNX.Containers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace pkNX.Structures.FlatBuffers;
@ -101,4 +102,60 @@ public bool IsPresentInGame(ushort species, byte form)
IPersonalInfo IPersonalTable.this[int index] => this[index];
IPersonalInfo IPersonalTable.this[ushort species, byte form] => this[species, form];
IPersonalInfo IPersonalTable.GetFormEntry(ushort species, byte form) => GetFormEntry(species, form);
public void FixMissingData()
{
// Fix all base forms
int laFormCount = 0;
for (ushort i = 1; i <= Legal.MaxSpeciesID_8; i++)
{
var l = Table[i];
l.DexIndexNational = i;
var s = ResourcesUtil.SWSH.Table[i];
Debug.Assert(l.DexIndexNational == s.DexIndexNational);
if (l.HP == 0)
{
l.SetPersonalInfo(s);
}
if (l.FormCount == 1)
continue;
if (l.FormStatsIndex != 0)
Debug.Assert(l.FormStatsIndex == (MaxSpeciesID + 1) + laFormCount);
l.FormStatsIndex = (MaxSpeciesID + 1) + laFormCount;
laFormCount += l.FormCount - 1;
for (byte f = 1; f < l.FormCount; f++)
{
var formL = Table[l.FormStatsIndex + (f - 1)];
if (formL.HP == 0)
{
// Check if SWSH table has form data for this entry
if (f < s.FormCount)
{
if (s.FormCount <= l.FormCount || (FormInfo.HasBattleOnlyForm(i) && !FormInfo.IsBattleOnlyForm(i, f, 8)))
{
var formS = ResourcesUtil.SWSH.GetFormEntry(i, f);
Debug.Assert(formL.DexIndexNational == formS.DexIndexNational);
formL.SetPersonalInfo(formS);
}
}
else
{
// No form data was found, just write the base form data
formL.SetPersonalInfo(l);
}
}
}
}
Debug.WriteLine("Auto fix for PLA data succeded");
}
}

View File

@ -71,7 +71,7 @@ public class PersonalInfoLAfb
[FlatBufferItem(40)] public ushort Item3 { get; set; } // Always Default (0)
[FlatBufferItem(41)] public byte EggGroup1 { get; set; } // byte
[FlatBufferItem(42)] public byte EggGroup2 { get; set; } // byte
[FlatBufferItem(43)] public ushort HatchSpecies { get; set; } // ushort
[FlatBufferItem(43)] public ushort HatchedSpecies { get; set; } // ushort
[FlatBufferItem(44)] public ushort LocalFormIndex { get; set; } // ushort
[FlatBufferItem(45)] public bool Field_45 { get; set; } // byte
[FlatBufferItem(46)] public ushort Field_46 { get; set; } // ushort

View File

@ -227,13 +227,13 @@ private void AddPersonalLines(List<string> lines, IPersonalInfo pi, int entry, s
lines.Add("======");
if (pi is IPersonalInfoSWSH s)
{
if (s.PokeDexIndex != 0)
lines.Add($"Galar Dex: #{s.PokeDexIndex:000}");
if (s.DexIndexRegional != 0)
lines.Add($"Galar Dex: #{s.DexIndexRegional:000}");
if (s.ArmorDexIndex != 0)
lines.Add($"Armor Dex: #{s.ArmorDexIndex:000}");
if (s.CrownDexIndex != 0)
lines.Add($"Crown Dex: #{s.CrownDexIndex:000}");
if (s.PokeDexIndex == 0 && s.ArmorDexIndex == 0 && s.CrownDexIndex == 0)
if (s.DexIndexRegional == 0 && s.ArmorDexIndex == 0 && s.CrownDexIndex == 0)
lines.Add("Galar Dex: Foreign");
if (s.CanNotDynamax)

View File

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using static pkNX.Structures.Species;
@ -8,6 +9,11 @@ namespace pkNX.Structures;
/// </summary>
public static class FormInfo
{
public static bool HasBattleOnlyForm(ushort species)
{
return BattleOnly.Contains(species);
}
/// <summary>
/// Checks if the form cannot exist outside of a Battle.
/// </summary>
@ -17,7 +23,7 @@ public static class FormInfo
/// <returns>True if it can only exist in a battle, false if it can exist outside of battle.</returns>
public static bool IsBattleOnlyForm(ushort species, byte form, int format)
{
if (!BattleOnly.Contains(species))
if (!HasBattleOnlyForm(species))
return false;
// Some species have battle only forms as well as out-of-battle forms (other than base form).
@ -29,13 +35,154 @@ public static bool IsBattleOnlyForm(ushort species, byte form, int format)
case (int)Mimikyu when form == 2: // Totem disguise Mimikyu
case (int)Necrozma when form < 3: // Only mark Ultra Necrozma as Battle Only
return false;
case (int)Minior: return form < 7; // Minior Shields-Down
case (int)Minior:
return form < 7; // Minior Shields-Down
default:
return form != 0;
}
}
public static byte RemoveBattleOnlyFormsFromCount(ushort species, byte formCount, int format)
{
if (!HasBattleOnlyForm(species))
return formCount;
return Math.Min(formCount, GetOutOfBattleFormCount_Impl(species));
}
public static byte RemoveTotemFormsFromCount(ushort species, byte formCount, int format)
{
if (!HasTotemForm(species))
return formCount;
return Math.Min(formCount, GetOutOfBattleFormCount_Impl(species));
}
public static byte GetOutOfBattleFormCount(ushort species, byte formCount, int format)
{
if (!HasTotemForm(species) && !HasBattleOnlyForm(species))
return formCount;
return GetOutOfBattleFormCount_Impl(species);
}
/// <summary>
/// Warning! Untested! Keep this private untill it's properly tested.
/// Get the amount of forms that can be used out of battle and carried over to newer generations
/// </summary>
/// <remarks>This removes mega forms, totem forms, dynamax forms, etc.</remarks>
/// <param name="species">The species to get the count for</param>
/// <returns></returns>
private static byte GetOutOfBattleFormCount_Impl(ushort species)
{
return species switch
{
(int)Rattata => 2,// Standard Form, Alolan Form
(int)Raticate => 2,// Standard Form, Alolan Form
(int)Pikachu => 10, // Pikachu with cap
(int)Raichu => 2, // Standard Form, Alolan Form
(int)Sandshrew => 2, // Standard Form, Alolan Form
(int)Sandslash => 2, // Standard Form, Alolan Form
(int)Vulpix => 2, // Standard Form, Alolan Form
(int)Ninetales => 2, // Standard Form, Alolan Form
(int)Diglett => 2, // Standard Form, Alolan Form
(int)Dugtrio => 2, // Standard Form, Alolan Form
(int)Meowth => 3, // Standard Form, Alolan Form, Galarian Form
(int)Persian => 2, // Standard Form, Alolan Form
(int)Growlithe => 2, // Standard Form, Hisuian Form
(int)Arcanine => 2, // Standard Form, Hisuian Form
(int)Geodude => 2, // Standard Form, Hisuian Form
(int)Graveler => 2, // Standard Form, Hisuian Form
(int)Golem => 2, // Standard Form, Hisuian Form
(int)Ponyta => 2, // Standard Form, Galarian Form
(int)Rapidash => 2, // Standard Form, Galarian Form
(int)Slowpoke => 2, // Standard Form, Galarian Form
(int)Slowbro => 2, // Standard Form, Galarian Form
(int)Farfetchd => 2, // Standard Form, Galarian Form
(int)Grimer => 2, // Standard Form, Alolan Form
(int)Muk => 2, // Standard Form, Alolan Form
(int)Voltorb => 2, // Standard Form, Hisuian Form
(int)Electrode => 2, // Standard Form, Hisuian Form
(int)Exeggutor => 2, // Standard Form, Alolan Form
(int)Marowak => 2, // Standard Form, Alolan Form
(int)Weezing => 2, // Standard Form, Galarian Form
(int)MrMime => 2, // Standard Form, Galarian Form
(int)Articuno => 2, // Standard Form, Galarian Form
(int)Zapdos => 2, // Standard Form, Galarian Form
(int)Moltres => 2, // Standard Form, Galarian Form
(int)Typhlosion => 2, // Standard Form, Hisuian Form
(int)Slowking => 2, // Standard Form, Galarian Form
(int)Unown => 28, // Unknown forms have no personal data
(int)Sneasel => 2, // Standard Form, Hisuian Form
(int)Corsola => 2, // Standard Form, Galarian Form
(int)Zigzagoon => 2, // Standard Form, Galarian Form
(int)Linoone => 2, // Standard Form, Galarian Form
//Spinda?
(int)Castform => 4, // Normal Form, Sunny Form, Rainy Form, Snowy Form
(int)Burmy => 3, // Plant Cloak, Sandy Cloak, Trash Cloak
(int)Wormadam => 3, // Plant Cloak, Sandy Cloak, Trash Cloak
(int)Shellos => 2, // West Sea, East Sea
(int)Gastrodon => 2, // West Sea, East Sea
(int)Rotom => 6, // Standard Form, Heat Rotom, Wash Rotom, Frost Rotom, Fan Rotom, Mow Rotom
(int)Dialga => 2, // Standard Form, Origin Form
(int)Palkia => 2, // Standard Form, Origin Form
(int)Giratina => 2, // Standard Form, Origin Form
(int)Shaymin => 2, // Land Form, Sky Form
(int)Samurott => 2, // Standard Form, Hisuian Form
(int)Lilligant => 2, // Standard Form, Hisuian Form
(int)Basculin => 3, // Red-Striped Form, Blue-Striped Form, White-Striped Form
(int)Darumaka => 2,// Standard Form, Galarian Form
(int)Darmanitan => 2,// Standard Form, Galarian non-Zen Form
(int)Yamask => 2,// Standard Form, Galarian Form
(int)Zorua => 2,// Standard Form, Hisuian Form
(int)Zoroark => 2,// Standard Form, Hisuian Form
(int)Deerling => 4,
(int)Sawsbuck => 4,
(int)Braviary => 2,// Standard Form, Hisuian Form
(int)Tornadus => 2, // Incarnate Form, Therian Form
(int)Thundurus => 2, // Incarnate Form, Therian Form
(int)Landorus => 2, // Incarnate Form, Therian Form
(int)Kyurem => 3, // Normal Form, Black Form, White Form
(int)Keldeo => 2, // Ordinary Form, Resolute Form
(int)Meloetta => 2, // Aria Form, Pirouette Form
(int)Genesect => 5, // Normal, Electric, Fire, Ice, Water
(int)Flabébé => 5, // Red Flower, Yellow Flower, Orange Flower, Blue Flower, White Flower
(int)Floette => 6, // Red Flower, Yellow Flower, Orange Flower, Blue Flower, White Flower
(int)Florges => 5, // Red Flower, Yellow Flower, Orange Flower, Blue Flower, White Flower
//(int)Aegislash => 2, // Sword Form, Shield Form
(int)Sliggoo => 2,// Standard Form, Hisuian Form
(int)Goodra => 2,// Standard Form, Hisuian Form
(int)Avalugg => 2,// Standard Form, Hisuian Form
(int)Zygarde => 4,// (0,1,2,3) can be out-of-battle, Zygarde Complete (4) is a battle form
(int)Decidueye => 2,// Standard Form, Hisuian Form
(int)Oricorio => 4, // Baile Style, Pom-Pom Style, Pa'u Style, Sensu Style
(int)Lycanroc => 3, // Midday Form, Midnight Form, Dusk Form
//(int)Wishiwashi => 2, // Solo Form, School Form
(int)Silvally => 18, // Form for each type
(int)Minior => 14, // (0-7) Meteor Forms, 7-14 are Core Forms
(int)Mimikyu => 2, // Standard Form (0) && Totem disguise Mimikyu (2)
(int)Necrozma => 3, // Standard Form, Dusk Mane Necrozma, Dawn Wings Necrozma
(int)Toxtricity => 2, // Amped Form, Low Key Form
(int)Urshifu => 2, // Single Strike Style, Rapid Strike Style
(int)Calyrex => 3, // Standard Form, Ice Rider, Shadow Rider
(int)Enamorus => 2, // Incarnate Form, Therian Form
_ => 1,
};
}
/// <summary>
/// Reverts the Battle Form to the form it would have outside of Battle.
/// </summary>
@ -225,6 +372,11 @@ public static bool IsTotemForm(ushort species, byte form)
return form == 1;
}
public static bool HasTotemForm(ushort species)
{
return Legal.Totem_USUM.Contains(species);
}
/// <summary>
/// Gets the base <see cref="form"/> for the <see cref="species"/> when the Totem form is reverted (on transfer).
/// </summary>

View File

@ -20,6 +20,8 @@ public sealed class PersonalInfo8SWSH : IPersonalInfoSWSH
public PersonalInfo8SWSH(byte[] data)
{
Data = data;
DexIndexNational = ModelID;
TMHM = new bool[200];
for (var i = 0; i < CountTR; i++)
{
@ -97,24 +99,29 @@ public byte[] Write()
public int Height { get => ReadUInt16LittleEndian(Data.AsSpan(0x24)); set => WriteUInt16LittleEndian(Data.AsSpan(0x24), (ushort)value); }
public int Weight { get => ReadUInt16LittleEndian(Data.AsSpan(0x26)); set => WriteUInt16LittleEndian(Data.AsSpan(0x26), (ushort)value); }
public ushort Species { get => ReadUInt16LittleEndian(Data.AsSpan(0x4C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x4C), value); }
public ushort ModelID { get => (ushort)ReadUInt32LittleEndian(Data.AsSpan(0x4C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x4C), value); } // Model ID
public ushort HatchedSpecies { get => ReadUInt16LittleEndian(Data.AsSpan(0x56)); set => WriteUInt16LittleEndian(Data.AsSpan(0x56), value); }
public ushort LocalFormIndex { get => ReadUInt16LittleEndian(Data.AsSpan(0x58)); set => WriteUInt16LittleEndian(Data.AsSpan(0x58), value); } // local region base form
public ushort RegionalFlags { get => ReadUInt16LittleEndian(Data.AsSpan(0x5A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x5A), value); }
public bool IsRegionalForm { get => (RegionalFlags & 1) == 1; set => RegionalFlags = (ushort)((RegionalFlags & 0xFFFE) | (value ? 1 : 0)); }
public bool CanNotDynamax { get => ((Data[0x5A] >> 2) & 1) == 1; set => Data[0x5A] = (byte)((Data[0x5A] & ~4) | (value ? 4 : 0)); }
public ushort PokeDexIndex { get => ReadUInt16LittleEndian(Data.AsSpan(0x5C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x5C), value); }
public byte RegionalFormIndex { get => (byte)ReadUInt16LittleEndian(Data.AsSpan(0x5E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x5E), value); } // form index of this entry
public ushort DexIndexRegional { get => ReadUInt16LittleEndian(Data.AsSpan(0x5C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x5C), value); }
public ushort Form { get => (byte)ReadUInt16LittleEndian(Data.AsSpan(0x5E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x5E), value); } // form index of this entry
// 0xA8-0xAB are armor type tutors, one bit for each type
public ushort ArmorDexIndex { get => ReadUInt16LittleEndian(Data.AsSpan(0xAC)); set => WriteUInt16LittleEndian(Data.AsSpan(0xAC), value); }
public ushort CrownDexIndex { get => ReadUInt16LittleEndian(Data.AsSpan(0xAE)); set => WriteUInt16LittleEndian(Data.AsSpan(0xAE), value); }
public ushort DexIndexNational { get; set; }
/// <summary>
/// Gets the Form that any offspring will hatch with, assuming it is holding an Everstone.
/// </summary>
public byte HatchFormIndexEverstone => IsRegionalForm ? RegionalFormIndex : (byte)LocalFormIndex;
public byte HatchFormIndexEverstone => IsRegionalForm ? (byte)Form : (byte)LocalFormIndex;
/// <summary>
/// Checks if the entry shows up in any of the built-in Pokédex.
/// </summary>
public bool IsInDex => PokeDexIndex != 0 || ArmorDexIndex != 0 || CrownDexIndex != 0;
public bool IsInDex => DexIndexRegional != 0 || ArmorDexIndex != 0 || CrownDexIndex != 0;
}

View File

@ -97,4 +97,10 @@ public static void GetSortedStatIndexes(this IBaseStat pi, Span<(int Index, int
}
}
}
public static void SetIBaseStats(this IBaseStat self, IBaseStat other)
{
for (int j = 0; j < other.GetNumBaseStats(); ++j)
self.SetBaseStatValue(j, other.GetBaseStatValue(j));
}
}

View File

@ -75,4 +75,10 @@ public static void SetEVYieldValue(this IEffortValueYield stats, int index, int
/// Gets the total number of base stats available.
/// </summary>
public static int GetNumEVs(this IEffortValueYield _) => 6;
public static void SetIEffortValueYield(this IEffortValueYield self, IEffortValueYield other)
{
for (int j = 0; j < other.GetNumEVs(); ++j)
self.SetEVYieldValue(j, other.GetEVYieldValue(j));
}
}

View File

@ -66,4 +66,10 @@ public static void SetAbilityAtIndex(this IPersonalAbility a, int index, int val
/// </summary>
/// <remarks>Duplicate abilities still count separately.</remarks>
public static int GetNumAbilities(this IPersonalAbility _) => 3;
public static void SetIPersonalAbility(this IPersonalAbility self, IPersonalAbility other)
{
for (int j = 0; j < other.GetNumAbilities(); ++j)
self.SetAbilityAtIndex(j, other.GetAbilityAtIndex(j));
}
}

View File

@ -57,4 +57,26 @@ public static class PersonalEggExtensions
/// <param name="group">Egg group</param>
/// <returns>Egg is present in entry</returns>
public static bool IsEggGroup(this IPersonalEgg pi, int group) => pi.EggGroup1 == group || pi.EggGroup2 == group;
public static void SetIPersonalEgg(this IPersonalEgg self, IPersonalEgg other)
{
self.EggGroup1 = other.EggGroup1;
self.EggGroup2 = other.EggGroup2;
if (self is IPersonalEgg_1 self_1 && other is IPersonalEgg_1 other_1)
{
self_1.HatchCycles = other_1.HatchCycles;
}
if (self is IPersonalEgg_2 self_2 && other is IPersonalEgg_2 other_2)
{
self_2.HatchCycles = other_2.HatchCycles;
self_2.HatchedSpecies = other_2.HatchedSpecies;
}
if (self is IPersonalEgg_3 self_3 && other is IPersonalEgg_3 other_3)
{
self_3.HatchedSpecies = other_3.HatchedSpecies;
}
}
}

View File

@ -68,5 +68,4 @@ public static bool IsFormWithinRange(this IPersonalFormInfo info, byte form)
return true;
return form < info.FormCount;
}
}

View File

@ -63,8 +63,6 @@ public interface IPersonalInfoSWSH : IPersonalInfoBin, IPersonalInfo, IPersonalE
bool IsRegionalForm { get; set; }
ushort RegionalFlags { get; set; }
bool CanNotDynamax { get; set; }
ushort PokeDexIndex { get; set; }
byte RegionalFormIndex { get; set; }
ushort ArmorDexIndex { get; set; }
ushort CrownDexIndex { get; set; }
}
@ -75,4 +73,29 @@ public interface IPersonalInfoPLA : IPersonalInfo, IPersonalEgg_3, IMovesInfo_3,
ushort Field_46 { get; set; } // ushort
byte Field_47 { get; set; } // byte
}
public static class IPersonalInfoExt
{
public static void SetPersonalInfo(this IPersonalInfo self, IPersonalInfo other)
{
self.SetIBaseStats(other);
self.SetIEffortValueYield(other);
self.SetIPersonalAbility(other);
self.SetIPersonalItems(other);
self.SetIPersonalType(other);
self.SetIPersonalEgg(other);
self.SetIPersonalTraits(other);
self.SetIPersonalMisc(other);
if (self is IPersonalInfo_1 self_1 && other is IPersonalInfo_1 other_1)
{
self_1.SetIMovesInfo(other_1);
}
if (self is IPersonalInfo_2 self_2 && other is IPersonalInfo_2 other_2)
{
self_2.SetIMovesInfo(other_2);
}
}
}
}

View File

@ -62,4 +62,10 @@ public static void SetItemAtIndex(this IPersonalItems a, int index, int value)
/// </summary>
/// <remarks>Duplicate items still count separately.</remarks>
public static int GetNumItems(this IPersonalItems _) => 3;
public static void SetIPersonalItems(this IPersonalItems self, IPersonalItems other)
{
for (int j = 0; j < other.GetNumItems(); ++j)
self.SetItemAtIndex(j, other.GetItemAtIndex(j));
}
}

View File

@ -16,19 +16,34 @@ public interface IPersonalMisc
/// </summary>
public interface IPersonalMisc_1 : IPersonalMisc
{
ushort Species { get; set; }
ushort ModelID { get; set; }
ushort Form { get; set; }
bool IsPresentInGame { get; set; }
ushort LocalFormIndex { get; set; }
ushort DexIndexNational { get; set; } // ushort
ushort DexIndexRegional { get; set; } // ushort
}
public interface IPersonalMisc_2 : IPersonalMisc_1
{
ushort Form { get; set; }
ushort DexIndexNational { get; set; } // ushort
ushort DexIndexRegional { get; set; } // ushort
ushort DexIndexLocal1 { get; set; } // uint
ushort DexIndexLocal2 { get; set; } // uint
ushort DexIndexLocal3 { get; set; } // uint
ushort DexIndexLocal4 { get; set; } // uint
ushort DexIndexLocal5 { get; set; } // uint
}
public static class IPersonalMiscExtensions
{
public static void SetIPersonalMisc(this IPersonalMisc self, IPersonalMisc other)
{
self.EvoStage = other.EvoStage;
if (self is IPersonalMisc_1 self_1 && other is IPersonalMisc_1 other_1)
{
self_1.DexIndexNational = other_1.DexIndexNational;
self_1.Form = other_1.Form;
}
}
}

View File

@ -90,4 +90,18 @@ public static int RandomGender(this IPersonalTraits info)
var fix = info.GetFixedGenderType();
return fix >= 0 ? (int)fix : Util.Rand.Next(2);
}
public static void SetIPersonalTraits(this IPersonalTraits self, IPersonalTraits other)
{
self.Gender = other.Gender;
self.EXPGrowth = other.EXPGrowth;
self.BaseEXP = other.BaseEXP;
self.CatchRate = other.CatchRate;
self.EscapeRate = other.EscapeRate;
self.BaseFriendship = other.BaseFriendship;
self.EscapeRate = other.EscapeRate;
self.Color = other.Color;
self.Height = other.Height;
self.Weight = other.Weight;
}
}

View File

@ -51,4 +51,10 @@ public static bool IsType(this IPersonalType detail, Types type1, Types type2)
/// <param name="type2">Second type</param>
/// <returns>Typing is an exact match</returns>
public static bool IsValidTypeCombination(this IPersonalType detail, Types type1, Types type2) => detail.Type1 == type1 && detail.Type2 == type2;
public static void SetIPersonalType(this IPersonalType self, IPersonalType other)
{
self.Type1 = other.Type1;
self.Type2 = other.Type2;
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.Diagnostics;
namespace pkNX.Structures;
@ -77,4 +78,77 @@ public bool IsPresentInGame(ushort species, byte form)
IPersonalInfo IPersonalTable.this[int index] => this[index];
IPersonalInfo IPersonalTable.this[ushort species, byte form] => this[species, form];
IPersonalInfo IPersonalTable.GetFormEntry(ushort species, byte form) => GetFormEntry(species, form);
public void FixMissingData()
{
// Fix all base forms
int sFormCount = 0;
for (ushort i = 1; i <= Legal.MaxSpeciesID_7_USUM; i++)
{
var s = Table[i];
s.DexIndexNational = i;
var u = ResourcesUtil.USUM.Table[i];
if (s.FormCount == 0)
{
s.SetPersonalInfo(u);
s.FormCount = Math.Max((byte)1, FormInfo.GetOutOfBattleFormCount(i, u.FormCount, 7));
}
if (s.FormCount == 1)
continue;
if (s.FormStatsIndex != 0)
Debug.Assert(s.FormStatsIndex == (MaxSpeciesID + 1) + sFormCount);
s.FormStatsIndex = (MaxSpeciesID + 1) + sFormCount;
sFormCount += s.FormCount - 1;
for (byte f = 1; f < s.FormCount; f++)
{
var formS = Table[s.FormStatsIndex + (f - 1)];
if (formS.FormCount == 0)
{
// Check if USUM table has form data for this entry
if (f < u.FormCount)
{
if (u.FormCount <= s.FormCount || (FormInfo.HasTotemForm(i) && !FormInfo.IsTotemForm(i, f)))
{
var formU = ResourcesUtil.USUM.GetFormEntry(i, f);
formS.SetPersonalInfo(formU);
}
}
else
{
// No form data was found, just write the base form data
formS.SetPersonalInfo(s);
}
formS.FormCount = s.FormCount;
formS.FormStatsIndex = s.FormStatsIndex;
}
formS.DexIndexNational = i;
formS.Form = f;
}
}
// Fix form number
for (ushort i = Legal.MaxSpeciesID_7_USUM; i <= MaxSpeciesID; i++)
{
var s = Table[i];
s.DexIndexNational = i;
for (byte f = 1; f < s.FormCount; f++)
{
var formS = Table[s.FormStatsIndex + (f - 1)];
formS.DexIndexNational = i;
formS.Form = f;
}
}
Debug.WriteLine("Auto fix for SWSH data succeded");
}
}

View File

@ -30,6 +30,11 @@ public class ResourcesUtil
/// </summary>
public static readonly IReadOnlyList<EvolutionMethod[]> USUM_Evolutions = EvolutionSet7.GetArray(GetReader("uu"));
static ResourcesUtil()
{
SWSH.FixMissingData();
}
private static ReadOnlySpan<byte> GetTableBinary(string game) => GetBinaryResource($"personal_{game}");
private static ReadOnlySpan<byte> GetEvolutionBinary(string game) => GetBinaryResource($"evos_{game}.pkl");
private static BinLinkerAccessor GetReader(string resource) => BinLinkerAccessor.Get(GetEvolutionBinary(resource), resource);

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
@ -58,6 +59,8 @@ public static T[] GetArray<T>(this ReadOnlySpan<byte> entries, FromBytesConstruc
if (entries.Length < size)
return Array.Empty<T>();
Debug.Assert(entries.Length % size == 0, "This data can't be split into equally sized entries with the provided slice size");
var array = new T[entries.Length / size];
for (int i = 0; i < entries.Length; i += size)
{

View File

@ -108,7 +108,7 @@ private void DumpMoveUsers(IPersonalTable pt, Learnset8a lr)
if (isShop)
{
var species = pt.Table.OfType<IPersonalInfoPLA>().Where(z => z.SpecialTutors[0][shopIndex] && z.IsPresentInGame);
var names = species.Select(z => $"{spec[z.Species]}{(z.Form == 0 ? "" : $"-{z.Form}")}");
var names = species.Select(z => $"{spec[z.ModelID]}{(z.Form == 0 ? "" : $"-{z.Form}")}");
r.Add($"\tTutors: {string.Join(", ", names)}");
}
@ -893,7 +893,7 @@ private void DumpDexSummarySpecies()
{
var p = (IPersonalInfoPLA)pt[i];
bool any = false;
var specForm = $"{p.Species:000}\t{p.Form}\t{s[p.Species]}{(p.Form == 0 ? "" : $"-{p.Form:00}")}";
var specForm = $"{p.ModelID:000}\t{p.Form}\t{s[p.ModelID]}{(p.Form == 0 ? "" : $"-{p.Form:00}")}";
if (p.DexIndexRegional != 0)
{
dex.Add($"{p.DexIndexRegional:000}\t{specForm}");

View File

@ -464,9 +464,9 @@ public void DumpGalarDex()
{
var p = (IPersonalInfoSWSH)pt[i];
bool any = false;
if (p.PokeDexIndex != 0)
if (p.DexIndexRegional != 0)
{
galar.Add($"{p.PokeDexIndex:000} - [{i:000]} - {s[i]}");
galar.Add($"{p.DexIndexRegional:000} - [{i:000]} - {s[i]}");
any = true;
}
if (p.ArmorDexIndex != 0)

View File

@ -276,12 +276,12 @@ public int[] GetSpeciesBanlist()
{
if (pi.IsPresentInGame)
{
banned.Remove(pi.Species);
hasForm.Add(pi.Species);
banned.Remove(pi.ModelID);
hasForm.Add(pi.ModelID);
}
else if (!hasForm.Contains(pi.Species))
else if (!hasForm.Contains(pi.ModelID))
{
banned.Add(pi.Species);
banned.Add(pi.ModelID);
}
}
return banned.ToArray();
@ -291,8 +291,8 @@ public int GetRandomForm(int spec)
{
var pt = Data.PersonalData;
var formRand = pt.Table.Cast<IPersonalMisc_1>()
.Where(z => z.IsPresentInGame && !(Legal.BattleExclusiveForms.Contains(z.Species) || Legal.BattleFusions.Contains(z.Species)))
.GroupBy(z => z.Species)
.Where(z => z.IsPresentInGame && !(Legal.BattleExclusiveForms.Contains(z.ModelID) || Legal.BattleFusions.Contains(z.ModelID)))
.GroupBy(z => z.ModelID)
.ToDictionary(z => z.Key, z => z.ToList());
if (!formRand.TryGetValue((ushort)spec, out var entries))

View File

@ -85,12 +85,12 @@ private void RandomizeArea(ResidentArea8a area, SpeciesSettings settings)
{
if (pi.IsPresentInGame)
{
banned.Remove(pi.Species);
hasForm.Add(pi.Species);
banned.Remove(pi.ModelID);
hasForm.Add(pi.ModelID);
}
else if (!hasForm.Contains(pi.Species))
else if (!hasForm.Contains(pi.ModelID))
{
banned.Add(pi.Species);
banned.Add(pi.ModelID);
}
}
@ -98,8 +98,8 @@ private void RandomizeArea(ResidentArea8a area, SpeciesSettings settings)
rand.Initialize(settings, banned.ToArray());
var formRand = pt.Table.Cast<IPersonalMisc_1>()
.Where(z => z.IsPresentInGame && !(Legal.BattleExclusiveForms.Contains(z.Species) || Legal.BattleFusions.Contains(z.Species)))
.GroupBy(z => z.Species)
.Where(z => z.IsPresentInGame && !(Legal.BattleExclusiveForms.Contains(z.ModelID) || Legal.BattleFusions.Contains(z.ModelID)))
.GroupBy(z => z.ModelID)
.ToDictionary(z => z.Key, z => z.ToList());
var encounters = area.Encounters;

View File

@ -39,7 +39,7 @@ public MapViewer8a(GameManagerPLA rom, GFPack resident)
if (!e.IsPresentInGame)
continue;
var species = e.Species;
var species = e.ModelID;
if (nameList.All(z => z.Value != species))
nameList.Add(new(speciesNames[species], species));
}

View File

@ -315,8 +315,8 @@ public void LoadPersonal(IPersonalInfo pkm)
if (pkm is IPersonalInfoSWSH swsh)
{
L_TM.Text = "TMs/TRs:";
MT_GoID.Text = swsh.Species.ToString("000");
TB_RegionalDex.Text = swsh.PokeDexIndex.ToString("000");
MT_GoID.Text = swsh.ModelID.ToString("000");
TB_RegionalDex.Text = swsh.DexIndexRegional.ToString("000");
TB_ArmorDex.Text = swsh.ArmorDexIndex.ToString("000");
TB_CrownDex.Text = swsh.CrownDexIndex.ToString("000");
CHK_IsPresentInGame.Checked = swsh.IsPresentInGame;
@ -404,7 +404,7 @@ public void SavePersonal()
}
if (pkm is IPersonalInfoSWSH swsh)
{
swsh.PokeDexIndex = Convert.ToUInt16(TB_RegionalDex.Text);
swsh.DexIndexRegional = Convert.ToUInt16(TB_RegionalDex.Text);
swsh.ArmorDexIndex = Convert.ToUInt16(TB_ArmorDex.Text);
swsh.CrownDexIndex = Convert.ToUInt16(TB_CrownDex.Text);
swsh.IsPresentInGame = CHK_IsPresentInGame.Checked;

View File

@ -222,7 +222,7 @@ public void LoadPersonal(IPersonalInfoPLA pkm)
TB_SPDEVs.Text = pkm.EV_SPD.ToString(TB_SPDEVs.Mask);
TB_BST.Text = pkm.GetBaseStatTotal().ToString(TB_BST.Mask);
TB_Classification.Text = classifications[pkm.Species];
TB_Classification.Text = classifications[pkm.ModelID];
CB_Type1.SelectedIndex = (int)pkm.Type1;
CB_Type2.SelectedIndex = (int)pkm.Type2;
@ -310,10 +310,10 @@ public void UpdateGenderDetailLabel()
public void UpdateButtonStates()
{
B_PreviousPokemon.Enabled = cPersonal.Species > 0;
B_PreviousPokemon.Enabled = cPersonal.ModelID > 0;
B_PreviousForm.Enabled = cPersonal.Form > 0;
B_NextForm.Enabled = (cPersonal.Form + 1) < cPersonal.FormCount;
B_NextPokemon.Enabled = (cPersonal.Species + 1) < Data.PersonalData.MaxSpeciesID;
B_NextPokemon.Enabled = (cPersonal.ModelID + 1) < Data.PersonalData.MaxSpeciesID;
}
public void SavePersonal()
@ -471,6 +471,93 @@ public void SaveEvolutions()
row.SaveEvolution();
}
public void AutoFillPersonal()
{
Debug.Assert(Data.PersonalData is PersonalTable8LA, "This function is build for PLA data. It needs to be updated if more data is added.");
var la = (PersonalTable8LA)Data.PersonalData;
la.FixMissingData();
}
public void AutoFillEvolutions()
{
var usum = ResourcesUtil.USUM_Evolutions;
var usumPersonal = ResourcesUtil.SWSH;
var swsh = ResourcesUtil.SWSH_Evolutions;
var swshPersonal = ResourcesUtil.SWSH;
for (int i = 0; i < usum.Count; i++)
{
EvolutionMethod[] evoSet = swsh[i];
EvolutionMethod[] usumEvos = usum[i];
if (evoSet[0].Method == EvolutionType.None && usumEvos[0].Method != EvolutionType.None)
{
for (int j = 0; j < usumEvos.Length; j++)
{
if (usumEvos[j].Method == EvolutionType.None)
{
continue;
}
var usumEntry = usumEvos[j];
var evoEntry = evoSet[j];
evoEntry.Species = usumEntry.Species;
evoEntry.Form = usumEntry.Form;
evoEntry.Argument = usumEntry.Argument;
evoEntry.Method = usumEntry.Method;
evoEntry.Level = usumEntry.Level;
}
}
}
var la = Data.EvolutionData;
for (int i = 0; i < la.Length; i++)
{
EvolutionSet8a evoSet = la[i];
if (evoSet.Table == null || evoSet.Table.Length == 0)
{
var species = evoSet.Species;
var form = evoSet.Form;
if (species > Legal.MaxSpeciesID_8)
{
continue;
}
int index = swshPersonal.GetFormIndex(species, (byte)form);
if (index == 0)
{
// Assume the form doesn't exsist in the game
continue;
}
var swshEvos = swsh[index];
List<EvolutionEntry8a> entries = new();
for (int j = 0; j < swshEvos.Length; j++)
{
var swhsEntry = swshEvos[j];
if (swhsEntry.Method == EvolutionType.None)
{
continue;
}
entries.Add(new EvolutionEntry8a
{
Species = swhsEntry.Species,
Form = swhsEntry.Form,
Argument = swhsEntry.Argument,
Method = (ushort)swhsEntry.Method,
Level = swhsEntry.Level
});
}
evoSet.Table = entries.ToArray();
}
}
}
private void B_PDumpTable_Click(object sender, EventArgs e)
{
@ -604,75 +691,11 @@ private void B_LearnMetronome_Click(object sender, EventArgs e)
private void B_AufoFill_Click(object sender, EventArgs e)
{
Debug.Assert(Data.PersonalData is PersonalTable8LA, "This function is build for PLA data. It needs to be updated if more data is added.");
// Make sure any modifications are saved before forcing to reload everything
SaveCurrent();
var swsh = ResourcesUtil.SWSH;
var usum = ResourcesUtil.USUM;
// Fix gender for ss data
for (ushort i = 1; i <= usum.MaxSpeciesID; i++)
{
var ss = swsh.Table[i];
if (ss.HP == 0)
ss.Gender = usum.Table[i].Gender;
}
// Fill all data for la
var la = (PersonalTable8LA)Data.PersonalData;
for (ushort i = 1; i <= swsh.MaxSpeciesID; i++)
{
var fc = la.Table[i].FormCount;
for (byte f = 0; f < fc; f++)
{
var l = la.GetFormEntry(i, f);
if (l == null || l.HP != 0)
continue;
var s = swsh.GetFormEntry(i, f);
//IBaseStat
for (int j = 0; j < s.GetNumBaseStats(); ++j)
l.SetBaseStatValue(j, s.GetBaseStatValue(j));
//IEffortValueYield
for (int j = 0; j < s.GetNumEVs(); ++j)
l.SetEVYieldValue(j, s.GetEVYieldValue(j));
//IPersonalAbility
for (int j = 0; j < s.GetNumAbilities(); ++j)
l.SetAbilityAtIndex(j, s.GetAbilityAtIndex(j));
//IPersonalItems
for (int j = 0; j < s.GetNumItems(); ++j)
l.SetItemAtIndex(j, s.GetItemAtIndex(j));
//IPersonalType
l.Type1 = s.Type1;
l.Type2 = s.Type2;
//IPersonalEgg_2
l.EggGroup1 = s.EggGroup1;
l.EggGroup2 = s.EggGroup2;
//l.HatchCycles = s.HatchCycles;
l.HatchedSpecies = s.HatchedSpecies;
//IPersonalTraits
l.Gender = s.Gender;
l.EXPGrowth = s.EXPGrowth;
l.BaseEXP = s.BaseEXP;
l.CatchRate = s.CatchRate;
l.BaseFriendship = s.BaseFriendship;
l.EscapeRate = s.EscapeRate;
l.Color = s.Color;
l.Height = s.Height;
l.Weight = s.Weight;
//IPersonalMisc_1
//l.Species = i;
l.DexIndexNational = i;
l.EvoStage = s.EvoStage;
}
}
AutoFillPersonal();
AutoFillEvolutions();
// Reload selected
LoadIndex(CB_Species.SelectedIndex);
@ -681,8 +704,8 @@ private void B_AufoFill_Click(object sender, EventArgs e)
private void B_NextPokemon_Click(object sender, EventArgs e)
{
Debug.Assert(cPersonal.Species < Data.PersonalData.MaxSpeciesID);
CB_Species.SelectedIndex = cPersonal.Species + 1;
Debug.Assert(cPersonal.ModelID < Data.PersonalData.MaxSpeciesID);
CB_Species.SelectedIndex = cPersonal.ModelID + 1;
}
private void B_NextForm_Click(object sender, EventArgs e)
@ -690,7 +713,7 @@ private void B_NextForm_Click(object sender, EventArgs e)
Debug.Assert(cPersonal.Form < cPersonal.FormCount);
var pt = Data.PersonalData;
CB_Species.SelectedIndex = pt.GetFormIndex(cPersonal.Species, (byte)(cPersonal.Form + 1));
CB_Species.SelectedIndex = pt.GetFormIndex(cPersonal.ModelID, (byte)(cPersonal.Form + 1));
}
private void B_PreviousForm_Click(object sender, EventArgs e)
@ -698,13 +721,13 @@ private void B_PreviousForm_Click(object sender, EventArgs e)
Debug.Assert(cPersonal.Form > 0);
var pt = Data.PersonalData;
CB_Species.SelectedIndex = pt.GetFormIndex(cPersonal.Species, (byte)(cPersonal.Form - 1));
CB_Species.SelectedIndex = pt.GetFormIndex(cPersonal.ModelID, (byte)(cPersonal.Form - 1));
}
private void B_PreviousPokemon_Click(object sender, EventArgs e)
{
Debug.Assert(cPersonal.Species > 0);
CB_Species.SelectedIndex = cPersonal.Species - 1;
Debug.Assert(cPersonal.ModelID > 0);
CB_Species.SelectedIndex = cPersonal.ModelID - 1;
}
private void B_Save_Click(object sender, EventArgs e)