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.
217 lines
5.6 KiB
C#
217 lines
5.6 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.Linq;
|
|
using Eluant;
|
|
using OpenRA.Mods.Common.Activities;
|
|
using OpenRA.Mods.Common.Effects;
|
|
using OpenRA.Mods.Common.Traits;
|
|
using OpenRA.Primitives;
|
|
using OpenRA.Scripting;
|
|
using OpenRA.Traits;
|
|
|
|
namespace OpenRA.Mods.Common.Scripting
|
|
{
|
|
[ExposedForDestroyedActors]
|
|
[ScriptPropertyGroup("General")]
|
|
public class BaseActorProperties : ScriptActorProperties
|
|
{
|
|
// Note: This class must not make any trait queries so that this
|
|
// remains safe to call on dead actors.
|
|
public BaseActorProperties(ScriptContext context, Actor self)
|
|
: base(context, self) { }
|
|
|
|
[Desc("Specifies whether the actor is in the world.")]
|
|
public bool IsInWorld
|
|
{
|
|
get => Self.IsInWorld;
|
|
|
|
set
|
|
{
|
|
if (value)
|
|
Self.World.AddFrameEndTask(w => w.Add(Self));
|
|
else
|
|
Self.World.AddFrameEndTask(w => w.Remove(Self));
|
|
}
|
|
}
|
|
|
|
[Desc("Specifies whether the actor is alive or dead.")]
|
|
public bool IsDead => Self.IsDead;
|
|
|
|
[Desc("Specifies whether the actor is idle (not performing any activities).")]
|
|
public bool IsIdle => Self.IsIdle;
|
|
|
|
[Desc("The player that owns the actor.")]
|
|
public Player Owner
|
|
{
|
|
get => Self.Owner;
|
|
|
|
set
|
|
{
|
|
if (value == null)
|
|
throw new LuaException($"Attempted to change the owner of actor '{Self}' to nil value.");
|
|
|
|
if (Self.Owner != value)
|
|
Self.ChangeOwner(value);
|
|
}
|
|
}
|
|
|
|
[Desc("The type of the actor (e.g. \"e1\").")]
|
|
public string Type => Self.Info.Name;
|
|
|
|
[Desc("Test whether an actor has a specific property.")]
|
|
public bool HasProperty(string name)
|
|
{
|
|
return Self.HasScriptProperty(name);
|
|
}
|
|
|
|
[Desc("Render a target flash on the actor.")]
|
|
public void Flash(Color color, int count = 2, int interval = 2, int delay = 0)
|
|
{
|
|
// TODO: We can't use floats with Lua, so use the default 0.5f here
|
|
Self.World.Add(new FlashTarget(Self, color, 0.5f, count, interval, delay));
|
|
}
|
|
|
|
[Desc("The effective owner of the actor.")]
|
|
public Player EffectiveOwner
|
|
{
|
|
get
|
|
{
|
|
if (Self.EffectiveOwner == null || Self.EffectiveOwner.Owner == null)
|
|
return Self.Owner;
|
|
|
|
return Self.EffectiveOwner.Owner;
|
|
}
|
|
}
|
|
}
|
|
|
|
[ScriptPropertyGroup("General")]
|
|
public class GeneralProperties : ScriptActorProperties
|
|
{
|
|
readonly IFacing facing;
|
|
readonly AutoTarget autotarget;
|
|
readonly ScriptTags scriptTags;
|
|
readonly Tooltip[] tooltips;
|
|
|
|
public GeneralProperties(ScriptContext context, Actor self)
|
|
: base(context, self)
|
|
{
|
|
facing = self.TraitOrDefault<IFacing>();
|
|
autotarget = self.TraitOrDefault<AutoTarget>();
|
|
scriptTags = self.TraitOrDefault<ScriptTags>();
|
|
tooltips = self.TraitsImplementing<Tooltip>().ToArray();
|
|
}
|
|
|
|
[Desc("The actor position in cell coordinates.")]
|
|
public CPos Location => Self.Location;
|
|
|
|
[Desc("The actor position in world coordinates.")]
|
|
public WPos CenterPosition => Self.CenterPosition;
|
|
|
|
[Desc("The direction that the actor is facing.")]
|
|
public WAngle Facing
|
|
{
|
|
get
|
|
{
|
|
if (facing == null)
|
|
throw new LuaException($"Actor '{Self}' doesn't define a facing");
|
|
|
|
return facing.Facing;
|
|
}
|
|
}
|
|
|
|
[ScriptActorPropertyActivity]
|
|
[Desc("Instantly moves the actor to the specified cell.")]
|
|
public void Teleport(CPos cell)
|
|
{
|
|
Self.QueueActivity(new SimpleTeleport(cell));
|
|
}
|
|
|
|
[ScriptActorPropertyActivity]
|
|
[Desc("Run an arbitrary Lua function.")]
|
|
public void CallFunc([ScriptEmmyTypeOverride("fun()")] LuaFunction func)
|
|
{
|
|
Self.QueueActivity(new CallLuaFunc(func, Context));
|
|
}
|
|
|
|
[ScriptActorPropertyActivity]
|
|
[Desc("Wait for a specified number of game ticks (25 ticks = 1 second).")]
|
|
public void Wait(int ticks)
|
|
{
|
|
Self.QueueActivity(new Wait(ticks));
|
|
}
|
|
|
|
[ScriptActorPropertyActivity]
|
|
[Desc("Remove the actor from the game, without triggering any death notification.")]
|
|
public void Destroy()
|
|
{
|
|
Self.QueueActivity(new RemoveSelf());
|
|
}
|
|
|
|
[Desc("Attempt to cancel any active activities.")]
|
|
public void Stop()
|
|
{
|
|
Self.CancelActivity();
|
|
}
|
|
|
|
[Desc("Current actor stance. Returns nil if this actor doesn't support stances.")]
|
|
public string Stance
|
|
{
|
|
get => autotarget?.Stance.ToString();
|
|
|
|
set
|
|
{
|
|
if (autotarget == null)
|
|
return;
|
|
|
|
if (!Enum<UnitStance>.TryParse(value, true, out var stance))
|
|
throw new LuaException($"Unknown stance type '{value}'");
|
|
|
|
autotarget.SetStance(Self, stance);
|
|
}
|
|
}
|
|
|
|
[Desc("The actor's tooltip name. Returns nil if the actor has no tooltip.")]
|
|
public string TooltipName
|
|
{
|
|
get
|
|
{
|
|
var tooltip = tooltips.FirstEnabledConditionalTraitOrDefault();
|
|
if (tooltip == null)
|
|
return null;
|
|
|
|
return TranslationProvider.GetString(tooltip.Info.Name);
|
|
}
|
|
}
|
|
|
|
[Desc("Specifies whether or not the actor supports 'tags'.")]
|
|
public bool IsTaggable => scriptTags != null;
|
|
|
|
[Desc("Add a tag to the actor. Returns true on success, false otherwise (for example the actor may already have the given tag).")]
|
|
public bool AddTag(string tag)
|
|
{
|
|
return IsTaggable && scriptTags.AddTag(tag);
|
|
}
|
|
|
|
[Desc("Remove a tag from the actor. Returns true on success, false otherwise (tag was not present).")]
|
|
public bool RemoveTag(string tag)
|
|
{
|
|
return IsTaggable && scriptTags.RemoveTag(tag);
|
|
}
|
|
|
|
[Desc("Specifies whether or not the actor has a particular tag.")]
|
|
public bool HasTag(string tag)
|
|
{
|
|
return IsTaggable && scriptTags.HasTag(tag);
|
|
}
|
|
}
|
|
}
|