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>
/// Marks the Pokemon as caught. This makes it so that the Pokemon is not considered valid in battle anymore.
/// </summary>
public void MarkAsCaught();
void MarkAsCaught();
/// <summary>
/// The script for the held item.
@ -293,7 +293,7 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// <summary>
/// Suppresses the ability of the Pokémon.
/// </summary>
public void SuppressAbility();
void SuppressAbility();
/// <summary>
/// Returns the currently active ability.
@ -313,15 +313,21 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// </summary>
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>
/// Change the species of the Pokemon.
/// </summary>
void ChangeSpecies(ISpecies species, IForm form);
void ChangeSpecies(ISpecies species, IForm form, EventHook? eventHook = null);
/// <summary>
/// Change the form of the Pokemon.
/// </summary>
void ChangeForm(IForm form, EventBatchId batchId = default);
void ChangeForm(IForm form, EventBatchId batchId = default, EventHook? eventHook = null);
/// <summary>
/// Whether the Pokemon is useable in a battle.
@ -606,7 +612,7 @@ public class PokemonImpl : ScriptSource, IPokemon
public IDynamicLibrary Library { get; }
/// <inheritdoc />
public ISpecies Species { get; }
public ISpecies Species { get; private set; }
/// <inheritdoc />
public IForm Form { get; private set; }
@ -985,8 +991,25 @@ public class PokemonImpl : ScriptSource, IPokemon
public void RecalculateBoostedStats() => Library.StatCalculator.CalculateBoostedStats(this, BoostedStats);
/// <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 (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);
}
/// <inheritdoc />
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,
});

View File

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

View File

@ -12,6 +12,7 @@ public class ScriptResolver
private IReadOnlyDictionary<(ScriptCategory, StringKey), Func<Script>> _scriptCtors;
private IReadOnlyDictionary<StringKey, Func<IItem, ItemScript>> _itemScriptCtors;
private readonly Dictionary<IItem, ItemScript> _itemBattleScripts = new();
private readonly Dictionary<IItem, ItemScript> _itemScripts = new();
/// <inheritdoc cref="ScriptResolver"/>
public ScriptResolver(IReadOnlyDictionary<(ScriptCategory, StringKey), Func<Script>> scriptCtors,
@ -67,4 +68,32 @@ public class ScriptResolver
_itemBattleScripts[item] = script;
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,
"additionalData": {
"flingPower": 30
},
"effect": {
"name": "evolution_use"
}
},
{
@ -4591,6 +4594,12 @@
"parameters": {
"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 />
public override bool IsTargetValid(IPokemon target) => !target.IsFainted;
public override bool IsTargetValid(IPokemon target) => !target.IsFainted && target.CurrentHealth < target.MaxHealth;
/// <inheritdoc />
public override void OnUseWithTarget(IPokemon target, EventHook eventHook)