pkmn-classic-framework/library/Structures/PokemonBase.cs
2022-08-22 23:44:45 -04:00

377 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using PkmnFoundations.Pokedex;
using PkmnFoundations.Support;
namespace PkmnFoundations.Structures
{
public abstract class PokemonBase : BinarySerializableBase
{
public PokemonBase(Pokedex.Pokedex pokedex)
: base()
{
m_pokedex = pokedex;
Initialize();
}
private void Initialize()
{
m_species_pair = Species.CreatePair(m_pokedex);
m_form_pair = Form.CreatePairForSpecies(m_pokedex, () => Species);
m_item_pair = Item.CreatePairForGeneration(m_pokedex, () => Generation);
m_ability_pair = Ability.CreatePair(m_pokedex);
}
protected Pokedex.Pokedex m_pokedex;
private MoveSlot[] m_moves = new MoveSlot[4];
private LazyKeyValuePair<int, Species> m_species_pair;
public int SpeciesID
{
get { return m_species_pair.Key; }
set
{
// changing species will cause the looked up Form to be
// incorrect so null it out.
m_species_pair.Key = value;
m_form_pair.Invalidate();
}
}
public Species Species
{
get { return m_species_pair.Value; }
set
{
m_species_pair.Value = value;
m_form_pair.Invalidate();
}
}
private LazyKeyValuePair<byte, Form> m_form_pair;
public byte FormID
{
get { return m_form_pair.Key; }
set
{
m_form_pair.Key = value;
}
}
public Form Form
{
get { return m_form_pair.Value; }
set
{
SpeciesID = value.SpeciesID;
m_form_pair.Value = value;
}
}
public abstract Generations Generation { get; }
private LazyKeyValuePair<int, Item> m_item_pair;
public int HeldItemID
{
get { return m_item_pair.Key; }
set { m_item_pair.Key = value; }
}
public Item HeldItem
{
get { return m_item_pair.Value; }
set { m_item_pair.Value = value; }
}
private LazyKeyValuePair<int, Ability> m_ability_pair;
public int AbilityID
{
get { return m_ability_pair.Key; }
set { m_ability_pair.Key = value; }
}
public Ability Ability
{
get { return m_ability_pair.Value; }
set { m_ability_pair.Value = value; }
}
public IList<MoveSlot> Moves { get { return m_moves; } }
public uint TrainerID { get; set; }
public uint Personality { get; set; }
public virtual Natures Nature { get { return (Natures)(Personality % 25u); } }
public byte Happiness { get; set; }
public Languages Language { get; set; }
public IvStatValues IVs { get; set; }
public ByteStatValues EVs { get; set; }
public abstract byte Level { get; set; }
public virtual Genders Gender
{
get
{
// https://bulbapedia.bulbagarden.net/wiki/Gender#In_Generations_III_to_V
var ratio = Species.GenderRatio;
switch (ratio)
{
case 0:
return Genders.Male;
case 8:
return Genders.Female;
case 255:
return Genders.None;
default:
{
// massage gender value into the corresponding value in bulbapedia table.
// This is off-by-one in true Game Freak fashion, causing a slight bias towards male Pokémon.
uint mangledRatio = ratio * 32u - 1;
uint pGender = Personality & 0xffu;
return pGender >= mangledRatio ? Genders.Male : Genders.Female;
}
}
}
set
{
// xxx: we don't really want this setter at all but we need to be able to override and provide a setter
// https://github.com/dotnet/csharplang/issues/1568
throw new NotSupportedException();
}
}
public abstract string Nickname { get; set; }
public virtual bool IsShiny
{
get
{
// Gen3/4/5 formula. Gen6 must override.
return ShinyTest(Personality, TrainerID, 3);
}
}
public static bool ShinyTest(uint personality, uint trainerId, int trimBits)
{
uint step1 = personality ^ trainerId;
int step2 = (int)((step1 >> 16) ^ (step1 & 0xffffu));
return step2 >> trimBits == 0;
}
public Characteristic Characteristic
{
get
{
int toCheck = (int)(Personality % 6u);
Stats bestStat = (Structures.Stats)(toCheck + 1);
byte bestStatValue = 0;
for (int x = 0; x < 6; x++)
{
if (IVs[(Structures.Stats)(x + 1)] > bestStatValue)
{
bestStat = (Structures.Stats)(toCheck + 1);
bestStatValue = IVs[(Structures.Stats)(x + 1)];
}
toCheck++;
toCheck %= 6;
}
return new Characteristic(bestStat, (byte)(bestStatValue % (byte)5));
}
}
public virtual ValidationSummary Validate()
{
// xxx: this is another one of those inheritance diamond things.
// Since we specialize by generation after specializing by
// structure type, we have to roll our own vtable here to be able
// to have generation-specific validation in each inheritance
// branch.
// todo: Move some cross-generational validation logic over to
// this base method.
switch (this.Generation)
{
case Generations.Generation4:
return Pokemon4.ValidateActual(this);
case Generations.Generation5:
return Pokemon5.ValidateActual(this);
default:
return new ValidationSummary() { IsValid = true };
}
}
protected static List<int> BlockScramble(uint personality)
{
int x = 4; // todo: this can be an argument but YAGNI
int xFactorial = 24;
//if (x < 0) throw new ArgumentOutOfRangeException();
//if (x == 0) return new int[0];
int index;
List<int> remaining = Enumerable.Range(0, x).ToList();
List<int> result = new List<int>(x);
while (x > 0)
{
int xMinusOneFactorial = xFactorial / x;
index = (int)((personality % xFactorial) / xMinusOneFactorial);
result.Add(remaining[index]);
remaining.RemoveAt(index);
x--;
xFactorial = xMinusOneFactorial;
}
return result;
}
protected static List<int> Invert(List<int> arg)
{
// todo: this is of general utility and should go in a utility class.
int max = arg.Max();
List<int> result = new List<int>(max);
for (int x = 0; x <= max; x++)
result.Add(arg.IndexOf(x));
return result;
}
#region Experience formulas
public static int ExperienceAt(int level, GrowthRates gr)
{
if (level > 100 || level < 1) throw new ArgumentOutOfRangeException("level");
if (level == 1) return 0;
switch (gr)
{
case GrowthRates.Slow:
return ExperienceAt_Slow(level);
case GrowthRates.Medium:
return ExperienceAt_Medium(level);
case GrowthRates.Fast:
return ExperienceAt_Fast(level);
case GrowthRates.MediumSlow:
return ExperienceAt_MediumSlow(level);
case GrowthRates.Erratic:
return ExperienceAt_Erratic(level);
case GrowthRates.Fluctuating:
return ExperienceAt_Fluctuating(level);
}
throw new ArgumentException("gr");
}
public static byte LevelAt(int experience, GrowthRates gr)
{
if (experience < 0) throw new ArgumentOutOfRangeException("experience");
int minLevel = 1, maxLevel = 100;
int minExp = ExperienceAt(1, gr), maxExp = ExperienceAt(100, gr);
while (1 < 2)
{
if (maxExp <= experience) return (byte)maxLevel;
if (minLevel + 1 >= maxLevel) return (byte)minLevel;
int midLevel = (minLevel + maxLevel) >> 1;
int midExp = ExperienceAt(midLevel, gr);
if (experience >= midExp)
{
minLevel = midLevel;
minExp = midExp;
}
else
{
maxLevel = midLevel;
maxExp = midExp;
}
}
}
private static int ExperienceAt_Slow(int level)
{
int cube = level * level * level;
return 5 * cube / 4;
}
private static int ExperienceAt_Medium(int level)
{
int cube = level * level * level;
return cube;
}
private static int ExperienceAt_Fast(int level)
{
int cube = level * level * level;
return 4 * cube / 5;
}
private static int ExperienceAt_MediumSlow(int level)
{
int square = level * level;
int cube = square * level;
return 6 * cube / 5 - 15 * square + 100 * level - 140;
}
private static int ExperienceAt_Erratic(int level)
{
int cube = level * level * level;
if (level < 50)
return (cube * (100 - level)) / 50;
else if (level < 68)
return (cube * (150 - level)) / 100;
else if (level < 98)
// note that there is intentional rounding error cause by /3 inside the formula.
return (cube * ((1911 - 10 * level) / 3)) / 500;
else
return (cube * (160 - level)) / 100;
}
private static int ExperienceAt_Fluctuating(int level)
{
int cube = level * level * level;
if (level < 15)
return cube * ((level + 1) / 3 + 24) / 50;
else if (level < 36)
return cube * (level + 14) / 50;
else
return cube * (level / 2 + 32) / 50;
}
#endregion
}
public struct Characteristic
{
public Stats BestIv;
public byte BestIvModulo;
public Characteristic(Stats best_iv, byte best_iv_modulo)
{
BestIv = best_iv;
BestIvModulo = best_iv_modulo;
}
private static String[,] m_phrases = new String[,]
{
{ "Loves to eat", "Takes plenty of siestas", "Nods off a lot", "Scatters things often", "Likes to relax"},
{ "Proud of its power", "Likes to thrash about", "A little quick tempered", "Likes to fight", "Quick tempered"},
{ "Sturdy body", "Capable of taking hits", "Highly persistent", "Good endurance", "Good perseverance"},
{ "Likes to run", "Alert to sounds", "Impetuous and silly", "Somewhat of a clown", "Quick to flee"},
{ "Highly curious", "Mischievous", "Thoroughly cunning", "Often lost in thought", "Very finicky"},
{ "Strong willed", "Somewhat vain", "Strongly defiant", "Hates to lose", "Somewhat stubborn"},
};
public override string ToString()
{
// http://bulbapedia.bulbagarden.net/wiki/Characteristic#List_of_Characteristics
// todo: i18n
if (BestIv < Stats.Hp || BestIv > Stats.SpecialDefense) throw new InvalidOperationException();
if (BestIvModulo > 4) throw new InvalidOperationException();
return m_phrases[(int)BestIv - 1, BestIvModulo];
}
}
}