mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-10 06:34:19 +00:00
Break up PKX into separate classes
Many years ago, PKX used to be a >4,000 line bloated file, which spun off multiple classes like CommonEdits and most of the early non-GUI PKM related logic. Now, it's just a stub to source the latest generation & personal table. Separate files = more concise info, and more room to grow to do more advanced things. Makes the IsPresent methods public (no longer internal).
This commit is contained in:
parent
124bbf98ad
commit
a57f40ae7d
37 changed files with 465 additions and 353 deletions
|
@ -57,7 +57,7 @@ namespace PKHeX.Core
|
|||
case PersonalInfo.RatioMagicMale: return 0;
|
||||
}
|
||||
if (!pk.IsGenderValid())
|
||||
return PKX.GetGenderFromPIDAndRatio(pk.PID, gt);
|
||||
return EntityGender.GetFromPIDAndRatio(pk.PID, gt);
|
||||
return pk.Gender;
|
||||
}
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ namespace PKHeX.Core
|
|||
if (pk is PK5 pk5 && index == 2)
|
||||
pk5.HiddenAbility = true;
|
||||
else if (pk.Format <= 5)
|
||||
pk.PID = PKX.GetRandomPID(Util.Rand, pk.Species, pk.Gender, pk.Version, pk.Nature, pk.Form, (uint)(index * 0x10001));
|
||||
pk.PID = EntityPID.GetRandomPID(Util.Rand, pk.Species, pk.Gender, pk.Version, pk.Nature, pk.Form, (uint)(index * 0x10001));
|
||||
pk.RefreshAbility(index);
|
||||
}
|
||||
|
||||
|
|
112
PKHeX.Core/Editing/NatureAmp.cs
Normal file
112
PKHeX.Core/Editing/NatureAmp.cs
Normal file
|
@ -0,0 +1,112 @@
|
|||
using System;
|
||||
using static PKHeX.Core.NatureAmpRequest;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Logic for mutating a nature to amplify certain stats.
|
||||
/// </summary>
|
||||
public static class NatureAmp
|
||||
{
|
||||
/// <summary>
|
||||
/// Mutate the nature amp indexes to match the request
|
||||
/// </summary>
|
||||
/// <param name="type">Request type to modify the provided <see cref="statIndex"/></param>
|
||||
/// <param name="statIndex">Stat Index to mutate</param>
|
||||
/// <param name="currentNature">Current nature to derive the current amps from</param>
|
||||
/// <returns>New nature value</returns>
|
||||
public static int GetNewNature(this NatureAmpRequest type, int statIndex, int currentNature)
|
||||
{
|
||||
if (currentNature > (int)Nature.Quirky)
|
||||
return -1;
|
||||
|
||||
var (up, dn) = GetNatureModification(currentNature);
|
||||
|
||||
return GetNewNature(type, statIndex, up, dn);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GetNewNature(PKHeX.Core.NatureAmpRequest,int,int)"/>
|
||||
public static int GetNewNature(NatureAmpRequest type, int statIndex, int up, int dn)
|
||||
{
|
||||
//
|
||||
switch (type)
|
||||
{
|
||||
case Increase when up != statIndex:
|
||||
up = statIndex;
|
||||
break;
|
||||
case Decrease when dn != statIndex:
|
||||
dn = statIndex;
|
||||
break;
|
||||
case Neutral when up != statIndex && dn != statIndex:
|
||||
up = dn = statIndex;
|
||||
break;
|
||||
default:
|
||||
return -1; // failure
|
||||
}
|
||||
|
||||
return CreateNatureFromAmps(up, dn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recombine the stat amps into a nature value.
|
||||
/// </summary>
|
||||
/// <param name="up">Increased stat</param>
|
||||
/// <param name="dn">Decreased stat</param>
|
||||
/// <returns>Nature</returns>
|
||||
public static int CreateNatureFromAmps(int up, int dn)
|
||||
{
|
||||
if ((uint)up > 5 || (uint)dn > 5)
|
||||
return -1;
|
||||
return (up * 5) + dn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompose the nature to the two stat indexes that are modified
|
||||
/// </summary>
|
||||
public static (int up, int dn) GetNatureModification(int nature)
|
||||
{
|
||||
var up = (nature / 5) + 1;
|
||||
var dn = (nature % 5) + 1;
|
||||
return (up, dn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the nature is out of range or the stat amplifications are not neutral.
|
||||
/// </summary>
|
||||
/// <param name="nature">Nature</param>
|
||||
/// <param name="up">Increased stat</param>
|
||||
/// <param name="dn">Decreased stat</param>
|
||||
/// <returns>True if nature modification values are equal or the Nature is out of range.</returns>
|
||||
public static bool IsNeutralOrInvalid(int nature, int up, int dn)
|
||||
{
|
||||
return up == dn || nature >= 25; // invalid
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IsNeutralOrInvalid(int, int, int)"/>
|
||||
public static bool IsNeutralOrInvalid(int nature)
|
||||
{
|
||||
var (up, dn) = GetNatureModification(nature);
|
||||
return IsNeutralOrInvalid(nature, up, dn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates stats according to the specified nature.
|
||||
/// </summary>
|
||||
/// <param name="stats">Current stats to amplify if appropriate</param>
|
||||
/// <param name="nature">Nature</param>
|
||||
public static void ModifyStatsForNature(Span<ushort> stats, int nature)
|
||||
{
|
||||
var (up, dn) = GetNatureModification(nature);
|
||||
if (IsNeutralOrInvalid(nature, up, dn))
|
||||
return;
|
||||
stats[up] *= 11; stats[up] /= 10;
|
||||
stats[dn] *= 9; stats[dn] /= 10;
|
||||
}
|
||||
}
|
||||
|
||||
public enum NatureAmpRequest
|
||||
{
|
||||
Neutral,
|
||||
Increase,
|
||||
Decrease,
|
||||
}
|
|
@ -23,7 +23,7 @@ namespace PKHeX.Core
|
|||
public static void AddFromLocalFile(string file, ConcurrentBag<SlotCache> db, ITrainerInfo dest, ICollection<string> validExtensions)
|
||||
{
|
||||
var fi = new FileInfo(file);
|
||||
if (!validExtensions.Contains(fi.Extension) || !PKX.IsPKM(fi.Length))
|
||||
if (!validExtensions.Contains(fi.Extension) || !EntityDetection.IsSizePlausible(fi.Length))
|
||||
return;
|
||||
|
||||
var data = File.ReadAllBytes(file);
|
||||
|
@ -98,7 +98,7 @@ namespace PKHeX.Core
|
|||
public static void AddFromLocalFile(string file, ICollection<SlotCache> db, ITrainerInfo dest, ICollection<string> validExtensions)
|
||||
{
|
||||
var fi = new FileInfo(file);
|
||||
if (!validExtensions.Contains(fi.Extension) || !PKX.IsPKM(fi.Length))
|
||||
if (!validExtensions.Contains(fi.Extension) || !EntityDetection.IsSizePlausible(fi.Length))
|
||||
return;
|
||||
|
||||
var data = File.ReadAllBytes(file);
|
||||
|
|
|
@ -272,7 +272,7 @@ namespace PKHeX.Core
|
|||
return true;
|
||||
|
||||
if (Species == (int) Core.Species.Azurill && Generation == 4 && Location == 233 && pkm.Gender == 0)
|
||||
return PKX.GetGenderFromPIDAndRatio(pkm.PID, 0xBF) == 1;
|
||||
return EntityGender.GetFromPIDAndRatio(pkm.PID, 0xBF) == 1;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
protected override void SetPINGA(PKM pk, EncounterCriteria criteria)
|
||||
{
|
||||
int gender = criteria.GetGender(PKX.GetGenderFromPID(Species, PID), pk.PersonalInfo);
|
||||
int gender = criteria.GetGender(EntityGender.GetFromPID(Species, PID), pk.PersonalInfo);
|
||||
int nature = (int)Nature;
|
||||
int ability = Ability.IsSingleValue(out var index) ? index : 0;
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ namespace PKHeX.Core
|
|||
protected override void SetPINGA(PKM pk, EncounterCriteria criteria)
|
||||
{
|
||||
var pi = pk.PersonalInfo;
|
||||
int gender = criteria.GetGender(PKX.GetGenderFromPID(Species, PID), pi);
|
||||
int gender = criteria.GetGender(EntityGender.GetFromPID(Species, PID), pi);
|
||||
int nature = (int)criteria.GetNature(Nature);
|
||||
int ability = criteria.GetAbilityFromNumber(Ability);
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
protected override void SetPINGA(PKM pk, EncounterCriteria criteria)
|
||||
{
|
||||
var pi = pk.PersonalInfo;
|
||||
int gender = criteria.GetGender(PKX.GetGenderFromPID(Species, PID), pi);
|
||||
int gender = criteria.GetGender(EntityGender.GetFromPID(Species, PID), pi);
|
||||
int nature = (int)criteria.GetNature(Nature);
|
||||
int ability = criteria.GetAbilityFromNumber(Ability);
|
||||
|
||||
|
|
|
@ -82,7 +82,7 @@ namespace PKHeX.Core
|
|||
var pid = a << 16 | b;
|
||||
|
||||
// Process Conditions
|
||||
if (PKX.GetUnownForm(pid) == form) // matches form, does it match nature?
|
||||
if (EntityPID.GetUnownForm3(pid) == form) // matches form, does it match nature?
|
||||
{
|
||||
switch (VerifyPIDCriteria(pid, info))
|
||||
{
|
||||
|
|
|
@ -530,7 +530,7 @@ namespace PKHeX.Core
|
|||
return false;
|
||||
|
||||
const int AzurillGenderRatio = 0xBF;
|
||||
var gender = PKX.GetGenderFromPIDAndRatio(pk.PID, AzurillGenderRatio);
|
||||
var gender = EntityGender.GetFromPIDAndRatio(pk.PID, AzurillGenderRatio);
|
||||
if (gender != 1)
|
||||
return false;
|
||||
|
||||
|
@ -916,7 +916,7 @@ namespace PKHeX.Core
|
|||
private static (int Species, int Gender) GetCuteCharmGenderSpecies(PKM pk, uint pid, int currentSpecies) => currentSpecies switch
|
||||
{
|
||||
// Nincada evo chain travels from M/F -> Genderless Shedinja
|
||||
(int)Species.Shedinja => ((int)Species.Nincada, PKX.GetGenderFromPID((int)Species.Nincada, pid)),
|
||||
(int)Species.Shedinja => ((int)Species.Nincada, EntityGender.GetFromPID((int)Species.Nincada, pid)),
|
||||
|
||||
// These evolved species cannot be encountered with cute charm.
|
||||
// 100% fixed gender does not modify PID; override this with the encounter species for correct calculation.
|
||||
|
|
|
@ -69,14 +69,14 @@ namespace PKHeX.Core
|
|||
var current = pkm.Gender;
|
||||
if (current == 2) // shedinja, genderless
|
||||
return true;
|
||||
var gender = PKX.GetGenderFromPID(original, pkm.EncryptionConstant);
|
||||
var gender = EntityGender.GetFromPID(original, pkm.EncryptionConstant);
|
||||
return gender == current;
|
||||
}
|
||||
|
||||
private static bool IsValidGenderMismatch(PKM pkm) => pkm.Species switch
|
||||
{
|
||||
// Shedinja evolution gender glitch, should match original Gender
|
||||
(int) Species.Shedinja when pkm.Format == 4 => pkm.Gender == PKX.GetGenderFromPIDAndRatio(pkm.EncryptionConstant, 0x7F), // 50M-50F
|
||||
(int) Species.Shedinja when pkm.Format == 4 => pkm.Gender == EntityGender.GetFromPIDAndRatio(pkm.EncryptionConstant, 0x7F), // 50M-50F
|
||||
|
||||
// Evolved from Azurill after transferring to keep gender
|
||||
(int) Species.Marill or (int) Species.Azumarill when pkm.Format >= 6 => pkm.Gender == 1 && (pkm.EncryptionConstant & 0xFF) > 0x3F,
|
||||
|
|
|
@ -219,7 +219,7 @@ namespace PKHeX.Core
|
|||
TID = TID,
|
||||
SID = SID,
|
||||
EXP = IsEgg ? Experience.GetEXP(5, PersonalInfo.EXPGrowth) : EXP,
|
||||
Gender = PKX.GetGenderFromPID(Species, PID),
|
||||
Gender = EntityGender.GetFromPID(Species, PID),
|
||||
Form = Form,
|
||||
// IsEgg = false, -- already false
|
||||
OT_Friendship = 70,
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace PKHeX.Core
|
|||
/// <summary>
|
||||
/// Valid file extensions that represent <see cref="PKM"/> data, without the leading '.'
|
||||
/// </summary>
|
||||
public static readonly string[] Extensions = PKX.GetPKMExtensions();
|
||||
public static readonly string[] Extensions = EntityFileExtension.GetExtensions();
|
||||
public abstract int SIZE_PARTY { get; }
|
||||
public abstract int SIZE_STORED { get; }
|
||||
public string Extension => GetType().Name.ToLowerInvariant();
|
||||
|
@ -637,7 +637,7 @@ namespace PKHeX.Core
|
|||
if (gen is not (3 or 4 or 5))
|
||||
return gender == (gender & 1);
|
||||
|
||||
return gender == PKX.GetGenderFromPIDAndRatio(PID, gv);
|
||||
return gender == EntityGender.GetFromPIDAndRatio(PID, gv);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -737,7 +737,7 @@ namespace PKHeX.Core
|
|||
LoadStats(stats, p, level);
|
||||
|
||||
// Account for nature
|
||||
PKX.ModifyStatsForNature(stats, StatNature);
|
||||
NatureAmp.ModifyStatsForNature(stats, StatNature);
|
||||
}
|
||||
|
||||
private void LoadStats(Span<ushort> stats, PersonalInfo p, IHyperTrain t, int level)
|
||||
|
@ -888,7 +888,7 @@ namespace PKHeX.Core
|
|||
public virtual void SetShiny()
|
||||
{
|
||||
var rnd = Util.Rand;
|
||||
do { PID = PKX.GetRandomPID(rnd, Species, Gender, Version, Nature, Form, PID); }
|
||||
do { PID = EntityPID.GetRandomPID(rnd, Species, Gender, Version, Nature, Form, PID); }
|
||||
while (!IsShiny);
|
||||
if (Format >= 6 && (Gen3 || Gen4 || Gen5))
|
||||
EncryptionConstant = PID;
|
||||
|
@ -923,7 +923,7 @@ namespace PKHeX.Core
|
|||
public void SetPIDGender(int gender)
|
||||
{
|
||||
var rnd = Util.Rand;
|
||||
do PID = PKX.GetRandomPID(rnd, Species, gender, Version, Nature, Form, PID);
|
||||
do PID = EntityPID.GetRandomPID(rnd, Species, gender, Version, Nature, Form, PID);
|
||||
while (IsShiny);
|
||||
if (Format >= 6 && (Gen3 || Gen4 || Gen5))
|
||||
EncryptionConstant = PID;
|
||||
|
@ -939,7 +939,7 @@ namespace PKHeX.Core
|
|||
public void SetPIDNature(int nature)
|
||||
{
|
||||
var rnd = Util.Rand;
|
||||
do PID = PKX.GetRandomPID(rnd, Species, Gender, Version, nature, Form, PID);
|
||||
do PID = EntityPID.GetRandomPID(rnd, Species, Gender, Version, nature, Form, PID);
|
||||
while (IsShiny);
|
||||
if (Format >= 6 && (Gen3 || Gen4 || Gen5))
|
||||
EncryptionConstant = PID;
|
||||
|
@ -956,7 +956,7 @@ namespace PKHeX.Core
|
|||
public void SetPIDUnown3(int form)
|
||||
{
|
||||
var rnd = Util.Rand;
|
||||
do PID = rnd.Rand32(); while (PKX.GetUnownForm(PID) != form);
|
||||
do PID = rnd.Rand32(); while (EntityPID.GetUnownForm3(PID) != form);
|
||||
if (Format >= 6 && (Gen3 || Gen4 || Gen5))
|
||||
EncryptionConstant = PID;
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
public sealed override uint EncryptionConstant { get => PID; set { } }
|
||||
public sealed override int Nature { get => (int)(PID % 25); set { } }
|
||||
public sealed override bool IsNicknamed { get => SpeciesName.IsNicknamed(Species, Nickname, Language, 3); set { } }
|
||||
public sealed override int Gender { get => PKX.GetGenderFromPID(Species, PID); set { } }
|
||||
public sealed override int Gender { get => EntityGender.GetFromPID(Species, PID); set { } }
|
||||
public sealed override int Characteristic => -1;
|
||||
public sealed override int CurrentFriendship { get => OT_Friendship; set => OT_Friendship = value; }
|
||||
public sealed override int CurrentHandler { get => 0; set { } }
|
||||
|
@ -39,13 +39,13 @@
|
|||
|
||||
public sealed override int Form
|
||||
{
|
||||
get => Species == (int)Core.Species.Unown ? PKX.GetUnownForm(PID) : 0;
|
||||
get => Species == (int)Core.Species.Unown ? EntityPID.GetUnownForm3(PID) : 0;
|
||||
set
|
||||
{
|
||||
if (Species != (int)Core.Species.Unown)
|
||||
return;
|
||||
var rnd = Util.Rand;
|
||||
while (PKX.GetUnownForm(PID) != value)
|
||||
while (EntityPID.GetUnownForm3(PID) != value)
|
||||
PID = rnd.Rand32();
|
||||
}
|
||||
}
|
||||
|
|
56
PKHeX.Core/PKM/Util/EntityDetection.cs
Normal file
56
PKHeX.Core/PKM/Util/EntityDetection.cs
Normal file
|
@ -0,0 +1,56 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public static class EntityDetection
|
||||
{
|
||||
private static readonly HashSet<int> Sizes = new()
|
||||
{
|
||||
PokeCrypto.SIZE_1JLIST, PokeCrypto.SIZE_1ULIST,
|
||||
PokeCrypto.SIZE_2ULIST, PokeCrypto.SIZE_2JLIST, PokeCrypto.SIZE_2STADIUM,
|
||||
PokeCrypto.SIZE_3STORED, PokeCrypto.SIZE_3PARTY,
|
||||
PokeCrypto.SIZE_3CSTORED, PokeCrypto.SIZE_3XSTORED,
|
||||
PokeCrypto.SIZE_4STORED, PokeCrypto.SIZE_4PARTY,
|
||||
PokeCrypto.SIZE_5PARTY,
|
||||
PokeCrypto.SIZE_6STORED, PokeCrypto.SIZE_6PARTY,
|
||||
PokeCrypto.SIZE_8STORED, PokeCrypto.SIZE_8PARTY,
|
||||
PokeCrypto.SIZE_8ASTORED, PokeCrypto.SIZE_8APARTY,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the given length is valid for a <see cref="PKM"/>.
|
||||
/// </summary>
|
||||
/// <param name="len">Data length of the file/array.</param>
|
||||
/// <returns>A <see cref="bool"/> indicating whether or not the length is valid for a <see cref="PKM"/>.</returns>
|
||||
public static bool IsSizePlausible(long len) => Sizes.Contains((int)len);
|
||||
|
||||
public static bool IsPresentGB(ReadOnlySpan<byte> data) => data[0] != 0; // Species non-zero
|
||||
public static bool IsPresentGC(ReadOnlySpan<byte> data) => ReadUInt16BigEndian(data) != 0; // Species non-zero
|
||||
public static bool IsPresentGBA(ReadOnlySpan<byte> data) => (data[0x13] & 0xFB) == 2; // ignore egg flag, must be FlagHasSpecies.
|
||||
|
||||
public static bool IsPresent(ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (ReadUInt32LittleEndian(data) != 0) // PID
|
||||
return true;
|
||||
ushort species = ReadUInt16LittleEndian(data[8..]);
|
||||
return species != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a function that can check a byte array (at an offset) to see if a <see cref="PKM"/> is possibly present.
|
||||
/// </summary>
|
||||
/// <param name="blank"></param>
|
||||
/// <returns>Function that checks if a byte array (at an offset) has a <see cref="PKM"/> present</returns>
|
||||
public static Func<byte[], bool> GetFuncIsPresent(PKM blank)
|
||||
{
|
||||
if (blank.Format >= 4)
|
||||
return x => IsPresent(x);
|
||||
if (blank.Format <= 2)
|
||||
return x => IsPresentGB(x);
|
||||
if (blank.Data.Length <= PokeCrypto.SIZE_3PARTY)
|
||||
return x => IsPresentGBA(x);
|
||||
return x => IsPresentGC(x);
|
||||
}
|
||||
}
|
67
PKHeX.Core/PKM/Util/EntityFileExtension.cs
Normal file
67
PKHeX.Core/PKM/Util/EntityFileExtension.cs
Normal file
|
@ -0,0 +1,67 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public static class EntityFileExtension
|
||||
{
|
||||
private const string ExtensionPB7 = "pb7";
|
||||
private const string ExtensionPB8 = "pb8";
|
||||
private const string ExtensionPA8 = "pa8";
|
||||
|
||||
public static IReadOnlyList<string> Extensions7b => new[] { ExtensionPB7 };
|
||||
|
||||
/// <summary>
|
||||
/// Gets an array of valid <see cref="PKM"/> file extensions.
|
||||
/// </summary>
|
||||
/// <param name="maxGeneration">Maximum Generation to permit</param>
|
||||
/// <returns>Valid <see cref="PKM"/> file extensions.</returns>
|
||||
public static string[] GetExtensions(int maxGeneration = PKX.Generation)
|
||||
{
|
||||
var result = new List<string>();
|
||||
int min = maxGeneration is <= 2 or >= 7 ? 1 : 3;
|
||||
for (int i = min; i <= maxGeneration; i++)
|
||||
result.Add($"pk{i}");
|
||||
|
||||
if (maxGeneration >= 3)
|
||||
{
|
||||
result.Add("ck3"); // colosseum
|
||||
result.Add("xk3"); // xd
|
||||
}
|
||||
if (maxGeneration >= 4)
|
||||
result.Add("bk4"); // battle revolution
|
||||
if (maxGeneration >= 7)
|
||||
result.Add(ExtensionPB7); // let's go
|
||||
if (maxGeneration >= 8)
|
||||
result.Add(ExtensionPB8); // Brilliant Diamond & Shining Pearl
|
||||
if (maxGeneration >= 8)
|
||||
result.Add(ExtensionPA8); // Legends: Arceus
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Roughly detects the PKM format from the file's extension.
|
||||
/// </summary>
|
||||
/// <param name="ext">File extension.</param>
|
||||
/// <param name="prefer">Preference if not a valid extension, usually the highest acceptable format.</param>
|
||||
/// <returns>Format hint that the file is.</returns>
|
||||
public static int GetFormatFromExtension(string ext, int prefer)
|
||||
{
|
||||
if (string.IsNullOrEmpty(ext))
|
||||
return prefer;
|
||||
return GetFormatFromExtension(ext[^1], prefer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Roughly detects the PKM format from the file's extension.
|
||||
/// </summary>
|
||||
/// <param name="last">Last character of the file's extension.</param>
|
||||
/// <param name="prefer">Preference if not a valid extension, usually the highest acceptable format.</param>
|
||||
/// <returns>Format hint that the file is.</returns>
|
||||
public static int GetFormatFromExtension(char last, int prefer)
|
||||
{
|
||||
if (last is >= '1' and <= '9')
|
||||
return last - '0';
|
||||
return last == 'x' ? 6 : prefer;
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@ public static class EntityFormat
|
|||
/// <returns>An integer indicating the generation of the PKM file, or -1 if the data is invalid.</returns>
|
||||
public static EntityFormatDetected GetFormat(ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (!PKX.IsPKM(data.Length))
|
||||
if (!EntityDetection.IsSizePlausible(data.Length))
|
||||
return None;
|
||||
|
||||
return GetFormatInternal(data);
|
||||
|
|
47
PKHeX.Core/PKM/Util/EntityGender.cs
Normal file
47
PKHeX.Core/PKM/Util/EntityGender.cs
Normal file
|
@ -0,0 +1,47 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
public static class EntityGender
|
||||
{
|
||||
/// <summary>
|
||||
/// Translates a Gender string to Gender integer.
|
||||
/// </summary>
|
||||
/// <param name="s">Gender string</param>
|
||||
/// <returns>Gender integer</returns>
|
||||
public static int GetFromString(string s)
|
||||
{
|
||||
if (s.Length != 1)
|
||||
return 2;
|
||||
return GetFromChar(s[0]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Translates a Gender char to Gender integer.
|
||||
/// </summary>
|
||||
public static int GetFromChar(char c) => c switch
|
||||
{
|
||||
'♂' or 'M' => 0,
|
||||
'♀' or 'F' => 1,
|
||||
_ => 2,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the gender ID of the species based on the Personality ID.
|
||||
/// </summary>
|
||||
/// <param name="species">National Dex ID.</param>
|
||||
/// <param name="pid">Personality ID.</param>
|
||||
/// <returns>Gender ID (0/1/2)</returns>
|
||||
/// <remarks>This method should only be used for Generations 3-5 origin.</remarks>
|
||||
public static int GetFromPID(int species, uint pid)
|
||||
{
|
||||
int gt = PKX.Personal[species].Gender;
|
||||
return GetFromPIDAndRatio(pid, gt);
|
||||
}
|
||||
|
||||
public static int GetFromPIDAndRatio(uint pid, int gr) => gr switch
|
||||
{
|
||||
PersonalInfo.RatioMagicGenderless => 2,
|
||||
PersonalInfo.RatioMagicFemale => 1,
|
||||
PersonalInfo.RatioMagicMale => 0,
|
||||
_ => (pid & 0xFF) < gr ? 1 : 0,
|
||||
};
|
||||
}
|
79
PKHeX.Core/PKM/Util/EntityPID.cs
Normal file
79
PKHeX.Core/PKM/Util/EntityPID.cs
Normal file
|
@ -0,0 +1,79 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public static class EntityPID
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a random PID according to specifications.
|
||||
/// </summary>
|
||||
/// <param name="rnd">RNG to use</param>
|
||||
/// <param name="species">National Dex ID</param>
|
||||
/// <param name="gender">Current Gender</param>
|
||||
/// <param name="origin">Origin Generation</param>
|
||||
/// <param name="nature">Nature</param>
|
||||
/// <param name="form">Form</param>
|
||||
/// <param name="oldPID">Current PID</param>
|
||||
/// <remarks>Used to retain ability bits.</remarks>
|
||||
/// <returns>Rerolled PID.</returns>
|
||||
public static uint GetRandomPID(Random rnd, int species, int gender, int origin, int nature, int form, uint oldPID)
|
||||
{
|
||||
// Gen6+ (and VC) PIDs do not tie PID to Nature/Gender/Ability
|
||||
if (origin >= 24)
|
||||
return rnd.Rand32();
|
||||
|
||||
// Below logic handles Gen3-5.
|
||||
// No need to get form specific entry, as Gen3-5 do not have that feature.
|
||||
int gt = PKX.Personal[species].Gender;
|
||||
bool g34 = origin <= 15;
|
||||
uint abilBitVal = g34 ? oldPID & 0x0000_0001 : oldPID & 0x0001_0000;
|
||||
|
||||
bool g3unown = origin <= 5 && species == (int)Species.Unown;
|
||||
bool singleGender = PersonalInfo.IsSingleGender(gt); // single gender, skip gender check
|
||||
while (true) // Loop until we find a suitable PID
|
||||
{
|
||||
uint pid = rnd.Rand32();
|
||||
|
||||
// Gen 3/4: Nature derived from PID
|
||||
if (g34 && pid % 25 != nature)
|
||||
continue;
|
||||
|
||||
// Gen 3 Unown: Letter/form derived from PID
|
||||
if (g3unown)
|
||||
{
|
||||
var pidLetter = GetUnownForm3(pid);
|
||||
if (pidLetter != form)
|
||||
continue;
|
||||
}
|
||||
else if (g34)
|
||||
{
|
||||
if (abilBitVal != (pid & 0x0000_0001)) // keep ability bits
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (abilBitVal != (pid & 0x0001_0000)) // keep ability bits
|
||||
continue;
|
||||
}
|
||||
|
||||
if (singleGender) // Set Gender(less)
|
||||
return pid; // PID can be anything
|
||||
|
||||
// Gen 3/4/5: Gender derived from PID
|
||||
if (gender == EntityGender.GetFromPIDAndRatio(pid, gt))
|
||||
return pid;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Unown Forme ID from PID.
|
||||
/// </summary>
|
||||
/// <param name="pid">Personality ID</param>
|
||||
/// <remarks>Should only be used for 3rd Generation origin specimens.</remarks>
|
||||
/// <returns></returns>
|
||||
public static int GetUnownForm3(uint pid)
|
||||
{
|
||||
var value = (pid & 0x3000000) >> 18 | (pid & 0x30000) >> 12 | (pid & 0x300) >> 6 | (pid & 0x3);
|
||||
return (int)(value % 28);
|
||||
}
|
||||
}
|
|
@ -1,275 +1,25 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Common logic for <see cref="PKM"/> data providing and manipulation.
|
||||
/// </summary>
|
||||
public static class PKX
|
||||
{
|
||||
internal static readonly PersonalTable Personal = PersonalTable.LA;
|
||||
public const int Generation = 8;
|
||||
|
||||
/// <summary>
|
||||
/// Common logic for <see cref="PKM"/> data providing and manipulation.
|
||||
/// Reorders (in place) the input array of stats to have the Speed value last rather than before the SpA/SpD stats.
|
||||
/// </summary>
|
||||
public static class PKX
|
||||
/// <param name="value">Input array to reorder</param>
|
||||
/// <returns>Same array, reordered.</returns>
|
||||
public static void ReorderSpeedLast(Span<int> value)
|
||||
{
|
||||
internal static readonly PersonalTable Personal = PersonalTable.LA;
|
||||
public const int Generation = 8;
|
||||
|
||||
private static readonly HashSet<int> Sizes = new()
|
||||
{
|
||||
PokeCrypto.SIZE_1JLIST, PokeCrypto.SIZE_1ULIST,
|
||||
PokeCrypto.SIZE_2ULIST, PokeCrypto.SIZE_2JLIST, PokeCrypto.SIZE_2STADIUM,
|
||||
PokeCrypto.SIZE_3STORED, PokeCrypto.SIZE_3PARTY,
|
||||
PokeCrypto.SIZE_3CSTORED, PokeCrypto.SIZE_3XSTORED,
|
||||
PokeCrypto.SIZE_4STORED, PokeCrypto.SIZE_4PARTY,
|
||||
PokeCrypto.SIZE_5PARTY,
|
||||
PokeCrypto.SIZE_6STORED, PokeCrypto.SIZE_6PARTY,
|
||||
PokeCrypto.SIZE_8STORED, PokeCrypto.SIZE_8PARTY,
|
||||
PokeCrypto.SIZE_8ASTORED, PokeCrypto.SIZE_8APARTY,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the given length is valid for a <see cref="PKM"/>.
|
||||
/// </summary>
|
||||
/// <param name="len">Data length of the file/array.</param>
|
||||
/// <returns>A <see cref="bool"/> indicating whether or not the length is valid for a <see cref="PKM"/>.</returns>
|
||||
public static bool IsPKM(long len) => Sizes.Contains((int)len);
|
||||
|
||||
/// <summary>
|
||||
/// Translates a Gender string to Gender integer.
|
||||
/// </summary>
|
||||
/// <param name="s">Gender string</param>
|
||||
/// <returns>Gender integer</returns>
|
||||
public static int GetGenderFromString(string s)
|
||||
{
|
||||
if (s.Length != 1)
|
||||
return 2;
|
||||
return GetGenderFromChar(s[0]);
|
||||
}
|
||||
|
||||
private static int GetGenderFromChar(char c) => c switch
|
||||
{
|
||||
'♂' or 'M' => 0,
|
||||
'♀' or 'F' => 1,
|
||||
_ => 2,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the nature modification values and checks if they are equal.
|
||||
/// </summary>
|
||||
/// <param name="nature">Nature</param>
|
||||
/// <param name="incr">Increased stat</param>
|
||||
/// <param name="decr">Decreased stat</param>
|
||||
/// <returns>True if nature modification values are equal or the Nature is out of range.</returns>
|
||||
public static bool GetNatureModification(int nature, out int incr, out int decr)
|
||||
{
|
||||
incr = (nature / 5) + 1;
|
||||
decr = (nature % 5) + 1;
|
||||
return incr == decr || nature >= 25; // invalid
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates stats according to the specified nature.
|
||||
/// </summary>
|
||||
/// <param name="stats">Current stats to amplify if appropriate</param>
|
||||
/// <param name="nature">Nature</param>
|
||||
public static void ModifyStatsForNature(Span<ushort> stats, int nature)
|
||||
{
|
||||
if (GetNatureModification(nature, out int incr, out int decr))
|
||||
return;
|
||||
stats[incr] *= 11; stats[incr] /= 10;
|
||||
stats[decr] *= 9; stats[decr] /= 10;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a random PID according to specifications.
|
||||
/// </summary>
|
||||
/// <param name="rnd">RNG to use</param>
|
||||
/// <param name="species">National Dex ID</param>
|
||||
/// <param name="gender">Current Gender</param>
|
||||
/// <param name="origin">Origin Generation</param>
|
||||
/// <param name="nature">Nature</param>
|
||||
/// <param name="form">Form</param>
|
||||
/// <param name="oldPID">Current PID</param>
|
||||
/// <remarks>Used to retain ability bits.</remarks>
|
||||
/// <returns>Rerolled PID.</returns>
|
||||
public static uint GetRandomPID(Random rnd, int species, int gender, int origin, int nature, int form, uint oldPID)
|
||||
{
|
||||
// Gen6+ (and VC) PIDs do not tie PID to Nature/Gender/Ability
|
||||
if (origin >= 24)
|
||||
return rnd.Rand32();
|
||||
|
||||
// Below logic handles Gen3-5.
|
||||
|
||||
int gt = Personal[species].Gender;
|
||||
bool g34 = origin <= 15;
|
||||
uint abilBitVal = g34 ? oldPID & 0x0000_0001 : oldPID & 0x0001_0000;
|
||||
|
||||
bool g3unown = origin <= 5 && species == (int)Species.Unown;
|
||||
bool singleGender = PersonalInfo.IsSingleGender(gt); // single gender, skip gender check
|
||||
while (true) // Loop until we find a suitable PID
|
||||
{
|
||||
uint pid = rnd.Rand32();
|
||||
|
||||
// Gen 3/4: Nature derived from PID
|
||||
if (g34 && pid%25 != nature)
|
||||
continue;
|
||||
|
||||
// Gen 3 Unown: Letter/form derived from PID
|
||||
if (g3unown)
|
||||
{
|
||||
var pidLetter = GetUnownForm(pid);
|
||||
if (pidLetter != form)
|
||||
continue;
|
||||
}
|
||||
else if (g34)
|
||||
{
|
||||
if (abilBitVal != (pid & 0x0000_0001)) // keep ability bits
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (abilBitVal != (pid & 0x0001_0000)) // keep ability bits
|
||||
continue;
|
||||
}
|
||||
|
||||
if (singleGender) // Set Gender(less)
|
||||
return pid; // PID can be anything
|
||||
|
||||
// Gen 3/4/5: Gender derived from PID
|
||||
if (gender == GetGenderFromPIDAndRatio(pid, gt))
|
||||
return pid;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Unown Forme ID from PID.
|
||||
/// </summary>
|
||||
/// <param name="pid">Personality ID</param>
|
||||
/// <remarks>Should only be used for 3rd Generation origin specimens.</remarks>
|
||||
/// <returns></returns>
|
||||
public static int GetUnownForm(uint pid)
|
||||
{
|
||||
var value = (pid & 0x3000000) >> 18 | (pid & 0x30000) >> 12 | (pid & 0x300) >> 6 | (pid & 0x3);
|
||||
return (int)(value % 28);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the gender ID of the species based on the Personality ID.
|
||||
/// </summary>
|
||||
/// <param name="species">National Dex ID.</param>
|
||||
/// <param name="pid">Personality ID.</param>
|
||||
/// <returns>Gender ID (0/1/2)</returns>
|
||||
/// <remarks>This method should only be used for Generations 3-5 origin.</remarks>
|
||||
public static int GetGenderFromPID(int species, uint pid)
|
||||
{
|
||||
int gt = Personal[species].Gender;
|
||||
return GetGenderFromPIDAndRatio(pid, gt);
|
||||
}
|
||||
|
||||
public static int GetGenderFromPIDAndRatio(uint pid, int gr) => gr switch
|
||||
{
|
||||
PersonalInfo.RatioMagicGenderless => 2,
|
||||
PersonalInfo.RatioMagicFemale => 1,
|
||||
PersonalInfo.RatioMagicMale => 0,
|
||||
_ => (pid & 0xFF) < gr ? 1 : 0,
|
||||
};
|
||||
|
||||
internal const string ExtensionPB7 = "pb7";
|
||||
internal const string ExtensionPB8 = "pb8";
|
||||
internal const string ExtensionPA8 = "pa8";
|
||||
|
||||
/// <summary>
|
||||
/// Gets an array of valid <see cref="PKM"/> file extensions.
|
||||
/// </summary>
|
||||
/// <param name="maxGeneration">Maximum Generation to permit</param>
|
||||
/// <returns>Valid <see cref="PKM"/> file extensions.</returns>
|
||||
public static string[] GetPKMExtensions(int maxGeneration = Generation)
|
||||
{
|
||||
var result = new List<string>();
|
||||
int min = maxGeneration is <= 2 or >= 7 ? 1 : 3;
|
||||
for (int i = min; i <= maxGeneration; i++)
|
||||
result.Add($"pk{i}");
|
||||
|
||||
if (maxGeneration >= 3)
|
||||
{
|
||||
result.Add("ck3"); // colosseum
|
||||
result.Add("xk3"); // xd
|
||||
}
|
||||
if (maxGeneration >= 4)
|
||||
result.Add("bk4"); // battle revolution
|
||||
if (maxGeneration >= 7)
|
||||
result.Add(ExtensionPB7); // let's go
|
||||
if (maxGeneration >= 8)
|
||||
result.Add(ExtensionPB8); // Brilliant Diamond & Shining Pearl
|
||||
if (maxGeneration >= 8)
|
||||
result.Add(ExtensionPA8); // Legends: Arceus
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Roughly detects the PKM format from the file's extension.
|
||||
/// </summary>
|
||||
/// <param name="ext">File extension.</param>
|
||||
/// <param name="prefer">Preference if not a valid extension, usually the highest acceptable format.</param>
|
||||
/// <returns>Format hint that the file is.</returns>
|
||||
public static int GetPKMFormatFromExtension(string ext, int prefer)
|
||||
{
|
||||
if (string.IsNullOrEmpty(ext))
|
||||
return prefer;
|
||||
return GetPKMFormatFromExtension(ext[^1], prefer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Roughly detects the PKM format from the file's extension.
|
||||
/// </summary>
|
||||
/// <param name="last">Last character of the file's extension.</param>
|
||||
/// <param name="prefer">Preference if not a valid extension, usually the highest acceptable format.</param>
|
||||
/// <returns>Format hint that the file is.</returns>
|
||||
public static int GetPKMFormatFromExtension(char last, int prefer)
|
||||
{
|
||||
if (last is >= '1' and <= '9')
|
||||
return last - '0';
|
||||
return last == 'x' ? 6 : prefer;
|
||||
}
|
||||
|
||||
internal static bool IsPKMPresentGB(ReadOnlySpan<byte> data) => data[0] != 0; // Species non-zero
|
||||
internal static bool IsPKMPresentGC(ReadOnlySpan<byte> data) => ReadUInt16BigEndian(data) != 0; // Species non-zero
|
||||
internal static bool IsPKMPresentGBA(ReadOnlySpan<byte> data) => (data[0x13] & 0xFB) == 2; // ignore egg flag, must be FlagHasSpecies.
|
||||
|
||||
internal static bool IsPKMPresent(ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (ReadUInt32LittleEndian(data) != 0) // PID
|
||||
return true;
|
||||
ushort species = ReadUInt16LittleEndian(data[8..]);
|
||||
return species != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a function that can check a byte array (at an offset) to see if a <see cref="PKM"/> is possibly present.
|
||||
/// </summary>
|
||||
/// <param name="blank"></param>
|
||||
/// <returns>Function that checks if a byte array (at an offset) has a <see cref="PKM"/> present</returns>
|
||||
public static Func<byte[], bool> GetFuncIsPKMPresent(PKM blank)
|
||||
{
|
||||
if (blank.Format >= 4)
|
||||
return x => IsPKMPresent(x);
|
||||
if (blank.Format <= 2)
|
||||
return x => IsPKMPresentGB(x);
|
||||
if (blank.Data.Length <= PokeCrypto.SIZE_3PARTY)
|
||||
return x => IsPKMPresentGBA(x);
|
||||
return x => IsPKMPresentGC(x);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reorders (in place) the input array of stats to have the Speed value last rather than before the SpA/SpD stats.
|
||||
/// </summary>
|
||||
/// <param name="value">Input array to reorder</param>
|
||||
/// <returns>Same array, reordered.</returns>
|
||||
public static void ReorderSpeedLast(Span<int> value)
|
||||
{
|
||||
var spe = value[3];
|
||||
value[3] = value[4];
|
||||
value[4] = value[5];
|
||||
value[5] = spe;
|
||||
}
|
||||
var spe = value[3];
|
||||
value[3] = value[4];
|
||||
value[4] = value[5];
|
||||
value[5] = spe;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -234,7 +234,7 @@ namespace PKHeX.Core
|
|||
public override bool HasParty => true;
|
||||
private int StringLength => Japanese ? GBPKML.StringLengthJapanese : GBPKML.StringLengthNotJapan;
|
||||
|
||||
public override bool IsPKMPresent(ReadOnlySpan<byte> data) => PKX.IsPKMPresentGB(data);
|
||||
public override bool IsPKMPresent(ReadOnlySpan<byte> data) => EntityDetection.IsPresentGB(data);
|
||||
|
||||
// Checksums
|
||||
protected override void SetChecksums() => Data[Offsets.ChecksumOfs] = GetRBYChecksum(Offsets.OT, Offsets.ChecksumOfs);
|
||||
|
|
|
@ -271,7 +271,7 @@ namespace PKHeX.Core
|
|||
public override int MaxMoney => 999999;
|
||||
public override int MaxCoins => 9999;
|
||||
|
||||
public override bool IsPKMPresent(ReadOnlySpan<byte> data) => PKX.IsPKMPresentGB(data);
|
||||
public override bool IsPKMPresent(ReadOnlySpan<byte> data) => EntityDetection.IsPresentGB(data);
|
||||
|
||||
public int EventWorkCount => 0x100;
|
||||
public int EventFlagCount => 2000;
|
||||
|
|
|
@ -178,7 +178,7 @@ namespace PKHeX.Core
|
|||
|
||||
public sealed override bool HasParty => true;
|
||||
|
||||
public sealed override bool IsPKMPresent(ReadOnlySpan<byte> data) => PKX.IsPKMPresentGBA(data);
|
||||
public sealed override bool IsPKMPresent(ReadOnlySpan<byte> data) => EntityDetection.IsPresentGBA(data);
|
||||
protected sealed override PKM GetPKM(byte[] data) => new PK3(data);
|
||||
protected sealed override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray3(data);
|
||||
|
||||
|
@ -479,7 +479,7 @@ namespace PKHeX.Core
|
|||
|
||||
public uint DexPIDUnown { get => ReadUInt32LittleEndian(Small.AsSpan(PokeDex + 0x4)); set => WriteUInt32LittleEndian(Small.AsSpan(PokeDex + 0x4), value); }
|
||||
public uint DexPIDSpinda { get => ReadUInt32LittleEndian(Small.AsSpan(PokeDex + 0x8)); set => WriteUInt32LittleEndian(Small.AsSpan(PokeDex + 0x8), value); }
|
||||
public int DexUnownForm => PKX.GetUnownForm(DexPIDUnown);
|
||||
public int DexUnownForm => EntityPID.GetUnownForm3(DexPIDUnown);
|
||||
|
||||
public sealed override bool GetCaught(int species)
|
||||
{
|
||||
|
|
|
@ -153,7 +153,7 @@ namespace PKHeX.Core
|
|||
public override int MaxMoney => 9999999;
|
||||
|
||||
public override int BoxCount => 3;
|
||||
public override bool IsPKMPresent(ReadOnlySpan<byte> data) => PKX.IsPKMPresentGC(data);
|
||||
public override bool IsPKMPresent(ReadOnlySpan<byte> data) => EntityDetection.IsPresentGC(data);
|
||||
|
||||
private static byte[] EncryptColosseum(ReadOnlySpan<byte> input, Span<byte> digest)
|
||||
{
|
||||
|
|
|
@ -124,7 +124,7 @@ namespace PKHeX.Core
|
|||
|
||||
public override int BoxCount => 50;
|
||||
public override bool HasParty => false;
|
||||
public override bool IsPKMPresent(ReadOnlySpan<byte> data) => PKX.IsPKMPresentGBA(data);
|
||||
public override bool IsPKMPresent(ReadOnlySpan<byte> data) => EntityDetection.IsPresentGBA(data);
|
||||
|
||||
// Checksums
|
||||
protected override void SetChecksums() => Blocks.SetChecksums(Data);
|
||||
|
|
|
@ -187,7 +187,7 @@ namespace PKHeX.Core
|
|||
|
||||
public override int BoxCount => 8;
|
||||
|
||||
public override bool IsPKMPresent(ReadOnlySpan<byte> data) => PKX.IsPKMPresentGC(data);
|
||||
public override bool IsPKMPresent(ReadOnlySpan<byte> data) => EntityDetection.IsPresentGC(data);
|
||||
|
||||
// Checksums
|
||||
protected override void SetChecksums()
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace PKHeX.Core
|
|||
{
|
||||
protected internal override string ShortSummary => $"{OT} ({Version}) - {Blocks.Played.LastSavedTime}";
|
||||
public override string Extension => ".bin";
|
||||
public override IReadOnlyList<string> PKMExtensions => new[] {PKX.ExtensionPB7};
|
||||
public override IReadOnlyList<string> PKMExtensions => EntityFileExtension.Extensions7b;
|
||||
|
||||
public override Type PKMType => typeof(PB7);
|
||||
public override PKM BlankPKM => new PB7();
|
||||
|
|
|
@ -352,7 +352,7 @@ namespace PKHeX.Core
|
|||
public abstract IReadOnlyList<ushort> HeldItems { get; }
|
||||
protected virtual byte[] BoxBuffer => Data;
|
||||
protected virtual byte[] PartyBuffer => Data;
|
||||
public virtual bool IsPKMPresent(ReadOnlySpan<byte> data) => PKX.IsPKMPresent(data);
|
||||
public virtual bool IsPKMPresent(ReadOnlySpan<byte> data) => EntityDetection.IsPresent(data);
|
||||
public virtual PKM GetDecryptedPKM(byte[] data) => GetPKM(DecryptPKM(data));
|
||||
public virtual PKM GetPartySlot(byte[] data, int offset) => GetDecryptedPKM(GetData(data, offset, SIZE_PARTY));
|
||||
public virtual PKM GetStoredSlot(byte[] data, int offset) => GetDecryptedPKM(GetData(data, offset, SIZE_STORED));
|
||||
|
|
|
@ -174,7 +174,7 @@ namespace PKHeX.Core
|
|||
public static IEnumerable<PKM> GetPKMsFromPaths(IEnumerable<string> files, int generation)
|
||||
{
|
||||
var result = files
|
||||
.Where(file => PKX.IsPKM(new FileInfo(file).Length))
|
||||
.Where(file => EntityDetection.IsSizePlausible(new FileInfo(file).Length))
|
||||
.Select(File.ReadAllBytes)
|
||||
.Select(data => EntityFormat.GetFromBytes(data, prefer: generation));
|
||||
|
||||
|
|
|
@ -195,7 +195,7 @@ namespace PKHeX.Core
|
|||
var di = new DirectoryInfo(templatePath);
|
||||
string path = Path.Combine(templatePath, $"{di.Name}.{sav.PKMType.Name.ToLowerInvariant()}");
|
||||
|
||||
if (!File.Exists(path) || !PKX.IsPKM(new FileInfo(path).Length))
|
||||
if (!File.Exists(path) || !EntityDetection.IsSizePlausible(new FileInfo(path).Length))
|
||||
return LoadTemplateInternal(sav);
|
||||
|
||||
var pk = EntityFormat.GetFromBytes(File.ReadAllBytes(path), prefer: sav.Generation);
|
||||
|
|
|
@ -172,7 +172,7 @@ namespace PKHeX.Core
|
|||
pk = null;
|
||||
return false;
|
||||
}
|
||||
var format = PKX.GetPKMFormatFromExtension(ext, sav?.Generation ?? 6);
|
||||
var format = EntityFileExtension.GetFormatFromExtension(ext, sav?.Generation ?? 6);
|
||||
pk = EntityFormat.GetFromBytes(data, prefer: format);
|
||||
return pk != null;
|
||||
}
|
||||
|
@ -192,7 +192,7 @@ namespace PKHeX.Core
|
|||
return false;
|
||||
}
|
||||
var length = data.Length;
|
||||
if (PKX.IsPKM(length / sav.SlotCount) || PKX.IsPKM(length / sav.BoxSlotCount))
|
||||
if (EntityDetection.IsSizePlausible(length / sav.SlotCount) || EntityDetection.IsSizePlausible(length / sav.BoxSlotCount))
|
||||
{
|
||||
pkms = ArrayUtil.EnumerateSplit(data, length);
|
||||
return true;
|
||||
|
@ -253,7 +253,7 @@ namespace PKHeX.Core
|
|||
return null;
|
||||
if (fi.Length == GP1.SIZE && TryGetGP1(File.ReadAllBytes(file), out var gp1))
|
||||
return gp1.ConvertToPB7(sav);
|
||||
if (!PKX.IsPKM(fi.Length) && !MysteryGift.IsMysteryGift(fi.Length))
|
||||
if (!EntityDetection.IsSizePlausible(fi.Length) && !MysteryGift.IsMysteryGift(fi.Length))
|
||||
return null;
|
||||
var data = File.ReadAllBytes(file);
|
||||
var ext = fi.Extension;
|
||||
|
|
|
@ -385,7 +385,7 @@ namespace PKHeX.WinForms.Controls
|
|||
|
||||
private static string ReloadGender(string text, IReadOnlyList<string> genders)
|
||||
{
|
||||
var index = PKX.GetGenderFromString(text);
|
||||
var index = EntityGender.GetFromString(text);
|
||||
if (index >= 2)
|
||||
return text;
|
||||
return genders[index];
|
||||
|
@ -605,7 +605,7 @@ namespace PKHeX.WinForms.Controls
|
|||
}
|
||||
Entity.Gender = gender;
|
||||
|
||||
if (PKX.GetGenderFromString(CB_Form.Text) < 2) // Gendered Forms
|
||||
if (EntityGender.GetFromString(CB_Form.Text) < 2) // Gendered Forms
|
||||
CB_Form.SelectedIndex = Math.Min(gender, CB_Form.Items.Count - 1);
|
||||
|
||||
UpdatePreviewSprite?.Invoke(UC_Gender, EventArgs.Empty);
|
||||
|
@ -987,7 +987,7 @@ namespace PKHeX.WinForms.Controls
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (CB_Form.Enabled && PKX.GetGenderFromString(CB_Form.Text) < 2)
|
||||
else if (CB_Form.Enabled && EntityGender.GetFromString(CB_Form.Text) < 2)
|
||||
{
|
||||
if (CB_Form.Items.Count == 2) // actually M/F; Pumpkaboo formes in German are S,M,L,XL
|
||||
{
|
||||
|
|
|
@ -21,8 +21,7 @@ namespace PKHeX.WinForms.Controls
|
|||
MT_Base = new[] {TB_BaseHP, TB_BaseATK, TB_BaseDEF, TB_BaseSPE, TB_BaseSPA, TB_BaseSPD};
|
||||
|
||||
TB_BST.ResetForeColor();
|
||||
TB_IVTotal.ForeColor = MT_EVs[0].ForeColor;
|
||||
TB_EVTotal.ForeColor = MT_EVs[0].ForeColor;
|
||||
TB_IVTotal.ForeColor = TB_EVTotal.ForeColor = MT_EVs[0].ForeColor;
|
||||
}
|
||||
|
||||
public Color EVsInvalid { get; set; } = Color.Red;
|
||||
|
@ -315,6 +314,9 @@ namespace PKHeX.WinForms.Controls
|
|||
|
||||
private void ClickStatLabel(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (Entity.Format < 3)
|
||||
return;
|
||||
|
||||
if (ModifierKeys == Keys.None)
|
||||
return;
|
||||
|
||||
|
@ -322,22 +324,17 @@ namespace PKHeX.WinForms.Controls
|
|||
if (index < 0)
|
||||
return;
|
||||
|
||||
if (Entity.Format < 3)
|
||||
var request = ModifierKeys switch
|
||||
{
|
||||
Keys.Control => NatureAmpRequest.Neutral,
|
||||
Keys.Alt => NatureAmpRequest.Decrease,
|
||||
_ => NatureAmpRequest.Increase,
|
||||
};
|
||||
|
||||
var newNature = request.GetNewNature(index, Entity.StatNature);
|
||||
if (newNature == -1)
|
||||
return;
|
||||
|
||||
var current = Entity.StatNature;
|
||||
var up = current / 5;
|
||||
var dn = current % 5;
|
||||
switch (ModifierKeys)
|
||||
{
|
||||
case Keys.Shift when up != index: up = index; break;
|
||||
case Keys.Alt when dn != index: dn = index; break;
|
||||
case Keys.Control when up != index && dn != index: up = dn = index; break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
var newNature = (up * 5) + dn;
|
||||
MainEditor.ChangeNature(newNature);
|
||||
}
|
||||
|
||||
|
@ -459,16 +456,19 @@ namespace PKHeX.WinForms.Controls
|
|||
public string UpdateNatureModification(int nature)
|
||||
{
|
||||
// Reset Label Colors
|
||||
for (var i = 1; i < L_Stats.Length; i++)
|
||||
L_Stats[i].ResetForeColor();
|
||||
foreach (var l in L_Stats)
|
||||
l.ResetForeColor();
|
||||
|
||||
// Set Colored StatLabels only if Nature isn't Neutral
|
||||
if (PKX.GetNatureModification(nature, out int incr, out int decr))
|
||||
var (up, dn) = NatureAmp.GetNatureModification(nature);
|
||||
if (NatureAmp.IsNeutralOrInvalid(nature, up, dn))
|
||||
return "-/-";
|
||||
|
||||
L_Stats[incr].ForeColor = StatIncreased;
|
||||
L_Stats[decr].ForeColor = StatDecreased;
|
||||
return $"+{L_Stats[incr].Text} / -{L_Stats[decr].Text}".Replace(":", "");
|
||||
var incr = L_Stats[up];
|
||||
var decr = L_Stats[dn];
|
||||
incr.ForeColor = StatIncreased;
|
||||
decr.ForeColor = StatDecreased;
|
||||
return $"+{incr.Text} / -{decr.Text}".Replace(":", "");
|
||||
}
|
||||
|
||||
public void SetATKIVGender(int gender)
|
||||
|
@ -565,12 +565,13 @@ namespace PKHeX.WinForms.Controls
|
|||
{
|
||||
int current = ganbaru.GetGV(i);
|
||||
var max = ganbaru.GetMax(entity, i);
|
||||
var tb = MT_GVs[i];
|
||||
if (current > max)
|
||||
MT_GVs[i].BackColor = EVsInvalid;
|
||||
tb.BackColor = EVsInvalid;
|
||||
else if (current == max)
|
||||
MT_GVs[i].BackColor = StatHyperTrained;
|
||||
tb.BackColor = StatHyperTrained;
|
||||
else
|
||||
MT_GVs[i].ResetBackColor();
|
||||
tb.ResetBackColor();
|
||||
}
|
||||
|
||||
public void ToggleInterface(PKM pk, int gen)
|
||||
|
@ -589,14 +590,14 @@ namespace PKHeX.WinForms.Controls
|
|||
Label_SPA.Visible = false;
|
||||
Label_SPC.Visible = true;
|
||||
TB_IVHP.Enabled = false;
|
||||
SetEVMaskSize(Stat_HP.Size, "00000");
|
||||
SetEVMaskSize(Stat_HP.Size, "00000", MT_EVs);
|
||||
break;
|
||||
case 2:
|
||||
FLP_SpD.Visible = true;
|
||||
Label_SPA.Visible = true;
|
||||
Label_SPC.Visible = false;
|
||||
TB_IVHP.Enabled = false;
|
||||
SetEVMaskSize(Stat_HP.Size, "00000");
|
||||
SetEVMaskSize(Stat_HP.Size, "00000", MT_EVs);
|
||||
TB_EVSPD.Enabled = TB_IVSPD.Enabled = false;
|
||||
break;
|
||||
default:
|
||||
|
@ -604,7 +605,7 @@ namespace PKHeX.WinForms.Controls
|
|||
Label_SPA.Visible = true;
|
||||
Label_SPC.Visible = false;
|
||||
TB_IVHP.Enabled = true;
|
||||
SetEVMaskSize(TB_EVTotal.Size, "000");
|
||||
SetEVMaskSize(TB_EVTotal.Size, "000", MT_EVs);
|
||||
TB_EVSPD.Enabled = TB_IVSPD.Enabled = true;
|
||||
break;
|
||||
}
|
||||
|
@ -622,9 +623,9 @@ namespace PKHeX.WinForms.Controls
|
|||
foreach (var mtb in MT_GVs)
|
||||
mtb.Visible = showGV;
|
||||
|
||||
void SetEVMaskSize(Size s, string Mask)
|
||||
static void SetEVMaskSize(Size s, string Mask, MaskedTextBox[] arr)
|
||||
{
|
||||
foreach (var ctrl in MT_EVs)
|
||||
foreach (var ctrl in arr)
|
||||
{
|
||||
ctrl.Size = s;
|
||||
ctrl.Mask = Mask;
|
||||
|
|
|
@ -289,7 +289,7 @@ namespace PKHeX.WinForms
|
|||
private void TryProcess(string source, string destDir, IReadOnlyList<StringInstruction> metaFilters, IReadOnlyList<StringInstruction> pkFilters, IReadOnlyList<StringInstruction> instructions)
|
||||
{
|
||||
var fi = new FileInfo(source);
|
||||
if (!PKX.IsPKM(fi.Length))
|
||||
if (!EntityDetection.IsSizePlausible(fi.Length))
|
||||
return;
|
||||
|
||||
byte[] data = File.ReadAllBytes(source);
|
||||
|
|
|
@ -215,13 +215,13 @@ namespace PKHeX.WinForms
|
|||
TID = Convert.ToUInt16(TB_TID.Text),
|
||||
SID = Convert.ToUInt16(TB_SID.Text),
|
||||
Form = (uint)CB_Form.SelectedIndex,
|
||||
Gender = (uint)PKX.GetGenderFromString(Label_Gender.Text) & 0x3,
|
||||
Gender = (uint)EntityGender.GetFromString(Label_Gender.Text) & 0x3,
|
||||
Level = Convert.ToUInt16(TB_Level.Text),
|
||||
IsShiny = CHK_Shiny.Checked,
|
||||
IsNicknamed = CHK_Nicknamed.Checked,
|
||||
Nickname = TB_Nickname.Text,
|
||||
OT_Name = TB_OT.Text,
|
||||
OT_Gender = (uint)PKX.GetGenderFromString(Label_OTGender.Text) & 1,
|
||||
OT_Gender = (uint)EntityGender.GetFromString(Label_OTGender.Text) & 1,
|
||||
};
|
||||
|
||||
offset = index * 0x1B4;
|
||||
|
@ -295,7 +295,7 @@ namespace PKHeX.WinForms
|
|||
|
||||
var species = WinFormsUtil.GetIndex(CB_Species);
|
||||
var form = CB_Form.SelectedIndex & 0x1F;
|
||||
var gender = PKX.GetGenderFromString(Label_Gender.Text);
|
||||
var gender = EntityGender.GetFromString(Label_Gender.Text);
|
||||
var item = WinFormsUtil.GetIndex(CB_HeldItem);
|
||||
bpkx.Image = SpriteUtil.GetSprite(species, form, gender, 0, item, false, CHK_Shiny.Checked, 6);
|
||||
|
||||
|
@ -304,7 +304,7 @@ namespace PKHeX.WinForms
|
|||
|
||||
private void UpdateOTGender(object sender, EventArgs e)
|
||||
{
|
||||
var g = PKX.GetGenderFromString(Label_OTGender.Text);
|
||||
var g = EntityGender.GetFromString(Label_OTGender.Text);
|
||||
Label_OTGender.Text = gendersymbols[g ^ 1];
|
||||
|
||||
Write_Entry(this, EventArgs.Empty);
|
||||
|
@ -317,7 +317,7 @@ namespace PKHeX.WinForms
|
|||
var pi = SAV.Personal[species];
|
||||
if (pi.IsDualGender)
|
||||
{
|
||||
var fg = PKX.GetGenderFromString(Label_Gender.Text);
|
||||
var fg = EntityGender.GetFromString(Label_Gender.Text);
|
||||
fg = (fg ^ 1) & 1;
|
||||
Label_Gender.Text = gendersymbols[fg];
|
||||
}
|
||||
|
@ -328,14 +328,14 @@ namespace PKHeX.WinForms
|
|||
return;
|
||||
}
|
||||
|
||||
var g = PKX.GetGenderFromString(CB_Form.Text);
|
||||
var g = EntityGender.GetFromString(CB_Form.Text);
|
||||
if (g == 0 && Label_Gender.Text != gendersymbols[0])
|
||||
CB_Form.SelectedIndex = 1;
|
||||
else if (g == 1 && Label_Gender.Text != gendersymbols[1])
|
||||
CB_Form.SelectedIndex = 0;
|
||||
|
||||
if (species == (int)Species.Pyroar)
|
||||
CB_Form.SelectedIndex = PKX.GetGenderFromString(Label_Gender.Text);
|
||||
CB_Form.SelectedIndex = EntityGender.GetFromString(Label_Gender.Text);
|
||||
|
||||
Write_Entry(this, EventArgs.Empty);
|
||||
}
|
||||
|
|
|
@ -190,7 +190,7 @@ namespace PKHeX.WinForms
|
|||
pkm.Ability = WinFormsUtil.GetIndex(CB_Ability);
|
||||
pkm.AbilityNumber = CB_Ability.SelectedIndex << 1;
|
||||
pkm.Nature = WinFormsUtil.GetIndex(CB_Nature);
|
||||
pkm.Gender = PKX.GetGenderFromString(Label_Gender.Text);
|
||||
pkm.Gender = EntityGender.GetFromString(Label_Gender.Text);
|
||||
pkm.Form = CB_Form.SelectedIndex;
|
||||
pkm.EV_HP = Math.Min(Convert.ToInt32(TB_HPEV.Text), 252);
|
||||
pkm.EV_ATK = Math.Min(Convert.ToInt32(TB_ATKEV.Text), 252);
|
||||
|
@ -347,7 +347,7 @@ namespace PKHeX.WinForms
|
|||
// Set a sane gender
|
||||
var gender = SAV.Personal[species].FixedGender;
|
||||
if (gender == -1)
|
||||
gender = PKX.GetGenderFromString(Label_Gender.Text);
|
||||
gender = EntityGender.GetFromString(Label_Gender.Text);
|
||||
SetGenderLabel(gender);
|
||||
|
||||
SetAbilityList();
|
||||
|
@ -358,7 +358,7 @@ namespace PKHeX.WinForms
|
|||
SetAbilityList();
|
||||
|
||||
// If form has a single gender, account for it.
|
||||
if (PKX.GetGenderFromString(CB_Form.Text) < 2)
|
||||
if (EntityGender.GetFromString(CB_Form.Text) < 2)
|
||||
Label_Gender.Text = Main.GenderSymbols[CB_Form.SelectedIndex];
|
||||
}
|
||||
|
||||
|
@ -369,7 +369,7 @@ namespace PKHeX.WinForms
|
|||
var fg = pi.FixedGender;
|
||||
if (fg == -1) // dual gender
|
||||
{
|
||||
fg = PKX.GetGenderFromString(Label_Gender.Text);
|
||||
fg = EntityGender.GetFromString(Label_Gender.Text);
|
||||
fg = (fg ^ 1) & 1;
|
||||
}
|
||||
Label_Gender.Text = Main.GenderSymbols[fg];
|
||||
|
|
|
@ -65,10 +65,10 @@ namespace PKHeX.Tests.Legality
|
|||
{
|
||||
var fi = new FileInfo(file);
|
||||
fi.Should().NotBeNull($"the test file '{file}' should be a valid file");
|
||||
PKX.IsPKM(fi.Length).Should().BeTrue($"the test file '{file}' should have a valid file length");
|
||||
EntityDetection.IsSizePlausible(fi.Length).Should().BeTrue($"the test file '{file}' should have a valid file length");
|
||||
|
||||
var data = File.ReadAllBytes(file);
|
||||
var format = PKX.GetPKMFormatFromExtension(file[^1], -1);
|
||||
var format = EntityFileExtension.GetFormatFromExtension(file[^1], -1);
|
||||
format.Should().BeLessOrEqualTo(PKX.Generation, "filename is expected to have a valid extension");
|
||||
|
||||
var dn = fi.DirectoryName ?? string.Empty;
|
||||
|
|
Loading…
Reference in a new issue