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,83 @@
#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.Primitives;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Disables the actor when a power outage is triggered (see `InfiltrateForPowerOutage` for more information).")]
public class AffectedByPowerOutageInfo : ConditionalTraitInfo
{
[GrantedConditionReference]
[Desc("The condition to grant while there is a power outage.")]
public readonly string Condition = null;
public override object Create(ActorInitializer init) { return new AffectedByPowerOutage(init.Self, this); }
}
public class AffectedByPowerOutage : ConditionalTrait<AffectedByPowerOutageInfo>, INotifyOwnerChanged, ISelectionBar, INotifyCreated, INotifyAddedToWorld
{
PowerManager playerPower;
int token = Actor.InvalidConditionToken;
public AffectedByPowerOutage(Actor self, AffectedByPowerOutageInfo info)
: base(info)
{
playerPower = self.Owner.PlayerActor.Trait<PowerManager>();
}
void INotifyAddedToWorld.AddedToWorld(Actor self) { UpdateStatus(self); }
protected override void TraitEnabled(Actor self) { UpdateStatus(self); }
protected override void TraitDisabled(Actor self) { Revoke(self); }
float ISelectionBar.GetValue()
{
if (IsTraitDisabled || playerPower.PowerOutageRemainingTicks <= 0)
return 0;
return (float)playerPower.PowerOutageRemainingTicks / playerPower.PowerOutageTotalTicks;
}
Color ISelectionBar.GetColor()
{
return Color.Yellow;
}
bool ISelectionBar.DisplayWhenEmpty => false;
public void UpdateStatus(Actor self)
{
if (!IsTraitDisabled && playerPower.PowerOutageRemainingTicks > 0)
Grant(self);
else
Revoke(self);
}
void Grant(Actor self)
{
if (token == Actor.InvalidConditionToken)
token = self.GrantCondition(Info.Condition);
}
void Revoke(Actor self)
{
if (token != Actor.InvalidConditionToken)
token = self.RevokeCondition(token);
}
void INotifyOwnerChanged.OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)
{
playerPower = newOwner.PlayerActor.Trait<PowerManager>();
UpdateStatus(self);
}
}
}

View File

@@ -0,0 +1,220 @@
#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 OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[TraitLocation(SystemActors.Player)]
[Desc("Attach this to the player actor.")]
public class PowerManagerInfo : TraitInfo, Requires<DeveloperModeInfo>
{
[Desc("Interval (in milliseconds) at which to warn the player of low power.")]
public readonly int AdviceInterval = 10000;
[Desc("The speech notification to play when the player is low power.")]
[NotificationReference("Speech")]
public readonly string SpeechNotification = null;
[Desc("The text notification to display when the player is low power.")]
[FluentReference(optional: true)]
public readonly string TextNotification = null;
public override object Create(ActorInitializer init) { return new PowerManager(init.Self, this); }
}
public class PowerManager : INotifyCreated, ITick, ISync, IResolveOrder
{
readonly Actor self;
readonly PowerManagerInfo info;
readonly DeveloperMode devMode;
readonly Dictionary<Actor, int> powerDrain = [];
[VerifySync]
public int PowerProvided { get; private set; }
[VerifySync]
public int PowerDrained { get; private set; }
public int ExcessPower => PowerProvided - PowerDrained;
public int PowerOutageRemainingTicks { get; private set; }
public int PowerOutageTotalTicks { get; private set; }
public bool PlayLowPowerNotification { get; set; }
long lastPowerAdviceTime;
bool isLowPower;
bool wasLowPower;
bool wasHackEnabled;
public PowerManager(Actor self, PowerManagerInfo info)
{
this.self = self;
this.info = info;
devMode = self.Trait<DeveloperMode>();
wasHackEnabled = devMode.UnlimitedPower;
PlayLowPowerNotification = info.AdviceInterval > 0;
}
void INotifyCreated.Created(Actor self)
{
// Map placed actors will query an inconsistent power state when they are created
// (it will depend on the order that they are spawned by the world)
// Tell them to query the correct state once the world has been fully created
self.World.AddFrameEndTask(_ => UpdatePowerRequiringActors());
}
public void UpdateActor(Actor a)
{
// Do not add power from actors that are not in the world
if (!a.IsInWorld)
return;
// Old is 0 if a is not in powerDrain
powerDrain.TryGetValue(a, out var old);
var amount = a.TraitsImplementing<Power>().Where(t => !t.IsTraitDisabled).Sum(p => p.GetEnabledPower());
powerDrain[a] = amount;
if (amount == old || devMode.UnlimitedPower)
return;
if (old > 0)
PowerProvided -= old;
else if (old < 0)
PowerDrained += old;
if (amount > 0)
PowerProvided += amount;
else if (amount < 0)
PowerDrained -= amount;
UpdatePowerState();
}
public void RemoveActor(Actor a)
{
// Do not remove power from actors that are still in the world
if (a.IsInWorld)
return;
if (!powerDrain.TryGetValue(a, out var amount))
return;
powerDrain.Remove(a);
if (devMode.UnlimitedPower)
return;
if (amount > 0)
PowerProvided -= amount;
else if (amount < 0)
PowerDrained += amount;
UpdatePowerState();
}
void UpdatePowerState()
{
isLowPower = ExcessPower < 0;
if (isLowPower != wasLowPower)
UpdatePowerRequiringActors();
// Force the notification to play immediately
if (isLowPower && !wasLowPower)
lastPowerAdviceTime = -info.AdviceInterval;
wasLowPower = isLowPower;
}
void ITick.Tick(Actor self)
{
if (wasHackEnabled != devMode.UnlimitedPower)
{
PowerProvided = 0;
PowerDrained = 0;
if (!devMode.UnlimitedPower)
{
foreach (var kv in powerDrain)
{
if (kv.Value > 0)
PowerProvided += kv.Value;
else if (kv.Value < 0)
PowerDrained -= kv.Value;
}
}
wasHackEnabled = devMode.UnlimitedPower;
UpdatePowerState();
}
if (PlayLowPowerNotification && isLowPower && Game.RunTime > lastPowerAdviceTime + info.AdviceInterval)
{
Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", info.SpeechNotification, self.Owner.Faction.InternalName);
TextNotificationsManager.AddTransientLine(self.Owner, info.TextNotification);
lastPowerAdviceTime = Game.RunTime;
}
if (PowerOutageRemainingTicks > 0 && --PowerOutageRemainingTicks == 0)
UpdatePowerOutageActors();
}
public PowerState PowerState
{
get
{
if (PowerProvided >= PowerDrained)
return PowerState.Normal;
if (PowerProvided > PowerDrained / 2)
return PowerState.Low;
return PowerState.Critical;
}
}
public void TriggerPowerOutage(int totalTicks)
{
PowerOutageTotalTicks = PowerOutageRemainingTicks = totalTicks;
UpdatePowerOutageActors();
}
void UpdatePowerOutageActors()
{
var traitPairs = self.World.ActorsWithTrait<AffectedByPowerOutage>()
.Where(p => !p.Actor.IsDead && p.Actor.IsInWorld && p.Actor.Owner == self.Owner);
foreach (var p in traitPairs)
p.Trait.UpdateStatus(p.Actor);
}
void UpdatePowerRequiringActors()
{
var traitPairs = self.World.ActorsWithTrait<INotifyPowerLevelChanged>()
.Where(p => !p.Actor.IsDead && p.Actor.IsInWorld && p.Actor.Owner == self.Owner);
foreach (var p in traitPairs)
p.Trait.PowerLevelChanged(p.Actor);
}
void IResolveOrder.ResolveOrder(Actor self, Order order)
{
if (devMode.Enabled && order.OrderString == "PowerOutage")
TriggerPowerOutage((int)order.ExtraData);
}
}
}

