mirror of
https://github.com/kwsch/PKHeX
synced 2024-12-18 00:13:10 +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
385 lines
17 KiB
C#
385 lines
17 KiB
C#
using System.Collections.Generic;
|
|
using static PKHeX.Core.LegalityCheckStrings;
|
|
using static PKHeX.Core.Ball;
|
|
|
|
namespace PKHeX.Core
|
|
{
|
|
/// <summary>
|
|
/// Verifies the <see cref="PKM.Ball"/> value.
|
|
/// </summary>
|
|
public sealed class BallVerifier : Verifier
|
|
{
|
|
protected override CheckIdentifier Identifier => CheckIdentifier.Ball;
|
|
private CheckResult NONE => GetInvalid(LBallNone);
|
|
|
|
public override void Verify(LegalityAnalysis data)
|
|
{
|
|
if (data.pkm.Format <= 2)
|
|
return; // no ball info saved
|
|
var result = VerifyBall(data);
|
|
data.AddLine(result);
|
|
}
|
|
|
|
private CheckResult VerifyBall(LegalityAnalysis data)
|
|
{
|
|
var EncounterMatch = data.EncounterMatch;
|
|
var Info = data.Info;
|
|
|
|
// Fixed ball cases -- can be only one ball ever
|
|
switch (EncounterMatch)
|
|
{
|
|
case MysteryGift g:
|
|
return VerifyBallMysteryGift(data, g);
|
|
case EncounterTrade t:
|
|
return VerifyBallEquals(data, t.Ball);
|
|
case EncounterStatic s when s.Gift:
|
|
return VerifyBallEquals(data, s.Ball);
|
|
}
|
|
|
|
// Capture / Inherit cases -- can be one of many balls
|
|
var pkm = data.pkm;
|
|
if (pkm.Species == (int)Species.Shedinja && data.EncounterMatch.Species != (int)Species.Shedinja) // Shedinja. For gen3, copy the ball from Nincada
|
|
{
|
|
// Only Gen3 origin Shedinja can copy the wild ball.
|
|
// Evolution chains will indicate if it could have existed as Shedinja in Gen3.
|
|
// The special move verifier has a similar check!
|
|
if (Info.Generation != 3 || Info.EvoChainsAllGens[3].Count != 2)
|
|
return VerifyBallEquals(data, (int)Poke); // Pokeball Only
|
|
}
|
|
|
|
if (pkm.Ball == (int)Heavy && Legal.AlolanCaptureNoHeavyBall.Contains(EncounterMatch.Species) && !EncounterMatch.EggEncounter && pkm.SM)
|
|
return GetInvalid(LBallHeavy); // Heavy Ball, can inherit if from egg (USUM fixed catch rate calc)
|
|
|
|
return EncounterMatch switch
|
|
{
|
|
EncounterStatic e => VerifyBallStatic(data, e),
|
|
EncounterSlot w => VerifyBallWild(data, w),
|
|
EncounterEgg _ => VerifyBallEgg(data),
|
|
EncounterInvalid _ => VerifyBallEquals(data, pkm.Ball), // ignore ball, pass whatever
|
|
_ => VerifyBallEquals(data, (int)Poke)
|
|
};
|
|
}
|
|
|
|
private CheckResult VerifyBallMysteryGift(LegalityAnalysis data, MysteryGift g)
|
|
{
|
|
if (g.Generation == 4 && g.Species == (int)Species.Manaphy && g.Ball == 0) // there is no ball data in Manaphy Mystery Gift from Gen4
|
|
return VerifyBallEquals(data, (int)Poke); // Pokeball
|
|
return VerifyBallEquals(data, g.Ball);
|
|
}
|
|
|
|
private CheckResult VerifyBallStatic(LegalityAnalysis data, EncounterStatic s)
|
|
{
|
|
if (s.Location == 75 && s.Generation == 5) // Entree Forest (Dream World)
|
|
return VerifyBallEquals(data, Legal.DreamWorldBalls);
|
|
return VerifyBallEquals(data, Legal.GetWildBalls(data.Info.Generation, data.Info.Game));
|
|
}
|
|
|
|
private static Ball GetRequiredBallValue(int gen, int loc)
|
|
{
|
|
return gen switch
|
|
{
|
|
// For Gen3 Safari Zones, we've already deferred partial match encounters.
|
|
3 when Locations.IsSafariZoneLocation3(loc) => Safari,
|
|
|
|
// For Gen4 Safari Zones and BCC, we've already deferred partial match encounters.
|
|
4 when Locations.IsSafariZoneLocation4(loc) => Safari,
|
|
4 when Locations.BugCatchingContest4 == loc => Sport,
|
|
|
|
// Poké Pelago
|
|
7 when loc == 30016 => Poke,
|
|
|
|
_ => None,
|
|
};
|
|
}
|
|
|
|
private CheckResult VerifyBallWild(LegalityAnalysis data, EncounterSlot w)
|
|
{
|
|
var req = BallExtensions.GetRequiredBallValueWild(w.Generation, w.Location);
|
|
if (req != None)
|
|
return VerifyBallEquals(data, (int) req);
|
|
|
|
return VerifyBallEquals(data, Legal.GetWildBalls(data.Info.Generation, data.Info.Game));
|
|
}
|
|
|
|
private CheckResult VerifyBallEgg(LegalityAnalysis data)
|
|
{
|
|
var pkm = data.pkm;
|
|
if (data.Info.Generation < 6) // No inheriting Balls
|
|
return VerifyBallEquals(data, (int)Poke); // Must be Pokéball -- no ball inheritance.
|
|
|
|
return pkm.Ball switch
|
|
{
|
|
(int)Poke => GetValid(LBallEnc), // Poké Ball
|
|
(int)Master => GetInvalid(LBallEggMaster), // Master Ball
|
|
(int)Cherish => GetInvalid(LBallEggCherish), // Cherish Ball
|
|
_ => VerifyBallInherited(data)
|
|
};
|
|
}
|
|
|
|
private CheckResult VerifyBallInherited(LegalityAnalysis data)
|
|
{
|
|
return data.Info.Generation switch
|
|
{
|
|
6 => VerifyBallEggGen6(data), // Gen6 Inheritance Rules
|
|
7 => VerifyBallEggGen7(data), // Gen7 Inheritance Rules
|
|
8 => VerifyBallEggGen8(data),
|
|
_ => NONE
|
|
};
|
|
}
|
|
|
|
private CheckResult VerifyBallEggGen6(LegalityAnalysis data)
|
|
{
|
|
var pkm = data.pkm;
|
|
int species = data.EncounterMatch.Species;
|
|
if (pkm.Gender == 2 || Legal.BreedMaleOnly.Contains(species)) // Genderless
|
|
return VerifyBallEquals(data, (int)Poke); // Must be Pokéball as ball can only pass via mother (not Ditto!)
|
|
|
|
Ball ball = (Ball)pkm.Ball;
|
|
|
|
if (ball == Safari) // Safari Ball
|
|
{
|
|
if (!Legal.Inherit_Safari.Contains(species))
|
|
return GetInvalid(LBallSpecies);
|
|
if (pkm.AbilityNumber == 4)
|
|
return GetInvalid(LBallAbility);
|
|
return GetValid(LBallSpeciesPass);
|
|
}
|
|
if (ball.IsApricornBall()) // Apricorn Ball
|
|
{
|
|
if (!Legal.Inherit_Apricorn6.Contains(species))
|
|
return GetInvalid(LBallSpecies);
|
|
if (pkm.AbilityNumber == 4)
|
|
return GetInvalid(LBallAbility);
|
|
return GetValid(LBallSpeciesPass);
|
|
}
|
|
if (ball == Sport) // Sport Ball
|
|
{
|
|
if (!Legal.Inherit_Sport.Contains(species))
|
|
return GetInvalid(LBallSpecies);
|
|
if (pkm.AbilityNumber == 4)
|
|
return GetInvalid(LBallAbility);
|
|
return GetValid(LBallSpeciesPass);
|
|
}
|
|
if (ball == Dream) // Dream Ball
|
|
{
|
|
if (pkm.AbilityNumber == 4 && Legal.Ban_DreamHidden.Contains(species))
|
|
return GetInvalid(LBallAbility);
|
|
if (Legal.Inherit_Dream.Contains(species))
|
|
return GetValid(LBallSpeciesPass);
|
|
return GetInvalid(LBallSpecies);
|
|
}
|
|
if (Dusk <= ball && ball <= Quick) // Dusk Heal Quick
|
|
{
|
|
if (!Legal.Ban_Gen4Ball_6.Contains(species))
|
|
return GetValid(LBallSpeciesPass);
|
|
return GetInvalid(LBallSpecies);
|
|
}
|
|
if (Ultra <= ball && ball <= Premier) // Don't worry, Safari was already checked.
|
|
{
|
|
if (Legal.Ban_Gen3Ball.Contains(species))
|
|
return GetInvalid(LBallSpecies);
|
|
if (pkm.AbilityNumber == 4 && Legal.Ban_Gen3BallHidden.Contains(pkm.SpecForm))
|
|
return GetInvalid(LBallAbility);
|
|
return GetValid(LBallSpeciesPass);
|
|
}
|
|
|
|
if (species > 650 && species != 700) // Sylveon
|
|
{
|
|
if (Legal.WildPokeballs6.Contains(pkm.Ball))
|
|
return GetValid(LBallSpeciesPass);
|
|
return GetInvalid(LBallSpecies);
|
|
}
|
|
|
|
if (ball >= Dream)
|
|
return GetInvalid(LBallUnavailable);
|
|
|
|
return NONE;
|
|
}
|
|
|
|
private CheckResult VerifyBallEggGen7(LegalityAnalysis data)
|
|
{
|
|
var pkm = data.pkm;
|
|
int species = data.EncounterMatch.Species;
|
|
if (722 <= species && species <= 730) // G7 Starters
|
|
return VerifyBallEquals(data, (int)Poke);
|
|
|
|
Ball ball = (Ball)pkm.Ball;
|
|
|
|
if (ball == Safari)
|
|
{
|
|
if (!(Legal.Inherit_Safari.Contains(species) || Legal.Inherit_SafariMale.Contains(species)))
|
|
return GetInvalid(LBallSpecies);
|
|
if (pkm.AbilityNumber == 4 && Legal.Ban_SafariBallHidden_7.Contains(species))
|
|
return GetInvalid(LBallAbility);
|
|
return GetValid(LBallSpeciesPass);
|
|
}
|
|
if (ball.IsApricornBall()) // Apricorn Ball
|
|
{
|
|
if (!Legal.Inherit_Apricorn7.Contains(species))
|
|
return GetInvalid(LBallSpecies);
|
|
if (pkm.AbilityNumber == 4 && Legal.Ban_NoHidden7Apricorn.Contains(species | pkm.AltForm << 11)) // lineage is 3->2->origin
|
|
return GetInvalid(LBallAbility);
|
|
return GetValid(LBallSpeciesPass);
|
|
}
|
|
if (ball == Sport) // Sport Ball
|
|
{
|
|
if (!Legal.Inherit_Sport.Contains(species))
|
|
return GetInvalid(LBallSpecies);
|
|
if (pkm.AbilityNumber == 4 && (species == (int)Species.Volbeat || species == (int)Species.Illumise)) // Volbeat/Illumise
|
|
return GetInvalid(LBallAbility);
|
|
return GetValid(LBallSpeciesPass);
|
|
}
|
|
if (ball == Dream) // Dream Ball
|
|
{
|
|
if (Legal.Inherit_Dream.Contains(species) || Legal.Inherit_DreamMale.Contains(species))
|
|
return GetValid(LBallSpeciesPass);
|
|
return GetInvalid(LBallSpecies);
|
|
}
|
|
if (Dusk <= ball && ball <= Quick) // Dusk Heal Quick
|
|
{
|
|
if (!Legal.Ban_Gen4Ball_7.Contains(species))
|
|
return GetValid(LBallSpeciesPass);
|
|
return GetInvalid(LBallSpecies);
|
|
}
|
|
if (Ultra <= ball && ball <= Premier) // Don't worry, Safari was already checked.
|
|
{
|
|
if (!Legal.Ban_Gen3Ball_7.Contains(species))
|
|
return GetValid(LBallSpeciesPass);
|
|
return GetInvalid(LBallSpecies);
|
|
}
|
|
|
|
if (ball == Beast)
|
|
{
|
|
if (species == (int)Species.Flabébé && pkm.AltForm == 3 && pkm.AbilityNumber == 4)
|
|
return GetInvalid(LBallAbility); // Can't obtain Flabébé-Blue with Hidden Ability in wild
|
|
if (species == (int)Species.Voltorb && pkm.AbilityNumber == 4)
|
|
return GetInvalid(LBallAbility); // Can't obtain with Hidden Ability in wild (can only breed with Ditto)
|
|
if (((int)Species.Pikipek <= species && species <= (int)Species.Kommoo) || (Legal.AlolanCaptureOffspring.Contains(species) && !Legal.PastGenAlolanNativesUncapturable.Contains(species)))
|
|
return GetValid(LBallSpeciesPass);
|
|
if (Legal.PastGenAlolanScans.Contains(species))
|
|
return GetValid(LBallSpeciesPass);
|
|
// next statement catches all new alolans
|
|
}
|
|
|
|
if (species > (int)Species.Volcanion)
|
|
return VerifyBallEquals(data, Legal.WildPokeballs7);
|
|
|
|
if (ball > Beast)
|
|
return GetInvalid(LBallUnavailable);
|
|
|
|
return NONE;
|
|
}
|
|
|
|
private CheckResult VerifyBallEggGen8(LegalityAnalysis data)
|
|
{
|
|
var pkm = data.pkm;
|
|
int species = data.EncounterMatch.Species;
|
|
if (722 <= species && species <= 730) // G7 Starters
|
|
return VerifyBallEquals(data, (int)Poke);
|
|
if ((int)Species.Grookey <= species && species <= (int)Species.Inteleon) // G8 Starters
|
|
return VerifyBallEquals(data, (int)Poke);
|
|
|
|
if (IsGalarCatchAndBreed(species))
|
|
{
|
|
if (Legal.WildPokeballs8.Contains(pkm.Ball))
|
|
return GetValid(LBallSpeciesPass);
|
|
if (species >= (int)Species.Grookey)
|
|
return GetInvalid(LBallSpecies);
|
|
}
|
|
|
|
Ball ball = (Ball)pkm.Ball;
|
|
|
|
if (ball == Safari)
|
|
{
|
|
if (!(Legal.Inherit_Safari.Contains(species) || Legal.Inherit_SafariMale.Contains(species)))
|
|
return GetInvalid(LBallSpecies);
|
|
if (pkm.AbilityNumber == 4 && Legal.Ban_SafariBallHidden_7.Contains(species))
|
|
return GetInvalid(LBallAbility);
|
|
return GetValid(LBallSpeciesPass);
|
|
}
|
|
if (ball.IsApricornBall()) // Apricorn Ball
|
|
{
|
|
if (!Legal.Inherit_Apricorn7.Contains(species))
|
|
return GetInvalid(LBallSpecies);
|
|
if (pkm.AbilityNumber == 4 && Legal.Ban_NoHidden8Apricorn.Contains(species | pkm.AltForm << 11)) // lineage is 3->2->origin
|
|
return GetInvalid(LBallAbility);
|
|
return GetValid(LBallSpeciesPass);
|
|
}
|
|
if (ball == Sport) // Sport Ball
|
|
{
|
|
if (!Legal.Inherit_Sport.Contains(species))
|
|
return GetInvalid(LBallSpecies);
|
|
if (pkm.AbilityNumber == 4 && (species == (int)Species.Volbeat || species == (int)Species.Illumise)) // Volbeat/Illumise
|
|
return GetInvalid(LBallAbility);
|
|
return GetValid(LBallSpeciesPass);
|
|
}
|
|
if (ball == Dream) // Dream Ball
|
|
{
|
|
if (Legal.Inherit_Dream.Contains(species) || Legal.Inherit_DreamMale.Contains(species))
|
|
return GetValid(LBallSpeciesPass);
|
|
return GetInvalid(LBallSpecies);
|
|
}
|
|
if (Dusk <= ball && ball <= Quick) // Dusk Heal Quick
|
|
{
|
|
if (!Legal.Ban_Gen4Ball_7.Contains(species))
|
|
return GetValid(LBallSpeciesPass);
|
|
return GetInvalid(LBallSpecies);
|
|
}
|
|
if (Ultra <= ball && ball <= Premier) // Don't worry, Safari was already checked.
|
|
{
|
|
if (!Legal.Ban_Gen3Ball_7.Contains(species))
|
|
return GetValid(LBallSpeciesPass);
|
|
return GetInvalid(LBallSpecies);
|
|
}
|
|
|
|
if (ball == Beast)
|
|
{
|
|
if (species == (int)Species.Flabébé && pkm.AltForm == 3 && pkm.AbilityNumber == 4)
|
|
return GetInvalid(LBallAbility); // Can't obtain Flabébé-Blue with Hidden Ability in wild
|
|
if (((int)Species.Pikipek <= species && species <= (int)Species.Kommoo) || (Legal.AlolanCaptureOffspring.Contains(species) && !Legal.PastGenAlolanNativesUncapturable.Contains(species)))
|
|
return GetValid(LBallSpeciesPass);
|
|
if (Legal.PastGenAlolanScans.Contains(species))
|
|
return GetValid(LBallSpeciesPass);
|
|
// next statement catches all new alolans
|
|
}
|
|
|
|
if (species > Legal.MaxSpeciesID_7_USUM)
|
|
return VerifyBallEquals(data, Legal.WildPokeballs8);
|
|
|
|
if (species > (int)Species.Volcanion)
|
|
return VerifyBallEquals(data, Legal.WildPokeballs7);
|
|
|
|
if (ball > Beast)
|
|
return GetInvalid(LBallUnavailable);
|
|
|
|
return NONE;
|
|
}
|
|
|
|
public static bool IsGalarCatchAndBreed(int species)
|
|
{
|
|
if ((int)Species.Grookey <= species && species <= (int)Species.Inteleon) // starter
|
|
return false;
|
|
var pt = PersonalTable.SWSH;
|
|
var pi = ((PersonalInfoSWSH) pt.GetFormeEntry(species, 0));
|
|
bool galar = pi.PokeDexIndex != 0;
|
|
if (galar)
|
|
return true;
|
|
var armor = pi.ArmorDexIndex != 0;
|
|
if (armor)
|
|
{
|
|
if (722 <= species && species <= 730) // G7 Starters
|
|
return false;
|
|
if ((int) Species.Porygon == species)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private CheckResult VerifyBallEquals(LegalityAnalysis data, int ball) => GetResult(ball == data.pkm.Ball);
|
|
private CheckResult VerifyBallEquals(LegalityAnalysis data, HashSet<int> balls) => GetResult(balls.Contains(data.pkm.Ball));
|
|
private CheckResult VerifyBallEquals(LegalityAnalysis data, ICollection<int> balls) => GetResult(balls.Contains(data.pkm.Ball));
|
|
private CheckResult GetResult(bool valid) => valid ? GetValid(LBallEnc) : GetInvalid(LBallEncMismatch);
|
|
}
|
|
}
|