Work on item use/evolutions
All checks were successful
Build / Build (push) Successful in 1m7s

This commit is contained in:
Deukhoofd 2025-08-18 11:57:03 +02:00
parent e5041ec5f0
commit f061ba2455
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
7 changed files with 157 additions and 12 deletions

View File

@ -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;
}
/// <inheritdoc />
public EventBatchId BatchId { get; init; }
}

View File

@ -204,7 +204,7 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// <summary> /// <summary>
/// Marks the Pokemon as caught. This makes it so that the Pokemon is not considered valid in battle anymore. /// Marks the Pokemon as caught. This makes it so that the Pokemon is not considered valid in battle anymore.
/// </summary> /// </summary>
public void MarkAsCaught(); void MarkAsCaught();
/// <summary> /// <summary>
/// The script for the held item. /// The script for the held item.
@ -293,7 +293,7 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// <summary> /// <summary>
/// Suppresses the ability of the Pokémon. /// Suppresses the ability of the Pokémon.
/// </summary> /// </summary>
public void SuppressAbility(); void SuppressAbility();
/// <summary> /// <summary>
/// Returns the currently active ability. /// Returns the currently active ability.
@ -313,15 +313,21 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// </summary> /// </summary>
void RecalculateBoostedStats(); void RecalculateBoostedStats();
/// <summary>
/// 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.
/// </summary>
bool EvolveTo(IEvolution evolution, EventHook? eventHook = null);
/// <summary> /// <summary>
/// Change the species of the Pokemon. /// Change the species of the Pokemon.
/// </summary> /// </summary>
void ChangeSpecies(ISpecies species, IForm form); void ChangeSpecies(ISpecies species, IForm form, EventHook? eventHook = null);
/// <summary> /// <summary>
/// Change the form of the Pokemon. /// Change the form of the Pokemon.
/// </summary> /// </summary>
void ChangeForm(IForm form, EventBatchId batchId = default); void ChangeForm(IForm form, EventBatchId batchId = default, EventHook? eventHook = null);
/// <summary> /// <summary>
/// Whether the Pokemon is useable in a battle. /// Whether the Pokemon is useable in a battle.
@ -606,7 +612,7 @@ public class PokemonImpl : ScriptSource, IPokemon
public IDynamicLibrary Library { get; } public IDynamicLibrary Library { get; }
/// <inheritdoc /> /// <inheritdoc />
public ISpecies Species { get; } public ISpecies Species { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public IForm Form { 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 RecalculateBoostedStats() => Library.StatCalculator.CalculateBoostedStats(this, BoostedStats);
/// <inheritdoc /> /// <inheritdoc />
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;
}
/// <inheritdoc />
public void ChangeSpecies(ISpecies species, IForm form, EventHook? eventHook = null)
{
eventHook ??= BattleData?.Battle.EventHook;
if (Species == species) if (Species == species)
{ {
if (form != Form) if (form != Form)
@ -1007,18 +1030,20 @@ public class PokemonImpl : ScriptSource, IPokemon
} }
var batchId = new EventBatchId(); var batchId = new EventBatchId();
BattleData?.Battle.EventHook.Invoke(new SpeciesChangeEvent(this, species, form) eventHook?.Invoke(new SpeciesChangeEvent(this, species, form)
{ {
BatchId = batchId, BatchId = batchId,
}); });
Species = species;
ChangeForm(form, batchId); ChangeForm(form, batchId);
} }
/// <inheritdoc /> /// <inheritdoc />
public void ChangeForm(IForm form, EventBatchId batchId = default) public void ChangeForm(IForm form, EventBatchId batchId = default, EventHook? eventHook = null)
{ {
if (form == Form) if (form == Form)
return; return;
eventHook ??= BattleData?.Battle.EventHook;
var oldAbility = Form.GetAbility(AbilityIndex); var oldAbility = Form.GetAbility(AbilityIndex);
@ -1068,7 +1093,7 @@ public class PokemonImpl : ScriptSource, IPokemon
// TODO: form specific moves? // TODO: form specific moves?
BattleData?.Battle.EventHook.Invoke(new FormChangeEvent(this, form) eventHook?.Invoke(new FormChangeEvent(this, form)
{ {
BatchId = batchId, BatchId = batchId,
}); });

View File

@ -46,12 +46,12 @@ public abstract class ItemScript : IDeepCloneable
/// <summary> /// <summary>
/// Returns whether the item can be held by a Pokémon. /// Returns whether the item can be held by a Pokémon.
/// </summary> /// </summary>
public virtual bool IsHoldable => false; public virtual bool IsHoldable => true;
/// <summary> /// <summary>
/// Returns whether the item can be held by the given target. /// Returns whether the item can be held by the given target.
/// </summary> /// </summary>
public virtual bool CanTargetHold(IPokemon pokemon) => false; public virtual bool CanTargetHold(IPokemon pokemon) => true;
/// <summary> /// <summary>
/// Handles the use of the item. /// Handles the use of the item.

View File

@ -12,6 +12,7 @@ public class ScriptResolver
private IReadOnlyDictionary<(ScriptCategory, StringKey), Func<Script>> _scriptCtors; private IReadOnlyDictionary<(ScriptCategory, StringKey), Func<Script>> _scriptCtors;
private IReadOnlyDictionary<StringKey, Func<IItem, ItemScript>> _itemScriptCtors; private IReadOnlyDictionary<StringKey, Func<IItem, ItemScript>> _itemScriptCtors;
private readonly Dictionary<IItem, ItemScript> _itemBattleScripts = new(); private readonly Dictionary<IItem, ItemScript> _itemBattleScripts = new();
private readonly Dictionary<IItem, ItemScript> _itemScripts = new();
/// <inheritdoc cref="ScriptResolver"/> /// <inheritdoc cref="ScriptResolver"/>
public ScriptResolver(IReadOnlyDictionary<(ScriptCategory, StringKey), Func<Script>> scriptCtors, public ScriptResolver(IReadOnlyDictionary<(ScriptCategory, StringKey), Func<Script>> scriptCtors,
@ -67,4 +68,32 @@ public class ScriptResolver
_itemBattleScripts[item] = script; _itemBattleScripts[item] = script;
return true; return true;
} }
/// <summary>
/// Try and resolve an item script for the given item. If the item does not have a script, return false.
/// </summary>
public bool TryResolveItemScript(IItem item, [MaybeNullWhen(false)] out ItemScript script)
{
if (_itemScripts.TryGetValue(item, out script))
{
return true;
}
var effect = item.Effect;
if (effect == null)
{
script = null;
return false;
}
if (!_itemScriptCtors.TryGetValue(effect.Name, out var scriptCtor))
{
script = null;
return false;
}
script = scriptCtor(item);
script.OnInitialize(effect.Parameters);
_itemScripts[item] = script;
return true;
}
} }

