Initial commit: OpenRA game engine
Some checks failed
Continuous Integration / Linux (.NET 8.0) (push) Has been cancelled
Continuous Integration / Windows (.NET 8.0) (push) Has been cancelled

Fork from OpenRA/OpenRA with one-click launch script (start-ra.cmd)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
let5sne.win10
2026-01-10 21:46:54 +08:00
commit 9cf6ebb986
4065 changed files with 635973 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
#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 OpenRA.Mods.Common.Activities;
using OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("Movement")]
public class AircraftProperties : ScriptActorProperties, Requires<AircraftInfo>
{
readonly Aircraft aircraft;
public AircraftProperties(ScriptContext context, Actor self)
: base(context, self)
{
aircraft = self.Trait<Aircraft>();
}
[ScriptActorPropertyActivity]
[Desc("Fly within the cell grid.")]
public void Move(CPos cell)
{
Self.QueueActivity(new Fly(Self, Target.FromCell(Self.World, cell)));
}
[ScriptActorPropertyActivity]
[Desc("Return to the base, which is either the destination given, or an auto-selected one otherwise.")]
public void ReturnToBase(Actor destination = null)
{
Self.QueueActivity(new ReturnToBase(Self, destination, true));
}
[ScriptActorPropertyActivity]
[Desc("Queues a landing activity on the specified actor.")]
public void Land(Actor landOn)
{
Self.QueueActivity(new Land(Self, Target.FromActor(landOn)));
}
[ScriptActorPropertyActivity]
[Desc("Starts the resupplying activity when being on a host building.")]
public void Resupply()
{
var atLandAltitude = Self.World.Map.DistanceAboveTerrain(Self.CenterPosition) == aircraft.Info.LandAltitude;
var host = aircraft.GetActorBelow();
if (atLandAltitude && host != null)
Self.QueueActivity(new Resupply(Self, host, WDist.Zero));
}
}
}

View File

@@ -0,0 +1,39 @@
#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 OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("Support Powers")]
public class AirstrikeProperties : ScriptActorProperties, Requires<AirstrikePowerInfo>
{
readonly AirstrikePower ap;
public AirstrikeProperties(ScriptContext context, Actor self)
: base(context, self)
{
ap = self.TraitsImplementing<AirstrikePower>().First();
}
[Desc("Activate the actor's Airstrike Power. Returns the aircraft that will attack.")]
public Actor[] TargetAirstrike(WPos target, WAngle? facing = null)
{
foreach (var notify in Self.TraitsImplementing<INotifySupportPower>())
notify.Activated(Self);
return ap.SendAirstrike(Self, target, facing);
}
}
}

View File

@@ -0,0 +1,67 @@
#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.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("AmmoPool")]
public class AmmoPoolProperties : ScriptActorProperties, Requires<AmmoPoolInfo>
{
readonly Actor self;
readonly AmmoPool[] ammoPools;
public AmmoPoolProperties(ScriptContext context, Actor self)
: base(context, self)
{
this.self = self;
ammoPools = self.TraitsImplementing<AmmoPool>().ToArray();
}
[Desc("Returns the count of the actor's specified ammopool.")]
public int AmmoCount(string poolName = "primary")
{
var pool = ammoPools.FirstOrDefault(a => a.Info.Name == poolName);
if (pool == null)
throw new LuaException($"Invalid ammopool name {poolName} queried on actor {self}.");
return pool.CurrentAmmoCount;
}
[Desc("Returns the maximum count of ammo the actor can load.")]
public int MaximumAmmoCount(string poolName = "primary")
{
var pool = ammoPools.FirstOrDefault(a => a.Info.Name == poolName);
if (pool == null)
throw new LuaException($"Invalid ammopool name {poolName} queried on actor {self}.");
return pool.Info.Ammo;
}
[Desc("Adds the specified amount of ammo to the specified ammopool.",
"(Use a negative amount to remove ammo.)")]
public void Reload(string poolName = "primary", int amount = 1)
{
var pool = ammoPools.FirstOrDefault(a => a.Info.Name == poolName);
if (pool == null)
throw new LuaException($"Invalid ammopool name {poolName} queried on actor {self}.");
if (amount > 0)
pool.GiveAmmo(self, amount);
else
pool.TakeAmmo(self, -amount);
}
}
}

View File

@@ -0,0 +1,47 @@
#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 OpenRA.Mods.Common.Activities;
using OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("Ability")]
public class CaptureProperties : ScriptActorProperties, Requires<CaptureManagerInfo>
{
readonly CaptureManager captureManager;
public CaptureProperties(ScriptContext context, Actor self)
: base(context, self)
{
captureManager = Self.Trait<CaptureManager>();
}
[Desc("Captures the target actor.")]
public void Capture(Actor target)
{
if (!CanCapture(target))
return;
// NB: Scripted actions get no visible targetlines.
Self.QueueActivity(new CaptureActor(Self, Target.FromActor(target), null));
}
[Desc("Checks if the target actor can be captured.")]
public bool CanCapture(Actor target)
{
var targetManager = target.TraitOrDefault<CaptureManager>();
return targetManager != null && captureManager.CanTarget(targetManager);
}
}
}

View File

@@ -0,0 +1,49 @@
#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 Eluant;
using OpenRA.Mods.Common.Activities;
using OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("Ability")]
public class CarryallProperties : ScriptActorProperties, Requires<CarryallInfo>
{
readonly Carryall carryall;
public CarryallProperties(ScriptContext context, Actor self)
: base(context, self)
{
carryall = Self.Trait<Carryall>();
}
[ScriptActorPropertyActivity]
[Desc("Pick up the target actor.")]
public void PickupCarryable(Actor target)
{
var carryable = target.TraitOrDefault<Carryable>();
if (carryable == null)
throw new LuaException($"Actor '{Self}' cannot carry actor '{target}'!");
Self.QueueActivity(new PickupUnit(Self, target, carryall.Info.BeforeLoadDelay, null));
}
[ScriptActorPropertyActivity]
[Desc("Drop the actor being carried at the target location.")]
public void DeliverCarryable(CPos target)
{
Self.QueueActivity(new DeliverUnit(Self, Target.FromCell(Self.World, target), carryall.Info.DropRange, null));
}
}
}

View File

