The ExtractEmmyLuaAPI utility command, invoked with `--emmy-lua-api`, produces a documentation file that is used by the [OpenRA Lua Language Extension](https://marketplace.visualstudio.com/items?itemName=openra.vscode-openra-lua) to provide documentation and type information is VSCode and VSCode compatible editors when editing the Lua scripts. We improve the documentation and types produced by this utility in a few ways: - Require descriptions to be provided for all items. - Fix the type definitions of the base engine types (cpos, wpos, wangle, wdist, wvec, cvec) to match with the actual bindings on the C# side. Add some extra bindings for these types to increase their utility. - Introduce ScriptEmmyTypeOverrideAttribute to allow the C# side of the bindings to provide a more specific type. The utility command now requires this to be used to avoid accidentally exporting poor type information. - Fix a handful of scripts where the new type information revealed warnings. The ability to ScriptEmmyTypeOverrideAttribute allows parameters and return types to provide a more specific type compared to the previous, weak, type definition. For example LuaValue mapped to `any`, LuaTable mapped to `table`, and LuaFunction mapped to `function`. These types are all non-specific. `any` can be anything, `table` is a table without known types for its keys or values, `function` is a function with an unknown signature. Now, we can provide specific types. , e.g. instead of `table`, ReinforcementsGlobal.ReinforceWithTransport is able to specify `{ [1]: actor, [2]: actor[] }` - a table with keys 1 and 2, whose values are an actor, and a table of actors respectively. The callback functions in MapGlobal now have signatures, e.g. instead of `function` we have `fun(a: actor):boolean`. In UtilsGlobal, we also make use of generic types. These work in a similar fashion to generics in C#. These methods operate on collections, we can introduce a generic parameter named `T` for the type of the items in those collections. Now the return type and callback parameters can also use that generic type. This means the return type or callback functions operate on the same type as whatever type is in the collection you pass in. e.g. Utils.Do accepts a collection typed as `T[]` with a callback function invoked on each item typed as `fun(item: T)`. If you pass in actors, the callback operates on an actor. If you pass in strings, the callback operates on a string, etc. Overall, these changes should result in an improved user experience for those editing OpenRA Lua scripts in a compatible IDE.
164 lines
4.3 KiB
C#
164 lines
4.3 KiB
C#
#region Copyright & License Information
|
|
/*
|
|
* Copyright (c) The OpenRA Developers and Contributors
|
|
* This file is part of OpenRA, which is free software. It is made
|
|
* available to you under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation, either version 3 of
|
|
* the License, or (at your option) any later version. For more
|
|
* information, see COPYING.
|
|
*/
|
|
#endregion
|
|
|
|
using System;
|
|
using System.ComponentModel;
|
|
using System.Reflection;
|
|
using OpenRA.Mods.Common.Traits;
|
|
using OpenRA.Traits;
|
|
|
|
namespace OpenRA.Mods.Common
|
|
{
|
|
public class FacingInit : ValueActorInit<WAngle>, ISingleInstanceInit
|
|
{
|
|
public FacingInit(WAngle value)
|
|
: base(value) { }
|
|
}
|
|
|
|
public class TerrainOrientationInit : ValueActorInit<WRot>, ISingleInstanceInit, ISuppressInitExport
|
|
{
|
|
public TerrainOrientationInit(WRot value)
|
|
: base(value) { }
|
|
}
|
|
|
|
public class CreationActivityDelayInit : ValueActorInit<int>, ISingleInstanceInit
|
|
{
|
|
public CreationActivityDelayInit(int value)
|
|
: base(value) { }
|
|
}
|
|
|
|
public class DynamicFacingInit : ValueActorInit<Func<WAngle>>, ISingleInstanceInit
|
|
{
|
|
public DynamicFacingInit(Func<WAngle> value)
|
|
: base(value) { }
|
|
}
|
|
|
|
// Cannot use ValueInit because map.yaml is expected to use the numeric value instead of enum name
|
|
public class SubCellInit : ActorInit, ISingleInstanceInit
|
|
{
|
|
readonly int value;
|
|
public SubCellInit(SubCell value)
|
|
{
|
|
this.value = (int)value;
|
|
}
|
|
|
|
public virtual SubCell Value => (SubCell)value;
|
|
|
|
public void Initialize(MiniYaml yaml)
|
|
{
|
|
Initialize((int)FieldLoader.GetValue(nameof(value), typeof(int), yaml.Value));
|
|
}
|
|
|
|
public void Initialize(int value)
|
|
{
|
|
GetType()
|
|
.GetField(nameof(value), BindingFlags.NonPublic | BindingFlags.Instance)
|
|
?.SetValue(this, value);
|
|
}
|
|
|
|
public override MiniYaml Save()
|
|
{
|
|
return new MiniYaml(FieldSaver.FormatValue(value));
|
|
}
|
|
}
|
|
|
|
public class CenterPositionInit : ValueActorInit<WPos>, ISingleInstanceInit
|
|
{
|
|
public CenterPositionInit(WPos value)
|
|
: base(value) { }
|
|
}
|
|
|
|
// Allows maps / transformations to specify the faction variant of an actor.
|
|
public class FactionInit : ValueActorInit<string>, ISingleInstanceInit
|
|
{
|
|
public FactionInit(string value)
|
|
: base(value) { }
|
|
}
|
|
|
|
public class EffectiveOwnerInit : ValueActorInit<Player>
|
|
{
|
|
public EffectiveOwnerInit(Player value)
|
|
: base(value) { }
|
|
}
|
|
|
|
sealed class ActorInitLoader : TypeConverter
|
|
{
|
|
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
|
{
|
|
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
|
|
}
|
|
|
|
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
|
|
{
|
|
return new ActorInitActorReference(value as string);
|
|
}
|
|
|
|
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
|
{
|
|
return destinationType == typeof(string) || base.CanConvertTo(context, destinationType);
|
|
}
|
|
|
|
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
|
|
{
|
|
if (destinationType == typeof(string) && value is ActorInitActorReference reference)
|
|
return reference.InternalName;
|
|
|
|
return base.ConvertTo(context, culture, value, destinationType);
|
|
}
|
|
}
|
|
|
|
[TypeConverter(typeof(ActorInitLoader))]
|
|
public class ActorInitActorReference
|
|
{
|
|
public readonly string InternalName;
|
|
readonly Actor actor;
|
|
|
|
public ActorInitActorReference(Actor actor)
|
|
{
|
|
this.actor = actor;
|
|
}
|
|
|
|
public ActorInitActorReference(string internalName)
|
|
{
|
|
InternalName = internalName;
|
|
}
|
|
|
|
Actor InnerValue(World world)
|
|
{
|
|
if (actor != null)
|
|
return actor;
|
|
|
|
var sma = world.WorldActor.Trait<SpawnMapActors>();
|
|
return sma.Actors[InternalName];
|
|
}
|
|
|
|
/// <summary>
|
|
/// The lazy value may reference other actors that have not been created
|
|
/// yet, so must not be resolved from the actor constructor or Created method.
|
|
/// Use a FrameEndTask or wait until it is actually needed.
|
|
/// </summary>
|
|
public Lazy<Actor> Actor(World world)
|
|
{
|
|
return new Lazy<Actor>(() => InnerValue(world));
|
|
}
|
|
|
|
public static implicit operator ActorInitActorReference(Actor a)
|
|
{
|
|
return new ActorInitActorReference(a);
|
|
}
|
|
|
|
public static implicit operator ActorInitActorReference(string mapName)
|
|
{
|
|
return new ActorInitActorReference(mapName);
|
|
}
|
|
}
|
|
}
|