mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-23 20:43:07 +00:00
6ee7a8724b
* Rework gen1 slot loading Slot templates are precomputed from ROM data and just loaded straight in, with tight coupling to the encounter area (grouped by slot types). * Revise fuzzy met check for underleveled wild evos Example: Level 23 poliwhirl in RBY as a level 50 poliwhirl, will assume the chain is 25-50 for poliwhirl (as poliwag evolves at 25). Instead of revising the origin chain, just ignore the evo min level in the comparison. Previous commit fixed it for gen1. * Rework gen2-4 slot loading Gen4 not finished, Type Encounter data and some edge encounters not recognizing yet... * Add feebas slots for old/good encounters * Begin moving properties Great news! Gen5-7 need to be de-dumbed like Gen1-4. Then I can remove the bang (!) on the Area accessor and ensure that it's never null! * Split off XD pokespot slot encounter table type * Set area in constructor * Deduplicate g3 roaming encounters * Deduplicate xd encounter locations (rebattle) Only difference is met location; no need to create 500 extra encounter objects. A simple contains check is ok (rarely in gen3 format). * Make all slots have a readonly reference to their parent area * Minor clean * Remove "Safari" slot type flag Can be determined via other means (generation-location), allows us to reduce the size of SlotType member to a byte Output of slot binaries didn't preserve the Safari flag anyway. * Update SlotType.cs * Handle type encounters correctly * Merge safari area into regular xy area * Merge dexnav accessor logic * fix some logic so that tests pass again rearrange g5 dw init to be done outside of static constructor (initializer instead) PIDGenerator: friend safari slots now generate with required flawless IV count * Add cianwood tentacool gift encounter * Remove unnecessary abstractions Fake area just returned a slot; since Slots have a non-null reference to the area, we can just return the slot and use the API to grab a list of possible slots for the chain. Increase restrictiveness of location/type get-set operations * Minor tweaks, pass parameters DexNav observed state isn't necessary to use, only need to see if it's possible to dexnav. Now that we have metadata for slots, we can. * Remove unused legality tables
372 lines
16 KiB
C#
372 lines
16 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using static PKHeX.Core.LegalityCheckStrings;
|
|
|
|
namespace PKHeX.Core
|
|
{
|
|
/// <summary>
|
|
/// Verifies the <see cref="PKM.AltForm"/> value.
|
|
/// </summary>
|
|
public sealed class FormVerifier : Verifier
|
|
{
|
|
protected override CheckIdentifier Identifier => CheckIdentifier.Form;
|
|
|
|
public override void Verify(LegalityAnalysis data)
|
|
{
|
|
var pkm = data.pkm;
|
|
if (pkm.Format < 4)
|
|
return; // no forms exist
|
|
var result = VerifyForm(data);
|
|
data.AddLine(result);
|
|
|
|
if (pkm is IFormArgument f)
|
|
data.AddLine(VerifyFormArgument(data, f.FormArgument));
|
|
}
|
|
|
|
private CheckResult VALID => GetValid(LFormValid);
|
|
|
|
private CheckResult VerifyForm(LegalityAnalysis data)
|
|
{
|
|
var pkm = data.pkm;
|
|
var PersonalInfo = data.PersonalInfo;
|
|
|
|
int count = PersonalInfo.FormeCount;
|
|
var form = pkm.AltForm;
|
|
if (count <= 1 && form == 0)
|
|
return VALID; // no forms to check
|
|
|
|
var species = pkm.Species;
|
|
var EncounterMatch = data.EncounterMatch;
|
|
var Info = data.Info;
|
|
|
|
if (!PersonalInfo.IsFormeWithinRange(form) && !FormConverter.IsValidOutOfBoundsForme(species, form, Info.Generation))
|
|
return GetInvalid(string.Format(LFormInvalidRange, count - 1, form));
|
|
|
|
if (EncounterMatch is EncounterSlot w && w.Area.Type == SlotType.FriendSafari)
|
|
{
|
|
VerifyFormFriendSafari(data);
|
|
}
|
|
else if (EncounterMatch is EncounterEgg)
|
|
{
|
|
if (FormConverter.IsTotemForm(species, form, data.Info.Generation))
|
|
return GetInvalid(LFormInvalidGame);
|
|
}
|
|
|
|
switch (species)
|
|
{
|
|
case (int)Species.Pikachu when Info.Generation == 6: // Cosplay
|
|
bool isStatic = EncounterMatch is EncounterStatic;
|
|
if (isStatic != (form != 0))
|
|
return GetInvalid(isStatic ? LFormPikachuCosplayInvalid : LFormPikachuCosplay);
|
|
break;
|
|
|
|
case (int)Species.Pikachu when Info.Generation >= 7: // Cap
|
|
bool IsValidPikachuCap()
|
|
{
|
|
return EncounterMatch switch
|
|
{
|
|
WC7 wc7 => (wc7.Form == form),
|
|
WC8 wc => (wc.Form == form),
|
|
EncounterStatic s => (s.Form == form),
|
|
_ => (form == 0)
|
|
};
|
|
}
|
|
|
|
if (!IsValidPikachuCap())
|
|
{
|
|
bool gift = EncounterMatch is MysteryGift g && g.Form != form;
|
|
var msg = gift ? LFormPikachuEventInvalid : LFormInvalidGame;
|
|
return GetInvalid(msg);
|
|
}
|
|
break;
|
|
case (int)Species.Unown when Info.Generation == 2 && form >= 26:
|
|
return GetInvalid(string.Format(LFormInvalidRange, "Z", form == 26 ? "!" : "?"));
|
|
case (int)Species.Giratina when form == 1 ^ pkm.HeldItem == 112: // Giratina, Origin form only with Griseous Orb
|
|
return GetInvalid(LFormItemInvalid);
|
|
|
|
case (int)Species.Arceus:
|
|
{
|
|
int arceus = GetArceusFormFromHeldItem(pkm.HeldItem, pkm.Format);
|
|
return arceus != form ? GetInvalid(LFormItemInvalid) : GetValid(LFormItem);
|
|
}
|
|
case (int)Species.Keldeo:
|
|
{
|
|
// can mismatch in gen5 via BW tutor and transfer up
|
|
// can mismatch in gen8+ as the form activates in battle when knowing the move; outside of battle can be either state.
|
|
if (Info.Generation == 5 || pkm.Format >= 8)
|
|
break;
|
|
int index = Array.IndexOf(pkm.Moves, 548); // Secret Sword
|
|
bool noSword = index < 0;
|
|
if (form == 0 ^ noSword) // mismatch
|
|
Info.Moves[noSword ? 0 : index] = new CheckMoveResult(Info.Moves[noSword ? 0 : index], Severity.Invalid, LMoveKeldeoMismatch, CheckIdentifier.Move);
|
|
break;
|
|
}
|
|
case (int)Species.Genesect:
|
|
{
|
|
int genesect = GetGenesectFormFromHeldItem(pkm.HeldItem);
|
|
return genesect != form ? GetInvalid(LFormItemInvalid) : GetValid(LFormItem);
|
|
}
|
|
case (int)Species.Greninja:
|
|
if (form > 1) // Ash Battle Bond active
|
|
return GetInvalid(LFormBattle);
|
|
if (form != 0 && !(EncounterMatch is MysteryGift)) // Formes are not breedable, MysteryGift already checked
|
|
return GetInvalid(string.Format(LFormInvalidRange, 0, form));
|
|
break;
|
|
|
|
case (int)Species.Scatterbug:
|
|
case (int)Species.Spewpa:
|
|
if (form > 17) // Fancy & Pokéball
|
|
return GetInvalid(LFormVivillonEventPre);
|
|
if (!(pkm is IGeoTrack tr))
|
|
break;
|
|
if (!Legal.CheckVivillonPattern(form, (byte)tr.Country, (byte)tr.Region))
|
|
data.AddLine(Get(LFormVivillonInvalid, Severity.Fishy));
|
|
break;
|
|
case (int)Species.Vivillon:
|
|
if (form > 17) // Fancy & Pokéball
|
|
{
|
|
if (!(EncounterMatch is MysteryGift))
|
|
return GetInvalid(LFormVivillonInvalid);
|
|
return GetValid(LFormVivillon);
|
|
}
|
|
if (!(pkm is IGeoTrack trv))
|
|
break;
|
|
if (!Legal.CheckVivillonPattern(form, (byte)trv.Country, (byte)trv.Region))
|
|
data.AddLine(Get(LFormVivillonInvalid, Severity.Fishy));
|
|
break;
|
|
|
|
case (int)Species.Floette when form == 5: // Floette Eternal Flower -- Never Released
|
|
if (!(EncounterMatch is MysteryGift))
|
|
return GetInvalid(LFormEternalInvalid);
|
|
return GetValid(LFormEternal);
|
|
case (int)Species.Meowstic when form != pkm.Gender:
|
|
return GetInvalid(LGenderInvalidNone);
|
|
|
|
case (int)Species.Silvally:
|
|
{
|
|
int silvally = GetSilvallyFormFromHeldItem(pkm.HeldItem);
|
|
return silvally != form ? GetInvalid(LFormItemInvalid) : GetValid(LFormItem);
|
|
}
|
|
|
|
case (int)Species.Lillipup when Info.EncounterMatch.EggEncounter && form == 1 && pkm.SM:
|
|
case (int)Species.Lycanroc when Info.EncounterMatch.EggEncounter && form == 2 && pkm.SM:
|
|
return GetInvalid(LFormInvalidGame);
|
|
|
|
// Toxel encounters have already been checked for the nature-specific evolution criteria.
|
|
case (int)Species.Toxtricity when Info.EncounterMatch.Species == (int)Species.Toxtricity:
|
|
{
|
|
// The game enforces the Nature for Toxtricity encounters too!
|
|
if (pkm.AltForm != EvolutionMethod.GetAmpLowKeyResult(pkm.Nature))
|
|
return GetInvalid(LFormInvalidNature);
|
|
break;
|
|
}
|
|
|
|
// Impossible Egg forms
|
|
case (int)Species.Rotom when pkm.IsEgg && form != 0:
|
|
case (int)Species.Furfrou when pkm.IsEgg && form != 0:
|
|
return GetInvalid(LEggSpecies);
|
|
|
|
// Party Only Forms
|
|
case (int)Species.Shaymin:
|
|
case (int)Species.Furfrou:
|
|
case (int)Species.Hoopa:
|
|
if (form != 0 && pkm.Box > -1 && pkm.Format <= 6) // has form but stored in box
|
|
return GetInvalid(LFormParty);
|
|
break;
|
|
|
|
// Battle only Forms with other legal forms allowed
|
|
case (int)Species.Zygarde when form >= 4: // Zygarde Complete
|
|
case (int)Species.Minior when form < 7: // Minior Shield
|
|
case (int)Species.Necrozma when form == 3: // Ultra Necrozma
|
|
return GetInvalid(LFormBattle);
|
|
case (int)Species.Necrozma when form < 3: // Necrozma Fused forms & default
|
|
case (int)Species.Mimikyu when form == 2: // Totem disguise Mimikyu
|
|
return VALID;
|
|
}
|
|
|
|
if (form == 0)
|
|
return VALID;
|
|
|
|
// everything below here is currently an altform
|
|
var format = pkm.Format;
|
|
if (format >= 7 && Info.Generation < 7)
|
|
{
|
|
if (species == 25 || Legal.AlolanOriginForms.Contains(species) || Legal.AlolanVariantEvolutions12.Contains(data.EncounterOriginal.Species))
|
|
return GetInvalid(LFormInvalidGame);
|
|
}
|
|
if (format >= 8 && Info.Generation < 8)
|
|
{
|
|
var orig = data.EncounterOriginal.Species;
|
|
if (Legal.GalarOriginForms.Contains(species) || Legal.GalarVariantFormEvolutions.Contains(orig))
|
|
{
|
|
if (species == (int)Species.Meowth && data.EncounterOriginal.Form != 2)
|
|
{
|
|
// We're okay here. There's also Alolan Meowth...
|
|
}
|
|
else if ((orig == (int) Species.MrMime || orig == (int)Species.MimeJr) && pkm.CurrentLevel > data.EncounterOriginal.LevelMin && Info.Generation >= 4)
|
|
{
|
|
// We're okay with a Mime Jr. that has evolved via level up.
|
|
}
|
|
else
|
|
{
|
|
return GetInvalid(LFormInvalidGame);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (IsBattleOnlyForm(species, form, format))
|
|
return GetInvalid(LFormBattle);
|
|
|
|
return VALID;
|
|
}
|
|
|
|
public static bool IsBattleOnlyForm(int species, int form, int format)
|
|
{
|
|
if (!BattleOnly.Contains(species))
|
|
return false;
|
|
if (species == (int) Species.Darmanitan && form == 2 && format >= 8)
|
|
return false; // this one is OK, Galarian non-Zen
|
|
if (species == (int) Species.Slowbro && form == 2 && format >= 8)
|
|
return false; // this one is OK, Galarian Slowbro (not a Mega)
|
|
if (species == (int) Species.Necrozma && form != 3)
|
|
return false; // Only mark Ultra Necrozma as Battle Only
|
|
return true;
|
|
}
|
|
|
|
public static int GetArceusFormFromHeldItem(int item, int format)
|
|
{
|
|
if (777 <= item && item <= 793)
|
|
return Array.IndexOf(Legal.Arceus_ZCrystal, (ushort)item) + 1;
|
|
|
|
int form = 0;
|
|
if ((298 <= item && item <= 313) || item == 644)
|
|
form = Array.IndexOf(Legal.Arceus_Plate, (ushort)item) + 1;
|
|
if (format == 4 && form >= 9)
|
|
return form + 1; // ??? type Form shifts everything by 1
|
|
return form;
|
|
}
|
|
|
|
public static int GetSilvallyFormFromHeldItem(int item)
|
|
{
|
|
if (904 <= item && item <= 920)
|
|
return item - 903;
|
|
return 0;
|
|
}
|
|
|
|
public static int GetGenesectFormFromHeldItem(int item)
|
|
{
|
|
if (116 <= item && item <= 119)
|
|
return item - 115;
|
|
return 0;
|
|
}
|
|
|
|
private static readonly HashSet<int> BattleOnly = GetBattleFormSet();
|
|
|
|
private static HashSet<int> GetBattleFormSet()
|
|
{
|
|
var hs = new HashSet<int>();
|
|
hs.UnionWith(Legal.BattleForms);
|
|
hs.UnionWith(Legal.BattleMegas);
|
|
hs.UnionWith(Legal.BattlePrimals);
|
|
return hs;
|
|
}
|
|
|
|
private static readonly HashSet<int> SafariFloette = new HashSet<int> { 0, 1, 3 }; // 0/1/3 - RBY
|
|
|
|
private void VerifyFormFriendSafari(LegalityAnalysis data)
|
|
{
|
|
var pkm = data.pkm;
|
|
switch (pkm.Species)
|
|
{
|
|
case (int)Species.Floette when !SafariFloette.Contains(pkm.AltForm): // Floette
|
|
case (int)Species.Florges when !SafariFloette.Contains(pkm.AltForm): // Florges
|
|
data.AddLine(GetInvalid(LFormSafariFlorgesColor));
|
|
break;
|
|
case 710 when pkm.AltForm != 0: // Pumpkaboo
|
|
case (int)Species.Gourgeist when pkm.AltForm != 0: // Average
|
|
data.AddLine(GetInvalid(LFormSafariPumpkabooAverage));
|
|
break;
|
|
case (int)Species.Gastrodon when pkm.AltForm != 0: // West
|
|
data.AddLine(GetInvalid(LFormSafariFlorgesColor));
|
|
break;
|
|
case (int)Species.Sawsbuck when pkm.AltForm != 0: // Sawsbuck
|
|
data.AddLine(GetInvalid(LFormSafariSawsbuckSpring));
|
|
break;
|
|
}
|
|
}
|
|
|
|
private CheckResult VerifyFormArgument(LegalityAnalysis data, in uint arg)
|
|
{
|
|
var pkm = data.pkm;
|
|
switch (data.pkm.Species)
|
|
{
|
|
default:
|
|
{
|
|
if (arg != 0)
|
|
return GetInvalid(LFormArgumentNotAllowed);
|
|
break;
|
|
}
|
|
case (int)Species.Furfrou:
|
|
{
|
|
if (arg > 5)
|
|
return GetInvalid(LFormArgumentHigh);
|
|
if (arg == 0 && pkm.AltForm != 0)
|
|
return GetInvalid(LFormArgumentNotAllowed);
|
|
if (arg != 0 && pkm.AltForm == 0)
|
|
return GetInvalid(LFormArgumentNotAllowed);
|
|
break;
|
|
}
|
|
case (int)Species.Hoopa:
|
|
{
|
|
if (arg > 3)
|
|
return GetInvalid(LFormArgumentHigh);
|
|
if (arg == 0 && pkm.AltForm != 0)
|
|
return GetInvalid(LFormArgumentNotAllowed);
|
|
if (arg != 0 && pkm.AltForm == 0)
|
|
return GetInvalid(LFormArgumentNotAllowed);
|
|
break;
|
|
}
|
|
case (int)Species.Yamask when pkm.AltForm == 1:
|
|
{
|
|
if (pkm.IsEgg)
|
|
{
|
|
if (arg != 0)
|
|
return GetInvalid(LFormArgumentNotAllowed);
|
|
}
|
|
break;
|
|
}
|
|
case (int)Species.Runerigus:
|
|
{
|
|
if (data.EncounterMatch.Species == (int)Species.Runerigus)
|
|
{
|
|
if (arg != 0)
|
|
return GetInvalid(LFormArgumentNotAllowed);
|
|
}
|
|
else
|
|
{
|
|
if (arg < 49)
|
|
return GetInvalid(LFormArgumentLow);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case (int)Species.Alcremie:
|
|
{
|
|
if (data.EncounterMatch.Species == (int)Species.Alcremie)
|
|
{
|
|
if (arg != 0)
|
|
return GetInvalid(LFormArgumentNotAllowed);
|
|
}
|
|
else
|
|
{
|
|
if (arg > (uint)AlcremieDecoration.Ribbon)
|
|
return GetInvalid(LFormArgumentHigh);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return GetValid(LFormArgumentValid);
|
|
}
|
|
}
|
|
}
|