@@ -0,0 +1,39 @@
#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 OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("Cloak")]
public class CloakProperties : ScriptActorProperties, Requires<CloakInfo>
{
readonly Cloak[] cloaks;
public CloakProperties(ScriptContext context, Actor self)
: base(context, self)
{
cloaks = self.TraitsImplementing<Cloak>().ToArray();
}
[Desc("Returns true if the actor is cloaked.")]
public bool IsCloaked
{
get
{
return cloaks.Any(c => c.Cloaked);
}
}
}
}

View File

@@ -0,0 +1,110 @@
#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.Activities;
using OpenRA.Mods.Common.Activities;
using OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("Combat")]
public class CombatProperties : ScriptActorProperties, Requires<AttackBaseInfo>, Requires<IMoveInfo>
{
readonly IMove move;
public CombatProperties(ScriptContext context, Actor self)
: base(context, self)
{
move = self.Trait<IMove>();
}
[ScriptActorPropertyActivity]
[Desc("Ignoring visibility, find the closest hostile target and attack move to within 2 cells of it.")]
public void Hunt()
{
Self.QueueActivity(new Hunt(Self));
}
[ScriptActorPropertyActivity]
[Desc("Move to a cell, but stop and attack anything within range on the way. " +
"closeEnough defines an optional range (in cells) that will be considered " +
"close enough to complete the activity.")]
public void AttackMove(CPos cell, int closeEnough = 0)
{
Self.QueueActivity(new AttackMoveActivity(Self, () => move.MoveTo(cell, closeEnough)));
}
[ScriptActorPropertyActivity]
[Desc("Patrol along a set of given waypoints. The action is repeated by default, " +
"and the actor will wait for `wait` ticks at each waypoint.")]
public void Patrol(CPos[] waypoints, bool loop = true, int wait = 0)
{
foreach (var wpt in waypoints)
{
Self.QueueActivity(new AttackMoveActivity(Self, () => move.MoveTo(wpt, 2)));
Self.QueueActivity(new Wait(wait));
}
if (loop)
Self.QueueActivity(new CallFunc(() => Patrol(waypoints, loop, wait)));
}
[ScriptActorPropertyActivity]
[Desc("Patrol along a set of given waypoints until a condition becomes true. " +
"The actor will wait for `wait` ticks at each waypoint. " +
"The callback function will be called as func(self: actor):boolean.")]
public void PatrolUntil(CPos[] waypoints, [ScriptEmmyTypeOverride("fun(self: actor):boolean")] LuaFunction func, int wait = 0)
{
Patrol(waypoints, false, wait);
var repeat = func.Call(Self.ToLuaValue(Context)).First().ToBoolean();
if (repeat)
using (var f = func.CopyReference() as LuaFunction)
Self.QueueActivity(new CallFunc(() => PatrolUntil(waypoints, f, wait)));
}
}
[ScriptPropertyGroup("Combat")]
public class GeneralCombatProperties : ScriptActorProperties, Requires<AttackBaseInfo>
{
readonly AttackBase[] attackBases;
public GeneralCombatProperties(ScriptContext context, Actor self)
: base(context, self)
{
attackBases = self.TraitsImplementing<AttackBase>().ToArray();
}
[Desc("Attack the target actor. The target actor needs to be visible.")]
public void Attack(Actor targetActor, bool allowMove = true, bool forceAttack = false)
{
var target = Target.FromActor(targetActor);
if (!target.IsValidFor(Self))
Log.Write("lua", $"{targetActor} is an invalid target for {Self}!");
if (!targetActor.Info.HasTraitInfo<FrozenUnderFogInfo>() && !targetActor.CanBeViewedByPlayer(Self.Owner))
Log.Write("lua", $"{targetActor} is not revealed for player {Self.Owner}!");
foreach (var attack in attackBases)
attack.AttackTarget(target, AttackSource.Default, true, allowMove, forceAttack);
}
[Desc("Checks if the targeted actor is a valid target for this actor.")]
public bool CanTarget(Actor targetActor)
{
return Target.FromActor(targetActor).IsValidFor(Self);
}
}
}

View File

@@ -0,0 +1,59 @@
#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.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("General")]
public class ConditionProperties : ScriptActorProperties, Requires<ExternalConditionInfo>
{
readonly ExternalCondition[] externalConditions;
public ConditionProperties(ScriptContext context, Actor self)
: base(context, self)
{
externalConditions = self.TraitsImplementing<ExternalCondition>().ToArray();
}
[Desc("Grant an external condition on this actor and return the revocation token.",
"Conditions must be defined on an ExternalConditions trait on the actor.",
"If duration > 0 the condition will be automatically revoked after the defined number of ticks.")]
public int GrantCondition(string condition, int duration = 0)
{
var external = externalConditions.FirstOrDefault(t => t.Info.Condition == condition && t.CanGrantCondition(this));
if (external == null)
throw new LuaException($"Condition `{condition}` has not been listed on an enabled ExternalCondition trait");
return external.GrantCondition(Self, this, duration);
}
[Desc("Revoke a condition using the token returned by GrantCondition.")]
public void RevokeCondition(int token)
{
foreach (var external in externalConditions)
if (external.TryRevokeCondition(Self, this, token))
break;
}
[Desc("Check whether this actor accepts a specific external condition.")]
public bool AcceptsCondition(string condition)
{
return externalConditions
.Any(t => t.Info.Condition == condition && t.CanGrantCondition(this));
}
}
}

View File

@@ -0,0 +1,73 @@
#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 Eluant;
using OpenRA.Mods.Common.Activities;
using OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("Ability")]
public class DeliversCashProperties : ScriptActorProperties, Requires<IMoveInfo>, Requires<DeliversCashInfo>
{
readonly DeliversCashInfo info;
public DeliversCashProperties(ScriptContext context, Actor self)
: base(context, self)
{
info = Self.Info.TraitInfo<DeliversCashInfo>();
}
[ScriptActorPropertyActivity]
[Desc("Deliver cash to the target actor.")]
public void DeliverCash(Actor target)
{
var t = Target.FromActor(target);
// NB: Scripted actions get no visible targetlines.
Self.QueueActivity(new DonateCash(Self, t, info.Payload, info.PlayerExperience, null));
}
}
[ScriptPropertyGroup("Ability")]
public class DeliversExperienceProperties : ScriptActorProperties, Requires<IMoveInfo>, Requires<DeliversExperienceInfo>
{
readonly DeliversExperienceInfo deliversExperience;
readonly GainsExperience gainsExperience;
public DeliversExperienceProperties(ScriptContext context, Actor self)
: base(context, self)
{
deliversExperience = Self.Info.TraitInfo<DeliversExperienceInfo>();
gainsExperience = Self.Trait<GainsExperience>();
}
[ScriptActorPropertyActivity]
[Desc("Deliver experience to the target actor.")]
public void DeliverExperience(Actor target)
{
var targetGainsExperience = target.TraitOrDefault<GainsExperience>();
if (targetGainsExperience == null)
throw new LuaException($"Actor '{target}' cannot gain experience!");
if (targetGainsExperience.Level == targetGainsExperience.MaxLevel)
return;
var level = gainsExperience.Level;
var t = Target.FromActor(target);
// NB: Scripted actions get no visible targetlines.
Self.QueueActivity(new DonateExperience(Self, t, level, deliversExperience.PlayerExperience, null));
}
}
}

