Misc legality fixes for Z-A alterations

- Evolving knowing move: relearnable additions in the evolved stage was bypassing the requirement (Sylveon can relearn Charm at any level, but Eevee cannot). Prune tree to only check if pre-evolutions could have learned move.
- FormArgument requiring a minimum level to actually use the move (Primeape). Probably isn't a "complete" check, since it's implemented differently compared to Qwilfish's logic. Might be worth revising in the future to be consistent (using the same as Primeape logic? if in game, and can learn, can increase from 0).
- Flag Hangry Morpeko if cannot learn Aura Wheel yet
- Flag mega evo mismatches for Tatsugiri/Magearna/Meowstic
- Permit mega meowstic gender in party

- Remap DLC TMs (I forgot this remapping was needed; pkNX dumped it but I didn't update the table until now...)
This commit is contained in:
Kurt 2025-12-20 14:44:08 -06:00
parent 1edfbfab0e
commit 6609dd210b
8 changed files with 101 additions and 29 deletions

View File

@ -99,4 +99,43 @@ public EvolutionHistory AsSingle(EntityContext context)
single.Set(context, Get(context).ToArray());
return single;
}
public EvolutionHistory PruneKeepPreEvolutions(ushort species) => new()
{
Gen1 = PruneKeepPreEvolutions(Gen1, species),
Gen2 = PruneKeepPreEvolutions(Gen2, species),
Gen3 = PruneKeepPreEvolutions(Gen3, species),
Gen4 = PruneKeepPreEvolutions(Gen4, species),
Gen5 = PruneKeepPreEvolutions(Gen5, species),
Gen6 = PruneKeepPreEvolutions(Gen6, species),
Gen7 = PruneKeepPreEvolutions(Gen7, species),
Gen8 = PruneKeepPreEvolutions(Gen8, species),
Gen9 = PruneKeepPreEvolutions(Gen9, species),
Gen7b = PruneKeepPreEvolutions(Gen7b, species),
Gen8a = PruneKeepPreEvolutions(Gen8a, species),
Gen8b = PruneKeepPreEvolutions(Gen8b, species),
Gen9a = PruneKeepPreEvolutions(Gen9a, species),
};
private static EvoCriteria[] PruneKeepPreEvolutions(EvoCriteria[] src, ushort species)
{
// Most evolved species is at the lowest index.
// If `species` is at the current index, only keep indexes after.
var start = GetSpeciesIndex(src, species);
if (start == -1)
return src;
if (start == src.Length - 1)
return NONE;
return src[(start + 1)..];
}
private static int GetSpeciesIndex(ReadOnlySpan<EvoCriteria> array, ushort species)
{
for (int i = 0; i < array.Length; i++)
{
if (array[i].Species == species)
return i;
}
return -1;
}
}

View File

@ -68,6 +68,7 @@ public static bool IsFormArgEvolution(ushort species)
[
(int)Charm,
(int)BabyDollEyes,
(int)DisarmingVoice, // Z-A
];
/// <summary>
@ -90,34 +91,23 @@ public static bool IsValidEvolutionWithMove(PKM pk, LegalInfo info)
var move = GetSpeciesEvolutionMove(species);
if (move is NONE)
return true; // not a move evolution
if (move is EEVEE)
return IsValidEvolutionWithMoveSylveon(pk, enc, info);
if (!IsMoveSlotAvailable(info.Moves))
return false;
if (pk.HasMove(move))
return true;
// Check the entire chain to see if it could have learnt it at any point.
var head = LearnGroupUtil.GetCurrentGroup(pk);
return MemoryPermissions.GetCanKnowMove(enc, move, info.EvoChainsAllGens, pk, head);
var pruned = info.EvoChainsAllGens.PruneKeepPreEvolutions(species);
if (move is EEVEE)
return IsValidEvolutionWithMoveAny(enc, EeveeFairyMoves, pruned, pk, head);
return MemoryPermissions.GetCanKnowMove(enc, move, pruned, pk, head);
}
private static bool IsValidEvolutionWithMoveSylveon(PKM pk, IEncounterTemplate enc, LegalInfo info)
private static bool IsValidEvolutionWithMoveAny(IEncounterTemplate enc, ReadOnlySpan<ushort> any, EvolutionHistory history, PKM pk, ILearnGroup head)
{
if (!IsMoveSlotAvailable(info.Moves))
return false;
foreach (var move in EeveeFairyMoves)
foreach (var move in any)
{
if (pk.HasMove(move))
return true;
}
var head = LearnGroupUtil.GetCurrentGroup(pk);
foreach (var move in EeveeFairyMoves)
{
if (MemoryPermissions.GetCanKnowMove(enc, move, info.EvoChainsAllGens, pk, head))
if (MemoryPermissions.GetCanKnowMove(enc, move, history, pk, head))
return true;
}
return false;

View File

@ -146,7 +146,6 @@ public enum LegalityCheckResultCode : ushort
FormPikachuCosplay,
FormPikachuCosplayInvalid,
FormPikachuEventInvalid,
FormInvalidExpect_0,
FormValid,
FormVivillon,
FormVivillonEventPre,
@ -398,6 +397,7 @@ public enum LegalityCheckResultCode : ushort
EvoTradeReqOutsider_0,
FormArgumentLEQ_0,
FormArgumentGEQ_0,
FormInvalidExpect_0,
HyperTrainLevelGEQ_0, // level
IVAllEqual_0,
IVFlawlessCountGEQ_0, // count

View File

@ -92,6 +92,9 @@ public static bool IsMegaForm(ushort species, byte form)
(ushort)Mimikyu => (byte)(form & 2),
(ushort)Ogerpon => (byte)(form & 3),
(ushort)Floette => 5,
(ushort)Tatsugiri => (byte)(form - 3), // Mega (form specific)
(ushort)Magearna => (byte)(form - 2), // Mega (form specific)
(ushort)Meowstic => (byte)(form - 2), // Mega (gendered)
_ => 0,
};

