Compare commits
3 Commits
bleed
...
devtest-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54675d6f2b | ||
|
|
2101e74470 | ||
|
|
73ccc83971 |
915
.editorconfig
915
.editorconfig
File diff suppressed because it is too large
Load Diff
21
.github/workflows/ci.yml
vendored
21
.github/workflows/ci.yml
vendored
@@ -15,23 +15,22 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v4
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: Check Code
|
||||
run: |
|
||||
make check
|
||||
make tests
|
||||
|
||||
- name: Check Mods
|
||||
run: |
|
||||
sudo apt-get install lua5.1
|
||||
make check-scripts
|
||||
make TREAT_WARNINGS_AS_ERRORS=true test
|
||||
make test
|
||||
|
||||
linux-mono:
|
||||
name: Linux (mono)
|
||||
@@ -39,7 +38,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Check Code
|
||||
run: |
|
||||
@@ -49,7 +48,7 @@ jobs:
|
||||
- name: Check Mods
|
||||
run: |
|
||||
# check-scripts does not depend on .net/mono, so is not needed here
|
||||
make RUNTIME=mono TREAT_WARNINGS_AS_ERRORS=true test
|
||||
make RUNTIME=mono test
|
||||
|
||||
windows:
|
||||
name: Windows (.NET 6.0)
|
||||
@@ -57,10 +56,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v4
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
@@ -70,12 +69,12 @@ jobs:
|
||||
# Work around runtime failures on the GH Actions runner
|
||||
dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org
|
||||
.\make.ps1 check
|
||||
.\make.ps1 tests
|
||||
dotnet build OpenRA.Test\OpenRA.Test.csproj -c Debug --nologo -p:TargetPlatform=win-x64
|
||||
dotnet test bin\OpenRA.Test.dll --test-adapter-path:.
|
||||
|
||||
- name: Check Mods
|
||||
run: |
|
||||
choco install lua --version 5.1.5.52
|
||||
chocolatey install lua --version 5.1.5.52
|
||||
$ENV:Path = $ENV:Path + ";C:\Program Files (x86)\Lua\5.1\"
|
||||
$ENV:TREAT_WARNINGS_AS_ERRORS = "true"
|
||||
.\make.ps1 check-scripts
|
||||
.\make.ps1 test
|
||||
|
||||
147
.github/workflows/documentation.yml
vendored
147
.github/workflows/documentation.yml
vendored
@@ -1,9 +1,6 @@
|
||||
name: Deploy Documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ bleed ]
|
||||
tags: [ 'release-*', 'playtest-*' ]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
@@ -15,49 +12,18 @@ permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
name: Prepare version strings
|
||||
wiki:
|
||||
name: Update Wiki
|
||||
if: github.repository == 'openra/openra'
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Prepare environment variables
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "push" ]; then
|
||||
if [ "${{ github.ref_type }}" = "tag" ]; then
|
||||
VERSION_TYPE=`echo "${GITHUB_REF#refs/tags/}" | cut -d"-" -f1`
|
||||
echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
echo "VERSION_TYPE=$VERSION_TYPE" >> $GITHUB_ENV
|
||||
else
|
||||
echo "GIT_TAG=bleed" >> $GITHUB_ENV
|
||||
echo "VERSION_TYPE=bleed" >> $GITHUB_ENV
|
||||
fi
|
||||
else
|
||||
VERSION_TYPE=`echo "${{ github.event.inputs.tag }}" | cut -d"-" -f1`
|
||||
echo "GIT_TAG=${{ github.event.inputs.tag }}" >> $GITHUB_ENV
|
||||
echo "VERSION_TYPE=$VERSION_TYPE" >> $GITHUB_ENV
|
||||
fi
|
||||
outputs:
|
||||
git_tag: ${{ env.GIT_TAG }}
|
||||
version_type: ${{ env.VERSION_TYPE }}
|
||||
|
||||
wiki:
|
||||
name: Update Wiki
|
||||
needs: prepare
|
||||
if: github.repository == 'openra/openra' && needs.prepare.outputs.version_type != 'bleed'
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Debug output
|
||||
run: |
|
||||
echo ${{ needs.prepare.outputs.git_tag }}
|
||||
echo ${{ needs.prepare.outputs.version_type }}
|
||||
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ needs.prepare.outputs.git_tag }}
|
||||
ref: ${{ github.event.inputs.tag }}
|
||||
|
||||
- name: Install .NET 6
|
||||
uses: actions/setup-dotnet@v4
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
@@ -66,53 +32,49 @@ jobs:
|
||||
make all
|
||||
|
||||
- name: Clone Wiki
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: openra/openra.wiki
|
||||
token: ${{ secrets.DOCS_TOKEN }}
|
||||
path: wiki
|
||||
|
||||
- name: Update Wiki (Playtest)
|
||||
if: startsWith(needs.prepare.outputs.git_tag, 'playtest-')
|
||||
if: startsWith(github.event.inputs.tag, 'playtest-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --settings-docs "${{ needs.prepare.outputs.git_tag }}" > "wiki/Settings (playtest).md"
|
||||
./utility.sh all --settings-docs "${GIT_TAG}" > "wiki/Settings (playtest).md"
|
||||
|
||||
- name: Update Wiki (Release)
|
||||
if: startsWith(needs.prepare.outputs.git_tag, 'release-')
|
||||
if: startsWith(github.event.inputs.tag, 'release-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --settings-docs "${{ needs.prepare.outputs.git_tag }}" > "wiki/Settings.md"
|
||||
./utility.sh all --settings-docs "${GIT_TAG}" > "wiki/Settings.md"
|
||||
|
||||
- name: Push Wiki
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
cd wiki
|
||||
git config --local user.email "actions@github.com"
|
||||
git config --local user.name "GitHub Actions"
|
||||
git status
|
||||
git diff-index --quiet HEAD || \
|
||||
(
|
||||
git add --all && \
|
||||
git commit -m "Update auto-generated documentation for ${{ needs.prepare.outputs.git_tag }}" && \
|
||||
git push origin master
|
||||
)
|
||||
git add --all
|
||||
git commit -m "Update auto-generated documentation for ${GIT_TAG}"
|
||||
git push origin master
|
||||
|
||||
docs:
|
||||
name: Update docs.openra.net
|
||||
needs: prepare
|
||||
if: github.repository == 'openra/openra'
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Debug output
|
||||
run: |
|
||||
echo ${{ needs.prepare.outputs.git_tag }}
|
||||
echo ${{ needs.prepare.outputs.version_type }}
|
||||
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ needs.prepare.outputs.git_tag }}
|
||||
ref: ${{ github.event.inputs.tag }}
|
||||
|
||||
- name: Install .NET 6
|
||||
uses: actions/setup-dotnet@v4
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
@@ -120,31 +82,62 @@ jobs:
|
||||
run: |
|
||||
make all
|
||||
|
||||
# version_type is release/playtest/bleed - the name of the target branch.
|
||||
- name: Clone docs.openra.net
|
||||
uses: actions/checkout@v4
|
||||
- name: Clone docs.openra.net (Playtest)
|
||||
if: startsWith(github.event.inputs.tag, 'playtest-')
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: openra/docs
|
||||
token: ${{ secrets.DOCS_TOKEN }}
|
||||
path: docs
|
||||
ref: ${{ needs.prepare.outputs.version_type }}
|
||||
ref: playtest
|
||||
|
||||
- name: Generate docs files
|
||||
- name: Clone docs.openra.net (Release)
|
||||
if: startsWith(github.event.inputs.tag, 'release-')
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: openra/docs
|
||||
token: ${{ secrets.DOCS_TOKEN }}
|
||||
path: docs
|
||||
ref: release
|
||||
|
||||
- name: Update docs.openra.net (Playtest)
|
||||
if: startsWith(github.event.inputs.tag, 'playtest-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --docs "${{ needs.prepare.outputs.git_tag }}" | python3 ./packaging/format-docs.py > "docs/api/traits.md"
|
||||
./utility.sh all --weapon-docs "${{ needs.prepare.outputs.git_tag }}" | python3 ./packaging/format-docs.py > "docs/api/weapons.md"
|
||||
./utility.sh all --sprite-sequence-docs "${{ needs.prepare.outputs.git_tag }}" | python3 ./packaging/format-docs.py > "docs/api/sprite-sequences.md"
|
||||
./utility.sh all --lua-docs "${{ needs.prepare.outputs.git_tag }}" > "docs/api/lua.md"
|
||||
./utility.sh all --docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/traits.md"
|
||||
./utility.sh all --weapon-docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/weapons.md"
|
||||
./utility.sh all --sprite-sequence-docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/sprite-sequences.md"
|
||||
./utility.sh all --lua-docs "${GIT_TAG}" > "docs/api/lua.md"
|
||||
|
||||
- name: Update docs.openra.net
|
||||
- name: Update docs.openra.net (Release)
|
||||
if: startsWith(github.event.inputs.tag, 'release-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/traits.md"
|
||||
./utility.sh all --weapon-docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/weapons.md"
|
||||
./utility.sh all --sprite-sequence-docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/sprite-sequences.md"
|
||||
./utility.sh all --lua-docs "${GIT_TAG}" > "docs/api/lua.md"
|
||||
|
||||
- name: Commit docs.openra.net
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
cd docs
|
||||
git config --local user.email "actions@github.com"
|
||||
git config --local user.name "GitHub Actions"
|
||||
git status
|
||||
git diff-index --quiet HEAD || \
|
||||
(
|
||||
git add api/*.md && \
|
||||
git commit -m "Update auto-generated documentation for ${{ needs.prepare.outputs.git_tag }}" && \
|
||||
git push origin ${{ needs.prepare.outputs.version_type }}
|
||||
)
|
||||
git add api/*.md
|
||||
git commit -m "Update auto-generated documentation for ${GIT_TAG}"
|
||||
|
||||
- name: Push docs.openra.net (Release)
|
||||
if: startsWith(github.event.inputs.tag, 'release-')
|
||||
run: |
|
||||
cd docs
|
||||
git push origin release
|
||||
|
||||
- name: Push docs.openra.net (Playtest)
|
||||
if: startsWith(github.event.inputs.tag, 'playtest-')
|
||||
run: |
|
||||
cd docs
|
||||
git push origin playtest
|
||||
|
||||
14
.github/workflows/packaging.yml
vendored
14
.github/workflows/packaging.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare Environment
|
||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
@@ -40,10 +40,10 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v4
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
@@ -70,10 +70,10 @@ jobs:
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v4
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
@@ -105,10 +105,10 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v4
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
|
||||
2
AUTHORS
2
AUTHORS
@@ -120,7 +120,6 @@ Also thanks to:
|
||||
* Mike Gagné (AngryBirdz)
|
||||
* Muh
|
||||
* Mustafa Alperen Seki (MustaphaTR)
|
||||
* Nathan Nichols (cracksmoka420)
|
||||
* Neil Shivkar (havok13888)
|
||||
* Nikolay Fomin (netnazgul)
|
||||
* Nooze
|
||||
@@ -150,7 +149,6 @@ Also thanks to:
|
||||
* Teemu Nieminen (Temeez)
|
||||
* Thomas Christlieb (ThomasChr)
|
||||
* Tim Mylemans (gecko)
|
||||
* Tinix
|
||||
* Tirili
|
||||
* Tomas Einarsson (Mesacer)
|
||||
* Tom van Leth (tovl)
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<Optimize>false</Optimize>
|
||||
<!-- Enable only for Debug builds to improve compile-time performance for Release builds -->
|
||||
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||
<!-- Enabling GenerateDocumentationFile is required for IDE0005 (Remove unnecessary import)
|
||||
<!-- Enabling GenerateDocumentationFile is required for IDE0005 (Remove unnecessary import)
|
||||
rule to run in command line builds. https://github.com/dotnet/roslyn/issues/41640
|
||||
Enable only for Debug builds to improve compile-time performance for Release builds -->
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
@@ -51,10 +51,8 @@
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<!-- StyleCop/Roslynator -->
|
||||
<!-- StyleCop -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
|
||||
<!-- Roslynator analyzers fail to run under Mono (AD0001) -->
|
||||
<PackageReference Include="Roslynator.Analyzers" Version="4.2.0" PrivateAssets="All" Condition="'$(MSBuildRuntimeType)'!='Mono'" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
9
Makefile
9
Makefile
@@ -130,7 +130,7 @@ endif
|
||||
check-scripts:
|
||||
@echo
|
||||
@echo "Checking for Lua syntax errors..."
|
||||
@find mods/*/maps/ mods/*/scripts/ -iname "*.lua" -print0 | xargs -0n1 luac -p
|
||||
@find lua/ mods/*/{maps,scripts}/ -iname "*.lua" -print0 | xargs -0n1 luac -p
|
||||
|
||||
test: all
|
||||
@echo
|
||||
@@ -146,11 +146,6 @@ test: all
|
||||
@echo "Testing Red Alert mod MiniYAML..."
|
||||
@./utility.sh ra --check-yaml
|
||||
|
||||
tests:
|
||||
@dotnet build OpenRA.Test/OpenRA.Test.csproj -c Debug --nologo -p:TargetPlatform=$(TARGETPLATFORM)
|
||||
@echo
|
||||
@dotnet test bin/OpenRA.Test.dll --test-adapter-path:.
|
||||
|
||||
############# LOCAL INSTALLATION AND DOWNSTREAM PACKAGING ##############
|
||||
#
|
||||
version: VERSION mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modcontent/mod.yaml mods/all/mod.yaml
|
||||
@@ -185,7 +180,7 @@ help:
|
||||
@echo ' make [RUNTIME=net6] TARGETPLATFORM=unix-generic'
|
||||
@echo
|
||||
@echo 'to check the official mods for erroneous yaml files, run:'
|
||||
@echo ' make [RUNTIME=net6] [TREAT_WARNINGS_AS_ERRORS=false] test'
|
||||
@echo ' make [RUNTIME=net6] test'
|
||||
@echo
|
||||
@echo 'to check the engine and official mod dlls for code style violations, run:'
|
||||
@echo ' make [RUNTIME=net6] check'
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace OpenRA.Activities
|
||||
bool firstRunCompleted;
|
||||
bool lastRun;
|
||||
|
||||
protected Activity()
|
||||
public Activity()
|
||||
{
|
||||
IsInterruptible = true;
|
||||
ChildHasPriority = true;
|
||||
@@ -146,22 +146,18 @@ namespace OpenRA.Activities
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Called every tick to run activity logic. Returns false if the activity should
|
||||
/// remain active, or true if it is complete. Cancelled activities must ensure they
|
||||
/// return the actor to a consistent state before returning true.
|
||||
/// </para>
|
||||
/// <para>
|
||||
///
|
||||
/// Child activities can be queued using QueueChild, and these will be ticked
|
||||
/// instead of the parent while they are active. Activities that need to run logic
|
||||
/// in parallel with child activities should set ChildHasPriority to false and
|
||||
/// manually call TickChildren.
|
||||
/// </para>
|
||||
/// <para>
|
||||
///
|
||||
/// Queuing one or more child activities and returning true is valid, and causes
|
||||
/// the activity to be completed immediately (without ticking again) once the
|
||||
/// children have completed.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public virtual bool Tick(Actor self)
|
||||
{
|
||||
@@ -226,11 +222,10 @@ namespace OpenRA.Activities
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Prints the activity tree, starting from the top or optionally from a given origin.</para>
|
||||
/// <para>
|
||||
/// Prints the activity tree, starting from the top or optionally from a given origin.
|
||||
///
|
||||
/// Call this method from any place that's called during a tick, such as the Tick() method itself or
|
||||
/// the Before(First|Last)Run() methods. The origin activity will be marked in the output.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="self">The actor performing this activity.</param>
|
||||
/// <param name="origin">Activity from which to start traversing, and which to mark. If null, mark the calling activity, and start traversal from the top.</param>
|
||||
|
||||
@@ -35,9 +35,6 @@ namespace OpenRA
|
||||
|
||||
public sealed class Actor : IScriptBindable, IScriptNotifyBind, ILuaTableBinding, ILuaEqualityBinding, ILuaToStringBinding, IEquatable<Actor>, IDisposable
|
||||
{
|
||||
/// <summary>Value used to represent an invalid token.</summary>
|
||||
public const int InvalidConditionToken = -1;
|
||||
|
||||
internal readonly struct SyncHash
|
||||
{
|
||||
public readonly ISync Trait;
|
||||
@@ -71,12 +68,7 @@ namespace OpenRA
|
||||
public IEffectiveOwner EffectiveOwner { get; }
|
||||
public IOccupySpace OccupiesSpace { get; }
|
||||
public ITargetable[] Targetables { get; }
|
||||
public IEnumerable<ITargetablePositions> EnabledTargetablePositions { get; }
|
||||
readonly ICrushable[] crushables;
|
||||
public ICrushable[] Crushables
|
||||
{
|
||||
get => crushables ?? throw new InvalidOperationException($"Crushables for {Info.Name} are not initialized.");
|
||||
}
|
||||
public IEnumerable<ITargetablePositions> EnabledTargetablePositions { get; private set; }
|
||||
|
||||
public bool IsIdle => CurrentActivity == null;
|
||||
public bool IsDead => Disposed || (health != null && health.IsDead);
|
||||
@@ -86,7 +78,10 @@ namespace OpenRA
|
||||
|
||||
public WRot Orientation => facing?.Orientation ?? WRot.None;
|
||||
|
||||
sealed class ConditionState
|
||||
/// <summary>Value used to represent an invalid token.</summary>
|
||||
public static readonly int InvalidConditionToken = -1;
|
||||
|
||||
class ConditionState
|
||||
{
|
||||
/// <summary>Delegates that have registered to be notified when this condition changes.</summary>
|
||||
public readonly List<VariableObserverNotifier> Notifiers = new();
|
||||
@@ -160,7 +155,6 @@ namespace OpenRA
|
||||
var targetablesList = new List<ITargetable>();
|
||||
var targetablePositionsList = new List<ITargetablePositions>();
|
||||
var syncHashesList = new List<SyncHash>();
|
||||
var crushablesList = new List<ICrushable>();
|
||||
|
||||
foreach (var traitInfo in Info.TraitsInConstructOrder())
|
||||
{
|
||||
@@ -187,7 +181,6 @@ namespace OpenRA
|
||||
{ if (trait is ITargetable t) targetablesList.Add(t); }
|
||||
{ if (trait is ITargetablePositions t) targetablePositionsList.Add(t); }
|
||||
{ if (trait is ISync t) syncHashesList.Add(new SyncHash(t)); }
|
||||
{ if (trait is ICrushable t) crushablesList.Add(t); }
|
||||
}
|
||||
|
||||
resolveOrders = resolveOrdersList.ToArray();
|
||||
@@ -202,7 +195,6 @@ namespace OpenRA
|
||||
EnabledTargetablePositions = targetablePositions.Where(Exts.IsTraitEnabled);
|
||||
enabledTargetableWorldPositions = EnabledTargetablePositions.SelectMany(tp => tp.TargetablePositions(this));
|
||||
SyncHashes = syncHashesList.ToArray();
|
||||
crushables = crushablesList.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,14 +61,14 @@ namespace OpenRA
|
||||
|
||||
public static readonly CVec[] Directions =
|
||||
{
|
||||
new(-1, -1),
|
||||
new(-1, 0),
|
||||
new(-1, 1),
|
||||
new(0, -1),
|
||||
new(0, 1),
|
||||
new(1, -1),
|
||||
new(1, 0),
|
||||
new(1, 1),
|
||||
new CVec(-1, -1),
|
||||
new CVec(-1, 0),
|
||||
new CVec(-1, 1),
|
||||
new CVec(0, -1),
|
||||
new CVec(0, 1),
|
||||
new CVec(1, -1),
|
||||
new CVec(1, 0),
|
||||
new CVec(1, 1),
|
||||
};
|
||||
|
||||
#region Scripting interface
|
||||
|
||||
@@ -22,9 +22,6 @@ namespace OpenRA
|
||||
// Fixed byte pattern for the OID header
|
||||
static readonly byte[] OIDHeader = { 0x30, 0xD, 0x6, 0x9, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0xD, 0x1, 0x1, 0x1, 0x5, 0x0 };
|
||||
|
||||
static readonly char[] HexUpperAlphabet = "0123456789ABCDEF".ToArray();
|
||||
static readonly char[] HexLowerAlphabet = "0123456789abcdef".ToArray();
|
||||
|
||||
public static string PublicKeyFingerprint(RSAParameters parameters)
|
||||
{
|
||||
// Public key fingerprint is defined as the SHA1 of the modulus + exponent bytes
|
||||
@@ -56,33 +53,33 @@ namespace OpenRA
|
||||
using (var s = new MemoryStream(data))
|
||||
{
|
||||
// SEQUENCE
|
||||
s.ReadUInt8();
|
||||
s.ReadByte();
|
||||
ReadTLVLength(s);
|
||||
|
||||
// SEQUENCE -> fixed header junk
|
||||
s.ReadUInt8();
|
||||
s.ReadByte();
|
||||
var headerLength = ReadTLVLength(s);
|
||||
s.Position += headerLength;
|
||||
|
||||
// SEQUENCE -> BIT_STRING
|
||||
s.ReadUInt8();
|
||||
s.ReadByte();
|
||||
ReadTLVLength(s);
|
||||
s.ReadUInt8();
|
||||
s.ReadByte();
|
||||
|
||||
// SEQUENCE -> BIT_STRING -> SEQUENCE
|
||||
s.ReadUInt8();
|
||||
s.ReadByte();
|
||||
ReadTLVLength(s);
|
||||
|
||||
// SEQUENCE -> BIT_STRING -> SEQUENCE -> INTEGER (modulus)
|
||||
s.ReadUInt8();
|
||||
s.ReadByte();
|
||||
var modulusLength = ReadTLVLength(s);
|
||||
s.ReadUInt8();
|
||||
s.ReadByte();
|
||||
var modulus = s.ReadBytes(modulusLength - 1);
|
||||
|
||||
// SEQUENCE -> BIT_STRING -> SEQUENCE -> INTEGER (exponent)
|
||||
s.ReadUInt8();
|
||||
s.ReadByte();
|
||||
var exponentLength = ReadTLVLength(s);
|
||||
s.ReadUInt8();
|
||||
s.ReadByte();
|
||||
var exponent = s.ReadBytes(exponentLength - 1);
|
||||
|
||||
return new RSAParameters
|
||||
@@ -161,13 +158,13 @@ namespace OpenRA
|
||||
|
||||
static int ReadTLVLength(Stream s)
|
||||
{
|
||||
var length = s.ReadUInt8();
|
||||
var length = s.ReadByte();
|
||||
if (length < 0x80)
|
||||
return length;
|
||||
|
||||
Span<byte> data = stackalloc byte[4];
|
||||
s.ReadBytes(data[..Math.Min(length & 0x7F, 4)]);
|
||||
return BitConverter.ToInt32(data);
|
||||
var data = new byte[4];
|
||||
s.ReadBytes(data, 0, Math.Min(length & 0x7F, 4));
|
||||
return BitConverter.ToInt32(data.ToArray(), 0);
|
||||
}
|
||||
|
||||
static int TripletFullLength(int dataLength)
|
||||
@@ -190,10 +187,8 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to decrypt string with exception:");
|
||||
Log.Write("debug", e);
|
||||
Console.WriteLine("String decryption failed:");
|
||||
Console.WriteLine(e);
|
||||
Log.Write("debug", "Failed to decrypt string with exception: {0}", e);
|
||||
Console.WriteLine("String decryption failed: {0}", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -216,10 +211,8 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to sign string with exception");
|
||||
Log.Write("debug", e);
|
||||
Console.WriteLine("String signing failed:");
|
||||
Console.WriteLine(e);
|
||||
Log.Write("debug", "Failed to sign string with exception: {0}", e);
|
||||
Console.WriteLine("String signing failed: {0}", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -242,54 +235,27 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to verify signature with exception:");
|
||||
Log.Write("debug", e);
|
||||
Console.WriteLine("Signature validation failed:");
|
||||
Console.WriteLine(e);
|
||||
Log.Write("debug", "Failed to verify signature with exception: {0}", e);
|
||||
Console.WriteLine("Signature validation failed: {0}", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static string SHA1Hash(Stream data)
|
||||
{
|
||||
using var csp = SHA1.Create();
|
||||
return ToHex(csp.ComputeHash(data), true);
|
||||
using (var csp = SHA1.Create())
|
||||
return new string(csp.ComputeHash(data).SelectMany(a => a.ToString("x2")).ToArray());
|
||||
}
|
||||
|
||||
public static string SHA1Hash(byte[] data)
|
||||
{
|
||||
using var csp = SHA1.Create();
|
||||
return ToHex(csp.ComputeHash(data), true);
|
||||
using (var csp = SHA1.Create())
|
||||
return new string(csp.ComputeHash(data).SelectMany(a => a.ToString("x2")).ToArray());
|
||||
}
|
||||
|
||||
public static string SHA1Hash(string data)
|
||||
{
|
||||
return SHA1Hash(Encoding.UTF8.GetBytes(data));
|
||||
}
|
||||
|
||||
public static string ToHex(ReadOnlySpan<byte> source, bool lowerCase = false)
|
||||
{
|
||||
if (source.Length == 0)
|
||||
return string.Empty;
|
||||
|
||||
// excessively avoid stack overflow if source is too large (considering that we're allocating a new string)
|
||||
var buffer = source.Length <= 256 ? stackalloc char[source.Length * 2] : new char[source.Length * 2];
|
||||
return ToHexInternal(source, buffer, lowerCase);
|
||||
}
|
||||
|
||||
static string ToHexInternal(ReadOnlySpan<byte> source, Span<char> buffer, bool lowerCase)
|
||||
{
|
||||
var sourceIndex = 0;
|
||||
var alphabet = lowerCase ? HexLowerAlphabet : HexUpperAlphabet;
|
||||
|
||||
for (var i = 0; i < buffer.Length; i += 2)
|
||||
{
|
||||
var b = source[sourceIndex++];
|
||||
buffer[i] = alphabet[b >> 4];
|
||||
buffer[i + 1] = alphabet[b & 0xF];
|
||||
}
|
||||
|
||||
return new string(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,6 @@ namespace OpenRA
|
||||
// Several types of support directory types are available, depending on
|
||||
// how the player has installed and launched the game.
|
||||
// Read registration metadata from all of them
|
||||
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
||||
foreach (var source in GetSupportDirs(ModRegistration.User | ModRegistration.System))
|
||||
{
|
||||
var metadataPath = Path.Combine(source, "ModMetadata");
|
||||
@@ -77,13 +76,13 @@ namespace OpenRA
|
||||
{
|
||||
try
|
||||
{
|
||||
var yaml = MiniYaml.FromFile(path, stringPool: stringPool).First().Value;
|
||||
var yaml = MiniYaml.FromStream(File.OpenRead(path), path).First().Value;
|
||||
LoadMod(yaml, path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", $"Failed to parse mod metadata file '{path}'");
|
||||
Log.Write("debug", e);
|
||||
Log.Write("debug", "Failed to parse mod metadata file '{0}'", path);
|
||||
Log.Write("debug", e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,17 +94,17 @@ namespace OpenRA
|
||||
|
||||
if (sheetBuilder != null)
|
||||
{
|
||||
var iconNode = yaml.NodeWithKeyOrDefault("Icon");
|
||||
var iconNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon");
|
||||
if (iconNode != null && !string.IsNullOrEmpty(iconNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(iconNode.Value.Value)))
|
||||
mod.Icon = sheetBuilder.Add(new Png(stream));
|
||||
|
||||
var icon2xNode = yaml.NodeWithKeyOrDefault("Icon2x");
|
||||
var icon2xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon2x");
|
||||
if (icon2xNode != null && !string.IsNullOrEmpty(icon2xNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(icon2xNode.Value.Value)))
|
||||
mod.Icon2x = sheetBuilder.Add(new Png(stream), 1f / 2);
|
||||
|
||||
var icon3xNode = yaml.NodeWithKeyOrDefault("Icon3x");
|
||||
var icon3xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon3x");
|
||||
if (icon3xNode != null && !string.IsNullOrEmpty(icon3xNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(icon3xNode.Value.Value)))
|
||||
mod.Icon3x = sheetBuilder.Add(new Png(stream), 1f / 3);
|
||||
@@ -123,7 +122,7 @@ namespace OpenRA
|
||||
return;
|
||||
|
||||
var key = ExternalMod.MakeKey(mod);
|
||||
var yaml = new MiniYamlNode("Registration", new MiniYaml("", new[]
|
||||
var yaml = new MiniYamlNode("Registration", new MiniYaml("", new List<MiniYamlNode>()
|
||||
{
|
||||
new MiniYamlNode("Id", mod.Id),
|
||||
new MiniYamlNode("Version", mod.Metadata.Version),
|
||||
@@ -132,21 +131,17 @@ namespace OpenRA
|
||||
new MiniYamlNode("LaunchArgs", new[] { "Game.Mod=" + mod.Id }.Concat(launchArgs).JoinWith(", "))
|
||||
}));
|
||||
|
||||
var iconNodes = new List<MiniYamlNode>();
|
||||
|
||||
using (var stream = mod.Package.GetStream("icon.png"))
|
||||
if (stream != null)
|
||||
iconNodes.Add(new MiniYamlNode("Icon", Convert.ToBase64String(stream.ReadAllBytes())));
|
||||
yaml.Value.Nodes.Add(new MiniYamlNode("Icon", Convert.ToBase64String(stream.ReadAllBytes())));
|
||||
|
||||
using (var stream = mod.Package.GetStream("icon-2x.png"))
|
||||
if (stream != null)
|
||||
iconNodes.Add(new MiniYamlNode("Icon2x", Convert.ToBase64String(stream.ReadAllBytes())));
|
||||
yaml.Value.Nodes.Add(new MiniYamlNode("Icon2x", Convert.ToBase64String(stream.ReadAllBytes())));
|
||||
|
||||
using (var stream = mod.Package.GetStream("icon-3x.png"))
|
||||
if (stream != null)
|
||||
iconNodes.Add(new MiniYamlNode("Icon3x", Convert.ToBase64String(stream.ReadAllBytes())));
|
||||
|
||||
yaml = yaml.WithValue(yaml.Value.WithNodesAppended(iconNodes));
|
||||
yaml.Value.Nodes.Add(new MiniYamlNode("Icon3x", Convert.ToBase64String(stream.ReadAllBytes())));
|
||||
|
||||
var sources = new HashSet<string>();
|
||||
if (registration.HasFlag(ModRegistration.System))
|
||||
@@ -179,7 +174,7 @@ namespace OpenRA
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to register current mod metadata");
|
||||
Log.Write("debug", e);
|
||||
Log.Write("debug", e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,7 +201,7 @@ namespace OpenRA
|
||||
string modKey = null;
|
||||
try
|
||||
{
|
||||
var yaml = MiniYaml.FromFile(path).First().Value;
|
||||
var yaml = MiniYaml.FromStream(File.OpenRead(path), path).First().Value;
|
||||
var m = FieldLoader.Load<ExternalMod>(yaml);
|
||||
modKey = ExternalMod.MakeKey(m);
|
||||
|
||||
@@ -218,8 +213,8 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", $"Failed to parse mod metadata file '{path}'");
|
||||
Log.Write("debug", e);
|
||||
Log.Write("debug", "Failed to parse mod metadata file '{0}'", path);
|
||||
Log.Write("debug", e.ToString());
|
||||
}
|
||||
|
||||
// Remove from the ingame mod switcher
|
||||
@@ -230,12 +225,12 @@ namespace OpenRA
|
||||
try
|
||||
{
|
||||
File.Delete(path);
|
||||
Log.Write("debug", $"Removed invalid mod metadata file '{path}'");
|
||||
Log.Write("debug", "Removed invalid mod metadata file '{0}'", path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", $"Failed to remove mod metadata file '{path}'");
|
||||
Log.Write("debug", e);
|
||||
Log.Write("debug", "Failed to remove mod metadata file '{0}'", path);
|
||||
Log.Write("debug", e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -256,13 +251,13 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", $"Failed to remove mod metadata file '{path}'");
|
||||
Log.Write("debug", e);
|
||||
Log.Write("debug", "Failed to remove mod metadata file '{0}'", path);
|
||||
Log.Write("debug", e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static IEnumerable<string> GetSupportDirs(ModRegistration registration)
|
||||
IEnumerable<string> GetSupportDirs(ModRegistration registration)
|
||||
{
|
||||
var sources = new HashSet<string>(4);
|
||||
if (registration.HasFlag(ModRegistration.System))
|
||||
|
||||
@@ -22,14 +22,20 @@ namespace OpenRA
|
||||
{
|
||||
public static class Exts
|
||||
{
|
||||
public static string FormatInvariant(this string format, params object[] args)
|
||||
public static bool IsUppercase(this string str)
|
||||
{
|
||||
return string.Format(CultureInfo.InvariantCulture, format, args);
|
||||
return string.Compare(str.ToUpperInvariant(), str, false) == 0;
|
||||
}
|
||||
|
||||
public static string FormatCurrent(this string format, params object[] args)
|
||||
public static string F(this string format, params object[] args)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, format, args);
|
||||
return string.Format(format, args);
|
||||
}
|
||||
|
||||
public static T WithDefault<T>(T def, Func<T> f)
|
||||
{
|
||||
try { return f(); }
|
||||
catch { return def; }
|
||||
}
|
||||
|
||||
public static Lazy<T> Lazy<T>(Func<T> p) { return new Lazy<T>(p); }
|
||||
@@ -108,57 +114,23 @@ namespace OpenRA
|
||||
|
||||
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k, V v)
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
// SAFETY: Dictionary cannot be modified whilst the ref is alive.
|
||||
ref var value = ref System.Runtime.InteropServices.CollectionsMarshal.GetValueRefOrAddDefault(d, k, out var exists);
|
||||
if (!exists)
|
||||
value = v;
|
||||
return value;
|
||||
#else
|
||||
if (!d.TryGetValue(k, out var ret))
|
||||
d.Add(k, ret = v);
|
||||
return ret;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k, Func<K, V> createFn)
|
||||
{
|
||||
// Cannot use CollectionsMarshal.GetValueRefOrAddDefault here,
|
||||
// the creation function could mutate the dictionary which would invalidate the ref.
|
||||
if (!d.TryGetValue(k, out var ret))
|
||||
d.Add(k, ret = createFn(k));
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static T GetOrAdd<T>(this HashSet<T> set, T value)
|
||||
{
|
||||
if (!set.TryGetValue(value, out var ret))
|
||||
set.Add(ret = value);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static T GetOrAdd<T>(this HashSet<T> set, T value, Func<T, T> createFn)
|
||||
{
|
||||
if (!set.TryGetValue(value, out var ret))
|
||||
set.Add(ret = createFn(value));
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static int IndexOf<T>(this T[] array, T value)
|
||||
{
|
||||
return Array.IndexOf(array, value);
|
||||
}
|
||||
|
||||
public static T FirstOrDefault<T>(this T[] array, Predicate<T> match)
|
||||
{
|
||||
return Array.Find(array, match);
|
||||
}
|
||||
|
||||
public static T FirstOrDefault<T>(this List<T> list, Predicate<T> match)
|
||||
{
|
||||
return list.Find(match);
|
||||
}
|
||||
|
||||
public static T Random<T>(this IEnumerable<T> ts, MersenneTwister r)
|
||||
{
|
||||
return Random(ts, r, true);
|
||||
@@ -171,7 +143,7 @@ namespace OpenRA
|
||||
|
||||
static T Random<T>(IEnumerable<T> ts, MersenneTwister r, bool throws)
|
||||
{
|
||||
var xs = ts as IReadOnlyCollection<T>;
|
||||
var xs = ts as ICollection<T>;
|
||||
xs ??= ts.ToList();
|
||||
if (xs.Count == 0)
|
||||
{
|
||||
@@ -326,9 +298,9 @@ namespace OpenRA
|
||||
|
||||
// Adjust for other rounding modes
|
||||
if (round == ISqrtRoundMode.Nearest && remainder > root)
|
||||
root++;
|
||||
root += 1;
|
||||
else if (round == ISqrtRoundMode.Ceiling && root * root < number)
|
||||
root++;
|
||||
root += 1;
|
||||
|
||||
return root;
|
||||
}
|
||||
@@ -367,9 +339,9 @@ namespace OpenRA
|
||||
|
||||
// Adjust for other rounding modes
|
||||
if (round == ISqrtRoundMode.Nearest && remainder > root)
|
||||
root++;
|
||||
root += 1;
|
||||
else if (round == ISqrtRoundMode.Ceiling && root * root < number)
|
||||
root++;
|
||||
root += 1;
|
||||
|
||||
return root;
|
||||
}
|
||||
@@ -379,11 +351,6 @@ namespace OpenRA
|
||||
return number * 46341 / 32768;
|
||||
}
|
||||
|
||||
public static int MultiplyBySqrtTwoOverTwo(int number)
|
||||
{
|
||||
return (int)(number * 23170L / 32768L);
|
||||
}
|
||||
|
||||
public static int IntegerDivisionRoundingAwayFromZero(int dividend, int divisor)
|
||||
{
|
||||
var quotient = Math.DivRem(dividend, divisor, out var remainder);
|
||||
@@ -521,22 +488,17 @@ namespace OpenRA
|
||||
return result;
|
||||
}
|
||||
|
||||
public static byte ParseByteInvariant(string s)
|
||||
{
|
||||
return byte.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
public static short ParseInt16Invariant(string s)
|
||||
{
|
||||
return short.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
public static int ParseInt32Invariant(string s)
|
||||
public static int ParseIntegerInvariant(string s)
|
||||
{
|
||||
return int.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
public static bool TryParseInt32Invariant(string s, out int i)
|
||||
public static byte ParseByte(string s)
|
||||
{
|
||||
return byte.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
public static bool TryParseIntegerInvariant(string s, out int i)
|
||||
{
|
||||
return int.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
|
||||
}
|
||||
@@ -546,26 +508,6 @@ namespace OpenRA
|
||||
return long.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
|
||||
}
|
||||
|
||||
public static string ToStringInvariant(this byte i)
|
||||
{
|
||||
return i.ToString(NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
public static string ToStringInvariant(this byte i, string format)
|
||||
{
|
||||
return i.ToString(format, NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
public static string ToStringInvariant(this int i)
|
||||
{
|
||||
return i.ToString(NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
public static string ToStringInvariant(this int i, string format)
|
||||
{
|
||||
return i.ToString(format, NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
public static bool IsTraitEnabled<T>(this T trait)
|
||||
{
|
||||
return trait is not IDisabledTrait disabledTrait || !disabledTrait.IsTraitDisabled;
|
||||
@@ -629,7 +571,7 @@ namespace OpenRA
|
||||
Current = default;
|
||||
}
|
||||
|
||||
public readonly LineSplitEnumerator GetEnumerator() => this;
|
||||
public LineSplitEnumerator GetEnumerator() => this;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
|
||||
@@ -87,7 +87,6 @@ namespace OpenRA
|
||||
{ typeof(WAngle), ParseWAngle },
|
||||
{ typeof(WRot), ParseWRot },
|
||||
{ typeof(CPos), ParseCPos },
|
||||
{ typeof(CPos[]), ParseCPosArray },
|
||||
{ typeof(CVec), ParseCVec },
|
||||
{ typeof(CVec[]), ParseCVecArray },
|
||||
{ typeof(BooleanExpression), ParseBooleanExpression },
|
||||
@@ -113,19 +112,10 @@ namespace OpenRA
|
||||
{ typeof(Nullable<>), ParseNullable },
|
||||
};
|
||||
|
||||
static readonly object BoxedTrue = true;
|
||||
static readonly object BoxedFalse = false;
|
||||
static readonly object[] BoxedInts = Exts.MakeArray(33, i => (object)i);
|
||||
|
||||
static object ParseInt(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (Exts.TryParseInt32Invariant(value, out var res))
|
||||
{
|
||||
if (res >= 0 && res < BoxedInts.Length)
|
||||
return BoxedInts[res];
|
||||
if (Exts.TryParseIntegerInvariant(value, out var res))
|
||||
return res;
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
@@ -196,11 +186,11 @@ namespace OpenRA
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma);
|
||||
if (parts.Length == 3
|
||||
&& WDist.TryParse(parts[0], out var rx)
|
||||
&& WDist.TryParse(parts[1], out var ry)
|
||||
&& WDist.TryParse(parts[2], out var rz))
|
||||
return new WVec(rx, ry, rz);
|
||||
if (parts.Length == 3)
|
||||
{
|
||||
if (WDist.TryParse(parts[0], out var rx) && WDist.TryParse(parts[1], out var ry) && WDist.TryParse(parts[2], out var rz))
|
||||
return new WVec(rx, ry, rz);
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
@@ -220,8 +210,8 @@ namespace OpenRA
|
||||
for (var i = 0; i < vecs.Length; ++i)
|
||||
{
|
||||
if (WDist.TryParse(parts[3 * i], out var rx)
|
||||
&& WDist.TryParse(parts[3 * i + 1], out var ry)
|
||||
&& WDist.TryParse(parts[3 * i + 2], out var rz))
|
||||
&& WDist.TryParse(parts[3 * i + 1], out var ry)
|
||||
&& WDist.TryParse(parts[3 * i + 2], out var rz))
|
||||
vecs[i] = new WVec(rx, ry, rz);
|
||||
}
|
||||
|
||||
@@ -236,11 +226,13 @@ namespace OpenRA
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma);
|
||||
if (parts.Length == 3
|
||||
&& WDist.TryParse(parts[0], out var rx)
|
||||
&& WDist.TryParse(parts[1], out var ry)
|
||||
&& WDist.TryParse(parts[2], out var rz))
|
||||
return new WPos(rx, ry, rz);
|
||||
if (parts.Length == 3)
|
||||
{
|
||||
if (WDist.TryParse(parts[0], out var rx)
|
||||
&& WDist.TryParse(parts[1], out var ry)
|
||||
&& WDist.TryParse(parts[2], out var rz))
|
||||
return new WPos(rx, ry, rz);
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
@@ -248,7 +240,7 @@ namespace OpenRA
|
||||
|
||||
static object ParseWAngle(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (Exts.TryParseInt32Invariant(value, out var res))
|
||||
if (Exts.TryParseIntegerInvariant(value, out var res))
|
||||
return new WAngle(res);
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
@@ -258,11 +250,13 @@ namespace OpenRA
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma);
|
||||
if (parts.Length == 3
|
||||
&& Exts.TryParseInt32Invariant(parts[0], out var rr)
|
||||
&& Exts.TryParseInt32Invariant(parts[1], out var rp)
|
||||
&& Exts.TryParseInt32Invariant(parts[2], out var ry))
|
||||
return new WRot(new WAngle(rr), new WAngle(rp), new WAngle(ry));
|
||||
if (parts.Length == 3)
|
||||
{
|
||||
if (Exts.TryParseIntegerInvariant(parts[0], out var rr)
|
||||
&& Exts.TryParseIntegerInvariant(parts[1], out var rp)
|
||||
&& Exts.TryParseIntegerInvariant(parts[2], out var ry))
|
||||
return new WRot(new WAngle(rr), new WAngle(rp), new WAngle(ry));
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
@@ -275,33 +269,10 @@ namespace OpenRA
|
||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length == 3)
|
||||
return new CPos(
|
||||
Exts.ParseInt32Invariant(parts[0]),
|
||||
Exts.ParseInt32Invariant(parts[1]),
|
||||
Exts.ParseByteInvariant(parts[2]));
|
||||
return new CPos(Exts.ParseInt32Invariant(parts[0]), Exts.ParseInt32Invariant(parts[1]));
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseCPosArray(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma);
|
||||
|
||||
if (parts.Length % 2 != 0)
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
|
||||
var vecs = new CPos[parts.Length / 2];
|
||||
for (var i = 0; i < vecs.Length; i++)
|
||||
{
|
||||
if (int.TryParse(parts[2 * i], out var rx)
|
||||
&& int.TryParse(parts[2 * i + 1], out var ry))
|
||||
vecs[i] = new CPos(rx, ry);
|
||||
}
|
||||
|
||||
return vecs;
|
||||
Exts.ParseIntegerInvariant(parts[0]),
|
||||
Exts.ParseIntegerInvariant(parts[1]),
|
||||
Exts.ParseByte(parts[2]));
|
||||
return new CPos(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
@@ -312,7 +283,7 @@ namespace OpenRA
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||
return new CVec(Exts.ParseInt32Invariant(parts[0]), Exts.ParseInt32Invariant(parts[1]));
|
||||
return new CVec(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
@@ -390,7 +361,7 @@ namespace OpenRA
|
||||
static object ParseBool(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (bool.TryParse(value.ToLowerInvariant(), out var result))
|
||||
return result ? BoxedTrue : BoxedFalse;
|
||||
return result;
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
@@ -405,7 +376,7 @@ namespace OpenRA
|
||||
|
||||
var ints = new int2[parts.Length / 2];
|
||||
for (var i = 0; i < ints.Length; i++)
|
||||
ints[i] = new int2(Exts.ParseInt32Invariant(parts[2 * i]), Exts.ParseInt32Invariant(parts[2 * i + 1]));
|
||||
ints[i] = new int2(Exts.ParseIntegerInvariant(parts[2 * i]), Exts.ParseIntegerInvariant(parts[2 * i + 1]));
|
||||
|
||||
return ints;
|
||||
}
|
||||
@@ -418,7 +389,7 @@ namespace OpenRA
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||
return new Size(Exts.ParseInt32Invariant(parts[0]), Exts.ParseInt32Invariant(parts[1]));
|
||||
return new Size(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
@@ -432,7 +403,7 @@ namespace OpenRA
|
||||
if (parts.Length != 2)
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
|
||||
return new int2(Exts.ParseInt32Invariant(parts[0]), Exts.ParseInt32Invariant(parts[1]));
|
||||
return new int2(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
@@ -480,10 +451,10 @@ namespace OpenRA
|
||||
{
|
||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||
return new Rectangle(
|
||||
Exts.ParseInt32Invariant(parts[0]),
|
||||
Exts.ParseInt32Invariant(parts[1]),
|
||||
Exts.ParseInt32Invariant(parts[2]),
|
||||
Exts.ParseInt32Invariant(parts[3]));
|
||||
Exts.ParseIntegerInvariant(parts[0]),
|
||||
Exts.ParseIntegerInvariant(parts[1]),
|
||||
Exts.ParseIntegerInvariant(parts[2]),
|
||||
Exts.ParseIntegerInvariant(parts[3]));
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
@@ -498,11 +469,11 @@ namespace OpenRA
|
||||
|
||||
static object ParseHashSetOrList(string fieldName, Type fieldType, string value, MiniYaml yaml, MemberInfo field)
|
||||
{
|
||||
var set = Activator.CreateInstance(fieldType);
|
||||
if (value == null)
|
||||
return Activator.CreateInstance(fieldType);
|
||||
return set;
|
||||
|
||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||
var set = Activator.CreateInstance(fieldType, parts.Length);
|
||||
var arguments = fieldType.GetGenericArguments();
|
||||
var addMethod = fieldType.GetMethod(nameof(List<object>.Add), arguments);
|
||||
var addArgs = new object[1];
|
||||
@@ -517,10 +488,7 @@ namespace OpenRA
|
||||
|
||||
static object ParseDictionary(string fieldName, Type fieldType, string value, MiniYaml yaml, MemberInfo field)
|
||||
{
|
||||
if (yaml == null)
|
||||
return Activator.CreateInstance(fieldType);
|
||||
|
||||
var dict = Activator.CreateInstance(fieldType, yaml.Nodes.Length);
|
||||
var dict = Activator.CreateInstance(fieldType);
|
||||
var arguments = fieldType.GetGenericArguments();
|
||||
var addMethod = fieldType.GetMethod(nameof(Dictionary<object, object>.Add), arguments);
|
||||
var addArgs = new object[2];
|
||||
@@ -551,7 +519,7 @@ namespace OpenRA
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return null;
|
||||
|
||||
var innerType = fieldType.GetGenericArguments()[0];
|
||||
var innerType = fieldType.GetGenericArguments().First();
|
||||
var innerValue = GetValue("Nullable<T>", innerType, value, field);
|
||||
return fieldType.GetConstructor(new[] { innerType }).Invoke(new[] { innerValue });
|
||||
}
|
||||
@@ -559,7 +527,7 @@ namespace OpenRA
|
||||
public static void Load(object self, MiniYaml my)
|
||||
{
|
||||
var loadInfo = TypeLoadInfo[self.GetType()];
|
||||
List<string> missing = null;
|
||||
var missing = new List<string>();
|
||||
|
||||
Dictionary<string, MiniYaml> md = null;
|
||||
|
||||
@@ -574,7 +542,6 @@ namespace OpenRA
|
||||
val = fli.Loader(my);
|
||||
else
|
||||
{
|
||||
missing ??= new List<string>();
|
||||
missing.Add(fli.YamlName);
|
||||
continue;
|
||||
}
|
||||
@@ -584,11 +551,7 @@ namespace OpenRA
|
||||
if (!TryGetValueFromYaml(fli.YamlName, fli.Field, md, out val))
|
||||
{
|
||||
if (fli.Attribute.Required)
|
||||
{
|
||||
missing ??= new List<string>();
|
||||
missing.Add(fli.YamlName);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -596,7 +559,7 @@ namespace OpenRA
|
||||
fli.Field.SetValue(self, val);
|
||||
}
|
||||
|
||||
if (missing != null)
|
||||
if (missing.Count > 0)
|
||||
throw new MissingFieldsException(missing.ToArray());
|
||||
}
|
||||
|
||||
@@ -657,17 +620,12 @@ namespace OpenRA
|
||||
|
||||
public static object GetValue(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
return GetValue(fieldName, fieldType, value, null, field);
|
||||
return GetValue(fieldName, fieldType, new MiniYaml(value), field);
|
||||
}
|
||||
|
||||
public static object GetValue(string fieldName, Type fieldType, MiniYaml yaml, MemberInfo field)
|
||||
{
|
||||
return GetValue(fieldName, fieldType, yaml.Value, yaml, field);
|
||||
}
|
||||
|
||||
static object GetValue(string fieldName, Type fieldType, string value, MiniYaml yaml, MemberInfo field)
|
||||
{
|
||||
value = value?.Trim();
|
||||
var value = yaml.Value?.Trim();
|
||||
if (fieldType.IsGenericType)
|
||||
{
|
||||
if (GenericTypeParsers.TryGetValue(fieldType.GetGenericTypeDefinition(), out var parseFuncGeneric))
|
||||
|
||||
@@ -15,7 +15,6 @@ using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
@@ -59,7 +58,7 @@ namespace OpenRA
|
||||
|
||||
return new MiniYaml(
|
||||
null,
|
||||
fields.Select(info => new MiniYamlNode(info.YamlName, FormatValue(o, info.Field))));
|
||||
fields.Select(info => new MiniYamlNode(info.YamlName, FormatValue(o, info.Field))).ToList());
|
||||
}
|
||||
|
||||
public static MiniYamlNode SaveField(object o, string field)
|
||||
@@ -85,7 +84,7 @@ namespace OpenRA
|
||||
// This is only for documentation generation
|
||||
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
||||
{
|
||||
var result = new StringBuilder();
|
||||
var result = "";
|
||||
var dict = (System.Collections.IDictionary)v;
|
||||
foreach (var kvp in dict)
|
||||
{
|
||||
@@ -95,10 +94,10 @@ namespace OpenRA
|
||||
var formattedKey = FormatValue(key);
|
||||
var formattedValue = FormatValue(value);
|
||||
|
||||
result.Append($"{formattedKey}: {formattedValue}{Environment.NewLine}");
|
||||
result += $"{formattedKey}: {formattedValue}{Environment.NewLine}";
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
return result;
|
||||
}
|
||||
|
||||
if (v is DateTime d)
|
||||
|
||||
@@ -16,7 +16,6 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using ICSharpCode.SharpZipLib.Checksum;
|
||||
using ICSharpCode.SharpZipLib.Zip.Compression;
|
||||
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
@@ -46,13 +45,12 @@ namespace OpenRA.FileFormats
|
||||
var data = new List<byte>();
|
||||
Type = SpriteFrameType.Rgba32;
|
||||
|
||||
byte bitDepth = 8;
|
||||
while (true)
|
||||
{
|
||||
var length = IPAddress.NetworkToHostOrder(s.ReadInt32());
|
||||
var type = s.ReadASCII(4);
|
||||
var type = Encoding.UTF8.GetString(s.ReadBytes(4));
|
||||
var content = s.ReadBytes(length);
|
||||
s.ReadInt32(); // crc
|
||||
/*var crc = */s.ReadInt32();
|
||||
|
||||
if (!headerParsed && type != "IHDR")
|
||||
throw new InvalidDataException("Invalid PNG file - header does not appear first.");
|
||||
@@ -68,8 +66,8 @@ namespace OpenRA.FileFormats
|
||||
Width = IPAddress.NetworkToHostOrder(ms.ReadInt32());
|
||||
Height = IPAddress.NetworkToHostOrder(ms.ReadInt32());
|
||||
|
||||
bitDepth = ms.ReadUInt8();
|
||||
var colorType = (PngColorType)ms.ReadUInt8();
|
||||
var bitDepth = ms.ReadUInt8();
|
||||
var colorType = (PngColorType)ms.ReadByte();
|
||||
if (IsPaletted(bitDepth, colorType))
|
||||
Type = SpriteFrameType.Indexed8;
|
||||
else if (colorType == PngColorType.Color)
|
||||
@@ -77,9 +75,9 @@ namespace OpenRA.FileFormats
|
||||
|
||||
Data = new byte[Width * Height * PixelStride];
|
||||
|
||||
var compression = ms.ReadUInt8();
|
||||
ms.ReadUInt8(); // filter
|
||||
var interlace = ms.ReadUInt8();
|
||||
var compression = ms.ReadByte();
|
||||
/*var filter = */ms.ReadByte();
|
||||
var interlace = ms.ReadByte();
|
||||
|
||||
if (compression != 0)
|
||||
throw new InvalidDataException("Compression method not supported");
|
||||
@@ -94,10 +92,10 @@ namespace OpenRA.FileFormats
|
||||
|
||||
case "PLTE":
|
||||
{
|
||||
Palette = new Color[length / 3];
|
||||
for (var i = 0; i < Palette.Length; i++)
|
||||
Palette = new Color[256];
|
||||
for (var i = 0; i < length / 3; i++)
|
||||
{
|
||||
var r = ms.ReadUInt8(); var g = ms.ReadUInt8(); var b = ms.ReadUInt8();
|
||||
var r = ms.ReadByte(); var g = ms.ReadByte(); var b = ms.ReadByte();
|
||||
Palette[i] = Color.FromArgb(r, g, b);
|
||||
}
|
||||
|
||||
@@ -110,7 +108,7 @@ namespace OpenRA.FileFormats
|
||||
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
Palette[i] = Color.FromArgb(ms.ReadUInt8(), Palette[i]);
|
||||
Palette[i] = Color.FromArgb(ms.ReadByte(), Palette[i]);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -138,77 +136,22 @@ namespace OpenRA.FileFormats
|
||||
{
|
||||
var pxStride = PixelStride;
|
||||
var rowStride = Width * pxStride;
|
||||
var pixelsPerByte = 8 / bitDepth;
|
||||
var sourceRowStride = Exts.IntegerDivisionRoundingAwayFromZero(rowStride, pixelsPerByte);
|
||||
|
||||
Span<byte> prevLine = new byte[rowStride];
|
||||
var prevLine = new byte[rowStride];
|
||||
for (var y = 0; y < Height; y++)
|
||||
{
|
||||
var filter = (PngFilter)ds.ReadUInt8();
|
||||
ds.ReadBytes(Data, y * rowStride, sourceRowStride);
|
||||
var line = Data.AsSpan(y * rowStride, rowStride);
|
||||
var filter = (PngFilter)ds.ReadByte();
|
||||
var line = ds.ReadBytes(rowStride);
|
||||
|
||||
// If the source has a bit depth of 1, 2 or 4 it packs multiple pixels per byte.
|
||||
// Unpack to bit depth of 8, yielding 1 pixel per byte.
|
||||
// This makes life easier for consumers of palleted data.
|
||||
if (bitDepth < 8)
|
||||
{
|
||||
var mask = 0xFF >> (8 - bitDepth);
|
||||
for (var i = sourceRowStride - 1; i >= 0; i--)
|
||||
{
|
||||
var packed = line[i];
|
||||
for (var j = 0; j < pixelsPerByte; j++)
|
||||
{
|
||||
var dest = i * pixelsPerByte + j;
|
||||
if (dest < line.Length) // Guard against last byte being only partially packed
|
||||
line[dest] = (byte)(packed >> (8 - (j + 1) * bitDepth) & mask);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < rowStride; i++)
|
||||
line[i] = i < pxStride
|
||||
? UnapplyFilter(filter, line[i], 0, prevLine[i], 0)
|
||||
: UnapplyFilter(filter, line[i], line[i - pxStride], prevLine[i], prevLine[i - pxStride]);
|
||||
|
||||
switch (filter)
|
||||
{
|
||||
case PngFilter.None:
|
||||
break;
|
||||
case PngFilter.Sub:
|
||||
for (var i = pxStride; i < rowStride; i++)
|
||||
line[i] += line[i - pxStride];
|
||||
break;
|
||||
case PngFilter.Up:
|
||||
for (var i = 0; i < rowStride; i++)
|
||||
line[i] += prevLine[i];
|
||||
break;
|
||||
case PngFilter.Average:
|
||||
for (var i = 0; i < pxStride; i++)
|
||||
line[i] += Average(0, prevLine[i]);
|
||||
for (var i = pxStride; i < rowStride; i++)
|
||||
line[i] += Average(line[i - pxStride], prevLine[i]);
|
||||
break;
|
||||
case PngFilter.Paeth:
|
||||
for (var i = 0; i < pxStride; i++)
|
||||
line[i] += Paeth(0, prevLine[i], 0);
|
||||
for (var i = pxStride; i < rowStride; i++)
|
||||
line[i] += Paeth(line[i - pxStride], prevLine[i], prevLine[i - pxStride]);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("Unsupported Filter");
|
||||
}
|
||||
Array.Copy(line, 0, Data, y * rowStride, rowStride);
|
||||
|
||||
prevLine = line;
|
||||
}
|
||||
|
||||
static byte Average(byte a, byte b) => (byte)((a + b) / 2);
|
||||
|
||||
static byte Paeth(byte a, byte b, byte c)
|
||||
{
|
||||
var p = a + b - c;
|
||||
var pa = Math.Abs(p - a);
|
||||
var pb = Math.Abs(p - b);
|
||||
var pc = Math.Abs(p - c);
|
||||
|
||||
return (pa <= pb && pa <= pc) ? a :
|
||||
(pb <= pc) ? b : c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,13 +228,38 @@ namespace OpenRA.FileFormats
|
||||
return isPng;
|
||||
}
|
||||
|
||||
static byte UnapplyFilter(PngFilter f, byte x, byte a, byte b, byte c)
|
||||
{
|
||||
switch (f)
|
||||
{
|
||||
case PngFilter.None: return x;
|
||||
case PngFilter.Sub: return (byte)(x + a);
|
||||
case PngFilter.Up: return (byte)(x + b);
|
||||
case PngFilter.Average: return (byte)(x + (a + b) / 2);
|
||||
case PngFilter.Paeth: return (byte)(x + Paeth(a, b, c));
|
||||
default:
|
||||
throw new InvalidOperationException("Unsupported Filter");
|
||||
}
|
||||
}
|
||||
|
||||
static byte Paeth(byte a, byte b, byte c)
|
||||
{
|
||||
var p = a + b - c;
|
||||
var pa = Math.Abs(p - a);
|
||||
var pb = Math.Abs(p - b);
|
||||
var pc = Math.Abs(p - c);
|
||||
|
||||
return (pa <= pb && pa <= pc) ? a :
|
||||
(pb <= pc) ? b : c;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum PngColorType : byte { Indexed = 1, Color = 2, Alpha = 4 }
|
||||
enum PngFilter : byte { None, Sub, Up, Average, Paeth }
|
||||
enum PngColorType { Indexed = 1, Color = 2, Alpha = 4 }
|
||||
enum PngFilter { None, Sub, Up, Average, Paeth }
|
||||
|
||||
static bool IsPaletted(byte bitDepth, PngColorType colorType)
|
||||
{
|
||||
if (bitDepth <= 8 && colorType == (PngColorType.Indexed | PngColorType.Color))
|
||||
if (bitDepth == 8 && colorType == (PngColorType.Indexed | PngColorType.Color))
|
||||
return true;
|
||||
|
||||
if (bitDepth == 8 && colorType == (PngColorType.Color | PngColorType.Alpha))
|
||||
@@ -303,16 +271,16 @@ namespace OpenRA.FileFormats
|
||||
throw new InvalidDataException("Unknown pixel format");
|
||||
}
|
||||
|
||||
static void WritePngChunk(Stream output, string type, Stream input)
|
||||
void WritePngChunk(Stream output, string type, Stream input)
|
||||
{
|
||||
input.Position = 0;
|
||||
|
||||
var typeBytes = Encoding.ASCII.GetBytes(type);
|
||||
output.Write(IPAddress.HostToNetworkOrder((int)input.Length));
|
||||
output.Write(typeBytes);
|
||||
output.WriteArray(typeBytes);
|
||||
|
||||
var data = input.ReadAllBytes();
|
||||
output.Write(data);
|
||||
output.WriteArray(data);
|
||||
|
||||
var crc32 = new Crc32();
|
||||
crc32.Update(typeBytes);
|
||||
@@ -324,7 +292,7 @@ namespace OpenRA.FileFormats
|
||||
{
|
||||
using (var output = new MemoryStream())
|
||||
{
|
||||
output.Write(Signature);
|
||||
output.WriteArray(Signature);
|
||||
using (var header = new MemoryStream())
|
||||
{
|
||||
header.Write(IPAddress.HostToNetworkOrder(Width));
|
||||
@@ -372,14 +340,13 @@ namespace OpenRA.FileFormats
|
||||
|
||||
using (var data = new MemoryStream())
|
||||
{
|
||||
using (var compressed = new DeflaterOutputStream(data, new Deflater(Deflater.BEST_COMPRESSION)))
|
||||
using (var compressed = new DeflaterOutputStream(data))
|
||||
{
|
||||
var rowStride = Width * PixelStride;
|
||||
for (var y = 0; y < Height; y++)
|
||||
{
|
||||
// Assuming no filtering for simplicity
|
||||
const byte FilterType = 0;
|
||||
compressed.WriteByte(FilterType);
|
||||
// Write uncompressed scanlines for simplicity
|
||||
compressed.WriteByte(0);
|
||||
compressed.Write(Data, y * rowStride, rowStride);
|
||||
}
|
||||
|
||||
@@ -394,7 +361,7 @@ namespace OpenRA.FileFormats
|
||||
{
|
||||
using (var text = new MemoryStream())
|
||||
{
|
||||
text.Write(Encoding.ASCII.GetBytes(kv.Key + (char)0 + kv.Value));
|
||||
text.WriteArray(Encoding.ASCII.GetBytes(kv.Key + (char)0 + kv.Value));
|
||||
WritePngChunk(output, "tEXt", text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,8 +47,8 @@ namespace OpenRA.FileFormats
|
||||
throw new NotSupportedException($"Metadata version {version} is not supported");
|
||||
|
||||
// Read game info (max 100K limit as a safeguard against corrupted files)
|
||||
var data = fs.ReadLengthPrefixedString(Encoding.UTF8, 1024 * 100);
|
||||
GameInfo = GameInformation.Deserialize(data, path);
|
||||
var data = fs.ReadString(Encoding.UTF8, 1024 * 100);
|
||||
GameInfo = GameInformation.Deserialize(data);
|
||||
}
|
||||
|
||||
public void Write(BinaryWriter writer)
|
||||
@@ -62,7 +62,7 @@ namespace OpenRA.FileFormats
|
||||
{
|
||||
// Write lobby info data
|
||||
writer.Flush();
|
||||
dataLength += writer.BaseStream.WriteLengthPrefixedString(Encoding.UTF8, GameInfo.Serialize());
|
||||
dataLength += writer.BaseStream.WriteString(Encoding.UTF8, GameInfo.Serialize());
|
||||
}
|
||||
|
||||
// Write total length & end marker
|
||||
|
||||
@@ -83,14 +83,14 @@ namespace OpenRA.FileSystem
|
||||
|
||||
public void Mount(string name, string explicitName = null)
|
||||
{
|
||||
var optional = name.StartsWith('~');
|
||||
var optional = name.StartsWith("~", StringComparison.Ordinal);
|
||||
if (optional)
|
||||
name = name[1..];
|
||||
|
||||
try
|
||||
{
|
||||
IReadOnlyPackage package;
|
||||
if (name.StartsWith('$'))
|
||||
if (name.StartsWith("$", StringComparison.Ordinal))
|
||||
{
|
||||
name = name[1..];
|
||||
|
||||
@@ -109,8 +109,10 @@ namespace OpenRA.FileSystem
|
||||
|
||||
Mount(package, explicitName);
|
||||
}
|
||||
catch when (optional)
|
||||
catch
|
||||
{
|
||||
if (!optional)
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +161,9 @@ namespace OpenRA.FileSystem
|
||||
explicitMounts.Remove(key);
|
||||
|
||||
// Mod packages aren't owned by us, so we shouldn't dispose them
|
||||
if (!modPackages.Remove(package))
|
||||
if (modPackages.Contains(package))
|
||||
modPackages.Remove(package);
|
||||
else
|
||||
package.Dispose();
|
||||
}
|
||||
else
|
||||
@@ -186,12 +190,6 @@ namespace OpenRA.FileSystem
|
||||
UnmountAll();
|
||||
foreach (var kv in manifest.Packages)
|
||||
Mount(kv.Key, kv.Value);
|
||||
|
||||
mountedPackages.TrimExcess();
|
||||
explicitMounts.TrimExcess();
|
||||
modPackages.TrimExcess();
|
||||
foreach (var packages in fileIndex.Values)
|
||||
packages.TrimExcess();
|
||||
}
|
||||
|
||||
Stream GetFromCache(string filename)
|
||||
@@ -228,11 +226,14 @@ namespace OpenRA.FileSystem
|
||||
public bool TryOpen(string filename, out Stream s)
|
||||
{
|
||||
var explicitSplit = filename.IndexOf('|');
|
||||
if (explicitSplit > 0 && explicitMounts.TryGetValue(filename[..explicitSplit], out var explicitPackage))
|
||||
if (explicitSplit > 0)
|
||||
{
|
||||
s = explicitPackage.GetStream(filename[(explicitSplit + 1)..]);
|
||||
if (s != null)
|
||||
return true;
|
||||
if (explicitMounts.TryGetValue(filename[..explicitSplit], out var explicitPackage))
|
||||
{
|
||||
s = explicitPackage.GetStream(filename[(explicitSplit + 1)..]);
|
||||
if (s != null)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
s = GetFromCache(filename);
|
||||
@@ -261,10 +262,10 @@ namespace OpenRA.FileSystem
|
||||
public bool Exists(string filename)
|
||||
{
|
||||
var explicitSplit = filename.IndexOf('|');
|
||||
if (explicitSplit > 0 &&
|
||||
explicitMounts.TryGetValue(filename[..explicitSplit], out var explicitPackage) &&
|
||||
explicitPackage.Contains(filename[(explicitSplit + 1)..]))
|
||||
return true;
|
||||
if (explicitSplit > 0)
|
||||
if (explicitMounts.TryGetValue(filename[..explicitSplit], out var explicitPackage))
|
||||
if (explicitPackage.Contains(filename[(explicitSplit + 1)..]))
|
||||
return true;
|
||||
|
||||
return fileIndex.ContainsKey(filename);
|
||||
}
|
||||
@@ -294,7 +295,7 @@ namespace OpenRA.FileSystem
|
||||
public static string ResolveAssemblyPath(string path, Manifest manifest, InstalledMods installedMods)
|
||||
{
|
||||
var explicitSplit = path.IndexOf('|');
|
||||
if (explicitSplit > 0 && !path.StartsWith('^'))
|
||||
if (explicitSplit > 0 && !path.StartsWith("^"))
|
||||
{
|
||||
var parent = path[..explicitSplit];
|
||||
var filename = path[(explicitSplit + 1)..];
|
||||
@@ -303,7 +304,7 @@ namespace OpenRA.FileSystem
|
||||
if (parentPath == null)
|
||||
return null;
|
||||
|
||||
if (parentPath.StartsWith('$'))
|
||||
if (parentPath.StartsWith("$", StringComparison.Ordinal))
|
||||
{
|
||||
if (!installedMods.TryGetValue(parentPath[1..], out var mod))
|
||||
return null;
|
||||
|
||||
@@ -41,11 +41,7 @@ namespace OpenRA.FileSystem
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
var combined = Path.Combine(Name, filename);
|
||||
if (!File.Exists(combined))
|
||||
return null;
|
||||
|
||||
try { return File.OpenRead(combined); }
|
||||
try { return File.OpenRead(Path.Combine(Name, filename)); }
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
@@ -84,7 +80,7 @@ namespace OpenRA.FileSystem
|
||||
// in FileSystem.OpenPackage. Their internal name therefore contains the
|
||||
// full parent path too. We need to be careful to not add a second path
|
||||
// prefix to these hacked packages.
|
||||
var filePath = filename.StartsWith(Name, StringComparison.Ordinal) ? filename : Path.Combine(Name, filename);
|
||||
var filePath = filename.StartsWith(Name) ? filename : Path.Combine(Name, filename);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
|
||||
using (var s = File.Create(filePath))
|
||||
@@ -98,7 +94,7 @@ namespace OpenRA.FileSystem
|
||||
// in FileSystem.OpenPackage. Their internal name therefore contains the
|
||||
// full parent path too. We need to be careful to not add a second path
|
||||
// prefix to these hacked packages.
|
||||
var filePath = filename.StartsWith(Name, StringComparison.Ordinal) ? filename : Path.Combine(Name, filename);
|
||||
var filePath = filename.StartsWith(Name) ? filename : Path.Combine(Name, filename);
|
||||
if (Directory.Exists(filePath))
|
||||
Directory.Delete(filePath, true);
|
||||
else if (File.Exists(filePath))
|
||||
|
||||
@@ -19,9 +19,9 @@ namespace OpenRA.FileSystem
|
||||
{
|
||||
public class ZipFileLoader : IPackageLoader
|
||||
{
|
||||
const uint ZipSignature = 0x04034b50;
|
||||
static readonly uint ZipSignature = 0x04034b50;
|
||||
|
||||
public class ReadOnlyZipFile : IReadOnlyPackage
|
||||
class ReadOnlyZipFile : IReadOnlyPackage
|
||||
{
|
||||
public string Name { get; protected set; }
|
||||
protected ZipFile pkg;
|
||||
@@ -68,7 +68,6 @@ namespace OpenRA.FileSystem
|
||||
public void Dispose()
|
||||
{
|
||||
pkg?.Close();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
|
||||
@@ -94,7 +93,7 @@ namespace OpenRA.FileSystem
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ReadWriteZipFile : ReadOnlyZipFile, IReadWritePackage
|
||||
sealed class ReadWriteZipFile : ReadOnlyZipFile, IReadWritePackage
|
||||
{
|
||||
readonly MemoryStream pkgStream = new();
|
||||
|
||||
@@ -118,7 +117,10 @@ namespace OpenRA.FileSystem
|
||||
|
||||
void Commit()
|
||||
{
|
||||
File.WriteAllBytes(Name, pkgStream.ToArray());
|
||||
var pos = pkgStream.Position;
|
||||
pkgStream.Position = 0;
|
||||
File.WriteAllBytes(Name, pkgStream.ReadBytes((int)pkgStream.Length));
|
||||
pkgStream.Position = pos;
|
||||
}
|
||||
|
||||
public void Update(string filename, byte[] contents)
|
||||
@@ -145,7 +147,7 @@ namespace OpenRA.FileSystem
|
||||
|
||||
public ZipFolder(ReadOnlyZipFile parent, string path)
|
||||
{
|
||||
if (path.EndsWith('/'))
|
||||
if (path.EndsWith("/", StringComparison.Ordinal))
|
||||
path = path[..^1];
|
||||
|
||||
Name = path;
|
||||
@@ -188,7 +190,7 @@ namespace OpenRA.FileSystem
|
||||
public void Dispose() { /* nothing to do */ }
|
||||
}
|
||||
|
||||
sealed class StaticStreamDataSource : IStaticDataSource
|
||||
class StaticStreamDataSource : IStaticDataSource
|
||||
{
|
||||
readonly Stream s;
|
||||
public StaticStreamDataSource(Stream s)
|
||||
|
||||
@@ -86,9 +86,8 @@ namespace OpenRA
|
||||
|
||||
static void JoinInner(OrderManager om)
|
||||
{
|
||||
// Refresh static classes before the game starts.
|
||||
// Refresh TextNotificationsManager before the game starts.
|
||||
TextNotificationsManager.Clear();
|
||||
UnitOrders.Clear();
|
||||
|
||||
// HACK: The shellmap World and OrderManager are owned by the main menu's WorldRenderer instead of Game.
|
||||
// This allows us to switch Game.OrderManager from the shellmap to the new network connection when joining
|
||||
@@ -180,7 +179,6 @@ namespace OpenRA
|
||||
}
|
||||
|
||||
public static event Action BeforeGameStart = () => { };
|
||||
public static event Action AfterGameStart = () => { };
|
||||
internal static void StartGame(string mapUID, WorldType type)
|
||||
{
|
||||
// Dispose of the old world before creating a new one.
|
||||
@@ -224,12 +222,6 @@ namespace OpenRA
|
||||
// Much better to clean up now then to drop frames during gameplay for GC pauses.
|
||||
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
|
||||
GC.Collect();
|
||||
|
||||
// PostLoadComplete is designed for anything that should trigger at the very end of loading.
|
||||
// e.g. audio notifications that the game is starting.
|
||||
OrderManager.World.PostLoadComplete(worldRenderer);
|
||||
|
||||
AfterGameStart();
|
||||
}
|
||||
|
||||
public static void RestartGame()
|
||||
@@ -423,7 +415,7 @@ namespace OpenRA
|
||||
|
||||
// Sanitize input from platform-specific launchers
|
||||
// Process.Start requires paths to not be quoted, even if they contain spaces
|
||||
if (launchPath != null && launchPath[0] == '"' && launchPath.Last() == '"')
|
||||
if (launchPath != null && launchPath.First() == '"' && launchPath.Last() == '"')
|
||||
launchPath = launchPath[1..^1];
|
||||
|
||||
// Metadata registration requires an explicit launch path
|
||||
@@ -531,11 +523,10 @@ namespace OpenRA
|
||||
.Where(m => m.Status == MapStatus.Available && m.Visibility.HasFlag(MapVisibility.Shellmap))
|
||||
.Select(m => m.Uid);
|
||||
|
||||
var shellmap = shellmaps.RandomOrDefault(CosmeticRandom);
|
||||
if (shellmap == null)
|
||||
if (!shellmaps.Any())
|
||||
throw new InvalidDataException("No valid shellmaps available");
|
||||
|
||||
return shellmap;
|
||||
return shellmaps.Random(CosmeticRandom);
|
||||
}
|
||||
|
||||
public static void SwitchToExternalMod(ExternalMod mod, string[] launchArguments = null, Action onFailed = null)
|
||||
@@ -590,7 +581,7 @@ namespace OpenRA
|
||||
Log.Write("debug", "Taking screenshot " + path);
|
||||
|
||||
Renderer.SaveScreenshot(path);
|
||||
TextNotificationsManager.Debug(TranslationProvider.GetString(SavedScreenshot, Translation.Arguments("filename", filename)));
|
||||
TextNotificationsManager.Debug(ModData.Translation.GetString(SavedScreenshot, Translation.Arguments("filename", filename)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -609,10 +600,7 @@ namespace OpenRA
|
||||
|
||||
if (orderManager.LastTickTime.ShouldAdvance(tick))
|
||||
{
|
||||
if (orderManager.GameStarted && orderManager.LocalFrameNumber == 0)
|
||||
PerfHistory.Reset(); // Remove history that occurred whilst the new game was loading.
|
||||
|
||||
using (var sample = new PerfSample("tick_time"))
|
||||
using (new PerfSample("tick_time"))
|
||||
{
|
||||
orderManager.LastTickTime.AdvanceTickTime(tick);
|
||||
|
||||
@@ -621,11 +609,7 @@ namespace OpenRA
|
||||
Sync.RunUnsynced(world, orderManager.TickImmediate);
|
||||
|
||||
if (world == null)
|
||||
{
|
||||
if (orderManager.GameStarted)
|
||||
PerfHistory.Reset(); // Remove old history when a new game starts.
|
||||
return;
|
||||
}
|
||||
|
||||
if (orderManager.TryTick())
|
||||
{
|
||||
@@ -679,7 +663,7 @@ namespace OpenRA
|
||||
// Prepare renderables (i.e. render voxels) before calling BeginFrame
|
||||
using (new PerfSample("render_prepare"))
|
||||
{
|
||||
worldRenderer?.BeginFrame();
|
||||
Renderer.WorldModelRenderer.BeginFrame();
|
||||
|
||||
// World rendering is disabled while the loading screen is displayed
|
||||
if (worldRenderer != null && !worldRenderer.World.IsLoadingGameSave)
|
||||
@@ -689,7 +673,7 @@ namespace OpenRA
|
||||
}
|
||||
|
||||
Ui.PrepareRenderables();
|
||||
worldRenderer?.EndFrame();
|
||||
Renderer.WorldModelRenderer.EndFrame();
|
||||
}
|
||||
|
||||
// worldRenderer is null during the initial install/download screen
|
||||
@@ -793,7 +777,7 @@ namespace OpenRA
|
||||
var logicWorld = worldRenderer?.World;
|
||||
|
||||
// ReplayTimestep = 0 means the replay is paused: we need to keep logicInterval as UI.Timestep to avoid breakage
|
||||
if (logicWorld != null && (!logicWorld.IsReplay || logicWorld.ReplayTimestep != 0))
|
||||
if (logicWorld != null && !(logicWorld.IsReplay && logicWorld.ReplayTimestep == 0))
|
||||
logicInterval = logicWorld == OrderManager.World ? OrderManager.SuggestedTimestep : logicWorld.Timestep;
|
||||
|
||||
// Ideal time between screen updates
|
||||
@@ -928,15 +912,15 @@ namespace OpenRA
|
||||
{
|
||||
var endpoints = new List<IPEndPoint>
|
||||
{
|
||||
new(IPAddress.IPv6Any, settings.ListenPort),
|
||||
new(IPAddress.Any, settings.ListenPort)
|
||||
new IPEndPoint(IPAddress.IPv6Any, settings.ListenPort),
|
||||
new IPEndPoint(IPAddress.Any, settings.ListenPort)
|
||||
};
|
||||
server = new Server.Server(endpoints, settings, ModData, ServerType.Multiplayer);
|
||||
|
||||
return server.GetEndpointForLocalConnection();
|
||||
}
|
||||
|
||||
public static ConnectionTarget CreateLocalServer(string map, bool isSkirmish = false)
|
||||
public static ConnectionTarget CreateLocalServer(string map)
|
||||
{
|
||||
var settings = new ServerSettings()
|
||||
{
|
||||
@@ -950,9 +934,9 @@ namespace OpenRA
|
||||
// This would break the Restart button, which relies on the PlayerIndex always being the same for local servers
|
||||
var endpoints = new List<IPEndPoint>
|
||||
{
|
||||
new(IPAddress.Loopback, 0)
|
||||
new IPEndPoint(IPAddress.Loopback, 0)
|
||||
};
|
||||
server = new Server.Server(endpoints, settings, ModData, isSkirmish ? ServerType.Skirmish : ServerType.Local);
|
||||
server = new Server.Server(endpoints, settings, ModData, ServerType.Local);
|
||||
|
||||
return server.GetEndpointForLocalConnection();
|
||||
}
|
||||
|
||||
@@ -49,13 +49,13 @@ namespace OpenRA
|
||||
playersByRuntime = new Dictionary<OpenRA.Player, Player>();
|
||||
}
|
||||
|
||||
public static GameInformation Deserialize(string data, string path)
|
||||
public static GameInformation Deserialize(string data)
|
||||
{
|
||||
try
|
||||
{
|
||||
var info = new GameInformation();
|
||||
|
||||
var nodes = MiniYaml.FromString(data, path);
|
||||
var nodes = MiniYaml.FromString(data);
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
var keyParts = node.Key.Split('@');
|
||||
@@ -85,7 +85,7 @@ namespace OpenRA
|
||||
{
|
||||
var nodes = new List<MiniYamlNode>
|
||||
{
|
||||
new("Root", FieldSaver.Save(this))
|
||||
new MiniYamlNode("Root", FieldSaver.Save(this))
|
||||
};
|
||||
|
||||
for (var i = 0; i < Players.Count; i++)
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace OpenRA
|
||||
/// </summary>
|
||||
public class ActorInfo
|
||||
{
|
||||
public const char AbstractActorPrefix = '^';
|
||||
public const string AbstractActorPrefix = "^";
|
||||
public const char TraitInstanceSeparator = '@';
|
||||
|
||||
/// <summary>
|
||||
@@ -130,7 +130,6 @@ namespace OpenRA
|
||||
|
||||
// Continue resolving traits as long as possible.
|
||||
// Each time we resolve some traits, this means dependencies for other traits may then be possible to satisfy in the next pass.
|
||||
#pragma warning disable CA1851 // Possible multiple enumerations of 'IEnumerable' collection
|
||||
var readyToResolve = more.ToList();
|
||||
while (readyToResolve.Count != 0)
|
||||
{
|
||||
@@ -139,30 +138,29 @@ namespace OpenRA
|
||||
readyToResolve.Clear();
|
||||
readyToResolve.AddRange(more);
|
||||
}
|
||||
#pragma warning restore CA1851
|
||||
|
||||
if (unresolved.Count != 0)
|
||||
{
|
||||
var exceptionString = "ActorInfo(\"" + Name + "\") failed to initialize because of the following:\n";
|
||||
var exceptionString = "ActorInfo(\"" + Name + "\") failed to initialize because of the following:\r\n";
|
||||
var missing = unresolved.SelectMany(u => u.Dependencies.Where(d => !source.Any(s => AreResolvable(d, s.Type)))).Distinct();
|
||||
|
||||
exceptionString += "Missing:\n";
|
||||
exceptionString += "Missing:\r\n";
|
||||
foreach (var m in missing)
|
||||
exceptionString += m + " \n";
|
||||
exceptionString += m + " \r\n";
|
||||
|
||||
exceptionString += "Unresolved:\n";
|
||||
exceptionString += "Unresolved:\r\n";
|
||||
foreach (var u in unresolved)
|
||||
{
|
||||
var deps = u.Dependencies.Where(d => !resolved.Exists(r => r.Type == d));
|
||||
var optDeps = u.OptionalDependencies.Where(d => !resolved.Exists(r => r.Type == d));
|
||||
var allDeps = string.Join(", ", deps.Select(o => o.ToString()).Concat(optDeps.Select(o => $"[{o}]")));
|
||||
exceptionString += $"{u.Type}: {{ {allDeps} }}\n";
|
||||
exceptionString += $"{u.Type}: {{ {allDeps} }}\r\n";
|
||||
}
|
||||
|
||||
throw new YamlException(exceptionString);
|
||||
}
|
||||
|
||||
constructOrderCache = resolved.ConvertAll(r => r.Trait);
|
||||
constructOrderCache = resolved.Select(r => r.Trait).ToList();
|
||||
return constructOrderCache;
|
||||
}
|
||||
|
||||
@@ -187,7 +185,7 @@ namespace OpenRA
|
||||
public bool HasTraitInfo<T>() where T : ITraitInfoInterface { return traits.Contains<T>(); }
|
||||
public T TraitInfo<T>() where T : ITraitInfoInterface { return traits.Get<T>(); }
|
||||
public T TraitInfoOrDefault<T>() where T : ITraitInfoInterface { return traits.GetOrDefault<T>(); }
|
||||
public IReadOnlyCollection<T> TraitInfos<T>() where T : ITraitInfoInterface { return traits.WithInterface<T>(); }
|
||||
public IEnumerable<T> TraitInfos<T>() where T : ITraitInfoInterface { return traits.WithInterface<T>(); }
|
||||
|
||||
public BitSet<TargetableType> GetAllTargetTypes()
|
||||
{
|
||||
|
||||
@@ -28,14 +28,14 @@ namespace OpenRA.GameRules
|
||||
Title = value.Value;
|
||||
|
||||
var nd = value.ToDictionary();
|
||||
if (nd.TryGetValue("Hidden", out var yaml))
|
||||
bool.TryParse(yaml.Value, out Hidden);
|
||||
if (nd.ContainsKey("Hidden"))
|
||||
bool.TryParse(nd["Hidden"].Value, out Hidden);
|
||||
|
||||
if (nd.TryGetValue("VolumeModifier", out yaml))
|
||||
VolumeModifier = FieldLoader.GetValue<float>("VolumeModifier", yaml.Value);
|
||||
if (nd.ContainsKey("VolumeModifier"))
|
||||
VolumeModifier = FieldLoader.GetValue<float>("VolumeModifier", nd["VolumeModifier"].Value);
|
||||
|
||||
var ext = nd.TryGetValue("Extension", out yaml) ? yaml.Value : "aud";
|
||||
Filename = (nd.TryGetValue("Filename", out yaml) ? yaml.Value : key) + "." + ext;
|
||||
var ext = nd.ContainsKey("Extension") ? nd["Extension"].Value : "aud";
|
||||
Filename = (nd.ContainsKey("Filename") ? nd["Filename"].Value : key) + "." + ext;
|
||||
}
|
||||
|
||||
public void Load(IReadOnlyFileSystem fileSystem)
|
||||
|
||||
@@ -124,7 +124,7 @@ namespace OpenRA
|
||||
{
|
||||
var actors = MergeOrDefault("Manifest,Rules", fs, m.Rules, null, null,
|
||||
k => new ActorInfo(modData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value),
|
||||
filterNode: n => n.Key.StartsWith(ActorInfo.AbstractActorPrefix));
|
||||
filterNode: n => n.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal));
|
||||
|
||||
var weapons = MergeOrDefault("Manifest,Weapons", fs, m.Weapons, null, null,
|
||||
k => new WeaponInfo(k.Value));
|
||||
@@ -182,7 +182,7 @@ namespace OpenRA
|
||||
{
|
||||
var actors = MergeOrDefault("Rules", fileSystem, m.Rules, mapRules, dr.Actors,
|
||||
k => new ActorInfo(modData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value),
|
||||
filterNode: n => n.Key.StartsWith(ActorInfo.AbstractActorPrefix));
|
||||
filterNode: n => n.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal));
|
||||
|
||||
var weapons = MergeOrDefault("Weapons", fileSystem, m.Weapons, mapWeapons, dr.Weapons,
|
||||
k => new WeaponInfo(k.Value));
|
||||
@@ -226,10 +226,10 @@ namespace OpenRA
|
||||
|
||||
static bool AnyCustomYaml(MiniYaml yaml)
|
||||
{
|
||||
return yaml != null && (yaml.Value != null || yaml.Nodes.Length > 0);
|
||||
return yaml != null && (yaml.Value != null || yaml.Nodes.Count > 0);
|
||||
}
|
||||
|
||||
static bool AnyFlaggedTraits(ModData modData, IEnumerable<MiniYamlNode> actors)
|
||||
static bool AnyFlaggedTraits(ModData modData, List<MiniYamlNode> actors)
|
||||
{
|
||||
foreach (var actorNode in actors)
|
||||
{
|
||||
@@ -244,7 +244,7 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Write("debug", "Error in AnyFlaggedTraits\n" + ex.ToString());
|
||||
Log.Write("debug", "Error in AnyFlaggedTraits\r\n" + ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,18 +260,18 @@ namespace OpenRA
|
||||
return true;
|
||||
|
||||
// Any trait overrides that aren't explicitly whitelisted are flagged
|
||||
if (mapRules == null)
|
||||
return false;
|
||||
|
||||
if (AnyFlaggedTraits(modData, mapRules.Nodes))
|
||||
return true;
|
||||
|
||||
if (mapRules.Value != null)
|
||||
if (mapRules != null)
|
||||
{
|
||||
var mapFiles = FieldLoader.GetValue<string[]>("value", mapRules.Value);
|
||||
foreach (var f in mapFiles)
|
||||
if (AnyFlaggedTraits(modData, MiniYaml.FromStream(fileSystem.Open(f), f)))
|
||||
return true;
|
||||
if (AnyFlaggedTraits(modData, mapRules.Nodes))
|
||||
return true;
|
||||
|
||||
if (mapRules.Value != null)
|
||||
{
|
||||
var mapFiles = FieldLoader.GetValue<string[]>("value", mapRules.Value);
|
||||
foreach (var f in mapFiles)
|
||||
if (AnyFlaggedTraits(modData, MiniYaml.FromStream(fileSystem.Open(f), f)))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -40,16 +40,16 @@ namespace OpenRA.GameRules
|
||||
static Dictionary<string, SoundPool> ParseSoundPool(MiniYaml y, string key)
|
||||
{
|
||||
var ret = new Dictionary<string, SoundPool>();
|
||||
var classifiction = y.NodeWithKey(key);
|
||||
var classifiction = y.Nodes.First(x => x.Key == key);
|
||||
foreach (var t in classifiction.Value.Nodes)
|
||||
{
|
||||
var volumeModifier = 1f;
|
||||
var volumeModifierNode = t.Value.NodeWithKeyOrDefault(nameof(SoundPool.VolumeModifier));
|
||||
var volumeModifierNode = t.Value.Nodes.FirstOrDefault(x => x.Key == nameof(SoundPool.VolumeModifier));
|
||||
if (volumeModifierNode != null)
|
||||
volumeModifier = FieldLoader.GetValue<float>(volumeModifierNode.Key, volumeModifierNode.Value.Value);
|
||||
|
||||
var interruptType = SoundPool.DefaultInterruptType;
|
||||
var interruptTypeNode = t.Value.NodeWithKeyOrDefault(nameof(SoundPool.InterruptType));
|
||||
var interruptTypeNode = t.Value.Nodes.FirstOrDefault(x => x.Key == nameof(SoundPool.InterruptType));
|
||||
if (interruptTypeNode != null)
|
||||
interruptType = FieldLoader.GetValue<SoundPool.InterruptType>(interruptTypeNode.Key, interruptTypeNode.Value.Value);
|
||||
|
||||
|
||||
@@ -139,14 +139,13 @@ namespace OpenRA.GameRules
|
||||
{
|
||||
// Resolve any weapon-level yaml inheritance or removals
|
||||
// HACK: The "Defaults" sequence syntax prevents us from doing this generally during yaml parsing
|
||||
content = content.WithNodes(MiniYaml.Merge(new IReadOnlyCollection<MiniYamlNode>[] { content.Nodes }));
|
||||
content.Nodes = MiniYaml.Merge(new[] { content.Nodes });
|
||||
FieldLoader.Load(this, content);
|
||||
}
|
||||
|
||||
static object LoadProjectile(MiniYaml yaml)
|
||||
{
|
||||
var proj = yaml.NodeWithKeyOrDefault("Projectile")?.Value;
|
||||
if (proj == null)
|
||||
if (!yaml.ToDictionary().TryGetValue("Projectile", out var proj))
|
||||
return null;
|
||||
|
||||
var ret = Game.CreateObject<IProjectileInfo>(proj.Value + "Info");
|
||||
@@ -160,7 +159,7 @@ namespace OpenRA.GameRules
|
||||
static object LoadWarheads(MiniYaml yaml)
|
||||
{
|
||||
var retList = new List<IWarhead>();
|
||||
foreach (var node in yaml.Nodes.Where(n => n.Key.StartsWith("Warhead", StringComparison.Ordinal)))
|
||||
foreach (var node in yaml.Nodes.Where(n => n.Key.StartsWith("Warhead")))
|
||||
{
|
||||
var ret = Game.CreateObject<IWarhead>(node.Value.Value + "Warhead");
|
||||
if (ret == null)
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
@@ -37,7 +38,7 @@ namespace OpenRA
|
||||
static object LoadSpeeds(MiniYaml y)
|
||||
{
|
||||
var ret = new Dictionary<string, GameSpeed>();
|
||||
var speedsNode = y.NodeWithKeyOrDefault("Speeds");
|
||||
var speedsNode = y.Nodes.FirstOrDefault(n => n.Key == "Speeds");
|
||||
if (speedsNode == null)
|
||||
throw new YamlException("Error parsing GameSpeeds: Missing Speeds node!");
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace OpenRA.Graphics
|
||||
bool backwards;
|
||||
bool tickAlways;
|
||||
int timeUntilNextFrame;
|
||||
Action tickFunc;
|
||||
Action tickFunc = () => { };
|
||||
|
||||
public Animation(World world, string name)
|
||||
: this(world, name, () => WAngle.Zero) { }
|
||||
@@ -164,7 +164,7 @@ namespace OpenRA.Graphics
|
||||
if (frame >= CurrentSequence.Length)
|
||||
{
|
||||
frame = CurrentSequence.Length - 1;
|
||||
tickFunc = null;
|
||||
tickFunc = () => { };
|
||||
after?.Invoke();
|
||||
}
|
||||
};
|
||||
@@ -212,13 +212,13 @@ namespace OpenRA.Graphics
|
||||
public void Tick(int t)
|
||||
{
|
||||
if (tickAlways)
|
||||
tickFunc?.Invoke();
|
||||
tickFunc();
|
||||
else
|
||||
{
|
||||
timeUntilNextFrame -= t;
|
||||
while (timeUntilNextFrame <= 0)
|
||||
{
|
||||
tickFunc?.Invoke();
|
||||
tickFunc();
|
||||
timeUntilNextFrame += CurrentSequenceTickOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,12 +77,11 @@ namespace OpenRA.Graphics
|
||||
cachedPanelSprites = new Dictionary<string, Sprite[]>();
|
||||
cachedCollectionSheets = new Dictionary<Collection, (Sheet, int)>();
|
||||
|
||||
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
||||
var chrome = MiniYaml.Merge(modData.Manifest.Chrome
|
||||
.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s, stringPool: stringPool)));
|
||||
.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s)));
|
||||
|
||||
foreach (var c in chrome)
|
||||
if (!c.Key.StartsWith('^'))
|
||||
if (!c.Key.StartsWith("^", StringComparison.Ordinal))
|
||||
LoadCollection(c.Key, c.Value);
|
||||
}
|
||||
|
||||
@@ -235,7 +234,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
// PERF: We don't need to search for images if there are no definitions.
|
||||
// PERF: It's more efficient to send an empty array rather than an array of 9 nulls.
|
||||
if (collection.Regions.Count == 0)
|
||||
if (!collection.Regions.Any())
|
||||
return Array.Empty<Sprite>();
|
||||
|
||||
// Support manual definitions for unusual dialog layouts
|
||||
@@ -264,13 +263,13 @@ namespace OpenRA.Graphics
|
||||
|
||||
if (!collections.TryGetValue(collectionName, out var collection))
|
||||
{
|
||||
Log.Write("debug", $"Could not find collection '{collectionName}'");
|
||||
Log.Write("debug", "Could not find collection '{0}'", collectionName);
|
||||
return new Size(0, 0);
|
||||
}
|
||||
|
||||
if (collection.PanelRegion == null || collection.PanelRegion.Length != 8)
|
||||
{
|
||||
Log.Write("debug", $"Collection '{collectionName}' does not define a valid PanelRegion");
|
||||
Log.Write("debug", "Collection '{0}' does not define a valid PanelRegion", collectionName);
|
||||
return new Size(0, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
public sealed class CursorManager
|
||||
{
|
||||
sealed class Cursor
|
||||
class Cursor
|
||||
{
|
||||
public string Name;
|
||||
public int2 PaddedSize;
|
||||
|
||||
@@ -24,11 +24,10 @@ namespace OpenRA.Graphics
|
||||
public CursorProvider(ModData modData)
|
||||
{
|
||||
var fileSystem = modData.DefaultFileSystem;
|
||||
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
||||
var sequenceYaml = MiniYaml.Merge(modData.Manifest.Cursors.Select(
|
||||
s => MiniYaml.FromStream(fileSystem.Open(s), s, stringPool: stringPool)));
|
||||
s => MiniYaml.FromStream(fileSystem.Open(s), s)));
|
||||
|
||||
var cursorsYaml = new MiniYaml(null, sequenceYaml).NodeWithKey("Cursors").Value;
|
||||
var nodesDict = new MiniYaml(null, sequenceYaml).ToDictionary();
|
||||
|
||||
// Overwrite previous definitions if there are duplicates
|
||||
var pals = new Dictionary<string, IProvidesCursorPaletteInfo>();
|
||||
@@ -36,14 +35,14 @@ namespace OpenRA.Graphics
|
||||
if (p.Palette != null)
|
||||
pals[p.Palette] = p;
|
||||
|
||||
Palettes = cursorsYaml.Nodes.Select(n => n.Value.Value)
|
||||
Palettes = nodesDict["Cursors"].Nodes.Select(n => n.Value.Value)
|
||||
.Where(p => p != null)
|
||||
.Distinct()
|
||||
.ToDictionary(p => p, p => pals[p].ReadPalette(modData.DefaultFileSystem));
|
||||
|
||||
var frameCache = new FrameCache(fileSystem, modData.SpriteLoaders);
|
||||
var cursors = new Dictionary<string, CursorSequence>();
|
||||
foreach (var s in cursorsYaml.Nodes)
|
||||
foreach (var s in nodesDict["Cursors"].Nodes)
|
||||
foreach (var sequence in s.Value.Nodes)
|
||||
cursors.Add(sequence.Key, new CursorSequence(frameCache, sequence.Key, s.Key, s.Value.Value, sequence.Value));
|
||||
|
||||
|
||||
@@ -27,20 +27,19 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
var d = info.ToDictionary();
|
||||
|
||||
Start = Exts.ParseInt32Invariant(d["Start"].Value);
|
||||
Start = Exts.ParseIntegerInvariant(d["Start"].Value);
|
||||
Palette = palette;
|
||||
Name = name;
|
||||
|
||||
var cursorSprites = cache[cursorSrc];
|
||||
Frames = cursorSprites.Skip(Start).ToArray();
|
||||
|
||||
if ((d.TryGetValue("Length", out var yaml) && yaml.Value == "*") ||
|
||||
(d.TryGetValue("End", out yaml) && yaml.Value == "*"))
|
||||
if ((d.ContainsKey("Length") && d["Length"].Value == "*") || (d.ContainsKey("End") && d["End"].Value == "*"))
|
||||
Length = Frames.Length;
|
||||
else if (d.TryGetValue("Length", out yaml))
|
||||
Length = Exts.ParseInt32Invariant(yaml.Value);
|
||||
else if (d.TryGetValue("End", out yaml))
|
||||
Length = Exts.ParseInt32Invariant(yaml.Value) - Start;
|
||||
else if (d.ContainsKey("Length"))
|
||||
Length = Exts.ParseIntegerInvariant(d["Length"].Value);
|
||||
else if (d.ContainsKey("End"))
|
||||
Length = Exts.ParseIntegerInvariant(d["End"].Value) - Start;
|
||||
else
|
||||
Length = 1;
|
||||
|
||||
@@ -52,15 +51,15 @@ namespace OpenRA.Graphics
|
||||
if (Length > cursorSprites.Length)
|
||||
throw new YamlException($"Cursor {name}: {nameof(Length)} is greater than the length of the sprite sequence.");
|
||||
|
||||
if (d.TryGetValue("X", out yaml))
|
||||
if (d.ContainsKey("X"))
|
||||
{
|
||||
Exts.TryParseInt32Invariant(yaml.Value, out var x);
|
||||
Exts.TryParseIntegerInvariant(d["X"].Value, out var x);
|
||||
Hotspot = Hotspot.WithX(x);
|
||||
}
|
||||
|
||||
if (d.TryGetValue("Y", out yaml))
|
||||
if (d.ContainsKey("Y"))
|
||||
{
|
||||
Exts.TryParseInt32Invariant(yaml.Value, out var y);
|
||||
Exts.TryParseIntegerInvariant(d["Y"].Value, out var y);
|
||||
Hotspot = Hotspot.WithY(y);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,9 +79,6 @@ namespace OpenRA.Graphics
|
||||
CopyPaletteToBuffer(index, p);
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage(
|
||||
"Performance", "CA1854:Prefer the 'IDictionary.TryGetValue(TKey, out TValue)' method",
|
||||
Justification = "False positive - indexer is a set not a get.")]
|
||||
public void ReplacePalette(string name, IPalette p)
|
||||
{
|
||||
if (mutablePalettes.ContainsKey(name))
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Linq;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public class MarkerTileRenderable : IRenderable, IFinalizedRenderable
|
||||
{
|
||||
readonly CPos pos;
|
||||
readonly Color color;
|
||||
|
||||
public MarkerTileRenderable(CPos pos, Color color)
|
||||
{
|
||||
this.pos = pos;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public WPos Pos => WPos.Zero;
|
||||
public int ZOffset => 0;
|
||||
public bool IsDecoration => true;
|
||||
|
||||
public IRenderable WithZOffset(int newOffset) { return this; }
|
||||
|
||||
public IRenderable OffsetBy(in WVec vec)
|
||||
{
|
||||
return new MarkerTileRenderable(pos, color);
|
||||
}
|
||||
|
||||
public IRenderable AsDecoration() { return this; }
|
||||
|
||||
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
|
||||
public void Render(WorldRenderer wr)
|
||||
{
|
||||
var map = wr.World.Map;
|
||||
var r = map.Grid.Ramps[map.Ramp[pos]];
|
||||
var wpos = map.CenterOfCell(pos) - new WVec(0, 0, r.CenterHeightOffset);
|
||||
|
||||
var corners = r.Corners.Select(corner => wr.Viewport.WorldToViewPx(wr.Screen3DPosition(wpos + corner))).ToList();
|
||||
|
||||
Game.Renderer.RgbaColorRenderer.FillRect(corners[0], corners[1], corners[2], corners[3], color);
|
||||
}
|
||||
|
||||
public void RenderDebugGeometry(WorldRenderer wr) { }
|
||||
public Rectangle ScreenBounds(WorldRenderer wr) { return Rectangle.Empty; }
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,9 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
@@ -29,14 +30,6 @@ namespace OpenRA.Graphics
|
||||
Rectangle AggregateBounds { get; }
|
||||
}
|
||||
|
||||
public interface IModelWidget
|
||||
{
|
||||
public string Palette { get; }
|
||||
public float Scale { get; }
|
||||
public void Setup(Func<bool> isVisible, Func<string> getPalette, Func<string> getPlayerPalette,
|
||||
Func<float> getScale, Func<IModel> getVoxel, Func<WRot> getRotation);
|
||||
}
|
||||
|
||||
public readonly struct ModelRenderData
|
||||
{
|
||||
public readonly int Start;
|
||||
@@ -51,13 +44,51 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
public interface IModelCacheInfo : ITraitInfoInterface { }
|
||||
|
||||
public interface IModelCache
|
||||
public interface IModelCache : IDisposable
|
||||
{
|
||||
IModel GetModel(string model);
|
||||
IModel GetModelSequence(string model, string sequence);
|
||||
bool HasModelSequence(string model, string sequence);
|
||||
IVertexBuffer<ModelVertex> VertexBuffer { get; }
|
||||
IVertexBuffer<Vertex> VertexBuffer { get; }
|
||||
}
|
||||
|
||||
public interface IModelSequenceLoader
|
||||
{
|
||||
Action<string> OnMissingModelError { get; set; }
|
||||
IModelCache CacheModels(IReadOnlyFileSystem fileSystem, ModData modData, IReadOnlyDictionary<string, MiniYamlNode> modelDefinitions);
|
||||
}
|
||||
|
||||
public class PlaceholderModelSequenceLoader : IModelSequenceLoader
|
||||
{
|
||||
public Action<string> OnMissingModelError { get; set; }
|
||||
|
||||
class PlaceholderModelCache : IModelCache
|
||||
{
|
||||
public IVertexBuffer<Vertex> VertexBuffer => throw new NotImplementedException();
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public IModel GetModel(string model)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IModel GetModelSequence(string model, string sequence)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool HasModelSequence(string model, string sequence)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public PlaceholderModelSequenceLoader(ModData modData) { }
|
||||
|
||||
public IModelCache CacheModels(IReadOnlyFileSystem fileSystem, ModData modData, IReadOnlyDictionary<string, MiniYamlNode> modelDefinitions)
|
||||
{
|
||||
return new PlaceholderModelCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,11 +12,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public class ModelRenderProxy
|
||||
{
|
||||
@@ -34,14 +32,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
}
|
||||
}
|
||||
|
||||
[TraitLocation(SystemActors.World | SystemActors.EditorWorld)]
|
||||
[Desc("Render voxels")]
|
||||
public class ModelRendererInfo : TraitInfo, Requires<IModelCacheInfo>
|
||||
{
|
||||
public override object Create(ActorInitializer init) { return new ModelRenderer(this, init.Self); }
|
||||
}
|
||||
|
||||
public sealed class ModelRenderer : IDisposable, IRenderer, INotifyActorDisposing
|
||||
public sealed class ModelRenderer : IDisposable
|
||||
{
|
||||
// Static constants
|
||||
static readonly float[] ShadowDiffuse = new float[] { 0, 0, 0 };
|
||||
@@ -55,32 +46,28 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
|
||||
readonly Renderer renderer;
|
||||
readonly IShader shader;
|
||||
public readonly IModelCache ModelCache;
|
||||
|
||||
readonly Dictionary<Sheet, IFrameBuffer> mappedBuffers = new();
|
||||
readonly Stack<KeyValuePair<Sheet, IFrameBuffer>> unmappedBuffers = new();
|
||||
readonly List<(Sheet Sheet, Action Func)> doRender = new();
|
||||
readonly int sheetSize;
|
||||
|
||||
SheetBuilder sheetBuilderForFrame;
|
||||
bool isInFrame;
|
||||
|
||||
public void SetPalette(HardwarePalette palette)
|
||||
public ModelRenderer(Renderer renderer, IShader shader)
|
||||
{
|
||||
shader.SetTexture("Palette", palette.Texture);
|
||||
shader.SetVec("PaletteRows", palette.Height);
|
||||
this.renderer = renderer;
|
||||
this.shader = shader;
|
||||
}
|
||||
|
||||
public ModelRenderer(ModelRendererInfo info, Actor self)
|
||||
public void SetPalette(ITexture palette)
|
||||
{
|
||||
renderer = Game.Renderer;
|
||||
shader = renderer.CreateShader(new ModelShaderBindings());
|
||||
renderer.WorldRenderers = renderer.WorldRenderers.Append(this).ToArray();
|
||||
shader.SetTexture("Palette", palette);
|
||||
}
|
||||
|
||||
ModelCache = self.Trait<IModelCache>();
|
||||
|
||||
sheetSize = Game.Settings.Graphics.SheetSize;
|
||||
var a = 2f / sheetSize;
|
||||
public void SetViewportParams()
|
||||
{
|
||||
var a = 2f / renderer.SheetSize;
|
||||
var view = new[]
|
||||
{
|
||||
a, 0, 0, 0,
|
||||
@@ -189,12 +176,12 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
var spriteCenter = new float2(sb.Left + sb.Width / 2, sb.Top + sb.Height / 2);
|
||||
var shadowCenter = new float2(ssb.Left + ssb.Width / 2, ssb.Top + ssb.Height / 2);
|
||||
|
||||
var translateMtx = Util.TranslationMatrix(spriteCenter.X - spriteOffset.X, sheetSize - (spriteCenter.Y - spriteOffset.Y), 0);
|
||||
var shadowTranslateMtx = Util.TranslationMatrix(shadowCenter.X - shadowSpriteOffset.X, sheetSize - (shadowCenter.Y - shadowSpriteOffset.Y), 0);
|
||||
var translateMtx = Util.TranslationMatrix(spriteCenter.X - spriteOffset.X, renderer.SheetSize - (spriteCenter.Y - spriteOffset.Y), 0);
|
||||
var shadowTranslateMtx = Util.TranslationMatrix(shadowCenter.X - shadowSpriteOffset.X, renderer.SheetSize - (shadowCenter.Y - shadowSpriteOffset.Y), 0);
|
||||
var correctionTransform = Util.MatrixMultiply(translateMtx, FlipMtx);
|
||||
var shadowCorrectionTransform = Util.MatrixMultiply(shadowTranslateMtx, ShadowScaleFlipMtx);
|
||||
|
||||
void RenderFunc()
|
||||
doRender.Add((sprite.Sheet, () =>
|
||||
{
|
||||
foreach (var m in models)
|
||||
{
|
||||
@@ -226,18 +213,16 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
// Transform light vector from shadow -> world -> limb coords
|
||||
var lightDirection = ExtractRotationVector(Util.MatrixMultiply(it, lightTransform));
|
||||
|
||||
Render(rd, ModelCache, Util.MatrixMultiply(transform, t), lightDirection,
|
||||
lightAmbientColor, lightDiffuseColor, color.TextureIndex, normals.TextureIndex);
|
||||
Render(rd, wr.World.ModelCache, Util.MatrixMultiply(transform, t), lightDirection,
|
||||
lightAmbientColor, lightDiffuseColor, color.TextureMidIndex, normals.TextureMidIndex);
|
||||
|
||||
// Disable shadow normals by forcing zero diffuse and identity ambient light
|
||||
if (m.ShowShadow)
|
||||
Render(rd, ModelCache, Util.MatrixMultiply(shadow, t), lightDirection,
|
||||
ShadowAmbient, ShadowDiffuse, shadowPalette.TextureIndex, normals.TextureIndex);
|
||||
Render(rd, wr.World.ModelCache, Util.MatrixMultiply(shadow, t), lightDirection,
|
||||
ShadowAmbient, ShadowDiffuse, shadowPalette.TextureMidIndex, normals.TextureMidIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
doRender.Add((sprite.Sheet, RenderFunc));
|
||||
}));
|
||||
|
||||
var screenLightVector = Util.MatrixVectorMultiply(invShadowTransform, ZVector);
|
||||
screenLightVector = Util.MatrixVectorMultiply(cameraTransform, screenLightVector);
|
||||
@@ -252,9 +237,9 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
|
||||
// Width and height must be even to avoid rendering glitches
|
||||
if ((width & 1) == 1)
|
||||
width++;
|
||||
width += 1;
|
||||
if ((height & 1) == 1)
|
||||
height++;
|
||||
height += 1;
|
||||
|
||||
size = new Size(width, height);
|
||||
}
|
||||
@@ -282,17 +267,17 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
IModelCache cache,
|
||||
float[] t, float[] lightDirection,
|
||||
float[] ambientLight, float[] diffuseLight,
|
||||
float colorPaletteTextureIndex, float normalsPaletteTextureIndex)
|
||||
float colorPaletteTextureMidIndex, float normalsPaletteTextureMidIndex)
|
||||
{
|
||||
shader.SetTexture("DiffuseTexture", renderData.Sheet.GetTexture());
|
||||
shader.SetVec("Palettes", colorPaletteTextureIndex, normalsPaletteTextureIndex);
|
||||
shader.SetVec("PaletteRows", colorPaletteTextureMidIndex, normalsPaletteTextureMidIndex);
|
||||
shader.SetMatrix("TransformMatrix", t);
|
||||
shader.SetVec("LightDirection", lightDirection, 4);
|
||||
shader.SetVec("AmbientLight", ambientLight, 3);
|
||||
shader.SetVec("DiffuseLight", diffuseLight, 3);
|
||||
|
||||
shader.PrepareRender();
|
||||
renderer.DrawBatch(cache.VertexBuffer, shader, renderData.Start, renderData.Count, PrimitiveType.TriangleList);
|
||||
renderer.DrawBatch(cache.VertexBuffer, renderData.Start, renderData.Count, PrimitiveType.TriangleList);
|
||||
}
|
||||
|
||||
public void BeginFrame()
|
||||
@@ -313,14 +298,14 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
Game.Renderer.Flush();
|
||||
fbo.Bind();
|
||||
|
||||
Game.Renderer.EnableDepthBuffer();
|
||||
Game.Renderer.Context.EnableDepthBuffer();
|
||||
return fbo;
|
||||
}
|
||||
|
||||
static void DisableFrameBuffer(IFrameBuffer fbo)
|
||||
void DisableFrameBuffer(IFrameBuffer fbo)
|
||||
{
|
||||
Game.Renderer.Flush();
|
||||
Game.Renderer.DisableDepthBuffer();
|
||||
Game.Renderer.Context.DisableDepthBuffer();
|
||||
fbo.Unbind();
|
||||
}
|
||||
|
||||
@@ -368,7 +353,8 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
return kv.Key;
|
||||
}
|
||||
|
||||
var framebuffer = renderer.CreateFrameBuffer(new Size(sheetSize, sheetSize));
|
||||
var size = new Size(renderer.SheetSize, renderer.SheetSize);
|
||||
var framebuffer = renderer.Context.CreateFrameBuffer(size);
|
||||
var sheet = new Sheet(SheetType.BGRA, framebuffer.Texture);
|
||||
mappedBuffers.Add(sheet, framebuffer);
|
||||
|
||||
@@ -385,12 +371,6 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
|
||||
mappedBuffers.Clear();
|
||||
unmappedBuffers.Clear();
|
||||
renderer.WorldRenderers = renderer.WorldRenderers.Where(r => r != this).ToArray();
|
||||
}
|
||||
|
||||
void INotifyActorDisposing.Disposing(Actor a)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public readonly struct ModelVertex
|
||||
{
|
||||
// 3d position
|
||||
public readonly float X, Y, Z;
|
||||
|
||||
// Primary and secondary texture coordinates or RGBA color
|
||||
public readonly float S, T, U, V;
|
||||
|
||||
// Palette and channel flags
|
||||
public readonly float P, C;
|
||||
|
||||
public ModelVertex(in float3 xyz, float s, float t, float u, float v, float p, float c)
|
||||
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c) { }
|
||||
|
||||
public ModelVertex(float x, float y, float z, float s, float t, float u, float v, float p, float c)
|
||||
{
|
||||
X = x; Y = y; Z = z;
|
||||
S = s; T = t;
|
||||
U = u; V = v;
|
||||
P = p; C = c;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ModelShaderBindings : ShaderBindings
|
||||
{
|
||||
public ModelShaderBindings()
|
||||
: base("model")
|
||||
{ }
|
||||
|
||||
public override ShaderVertexAttribute[] Attributes { get; } = new[]
|
||||
{
|
||||
new ShaderVertexAttribute("aVertexPosition", ShaderVertexAttributeType.Float, 3, 0),
|
||||
new ShaderVertexAttribute("aVertexTexCoord", ShaderVertexAttributeType.Float, 4, 12),
|
||||
new ShaderVertexAttribute("aVertexTexMetadata", ShaderVertexAttributeType.Float, 2, 28),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public static Color GetColor(this IPalette palette, int index)
|
||||
{
|
||||
return Color.FromArgb(palette[index]);
|
||||
return Color.FromArgb((int)palette[index]);
|
||||
}
|
||||
|
||||
public static IPalette AsReadOnly(this IPalette palette)
|
||||
@@ -40,7 +40,7 @@ namespace OpenRA.Graphics
|
||||
return new ReadOnlyPalette(palette);
|
||||
}
|
||||
|
||||
sealed class ReadOnlyPalette : IPalette
|
||||
class ReadOnlyPalette : IPalette
|
||||
{
|
||||
readonly IPalette palette;
|
||||
public ReadOnlyPalette(IPalette palette) { this.palette = palette; }
|
||||
@@ -103,7 +103,7 @@ namespace OpenRA.Graphics
|
||||
: this(p)
|
||||
{
|
||||
for (var i = 0; i < Palette.Size; i++)
|
||||
colors[i] = r.GetRemappedColor(this.GetColor(i), i).ToArgb();
|
||||
colors[i] = (uint)r.GetRemappedColor(this.GetColor(i), i).ToArgb();
|
||||
}
|
||||
|
||||
public ImmutablePalette(IPalette p)
|
||||
@@ -142,7 +142,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public void SetColor(int index, Color color)
|
||||
{
|
||||
colors[index] = color.ToArgb();
|
||||
colors[index] = (uint)color.ToArgb();
|
||||
}
|
||||
|
||||
public void SetFromPalette(IPalette p)
|
||||
@@ -153,7 +153,7 @@ namespace OpenRA.Graphics
|
||||
public void ApplyRemap(IPaletteRemap r)
|
||||
{
|
||||
for (var i = 0; i < Palette.Size; i++)
|
||||
colors[i] = r.GetRemappedColor(this.GetColor(i), i).ToArgb();
|
||||
colors[i] = (uint)r.GetRemappedColor(this.GetColor(i), i).ToArgb();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,17 +13,19 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
public sealed class PaletteReference
|
||||
{
|
||||
readonly float index;
|
||||
readonly HardwarePalette hardwarePalette;
|
||||
|
||||
public readonly string Name;
|
||||
public IPalette Palette { get; internal set; }
|
||||
public int TextureIndex { get; }
|
||||
public float TextureIndex => index / hardwarePalette.Height;
|
||||
public float TextureMidIndex => (index + 0.5f) / hardwarePalette.Height;
|
||||
|
||||
public PaletteReference(string name, int index, IPalette palette, HardwarePalette hardwarePalette)
|
||||
{
|
||||
Name = name;
|
||||
Palette = palette;
|
||||
TextureIndex = index;
|
||||
this.index = index;
|
||||
this.hardwarePalette = hardwarePalette;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,12 +20,13 @@ namespace OpenRA
|
||||
Automatic,
|
||||
ANGLE,
|
||||
Modern,
|
||||
Embedded
|
||||
Embedded,
|
||||
Legacy
|
||||
}
|
||||
|
||||
public interface IPlatform
|
||||
{
|
||||
IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int vertexBatchSize, int indexBatchSize, int videoDisplay, GLProfile profile);
|
||||
IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay, GLProfile profile, bool enableLegacyGL);
|
||||
ISoundEngine CreateSound(string device);
|
||||
IFont CreateFont(byte[] data);
|
||||
}
|
||||
@@ -82,18 +83,16 @@ namespace OpenRA
|
||||
|
||||
public interface IGraphicsContext : IDisposable
|
||||
{
|
||||
IVertexBuffer<T> CreateVertexBuffer<T>(int size) where T : struct;
|
||||
T[] CreateVertices<T>(int size) where T : struct;
|
||||
IIndexBuffer CreateIndexBuffer(uint[] indices);
|
||||
IVertexBuffer<Vertex> CreateVertexBuffer(int size);
|
||||
Vertex[] CreateVertices(int size);
|
||||
ITexture CreateTexture();
|
||||
IFrameBuffer CreateFrameBuffer(Size s);
|
||||
IFrameBuffer CreateFrameBuffer(Size s, Color clearColor);
|
||||
IShader CreateShader(IShaderBindings shaderBindings);
|
||||
IShader CreateShader(string name);
|
||||
void EnableScissor(int x, int y, int width, int height);
|
||||
void DisableScissor();
|
||||
void Present();
|
||||
void DrawPrimitives(PrimitiveType pt, int firstVertex, int numVertices);
|
||||
void DrawElements(int numIndices, int offset);
|
||||
void Clear();
|
||||
void EnableDepthBuffer();
|
||||
void DisableDepthBuffer();
|
||||
@@ -103,14 +102,7 @@ namespace OpenRA
|
||||
string GLVersion { get; }
|
||||
}
|
||||
|
||||
public interface IRenderer
|
||||
{
|
||||
void BeginFrame();
|
||||
void EndFrame();
|
||||
void SetPalette(HardwarePalette palette);
|
||||
}
|
||||
|
||||
public interface IVertexBuffer<T> : IDisposable where T : struct
|
||||
public interface IVertexBuffer<T> : IDisposable
|
||||
{
|
||||
void Bind();
|
||||
void SetData(T[] vertices, int length);
|
||||
@@ -122,11 +114,6 @@ namespace OpenRA
|
||||
void SetData(T[] vertices, int offset, int start, int length);
|
||||
}
|
||||
|
||||
public interface IIndexBuffer : IDisposable
|
||||
{
|
||||
void Bind();
|
||||
}
|
||||
|
||||
public interface IShader
|
||||
{
|
||||
void SetBool(string name, bool value);
|
||||
@@ -137,17 +124,6 @@ namespace OpenRA
|
||||
void SetTexture(string param, ITexture texture);
|
||||
void SetMatrix(string param, float[] mtx);
|
||||
void PrepareRender();
|
||||
void Bind();
|
||||
}
|
||||
|
||||
public interface IShaderBindings
|
||||
{
|
||||
string VertexShaderName { get; }
|
||||
string VertexShaderCode { get; }
|
||||
string FragmentShaderName { get; }
|
||||
string FragmentShaderCode { get; }
|
||||
int Stride { get; }
|
||||
ShaderVertexAttribute[] Attributes { get; }
|
||||
}
|
||||
|
||||
public enum TextureScaleFilter { Nearest, Linear }
|
||||
@@ -156,7 +132,6 @@ namespace OpenRA
|
||||
{
|
||||
void SetData(byte[] colors, int width, int height);
|
||||
void SetFloatData(float[] data, int width, int height);
|
||||
void SetDataFromReadBuffer(Rectangle rect);
|
||||
byte[] GetData();
|
||||
Size Size { get; }
|
||||
TextureScaleFilter ScaleFilter { get; set; }
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public readonly struct RenderPostProcessPassVertex
|
||||
{
|
||||
public readonly float X, Y;
|
||||
|
||||
public RenderPostProcessPassVertex(float x, float y)
|
||||
{
|
||||
X = x; Y = y;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public readonly struct RenderPostProcessPassTexturedVertex
|
||||
{
|
||||
// 3d position
|
||||
public readonly float X, Y;
|
||||
public readonly float S, T;
|
||||
|
||||
public RenderPostProcessPassTexturedVertex(float x, float y, float s, float t)
|
||||
{
|
||||
X = x; Y = y;
|
||||
S = s; T = t;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class RenderPostProcessPassShaderBindings : ShaderBindings
|
||||
{
|
||||
public RenderPostProcessPassShaderBindings(string name)
|
||||
: base("postprocess", "postprocess_" + name) { }
|
||||
|
||||
public override ShaderVertexAttribute[] Attributes { get; } = new[]
|
||||
{
|
||||
new ShaderVertexAttribute("aVertexPosition", ShaderVertexAttributeType.Float, 2, 0)
|
||||
};
|
||||
}
|
||||
|
||||
public sealed class RenderPostProcessPassTexturedShaderBindings : ShaderBindings
|
||||
{
|
||||
public RenderPostProcessPassTexturedShaderBindings(string name)
|
||||
: base("postprocess_textured", "postprocess_textured_" + name)
|
||||
{ }
|
||||
|
||||
public override ShaderVertexAttribute[] Attributes { get; } = new[]
|
||||
{
|
||||
new ShaderVertexAttribute("aVertexPosition", ShaderVertexAttributeType.Float, 2, 0),
|
||||
new ShaderVertexAttribute("aVertexTexCoord", ShaderVertexAttributeType.Float, 2, 8),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ namespace OpenRA.Graphics
|
||||
static readonly float3 Offset = new(0.5f, 0.5f, 0f);
|
||||
|
||||
readonly SpriteRenderer parent;
|
||||
readonly Vertex[] vertices = new Vertex[4];
|
||||
readonly Vertex[] vertices = new Vertex[6];
|
||||
|
||||
public RgbaColorRenderer(SpriteRenderer parent)
|
||||
{
|
||||
@@ -45,12 +45,14 @@ namespace OpenRA.Graphics
|
||||
var eb = endColor.B / 255.0f;
|
||||
var ea = endColor.A / 255.0f;
|
||||
|
||||
vertices[0] = new Vertex(start - corner + Offset, sr, sg, sb, sa, 0);
|
||||
vertices[1] = new Vertex(start + corner + Offset, sr, sg, sb, sa, 0);
|
||||
vertices[2] = new Vertex(end + corner + Offset, er, eg, eb, ea, 0);
|
||||
vertices[3] = new Vertex(end - corner + Offset, er, eg, eb, ea, 0);
|
||||
vertices[0] = new Vertex(start - corner + Offset, sr, sg, sb, sa, 0, 0);
|
||||
vertices[1] = new Vertex(start + corner + Offset, sr, sg, sb, sa, 0, 0);
|
||||
vertices[2] = new Vertex(end + corner + Offset, er, eg, eb, ea, 0, 0);
|
||||
vertices[3] = new Vertex(end + corner + Offset, er, eg, eb, ea, 0, 0);
|
||||
vertices[4] = new Vertex(end - corner + Offset, er, eg, eb, ea, 0, 0);
|
||||
vertices[5] = new Vertex(start - corner + Offset, sr, sg, sb, sa, 0, 0);
|
||||
|
||||
parent.DrawRGBAQuad(vertices, blendMode);
|
||||
parent.DrawRGBAVertices(vertices, blendMode);
|
||||
}
|
||||
|
||||
public void DrawLine(in float3 start, in float3 end, float width, Color color, BlendMode blendMode = BlendMode.Alpha)
|
||||
@@ -64,11 +66,13 @@ namespace OpenRA.Graphics
|
||||
var b = color.B / 255.0f;
|
||||
var a = color.A / 255.0f;
|
||||
|
||||
vertices[0] = new Vertex(start - corner + Offset, r, g, b, a, 0);
|
||||
vertices[1] = new Vertex(start + corner + Offset, r, g, b, a, 0);
|
||||
vertices[2] = new Vertex(end + corner + Offset, r, g, b, a, 0);
|
||||
vertices[3] = new Vertex(end - corner + Offset, r, g, b, a, 0);
|
||||
parent.DrawRGBAQuad(vertices, blendMode);
|
||||
vertices[0] = new Vertex(start - corner + Offset, r, g, b, a, 0, 0);
|
||||
vertices[1] = new Vertex(start + corner + Offset, r, g, b, a, 0, 0);
|
||||
vertices[2] = new Vertex(end + corner + Offset, r, g, b, a, 0, 0);
|
||||
vertices[3] = new Vertex(end + corner + Offset, r, g, b, a, 0, 0);
|
||||
vertices[4] = new Vertex(end - corner + Offset, r, g, b, a, 0, 0);
|
||||
vertices[5] = new Vertex(start - corner + Offset, r, g, b, a, 0, 0);
|
||||
parent.DrawRGBAVertices(vertices, blendMode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -76,7 +80,7 @@ namespace OpenRA.Graphics
|
||||
/// Will behave badly if the lines are parallel.
|
||||
/// Z position is the average of a and b (ignores actual intersection point if it exists).
|
||||
/// </summary>
|
||||
static float3 IntersectionOf(in float3 a, in float3 da, in float3 b, in float3 db)
|
||||
float3 IntersectionOf(in float3 a, in float3 da, in float3 b, in float3 db)
|
||||
{
|
||||
var crossA = a.X * (a.Y + da.Y) - a.Y * (a.X + da.X);
|
||||
var crossB = b.X * (b.Y + db.Y) - b.Y * (b.X + db.X);
|
||||
@@ -153,11 +157,13 @@ namespace OpenRA.Graphics
|
||||
var cd = closed || i < limit - 1 ? IntersectionOf(end - corner, dir, end - nextCorner, nextDir) : end - corner;
|
||||
|
||||
// Fill segment
|
||||
vertices[0] = new Vertex(ca + Offset, r, g, b, a, 0);
|
||||
vertices[1] = new Vertex(cb + Offset, r, g, b, a, 0);
|
||||
vertices[2] = new Vertex(cc + Offset, r, g, b, a, 0);
|
||||
vertices[3] = new Vertex(cd + Offset, r, g, b, a, 0);
|
||||
parent.DrawRGBAQuad(vertices, blendMode);
|
||||
vertices[0] = new Vertex(ca + Offset, r, g, b, a, 0, 0);
|
||||
vertices[1] = new Vertex(cb + Offset, r, g, b, a, 0, 0);
|
||||
vertices[2] = new Vertex(cc + Offset, r, g, b, a, 0, 0);
|
||||
vertices[3] = new Vertex(cc + Offset, r, g, b, a, 0, 0);
|
||||
vertices[4] = new Vertex(cd + Offset, r, g, b, a, 0, 0);
|
||||
vertices[5] = new Vertex(ca + Offset, r, g, b, a, 0, 0);
|
||||
parent.DrawRGBAVertices(vertices, blendMode);
|
||||
|
||||
// Advance line segment
|
||||
end = next;
|
||||
@@ -194,6 +200,20 @@ namespace OpenRA.Graphics
|
||||
DrawPolygon(new[] { tl, tr, br, bl }, width, color, blendMode);
|
||||
}
|
||||
|
||||
public void FillTriangle(in float3 a, in float3 b, in float3 c, Color color, BlendMode blendMode = BlendMode.Alpha)
|
||||
{
|
||||
color = Util.PremultiplyAlpha(color);
|
||||
var cr = color.R / 255.0f;
|
||||
var cg = color.G / 255.0f;
|
||||
var cb = color.B / 255.0f;
|
||||
var ca = color.A / 255.0f;
|
||||
|
||||
vertices[0] = new Vertex(a + Offset, cr, cg, cb, ca, 0, 0);
|
||||
vertices[1] = new Vertex(b + Offset, cr, cg, cb, ca, 0, 0);
|
||||
vertices[2] = new Vertex(c + Offset, cr, cg, cb, ca, 0, 0);
|
||||
parent.DrawRGBAVertices(vertices, blendMode);
|
||||
}
|
||||
|
||||
public void FillRect(in float3 tl, in float3 br, Color color, BlendMode blendMode = BlendMode.Alpha)
|
||||
{
|
||||
var tr = new float3(br.X, tl.Y, tl.Z);
|
||||
@@ -209,11 +229,13 @@ namespace OpenRA.Graphics
|
||||
var cb = color.B / 255.0f;
|
||||
var ca = color.A / 255.0f;
|
||||
|
||||
vertices[0] = new Vertex(a + Offset, cr, cg, cb, ca, 0);
|
||||
vertices[1] = new Vertex(b + Offset, cr, cg, cb, ca, 0);
|
||||
vertices[2] = new Vertex(c + Offset, cr, cg, cb, ca, 0);
|
||||
vertices[3] = new Vertex(d + Offset, cr, cg, cb, ca, 0);
|
||||
parent.DrawRGBAQuad(vertices, blendMode);
|
||||
vertices[0] = new Vertex(a + Offset, cr, cg, cb, ca, 0, 0);
|
||||
vertices[1] = new Vertex(b + Offset, cr, cg, cb, ca, 0, 0);
|
||||
vertices[2] = new Vertex(c + Offset, cr, cg, cb, ca, 0, 0);
|
||||
vertices[3] = new Vertex(c + Offset, cr, cg, cb, ca, 0, 0);
|
||||
vertices[4] = new Vertex(d + Offset, cr, cg, cb, ca, 0, 0);
|
||||
vertices[5] = new Vertex(a + Offset, cr, cg, cb, ca, 0, 0);
|
||||
parent.DrawRGBAVertices(vertices, blendMode);
|
||||
}
|
||||
|
||||
public void FillRect(in float3 a, in float3 b, in float3 c, in float3 d, Color topLeftColor, Color topRightColor, Color bottomRightColor, Color bottomLeftColor, BlendMode blendMode = BlendMode.Alpha)
|
||||
@@ -221,9 +243,11 @@ namespace OpenRA.Graphics
|
||||
vertices[0] = VertexWithColor(a + Offset, topLeftColor);
|
||||
vertices[1] = VertexWithColor(b + Offset, topRightColor);
|
||||
vertices[2] = VertexWithColor(c + Offset, bottomRightColor);
|
||||
vertices[3] = VertexWithColor(d + Offset, bottomLeftColor);
|
||||
vertices[3] = VertexWithColor(c + Offset, bottomRightColor);
|
||||
vertices[4] = VertexWithColor(d + Offset, bottomLeftColor);
|
||||
vertices[5] = VertexWithColor(a + Offset, topLeftColor);
|
||||
|
||||
parent.DrawRGBAQuad(vertices, blendMode);
|
||||
parent.DrawRGBAVertices(vertices, blendMode);
|
||||
}
|
||||
|
||||
static Vertex VertexWithColor(in float3 xyz, Color color)
|
||||
@@ -234,7 +258,7 @@ namespace OpenRA.Graphics
|
||||
var cb = color.B / 255.0f;
|
||||
var ca = color.A / 255.0f;
|
||||
|
||||
return new Vertex(xyz, cr, cg, cb, ca, 0);
|
||||
return new Vertex(xyz, cr, cg, cb, ca, 0, 0);
|
||||
}
|
||||
|
||||
public void FillEllipse(in float3 tl, in float3 br, Color color, BlendMode blendMode = BlendMode.Alpha)
|
||||
|
||||
@@ -94,7 +94,7 @@ namespace OpenRA.Graphics
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
// Nodes starting with ^ are inheritable but never loaded directly
|
||||
if (node.Key.StartsWith(ActorInfo.AbstractActorPrefix))
|
||||
if (node.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
images[node.Key] = modData.SpriteSequenceLoader.ParseSequences(modData, tileSet, SpriteCache, node);
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public enum ShaderVertexAttributeType
|
||||
{
|
||||
// Assign the underlying OpenGL type values
|
||||
// to simplify enum use in the shader
|
||||
Float = 0x1406, // GL_FLOAT
|
||||
Int = 0x1404, // GL_INT
|
||||
UInt = 0x1405 // GL_UNSIGNED_INT
|
||||
}
|
||||
|
||||
public readonly struct ShaderVertexAttribute
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly ShaderVertexAttributeType Type;
|
||||
public readonly int Components;
|
||||
public readonly int Offset;
|
||||
|
||||
public ShaderVertexAttribute(string name, ShaderVertexAttributeType type, int components, int offset)
|
||||
{
|
||||
Name = name;
|
||||
Type = type;
|
||||
Components = components;
|
||||
Offset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class ShaderBindings : IShaderBindings
|
||||
{
|
||||
public string VertexShaderName { get; }
|
||||
public string VertexShaderCode { get; }
|
||||
public string FragmentShaderName { get; }
|
||||
public string FragmentShaderCode { get; }
|
||||
public int Stride { get; }
|
||||
|
||||
public abstract ShaderVertexAttribute[] Attributes { get; }
|
||||
|
||||
protected ShaderBindings(string name)
|
||||
: this(name, name) { }
|
||||
|
||||
protected ShaderBindings(string vertexName, string fragmentName)
|
||||
{
|
||||
Stride = Attributes.Sum(a => a.Components * 4);
|
||||
VertexShaderName = vertexName;
|
||||
VertexShaderCode = GetShaderCode(VertexShaderName + ".vert");
|
||||
FragmentShaderName = fragmentName;
|
||||
FragmentShaderCode = GetShaderCode(FragmentShaderName + ".frag");
|
||||
}
|
||||
|
||||
public static string GetShaderCode(string filename)
|
||||
{
|
||||
var filepath = Path.Combine(Platform.EngineDir, "glsl", filename);
|
||||
return File.ReadAllText(filepath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,16 +82,16 @@ namespace OpenRA.Graphics
|
||||
this.margin = margin;
|
||||
}
|
||||
|
||||
public Sprite Add(ISpriteFrame frame, bool premultiplied = false) { return Add(frame.Data, frame.Type, frame.Size, 0, frame.Offset, premultiplied); }
|
||||
public Sprite Add(byte[] src, SpriteFrameType type, Size size, bool premultiplied = false) { return Add(src, type, size, 0, float3.Zero, premultiplied); }
|
||||
public Sprite Add(byte[] src, SpriteFrameType type, Size size, float zRamp, in float3 spriteOffset, bool premultiplied = false)
|
||||
public Sprite Add(ISpriteFrame frame) { return Add(frame.Data, frame.Type, frame.Size, 0, frame.Offset); }
|
||||
public Sprite Add(byte[] src, SpriteFrameType type, Size size) { return Add(src, type, size, 0, float3.Zero); }
|
||||
public Sprite Add(byte[] src, SpriteFrameType type, Size size, float zRamp, in float3 spriteOffset)
|
||||
{
|
||||
// Don't bother allocating empty sprites
|
||||
if (size.Width == 0 || size.Height == 0)
|
||||
return new Sprite(Current, Rectangle.Empty, 0, spriteOffset, CurrentChannel, BlendMode.Alpha);
|
||||
|
||||
var rect = Allocate(size, zRamp, spriteOffset);
|
||||
Util.FastCopyIntoChannel(rect, src, type, premultiplied);
|
||||
Util.FastCopyIntoChannel(rect, src, type);
|
||||
Current.CommitBufferedData();
|
||||
return rect;
|
||||
}
|
||||
|
||||
@@ -42,11 +42,11 @@ namespace OpenRA.Graphics
|
||||
// in rendering a line of texels that sample outside the sprite rectangle.
|
||||
// Insetting the texture coordinates by a small fraction of a pixel avoids this
|
||||
// with negligible impact on the 1:1 rendering case.
|
||||
const float Inset = 1 / 128f;
|
||||
Left = (Math.Min(bounds.Left, bounds.Right) + Inset) / sheet.Size.Width;
|
||||
Top = (Math.Min(bounds.Top, bounds.Bottom) + Inset) / sheet.Size.Height;
|
||||
Right = (Math.Max(bounds.Left, bounds.Right) - Inset) / sheet.Size.Width;
|
||||
Bottom = (Math.Max(bounds.Top, bounds.Bottom) - Inset) / sheet.Size.Height;
|
||||
var inset = 1 / 128f;
|
||||
Left = (Math.Min(bounds.Left, bounds.Right) + inset) / sheet.Size.Width;
|
||||
Top = (Math.Min(bounds.Top, bounds.Bottom) + inset) / sheet.Size.Height;
|
||||
Right = (Math.Max(bounds.Left, bounds.Right) - inset) / sheet.Size.Width;
|
||||
Bottom = (Math.Max(bounds.Top, bounds.Bottom) - inset) / sheet.Size.Height;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,15 +18,17 @@ using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public sealed class SpriteCache : IDisposable
|
||||
public class SpriteCache : IDisposable
|
||||
{
|
||||
public readonly Dictionary<SheetType, SheetBuilder> SheetBuilders;
|
||||
readonly ISpriteLoader[] loaders;
|
||||
readonly IReadOnlyFileSystem fileSystem;
|
||||
|
||||
readonly Dictionary<int, (int[] Frames, MiniYamlNode.SourceLocation Location, Func<ISpriteFrame, ISpriteFrame> AdjustFrame, bool Premultiplied)> spriteReservations = new();
|
||||
readonly Dictionary<int, (int[] Frames, MiniYamlNode.SourceLocation Location)> spriteReservations = new();
|
||||
readonly Dictionary<int, (int[] Frames, MiniYamlNode.SourceLocation Location)> frameReservations = new();
|
||||
readonly Dictionary<string, List<int>> reservationsByFilename = new();
|
||||
|
||||
readonly Dictionary<int, ISpriteFrame[]> resolvedFrames = new();
|
||||
readonly Dictionary<int, Sprite[]> resolvedSprites = new();
|
||||
|
||||
readonly Dictionary<int, (string Filename, MiniYamlNode.SourceLocation Location)> missingFiles = new();
|
||||
@@ -45,10 +47,18 @@ namespace OpenRA.Graphics
|
||||
this.loaders = loaders;
|
||||
}
|
||||
|
||||
public int ReserveSprites(string filename, IEnumerable<int> frames, MiniYamlNode.SourceLocation location, Func<ISpriteFrame, ISpriteFrame> adjustFrame = null, bool premultiplied = false)
|
||||
public int ReserveSprites(string filename, IEnumerable<int> frames, MiniYamlNode.SourceLocation location)
|
||||
{
|
||||
var token = nextReservationToken++;
|
||||
spriteReservations[token] = (frames?.ToArray(), location, adjustFrame, premultiplied);
|
||||
spriteReservations[token] = (frames?.ToArray(), location);
|
||||
reservationsByFilename.GetOrAdd(filename, _ => new List<int>()).Add(token);
|
||||
return token;
|
||||
}
|
||||
|
||||
public int ReserveFrames(string filename, IEnumerable<int> frames, MiniYamlNode.SourceLocation location)
|
||||
{
|
||||
var token = nextReservationToken++;
|
||||
frameReservations[token] = (frames?.ToArray(), location);
|
||||
reservationsByFilename.GetOrAdd(filename, _ => new List<int>()).Add(token);
|
||||
return token;
|
||||
}
|
||||
@@ -74,73 +84,60 @@ namespace OpenRA.Graphics
|
||||
foreach (var sb in SheetBuilders.Values)
|
||||
sb.Current.CreateBuffer();
|
||||
|
||||
var pendingResolve = new List<(
|
||||
string Filename,
|
||||
int FrameIndex,
|
||||
bool Premultiplied,
|
||||
Func<ISpriteFrame, ISpriteFrame> AdjustFrame,
|
||||
ISpriteFrame Frame,
|
||||
Sprite[] SpritesForToken)>();
|
||||
var spriteCache = new Dictionary<int, Sprite>();
|
||||
foreach (var (filename, tokens) in reservationsByFilename)
|
||||
{
|
||||
modData.LoadScreen?.Display();
|
||||
var loadedFrames = GetFrames(fileSystem, filename, loaders, out _);
|
||||
foreach (var token in tokens)
|
||||
{
|
||||
if (spriteReservations.TryGetValue(token, out var rs))
|
||||
if (frameReservations.TryGetValue(token, out var r))
|
||||
{
|
||||
if (loadedFrames != null)
|
||||
{
|
||||
if (r.Frames != null)
|
||||
{
|
||||
var resolved = new ISpriteFrame[loadedFrames.Length];
|
||||
foreach (var i in r.Frames)
|
||||
resolved[i] = loadedFrames[i];
|
||||
resolvedFrames[token] = resolved;
|
||||
}
|
||||
else
|
||||
resolvedFrames[token] = loadedFrames;
|
||||
}
|
||||
else
|
||||
{
|
||||
resolvedFrames[token] = null;
|
||||
missingFiles[token] = (filename, r.Location);
|
||||
}
|
||||
}
|
||||
|
||||
if (spriteReservations.TryGetValue(token, out r))
|
||||
{
|
||||
if (loadedFrames != null)
|
||||
{
|
||||
var resolved = new Sprite[loadedFrames.Length];
|
||||
resolvedSprites[token] = resolved;
|
||||
var frames = rs.Frames ?? Enumerable.Range(0, loadedFrames.Length);
|
||||
|
||||
var frames = r.Frames ?? Enumerable.Range(0, loadedFrames.Length);
|
||||
foreach (var i in frames)
|
||||
{
|
||||
var frame = loadedFrames[i];
|
||||
if (rs.AdjustFrame != null)
|
||||
frame = rs.AdjustFrame(frame);
|
||||
pendingResolve.Add((filename, i, rs.Premultiplied, rs.AdjustFrame, frame, resolved));
|
||||
}
|
||||
resolved[i] = spriteCache.GetOrAdd(i,
|
||||
f => SheetBuilders[SheetBuilder.FrameTypeToSheetType(loadedFrames[f].Type)].Add(loadedFrames[f]));
|
||||
|
||||
resolvedSprites[token] = resolved;
|
||||
}
|
||||
else
|
||||
{
|
||||
resolvedSprites[token] = null;
|
||||
missingFiles[token] = (filename, rs.Location);
|
||||
missingFiles[token] = (filename, r.Location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spriteCache.Clear();
|
||||
}
|
||||
|
||||
spriteReservations.Clear();
|
||||
spriteReservations.TrimExcess();
|
||||
frameReservations.Clear();
|
||||
reservationsByFilename.Clear();
|
||||
reservationsByFilename.TrimExcess();
|
||||
|
||||
// When the sheet builder is adding sprites, it reserves height for the tallest sprite seen along the row.
|
||||
// We can achieve better sheet packing by keeping sprites with similar heights together.
|
||||
var orderedPendingResolve = pendingResolve.OrderBy(x => x.Frame.Size.Height);
|
||||
|
||||
var spriteCache = new Dictionary<(
|
||||
string Filename,
|
||||
int FrameIndex,
|
||||
bool Premultiplied,
|
||||
Func<ISpriteFrame, ISpriteFrame> AdjustFrame),
|
||||
Sprite>(pendingResolve.Count);
|
||||
foreach (var (filename, frameIndex, premultiplied, adjustFrame, frame, spritesForToken) in orderedPendingResolve)
|
||||
{
|
||||
// Premultiplied and non-premultiplied sprites must be cached separately
|
||||
// to cover the case where the same image is requested in both versions.
|
||||
spritesForToken[frameIndex] = spriteCache.GetOrAdd(
|
||||
(filename, frameIndex, premultiplied, adjustFrame),
|
||||
_ =>
|
||||
{
|
||||
var sheetBuilder = SheetBuilders[SheetBuilder.FrameTypeToSheetType(frame.Type)];
|
||||
return sheetBuilder.Add(frame, premultiplied);
|
||||
});
|
||||
|
||||
modData.LoadScreen?.Display();
|
||||
}
|
||||
|
||||
foreach (var sb in SheetBuilders.Values)
|
||||
sb.Current.ReleaseBuffer();
|
||||
@@ -148,11 +145,18 @@ namespace OpenRA.Graphics
|
||||
|
||||
public Sprite[] ResolveSprites(int token)
|
||||
{
|
||||
if (!resolvedSprites.Remove(token, out var resolved))
|
||||
throw new InvalidOperationException($"{nameof(token)} {token} has either already been resolved, or was never reserved via {nameof(ReserveSprites)}");
|
||||
var resolved = resolvedSprites[token];
|
||||
resolvedSprites.Remove(token);
|
||||
if (missingFiles.TryGetValue(token, out var r))
|
||||
throw new FileNotFoundException($"{r.Location}: {r.Filename} not found", r.Filename);
|
||||
|
||||
resolvedSprites.TrimExcess();
|
||||
return resolved;
|
||||
}
|
||||
|
||||
public ISpriteFrame[] ResolveFrames(int token)
|
||||
{
|
||||
var resolved = resolvedFrames[token];
|
||||
resolvedFrames.Remove(token);
|
||||
if (missingFiles.TryGetValue(token, out var r))
|
||||
throw new FileNotFoundException($"{r.Location}: {r.Filename} not found", r.Filename);
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
static float2 Rotate(float2 v, float sina, float cosa, float2 offset)
|
||||
float2 Rotate(float2 v, float sina, float cosa, float2 offset)
|
||||
{
|
||||
return new float2(
|
||||
v.X * cosa - v.Y * sina + offset.X,
|
||||
@@ -427,7 +427,7 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
sealed class GlyphInfo
|
||||
class GlyphInfo
|
||||
{
|
||||
public float Advance;
|
||||
public int2 Offset;
|
||||
|
||||
@@ -22,30 +22,20 @@ namespace OpenRA.Graphics
|
||||
/// </summary>
|
||||
public enum SpriteFrameType
|
||||
{
|
||||
/// <summary>
|
||||
/// 8 bit index into an external palette.
|
||||
/// </summary>
|
||||
// 8 bit index into an external palette
|
||||
Indexed8,
|
||||
|
||||
/// <summary>
|
||||
/// 32 bit color such as returned by Color.ToArgb() or the bmp file format
|
||||
/// (remember that little-endian systems place the little bits in the first byte).
|
||||
/// </summary>
|
||||
// 32 bit color such as returned by Color.ToArgb() or the bmp file format
|
||||
// (remember that little-endian systems place the little bits in the first byte!)
|
||||
Bgra32,
|
||||
|
||||
/// <summary>
|
||||
/// Like BGRA, but without an alpha channel.
|
||||
/// </summary>
|
||||
// Like BGRA, but without an alpha channel
|
||||
Bgr24,
|
||||
|
||||
/// <summary>
|
||||
/// 32 bit color in big-endian format, like png.
|
||||
/// </summary>
|
||||
// 32 bit color in big-endian format, like png
|
||||
Rgba32,
|
||||
|
||||
/// <summary>
|
||||
/// Like RGBA, but without an alpha channel.
|
||||
/// </summary>
|
||||
// Like RGBA, but without an alpha channel
|
||||
Rgb24
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
@@ -20,7 +19,6 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
public const int SheetCount = 8;
|
||||
static readonly string[] SheetIndexToTextureName = Exts.MakeArray(SheetCount, i => $"Texture{i}");
|
||||
static readonly int UintSize = Marshal.SizeOf(typeof(uint));
|
||||
|
||||
readonly Renderer renderer;
|
||||
readonly IShader shader;
|
||||
@@ -29,21 +27,21 @@ namespace OpenRA.Graphics
|
||||
readonly Sheet[] sheets = new Sheet[SheetCount];
|
||||
|
||||
BlendMode currentBlend = BlendMode.Alpha;
|
||||
int vertexCount = 0;
|
||||
int sheetCount = 0;
|
||||
int nv = 0;
|
||||
int ns = 0;
|
||||
|
||||
public SpriteRenderer(Renderer renderer, IShader shader)
|
||||
{
|
||||
this.renderer = renderer;
|
||||
this.shader = shader;
|
||||
vertices = renderer.Context.CreateVertices<Vertex>(renderer.TempVertexBufferSize);
|
||||
vertices = renderer.Context.CreateVertices(renderer.TempBufferSize);
|
||||
}
|
||||
|
||||
public void Flush()
|
||||
{
|
||||
if (vertexCount > 0)
|
||||
if (nv > 0)
|
||||
{
|
||||
for (var i = 0; i < sheetCount; i++)
|
||||
for (var i = 0; i < ns; i++)
|
||||
{
|
||||
shader.SetTexture(SheetIndexToTextureName[i], sheets[i].GetTexture());
|
||||
sheets[i] = null;
|
||||
@@ -52,11 +50,12 @@ namespace OpenRA.Graphics
|
||||
renderer.Context.SetBlendMode(currentBlend);
|
||||
shader.PrepareRender();
|
||||
|
||||
renderer.DrawQuadBatch(ref vertices, shader, vertexCount);
|
||||
// PERF: The renderer may choose to replace vertices with a different temporary buffer.
|
||||
renderer.DrawBatch(ref vertices, nv, PrimitiveType.TriangleList);
|
||||
renderer.Context.SetBlendMode(BlendMode.None);
|
||||
|
||||
vertexCount = 0;
|
||||
sheetCount = 0;
|
||||
nv = 0;
|
||||
ns = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +63,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
renderer.CurrentBatchRenderer = this;
|
||||
|
||||
if (s.BlendMode != currentBlend || vertexCount + 4 > renderer.TempVertexBufferSize)
|
||||
if (s.BlendMode != currentBlend || nv + 6 > renderer.TempBufferSize)
|
||||
Flush();
|
||||
|
||||
currentBlend = s.BlendMode;
|
||||
@@ -72,7 +71,7 @@ namespace OpenRA.Graphics
|
||||
// Check if the sheet (or secondary data sheet) have already been mapped
|
||||
var sheet = s.Sheet;
|
||||
var sheetIndex = 0;
|
||||
for (; sheetIndex < sheetCount; sheetIndex++)
|
||||
for (; sheetIndex < ns; sheetIndex++)
|
||||
if (sheets[sheetIndex] == sheet)
|
||||
break;
|
||||
|
||||
@@ -81,7 +80,7 @@ namespace OpenRA.Graphics
|
||||
if (ss != null)
|
||||
{
|
||||
var secondarySheet = ss.SecondarySheet;
|
||||
for (; secondarySheetIndex < sheetCount; secondarySheetIndex++)
|
||||
for (; secondarySheetIndex < ns; secondarySheetIndex++)
|
||||
if (sheets[secondarySheetIndex] == secondarySheet)
|
||||
break;
|
||||
|
||||
@@ -100,22 +99,22 @@ namespace OpenRA.Graphics
|
||||
secondarySheetIndex = ss != null && ss.SecondarySheet != sheet ? 1 : 0;
|
||||
}
|
||||
|
||||
if (sheetIndex >= sheetCount)
|
||||
if (sheetIndex >= ns)
|
||||
{
|
||||
sheets[sheetIndex] = sheet;
|
||||
sheetCount++;
|
||||
ns++;
|
||||
}
|
||||
|
||||
if (secondarySheetIndex >= sheetCount && ss != null)
|
||||
if (secondarySheetIndex >= ns && ss != null)
|
||||
{
|
||||
sheets[secondarySheetIndex] = ss.SecondarySheet;
|
||||
sheetCount++;
|
||||
ns++;
|
||||
}
|
||||
|
||||
return new int2(sheetIndex, secondarySheetIndex);
|
||||
}
|
||||
|
||||
static int ResolveTextureIndex(Sprite s, PaletteReference pal)
|
||||
float ResolveTextureIndex(Sprite s, PaletteReference pal)
|
||||
{
|
||||
if (pal == null)
|
||||
return 0;
|
||||
@@ -129,20 +128,20 @@ namespace OpenRA.Graphics
|
||||
return pal.TextureIndex;
|
||||
}
|
||||
|
||||
internal void DrawSprite(Sprite s, int paletteTextureIndex, in float3 location, in float3 scale, float rotation = 0f)
|
||||
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, in float3 scale, float rotation = 0f)
|
||||
{
|
||||
var samplers = SetRenderStateForSprite(s);
|
||||
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, vertexCount, scale * s.Size, float3.Ones,
|
||||
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, float3.Ones,
|
||||
1f, rotation);
|
||||
vertexCount += 4;
|
||||
nv += 6;
|
||||
}
|
||||
|
||||
internal void DrawSprite(Sprite s, int paletteTextureIndex, in float3 location, float scale, float rotation = 0f)
|
||||
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, float scale, float rotation = 0f)
|
||||
{
|
||||
var samplers = SetRenderStateForSprite(s);
|
||||
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, vertexCount, scale * s.Size, float3.Ones,
|
||||
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, float3.Ones,
|
||||
1f, rotation);
|
||||
vertexCount += 4;
|
||||
nv += 6;
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, PaletteReference pal, in float3 location, float scale = 1f, float rotation = 0f)
|
||||
@@ -150,13 +149,13 @@ namespace OpenRA.Graphics
|
||||
DrawSprite(s, ResolveTextureIndex(s, pal), location, scale, rotation);
|
||||
}
|
||||
|
||||
internal void DrawSprite(Sprite s, int paletteTextureIndex, in float3 location, float scale, in float3 tint, float alpha,
|
||||
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, float scale, in float3 tint, float alpha,
|
||||
float rotation = 0f)
|
||||
{
|
||||
var samplers = SetRenderStateForSprite(s);
|
||||
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, vertexCount, scale * s.Size, tint, alpha,
|
||||
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, tint, alpha,
|
||||
rotation);
|
||||
vertexCount += 4;
|
||||
nv += 6;
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, PaletteReference pal, in float3 location, float scale, in float3 tint, float alpha,
|
||||
@@ -165,14 +164,14 @@ namespace OpenRA.Graphics
|
||||
DrawSprite(s, ResolveTextureIndex(s, pal), location, scale, tint, alpha, rotation);
|
||||
}
|
||||
|
||||
internal void DrawSprite(Sprite s, int paletteTextureIndex, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint, float alpha)
|
||||
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint, float alpha)
|
||||
{
|
||||
var samplers = SetRenderStateForSprite(s);
|
||||
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, paletteTextureIndex, tint, alpha, vertexCount);
|
||||
vertexCount += 4;
|
||||
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, paletteTextureIndex, tint, alpha, nv);
|
||||
nv += 6;
|
||||
}
|
||||
|
||||
public void DrawVertexBuffer(IVertexBuffer<Vertex> buffer, IIndexBuffer indices, int start, int length, IEnumerable<Sheet> sheets, BlendMode blendMode)
|
||||
public void DrawVertexBuffer(IVertexBuffer<Vertex> buffer, int start, int length, PrimitiveType type, IEnumerable<Sheet> sheets, BlendMode blendMode)
|
||||
{
|
||||
var i = 0;
|
||||
foreach (var s in sheets)
|
||||
@@ -186,7 +185,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
renderer.Context.SetBlendMode(blendMode);
|
||||
shader.PrepareRender();
|
||||
renderer.DrawQuadBatch(buffer, indices, shader, length, UintSize * start);
|
||||
renderer.DrawBatch(buffer, start, length, type);
|
||||
renderer.Context.SetBlendMode(BlendMode.None);
|
||||
}
|
||||
|
||||
@@ -197,32 +196,29 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
|
||||
// For RGBAColorRenderer
|
||||
internal void DrawRGBAQuad(Vertex[] v, BlendMode blendMode)
|
||||
internal void DrawRGBAVertices(Vertex[] v, BlendMode blendMode)
|
||||
{
|
||||
renderer.CurrentBatchRenderer = this;
|
||||
|
||||
if (currentBlend != blendMode || vertexCount + 4 > renderer.TempVertexBufferSize)
|
||||
if (currentBlend != blendMode || nv + v.Length > renderer.TempBufferSize)
|
||||
Flush();
|
||||
|
||||
currentBlend = blendMode;
|
||||
|
||||
Array.Copy(v, 0, vertices, vertexCount, v.Length);
|
||||
vertexCount += 4;
|
||||
Array.Copy(v, 0, vertices, nv, v.Length);
|
||||
nv += v.Length;
|
||||
}
|
||||
|
||||
public void SetPalette(HardwarePalette palette)
|
||||
public void SetPalette(ITexture palette, ITexture colorShifts)
|
||||
{
|
||||
shader.SetTexture("Palette", palette.Texture);
|
||||
shader.SetTexture("ColorShifts", palette.ColorShifts);
|
||||
shader.SetVec("PaletteRows", palette.Height);
|
||||
shader.SetTexture("Palette", palette);
|
||||
shader.SetTexture("ColorShifts", colorShifts);
|
||||
}
|
||||
|
||||
public void SetViewportParams(Size sheetSize, int downscale, float depthMargin, int2 scroll)
|
||||
{
|
||||
// OpenGL only renders x and y coordinates inside [-1, 1] range. We project world coordinates
|
||||
// using p1 to values [0, 2] and then subtract by 1 using p2, where p stands for projection. It's
|
||||
// standard practice for shaders to use a projection matrix, but as we project orthographically
|
||||
// we are able to send less data to the GPU.
|
||||
// Calculate the scale (r1) and offset (r2) that convert from OpenRA viewport pixels
|
||||
// to OpenGL normalized device coordinates (NDC). OpenGL expects coordinates to vary from [-1, 1],
|
||||
// so we rescale viewport pixels to the range [0, 2] using r1 then subtract 1 using r2.
|
||||
var width = 2f / (downscale * sheetSize.Width);
|
||||
var height = 2f / (downscale * sheetSize.Height);
|
||||
|
||||
@@ -243,8 +239,8 @@ namespace OpenRA.Graphics
|
||||
var depth = depthMargin != 0f ? 2f / (downscale * (sheetSize.Height + depthMargin)) : 0;
|
||||
shader.SetVec("DepthTextureScale", 128 * depth);
|
||||
shader.SetVec("Scroll", scroll.X, scroll.Y, depthMargin != 0f ? scroll.Y : 0);
|
||||
shader.SetVec("p1", width, height, -depth);
|
||||
shader.SetVec("p2", -1, -1, depthMargin != 0f ? 1 : 0);
|
||||
shader.SetVec("r1", width, height, -depth);
|
||||
shader.SetVec("r2", -1, -1, depthMargin != 0f ? 1 : 0);
|
||||
}
|
||||
|
||||
public void SetDepthPreview(bool enabled, float contrast, float offset)
|
||||
|
||||
@@ -12,15 +12,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public sealed class TerrainSpriteLayer : IDisposable
|
||||
{
|
||||
// PERF: we can reuse the IndexBuffer as all layers have the same size.
|
||||
static readonly ConditionalWeakTable<World, IndexBufferRc> IndexBuffers = new();
|
||||
readonly IndexBufferRc indexBufferWrapper;
|
||||
static readonly int[] CornerVertexMap = { 0, 1, 2, 2, 3, 0 };
|
||||
|
||||
public readonly BlendMode BlendMode;
|
||||
|
||||
@@ -31,8 +28,7 @@ namespace OpenRA.Graphics
|
||||
readonly Vertex[] vertices;
|
||||
readonly bool[] ignoreTint;
|
||||
readonly HashSet<int> dirtyRows = new();
|
||||
readonly int indexRowStride;
|
||||
readonly int vertexRowStride;
|
||||
readonly int rowStride;
|
||||
readonly bool restrictToBounds;
|
||||
|
||||
readonly WorldRenderer worldRenderer;
|
||||
@@ -47,25 +43,19 @@ namespace OpenRA.Graphics
|
||||
this.emptySprite = emptySprite;
|
||||
sheets = new Sheet[SpriteRenderer.SheetCount];
|
||||
BlendMode = blendMode;
|
||||
|
||||
map = world.Map;
|
||||
rowStride = 6 * map.MapSize.X;
|
||||
|
||||
vertexRowStride = 4 * map.MapSize.X;
|
||||
vertices = new Vertex[vertexRowStride * map.MapSize.Y];
|
||||
vertexBuffer = Game.Renderer.Context.CreateVertexBuffer<Vertex>(vertices.Length);
|
||||
|
||||
indexRowStride = 6 * map.MapSize.X;
|
||||
lock (IndexBuffers)
|
||||
{
|
||||
indexBufferWrapper = IndexBuffers.GetValue(world, world => new IndexBufferRc(world));
|
||||
indexBufferWrapper.AddRef();
|
||||
}
|
||||
|
||||
vertices = new Vertex[rowStride * map.MapSize.Y];
|
||||
palettes = new PaletteReference[map.MapSize.X * map.MapSize.Y];
|
||||
vertexBuffer = Game.Renderer.Context.CreateVertexBuffer(vertices.Length);
|
||||
|
||||
wr.PaletteInvalidated += UpdatePaletteIndices;
|
||||
|
||||
if (wr.TerrainLighting != null)
|
||||
{
|
||||
ignoreTint = new bool[vertexRowStride * map.MapSize.Y];
|
||||
ignoreTint = new bool[rowStride * map.MapSize.Y];
|
||||
wr.TerrainLighting.CellChanged += UpdateTint;
|
||||
}
|
||||
}
|
||||
@@ -75,9 +65,8 @@ namespace OpenRA.Graphics
|
||||
for (var i = 0; i < vertices.Length; i++)
|
||||
{
|
||||
var v = vertices[i];
|
||||
var p = palettes[i / 4]?.TextureIndex ?? 0;
|
||||
var c = (uint)((p & 0xFFFF) << 16) | (v.C & 0xFFFF);
|
||||
vertices[i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, c, v.R, v.G, v.B, v.A);
|
||||
var p = palettes[i / 6]?.TextureIndex ?? 0;
|
||||
vertices[i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, p, v.C, v.R, v.G, v.B, v.A);
|
||||
}
|
||||
|
||||
for (var row = 0; row < map.MapSize.Y; row++)
|
||||
@@ -108,13 +97,13 @@ namespace OpenRA.Graphics
|
||||
|
||||
void UpdateTint(MPos uv)
|
||||
{
|
||||
var offset = vertexRowStride * uv.V + 4 * uv.U;
|
||||
var offset = rowStride * uv.V + 6 * uv.U;
|
||||
if (ignoreTint[offset])
|
||||
{
|
||||
for (var i = 0; i < 4; i++)
|
||||
for (var i = 0; i < 6; i++)
|
||||
{
|
||||
var v = vertices[offset + i];
|
||||
vertices[offset + i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, v.C, v.A * float3.Ones, v.A);
|
||||
vertices[offset + i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, v.P, v.C, v.A * float3.Ones, v.A);
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -125,7 +114,7 @@ namespace OpenRA.Graphics
|
||||
// transparent for isometric tiles
|
||||
var tl = worldRenderer.TerrainLighting;
|
||||
var pos = map.CenterOfCell(uv.ToCPos(map));
|
||||
var step = map.Grid.TileScale / 2;
|
||||
var step = map.Grid.Type == MapGridType.RectangularIsometric ? 724 : 512;
|
||||
var weights = new[]
|
||||
{
|
||||
tl.TintAt(pos + new WVec(-step, -step, 0)),
|
||||
@@ -136,10 +125,10 @@ namespace OpenRA.Graphics
|
||||
|
||||
// Apply tint directly to the underlying vertices
|
||||
// This saves us from having to re-query the sprite information, which has not changed
|
||||
for (var i = 0; i < 4; i++)
|
||||
for (var i = 0; i < 6; i++)
|
||||
{
|
||||
var v = vertices[offset + i];
|
||||
vertices[offset + i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, v.C, v.A * weights[i], v.A);
|
||||
vertices[offset + i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, v.P, v.C, v.A * weights[CornerVertexMap[i]], v.A);
|
||||
}
|
||||
|
||||
dirtyRows.Add(uv.V);
|
||||
@@ -191,7 +180,7 @@ namespace OpenRA.Graphics
|
||||
if (!map.Tiles.Contains(uv))
|
||||
return;
|
||||
|
||||
var offset = vertexRowStride * uv.V + 4 * uv.U;
|
||||
var offset = rowStride * uv.V + 6 * uv.U;
|
||||
Util.FastCreateQuad(vertices, pos, sprite, samplers, palette?.TextureIndex ?? 0, offset, scale * sprite.Size, alpha * float3.Ones, alpha);
|
||||
palettes[uv.V * map.MapSize.X + uv.U] = palette;
|
||||
|
||||
@@ -220,13 +209,13 @@ namespace OpenRA.Graphics
|
||||
if (!dirtyRows.Remove(row))
|
||||
continue;
|
||||
|
||||
var rowOffset = vertexRowStride * row;
|
||||
vertexBuffer.SetData(vertices, rowOffset, rowOffset, vertexRowStride);
|
||||
var rowOffset = rowStride * row;
|
||||
vertexBuffer.SetData(vertices, rowOffset, rowOffset, rowStride);
|
||||
}
|
||||
|
||||
Game.Renderer.WorldSpriteRenderer.DrawVertexBuffer(
|
||||
vertexBuffer, indexBufferWrapper.Buffer, indexRowStride * firstRow,
|
||||
indexRowStride * (lastRow - firstRow), sheets, BlendMode);
|
||||
vertexBuffer, rowStride * firstRow, rowStride * (lastRow - firstRow),
|
||||
PrimitiveType.TriangleList, sheets, BlendMode);
|
||||
|
||||
Game.Renderer.Flush();
|
||||
}
|
||||
@@ -238,29 +227,6 @@ namespace OpenRA.Graphics
|
||||
worldRenderer.TerrainLighting.CellChanged -= UpdateTint;
|
||||
|
||||
vertexBuffer.Dispose();
|
||||
|
||||
lock (IndexBuffers)
|
||||
indexBufferWrapper.Dispose();
|
||||
}
|
||||
|
||||
sealed class IndexBufferRc : IDisposable
|
||||
{
|
||||
public IIndexBuffer Buffer;
|
||||
int count;
|
||||
|
||||
public IndexBufferRc(World world)
|
||||
{
|
||||
Buffer = Game.Renderer.Context.CreateIndexBuffer(Util.CreateQuadIndices(world.Map.MapSize.X * world.Map.MapSize.Y));
|
||||
}
|
||||
|
||||
public void AddRef() { count++; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
count--;
|
||||
if (count == 0)
|
||||
Buffer.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
@@ -21,17 +20,7 @@ namespace OpenRA.Graphics
|
||||
// yes, our channel order is nuts.
|
||||
static readonly int[] ChannelMasks = { 2, 1, 0, 3 };
|
||||
|
||||
public static uint[] CreateQuadIndices(int quads)
|
||||
{
|
||||
var indices = new uint[quads * 6];
|
||||
ReadOnlySpan<uint> cornerVertexMap = stackalloc uint[] { 0, 1, 2, 2, 3, 0 };
|
||||
for (var i = 0; i < indices.Length; i++)
|
||||
indices[i] = cornerVertexMap[i % 6] + (uint)(4 * (i / 6));
|
||||
|
||||
return indices;
|
||||
}
|
||||
|
||||
public static void FastCreateQuad(Vertex[] vertices, in float3 o, Sprite r, int2 samplers, int paletteTextureIndex, int nv,
|
||||
public static void FastCreateQuad(Vertex[] vertices, in float3 o, Sprite r, int2 samplers, float paletteTextureIndex, int nv,
|
||||
in float3 size, in float3 tint, float alpha, float rotation = 0f)
|
||||
{
|
||||
float3 a, b, c, d;
|
||||
@@ -73,7 +62,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public static void FastCreateQuad(Vertex[] vertices,
|
||||
in float3 a, in float3 b, in float3 c, in float3 d,
|
||||
Sprite r, int2 samplers, int paletteTextureIndex,
|
||||
Sprite r, int2 samplers, float paletteTextureIndex,
|
||||
in float3 tint, float alpha, int nv)
|
||||
{
|
||||
float sl = 0;
|
||||
@@ -95,33 +84,76 @@ namespace OpenRA.Graphics
|
||||
attribC |= samplers.Y << 9;
|
||||
}
|
||||
|
||||
attribC |= (paletteTextureIndex & 0xFFFF) << 16;
|
||||
|
||||
var uAttribC = (uint)attribC;
|
||||
vertices[nv] = new Vertex(a, r.Left, r.Top, sl, st, uAttribC, tint, alpha);
|
||||
vertices[nv + 1] = new Vertex(b, r.Right, r.Top, sr, st, uAttribC, tint, alpha);
|
||||
vertices[nv + 2] = new Vertex(c, r.Right, r.Bottom, sr, sb, uAttribC, tint, alpha);
|
||||
vertices[nv + 3] = new Vertex(d, r.Left, r.Bottom, sl, sb, uAttribC, tint, alpha);
|
||||
var fAttribC = (float)attribC;
|
||||
vertices[nv] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC, tint, alpha);
|
||||
vertices[nv + 1] = new Vertex(b, r.Right, r.Top, sr, st, paletteTextureIndex, fAttribC, tint, alpha);
|
||||
vertices[nv + 2] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC, tint, alpha);
|
||||
vertices[nv + 3] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC, tint, alpha);
|
||||
vertices[nv + 4] = new Vertex(d, r.Left, r.Bottom, sl, sb, paletteTextureIndex, fAttribC, tint, alpha);
|
||||
vertices[nv + 5] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC, tint, alpha);
|
||||
}
|
||||
|
||||
public static void FastCopyIntoChannel(Sprite dest, byte[] src, SpriteFrameType srcType, bool premultiplied = false)
|
||||
public static void FastCopyIntoChannel(Sprite dest, byte[] src, SpriteFrameType srcType)
|
||||
{
|
||||
var destData = dest.Sheet.GetData();
|
||||
var stride = dest.Sheet.Size.Width;
|
||||
var x = dest.Bounds.Left;
|
||||
var y = dest.Bounds.Top;
|
||||
var width = dest.Bounds.Width;
|
||||
var height = dest.Bounds.Height;
|
||||
|
||||
if (dest.Channel == TextureChannel.RGBA)
|
||||
{
|
||||
CopyIntoRgba(src, srcType, premultiplied, destData, x, y, width, height, stride);
|
||||
var destStride = dest.Sheet.Size.Width;
|
||||
unsafe
|
||||
{
|
||||
// Cast the data to an int array so we can copy the src data directly
|
||||
fixed (byte* bd = &destData[0])
|
||||
{
|
||||
var data = (int*)bd;
|
||||
var x = dest.Bounds.Left;
|
||||
var y = dest.Bounds.Top;
|
||||
|
||||
var k = 0;
|
||||
for (var j = 0; j < height; j++)
|
||||
{
|
||||
for (var i = 0; i < width; i++)
|
||||
{
|
||||
byte r, g, b, a;
|
||||
switch (srcType)
|
||||
{
|
||||
case SpriteFrameType.Bgra32:
|
||||
case SpriteFrameType.Bgr24:
|
||||
{
|
||||
b = src[k++];
|
||||
g = src[k++];
|
||||
r = src[k++];
|
||||
a = srcType == SpriteFrameType.Bgra32 ? src[k++] : (byte)255;
|
||||
break;
|
||||
}
|
||||
|
||||
case SpriteFrameType.Rgba32:
|
||||
case SpriteFrameType.Rgb24:
|
||||
{
|
||||
r = src[k++];
|
||||
g = src[k++];
|
||||
b = src[k++];
|
||||
a = srcType == SpriteFrameType.Rgba32 ? src[k++] : (byte)255;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException($"Unknown SpriteFrameType {srcType}");
|
||||
}
|
||||
|
||||
var cc = Color.FromArgb(a, r, g, b);
|
||||
data[(y + j) * destStride + x + i] = PremultiplyAlpha(cc).ToArgb();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Copy into single channel of destination.
|
||||
var destStride = stride * 4;
|
||||
var destOffset = destStride * y + x * 4 + ChannelMasks[(int)dest.Channel];
|
||||
var destStride = dest.Sheet.Size.Width * 4;
|
||||
var destOffset = destStride * dest.Bounds.Top + dest.Bounds.Left * 4 + ChannelMasks[(int)dest.Channel];
|
||||
var destSkip = destStride - 4 * width;
|
||||
|
||||
var srcOffset = 0;
|
||||
@@ -138,119 +170,56 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
static void CopyIntoRgba(
|
||||
byte[] src, SpriteFrameType srcType, bool premultiplied, byte[] dest, int x, int y, int width, int height, int stride)
|
||||
{
|
||||
var si = 0;
|
||||
var di = y * stride + x;
|
||||
var d = MemoryMarshal.Cast<byte, uint>(dest);
|
||||
|
||||
// SpriteFrameType.Brga32 is a common source format, and it matches the destination format.
|
||||
// Provide a fast past that just performs memory copies.
|
||||
if (srcType == SpriteFrameType.Bgra32)
|
||||
{
|
||||
var s = MemoryMarshal.Cast<byte, uint>(src);
|
||||
for (var h = 0; h < height; h++)
|
||||
{
|
||||
s[si..(si + width)].CopyTo(d[di..(di + width)]);
|
||||
|
||||
if (!premultiplied)
|
||||
{
|
||||
for (var w = 0; w < width; w++)
|
||||
{
|
||||
d[di] = PremultiplyAlpha(Color.FromArgb(d[di])).ToArgb();
|
||||
di++;
|
||||
}
|
||||
|
||||
di -= width;
|
||||
}
|
||||
|
||||
si += width;
|
||||
di += stride;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (var h = 0; h < height; h++)
|
||||
{
|
||||
for (var w = 0; w < width; w++)
|
||||
{
|
||||
byte r, g, b, a;
|
||||
switch (srcType)
|
||||
{
|
||||
case SpriteFrameType.Bgra32:
|
||||
case SpriteFrameType.Bgr24:
|
||||
b = src[si++];
|
||||
g = src[si++];
|
||||
r = src[si++];
|
||||
a = srcType == SpriteFrameType.Bgra32 ? src[si++] : byte.MaxValue;
|
||||
break;
|
||||
|
||||
case SpriteFrameType.Rgba32:
|
||||
case SpriteFrameType.Rgb24:
|
||||
r = src[si++];
|
||||
g = src[si++];
|
||||
b = src[si++];
|
||||
a = srcType == SpriteFrameType.Rgba32 ? src[si++] : byte.MaxValue;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException($"Unknown SpriteFrameType {srcType}");
|
||||
}
|
||||
|
||||
var c = Color.FromArgb(a, r, g, b);
|
||||
if (!premultiplied)
|
||||
c = PremultiplyAlpha(c);
|
||||
d[di++] = c.ToArgb();
|
||||
}
|
||||
|
||||
di += stride - width;
|
||||
}
|
||||
}
|
||||
|
||||
public static void FastCopyIntoSprite(Sprite dest, Png src)
|
||||
{
|
||||
var destData = dest.Sheet.GetData();
|
||||
var stride = dest.Sheet.Size.Width;
|
||||
var x = dest.Bounds.Left;
|
||||
var y = dest.Bounds.Top;
|
||||
var destStride = dest.Sheet.Size.Width;
|
||||
var width = dest.Bounds.Width;
|
||||
var height = dest.Bounds.Height;
|
||||
|
||||
var si = 0;
|
||||
var di = y * stride + x;
|
||||
var d = MemoryMarshal.Cast<byte, uint>(destData);
|
||||
|
||||
for (var h = 0; h < height; h++)
|
||||
unsafe
|
||||
{
|
||||
for (var w = 0; w < width; w++)
|
||||
// Cast the data to an int array so we can copy the src data directly
|
||||
fixed (byte* bd = &destData[0])
|
||||
{
|
||||
Color c;
|
||||
switch (src.Type)
|
||||
var data = (int*)bd;
|
||||
var x = dest.Bounds.Left;
|
||||
var y = dest.Bounds.Top;
|
||||
|
||||
var k = 0;
|
||||
for (var j = 0; j < height; j++)
|
||||
{
|
||||
case SpriteFrameType.Indexed8:
|
||||
c = src.Palette[src.Data[si++]];
|
||||
break;
|
||||
for (var i = 0; i < width; i++)
|
||||
{
|
||||
Color cc;
|
||||
switch (src.Type)
|
||||
{
|
||||
case SpriteFrameType.Indexed8:
|
||||
{
|
||||
cc = src.Palette[src.Data[k++]];
|
||||
break;
|
||||
}
|
||||
|
||||
case SpriteFrameType.Rgba32:
|
||||
case SpriteFrameType.Rgb24:
|
||||
var r = src.Data[si++];
|
||||
var g = src.Data[si++];
|
||||
var b = src.Data[si++];
|
||||
var a = src.Type == SpriteFrameType.Rgba32 ? src.Data[si++] : byte.MaxValue;
|
||||
c = Color.FromArgb(a, r, g, b);
|
||||
break;
|
||||
case SpriteFrameType.Rgba32:
|
||||
case SpriteFrameType.Rgb24:
|
||||
{
|
||||
var r = src.Data[k++];
|
||||
var g = src.Data[k++];
|
||||
var b = src.Data[k++];
|
||||
var a = src.Type == SpriteFrameType.Rgba32 ? src.Data[k++] : (byte)255;
|
||||
cc = Color.FromArgb(a, r, g, b);
|
||||
break;
|
||||
}
|
||||
|
||||
// PNGs don't support BGR[A], so no need to include them here
|
||||
default:
|
||||
throw new InvalidOperationException($"Unknown SpriteFrameType {src.Type}");
|
||||
// Pngs don't support BGR[A], so no need to include them here
|
||||
default:
|
||||
throw new InvalidOperationException($"Unknown SpriteFrameType {src.Type}");
|
||||
}
|
||||
|
||||
data[(y + j) * destStride + x + i] = PremultiplyAlpha(cc).ToArgb();
|
||||
}
|
||||
}
|
||||
|
||||
d[di++] = PremultiplyAlpha(c).ToArgb();
|
||||
}
|
||||
|
||||
di += stride - width;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,5 +305,239 @@ namespace OpenRA.Graphics
|
||||
(int)((byte)(t * a2 * c2.G + 0.5f) + (1 - t) * (byte)(a1 * c1.G + 0.5f)),
|
||||
(int)((byte)(t * a2 * c2.B + 0.5f) + (1 - t) * (byte)(a1 * c1.B + 0.5f))));
|
||||
}
|
||||
|
||||
public static float[] IdentityMatrix()
|
||||
{
|
||||
return Exts.MakeArray(16, j => (j % 5 == 0) ? 1.0f : 0);
|
||||
}
|
||||
|
||||
public static float[] ScaleMatrix(float sx, float sy, float sz)
|
||||
{
|
||||
var mtx = IdentityMatrix();
|
||||
mtx[0] = sx;
|
||||
mtx[5] = sy;
|
||||
mtx[10] = sz;
|
||||
return mtx;
|
||||
}
|
||||
|
||||
public static float[] TranslationMatrix(float x, float y, float z)
|
||||
{
|
||||
var mtx = IdentityMatrix();
|
||||
mtx[12] = x;
|
||||
mtx[13] = y;
|
||||
mtx[14] = z;
|
||||
return mtx;
|
||||
}
|
||||
|
||||
public static float[] MatrixMultiply(float[] lhs, float[] rhs)
|
||||
{
|
||||
var mtx = new float[16];
|
||||
for (var i = 0; i < 4; i++)
|
||||
for (var j = 0; j < 4; j++)
|
||||
{
|
||||
mtx[4 * i + j] = 0;
|
||||
for (var k = 0; k < 4; k++)
|
||||
mtx[4 * i + j] += lhs[4 * k + j] * rhs[4 * i + k];
|
||||
}
|
||||
|
||||
return mtx;
|
||||
}
|
||||
|
||||
public static float[] MatrixVectorMultiply(float[] mtx, float[] vec)
|
||||
{
|
||||
var ret = new float[4];
|
||||
for (var j = 0; j < 4; j++)
|
||||
{
|
||||
ret[j] = 0;
|
||||
for (var k = 0; k < 4; k++)
|
||||
ret[j] += mtx[4 * k + j] * vec[k];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static float[] MatrixInverse(float[] m)
|
||||
{
|
||||
var mtx = new float[16];
|
||||
|
||||
mtx[0] = m[5] * m[10] * m[15] -
|
||||
m[5] * m[11] * m[14] -
|
||||
m[9] * m[6] * m[15] +
|
||||
m[9] * m[7] * m[14] +
|
||||
m[13] * m[6] * m[11] -
|
||||
m[13] * m[7] * m[10];
|
||||
|
||||
mtx[4] = -m[4] * m[10] * m[15] +
|
||||
m[4] * m[11] * m[14] +
|
||||
m[8] * m[6] * m[15] -
|
||||
m[8] * m[7] * m[14] -
|
||||
m[12] * m[6] * m[11] +
|
||||
m[12] * m[7] * m[10];
|
||||
|
||||
mtx[8] = m[4] * m[9] * m[15] -
|
||||
m[4] * m[11] * m[13] -
|
||||
m[8] * m[5] * m[15] +
|
||||
m[8] * m[7] * m[13] +
|
||||
m[12] * m[5] * m[11] -
|
||||
m[12] * m[7] * m[9];
|
||||
|
||||
mtx[12] = -m[4] * m[9] * m[14] +
|
||||
m[4] * m[10] * m[13] +
|
||||
m[8] * m[5] * m[14] -
|
||||
m[8] * m[6] * m[13] -
|
||||
m[12] * m[5] * m[10] +
|
||||
m[12] * m[6] * m[9];
|
||||
|
||||
mtx[1] = -m[1] * m[10] * m[15] +
|
||||
m[1] * m[11] * m[14] +
|
||||
m[9] * m[2] * m[15] -
|
||||
m[9] * m[3] * m[14] -
|
||||
m[13] * m[2] * m[11] +
|
||||
m[13] * m[3] * m[10];
|
||||
|
||||
mtx[5] = m[0] * m[10] * m[15] -
|
||||
m[0] * m[11] * m[14] -
|
||||
m[8] * m[2] * m[15] +
|
||||
m[8] * m[3] * m[14] +
|
||||
m[12] * m[2] * m[11] -
|
||||
m[12] * m[3] * m[10];
|
||||
|
||||
mtx[9] = -m[0] * m[9] * m[15] +
|
||||
m[0] * m[11] * m[13] +
|
||||
m[8] * m[1] * m[15] -
|
||||
m[8] * m[3] * m[13] -
|
||||
m[12] * m[1] * m[11] +
|
||||
m[12] * m[3] * m[9];
|
||||
|
||||
mtx[13] = m[0] * m[9] * m[14] -
|
||||
m[0] * m[10] * m[13] -
|
||||
m[8] * m[1] * m[14] +
|
||||
m[8] * m[2] * m[13] +
|
||||
m[12] * m[1] * m[10] -
|
||||
m[12] * m[2] * m[9];
|
||||
|
||||
mtx[2] = m[1] * m[6] * m[15] -
|
||||
m[1] * m[7] * m[14] -
|
||||
m[5] * m[2] * m[15] +
|
||||
m[5] * m[3] * m[14] +
|
||||
m[13] * m[2] * m[7] -
|
||||
m[13] * m[3] * m[6];
|
||||
|
||||
mtx[6] = -m[0] * m[6] * m[15] +
|
||||
m[0] * m[7] * m[14] +
|
||||
m[4] * m[2] * m[15] -
|
||||
m[4] * m[3] * m[14] -
|
||||
m[12] * m[2] * m[7] +
|
||||
m[12] * m[3] * m[6];
|
||||
|
||||
mtx[10] = m[0] * m[5] * m[15] -
|
||||
m[0] * m[7] * m[13] -
|
||||
m[4] * m[1] * m[15] +
|
||||
m[4] * m[3] * m[13] +
|
||||
m[12] * m[1] * m[7] -
|
||||
m[12] * m[3] * m[5];
|
||||
|
||||
mtx[14] = -m[0] * m[5] * m[14] +
|
||||
m[0] * m[6] * m[13] +
|
||||
m[4] * m[1] * m[14] -
|
||||
m[4] * m[2] * m[13] -
|
||||
m[12] * m[1] * m[6] +
|
||||
m[12] * m[2] * m[5];
|
||||
|
||||
mtx[3] = -m[1] * m[6] * m[11] +
|
||||
m[1] * m[7] * m[10] +
|
||||
m[5] * m[2] * m[11] -
|
||||
m[5] * m[3] * m[10] -
|
||||
m[9] * m[2] * m[7] +
|
||||
m[9] * m[3] * m[6];
|
||||
|
||||
mtx[7] = m[0] * m[6] * m[11] -
|
||||
m[0] * m[7] * m[10] -
|
||||
m[4] * m[2] * m[11] +
|
||||
m[4] * m[3] * m[10] +
|
||||
m[8] * m[2] * m[7] -
|
||||
m[8] * m[3] * m[6];
|
||||
|
||||
mtx[11] = -m[0] * m[5] * m[11] +
|
||||
m[0] * m[7] * m[9] +
|
||||
m[4] * m[1] * m[11] -
|
||||
m[4] * m[3] * m[9] -
|
||||
m[8] * m[1] * m[7] +
|
||||
m[8] * m[3] * m[5];
|
||||
|
||||
mtx[15] = m[0] * m[5] * m[10] -
|
||||
m[0] * m[6] * m[9] -
|
||||
m[4] * m[1] * m[10] +
|
||||
m[4] * m[2] * m[9] +
|
||||
m[8] * m[1] * m[6] -
|
||||
m[8] * m[2] * m[5];
|
||||
|
||||
var det = m[0] * mtx[0] + m[1] * mtx[4] + m[2] * mtx[8] + m[3] * mtx[12];
|
||||
if (det == 0)
|
||||
return null;
|
||||
|
||||
for (var i = 0; i < 16; i++)
|
||||
mtx[i] *= 1 / det;
|
||||
|
||||
return mtx;
|
||||
}
|
||||
|
||||
public static float[] MakeFloatMatrix(Int32Matrix4x4 imtx)
|
||||
{
|
||||
var multipler = 1f / imtx.M44;
|
||||
return new[]
|
||||
{
|
||||
imtx.M11 * multipler,
|
||||
imtx.M12 * multipler,
|
||||
imtx.M13 * multipler,
|
||||
imtx.M14 * multipler,
|
||||
|
||||
imtx.M21 * multipler,
|
||||
imtx.M22 * multipler,
|
||||
imtx.M23 * multipler,
|
||||
imtx.M24 * multipler,
|
||||
|
||||
imtx.M31 * multipler,
|
||||
imtx.M32 * multipler,
|
||||
imtx.M33 * multipler,
|
||||
imtx.M34 * multipler,
|
||||
|
||||
imtx.M41 * multipler,
|
||||
imtx.M42 * multipler,
|
||||
imtx.M43 * multipler,
|
||||
imtx.M44 * multipler,
|
||||
};
|
||||
}
|
||||
|
||||
public static float[] MatrixAABBMultiply(float[] mtx, float[] bounds)
|
||||
{
|
||||
// Corner offsets
|
||||
var ix = new uint[] { 0, 0, 0, 0, 3, 3, 3, 3 };
|
||||
var iy = new uint[] { 1, 1, 4, 4, 1, 1, 4, 4 };
|
||||
var iz = new uint[] { 2, 5, 2, 5, 2, 5, 2, 5 };
|
||||
|
||||
// Vectors to opposing corner
|
||||
var ret = new[]
|
||||
{
|
||||
float.MaxValue, float.MaxValue, float.MaxValue,
|
||||
float.MinValue, float.MinValue, float.MinValue
|
||||
};
|
||||
|
||||
// Transform vectors and find new bounding box
|
||||
for (var i = 0; i < 8; i++)
|
||||
{
|
||||
var vec = new[] { bounds[ix[i]], bounds[iy[i]], bounds[iz[i]], 1 };
|
||||
var tvec = MatrixVectorMultiply(mtx, vec);
|
||||
|
||||
ret[0] = Math.Min(ret[0], tvec[0] / tvec[3]);
|
||||
ret[1] = Math.Min(ret[1], tvec[1] / tvec[3]);
|
||||
ret[2] = Math.Min(ret[2], tvec[2] / tvec[3]);
|
||||
ret[3] = Math.Max(ret[3], tvec[0] / tvec[3]);
|
||||
ret[4] = Math.Max(ret[4], tvec[1] / tvec[3]);
|
||||
ret[5] = Math.Max(ret[5], tvec[2] / tvec[3]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,42 +23,27 @@ namespace OpenRA.Graphics
|
||||
public readonly float S, T, U, V;
|
||||
|
||||
// Palette and channel flags
|
||||
public readonly uint C;
|
||||
public readonly float P, C;
|
||||
|
||||
// Color tint
|
||||
public readonly float R, G, B, A;
|
||||
|
||||
public Vertex(in float3 xyz, float s, float t, float u, float v, uint c)
|
||||
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, c, float3.Ones, 1f) { }
|
||||
public Vertex(in float3 xyz, float s, float t, float u, float v, float p, float c)
|
||||
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c, float3.Ones, 1f) { }
|
||||
|
||||
public Vertex(in float3 xyz, float s, float t, float u, float v, uint c, in float3 tint, float a)
|
||||
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, c, tint.X, tint.Y, tint.Z, a) { }
|
||||
public Vertex(in float3 xyz, float s, float t, float u, float v, float p, float c, in float3 tint, float a)
|
||||
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c, tint.X, tint.Y, tint.Z, a) { }
|
||||
|
||||
public Vertex(float x, float y, float z, float s, float t, float u, float v, uint c, in float3 tint, float a)
|
||||
: this(x, y, z, s, t, u, v, c, tint.X, tint.Y, tint.Z, a) { }
|
||||
public Vertex(float x, float y, float z, float s, float t, float u, float v, float p, float c, in float3 tint, float a)
|
||||
: this(x, y, z, s, t, u, v, p, c, tint.X, tint.Y, tint.Z, a) { }
|
||||
|
||||
public Vertex(float x, float y, float z, float s, float t, float u, float v, uint c, float r, float g, float b, float a)
|
||||
public Vertex(float x, float y, float z, float s, float t, float u, float v, float p, float c, float r, float g, float b, float a)
|
||||
{
|
||||
X = x; Y = y; Z = z;
|
||||
S = s; T = t;
|
||||
U = u; V = v;
|
||||
C = c;
|
||||
P = p; C = c;
|
||||
R = r; G = g; B = b; A = a;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class CombinedShaderBindings : ShaderBindings
|
||||
{
|
||||
public CombinedShaderBindings()
|
||||
: base("combined")
|
||||
{ }
|
||||
|
||||
public override ShaderVertexAttribute[] Attributes { get; } = new[]
|
||||
{
|
||||
new ShaderVertexAttribute("aVertexPosition", ShaderVertexAttributeType.Float, 3, 0),
|
||||
new ShaderVertexAttribute("aVertexTexCoord", ShaderVertexAttributeType.Float, 4, 12),
|
||||
new ShaderVertexAttribute("aVertexAttributes", ShaderVertexAttributeType.UInt, 1, 28),
|
||||
new ShaderVertexAttribute("aVertexTint", ShaderVertexAttributeType.Float, 4, 32)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ namespace OpenRA.Graphics
|
||||
UpdateViewportZooms();
|
||||
}
|
||||
|
||||
static float CalculateMinimumZoom(float minHeight, float maxHeight)
|
||||
float CalculateMinimumZoom(float minHeight, float maxHeight)
|
||||
{
|
||||
var h = Game.Renderer.NativeResolution.Height;
|
||||
|
||||
@@ -282,23 +282,11 @@ namespace OpenRA.Graphics
|
||||
IEnumerable<MPos> CandidateMouseoverCells(int2 world)
|
||||
{
|
||||
var map = worldRenderer.World.Map;
|
||||
var tileScale = map.Grid.TileScale / 2;
|
||||
var minPos = worldRenderer.ProjectedPosition(world);
|
||||
|
||||
// Find all the cells that could potentially have been clicked.
|
||||
MPos a;
|
||||
MPos b;
|
||||
if (map.Grid.Type == MapGridType.RectangularIsometric)
|
||||
{
|
||||
// TODO: this generates too many cells.
|
||||
a = map.CellContaining(minPos - new WVec(tileScale, 0, 0)).ToMPos(map.Grid.Type);
|
||||
b = map.CellContaining(minPos + new WVec(tileScale, tileScale * map.Grid.MaximumTerrainHeight, 0)).ToMPos(map.Grid.Type);
|
||||
}
|
||||
else
|
||||
{
|
||||
a = map.CellContaining(minPos).ToMPos(map.Grid.Type);
|
||||
b = map.CellContaining(minPos + new WVec(0, tileScale * map.Grid.MaximumTerrainHeight, 0)).ToMPos(map.Grid.Type);
|
||||
}
|
||||
// Find all the cells that could potentially have been clicked
|
||||
var a = map.CellContaining(minPos - new WVec(1024, 0, 0)).ToMPos(map.Grid.Type);
|
||||
var b = map.CellContaining(minPos + new WVec(512, 512 * map.Grid.MaximumTerrainHeight, 0)).ToMPos(map.Grid.Type);
|
||||
|
||||
for (var v = b.V; v >= a.V; v--)
|
||||
for (var u = b.U; u >= a.U; u--)
|
||||
@@ -311,13 +299,10 @@ namespace OpenRA.Graphics
|
||||
|
||||
public void Center(IEnumerable<Actor> actors)
|
||||
{
|
||||
var actorsCollection = actors as IReadOnlyCollection<Actor>;
|
||||
actorsCollection ??= actors.ToList();
|
||||
|
||||
if (actorsCollection.Count == 0)
|
||||
if (!actors.Any())
|
||||
return;
|
||||
|
||||
Center(actorsCollection.Select(a => a.CenterPosition).Average());
|
||||
Center(actors.Select(a => a.CenterPosition).Average());
|
||||
}
|
||||
|
||||
public void Center(WPos pos)
|
||||
|
||||
@@ -44,14 +44,12 @@ namespace OpenRA.Graphics
|
||||
readonly List<IFinalizedRenderable> preparedAnnotationRenderables = new();
|
||||
|
||||
readonly List<IRenderable> renderablesBuffer = new();
|
||||
readonly IRenderer[] renderers;
|
||||
readonly IRenderPostProcessPass[] postProcessPasses;
|
||||
|
||||
internal WorldRenderer(ModData modData, World world)
|
||||
{
|
||||
World = world;
|
||||
TileSize = World.Map.Grid.TileSize;
|
||||
TileScale = World.Map.Grid.TileScale;
|
||||
TileScale = World.Map.Grid.Type == MapGridType.RectangularIsometric ? 1448 : 1024;
|
||||
Viewport = new Viewport(this, world.Map);
|
||||
|
||||
createPaletteReference = CreatePaletteReference;
|
||||
@@ -68,24 +66,9 @@ namespace OpenRA.Graphics
|
||||
palette.Initialize();
|
||||
|
||||
TerrainLighting = world.WorldActor.TraitOrDefault<ITerrainLighting>();
|
||||
renderers = world.WorldActor.TraitsImplementing<IRenderer>().ToArray();
|
||||
terrainRenderer = world.WorldActor.TraitOrDefault<IRenderTerrain>();
|
||||
|
||||
debugVis = Exts.Lazy(() => world.WorldActor.TraitOrDefault<DebugVisualizations>());
|
||||
|
||||
postProcessPasses = world.WorldActor.TraitsImplementing<IRenderPostProcessPass>().ToArray();
|
||||
}
|
||||
|
||||
public void BeginFrame()
|
||||
{
|
||||
foreach (var r in renderers)
|
||||
r.BeginFrame();
|
||||
}
|
||||
|
||||
public void EndFrame()
|
||||
{
|
||||
foreach (var r in renderers)
|
||||
r.EndFrame();
|
||||
}
|
||||
|
||||
public void UpdatePalettesForPlayer(string internalName, Color color, bool replaceExisting)
|
||||
@@ -104,7 +87,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
// HACK: This is working around the fact that palettes are defined on traits rather than sequences
|
||||
// and can be removed once this has been fixed.
|
||||
return string.IsNullOrEmpty(name) ? null : palettes.GetOrAdd(name, createPaletteReference);
|
||||
return name == null ? null : palettes.GetOrAdd(name, createPaletteReference);
|
||||
}
|
||||
|
||||
public void AddPalette(string name, ImmutablePalette pal, bool allowModifiers = false, bool allowOverwrite = false)
|
||||
@@ -126,8 +109,8 @@ namespace OpenRA.Graphics
|
||||
palette.ReplacePalette(name, pal);
|
||||
|
||||
// Update cached PlayerReference if one exists
|
||||
if (palettes.TryGetValue(name, out var paletteReference))
|
||||
paletteReference.Palette = pal;
|
||||
if (palettes.ContainsKey(name))
|
||||
palettes[name].Palette = pal;
|
||||
}
|
||||
|
||||
public void SetPaletteColorShift(string name, float hueOffset, float satOffset, float valueModifier, float minHue, float maxHue)
|
||||
@@ -287,8 +270,6 @@ namespace OpenRA.Graphics
|
||||
if (enableDepthBuffer)
|
||||
Game.Renderer.ClearDepthBuffer();
|
||||
|
||||
ApplyPostProcessing(PostProcessPassType.AfterActors);
|
||||
|
||||
World.ApplyToActorsWithTrait<IRenderAboveWorld>((actor, trait) =>
|
||||
{
|
||||
if (actor.IsInWorld && !actor.Disposed)
|
||||
@@ -298,8 +279,6 @@ namespace OpenRA.Graphics
|
||||
if (enableDepthBuffer)
|
||||
Game.Renderer.ClearDepthBuffer();
|
||||
|
||||
ApplyPostProcessing(PostProcessPassType.AfterWorld);
|
||||
|
||||
World.ApplyToActorsWithTrait<IRenderShroud>((actor, trait) => trait.RenderShroud(this));
|
||||
|
||||
if (enableDepthBuffer)
|
||||
@@ -313,23 +292,9 @@ namespace OpenRA.Graphics
|
||||
foreach (var r in g)
|
||||
r.Render(this);
|
||||
|
||||
ApplyPostProcessing(PostProcessPassType.AfterShroud);
|
||||
|
||||
Game.Renderer.Flush();
|
||||
}
|
||||
|
||||
void ApplyPostProcessing(PostProcessPassType type)
|
||||
{
|
||||
foreach (var pass in postProcessPasses)
|
||||
{
|
||||
if (pass.Type != type || !pass.Enabled)
|
||||
continue;
|
||||
|
||||
Game.Renderer.Flush();
|
||||
pass.Draw(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawAnnotations()
|
||||
{
|
||||
Game.Renderer.EnableAntialiasingFilter();
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
@@ -30,26 +31,29 @@ namespace OpenRA
|
||||
if (!string.IsNullOrEmpty(node.Value))
|
||||
Default = FieldLoader.GetValue<Hotkey>("value", node.Value);
|
||||
|
||||
var nodeDict = node.ToDictionary();
|
||||
var descriptionNode = node.Nodes.FirstOrDefault(n => n.Key == "Description");
|
||||
if (descriptionNode != null)
|
||||
Description = descriptionNode.Value.Value;
|
||||
|
||||
if (nodeDict.TryGetValue("Description", out var descriptionYaml))
|
||||
Description = descriptionYaml.Value;
|
||||
var typesNode = node.Nodes.FirstOrDefault(n => n.Key == "Types");
|
||||
if (typesNode != null)
|
||||
Types = FieldLoader.GetValue<HashSet<string>>("Types", typesNode.Value.Value);
|
||||
|
||||
if (nodeDict.TryGetValue("Types", out var typesYaml))
|
||||
Types = FieldLoader.GetValue<HashSet<string>>("Types", typesYaml.Value);
|
||||
var contextsNode = node.Nodes.FirstOrDefault(n => n.Key == "Contexts");
|
||||
if (contextsNode != null)
|
||||
Contexts = FieldLoader.GetValue<HashSet<string>>("Contexts", contextsNode.Value.Value);
|
||||
|
||||
if (nodeDict.TryGetValue("Contexts", out var contextYaml))
|
||||
Contexts = FieldLoader.GetValue<HashSet<string>>("Contexts", contextYaml.Value);
|
||||
|
||||
if (nodeDict.TryGetValue("Platform", out var platformYaml))
|
||||
var platformNode = node.Nodes.FirstOrDefault(n => n.Key == "Platform");
|
||||
if (platformNode != null)
|
||||
{
|
||||
var platformOverride = platformYaml.NodeWithKeyOrDefault(Platform.CurrentPlatform.ToString());
|
||||
var platformOverride = platformNode.Value.Nodes.FirstOrDefault(n => n.Key == Platform.CurrentPlatform.ToString());
|
||||
if (platformOverride != null)
|
||||
Default = FieldLoader.GetValue<Hotkey>("value", platformOverride.Value.Value);
|
||||
}
|
||||
|
||||
if (nodeDict.TryGetValue("Readonly", out var readonlyYaml))
|
||||
Readonly = FieldLoader.GetValue<bool>("Readonly", readonlyYaml.Value);
|
||||
var readonlyNode = node.Nodes.FirstOrDefault(n => n.Key == "Readonly");
|
||||
if (readonlyNode != null)
|
||||
Readonly = FieldLoader.GetValue<bool>("Readonly", readonlyNode.Value.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace OpenRA
|
||||
|
||||
foreach (var kv in settings)
|
||||
{
|
||||
if (definitions.TryGetValue(kv.Key, out var definition) && !definition.Readonly)
|
||||
if (definitions.ContainsKey(kv.Key) && !definitions[kv.Key].Readonly)
|
||||
keys[kv.Key] = kv.Value;
|
||||
}
|
||||
|
||||
@@ -43,9 +43,6 @@ namespace OpenRA
|
||||
hd.Value.HasDuplicates = GetFirstDuplicate(hd.Value, this[hd.Value.Name].GetValue()) != null;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage(
|
||||
"Performance", "CA1854:Prefer the 'IDictionary.TryGetValue(TKey, out TValue)' method",
|
||||
Justification = "Func must perform a live lookup in the collection, as the lookup value can change.")]
|
||||
internal Func<Hotkey> GetHotkeyReference(string name)
|
||||
{
|
||||
// Is this a mod-defined hotkey?
|
||||
|
||||
@@ -13,8 +13,7 @@ using System.Collections.Generic;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
// List of keycodes. Duplicated from SDL 2.0.1, with the addition
|
||||
// of MOUSE4 and MOUSE5.
|
||||
// List of keycodes, duplicated from SDL 2.0.1
|
||||
public enum Keycode
|
||||
{
|
||||
UNKNOWN = 0,
|
||||
@@ -253,8 +252,6 @@ namespace OpenRA
|
||||
KBDILLUMUP = 280 | (1 << 30),
|
||||
EJECT = 281 | (1 << 30),
|
||||
SLEEP = 282 | (1 << 30),
|
||||
MOUSE4 = 283 | (1 << 30),
|
||||
MOUSE5 = 284 | (1 << 30)
|
||||
}
|
||||
|
||||
public static class KeycodeExts
|
||||
@@ -497,8 +494,6 @@ namespace OpenRA
|
||||
{ Keycode.KBDILLUMUP, "KBDIllumUp" },
|
||||
{ Keycode.EJECT, "Eject" },
|
||||
{ Keycode.SLEEP, "Sleep" },
|
||||
{ Keycode.MOUSE4, "Mouse 4" },
|
||||
{ Keycode.MOUSE5, "Mouse 5" },
|
||||
};
|
||||
|
||||
public static string DisplayString(Keycode k)
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace OpenRA
|
||||
return mods;
|
||||
}
|
||||
|
||||
static Manifest LoadMod(string id, string path)
|
||||
Manifest LoadMod(string id, string path)
|
||||
{
|
||||
IReadOnlyPackage package = null;
|
||||
try
|
||||
@@ -79,7 +79,7 @@ namespace OpenRA
|
||||
return null;
|
||||
}
|
||||
|
||||
static Dictionary<string, Manifest> GetInstalledMods(IEnumerable<string> searchPaths, IEnumerable<string> explicitPaths)
|
||||
Dictionary<string, Manifest> GetInstalledMods(IEnumerable<string> searchPaths, IEnumerable<string> explicitPaths)
|
||||
{
|
||||
var ret = new Dictionary<string, Manifest>();
|
||||
var candidates = GetCandidateMods(searchPaths)
|
||||
|
||||
@@ -66,10 +66,8 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("Failed to load keys:");
|
||||
Console.WriteLine(e);
|
||||
Log.Write("debug", $"Failed to load player keypair from `{filePath}` with exception:");
|
||||
Log.Write("debug", e);
|
||||
Console.WriteLine("Failed to load keys: {0}", e);
|
||||
Log.Write("debug", "Failed to load player keypair from `{0}` with exception: {1}", filePath, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,17 +82,16 @@ namespace OpenRA
|
||||
{
|
||||
var client = HttpClientFactory.Create();
|
||||
|
||||
var url = playerDatabase.Profile + Fingerprint;
|
||||
var httpResponseMessage = await client.GetAsync(url);
|
||||
var httpResponseMessage = await client.GetAsync(playerDatabase.Profile + Fingerprint);
|
||||
var result = await httpResponseMessage.Content.ReadAsStreamAsync();
|
||||
|
||||
var yaml = MiniYaml.FromStream(result, url).First();
|
||||
var yaml = MiniYaml.FromStream(result).First();
|
||||
if (yaml.Key == "Player")
|
||||
{
|
||||
innerData = FieldLoader.Load<PlayerProfile>(yaml.Value);
|
||||
if (innerData.KeyRevoked)
|
||||
{
|
||||
Log.Write("debug", $"Revoking key with fingerprint {Fingerprint}");
|
||||
Log.Write("debug", "Revoking key with fingerprint {0}", Fingerprint);
|
||||
DeleteKeypair();
|
||||
}
|
||||
else
|
||||
@@ -105,8 +102,7 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to parse player data result with exception:");
|
||||
Log.Write("debug", e);
|
||||
Log.Write("debug", "Failed to parse player data result with exception: {0}", e);
|
||||
innerState = LinkState.ConnectionFailed;
|
||||
}
|
||||
finally
|
||||
@@ -140,10 +136,8 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to generate keypair with exception:");
|
||||
Log.Write("debug", e);
|
||||
Console.WriteLine("Key generation failed:");
|
||||
Console.WriteLine(e);
|
||||
Log.Write("debug", "Failed to generate keypair with exception: {1}", e);
|
||||
Console.WriteLine("Key generation failed: {0}", e);
|
||||
|
||||
innerState = LinkState.Uninitialized;
|
||||
}
|
||||
@@ -158,10 +152,8 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to delete keypair with exception:");
|
||||
Log.Write("debug", e);
|
||||
Console.WriteLine("Key deletion failed:");
|
||||
Console.WriteLine(e);
|
||||
Log.Write("debug", "Failed to delete keypair with exception: {1}", e);
|
||||
Console.WriteLine("Key deletion failed: {0}", e);
|
||||
}
|
||||
|
||||
innerState = LinkState.Uninitialized;
|
||||
|
||||
26
OpenRA.Game/LogProxy.cs
Normal file
26
OpenRA.Game/LogProxy.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public interface ILog
|
||||
{
|
||||
void Write(string channel, string format, params object[] args);
|
||||
}
|
||||
|
||||
public class LogProxy : ILog
|
||||
{
|
||||
public void Write(string channel, string format, params object[] args)
|
||||
{
|
||||
Log.Write(channel, format, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,17 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ModelSequenceFormat : IGlobalModData
|
||||
{
|
||||
public readonly string Type;
|
||||
public readonly IReadOnlyDictionary<string, MiniYaml> Metadata;
|
||||
public ModelSequenceFormat(MiniYaml yaml)
|
||||
{
|
||||
Type = yaml.Value;
|
||||
Metadata = new ReadOnlyDictionary<string, MiniYaml>(yaml.ToDictionary());
|
||||
}
|
||||
}
|
||||
|
||||
public class ModMetadata
|
||||
{
|
||||
public string Title;
|
||||
@@ -54,7 +65,7 @@ namespace OpenRA
|
||||
}
|
||||
|
||||
/// <summary>Describes what is to be loaded in order to run a mod.</summary>
|
||||
public sealed class Manifest : IDisposable
|
||||
public class Manifest : IDisposable
|
||||
{
|
||||
public readonly string Id;
|
||||
public readonly IReadOnlyPackage Package;
|
||||
@@ -94,8 +105,7 @@ namespace OpenRA
|
||||
Id = modId;
|
||||
Package = package;
|
||||
|
||||
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
||||
var nodes = MiniYaml.FromStream(package.GetStream("mod.yaml"), $"{package.Name}:mod.yaml", stringPool: stringPool);
|
||||
var nodes = MiniYaml.FromStream(package.GetStream("mod.yaml"), "mod.yaml");
|
||||
for (var i = nodes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (nodes[i].Key != "Include")
|
||||
@@ -108,7 +118,7 @@ namespace OpenRA
|
||||
throw new YamlException($"{nodes[i].Location}: File `{filename}` not found.");
|
||||
|
||||
nodes.RemoveAt(i);
|
||||
nodes.InsertRange(i, MiniYaml.FromStream(contents, $"{package.Name}:{filename}", stringPool: stringPool));
|
||||
nodes.InsertRange(i, MiniYaml.FromStream(contents, filename));
|
||||
}
|
||||
|
||||
// Merge inherited overrides
|
||||
@@ -147,25 +157,25 @@ namespace OpenRA
|
||||
// Allow inherited mods to import parent maps.
|
||||
var compat = new List<string> { Id };
|
||||
|
||||
if (yaml.TryGetValue("SupportsMapsFrom", out var entry))
|
||||
compat.AddRange(entry.Value.Split(',').Select(c => c.Trim()));
|
||||
if (yaml.ContainsKey("SupportsMapsFrom"))
|
||||
compat.AddRange(yaml["SupportsMapsFrom"].Value.Split(',').Select(c => c.Trim()));
|
||||
|
||||
MapCompatibility = compat.ToArray();
|
||||
|
||||
if (yaml.TryGetValue("DefaultOrderGenerator", out entry))
|
||||
DefaultOrderGenerator = entry.Value;
|
||||
if (yaml.ContainsKey("DefaultOrderGenerator"))
|
||||
DefaultOrderGenerator = yaml["DefaultOrderGenerator"].Value;
|
||||
|
||||
if (yaml.TryGetValue("PackageFormats", out entry))
|
||||
PackageFormats = FieldLoader.GetValue<string[]>("PackageFormats", entry.Value);
|
||||
if (yaml.ContainsKey("PackageFormats"))
|
||||
PackageFormats = FieldLoader.GetValue<string[]>("PackageFormats", yaml["PackageFormats"].Value);
|
||||
|
||||
if (yaml.TryGetValue("SoundFormats", out entry))
|
||||
SoundFormats = FieldLoader.GetValue<string[]>("SoundFormats", entry.Value);
|
||||
if (yaml.ContainsKey("SoundFormats"))
|
||||
SoundFormats = FieldLoader.GetValue<string[]>("SoundFormats", yaml["SoundFormats"].Value);
|
||||
|
||||
if (yaml.TryGetValue("SpriteFormats", out entry))
|
||||
SpriteFormats = FieldLoader.GetValue<string[]>("SpriteFormats", entry.Value);
|
||||
if (yaml.ContainsKey("SpriteFormats"))
|
||||
SpriteFormats = FieldLoader.GetValue<string[]>("SpriteFormats", yaml["SpriteFormats"].Value);
|
||||
|
||||
if (yaml.TryGetValue("VideoFormats", out entry))
|
||||
VideoFormats = FieldLoader.GetValue<string[]>("VideoFormats", entry.Value);
|
||||
if (yaml.ContainsKey("VideoFormats"))
|
||||
VideoFormats = FieldLoader.GetValue<string[]>("VideoFormats", yaml["VideoFormats"].Value);
|
||||
}
|
||||
|
||||
public void LoadCustomData(ObjectCreator oc)
|
||||
@@ -201,18 +211,18 @@ namespace OpenRA
|
||||
|
||||
static string[] YamlList(Dictionary<string, MiniYaml> yaml, string key)
|
||||
{
|
||||
if (!yaml.TryGetValue(key, out var value))
|
||||
if (!yaml.ContainsKey(key))
|
||||
return Array.Empty<string>();
|
||||
|
||||
return value.Nodes.Select(n => n.Key).ToArray();
|
||||
return yaml[key].ToDictionary().Keys.ToArray();
|
||||
}
|
||||
|
||||
static IReadOnlyDictionary<string, string> YamlDictionary(Dictionary<string, MiniYaml> yaml, string key)
|
||||
{
|
||||
if (!yaml.TryGetValue(key, out var value))
|
||||
if (!yaml.ContainsKey(key))
|
||||
return new Dictionary<string, string>();
|
||||
|
||||
return value.ToDictionary(my => my.Value);
|
||||
return yaml[key].ToDictionary(my => my.Value);
|
||||
}
|
||||
|
||||
public bool Contains<T>() where T : IGlobalModData
|
||||
|
||||
@@ -175,7 +175,8 @@ namespace OpenRA
|
||||
protected CompositeActorInit(TraitInfo info)
|
||||
: base(info.InstanceName) { }
|
||||
|
||||
protected CompositeActorInit() { }
|
||||
protected CompositeActorInit()
|
||||
: base() { }
|
||||
|
||||
public virtual void Initialize(MiniYaml yaml)
|
||||
{
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace OpenRA
|
||||
{
|
||||
public interface ISuppressInitExport { }
|
||||
|
||||
public class ActorReference : IEnumerable<object>
|
||||
public class ActorReference : IEnumerable
|
||||
{
|
||||
public string Type;
|
||||
readonly Lazy<TypeDictionary> initDict;
|
||||
@@ -84,7 +84,7 @@ namespace OpenRA
|
||||
|
||||
public MiniYaml Save(Func<ActorInit, bool> initFilter = null)
|
||||
{
|
||||
var nodes = new List<MiniYamlNode>();
|
||||
var ret = new MiniYaml(Type);
|
||||
foreach (var o in initDict.Value)
|
||||
{
|
||||
if (o is not ActorInit init || o is ISuppressInitExport)
|
||||
@@ -98,15 +98,13 @@ namespace OpenRA
|
||||
if (!string.IsNullOrEmpty(init.InstanceName))
|
||||
initName += ActorInfo.TraitInstanceSeparator + init.InstanceName;
|
||||
|
||||
nodes.Add(new MiniYamlNode(initName, init.Save()));
|
||||
ret.Nodes.Add(new MiniYamlNode(initName, init.Save()));
|
||||
}
|
||||
|
||||
return new MiniYaml(Type, nodes);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public IEnumerator<object> GetEnumerator() { return initDict.Value.GetEnumerator(); }
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
|
||||
public IEnumerator GetEnumerator() { return initDict.Value.GetEnumerator(); }
|
||||
|
||||
public ActorReference Clone()
|
||||
{
|
||||
@@ -139,7 +137,7 @@ namespace OpenRA
|
||||
return removed;
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<T> GetAll<T>() where T : ActorInit
|
||||
public IEnumerable<T> GetAll<T>() where T : ActorInit
|
||||
{
|
||||
return initDict.Value.WithInterface<T>();
|
||||
}
|
||||
@@ -152,9 +150,8 @@ namespace OpenRA
|
||||
// If a more specific init is not available, fall back to an unnamed init.
|
||||
// If duplicate inits are defined, take the last to match standard yaml override expectations
|
||||
if (info != null && !string.IsNullOrEmpty(info.InstanceName))
|
||||
return
|
||||
inits.LastOrDefault(i => i.InstanceName == info.InstanceName) ??
|
||||
inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName));
|
||||
return inits.LastOrDefault(i => i.InstanceName == info.InstanceName) ??
|
||||
inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName));
|
||||
|
||||
// Untagged traits will only use untagged inits
|
||||
return inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName));
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public readonly struct CellCoordsRegion : IEnumerable<CPos>
|
||||
{
|
||||
public struct CellCoordsEnumerator : IEnumerator<CPos>
|
||||
{
|
||||
readonly CellCoordsRegion r;
|
||||
|
||||
public CellCoordsEnumerator(CellCoordsRegion region)
|
||||
: this()
|
||||
{
|
||||
r = region;
|
||||
Reset();
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
var x = Current.X + 1;
|
||||
var y = Current.Y;
|
||||
|
||||
// Check for column overflow.
|
||||
if (x > r.BottomRight.X)
|
||||
{
|
||||
y++;
|
||||
x = r.TopLeft.X;
|
||||
|
||||
// Check for row overflow.
|
||||
if (y > r.BottomRight.Y)
|
||||
return false;
|
||||
}
|
||||
|
||||
Current = new CPos(x, y);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Current = new CPos(r.TopLeft.X - 1, r.TopLeft.Y);
|
||||
}
|
||||
|
||||
public CPos Current { get; private set; }
|
||||
|
||||
readonly object IEnumerator.Current => Current;
|
||||
public readonly void Dispose() { }
|
||||
}
|
||||
|
||||
public CellCoordsRegion(CPos topLeft, CPos bottomRight)
|
||||
{
|
||||
TopLeft = topLeft;
|
||||
BottomRight = bottomRight;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{TopLeft}->{BottomRight}";
|
||||
}
|
||||
|
||||
public CellCoordsEnumerator GetEnumerator()
|
||||
{
|
||||
return new CellCoordsEnumerator(this);
|
||||
}
|
||||
|
||||
IEnumerator<CPos> IEnumerable<CPos>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public CPos TopLeft { get; }
|
||||
public CPos BottomRight { get; }
|
||||
}
|
||||
}
|
||||
@@ -24,10 +24,10 @@ namespace OpenRA
|
||||
protected readonly T[] Entries;
|
||||
protected readonly Rectangle Bounds;
|
||||
|
||||
protected CellLayerBase(Map map)
|
||||
public CellLayerBase(Map map)
|
||||
: this(map.Grid.Type, new Size(map.MapSize.X, map.MapSize.Y)) { }
|
||||
|
||||
protected CellLayerBase(MapGridType gridType, Size size)
|
||||
public CellLayerBase(MapGridType gridType, Size size)
|
||||
{
|
||||
Size = size;
|
||||
Bounds = new Rectangle(0, 0, Size.Width, Size.Height);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
@@ -63,9 +64,9 @@ namespace OpenRA
|
||||
}
|
||||
|
||||
/// <summary>Returns the minimal region that covers at least the specified cells.</summary>
|
||||
public static CellRegion BoundingRegion(MapGridType shape, IReadOnlyCollection<CPos> cells)
|
||||
public static CellRegion BoundingRegion(MapGridType shape, IEnumerable<CPos> cells)
|
||||
{
|
||||
if (cells == null || cells.Count == 0)
|
||||
if (cells == null || !cells.Any())
|
||||
throw new ArgumentException("cells must not be null or empty.", nameof(cells));
|
||||
|
||||
var minU = int.MaxValue;
|
||||
@@ -102,7 +103,6 @@ namespace OpenRA
|
||||
}
|
||||
|
||||
public MapCoordsRegion MapCoords => new(mapTopLeft, mapBottomRight);
|
||||
public CellCoordsRegion CellCoords => new(TopLeft, BottomRight);
|
||||
|
||||
public CellRegionEnumerator GetEnumerator()
|
||||
{
|
||||
@@ -136,12 +136,12 @@ namespace OpenRA
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
u++;
|
||||
u += 1;
|
||||
|
||||
// Check for column overflow
|
||||
if (u > r.mapBottomRight.U)
|
||||
{
|
||||
v++;
|
||||
v += 1;
|
||||
u = r.mapTopLeft.U;
|
||||
|
||||
// Check for row overflow
|
||||
@@ -162,8 +162,8 @@ namespace OpenRA
|
||||
}
|
||||
|
||||
public CPos Current { get; private set; }
|
||||
readonly object IEnumerator.Current => Current;
|
||||
public readonly void Dispose() { }
|
||||
object IEnumerator.Current => Current;
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
@@ -66,7 +65,7 @@ namespace OpenRA
|
||||
MissionSelector = 4
|
||||
}
|
||||
|
||||
sealed class MapField
|
||||
class MapField
|
||||
{
|
||||
enum Type { Normal, NodeList, MiniYaml }
|
||||
readonly FieldInfo field;
|
||||
@@ -91,13 +90,13 @@ namespace OpenRA
|
||||
throw new InvalidOperationException("Map does not have a field/property " + fieldName);
|
||||
|
||||
var t = field != null ? field.FieldType : property.PropertyType;
|
||||
type = t == typeof(IReadOnlyCollection<MiniYamlNode>) ? Type.NodeList :
|
||||
type = t == typeof(List<MiniYamlNode>) ? Type.NodeList :
|
||||
t == typeof(MiniYaml) ? Type.MiniYaml : Type.Normal;
|
||||
}
|
||||
|
||||
public void Deserialize(Map map, MiniYaml yaml)
|
||||
public void Deserialize(Map map, List<MiniYamlNode> nodes)
|
||||
{
|
||||
var node = yaml.NodeWithKeyOrDefault(key);
|
||||
var node = nodes.FirstOrDefault(n => n.Key == key);
|
||||
if (node == null)
|
||||
{
|
||||
if (required)
|
||||
@@ -131,14 +130,14 @@ namespace OpenRA
|
||||
var value = field != null ? field.GetValue(map) : property.GetValue(map, null);
|
||||
if (type == Type.NodeList)
|
||||
{
|
||||
var listValue = (IReadOnlyCollection<MiniYamlNode>)value;
|
||||
var listValue = (List<MiniYamlNode>)value;
|
||||
if (required || listValue.Count > 0)
|
||||
nodes.Add(new MiniYamlNode(key, null, listValue));
|
||||
}
|
||||
else if (type == Type.MiniYaml)
|
||||
{
|
||||
var yamlValue = (MiniYaml)value;
|
||||
if (required || (yamlValue != null && (yamlValue.Value != null || yamlValue.Nodes.Length > 0)))
|
||||
if (required || (yamlValue != null && (yamlValue.Value != null || yamlValue.Nodes.Count > 0)))
|
||||
nodes.Add(new MiniYamlNode(key, yamlValue));
|
||||
}
|
||||
else
|
||||
@@ -150,7 +149,7 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class Map : IReadOnlyFileSystem, IDisposable
|
||||
public class Map : IReadOnlyFileSystem, IDisposable
|
||||
{
|
||||
public const int SupportedMapFormat = 11;
|
||||
public const int CurrentMapFormat = 12;
|
||||
@@ -159,26 +158,26 @@ namespace OpenRA
|
||||
/// <summary>Defines the order of the fields in map.yaml.</summary>
|
||||
static readonly MapField[] YamlFields =
|
||||
{
|
||||
new("MapFormat"),
|
||||
new("RequiresMod"),
|
||||
new("Title"),
|
||||
new("Author"),
|
||||
new("Tileset"),
|
||||
new("MapSize"),
|
||||
new("Bounds"),
|
||||
new("Visibility"),
|
||||
new("Categories"),
|
||||
new("LockPreview", required: false, ignoreIfValue: "False"),
|
||||
new("Players", "PlayerDefinitions"),
|
||||
new("Actors", "ActorDefinitions"),
|
||||
new("Rules", "RuleDefinitions", required: false),
|
||||
new("Translations", "TranslationDefinitions", required: false),
|
||||
new("Sequences", "SequenceDefinitions", required: false),
|
||||
new("ModelSequences", "ModelSequenceDefinitions", required: false),
|
||||
new("Weapons", "WeaponDefinitions", required: false),
|
||||
new("Voices", "VoiceDefinitions", required: false),
|
||||
new("Music", "MusicDefinitions", required: false),
|
||||
new("Notifications", "NotificationDefinitions", required: false),
|
||||
new MapField("MapFormat"),
|
||||
new MapField("RequiresMod"),
|
||||
new MapField("Title"),
|
||||
new MapField("Author"),
|
||||
new MapField("Tileset"),
|
||||
new MapField("MapSize"),
|
||||
new MapField("Bounds"),
|
||||
new MapField("Visibility"),
|
||||
new MapField("Categories"),
|
||||
new MapField("Translations", required: false, ignoreIfValue: ""),
|
||||
new MapField("LockPreview", required: false, ignoreIfValue: "False"),
|
||||
new MapField("Players", "PlayerDefinitions"),
|
||||
new MapField("Actors", "ActorDefinitions"),
|
||||
new MapField("Rules", "RuleDefinitions", required: false),
|
||||
new MapField("Sequences", "SequenceDefinitions", required: false),
|
||||
new MapField("ModelSequences", "ModelSequenceDefinitions", required: false),
|
||||
new MapField("Weapons", "WeaponDefinitions", required: false),
|
||||
new MapField("Voices", "VoiceDefinitions", required: false),
|
||||
new MapField("Music", "MusicDefinitions", required: false),
|
||||
new MapField("Notifications", "NotificationDefinitions", required: false),
|
||||
};
|
||||
|
||||
// Format versions
|
||||
@@ -194,22 +193,22 @@ namespace OpenRA
|
||||
public Rectangle Bounds;
|
||||
public MapVisibility Visibility = MapVisibility.Lobby;
|
||||
public string[] Categories = { "Conquest" };
|
||||
public string[] Translations;
|
||||
|
||||
public int2 MapSize { get; private set; }
|
||||
|
||||
// Player and actor yaml. Public for access by the map importers and lint checks.
|
||||
public IReadOnlyCollection<MiniYamlNode> PlayerDefinitions = ImmutableArray<MiniYamlNode>.Empty;
|
||||
public IReadOnlyCollection<MiniYamlNode> ActorDefinitions = ImmutableArray<MiniYamlNode>.Empty;
|
||||
public List<MiniYamlNode> PlayerDefinitions = new();
|
||||
public List<MiniYamlNode> ActorDefinitions = new();
|
||||
|
||||
// Custom map yaml. Public for access by the map importers and lint checks
|
||||
public MiniYaml RuleDefinitions;
|
||||
public MiniYaml TranslationDefinitions;
|
||||
public MiniYaml SequenceDefinitions;
|
||||
public MiniYaml ModelSequenceDefinitions;
|
||||
public MiniYaml WeaponDefinitions;
|
||||
public MiniYaml VoiceDefinitions;
|
||||
public MiniYaml MusicDefinitions;
|
||||
public MiniYaml NotificationDefinitions;
|
||||
public readonly MiniYaml RuleDefinitions;
|
||||
public readonly MiniYaml SequenceDefinitions;
|
||||
public readonly MiniYaml ModelSequenceDefinitions;
|
||||
public readonly MiniYaml WeaponDefinitions;
|
||||
public readonly MiniYaml VoiceDefinitions;
|
||||
public readonly MiniYaml MusicDefinitions;
|
||||
public readonly MiniYaml NotificationDefinitions;
|
||||
|
||||
public readonly Dictionary<CPos, TerrainTile> ReplacedInvalidTerrainTiles = new();
|
||||
|
||||
@@ -257,6 +256,8 @@ namespace OpenRA
|
||||
CellLayer<byte> projectedHeight;
|
||||
Rectangle projectionSafeBounds;
|
||||
|
||||
internal Translation Translation;
|
||||
|
||||
public static string ComputeUID(IReadOnlyPackage package)
|
||||
{
|
||||
return ComputeUID(package, GetMapFormat(package));
|
||||
@@ -275,10 +276,7 @@ namespace OpenRA
|
||||
try
|
||||
{
|
||||
foreach (var filename in contents)
|
||||
if (filename.EndsWith(".yaml", StringComparison.Ordinal) ||
|
||||
filename.EndsWith(".bin", StringComparison.Ordinal) ||
|
||||
filename.EndsWith(".lua", StringComparison.Ordinal) ||
|
||||
(format >= 12 && filename == "map.png"))
|
||||
if (filename.EndsWith(".yaml") || filename.EndsWith(".bin") || filename.EndsWith(".lua") || (format >= 12 && filename == "map.png"))
|
||||
streams.Add(package.GetStream(filename));
|
||||
|
||||
// Take the SHA1
|
||||
@@ -361,15 +359,15 @@ namespace OpenRA
|
||||
if (!Package.Contains("map.yaml") || !Package.Contains("map.bin"))
|
||||
throw new InvalidDataException($"Not a valid map\n File: {package.Name}");
|
||||
|
||||
var yaml = new MiniYaml(null, MiniYaml.FromStream(Package.GetStream("map.yaml"), $"{package.Name}:map.yaml"));
|
||||
var yaml = new MiniYaml(null, MiniYaml.FromStream(Package.GetStream("map.yaml"), package.Name));
|
||||
foreach (var field in YamlFields)
|
||||
field.Deserialize(this, yaml);
|
||||
field.Deserialize(this, yaml.Nodes);
|
||||
|
||||
if (MapFormat < SupportedMapFormat)
|
||||
throw new InvalidDataException($"Map format {MapFormat} is not supported.\n File: {package.Name}");
|
||||
|
||||
PlayerDefinitions = yaml.NodeWithKeyOrDefault("Players")?.Value.Nodes ?? ImmutableArray<MiniYamlNode>.Empty;
|
||||
ActorDefinitions = yaml.NodeWithKeyOrDefault("Actors")?.Value.Nodes ?? ImmutableArray<MiniYamlNode>.Empty;
|
||||
PlayerDefinitions = MiniYaml.NodesOrEmpty(yaml, "Players");
|
||||
ActorDefinitions = MiniYaml.NodesOrEmpty(yaml, "Actors");
|
||||
|
||||
Grid = modData.Manifest.Get<MapGrid>();
|
||||
|
||||
@@ -445,14 +443,14 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", $"Failed to load rules for {Title} with error");
|
||||
Log.Write("debug", e);
|
||||
Log.Write("debug", "Failed to load rules for {0} with error {1}", Title, e);
|
||||
InvalidCustomRules = true;
|
||||
InvalidCustomRulesException = e;
|
||||
Rules = Ruleset.LoadDefaultsForTileSet(modData, Tileset);
|
||||
}
|
||||
|
||||
Sequences = new SequenceSet(this, modData, Tileset, SequenceDefinitions);
|
||||
Translation = new Translation(Game.Settings.Player.Language, Translations, this);
|
||||
|
||||
var tl = new MPos(0, 0).ToCPos(this);
|
||||
var br = new MPos(MapSize.X - 1, MapSize.Y - 1).ToCPos(this);
|
||||
@@ -611,7 +609,7 @@ namespace OpenRA
|
||||
|
||||
// Odd-height ramps get bumped up a level to the next even height layer
|
||||
if ((height & 1) == 1 && Ramp[uv] != 0)
|
||||
height++;
|
||||
height += 1;
|
||||
|
||||
var candidates = new List<PPos>();
|
||||
|
||||
@@ -650,24 +648,21 @@ namespace OpenRA
|
||||
foreach (var file in Package.Contents)
|
||||
toPackage.Update(file, Package.GetStream(file).ReadAllBytes());
|
||||
|
||||
void UpdatePackage(string filename, byte[] data)
|
||||
if (!LockPreview)
|
||||
{
|
||||
if (Package != toPackage)
|
||||
toPackage.Update(filename, data);
|
||||
else
|
||||
{
|
||||
var stream = Package.GetStream(filename);
|
||||
if (stream == null || !Enumerable.SequenceEqual(data, stream.ReadAllBytes()))
|
||||
toPackage.Update(filename, data);
|
||||
}
|
||||
var previewData = SavePreview();
|
||||
if (Package != toPackage || !Enumerable.SequenceEqual(previewData, Package.GetStream("map.png").ReadAllBytes()))
|
||||
toPackage.Update("map.png", previewData);
|
||||
}
|
||||
|
||||
if (!LockPreview)
|
||||
UpdatePackage("map.png", SavePreview());
|
||||
|
||||
// Update the package with the new map data
|
||||
UpdatePackage("map.yaml", Encoding.UTF8.GetBytes(root.WriteToString()));
|
||||
UpdatePackage("map.bin", SaveBinaryData());
|
||||
var textData = Encoding.UTF8.GetBytes(root.WriteToString());
|
||||
if (Package != toPackage || !Enumerable.SequenceEqual(textData, Package.GetStream("map.yaml").ReadAllBytes()))
|
||||
toPackage.Update("map.yaml", textData);
|
||||
|
||||
var binaryData = SaveBinaryData();
|
||||
if (Package != toPackage || !Enumerable.SequenceEqual(binaryData, Package.GetStream("map.bin").ReadAllBytes()))
|
||||
toPackage.Update("map.bin", binaryData);
|
||||
|
||||
Package = toPackage;
|
||||
|
||||
@@ -688,16 +683,16 @@ namespace OpenRA
|
||||
writer.Write((ushort)MapSize.Y);
|
||||
|
||||
// Data offsets
|
||||
const int TilesOffset = 17;
|
||||
var tilesOffset = 17;
|
||||
var heightsOffset = Grid.MaximumTerrainHeight > 0 ? 3 * MapSize.X * MapSize.Y + 17 : 0;
|
||||
var resourcesOffset = (Grid.MaximumTerrainHeight > 0 ? 4 : 3) * MapSize.X * MapSize.Y + 17;
|
||||
|
||||
writer.Write((uint)TilesOffset);
|
||||
writer.Write((uint)tilesOffset);
|
||||
writer.Write((uint)heightsOffset);
|
||||
writer.Write((uint)resourcesOffset);
|
||||
|
||||
// Tile data
|
||||
if (TilesOffset != 0)
|
||||
if (tilesOffset != 0)
|
||||
{
|
||||
for (var i = 0; i < MapSize.X; i++)
|
||||
{
|
||||
@@ -755,7 +750,7 @@ namespace OpenRA
|
||||
{
|
||||
var actorTypes = Rules.Actors.Values.Where(a => a.HasTraitInfo<IMapPreviewSignatureInfo>());
|
||||
var actors = ActorDefinitions.Where(a => actorTypes.Any(ai => ai.Name == a.Value.Value));
|
||||
var positions = new List<(MPos Uv, Color Color)>();
|
||||
var positions = new List<(MPos Position, Color Color)>();
|
||||
foreach (var actor in actors)
|
||||
{
|
||||
var s = new ActorReference(actor.Value.Value, actor.Value.ToDictionary());
|
||||
@@ -779,10 +774,19 @@ namespace OpenRA
|
||||
|
||||
if (Grid.MaximumTerrainHeight > 0)
|
||||
{
|
||||
(top, bottom) = GetCellSpaceBounds();
|
||||
// The minimap is drawn in cell space, so we need to
|
||||
// unproject the PPos bounds to find the MPos boundaries.
|
||||
// This matches the calculation in RadarWidget that is used ingame
|
||||
for (var x = Bounds.Left; x < Bounds.Right; x++)
|
||||
{
|
||||
var allTop = Unproject(new PPos(x, Bounds.Top));
|
||||
var allBottom = Unproject(new PPos(x, Bounds.Bottom));
|
||||
if (allTop.Count > 0)
|
||||
top = Math.Min(top, allTop.MinBy(uv => uv.V).V);
|
||||
|
||||
if (top == int.MaxValue || bottom == int.MinValue)
|
||||
throw new InvalidDataException("The map has invalid boundaries");
|
||||
if (allBottom.Count > 0)
|
||||
bottom = Math.Max(bottom, allBottom.MaxBy(uv => uv.V).V);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -799,21 +803,18 @@ namespace OpenRA
|
||||
bitmapWidth = 2 * bitmapWidth - 1;
|
||||
|
||||
var stride = bitmapWidth * 4;
|
||||
const int PxStride = 4;
|
||||
var pxStride = 4;
|
||||
var minimapData = new byte[stride * height];
|
||||
(Color Left, Color Right) terrainColor = default;
|
||||
|
||||
var colorsByPosition = positions
|
||||
.GroupBy(p => p.Uv)
|
||||
.ToDictionary(g => g.Key, g => g.First().Color);
|
||||
for (var y = 0; y < height; y++)
|
||||
{
|
||||
for (var x = 0; x < width; x++)
|
||||
{
|
||||
var uv = new MPos(x + Bounds.Left, y + top);
|
||||
|
||||
// TryGetValue will return Color.Transparent if not found
|
||||
colorsByPosition.TryGetValue(uv, out var actorColor);
|
||||
// FirstOrDefault will return a (MPos.Zero, Color.Transparent) if positions is empty
|
||||
var actorColor = positions.FirstOrDefault(ap => ap.Position == uv).Color;
|
||||
if (actorColor.A == 0)
|
||||
terrainColor = GetTerrainColorPair(uv);
|
||||
|
||||
@@ -821,10 +822,10 @@ namespace OpenRA
|
||||
{
|
||||
// Odd rows are shifted right by 1px
|
||||
var dx = uv.V & 1;
|
||||
var xOffset = PxStride * (2 * x + dx);
|
||||
var xOffset = pxStride * (2 * x + dx);
|
||||
if (x + dx > 0)
|
||||
{
|
||||
var z = y * stride + xOffset - PxStride;
|
||||
var z = y * stride + xOffset - pxStride;
|
||||
var c = actorColor.A == 0 ? terrainColor.Left : actorColor;
|
||||
minimapData[z++] = c.R;
|
||||
minimapData[z++] = c.G;
|
||||
@@ -844,7 +845,7 @@ namespace OpenRA
|
||||
}
|
||||
else
|
||||
{
|
||||
var z = y * stride + PxStride * x;
|
||||
var z = y * stride + pxStride * x;
|
||||
var c = actorColor.A == 0 ? terrainColor.Left : actorColor;
|
||||
minimapData[z++] = c.R;
|
||||
minimapData[z++] = c.G;
|
||||
@@ -858,28 +859,6 @@ namespace OpenRA
|
||||
return png.Save();
|
||||
}
|
||||
|
||||
public (int Top, int Bottom) GetCellSpaceBounds()
|
||||
{
|
||||
var top = int.MaxValue;
|
||||
var bottom = int.MinValue;
|
||||
|
||||
// The minimap is drawn in cell space, so we need to
|
||||
// unproject the PPos bounds to find the MPos boundaries.
|
||||
// This matches the calculation in RadarWidget that is used ingame
|
||||
for (var x = Bounds.Left; x < Bounds.Right; x++)
|
||||
{
|
||||
var allTop = Unproject(new PPos(x, Bounds.Top));
|
||||
var allBottom = Unproject(new PPos(x, Bounds.Bottom));
|
||||
if (allTop.Count > 0)
|
||||
top = Math.Min(top, allTop.MinBy(uv => uv.V).V);
|
||||
|
||||
if (allBottom.Count > 0)
|
||||
bottom = Math.Max(bottom, allBottom.MaxBy(uv => uv.V).V);
|
||||
}
|
||||
|
||||
return (top, bottom);
|
||||
}
|
||||
|
||||
public bool Contains(CPos cell)
|
||||
{
|
||||
if (Grid.Type == MapGridType.RectangularIsometric)
|
||||
@@ -1200,7 +1179,7 @@ namespace OpenRA
|
||||
// Project this guessed cell and take the first available cell
|
||||
// If it is projected outside the layer, then make another guess.
|
||||
var allProjected = ProjectedCellsCovering(uv);
|
||||
var projected = allProjected.Length > 0 ? allProjected[0]
|
||||
var projected = allProjected.Length > 0 ? allProjected.First()
|
||||
: new PPos(uv.U, uv.V.Clamp(Bounds.Top, Bounds.Bottom));
|
||||
|
||||
// Clamp the projected cell to the map area
|
||||
@@ -1228,7 +1207,7 @@ namespace OpenRA
|
||||
// This shouldn't happen. But if it does, return the original value and hope the caller doesn't explode.
|
||||
if (unProjected.Count == 0)
|
||||
{
|
||||
Log.Write("debug", $"Failed to clamp map cell {uv} to map bounds");
|
||||
Log.Write("debug", "Failed to clamp map cell {0} to map bounds", uv);
|
||||
return uv;
|
||||
}
|
||||
}
|
||||
@@ -1269,7 +1248,7 @@ namespace OpenRA
|
||||
PPos edge;
|
||||
if (allProjected.Length > 0)
|
||||
{
|
||||
var puv = allProjected[0];
|
||||
var puv = allProjected.First();
|
||||
var horizontalBound = (puv.U - Bounds.Left < Bounds.Width / 2) ? Bounds.Left : Bounds.Right;
|
||||
var verticalBound = (puv.V - Bounds.Top < Bounds.Height / 2) ? Bounds.Top : Bounds.Bottom;
|
||||
|
||||
@@ -1300,7 +1279,7 @@ namespace OpenRA
|
||||
// This shouldn't happen. But if it does, return the original value and hope the caller doesn't explode.
|
||||
if (unProjected.Count == 0)
|
||||
{
|
||||
Log.Write("debug", $"Failed to find closest edge for map cell {uv}");
|
||||
Log.Write("debug", "Failed to find closest edge for map cell {0}", uv);
|
||||
return uv;
|
||||
}
|
||||
}
|
||||
@@ -1369,18 +1348,13 @@ namespace OpenRA
|
||||
throw new ArgumentOutOfRangeException(nameof(maxRange),
|
||||
$"The requested range ({maxRange}) cannot exceed the value of MaximumTileSearchRange ({Grid.MaximumTileSearchRange})");
|
||||
|
||||
return FindTilesInAnnulus();
|
||||
|
||||
IEnumerable<CPos> FindTilesInAnnulus()
|
||||
for (var i = minRange; i <= maxRange; i++)
|
||||
{
|
||||
for (var i = minRange; i <= maxRange; i++)
|
||||
foreach (var offset in Grid.TilesByDistance[i])
|
||||
{
|
||||
foreach (var offset in Grid.TilesByDistance[i])
|
||||
{
|
||||
var t = offset + center;
|
||||
if (allowOutsideBounds ? Tiles.Contains(t) : Contains(t))
|
||||
yield return t;
|
||||
}
|
||||
var t = offset + center;
|
||||
if (allowOutsideBounds ? Tiles.Contains(t) : Contains(t))
|
||||
yield return t;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1436,6 +1410,14 @@ namespace OpenRA
|
||||
return false;
|
||||
}
|
||||
|
||||
public string Translate(string key, IDictionary<string, object> args = null)
|
||||
{
|
||||
if (Translation.TryGetString(key, out var message, args))
|
||||
return message;
|
||||
|
||||
return modData.Translation.GetString(key, args);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Sequences.Dispose();
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace OpenRA
|
||||
readonly object syncRoot = new();
|
||||
readonly Queue<MapPreview> generateMinimap = new();
|
||||
|
||||
public HashSet<string> StringPool { get; } = new();
|
||||
public Dictionary<string, string> StringPool { get; } = new Dictionary<string, string>();
|
||||
|
||||
readonly List<MapDirectoryTracker> mapDirectoryTrackers = new();
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace OpenRA
|
||||
? MapClassification.Unknown : Enum<MapClassification>.Parse(kv.Value);
|
||||
|
||||
IReadOnlyPackage package;
|
||||
var optional = name.StartsWith('~');
|
||||
var optional = name.StartsWith("~", StringComparison.Ordinal);
|
||||
if (optional)
|
||||
name = name[1..];
|
||||
|
||||
@@ -106,7 +106,7 @@ namespace OpenRA
|
||||
// HACK: If the path is inside the support directory then we may need to create it
|
||||
// Assume that the path is a directory if there is not an existing file with the same name
|
||||
var resolved = Platform.ResolvePath(name);
|
||||
if (resolved.StartsWith(Platform.SupportDir, StringComparison.Ordinal) && !File.Exists(resolved))
|
||||
if (resolved.StartsWith(Platform.SupportDir) && !File.Exists(resolved))
|
||||
Directory.CreateDirectory(resolved);
|
||||
|
||||
package = modData.ModFiles.OpenPackage(name);
|
||||
@@ -123,15 +123,10 @@ namespace OpenRA
|
||||
mapDirectoryTrackers.Add(new MapDirectoryTracker(mapGrid, package, classification));
|
||||
}
|
||||
|
||||
// PERF: Load the mod YAML once outside the loop, and reuse it when resolving each maps custom YAML.
|
||||
var modDataRules = modData.GetRulesYaml();
|
||||
foreach (var kv in MapLocations)
|
||||
{
|
||||
foreach (var map in kv.Key.Contents)
|
||||
{
|
||||
LoadMapInternal(map, kv.Key, kv.Value, mapGrid, null, modDataRules);
|
||||
GC.Collect();
|
||||
}
|
||||
LoadMap(map, kv.Key, kv.Value, mapGrid, null);
|
||||
}
|
||||
|
||||
// We only want to track maps in runtime, not at loadtime
|
||||
@@ -139,11 +134,6 @@ namespace OpenRA
|
||||
}
|
||||
|
||||
public void LoadMap(string map, IReadOnlyPackage package, MapClassification classification, MapGrid mapGrid, string oldMap)
|
||||
{
|
||||
LoadMapInternal(map, package, classification, mapGrid, oldMap, null);
|
||||
}
|
||||
|
||||
void LoadMapInternal(string map, IReadOnlyPackage package, MapClassification classification, MapGrid mapGrid, string oldMap, IEnumerable<List<MiniYamlNode>> modDataRules)
|
||||
{
|
||||
IReadOnlyPackage mapPackage = null;
|
||||
try
|
||||
@@ -151,15 +141,10 @@ namespace OpenRA
|
||||
using (new PerfTimer(map))
|
||||
{
|
||||
mapPackage = package.OpenPackage(map, modData.ModFiles);
|
||||
Log.Write("debug", $"Loading Map: {map} FROM {mapPackage.Name}");
|
||||
Console.WriteLine($"Loading Map: {map} FROM {mapPackage.Name}");
|
||||
if (mapPackage != null)
|
||||
{
|
||||
var uid = Map.ComputeUID(mapPackage);
|
||||
previews[uid].UpdateFromMap(mapPackage, package, classification, modData.Manifest.MapCompatibility, mapGrid.Type, modDataRules);
|
||||
|
||||
// Freeing the package to save memory if there is a lot of Maps
|
||||
previews[uid].PackageDispose();
|
||||
previews[uid].UpdateFromMap(mapPackage, package, classification, modData.Manifest.MapCompatibility, mapGrid.Type);
|
||||
|
||||
if (oldMap != uid)
|
||||
{
|
||||
@@ -173,16 +158,10 @@ namespace OpenRA
|
||||
catch (Exception e)
|
||||
{
|
||||
mapPackage?.Dispose();
|
||||
Console.WriteLine($"Failed to load map: {map}");
|
||||
Console.WriteLine("Details:");
|
||||
Console.WriteLine(e);
|
||||
Console.WriteLine("StackTrace:");
|
||||
Console.WriteLine(System.Environment.StackTrace);
|
||||
Log.Write("debug", $"Failed to load map: {map}");
|
||||
Log.Write("debug", "Details:");
|
||||
Log.Write("debug", e);
|
||||
Log.Write("debug", "StackTrace:");
|
||||
Log.Write("debug", System.Environment.StackTrace);
|
||||
Console.WriteLine("Failed to load map: {0}", map);
|
||||
Console.WriteLine("Details: {0}", e);
|
||||
Log.Write("debug", "Failed to load map: {0}", map);
|
||||
Log.Write("debug", "Details: {0}", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,13 +181,13 @@ namespace OpenRA
|
||||
continue;
|
||||
|
||||
var name = kv.Key;
|
||||
var optional = name.StartsWith('~');
|
||||
var optional = name.StartsWith("~", StringComparison.Ordinal);
|
||||
if (optional)
|
||||
name = name[1..];
|
||||
|
||||
// Don't try to open the map directory in the support directory if it doesn't exist
|
||||
var resolved = Platform.ResolvePath(name);
|
||||
if (resolved.StartsWith(Platform.SupportDir, StringComparison.Ordinal) && (!Directory.Exists(resolved) || !File.Exists(resolved)))
|
||||
if (resolved.StartsWith(Platform.SupportDir) && (!Directory.Exists(resolved) || !File.Exists(resolved)))
|
||||
continue;
|
||||
|
||||
using (var package = (IReadWritePackage)modData.ModFiles.OpenPackage(name))
|
||||
@@ -245,12 +224,11 @@ namespace OpenRA
|
||||
.ToList();
|
||||
|
||||
foreach (var uid in queryUids)
|
||||
previews[uid].UpdateRemoteSearch(MapStatus.Searching, null, null);
|
||||
previews[uid].UpdateRemoteSearch(MapStatus.Searching, null);
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var client = HttpClientFactory.Create();
|
||||
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
||||
|
||||
// Limit each query to 50 maps at a time to avoid request size limits
|
||||
for (var i = 0; i < queryUids.Count; i += 50)
|
||||
@@ -262,27 +240,26 @@ namespace OpenRA
|
||||
var httpResponseMessage = await client.GetAsync(url);
|
||||
var result = await httpResponseMessage.Content.ReadAsStreamAsync();
|
||||
|
||||
var yaml = MiniYaml.FromStream(result, url, stringPool: stringPool);
|
||||
var yaml = MiniYaml.FromStream(result);
|
||||
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, mapDetailsReceived);
|
||||
|
||||
foreach (var uid in batchUids)
|
||||
{
|
||||
var p = previews[uid];
|
||||
if (p.Status != MapStatus.DownloadAvailable)
|
||||
p.UpdateRemoteSearch(MapStatus.Unavailable, null, null);
|
||||
p.UpdateRemoteSearch(MapStatus.Unavailable, null);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Remote map query failed with error:");
|
||||
Log.Write("debug", e);
|
||||
Log.Write("debug", $"URL was: {url}");
|
||||
Log.Write("debug", "Remote map query failed with error: {0}", e);
|
||||
Log.Write("debug", "URL was: {0}", url);
|
||||
|
||||
foreach (var uid in batchUids)
|
||||
{
|
||||
var p = previews[uid];
|
||||
p.UpdateRemoteSearch(MapStatus.Unavailable, null, null);
|
||||
p.UpdateRemoteSearch(MapStatus.Unavailable, null);
|
||||
mapQueryFailed?.Invoke(p);
|
||||
}
|
||||
}
|
||||
@@ -295,11 +272,11 @@ namespace OpenRA
|
||||
Log.Write("debug", "MapCache.LoadAsyncInternal started");
|
||||
|
||||
// Milliseconds to wait on one loop when nothing to do
|
||||
const int EmptyDelay = 50;
|
||||
var emptyDelay = 50;
|
||||
|
||||
// Keep the thread alive for at least 5 seconds after the last minimap generation
|
||||
const int MaxKeepAlive = 5000 / EmptyDelay;
|
||||
var keepAlive = MaxKeepAlive;
|
||||
var maxKeepAlive = 5000 / emptyDelay;
|
||||
var keepAlive = maxKeepAlive;
|
||||
|
||||
while (true)
|
||||
{
|
||||
@@ -319,11 +296,11 @@ namespace OpenRA
|
||||
|
||||
if (todo.Count == 0)
|
||||
{
|
||||
Thread.Sleep(EmptyDelay);
|
||||
Thread.Sleep(emptyDelay);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
keepAlive = MaxKeepAlive;
|
||||
keepAlive = maxKeepAlive;
|
||||
|
||||
// Render the minimap into the shared sheet
|
||||
foreach (var p in todo)
|
||||
@@ -338,8 +315,7 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to load minimap with exception:");
|
||||
Log.Write("debug", e);
|
||||
Log.Write("debug", "Failed to load minimap with exception: {0}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -361,8 +337,8 @@ namespace OpenRA
|
||||
|
||||
while (this[uid].Status != MapStatus.Available)
|
||||
{
|
||||
if (mapUpdates.TryGetValue(uid, out var newUid))
|
||||
uid = newUid;
|
||||
if (mapUpdates.ContainsKey(uid))
|
||||
uid = mapUpdates[uid];
|
||||
else
|
||||
return null;
|
||||
}
|
||||
@@ -385,6 +361,7 @@ namespace OpenRA
|
||||
{
|
||||
// Wait for any existing thread to exit before starting a new one.
|
||||
previewLoaderThread?.Join();
|
||||
|
||||
previewLoaderThread = new Thread(LoadAsyncInternal)
|
||||
{
|
||||
Name = "Map Preview Loader",
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace OpenRA
|
||||
// Check for column overflow
|
||||
if (u > r.BottomRight.U)
|
||||
{
|
||||
v++;
|
||||
v += 1;
|
||||
u = r.TopLeft.U;
|
||||
|
||||
// Check for row overflow
|
||||
@@ -53,8 +53,8 @@ namespace OpenRA
|
||||
}
|
||||
|
||||
public MPos Current { get; private set; }
|
||||
readonly object IEnumerator.Current => Current;
|
||||
public readonly void Dispose() { }
|
||||
object IEnumerator.Current => Current;
|
||||
public void Dispose() { }
|
||||
}
|
||||
|
||||
public MapCoordsRegion(MPos mapTopLeft, MPos mapBottomRight)
|
||||
|
||||
@@ -116,26 +116,22 @@ namespace OpenRA
|
||||
|
||||
public readonly WVec[] SubCellOffsets =
|
||||
{
|
||||
new(0, 0, 0), // full cell - index 0
|
||||
new(-299, -256, 0), // top left - index 1
|
||||
new(256, -256, 0), // top right - index 2
|
||||
new(0, 0, 0), // center - index 3
|
||||
new(-299, 256, 0), // bottom left - index 4
|
||||
new(256, 256, 0), // bottom right - index 5
|
||||
new WVec(0, 0, 0), // full cell - index 0
|
||||
new WVec(-299, -256, 0), // top left - index 1
|
||||
new WVec(256, -256, 0), // top right - index 2
|
||||
new WVec(0, 0, 0), // center - index 3
|
||||
new WVec(-299, 256, 0), // bottom left - index 4
|
||||
new WVec(256, 256, 0), // bottom right - index 5
|
||||
};
|
||||
|
||||
public CellRamp[] Ramps { get; }
|
||||
|
||||
internal readonly CVec[][] TilesByDistance;
|
||||
|
||||
public int TileScale { get; }
|
||||
|
||||
public MapGrid(MiniYaml yaml)
|
||||
{
|
||||
FieldLoader.Load(this, yaml);
|
||||
|
||||
TileScale = Type == MapGridType.RectangularIsometric ? 1448 : 1024;
|
||||
|
||||
// The default subcell index defaults to the middle entry
|
||||
var defaultSubCellIndex = (byte)DefaultSubCell;
|
||||
if (defaultSubCellIndex == byte.MaxValue)
|
||||
|
||||
@@ -59,13 +59,12 @@ namespace OpenRA
|
||||
public readonly string rules;
|
||||
public readonly string players_block;
|
||||
public readonly int mapformat;
|
||||
public readonly string game_mod;
|
||||
}
|
||||
|
||||
public sealed class MapPreview : IDisposable, IReadOnlyFileSystem
|
||||
public class MapPreview : IDisposable, IReadOnlyFileSystem
|
||||
{
|
||||
/// <summary>Wrapper that enables map data to be replaced in an atomic fashion.</summary>
|
||||
sealed class InnerData
|
||||
class InnerData
|
||||
{
|
||||
public int MapFormat;
|
||||
public string Title;
|
||||
@@ -91,7 +90,6 @@ namespace OpenRA
|
||||
public MiniYaml SequenceDefinitions;
|
||||
public MiniYaml ModelSequenceDefinitions;
|
||||
|
||||
public Translation Translation { get; private set; }
|
||||
public ActorInfo WorldActorInfo { get; private set; }
|
||||
public ActorInfo PlayerActorInfo { get; private set; }
|
||||
|
||||
@@ -112,7 +110,7 @@ namespace OpenRA
|
||||
return key == "world" || key == "player";
|
||||
}
|
||||
|
||||
public void SetCustomRules(ModData modData, IReadOnlyFileSystem fileSystem, Dictionary<string, MiniYaml> yaml, IEnumerable<List<MiniYamlNode>> modDataRules)
|
||||
public void SetCustomRules(ModData modData, IReadOnlyFileSystem fileSystem, Dictionary<string, MiniYaml> yaml)
|
||||
{
|
||||
RuleDefinitions = LoadRuleSection(yaml, "Rules");
|
||||
WeaponDefinitions = LoadRuleSection(yaml, "Weapons");
|
||||
@@ -122,29 +120,21 @@ namespace OpenRA
|
||||
SequenceDefinitions = LoadRuleSection(yaml, "Sequences");
|
||||
ModelSequenceDefinitions = LoadRuleSection(yaml, "ModelSequences");
|
||||
|
||||
Translation = yaml.TryGetValue("Translations", out var node) && node != null
|
||||
? new Translation(Game.Settings.Player.Language, FieldLoader.GetValue<string[]>("value", node.Value), fileSystem)
|
||||
: null;
|
||||
|
||||
try
|
||||
{
|
||||
// PERF: Implement a minimal custom loader for custom world and player actors to minimize loading time
|
||||
// This assumes/enforces that these actor types can only inherit abstract definitions (starting with ^)
|
||||
if (RuleDefinitions != null)
|
||||
{
|
||||
modDataRules ??= modData.GetRulesYaml();
|
||||
var files = Enumerable.Empty<string>();
|
||||
var files = modData.Manifest.Rules.AsEnumerable();
|
||||
if (RuleDefinitions.Value != null)
|
||||
{
|
||||
var mapFiles = FieldLoader.GetValue<string[]>("value", RuleDefinitions.Value);
|
||||
files = files.Append(mapFiles);
|
||||
}
|
||||
|
||||
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
||||
var sources =
|
||||
modDataRules.Select(x => x.Where(IsLoadableRuleDefinition).ToList())
|
||||
.Concat(files.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s, stringPool: stringPool).Where(IsLoadableRuleDefinition).ToList()));
|
||||
if (RuleDefinitions.Nodes.Length > 0)
|
||||
var sources = files.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s).Where(IsLoadableRuleDefinition).ToList());
|
||||
if (RuleDefinitions.Nodes.Count > 0)
|
||||
sources = sources.Append(RuleDefinitions.Nodes.Where(IsLoadableRuleDefinition).ToList());
|
||||
|
||||
var yamlNodes = MiniYaml.Merge(sources);
|
||||
@@ -155,8 +145,7 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", $"Failed to load rules for `{Title}` with error:");
|
||||
Log.Write("debug", e);
|
||||
Log.Write("debug", "Failed to load rules for `{0}` with error: {1}", Title, e.Message);
|
||||
}
|
||||
|
||||
WorldActorInfo = modData.DefaultRules.Actors[SystemActors.World];
|
||||
@@ -174,23 +163,7 @@ namespace OpenRA
|
||||
readonly ModData modData;
|
||||
|
||||
public readonly string Uid;
|
||||
public string PackageName { get; private set; }
|
||||
IReadOnlyPackage pPackage;
|
||||
public IReadOnlyPackage Package
|
||||
{
|
||||
get
|
||||
{
|
||||
if (pPackage == null)
|
||||
{
|
||||
pPackage = parentPackage.OpenPackage(PackageName, modData.ModFiles);
|
||||
}
|
||||
|
||||
return pPackage;
|
||||
}
|
||||
|
||||
private set => pPackage = value;
|
||||
}
|
||||
|
||||
public IReadOnlyPackage Package { get; private set; }
|
||||
IReadOnlyPackage parentPackage;
|
||||
|
||||
volatile InnerData innerData;
|
||||
@@ -221,19 +194,6 @@ namespace OpenRA
|
||||
public long DownloadBytes { get; private set; }
|
||||
public int DownloadPercentage { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Functionality mirrors <see cref="TranslationProvider.GetString"/>, except instead of using
|
||||
/// loaded <see cref="Map"/>'s translations as backup, we use this <see cref="MapPreview"/>'s.
|
||||
/// </summary>
|
||||
public string GetLocalisedString(string key, IDictionary<string, object> args = null)
|
||||
{
|
||||
// PERF: instead of loading mod level Translation per each MapPreview, reuse the already loaded one in TranslationProvider.
|
||||
if (TranslationProvider.TryGetModString(key, out var message, args))
|
||||
return message;
|
||||
|
||||
return innerData.Translation?.GetString(key, args) ?? key;
|
||||
}
|
||||
|
||||
Sprite minimap;
|
||||
bool generatingMinimap;
|
||||
public Sprite GetMinimap()
|
||||
@@ -274,7 +234,6 @@ namespace OpenRA
|
||||
{
|
||||
this.cache = cache;
|
||||
this.modData = modData;
|
||||
this.pPackage = null;
|
||||
|
||||
Uid = uid;
|
||||
innerData = new InnerData
|
||||
@@ -301,10 +260,9 @@ namespace OpenRA
|
||||
{
|
||||
this.modData = modData;
|
||||
cache = modData.MapCache;
|
||||
this.pPackage = null;
|
||||
|
||||
Uid = map.Uid;
|
||||
PackageName = map.Package.Name;
|
||||
Package = map.Package;
|
||||
|
||||
var mapPlayers = new MapPlayers(map.PlayerDefinitions);
|
||||
var spawns = new List<CPos>();
|
||||
@@ -335,17 +293,16 @@ namespace OpenRA
|
||||
innerData.SetCustomRules(modData, this, new Dictionary<string, MiniYaml>()
|
||||
{
|
||||
{ "Rules", map.RuleDefinitions },
|
||||
{ "Translations", map.TranslationDefinitions },
|
||||
{ "Weapons", map.WeaponDefinitions },
|
||||
{ "Voices", map.VoiceDefinitions },
|
||||
{ "Music", map.MusicDefinitions },
|
||||
{ "Notifications", map.NotificationDefinitions },
|
||||
{ "Sequences", map.SequenceDefinitions },
|
||||
{ "ModelSequences", map.ModelSequenceDefinitions }
|
||||
}, null);
|
||||
});
|
||||
}
|
||||
|
||||
public void UpdateFromMap(IReadOnlyPackage p, IReadOnlyPackage parent, MapClassification classification, string[] mapCompatibility, MapGridType gridType, IEnumerable<List<MiniYamlNode>> modDataRules)
|
||||
public void UpdateFromMap(IReadOnlyPackage p, IReadOnlyPackage parent, MapClassification classification, string[] mapCompatibility, MapGridType gridType)
|
||||
{
|
||||
Dictionary<string, MiniYaml> yaml;
|
||||
using (var yamlStream = p.GetStream("map.yaml"))
|
||||
@@ -353,10 +310,10 @@ namespace OpenRA
|
||||
if (yamlStream == null)
|
||||
throw new FileNotFoundException("Required file map.yaml not present in this map");
|
||||
|
||||
yaml = new MiniYaml(null, MiniYaml.FromStream(yamlStream, $"{p.Name}:map.yaml", stringPool: cache.StringPool)).ToDictionary();
|
||||
yaml = new MiniYaml(null, MiniYaml.FromStream(yamlStream, "map.yaml", stringPool: cache.StringPool)).ToDictionary();
|
||||
}
|
||||
|
||||
PackageName = p.Name;
|
||||
Package = p;
|
||||
parentPackage = parent;
|
||||
|
||||
var newData = innerData.Clone();
|
||||
@@ -435,7 +392,7 @@ namespace OpenRA
|
||||
newData.Status = MapStatus.Unavailable;
|
||||
}
|
||||
|
||||
newData.SetCustomRules(modData, this, yaml, modDataRules);
|
||||
newData.SetCustomRules(modData, this, yaml);
|
||||
|
||||
if (cache.LoadPreviewImages && p.Contains("map.png"))
|
||||
using (var dataStream = p.GetStream("map.png"))
|
||||
@@ -447,7 +404,7 @@ namespace OpenRA
|
||||
innerData = newData;
|
||||
}
|
||||
|
||||
public void UpdateRemoteSearch(MapStatus status, MiniYaml yaml, string[] mapCompatibility, Action<MapPreview> parseMetadata = null)
|
||||
public void UpdateRemoteSearch(MapStatus status, MiniYaml yaml, Action<MapPreview> parseMetadata = null)
|
||||
{
|
||||
var newData = innerData.Clone();
|
||||
newData.Status = status;
|
||||
@@ -487,31 +444,21 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed parsing mapserver minimap response:");
|
||||
Log.Write("debug", e);
|
||||
Log.Write("debug", "Failed parsing mapserver minimap response: {0}", e);
|
||||
newData.Preview = null;
|
||||
}
|
||||
}
|
||||
|
||||
var playersString = Encoding.UTF8.GetString(Convert.FromBase64String(r.players_block));
|
||||
newData.Players = new MapPlayers(MiniYaml.FromString(playersString,
|
||||
$"{yaml.NodeWithKey(nameof(r.players_block)).Location.Name}:{nameof(r.players_block)}"));
|
||||
newData.Players = new MapPlayers(MiniYaml.FromString(playersString));
|
||||
|
||||
var rulesString = Encoding.UTF8.GetString(Convert.FromBase64String(r.rules));
|
||||
var rulesYaml = new MiniYaml("", MiniYaml.FromString(rulesString,
|
||||
$"{yaml.NodeWithKey(nameof(r.rules)).Location.Name}:{nameof(r.rules)}")).ToDictionary();
|
||||
newData.SetCustomRules(modData, this, rulesYaml, null);
|
||||
|
||||
// Map is for a different mod: update its information so it can be displayed
|
||||
// in the cross-mod server browser UI, but mark it as unavailable so it can't
|
||||
// be selected in a server for the current mod.
|
||||
if (!mapCompatibility.Contains(r.game_mod))
|
||||
newData.Status = MapStatus.Unavailable;
|
||||
var rulesYaml = new MiniYaml("", MiniYaml.FromString(rulesString)).ToDictionary();
|
||||
newData.SetCustomRules(modData, this, rulesYaml);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed parsing mapserver response:");
|
||||
Log.Write("debug", e);
|
||||
Log.Write("debug", "Failed parsing mapserver response: {0}", e);
|
||||
}
|
||||
|
||||
// Commit updated data before running the callbacks
|
||||
@@ -578,21 +525,20 @@ namespace OpenRA
|
||||
await response.ReadAsStreamWithProgress(fileStream, OnDownloadProgress, CancellationToken.None);
|
||||
|
||||
mapInstallPackage.Update(mapFilename, fileStream.ToArray());
|
||||
Log.Write("debug", $"Downloaded map to '{mapFilename}'");
|
||||
Log.Write("debug", "Downloaded map to '{0}'", mapFilename);
|
||||
|
||||
var package = mapInstallPackage.OpenPackage(mapFilename, modData.ModFiles);
|
||||
if (package == null)
|
||||
innerData.Status = MapStatus.DownloadError;
|
||||
else
|
||||
{
|
||||
UpdateFromMap(package, mapInstallPackage, MapClassification.User, null, GridType, null);
|
||||
UpdateFromMap(package, mapInstallPackage, MapClassification.User, null, GridType);
|
||||
Game.RunAfterTick(onSuccess);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Map installation failed with error:");
|
||||
Log.Write("debug", e);
|
||||
Log.Write("debug", "Map installation failed with error: {0}", e);
|
||||
innerData.Status = MapStatus.DownloadError;
|
||||
}
|
||||
});
|
||||
@@ -605,15 +551,10 @@ namespace OpenRA
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
PackageDispose();
|
||||
}
|
||||
|
||||
public void PackageDispose()
|
||||
{
|
||||
if (pPackage != null)
|
||||
if (Package != null)
|
||||
{
|
||||
pPackage.Dispose();
|
||||
pPackage = null;
|
||||
Package.Dispose();
|
||||
Package = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
@@ -54,15 +53,5 @@ namespace OpenRA
|
||||
{
|
||||
return Bounds.Contains(uv.U, uv.V);
|
||||
}
|
||||
|
||||
public int IndexOf(T value, int startIndex)
|
||||
{
|
||||
return Array.IndexOf(Entries, value, startIndex);
|
||||
}
|
||||
|
||||
public void SetAll(T value)
|
||||
{
|
||||
Array.Fill(Entries, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,12 +93,12 @@ namespace OpenRA
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
u++;
|
||||
u += 1;
|
||||
|
||||
// Check for column overflow
|
||||
if (u > r.BottomRight.U)
|
||||
{
|
||||
v++;
|
||||
v += 1;
|
||||
u = r.TopLeft.U;
|
||||
|
||||
// Check for row overflow
|
||||
@@ -118,8 +118,8 @@ namespace OpenRA
|
||||
}
|
||||
|
||||
public PPos Current { get; private set; }
|
||||
readonly object IEnumerator.Current => Current;
|
||||
public readonly void Dispose() { }
|
||||
object IEnumerator.Current => Current;
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace OpenRA
|
||||
|
||||
// HACK: Temporary placeholder to avoid having to change all the traits that reference this constant.
|
||||
// This can be removed after the palette references have been moved from traits to sequences.
|
||||
public static class TileSet
|
||||
public class TileSet
|
||||
{
|
||||
public const string TerrainPaletteInternalName = "terrain";
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileSystem;
|
||||
@@ -20,36 +19,18 @@ namespace OpenRA
|
||||
{
|
||||
public static class MiniYamlExts
|
||||
{
|
||||
public static void WriteToFile(this IEnumerable<MiniYamlNode> y, string filename)
|
||||
public static void WriteToFile(this List<MiniYamlNode> y, string filename)
|
||||
{
|
||||
File.WriteAllLines(filename, y.ToLines().Select(x => x.TrimEnd()).ToArray());
|
||||
}
|
||||
|
||||
public static string WriteToString(this IEnumerable<MiniYamlNode> y)
|
||||
public static string WriteToString(this List<MiniYamlNode> y)
|
||||
{
|
||||
// Remove all trailing newlines and restore the final EOF newline
|
||||
return y.ToLines().JoinWith("\n").TrimEnd('\n') + "\n";
|
||||
}
|
||||
|
||||
public static IEnumerable<string> ToLines(this IEnumerable<MiniYamlNode> y)
|
||||
{
|
||||
foreach (var kv in y)
|
||||
foreach (var line in kv.Value.ToLines(kv.Key, kv.Comment))
|
||||
yield return line;
|
||||
}
|
||||
|
||||
public static void WriteToFile(this IEnumerable<MiniYamlNodeBuilder> y, string filename)
|
||||
{
|
||||
File.WriteAllLines(filename, y.ToLines().Select(x => x.TrimEnd()).ToArray());
|
||||
}
|
||||
|
||||
public static string WriteToString(this IEnumerable<MiniYamlNodeBuilder> y)
|
||||
{
|
||||
// Remove all trailing newlines and restore the final EOF newline
|
||||
return y.ToLines().JoinWith("\n").TrimEnd('\n') + "\n";
|
||||
}
|
||||
|
||||
public static IEnumerable<string> ToLines(this IEnumerable<MiniYamlNodeBuilder> y)
|
||||
public static IEnumerable<string> ToLines(this List<MiniYamlNode> y)
|
||||
{
|
||||
foreach (var kv in y)
|
||||
foreach (var line in kv.Value.ToLines(kv.Key, kv.Comment))
|
||||
@@ -57,33 +38,18 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class MiniYamlNode
|
||||
public class MiniYamlNode
|
||||
{
|
||||
public readonly struct SourceLocation
|
||||
public struct SourceLocation
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly int Line;
|
||||
|
||||
public SourceLocation(string name, int line)
|
||||
{
|
||||
Name = name;
|
||||
Line = line;
|
||||
}
|
||||
|
||||
public override string ToString() { return $"{Name}:{Line}"; }
|
||||
public string Filename; public int Line;
|
||||
public override string ToString() { return $"{Filename}:{Line}"; }
|
||||
}
|
||||
|
||||
public readonly SourceLocation Location;
|
||||
public readonly string Key;
|
||||
public readonly MiniYaml Value;
|
||||
public readonly string Comment;
|
||||
|
||||
public MiniYamlNode WithValue(MiniYaml value)
|
||||
{
|
||||
if (Value == value)
|
||||
return this;
|
||||
return new MiniYamlNode(Key, value, Comment, Location);
|
||||
}
|
||||
public SourceLocation Location;
|
||||
public string Key;
|
||||
public MiniYaml Value;
|
||||
public string Comment;
|
||||
|
||||
public MiniYamlNode(string k, MiniYaml v, string c = null)
|
||||
{
|
||||
@@ -99,74 +65,42 @@ namespace OpenRA
|
||||
}
|
||||
|
||||
public MiniYamlNode(string k, string v, string c = null)
|
||||
: this(k, new MiniYaml(v, Enumerable.Empty<MiniYamlNode>()), c) { }
|
||||
: this(k, v, c, null) { }
|
||||
|
||||
public MiniYamlNode(string k, string v, IEnumerable<MiniYamlNode> n)
|
||||
public MiniYamlNode(string k, string v, List<MiniYamlNode> n)
|
||||
: this(k, new MiniYaml(v, n), null) { }
|
||||
|
||||
public MiniYamlNode(string k, string v, string c, List<MiniYamlNode> n)
|
||||
: this(k, new MiniYaml(v, n), c) { }
|
||||
|
||||
public MiniYamlNode(string k, string v, string c, List<MiniYamlNode> n, SourceLocation loc)
|
||||
: this(k, new MiniYaml(v, n), c, loc) { }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{{YamlNode: {Key} @ {Location}}}";
|
||||
}
|
||||
|
||||
public MiniYamlNode Clone()
|
||||
{
|
||||
return new MiniYamlNode(Key, Value.Clone(), Comment, Location);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class MiniYaml
|
||||
public class MiniYaml
|
||||
{
|
||||
const int SpacesPerLevel = 4;
|
||||
static readonly Func<string, string> StringIdentity = s => s;
|
||||
static readonly Func<MiniYaml, MiniYaml> MiniYamlIdentity = my => my;
|
||||
public string Value;
|
||||
public List<MiniYamlNode> Nodes;
|
||||
|
||||
public readonly string Value;
|
||||
public readonly ImmutableArray<MiniYamlNode> Nodes;
|
||||
|
||||
public MiniYaml WithValue(string value)
|
||||
public MiniYaml Clone()
|
||||
{
|
||||
if (Value == value)
|
||||
return this;
|
||||
return new MiniYaml(value, Nodes);
|
||||
}
|
||||
|
||||
public MiniYaml WithNodes(IEnumerable<MiniYamlNode> nodes)
|
||||
{
|
||||
if (nodes is ImmutableArray<MiniYamlNode> n && Nodes == n)
|
||||
return this;
|
||||
return new MiniYaml(Value, nodes);
|
||||
}
|
||||
|
||||
public MiniYaml WithNodesAppended(IEnumerable<MiniYamlNode> nodes)
|
||||
{
|
||||
var newNodes = Nodes.AddRange(nodes);
|
||||
if (Nodes == newNodes)
|
||||
return this;
|
||||
return new MiniYaml(Value, newNodes);
|
||||
}
|
||||
|
||||
public MiniYamlNode NodeWithKey(string key)
|
||||
{
|
||||
var result = NodeWithKeyOrDefault(key);
|
||||
if (result == null)
|
||||
throw new InvalidDataException($"No node with key '{key}'");
|
||||
return result;
|
||||
}
|
||||
|
||||
public MiniYamlNode NodeWithKeyOrDefault(string key)
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
var first = true;
|
||||
MiniYamlNode result = null;
|
||||
var clonedNodes = new List<MiniYamlNode>(Nodes.Count);
|
||||
foreach (var node in Nodes)
|
||||
{
|
||||
if (node.Key != key)
|
||||
continue;
|
||||
|
||||
if (!first)
|
||||
throw new InvalidDataException($"Duplicate key '{node.Key}' in {node.Location}");
|
||||
|
||||
first = false;
|
||||
result = node;
|
||||
}
|
||||
|
||||
return result;
|
||||
clonedNodes.Add(node.Clone());
|
||||
return new MiniYaml(Value, clonedNodes);
|
||||
}
|
||||
|
||||
public Dictionary<string, MiniYaml> ToDictionary()
|
||||
@@ -182,40 +116,47 @@ namespace OpenRA
|
||||
public Dictionary<TKey, TElement> ToDictionary<TKey, TElement>(
|
||||
Func<string, TKey> keySelector, Func<MiniYaml, TElement> elementSelector)
|
||||
{
|
||||
var ret = new Dictionary<TKey, TElement>(Nodes.Length);
|
||||
var ret = new Dictionary<TKey, TElement>(Nodes.Count);
|
||||
foreach (var y in Nodes)
|
||||
{
|
||||
var key = keySelector(y.Key);
|
||||
var element = elementSelector(y.Value);
|
||||
if (!ret.TryAdd(key, element))
|
||||
throw new InvalidDataException($"Duplicate key '{y.Key}' in {y.Location}");
|
||||
try
|
||||
{
|
||||
ret.Add(key, element);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
throw new InvalidDataException($"Duplicate key '{y.Key}' in {y.Location}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public MiniYaml(string value)
|
||||
: this(value, Enumerable.Empty<MiniYamlNode>()) { }
|
||||
: this(value, null) { }
|
||||
|
||||
public MiniYaml(string value, IEnumerable<MiniYamlNode> nodes)
|
||||
public MiniYaml(string value, List<MiniYamlNode> nodes)
|
||||
{
|
||||
Value = value;
|
||||
Nodes = ImmutableArray.CreateRange(nodes);
|
||||
Nodes = nodes ?? new List<MiniYamlNode>();
|
||||
}
|
||||
|
||||
static List<MiniYamlNode> FromLines(IEnumerable<ReadOnlyMemory<char>> lines, string name, bool discardCommentsAndWhitespace, HashSet<string> stringPool)
|
||||
public static List<MiniYamlNode> NodesOrEmpty(MiniYaml y, string s)
|
||||
{
|
||||
// YAML config often contains repeated strings for key, values, comments.
|
||||
// Pool these strings so we only need one copy of each unique string.
|
||||
// This saves on long-term memory usage as parsed values can often live a long time.
|
||||
// A caller can also provide a pool as input, allowing de-duplication across multiple parses.
|
||||
stringPool ??= new HashSet<string>();
|
||||
var nd = y.ToDictionary();
|
||||
return nd.ContainsKey(s) ? nd[s].Nodes : new List<MiniYamlNode>();
|
||||
}
|
||||
|
||||
var result = new List<List<MiniYamlNode>>
|
||||
static List<MiniYamlNode> FromLines(IEnumerable<ReadOnlyMemory<char>> lines, string filename, bool discardCommentsAndWhitespace, Dictionary<string, string> stringPool)
|
||||
{
|
||||
stringPool ??= new Dictionary<string, string>();
|
||||
|
||||
var levels = new List<List<MiniYamlNode>>
|
||||
{
|
||||
new()
|
||||
new List<MiniYamlNode>()
|
||||
};
|
||||
var parsedLines = new List<(int Level, string Key, string Value, string Comment, MiniYamlNode.SourceLocation Location)>();
|
||||
|
||||
var lineNo = 0;
|
||||
foreach (var ll in lines)
|
||||
@@ -231,7 +172,7 @@ namespace OpenRA
|
||||
ReadOnlySpan<char> key = default;
|
||||
ReadOnlySpan<char> value = default;
|
||||
ReadOnlySpan<char> comment = default;
|
||||
var location = new MiniYamlNode.SourceLocation(name, lineNo);
|
||||
var location = new MiniYamlNode.SourceLocation { Filename = filename, Line = lineNo };
|
||||
|
||||
if (line.Length > 0)
|
||||
{
|
||||
@@ -262,6 +203,15 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
if (levels.Count <= level)
|
||||
throw new YamlException($"Bad indent in miniyaml at {location}");
|
||||
|
||||
while (levels.Count > level + 1)
|
||||
{
|
||||
levels[^1].TrimExcess();
|
||||
levels.RemoveAt(levels.Count - 1);
|
||||
}
|
||||
|
||||
// Extract key, value, comment from line as `<key>: <value>#<comment>`
|
||||
// The # character is allowed in the value if escaped (\#).
|
||||
// Leading and trailing whitespace is always trimmed from keys.
|
||||
@@ -283,7 +233,7 @@ namespace OpenRA
|
||||
if (commentStart < 0 && line[i] == '#' && (i == 0 || line[i - 1] != '\\'))
|
||||
{
|
||||
commentStart = i + 1;
|
||||
if (i <= keyStart + keyLength)
|
||||
if (commentStart <= keyLength)
|
||||
keyLength = i - keyStart;
|
||||
else
|
||||
valueLength = i - valueStart;
|
||||
@@ -321,12 +271,6 @@ namespace OpenRA
|
||||
|
||||
if (!key.IsEmpty || !discardCommentsAndWhitespace)
|
||||
{
|
||||
if (parsedLines.Count > 0 && parsedLines[^1].Level < level - 1)
|
||||
throw new YamlException($"Bad indent in miniyaml at {location}");
|
||||
|
||||
while (parsedLines.Count > 0 && parsedLines[^1].Level > level)
|
||||
BuildCompletedSubNode(level);
|
||||
|
||||
var keyString = key.IsEmpty ? null : key.ToString();
|
||||
var valueString = value.IsEmpty ? null : value.ToString();
|
||||
|
||||
@@ -334,75 +278,44 @@ namespace OpenRA
|
||||
// (i.e. a lone # at the end of a line) can be correctly re-serialized
|
||||
var commentString = comment == default ? null : comment.ToString();
|
||||
|
||||
keyString = keyString == null ? null : stringPool.GetOrAdd(keyString);
|
||||
valueString = valueString == null ? null : stringPool.GetOrAdd(valueString);
|
||||
commentString = commentString == null ? null : stringPool.GetOrAdd(commentString);
|
||||
keyString = keyString == null ? null : stringPool.GetOrAdd(keyString, keyString);
|
||||
valueString = valueString == null ? null : stringPool.GetOrAdd(valueString, valueString);
|
||||
commentString = commentString == null ? null : stringPool.GetOrAdd(commentString, commentString);
|
||||
|
||||
parsedLines.Add((level, keyString, valueString, commentString, location));
|
||||
var nodes = new List<MiniYamlNode>();
|
||||
levels[level].Add(new MiniYamlNode(keyString, valueString, commentString, nodes, location));
|
||||
|
||||
levels.Add(nodes);
|
||||
}
|
||||
}
|
||||
|
||||
if (parsedLines.Count > 0)
|
||||
BuildCompletedSubNode(0);
|
||||
foreach (var nodes in levels)
|
||||
nodes.TrimExcess();
|
||||
|
||||
return result[0];
|
||||
|
||||
void BuildCompletedSubNode(int level)
|
||||
{
|
||||
var lastLevel = parsedLines[^1].Level;
|
||||
while (lastLevel >= result.Count)
|
||||
result.Add(new List<MiniYamlNode>());
|
||||
|
||||
while (parsedLines.Count > 0 && parsedLines[^1].Level >= level)
|
||||
{
|
||||
var parent = parsedLines[^1];
|
||||
var startOfRange = parsedLines.Count - 1;
|
||||
while (startOfRange > 0 && parsedLines[startOfRange - 1].Level == parent.Level)
|
||||
startOfRange--;
|
||||
|
||||
for (var i = startOfRange; i < parsedLines.Count - 1; i++)
|
||||
{
|
||||
var sibling = parsedLines[i];
|
||||
result[parent.Level].Add(
|
||||
new MiniYamlNode(sibling.Key, new MiniYaml(sibling.Value), sibling.Comment, sibling.Location));
|
||||
}
|
||||
|
||||
var childNodes = parent.Level + 1 < result.Count ? result[parent.Level + 1] : null;
|
||||
result[parent.Level].Add(new MiniYamlNode(
|
||||
parent.Key,
|
||||
new MiniYaml(parent.Value, childNodes ?? Enumerable.Empty<MiniYamlNode>()),
|
||||
parent.Comment,
|
||||
parent.Location));
|
||||
childNodes?.Clear();
|
||||
|
||||
parsedLines.RemoveRange(startOfRange, parsedLines.Count - startOfRange);
|
||||
}
|
||||
}
|
||||
return levels[0];
|
||||
}
|
||||
|
||||
public static List<MiniYamlNode> FromFile(string path, bool discardCommentsAndWhitespace = true, HashSet<string> stringPool = null)
|
||||
public static List<MiniYamlNode> FromFile(string path, bool discardCommentsAndWhitespace = true, Dictionary<string, string> stringPool = null)
|
||||
{
|
||||
return FromStream(File.OpenRead(path), path, discardCommentsAndWhitespace, stringPool);
|
||||
}
|
||||
|
||||
public static List<MiniYamlNode> FromStream(Stream s, string name, bool discardCommentsAndWhitespace = true, HashSet<string> stringPool = null)
|
||||
public static List<MiniYamlNode> FromStream(Stream s, string fileName = "<no filename available>", bool discardCommentsAndWhitespace = true, Dictionary<string, string> stringPool = null)
|
||||
{
|
||||
return FromLines(s.ReadAllLinesAsMemory(), name, discardCommentsAndWhitespace, stringPool);
|
||||
return FromLines(s.ReadAllLinesAsMemory(), fileName, discardCommentsAndWhitespace, stringPool);
|
||||
}
|
||||
|
||||
public static List<MiniYamlNode> FromString(string text, string name, bool discardCommentsAndWhitespace = true, HashSet<string> stringPool = null)
|
||||
public static List<MiniYamlNode> FromString(string text, string fileName = "<no filename available>", bool discardCommentsAndWhitespace = true, Dictionary<string, string> stringPool = null)
|
||||
{
|
||||
return FromLines(text.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None).Select(s => s.AsMemory()), name, discardCommentsAndWhitespace, stringPool);
|
||||
return FromLines(text.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None).Select(s => s.AsMemory()), fileName, discardCommentsAndWhitespace, stringPool);
|
||||
}
|
||||
|
||||
public static List<MiniYamlNode> Merge(IEnumerable<IReadOnlyCollection<MiniYamlNode>> sources)
|
||||
public static List<MiniYamlNode> Merge(IEnumerable<List<MiniYamlNode>> sources)
|
||||
{
|
||||
var sourcesList = sources.ToList();
|
||||
if (sourcesList.Count == 0)
|
||||
if (!sources.Any())
|
||||
return new List<MiniYamlNode>();
|
||||
|
||||
var tree = sourcesList
|
||||
.Where(s => s != null)
|
||||
var tree = sources.Where(s => s != null)
|
||||
.Select(MergeSelfPartial)
|
||||
.Aggregate(MergePartial)
|
||||
.Where(n => n.Key != null)
|
||||
@@ -411,39 +324,39 @@ namespace OpenRA
|
||||
var resolved = new Dictionary<string, MiniYaml>(tree.Count);
|
||||
foreach (var kv in tree)
|
||||
{
|
||||
// Inheritance is tracked from parent->child, but not from child->parentsiblings.
|
||||
var inherited = ImmutableDictionary<string, MiniYamlNode.SourceLocation>.Empty.Add(kv.Key, default);
|
||||
var inherited = new Dictionary<string, MiniYamlNode.SourceLocation>
|
||||
{
|
||||
{ kv.Key, default }
|
||||
};
|
||||
|
||||
var children = ResolveInherits(kv.Value, tree, inherited);
|
||||
resolved.Add(kv.Key, new MiniYaml(kv.Value.Value, children));
|
||||
}
|
||||
|
||||
// Resolve any top-level removals (e.g. removing whole actor blocks)
|
||||
var nodes = new MiniYaml("", resolved.Select(kv => new MiniYamlNode(kv.Key, kv.Value)));
|
||||
return ResolveInherits(nodes, tree, ImmutableDictionary<string, MiniYamlNode.SourceLocation>.Empty);
|
||||
var nodes = new MiniYaml("", resolved.Select(kv => new MiniYamlNode(kv.Key, kv.Value)).ToList());
|
||||
return ResolveInherits(nodes, tree, new Dictionary<string, MiniYamlNode.SourceLocation>());
|
||||
}
|
||||
|
||||
static void MergeIntoResolved(MiniYamlNode overrideNode, List<MiniYamlNode> existingNodes, HashSet<string> existingNodeKeys,
|
||||
Dictionary<string, MiniYaml> tree, ImmutableDictionary<string, MiniYamlNode.SourceLocation> inherited)
|
||||
static void MergeIntoResolved(MiniYamlNode overrideNode, List<MiniYamlNode> existingNodes,
|
||||
Dictionary<string, MiniYaml> tree, Dictionary<string, MiniYamlNode.SourceLocation> inherited)
|
||||
{
|
||||
if (existingNodeKeys.Add(overrideNode.Key))
|
||||
var existingNode = existingNodes.Find(n => n.Key == overrideNode.Key);
|
||||
if (existingNode != null)
|
||||
{
|
||||
existingNodes.Add(overrideNode);
|
||||
return;
|
||||
existingNode.Value = MergePartial(existingNode.Value, overrideNode.Value);
|
||||
existingNode.Value.Nodes = ResolveInherits(existingNode.Value, tree, inherited);
|
||||
}
|
||||
|
||||
var existingNodeIndex = IndexOfKey(existingNodes, overrideNode.Key);
|
||||
var existingNode = existingNodes[existingNodeIndex];
|
||||
var value = MergePartial(existingNode.Value, overrideNode.Value);
|
||||
var nodes = ResolveInherits(value, tree, inherited);
|
||||
if (!value.Nodes.SequenceEqual(nodes))
|
||||
value = value.WithNodes(nodes);
|
||||
existingNodes[existingNodeIndex] = existingNode.WithValue(value);
|
||||
else
|
||||
existingNodes.Add(overrideNode.Clone());
|
||||
}
|
||||
|
||||
static List<MiniYamlNode> ResolveInherits(MiniYaml node, Dictionary<string, MiniYaml> tree, ImmutableDictionary<string, MiniYamlNode.SourceLocation> inherited)
|
||||
static List<MiniYamlNode> ResolveInherits(MiniYaml node, Dictionary<string, MiniYaml> tree, Dictionary<string, MiniYamlNode.SourceLocation> inherited)
|
||||
{
|
||||
var resolved = new List<MiniYamlNode>(node.Nodes.Length);
|
||||
var resolvedKeys = new HashSet<string>(node.Nodes.Length);
|
||||
var resolved = new List<MiniYamlNode>(node.Nodes.Count);
|
||||
|
||||
// Inheritance is tracked from parent->child, but not from child->parentsiblings.
|
||||
inherited = new Dictionary<string, MiniYamlNode.SourceLocation>(inherited);
|
||||
|
||||
foreach (var n in node.Nodes)
|
||||
{
|
||||
@@ -453,29 +366,24 @@ namespace OpenRA
|
||||
throw new YamlException(
|
||||
$"{n.Location}: Parent type `{n.Value.Value}` not found");
|
||||
|
||||
try
|
||||
{
|
||||
inherited = inherited.Add(n.Value.Value, n.Location);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
if (inherited.ContainsKey(n.Value.Value))
|
||||
throw new YamlException($"{n.Location}: Parent type `{n.Value.Value}` was already inherited by this yaml tree at {inherited[n.Value.Value]} (note: may be from a derived tree)");
|
||||
}
|
||||
|
||||
inherited.Add(n.Value.Value, n.Location);
|
||||
foreach (var r in ResolveInherits(parent, tree, inherited))
|
||||
MergeIntoResolved(r, resolved, resolvedKeys, tree, inherited);
|
||||
MergeIntoResolved(r, resolved, tree, inherited);
|
||||
}
|
||||
else if (n.Key.StartsWith('-'))
|
||||
else if (n.Key.StartsWith("-", StringComparison.Ordinal))
|
||||
{
|
||||
var removed = n.Key[1..];
|
||||
if (resolved.RemoveAll(r => r.Key == removed) == 0)
|
||||
throw new YamlException($"{n.Location}: There are no elements with key `{removed}` to remove");
|
||||
resolvedKeys.Remove(removed);
|
||||
}
|
||||
else
|
||||
MergeIntoResolved(n, resolved, resolvedKeys, tree, inherited);
|
||||
MergeIntoResolved(n, resolved, tree, inherited);
|
||||
}
|
||||
|
||||
resolved.TrimExcess();
|
||||
return resolved;
|
||||
}
|
||||
|
||||
@@ -483,7 +391,7 @@ namespace OpenRA
|
||||
/// Merges any duplicate keys that are defined within the same set of nodes.
|
||||
/// Does not resolve inheritance or node removals.
|
||||
/// </summary>
|
||||
static IReadOnlyCollection<MiniYamlNode> MergeSelfPartial(IReadOnlyCollection<MiniYamlNode> existingNodes)
|
||||
static List<MiniYamlNode> MergeSelfPartial(List<MiniYamlNode> existingNodes)
|
||||
{
|
||||
var keys = new HashSet<string>(existingNodes.Count);
|
||||
var ret = new List<MiniYamlNode>(existingNodes.Count);
|
||||
@@ -494,20 +402,17 @@ namespace OpenRA
|
||||
else
|
||||
{
|
||||
// Node with the same key has already been added: merge new node over the existing one
|
||||
var originalIndex = IndexOfKey(ret, n.Key);
|
||||
var original = ret[originalIndex];
|
||||
ret[originalIndex] = original.WithValue(MergePartial(original.Value, n.Value));
|
||||
var original = ret.First(r => r.Key == n.Key);
|
||||
original.Value = MergePartial(original.Value, n.Value);
|
||||
}
|
||||
}
|
||||
|
||||
ret.TrimExcess();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static MiniYaml MergePartial(MiniYaml existingNodes, MiniYaml overrideNodes)
|
||||
{
|
||||
existingNodes?.Nodes.ToDictionaryWithConflictLog(x => x.Key, "MiniYaml.Merge", null, x => $"{x.Key} (at {x.Location})");
|
||||
overrideNodes?.Nodes.ToDictionaryWithConflictLog(x => x.Key, "MiniYaml.Merge", null, x => $"{x.Key} (at {x.Location})");
|
||||
|
||||
if (existingNodes == null)
|
||||
return overrideNodes;
|
||||
|
||||
@@ -517,7 +422,7 @@ namespace OpenRA
|
||||
return new MiniYaml(overrideNodes.Value ?? existingNodes.Value, MergePartial(existingNodes.Nodes, overrideNodes.Nodes));
|
||||
}
|
||||
|
||||
static IReadOnlyCollection<MiniYamlNode> MergePartial(IReadOnlyCollection<MiniYamlNode> existingNodes, IReadOnlyCollection<MiniYamlNode> overrideNodes)
|
||||
static List<MiniYamlNode> MergePartial(List<MiniYamlNode> existingNodes, List<MiniYamlNode> overrideNodes)
|
||||
{
|
||||
if (existingNodes.Count == 0)
|
||||
return overrideNodes;
|
||||
@@ -526,67 +431,27 @@ namespace OpenRA
|
||||
return existingNodes;
|
||||
|
||||
var ret = new List<MiniYamlNode>(existingNodes.Count + overrideNodes.Count);
|
||||
var plainKeys = new HashSet<string>(existingNodes.Count + overrideNodes.Count);
|
||||
|
||||
foreach (var node in existingNodes)
|
||||
MergeNode(node);
|
||||
foreach (var node in overrideNodes)
|
||||
MergeNode(node);
|
||||
var existingDict = existingNodes.ToDictionaryWithConflictLog(x => x.Key, "MiniYaml.Merge", null, x => $"{x.Key} (at {x.Location})");
|
||||
var overrideDict = overrideNodes.ToDictionaryWithConflictLog(x => x.Key, "MiniYaml.Merge", null, x => $"{x.Key} (at {x.Location})");
|
||||
var allKeys = existingDict.Keys.Union(overrideDict.Keys);
|
||||
|
||||
void MergeNode(MiniYamlNode node)
|
||||
foreach (var key in allKeys)
|
||||
{
|
||||
// Append Removal nodes to the result.
|
||||
// Therefore: we know the remainder of the method deals with a plain node.
|
||||
if (node.Key.StartsWith('-'))
|
||||
{
|
||||
ret.Add(node);
|
||||
return;
|
||||
}
|
||||
existingDict.TryGetValue(key, out var existingNode);
|
||||
overrideDict.TryGetValue(key, out var overrideNode);
|
||||
|
||||
// If no previous node with this key is present, it is new and can just be appended.
|
||||
if (plainKeys.Add(node.Key))
|
||||
{
|
||||
ret.Add(node);
|
||||
return;
|
||||
}
|
||||
|
||||
// A Removal node is closer than the previous node.
|
||||
// We should not merge the new node, as the data being merged will jump before the Removal.
|
||||
// Instead, append it so the previous node is applied, then removed, then the new node is applied.
|
||||
var previousNodeIndex = LastIndexOfKey(ret, node.Key);
|
||||
var previousRemovalNodeIndex = LastIndexOfKey(ret, $"-{node.Key}");
|
||||
if (previousRemovalNodeIndex != -1 && previousRemovalNodeIndex > previousNodeIndex)
|
||||
{
|
||||
ret.Add(node);
|
||||
return;
|
||||
}
|
||||
|
||||
// A previous node is present with no intervening Removal.
|
||||
// We should merge the new one into it, in place.
|
||||
ret[previousNodeIndex] = node.WithValue(MergePartial(ret[previousNodeIndex].Value, node.Value));
|
||||
var loc = overrideNode?.Location ?? default;
|
||||
var comment = (overrideNode ?? existingNode).Comment;
|
||||
var merged = (existingNode == null || overrideNode == null) ? overrideNode ?? existingNode :
|
||||
new MiniYamlNode(key, MergePartial(existingNode.Value, overrideNode.Value), comment, loc);
|
||||
ret.Add(merged);
|
||||
}
|
||||
|
||||
ret.TrimExcess();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int IndexOfKey(List<MiniYamlNode> nodes, string key)
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
for (var i = 0; i < nodes.Count; i++)
|
||||
if (nodes[i].Key == key)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int LastIndexOfKey(List<MiniYamlNode> nodes, string key)
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
for (var i = nodes.Count - 1; i >= 0; i--)
|
||||
if (nodes[i].Key == key)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public IEnumerable<string> ToLines(string key, string comment = null)
|
||||
{
|
||||
var hasKey = !string.IsNullOrEmpty(key);
|
||||
@@ -609,100 +474,14 @@ namespace OpenRA
|
||||
files = files.Append(mapFiles);
|
||||
}
|
||||
|
||||
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
||||
IEnumerable<IReadOnlyCollection<MiniYamlNode>> yaml = files.Select(s => FromStream(fileSystem.Open(s), s, stringPool: stringPool));
|
||||
if (mapRules != null && mapRules.Nodes.Length > 0)
|
||||
var yaml = files.Select(s => FromStream(fileSystem.Open(s), s));
|
||||
if (mapRules != null && mapRules.Nodes.Count > 0)
|
||||
yaml = yaml.Append(mapRules.Nodes);
|
||||
|
||||
return Merge(yaml);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class MiniYamlNodeBuilder
|
||||
{
|
||||
public MiniYamlNode.SourceLocation Location;
|
||||
public string Key;
|
||||
public MiniYamlBuilder Value;
|
||||
public string Comment;
|
||||
|
||||
public MiniYamlNodeBuilder(MiniYamlNode node)
|
||||
{
|
||||
Location = node.Location;
|
||||
Key = node.Key;
|
||||
Value = new MiniYamlBuilder(node.Value);
|
||||
Comment = node.Comment;
|
||||
}
|
||||
|
||||
public MiniYamlNodeBuilder(string k, MiniYamlBuilder v, string c = null)
|
||||
{
|
||||
Key = k;
|
||||
Value = v;
|
||||
Comment = c;
|
||||
}
|
||||
|
||||
public MiniYamlNodeBuilder(string k, MiniYamlBuilder v, string c, MiniYamlNode.SourceLocation loc)
|
||||
: this(k, v, c)
|
||||
{
|
||||
Location = loc;
|
||||
}
|
||||
|
||||
public MiniYamlNodeBuilder(string k, string v, string c = null)
|
||||
: this(k, new MiniYamlBuilder(v, null), c) { }
|
||||
|
||||
public MiniYamlNodeBuilder(string k, string v, List<MiniYamlNode> n)
|
||||
: this(k, new MiniYamlBuilder(v, n), null) { }
|
||||
|
||||
public MiniYamlNode Build()
|
||||
{
|
||||
return new MiniYamlNode(Key, Value.Build(), Comment, Location);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class MiniYamlBuilder
|
||||
{
|
||||
public string Value;
|
||||
public List<MiniYamlNodeBuilder> Nodes;
|
||||
|
||||
public MiniYamlBuilder(MiniYaml yaml)
|
||||
{
|
||||
Value = yaml.Value;
|
||||
Nodes = yaml.Nodes.Select(n => new MiniYamlNodeBuilder(n)).ToList();
|
||||
}
|
||||
|
||||
public MiniYamlBuilder(string value)
|
||||
: this(value, null) { }
|
||||
|
||||
public MiniYamlBuilder(string value, List<MiniYamlNode> nodes)
|
||||
{
|
||||
Value = value;
|
||||
Nodes = nodes == null ? new List<MiniYamlNodeBuilder>() : nodes.ConvertAll(x => new MiniYamlNodeBuilder(x));
|
||||
}
|
||||
|
||||
public MiniYaml Build()
|
||||
{
|
||||
return new MiniYaml(Value, Nodes.Select(n => n.Build()));
|
||||
}
|
||||
|
||||
public IEnumerable<string> ToLines(string key, string comment = null)
|
||||
{
|
||||
var hasKey = !string.IsNullOrEmpty(key);
|
||||
var hasValue = !string.IsNullOrEmpty(Value);
|
||||
var hasComment = comment != null;
|
||||
yield return (hasKey ? key + ":" : "")
|
||||
+ (hasValue ? " " + Value.Replace("#", "\\#") : "")
|
||||
+ (hasComment ? (hasKey || hasValue ? " " : "") + "#" + comment : "");
|
||||
|
||||
if (Nodes != null)
|
||||
foreach (var line in Nodes.ToLines())
|
||||
yield return "\t" + line;
|
||||
}
|
||||
|
||||
public MiniYamlNodeBuilder NodeWithKeyOrDefault(string key)
|
||||
{
|
||||
return Nodes.SingleOrDefault(n => n.Key == key);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class YamlException : Exception
|
||||
{
|
||||
|
||||
@@ -33,8 +33,10 @@ namespace OpenRA
|
||||
public readonly ISpriteLoader[] SpriteLoaders;
|
||||
public readonly ITerrainLoader TerrainLoader;
|
||||
public readonly ISpriteSequenceLoader SpriteSequenceLoader;
|
||||
public readonly IModelSequenceLoader ModelSequenceLoader;
|
||||
public readonly IVideoLoader[] VideoLoaders;
|
||||
public readonly HotkeyManager Hotkeys;
|
||||
public readonly Translation Translation;
|
||||
public ILoadScreen LoadScreen { get; }
|
||||
public CursorProvider CursorProvider { get; private set; }
|
||||
public FS ModFiles;
|
||||
@@ -89,8 +91,19 @@ namespace OpenRA
|
||||
|
||||
SpriteSequenceLoader = (ISpriteSequenceLoader)sequenceCtor.Invoke(new[] { this });
|
||||
|
||||
var modelFormat = Manifest.Get<ModelSequenceFormat>();
|
||||
var modelLoader = ObjectCreator.FindType(modelFormat.Type + "Loader");
|
||||
var modelCtor = modelLoader?.GetConstructor(new[] { typeof(ModData) });
|
||||
if (modelLoader == null || !modelLoader.GetInterfaces().Contains(typeof(IModelSequenceLoader)) || modelCtor == null)
|
||||
throw new InvalidOperationException($"Unable to find a model loader for type '{modelFormat.Type}'.");
|
||||
|
||||
ModelSequenceLoader = (IModelSequenceLoader)modelCtor.Invoke(new[] { this });
|
||||
ModelSequenceLoader.OnMissingModelError = s => Log.Write("debug", s);
|
||||
|
||||
Hotkeys = new HotkeyManager(ModFiles, Game.Settings.Keys, Manifest);
|
||||
|
||||
Translation = new Translation(Game.Settings.Player.Language, Manifest.Translations, DefaultFileSystem);
|
||||
|
||||
defaultRules = Exts.Lazy(() => Ruleset.LoadDefaults(this));
|
||||
defaultTerrainInfo = Exts.Lazy(() =>
|
||||
{
|
||||
@@ -124,7 +137,6 @@ namespace OpenRA
|
||||
// horribly when you use ModData in unexpected ways.
|
||||
ChromeMetrics.Initialize(this);
|
||||
ChromeProvider.Initialize(this);
|
||||
TranslationProvider.Initialize(this, fileSystem);
|
||||
|
||||
Game.Sound.Initialize(SoundLoaders, fileSystem);
|
||||
|
||||
@@ -156,12 +168,6 @@ namespace OpenRA
|
||||
return map;
|
||||
}
|
||||
|
||||
public List<MiniYamlNode>[] GetRulesYaml()
|
||||
{
|
||||
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
||||
return Manifest.Rules.Select(s => MiniYaml.FromStream(DefaultFileSystem.Open(s), s, stringPool: stringPool)).ToArray();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
LoadScreen?.Dispose();
|
||||
|
||||
@@ -260,15 +260,15 @@ namespace OpenRA.Network
|
||||
try
|
||||
{
|
||||
var ms = new MemoryStream();
|
||||
ms.Write(packet.Length);
|
||||
ms.Write(packet);
|
||||
ms.WriteArray(BitConverter.GetBytes(packet.Length));
|
||||
ms.WriteArray(packet);
|
||||
|
||||
foreach (var s in queuedSyncPackets)
|
||||
{
|
||||
var q = OrderIO.SerializeSync(s);
|
||||
|
||||
ms.Write(q.Length);
|
||||
ms.Write(q);
|
||||
ms.WriteArray(BitConverter.GetBytes(q.Length));
|
||||
ms.WriteArray(q);
|
||||
|
||||
sentSync.Enqueue(s);
|
||||
}
|
||||
|
||||
@@ -122,10 +122,10 @@ namespace OpenRA.Network
|
||||
LastSyncFrame = rs.ReadInt32();
|
||||
lastSyncPacket = rs.ReadBytes(Order.SyncHashOrderLength);
|
||||
|
||||
var globalSettings = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength), $"{filepath}:globalSettings");
|
||||
var globalSettings = MiniYaml.FromString(rs.ReadString(Encoding.UTF8, Connection.MaxOrderLength));
|
||||
GlobalSettings = Session.Global.Deserialize(globalSettings[0].Value);
|
||||
|
||||
var slots = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength), $"{filepath}:slots");
|
||||
var slots = MiniYaml.FromString(rs.ReadString(Encoding.UTF8, Connection.MaxOrderLength));
|
||||
Slots = new Dictionary<string, Session.Slot>();
|
||||
foreach (var s in slots)
|
||||
{
|
||||
@@ -133,7 +133,7 @@ namespace OpenRA.Network
|
||||
Slots.Add(slot.PlayerReference, slot);
|
||||
}
|
||||
|
||||
var slotClients = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength), $"{filepath}:slotClients");
|
||||
var slotClients = MiniYaml.FromString(rs.ReadString(Encoding.UTF8, Connection.MaxOrderLength));
|
||||
SlotClients = new Dictionary<string, SlotClient>();
|
||||
foreach (var s in slotClients)
|
||||
{
|
||||
@@ -144,9 +144,9 @@ namespace OpenRA.Network
|
||||
if (rs.Position != traitDataOffset || rs.ReadInt32() != TraitDataMarker)
|
||||
throw new InvalidDataException("Invalid orasav file");
|
||||
|
||||
var traitData = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength), $"{filepath}:traitData");
|
||||
var traitData = MiniYaml.FromString(rs.ReadString(Encoding.UTF8, Connection.MaxOrderLength));
|
||||
foreach (var td in traitData)
|
||||
TraitData.Add(Exts.ParseInt32Invariant(td.Key), td.Value);
|
||||
TraitData.Add(int.Parse(td.Key), td.Value);
|
||||
|
||||
rs.Seek(0, SeekOrigin.Begin);
|
||||
ordersStream.Write(rs.ReadBytes(metadataOffset), 0, metadataOffset);
|
||||
@@ -226,10 +226,10 @@ namespace OpenRA.Network
|
||||
clientSlot = firstBotSlotIndex;
|
||||
}
|
||||
|
||||
ordersStream.Write(data.Length + 8);
|
||||
ordersStream.Write(frame);
|
||||
ordersStream.Write(clientSlot);
|
||||
ordersStream.Write(data);
|
||||
ordersStream.WriteArray(BitConverter.GetBytes(data.Length + 8));
|
||||
ordersStream.WriteArray(BitConverter.GetBytes(frame));
|
||||
ordersStream.WriteArray(BitConverter.GetBytes(clientSlot));
|
||||
ordersStream.WriteArray(data);
|
||||
LastOrdersFrame = frame;
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@ namespace OpenRA.Network
|
||||
// Send the trait data first to guarantee that it is available when needed
|
||||
foreach (var kv in TraitData)
|
||||
{
|
||||
var data = new List<MiniYamlNode>() { new(kv.Key.ToStringInvariant(), kv.Value) }.WriteToString();
|
||||
var data = new List<MiniYamlNode>() { new MiniYamlNode(kv.Key.ToString(), kv.Value) }.WriteToString();
|
||||
packetFn(0, 0, Order.FromTargetString("SaveTraitData", data, true).Serialize());
|
||||
}
|
||||
|
||||
@@ -284,40 +284,39 @@ namespace OpenRA.Network
|
||||
// - File offset of metadata start marker
|
||||
// - File offset of custom trait data
|
||||
// - Metadata end marker
|
||||
using (var file = File.Create(path))
|
||||
{
|
||||
ordersStream.Seek(0, SeekOrigin.Begin);
|
||||
ordersStream.CopyTo(file);
|
||||
file.Write(MetadataMarker);
|
||||
file.Write(LastOrdersFrame);
|
||||
file.Write(LastSyncFrame);
|
||||
file.Write(lastSyncPacket, 0, Order.SyncHashOrderLength);
|
||||
var file = File.Create(path);
|
||||
|
||||
var globalSettingsNodes = new List<MiniYamlNode>() { GlobalSettings.Serialize() };
|
||||
file.WriteLengthPrefixedString(Encoding.UTF8, globalSettingsNodes.WriteToString());
|
||||
ordersStream.Seek(0, SeekOrigin.Begin);
|
||||
ordersStream.CopyTo(file);
|
||||
file.Write(BitConverter.GetBytes(MetadataMarker), 0, 4);
|
||||
file.Write(BitConverter.GetBytes(LastOrdersFrame), 0, 4);
|
||||
file.Write(BitConverter.GetBytes(LastSyncFrame), 0, 4);
|
||||
file.Write(lastSyncPacket, 0, Order.SyncHashOrderLength);
|
||||
|
||||
var slotNodes = Slots
|
||||
.Select(s => s.Value.Serialize())
|
||||
.ToList();
|
||||
file.WriteLengthPrefixedString(Encoding.UTF8, slotNodes.WriteToString());
|
||||
var globalSettingsNodes = new List<MiniYamlNode>() { GlobalSettings.Serialize() };
|
||||
file.WriteString(Encoding.UTF8, globalSettingsNodes.WriteToString());
|
||||
|
||||
var slotClientNodes = SlotClients
|
||||
.Select(s => s.Value.Serialize(s.Key))
|
||||
.ToList();
|
||||
file.WriteLengthPrefixedString(Encoding.UTF8, slotClientNodes.WriteToString());
|
||||
var slotNodes = Slots
|
||||
.Select(s => s.Value.Serialize())
|
||||
.ToList();
|
||||
file.WriteString(Encoding.UTF8, slotNodes.WriteToString());
|
||||
|
||||
var traitDataOffset = file.Length;
|
||||
file.Write(TraitDataMarker);
|
||||
var slotClientNodes = SlotClients
|
||||
.Select(s => s.Value.Serialize(s.Key))
|
||||
.ToList();
|
||||
file.WriteString(Encoding.UTF8, slotClientNodes.WriteToString());
|
||||
|
||||
var traitDataNodes = TraitData
|
||||
.Select(kv => new MiniYamlNode(kv.Key.ToStringInvariant(), kv.Value))
|
||||
.ToList();
|
||||
file.WriteLengthPrefixedString(Encoding.UTF8, traitDataNodes.WriteToString());
|
||||
var traitDataOffset = file.Length;
|
||||
file.Write(BitConverter.GetBytes(TraitDataMarker), 0, 4);
|
||||
|
||||
file.Write((int)ordersStream.Length);
|
||||
file.Write((int)traitDataOffset);
|
||||
file.Write(EOFMarker);
|
||||
}
|
||||
var traitDataNodes = TraitData
|
||||
.Select(kv => new MiniYamlNode(kv.Key.ToString(), kv.Value))
|
||||
.ToList();
|
||||
file.WriteString(Encoding.UTF8, traitDataNodes.WriteToString());
|
||||
|
||||
file.Write(BitConverter.GetBytes(ordersStream.Length), 0, 4);
|
||||
file.Write(BitConverter.GetBytes(traitDataOffset), 0, 4);
|
||||
file.Write(BitConverter.GetBytes(EOFMarker), 0, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ namespace OpenRA.Network
|
||||
static object LoadClients(MiniYaml yaml)
|
||||
{
|
||||
var clients = new List<GameClient>();
|
||||
var clientsNode = yaml.NodeWithKeyOrDefault("Clients");
|
||||
var clientsNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Clients");
|
||||
if (clientsNode != null)
|
||||
{
|
||||
var regex = new Regex(@"Client@\d+");
|
||||
@@ -159,7 +159,7 @@ namespace OpenRA.Network
|
||||
// Games advertised using the old API used a single Mods field
|
||||
if (Mod == null || Version == null)
|
||||
{
|
||||
var modsNode = yaml.NodeWithKeyOrDefault("Mods");
|
||||
var modsNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Mods");
|
||||
if (modsNode != null)
|
||||
{
|
||||
var modVersion = modsNode.Value.Value.Split('@');
|
||||
@@ -169,8 +169,9 @@ namespace OpenRA.Network
|
||||
}
|
||||
|
||||
// Games advertised using the old API calculated the play time locally
|
||||
if (State == 2 && PlayTime < 0 && DateTime.TryParse(Started, out var startTime))
|
||||
PlayTime = (int)(DateTime.UtcNow - startTime).TotalSeconds;
|
||||
if (State == 2 && PlayTime < 0)
|
||||
if (DateTime.TryParse(Started, out var startTime))
|
||||
PlayTime = (int)(DateTime.UtcNow - startTime).TotalSeconds;
|
||||
|
||||
var externalKey = ExternalMod.MakeKey(Mod, Version);
|
||||
if (Game.ExternalMods.TryGetValue(externalKey, out var external) && external.Version == Version)
|
||||
@@ -216,7 +217,7 @@ namespace OpenRA.Network
|
||||
Name = server.Settings.Name;
|
||||
|
||||
// IP address will be replaced with a real value by the master server / receiving LAN client
|
||||
Address = "0.0.0.0:" + server.Settings.ListenPort.ToStringInvariant();
|
||||
Address = "0.0.0.0:" + server.Settings.ListenPort.ToString();
|
||||
State = (int)server.State;
|
||||
MaxPlayers = server.LobbyInfo.Slots.Count(s => !s.Value.Closed) - server.LobbyInfo.Clients.Count(c1 => c1.Bot != null);
|
||||
Map = server.Map.Uid;
|
||||
@@ -233,7 +234,7 @@ namespace OpenRA.Network
|
||||
|
||||
public string ToPOSTData(bool lanGame)
|
||||
{
|
||||
var root = new List<MiniYamlNode>() { new("Protocol", ProtocolVersion.ToStringInvariant()) };
|
||||
var root = new List<MiniYamlNode>() { new MiniYamlNode("Protocol", ProtocolVersion.ToString()) };
|
||||
foreach (var field in SerializeFields)
|
||||
root.Add(FieldSaver.SaveField(this, field));
|
||||
|
||||
@@ -242,16 +243,18 @@ namespace OpenRA.Network
|
||||
// Add fields that are normally generated by the master server
|
||||
// LAN games overload the Id with a GUID string (rather than an ID) to allow deduplication
|
||||
root.Add(new MiniYamlNode("Id", Platform.SessionGUID.ToString()));
|
||||
root.Add(new MiniYamlNode("Players", Clients.Count(c => !c.IsBot && !c.IsSpectator).ToStringInvariant()));
|
||||
root.Add(new MiniYamlNode("Spectators", Clients.Count(c => c.IsSpectator).ToStringInvariant()));
|
||||
root.Add(new MiniYamlNode("Bots", Clients.Count(c => c.IsBot).ToStringInvariant()));
|
||||
root.Add(new MiniYamlNode("Players", Clients.Count(c => !c.IsBot && !c.IsSpectator).ToString()));
|
||||
root.Add(new MiniYamlNode("Spectators", Clients.Count(c => c.IsSpectator).ToString()));
|
||||
root.Add(new MiniYamlNode("Bots", Clients.Count(c => c.IsBot).ToString()));
|
||||
|
||||
// Included for backwards compatibility with older clients that don't support separated Mod/Version.
|
||||
root.Add(new MiniYamlNode("Mods", Mod + "@" + Version));
|
||||
}
|
||||
|
||||
var clientsNode = new MiniYaml("", Clients.Select((c, i) =>
|
||||
new MiniYamlNode("Client@" + i, FieldSaver.Save(c))));
|
||||
var clientsNode = new MiniYaml("");
|
||||
var i = 0;
|
||||
foreach (var c in Clients)
|
||||
clientsNode.Nodes.Add(new MiniYamlNode("Client@" + i++.ToString(), FieldSaver.Save(c)));
|
||||
|
||||
root.Add(new MiniYamlNode("Clients", clientsNode));
|
||||
return new MiniYaml("", root)
|
||||
|
||||
@@ -19,9 +19,9 @@ using ICSharpCode.SharpZipLib.Zip;
|
||||
|
||||
namespace OpenRA.Network
|
||||
{
|
||||
public static class GeoIP
|
||||
public class GeoIP
|
||||
{
|
||||
sealed class IP2LocationReader
|
||||
class IP2LocationReader
|
||||
{
|
||||
public readonly DateTime Date;
|
||||
readonly Stream stream;
|
||||
@@ -128,8 +128,7 @@ namespace OpenRA.Network
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("geoip", "DatabaseReader failed:");
|
||||
Log.Write("geoip", e);
|
||||
Log.Write("geoip", "DatabaseReader failed: {0}", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,8 +142,7 @@ namespace OpenRA.Network
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("geoip", "LookupCountry failed:");
|
||||
Log.Write("geoip", e);
|
||||
Log.Write("geoip", "LookupCountry failed: {0}", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,16 +20,16 @@ namespace OpenRA.Network
|
||||
public string Version;
|
||||
public string AuthToken;
|
||||
|
||||
public static HandshakeRequest Deserialize(string data, string name)
|
||||
public static HandshakeRequest Deserialize(string data)
|
||||
{
|
||||
var handshake = new HandshakeRequest();
|
||||
FieldLoader.Load(handshake, MiniYaml.FromString(data, name).First().Value);
|
||||
FieldLoader.Load(handshake, MiniYaml.FromString(data).First().Value);
|
||||
return handshake;
|
||||
}
|
||||
|
||||
public string Serialize()
|
||||
{
|
||||
var data = new List<MiniYamlNode> { new("Handshake", FieldSaver.Save(this)) };
|
||||
var data = new List<MiniYamlNode> { new MiniYamlNode("Handshake", FieldSaver.Save(this)) };
|
||||
return data.WriteToString();
|
||||
}
|
||||
}
|
||||
@@ -51,14 +51,14 @@ namespace OpenRA.Network
|
||||
[FieldLoader.Ignore]
|
||||
public Session.Client Client;
|
||||
|
||||
public static HandshakeResponse Deserialize(string data, string name)
|
||||
public static HandshakeResponse Deserialize(string data)
|
||||
{
|
||||
var handshake = new HandshakeResponse
|
||||
{
|
||||
Client = new Session.Client()
|
||||
};
|
||||
|
||||
var ys = MiniYaml.FromString(data, name);
|
||||
var ys = MiniYaml.FromString(data);
|
||||
foreach (var y in ys)
|
||||
{
|
||||
switch (y.Key)
|
||||
@@ -79,9 +79,9 @@ namespace OpenRA.Network
|
||||
{
|
||||
var data = new List<MiniYamlNode>
|
||||
{
|
||||
new("Handshake", null,
|
||||
new MiniYamlNode("Handshake", null,
|
||||
new[] { "Mod", "Version", "Password", "Fingerprint", "AuthSignature", "OrdersProtocol" }.Select(p => FieldSaver.SaveField(this, p)).ToList()),
|
||||
new("Client", FieldSaver.Save(Client))
|
||||
new MiniYamlNode("Client", FieldSaver.Save(Client))
|
||||
};
|
||||
|
||||
return data.WriteToString();
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Linguini.Shared.Types.Bundle;
|
||||
|
||||
namespace OpenRA.Network
|
||||
@@ -55,51 +57,61 @@ namespace OpenRA.Network
|
||||
public readonly string Key = string.Empty;
|
||||
|
||||
[FieldLoader.LoadUsing(nameof(LoadArguments))]
|
||||
public readonly Dictionary<string, object> Arguments;
|
||||
public readonly FluentArgument[] Arguments = Array.Empty<FluentArgument>();
|
||||
|
||||
public string TranslatedText { get; }
|
||||
|
||||
static object LoadArguments(MiniYaml yaml)
|
||||
{
|
||||
var arguments = new Dictionary<string, object>();
|
||||
var argumentsNode = yaml.NodeWithKeyOrDefault("Arguments");
|
||||
var arguments = new List<FluentArgument>();
|
||||
var argumentsNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Arguments");
|
||||
if (argumentsNode != null)
|
||||
{
|
||||
foreach (var argumentNode in argumentsNode.Value.Nodes)
|
||||
{
|
||||
var argument = FieldLoader.Load<FluentArgument>(argumentNode.Value);
|
||||
if (argument.Type == FluentArgument.FluentArgumentType.Number)
|
||||
{
|
||||
if (!double.TryParse(argument.Value, out var number))
|
||||
Log.Write("debug", $"Failed to parse {argument.Value}");
|
||||
|
||||
arguments.Add(argument.Key, number);
|
||||
}
|
||||
else
|
||||
arguments.Add(argument.Key, argument.Value);
|
||||
}
|
||||
var regex = new Regex(@"Argument@\d+");
|
||||
foreach (var argument in argumentsNode.Value.Nodes)
|
||||
if (regex.IsMatch(argument.Key))
|
||||
arguments.Add(FieldLoader.Load<FluentArgument>(argument.Value));
|
||||
}
|
||||
|
||||
return arguments;
|
||||
return arguments.ToArray();
|
||||
}
|
||||
|
||||
public LocalizedMessage(MiniYaml yaml)
|
||||
public LocalizedMessage(ModData modData, MiniYaml yaml)
|
||||
{
|
||||
// Let the FieldLoader do the dirty work of loading the public fields.
|
||||
FieldLoader.Load(this, yaml);
|
||||
|
||||
var argumentDictionary = new Dictionary<string, object>();
|
||||
foreach (var argument in Arguments)
|
||||
{
|
||||
if (argument.Type == FluentArgument.FluentArgumentType.Number)
|
||||
{
|
||||
if (!double.TryParse(argument.Value, out var number))
|
||||
Log.Write("debug", $"Failed to parse {argument.Value}");
|
||||
|
||||
argumentDictionary.Add(argument.Key, number);
|
||||
}
|
||||
else
|
||||
argumentDictionary.Add(argument.Key, argument.Value);
|
||||
}
|
||||
|
||||
TranslatedText = modData.Translation.GetString(Key, argumentDictionary);
|
||||
}
|
||||
|
||||
public static string Serialize(string key, Dictionary<string, object> arguments = null)
|
||||
{
|
||||
var root = new List<MiniYamlNode>
|
||||
{
|
||||
new("Protocol", ProtocolVersion.ToStringInvariant()),
|
||||
new("Key", key)
|
||||
new MiniYamlNode("Protocol", ProtocolVersion.ToString()),
|
||||
new MiniYamlNode("Key", key)
|
||||
};
|
||||
|
||||
if (arguments != null)
|
||||
{
|
||||
var argumentsNode = new MiniYaml("", arguments
|
||||
.Select(a => new FluentArgument(a.Key, a.Value))
|
||||
.Select((argument, i) => new MiniYamlNode("Argument@" + i, FieldSaver.Save(argument))));
|
||||
var argumentsNode = new MiniYaml("");
|
||||
var i = 0;
|
||||
foreach (var argument in arguments.Select(a => new FluentArgument(a.Key, a.Value)))
|
||||
argumentsNode.Nodes.Add(new MiniYamlNode("Argument@" + i++, FieldSaver.Save(argument)));
|
||||
|
||||
root.Add(new MiniYamlNode("Arguments", argumentsNode));
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace OpenRA.Network
|
||||
{
|
||||
public enum NatStatus { Enabled, Disabled, NotSupported }
|
||||
|
||||
public static class Nat
|
||||
public class Nat
|
||||
{
|
||||
public static NatStatus Status => NatUtility.IsSearching ? natDevice != null ? NatStatus.Enabled : NatStatus.NotSupported : NatStatus.Disabled;
|
||||
|
||||
@@ -49,8 +49,8 @@ namespace OpenRA.Network
|
||||
// Only interact with one at a time. Some support both UPnP and NAT-PMP.
|
||||
natDevice = args.Device;
|
||||
|
||||
Log.Write("nat", $"Device found: {natDevice.DeviceEndpoint}");
|
||||
Log.Write("nat", $"Type: {natDevice.NatProtocol}");
|
||||
Log.Write("nat", "Device found: {0}", natDevice.DeviceEndpoint);
|
||||
Log.Write("nat", "Type: {0}", natDevice.NatProtocol);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -156,18 +156,7 @@ namespace OpenRA
|
||||
else
|
||||
{
|
||||
var pos = new WPos(r.ReadInt32(), r.ReadInt32(), r.ReadInt32());
|
||||
|
||||
var numberOfTerrainPositions = r.ReadInt16();
|
||||
if (numberOfTerrainPositions == -1)
|
||||
target = Target.FromPos(pos);
|
||||
else
|
||||
{
|
||||
var terrainPositions = new WPos[numberOfTerrainPositions];
|
||||
for (var i = 0; i < numberOfTerrainPositions; i++)
|
||||
terrainPositions[i] = new WPos(r.ReadInt32(), r.ReadInt32(), r.ReadInt32());
|
||||
|
||||
target = Target.FromSerializedTerrainPosition(pos, terrainPositions);
|
||||
}
|
||||
target = Target.FromPos(pos);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -220,7 +209,7 @@ namespace OpenRA
|
||||
|
||||
default:
|
||||
{
|
||||
Log.Write("debug", $"Received unknown order with type {type}");
|
||||
Log.Write("debug", "Received unknown order with type {0}", type);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -228,7 +217,7 @@ namespace OpenRA
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Caught exception while processing order");
|
||||
Log.Write("debug", e);
|
||||
Log.Write("debug", e.ToString());
|
||||
|
||||
// HACK: this can hopefully go away in the future
|
||||
TextNotificationsManager.Debug("Ignoring malformed order that would have crashed the game");
|
||||
@@ -399,21 +388,6 @@ namespace OpenRA
|
||||
w.Write(targetState.Pos.X);
|
||||
w.Write(targetState.Pos.Y);
|
||||
w.Write(targetState.Pos.Z);
|
||||
|
||||
// Don't send extra data over the network that will be restored by the Target ctor
|
||||
var terrainPositions = targetState.TerrainPositions.Length;
|
||||
if (terrainPositions == 1 && targetState.TerrainPositions[0] == targetState.Pos)
|
||||
w.Write((short)-1);
|
||||
else
|
||||
{
|
||||
w.Write((short)terrainPositions);
|
||||
foreach (var position in targetState.TerrainPositions)
|
||||
{
|
||||
w.Write(position.X);
|
||||
w.Write(position.Y);
|
||||
w.Write(position.Z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace OpenRA.Network
|
||||
// the Order objects directly on the local client.
|
||||
data = new MemoryStream();
|
||||
foreach (var o in orders)
|
||||
data.Write(o.Serialize());
|
||||
data.WriteArray(o.Serialize());
|
||||
}
|
||||
|
||||
public OrderPacket(MemoryStream data)
|
||||
@@ -55,7 +55,7 @@ namespace OpenRA.Network
|
||||
public byte[] Serialize(int frame)
|
||||
{
|
||||
var ms = new MemoryStream((int)data.Length + 4);
|
||||
ms.Write(frame);
|
||||
ms.WriteArray(BitConverter.GetBytes(frame));
|
||||
|
||||
data.Position = 0;
|
||||
data.CopyTo(ms);
|
||||
@@ -83,19 +83,19 @@ namespace OpenRA.Network
|
||||
public static byte[] SerializeSync((int Frame, int SyncHash, ulong DefeatState) data)
|
||||
{
|
||||
var ms = new MemoryStream(4 + Order.SyncHashOrderLength);
|
||||
ms.Write(data.Frame);
|
||||
ms.WriteArray(BitConverter.GetBytes(data.Frame));
|
||||
ms.WriteByte((byte)OrderType.SyncHash);
|
||||
ms.Write(data.SyncHash);
|
||||
ms.Write(data.DefeatState);
|
||||
ms.WriteArray(BitConverter.GetBytes(data.SyncHash));
|
||||
ms.WriteArray(BitConverter.GetBytes(data.DefeatState));
|
||||
return ms.GetBuffer();
|
||||
}
|
||||
|
||||
public static byte[] SerializePingResponse(long timestamp, byte queueLength)
|
||||
{
|
||||
var ms = new MemoryStream(14);
|
||||
ms.Write(0);
|
||||
ms.WriteArray(BitConverter.GetBytes(0));
|
||||
ms.WriteByte((byte)OrderType.Ping);
|
||||
ms.Write(timestamp);
|
||||
ms.WriteArray(BitConverter.GetBytes(timestamp));
|
||||
ms.WriteByte(queueLength);
|
||||
return ms.GetBuffer();
|
||||
}
|
||||
|
||||
@@ -22,9 +22,6 @@ namespace OpenRA.Network
|
||||
{
|
||||
const OrderPacket ClientDisconnected = null;
|
||||
|
||||
[TranslationReference("frame")]
|
||||
const string DesyncCompareLogs = "notification-desync-compare-logs";
|
||||
|
||||
readonly SyncReport syncReport;
|
||||
readonly Dictionary<int, Queue<(int Frame, OrderPacket Orders)>> pendingOrders = new();
|
||||
readonly Dictionary<int, (int SyncHash, ulong DefeatState)> syncForFrame = new();
|
||||
@@ -39,9 +36,6 @@ namespace OpenRA.Network
|
||||
public string ServerError = null;
|
||||
public bool AuthenticationFailed = false;
|
||||
|
||||
// The default null means "no map restriction" while an empty set means "all maps restricted"
|
||||
public HashSet<string> ServerMapPool = null;
|
||||
|
||||
public int NetFrameNumber { get; private set; }
|
||||
public int LocalFrameNumber;
|
||||
|
||||
@@ -76,7 +70,7 @@ namespace OpenRA.Network
|
||||
public int Client;
|
||||
public Order Order;
|
||||
|
||||
public override readonly string ToString()
|
||||
public override string ToString()
|
||||
{
|
||||
return $"ClientId: {Client} {Order}";
|
||||
}
|
||||
@@ -91,7 +85,7 @@ namespace OpenRA.Network
|
||||
World.OutOfSync();
|
||||
IsOutOfSync = true;
|
||||
|
||||
TextNotificationsManager.AddSystemLine(DesyncCompareLogs, Translation.Arguments("frame", frame));
|
||||
TextNotificationsManager.AddSystemLine($"Out of sync in frame {frame}.\nCompare syncreport.log with other players.");
|
||||
}
|
||||
|
||||
public void StartGame()
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace OpenRA.Network
|
||||
{
|
||||
public sealed class ReplayConnection : IConnection
|
||||
{
|
||||
sealed class Chunk
|
||||
class Chunk
|
||||
{
|
||||
public int Frame;
|
||||
public (int ClientId, byte[] Packet)[] Packets;
|
||||
@@ -69,7 +69,7 @@ namespace OpenRA.Network
|
||||
if (o.OrderString == "StartGame")
|
||||
IsValid = true;
|
||||
else if (o.OrderString == "SyncInfo" && !IsValid)
|
||||
LobbyInfo = Session.Deserialize(o.TargetString, o.OrderString);
|
||||
LobbyInfo = Session.Deserialize(o.TargetString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace OpenRA.Network
|
||||
}
|
||||
}
|
||||
|
||||
file.Write(initialContent);
|
||||
file.WriteArray(initialContent);
|
||||
writer = new BinaryWriter(file);
|
||||
}
|
||||
|
||||
@@ -92,8 +92,8 @@ namespace OpenRA.Network
|
||||
public void ReceiveFrame(int clientID, int frame, byte[] data)
|
||||
{
|
||||
var ms = new MemoryStream(4 + data.Length);
|
||||
ms.Write(frame);
|
||||
ms.Write(data);
|
||||
ms.WriteArray(BitConverter.GetBytes(frame));
|
||||
ms.WriteArray(data);
|
||||
Receive(clientID, ms.GetBuffer());
|
||||
}
|
||||
|
||||
|
||||
@@ -41,13 +41,13 @@ namespace OpenRA.Network
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Session Deserialize(string data, string name)
|
||||
public static Session Deserialize(string data)
|
||||
{
|
||||
try
|
||||
{
|
||||
var session = new Session();
|
||||
|
||||
var nodes = MiniYaml.FromString(data, name);
|
||||
var nodes = MiniYaml.FromString(data);
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
var strings = node.Key.Split('@');
|
||||
@@ -227,7 +227,7 @@ namespace OpenRA.Network
|
||||
{
|
||||
var gs = FieldLoader.Load<Global>(data);
|
||||
|
||||
var optionsNode = data.NodeWithKeyOrDefault("Options");
|
||||
var optionsNode = data.Nodes.FirstOrDefault(n => n.Key == "Options");
|
||||
if (optionsNode != null)
|
||||
foreach (var n in optionsNode.Value.Nodes)
|
||||
gs.LobbyOptions[n.Key] = FieldLoader.Load<LobbyOptionState>(n.Value);
|
||||
@@ -238,9 +238,8 @@ namespace OpenRA.Network
|
||||
public MiniYamlNode Serialize()
|
||||
{
|
||||
var data = new MiniYamlNode("GlobalSettings", FieldSaver.Save(this));
|
||||
var options = LobbyOptions.Select(kv => new MiniYamlNode(kv.Key, FieldSaver.Save(kv.Value)));
|
||||
data = data.WithValue(data.Value.WithNodesAppended(
|
||||
new[] { new MiniYamlNode("Options", new MiniYaml(null, options)) }));
|
||||
var options = LobbyOptions.Select(kv => new MiniYamlNode(kv.Key, FieldSaver.Save(kv.Value))).ToList();
|
||||
data.Value.Nodes.Add(new MiniYamlNode("Options", new MiniYaml(null, options)));
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -265,7 +264,7 @@ namespace OpenRA.Network
|
||||
{
|
||||
var sessionData = new List<MiniYamlNode>()
|
||||
{
|
||||
new("DisabledSpawnPoints", FieldSaver.FormatValue(DisabledSpawnPoints))
|
||||
new MiniYamlNode("DisabledSpawnPoints", FieldSaver.FormatValue(DisabledSpawnPoints))
|
||||
};
|
||||
|
||||
foreach (var client in Clients)
|
||||
|
||||
@@ -19,7 +19,7 @@ using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Network
|
||||
{
|
||||
sealed class SyncReport
|
||||
class SyncReport
|
||||
{
|
||||
const int NumSyncReports = 7;
|
||||
static readonly Cache<Type, TypeInfo> TypeInfoCache = new(t => new TypeInfo(t));
|
||||
@@ -119,12 +119,12 @@ namespace OpenRA.Network
|
||||
{
|
||||
desyncFrameFound = true;
|
||||
var mod = Game.ModData.Manifest.Metadata;
|
||||
Log.Write("sync", $"Player: {Game.Settings.Player.Name} ({Platform.CurrentPlatform} {Environment.OSVersion} {Platform.RuntimeVersion})");
|
||||
Log.Write("sync", "Player: {0} ({1} {2} {3})", Game.Settings.Player.Name, Platform.CurrentPlatform, Environment.OSVersion, Platform.RuntimeVersion);
|
||||
if (Game.IsHost)
|
||||
Log.Write("sync", "Player is host.");
|
||||
Log.Write("sync", $"Game ID: {orderManager.LobbyInfo.GlobalSettings.GameUid} (Mod: {mod.Title} at Version {mod.Version})");
|
||||
Log.Write("sync", $"Sync for net frame {r.Frame} -------------");
|
||||
Log.Write("sync", $"SharedRandom: {r.SyncedRandom} (#{r.TotalCount})");
|
||||
Log.Write("sync", "Game ID: {0} (Mod: {1} at Version {2})", orderManager.LobbyInfo.GlobalSettings.GameUid, mod.Title, mod.Version);
|
||||
Log.Write("sync", "Sync for net frame {0} -------------", r.Frame);
|
||||
Log.Write("sync", "SharedRandom: {0} (#{1})", r.SyncedRandom, r.TotalCount);
|
||||
Log.Write("sync", "Synced Traits:");
|
||||
foreach (var a in r.Traits)
|
||||
{
|
||||
@@ -139,7 +139,7 @@ namespace OpenRA.Network
|
||||
Log.Write("sync", "Synced Effects:");
|
||||
foreach (var e in r.Effects)
|
||||
{
|
||||
Log.Write("sync", $"\t {e.Name} ({e.Hash})");
|
||||
Log.Write("sync", "\t {0} ({1})", e.Name, e.Hash);
|
||||
|
||||
var nvp = e.NamesValues;
|
||||
for (var i = 0; i < nvp.Names.Length; i++)
|
||||
@@ -149,7 +149,7 @@ namespace OpenRA.Network
|
||||
|
||||
Log.Write("sync", "Orders Issued:");
|
||||
foreach (var o in r.Orders)
|
||||
Log.Write("sync", $"\t {o}");
|
||||
Log.Write("sync", "\t {0}", o.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ namespace OpenRA.Network
|
||||
Log.Write("sync", $"Recorded frames do not contain the frame {frame}. No sync report available!");
|
||||
}
|
||||
|
||||
sealed class Report
|
||||
class Report
|
||||
{
|
||||
public int Frame;
|
||||
public int SyncedRandom;
|
||||
@@ -201,12 +201,8 @@ namespace OpenRA.Network
|
||||
public TypeInfo(Type type)
|
||||
{
|
||||
const BindingFlags Flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
||||
var fields = type.GetFields(Flags)
|
||||
.Where(fi => !fi.IsLiteral && !fi.IsStatic && fi.HasAttribute<SyncAttribute>())
|
||||
.ToList();
|
||||
var properties = type.GetProperties(Flags)
|
||||
.Where(pi => pi.HasAttribute<SyncAttribute>())
|
||||
.ToList();
|
||||
var fields = type.GetFields(Flags).Where(fi => !fi.IsLiteral && !fi.IsStatic && fi.HasAttribute<SyncAttribute>());
|
||||
var properties = type.GetProperties(Flags).Where(pi => pi.HasAttribute<SyncAttribute>());
|
||||
|
||||
foreach (var prop in properties)
|
||||
if (!prop.CanRead || prop.GetIndexParameters().Length > 0)
|
||||
@@ -304,7 +300,7 @@ namespace OpenRA.Network
|
||||
|
||||
public object this[int index]
|
||||
{
|
||||
readonly get
|
||||
get
|
||||
{
|
||||
if (item2OrSentinel == Sentinel)
|
||||
return ((object[])item1OrArray)[index];
|
||||
|
||||
@@ -20,33 +20,11 @@ namespace OpenRA.Network
|
||||
{
|
||||
public const int ChatMessageMaxLength = 2500;
|
||||
|
||||
[TranslationReference("player")]
|
||||
const string Joined = "notification-joined";
|
||||
|
||||
[TranslationReference("player")]
|
||||
const string Left = "notification-lobby-disconnected";
|
||||
|
||||
[TranslationReference]
|
||||
const string GameStarted = "notification-game-has-started";
|
||||
|
||||
[TranslationReference]
|
||||
const string GameSaved = "notification-game-saved";
|
||||
|
||||
[TranslationReference("player")]
|
||||
const string GamePaused = "notification-game-paused";
|
||||
|
||||
[TranslationReference("player")]
|
||||
const string GameUnpaused = "notification-game-unpaused";
|
||||
|
||||
public static int? KickVoteTarget { get; internal set; }
|
||||
|
||||
static Player FindPlayerByClient(this World world, Session.Client c)
|
||||
{
|
||||
return world.Players.FirstOrDefault(p => p.ClientIndex == c.Index && p.PlayerReference.Playable);
|
||||
}
|
||||
|
||||
static bool OrderNotFromServerOrWorldIsReplay(int clientId, World world) => clientId != 0 || (world != null && world.IsReplay);
|
||||
|
||||
internal static void ProcessOrder(OrderManager orderManager, World world, int clientId, Order order)
|
||||
{
|
||||
switch (order.OrderString)
|
||||
@@ -58,350 +36,324 @@ namespace OpenRA.Network
|
||||
|
||||
// Client side translated server message
|
||||
case "LocalizedMessage":
|
||||
{
|
||||
if (string.IsNullOrEmpty(order.TargetString))
|
||||
break;
|
||||
|
||||
var yaml = MiniYaml.FromString(order.TargetString, order.OrderString);
|
||||
foreach (var node in yaml)
|
||||
{
|
||||
var localizedMessage = new LocalizedMessage(node.Value);
|
||||
if (localizedMessage.Key == Joined)
|
||||
TextNotificationsManager.AddPlayerJoinedLine(localizedMessage.Key, localizedMessage.Arguments);
|
||||
else if (localizedMessage.Key == Left)
|
||||
TextNotificationsManager.AddPlayerLeftLine(localizedMessage.Key, localizedMessage.Arguments);
|
||||
else
|
||||
TextNotificationsManager.AddSystemLine(localizedMessage.Key, localizedMessage.Arguments);
|
||||
}
|
||||
if (string.IsNullOrEmpty(order.TargetString))
|
||||
break;
|
||||
|
||||
break;
|
||||
}
|
||||
var yaml = MiniYaml.FromString(order.TargetString);
|
||||
foreach (var node in yaml)
|
||||
{
|
||||
var localizedMessage = new LocalizedMessage(Game.ModData, node.Value);
|
||||
TextNotificationsManager.AddSystemLine(localizedMessage.TranslatedText);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "DisableChatEntry":
|
||||
{
|
||||
if (OrderNotFromServerOrWorldIsReplay(clientId, world))
|
||||
{
|
||||
// Order must originate from the server
|
||||
// Don't disable chat in replays
|
||||
if (clientId != 0 || (world != null && world.IsReplay))
|
||||
break;
|
||||
|
||||
// Server may send MaxValue to indicate that it is disabled until further notice
|
||||
if (order.ExtraData == uint.MaxValue)
|
||||
TextNotificationsManager.ChatDisabledUntil = uint.MaxValue;
|
||||
else
|
||||
TextNotificationsManager.ChatDisabledUntil = Game.RunTime + order.ExtraData;
|
||||
|
||||
break;
|
||||
|
||||
// Server may send MaxValue to indicate that it is disabled until further notice
|
||||
if (order.ExtraData == uint.MaxValue)
|
||||
TextNotificationsManager.ChatDisabledUntil = uint.MaxValue;
|
||||
else
|
||||
TextNotificationsManager.ChatDisabledUntil = Game.RunTime + order.ExtraData;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "StartKickVote":
|
||||
{
|
||||
if (OrderNotFromServerOrWorldIsReplay(clientId, world))
|
||||
break;
|
||||
|
||||
KickVoteTarget = (int)order.ExtraData;
|
||||
break;
|
||||
}
|
||||
|
||||
case "EndKickVote":
|
||||
{
|
||||
if (OrderNotFromServerOrWorldIsReplay(clientId, world))
|
||||
break;
|
||||
|
||||
if (KickVoteTarget == (int)order.ExtraData)
|
||||
KickVoteTarget = null;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
case "Chat":
|
||||
{
|
||||
var client = orderManager.LobbyInfo.ClientWithIndex(clientId);
|
||||
if (client == null)
|
||||
break;
|
||||
|
||||
// Cut chat messages to the hard limit to avoid exploits
|
||||
var message = order.TargetString;
|
||||
if (message.Length > ChatMessageMaxLength)
|
||||
message = order.TargetString[..ChatMessageMaxLength];
|
||||
|
||||
// ExtraData 0 means this is a normal chat order, everything else is team chat
|
||||
if (order.ExtraData == 0)
|
||||
{
|
||||
var p = world?.FindPlayerByClient(client);
|
||||
var suffix = (p != null && p.WinState == WinState.Lost) ? " (Dead)" : "";
|
||||
suffix = client.IsObserver ? " (Spectator)" : suffix;
|
||||
var client = orderManager.LobbyInfo.ClientWithIndex(clientId);
|
||||
if (client == null)
|
||||
break;
|
||||
|
||||
if (orderManager.LocalClient != null && client != orderManager.LocalClient && client.Team > 0 && client.Team == orderManager.LocalClient.Team)
|
||||
suffix += " (Ally)";
|
||||
// Cut chat messages to the hard limit to avoid exploits
|
||||
var message = order.TargetString;
|
||||
if (message.Length > ChatMessageMaxLength)
|
||||
message = order.TargetString[..ChatMessageMaxLength];
|
||||
|
||||
TextNotificationsManager.AddChatLine(clientId, client.Name + suffix, message, client.Color);
|
||||
break;
|
||||
}
|
||||
// ExtraData 0 means this is a normal chat order, everything else is team chat
|
||||
if (order.ExtraData == 0)
|
||||
{
|
||||
var p = world?.FindPlayerByClient(client);
|
||||
var suffix = (p != null && p.WinState == WinState.Lost) ? " (Dead)" : "";
|
||||
suffix = client.IsObserver ? " (Spectator)" : suffix;
|
||||
|
||||
// We are still in the lobby
|
||||
if (world == null)
|
||||
{
|
||||
var prefix = order.ExtraData == uint.MaxValue ? "[Spectators] " : "[Team] ";
|
||||
if (orderManager.LocalClient != null && client.Team == orderManager.LocalClient.Team)
|
||||
TextNotificationsManager.AddChatLine(clientId, prefix + client.Name, message, client.Color);
|
||||
if (orderManager.LocalClient != null && client != orderManager.LocalClient && client.Team > 0 && client.Team == orderManager.LocalClient.Team)
|
||||
suffix += " (Ally)";
|
||||
|
||||
TextNotificationsManager.AddChatLine(clientId, client.Name + suffix, message, client.Color);
|
||||
break;
|
||||
}
|
||||
|
||||
// We are still in the lobby
|
||||
if (world == null)
|
||||
{
|
||||
var prefix = order.ExtraData == uint.MaxValue ? "[Spectators] " : "[Team] ";
|
||||
if (orderManager.LocalClient != null && client.Team == orderManager.LocalClient.Team)
|
||||
TextNotificationsManager.AddChatLine(clientId, prefix + client.Name, message, client.Color);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
var player = world.FindPlayerByClient(client);
|
||||
var localClientIsObserver = world.IsReplay || (orderManager.LocalClient != null && orderManager.LocalClient.IsObserver)
|
||||
|| (world.LocalPlayer != null && world.LocalPlayer.WinState != WinState.Undefined);
|
||||
|
||||
// ExtraData gives us the team number, uint.MaxValue means Spectators
|
||||
if (order.ExtraData == uint.MaxValue && localClientIsObserver)
|
||||
{
|
||||
// Validate before adding the line
|
||||
if (client.IsObserver || (player != null && player.WinState != WinState.Undefined))
|
||||
TextNotificationsManager.AddChatLine(clientId, "[Spectators] " + client.Name, message, client.Color);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
var valid = client.Team == order.ExtraData && player != null && player.WinState == WinState.Undefined;
|
||||
var isSameTeam = orderManager.LocalClient != null && order.ExtraData == orderManager.LocalClient.Team
|
||||
&& world.LocalPlayer != null && world.LocalPlayer.WinState == WinState.Undefined;
|
||||
|
||||
if (valid && (isSameTeam || world.IsReplay))
|
||||
TextNotificationsManager.AddChatLine(clientId, "[Team" + (world.IsReplay ? " " + order.ExtraData : "") + "] " + client.Name, message, client.Color);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
var player = world.FindPlayerByClient(client);
|
||||
var localClientIsObserver = world.IsReplay || (orderManager.LocalClient != null && orderManager.LocalClient.IsObserver)
|
||||
|| (world.LocalPlayer != null && world.LocalPlayer.WinState != WinState.Undefined);
|
||||
|
||||
// ExtraData gives us the team number, uint.MaxValue means Spectators
|
||||
if (order.ExtraData == uint.MaxValue && localClientIsObserver)
|
||||
{
|
||||
// Validate before adding the line
|
||||
if (client.IsObserver || (player != null && player.WinState != WinState.Undefined))
|
||||
TextNotificationsManager.AddChatLine(clientId, "[Spectators] " + client.Name, message, client.Color);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
var valid = client.Team == order.ExtraData && player != null && player.WinState == WinState.Undefined;
|
||||
var isSameTeam = orderManager.LocalClient != null && order.ExtraData == orderManager.LocalClient.Team
|
||||
&& world.LocalPlayer != null && world.LocalPlayer.WinState == WinState.Undefined;
|
||||
|
||||
if (valid && (isSameTeam || world.IsReplay))
|
||||
TextNotificationsManager.AddChatLine(clientId, "[Team" + (world.IsReplay ? " " + order.ExtraData : "") + "] " + client.Name, message, client.Color);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "StartGame":
|
||||
{
|
||||
if (Game.ModData.MapCache[orderManager.LobbyInfo.GlobalSettings.Map].Status != MapStatus.Available)
|
||||
{
|
||||
Game.Disconnect();
|
||||
Game.LoadShellMap();
|
||||
if (Game.ModData.MapCache[orderManager.LobbyInfo.GlobalSettings.Map].Status != MapStatus.Available)
|
||||
{
|
||||
Game.Disconnect();
|
||||
Game.LoadShellMap();
|
||||
|
||||
// TODO: After adding a startup error dialog, notify the replay load failure.
|
||||
// TODO: After adding a startup error dialog, notify the replay load failure.
|
||||
break;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(order.TargetString))
|
||||
{
|
||||
var data = MiniYaml.FromString(order.TargetString);
|
||||
var saveLastOrdersFrame = data.FirstOrDefault(n => n.Key == "SaveLastOrdersFrame");
|
||||
if (saveLastOrdersFrame != null)
|
||||
orderManager.GameSaveLastFrame =
|
||||
FieldLoader.GetValue<int>("saveLastOrdersFrame", saveLastOrdersFrame.Value.Value);
|
||||
|
||||
var saveSyncFrame = data.FirstOrDefault(n => n.Key == "SaveSyncFrame");
|
||||
if (saveSyncFrame != null)
|
||||
orderManager.GameSaveLastSyncFrame =
|
||||
FieldLoader.GetValue<int>("SaveSyncFrame", saveSyncFrame.Value.Value);
|
||||
}
|
||||
else
|
||||
TextNotificationsManager.AddSystemLine("The game has started.");
|
||||
|
||||
Game.StartGame(orderManager.LobbyInfo.GlobalSettings.Map, WorldType.Regular);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(order.TargetString))
|
||||
{
|
||||
var data = MiniYaml.FromString(order.TargetString, order.OrderString);
|
||||
var saveLastOrdersFrame = data.FirstOrDefault(n => n.Key == "SaveLastOrdersFrame");
|
||||
if (saveLastOrdersFrame != null)
|
||||
orderManager.GameSaveLastFrame =
|
||||
FieldLoader.GetValue<int>("saveLastOrdersFrame", saveLastOrdersFrame.Value.Value);
|
||||
|
||||
var saveSyncFrame = data.FirstOrDefault(n => n.Key == "SaveSyncFrame");
|
||||
if (saveSyncFrame != null)
|
||||
orderManager.GameSaveLastSyncFrame =
|
||||
FieldLoader.GetValue<int>("SaveSyncFrame", saveSyncFrame.Value.Value);
|
||||
}
|
||||
else
|
||||
TextNotificationsManager.AddSystemLine(GameStarted);
|
||||
|
||||
Game.StartGame(orderManager.LobbyInfo.GlobalSettings.Map, WorldType.Regular);
|
||||
break;
|
||||
}
|
||||
|
||||
case "SaveTraitData":
|
||||
{
|
||||
var data = MiniYaml.FromString(order.TargetString, order.OrderString)[0];
|
||||
var traitIndex = Exts.ParseInt32Invariant(data.Key);
|
||||
{
|
||||
var data = MiniYaml.FromString(order.TargetString)[0];
|
||||
var traitIndex = int.Parse(data.Key);
|
||||
|
||||
world?.AddGameSaveTraitData(traitIndex, data.Value);
|
||||
world?.AddGameSaveTraitData(traitIndex, data.Value);
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "GameSaved":
|
||||
if (!orderManager.World.IsReplay)
|
||||
TextNotificationsManager.AddSystemLine(GameSaved);
|
||||
TextNotificationsManager.AddSystemLine("Game saved");
|
||||
|
||||
foreach (var nsr in orderManager.World.WorldActor.TraitsImplementing<INotifyGameSaved>())
|
||||
nsr.GameSaved(orderManager.World);
|
||||
break;
|
||||
|
||||
case "PauseGame":
|
||||
{
|
||||
var client = orderManager.LobbyInfo.ClientWithIndex(clientId);
|
||||
if (client != null)
|
||||
{
|
||||
var pause = order.TargetString == "Pause";
|
||||
var client = orderManager.LobbyInfo.ClientWithIndex(clientId);
|
||||
if (client != null)
|
||||
{
|
||||
var pause = order.TargetString == "Pause";
|
||||
|
||||
// Prevent injected unpause orders from restarting a finished game
|
||||
if (orderManager.World.IsGameOver && !pause)
|
||||
break;
|
||||
// Prevent injected unpause orders from restarting a finished game
|
||||
if (orderManager.World.IsGameOver && !pause)
|
||||
break;
|
||||
|
||||
if (orderManager.World.Paused != pause && world != null && world.LobbyInfo.NonBotClients.Count() > 1)
|
||||
TextNotificationsManager.AddSystemLine(pause ? GamePaused : GameUnpaused, Translation.Arguments("player", client.Name));
|
||||
if (orderManager.World.Paused != pause && world != null && world.LobbyInfo.NonBotClients.Count() > 1)
|
||||
{
|
||||
var pausetext = $"The game is {(pause ? "paused" : "un-paused")} by {client.Name}";
|
||||
TextNotificationsManager.AddSystemLine(pausetext);
|
||||
}
|
||||
|
||||
orderManager.World.Paused = pause;
|
||||
orderManager.World.PredictedPaused = pause;
|
||||
orderManager.World.Paused = pause;
|
||||
orderManager.World.PredictedPaused = pause;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "HandshakeRequest":
|
||||
{
|
||||
// Switch to the server's mod if we need and are able to
|
||||
var mod = Game.ModData.Manifest;
|
||||
var request = HandshakeRequest.Deserialize(order.TargetString, order.OrderString);
|
||||
|
||||
var externalKey = ExternalMod.MakeKey(request.Mod, request.Version);
|
||||
if ((request.Mod != mod.Id || request.Version != mod.Metadata.Version) &&
|
||||
Game.ExternalMods.TryGetValue(externalKey, out var external))
|
||||
{
|
||||
// The ConnectionFailedLogic will prompt the user to switch mods
|
||||
CurrentServerSettings.ServerExternalMod = external;
|
||||
orderManager.Connection.Dispose();
|
||||
// Switch to the server's mod if we need and are able to
|
||||
var mod = Game.ModData.Manifest;
|
||||
var request = HandshakeRequest.Deserialize(order.TargetString);
|
||||
|
||||
var externalKey = ExternalMod.MakeKey(request.Mod, request.Version);
|
||||
if ((request.Mod != mod.Id || request.Version != mod.Metadata.Version) &&
|
||||
Game.ExternalMods.TryGetValue(externalKey, out var external))
|
||||
{
|
||||
// The ConnectionFailedLogic will prompt the user to switch mods
|
||||
CurrentServerSettings.ServerExternalMod = external;
|
||||
orderManager.Connection.Dispose();
|
||||
break;
|
||||
}
|
||||
|
||||
Game.Settings.Player.Name = Settings.SanitizedPlayerName(Game.Settings.Player.Name);
|
||||
Game.Settings.Save();
|
||||
|
||||
// Otherwise send the handshake with our current settings and let the server reject us
|
||||
var info = new Session.Client()
|
||||
{
|
||||
Name = Game.Settings.Player.Name,
|
||||
PreferredColor = Game.Settings.Player.Color,
|
||||
Color = Game.Settings.Player.Color,
|
||||
Faction = "Random",
|
||||
SpawnPoint = 0,
|
||||
Team = 0,
|
||||
State = Session.ClientState.Invalid
|
||||
};
|
||||
|
||||
var localProfile = Game.LocalPlayerProfile;
|
||||
var response = new HandshakeResponse()
|
||||
{
|
||||
Client = info,
|
||||
Mod = mod.Id,
|
||||
Version = mod.Metadata.Version,
|
||||
Password = CurrentServerSettings.Password,
|
||||
Fingerprint = localProfile.Fingerprint,
|
||||
OrdersProtocol = ProtocolVersion.Orders
|
||||
};
|
||||
|
||||
if (request.AuthToken != null && response.Fingerprint != null)
|
||||
response.AuthSignature = localProfile.Sign(request.AuthToken);
|
||||
|
||||
orderManager.IssueOrder(new Order("HandshakeResponse", null, false)
|
||||
{
|
||||
Type = OrderType.Handshake,
|
||||
IsImmediate = true,
|
||||
TargetString = response.Serialize()
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
Game.Settings.Player.Name = Settings.SanitizedPlayerName(Game.Settings.Player.Name);
|
||||
Game.Settings.Save();
|
||||
|
||||
// Otherwise send the handshake with our current settings and let the server reject us
|
||||
var info = new Session.Client()
|
||||
{
|
||||
Name = Game.Settings.Player.Name,
|
||||
PreferredColor = Game.Settings.Player.Color,
|
||||
Color = Game.Settings.Player.Color,
|
||||
Faction = "Random",
|
||||
SpawnPoint = 0,
|
||||
Team = 0,
|
||||
State = Session.ClientState.Invalid
|
||||
};
|
||||
|
||||
var localProfile = Game.LocalPlayerProfile;
|
||||
var response = new HandshakeResponse()
|
||||
{
|
||||
Client = info,
|
||||
Mod = mod.Id,
|
||||
Version = mod.Metadata.Version,
|
||||
Password = CurrentServerSettings.Password,
|
||||
Fingerprint = localProfile.Fingerprint,
|
||||
OrdersProtocol = ProtocolVersion.Orders
|
||||
};
|
||||
|
||||
if (request.AuthToken != null && response.Fingerprint != null)
|
||||
response.AuthSignature = localProfile.Sign(request.AuthToken);
|
||||
|
||||
orderManager.IssueOrder(new Order("HandshakeResponse", null, false)
|
||||
{
|
||||
Type = OrderType.Handshake,
|
||||
IsImmediate = true,
|
||||
TargetString = response.Serialize()
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "ServerError":
|
||||
{
|
||||
orderManager.ServerError = order.TargetString;
|
||||
orderManager.AuthenticationFailed = false;
|
||||
break;
|
||||
}
|
||||
{
|
||||
orderManager.ServerError = order.TargetString;
|
||||
orderManager.AuthenticationFailed = false;
|
||||
break;
|
||||
}
|
||||
|
||||
case "AuthenticationError":
|
||||
{
|
||||
// The ConnectionFailedLogic will prompt the user for the password
|
||||
orderManager.ServerError = order.TargetString;
|
||||
orderManager.AuthenticationFailed = true;
|
||||
break;
|
||||
}
|
||||
{
|
||||
// The ConnectionFailedLogic will prompt the user for the password
|
||||
orderManager.ServerError = order.TargetString;
|
||||
orderManager.AuthenticationFailed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case "SyncInfo":
|
||||
{
|
||||
orderManager.LobbyInfo = Session.Deserialize(order.TargetString, order.OrderString);
|
||||
Game.SyncLobbyInfo();
|
||||
break;
|
||||
}
|
||||
{
|
||||
orderManager.LobbyInfo = Session.Deserialize(order.TargetString);
|
||||
Game.SyncLobbyInfo();
|
||||
break;
|
||||
}
|
||||
|
||||
case "SyncLobbyClients":
|
||||
{
|
||||
var clients = new List<Session.Client>();
|
||||
var nodes = MiniYaml.FromString(order.TargetString, order.OrderString);
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
var strings = node.Key.Split('@');
|
||||
if (strings[0] == "Client")
|
||||
clients.Add(Session.Client.Deserialize(node.Value));
|
||||
}
|
||||
var clients = new List<Session.Client>();
|
||||
var nodes = MiniYaml.FromString(order.TargetString);
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
var strings = node.Key.Split('@');
|
||||
if (strings[0] == "Client")
|
||||
clients.Add(Session.Client.Deserialize(node.Value));
|
||||
}
|
||||
|
||||
orderManager.LobbyInfo.Clients = clients;
|
||||
Game.SyncLobbyInfo();
|
||||
break;
|
||||
}
|
||||
orderManager.LobbyInfo.Clients = clients;
|
||||
Game.SyncLobbyInfo();
|
||||
break;
|
||||
}
|
||||
|
||||
case "SyncLobbySlots":
|
||||
{
|
||||
var slots = new Dictionary<string, Session.Slot>();
|
||||
var nodes = MiniYaml.FromString(order.TargetString, order.OrderString);
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
var strings = node.Key.Split('@');
|
||||
if (strings[0] == "Slot")
|
||||
var slots = new Dictionary<string, Session.Slot>();
|
||||
var nodes = MiniYaml.FromString(order.TargetString);
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
var slot = Session.Slot.Deserialize(node.Value);
|
||||
slots.Add(slot.PlayerReference, slot);
|
||||
var strings = node.Key.Split('@');
|
||||
if (strings[0] == "Slot")
|
||||
{
|
||||
var slot = Session.Slot.Deserialize(node.Value);
|
||||
slots.Add(slot.PlayerReference, slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
orderManager.LobbyInfo.Slots = slots;
|
||||
Game.SyncLobbyInfo();
|
||||
break;
|
||||
}
|
||||
orderManager.LobbyInfo.Slots = slots;
|
||||
Game.SyncLobbyInfo();
|
||||
break;
|
||||
}
|
||||
|
||||
case "SyncLobbyGlobalSettings":
|
||||
{
|
||||
var nodes = MiniYaml.FromString(order.TargetString, order.OrderString);
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
var strings = node.Key.Split('@');
|
||||
if (strings[0] == "GlobalSettings")
|
||||
orderManager.LobbyInfo.GlobalSettings = Session.Global.Deserialize(node.Value);
|
||||
}
|
||||
var nodes = MiniYaml.FromString(order.TargetString);
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
var strings = node.Key.Split('@');
|
||||
if (strings[0] == "GlobalSettings")
|
||||
orderManager.LobbyInfo.GlobalSettings = Session.Global.Deserialize(node.Value);
|
||||
}
|
||||
|
||||
Game.SyncLobbyInfo();
|
||||
break;
|
||||
}
|
||||
Game.SyncLobbyInfo();
|
||||
break;
|
||||
}
|
||||
|
||||
case "SyncConnectionQuality":
|
||||
{
|
||||
var nodes = MiniYaml.FromString(order.TargetString, order.OrderString);
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
var strings = node.Key.Split('@');
|
||||
if (strings[0] == "ConnectionQuality")
|
||||
var nodes = MiniYaml.FromString(order.TargetString);
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
var client = orderManager.LobbyInfo.Clients.FirstOrDefault(c => c.Index == Exts.ParseInt32Invariant(strings[1]));
|
||||
if (client != null)
|
||||
client.ConnectionQuality = FieldLoader.GetValue<Session.ConnectionQuality>("ConnectionQuality", node.Value.Value);
|
||||
var strings = node.Key.Split('@');
|
||||
if (strings[0] == "ConnectionQuality")
|
||||
{
|
||||
var client = orderManager.LobbyInfo.Clients.FirstOrDefault(c => c.Index == int.Parse(strings[1]));
|
||||
if (client != null)
|
||||
client.ConnectionQuality = FieldLoader.GetValue<Session.ConnectionQuality>("ConnectionQuality", node.Value.Value);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "SyncMapPool":
|
||||
{
|
||||
orderManager.ServerMapPool = FieldLoader.GetValue<HashSet<string>>("SyncMapPool", order.TargetString);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
if (world == null)
|
||||
{
|
||||
if (world == null)
|
||||
break;
|
||||
|
||||
if (order.GroupedActors == null)
|
||||
ResolveOrder(order, world, orderManager, clientId);
|
||||
else
|
||||
foreach (var subject in order.GroupedActors)
|
||||
ResolveOrder(Order.FromGroupedOrder(order, subject), world, orderManager, clientId);
|
||||
|
||||
break;
|
||||
|
||||
if (order.GroupedActors == null)
|
||||
ResolveOrder(order, world, orderManager, clientId);
|
||||
else
|
||||
foreach (var subject in order.GroupedActors)
|
||||
ResolveOrder(Order.FromGroupedOrder(order, subject), world, orderManager, clientId);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -413,10 +365,5 @@ namespace OpenRA.Network
|
||||
if (world.OrderValidators.All(vo => vo.OrderValidation(orderManager, world, clientId, order)))
|
||||
order.Subject.ResolveOrder(order);
|
||||
}
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
KickVoteTarget = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,15 +49,13 @@ namespace OpenRA
|
||||
assemblies = assemblyList.SelectMany(asm => asm.GetNamespaces().Select(ns => (asm, ns))).ToArray();
|
||||
}
|
||||
|
||||
static void LoadAssembly(List<Assembly> assemblyList, string resolvedPath)
|
||||
void LoadAssembly(List<Assembly> assemblyList, string resolvedPath)
|
||||
{
|
||||
// .NET doesn't provide any way of querying the metadata of an assembly without either:
|
||||
// (a) loading duplicate data into the application domain, breaking the world.
|
||||
// (b) crashing if the assembly has already been loaded.
|
||||
// We can't check the internal name of the assembly, so we'll work off the data instead
|
||||
string hash;
|
||||
using (var stream = File.OpenRead(resolvedPath))
|
||||
hash = CryptoUtil.SHA1Hash(stream);
|
||||
var hash = CryptoUtil.SHA1Hash(File.ReadAllBytes(resolvedPath));
|
||||
|
||||
if (!ResolvedAssemblies.TryGetValue(hash, out var assembly))
|
||||
{
|
||||
@@ -133,9 +131,9 @@ namespace OpenRA
|
||||
|
||||
public ConstructorInfo GetCtor(Type type)
|
||||
{
|
||||
const BindingFlags Flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;
|
||||
var ctors = type.GetConstructors(Flags).Where(x => x.HasAttribute<UseCtorAttribute>()).ToList();
|
||||
if (ctors.Count > 1)
|
||||
var flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;
|
||||
var ctors = type.GetConstructors(flags).Where(x => x.HasAttribute<UseCtorAttribute>());
|
||||
if (ctors.Count() > 1)
|
||||
throw new InvalidOperationException("ObjectCreator: UseCtor on multiple constructors; invalid.");
|
||||
return ctors.FirstOrDefault();
|
||||
}
|
||||
@@ -152,8 +150,8 @@ namespace OpenRA
|
||||
for (var i = 0; i < p.Length; i++)
|
||||
{
|
||||
var key = p[i].Name;
|
||||
if (!args.TryGetValue(key, out var arg)) throw new InvalidOperationException($"ObjectCreator: key `{key}' not found");
|
||||
a[i] = arg;
|
||||
if (!args.ContainsKey(key)) throw new InvalidOperationException($"ObjectCreator: key `{key}' not found");
|
||||
a[i] = args[key];
|
||||
}
|
||||
|
||||
return ctor.Invoke(a);
|
||||
|
||||
@@ -6,14 +6,12 @@
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="6.0.0" />
|
||||
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(MSBuildRuntimeType)'=='Mono'">
|
||||
<PackageReference Include="System.Collections.Immutable" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Linguini.Bundle" Version="0.6.0" />
|
||||
<PackageReference Include="OpenRA-Eluant" Version="1.0.22" />
|
||||
<PackageReference Include="Linguini.Bundle" Version="0.4.0" />
|
||||
<PackageReference Include="OpenRA-Eluant" Version="1.0.20" />
|
||||
<PackageReference Include="Mono.NAT" Version="3.0.4" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -79,72 +79,6 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
public static string OperatingSystem
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CurrentPlatform == PlatformType.Linux)
|
||||
{
|
||||
var desktopType = Environment.GetEnvironmentVariable("XDG_CURRENT_DESKTOP");
|
||||
var sessionType = Environment.GetEnvironmentVariable("XDG_SESSION_TYPE");
|
||||
|
||||
string suffix;
|
||||
if (!string.IsNullOrEmpty(desktopType) && !string.IsNullOrEmpty(sessionType))
|
||||
suffix = $" ({desktopType};{sessionType})";
|
||||
else if (!string.IsNullOrEmpty(desktopType))
|
||||
suffix = $" ({desktopType})";
|
||||
else if (!string.IsNullOrEmpty(sessionType))
|
||||
suffix = $" ({sessionType})";
|
||||
else
|
||||
suffix = "";
|
||||
|
||||
try
|
||||
{
|
||||
var psi = new ProcessStartInfo("hostnamectl", "status")
|
||||
{
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true
|
||||
};
|
||||
|
||||
var p = Process.Start(psi);
|
||||
string line;
|
||||
while ((line = p.StandardOutput.ReadLine()) != null)
|
||||
if (line.StartsWith("Operating System: ", StringComparison.Ordinal))
|
||||
return line[18..] + suffix;
|
||||
}
|
||||
catch { }
|
||||
|
||||
if (File.Exists("/etc/os-release"))
|
||||
foreach (var line in File.ReadLines("/etc/os-release"))
|
||||
if (line.StartsWith("PRETTY_NAME=", StringComparison.Ordinal))
|
||||
return line[13..^1] + suffix;
|
||||
}
|
||||
else if (CurrentPlatform == PlatformType.OSX)
|
||||
{
|
||||
try
|
||||
{
|
||||
var psi = new ProcessStartInfo("system_profiler", "SPSoftwareDataType")
|
||||
{
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true
|
||||
};
|
||||
|
||||
var p = Process.Start(psi);
|
||||
string line;
|
||||
while ((line = p.StandardOutput.ReadLine()) != null)
|
||||
{
|
||||
line = line.Trim();
|
||||
if (line.StartsWith("System Version: ", StringComparison.Ordinal))
|
||||
return line[16..];
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
return Environment.OSVersion.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Directory containing user-specific support files (settings, maps, replays, game data, etc).
|
||||
/// </summary>
|
||||
@@ -274,7 +208,7 @@ namespace OpenRA
|
||||
throw new DirectoryNotFoundException(path);
|
||||
|
||||
if (!path.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) &&
|
||||
!path.EndsWith(Path.AltDirectorySeparatorChar.ToString(), StringComparison.Ordinal))
|
||||
!path.EndsWith(Path.AltDirectorySeparatorChar.ToString(), StringComparison.Ordinal))
|
||||
path += Path.DirectorySeparatorChar;
|
||||
|
||||
engineDirAccessed = true;
|
||||
|
||||
@@ -133,7 +133,7 @@ namespace OpenRA
|
||||
|
||||
static FactionInfo ResolveDisplayFaction(World world, string factionName)
|
||||
{
|
||||
var factions = world.Map.Rules.Actors[SystemActors.World].TraitInfos<FactionInfo>();
|
||||
var factions = world.Map.Rules.Actors[SystemActors.World].TraitInfos<FactionInfo>().ToArray();
|
||||
|
||||
return factions.FirstOrDefault(f => f.InternalName == factionName) ?? factions.First();
|
||||
}
|
||||
@@ -216,7 +216,7 @@ namespace OpenRA
|
||||
{
|
||||
var logic = PlayerActor.TraitsImplementing<IBot>().FirstOrDefault(b => b.Info.Type == BotType);
|
||||
if (logic == null)
|
||||
Log.Write("debug", $"Invalid bot type: {BotType}");
|
||||
Log.Write("debug", "Invalid bot type: {0}", BotType);
|
||||
else
|
||||
logic.Activate(this);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Graphics;
|
||||
@@ -93,10 +94,10 @@ namespace OpenRA
|
||||
});
|
||||
}
|
||||
|
||||
var labelNode = yaml.NodeWithKeyOrDefault("Label");
|
||||
var icon24Node = yaml.NodeWithKeyOrDefault("Icon24");
|
||||
var icon48Node = yaml.NodeWithKeyOrDefault("Icon48");
|
||||
var icon72Node = yaml.NodeWithKeyOrDefault("Icon72");
|
||||
var labelNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Label");
|
||||
var icon24Node = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon24");
|
||||
var icon48Node = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon48");
|
||||
var icon72Node = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon72");
|
||||
if (labelNode == null)
|
||||
return null;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
@@ -30,7 +31,7 @@ namespace OpenRA
|
||||
{
|
||||
var badges = new List<PlayerBadge>();
|
||||
|
||||
var badgesNode = yaml.NodeWithKeyOrDefault("Badges");
|
||||
var badgesNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Badges");
|
||||
if (badgesNode != null)
|
||||
{
|
||||
var playerDatabase = Game.ModData.Manifest.Get<PlayerDatabase>();
|
||||
|
||||
@@ -56,27 +56,17 @@ namespace OpenRA.Primitives
|
||||
int Index(DelayedAction action)
|
||||
{
|
||||
// Returns the index of the next action with a strictly greater time.
|
||||
var index = actions.BinarySearch(action, DelayedAction.TimeComparer);
|
||||
var index = actions.BinarySearch(action);
|
||||
if (index < 0)
|
||||
return ~index;
|
||||
while (index < actions.Count && DelayedAction.TimeComparer.Compare(action, actions[index]) >= 0)
|
||||
while (index < actions.Count && action.CompareTo(actions[index]) >= 0)
|
||||
index++;
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
readonly struct DelayedAction
|
||||
readonly struct DelayedAction : IComparable<DelayedAction>
|
||||
{
|
||||
sealed class DelayedActionTimeComparer : IComparer<DelayedAction>
|
||||
{
|
||||
public int Compare(DelayedAction x, DelayedAction y)
|
||||
{
|
||||
return x.Time.CompareTo(y.Time);
|
||||
}
|
||||
}
|
||||
|
||||
public static IComparer<DelayedAction> TimeComparer = new DelayedActionTimeComparer();
|
||||
|
||||
public readonly long Time;
|
||||
public readonly Action Action;
|
||||
|
||||
@@ -86,6 +76,11 @@ namespace OpenRA.Primitives
|
||||
Time = time;
|
||||
}
|
||||
|
||||
public int CompareTo(DelayedAction other)
|
||||
{
|
||||
return Time.CompareTo(other.Time);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Time: " + Time + " Action: " + Action;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user