View File

@@ -0,0 +1,40 @@
#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 OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("Combat")]
public class DemolitionProperties : ScriptActorProperties, Requires<IMoveInfo>, Requires<DemolitionInfo>
{
readonly Demolition[] demolitions;
public DemolitionProperties(ScriptContext context, Actor self)
: base(context, self)
{
demolitions = Self.TraitsImplementing<Demolition>().ToArray();
}
[ScriptActorPropertyActivity]
[Desc("Demolish the target actor.")]
public void Demolish(Actor target)
{
// NB: Scripted actions get no visible targetlines.
var demolition = demolitions.FirstEnabledConditionalTraitOrDefault();
if (demolition != null)
Self.QueueActivity(demolition.GetDemolishActivity(Self, Target.FromActor(target), null));
}
}
}

View File

@@ -0,0 +1,28 @@
#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 OpenRA.Scripting;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("Diplomacy")]
public class DiplomacyProperties : ScriptPlayerProperties
{
public DiplomacyProperties(ScriptContext context, Player player)
: base(context, player) { }
[Desc("Returns true if the player is allied with the other player.")]
public bool IsAlliedWith(Player targetPlayer)
{
return Player.IsAlliedWith(targetPlayer);
}
}
}

View File

@@ -0,0 +1,53 @@
#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 OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("Experience")]
public class GainsExperienceProperties : ScriptActorProperties, Requires<GainsExperienceInfo>
{
readonly GainsExperience exp;
public GainsExperienceProperties(ScriptContext context, Actor self)
: base(context, self)
{
exp = self.Trait<GainsExperience>();
}
[Desc("The actor's amount of experience.")]
public int Experience => exp.Experience;
[Desc("The actor's level.")]
public int Level => exp.Level;
[Desc("The actor's maximum possible level.")]
public int MaxLevel => exp.MaxLevel;
[Desc("Returns true if the actor can gain a level.")]
public bool CanGainLevel => exp.CanGainLevel;
[Desc("Gives the actor experience. If 'silent' is true, no animation or sound will be played if the actor levels up.")]
public void GiveExperience(int amount, bool silent = false)
{
exp.GiveExperience(amount, silent);
}
[Desc("Gives the actor level(s). If 'silent' is true, no animation or sound will be played.")]
public void GiveLevels(int numLevels, bool silent = false)
{
exp.GiveLevels(numLevels, silent);
}
}
}

View File

@@ -0,0 +1,226 @@
#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.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 (displayed) owner of the actor. " +
"This may differ from the true owner in some cases, such as disguised actors.")]
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 AutoTarget autotarget;
readonly ScriptTags scriptTags;
readonly Tooltip[] tooltips;
public GeneralProperties(ScriptContext context, Actor self)
: base(context, self)
{
autotarget = self.TraitOrDefault<AutoTarget>();
scriptTags = self.TraitOrDefault<ScriptTags>();
tooltips = self.TraitsImplementing<Tooltip>().ToArray();
}
[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.TryParse<UnitStance>(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 FluentProvider.GetMessage(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);
}
}
[ScriptPropertyGroup("General")]
public class LocationProperties : ScriptActorProperties, Requires<IOccupySpaceInfo>
{
public LocationProperties(ScriptContext context, Actor self)
: base(context, self) { }
[Desc("The actor position in cell coordinates.")]
public CPos Location => Self.Location;
[Desc("The actor position in world coordinates.")]
public WPos CenterPosition => Self.CenterPosition;
}
[ScriptPropertyGroup("General")]
public class FacingProperties : ScriptActorProperties, Requires<IFacingInfo>
{
readonly IFacing facing;
public FacingProperties(ScriptContext context, Actor self)
: base(context, self)
{
facing = self.Trait<IFacing>();
}
[Desc("The direction that the actor is facing.")]
public WAngle Facing => facing.Facing;
}
}

View File

@@ -0,0 +1,36 @@
#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 OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("Combat")]
public class GuardProperties : ScriptActorProperties, Requires<GuardInfo>, Requires<IMoveInfo>
{
readonly Guard guard;
public GuardProperties(ScriptContext context, Actor self)
: base(context, self)
{
guard = self.Trait<Guard>();
}
[ScriptActorPropertyActivity]
[Desc("Guard the target actor.")]
public void Guard(Actor targetActor)
{
if (targetActor.Info.HasTraitInfo<GuardableInfo>())
guard.GuardTarget(Self, Target.FromActor(targetActor));
}
}
}

View File

@@ -0,0 +1,33 @@
#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 OpenRA.Mods.Common.Activities;
using OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("Movement")]
public class HarvesterProperties : ScriptActorProperties, Requires<HarvesterInfo>
{
public HarvesterProperties(ScriptContext context, Actor self)
: base(context, self)
{ }
[ScriptActorPropertyActivity]
[Desc("Search for nearby resources and begin harvesting.")]
public void FindResources()
{
Self.QueueActivity(new FindAndDeliverResources(Self));
}
}
}

View File

@@ -0,0 +1,53 @@
#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 Eluant;
using OpenRA.Primitives;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("General")]
public class HealthProperties : ScriptActorProperties, Requires<IHealthInfo>
{
readonly IHealth health;
public HealthProperties(ScriptContext context, Actor self)
: base(context, self)
{
health = self.Trait<IHealth>();
}
[Desc("Current health of the actor.")]
public int Health
{
get => health.HP;
set => health.InflictDamage(Self, Self, new Damage(health.HP - value), true);
}
[Desc("Maximum health of the actor.")]
public int MaxHealth => health.MaxHP;
[Desc("Kill the actor. damageTypes may be omitted, specified as a string, or as table of strings.")]
public void Kill([ScriptEmmyTypeOverride("string|{ [unknown]: string }")] object damageTypes = null)
{
Damage damage;
if (damageTypes is string d)
damage = new Damage(health.MaxHP, new BitSet<DamageType>([d]));
else if (damageTypes is LuaTable t && t.TryGetClrValue(out string[] ds))
damage = new Damage(health.MaxHP, new BitSet<DamageType>(ds));
else
damage = new Damage(health.MaxHP);
health.InflictDamage(Self, Self, damage, true);
}
}
}