View File

@@ -0,0 +1,57 @@
#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 OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
public class PowerInfo : ConditionalTraitInfo
{
[Desc("If negative, it will drain power. If positive, it will provide power.")]
public readonly int Amount = 0;
public override object Create(ActorInitializer init) { return new Power(init.Self, this); }
}
public class Power : ConditionalTrait<PowerInfo>, INotifyAddedToWorld, INotifyRemovedFromWorld, INotifyOwnerChanged
{
readonly Lazy<IPowerModifier[]> powerModifiers;
public PowerManager PlayerPower { get; private set; }
public int GetEnabledPower()
{
return Util.ApplyPercentageModifiers(Info.Amount, powerModifiers.Value.Select(m => m.GetPowerModifier()));
}
public Power(Actor self, PowerInfo info)
: base(info)
{
PlayerPower = self.Owner.PlayerActor.Trait<PowerManager>();
powerModifiers = Exts.Lazy(() => self.TraitsImplementing<IPowerModifier>().ToArray());
}
protected override void TraitEnabled(Actor self) { PlayerPower.UpdateActor(self); }
protected override void TraitDisabled(Actor self) { PlayerPower.UpdateActor(self); }
void INotifyAddedToWorld.AddedToWorld(Actor self) { PlayerPower.UpdateActor(self); }
void INotifyRemovedFromWorld.RemovedFromWorld(Actor self) { PlayerPower.RemoveActor(self); }
void INotifyOwnerChanged.OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)
{
PlayerPower.RemoveActor(self);
PlayerPower = newOwner.PlayerActor.Trait<PowerManager>();
PlayerPower.UpdateActor(self);
}
}
}

View File

@@ -0,0 +1,46 @@
#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.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Scale power amount with the current health.")]
public class ScalePowerWithHealthInfo : TraitInfo, Requires<PowerInfo>, Requires<IHealthInfo>
{
public override object Create(ActorInitializer init) { return new ScalePowerWithHealth(init.Self); }
}
public class ScalePowerWithHealth : IPowerModifier, INotifyDamage, INotifyOwnerChanged
{
readonly IHealth health;
PowerManager power;
public ScalePowerWithHealth(Actor self)
{
power = self.Owner.PlayerActor.Trait<PowerManager>();
health = self.Trait<IHealth>();
}
int IPowerModifier.GetPowerModifier()
{
// Cast to long to avoid overflow when multiplying by the health
return (int)(100L * health.HP / health.MaxHP);
}
void INotifyDamage.Damaged(Actor self, AttackInfo e) { power.UpdateActor(self); }
void INotifyOwnerChanged.OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)
{
power = newOwner.PlayerActor.Trait<PowerManager>();
}
}
}