View File

@ -1840,6 +1840,9 @@
"price": 3000, "price": 3000,
"additionalData": { "additionalData": {
"flingPower": 30 "flingPower": 30
},
"effect": {
"name": "evolution_use"
} }
}, },
{ {
@ -4591,6 +4594,12 @@
"parameters": { "parameters": {
"heal_amount": 20 "heal_amount": 20
} }
},
"battleEffect": {
"name": "healing_item",
"parameters": {
"healAmount": 20
}
} }
}, },
{ {

View File

@ -0,0 +1,54 @@
using PkmnLib.Static.Species;
namespace PkmnLib.Plugin.Gen7.Scripts.Items;
[ItemScript("evolution_use")]
public class EvolutionItem : ItemScript
{
/// <inheritdoc />
public EvolutionItem(IItem item) : base(item)
{
}
/// <inheritdoc />
public override bool IsItemUsable => true;
/// <inheritdoc />
public override bool RequiresTarget => true;
/// <inheritdoc />
public override bool IsTargetValid(IPokemon target)
{
foreach (var x in target.Species.EvolutionData)
{
switch (x)
{
case ItemUseEvolution itemUseEvolution when itemUseEvolution.Item == Item.Name:
return true;
case ItemGenderEvolution itemGenderEvolution when itemGenderEvolution.Item == Item.Name:
return itemGenderEvolution.Gender == target.Gender;
}
}
return false;
}
/// <inheritdoc />
public override void OnUseWithTarget(IPokemon target, EventHook eventHook)
{
var evolutionData = target.Species.EvolutionData.FirstOrDefault(x =>
{
switch (x)
{
case ItemUseEvolution itemUseEvolution when itemUseEvolution.Item == Item.Name:
case ItemGenderEvolution itemGenderEvolution when itemGenderEvolution.Item == Item.Name:
return true;
default:
return false;
}
});
if (evolutionData == null)
return;
target.EvolveTo(evolutionData, eventHook);
}
}

View File

@ -29,7 +29,7 @@ public class HealingItem : ItemScript
} }
/// <inheritdoc /> /// <inheritdoc />
public override bool IsTargetValid(IPokemon target) => !target.IsFainted; public override bool IsTargetValid(IPokemon target) => !target.IsFainted && target.CurrentHealth < target.MaxHealth;
/// <inheritdoc /> /// <inheritdoc />
public override void OnUseWithTarget(IPokemon target, EventHook eventHook) public override void OnUseWithTarget(IPokemon target, EventHook eventHook)