View File

@@ -0,0 +1,41 @@
#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 OpenRA.Mods.Common.Activities;
using OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("Ability")]
public class InstantlyRepairsProperties : ScriptActorProperties, Requires<IMoveInfo>, Requires<InstantlyRepairsInfo>
{
readonly InstantlyRepairs[] instantlyRepairs;
public InstantlyRepairsProperties(ScriptContext context, Actor self)
: base(context, self)
{
instantlyRepairs = Self.TraitsImplementing<InstantlyRepairs>().ToArray();
}
[ScriptActorPropertyActivity]
[Desc("Enter the target actor to repair it instantly.")]
public void InstantlyRepair(Actor target)
{
// NB: Scripted actions get no visible targetlines.
var repair = instantlyRepairs.FirstEnabledConditionalTraitOrDefault();
if (repair != null)
Self.QueueActivity(new InstantRepair(Self, Target.FromActor(target), repair.Info, null));
}
}
}

View File

@@ -0,0 +1,129 @@
#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 Eluant;
using OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("MissionObjectives")]
public class MissionObjectiveProperties : ScriptPlayerProperties, Requires<MissionObjectivesInfo>
{
readonly MissionObjectives mo;
readonly bool shortGame;
public MissionObjectiveProperties(ScriptContext context, Player player)
: base(context, player)
{
mo = player.PlayerActor.Trait<MissionObjectives>();
shortGame = player.World.WorldActor.Trait<MapOptions>().ShortGame;
}
[ScriptActorPropertyActivity]
[Desc("Add a mission objective for this player. The function returns the " +
"ID of the newly created objective, so that it can be referred to later.")]
public int AddObjective(string description, string type = "Primary", bool required = true)
{
return mo.Add(Player, description, type, required);
}
[ScriptActorPropertyActivity]
[Desc("Add a primary mission objective for this player. The function returns the " +
"ID of the newly created objective, so that it can be referred to later.")]
public int AddPrimaryObjective(string description)
{
return AddObjective(description);
}
[ScriptActorPropertyActivity]
[Desc("Add a secondary mission objective for this player. The function returns the " +
"ID of the newly created objective, so that it can be referred to later.")]
public int AddSecondaryObjective(string description)
{
return AddObjective(description, "Secondary", false);
}
[ScriptActorPropertyActivity]
[Desc("Mark an objective as completed. This needs the objective ID returned " +
"by AddObjective as argument. When this player has completed all primary " +
"objectives, (s)he has won the game.")]
public void MarkCompletedObjective(int id)
{
if (id < 0 || id >= mo.Objectives.Count)
throw new LuaException("Objective ID is out of range.");
mo.MarkCompleted(Player, id);
}
[ScriptActorPropertyActivity]
[Desc("Mark an objective as failed. This needs the objective ID returned " +
"by AddObjective as argument. Secondary objectives do not have any " +
"influence whatsoever on the outcome of the game. " +
"It is possible to mark a completed objective as a failure.")]
public void MarkFailedObjective(int id)
{
if (id < 0 || id >= mo.Objectives.Count)
throw new LuaException("Objective ID is out of range.");
mo.MarkFailed(Player, id);
}
[ScriptActorPropertyActivity]
[Desc("Returns true if the objective has been successfully completed, false otherwise.")]
public bool IsObjectiveCompleted(int id)
{
if (id < 0 || id >= mo.Objectives.Count)
throw new LuaException("Objective ID is out of range.");
return mo.Objectives[id].State == ObjectiveState.Completed;
}
[ScriptActorPropertyActivity]
[Desc("Returns true if the objective has been failed, false otherwise.")]
public bool IsObjectiveFailed(int id)
{
if (id < 0 || id >= mo.Objectives.Count)
throw new LuaException("Objective ID is out of range.");
return mo.Objectives[id].State == ObjectiveState.Failed;
}
[ScriptActorPropertyActivity]
[Desc("Returns the description of an objective.")]
public string GetObjectiveDescription(int id)
{
if (id < 0 || id >= mo.Objectives.Count)
throw new LuaException("Objective ID is out of range.");
return mo.Objectives[id].Description;
}
[ScriptActorPropertyActivity]
[Desc("Returns the type of an objective.")]
public string GetObjectiveType(int id)
{
if (id < 0 || id >= mo.Objectives.Count)
throw new LuaException("Objective ID is out of range.");
return mo.Objectives[id].Type;
}
[ScriptActorPropertyActivity]
[Desc("Returns true if this player has lost all units/actors that have " +
"the MustBeDestroyed trait (according to the short game option).")]
public bool HasNoRequiredUnits()
{
return Player.HasNoRequiredUnits(shortGame);
}
}
}

View File

@@ -0,0 +1,72 @@
#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 OpenRA.Mods.Common.Activities;
using OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("Movement")]
public class MobileProperties : ScriptActorProperties, Requires<MobileInfo>
{
readonly Mobile mobile;
public MobileProperties(ScriptContext context, Actor self)
: base(context, self)
{
mobile = self.Trait<Mobile>();
}
[ScriptActorPropertyActivity]
[Desc("Moves within the cell grid. closeEnough defines an optional range " +
"(in cells) that will be considered close enough to complete the activity.")]
public void Move(CPos cell, int closeEnough = 0)
{
Self.QueueActivity(new Move(Self, cell, WDist.FromCells(closeEnough)));
}
[ScriptActorPropertyActivity]
[Desc("Moves within the cell grid, ignoring lane biases.")]
public void ScriptedMove(CPos cell)
{
Self.QueueActivity(new Move(Self, cell));
}
[ScriptActorPropertyActivity]
[Desc("Moves from outside the world into the cell grid.")]
public void MoveIntoWorld(CPos cell)
{
var pos = Self.CenterPosition;
mobile.SetPosition(Self, cell);
mobile.SetCenterPosition(Self, pos);
Self.QueueActivity(mobile.ReturnToCell(Self));
}
[ScriptActorPropertyActivity]
[Desc("Leave the current position in a random direction.")]
public void Scatter()
{
Self.QueueActivity(false, new Nudge(Self));
}
[ScriptActorPropertyActivity]
[Desc("Move to and enter the transport.")]
public void EnterTransport(Actor transport)
{
Self.QueueActivity(new RideTransport(Self, Target.FromActor(transport), null));
}
[Desc("Whether the actor can move (false if immobilized).")]
public bool IsMobile => !mobile.IsTraitDisabled && !mobile.IsTraitPaused;
}
}

