mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-22 12:03:10 +00:00
Misc tweaks
Allow nickname->species with span add ConstantExpected annotations Correct Gen8b ability set calling CommonEdits instead of direct setter Slightly better perf on gen5/gen8+ location fetch Misc pkm ctor fixes for b2w2 trade & wc3 eggs wurmple evo now has an enum to be more explicit no recursion for gen5 generator fix showdown line length check allowing 86 instead of 80
This commit is contained in:
parent
afc29fb203
commit
fd02b97ce1
39 changed files with 251 additions and 180 deletions
|
@ -51,7 +51,7 @@ public static class CommonEdits
|
|||
/// Sets the <see cref="PKM.Ability"/> value by sanity checking the provided <see cref="PKM.Ability"/> against the possible pool of abilities.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="abil">Desired <see cref="PKM.Ability"/> to set.</param>
|
||||
/// <param name="abil">Desired <see cref="Ability"/> value to set.</param>
|
||||
public static void SetAbility(this PKM pk, int abil)
|
||||
{
|
||||
if (abil < 0)
|
||||
|
|
|
@ -145,7 +145,8 @@ public sealed class ShowdownSet : IBattleTemplate
|
|||
// We will handle this 1-2 letter edge case only if the line is the first line of the set, in the rare chance we are importing for a non-English language?
|
||||
private const int MinLength = 3;
|
||||
private const int MaxLength = 80;
|
||||
private static bool IsLengthOutOfRange(ReadOnlySpan<char> trim) => (uint)(trim.Length - MinLength) > MaxLength + MinLength;
|
||||
private static bool IsLengthOutOfRange(ReadOnlySpan<char> trim) => IsLengthOutOfRange(trim.Length);
|
||||
private static bool IsLengthOutOfRange(int length) => (uint)(length - MinLength) > MaxLength - MinLength;
|
||||
|
||||
private void ParseLines(SpanLineEnumerator lines)
|
||||
{
|
||||
|
|
|
@ -10,23 +10,22 @@ public static class WurmpleUtil
|
|||
/// </summary>
|
||||
/// <param name="encryptionConstant">Encryption Constant</param>
|
||||
/// <returns>Wurmple Evolution Value</returns>
|
||||
public static uint GetWurmpleEvoVal(uint encryptionConstant)
|
||||
public static WurmpleEvolution GetWurmpleEvoVal(uint encryptionConstant)
|
||||
{
|
||||
var evoVal = encryptionConstant >> 16;
|
||||
return evoVal % 10 / 5;
|
||||
return (WurmpleEvolution)(evoVal % 10 / 5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the evo chain of Wurmple
|
||||
/// </summary>
|
||||
/// <param name="species">Current species</param>
|
||||
/// <returns>-1 if not a Wurmple Evo, 0 if Silcoon chain, 1 if Cascoon chain</returns>
|
||||
public static int GetWurmpleEvoGroup(ushort species)
|
||||
/// <param name="species">Current species, must be evolved from Wurmple.</param>
|
||||
public static WurmpleEvolution GetWurmpleEvoGroup(ushort species)
|
||||
{
|
||||
int wIndex = species - (int)Species.Silcoon;
|
||||
if ((wIndex & 3) != wIndex) // Wurmple evo, [0,3]
|
||||
return -1;
|
||||
return wIndex >> 1; // Silcoon, Cascoon
|
||||
return WurmpleEvolution.None;
|
||||
return (WurmpleEvolution)(wIndex >> 1); // Silcoon, Cascoon
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -35,7 +34,7 @@ public static class WurmpleUtil
|
|||
/// <param name="evoVal">Wurmple Evolution Value</param>
|
||||
/// <remarks>0 = Silcoon, 1 = Cascoon</remarks>
|
||||
/// <returns>Encryption Constant</returns>
|
||||
public static uint GetWurmpleEncryptionConstant(int evoVal)
|
||||
public static uint GetWurmpleEncryptionConstant(WurmpleEvolution evoVal)
|
||||
{
|
||||
uint result;
|
||||
var rnd = Util.Rand;
|
||||
|
@ -51,8 +50,23 @@ public static class WurmpleUtil
|
|||
/// <returns>True if valid, false if invalid</returns>
|
||||
public static bool IsWurmpleEvoValid(PKM pk)
|
||||
{
|
||||
uint evoVal = GetWurmpleEvoVal(pk.EncryptionConstant);
|
||||
int wIndex = GetWurmpleEvoGroup(pk.Species);
|
||||
var evoVal = GetWurmpleEvoVal(pk.EncryptionConstant);
|
||||
var wIndex = GetWurmpleEvoGroup(pk.Species);
|
||||
return evoVal == wIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the evolution of Wurmple
|
||||
/// </summary>
|
||||
public enum WurmpleEvolution
|
||||
{
|
||||
/// <summary> Invalid value </summary>
|
||||
None = -1,
|
||||
|
||||
/// <summary> Evolves into Silcoon/Beautifly </summary>
|
||||
Silcoon = 0,
|
||||
|
||||
/// <summary> Evolves into Cascoon/Dustox </summary>
|
||||
Cascoon = 1,
|
||||
}
|
||||
|
|
|
@ -62,54 +62,3 @@ public static class GameLanguage
|
|||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Program Languages supported; mirrors <see cref="GameLanguage.LanguageCodes"/>.
|
||||
/// </summary>
|
||||
public enum ProgramLanguage
|
||||
{
|
||||
/// <summary>
|
||||
/// Japanese
|
||||
/// </summary>
|
||||
日本語,
|
||||
|
||||
/// <summary>
|
||||
/// English
|
||||
/// </summary>
|
||||
English,
|
||||
|
||||
/// <summary>
|
||||
/// French
|
||||
/// </summary>
|
||||
Français,
|
||||
|
||||
/// <summary>
|
||||
/// Italian
|
||||
/// </summary>
|
||||
Italiano,
|
||||
|
||||
/// <summary>
|
||||
/// German
|
||||
/// </summary>
|
||||
Deutsch,
|
||||
|
||||
/// <summary>
|
||||
/// Spanish
|
||||
/// </summary>
|
||||
Español,
|
||||
|
||||
/// <summary>
|
||||
/// Korean
|
||||
/// </summary>
|
||||
한국어,
|
||||
|
||||
/// <summary>
|
||||
/// Simplified Chinese
|
||||
/// </summary>
|
||||
简体中文,
|
||||
|
||||
/// <summary>
|
||||
/// Traditional Chinese
|
||||
/// </summary>
|
||||
繁體中文,
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using static PKHeX.Core.GameVersion;
|
||||
using static PKHeX.Core.Locations;
|
||||
|
||||
|
@ -101,12 +102,15 @@ public sealed class MetDataSource
|
|||
private IReadOnlyList<ComboItem> CreateGen5Transfer()
|
||||
{
|
||||
// PokéTransfer to front
|
||||
var met = MetGen5.ToArray();
|
||||
var index = Array.FindIndex(met, static z => z.Value == Locations.Transfer4);
|
||||
var xfr = met[index];
|
||||
Array.Copy(met, 0, met, 1, index);
|
||||
met[0] = xfr;
|
||||
return met;
|
||||
var index = MetGen5.FindIndex(static z => z.Value == Locations.Transfer4);
|
||||
var xfr = MetGen5[index];
|
||||
var result = new ComboItem[MetGen5.Count];
|
||||
result[0] = xfr;
|
||||
var dest = result.AsSpan(1);
|
||||
var span = CollectionsMarshal.AsSpan(MetGen5);
|
||||
span[..index].CopyTo(dest);
|
||||
span[(index + 1)..].CopyTo(dest[index..]);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<ComboItem> CreateGen6(GameStrings s)
|
||||
|
@ -230,10 +234,15 @@ public sealed class MetDataSource
|
|||
// Insert the BDSP none location if the format requires it.
|
||||
if (context is EntityContext.Gen8b && !BDSP.Contains(version))
|
||||
{
|
||||
var list = new List<ComboItem>(result.Count + 1);
|
||||
list.AddRange(result);
|
||||
list.Insert(1, new ComboItem($"{list[0].Text} (BD/SP)", Locations.Default8bNone));
|
||||
result = list;
|
||||
var bdsp = new ComboItem[result.Count + 1];
|
||||
var none = bdsp[0] = result[0];
|
||||
bdsp[1] = new ComboItem($"{none.Text} (BD/SP)", Locations.Default8bNone);
|
||||
var dest = bdsp.AsSpan(2);
|
||||
if (result is ComboItem[] arr)
|
||||
arr.AsSpan(1).CopyTo(dest);
|
||||
else if (result is List<ComboItem> list)
|
||||
CollectionsMarshal.AsSpan(list)[1..].CopyTo(dest);
|
||||
return bdsp;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
52
PKHeX.Core/Game/GameStrings/ProgramLanguage.cs
Normal file
52
PKHeX.Core/Game/GameStrings/ProgramLanguage.cs
Normal file
|
@ -0,0 +1,52 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Program Languages supported; mirrors <see cref="GameLanguage.LanguageCodes"/>.
|
||||
/// </summary>
|
||||
public enum ProgramLanguage
|
||||
{
|
||||
/// <summary>
|
||||
/// Japanese
|
||||
/// </summary>
|
||||
日本語,
|
||||
|
||||
/// <summary>
|
||||
/// English
|
||||
/// </summary>
|
||||
English,
|
||||
|
||||
/// <summary>
|
||||
/// French
|
||||
/// </summary>
|
||||
Français,
|
||||
|
||||
/// <summary>
|
||||
/// Italian
|
||||
/// </summary>
|
||||
Italiano,
|
||||
|
||||
/// <summary>
|
||||
/// German
|
||||
/// </summary>
|
||||
Deutsch,
|
||||
|
||||
/// <summary>
|
||||
/// Spanish
|
||||
/// </summary>
|
||||
Español,
|
||||
|
||||
/// <summary>
|
||||
/// Korean
|
||||
/// </summary>
|
||||
한국어,
|
||||
|
||||
/// <summary>
|
||||
/// Simplified Chinese
|
||||
/// </summary>
|
||||
简体中文,
|
||||
|
||||
/// <summary>
|
||||
/// Traditional Chinese
|
||||
/// </summary>
|
||||
繁體中文,
|
||||
}
|
|
@ -73,9 +73,6 @@ public static class EncounterEvent
|
|||
public static WC9[] EGDB_G9 { get; private set; } = Array.Empty<WC9>();
|
||||
#endregion
|
||||
|
||||
/// <summary>Indicates if the databases are initialized.</summary>
|
||||
public static bool Initialized => MGDB_G3.Length != 0;
|
||||
|
||||
private static PCD[] GetPCDDB(ReadOnlySpan<byte> bin) => Get(bin, PCD.Size, static d => new PCD(d));
|
||||
private static PGF[] GetPGFDB(ReadOnlySpan<byte> bin) => Get(bin, PGF.Size, static d => new PGF(d));
|
||||
|
||||
|
@ -178,6 +175,7 @@ public static class EncounterEvent
|
|||
{
|
||||
var regular = new IReadOnlyList<MysteryGift>[]
|
||||
{
|
||||
MGDB_G3,
|
||||
MGDB_G4, EGDB_G4,
|
||||
MGDB_G5, EGDB_G5,
|
||||
MGDB_G6, EGDB_G6,
|
||||
|
@ -188,8 +186,7 @@ public static class EncounterEvent
|
|||
MGDB_G8B, EGDB_G8B,
|
||||
MGDB_G9, EGDB_G9,
|
||||
}.SelectMany(z => z);
|
||||
regular = regular.Where(mg => mg is { IsItem: false, IsEntity: true, Species: > 0 });
|
||||
var result = MGDB_G3.Concat(regular);
|
||||
var result = regular.Where(mg => mg is { IsItem: false, IsEntity: true, Species: not 0 });
|
||||
if (sorted)
|
||||
result = result.OrderBy(mg => mg.Species);
|
||||
return result;
|
||||
|
|
|
@ -11,7 +11,7 @@ internal static class Encounters3Colo
|
|||
new(197, 26) { Moves = new(044, 269, 290, 289) }, // Umbreon (Bite)
|
||||
};
|
||||
|
||||
internal static readonly string[] TrainerNameDuking = { string.Empty, "ギンザル", "DUKING", "DOKING", "RODRIGO", "GRAND", string.Empty, "GERMÁN", };
|
||||
internal static readonly string[] TrainerNameDuking = { string.Empty, "ギンザル", "DUKING", "DOKING", "RODRIGO", "GRAND", string.Empty, "GERMÁN" };
|
||||
|
||||
internal static readonly EncounterGift3Colo[] Gifts =
|
||||
{
|
||||
|
|
|
@ -15,12 +15,12 @@ internal static class Encounters8a
|
|||
internal static readonly EncounterStatic8a[] StaticLA =
|
||||
{
|
||||
// Gifts
|
||||
new(722,000,05,M,M) { Location = 006, FixedBall = Ball.LAPoke, Method = Fixed, }, // Rowlet
|
||||
new(155,000,05,M,M) { Location = 006, FixedBall = Ball.LAPoke, Method = Fixed, }, // Cyndaquil
|
||||
new(501,000,05,M,M) { Location = 006, FixedBall = Ball.LAPoke, Method = Fixed, }, // Oshawott
|
||||
new(037,001,40,M,M) { Location = 088, FixedBall = Ball.LAPoke, Method = Fixed, }, // Vulpix-1
|
||||
new(483,000,65,M,M) { Location = 109, FlawlessIVCount = 3, FixedBall = Ball.LAOrigin, Method = Fixed, }, // Dialga
|
||||
new(484,000,65,M,M) { Location = 109, FlawlessIVCount = 3, FixedBall = Ball.LAOrigin, Method = Fixed, }, // Palkia
|
||||
new(722,000,05,M,M) { Location = 006, FixedBall = Ball.LAPoke, Method = Fixed }, // Rowlet
|
||||
new(155,000,05,M,M) { Location = 006, FixedBall = Ball.LAPoke, Method = Fixed }, // Cyndaquil
|
||||
new(501,000,05,M,M) { Location = 006, FixedBall = Ball.LAPoke, Method = Fixed }, // Oshawott
|
||||
new(037,001,40,M,M) { Location = 088, FixedBall = Ball.LAPoke, Method = Fixed }, // Vulpix-1
|
||||
new(483,000,65,M,M) { Location = 109, FlawlessIVCount = 3, FixedBall = Ball.LAOrigin, Method = Fixed }, // Dialga
|
||||
new(484,000,65,M,M) { Location = 109, FlawlessIVCount = 3, FixedBall = Ball.LAOrigin, Method = Fixed }, // Palkia
|
||||
new(493,000,75,M,M) { Location = 109, FlawlessIVCount = 3, FixedBall = Ball.LAPoke, Method = Fixed, FatefulEncounter = true }, // Arceus
|
||||
|
||||
// Static Encounters - Scripted Table Slots
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
namespace PKHeX.Core;
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Encounter data from <see cref="GameVersion.GO"/>, which has multiple generations of origin.
|
||||
/// </summary>
|
||||
#if !DEBUG
|
||||
internal static class EncountersGO
|
||||
{
|
||||
internal const int MAX_LEVEL = 50;
|
||||
internal static class EncountersGO
|
||||
{
|
||||
internal const int MAX_LEVEL = 50;
|
||||
|
||||
internal static readonly EncounterArea7g[] SlotsGO_GG = EncounterArea7g.GetArea(EncounterUtil.Get("go_lgpe", "go"));
|
||||
internal static readonly EncounterArea8g[] SlotsGO = EncounterArea8g.GetArea(EncounterUtil.Get("go_home", "go"));
|
||||
}
|
||||
internal static readonly EncounterArea7g[] SlotsGO_GG = EncounterArea7g.GetArea(EncounterUtil.Get("go_lgpe", "go"));
|
||||
internal static readonly EncounterArea8g[] SlotsGO = EncounterArea8g.GetArea(EncounterUtil.Get("go_home", "go"));
|
||||
}
|
||||
#else
|
||||
public static class EncountersGO
|
||||
{
|
||||
|
|
|
@ -95,10 +95,13 @@ public record struct EncounterEnumerator5(PKM Entity, EvoCriteria[] Chain, GameV
|
|||
State = YieldState.BredSplit;
|
||||
return SetCurrent(egg);
|
||||
case YieldState.BredSplit:
|
||||
State = Entity.Egg_Location == Locations.Daycare5 ? YieldState.End : YieldState.StartCaptures;
|
||||
if (!EncounterGenerator5.TryGetSplit((EncounterEgg)Current.Encounter, Chain, out egg))
|
||||
return MoveNext();
|
||||
return SetCurrent(egg);
|
||||
bool daycare = Entity.Egg_Location == Locations.Daycare5;
|
||||
State = daycare ? YieldState.End : YieldState.StartCaptures;
|
||||
if (EncounterGenerator5.TryGetSplit((EncounterEgg)Current.Encounter, Chain, out egg))
|
||||
return SetCurrent(egg);
|
||||
if (daycare)
|
||||
break; // no other encounters
|
||||
goto case YieldState.StartCaptures;
|
||||
|
||||
case YieldState.TradeStart:
|
||||
if (Version == GameVersion.W)
|
||||
|
|
|
@ -102,9 +102,10 @@ public sealed record EncounterTrade5B2W2 : IEncounterable, IEncounterMatch, IFix
|
|||
{
|
||||
if (pk.IsShiny)
|
||||
pk.PID ^= 0x1000_0000;
|
||||
pk.Nature = (int)criteria.GetNature(Nature.Random);
|
||||
pk.Gender = criteria.GetGender(-1, pi);
|
||||
pk.RefreshAbility(criteria.GetAbilityFromNumber(Ability));
|
||||
var nature = (int)criteria.GetNature(Nature);
|
||||
var gender = criteria.GetGender(Gender, pi);
|
||||
var ability = criteria.GetAbilityFromNumber(Ability);
|
||||
PIDGenerator.SetRandomWildPID5(pk, nature, ability, gender);
|
||||
criteria.SetRandomIVs(pk, IVs);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ public sealed record EncounterFixed9
|
|||
public Ball FixedBall => Ball.None;
|
||||
public bool IsShiny => false;
|
||||
public int EggLocation => 0;
|
||||
public AbilityPermission Ability => AbilityPermission.Any12;
|
||||
public AbilityPermission Ability => Any12;
|
||||
|
||||
public required ushort Species { get; init; }
|
||||
public required byte Form { get; init; }
|
||||
|
|
|
@ -92,7 +92,7 @@ public static class Roaming8bRNG
|
|||
pk.IV_SPE = ivs[5];
|
||||
|
||||
// Ability
|
||||
pk.SetAbilityIndex((int)xoro.NextUInt(2));
|
||||
pk.RefreshAbility((int)xoro.NextUInt(2));
|
||||
|
||||
// Remainder
|
||||
var scale = (IScaledSize)pk;
|
||||
|
|
|
@ -103,7 +103,7 @@ public static class Wild8bRNG
|
|||
AbilityPermission.Any12H => (int)xors.NextUInt(3),
|
||||
_ => (int)ability >> 1,
|
||||
};
|
||||
pk.SetAbilityIndex(n);
|
||||
pk.RefreshAbility(n);
|
||||
|
||||
// Gender (skip this if gender is fixed)
|
||||
var genderRatio = PersonalTable.BDSP.GetFormEntry(pk.Species, pk.Form).Gender;
|
||||
|
|
|
@ -22,7 +22,7 @@ public sealed class NicknameVerifier : Verifier
|
|||
data.AddLine(GetInvalid(LNickLengthShort));
|
||||
return;
|
||||
}
|
||||
if (pk.Species > SpeciesName.SpeciesLang[0].Count)
|
||||
if (pk.Species > SpeciesName.MaxSpeciesID)
|
||||
{
|
||||
data.AddLine(Get(LNickLengthShort, Severity.Invalid));
|
||||
return;
|
||||
|
@ -143,7 +143,7 @@ public sealed class NicknameVerifier : Verifier
|
|||
data.AddLine(GetInvalid(LNickMatchLanguageFlag));
|
||||
}
|
||||
|
||||
private bool VerifyUnNicknamedEncounter(LegalityAnalysis data, PKM pk, string nickname)
|
||||
private bool VerifyUnNicknamedEncounter(LegalityAnalysis data, PKM pk, ReadOnlySpan<char> nickname)
|
||||
{
|
||||
if (pk.IsNicknamed)
|
||||
{
|
||||
|
@ -157,11 +157,11 @@ public sealed class NicknameVerifier : Verifier
|
|||
return true;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < SpeciesName.SpeciesDict.Count; i++)
|
||||
foreach (var language in Language.GetAvailableGameLanguages(pk.Format))
|
||||
{
|
||||
if (!SpeciesName.SpeciesDict[i].TryGetValue(nickname, out var species))
|
||||
if (!SpeciesName.TryGetSpecies(nickname, language, out var species))
|
||||
continue;
|
||||
var msg = species == pk.Species && i != pk.Language ? LNickMatchNoOthersFail : LNickMatchLanguageFlag;
|
||||
var msg = species == pk.Species && language != pk.Language ? LNickMatchNoOthersFail : LNickMatchLanguageFlag;
|
||||
data.AddLine(Get(msg, ParseSettings.NicknamedAnotherSpecies));
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -84,8 +84,8 @@ public sealed class PIDVerifier : Verifier
|
|||
if (pk.Species == (int)Species.Wurmple)
|
||||
{
|
||||
// Indicate what it will evolve into
|
||||
uint evoVal = WurmpleUtil.GetWurmpleEvoVal(pk.EncryptionConstant);
|
||||
var evolvesTo = evoVal == 0 ? (int)Species.Beautifly : (int)Species.Dustox;
|
||||
var evoVal = WurmpleUtil.GetWurmpleEvoVal(pk.EncryptionConstant);
|
||||
var evolvesTo = evoVal == WurmpleEvolution.Silcoon ? (int)Species.Beautifly : (int)Species.Dustox;
|
||||
var species = ParseSettings.SpeciesStrings[evolvesTo];
|
||||
var msg = string.Format(L_XWurmpleEvo_0, species);
|
||||
data.AddLine(GetValid(msg, CheckIdentifier.EC));
|
||||
|
|
|
@ -127,7 +127,11 @@ public sealed class WC3 : MysteryGift, IRibbonSetEvent3, ILangNicknamedTemplate,
|
|||
pk.Language = (int)GetSafeLanguage((LanguageID)tr.Language);
|
||||
pk.OT_Name = !string.IsNullOrWhiteSpace(OT_Name) ? OT_Name : tr.OT;
|
||||
if (IsEgg)
|
||||
{
|
||||
pk.IsEgg = true; // lang should be set to japanese already
|
||||
if (pk.OT_Trash[0] == 0xFF)
|
||||
pk.OT_Name = "ゲーフリ";
|
||||
}
|
||||
}
|
||||
pk.Nickname = Nickname ?? SpeciesName.GetSpeciesNameGeneration(Species, pk.Language, 3); // will be set to Egg nickname if appropriate by PK3 setter
|
||||
|
||||
|
@ -200,6 +204,7 @@ public sealed class WC3 : MysteryGift, IRibbonSetEvent3, ILangNicknamedTemplate,
|
|||
var seed = Util.Rand32();
|
||||
seed = TID16 == 06930 ? MystryMew.GetSeed(seed, Method) : GetSaneSeed(seed);
|
||||
PIDGenerator.SetValuesFromSeed(pk, Method, seed);
|
||||
pk.RefreshAbility((int)(pk.EncryptionConstant & 1));
|
||||
}
|
||||
|
||||
private uint GetSaneSeed(uint seed) => Method switch
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
@ -19,7 +20,7 @@ public abstract class HomeOptional1
|
|||
protected HomeOptional1(ushort size) => Buffer = new byte[size];
|
||||
protected HomeOptional1(Memory<byte> buffer) => Buffer = buffer;
|
||||
|
||||
protected void EnsureSize(int size)
|
||||
protected void EnsureSize([ConstantExpected] int size)
|
||||
{
|
||||
if (Buffer.Length != size)
|
||||
throw new ArgumentOutOfRangeException(nameof(size), size, $"Expected size {Buffer.Length} but received {size}.");
|
||||
|
|
|
@ -27,7 +27,7 @@ public abstract class PKM : ISpeciesForm, ITrainerID32, IGeneration, IShiny, ILa
|
|||
public readonly byte[] Data; // Raw Storage
|
||||
|
||||
protected PKM(byte[] data) => Data = data;
|
||||
protected PKM(int size) => Data = new byte[size];
|
||||
protected PKM([ConstantExpected] int size) => Data = new byte[size];
|
||||
|
||||
public virtual byte[] EncryptedPartyData => Encrypt().AsSpan(0, SIZE_PARTY).ToArray();
|
||||
public virtual byte[] EncryptedBoxData => Encrypt().AsSpan(0, SIZE_STORED).ToArray();
|
||||
|
@ -939,25 +939,6 @@ public abstract class PKM : ISpeciesForm, ITrainerID32, IGeneration, IShiny, ILa
|
|||
SetIVs(ivs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Randomizes the IVs within game constraints.
|
||||
/// </summary>
|
||||
/// <param name="template">IV template to generate from</param>
|
||||
/// <param name="minFlawless">Count of flawless IVs to set. If none provided, a count will be detected.</param>
|
||||
/// <returns>Randomized IVs if desired.</returns>
|
||||
public void SetRandomIVsTemplate(IndividualValueSet template, int minFlawless = 0)
|
||||
{
|
||||
Span<int> ivs = stackalloc int[6];
|
||||
var rnd = Util.Rand;
|
||||
do
|
||||
{
|
||||
for (int i = 0; i < 6; i++)
|
||||
ivs[i] = template[i] < 0 ? rnd.Next(MaxIV + 1) : template[i];
|
||||
} while (ivs.Count(MaxIV) < minFlawless);
|
||||
|
||||
SetIVs(ivs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies all shared properties from the current <see cref="PKM"/> to the <see cref="result"/> <see cref="PKM"/>.
|
||||
/// </summary>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
|
@ -8,7 +9,7 @@ namespace PKHeX.Core;
|
|||
public abstract class G3PKM : PKM, IRibbonSetEvent3, IRibbonSetCommon3, IRibbonSetUnique3, IRibbonSetOnly3, IRibbonSetRibbons, IContestStats
|
||||
{
|
||||
protected G3PKM(byte[] data) : base(data) { }
|
||||
protected G3PKM(int size) : base(size) { }
|
||||
protected G3PKM([ConstantExpected] int size) : base(size) { }
|
||||
|
||||
public abstract override PersonalInfo3 PersonalInfo { get; }
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
|
@ -7,7 +8,7 @@ public abstract class G4PKM : PKM,
|
|||
IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetUnique3, IRibbonSetUnique4, IRibbonSetCommon3, IRibbonSetCommon4, IRibbonSetRibbons, IContestStats, IGroundTile
|
||||
{
|
||||
protected G4PKM(byte[] data) : base(data) { }
|
||||
protected G4PKM(int size) : base(size) { }
|
||||
protected G4PKM([ConstantExpected] int size) : base(size) { }
|
||||
|
||||
// Maximums
|
||||
public sealed override ushort MaxMoveID => Legal.MaxMoveID_4;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
|
@ -8,7 +9,7 @@ public abstract class G6PKM : PKM, ISanityChecksum
|
|||
public override int SIZE_PARTY => PokeCrypto.SIZE_6PARTY;
|
||||
public override int SIZE_STORED => PokeCrypto.SIZE_6STORED;
|
||||
protected G6PKM(byte[] data) : base(data) { }
|
||||
protected G6PKM(int size) : base(size) { }
|
||||
protected G6PKM([ConstantExpected] int size) : base(size) { }
|
||||
|
||||
// Trash Bytes
|
||||
public sealed override Span<byte> Nickname_Trash => Data.AsSpan(0x40, 26);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
|
@ -19,7 +20,7 @@ public abstract class GBPKM : PKM
|
|||
|
||||
public sealed override ReadOnlySpan<ushort> ExtraBytes => ReadOnlySpan<ushort>.Empty;
|
||||
|
||||
protected GBPKM(int size) : base(size) { }
|
||||
protected GBPKM([ConstantExpected] int size) : base(size) { }
|
||||
protected GBPKM(byte[] data) : base(data) { }
|
||||
|
||||
public sealed override byte[] EncryptedPartyData => Encrypt();
|
||||
|
|
|
@ -29,6 +29,11 @@ public static class Language
|
|||
private static ReadOnlySpan<byte> Languages_3 => Languages[..6]; // [..KOR)
|
||||
private const LanguageID SafeLanguage = English;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the available languages for the given generation.
|
||||
/// </summary>
|
||||
/// <param name="generation">Generation to check.</param>
|
||||
/// <returns>Available languages for the given generation.</returns>
|
||||
public static ReadOnlySpan<byte> GetAvailableGameLanguages(int generation = PKX.Generation) => generation switch
|
||||
{
|
||||
1 => Languages_3, // No KOR
|
||||
|
@ -40,6 +45,13 @@ public static class Language
|
|||
|
||||
private static bool HasLanguage(ReadOnlySpan<byte> permitted, byte language) => permitted.Contains(language);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the language that is safe to use for the given generation.
|
||||
/// </summary>
|
||||
/// <param name="generation">Generation to check.</param>
|
||||
/// <param name="prefer">Preferred language.</param>
|
||||
/// <param name="game">Game version to check.</param>
|
||||
/// <returns>Language that is safe to use for the given generation.</returns>
|
||||
public static LanguageID GetSafeLanguage(int generation, LanguageID prefer, GameVersion game = GameVersion.Any) => generation switch
|
||||
{
|
||||
1 when game == GameVersion.BU => Japanese,
|
||||
|
@ -50,6 +62,11 @@ public static class Language
|
|||
_ => HasLanguage(Languages, (byte)prefer) ? prefer : SafeLanguage,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the 2-character language name for the given <see cref="LanguageID"/>.
|
||||
/// </summary>
|
||||
/// <param name="language">Language ID to get the 2-character name for.</param>
|
||||
/// <returns>2-character language name.</returns>
|
||||
public static string GetLanguage2CharName(this LanguageID language) => language switch
|
||||
{
|
||||
Japanese => "ja",
|
||||
|
|
|
@ -13,7 +13,7 @@ public static class SpeciesName
|
|||
/// <summary>
|
||||
/// Species name lists indexed by the <see cref="LanguageID"/> value.
|
||||
/// </summary>
|
||||
public static readonly IReadOnlyList<IReadOnlyList<string>> SpeciesLang = new[]
|
||||
private static readonly IReadOnlyList<IReadOnlyList<string>> SpeciesLang = new[]
|
||||
{
|
||||
Util.GetSpeciesList("ja"), // 0 (unused, invalid)
|
||||
Util.GetSpeciesList("ja"), // 1
|
||||
|
@ -28,6 +28,11 @@ public static class SpeciesName
|
|||
Util.GetSpeciesList("zh2"), // 10 Traditional
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum valid species ID stored in the <see cref="SpeciesLang"/> list.
|
||||
/// </summary>
|
||||
public static readonly ushort MaxSpeciesID = (ushort)(SpeciesLang[0].Count - 1);
|
||||
|
||||
/// <summary>
|
||||
/// Egg name list indexed by the <see cref="LanguageID"/> value.
|
||||
/// </summary>
|
||||
|
@ -50,7 +55,25 @@ public static class SpeciesName
|
|||
/// <summary>
|
||||
/// <see cref="PKM.Nickname"/> to <see cref="Species"/> table for all <see cref="LanguageID"/> values.
|
||||
/// </summary>
|
||||
public static readonly IReadOnlyList<Dictionary<string, int>> SpeciesDict = Util.GetMultiDictionary(SpeciesLang, 1);
|
||||
private static readonly IReadOnlyList<Dictionary<int, ushort>> SpeciesDict = GetDictionary(SpeciesLang);
|
||||
|
||||
private static IReadOnlyList<Dictionary<int, ushort>> GetDictionary(IReadOnlyList<IReadOnlyList<string>> names)
|
||||
{
|
||||
var result = new Dictionary<int, ushort>[names.Count];
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
{
|
||||
var dict = new Dictionary<int, ushort>();
|
||||
var speciesList = names[i];
|
||||
for (ushort species = 1; species < speciesList.Count; species++)
|
||||
{
|
||||
var name = speciesList[species];
|
||||
var hash = string.GetHashCode(name);
|
||||
dict.Add(hash, species);
|
||||
}
|
||||
result[i] = dict;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a Pokémon's default name for the desired language ID.
|
||||
|
@ -270,19 +293,18 @@ public static class SpeciesName
|
|||
/// </summary>
|
||||
/// <param name="speciesName">Species Name</param>
|
||||
/// <param name="language">Language the name is from</param>
|
||||
/// <returns>Species ID</returns>
|
||||
/// <remarks>Only use this for modern era name -> ID fetching.</remarks>
|
||||
public static int GetSpeciesID(string speciesName, int language = (int)LanguageID.English)
|
||||
/// <param name="species">Species ID</param>
|
||||
/// <returns>True if the species was found, False if not</returns>
|
||||
public static bool TryGetSpecies(ReadOnlySpan<char> speciesName, int language, out ushort species)
|
||||
{
|
||||
if (SpeciesDict[language].TryGetValue(speciesName, out var value))
|
||||
return value;
|
||||
|
||||
// stupid ’, ignore language if we match these.
|
||||
return speciesName switch
|
||||
{
|
||||
"Farfetch'd" => (int)Species.Farfetchd,
|
||||
"Sirfetch'd" => (int)Species.Sirfetchd,
|
||||
_ => -1,
|
||||
};
|
||||
// Eventually we'll refactor this once Dictionary<string, ushort> supports ReadOnlySpan<char> fetching in NET 9 runtime/#27229
|
||||
var hash = string.GetHashCode(speciesName);
|
||||
var dict = SpeciesDict[language];
|
||||
if (!dict.TryGetValue(hash, out species))
|
||||
return false;
|
||||
// Double check the species name
|
||||
var arr = SpeciesLang[language];
|
||||
var expect = arr[species];
|
||||
return speciesName.SequenceEqual(expect);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
@ -39,7 +40,7 @@ public abstract class SAV5 : SaveFile, ISaveBlock5BW, IEventFlag37
|
|||
public override int MaxBallID => Legal.MaxBallID_5;
|
||||
public override int MaxGameID => Legal.MaxGameID_5; // B2
|
||||
|
||||
protected SAV5(int size) : base(size)
|
||||
protected SAV5([ConstantExpected] int size) : base(size)
|
||||
{
|
||||
Initialize();
|
||||
ClearBoxes();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
@ -40,7 +41,7 @@ public abstract class SAV_STADIUM : SaveFile, ILangDeviantSave
|
|||
IsPairSwapped = true;
|
||||
}
|
||||
|
||||
protected SAV_STADIUM(bool japanese, int size) : base(size)
|
||||
protected SAV_STADIUM(bool japanese, [ConstantExpected] int size) : base(size)
|
||||
{
|
||||
Japanese = japanese;
|
||||
OT = SaveUtil.GetSafeTrainerName(this, (LanguageID)Language);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
@ -7,7 +8,7 @@ public sealed class InventoryPouch3 : InventoryPouch
|
|||
{
|
||||
public uint SecurityKey { private get; set; } // = 0 // Gen3 Only
|
||||
|
||||
public InventoryPouch3(InventoryType type, IItemStorage info, int maxCount, int offset, int size)
|
||||
public InventoryPouch3(InventoryType type, IItemStorage info, int maxCount, int offset, [ConstantExpected] int size)
|
||||
: base(type, info, maxCount, offset, size)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed class InventoryPouch3GC : InventoryPouch
|
||||
{
|
||||
public InventoryPouch3GC(InventoryType type, IItemStorage info, int maxCount, int offset, int size)
|
||||
public InventoryPouch3GC(InventoryType type, IItemStorage info, int maxCount, int offset, [ConstantExpected] int size)
|
||||
: base(type, info, maxCount, offset, size)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
@ -13,7 +14,7 @@ public sealed class InventoryPouch7b : InventoryPouch
|
|||
|
||||
public override InventoryItem7b GetEmpty(int itemID = 0, int count = 0) => new() { Index = itemID, Count = count };
|
||||
|
||||
public InventoryPouch7b(InventoryType type, IItemStorage info, int maxCount, int offset, int size) : base(type, info, maxCount, offset, size) { }
|
||||
public InventoryPouch7b(InventoryType type, IItemStorage info, int maxCount, int offset, [ConstantExpected] int size) : base(type, info, maxCount, offset, size) { }
|
||||
|
||||
public override void GetPouch(ReadOnlySpan<byte> data)
|
||||
{
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
@ -11,7 +12,7 @@ public sealed class InventoryPouch8 : InventoryPouch
|
|||
public bool SetNew { get; set; }
|
||||
private int[] OriginalItems = Array.Empty<int>();
|
||||
|
||||
public InventoryPouch8(InventoryType type, IItemStorage info, int maxCount, int offset, int size) : base(type, info, maxCount, offset, size) { }
|
||||
public InventoryPouch8(InventoryType type, IItemStorage info, int maxCount, int offset, [ConstantExpected] int size) : base(type, info, maxCount, offset, size) { }
|
||||
|
||||
public override InventoryItem8 GetEmpty(int itemID = 0, int count = 0) => new() { Index = itemID, Count = count };
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed class InventoryPouchGB : InventoryPouch
|
||||
{
|
||||
public InventoryPouchGB(InventoryType type, IItemStorage info, int maxCount, int offset, int size)
|
||||
public InventoryPouchGB(InventoryType type, IItemStorage info, int maxCount, int offset, [ConstantExpected] int size)
|
||||
: base(type, info, maxCount, offset, size)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -221,20 +221,4 @@ public static partial class Util
|
|||
int index = input.IndexOf(c);
|
||||
return index < 0 ? input : input[..index];
|
||||
}
|
||||
|
||||
public static Dictionary<string, int>[] GetMultiDictionary(IReadOnlyList<IReadOnlyList<string>> nameArray, int start)
|
||||
{
|
||||
var result = new Dictionary<string, int>[nameArray.Count];
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
result[i] = GetDictionary(nameArray[i], start);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Dictionary<string, int> GetDictionary(IReadOnlyList<string> names, int start)
|
||||
{
|
||||
var result = new Dictionary<string, int>(names.Count - start);
|
||||
for (int i = start; i < names.Count; i++)
|
||||
result.Add(names[i], i);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1751,12 +1751,10 @@ public sealed partial class PKMEditor : UserControl, IMainEditor
|
|||
|
||||
private void ValidateMovePaint(object? sender, DrawItemEventArgs e)
|
||||
{
|
||||
if (sender is null || e.Index < 0)
|
||||
if (sender is not ComboBox cb || e.Index < 0 || cb.Items[e.Index] is not ComboItem item)
|
||||
return;
|
||||
|
||||
var cb = (ComboBox)sender;
|
||||
var item = cb.Items[e.Index];
|
||||
var (text, value) = (ComboItem)item;
|
||||
var (text, value) = item;
|
||||
var valid = LegalMoveSource.Info.CanLearn((ushort)value) && !HaX;
|
||||
|
||||
var current = (e.State & DrawItemState.Selected) != 0;
|
||||
|
|
|
@ -28,7 +28,7 @@ public partial class SAV_EventReset1 : Form
|
|||
var specName = split[0][G1OverworldSpawner.FlagPropertyPrefix.Length..];
|
||||
|
||||
// convert species name to current localization language
|
||||
var species = SpeciesName.GetSpeciesID(specName);
|
||||
SpeciesName.TryGetSpecies(specName, (int)LanguageID.English, out var species);
|
||||
var pkmname = GameInfo.Strings.specieslist[species];
|
||||
|
||||
if (split.Length != 1)
|
||||
|
|
|
@ -235,9 +235,13 @@ public partial class SAV_PokedexLA : Form
|
|||
{
|
||||
var selectedForm = Dex.GetSelectedForm(species);
|
||||
CB_DisplayForm.SelectedIndex = 0;
|
||||
for (var i = 0; i < CB_DisplayForm.Items.Count; ++i)
|
||||
var items = CB_DisplayForm.Items;
|
||||
for (var i = 0; i < items.Count; ++i)
|
||||
{
|
||||
if (((ComboItem)CB_DisplayForm.Items[i]).Value != selectedForm)
|
||||
var item = items[i];
|
||||
if (item is not ComboItem cb)
|
||||
throw new Exception("Invalid item type");
|
||||
if (cb.Value != selectedForm)
|
||||
continue;
|
||||
|
||||
CB_DisplayForm.SelectedIndex = i;
|
||||
|
|
|
@ -203,7 +203,7 @@ public static class WinFormsTranslator
|
|||
var argCount = constructors[0].GetParameters().Length;
|
||||
try
|
||||
{
|
||||
var _ = (Form?)Activator.CreateInstance(t, new object[argCount]);
|
||||
_ = (Form?)Activator.CreateInstance(t, new object[argCount]);
|
||||
}
|
||||
// This is a debug utility method, will always be logging. Shouldn't ever fail.
|
||||
catch
|
||||
|
|
|
@ -22,6 +22,28 @@ public class StringQualityTests
|
|||
CheckMetLocations(language);
|
||||
CheckItemNames(language);
|
||||
CheckMoveNames(language);
|
||||
CheckSpeciesNames(language);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks for duplicate hashes in the species list.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Uses hashes instead of strings as other logic uses dictionaries of hashes.
|
||||
/// </remarks>
|
||||
private static void CheckSpeciesNames(string language)
|
||||
{
|
||||
var strings = GameInfo.GetStrings(language);
|
||||
var arr = strings.specieslist;
|
||||
var hashset = new HashSet<int>(arr.Length);
|
||||
var duplicates = new List<string>(0);
|
||||
foreach (var line in arr)
|
||||
{
|
||||
var hash = line.GetHashCode();
|
||||
if (!hashset.Add(hash))
|
||||
duplicates.Add(line);
|
||||
}
|
||||
duplicates.Count.Should().Be(0, "expected no duplicate strings.");
|
||||
}
|
||||
|
||||
private static void CheckMoveNames(string language)
|
||||
|
|
Loading…
Reference in a new issue