diff --git a/PkmnLib.Dynamic/Events/EvolutionEvent.cs b/PkmnLib.Dynamic/Events/EvolutionEvent.cs
new file mode 100644
index 0000000..36d05fa
--- /dev/null
+++ b/PkmnLib.Dynamic/Events/EvolutionEvent.cs
@@ -0,0 +1,28 @@
+using PkmnLib.Dynamic.Models;
+using PkmnLib.Static.Species;
+
+namespace PkmnLib.Dynamic.Events;
+
+public record EvolutionEvent : IEventData
+{
+ public IPokemon Pokemon { get; }
+ public ISpecies Species { get; }
+ public IForm OriginalForm { get; }
+ public ISpecies NewSpecies { get; }
+ public IForm NewForm { get; }
+ public IEvolution Evolution { get; }
+
+ public EvolutionEvent(IPokemon pokemon, ISpecies species, IForm originalForm, ISpecies newSpecies, IForm newForm,
+ IEvolution evolution)
+ {
+ Pokemon = pokemon;
+ Species = species;
+ NewSpecies = newSpecies;
+ Evolution = evolution;
+ OriginalForm = originalForm;
+ NewForm = newForm;
+ }
+
+ ///
+ public EventBatchId BatchId { get; init; }
+}
\ No newline at end of file
diff --git a/PkmnLib.Dynamic/Models/Pokemon.cs b/PkmnLib.Dynamic/Models/Pokemon.cs
index 39e797d..dad5d5e 100644
--- a/PkmnLib.Dynamic/Models/Pokemon.cs
+++ b/PkmnLib.Dynamic/Models/Pokemon.cs
@@ -204,7 +204,7 @@ public interface IPokemon : IScriptSource, IDeepCloneable
///
/// Marks the Pokemon as caught. This makes it so that the Pokemon is not considered valid in battle anymore.
///
- public void MarkAsCaught();
+ void MarkAsCaught();
///
/// The script for the held item.
@@ -293,7 +293,7 @@ public interface IPokemon : IScriptSource, IDeepCloneable
///
/// Suppresses the ability of the Pokémon.
///
- public void SuppressAbility();
+ void SuppressAbility();
///
/// Returns the currently active ability.
@@ -313,15 +313,21 @@ public interface IPokemon : IScriptSource, IDeepCloneable
///
void RecalculateBoostedStats();
+ ///
+ /// Evolves the Pokemon to a specific evolution. This will not check whether the evolution is valid, so
+ /// you should check that before calling this method.
+ ///
+ bool EvolveTo(IEvolution evolution, EventHook? eventHook = null);
+
///
/// Change the species of the Pokemon.
///
- void ChangeSpecies(ISpecies species, IForm form);
+ void ChangeSpecies(ISpecies species, IForm form, EventHook? eventHook = null);
///
/// Change the form of the Pokemon.
///
- void ChangeForm(IForm form, EventBatchId batchId = default);
+ void ChangeForm(IForm form, EventBatchId batchId = default, EventHook? eventHook = null);
///
/// Whether the Pokemon is useable in a battle.
@@ -606,7 +612,7 @@ public class PokemonImpl : ScriptSource, IPokemon
public IDynamicLibrary Library { get; }
///
- public ISpecies Species { get; }
+ public ISpecies Species { get; private set; }
///
public IForm Form { get; private set; }
@@ -985,8 +991,25 @@ public class PokemonImpl : ScriptSource, IPokemon
public void RecalculateBoostedStats() => Library.StatCalculator.CalculateBoostedStats(this, BoostedStats);
///
- public void ChangeSpecies(ISpecies species, IForm form)
+ public bool EvolveTo(IEvolution evolution, EventHook? eventHook = null)
{
+ if (!Species.EvolutionData.Contains(evolution))
+ return false;
+ if (!Library.StaticLibrary.Species.TryGet(evolution.ToSpecies, out var species))
+ return false;
+
+ // TODO: Consider how forms work with evolution items.
+ var newForm = species.GetDefaultForm();
+ eventHook?.Invoke(new EvolutionEvent(this, Species, Form, species, newForm, evolution));
+
+ ChangeSpecies(species, newForm, eventHook);
+ return true;
+ }
+
+ ///
+ public void ChangeSpecies(ISpecies species, IForm form, EventHook? eventHook = null)
+ {
+ eventHook ??= BattleData?.Battle.EventHook;
if (Species == species)
{
if (form != Form)
@@ -1007,18 +1030,20 @@ public class PokemonImpl : ScriptSource, IPokemon
}
var batchId = new EventBatchId();
- BattleData?.Battle.EventHook.Invoke(new SpeciesChangeEvent(this, species, form)
+ eventHook?.Invoke(new SpeciesChangeEvent(this, species, form)
{
BatchId = batchId,
});
+ Species = species;
ChangeForm(form, batchId);
}
///
- public void ChangeForm(IForm form, EventBatchId batchId = default)
+ public void ChangeForm(IForm form, EventBatchId batchId = default, EventHook? eventHook = null)
{
if (form == Form)
return;
+ eventHook ??= BattleData?.Battle.EventHook;
var oldAbility = Form.GetAbility(AbilityIndex);
@@ -1068,7 +1093,7 @@ public class PokemonImpl : ScriptSource, IPokemon
// TODO: form specific moves?
- BattleData?.Battle.EventHook.Invoke(new FormChangeEvent(this, form)
+ eventHook?.Invoke(new FormChangeEvent(this, form)
{
BatchId = batchId,
});
diff --git a/PkmnLib.Dynamic/ScriptHandling/ItemScript.cs b/PkmnLib.Dynamic/ScriptHandling/ItemScript.cs
index 28c0149..4f8aa4f 100644
--- a/PkmnLib.Dynamic/ScriptHandling/ItemScript.cs
+++ b/PkmnLib.Dynamic/ScriptHandling/ItemScript.cs
@@ -46,12 +46,12 @@ public abstract class ItemScript : IDeepCloneable
///
/// Returns whether the item can be held by a Pokémon.
///
- public virtual bool IsHoldable => false;
+ public virtual bool IsHoldable => true;
///
/// Returns whether the item can be held by the given target.
///
- public virtual bool CanTargetHold(IPokemon pokemon) => false;
+ public virtual bool CanTargetHold(IPokemon pokemon) => true;
///
/// Handles the use of the item.
diff --git a/PkmnLib.Dynamic/ScriptHandling/ScriptResolver.cs b/PkmnLib.Dynamic/ScriptHandling/ScriptResolver.cs
index 9ff076c..f84cfc6 100644
--- a/PkmnLib.Dynamic/ScriptHandling/ScriptResolver.cs
+++ b/PkmnLib.Dynamic/ScriptHandling/ScriptResolver.cs
@@ -12,6 +12,7 @@ public class ScriptResolver
private IReadOnlyDictionary<(ScriptCategory, StringKey), Func