View File

@@ -0,0 +1,39 @@
#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 OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("Support Powers")]
public class NukeProperties : ScriptActorProperties, Requires<NukePowerInfo>
{
readonly NukePower np;
public NukeProperties(ScriptContext context, Actor self)
: base(context, self)
{
np = self.TraitsImplementing<NukePower>().First();
}
[Desc("Activate the actor's NukePower.")]
public void ActivateNukePower(CPos target)
{
np.Activate(Self, Self.World.Map.CenterOfCell(target));
foreach (var notify in Self.TraitsImplementing<INotifySupportPower>())
notify.Activated(Self);
}
}
}

View File

@@ -0,0 +1,43 @@
#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 OpenRA.Mods.Common.Activities;
using OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("Transports")]
public class ParadropProperties : ScriptActorProperties, Requires<CargoInfo>, Requires<ParaDropInfo>
{
readonly ParaDrop paradrop;
public ParadropProperties(ScriptContext context, Actor self)
: base(context, self)
{
paradrop = self.Trait<ParaDrop>();
}
[ScriptActorPropertyActivity]
[Desc("Command transport to paradrop passengers near the target cell.")]
public void Paradrop(CPos cell)
{
foreach (var notify in Self.TraitsImplementing<INotifySupportPower>())
notify.Activated(Self);
paradrop.SetLZ(cell, true);
Self.QueueActivity(new Fly(Self, Target.FromCell(Self.World, cell)));
Self.QueueActivity(new FlyOffMap(Self));
Self.QueueActivity(new RemoveSelf());
}
}
}

View File

@@ -0,0 +1,40 @@
#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 OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("Support Powers")]
public class ParatroopersProperties : ScriptActorProperties, Requires<ParatroopersPowerInfo>
{
readonly ParatroopersPower pp;
public ParatroopersProperties(ScriptContext context, Actor self)
: base(context, self)
{
pp = self.TraitsImplementing<ParatroopersPower>().First();
}
[Desc("Activate the actor's Paratroopers Power. Returns the aircraft that will drop the reinforcements.")]
public Actor[] TargetParatroopers(WPos target, WAngle? facing = null)
{
foreach (var notify in Self.TraitsImplementing<INotifySupportPower>())
notify.Activated(Self);
var actors = pp.SendParatroopers(Self, target, facing);
return actors.Aircraft;
}
}
}

View File

@@ -0,0 +1,58 @@
#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.Traits;
using OpenRA.Scripting;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("Player")]
public class PlayerConditionProperties : ScriptPlayerProperties
{
readonly ExternalCondition[] externalConditions;
public PlayerConditionProperties(ScriptContext context, Player player)
: base(context, player)
{
externalConditions = player.PlayerActor.TraitsImplementing<ExternalCondition>().ToArray();
}
[Desc("Grant an external condition on the player actor and return the revocation token.",
"Conditions must be defined on an ExternalConditions trait on the player actor.",
"If duration > 0 the condition will be automatically revoked after the defined number of ticks.")]
public int GrantCondition(string condition, int duration = 0)
{
var external = externalConditions.FirstOrDefault(t => t.Info.Condition == condition && t.CanGrantCondition(this));
if (external == null)
throw new LuaException($"Condition `{condition}` has not been listed on an enabled ExternalCondition trait");
return external.GrantCondition(Player.PlayerActor, this, duration);
}
[Desc("Revoke a condition using the token returned by GrantCondition.")]
public void RevokeCondition(int token)
{
foreach (var external in externalConditions)
if (external.TryRevokeCondition(Player.PlayerActor, this, token))
break;
}
[Desc("Check whether this player actor accepts a specific external condition.")]
public bool AcceptsCondition(string condition)
{
return externalConditions
.Any(t => t.Info.Condition == condition && t.CanGrantCondition(this));
}
}
}

View File

@@ -0,0 +1,37 @@
#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 OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("Player")]
public class PlayerExperienceProperties : ScriptPlayerProperties, Requires<PlayerExperienceInfo>
{
readonly PlayerExperience exp;
public PlayerExperienceProperties(ScriptContext context, Player player)
: base(context, player)
{
exp = player.PlayerActor.Trait<PlayerExperience>();
}
[Desc("Get or set the current experience.")]
public int Experience
{
get => exp.Experience;
set => exp.GiveExperience(value - exp.Experience);
}
}
}

View File

