Compare commits

72 Commits

Author SHA1 Message Date
hacker
50277c59d7 Updated version
Some checks failed
Continuous Integration / Linux (.NET 6.0) (push) Has been cancelled
Continuous Integration / Linux (mono) (push) Has been cancelled
Continuous Integration / Windows (.NET 6.0) (push) Has been cancelled
2025-11-02 03:08:22 +00:00
hacker
2a13752203 Added Dockerfile 2025-11-02 03:05:27 +00:00
hacker
8ce3ea5f2c Log chat messages on the server 2025-11-02 03:05:19 +00:00
Gustas
b4f3d8ae02 Increase perf for parsing remote maps 2025-03-30 18:21:18 +01:00
Gustas
d70c30763b Add a backup for unloaded bots 2025-03-30 18:17:26 +01:00
Gustas
27297a1ba9 Make lobby names dynamic, incase players don't have maps 2025-03-30 18:17:19 +01:00
Gustas
2890ccf2b1 Fix game browser tooltips being untranslated 2025-03-30 18:17:14 +01:00
Gustas
f9f1889112 Fix custom bot names being untranslated in lobby 2025-03-30 10:21:09 +01:00
Gustas
d9c5821329 Fix resource area calculation 2025-03-29 16:54:56 +02:00
Gustas
7fc415de19 Fix rallypoints not showing full target lines 2025-03-29 16:42:13 +02:00
Gustas
ec01f63317 Add missing dispose calls 2025-03-29 13:58:24 +00:00
Gustas
c9ac702217 Fix camera being in an incorrect position of first frame 2025-03-28 21:18:38 +00:00
Gustas
1913c608a6 Fix telemetry data description 2025-03-28 21:13:40 +00:00
Gustas
94ac6cc400 Fix alignment regressions 2025-03-28 21:13:32 +00:00
JovialFeline
97d9520bf0 Get Fluent bot names in server list 2025-03-27 23:05:59 +02:00
N.N
e7300cc430 Fix wrong Spread in Warheads
(cherry picked from commit e78ab81b26)
2025-03-23 16:11:40 +02:00
Paul Chote
677d7e47a4 Work around Raspberry Pi GLSL bug.
(cherry picked from commit e8391de36a)
2025-03-23 15:46:01 +02:00
Matthias Mailänder
5f287d38ab Avoid a thirdparty action. 2025-03-18 14:58:23 +02:00
N.N
3a00d5a679 Fix crush anim 2025-03-18 14:27:00 +02:00
Matthias Mailänder
d5d43ee97c Avoid thirdparty action. 2025-03-18 14:18:39 +02:00
Paul Chote
fd12ef1535 Fix FlashPostProcessEffect channel order. 2025-03-18 14:11:29 +02:00
RoosterDragon
de8317d9b8 Harvesters prefer ore near to the refinery.
When searching for ore, harvesters will prefer ore close to their refinery, the dockPos. However this only works when this is set, and is was not being set when expected. Fixing this prevents harvesters moving in straight lines, particularly when the ore is to the left of the refinery, which is the default path search direction.

Fixes a regression from d0974cfdd2.
2025-03-16 10:39:47 +00:00
Gustas
b953f4a40d Fix a potential crash when cancelling capture 2025-03-16 10:29:48 +00:00
Gustas
b565f0903a Fix tree husks on jungle tileset 2025-03-16 10:26:29 +00:00
N.N
da71a8271d Fix frame on 1141 tile 2025-03-12 16:48:39 +02:00
Gustas
54558169e1 Additional punctuation fixup 2025-03-03 21:02:01 +00:00
test1232156
bcb953309a Remove unnecessary punctuation from descriptions 2025-03-02 18:58:41 +02:00
Paul Chote
a371826613 Fix .NET 6 CI false positives. 2025-02-28 21:00:02 +01:00
JovialFeline
82ee3d779c Fix soviet-06 harvester production crash 2025-02-26 23:40:44 +02:00
Paul Chote
4ea0e70589 Sanitize saved skirmish factions. 2025-02-22 15:32:06 +02:00
Gustas
3f4e4db836 vs seems more standard 2025-02-19 21:01:44 +00:00
Gustas
85020bc2af Adjust to a 4 space standard 2025-02-19 20:55:30 +00:00
Gustas
70bb01a2c7 Tooltips should not have punctuation 2025-02-16 14:12:44 +02:00
Gustas
38d4719073 Re-add a mini lobby options menu to missionbrowser 2025-02-16 12:41:29 +01:00
RoosterDragon
aebb97967d Fix a bad comparison against Target.Invalid.
Target.Invalid acts like a NaN, and will not compare equal with itself. Compare against the TargetType instead, which performs the intended comparison.
2025-02-15 13:22:35 +00:00
Gustas
97f81d0aa4 Update SDL to 2.32.0 2025-02-15 13:19:59 +00:00
JovialFeline
2030c17a8b Add Lua documentation details 2025-02-08 14:02:27 +02:00
Gustas
b6dcd8d9af Fix bot harvesters crashing 2025-02-01 20:36:33 +00:00
Gustas
9a78f22e86 Match the check inside .CenterPosition 2025-02-01 20:33:23 +00:00
Gustas
19ca3d610b Allow tileset to be entered in lower case 2025-02-01 20:23:04 +00:00
Gustas
dd74d3572a Fix utility commands losing data 2025-02-01 20:23:00 +00:00
JovialFeline
ad7e5d62cd Fix unrepairable desert bridge 2025-01-30 14:10:15 +02:00
JovialFeline
ece7547695 D2k shellmap: Hide pickup indicators 2025-01-28 19:04:14 +02:00
JovialFeline
944bc7667a Fix some campaign Fluent keys 2025-01-28 18:57:43 +02:00
JovialFeline
0ac28e1f5f Restore StartGameNotification in missions 2025-01-28 16:23:29 +02:00
JovialFeline
ca3a57289e Hide owner row for D2k special mission Outposts 2025-01-28 16:11:40 +02:00
JovialFeline
a4641be7e7 Fix ordos-04 to use internal bot names 2025-01-16 13:09:38 +02:00
Paul Chote
0651308210 Only expose Facing for actors that define a facing. 2025-01-15 18:07:31 +02:00
Paul Chote
cac6aebb6c Only expose Location and CenterPosition for actors that occupy space. 2025-01-15 18:07:31 +02:00
JovialFeline
0baddf46e2 Correct nod-06c capture objective 2025-01-15 17:39:49 +02:00
Gustas
e0d893b736 Raname GrantConditionOnDeployWithCharge to GrantChargedConditionOnToggle
Also allow deploying without cancelling current activity, and make saboteurs use it

(cherry picked from commit 8b4478260e)
2024-12-27 16:38:07 +02:00
Gustas
2ccfa425b2 Cancel movement in invalid states
(cherry picked from commit d9cfb2c278)
2024-12-27 16:31:05 +02:00
Gustas
52102da839 Fix harvesters refusing to queue dock orders whenn empty
(cherry picked from commit 1904012604)
2024-12-27 16:30:58 +02:00
test1232156
fc08f18663 Fix translation key
Co-Authored-By: Gustas <37534529+punkpun@users.noreply.github.com>
2024-12-21 19:30:50 +02:00
Pavel Penev
cc064d0dad Added descriptions to ContentInstallerFileSystemLoader
(cherry picked from commit 955bf96a84)
2024-12-21 16:04:36 +02:00
Pavel Penev
8b997a5656 Just some package definition reordering
Mostly superficial, but in case of file name collisions we want the mod-specific files to override the common ones.

(cherry picked from commit f210b39bbc)
2024-12-21 16:04:32 +02:00
Pavel Penev
7d8fff535d Added missing D2k packages
(cherry picked from commit 59c0c908cc)
2024-12-21 16:04:28 +02:00
Pavel Penev
0855a9e5bf Fixed whitespace issues in mod.yaml files
An oversight from PR 21598.

(cherry picked from commit 640aa6545b)
2024-12-21 16:04:24 +02:00
Gustas
3fa2b9fe79 Add delete hotkey
(cherry picked from commit 269542c4b0)
2024-12-20 23:57:55 +02:00
Gustas
c561509c82 Add delete button to the map editor
(cherry picked from commit f820adab51)
2024-12-20 23:57:50 +02:00
Gustas
0566b6f787 Update extractors to preserve indentation
(cherry picked from commit 48d5b3c8d9)
2024-12-20 22:00:58 +02:00
Gustas
e2821933a4 Fixup new lines in encyclopedias
(cherry picked from commit 81875a1f3f)
2024-12-20 22:00:57 +02:00
Gustas
34c29f872a Fix unit descriptions having incorrect indentation
(cherry picked from commit 0bd139b061)
2024-12-20 22:00:56 +02:00
test1232156
2192a6b4f9 Encyclopedia fixup
(cherry picked from commit 9719c1a3b0)
2024-12-20 22:00:54 +02:00
RoosterDragon
68ad9163ff Fix BaseBuilderBotModule.LocomotorsForProducibles.
Account for per-actor production (e.g. ProductionQueue) and per-player production (e.g. ClassicProductionQueue). This requires resolving the Production and ProductionQueue traits on both the producing actor, and the owning player actor.

When setting rally points, check the actor didn't die first.
2024-12-19 19:36:39 +02:00
Gustas
5f6eecc1f6 Reduce the size of battlefield news when messages are short
(cherry picked from commit 2a1eeae7ca)
2024-12-19 18:18:26 +02:00
Gustas
5edca87622 Fix Package 'NuGet.CommandLine' 4.4.1 has a known moderate severity vulnerability
(cherry picked from commit 43325fdee7)
2024-12-12 16:01:35 +02:00
Mustafa Alperen Seki
fa01c24f23 Fix broken condition support for InstantlyRepairable. 2024-12-12 00:30:11 +02:00
Pavel Penev
593c2735ce Revert "Restore threaded renderer on Windows in windowed mode."
This reverts commit 874fa67036.
2024-12-12 00:26:05 +02:00
test1232156
48e72c62f3 Translation key fixes
Co-Authored-By: Gustas <37534529+punkpun@users.noreply.github.com>
2024-12-04 22:10:37 +02:00
Matthias Mailänder
f316d4c757 Update dependency model to fix CVE warnings. 2024-12-02 21:31:01 +02:00
RoosterDragon
d6dba3e545 Fix Animation shadow to account for height.
If a unit is above the terrain, the shadow shouldn't display directly underneath the unit, it should take the height into account and display further down. This fix uses the same adjustment as applied by the WithShadow trait.
2024-11-30 14:45:40 +02:00
140 changed files with 3080 additions and 1922 deletions

View File

@@ -336,7 +336,7 @@ dotnet_diagnostic.IDE0305.severity = silent # Requires C# 12
#dotnet_style_readonly_field = true
dotnet_diagnostic.IDE0044.severity = warning
## Language keyword vs. framework types preferences
## Language keyword vs framework types preferences
# IDE0049 Use language keywords instead of framework type names for type references
#dotnet_style_predefined_type_for_locals_parameters_members = true

View File

@@ -14,6 +14,9 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Remove System .NET
run: sudo apt-get remove -y dotnet*
- name: Clone Repository
uses: actions/checkout@v4

View File

@@ -15,73 +15,56 @@ jobs:
runs-on: ubuntu-22.04
if: github.repository == 'openra/openra'
steps:
- name: Download Packages
- name: Download Butler
env:
BUTLER_API_KEY: ${{ secrets.BUTLER_CREDENTIALS }}
run: |
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}-x64.exe"
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}-x64-winportable.zip" -O "OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip"
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}.dmg"
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Dune-2000-x86_64.AppImage"
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Red-Alert-x86_64.AppImage"
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Tiberian-Dawn-x86_64.AppImage"
wget -q "https://raw.githubusercontent.com/${{ github.repository }}/${{ github.event.inputs.tag }}/packaging/.itch.toml"
zip -u "OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip" .itch.toml
wget -cq -O butler-linux-amd64.zip https://broth.itch.ovh/butler/linux-amd64/LATEST/archive/default
unzip butler-linux-amd64.zip
rm butler-linux-amd64.zip
chmod +x butler
./butler -V
./butler login
- name: Publish Windows Installer
uses: josephbmanley/butler-publish-itchio-action@master
env:
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
CHANNEL: win
ITCH_GAME: openra
ITCH_USER: openra
VERSION: ${{ github.event.inputs.tag }}
PACKAGE: OpenRA-${{ github.event.inputs.tag }}-x64.exe
BUTLER_API_KEY: ${{ secrets.BUTLER_CREDENTIALS }}
run: |
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}-x64.exe"
./butler push "OpenRA-${{ github.event.inputs.tag }}-x64.exe" "openra/openra:win" --userversion ${{ github.event.inputs.tag }}
- name: Publish Windows Itch Bundle
uses: josephbmanley/butler-publish-itchio-action@master
env:
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
CHANNEL: itch
ITCH_GAME: openra
ITCH_USER: openra
VERSION: ${{ github.event.inputs.tag }}
PACKAGE: OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip
BUTLER_API_KEY: ${{ secrets.BUTLER_CREDENTIALS }}
run: |
wget -q "https://raw.githubusercontent.com/${{ github.repository }}/${{ github.event.inputs.tag }}/packaging/.itch.toml"
zip -u "OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip" .itch.toml
./butler push "OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip" "openra/openra:itch" --userversion ${{ github.event.inputs.tag }}
- name: Publish macOS Package
uses: josephbmanley/butler-publish-itchio-action@master
env:
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
CHANNEL: macos
ITCH_GAME: openra
ITCH_USER: openra
VERSION: ${{ github.event.inputs.tag }}
PACKAGE: OpenRA-${{ github.event.inputs.tag }}.dmg
BUTLER_API_KEY: ${{ secrets.BUTLER_CREDENTIALS }}
run: |
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}.dmg"
./butler push "OpenRA-${{ github.event.inputs.tag }}.dmg" "openra/openra:macos" --userversion ${{ github.event.inputs.tag }}
- name: Publish RA AppImage
uses: josephbmanley/butler-publish-itchio-action@master
env:
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
CHANNEL: linux-ra
ITCH_GAME: openra
ITCH_USER: openra
VERSION: ${{ github.event.inputs.tag }}
PACKAGE: OpenRA-Red-Alert-x86_64.AppImage
BUTLER_API_KEY: ${{ secrets.BUTLER_CREDENTIALS }}
run: |
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Red-Alert-x86_64.AppImage"
./butler push "OpenRA-Red-Alert-x86_64.AppImage" "openra/openra:linux-ra" --userversion ${{ github.event.inputs.tag }}
- name: Publish TD AppImage
uses: josephbmanley/butler-publish-itchio-action@master
env:
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
CHANNEL: linux-cnc
ITCH_GAME: openra
ITCH_USER: openra
VERSION: ${{ github.event.inputs.tag }}
PACKAGE: OpenRA-Tiberian-Dawn-x86_64.AppImage
BUTLER_API_KEY: ${{ secrets.BUTLER_CREDENTIALS }}
run: |
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Tiberian-Dawn-x86_64.AppImage"
./butler push "OpenRA-Tiberian-Dawn-x86_64.AppImage" "openra/openra:linux-cnc" --userversion ${{ github.event.inputs.tag }}
- name: Publish D2k AppImage
uses: josephbmanley/butler-publish-itchio-action@master
env:
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
CHANNEL: linux-d2k
ITCH_GAME: openra
ITCH_USER: openra
VERSION: ${{ github.event.inputs.tag }}
PACKAGE: OpenRA-Dune-2000-x86_64.AppImage
BUTLER_API_KEY: ${{ secrets.BUTLER_CREDENTIALS }}
run: |
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Dune-2000-x86_64.AppImage"
./butler push "OpenRA-Dune-2000-x86_64.AppImage" "openra/openra:linux-d2k" --userversion ${{ github.event.inputs.tag }}

View File

