PKHeX/PKHeX.Core/Legality/Verifiers/Ability/AbilityVerifier.cs

489 lines
20 KiB
C#
Raw Normal View History

using System.Collections.Generic;
using static PKHeX.Core.LegalityCheckStrings;
namespace PKHeX.Core
{
2018-07-02 02:17:37 +00:00
/// <summary>
/// Verifies the <see cref="PKM.Ability"/> values.
/// </summary>
public sealed class AbilityVerifier : Verifier
{
protected override CheckIdentifier Identifier => CheckIdentifier.Ability;
public override void Verify(LegalityAnalysis data)
{
var result = VerifyAbility(data);
data.AddLine(result);
}
private CheckResult VALID => GetValid(LAbilityFlag);
private CheckResult INVALID => GetInvalid(LAbilityMismatch);
private enum AbilityState : byte
{
CanMismatch,
MustMatch,
}
private CheckResult VerifyAbility(LegalityAnalysis data)
{
var pkm = data.pkm;
var pi = data.PersonalInfo;
// Check ability is possible (within bounds)
int ability = pkm.Ability;
int abilIndex = pi.GetAbilityIndex(ability);
if (abilIndex < 0)
return GetInvalid(LAbilityUnexpected);
var abilities = pi.Abilities;
int format = pkm.Format;
if (format >= 6)
{
var num = pkm.AbilityNumber;
2021-07-30 23:22:10 +00:00
if (!IsValidAbilityBits(num))
return INVALID;
// Check AbilityNumber points to ability
int an = num >> 1;
if (an >= abilities.Count || abilities[an] != ability)
return INVALID;
2020-11-28 04:44:44 +00:00
// Check AbilityNumber for transfers without unique abilities
int gen = data.Info.Generation;
if (gen is 3 or 4 or 5 && num != 4)
2020-11-28 04:44:44 +00:00
{
// To determine AbilityNumber [PK5->PK6], check if the first ability in Personal matches the ability.
// It is not possible to flip it to the other index as capsule requires unique abilities.
if (abilities[0] == abilities[1] && num != 1)
2021-02-04 06:57:23 +00:00
{
// Check if any pre-evolution could have it flipped.
var evos = data.Info.EvoChainsAllGens[6];
var pt = GameData.GetPersonal(GameUtil.GetVersion(format));
if (!GetWasDual(evos, pt, pkm))
return INVALID;
2021-02-04 06:57:23 +00:00
}
2020-11-28 04:44:44 +00:00
}
}
if (format >= 8) // Ability Patch
{
if (pkm.AbilityNumber == 4)
{
if (CanAbilityPatch(format, abilities, pkm.Species))
return GetValid(LAbilityPatchUsed);
var e = data.EncounterOriginal;
if (e.Species != pkm.Species && CanAbilityPatch(format, PKX.Personal.GetFormEntry(e.Species, e.Form).Abilities, e.Species))
return GetValid(LAbilityPatchUsed);
// Verify later, it may be encountered with its hidden ability without using an ability patch.
}
}
var enc = data.EncounterMatch;
if (enc is MysteryGift {Generation: >= 4} g)
Offload EncounterSlot loading logic to reduce complexity (#2980) * 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
2020-08-30 17:23:22 +00:00
return VerifyAbilityMG(data, g, abilities);
if (format < 6)
return VerifyAbility345(data, enc, abilities, abilIndex);
return VerifyAbility(data, abilities, abilIndex);
}
2021-07-30 23:22:10 +00:00
public static bool IsValidAbilityBits(int num) => num is 1 or 2 or 4;
private static bool GetWasDual(IReadOnlyList<EvoCriteria> evos, PersonalTable pt, ISpeciesForm pk)
{
2021-05-18 20:29:55 +00:00
foreach (var evo in evos)
{
if (evo.Species == pk.Species)
continue;
var pe = pt.GetFormEntry(evo.Species, evo.Form);
var abils = pe.Abilities;
if (CanAbilityCapsule(6, abils))
return true;
}
return false;
}
private CheckResult VerifyAbility(LegalityAnalysis data, IReadOnlyList<int> abilities, int abilIndex)
{
Offload EncounterSlot loading logic to reduce complexity (#2980) * 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
2020-08-30 17:23:22 +00:00
var enc = data.EncounterMatch;
var eabil = GetEncounterFixedAbilityNumber(enc);
if (eabil >= 0)
{
if ((data.pkm.AbilityNumber == 4) != (eabil == 4))
return GetInvalid(LAbilityHiddenFail);
if (eabil > 0)
return VerifyFixedAbility(data, abilities, AbilityState.CanMismatch, eabil, abilIndex);
}
var gen = enc.Generation;
return gen switch
{
Offload EncounterSlot loading logic to reduce complexity (#2980) * 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
2020-08-30 17:23:22 +00:00
5 => VerifyAbility5(data, enc, abilities),
6 => VerifyAbility6(data, enc),
7 => VerifyAbility7(data, enc),
>=8 => VALID,
2021-08-20 20:49:20 +00:00
_ => CheckMatch(data.pkm, abilities, gen, AbilityState.CanMismatch, enc),
};
}
private CheckResult VerifyAbility345(LegalityAnalysis data, IEncounterTemplate enc, IReadOnlyList<int> abilities, int abilIndex)
{
var pkm = data.pkm;
int format = pkm.Format;
var state = AbilityState.MustMatch;
if (format is (3 or 4 or 5) && abilities[0] != abilities[1]) // 3-4/5 and have 2 distinct abilities now
state = VerifyAbilityPreCapsule(data, abilities);
int encounterAbility = GetEncounterFixedAbilityNumber(enc);
if (encounterAbility >= 0)
{
if ((pkm.AbilityNumber == 4) != (encounterAbility == 4))
return GetInvalid(LAbilityHiddenFail);
if (encounterAbility > 0)
return VerifyFixedAbility(data, abilities, state, encounterAbility, abilIndex);
}
int gen = enc.Generation;
if (gen == 5)
Offload EncounterSlot loading logic to reduce complexity (#2980) * 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
2020-08-30 17:23:22 +00:00
return VerifyAbility5(data, enc, abilities);
return CheckMatch(pkm, abilities, gen, state, enc);
}
private CheckResult VerifyFixedAbility(LegalityAnalysis data, IReadOnlyList<int> abilities, AbilityState state, int encounterAbility, int abilIndex)
{
var pkm = data.pkm;
var enc = data.Info.EncounterMatch;
if (enc.Generation >= 6)
{
if (IsAbilityCapsuleModified(pkm, abilities, encounterAbility))
return GetValid(LAbilityCapsuleUsed);
if (pkm.AbilityNumber != encounterAbility)
return INVALID;
return VALID;
}
if ((pkm.AbilityNumber == 4) != (encounterAbility == 4))
return GetInvalid(LAbilityHiddenFail);
bool hasEvolved = enc.Species != pkm.Species;
if (hasEvolved && state != AbilityState.CanMismatch)
{
// Evolving in Gen3 does not mutate the ability bit, so any mismatched abilities will stay mismatched.
if (enc.Generation == 3)
{
if (encounterAbility == 1 << abilIndex)
return VALID;
// If it is in a future game and does not match the fixed ability, then it must match the PID.
if (pkm.Format != 3)
return GetPIDAbilityMatch(pkm, abilities);
// No way to un-mismatch it while existing solely on Gen3 games.
return INVALID;
}
return CheckMatch(pkm, abilities, enc.Generation, AbilityState.MustMatch, enc);
}
if (encounterAbility == 1 << abilIndex)
return VALID;
if (pkm.AbilityNumber == encounterAbility)
return VALID;
if (state == AbilityState.CanMismatch || encounterAbility == 0)
return CheckMatch(pkm, abilities, enc.Generation, AbilityState.MustMatch, enc);
if (IsAbilityCapsuleModified(pkm, abilities, encounterAbility))
return GetValid(LAbilityCapsuleUsed);
return INVALID;
}
private AbilityState VerifyAbilityPreCapsule(LegalityAnalysis data, IReadOnlyList<int> abilities)
{
var info = data.Info;
// Gen4/5 origin
if (info.Generation != 3)
return AbilityState.MustMatch;
// Gen3 origin... a lot of edge cases to check.
var pkm = data.pkm;
var format = pkm.Format;
// CXD pokemon can have any ability without matching PID
if (format == 3)
{
if (pkm.Version == (int)GameVersion.CXD)
return AbilityState.CanMismatch;
return AbilityState.MustMatch;
}
// Evovled in Gen4/5
if (pkm.Species > Legal.MaxSpeciesID_3)
return AbilityState.MustMatch;
// If the species could not exist in Gen3, must match.
var g3 = info.EvoChainsAllGens[3];
if (g3.Count == 0)
return AbilityState.MustMatch;
// Fall through when gen3 pkm transferred to gen4/5
var maxGen3Species = g3[0].Species;
return VerifyAbilityGen3Transfer(data, abilities, maxGen3Species);
}
private AbilityState VerifyAbilityGen3Transfer(LegalityAnalysis data, IReadOnlyList<int> abilities, int maxGen3Species)
{
var pkm = data.pkm;
var pers = (PersonalInfoG3)PersonalTable.E[maxGen3Species];
if (pers.Ability1 != pers.Ability2) // Excluding Colosseum/XD, a Gen3 pkm must match PID if it has 2 unique abilities
return pkm.Version == (int) GameVersion.CXD ? AbilityState.CanMismatch : AbilityState.MustMatch;
if (pkm.Species != maxGen3Species) // it has evolved in either gen 4 or gen 5; the ability must match PID
return AbilityState.MustMatch;
var chain = data.Info.EvoChainsAllGens;
bool evolved45 = chain[4].Count > 1 || (pkm.Format == 5 && chain[5].Count > 1);
if (evolved45)
{
if (pkm.Ability == pers.Ability1) // Could evolve in Gen4/5 and have a Gen3 only ability
return AbilityState.CanMismatch; // Not evolved in Gen4/5, doesn't need to match PIDAbility
if (pkm.Ability == abilities[1]) // It could evolve in Gen4/5 and have Gen4 second ability
return AbilityState.MustMatch; // Evolved in Gen4/5, must match PIDAbility
}
// If we reach here, it has not evolved in Gen4/5 games or has an invalid ability.
// The ability does not need to match the PIDAbility, but only Gen3 ability is allowed.
if (pkm.Ability != pers.Ability1) // Not evolved in Gen4/5, but doesn't have Gen3 only ability
data.AddLine(GetInvalid(LAbilityMismatch3)); // probably bad to do this here
return AbilityState.CanMismatch;
}
Offload EncounterSlot loading logic to reduce complexity (#2980) * 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
2020-08-30 17:23:22 +00:00
private CheckResult VerifyAbilityMG(LegalityAnalysis data, MysteryGift g, IReadOnlyList<int> abilities)
{
Offload EncounterSlot loading logic to reduce complexity (#2980) * 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
2020-08-30 17:23:22 +00:00
if (g is PCD d)
return VerifyAbilityPCD(data, abilities, d);
var pkm = data.pkm;
Offload EncounterSlot loading logic to reduce complexity (#2980) * 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
2020-08-30 17:23:22 +00:00
if (g is PGT) // Ranger Manaphy
return (pkm.Format >= 6 ? (pkm.AbilityNumber == 1) : (pkm.AbilityNumber < 4)) ? VALID : GetInvalid(LAbilityMismatchGift);
Offload EncounterSlot loading logic to reduce complexity (#2980) * 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
2020-08-30 17:23:22 +00:00
var cardType = g.AbilityType;
if (cardType == 4) // 1/2/H
return VALID;
Offload EncounterSlot loading logic to reduce complexity (#2980) * 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
2020-08-30 17:23:22 +00:00
int abilNumber = pkm.AbilityNumber;
if (cardType == 3) // 1/2
return abilNumber == 4 ? GetInvalid(LAbilityMismatchGift) : VALID;
// Only remaining matches are fixed index abilities
Offload EncounterSlot loading logic to reduce complexity (#2980) * 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
2020-08-30 17:23:22 +00:00
int cardAbilIndex = 1 << cardType;
if (abilNumber == cardAbilIndex)
return VALID;
// Can still match if the ability was changed via ability capsule...
// However, it can't change to/from Hidden Abilities.
Offload EncounterSlot loading logic to reduce complexity (#2980) * 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
2020-08-30 17:23:22 +00:00
if (abilNumber == 4 || cardType == 2)
return GetInvalid(LAbilityHiddenFail);
// Ability can be flipped 0/1 if Ability Capsule is available, is not Hidden Ability, and Abilities are different.
if (pkm.Format >= 6)
{
if (CanAbilityCapsule(6, abilities))
return GetValid(LAbilityCapsuleUsed);
// Maybe was evolved after using ability capsule.
var evos = data.Info.EvoChainsAllGens[pkm.Format];
if (GetWasDual(evos, PKX.Personal, pkm))
return GetValid(LAbilityCapsuleUsed);
}
return pkm.Format < 6 ? GetInvalid(LAbilityMismatchPID) : INVALID;
}
private CheckResult VerifyAbilityPCD(LegalityAnalysis data, IReadOnlyList<int> abilities, PCD pcd)
{
var pkm = data.pkm;
var format = pkm.Format;
if (format >= 6)
{
if (!CanAbilityCapsule(format, abilities))
{
// Gen3-5 transfer with same ability -> 1st ability that matches
if (pkm.AbilityNumber == 1)
return VALID;
return CheckMatch(pkm, abilities, 4, AbilityState.MustMatch, pcd); // evolved, must match
}
if (pkm.AbilityNumber < 4) // Ability Capsule can change between 1/2
return GetValid(LAbilityCapsuleUsed);
}
if (pcd.Species != pkm.Species)
return CheckMatch(pkm, abilities, 4, AbilityState.MustMatch, pcd); // evolved, must match
// Edge case (PID ability gift mismatch) -- must match gift ability.
return pkm.Ability == pcd.Gift.PK.Ability ? VALID : INVALID;
}
private CheckResult VerifyAbility5(LegalityAnalysis data, IEncounterTemplate enc, IReadOnlyList<int> abilities)
{
var pkm = data.pkm;
// Eggs and Encounter Slots are not yet checked for Hidden Ability potential.
return enc switch
{
EncounterSlot5 w when pkm.AbilityNumber == 4 != w.IsHiddenGrotto => GetInvalid(w.IsHiddenGrotto ? LAbilityMismatchGrotto : LAbilityHiddenFail),
EncounterEgg e when pkm.AbilityNumber == 4 && AbilityBreedLegality.BanHidden5.Contains(e.Species) => GetInvalid(LAbilityHiddenUnavailable),
2021-08-20 20:49:20 +00:00
_ => CheckMatch(data.pkm, abilities, 5, pkm.Format == 5 ? AbilityState.MustMatch : AbilityState.CanMismatch, enc),
};
}
private CheckResult VerifyAbility6(LegalityAnalysis data, IEncounterTemplate enc)
{
var pkm = data.pkm;
if (pkm.AbilityNumber != 4)
return VALID;
// Eggs and Encounter Slots are not yet checked for Hidden Ability potential.
return enc switch
{
EncounterSlot6XY {IsFriendSafari: true} => VALID,
EncounterSlot6XY {IsHorde: true} => VALID,
EncounterSlot6AO {IsHorde: true} => VALID,
EncounterSlot6AO {CanDexNav: true} => VALID,
EncounterSlot => GetInvalid(LAbilityMismatchHordeSafari),
EncounterEgg egg when AbilityBreedLegality.BanHidden6.Contains(egg.Species | (egg.Form << 11)) => GetInvalid(LAbilityHiddenUnavailable),
2021-08-20 20:49:20 +00:00
_ => VALID,
};
}
private CheckResult VerifyAbility7(LegalityAnalysis data, IEncounterTemplate enc)
{
var pkm = data.pkm;
if (pkm.AbilityNumber != 4)
return VALID;
return enc switch
{
EncounterSlot7 {IsSOS: false} => GetInvalid(LAbilityMismatchSOS),
EncounterEgg egg when AbilityBreedLegality.BanHidden7.Contains(egg.Species | (egg.Form << 11)) => GetInvalid(LAbilityHiddenUnavailable),
2021-08-20 20:49:20 +00:00
_ => VALID,
};
}
/// <summary>
/// Final checks assuming nothing else has flagged the ability.
/// </summary>
/// <param name="pkm">Pokémon</param>
/// <param name="abilities">Current abilities</param>
/// <param name="gen">Generation</param>
/// <param name="state">Permissive to allow ability to deviate under special circumstances</param>
/// <param name="enc">Encounter template the <see cref="pkm"/> was matched to.</param>
private CheckResult CheckMatch(PKM pkm, IReadOnlyList<int> abilities, int gen, AbilityState state, IEncounterTemplate enc)
{
if (gen is (3 or 4) && pkm.AbilityNumber == 4)
return GetInvalid(LAbilityHiddenUnavailable);
// other cases of hidden ability already flagged, all that is left is 1/2 mismatching
if (state != AbilityState.MustMatch)
return VALID;
// Check that the ability bit is correct.
if (pkm is G3PKM g3)
{
var abit = g3.AbilityBit;
// We've sanitized our personal data to replace "None" abilities with the first ability.
// Granbull, Vibrava, and Flygon have dual abilities being the same.
if (abilities[0] == abilities[1] && g3.Species is not ((int)Species.Granbull or (int)Species.Vibrava or (int)Species.Flygon)) // Not a dual ability
{
// Must not have the Ability bit flag set.
// Shadow encounters set a random ability index; don't bother checking if it's a re-battle for ability bit flipping.
if (abit && enc is not EncounterStaticShadow)
return GetInvalid(LAbilityMismatchFlag, CheckIdentifier.PID);
}
else
{
// Gen3 mainline origin sets the Ability index based on the PID, but only if it has two abilities.
// Version value check isn't factually correct, but there are no C/XD gifts with (Version!=15) that have two abilities.
// Pikachu, Celebi, Ho-Oh
if (pkm.Version != (int)GameVersion.CXD && abit != ((pkm.PID & 1) == 1))
return GetInvalid(LAbilityMismatchPID, CheckIdentifier.PID);
}
}
else if (pkm.Format >= 6)
{
// 6+ already checked at the top of the verifier call stack
return VALID;
}
// 3-5
return GetPIDAbilityMatch(pkm, abilities);
}
private CheckResult GetPIDAbilityMatch(PKM pkm, IReadOnlyList<int> abilities)
{
// Ability Number bits are already verified as clean.
var abil = abilities[pkm.AbilityNumber >> 1];
if (abil != pkm.Ability)
return GetInvalid(LAbilityMismatchPID);
return VALID;
}
// Ability Capsule can change between 1/2
private static bool IsAbilityCapsuleModified(PKM pkm, IReadOnlyList<int> abilities, int encounterAbility)
{
if (!CanAbilityCapsule(pkm.Format, abilities))
return false;
if (pkm.AbilityNumber == 4)
return false; // Cannot alter to hidden ability.
if (encounterAbility == 4)
return false; // Cannot alter from hidden ability.
return true;
}
public static bool CanAbilityCapsule(int format, IReadOnlyList<int> abilities)
{
if (format < 6) // Ability Capsule does not exist
return false;
return abilities[0] != abilities[1]; // Cannot alter ability index if it is the same as the other ability.
}
public static bool CanAbilityPatch(int format, IReadOnlyList<int> abilities, int species)
{
if (format < 8) // Ability Patch does not exist
return false;
// Can alter ability index if it is different from the other abilities.
var h = abilities[2];
if (h != abilities[0] || h != abilities[1])
return true;
// Some species have a distinct hidden ability only on another form, and can change between that form and its current form.
return species switch
{
(int)Species.Giratina => true, // Form-0 is a/a/h
(int)Species.Tornadus => true, // Form-0 is a/a/h
(int)Species.Thundurus => true, // Form-0 is a/a/h
(int)Species.Landorus => true, // Form-0 is a/a/h
2021-08-20 20:49:20 +00:00
_ => false,
};
}
private static int GetEncounterFixedAbilityNumber(IEncounterTemplate enc) => enc switch
{
IFixedAbilityNumber s => s.Ability,
2021-08-20 20:49:20 +00:00
_ => -1,
};
}
}