@@ -0,0 +1,127 @@
#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.Collections.Generic;
using System.Linq;
using Eluant;
using OpenRA.Mods.Common.Traits;
using OpenRA.Primitives;
using OpenRA.Scripting;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("Player")]
public class PlayerProperties : ScriptPlayerProperties
{
public PlayerProperties(ScriptContext context, Player player)
: base(context, player) { }
[Desc("The player's internal name.")]
public string InternalName => Player.InternalName;
[Desc("The player's name.")]
public string Name => Player.ResolvedPlayerName;
[Desc("The player's color.")]
public Color Color => Player.GetColor(Player);
[Desc("The player's faction.")]
public string Faction => Player.Faction.InternalName;
[Desc("The player's spawnpoint ID.")]
public int Spawn => Player.SpawnPoint;
[Desc("The player's home/starting location.")]
public CPos HomeLocation => Player.HomeLocation;
[Desc("The player's team ID.")]
public int Team
{
get
{
var c = Player.World.LobbyInfo.Clients.FirstOrDefault(i => i.Index == Player.ClientIndex);
return c?.Team ?? 0;
}
}
[Desc("The player's handicap level.")]
public int Handicap
{
get
{
var c = Player.World.LobbyInfo.Clients.FirstOrDefault(i => i.Index == Player.ClientIndex);
return c?.Handicap ?? 0;
}
}
[Desc("Returns true if the player is a bot.")]
public bool IsBot => Player.IsBot;
[Desc("Returns true if the player is non combatant.")]
public bool IsNonCombatant => Player.NonCombatant;
[Desc("Returns true if the player is the local player.")]
public bool IsLocalPlayer => Player == (Player.World.RenderPlayer ?? Player.World.LocalPlayer);
[Desc("Returns all living actors staying inside the world for this player.")]
public Actor[] GetActors()
{
return Player.World.Actors.Where(actor => actor.Owner == Player && !actor.IsDead && actor.IsInWorld).ToArray();
}
[Desc("Returns an array of actors representing all ground attack units of this player.")]
public Actor[] GetGroundAttackers()
{
return Player.World.ActorsHavingTrait<AttackBase>()
.Where(a => a.Owner == Player && !a.IsDead && a.IsInWorld && a.Info.HasTraitInfo<MobileInfo>())
.ToArray();
}
[Desc("Returns all living actors of the specified type of this player.")]
public Actor[] GetActorsByType(string type)
{
var result = new List<Actor>();
if (!Context.World.Map.Rules.Actors.TryGetValue(type, out var ai))
throw new LuaException($"Unknown actor type '{type}'");
result.AddRange(Player.World.Actors
.Where(actor => actor.Owner == Player && !actor.IsDead && actor.IsInWorld && actor.Info.Name == ai.Name));
return result.ToArray();
}
[Desc("Returns all living actors of the specified types of this player.")]
public Actor[] GetActorsByTypes(string[] types)
{
var result = new List<Actor>();
foreach (var type in types)
if (!Context.World.Map.Rules.Actors.ContainsKey(type))
throw new LuaException($"Unknown actor type '{type}'");
result.AddRange(Player.World.Actors
.Where(actor => actor.Owner == Player && !actor.IsDead && actor.IsInWorld && types.Contains(actor.Info.Name)));
return result.ToArray();
}
[Desc("Check if the player has these prerequisites available.")]
public bool HasPrerequisites(string[] type)
{
var tt = Player.PlayerActor.TraitOrDefault<TechTree>();
if (tt == null)
throw new LuaException($"Missing TechTree trait on player {Player}!");
return tt.HasPrerequisites(type);
}
}
}

View File

@@ -0,0 +1,47 @@
#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 OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("Player")]
public class PlayerStatsProperties : ScriptPlayerProperties, Requires<PlayerStatisticsInfo>
{
readonly PlayerStatistics stats;
public PlayerStatsProperties(ScriptContext context, Player player)
: base(context, player)
{
stats = player.PlayerActor.Trait<PlayerStatistics>();
}
[Desc("The combined value of units killed by this player.")]
public int KillsCost => stats.KillsCost;
[Desc("The combined value of all units lost by this player.")]
public int DeathsCost => stats.DeathsCost;
[Desc("The total number of units killed by this player.")]
public int UnitsKilled => stats.UnitsKilled;
[Desc("The total number of units lost by this player.")]
public int UnitsLost => stats.UnitsDead;
[Desc("The total number of buildings killed by this player.")]
public int BuildingsKilled => stats.BuildingsKilled;
[Desc("The total number of buildings lost by this player.")]
public int BuildingsLost => stats.BuildingsDead;
}
}

View File

@@ -0,0 +1,72 @@
#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 OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
using PowerTrait = OpenRA.Mods.Common.Traits.Power;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("Power")]
public class PlayerPowerProperties : ScriptPlayerProperties, Requires<PowerManagerInfo>
{
readonly PowerManager pm;
public PlayerPowerProperties(ScriptContext context, Player player)
: base(context, player)
{
pm = player.PlayerActor.Trait<PowerManager>();
}
[Desc("Returns the total of the power the player has.")]
public int PowerProvided => pm.PowerProvided;
[Desc("Returns the power used by the player.")]
public int PowerDrained => pm.PowerDrained;
[Desc("Returns the player's power state " +
"(\"Normal\", \"Low\" or \"Critical\").")]
public string PowerState => pm.PowerState.ToString();
[Desc("Triggers low power for the chosen amount of ticks.")]
public void TriggerPowerOutage(int ticks)
{
pm.TriggerPowerOutage(ticks);
}
[Desc("Whether the player should receive a notification when low on power.")]
public bool PlayLowPowerNotification
{
get => pm.PlayLowPowerNotification;
set => pm.PlayLowPowerNotification = value;
}
}
[ScriptPropertyGroup("Power")]
public class ActorPowerProperties : ScriptActorProperties, Requires<PowerInfo>
{
readonly PowerTrait[] power;
public ActorPowerProperties(ScriptContext context, Actor self)
: base(context, self)
{
power = self.TraitsImplementing<PowerTrait>().ToArray();
}
[Desc("Returns the power drained/provided by this actor.")]
public int Power
{
get { return power.Sum(p => p.GetEnabledPower()); }
}
}
}

View File

