Compare commits

..

No commits in common. "master" and "26.02.27" have entirely different histories.

403 changed files with 12759 additions and 24389 deletions

View File

@ -9,7 +9,7 @@ Supporta i seguenti tipi di file:
* File di Memory Card GameCube (\*.raw, \*.bin) contenenti File di Salvataggio Pokémon.
* File di Entità Pokémon individuali (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4, \*.rk4)
* File di Dono Segreto (\*.pgt, \*.pcd, \*.pgf, .wc\*) inclusa conversione in .pk\*
* Importazione di Entità del GO Park (\*.gp1) inclusa conversione in .pb7
* Importazione di Entità del Go Park (\*.gp1) inclusa conversione in .pb7
* Importazione di squadre da Video Lotta del 3DS decriptati
* Trasferimento da una generazione all'altra, convertendo i formati propriamente.

View File

@ -9,7 +9,7 @@ PKHeX
* GameCube 宝可梦游戏存档包含 GameCube 记忆存档 (\*.raw, \*.bin)
* 单个宝可梦实体文件 (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4, \*.rk4)
* 神秘礼物文件 (\*.pgt, \*.pcd, \*.pgf, .wc\*) 并转换为 .pk\*
* 导入 GO Park存档 (\*.gp1) 并转换为 .pb7
* 导入 Go Park存档 (\*.gp1) 并转换为 .pb7
* 从已破解的 3DS 对战视频中导入队伍
* 支持宝可梦在不同世代的间转移,并转换文件格式

View File

@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>26.03.20</Version>
<Version>26.02.27</Version>
<LangVersion>14</LangVersion>
<Nullable>enable</Nullable>
<NeutralLanguage>en</NeutralLanguage>

View File

