Begin work on outlining dynamic side
This commit is contained in:
23
PkmnLib.Dynamic/ScriptHandling/Registry/Plugin.cs
Normal file
23
PkmnLib.Dynamic/ScriptHandling/Registry/Plugin.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace PkmnLib.Dynamic.ScriptHandling.Registry;
|
||||
|
||||
/// <summary>
|
||||
/// A plugin is a way to register scripts and other dynamic components to the script registry.
|
||||
/// </summary>
|
||||
public abstract class Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the plugin. Mostly used for debugging purposes.
|
||||
/// </summary>
|
||||
public abstract string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// When the plugin should be loaded. Lower values are loaded first.
|
||||
/// 0 should be reserved for the core battle scripts.
|
||||
/// </summary>
|
||||
public abstract uint LoadOrder { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Run the registration of the plugin when we're building the library.
|
||||
/// </summary>
|
||||
public abstract void Register(ScriptRegistry registry);
|
||||
}
|
||||
14
PkmnLib.Dynamic/ScriptHandling/Registry/ScriptAttribute.cs
Normal file
14
PkmnLib.Dynamic/ScriptHandling/Registry/ScriptAttribute.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace PkmnLib.Dynamic.ScriptHandling;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class ScriptAttribute : Attribute
|
||||
{
|
||||
public ScriptCategory Category { get; }
|
||||
public string Name { get; }
|
||||
|
||||
public ScriptAttribute(ScriptCategory category, string name)
|
||||
{
|
||||
Category = category;
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
56
PkmnLib.Dynamic/ScriptHandling/Registry/ScriptRegistry.cs
Normal file
56
PkmnLib.Dynamic/ScriptHandling/Registry/ScriptRegistry.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using PkmnLib.Dynamic.Libraries;
|
||||
|
||||
namespace PkmnLib.Dynamic.ScriptHandling;
|
||||
|
||||
public class ScriptRegistry
|
||||
{
|
||||
private Dictionary<(ScriptCategory category, string name), Func<Script>> _scriptTypes = new();
|
||||
private IBattleStatCalculator? _battleStatCalculator;
|
||||
private IDamageCalculator? _damageCalculator;
|
||||
private IMiscLibrary? _miscLibrary;
|
||||
|
||||
public void RegisterAssemblyScripts(Assembly assembly)
|
||||
{
|
||||
var baseType = typeof(Script);
|
||||
foreach (var type in assembly.GetTypes().Where(t => baseType.IsAssignableFrom(t)))
|
||||
{
|
||||
var attribute = type.GetCustomAttribute<ScriptAttribute>();
|
||||
if (attribute == null)
|
||||
continue;
|
||||
|
||||
RegisterScriptType(attribute.Category, attribute.Name, type);
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterScriptType(ScriptCategory category, string name, Type type)
|
||||
{
|
||||
if (name == null)
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
if (type == null)
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
|
||||
var constructor = type.GetConstructor(Type.EmptyTypes);
|
||||
if (constructor == null)
|
||||
throw new ArgumentException("The type must have a parameterless constructor.");
|
||||
|
||||
// We create a lambda that creates a new instance of the script type.
|
||||
// This is more performant than using Activator.CreateInstance.
|
||||
_scriptTypes[(category, name)] = Expression.Lambda<Func<Script>>(Expression.New(constructor)).Compile();
|
||||
}
|
||||
|
||||
public void RegisterBattleStatCalculator<T>(T battleStatCalculator)
|
||||
where T : IBattleStatCalculator => _battleStatCalculator = battleStatCalculator;
|
||||
|
||||
public void RegisterDamageCalculator<T>(T damageCalculator)
|
||||
where T : IDamageCalculator => _damageCalculator = damageCalculator;
|
||||
|
||||
public void RegisterMiscLibrary<T>(T miscLibrary) where T : IMiscLibrary
|
||||
=> _miscLibrary = miscLibrary;
|
||||
|
||||
internal Dictionary<(ScriptCategory category, string name), Func<Script>> ScriptTypes => _scriptTypes;
|
||||
internal IBattleStatCalculator? BattleStatCalculator => _battleStatCalculator;
|
||||
internal IDamageCalculator? DamageCalculator => _damageCalculator;
|
||||
internal IMiscLibrary? MiscLibrary => _miscLibrary;
|
||||
}
|
||||
6
PkmnLib.Dynamic/ScriptHandling/Script.cs
Normal file
6
PkmnLib.Dynamic/ScriptHandling/Script.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace PkmnLib.Dynamic.ScriptHandling;
|
||||
|
||||
public abstract class Script
|
||||
{
|
||||
|
||||
}
|
||||
57
PkmnLib.Dynamic/ScriptHandling/ScriptCategory.cs
Normal file
57
PkmnLib.Dynamic/ScriptHandling/ScriptCategory.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using PkmnLib.Dynamic.Models;
|
||||
using PkmnLib.Dynamic.Models.Choices;
|
||||
|
||||
namespace PkmnLib.Dynamic.ScriptHandling;
|
||||
|
||||
/// <summary>
|
||||
/// A script category defines a sub-group of scripts. This can be used to have multiple scripts with
|
||||
/// the same name, but a different script. It should be completely valid for a move to have the same
|
||||
/// name as an ability, or more commonly: for a script attached to a Pokemon to have the same name as
|
||||
/// a move that placed it there.
|
||||
/// </summary>
|
||||
public enum ScriptCategory
|
||||
{
|
||||
/// <summary>
|
||||
/// A script that belongs to a move. This generally is only the script that is attached to a
|
||||
/// <see cref="IMoveChoice"/> and <see cref="IExecutingMove"/>
|
||||
/// </summary>
|
||||
Move = 0,
|
||||
|
||||
/// <summary>
|
||||
/// An ability script. Scripts in this category are always abilities, and therefore always
|
||||
/// attached to a Pokemon.
|
||||
/// </summary>
|
||||
Ability = 1,
|
||||
|
||||
/// <summary>
|
||||
/// A non volatile status script. Scripts in this category are always non volatile statuses, and
|
||||
/// therefore always attached to a Pokemon.
|
||||
/// </summary>
|
||||
Status = 2,
|
||||
|
||||
/// <summary>
|
||||
/// A volatile status script. Scripts in this category are always volatile status effects, and
|
||||
/// therefore always attached to a Pokemon.
|
||||
/// </summary>
|
||||
Pokemon = 3,
|
||||
|
||||
/// <summary>
|
||||
/// A script that can be attached to an entire side.
|
||||
/// </summary>
|
||||
Side = 4,
|
||||
|
||||
/// <summary>
|
||||
/// A script that can be attached to the entire battle.
|
||||
/// </summary>
|
||||
Battle = 5,
|
||||
|
||||
/// <summary>
|
||||
/// A special script for weather, for use on battles.
|
||||
/// </summary>
|
||||
Weather = 6,
|
||||
|
||||
/// <summary>
|
||||
/// A special script for held items. As they're part of a held item, they're attached to a Pokemon.
|
||||
/// </summary>
|
||||
ItemBattleTrigger = 7,
|
||||
}
|
||||
22
PkmnLib.Dynamic/ScriptHandling/ScriptContainer.cs
Normal file
22
PkmnLib.Dynamic/ScriptHandling/ScriptContainer.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace PkmnLib.Dynamic.ScriptHandling;
|
||||
|
||||
public class ScriptContainer : IEnumerable<ScriptContainer>
|
||||
{
|
||||
private Script? _script = null;
|
||||
|
||||
public bool IsEmpty => _script is null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerator<ScriptContainer> GetEnumerator()
|
||||
{
|
||||
yield return this;
|
||||
}
|
||||
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
71
PkmnLib.Dynamic/ScriptHandling/ScriptIterator.cs
Normal file
71
PkmnLib.Dynamic/ScriptHandling/ScriptIterator.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace PkmnLib.Dynamic.ScriptHandling;
|
||||
|
||||
public class ScriptIterator : IEnumerable<ScriptContainer>
|
||||
{
|
||||
private readonly IReadOnlyList<IEnumerable<ScriptContainer>> _scripts;
|
||||
private int _index = -1;
|
||||
private int _setIndex = -1;
|
||||
|
||||
public ScriptIterator(IReadOnlyList<IEnumerable<ScriptContainer>> scripts)
|
||||
{
|
||||
_scripts = scripts;
|
||||
}
|
||||
|
||||
bool IncrementToNext()
|
||||
{
|
||||
if (_index != -1)
|
||||
{
|
||||
var current = _scripts[_index];
|
||||
if (current is IScriptSet)
|
||||
{
|
||||
_setIndex += 1;
|
||||
if (_setIndex >= current.Count())
|
||||
{
|
||||
_setIndex = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_index += 1;
|
||||
for (; _index < _scripts.Count; _index++)
|
||||
{
|
||||
switch (_scripts[_index])
|
||||
{
|
||||
case IScriptSet:
|
||||
_setIndex = 0;
|
||||
return true;
|
||||
case ScriptContainer { IsEmpty: false }:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerator<ScriptContainer> GetEnumerator()
|
||||
{
|
||||
while (IncrementToNext())
|
||||
{
|
||||
var current = _scripts[_index];
|
||||
yield return current switch
|
||||
{
|
||||
IScriptSet set => set.At(_setIndex),
|
||||
ScriptContainer container => container,
|
||||
_ => throw new InvalidOperationException("Invalid script type")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
16
PkmnLib.Dynamic/ScriptHandling/ScriptSet.cs
Normal file
16
PkmnLib.Dynamic/ScriptHandling/ScriptSet.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using FluentResults;
|
||||
|
||||
namespace PkmnLib.Dynamic.ScriptHandling;
|
||||
|
||||
public interface IScriptSet : IEnumerable<ScriptContainer>
|
||||
{
|
||||
Result<ScriptContainer> Add(Script script);
|
||||
Result<ScriptContainer?> Add(string scriptKey);
|
||||
ScriptContainer? Get(string scriptKey);
|
||||
void Remove(string scriptKey);
|
||||
void Clear();
|
||||
void Contains(string scriptKey);
|
||||
ScriptContainer At(int index);
|
||||
int Count { get; }
|
||||
IEnumerable<string> GetScriptNames();
|
||||
}
|
||||
52
PkmnLib.Dynamic/ScriptHandling/ScriptSource.cs
Normal file
52
PkmnLib.Dynamic/ScriptHandling/ScriptSource.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
namespace PkmnLib.Dynamic.ScriptHandling;
|
||||
|
||||
public interface IScriptSource
|
||||
{
|
||||
ScriptIterator GetScripts();
|
||||
|
||||
/// <summary>
|
||||
/// The number of scripts that are expected to be relevant for this source. This generally is
|
||||
/// The number of its own scripts + the number of scripts for any parents.
|
||||
/// </summary>
|
||||
int ScriptCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// This should add all scripts belonging to this source to the scripts Vec, disregarding its
|
||||
/// potential parents.
|
||||
/// </summary>
|
||||
/// <param name="scripts"></param>
|
||||
void GetOwnScripts(List<IEnumerable<ScriptContainer>> scripts);
|
||||
|
||||
/// <summary>
|
||||
/// This should add all scripts that are relevant to the source the the scripts Vec, including
|
||||
/// everything that belongs to its parents.
|
||||
/// </summary>
|
||||
/// <param name="scripts"></param>
|
||||
void CollectScripts(List<IEnumerable<ScriptContainer>> scripts);
|
||||
}
|
||||
|
||||
public abstract class ScriptSource : IScriptSource
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public ScriptIterator GetScripts()
|
||||
{
|
||||
if (_scripts == null)
|
||||
{
|
||||
_scripts = new List<IEnumerable<ScriptContainer>>(ScriptCount);
|
||||
CollectScripts(_scripts);
|
||||
}
|
||||
return new ScriptIterator(_scripts);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract int ScriptCount { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void GetOwnScripts(List<IEnumerable<ScriptContainer>> scripts);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void CollectScripts(List<IEnumerable<ScriptContainer>> scripts);
|
||||
|
||||
/// <inheritdoc />
|
||||
private List<IEnumerable<ScriptContainer>>? _scripts;
|
||||
}
|
||||
Reference in New Issue
Block a user