Changes for Legends: Z-A support (#4596)

Refer to pull request notes and the eventual changelog for a high-level summary.

Co-authored-by: Matt <17801814+sora10pls@users.noreply.github.com>
Co-authored-by: Lusamine <30205550+Lusamine@users.noreply.github.com>
Co-authored-by: SciresM <8676005+SciresM@users.noreply.github.com>
This commit is contained in:
Kurt 2025-10-26 19:01:44 -05:00 committed by GitHub
parent ae526a5bd5
commit fd1c538cc5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
556 changed files with 25614 additions and 900 deletions

View File

@ -24,7 +24,7 @@ PKHeX erwartet entschlüsselte Spielstände. Da diese konsolenspezifisch verschl
## Screenshots
![Main Window](https://i.imgur.com/7ErmRJI.png)
![Main Window](https://i.imgur.com/0KYz0rO.png)
## Erstellen

View File

@ -24,7 +24,7 @@ PKHeX espera archivos de guardado que no estén cifrados con las claves específ
## Capturas de Pantalla
![Pantalla principal](https://i.imgur.com/oM407mV.png)
![Pantalla principal](https://i.imgur.com/JFKIhnz.png)
## Building

View File

@ -23,7 +23,7 @@ PKHeX attend des fichiers de sauvegarde qui ne sont pas chiffrés avec des clés
## Captures d'écran
![Main Window](https://i.imgur.com/YEdBzlt.png)
![Main Window](https://i.imgur.com/CpUzqmY.png)
## Construction

View File

@ -24,7 +24,7 @@ PKHeX si aspetta file di salvataggio non criptati con le chiavi specifiche della
## Screenshots
![Main Window](https://i.imgur.com/ICmQ41m.png)
![Main Window](https://i.imgur.com/vrWs9Xq.png)
## Building

View File

@ -24,7 +24,7 @@ PKHeX는 콘솔 전용 키로 암호화되지 않은 세이브 파일을 요구
## 스크린샷
![Main Window](https://i.imgur.com/HZs37cM.png)
![Main Window](https://i.imgur.com/vDiaS7k.png)
## 빌드

View File

@ -24,7 +24,7 @@ PKHeX 所读取存档文件必须是未经主机唯一密钥加密,因此请
## 截图
![主介面](https://i.imgur.com/SfskT2Q.png)
![主介面](https://i.imgur.com/MPN4Hk9.png)
## 构建

View File

@ -24,7 +24,7 @@ PKHeX 所讀取檔案須未經主機唯一密鑰加密,因而請使用儲存
## 螢幕擷取截圖
![主介面](https://i.imgur.com/zEGGuJC.png)
![主介面](https://i.imgur.com/8IQx2jo.png)
## 構建

View File

@ -0,0 +1,195 @@
using System;
namespace PKHeX.Core;
/// <summary>
/// Logic for modifying the Plus Record flags of a <see cref="PA9"/>.
/// </summary>
public static class PlusRecordApplicator
{
/// <summary>
/// Sets all the Plus Record flags for the <see cref="record"/> to the given value.
/// </summary>
/// <param name="record">Pokémon to modify.</param>
/// <param name="count">Total count of flags to modify [0,x).</param>
/// <param name="value">Value to set for each record.</param>
public static void SetPlusFlagsAll(this IPlusRecord record, int count, bool value)
{
for (int i = 0; i < count; i++)
record.SetMovePlusFlag(i, value);
}
/// <summary>
/// Clears the Plus Record flags for the <see cref="record"/>.
/// </summary>
/// <param name="record">Pokémon to modify.</param>
/// <param name="count">Total count of flags to modify [0,x).</param>
public static void ClearPlusFlags(this IPlusRecord record, int count) => record.SetPlusFlagsAll(count, false);
/// <summary>
/// Sets the Plus Record flags for the <see cref="record"/> based on the legality of learning moves.
/// </summary>
/// <param name="record">Pokémon to modify.</param>
/// <param name="permit">Sanity check to retrieve plus record indexes.</param>
/// <param name="la">Legality analysis of the Pokémon.</param>
/// <param name="seedOfMastery">Use a Seed of Mastery to bypass the level requirement of mastering the move.</param>
/// <param name="tm">Apply TM flags as Plus too.</param>
public static void SetPlusFlags(this IPlusRecord record, IPermitPlus permit, LegalityAnalysis la, bool seedOfMastery, bool tm)
{
// Hopefully this is only called for Legends: Z-A format entities.
var entity = la.Entity;
var context = entity.Context;
var evos = la.Info.EvoChainsAllGens.Get(context);
switch (la.Entity.Context)
{
case EntityContext.Gen9a:
{
var learn = LearnSource9ZA.Instance;
SetPlusFlagsNatural(record, permit, evos, learn, seedOfMastery);
if (tm)
{
var table = PersonalTable.ZA;
SetPlusFlagsTM<PersonalTable9ZA, PersonalInfo9ZA, LearnSource9ZA>(record, permit, evos, learn, seedOfMastery, table);
}
break;
}
default:
throw new Exception("Format not supported.");
}
}
public static void SetPlusFlagsNatural<TSource>(IPlusRecord record, IPermitPlus permit, ReadOnlySpan<EvoCriteria> evos, TSource source, bool seedOfMastery) where TSource : ILearnSourceBonus
{
var indexes = permit.PlusMoveIndexes;
foreach (var evo in evos)
{
var (levelUp, plus) = source.GetLearnsetAndOther(evo.Species, evo.Form);
var set = seedOfMastery ? levelUp : plus;
var levels = set.GetAllLevels();
var moves = set.GetAllMoves();
for (int i = 0; i < levels.Length; i++)
{
if (evo.LevelMax < levels[i])
break;
var move = moves[i];
var index = indexes.IndexOf(move);
record.SetMovePlusFlag(index);
}
}
}
public static void SetPlusFlagsTM<TTable, TInfo, TSource>(IPlusRecord record, IPermitPlus permit,
ReadOnlySpan<EvoCriteria> evos,
TSource source, bool seedOfMastery,
TTable table)
where TTable : IPersonalTable<TInfo>
where TInfo : IPersonalInfo, IPersonalInfoTM
where TSource : ILearnSourceBonus
{
var indexes = permit.PlusMoveIndexes;
foreach (var evo in evos)
{
var pi = table[evo.Species, evo.Form];
var (levelUp, plus) = source.GetLearnsetAndOther(evo.Species, evo.Form);
var set = seedOfMastery ? levelUp : plus;
for (int index = 0; index < indexes.Length; index++)
{
var move = indexes[index];
var tmIndex = permit.RecordPermitIndexes.IndexOf(move);
if (tmIndex != -1 && pi.GetIsLearnTM(tmIndex))
record.SetMovePlusFlag(index);
}
}
}
/// <summary>
/// Sets all moves that would be learned and naturally available as Plus based on the given level
/// </summary>
/// <param name="record">Record to modify</param>
/// <param name="permit">Permit to use</param>
/// <param name="plus">Learnset to use</param>
/// <param name="level">Current level</param>
/// <param name="extra">Extra moves to set as Plus</param>
public static void SetPlusFlagsEncounter(IPlusRecord record, IPermitPlus permit, Learnset plus, byte level, params ReadOnlySpan<ushort> extra)
{
var indexes = permit.PlusMoveIndexes;
var levels = plus.GetAllLevels();
var moves = plus.GetAllMoves();
for (int i = 0; i < levels.Length; i++)
{
if (level < levels[i])
break;
var move = moves[i];
var index = indexes.IndexOf(move);
record.SetMovePlusFlag(index);
}
if (extra.Length != 0)
SetPlusFlagsSpecific(record, permit, extra);
}
public static void SetPlusFlagsSpecific(IPlusRecord record, IPermitPlus permit, ReadOnlySpan<ushort> extra)
{
var indexes = permit.PlusMoveIndexes;
foreach (var move in extra)
{
var index = indexes.IndexOf(move);
record.SetMovePlusFlag(index);
}
}
public static void SetPlusFlags<T>(this T pk, IPermitPlus permit, PlusRecordApplicatorOption option)
where T : PKM, IPlusRecord
=> SetPlusFlags(pk, pk, permit, option);
public static void SetPlusFlags(this IPlusRecord record, PKM pk, IPermitPlus permit, PlusRecordApplicatorOption option)
{
record.ClearPlusFlags(permit.PlusCountTotal);
if (option is PlusRecordApplicatorOption.None)
return;
if (option is PlusRecordApplicatorOption.ForceAll)
{
record.SetPlusFlagsAll(permit.PlusCountUsed, true);
return;
}
var la = new LegalityAnalysis(pk);
SetPlusFlagsInternal(record, permit, option, la);
}
public static void SetPlusFlags(this IPlusRecord record, IPermitPlus permit, PlusRecordApplicatorOption option, LegalityAnalysis la)
{
record.ClearPlusFlags(permit.PlusCountTotal);
if (option is PlusRecordApplicatorOption.None)
return;
if (option is PlusRecordApplicatorOption.ForceAll)
{
record.SetPlusFlagsAll(permit.PlusCountUsed, true);
return;
}
SetPlusFlagsInternal(record, permit, option, la);
}
private static void SetPlusFlagsInternal(IPlusRecord record, IPermitPlus permit, PlusRecordApplicatorOption option, LegalityAnalysis la)
{
if (option is PlusRecordApplicatorOption.LegalCurrent)
record.SetPlusFlags(permit, la, false, false);
else if (option is PlusRecordApplicatorOption.LegalCurrentTM)
record.SetPlusFlags(permit, la, false, true);
else if (option is PlusRecordApplicatorOption.LegalSeedTM)
record.SetPlusFlags(permit, la, true, true);
}
}
public enum PlusRecordApplicatorOption
{
None,
ForceAll,
LegalCurrent,
LegalCurrentTM,
LegalSeedTM,
}

View File

@ -104,6 +104,8 @@ public static void SetRecordFlags(this ITechRecord pk, ReadOnlySpan<ushort> move
{
if (pk is PK9 pk9)
SetRecordFlags<PersonalTable9SV, PersonalInfo9SV>(pk9, moves, evos, PersonalTable.SV);
else if (pk is PA9 pa9)
SetRecordFlags<PersonalTable9ZA, PersonalInfo9ZA>(pa9, moves, evos, PersonalTable.ZA);
else if (pk is PK8 pk8)
SetRecordFlags<PersonalTable8SWSH, PersonalInfo8SWSH>(pk8, moves, evos, PersonalTable.SWSH);
}
@ -113,6 +115,8 @@ public static void SetRecordFlagsAll(this ITechRecord pk, ReadOnlySpan<EvoCriter
{
if (pk is PK9 pk9)
SetRecordFlagsAll<PersonalTable9SV, PersonalInfo9SV>(pk9, evos, PersonalTable.SV);
else if (pk is PA9 pa9)
SetRecordFlagsAll<PersonalTable9ZA, PersonalInfo9ZA>(pa9, evos, PersonalTable.ZA);
else if (pk is PK8 pk8)
SetRecordFlagsAll<PersonalTable8SWSH, PersonalInfo8SWSH>(pk8, evos, PersonalTable.SWSH);
}
@ -121,6 +125,7 @@ public static void SetRecordFlagsAll(this ITechRecord pk, ReadOnlySpan<EvoCriter
public static bool IsRecordPermitted(this ITechRecord pk, ReadOnlySpan<EvoCriteria> evos, int index) => pk switch
{
PK9 => IsRecordPermitted<PersonalTable9SV, PersonalInfo9SV>(evos, PersonalTable.SV, index),
PA9 => IsRecordPermitted<PersonalTable9ZA, PersonalInfo9ZA>(evos, PersonalTable.ZA, index),
PK8 => IsRecordPermitted<PersonalTable8SWSH, PersonalInfo8SWSH>(evos, PersonalTable.SWSH, index),
_ => false,
};

View File

@ -49,7 +49,7 @@ public sealed record BattleTemplateTuple(BattleTemplateToken Token, string Text)
public static ReadOnlySpan<char> GetMoveDisplay(MoveDisplayStyle style = MoveDisplayStyle.Fill) => style switch
{
MoveDisplayStyle.Fill => "----",
MoveDisplayStyle.Directional => "↑←↓",
MoveDisplayStyle.Directional => "↑←↓",
_ => throw new ArgumentOutOfRangeException(nameof(style), style, null),
};

View File

@ -70,7 +70,7 @@ private static LanguageID GetLanguage(LanguageID choice, LanguageID program)
private static MoveDisplayStyle GetMoveDisplayStyle(MoveDisplayStyle style, EntityContext context) => style switch
{
//MoveDisplayStyle.Directional when context is EntityContext.Gen9a => MoveDisplayStyle.Directional, TODO ZA
MoveDisplayStyle.Directional when context is EntityContext.Gen9a => MoveDisplayStyle.Directional,
_ => MoveDisplayStyle.Fill,
};
}

View File

@ -154,6 +154,9 @@ public static string GetFormNameFromShowdownFormName(ushort species, string form
(int)Maushold when form is "Four" => "Family of Four",
(int)Urshifu or (int)Pikachu or (int)Alcremie => form.Replace('-', ' '), // Strike and Cosplay
(int)Pumpkaboo or (int)Gourgeist when form is "Average" => "Medium",
(int)Pumpkaboo or (int)Gourgeist when form is "Super" => "Jumbo",
_ => FormInfo.HasTotemForm(species) && form.EndsWith("Totem", StringComparison.OrdinalIgnoreCase) ? "Large" : form,
};
}

View File

@ -523,7 +523,7 @@ private static string GetSpeciesNickname(string specForm, string nickname, ushor
{
if (nickname.Length == 0 || nickname == specForm)
return specForm;
bool isNicknamed = SpeciesName.IsNicknamedAnyLanguage(species, nickname, context.Generation());
bool isNicknamed = SpeciesName.IsNicknamedAnyLanguage(species, nickname, context);
if (!isNicknamed)
return specForm;
return $"{nickname} ({specForm})";

View File

@ -17,7 +17,7 @@ public static class BatchEditing
{
public static readonly Type[] Types =
[
typeof (PK9),
typeof (PK9), typeof (PA9),
typeof (PK8), typeof (PA8), typeof (PB8),
typeof (PB7),
typeof (PK7), typeof (PK6), typeof (PK5), typeof (PK4), typeof(BK4), typeof(RK4),
@ -30,7 +30,7 @@ public static class BatchEditing
/// </summary>
private static readonly string[] CustomProperties =
[
PROP_LEGAL, PROP_TYPENAME, PROP_RIBBONS, PROP_CONTESTSTATS, PROP_MOVEMASTERY,
PROP_LEGAL, PROP_TYPENAME, PROP_RIBBONS, PROP_CONTESTSTATS, PROP_MOVEMASTERY, PROP_MOVEPLUS,
PROP_TYPE1, PROP_TYPE2, PROP_TYPEEITHER,
IdentifierContains, nameof(ISlotInfo.Slot), nameof(SlotInfoBox.Box),
];
@ -77,6 +77,7 @@ public static class BatchEditing
internal const string PROP_EVS = "EVs";
internal const string PROP_CONTESTSTATS = "ContestStats";
internal const string PROP_MOVEMASTERY = "MoveMastery";
internal const string PROP_MOVEPLUS = "PlusMoves";
internal const string IdentifierContains = nameof(IdentifierContains);
private static string[][] GetPropArray<T>(Dictionary<string, T>.AlternateLookup<ReadOnlySpan<char>>[] types, ReadOnlySpan<string> extra)

View File

@ -52,6 +52,7 @@ public static class BatchMods
new ComplexSuggestion(nameof(PKM.CurrentLevel), (_, _, info) => BatchModifications.SetMinimumCurrentLevel(info)),
new ComplexSuggestion(PROP_CONTESTSTATS, p => p is IContestStats, (_, value, info) => BatchModifications.SetContestStats(info.Entity, info.Legality, value)),
new ComplexSuggestion(PROP_MOVEMASTERY, (_, value, info) => BatchModifications.SetSuggestedMasteryData(info, value)),
new ComplexSuggestion(PROP_MOVEPLUS, (_, value, info) => BatchModifications.SetSuggestedMovePlusData(info, value)),
];
private static DateOnly ParseDate(ReadOnlySpan<char> val) => DateOnly.ParseExact(val, "yyyyMMdd", CultureInfo.InvariantCulture);

View File

@ -69,6 +69,28 @@ public static ModifyResult SetSuggestedMasteryData(BatchInfo info, ReadOnlySpan<
return ModifyResult.Modified;
}
/// <summary>
/// Sets all legal Plus Move flag data for the Entity.
/// </summary>
/// <remarks>Only applicable for <see cref="IPlusRecord"/>.</remarks>
public static ModifyResult SetSuggestedMovePlusData(BatchInfo info, ReadOnlySpan<char> value)
{
var pk = info.Entity;
if (pk is not IPlusRecord t || pk.PersonalInfo is not IPermitPlus p)
return ModifyResult.Skipped;
PlusRecordApplicatorOption option;
if (IsNone(value))
option = PlusRecordApplicatorOption.None;
else if (IsAll(value))
option = PlusRecordApplicatorOption.LegalSeedTM;
else
option = PlusRecordApplicatorOption.LegalCurrent;
t.SetPlusFlags(p, option, info.Legality);
return ModifyResult.Modified;
}
/// <summary>
/// Sets suggested ribbon data for the Entity.
/// </summary>

View File

@ -58,7 +58,8 @@ public static void SetAbility(this PKM pk, int abilityID)
if (abilityID < 0)
return;
var index = pk.PersonalInfo.GetIndexOfAbility(abilityID);
index = Math.Max(0, index);
if (index < 0)
return; // leave original value
pk.SetAbilityIndex(index);
}
@ -258,6 +259,13 @@ public static void ApplySetDetails(this PKM pk, IBattleTemplate set)
t.ClearRecordFlags();
t.SetRecordFlags(set.Moves, legal.Info.EvoChainsAllGens.Get(pk.Context));
}
if (pk is IPlusRecord plus && pk.PersonalInfo is IPermitPlus permit)
{
plus.ClearPlusFlags(permit.PlusCountTotal);
plus.SetPlusFlags(permit, legal, true, true);
}
if (legal.Parsed && !MoveResult.AllValid(legal.Info.Relearn))
pk.SetRelearnMoves(legal);
pk.ResetPartyStats();

View File

@ -23,6 +23,7 @@ public static class Pokerus
PA8 pa8 => HasVisitedAnother(pa8, enc),
PB7 => false, // Does not exist in game.
PK9 => false, // Does not exist in game, does not get copied over via HOME.
PA9 => false, // Does not exist in game, does not get copied over via HOME.
_ => true,
};
@ -60,6 +61,7 @@ private static bool HasVisitedAnother(PA8 pk, ISpeciesForm enc)
EntityContext.Gen7b => false,
EntityContext.Gen8a => false,
EntityContext.Gen9 => false,
EntityContext.Gen9a => false,
_ => true,
};

View File

@ -44,6 +44,7 @@ public static PKM[] GetExtraPKM(this SaveFile sav, IReadOnlyList<SlotInfoMisc> s
SAV8BS bs => GetExtraSlots8b(bs),
SAV8LA la => GetExtraSlots8a(la),
SAV9SV sv => GetExtraSlots9(sv),
SAV9ZA za => GetExtraSlots9a(za),
_ => None,
};
@ -240,4 +241,38 @@ private static List<SlotInfoMisc> GetExtraSlots9(SAV9SV sav)
}
return list;
}
private static List<SlotInfoMisc> GetExtraSlots9a(SAV9ZA sav)
{
var list = new List<SlotInfoMisc>();
var shinyCache = sav.Blocks.GetBlock(SaveBlockAccessor9ZA.KStoredShinyEntity);
for (int i = 0; i < 10; i++)
{
const int size = 0x1F0;
var ofs = (i * size) + 8;
var entry = shinyCache.Raw.Slice(ofs, size);
if (EntityDetection.IsPresent(entry.Span))
list.Add(new(entry, i, true) { Type = StorageSlotType.Shiny, HideLegality = true }); // no OT info
else
break;
}
var giveAway = sav.Blocks.GetBlock(SaveBlockAccessor9ZA.KStoredEventEntity);
for (int i = 0; i < 128; i++)
{
const int size = 0x1A8;
var ofs = (i * size) + 8;
var entry = giveAway.Raw.Slice(ofs, PokeCrypto.SIZE_9PARTY);
if (EntityDetection.IsPresent(entry.Span))
list.Add(new(entry, i, true) { Type = StorageSlotType.Misc });
else
break;
}
var block = sav.Blocks.GetBlock(SaveBlockAccessor9ZA.KFusedCalyrex);
list.Add(new(block.Raw, 0, true) { Type = StorageSlotType.FusedCalyrex });
return list;
}
}

View File

@ -16,6 +16,8 @@ public enum StorageSlotType : byte
Daycare,
/// <summary> Global Trade Station (GTS) </summary>
GTS,
/// <summary> Shiny Overworld Cache </summary>
Shiny,
/// <summary> Fused Legendary Storage </summary>
FusedKyurem,

View File

@ -233,6 +233,11 @@ public enum GameVersion : byte
/// Pokémon Violet (NX)
/// </summary>
VL = 51,
/// <summary>
/// Pokémon Legends: (Z-A) (NX)
/// </summary>
ZA = 52,
#endregion
// The following values are not actually stored values in pk data,

View File

@ -61,4 +61,9 @@ public enum LanguageID : byte
/// Chinese Traditional (繁體中文)
/// </summary>
ChineseT = 10,
/// <summary>
/// Spanish (LATAM)
/// </summary>
SpanishL = 11,
}

View File

@ -925,5 +925,6 @@ public enum Move : ushort
PsychicNoise,
UpperHand,
MalignantChain,
NihilLight,
MAX_COUNT,
}

View File

@ -31,7 +31,7 @@ public FilteredGameDataSource(SaveFile sav, GameDataSource source, bool HaX = fa
var gamelist = GameUtil.GetVersionsWithinRange(sav, sav.Generation).ToList();
Games = Source.VersionDataSource.Where(g => gamelist.Contains((GameVersion)g.Value) || g.Value == 0).ToList();
Languages = Source.LanguageDataSource(sav.Generation);
Languages = Source.LanguageDataSource(sav.Generation, sav.Context);
Balls = Source.BallDataSource.Where(b => b.Value <= sav.MaxBallID).ToList();
Abilities = Source.AbilityDataSource.Where(a => a.Value <= sav.MaxAbilityID).ToList();
@ -57,6 +57,7 @@ public FilteredGameDataSource(SaveFile sav, GameDataSource source, bool HaX = fa
// BD/SP can be handled by <= MaxSpeciesID as it as no gaps in species availability.
SAV8SWSH g8 => FilterUnavailable(result, g8.Personal),
SAV9SV g9 => FilterUnavailable(result, g9.Personal),
SAV9ZA za => FilterUnavailable(result, za.Personal),
_ => FilterAbove(result, sav.MaxSpeciesID),
};
return result;

View File

@ -29,6 +29,7 @@ public sealed class GameDataSource
(byte)LanguageID.Korean,
(byte)LanguageID.ChineseS,
(byte)LanguageID.ChineseT,
(byte)LanguageID.SpanishL,
];
/// <summary>
@ -36,11 +37,12 @@ public sealed class GameDataSource
/// </summary>
/// <param name="generation">Generation to get the language list for.</param>
/// <returns>List of languages to display.</returns>
public IReadOnlyList<ComboItem> LanguageDataSource(byte generation) => generation switch
public IReadOnlyList<ComboItem> LanguageDataSource(byte generation, EntityContext context) => generation switch
{
3 => LanguageList[..6], // No Korean+
< 7 => LanguageList[..7], // No Chinese+
_ => [.. LanguageList],
_ when context is EntityContext.Gen9a => [.. LanguageList],
_ => [.. LanguageList.AsSpan(0, LanguageList.Length - 1)],
};
public GameDataSource(GameStrings s)
@ -97,6 +99,7 @@ public GameDataSource(GameStrings s)
/// <remarks>Most recent games are at the top, loosely following Generation groups.</remarks>
private static ReadOnlySpan<byte> OrderedVersionArray =>
[
52, // 9 Z-A
50, 51, // 9 S/V
47, // 8 PLA
48, 49, // 8 BD/SP

View File

@ -38,8 +38,8 @@ public static string GetVersionName(GameVersion version)
return version.ToString();
}
public static IReadOnlyList<ComboItem> LanguageDataSource(byte generation)
=> Sources.LanguageDataSource(generation);
public static IReadOnlyList<ComboItem> LanguageDataSource(byte generation, EntityContext context)
=> Sources.LanguageDataSource(generation, context);
/// <summary>
/// Gets the location name for the specified parameters.

View File

@ -21,7 +21,7 @@ public sealed class GameStrings : IBasicStrings
// Met Locations
public readonly LocationSet0 Gen2, Gen3, CXD;
public readonly LocationSet4 Gen4;
public readonly LocationSet6 Gen5, Gen6, Gen7, Gen7b, Gen8, Gen8a, Gen8b, Gen9;
public readonly LocationSet6 Gen5, Gen6, Gen7, Gen7b, Gen8, Gen8a, Gen8b, Gen9, Gen9a;
// Misc
public readonly string[] wallpapernames, puffs, walkercourses;
@ -143,6 +143,7 @@ internal GameStrings(string langFilePrefix)
Gen8a = Get6("la");
Gen8b = Get6("bdsp");
Gen9 = Get6("sv");
Gen9a = Get6("za");
Sanitize();
@ -282,6 +283,7 @@ private void SanitizeItemNames()
SanitizeItemsLA(itemlist);
SanitizeItemsSV(itemlist);
SanitizeItemsZA(itemlist);
if (Language is French)
{
@ -300,6 +302,26 @@ private void SanitizeItemNames()
itemlist[1763] += " (LA)"; // Secret Medicine
}
private void SanitizeItemsZA(Span<string> items)
{
// Seed of Mastery
items[1622] += " (LA)";
items[2558] += " (ZA)";
// Canari Plushes
Canari(items[2620..]); // Red
Canari(items[2623..]); // Gold
Canari(items[2626..]); // Pink
Canari(items[2629..]); // Green
Canari(items[2632..]); // Blue
return;
static void Canari(Span<string> arr)
{
arr[0] += " (1)";
arr[1] += " (2)";
arr[2] += " (3)";
}
}
private static void SanitizeItemsSV(Span<string> items)
{
items[2313] += " (1)"; // Academy Bottle
@ -397,6 +419,7 @@ private void SanitizeMetLocations()
SanitizeMetGen8a(Gen8a);
SanitizeMetGen8b(Gen8b);
SanitizeMetGen9(Gen9);
SanitizeMetGen9a(Gen9a);
if (Language is Italian or Spanish)
{
@ -692,6 +715,15 @@ private void SanitizeMetGen9(LocationSet6 set)
// set.Met3[18] += " (-)"; // Pokémon HOME -- duplicate with 40000's entry
}
private static void SanitizeMetGen9a(LocationSet6 set)
{
// ZA: Truncated list to remove all after 235 (no encounters there).
Deduplicate(set.Met0, 00000);
Deduplicate(set.Met3, 30000);
Deduplicate(set.Met4, 40000);
Deduplicate(set.Met6, 60000);
}
private static void Deduplicate(Span<string> arr, int group)
{
var counts = new Dictionary<string, int>();
@ -737,6 +769,7 @@ private static void Deduplicate(Span<string> arr, int group)
EntityContext.Gen4 => g4items, // mail names changed 4->5
EntityContext.Gen8b => GetItemStrings8b(),
EntityContext.Gen9 => GetItemStrings9(),
EntityContext.Gen9a => GetItemStrings9a(),
_ => itemlist,
};
@ -778,6 +811,48 @@ static void InsertZero(Span<string> arr, string insert)
}
}
private string[] GetItemStrings9a()
{
// in Generation 9, TM #'s are padded to 3 digits; format them appropriately here
var clone = (string[])itemlist.Clone();
var span = clone.AsSpan();
var zero = Language is Japanese or ChineseS or ChineseT ? '' : '0';
var prefix = span[328].AsSpan(0, 2);
InsertZero(prefix, span[328..420], zero, 1); // 01-92
InsertZero(prefix, span[618..621], zero, 93); // 93-95
InsertZero(prefix, span[690..694], zero, 96); // 96-99
InsertZero(prefix, span[2160..2290], zero, 100); // 100-229
return clone;
static void InsertZero(ReadOnlySpan<char> prefix, Span<string> arr, char zero, int start)
{
int i = 0;
foreach (ref var item in arr)
{
var index = start + i;
var remap = ItemConverter.RemapTechnicalMachineItemName9a;
if (index >= remap.Length)
break;
index += remap[index];
item = GetActualName(prefix, index, zero);
i++;
}
arr[i..].Clear();
}
static string GetActualName(ReadOnlySpan<char> prefix, int value, char pad)
{
Span<char> buffer = stackalloc char[5];
prefix.CopyTo(buffer);
var arr = buffer[2..];
arr.Fill(pad);
arr[2] += (char)(value % 10); value /= 10;
arr[1] += (char)(value % 10); value /= 10;
arr[0] += (char)(value % 10);
return new string(buffer);
}
}
private string[] GetItemStrings3(GameVersion version)
{
switch (version)
@ -869,6 +944,7 @@ private static byte GetGeneration(byte generation, bool isEggLocation, byte form
8 when version is GameVersion.PLA => Gen8a,
8 when GameVersion.BDSP.Contains(version) => Gen8b,
8 => Gen8,
9 when version is GameVersion.ZA => Gen9a,
9 => Gen9,
_ => null,

View File

@ -23,6 +23,7 @@ public sealed class MetDataSource(GameStrings s)
private readonly List<ComboItem> MetGen8a = CreateGen8a(s);
private readonly List<ComboItem> MetGen8b = CreateGen8b(s);
private readonly List<ComboItem> MetGen9 = CreateGen9(s);
private readonly List<ComboItem> MetGen9a = CreateGen9a(s);
private IReadOnlyList<ComboItem>? MetGen4Transfer;
private IReadOnlyList<ComboItem>? MetGen5Transfer;
@ -150,6 +151,7 @@ private static List<ComboItem> CreateGen8(GameStrings s)
locations.Add(new ComboItem(s.gamelist[(int)BD], LocationsHOME.SWBD));
locations.Add(new ComboItem(s.gamelist[(int)SP], LocationsHOME.SHSP));
locations.Add(new ComboItem(s.gamelist[(int)PLA], LocationsHOME.SWLA));
// No backwards from ZA+
return locations;
}
@ -197,6 +199,18 @@ private static List<ComboItem> CreateGen9(GameStrings s)
return locations;
}
private static List<ComboItem> CreateGen9a(GameStrings s)
{
var locations = Util.GetCBList(s.Gen9a.Met0, 0);
Util.AddCBWithOffset(locations, s.Gen9a.Met6, 60000, Locations.Daycare5);
Util.AddCBWithOffset(locations, s.Gen9a.Met3, 30000, Locations.LinkTrade6);
Util.AddCBWithOffset(locations, s.Gen9a.Met0, 00000, Locations9a.Met0);
Util.AddCBWithOffset(locations, s.Gen9a.Met3, 30000, Locations9a.Met3);
Util.AddCBWithOffset(locations, s.Gen9a.Met4, 40000, Locations9a.Met4);
Util.AddCBWithOffset(locations, s.Gen9a.Met6, 60000, Locations9a.Met6);
return locations;
}
/// <summary>
/// Fetches a Met Location list for a <see cref="version"/> that has been transferred away from and overwritten.
/// </summary>
@ -257,6 +271,7 @@ public IReadOnlyList<ComboItem> GetLocationList(GameVersion version, EntityConte
BD or SP => Partition2(MetGen8b, IsMetLocation8BDSP),
PLA => Partition2(MetGen8a, IsMetLocation8LA),
SL or VL => Partition2(MetGen9, IsMetLocation9SV),
ZA => Partition2(MetGen9a, IsMetLocation9ZA),
_ => GetLocationListModified(version, context),
};

View File

@ -80,6 +80,8 @@ private static GameVersion[] GetValidGameVersions()
// Gen9
SL or VL => SV,
ZA => ZA,
_ => Invalid,
};
@ -130,7 +132,7 @@ public static byte GetGeneration(this GameVersion version)
RD or GN or BU or YW => 1,
GD or SI or C => 2,
S or R or E or FR or LG or CXD => 3,
D or P or Pt or HG or SS => 4,
D or P or Pt or HG or SS or BATREV => 4,
B or W or B2 or W2 => 5,
X or Y or AS or OR => 6,
GP or GE => 7,
@ -140,6 +142,7 @@ public static byte GetGeneration(this GameVersion version)
BD or SP => 8,
SW or SH => 8,
SL or VL => 9,
ZA => 9,
_ => 0
};
@ -163,6 +166,7 @@ public static byte GetGeneration(this GameVersion version)
BD or SP => Legal.MaxSpeciesID_8b,
SW or SH => Legal.MaxSpeciesID_8,
SL or VL => Legal.MaxSpeciesID_9,
ZA => Legal.MaxSpeciesID_9a,
_ => 0
};
@ -189,7 +193,7 @@ public static bool Contains(this GameVersion g1, GameVersion g2)
public static bool IsGen7(this GameVersion version) => version is SN or MN or US or UM;
public static bool IsGen7b(this GameVersion version) => version is GP or GE;
public static bool IsGen8(this GameVersion version) => version is SW or SH or PLA or BD or SP;
public static bool IsGen9(this GameVersion version) => version is SL or VL;
public static bool IsGen9(this GameVersion version) => version is SL or VL or ZA;
/// <summary>
/// Checks if the <see cref="lump"/> version is the lump of the requested saved <see cref="version"/>.
@ -241,7 +245,7 @@ public static bool Contains(this GameVersion g1, GameVersion g2)
Gen8 => version is SW or SH or BD or SP or SWSH or BDSP or PLA,
SV => version is SL or VL,
Gen9 => version is SL or VL or SV,
Gen9 => version is SL or VL or SV or ZA,
_ => false,
};

View File

@ -170,4 +170,5 @@ public static bool IsEggLocationBred4(ushort loc, GameVersion version)
public static bool IsMetLocation8BDSP(ushort z) => z <= 657; // Ramanas Park (Genome Room)
public static bool IsMetLocation8LA(ushort z) => z <= 155; // Training Grounds
public static bool IsMetLocation9SV(ushort z) => z <= 200; // Terarium (Entry Tunnel)
public static bool IsMetLocation9ZA(ushort z) => z <= 235; // The Sewers
}

View File

@ -76,8 +76,8 @@ public static class Locations9
40001, 40002, 40003, 40004, 40005, 40006, 40007, 40008, 40009,
40010, 40011, 40012, 40013, 40014, 40015, 40016, 40017, 40018, 40019,
40020, 40021, 40022, 40024, 40024, 40025, 40026, 40027, 40028, 40029,
40030, 40032, 40033, 40034, 40035, 40036, 40037, 40038, 40039, 40039,
40040, 40041, 40042, 40043, 40044, 40045, 40047, 40047, 40048, 40049,
40030, 40032, 40033, 40034, 40035, 40036, 40037, 40038, 40039,
40040, 40041, 40042, 40043, 40044, 40045, 40046, 40047, 40048, 40049,
40050, 40051, 40052, 40053, 40054, 40055, 40056, 40057, 40058, 40059,
40060, 40061, 40062, 40063, 40064, 40065, 40066, 40067, 40068, 40069,
40070, 40071, 40072, 40073, 40074, 40075, 40076, 40077, 40078,

View File

@ -0,0 +1,69 @@
using System;
namespace PKHeX.Core
{
/// <summary>
/// Locations for <see cref="GameVersion.ZA"/>.
/// </summary>
internal static class Locations9a
{
public static ReadOnlySpan<ushort> Met0 =>
[
002, 004, 006, 007, 008, 009,
010, 011, 012, 013, 014, 015, 016, 017, 018, 019,
020, 021, 022, 023, 024, 025, 026, 027, 028, 029,
030, 031, 032, 033, 034, 035, 036, 037, 038, 039,
040, 041, 042, 043, 044, 045, 046, 047, 048, 049,
050, 051, 052, 053, 054, 055, 056, 057, 058, 059,
060, 061, 062, 063, 064, 065, 066, 067, 068, 069,
070, 071, 072, 073, 074, 075, 076, 077, 078, 079,
080, 081, 082, 083, 084, 085, 086, 087, 088, 089,
090, 091, 092, 093, 094, 095, 096, 097, 098, 099,
100, 101, 102, 103, 104, 105, 106, 107, 108, 109,
110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
120, 121, 122, 123, 124, 125, 126, 127, 128, 129,
130, 131, 132, 133, 134, 135, 136, 137, 138, 139,
140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
160, 161, 162, 163, 164, 165, 166, 167, 168, 169,
170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
180, 181, 182, 183, 184, 185, 186, 187, 188, 189,
190, 191, 192, 193, 194, 195, 196, 197, 198, 199,
200, 201, 202, 203, 204, 205, 206, 207, 208, 209,
210, 211, 212, 213, 214, 215, 216, 217, 218, 219,
220, 221, 222, 223, 224, 225, 226, 227, 228, 229,
230, 231, 232, 233, 234, 235,
];
/// <summary>
/// Available location list for the 30000 set of location names.
/// </summary>
public static ReadOnlySpan<ushort> Met3 =>
[
30001, 30003, 30004, 30005, 30006, 30007, 30008, 30009,
30010, 30011, 30012, 30013, 30014, 30015, 30016, 30017, 30018, 30019,
30020, 30021, 30022, 30023, 30024, 30025, 30026, 30027, 30028, 30029,
30030, 30031, 30032,
];
/// <summary>
/// Available location list for the 40000 set of location names.
/// </summary>
public static ReadOnlySpan<ushort> Met4 =>
[
40001, 40002, 40003, 40005, 40006, 40007, 40008, 40009,
40010, 40011, 40012, 40014, 40015, 40016, 40017, 40018,
40020, 40021, 40022, 40024, 40024, 40026, 40027, 40029,
40030, 40031, 40032, 40033, 40034, 40035, 40036, 40037, 40039,
40040, 40041, 40042, 40043, 40045, 40046, 40047, 40048, 40049,
40051, 40052, 40053, 40054, 40055, 40056, 40057, 40058,
40060, 40061, 40062, 40063, 40064, 40065, 40066, 40067, 40068, 40069,
40070,
];
/// <summary>
/// Available location list for the 60000 set of location names.
/// </summary>
public static ReadOnlySpan<ushort> Met6 => [ /* X/Y */ 60001, 60003, /* OR/AS */ 60004, /* Z-A */ 60005];
}
}

View File

@ -172,14 +172,6 @@ public sealed class ItemStorage9SV : IItemStorage
InventoryType.Ingredients, InventoryType.Candy,
];
private static ReadOnlySpan<InventoryType> ValidHeldTypes =>
[
InventoryType.Items,
InventoryType.TMHMs,
InventoryType.Medicine, InventoryType.Berries, InventoryType.Balls, InventoryType.BattleItems,
InventoryType.Treasure,
];
public static ReadOnlySpan<ushort> Unreleased =>
[
0016, // Cherish Ball
@ -240,27 +232,7 @@ public bool IsLegal(InventoryType type, int itemIndex, int itemCount)
_ => throw new ArgumentOutOfRangeException(nameof(type)),
};
public static ushort[] GetAllHeld()
{
var valid = ValidHeldTypes;
var sum = 0;
foreach (var type in valid)
sum += GetLegal(type).Length;
var result = new ushort[sum];
LoadAllHeld(valid, result);
return result;
}
private static void LoadAllHeld(ReadOnlySpan<InventoryType> valid, Span<ushort> dest)
{
foreach (var type in valid)
{
var legal = GetLegal(type);
legal.CopyTo(dest);
dest = dest[legal.Length..];
}
}
public static ushort[] GetAllHeld() => [..Other, ..Machine, ..Medicine, ..Berry, ..Balls, ..Battle, ..Treasure];
public static InventoryType GetInventoryPouch(ushort itemIndex)
{

View File

@ -0,0 +1,236 @@
using System;
using static PKHeX.Core.Species;
namespace PKHeX.Core;
public sealed class ItemStorage9ZA : IItemStorage
{
public static readonly ItemStorage9ZA Instance = new();
public static ReadOnlySpan<ushort> Medicine => // 0
[
0017, 0018, 0019, 0020, 0021, 0022, 0023, 0024, 0025, 0026,
0027, 0028, 0029, 0030, 0031, 0032, 0033, 0708,
];
public static ReadOnlySpan<ushort> Balls => // 1
[
0001, 0002, 0003, 0004, 0005, 0006, 0007, 0008, 0009, 0010,
0011, 0012, 0013, 0014, 0015, 0016, 0492, 0493, 0494, 0495,
0496, 0497, 0498, 0499, 0576, 0851,
];
public static ReadOnlySpan<ushort> Other => // 2 (Items)
[
0045, 0046, 0047, 0048, 0049, 0050, 0052, 0080, 0081, 0082,
0083, 0084, 0085, 0103, 0107, 0108, 0109, 0214, 0217, 0218,
0221, 0222, 0230, 0231, 0232, 0233, 0234, 0236, 0237, 0238,
0239, 0240, 0241, 0242, 0243, 0244, 0245, 0246, 0247, 0248,
0249, 0250, 0251, 0253, 0266, 0267, 0268, 0270, 0275, 0289,
0290, 0291, 0292, 0293, 0294, 0296, 0538, 0540, 0564, 0565,
0566, 0567, 0568, 0569, 0570, 0639, 0640, 0646, 0647, 0710,
0711, 0795, 0796, 0849, 1124, 1125, 1126, 1127, 1128, 1231,
1232, 1233, 1234, 1235, 1236, 1237, 1238, 1239, 1240, 1241,
1242, 1243, 1244, 1245, 1246, 1247, 1248, 1249, 1250, 1251,
1582, 1592, 2401, 2558, 2618, 2619, // Colorful Screw cannot be held.
];
public static ReadOnlySpan<ushort> Treasure => // 3
[
0086, 0088, 0089, 0092, 0571, 0581, 0582,
];
public static ReadOnlySpan<ushort> Key => // 4
[
0632, 0700, 0765, 0847, 2588, 2589, 2590, 2591, 2592, 2595,
2596, 2597, 2598, 2599, 2600, 2620, 2621, 2622, 2623, 2624,
2625, 2626, 2627, 2628, 2629, 2630, 2631, 2632, 2633, 2634,
];
public static ReadOnlySpan<ushort> Berry => // 5
[
0149, 0150, 0151, 0152, 0153, 0155, 0156, 0157, 0158, 0169,
0170, 0171, 0172, 0173, 0174, 0184, 0185, 0186, 0187, 0188,
0189, 0190, 0191, 0192, 0193, 0194, 0195, 0196, 0197, 0198,
0199, 0200, 0686,
];
public static ReadOnlySpan<ushort> TM => // 6
[
0328, 0329, 0330, 0331, 0332, 0333, 0334, 0335, 0336, 0337,
0338, 0339, 0340, 0341, 0342, 0343, 0344, 0345, 0346, 0347,
0348, 0349, 0350, 0351, 0352, 0353, 0354, 0355, 0356, 0357,
0358, 0359, 0360, 0361, 0362, 0363, 0364, 0365, 0366, 0367,
0368, 0369, 0370, 0371, 0372, 0373, 0374, 0375, 0376, 0377,
0378, 0379, 0380, 0381, 0382, 0383, 0384, 0385, 0386, 0387,
0388, 0389, 0390, 0391, 0392, 0393, 0394, 0395, 0396, 0397,
0398, 0399, 0400, 0401, 0402, 0403, 0404, 0405, 0406, 0407,
0408, 0409, 0410, 0411, 0412, 0413, 0414, 0415, 0416, 0417,
0418, 0419, 0618, 0619, 0620, 0690, 0691, 0692, 0693, 2160,
2162, 2163, 2164, 2165, 2166, 2167, 2168,
];
public static ReadOnlySpan<ushort> MegaStones => // 7
[
0656, 0657, 0658, 0659, 0660, 0661, 0662, 0663, 0665, 0666,
0667, 0668, 0669, 0670, 0671, 0672, 0673, 0674, 0675, 0676,
0677, 0678, 0679, 0680, 0681, 0682, 0683, 0754, 0755, 0756,
0757, 0758, 0759, 0760, 0761, 0762, 0763, 0764, 0767, 0768,
0769, 0770, 2559, 2560, 2561, 2562, 2563, 2564, 2565, 2566,
2569, 2570, 2571, 2572, 2573, 2574, 2575, 2576, 2577, 2578,
2579, 2580, 2581, 2582, 2583, 2584, 2585, 2587,
];
internal static ReadOnlySpan<InventoryType> ValidTypes =>
[
// Display Order
InventoryType.Medicine,
InventoryType.Balls,
InventoryType.Berries,
InventoryType.Items, // Other
InventoryType.TMHMs,
InventoryType.MegaStones,
InventoryType.Treasure,
InventoryType.KeyItems,
];
public static ReadOnlySpan<ushort> Unreleased =>
[
0005, // Safari Ball
0499, // Sport Ball (first Season end reward)
0662, // Mewtwonite X
0663, // Mewtwonite Y
0764, // Diancite
0576, // Dream Ball
0851, // Beast Ball
2575, // Chesnaughtite
2576, // Delphoxite
];
public int GetMax(InventoryType type) => type switch
{
InventoryType.Medicine => 999,
InventoryType.Balls => 999,
InventoryType.Berries => 999,
InventoryType.Items => 999, // Other
InventoryType.TMHMs => 1,
InventoryType.MegaStones => 1,
InventoryType.Treasure => 999,
InventoryType.KeyItems => 1,
_ => throw new ArgumentOutOfRangeException(nameof(type)),
};
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => !Unreleased.Contains((ushort)itemIndex);
public ReadOnlySpan<ushort> GetItems(InventoryType type) => GetLegal(type);
public static ReadOnlySpan<ushort> GetLegal(InventoryType type) => type switch
{
InventoryType.Medicine => Medicine,
InventoryType.Balls => Balls,
InventoryType.Berries => Berry,
InventoryType.Items => Other,
InventoryType.TMHMs => TM,
InventoryType.MegaStones => MegaStones,
InventoryType.Treasure => Treasure,
InventoryType.KeyItems => Key,
_ => throw new ArgumentOutOfRangeException(nameof(type)),
};
public static ushort[] GetAllHeld() => [..Medicine, ..Balls, ..Berry, ..MegaStones, ..Treasure, ..Other[..^1]]; // Exclude Colorful Screw
public static InventoryType GetInventoryPouch(ushort itemIndex)
{
foreach (var type in ValidTypes)
{
var legal = GetLegal(type);
if (legal.Contains(itemIndex))
return type;
}
return InventoryType.None;
}
public static bool IsMegaStone(ushort item) => MegaStones.Contains(item);
/// <summary>
/// Retrieves the expected Mega Stone item ID for a given Pokémon species and form.
/// </summary>
/// <returns>0 if no item is expected for this species and form combination (in other words, not a mega form).</returns>
public static ushort GetExpectedMegaStone(ushort species, byte form) => (Species)species switch
{
Gengar when form == 1 => 0656,
Gardevoir when form == 1 => 0657,
Ampharos when form == 1 => 0658,
Venusaur when form == 1 => 0659,
Charizard when form == 1 => 0660, // X
Blastoise when form == 1 => 0661,
Mewtwo when form == 1 => 0662, // X
Mewtwo when form == 2 => 0663, // Y
Medicham when form == 1 => 0665,
Houndoom when form == 1 => 0666,
Aggron when form == 1 => 0667,
Banette when form == 1 => 0668,
Tyranitar when form == 1 => 0669,
Scizor when form == 1 => 0670,
Pinsir when form == 1 => 0671,
Aerodactyl when form == 1 => 0672,
Lucario when form == 1 => 0673,
Abomasnow when form == 1 => 0674,
Kangaskhan when form == 1 => 0675,
Gyarados when form == 1 => 0676,
Absol when form == 1 => 0677,
Charizard when form == 2 => 0678, // Y
Alakazam when form == 1 => 0679,
Heracross when form == 1 => 0680,
Mawile when form == 1 => 0681,
Manectric when form == 1 => 0682,
Garchomp when form == 1 => 0683,
Sableye when form == 1 => 0754,
Altaria when form == 1 => 0755,
Gallade when form == 1 => 0756,
Audino when form == 1 => 0757,
Metagross when form == 1 => 0758,
Sharpedo when form == 1 => 0759,
Slowbro when form == 1 => 0760,
Steelix when form == 1 => 0761,
Pidgeot when form == 1 => 0762,
Glalie when form == 1 => 0763,
Diancie when form == 1 => 0764,
Camerupt when form == 1 => 0767,
Lopunny when form == 1 => 0768,
Salamence when form == 1 => 0769,
Beedrill when form == 1 => 0770,
Clefable when form == 1 => 2559,
Victreebel when form == 1 => 2560,
Starmie when form == 1 => 2561,
Dragonite when form == 1 => 2562,
Meganium when form == 1 => 2563,
Feraligatr when form == 1 => 2564,
Skarmory when form == 1 => 2565,
Froslass when form == 1 => 2566,
Emboar when form == 1 => 2569,
Excadrill when form == 1 => 2570,
Scolipede when form == 1 => 2571,
Scrafty when form == 1 => 2572,
Eelektross when form == 1 => 2573,
Chandelure when form == 1 => 2574,
Chesnaught when form == 1 => 2575,
Delphox when form == 1 => 2576,
Greninja when form == 3 => 2577,
Pyroar when form == 1 => 2578,
Floette when form == 6 => 2579,
Malamar when form == 1 => 2580,
Barbaracle when form == 1 => 2581,
Dragalge when form == 1 => 2582,
Hawlucha when form == 1 => 2583,
Zygarde when form == 5 => 2584,
Drampa when form == 1 => 2585,
Falinks when form == 1 => 2587,
_ => 0,
};
}

View File

@ -80,7 +80,7 @@ public static byte[] Write32(byte[][] data, ReadOnlySpan<byte> identifier, int p
for (int i = 0; i < count; i++)
{
// Write File Offset
var fileOffset = (ms.Position - start) + dataOffset;
var fileOffset = bw.BaseStream.Length - start;
bw.Seek(start + 4 + (i * sizeof(uint)), SeekOrigin.Begin);
bw.Write((uint)fileOffset);
// Write File to Stream
@ -94,8 +94,12 @@ public static byte[] Write32(byte[][] data, ReadOnlySpan<byte> identifier, int p
}
// Cap the File
bw.Seek(start + 4 + (count * sizeof(uint)), SeekOrigin.Begin);
bw.Write((uint)((ms.Position - start) + dataOffset));
{
var fileOffset = bw.BaseStream.Length - start;
bw.Seek(start + 4 + (count * sizeof(uint)), SeekOrigin.Begin);
bw.Write((uint)fileOffset);
}
// Return the byte array
return ms.ToArray();
}
@ -121,7 +125,7 @@ public static byte[] Write16(byte[][] data, ReadOnlySpan<byte> identifier, int p
for (int i = 0; i < count; i++)
{
// Write File Offset
var fileOffset = ((ms.Position - start) + dataOffset);
var fileOffset = bw.BaseStream.Length - start;
bw.Seek(start + 4 + (i * sizeof(ushort)), SeekOrigin.Begin);
bw.Write((ushort)fileOffset);
// Write File to Stream
@ -135,8 +139,12 @@ public static byte[] Write16(byte[][] data, ReadOnlySpan<byte> identifier, int p
}
// Cap the File
bw.Seek(start + 4 + (count * sizeof(ushort)), SeekOrigin.Begin);
bw.Write((ushort)((ms.Position - start) + dataOffset));
{
var fileOffset = bw.BaseStream.Length - start;
bw.Seek(start + 4 + (count * sizeof(ushort)), SeekOrigin.Begin);
bw.Write((ushort)fileOffset);
}
// Return the byte array
return ms.ToArray();
}

View File

@ -40,6 +40,7 @@ public static class Breeding
/// <summary>
/// Checks if the species <see cref="encryptionConstant"/> is valid for the <see cref="gender"/> if originated from Gen3/4 daycare eggs.
/// </summary>
/// <remarks>Only applies to species that satisfy <see cref="IsGenderSpeciesDetermination"/>.</remarks>
/// <param name="encryptionConstant">Encryption Constant</param>
/// <param name="gender">Gender</param>
/// <returns>True if valid</returns>
@ -129,6 +130,8 @@ public static bool CanHatchAsEgg(ushort species, byte form, EntityContext contex
/// <summary>
/// Some species can have forms that cannot exist as egg (event/special forms). Same idea as <see cref="FormInfo.IsTotemForm(ushort,byte,EntityContext)"/>
/// </summary>
/// <param name="species">Current species</param>
/// <param name="form">Current form, not 0.</param>
/// <returns>True if it can be bred.</returns>
private static bool IsBreedableForm(ushort species, byte form) => species switch
{

View File

@ -15,7 +15,7 @@ public sealed class BulkAnalysis
public readonly ITrainerInfo Trainer;
public readonly List<BulkCheckResult> Parse = [];
public readonly Dictionary<ulong, (SlotCache Slot, int Index)> Trackers = [];
public readonly bool Valid;
public bool Valid => Parse.Count == 0 || Parse.TrueForAll(static z => z.Result.Valid);
public readonly BulkAnalysisSettings Settings;
private readonly bool[] CloneFlags;
@ -32,6 +32,7 @@ public sealed class BulkAnalysis
public BulkAnalysis(SaveFile sav, BulkAnalysisSettings settings) : this(GetSlots(sav), settings, sav)
{
RunSaveSpecificChecks();
}
public BulkAnalysis(IEnumerable<SlotCache> source, BulkAnalysisSettings settings, ITrainerInfo tr) : this(GetSlots(source), settings, tr)
@ -47,7 +48,6 @@ private BulkAnalysis(List<SlotCache> list, BulkAnalysisSettings settings, ITrain
CloneFlags = new bool[AllData.Count];
ScanAll();
Valid = Parse.Count == 0 || Parse.TrueForAll(static z => z.Result.Valid);
}
private static List<SlotCache> GetSlots(SaveFile sav)
@ -97,6 +97,12 @@ private static bool IsEmptyData(SlotCache obj)
new DuplicateGiftChecker(),
];
private static readonly List<IBulkAnalyzer> SaveAnalyzers =
[
new DuplicateFusionChecker(),
new DuplicateMegaChecker(),
];
private void ScanAll()
{
foreach (var analyzer in Analyzers)
@ -105,6 +111,12 @@ private void ScanAll()
analyzer.Analyze(this);
}
private void RunSaveSpecificChecks()
{
foreach (var analyzer in SaveAnalyzers)
analyzer.Analyze(this);
}
private static string GetSummary(SlotCache entry) => $"[{entry.Identify()}]";
/// <summary>
@ -143,6 +155,8 @@ private void ScanAll()
Parse.Add(new(chk, line, index1, index2));
}
public void AddMessage(string message, CheckResult chk, int index = BulkCheckResult.NoIndex) => Parse.Add(new(chk, message, index));
public void AddExternal(SlotCache first, CheckIdentifier id, int index1, ushort identity, ushort argument = 0, Severity s = Severity.Invalid)
=> AddLine(first, id, index1, LegalityCheckResultCode.External, identity, argument, s);
@ -163,22 +177,27 @@ private static LegalityAnalysis[] GetIndividualAnalysis(ReadOnlySpan<SlotCache>
public string Report(LegalityLocalizationSet localization)
{
var sb = new StringBuilder(1024);
var context = LegalityLocalizationContext.Create(AllAnalysis[0], localization);
foreach (var (chk, comment, index1, index2) in Parse)
{
if (sb.Length != 0)
sb.AppendLine(); // gap for next result
var code = chk.Result;
string template;
if (code is LegalityCheckResultCode.External)
template = ExternalBulkCheck.Localize(chk, localization, AllAnalysis, index1, index2);
{
var judge = localization.Description(chk.Judgement);
var template = ExternalBulkCheck.Localize(chk, localization, AllAnalysis, index1, index2);
sb.AppendFormat(localization.Lines.F0_1, judge, template);
}
else
template = code.GetTemplate(localization.Lines);
var judge = localization.Description(chk.Judgement);
sb.AppendFormat(localization.Lines.F0_1, judge, template);
{
sb.Append(context.Humanize(chk));
}
sb.AppendLine();
sb.AppendLine(comment);
if (comment.Length != 0)
sb.AppendLine(comment);
}
if (sb.Length == 0)
sb.AppendLine(localization.Lines.Valid);

View File

@ -0,0 +1,108 @@
using System;
namespace PKHeX.Core.Bulk;
public sealed class DuplicateFusionChecker : IBulkAnalyzer
{
/// <summary>
/// <see cref="StorageSlotType"/>
/// </summary>
/// <param name="input"></param>
public void Analyze(BulkAnalysis input)
{
if (input.Trainer is not SaveFile sav)
return;
// Rule: Only 1 Fusion of each of the stored fused species.
Span<int> seen = stackalloc int[4]; // 0: Kyurem, 1: Necrozma-Dawn, 2: Necrozma-Dusk, 3: Calyrex-Ice/Ghost
const int seenNotSet = -1;
seen.Fill(seenNotSet);
SearchForDuplicates(input, seen, seenNotSet);
// Rule: The consumed species to obtain (species, form) must be present in the save file's fused storage.
if (seen.ContainsAnyExcept(seenNotSet))
CheckConsumedSlots(input, sav, seen, seenNotSet);
}
private static void SearchForDuplicates(BulkAnalysis input, Span<int> seen, int seenNotSet)
{
for (var i = 0; i < input.AllData.Count; i++)
{
var slot = input.AllData[i];
var pk = slot.Entity;
var (species, form) = (pk.Species, pk.Form);
if (form == 0) // eager check -- fused forms always have form > 0
continue;
var index = GetIndexFusedStorage(species, form);
if (index == -1)
continue;
if (seen[index] != seenNotSet) // Already given to another slot.
{
var other = seen[index];
input.AddLine(slot, input.AllData[other], CheckIdentifier.Form, i, index2: other, LegalityCheckResultCode.BulkDuplicateFusionSlot, species);
continue;
}
// First time seeing this Fusion, all good.
seen[index] = i;
}
}
private static void CheckConsumedSlots(BulkAnalysis input, SaveFile sav, Span<int> seen, int seenNotSet)
{
// Check that fused species is present in the fused source
var extraSlots = sav.GetExtraSlots();
for (int i = 0; i < seen.Length; i++)
{
var index = seen[i];
if (index == seenNotSet)
continue;
var extra = extraSlots.Find(z => GetIndexFusedStorage(z.Type) == i);
if (extra is null) // uhh? shouldn't be in this save file, will be flagged by regular legality check.
continue;
var exist = input.AllData[index];
var pk = exist.Entity;
var form = pk.Form;
var source = extra.Read(sav);
if (source.Form != 0 || !IsValidStoredBaseSpecies(i, source.Species, form))
input.AddLine(exist, CheckIdentifier.Form, i, LegalityCheckResultCode.BulkFusionSourceInvalid, source.Species, source.Form);
}
}
private static int GetIndexFusedStorage(StorageSlotType type) => type switch
{
StorageSlotType.FusedKyurem => 0,
StorageSlotType.FusedNecrozmaS => 1,
StorageSlotType.FusedNecrozmaM => 2,
StorageSlotType.FusedCalyrex => 3,
_ => -1,
};
// The games let you fuse two separate Necrozma with the box legends (N-Solarizer and N-Lunarizer).
private static int GetIndexFusedStorage(ushort species, byte form) => (species, form) switch
{
((ushort)Species.Kyurem, _) => 0, // DNA Splicers
((ushort)Species.Necrozma, 1) => 1, // N-Solarizer
((ushort)Species.Necrozma, 2) => 2, // N-Lunarizer
((ushort)Species.Calyrex, _) => 3, // Reins of Unity
_ => -1,
};
private static bool IsValidStoredBaseSpecies(int index, ushort consumedSpecies, byte resultForm) => index switch
{
0 when resultForm == 1 => consumedSpecies is (ushort)Species.Reshiram,
0 when resultForm == 2 => consumedSpecies is (ushort)Species.Zekrom,
1 => consumedSpecies is (ushort)Species.Solgaleo,
2 => consumedSpecies is (ushort)Species.Lunala,
3 when resultForm == 1 => consumedSpecies == (ushort)Species.Glastrier,
3 when resultForm == 2 => consumedSpecies == (ushort)Species.Spectrier,
_ => false,
};
}

View File

@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core.Bulk;
public sealed class DuplicateMegaChecker : IBulkAnalyzer
{
private const CheckIdentifier Identifier = CheckIdentifier.HeldItem;
public void Analyze(BulkAnalysis input)
{
if (input.Trainer is SAV9ZA za)
AnalyzeInventory(input, za);
else
AnalyzeNoInventory(input);
}
private static void AnalyzeInventory(BulkAnalysis input, SAV9ZA za)
{
var items = za.Items;
// Rule: Only 1 Mega Stone of a given Item ID may be held across all Pokémon, or present in the bag.
Dictionary<ushort, int> seenStones = []; // ushort item, int index
for (var i = 0; i < input.AllData.Count; i++)
{
var slot = input.AllData[i];
var pk = slot.Entity;
var stone = (ushort)pk.HeldItem;
if (!ItemStorage9ZA.IsMegaStone(stone))
continue;
var item = items.GetItem(stone);
if (item.Count == 0) // Not acquired by the save file, thus not able to be held.
input.AddLine(slot, Identifier, BulkCheckResult.NoIndex, BulkNotAcquiredMegaStoneInventory, stone);
else if (!item.IsHeld) // Not marked as held, so it's still "in the bag" (not given).
input.AddLine(slot, Identifier, BulkCheckResult.NoIndex, BulkDuplicateMegaStoneInventory, stone);
else if (seenStones.TryGetValue(stone, out var otherIndex)) // Already given to another slot.
input.AddLine(slot, input.AllData[otherIndex], Identifier, i, index2: otherIndex, BulkDuplicateMegaStoneSlot, stone);
else // First time seeing this Mega Stone, all good.
seenStones[stone] = i;
}
CheckOtherUnassigned(input, items, ItemStorage9ZA.MegaStones, seenStones);
}
private static void CheckOtherUnassigned(BulkAnalysis input, MyItem9a items, ReadOnlySpan<ushort> megaStones, IReadOnlyDictionary<ushort, int> seenStones)
{
// Check that all other un-assigned stones are not marked as assigned.
foreach (var stone in megaStones)
{
if (seenStones.ContainsKey(stone))
continue;
var item = items.GetItem(stone);
if (item.IsHeld)
input.AddMessage(string.Empty, CheckResult.Get(Severity.Invalid, CheckIdentifier.HeldItem, BulkAssignedMegaStoneNotFound_0, stone));
}
}
private static void AnalyzeNoInventory(BulkAnalysis input)
{
// Rule: Only 1 Mega Stone of a given Item ID may be held across all Pokémon, or present in the bag.
Dictionary<ushort, int> seenStones = []; // ushort item, int index
for (var i = 0; i < input.AllData.Count; i++)
{
var slot = input.AllData[i];
var pk = slot.Entity;
var stone = (ushort)pk.HeldItem;
if (!ItemStorage9ZA.IsMegaStone(stone))
continue;
if (seenStones.TryGetValue(stone, out var otherIndex)) // Already given to another slot.
input.AddLine(slot, input.AllData[otherIndex], Identifier, i, index2: otherIndex, BulkDuplicateMegaStoneSlot, stone);
else // First time seeing this Mega Stone, all good.
seenStones[stone] = i;
}
}
}

View File

@ -40,6 +40,9 @@ public static class EncounterEvent
/// <summary>Event Database for Generation 9 <see cref="EntityContext.Gen9"/></summary>
public static readonly WC9[] MGDB_G9 = GetWC9DB(Util.GetBinaryResource("wc9.pkl"));
/// <summary>Event Database for Generation 9 <see cref="EntityContext.Gen9a"/></summary>
public static readonly WA9[] MGDB_G9A = GetWA9DB(Util.GetBinaryResource("wa9.pkl"));
#endregion
#region Locally Loaded Data
@ -55,20 +58,23 @@ public static class EncounterEvent
/// <summary>Event Database for Generation 7</summary>
public static WC7[] EGDB_G7 { get; private set; } = [];
/// <summary>Event Database for Generation 7 <see cref="GameVersion.GG"/></summary>
/// <summary>Event Database for Generation 7 <see cref="EntityContext.Gen7b"/></summary>
public static WB7[] EGDB_G7GG { get; private set; } = [];
/// <summary>Event Database for Generation 8</summary>
/// <summary>Event Database for Generation 8 <see cref="EntityContext.Gen8"/></summary>
public static WC8[] EGDB_G8 { get; private set; } = [];
/// <summary>Event Database for Generation 8 <see cref="GameVersion.PLA"/></summary>
/// <summary>Event Database for Generation 8 <see cref="EntityContext.Gen8a"/></summary>
public static WA8[] EGDB_G8A { get; private set; } = [];
/// <summary>Event Database for Generation 8 <see cref="GameVersion.BDSP"/></summary>
/// <summary>Event Database for Generation 8 <see cref="EntityContext.Gen8b"/></summary>
public static WB8[] EGDB_G8B { get; private set; } = [];
/// <summary>Event Database for Generation 9 <see cref="GameVersion.SV"/></summary>
/// <summary>Event Database for Generation 9 <see cref="EntityContext.Gen9"/></summary>
public static WC9[] EGDB_G9 { get; private set; } = [];
/// <summary>Event Database for Generation 9 <see cref="EntityContext.Gen9a"/></summary>
public static WA9[] EGDB_G9A { get; private set; } = [];
#endregion
private static PCD[] GetPCDDB(Memory<byte> bin) => Get(bin, PCD.Size, static d => new PCD(d));
@ -82,6 +88,7 @@ public static class EncounterEvent
private static WB8[] GetWB8DB(Memory<byte> bin) => Get(bin, WB8.Size, static d => new WB8(d));
private static WA8[] GetWA8DB(Memory<byte> bin) => Get(bin, WA8.Size, static d => new WA8(d));
private static WC9[] GetWC9DB(Memory<byte> bin) => Get(bin, WC9.Size, static d => new WC9(d));
private static WA9[] GetWA9DB(Memory<byte> bin) => Get(bin, WA9.Size, static d => new WA9(d));
private static T[] Get<T>(Memory<byte> bin, int size, Func<Memory<byte>, T> ctor)
{
@ -114,6 +121,7 @@ public static void RefreshMGDB(params ReadOnlySpan<string> paths)
HashSet<WB8>? b8 = null; List<WB8>? lb8 = null;
HashSet<WA8>? a8 = null; List<WA8>? la8 = null;
HashSet<WC9>? g9 = null; List<WC9>? lg9 = null;
HashSet<WA9>? a9 = null; List<WA9>? la9 = null;
// Load external files
// For each file, load the gift object into the appropriate list.
@ -135,6 +143,7 @@ public static void RefreshMGDB(params ReadOnlySpan<string> paths)
WB8 wb8 => AddOrExpand(ref b8, ref lb8, wb8),
WA8 wa8 => AddOrExpand(ref a8, ref la8, wa8),
WC9 wc9 => AddOrExpand(ref g9, ref lg9, wc9),
WA9 wa9 => AddOrExpand(ref a9, ref la9, wa9),
_ => false,
};
if (!added)
@ -165,6 +174,7 @@ static bool AddOrExpand<T>([NotNullWhen(true)] ref HashSet<T>? arr, ref List<T>?
EGDB_G8A = SetArray(la8);
EGDB_G8B = SetArray(lb8);
EGDB_G9 = SetArray(lg9);
EGDB_G9A = SetArray(la9);
return;
[MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@ -28,6 +28,12 @@ public static ReadOnlySpan<byte> Get([ConstantExpected] string resource)
public static BinLinkerAccessor Get([ConstantExpected] string resource, [Length(2, 2)] ReadOnlySpan<byte> ident)
=> BinLinkerAccessor.Get(Get(resource), ident);
/// <summary>
/// Gets an index-able accessor for the specified resource.
/// </summary>
public static BinLinkerAccessor16 Get16([ConstantExpected] string resource, [Length(2, 2)] ReadOnlySpan<byte> ident)
=> BinLinkerAccessor16.Get(Get(resource), ident);
/// <summary>
/// Grabs the localized names for individual templates for all languages from the specified <see cref="index"/> of the <see cref="names"/> list.
/// </summary>

View File

@ -0,0 +1,91 @@
using static PKHeX.Core.EncounterUtil;
using static PKHeX.Core.Shiny;
namespace PKHeX.Core;
internal static class Encounters9a
{
internal static readonly EncounterArea9a[] Slots = EncounterArea9a.GetAreas(Get16("za", "za"u8));
internal static readonly EncounterGift9a[] Gifts =
[
// Z-A Starters
new(0152,0,05,128) { Location = 30026, FlawlessIVCount = 3, Moves = new(033,039,000,000) }, // Chikorita (test_encount_init_poke_0)
new(0498,0,05,128) { Location = 30026, FlawlessIVCount = 3, Moves = new(033,039,000,000) }, // Tepig (test_encount_init_poke_1)
new(0158,0,05,128) { Location = 30026, FlawlessIVCount = 3, Moves = new(033,043,000,000) }, // Totodile (test_encount_init_poke_2)
// Kanto Starters
new(0001,0,05,000) { Location = 30027, FlawlessIVCount = 3 }, // Bulbasaur (sub_addpoke_fushigidane)
new(0004,0,05,000) { Location = 30027, FlawlessIVCount = 3 }, // Charmander (sub_addpoke_hitokage)
new(0007,0,05,000) { Location = 30027, FlawlessIVCount = 3 }, // Squirtle (sub_addpoke_zenigame)
// Kalos Starters
new(0650,0,14,128) { Location = 30030, Gender = 1, Nature = Nature.Impish, IVs = new(31,31,31,15,15,15) }, // Chespin (sub_addpoke_harimaron)
new(0653,0,14,128) { Location = 30031, Gender = 1, Nature = Nature.Lonely, IVs = new(15,15,15,31,31,31) }, // Fennekin (sub_addpoke_fokko)
new(0656,0,14,128) { Location = 00069, Gender = 0, Nature = Nature.Sassy, IVs = new(15,31,15,31,31,15) }, // Froakie (sub_addpoke_keromatsu)
// Side Missions
new(0618,1,38,128) { Location = 30028, Gender = 0, FlawlessIVCount = 3, Moves = new(319,334,089,340), Trainer = TrainerGift9a.Stunfisk }, // Stunfisk-1 (sub_addpoke_gmaggyo)
new(0665,8,09,128) { Location = 30029, Gender = 0, Nature = Nature.Naive, FlawlessIVCount = 3 }, // Spewpa-8 (sub_addpoke_kofuurai)
// Eternal Flower Floette
new(0670,5,72,128) { Location = 30026, Gender = 1, Nature = Nature.Modest, FlawlessIVCount = 3, Moves = new(617,412,202,235), Trainer = TrainerGift9a.Floette }, // Floette-5 (addpoke_floette_eien)
// Korrina's Lucario
new(0448,0,50,128) { Location = 30026, Gender = 0, Nature = Nature.Hardy, IVs = new(31,20,31,20,20,31), Moves = new(396,418,249,182), Trainer = TrainerGift9a.Lucario }, // Lucario (sub_addpoke_rukario)
// Rogue Mega Absol
new(0359,0,30,128) { Location = 30026, FlawlessIVCount = 3, Moves = new(400,163,098,014) }, // Absol (vsmega_init_abusoru)
// Fossils (Tutorial Revival)
new(0696,0,20,128) { Location = 30027, FlawlessIVCount = 3, Moves = new(046,088,204,044) }, // Tyrunt (sub_addpoke_chigoras)
new(0698,0,20,128) { Location = 30027, FlawlessIVCount = 3, Moves = new(088,196,036,054) }, // Amaura (sub_addpoke_amarus)
// Fossils
new(0142,0,20,000) { Location = 30027, Shiny = Random }, // Aerodactyl (restoration_ptera)
new(0696,0,20,000) { Location = 30027, Shiny = Random }, // Tyrunt (restoration_chigoras)
new(0698,0,20,000) { Location = 30027, Shiny = Random }, // Amaura (restoration_amarus)
// Alpha Fossils
new(0142,0,35,255) { Location = 30027, Shiny = Random, IsAlpha = true, FlawlessIVCount = 3 }, // Aerodactyl (restoration_ptera)
new(0696,0,35,255) { Location = 30027, Shiny = Random, IsAlpha = true, FlawlessIVCount = 3 }, // Tyrunt (restoration_chigoras)
new(0698,0,35,255) { Location = 30027, Shiny = Random, IsAlpha = true, FlawlessIVCount = 3 }, // Amaura (restoration_amarus)
];
internal static readonly EncounterStatic9a[] Static =
[
// Legendary and Mythical Pokémon
new(0716,0,75,128) { Location = 00210, FlawlessIVCount = 3, Moves = new(224,585,532,601) }, // Xerneas (m10_x)
new(0717,0,75,128) { Location = 00075, FlawlessIVCount = 3, Moves = new(542,399,094,613) }, // Yveltal (m10_y)
new(0718,2,84,128) { Location = 00212, Nature = Nature.Quiet, IVs = new(31,31,15,19,31,28), Moves = new(687,614,615,616) }, // Zygarde-2 (ect_boss_0718_01)
// Main Missions / Side Missions
new(0013,0,45,255) { Location = 00055, Gender = 0, Nature = Nature.Naive, IsAlpha = true, FlawlessIVCount = 3 }, // Weedle (sub_023_oybn)
new(0092,0,40,128) { Location = 00231 }, // Gastly (ect_d03_01_z092_ev)
new(0094,0,42,128) { Location = 00231 }, // Gengar (ect_d03_01_z094_ev)
new(0302,0,10,128) { Location = 00205, Gender = 0, Nature = Nature.Naive, Moves = new(033,043,425,000) }, // Sableye (sub_003_ghost)
new(0303,0,52,255) { Location = 00057, Gender = 1, Nature = Nature.Hardy, FlawlessIVCount = 3 }, // Mawile (sub_107_kucheat)
new(0505,0,50,255) { Location = 00014, Gender = 0, Nature = Nature.Bold, IsAlpha = true, FlawlessIVCount = 3 }, // Watchog (sub_056_evoybn)
new(0569,0,50,128) { Location = 00030, Gender = 0, Nature = Nature.Naughty, Moves = new(398,693,205,232) }, // Garbodor (sub_027_predator)
new(0587,0,53,128) { Location = 00089, Gender = 1, Nature = Nature.Naive, Moves = new(113,521,403,209) }, // Emolga (sub_115_emonga)
new(0659,0,10,255) { Location = 00079, Gender = 0, Nature = Nature.Mild, IsAlpha = true, FlawlessIVCount = 3 }, // Bunnelby (sub_002_oybn)
new(0660,0,28,128) { Location = 00012, Gender = 0, Nature = Nature.Adamant }, // Diggersby (sub_055_evpoke)
new(0707,0,40,128) { Location = 00235, Gender = 0, Nature = Nature.Timid, Moves = new(583,430,577,319) }, // Klefki (m05_cleffy)
// Side Missions EX
// new(0150,0,70,128) { Location = 00234, FlawlessIVCount = 3, Moves = new(094,396,427,133) }, // Mewtwo (sub_120_mewtwo)
// new(0703,0,66,128) { Location = 00063, FlawlessIVCount = 3, Moves = new(444,430,605,157) }, // Carbink (sub_119_melecie_01)
// new(0703,0,66,128) { Location = 00063, FlawlessIVCount = 3, Moves = new(444,446,408,605) }, // Carbink (sub_119_melecie_02)
// new(0719,0,70,128) { Location = 00063, FlawlessIVCount = 3, Moves = new(591,585,446,577) }, // Diancie (sub_119_diancie)
];
private const string tradeZA = "tradeza";
private static readonly string[][] TradeNames = Util.GetLanguageStrings11(tradeZA);
internal static readonly EncounterTrade9a[] Trades =
[
new(TradeNames,0,0214,0,12) { ID32 = 797394, OTGender = 1, Gender = 0, Nature = Nature.Brave, IVs = new(31,31,15,31,15,15), Moves = new(033,043,203,042) }, // Heracross (sub_tradepoke_heracros)
new(TradeNames,1,0447,0,25) { ID32 = 348226, OTGender = 0, Gender = 0, Nature = Nature.Rash, IVs = new(15,31,15,31,31,15), Moves = new(418,098,249,197) }, // Riolu (sub_tradepoke_riolu)
new(TradeNames,2,0079,1,30) { ID32 = 934764, OTGender = 0, Gender = 0, FlawlessIVCount = 3, Moves = new(352,133,428,029) }, // Slowpoke-1 (sub_addpoke_gyadon)
new(TradeNames,3,0026,1,64) { ID32 = 693489, OTGender = 1, Gender = 0, Nature = Nature.Jolly, IVs = new(20,20,20,20,20,20), Moves = new(094,087,057,583) }, // Raichu-1 (sub_addpoke_araichu)
];
}

View File

@ -21,7 +21,7 @@ public DistributionWindow(int startYear, int startMonth, int startDay, int endYe
/// </summary>
/// <param name="obtained">Date obtained.</param>
/// <returns>True if the date obtained is within the date of availability for the given range.</returns>
public bool Contains(DateOnly obtained) => (Start <= obtained && End is null) || obtained <= End;
public bool Contains(DateOnly obtained) => Start <= obtained && !(obtained > End);
/// <summary>
/// Get a valid date within the generation range.

View File

@ -24,6 +24,7 @@ public static class EncounterServerDate
WA8 wa8 => Result(wa8.IsWithinDistributionWindow(obtained)),
WB8 wb8 => Result(wb8.IsWithinDistributionWindow(obtained)),
WC9 wc9 => Result(wc9.IsWithinDistributionWindow(obtained)),
WA9 wa9 => Result(wa9.IsWithinDistributionWindow(obtained)),
EncounterSlot7GO g7 => Result(g7.IsWithinDistributionWindow(obtained)),
EncounterSlot8GO g8 => Result(g8.IsWithinDistributionWindow(obtained)),
_ => throw new ArgumentOutOfRangeException(nameof(enc)),
@ -44,11 +45,15 @@ public static class EncounterServerDate
/// <inheritdoc cref="IsWithinDistributionWindow(IEncounterServerDate,DateOnly)"/>
public static bool IsWithinDistributionWindow(this WC9 card, DateOnly obtained) => card.GetDistributionWindow(out var window) && window.Contains(obtained);
/// <inheritdoc cref="IsWithinDistributionWindow(IEncounterServerDate,DateOnly)"/>
public static bool IsWithinDistributionWindow(this WA9 card, DateOnly obtained) => card.GetDistributionWindow(out var window) && window.Contains(obtained);
public static bool GetDistributionWindow(this WB7 card, out DistributionWindow window) => WB7Gifts.TryGetValue(card.CardID, out window);
public static bool GetDistributionWindow(this WC8 card, out DistributionWindow window) => WC8Gifts.TryGetValue(card.CardID, out window) || WC8GiftsChk.TryGetValue(card.Checksum, out window);
public static bool GetDistributionWindow(this WA8 card, out DistributionWindow window) => WA8Gifts.TryGetValue(card.CardID, out window);
public static bool GetDistributionWindow(this WB8 card, out DistributionWindow window) => WB8Gifts.TryGetValue(card.CardID, out window);
public static bool GetDistributionWindow(this WC9 card, out DistributionWindow window) => WC9Gifts.TryGetValue(card.CardID, out window) || WC9GiftsChk.TryGetValue(card.Checksum, out window);
public static bool GetDistributionWindow(this WA9 card, out DistributionWindow window) => WA9Gifts.TryGetValue(card.CardID, out window);
/// <summary>
/// Initial introduction of HOME support for SW/SH; gift availability (generating) was revised in 3.0.0.
@ -228,7 +233,7 @@ public static class EncounterServerDate
{1548, new(2025, 09, 18, 2025, 10, 01)}, // Shiny Chi-Yu
{0524, new(2025, 08, 14, 2025, 08, 31)}, // WCS 2025 Toedscool
{0525, new(2025, 08, 15, 2025, 08, 23)}, // WCS 2025 Luca Ceribelli's Farigiraf
{1540, new(2025, 09, 25, 2025, 10, 24)}, // Shiny Miraidon / Koraidon Gift
{1540, new(2025, 09, 25, 2025, 10, 25)}, // Shiny Miraidon / Koraidon Gift
{9021, HOME3_ML}, // Hidden Ability Sprigatito
{9022, HOME3_ML}, // Hidden Ability Fuecoco
@ -236,4 +241,13 @@ public static class EncounterServerDate
{9024, new(2024, 10, 16)}, // Shiny Meloetta
{9025, new(2024, 11, 01)}, // PokéCenter Birthday Tandemaus
};
/// <summary>
/// Minimum date the gift can be received.
/// </summary>
private static readonly Dictionary<int, DistributionWindow> WA9Gifts = new()
{
{1601, new(2025, 10, 14, 2026, 3, 1, +2)}, // Ralts holding Gardevoirite
{0102, new(2025, 10, 23, 2026, 2, 1, +2)}, // Slowpoke Poké Center Gift
};
}

View File

@ -23,6 +23,8 @@ public IEnumerable<IEncounterable> GetEncounters(PKM pk, LegalInfo info)
public IEnumerable<IEncounterable> GetPossible(PKM pk, EvoCriteria[] chain, GameVersion version, EncounterTypeGroup groups)
{
if (version is GameVersion.BATREV)
yield break;
var iterator = new EncounterPossible4(chain, groups, version, pk);
foreach (var enc in iterator)
yield return enc;

View File

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using static PKHeX.Core.GameVersion;
@ -50,14 +51,14 @@ public IEnumerable<IEncounterable> GetEncounters(PKM pk, EvoCriteria[] chain, Le
private const EntityContext Context = EntityContext.Gen9;
private const byte EggLevel = 1;
public static bool TryGetEgg(PKM pk, EvoCriteria[] chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg9? result)
public static bool TryGetEgg(PKM pk, ReadOnlySpan<EvoCriteria> chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg9? result)
{
if (version == 0 && pk.IsEgg)
version = SL;
return TryGetEgg(chain, version, out result);
}
public static bool TryGetEgg(EvoCriteria[] chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg9? result)
public static bool TryGetEgg(ReadOnlySpan<EvoCriteria> chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg9? result)
{
result = null;
var devolved = chain[^1];

View File

@ -0,0 +1,23 @@
using System.Collections.Generic;
namespace PKHeX.Core;
public sealed class EncounterGenerator9a : IEncounterGenerator
{
public static readonly EncounterGenerator9a Instance = new();
public bool CanGenerateEggs => false;
public IEnumerable<IEncounterable> GetPossible(PKM _, EvoCriteria[] chain, GameVersion __, EncounterTypeGroup groups)
{
var iterator = new EncounterPossible9a(chain, groups);
foreach (var enc in iterator)
yield return enc;
}
public IEnumerable<IEncounterable> GetEncounters(PKM pk, EvoCriteria[] chain, LegalInfo info)
{
var iterator = new EncounterEnumerator9a(pk, chain);
foreach (var enc in iterator)
yield return enc.Encounter;
}
}

View File

@ -0,0 +1,33 @@
using System.Collections.Generic;
using static PKHeX.Core.GameVersion;
namespace PKHeX.Core;
public sealed class EncounterGenerator9X : IEncounterGenerator
{
public static readonly EncounterGenerator9X Instance = new();
public bool CanGenerateEggs => false;
public IEnumerable<IEncounterable> GetPossible(PKM pk, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups) => game switch
{
ZA => EncounterGenerator9a.Instance.GetPossible(pk, chain, game, groups),
SL or VL => EncounterGenerator9.Instance.GetPossible(pk, chain, game, groups),
_ => [],
};
public IEnumerable<IEncounterable> GetEncounters(PKM pk, LegalInfo info)
{
var chain = EncounterOrigin.GetOriginChain(pk, 9, pk.Context);
if (chain.Length == 0)
return [];
return GetEncounters(pk, chain, info);
}
public IEnumerable<IEncounterable> GetEncounters(PKM pk, EvoCriteria[] chain, LegalInfo info) => pk.Version switch
{
ZA => EncounterGenerator9a.Instance.GetEncounters(pk, chain, info),
SL or VL => EncounterGenerator9.Instance.GetEncounters(pk, chain, info),
_ => [],
};
}

View File

@ -553,4 +553,19 @@ public bool IsSatisfiedIVs(uint iv32)
if (!IsSatisfiedIV(IV_SPD, (int)((iv32 >> 25) & 0x1F))) return false;
return true;
}
/// <summary>
/// Gets the IV based on the specified index (visual order).
/// </summary>
/// <param name="index">Stat index (visual order).</param>
public sbyte GetIV(int index) => index switch
{
0 => IV_HP,
1 => IV_ATK,
2 => IV_DEF,
3 => IV_SPA,
4 => IV_SPD,
5 => IV_SPE,
_ => throw new ArgumentOutOfRangeException(nameof(index), index, null),
};
}

View File

@ -29,7 +29,7 @@ public static class EncounterGenerator
6 => EncounterGenerator6.Instance.GetEncounters(pk, info),
7 => EncounterGenerator7X.Instance.GetEncounters(pk, info),
8 => EncounterGenerator8X.Instance.GetEncounters(pk, info),
9 => EncounterGenerator9.Instance.GetEncounters(pk, info),
9 => EncounterGenerator9X.Instance.GetEncounters(pk, info),
_ => EncounterGeneratorDummy.Instance.GetEncounters(pk, info),
};
@ -61,7 +61,11 @@ public static class EncounterGenerator
GameVersion.BD or GameVersion.SP => EncounterGenerator8b.Instance,
_ => EncounterGenerator8.Instance,
},
9 => EncounterGenerator9.Instance,
9 => version switch
{
GameVersion.ZA => EncounterGenerator9a.Instance,
_ => EncounterGenerator9.Instance,
},
_ => EncounterGeneratorDummy.Instance,
};
}

View File

@ -0,0 +1,156 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace PKHeX.Core;
/// <summary>
/// Iterates to find possible encounters for <see cref="EntityContext.Gen9a"/> encounters.
/// </summary>
public record struct EncounterPossible9a(EvoCriteria[] Chain, EncounterTypeGroup Flags) : IEnumerator<IEncounterable>
{
public IEncounterable Current { get; private set; } = null!;
private int Index;
private int SubIndex;
private YieldState State;
readonly object IEnumerator.Current => Current;
public readonly void Reset() => throw new NotSupportedException();
public readonly void Dispose() { }
public readonly IEnumerator<IEncounterable> GetEnumerator() => this;
private enum YieldState : byte
{
Start,
EventStart,
Event,
EventLocal,
TradeStart,
Trade,
StaticStart,
SlotStart,
Slot,
SlotEnd,
StaticCapture,
StaticGift,
StaticEnd,
End,
}
public bool MoveNext()
{
switch (State)
{
case YieldState.Start:
if (Chain.Length == 0)
break;
goto case YieldState.EventStart;
case YieldState.EventStart:
if (!Flags.HasFlag(EncounterTypeGroup.Mystery))
goto case YieldState.TradeStart;
State = YieldState.Event; goto case YieldState.Event;
case YieldState.Event:
if (TryGetNext(EncounterEvent.MGDB_G9A))
return true;
Index = 0; State = YieldState.EventLocal; goto case YieldState.EventLocal;
case YieldState.EventLocal:
if (TryGetNext(EncounterEvent.EGDB_G9A))
return true;
Index = 0; goto case YieldState.TradeStart;
case YieldState.TradeStart:
if (!Flags.HasFlag(EncounterTypeGroup.Trade))
goto case YieldState.StaticStart;
State = YieldState.Trade; goto case YieldState.Trade;
case YieldState.Trade:
if (TryGetNext(Encounters9a.Trades))
return true;
Index = 0; goto case YieldState.StaticStart;
case YieldState.StaticStart:
if (!Flags.HasFlag(EncounterTypeGroup.Static))
goto case YieldState.SlotStart;
goto case YieldState.StaticCapture;
case YieldState.StaticCapture:
if (TryGetNext(Encounters9a.Static))
return true;
Index = 0; State = YieldState.StaticGift; goto case YieldState.StaticGift;
case YieldState.StaticGift:
if (TryGetNext(Encounters9a.Gifts))
return true;
Index = 0; goto case YieldState.StaticEnd;
case YieldState.StaticEnd:
goto case YieldState.SlotStart;
case YieldState.SlotStart:
if (!Flags.HasFlag(EncounterTypeGroup.Slot))
goto case YieldState.End;
goto case YieldState.Slot;
case YieldState.Slot:
if (TryGetNext<EncounterArea9a, EncounterSlot9a>(Encounters9a.Slots))
return true;
goto case YieldState.SlotEnd;
case YieldState.SlotEnd:
goto case YieldState.End;
case YieldState.End:
break;
}
return false;
}
private bool TryGetNext<TArea, TSlot>(TArea[] areas)
where TArea : class, IEncounterArea<TSlot>
where TSlot : class, IEncounterable, IEncounterMatch
{
for (; Index < areas.Length; Index++, SubIndex = 0)
{
var area = areas[Index];
if (TryGetNextSub(area.Slots))
return true;
}
return false;
}
private bool TryGetNextSub<T>(T[] slots) where T : class, IEncounterable, IEncounterMatch
{
while (SubIndex < slots.Length)
{
var enc = slots[SubIndex++];
foreach (var evo in Chain)
{
if (enc.Species != evo.Species)
continue;
return SetCurrent(enc);
}
}
return false;
}
private bool TryGetNext<T>(T[] db) where T : class, IEncounterable, IEncounterMatch
{
for (; Index < db.Length;)
{
var enc = db[Index++];
foreach (var evo in Chain)
{
if (evo.Species != enc.Species)
continue;
return SetCurrent(enc);
}
}
return false;
}
private bool SetCurrent(IEncounterable match)
{
Current = match;
return true;
}
}

View File

@ -0,0 +1,210 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace PKHeX.Core;
/// <summary>
/// Iterates to find potentially matched encounters for <see cref="GameVersion.ZA"/>.
/// </summary>
public record struct EncounterEnumerator9a(PKM Entity, EvoCriteria[] Chain) : IEnumerator<MatchedEncounter<IEncounterable>>
{
private IEncounterable? Deferred;
private int Index;
private int SubIndex;
private EncounterMatchRating Rating = EncounterMatchRating.MaxNotMatch;
private bool Yielded;
public MatchedEncounter<IEncounterable> Current { get; private set; }
private YieldState State;
private ushort met;
private bool mustBeSlot;
readonly object IEnumerator.Current => Current;
public readonly void Reset() => throw new NotSupportedException();
public readonly void Dispose() { }
public readonly IEnumerator<MatchedEncounter<IEncounterable>> GetEnumerator() => this;
private enum YieldState : byte
{
Start,
Event,
EventLocal,
TradeStart,
Trade,
StartCaptures,
StaticStart,
SlotStart,
Slot,
SlotEnd,
StaticCapture,
StaticGift,
StaticEnd,
Fallback,
End,
}
public bool MoveNext()
{
switch (State)
{
case YieldState.Start:
if (Chain.Length == 0)
break;
if (!Entity.FatefulEncounter)
goto case YieldState.TradeStart;
State = YieldState.Event; goto case YieldState.Event;
case YieldState.Event:
if (TryGetNext(EncounterEvent.MGDB_G9A))
return true;
Index = 0; State = YieldState.EventLocal; goto case YieldState.EventLocal;
case YieldState.EventLocal:
if (TryGetNext(EncounterEvent.EGDB_G9A))
return true;
if (Yielded)
break;
Index = 0; goto case YieldState.TradeStart;
case YieldState.TradeStart:
if (Entity.MetLocation != Locations.LinkTrade6NPC)
goto case YieldState.StartCaptures;
State = YieldState.Trade; goto case YieldState.Trade;
case YieldState.Trade:
if (TryGetNext(Encounters9a.Trades))
return true;
if (Yielded)
break;
Index = 0; goto case YieldState.StartCaptures;
case YieldState.StartCaptures:
InitializeWildLocationInfo();
if (mustBeSlot)
goto case YieldState.SlotStart;
goto case YieldState.StaticStart;
case YieldState.SlotStart:
if (!EncounterStateUtil.CanBeWildEncounter(Entity))
goto case YieldState.SlotEnd;
State = YieldState.Slot; goto case YieldState.Slot;
case YieldState.Slot:
if (TryGetNext<EncounterArea9a, EncounterSlot9a>(Encounters9a.Slots))
return true;
Index = 0; goto case YieldState.SlotEnd;
case YieldState.SlotEnd:
if (!mustBeSlot)
goto case YieldState.Fallback; // already checked everything else
goto case YieldState.StaticStart;
case YieldState.StaticStart:
State = YieldState.StaticCapture; goto case YieldState.StaticCapture;
case YieldState.StaticCapture:
if (TryGetNext(Encounters9a.Static))
return true;
Index = 0; State = YieldState.StaticGift; goto case YieldState.StaticGift;
case YieldState.StaticGift:
if (TryGetNext(Encounters9a.Gifts))
return true;
Index = 0; State = YieldState.StaticEnd; goto case YieldState.StaticEnd;
case YieldState.StaticEnd:
if (mustBeSlot)
goto case YieldState.Fallback; // already checked everything else
Index = 0; goto case YieldState.SlotStart;
case YieldState.Fallback:
State = YieldState.End;
if (Deferred != null)
return SetCurrent(Deferred, Rating);
break;
}
return false;
}
private void InitializeWildLocationInfo()
{
met = Entity.MetLocation;
mustBeSlot = Entity is IRibbonIndex r && r.HasEncounterMark();
}
private bool TryGetNext<TArea, TSlot>(TArea[] areas)
where TArea : class, IEncounterArea<TSlot>, IAreaLocation
where TSlot : class, IEncounterable, IEncounterMatch
{
for (; Index < areas.Length; Index++, SubIndex = 0)
{
var area = areas[Index];
if (!area.IsMatchLocation(met))
continue;
if (TryGetNextSub(area.Slots))
return true;
}
return false;
}
private bool TryGetNextSub<T>(T[] slots) where T : class, IEncounterable, IEncounterMatch
{
while (SubIndex < slots.Length)
{
var enc = slots[SubIndex++];
foreach (var evo in Chain)
{
if (enc.Species != evo.Species)
continue;
if (!enc.IsMatchExact(Entity, evo))
break;
var rating = enc.GetMatchRating(Entity);
if (rating == EncounterMatchRating.Match)
return SetCurrent(enc);
if (rating < Rating)
{
Deferred = enc;
Rating = rating;
}
break;
}
}
return false;
}
private bool TryGetNext<T>(T[] db) where T : class, IEncounterable, IEncounterMatch
{
for (; Index < db.Length;)
{
var enc = db[Index++];
foreach (var evo in Chain)
{
if (evo.Species != enc.Species)
continue;
if (!enc.IsMatchExact(Entity, evo))
break;
var rating = enc.GetMatchRating(Entity);
if (rating == EncounterMatchRating.Match)
return SetCurrent(enc);
if (rating < Rating)
{
Deferred = enc;
Rating = rating;
}
break;
}
}
return false;
}
private bool SetCurrent<T>(T enc, EncounterMatchRating rating = EncounterMatchRating.Match) where T : IEncounterable
{
Current = new MatchedEncounter<IEncounterable>(enc, rating);
Yielded = true;
return true;
}
}

View File

@ -89,6 +89,13 @@ private static IEnumerable<IEncounterable> GetLearnInternal(ushort species, byte
foreach (var enc in encs)
yield return enc;
}
if (PersonalTable.ZA.IsPresentInGame(species, form))
{
var blank = new PA9 { Species = species, Form = form };
var encs = EncounterMovesetGenerator.GenerateEncounters(blank, moves, GameVersion.ZA);
foreach (var enc in encs)
yield return enc;
}
if (PersonalTable.LA.IsPresentInGame(species, form))
{
var blank = new PA8 { Species = species, Form = form };

View File

@ -41,7 +41,7 @@ public string LongName
public PB7 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage789((LanguageID)tr.Language);
var rnd = Util.Rand;
var date = this.GetRandomValidDate();
var pk = new PB7

View File

@ -113,7 +113,7 @@ public bool IsBallValid(Ball ball, ushort currentSpecies, PKM pk)
public PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
var pk = GetBlank();
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage789((LanguageID)tr.Language);
var rnd = Util.Rand;
{
pk.Language = language;

View File

@ -32,7 +32,7 @@ public sealed record EncounterSlot1(EncounterArea1 Parent, ushort Species, byte
public PK1 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
var version = this.GetCompatibleVersion(tr.Version);
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version);
int language = (int)Language.GetSafeLanguage1((LanguageID)tr.Language, version);
var isJapanese = language == (int)LanguageID.Japanese;
var pi = EncounterUtil.GetPersonal1(version, Species);
var pk = new PK1(isJapanese)

View File

@ -35,7 +35,7 @@ public sealed record EncounterStatic1(ushort Species, byte Level, GameVersion Ve
public PK1 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
var version = this.GetCompatibleVersion(tr.Version);
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version);
int language = (int)Language.GetSafeLanguage1((LanguageID)tr.Language, version);
var isJapanese = language == (int)LanguageID.Japanese;
var pi = EncounterUtil.GetPersonal1(version, Species);
var pk = new PK1(isJapanese)

View File

@ -116,7 +116,7 @@ public PK1 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
bool gsc = CanObtainMinGSC();
var level = gsc ? LevelMinGSC : LevelMinRBY;
var version = this.GetCompatibleVersion(tr.Version);
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version);
int language = (int)Language.GetSafeLanguage1((LanguageID)tr.Language, version);
var isJapanese = language == (int)LanguageID.Japanese;
var pi = EncounterUtil.GetPersonal1(version, Species);
var pk = new PK1(isJapanese)

View File

@ -31,7 +31,7 @@ public sealed record EncounterEgg2(ushort Species, GameVersion Version) : IEncou
public PK2 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, Version);
int language = (int)Language.GetSafeLanguage2((LanguageID)tr.Language);
var rnd = Util.Rand;
var pk = new PK2(language == (int)LanguageID.Japanese)

View File

@ -80,7 +80,7 @@ public bool IsTreeAvailable(ushort trainerID)
public PK2 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage2((LanguageID)tr.Language);
var isJapanese = language == (int)LanguageID.Japanese;
var pi = PersonalTable.C[Species];
var rnd = Util.Rand;

View File

@ -43,7 +43,7 @@ public sealed record EncounterStatic2(ushort Species, byte Level, GameVersion Ve
public PK2 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
var version = this.GetCompatibleVersion(tr.Version);
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version);
int language = (int)Language.GetSafeLanguage2((LanguageID)tr.Language);
var pi = PersonalTable.C[Species];
var pk = new PK2
{

View File

@ -57,7 +57,7 @@ public EncounterTrade2(ReadOnlySpan<string[]> names, byte index, ushort species,
public PK2 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
var version = this.GetCompatibleVersion(tr.Version);
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version);
int language = (int)Language.GetSafeLanguage2((LanguageID)tr.Language);
var isJapanese = language == (int)LanguageID.Japanese;
var pi = PersonalTable.C[Species];
var pk = new PK2(isJapanese)

View File

@ -85,7 +85,7 @@ private int GetTemplateLanguage(ITrainerInfo tr)
{
if (IsJapaneseBonusDisk)
return 1; // Japanese
return (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
return (int)Language.GetSafeLanguage3((LanguageID)tr.Language);
}
private static void SetPINGA(CK3 pk, in EncounterCriteria criteria, PersonalInfo3 pi)

View File

@ -78,7 +78,7 @@ public CK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
return pk;
}
private int GetTemplateLanguage(ITrainerInfo tr) => IsEReader ? 1 : (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
private int GetTemplateLanguage(ITrainerInfo tr) => IsEReader ? 1 : (int)Language.GetSafeLanguage3((LanguageID)tr.Language);
private void SetPINGA(CK3 pk, in EncounterCriteria criteria, PersonalInfo3 pi)
{

View File

@ -67,7 +67,7 @@ public CK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
return pk;
}
private int GetTemplateLanguage(ITrainerInfo tr) => (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
private int GetTemplateLanguage(ITrainerInfo tr) => (int)Language.GetSafeLanguage3((LanguageID)tr.Language);
private void SetPINGA(CK3 pk, EncounterCriteria criteria)
{

View File

@ -45,7 +45,7 @@ public RandomCorrelationRating IsCompatible(PIDType type, PKM pk)
public PK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, Version);
int language = (int)Language.GetSafeLanguage3((LanguageID)tr.Language);
var pk = new PK3
{

View File

@ -39,7 +39,7 @@ public record EncounterSlot3(EncounterArea3 Parent, ushort Species, byte Form, b
public PK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage3((LanguageID)tr.Language);
var version = Version switch
{
GameVersion.RSE => tr.Version switch

View File

@ -92,7 +92,7 @@ private int GetTemplateLanguage(ITrainerInfo tr)
if (Species is (ushort)Core.Species.Deoxys && tr.Language == 1)
return (int)LanguageID.English;
return (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
return (int)Language.GetSafeLanguage3((LanguageID)tr.Language);
}
private void SetPINGA(PK3 pk, in EncounterCriteria criteria, PersonalInfo3 pi)

View File

@ -86,7 +86,7 @@ public EncounterTrade3(ReadOnlySpan<string[]> names, byte index, GameVersion ver
public PK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage3((LanguageID)tr.Language);
var version = this.GetCompatibleVersion(tr.Version);
var pi = PersonalTable.E[Species];
var pk = new PK3

View File

@ -44,7 +44,7 @@ public sealed record EncounterShadow3XD(byte Index, ushort Gauge, ReadOnlyMemory
public XK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage3((LanguageID)tr.Language);
var pi = PersonalTable.E[Species];
var pk = new XK3
{

View File

@ -32,7 +32,7 @@ public sealed record EncounterSlot3XD(EncounterArea3XD Parent, ushort Species, b
public XK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage3((LanguageID)tr.Language);
var pi = PersonalTable.E[Species];
var pk = new XK3
{

View File

@ -36,7 +36,7 @@ public sealed record EncounterStatic3XD(ushort Species, byte Level)
public XK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage3((LanguageID)tr.Language);
var pi = PersonalTable.E[Species];
var pk = new XK3
{

View File

@ -91,7 +91,7 @@ public XK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
return pk;
}
private int GetTemplateLanguage(ITrainerInfo tr) => (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
private int GetTemplateLanguage(ITrainerInfo tr) => (int)Language.GetSafeLanguage3((LanguageID)tr.Language);
private static void SetPINGA(XK3 pk, in EncounterCriteria criteria, PersonalInfo3 pi)
{

View File

@ -50,7 +50,7 @@ public RandomCorrelationRating IsCompatible(PIDType type, PKM pk)
public PK4 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, Version);
int language = (int)Language.GetSafeLanguage456((LanguageID)tr.Language);
var date = EncounterDate.GetDateNDS();
var pk = new PK4

View File

@ -46,7 +46,7 @@ private Ball GetRequiredBallValue(Ball fallback = Ball.None)
public PK4 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage456((LanguageID)tr.Language);
var pi = PersonalTable.HGSS[Species];
var pk = new PK4
{

View File

@ -53,7 +53,7 @@ public sealed record EncounterStatic4(GameVersion Version)
public PK4 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage456((LanguageID)tr.Language);
var version = this.GetCompatibleVersion(tr.Version);
var pi = PersonalTable.HGSS[Species];
var pk = new PK4

View File

@ -69,7 +69,7 @@ public static EncounterStatic4Pokewalker[] GetAll(ReadOnlySpan<byte> data)
public PK4 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage456((LanguageID)tr.Language);
var version = this.GetCompatibleVersion(tr.Version);
var pi = PersonalTable.HGSS[Species];
var pk = new PK4

View File

@ -77,7 +77,7 @@ public EncounterTrade4PID(ReadOnlySpan<string[]> names, byte index, GameVersion
public PK4 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage456((LanguageID)tr.Language);
var version = this.GetCompatibleVersion(tr.Version);
var pi = PersonalTable.DP[Species];
var pk = new PK4

View File

@ -85,7 +85,7 @@ public EncounterTrade4RanchGift(ushort species, byte met, byte level)
public PK4 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage456((LanguageID)tr.Language);
var version = this.GetCompatibleVersion(tr.Version);
var pi = PersonalTable.DP[Species];
var pk = new PK4

View File

@ -34,7 +34,7 @@ public sealed record EncounterEgg5(ushort Species, byte Form, GameVersion Versio
public PK5 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, Version);
int language = (int)Language.GetSafeLanguage456((LanguageID)tr.Language);
var date = EncounterDate.GetDateNDS();
var pk = new PK5

View File

@ -47,7 +47,7 @@ public sealed record EncounterSlot5(EncounterArea5 Parent, ushort Species, byte
public PK5 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage456((LanguageID)tr.Language);
var pi = PersonalTable.B2W2[Species];
var pk = new PK5
{

View File

@ -40,7 +40,7 @@ public sealed record EncounterStatic5(GameVersion Version)
public PK5 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage456((LanguageID)tr.Language);
var version = this.GetCompatibleVersion(tr.Version);
var pi = PersonalTable.B2W2[Species];
var pk = new PK5

View File

@ -32,7 +32,7 @@ public EncounterStatic5Entree(GameVersion version, ushort species, byte level, b
public PK5 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage456((LanguageID)tr.Language);
var version = this.GetCompatibleVersion(tr.Version);
var pi = PersonalTable.B2W2[Species];
var pk = new PK5

View File

@ -47,7 +47,7 @@ public sealed record EncounterStatic5N(uint PID)
public PK5 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage456((LanguageID)tr.Language);
var version = this.GetCompatibleVersion(tr.Version);
var pi = PersonalTable.B2W2[Species];
var pk = new PK5

View File

@ -28,7 +28,7 @@ public sealed record EncounterStatic5Radar(ushort Species, byte Form, AbilityPer
public PK5 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage456((LanguageID)tr.Language);
var version = this.GetCompatibleVersion(tr.Version);
var pi = PersonalTable.B2W2[Species];
var pk = new PK5

View File

@ -71,7 +71,7 @@ public EncounterTrade5B2W2(string[] names, GameVersion version)
public PK5 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage456((LanguageID)tr.Language);
var version = this.GetCompatibleVersion(tr.Version);
var pi = PersonalTable.B2W2[Species];
var pk = new PK5

View File

@ -69,7 +69,7 @@ public EncounterTrade5BW(ReadOnlySpan<string[]> names, byte index, GameVersion v
public PK5 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage456((LanguageID)tr.Language);
var version = this.GetCompatibleVersion(tr.Version);
var pi = PersonalTable.BW[Species];
var pk = new PK5

View File

@ -32,7 +32,7 @@ public sealed record EncounterEgg6(ushort Species, byte Form, GameVersion Versio
public PK6 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, Version);
int language = (int)Language.GetSafeLanguage456((LanguageID)tr.Language);
var date = EncounterDate.GetDate3DS();
var pi = PersonalTable.AO[Species, Form];
var rnd = Util.Rand;

View File

@ -57,7 +57,7 @@ private ReadOnlySpan<ushort> GetDexNavMoves()
public PK6 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage456((LanguageID)tr.Language);
var pi = PersonalTable.AO[Species];
var geo = tr.GetRegionOrigin(language);
var pk = new PK6

View File

@ -49,7 +49,7 @@ public sealed record EncounterSlot6XY(EncounterArea6XY Parent, ushort Species, b
public PK6 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage456((LanguageID)tr.Language);
var version = Version != GameVersion.XY ? Version : tr.Version is GameVersion.X or GameVersion.Y ? tr.Version : GameVersion.X;
var form = GetWildForm(Form);
var pi = PersonalTable.XY[Species, form];

View File

@ -50,7 +50,7 @@ public sealed record EncounterStatic6(GameVersion Version)
public PK6 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage456((LanguageID)tr.Language);
var version = this.GetCompatibleVersion(tr.Version);
var pi = PersonalTable.AO[Species];
var rnd = Util.Rand;

View File

@ -66,7 +66,7 @@ public EncounterTrade6(ReadOnlySpan<string[]> names, byte index, GameVersion ver
public PK6 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage456((LanguageID)tr.Language);
var version = this.GetCompatibleVersion(tr.Version);
var pi = PersonalTable.AO[Species];
var rnd = Util.Rand;

View File

@ -30,7 +30,7 @@ public sealed record EncounterEgg7(ushort Species, byte Form, GameVersion Versio
public PK7 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, Version);
int language = (int)Language.GetSafeLanguage789((LanguageID)tr.Language);
var date = EncounterDate.GetDate3DS();
var pi = PersonalTable.USUM[Species, Form];
var rnd = Util.Rand;

View File

@ -45,7 +45,7 @@ public sealed record EncounterSlot7(EncounterArea7 Parent, ushort Species, byte
public PK7 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted);
public PK7 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage789((LanguageID)tr.Language);
var form = GetWildForm(Form);
var pi = PersonalTable.USUM[Species, form];
var geo = tr.GetRegionOrigin(language);

View File

@ -53,7 +53,7 @@ public sealed record EncounterStatic7(GameVersion Version)
public PK7 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage789((LanguageID)tr.Language);
var version = this.GetCompatibleVersion(tr.Version);
var pi = PersonalTable.USUM[Species, Form];
var geo = tr.GetRegionOrigin(language);

View File

@ -64,7 +64,7 @@ public EncounterTrade7(ReadOnlySpan<string[]> names, byte index, GameVersion ver
public PK7 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage789((LanguageID)tr.Language);
var version = this.GetCompatibleVersion(tr.Version);
var pi = PersonalTable.USUM[Species, Form];
var rnd = Util.Rand;

View File

@ -28,7 +28,7 @@ public sealed record EncounterSlot7b(EncounterArea7b Parent, ushort Species, byt
public PB7 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted);
public PB7 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage789((LanguageID)tr.Language);
var pi = PersonalTable.GG[Species];
var date = EncounterDate.GetDateSwitch();
var pk = new PB7

View File

@ -34,7 +34,7 @@ public sealed record EncounterStatic7b(GameVersion Version)
public PB7 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted);
public PB7 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage789((LanguageID)tr.Language);
var version = this.GetCompatibleVersion(tr.Version);
var pi = PersonalTable.GG[Species, Form];
var date = EncounterDate.GetDateSwitch();

View File

@ -46,7 +46,7 @@ public sealed record EncounterTrade7b(GameVersion Version) : IEncounterable, IEn
public PB7 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
int language = (int)Language.GetSafeLanguage789((LanguageID)tr.Language);
var version = this.GetCompatibleVersion(tr.Version);
var pi = PersonalTable.GG[Species, Form];
var date = EncounterDate.GetDateSwitch();

View File

@ -28,7 +28,7 @@ public sealed record EncounterEgg8(ushort Species, byte Form, GameVersion Versio
public PK8 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, Version);
int language = (int)Language.GetSafeLanguage789((LanguageID)tr.Language);
var date = EncounterDate.GetDateSwitch();
var pi = PersonalTable.SWSH[Species, Form];
var rnd = Util.Rand;

Some files were not shown because too many files have changed in this diff Show More