Implementation of Pokeballs
This commit is contained in:
parent
0518499a4c
commit
42e3273483
19
PkmnLib.Dynamic/Events/CaptureAttemptEvent.cs
Normal file
19
PkmnLib.Dynamic/Events/CaptureAttemptEvent.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using PkmnLib.Dynamic.Libraries;
|
||||
using PkmnLib.Dynamic.Models;
|
||||
|
||||
namespace PkmnLib.Dynamic.Events;
|
||||
|
||||
public class CaptureAttemptEvent : IEventData
|
||||
{
|
||||
public CaptureAttemptEvent(IPokemon target, CaptureResult result)
|
||||
{
|
||||
Target = target;
|
||||
Result = result;
|
||||
}
|
||||
|
||||
public IPokemon Target { get; init; }
|
||||
public CaptureResult Result { get; init; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public EventBatchId BatchId { get; init; }
|
||||
}
|
25
PkmnLib.Dynamic/Libraries/CaptureLibrary.cs
Normal file
25
PkmnLib.Dynamic/Libraries/CaptureLibrary.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using PkmnLib.Dynamic.Models;
|
||||
using PkmnLib.Static;
|
||||
|
||||
namespace PkmnLib.Dynamic.Libraries;
|
||||
|
||||
public record struct CaptureResult
|
||||
{
|
||||
public CaptureResult(bool IsCaught, int Shakes, bool CriticalCapture)
|
||||
{
|
||||
this.IsCaught = IsCaught;
|
||||
this.Shakes = Shakes;
|
||||
this.CriticalCapture = CriticalCapture;
|
||||
}
|
||||
|
||||
public bool IsCaught { get; init; }
|
||||
public int Shakes { get; init; }
|
||||
public bool CriticalCapture { get; init; }
|
||||
|
||||
public static CaptureResult Failed => new CaptureResult(false, 0, false);
|
||||
}
|
||||
|
||||
public interface ICaptureLibrary
|
||||
{
|
||||
CaptureResult TryCapture(IPokemon target, IItem captureItem, IBattleRandom random);
|
||||
}
|
@ -32,6 +32,11 @@ public interface IDynamicLibrary
|
||||
/// </summary>
|
||||
IMiscLibrary MiscLibrary { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The capture library deals with the calculation of the capture rate of a Pokémon.
|
||||
/// </summary>
|
||||
ICaptureLibrary CaptureLibrary { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A holder of the script types that can be resolved by this library.
|
||||
/// </summary>
|
||||
@ -58,19 +63,22 @@ public class DynamicLibraryImpl : IDynamicLibrary
|
||||
throw new InvalidOperationException("Stat calculator not found in plugins.");
|
||||
if (registry.MiscLibrary is null)
|
||||
throw new InvalidOperationException("Misc library not found in plugins.");
|
||||
if (registry.CaptureLibrary is null)
|
||||
throw new InvalidOperationException("Capture library not found in plugins.");
|
||||
var scriptResolver = new ScriptResolver(registry.ScriptTypes, registry.ItemScriptTypes);
|
||||
return new DynamicLibraryImpl(staticLibrary, registry.BattleStatCalculator,
|
||||
registry.DamageCalculator, registry.MiscLibrary, scriptResolver);
|
||||
registry.DamageCalculator, registry.MiscLibrary, registry.CaptureLibrary, scriptResolver);
|
||||
}
|
||||
|
||||
private DynamicLibraryImpl(IStaticLibrary staticLibrary, IBattleStatCalculator statCalculator,
|
||||
IDamageCalculator damageCalculator, IMiscLibrary miscLibrary, ScriptResolver scriptResolver)
|
||||
IDamageCalculator damageCalculator, IMiscLibrary miscLibrary, ICaptureLibrary captureLibrary, ScriptResolver scriptResolver)
|
||||
{
|
||||
StaticLibrary = staticLibrary;
|
||||
StatCalculator = statCalculator;
|
||||
DamageCalculator = damageCalculator;
|
||||
MiscLibrary = miscLibrary;
|
||||
ScriptResolver = scriptResolver;
|
||||
CaptureLibrary = captureLibrary;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -84,7 +92,10 @@ public class DynamicLibraryImpl : IDynamicLibrary
|
||||
|
||||
/// <inheritdoc />
|
||||
public IMiscLibrary MiscLibrary { get; }
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICaptureLibrary CaptureLibrary { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ScriptResolver ScriptResolver { get; }
|
||||
}
|
@ -3,6 +3,7 @@ using PkmnLib.Dynamic.Libraries;
|
||||
using PkmnLib.Dynamic.Models.BattleFlow;
|
||||
using PkmnLib.Dynamic.Models.Choices;
|
||||
using PkmnLib.Dynamic.ScriptHandling;
|
||||
using PkmnLib.Static;
|
||||
using PkmnLib.Static.Utils;
|
||||
|
||||
namespace PkmnLib.Dynamic.Models;
|
||||
@ -116,6 +117,8 @@ public interface IBattle : IScriptSource, IDeepCloneable
|
||||
/// for a single turn. The outer list is ordered from oldest to newest turn.
|
||||
/// </summary>
|
||||
IReadOnlyList<IReadOnlyList<ITurnChoice>> PreviousTurnChoices { get; }
|
||||
|
||||
CaptureResult AttempCapture(byte sideIndex, byte position, IItem item);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IBattle"/>
|
||||
@ -335,6 +338,24 @@ public class BattleImpl : ScriptSource, IBattle
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<IReadOnlyList<ITurnChoice>> PreviousTurnChoices => _previousTurnChoices;
|
||||
|
||||
/// <inheritdoc />
|
||||
public CaptureResult AttempCapture(byte sideIndex, byte position, IItem item)
|
||||
{
|
||||
var target = GetPokemon(sideIndex, position);
|
||||
if (target is not { IsUsable: true })
|
||||
return CaptureResult.Failed;
|
||||
|
||||
var attemptCapture = Library.CaptureLibrary.TryCapture(target, item, Random);
|
||||
if (attemptCapture.IsCaught)
|
||||
{
|
||||
target.MarkAsCaught();
|
||||
var side = Sides[target.BattleData!.SideIndex];
|
||||
side.ForceClearPokemonFromField(target.BattleData.Position);
|
||||
}
|
||||
EventHook.Invoke(new CaptureAttemptEvent(target, attemptCapture));
|
||||
return attemptCapture;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int ScriptCount => 2;
|
||||
|
||||
|
@ -217,6 +217,13 @@ public class BattleSideImpl : ScriptSource, IBattleSide
|
||||
/// <inheritdoc />
|
||||
public void ForceClearPokemonFromField(byte index)
|
||||
{
|
||||
var pokemon = _pokemon[index];
|
||||
if (pokemon is not null)
|
||||
{
|
||||
pokemon.RunScriptHook(script => script.OnRemove());
|
||||
pokemon.SetOnBattlefield(false);
|
||||
}
|
||||
|
||||
_pokemon[index] = null;
|
||||
}
|
||||
|
||||
|
@ -201,6 +201,8 @@ public interface IPokemon : IScriptSource, IDeepCloneable
|
||||
/// Whether or not this Pokemon was caught this battle.
|
||||
/// </summary>
|
||||
bool IsCaught { get; }
|
||||
|
||||
public void MarkAsCaught();
|
||||
|
||||
/// <summary>
|
||||
/// The script for the held item.
|
||||
@ -632,6 +634,12 @@ public class PokemonImpl : ScriptSource, IPokemon
|
||||
/// <inheritdoc />
|
||||
public bool IsCaught { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void MarkAsCaught()
|
||||
{
|
||||
IsCaught = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ScriptContainer HeldItemTriggerScript { get; } = new();
|
||||
|
||||
|
@ -1,10 +1,19 @@
|
||||
using PkmnLib.Dynamic.Models;
|
||||
using PkmnLib.Static;
|
||||
using PkmnLib.Static.Utils;
|
||||
|
||||
namespace PkmnLib.Dynamic.ScriptHandling;
|
||||
|
||||
public abstract class ItemScript : IDeepCloneable
|
||||
{
|
||||
protected ItemScript(IItem item)
|
||||
{
|
||||
Item = item;
|
||||
}
|
||||
|
||||
protected IItem Item { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the script with the given parameters for a specific item
|
||||
/// </summary>
|
||||
|
24
PkmnLib.Dynamic/ScriptHandling/PokeballScript.cs
Normal file
24
PkmnLib.Dynamic/ScriptHandling/PokeballScript.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using PkmnLib.Dynamic.Models;
|
||||
using PkmnLib.Static;
|
||||
|
||||
namespace PkmnLib.Dynamic.ScriptHandling;
|
||||
|
||||
public abstract class PokeballScript : ItemScript
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected PokeballScript(IItem item) : base(item)
|
||||
{
|
||||
}
|
||||
|
||||
public abstract byte GetCatchRate(IPokemon target);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnUseWithTarget(IPokemon target)
|
||||
{
|
||||
var battleData = target.BattleData;
|
||||
if (battleData == null)
|
||||
return;
|
||||
|
||||
battleData.Battle.AttempCapture(battleData.SideIndex, battleData.Position, Item);
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using JetBrains.Annotations;
|
||||
using PkmnLib.Dynamic.Libraries;
|
||||
using PkmnLib.Static;
|
||||
using PkmnLib.Static.Utils;
|
||||
|
||||
namespace PkmnLib.Dynamic.ScriptHandling.Registry;
|
||||
@ -13,10 +14,11 @@ namespace PkmnLib.Dynamic.ScriptHandling.Registry;
|
||||
public class ScriptRegistry
|
||||
{
|
||||
private readonly Dictionary<(ScriptCategory category, StringKey name), Func<Script>> _scriptTypes = new();
|
||||
private readonly Dictionary<StringKey, Func<ItemScript>> _itemScriptTypes = new();
|
||||
private readonly Dictionary<StringKey, Func<IItem, ItemScript>> _itemScriptTypes = new();
|
||||
private IBattleStatCalculator? _battleStatCalculator;
|
||||
private IDamageCalculator? _damageCalculator;
|
||||
private IMiscLibrary? _miscLibrary;
|
||||
private ICaptureLibrary? _captureLibrary;
|
||||
|
||||
/// <summary>
|
||||
/// Automatically register all scripts in the given assembly that have the <see cref="ScriptAttribute"/>, and
|
||||
@ -73,13 +75,15 @@ public class ScriptRegistry
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
|
||||
var constructor = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
|
||||
null, Type.EmptyTypes, null);
|
||||
null, [typeof(IItem)], null);
|
||||
if (constructor == null)
|
||||
throw new ArgumentException($"Type {type} does not have a parameterless constructor.");
|
||||
throw new ArgumentException($"Type {type} does not have a constructor that takes an IItem.");
|
||||
|
||||
// We create a lambda that creates a new instance of the script type.
|
||||
// This is more performant than using Activator.CreateInstance.
|
||||
_itemScriptTypes[name] = Expression.Lambda<Func<ItemScript>>(Expression.New(constructor)).Compile();
|
||||
var parameterExpression = Expression.Parameter(typeof(IItem), "item");
|
||||
var newExpression = Expression.New(constructor, parameterExpression);
|
||||
_itemScriptTypes[name] = Expression.Lambda<Func<IItem, ItemScript>>(newExpression, parameterExpression).Compile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -99,10 +103,17 @@ public class ScriptRegistry
|
||||
/// </summary>
|
||||
public void RegisterMiscLibrary<T>(T miscLibrary) where T : IMiscLibrary
|
||||
=> _miscLibrary = miscLibrary;
|
||||
|
||||
/// <summary>
|
||||
/// Register a capture library.
|
||||
/// </summary>
|
||||
public void RegisterCaptureLibrary<T>(T captureLibrary) where T : ICaptureLibrary
|
||||
=> _captureLibrary = captureLibrary;
|
||||
|
||||
internal IReadOnlyDictionary<(ScriptCategory category, StringKey name), Func<Script>> ScriptTypes => _scriptTypes;
|
||||
internal IReadOnlyDictionary<StringKey, Func<ItemScript>> ItemScriptTypes => _itemScriptTypes;
|
||||
internal IReadOnlyDictionary<StringKey, Func<IItem, ItemScript>> ItemScriptTypes => _itemScriptTypes;
|
||||
internal IBattleStatCalculator? BattleStatCalculator => _battleStatCalculator;
|
||||
internal IDamageCalculator? DamageCalculator => _damageCalculator;
|
||||
internal IMiscLibrary? MiscLibrary => _miscLibrary;
|
||||
internal ICaptureLibrary? CaptureLibrary => _captureLibrary;
|
||||
}
|
@ -493,7 +493,7 @@ public abstract class Script : IDeepCloneable
|
||||
/// rate of this attempt. Pokeball modifier effects should be implemented here, as well as for
|
||||
/// example status effects that change capture rates.
|
||||
/// </summary>
|
||||
public virtual void ChangeCaptureRateBonus(IPokemon pokemon, IItem pokeball, ref byte modifier)
|
||||
public virtual void ChangeCatchRateBonus(IPokemon pokemon, IItem pokeball, ref byte modifier)
|
||||
{
|
||||
}
|
||||
}
|
@ -10,11 +10,12 @@ namespace PkmnLib.Dynamic.ScriptHandling;
|
||||
public class ScriptResolver
|
||||
{
|
||||
private IReadOnlyDictionary<(ScriptCategory, StringKey), Func<Script>> _scriptCtors;
|
||||
private IReadOnlyDictionary<StringKey, Func<ItemScript>> _itemScriptCtors;
|
||||
private IReadOnlyDictionary<StringKey, Func<IItem, ItemScript>> _itemScriptCtors;
|
||||
private readonly Dictionary<IItem, ItemScript> _itemBattleScripts = new();
|
||||
|
||||
/// <inheritdoc cref="ScriptResolver"/>
|
||||
public ScriptResolver(IReadOnlyDictionary<(ScriptCategory, StringKey), Func<Script>> scriptCtors,
|
||||
IReadOnlyDictionary<StringKey, Func<ItemScript>> itemScriptCtors)
|
||||
IReadOnlyDictionary<StringKey, Func<IItem, ItemScript>> itemScriptCtors)
|
||||
{
|
||||
_scriptCtors = scriptCtors;
|
||||
_itemScriptCtors = itemScriptCtors;
|
||||
@ -46,6 +47,11 @@ public class ScriptResolver
|
||||
/// </summary>
|
||||
public bool TryResolveBattleItemScript(IItem item, [MaybeNullWhen(false)] out ItemScript script)
|
||||
{
|
||||
if (_itemBattleScripts.TryGetValue(item, out script))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var effect = item.BattleEffect;
|
||||
if (effect == null)
|
||||
{
|
||||
@ -58,8 +64,9 @@ public class ScriptResolver
|
||||
return false;
|
||||
}
|
||||
|
||||
script = scriptCtor();
|
||||
script = scriptCtor(item);
|
||||
script.OnInitialize(effect.Parameters);
|
||||
_itemBattleScripts[item] = script;
|
||||
return true;
|
||||
}
|
||||
}
|
@ -36,5 +36,6 @@ public class Gen7Plugin : Dynamic.ScriptHandling.Registry.Plugin
|
||||
registry.RegisterBattleStatCalculator(new Gen7BattleStatCalculator());
|
||||
registry.RegisterDamageCalculator(new Gen7DamageCalculator(_configuration.DamageCalculatorHasRandomness));
|
||||
registry.RegisterMiscLibrary(new Gen7MiscLibrary());
|
||||
registry.RegisterCaptureLibrary(new Gen7CaptureLibrary());
|
||||
}
|
||||
}
|
53
Plugins/PkmnLib.Plugin.Gen7/Libraries/Gen7CaptureLibrary.cs
Normal file
53
Plugins/PkmnLib.Plugin.Gen7/Libraries/Gen7CaptureLibrary.cs
Normal file
@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using PkmnLib.Dynamic.Libraries;
|
||||
using PkmnLib.Dynamic.Models;
|
||||
using PkmnLib.Dynamic.ScriptHandling;
|
||||
using PkmnLib.Dynamic.ScriptHandling.Registry;
|
||||
using PkmnLib.Static;
|
||||
|
||||
namespace PkmnLib.Plugin.Gen7.Libraries;
|
||||
|
||||
public class Gen7CaptureLibrary : ICaptureLibrary
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public CaptureResult TryCapture(IPokemon target, IItem captureItem, IBattleRandom random)
|
||||
{
|
||||
var maxHealth = target.BoostedStats.Hp;
|
||||
var currentHealth = target.CurrentHealth;
|
||||
var catchRate = target.Species.CaptureRate;
|
||||
|
||||
byte bonusBall = 1;
|
||||
if (target.Library.ScriptResolver.TryResolveBattleItemScript(captureItem, out var script) &&
|
||||
script is PokeballScript pokeballScript)
|
||||
{
|
||||
bonusBall = pokeballScript.GetCatchRate(target);
|
||||
}
|
||||
|
||||
byte bonusStatus = 1;
|
||||
target.RunScriptHook(x => x.ChangeCatchRateBonus(target, captureItem, ref bonusStatus));
|
||||
|
||||
var modifiedCatchRate =
|
||||
(((3.0 * maxHealth) - (2.0 * currentHealth)) * catchRate * bonusBall) / (3.0 * maxHealth);
|
||||
modifiedCatchRate *= bonusStatus;
|
||||
|
||||
var shakeProbability = 65536 / Math.Pow((255 / modifiedCatchRate), 0.1875);
|
||||
byte shakes = 0;
|
||||
if (modifiedCatchRate >= 255)
|
||||
{
|
||||
shakes = 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
// FIXME: Implement critical capture
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
if (random.GetInt(0, 65536) < shakeProbability)
|
||||
{
|
||||
shakes++;
|
||||
}
|
||||
}
|
||||
}
|
||||
var success = shakes >= 3;
|
||||
return new CaptureResult(success, shakes, false);
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ using System.Collections.Generic;
|
||||
using PkmnLib.Dynamic.Models;
|
||||
using PkmnLib.Dynamic.ScriptHandling;
|
||||
using PkmnLib.Dynamic.ScriptHandling.Registry;
|
||||
using PkmnLib.Static;
|
||||
using PkmnLib.Static.Utils;
|
||||
|
||||
namespace PkmnLib.Plugin.Gen7.Scripts.Items;
|
||||
@ -11,6 +12,11 @@ public class HealingItem : ItemScript
|
||||
{
|
||||
private uint _healAmount;
|
||||
|
||||
/// <inheritdoc />
|
||||
public HealingItem(IItem item) : base(item)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsItemUsable => true;
|
||||
|
||||
|
40
Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/StaticPokeball.cs
Normal file
40
Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/StaticPokeball.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using System.Collections.Generic;
|
||||
using PkmnLib.Dynamic.Models;
|
||||
using PkmnLib.Dynamic.ScriptHandling;
|
||||
using PkmnLib.Dynamic.ScriptHandling.Registry;
|
||||
using PkmnLib.Static;
|
||||
using PkmnLib.Static.Utils;
|
||||
|
||||
namespace PkmnLib.Plugin.Gen7.Scripts.Items;
|
||||
|
||||
/// <summary>
|
||||
/// An implementation of a pokeball script that just has a flat catch rate bonus.
|
||||
/// </summary>
|
||||
[ItemScript("pokeball")]
|
||||
public class StaticPokeball : PokeballScript
|
||||
{
|
||||
private byte _catchRate;
|
||||
|
||||
/// <inheritdoc />
|
||||
public StaticPokeball(IItem item) : base(item)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnInitialize(IReadOnlyDictionary<StringKey, object?>? parameters)
|
||||
{
|
||||
if (parameters == null || !parameters.TryGetValue("catchRate", out var catchRateObj) ||
|
||||
catchRateObj is not byte catchRate)
|
||||
{
|
||||
catchRate = 1;
|
||||
}
|
||||
|
||||
_catchRate = catchRate;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override byte GetCatchRate(IPokemon target)
|
||||
{
|
||||
return _catchRate;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user