@@ -57,13 +57,11 @@ jobs:
./packaging/linux/buildpackage.sh "${GIT_TAG}" "${PWD}/build/linux"
- name: Upload Packages
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref }}
overwrite: true
file_glob: true
file: build/linux/*
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
gh release upload ${{ github.ref_name }} build/linux/*
macos:
name: macOS Disk Image
@@ -92,13 +90,11 @@ jobs:
./packaging/macos/buildpackage.sh "${GIT_TAG}" "${PWD}/build/macos"
- name: Upload Package
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref }}
overwrite: true
file_glob: true
file: build/macos/*
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
gh release upload ${{ github.ref_name }} build/macos/*
windows:
name: Windows Installers
@@ -124,10 +120,8 @@ jobs:
./packaging/windows/buildpackage.sh "${GIT_TAG}" "${PWD}/build/windows"
- name: Upload Packages
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref }}
overwrite: true
file_glob: true
file: build/windows/*
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
gh release upload ${{ github.ref_name }} build/windows/*

29
Dockerfile Normal file
View 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"]

View File

@@ -22,6 +22,7 @@ namespace OpenRA.Graphics
public string Name { get; private set; }
public bool IsDecoration { get; set; }
readonly Map map;
readonly SequenceSet sequences;
readonly Func<WAngle> facingFunc;
readonly Func<bool> paused;
@@ -43,6 +44,7 @@ namespace OpenRA.Graphics
public Animation(World world, string name, Func<WAngle> facingFunc, Func<bool> paused)
{
map = world.Map;
sequences = world.Map.Sequences;
Name = name.ToLowerInvariant();
this.facingFunc = facingFunc;
@@ -65,8 +67,10 @@ namespace OpenRA.Graphics
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
if (shadow != null)
{
var height = map.DistanceAboveTerrain(pos).Length;
var shadowRenderable = new SpriteRenderable(
shadow, pos, offset, CurrentSequence.ShadowZOffset + zOffset, palette,
shadow, pos, offset - new WVec(0, 0, height), CurrentSequence.ShadowZOffset + zOffset + height, palette,
CurrentSequence.Scale, 1f, float3.Ones, tintModifiers,
true, rotation);
return new IRenderable[] { shadowRenderable, imageRenderable };

View File

@@ -149,6 +149,10 @@ namespace OpenRA.Graphics
var buffer = data;
ReleaseBuffer();
// We aren't commiting data to the GPU, so let's not delete our data.
if (Game.Renderer == null)
return false;
// Only transfer if the destination has no data that would be lost by overwriting.
if (buffer != null && destination.data == null && destination.texture == null)
{

View File

@@ -14,6 +14,7 @@ using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using OpenRA.FileSystem;
@@ -244,39 +245,61 @@ namespace OpenRA
{
var client = HttpClientFactory.Create();
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
var buffer = new StringBuilder();
// Limit each query to 50 maps at a time to avoid request size limits
for (var i = 0; i < queryUids.Count; i += 50)
{
var batchUids = queryUids.Skip(i).Take(50).ToList();
var url = repositoryUrl + "hash/" + string.Join(",", batchUids) + "/yaml";
try
using (new PerfTimer("RemoteMapDetails"))
{
var httpResponseMessage = await client.GetAsync(url);
var result = await httpResponseMessage.Content.ReadAsStreamAsync();
var yaml = MiniYaml.FromStream(result, url, stringPool: stringPool);
foreach (var kv in yaml)
previews[kv.Key].UpdateRemoteSearch(MapStatus.DownloadAvailable, kv.Value, modData.Manifest.MapCompatibility, mapDetailsReceived);
foreach (var uid in batchUids)
try
{
var p = previews[uid];
if (p.Status != MapStatus.DownloadAvailable)
p.UpdateRemoteSearch(MapStatus.Unavailable, null, null);
await using (var resultStream = await client.GetStreamAsync(url))
{
using (var resultReader = new StreamReader(resultStream))
{
while (true)
{
var line = await resultReader.ReadLineAsync();
if (line == null || !line.StartsWith('\t'))
{
var yaml = MiniYaml.FromString(buffer.ToString(), url, stringPool: stringPool);
buffer.Clear();
foreach (var kv in yaml)
previews[kv.Key].UpdateRemoteSearch(MapStatus.DownloadAvailable, kv.Value, modData.Manifest.MapCompatibility, mapDetailsReceived);
if (line == null)
break;
}
buffer.Append(line);
buffer.Append('\n');
}
}
}
foreach (var uid in batchUids)
{
var p = previews[uid];
if (p.Status != MapStatus.DownloadAvailable)
p.UpdateRemoteSearch(MapStatus.Unavailable, null, null);
}
}
}
catch (Exception e)
{
Log.Write("debug", "Remote map query failed with error:");
Log.Write("debug", e);
Log.Write("debug", $"URL was: {url}");
foreach (var uid in batchUids)
catch (Exception e)
{
var p = previews[uid];
p.UpdateRemoteSearch(MapStatus.Unavailable, null, null);
mapQueryFailed?.Invoke(p);
Log.Write("debug", "Remote map query failed with error:");
Log.Write("debug", e);
Log.Write("debug", $"URL was: {url}");
foreach (var uid in batchUids)
{
var p = previews[uid];
p.UpdateRemoteSearch(MapStatus.Unavailable, null, null);
mapQueryFailed?.Invoke(p);
}
}
}
}

View File

@@ -249,11 +249,26 @@ namespace OpenRA
/// </summary>
public string GetMessage(string key, object[] args = null)
{
// PERF: instead of loading mod level strings per each MapPreview, reuse the already loaded one in FluentProvider.
if (FluentProvider.TryGetModMessage(key, out var message, args))
if (TryGetMessage(key, out var message, args))
return message;
return innerData.FluentBundle?.GetMessage(key, args) ?? key;
return key;
}
/// <summary>
/// Functionality mirrors <see cref="FluentProvider.TryGetMessage"/>, except instead of using
/// loaded <see cref="Map"/>'s fluent bundle as backup, we use this <see cref="MapPreview"/>'s.
/// </summary>
public bool TryGetMessage(string key, out string message, object[] args = null)
{
// PERF: instead of loading mod level strings per each MapPreview, reuse the already loaded one in FluentProvider.
if (FluentProvider.TryGetModMessage(key, out message, args))
return true;
if (innerData.FluentBundle == null)
return false;
return innerData.FluentBundle.TryGetMessage(key, out message, args);
}
Sprite minimap;

View File

@@ -3,7 +3,7 @@
<RootNamespace>OpenRA</RootNamespace>
</PropertyGroup>
<ItemGroup Condition="'$(MSBuildRuntimeType)'!='Mono'">
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="6.0.2" />
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
</ItemGroup>
<ItemGroup Condition="'$(MSBuildRuntimeType)'=='Mono'">

View File

@@ -1005,9 +1005,12 @@ namespace OpenRA.Server
}
case "Chat":
{
if (!IsMultiplayer || !playerMessageTracker.IsPlayerAtFloodLimit(conn))
DispatchOrdersToClients(conn, 0, o.Serialize());
{
var client = GetClient(conn);
Console.WriteLine($"[{DateTime.Now.ToString(Settings.TimestampFormat)}] {client.Name}: {o.TargetString}");
if (Type == ServerType.Local || !playerMessageTracker.IsPlayerAtFloodLimit(conn))
DispatchOrdersToClients(conn, 0, o.Serialize());
break;
}

View File

@@ -23,7 +23,8 @@ namespace OpenRA.Mods.Cnc.Scripting
public ChronosphereProperties(ScriptContext context, Actor self)
: base(context, self) { }
[Desc("Chronoshift a group of actors. A duration of 0 will teleport the actors permanently.")]
[Desc("Chronoshift a group of actors. A duration of 0 will teleport the actors permanently. " +
"If a given cell is unexplored for this power's owner, the closest valid cell will be used instead.")]
public void Chronoshift([ScriptEmmyTypeOverride("{ [actor]: cpos }")] LuaTable unitLocationPairs, int duration = 0, bool killCargo = false)
{
foreach (var kv in unitLocationPairs)

View File

@@ -23,7 +23,7 @@ namespace OpenRA.Mods.Cnc.Traits
public override object Create(ActorInitializer init) { return new ChronoVortexRenderer(init.Self); }
}
public sealed class ChronoVortexRenderer : IRenderPostProcessPass
public sealed class ChronoVortexRenderer : IRenderPostProcessPass, INotifyActorDisposing
{
readonly Renderer renderer;
readonly IShader shader;
@@ -105,5 +105,11 @@ namespace OpenRA.Mods.Cnc.Traits
vortices.Clear();
}
void INotifyActorDisposing.Disposing(Actor self)
{
vortexSheet.Dispose();
vortexBuffer.Dispose();
}
}
}

View File

@@ -152,7 +152,7 @@ namespace OpenRA.Mods.Common.Activities
void CancelCapture()
{
manager.CancelCapture(enterCaptureManager);
manager.CancelCapture(enterActor, enterCaptureManager);
}
}
}

View File

@@ -173,7 +173,8 @@ namespace OpenRA.Mods.Common.Activities
// Prioritise search by these locations in this order: lastHarvestedCell -> lastLinkedDock -> self.
CPos searchFromLoc;
int searchRadius;
WPos? dockPos = null;
var dockPos = harv.DockClientManager?.LastReservedHost?.DockPosition;
if (lastHarvestedCell.HasValue)
{
searchRadius = harvInfo.SearchFromHarvesterRadius;
@@ -182,12 +183,8 @@ namespace OpenRA.Mods.Common.Activities
else
{
searchRadius = harvInfo.SearchFromProcRadius;
var dock = harv.DockClientManager?.LastReservedHost;
if (dock != null)
{
dockPos = dock.DockPosition;
if (dockPos != null)
searchFromLoc = self.World.Map.CellContaining(dockPos.Value);
}
else
searchFromLoc = self.Location;
}

View File

@@ -29,6 +29,8 @@ namespace OpenRA.Mods.Common.Activities
readonly bool forceEnter;
readonly bool ignoreOccupancy;
bool dockingCancelled;
public MoveToDock(Actor self, Actor dockHostActor = null, IDockHost dockHost = null,
bool forceEnter = false, bool ignoreOccupancy = false, Color? dockLineColor = null)
{
@@ -44,17 +46,25 @@ namespace OpenRA.Mods.Common.Activities
protected override void OnFirstRun(Actor self)
{
if (IsCanceling || dockClient.IsTraitDisabled)
if (dockClient.IsTraitDisabled)
return;
// We were ordered to dock to an actor but host was unspecified.
if (dockHostActor != null && dockHost == null)
{
if (dockHostActor.IsDead || !dockHostActor.IsInWorld)
{
dockingCancelled = true;
return;
}
var link = dockClient.AvailableDockHosts(dockHostActor, default, forceEnter, ignoreOccupancy)
.ClosestDock(self, dockClient);
if (link.HasValue)
dockHost = link.Value.Trait;
else
dockingCancelled = true;
}
}
@@ -63,7 +73,7 @@ namespace OpenRA.Mods.Common.Activities
if (IsCanceling)
return true;
if (dockClient.IsTraitDisabled)
if (dockingCancelled || dockClient.IsTraitDisabled)
{
Cancel(self, true);
return true;

View File

@@ -11,7 +11,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Mods.Common.EditorBrushes;
using OpenRA.Mods.Common.Graphics;
using OpenRA.Mods.Common.Traits;
using OpenRA.Widgets;
@@ -87,6 +89,12 @@ namespace OpenRA.Mods.Common.Widgets
return ((long)pixelDistance << 32) + worldZPosition;
}
public void DeleteSelection(MapBlitFilters filters)
{
if (Selection.Area != null)
editorActionManager.Add(new DeleteAreaAction(world.Map, filters, Selection.Area, resourceLayer, actorLayer));
}
public void ClearSelection(bool updateSelectedTab = false)
{
if (Selection.HasSelection)
@@ -335,6 +343,114 @@ namespace OpenRA.Mods.Common.Widgets
}
}
sealed class DeleteAreaAction : IEditorAction
{
[FluentReference("x", "y", "width", "height")]
const string RemovedArea = "notification-removed-area";
public string Text { get; }
readonly EditorBlitSource editorBlitSource;
readonly MapBlitFilters blitFilters;
readonly IResourceLayer resourceLayer;
readonly EditorActorLayer editorActorLayer;
readonly CellRegion area;
readonly Map map;
public DeleteAreaAction(Map map, MapBlitFilters blitFilters, CellRegion area, IResourceLayer resourceLayer, EditorActorLayer editorActorLayer)
{
this.map = map;
this.blitFilters = blitFilters;
this.resourceLayer = resourceLayer;
this.editorActorLayer = editorActorLayer;
this.area = area;
editorBlitSource = EditorBlit.CopyRegionContents(map, editorActorLayer, resourceLayer, area, blitFilters);
Text = FluentProvider.GetMessage(RemovedArea,
"x", area.TopLeft.X,
"y", area.TopLeft.Y,
"width", area.BottomRight.X - area.TopLeft.X,
"height", area.BottomRight.Y - area.TopLeft.Y);
}
public void Execute()
{
Do();
}
public void Do()
{
if (blitFilters.HasFlag(MapBlitFilters.Actors))
{
// Clear any existing actors in the paste cells.
foreach (var regionActor in editorActorLayer.PreviewsInCellRegion(area.CellCoords).ToList())
editorActorLayer.Remove(regionActor);
}
foreach (var tileKeyValuePair in editorBlitSource.Tiles)
{
var position = tileKeyValuePair.Key;
if (!map.Tiles.Contains(position))
continue;
// Clear any existing resources.
if (resourceLayer != null && blitFilters.HasFlag(MapBlitFilters.Resources))
resourceLayer.ClearResources(position);
if (blitFilters.HasFlag(MapBlitFilters.Terrain))
{
map.Tiles[position] = map.Rules.TerrainInfo.DefaultTerrainTile;
map.Height[position] = 0;
}
}
}
public void Undo()
{
foreach (var tileKeyValuePair in editorBlitSource.Tiles)
{
var position = tileKeyValuePair.Key;
if (!map.Tiles.Contains(position))
continue;
var tile = tileKeyValuePair.Value;
var resourceLayerContents = tile.ResourceLayerContents;
if (blitFilters.HasFlag(MapBlitFilters.Terrain))
{
map.Tiles[position] = tile.TerrainTile;
map.Height[position] = tile.Height;
}
if (blitFilters.HasFlag(MapBlitFilters.Resources) &&
resourceLayerContents.HasValue &&
!string.IsNullOrWhiteSpace(resourceLayerContents.Value.Type))
resourceLayer.AddResource(resourceLayerContents.Value.Type, position, resourceLayerContents.Value.Density);
}
if (blitFilters.HasFlag(MapBlitFilters.Actors))
{
// Create copies of the original actors, update their locations, and place.
foreach (var actorKeyValuePair in editorBlitSource.Actors)
{
var copy = actorKeyValuePair.Value.Export();
var locationInit = copy.GetOrDefault<LocationInit>();
if (locationInit != null)
{
if (!map.Tiles.Contains(locationInit.Value))
continue;
copy.RemoveAll<LocationInit>();
copy.Add(new LocationInit(locationInit.Value));
}
editorActorLayer.Add(copy);
}
}
}
}
sealed class RemoveSelectedActorAction : IEditorAction
{
[FluentReference("name", "id")]

View File

@@ -13,23 +13,28 @@ using System.Collections.Generic;
namespace OpenRA.Mods.Common.FileSystem
{
[Desc("A file system that loads game assets installed by the user into their support directory.")]
public class ContentInstallerFileSystemLoader : IFileSystemLoader, IFileSystemExternalContent
{
[FieldLoader.Require]
[Desc("Mod to use for content installation.")]
public readonly string ContentInstallerMod = null;
[FieldLoader.Require]
public readonly Dictionary<string, string> Packages = null;
[Desc("A list of mod-provided packages. Anything required to display the initial load screen must be listed here.")]
public readonly Dictionary<string, string> SystemPackages = null;
[Desc("A list of user-installed packages. If missing (and not marked as optional), these will trigger the content installer.")]
public readonly Dictionary<string, string> ContentPackages = null;
public readonly Dictionary<string, string> ContentFiles = null;
[Desc("Files that aren't mounted as packages, but still need to trigger the content installer if missing.")]
public readonly Dictionary<string, string> RequiredContentFiles = null;
bool contentAvailable = true;
bool isContentAvailable = true;
public void Mount(OpenRA.FileSystem.FileSystem fileSystem, ObjectCreator objectCreator)
{
foreach (var kv in Packages)
foreach (var kv in SystemPackages)
fileSystem.Mount(kv.Key, kv.Value);
if (ContentPackages != null)
@@ -42,23 +47,23 @@ namespace OpenRA.Mods.Common.FileSystem
}
catch
{
contentAvailable = false;
isContentAvailable = false;
}
}
}
if (ContentFiles != null)
foreach (var kv in ContentFiles)
if (RequiredContentFiles != null)
foreach (var kv in RequiredContentFiles)
if (!fileSystem.Exists(kv.Key))
contentAvailable = false;
isContentAvailable = false;
}
bool IFileSystemExternalContent.InstallContentIfRequired(ModData modData)
{
if (!contentAvailable)
if (!isContentAvailable)
Game.InitializeMod(ContentInstallerMod, new Arguments());
return !contentAvailable;
return !isContentAvailable;
}
}
}

View File

@@ -3,6 +3,7 @@
<ProjectReference Include="..\OpenRA.Game\OpenRA.Game.csproj">
<Private>False</Private>
</ProjectReference>
<PackageReference Include="NuGet.CommandLine" Version="6.12.1" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="OpenRA-FuzzyLogicLibrary" Version="1.0.1" />
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />

View File

@@ -82,7 +82,8 @@ namespace OpenRA.Mods.Common.Scripting
GetScriptTriggers(actor).RegisterCallback(Trigger.OnIdle, func, Context);
}
[Desc("Call a function when the actor is damaged. The callback " +
[Desc("Call a function when the actor is damaged. " +
"Repairs or other negative damage can activate this trigger. The callback " +
"function will be called as func(self: actor, attacker: actor, damage: integer).")]
public void OnDamaged(Actor actor, [ScriptEmmyTypeOverride("fun(self: actor, attacker: actor, damage: integer)")] LuaFunction func)
{
@@ -130,7 +131,8 @@ namespace OpenRA.Mods.Common.Scripting
GetScriptTriggers(a).OnKilledInternal += OnMemberKilled;
}
[Desc("Call a function when one of the actors in a group is killed. The callback " +
[Desc("Call a function when one of the actors in a group is killed. " +
"This trigger is only called once. The callback " +
"function will be called as func(killed: actor).")]
public void OnAnyKilled(Actor[] actors, [ScriptEmmyTypeOverride("fun(killed: actor)")] LuaFunction func)
{
@@ -164,12 +166,12 @@ namespace OpenRA.Mods.Common.Scripting
[Desc("Call a function when this actor produces another actor. " +
"The callback function will be called as func(producer: actor, produced: actor).")]
public void OnProduction(Actor actors, [ScriptEmmyTypeOverride("fun(producer: actor, produced: actor)")] LuaFunction func)
public void OnProduction(Actor actor, [ScriptEmmyTypeOverride("fun(producer: actor, produced: actor)")] LuaFunction func)
{
if (actors == null)
throw new NullReferenceException(nameof(actors));
if (actor == null)
throw new NullReferenceException(nameof(actor));
GetScriptTriggers(actors).RegisterCallback(Trigger.OnProduction, func, Context);
GetScriptTriggers(actor).RegisterCallback(Trigger.OnProduction, func, Context);
}
[Desc("Call a function when any actor produces another actor. The callback " +
@@ -304,15 +306,16 @@ namespace OpenRA.Mods.Common.Scripting
[Desc("Call a function when this actor is captured. The callback function " +
"will be called as func(self: actor, captor: actor, oldOwner: player, newOwner: player).")]
public void OnCapture(Actor actors, [ScriptEmmyTypeOverride("fun(self: actor, captor: actor, oldOwner: player, newOwner: player)")] LuaFunction func)
public void OnCapture(Actor actor, [ScriptEmmyTypeOverride("fun(self: actor, captor: actor, oldOwner: player, newOwner: player)")] LuaFunction func)
{
if (actors == null)
throw new NullReferenceException(nameof(actors));
if (actor == null)
throw new NullReferenceException(nameof(actor));
GetScriptTriggers(actors).RegisterCallback(Trigger.OnCapture, func, Context);
GetScriptTriggers(actor).RegisterCallback(Trigger.OnCapture, func, Context);
}
[Desc("Call a function when this actor is killed or captured. " +
"This trigger is only called once. " +
"The callback function will be called as func().")]
public void OnKilledOrCaptured(Actor actor, [ScriptEmmyTypeOverride("fun()")] LuaFunction func)
{
@@ -345,6 +348,7 @@ namespace OpenRA.Mods.Common.Scripting
}
[Desc("Call a function when all of the actors in a group have been killed or captured. " +
"This trigger is only called once. " +
"The callback function will be called as func().")]
public void OnAllKilledOrCaptured(Actor[] actors, [ScriptEmmyTypeOverride("fun()")] LuaFunction func)
{

View File

@@ -80,7 +80,8 @@ namespace OpenRA.Mods.Common.Scripting
Self.World.Add(new FlashTarget(Self, color, 0.5f, count, interval, delay));
}
[Desc("The effective owner of the actor.")]
[Desc("The effective (displayed) owner of the actor. " +
"This may differ from the true owner in some cases, such as disguised actors.")]
public Player EffectiveOwner
{
get
@@ -96,7 +97,6 @@ namespace OpenRA.Mods.Common.Scripting
[ScriptPropertyGroup("General")]
public class GeneralProperties : ScriptActorProperties
{
readonly IFacing facing;
readonly AutoTarget autotarget;
readonly ScriptTags scriptTags;
readonly Tooltip[] tooltips;
@@ -104,30 +104,11 @@ namespace OpenRA.Mods.Common.Scripting
public GeneralProperties(ScriptContext context, Actor self)
: base(context, self)
{
facing = self.TraitOrDefault<IFacing>();
autotarget = self.TraitOrDefault<AutoTarget>();
scriptTags = self.TraitOrDefault<ScriptTags>();
tooltips = self.TraitsImplementing<Tooltip>().ToArray();
}
[Desc("The actor position in cell coordinates.")]
public CPos Location => Self.Location;
[Desc("The actor position in world coordinates.")]
public WPos CenterPosition => Self.CenterPosition;
[Desc("The direction that the actor is facing.")]
public WAngle Facing
{
get
{
if (facing == null)
throw new LuaException($"Actor '{Self}' doesn't define a facing");
return facing.Facing;
}
}
[ScriptActorPropertyActivity]
[Desc("Instantly moves the actor to the specified cell.")]
public void Teleport(CPos cell)
@@ -213,4 +194,32 @@ namespace OpenRA.Mods.Common.Scripting
return IsTaggable && scriptTags.HasTag(tag);
}
}
[ScriptPropertyGroup("General")]
public class LocationProperties : ScriptActorProperties, Requires<IOccupySpaceInfo>
{
public LocationProperties(ScriptContext context, Actor self)
: base(context, self) { }
[Desc("The actor position in cell coordinates.")]
public CPos Location => Self.Location;
[Desc("The actor position in world coordinates.")]
public WPos CenterPosition => Self.CenterPosition;
}
[ScriptPropertyGroup("General")]
public class FacingProperties : ScriptActorProperties, Requires<IFacingInfo>
{
readonly IFacing facing;
public FacingProperties(ScriptContext context, Actor self)
: base(context, self)
{
facing = self.Trait<IFacing>();
}
[Desc("The direction that the actor is facing.")]
public WAngle Facing => facing.Facing;
}
}

View File

@@ -68,7 +68,8 @@ namespace OpenRA.Mods.Common.Scripting
[ScriptActorPropertyActivity]
[Desc("Mark an objective as failed. This needs the objective ID returned " +
"by AddObjective as argument. Secondary objectives do not have any " +
"influence whatsoever on the outcome of the game.")]
"influence whatsoever on the outcome of the game. " +
"It is possible to mark a completed objective as a failure.")]
public void MarkFailedObjective(int id)
{
if (id < 0 || id >= mo.Objectives.Count)

View File

@@ -87,9 +87,6 @@ namespace OpenRA.Mods.Common.Server
[FluentReference("faction")]
const string InvalidFactionSelected = "notification-invalid-faction-selected";
[FluentReference("factions")]
const string SupportedFactions = "notification-supported-factions";
[FluentReference]
const string RequiresHost = "notification-requires-host";
@@ -609,9 +606,8 @@ namespace OpenRA.Mods.Common.Server
foreach (var c in server.LobbyInfo.Clients)
{
c.Faction = SanitizePlayerFaction(server, c.Faction, selectableFactions);
c.State = Session.ClientState.Invalid;
if (!selectableFactions.Contains(c.Faction))
c.Faction = "Random";
}
// Reassign players into new slots based on their old slots:
@@ -1055,15 +1051,13 @@ namespace OpenRA.Mods.Common.Server
if (server.LobbyInfo.Slots[targetClient.Slot].LockFaction)
return true;
var factions = server.Map.WorldActorInfo.TraitInfos<FactionInfo>()
.Where(f => f.Selectable).Select(f => f.InternalName)
.ToList();
var faction = parts[1];
if (!factions.Contains(faction))
var isValidFaction = server.Map.WorldActorInfo.TraitInfos<FactionInfo>()
.Any(f => f.Selectable && f.InternalName == client.Faction);
if (!isValidFaction)
{
server.SendFluentMessageTo(conn, InvalidFactionSelected, new object[] { "faction", faction });
server.SendFluentMessageTo(conn, SupportedFactions, new object[] { "factions", factions.JoinWith(", ") });
return true;
}
@@ -1438,6 +1432,11 @@ namespace OpenRA.Mods.Common.Server
}
}
public static string SanitizePlayerFaction(S server, string askedFaction, IEnumerable<string> validFactions)
{
return !validFactions.Contains(askedFaction) ? "Random" : askedFaction;
}
static string MissionBriefingOrDefault(S server)
{
var missionData = server.Map.WorldActorInfo.TraitInfoOrDefault<MissionDataInfo>();

View File

@@ -92,12 +92,18 @@ namespace OpenRA.Mods.Common.Server
}
}
var selectableFactions = server.Map.WorldActorInfo.TraitInfos<FactionInfo>()
.Where(f => f.Selectable)
.Select(f => f.InternalName)
.ToList();
var playerNode = nodes.NodeWithKeyOrDefault("Player");
if (playerNode != null)
{
var client = server.GetClient(conn);
SkirmishSlot.DeserializeToClient(playerNode.Value, client);
client.Color = LobbyCommands.SanitizePlayerColor(server, client.Color, client.Index);
client.Faction = LobbyCommands.SanitizePlayerFaction(server, client.Faction, selectableFactions);
}
var botsNode = nodes.NodeWithKeyOrDefault("Bots");
@@ -128,6 +134,8 @@ namespace OpenRA.Mods.Common.Server
if (client.Slot != null && !server.LobbyInfo.Slots[client.Slot].LockColor)
client.Color = LobbyCommands.SanitizePlayerColor(server, client.Color, client.Index);
client.Faction = LobbyCommands.SanitizePlayerFaction(server, client.Faction, selectableFactions);
server.LobbyInfo.Clients.Add(client);
S.SyncClientToPlayerReference(client, server.Map.Players.Players[client.Slot]);
}

View File

@@ -1325,9 +1325,11 @@ namespace OpenRA.Mods.Common.Traits
public override IEnumerable<TargetLineNode> TargetLineNodes(Actor self)
{
if (ChildActivity != null)
foreach (var n in ChildActivity.TargetLineNodes(self))
yield return n;
var a = ChildActivity;
for (; a != null; a = a.NextActivity)
if (!a.IsCanceling)
foreach (var n in a.TargetLineNodes(self))
yield return n;
}
}

View File

@@ -240,7 +240,7 @@ namespace OpenRA.Mods.Common.Traits
for (var i = 0; i < updateCount; i++)
{
var rp = rallyPoints.Pop();
if (rp.Actor.Owner == player)
if (rp.Actor.Owner == player && !rp.Actor.Disposed)
SetRallyPoint(bot, rp);
}
}
@@ -343,23 +343,37 @@ namespace OpenRA.Mods.Common.Traits
Locomotor[] LocomotorsForProducibles(Actor producer)
{
var buildingInfo = producer.Info.TraitInfoOrDefault<BuildingInfo>();
var productionInfo = producer.Info.TraitInfoOrDefault<ProductionInfo>();
var locomotors = Array.Empty<Locomotor>();
// Per-actor production
var productions = producer.TraitsImplementing<Production>();
if (productionInfo != null && productionInfo.Produces.Length > 0)
// Player-wide production
if (!productions.Any())
productions = producer.World.ActorsWithTrait<Production>().Where(x => x.Actor.Owner != producer.Owner).Select(x => x.Trait);
var produces = productions.SelectMany(p => p.Info.Produces).ToHashSet();
var locomotors = Array.Empty<Locomotor>();
if (produces.Count > 0)
{
var productionQueues = producer.Owner.PlayerActor.TraitsImplementing<ProductionQueue>()
.Where(pq => productionInfo.Produces.Contains(pq.Info.Type));
// Per-actor production
var productionQueues = producer.TraitsImplementing<ProductionQueue>();
// Player-wide production
if (!productionQueues.Any())
productionQueues = producer.Owner.PlayerActor.TraitsImplementing<ProductionQueue>();
productionQueues = productionQueues.Where(pq => produces.Contains(pq.Info.Type));
var producibles = productionQueues.SelectMany(pq => pq.BuildableItems());
var locomotorNames = producibles
.Select(p => p.TraitInfoOrDefault<MobileInfo>())
.Where(mi => mi != null)
.Select(mi => mi.Locomotor)
.ToHashSet();
locomotors = world.WorldActor.TraitsImplementing<Locomotor>()
.Where(l => locomotorNames.Contains(l.Info.Name))
.ToArray();
if (locomotorNames.Count != 0)
locomotors = world.WorldActor.TraitsImplementing<Locomotor>()
.Where(l => locomotorNames.Contains(l.Info.Name))
.ToArray();
}
return locomotors;

View File

@@ -173,7 +173,7 @@ namespace OpenRA.Mods.Common.Traits
// Returns true if FindNextResource was called.
bool HarvestIfAble(IBot bot, HarvesterTraitWrapper h)
{
if (h.Mobile == null)
if (h.Actor.IsDead || !h.Actor.IsInWorld || h.Mobile == null)
return false;
if (!h.Actor.IsIdle)
@@ -195,7 +195,7 @@ namespace OpenRA.Mods.Common.Traits
// Tell the idle harvester to quit slacking:
var newSafeResourcePatch = FindNextResource(h.Actor, h);
AIUtils.BotDebug($"AI: Harvester {h.Actor} is idle. Ordering to {newSafeResourcePatch} in search for new resources.");
if (newSafeResourcePatch != Target.Invalid)
if (newSafeResourcePatch.Type != TargetType.Invalid)
bot.QueueOrder(new Order("Harvest", h.Actor, newSafeResourcePatch, false));
else
h.NoResourcesCooldown = Info.ScanIntervalMultiplerWhenNoResources;

View File

@@ -142,7 +142,7 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
new("Units", FieldSaver.FormatValue(Units.Select(a => a.ActorID).ToArray()))
};
if (Target != Target.Invalid)
if (Target.Type != TargetType.Invalid)
{
nodes.Add(new MiniYamlNode("ActorToTarget", FieldSaver.FormatValue(TargetActor.ActorID)));
nodes.Add(new MiniYamlNode("TargetOffset", FieldSaver.FormatValue(Target.CenterPosition - TargetActor.CenterPosition)));

View File

@@ -70,7 +70,7 @@ namespace OpenRA.Mods.Common.Traits
Info.EnterCursor,
Info.EnterBlockedCursor,
() => Info.RequiresForceMove,
DockingPossible,
CanQueueDockAt,
CanDockAt);
}
}
@@ -109,9 +109,13 @@ namespace OpenRA.Mods.Common.Traits
string IOrderVoice.VoicePhraseForOrder(Actor self, Order order)
{
if (order.OrderString == "Dock" && CanDockAt(order.Target.Actor, false))
return Info.Voice;
else if (order.OrderString == "ForceDock" && CanDockAt(order.Target.Actor, true))
if (order.Target.Type != TargetType.Actor || IsTraitDisabled)
return null;
if (order.OrderString != "Dock" && order.OrderString != "ForceDock")
return null;
if (CanQueueDockAt(order.Target.Actor, order.OrderString == "ForceDock", order.Queued))
return Info.Voice;
return null;
@@ -125,12 +129,6 @@ namespace OpenRA.Mods.Common.Traits
return null;
}
/// <summary>Clone of <see cref="DockClientManager.DockingPossible(Actor, bool)"/>.</summary>
public bool DockingPossible(Actor target, bool forceEnter)
{
return !IsTraitDisabled && target.TraitsImplementing<DockHost>().Any(host => dockClients.Any(client => client.CanDock(host.GetDockType)));
}
/// <summary>Clone of <see cref="DockClientManager.CanDockAt(Actor, bool, bool)"/>.</summary>
public bool CanDockAt(Actor target, bool forceEnter)
{
@@ -140,5 +138,16 @@ namespace OpenRA.Mods.Common.Traits
return !IsTraitDisabled && target.TraitsImplementing<DockHost>().Any(
host => dockClients.Any(client => client.CanDockAt(target, host, forceEnter, true)));
}
/// <summary>Clone of <see cref="DockClientManager.CanQueueDockAt(Actor, bool, bool)"/>.</summary>
public bool CanQueueDockAt(Actor target, bool forceEnter, bool isQueued)
{
if (Info.RequiresForceMove && !forceEnter)
return false;
return (!IsTraitDisabled)
&& target.TraitsImplementing<IDockHost>().Any(
host => dockClients.Any(client => client.CanQueueDockAt(target, host, forceEnter, isQueued)));
}
}
}

View File

@@ -189,7 +189,7 @@ namespace OpenRA.Mods.Common.Traits
if (target != currentTarget)
{
if (currentTargetManager != null)
CancelCapture(currentTargetManager);
CancelCapture(currentTarget, currentTargetManager);
targetManager.currentCaptors.Add(self);
currentTarget = target;
@@ -239,12 +239,11 @@ namespace OpenRA.Mods.Common.Traits
/// This method revokes the capturing conditions on the captor and target
/// and resets any capturing progress.
/// </summary>
public void CancelCapture(CaptureManager targetManager)
public void CancelCapture(Actor target, CaptureManager targetManager)
{
if (currentTarget == null)
return;
var target = targetManager.self;
foreach (var w in progressWatchers)
w.Update(self, self, target, 0, 0);

View File

@@ -17,13 +17,13 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Allow deploying on specified charge to grant a condition for a specified duration.")]
public class GrantConditionOnDeployWithChargeInfo : PausableConditionalTraitInfo, IRulesetLoaded
[Desc("Grant a condition via player orders for a specified amount of time.")]
public class GrantChargedConditionOnToggleInfo : PausableConditionalTraitInfo, IRulesetLoaded
{
[FieldLoader.Require]
[GrantedConditionReference]
[Desc("The condition to grant after deploying.")]
public readonly string DeployedCondition = null;
[Desc("The condition to grant when enabled.")]
public readonly string ActivatedCondition = null;
[GrantedConditionReference]
[Desc("The condition to grant when charge is above " + nameof(ChargeThreshhold) + ".")]
@@ -35,7 +35,7 @@ namespace OpenRA.Mods.Common.Traits
[Desc("Cooldown (in ticks) to reach full charge.")]
public readonly int ChargeDuration = 500;
[Desc("The amount of charge that needs to be present for deploy to be issued. " +
[Desc("The amount of charge that needs to be present to turn on the condition. " +
"If set to -1, threshold is set to full charge. " +
"If activated without full charge " + nameof(ConditionDuration) + " is percentally smaller.")]
public readonly int ChargeThreshhold = -1;
@@ -43,30 +43,39 @@ namespace OpenRA.Mods.Common.Traits
[Desc("How long (in ticks) should the condition stay active?")]
public readonly int ConditionDuration = 1;
[Desc("Can " + nameof(DeployedCondition) + " be canceled by followup deploy order?")]
[Desc("Can " + nameof(ActivatedCondition) + " be turned off manually?")]
public readonly bool CanCancelCondition = false;
[CursorReference]
[Desc("Cursor to display when able to (un)deploy the actor.")]
public readonly string DeployCursor = "deploy";
[Desc("Should we interrupt the current activity")]
public readonly bool CancelsCurrentActivity = false;
[CursorReference]
[Desc("Cursor to display when unable to (un)deploy the actor.")]
public readonly string DeployBlockedCursor = "deploy-blocked";
[Desc("Cursor to display when able to trigger a state change.")]
public readonly string Cursor = "deploy";
[Desc("Play a randomly selected sound from this list when deploying.")]
public readonly string[] DeploySounds = null;
[CursorReference]
[Desc("Cursor to display when unable to trigger a state change.")]
public readonly string BlockedCursor = "deploy-blocked";
[Desc("Play a randomly selected sound from this list when undeploying.")]
public readonly string[] UndeploySounds = null;
[Desc("Play a randomly selected sound from this list when turning on.")]
public readonly string[] ActivationSounds = null;
[Desc("Play a randomly selected sound from this list when turning off.")]
public readonly string[] DeactivattionSounds = null;
[VoiceReference]
public readonly string Voice = "Action";
public readonly Color ChargingColor = Color.Magenta;
public readonly Color DeployedColor = Color.DarkMagenta;
[Desc("Color of the charge bar when deactivated.")]
public readonly Color DeactivatedColor = Color.Magenta;
public override object Create(ActorInitializer init) { return new GrantConditionOnDeployWithCharge(this); }
[Desc("Color of the charge bar when activated.")]
public readonly Color ActivatedColor = Color.DarkMagenta;
[Desc("Should the charge bar be displayed when not charged or the trait is disabled?")]
public readonly bool DisplayBarWhenEmpty = true;
public override object Create(ActorInitializer init) { return new GrantChargedConditionOnToggle(this); }
public override void RulesetLoaded(Ruleset rules, ActorInfo ai)
{
@@ -80,37 +89,37 @@ namespace OpenRA.Mods.Common.Traits
}
}
public class GrantConditionOnDeployWithCharge : PausableConditionalTrait<GrantConditionOnDeployWithChargeInfo>,
public class GrantChargedConditionOnToggle : PausableConditionalTrait<GrantChargedConditionOnToggleInfo>,
IIssueOrder, IResolveOrder, ITick, ISelectionBar, IOrderVoice, ISync, IIssueDeployOrder
{
[Sync]
int chargeTick = 0;
bool deployed = false;
bool isActive = false;
int deployedToken = Actor.InvalidConditionToken;
int activatedToken = Actor.InvalidConditionToken;
int chargedToken = Actor.InvalidConditionToken;
readonly int chargeThreshold;
readonly int deployedChargeThreshold;
readonly int activatedChargeThreshold;
public GrantConditionOnDeployWithCharge(GrantConditionOnDeployWithChargeInfo info)
public GrantChargedConditionOnToggle(GrantChargedConditionOnToggleInfo info)
: base(info)
{
chargeTick = info.InitialCharge < 0 || info.InitialCharge >= info.ChargeDuration ? Info.ChargeDuration : info.InitialCharge;
// PERF: Cache the conversions.
chargeThreshold = Info.ChargeThreshhold < 0 || Info.ChargeThreshhold > Info.ChargeDuration ? Info.ChargeDuration : Info.ChargeThreshhold;
deployedChargeThreshold = chargeThreshold * Info.ConditionDuration / Info.ChargeDuration;
activatedChargeThreshold = chargeThreshold * Info.ConditionDuration / Info.ChargeDuration;
}
protected override void TraitDisabled(Actor self)
{
base.TraitDisabled(self);
if (deployed)
Undeploy(self);
if (isActive)
Deactivate(self);
// Reset charge.
chargeTick = Info.InitialCharge < 0 || Info.InitialCharge > Info.ChargeDuration ? Info.ChargeDuration : Info.InitialCharge;
@@ -120,7 +129,7 @@ namespace OpenRA.Mods.Common.Traits
public string VoicePhraseForOrder(Actor self, Order order)
{
return order.OrderString == "DeployWithCharge" ? Info.Voice : null;
return order.OrderString == "ActivateCondition" ? Info.Voice : null;
}
public IEnumerable<IOrderTargeter> Orders
@@ -128,14 +137,14 @@ namespace OpenRA.Mods.Common.Traits
get
{
if (!IsTraitDisabled)
yield return new DeployOrderTargeter("DeployWithCharge", 5,
() => CanDeploy() ? Info.DeployCursor : Info.DeployBlockedCursor);
yield return new DeployOrderTargeter("ActivateCondition", 5,
() => CanToggle() ? Info.Cursor : Info.BlockedCursor);
}
}
public Order IssueOrder(Actor self, IOrderTargeter order, in Target target, bool queued)
{
if (order.OrderID == "DeployWithCharge")
if (order.OrderID == "ActivateCondition")
return new Order(order.OrderID, self, queued);
return null;
@@ -143,55 +152,60 @@ namespace OpenRA.Mods.Common.Traits
Order IIssueDeployOrder.IssueDeployOrder(Actor self, bool queued)
{
return new Order("DeployWithCharge", self, queued);
return new Order("ActivateCondition", self, queued);
}
bool IIssueDeployOrder.CanIssueDeployOrder(Actor self, bool queued) { return queued || CanDeploy(); }
bool IIssueDeployOrder.CanIssueDeployOrder(Actor self, bool queued) { return queued || CanToggle(); }
public void ResolveOrder(Actor self, Order order)
{
if (order.OrderString == "DeployWithCharge")
self.QueueActivity(order.Queued, new DeployForGrantedConditionWithCharge(self, this));
if (order.OrderString != "ActivateCondition")
return;
if (order.Queued || Info.CancelsCurrentActivity)
self.QueueActivity(order.Queued, new ToggleChargedCondition(self, this));
else if (CanToggle())
ToggleState(self);
}
public bool CanDeploy() => !IsTraitDisabled && !IsTraitPaused && ((!deployed && chargeTick >= chargeThreshold) || (deployed && Info.CanCancelCondition));
public bool CanToggle() => !IsTraitDisabled && !IsTraitPaused && ((!isActive && chargeTick >= chargeThreshold) || (isActive && Info.CanCancelCondition));
public void TriggerDeploy(Actor self)
public void ToggleState(Actor self)
{
if (deployed)
if (isActive)
{
// Keep the percentage of the unused charge.
chargeTick = chargeTick * Info.ChargeDuration / Info.ConditionDuration;
Undeploy(self);
Deactivate(self);
}
else
{
// If deployed without full charge, reduce the deploy duration.
// If activated without full charge, subtract from the activated duration.
chargeTick = chargeTick * Info.ConditionDuration / Info.ChargeDuration;
Deploy(self);
Activate(self);
}
}
void Deploy(Actor self)
void Activate(Actor self)
{
if (Info.DeploySounds != null && Info.DeploySounds.Length > 0)
Game.Sound.Play(SoundType.World, Info.DeploySounds, self.World, self.CenterPosition);
if (Info.ActivationSounds != null && Info.ActivationSounds.Length > 0)
Game.Sound.Play(SoundType.World, Info.ActivationSounds, self.World, self.CenterPosition);
if (deployedToken == Actor.InvalidConditionToken)
deployedToken = self.GrantCondition(Info.DeployedCondition);
if (activatedToken == Actor.InvalidConditionToken)
activatedToken = self.GrantCondition(Info.ActivatedCondition);
deployed = true;
isActive = true;
}
void Undeploy(Actor self)
void Deactivate(Actor self)
{
if (Info.UndeploySounds != null && Info.UndeploySounds.Length > 0)
Game.Sound.Play(SoundType.World, Info.UndeploySounds, self.World, self.CenterPosition);
if (Info.DeactivattionSounds != null && Info.DeactivattionSounds.Length > 0)
Game.Sound.Play(SoundType.World, Info.DeactivattionSounds, self.World, self.CenterPosition);
if (deployedToken != Actor.InvalidConditionToken)
deployedToken = self.RevokeCondition(deployedToken);
if (activatedToken != Actor.InvalidConditionToken)
activatedToken = self.RevokeCondition(activatedToken);
deployed = false;
isActive = false;
}
void ITick.Tick(Actor self)
@@ -199,12 +213,12 @@ namespace OpenRA.Mods.Common.Traits
if (IsTraitDisabled || IsTraitPaused)
return;
if (deployed)
if (isActive)
{
if (chargeTick > 0)
chargeTick--;
else
Undeploy(self);
Deactivate(self);
}
else
{
@@ -214,7 +228,7 @@ namespace OpenRA.Mods.Common.Traits
if (Info.ChargedCondition != null)
{
if (chargeTick < (deployed ? deployedChargeThreshold : chargeThreshold))
if (chargeTick < (isActive ? activatedChargeThreshold : chargeThreshold))
{
if (chargedToken != Actor.InvalidConditionToken)
chargedToken = self.RevokeCondition(chargedToken);
@@ -232,28 +246,28 @@ namespace OpenRA.Mods.Common.Traits
if (IsTraitDisabled)
return 0f;
return deployed
return isActive
? (float)chargeTick / Info.ConditionDuration
: (float)chargeTick / Info.ChargeDuration;
}
Color ISelectionBar.GetColor() { return deployed ? Info.DeployedColor : Info.ChargingColor; }
bool ISelectionBar.DisplayWhenEmpty => false;
Color ISelectionBar.GetColor() { return isActive ? Info.ActivatedColor : Info.DeactivatedColor; }
bool ISelectionBar.DisplayWhenEmpty => Info.DisplayBarWhenEmpty;
}
public class DeployForGrantedConditionWithCharge : Activity
public class ToggleChargedCondition : Activity
{
readonly GrantConditionOnDeployWithCharge deploy;
readonly GrantChargedConditionOnToggle toggle;
public DeployForGrantedConditionWithCharge(Actor self, GrantConditionOnDeployWithCharge deploy)
public ToggleChargedCondition(Actor self, GrantChargedConditionOnToggle toggle)
{
this.deploy = deploy;
this.toggle = toggle;
}
protected override void OnFirstRun(Actor self)
{
if (deploy.CanDeploy())
deploy.TriggerDeploy(self);
if (toggle.CanToggle())
toggle.ToggleState(self);
}
public override IEnumerable<TargetLineNode> TargetLineNodes(Actor self)

View File

@@ -37,9 +37,14 @@ namespace OpenRA.Mods.Common.Traits
public virtual bool CanDockAt(Actor hostActor, IDockHost host, bool forceEnter = false, bool ignoreOccupancy = false)
{
return (forceEnter || self.Owner.IsAlliedWith(hostActor.Owner)) &&
CanDock(host.GetDockType, forceEnter) &&
host.IsDockingPossible(self, this, ignoreOccupancy);
return CanDock(host.GetDockType, forceEnter)
&& host.IsDockingPossible(self, this, ignoreOccupancy);
}
public virtual bool CanQueueDockAt(Actor hostActor, IDockHost host, bool forceEnter, bool isQueued)
{
return CanDock(host.GetDockType, true)
&& host.IsDockingPossible(self, this, true);
}
public virtual void OnDockStarted(Actor self, Actor hostActor, IDockHost host) { }

View File

@@ -160,7 +160,7 @@ namespace OpenRA.Mods.Common.Traits
Info.EnterCursor,
Info.EnterBlockedCursor,
() => requireForceMove,
DockingPossible,
CanQueueDockAt,
(target, forceEnter) => CanDockAt(target, forceEnter, true));
}
}
@@ -194,9 +194,10 @@ namespace OpenRA.Mods.Common.Traits
if (order.Target.Type != TargetType.Actor || IsTraitDisabled)
return null;
if (order.OrderString == "Dock" && CanDockAt(order.Target.Actor, false, true))
return Info.Voice;
else if (order.OrderString == "ForceDock" && CanDockAt(order.Target.Actor, true, true))
if (order.OrderString != "Dock" && order.OrderString != "ForceDock")
return null;
if (CanQueueDockAt(order.Target.Actor, order.OrderString == "ForceDock", order.Queued))
return Info.Voice;
return null;
@@ -225,13 +226,13 @@ namespace OpenRA.Mods.Common.Traits
}
/// <summary>Do we have an enabled client with matching <paramref name="type"/>.</summary>
public bool DockingPossible(BitSet<DockType> type, bool forceEnter = false)
public bool CanDock(BitSet<DockType> type, bool forceEnter = false)
{
return !IsTraitDisabled && dockClients.Any(client => client.CanDock(type, forceEnter));
}
/// <summary>Does this <paramref name="target"/> contain at least one enabled <see cref="IDockHost"/> with maching <see cref="DockType"/>.</summary>
public bool DockingPossible(Actor target, bool forceEnter = false)
public bool CanDock(Actor target, bool forceEnter = false)
{
return !IsTraitDisabled &&
target.TraitsImplementing<IDockHost>()
@@ -252,6 +253,14 @@ namespace OpenRA.Mods.Common.Traits
host => dockClients.Any(client => client.CanDockAt(target, host, forceEnter, ignoreOccupancy)));
}
/// <summary>Can we dock to this <paramref name="target"/>.</summary>
public bool CanQueueDockAt(Actor target, bool forceEnter, bool isQueued)
{
return !IsTraitDisabled
&& target.TraitsImplementing<IDockHost>()
.Any(host => dockClients.Any(client => client.CanQueueDockAt(target, host, forceEnter, isQueued)));
}
/// <summary>Find the closest viable <see cref="IDockHost"/>.</summary>
/// <remarks>If <paramref name="type"/> is not set, scans all clients. Does not check if <see cref="DockClientManager"/> is enabled.</remarks>
public TraitPair<IDockHost>? ClosestDock(IDockHost ignore, BitSet<DockType> type = default, bool forceEnter = false, bool ignoreOccupancy = false)
@@ -292,11 +301,11 @@ namespace OpenRA.Mods.Common.Traits
readonly string enterCursor;
readonly string enterBlockedCursor;
readonly Func<bool> requireForceMove;
readonly Func<Actor, bool, bool> canTarget;
readonly Func<Actor, bool, bool, bool> canTarget;
readonly Func<Actor, bool, bool> useEnterCursor;
public DockActorTargeter(int priority, string enterCursor, string enterBlockedCursor,
Func<bool> requireForceMove, Func<Actor, bool, bool> canTarget, Func<Actor, bool, bool> useEnterCursor)
Func<bool> requireForceMove, Func<Actor, bool, bool, bool> canTarget, Func<Actor, bool, bool> useEnterCursor)
{
OrderID = "Dock";
OrderPriority = priority;
@@ -325,10 +334,13 @@ namespace OpenRA.Mods.Common.Traits
if (requireForceMove() && !forceEnter)
return false;
if (!canTarget(target.Actor, forceEnter))
if (!canTarget(target.Actor, forceEnter, IsQueued))
return false;
cursor = useEnterCursor(target.Actor, forceEnter) ? enterCursor : enterBlockedCursor;
cursor = IsQueued || useEnterCursor(target.Actor, forceEnter)
? enterCursor
: enterBlockedCursor;
return true;
}

View File

@@ -137,6 +137,12 @@ namespace OpenRA.Mods.Common.Traits
&& (self.Owner == hostActor.Owner || (ignoreOccupancy && self.Owner.IsAlliedWith(hostActor.Owner)));
}
public override bool CanQueueDockAt(Actor hostActor, IDockHost host, bool forceEnter, bool isQueued)
{
return base.CanQueueDockAt(hostActor, host, forceEnter, isQueued)
&& self.Owner.IsAlliedWith(hostActor.Owner);
}
void UpdateCondition(Actor self)
{
if (string.IsNullOrEmpty(Info.EmptyCondition))

View File

@@ -87,11 +87,11 @@ namespace OpenRA.Mods.Common.Traits
bool IsValidActor(Actor target)
{
var instantlyRepairable = target.Info.TraitInfoOrDefault<InstantlyRepairableInfo>();
if (instantlyRepairable == null)
var instantlyRepairable = target.TraitOrDefault<InstantlyRepairable>();
if (instantlyRepairable == null || instantlyRepairable.IsTraitDisabled)
return false;
if (!instantlyRepairable.Types.IsEmpty && !instantlyRepairable.Types.Overlaps(Info.Types))
if (!instantlyRepairable.Info.Types.IsEmpty && !instantlyRepairable.Info.Types.Overlaps(Info.Types))
return false;
return true;

View File

@@ -1000,6 +1000,23 @@ namespace OpenRA.Mods.Common.Traits
foreach (var cell in rallyPoint)
QueueChild(new AttackMoveActivity(self, () => mobile.MoveTo(cell, 1, evaluateNearestMovableCell: true, targetLineColor: Color.OrangeRed)));
}
public override IEnumerable<Target> GetTargets(Actor self)
{
if (ChildActivity != null)
return ChildActivity.GetTargets(self);
return Target.None;
}
public override IEnumerable<TargetLineNode> TargetLineNodes(Actor self)
{
var a = ChildActivity;
for (; a != null; a = a.NextActivity)
if (!a.IsCanceling)
foreach (var n in a.TargetLineNodes(self))
yield return n;
}
}
Activity ICreationActivity.GetCreationActivity()

View File

@@ -61,7 +61,7 @@ namespace OpenRA.Mods.Common.Traits
protected override void PrepareRender(WorldRenderer wr, IShader shader)
{
shader.SetVec("Blend", blend);
shader.SetVec("Color", (float)Info.Color.B / 255, (float)Info.Color.G / 255, (float)Info.Color.R / 255);
shader.SetVec("Color", (float)Info.Color.R / 255, (float)Info.Color.G / 255, (float)Info.Color.B / 255);
}
}
}

View File

@@ -174,12 +174,11 @@ namespace OpenRA.Mods.Common.Traits
void UpdateNetWorth(string oldResourceType, int oldDensity, string newResourceType, int newDensity)
{
// Density + 1 as workaround for fixing ResourceLayer.Harvest as it would be very disruptive to balancing
if (oldResourceType != null && oldDensity > 0 && resourceValues.TryGetValue(oldResourceType, out var oldResourceValue))
NetWorth -= (oldDensity + 1) * oldResourceValue;
NetWorth -= oldDensity * oldResourceValue;
if (newResourceType != null && newDensity > 0 && resourceValues.TryGetValue(newResourceType, out var newResourceValue))
NetWorth += (newDensity + 1) * newResourceValue;
NetWorth += newDensity * newResourceValue;
}
public int CalculateRegionValue(CellRegion sourceRegion)
@@ -188,39 +187,44 @@ namespace OpenRA.Mods.Common.Traits
foreach (var cell in sourceRegion.CellCoords)
{
var mcell = cell.ToMPos(Map);
if (Map.Resources.Contains(mcell) && Map.Resources[mcell].Type != 0)
{
resourceValueInRegion++;
var rcell = Map.Resources[mcell];
if (ResourceTypesByIndex.TryGetValue(rcell.Type, out var resourceType) && resourceValues.TryGetValue(resourceType, out var resourceValuePerUnit))
resourceValueInRegion += Tiles[mcell].Density * resourceValuePerUnit;
}
if (!Map.Resources.Contains(mcell))
continue;
var resource = Map.Resources[mcell].Type;
if (resource != 0
&& ResourceTypesByIndex.TryGetValue(resource, out var resourceType)
&& resourceValues.TryGetValue(resourceType, out var resourceValuePerUnit))
resourceValueInRegion += Tiles[mcell].Density * resourceValuePerUnit;
}
return resourceValueInRegion;
}
protected virtual int CalculateCellDensity(ResourceLayerContents contents, CPos c)
/// <summary>
/// Matches the logic in <see cref="ResourceLayer"/> trait.
/// </summary>
protected virtual int CalculateCellDensity(ResourceLayerContents contents, CPos cell)
{
var resources = Map.Resources;
if (contents.Type == null || !info.ResourceTypes.TryGetValue(contents.Type, out var resourceInfo) || resources[c].Type != resourceInfo.ResourceIndex)
if (contents.Type == null || !info.ResourceTypes.TryGetValue(contents.Type, out var resourceInfo) || resources[cell].Type != resourceInfo.ResourceIndex)
return 0;
if (!info.RecalculateResourceDensity)
return contents.Density.Clamp(1, resourceInfo.MaxDensity);
// Set density based on the number of neighboring resources
// Set density based on the number of neighboring resources.
var adjacent = 0;
for (var u = -1; u < 2; u++)
var directions = CVec.Directions;
for (var i = 0; i < directions.Length; i++)
{
for (var v = -1; v < 2; v++)
{
var cell = c + new CVec(u, v);
if (resources.Contains(cell) && resources[cell].Type == resourceInfo.ResourceIndex)
adjacent++;
}
var c = cell + directions[i];
if (resources.Contains(c) && resources[c].Type == resourceInfo.ResourceIndex)
++adjacent;
}
// We need to have at least one resource in the cell.
// HACK: we should not be lerping to 9, as maximum adjacent resources is 8.
// HACK: it's too disruptive to fix.
return Math.Max(int2.Lerp(0, resourceInfo.MaxDensity, adjacent, 9), 1);
}

View File

@@ -14,7 +14,7 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
public abstract class RenderPostProcessPassBase : IRenderPostProcessPass
public abstract class RenderPostProcessPassBase : IRenderPostProcessPass, INotifyActorDisposing
{
readonly Renderer renderer;
readonly IShader shader;
@@ -52,5 +52,10 @@ namespace OpenRA.Mods.Common.Traits
protected abstract bool Enabled { get; }
protected abstract void PrepareRender(WorldRenderer wr, IShader shader);
void INotifyActorDisposing.Disposing(Actor self)
{
buffer.Dispose();
}
}
}

View File

@@ -160,7 +160,9 @@ namespace OpenRA.Mods.Common.Traits
++adjacent;
}
// Adjacent includes the current cell, so is always >= 1
// We need to have at least one resource in the cell.
// HACK: we should not be lerping to 9, as maximum adjacent resources is 8.
// HACK: it's too disruptive to fix.
var density = Math.Max(int2.Lerp(0, resourceInfo.MaxDensity, adjacent, 9), 1);
Content[cell] = new ResourceLayerContents(resource.Type, density);
}

View File

@@ -220,19 +220,26 @@ namespace OpenRA.Mods.Common.Traits
bool OnDockTick(Actor self, Actor hostActor, IDockHost dock);
void OnDockCompleted(Actor self, Actor hostActor, IDockHost host);
/// <summary>Is this client allowed to dock.</summary>
/// <summary>Are we allowed to dock.</summary>
/// <remarks>
/// Does not check if <see cref="Traits.DockClientManager"/> is enabled.
/// Function should only be called from within <see cref="IDockClient"/> or <see cref="Traits.DockClientManager"/>.
/// </remarks>
bool CanDock(BitSet<DockType> type, bool forceEnter = false);
/// <summary>Is this client allowed to dock to <paramref name="host"/>.</summary>
/// <summary>Are we allowed to dock to this <paramref name="host"/>.</summary>
/// <remarks>
/// Does not check if <see cref="Traits.DockClientManager"/> is enabled.
/// Function should only be called from within <see cref="IDockClient"/> or <see cref="Traits.DockClientManager"/>.
/// </remarks>
bool CanDockAt(Actor hostActor, IDockHost host, bool forceEnter = false, bool ignoreOccupancy = false);
/// <summary>Are we allowed to give a docking order for this <paramref name="host"/>.</summary>
/// <remarks>
/// Does not check if <see cref="Traits.DockClientManager"/> is enabled.
/// Function should only be called from within <see cref="IDockClient"/> or <see cref="Traits.DockClientManager"/>.
/// </remarks>
bool CanQueueDockAt(Actor hostActor, IDockHost host, bool forceEnter, bool isQueued);
}
public interface IDockHostInfo : ITraitInfoInterface { }

View File

@@ -35,8 +35,9 @@ namespace OpenRA.Mods.Common.UtilityCommands
var palette = new ImmutablePalette(args[1], new[] { 0 }, Array.Empty<int>());
SequenceSet sequences;
if (modData.DefaultTerrainInfo.ContainsKey(args[2]))
sequences = new SequenceSet(modData.ModFiles, modData, args[2], null);
var tilesetUpper = args[2].ToUpperInvariant();
if (modData.DefaultTerrainInfo.ContainsKey(tilesetUpper))
sequences = new SequenceSet(modData.ModFiles, modData, tilesetUpper, null);
else
{
var mapPackage = new Folder(Platform.EngineDir).OpenPackage(args[2], modData.ModFiles);

View File

@@ -48,7 +48,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
foreach (var layout in chromeLayouts)
{
var fluentFolder = layout.Key + "|languages";
var fluentFolder = layout.Key + "|fluent";
var fluentPackage = modData.ModFiles.OpenPackage(fluentFolder);
var fluentPath = Path.Combine(fluentPackage.Name, "chrome.ftl");
@@ -183,7 +183,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
type = type.Replace("text", "");
}
build += $" .{type} = {candidate.Value}\n";
build += $" .{type} = {candidate.Value}\n";
foreach (var node in candidate.Nodes)
node.Value.Value = $"{key}.{type}";
}
@@ -262,12 +262,23 @@ namespace OpenRA.Mods.Common.UtilityCommands
&& !UpdateUtils.IsAlreadyExtracted(childNode.Value.Value)
&& childNode.Value.Value.Any(char.IsLetterOrDigit))
{
var value = childNode.Value.Value
.Replace("\\n", "\n ")
var replaced = false;
var value = childNode.Value.Value;
if (value.Contains("\\n"))
{
value = value.Replace("\\n", "\n ");
replaced = true;
}
value = value
.Replace("{", "<")
.Replace("}", ">")
.Trim().Trim('\n');
// Preserve indentation
if (replaced)
value = "\n " + value;
validChildTypes.Add((childNode, childType.ToLowerInvariant(), value));
}
}

View File

@@ -64,7 +64,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
}
}
var fluentPackage = modData.ModFiles.OpenPackage(modData.Manifest.Id + "|languages");
var fluentPackage = modData.ModFiles.OpenPackage(modData.Manifest.Id + "|fluent");
ExtractFromFile(Path.Combine(fluentPackage.Name, "rules.ftl"), modRules, traitInfos);
modRules.Save();
@@ -214,7 +214,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
foreach (var candidate in grouping)
{
var type = candidate.Key;
build += $" .{type} = {candidate.Value}\n";
build += $" .{type} = {candidate.Value}\n";
foreach (var node in candidate.Nodes)
node.Value.Value = $"{key}.{type}";
@@ -307,9 +307,16 @@ namespace OpenRA.Mods.Common.UtilityCommands
if (string.IsNullOrEmpty(propertyValue) || UpdateUtils.IsAlreadyExtracted(propertyValue) || !propertyValue.Any(char.IsLetterOrDigit))
continue;
var value = propertyValue
.Replace("\\n", "\n ")
.Trim().Trim('\n');
var replaced = false;
var value = propertyValue;
if (value.Contains("\\n"))
{
value = value.Replace("\\n", "\n ");
replaced = true;
}
// Preserve indentation
value = (replaced ? "\n " : "") + value.Trim().Trim('\n');
var actorName = ToLowerActor(actor.Key);
var key = traitInfo;

View File

@@ -179,5 +179,11 @@ namespace OpenRA.Mods.Common.Widgets
OnChange();
}
}
public override void Removed()
{
mixerSheet?.Dispose();
base.Removed();
}
}
}

View File

@@ -19,6 +19,7 @@ namespace OpenRA.Mods.Common.Widgets
{
Sprite hueSprite;
Sprite pickerSprite;
Sheet hueSheet;
public HueSliderWidget() { }
public HueSliderWidget(HueSliderWidget other)
@@ -28,7 +29,7 @@ namespace OpenRA.Mods.Common.Widgets
{
base.Initialize(args);
var hueSheet = new Sheet(SheetType.BGRA, new Size(256, 1));
hueSheet = new Sheet(SheetType.BGRA, new Size(256, 1));
var buffer = new byte[4 * 256];
@@ -63,5 +64,11 @@ namespace OpenRA.Mods.Common.Widgets
var pos = RenderOrigin + new int2(PxFromValue(Value).Clamp(0, rb.Width - 1) - (int)pickerSprite.Size.X / 2, (rb.Height - (int)pickerSprite.Size.Y) / 2);
WidgetUtils.DrawSprite(pickerSprite, pos);
}
public override void Removed()
{
hueSheet?.Dispose();
base.Removed();
}
}
}

View File

@@ -33,7 +33,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
public LabelWidget DiagonalLabel;
public LabelWidget ResourceCounterLabel;
MapBlitFilters copyFilters = MapBlitFilters.All;
MapBlitFilters selectionFilters = MapBlitFilters.All;
EditorBlitSource? clipboard;
[ObjectCreator.UseCtor]
@@ -81,12 +81,15 @@ namespace OpenRA.Mods.Common.Widgets.Logic
worldRenderer,
clipboard.Value,
resourceLayer,
() => copyFilters));
() => selectionFilters));
};
pasteButton.IsDisabled = () => clipboard == null;
pasteButton.IsHighlighted = () => editor.CurrentBrush is EditorCopyPasteBrush;
var deleteAreaSelectionButton = areaEditPanel.Get<ButtonWidget>("SELECTION_DELETE_BUTTON");
deleteAreaSelectionButton.OnClick = () => editor.DefaultBrush.DeleteSelection(selectionFilters);
var closeAreaSelectionButton = areaEditPanel.Get<ButtonWidget>("SELECTION_CANCEL_BUTTON");
closeAreaSelectionButton.OnClick = () => editor.DefaultBrush.ClearSelection(updateSelectedTab: true);
@@ -102,15 +105,15 @@ namespace OpenRA.Mods.Common.Widgets.Logic
editorActorLayer,
resourceLayer,
editor.DefaultBrush.Selection.Area,
copyFilters);
selectionFilters);
}
void CreateCategoryPanel(MapBlitFilters copyFilter, CheckboxWidget checkbox)
{
checkbox.GetText = () => copyFilter.ToString();
checkbox.IsChecked = () => copyFilters.HasFlag(copyFilter);
checkbox.IsChecked = () => selectionFilters.HasFlag(copyFilter);
checkbox.IsVisible = () => true;
checkbox.OnClick = () => copyFilters ^= copyFilter;
checkbox.OnClick = () => selectionFilters ^= copyFilter;
}
protected override void Dispose(bool disposing)

View File

@@ -50,6 +50,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
readonly LabelWidget productionTime;
readonly Widget productionPowerIcon;
readonly LabelWidget productionPower;
readonly List<Sheet> sheets = new();
ActorInfo selectedActor;
ScrollItemWidget firstItem;
@@ -81,6 +82,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
defaultPortrait = new Png(modData.DefaultFileSystem.Open("encyclopedia/default.png"));
var spriteBounds = new Rectangle(0, 0, defaultPortrait.Width, defaultPortrait.Height);
var sheet = new Sheet(SheetType.BGRA, spriteBounds.Size.NextPowerOf2());
sheets.Add(sheet);
sheet.CreateBuffer();
sheet.GetTexture().ScaleFilter = TextureScaleFilter.Linear;
portraitSprite = new Sprite(sheet, spriteBounds, TextureChannel.RGBA);
@@ -304,5 +306,13 @@ namespace OpenRA.Mods.Common.Widgets.Logic
time = time * bi.BuildDurationModifier * pbi / 10000;
return time;
}
protected override void Dispose(bool disposing)
{
foreach (var sheet in sheets)
sheet.Dispose();
base.Dispose(disposing);
}
}
}

View File

@@ -751,7 +751,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
}
else
{
LobbyUtils.SetupNameWidget(template, client, orderManager, worldRenderer);
LobbyUtils.SetupNameWidget(template, client, orderManager, worldRenderer, map);
LobbyUtils.SetupTeamWidget(template, client);
LobbyUtils.SetupHandicapWidget(template, client);
LobbyUtils.SetupSpawnWidget(template, client);
@@ -803,7 +803,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
LobbyUtils.SetupPlayerActionWidget(template, client, orderManager, worldRenderer,
lobby, () => panel = PanelType.Kick, () => panel = PanelType.Players);
else
LobbyUtils.SetupNameWidget(template, client, orderManager, worldRenderer);
LobbyUtils.SetupNameWidget(template, client, orderManager, worldRenderer, map);
if (client.IsAdmin)
LobbyUtils.SetupReadyWidget(template, client);

View File

@@ -33,6 +33,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic
[FluentReference]
const string Bots = "options-lobby-slot.bots";
[FluentReference]
const string BotPlayer = "label-bot-player";
[FluentReference]
const string BotsDisabled = "options-lobby-slot.bots-disabled";
@@ -75,7 +78,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
foreach (var b in map.PlayerActorInfo.TraitInfos<IBotInfo>())
{
var botController = orderManager.LobbyInfo.Clients.FirstOrDefault(c => c.IsAdmin);
bots.Add(new SlotDropDownOption(FluentProvider.GetMessage(b.Name),
bots.Add(new SlotDropDownOption(map.GetMessage(b.Name),
$"slot_bot {slot.PlayerReference} {botController.Index} {b.Type}",
() => client != null && client.Bot == b.Type));
}
@@ -425,14 +428,22 @@ namespace OpenRA.Mods.Common.Widgets.Logic
HideChildWidget(parent, "SLOT_OPTIONS");
}
public static void SetupNameWidget(Widget parent, Session.Client c, OrderManager orderManager, WorldRenderer worldRenderer)
public static void SetupNameWidget(Widget parent, Session.Client c, OrderManager orderManager, WorldRenderer worldRenderer, MapPreview map)
{
var label = parent.Get<LabelWidget>("NAME");
label.IsVisible = () => true;
var font = Game.Renderer.Fonts[label.Font];
var name = c.IsBot ? FluentProvider.GetMessage(c.Name) : c.Name;
var text = WidgetUtils.TruncateText(name, label.Bounds.Width, font);
label.GetText = () => text;
var clientName = new CachedTransform<MapStatus, string>(s =>
{
var name = c.Name;
if (c.IsBot && !map.TryGetMessage(c.Name, out name))
name = FluentProvider.GetMessage(BotPlayer);
return WidgetUtils.TruncateText(name, label.Bounds.Width, font);
});
label.GetText = () => clientName.Update(map.Status);
SetupProfileWidget(parent, c, orderManager, worldRenderer);
}
@@ -450,9 +461,23 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var closed = FluentProvider.GetMessage(Closed);
var open = FluentProvider.GetMessage(Open);
var clientName = new CachedTransform<MapStatus, string>(s =>
{
if (c.IsBot)
{
if (map.TryGetMessage(c.Name, out var message))
return message;
else
return FluentProvider.GetMessage(BotPlayer);
}
return c.Name;
});
slot.GetText = () => truncated.Update(c != null ?
c.IsBot ? FluentProvider.GetMessage(c.Name) : c.Name
: s.Closed ? closed : open);
clientName.Update(map.Status)
: s.Closed ? closed : open);
slot.OnMouseDown = _ => ShowSlotDropDown(slot, s, c, orderManager, map, modData);

View File

@@ -43,6 +43,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
protected MenuType menuType = MenuType.Main;
readonly Widget rootMenu;
readonly ScrollPanelWidget newsPanel;
readonly int maxNewsHeight;
readonly Widget newsTemplate;
readonly LabelWidget newsStatus;
readonly ModData modData;
@@ -230,6 +231,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
newsPanel = Ui.LoadWidget<ScrollPanelWidget>("NEWS_PANEL", null, new WidgetArgs());
newsTemplate = newsPanel.Get("NEWS_ITEM_TEMPLATE");
newsPanel.RemoveChild(newsTemplate);
maxNewsHeight = newsPanel.Bounds.Height;
newsStatus = newsPanel.Get<LabelWidget>("NEWS_STATUS");
SetNewsStatus(FluentProvider.GetMessage(LoadingNews));
@@ -416,10 +418,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
newsPanel.RemoveChildren();
SetNewsStatus("");
foreach (var i in newsItems)
foreach (var item in newsItems)
{
var item = i;
var newsItem = newsTemplate.Clone();
var titleLabel = newsItem.Get<LabelWidget>("TITLE");
@@ -441,6 +441,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
newsPanel.AddChild(newsItem);
newsPanel.Layout.AdjustChildren();
newsPanel.Bounds.Height = Math.Min(newsPanel.ContentHeight, maxNewsHeight);
}
}

View File

@@ -68,6 +68,18 @@ namespace OpenRA.Mods.Common.Widgets.Logic
readonly ScrollItemWidget headerTemplate;
readonly ScrollItemWidget template;
readonly Widget miniOptions;
readonly DropDownButtonWidget difficultyButton;
readonly DropDownButtonWidget gameSpeedButton;
readonly string unsetDifficulty;
readonly string defaultTooltop;
// For remembering options
// TODO: this should be persistent across game sessions
string selectedDifficulty;
string selectedGameSpeed;
bool minifiedOptions = true;
MapPreview selectedMap;
PlayingVideo playingVideo;
readonly Dictionary<string, string> missionOptions = new();
@@ -125,6 +137,16 @@ namespace OpenRA.Mods.Common.Widgets.Logic
stopInfoVideoButton.IsVisible = () => playingVideo == PlayingVideo.Info;
stopInfoVideoButton.OnClick = () => StopVideo(videoPlayer);
miniOptions = widget.GetOrNull("MISSION_MINIFIED_OPTIONS");
if (miniOptions != null)
{
miniOptions.IsVisible = () => minifiedOptions;
difficultyButton = miniOptions.GetOrNull<DropDownButtonWidget>("DIFFICULTY");
gameSpeedButton = miniOptions.GetOrNull<DropDownButtonWidget>("GAMESPEED");
unsetDifficulty = FluentProvider.GetMessage(difficultyButton.Text);
defaultTooltop = FluentProvider.GetMessage(difficultyButton.TooltipText);
}
var allPreviews = new List<MapPreview>();
missionList.RemoveChildren();
@@ -204,16 +226,14 @@ namespace OpenRA.Mods.Common.Widgets.Logic
};
var tabContainer = widget.Get("MISSION_TABS");
tabContainer.IsVisible = () => true;
tabContainer.IsVisible = () => !minifiedOptions;
var optionsTab = tabContainer.Get<ButtonWidget>("OPTIONS_TAB");
optionsTab.IsHighlighted = () => panel == PanelType.Options;
optionsTab.IsDisabled = () => false;
optionsTab.OnClick = () => panel = PanelType.Options;
var missionTab = tabContainer.Get<ButtonWidget>("MISSIONINFO_TAB");
missionTab.IsHighlighted = () => panel == PanelType.MissionInfo;
missionTab.IsDisabled = () => false;
missionTab.OnClick = () => panel = PanelType.MissionInfo;
}
@@ -319,6 +339,44 @@ namespace OpenRA.Mods.Common.Widgets.Logic
.Where(o => o.IsVisible)
.OrderBy(o => o.DisplayOrder).ToArray();
minifiedOptions = allOptions.All(o => o.Id == "difficulty" || o.Id == "gamespeed");
if (minifiedOptions)
BuildMinifiedOptions(allOptions);
else
BuildOptions(allOptions);
}
void SetMapDifficulty(LobbyOption option)
{
selectedDifficulty ??= option.DefaultValue;
if (option.Values.ContainsKey(selectedDifficulty))
missionOptions[option.Id] = selectedDifficulty;
else
missionOptions[option.Id] = option.DefaultValue;
}
void SetMapSpeed(LobbyOption option)
{
selectedGameSpeed ??= option.DefaultValue;
if (option.Values.ContainsKey(selectedGameSpeed))
missionOptions[option.Id] = selectedGameSpeed;
else
missionOptions[option.Id] = option.DefaultValue;
}
void OnOptionSelected(string optionId, string value)
{
// Only remember when the user manually changes the value
if (optionId == "difficulty")
selectedDifficulty = value;
else if (optionId == "gamespeed")
selectedGameSpeed = value;
missionOptions[optionId] = value;
}
void BuildOptions(LobbyOption[] allOptions)
{
Widget row = null;
var checkboxColumns = new Queue<CheckboxWidget>();
var dropdownColumns = new Queue<DropDownButtonWidget>();
@@ -364,7 +422,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic
foreach (var option in allOptions.Where(o => o is not LobbyBooleanOption))
{
missionOptions[option.Id] = option.DefaultValue;
if (option.Id == "difficulty")
SetMapDifficulty(option);
else if (option.Id == "gamespeed")
SetMapSpeed(option);
else
missionOptions[option.Id] = option.DefaultValue;
if (dropdownColumns.Count == 0)
{
@@ -378,42 +441,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic
optionsContainer.AddChild(row);
}
var dropdown = dropdownColumns.Dequeue();
var val = dropdownColumns.Dequeue();
SetupDropdown(val, option);
dropdown.GetText = () =>
{
if (option.Values.TryGetValue(missionOptions[option.Id], out var value))
return value;
return FluentProvider.GetMessage(NotAvailable);
};
if (option.Description != null)
{
var (text, desc) = LobbyUtils.SplitOnFirstToken(option.Description);
dropdown.GetTooltipText = () => text;
dropdown.GetTooltipDesc = () => desc;
}
dropdown.IsVisible = () => true;
dropdown.IsDisabled = () => option.IsLocked;
dropdown.OnMouseDown = _ =>
{
ScrollItemWidget SetupItem(KeyValuePair<string, string> c, ScrollItemWidget template)
{
bool IsSelected() => missionOptions[option.Id] == c.Key;
void OnClick() => missionOptions[option.Id] = c.Key;
var item = ScrollItemWidget.Setup(template, IsSelected, OnClick);
item.Get<LabelWidget>("LABEL").GetText = () => c.Value;
return item;
}
dropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", option.Values.Count * 30, option.Values, SetupItem);
};
var label = row.GetOrNull<LabelWidget>(dropdown.Id + "_DESC");
var label = row.GetOrNull<LabelWidget>(val.Id + "_DESC");
if (label != null)
{
label.GetText = () => option.Name + ":";
@@ -422,6 +453,76 @@ namespace OpenRA.Mods.Common.Widgets.Logic
}
}
void BuildMinifiedOptions(LobbyOption[] allOptions)
{
if (difficultyButton != null)
{
var mapDifficulty = allOptions.FirstOrDefault(sld => sld.Id == "difficulty");
if (mapDifficulty != null)
{
SetMapDifficulty(mapDifficulty);
SetupDropdown(difficultyButton, mapDifficulty);
}
else
{
difficultyButton.IsDisabled = () => true;
difficultyButton.GetText = () => unsetDifficulty;
difficultyButton.GetTooltipText = () => defaultTooltop;
}
}
if (gameSpeedButton != null)
{
var gameSpeed = allOptions.FirstOrDefault(sld => sld.Id == "gamespeed");
if (gameSpeed != null)
{
SetMapSpeed(gameSpeed);
SetupDropdown(gameSpeedButton, gameSpeed);
}
else
{
gameSpeedButton.IsDisabled = () => true;
gameSpeedButton.GetText = () => FluentProvider.GetMessage(NotAvailable);
}
}
}
void SetupDropdown(DropDownButtonWidget dropdown, LobbyOption option)
{
dropdown.GetText = () =>
{
if (option.Values.TryGetValue(missionOptions[option.Id], out var value))
return value;
return FluentProvider.GetMessage(NotAvailable);
};
if (option.Description != null)
{
var (text, desc) = LobbyUtils.SplitOnFirstToken(option.Description);
dropdown.GetTooltipText = () => text;
dropdown.GetTooltipDesc = () => desc;
}
dropdown.IsVisible = () => true;
dropdown.IsDisabled = () => option.IsLocked;
dropdown.OnMouseDown = _ =>
{
ScrollItemWidget SetupItem(KeyValuePair<string, string> c, ScrollItemWidget template)
{
bool IsSelected() => missionOptions[option.Id] == c.Key;
void OnClick() => OnOptionSelected(option.Id, c.Key);
var item = ScrollItemWidget.Setup(template, IsSelected, OnClick);
item.Get<LabelWidget>("LABEL").GetText = () => c.Value;
return item;
}
dropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", option.Values.Count * 30, option.Values, SetupItem);
};
}
float cachedSoundVolume;
float cachedMusicVolume;
void MuteSounds()

View File

@@ -14,6 +14,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BeaconLib;
using OpenRA.Graphics;
using OpenRA.Network;
using OpenRA.Primitives;
using OpenRA.Server;
@@ -49,6 +50,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic
[FluentReference("bots")]
const string BotsLabel = "label-bots-count";
[FluentReference]
const string BotPlayer = "label-bot-player";
[FluentReference("spectators")]
const string SpectatorsLabel = "label-spectators-count";
@@ -604,14 +608,21 @@ namespace OpenRA.Mods.Common.Widgets.Logic
foreach (var option in kv.Value)
{
var o = option;
var playerName = new CachedTransform<(MapStatus, int, SpriteFont), string>(s =>
{
var name = o.IsBot
? currentMap.TryGetMessage(o.Name, out var msg) ? msg : FluentProvider.GetMessage(BotPlayer)
: o.Name;
return WidgetUtils.TruncateText(name, s.Item2, s.Item3);
});
var item = ScrollItemWidget.Setup(clientTemplate, () => false, () => { });
if (!o.IsSpectator && server.Mod == modData.Manifest.Id)
{
var label = item.Get<LabelWidget>("LABEL");
var font = Game.Renderer.Fonts[label.Font];
var name = WidgetUtils.TruncateText(o.Name, label.Bounds.Width, font);
label.GetText = () => name;
label.GetText = () => playerName.Update((currentMap.Status, label.Bounds.Width, font));
label.GetColor = () => o.Color;
var flag = item.Get<ImageWidget>("FLAG");
@@ -623,11 +634,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{
var label = item.Get<LabelWidget>("NOFLAG_LABEL");
var font = Game.Renderer.Fonts[label.Font];
var name = WidgetUtils.TruncateText(o.Name, label.Bounds.Width, font);
// Force spectator color to prevent spoofing by the server
var color = o.IsSpectator ? Color.White : o.Color;
label.GetText = () => name;
label.GetText = () => playerName.Update((currentMap.Status, label.Bounds.Width, font));
label.GetColor = () => color;
}
@@ -761,14 +771,22 @@ namespace OpenRA.Mods.Common.Widgets.Logic
if (game.Clients.Length > 0)
{
var displayClients = game.Clients.Select(c => c.Name);
if (game.Clients.Length > 10)
displayClients = displayClients
.Take(9)
.Append(FluentProvider.GetMessage(OtherPlayers, "players", game.Clients.Length - 9));
var preview = modData.MapCache[game.Map];
var tooltip = new CachedTransform<MapStatus, string>(s =>
{
var displayClients = game.Clients.Select(c => c.IsBot
? preview.TryGetMessage(c.Name, out var msg) ? msg : FluentProvider.GetMessage(BotPlayer)
: c.Name);
var tooltip = displayClients.JoinWith("\n");
players.GetTooltipText = () => tooltip;
if (game.Clients.Length > 10)
displayClients = displayClients
.Take(9)
.Append(FluentProvider.GetMessage(OtherPlayers, "players", game.Clients.Length - 9));
return displayClients.JoinWith("\n");
});
players.GetTooltipText = () => tooltip.Update(preview.Status);
}
else
players.GetTooltipText = null;

View File

@@ -40,6 +40,7 @@ namespace OpenRA.Mods.Common.Widgets
readonly Stopwatch playTime = new();
int textureWidth;
int textureHeight;
Sheet videoSheet;
Action onComplete;
@@ -123,7 +124,8 @@ namespace OpenRA.Mods.Common.Widgets
textureWidth = Exts.NextPowerOf2(video.Width);
textureHeight = Exts.NextPowerOf2(video.Height);
var videoSheet = new Sheet(SheetType.BGRA, new Size(textureWidth, textureHeight));
videoSheet?.Dispose();
videoSheet = new Sheet(SheetType.BGRA, new Size(textureWidth, textureHeight));
videoSheet.GetTexture().ScaleFilter = TextureScaleFilter.Linear;
videoSheet.GetTexture().SetData(video.CurrentFrameData, textureWidth, textureHeight);
@@ -301,5 +303,11 @@ namespace OpenRA.Mods.Common.Widgets
Stop();
Video = null;
}
public override void Removed()
{
videoSheet?.Dispose();
overlaySheet?.Dispose();
}
}
}

View File

@@ -3,7 +3,7 @@
<ProjectReference Include="..\OpenRA.Game\OpenRA.Game.csproj" />
<PackageReference Include="OpenRA-Freetype6" Version="1.0.11" />
<PackageReference Include="OpenRA-OpenAL-CS" Version="1.0.22" />
<PackageReference Include="OpenRA-SDL2-CS" Version="1.0.41" />
<PackageReference Include="OpenRA-SDL2-CS" Version="1.0.42" />
</ItemGroup>
<ItemGroup>
<Content Include="OpenRA.Platforms.Default.dll.config" Condition="'$(TargetPlatform)' != 'win-x64' And '$(TargetPlatform)' != 'win-x86'">

View File

@@ -341,7 +341,15 @@ namespace OpenRA.Platforms.Default
// Run graphics rendering on a dedicated thread.
// The calling thread will then have more time to process other tasks, since rendering happens in parallel.
// If the calling thread is the main game thread, this means it can run more logic and render ticks.
Context = new ThreadedGraphicsContext(new Sdl2GraphicsContext(this), vertexBatchSize, indexBatchSize);
// This is disabled when running in windowed mode on Windows because it breaks the ability to minimize/restore the window.
if (Platform.CurrentPlatform == PlatformType.Windows && windowMode == WindowMode.Windowed)
{
var ctx = new Sdl2GraphicsContext(this);
ctx.InitializeOpenGL();
Context = ctx;
}
else
Context = new ThreadedGraphicsContext(new Sdl2GraphicsContext(this), vertexBatchSize, indexBatchSize);
Context.SetVSyncEnabled(Game.Settings.Graphics.VSync);

View File

@@ -21,7 +21,7 @@
</AssemblyAttribute>
</ItemGroup>
<ItemGroup>
<PackageReference Include="OpenRA-SDL2-CS" Version="1.0.41" />
<PackageReference Include="OpenRA-SDL2-CS" Version="1.0.42" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\OpenRA.Game\OpenRA.Game.csproj" />

View File

@@ -73,26 +73,26 @@ vec4 linear2srgb(vec4 c)
return c.a * vec4(linear2srgb(c.r / c.a), linear2srgb(c.g / c.a), linear2srgb(c.b / c.a), 1.0f);
}
ivec2 Size(uint samplerIndex)
vec2 Size(uint samplerIndex)
{
switch (samplerIndex)
{
case 7u:
return textureSize(Texture7, 0);
return vec2(textureSize(Texture7, 0));
case 6u:
return textureSize(Texture6, 0);
return vec2(textureSize(Texture6, 0));
case 5u:
return textureSize(Texture5, 0);
return vec2(textureSize(Texture5, 0));
case 4u:
return textureSize(Texture4, 0);
return vec2(textureSize(Texture4, 0));
case 3u:
return textureSize(Texture3, 0);
return vec2(textureSize(Texture3, 0));
case 2u:
return textureSize(Texture2, 0);
return vec2(textureSize(Texture2, 0));
case 1u:
return textureSize(Texture1, 0);
return vec2(textureSize(Texture1, 0));
default:
return textureSize(Texture0, 0);
return vec2(textureSize(Texture0, 0));
}
}
@@ -160,7 +160,7 @@ void main()
vec4 c;
if (EnablePixelArtScaling)
{
vec2 textureSize = vec2(Size(vChannelSampler));
vec2 textureSize = Size(vChannelSampler);
vec2 vUv = coords.st * textureSize;
vec2 offset = fract(vUv);
vec2 pixelsPerTexel = vec2(1.0 / dFdx(vUv.x), 1.0 / dFdy(vUv.y));

View File

@@ -1,10 +1,10 @@
Metadata:
Title: All mods
Version: {DEV_VERSION}
Version: release-20250330
Hidden: true
FileSystem: DefaultFileSystem
Packages:
Packages:
^EngineDir
Cursors:

View File

@@ -1,6 +1,6 @@
Metadata:
Title: mod-title
Version: {DEV_VERSION}
Version: release-20250330
Hidden: true
FileSystem: DefaultFileSystem

View File

@@ -284,29 +284,27 @@ ScrollPanel@NEWS_PANEL:
Width: 400
Height: 265
Background: panel-black
TopBottomSpacing: 5
TopBottomSpacing: 10
ItemSpacing: 5
Children:
Container@NEWS_ITEM_TEMPLATE:
X: 10
Y: 5
Width: PARENT_WIDTH - 40
Height: 45
Height: 40
Children:
Label@TITLE:
Y: 1
Width: PARENT_WIDTH
Height: 25
Height: 20
Align: Center
Font: Bold
Label@AUTHOR_DATETIME:
Y: 26
Y: 21
Width: PARENT_WIDTH
Height: 15
Align: Center
Font: TinyBold
Label@CONTENT:
Y: 46
Y: 40
Width: PARENT_WIDTH
Label@NEWS_STATUS:
X: 80

View File

@@ -439,7 +439,7 @@ Container@EDITOR_WORLD_ROOT:
Y: 1
Width: 55
Height: 25
Text: label-tools-bg-categories
Text: label-tool-bg-category
Align: Right
Font: TinyBold
DropDownButton@TOOLS_DROPDOWN:
@@ -634,7 +634,7 @@ Container@EDITOR_WORLD_ROOT:
Height: 25
Font: Bold
Align: Left
Text: label-copy-filters
Text: label-selection-filters
Checkbox@COPY_FILTER_TERRAIN_CHECKBOX:
X: 7
Y: 70
@@ -688,6 +688,17 @@ Container@EDITOR_WORLD_ROOT:
Width: 55
Height: 22
Align: Left
Button@SELECTION_DELETE_BUTTON:
X: 7
Y: 222
Width: 75
Height: 25
Text: button-delete-area.label
Font: Bold
Key: EditorDeleteSelection
TooltipTemplate: BUTTON_TOOLTIP
TooltipText: button-delete-area.tooltip
TooltipContainer: TOOLTIP_CONTAINER
Button@SELECTION_CANCEL_BUTTON:
X: 208
Y: 222
@@ -779,8 +790,12 @@ Container@EDITOR_WORLD_ROOT:
X: 7
Width: 75
Height: 25
Text: button-container-delete
Text: button-delete-actor.label
Font: Bold
Key: EditorDeleteSelection
TooltipTemplate: BUTTON_TOOLTIP
TooltipText: button-delete-actor.tooltip
TooltipContainer: TOOLTIP_CONTAINER
Button@CANCEL_BUTTON:
X: 127
Width: 75

View File

@@ -12,7 +12,7 @@ Container@SKIRMISH_STATS:
Width: 80
Height: 20
Font: MediumBold
Text: label-objective-mission
Text: label-mission-objective
Label@STATS_STATUS:
X: 95
Y: 17
@@ -25,7 +25,7 @@ Container@SKIRMISH_STATS:
Width: 482
Height: 20
Font: Bold
Text: checkbox-objective-stats
Text: checkbox-stats-objective
Disabled: true
TextColorDisabled: FFFFFF
Container@STATS_HEADERS:

View File

@@ -489,7 +489,7 @@ Container@OBSERVER_WIDGETS:
Align: Right
Shadow: True
Label@DERRICKS_HEADER:
X: 650
X: 645
Width: 80
Height: PARENT_HEIGHT
Font: Bold

View File

@@ -16,14 +16,14 @@ Background@KICK_CLIENT_DIALOG:
Height: 25
Font: Regular
Align: Center
Text: label-kick-client-dialog-texta
Text: label-kick-client-dialog-text-a
Label@TEXTB:
Y: 86
Width: PARENT_WIDTH
Height: 25
Font: Regular
Align: Center
Text: label-kick-client-dialog-textb
Text: label-kick-client-dialog-text-b
Checkbox@PREVENT_REJOINING_CHECKBOX:
X: (PARENT_WIDTH - WIDTH) / 2
Y: 120
@@ -98,14 +98,14 @@ Background@FORCE_START_DIALOG:
Height: 25
Font: Regular
Align: Center
Text: label-force-start-dialog-texta
Text: label-force-start-dialog-text-a
Label@TEXTB:
Y: 86
Width: PARENT_WIDTH
Height: 25
Font: Regular
Align: Center
Text: label-force-start-dialog-textb
Text: label-force-start-dialog-text-b
Container@KICK_WARNING:
Width: PARENT_WIDTH
Children:

View File

@@ -256,8 +256,8 @@ Container@LOBBY_PLAYER_BIN:
Height: 25
Text: label-lobby-players-faction
Label@TEAM:
X: 435
Width: 44
X: 425
Width: 45
Height: 25
Align: Center
DropDownButton@TEAM_DROPDOWN:

View File

@@ -68,6 +68,44 @@ Container@MISSIONBROWSER_PANEL:
IgnoreMouseOver: True
IgnoreMouseInput: True
ShowSpawnPoints: False
Container@MISSION_MINIFIED_OPTIONS:
Width: PARENT_WIDTH
Height: PARENT_HEIGHT
Visible: False
Children:
LabelForInput@DIFFICULTY_DESC:
Y: PARENT_HEIGHT - HEIGHT + 2
Width: 56
Height: 25
Align: Right
Text: dropdown-missionbrowser-difficulty.label
For: DIFFICULTY
DropDownButton@DIFFICULTY:
X: 61
Y: PARENT_HEIGHT - HEIGHT + 2
Width: 120
Height: 25
Font: Regular
Text: label-missionbrowser-normal-difficulty
TooltipText: dropdown-missionbrowser-difficulty.description
PanelRoot: MISSION_DROPDOWN_PANEL_ROOT
TooltipContainer: TOOLTIP_CONTAINER
LabelForInput@GAMESPEED_DESC:
X: PARENT_WIDTH - WIDTH - 125
Y: PARENT_HEIGHT - HEIGHT + 2
Width: 120
Height: 25
Align: Right
Text: dropdown-missionbrowser-gamespeed
For: GAMESPEED
DropDownButton@GAMESPEED:
X: PARENT_WIDTH - WIDTH
Y: PARENT_HEIGHT - HEIGHT + 2
Width: 120
Height: 25
Font: Regular
PanelRoot: MISSION_DROPDOWN_PANEL_ROOT
TooltipContainer: TOOLTIP_CONTAINER
Container@MISSION_TABS:
Width: PARENT_WIDTH
Y: PARENT_HEIGHT - 31

View File

@@ -90,7 +90,7 @@ Container@ADVANCED_PANEL:
Font: Regular
Text: checkbox-sendsysinfo-container
Label@SENDSYSINFO_DESC:
Y: 15
Y: 30
Width: PARENT_WIDTH
Height: 30
Font: Tiny

View File

@@ -1,7 +1,7 @@
## world
dropdown-difficulty =
.label = Difficulty
.description = The difficulty of the mission.
.description = The difficulty of the mission
options-difficulty =
.easy = Easy
@@ -10,7 +10,7 @@ options-difficulty =
## player
bot-campaign-ai =
.name = Campaign Player AI
.name = Campaign Player AI
## campaign-maprules.yaml
actor-moneycrate-name = Money Crate

View File

@@ -55,7 +55,6 @@ label-save-map-background-directory = Directory:
label-save-map-background-filename = Filename:
button-save-map-panel = Save
label-actor-edit-panel-id = ID
button-container-delete = Delete
button-container-ok = OK
button-editor-world-root-options-tooltip = Menu
label-tiles-bg-search = Search:
@@ -65,12 +64,12 @@ label-actor-bg-owner = Owner:
label-area-selection = Area Selection
label-area-info = Area Info
label-selected-area-diagonal = Diagonal:
label-selected-area-resources= Resources:
label-copy-filters = Copy Filters
label-selected-area-resources = Resources:
label-selection-filters = Filters
label-filter-terrain = Terrain
label-filter-resources = Resources
label-filter-actors = Actors
label-tools-bg-categories = Tool:
label-tool-bg-category = Tool:
button-marker-tiles-clear-current = Clear Current
button-marker-tiles-clear-all = Clear All
label-marker-layer-num-sides = Number of Sides
@@ -85,21 +84,29 @@ button-map-editor-tab-container-actors-tooltip = Actors
button-map-editor-tab-container-tools-tooltip = Tools
button-map-editor-tab-container-history-tooltip = History
button-delete-area =
.label = Delete
.tooltip = Remove everything in the selected area
button-delete-actor =
.label = Delete
.tooltip = Remove the selected actor
button-editor-world-root-copy =
.label = Copy
.tooltip = Copy the selected area
.label = Copy
.tooltip = Copy the selected area
button-editor-world-root-paste =
.label = Paste
.tooltip = Paste the saved area
.label = Paste
.tooltip = Paste the saved area
button-editor-world-root-undo =
.label = Undo
.tooltip = Undo last step
.label = Undo
.tooltip = Undo last step
button-editor-world-root-redo =
.label = Redo
.tooltip = Redo last step
.label = Redo
.tooltip = Redo last step
dropdownbutton-editor-world-root-overlay-button = Overlays
button-select-categories-buttons-all = All
@@ -126,8 +133,8 @@ label-gamesave-loading-screen-desc = Press Escape to cancel loading and return t
## ingame-chat.yaml, ingame-infochat.yaml
button-chat-chrome-mode =
.label = Team
.tooltip = Toggle chat mode
.label = Team
.tooltip = Toggle chat mode
## ingame-debug.yaml
label-debug-panel-title = Debug Options
@@ -166,8 +173,8 @@ label-script-error-panel-desc-b = The details of the error have been saved to lu
label-script-error-panel-desc-c = Please send this file to the map author so that they can fix this issue.
## ingame-infostats.yaml
label-objective-mission = Mission:
checkbox-objective-stats = Destroy all opposition!
label-mission-objective = Mission:
checkbox-stats-objective = Destroy all opposition!
label-stats-player = Player
label-stats-faction = Faction
label-stats-score = Score
@@ -179,20 +186,20 @@ button-replay-player-pause-tooltip = Pause
button-replay-player-play-tooltip = Play
button-replay-player-slow =
.tooltip = Slow speed
.label = 50%
.tooltip = Slow speed
.label = 50%
button-replay-player-regular =
.tooltip = Regular speed
.label = 100%
.tooltip = Regular speed
.label = 100%
button-replay-player-fast =
.tooltip = Fast speed
.label = 200%
.tooltip = Fast speed
.label = 200%
button-replay-player-maximum =
.tooltip = Maximum speed
.label = MAX
.tooltip = Maximum speed
.label = MAX
label-basic-stats-player-header = Player
label-basic-stats-cash-header = Cash
@@ -228,12 +235,13 @@ label-combat-stats-army-value-header = Army Value
label-combat-stats-vision-header = Vision
supportpowers-support-powers-palette =
.ready = Ready
.hold = On Hold
.ready = Ready
.hold = On Hold
button-command-bar-attack-move =
.tooltip = Attack Move
.tooltipdesc = Selected units will move to the desired location
.tooltip = Attack Move
.tooltipdesc =
Selected units will move to the desired location
and attack any enemies they encounter en route.
Hold <(Ctrl)> while targeting to order an Assault Move
@@ -242,8 +250,9 @@ button-command-bar-attack-move =
Left-click icon then right-click on target location.
button-command-bar-force-move =
.tooltip = Force Move
.tooltipdesc = Selected units will move to the desired location
.tooltip = Force Move
.tooltipdesc =
Selected units will move to the desired location
- Default activity for the target is suppressed
- Vehicles will attempt to crush enemies at the target location
- Helicopters will land at the target location
@@ -252,8 +261,9 @@ button-command-bar-force-move =
Hold <(Alt)> to activate temporarily while commanding units.
button-command-bar-force-attack =
.tooltip = Force Attack
.tooltipdesc = Selected units will attack the targeted unit or location
.tooltip = Force Attack
.tooltipdesc =
Selected units will attack the targeted unit or location
- Default activity for the target is suppressed
- Allows targeting of own or ally forces
- Long-range artillery units will always target the
@@ -263,14 +273,16 @@ button-command-bar-force-attack =
Hold <(Ctrl)> to activate temporarily while commanding units.
button-command-bar-guard =
.tooltip = Guard
.tooltipdesc = Selected units will follow the targeted unit.
.tooltip = Guard
.tooltipdesc =
Selected units will follow the targeted unit.
Left-click icon then right-click on target unit.
button-command-bar-deploy =
.tooltip = Deploy
.tooltipdesc = Selected units will perform their default deploy activity
.tooltip = Deploy
.tooltipdesc =
Selected units will perform their default deploy activity
- MCVs will unpack into a Construction Yard
- Construction Yards will re-pack into a MCV
- Transports will unload their passengers
@@ -278,22 +290,25 @@ button-command-bar-deploy =
Acts immediately on selected units.
button-command-bar-scatter =
.tooltip = Scatter
.tooltipdesc = Selected units will stop their current activity
.tooltip = Scatter
.tooltipdesc =
Selected units will stop their current activity
and move to a nearby location.
Acts immediately on selected units.
button-command-bar-stop =
.tooltip = Stop
.tooltipdesc = Selected units will stop their current activity.
.tooltip = Stop
.tooltipdesc =
Selected units will stop their current activity.
Selected buildings will reset their rally point.
Acts immediately on selected targets.
button-command-bar-queue-orders =
.tooltip = Waypoint Mode
.tooltipdesc = Use Waypoint Mode to give multiple linking commands
.tooltip = Waypoint Mode
.tooltipdesc =
Use Waypoint Mode to give multiple linking commands
to the selected units. Units will execute the commands
immediately upon receiving them.
@@ -301,26 +316,30 @@ button-command-bar-queue-orders =
Hold <(Shift)> to activate temporarily while commanding units.
button-stance-bar-attackanything =
.tooltip = Attack Anything Stance
.tooltipdesc = Set the selected units to Attack Anything stance:
.tooltip = Attack Anything Stance
.tooltipdesc =
Set the selected units to Attack Anything stance:
- Units will attack enemy units and structures on sight
- Units will pursue attackers across the battlefield
button-stance-bar-defend =
.tooltip = Defend Stance
.tooltipdesc = Set the selected units to Defend stance:
.tooltip = Defend Stance
.tooltipdesc =
Set the selected units to Defend stance:
- Units will attack enemy units on sight
- Units will not move or pursue enemies
button-stance-bar-returnfire =
.tooltip = Return Fire Stance
.tooltipdesc = Set the selected units to Return Fire stance:
.tooltip = Return Fire Stance
.tooltipdesc =
Set the selected units to Return Fire stance:
- Units will retaliate against enemies that attack them
- Units will not move or pursue enemies
button-stance-bar-holdfire =
.tooltip = Hold Fire Stance
.tooltipdesc = Set the selected units to Hold Fire stance:
.tooltip = Hold Fire Stance
.tooltipdesc =
Set the selected units to Hold Fire stance:
- Units will not fire upon enemies
- Units will not move or pursue enemies
@@ -336,19 +355,19 @@ button-production-types-vehicle-tooltip = Vehicles
button-production-types-aircraft-tooltip = Aircraft
productionpalette-player-widgets-production-palette =
.ready = Ready
.hold = On Hold
.ready = Ready
.hold = On Hold
## lobby-kickdialogs.yaml
label-kick-client-dialog-texta = You may also apply a temporary ban, preventing
label-kick-client-dialog-textb = them from joining for the remainder of this game.
label-kick-client-dialog-text-a = You may also apply a temporary ban, preventing
label-kick-client-dialog-text-b = them from joining for the remainder of this game.
checkbox-kick-client-dialog-prevent-rejoining = Temporarily Ban
button-kick-client-dialog = Kick
label-kick-spectators-dialog-title = Kick Spectators
button-kick-spectators-dialog-ok = Ok
label-force-start-dialog-title = Start Game?
label-force-start-dialog-texta = One or more players are not yet ready.
label-force-start-dialog-textb = Are you sure that you want to force start the game?
label-force-start-dialog-text-a = One or more players are not yet ready.
label-force-start-dialog-text-b = Are you sure that you want to force start the game?
label-kick-warning-a = One or more clients are missing the selected
label-kick-warning-b = map, and will be kicked from the server.
button-force-start-dialog-start = Start
@@ -419,8 +438,8 @@ button-multiplayer-tabs-servers-tab = Servers
button-bg-changemap = Change Map
button-lobbychat-chat-mode =
.label = Team
.tooltip = Toggle chat mode
.label = Team
.tooltip = Toggle chat mode
button-server-lobby-disconnect = Leave Game
button-server-lobby-start-game = Start Game
@@ -512,6 +531,12 @@ button-missionbrowser-panel-stop-briefing-video = Stop Briefing
button-missionbrowser-panel-start-info-video = Watch Info Video
button-missionbrowser-panel-stop-info-video = Stop Info Video
button-missionbrowser-panel-play = Play
dropdown-missionbrowser-difficulty =
.label = Difficulty
.description = The difficulty of the mission
dropdown-missionbrowser-gamespeed = Speed:
label-missionbrowser-normal-difficulty = Normal
## multiplayer-browser.yaml
image-bg-password-protected-tooltip = Requires Password
@@ -632,8 +657,8 @@ checkbox-mute-sound-container = Mute Sound
label-sound-volume-container = Sound Volume:
checkbox-mute-background-music-container =
.label = Mute Menu Music
.tooltip = Mute background music when no specific track is playing
.label = Mute Menu Music
.tooltip = Mute background music when no specific track is playing
label-music-title-volume-container = Music Volume:
label-audio-device-container = Audio Device:
@@ -645,16 +670,16 @@ label-target-lines-dropdown-container = Target Lines:
label-status-bar-dropdown-container-bars = Status Bars:
checkbox-player-stance-colors-container =
.label = Player Relationship Colors
.tooltip = Change player colors based on relationship (own, enemy, ally, neutral)
.label = Player Relationship Colors
.tooltip = Change player colors based on relationship (own, enemy, ally, neutral)
checkbox-ui-feedback-container =
.label = Show UI Feedback Notifications
.tooltip = Show transient text notifications for UI events
.label = Show UI Feedback Notifications
.tooltip = Show transient text notifications for UI events
checkbox-transients-container =
.label = Show Game Event Notifications
.tooltip = Show transient text notifications for game events
.label = Show Game Event Notifications
.tooltip = Show transient text notifications for game events
checkbox-hide-replay-chat-container = Hide Chat in Replays
label-video-section-header = Video
@@ -688,12 +713,12 @@ label-notices-readonly-notice = This hotkey cannot be modified
button-hotkey-remap-dialog-override = Override
button-hotkey-remap-dialog-clear =
.label = Clear
.tooltip = Unbind the hotkey
.label = Clear
.tooltip = Unbind the hotkey
button-hotkey-remap-dialog-reset =
.label = Reset
.tooltip = Reset to default
.label = Reset
.tooltip = Reset to default
## settings-input.yaml
label-zoom-modifier-container = Zoom Modifier:

View File

@@ -105,7 +105,7 @@ destroy-gdi-supporter-houses = Destroy the houses of the GDI supporters
in the village.
## nod06c
infiltrate-barracks-factory-conyard = Infiltrate the barracks, weapons factory and
capture-barracks-factory-conyard = Capture the barracks, weapons factory and
construction yard.
## nod07a

View File

@@ -35,7 +35,7 @@ resource-tiberium = Tiberium
faction-random =
.name = Any
.description = Random Faction
A random faction is chosen at the start of the game.
A random faction is chosen at the start of the game
faction-gdi =
.name = GDI
@@ -60,27 +60,36 @@ notification-structure-lost = Structure lost.
## aircraft.yaml
actor-tran =
.name = Chinook Transport
.description = Fast Infantry Transport Helicopter.
Unarmed.
.encyclopedia = The chinook is a flying transport capable of carrying a detachement of infantry. It is mostly used to transport commandos or engineers for backline-destruction.
.name = Chinook Transport
.description =
Fast Infantry Transport Helicopter.
Unarmed
.encyclopedia =
The chinook is a flying transport capable of carrying a detachement of infantry. It is mostly used to transport commandos or engineers for backline-destruction.
Like all aircraft, it can be handy in a pinch to crush a commando by landing!
actor-heli =
.name = Apache Longbow
.description = Helicopter Gunship with chainguns.
Strong vs. Infantry, Light Vehicles and Aircraft.
Weak vs. Tanks.
.encyclopedia = Has more health than the Orca and acts as a support unit. It works well with artillery, as it can kill approaching light vehicles.
Ammo is automatically reloaded in the air.
.name = Apache Longbow
.description =
Helicopter Gunship with chainguns.
Strong vs Infantry, Light Vehicles and Aircraft
Weak vs Tanks
.encyclopedia =
Has more health than the Orca and acts as a support unit. It works well with artillery, as it can kill approaching light vehicles.
Ammo is automatically reloaded in the air.
actor-orca =
.name = Orca
.description = Helicopter gunship with AG missiles.
Strong vs. Buildings and Tanks.
Weak vs. Infantry.
.encyclopedia = Fast but fragile, the Orca harasses enemy vehicles and aircraft with AT missiles. It is best used opportunistically, and can be kept alive longer with careful micro. Its AA missiles are effective but have a hard time hitting fast-moving air targets.
Ammo is automatically reloaded in the air.
.name = Orca
.description =
Helicopter gunship with AG missiles.
Strong vs Buildings and Tanks
Weak vs Infantry
.encyclopedia =
Fast but fragile, the Orca harasses enemy vehicles and aircraft with AT missiles. It is best used opportunistically, and can be kept alive longer with careful micro. Its AA missiles are effective but have a hard time hitting fast-moving air targets.
Ammo is automatically reloaded in the air.
actor-c17-name = Supply Aircraft
actor-a10-name = A10 Bomber
@@ -181,12 +190,12 @@ meta-civbuildinghusk-generic-name = Civilian Building (Destroyed)
meta-civfield-generic-name = Field
meta-civhaystackorigloo =
.winter-name = Igloo
.summer-name = Haystack
.winter-name = Igloo
.summer-name = Haystack
meta-civhaystackorigloohusk =
.winter-name = Igloo (Destroyed)
.summer-name = Haystack (Destroyed)
.winter-name = Igloo (Destroyed)
.summer-name = Haystack (Destroyed)
meta-tree-name = Tree
meta-treehusk-name = Tree (Burnt)
@@ -197,8 +206,8 @@ meta-helicopterhusk-generic-name = Destroyed Helicopter
meta-bridge-name = Bridge
meta-crate =
.name = Crate
.generic-name = Crate
.name = Crate
.generic-name = Crate
## husks.yaml
actor-mcv-husk-name = Mobile Construction Vehicle (Destroyed)
@@ -219,81 +228,113 @@ actor-truck-husk-name = Supply Truck (Destroyed)
## infantry.yaml
actor-e1 =
.name = Minigunner
.description = General-purpose infantry.
Strong vs. Infantry.
Weak vs. Vehicles.
.encyclopedia = The humble Minigunner is a highly specialized infantry unit designed to soak damage and kill other infantry.
Like most TD units, they can see farther than they can shoot, and work well in packs to eliminate enemy infantry.
.name = Minigunner
.description =
General-purpose infantry.
Strong vs Infantry
Weak vs Vehicles
.encyclopedia =
The humble Minigunner is a highly specialized infantry unit designed to soak damage and kill other infantry.
Like most TD units, they can see farther than they can shoot, and work well in packs to eliminate enemy infantry.
actor-e2 =
.name = Grenadier
.description = Fast infantry with grenades.
Strong vs. Buildings, slow-moving targets.
.encyclopedia = Fast and able to keep up with medium tanks, letting it work well with flanking armies.
While not as powerful as the Minigunner or a Rocket soldier, its speed can be used to your advantage.
.name = Grenadier
.description =
Fast infantry with grenades.
Strong vs Buildings, slow-moving targets
.encyclopedia =
Fast and able to keep up with medium tanks, letting it work well with flanking armies.
While not as powerful as the Minigunner or a Rocket soldier, its speed can be used to your advantage.
actor-e3 =
.name = Rocket Soldier
.description = Anti-tank/Anti-aircraft infantry.
Strong vs. Tanks and Aircraft.
Weak vs. Infantry.
.encyclopedia = Highly vulnerable but excelling at eliminating enemy armor and aircraft. It is the slowest unit in the game, lagging behind other infantry and even mammoth tanks.
Its large weapons range makes up for its slow speed, making it most effective when used defensively, where it can be protected and provide vision.
.name = Rocket Soldier
.description =
Anti-tank/Anti-aircraft infantry.
Strong vs Tanks and Aircraft
Weak vs Infantry
.encyclopedia =
Highly vulnerable but excelling at eliminating enemy armor and aircraft. It is the slowest unit in the game, lagging behind other infantry and even mammoth tanks.
Its large weapons range makes up for its slow speed, making it most effective when used defensively, where it can be protected and provide vision.
actor-e4 =
.name = Flamethrower
.description = Advanced anti-infantry unit.
Strong vs. Infantry and Buildings.
Weak vs. Tanks.
.encyclopedia = Good for flanking and burning down structures. It has more health than a standard Minigunner, but its short weapons range makes it difficult to use in large armies.
.name = Flamethrower
.description =
Advanced anti-infantry unit.
Strong vs Infantry and Buildings
Weak vs Tanks
.encyclopedia =
Good for flanking and burning down structures. It has more health than a standard Minigunner, but its short weapons range makes it difficult to use in large armies.
actor-e5 =
.name = Chemical Warrior
.description = Advanced general-purpose infantry.
Strong vs. all Ground units.
.encyclopedia = Similar stats to the Flamethrower but deals good damage vs. heavy armor. Great for stopping enemy armor from crushing your forces.
It can walk on Tiberium without taking damage, making it particularly useful for harvester harassment on certain maps.
.name = Chemical Warrior
.description =
Advanced general-purpose infantry.
Strong vs all Ground units
.encyclopedia =
Similar stats to the Flamethrower but deals good damage vs heavy armor. Great for stopping enemy armor from crushing your forces.
It can walk on Tiberium without taking damage, making it particularly useful for harvester harassment on certain maps.
actor-e6 =
.name = Engineer
.description = Damages and captures enemy structures.
Repairs destroyed vehicles.
Unarmed.
.encyclopedia = Like the rocket soldier, the Engineer is slow and requires escorts to be used effectively. It can instantly capture structures but is consumed in the process.
Capturing a civilian structure requires only one engineer, while capturing an enemy structure typically requires two engineers, or one if the structure is heavily damaged.
Engineers can also be used to repair friendly structures or restore husks from destroyed vehicles.
.name = Engineer
.description =
Damages and captures enemy structures.
Repairs destroyed vehicles.
Unarmed
.encyclopedia =
Like the rocket soldier, the Engineer is slow and requires escorts to be used effectively. It can instantly capture structures but is consumed in the process.
Capturing a civilian structure requires only one engineer, while capturing an enemy structure typically requires two engineers, or one if the structure is heavily damaged.
Engineers can also be used to repair friendly structures or restore husks from destroyed vehicles.
actor-rmbo =
.name = Commando
.description = Elite sniper infantry unit.
Strong vs Infantry and Buildings.
Weak vs Vehicles.
.encyclopedia = Equipped with a long-range sniper rifle, the Commando fires slowly but can eliminate enemy infantry from a distance when well-supported.
It carries C4, for surprise backline structure destruction.
.name = Commando
.description =
Elite sniper infantry unit.
Strong vs Infantry and Buildings
Weak vs Vehicles
.encyclopedia =
Equipped with a long-range sniper rifle, the Commando fires slowly but can eliminate enemy infantry from a distance when well-supported.
It carries C4, for surprise backline structure destruction.
actor-pvice =
.description = Mutated abomination that spits liquid Tiberium.
Strong vs Infantry and Buildings.
Weak vs Aircraft.
.encyclopedia = A mutated lifeform created from the strange properties of Tiberium, when infantry units are expose to it.
They regenerate health quickly and have a short-range Tiberium weapon.
.description =
Mutated abomination that spits liquid Tiberium.
Strong vs Infantry and Buildings
Weak vs Aircraft
.encyclopedia =
A mutated lifeform created from the strange properties of Tiberium, when infantry units are expose to it.
They regenerate health quickly and have a short-range Tiberium weapon.
actor-steg =
.name = Stegosaurus
.description = A large, heavily built, herbivorous quadruped.
.name = Stegosaurus
.description =
A large, heavily built,
herbivorous quadruped.
actor-trex =
.name = Tyrannosaurus rex
.description = Bipedal carnivore with a massive skull.
.name = Tyrannosaurus rex
.description =
Bipedal carnivore with
a massive skull.
actor-tric =
.name = Triceratops
.description = Quadruped with large bony frill and three horns.
.name = Triceratops
.description =
Quadruped with large bony
frill and three horns.
actor-rapt =
.name = Velociraptor
.description = Bipedal with enlarged sickle-shaped claw on each hindfoot.
.name = Velociraptor
.description =
Bipedal with enlarged sickle-shaped
claw on each hindfoot.
## misc.yaml
actor-wcrate-name = Wooden Crate
@@ -326,155 +367,198 @@ notification-nuclear-weapon-launched = Nuclear weapon launched.
notification-nuclear-warhead-approaching = Nuclear warhead approaching.
actor-fact =
.name = Construction Yard
.description = Builds structures.
.encyclopedia = The core of any base. It produces buildings and defenses while also providing a build radius. It has a large health pool, but is difficult to replace, making it paramount to defend.
It can be unpacked into a mobile version using the deploy key, but takes a small amount of time to do so.
.name = Construction Yard
.description = Builds structures.
.encyclopedia =
The core of any base. It produces buildings and defenses while also providing a build radius. It has a large health pool, but is difficult to replace, making it paramount to defend.
It can be unpacked into a mobile version using the deploy key, but takes a small amount of time to do so.
actor-fact-gdi-name = GDI Construction Yard
actor-fact-nod-name = Nod Construction Yard
actor-nuke =
.name = Power Plant
.description = Generates power.
.encyclopedia = Provides power to the structures in your base. Power output is directly related to the power plant's condition, so protect them during battles.
.name = Power Plant
.description = Generates power.
.encyclopedia =
Provides power to the structures in your base. Power output is directly related to the power plant's condition, so protect them during battles.
actor-nuk2 =
.name = Advanced Power Plant
.description = Provides more power, cheaper than the
.name = Advanced Power Plant
.description =
Provides more power, cheaper than the
standard Power Plant.
.encyclopedia = Provides more power to the structures in your base, making it more cost-effective than the Power Plant. Power output is directly related to its condition, so protect it during battle.
.encyclopedia =
Provides more power to the structures in your base, making it more cost-effective than the Power Plant. Power output is directly related to its condition, so protect it during battle.
actor-proc =
.name = Tiberium Refinery
.description = Processes raw Tiberium
into usable resources.
.encyclopedia = Stores and processes the alien material and resource, Tiberium. Harvesters collect Tiberium in the field and deposit it at the refinery, where it is converted into credits.
The refinery stores Tiberium and immediately deploys a harvester once constructed. 3 harvesters is the maximum it can handle for close refining.
.name = Tiberium Refinery
.description =
Processes raw Tiberium
into usable resources.
.encyclopedia =
Stores and processes the alien material and resource, Tiberium. Harvesters collect Tiberium in the field and deposit it at the refinery, where it is converted into credits.
The refinery stores Tiberium and immediately deploys a harvester once constructed. 3 harvesters is the maximum it can handle for close refining.
actor-silo =
.name = Tiberium Silo
.description = Stores processed Tiberium.
.encyclopedia = Stores credits. If storage is full, harvesters will wait until credits are spent.
Useful when harvesting the more valuable blue Tiberium, as a single refinery cannot store a full load.
.name = Tiberium Silo
.description = Stores processed Tiberium.
.encyclopedia =
Stores credits. If storage is full, harvesters will wait until credits are spent.
Useful when harvesting the more valuable blue Tiberium, as a single refinery cannot store a full load.
actor-pyle =
.name = Barracks
.description = Trains infantry
.encyclopedia = Produces infantry for GDI. Once unlocked, advanced GDI infantry are hard-hitting and fast.
.name = Barracks
.description = Trains infantry.
.encyclopedia =
Produces infantry for GDI. Once unlocked, advanced GDI infantry are hard-hitting and fast.
actor-hand =
.name = Hand of Nod
.description = Trains infantry
.encyclopedia = Produces infantry for Nod. Once unlocked, advanced Nod infantry are slow but have high health pools.
.name = Hand of Nod
.description = Trains infantry.
.encyclopedia =
Produces infantry for Nod. Once unlocked, advanced Nod infantry are slow but have high health pools.
actor-afld =
.name = Airstrip
.description = Provides a dropzone
for vehicle reinforcements.
.encyclopedia = Delivers vehicles by plane for Nod. Nod vehicles are fast but fragile. Produce harvesters at the start to jumpstart your economy.
.name = Airstrip
.description =
Provides a dropzone
for vehicle reinforcements.
.encyclopedia =
Delivers vehicles by plane for Nod. Nod vehicles are fast but fragile. Produce harvesters at the start to jumpstart your economy.
actor-weap =
.name = Weapons Factory
.description = Produces vehicles.
.encyclopedia = Produces vehicles for GDI. GDI vehicles tend to be slow but hard-hitting. Produce harvesters at the start to jumpstart your economy.
.name = Weapons Factory
.description = Produces vehicles.
.encyclopedia =
Produces vehicles for GDI. GDI vehicles tend to be slow but hard-hitting. Produce harvesters at the start to jumpstart your economy.
actor-hpad =
.name = Helipad
.description = Produces and repairs helicopters.
.encyclopedia = Produces and repairs helicopters. Requires a Communications Center to build advanced attack helicopters.
.name = Helipad
.description =
Produces and repairs helicopters.
.encyclopedia =
Produces and repairs helicopters. Requires a Communications Center to build advanced attack helicopters.
actor-hq =
.name = Communications Center
.description = Provides radar and Air Strike support power
.name = Communications Center
.description =
Provides radar and Air Strike support power.
Unlocks higher-tech units and buildings.
Requires power to operate.
.airstrikepower-name = Air Strike
.airstrikepower-description = Deploy an aerial napalm strike.
.airstrikepower-name = Air Strike
.airstrikepower-description = Deploy an aerial napalm strike.
Burns buildings and infantry along a line.
.encyclopedia = Grants the player access to the minimap (top right) when there is sufficient power. Unlocks new units while also granting access to the airstrike support power.
.encyclopedia =
Grants the player access to the minimap (top right) when there is sufficient power. Unlocks new units while also granting access to the airstrike support power.
actor-fix =
.name = Repair Facility
.description = Repairs vehicles.
.encyclopedia = Repairs vehicles. The repair command can be used on units to send them to the nearest repair facility.
.name = Repair Facility
.description = Repairs vehicles.
.encyclopedia =
Repairs vehicles. The repair command can be used on units to send them to the nearest repair facility.
actor-eye =
.name = Advanced Communications Center
.description = Provides radar and Orbital Ion Cannon support power.
.name = Advanced Communications Center
.description =
Provides radar and Orbital Ion Cannon support power.
Unlocks Mammoth Tank and Commando.
Requires power to operate.
.ioncannonpower-name = Ion Cannon
.ioncannonpower-description = Initiates an Ion Cannon strike.
.ioncannonpower-name = Ion Cannon
.ioncannonpower-description = Initiates an Ion Cannon strike.
Applies instant damage to a small area.
.encyclopedia = Unlocks advanced units and the ion cannon support power. If the Communications Center is lost, it restores minimap functionality and unit production.
.encyclopedia =
Unlocks advanced units and the ion cannon support power. If the Communications Center is lost, it restores minimap functionality and unit production.
actor-tmpl =
.name = Temple of Nod
.description = Provides Nuclear Strike support power.
.name = Temple of Nod
.description =
Provides Nuclear Strike support power.
Unlocks Stealth Tank, Chem. Warrior and Obelisk of Light.
Requires power to operate.
.nukepower-name = Nuclear Strike
.nukepower-description = Launches a tactical nuclear warhead.
.nukepower-name = Nuclear Strike
.nukepower-description = Launches a tactical nuclear warhead.
Applies heavy damage over a large area.
.encyclopedia = Unlocks advanced units and the Nuke support power. Keeps unit unlocks online if the Communications Center is lost.
.encyclopedia =
Unlocks advanced units and the Nuke support power. Keeps unit unlocks online if the Communications Center is lost.
actor-gun =
.name = Turret
.description = Basic Anti-Tank base defense.
Strong vs Tanks and Vehicles.
Weak vs Infantry.
.encyclopedia = Base defense armed with an armor-piercing cannon, it deals significant damage to vehicles in range.
.name = Turret
.description =
Basic Anti-Tank base defense.
Strong vs Tanks and Vehicles
Weak vs Infantry
.encyclopedia =
Base defense armed with an armor-piercing cannon, it deals significant damage to vehicles in range.
actor-sam =
.name = SAM Site
.description = Anti-Aircraft base defense.
Strong vs Aircraft.
Cannot target Ground units.
.encyclopedia = Nod anti-air base defense. It remains protected when closed and opens to engage aircraft.
.name = SAM Site
.description =
Anti-Aircraft base defense.
Strong vs Aircraft
Cannot target Ground units
.encyclopedia =
Nod anti-air base defense. It remains protected when closed and opens to engage aircraft.
actor-obli =
.name = Obelisk of Light
.description = Advanced base defense.
Requires power to operate.
Strong vs. all Ground units.
Cannot target Aircraft.
.encyclopedia = Advanced defense for Nod that quickly destroys ground targets with a powerful laser.
.name = Obelisk of Light
.description =
Advanced base defense.
Requires power to operate.
Strong vs all Ground units
Cannot target Aircraft
.encyclopedia =
Advanced defense for Nod that quickly destroys ground targets with a powerful laser.
actor-gtwr =
.name = Guard Tower
.description = Basic defensive structure.
Strong vs. Infantry.
Weak vs. Tanks.
.encyclopedia = Base defense armed with a high-velocity machine gun, it shreds infantry and light vehicles within range with its large area of effect.
.name = Guard Tower
.description =
Basic defensive structure.
Strong vs Infantry
Weak vs Tanks
.encyclopedia =
Base defense armed with a high-velocity machine gun, it shreds infantry and light vehicles within range with its large area of effect.
actor-atwr =
.name = Advanced Guard Tower
.description = All-purpose defensive structure.
Strong vs. Aircraft and Infantry.
Weak vs. Tanks.
.encyclopedia = Advanced defense for GDI that fires volleys of high explosive missiles at both ground and air targets. Effective versus everything.
.name = Advanced Guard Tower
.description =
All-purpose defensive structure.
Strong vs Aircraft and Infantry
Weak vs Tanks
.encyclopedia =
Advanced defense for GDI that fires volleys of high explosive missiles at both ground and air targets. Effective versus everything.
actor-sbag =
.name = Sandbag Barrier
.description = Stops infantry and light vehicles.
Can be crushed by tanks.
.encyclopedia = Blocks movement of infantry and light vehicles, but can be crushed by tanks. Immune to small arms fire.
This barrier can be built in multiple segments once an initial sandbag has been placed.
.name = Sandbag Barrier
.description =
Stops infantry and light vehicles.
Can be crushed by tanks.
.encyclopedia =
Blocks movement of infantry and light vehicles, but can be crushed by tanks. Immune to small arms fire.
This barrier can be built in multiple segments once an initial sandbag has been placed.
actor-cycl =
.name = Chain Link Barrier
.description = Stops infantry and light vehicles.
Can be crushed by tanks.
.encyclopedia = Blocks infantry movement and light vehicles, but can be crushed by tanks. Immune to small arms fire.
This barrier can be built in multiple segments once an initial chain link has been placed.
.name = Chain Link Barrier
.description =
Stops infantry and light vehicles.
Can be crushed by tanks.
.encyclopedia =
Blocks movement of infantry and light vehicles, but can be crushed by tanks. Immune to small arms fire.
This barrier can be built in multiple segments once an initial chain link has been placed.
actor-brik =
.name = Concrete Barrier
.description = Stops infantry and most tanks.
.name = Concrete Barrier
.description =
Stops infantry and most tanks.
Blocks some projectiles.
.encyclopedia = Blocks all unit movement, except for Mammoth Tanks which can crush it. Immune to small arms fire.
This barrier can be built in multiple segments once an initial wall has been placed.
.encyclopedia =
Blocks all unit movement, except for Mammoth Tanks which can crush it. Immune to small arms fire.
This barrier can be built in multiple segments once an initial wall has been placed.
actor-barracks-name = Infantry Production
actor-vehicleproduction-name = Vehicle Production
@@ -487,28 +571,32 @@ actor-hosp-husk-name = Hospital (Destroyed)
actor-bio-husk-name = Biological Lab (Destroyed)
actor-hosp =
.name = Hospital
.captured-desc = Provides infantry with self-healing.
.capturable-desc = Capture to enable self-healing for infantry.
.encyclopedia = Heals friendly infantry over time when captured.
.name = Hospital
.captured-desc = Provides infantry with self-healing.
.capturable-desc = Capture to enable self-healing for infantry.
.encyclopedia =
Heals friendly infantry over time when captured.
actor-miss =
.name = Tech Center
.captured-desc = Provides range of vision.
.capturable-desc = Capture to give visual range.
.encyclopedia = Provides a wide area of vision when captured.
.name = Tech Center
.captured-desc = Provides range of vision.
.capturable-desc = Capture to give visual range.
.encyclopedia =
Provides a wide area of vision when captured.
actor-bio =
.name = Biological Lab
.captured-desc = Provides infantry with Tiberium immunity.
.capturable-desc = Capture to enable Tiberium immunity for infantry.
.encyclopedia = Grants friendly infantry immunity to Tiberium when captured.
.name = Biological Lab
.captured-desc = Provides infantry with Tiberium immunity.
.capturable-desc = Capture to enable Tiberium immunity for infantry.
.encyclopedia =
Grants friendly infantry immunity to Tiberium when captured.
actor-v19 =
.name = Oil Derrick
.captured-desc = Provides additional funds.
.capturable-desc = Capture to receive additional funds.
.encyclopedia = Produces a small amount of income frequently when captured. Makes less money than building a harvester, but provides consistent income.
.name = Oil Derrick
.captured-desc = Provides additional funds.
.capturable-desc = Capture to receive additional funds.
.encyclopedia =
Produces a small amount of income frequently when captured. Makes less money than building a harvester, but provides consistent income.
## trees.yaml
actor-splitblue-name = Blue Blossom Tree
@@ -517,132 +605,174 @@ actor-t13-transformable-name = (Tree that can transform into a Blossom Tree)
## vehicles.yaml
actor-mcv =
.name = Mobile Construction Vehicle
.description = Deploys into a Construction Yard.
Unarmed.
.encyclopedia = Deploying an MCV changes it into a Construction Yard. The MCV has more health in its deployed form, but undeploying can be helpful to escape infantry.
If you have a teammate, they can “gift” you an MCV by destroying an undeployed one near your base, allowing you to reclaim it with an engineer.
.name = Mobile Construction Vehicle
.description =
Deploys into a Construction Yard.
Unarmed
.encyclopedia =
Deploying an MCV changes it into a Construction Yard. The MCV has more health in its deployed form, but undeploying can be helpful to escape infantry.
If you have a teammate, they can “gift” you an MCV by destroying an undeployed one near your base, allowing you to reclaim it with an engineer.
actor-harv =
.name = Harvester
.generic-name = Harvester
.description = Collects Tiberium for processing.
Unarmed.
.encyclopedia = Harvesters slowly mine Tiberium and deposit it in your refinery, where it is converted into credits. They can also be sent to allied refineries to provide funds.
.name = Harvester
.generic-name = Harvester
.description =
Collects Tiberium for processing.
Unarmed
.encyclopedia =
Harvesters slowly mine Tiberium and deposit it in your refinery, where it is converted into credits. They can also be sent to allied refineries to provide funds.
notification-harvester-lost = Harvester lost.
actor-apc =
.name = APC
.description = Armed infantry transport.
.name = APC
.description =
Armed infantry transport.
Can attack Aircraft.
Strong vs. Vehicles.
Weak vs. Infantry.
.encyclopedia = The APC (Armored Personnel Carrier) transports infantry, making it handy for capturing civilian structures. While its flak gun does not do much damage to enemy vehicles, the APC can resist enemy shots while your Hum-vees deal the damage.
It also functions as your primary AA unit; if you hit the stop command, it'll retarget onto any nearby enemy air units.
Strong vs Vehicles
Weak vs Infantry
.encyclopedia =
The APC (Armored Personnel Carrier) transports infantry, making it handy for capturing civilian structures. While its flak gun does not do much damage to enemy vehicles, the APC can resist enemy shots while your Hum-vees deal the damage.
It also functions as your primary AA unit; if you hit the stop command, it'll retarget onto any nearby enemy air units.
actor-arty =
.name = Artillery
.description = Long-range artillery.
Strong vs. Infantry, Vehicles and Buildings.
.encyclopedia = Nod's artillery is a glass cannon, fragile but dealing large amounts of damage at long range. Particularly strong vs structures and infantry, it can destroy stationary tanks.
Highly vulnerable when clumped due to its explosion on death.
.name = Artillery
.description =
Long-range artillery.
Strong vs Infantry, Vehicles and Buildings
.encyclopedia =
Nod's artillery is a glass cannon, fragile but dealing large amounts of damage at long range. Particularly strong vs structures and infantry, it can destroy stationary tanks.
Highly vulnerable when clumped due to its explosion on death.
actor-ftnk =
.name = Flame Tank
.description = Heavily armored flame-throwing vehicle.
Strong vs. Infantry, Buildings and Vehicles.
Weak vs. Tanks.
.encyclopedia = Roast infantry and structures alike, making them great for surprise attacks. Be wary of their splash damage on death, but be aware it can also be used to your advantage.
.name = Flame Tank
.description =
Heavily armored flame-throwing vehicle.
Strong vs Infantry, Buildings and Vehicles
Weak vs Tanks
.encyclopedia =
Roast infantry and structures alike, making them great for surprise attacks. Be wary of their splash damage on death, but be aware it can also be used to your advantage.
actor-bggy =
.name = Nod Buggy
.description = Fast scout and anti-infantry vehicle.
Strong vs. Infantry.
Weak vs. Tanks.
.encyclopedia = Strong versus light vehicles and infantry, Buggies are great at scouting and killing isolated infantry. Though it is a little cheaper and faster than the GDI Hum-vee, they are more fragile.
.name = Nod Buggy
.description =
Fast scout and anti-infantry vehicle.
Strong vs Infantry
Weak vs Tanks
.encyclopedia =
Strong versus light vehicles and infantry, Buggies are great at scouting and killing isolated infantry. Though it is a little cheaper and faster than the GDI Hum-vee, they are more fragile.
actor-bike =
.name = Recon Bike
.description = Fast scout vehicle with rockets.
.name = Recon Bike
.description =
Fast scout vehicle with rockets.
Can attack Aircraft.
Strong vs. Vehicles and Tanks.
Weak vs. Infantry.
.encyclopedia = The Nod bike is a very fast vehicle armed with armor-piercing rockets, which makes it great for hit and run and harvester harassment.
Be wary that its rockets are slow and have a hard time hitting fast-moving targets.
Strong vs Vehicles and Tanks
Weak vs Infantry
.encyclopedia =
The Nod bike is a very fast vehicle armed with armor-piercing rockets, which makes it great for hit and run and harvester harassment.
Be wary that its rockets are slow and have a hard time hitting fast-moving targets.
actor-jeep =
.name = Hum-vee
.description = Fast scout and anti-infantry vehicle.
Strong vs. Infantry.
Weak vs. Tanks.
.encyclopedia = Strong versus light vehicles and infantry, Hum-vees are great at scouting and eliminating isolated infantry.
They're excellent for dealing with Nod bike spam and should be built instead of tanks in such engagements. Although a little slower than the Nod buggy, it has more health.
.name = Hum-vee
.description =
Fast scout and anti-infantry vehicle.
Strong vs Infantry
Weak vs Tanks
.encyclopedia =
Strong versus light vehicles and infantry, Hum-vees are great at scouting and eliminating isolated infantry.
They're excellent for dealing with Nod bike spam and should be built instead of tanks in such engagements. Although a little slower than the Nod buggy, it has more health.
actor-ltnk =
.name = Light Tank
.description = Fast, light tank.
Strong vs. Vehicles and Tanks.
Weak vs. Infantry.
.encyclopedia = With its great speed and high health pool, the light tank works great as a frontline unit in various compositions despite its damage output.
It should be combined with bikes, stealth tanks, or infantry when facing medium tanks.
.name = Light Tank
.description =
Fast, light tank.
Strong vs Vehicles and Tanks
Weak vs Infantry
.encyclopedia =
With its great speed and high health pool, the light tank works great as a frontline unit in various compositions despite its damage output.
It should be combined with bikes, stealth tanks, or infantry when facing medium tanks.
actor-mtnk =
.name = Medium Tank
.description = General-Purpose GDI Tank.
Strong vs. Tanks and Vehicles.
Weak vs. Infantry.
.encyclopedia = The workhorse of GDI, offering a good balance of speed, durability and firepower. It will easily destroy Nod light tanks and can tear through structures if it sneaks into a base.
Mix minigunners with your medium tank armies to deal with enemy infantry.
.name = Medium Tank
.description =
General-purpose GDI Tank.
Strong vs Tanks and Vehicles
Weak vs Infantry
.encyclopedia =
The workhorse of GDI, offering a good balance of speed, durability and firepower. It will easily destroy Nod light tanks and can tear through structures if it sneaks into a base.
Mix minigunners with your medium tank armies to deal with enemy infantry.
actor-htnk =
.name = Mammoth Tank
.description = Heavily armored GDI Tank.
.name = Mammoth Tank
.description =
Heavily armored GDI Tank.
Can attack Aircraft.
Strong vs. Everything.
.encyclopedia = A crawling battle station, capable of self-repair and engaging any threat. It is particularly effective against enemy armor, but can also destroy small amounts of infantry or aircraft.
When in an enemy base, put it on aggressive stance to have it carve a path of destruction as it moves.
Strong vs Everything
.encyclopedia =
A crawling battle station, capable of self-repair and engaging any threat. It is particularly effective against enemy armor, but can also destroy small amounts of infantry or aircraft.
When in an enemy base, put it on aggressive stance to have it carve a path of destruction as it moves.
actor-msam =
.name = Rocket Launcher
.description = Long-range rocket artillery.
Strong vs. all ground units.
.encyclopedia = Referred to as “MLRS” by players (Multiple Launch Rocket System), this artillery platform fires volleys of rockets at distant targets. It is particularly effective against light vehicles and does moderate damage vs infantry, buildings, and heavy armor.
A strong unit to build vs enemy artillery, its high bullet velocity and damage to light vehicles makes it the perfect counter to Nod artillery.
.name = Rocket Launcher
.description =
Long-range rocket artillery.
Strong vs all ground units
.encyclopedia =
Referred to as “MLRS” by players (Multiple Launch Rocket System), this artillery platform fires volleys of rockets at distant targets. It is particularly effective against light vehicles and does moderate damage vs infantry, buildings, and heavy armor.
A strong unit to build vs enemy artillery, its high bullet velocity and damage to light vehicles makes it the perfect counter to Nod artillery.
actor-mlrs =
.name = Mobile SAM.
.description = Powerful anti-air unit.
Cannot attack ground units.
.encyclopedia = Generally referred to as “MSAM” by players, this Nod vehicle is a dedicated anti-air vehicle when their bikes do not cut it. It fires slow-moving missiles with powerful splash damage.
.name = Mobile SAM
.description =
Powerful anti-air unit.
Cannot attack ground units.
.encyclopedia =
Generally referred to as “MSAM” by players, this Nod vehicle is a dedicated anti-air vehicle when their bikes do not cut it. It fires slow-moving missiles with powerful splash damage.
actor-stnk =
.name = Stealth Tank
.description = Long-range missile tank that can cloak.
.name = Stealth Tank
.description =
Long-range missile tank that can cloak.
Can attack Aircraft.
Has weak armor. Can be spotted by infantry and defense structures.
Strong vs. Vehicles and Tanks.
Weak vs. Infantry.
.encyclopedia = Cloaked units that become visible when damaged or firing their weapons. They can also be detected by defenses within a small radius or infantry at a one-cell range.
Their long-range AP missiles are excellent for dealing with enemy armor or air. They can also crush infantry in a pinch.
Has weak armor. Can be spotted by infantry and
defense structures.
Strong vs Vehicles and Tanks
Weak vs Infantry
.encyclopedia =
Cloaked units that become visible when damaged or firing their weapons. They can also be detected by defenses within a small radius or infantry at a one-cell range.
Their long-range AP missiles are excellent for dealing with enemy armor or air. They can also crush infantry in a pinch.
actor-mhq =
.name = Mobile HQ
.description = Mobile base of operations.
.name = Mobile HQ
.description =
Mobile base of operations.
actor-truck =
.name = Supply Truck
.description = Transports cash to other players.
Builds quickly.
Unarmed.
.encyclopedia = Supply trucks are a convenient way to share cash when your ally is out of money, or you can't spend yours fast enough. They build far quicker than their cost suggests.
.name = Supply Truck
.description =
Transports cash to other players.
Builds quickly
Unarmed
.encyclopedia =
Supply trucks are a convenient way to share cash when your ally is out of money, or you can't spend yours fast enough. They build far quicker than their cost suggests.
## ai.yaml
bot-cabal =
.name = Cabal
.name = Cabal
bot-watson =
.name = Watson
.name = Watson
bot-hal9001 =
.name = HAL 9001
.name = HAL 9001

View File

@@ -62,7 +62,7 @@ WorldLoaded = function()
InitObjectives(Nod)
NodObjective1 = AddPrimaryObjective(Nod, "steal-nuclear-detonator")
InfiltrateObjective = AddSecondaryObjective(Nod, "infiltrate-barracks-factory-conyard")
CaptureObjective = AddSecondaryObjective(Nod, "capture-barracks-factory-conyard")
InsertNodUnits()
@@ -111,18 +111,18 @@ WorldLoaded = function()
Trigger.AfterDelay(ProductionDelay, ProdTriggerFunction)
Trigger.OnAnyKilled(BuildingsToCapture, function()
if not Nod.IsObjectiveCompleted(InfiltrateObjective) then
Nod.MarkFailedObjective(InfiltrateObjective)
if not Nod.IsObjectiveCompleted(CaptureObjective) then
Nod.MarkFailedObjective(CaptureObjective)
end
end)
local captured = 0
Utils.Do(BuildingsToCapture, function(building)
local captured = 0
Trigger.OnCapture(building, function()
captured = captured + 1
if captured == 3 then
Nod.MarkCompletedObjective(InfiltrateObjective)
Nod.MarkCompletedObjective(CaptureObjective)
end
end)
end)

View File

@@ -1,6 +1,6 @@
Metadata:
Title: mod-title
Version: {DEV_VERSION}
Version: release-20250330
Website: https://www.openra.net
WebIcon32: https://www.openra.net/images/icons/cnc_32x32.png
WindowTitle: mod-windowtitle
@@ -8,13 +8,13 @@ Metadata:
PackageFormats: Mix
FileSystem: ContentInstallerFileSystem
Packages:
SystemPackages:
^EngineDir
$cnc: cnc
^EngineDir|mods/common: common
~^SupportDir|Content/cnc: content
cnc|scripts
common|scripts
cnc|scripts
cnc|uibits
ContentPackages:
content|speech.mix
@@ -30,6 +30,7 @@ FileSystem: ContentInstallerFileSystem
~content|scores-covertops.mix
~content|transit.mix
~content|general.mix
# Below are mod-provided (system) packages that need to be loaded after the content packages so they can override content assets.
cnc|bits/snow.mix
cnc|bits
cnc|bits/jungle
@@ -39,7 +40,7 @@ FileSystem: ContentInstallerFileSystem
MapFolders:
cnc|maps: System
~^SupportDir|maps/cnc/{DEV_VERSION}: User
~^SupportDir|maps/cnc/release-20250330: User
Rules:
cnc|rules/misc.yaml

View File

@@ -69,8 +69,6 @@ tc04:
SNOW: tc04.sno
JUNGLE: tc04.jun
idle:
damaged-idle:
Start: 1
tc04.husk:
Defaults:
@@ -80,7 +78,7 @@ tc04.husk:
SNOW: tc04.sno
JUNGLE: tc04.jun
idle:
Start: 2
Start: 1
tc05:
Defaults:
@@ -90,8 +88,6 @@ tc05:
SNOW: tc05.sno
JUNGLE: tc05.jun
idle:
damaged-idle:
Start: 1
tc05.husk:
Defaults:
@@ -101,7 +97,7 @@ tc05.husk:
SNOW: tc05.sno
JUNGLE: tc05.jun
idle:
Start: 2
Start: 1
tc03:
Defaults:

View File

@@ -152,32 +152,30 @@ ScrollPanel@SPECTATOR_LABEL_DROPDOWN_TEMPLATE:
ScrollPanel@NEWS_PANEL:
Width: 400
Height: 265
TopBottomSpacing: 5
TopBottomSpacing: 15
ItemSpacing: 5
Children:
Container@NEWS_ITEM_TEMPLATE:
X: 10
Y: 5
Width: PARENT_WIDTH - 40
Height: 45
Height: 40
Children:
Label@TITLE:
Width: PARENT_WIDTH
Height: 25
Height: 15
Align: Center
Font: Bold
Label@AUTHOR_DATETIME:
Y: 25
Y: 20
Width: PARENT_WIDTH
Height: 15
Align: Center
Font: TinyBold
Label@CONTENT:
Y: 45
Y: 40
Width: PARENT_WIDTH
Label@NEWS_STATUS:
X: 80
Y: 0
Width: PARENT_WIDTH - 80 - 80 - 24
Height: PARENT_HEIGHT
Align: Center

View File

@@ -399,7 +399,7 @@ Container@EDITOR_WORLD_ROOT:
Y: 12
Width: 55
Height: 25
Text: label-tools-bg-categories
Text: label-tool-bg-category
Align: Right
Font: TinyBold
DropDownButton@TOOLS_DROPDOWN:
@@ -600,7 +600,7 @@ Container@EDITOR_WORLD_ROOT:
Height: 25
Font: Bold
Align: Left
Text: label-copy-filters
Text: label-selection-filters
Checkbox@COPY_FILTER_TERRAIN_CHECKBOX:
X: 5
Y: 70
@@ -654,6 +654,17 @@ Container@EDITOR_WORLD_ROOT:
Width: 55
Height: 22
Align: Left
Button@SELECTION_DELETE_BUTTON:
X: 5
Y: 222
Width: 75
Height: 25
Text: button-delete-area.label
Font: Bold
Key: EditorDeleteSelection
TooltipTemplate: BUTTON_TOOLTIP
TooltipText: button-delete-area.tooltip
TooltipContainer: TOOLTIP_CONTAINER
Button@SELECTION_CANCEL_BUTTON:
X: 208
Y: 222
@@ -747,8 +758,12 @@ Container@EDITOR_WORLD_ROOT:
X: 5
Width: 75
Height: 25
Text: button-container-delete
Text: button-delete-actor.label
Font: Bold
Key: EditorDeleteSelection
TooltipTemplate: BUTTON_TOOLTIP
TooltipText: button-delete-actor.tooltip
TooltipContainer: TOOLTIP_CONTAINER
Button@CANCEL_BUTTON:
X: 127
Width: 75

View File

@@ -12,7 +12,7 @@ Container@SKIRMISH_STATS:
Width: 482
Height: 25
Font: MediumBold
Text: label-objective-mission
Text: label-mission-objective
Label@STATS_STATUS:
X: 100
Y: 22
@@ -25,7 +25,7 @@ Container@SKIRMISH_STATS:
Width: 482
Height: 20
Font: Bold
Text: checkbox-objective-stats
Text: checkbox-stats-objective
Disabled: true
TextColorDisabled: FFFFFF
Container@STATS_HEADERS:

View File

@@ -16,14 +16,14 @@ Background@KICK_CLIENT_DIALOG:
Height: 25
Font: Regular
Align: Center
Text: label-kick-client-dialog-texta
Text: label-kick-client-dialog-text-a
Label@TEXTB:
Y: 86
Width: PARENT_WIDTH
Height: 25
Font: Regular
Align: Center
Text: label-kick-client-dialog-textb
Text: label-kick-client-dialog-text-b
Checkbox@PREVENT_REJOINING_CHECKBOX:
X: (PARENT_WIDTH - WIDTH) / 2
Y: 120
@@ -97,14 +97,14 @@ Background@FORCE_START_DIALOG:
Height: 25
Font: Regular
Align: Center
Text: label-force-start-dialog-texta
Text: label-force-start-dialog-text-a
Label@TEXTB:
Y: 86
Width: PARENT_WIDTH
Height: 25
Font: Regular
Align: Center
Text: label-force-start-dialog-textb
Text: label-force-start-dialog-text-b
Container@KICK_WARNING:
Width: PARENT_WIDTH
Children:

View File

@@ -252,8 +252,8 @@ Container@LOBBY_PLAYER_BIN:
Height: 25
Text: label-lobby-players-faction
Label@TEAM:
X: 420
Width: 42
X: 410
Width: 43
Height: 25
Text: label-lobby-players-team
Align: Center

View File

@@ -76,6 +76,44 @@ Background@MISSIONBROWSER_PANEL:
Height: 31
Font: Bold
Text: button-missionbrowser-panel-mission-options
Container@MISSION_MINIFIED_OPTIONS:
Width: PARENT_WIDTH
Height: PARENT_HEIGHT
Visible: False
Children:
LabelForInput@DIFFICULTY_DESC:
Y: PARENT_HEIGHT - HEIGHT
Width: 56
Height: 25
Align: Right
Text: dropdown-missionbrowser-difficulty.label
For: DIFFICULTY
DropDownButton@DIFFICULTY:
X: 61
Y: PARENT_HEIGHT - HEIGHT
Width: 135
Height: 25
Font: Regular
Text: label-missionbrowser-normal-difficulty
TooltipText: dropdown-missionbrowser-difficulty.description
PanelRoot: MISSION_DROPDOWN_PANEL_ROOT
TooltipContainer: TOOLTIP_CONTAINER
LabelForInput@GAMESPEED_DESC:
X: PARENT_WIDTH - WIDTH - 115
Y: PARENT_HEIGHT - HEIGHT
Width: 120
Height: 25
Align: Right
Text: dropdown-missionbrowser-gamespeed
For: GAMESPEED
DropDownButton@GAMESPEED:
X: PARENT_WIDTH - WIDTH
Y: PARENT_HEIGHT - HEIGHT
Width: 110
Height: 25
Font: Regular
PanelRoot: MISSION_DROPDOWN_PANEL_ROOT
TooltipContainer: TOOLTIP_CONTAINER
Container@MISSION_DETAIL:
Y: 212
Width: PARENT_WIDTH

View File

@@ -90,7 +90,7 @@ Container@ADVANCED_PANEL:
Font: Regular
Text: checkbox-sendsysinfo-container
Label@SENDSYSINFO_DESC:
Y: 15
Y: 30
Width: PARENT_WIDTH
Height: 30
Font: Tiny

View File

@@ -52,7 +52,6 @@ label-save-map-panel-directory = Directory:
label-save-map-panel-filename = Filename:
button-save-map-panel = Save
label-actor-edit-panel-id = ID
button-container-delete = Delete
button-container-ok = OK
label-tiles-bg-search = Search:
label-bg-filter = Filter:
@@ -61,12 +60,12 @@ label-actor-bg-owner = Owner:
label-area-selection = Area Selection
label-area-info = Area Info
label-selected-area-diagonal = Diagonal:
label-selected-area-resources= Resources:
label-copy-filters = Copy Filters
label-selected-area-resources = Resources:
label-selection-filters = Filters
label-filter-terrain = Terrain
label-filter-resources = Resources
label-filter-actors = Actors
label-tools-bg-categories = Tool:
label-tool-bg-category = Tool:
button-marker-tiles-clear-current = Clear Current
button-marker-tiles-clear-all = Clear All
label-marker-layer-num-sides = Number of Sides
@@ -81,25 +80,33 @@ button-map-editor-tab-container-actors-tooltip = Actors
button-map-editor-tab-container-tools-tooltip = Tools
button-map-editor-tab-container-history-tooltip = History
button-delete-area =
.label = Delete
.tooltip = Remove everything in the selected area
button-delete-actor =
.label = Delete
.tooltip = Remove the selected actor
button-editor-world-root-options =
.label = Menu
.tooltip = Menu
.label = Menu
.tooltip = Menu
button-editor-world-root-copy =
.label = Copy
.tooltip = Copy the selected area
.label = Copy
.tooltip = Copy the selected area
button-editor-world-root-paste =
.label = Paste
.tooltip = Paste the saved area
.label = Paste
.tooltip = Paste the saved area
button-editor-world-root-undo =
.label = Undo
.tooltip = Undo last step
.label = Undo
.tooltip = Undo last step
button-editor-world-root-redo =
.label = Redo
.tooltip = Redo last step
.label = Redo
.tooltip = Redo last step
dropdownbutton-editor-world-root-overlay-button = Overlays
button-select-categories-buttons-all = All
@@ -119,8 +126,8 @@ button-gamesave-browser-panel-save = Save
## ingame-chat.yaml, ingame-infochat.yaml
button-chat-chrome-mode =
.label = Team
.tooltip = Toggle chat mode
.label = Team
.tooltip = Toggle chat mode
## ingame-debug-hpf.yaml
dropdownbutton-hpf-overlay-locomotor = Select Locomotor
@@ -156,8 +163,8 @@ label-script-error-panel-desc-b = The details of the error have been saved to lu
label-script-error-panel-desc-c = Please send this file to the map author so that they can fix this issue.
## ingame-infostats.yaml
label-objective-mission = Mission:
checkbox-objective-stats = Destroy all opposition!
label-mission-objective = Mission:
checkbox-stats-objective = Destroy all opposition!
label-stats-player = Player
label-stats-faction = Faction
label-stats-score = Score
@@ -167,15 +174,15 @@ label-stats-actions = Actions
label-menu-buttons-title = Options
## lobby-kickdialogs.yaml
label-kick-client-dialog-texta = You may also apply a temporary ban, preventing
label-kick-client-dialog-textb = them from joining for the remainder of this game.
label-kick-client-dialog-text-a = You may also apply a temporary ban, preventing
label-kick-client-dialog-text-b = them from joining for the remainder of this game.
checkbox-kick-client-dialog-prevent-rejoining = Temporarily Ban
button-kick-client-dialog = Kick
label-kick-spectators-dialog-title = Kick Spectators
button-kick-spectators-dialog-ok = Ok
label-force-start-dialog-title = Start Game?
label-force-start-dialog-texta = One or more players are not yet ready.
label-force-start-dialog-textb = Are you sure that you want to force start the game?
label-force-start-dialog-text-a = One or more players are not yet ready.
label-force-start-dialog-text-b = Are you sure that you want to force start the game?
label-kick-warning-a = One or more clients are missing the selected
label-kick-warning-b = map, and will be kicked from the server.
button-force-start-dialog-start = Start
@@ -251,8 +258,8 @@ button-multiplayer-tabs-servers-tab = Servers
button-server-lobby-changemap = Change Map
button-lobbychat-chat-mode =
.label = Team
.tooltip = Toggle chat mode
.label = Team
.tooltip = Toggle chat mode
button-server-lobby-start-game = Start Game
button-server-lobby-disconnect = Leave Game
@@ -343,6 +350,12 @@ button-missionbrowser-panel-stop-info-video = Stop Info Video
button-missionbrowser-panel-play = Play
button-missionbrowser-panel-mission-info = Mission Info
button-missionbrowser-panel-mission-options = Options
dropdown-missionbrowser-difficulty =
.label = Difficulty
.description = The difficulty of the mission
dropdown-missionbrowser-gamespeed = Speed:
label-missionbrowser-normal-difficulty = Normal
## multiplayer-browser.yaml
image-multiplayer-panel-password-protected-tooltip = Requires Password
@@ -454,8 +467,8 @@ checkbox-mute-sound-container = Mute Sound
label-sound-volume-container = Sound Volume:
checkbox-mute-background-music-container =
.label = Mute Menu Music
.tooltip = Mute background music when no specific track is playing
.label = Mute Menu Music
.tooltip = Mute background music when no specific track is playing
label-music-title-volume-container = Music Volume:
label-audio-device-container = Audio Device:
@@ -467,16 +480,16 @@ label-target-lines-dropdown-container = Target Lines:
label-status-bar-dropdown-container-bars = Status Bars:
checkbox-player-stance-colors-container =
.label = Player Relationship Colors
.tooltip = Change player colors based on relationship (own, enemy, ally, neutral)
.label = Player Relationship Colors
.tooltip = Change player colors based on relationship (own, enemy, ally, neutral)
checkbox-ui-feedback-container =
.label = Show UI Feedback Notifications
.tooltip = Show transient text notifications for UI events
.label = Show UI Feedback Notifications
.tooltip = Show transient text notifications for UI events
checkbox-transients-container =
.label = Show Game Event Notifications
.tooltip = Show transient text notifications for game events
.label = Show Game Event Notifications
.tooltip = Show transient text notifications for game events
checkbox-pause-shellmap-container = Pause Menu Background
checkbox-hide-replay-chat-container = Hide Chat in Replays
@@ -511,12 +524,12 @@ label-notices-readonly-notice = This hotkey cannot be modified
button-hotkey-remap-dialog-override = Override
button-hotkey-remap-dialog-clear =
.label = Clear
.tooltip = Unbind the hotkey
.label = Clear
.tooltip = Unbind the hotkey
button-hotkey-remap-dialog-reset =
.label = Reset
.tooltip = Reset to default
.label = Reset
.tooltip = Reset to default
## settings-input.yaml
label-zoom-modifier-container = Zoom Modifier:

View File

@@ -17,7 +17,6 @@ notification-insufficient-enabled-spawn-points = Unable to start the game until
notification-malformed-command = Malformed { $command } command.
notification-state-unchanged-ready = Cannot change state when marked as ready.
notification-invalid-faction-selected = Invalid faction selected: { $faction }.
notification-supported-factions = Supported values: { $factions }.
notification-state-unchanged-game-started = State cannot be changed once the game has started ({ $command }).
notification-requires-host = Only the host can do that.
notification-invalid-bot-slot = Cannot add bots to a slot with another client.
@@ -206,6 +205,9 @@ label-chat-availability =
*[other] Chat available in { $seconds } seconds...
}
## LobbyLogic, ServerListLogic
label-bot-player = AI Player
## IngameMenuLogic
menu-ingame =
.leave = Leave
@@ -804,6 +806,7 @@ notification-copied-tiles =
## EditorDefaultBrush
notification-selected-area = Selected area { $x },{ $y } ({ $width },{ $height })
notification-removed-area = Removed area { $x },{ $y } ({ $width },{ $height })
notification-selected-actor = Selected actor { $id }
notification-cleared-selection = Cleared selection
notification-removed-actor = Removed { $name } ({ $id })

View File

@@ -70,6 +70,7 @@ hotkey-description-editorredo = Redo
hotkey-description-editorcopy = Copy
hotkey-description-editorquicksave = Save Map
hotkey-description-editorpaste = Paste
hotkey-description-editordeleteselection = Delete Selection
hotkey-description-editorselecttab = Select Tab
hotkey-description-editortilestab = Tiles Tab
hotkey-description-editoroverlaystab = Overlays Tab

View File

@@ -34,6 +34,13 @@ EditorPaste: V Ctrl
Platform:
OSX: V Meta
EditorDeleteSelection: DELETE
Description: hotkey-description-editordeleteselection
Types: Editor
Contexts: editor
Platform:
OSX: BACKSPACE
EditorSelectTab: E
Description: hotkey-description-editorselecttab
Types: Editor

View File

@@ -1,6 +1,6 @@
Metadata:
Title: mod-title
Version: {DEV_VERSION}
Version: release-20250330
Hidden: true
FileSystem: DefaultFileSystem

View File

@@ -116,32 +116,30 @@ ScrollPanel@SPECTATOR_DROPDOWN_TEMPLATE:
ScrollPanel@NEWS_PANEL:
Width: 400
Height: 265
TopBottomSpacing: 5
TopBottomSpacing: 15
ItemSpacing: 5
Children:
Container@NEWS_ITEM_TEMPLATE:
X: 10
Y: 5
Width: PARENT_WIDTH - 40
Height: 45
Height: 40
Children:
Label@TITLE:
Width: PARENT_WIDTH
Height: 25
Height: 15
Align: Center
Font: Bold
Label@AUTHOR_DATETIME:
Y: 25
Y: 20
Width: PARENT_WIDTH
Height: 15
Align: Center
Font: TinyBold
Label@CONTENT:
Y: 45
Y: 40
Width: PARENT_WIDTH
Label@NEWS_STATUS:
X: 80
Y: 0
Width: PARENT_WIDTH - 80 - 80 - 24
Height: PARENT_HEIGHT
Align: Center

View File

@@ -12,7 +12,7 @@ Container@SKIRMISH_STATS:
Width: 482
Height: 25
Font: MediumBold
Text: label-objective-mission
Text: label-mission-objective
Label@STATS_STATUS:
X: 100
Y: 22
@@ -25,7 +25,7 @@ Container@SKIRMISH_STATS:
Width: 482
Height: 20
Font: Bold
Text: checkbox-objective-stats
Text: checkbox-stats-objective
Disabled: true
TextColorDisabled: FFFFFF
Container@STATS_HEADERS:

View File

@@ -252,8 +252,8 @@ Container@LOBBY_PLAYER_BIN:
Height: 25
Text: label-lobby-players-faction
Label@TEAM:
X: 420
Width: 42
X: 410
Width: 43
Height: 25
Align: Center
Text: label-lobby-players-team

View File

@@ -144,6 +144,45 @@ Background@MISSIONBROWSER_PANEL:
Visible: False
PanelRoot: MISSION_DROPDOWN_PANEL_ROOT
TooltipContainer: TOOLTIP_CONTAINER
Container@MISSION_MINIFIED_OPTIONS:
X: 220
Width: PARENT_WIDTH - 220
Height: PARENT_HEIGHT
Visible: False
Children:
LabelForInput@DIFFICULTY_DESC:
Y: PARENT_HEIGHT - 80
Width: 56
Height: 25
Align: Right
Text: dropdown-missionbrowser-difficulty.label
For: DIFFICULTY
DropDownButton@DIFFICULTY:
X: 71
Y: PARENT_HEIGHT - 80
Width: 140
Height: 25
Font: Regular
Text: label-missionbrowser-normal-difficulty
TooltipText: dropdown-missionbrowser-difficulty.description
PanelRoot: MISSION_DROPDOWN_PANEL_ROOT
TooltipContainer: TOOLTIP_CONTAINER
LabelForInput@GAMESPEED_DESC:
X: PARENT_WIDTH - 140 - WIDTH - 35
Y: PARENT_HEIGHT - 80
Width: 56
Height: 25
Align: Right
Text: dropdown-missionbrowser-gamespeed
For: GAMESPEED
DropDownButton@GAMESPEED:
X: PARENT_WIDTH - 160
Y: PARENT_HEIGHT - 80
Width: 140
Height: 25
Font: Regular
PanelRoot: MISSION_DROPDOWN_PANEL_ROOT
TooltipContainer: TOOLTIP_CONTAINER
Button@START_BRIEFING_VIDEO_BUTTON:
X: 20
Y: PARENT_HEIGHT - 45

View File

@@ -1,7 +1,7 @@
## world
dropdown-difficulty =
.label = Difficulty
.description = The difficulty of the mission.
.description = The difficulty of the mission
options-difficulty =
.easy = Easy
@@ -10,7 +10,7 @@ options-difficulty =
## player
bot-campaign-ai =
.name = Campaign Player AI
.name = Campaign Player AI
## campaign-tooltips.yaml
neutral-prefix = Neutral

View File

@@ -10,20 +10,20 @@ button-replay-player-pause-tooltip = Pause
button-replay-player-play-tooltip = Play
button-replay-player-slow =
.tooltip = Slow speed
.label = 50%
.tooltip = Slow speed
.label = 50%
button-replay-player-regular =
.tooltip = Regular speed
.label = 100%
.tooltip = Regular speed
.label = 100%
button-replay-player-fast =
.tooltip = Fast speed
.label = 200%
.tooltip = Fast speed
.label = 200%
button-replay-player-maximum =
.tooltip = Maximum speed
.label = MAX
.tooltip = Maximum speed
.label = MAX
label-basic-stats-player-header = Player
label-basic-stats-cash-header = Cash
@@ -63,12 +63,13 @@ label-mute-indicator = Audio Muted
## ingame-player.yaml
supportpowers-support-powers-palette =
.ready = READY
.hold = ON HOLD
.ready = READY
.hold = ON HOLD
button-command-bar-attack-move =
.tooltip = Attack Move
.tooltipdesc = Selected units will move to the desired location
.tooltip = Attack Move
.tooltipdesc =
Selected units will move to the desired location
and attack any enemies they encounter en route.
Hold <(Ctrl)> while targeting to order an Assault Move
@@ -77,8 +78,9 @@ button-command-bar-attack-move =
Left-click icon then right-click on target location.
button-command-bar-force-move =
.tooltip = Force Move
.tooltipdesc = Selected units will move to the desired location
.tooltip = Force Move
.tooltipdesc =
Selected units will move to the desired location
- Default activity for the target is suppressed
- Vehicles will attempt to crush enemies at the target location
- Deployed thumpers will undeploy and move to the target location
@@ -87,8 +89,9 @@ button-command-bar-force-move =
Hold <(Alt)> to activate temporarily while commanding units.
button-command-bar-force-attack =
.tooltip = Force Attack
.tooltipdesc = Selected units will attack the targeted unit or location
.tooltip = Force Attack
.tooltipdesc =
Selected units will attack the targeted unit or location
- Default activity for the target is suppressed
- Allows targeting of own or ally forces
@@ -96,14 +99,16 @@ button-command-bar-force-attack =
Hold <(Ctrl)> to activate temporarily while commanding units.
button-command-bar-guard =
.tooltip = Guard
.tooltipdesc = Selected units will follow the targeted unit.
.tooltip = Guard
.tooltipdesc =
Selected units will follow the targeted unit.
Left-click icon then right-click on target unit.
button-command-bar-deploy =
.tooltip = Deploy
.tooltipdesc = Selected units will perform their default deploy activity
.tooltip = Deploy
.tooltipdesc =
Selected units will perform their default deploy activity
- MCVs will unpack into a Construction Yard
- Thumpers will start or stop attracting worms
- Devastators will become immobilized and explode
@@ -111,22 +116,25 @@ button-command-bar-deploy =
Acts immediately on selected units.
button-command-bar-scatter =
.tooltip = Scatter
.tooltipdesc = Selected units will stop their current activity
.tooltip = Scatter
.tooltipdesc =
Selected units will stop their current activity
and move to a nearby location.
Acts immediately on selected units.
button-command-bar-stop =
.tooltip = Stop
.tooltipdesc = Selected units will stop their current activity.
.tooltip = Stop
.tooltipdesc =
Selected units will stop their current activity.
Selected buildings will reset their rally point.
Acts immediately on selected targets.
button-command-bar-queue-orders =
.tooltip = Waypoint Mode
.tooltipdesc = Use Waypoint Mode to give multiple linking commands
.tooltip = Waypoint Mode
.tooltipdesc =
Use Waypoint Mode to give multiple linking commands
to the selected units. Units will execute the commands
immediately upon receiving them.
@@ -134,26 +142,30 @@ button-command-bar-queue-orders =
Hold <(Shift)> to activate temporarily while commanding units.
button-stance-bar-attackanything =
.tooltip = Attack Anything Stance
.tooltipdesc = Set the selected units to Attack Anything stance:
.tooltip = Attack Anything Stance
.tooltipdesc =
Set the selected units to Attack Anything stance:
- Units will attack enemy units and structures on sight
- Units will pursue attackers across the battlefield
button-stance-bar-defend =
.tooltip = Defend Stance
.tooltipdesc = Set the selected units to Defend stance:
.tooltip = Defend Stance
.tooltipdesc =
Set the selected units to Defend stance:
- Units will attack enemy units on sight
- Units will not move or pursue enemies
button-stance-bar-returnfire =
.tooltip = Return Fire Stance
.tooltipdesc = Set the selected units to Return Fire stance:
.tooltip = Return Fire Stance
.tooltipdesc =
Set the selected units to Return Fire stance:
- Units will retaliate against enemies that attack them
- Units will not move or pursue enemies
button-stance-bar-holdfire =
.tooltip = Hold Fire Stance
.tooltipdesc = Set the selected units to Hold Fire stance:
.tooltip = Hold Fire Stance
.tooltipdesc =
Set the selected units to Hold Fire stance:
- Units will not fire upon enemies
- Units will not move or pursue enemies
@@ -164,8 +176,8 @@ button-top-buttons-power-tooltip = Power Down
button-top-buttons-options-tooltip = Options
productionpalette-sidebar-production-palette =
.ready = READY
.hold = ON HOLD
.ready = READY
.hold = ON HOLD
button-production-types-building-tooltip = Buildings
button-production-types-infantry-tooltip = Infantry

View File

@@ -17,7 +17,7 @@ not-enough-silos = We don't have enough silo space to store the required amount
## atreides-01ab, harkonnen-01ab, ordos01ab
build-concrete = Build a concrete foundation before placing structures.
build-windtrap = Build a Wind Trap to generate power.
build-wind-trap = Build a Wind Trap to generate power.
build-refinery = Build a Refinery to collect Spice.
build-silo = Build a Silo to store additional Spice.
@@ -43,7 +43,7 @@ eliminate-ordos-units-reinforcements = Eliminate all Ordos forces and reinforcem
destroy-harkonnen = Destroy the Harkonnen.
## atreides-04
fremen-leader = Fremen Leader.
fremen-leader = Fremen Leader
sietch-integrity = Sietch structural integrity: { $integrity }%
protect-fremen-sietch = Protect the Fremen Sietch.
keep-sietch-intact = Keep the Sietch { $integrity }% intact!
@@ -127,7 +127,7 @@ atreides-hostile = The Atreides are now hostile!
mercenaries-annihilated = The Mercenaries have been annihilated!
ally-mercenaries = Persuade the Mercenaries to fight alongside House Harkonnen.
mercenary-leader-captured-allied = Leader Captured. The mercenaries have been persuaded to fight with House Harkonnen.
can-not-stand-harkonnen-must-become-neutral = We cannot stand against the Harkonnen. We must become neutral.
cannot-stand-harkonnen-must-become-neutral = We cannot stand against the Harkonnen. We must become neutral.
atreides-commander = Atreides Commander
## harkonnen-09ab

View File

@@ -34,13 +34,13 @@ options-starting-units =
resource-spice = Spice
faction-random =
.name = Any
.description = Random House.
A random house is chosen at the start of the game.
.name = Any
.description = Random House
A random house is chosen at the start of the game
faction-atreides =
.name = Atreides
.description = House Atreides
.name = Atreides
.description = House Atreides
The noble Atreides, from the water world of Caladan,
rely on their Ornithopters to ensure air superiority.
They have allied themselves with the Fremen, the fearsome
@@ -58,14 +58,14 @@ faction-atreides =
- Airstrike
faction-harkonnen =
.name = Harkonnen
.description = House Harkonnen
.name = Harkonnen
.description = House Harkonnen
The evil Harkonnen will stop at nothing to gain control of the spice.
They rely on brute force and atomic weapons to achieve their goals:
wealth, and the destruction of House Atreides.
Faction Variations:
- Combat Tanks are more durable but move at a slower speed.
- Combat Tanks are more durable but move at a slower speed
Special Units:
- Sardaukar
@@ -75,15 +75,15 @@ faction-harkonnen =
- Death Hand Missile
faction-ordos =
.name = Ordos
.description = House Ordos
.name = Ordos
.description = House Ordos
From the icy world of Sigma Draconis IV, the insidious Ordos are known for
their wealth, greed, and treachery. They often turn to mercenaries, sabotage,
and forbidden Ixian technologies to gain the upper hand.
Faction Variations:
- Trikes are replaced by Raider Trikes.
- Combat Tanks are faster but less durable.
- Trikes are replaced by Raider Trikes
- Combat Tanks are faster but less durable
Special Units:
- Raider Trike
@@ -92,16 +92,16 @@ faction-ordos =
- Deviator
faction-corrino =
.name = Corrino
.name = Corrino
faction-mercenaries =
.name = Mercenaries
.name = Mercenaries
faction-smugglers =
.name = Smugglers
.name = Smugglers
faction-fremen =
.name = Fremen
.name = Fremen
## defaults.yaml
notification-unit-lost = Unit lost.
@@ -111,19 +111,23 @@ notification-primary-building-selected = Primary building selected.
## aircraft.yaml
actor-carryall-reinforce =
.name = Carryall
.description = Large winged, planet-bound ship
Automatically lifts harvesters to and from Spice fields.
Lifts vehicles to Repair Pads when ordered to.
.name = Carryall
.description =
Large winged, planet-bound ship
Automatically lifts harvesters to and from Spice fields.
Lifts vehicles to Repair Pads when ordered to.
actor-carryall-encyclopedia = Automatically transport Harvesters between the Spice Fields and Refineries. They can also pick up units and deliver them to the Repair Pad when ordered to.
actor-carryall-encyclopedia =
Automatically transport Harvesters between the Spice Fields and Refineries. They can also pick up units and deliver them to the Repair Pad when ordered to.
The Carryall is a lightly armored transport aircraft. It is vulnerable to missiles and can only be hit by anti-aircraft weapons.
actor-frigate-name = Frigate
actor-ornithopter =
.name = Ornithopter
.encyclopedia = The fastest aircraft on Dune, it is lightly armored and capable of dropping 500 lb bombs. Highly effective against infantry and lightly armored targets, with the ability to damage other armor types.
.name = Ornithopter
.encyclopedia =
The fastest aircraft on Dune, it is lightly armored and capable of dropping 500 lb bombs. Highly effective against infantry and lightly armored targets, with the ability to damage other armor types.
actor-ornithopter-husk-name = Ornithopter
actor-carryall-husk-name = Carryall
@@ -158,81 +162,100 @@ meta-combat-tank-husk-name = Combat Tank (Destroyed)
## infantry.yaml
actor-light-inf =
.name = Light Infantry
.description = General-purpose infantry.
Strong vs. Infantry.
Weak vs. Vehicles and Artillery.
.encyclopedia = Lightly armored foot soldiers, equipped with 9mm RP assault rifles. They are effective against infantry and lightly armored vehicles.
.name = Light Infantry
.description =
General-purpose infantry.
Strong vs Infantry
Weak vs Vehicles and Artillery
.encyclopedia =
Lightly armored foot soldiers, equipped with 9mm RP assault rifles. They are effective against infantry and lightly armored vehicles.
Light Infantry are resistant to missiles and large-caliber guns, but are very vulnerable to high-explosives, fire and bullet weapons.
Light Infantry are resistant to missiles and large-caliber guns, but are very vulnerable to high-explosives, fire, and small arms.
actor-engineer =
.name = Engineer
.description = Infiltrates and captures enemy structures.
Strong vs. Buildings.
Weak vs. Everything.
Repairs damaged cliffs.
.encyclopedia = Can be used to capture enemy buildings.
.name = Engineer
.description =
Infiltrates and captures enemy
structures.
Strong vs Buildings
Weak vs Everything
Repairs damaged cliffs
.encyclopedia =
Can be used to capture enemy buildings.
Engineers are resistant to anti-tank weaponry but are very vulnerable to high-explosives, fire and firearms.
Engineers are resistant to anti-tank weaponry but are very vulnerable to high-explosives, fire, and small arms.
actor-trooper =
.name = Trooper
.description = Anti-tank infantry.
Strong vs. Tanks.
Weak vs. Infantry and Artillery.
.encyclopedia = Armed with wire-guided, armor-piercing missile warheads, Troopers are very effective against vehicles and buildings but struggle against infantry.
.name = Trooper
.description =
Anti-tank infantry.
Strong vs Tanks
Weak vs Infantry and Artillery
.encyclopedia =
Armed with wire-guided, armor-piercing missile warheads, Troopers are very effective against vehicles and buildings but struggle against infantry.
Troopers are resistant to anti-tank weaponry but very vulnerable to high-explosives, fire and bullet weapons.
actor-thumper =
.name = Thumper Infantry
.description = Attracts nearby worms when deployed.
Unarmed.
.encyclopedia = Deploys a loud hammering device that draws Sandworms to the area.
.name = Thumper Infantry
.description =
Attracts nearby worms when deployed.
Unarmed
.encyclopedia =
Deploys a loud hammering device that draws Sandworms to the area.
actor-fremen =
.name = Fremen
.description = Elite infantry unit with assault rifles and rockets.
Strong vs. Infantry and Vehicles.
Weak vs. Artillery.
Special Ability: Invisibility.
.encyclopedia = The native desert warriors of Dune, armed with 10mm Assault Rifles and Rockets. Their firepower is equally effective against infantry and vehicles.
.name = Fremen
.description =
Elite infantry unit with assault rifles and rockets.
Strong vs Infantry and Vehicles
Weak vs Artillery
Special Ability: Invisibility
.encyclopedia =
The native desert warriors of Dune, armed with 10mm Assault Rifles and Rockets. Their firepower is equally effective against infantry and vehicles.
Fremen units are very vulnerable to high-explosive and bullet weapons.
actor-grenadier =
.name = Grenadier
.description = Infantry with grenades.
Strong vs. Buildings and Infantry.
Weak vs. Vehicles.
.encyclopedia = An infantry artillery unit strong against buildings. They have a chance of exploding when killed, so should not be grouped together.
.name = Grenadier
.description =
Infantry with grenades.
Strong vs Buildings and Infantry
Weak vs Vehicles
.encyclopedia =
An infantry artillery unit strong against buildings. They have a chance of exploding when killed, so should not be grouped together.
actor-sardaukar =
.name = Sardaukar
.description = Elite Corrino assault infantry.
Strong vs. Infantry and Vehicles.
Weak vs. Artillery.
.encyclopedia = Powerful heavy troopers equipped with a machine gun that is effective against infantry and a rocket launcher for targeting vehicles.
.name = Sardaukar
.description =
Elite Corrino assault infantry.
Strong vs Infantry and Vehicles
Weak vs Artillery
.encyclopedia =
Powerful heavy troopers equipped with a machine gun that is effective against infantry and a rocket launcher for targeting vehicles.
actor-mpsardaukar-description = Elite Harkonnen assault infantry.
Strong vs. Infantry and Vehicles.
Weak vs. Artillery.
actor-mpsardaukar-description =
Elite Harkonnen assault infantry.
Strong vs Infantry and Vehicles
Weak vs Artillery
actor-saboteur =
.name = Saboteur
.description = Sneaky infantry with explosives.
.name = Saboteur
.description =
Sneaky infantry with explosives.
Turns invisible for a limited time.
Strong vs. Buildings.
Weak vs. Everything.
Special Ability: Destroys buildings.
.encyclopedia = A specialized military unit of House Ordos, capable of demolishing enemy buildings upon entry, but dying in the resulting explosion. It can activate stealth mode to become invisible.
Strong vs Buildings
Weak vs Everything
Special Ability: Destroys buildings
.encyclopedia =
A specialized military unit of House Ordos, capable of demolishing enemy buildings upon entry, but dying in the resulting explosion. It can activate stealth mode to become invisible.
The Saboteur is resistant to anti-tank weaponry, but very vulnerable to high-explosives, fire, and bullet weapons.
actor-nsfremen-description = Elite infantry unit with assault rifles and rockets.
Strong vs. Infantry and Vehicles.
Weak vs. Artillery.
actor-nsfremen-description =
Elite infantry unit with assault rifles and rockets.
Strong vs Infantry and Vehicles
Weak vs Artillery
## misc.yaml
actor-crate-name = Crate
@@ -242,41 +265,54 @@ actor-camera-name = (reveals area to owner)
actor-wormspawner-name = (worm spawning location)
actor-upgrade-conyard =
.name = Construction Yard Upgrade
.description = Unlocks additional construction options:
(Large Concrete Slab and Rocket Turret)
.name = Construction Yard Upgrade
.description =
Unlocks additional construction options:
- Large Concrete Slab
- Rocket Turret
actor-upgrade-barracks =
.name = Barracks Upgrade
.description = Unlocks additional infantry:
(Trooper, Engineer and Thumper Infantry)
.name = Barracks Upgrade
.description =
Unlocks additional infantry:
- Trooper
- Engineer
- Thumper Infantry
Required to unlock faction-specific infantry:
(Atreides: Grenadier, Harkonnen: Sardaukar)
- Atreides: Grenadier
- Harkonnen: Sardaukar
actor-upgrade-light =
.name = Light Factory Upgrade
.description = Unlocks additional light units:
(Missile Quad)
.name = Light Factory Upgrade
.description =
Unlocks additional light units:
- Missile Quad
Required to unlock a faction-specific light unit:
(Ordos: Stealth Raider Trike)
- Ordos: Stealth Raider Trike
actor-upgrade-heavy =
.name = Heavy Factory Upgrade
.description = Unlocks additional construction options:
(Repair Pad and IX Research Center)
.name = Heavy Factory Upgrade
.description =
Unlocks additional construction options:
- Repair Pad
- IX Research Center
Unlocks additional heavy units:
(Siege Tank, Missile Tank and MCV)
- Siege Tank
- Missile Tank
- MCV
actor-upgrade-hightech =
.name = High Tech Factory Upgrade
.description = Unlocks the Atreides Air Strike superweapon.
.name = High Tech Factory Upgrade
.description =
Unlocks the Atreides Air Strike superweapon.
actor-deathhand =
.name = Death Hand
.encyclopedia = Armed with atomic cluster munitions, it detonates above its target, inflicting great damage over a wide area.
.name = Death Hand
.encyclopedia =
Armed with atomic cluster munitions, it detonates above its target, inflicting great damage over a wide area.
## structures.yaml
notification-construction-complete = Construction complete.
@@ -294,287 +330,338 @@ notification-fremen-ready = Fremen ready.
notification-saboteur-ready = Saboteur ready.
meta-concrete =
.generic-name = Structure
.description = Provides a strong foundation that protects
against terrain damage.
.generic-name = Structure
.description =
Provides a strong foundation that
protects against terrain damage.
actor-concrete-a =
.name = Concrete Slab
.encyclopedia = Buildings not constructed on a Concrete Slab will sustain ongoing damage from the harsh desert environment of Dune. While repairs are possible, placing structures on concrete prevents continuous weathering.
.name = Concrete Slab
.encyclopedia =
Buildings not constructed on a Concrete Slab will sustain ongoing damage from the harsh desert environment of Dune. While repairs are possible, placing structures on concrete prevents continuous weathering.
Concrete is vulnerable to most weapons and cannot be repaired once damaged.
actor-concrete-b-name = Large Concrete Slab
actor-construction-yard =
.name = Construction Yard
.description = Produces structures.
.encyclopedia = Serving as the foundation of any base built on Arrakis, the Construction Yard produces a small amount of power and enables the construction of new structures. Protect this structure! It is critical to the success of your base.
.name = Construction Yard
.description = Produces structures.
.encyclopedia =
Serving as the foundation of any base built on Arrakis, the Construction Yard produces a small amount of power and enables the construction of new structures. Protect this structure! It is critical to the success of your base.
Construction yards are fairly strong, but vulnerable to all weapons to varying degrees.
actor-wind-trap =
.name = Wind Trap
.description = Supplies power to other structures.
.encyclopedia = Produces power and water for your base. Large, above-ground ducts funnel wind currents underground into massive turbines, which drive power generators and humidity extractors.
.name = Wind Trap
.description =
Supplies power to other
structures.
.encyclopedia =
Produces power and water for your base. Large, above-ground ducts funnel wind currents underground into massive turbines, which drive power generators and humidity extractors.
Wind Traps are vulnerable to most weapons.
actor-barracks =
.name = Barracks
.description = Trains infantry.
.encyclopedia = Required for producing and training light infantry units, it can be upgraded to train advanced infantry in later missions.
.name = Barracks
.description = Trains infantry.
.encyclopedia =
Required for producing and training light infantry units, it can be upgraded to train advanced infantry in later missions.
Barracks are vulnerable to most weapons.
actor-refinery =
.name = Spice Refinery
.description = Harvesters unload Spice here for processing.
.encyclopedia = The basis of all Spice production on Dune. Harvesters transport mined Spice to the Refinery where it is converted into credits. Refined Spice is automatically distributed to Silos and Refineries for storage. Each refinery can store Spice. A Spice Harvester is delivered by Carryall once a Refinery is built.
.name = Spice Refinery
.description =
Harvesters unload Spice here
for processing.
.encyclopedia =
The basis of all Spice production on Dune. Harvesters transport mined Spice to the Refinery where it is converted into credits. Refined Spice is automatically distributed to Silos and Refineries for storage. Each refinery can store Spice. A Spice Harvester is delivered by Carryall once a Refinery is built.
Refineries are vulnerable to most weapons.
actor-silo =
.name = Silo
.description = Stores excess harvested Spice.
.encyclopedia = Store mined Spice. Any surplus from Refineries is evenly distributed among all available Silos. If storage capacity is exceeded, excess Spice is lost. Destroyed or captured Silos redistribute their contents, provided there is sufficient space.
.name = Silo
.description = Stores excess harvested Spice.
.encyclopedia =
Store mined Spice. Any surplus from Refineries is evenly distributed among all available Silos. If storage capacity is exceeded, excess Spice is lost. Destroyed or captured Silos redistribute their contents, provided there is sufficient space.
The Spice Silo is vulnerable to most weapons.
actor-light-factory =
.name = Light Factory
.description = Produces light vehicles.
.encyclopedia = Required to produce small, lightly armored combat vehicles. It can be upgraded in later missions to manufacture more advanced light vehicles.
.name = Light Factory
.description = Produces light vehicles.
.encyclopedia =
Required to produce small, lightly armored combat vehicles. It can be upgraded in later missions to manufacture more advanced light vehicles.
A Light Factory is vulnerable to most weapons.
actor-heavy-factory =
.name = Heavy Factory
.description = Produces heavy vehicles.
.encyclopedia = Enables the construction of heavy vehicles such as Harvesters and Combat Tanks. With upgrades, it unlocks advanced vehicles, though some may require additional buildings.
.name = Heavy Factory
.description = Produces heavy vehicles.
.encyclopedia =
Enables the construction of heavy vehicles such as Harvesters and Combat Tanks. With upgrades, it unlocks advanced vehicles, though some may require additional buildings.
The Heavy Factory is vulnerable to most weapons.
actor-outpost =
.name = Outpost
.description = Provides a radar map of the battlefield.
Requires power to operate.
.encyclopedia = Once enough power is available, the Radar Outpost activates, providing a radar map.
.name = Outpost
.description =
Provides a radar map of the battlefield.
Requires power to operate.
.encyclopedia =
Once enough power is available, the Radar Outpost activates, providing a radar map.
The Radar Outpost is vulnerable to most weapons.
actor-starport =
.name = Starport
.description = Dropzone for quick reinforcements, at a price.
.encyclopedia = Unlocks intergalactic trade with the CHOAM Merchants' Guild, where vehicles and airborne units can be purchased at varying rates. This facility is essential for acquiring units from the Guild.
.name = Starport
.description = Dropzone for quick reinforcements, at a price.
.encyclopedia =
Unlocks intergalactic trade with the CHOAM Merchants' Guild, where vehicles and airborne units can be purchased at varying rates. This facility is essential for acquiring units from the Guild.
Even with heavy armor, the Starport is vulnerable to most weapons.
actor-wall =
.name = Concrete Wall
.generic-name = Structure
.description = Stops units and blocks enemy fire.
.encyclopedia = The most effective defensive barriers on Dune, blocking tank fire and impeding unit movement.
.name = Concrete Wall
.generic-name = Structure
.description = Stops units and blocks enemy fire.
.encyclopedia =
The most effective defensive barriers on Dune, blocking tank fire and impeding unit movement.
Walls can only be damaged by explosive weapons, missiles and shells. Similar to concrete slabs, they cannot be repaired once damaged.
Walls can only be damaged by explosive weapons, missiles and shells. Similar to Concrete Slabs, they cannot be repaired once damaged.
actor-medium-gun-turret =
.name = Gun Turret
.description = Defensive structure.
Strong vs. Tanks.
Weak vs. Infantry and Aircraft.
.encyclopedia = A medium-range weapon that is effective against all types of vehicle, particularly heavily armored ones. It automatically fires upon any enemy unit within its range and requires power to operate.
.name = Gun Turret
.description =
Defensive structure.
Strong vs Tanks
Weak vs Infantry and Aircraft
.encyclopedia =
A medium-range weapon that is effective against all types of vehicle, particularly heavily armored ones. It automatically fires upon any enemy unit within its range and requires power to operate.
The Gun Turret is resistant to firearms and explosive weapons, but vulnerable to missiles and high-caliber guns.
The Gun Turret is resistant to small arms and explosive weapons, but vulnerable to missiles and high-caliber guns.
actor-large-gun-turret =
.name = Rocket Turret
.description = Defensive structure.
Strong vs. Infantry and Aircraft.
Weak vs. Tanks.
Requires power to operate.
.encyclopedia = An enhanced defensive structure with a longer range and faster rate of fire than the Gun Turret. Its advanced targeting system requires power to operate.
.name = Rocket Turret
.description =
Defensive structure.
Requires power to operate.
Strong vs Infantry and Aircraft
Weak vs Tanks
.encyclopedia =
An enhanced defensive structure with a longer range and faster rate of fire than the Gun Turret. Its advanced targeting system requires power to operate.
The Rocket Turret is resistant to firearms and explosive weapons, but vulnerable to missiles and high-caliber guns.
actor-repair-pad =
.name = Repair Pad
.description = Repairs vehicles.
Allows MCVs to be built.
.encyclopedia = Repairs units for a fraction of their production cost.
.name = Repair Pad
.description =
Repairs vehicles.
Allows MCVs to be built.
.encyclopedia =
Repairs units for a fraction of their production cost.
The Repair Pad is vulnerable to most weapons.
actor-high-tech-factory =
.name = High Tech Factory
.description = Unlocks advanced technology.
.airstrikepower-name = Air Strike
.airstrikepower-description = Ornithopters bomb the target.
.encyclopedia = Produces airborne units, and is required to build Carryalls. House Atreides can upgrade this facility to build Ornithopters for air strikes in later missions.
.name = High Tech Factory
.description = Unlocks advanced technology.
.airstrikepower-name = Air Strike
.airstrikepower-description = Ornithopters bomb the target.
.encyclopedia =
Produces airborne units, and is required to build Carryalls. House Atreides can upgrade this facility to build Ornithopters for air strikes in later missions.
The High Tech Factory is vulnerable to most weapons.
actor-research-centre =
.name = IX Research Center
.description = Unlocks advanced tanks.
.encyclopedia = Provides technology upgrades for both structures and vehicles. This facility is required to develop advanced special weapons and prototypes.
.name = IX Research Center
.description = Unlocks advanced tanks.
.encyclopedia =
Provides technology upgrades for both structures and vehicles. This facility is required to develop advanced special weapons and prototypes.
The IX Research Center is vulnerable to most weapons.
actor-palace =
.name = Palace
.description = Unlocks elite infantry and weapons.
.encyclopedia = Serves as the command center once built, offering additional options and special weapons.
.name = Palace
.description = Unlocks elite infantry and weapons.
.encyclopedia =
Serves as the command center once built, offering additional options and special weapons.
Even with heavy armor, the Palace is vulnerable to most weapons.
.nukepower-name = Death Hand
.nukepower-description = Launches an atomic missile at a target location.
.produceactorpower-fremen-name = Recruit Fremen
.produceactorpower-fremen-description = Elite infantry unit with assault rifles and rockets.
Strong vs. Infantry and Vehicles.
Weak vs. Artillery.
Special Ability: Invisibility.
.produceactorpower-saboteur-name = Recruit Saboteur
.produceactorpower-saboteur-description = Sneaky infantry with explosives.
.nukepower-name = Death Hand
.nukepower-description = Launches an atomic missile at a target location.
.produceactorpower-fremen-name = Recruit Fremen
.produceactorpower-fremen-description = Elite infantry unit with assault rifles and rockets.
Strong vs Infantry and Vehicles
Weak vs Artillery
Special Ability: Invisibility
.produceactorpower-saboteur-name = Recruit Saboteur
.produceactorpower-saboteur-description = Sneaky infantry with explosives.
Can be deployed to become invisible for a limited time.
Strong vs Buildings.
Weak vs Everything.
Special Ability: Destroys buildings.
Strong vs Buildings
Weak vs Everything
Special Ability: Destroys buildings
## vehicles.yaml
actor-mcv =
.name = Mobile Construction Vehicle
.description = Deploys into a Construction Yard.
Unarmed.
.encyclopedia = Must be driven to an area where it can be deployed. After finding a suitable rock surface, the MCV can be transformed into a Construction Yard.
.name = Mobile Construction Vehicle
.description =
Deploys into a Construction Yard.
Unarmed
.encyclopedia =
Must be driven to an area where it can be deployed. After finding a suitable rock surface, the MCV can be transformed into a Construction Yard.
MCVs are resistant to bullets and light-explosives. They are vulnerable to missiles and high-caliber guns.
actor-harvester =
.name = Spice Harvester
.description = Collects Spice for processing.
Unarmed.
.encyclopedia = Resistant to bullets, and to some degree, high explosives. They are vulnerable to missiles and high-caliber guns.
.name = Spice Harvester
.description =
Collects Spice for processing.
Unarmed
.encyclopedia =
Resistant to bullets, and to some degree, high explosives. They are vulnerable to missiles and high-caliber guns.
A Harvester is included with a Refinery.
actor-trike =
.name = Trike
.description = Fast scout.
Strong vs. Infantry.
Weak vs. Tanks.
.encyclopedia = Lightly armored, three-wheeled vehicles armed with heavy machine guns, effective against infantry and lightly armored vehicles.
.name = Trike
.description =
Fast scout.
Strong vs Infantry
Weak vs Tanks
.encyclopedia =
Lightly armored, three-wheeled vehicles armed with heavy machine guns, effective against infantry and lightly armored vehicles.
Trikes are vulnerable to most weapons, high-caliber guns are slightly less effective against them.
actor-quad =
.name = Missile Quad
.description = Missile Scout.
Strong vs. Vehicles.
Weak vs. Infantry.
.encyclopedia = Superior to the Trike in both armor and firepower, the Quad is a four-wheeled vehicle firing armor-piercing rockets. It is effective against most vehicles.
.name = Missile Quad
.description =
Missile Scout.
Strong vs Vehicles
Weak vs Infantry
.encyclopedia =
Superior to the Trike in both armor and firepower, the Quad is a four-wheeled vehicle firing armor-piercing rockets. It is effective against most vehicles.
Quads are resistant to bullets and, to a lesser degree, explosives. They are vulnerable to missiles and high-caliber guns.
actor-siege-tank =
.name = Siege Tank
.description = Siege Artillery.
Strong vs. Infantry and Buildings.
Weak vs. Tanks.
.encyclopedia = Incredibly effective against infantry and lightly armored vehicles, but struggles against heavily armored targets. It has a long firing range.
.name = Siege Tank
.description =
Siege Artillery.
Strong vs Infantry and Buildings
Weak vs Tanks
.encyclopedia =
Incredibly effective against infantry and lightly armored vehicles, but struggles against heavily armored targets. It has a long firing range.
Siege Tanks are resistant to bullets, and to some degree, explosives. They are vulnerable to missiles and high-caliber guns.
actor-missile-tank =
.name = Missile Tank
.description = Rocket Artillery.
Strong vs. Vehicles, Buildings and Aircraft.
Weak vs. Infantry.
.encyclopedia = Shoots down aircraft and is effective against most targets, except infantry.
.name = Missile Tank
.description =
Rocket Artillery.
Strong vs Vehicles, Buildings and Aircraft
Weak vs Infantry
.encyclopedia =
Shoots down aircraft and is effective against most targets, except infantry.
Missile Tanks are vulnerable to most weapons, high-caliber guns are slightly less effective.
actor-sonic-tank =
.name = Sonic Tank
.description = Fires sonic shocks.
Strong vs. Infantry and Vehicles.
Weak vs. Artillery.
.encyclopedia = Most effective against infantry and lightly armored vehicles, but weaker against armored targets.
.name = Sonic Tank
.description =
Fires sonic shocks.
Strong vs Infantry and Vehicles
Weak vs Artillery
.encyclopedia =
Most effective against infantry and lightly armored vehicles, but weaker against armored targets.
Its sonic waves damage all units in their path.
Resistant to bullets and small-explosives, but vulnerable to missiles and high-caliber guns.
actor-devastator =
.name = Devastator
.description = Super Heavy Tank.
Strong vs. Tanks.
Weak vs. Artillery.
.encyclopedia = As the most powerful tank on Dune, the Devastator is slow but highly effective against most units. It fires dual plasma charges and can self-destruct on command, damaging nearby units and structures.
.name = Devastator
.description =
Super Heavy Tank.
Strong vs Tanks
Weak vs Artillery
.encyclopedia =
As the most powerful tank on Dune, the Devastator is slow but highly effective against most units. It fires dual plasma charges and can self-destruct on command, damaging nearby units and structures.
Resistant to bullets and high explosives, but vulnerable to missiles and high-caliber guns.
actor-raider =
.name = Raider Trike
.description = Improved Scout.
Strong vs. Infantry and Light Vehicles.
Weak vs. Tanks.
.encyclopedia = Raider Trikes, upgraded by House Ordos, have enhanced firepower, speed, and armor. Equipped with dual 20mm cannons, they are strong against infantry and lightly armored vehicles.
.name = Raider Trike
.description =
Improved Scout.
Strong vs Infantry and Light Vehicles
Weak vs Tanks
.encyclopedia =
Raider Trikes, upgraded by House Ordos, have enhanced firepower, speed, and armor. Equipped with dual 20mm cannons, they are strong against infantry and lightly armored vehicles.
Raiders are vulnerable to most weapons, though high-caliber guns are slightly less effective against them.
actor-stealth-raider =
.name = Stealth Raider Trike
.description = Invisible Raider Trike.
Strong vs. Infantry and Light Vehicles.
Weak vs. Tanks.
.encyclopedia = A cloaked version of the Raider, good for stealth attacks. It uncloaks when it fires its machine guns.
.name = Stealth Raider Trike
.description =
Invisible Raider Trike.
Strong vs Infantry and Light Vehicles
Weak vs Tanks
.encyclopedia =
A cloaked version of the Raider, good for stealth attacks. It uncloaks when it fires its machine guns.
actor-deviator =
.name = Deviator
.description = Fires a warhead that changes
the allegiance of enemy vehicles.
.encyclopedia = Fires missiles that release a silicon cloud, temporarily altering the allegiance of targeted vehicles. Personnel are only slightly affected by the cloud.
.name = Deviator
.description =
Fires a warhead that changes the
allegiance of enemy vehicles.
.encyclopedia =
Fires missiles that release a silicon cloud, temporarily altering the allegiance of targeted vehicles. Personnel are only slightly affected by the cloud.
The Deviator is vulnerable to most weapons, high-caliber guns are slightly less effective.
meta-combat-tank-description = Main Battle Tank.
Strong vs. Tanks.
Weak vs. Infantry.
meta-combat-tank-description =
Main Battle Tank.
Strong vs Tanks
Weak vs Infantry
actor-combat-tank-a =
.name = Atreides Combat Tank
.encyclopedia = Effective against most vehicles but less suited against lightly armored targets.
.name = Atreides Combat Tank
.encyclopedia =
Effective against most vehicles but less suited against lightly armored targets.
Resistant to bullets and heavy explosives, but vulnerable to missiles and high-caliber guns.
actor-combat-tank-h =
.name = Harkonnen Combat Tank
.encyclopedia = Effective against most vehicles but less suited against lightly armored targets.
.name = Harkonnen Combat Tank
.encyclopedia =
Effective against most vehicles but less suited against lightly armored targets.
Stronger than its counterparts, but also slower.
actor-combat-tank-o =
.name = Ordos Combat Tank
.encyclopedia = Effective against most vehicles but less suited against lightly armored targets.
.name = Ordos Combat Tank
.encyclopedia =
Effective against most vehicles but less suited against lightly armored targets.
The fastest variant of Combat Tank, but also the weakest.
meta-destroyabletile =
.generic-name = Passage (destroyable)
.name = Passage (destroyable)
.generic-name = Passage (destroyable)
.name = Passage (destroyable)
meta-destroyedtile =
.generic-name = Passage (repairable)
.name = Passage (repairable)
.generic-name = Passage (repairable)
.name = Passage (repairable)
## ai.yaml
bot-omnius =
.name = Omnius
.name = Omnius
bot-vidius =
.name = Vidious
.name = Vidious
bot-gladius =
.name = Gladius
.name = Gladius

View File

@@ -54,7 +54,7 @@ AtreidesEntryPath = { AtreidesWaypoint.Location, AtreidesRally.Location }
Messages =
{
UserInterface.GetFluentMessage("build-concrete"),
UserInterface.GetFluentMessage("build-windtrap"),
UserInterface.GetFluentMessage("build-wind-trap"),
UserInterface.GetFluentMessage("build-refinery"),
UserInterface.GetFluentMessage("build-silo")
}

View File

@@ -54,7 +54,7 @@ AtreidesEntryPath = { AtreidesWaypoint.Location, AtreidesRally.Location }
Messages =
{
UserInterface.GetFluentMessage("build-concrete"),
UserInterface.GetFluentMessage("build-windtrap"),
UserInterface.GetFluentMessage("build-wind-trap"),
UserInterface.GetFluentMessage("build-refinery"),
UserInterface.GetFluentMessage("build-silo")
}

View File

@@ -54,7 +54,7 @@ HarkonnenEntryPath = { HarkonnenWaypoint.Location, HarkonnenRally.Location }
Messages =
{
UserInterface.GetFluentMessage("build-concrete"),
UserInterface.GetFluentMessage("build-windtrap"),
UserInterface.GetFluentMessage("build-wind-trap"),
UserInterface.GetFluentMessage("build-refinery"),
UserInterface.GetFluentMessage("build-silo")
}

View File

@@ -54,7 +54,7 @@ HarkonnenEntryPath = { HarkonnenWaypoint.Location, HarkonnenRally.Location }
Messages =
{
UserInterface.GetFluentMessage("build-concrete"),
UserInterface.GetFluentMessage("build-windtrap"),
UserInterface.GetFluentMessage("build-wind-trap"),
UserInterface.GetFluentMessage("build-refinery"),
UserInterface.GetFluentMessage("build-silo")
}

View File

@@ -79,3 +79,4 @@ outpost:
Tooltip@Modified:
Name: actor-outpost-modified-name
RequiresCondition: modified
ShowOwnerRow: false

Some files were not shown because too many files have changed in this diff Show More