Compare commits
28 Commits
log_chat_s
...
d40f5292b6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d40f5292b6 | ||
|
|
3a75add1ba | ||
|
|
b3bcdeb4ec | ||
|
|
dde39344a0 | ||
|
|
386f691c2e | ||
|
|
24623eaa65 | ||
|
|
dc89341634 | ||
|
|
8961b4986f | ||
|
|
cbf4207d22 | ||
|
|
c27bf85631 | ||
|
|
809cb16075 | ||
|
|
f4c186b7a6 | ||
|
|
c3cf94b67a | ||
|
|
8b3e7bec2a | ||
|
|
64ec6eef0a | ||
|
|
4dec1fe430 | ||
|
|
db3145ed5e | ||
|
|
e49135bb09 | ||
|
|
9d79e52989 | ||
|
|
0ac9d96ab8 | ||
|
|
5ce559c853 | ||
|
|
a4821b51a2 | ||
|
|
cfc026a1ac | ||
|
|
58ab3eb153 | ||
|
|
47b6542b1d | ||
|
|
3c7addcb80 | ||
|
|
37f1b9efbf | ||
|
|
82acdbc32a |
29
Dockerfile
Normal file
29
Dockerfile
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/sdk:6.0
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
apt-get update; \
|
||||||
|
apt-get -y upgrade; \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
curl \
|
||||||
|
wget \
|
||||||
|
make \
|
||||||
|
python3 \
|
||||||
|
unzip \
|
||||||
|
mono-complete
|
||||||
|
|
||||||
|
|
||||||
|
RUN useradd -d /home/openra -m -s /sbin/nologin openra
|
||||||
|
|
||||||
|
WORKDIR /home/openra
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN chown -R openra:openra .
|
||||||
|
|
||||||
|
USER openra
|
||||||
|
|
||||||
|
RUN make
|
||||||
|
|
||||||
|
EXPOSE 1234
|
||||||
|
|
||||||
|
ENTRYPOINT ["./launch-dedicated.sh"]
|
||||||
@@ -562,6 +562,11 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
return new LineSplitEnumerator(str.AsSpan(), separator);
|
return new LineSplitEnumerator(str.AsSpan(), separator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool TryParseInt32Invariant(string s, out int i)
|
||||||
|
{
|
||||||
|
return int.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ref struct LineSplitEnumerator
|
public ref struct LineSplitEnumerator
|
||||||
|
|||||||
@@ -41,7 +41,11 @@ namespace OpenRA.FileSystem
|
|||||||
|
|
||||||
public Stream GetStream(string filename)
|
public Stream GetStream(string filename)
|
||||||
{
|
{
|
||||||
try { return File.OpenRead(Path.Combine(Name, filename)); }
|
var combined = Path.Combine(Name, filename);
|
||||||
|
if (!File.Exists(combined))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
try { return File.OpenRead(combined); }
|
||||||
catch { return null; }
|
catch { return null; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,8 +86,9 @@ namespace OpenRA
|
|||||||
|
|
||||||
static void JoinInner(OrderManager om)
|
static void JoinInner(OrderManager om)
|
||||||
{
|
{
|
||||||
// Refresh TextNotificationsManager before the game starts.
|
// Refresh static classes before the game starts.
|
||||||
TextNotificationsManager.Clear();
|
TextNotificationsManager.Clear();
|
||||||
|
UnitOrders.Clear();
|
||||||
|
|
||||||
// HACK: The shellmap World and OrderManager are owned by the main menu's WorldRenderer instead of Game.
|
// HACK: The shellmap World and OrderManager are owned by the main menu's WorldRenderer instead of Game.
|
||||||
// This allows us to switch Game.OrderManager from the shellmap to the new network connection when joining
|
// This allows us to switch Game.OrderManager from the shellmap to the new network connection when joining
|
||||||
|
|||||||
@@ -20,11 +20,15 @@ namespace OpenRA.Network
|
|||||||
{
|
{
|
||||||
public const int ChatMessageMaxLength = 2500;
|
public const int ChatMessageMaxLength = 2500;
|
||||||
|
|
||||||
|
public static int? KickVoteTarget { get; internal set; }
|
||||||
|
|
||||||
static Player FindPlayerByClient(this World world, Session.Client c)
|
static Player FindPlayerByClient(this World world, Session.Client c)
|
||||||
{
|
{
|
||||||
return world.Players.FirstOrDefault(p => p.ClientIndex == c.Index && p.PlayerReference.Playable);
|
return world.Players.FirstOrDefault(p => p.ClientIndex == c.Index && p.PlayerReference.Playable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool OrderNotFromServerOrWorldIsReplay(int clientId, World world) => clientId != 0 || (world != null && world.IsReplay);
|
||||||
|
|
||||||
internal static void ProcessOrder(OrderManager orderManager, World world, int clientId, Order order)
|
internal static void ProcessOrder(OrderManager orderManager, World world, int clientId, Order order)
|
||||||
{
|
{
|
||||||
switch (order.OrderString)
|
switch (order.OrderString)
|
||||||
@@ -52,9 +56,7 @@ namespace OpenRA.Network
|
|||||||
|
|
||||||
case "DisableChatEntry":
|
case "DisableChatEntry":
|
||||||
{
|
{
|
||||||
// Order must originate from the server
|
if (OrderNotFromServerOrWorldIsReplay(clientId, world))
|
||||||
// Don't disable chat in replays
|
|
||||||
if (clientId != 0 || (world != null && world.IsReplay))
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Server may send MaxValue to indicate that it is disabled until further notice
|
// Server may send MaxValue to indicate that it is disabled until further notice
|
||||||
@@ -66,6 +68,26 @@ namespace OpenRA.Network
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "StartKickVote":
|
||||||
|
{
|
||||||
|
if (OrderNotFromServerOrWorldIsReplay(clientId, world))
|
||||||
|
break;
|
||||||
|
|
||||||
|
KickVoteTarget = (int)order.ExtraData;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "EndKickVote":
|
||||||
|
{
|
||||||
|
if (OrderNotFromServerOrWorldIsReplay(clientId, world))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (KickVoteTarget == (int)order.ExtraData)
|
||||||
|
KickVoteTarget = null;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "Chat":
|
case "Chat":
|
||||||
{
|
{
|
||||||
var client = orderManager.LobbyInfo.ClientWithIndex(clientId);
|
var client = orderManager.LobbyInfo.ClientWithIndex(clientId);
|
||||||
@@ -365,5 +387,10 @@ namespace OpenRA.Network
|
|||||||
if (world.OrderValidators.All(vo => vo.OrderValidation(orderManager, world, clientId, order)))
|
if (world.OrderValidators.All(vo => vo.OrderValidation(orderManager, world, clientId, order)))
|
||||||
order.Subject.ResolveOrder(order);
|
order.Subject.ResolveOrder(order);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void Clear()
|
||||||
|
{
|
||||||
|
KickVoteTarget = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Linguini.Bundle" Version="0.5.0" />
|
<PackageReference Include="Linguini.Bundle" Version="0.5.0" />
|
||||||
<PackageReference Include="OpenRA-Eluant" Version="1.0.21" />
|
<PackageReference Include="OpenRA-Eluant" Version="1.0.22" />
|
||||||
<PackageReference Include="Mono.NAT" Version="3.0.4" />
|
<PackageReference Include="Mono.NAT" Version="3.0.4" />
|
||||||
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||||
|
|||||||
@@ -148,6 +148,8 @@ namespace OpenRA.Server
|
|||||||
GameInformation gameInfo;
|
GameInformation gameInfo;
|
||||||
readonly List<GameInformation.Player> worldPlayers = new();
|
readonly List<GameInformation.Player> worldPlayers = new();
|
||||||
readonly Stopwatch pingUpdated = Stopwatch.StartNew();
|
readonly Stopwatch pingUpdated = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
public readonly VoteKickTracker VoteKickTracker;
|
||||||
readonly PlayerMessageTracker playerMessageTracker;
|
readonly PlayerMessageTracker playerMessageTracker;
|
||||||
|
|
||||||
public ServerState State
|
public ServerState State
|
||||||
@@ -318,6 +320,7 @@ namespace OpenRA.Server
|
|||||||
MapStatusCache = new MapStatusCache(modData, MapStatusChanged, type == ServerType.Dedicated && settings.EnableLintChecks);
|
MapStatusCache = new MapStatusCache(modData, MapStatusChanged, type == ServerType.Dedicated && settings.EnableLintChecks);
|
||||||
|
|
||||||
playerMessageTracker = new PlayerMessageTracker(this, DispatchOrdersToClient, SendLocalizedMessageTo);
|
playerMessageTracker = new PlayerMessageTracker(this, DispatchOrdersToClient, SendLocalizedMessageTo);
|
||||||
|
VoteKickTracker = new VoteKickTracker(this);
|
||||||
|
|
||||||
LobbyInfo = new Session
|
LobbyInfo = new Session
|
||||||
{
|
{
|
||||||
@@ -1009,6 +1012,9 @@ namespace OpenRA.Server
|
|||||||
|
|
||||||
case "Chat":
|
case "Chat":
|
||||||
{
|
{
|
||||||
|
var client = GetClient(conn);
|
||||||
|
Console.WriteLine($"[{DateTime.Now.ToString(Settings.TimestampFormat)}] {client.Name}: {o.TargetString}");
|
||||||
|
|
||||||
if (Type == ServerType.Local || !playerMessageTracker.IsPlayerAtFloodLimit(conn))
|
if (Type == ServerType.Local || !playerMessageTracker.IsPlayerAtFloodLimit(conn))
|
||||||
DispatchOrdersToClients(conn, 0, o.Serialize());
|
DispatchOrdersToClients(conn, 0, o.Serialize());
|
||||||
|
|
||||||
@@ -1163,15 +1169,8 @@ namespace OpenRA.Server
|
|||||||
return LobbyInfo.ClientWithIndex(conn.PlayerIndex);
|
return LobbyInfo.ClientWithIndex(conn.PlayerIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Does not check if client is admin.</summary>
|
public bool HasClientWonOrLost(Session.Client client) =>
|
||||||
public bool CanKickClient(Session.Client kickee)
|
worldPlayers.FirstOrDefault(p => p?.ClientIndex == client.Index)?.Outcome != WinState.Undefined;
|
||||||
{
|
|
||||||
if (State != ServerState.GameStarted || kickee.IsObserver)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
var player = worldPlayers.FirstOrDefault(p => p?.ClientIndex == kickee.Index);
|
|
||||||
return player != null && player.Outcome != WinState.Undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DropClient(Connection toDrop)
|
public void DropClient(Connection toDrop)
|
||||||
{
|
{
|
||||||
|
|||||||
223
OpenRA.Game/Server/VoteKickTracker.cs
Normal file
223
OpenRA.Game/Server/VoteKickTracker.cs
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
#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.Diagnostics;
|
||||||
|
using OpenRA.Network;
|
||||||
|
|
||||||
|
namespace OpenRA.Server
|
||||||
|
{
|
||||||
|
public sealed class VoteKickTracker
|
||||||
|
{
|
||||||
|
[TranslationReference("kickee")]
|
||||||
|
const string InsufficientVotes = "notification-insufficient-votes-to-kick";
|
||||||
|
|
||||||
|
[TranslationReference]
|
||||||
|
const string AlreadyVoted = "notification-kick-already-voted";
|
||||||
|
|
||||||
|
[TranslationReference("kicker", "kickee")]
|
||||||
|
const string VoteKickStarted = "notification-vote-kick-started";
|
||||||
|
|
||||||
|
[TranslationReference]
|
||||||
|
const string UnableToStartAVote = "notification-unable-to-start-a-vote";
|
||||||
|
|
||||||
|
[TranslationReference("kickee", "percentage")]
|
||||||
|
const string VoteKickProgress = "notification-vote-kick-in-progress";
|
||||||
|
|
||||||
|
[TranslationReference("kickee")]
|
||||||
|
const string VoteKickEnded = "notification-vote-kick-ended";
|
||||||
|
|
||||||
|
readonly Dictionary<int, bool> voteTracker = new();
|
||||||
|
readonly Dictionary<Session.Client, long> failedVoteKickers = new();
|
||||||
|
readonly Server server;
|
||||||
|
|
||||||
|
Stopwatch voteKickTimer;
|
||||||
|
(Session.Client Client, Connection Conn) kickee;
|
||||||
|
(Session.Client Client, Connection Conn) voteKickerStarter;
|
||||||
|
|
||||||
|
public VoteKickTracker(Server server)
|
||||||
|
{
|
||||||
|
this.server = server;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only admins and alive players can participate in a vote kick.
|
||||||
|
bool ClientHasPower(Session.Client client) => client.IsAdmin || (!client.IsObserver && !server.HasClientWonOrLost(client));
|
||||||
|
|
||||||
|
public void Tick()
|
||||||
|
{
|
||||||
|
if (voteKickTimer == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!server.Conns.Contains(kickee.Conn))
|
||||||
|
{
|
||||||
|
EndKickVote();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (voteKickTimer.ElapsedMilliseconds > server.Settings.VoteKickTimer)
|
||||||
|
EndKickVoteAndBlockKicker();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool VoteKick(Connection conn, Session.Client kicker, Connection kickeeConn, Session.Client kickee, int kickeeID, bool vote)
|
||||||
|
{
|
||||||
|
var voteInProgress = voteKickTimer != null;
|
||||||
|
|
||||||
|
if (server.State != ServerState.GameStarted
|
||||||
|
|| (kickee.IsAdmin && server.Type != ServerType.Dedicated)
|
||||||
|
|| (!voteInProgress && !vote) // Disallow starting a vote with a downvote
|
||||||
|
|| (voteInProgress && this.kickee.Client != kickee) // Disallow starting new votes when one is already ongoing.
|
||||||
|
|| !ClientHasPower(kicker))
|
||||||
|
{
|
||||||
|
server.SendLocalizedMessageTo(conn, UnableToStartAVote);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
short eligiblePlayers = 0;
|
||||||
|
var isKickeeOnline = false;
|
||||||
|
var adminIsDeadButOnline = false;
|
||||||
|
foreach (var c in server.Conns)
|
||||||
|
{
|
||||||
|
var client = server.GetClient(c);
|
||||||
|
if (client != kickee && ClientHasPower(client))
|
||||||
|
eligiblePlayers++;
|
||||||
|
|
||||||
|
if (c == kickeeConn)
|
||||||
|
isKickeeOnline = true;
|
||||||
|
|
||||||
|
if (client.IsAdmin && (client.IsObserver || server.HasClientWonOrLost(client)))
|
||||||
|
adminIsDeadButOnline = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isKickeeOnline)
|
||||||
|
{
|
||||||
|
EndKickVote();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eligiblePlayers < 2 || (adminIsDeadButOnline && !kickee.IsAdmin && eligiblePlayers < 3))
|
||||||
|
{
|
||||||
|
if (!kickee.IsObserver && !server.HasClientWonOrLost(kickee))
|
||||||
|
{
|
||||||
|
// Vote kick cannot be the sole deciding factor for a game.
|
||||||
|
server.SendLocalizedMessageTo(conn, InsufficientVotes, Translation.Arguments("kickee", kickee.Name));
|
||||||
|
EndKickVote();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (vote)
|
||||||
|
{
|
||||||
|
// If only a single player is playing, allow him to kick observers.
|
||||||
|
EndKickVote(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!voteInProgress)
|
||||||
|
{
|
||||||
|
// Prevent vote kick spam abuse.
|
||||||
|
if (failedVoteKickers.TryGetValue(kicker, out var time))
|
||||||
|
{
|
||||||
|
if (time + server.Settings.VoteKickerCooldown > kickeeConn.ConnectionTimer.ElapsedMilliseconds)
|
||||||
|
{
|
||||||
|
server.SendLocalizedMessageTo(conn, UnableToStartAVote);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
failedVoteKickers.Remove(kicker);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Write("server", $"Vote kick started on {kickeeID}.");
|
||||||
|
voteKickTimer = Stopwatch.StartNew();
|
||||||
|
server.SendLocalizedMessage(VoteKickStarted, Translation.Arguments("kicker", kicker.Name, "kickee", kickee.Name));
|
||||||
|
server.DispatchServerOrdersToClients(new Order("StartKickVote", null, false) { ExtraData = (uint)kickeeID }.Serialize());
|
||||||
|
this.kickee = (kickee, kickeeConn);
|
||||||
|
voteKickerStarter = (kicker, conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!voteTracker.ContainsKey(conn.PlayerIndex))
|
||||||
|
voteTracker[conn.PlayerIndex] = vote;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
server.SendLocalizedMessageTo(conn, AlreadyVoted, null);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
short votesFor = 0;
|
||||||
|
short votesAgainst = 0;
|
||||||
|
foreach (var c in voteTracker)
|
||||||
|
{
|
||||||
|
if (c.Value)
|
||||||
|
votesFor++;
|
||||||
|
else
|
||||||
|
votesAgainst++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Include the kickee in eligeablePlayers, so that in a 2v2 or any other even team
|
||||||
|
// matchup one team could not vote out the other team's player.
|
||||||
|
if (ClientHasPower(kickee))
|
||||||
|
{
|
||||||
|
eligiblePlayers++;
|
||||||
|
votesAgainst++;
|
||||||
|
}
|
||||||
|
|
||||||
|
var votesNeeded = eligiblePlayers / 2 + 1;
|
||||||
|
server.SendLocalizedMessage(VoteKickProgress, Translation.Arguments(
|
||||||
|
"kickee", kickee.Name,
|
||||||
|
"percentage", votesFor * 100 / eligiblePlayers));
|
||||||
|
|
||||||
|
// If a player or players during a vote lose or disconnect, it is possible that a downvote will
|
||||||
|
// kick a client. Guard against that situation.
|
||||||
|
if (vote && (votesFor >= votesNeeded))
|
||||||
|
{
|
||||||
|
EndKickVote(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// End vote if it can never succeed.
|
||||||
|
if (eligiblePlayers - votesAgainst < votesNeeded)
|
||||||
|
{
|
||||||
|
EndKickVoteAndBlockKicker();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
voteKickTimer.Restart();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EndKickVoteAndBlockKicker()
|
||||||
|
{
|
||||||
|
// Make sure vote kick is in progress.
|
||||||
|
if (voteKickTimer == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (server.Conns.Contains(voteKickerStarter.Conn))
|
||||||
|
failedVoteKickers[voteKickerStarter.Client] = voteKickerStarter.Conn.ConnectionTimer.ElapsedMilliseconds;
|
||||||
|
|
||||||
|
EndKickVote();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EndKickVote(bool sendMessage = true)
|
||||||
|
{
|
||||||
|
// Make sure vote kick is in progress.
|
||||||
|
if (voteKickTimer == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (sendMessage)
|
||||||
|
server.SendLocalizedMessage(VoteKickEnded, Translation.Arguments("kickee", kickee.Client.Name));
|
||||||
|
|
||||||
|
server.DispatchServerOrdersToClients(new Order("EndKickVote", null, false) { ExtraData = (uint)kickee.Client.Index }.Serialize());
|
||||||
|
|
||||||
|
voteKickTimer = null;
|
||||||
|
voteKickerStarter = (null, null);
|
||||||
|
kickee = (null, null);
|
||||||
|
voteTracker.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -114,6 +114,15 @@ namespace OpenRA
|
|||||||
[Desc("Delay in milliseconds before players can send chat messages after flood was detected.")]
|
[Desc("Delay in milliseconds before players can send chat messages after flood was detected.")]
|
||||||
public int FloodLimitCooldown = 15000;
|
public int FloodLimitCooldown = 15000;
|
||||||
|
|
||||||
|
[Desc("Can players vote to kick other players?")]
|
||||||
|
public bool EnableVoteKick = true;
|
||||||
|
|
||||||
|
[Desc("After how much time in miliseconds should the vote kick fail after idling?")]
|
||||||
|
public int VoteKickTimer = 30000;
|
||||||
|
|
||||||
|
[Desc("If a vote kick was unsuccessful for how long should the player who started the vote not be able to start new votes?")]
|
||||||
|
public int VoteKickerCooldown = 120000;
|
||||||
|
|
||||||
public ServerSettings Clone()
|
public ServerSettings Clone()
|
||||||
{
|
{
|
||||||
return (ServerSettings)MemberwiseClone();
|
return (ServerSettings)MemberwiseClone();
|
||||||
|
|||||||
@@ -549,7 +549,7 @@ namespace OpenRA.Traits
|
|||||||
{
|
{
|
||||||
Id = id;
|
Id = id;
|
||||||
Name = map.GetLocalisedString(name);
|
Name = map.GetLocalisedString(name);
|
||||||
Description = map.GetLocalisedString(description);
|
Description = description != null ? map.GetLocalisedString(description) : null;
|
||||||
IsVisible = visible;
|
IsVisible = visible;
|
||||||
DisplayOrder = displayorder;
|
DisplayOrder = displayorder;
|
||||||
Values = values.ToDictionary(v => v.Key, v => map.GetLocalisedString(v.Value));
|
Values = values.ToDictionary(v => v.Key, v => map.GetLocalisedString(v.Value));
|
||||||
|
|||||||
@@ -103,6 +103,8 @@ namespace OpenRA.Mods.Cnc.UtilityCommands
|
|||||||
if (Map.Rules.TerrainInfo is ITerrainInfoNotifyMapCreated notifyMapCreated)
|
if (Map.Rules.TerrainInfo is ITerrainInfoNotifyMapCreated notifyMapCreated)
|
||||||
notifyMapCreated.MapCreated(Map);
|
notifyMapCreated.MapCreated(Map);
|
||||||
|
|
||||||
|
ReplaceInvalidTerrainTiles(Map);
|
||||||
|
|
||||||
var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap";
|
var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap";
|
||||||
|
|
||||||
Map.Save(ZipFileLoader.Create(dest));
|
Map.Save(ZipFileLoader.Create(dest));
|
||||||
@@ -159,6 +161,19 @@ namespace OpenRA.Mods.Cnc.UtilityCommands
|
|||||||
missionData.Value.Nodes.Add(new MiniYamlNode("Briefing", briefing.Replace("\n", " ").ToString()));
|
missionData.Value.Nodes.Add(new MiniYamlNode("Briefing", briefing.Replace("\n", " ").ToString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ReplaceInvalidTerrainTiles(Map map)
|
||||||
|
{
|
||||||
|
var terrainInfo = map.Rules.TerrainInfo;
|
||||||
|
foreach (var uv in map.AllCells.MapCoords)
|
||||||
|
{
|
||||||
|
if (!terrainInfo.TryGetTerrainInfo(map.Tiles[uv], out _))
|
||||||
|
{
|
||||||
|
map.Tiles[uv] = terrainInfo.DefaultTerrainTile;
|
||||||
|
Console.WriteLine($"Replaced invalid terrain tile at {uv}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void SetBounds(Map map, IniSection mapSection)
|
static void SetBounds(Map map, IniSection mapSection)
|
||||||
{
|
{
|
||||||
var offsetX = Exts.ParseIntegerInvariant(mapSection.GetValue("X", "0"));
|
var offsetX = Exts.ParseIntegerInvariant(mapSection.GetValue("X", "0"));
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
*/
|
*/
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OpenRA.Activities;
|
using OpenRA.Activities;
|
||||||
@@ -185,7 +186,19 @@ namespace OpenRA.Mods.Common.Activities
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
var isSlider = aircraft.Info.CanSlide;
|
var isSlider = aircraft.Info.CanSlide;
|
||||||
var desiredFacing = delta.HorizontalLengthSquared != 0 ? delta.Yaw : aircraft.Facing;
|
|
||||||
|
var desiredFacing = aircraft.Facing;
|
||||||
|
if (delta.HorizontalLengthSquared != 0)
|
||||||
|
{
|
||||||
|
var facing = delta.Yaw;
|
||||||
|
|
||||||
|
// Prevent jittering.
|
||||||
|
var diff = Math.Abs(facing.Angle - desiredFacing.Angle);
|
||||||
|
var deadzone = aircraft.Info.TurnDeadzone.Angle;
|
||||||
|
if (diff > deadzone && diff < 1024 - deadzone)
|
||||||
|
desiredFacing = facing;
|
||||||
|
}
|
||||||
|
|
||||||
var move = isSlider ? aircraft.FlyStep(desiredFacing) : aircraft.FlyStep(aircraft.Facing);
|
var move = isSlider ? aircraft.FlyStep(desiredFacing) : aircraft.FlyStep(aircraft.Facing);
|
||||||
|
|
||||||
// Inside the minimum range, so reverse if we CanSlide, otherwise face away from the target.
|
// Inside the minimum range, so reverse if we CanSlide, otherwise face away from the target.
|
||||||
@@ -194,9 +207,7 @@ namespace OpenRA.Mods.Common.Activities
|
|||||||
if (isSlider)
|
if (isSlider)
|
||||||
FlyTick(self, aircraft, desiredFacing, aircraft.Info.CruiseAltitude, -move);
|
FlyTick(self, aircraft, desiredFacing, aircraft.Info.CruiseAltitude, -move);
|
||||||
else
|
else
|
||||||
{
|
|
||||||
FlyTick(self, aircraft, desiredFacing + new WAngle(512), aircraft.Info.CruiseAltitude, move);
|
FlyTick(self, aircraft, desiredFacing + new WAngle(512), aircraft.Info.CruiseAltitude, move);
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,32 +10,21 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using OpenRA.FileSystem;
|
|
||||||
using OpenRA.Mods.Common.Scripting;
|
using OpenRA.Mods.Common.Scripting;
|
||||||
using OpenRA.Server;
|
|
||||||
|
|
||||||
namespace OpenRA.Mods.Common.Lint
|
namespace OpenRA.Mods.Common.Lint
|
||||||
{
|
{
|
||||||
public class CheckLuaScript : ILintMapPass, ILintServerMapPass
|
public class CheckLuaScript : ILintMapPass
|
||||||
{
|
{
|
||||||
void ILintMapPass.Run(Action<string> emitError, Action<string> emitWarning, ModData modData, Map map)
|
void ILintMapPass.Run(Action<string> emitError, Action<string> emitWarning, ModData modData, Map map)
|
||||||
{
|
{
|
||||||
CheckLuaScriptFileExistance(emitError, map.Package, modData.DefaultFileSystem, map.Rules);
|
var luaScriptInfo = map.Rules.Actors[SystemActors.World].TraitInfoOrDefault<LuaScriptInfo>();
|
||||||
}
|
|
||||||
|
|
||||||
void ILintServerMapPass.Run(Action<string> emitError, Action<string> emitWarning, ModData modData, MapPreview map, Ruleset mapRules)
|
|
||||||
{
|
|
||||||
CheckLuaScriptFileExistance(emitError, map.Package, modData.DefaultFileSystem, mapRules);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void CheckLuaScriptFileExistance(Action<string> emitError, IReadOnlyPackage package, IReadOnlyFileSystem fileSystem, Ruleset mapRules)
|
|
||||||
{
|
|
||||||
var luaScriptInfo = mapRules.Actors[SystemActors.World].TraitInfoOrDefault<LuaScriptInfo>();
|
|
||||||
if (luaScriptInfo == null)
|
if (luaScriptInfo == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// We aren't running this lint on servers as they don't create map packages.
|
||||||
foreach (var script in luaScriptInfo.Scripts)
|
foreach (var script in luaScriptInfo.Scripts)
|
||||||
if (!package.Contains(script) && !fileSystem.Exists(script))
|
if (!map.Package.Contains(script) && !modData.DefaultFileSystem.Exists(script))
|
||||||
emitError($"Lua script `{script}` does not exist.");
|
emitError($"Lua script `{script}` does not exist.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ namespace OpenRA.Mods.Common.Scripting
|
|||||||
{
|
{
|
||||||
[TraitLocation(SystemActors.World)]
|
[TraitLocation(SystemActors.World)]
|
||||||
[Desc("Part of the new Lua API.")]
|
[Desc("Part of the new Lua API.")]
|
||||||
public class LuaScriptInfo : TraitInfo, Requires<SpawnMapActorsInfo>
|
public class LuaScriptInfo : TraitInfo, Requires<SpawnMapActorsInfo>, NotBefore<SpawnStartingUnitsInfo>
|
||||||
{
|
{
|
||||||
[Desc("File names with location relative to the map.")]
|
[Desc("File names with location relative to the map.")]
|
||||||
public readonly HashSet<string> Scripts = new();
|
public readonly HashSet<string> Scripts = new();
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ using S = OpenRA.Server.Server;
|
|||||||
|
|
||||||
namespace OpenRA.Mods.Common.Server
|
namespace OpenRA.Mods.Common.Server
|
||||||
{
|
{
|
||||||
public class LobbyCommands : ServerTrait, IInterpretCommand, INotifyServerStart, INotifyServerEmpty, IClientJoined
|
public class LobbyCommands : ServerTrait, IInterpretCommand, INotifyServerStart, INotifyServerEmpty, IClientJoined, OpenRA.Server.ITick
|
||||||
{
|
{
|
||||||
[TranslationReference]
|
[TranslationReference]
|
||||||
const string CustomRules = "notification-custom-rules";
|
const string CustomRules = "notification-custom-rules";
|
||||||
@@ -55,6 +55,9 @@ namespace OpenRA.Mods.Common.Server
|
|||||||
const string NoKickGameStarted = "notification-no-kick-game-started";
|
const string NoKickGameStarted = "notification-no-kick-game-started";
|
||||||
|
|
||||||
[TranslationReference("admin", "player")]
|
[TranslationReference("admin", "player")]
|
||||||
|
const string AdminKicked = "notification-admin-kicked";
|
||||||
|
|
||||||
|
[TranslationReference("player")]
|
||||||
const string Kicked = "notification-kicked";
|
const string Kicked = "notification-kicked";
|
||||||
|
|
||||||
[TranslationReference("admin", "player")]
|
[TranslationReference("admin", "player")]
|
||||||
@@ -156,6 +159,9 @@ namespace OpenRA.Mods.Common.Server
|
|||||||
[TranslationReference]
|
[TranslationReference]
|
||||||
const string YouWereKicked = "notification-you-were-kicked";
|
const string YouWereKicked = "notification-you-were-kicked";
|
||||||
|
|
||||||
|
[TranslationReference]
|
||||||
|
const string VoteKickDisabled = "notification-vote-kick-disabled";
|
||||||
|
|
||||||
readonly IDictionary<string, Func<S, Connection, Session.Client, string, bool>> commandHandlers = new Dictionary<string, Func<S, Connection, Session.Client, string, bool>>
|
readonly IDictionary<string, Func<S, Connection, Session.Client, string, bool>> commandHandlers = new Dictionary<string, Func<S, Connection, Session.Client, string, bool>>
|
||||||
{
|
{
|
||||||
{ "state", State },
|
{ "state", State },
|
||||||
@@ -170,6 +176,7 @@ namespace OpenRA.Mods.Common.Server
|
|||||||
{ "option", Option },
|
{ "option", Option },
|
||||||
{ "assignteams", AssignTeams },
|
{ "assignteams", AssignTeams },
|
||||||
{ "kick", Kick },
|
{ "kick", Kick },
|
||||||
|
{ "vote_kick", VoteKick },
|
||||||
{ "make_admin", MakeAdmin },
|
{ "make_admin", MakeAdmin },
|
||||||
{ "make_spectator", MakeSpectator },
|
{ "make_spectator", MakeSpectator },
|
||||||
{ "name", Name },
|
{ "name", Name },
|
||||||
@@ -207,7 +214,7 @@ namespace OpenRA.Mods.Common.Server
|
|||||||
lock (server.LobbyInfo)
|
lock (server.LobbyInfo)
|
||||||
{
|
{
|
||||||
// Kick command is always valid for the host
|
// Kick command is always valid for the host
|
||||||
if (command.StartsWith("kick "))
|
if (command.StartsWith("kick ", StringComparison.Ordinal) || command.StartsWith("vote_kick ", StringComparison.Ordinal))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (server.State == ServerState.GameStarted)
|
if (server.State == ServerState.GameStarted)
|
||||||
@@ -804,14 +811,14 @@ namespace OpenRA.Mods.Common.Server
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!server.CanKickClient(kickClient))
|
if (server.State == ServerState.GameStarted && !kickClient.IsObserver && !server.HasClientWonOrLost(kickClient))
|
||||||
{
|
{
|
||||||
server.SendLocalizedMessageTo(conn, NoKickGameStarted);
|
server.SendLocalizedMessageTo(conn, NoKickGameStarted);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Write("server", $"Kicking client {kickClientID}.");
|
Log.Write("server", $"Kicking client {kickClientID}.");
|
||||||
server.SendLocalizedMessage(Kicked, Translation.Arguments("admin", client.Name, "player", kickClient.Name));
|
server.SendLocalizedMessage(AdminKicked, Translation.Arguments("admin", client.Name, "player", kickClient.Name));
|
||||||
server.SendOrderTo(kickConn, "ServerError", YouWereKicked);
|
server.SendOrderTo(kickConn, "ServerError", YouWereKicked);
|
||||||
server.DropClient(kickConn);
|
server.DropClient(kickConn);
|
||||||
|
|
||||||
@@ -829,6 +836,62 @@ namespace OpenRA.Mods.Common.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool VoteKick(S server, Connection conn, Session.Client client, string s)
|
||||||
|
{
|
||||||
|
lock (server.LobbyInfo)
|
||||||
|
{
|
||||||
|
var split = s.Split(' ');
|
||||||
|
if (split.Length != 2)
|
||||||
|
{
|
||||||
|
server.SendLocalizedMessageTo(conn, MalformedCommand, Translation.Arguments("command", "vote_kick"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!server.Settings.EnableVoteKick)
|
||||||
|
{
|
||||||
|
server.SendLocalizedMessageTo(conn, VoteKickDisabled);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var kickConn = Exts.TryParseInt32Invariant(split[0], out var kickClientID)
|
||||||
|
? server.Conns.SingleOrDefault(c => server.GetClient(c)?.Index == kickClientID) : null;
|
||||||
|
|
||||||
|
if (kickConn == null)
|
||||||
|
{
|
||||||
|
server.SendLocalizedMessageTo(conn, KickNone);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var kickClient = server.GetClient(kickConn);
|
||||||
|
if (client == kickClient)
|
||||||
|
{
|
||||||
|
server.SendLocalizedMessageTo(conn, NoKickSelf);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bool.TryParse(split[1], out var vote))
|
||||||
|
{
|
||||||
|
server.SendLocalizedMessageTo(conn, MalformedCommand, Translation.Arguments("command", "vote_kick"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (server.VoteKickTracker.VoteKick(conn, client, kickConn, kickClient, kickClientID, vote))
|
||||||
|
{
|
||||||
|
Log.Write("server", $"Kicking client {kickClientID}.");
|
||||||
|
server.SendLocalizedMessage(Kicked, Translation.Arguments("player", kickClient.Name));
|
||||||
|
server.SendOrderTo(kickConn, "ServerError", YouWereKicked);
|
||||||
|
server.DropClient(kickConn);
|
||||||
|
|
||||||
|
server.SyncLobbyClients();
|
||||||
|
server.SyncLobbySlots();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenRA.Server.ITick.Tick(S server) => server.VoteKickTracker.Tick();
|
||||||
|
|
||||||
static bool MakeAdmin(S server, Connection conn, Session.Client client, string s)
|
static bool MakeAdmin(S server, Connection conn, Session.Client client, string s)
|
||||||
{
|
{
|
||||||
lock (server.LobbyInfo)
|
lock (server.LobbyInfo)
|
||||||
|
|||||||
@@ -170,18 +170,15 @@ namespace OpenRA.Mods.Common.Terrain
|
|||||||
|
|
||||||
void ITerrainInfoNotifyMapCreated.MapCreated(Map map)
|
void ITerrainInfoNotifyMapCreated.MapCreated(Map map)
|
||||||
{
|
{
|
||||||
// Randomize PickAny tile variants
|
// Randomize PickAny tile variants.
|
||||||
var r = new MersenneTwister();
|
var r = new MersenneTwister();
|
||||||
for (var j = map.Bounds.Top; j < map.Bounds.Bottom; j++)
|
foreach (var uv in map.AllCells.MapCoords)
|
||||||
{
|
{
|
||||||
for (var i = map.Bounds.Left; i < map.Bounds.Right; i++)
|
var type = map.Tiles[uv].Type;
|
||||||
{
|
if (!Templates.TryGetValue(type, out var template) || !template.PickAny)
|
||||||
var type = map.Tiles[new MPos(i, j)].Type;
|
continue;
|
||||||
if (!Templates.TryGetValue(type, out var template) || !template.PickAny)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
map.Tiles[new MPos(i, j)] = new TerrainTile(type, (byte)r.Next(0, template.TilesCount));
|
map.Tiles[uv] = new TerrainTile(type, (byte)r.Next(0, template.TilesCount));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
[Desc("Turn speed to apply when aircraft flies in circles while idle. Defaults to TurnSpeed if undefined.")]
|
[Desc("Turn speed to apply when aircraft flies in circles while idle. Defaults to TurnSpeed if undefined.")]
|
||||||
public readonly WAngle? IdleTurnSpeed = null;
|
public readonly WAngle? IdleTurnSpeed = null;
|
||||||
|
|
||||||
|
[Desc("When flying if the difference between current facing and desired facing is less than this value, don't turn. This prevents visual jitter.")]
|
||||||
|
public readonly WAngle TurnDeadzone = new(2);
|
||||||
|
|
||||||
[Desc("Maximum flight speed when cruising.")]
|
[Desc("Maximum flight speed when cruising.")]
|
||||||
public readonly int Speed = 1;
|
public readonly int Speed = 1;
|
||||||
|
|
||||||
|
|||||||
@@ -128,10 +128,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
var dropRange = carryall.Info.DropRange;
|
var dropRange = carryall.Info.DropRange;
|
||||||
var destination = carryable.Destination;
|
self.QueueActivity(true, new DeliverUnit(self, Target.FromCell(self.World, carryable.Destination ?? self.Location), dropRange, carryall.Info.TargetLineColor));
|
||||||
if (destination != null)
|
|
||||||
self.QueueActivity(true, new DeliverUnit(self, Target.FromCell(self.World, destination.Value), dropRange, carryall.Info.TargetLineColor));
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
{
|
{
|
||||||
var autoTarget = ScanForTarget(self, AllowMove, true);
|
var autoTarget = ScanForTarget(self, AllowMove, true);
|
||||||
|
|
||||||
if (autoTarget != Target.Invalid)
|
if (autoTarget.Type != TargetType.Invalid)
|
||||||
attacker = autoTarget.Actor;
|
attacker = autoTarget.Actor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ using System.Linq;
|
|||||||
|
|
||||||
namespace OpenRA.Mods.Common.Traits
|
namespace OpenRA.Mods.Common.Traits
|
||||||
{
|
{
|
||||||
[Desc("Grants a condition on the collector.")]
|
[Desc("Grants a condition to the collector and nearby units.")]
|
||||||
public class GrantExternalConditionCrateActionInfo : CrateActionInfo
|
public class GrantExternalConditionCrateActionInfo : CrateActionInfo
|
||||||
{
|
{
|
||||||
[FieldLoader.Require]
|
[FieldLoader.Require]
|
||||||
@@ -60,38 +60,36 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
|
|
||||||
public override void Activate(Actor collector)
|
public override void Activate(Actor collector)
|
||||||
{
|
{
|
||||||
|
if (collector.IsInWorld && !collector.IsDead)
|
||||||
|
GrantCondition(collector);
|
||||||
|
|
||||||
var actorsInRange = self.World.FindActorsInCircle(self.CenterPosition, info.Range)
|
var actorsInRange = self.World.FindActorsInCircle(self.CenterPosition, info.Range)
|
||||||
.Where(a => a != self && a != collector && a.Owner == collector.Owner && AcceptsCondition(a));
|
.Where(a => a != self && a != collector && a.IsInWorld && !a.IsDead && a.Owner == collector.Owner && AcceptsCondition(a))
|
||||||
|
.OrderBy(a => (a.CenterPosition - self.CenterPosition).LengthSquared);
|
||||||
|
|
||||||
if (info.MaxExtraCollectors > -1)
|
foreach (var a in info.MaxExtraCollectors > -1 ? actorsInRange.Take(info.MaxExtraCollectors) : actorsInRange)
|
||||||
actorsInRange = actorsInRange.Take(info.MaxExtraCollectors);
|
GrantCondition(a);
|
||||||
|
|
||||||
collector.World.AddFrameEndTask(w =>
|
|
||||||
{
|
|
||||||
foreach (var a in actorsInRange.Append(collector))
|
|
||||||
{
|
|
||||||
if (!a.IsInWorld || a.IsDead)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var externals = a.TraitsImplementing<ExternalCondition>()
|
|
||||||
.Where(t => t.Info.Condition == info.Condition);
|
|
||||||
|
|
||||||
ExternalCondition external = null;
|
|
||||||
for (var n = 0; n < info.Levels; n++)
|
|
||||||
{
|
|
||||||
if (external == null || !external.CanGrantCondition(self))
|
|
||||||
{
|
|
||||||
external = externals.FirstOrDefault(t => t.CanGrantCondition(self));
|
|
||||||
if (external == null)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
external.GrantCondition(a, self, info.Duration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
base.Activate(collector);
|
base.Activate(collector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GrantCondition(Actor actor)
|
||||||
|
{
|
||||||
|
var externals = actor.TraitsImplementing<ExternalCondition>()
|
||||||
|
.Where(t => t.Info.Condition == info.Condition);
|
||||||
|
|
||||||
|
ExternalCondition external = null;
|
||||||
|
for (var n = 0; n < info.Levels; n++)
|
||||||
|
{
|
||||||
|
if (external == null || !external.CanGrantCondition(self))
|
||||||
|
{
|
||||||
|
external = externals.FirstOrDefault(t => t.CanGrantCondition(self));
|
||||||
|
if (external == null)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
external.GrantCondition(actor, self, info.Duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
|
|
||||||
devMode = self.Trait<DeveloperMode>();
|
devMode = self.Trait<DeveloperMode>();
|
||||||
wasHackEnabled = devMode.UnlimitedPower;
|
wasHackEnabled = devMode.UnlimitedPower;
|
||||||
|
PlayLowPowerNotification = info.AdviceInterval > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void INotifyCreated.Created(Actor self)
|
void INotifyCreated.Created(Actor self)
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
|
|
||||||
[FieldLoader.Require]
|
[FieldLoader.Require]
|
||||||
[TranslationReference(dictionaryReference: LintDictionaryReference.Values)]
|
[TranslationReference(dictionaryReference: LintDictionaryReference.Values)]
|
||||||
[Desc("Difficulty levels supported by the map.")]
|
[Desc("Options to choose from.")]
|
||||||
public readonly Dictionary<string, string> Values = null;
|
public readonly Dictionary<string, string> Values = null;
|
||||||
|
|
||||||
[Desc("Prevent the option from being changed from its default value.")]
|
[Desc("Prevent the option from being changed from its default value.")]
|
||||||
|
|||||||
@@ -92,19 +92,73 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules
|
|||||||
defaultSpriteExtension = defaultSpriteExtensionNode.Value.Value;
|
defaultSpriteExtension = defaultSpriteExtensionNode.Value.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
var tilesetExtensionsNode = spriteSequenceFormatNode.LastChildMatching("TilesetExtensions");
|
var fromBackup = false;
|
||||||
|
|
||||||
|
var tilesetExtensionsNode = spriteSequenceFormatNode.LastChildMatching("TilesetExtensions")?.Value?.Nodes;
|
||||||
|
if (tilesetExtensionsNode == null)
|
||||||
|
{
|
||||||
|
switch (modData.Manifest.Id)
|
||||||
|
{
|
||||||
|
case "cnc":
|
||||||
|
fromBackup = true;
|
||||||
|
tilesetExtensionsNode = new List<MiniYamlNode>()
|
||||||
|
{
|
||||||
|
new MiniYamlNode("TEMPERAT", ".tem"),
|
||||||
|
new MiniYamlNode("SNOW", ".sno"),
|
||||||
|
new MiniYamlNode("INTERIOR", ".int"),
|
||||||
|
new MiniYamlNode("DESERT", ".des"),
|
||||||
|
new MiniYamlNode("JUNGLE", ".jun"),
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case "ra":
|
||||||
|
fromBackup = true;
|
||||||
|
tilesetExtensionsNode = new List<MiniYamlNode>()
|
||||||
|
{
|
||||||
|
new MiniYamlNode("TEMPERAT", ".tem"),
|
||||||
|
new MiniYamlNode("SNOW", ".sno"),
|
||||||
|
new MiniYamlNode("INTERIOR", ".int"),
|
||||||
|
new MiniYamlNode("DESERT", ".des"),
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case "ts":
|
||||||
|
fromBackup = true;
|
||||||
|
tilesetExtensionsNode = new List<MiniYamlNode>()
|
||||||
|
{
|
||||||
|
new MiniYamlNode("TEMPERATE", ".tem"),
|
||||||
|
new MiniYamlNode("SNOW", ".sno"),
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (tilesetExtensionsNode != null)
|
if (tilesetExtensionsNode != null)
|
||||||
{
|
{
|
||||||
reportModYamlChanges = true;
|
if (!fromBackup)
|
||||||
foreach (var n in tilesetExtensionsNode.Value.Nodes)
|
reportModYamlChanges = true;
|
||||||
|
|
||||||
|
foreach (var n in tilesetExtensionsNode)
|
||||||
tilesetExtensions[n.Key] = n.Value.Value;
|
tilesetExtensions[n.Key] = n.Value.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
var tilesetCodesNode = spriteSequenceFormatNode.LastChildMatching("TilesetCodes");
|
fromBackup = false;
|
||||||
|
|
||||||
|
var tilesetCodesNode = spriteSequenceFormatNode.LastChildMatching("TilesetCodes")?.Value?.Nodes;
|
||||||
|
if (tilesetCodesNode == null && modData.Manifest.Id == "ts")
|
||||||
|
{
|
||||||
|
fromBackup = true;
|
||||||
|
tilesetCodesNode = new List<MiniYamlNode>()
|
||||||
|
{
|
||||||
|
new MiniYamlNode("TEMPERATE", "t"),
|
||||||
|
new MiniYamlNode("SNOW", "a"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (tilesetCodesNode != null)
|
if (tilesetCodesNode != null)
|
||||||
{
|
{
|
||||||
reportModYamlChanges = true;
|
if (!fromBackup)
|
||||||
foreach (var n in tilesetCodesNode.Value.Nodes)
|
reportModYamlChanges = true;
|
||||||
|
|
||||||
|
foreach (var n in tilesetCodesNode)
|
||||||
tilesetCodes[n.Key] = n.Value.Value;
|
tilesetCodes[n.Key] = n.Value.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
this.isSavePanel = isSavePanel;
|
this.isSavePanel = isSavePanel;
|
||||||
Game.BeforeGameStart += OnGameStart;
|
Game.BeforeGameStart += OnGameStart;
|
||||||
|
|
||||||
panel.Get<ButtonWidget>("CANCEL_BUTTON").OnClick = () =>
|
var cancelButton = panel.Get<ButtonWidget>("CANCEL_BUTTON");
|
||||||
|
cancelButton.OnClick = () =>
|
||||||
{
|
{
|
||||||
Ui.CloseWindow();
|
Ui.CloseWindow();
|
||||||
onExit();
|
onExit();
|
||||||
@@ -117,17 +118,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
saveButton.IsVisible = () => true;
|
saveButton.IsVisible = () => true;
|
||||||
|
|
||||||
var saveWidgets = panel.Get("SAVE_WIDGETS");
|
var saveWidgets = panel.Get("SAVE_WIDGETS");
|
||||||
saveTextField = saveWidgets.Get<TextFieldWidget>("SAVE_TEXTFIELD");
|
|
||||||
gameList.Bounds.Height -= saveWidgets.Bounds.Height;
|
gameList.Bounds.Height -= saveWidgets.Bounds.Height;
|
||||||
saveWidgets.IsVisible = () => true;
|
saveWidgets.IsVisible = () => true;
|
||||||
|
|
||||||
saveTextField.OnEnterKey = _ =>
|
saveTextField = saveWidgets.Get<TextFieldWidget>("SAVE_TEXTFIELD");
|
||||||
{
|
saveTextField.OnEnterKey = input => saveButton.HandleKeyPress(input);
|
||||||
if (!string.IsNullOrWhiteSpace(saveTextField.Text))
|
saveTextField.OnEscKey = input => cancelButton.HandleKeyPress(input);
|
||||||
Save(world);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -40,12 +40,13 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
readonly World world;
|
readonly World world;
|
||||||
readonly ModData modData;
|
readonly ModData modData;
|
||||||
readonly Action<bool> hideMenu;
|
readonly Action<bool> hideMenu;
|
||||||
|
readonly Action closeMenu;
|
||||||
readonly IObjectivesPanel iop;
|
readonly IObjectivesPanel iop;
|
||||||
IngameInfoPanel activePanel;
|
IngameInfoPanel activePanel;
|
||||||
readonly bool hasError;
|
readonly bool hasError;
|
||||||
|
|
||||||
[ObjectCreator.UseCtor]
|
[ObjectCreator.UseCtor]
|
||||||
public GameInfoLogic(Widget widget, ModData modData, World world, IngameInfoPanel initialPanel, Action<bool> hideMenu)
|
public GameInfoLogic(Widget widget, ModData modData, World world, IngameInfoPanel initialPanel, Action<bool> hideMenu, Action closeMenu)
|
||||||
{
|
{
|
||||||
var panels = new Dictionary<IngameInfoPanel, (string Panel, string Label, Action<ButtonWidget, Widget> Setup)>()
|
var panels = new Dictionary<IngameInfoPanel, (string Panel, string Label, Action<ButtonWidget, Widget> Setup)>()
|
||||||
{
|
{
|
||||||
@@ -59,6 +60,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
this.world = world;
|
this.world = world;
|
||||||
this.modData = modData;
|
this.modData = modData;
|
||||||
this.hideMenu = hideMenu;
|
this.hideMenu = hideMenu;
|
||||||
|
this.closeMenu = closeMenu;
|
||||||
activePanel = initialPanel;
|
activePanel = initialPanel;
|
||||||
|
|
||||||
var visiblePanels = new List<IngameInfoPanel>();
|
var visiblePanels = new List<IngameInfoPanel>();
|
||||||
@@ -140,7 +142,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
var panel = hasError ? "SCRIPT_ERROR_PANEL" : iop.PanelName;
|
var panel = hasError ? "SCRIPT_ERROR_PANEL" : iop.PanelName;
|
||||||
Game.LoadWidget(world, panel, objectivesPanelContainer, new WidgetArgs()
|
Game.LoadWidget(world, panel, objectivesPanelContainer, new WidgetArgs()
|
||||||
{
|
{
|
||||||
{ "hideMenu", hideMenu }
|
{ "hideMenu", hideMenu },
|
||||||
|
{ "closeMenu", closeMenu },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
[TranslationReference]
|
[TranslationReference]
|
||||||
const string Gone = "label-client-state-disconnected";
|
const string Gone = "label-client-state-disconnected";
|
||||||
|
|
||||||
|
[TranslationReference]
|
||||||
|
const string KickTooltip = "button-kick-player";
|
||||||
|
|
||||||
[TranslationReference("player")]
|
[TranslationReference("player")]
|
||||||
const string KickTitle = "dialog-kick.title";
|
const string KickTitle = "dialog-kick.title";
|
||||||
|
|
||||||
@@ -58,8 +61,32 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
[TranslationReference]
|
[TranslationReference]
|
||||||
const string KickAccept = "dialog-kick.confirm";
|
const string KickAccept = "dialog-kick.confirm";
|
||||||
|
|
||||||
|
[TranslationReference]
|
||||||
|
const string KickVoteTooltip = "button-vote-kick-player";
|
||||||
|
|
||||||
|
[TranslationReference("player")]
|
||||||
|
const string VoteKickTitle = "dialog-vote-kick.title";
|
||||||
|
|
||||||
|
[TranslationReference]
|
||||||
|
const string VoteKickPrompt = "dialog-vote-kick.prompt";
|
||||||
|
|
||||||
|
[TranslationReference("bots")]
|
||||||
|
const string VoteKickPromptBreakBots = "dialog-vote-kick.prompt-break-bots";
|
||||||
|
|
||||||
|
[TranslationReference]
|
||||||
|
const string VoteKickVoteStart = "dialog-vote-kick.vote-start";
|
||||||
|
|
||||||
|
[TranslationReference]
|
||||||
|
const string VoteKickVoteFor = "dialog-vote-kick.vote-for";
|
||||||
|
|
||||||
|
[TranslationReference]
|
||||||
|
const string VoteKickVoteAgainst = "dialog-vote-kick.vote-against";
|
||||||
|
|
||||||
|
[TranslationReference]
|
||||||
|
const string VoteKickVoteCancel = "dialog-vote-kick.vote-cancel";
|
||||||
|
|
||||||
[ObjectCreator.UseCtor]
|
[ObjectCreator.UseCtor]
|
||||||
public GameInfoStatsLogic(Widget widget, ModData modData, World world, OrderManager orderManager, WorldRenderer worldRenderer, Action<bool> hideMenu)
|
public GameInfoStatsLogic(Widget widget, ModData modData, World world, OrderManager orderManager, WorldRenderer worldRenderer, Action<bool> hideMenu, Action closeMenu)
|
||||||
{
|
{
|
||||||
var player = world.LocalPlayer;
|
var player = world.LocalPlayer;
|
||||||
var playerPanel = widget.Get<ScrollPanelWidget>("PLAYER_LIST");
|
var playerPanel = widget.Get<ScrollPanelWidget>("PLAYER_LIST");
|
||||||
@@ -106,6 +133,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
var spectatorTemplate = playerPanel.Get("SPECTATOR_TEMPLATE");
|
var spectatorTemplate = playerPanel.Get("SPECTATOR_TEMPLATE");
|
||||||
var unmuteTooltip = TranslationProvider.GetString(Unmute);
|
var unmuteTooltip = TranslationProvider.GetString(Unmute);
|
||||||
var muteTooltip = TranslationProvider.GetString(Mute);
|
var muteTooltip = TranslationProvider.GetString(Mute);
|
||||||
|
var kickTooltip = TranslationProvider.GetString(KickTooltip);
|
||||||
|
var voteKickTooltip = TranslationProvider.GetString(KickVoteTooltip);
|
||||||
playerPanel.RemoveChildren();
|
playerPanel.RemoveChildren();
|
||||||
|
|
||||||
var teams = world.Players.Where(p => !p.NonCombatant && p.Playable)
|
var teams = world.Players.Where(p => !p.NonCombatant && p.Playable)
|
||||||
@@ -114,22 +143,81 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
.GroupBy(p => (world.LobbyInfo.ClientWithIndex(p.Player.ClientIndex) ?? new Session.Client()).Team)
|
.GroupBy(p => (world.LobbyInfo.ClientWithIndex(p.Player.ClientIndex) ?? new Session.Client()).Team)
|
||||||
.OrderByDescending(g => g.Sum(gg => gg.PlayerStatistics?.Experience ?? 0));
|
.OrderByDescending(g => g.Sum(gg => gg.PlayerStatistics?.Experience ?? 0));
|
||||||
|
|
||||||
void KickAction(Session.Client client)
|
void KickAction(Session.Client client, Func<bool> isVoteKick)
|
||||||
{
|
{
|
||||||
hideMenu(true);
|
hideMenu(true);
|
||||||
ConfirmationDialogs.ButtonPrompt(modData,
|
if (isVoteKick())
|
||||||
title: KickTitle,
|
{
|
||||||
titleArguments: Translation.Arguments("player", client.Name),
|
var botsCount = 0;
|
||||||
text: KickPrompt,
|
if (client.IsAdmin)
|
||||||
onConfirm: () =>
|
botsCount = world.Players.Count(p => p.IsBot && p.WinState == WinState.Undefined);
|
||||||
|
|
||||||
|
if (UnitOrders.KickVoteTarget == null)
|
||||||
{
|
{
|
||||||
orderManager.IssueOrder(Order.Command($"kick {client.Index} {false}"));
|
ConfirmationDialogs.ButtonPrompt(modData,
|
||||||
hideMenu(false);
|
title: VoteKickTitle,
|
||||||
},
|
titleArguments: Translation.Arguments("player", client.Name),
|
||||||
onCancel: () => hideMenu(false),
|
text: botsCount > 0 ? VoteKickPromptBreakBots : VoteKickPrompt,
|
||||||
confirmText: KickAccept);
|
textArguments: Translation.Arguments("bots", botsCount),
|
||||||
|
onConfirm: () =>
|
||||||
|
{
|
||||||
|
orderManager.IssueOrder(Order.Command($"vote_kick {client.Index} {true}"));
|
||||||
|
hideMenu(false);
|
||||||
|
closeMenu();
|
||||||
|
},
|
||||||
|
confirmText: VoteKickVoteStart,
|
||||||
|
onCancel: () => hideMenu(false));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfirmationDialogs.ButtonPrompt(modData,
|
||||||
|
title: VoteKickTitle,
|
||||||
|
titleArguments: Translation.Arguments("player", client.Name),
|
||||||
|
text: botsCount > 0 ? VoteKickPromptBreakBots : VoteKickPrompt,
|
||||||
|
textArguments: Translation.Arguments("bots", botsCount),
|
||||||
|
onConfirm: () =>
|
||||||
|
{
|
||||||
|
orderManager.IssueOrder(Order.Command($"vote_kick {client.Index} {true}"));
|
||||||
|
hideMenu(false);
|
||||||
|
closeMenu();
|
||||||
|
},
|
||||||
|
confirmText: VoteKickVoteFor,
|
||||||
|
onOther: () =>
|
||||||
|
{
|
||||||
|
Ui.CloseWindow();
|
||||||
|
orderManager.IssueOrder(Order.Command($"vote_kick {client.Index} {false}"));
|
||||||
|
hideMenu(false);
|
||||||
|
closeMenu();
|
||||||
|
},
|
||||||
|
otherText: VoteKickVoteAgainst,
|
||||||
|
onCancel: () => hideMenu(false),
|
||||||
|
cancelText: VoteKickVoteCancel);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ConfirmationDialogs.ButtonPrompt(modData,
|
||||||
|
title: KickTitle,
|
||||||
|
titleArguments: Translation.Arguments("player", client.Name),
|
||||||
|
text: KickPrompt,
|
||||||
|
onConfirm: () =>
|
||||||
|
{
|
||||||
|
orderManager.IssueOrder(Order.Command($"kick {client.Index} {false}"));
|
||||||
|
hideMenu(false);
|
||||||
|
},
|
||||||
|
confirmText: KickAccept,
|
||||||
|
onCancel: () => hideMenu(false));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var localClient = orderManager.LocalClient;
|
||||||
|
var localPlayer = localClient == null ? null : world.Players.FirstOrDefault(player => player.ClientIndex == localClient.Index);
|
||||||
|
bool LocalPlayerCanKick() => localClient != null
|
||||||
|
&& (Game.IsHost || ((!orderManager.LocalClient.IsObserver) && localPlayer.WinState == WinState.Undefined));
|
||||||
|
bool CanClientBeKicked(Session.Client client, Func<bool> isVoteKick) =>
|
||||||
|
client.Index != localClient.Index && client.State != Session.ClientState.Disconnected
|
||||||
|
&& (!client.IsAdmin || orderManager.LobbyInfo.GlobalSettings.Dedicated)
|
||||||
|
&& (!isVoteKick() || UnitOrders.KickVoteTarget == null || UnitOrders.KickVoteTarget == client.Index);
|
||||||
|
|
||||||
foreach (var t in teams)
|
foreach (var t in teams)
|
||||||
{
|
{
|
||||||
if (teams.Count() > 1)
|
if (teams.Count() > 1)
|
||||||
@@ -182,8 +270,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
muteCheckbox.GetTooltipText = () => muteCheckbox.IsChecked() ? unmuteTooltip : muteTooltip;
|
muteCheckbox.GetTooltipText = () => muteCheckbox.IsChecked() ? unmuteTooltip : muteTooltip;
|
||||||
|
|
||||||
var kickButton = item.Get<ButtonWidget>("KICK");
|
var kickButton = item.Get<ButtonWidget>("KICK");
|
||||||
kickButton.IsVisible = () => Game.IsHost && client.Index != orderManager.LocalClient?.Index && client.State != Session.ClientState.Disconnected && pp.WinState != WinState.Undefined && !pp.IsBot;
|
bool IsVoteKick() => !Game.IsHost || pp.WinState == WinState.Undefined;
|
||||||
kickButton.OnClick = () => KickAction(client);
|
kickButton.IsVisible = () => !pp.IsBot && LocalPlayerCanKick() && CanClientBeKicked(client, IsVoteKick);
|
||||||
|
kickButton.OnClick = () => KickAction(client, IsVoteKick);
|
||||||
|
kickButton.GetTooltipText = () => IsVoteKick() ? voteKickTooltip : kickTooltip;
|
||||||
|
|
||||||
playerPanel.AddChild(item);
|
playerPanel.AddChild(item);
|
||||||
}
|
}
|
||||||
@@ -217,8 +307,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
};
|
};
|
||||||
|
|
||||||
var kickButton = item.Get<ButtonWidget>("KICK");
|
var kickButton = item.Get<ButtonWidget>("KICK");
|
||||||
kickButton.IsVisible = () => Game.IsHost && client.Index != orderManager.LocalClient?.Index && client.State != Session.ClientState.Disconnected;
|
bool IsVoteKick() => !Game.IsHost;
|
||||||
kickButton.OnClick = () => KickAction(client);
|
kickButton.IsVisible = () => LocalPlayerCanKick() && CanClientBeKicked(client, IsVoteKick);
|
||||||
|
kickButton.OnClick = () => KickAction(client, IsVoteKick);
|
||||||
|
kickButton.GetTooltipText = () => IsVoteKick() ? voteKickTooltip : kickTooltip;
|
||||||
|
|
||||||
var muteCheckbox = item.Get<CheckboxWidget>("MUTE");
|
var muteCheckbox = item.Get<CheckboxWidget>("MUTE");
|
||||||
muteCheckbox.IsChecked = () => TextNotificationsManager.MutedPlayers[client.Index];
|
muteCheckbox.IsChecked = () => TextNotificationsManager.MutedPlayers[client.Index];
|
||||||
|
|||||||
@@ -200,7 +200,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
var gameInfoPanel = Game.LoadWidget(world, "GAME_INFO_PANEL", panelRoot, new WidgetArgs()
|
var gameInfoPanel = Game.LoadWidget(world, "GAME_INFO_PANEL", panelRoot, new WidgetArgs()
|
||||||
{
|
{
|
||||||
{ "initialPanel", initialPanel },
|
{ "initialPanel", initialPanel },
|
||||||
{ "hideMenu", requestHideMenu }
|
{ "hideMenu", requestHideMenu },
|
||||||
|
{ "closeMenu", CloseMenu },
|
||||||
});
|
});
|
||||||
|
|
||||||
gameInfoPanel.IsVisible = () => !hideMenu;
|
gameInfoPanel.IsVisible = () => !hideMenu;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OpenRA.Graphics;
|
using OpenRA.Graphics;
|
||||||
using OpenRA.Mods.Common.Traits;
|
using OpenRA.Mods.Common.Traits;
|
||||||
@@ -510,8 +511,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
{ "PARENT_BOTTOM", parentBounds.Height }
|
{ "PARENT_BOTTOM", parentBounds.Height }
|
||||||
};
|
};
|
||||||
|
|
||||||
var width = w.Width.Evaluate(substitutions);
|
var readOnlySubstitutions = new ReadOnlyDictionary<string, int>(substitutions);
|
||||||
var height = w.Height.Evaluate(substitutions);
|
var width = w.Width != null ? w.Width.Evaluate(readOnlySubstitutions) : 0;
|
||||||
|
var height = w.Height != null ? w.Height.Evaluate(readOnlySubstitutions) : 0;
|
||||||
|
|
||||||
substitutions.Add("WIDTH", width);
|
substitutions.Add("WIDTH", width);
|
||||||
substitutions.Add("HEIGHT", height);
|
substitutions.Add("HEIGHT", height);
|
||||||
@@ -520,8 +522,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
w.Bounds = new Rectangle(w.Bounds.X, w.Bounds.Y, width, w.Bounds.Height);
|
w.Bounds = new Rectangle(w.Bounds.X, w.Bounds.Y, width, w.Bounds.Height);
|
||||||
else
|
else
|
||||||
w.Bounds = new Rectangle(
|
w.Bounds = new Rectangle(
|
||||||
w.X.Evaluate(substitutions),
|
w.X != null ? w.X.Evaluate(readOnlySubstitutions) : 0,
|
||||||
w.Y.Evaluate(substitutions),
|
w.Y != null ? w.Y.Evaluate(readOnlySubstitutions) : 0,
|
||||||
width,
|
width,
|
||||||
height);
|
height);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
Metadata:
|
Metadata:
|
||||||
Title: All mods
|
Title: All mods
|
||||||
Version: {DEV_VERSION}
|
Version: release-20231010
|
||||||
Hidden: true
|
Hidden: true
|
||||||
|
|
||||||
Packages:
|
Packages:
|
||||||
|
|||||||
@@ -139,7 +139,6 @@ Container@SKIRMISH_STATS:
|
|||||||
Height: 25
|
Height: 25
|
||||||
Background: checkbox-toggle
|
Background: checkbox-toggle
|
||||||
TooltipContainer: TOOLTIP_CONTAINER
|
TooltipContainer: TOOLTIP_CONTAINER
|
||||||
TooltipText: Kick this player
|
|
||||||
Children:
|
Children:
|
||||||
Image:
|
Image:
|
||||||
ImageCollection: lobby-bits
|
ImageCollection: lobby-bits
|
||||||
@@ -182,7 +181,6 @@ Container@SKIRMISH_STATS:
|
|||||||
Height: 25
|
Height: 25
|
||||||
Background: checkbox-toggle
|
Background: checkbox-toggle
|
||||||
TooltipContainer: TOOLTIP_CONTAINER
|
TooltipContainer: TOOLTIP_CONTAINER
|
||||||
TooltipText: Kick this player
|
|
||||||
Children:
|
Children:
|
||||||
Image:
|
Image:
|
||||||
ImageCollection: lobby-bits
|
ImageCollection: lobby-bits
|
||||||
|
|||||||
@@ -1093,14 +1093,14 @@ Container@OBSERVER_WIDGETS:
|
|||||||
Align: Right
|
Align: Right
|
||||||
Shadow: True
|
Shadow: True
|
||||||
Label@ARMY_VALUE:
|
Label@ARMY_VALUE:
|
||||||
X: 605
|
X: 610
|
||||||
Y: 0
|
Y: 0
|
||||||
Width: 85
|
Width: 90
|
||||||
Height: PARENT_BOTTOM
|
Height: PARENT_BOTTOM
|
||||||
Align: Right
|
Align: Right
|
||||||
Shadow: True
|
Shadow: True
|
||||||
Label@VISION:
|
Label@VISION:
|
||||||
X: 690
|
X: 700
|
||||||
Y: 0
|
Y: 0
|
||||||
Width: 60
|
Width: 60
|
||||||
Height: PARENT_BOTTOM
|
Height: PARENT_BOTTOM
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
Metadata:
|
Metadata:
|
||||||
Title: Tiberian Dawn
|
Title: Tiberian Dawn
|
||||||
Version: {DEV_VERSION}
|
Version: release-20231010
|
||||||
Website: https://www.openra.net
|
Website: https://www.openra.net
|
||||||
WebIcon32: https://www.openra.net/images/icons/cnc_32x32.png
|
WebIcon32: https://www.openra.net/images/icons/cnc_32x32.png
|
||||||
WindowTitle: OpenRA - Tiberian Dawn
|
WindowTitle: OpenRA - Tiberian Dawn
|
||||||
@@ -37,7 +37,7 @@ Packages:
|
|||||||
|
|
||||||
MapFolders:
|
MapFolders:
|
||||||
cnc|maps: System
|
cnc|maps: System
|
||||||
~^SupportDir|maps/cnc/{DEV_VERSION}: User
|
~^SupportDir|maps/cnc/release-20231010: User
|
||||||
|
|
||||||
Rules:
|
Rules:
|
||||||
cnc|rules/misc.yaml
|
cnc|rules/misc.yaml
|
||||||
|
|||||||
@@ -136,7 +136,6 @@ Container@SKIRMISH_STATS:
|
|||||||
Height: 25
|
Height: 25
|
||||||
Background: checkbox-toggle
|
Background: checkbox-toggle
|
||||||
TooltipContainer: TOOLTIP_CONTAINER
|
TooltipContainer: TOOLTIP_CONTAINER
|
||||||
TooltipText: Kick this player
|
|
||||||
Children:
|
Children:
|
||||||
Image:
|
Image:
|
||||||
ImageCollection: lobby-bits
|
ImageCollection: lobby-bits
|
||||||
@@ -179,7 +178,6 @@ Container@SKIRMISH_STATS:
|
|||||||
Height: 25
|
Height: 25
|
||||||
Background: checkbox-toggle
|
Background: checkbox-toggle
|
||||||
TooltipContainer: TOOLTIP_CONTAINER
|
TooltipContainer: TOOLTIP_CONTAINER
|
||||||
TooltipText: Kick this player
|
|
||||||
Children:
|
Children:
|
||||||
Image:
|
Image:
|
||||||
ImageCollection: lobby-bits
|
ImageCollection: lobby-bits
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ notification-admin-change-configuration = Only the host can change the configura
|
|||||||
notification-changed-map = { $player } changed the map to { $map }
|
notification-changed-map = { $player } changed the map to { $map }
|
||||||
notification-option-changed = { $player } changed { $name } to { $value }.
|
notification-option-changed = { $player } changed { $name } to { $value }.
|
||||||
notification-you-were-kicked = You have been kicked from the server.
|
notification-you-were-kicked = You have been kicked from the server.
|
||||||
notification-kicked = { $admin } kicked { $player } from the server.
|
notification-admin-kicked = { $admin } kicked { $player } from the server.
|
||||||
|
notification-kicked = { $player } was kicked from the server.
|
||||||
notification-temp-ban = { $admin } temporarily banned { $player } from the server.
|
notification-temp-ban = { $admin } temporarily banned { $player } from the server.
|
||||||
notification-admin-transfer-admin = Only admins can transfer admin to another player.
|
notification-admin-transfer-admin = Only admins can transfer admin to another player.
|
||||||
notification-admin-move-spectators = Only the host can move players to spectators.
|
notification-admin-move-spectators = Only the host can move players to spectators.
|
||||||
@@ -95,6 +96,14 @@ notification-chat-temp-disabled =
|
|||||||
*[other] Chat is disabled. Please try again in { $remaining } seconds.
|
*[other] Chat is disabled. Please try again in { $remaining } seconds.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
## VoteKickTracker
|
||||||
|
notification-unable-to-start-a-vote = Unable to start a vote.
|
||||||
|
notification-insufficient-votes-to-kick = Insufficient votes to kick player { $kickee }.
|
||||||
|
notification-kick-already-voted = You have already voted.
|
||||||
|
notification-vote-kick-started = Player { $kicker } has started a vote to kick player { $kickee }.
|
||||||
|
notification-vote-kick-in-progress = { $percentage }% of players have voted to kick player { $kickee }.
|
||||||
|
notification-vote-kick-ended = Vote to kick player { $kickee } has failed.
|
||||||
|
|
||||||
## ActorEditLogic
|
## ActorEditLogic
|
||||||
label-duplicate-actor-id = Duplicate Actor ID
|
label-duplicate-actor-id = Duplicate Actor ID
|
||||||
label-actor-id = Enter an Actor ID
|
label-actor-id = Enter an Actor ID
|
||||||
@@ -149,12 +158,29 @@ label-mission-failed = Failed
|
|||||||
label-client-state-disconnected = Gone
|
label-client-state-disconnected = Gone
|
||||||
label-mute-player = Mute this player
|
label-mute-player = Mute this player
|
||||||
label-unmute-player = Unmute this player
|
label-unmute-player = Unmute this player
|
||||||
|
button-kick-player = Kick this player
|
||||||
|
button-vote-kick-player = Vote to kick this player
|
||||||
|
|
||||||
dialog-kick =
|
dialog-kick =
|
||||||
.title = Kick { $player }?
|
.title = Kick { $player }?
|
||||||
.prompt = They will not be able to rejoin this game.
|
.prompt = This player will not be able to rejoin the game.
|
||||||
.confirm = Kick
|
.confirm = Kick
|
||||||
|
|
||||||
|
dialog-vote-kick =
|
||||||
|
.title = Vote to kick { $player }?
|
||||||
|
.prompt = This player will not be able to rejoin the game.
|
||||||
|
.prompt-break-bots =
|
||||||
|
{ $bots ->
|
||||||
|
[one] Kicking the game admin will also kick 1 bot.
|
||||||
|
*[other] Kicking the game admin will also kick { $bots } bots.
|
||||||
|
}
|
||||||
|
.vote-start = Start Vote
|
||||||
|
.vote-for = Vote For
|
||||||
|
.vote-against = Vote Against
|
||||||
|
.vote-cancel = Abstain
|
||||||
|
|
||||||
|
notification-vote-kick-disabled = Vote kick is disabled on this server.
|
||||||
|
|
||||||
## GameTimerLogic
|
## GameTimerLogic
|
||||||
label-paused = Paused
|
label-paused = Paused
|
||||||
label-max-speed = Max Speed
|
label-max-speed = Max Speed
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
Metadata:
|
Metadata:
|
||||||
Title: Dune 2000
|
Title: Dune 2000
|
||||||
Version: {DEV_VERSION}
|
Version: release-20231010
|
||||||
Website: https://www.openra.net
|
Website: https://www.openra.net
|
||||||
WebIcon32: https://www.openra.net/images/icons/d2k_32x32.png
|
WebIcon32: https://www.openra.net/images/icons/d2k_32x32.png
|
||||||
WindowTitle: OpenRA - Dune 2000
|
WindowTitle: OpenRA - Dune 2000
|
||||||
@@ -25,7 +25,7 @@ Packages:
|
|||||||
|
|
||||||
MapFolders:
|
MapFolders:
|
||||||
d2k|maps: System
|
d2k|maps: System
|
||||||
~^SupportDir|maps/d2k/{DEV_VERSION}: User
|
~^SupportDir|maps/d2k/release-20231010: User
|
||||||
|
|
||||||
Rules:
|
Rules:
|
||||||
d2k|rules/misc.yaml
|
d2k|rules/misc.yaml
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
Metadata:
|
Metadata:
|
||||||
Title: Mod Content Manager
|
Title: Mod Content Manager
|
||||||
Version: {DEV_VERSION}
|
Version: release-20231010
|
||||||
Hidden: true
|
Hidden: true
|
||||||
|
|
||||||
Packages:
|
Packages:
|
||||||
|
|||||||
@@ -234,7 +234,6 @@ cncr-origin: C&C Remastered Collection (Origin version, English)
|
|||||||
RegistryValue: Install Dir
|
RegistryValue: Install Dir
|
||||||
IDFiles:
|
IDFiles:
|
||||||
Data/CNCDATA/RED_ALERT/CD1/REDALERT.MIX: 0e58f4b54f44f6cd29fecf8cf379d33cf2d4caef
|
Data/CNCDATA/RED_ALERT/CD1/REDALERT.MIX: 0e58f4b54f44f6cd29fecf8cf379d33cf2d4caef
|
||||||
Length: 4096
|
|
||||||
# The Remastered Collection doesn't include the RA Soviet CD unfortunately, so we can't install Soviet campaign briefings.
|
# The Remastered Collection doesn't include the RA Soviet CD unfortunately, so we can't install Soviet campaign briefings.
|
||||||
Install:
|
Install:
|
||||||
# Base game files:
|
# Base game files:
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ cncr-steam: C&C Remastered Collection (Steam version, English)
|
|||||||
AppId: 1213210
|
AppId: 1213210
|
||||||
IDFiles:
|
IDFiles:
|
||||||
Data/CNCDATA/RED_ALERT/CD1/REDALERT.MIX: 0e58f4b54f44f6cd29fecf8cf379d33cf2d4caef
|
Data/CNCDATA/RED_ALERT/CD1/REDALERT.MIX: 0e58f4b54f44f6cd29fecf8cf379d33cf2d4caef
|
||||||
Length: 4096
|
|
||||||
# The Remastered Collection doesn't include the RA Soviet CD unfortunately, so we can't install Soviet campaign briefings.
|
# The Remastered Collection doesn't include the RA Soviet CD unfortunately, so we can't install Soviet campaign briefings.
|
||||||
Install:
|
Install:
|
||||||
# Base game files:
|
# Base game files:
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ ARTY:
|
|||||||
Buildable:
|
Buildable:
|
||||||
Prerequisites: ~disabled
|
Prerequisites: ~disabled
|
||||||
|
|
||||||
|
FTRK:
|
||||||
|
Buildable:
|
||||||
|
Prerequisites: ~disabled
|
||||||
|
|
||||||
MCV:
|
MCV:
|
||||||
Buildable:
|
Buildable:
|
||||||
Prerequisites: ~disabled
|
Prerequisites: ~disabled
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ ARTY:
|
|||||||
Buildable:
|
Buildable:
|
||||||
Prerequisites: ~disabled
|
Prerequisites: ~disabled
|
||||||
|
|
||||||
|
FTRK:
|
||||||
|
Buildable:
|
||||||
|
Prerequisites: ~disabled
|
||||||
|
|
||||||
MCV:
|
MCV:
|
||||||
Buildable:
|
Buildable:
|
||||||
Prerequisites: ~disabled
|
Prerequisites: ~disabled
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ powerproxy.paratroopers:
|
|||||||
ParatroopersPower:
|
ParatroopersPower:
|
||||||
DropItems: E1,E1,E1,E2,E2
|
DropItems: E1,E1,E1,E2,E2
|
||||||
|
|
||||||
|
FTRK:
|
||||||
|
Buildable:
|
||||||
|
Prerequisites: ~disabled
|
||||||
|
|
||||||
MCV:
|
MCV:
|
||||||
Buildable:
|
Buildable:
|
||||||
Prerequisites: ~disabled
|
Prerequisites: ~disabled
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ powerproxy.paratroopers:
|
|||||||
ParatroopersPower:
|
ParatroopersPower:
|
||||||
DropItems: E1,E1,E1,E2,E2
|
DropItems: E1,E1,E1,E2,E2
|
||||||
|
|
||||||
|
FTRK:
|
||||||
|
Buildable:
|
||||||
|
Prerequisites: ~disabled
|
||||||
|
|
||||||
MCV:
|
MCV:
|
||||||
Buildable:
|
Buildable:
|
||||||
Prerequisites: ~disabled
|
Prerequisites: ~disabled
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ MSLO:
|
|||||||
Capturable:
|
Capturable:
|
||||||
Types: ~disabled
|
Types: ~disabled
|
||||||
|
|
||||||
|
FTRK:
|
||||||
|
Buildable:
|
||||||
|
Prerequisites: ~disabled
|
||||||
|
|
||||||
MCV:
|
MCV:
|
||||||
Buildable:
|
Buildable:
|
||||||
Prerequisites: ~disabled
|
Prerequisites: ~disabled
|
||||||
|
|||||||
@@ -75,6 +75,10 @@ MSLO:
|
|||||||
Buildable:
|
Buildable:
|
||||||
Prerequisites: ~disabled
|
Prerequisites: ~disabled
|
||||||
|
|
||||||
|
FTRK:
|
||||||
|
Buildable:
|
||||||
|
Prerequisites: ~disabled
|
||||||
|
|
||||||
MCV:
|
MCV:
|
||||||
Buildable:
|
Buildable:
|
||||||
Prerequisites: ~disabled
|
Prerequisites: ~disabled
|
||||||
|
|||||||
@@ -282,6 +282,10 @@ STEK:
|
|||||||
Buildable:
|
Buildable:
|
||||||
Prerequisites: ~disabled
|
Prerequisites: ~disabled
|
||||||
|
|
||||||
|
FTRK:
|
||||||
|
Buildable:
|
||||||
|
Prerequisites: ~disabled
|
||||||
|
|
||||||
MCV:
|
MCV:
|
||||||
Buildable:
|
Buildable:
|
||||||
Prerequisites: ~disabled
|
Prerequisites: ~disabled
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ GroundWavesDelays =
|
|||||||
normal = 3,
|
normal = 3,
|
||||||
hard = 2
|
hard = 2
|
||||||
}
|
}
|
||||||
|
GroundWavesDelay = GroundWavesDelays[Difficulty]
|
||||||
|
|
||||||
GroundWaves = function()
|
GroundWaves = function()
|
||||||
if not ForwardCommand.IsDead then
|
if not ForwardCommand.IsDead then
|
||||||
@@ -107,7 +108,7 @@ GroundWaves = function()
|
|||||||
local units = Reinforcements.Reinforce(BadGuy, Utils.Random(GroundAttackUnits), path)
|
local units = Reinforcements.Reinforce(BadGuy, Utils.Random(GroundAttackUnits), path)
|
||||||
Utils.Do(units, IdleHunt)
|
Utils.Do(units, IdleHunt)
|
||||||
|
|
||||||
Trigger.AfterDelay(DateTime.Minutes(GroundWavesDelays), GroundWaves)
|
Trigger.AfterDelay(DateTime.Minutes(GroundWavesDelay), GroundWaves)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -132,8 +133,6 @@ ProduceAircraft = function()
|
|||||||
end
|
end
|
||||||
|
|
||||||
ActivateAI = function()
|
ActivateAI = function()
|
||||||
GroundWavesDelays = GroundWavesDelays[Difficulty]
|
|
||||||
|
|
||||||
local buildings = Utils.Where(Map.ActorsInWorld, function(self) return self.Owner == USSR and self.HasProperty("StartBuildingRepairs") end)
|
local buildings = Utils.Where(Map.ActorsInWorld, function(self) return self.Owner == USSR and self.HasProperty("StartBuildingRepairs") end)
|
||||||
Utils.Do(buildings, function(actor)
|
Utils.Do(buildings, function(actor)
|
||||||
Trigger.OnDamaged(actor, function(building)
|
Trigger.OnDamaged(actor, function(building)
|
||||||
@@ -146,6 +145,6 @@ ActivateAI = function()
|
|||||||
ProduceBadGuyInfantry()
|
ProduceBadGuyInfantry()
|
||||||
ProduceUSSRInfantry()
|
ProduceUSSRInfantry()
|
||||||
ProduceVehicles()
|
ProduceVehicles()
|
||||||
Trigger.AfterDelay(DateTime.Minutes(GroundWavesDelays), GroundWaves)
|
Trigger.AfterDelay(DateTime.Minutes(GroundWavesDelay), GroundWaves)
|
||||||
Trigger.AfterDelay(DateTime.Minutes(5), ProduceAircraft)
|
Trigger.AfterDelay(DateTime.Minutes(5), ProduceAircraft)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -66,14 +66,21 @@ SetupTriggers = function()
|
|||||||
Trigger.OnAllKilledOrCaptured(SarinPlants, function()
|
Trigger.OnAllKilledOrCaptured(SarinPlants, function()
|
||||||
Greece.MarkCompletedObjective(CaptureSarin)
|
Greece.MarkCompletedObjective(CaptureSarin)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
Trigger.OnEnteredProximityTrigger(AlliesMove.CenterPosition, WDist.FromCells(3), function(actor, id)
|
||||||
|
if actor.Owner == Greece then
|
||||||
|
Trigger.RemoveProximityTrigger(id)
|
||||||
|
Media.PlaySpeechNotification(Greece, "SignalFlareSouth")
|
||||||
|
end
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
MCVArrived = false
|
MCVArrived = false
|
||||||
MCVArrivedTick = false
|
MCVArrivedTick = false
|
||||||
PowerDownTeslas = function()
|
PowerDownTeslas = function()
|
||||||
if not MCVArrived then
|
if not MCVArrived then
|
||||||
CaptureSarin = Greece.AddObjective("capture-sarin-plants-intact")
|
CaptureSarin = AddPrimaryObjective(Greece, "capture-sarin-plants-intact")
|
||||||
KillBase = Greece.AddObjective("destroy-enemy-compound")
|
KillBase = AddPrimaryObjective(Greece, "destroy-enemy-compound")
|
||||||
Greece.MarkCompletedObjective(TakeOutPower)
|
Greece.MarkCompletedObjective(TakeOutPower)
|
||||||
Media.PlaySpeechNotification(Greece, "ReinforcementsArrived")
|
Media.PlaySpeechNotification(Greece, "ReinforcementsArrived")
|
||||||
Reinforcements.Reinforce(Greece, MCVReinforcements[Difficulty], { AlliesSpawn.Location, AlliesMove.Location })
|
Reinforcements.Reinforce(Greece, MCVReinforcements[Difficulty], { AlliesSpawn.Location, AlliesMove.Location })
|
||||||
@@ -85,6 +92,7 @@ PowerDownTeslas = function()
|
|||||||
|
|
||||||
Trigger.AfterDelay(DateTime.Seconds(1), function()
|
Trigger.AfterDelay(DateTime.Seconds(1), function()
|
||||||
MCVArrivedTick = true
|
MCVArrivedTick = true
|
||||||
|
PrepareFinishingHunt(USSR)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
Trigger.AfterDelay(DateTime.Seconds(60), function()
|
Trigger.AfterDelay(DateTime.Seconds(60), function()
|
||||||
@@ -99,6 +107,25 @@ PowerDownTeslas = function()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
PrepareFinishingHunt = function(player)
|
||||||
|
local buildings = GetBaseBuildings(player)
|
||||||
|
|
||||||
|
Trigger.OnAllKilledOrCaptured(buildings, function()
|
||||||
|
Utils.Do(player.GetGroundAttackers(), function(actor)
|
||||||
|
actor.Stop()
|
||||||
|
IdleHunt(actor)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
GetBaseBuildings = function(player)
|
||||||
|
-- Excludes the unrepairable sarin plants, which is desired anyway.
|
||||||
|
local buildings = Utils.Where(player.GetActors(), function(actor)
|
||||||
|
return actor.HasProperty("StartBuildingRepairs")
|
||||||
|
end)
|
||||||
|
return buildings
|
||||||
|
end
|
||||||
|
|
||||||
Tick = function()
|
Tick = function()
|
||||||
USSR.Cash = 10000
|
USSR.Cash = 10000
|
||||||
BadGuy.Cash = 10000
|
BadGuy.Cash = 10000
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ Player:
|
|||||||
PlayerResources:
|
PlayerResources:
|
||||||
DefaultCash: 7500
|
DefaultCash: 7500
|
||||||
|
|
||||||
|
HARV:
|
||||||
|
-MustBeDestroyed:
|
||||||
|
|
||||||
APC:
|
APC:
|
||||||
Buildable:
|
Buildable:
|
||||||
Prerequisites: ~vehicles.allies
|
Prerequisites: ~vehicles.allies
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ MSLO:
|
|||||||
Buildable:
|
Buildable:
|
||||||
Prerequisites: ~disabled
|
Prerequisites: ~disabled
|
||||||
|
|
||||||
|
FTRK:
|
||||||
|
Buildable:
|
||||||
|
Prerequisites: ~disabled
|
||||||
|
|
||||||
MCV:
|
MCV:
|
||||||
Buildable:
|
Buildable:
|
||||||
Prerequisites: ~disabled
|
Prerequisites: ~disabled
|
||||||
|
|||||||
BIN
mods/ra/maps/soviet-13b/map.bin
Normal file
BIN
mods/ra/maps/soviet-13b/map.bin
Normal file
Binary file not shown.
BIN
mods/ra/maps/soviet-13b/map.png
Normal file
BIN
mods/ra/maps/soviet-13b/map.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
957
mods/ra/maps/soviet-13b/map.yaml
Normal file
957
mods/ra/maps/soviet-13b/map.yaml
Normal file
@@ -0,0 +1,957 @@
|
|||||||
|
MapFormat: 12
|
||||||
|
|
||||||
|
RequiresMod: ra
|
||||||
|
|
||||||
|
Title: 13b: Capture the Chronosphere
|
||||||
|
|
||||||
|
Author: Westwood Studios
|
||||||
|
|
||||||
|
Tileset: SNOW
|
||||||
|
|
||||||
|
MapSize: 128,128
|
||||||
|
|
||||||
|
Bounds: 18,14,92,93
|
||||||
|
|
||||||
|
Visibility: MissionSelector
|
||||||
|
|
||||||
|
Categories: Campaign
|
||||||
|
|
||||||
|
LockPreview: True
|
||||||
|
|
||||||
|
Players:
|
||||||
|
PlayerReference@Neutral:
|
||||||
|
Name: Neutral
|
||||||
|
OwnsWorld: True
|
||||||
|
NonCombatant: True
|
||||||
|
Faction: england
|
||||||
|
PlayerReference@Greece:
|
||||||
|
Name: Greece
|
||||||
|
Bot: campaign
|
||||||
|
Faction: allies
|
||||||
|
Color: E2E6F6
|
||||||
|
Allies: GoodGuy
|
||||||
|
Enemies: USSR
|
||||||
|
PlayerReference@GoodGuy:
|
||||||
|
Name: GoodGuy
|
||||||
|
Faction: allies
|
||||||
|
Color: E2E6F6
|
||||||
|
Allies: Greece
|
||||||
|
Enemies: USSR
|
||||||
|
PlayerReference@USSR:
|
||||||
|
Name: USSR
|
||||||
|
AllowBots: False
|
||||||
|
Playable: True
|
||||||
|
Required: True
|
||||||
|
LockFaction: True
|
||||||
|
Faction: soviet
|
||||||
|
LockColor: True
|
||||||
|
Color: FE1100
|
||||||
|
LockSpawn: True
|
||||||
|
LockTeam: True
|
||||||
|
Enemies: GoodGuy, Greece
|
||||||
|
|
||||||
|
Actors:
|
||||||
|
Actor0: cycl
|
||||||
|
Location: 42,73
|
||||||
|
Owner: Greece
|
||||||
|
Actor1: cycl
|
||||||
|
Location: 43,73
|
||||||
|
Owner: Greece
|
||||||
|
Actor2: cycl
|
||||||
|
Location: 44,73
|
||||||
|
Owner: Greece
|
||||||
|
Actor3: cycl
|
||||||
|
Location: 45,73
|
||||||
|
Owner: Greece
|
||||||
|
Actor4: cycl
|
||||||
|
Location: 46,73
|
||||||
|
Owner: Greece
|
||||||
|
Actor5: cycl
|
||||||
|
Location: 50,73
|
||||||
|
Owner: Greece
|
||||||
|
Actor6: cycl
|
||||||
|
Location: 51,73
|
||||||
|
Owner: Greece
|
||||||
|
Actor7: cycl
|
||||||
|
Location: 52,73
|
||||||
|
Owner: Greece
|
||||||
|
Actor8: cycl
|
||||||
|
Location: 53,73
|
||||||
|
Owner: Greece
|
||||||
|
Actor9: cycl
|
||||||
|
Location: 54,73
|
||||||
|
Owner: Greece
|
||||||
|
Actor10: cycl
|
||||||
|
Location: 55,73
|
||||||
|
Owner: Greece
|
||||||
|
Actor11: cycl
|
||||||
|
Location: 26,74
|
||||||
|
Owner: Greece
|
||||||
|
Actor12: cycl
|
||||||
|
Location: 27,74
|
||||||
|
Owner: Greece
|
||||||
|
Actor13: cycl
|
||||||
|
Location: 28,74
|
||||||
|
Owner: Greece
|
||||||
|
Actor14: cycl
|
||||||
|
Location: 29,74
|
||||||
|
Owner: Greece
|
||||||
|
Actor15: cycl
|
||||||
|
Location: 30,74
|
||||||
|
Owner: Greece
|
||||||
|
Actor16: cycl
|
||||||
|
Location: 35,74
|
||||||
|
Owner: Greece
|
||||||
|
Actor17: cycl
|
||||||
|
Location: 36,74
|
||||||
|
Owner: Greece
|
||||||
|
Actor18: cycl
|
||||||
|
Location: 41,74
|
||||||
|
Owner: Greece
|
||||||
|
Actor19: cycl
|
||||||
|
Location: 42,74
|
||||||
|
Owner: Greece
|
||||||
|
Actor20: cycl
|
||||||
|
Location: 55,74
|
||||||
|
Owner: Greece
|
||||||
|
Actor21: cycl
|
||||||
|
Location: 56,74
|
||||||
|
Owner: Greece
|
||||||
|
Actor22: cycl
|
||||||
|
Location: 25,75
|
||||||
|
Owner: Greece
|
||||||
|
Actor23: cycl
|
||||||
|
Location: 26,75
|
||||||
|
Owner: Greece
|
||||||
|
Actor24: cycl
|
||||||
|
Location: 36,75
|
||||||
|
Owner: Greece
|
||||||
|
Actor25: cycl
|
||||||
|
Location: 37,75
|
||||||
|
Owner: Greece
|
||||||
|
Actor26: cycl
|
||||||
|
Location: 38,75
|
||||||
|
Owner: Greece
|
||||||
|
Actor27: cycl
|
||||||
|
Location: 39,75
|
||||||
|
Owner: Greece
|
||||||
|
Actor28: cycl
|
||||||
|
Location: 40,75
|
||||||
|
Owner: Greece
|
||||||
|
Actor29: cycl
|
||||||
|
Location: 41,75
|
||||||
|
Owner: Greece
|
||||||
|
Actor30: cycl
|
||||||
|
Location: 56,75
|
||||||
|
Owner: Greece
|
||||||
|
Actor31: cycl
|
||||||
|
Location: 57,75
|
||||||
|
Owner: Greece
|
||||||
|
Actor32: cycl
|
||||||
|
Location: 58,75
|
||||||
|
Owner: Greece
|
||||||
|
Actor33: cycl
|
||||||
|
Location: 59,75
|
||||||
|
Owner: Greece
|
||||||
|
Actor34: cycl
|
||||||
|
Location: 25,76
|
||||||
|
Owner: Greece
|
||||||
|
Actor35: cycl
|
||||||
|
Location: 59,76
|
||||||
|
Owner: Greece
|
||||||
|
Actor36: cycl
|
||||||
|
Location: 62,79
|
||||||
|
Owner: Greece
|
||||||
|
Actor37: cycl
|
||||||
|
Location: 63,79
|
||||||
|
Owner: Greece
|
||||||
|
Actor38: cycl
|
||||||
|
Location: 64,79
|
||||||
|
Owner: Greece
|
||||||
|
Actor39: cycl
|
||||||
|
Location: 64,80
|
||||||
|
Owner: Greece
|
||||||
|
Actor40: cycl
|
||||||
|
Location: 65,80
|
||||||
|
Owner: Greece
|
||||||
|
Actor41: cycl
|
||||||
|
Location: 66,80
|
||||||
|
Owner: Greece
|
||||||
|
Actor42: cycl
|
||||||
|
Location: 66,81
|
||||||
|
Owner: Greece
|
||||||
|
Actor43: cycl
|
||||||
|
Location: 67,81
|
||||||
|
Owner: Greece
|
||||||
|
Actor44: cycl
|
||||||
|
Location: 68,81
|
||||||
|
Owner: Greece
|
||||||
|
Actor45: cycl
|
||||||
|
Location: 69,81
|
||||||
|
Owner: Greece
|
||||||
|
Actor46: cycl
|
||||||
|
Location: 66,88
|
||||||
|
Owner: Greece
|
||||||
|
Actor47: cycl
|
||||||
|
Location: 67,88
|
||||||
|
Owner: Greece
|
||||||
|
Actor48: cycl
|
||||||
|
Location: 68,88
|
||||||
|
Owner: Greece
|
||||||
|
Actor49: cycl
|
||||||
|
Location: 69,88
|
||||||
|
Owner: Greece
|
||||||
|
Actor50: cycl
|
||||||
|
Location: 70,88
|
||||||
|
Owner: Greece
|
||||||
|
Actor51: cycl
|
||||||
|
Location: 71,88
|
||||||
|
Owner: Greece
|
||||||
|
Actor52: cycl
|
||||||
|
Location: 71,89
|
||||||
|
Owner: Greece
|
||||||
|
Actor53: cycl
|
||||||
|
Location: 71,90
|
||||||
|
Owner: Greece
|
||||||
|
Actor54: cycl
|
||||||
|
Location: 57,93
|
||||||
|
Owner: Greece
|
||||||
|
Actor55: cycl
|
||||||
|
Location: 60,93
|
||||||
|
Owner: Greece
|
||||||
|
Actor56: cycl
|
||||||
|
Location: 57,94
|
||||||
|
Owner: Greece
|
||||||
|
Actor57: cycl
|
||||||
|
Location: 58,94
|
||||||
|
Owner: Greece
|
||||||
|
Actor58: cycl
|
||||||
|
Location: 59,94
|
||||||
|
Owner: Greece
|
||||||
|
Actor59: cycl
|
||||||
|
Location: 60,94
|
||||||
|
Owner: Greece
|
||||||
|
Actor60: brik
|
||||||
|
Location: 70,94
|
||||||
|
Owner: Greece
|
||||||
|
Actor61: brik
|
||||||
|
Location: 71,94
|
||||||
|
Owner: Greece
|
||||||
|
Actor62: brik
|
||||||
|
Location: 70,95
|
||||||
|
Owner: Greece
|
||||||
|
Actor63: brik
|
||||||
|
Location: 71,95
|
||||||
|
Owner: Greece
|
||||||
|
Actor64: cycl
|
||||||
|
Location: 47,96
|
||||||
|
Owner: Greece
|
||||||
|
Actor65: cycl
|
||||||
|
Location: 48,96
|
||||||
|
Owner: Greece
|
||||||
|
Actor66: cycl
|
||||||
|
Location: 49,96
|
||||||
|
Owner: Greece
|
||||||
|
Actor67: cycl
|
||||||
|
Location: 50,96
|
||||||
|
Owner: Greece
|
||||||
|
Actor68: cycl
|
||||||
|
Location: 47,97
|
||||||
|
Owner: Greece
|
||||||
|
Actor69: brik
|
||||||
|
Location: 26,100
|
||||||
|
Owner: Greece
|
||||||
|
Actor70: brik
|
||||||
|
Location: 27,100
|
||||||
|
Owner: Greece
|
||||||
|
Actor71: brik
|
||||||
|
Location: 28,100
|
||||||
|
Owner: Greece
|
||||||
|
Actor72: brik
|
||||||
|
Location: 29,100
|
||||||
|
Owner: Greece
|
||||||
|
Actor73: brik
|
||||||
|
Location: 70,100
|
||||||
|
Owner: Greece
|
||||||
|
Actor74: brik
|
||||||
|
Location: 71,100
|
||||||
|
Owner: Greece
|
||||||
|
Actor75: brik
|
||||||
|
Location: 26,101
|
||||||
|
Owner: Greece
|
||||||
|
Actor76: brik
|
||||||
|
Location: 29,101
|
||||||
|
Owner: Greece
|
||||||
|
Actor77: brik
|
||||||
|
Location: 70,101
|
||||||
|
Owner: Greece
|
||||||
|
Actor78: brik
|
||||||
|
Location: 71,101
|
||||||
|
Owner: Greece
|
||||||
|
Actor79: brik
|
||||||
|
Location: 26,102
|
||||||
|
Owner: Greece
|
||||||
|
Actor80: brik
|
||||||
|
Location: 29,102
|
||||||
|
Owner: Greece
|
||||||
|
Actor81: brik
|
||||||
|
Location: 26,103
|
||||||
|
Owner: Greece
|
||||||
|
Actor82: brik
|
||||||
|
Location: 27,103
|
||||||
|
Owner: Greece
|
||||||
|
Actor83: brik
|
||||||
|
Location: 28,103
|
||||||
|
Owner: Greece
|
||||||
|
Actor84: brik
|
||||||
|
Location: 29,103
|
||||||
|
Owner: Greece
|
||||||
|
Actor85: tc05
|
||||||
|
Location: 52,104
|
||||||
|
Owner: Neutral
|
||||||
|
Actor86: tc02
|
||||||
|
Location: 46,99
|
||||||
|
Owner: Neutral
|
||||||
|
Actor87: t01
|
||||||
|
Location: 92,88
|
||||||
|
Owner: Neutral
|
||||||
|
Actor88: tc04
|
||||||
|
Location: 76,95
|
||||||
|
Owner: Neutral
|
||||||
|
Actor89: tc01
|
||||||
|
Location: 76,91
|
||||||
|
Owner: Neutral
|
||||||
|
Actor90: tc04
|
||||||
|
Location: 60,65
|
||||||
|
Owner: Neutral
|
||||||
|
Actor91: t12
|
||||||
|
Location: 64,65
|
||||||
|
Owner: Neutral
|
||||||
|
Actor92: tc01
|
||||||
|
Location: 46,56
|
||||||
|
Owner: Neutral
|
||||||
|
Actor93: tc05
|
||||||
|
Location: 53,63
|
||||||
|
Owner: Neutral
|
||||||
|
Actor94: tc03
|
||||||
|
Location: 37,67
|
||||||
|
Owner: Neutral
|
||||||
|
Actor95: tc03
|
||||||
|
Location: 70,59
|
||||||
|
Owner: Neutral
|
||||||
|
Actor96: tc02
|
||||||
|
Location: 87,65
|
||||||
|
Owner: Neutral
|
||||||
|
Actor97: t16
|
||||||
|
Location: 42,48
|
||||||
|
Owner: Neutral
|
||||||
|
Actor98: t15
|
||||||
|
Location: 27,68
|
||||||
|
Owner: Neutral
|
||||||
|
Actor99: t14
|
||||||
|
Location: 56,68
|
||||||
|
Owner: Neutral
|
||||||
|
Actor100: t13
|
||||||
|
Location: 59,50
|
||||||
|
Owner: Neutral
|
||||||
|
Actor101: t12
|
||||||
|
Location: 92,54
|
||||||
|
Owner: Neutral
|
||||||
|
Actor102: tc04
|
||||||
|
Location: 23,63
|
||||||
|
Owner: Neutral
|
||||||
|
Actor103: tc02
|
||||||
|
Location: 18,52
|
||||||
|
Owner: Neutral
|
||||||
|
Actor104: tc01
|
||||||
|
Location: 29,53
|
||||||
|
Owner: Neutral
|
||||||
|
Actor105: t15
|
||||||
|
Location: 27,59
|
||||||
|
Owner: Neutral
|
||||||
|
Actor106: tc01
|
||||||
|
Location: 20,57
|
||||||
|
Owner: Neutral
|
||||||
|
Actor107: t16
|
||||||
|
Location: 19,58
|
||||||
|
Owner: Neutral
|
||||||
|
Actor108: t13
|
||||||
|
Location: 18,57
|
||||||
|
Owner: Neutral
|
||||||
|
Actor111: tc04
|
||||||
|
Location: 56,102
|
||||||
|
Owner: Neutral
|
||||||
|
Actor112: tc02
|
||||||
|
Location: 58,100
|
||||||
|
Owner: Neutral
|
||||||
|
Actor114: tc05
|
||||||
|
Location: 84,33
|
||||||
|
Owner: Neutral
|
||||||
|
Actor116: atek
|
||||||
|
Location: 26,96
|
||||||
|
Owner: Greece
|
||||||
|
Chronosphere: pdox
|
||||||
|
Location: 27,101
|
||||||
|
Owner: Greece
|
||||||
|
Actor118: pbox
|
||||||
|
Location: 48,75
|
||||||
|
Owner: Greece
|
||||||
|
Actor119: pbox
|
||||||
|
Location: 58,80
|
||||||
|
Owner: Greece
|
||||||
|
Actor120: pbox
|
||||||
|
Location: 34,74
|
||||||
|
Owner: Greece
|
||||||
|
Actor121: pbox
|
||||||
|
Location: 31,74
|
||||||
|
Owner: Greece
|
||||||
|
Actor122: hbox
|
||||||
|
Location: 46,99
|
||||||
|
Owner: Greece
|
||||||
|
Actor123: hbox
|
||||||
|
Location: 26,81
|
||||||
|
Owner: Greece
|
||||||
|
Actor124: hbox
|
||||||
|
Location: 26,93
|
||||||
|
Owner: Greece
|
||||||
|
Actor125: hbox
|
||||||
|
Location: 68,89
|
||||||
|
Owner: Greece
|
||||||
|
Radar4: dome
|
||||||
|
Location: 49,104
|
||||||
|
Owner: Greece
|
||||||
|
Actor127: gap
|
||||||
|
Location: 47,105
|
||||||
|
Owner: Greece
|
||||||
|
Actor128: gap
|
||||||
|
Location: 68,91
|
||||||
|
Owner: Greece
|
||||||
|
Actor129: gun
|
||||||
|
Location: 58,78
|
||||||
|
Owner: Greece
|
||||||
|
Facing: 892
|
||||||
|
Actor130: gun
|
||||||
|
Location: 60,80
|
||||||
|
Owner: Greece
|
||||||
|
Facing: 892
|
||||||
|
Actor131: gun
|
||||||
|
Location: 50,72
|
||||||
|
Owner: Greece
|
||||||
|
Actor132: gun
|
||||||
|
Location: 46,72
|
||||||
|
Owner: Greece
|
||||||
|
Actor133: gun
|
||||||
|
Location: 72,96
|
||||||
|
Owner: Greece
|
||||||
|
Facing: 764
|
||||||
|
Actor134: gun
|
||||||
|
Location: 72,99
|
||||||
|
Owner: Greece
|
||||||
|
Facing: 764
|
||||||
|
Actor135: fact
|
||||||
|
Location: 47,84
|
||||||
|
Owner: Greece
|
||||||
|
Actor136: proc
|
||||||
|
Location: 52,90
|
||||||
|
Owner: Greece
|
||||||
|
Actor137: proc
|
||||||
|
Location: 50,96
|
||||||
|
Owner: Greece
|
||||||
|
Actor138: silo
|
||||||
|
Location: 59,93
|
||||||
|
Owner: Greece
|
||||||
|
Actor139: silo
|
||||||
|
Location: 49,97
|
||||||
|
Owner: Greece
|
||||||
|
Actor140: silo
|
||||||
|
Location: 48,97
|
||||||
|
Owner: Greece
|
||||||
|
Actor141: silo
|
||||||
|
Location: 58,93
|
||||||
|
Owner: Greece
|
||||||
|
HPad1: hpad
|
||||||
|
Location: 32,73
|
||||||
|
Owner: Greece
|
||||||
|
HPad4: hpad
|
||||||
|
Location: 55,96
|
||||||
|
Owner: Greece
|
||||||
|
Actor144: apwr
|
||||||
|
Location: 44,86
|
||||||
|
Owner: Greece
|
||||||
|
Actor145: apwr
|
||||||
|
Location: 42,104
|
||||||
|
Owner: Greece
|
||||||
|
Actor146: apwr
|
||||||
|
Location: 43,98
|
||||||
|
Owner: Greece
|
||||||
|
Actor147: apwr
|
||||||
|
Location: 44,92
|
||||||
|
Owner: Greece
|
||||||
|
Actor148: apwr
|
||||||
|
Location: 27,83
|
||||||
|
Owner: Greece
|
||||||
|
Actor149: apwr
|
||||||
|
Location: 50,86
|
||||||
|
Owner: Greece
|
||||||
|
Actor150: apwr
|
||||||
|
Location: 37,104
|
||||||
|
Owner: Greece
|
||||||
|
Actor151: apwr
|
||||||
|
Location: 31,92
|
||||||
|
Owner: Greece
|
||||||
|
GreeceTent2: tent
|
||||||
|
Location: 19,98
|
||||||
|
Owner: Greece
|
||||||
|
GreeceTent1: tent
|
||||||
|
Location: 46,80
|
||||||
|
Owner: Greece
|
||||||
|
Radar1: dome
|
||||||
|
Location: 29,90
|
||||||
|
Owner: Greece
|
||||||
|
Radar2: dome
|
||||||
|
Location: 48,88
|
||||||
|
Owner: Greece
|
||||||
|
Radar3: dome
|
||||||
|
Location: 69,89
|
||||||
|
Owner: Greece
|
||||||
|
HPad2: hpad
|
||||||
|
Location: 56,76
|
||||||
|
Owner: Greece
|
||||||
|
GreeceShipyard: syrd
|
||||||
|
Location: 73,84
|
||||||
|
Owner: Greece
|
||||||
|
GoodGuyShipyard: syrd
|
||||||
|
Location: 32,43
|
||||||
|
Owner: GoodGuy
|
||||||
|
Actor160: brl3
|
||||||
|
Location: 71,87
|
||||||
|
Owner: Greece
|
||||||
|
Actor161: barl
|
||||||
|
Location: 71,86
|
||||||
|
Owner: Greece
|
||||||
|
Actor162: barl
|
||||||
|
Location: 70,87
|
||||||
|
Owner: Greece
|
||||||
|
Actor163: hbox
|
||||||
|
Location: 26,89
|
||||||
|
Owner: Greece
|
||||||
|
HPad3: hpad
|
||||||
|
Location: 61,80
|
||||||
|
Owner: Greece
|
||||||
|
Actor165: gap
|
||||||
|
Location: 24,46
|
||||||
|
Owner: GoodGuy
|
||||||
|
Actor166: gun
|
||||||
|
Location: 27,42
|
||||||
|
Owner: Greece
|
||||||
|
Facing: 892
|
||||||
|
Actor167: gun
|
||||||
|
Location: 25,41
|
||||||
|
Owner: Greece
|
||||||
|
Facing: 892
|
||||||
|
Actor168: agun
|
||||||
|
Location: 28,45
|
||||||
|
Owner: GoodGuy
|
||||||
|
Actor169: agun
|
||||||
|
Location: 23,42
|
||||||
|
Owner: GoodGuy
|
||||||
|
Actor170: fact
|
||||||
|
Location: 20,42
|
||||||
|
Owner: GoodGuy
|
||||||
|
GoodGuyWarFactory: weap
|
||||||
|
Location: 25,50
|
||||||
|
Owner: GoodGuy
|
||||||
|
Actor172: apwr
|
||||||
|
Location: 25,47
|
||||||
|
Owner: GoodGuy
|
||||||
|
Actor173: apwr
|
||||||
|
Location: 18,46
|
||||||
|
Owner: GoodGuy
|
||||||
|
Actor174: agun
|
||||||
|
Location: 24,50
|
||||||
|
Owner: GoodGuy
|
||||||
|
Actor175: apwr
|
||||||
|
Location: 27,79
|
||||||
|
Owner: Greece
|
||||||
|
GreeceWarFactory: weap
|
||||||
|
Location: 59,85
|
||||||
|
Owner: Greece
|
||||||
|
Actor177: agun
|
||||||
|
Location: 47,101
|
||||||
|
Owner: Greece
|
||||||
|
Actor178: v19
|
||||||
|
Location: 26,78
|
||||||
|
Owner: Greece
|
||||||
|
Actor179: brl3
|
||||||
|
Location: 26,77
|
||||||
|
Owner: Greece
|
||||||
|
Actor180: brl3
|
||||||
|
Location: 54,97
|
||||||
|
Owner: Greece
|
||||||
|
Actor181: barl
|
||||||
|
Location: 53,97
|
||||||
|
Owner: Greece
|
||||||
|
Actor182: v19
|
||||||
|
Location: 54,96
|
||||||
|
Owner: Greece
|
||||||
|
Actor183: brl3
|
||||||
|
Location: 53,96
|
||||||
|
Owner: Greece
|
||||||
|
Actor184: gun
|
||||||
|
Location: 63,78
|
||||||
|
Owner: Greece
|
||||||
|
Actor190: gap
|
||||||
|
Location: 30,88
|
||||||
|
Owner: Greece
|
||||||
|
StartAttack3: 2tnk
|
||||||
|
Location: 75,60
|
||||||
|
Owner: Greece
|
||||||
|
Facing: 892
|
||||||
|
StartAttack4: 2tnk
|
||||||
|
Location: 76,62
|
||||||
|
Owner: Greece
|
||||||
|
Facing: 892
|
||||||
|
StartAttack5: 2tnk
|
||||||
|
Location: 78,62
|
||||||
|
Owner: Greece
|
||||||
|
Facing: 892
|
||||||
|
StartAttack2: arty
|
||||||
|
Location: 80,59
|
||||||
|
Owner: Greece
|
||||||
|
Facing: 892
|
||||||
|
StartAttack1: arty
|
||||||
|
Location: 79,58
|
||||||
|
Owner: Greece
|
||||||
|
Facing: 892
|
||||||
|
V2A: v2rl
|
||||||
|
Location: 84,20
|
||||||
|
Owner: USSR
|
||||||
|
Facing: 508
|
||||||
|
V2B: v2rl
|
||||||
|
Location: 82,20
|
||||||
|
Owner: USSR
|
||||||
|
Facing: 508
|
||||||
|
Actor199: 4tnk
|
||||||
|
Location: 80,20
|
||||||
|
Owner: USSR
|
||||||
|
Facing: 508
|
||||||
|
Actor200: 4tnk
|
||||||
|
Location: 86,20
|
||||||
|
Owner: USSR
|
||||||
|
Facing: 508
|
||||||
|
Actor201: 3tnk
|
||||||
|
Location: 81,21
|
||||||
|
Owner: USSR
|
||||||
|
Facing: 508
|
||||||
|
Actor202: 3tnk
|
||||||
|
Location: 85,21
|
||||||
|
Owner: USSR
|
||||||
|
Facing: 508
|
||||||
|
Actor203: 2tnk
|
||||||
|
Location: 28,41
|
||||||
|
Owner: Greece
|
||||||
|
Actor204: 2tnk
|
||||||
|
Location: 30,41
|
||||||
|
Owner: Greece
|
||||||
|
Actor205: 2tnk
|
||||||
|
Location: 24,40
|
||||||
|
Owner: Greece
|
||||||
|
Facing: 892
|
||||||
|
Actor206: 2tnk
|
||||||
|
Location: 26,40
|
||||||
|
Owner: Greece
|
||||||
|
Facing: 892
|
||||||
|
Actor207: arty
|
||||||
|
Location: 29,42
|
||||||
|
Owner: Greece
|
||||||
|
Actor208: arty
|
||||||
|
Location: 24,41
|
||||||
|
Owner: Greece
|
||||||
|
Facing: 892
|
||||||
|
OreAttack3: 2tnk
|
||||||
|
Location: 39,16
|
||||||
|
Owner: GoodGuy
|
||||||
|
Facing: 764
|
||||||
|
OreAttack5: 2tnk
|
||||||
|
Location: 40,17
|
||||||
|
Owner: GoodGuy
|
||||||
|
Facing: 764
|
||||||
|
OreAttack4: 2tnk
|
||||||
|
Location: 41,16
|
||||||
|
Owner: GoodGuy
|
||||||
|
Facing: 764
|
||||||
|
OreAttack2: 2tnk
|
||||||
|
Location: 40,15
|
||||||
|
Owner: GoodGuy
|
||||||
|
Facing: 764
|
||||||
|
OreAttack6: arty
|
||||||
|
Location: 40,18
|
||||||
|
Owner: GoodGuy
|
||||||
|
Facing: 764
|
||||||
|
OreAttack1: arty
|
||||||
|
Location: 40,14
|
||||||
|
Owner: GoodGuy
|
||||||
|
Facing: 764
|
||||||
|
Actor215: 2tnk
|
||||||
|
Location: 68,97
|
||||||
|
Owner: Greece
|
||||||
|
Facing: 764
|
||||||
|
Actor216: 2tnk
|
||||||
|
Location: 68,98
|
||||||
|
Owner: Greece
|
||||||
|
Facing: 764
|
||||||
|
Actor217: 2tnk
|
||||||
|
Location: 67,96
|
||||||
|
Owner: Greece
|
||||||
|
Facing: 764
|
||||||
|
Actor218: 2tnk
|
||||||
|
Location: 67,99
|
||||||
|
Owner: Greece
|
||||||
|
Facing: 764
|
||||||
|
Actor221: e3
|
||||||
|
Location: 67,91
|
||||||
|
Owner: Greece
|
||||||
|
SubCell: 1
|
||||||
|
Actor222: e3
|
||||||
|
Location: 67,89
|
||||||
|
Owner: Greece
|
||||||
|
SubCell: 0
|
||||||
|
Actor223: e3
|
||||||
|
Location: 71,89
|
||||||
|
Owner: Greece
|
||||||
|
SubCell: 1
|
||||||
|
Actor224: e3
|
||||||
|
Location: 56,93
|
||||||
|
Owner: Greece
|
||||||
|
SubCell: 2
|
||||||
|
Actor225: e3
|
||||||
|
Location: 51,106
|
||||||
|
Owner: Greece
|
||||||
|
SubCell: 1
|
||||||
|
Actor226: e3
|
||||||
|
Location: 48,106
|
||||||
|
Owner: Greece
|
||||||
|
SubCell: 2
|
||||||
|
Actor227: e3
|
||||||
|
Location: 55,75
|
||||||
|
Owner: Greece
|
||||||
|
SubCell: 0
|
||||||
|
Actor228: e3
|
||||||
|
Location: 55,76
|
||||||
|
Owner: Greece
|
||||||
|
SubCell: 2
|
||||||
|
Actor229: e3
|
||||||
|
Location: 63,80
|
||||||
|
Owner: Greece
|
||||||
|
SubCell: 2
|
||||||
|
Actor230: e3
|
||||||
|
Location: 63,81
|
||||||
|
Owner: Greece
|
||||||
|
SubCell: 2
|
||||||
|
Actor231: e3
|
||||||
|
Location: 30,75
|
||||||
|
Owner: Greece
|
||||||
|
SubCell: 4
|
||||||
|
Actor232: e3
|
||||||
|
Location: 31,76
|
||||||
|
Owner: Greece
|
||||||
|
SubCell: 2
|
||||||
|
Actor233: e3
|
||||||
|
Location: 35,75
|
||||||
|
Owner: Greece
|
||||||
|
SubCell: 3
|
||||||
|
Actor234: e3
|
||||||
|
Location: 35,76
|
||||||
|
Owner: Greece
|
||||||
|
SubCell: 1
|
||||||
|
Actor235: e3
|
||||||
|
Location: 46,79
|
||||||
|
Owner: Greece
|
||||||
|
SubCell: 3
|
||||||
|
Actor236: e3
|
||||||
|
Location: 47,79
|
||||||
|
Owner: Greece
|
||||||
|
SubCell: 3
|
||||||
|
Actor237: e3
|
||||||
|
Location: 45,85
|
||||||
|
Owner: Greece
|
||||||
|
SubCell: 2
|
||||||
|
Actor238: e3
|
||||||
|
Location: 45,85
|
||||||
|
Owner: Greece
|
||||||
|
SubCell: 1
|
||||||
|
Actor239: e3
|
||||||
|
Location: 50,85
|
||||||
|
Owner: Greece
|
||||||
|
SubCell: 1
|
||||||
|
Actor240: e3
|
||||||
|
Location: 51,85
|
||||||
|
Owner: Greece
|
||||||
|
SubCell: 1
|
||||||
|
Actor241: e3
|
||||||
|
Location: 47,88
|
||||||
|
Owner: Greece
|
||||||
|
SubCell: 2
|
||||||
|
Actor242: e3
|
||||||
|
Location: 50,89
|
||||||
|
Owner: Greece
|
||||||
|
SubCell: 2
|
||||||
|
Actor243: e3
|
||||||
|
Location: 28,90
|
||||||
|
Owner: Greece
|
||||||
|
SubCell: 3
|
||||||
|
Actor244: e3
|
||||||
|
Location: 28,92
|
||||||
|
Owner: Greece
|
||||||
|
SubCell: 1
|
||||||
|
Actor245: e3
|
||||||
|
Location: 32,91
|
||||||
|
Owner: Greece
|
||||||
|
SubCell: 0
|
||||||
|
Actor246: dd
|
||||||
|
Location: 92,101
|
||||||
|
Owner: Greece
|
||||||
|
Facing: 892
|
||||||
|
Actor247: dd
|
||||||
|
Location: 38,24
|
||||||
|
Owner: Greece
|
||||||
|
Facing: 764
|
||||||
|
Actor248: pt
|
||||||
|
Location: 92,97
|
||||||
|
Owner: Greece
|
||||||
|
Facing: 892
|
||||||
|
Actor249: pt
|
||||||
|
Location: 97,101
|
||||||
|
Owner: Greece
|
||||||
|
Facing: 892
|
||||||
|
Actor250: pt
|
||||||
|
Location: 40,27
|
||||||
|
Owner: Greece
|
||||||
|
Facing: 764
|
||||||
|
Actor251: pt
|
||||||
|
Location: 41,21
|
||||||
|
Owner: Greece
|
||||||
|
Facing: 764
|
||||||
|
Actor252: pt
|
||||||
|
Location: 82,82
|
||||||
|
Owner: Greece
|
||||||
|
Facing: 508
|
||||||
|
Actor253: pt
|
||||||
|
Location: 82,87
|
||||||
|
Owner: Greece
|
||||||
|
Actor254: dd
|
||||||
|
Location: 80,81
|
||||||
|
Owner: Greece
|
||||||
|
Facing: 636
|
||||||
|
Actor255: dd
|
||||||
|
Location: 80,87
|
||||||
|
Owner: Greece
|
||||||
|
Facing: 892
|
||||||
|
Actor256: dd
|
||||||
|
Location: 35,41
|
||||||
|
Owner: GoodGuy
|
||||||
|
Actor257: dd
|
||||||
|
Location: 38,46
|
||||||
|
Owner: GoodGuy
|
||||||
|
Facing: 892
|
||||||
|
Actor258: pt
|
||||||
|
Location: 36,40
|
||||||
|
Owner: GoodGuy
|
||||||
|
Actor259: pt
|
||||||
|
Location: 39,44
|
||||||
|
Owner: GoodGuy
|
||||||
|
Facing: 892
|
||||||
|
Actor260: pt
|
||||||
|
Location: 35,39
|
||||||
|
Owner: GoodGuy
|
||||||
|
Actor273: fix
|
||||||
|
Owner: Greece
|
||||||
|
Location: 38,79
|
||||||
|
Jeep2: jeep
|
||||||
|
Owner: Greece
|
||||||
|
Location: 25,91
|
||||||
|
Facing: 384
|
||||||
|
Jeep1: jeep
|
||||||
|
Owner: Greece
|
||||||
|
Location: 24,91
|
||||||
|
Facing: 384
|
||||||
|
Actor277: arty
|
||||||
|
Owner: Greece
|
||||||
|
Location: 31,88
|
||||||
|
Facing: 951
|
||||||
|
Actor278: arty
|
||||||
|
Owner: Greece
|
||||||
|
Location: 34,94
|
||||||
|
Facing: 919
|
||||||
|
Actor279: arty
|
||||||
|
Owner: Greece
|
||||||
|
Location: 45,101
|
||||||
|
Facing: 1023
|
||||||
|
Actor280: arty
|
||||||
|
Owner: Greece
|
||||||
|
Location: 50,102
|
||||||
|
Facing: 991
|
||||||
|
Actor272: mine
|
||||||
|
Owner: Neutral
|
||||||
|
Location: 99,23
|
||||||
|
Actor274: mine
|
||||||
|
Owner: Neutral
|
||||||
|
Location: 64,21
|
||||||
|
Actor275: mine
|
||||||
|
Owner: Neutral
|
||||||
|
Location: 84,74
|
||||||
|
Actor276: mine
|
||||||
|
Owner: Neutral
|
||||||
|
Location: 84,92
|
||||||
|
DefaultCameraPosition: waypoint
|
||||||
|
Location: 83,20
|
||||||
|
Owner: Neutral
|
||||||
|
MCVEntry: waypoint
|
||||||
|
Location: 83,14
|
||||||
|
Owner: Neutral
|
||||||
|
EastWaterEntry: waypoint
|
||||||
|
Location: 91,106
|
||||||
|
Owner: Neutral
|
||||||
|
EastBeach1: waypoint
|
||||||
|
Location: 102,51
|
||||||
|
Owner: Neutral
|
||||||
|
EastBeach2: waypoint
|
||||||
|
Location: 103,19
|
||||||
|
Owner: Neutral
|
||||||
|
EastBeach3: waypoint
|
||||||
|
Location: 90,73
|
||||||
|
Owner: Neutral
|
||||||
|
WestWaterEntry: waypoint
|
||||||
|
Location: 18,22
|
||||||
|
Owner: Neutral
|
||||||
|
WestBeach1: waypoint
|
||||||
|
Location: 30,29
|
||||||
|
Owner: Neutral
|
||||||
|
WestBeach2: waypoint
|
||||||
|
Location: 36,17
|
||||||
|
Owner: Neutral
|
||||||
|
JeepWaypoint1: waypoint
|
||||||
|
Location: 23,47
|
||||||
|
Owner: Neutral
|
||||||
|
JeepWaypoint2: waypoint
|
||||||
|
Location: 25,91
|
||||||
|
Owner: Neutral
|
||||||
|
ChinookLZ: waypoint
|
||||||
|
Location: 51,16
|
||||||
|
Owner: Neutral
|
||||||
|
BridgeAttackProxy: waypoint
|
||||||
|
Location: 78,60
|
||||||
|
Owner: Neutral
|
||||||
|
ChronoshiftPoint: waypoint
|
||||||
|
Location: 32,101
|
||||||
|
Owner: Neutral
|
||||||
|
WarFactoryRally: waypoint
|
||||||
|
Location: 56,82
|
||||||
|
Owner: Neutral
|
||||||
|
CruiserStop: waypoint
|
||||||
|
Location: 52,25
|
||||||
|
Owner: Neutral
|
||||||
|
|
||||||
|
Rules: ra|rules/campaign-rules.yaml, ra|rules/campaign-tooltips.yaml, ra|rules/campaign-palettes.yaml, rules.yaml
|
||||||
|
|
||||||
|
Translations: ra|languages/lua/en.ftl, ra|languages/difficulties/en.ftl
|
||||||
83
mods/ra/maps/soviet-13b/rules.yaml
Normal file
83
mods/ra/maps/soviet-13b/rules.yaml
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
World:
|
||||||
|
LuaScript:
|
||||||
|
Scripts: campaign.lua, soviet13b.lua, soviet13b-AI.lua, utils.lua
|
||||||
|
MissionData:
|
||||||
|
BriefingVideo: soviet13.vqa
|
||||||
|
WinVideo: sovtstar.vqa
|
||||||
|
LossVideo: allymorf.vqa
|
||||||
|
StartVideo: mtnkfact.vqa
|
||||||
|
Briefing: We have another chance to capture the Chronosphere. Take out the Radar Domes to cut the link between them and the Chronosphere. Then capture it!
|
||||||
|
ScriptLobbyDropdown@difficulty:
|
||||||
|
ID: difficulty
|
||||||
|
Label: dropdown-difficulty.label
|
||||||
|
Description: dropdown-difficulty.description
|
||||||
|
Values:
|
||||||
|
easy: options-difficulty.easy
|
||||||
|
normal: options-difficulty.normal
|
||||||
|
hard: options-difficulty.hard
|
||||||
|
Default: normal
|
||||||
|
|
||||||
|
Player:
|
||||||
|
PlayerResources:
|
||||||
|
DefaultCash: 10000
|
||||||
|
|
||||||
|
AFLD:
|
||||||
|
AirstrikePower@parabombs:
|
||||||
|
Prerequisites: aircraft.soviet
|
||||||
|
ParatroopersPower@paratroopers:
|
||||||
|
DropItems: E1,E1,E1,E2,E2
|
||||||
|
|
||||||
|
PDOX:
|
||||||
|
Power:
|
||||||
|
Amount: 0
|
||||||
|
-WithColoredOverlay@IDISABLE:
|
||||||
|
Buildable:
|
||||||
|
Prerequisites: ~disabled
|
||||||
|
|
||||||
|
ATEK:
|
||||||
|
GpsPower:
|
||||||
|
DisplayTimerRelationships: Ally
|
||||||
|
|
||||||
|
MSLO:
|
||||||
|
Buildable:
|
||||||
|
Prerequisites: ~disabled
|
||||||
|
|
||||||
|
FTRK:
|
||||||
|
Buildable:
|
||||||
|
Prerequisites: ~disabled
|
||||||
|
|
||||||
|
MCV:
|
||||||
|
Buildable:
|
||||||
|
Prerequisites: ~disabled
|
||||||
|
|
||||||
|
MECH:
|
||||||
|
Buildable:
|
||||||
|
Prerequisites: ~disabled
|
||||||
|
|
||||||
|
IRON:
|
||||||
|
Buildable:
|
||||||
|
Prerequisites: ~disabled
|
||||||
|
|
||||||
|
QTNK:
|
||||||
|
Buildable:
|
||||||
|
Prerequisites: ~disabled
|
||||||
|
|
||||||
|
E7:
|
||||||
|
Buildable:
|
||||||
|
Prerequisites: ~disabled
|
||||||
|
|
||||||
|
E7.noautotarget:
|
||||||
|
Buildable:
|
||||||
|
Prerequisites: ~disabled
|
||||||
|
|
||||||
|
E3:
|
||||||
|
Buildable:
|
||||||
|
Prerequisites: ~tent
|
||||||
|
|
||||||
|
MSUB:
|
||||||
|
Buildable:
|
||||||
|
Prerequisites: ~disabled
|
||||||
|
|
||||||
|
THF:
|
||||||
|
Buildable:
|
||||||
|
Prerequisites: ~disabled
|
||||||
170
mods/ra/maps/soviet-13b/soviet13b-AI.lua
Normal file
170
mods/ra/maps/soviet-13b/soviet13b-AI.lua
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
--[[
|
||||||
|
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.
|
||||||
|
]]
|
||||||
|
WTransWays =
|
||||||
|
{
|
||||||
|
{ EastWaterEntry.Location, EastBeach1.Location },
|
||||||
|
{ EastWaterEntry.Location, EastBeach2.Location },
|
||||||
|
{ EastWaterEntry.Location, EastBeach3.Location },
|
||||||
|
{ WestWaterEntry.Location, WestBeach1.Location },
|
||||||
|
{ WestWaterEntry.Location, WestBeach2.Location }
|
||||||
|
}
|
||||||
|
|
||||||
|
WTransUnits =
|
||||||
|
{
|
||||||
|
hard = { { "2tnk", "2tnk", "e3", "e3", "e3" }, { "2tnk", "2tnk", "2tnk", "2tnk" } },
|
||||||
|
normal = { { "2tnk", "1tnk", "e3", "e3", "jeep" }, { "2tnk", "2tnk", "1tnk", "jeep" } },
|
||||||
|
easy = { { "2tnk", "e1", "e1", "e3", "e3" }, { "1tnk", "1tnk", "jeep", "jeep" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
WTransDelays =
|
||||||
|
{
|
||||||
|
easy = DateTime.Minutes(5),
|
||||||
|
normal = DateTime.Minutes(4),
|
||||||
|
hard = DateTime.Minutes(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
AttackGroup = { }
|
||||||
|
AttackGroupSize = 8
|
||||||
|
|
||||||
|
ProductionInterval =
|
||||||
|
{
|
||||||
|
easy = DateTime.Seconds(20),
|
||||||
|
normal = DateTime.Seconds(14),
|
||||||
|
hard = DateTime.Seconds(8)
|
||||||
|
}
|
||||||
|
|
||||||
|
AlliedInfantry = { "e1", "e3" }
|
||||||
|
AlliedVehiclesUpgradeDelay = DateTime.Minutes(8)
|
||||||
|
AlliedVehicleType = "Normal"
|
||||||
|
AlliedVehicles =
|
||||||
|
{
|
||||||
|
Normal = { "1tnk", "2tnk", "2tnk" },
|
||||||
|
Upgraded = { "2tnk", "2tnk", "arty" }
|
||||||
|
}
|
||||||
|
|
||||||
|
WTransWaves = function()
|
||||||
|
local way = Utils.Random(WTransWays)
|
||||||
|
local units = Utils.Random(WTransUnits)
|
||||||
|
local attackUnits = Reinforcements.ReinforceWithTransport(Greece, "lst", units , way, { way[2], way[1] })[2]
|
||||||
|
Utils.Do(attackUnits, function(a)
|
||||||
|
Trigger.OnAddedToWorld(a, function()
|
||||||
|
IdleHunt(a)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
Trigger.AfterDelay(WTransDelay, WTransWaves)
|
||||||
|
end
|
||||||
|
|
||||||
|
SendAttackGroup = function()
|
||||||
|
if #AttackGroup < AttackGroupSize then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
Utils.Do(AttackGroup, IdleHunt)
|
||||||
|
|
||||||
|
AttackGroup = { }
|
||||||
|
end
|
||||||
|
|
||||||
|
ProduceInfantry = function()
|
||||||
|
if (GreeceTent1.IsDead or GreeceTent1.Owner ~= Greece) and (GreeceTent2.IsDead or GreeceTent2.Owner ~= Greece) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
Greece.Build({ Utils.Random(AlliedInfantry) }, function(units)
|
||||||
|
table.insert(AttackGroup, units[1])
|
||||||
|
SendAttackGroup()
|
||||||
|
Trigger.AfterDelay(ProductionInterval[Difficulty], ProduceInfantry)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
ProduceVehicles = function()
|
||||||
|
if GreeceWarFactory.IsDead or GreeceWarFactory.Owner ~= Greece then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
Greece.Build({ Utils.Random(AlliedVehicles[AlliedVehicleType]) }, function(units)
|
||||||
|
table.insert(AttackGroup, units[1])
|
||||||
|
SendAttackGroup()
|
||||||
|
Trigger.AfterDelay(ProductionInterval[Difficulty], ProduceVehicles)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
AlliedAircraftType = { "heli" }
|
||||||
|
Longbows = { }
|
||||||
|
|
||||||
|
AlliedAircraft = function()
|
||||||
|
if (HPad1.IsDead or HPad1.Owner ~= Greece) and (HPad2.IsDead or HPad2.Owner ~= Greece) and (HPad3.IsDead or HPad3.Owner ~= Greece) and (HPad4.IsDead or HPad4.Owner ~= Greece) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
Greece.Build(AlliedAircraftType, function(units)
|
||||||
|
local longbow = units[1]
|
||||||
|
Longbows[#Longbows + 1] = longbow
|
||||||
|
|
||||||
|
Trigger.OnKilled(longbow, AlliedAircraft)
|
||||||
|
|
||||||
|
local alive = Utils.Where(Longbows, function(y) return not y.IsDead end)
|
||||||
|
if #alive < 2 then
|
||||||
|
Trigger.AfterDelay(DateTime.Seconds(75), AlliedAircraft)
|
||||||
|
end
|
||||||
|
|
||||||
|
InitializeAttackAircraft(longbow, USSR)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
SendCruiser = function()
|
||||||
|
if GoodGuyShipyard.IsDead or GoodGuyShipyard.Owner ~= GoodGuy then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local boat = Reinforcements.Reinforce(Greece, { "ca" }, { WestWaterEntry.Location })
|
||||||
|
Utils.Do(boat, function(ca)
|
||||||
|
ca.Move(CruiserStop.Location)
|
||||||
|
Trigger.OnKilled(ca, function()
|
||||||
|
Trigger.AfterDelay(DateTime.Minutes(6), SendCruiser)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
ChinookChalk = { "e1", "e1", "e1", "e3", "e3", "e3", "e3", "e3" }
|
||||||
|
ChinookPath = { WestWaterEntry.Location, ChinookLZ.Location }
|
||||||
|
|
||||||
|
SendChinook = function()
|
||||||
|
if (HPad1.IsDead or HPad1.Owner ~= Greece) and (HPad2.IsDead or HPad2.Owner ~= Greece) and (HPad3.IsDead or HPad3.Owner ~= Greece) and (HPad4.IsDead or HPad4.Owner ~= Greece) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local chalk = Reinforcements.ReinforceWithTransport(Greece, "tran", ChinookChalk , ChinookPath, { ChinookPath[1] })[2]
|
||||||
|
Utils.Do(chalk, function(unit)
|
||||||
|
Trigger.OnAddedToWorld(unit, IdleHunt)
|
||||||
|
end)
|
||||||
|
|
||||||
|
Trigger.AfterDelay(DateTime.Minutes(5), SendChinook)
|
||||||
|
end
|
||||||
|
|
||||||
|
ActivateAI = function()
|
||||||
|
WTransUnits = WTransUnits[Difficulty]
|
||||||
|
WTransDelay = WTransDelays[Difficulty]
|
||||||
|
|
||||||
|
local buildings = Utils.Where(Map.ActorsInWorld, function(self) return self.Owner == Greece and self.HasProperty("StartBuildingRepairs") end)
|
||||||
|
Utils.Do(buildings, function(actor)
|
||||||
|
Trigger.OnDamaged(actor, function(building)
|
||||||
|
if building.Owner == Greece and building.Health < building.MaxHealth * 3/4 then
|
||||||
|
building.StartBuildingRepairs()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
Trigger.AfterDelay(AlliedVehiclesUpgradeDelay, function() AlliedVehicleType = "Upgraded" end)
|
||||||
|
ProduceInfantry()
|
||||||
|
Trigger.AfterDelay(DateTime.Minutes(3), ProduceVehicles)
|
||||||
|
Trigger.AfterDelay(DateTime.Minutes(5), AlliedAircraft)
|
||||||
|
Trigger.AfterDelay(DateTime.Minutes(6), WTransWaves)
|
||||||
|
Trigger.AfterDelay(DateTime.Minutes(10), SendCruiser)
|
||||||
|
end
|
||||||
125
mods/ra/maps/soviet-13b/soviet13b.lua
Normal file
125
mods/ra/maps/soviet-13b/soviet13b.lua
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
--[[
|
||||||
|
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.
|
||||||
|
]]
|
||||||
|
Jeeps = { Jeep1, Jeep2 }
|
||||||
|
JeepWaypoints = { JeepWaypoint1.Location, JeepWaypoint2.Location }
|
||||||
|
OreAttackers = { OreAttack1, OreAttack2, OreAttack3, OreAttack4, OreAttack5, OreAttack6 }
|
||||||
|
RadarSites = { Radar1, Radar2, Radar3, Radar4 }
|
||||||
|
StartAttack = { StartAttack1, StartAttack2, StartAttack3, StartAttack4, StartAttack5 }
|
||||||
|
ChronoDemolitionTrigger = { CPos.New(36,96), CPos.New(37,96), CPos.New(37,97), CPos.New(38,97), CPos.New(38,98), CPos.New(39,98) }
|
||||||
|
OreAttackFootprintTrigger = { CPos.New(57,20), CPos.New(57,19), CPos.New(57,18), CPos.New(57,17), CPos.New(57,16), CPos.New(57,15), CPos.New(57,14) }
|
||||||
|
|
||||||
|
Start = function()
|
||||||
|
Reinforcements.Reinforce(USSR, { "mcv" }, { MCVEntry.Location, DefaultCameraPosition.Location }, 5)
|
||||||
|
|
||||||
|
Utils.Do(Jeeps, function(jeep)
|
||||||
|
jeep.Patrol(JeepWaypoints, true, 125)
|
||||||
|
end)
|
||||||
|
|
||||||
|
Utils.Do(StartAttack, function(a)
|
||||||
|
IdleHunt(a)
|
||||||
|
end)
|
||||||
|
|
||||||
|
ChronoCam = Actor.Create("camera", true, { Owner = USSR, Location = Chronosphere.Location})
|
||||||
|
end
|
||||||
|
|
||||||
|
MissionTriggers = function()
|
||||||
|
Trigger.OnAllKilled(RadarSites, function()
|
||||||
|
USSR.MarkCompletedObjective(TakeDownRadar)
|
||||||
|
ChronoshiftAlliedUnits()
|
||||||
|
end)
|
||||||
|
|
||||||
|
Trigger.OnCapture(Chronosphere, function()
|
||||||
|
if not USSR.IsObjectiveCompleted(TakeDownRadar) then
|
||||||
|
Media.DisplayMessage(UserInterface.Translate("chrono-trap-triggered"), UserInterface.Translate("headquarters"))
|
||||||
|
Chronosphere.Kill()
|
||||||
|
else
|
||||||
|
USSR.MarkCompletedObjective(CaptureChronosphere)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
Trigger.OnKilled(Chronosphere, function()
|
||||||
|
USSR.MarkFailedObjective(CaptureChronosphere)
|
||||||
|
end)
|
||||||
|
|
||||||
|
local chronoTriggered
|
||||||
|
Trigger.OnEnteredFootprint(ChronoDemolitionTrigger, function(actor, id)
|
||||||
|
if actor.Owner == USSR and not chronoTriggered and not USSR.IsObjectiveCompleted(TakeDownRadar) then
|
||||||
|
Trigger.RemoveFootprintTrigger(id)
|
||||||
|
Media.DisplayMessage(UserInterface.Translate("chrono-trap-triggered"), UserInterface.Translate("headquarters"))
|
||||||
|
chronoTriggered = true
|
||||||
|
Chronosphere.Kill()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
Trigger.OnEnteredProximityTrigger(ChinookLZ.CenterPosition, WDist.FromCells(5), function(actor, id)
|
||||||
|
if actor.Owner == USSR and actor.Type == "harv" then
|
||||||
|
Trigger.RemoveProximityTrigger(id)
|
||||||
|
SendChinook()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
local oreAttackTriggered
|
||||||
|
Trigger.OnEnteredFootprint(OreAttackFootprintTrigger, function(actor, id)
|
||||||
|
if actor.Owner == USSR and not oreAttackTriggered then
|
||||||
|
Trigger.RemoveProximityTrigger(id)
|
||||||
|
oreAttackTrigger = true
|
||||||
|
|
||||||
|
Utils.Do(OreAttackers, function(a)
|
||||||
|
if not a.IsDead then
|
||||||
|
IdleHunt(a)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
ChronoshiftAlliedUnits = function()
|
||||||
|
if Chronosphere.IsDead then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local cells = Utils.ExpandFootprint({ ChronoshiftPoint.Location }, false)
|
||||||
|
local units = { }
|
||||||
|
for i = 1, #cells do
|
||||||
|
local unit = Actor.Create("2tnk", true, { Owner = Greece, Facing = Angle.North })
|
||||||
|
units[unit] = cells[i]
|
||||||
|
IdleHunt(unit)
|
||||||
|
end
|
||||||
|
Chronosphere.Chronoshift(units)
|
||||||
|
end
|
||||||
|
|
||||||
|
Tick = function()
|
||||||
|
Greece.Cash = 20000
|
||||||
|
|
||||||
|
if USSR.HasNoRequiredUnits() then
|
||||||
|
Greece.MarkCompletedObjective(AlliesObjective)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
WorldLoaded = function()
|
||||||
|
USSR = Player.GetPlayer("USSR")
|
||||||
|
Greece = Player.GetPlayer("Greece")
|
||||||
|
GoodGuy = Player.GetPlayer("GoodGuy")
|
||||||
|
|
||||||
|
InitObjectives(USSR)
|
||||||
|
|
||||||
|
AlliesObjective = AddPrimaryObjective(Greece, "")
|
||||||
|
TakeDownRadar = AddPrimaryObjective(USSR, "destroy-allied-radar-sites")
|
||||||
|
CaptureChronosphere = AddPrimaryObjective(USSR, "capture-the-chronosphere")
|
||||||
|
|
||||||
|
Camera.Position = DefaultCameraPosition.CenterPosition
|
||||||
|
Start()
|
||||||
|
MissionTriggers()
|
||||||
|
ActivateAI()
|
||||||
|
GreeceWarFactory.RallyPoint = WarFactoryRally.Location
|
||||||
|
if Difficulty == "hard" then
|
||||||
|
V2A.Destroy()
|
||||||
|
V2B.Destroy()
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -33,6 +33,7 @@ Soviet Campaign:
|
|||||||
soviet-11a
|
soviet-11a
|
||||||
soviet-11b
|
soviet-11b
|
||||||
soviet-13a
|
soviet-13a
|
||||||
|
soviet-13b
|
||||||
Counterstrike Allied Missions:
|
Counterstrike Allied Missions:
|
||||||
sarin-gas-1-crackdown
|
sarin-gas-1-crackdown
|
||||||
sarin-gas-2-down-under
|
sarin-gas-2-down-under
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
Metadata:
|
Metadata:
|
||||||
Title: Red Alert
|
Title: Red Alert
|
||||||
Version: {DEV_VERSION}
|
Version: release-20231010
|
||||||
Website: https://www.openra.net
|
Website: https://www.openra.net
|
||||||
WebIcon32: https://www.openra.net/images/icons/ra_32x32.png
|
WebIcon32: https://www.openra.net/images/icons/ra_32x32.png
|
||||||
WindowTitle: OpenRA - Red Alert
|
WindowTitle: OpenRA - Red Alert
|
||||||
@@ -40,7 +40,7 @@ Packages:
|
|||||||
|
|
||||||
MapFolders:
|
MapFolders:
|
||||||
ra|maps: System
|
ra|maps: System
|
||||||
~^SupportDir|maps/ra/{DEV_VERSION}: User
|
~^SupportDir|maps/ra/release-20231010: User
|
||||||
|
|
||||||
Rules:
|
Rules:
|
||||||
ra|rules/misc.yaml
|
ra|rules/misc.yaml
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
Metadata:
|
Metadata:
|
||||||
Title: Tiberian Sun
|
Title: Tiberian Sun
|
||||||
Version: {DEV_VERSION}
|
Version: release-20231010
|
||||||
Website: https://www.openra.net
|
Website: https://www.openra.net
|
||||||
WebIcon32: https://www.openra.net/images/icons/ts_32x32.png
|
WebIcon32: https://www.openra.net/images/icons/ts_32x32.png
|
||||||
WindowTitle: OpenRA - Tiberian Sun
|
WindowTitle: OpenRA - Tiberian Sun
|
||||||
@@ -52,7 +52,7 @@ Packages:
|
|||||||
|
|
||||||
MapFolders:
|
MapFolders:
|
||||||
ts|maps: System
|
ts|maps: System
|
||||||
~^SupportDir|maps/ts/{DEV_VERSION}: User
|
~^SupportDir|maps/ts/release-20231010: User
|
||||||
|
|
||||||
Rules:
|
Rules:
|
||||||
ts|rules/ai.yaml
|
ts|rules/ai.yaml
|
||||||
|
|||||||
Reference in New Issue
Block a user