mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-04-25 16:35:02 -05:00
320 lines
11 KiB
C#
320 lines
11 KiB
C#
using System;
|
|
|
|
namespace PKHeX.Core;
|
|
|
|
/// <summary>
|
|
/// Generation 8 Static Encounter
|
|
/// </summary>
|
|
public sealed record EncounterStatic8a
|
|
: IEncounterable, IEncounterMatch, IEncounterConvertible<PA8>, ISeedCorrelation64<PKM>, IGenerateSeed64,
|
|
IAlphaReadOnly, IMasteryInitialMoveShop8, IScaledSizeReadOnly,
|
|
IMoveset, IFlawlessIVCount, IFatefulEncounterReadOnly, IFixedGender
|
|
{
|
|
public byte Generation => 8;
|
|
public EntityContext Context => EntityContext.Gen8a;
|
|
private const GameVersion Version = GameVersion.PLA;
|
|
GameVersion IVersion.Version => GameVersion.PLA;
|
|
public ushort EggLocation => 0;
|
|
ushort ILocation.Location => Location;
|
|
public bool IsShiny => Shiny == Shiny.Always;
|
|
public bool IsEgg => false;
|
|
public AbilityPermission Ability => AbilityPermission.Any12;
|
|
|
|
public ushort Species { get; }
|
|
public byte Form { get; }
|
|
public byte HeightScalar { get; }
|
|
public byte WeightScalar { get; }
|
|
public byte LevelMin { get; }
|
|
public byte LevelMax { get; init; }
|
|
public byte Gender { get; init; } = FixedGenderUtil.GenderRandom;
|
|
public required byte Location { get; init; }
|
|
public byte FlawlessIVCount { get; init; }
|
|
public Shiny Shiny { get; init; } = Shiny.Never;
|
|
public bool IsAlpha { get; init; }
|
|
public bool FatefulEncounter { get; init; }
|
|
public Ball FixedBall { get; init; }
|
|
public Moveset Moves { get; init; }
|
|
public EncounterStatic8aCorrelation Method { get; init; }
|
|
|
|
public string Name => "Static Encounter";
|
|
public string LongName => Name;
|
|
|
|
public bool HasFixedHeight => HeightScalar != NoScalar;
|
|
public bool HasFixedWeight => WeightScalar != NoScalar;
|
|
|
|
private const byte NoScalar = 0;
|
|
private const byte ScaleMax = 255;
|
|
|
|
public EncounterStatic8a(ushort species, byte form, byte level, byte heightScalar = NoScalar, byte weightScalar = NoScalar)
|
|
{
|
|
Species = species;
|
|
Form = form;
|
|
HeightScalar = heightScalar;
|
|
WeightScalar = weightScalar;
|
|
LevelMin = LevelMax = level;
|
|
}
|
|
|
|
#region Generating
|
|
PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria);
|
|
PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr);
|
|
public PA8 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted);
|
|
public PA8 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
|
|
{
|
|
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
|
|
var pi = PersonalTable.LA[Species, Form];
|
|
var pk = new PA8
|
|
{
|
|
Language = language,
|
|
Species = Species,
|
|
Form = Form,
|
|
CurrentLevel = LevelMin,
|
|
OriginalTrainerFriendship = pi.BaseFriendship,
|
|
FatefulEncounter = FatefulEncounter,
|
|
MetLocation = Location,
|
|
MetLevel = LevelMin,
|
|
MetDate = EncounterDate.GetDateSwitch(),
|
|
Version = Version,
|
|
|
|
OriginalTrainerName = tr.OT,
|
|
OriginalTrainerGender = tr.Gender,
|
|
ID32 = tr.ID32,
|
|
|
|
IsAlpha = IsAlpha,
|
|
Ball = (byte)(FixedBall == Ball.None ? Ball.LAPoke : FixedBall),
|
|
Nickname = SpeciesName.GetSpeciesNameGeneration(Species, language, Generation),
|
|
};
|
|
|
|
SetPINGA(pk, criteria);
|
|
|
|
pk.ResetPartyStats();
|
|
return pk;
|
|
}
|
|
|
|
private void SetPINGA(PA8 pk, EncounterCriteria criteria)
|
|
{
|
|
var para = GetParams();
|
|
var (_, slotSeed) = Overworld8aRNG.ApplyDetails(pk, criteria, para, IsAlpha);
|
|
Finalize(pk, slotSeed);
|
|
}
|
|
|
|
private void Finalize(PA8 pk, ulong slotSeed)
|
|
{
|
|
// Phione and Zorua have random levels; follow the correlation instead of giving the lowest level.
|
|
if (LevelMin != LevelMax)
|
|
pk.MetLevel = pk.CurrentLevel = Overworld8aRNG.GetRandomLevel(slotSeed, LevelMin, LevelMax);
|
|
|
|
// Disassociate the correlation if it is supposed to use the global 128-bit RNG state instead.
|
|
if (Method == EncounterStatic8aCorrelation.Fixed)
|
|
pk.EncryptionConstant = Util.Rand32();
|
|
|
|
if (HasFixedHeight)
|
|
pk.HeightScalar = HeightScalar;
|
|
if (HasFixedWeight)
|
|
pk.WeightScalar = WeightScalar;
|
|
pk.Scale = pk.HeightScalar;
|
|
|
|
pk.ResetHeight();
|
|
pk.ResetWeight();
|
|
SetEncounterMoves(pk, pk.MetLevel);
|
|
}
|
|
|
|
public void GenerateSeed64(PKM pk, ulong seed)
|
|
{
|
|
var pa8 = (PA8)pk;
|
|
var criteria = EncounterCriteria.Unrestricted;
|
|
var para = GetParams();
|
|
var (_, slotSeed) = Overworld8aRNG.ApplyDetails(pa8, criteria, para, IsAlpha);
|
|
Finalize(pa8, slotSeed);
|
|
}
|
|
|
|
private void SetEncounterMoves(PA8 pk, byte level)
|
|
{
|
|
Span<ushort> moves = stackalloc ushort[4];
|
|
var (learn, mastery) = GetLevelUpInfo();
|
|
LoadInitialMoveset(pk, moves, learn, level);
|
|
pk.SetMoves(moves);
|
|
pk.SetEncounterMasteryFlags(moves, mastery, level);
|
|
if (pk.AlphaMove != 0)
|
|
pk.SetMasteryFlagMove(pk.AlphaMove);
|
|
}
|
|
|
|
public (Learnset Learn, Learnset Mastery) GetLevelUpInfo() => LearnSource8LA.GetLearnsetAndMastery(Species, Form);
|
|
|
|
public void LoadInitialMoveset(PA8 pa8, Span<ushort> moves, Learnset learn, byte level)
|
|
{
|
|
if (Moves.HasMoves)
|
|
Moves.CopyTo(moves);
|
|
else
|
|
learn.SetEncounterMoves(level, moves);
|
|
if (IsAlpha)
|
|
pa8.AlphaMove = moves[0];
|
|
}
|
|
|
|
private OverworldParam8a GetParams()
|
|
{
|
|
var gender = GetGenderRatio();
|
|
return new OverworldParam8a
|
|
{
|
|
IsAlpha = IsAlpha,
|
|
FlawlessIVs = FlawlessIVCount,
|
|
Shiny = Shiny,
|
|
RollCount = 1, // Everything is shiny locked anyway
|
|
GenderRatio = gender,
|
|
};
|
|
}
|
|
|
|
private byte GetGenderRatio() => Gender switch
|
|
{
|
|
0 => PersonalInfo.RatioMagicMale,
|
|
1 => PersonalInfo.RatioMagicFemale,
|
|
_ => GetGenderRatioPersonal(),
|
|
};
|
|
|
|
private byte GetGenderRatioPersonal()
|
|
{
|
|
var pt = PersonalTable.LA;
|
|
var entry = pt.GetFormEntry(Species, Form);
|
|
return entry.Gender;
|
|
}
|
|
#endregion
|
|
|
|
#region Matching
|
|
public bool IsMatchExact(PKM pk, EvoCriteria evo)
|
|
{
|
|
if (!this.IsLevelWithinRange(pk.MetLevel))
|
|
return false;
|
|
if (Gender != FixedGenderUtil.GenderRandom && pk.Gender != Gender)
|
|
return false;
|
|
if (pk is IAlphaReadOnly a && a.IsAlpha != IsAlpha)
|
|
return false;
|
|
if (!IsMatchEggLocation(pk))
|
|
return false;
|
|
if (!IsMatchLocation(pk))
|
|
return false;
|
|
if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context))
|
|
return false;
|
|
if (FlawlessIVCount != 0 && pk.FlawlessIVCount < FlawlessIVCount)
|
|
return false;
|
|
|
|
if (pk is not IScaledSize s)
|
|
return true;
|
|
|
|
// 3 of the Alpha statics were mistakenly set as 127 scale. If they enter HOME on 3.0.1, they'll get bumped to 255.
|
|
if (IsAlpha && this is { HeightScalar: 127, WeightScalar: 127 }) // Average Size Alphas
|
|
{
|
|
// HOME >=3.0.1 ensures 255 scales for the 127's
|
|
// PLA and S/V could have safe-harbored them via <=3.0.0
|
|
if (pk.Context is EntityContext.Gen8a or EntityContext.Gen9)
|
|
{
|
|
if (s is not { HeightScalar: 127, WeightScalar: 127 }) // Original?
|
|
{
|
|
// Must match the HOME updated values AND must have the Alpha ribbon (visited HOME).
|
|
if (s is not { HeightScalar: ScaleMax, WeightScalar: ScaleMax })
|
|
return false;
|
|
if (pk is IRibbonSetMark9 { RibbonMarkAlpha: false })
|
|
return false;
|
|
// un-traded: don't bother checking; PLA could handle (PLA<->HOME) and never be traded.
|
|
// Don't bother checking for HOME Tracker. The updated values is sufficient.
|
|
// Having the Alpha mark set will be flagged if lacking a tracker, no need to block matching.
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Must match the HOME updated values
|
|
if (s is not { HeightScalar: ScaleMax, WeightScalar: ScaleMax })
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (HasFixedHeight && s.HeightScalar != HeightScalar)
|
|
return false;
|
|
if (HasFixedWeight && s.WeightScalar != WeightScalar)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool IsMatchEggLocation(PKM pk)
|
|
{
|
|
var expect = pk is PB8 ? Locations.Default8bNone : EggLocation;
|
|
return pk.EggLocation == expect;
|
|
}
|
|
|
|
private bool IsMatchLocation(PKM pk)
|
|
{
|
|
var metState = LocationsHOME.GetRemapState(Context, pk.Context);
|
|
if (metState == LocationRemapState.Original)
|
|
return pk.MetLocation == Location;
|
|
if (metState == LocationRemapState.Remapped)
|
|
return IsMetRemappedSWSH(pk);
|
|
return pk.MetLocation == Location || IsMetRemappedSWSH(pk);
|
|
}
|
|
|
|
private static bool IsMetRemappedSWSH(PKM pk) => pk.MetLocation == LocationsHOME.SWLA;
|
|
|
|
public EncounterMatchRating GetMatchRating(PKM pk)
|
|
{
|
|
if (Shiny != Shiny.Random && !Shiny.IsValid(pk))
|
|
return EncounterMatchRating.DeferredErrors;
|
|
if (FixedBall != Ball.None && pk.Ball != (byte)FixedBall)
|
|
return EncounterMatchRating.DeferredErrors;
|
|
|
|
if (!IsForcedMasteryCorrect(pk))
|
|
return EncounterMatchRating.DeferredErrors;
|
|
|
|
if (!MarkRules.IsMarkValidAlpha(pk, IsAlpha) || (pk is IAlphaReadOnly a && a.IsAlpha != IsAlpha))
|
|
return EncounterMatchRating.DeferredErrors;
|
|
|
|
if (IsAlpha && pk is PA8 { AlphaMove: 0 })
|
|
return EncounterMatchRating.Deferred;
|
|
|
|
return EncounterMatchRating.Match;
|
|
}
|
|
|
|
public bool IsForcedMasteryCorrect(PKM pk)
|
|
{
|
|
ushort alpha = 0;
|
|
if (IsAlpha && Moves.HasMoves)
|
|
{
|
|
if (pk is PA8 pa && (alpha = pa.AlphaMove) != Moves.Move1)
|
|
return false;
|
|
}
|
|
|
|
if (pk is not IMoveShop8Mastery p)
|
|
return true;
|
|
|
|
const bool allowAlphaPurchaseBug = true; // Everything else Alpha is pre-1.1
|
|
var level = pk.MetLevel;
|
|
var (learn, mastery) = GetLevelUpInfo();
|
|
if (!p.IsValidPurchasedEncounter(learn, level, alpha, allowAlphaPurchaseBug))
|
|
return false;
|
|
|
|
Span<ushort> moves = stackalloc ushort[4];
|
|
if (Moves.HasMoves)
|
|
Moves.CopyTo(moves);
|
|
else
|
|
learn.SetEncounterMoves(level, moves);
|
|
|
|
return p.IsValidMasteredEncounter(moves, learn, mastery, level, alpha, allowAlphaPurchaseBug);
|
|
}
|
|
#endregion
|
|
|
|
public bool TryGetSeed(PKM pk, out ulong seed)
|
|
{
|
|
// Check if it matches any single-roll seed.
|
|
var param = GetParams();
|
|
var solver = new XoroMachineSkip(pk.EncryptionConstant, pk.PID);
|
|
foreach (var s in solver)
|
|
{
|
|
if (!Overworld8aRNG.Verify(pk, s, param, HasFixedHeight, HasFixedWeight))
|
|
continue;
|
|
seed = s;
|
|
return true;
|
|
}
|
|
seed = 0;
|
|
return false;
|
|
}
|
|
}
|