@ -342,12 +342,12 @@ private bool ParseLineNature(ReadOnlySpan<char> input, ReadOnlySpan<string> natu
return false;
var nature = (Nature)index;
if (!nature.IsFixed)
if (!nature.IsFixed())
{
LogError(NatureUnrecognized, input);
return false;
}
if (Nature.IsFixed && Nature != nature)
if (Nature != Nature.Random && Nature != nature)
{
LogError(NatureAlreadySpecified, input);
return false;
@ -627,7 +627,7 @@ private void AddEVs(List<string> result, in BattleTemplateExportSettings setting
BattleTemplateToken.EVsAppendNature => GetStringStatsNatureAmp(EVs, 0, nameEVs, Nature),
_ => GetStringStats(EVs, 0, nameEVs),
};
if (token is BattleTemplateToken.EVsAppendNature && Nature.IsFixed)
if (token is BattleTemplateToken.EVsAppendNature && Nature.IsFixed())
line += $" ({settings.Localization.Strings.natures[(int)Nature]})";
result.Add(cfg.Push(BattleTemplateToken.EVs, line));
}
@ -1081,7 +1081,7 @@ private bool ParseLineEVs(ReadOnlySpan<char> line, BattleTemplateLocalization lo
return false; // invalid line
}
if (Nature.IsFixed) // specified in a separate Nature line
if (Nature != Nature.Random) // specified in a separate Nature line
LogError(NatureEffortAmpAlreadySpecified, natureName);
else
Nature = (Nature)natureIndex;
@ -1100,7 +1100,7 @@ private bool ParseLineEVs(ReadOnlySpan<char> line, BattleTemplateLocalization lo
result.TreatAmpsAsSpeedNotLast();
var ampNature = AdjustNature(result.Plus, result.Minus);
success &= ampNature;
if (ampNature && currentNature.IsFixed && currentNature != Nature)
if (ampNature && currentNature != Nature.Random && currentNature != Nature)
{
LogError(NatureEffortAmpConflictNature);
Nature = currentNature; // revert to original

View File

@ -205,10 +205,10 @@ public ModifyResult TryModify(TObject entity, IEnumerable<StringInstruction> fil
return result;
}
private static Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>>[] GetPropertyDictionaries(ReadOnlySpan<Type> types, int expectedMax)
private static Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>>[] GetPropertyDictionaries(IReadOnlyList<Type> types, int expectedMax)
{
var result = new Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>>[types.Length];
for (int i = 0; i < types.Length; i++)
var result = new Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>>[types.Count];
for (int i = 0; i < types.Count; i++)
result[i] = GetPropertyDictionary(types[i], ReflectUtil.GetAllPropertyInfoPublic, expectedMax).GetAlternateLookup<ReadOnlySpan<char>>();
return result;
}

View File

@ -30,10 +30,8 @@ public void SetNickname(string nick)
pk.ClearNickname();
return;
}
pk.PrepareNickname();
pk.Nickname = nick;
pk.IsNicknamed = true;
pk.Nickname = nick;
}
/// <summary>
@ -139,7 +137,7 @@ public bool SetUnshiny()
/// <param name="nature">Desired <see cref="PKM.Nature"/> value to set.</param>
public void SetNature(Nature nature)
{
if (!nature.IsFixed)
if (!nature.IsFixed())
nature = 0; // default valid
var format = pk.Format;

View File

@ -14,6 +14,9 @@ public sealed class AdvancedSettings
[LocalizedDescription("Folder path that contains dump(s) of block hash-names. If a specific dump file does not exist, only names defined within the program's code will be loaded.")]
public string PathBlockKeyList { get; set; } = string.Empty;
[LocalizedDescription("Hide event variables below this event type value. Removes event values from the GUI that the user doesn't care to view.")]
public NamedEventType HideEventTypeBelow { get; set; }
[LocalizedDescription("Hide event variable names for that contain any of the comma-separated substrings below. Removes event values from the GUI that the user doesn't care to view.")]
public string HideEvent8Contains { get; set; } = string.Empty;

View File

@ -1,12 +1,21 @@
using System;
using System;
namespace PKHeX.Core;
/// <summary>
/// Base class for defining a manipulation of box data.
/// </summary>
public abstract record BoxManipBase(BoxManipType Type, Func<SaveFile, bool> Usable) : IBoxManip
public abstract class BoxManipBase : IBoxManip
{
public BoxManipType Type { get; }
public Func<SaveFile, bool> Usable { get; }
protected BoxManipBase(BoxManipType type, Func<SaveFile, bool> usable)
{
Type = type;
Usable = usable;
}
public abstract string GetPrompt(bool all);
public abstract string GetFail(bool all);
public abstract string GetSuccess(bool all);

View File

@ -5,7 +5,7 @@ namespace PKHeX.Core;
/// <summary>
/// Clears contents of boxes by deleting all that satisfy a criteria.
/// </summary>
public sealed record BoxManipClear(BoxManipType Type, Func<PKM, bool> Criteria, Func<SaveFile, bool> Usable) : BoxManipBase(Type, Usable)
public sealed class BoxManipClear(BoxManipType Type, Func<PKM, bool> criteria, Func<SaveFile, bool> Usable) : BoxManipBase(Type, Usable)
{
public BoxManipClear(BoxManipType Type, Func<PKM, bool> Criteria) : this(Type, Criteria, _ => true) { }
@ -18,6 +18,6 @@ public override int Execute(SaveFile sav, BoxManipParam param)
var (start, stop, reverse) = param;
return sav.ClearBoxes(start, stop, Method);
bool Method(PKM p) => reverse ^ Criteria(p);
bool Method(PKM p) => reverse ^ criteria(p);
}
}

View File

@ -5,7 +5,7 @@ namespace PKHeX.Core;
/// <summary>
/// Clears contents of boxes by deleting all that satisfy a criteria based on a <see cref="SaveFile"/>.
/// </summary>
public sealed record BoxManipClearComplex(BoxManipType Type, Func<PKM, SaveFile, bool> Criteria, Func<SaveFile, bool> Usable) : BoxManipBase(Type, Usable)
public sealed class BoxManipClearComplex(BoxManipType Type, Func<PKM, SaveFile, bool> criteria, Func<SaveFile, bool> Usable) : BoxManipBase(Type, Usable)
{
public BoxManipClearComplex(BoxManipType Type, Func<PKM, SaveFile, bool> Criteria) : this(Type, Criteria, _ => true) { }
@ -18,6 +18,6 @@ public override int Execute(SaveFile sav, BoxManipParam param)
var (start, stop, reverse) = param;
return sav.ClearBoxes(start, stop, Method);
bool Method(PKM p) => reverse ^ Criteria(p, sav);
bool Method(PKM p) => reverse ^ criteria(p, sav);
}
}

View File

@ -7,7 +7,7 @@ namespace PKHeX.Core;
/// Clears contents of boxes by deleting all but the first duplicate detected.
/// </summary>
/// <typeparam name="T">Base type of the "is duplicate" hash for the duplicate detection.</typeparam>
public sealed record BoxManipClearDuplicate<T> : BoxManipBase
public sealed class BoxManipClearDuplicate<T> : BoxManipBase
{
private readonly HashSet<T> HashSet = [];
private readonly Func<PKM, bool> Criteria;

View File

@ -5,8 +5,8 @@ namespace PKHeX.Core;
/// <summary>
/// Modifies contents of boxes by using an <see cref="Action"/> to change data.
/// </summary>
public sealed record BoxManipModify(BoxManipType Type, Action<PKM> Action, Func<SaveFile, bool> Usable)
: BoxManipBase(Type, Usable)
public sealed class BoxManipModify(BoxManipType type, Action<PKM> Action, Func<SaveFile, bool> Usable)
: BoxManipBase(type, Usable)
{
public BoxManipModify(BoxManipType type, Action<PKM> Action) : this(type, Action, _ => true) { }

View File

@ -5,7 +5,7 @@ namespace PKHeX.Core;
/// <summary>
/// Modifies contents of boxes by using an <see cref="Action"/> (referencing a Save File) to change data.
/// </summary>
public sealed record BoxManipModifyComplex(BoxManipType Type, Action<PKM, SaveFile> Action, Func<SaveFile, bool> Usable)
public sealed class BoxManipModifyComplex(BoxManipType Type, Action<PKM, SaveFile> Action, Func<SaveFile, bool> Usable)
: BoxManipBase(Type, Usable)
{
public BoxManipModifyComplex(BoxManipType Type, Action<PKM, SaveFile> Action) : this(Type, Action, _ => true) { }

View File

@ -6,7 +6,7 @@ namespace PKHeX.Core;
/// <summary>
/// Sorts contents of boxes by using a Sorter to determine the order.
/// </summary>
public sealed record BoxManipSort(BoxManipType Type, Func<IEnumerable<PKM>, IEnumerable<PKM>> Sorter, Func<SaveFile, bool> Usable) : BoxManipBase(Type, Usable)
public sealed class BoxManipSort(BoxManipType Type, Func<IEnumerable<PKM>, IEnumerable<PKM>> Sorter, Func<SaveFile, bool> Usable) : BoxManipBase(Type, Usable)
{
public BoxManipSort(BoxManipType Type, Func<IEnumerable<PKM>, IEnumerable<PKM>> Sorter) : this(Type, Sorter, _ => true) { }

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
namespace PKHeX.Core;
@ -6,7 +6,7 @@ namespace PKHeX.Core;
/// <summary>
/// Sorts contents of boxes by using a <see cref="Sorter"/> (referencing a Save File) to determine the order.
/// </summary>
public sealed record BoxManipSortComplex : BoxManipBase
public sealed class BoxManipSortComplex : BoxManipBase
{
private readonly Func<IEnumerable<PKM>, SaveFile, int, IEnumerable<PKM>> Sorter;
public BoxManipSortComplex(BoxManipType type, Func<IEnumerable<PKM>, SaveFile, IEnumerable<PKM>> sorter) : this(type, sorter, _ => true) { }

View File

@ -61,11 +61,11 @@ private static List<SlotInfoMisc> GetExtraSlots2(SAV2 sav)
private static List<SlotInfoMisc> GetExtraSlots3(SAV3 sav)
{
if (sav is not SAV3FRLG frlg)
if (sav is not SAV3FRLG)
return None;
return
[
new(frlg.LargeBlock.SingleDaycareRoute5, 0) {Type = StorageSlotType.Daycare},
new(sav.LargeBuffer[0x3C98..], 0) {Type = StorageSlotType.Daycare},
];
}
@ -75,7 +75,7 @@ private static List<SlotInfoMisc> GetExtraSlots4(SAV4 sav)
if (sav.GTS > 0)
list.Add(new SlotInfoMisc(sav.GeneralBuffer[sav.GTS..], 0) { Type = StorageSlotType.GTS });
if (sav is SAV4HGSS hgss)
list.Add(new SlotInfoMisc(hgss.GeneralBuffer[SAV4HGSS.WalkerPair..], 1) {Type = StorageSlotType.Pokéwalker});
list.Add(new SlotInfoMisc(hgss.GeneralBuffer[SAV4HGSS.WalkerPair..], 1) {Type = StorageSlotType.Misc});
return list;
}
@ -84,7 +84,7 @@ private static List<SlotInfoMisc> GetExtraSlots5(SAV5 sav)
var list = new List<SlotInfoMisc>
{
new(sav.GTS.Upload, 0) {Type = StorageSlotType.GTS},
new(sav.GlobalLink.Upload, 0) { Type = StorageSlotType.PGL },
new(sav.GlobalLink.Upload, 0) { Type = StorageSlotType.Misc },
new(sav.BattleBox[0], 0) {Type = StorageSlotType.BattleBox},
new(sav.BattleBox[1], 1) {Type = StorageSlotType.BattleBox},
@ -106,7 +106,7 @@ private static List<SlotInfoMisc> GetExtraSlots6XY(SAV6XY sav)
[
new(sav.GTS.Upload, 0) {Type = StorageSlotType.GTS},
new(sav.Fused[0], 0) {Type = StorageSlotType.FusedKyurem},
new(sav.SUBE.GiveSlot, 0, Mutable: true) {Type = StorageSlotType.Scripted}, // Old Man
new(sav.SUBE.GiveSlot, 0, Mutable: true) {Type = StorageSlotType.Misc}, // Old Man
new(sav.BattleBox[0], 0) {Type = StorageSlotType.BattleBox},
new(sav.BattleBox[1], 1) {Type = StorageSlotType.BattleBox},
@ -123,7 +123,7 @@ private static List<SlotInfoMisc> GetExtraSlots6AO(SAV6AO sav)
[
new(sav.GTS.Upload, 0) { Type = StorageSlotType.GTS },
new(sav.Fused[0], 0) { Type = StorageSlotType.FusedKyurem },
new(sav.SUBE.GiveSlot, 0) {Type = StorageSlotType.Scripted},
new(sav.SUBE.GiveSlot, 0) {Type = StorageSlotType.Misc},
new(sav.BattleBox[0], 0) {Type = StorageSlotType.BattleBox},
new(sav.BattleBox[1], 1) {Type = StorageSlotType.BattleBox},
@ -150,9 +150,9 @@ private static List<SlotInfoMisc> GetExtraSlots7(SAV7 sav, bool all)
]);
list.AddRange(
[
new SlotInfoMisc(uu.BattleAgency[0], 0) {Type = StorageSlotType.BattleAgency},
new SlotInfoMisc(uu.BattleAgency[1], 1) {Type = StorageSlotType.BattleAgency},
new SlotInfoMisc(uu.BattleAgency[2], 2) {Type = StorageSlotType.BattleAgency},
new SlotInfoMisc(uu.BattleAgency[0], 0) {Type = StorageSlotType.Misc},
new SlotInfoMisc(uu.BattleAgency[1], 1) {Type = StorageSlotType.Misc},
new SlotInfoMisc(uu.BattleAgency[2], 2) {Type = StorageSlotType.Misc},
]);
}
@ -202,15 +202,15 @@ private static List<SlotInfoMisc> GetExtraSlots8b(SAV8BS sav)
{
return
[
new(sav.UgSaveData[0], 0, true) { Type = StorageSlotType.Underground, HideLegality = true },
new(sav.UgSaveData[1], 1, true) { Type = StorageSlotType.Underground, HideLegality = true },
new(sav.UgSaveData[2], 2, true) { Type = StorageSlotType.Underground, HideLegality = true },
new(sav.UgSaveData[3], 3, true) { Type = StorageSlotType.Underground, HideLegality = true },
new(sav.UgSaveData[4], 4, true) { Type = StorageSlotType.Underground, HideLegality = true },
new(sav.UgSaveData[5], 5, true) { Type = StorageSlotType.Underground, HideLegality = true },
new(sav.UgSaveData[6], 6, true) { Type = StorageSlotType.Underground, HideLegality = true },
new(sav.UgSaveData[7], 7, true) { Type = StorageSlotType.Underground, HideLegality = true },
new(sav.UgSaveData[8], 8, true) { Type = StorageSlotType.Underground, HideLegality = true },
new(sav.UgSaveData[0], 0, true) { Type = StorageSlotType.Misc, HideLegality = true },
new(sav.UgSaveData[1], 1, true) { Type = StorageSlotType.Misc, HideLegality = true },
new(sav.UgSaveData[2], 2, true) { Type = StorageSlotType.Misc, HideLegality = true },
new(sav.UgSaveData[3], 3, true) { Type = StorageSlotType.Misc, HideLegality = true },
new(sav.UgSaveData[4], 4, true) { Type = StorageSlotType.Misc, HideLegality = true },
new(sav.UgSaveData[5], 5, true) { Type = StorageSlotType.Misc, HideLegality = true },
new(sav.UgSaveData[6], 6, true) { Type = StorageSlotType.Misc, HideLegality = true },
new(sav.UgSaveData[7], 7, true) { Type = StorageSlotType.Misc, HideLegality = true },
new(sav.UgSaveData[8], 8, true) { Type = StorageSlotType.Misc, HideLegality = true },
];
}
@ -239,8 +239,8 @@ private static List<SlotInfoMisc> GetExtraSlots9(SAV9SV sav)
if (sav.Blocks.TryGetBlock(SaveBlockAccessor9SV.KSurpriseTrade, out var surprise))
{
list.Add(new(surprise.Raw[0x198..], 0) { Type = StorageSlotType.SurpriseTrade }); // my upload
list.Add(new(surprise.Raw[0x02C..], 1) { Type = StorageSlotType.SurpriseTrade }); // received from others
list.Add(new(surprise.Raw[0x198..], 0) { Type = StorageSlotType.Misc }); // my upload
list.Add(new(surprise.Raw[0x02C..], 1) { Type = StorageSlotType.Misc }); // received from others
}
return list;
}
@ -268,7 +268,7 @@ private static List<SlotInfoMisc> GetExtraSlots9a(SAV9ZA sav)
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, Mutable: true) { Type = StorageSlotType.Scripted });
list.Add(new(entry, i, true, Mutable: true) { Type = StorageSlotType.Misc });
else
break;
}

View File

@ -7,106 +7,28 @@ public enum StorageSlotType : byte
{
None = 0,
/// <summary>
/// Originated from Box
/// </summary>
Box,
/// <summary>
/// Originated from Party
/// </summary>
Party,
/// <summary>
/// Battle Box
/// </summary>
/// <summary> Battle Box </summary>
BattleBox,
/// <summary>
/// Daycare
/// </summary>
/// <summary> Daycare </summary>
Daycare,
/// <summary>
/// Miscellaneous Origin (usually in-game scripted event recollection)
/// </summary>
Scripted,
/// <summary>
/// Global Trade Station (GTS)
/// </summary>
/// <summary> Global Trade Station (GTS) </summary>
GTS,
/// <summary>
/// Pokémon Global Link (PGL)
/// </summary>
PGL,
/// <summary>
/// Surprise Trade Upload/Download
/// </summary>
SurpriseTrade,
/// <summary>
/// Shiny Overworld Cache
/// </summary>
/// <remarks>
/// <see cref="GameVersion.ZA"/>
/// </remarks>
/// <summary> Shiny Overworld Cache </summary>
Shiny,
/// <summary>
/// Underground area wild Pokémon cache
/// </summary>
/// <remarks>
/// <see cref="GameVersion.BD"/>
/// <see cref="GameVersion.SP"/>
/// </remarks>
Underground,
/// <summary>
/// Fused Legendary Storage
/// </summary>
Fused,
/// <summary>
/// Sub-tag for <see cref="Species.Kyurem"/> differentiation.
/// </summary>
/// <summary> Fused Legendary Storage </summary>
FusedKyurem,
/// <summary>
/// Sub-tag for <see cref="Species.Solgaleo"/> differentiation.
/// </summary>
FusedNecrozmaS,
/// <summary>
/// Sub-tag for <see cref="Species.Lunala"/> differentiation.
/// </summary>
FusedNecrozmaM,
/// <summary>
/// Sub-tag for <see cref="Species.Calyrex"/> differentiation.
/// </summary>
FusedCalyrex,
/// <summary>
/// Poké Pelago (Gen7)
/// </summary>
/// <summary> Miscellaneous </summary>
Misc,
/// <summary> Poké Pelago (Gen7) </summary>
Resort,
/// <summary>
/// Ride Legendary Slot (S/V)
/// </summary>
/// <remarks>
/// <see cref="GameVersion.SL"/>
/// <see cref="GameVersion.VL"/>
/// </remarks>
/// <summary> Ride Legendary Slot (S/V) </summary>
Ride,
/// <summary>
/// Battle Agency (Gen7)
/// </summary>
BattleAgency,
/// <summary>
/// Gen4 HeartGold/SoulSilver pedometer accessory upload
/// </summary>
Pokéwalker,
}

View File

@ -55,7 +55,7 @@ public static class NatureUtil
/// Checks if the provided <see cref="value"/> is a valid stored <see cref="Nature"/> value.
/// </summary>
/// <returns>True if value is an actual nature.</returns>
public bool IsFixed => value != Nature.Random;
public bool IsFixed() => value < Nature.Random;
/// <summary>
/// Checks if the provided <see cref="value"/> is a possible mint nature.
@ -63,12 +63,12 @@ public static class NatureUtil
/// <remarks>
/// The only valid mint natures are those which have a stat amp applied, or neutral nature being Serious.
/// </remarks>
public bool IsMint => (value.IsFixed && (byte)value % 6 != 0) || value == Nature.Serious;
public bool IsMint() => (value.IsFixed() && (byte)value % 6 != 0) || value == Nature.Serious;
/// <summary>
/// Checks if the provided <see cref="value"/> is a neutral nature which has no stat amps applied.
/// </summary>
public bool IsNeutral => value.IsFixed && (byte)value % 6 == 0;
public bool IsNeutral() => value.IsFixed() && (byte)value % 6 == 0;
/// <summary>
/// Converts the provided <see cref="value"/> to a neutral nature.

View File

@ -6,6 +6,8 @@ namespace PKHeX.Core;
public sealed class PlayerBag3E : PlayerBag, IPlayerBag3
{
private const int BaseOffset = 0x0498;
public override IReadOnlyList<InventoryPouch3> Pouches { get; } = GetPouches(ItemStorage3E.Instance);
public override ItemStorage3E Info => ItemStorage3E.Instance;
@ -19,7 +21,7 @@ public sealed class PlayerBag3E : PlayerBag, IPlayerBag3
new(0x000, 50, 999, info, PCItems),
];
public PlayerBag3E(SAV3E sav) : this(sav.LargeBlock.Inventory, sav.SmallBlock.SecurityKey) { }
public PlayerBag3E(SAV3E sav) : this(sav.Large[BaseOffset..], sav.SecurityKey) { }
public PlayerBag3E(ReadOnlySpan<byte> data, uint security)
{
UpdateSecurityKey(security);
@ -27,7 +29,7 @@ public PlayerBag3E(ReadOnlySpan<byte> data, uint security)
}
public override void CopyTo(SaveFile sav) => CopyTo((SAV3E)sav);
public void CopyTo(SAV3E sav) => CopyTo(sav.LargeBlock.Inventory);
public void CopyTo(SAV3E sav) => CopyTo(sav.Large[BaseOffset..]);
public void CopyTo(Span<byte> data) => Pouches.SaveAll(data);
public override int GetMaxCount(InventoryType type, int itemIndex)

View File

@ -4,13 +4,14 @@
namespace PKHeX.Core;
public sealed class PlayerBag3FRLG(bool VC) : PlayerBag, IPlayerBag3
public sealed class PlayerBag3FRLG : PlayerBag, IPlayerBag3
{
public override IItemStorage Info => GetInfo(VC);
private static IItemStorage GetInfo(bool vc) => vc ? ItemStorage3FRLG_VC.Instance : ItemStorage3FRLG.Instance;
public override IReadOnlyList<InventoryPouch3> Pouches { get; } = GetPouches(GetInfo(VC));
private const int BaseOffset = 0x0298;
private static InventoryPouch3[] GetPouches(IItemStorage info) =>
public override IReadOnlyList<InventoryPouch3> Pouches { get; } = GetPouches(ItemStorage3FRLG.Instance);
public override ItemStorage3FRLG Info => ItemStorage3FRLG.Instance;
private static InventoryPouch3[] GetPouches(ItemStorage3FRLG info) =>
[
new(0x078, 42, 999, info, Items),
new(0x120, 30, 001, info, KeyItems),
@ -20,15 +21,15 @@ public sealed class PlayerBag3FRLG(bool VC) : PlayerBag, IPlayerBag3
new(0x000, 30, 999, info, PCItems),
];
public PlayerBag3FRLG(SAV3FRLG sav) : this(sav.LargeBlock.Inventory, sav.SmallBlock.SecurityKey, sav.IsVirtualConsole) { }
public PlayerBag3FRLG(ReadOnlySpan<byte> data, uint security, bool vc) : this(vc)
public PlayerBag3FRLG(SAV3FRLG sav) : this(sav.Large[BaseOffset..], sav.SecurityKey) { }
public PlayerBag3FRLG(ReadOnlySpan<byte> data, uint security)
{
UpdateSecurityKey(security);
Pouches.LoadAll(data);
}
public override void CopyTo(SaveFile sav) => CopyTo((SAV3FRLG)sav);
public void CopyTo(SAV3FRLG sav) => CopyTo(sav.LargeBlock.Inventory);
public void CopyTo(SAV3FRLG sav) => CopyTo(sav.Large[BaseOffset..]);
public void CopyTo(Span<byte> data) => Pouches.SaveAll(data);
public override int GetMaxCount(InventoryType type, int itemIndex)

View File

@ -6,6 +6,8 @@ namespace PKHeX.Core;
public sealed class PlayerBag3RS : PlayerBag
{
private const int BaseOffset = 0x0498;
public override IReadOnlyList<InventoryPouch3> Pouches { get; } = GetPouches(ItemStorage3RS.Instance);
public override ItemStorage3RS Info => ItemStorage3RS.Instance;
@ -19,11 +21,11 @@ public sealed class PlayerBag3RS : PlayerBag
new(0x000, 50, 999, info, PCItems),
];
public PlayerBag3RS(SAV3RS sav) : this(sav.LargeBlock.Inventory) { }
public PlayerBag3RS(SAV3RS sav) : this(sav.Large[BaseOffset..]) { }
public PlayerBag3RS(ReadOnlySpan<byte> data) => Pouches.LoadAll(data);
public override void CopyTo(SaveFile sav) => CopyTo((SAV3RS)sav);
public void CopyTo(SAV3RS sav) => CopyTo(sav.LargeBlock.Inventory);
public void CopyTo(SAV3RS sav) => CopyTo(sav.Large[BaseOffset..]);
public void CopyTo(Span<byte> data) => Pouches.SaveAll(data);
public override int GetMaxCount(InventoryType type, int itemIndex)

View File

@ -28,7 +28,7 @@ public sealed class ItemStorage3Colo : IItemStorage
540, 541, 542, 546, 547,
];
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => !Unreleased.Contains((ushort)itemIndex);
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => true;
public ReadOnlySpan<ushort> GetItems(InventoryType type) => type switch
{

View File

@ -13,16 +13,17 @@ public sealed class ItemStorage3E : IItemStorage
public static ReadOnlySpan<ushort> Key =>
[
// R/S
259, 260, 261, 262, 263, 264, 265, 266, 268, 269, 270, 271, 272, 273, 274, 275, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288,
259, 260, 261, 262, 263, 264, 265, 266, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288,
// FR/LG
370, 371, 372,
349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374,
// E
375, 376,
];
private static readonly ushort[] PCItems = [.. General, .. Berry, .. Balls, .. Machine];
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => !Unreleased.Contains((ushort)itemIndex);
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => true;
public ReadOnlySpan<ushort> GetItems(InventoryType type) => type switch
{

View File

@ -13,12 +13,12 @@ public sealed class ItemStorage3FRLG : IItemStorage
public static ReadOnlySpan<ushort> Key =>
[
// R/S
260, 261, 262, 263, 264, 265,
259, 260, 261, 262, 263, 264, 265, 266, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288,
// FR/LG
349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374,
];
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => !Unreleased.Contains((ushort)itemIndex);
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => true;
public ReadOnlySpan<ushort> GetItems(InventoryType type) => type switch
{
@ -31,58 +31,3 @@ public sealed class ItemStorage3FRLG : IItemStorage
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
};
}
/// <summary>
/// Item storage for <see cref="GameVersion.FR"/> and <see cref="GameVersion.LG"/> on <see cref="GameConsole.NX"/>.
/// </summary>
public sealed class ItemStorage3FRLG_VC : IItemStorage // TODO VC RSE: delete me and any usages as RSE gives the remainder of items.
{
public static readonly ItemStorage3FRLG_VC Instance = new();
public ReadOnlySpan<ushort> GetItems(InventoryType type) => ItemStorage3FRLG.Instance.GetItems(type);
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => !IsUnreleasedHeld(itemIndex); // use VC unreleased list
public static bool IsUnreleasedHeld(int itemIndex) => Unreleased.Contains((ushort)itemIndex);
private static ReadOnlySpan<ushort> Unreleased =>
[
// Unobtainable
005, // Safari
// TODO RSE VC: Remove these
007, 012, // Dive Ball, Premier Ball (Unobtainable without trading from R/S/E)
039, 041, 042, 043, // Flutes (Yellow is obtainable via Coins)
// Unobtainable
044, // Berry Juice
// TODO RSE VC: Remove these
046, 047, // Shoal Salt, Shoal Shell
048, 049, 050, 051, // Shards
081, // Fluffy Tail
121, 122, 123, 124, 125, 126, 127, 128, 129, // Mail
168, // Liechi Berry (Mirage Island)
// Event Berries (Unobtainable)
169, // Ganlon Berry (Event)
170, // Salac Berry (Event)
171, // Petaya Berry (Event)
172, // Apicot Berry (Event)
173, // Lansat Berry (Event)
174, // Starf Berry (Event)
175, // Enigma Berry (Event)
// TODO RSE VC: Remove these
179, // BrightPowder
180, // White Herb
185, // Mental Herb
186, // Choice Band
191, // Soul Dew
192, // DeepSeaTooth
193, // DeepSeaScale
198, // Scope Lens
202, // Light Ball
219, // Shell Bell
254, 255, 256, 257, 258, 259, // Scarves
];
}

View File

@ -54,13 +54,13 @@ public sealed class ItemStorage3RS : IItemStorage
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
];
internal static ReadOnlySpan<ushort> Unreleased => [005, 044]; // Safari Ball, Berry Juice
internal static ReadOnlySpan<ushort> Unreleased => [005]; // Safari Ball
public static ushort[] GetAllHeld() => [..General, ..Balls, ..Berry, ..MachineOnlyTM];
private static readonly ushort[] PCItems = [..General, ..Key, .. Berry, ..Balls, ..Machine];
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => !Unreleased.Contains((ushort)itemIndex);
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => true;
public ReadOnlySpan<ushort> GetItems(InventoryType type) => type switch
{

View File

@ -59,7 +59,7 @@ public sealed class ItemStorage3XD : IItemStorage
590, 591, 592, 593,
];
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => !Unreleased.Contains((ushort)itemIndex);
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => true;
public ReadOnlySpan<ushort> GetItems(InventoryType type) => type switch
{

View File

@ -11,7 +11,7 @@ public sealed class ItemStorage4DP : ItemStorage4, IItemStorage
public static ushort[] GetAllHeld() => [..GeneralDP, ..Mail, ..Medicine, ..Berry, ..BallsDPPt, ..Battle, ..Machine[..^8]];
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => !Unreleased.Contains((ushort)itemIndex);
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => true;
public ReadOnlySpan<ushort> GetItems(InventoryType type) => type switch
{

View File

@ -27,7 +27,7 @@ public sealed class ItemStorage4HGSS : ItemStorage4, IItemStorage
492, 493, 494, 495, 496, 497, 498, 499, 500, // Apricorn Balls
];
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => !Unreleased.Contains((ushort)itemIndex);
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => true;
public ReadOnlySpan<ushort> GetItems(InventoryType type) => type switch
{

View File

@ -20,7 +20,7 @@ public sealed class ItemStorage4Pt : ItemStorage4, IItemStorage
public static ushort[] GetAllHeld() => [..GeneralPt, ..Mail, ..Medicine, ..Berry, ..BallsDPPt, ..Battle, ..Machine[..^8]];
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => !Unreleased.Contains((ushort)itemIndex);
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => true;
public ReadOnlySpan<ushort> GetItems(InventoryType type) => type switch
{

View File

@ -16,7 +16,7 @@ public sealed class ItemStorage5B2W2 : ItemStorage5, IItemStorage
616, 617, 621, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638,
];
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => !Unreleased.Contains((ushort)itemIndex);
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => true;
public ReadOnlySpan<ushort> GetItems(InventoryType type) => type switch
{

View File

@ -16,7 +16,7 @@ public sealed class ItemStorage5BW : ItemStorage5, IItemStorage
616, 617, 621, 623, 624, 625, 626,
];
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => !Unreleased.Contains((ushort)itemIndex);
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => true;
public ReadOnlySpan<ushort> GetItems(InventoryType type) => type switch
{

View File

@ -71,19 +71,24 @@ private static EncounterArea3[] GetRegular([ConstantExpected] string resource, [
new(147, 18, FR) { FixedBall = Ball.Poke, Location = 94 }, // Dratini
new(137, 26, FR) { FixedBall = Ball.Poke, Location = 94 }, // Porygon
new(386, 30, FR) { Location = 187, FatefulEncounter = true, Form = 1 }, // Deoxys @ Birth Island
new(386, 30, FR ) { Location = 187, FatefulEncounter = true, Form = 1 }, // Deoxys @ Birth Island
];
public static readonly EncounterStatic3[] StaticLG =
[
// Celadon City Game Corner
new(063, 09, FR) { FixedBall = Ball.Poke, Location = 94 }, // Abra
new(035, 08, FR) { FixedBall = Ball.Poke, Location = 94 }, // Clefairy
new(123, 25, FR) { FixedBall = Ball.Poke, Location = 94 }, // Scyther
new(147, 18, FR) { FixedBall = Ball.Poke, Location = 94 }, // Dratini
new(137, 26, FR) { FixedBall = Ball.Poke, Location = 94 }, // Porygon
new(063, 07, LG) { FixedBall = Ball.Poke, Location = 94 }, // Abra
new(035, 12, LG) { FixedBall = Ball.Poke, Location = 94 }, // Clefairy
new(127, 18, LG) { FixedBall = Ball.Poke, Location = 94 }, // Pinsir
new(147, 24, LG) { FixedBall = Ball.Poke, Location = 94 }, // Dratini
new(137, 18, LG) { FixedBall = Ball.Poke, Location = 94 }, // Porygon
new(386, 30, LG) { Location = 187, FatefulEncounter = true, Form = 2 }, // Deoxys @ Birth Island
new(386, 30, LG) { Location = 187, FatefulEncounter = true, Form = 2 }, // Deoxys @ Birth Island
];
private static ReadOnlySpan<byte> TradeContest_Cool => [ 30, 05, 05, 05, 05, 10 ];

View File

@ -112,7 +112,7 @@ public EncounterCriteria()
/// Determines whether a specific Nature is specified in the criteria or if complex nature mutations are allowed.
/// </summary>
/// <returns>><see langword="true"/> if a Nature is specified or complex nature mutations are allowed; otherwise, <see langword="false"/>.</returns>
public bool IsSpecifiedNature() => Nature.IsFixed || Mutations.IsComplexNature();
public bool IsSpecifiedNature() => Nature != Nature.Random || Mutations.IsComplexNature();
/// <summary>
/// Determines whether a level range is specified in the criteria.
@ -126,12 +126,6 @@ public EncounterCriteria()
/// <returns>><see langword="true"/> if an Ability is specified; otherwise, <see langword="false"/>.</returns>
public bool IsSpecifiedAbility() => Ability != Any12H;
/// <summary>
/// Determines whether the shiny value is explicitly specified rather than set to random.
/// </summary>
/// <returns>><see langword="true"/> if a Shiny is specified; otherwise, <see langword="false"/>.</returns>
public bool IsSpecifiedShiny() => Shiny != Shiny.Random;
/// <summary>
/// Determines whether all IVs are specified in the criteria.
/// </summary>
@ -189,20 +183,6 @@ public int GetCountSpecifiedIVs() => Convert.ToInt32(IV_HP != RandomIV)
_ => throw new ArgumentOutOfRangeException(nameof(ability), ability, null),
};
/// <summary>
/// Determines whether the specified shiny properties satisfy the shiny criteria based on the current <see cref="Shiny"/> setting.
/// </summary>
/// <returns>><see langword="true"/> if the index satisfies the shiny criteria; otherwise, <see langword="false"/>.</returns>
public bool IsSatisfiedShiny(uint xor, uint cmp) => Shiny switch
{
Shiny.Random => true,
Shiny.Never => xor > cmp, // not shiny
Shiny.AlwaysSquare => xor == 0, // square shiny
Shiny.AlwaysStar => xor < cmp && xor != 0, // star shiny
Shiny.Always => xor < cmp, // shiny
_ => false, // shouldn't be set
};
/// <summary>
/// Determines whether the specified Nature satisfies the criteria.
/// </summary>
@ -211,7 +191,7 @@ public int GetCountSpecifiedIVs() => Convert.ToInt32(IV_HP != RandomIV)
public bool IsSatisfiedNature(Nature nature)
{
if (Mutations.HasFlag(AllowOnlyNeutralNature))
return nature.IsNeutral;
return nature.IsNeutral();
if (Nature == Nature.Random)
return true;
return nature == Nature || Mutations.HasFlag(CanMintNature);
@ -320,7 +300,7 @@ public Nature GetNature(Nature encValue)
/// </summary>
public Nature GetNature()
{
if (Nature.IsFixed)
if (Nature != Nature.Random)
return Nature;
var result = (Nature)Util.Rand.Next(25);
if (Mutations.HasFlag(AllowOnlyNeutralNature))

View File

@ -12,8 +12,6 @@ public enum PogoType : byte
// Pokémon captured in the wild.
Wild,
WildLevel20,
WildLevel25,
// Pokémon hatched from Eggs.
Egg,
@ -139,8 +137,6 @@ public static class PogoTypeExtensions
public byte LevelMin => encounterType switch
{
Wild => 1,
WildLevel20 => 20,
WildLevel25 => 25,
Egg => 1,
Egg12km => 8,
Raid => 20,
@ -208,8 +204,6 @@ public static class PogoTypeExtensions
public int MinimumIV => encounterType switch
{
Wild => 0,
WildLevel20 => 0,
WildLevel25 => 0,
RaidMythical => 10,
RaidShadowMythical => 8,
RaidShadowMythicalGOWA => 8,
@ -330,6 +324,6 @@ public bool IsBallValid(Ball ball)
_ => Ball.None, // Poké, Great, Ultra
};
public bool IsSpecialResearch => encounterType is SpecialResearch or >= SpecialMythical and < TimedMythical;
public bool IsSpecialResearch => encounterType is >= SpecialMythical and < TimedMythical;
}
}

View File

@ -58,16 +58,12 @@ public PK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
// Force Hatch
Language = language,
OriginalTrainerName = tr.OT,
Nickname = SpeciesName.GetSpeciesNameGeneration(Species, language, Generation),
OriginalTrainerFriendship = 120,
MetLevel = 0,
MetLocation = Location,
};
// Copy from SaveFile's OT name. Trash bytes here should be pure, but our OT name might not always source from a PK3/SAV3.
// Condition the buffer as if it came from a correct SAV3 named after the OT.
var ot = pk.OriginalTrainerTrash;
ot[..(language == 1 ? 6 : 7)].Fill(0xFF);
pk.OriginalTrainerName = EncounterUtil.GetTrainerName(tr, language);
SetEncounterMoves(pk);

View File

@ -64,15 +64,11 @@ public PK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
Ball = (byte)GetRequiredBall(Ball.Poke),
Language = language,
OriginalTrainerName = tr.OT,
OriginalTrainerGender = tr.Gender,
ID32 = tr.ID32,
Nickname = SpeciesName.GetSpeciesNameGeneration(Species, language, Generation),
};
// Copy from SaveFile's OT name. Trash bytes here should be pure, but our OT name might not always source from a PK3/SAV3.
// Condition the buffer as if it came from a correct SAV3 named after the OT.
var ot = pk.OriginalTrainerTrash;
ot[..(language == 1 ? 6 : 7)].Fill(0xFF);
pk.OriginalTrainerName = EncounterUtil.GetTrainerName(tr, language);
SetPINGA(pk, criteria, pi);
SetEncounterMoves(pk);

View File

@ -41,7 +41,7 @@ public sealed record EncounterStatic3(ushort Species, byte Level, GameVersion Ve
public PK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = GetTemplateLanguage(tr);
int lang = GetTemplateLanguage(tr);
var version = this.GetCompatibleVersion(tr.Version);
var pi = PersonalTable.E[Species];
var pk = new PK3
@ -56,16 +56,12 @@ public PK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
Ball = (byte)(FixedBall != Ball.None ? FixedBall : Ball.Poke),
FatefulEncounter = FatefulEncounter,
Language = language,
Language = lang,
OriginalTrainerName = EncounterUtil.GetTrainerName(tr, lang),
OriginalTrainerGender = tr.Gender,
ID32 = tr.ID32,
Nickname = SpeciesName.GetSpeciesNameGeneration(Species, language, Generation),
Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation),
};
// Copy from SaveFile's OT name. Trash bytes here should be pure, but our OT name might not always source from a PK3/SAV3.
// Condition the buffer as if it came from a correct SAV3 named after the OT.
var ot = pk.OriginalTrainerTrash;
ot[..(language == 1 ? 6 : 7)].Fill(0xFF);
pk.OriginalTrainerName = EncounterUtil.GetTrainerName(tr, language);
if (IsEgg)
{

View File

@ -155,7 +155,7 @@ private bool IsMatchNatureGenderShiny(PKM pk)
return false;
if (Gender != FixedGenderUtil.GenderRandom && Gender != pk.Gender)
return false;
if (Nature.IsFixed && pk.Nature != Nature)
if (Nature != Nature.Random && pk.Nature != Nature)
return false;
return true;
}

View File

@ -153,7 +153,7 @@ public bool IsMatchExact(PKM pk, EvoCriteria evo)
return false;
if (IVs.IsSpecified && !Legal.GetIsFixedIVSequenceValidSkipRand(IVs, pk))
return false;
if (Nature.IsFixed && pk.Nature != Nature)
if (Nature != Nature.Random && pk.Nature != Nature)
return false;
if (FlawlessIVCount != 0 && pk.FlawlessIVCount < FlawlessIVCount)
return false;

View File

@ -167,7 +167,7 @@ private bool IsMatchNatureGenderShiny(PKM pk)
return false;
if (Gender != pk.Gender)
return false;
if (Nature.IsFixed && pk.Nature != Nature)
if (Nature != Nature.Random && pk.Nature != Nature)
return false;
return true;
}

View File

@ -145,7 +145,7 @@ public bool IsMatchExact(PKM pk, EvoCriteria evo)
return false;
if (IVs.IsSpecified && !Legal.GetIsFixedIVSequenceValidSkipRand(IVs, pk))
return false;
if (Nature.IsFixed && pk.Nature != Nature)
if (Nature != Nature.Random && pk.Nature != Nature)
return false;
if (FlawlessIVCount != 0 && pk.FlawlessIVCount < FlawlessIVCount)
return false;

View File

@ -166,7 +166,7 @@ private bool IsMatchNatureGenderShiny(PKM pk)
return false;
if (Gender != pk.Gender)
return false;
if (Nature.IsFixed && pk.Nature != Nature)
if (Nature != Nature.Random && pk.Nature != Nature)
return false;
return true;
}

