Files
OpenRA/OpenRA.Game/WVec.cs
RoosterDragon ab28e6a75a Improve Lua type documentation and bindings.
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.
2024-08-03 19:12:51 +03:00

184 lines
6.4 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 Eluant;
using Eluant.ObjectBinding;
using OpenRA.Scripting;
using OpenRA.Support;
namespace OpenRA
{
public readonly struct WVec : IEquatable<WVec>, IScriptBindable,
ILuaAdditionBinding, ILuaSubtractionBinding, ILuaEqualityBinding, ILuaUnaryMinusBinding,
ILuaMultiplicationBinding, ILuaDivisionBinding, ILuaTableBinding, ILuaToStringBinding
{
public readonly int X, Y, Z;
public WVec(int x, int y, int z) { X = x; Y = y; Z = z; }
public WVec(WDist x, WDist y, WDist z) { X = x.Length; Y = y.Length; Z = z.Length; }
public static readonly WVec Zero = new(0, 0, 0);
public static WVec operator +(in WVec a, in WVec b) { return new WVec(a.X + b.X, a.Y + b.Y, a.Z + b.Z); }
public static WVec operator -(in WVec a, in WVec b) { return new WVec(a.X - b.X, a.Y - b.Y, a.Z - b.Z); }
public static WVec operator -(in WVec a) { return new WVec(-a.X, -a.Y, -a.Z); }
public static WVec operator /(in WVec a, int b) { return new WVec(a.X / b, a.Y / b, a.Z / b); }
public static WVec operator *(int a, in WVec b) { return new WVec(a * b.X, a * b.Y, a * b.Z); }
public static WVec operator *(in WVec a, int b) { return b * a; }
public static bool operator ==(in WVec me, in WVec other) { return me.X == other.X && me.Y == other.Y && me.Z == other.Z; }
public static bool operator !=(in WVec me, in WVec other) { return !(me == other); }
public static int Dot(in WVec a, in WVec b) { return a.X * b.X + a.Y * b.Y + a.Z * b.Z; }
public long LengthSquared => (long)X * X + (long)Y * Y + (long)Z * Z;
public int Length => (int)Exts.ISqrt(LengthSquared);
public long HorizontalLengthSquared => (long)X * X + (long)Y * Y;
public int HorizontalLength => (int)Exts.ISqrt(HorizontalLengthSquared);
public long VerticalLengthSquared => (long)Z * Z;
public int VerticalLength => (int)Exts.ISqrt(VerticalLengthSquared);
public WVec Rotate(in WRot rot)
{
rot.AsMatrix(out var mtx);
return Rotate(ref mtx);
}
public WVec Rotate(ref Int32Matrix4x4 mtx)
{
var lx = (long)X;
var ly = (long)Y;
var lz = (long)Z;
return new WVec(
(int)((lx * mtx.M11 + ly * mtx.M21 + lz * mtx.M31) / mtx.M44),
(int)((lx * mtx.M12 + ly * mtx.M22 + lz * mtx.M32) / mtx.M44),
(int)((lx * mtx.M13 + ly * mtx.M23 + lz * mtx.M33) / mtx.M44));
}
public WAngle Yaw
{
get
{
if (LengthSquared == 0)
return WAngle.Zero;
// OpenRA defines north as -y
return WAngle.ArcTan(-Y, X) - new WAngle(256);
}
}
public static WVec Lerp(in WVec a, in WVec b, int mul, int div) { return a + (b - a) * mul / div; }
public static WVec LerpQuadratic(in WVec a, in WVec b, WAngle pitch, int mul, int div)
{
// Start with a linear lerp between the points
var ret = Lerp(a, b, mul, div);
if (pitch.Angle == 0)
return ret;
// Add an additional quadratic variation to height
// Uses decimal to avoid integer overflow
var offset = (int)((decimal)(b - a).Length * pitch.Tan() * mul * (div - mul) / (1024 * div * div));
return new WVec(ret.X, ret.Y, ret.Z + offset);
}
// Sampled a N-sample probability density function in the range [-1024..1024, -1024..1024]
// 1 sample produces a rectangular probability
// 2 samples produces a triangular probability
// ...
// N samples approximates a true Gaussian
public static WVec FromPDF(MersenneTwister r, int samples)
{
return new WVec(WDist.FromPDF(r, samples), WDist.FromPDF(r, samples), WDist.Zero);
}
public override int GetHashCode() { return X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode(); }
public bool Equals(WVec other) { return other == this; }
public override bool Equals(object obj) { return obj is WVec vec && Equals(vec); }
public override string ToString() { return X + "," + Y + "," + Z; }
#region Scripting interface
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
{
if (!left.TryGetClrValue(out WVec a) || !right.TryGetClrValue(out WVec b))
throw new LuaException("Attempted to call WVec.Add(WVec, WVec) with invalid arguments " +
$"({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
return new LuaCustomClrObject(a + b);
}
public LuaValue Subtract(LuaRuntime runtime, LuaValue left, LuaValue right)
{
if (!left.TryGetClrValue(out WVec a) || !right.TryGetClrValue(out WVec b))
throw new LuaException("Attempted to call WVec.Subtract(WVec, WVec) with invalid arguments " +
$"({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
return new LuaCustomClrObject(a - b);
}
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
{
if (!left.TryGetClrValue(out WVec a) || !right.TryGetClrValue(out WVec b))
return false;
return a == b;
}
public LuaValue Minus(LuaRuntime runtime)
{
return new LuaCustomClrObject(-this);
}
public LuaValue Multiply(LuaRuntime runtime, LuaValue left, LuaValue right)
{
if (!left.TryGetClrValue(out WVec a) || !right.TryGetClrValue(out int b))
throw new LuaException("Attempted to call WVec.Multiply(WVec, integer) with invalid arguments " +
$"({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
return new LuaCustomClrObject(a * b);
}
public LuaValue Divide(LuaRuntime runtime, LuaValue left, LuaValue right)
{
if (!left.TryGetClrValue(out WVec a) || !right.TryGetClrValue(out int b))
throw new LuaException("Attempted to call WVec.Divide(WVec, integer) with invalid arguments " +
$"({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
return new LuaCustomClrObject(a / b);
}
public LuaValue this[LuaRuntime runtime, LuaValue key]
{
get
{
switch (key.ToString())
{
case "X": return X;
case "Y": return Y;
case "Z": return Z;
case "Facing": return new LuaCustomClrObject(Yaw);
default: throw new LuaException($"WVec does not define a member '{key}'");
}
}
set => throw new LuaException("WVec is read-only. Use WVec.New to create a new value");
}
public LuaValue ToString(LuaRuntime runtime) => ToString();
#endregion
}
}