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_style_readonly_field = true
dotnet_diagnostic.IDE0044.severity = warning 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 # IDE0049 Use language keywords instead of framework type names for type references
#dotnet_style_predefined_type_for_locals_parameters_members = true #dotnet_style_predefined_type_for_locals_parameters_members = true

View File

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

View File

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

View File

@@ -57,13 +57,11 @@ jobs:
./packaging/linux/buildpackage.sh "${GIT_TAG}" "${PWD}/build/linux" ./packaging/linux/buildpackage.sh "${GIT_TAG}" "${PWD}/build/linux"
- name: Upload Packages - name: Upload Packages
uses: svenstaro/upload-release-action@v2 env:
with: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
repo_token: ${{ secrets.GITHUB_TOKEN }} shell: bash
tag: ${{ github.ref }} run: |
overwrite: true gh release upload ${{ github.ref_name }} build/linux/*
file_glob: true
file: build/linux/*
macos: macos:
name: macOS Disk Image name: macOS Disk Image
@@ -92,13 +90,11 @@ jobs:
./packaging/macos/buildpackage.sh "${GIT_TAG}" "${PWD}/build/macos" ./packaging/macos/buildpackage.sh "${GIT_TAG}" "${PWD}/build/macos"
- name: Upload Package - name: Upload Package
uses: svenstaro/upload-release-action@v2 env:
with: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
repo_token: ${{ secrets.GITHUB_TOKEN }} shell: bash
tag: ${{ github.ref }} run: |
overwrite: true gh release upload ${{ github.ref_name }} build/macos/*
file_glob: true
file: build/macos/*
windows: windows:
name: Windows Installers name: Windows Installers
@@ -124,10 +120,8 @@ jobs:
./packaging/windows/buildpackage.sh "${GIT_TAG}" "${PWD}/build/windows" ./packaging/windows/buildpackage.sh "${GIT_TAG}" "${PWD}/build/windows"
- name: Upload Packages - name: Upload Packages
uses: svenstaro/upload-release-action@v2 env:
with: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
repo_token: ${{ secrets.GITHUB_TOKEN }} shell: bash
tag: ${{ github.ref }} run: |
overwrite: true gh release upload ${{ github.ref_name }} build/windows/*
file_glob: true
file: 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 string Name { get; private set; }
public bool IsDecoration { get; set; } public bool IsDecoration { get; set; }
readonly Map map;
readonly SequenceSet sequences; readonly SequenceSet sequences;
readonly Func<WAngle> facingFunc; readonly Func<WAngle> facingFunc;
readonly Func<bool> paused; readonly Func<bool> paused;
@@ -43,6 +44,7 @@ namespace OpenRA.Graphics
public Animation(World world, string name, Func<WAngle> facingFunc, Func<bool> paused) public Animation(World world, string name, Func<WAngle> facingFunc, Func<bool> paused)
{ {
map = world.Map;
sequences = world.Map.Sequences; sequences = world.Map.Sequences;
Name = name.ToLowerInvariant(); Name = name.ToLowerInvariant();
this.facingFunc = facingFunc; this.facingFunc = facingFunc;
@@ -65,8 +67,10 @@ namespace OpenRA.Graphics
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc()); var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
if (shadow != null) if (shadow != null)
{ {
var height = map.DistanceAboveTerrain(pos).Length;
var shadowRenderable = new SpriteRenderable( 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, CurrentSequence.Scale, 1f, float3.Ones, tintModifiers,
true, rotation); true, rotation);
return new IRenderable[] { shadowRenderable, imageRenderable }; return new IRenderable[] { shadowRenderable, imageRenderable };

View File

@@ -149,6 +149,10 @@ namespace OpenRA.Graphics
var buffer = data; var buffer = data;
ReleaseBuffer(); 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. // Only transfer if the destination has no data that would be lost by overwriting.
if (buffer != null && destination.data == null && destination.texture == null) if (buffer != null && destination.data == null && destination.texture == null)
{ {

View File

@@ -14,6 +14,7 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using OpenRA.FileSystem; using OpenRA.FileSystem;
@@ -244,21 +245,42 @@ namespace OpenRA
{ {
var client = HttpClientFactory.Create(); var client = HttpClientFactory.Create();
var stringPool = new HashSet<string>(); // Reuse common strings in YAML 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 // Limit each query to 50 maps at a time to avoid request size limits
for (var i = 0; i < queryUids.Count; i += 50) for (var i = 0; i < queryUids.Count; i += 50)
{ {
var batchUids = queryUids.Skip(i).Take(50).ToList(); var batchUids = queryUids.Skip(i).Take(50).ToList();
var url = repositoryUrl + "hash/" + string.Join(",", batchUids) + "/yaml"; var url = repositoryUrl + "hash/" + string.Join(",", batchUids) + "/yaml";
using (new PerfTimer("RemoteMapDetails"))
{
try try
{ {
var httpResponseMessage = await client.GetAsync(url); await using (var resultStream = await client.GetStreamAsync(url))
var result = await httpResponseMessage.Content.ReadAsStreamAsync(); {
using (var resultReader = new StreamReader(resultStream))
var yaml = MiniYaml.FromStream(result, url, stringPool: stringPool); {
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) foreach (var kv in yaml)
previews[kv.Key].UpdateRemoteSearch(MapStatus.DownloadAvailable, kv.Value, modData.Manifest.MapCompatibility, mapDetailsReceived); 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) foreach (var uid in batchUids)
{ {
var p = previews[uid]; var p = previews[uid];
@@ -280,6 +302,7 @@ namespace OpenRA
} }
} }
} }
}
}); });
} }

View File

@@ -249,11 +249,26 @@ namespace OpenRA
/// </summary> /// </summary>
public string GetMessage(string key, object[] args = null) 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 (TryGetMessage(key, out var message, args))
if (FluentProvider.TryGetModMessage(key, out var message, args))
return message; 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; Sprite minimap;

View File

@@ -3,7 +3,7 @@
<RootNamespace>OpenRA</RootNamespace> <RootNamespace>OpenRA</RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup Condition="'$(MSBuildRuntimeType)'!='Mono'"> <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" /> <PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(MSBuildRuntimeType)'=='Mono'"> <ItemGroup Condition="'$(MSBuildRuntimeType)'=='Mono'">

View File

@@ -1006,7 +1006,10 @@ namespace OpenRA.Server
case "Chat": case "Chat":
{ {
if (!IsMultiplayer || !playerMessageTracker.IsPlayerAtFloodLimit(conn)) 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()); DispatchOrdersToClients(conn, 0, o.Serialize());
break; break;

View File

@@ -23,7 +23,8 @@ namespace OpenRA.Mods.Cnc.Scripting
public ChronosphereProperties(ScriptContext context, Actor self) public ChronosphereProperties(ScriptContext context, Actor self)
: base(context, 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) public void Chronoshift([ScriptEmmyTypeOverride("{ [actor]: cpos }")] LuaTable unitLocationPairs, int duration = 0, bool killCargo = false)
{ {
foreach (var kv in unitLocationPairs) 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 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 Renderer renderer;
readonly IShader shader; readonly IShader shader;
@@ -105,5 +105,11 @@ namespace OpenRA.Mods.Cnc.Traits
vortices.Clear(); vortices.Clear();
} }
void INotifyActorDisposing.Disposing(Actor self)
{
vortexSheet.Dispose();
vortexBuffer.Dispose();
}
} }
} }

View File

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

View File

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

View File

@@ -11,7 +11,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using OpenRA.Graphics; using OpenRA.Graphics;
using OpenRA.Mods.Common.EditorBrushes;
using OpenRA.Mods.Common.Graphics; using OpenRA.Mods.Common.Graphics;
using OpenRA.Mods.Common.Traits; using OpenRA.Mods.Common.Traits;
using OpenRA.Widgets; using OpenRA.Widgets;
@@ -87,6 +89,12 @@ namespace OpenRA.Mods.Common.Widgets
return ((long)pixelDistance << 32) + worldZPosition; 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) public void ClearSelection(bool updateSelectedTab = false)
{ {
if (Selection.HasSelection) 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 sealed class RemoveSelectedActorAction : IEditorAction
{ {
[FluentReference("name", "id")] [FluentReference("name", "id")]

View File

@@ -13,23 +13,28 @@ using System.Collections.Generic;
namespace OpenRA.Mods.Common.FileSystem 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 public class ContentInstallerFileSystemLoader : IFileSystemLoader, IFileSystemExternalContent
{ {
[FieldLoader.Require] [FieldLoader.Require]
[Desc("Mod to use for content installation.")]
public readonly string ContentInstallerMod = null; public readonly string ContentInstallerMod = null;
[FieldLoader.Require] [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> 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) 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); fileSystem.Mount(kv.Key, kv.Value);
if (ContentPackages != null) if (ContentPackages != null)
@@ -42,23 +47,23 @@ namespace OpenRA.Mods.Common.FileSystem
} }
catch catch
{ {
contentAvailable = false; isContentAvailable = false;
} }
} }
} }
if (ContentFiles != null) if (RequiredContentFiles != null)
foreach (var kv in ContentFiles) foreach (var kv in RequiredContentFiles)
if (!fileSystem.Exists(kv.Key)) if (!fileSystem.Exists(kv.Key))
contentAvailable = false; isContentAvailable = false;
} }
bool IFileSystemExternalContent.InstallContentIfRequired(ModData modData) bool IFileSystemExternalContent.InstallContentIfRequired(ModData modData)
{ {
if (!contentAvailable) if (!isContentAvailable)
Game.InitializeMod(ContentInstallerMod, new Arguments()); Game.InitializeMod(ContentInstallerMod, new Arguments());
return !contentAvailable; return !isContentAvailable;
} }
} }
} }

View File

@@ -3,6 +3,7 @@
<ProjectReference Include="..\OpenRA.Game\OpenRA.Game.csproj"> <ProjectReference Include="..\OpenRA.Game\OpenRA.Game.csproj">
<Private>False</Private> <Private>False</Private>
</ProjectReference> </ProjectReference>
<PackageReference Include="NuGet.CommandLine" Version="6.12.1" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" /> <PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="OpenRA-FuzzyLogicLibrary" Version="1.0.1" /> <PackageReference Include="OpenRA-FuzzyLogicLibrary" Version="1.0.1" />
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" /> <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); 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).")] "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) 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; 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).")] "function will be called as func(killed: actor).")]
public void OnAnyKilled(Actor[] actors, [ScriptEmmyTypeOverride("fun(killed: actor)")] LuaFunction func) 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. " + [Desc("Call a function when this actor produces another actor. " +
"The callback function will be called as func(producer: actor, produced: 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) if (actor == null)
throw new NullReferenceException(nameof(actors)); 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 " + [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 " + [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).")] "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) if (actor == null)
throw new NullReferenceException(nameof(actors)); 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. " + [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().")] "The callback function will be called as func().")]
public void OnKilledOrCaptured(Actor actor, [ScriptEmmyTypeOverride("fun()")] LuaFunction 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. " + [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().")] "The callback function will be called as func().")]
public void OnAllKilledOrCaptured(Actor[] actors, [ScriptEmmyTypeOverride("fun()")] LuaFunction 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)); 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 public Player EffectiveOwner
{ {
get get
@@ -96,7 +97,6 @@ namespace OpenRA.Mods.Common.Scripting
[ScriptPropertyGroup("General")] [ScriptPropertyGroup("General")]
public class GeneralProperties : ScriptActorProperties public class GeneralProperties : ScriptActorProperties
{ {
readonly IFacing facing;
readonly AutoTarget autotarget; readonly AutoTarget autotarget;
readonly ScriptTags scriptTags; readonly ScriptTags scriptTags;
readonly Tooltip[] tooltips; readonly Tooltip[] tooltips;
@@ -104,30 +104,11 @@ namespace OpenRA.Mods.Common.Scripting
public GeneralProperties(ScriptContext context, Actor self) public GeneralProperties(ScriptContext context, Actor self)
: base(context, self) : base(context, self)
{ {
facing = self.TraitOrDefault<IFacing>();
autotarget = self.TraitOrDefault<AutoTarget>(); autotarget = self.TraitOrDefault<AutoTarget>();
scriptTags = self.TraitOrDefault<ScriptTags>(); scriptTags = self.TraitOrDefault<ScriptTags>();
tooltips = self.TraitsImplementing<Tooltip>().ToArray(); 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] [ScriptActorPropertyActivity]
[Desc("Instantly moves the actor to the specified cell.")] [Desc("Instantly moves the actor to the specified cell.")]
public void Teleport(CPos cell) public void Teleport(CPos cell)
@@ -213,4 +194,32 @@ namespace OpenRA.Mods.Common.Scripting
return IsTaggable && scriptTags.HasTag(tag); return IsTaggable && scriptTags.HasTag(tag);
} }
} }
[ScriptPropertyGroup("General")]
public class LocationProperties : ScriptActorProperties, Requires<IOccupySpaceInfo>
{
public LocationProperties(ScriptContext context, Actor self)
: base(context, self) { }
[Desc("The actor position in cell coordinates.")]
public CPos Location => Self.Location;
[Desc("The actor position in world coordinates.")]
public WPos CenterPosition => Self.CenterPosition;
}
[ScriptPropertyGroup("General")]
public class FacingProperties : ScriptActorProperties, Requires<IFacingInfo>
{
readonly IFacing facing;
public FacingProperties(ScriptContext context, Actor self)
: base(context, self)
{
facing = self.Trait<IFacing>();
}
[Desc("The direction that the actor is facing.")]
public WAngle Facing => facing.Facing;
}
} }

View File

@@ -68,7 +68,8 @@ namespace OpenRA.Mods.Common.Scripting
[ScriptActorPropertyActivity] [ScriptActorPropertyActivity]
[Desc("Mark an objective as failed. This needs the objective ID returned " + [Desc("Mark an objective as failed. This needs the objective ID returned " +
"by AddObjective as argument. Secondary objectives do not have any " + "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) public void MarkFailedObjective(int id)
{ {
if (id < 0 || id >= mo.Objectives.Count) if (id < 0 || id >= mo.Objectives.Count)

View File

@@ -87,9 +87,6 @@ namespace OpenRA.Mods.Common.Server
[FluentReference("faction")] [FluentReference("faction")]
const string InvalidFactionSelected = "notification-invalid-faction-selected"; const string InvalidFactionSelected = "notification-invalid-faction-selected";
[FluentReference("factions")]
const string SupportedFactions = "notification-supported-factions";
[FluentReference] [FluentReference]
const string RequiresHost = "notification-requires-host"; const string RequiresHost = "notification-requires-host";
@@ -609,9 +606,8 @@ namespace OpenRA.Mods.Common.Server
foreach (var c in server.LobbyInfo.Clients) foreach (var c in server.LobbyInfo.Clients)
{ {
c.Faction = SanitizePlayerFaction(server, c.Faction, selectableFactions);
c.State = Session.ClientState.Invalid; c.State = Session.ClientState.Invalid;
if (!selectableFactions.Contains(c.Faction))
c.Faction = "Random";
} }
// Reassign players into new slots based on their old slots: // 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) if (server.LobbyInfo.Slots[targetClient.Slot].LockFaction)
return true; return true;
var factions = server.Map.WorldActorInfo.TraitInfos<FactionInfo>()
.Where(f => f.Selectable).Select(f => f.InternalName)
.ToList();
var faction = parts[1]; 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, InvalidFactionSelected, new object[] { "faction", faction });
server.SendFluentMessageTo(conn, SupportedFactions, new object[] { "factions", factions.JoinWith(", ") });
return true; 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) static string MissionBriefingOrDefault(S server)
{ {
var missionData = server.Map.WorldActorInfo.TraitInfoOrDefault<MissionDataInfo>(); 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"); var playerNode = nodes.NodeWithKeyOrDefault("Player");
if (playerNode != null) if (playerNode != null)
{ {
var client = server.GetClient(conn); var client = server.GetClient(conn);
SkirmishSlot.DeserializeToClient(playerNode.Value, client); SkirmishSlot.DeserializeToClient(playerNode.Value, client);
client.Color = LobbyCommands.SanitizePlayerColor(server, client.Color, client.Index); client.Color = LobbyCommands.SanitizePlayerColor(server, client.Color, client.Index);
client.Faction = LobbyCommands.SanitizePlayerFaction(server, client.Faction, selectableFactions);
} }
var botsNode = nodes.NodeWithKeyOrDefault("Bots"); var botsNode = nodes.NodeWithKeyOrDefault("Bots");
@@ -128,6 +134,8 @@ namespace OpenRA.Mods.Common.Server
if (client.Slot != null && !server.LobbyInfo.Slots[client.Slot].LockColor) if (client.Slot != null && !server.LobbyInfo.Slots[client.Slot].LockColor)
client.Color = LobbyCommands.SanitizePlayerColor(server, client.Color, client.Index); client.Color = LobbyCommands.SanitizePlayerColor(server, client.Color, client.Index);
client.Faction = LobbyCommands.SanitizePlayerFaction(server, client.Faction, selectableFactions);
server.LobbyInfo.Clients.Add(client); server.LobbyInfo.Clients.Add(client);
S.SyncClientToPlayerReference(client, server.Map.Players.Players[client.Slot]); S.SyncClientToPlayerReference(client, server.Map.Players.Players[client.Slot]);
} }

View File

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

View File

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

View File

@@ -173,7 +173,7 @@ namespace OpenRA.Mods.Common.Traits
// Returns true if FindNextResource was called. // Returns true if FindNextResource was called.
bool HarvestIfAble(IBot bot, HarvesterTraitWrapper h) bool HarvestIfAble(IBot bot, HarvesterTraitWrapper h)
{ {
if (h.Mobile == null) if (h.Actor.IsDead || !h.Actor.IsInWorld || h.Mobile == null)
return false; return false;
if (!h.Actor.IsIdle) if (!h.Actor.IsIdle)
@@ -195,7 +195,7 @@ namespace OpenRA.Mods.Common.Traits
// Tell the idle harvester to quit slacking: // Tell the idle harvester to quit slacking:
var newSafeResourcePatch = FindNextResource(h.Actor, h); var newSafeResourcePatch = FindNextResource(h.Actor, h);
AIUtils.BotDebug($"AI: Harvester {h.Actor} is idle. Ordering to {newSafeResourcePatch} in search for new resources."); 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)); bot.QueueOrder(new Order("Harvest", h.Actor, newSafeResourcePatch, false));
else else
h.NoResourcesCooldown = Info.ScanIntervalMultiplerWhenNoResources; 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())) 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("ActorToTarget", FieldSaver.FormatValue(TargetActor.ActorID)));
nodes.Add(new MiniYamlNode("TargetOffset", FieldSaver.FormatValue(Target.CenterPosition - TargetActor.CenterPosition))); 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.EnterCursor,
Info.EnterBlockedCursor, Info.EnterBlockedCursor,
() => Info.RequiresForceMove, () => Info.RequiresForceMove,
DockingPossible, CanQueueDockAt,
CanDockAt); CanDockAt);
} }
} }
@@ -109,9 +109,13 @@ namespace OpenRA.Mods.Common.Traits
string IOrderVoice.VoicePhraseForOrder(Actor self, Order order) string IOrderVoice.VoicePhraseForOrder(Actor self, Order order)
{ {
if (order.OrderString == "Dock" && CanDockAt(order.Target.Actor, false)) if (order.Target.Type != TargetType.Actor || IsTraitDisabled)
return Info.Voice; return null;
else if (order.OrderString == "ForceDock" && CanDockAt(order.Target.Actor, true))
if (order.OrderString != "Dock" && order.OrderString != "ForceDock")
return null;
if (CanQueueDockAt(order.Target.Actor, order.OrderString == "ForceDock", order.Queued))
return Info.Voice; return Info.Voice;
return null; return null;
@@ -125,12 +129,6 @@ namespace OpenRA.Mods.Common.Traits
return null; 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> /// <summary>Clone of <see cref="DockClientManager.CanDockAt(Actor, bool, bool)"/>.</summary>
public bool CanDockAt(Actor target, bool forceEnter) public bool CanDockAt(Actor target, bool forceEnter)
{ {
@@ -140,5 +138,16 @@ namespace OpenRA.Mods.Common.Traits
return !IsTraitDisabled && target.TraitsImplementing<DockHost>().Any( return !IsTraitDisabled && target.TraitsImplementing<DockHost>().Any(
host => dockClients.Any(client => client.CanDockAt(target, host, forceEnter, true))); 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 (target != currentTarget)
{ {
if (currentTargetManager != null) if (currentTargetManager != null)
CancelCapture(currentTargetManager); CancelCapture(currentTarget, currentTargetManager);
targetManager.currentCaptors.Add(self); targetManager.currentCaptors.Add(self);
currentTarget = target; currentTarget = target;
@@ -239,12 +239,11 @@ namespace OpenRA.Mods.Common.Traits
/// This method revokes the capturing conditions on the captor and target /// This method revokes the capturing conditions on the captor and target
/// and resets any capturing progress. /// and resets any capturing progress.
/// </summary> /// </summary>
public void CancelCapture(CaptureManager targetManager) public void CancelCapture(Actor target, CaptureManager targetManager)
{ {
if (currentTarget == null) if (currentTarget == null)
return; return;
var target = targetManager.self;
foreach (var w in progressWatchers) foreach (var w in progressWatchers)
w.Update(self, self, target, 0, 0); w.Update(self, self, target, 0, 0);

View File

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

View File

@@ -160,7 +160,7 @@ namespace OpenRA.Mods.Common.Traits
Info.EnterCursor, Info.EnterCursor,
Info.EnterBlockedCursor, Info.EnterBlockedCursor,
() => requireForceMove, () => requireForceMove,
DockingPossible, CanQueueDockAt,
(target, forceEnter) => CanDockAt(target, forceEnter, true)); (target, forceEnter) => CanDockAt(target, forceEnter, true));
} }
} }
@@ -194,9 +194,10 @@ namespace OpenRA.Mods.Common.Traits
if (order.Target.Type != TargetType.Actor || IsTraitDisabled) if (order.Target.Type != TargetType.Actor || IsTraitDisabled)
return null; return null;
if (order.OrderString == "Dock" && CanDockAt(order.Target.Actor, false, true)) if (order.OrderString != "Dock" && order.OrderString != "ForceDock")
return Info.Voice; return null;
else if (order.OrderString == "ForceDock" && CanDockAt(order.Target.Actor, true, true))
if (CanQueueDockAt(order.Target.Actor, order.OrderString == "ForceDock", order.Queued))
return Info.Voice; return Info.Voice;
return null; return null;
@@ -225,13 +226,13 @@ namespace OpenRA.Mods.Common.Traits
} }
/// <summary>Do we have an enabled client with matching <paramref name="type"/>.</summary> /// <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)); 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> /// <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 && return !IsTraitDisabled &&
target.TraitsImplementing<IDockHost>() target.TraitsImplementing<IDockHost>()
@@ -252,6 +253,14 @@ namespace OpenRA.Mods.Common.Traits
host => dockClients.Any(client => client.CanDockAt(target, host, forceEnter, ignoreOccupancy))); 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> /// <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> /// <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) 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 enterCursor;
readonly string enterBlockedCursor; readonly string enterBlockedCursor;
readonly Func<bool> requireForceMove; readonly Func<bool> requireForceMove;
readonly Func<Actor, bool, bool> canTarget; readonly Func<Actor, bool, bool, bool> canTarget;
readonly Func<Actor, bool, bool> useEnterCursor; readonly Func<Actor, bool, bool> useEnterCursor;
public DockActorTargeter(int priority, string enterCursor, string enterBlockedCursor, 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"; OrderID = "Dock";
OrderPriority = priority; OrderPriority = priority;
@@ -325,10 +334,13 @@ namespace OpenRA.Mods.Common.Traits
if (requireForceMove() && !forceEnter) if (requireForceMove() && !forceEnter)
return false; return false;
if (!canTarget(target.Actor, forceEnter)) if (!canTarget(target.Actor, forceEnter, IsQueued))
return false; return false;
cursor = useEnterCursor(target.Actor, forceEnter) ? enterCursor : enterBlockedCursor; cursor = IsQueued || useEnterCursor(target.Actor, forceEnter)
? enterCursor
: enterBlockedCursor;
return true; return true;
} }

View File

@@ -137,6 +137,12 @@ namespace OpenRA.Mods.Common.Traits
&& (self.Owner == hostActor.Owner || (ignoreOccupancy && self.Owner.IsAlliedWith(hostActor.Owner))); && (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) void UpdateCondition(Actor self)
{ {
if (string.IsNullOrEmpty(Info.EmptyCondition)) if (string.IsNullOrEmpty(Info.EmptyCondition))

View File

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

View File

@@ -1000,6 +1000,23 @@ namespace OpenRA.Mods.Common.Traits
foreach (var cell in rallyPoint) foreach (var cell in rallyPoint)
QueueChild(new AttackMoveActivity(self, () => mobile.MoveTo(cell, 1, evaluateNearestMovableCell: true, targetLineColor: Color.OrangeRed))); 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() Activity ICreationActivity.GetCreationActivity()

View File

@@ -61,7 +61,7 @@ namespace OpenRA.Mods.Common.Traits
protected override void PrepareRender(WorldRenderer wr, IShader shader) protected override void PrepareRender(WorldRenderer wr, IShader shader)
{ {
shader.SetVec("Blend", blend); 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) 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)) 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)) if (newResourceType != null && newDensity > 0 && resourceValues.TryGetValue(newResourceType, out var newResourceValue))
NetWorth += (newDensity + 1) * newResourceValue; NetWorth += newDensity * newResourceValue;
} }
public int CalculateRegionValue(CellRegion sourceRegion) public int CalculateRegionValue(CellRegion sourceRegion)
@@ -188,39 +187,44 @@ namespace OpenRA.Mods.Common.Traits
foreach (var cell in sourceRegion.CellCoords) foreach (var cell in sourceRegion.CellCoords)
{ {
var mcell = cell.ToMPos(Map); var mcell = cell.ToMPos(Map);
if (Map.Resources.Contains(mcell) && Map.Resources[mcell].Type != 0) if (!Map.Resources.Contains(mcell))
{ continue;
resourceValueInRegion++;
var rcell = Map.Resources[mcell]; var resource = Map.Resources[mcell].Type;
if (ResourceTypesByIndex.TryGetValue(rcell.Type, out var resourceType) && resourceValues.TryGetValue(resourceType, out var resourceValuePerUnit)) if (resource != 0
&& ResourceTypesByIndex.TryGetValue(resource, out var resourceType)
&& resourceValues.TryGetValue(resourceType, out var resourceValuePerUnit))
resourceValueInRegion += Tiles[mcell].Density * resourceValuePerUnit; resourceValueInRegion += Tiles[mcell].Density * resourceValuePerUnit;
} }
}
return resourceValueInRegion; 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; 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; return 0;
if (!info.RecalculateResourceDensity) if (!info.RecalculateResourceDensity)
return contents.Density.Clamp(1, resourceInfo.MaxDensity); 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; 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 c = cell + directions[i];
{ if (resources.Contains(c) && resources[c].Type == resourceInfo.ResourceIndex)
var cell = c + new CVec(u, v); ++adjacent;
if (resources.Contains(cell) && resources[cell].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); 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 namespace OpenRA.Mods.Common.Traits
{ {
public abstract class RenderPostProcessPassBase : IRenderPostProcessPass public abstract class RenderPostProcessPassBase : IRenderPostProcessPass, INotifyActorDisposing
{ {
readonly Renderer renderer; readonly Renderer renderer;
readonly IShader shader; readonly IShader shader;
@@ -52,5 +52,10 @@ namespace OpenRA.Mods.Common.Traits
protected abstract bool Enabled { get; } protected abstract bool Enabled { get; }
protected abstract void PrepareRender(WorldRenderer wr, IShader shader); 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;
} }
// 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); var density = Math.Max(int2.Lerp(0, resourceInfo.MaxDensity, adjacent, 9), 1);
Content[cell] = new ResourceLayerContents(resource.Type, density); 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); bool OnDockTick(Actor self, Actor hostActor, IDockHost dock);
void OnDockCompleted(Actor self, Actor hostActor, IDockHost host); void OnDockCompleted(Actor self, Actor hostActor, IDockHost host);
/// <summary>Is this client allowed to dock.</summary> /// <summary>Are we allowed to dock.</summary>
/// <remarks> /// <remarks>
/// Does not check if <see cref="Traits.DockClientManager"/> is enabled. /// 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"/>. /// Function should only be called from within <see cref="IDockClient"/> or <see cref="Traits.DockClientManager"/>.
/// </remarks> /// </remarks>
bool CanDock(BitSet<DockType> type, bool forceEnter = false); 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> /// <remarks>
/// Does not check if <see cref="Traits.DockClientManager"/> is enabled. /// 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"/>. /// Function should only be called from within <see cref="IDockClient"/> or <see cref="Traits.DockClientManager"/>.
/// </remarks> /// </remarks>
bool CanDockAt(Actor hostActor, IDockHost host, bool forceEnter = false, bool ignoreOccupancy = false); 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 { } 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>()); var palette = new ImmutablePalette(args[1], new[] { 0 }, Array.Empty<int>());
SequenceSet sequences; SequenceSet sequences;
if (modData.DefaultTerrainInfo.ContainsKey(args[2])) var tilesetUpper = args[2].ToUpperInvariant();
sequences = new SequenceSet(modData.ModFiles, modData, args[2], null); if (modData.DefaultTerrainInfo.ContainsKey(tilesetUpper))
sequences = new SequenceSet(modData.ModFiles, modData, tilesetUpper, null);
else else
{ {
var mapPackage = new Folder(Platform.EngineDir).OpenPackage(args[2], modData.ModFiles); 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) foreach (var layout in chromeLayouts)
{ {
var fluentFolder = layout.Key + "|languages"; var fluentFolder = layout.Key + "|fluent";
var fluentPackage = modData.ModFiles.OpenPackage(fluentFolder); var fluentPackage = modData.ModFiles.OpenPackage(fluentFolder);
var fluentPath = Path.Combine(fluentPackage.Name, "chrome.ftl"); var fluentPath = Path.Combine(fluentPackage.Name, "chrome.ftl");
@@ -262,12 +262,23 @@ namespace OpenRA.Mods.Common.UtilityCommands
&& !UpdateUtils.IsAlreadyExtracted(childNode.Value.Value) && !UpdateUtils.IsAlreadyExtracted(childNode.Value.Value)
&& childNode.Value.Value.Any(char.IsLetterOrDigit)) && childNode.Value.Value.Any(char.IsLetterOrDigit))
{ {
var value = childNode.Value.Value var replaced = false;
.Replace("\\n", "\n ") var value = childNode.Value.Value;
if (value.Contains("\\n"))
{
value = value.Replace("\\n", "\n ");
replaced = true;
}
value = value
.Replace("{", "<") .Replace("{", "<")
.Replace("}", ">") .Replace("}", ">")
.Trim().Trim('\n'); .Trim().Trim('\n');
// Preserve indentation
if (replaced)
value = "\n " + value;
validChildTypes.Add((childNode, childType.ToLowerInvariant(), 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); ExtractFromFile(Path.Combine(fluentPackage.Name, "rules.ftl"), modRules, traitInfos);
modRules.Save(); modRules.Save();
@@ -307,9 +307,16 @@ namespace OpenRA.Mods.Common.UtilityCommands
if (string.IsNullOrEmpty(propertyValue) || UpdateUtils.IsAlreadyExtracted(propertyValue) || !propertyValue.Any(char.IsLetterOrDigit)) if (string.IsNullOrEmpty(propertyValue) || UpdateUtils.IsAlreadyExtracted(propertyValue) || !propertyValue.Any(char.IsLetterOrDigit))
continue; continue;
var value = propertyValue var replaced = false;
.Replace("\\n", "\n ") var value = propertyValue;
.Trim().Trim('\n'); 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 actorName = ToLowerActor(actor.Key);
var key = traitInfo; var key = traitInfo;

View File

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

View File

@@ -19,6 +19,7 @@ namespace OpenRA.Mods.Common.Widgets
{ {
Sprite hueSprite; Sprite hueSprite;
Sprite pickerSprite; Sprite pickerSprite;
Sheet hueSheet;
public HueSliderWidget() { } public HueSliderWidget() { }
public HueSliderWidget(HueSliderWidget other) public HueSliderWidget(HueSliderWidget other)
@@ -28,7 +29,7 @@ namespace OpenRA.Mods.Common.Widgets
{ {
base.Initialize(args); 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]; 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); 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); 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 DiagonalLabel;
public LabelWidget ResourceCounterLabel; public LabelWidget ResourceCounterLabel;
MapBlitFilters copyFilters = MapBlitFilters.All; MapBlitFilters selectionFilters = MapBlitFilters.All;
EditorBlitSource? clipboard; EditorBlitSource? clipboard;
[ObjectCreator.UseCtor] [ObjectCreator.UseCtor]
@@ -81,12 +81,15 @@ namespace OpenRA.Mods.Common.Widgets.Logic
worldRenderer, worldRenderer,
clipboard.Value, clipboard.Value,
resourceLayer, resourceLayer,
() => copyFilters)); () => selectionFilters));
}; };
pasteButton.IsDisabled = () => clipboard == null; pasteButton.IsDisabled = () => clipboard == null;
pasteButton.IsHighlighted = () => editor.CurrentBrush is EditorCopyPasteBrush; 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"); var closeAreaSelectionButton = areaEditPanel.Get<ButtonWidget>("SELECTION_CANCEL_BUTTON");
closeAreaSelectionButton.OnClick = () => editor.DefaultBrush.ClearSelection(updateSelectedTab: true); closeAreaSelectionButton.OnClick = () => editor.DefaultBrush.ClearSelection(updateSelectedTab: true);
@@ -102,15 +105,15 @@ namespace OpenRA.Mods.Common.Widgets.Logic
editorActorLayer, editorActorLayer,
resourceLayer, resourceLayer,
editor.DefaultBrush.Selection.Area, editor.DefaultBrush.Selection.Area,
copyFilters); selectionFilters);
} }
void CreateCategoryPanel(MapBlitFilters copyFilter, CheckboxWidget checkbox) void CreateCategoryPanel(MapBlitFilters copyFilter, CheckboxWidget checkbox)
{ {
checkbox.GetText = () => copyFilter.ToString(); checkbox.GetText = () => copyFilter.ToString();
checkbox.IsChecked = () => copyFilters.HasFlag(copyFilter); checkbox.IsChecked = () => selectionFilters.HasFlag(copyFilter);
checkbox.IsVisible = () => true; checkbox.IsVisible = () => true;
checkbox.OnClick = () => copyFilters ^= copyFilter; checkbox.OnClick = () => selectionFilters ^= copyFilter;
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)

View File

@@ -50,6 +50,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
readonly LabelWidget productionTime; readonly LabelWidget productionTime;
readonly Widget productionPowerIcon; readonly Widget productionPowerIcon;
readonly LabelWidget productionPower; readonly LabelWidget productionPower;
readonly List<Sheet> sheets = new();
ActorInfo selectedActor; ActorInfo selectedActor;
ScrollItemWidget firstItem; ScrollItemWidget firstItem;
@@ -81,6 +82,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
defaultPortrait = new Png(modData.DefaultFileSystem.Open("encyclopedia/default.png")); defaultPortrait = new Png(modData.DefaultFileSystem.Open("encyclopedia/default.png"));
var spriteBounds = new Rectangle(0, 0, defaultPortrait.Width, defaultPortrait.Height); var spriteBounds = new Rectangle(0, 0, defaultPortrait.Width, defaultPortrait.Height);
var sheet = new Sheet(SheetType.BGRA, spriteBounds.Size.NextPowerOf2()); var sheet = new Sheet(SheetType.BGRA, spriteBounds.Size.NextPowerOf2());
sheets.Add(sheet);
sheet.CreateBuffer(); sheet.CreateBuffer();
sheet.GetTexture().ScaleFilter = TextureScaleFilter.Linear; sheet.GetTexture().ScaleFilter = TextureScaleFilter.Linear;
portraitSprite = new Sprite(sheet, spriteBounds, TextureChannel.RGBA); portraitSprite = new Sprite(sheet, spriteBounds, TextureChannel.RGBA);
@@ -304,5 +306,13 @@ namespace OpenRA.Mods.Common.Widgets.Logic
time = time * bi.BuildDurationModifier * pbi / 10000; time = time * bi.BuildDurationModifier * pbi / 10000;
return time; 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 else
{ {
LobbyUtils.SetupNameWidget(template, client, orderManager, worldRenderer); LobbyUtils.SetupNameWidget(template, client, orderManager, worldRenderer, map);
LobbyUtils.SetupTeamWidget(template, client); LobbyUtils.SetupTeamWidget(template, client);
LobbyUtils.SetupHandicapWidget(template, client); LobbyUtils.SetupHandicapWidget(template, client);
LobbyUtils.SetupSpawnWidget(template, client); LobbyUtils.SetupSpawnWidget(template, client);
@@ -803,7 +803,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
LobbyUtils.SetupPlayerActionWidget(template, client, orderManager, worldRenderer, LobbyUtils.SetupPlayerActionWidget(template, client, orderManager, worldRenderer,
lobby, () => panel = PanelType.Kick, () => panel = PanelType.Players); lobby, () => panel = PanelType.Kick, () => panel = PanelType.Players);
else else
LobbyUtils.SetupNameWidget(template, client, orderManager, worldRenderer); LobbyUtils.SetupNameWidget(template, client, orderManager, worldRenderer, map);
if (client.IsAdmin) if (client.IsAdmin)
LobbyUtils.SetupReadyWidget(template, client); LobbyUtils.SetupReadyWidget(template, client);

View File

@@ -33,6 +33,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic
[FluentReference] [FluentReference]
const string Bots = "options-lobby-slot.bots"; const string Bots = "options-lobby-slot.bots";
[FluentReference]
const string BotPlayer = "label-bot-player";
[FluentReference] [FluentReference]
const string BotsDisabled = "options-lobby-slot.bots-disabled"; 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>()) foreach (var b in map.PlayerActorInfo.TraitInfos<IBotInfo>())
{ {
var botController = orderManager.LobbyInfo.Clients.FirstOrDefault(c => c.IsAdmin); 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}", $"slot_bot {slot.PlayerReference} {botController.Index} {b.Type}",
() => client != null && client.Bot == b.Type)); () => client != null && client.Bot == b.Type));
} }
@@ -425,14 +428,22 @@ namespace OpenRA.Mods.Common.Widgets.Logic
HideChildWidget(parent, "SLOT_OPTIONS"); 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"); var label = parent.Get<LabelWidget>("NAME");
label.IsVisible = () => true; label.IsVisible = () => true;
var font = Game.Renderer.Fonts[label.Font]; 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); var clientName = new CachedTransform<MapStatus, string>(s =>
label.GetText = () => text; {
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); SetupProfileWidget(parent, c, orderManager, worldRenderer);
} }
@@ -450,8 +461,22 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var closed = FluentProvider.GetMessage(Closed); var closed = FluentProvider.GetMessage(Closed);
var open = FluentProvider.GetMessage(Open); 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 ? slot.GetText = () => truncated.Update(c != null ?
c.IsBot ? FluentProvider.GetMessage(c.Name) : c.Name clientName.Update(map.Status)
: s.Closed ? closed : open); : s.Closed ? closed : open);
slot.OnMouseDown = _ => ShowSlotDropDown(slot, s, c, orderManager, map, modData); 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; protected MenuType menuType = MenuType.Main;
readonly Widget rootMenu; readonly Widget rootMenu;
readonly ScrollPanelWidget newsPanel; readonly ScrollPanelWidget newsPanel;
readonly int maxNewsHeight;
readonly Widget newsTemplate; readonly Widget newsTemplate;
readonly LabelWidget newsStatus; readonly LabelWidget newsStatus;
readonly ModData modData; readonly ModData modData;
@@ -230,6 +231,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
newsPanel = Ui.LoadWidget<ScrollPanelWidget>("NEWS_PANEL", null, new WidgetArgs()); newsPanel = Ui.LoadWidget<ScrollPanelWidget>("NEWS_PANEL", null, new WidgetArgs());
newsTemplate = newsPanel.Get("NEWS_ITEM_TEMPLATE"); newsTemplate = newsPanel.Get("NEWS_ITEM_TEMPLATE");
newsPanel.RemoveChild(newsTemplate); newsPanel.RemoveChild(newsTemplate);
maxNewsHeight = newsPanel.Bounds.Height;
newsStatus = newsPanel.Get<LabelWidget>("NEWS_STATUS"); newsStatus = newsPanel.Get<LabelWidget>("NEWS_STATUS");
SetNewsStatus(FluentProvider.GetMessage(LoadingNews)); SetNewsStatus(FluentProvider.GetMessage(LoadingNews));
@@ -416,10 +418,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
newsPanel.RemoveChildren(); newsPanel.RemoveChildren();
SetNewsStatus(""); SetNewsStatus("");
foreach (var i in newsItems) foreach (var item in newsItems)
{ {
var item = i;
var newsItem = newsTemplate.Clone(); var newsItem = newsTemplate.Clone();
var titleLabel = newsItem.Get<LabelWidget>("TITLE"); var titleLabel = newsItem.Get<LabelWidget>("TITLE");
@@ -441,6 +441,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
newsPanel.AddChild(newsItem); newsPanel.AddChild(newsItem);
newsPanel.Layout.AdjustChildren(); 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 headerTemplate;
readonly ScrollItemWidget template; 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; MapPreview selectedMap;
PlayingVideo playingVideo; PlayingVideo playingVideo;
readonly Dictionary<string, string> missionOptions = new(); readonly Dictionary<string, string> missionOptions = new();
@@ -125,6 +137,16 @@ namespace OpenRA.Mods.Common.Widgets.Logic
stopInfoVideoButton.IsVisible = () => playingVideo == PlayingVideo.Info; stopInfoVideoButton.IsVisible = () => playingVideo == PlayingVideo.Info;
stopInfoVideoButton.OnClick = () => StopVideo(videoPlayer); 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>(); var allPreviews = new List<MapPreview>();
missionList.RemoveChildren(); missionList.RemoveChildren();
@@ -204,16 +226,14 @@ namespace OpenRA.Mods.Common.Widgets.Logic
}; };
var tabContainer = widget.Get("MISSION_TABS"); var tabContainer = widget.Get("MISSION_TABS");
tabContainer.IsVisible = () => true; tabContainer.IsVisible = () => !minifiedOptions;
var optionsTab = tabContainer.Get<ButtonWidget>("OPTIONS_TAB"); var optionsTab = tabContainer.Get<ButtonWidget>("OPTIONS_TAB");
optionsTab.IsHighlighted = () => panel == PanelType.Options; optionsTab.IsHighlighted = () => panel == PanelType.Options;
optionsTab.IsDisabled = () => false;
optionsTab.OnClick = () => panel = PanelType.Options; optionsTab.OnClick = () => panel = PanelType.Options;
var missionTab = tabContainer.Get<ButtonWidget>("MISSIONINFO_TAB"); var missionTab = tabContainer.Get<ButtonWidget>("MISSIONINFO_TAB");
missionTab.IsHighlighted = () => panel == PanelType.MissionInfo; missionTab.IsHighlighted = () => panel == PanelType.MissionInfo;
missionTab.IsDisabled = () => false;
missionTab.OnClick = () => panel = PanelType.MissionInfo; missionTab.OnClick = () => panel = PanelType.MissionInfo;
} }
@@ -319,6 +339,44 @@ namespace OpenRA.Mods.Common.Widgets.Logic
.Where(o => o.IsVisible) .Where(o => o.IsVisible)
.OrderBy(o => o.DisplayOrder).ToArray(); .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; Widget row = null;
var checkboxColumns = new Queue<CheckboxWidget>(); var checkboxColumns = new Queue<CheckboxWidget>();
var dropdownColumns = new Queue<DropDownButtonWidget>(); var dropdownColumns = new Queue<DropDownButtonWidget>();
@@ -364,6 +422,11 @@ namespace OpenRA.Mods.Common.Widgets.Logic
foreach (var option in allOptions.Where(o => o is not LobbyBooleanOption)) foreach (var option in allOptions.Where(o => o is not LobbyBooleanOption))
{ {
if (option.Id == "difficulty")
SetMapDifficulty(option);
else if (option.Id == "gamespeed")
SetMapSpeed(option);
else
missionOptions[option.Id] = option.DefaultValue; missionOptions[option.Id] = option.DefaultValue;
if (dropdownColumns.Count == 0) if (dropdownColumns.Count == 0)
@@ -378,8 +441,54 @@ namespace OpenRA.Mods.Common.Widgets.Logic
optionsContainer.AddChild(row); optionsContainer.AddChild(row);
} }
var dropdown = dropdownColumns.Dequeue(); var val = dropdownColumns.Dequeue();
SetupDropdown(val, option);
var label = row.GetOrNull<LabelWidget>(val.Id + "_DESC");
if (label != null)
{
label.GetText = () => option.Name + ":";
label.IsVisible = () => true;
}
}
}
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 = () => dropdown.GetText = () =>
{ {
if (option.Values.TryGetValue(missionOptions[option.Id], out var value)) if (option.Values.TryGetValue(missionOptions[option.Id], out var value))
@@ -403,7 +512,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
ScrollItemWidget SetupItem(KeyValuePair<string, string> c, ScrollItemWidget template) ScrollItemWidget SetupItem(KeyValuePair<string, string> c, ScrollItemWidget template)
{ {
bool IsSelected() => missionOptions[option.Id] == c.Key; bool IsSelected() => missionOptions[option.Id] == c.Key;
void OnClick() => missionOptions[option.Id] = c.Key; void OnClick() => OnOptionSelected(option.Id, c.Key);
var item = ScrollItemWidget.Setup(template, IsSelected, OnClick); var item = ScrollItemWidget.Setup(template, IsSelected, OnClick);
item.Get<LabelWidget>("LABEL").GetText = () => c.Value; item.Get<LabelWidget>("LABEL").GetText = () => c.Value;
@@ -412,14 +521,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic
dropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", option.Values.Count * 30, option.Values, SetupItem); dropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", option.Values.Count * 30, option.Values, SetupItem);
}; };
var label = row.GetOrNull<LabelWidget>(dropdown.Id + "_DESC");
if (label != null)
{
label.GetText = () => option.Name + ":";
label.IsVisible = () => true;
}
}
} }
float cachedSoundVolume; float cachedSoundVolume;

View File

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

View File

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

View File

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

View File

@@ -341,6 +341,14 @@ namespace OpenRA.Platforms.Default
// Run graphics rendering on a dedicated thread. // Run graphics rendering on a dedicated thread.
// The calling thread will then have more time to process other tasks, since rendering happens in parallel. // 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. // If the calling thread is the main game thread, this means it can run more logic and render ticks.
// 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 = new ThreadedGraphicsContext(new Sdl2GraphicsContext(this), vertexBatchSize, indexBatchSize);
Context.SetVSyncEnabled(Game.Settings.Graphics.VSync); Context.SetVSyncEnabled(Game.Settings.Graphics.VSync);

View File

@@ -21,7 +21,7 @@
</AssemblyAttribute> </AssemblyAttribute>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="OpenRA-SDL2-CS" Version="1.0.41" /> <PackageReference Include="OpenRA-SDL2-CS" Version="1.0.42" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\OpenRA.Game\OpenRA.Game.csproj" /> <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); 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) switch (samplerIndex)
{ {
case 7u: case 7u:
return textureSize(Texture7, 0); return vec2(textureSize(Texture7, 0));
case 6u: case 6u:
return textureSize(Texture6, 0); return vec2(textureSize(Texture6, 0));
case 5u: case 5u:
return textureSize(Texture5, 0); return vec2(textureSize(Texture5, 0));
case 4u: case 4u:
return textureSize(Texture4, 0); return vec2(textureSize(Texture4, 0));
case 3u: case 3u:
return textureSize(Texture3, 0); return vec2(textureSize(Texture3, 0));
case 2u: case 2u:
return textureSize(Texture2, 0); return vec2(textureSize(Texture2, 0));
case 1u: case 1u:
return textureSize(Texture1, 0); return vec2(textureSize(Texture1, 0));
default: default:
return textureSize(Texture0, 0); return vec2(textureSize(Texture0, 0));
} }
} }
@@ -160,7 +160,7 @@ void main()
vec4 c; vec4 c;
if (EnablePixelArtScaling) if (EnablePixelArtScaling)
{ {
vec2 textureSize = vec2(Size(vChannelSampler)); vec2 textureSize = Size(vChannelSampler);
vec2 vUv = coords.st * textureSize; vec2 vUv = coords.st * textureSize;
vec2 offset = fract(vUv); vec2 offset = fract(vUv);
vec2 pixelsPerTexel = vec2(1.0 / dFdx(vUv.x), 1.0 / dFdy(vUv.y)); vec2 pixelsPerTexel = vec2(1.0 / dFdx(vUv.x), 1.0 / dFdy(vUv.y));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -68,6 +68,44 @@ Container@MISSIONBROWSER_PANEL:
IgnoreMouseOver: True IgnoreMouseOver: True
IgnoreMouseInput: True IgnoreMouseInput: True
ShowSpawnPoints: False 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: Container@MISSION_TABS:
Width: PARENT_WIDTH Width: PARENT_WIDTH
Y: PARENT_HEIGHT - 31 Y: PARENT_HEIGHT - 31

View File

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

View File

@@ -1,7 +1,7 @@
## world ## world
dropdown-difficulty = dropdown-difficulty =
.label = Difficulty .label = Difficulty
.description = The difficulty of the mission. .description = The difficulty of the mission
options-difficulty = options-difficulty =
.easy = Easy .easy = Easy

View File

@@ -55,7 +55,6 @@ label-save-map-background-directory = Directory:
label-save-map-background-filename = Filename: label-save-map-background-filename = Filename:
button-save-map-panel = Save button-save-map-panel = Save
label-actor-edit-panel-id = ID label-actor-edit-panel-id = ID
button-container-delete = Delete
button-container-ok = OK button-container-ok = OK
button-editor-world-root-options-tooltip = Menu button-editor-world-root-options-tooltip = Menu
label-tiles-bg-search = Search: label-tiles-bg-search = Search:
@@ -65,12 +64,12 @@ label-actor-bg-owner = Owner:
label-area-selection = Area Selection label-area-selection = Area Selection
label-area-info = Area Info label-area-info = Area Info
label-selected-area-diagonal = Diagonal: label-selected-area-diagonal = Diagonal:
label-selected-area-resources= Resources: label-selected-area-resources = Resources:
label-copy-filters = Copy Filters label-selection-filters = Filters
label-filter-terrain = Terrain label-filter-terrain = Terrain
label-filter-resources = Resources label-filter-resources = Resources
label-filter-actors = Actors 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-current = Clear Current
button-marker-tiles-clear-all = Clear All button-marker-tiles-clear-all = Clear All
label-marker-layer-num-sides = Number of Sides label-marker-layer-num-sides = Number of Sides
@@ -85,6 +84,14 @@ button-map-editor-tab-container-actors-tooltip = Actors
button-map-editor-tab-container-tools-tooltip = Tools button-map-editor-tab-container-tools-tooltip = Tools
button-map-editor-tab-container-history-tooltip = History 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 = button-editor-world-root-copy =
.label = Copy .label = Copy
.tooltip = Copy the selected area .tooltip = Copy the selected area
@@ -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. label-script-error-panel-desc-c = Please send this file to the map author so that they can fix this issue.
## ingame-infostats.yaml ## ingame-infostats.yaml
label-objective-mission = Mission: label-mission-objective = Mission:
checkbox-objective-stats = Destroy all opposition! checkbox-stats-objective = Destroy all opposition!
label-stats-player = Player label-stats-player = Player
label-stats-faction = Faction label-stats-faction = Faction
label-stats-score = Score label-stats-score = Score
@@ -233,7 +240,8 @@ supportpowers-support-powers-palette =
button-command-bar-attack-move = button-command-bar-attack-move =
.tooltip = Attack Move .tooltip = Attack Move
.tooltipdesc = Selected units will move to the desired location .tooltipdesc =
Selected units will move to the desired location
and attack any enemies they encounter en route. and attack any enemies they encounter en route.
Hold <(Ctrl)> while targeting to order an Assault Move Hold <(Ctrl)> while targeting to order an Assault Move
@@ -243,7 +251,8 @@ button-command-bar-attack-move =
button-command-bar-force-move = button-command-bar-force-move =
.tooltip = Force Move .tooltip = Force Move
.tooltipdesc = Selected units will move to the desired location .tooltipdesc =
Selected units will move to the desired location
- Default activity for the target is suppressed - Default activity for the target is suppressed
- Vehicles will attempt to crush enemies at the target location - Vehicles will attempt to crush enemies at the target location
- Helicopters will land at the target location - Helicopters will land at the target location
@@ -253,7 +262,8 @@ button-command-bar-force-move =
button-command-bar-force-attack = button-command-bar-force-attack =
.tooltip = Force Attack .tooltip = Force Attack
.tooltipdesc = Selected units will attack the targeted unit or location .tooltipdesc =
Selected units will attack the targeted unit or location
- Default activity for the target is suppressed - Default activity for the target is suppressed
- Allows targeting of own or ally forces - Allows targeting of own or ally forces
- Long-range artillery units will always target the - Long-range artillery units will always target the
@@ -264,13 +274,15 @@ button-command-bar-force-attack =
button-command-bar-guard = button-command-bar-guard =
.tooltip = Guard .tooltip = Guard
.tooltipdesc = Selected units will follow the targeted unit. .tooltipdesc =
Selected units will follow the targeted unit.
Left-click icon then right-click on target unit. Left-click icon then right-click on target unit.
button-command-bar-deploy = button-command-bar-deploy =
.tooltip = Deploy .tooltip = Deploy
.tooltipdesc = Selected units will perform their default deploy activity .tooltipdesc =
Selected units will perform their default deploy activity
- MCVs will unpack into a Construction Yard - MCVs will unpack into a Construction Yard
- Construction Yards will re-pack into a MCV - Construction Yards will re-pack into a MCV
- Transports will unload their passengers - Transports will unload their passengers
@@ -279,21 +291,24 @@ button-command-bar-deploy =
button-command-bar-scatter = button-command-bar-scatter =
.tooltip = Scatter .tooltip = Scatter
.tooltipdesc = Selected units will stop their current activity .tooltipdesc =
Selected units will stop their current activity
and move to a nearby location. and move to a nearby location.
Acts immediately on selected units. Acts immediately on selected units.
button-command-bar-stop = button-command-bar-stop =
.tooltip = Stop .tooltip = Stop
.tooltipdesc = Selected units will stop their current activity. .tooltipdesc =
Selected units will stop their current activity.
Selected buildings will reset their rally point. Selected buildings will reset their rally point.
Acts immediately on selected targets. Acts immediately on selected targets.
button-command-bar-queue-orders = button-command-bar-queue-orders =
.tooltip = Waypoint Mode .tooltip = Waypoint Mode
.tooltipdesc = Use Waypoint Mode to give multiple linking commands .tooltipdesc =
Use Waypoint Mode to give multiple linking commands
to the selected units. Units will execute the commands to the selected units. Units will execute the commands
immediately upon receiving them. immediately upon receiving them.
@@ -302,25 +317,29 @@ button-command-bar-queue-orders =
button-stance-bar-attackanything = button-stance-bar-attackanything =
.tooltip = Attack Anything Stance .tooltip = Attack Anything Stance
.tooltipdesc = Set the selected units to Attack Anything stance: .tooltipdesc =
Set the selected units to Attack Anything stance:
- Units will attack enemy units and structures on sight - Units will attack enemy units and structures on sight
- Units will pursue attackers across the battlefield - Units will pursue attackers across the battlefield
button-stance-bar-defend = button-stance-bar-defend =
.tooltip = Defend Stance .tooltip = Defend Stance
.tooltipdesc = Set the selected units to Defend stance: .tooltipdesc =
Set the selected units to Defend stance:
- Units will attack enemy units on sight - Units will attack enemy units on sight
- Units will not move or pursue enemies - Units will not move or pursue enemies
button-stance-bar-returnfire = button-stance-bar-returnfire =
.tooltip = Return Fire Stance .tooltip = Return Fire Stance
.tooltipdesc = Set the selected units to Return Fire stance: .tooltipdesc =
Set the selected units to Return Fire stance:
- Units will retaliate against enemies that attack them - Units will retaliate against enemies that attack them
- Units will not move or pursue enemies - Units will not move or pursue enemies
button-stance-bar-holdfire = button-stance-bar-holdfire =
.tooltip = Hold Fire Stance .tooltip = Hold Fire Stance
.tooltipdesc = Set the selected units to Hold Fire stance: .tooltipdesc =
Set the selected units to Hold Fire stance:
- Units will not fire upon enemies - Units will not fire upon enemies
- Units will not move or pursue enemies - Units will not move or pursue enemies
@@ -340,15 +359,15 @@ productionpalette-player-widgets-production-palette =
.hold = On Hold .hold = On Hold
## lobby-kickdialogs.yaml ## lobby-kickdialogs.yaml
label-kick-client-dialog-texta = You may also apply a temporary ban, preventing label-kick-client-dialog-text-a = 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-b = them from joining for the remainder of this game.
checkbox-kick-client-dialog-prevent-rejoining = Temporarily Ban checkbox-kick-client-dialog-prevent-rejoining = Temporarily Ban
button-kick-client-dialog = Kick button-kick-client-dialog = Kick
label-kick-spectators-dialog-title = Kick Spectators label-kick-spectators-dialog-title = Kick Spectators
button-kick-spectators-dialog-ok = Ok button-kick-spectators-dialog-ok = Ok
label-force-start-dialog-title = Start Game? label-force-start-dialog-title = Start Game?
label-force-start-dialog-texta = One or more players are not yet ready. label-force-start-dialog-text-a = 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-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-a = One or more clients are missing the selected
label-kick-warning-b = map, and will be kicked from the server. label-kick-warning-b = map, and will be kicked from the server.
button-force-start-dialog-start = Start button-force-start-dialog-start = Start
@@ -512,6 +531,12 @@ button-missionbrowser-panel-stop-briefing-video = Stop Briefing
button-missionbrowser-panel-start-info-video = Watch Info Video button-missionbrowser-panel-start-info-video = Watch Info Video
button-missionbrowser-panel-stop-info-video = Stop Info Video button-missionbrowser-panel-stop-info-video = Stop Info Video
button-missionbrowser-panel-play = Play 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 ## multiplayer-browser.yaml
image-bg-password-protected-tooltip = Requires Password image-bg-password-protected-tooltip = Requires Password

View File

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

View File

@@ -35,7 +35,7 @@ resource-tiberium = Tiberium
faction-random = faction-random =
.name = Any .name = Any
.description = Random Faction .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 = faction-gdi =
.name = GDI .name = GDI
@@ -61,25 +61,34 @@ notification-structure-lost = Structure lost.
## aircraft.yaml ## aircraft.yaml
actor-tran = actor-tran =
.name = Chinook Transport .name = Chinook Transport
.description = Fast Infantry Transport Helicopter. .description =
Unarmed. Fast Infantry Transport Helicopter.
.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. 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! Like all aircraft, it can be handy in a pinch to crush a commando by landing!
actor-heli = actor-heli =
.name = Apache Longbow .name = Apache Longbow
.description = Helicopter Gunship with chainguns. .description =
Strong vs. Infantry, Light Vehicles and Aircraft. Helicopter Gunship with chainguns.
Weak vs. Tanks. Strong vs Infantry, Light Vehicles and Aircraft
.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. 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. Ammo is automatically reloaded in the air.
actor-orca = actor-orca =
.name = Orca .name = Orca
.description = Helicopter gunship with AG missiles. .description =
Strong vs. Buildings and Tanks. Helicopter gunship with AG missiles.
Weak vs. Infantry. Strong vs Buildings and Tanks
.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. 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. Ammo is automatically reloaded in the air.
actor-c17-name = Supply Aircraft actor-c17-name = Supply Aircraft
@@ -220,80 +229,112 @@ actor-truck-husk-name = Supply Truck (Destroyed)
## infantry.yaml ## infantry.yaml
actor-e1 = actor-e1 =
.name = Minigunner .name = Minigunner
.description = General-purpose infantry. .description =
Strong vs. Infantry. General-purpose infantry.
Weak vs. Vehicles. Strong vs Infantry
.encyclopedia = The humble Minigunner is a highly specialized infantry unit designed to soak damage and kill other 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. Like most TD units, they can see farther than they can shoot, and work well in packs to eliminate enemy infantry.
actor-e2 = actor-e2 =
.name = Grenadier .name = Grenadier
.description = Fast infantry with grenades. .description =
Strong vs. Buildings, slow-moving targets. Fast infantry with grenades.
.encyclopedia = Fast and able to keep up with medium tanks, letting it work well with flanking armies. 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. While not as powerful as the Minigunner or a Rocket soldier, its speed can be used to your advantage.
actor-e3 = actor-e3 =
.name = Rocket Soldier .name = Rocket Soldier
.description = Anti-tank/Anti-aircraft infantry. .description =
Strong vs. Tanks and Aircraft. Anti-tank/Anti-aircraft infantry.
Weak vs. Infantry. Strong vs Tanks and Aircraft
.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. 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. 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 = actor-e4 =
.name = Flamethrower .name = Flamethrower
.description = Advanced anti-infantry unit. .description =
Strong vs. Infantry and Buildings. Advanced anti-infantry unit.
Weak vs. Tanks. Strong vs Infantry and Buildings
.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. 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 = actor-e5 =
.name = Chemical Warrior .name = Chemical Warrior
.description = Advanced general-purpose infantry. .description =
Strong vs. all Ground units. Advanced general-purpose infantry.
.encyclopedia = Similar stats to the Flamethrower but deals good damage vs. heavy armor. Great for stopping enemy armor from crushing your forces. 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. It can walk on Tiberium without taking damage, making it particularly useful for harvester harassment on certain maps.
actor-e6 = actor-e6 =
.name = Engineer .name = Engineer
.description = Damages and captures enemy structures. .description =
Damages and captures enemy structures.
Repairs destroyed vehicles. Repairs destroyed vehicles.
Unarmed. 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. .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. 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. Engineers can also be used to repair friendly structures or restore husks from destroyed vehicles.
actor-rmbo = actor-rmbo =
.name = Commando .name = Commando
.description = Elite sniper infantry unit. .description =
Strong vs Infantry and Buildings. Elite sniper infantry unit.
Weak vs Vehicles. Strong vs Infantry and Buildings
.encyclopedia = Equipped with a long-range sniper rifle, the Commando fires slowly but can eliminate enemy infantry from a distance when well-supported. 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. It carries C4, for surprise backline structure destruction.
actor-pvice = actor-pvice =
.description = Mutated abomination that spits liquid Tiberium. .description =
Strong vs Infantry and Buildings. Mutated abomination that spits liquid Tiberium.
Weak vs Aircraft. Strong vs Infantry and Buildings
.encyclopedia = A mutated lifeform created from the strange properties of Tiberium, when infantry units are expose to it. 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. They regenerate health quickly and have a short-range Tiberium weapon.
actor-steg = actor-steg =
.name = Stegosaurus .name = Stegosaurus
.description = A large, heavily built, herbivorous quadruped. .description =
A large, heavily built,
herbivorous quadruped.
actor-trex = actor-trex =
.name = Tyrannosaurus rex .name = Tyrannosaurus rex
.description = Bipedal carnivore with a massive skull. .description =
Bipedal carnivore with
a massive skull.
actor-tric = actor-tric =
.name = Triceratops .name = Triceratops
.description = Quadruped with large bony frill and three horns. .description =
Quadruped with large bony
frill and three horns.
actor-rapt = actor-rapt =
.name = Velociraptor .name = Velociraptor
.description = Bipedal with enlarged sickle-shaped claw on each hindfoot. .description =
Bipedal with enlarged sickle-shaped
claw on each hindfoot.
## misc.yaml ## misc.yaml
actor-wcrate-name = Wooden Crate actor-wcrate-name = Wooden Crate
@@ -328,7 +369,9 @@ notification-nuclear-warhead-approaching = Nuclear warhead approaching.
actor-fact = actor-fact =
.name = Construction Yard .name = Construction Yard
.description = Builds structures. .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. .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. 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-gdi-name = GDI Construction Yard
@@ -337,143 +380,184 @@ actor-fact-nod-name = Nod Construction Yard
actor-nuke = actor-nuke =
.name = Power Plant .name = Power Plant
.description = Generates power. .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. .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 = actor-nuk2 =
.name = Advanced Power Plant .name = Advanced Power Plant
.description = Provides more power, cheaper than the .description =
Provides more power, cheaper than the
standard Power Plant. 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 = actor-proc =
.name = Tiberium Refinery .name = Tiberium Refinery
.description = Processes raw Tiberium .description =
Processes raw Tiberium
into usable resources. 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. .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. The refinery stores Tiberium and immediately deploys a harvester once constructed. 3 harvesters is the maximum it can handle for close refining.
actor-silo = actor-silo =
.name = Tiberium Silo .name = Tiberium Silo
.description = Stores processed Tiberium. .description = Stores processed Tiberium.
.encyclopedia = Stores credits. If storage is full, harvesters will wait until credits are spent. .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. Useful when harvesting the more valuable blue Tiberium, as a single refinery cannot store a full load.
actor-pyle = actor-pyle =
.name = Barracks .name = Barracks
.description = Trains infantry .description = Trains infantry.
.encyclopedia = Produces infantry for GDI. Once unlocked, advanced GDI infantry are hard-hitting and fast. .encyclopedia =
Produces infantry for GDI. Once unlocked, advanced GDI infantry are hard-hitting and fast.
actor-hand = actor-hand =
.name = Hand of Nod .name = Hand of Nod
.description = Trains infantry .description = Trains infantry.
.encyclopedia = Produces infantry for Nod. Once unlocked, advanced Nod infantry are slow but have high health pools. .encyclopedia =
Produces infantry for Nod. Once unlocked, advanced Nod infantry are slow but have high health pools.
actor-afld = actor-afld =
.name = Airstrip .name = Airstrip
.description = Provides a dropzone .description =
Provides a dropzone
for vehicle reinforcements. 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. .encyclopedia =
Delivers vehicles by plane for Nod. Nod vehicles are fast but fragile. Produce harvesters at the start to jumpstart your economy.
actor-weap = actor-weap =
.name = Weapons Factory .name = Weapons Factory
.description = Produces vehicles. .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. .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 = actor-hpad =
.name = Helipad .name = Helipad
.description = Produces and repairs helicopters. .description =
.encyclopedia = Produces and repairs helicopters. Requires a Communications Center to build advanced attack helicopters. Produces and repairs helicopters.
.encyclopedia =
Produces and repairs helicopters. Requires a Communications Center to build advanced attack helicopters.
actor-hq = actor-hq =
.name = Communications Center .name = Communications Center
.description = Provides radar and Air Strike support power .description =
Provides radar and Air Strike support power.
Unlocks higher-tech units and buildings. Unlocks higher-tech units and buildings.
Requires power to operate. Requires power to operate.
.airstrikepower-name = Air Strike .airstrikepower-name = Air Strike
.airstrikepower-description = Deploy an aerial napalm strike. .airstrikepower-description = Deploy an aerial napalm strike.
Burns buildings and infantry along a line. 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 = actor-fix =
.name = Repair Facility .name = Repair Facility
.description = Repairs vehicles. .description = Repairs vehicles.
.encyclopedia = Repairs vehicles. The repair command can be used on units to send them to the nearest repair facility. .encyclopedia =
Repairs vehicles. The repair command can be used on units to send them to the nearest repair facility.
actor-eye = actor-eye =
.name = Advanced Communications Center .name = Advanced Communications Center
.description = Provides radar and Orbital Ion Cannon support power. .description =
Provides radar and Orbital Ion Cannon support power.
Unlocks Mammoth Tank and Commando. Unlocks Mammoth Tank and Commando.
Requires power to operate. Requires power to operate.
.ioncannonpower-name = Ion Cannon .ioncannonpower-name = Ion Cannon
.ioncannonpower-description = Initiates an Ion Cannon strike. .ioncannonpower-description = Initiates an Ion Cannon strike.
Applies instant damage to a small area. 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 = actor-tmpl =
.name = Temple of Nod .name = Temple of Nod
.description = Provides Nuclear Strike support power. .description =
Provides Nuclear Strike support power.
Unlocks Stealth Tank, Chem. Warrior and Obelisk of Light. Unlocks Stealth Tank, Chem. Warrior and Obelisk of Light.
Requires power to operate. Requires power to operate.
.nukepower-name = Nuclear Strike .nukepower-name = Nuclear Strike
.nukepower-description = Launches a tactical nuclear warhead. .nukepower-description = Launches a tactical nuclear warhead.
Applies heavy damage over a large area. 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 = actor-gun =
.name = Turret .name = Turret
.description = Basic Anti-Tank base defense. .description =
Strong vs Tanks and Vehicles. Basic Anti-Tank base defense.
Weak vs Infantry. Strong vs Tanks and Vehicles
.encyclopedia = Base defense armed with an armor-piercing cannon, it deals significant damage to vehicles in range. Weak vs Infantry
.encyclopedia =
Base defense armed with an armor-piercing cannon, it deals significant damage to vehicles in range.
actor-sam = actor-sam =
.name = SAM Site .name = SAM Site
.description = Anti-Aircraft base defense. .description =
Strong vs Aircraft. Anti-Aircraft base defense.
Cannot target Ground units. Strong vs Aircraft
.encyclopedia = Nod anti-air base defense. It remains protected when closed and opens to engage aircraft. Cannot target Ground units
.encyclopedia =
Nod anti-air base defense. It remains protected when closed and opens to engage aircraft.
actor-obli = actor-obli =
.name = Obelisk of Light .name = Obelisk of Light
.description = Advanced base defense. .description =
Advanced base defense.
Requires power to operate. Requires power to operate.
Strong vs. all Ground units. Strong vs all Ground units
Cannot target Aircraft. Cannot target Aircraft
.encyclopedia = Advanced defense for Nod that quickly destroys ground targets with a powerful laser. .encyclopedia =
Advanced defense for Nod that quickly destroys ground targets with a powerful laser.
actor-gtwr = actor-gtwr =
.name = Guard Tower .name = Guard Tower
.description = Basic defensive structure. .description =
Strong vs. Infantry. Basic defensive structure.
Weak vs. Tanks. Strong vs Infantry
.encyclopedia = Base defense armed with a high-velocity machine gun, it shreds infantry and light vehicles within range with its large area of effect. 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 = actor-atwr =
.name = Advanced Guard Tower .name = Advanced Guard Tower
.description = All-purpose defensive structure. .description =
Strong vs. Aircraft and Infantry. All-purpose defensive structure.
Weak vs. Tanks. Strong vs Aircraft and Infantry
.encyclopedia = Advanced defense for GDI that fires volleys of high explosive missiles at both ground and air targets. Effective versus everything. 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 = actor-sbag =
.name = Sandbag Barrier .name = Sandbag Barrier
.description = Stops infantry and light vehicles. .description =
Stops infantry and light vehicles.
Can be crushed by tanks. Can be crushed by tanks.
.encyclopedia = Blocks movement of infantry and light vehicles, but can be crushed by tanks. Immune to small arms fire. .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. This barrier can be built in multiple segments once an initial sandbag has been placed.
actor-cycl = actor-cycl =
.name = Chain Link Barrier .name = Chain Link Barrier
.description = Stops infantry and light vehicles. .description =
Stops infantry and light vehicles.
Can be crushed by tanks. Can be crushed by tanks.
.encyclopedia = Blocks infantry movement and light vehicles, but can be crushed by tanks. Immune to small arms fire. .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. This barrier can be built in multiple segments once an initial chain link has been placed.
actor-brik = actor-brik =
.name = Concrete Barrier .name = Concrete Barrier
.description = Stops infantry and most tanks. .description =
Stops infantry and most tanks.
Blocks some projectiles. Blocks some projectiles.
.encyclopedia = Blocks all unit movement, except for Mammoth Tanks which can crush it. Immune to small arms fire. .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. This barrier can be built in multiple segments once an initial wall has been placed.
actor-barracks-name = Infantry Production actor-barracks-name = Infantry Production
@@ -490,25 +574,29 @@ actor-hosp =
.name = Hospital .name = Hospital
.captured-desc = Provides infantry with self-healing. .captured-desc = Provides infantry with self-healing.
.capturable-desc = Capture to enable self-healing for infantry. .capturable-desc = Capture to enable self-healing for infantry.
.encyclopedia = Heals friendly infantry over time when captured. .encyclopedia =
Heals friendly infantry over time when captured.
actor-miss = actor-miss =
.name = Tech Center .name = Tech Center
.captured-desc = Provides range of vision. .captured-desc = Provides range of vision.
.capturable-desc = Capture to give visual range. .capturable-desc = Capture to give visual range.
.encyclopedia = Provides a wide area of vision when captured. .encyclopedia =
Provides a wide area of vision when captured.
actor-bio = actor-bio =
.name = Biological Lab .name = Biological Lab
.captured-desc = Provides infantry with Tiberium immunity. .captured-desc = Provides infantry with Tiberium immunity.
.capturable-desc = Capture to enable Tiberium immunity for infantry. .capturable-desc = Capture to enable Tiberium immunity for infantry.
.encyclopedia = Grants friendly infantry immunity to Tiberium when captured. .encyclopedia =
Grants friendly infantry immunity to Tiberium when captured.
actor-v19 = actor-v19 =
.name = Oil Derrick .name = Oil Derrick
.captured-desc = Provides additional funds. .captured-desc = Provides additional funds.
.capturable-desc = Capture to receive 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. .encyclopedia =
Produces a small amount of income frequently when captured. Makes less money than building a harvester, but provides consistent income.
## trees.yaml ## trees.yaml
actor-splitblue-name = Blue Blossom Tree actor-splitblue-name = Blue Blossom Tree
@@ -518,124 +606,166 @@ actor-t13-transformable-name = (Tree that can transform into a Blossom Tree)
## vehicles.yaml ## vehicles.yaml
actor-mcv = actor-mcv =
.name = Mobile Construction Vehicle .name = Mobile Construction Vehicle
.description = Deploys into a Construction Yard. .description =
Unarmed. Deploys into a Construction Yard.
.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. 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. 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 = actor-harv =
.name = Harvester .name = Harvester
.generic-name = Harvester .generic-name = Harvester
.description = Collects Tiberium for processing. .description =
Unarmed. Collects Tiberium for processing.
.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. 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. notification-harvester-lost = Harvester lost.
actor-apc = actor-apc =
.name = APC .name = APC
.description = Armed infantry transport. .description =
Armed infantry transport.
Can attack Aircraft. Can attack Aircraft.
Strong vs. Vehicles. Strong vs Vehicles
Weak vs. Infantry. 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. .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. 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 = actor-arty =
.name = Artillery .name = Artillery
.description = Long-range artillery. .description =
Strong vs. Infantry, Vehicles and Buildings. Long-range artillery.
.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. 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. Highly vulnerable when clumped due to its explosion on death.
actor-ftnk = actor-ftnk =
.name = Flame Tank .name = Flame Tank
.description = Heavily armored flame-throwing vehicle. .description =
Strong vs. Infantry, Buildings and Vehicles. Heavily armored flame-throwing vehicle.
Weak vs. Tanks. Strong vs Infantry, Buildings and Vehicles
.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. 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 = actor-bggy =
.name = Nod Buggy .name = Nod Buggy
.description = Fast scout and anti-infantry vehicle. .description =
Strong vs. Infantry. Fast scout and anti-infantry vehicle.
Weak vs. Tanks. Strong vs Infantry
.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. 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 = actor-bike =
.name = Recon Bike .name = Recon Bike
.description = Fast scout vehicle with rockets. .description =
Fast scout vehicle with rockets.
Can attack Aircraft. Can attack Aircraft.
Strong vs. Vehicles and Tanks. Strong vs Vehicles and Tanks
Weak vs. Infantry. 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. .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. Be wary that its rockets are slow and have a hard time hitting fast-moving targets.
actor-jeep = actor-jeep =
.name = Hum-vee .name = Hum-vee
.description = Fast scout and anti-infantry vehicle. .description =
Strong vs. Infantry. Fast scout and anti-infantry vehicle.
Weak vs. Tanks. Strong vs Infantry
.encyclopedia = Strong versus light vehicles and infantry, Hum-vees are great at scouting and eliminating isolated 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. 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 = actor-ltnk =
.name = Light Tank .name = Light Tank
.description = Fast, light tank. .description =
Strong vs. Vehicles and Tanks. Fast, light tank.
Weak vs. Infantry. Strong vs Vehicles and Tanks
.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. 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. It should be combined with bikes, stealth tanks, or infantry when facing medium tanks.
actor-mtnk = actor-mtnk =
.name = Medium Tank .name = Medium Tank
.description = General-Purpose GDI Tank. .description =
Strong vs. Tanks and Vehicles. General-purpose GDI Tank.
Weak vs. Infantry. Strong vs Tanks and Vehicles
.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. 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. Mix minigunners with your medium tank armies to deal with enemy infantry.
actor-htnk = actor-htnk =
.name = Mammoth Tank .name = Mammoth Tank
.description = Heavily armored GDI Tank. .description =
Heavily armored GDI Tank.
Can attack Aircraft. Can attack Aircraft.
Strong vs. Everything. 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. .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. When in an enemy base, put it on aggressive stance to have it carve a path of destruction as it moves.
actor-msam = actor-msam =
.name = Rocket Launcher .name = Rocket Launcher
.description = Long-range rocket artillery. .description =
Strong vs. all ground units. Long-range rocket artillery.
.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. 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. 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 = actor-mlrs =
.name = Mobile SAM. .name = Mobile SAM
.description = Powerful anti-air unit. .description =
Powerful anti-air unit.
Cannot attack ground units. 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. .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 = actor-stnk =
.name = Stealth Tank .name = Stealth Tank
.description = Long-range missile tank that can cloak. .description =
Long-range missile tank that can cloak.
Can attack Aircraft. Can attack Aircraft.
Has weak armor. Can be spotted by infantry and defense structures. Has weak armor. Can be spotted by infantry and
Strong vs. Vehicles and Tanks. defense structures.
Weak vs. Infantry. Strong vs Vehicles and Tanks
.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. 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. Their long-range AP missiles are excellent for dealing with enemy armor or air. They can also crush infantry in a pinch.
actor-mhq = actor-mhq =
.name = Mobile HQ .name = Mobile HQ
.description = Mobile base of operations. .description =
Mobile base of operations.
actor-truck = actor-truck =
.name = Supply Truck .name = Supply Truck
.description = Transports cash to other players. .description =
Builds quickly. Transports cash to other players.
Unarmed. Builds quickly
.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. 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 ## ai.yaml
bot-cabal = bot-cabal =

View File

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

View File

@@ -1,6 +1,6 @@
Metadata: Metadata:
Title: mod-title Title: mod-title
Version: {DEV_VERSION} Version: release-20250330
Website: https://www.openra.net Website: https://www.openra.net
WebIcon32: https://www.openra.net/images/icons/cnc_32x32.png WebIcon32: https://www.openra.net/images/icons/cnc_32x32.png
WindowTitle: mod-windowtitle WindowTitle: mod-windowtitle
@@ -8,13 +8,13 @@ Metadata:
PackageFormats: Mix PackageFormats: Mix
FileSystem: ContentInstallerFileSystem FileSystem: ContentInstallerFileSystem
Packages: SystemPackages:
^EngineDir ^EngineDir
$cnc: cnc $cnc: cnc
^EngineDir|mods/common: common ^EngineDir|mods/common: common
~^SupportDir|Content/cnc: content ~^SupportDir|Content/cnc: content
cnc|scripts
common|scripts common|scripts
cnc|scripts
cnc|uibits cnc|uibits
ContentPackages: ContentPackages:
content|speech.mix content|speech.mix
@@ -30,6 +30,7 @@ FileSystem: ContentInstallerFileSystem
~content|scores-covertops.mix ~content|scores-covertops.mix
~content|transit.mix ~content|transit.mix
~content|general.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/snow.mix
cnc|bits cnc|bits
cnc|bits/jungle cnc|bits/jungle
@@ -39,7 +40,7 @@ FileSystem: ContentInstallerFileSystem
MapFolders: MapFolders:
cnc|maps: System cnc|maps: System
~^SupportDir|maps/cnc/{DEV_VERSION}: User ~^SupportDir|maps/cnc/release-20250330: User
Rules: Rules:
cnc|rules/misc.yaml cnc|rules/misc.yaml

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -76,6 +76,44 @@ Background@MISSIONBROWSER_PANEL:
Height: 31 Height: 31
Font: Bold Font: Bold
Text: button-missionbrowser-panel-mission-options 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: Container@MISSION_DETAIL:
Y: 212 Y: 212
Width: PARENT_WIDTH Width: PARENT_WIDTH

View File

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

View File

@@ -52,7 +52,6 @@ label-save-map-panel-directory = Directory:
label-save-map-panel-filename = Filename: label-save-map-panel-filename = Filename:
button-save-map-panel = Save button-save-map-panel = Save
label-actor-edit-panel-id = ID label-actor-edit-panel-id = ID
button-container-delete = Delete
button-container-ok = OK button-container-ok = OK
label-tiles-bg-search = Search: label-tiles-bg-search = Search:
label-bg-filter = Filter: label-bg-filter = Filter:
@@ -61,12 +60,12 @@ label-actor-bg-owner = Owner:
label-area-selection = Area Selection label-area-selection = Area Selection
label-area-info = Area Info label-area-info = Area Info
label-selected-area-diagonal = Diagonal: label-selected-area-diagonal = Diagonal:
label-selected-area-resources= Resources: label-selected-area-resources = Resources:
label-copy-filters = Copy Filters label-selection-filters = Filters
label-filter-terrain = Terrain label-filter-terrain = Terrain
label-filter-resources = Resources label-filter-resources = Resources
label-filter-actors = Actors 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-current = Clear Current
button-marker-tiles-clear-all = Clear All button-marker-tiles-clear-all = Clear All
label-marker-layer-num-sides = Number of Sides label-marker-layer-num-sides = Number of Sides
@@ -81,6 +80,14 @@ button-map-editor-tab-container-actors-tooltip = Actors
button-map-editor-tab-container-tools-tooltip = Tools button-map-editor-tab-container-tools-tooltip = Tools
button-map-editor-tab-container-history-tooltip = History 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 = button-editor-world-root-options =
.label = Menu .label = Menu
.tooltip = Menu .tooltip = Menu
@@ -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. label-script-error-panel-desc-c = Please send this file to the map author so that they can fix this issue.
## ingame-infostats.yaml ## ingame-infostats.yaml
label-objective-mission = Mission: label-mission-objective = Mission:
checkbox-objective-stats = Destroy all opposition! checkbox-stats-objective = Destroy all opposition!
label-stats-player = Player label-stats-player = Player
label-stats-faction = Faction label-stats-faction = Faction
label-stats-score = Score label-stats-score = Score
@@ -167,15 +174,15 @@ label-stats-actions = Actions
label-menu-buttons-title = Options label-menu-buttons-title = Options
## lobby-kickdialogs.yaml ## lobby-kickdialogs.yaml
label-kick-client-dialog-texta = You may also apply a temporary ban, preventing label-kick-client-dialog-text-a = 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-b = them from joining for the remainder of this game.
checkbox-kick-client-dialog-prevent-rejoining = Temporarily Ban checkbox-kick-client-dialog-prevent-rejoining = Temporarily Ban
button-kick-client-dialog = Kick button-kick-client-dialog = Kick
label-kick-spectators-dialog-title = Kick Spectators label-kick-spectators-dialog-title = Kick Spectators
button-kick-spectators-dialog-ok = Ok button-kick-spectators-dialog-ok = Ok
label-force-start-dialog-title = Start Game? label-force-start-dialog-title = Start Game?
label-force-start-dialog-texta = One or more players are not yet ready. label-force-start-dialog-text-a = 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-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-a = One or more clients are missing the selected
label-kick-warning-b = map, and will be kicked from the server. label-kick-warning-b = map, and will be kicked from the server.
button-force-start-dialog-start = Start button-force-start-dialog-start = Start
@@ -343,6 +350,12 @@ button-missionbrowser-panel-stop-info-video = Stop Info Video
button-missionbrowser-panel-play = Play button-missionbrowser-panel-play = Play
button-missionbrowser-panel-mission-info = Mission Info button-missionbrowser-panel-mission-info = Mission Info
button-missionbrowser-panel-mission-options = Options 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 ## multiplayer-browser.yaml
image-multiplayer-panel-password-protected-tooltip = Requires Password image-multiplayer-panel-password-protected-tooltip = Requires Password

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -144,6 +144,45 @@ Background@MISSIONBROWSER_PANEL:
Visible: False Visible: False
PanelRoot: MISSION_DROPDOWN_PANEL_ROOT PanelRoot: MISSION_DROPDOWN_PANEL_ROOT
TooltipContainer: TOOLTIP_CONTAINER 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: Button@START_BRIEFING_VIDEO_BUTTON:
X: 20 X: 20
Y: PARENT_HEIGHT - 45 Y: PARENT_HEIGHT - 45

View File

@@ -1,7 +1,7 @@
## world ## world
dropdown-difficulty = dropdown-difficulty =
.label = Difficulty .label = Difficulty
.description = The difficulty of the mission. .description = The difficulty of the mission
options-difficulty = options-difficulty =
.easy = Easy .easy = Easy

View File

@@ -68,7 +68,8 @@ supportpowers-support-powers-palette =
button-command-bar-attack-move = button-command-bar-attack-move =
.tooltip = Attack Move .tooltip = Attack Move
.tooltipdesc = Selected units will move to the desired location .tooltipdesc =
Selected units will move to the desired location
and attack any enemies they encounter en route. and attack any enemies they encounter en route.
Hold <(Ctrl)> while targeting to order an Assault Move Hold <(Ctrl)> while targeting to order an Assault Move
@@ -78,7 +79,8 @@ button-command-bar-attack-move =
button-command-bar-force-move = button-command-bar-force-move =
.tooltip = Force Move .tooltip = Force Move
.tooltipdesc = Selected units will move to the desired location .tooltipdesc =
Selected units will move to the desired location
- Default activity for the target is suppressed - Default activity for the target is suppressed
- Vehicles will attempt to crush enemies at the target location - Vehicles will attempt to crush enemies at the target location
- Deployed thumpers will undeploy and move to the target location - Deployed thumpers will undeploy and move to the target location
@@ -88,7 +90,8 @@ button-command-bar-force-move =
button-command-bar-force-attack = button-command-bar-force-attack =
.tooltip = Force Attack .tooltip = Force Attack
.tooltipdesc = Selected units will attack the targeted unit or location .tooltipdesc =
Selected units will attack the targeted unit or location
- Default activity for the target is suppressed - Default activity for the target is suppressed
- Allows targeting of own or ally forces - Allows targeting of own or ally forces
@@ -97,13 +100,15 @@ button-command-bar-force-attack =
button-command-bar-guard = button-command-bar-guard =
.tooltip = Guard .tooltip = Guard
.tooltipdesc = Selected units will follow the targeted unit. .tooltipdesc =
Selected units will follow the targeted unit.
Left-click icon then right-click on target unit. Left-click icon then right-click on target unit.
button-command-bar-deploy = button-command-bar-deploy =
.tooltip = Deploy .tooltip = Deploy
.tooltipdesc = Selected units will perform their default deploy activity .tooltipdesc =
Selected units will perform their default deploy activity
- MCVs will unpack into a Construction Yard - MCVs will unpack into a Construction Yard
- Thumpers will start or stop attracting worms - Thumpers will start or stop attracting worms
- Devastators will become immobilized and explode - Devastators will become immobilized and explode
@@ -112,21 +117,24 @@ button-command-bar-deploy =
button-command-bar-scatter = button-command-bar-scatter =
.tooltip = Scatter .tooltip = Scatter
.tooltipdesc = Selected units will stop their current activity .tooltipdesc =
Selected units will stop their current activity
and move to a nearby location. and move to a nearby location.
Acts immediately on selected units. Acts immediately on selected units.
button-command-bar-stop = button-command-bar-stop =
.tooltip = Stop .tooltip = Stop
.tooltipdesc = Selected units will stop their current activity. .tooltipdesc =
Selected units will stop their current activity.
Selected buildings will reset their rally point. Selected buildings will reset their rally point.
Acts immediately on selected targets. Acts immediately on selected targets.
button-command-bar-queue-orders = button-command-bar-queue-orders =
.tooltip = Waypoint Mode .tooltip = Waypoint Mode
.tooltipdesc = Use Waypoint Mode to give multiple linking commands .tooltipdesc =
Use Waypoint Mode to give multiple linking commands
to the selected units. Units will execute the commands to the selected units. Units will execute the commands
immediately upon receiving them. immediately upon receiving them.
@@ -135,25 +143,29 @@ button-command-bar-queue-orders =
button-stance-bar-attackanything = button-stance-bar-attackanything =
.tooltip = Attack Anything Stance .tooltip = Attack Anything Stance
.tooltipdesc = Set the selected units to Attack Anything stance: .tooltipdesc =
Set the selected units to Attack Anything stance:
- Units will attack enemy units and structures on sight - Units will attack enemy units and structures on sight
- Units will pursue attackers across the battlefield - Units will pursue attackers across the battlefield
button-stance-bar-defend = button-stance-bar-defend =
.tooltip = Defend Stance .tooltip = Defend Stance
.tooltipdesc = Set the selected units to Defend stance: .tooltipdesc =
Set the selected units to Defend stance:
- Units will attack enemy units on sight - Units will attack enemy units on sight
- Units will not move or pursue enemies - Units will not move or pursue enemies
button-stance-bar-returnfire = button-stance-bar-returnfire =
.tooltip = Return Fire Stance .tooltip = Return Fire Stance
.tooltipdesc = Set the selected units to Return Fire stance: .tooltipdesc =
Set the selected units to Return Fire stance:
- Units will retaliate against enemies that attack them - Units will retaliate against enemies that attack them
- Units will not move or pursue enemies - Units will not move or pursue enemies
button-stance-bar-holdfire = button-stance-bar-holdfire =
.tooltip = Hold Fire Stance .tooltip = Hold Fire Stance
.tooltipdesc = Set the selected units to Hold Fire stance: .tooltipdesc =
Set the selected units to Hold Fire stance:
- Units will not fire upon enemies - Units will not fire upon enemies
- Units will not move or pursue enemies - Units will not move or pursue enemies

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 ## atreides-01ab, harkonnen-01ab, ordos01ab
build-concrete = Build a concrete foundation before placing structures. 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-refinery = Build a Refinery to collect Spice.
build-silo = Build a Silo to store additional 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. destroy-harkonnen = Destroy the Harkonnen.
## atreides-04 ## atreides-04
fremen-leader = Fremen Leader. fremen-leader = Fremen Leader
sietch-integrity = Sietch structural integrity: { $integrity }% sietch-integrity = Sietch structural integrity: { $integrity }%
protect-fremen-sietch = Protect the Fremen Sietch. protect-fremen-sietch = Protect the Fremen Sietch.
keep-sietch-intact = Keep the Sietch { $integrity }% intact! 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! mercenaries-annihilated = The Mercenaries have been annihilated!
ally-mercenaries = Persuade the Mercenaries to fight alongside House Harkonnen. 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. 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 atreides-commander = Atreides Commander
## harkonnen-09ab ## harkonnen-09ab

View File

@@ -35,8 +35,8 @@ resource-spice = Spice
faction-random = faction-random =
.name = Any .name = Any
.description = Random House. .description = Random House
A random house is chosen at the start of the game. A random house is chosen at the start of the game
faction-atreides = faction-atreides =
.name = Atreides .name = Atreides
@@ -65,7 +65,7 @@ faction-harkonnen =
wealth, and the destruction of House Atreides. wealth, and the destruction of House Atreides.
Faction Variations: 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: Special Units:
- Sardaukar - Sardaukar
@@ -82,8 +82,8 @@ faction-ordos =
and forbidden Ixian technologies to gain the upper hand. and forbidden Ixian technologies to gain the upper hand.
Faction Variations: Faction Variations:
- Trikes are replaced by Raider Trikes. - Trikes are replaced by Raider Trikes
- Combat Tanks are faster but less durable. - Combat Tanks are faster but less durable
Special Units: Special Units:
- Raider Trike - Raider Trike
@@ -112,18 +112,22 @@ notification-primary-building-selected = Primary building selected.
## aircraft.yaml ## aircraft.yaml
actor-carryall-reinforce = actor-carryall-reinforce =
.name = Carryall .name = Carryall
.description = Large winged, planet-bound ship .description =
Large winged, planet-bound ship
Automatically lifts harvesters to and from Spice fields. Automatically lifts harvesters to and from Spice fields.
Lifts vehicles to Repair Pads when ordered to. 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. 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-frigate-name = Frigate
actor-ornithopter = actor-ornithopter =
.name = 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. .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-ornithopter-husk-name = Ornithopter
actor-carryall-husk-name = Carryall actor-carryall-husk-name = Carryall
@@ -159,80 +163,99 @@ meta-combat-tank-husk-name = Combat Tank (Destroyed)
## infantry.yaml ## infantry.yaml
actor-light-inf = actor-light-inf =
.name = Light Infantry .name = Light Infantry
.description = General-purpose infantry. .description =
Strong vs. Infantry. General-purpose infantry.
Weak vs. Vehicles and Artillery. Strong vs Infantry
.encyclopedia = Lightly armored foot soldiers, equipped with 9mm RP assault rifles. They are effective against infantry and lightly armored vehicles. 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 = actor-engineer =
.name = Engineer .name = Engineer
.description = Infiltrates and captures enemy structures. .description =
Strong vs. Buildings. Infiltrates and captures enemy
Weak vs. Everything. structures.
Repairs damaged cliffs. Strong vs Buildings
.encyclopedia = Can be used to capture enemy 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 = actor-trooper =
.name = Trooper .name = Trooper
.description = Anti-tank infantry. .description =
Strong vs. Tanks. Anti-tank infantry.
Weak vs. Infantry and Artillery. Strong vs Tanks
.encyclopedia = Armed with wire-guided, armor-piercing missile warheads, Troopers are very effective against vehicles and buildings but struggle against infantry. 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. Troopers are resistant to anti-tank weaponry but very vulnerable to high-explosives, fire and bullet weapons.
actor-thumper = actor-thumper =
.name = Thumper Infantry .name = Thumper Infantry
.description = Attracts nearby worms when deployed. .description =
Unarmed. Attracts nearby worms when deployed.
.encyclopedia = Deploys a loud hammering device that draws Sandworms to the area. Unarmed
.encyclopedia =
Deploys a loud hammering device that draws Sandworms to the area.
actor-fremen = actor-fremen =
.name = Fremen .name = Fremen
.description = Elite infantry unit with assault rifles and rockets. .description =
Strong vs. Infantry and Vehicles. Elite infantry unit with assault rifles and rockets.
Weak vs. Artillery. Strong vs Infantry and Vehicles
Special Ability: Invisibility. Weak vs Artillery
.encyclopedia = The native desert warriors of Dune, armed with 10mm Assault Rifles and Rockets. Their firepower is equally effective against infantry and vehicles. 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. Fremen units are very vulnerable to high-explosive and bullet weapons.
actor-grenadier = actor-grenadier =
.name = Grenadier .name = Grenadier
.description = Infantry with grenades. .description =
Strong vs. Buildings and Infantry. Infantry with grenades.
Weak vs. Vehicles. Strong vs Buildings and Infantry
.encyclopedia = An infantry artillery unit strong against buildings. They have a chance of exploding when killed, so should not be grouped together. 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 = actor-sardaukar =
.name = Sardaukar .name = Sardaukar
.description = Elite Corrino assault infantry. .description =
Strong vs. Infantry and Vehicles. Elite Corrino assault infantry.
Weak vs. Artillery. Strong vs Infantry and Vehicles
.encyclopedia = Powerful heavy troopers equipped with a machine gun that is effective against infantry and a rocket launcher for targeting 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. actor-mpsardaukar-description =
Strong vs. Infantry and Vehicles. Elite Harkonnen assault infantry.
Weak vs. Artillery. Strong vs Infantry and Vehicles
Weak vs Artillery
actor-saboteur = actor-saboteur =
.name = Saboteur .name = Saboteur
.description = Sneaky infantry with explosives. .description =
Sneaky infantry with explosives.
Turns invisible for a limited time. Turns invisible for a limited time.
Strong vs. Buildings. Strong vs Buildings
Weak vs. Everything. Weak vs Everything
Special Ability: Destroys buildings. 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. .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. 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. actor-nsfremen-description =
Strong vs. Infantry and Vehicles. Elite infantry unit with assault rifles and rockets.
Weak vs. Artillery. Strong vs Infantry and Vehicles
Weak vs Artillery
## misc.yaml ## misc.yaml
actor-crate-name = Crate actor-crate-name = Crate
@@ -243,40 +266,53 @@ actor-wormspawner-name = (worm spawning location)
actor-upgrade-conyard = actor-upgrade-conyard =
.name = Construction Yard Upgrade .name = Construction Yard Upgrade
.description = Unlocks additional construction options: .description =
(Large Concrete Slab and Rocket Turret) Unlocks additional construction options:
- Large Concrete Slab
- Rocket Turret
actor-upgrade-barracks = actor-upgrade-barracks =
.name = Barracks Upgrade .name = Barracks Upgrade
.description = Unlocks additional infantry: .description =
(Trooper, Engineer and Thumper Infantry) Unlocks additional infantry:
- Trooper
- Engineer
- Thumper Infantry
Required to unlock faction-specific infantry: Required to unlock faction-specific infantry:
(Atreides: Grenadier, Harkonnen: Sardaukar) - Atreides: Grenadier
- Harkonnen: Sardaukar
actor-upgrade-light = actor-upgrade-light =
.name = Light Factory Upgrade .name = Light Factory Upgrade
.description = Unlocks additional light units: .description =
(Missile Quad) Unlocks additional light units:
- Missile Quad
Required to unlock a faction-specific light unit: Required to unlock a faction-specific light unit:
(Ordos: Stealth Raider Trike) - Ordos: Stealth Raider Trike
actor-upgrade-heavy = actor-upgrade-heavy =
.name = Heavy Factory Upgrade .name = Heavy Factory Upgrade
.description = Unlocks additional construction options: .description =
(Repair Pad and IX Research Center) Unlocks additional construction options:
- Repair Pad
- IX Research Center
Unlocks additional heavy units: Unlocks additional heavy units:
(Siege Tank, Missile Tank and MCV) - Siege Tank
- Missile Tank
- MCV
actor-upgrade-hightech = actor-upgrade-hightech =
.name = High Tech Factory Upgrade .name = High Tech Factory Upgrade
.description = Unlocks the Atreides Air Strike superweapon. .description =
Unlocks the Atreides Air Strike superweapon.
actor-deathhand = actor-deathhand =
.name = Death Hand .name = Death Hand
.encyclopedia = Armed with atomic cluster munitions, it detonates above its target, inflicting great damage over a wide area. .encyclopedia =
Armed with atomic cluster munitions, it detonates above its target, inflicting great damage over a wide area.
## structures.yaml ## structures.yaml
notification-construction-complete = Construction complete. notification-construction-complete = Construction complete.
@@ -295,12 +331,14 @@ notification-saboteur-ready = Saboteur ready.
meta-concrete = meta-concrete =
.generic-name = Structure .generic-name = Structure
.description = Provides a strong foundation that protects .description =
against terrain damage. Provides a strong foundation that
protects against terrain damage.
actor-concrete-a = actor-concrete-a =
.name = Concrete Slab .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. .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. Concrete is vulnerable to most weapons and cannot be repaired once damaged.
@@ -309,64 +347,78 @@ actor-concrete-b-name = Large Concrete Slab
actor-construction-yard = actor-construction-yard =
.name = Construction Yard .name = Construction Yard
.description = Produces structures. .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. .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. Construction yards are fairly strong, but vulnerable to all weapons to varying degrees.
actor-wind-trap = actor-wind-trap =
.name = Wind Trap .name = Wind Trap
.description = Supplies power to other structures. .description =
.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. 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. Wind Traps are vulnerable to most weapons.
actor-barracks = actor-barracks =
.name = Barracks .name = Barracks
.description = Trains infantry. .description = Trains infantry.
.encyclopedia = Required for producing and training light infantry units, it can be upgraded to train advanced infantry in later missions. .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. Barracks are vulnerable to most weapons.
actor-refinery = actor-refinery =
.name = Spice Refinery .name = Spice Refinery
.description = Harvesters unload Spice here for processing. .description =
.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. 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. Refineries are vulnerable to most weapons.
actor-silo = actor-silo =
.name = Silo .name = Silo
.description = Stores excess harvested Spice. .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. .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. The Spice Silo is vulnerable to most weapons.
actor-light-factory = actor-light-factory =
.name = Light Factory .name = Light Factory
.description = Produces light vehicles. .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. .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. A Light Factory is vulnerable to most weapons.
actor-heavy-factory = actor-heavy-factory =
.name = Heavy Factory .name = Heavy Factory
.description = Produces heavy vehicles. .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. .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. The Heavy Factory is vulnerable to most weapons.
actor-outpost = actor-outpost =
.name = Outpost .name = Outpost
.description = Provides a radar map of the battlefield. .description =
Provides a radar map of the battlefield.
Requires power to operate. Requires power to operate.
.encyclopedia = Once enough power is available, the Radar Outpost activates, providing a radar map. .encyclopedia =
Once enough power is available, the Radar Outpost activates, providing a radar map.
The Radar Outpost is vulnerable to most weapons. The Radar Outpost is vulnerable to most weapons.
actor-starport = actor-starport =
.name = Starport .name = Starport
.description = Dropzone for quick reinforcements, at a price. .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. .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. Even with heavy armor, the Starport is vulnerable to most weapons.
@@ -374,35 +426,41 @@ actor-wall =
.name = Concrete Wall .name = Concrete Wall
.generic-name = Structure .generic-name = Structure
.description = Stops units and blocks enemy fire. .description = Stops units and blocks enemy fire.
.encyclopedia = The most effective defensive barriers on Dune, blocking tank fire and impeding unit movement. .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 = actor-medium-gun-turret =
.name = Gun Turret .name = Gun Turret
.description = Defensive structure. .description =
Strong vs. Tanks. Defensive structure.
Weak vs. Infantry and Aircraft. Strong vs Tanks
.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. 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 = actor-large-gun-turret =
.name = Rocket Turret .name = Rocket Turret
.description = Defensive structure. .description =
Strong vs. Infantry and Aircraft. Defensive structure.
Weak vs. Tanks.
Requires power to operate. 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. 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. The Rocket Turret is resistant to firearms and explosive weapons, but vulnerable to missiles and high-caliber guns.
actor-repair-pad = actor-repair-pad =
.name = Repair Pad .name = Repair Pad
.description = Repairs vehicles. .description =
Repairs vehicles.
Allows MCVs to be built. Allows MCVs to be built.
.encyclopedia = Repairs units for a fraction of their production cost. .encyclopedia =
Repairs units for a fraction of their production cost.
The Repair Pad is vulnerable to most weapons. The Repair Pad is vulnerable to most weapons.
@@ -411,96 +469,113 @@ actor-high-tech-factory =
.description = Unlocks advanced technology. .description = Unlocks advanced technology.
.airstrikepower-name = Air Strike .airstrikepower-name = Air Strike
.airstrikepower-description = Ornithopters bomb the target. .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. .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. The High Tech Factory is vulnerable to most weapons.
actor-research-centre = actor-research-centre =
.name = IX Research Center .name = IX Research Center
.description = Unlocks advanced tanks. .description = Unlocks advanced tanks.
.encyclopedia = Provides technology upgrades for both structures and vehicles. This facility is required to develop advanced special weapons and prototypes. .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. The IX Research Center is vulnerable to most weapons.
actor-palace = actor-palace =
.name = Palace .name = Palace
.description = Unlocks elite infantry and weapons. .description = Unlocks elite infantry and weapons.
.encyclopedia = Serves as the command center once built, offering additional options and special 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. Even with heavy armor, the Palace is vulnerable to most weapons.
.nukepower-name = Death Hand .nukepower-name = Death Hand
.nukepower-description = Launches an atomic missile at a target location. .nukepower-description = Launches an atomic missile at a target location.
.produceactorpower-fremen-name = Recruit Fremen .produceactorpower-fremen-name = Recruit Fremen
.produceactorpower-fremen-description = Elite infantry unit with assault rifles and rockets. .produceactorpower-fremen-description = Elite infantry unit with assault rifles and rockets.
Strong vs. Infantry and Vehicles. Strong vs Infantry and Vehicles
Weak vs. Artillery. Weak vs Artillery
Special Ability: Invisibility. Special Ability: Invisibility
.produceactorpower-saboteur-name = Recruit Saboteur .produceactorpower-saboteur-name = Recruit Saboteur
.produceactorpower-saboteur-description = Sneaky infantry with explosives. .produceactorpower-saboteur-description = Sneaky infantry with explosives.
Can be deployed to become invisible for a limited time. Can be deployed to become invisible for a limited time.
Strong vs Buildings. Strong vs Buildings
Weak vs Everything. Weak vs Everything
Special Ability: Destroys buildings. Special Ability: Destroys buildings
## vehicles.yaml ## vehicles.yaml
actor-mcv = actor-mcv =
.name = Mobile Construction Vehicle .name = Mobile Construction Vehicle
.description = Deploys into a Construction Yard. .description =
Unarmed. Deploys into a Construction Yard.
.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. 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. MCVs are resistant to bullets and light-explosives. They are vulnerable to missiles and high-caliber guns.
actor-harvester = actor-harvester =
.name = Spice Harvester .name = Spice Harvester
.description = Collects Spice for processing. .description =
Unarmed. Collects Spice for processing.
.encyclopedia = Resistant to bullets, and to some degree, high explosives. They are vulnerable to missiles and high-caliber guns. 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. A Harvester is included with a Refinery.
actor-trike = actor-trike =
.name = Trike .name = Trike
.description = Fast scout. .description =
Strong vs. Infantry. Fast scout.
Weak vs. Tanks. Strong vs Infantry
.encyclopedia = Lightly armored, three-wheeled vehicles armed with heavy machine guns, effective against infantry and lightly armored vehicles. 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. Trikes are vulnerable to most weapons, high-caliber guns are slightly less effective against them.
actor-quad = actor-quad =
.name = Missile Quad .name = Missile Quad
.description = Missile Scout. .description =
Strong vs. Vehicles. Missile Scout.
Weak vs. Infantry. Strong vs Vehicles
.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. 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. Quads are resistant to bullets and, to a lesser degree, explosives. They are vulnerable to missiles and high-caliber guns.
actor-siege-tank = actor-siege-tank =
.name = Siege Tank .name = Siege Tank
.description = Siege Artillery. .description =
Strong vs. Infantry and Buildings. Siege Artillery.
Weak vs. Tanks. Strong vs Infantry and Buildings
.encyclopedia = Incredibly effective against infantry and lightly armored vehicles, but struggles against heavily armored targets. It has a long firing range. 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. Siege Tanks are resistant to bullets, and to some degree, explosives. They are vulnerable to missiles and high-caliber guns.
actor-missile-tank = actor-missile-tank =
.name = Missile Tank .name = Missile Tank
.description = Rocket Artillery. .description =
Strong vs. Vehicles, Buildings and Aircraft. Rocket Artillery.
Weak vs. Infantry. Strong vs Vehicles, Buildings and Aircraft
.encyclopedia = Shoots down aircraft and is effective against most targets, except infantry. 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. Missile Tanks are vulnerable to most weapons, high-caliber guns are slightly less effective.
actor-sonic-tank = actor-sonic-tank =
.name = Sonic Tank .name = Sonic Tank
.description = Fires sonic shocks. .description =
Strong vs. Infantry and Vehicles. Fires sonic shocks.
Weak vs. Artillery. Strong vs Infantry and Vehicles
.encyclopedia = Most effective against infantry and lightly armored vehicles, but weaker against armored targets. 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. Its sonic waves damage all units in their path.
@@ -508,56 +583,68 @@ actor-sonic-tank =
actor-devastator = actor-devastator =
.name = Devastator .name = Devastator
.description = Super Heavy Tank. .description =
Strong vs. Tanks. Super Heavy Tank.
Weak vs. Artillery. Strong vs Tanks
.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. 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. Resistant to bullets and high explosives, but vulnerable to missiles and high-caliber guns.
actor-raider = actor-raider =
.name = Raider Trike .name = Raider Trike
.description = Improved Scout. .description =
Strong vs. Infantry and Light Vehicles. Improved Scout.
Weak vs. Tanks. Strong vs Infantry and Light Vehicles
.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. 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. Raiders are vulnerable to most weapons, though high-caliber guns are slightly less effective against them.
actor-stealth-raider = actor-stealth-raider =
.name = Stealth Raider Trike .name = Stealth Raider Trike
.description = Invisible Raider Trike. .description =
Strong vs. Infantry and Light Vehicles. Invisible Raider Trike.
Weak vs. Tanks. Strong vs Infantry and Light Vehicles
.encyclopedia = A cloaked version of the Raider, good for stealth attacks. It uncloaks when it fires its machine guns. Weak vs Tanks
.encyclopedia =
A cloaked version of the Raider, good for stealth attacks. It uncloaks when it fires its machine guns.
actor-deviator = actor-deviator =
.name = Deviator .name = Deviator
.description = Fires a warhead that changes .description =
the allegiance of enemy vehicles. Fires a warhead that changes the
.encyclopedia = Fires missiles that release a silicon cloud, temporarily altering the allegiance of targeted vehicles. Personnel are only slightly affected by the cloud. 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. The Deviator is vulnerable to most weapons, high-caliber guns are slightly less effective.
meta-combat-tank-description = Main Battle Tank. meta-combat-tank-description =
Strong vs. Tanks. Main Battle Tank.
Weak vs. Infantry. Strong vs Tanks
Weak vs Infantry
actor-combat-tank-a = actor-combat-tank-a =
.name = Atreides Combat Tank .name = Atreides Combat Tank
.encyclopedia = Effective against most vehicles but less suited against lightly armored targets. .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. Resistant to bullets and heavy explosives, but vulnerable to missiles and high-caliber guns.
actor-combat-tank-h = actor-combat-tank-h =
.name = Harkonnen Combat Tank .name = Harkonnen Combat Tank
.encyclopedia = Effective against most vehicles but less suited against lightly armored targets. .encyclopedia =
Effective against most vehicles but less suited against lightly armored targets.
Stronger than its counterparts, but also slower. Stronger than its counterparts, but also slower.
actor-combat-tank-o = actor-combat-tank-o =
.name = Ordos Combat Tank .name = Ordos Combat Tank
.encyclopedia = Effective against most vehicles but less suited against lightly armored targets. .encyclopedia =
Effective against most vehicles but less suited against lightly armored targets.
The fastest variant of Combat Tank, but also the weakest. The fastest variant of Combat Tank, but also the weakest.

View File

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

View File

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

View File

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

View File

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

View File

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

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