mirror of
https://github.com/mm201/pkmn-classic-framework.git
synced 2026-03-22 01:44:20 -05:00
377 lines
12 KiB
C#
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];
|
|
}
|
|
}
|
|
}
|