@@ -0,0 +1,311 @@
#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.Collections.Generic;
using System.Linq;
using Eluant;
using OpenRA.Mods.Common.Activities;
using OpenRA.Mods.Common.Traits;
using OpenRA.Primitives;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("Production")]
public class ProductionProperties : ScriptActorProperties, Requires<ProductionInfo>
{
readonly Production[] productionTraits;
public ProductionProperties(ScriptContext context, Actor self)
: base(context, self)
{
productionTraits = self.TraitsImplementing<Production>().ToArray();
}
[ScriptActorPropertyActivity]
[Desc("Build a unit, ignoring the production queue. The activity will wait if the exit is blocked.",
"If productionType is nil or unavailable, then an exit will be selected based on 'Buildable.BuildAtProductionType'.",
"If 'Buildable.BuildAtProductionType' is not set either, a random exit will be selected.")]
public void Produce(string actorType, string factionVariant = null, string productionType = null)
{
if (!Self.World.Map.Rules.Actors.TryGetValue(actorType, out var actorInfo))
throw new LuaException($"Unknown actor type '{actorType}'");
var bi = actorInfo.TraitInfo<BuildableInfo>();
Self.QueueActivity(new WaitFor(() =>
{
// Go through all available traits and see which one successfully produces
foreach (var p in productionTraits)
{
var type = productionType ?? bi.BuildAtProductionType;
if (!string.IsNullOrEmpty(type) && !p.Info.Produces.Contains(type))
continue;
var inits = new TypeDictionary
{
new OwnerInit(Self.Owner),
new FactionInit(factionVariant ?? BuildableInfo.GetInitialFaction(actorInfo, p.Faction))
};
if (p.Produce(Self, actorInfo, type, inits, 0))
return true;
}
// We didn't produce anything, wait until we do
return false;
}));
}
}
[ScriptPropertyGroup("Production")]
public class RallyPointProperties : ScriptActorProperties, Requires<RallyPointInfo>
{
readonly RallyPoint rp;
public RallyPointProperties(ScriptContext context, Actor self)
: base(context, self)
{
rp = self.Trait<RallyPoint>();
}
[Desc("Query or set a factory's rally point.")]
public CPos RallyPoint
{
get
{
if (rp.Path.Count > 0)
return rp.Path[^1];
var exit = Self.NearestExitOrDefault(Self.CenterPosition);
if (exit != null)
return Self.Location + exit.Info.ExitCell;
return Self.Location;
}
set => rp.Path = [value];
}
}
[ScriptPropertyGroup("Production")]
public class PrimaryBuildingProperties : ScriptActorProperties, Requires<PrimaryBuildingInfo>
{
readonly PrimaryBuilding pb;
public PrimaryBuildingProperties(ScriptContext context, Actor self)
: base(context, self)
{
pb = self.Trait<PrimaryBuilding>();
}
[Desc("Query or set the factory's primary building status.")]
public bool IsPrimaryBuilding
{
get => pb.IsPrimary;
set => pb.SetPrimaryProducer(Self, value);
}
}
[ScriptPropertyGroup("Production")]
public class ProductionQueueProperties : ScriptActorProperties, Requires<ProductionQueueInfo>, Requires<ScriptTriggersInfo>
{
readonly ProductionQueue[] queues;
readonly ScriptTriggers triggers;
public ProductionQueueProperties(ScriptContext context, Actor self)
: base(context, self)
{
queues = self.TraitsImplementing<ProductionQueue>().Where(q => q.Enabled).ToArray();
triggers = TriggerGlobal.GetScriptTriggers(self);
}
[Desc("Build the specified set of actors using a TD-style (per building) production queue. " +
"The function will return true if production could be started, false otherwise. " +
"If an actionFunc is given, it will be called as actionFunc(actors: actor[]) once " +
"production of all actors has been completed. The actors array is guaranteed to " +
"only contain alive actors.")]
public bool Build(string[] actorTypes, [ScriptEmmyTypeOverride("fun(actors: actor[])")] LuaFunction actionFunc = null)
{
if (triggers.HasAnyCallbacksFor(Trigger.OnProduction))
return false;
var queue = queues.Where(q => actorTypes.All(t => GetBuildableInfo(t).Queue.Contains(q.Info.Type)))
.FirstOrDefault(q => !q.AllQueued().Any());
if (queue == null)
return false;
if (actionFunc != null)
{
var player = Self.Owner;
var squadSize = actorTypes.Length;
var squad = new List<Actor>();
var func = actionFunc.CopyReference() as LuaFunction;
Action<Actor, Actor> productionHandler = (a, b) => { };
productionHandler = (factory, unit) =>
{
if (player != factory.Owner)
{
triggers.OnProducedInternal -= productionHandler;
return;
}
squad.Add(unit);
if (squad.Count >= squadSize)
{
using (func)
using (var luaSquad = squad.Where(u => !u.IsDead).ToArray().ToLuaValue(Context))
func.Call(luaSquad).Dispose();
triggers.OnProducedInternal -= productionHandler;
}
};
triggers.OnProducedInternal += productionHandler;
}
foreach (var actorType in actorTypes)
queue.ResolveOrder(Self, Order.StartProduction(Self, actorType, 1));
return true;
}
[Desc("Check whether the factory's production queue that builds this type of actor is currently busy. " +
"Note: it does not check whether this particular type of actor is being produced.")]
public bool IsProducing(string actorType)
{
if (triggers.HasAnyCallbacksFor(Trigger.OnProduction))
return true;
return queues.Any(q => GetBuildableInfo(actorType).Queue.Contains(q.Info.Type) && q.AllQueued().Any());
}
BuildableInfo GetBuildableInfo(string actorType)
{
var ri = Self.World.Map.Rules.Actors[actorType];
var bi = ri.TraitInfoOrDefault<BuildableInfo>();
if (bi == null)
throw new LuaException($"Actor of type {actorType} cannot be produced");
else
return bi;
}
}
[ScriptPropertyGroup("Production")]
public class ClassicProductionQueueProperties : ScriptPlayerProperties, Requires<ClassicProductionQueueInfo>, Requires<ScriptTriggersInfo>
{
readonly Dictionary<string, Action<Actor, Actor>> productionHandlers;
readonly Dictionary<string, ClassicProductionQueue> queues;
public ClassicProductionQueueProperties(ScriptContext context, Player player)
: base(context, player)
{
productionHandlers = [];
queues = [];
foreach (var q in player.PlayerActor.TraitsImplementing<ClassicProductionQueue>().Where(q => q.Enabled))
queues.Add(q.Info.Type, q);
void GlobalProductionHandler(Actor factory, Actor unit)
{
if (factory.Owner != player)
return;
var queue = GetBuildableInfo(unit.Info.Name).Queue.First();
if (productionHandlers.TryGetValue(queue, out var productionHandler))
productionHandler(factory, unit);
}
var triggers = TriggerGlobal.GetScriptTriggers(player.PlayerActor);
triggers.OnOtherProducedInternal += GlobalProductionHandler;
}
[Desc("Build the specified set of actors using classic (RA-style) production queues. " +
"The function will return true if production could be started, false otherwise. " +
"If an actionFunc is given, it will be called as actionFunc(actors: actor[]) once " +
"production of all actors has been completed. The actors array is guaranteed to " +
"only contain alive actors. Note: This function will fail to work when called " +
"during the first tick.")]
public bool Build(string[] actorTypes, [ScriptEmmyTypeOverride("fun(actors: actor[])")] LuaFunction actionFunc = null)
{
var typeToQueueMap = new Dictionary<string, string>();
foreach (var actorType in actorTypes.Distinct())
typeToQueueMap.Add(actorType, GetBuildableInfo(actorType).Queue.First());
// PERF: queues tend to live for a long time so cast to array.
var queueTypes = typeToQueueMap.Values.Distinct().ToArray();
if (queueTypes.Any(t => !queues.ContainsKey(t) || productionHandlers.ContainsKey(t)))
return false;
if (queueTypes.Any(t => queues[t].AllQueued().Any()))
return false;
if (actionFunc != null)
{
var squadSize = actorTypes.Length;
var squad = new List<Actor>();
var func = actionFunc.CopyReference() as LuaFunction;
void ProductionHandler(Actor factory, Actor unit)
{
squad.Add(unit);
if (squad.Count >= squadSize)
{
using (func)
using (var luaSquad = squad.Where(u => !u.IsDead).ToArray().ToLuaValue(Context))
func.Call(luaSquad).Dispose();
foreach (var q in queueTypes)
productionHandlers.Remove(q);
}
}
foreach (var q in queueTypes)
productionHandlers.Add(q, ProductionHandler);
}
foreach (var actorType in actorTypes)
{
var queue = queues[typeToQueueMap[actorType]];
queue.ResolveOrder(queue.Actor, Order.StartProduction(queue.Actor, actorType, 1));
}
return true;
}
[Desc("Check whether the production queue that builds this type of actor is currently busy. " +
"Note: it does not check whether this particular type of actor is being produced.")]
public bool IsProducing(string actorType)
{
var queue = GetBuildableInfo(actorType).Queue.First();
if (!queues.TryGetValue(queue, out var cpq))
return true;
return productionHandlers.ContainsKey(queue) || cpq.AllQueued().Any();
}
BuildableInfo GetBuildableInfo(string actorType)
{
var ri = Player.World.Map.Rules.Actors[actorType];
var bi = ri.TraitInfoOrDefault<BuildableInfo>();
if (bi == null)
throw new LuaException($"Actor of type {actorType} cannot be produced");
else
return bi;
}
}
}