View File

@ -68,11 +68,7 @@ private CheckResult VerifyFormArgument(LegalityAnalysis data, IFormArgument f)
> 9_999 => GetInvalid(FormArgumentLEQ_0, 9999),
_ => arg == 0 || HasVisitedPLA(data, Stantler) ? GetValid(FormArgumentValid) : GetInvalid(FormArgumentNotAllowed),
},
Primeape => arg switch
{
> 9_999 => GetInvalid(FormArgumentLEQ_0, 9999),
_ => arg == 0 || HasVisitedSV(data, Primeape) || HasVisitedZA(data, Primeape) ? GetValid(FormArgumentValid) : GetInvalid(FormArgumentNotAllowed),
},
Primeape => CheckPrimeape(data, pk, arg, enc),
Bisharp => arg switch
{
> 9_999 => GetInvalid(FormArgumentLEQ_0, 9999),
@ -123,6 +119,27 @@ private CheckResult VerifyFormArgument(LegalityAnalysis data, IFormArgument f)
};
}
private CheckResult CheckPrimeape(LegalityAnalysis data, PKM pk, uint arg, IEncounterable enc)
{
if (arg == 0)
return GetValid(FormArgumentValid);
if (arg > 9_999)
return GetInvalid(FormArgumentLEQ_0, 9999);
if (HasVisitedSV(data, Primeape) || HasVisitedZA(data, Primeape))
{
const ushort move = (ushort)Move.RageFist;
// Eager check
if (pk.HasMove(move))
return GetValid(FormArgumentValid);
var head = LearnGroupUtil.GetCurrentGroup(pk);
if (MemoryPermissions.GetCanKnowMove(enc, move, data.Info.EvoChainsAllGens, pk, head))
return GetValid(FormArgumentValid);
}
return GetInvalid(FormArgumentLEQ_0, 0); // Can't increase from 0.
}
private CheckResult CheckFarfetchd(LegalityAnalysis data, PKM pk, uint arg)
{
if (arg == 0)

View File

@ -143,7 +143,7 @@ private CheckResult VerifyForm(LegalityAnalysis data)
if (enc is not EncounterGift9a)
return GetInvalid(FormEternalInvalid);
return GetValid(FormEternal);
case Meowstic when form != pk.Gender:
case Meowstic when (form & 1) != pk.Gender:
return GetInvalid(GenderInvalidNone);
case Silvally:
@ -197,7 +197,14 @@ private CheckResult VerifyBattleForms9a(LegalityAnalysis data, ushort species, b
// Battle forms can exist in Party.
if (!FormInfo.IsMegaForm(species, form) && !FormInfo.IsPrimalForm(species, form))
{
// Morpeko-1 can exist in party, but only if it's at a level where it can know the move Aura Wheel; only learns starting at Lv. 57.
// You can use move menu to forget Aura Wheel while it's in Hangry Mode, but the form doesn't revert, kinda like Mega Rayquaza with Dragon Ascent
if (species is (int)Morpeko && !MemoryPermissions.GetCanKnowMove(data.EncounterMatch, (ushort)Move.AuraWheel, data.Info.EvoChainsAllGens, data.Entity, LearnGroupUtil.GetCurrentGroup(data.Entity)))
return GetInvalid(FormInvalidExpect_0, 0);
return VALID;
}
var megaStone = ItemStorage9ZA.GetExpectedMegaStoneOrPrimalOrb(species, form);
if (megaStone == 0 || data.Entity.HeldItem == megaStone)

View File

@ -258,5 +258,12 @@ public static ushort GetTechnicalMachineIndex9a(ushort oldTM)
-68, +06, -68, -07, -07, -64, -71, -07, +15, -07,
-07, -07, -07, -07, -90, -07, -07, -07, -80, -07,
-63, 0, -08, -08, -13, -08, -71, -08, -08,
0,
+01, +02, +02, +03, +04, +06, +06, +06, +07, +08,
+09, +12, +12, +13, +15, +17, +17, +29, +29, -21,
-20, -19, -17, -16, -15, -15, -12, -11, -10, -09,
-09, -09, -07, -06, -06, -05, -05, -03, -03, -03,
-03, -03, -03, -03, -03, -03, -03, -03, -03, -01,
-01, -01,
];
}

View File

@ -58,15 +58,24 @@ public static class TechnicalMachine9a
public static int SetAllTechnicalMachines(SAV9ZA sav, bool collected = false)
{
int ctr = 0;
var field = sav.Blocks.FieldItems;
var inv = sav.Items;
var finalQuantity = collected ? 1 : 0;
foreach (var item in TechnicalMachines)
{
var hash = FnvHash.HashFnv1a_64(item.FieldItem);
var index = sav.Blocks.FieldItems.GetIndex(hash);
var index = field.GetIndex(hash);
if (index == -1)
continue; // Shouldn't happen. All TMs should be populated in a save file, except if it's a DLC TM (not applicable).
sav.Blocks.FieldItems.SetValue(index, collected);
sav.Items.SetItemQuantity(item.ItemID, collected ? 1 : 0);
ctr++;
if (field.GetValue(index) != collected)
field.SetValue(index, collected);
else
ctr--;
if (inv.GetItemQuantity(item.ItemID) != finalQuantity)
inv.SetItemQuantity(item.ItemID, finalQuantity);
}
return ctr;
}