PKHeX/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot.cs
Kurt ccf87242c1 Eliminate boxing on encounter search (criteria)
struct implementing interface is boxed when passed to method that accepts interface (not generic method).
Removes IDexLevel (no other inheritors but EvoCriteria) and uses the primitive the data is stored (array, not IReadOnlyList) for slightly better perf.
2022-05-07 18:29:36 -07:00

243 lines
9.4 KiB
C#

using System;
using static PKHeX.Core.Species;
namespace PKHeX.Core
{
/// <summary>
/// Wild Encounter Slot data
/// </summary>
/// <remarks>Wild encounter slots are found as random encounters in-game.</remarks>
public abstract record EncounterSlot(EncounterArea Area, int Species, int Form, byte LevelMin, byte LevelMax) : IEncounterable, IEncounterMatch
{
public abstract int Generation { get; }
public bool EggEncounter => false;
public virtual bool IsShiny => false;
protected readonly EncounterArea Area = Area;
public GameVersion Version => Area.Version;
public int Location => Area.Location;
public int EggLocation => 0;
public virtual Ball FixedBall => Ball.None;
public virtual Shiny Shiny => Shiny.Random;
public bool IsFixedLevel => LevelMin == LevelMax;
public bool IsRandomLevel => LevelMin != LevelMax;
private protected const string wild = "Wild Encounter";
public string Name => wild;
/// <summary>
/// Gets if the specified level inputs are within range of the <see cref="LevelMin"/> and <see cref="LevelMax"/>
/// </summary>
/// <param name="lvl">Single level</param>
/// <returns>True if within slot's range, false if impossible.</returns>
public bool IsLevelWithinRange(int lvl) => LevelMin <= lvl && lvl <= LevelMax;
/// <summary>
/// Gets if the specified level inputs are within range of the <see cref="LevelMin"/> and <see cref="LevelMax"/>
/// </summary>
/// <param name="min">Highest value the low end of levels can be</param>
/// <param name="max">Lowest value the high end of levels can be</param>
/// <returns>True if within slot's range, false if impossible.</returns>
public bool IsLevelWithinRange(byte min, byte max) => LevelMin <= max && min <= LevelMax;
/// <summary>
/// Gets if the specified level inputs are within range of the <see cref="LevelMin"/> and <see cref="LevelMax"/>
/// </summary>
/// <param name="lvl">Single level</param>
/// <param name="minDecrease">Highest value the low end of levels can be</param>
/// <param name="maxIncrease">Lowest value the high end of levels can be</param>
/// <returns>True if within slot's range, false if impossible.</returns>
public bool IsLevelWithinRange(int lvl, int minDecrease, int maxIncrease) => LevelMin - minDecrease <= lvl && lvl <= LevelMax + maxIncrease;
/// <summary>
/// Gets if the specified level inputs are within range of the <see cref="LevelMin"/> and <see cref="LevelMax"/>
/// </summary>
/// <param name="min">Lowest level allowed</param>
/// <param name="max">Highest level allowed</param>
/// <param name="minDecrease">Highest value the low end of levels can be</param>
/// <param name="maxIncrease">Lowest value the high end of levels can be</param>
/// <returns>True if within slot's range, false if impossible.</returns>
public bool IsLevelWithinRange(byte min, byte max, int minDecrease, int maxIncrease) => LevelMin - minDecrease <= max && min <= LevelMax + maxIncrease;
public virtual string LongName
{
get
{
if (Area.Type == SlotType.Any)
return wild;
return $"{wild} {Area.Type.ToString().Replace('_', ' ')}";
}
}
public PKM ConvertToPKM(ITrainerInfo sav) => ConvertToPKM(sav, EncounterCriteria.Unrestricted);
public PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria)
{
var pk = EntityBlank.GetBlank(Generation, Version);
sav.ApplyTo(pk);
ApplyDetails(sav, criteria, pk);
return pk;
}
protected virtual void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk)
{
var version = this.GetCompatibleVersion((GameVersion) sav.Game);
int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID) sav.Language, version);
int level = LevelMin;
pk.Species = Species;
pk.Form = GetWildForm(pk, Form, sav);
pk.Language = lang;
pk.CurrentLevel = level;
pk.Version = (int)version;
pk.Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation);
ApplyDetailsBall(pk);
pk.Language = lang;
pk.OT_Friendship = pk.PersonalInfo.BaseFriendship;
SetMetData(pk, level, Location);
SetPINGA(pk, criteria);
SetEncounterMoves(pk, version, level);
SetFormatSpecificData(pk);
if (pk.Format < 6)
return;
sav.ApplyHandlingTrainerInfo(pk);
if (pk is IScaledSize { HeightScalar: 0, WeightScalar: 0 } s)
{
s.HeightScalar = PokeSizeUtil.GetRandomScalar();
s.WeightScalar = PokeSizeUtil.GetRandomScalar();
}
}
protected virtual void ApplyDetailsBall(PKM pk)
{
var ball = FixedBall;
pk.Ball = (int)(ball == Ball.None ? Ball.Poke : ball);
}
protected virtual void SetEncounterMoves(PKM pk, GameVersion version, int level)
{
var moves = MoveLevelUp.GetEncounterMoves(pk, level, version);
pk.SetMoves(moves);
pk.SetMaximumPPCurrent(moves);
}
protected virtual void SetFormatSpecificData(PKM pk) { }
protected virtual void SetPINGA(PKM pk, EncounterCriteria criteria)
{
var pi = pk.PersonalInfo;
int gender = criteria.GetGender(-1, pi);
int nature = (int)criteria.GetNature(Nature.Random);
var ability = criteria.GetAbilityFromNumber(Ability);
if (Generation == 3 && Species == (int)Unown)
{
do
{
PIDGenerator.SetRandomWildPID(pk, pk.Format, nature, ability, gender);
ability ^= 1; // some nature-forms cannot have a certain PID-ability set, so just flip it as Unown doesn't have dual abilities.
} while (pk.Form != Form);
}
else
{
PIDGenerator.SetRandomWildPID(pk, pk.Format, nature, ability, gender);
}
pk.Gender = gender;
pk.StatNature = nature;
}
private void SetMetData(PKM pk, int level, int location)
{
if (pk.Format <= 2 && Version != GameVersion.C)
return;
pk.Met_Location = location;
pk.Met_Level = level;
if (pk.Format >= 4)
pk.MetDate = DateTime.Today;
}
public bool IsRandomUnspecificForm => Form >= FormDynamic;
private const int FormDynamic = FormVivillon;
protected internal const int FormVivillon = 30;
protected internal const int FormRandom = 31;
private static int GetWildForm(PKM pk, int form, ITrainerInfo sav)
{
if (form < FormDynamic) // specified form
return form;
if (form == FormRandom) // flagged as totally random
{
if (pk.Species == (int)Minior)
return 7 + Util.Rand.Next(7);
return Util.Rand.Next(pk.PersonalInfo.FormCount);
}
int species = pk.Species;
if (species is >= (int)Scatterbug and <= (int)Vivillon)
{
if (sav is IRegionOrigin o)
return Vivillon3DS.GetPattern(o.Country, o.Region);
}
return 0;
}
public virtual string GetConditionString(out bool valid)
{
valid = true;
return LegalityCheckStrings.LEncCondition;
}
public bool IsMatchExact(PKM pkm, EvoCriteria evo) => true; // Matched by Area
public virtual EncounterMatchRating GetMatchRating(PKM pkm)
{
if (IsDeferredWurmple(pkm))
return EncounterMatchRating.PartialMatch;
if (pkm.Format >= 5)
{
bool isHidden = pkm.AbilityNumber == 4;
if (isHidden && this.IsPartialMatchHidden(pkm.Species, Species))
return EncounterMatchRating.PartialMatch;
if (IsDeferredHiddenAbility(isHidden))
return EncounterMatchRating.Deferred;
}
return EncounterMatchRating.Match;
}
protected virtual HiddenAbilityPermission IsHiddenAbilitySlot() => HiddenAbilityPermission.Never;
public AbilityPermission Ability => IsHiddenAbilitySlot() switch
{
HiddenAbilityPermission.Never => AbilityPermission.Any12,
HiddenAbilityPermission.Always => AbilityPermission.OnlyHidden,
_ => AbilityPermission.Any12H,
};
private bool IsDeferredWurmple(PKM pkm) => Species == (int)Wurmple && pkm.Species != (int)Wurmple && !WurmpleUtil.IsWurmpleEvoValid(pkm);
private bool IsDeferredHiddenAbility(bool IsHidden) => IsHiddenAbilitySlot() switch
{
HiddenAbilityPermission.Never => IsHidden,
HiddenAbilityPermission.Always => !IsHidden,
_ => false,
};
protected enum HiddenAbilityPermission
{
Always,
Never,
Possible,
}
}
}