View File

@@ -0,0 +1,47 @@
#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 OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("General")]
public class RepairableBuildingProperties : ScriptActorProperties, Requires<RepairableBuildingInfo>
{
readonly RepairableBuilding rb;
public RepairableBuildingProperties(ScriptContext context, Actor self)
: base(context, self)
{
rb = self.Trait<RepairableBuilding>();
}
[Desc("Start repairs on this building. `repairer` can be an allied player.")]
public void StartBuildingRepairs(Player repairer = null)
{
repairer ??= Self.Owner;
if (!rb.Repairers.Contains(repairer))
rb.RepairBuilding(Self, repairer);
}
[Desc("Stop repairs on this building. `repairer` can be an allied player.")]
public void StopBuildingRepairs(Player repairer = null)
{
repairer ??= Self.Owner;
if (rb.RepairActive && rb.Repairers.Contains(repairer))
rb.RepairBuilding(Self, repairer);
}
}
}

View File

@@ -0,0 +1,47 @@
#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 OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("Resources")]
public class ResourceProperties : ScriptPlayerProperties, Requires<PlayerResourcesInfo>
{
readonly PlayerResources pr;
public ResourceProperties(ScriptContext context, Player player)
: base(context, player)
{
pr = player.PlayerActor.Trait<PlayerResources>();
}
[Desc("The amount of harvestable resources held by the player.")]
public int Resources
{
get => pr.Resources;
set => pr.Resources = value.Clamp(0, pr.ResourceCapacity);
}
[Desc("The maximum resource storage of the player.")]
public int ResourceCapacity => pr.ResourceCapacity;
[Desc("The amount of cash held by the player.")]
public int Cash
{
get => pr.Cash;
set => pr.Cash = Math.Max(0, value);
}
}
}

View File

@@ -0,0 +1,36 @@
#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 OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("Movement")]
public class ScaredCatProperties : ScriptActorProperties, Requires<ScaredyCatInfo>
{
readonly ScaredyCat scaredyCat;
public ScaredCatProperties(ScriptContext context, Actor self)
: base(context, self)
{
scaredyCat = self.Trait<ScaredyCat>();
}
[ScriptActorPropertyActivity]
[Desc("Makes the unit automatically run around and become faster.")]
public void Panic()
{
scaredyCat.Panic();
}
}
}

View File

@@ -0,0 +1,31 @@
#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 OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("General")]
public class SellableProperties : ScriptActorProperties, Requires<SellableInfo>
{
public SellableProperties(ScriptContext context, Actor self)
: base(context, self) { }
[Desc("Start selling the actor.")]
public void Sell()
{
// PERF: No trait lookup cache in the constructor to avoid doing it for all buildings except just the ones getting sold.
Self.Trait<Sellable>().Sell(Self);
}
}
}

View File

@@ -0,0 +1,36 @@
#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 OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("General")]
public class TransformProperties : ScriptActorProperties, Requires<TransformsInfo>
{
readonly Transforms transforms;
public TransformProperties(ScriptContext context, Actor self)
: base(context, self)
{
transforms = self.Trait<Transforms>();
}
[ScriptActorPropertyActivity]
[Desc("Queue a new transformation.")]
public void Deploy()
{
transforms.DeployTransform(true);
}
}
}

View File

@@ -0,0 +1,66 @@
#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.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("Transports")]
public class TransportProperties : ScriptActorProperties, Requires<CargoInfo>
{
readonly Cargo cargo;
public TransportProperties(ScriptContext context, Actor self)
: base(context, self)
{
cargo = self.Trait<Cargo>();
}
[Desc("Returns references to passengers inside the transport.")]
public Actor[] Passengers => cargo.Passengers.ToArray();
[Desc("Specifies whether transport has any passengers.")]
public bool HasPassengers => cargo.Passengers.Any();
[Desc("Specifies the amount of passengers.")]
public int PassengerCount => cargo.Passengers.Count();
[Desc("Teleport an existing actor inside this transport.")]
public void LoadPassenger(Actor a)
{
if (!a.IsIdle)
throw new LuaException("LoadPassenger requires the passenger to be idle.");
cargo.Load(Self, a);
}
[Desc("Remove an existing actor (or first actor if none specified) from the transport. This actor is not added to the world.")]
public Actor UnloadPassenger(Actor a = null) { return cargo.Unload(Self, a); }
[ScriptActorPropertyActivity]
[Desc("Command transport to unload passengers.")]
public void UnloadPassengers(CPos? cell = null, int unloadRange = 5)
{
if (cell.HasValue)
{
var destination = Target.FromCell(Self.World, cell.Value);
Self.QueueActivity(new UnloadCargo(Self, destination, WDist.FromCells(unloadRange)));
}
else
Self.QueueActivity(new UnloadCargo(Self, WDist.FromCells(unloadRange)));
}
}
}