View File

@ -122,6 +122,8 @@ protected virtual void SetPINGA(PK8 pk, in EncounterCriteria criteria, PersonalI
}
FinishCorrelation(pk, seed);
if (criteria.IsSpecifiedNature() && criteria.Nature != pk.Nature && criteria.Nature.IsMint())
pk.StatNature = criteria.Nature;
}
protected GenerateParam8 GetParam() => GetParam(Info);

View File

@ -57,10 +57,6 @@ protected override void SetPINGA(PK8 pk, in EncounterCriteria criteria, Personal
{
Span<int> iv = stackalloc int[6];
// Honor a shiny request only at the end; generate as never-shiny to avoid shiny PID rejection in main RNG method.
var isShinyRequested = criteria.Shiny.IsShiny();
var iterCriteria = criteria with { Shiny = ShinyMethod };
int ctr = 0;
var rand = new Xoroshiro128Plus(Util.Rand.Rand64());
var param = GetParam(pi);
@ -68,22 +64,23 @@ protected override void SetPINGA(PK8 pk, in EncounterCriteria criteria, Personal
const int max = 100_000;
do
{
if (TryApply(pk, seed = rand.Next(), iv, param, iterCriteria))
if (TryApply(pk, seed = rand.Next(), iv, param, criteria))
break;
} while (++ctr < max);
if (ctr == max) // fail
{
iterCriteria = iterCriteria.WithoutIVs();
if (!TryApply(pk, seed = rand.Next(), iv, param, iterCriteria))
if (!TryApply(pk, seed = rand.Next(), iv, param, criteria.WithoutIVs()))
{
iterCriteria = EncounterCriteria.Unrestricted with { Shiny = ShinyMethod };
while (!TryApply(pk, seed = rand.Next(), iv, param, iterCriteria)) { }
var tmp = EncounterCriteria.Unrestricted with { Shiny = ShinyMethod };
while (!TryApply(pk, seed = rand.Next(), iv, param, tmp)) { }
}
}
FinishCorrelation(pk, seed);
if (isShinyRequested)
if (criteria.IsSpecifiedNature() && criteria.Nature != pk.Nature && criteria.Nature.IsMint())
pk.StatNature = criteria.Nature;
if (criteria.Shiny.IsShiny())
pk.PID = ShinyUtil.GetShinyPID(pk.TID16, pk.SID16, pk.PID, ShinyXor);
}

View File

@ -209,7 +209,7 @@ private bool IsMatchNatureGenderShiny(PKM pk)
{
if (!Shiny.IsValid(pk))
return false;
if (Nature.IsFixed && pk.Nature != Nature)
if (Nature != Nature.Random && pk.Nature != Nature)
return false;
if (Gender != FixedGenderUtil.GenderRandom && pk.Gender != Gender)
return false;

View File

@ -154,7 +154,7 @@ private void SetPINGA(PK9 pk, in EncounterCriteria criteria, PersonalInfo9SV pi)
if (Gender != FixedGenderUtil.GenderRandom)
pk.Gender = Gender;
if (Nature.IsFixed)
if (Nature != Nature.Random)
pk.Nature = pk.StatNature = Nature;
}
#endregion
@ -178,7 +178,7 @@ public bool IsMatchExact(PKM pk, EvoCriteria evo)
return false;
if (TeraType != GemType.Random && pk is ITeraType t && !Tera9RNG.IsMatchTeraType(TeraType, Species, Form, (byte)t.TeraTypeOriginal))
return false;
if (Nature.IsFixed && pk.Nature != Nature)
if (Nature != Nature.Random && pk.Nature != Nature)
return false;
return true;

View File

@ -158,7 +158,7 @@ public bool IsMatchExact(PKM pk, EvoCriteria evo)
return false;
if (FlawlessIVCount != 0 && pk.FlawlessIVCount < FlawlessIVCount)
return false;
if (Nature.IsFixed && pk.Nature != Nature)
if (Nature != Nature.Random && pk.Nature != Nature)
return false;
if (pk is IAlphaReadOnly a && a.IsAlpha != IsAlpha)
return false;

View File

@ -135,7 +135,7 @@ public bool IsMatchExact(PKM pk, EvoCriteria evo)
return false;
if (FlawlessIVCount != 0 && pk.FlawlessIVCount < FlawlessIVCount)
return false;
if (Nature.IsFixed && pk.Nature != Nature)
if (Nature != Nature.Random && pk.Nature != Nature)
return false;
if (pk is IAlphaReadOnly a && a.IsAlpha != IsAlpha)
return false;

View File

@ -155,7 +155,7 @@ private bool IsMatchNatureGenderShiny(PKM pk)
return false;
if (pk.Gender != Gender)
return false;
if (Nature.IsFixed && pk.Nature != Nature)
if (Nature != Nature.Random && pk.Nature != Nature)
return false;
return true;
}
@ -192,7 +192,7 @@ public bool IsMatchExact(PKM pk, EvoCriteria evo)
return false;
if (FlawlessIVCount != 0 && pk.FlawlessIVCount < FlawlessIVCount)
return false;
if (Nature.IsFixed && pk.Nature != Nature)
if (Nature != Nature.Random && pk.Nature != Nature)
return false;
if (pk is IAlphaReadOnly a && a.IsAlpha != IsAlpha)
return false;

View File

@ -36,10 +36,10 @@ public static bool GenerateData(PA9 pk, in GenerateParam9a enc, in EncounterCrit
{
var rand = new Xoroshiro128Plus(seed);
pk.EncryptionConstant = (uint)rand.NextInt(uint.MaxValue);
var pid = GetAdaptedPID(ref rand, pk, enc);
if (enc.Shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(ShinyUtil.GetShinyXor(pid, pk.ID32), 16))
pk.PID = GetAdaptedPID(ref rand, pk, enc);
if (enc.Shiny is Shiny.Random && criteria.Shiny.IsShiny() != pk.IsShiny)
return false;
pk.PID = pid;
Span<int> ivs = [UNSET, UNSET, UNSET, UNSET, UNSET, UNSET];
if (enc.IVs.IsSpecified)
@ -104,7 +104,7 @@ public static bool GenerateData(PA9 pk, in GenerateParam9a enc, in EncounterCrit
return false;
pk.Gender = gender;
var nature = enc.Nature.IsFixed ? enc.Nature
var nature = enc.Nature != Nature.Random ? enc.Nature
: (Nature)rand.NextInt(25);
// Compromise on Nature -- some are fixed, some are random. If the request wants a specific nature, just mint it.
@ -264,7 +264,7 @@ private static bool IsMatchIVsAndFollowing(PKM pk, in GenerateParam9a enc, Xoros
if (pk.Gender != gender)
return false;
var nature = enc.Nature.IsFixed ? enc.Nature
var nature = enc.Nature != Nature.Random ? enc.Nature
: (Nature)rand.NextInt(25);
if (pk.Nature != nature)
return false;

View File

@ -13,5 +13,5 @@ public interface IFixedNature
/// <summary>
/// Indicates if the nature is a single value (not random).
/// </summary>
bool IsFixedNature => Nature.IsFixed;
bool IsFixedNature => Nature != Nature.Random;
}

View File

@ -22,14 +22,8 @@ public static class EncounterVerifier
private static CheckResult VerifyEncounter(PKM pk, IEncounterTemplate enc) => enc switch
{
EncounterShadow3Colo { IsEReader: true } when pk.Language != (int)LanguageID.Japanese => GetInvalid(G3EReader),
// Mew @ Faraway Island (Emerald)
EncounterStatic3 { Species: (int)Species.Mew } when pk.Language != (int)LanguageID.Japanese
=> GetInvalid(EncUnreleasedEMewJP),
// Deoxys @ Birth Island (FireRed/LeafGreen) - Never distributed in Japan during GBA Cart era. NX virtual console added for all.
EncounterStatic3 { Species: (int)Species.Deoxys, Location: 200 } when pk.Language == (int)LanguageID.Japanese && !ParseSettings.AllowGen3EventTicketsAll(pk)
=> GetInvalid(EncUnreleased),
EncounterStatic3 { Species: (int)Species.Mew } when pk.Language != (int)LanguageID.Japanese => GetInvalid(EncUnreleasedEMewJP),
EncounterStatic3 { Species: (int)Species.Deoxys, Location: 200 } when pk.Language == (int)LanguageID.Japanese => GetInvalid(EncUnreleased),
EncounterStatic4 { Species: (int)Species.Shaymin } when pk.Language == (int)LanguageID.Korean => GetInvalid(EncUnreleased),
EncounterStatic4 { IsRoaming: true } when pk is G4PKM { MetLocation: 193, GroundTile: GroundTileType.Water } => GetInvalid(G4InvalidTileR45Surf),
MysteryGift g => VerifyEncounterEvent(pk, g),
@ -152,8 +146,6 @@ private static CheckResult VerifyEncounterEgg3(PKM pk)
return GetValid(EggLocation);
// Version isn't updated when hatching on a different game. Check any game.
if (!ParseSettings.AllowGBACrossTransferRSE(pk)) // Must match the origin game (Nintendo Switch VC)
return GetInvalid(EggLocationInvalid);
if (EggHatchLocation3.IsValidMet3Any(met))
return GetValid(EggLocationTrade);
return GetInvalid(EggLocationInvalid);

View File

@ -10,7 +10,7 @@ public sealed class EvolutionGroup3 : IEvolutionGroup
private static PersonalTable3 Personal => PersonalTable.E;
private static EvolutionRuleTweak Tweak => EvolutionRuleTweak.Default;
public IEvolutionGroup? GetNext(PKM pk, EvolutionOrigin enc) => pk.Format > Generation ? EvolutionGroup4.Instance : null; // TODO HOME FR/LG
public IEvolutionGroup? GetNext(PKM pk, EvolutionOrigin enc) => pk.Format > Generation ? EvolutionGroup4.Instance : null;
public IEvolutionGroup? GetPrevious(PKM pk, EvolutionOrigin enc) => null;
public void DiscardForOrigin(Span<EvoCriteria> result, PKM pk, EvolutionOrigin enc) => EvolutionUtil.Discard(result, Personal);

View File

@ -94,12 +94,6 @@ private static void CheckEncounterMoves(Span<MoveResult> result, ReadOnlySpan<us
private static void Check(Span<MoveResult> result, ReadOnlySpan<ushort> current, PKM pk, EvoCriteria evo, int stage, MoveSourceType types)
{
if (!ParseSettings.AllowGBACrossTransferRSE(pk))
{
CheckNX(result, current, pk, evo, stage, types);
return;
}
var rs = LearnSource3RS.Instance;
var species = evo.Species;
if (!rs.TryGetPersonal(species, evo.Form, out var rp))
@ -143,48 +137,6 @@ private static void Check(Span<MoveResult> result, ReadOnlySpan<ushort> current,
}
}
private static void CheckNX(Span<MoveResult> result, ReadOnlySpan<ushort> current, PKM pk, EvoCriteria evo, int stage, MoveSourceType types)
{
var species = evo.Species;
var fr = LearnSource3FR.Instance;
if (!fr.TryGetPersonal(species, evo.Form, out var fp))
return; // should never happen.
var lg = LearnSource3LG.Instance;
var lp = lg[species];
var isFireRed = pk.Version == GameVersion.FR;
ILearnSource<PersonalInfo3> primaryLS = isFireRed ? fr : lg;
ILearnSource<PersonalInfo3> secondaryLS = !isFireRed ? fr : lg;
var primaryPI = isFireRed ? fp : lp;
var secondaryPI = !isFireRed ? fp : lp;
var primaryEnv = isFireRed ? LearnEnvironment.FR : LearnEnvironment.LG;
for (int i = result.Length - 1; i >= 0; i--)
{
if (result[i].Valid)
continue;
// Level Up moves are different for each game, but TM/HM is shared.
var move = current[i];
var chk = primaryLS.GetCanLearn(pk, primaryPI, evo, move, types);
if (chk != default)
{
result[i] = new(chk, (byte)stage, Context);
continue;
}
chk = secondaryLS.GetCanLearn(pk, secondaryPI, evo, move, types & MoveSourceType.LevelUp); // Tutors same as FR
if (chk != default)
{
result[i] = new(chk, (byte)stage, Context);
continue;
}
// Tutors are indexes 0-14, same as Emerald.
if (LearnSource3E.GetIsTutorFRLG(evo.Species, move))
result[i] = new(new(LearnMethod.Tutor, primaryEnv));
}
}
public void GetAllMoves(Span<bool> result, PKM pk, EvolutionHistory history, IEncounterTemplate enc, MoveSourceType types = MoveSourceType.All, LearnOption option = LearnOption.Current)
{
if (types.HasFlag(MoveSourceType.Encounter) && enc.Context == Context)
@ -206,16 +158,6 @@ public void GetAllMoves(Span<bool> result, PKM pk, EvolutionHistory history, IEn
private static void GetAllMoves(Span<bool> result, PKM pk, EvoCriteria evo, MoveSourceType types)
{
if (!ParseSettings.AllowGBACrossTransferRSE(pk)) // NX
{
LearnSource3FR.Instance.GetAllMoves(result, pk, evo, types);
LearnSource3LG.Instance.GetAllMoves(result, pk, evo, types & (MoveSourceType.LevelUp));
// Tutors are indexes 0-14, same as Emerald.
if (types.HasFlag(MoveSourceType.EnhancedTutor))
LearnSource3E.GetAllTutorMovesFRLG(result, evo.Species);
return;
}
LearnSource3E.Instance.GetAllMoves(result, pk, evo, types);
LearnSource3RS.Instance.GetAllMoves(result, pk, evo, types & (MoveSourceType.LevelUp | MoveSourceType.AllTutors));
LearnSource3FR.Instance.GetAllMoves(result, pk, evo, types & (MoveSourceType.LevelUp | MoveSourceType.AllTutors));

View File

@ -144,33 +144,10 @@ public void GetAllMoves(Span<bool> result, PKM pk, EvoCriteria evo, MoveSourceTy
}
}
// TODO RSE VC: Remove these.
internal static bool GetIsTutorFRLG(ushort species, ushort move)
{
var info = Personal[species];
var index = Tutor_E.IndexOf(move);
if ((uint)index >= 15)
return false;
return info.TypeTutors[index];
}
internal static void GetAllTutorMovesFRLG(Span<bool> result, ushort species)
{
var pi = Personal[species];
var flags = pi.TypeTutors;
var moves = Tutor_E;
for (int i = 0; i < 15; i++)
{
if (flags[i])
result[moves[i]] = true;
}
}
private static ReadOnlySpan<ushort> Tutor_E =>
[
// Introduced in FR/LG
005, 014, 025, 034, 038, 068, 069, 102, 118, 135, 138, 086, 153, 157, 164,
// Introduced in Emerald
223, 205, 244, 173, 196, 203, 189, 008, 207, 214, 129, 111, 009, 007, 210,
005, 014, 025, 034, 038, 068, 069, 102, 118, 135,
138, 086, 153, 157, 164, 223, 205, 244, 173, 196,
203, 189, 008, 207, 214, 129, 111, 009, 007, 210,
];
}

View File

@ -1,5 +1,4 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace PKHeX.Core;
@ -20,8 +19,7 @@ public static class Legal
internal const int MaxSpeciesID_3 = 386;
internal const int MaxMoveID_3 = 354;
internal const int MaxItemID_3_RS = 346;
internal const int MaxItemID_3_FRLG = 374;
internal const int MaxItemID_3 = 374;
internal const int MaxItemID_3_E = 376;
internal const int MaxItemID_3_COLO = 547;
internal const int MaxItemID_3_XD = 593;
@ -304,40 +302,4 @@ public static bool GetIsFixedIVSequenceValidNoRand(in IndividualValueSet IVs, PK
if (IVs.SPD != pk.IV_SPD) return false;
return true;
}
/// <summary>
/// For a species with a potentially valid FR/LG origin encounter, flag if not permitted.
/// </summary>
public static bool IsForeignFRLG(ushort species) => IsForeign(ForeignFRLG, species, ShiftFRLG);
private static bool IsForeign(ReadOnlySpan<byte> bitSet, int species, [ConstantExpected] int shift)
{
species -= shift;
var offset = species >> 3;
if ((uint)offset >= bitSet.Length)
return false;
var bit = species & 7;
if ((bitSet[offset] & (1 << bit)) != 0)
return true;
return false;
}
private const ushort ShiftFRLG = 151; // First unavailable Species (Mew)
/// <summary>
/// Bitset representing species that are considered unobtainable in FR/LG.
/// Includes foreign transfers and time-of-day evolutions.
/// First species is Mew (151), last is Deoxys (386).
/// </summary>
/// <remarks>
/// Source: https://www.serebii.net/fireredleafgreen/unobtainable.shtml
/// </remarks>
private static ReadOnlySpan<byte> ForeignFRLG =>
[
0xFF, 0x33, 0x18, 0x60, 0x04, 0x63, 0x50, 0x0D,
0x84, 0x40, 0x00, 0x04, 0xF0, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFD, 0xFF, 0xFF, 0x07,
];
}

View File

@ -421,9 +421,8 @@ public sealed class LegalityCheckLocalization
public string TrashBytesExpected { get; set; } = "Expected Trash Bytes.";
public string TrashBytesMismatchInitial { get; set; } = "Expected initial trash bytes to match the encounter.";
public string TrashBytesMissingTerminatorFinal { get; set; } = "Final terminator missing.";
public string TrashBytesMissingTerminator { get; set; } = "Final terminator missing.";
public string TrashBytesShouldBeEmpty { get; set; } = "Trash Bytes should be cleared.";
public string TrashBytesResetViaTransfer { get; set; } = "Trash Bytes were reset via transfer.";
#endregion

View File

@ -410,9 +410,8 @@ public static class LegalityCheckResultCodeExtensions
TransferTrackerShouldBeZero => localization.TransferTrackerShouldBeZero,
TrashBytesExpected => localization.TrashBytesExpected,
TrashBytesMismatchInitial => localization.TrashBytesMismatchInitial,
TrashBytesMissingTerminatorFinal => localization.TrashBytesMissingTerminatorFinal,
TrashBytesMissingTerminator => localization.TrashBytesMissingTerminator,
TrashBytesShouldBeEmpty => localization.TrashBytesShouldBeEmpty,
TrashBytesResetViaTransfer => localization.TrashBytesResetViaTransfer,
WordFilterInvalidCharacter_0 => localization.WordFilterInvalidCharacter_0,
WordFilterFlaggedPattern_01 => localization.WordFilterFlaggedPattern_01,
WordFilterTooManyNumbers_0 => localization.WordFilterTooManyNumbers_0,

View File

@ -55,8 +55,7 @@ private static bool TryApplyFromSeed(PK8 pk, in EncounterCriteria criteria, Shin
if (shiny == Shiny.AlwaysStar && type != Shiny.AlwaysStar)
return false;
}
if (shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(GetShinyXor(pid, pk.ID32), 16))
return false;
pk.PID = pid;
// IVs

View File

@ -114,7 +114,7 @@ public static bool Verify(PKM pk, ulong seed, Span<int> ivs, in GenerateParam8 p
break;
}
var nature = param.Nature.IsFixed ? param.Nature
var nature = param.Nature != Nature.Random ? param.Nature
: param.Species == (int)Species.Toxtricity
? ToxtricityUtil.GetRandomNature(ref rng, pk.Form)
: (Nature)rng.NextInt(25);
@ -160,8 +160,6 @@ public static bool TryApply(PK8 pk, ulong seed, Span<int> ivs, in GenerateParam8
var trID = (uint)rng.NextInt();
var pid = (uint)rng.NextInt();
// Battle
var xor = GetShinyXor(pid, trID);
bool isShiny = xor < 16;
if (isShiny && param.Shiny == Shiny.Never)
@ -169,8 +167,9 @@ public static bool TryApply(PK8 pk, ulong seed, Span<int> ivs, in GenerateParam8
ForceShinyState(false, ref pid, trID, 0);
isShiny = false;
}
if (param.Shiny is Shiny.Random && isShiny != criteria.Shiny.IsShiny())
return false;
// Captured
if (isShiny)
{
if (!GetIsShiny6(pk.ID32, pid))
@ -182,8 +181,6 @@ public static bool TryApply(PK8 pk, ulong seed, Span<int> ivs, in GenerateParam8
pid ^= 0x1000_0000;
}
if (param.Shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(GetShinyXor(pid, pk.ID32), 16))
return false;
pk.PID = pid;
const int UNSET = -1;
@ -239,7 +236,7 @@ public static bool TryApply(PK8 pk, ulong seed, Span<int> ivs, in GenerateParam8
return false;
pk.Gender = gender;
var nature = param.Nature.IsFixed ? param.Nature
var nature = param.Nature != Nature.Random ? param.Nature
: param.Species == (int)Species.Toxtricity
? ToxtricityUtil.GetRandomNature(ref rng, pk.Form)
: (Nature)rng.NextInt(25);

View File

@ -129,8 +129,6 @@ public static bool TryApplyFromSeed(PA8 pk, in EncounterCriteria criteria, in Ov
if (para.Shiny == Shiny.AlwaysStar && type != Shiny.AlwaysStar)
return false;
}
if (para.Shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(GetShinyXor(pid, pk.ID32), 16))
return false;
pk.PID = pid;
Span<int> ivs = [UNSET, UNSET, UNSET, UNSET, UNSET, UNSET];
@ -174,8 +172,6 @@ public static bool TryApplyFromSeed(PA8 pk, in EncounterCriteria criteria, in Ov
pk.Gender = gender;
var nature = (Nature)rand.NextInt(25);
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(nature))
return false;
pk.Nature = pk.StatNature = nature;
var (height, weight) = para.IsAlpha
@ -183,10 +179,16 @@ public static bool TryApplyFromSeed(PA8 pk, in EncounterCriteria criteria, in Ov
: ((byte)(rand.NextInt(0x81) + rand.NextInt(0x80)),
(byte)(rand.NextInt(0x81) + rand.NextInt(0x80)));
pk.HeightScalar = height;
pk.WeightScalar = weight;
pk.ResetHeight();
pk.ResetWeight();
if (pk is IScaledSize s)
{
s.HeightScalar = height;
s.WeightScalar = weight;
if (pk is IScaledSizeValue a)
{
a.ResetHeight();
a.ResetWeight();
}
}
return true;
}

View File

@ -65,8 +65,6 @@ public static bool TryApplyFromSeed(PB8 pk, in EncounterCriteria criteria, Shiny
if (shiny == Shiny.AlwaysStar && type != Shiny.AlwaysStar)
return false;
}
if (shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(GetShinyXor(pid, pk.ID32), 16))
return false;
pk.PID = pid;
// Check IVs: Create flawless IVs at random indexes, then the random IVs for not flawless.

View File

@ -65,8 +65,6 @@ public static bool TryApplyFromSeed(PB8 pk, in EncounterCriteria criteria, Shiny
if (shiny == Shiny.AlwaysStar && type != Shiny.AlwaysStar)
return false;
}
if (shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(GetShinyXor(pid, pk.ID32), 16))
return false;
pk.PID = pid;
// Check IVs: Create flawless IVs at random indexes, then the random IVs for not flawless.

View File

@ -62,10 +62,10 @@ public static bool GenerateData(PK9 pk, in GenerateParam9 enc, in EncounterCrite
{
var rand = new Xoroshiro128Plus(seed);
pk.EncryptionConstant = (uint)rand.NextInt(uint.MaxValue);
var pid = GetAdaptedPID(ref rand, pk, enc);
if (enc.Shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(GetShinyXor(pid, pk.ID32), 16))
pk.PID = GetAdaptedPID(ref rand, pk, enc);
if (enc.Shiny is Shiny.Random && criteria.Shiny.IsShiny() != pk.IsShiny)
return false;
pk.PID = pid;
const int UNSET = -1;
const int MAX = 31;
@ -121,7 +121,7 @@ public static bool GenerateData(PK9 pk, in GenerateParam9 enc, in EncounterCrite
return false;
pk.Gender = gender;
var nature = enc.Nature.IsFixed ? enc.Nature : enc.Species == (int)Species.Toxtricity
var nature = enc.Nature != Nature.Random ? enc.Nature : enc.Species == (int)Species.Toxtricity
? ToxtricityUtil.GetRandomNature(ref rand, pk.Form)
: (Nature)rand.NextInt(25);
@ -205,7 +205,7 @@ public static bool IsMatch(PKM pk, in GenerateParam9 enc, in ulong seed)
if (pk.Gender != gender)
return false;
var nature = enc.Nature.IsFixed ? enc.Nature : enc.Species == (int)Species.Toxtricity
var nature = enc.Nature != Nature.Random ? enc.Nature : enc.Species == (int)Species.Toxtricity
? ToxtricityUtil.GetRandomNature(ref rand, pk.Form)
: (Nature)rand.NextInt(25);
if (pk.Nature != nature)

View File

@ -4,7 +4,7 @@ namespace PKHeX.Core;
/// Parameters used to generate data for an encounter.
/// </summary>
/// <param name="Species">Species to generate.</param>
/// <param name="GenderRatio">Gender ratio byte from Personal Info.</param>
/// <param name="GenderRatio">Gender ratio byte.</param>
/// <param name="FlawlessIVs">Count of IVs that are perfect.</param>
/// <param name="RollCount">Count of shiny rolls allowed for the PID calculation.</param>
/// <param name="Height">Height value to generate. If zero, full random.</param>

View File

@ -51,7 +51,7 @@ public static bool IsHeldItemAllowed(int item, EntityContext context)
// Combined bitflags for released held items across generations.
private static readonly bool[] ReleasedHeldItems_2 = GetPermitList(MaxItemID_2, HeldItems_GSC);
private static readonly bool[] ReleasedHeldItems_3 = GetPermitList(MaxItemID_3_RS, HeldItems_RS, ItemStorage3RS.Unreleased); // Safari Ball
private static readonly bool[] ReleasedHeldItems_3 = GetPermitList(MaxItemID_3, HeldItems_RS, ItemStorage3RS.Unreleased); // Safari Ball
private static readonly bool[] ReleasedHeldItems_4 = GetPermitList(MaxItemID_4_HGSS, HeldItems_HGSS, ItemStorage4.Unreleased);
private static readonly bool[] ReleasedHeldItems_5 = GetPermitList(MaxItemID_5_B2W2, HeldItems_BW, ItemStorage5.Unreleased);
private static readonly bool[] ReleasedHeldItems_6 = GetPermitList(MaxItemID_6_AO, HeldItems_AO, ItemStorage6XY.Unreleased);

View File

@ -31,17 +31,7 @@ public static class ParseSettings
/// Setting to specify if an analysis should permit data sourced from the physical cartridge era of Game Boy games.
/// </summary>
/// <remarks>If false, indicates to use Virtual Console rules (which are transferable to Gen7+)</remarks>
public static bool AllowEraCartGB { private get; set; }
/// <summary>
/// Setting to specify if an analysis should permit data sourced from the physical cartridge era of Game Boy Advance games.
/// </summary>
public static bool AllowEraCartGBA { private get; set; } = true;
/// <summary>
/// Setting to specify if an analysis should permit data sourced from the Nintendo Switch Virtual Console era of Game Boy Advance games.
/// </summary>
public static bool AllowEraSwitchGBA { private get; set; }
public static bool AllowGBCartEra { private get; set; }
/// <summary>
/// Setting to specify if an analysis should permit trading a Generation 1 origin file to Generation 2, then back. Useful for checking RBY Metagame rules.
@ -65,35 +55,27 @@ public static class ParseSettings
/// <returns>True if Crystal data is allowed</returns>
public static bool AllowGen2MoveReminder(PKM pk) => !pk.Korean && AllowGBStadium2;
public static bool AllowGen2OddEgg(PKM pk) => !pk.Japanese || AllowEraCartGB;
public static bool AllowGen2OddEgg(PKM pk) => !pk.Japanese || AllowGBCartEra;
public static bool AllowGBVirtualConsole3DS => !AllowEraCartGB;
public static bool AllowGBEraEvents => AllowEraCartGB;
public static bool AllowGBStadium2 => AllowEraCartGB;
// This logic will likely need to change (format check): TODO HOME FR/LG
public static bool AllowGBACrossTransferXD(PKM pk) => AllowEraCartGBA;
public static bool AllowGBACrossTransferRSE(PKM pk) => AllowEraCartGBA;
public static bool AllowGen3EventTicketsAll(PKM pk) => AllowEraSwitchGBA;
public static bool AllowGBVirtualConsole3DS => !AllowGBCartEra;
public static bool AllowGBEraEvents => AllowGBCartEra;
public static bool AllowGBStadium2 => AllowGBCartEra;
/// <summary>
/// Initializes certain settings
/// </summary>
/// <param name="sav">Newly loaded save file</param>
/// <returns>Save file is Physical GB cartridge save file (not Virtual Console)</returns>
public static void InitFromSaveFileData(SaveFile sav)
public static bool InitFromSaveFileData(SaveFile sav)
{
ActiveTrainer = sav;
AllowEraCartGB = sav switch
return AllowGBCartEra = sav switch
{
SAV1 { IsVirtualConsole: true } => false,
SAV2 { IsVirtualConsole: true } => false,
{ Generation: 1 or 2 } => true,
_ => false,
};
var isVirtual3 = sav is SAV3 { IsVirtualConsole: true };
AllowEraSwitchGBA = isVirtual3;
AllowEraCartGBA = !isVirtual3; // sav.Generation >= 8; TODO HOME FR/LG
}
internal static bool IgnoreTransferIfNoTracker => Settings.HOMETransfer.HOMETransferTrackerNotPresent == Severity.Invalid;

View File

@ -363,13 +363,10 @@ public enum LegalityCheckResultCode : ushort
TransferEncryptGen6Xor,
TransferTrackerMissing,
TransferTrackerShouldBeZero,
// Trash Bytes
TrashBytesExpected,
TrashBytesMismatchInitial,
TrashBytesMissingTerminatorFinal,
TrashBytesMissingTerminator,
TrashBytesShouldBeEmpty,
TrashBytesResetViaTransfer,
// Bulk Cross-Comparison
BulkCloneDetectedDetails,

View File

@ -18,7 +18,7 @@ public override void Verify(LegalityAnalysis data)
// If no stats have been increased from the initial amount, then we're done here.
// some encounters have contest stats built in. they're already checked by the initial encounter match.
if (!s.HasContestStats() || data.EncounterOriginal is IContestStatsReadOnly ro && ro.IsContestEqual(s))
if (!s.HasContestStats())
return;
// Check the correlation of Stats & Sheen!

View File

@ -61,7 +61,6 @@ public void SetMaxContestStats(IEncounterTemplate enc, EvolutionHistory h)
public static ContestStatGranting GetContestStatRestriction(PKM pk, byte origin, EvolutionHistory h) => origin switch
{
3 when pk.Format == 3 && !ParseSettings.AllowGBACrossTransferRSE(pk) => None,
3 => pk.Format < 6 ? CorrelateSheen : Mixed,
4 => pk.Format < 6 ? CorrelateSheen : Mixed,

View File

@ -14,12 +14,6 @@ internal void Verify(LegalityAnalysis data, PKM pk)
if (pk.Format == 1) // not stored in Gen1 format
return;
if (pk.Format == 3 && !ParseSettings.AllowGBACrossTransferRSE(pk))
{
VerifyNone(data, pk);
return;
}
var strain = pk.PokerusStrain;
var days = pk.PokerusDays;
var enc = data.Info.EncounterMatch;
@ -28,14 +22,4 @@ internal void Verify(LegalityAnalysis data, PKM pk)
if (!Pokerus.IsDurationValid(strain, days, out var max))
data.AddLine(GetInvalid(PokerusDaysLEQ_0, (ushort)max));
}
private void VerifyNone(LegalityAnalysis data, PKM pk)
{
var strain = pk.PokerusStrain;
var days = pk.PokerusDays;
if (strain != 0)
data.AddLine(GetInvalid(PokerusStrainUnobtainable_0, (ushort)strain));
if (days != 0)
data.AddLine(GetInvalid(PokerusDaysLEQ_0, 0));
}
}

View File

@ -1,277 +0,0 @@
using System;
using static PKHeX.Core.LegalityCheckResultCode;
using static PKHeX.Core.CheckIdentifier;
namespace PKHeX.Core;
public sealed class MiscVerifierG3 : Verifier
{
protected override CheckIdentifier Identifier => Misc;
public override void Verify(LegalityAnalysis data)
{
if (data.Entity is G3PKM pk)
Verify(data, pk);
}
internal void Verify(LegalityAnalysis data, G3PKM pk)
{
VerifyTrash(data, pk);
if (ParseSettings.AllowGBACrossTransferRSE(pk))
return;
// Notes:
// Nicknamed by player: all FF'd trash.
// In-game trade: clean 00'd with a single FF on each.
// Only FR/LG are released. Only can originate from FR/LG.
if (pk.Version is not (GameVersion.FR or GameVersion.LG))
data.AddLine(GetInvalid(TradeNotAvailable));
else if (Legal.IsForeignFRLG(pk.Species))
data.AddLine(GetInvalid(TradeNotAvailable));
if (ItemStorage3FRLG_VC.IsUnreleasedHeld(pk.HeldItem))
data.AddLine(GetInvalid(ItemUnreleased));
if ((Ball)pk.Ball is Ball.Dive or Ball.Premier)
data.AddLine(GetInvalid(BallUnavailable));
}
private void VerifyTrash(LegalityAnalysis data, G3PKM pk)
{
if (pk is PK3 pk3)
VerifyTrash(data, pk3);
else
VerifyTrashCXD(data, pk);
}
private static void VerifyTrashCXD(LegalityAnalysis data, G3PKM pk)
{
// Buffers should be entirely clean.
var ot = pk.OriginalTrainerTrash;
var result = TrashBytesUTF16.IsTrashNone(ot);
if (result.IsInvalid)
data.AddLine(Get(Trainer, Severity.Invalid, TrashBytesShouldBeEmpty));
var nick = pk.NicknameTrash;
result = TrashBytesUTF16.IsTrashNone(nick);
if (result.IsInvalid)
data.AddLine(Get(Nickname, Severity.Invalid, TrashBytesShouldBeEmpty));
}
private void VerifyTrash(LegalityAnalysis data, PK3 pk)
{
if (!pk.IsEgg && TrashByteRules3.IsResetTrash(pk))
{
data.AddLine(GetValid(TrashBytesResetViaTransfer));
return; // OK
}
var enc = data.EncounterOriginal;
if (enc is EncounterTrade3)
VerifyTrashTrade(data, pk);
else if (enc is EncounterGift3 g3)
VerifyTrashEvent3(data, pk, g3);
else if (enc is EncounterGift3JPN jp)
VerifyTrashEvent3(data, pk, jp);
else if (enc is EncounterGift3NY ny)
VerifyTrashEvent3(data, pk, ny);
else if (pk.Japanese && !(pk.IsEgg && pk.OriginalTrainerTrash[^1] == 0xFF))
VerifyTrashJPN(data, pk);
else
VerifyTrashINT(data, pk);
}
private static void VerifyTrashEvent3(LegalityAnalysis data, PK3 pk, EncounterGift3NY ny)
{
// todo
}
private static void VerifyTrashEvent3(LegalityAnalysis data, PK3 pk, EncounterGift3JPN jp)
{
// todo
}
private static void VerifyTrashEvent3(LegalityAnalysis data, PK3 pk, EncounterGift3 g3)
{
// todo
}
private static void VerifyTrashTrade(LegalityAnalysis data, PK3 pk)
{
if (!TrashByteRules3.IsTerminatedZero(pk.OriginalTrainerTrash))
data.AddLine(GetInvalid(Trainer, TrashBytesShouldBeEmpty));
if (!TrashByteRules3.IsTerminatedZero(pk.NicknameTrash))
data.AddLine(GetInvalid(Nickname, TrashBytesShouldBeEmpty));
}
private static void VerifyTrashJPN(LegalityAnalysis data, PK3 pk)
{
var trash = pk.OriginalTrainerTrash;
// OT name from save file is copied byte-for-byte. Byte 7 & 8 are always zero.
if (!TrashByteRules3.IsTerminatedFFZero(trash, 6))
data.AddLine(GetInvalid(Trainer, TrashBytesMissingTerminatorFinal));
// Nickname can be all FF's (nicknamed) or whatever random garbage is in the buffer before filling. Unsure if we can reliably check this, but it should be "dirty" usually.
// If it is clean, flag as fishy.
FlagIsNicknameClean(data, pk);
}
private static void VerifyTrashINT(LegalityAnalysis data, PK3 pk)
{
var trash = pk.OriginalTrainerTrash;
// OT name from save file is copied byte-for-byte. All 8 bytes are initialized to FF on new game.
if (!TrashByteRules3.IsTerminatedFFZero(trash, 7))
{
if (!TrashByteRules3.IsTrashPatternDefaultTrainer(trash, pk.Version, (LanguageID)pk.Language))
data.AddLine(GetInvalid(Trainer, TrashBytesMissingTerminatorFinal));
}
// Nickname can be all FF's (nicknamed) or whatever random garbage is in the buffer before filling. Unsure if we can reliably check this, but it should be "dirty" usually.
// If it is clean, flag as fishy.
FlagIsNicknameClean(data, pk);
}
private static void FlagIsNicknameClean(LegalityAnalysis data, PK3 pk)
{
if (!pk.IsNicknamed || pk.IsEgg)
return;
// Japanese only fills the first 5+1 bytes; everything else is trash.
// International games are 10 chars (full buffer) max; implicit terminator if full.
var nick = pk.GetNicknamePrefillRegion();
if (!TrashByteRules3.IsTerminatedFF(nick))
data.AddLine(GetInvalid(Trainer, TrashBytesMismatchInitial));
}
}
public static class TrashByteRules3
{
// PK3 stores u8[length] for OT name and Nickname.
// Due to how the game initializes the buffer for each, specific patterns in the unused bytes (after the string, within the allocated max buffer) can arise.
// When transferred to Colosseum/XD, the encoding method switches to u16[length], thus discarding the original buffer along with its "trash".
// For original encounters from a mainline save file,
// - OT Name: the game copies the entire buffer from the save file OT as the PK3's OT. Thus, that must match exactly.
// - - Japanese OT names are 5 chars, international is 7 chars. Manually entered strings are FF terminated to max length + 1.
// - - Default OT (Japanese) names were padded with FF to len=6, so they always match manually entered names (no trash).
// - - Default OT (International) names from the character select screen can have trash bytes due to being un-padded (single FF end of string, saves ROM space).
// - - verification of Default OTs todo (if OT dirty, check if is default with expected trash pattern)
// - Nickname: the buffer has garbage RAM data leftover in the nickname field, thus it should be "dirty" usually.
// - Nicknamed: when nicknamed, the game fills the buffer with FFs then applies the nickname.
// For event encounters from GameCube:
// - OT Name: todo
// - Nickname: todo
// For event encounters directly injected into the save file via GBA multiboot:
// - OT Name: todo
// - Nickname: todo
private const byte Terminator = StringConverter3.TerminatorByte;
public static bool IsResetTrash(PK3 pk3)
{
if (!ParseSettings.AllowGBACrossTransferXD(pk3))
return false;
if (!IsTerminatedZero(pk3.OriginalTrainerTrash))
return false;
if (pk3.IsNicknamed)
return true;
if (!IsTerminatedZero(pk3.NicknameTrash))
return false;
return true;
}
public static bool IsTerminatedZero(ReadOnlySpan<byte> data)
{
var first = TrashBytes8.GetTerminatorIndex(data);
if (first == -1 || first >= data.Length - 1)
return true;
return !data[(first+1)..].ContainsAnyExcept<byte>(0);
}
public static bool IsTerminatedFF(ReadOnlySpan<byte> data)
{
var first = TrashBytes8.GetTerminatorIndex(data);
if (first == -1 || first >= data.Length - 1)
return true;
return !data[(first + 1)..].ContainsAnyExcept<byte>(0xFF);
}
public static bool IsTerminatedFFZero(ReadOnlySpan<byte> data, int preFill = 0)
{
if (preFill == 0)
return IsTerminatedZero(data);
var first = TrashBytes8.GetTerminatorIndex(data);
if (first == -1 || first >= data.Length - 1)
return true;
first++;
if (first < preFill)
{
var inner = data[first..preFill];
if (inner.ContainsAnyExcept(Terminator))
return false;
first = preFill;
first++;
if (first >= data.Length - 1)
return true;
}
return !data[first..].ContainsAnyExcept<byte>(0);
}
// TRASH BYTES: New Game Default OTs
// Default OT names in International (not JPN) Gen3 mainline games memcpy 7 chars and FF from the "default OT name" table, regardless of strlen.
// Copied strings therefore contain trash from the next string entry that is encoded into the rom's string table.
// Below is a list of possible (version, language, trash) default OTs, as initialized by the game. An `*` is used to denote the terminator.
/// <summary>
/// Checks if the specified trash byte pattern matches a default trainer name pattern for the given game <see cref="version"/> and <see cref="language"/>.
/// </summary>
/// <remarks>Default trainer names in certain Generation 3 Pokémon games may include trailing bytes ("trash") due to how names are stored in the game's ROM.
/// This method checks if the provided pattern matches any of these known default patterns for the specified version and language.
/// </remarks>
public static bool IsTrashPatternDefaultTrainer(ReadOnlySpan<byte> trash, GameVersion version, LanguageID language) => version switch
{
GameVersion.R or GameVersion.S => IsTrashPatternDefaultTrainerRS(trash, language),
GameVersion.E => IsTrashPatternDefaultTrainerE(trash, language),
GameVersion.FR => IsTrashPatternDefaultTrainerFR(trash, language),
GameVersion.LG => IsTrashPatternDefaultTrainerLG(trash, language),
_ => false,
};
/// <inheritdoc cref="IsTrashPatternDefaultTrainer"/>
/// <remarks>Default OT names present in <see cref="GameVersion.R"/> and <see cref="GameVersion.S"/> based on the language of the game.</remarks>
public static bool IsTrashPatternDefaultTrainerRS(ReadOnlySpan<byte> trash, LanguageID language) => language switch
{
// TODO
LanguageID.English => trash switch
{
[0xCD, 0xBB, 0xCC, 0xBB, 0xFF, 0xCE, 0xDC] => true, // SARA*Th
_ => false,
},
_ => false,
};
/// <inheritdoc cref="IsTrashPatternDefaultTrainer"/>
/// <remarks>Default OT names present in <see cref="GameVersion.E"/> based on the language of the game.</remarks>
public static bool IsTrashPatternDefaultTrainerE(ReadOnlySpan<byte> trash, LanguageID language) => language switch
{
// TODO
_ => false,
};
/// <inheritdoc cref="IsTrashPatternDefaultTrainer"/>
/// <remarks>Default OT names present in <see cref="GameVersion.FR"/> based on the language of the game.</remarks>
public static bool IsTrashPatternDefaultTrainerFR(ReadOnlySpan<byte> trash, LanguageID language) => language switch
{
// TODO
_ => false,
};
/// <inheritdoc cref="IsTrashPatternDefaultTrainer"/>
/// <remarks>Default OT names present in <see cref="GameVersion.LG"/> based on the language of the game.</remarks>
public static bool IsTrashPatternDefaultTrainerLG(ReadOnlySpan<byte> trash, LanguageID language) => language switch
{
// TODO
_ => false,
};
}

View File

@ -17,7 +17,7 @@ internal static void VerifyStatNature(LegalityAnalysis data, PKM pk)
return;
// Must be a valid mint nature.
if (!statNature.IsMint)
if (!statNature.IsMint())
data.AddLine(Get(Invalid, Misc, StatNatureInvalid));
}

View File

@ -19,7 +19,6 @@ public sealed class MiscVerifier : Verifier
private static readonly MiscG1Verifier Gen1 = new();
private static readonly MiscEvolutionVerifier Evolution = new();
private static readonly MiscVerifierSK2 Stadium2 = new();
private static readonly MiscVerifierG3 Gen3 = new();
private static readonly MiscVerifierG4 Gen4 = new();
private static readonly MiscVerifierPK6 Gen6 = new();
private static readonly MiscVerifierPK5 Gen5 = new();
@ -42,7 +41,6 @@ public override void Verify(LegalityAnalysis data)
// Verify gimmick data
switch (pk)
{
case G3PKM pk3: Gen3.Verify(data, pk3); break;
case G4PKM pk4: Gen4.Verify(data, pk4); break;
case PK5 pk5: Gen5.Verify(data, pk5); break;
case PK6 pk6: Gen6.Verify(data, pk6); break;

View File

@ -24,7 +24,7 @@ public override void Verify(LegalityAnalysis data)
if (pk.PID == 0)
data.AddLine(Get(Severity.Fishy, PIDZero));
if (!pk.Nature.IsFixed) // out of range
if (!pk.Nature.IsFixed()) // out of range
data.AddLine(GetInvalid(PIDNatureMismatch));
if (data.Info.EncounterMatch is IEncounterEgg egg)
VerifyEggPID(data, pk, egg);

View File

@ -324,21 +324,6 @@ public static bool GetValidRibbonStateNational(PKM pk, IEncounterTemplate enc)
return true;
}
/// <summary>
/// Checks if the input can receive the <see cref="IRibbonSetEvent3.RibbonEarth"/> ribbon.
/// </summary>
/// <remarks>
/// If returns true, can have the ribbon. If returns false, must not have the ribbon.
/// </remarks>
public static bool IsEarthRibbonAllowed(PKM pk, IEncounterTemplate enc)
{
if (enc.Generation != 3)
return false;
if (!ParseSettings.AllowGBACrossTransferXD(pk))
return false;
return true;
}
/// <summary>
/// Gets the max count values the input can receive for the <see cref="IRibbonSetMemory6.RibbonCountMemoryContest"/> and <see cref="IRibbonSetMemory6.RibbonCountMemoryBattle"/> ribbon counts.
/// </summary>
@ -375,11 +360,9 @@ public static (byte Contest, byte Battle) GetMaxMemoryCounts(EvolutionHistory ev
/// <summary>
/// Checks if the input evolution history could have participated in Generation 3 contests.
/// </summary>
public static bool IsAllowedContest3(EvolutionHistory evos, PKM pk)
public static bool IsAllowedContest3(EvolutionHistory evos)
{
// Any species can enter contests in Gen3.
if (!ParseSettings.AllowGBACrossTransferRSE(pk))
return false;
return evos.HasVisitedGen3;
}

View File

@ -21,7 +21,7 @@ public void Parse(in RibbonVerifierArguments args, ref RibbonResultList list)
if (!r.RibbonEarth)
list.Add(Earth, true);
}
else if (r.RibbonEarth && !RibbonRules.IsEarthRibbonAllowed(args.Entity, enc))
else if (r.RibbonEarth && enc.Generation != 3)
{
list.Add(Earth);
}
@ -41,7 +41,7 @@ public void Parse(in RibbonVerifierArguments args, ref RibbonResultList list)
{
// The Earth Ribbon is a ribbon exclusive to Pokémon Colosseum and Pokémon XD: Gale of Darkness
// Awarded to all Pokémon on the player's team when they complete the Mt. Battle challenge without switching the team at any point.
if (r.RibbonEarth && !RibbonRules.IsEarthRibbonAllowed(args.Entity, enc))
if (r.RibbonEarth && enc.Generation != 3)
list.Add(Earth);
var nationalRequired = RibbonRules.GetValidRibbonStateNational(args.Entity, enc);

View File

@ -12,7 +12,7 @@ public static void Parse(this IRibbonSetOnly3 r, in RibbonVerifierArguments args
if (r.RibbonWorld)
list.Add(RibbonIndex.World);
if (!RibbonRules.IsAllowedContest3(args.History, args.Entity))
if (!RibbonRules.IsAllowedContest3(args.History))
FlagContestAny(r, ref list);
else
FlagContest(r, ref list);

View File

@ -13,7 +13,7 @@ public void Parse(in RibbonVerifierArguments args, ref RibbonResultList list)
if (!RibbonRules.IsAllowedBattleFrontier4(evos))
FlagAnyAbility(r, ref list);
if (RibbonRules.IsAllowedContest3(evos, args.Entity))
if (RibbonRules.IsAllowedContest3(evos))
AddMissingContest3(r, ref list);
else
FlagAnyContest3(r, ref list);

View File

@ -62,11 +62,11 @@ private void VerifyTrashBytesPCD(LegalityAnalysis data, PKM pk, PCD pcd)
private void VerifyTrashBytesHOME(LegalityAnalysis data, PKM pk)
{
if (!TrashBytesUTF16.IsFinalTerminatorPresent(pk.NicknameTrash))
data.AddLine(GetInvalid(CheckIdentifier.Nickname, TrashBytesMissingTerminatorFinal));
data.AddLine(GetInvalid(CheckIdentifier.Nickname, TrashBytesMissingTerminator));
if (!TrashBytesUTF16.IsFinalTerminatorPresent(pk.OriginalTrainerTrash))
data.AddLine(GetInvalid(CheckIdentifier.Trainer, TrashBytesMissingTerminatorFinal));
data.AddLine(GetInvalid(CheckIdentifier.Trainer, TrashBytesMissingTerminator));
if (!TrashBytesUTF16.IsFinalTerminatorPresent(pk.HandlingTrainerTrash))
data.AddLine(GetInvalid(CheckIdentifier.Handler, TrashBytesMissingTerminatorFinal));
data.AddLine(GetInvalid(CheckIdentifier.Handler, TrashBytesMissingTerminator));
if (pk.IsEgg)
{

View File

@ -626,7 +626,7 @@ public override bool IsMatchExact(PKM pk, EvoCriteria evo)
if (MetLevel != 0 && MetLevel != pk.MetLevel) return false;
if ((Ball == 0 ? 4 : Ball) != pk.Ball) return false;
if (OTGender < 2 && OTGender != pk.OriginalTrainerGender) return false;
if (Nature.IsFixed && pk.Nature != Nature) return false;
if (Nature != Nature.Random && pk.Nature != Nature) return false;
if (Gender != 3 && Gender != pk.Gender) return false;
if (pk is IScaledSize s)

View File

@ -398,15 +398,4 @@ public override int GetStringTerminatorIndex(ReadOnlySpan<byte> data)
public override int GetStringLength(ReadOnlySpan<byte> data)
=> TrashBytes8.GetStringLength(data);
public override int GetBytesPerChar() => 1;
public override void PrepareNickname() => GetNicknamePrefillRegion().Fill(StringConverter3.TerminatorByte);
public Span<byte> GetNicknamePrefillRegion()
{
// Japanese only fills the first 5+1 bytes; everything else is trash.
// International games are 10 chars (full buffer) max; implicit terminator if full.
if (Japanese)
return NicknameTrash[..6];
return NicknameTrash;
}
}

View File

@ -11,7 +11,7 @@ namespace PKHeX.Core;
/// Object representing a <see cref="PKM"/>'s data and derived properties.
/// </summary>
[DynamicallyAccessedMembers(PublicProperties | NonPublicProperties | PublicParameterlessConstructor)]
public abstract class PKM : ISpeciesForm, ITrainerID32, IGeneration, IShiny, ILangNick, IGameValueLimit, INature, IFatefulEncounter, IStringConverter, ITrashIntrospection, IContext
public abstract class PKM : ISpeciesForm, ITrainerID32, IGeneration, IShiny, ILangNick, IGameValueLimit, INature, IFatefulEncounter, IStringConverter, ITrashIntrospection
{
public abstract int SIZE_PARTY { get; }
public abstract int SIZE_STORED { get; }
@ -44,11 +44,6 @@ public abstract class PKM : ISpeciesForm, ITrainerID32, IGeneration, IShiny, ILa
public abstract Span<byte> OriginalTrainerTrash { get; }
public virtual Span<byte> HandlingTrainerTrash => [];
/// <summary>
/// Conditions the <see cref="NicknameTrash"/> data to safely terminate the Nickname string from the text entry screen.
/// </summary>
public virtual void PrepareNickname() { }
protected abstract byte[] Encrypt();
public abstract EntityContext Context { get; }
public byte Format => Context.Generation;

View File

@ -168,7 +168,7 @@ private bool SearchSimple(PKM pk)
return false;
if (Ability > -1 && pk.Ability != Ability)
return false;
if (Nature.IsFixed && pk.StatNature != Nature)
if (Nature.IsFixed() && pk.StatNature != Nature)
return false;
if (Item > -1 && pk.HeldItem != Item)
return false;

View File

@ -17,7 +17,7 @@ public abstract class G3PKM : PKM, IRibbonSetEvent3, IRibbonSetCommon3, IRibbonS
public sealed override ushort MaxMoveID => Legal.MaxMoveID_3;
public sealed override ushort MaxSpeciesID => Legal.MaxSpeciesID_3;
public sealed override int MaxAbilityID => Legal.MaxAbilityID_3;
public sealed override int MaxItemID => Legal.MaxItemID_3_E;
public sealed override int MaxItemID => Legal.MaxItemID_3;
public sealed override int MaxBallID => Legal.MaxBallID_3;
public sealed override GameVersion MaxGameID => Legal.MaxGameID_3;
public sealed override int MaxIV => 31;

View File

@ -289,7 +289,7 @@ public sealed override byte Ball
BallDPPt = Clamp(value, Core.Ball.Cherish);
// Only set the HG/SS value if it originated in HG/SS and was not an event.
if (WasCreatedInHGSS && this is not BK4)
if (WasCreatedInHGSS)
BallHGSS = Clamp(value, Core.Ball.Sport);
else
BallHGSS = 0;

View File

@ -35,20 +35,3 @@ public interface IStringConverter
/// <returns>Count of bytes written to <see cref="data"/>.</returns>
int SetString(Span<byte> data, ReadOnlySpan<char> text, int length, StringConverterOption option);
}
public delegate string StringGetter(ReadOnlySpan<byte> data);
public delegate int StringLoader(ReadOnlySpan<byte> data, Span<char> text);
public delegate int StringSetter(Span<byte> data, ReadOnlySpan<char> text, int length, StringConverterOption option);
public sealed class CustomStringConverter : IStringConverter, IGeneration, IContext
{
public required StringGetter Get { get; init; }
public required StringLoader Load { get; init; }
public required StringSetter Set { get; init; }
public required byte Generation { get; init; }
public required EntityContext Context { get; init; }
public string GetString(ReadOnlySpan<byte> data) => Get(data);
public int LoadString(ReadOnlySpan<byte> data, Span<char> text) => Load(data, text);
public int SetString(Span<byte> data, ReadOnlySpan<char> text, int length, StringConverterOption option) => Set(data, text, length, option);
}

View File

@ -73,8 +73,8 @@ public static byte GetItemOld2(ushort item)
NaN, 058, 059, 061, 444, NaN, NaN, 216, 445, 446, // 5
NaN, 447, 051, 038, 039, 040, 478, 464, 456, 484, // 6
NaN, 482, 033, 217, 151, NaN, 237, 244, 149, 153, // 7
152, 245, 221, 156, 150, 485, 086, 087, 222, 486, // 8
NaN, 223, 487, 488, 224, 243, 248, 490, 241, 491, // 9
152, 245, 221, 156, 150, 485, 086, 087, 222, 487, // 8
NaN, 223, 486, 488, 224, 243, 248, 490, 241, 491, // 9
NaN, 489, 240, 473, NaN, 259, 228, 246, 242, 157, // 10
088, 089, 229, 247, 504, NaN, NaN, 239, 258, 230, // 11
NaN, 034, 035, 036, 037, 238, 231, 475, 481, NaN, // 12

View File

@ -85,15 +85,6 @@ public static byte GetGenderRatio(ushort species)
return NG;
}
/// <summary>
/// Checks if the species (base form) is a single gender.
/// </summary>
public static bool IsSingleGender(ushort species)
{
var ratio = GetGenderRatio(species);
return ratio is OM or OF or NG;
}
private static ReadOnlySpan<byte> GenderRatios =>
[
NG, VM, VM, VM, VM, VM, VM, VM, VM, VM, HH, HH, HH, HH, HH, HH, HH, HH, HH, HH, HH, HH, HH, HH, HH, HH, HH, HH, HH, OF, OF, OF, OM, OM, OM, MF, MF, MF, MF, MF,

View File

@ -12,4 +12,4 @@
"FrameNewGame": "Neues Spiel: 0x{0}, Frame: {1}",
"FrameInitial": "Initial: 0x{0}, Frame: {1}",
"SuffixDays": " (tage: {0})"
}
}

View File

@ -12,4 +12,4 @@
"FrameNewGame": "New Game: 0x{0}, Frame: {1}",
"FrameInitial": "Initial: 0x{0}, Frame: {1}",
"SuffixDays": " (days: {0})"
}
}

View File

@ -12,4 +12,4 @@
"FrameNewGame": "Nuevo juego: 0x{0}, Fotograma: {1}",
"FrameInitial": "Inicial: 0x{0}, Fotograma: {1}",
"SuffixDays": " (días: {0})"
}
}

View File

@ -12,4 +12,4 @@
"FrameNewGame": "Nuevo juego: 0x{0}, Fotograma: {1}",
"FrameInitial": "Inicial: 0x{0}, Fotograma: {1}",
"SuffixDays": " (días: {0})"
}
}

View File

@ -12,4 +12,4 @@
"FrameNewGame": "Nouvelle partie : 0x{0}, Image : {1}",
"FrameInitial": "Initial : 0x{0}, Image : {1}",
"SuffixDays": " (jours : {0})"
}
}

View File

@ -12,4 +12,4 @@
"FrameNewGame": "Nuova partita: 0x{0}, Frame: {1}",
"FrameInitial": "Iniziale: 0x{0}, Frame: {1}",
"SuffixDays": " (giorni: {0})"
}
}

View File

@ -12,4 +12,4 @@
"FrameNewGame": "新規ゲーム: 0x{0}, フレーム: {1}",
"FrameInitial": "初期値: 0x{0}, フレーム: {1}",
"SuffixDays": " (日数: {0})"
}
}

View File

@ -12,4 +12,4 @@
"FrameNewGame": "새 게임: 0x{0}, 프레임: {1}",
"FrameInitial": "초기값: 0x{0}, 프레임: {1}",
"SuffixDays": " (일: {0})"
}
}

View File

@ -12,4 +12,4 @@
"FrameNewGame": "新游戏0x{0},帧:{1}",
"FrameInitial": "初始0x{0},帧:{1}",
"SuffixDays": " (天数:{0})"
}
}

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