Compare commits
72 Commits
log_chat_s
...
log_chat_s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50277c59d7 | ||
|
|
2a13752203 | ||
|
|
8ce3ea5f2c | ||
|
|
b4f3d8ae02 | ||
|
|
d70c30763b | ||
|
|
27297a1ba9 | ||
|
|
2890ccf2b1 | ||
|
|
f9f1889112 | ||
|
|
d9c5821329 | ||
|
|
7fc415de19 | ||
|
|
ec01f63317 | ||
|
|
c9ac702217 | ||
|
|
1913c608a6 | ||
|
|
94ac6cc400 | ||
|
|
97d9520bf0 | ||
|
|
e7300cc430 | ||
|
|
677d7e47a4 | ||
|
|
5f287d38ab | ||
|
|
3a00d5a679 | ||
|
|
d5d43ee97c | ||
|
|
fd12ef1535 | ||
|
|
de8317d9b8 | ||
|
|
b953f4a40d | ||
|
|
b565f0903a | ||
|
|
da71a8271d | ||
|
|
54558169e1 | ||
|
|
bcb953309a | ||
|
|
a371826613 | ||
|
|
82ee3d779c | ||
|
|
4ea0e70589 | ||
|
|
3f4e4db836 | ||
|
|
85020bc2af | ||
|
|
70bb01a2c7 | ||
|
|
38d4719073 | ||
|
|
aebb97967d | ||
|
|
97f81d0aa4 | ||
|
|
2030c17a8b | ||
|
|
b6dcd8d9af | ||
|
|
9a78f22e86 | ||
|
|
19ca3d610b | ||
|
|
dd74d3572a | ||
|
|
ad7e5d62cd | ||
|
|
ece7547695 | ||
|
|
944bc7667a | ||
|
|
0ac28e1f5f | ||
|
|
ca3a57289e | ||
|
|
a4641be7e7 | ||
|
|
0651308210 | ||
|
|
cac6aebb6c | ||
|
|
0baddf46e2 | ||
|
|
e0d893b736 | ||
|
|
2ccfa425b2 | ||
|
|
52102da839 | ||
|
|
fc08f18663 | ||
|
|
cc064d0dad | ||
|
|
8b997a5656 | ||
|
|
7d8fff535d | ||
|
|
0855a9e5bf | ||
|
|
3fa2b9fe79 | ||
|
|
c561509c82 | ||
|
|
0566b6f787 | ||
|
|
e2821933a4 | ||
|
|
34c29f872a | ||
|
|
2192a6b4f9 | ||
|
|
68ad9163ff | ||
|
|
5f6eecc1f6 | ||
|
|
5edca87622 | ||
|
|
fa01c24f23 | ||
|
|
593c2735ce | ||
|
|
48e72c62f3 | ||
|
|
f316d4c757 | ||
|
|
d6dba3e545 |
159
.editorconfig
159
.editorconfig
@@ -63,7 +63,7 @@ dotnet_diagnostic.IDE0161.severity = warning
|
|||||||
|
|
||||||
# IDE0200 Remove unnecessary lambda expression
|
# IDE0200 Remove unnecessary lambda expression
|
||||||
#csharp_style_prefer_method_group_conversion = true
|
#csharp_style_prefer_method_group_conversion = true
|
||||||
dotnet_diagnostic.IDE0200.severity = warning
|
dotnet_diagnostic.IDE0200.severity = silent # Requires C# 11
|
||||||
|
|
||||||
# IDE0210 Convert to top-level statements/IDE0211 Convert to 'Program.Main' style program
|
# IDE0210 Convert to top-level statements/IDE0211 Convert to 'Program.Main' style program
|
||||||
csharp_style_prefer_top_level_statements = false
|
csharp_style_prefer_top_level_statements = false
|
||||||
@@ -72,7 +72,7 @@ dotnet_diagnostic.IDE0211.severity = warning
|
|||||||
|
|
||||||
# IDE0290 Use primary constructor
|
# IDE0290 Use primary constructor
|
||||||
#csharp_style_prefer_primary_constructors = true
|
#csharp_style_prefer_primary_constructors = true
|
||||||
dotnet_diagnostic.IDE0290.severity = silent
|
dotnet_diagnostic.IDE0200.severity = silent # Requires C# 12
|
||||||
|
|
||||||
# IDE0330 Prefer 'System.Threading.Lock'
|
# IDE0330 Prefer 'System.Threading.Lock'
|
||||||
#csharp_prefer_system_threading_lock = true
|
#csharp_prefer_system_threading_lock = true
|
||||||
@@ -229,10 +229,6 @@ dotnet_diagnostic.IDE0100.severity = warning
|
|||||||
# No options
|
# No options
|
||||||
dotnet_diagnostic.IDE0120.severity = warning
|
dotnet_diagnostic.IDE0120.severity = warning
|
||||||
|
|
||||||
# IDE0121 Simplify LINQ type check and cast
|
|
||||||
# No options
|
|
||||||
dotnet_diagnostic.IDE0121.severity = warning
|
|
||||||
|
|
||||||
# IDE0130 Namespace does not match folder structure
|
# IDE0130 Namespace does not match folder structure
|
||||||
#dotnet_style_namespace_match_folder = true
|
#dotnet_style_namespace_match_folder = true
|
||||||
# This rule doesn't appear to work (never reports violations)
|
# This rule doesn't appear to work (never reports violations)
|
||||||
@@ -296,7 +292,7 @@ dotnet_diagnostic.IDE0220.severity = warning
|
|||||||
|
|
||||||
# IDE0230 Use UTF-8 string literal
|
# IDE0230 Use UTF-8 string literal
|
||||||
#csharp_style_prefer_utf8_string_literals = true
|
#csharp_style_prefer_utf8_string_literals = true
|
||||||
dotnet_diagnostic.IDE0230.severity = warning
|
dotnet_diagnostic.IDE0230.severity = silent # Requires C# 11
|
||||||
|
|
||||||
# IDE0240 Nullable directive is redundant
|
# IDE0240 Nullable directive is redundant
|
||||||
# No options
|
# No options
|
||||||
@@ -312,43 +308,27 @@ dotnet_diagnostic.IDE0241.severity = warning
|
|||||||
|
|
||||||
# IDE0300 Use collection expression for array
|
# IDE0300 Use collection expression for array
|
||||||
# From above, uses dotnet_style_prefer_collection_expression
|
# From above, uses dotnet_style_prefer_collection_expression
|
||||||
dotnet_diagnostic.IDE0300.severity = warning
|
dotnet_diagnostic.IDE0300.severity = silent # Requires C# 12
|
||||||
|
|
||||||
# IDE0301 Use collection expression for empty
|
# IDE0301 Use collection expression for empty
|
||||||
# From above, uses dotnet_style_prefer_collection_expression
|
# From above, uses dotnet_style_prefer_collection_expression
|
||||||
dotnet_diagnostic.IDE0301.severity = warning
|
dotnet_diagnostic.IDE0301.severity = silent # Requires C# 12
|
||||||
|
|
||||||
# IDE0302 Use collection expression for stackalloc
|
# IDE0302 Use collection expression for stackalloc
|
||||||
# From above, uses dotnet_style_prefer_collection_expression
|
# From above, uses dotnet_style_prefer_collection_expression
|
||||||
dotnet_diagnostic.IDE0302.severity = warning
|
dotnet_diagnostic.IDE0302.severity = silent # Requires C# 12
|
||||||
|
|
||||||
# IDE0303 Use collection expression for 'Create()'
|
# IDE0303 Use collection expression for 'Create()'
|
||||||
# From above, uses dotnet_style_prefer_collection_expression
|
# From above, uses dotnet_style_prefer_collection_expression
|
||||||
dotnet_diagnostic.IDE0303.severity = warning
|
dotnet_diagnostic.IDE0303.severity = silent # Requires C# 12
|
||||||
|
|
||||||
# IDE0304 Use collection expression for builder
|
# IDE0304 Use collection expression for builder
|
||||||
# From above, uses dotnet_style_prefer_collection_expression
|
# From above, uses dotnet_style_prefer_collection_expression
|
||||||
dotnet_diagnostic.IDE0304.severity = warning
|
dotnet_diagnostic.IDE0304.severity = silent # Requires C# 12
|
||||||
|
|
||||||
# IDE0305 Use collection expression for fluent
|
# IDE0305 Use collection expression for fluent
|
||||||
# From above, uses dotnet_style_prefer_collection_expression
|
# From above, uses dotnet_style_prefer_collection_expression
|
||||||
dotnet_diagnostic.IDE0305.severity = silent
|
dotnet_diagnostic.IDE0305.severity = silent # Requires C# 12
|
||||||
|
|
||||||
# IDE0306 Use collection expression for new
|
|
||||||
# From above, uses dotnet_style_prefer_collection_expression
|
|
||||||
dotnet_diagnostic.IDE0306.severity = warning
|
|
||||||
|
|
||||||
# IDE0340 Use unbound generic type
|
|
||||||
#csharp_style_prefer_unbound_generic_type_in_nameof = true
|
|
||||||
dotnet_diagnostic.IDE0340.severity = silent # Requires C# 14
|
|
||||||
|
|
||||||
# IDE0350 Use implicitly typed lambda
|
|
||||||
#csharp_style_prefer_implicitly_typed_lambda_expression = true
|
|
||||||
dotnet_diagnostic.IDE0350.severity = warning
|
|
||||||
|
|
||||||
# IDE0360 Simplify property accessor
|
|
||||||
#csharp_style_prefer_simple_property_accessors = true
|
|
||||||
dotnet_diagnostic.IDE0360.severity = silent # Requires C# 13
|
|
||||||
|
|
||||||
## Field preferences
|
## Field preferences
|
||||||
|
|
||||||
@@ -393,10 +373,6 @@ dotnet_diagnostic.IDE0251.severity = warning
|
|||||||
#csharp_prefer_static_anonymous_function = true
|
#csharp_prefer_static_anonymous_function = true
|
||||||
dotnet_diagnostic.IDE0320.severity = warning
|
dotnet_diagnostic.IDE0320.severity = warning
|
||||||
|
|
||||||
# IDE0380 Remove unnecessary 'unsafe' modifier
|
|
||||||
# No options
|
|
||||||
dotnet_diagnostic.IDE0380.severity = warning
|
|
||||||
|
|
||||||
## Null-checking preferences
|
## Null-checking preferences
|
||||||
|
|
||||||
# IDE1005 Use conditional delegate call
|
# IDE1005 Use conditional delegate call
|
||||||
@@ -411,7 +387,7 @@ dotnet_diagnostic.IDE0060.severity = warning
|
|||||||
|
|
||||||
# IDE0280 Use 'nameof'
|
# IDE0280 Use 'nameof'
|
||||||
# No options
|
# No options
|
||||||
dotnet_diagnostic.IDE0280.severity = warning
|
dotnet_diagnostic.IDE0280.severity = silent # Requires C# 11
|
||||||
|
|
||||||
## Parentheses preferences
|
## Parentheses preferences
|
||||||
|
|
||||||
@@ -450,7 +426,7 @@ dotnet_diagnostic.IDE0083.severity = warning
|
|||||||
|
|
||||||
# IDE0170 Simplify property pattern
|
# IDE0170 Simplify property pattern
|
||||||
#csharp_style_prefer_extended_property_pattern = true
|
#csharp_style_prefer_extended_property_pattern = true
|
||||||
dotnet_diagnostic.IDE0170.severity = warning
|
dotnet_diagnostic.IDE0170.severity = silent # Requires C# 10
|
||||||
|
|
||||||
## Suppression preferences
|
## Suppression preferences
|
||||||
|
|
||||||
@@ -458,10 +434,6 @@ dotnet_diagnostic.IDE0170.severity = warning
|
|||||||
#dotnet_remove_unnecessary_suppression_exclusions = none
|
#dotnet_remove_unnecessary_suppression_exclusions = none
|
||||||
dotnet_diagnostic.IDE0079.severity = warning
|
dotnet_diagnostic.IDE0079.severity = warning
|
||||||
|
|
||||||
# IDE0370 Remove unnecessary suppression (null-forgiving operator)
|
|
||||||
# No options
|
|
||||||
dotnet_diagnostic.IDE0370.severity = warning
|
|
||||||
|
|
||||||
## 'this' and 'Me' preferences
|
## 'this' and 'Me' preferences
|
||||||
|
|
||||||
# IDE0003/IDE0009 Remove 'this' or 'Me' qualification/Add 'this' or 'Me' qualification
|
# IDE0003/IDE0009 Remove 'this' or 'Me' qualification/Add 'this' or 'Me' qualification
|
||||||
@@ -776,7 +748,7 @@ dotnet_diagnostic.CA1305.severity = warning
|
|||||||
dotnet_diagnostic.CA1310.severity = warning
|
dotnet_diagnostic.CA1310.severity = warning
|
||||||
|
|
||||||
# Specify a culture or use an invariant version.
|
# Specify a culture or use an invariant version.
|
||||||
dotnet_diagnostic.CA1311.severity = warning
|
dotnet_diagnostic.CA1311.severity = suggestion # TODO: Change to warning once using .NET 7 or later.
|
||||||
|
|
||||||
### Portability and Interoperability Rules
|
### Portability and Interoperability Rules
|
||||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/interoperability-warnings
|
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/interoperability-warnings
|
||||||
@@ -791,25 +763,19 @@ dotnet_diagnostic.CA1417.severity = warning
|
|||||||
dotnet_diagnostic.CA1507.severity = warning
|
dotnet_diagnostic.CA1507.severity = warning
|
||||||
|
|
||||||
# Use ArgumentNullException throw helper.
|
# Use ArgumentNullException throw helper.
|
||||||
dotnet_diagnostic.CA1510.severity = warning
|
dotnet_diagnostic.CA1510.severity = suggestion # TODO: Change to warning once using .NET 7 or later.
|
||||||
|
|
||||||
# Use ArgumentException throw helper.
|
# Use ArgumentException throw helper.
|
||||||
dotnet_diagnostic.CA1511.severity = warning
|
dotnet_diagnostic.CA1511.severity = suggestion # TODO: Change to warning once using .NET 7 or later.
|
||||||
|
|
||||||
# Use ArgumentOutOfRangeException throw helper.
|
# Use ArgumentOutOfRangeException throw helper.
|
||||||
dotnet_diagnostic.CA1512.severity = warning
|
dotnet_diagnostic.CA1512.severity = suggestion # TODO: Change to warning once using .NET 8 or later.
|
||||||
|
|
||||||
# Use ObjectDisposedException throw helper.
|
# Use ObjectDisposedException throw helper.
|
||||||
dotnet_diagnostic.CA1513.severity = warning
|
dotnet_diagnostic.CA1513.severity = suggestion # TODO: Change to warning once using .NET 7 or later.
|
||||||
|
|
||||||
# Avoid redundant length argument.
|
# Avoid redundant length argument.
|
||||||
dotnet_diagnostic.CA1514.severity = warning
|
dotnet_diagnostic.CA1514.severity = suggestion # TODO: Change to warning once using .NET 8 or later.
|
||||||
|
|
||||||
# Consider making public types internal.
|
|
||||||
dotnet_diagnostic.CA1515.severity = warning
|
|
||||||
|
|
||||||
# Use cross-platform intrinsics.
|
|
||||||
dotnet_diagnostic.CA1516.severity = warning
|
|
||||||
|
|
||||||
### Naming Rules
|
### Naming Rules
|
||||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/naming-warnings
|
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/naming-warnings
|
||||||
@@ -902,8 +868,8 @@ dotnet_diagnostic.CA1843.severity = warning
|
|||||||
# Provide memory-based overrides of async methods when subclassing 'Stream'.
|
# Provide memory-based overrides of async methods when subclassing 'Stream'.
|
||||||
dotnet_diagnostic.CA1844.severity = warning
|
dotnet_diagnostic.CA1844.severity = warning
|
||||||
|
|
||||||
# Use span-based 'string.Concat'.
|
# Use span-based 'string.Concat'. (Not available on mono)
|
||||||
dotnet_diagnostic.CA1845.severity = warning
|
dotnet_diagnostic.CA1845.severity = none
|
||||||
|
|
||||||
# Prefer AsSpan over Substring.
|
# Prefer AsSpan over Substring.
|
||||||
dotnet_diagnostic.CA1846.severity = warning
|
dotnet_diagnostic.CA1846.severity = warning
|
||||||
@@ -912,34 +878,34 @@ dotnet_diagnostic.CA1846.severity = warning
|
|||||||
dotnet_diagnostic.CA1847.severity = warning
|
dotnet_diagnostic.CA1847.severity = warning
|
||||||
|
|
||||||
# Call async methods when in an async method.
|
# Call async methods when in an async method.
|
||||||
dotnet_diagnostic.CA1849.severity = warning
|
dotnet_diagnostic.CA1849.severity = suggestion # TODO: Change to warning once using .NET 7 or later.
|
||||||
|
|
||||||
# Prefer static HashData method over ComputeHash.
|
# Prefer static HashData method over ComputeHash. (Not available on mono)
|
||||||
dotnet_diagnostic.CA1850.severity = warning
|
dotnet_diagnostic.CA1850.severity = none # TODO: Change to warning once using .NET 7 or later AND once supported by mono.
|
||||||
|
|
||||||
# Possible multiple enumerations of IEnumerable collection.
|
# Possible multiple enumerations of IEnumerable collection.
|
||||||
#dotnet_code_quality.CA1851.enumeration_methods =
|
#dotnet_code_quality.CA1851.enumeration_methods =
|
||||||
dotnet_code_quality.CA1851.linq_chain_methods = M:OpenRA.Traits.IRenderModifier.Modify*
|
dotnet_code_quality.CA1851.linq_chain_methods = M:OpenRA.Traits.IRenderModifier.Modify*
|
||||||
dotnet_code_quality.CA1851.assume_method_enumerates_parameters = true
|
dotnet_code_quality.CA1851.assume_method_enumerates_parameters = true
|
||||||
dotnet_diagnostic.CA1851.severity = warning
|
dotnet_diagnostic.CA1851.severity = suggestion # TODO: Change to warning once using .NET 7 or later.
|
||||||
|
|
||||||
# Seal internal types.
|
# Seal internal types.
|
||||||
dotnet_diagnostic.CA1852.severity = warning
|
dotnet_diagnostic.CA1852.severity = suggestion # TODO: Change to warning once using .NET 7 or later.
|
||||||
|
|
||||||
# Unnecessary call to 'Dictionary.ContainsKey(key)'.
|
# Unnecessary call to 'Dictionary.ContainsKey(key)'.
|
||||||
dotnet_diagnostic.CA1853.severity = warning
|
dotnet_diagnostic.CA1853.severity = suggestion # TODO: Change to warning once using .NET 7 or later.
|
||||||
|
|
||||||
# Prefer the IDictionary.TryGetValue(TKey, out TValue) method.
|
# Prefer the IDictionary.TryGetValue(TKey, out TValue) method.
|
||||||
dotnet_diagnostic.CA1854.severity = warning
|
dotnet_diagnostic.CA1854.severity = suggestion # TODO: Change to warning once using .NET 7 or later.
|
||||||
|
|
||||||
# Use Span<T>.Clear() instead of Span<T>.Fill().
|
# Use Span<T>.Clear() instead of Span<T>.Fill().
|
||||||
dotnet_diagnostic.CA1855.severity = warning
|
dotnet_diagnostic.CA1855.severity = suggestion # TODO: Change to warning once using .NET 7 or later.
|
||||||
|
|
||||||
# Incorrect usage of ConstantExpected attribute.
|
# Incorrect usage of ConstantExpected attribute.
|
||||||
dotnet_diagnostic.CA1856.severity = warning
|
dotnet_diagnostic.CA1856.severity = suggestion # TODO: Change to warning once using .NET 7 or later.
|
||||||
|
|
||||||
# The parameter expects a constant for optimal performance.
|
# The parameter expects a constant for optimal performance.
|
||||||
dotnet_diagnostic.CA1857.severity = warning
|
dotnet_diagnostic.CA1857.severity = suggestion # TODO: Change to warning once using .NET 7 or later.
|
||||||
|
|
||||||
# Use StartsWith instead of IndexOf.
|
# Use StartsWith instead of IndexOf.
|
||||||
dotnet_diagnostic.CA1858.severity = warning
|
dotnet_diagnostic.CA1858.severity = warning
|
||||||
@@ -951,24 +917,24 @@ dotnet_diagnostic.CA1860.severity = warning
|
|||||||
dotnet_diagnostic.CA1862.severity = warning
|
dotnet_diagnostic.CA1862.severity = warning
|
||||||
|
|
||||||
# Use 'CompositeFormat'.
|
# Use 'CompositeFormat'.
|
||||||
dotnet_diagnostic.CA1863.severity = warning
|
dotnet_diagnostic.CA1863.severity = suggestion # TODO: Change to warning once using .NET 7 or later.
|
||||||
|
|
||||||
# Prefer the 'IDictionary.TryAdd(TKey, TValue)' method.
|
# Prefer the 'IDictionary.TryAdd(TKey, TValue)' method.
|
||||||
dotnet_diagnostic.CA1864.severity = warning
|
dotnet_diagnostic.CA1864.severity = suggestion # TODO: Change to warning once using .NET 8 or later.
|
||||||
|
|
||||||
# Use 'string.Method(char)' instead of 'string.Method(string)' for string with single char.
|
# Use 'string.Method(char)' instead of 'string.Method(string)' for string with single char.
|
||||||
dotnet_diagnostic.CA1865.severity = warning
|
dotnet_diagnostic.CA1865.severity = suggestion # TODO: Change to warning once using .NET 8 or later.
|
||||||
dotnet_diagnostic.CA1866.severity = warning
|
dotnet_diagnostic.CA1866.severity = suggestion # TODO: Change to warning once using .NET 8 or later.
|
||||||
dotnet_diagnostic.CA1867.severity = warning
|
dotnet_diagnostic.CA1867.severity = suggestion # TODO: Change to warning once using .NET 8 or later.
|
||||||
|
|
||||||
# Unnecessary call to 'Contains' for sets.
|
# Unnecessary call to 'Contains' for sets.
|
||||||
dotnet_diagnostic.CA1868.severity = warning
|
dotnet_diagnostic.CA1868.severity = suggestion # TODO: Change to warning once using .NET 8 or later.
|
||||||
|
|
||||||
# Cache and reuse 'JsonSerializerOptions' instances.
|
# Cache and reuse 'JsonSerializerOptions' instances.
|
||||||
dotnet_diagnostic.CA1869.severity = warning
|
dotnet_diagnostic.CA1869.severity = suggestion # TODO: Change to warning once using .NET 8 or later.
|
||||||
|
|
||||||
# Use a cached 'SearchValues' instance.
|
# Use a cached 'SearchValues' instance.
|
||||||
dotnet_diagnostic.CA1870.severity = warning
|
dotnet_diagnostic.CA1870.severity = suggestion # TODO: Change to warning once using .NET 8 or later.
|
||||||
|
|
||||||
# Do not pass a nullable struct to 'ArgumentNullException.ThrowIfNull'.
|
# Do not pass a nullable struct to 'ArgumentNullException.ThrowIfNull'.
|
||||||
dotnet_diagnostic.CA1871.severity = warning
|
dotnet_diagnostic.CA1871.severity = warning
|
||||||
@@ -976,18 +942,6 @@ dotnet_diagnostic.CA1871.severity = warning
|
|||||||
# Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString'.
|
# Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString'.
|
||||||
dotnet_diagnostic.CA1872.severity = warning
|
dotnet_diagnostic.CA1872.severity = warning
|
||||||
|
|
||||||
# Avoid potentially expensive logging.
|
|
||||||
dotnet_diagnostic.CA1873.severity = warning
|
|
||||||
|
|
||||||
# Use 'Regex.IsMatch'.
|
|
||||||
dotnet_diagnostic.CA1874.severity = warning
|
|
||||||
|
|
||||||
# Use Use 'Regex.Count'.
|
|
||||||
dotnet_diagnostic.CA1875.severity = warning
|
|
||||||
|
|
||||||
# Use 'Path.Combine' or 'Path.Join' overloads.
|
|
||||||
dotnet_diagnostic.CA1877.severity = warning
|
|
||||||
|
|
||||||
### Reliability Rules
|
### Reliability Rules
|
||||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/reliability-warnings
|
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/reliability-warnings
|
||||||
|
|
||||||
@@ -1010,7 +964,7 @@ dotnet_diagnostic.CA2016.severity = warning
|
|||||||
dotnet_diagnostic.CA2018.severity = warning
|
dotnet_diagnostic.CA2018.severity = warning
|
||||||
|
|
||||||
# ThreadStatic fields should not use inline initialization.
|
# ThreadStatic fields should not use inline initialization.
|
||||||
dotnet_diagnostic.CA2019.severity = warning
|
dotnet_diagnostic.CA2019.severity = suggestion # TODO: Change to warning once using .NET 7 or later.
|
||||||
|
|
||||||
# Don't call Enumerable.Cast<T> or Enumerable.OfType<T> with incompatible types.
|
# Don't call Enumerable.Cast<T> or Enumerable.OfType<T> with incompatible types.
|
||||||
dotnet_diagnostic.CA2021.severity = warning
|
dotnet_diagnostic.CA2021.severity = warning
|
||||||
@@ -1018,18 +972,6 @@ dotnet_diagnostic.CA2021.severity = warning
|
|||||||
# Avoid inexact read with Stream.Read.
|
# Avoid inexact read with Stream.Read.
|
||||||
dotnet_diagnostic.CA2022.severity = warning
|
dotnet_diagnostic.CA2022.severity = warning
|
||||||
|
|
||||||
# Invalid braces in message template.
|
|
||||||
dotnet_diagnostic.CA2023.severity = warning
|
|
||||||
|
|
||||||
# Do not use StreamReader.EndOfStream in async methods.
|
|
||||||
dotnet_diagnostic.CA2024.severity = warning
|
|
||||||
|
|
||||||
# Do not pass 'IDisposable' instances into unawaited tasks.
|
|
||||||
dotnet_diagnostic.CA2025.severity = warning
|
|
||||||
|
|
||||||
# Prefer JsonElement.Parse over JsonDocument.Parse().RootElement.
|
|
||||||
dotnet_diagnostic.CA2026.severity = warning
|
|
||||||
|
|
||||||
### Security Rules
|
### Security Rules
|
||||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/security-warnings
|
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/security-warnings
|
||||||
|
|
||||||
@@ -1099,10 +1041,10 @@ dotnet_diagnostic.CA2250.severity = warning
|
|||||||
dotnet_diagnostic.CA2251.severity = warning
|
dotnet_diagnostic.CA2251.severity = warning
|
||||||
|
|
||||||
# Ensure ThreadStatic is only used with static fields.
|
# Ensure ThreadStatic is only used with static fields.
|
||||||
dotnet_diagnostic.CA2259.severity = warning
|
dotnet_diagnostic.CA2259.severity = suggestion # TODO: Change to warning once using .NET 7 or later.
|
||||||
|
|
||||||
# Prefer generic overload when type is known.
|
# Prefer generic overload when type is known.
|
||||||
dotnet_diagnostic.CA2263.severity = warning
|
dotnet_diagnostic.CA2263.severity = none # TODO: Change to warning once mono is dropped.
|
||||||
|
|
||||||
# Do not pass a non-nullable value to 'ArgumentNullException.ThrowIfNull'.
|
# Do not pass a non-nullable value to 'ArgumentNullException.ThrowIfNull'.
|
||||||
dotnet_diagnostic.CA2264.severity = warning
|
dotnet_diagnostic.CA2264.severity = warning
|
||||||
@@ -1318,24 +1260,3 @@ dotnet_diagnostic.RCS1239.severity = warning
|
|||||||
|
|
||||||
# Use element access.
|
# Use element access.
|
||||||
dotnet_diagnostic.RCS1246.severity = warning
|
dotnet_diagnostic.RCS1246.severity = warning
|
||||||
|
|
||||||
# Use enum field explicitly.
|
|
||||||
dotnet_diagnostic.RCS1257.severity = warning
|
|
||||||
|
|
||||||
# Unnecessary enum flag.
|
|
||||||
dotnet_diagnostic.RCS1258.severity = warning
|
|
||||||
|
|
||||||
# Resource can be disposed asynchronously.
|
|
||||||
dotnet_diagnostic.RCS1261.severity = warning
|
|
||||||
|
|
||||||
# Unnecessary raw string literal.
|
|
||||||
dotnet_diagnostic.RCS1262.severity = warning
|
|
||||||
|
|
||||||
# Remove redundant catch block.
|
|
||||||
dotnet_diagnostic.RCS1265.severity = warning
|
|
||||||
|
|
||||||
# Use raw string literal.
|
|
||||||
dotnet_diagnostic.RCS1266.severity = warning
|
|
||||||
|
|
||||||
# Use string interpolation instead of 'string.Concat'.
|
|
||||||
dotnet_diagnostic.RCS1267.severity = warning
|
|
||||||
|
|||||||
1
.github/ISSUE_TEMPLATE/crash-report.md
vendored
1
.github/ISSUE_TEMPLATE/crash-report.md
vendored
@@ -16,6 +16,7 @@ assignees: ''
|
|||||||
- OpenRA Version: ENGINE VERSION
|
- OpenRA Version: ENGINE VERSION
|
||||||
- OpenRA Mod: GAME MOD
|
- OpenRA Mod: GAME MOD
|
||||||
- Source: Official download package OR self-compiled OR third-party package
|
- Source: Official download package OR self-compiled OR third-party package
|
||||||
|
- For self-compiled or third-party packages: Mono version
|
||||||
|
|
||||||
|
|
||||||
## Exception log
|
## Exception log
|
||||||
|
|||||||
6
.github/dependabot.yml
vendored
6
.github/dependabot.yml
vendored
@@ -1,6 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: github-actions
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: daily
|
|
||||||
54
.github/workflows/ci.yml
vendored
54
.github/workflows/ci.yml
vendored
@@ -2,30 +2,28 @@ name: Continuous Integration
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
paths-ignore:
|
|
||||||
- '*.md'
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ bleed, 'prep-*' ]
|
branches: [ bleed, 'prep-*' ]
|
||||||
paths-ignore:
|
|
||||||
- '*.md'
|
|
||||||
- '*.py'
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read # to fetch code (actions/checkout)
|
contents: read # to fetch code (actions/checkout)
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
linux:
|
linux:
|
||||||
name: Linux (.NET 8.0)
|
name: Linux (.NET 6.0)
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone Repository
|
- name: Remove System .NET
|
||||||
uses: actions/checkout@v6
|
run: sudo apt-get remove -y dotnet*
|
||||||
|
|
||||||
- name: Install .NET 8.0
|
- name: Clone Repository
|
||||||
uses: actions/setup-dotnet@v5
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install .NET 6.0
|
||||||
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: '8.0.x'
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
- name: Check Code
|
- name: Check Code
|
||||||
run: |
|
run: |
|
||||||
@@ -38,18 +36,36 @@ jobs:
|
|||||||
make check-scripts
|
make check-scripts
|
||||||
make TREAT_WARNINGS_AS_ERRORS=true test
|
make TREAT_WARNINGS_AS_ERRORS=true test
|
||||||
|
|
||||||
windows:
|
linux-mono:
|
||||||
name: Windows (.NET 8.0)
|
name: Linux (mono)
|
||||||
runs-on: windows-2022
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone Repository
|
- name: Clone Repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install .NET 8.0
|
- name: Check Code
|
||||||
uses: actions/setup-dotnet@v5
|
run: |
|
||||||
|
mono --version
|
||||||
|
make RUNTIME=mono check
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
windows:
|
||||||
|
name: Windows (.NET 6.0)
|
||||||
|
runs-on: windows-2019
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Clone Repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install .NET 6.0
|
||||||
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: '8.0.x'
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
- name: Check Code
|
- name: Check Code
|
||||||
shell: powershell
|
shell: powershell
|
||||||
@@ -61,7 +77,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Check Mods
|
- name: Check Mods
|
||||||
run: |
|
run: |
|
||||||
choco install lua --version 5.1.5.52 --no-progress
|
choco install lua --version 5.1.5.52
|
||||||
$ENV:Path = $ENV:Path + ";C:\Program Files (x86)\Lua\5.1\"
|
$ENV:Path = $ENV:Path + ";C:\Program Files (x86)\Lua\5.1\"
|
||||||
$ENV:TREAT_WARNINGS_AS_ERRORS = "true"
|
$ENV:TREAT_WARNINGS_AS_ERRORS = "true"
|
||||||
.\make.ps1 check-scripts
|
.\make.ps1 check-scripts
|
||||||
|
|||||||
20
.github/workflows/documentation.yml
vendored
20
.github/workflows/documentation.yml
vendored
@@ -52,21 +52,21 @@ jobs:
|
|||||||
echo ${{ needs.prepare.outputs.version_type }}
|
echo ${{ needs.prepare.outputs.version_type }}
|
||||||
|
|
||||||
- name: Clone Repository
|
- name: Clone Repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ needs.prepare.outputs.git_tag }}
|
ref: ${{ needs.prepare.outputs.git_tag }}
|
||||||
|
|
||||||
- name: Install .NET 8
|
- name: Install .NET 6
|
||||||
uses: actions/setup-dotnet@v5
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: '8.0.x'
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
- name: Prepare Environment
|
- name: Prepare Environment
|
||||||
run: |
|
run: |
|
||||||
make all
|
make all
|
||||||
|
|
||||||
- name: Clone Wiki
|
- name: Clone Wiki
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: openra/openra.wiki
|
repository: openra/openra.wiki
|
||||||
token: ${{ secrets.DOCS_TOKEN }}
|
token: ${{ secrets.DOCS_TOKEN }}
|
||||||
@@ -107,14 +107,14 @@ jobs:
|
|||||||
echo ${{ needs.prepare.outputs.version_type }}
|
echo ${{ needs.prepare.outputs.version_type }}
|
||||||
|
|
||||||
- name: Clone Repository
|
- name: Clone Repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ needs.prepare.outputs.git_tag }}
|
ref: ${{ needs.prepare.outputs.git_tag }}
|
||||||
|
|
||||||
- name: Install .NET 8
|
- name: Install .NET 6
|
||||||
uses: actions/setup-dotnet@v5
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: '8.0.x'
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
- name: Prepare Environment
|
- name: Prepare Environment
|
||||||
run: |
|
run: |
|
||||||
@@ -122,7 +122,7 @@ jobs:
|
|||||||
|
|
||||||
# version_type is release/playtest/bleed - the name of the target branch.
|
# version_type is release/playtest/bleed - the name of the target branch.
|
||||||
- name: Clone docs.openra.net
|
- name: Clone docs.openra.net
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: openra/docs
|
repository: openra/docs
|
||||||
token: ${{ secrets.DOCS_TOKEN }}
|
token: ${{ secrets.DOCS_TOKEN }}
|
||||||
|
|||||||
58
.github/workflows/packaging.yml
vendored
58
.github/workflows/packaging.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Clone Repository
|
- name: Clone Repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Prepare Environment
|
- name: Prepare Environment
|
||||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||||
@@ -40,12 +40,12 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Clone Repository
|
- name: Clone Repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install .NET 8.0
|
- name: Install .NET 6.0
|
||||||
uses: actions/setup-dotnet@v5
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: '8.0.x'
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
- name: Prepare Environment
|
- name: Prepare Environment
|
||||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||||
@@ -65,15 +65,15 @@ jobs:
|
|||||||
|
|
||||||
macos:
|
macos:
|
||||||
name: macOS Disk Image
|
name: macOS Disk Image
|
||||||
runs-on: macos-14
|
runs-on: macos-13
|
||||||
steps:
|
steps:
|
||||||
- name: Clone Repository
|
- name: Clone Repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install .NET 8.0
|
- name: Install .NET 6.0
|
||||||
uses: actions/setup-dotnet@v5
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: '8.0.x'
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
- name: Prepare Environment
|
- name: Prepare Environment
|
||||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||||
@@ -101,12 +101,12 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Clone Repository
|
- name: Clone Repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install .NET 8.0
|
- name: Install .NET 6.0
|
||||||
uses: actions/setup-dotnet@v5
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: '8.0.x'
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
- name: Prepare Environment
|
- name: Prepare Environment
|
||||||
run: |
|
run: |
|
||||||
@@ -119,37 +119,9 @@ jobs:
|
|||||||
mkdir -p build/windows
|
mkdir -p build/windows
|
||||||
./packaging/windows/buildpackage.sh "${GIT_TAG}" "${PWD}/build/windows"
|
./packaging/windows/buildpackage.sh "${GIT_TAG}" "${PWD}/build/windows"
|
||||||
|
|
||||||
- name: Upload Installer
|
|
||||||
id: unsigned-artifact
|
|
||||||
if: github.repository == 'openra/openra'
|
|
||||||
uses: actions/upload-artifact@v6
|
|
||||||
with:
|
|
||||||
path: build/windows/*.exe
|
|
||||||
|
|
||||||
- name: Microsoft Authenticode
|
|
||||||
id: signpath
|
|
||||||
if: github.repository == 'openra/openra'
|
|
||||||
uses: signpath/github-action-submit-signing-request@v2
|
|
||||||
with:
|
|
||||||
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
|
||||||
organization-id: '${{ secrets.SIGNPATH_ORGANISATION_ID }}'
|
|
||||||
project-slug: 'OpenRA'
|
|
||||||
signing-policy-slug: 'release-signing'
|
|
||||||
github-artifact-id: '${{ steps.unsigned-artifact.outputs.artifact-id }}'
|
|
||||||
wait-for-completion: true
|
|
||||||
output-artifact-directory: 'build/windows/signed/'
|
|
||||||
parameters: |
|
|
||||||
version: ${{ toJSON(github.ref_name) }}
|
|
||||||
|
|
||||||
- name: Upload Packages
|
- name: Upload Packages
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
gh release upload "${{ github.ref_name }}" build/windows/*.zip
|
gh release upload ${{ github.ref_name }} build/windows/*
|
||||||
|
|
||||||
if [ "${GITHUB_REPOSITORY}" = "openra/openra" ]; then
|
|
||||||
gh release upload "${{ github.ref_name }}" build/windows/signed/*.exe
|
|
||||||
else
|
|
||||||
gh release upload "${{ github.ref_name }}" build/windows/*.exe
|
|
||||||
fi
|
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -25,6 +25,10 @@ IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
|
|||||||
\#*
|
\#*
|
||||||
.*.sw?
|
.*.sw?
|
||||||
|
|
||||||
|
# Monodevelop
|
||||||
|
*.pidb
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
# Mac OS X
|
# Mac OS X
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
|||||||
3
AUTHORS
3
AUTHORS
@@ -36,7 +36,6 @@ Also thanks to:
|
|||||||
* Andreas Beck (baxtor)
|
* Andreas Beck (baxtor)
|
||||||
* Ang Soon Li (asl97)
|
* Ang Soon Li (asl97)
|
||||||
* Arik Lirette (Angusm3)
|
* Arik Lirette (Angusm3)
|
||||||
* Ashley Newson
|
|
||||||
* Barnaby Smith (mvi)
|
* Barnaby Smith (mvi)
|
||||||
* Bellator
|
* Bellator
|
||||||
* Bernd Stellwag (burned42)
|
* Bernd Stellwag (burned42)
|
||||||
@@ -55,7 +54,6 @@ Also thanks to:
|
|||||||
* Constantin Helmig (CH4Code)
|
* Constantin Helmig (CH4Code)
|
||||||
* D2k Sardaukar
|
* D2k Sardaukar
|
||||||
* D'Arcy Rush (r34ch)
|
* D'Arcy Rush (r34ch)
|
||||||
* Dan Stoian (stoiandan)
|
|
||||||
* Daniel Derejvanik (Harisson)
|
* Daniel Derejvanik (Harisson)
|
||||||
* Danny Keary (Dan9550)
|
* Danny Keary (Dan9550)
|
||||||
* David Jiménez (Rydra)
|
* David Jiménez (Rydra)
|
||||||
@@ -143,7 +141,6 @@ Also thanks to:
|
|||||||
* Riderr3
|
* Riderr3
|
||||||
* riiga
|
* riiga
|
||||||
* Rikhardur Bjarni Einarsson (WolfGaming)
|
* Rikhardur Bjarni Einarsson (WolfGaming)
|
||||||
* Robert Nordan (robpvn)
|
|
||||||
* Sascha Biedermann (bidifx)
|
* Sascha Biedermann (bidifx)
|
||||||
* Sean Hunt (coppro)
|
* Sean Hunt (coppro)
|
||||||
* Shawn Collins (UberWaffe)
|
* Shawn Collins (UberWaffe)
|
||||||
|
|||||||
@@ -24,6 +24,6 @@ Don't forget to add yourself to [AUTHORS](https://github.com/OpenRA/OpenRA/blob/
|
|||||||
|
|
||||||
Please propose a [CHANGELOG](https://github.com/OpenRA/OpenRA/wiki/CHANGELOG) entry in the pull-request comments.
|
Please propose a [CHANGELOG](https://github.com/OpenRA/OpenRA/wiki/CHANGELOG) entry in the pull-request comments.
|
||||||
|
|
||||||
While your pull-request is in review it will be helpful if you join [Discord](https://discord.openra.net) or [IRC](ircs://irc.libera.chat:6697/openra) to discuss the changes.
|
While your pull-request is in review it will be helpful if you join [IRC](irc://chat.freenode.net/openra) to discuss the changes.
|
||||||
|
|
||||||
See also the in-depth guide on [contributing](https://github.com/OpenRA/OpenRA/wiki/Contributing) to the OpenRA project.
|
See also the in-depth guide on [contributing](https://github.com/OpenRA/OpenRA/wiki/Contributing) to the OpenRA project.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<Optimize>true</Optimize>
|
<Optimize>true</Optimize>
|
||||||
<LangVersion>12</LangVersion>
|
<LangVersion>9</LangVersion>
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<DebugSymbols>true</DebugSymbols>
|
||||||
<EngineRootPath Condition="'$(EngineRootPath)' == ''">..</EngineRootPath>
|
<EngineRootPath Condition="'$(EngineRootPath)' == ''">..</EngineRootPath>
|
||||||
<OutputPath>$(EngineRootPath)/bin</OutputPath>
|
<OutputPath>$(EngineRootPath)/bin</OutputPath>
|
||||||
@@ -18,7 +18,8 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework Condition="'$(MSBuildRuntimeType)'!='Mono'">net6.0</TargetFramework>
|
||||||
|
<TargetFramework Condition="'$(MSBuildRuntimeType)'=='Mono'">netstandard2.1</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
@@ -52,8 +53,9 @@
|
|||||||
|
|
||||||
<!-- StyleCop/Roslynator -->
|
<!-- StyleCop/Roslynator -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" PrivateAssets="All" />
|
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
|
||||||
<PackageReference Include="Roslynator.Analyzers" Version="4.13.0" PrivateAssets="All" />
|
<!-- Roslynator analyzers fail to run under Mono (AD0001) -->
|
||||||
<PackageReference Include="Roslynator.Formatting.Analyzers" Version="4.13.0" PrivateAssets="All" />
|
<PackageReference Include="Roslynator.Analyzers" Version="4.2.0" PrivateAssets="All" Condition="'$(MSBuildRuntimeType)'!='Mono'" />
|
||||||
|
<PackageReference Include="Roslynator.Formatting.Analyzers" Version="4.2.0" PrivateAssets="All" Condition="'$(MSBuildRuntimeType)'!='Mono'" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM mcr.microsoft.com/dotnet/sdk:8.0
|
FROM mcr.microsoft.com/dotnet/sdk:6.0
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
apt-get update; \
|
apt-get update; \
|
||||||
|
|||||||
12
INSTALL.md
12
INSTALL.md
@@ -8,7 +8,7 @@ Windows
|
|||||||
|
|
||||||
Compiling OpenRA requires the following dependencies:
|
Compiling OpenRA requires the following dependencies:
|
||||||
* [Windows PowerShell >= 4.0](https://microsoft.com/powershell) (included by default in recent Windows 10 versions)
|
* [Windows PowerShell >= 4.0](https://microsoft.com/powershell) (included by default in recent Windows 10 versions)
|
||||||
* [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) (or via Visual Studio)
|
* [.NET 6 SDK](https://dotnet.microsoft.com/download/dotnet/6.0) (or via Visual Studio)
|
||||||
|
|
||||||
To compile OpenRA, open the `OpenRA.sln` solution in the main folder, build it from the command-line with `dotnet` or use the Makefile analogue command `make all` scripted in PowerShell syntax.
|
To compile OpenRA, open the `OpenRA.sln` solution in the main folder, build it from the command-line with `dotnet` or use the Makefile analogue command `make all` scripted in PowerShell syntax.
|
||||||
|
|
||||||
@@ -17,9 +17,11 @@ Run the game with `launch-game.cmd`. It can be handed arguments that specify the
|
|||||||
Linux
|
Linux
|
||||||
=====
|
=====
|
||||||
|
|
||||||
.NET 8 is required to compile OpenRA. The [.NET 8 download page](https://dotnet.microsoft.com/download/dotnet/8.0) provides repositories for various package managers and binary releases for several architectures.
|
.NET 6 or Mono (version 6.12 or later) is required to compile OpenRA. We recommend using .NET 6 when possible, as Mono is poorly packaged by most Linux distributions (e.g. missing the required `msbuild` toolchain), and has been deprecated as a standalone project.
|
||||||
|
|
||||||
To compile OpenRA, run `make` from the command line. After this one can run the game with `./launch-game.sh`. It is also possible to specify the mod you wish to run from the command line, e.g. with `./launch-game.sh Game.Mod=ts` if you wish to try the experimental Tiberian Sun mod.
|
The [.NET 6 download page](https://dotnet.microsoft.com/download/dotnet/6.0) provides repositories for various package managers and binary releases for several architectures. If you prefer to use Mono, we suggest adding the [upstream repository](https://www.mono-project.com/download/stable/#download-lin) for your distro to obtain the latest version and the `msbuild` toolchain.
|
||||||
|
|
||||||
|
To compile OpenRA, run `make` from the command line (or `make RUNTIME=mono` if using Mono). After this one can run the game with `./launch-game.sh`. It is also possible to specify the mod you wish to run from the command line, e.g. with `./launch-game.sh Game.Mod=ts` if you wish to try the experimental Tiberian Sun mod.
|
||||||
|
|
||||||
The default behaviour on the x86_64 architecture is to download several pre-compiled native libraries using the Nuget packaging manager. If you prefer to use system libraries, compile instead using `make TARGETPLATFORM=unix-generic`.
|
The default behaviour on the x86_64 architecture is to download several pre-compiled native libraries using the Nuget packaging manager. If you prefer to use system libraries, compile instead using `make TARGETPLATFORM=unix-generic`.
|
||||||
|
|
||||||
@@ -76,6 +78,6 @@ Type `sudo make install` for system-wide installation. Run `sudo make install-li
|
|||||||
macOS
|
macOS
|
||||||
=====
|
=====
|
||||||
|
|
||||||
[.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0) is required to compile OpenRA.
|
[.NET 6](https://dotnet.microsoft.com/download/dotnet/6.0) or [Mono](https://www.mono-project.com/download/stable/#download-mac) (version 6.12 or later) is required to compile OpenRA. We recommend using .NET 6 unless you are running a very old version of macOS (10.9 through 10.14).
|
||||||
|
|
||||||
To compile OpenRA, run `make` from the command line. Run with `./launch-game.sh`.
|
To compile OpenRA, run `make` from the command line (or `make RUNTIME=mono` if using Mono). Run with `./launch-game.sh`.
|
||||||
|
|||||||
43
Makefile
43
Makefile
@@ -3,17 +3,20 @@
|
|||||||
# to compile, run:
|
# to compile, run:
|
||||||
# make
|
# make
|
||||||
#
|
#
|
||||||
|
# to compile using Mono (version 6.12 or greater) instead of .NET 6, run:
|
||||||
|
# make RUNTIME=mono
|
||||||
|
#
|
||||||
# to compile using system libraries for native dependencies, run:
|
# to compile using system libraries for native dependencies, run:
|
||||||
# make TARGETPLATFORM=unix-generic
|
# make [RUNTIME=net6] TARGETPLATFORM=unix-generic
|
||||||
#
|
#
|
||||||
# to check the official mods for erroneous yaml files, run:
|
# to check the official mods for erroneous yaml files, run:
|
||||||
# make test
|
# make [RUNTIME=net6] test
|
||||||
#
|
#
|
||||||
# to check the engine and official mod dlls for code style violations, run:
|
# to check the engine and official mod dlls for code style violations, run:
|
||||||
# make check
|
# make [RUNTIME=net6] check
|
||||||
#
|
#
|
||||||
# to compile and install Red Alert, Tiberian Dawn, and Dune 2000, run:
|
# to compile and install Red Alert, Tiberian Dawn, and Dune 2000, run:
|
||||||
# make [prefix=/foo] [bindir=/bar/bin] install
|
# make [RUNTIME=net6] [prefix=/foo] [bindir=/bar/bin] install
|
||||||
#
|
#
|
||||||
# to compile and install Red Alert, Tiberian Dawn, and Dune 2000
|
# to compile and install Red Alert, Tiberian Dawn, and Dune 2000
|
||||||
# using system libraries for native dependencies, run:
|
# using system libraries for native dependencies, run:
|
||||||
@@ -45,12 +48,15 @@ gameinstalldir ?= $(libdir)/openra
|
|||||||
|
|
||||||
# Toolchain
|
# Toolchain
|
||||||
CWD = $(shell pwd)
|
CWD = $(shell pwd)
|
||||||
|
MSBUILD = msbuild -verbosity:m -nologo
|
||||||
DOTNET = dotnet
|
DOTNET = dotnet
|
||||||
|
MONO = mono
|
||||||
RM = rm
|
RM = rm
|
||||||
RM_R = $(RM) -r
|
RM_R = $(RM) -r
|
||||||
RM_F = $(RM) -f
|
RM_F = $(RM) -f
|
||||||
RM_RF = $(RM) -rf
|
RM_RF = $(RM) -rf
|
||||||
|
|
||||||
|
RUNTIME ?= net6
|
||||||
CONFIGURATION ?= Release
|
CONFIGURATION ?= Release
|
||||||
DOTNET_RID = $(shell ${DOTNET} --info | grep RID: | cut -w -f3)
|
DOTNET_RID = $(shell ${DOTNET} --info | grep RID: | cut -w -f3)
|
||||||
ARCH_X64 = $(shell echo ${DOTNET_RID} | grep x64)
|
ARCH_X64 = $(shell echo ${DOTNET_RID} | grep x64)
|
||||||
@@ -85,12 +91,18 @@ endif
|
|||||||
#
|
#
|
||||||
all:
|
all:
|
||||||
@echo "Compiling in ${CONFIGURATION} mode..."
|
@echo "Compiling in ${CONFIGURATION} mode..."
|
||||||
|
ifeq ($(RUNTIME), mono)
|
||||||
|
@command -v $(firstword $(MSBUILD)) >/dev/null || (echo "OpenRA requires the '$(MSBUILD)' tool provided by Mono >= 6.12."; exit 1)
|
||||||
|
@$(MSBUILD) -t:Build -restore -p:Configuration=${CONFIGURATION} -p:TargetPlatform=$(TARGETPLATFORM)
|
||||||
|
else
|
||||||
@$(DOTNET) build -c ${CONFIGURATION} -nologo -p:TargetPlatform=$(TARGETPLATFORM)
|
@$(DOTNET) build -c ${CONFIGURATION} -nologo -p:TargetPlatform=$(TARGETPLATFORM)
|
||||||
|
endif
|
||||||
ifeq ($(TARGETPLATFORM), unix-generic)
|
ifeq ($(TARGETPLATFORM), unix-generic)
|
||||||
@./configure-system-libraries.sh
|
@./configure-system-libraries.sh
|
||||||
endif
|
endif
|
||||||
@./fetch-geoip.sh
|
@./fetch-geoip.sh
|
||||||
|
|
||||||
|
# dotnet clean and msbuild -t:Clean leave files that cause problems when switching between mono/dotnet
|
||||||
# Deleting the intermediate / output directories ensures the build directory is actually clean
|
# Deleting the intermediate / output directories ensures the build directory is actually clean
|
||||||
clean:
|
clean:
|
||||||
@-$(RM_RF) ./bin ./*/obj
|
@-$(RM_RF) ./bin ./*/obj
|
||||||
@@ -99,8 +111,12 @@ clean:
|
|||||||
check:
|
check:
|
||||||
@echo
|
@echo
|
||||||
@echo "Compiling in Debug mode..."
|
@echo "Compiling in Debug mode..."
|
||||||
|
ifeq ($(RUNTIME), mono)
|
||||||
|
@$(MSBUILD) -t:clean\;build -restore -p:Configuration=Debug -warnaserror -p:TargetPlatform=$(TARGETPLATFORM)
|
||||||
|
else
|
||||||
@$(DOTNET) clean -c Debug --nologo --verbosity minimal
|
@$(DOTNET) clean -c Debug --nologo --verbosity minimal
|
||||||
@$(DOTNET) build -c Debug -nologo -warnaserror -p:TargetPlatform=$(TARGETPLATFORM)
|
@$(DOTNET) build -c Debug -nologo -warnaserror -p:TargetPlatform=$(TARGETPLATFORM)
|
||||||
|
endif
|
||||||
ifeq ($(TARGETPLATFORM), unix-generic)
|
ifeq ($(TARGETPLATFORM), unix-generic)
|
||||||
@./configure-system-libraries.sh
|
@./configure-system-libraries.sh
|
||||||
endif
|
endif
|
||||||
@@ -119,19 +135,15 @@ check-scripts:
|
|||||||
test: all
|
test: all
|
||||||
@echo
|
@echo
|
||||||
@echo "Testing Tiberian Sun mod MiniYAML..."
|
@echo "Testing Tiberian Sun mod MiniYAML..."
|
||||||
@./utility.sh ts-content --check-yaml
|
|
||||||
@./utility.sh ts --check-yaml
|
@./utility.sh ts --check-yaml
|
||||||
@echo
|
@echo
|
||||||
@echo "Testing Dune 2000 mod MiniYAML..."
|
@echo "Testing Dune 2000 mod MiniYAML..."
|
||||||
@./utility.sh d2k-content --check-yaml
|
|
||||||
@./utility.sh d2k --check-yaml
|
@./utility.sh d2k --check-yaml
|
||||||
@echo
|
@echo
|
||||||
@echo "Testing Tiberian Dawn mod MiniYAML..."
|
@echo "Testing Tiberian Dawn mod MiniYAML..."
|
||||||
@./utility.sh cnc-content --check-yaml
|
|
||||||
@./utility.sh cnc --check-yaml
|
@./utility.sh cnc --check-yaml
|
||||||
@echo
|
@echo
|
||||||
@echo "Testing Red Alert mod MiniYAML..."
|
@echo "Testing Red Alert mod MiniYAML..."
|
||||||
@./utility.sh ra-content --check-yaml
|
|
||||||
@./utility.sh ra --check-yaml
|
@./utility.sh ra --check-yaml
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
@@ -149,7 +161,7 @@ endif
|
|||||||
@sh -c '. ./packaging/functions.sh; set_mod_version "$(VERSION)" mods/*/mod.yaml'
|
@sh -c '. ./packaging/functions.sh; set_mod_version "$(VERSION)" mods/*/mod.yaml'
|
||||||
|
|
||||||
install:
|
install:
|
||||||
@sh -c '. ./packaging/functions.sh; install_assemblies $(CWD) $(DESTDIR)$(gameinstalldir) $(TARGETPLATFORM) True True True'
|
@sh -c '. ./packaging/functions.sh; install_assemblies $(CWD) $(DESTDIR)$(gameinstalldir) $(TARGETPLATFORM) $(RUNTIME) True True True'
|
||||||
@sh -c '. ./packaging/functions.sh; install_data $(CWD) $(DESTDIR)$(gameinstalldir) cnc d2k ra'
|
@sh -c '. ./packaging/functions.sh; install_data $(CWD) $(DESTDIR)$(gameinstalldir) cnc d2k ra'
|
||||||
|
|
||||||
install-linux-shortcuts:
|
install-linux-shortcuts:
|
||||||
@@ -166,21 +178,24 @@ help:
|
|||||||
@echo 'to compile, run:'
|
@echo 'to compile, run:'
|
||||||
@echo ' make'
|
@echo ' make'
|
||||||
@echo
|
@echo
|
||||||
|
@echo 'to compile using Mono (version 6.12 or greater) instead of .NET 6, run:'
|
||||||
|
@echo ' make RUNTIME=mono'
|
||||||
|
@echo
|
||||||
@echo 'to compile using system libraries for native dependencies, run:'
|
@echo 'to compile using system libraries for native dependencies, run:'
|
||||||
@echo ' make TARGETPLATFORM=unix-generic'
|
@echo ' make [RUNTIME=net6] TARGETPLATFORM=unix-generic'
|
||||||
@echo
|
@echo
|
||||||
@echo 'to check the official mods for erroneous yaml files, run:'
|
@echo 'to check the official mods for erroneous yaml files, run:'
|
||||||
@echo ' make [TREAT_WARNINGS_AS_ERRORS=false] test'
|
@echo ' make [RUNTIME=net6] [TREAT_WARNINGS_AS_ERRORS=false] test'
|
||||||
@echo
|
@echo
|
||||||
@echo 'to check the engine and official mod dlls for code style violations, run:'
|
@echo 'to check the engine and official mod dlls for code style violations, run:'
|
||||||
@echo ' make check'
|
@echo ' make [RUNTIME=net6] check'
|
||||||
@echo
|
@echo
|
||||||
@echo 'to compile and install Red Alert, Tiberian Dawn, and Dune 2000 run:'
|
@echo 'to compile and install Red Alert, Tiberian Dawn, and Dune 2000 run:'
|
||||||
@echo ' make [prefix=/foo] [TARGETPLATFORM=unix-generic] install'
|
@echo ' make [RUNTIME=net6] [prefix=/foo] [TARGETPLATFORM=unix-generic] install'
|
||||||
@echo
|
@echo
|
||||||
@echo 'to compile and install Red Alert, Tiberian Dawn, and Dune 2000'
|
@echo 'to compile and install Red Alert, Tiberian Dawn, and Dune 2000'
|
||||||
@echo 'using system libraries for native dependencies, run:'
|
@echo 'using system libraries for native dependencies, run:'
|
||||||
@echo ' make [prefix=/foo] [bindir=/bar/bin] TARGETPLATFORM=unix-generic install'
|
@echo ' make [RUNTIME=net6] [prefix=/foo] [bindir=/bar/bin] TARGETPLATFORM=unix-generic install'
|
||||||
@echo
|
@echo
|
||||||
@echo 'to install FreeDesktop startup scripts, desktop files, icons, and MIME metadata'
|
@echo 'to install FreeDesktop startup scripts, desktop files, icons, and MIME metadata'
|
||||||
@echo ' make install-linux-shortcuts'
|
@echo ' make install-linux-shortcuts'
|
||||||
|
|||||||
@@ -89,21 +89,21 @@ namespace OpenRA
|
|||||||
sealed class ConditionState
|
sealed class ConditionState
|
||||||
{
|
{
|
||||||
/// <summary>Delegates that have registered to be notified when this condition changes.</summary>
|
/// <summary>Delegates that have registered to be notified when this condition changes.</summary>
|
||||||
public readonly List<VariableObserverNotifier> Notifiers = [];
|
public readonly List<VariableObserverNotifier> Notifiers = new();
|
||||||
|
|
||||||
/// <summary>Unique integers identifying granted instances of the condition.</summary>
|
/// <summary>Unique integers identifying granted instances of the condition.</summary>
|
||||||
public readonly HashSet<int> Tokens = [];
|
public readonly HashSet<int> Tokens = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly Dictionary<string, ConditionState> conditionStates = [];
|
readonly Dictionary<string, ConditionState> conditionStates = new();
|
||||||
|
|
||||||
/// <summary>Each granted condition receives a unique token that is used when revoking.</summary>
|
/// <summary>Each granted condition receives a unique token that is used when revoking.</summary>
|
||||||
readonly Dictionary<int, string> conditionTokens = [];
|
readonly Dictionary<int, string> conditionTokens = new();
|
||||||
|
|
||||||
int nextConditionToken = 1;
|
int nextConditionToken = 1;
|
||||||
|
|
||||||
/// <summary>Cache of condition -> enabled state for quick evaluation of token counter conditions.</summary>
|
/// <summary>Cache of condition -> enabled state for quick evaluation of token counter conditions.</summary>
|
||||||
readonly Dictionary<string, int> conditionCache = [];
|
readonly Dictionary<string, int> conditionCache = new();
|
||||||
|
|
||||||
/// <summary>Read-only version of conditionCache that is passed to IConditionConsumers.</summary>
|
/// <summary>Read-only version of conditionCache that is passed to IConditionConsumers.</summary>
|
||||||
readonly IReadOnlyDictionary<string, int> readOnlyConditionCache;
|
readonly IReadOnlyDictionary<string, int> readOnlyConditionCache;
|
||||||
@@ -123,9 +123,6 @@ namespace OpenRA
|
|||||||
readonly IEnumerable<WPos> enabledTargetableWorldPositions;
|
readonly IEnumerable<WPos> enabledTargetableWorldPositions;
|
||||||
bool created;
|
bool created;
|
||||||
|
|
||||||
IEnumerable<IRenderable> renderables;
|
|
||||||
WorldRenderer lastWorldRenderer;
|
|
||||||
|
|
||||||
internal Actor(World world, string name, TypeDictionary initDict)
|
internal Actor(World world, string name, TypeDictionary initDict)
|
||||||
{
|
{
|
||||||
var duplicateInit = initDict.WithInterface<ISingleInstanceInit>().GroupBy(i => i.GetType())
|
var duplicateInit = initDict.WithInterface<ISingleInstanceInit>().GroupBy(i => i.GetType())
|
||||||
@@ -290,18 +287,11 @@ namespace OpenRA
|
|||||||
|
|
||||||
public IEnumerable<IRenderable> Render(WorldRenderer wr)
|
public IEnumerable<IRenderable> Render(WorldRenderer wr)
|
||||||
{
|
{
|
||||||
if (lastWorldRenderer != wr)
|
|
||||||
{
|
|
||||||
// PERF: Cache the enumerable to reduce allocations.
|
|
||||||
lastWorldRenderer = wr;
|
|
||||||
renderables = Renderables(wr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// PERF: Avoid LINQ.
|
// PERF: Avoid LINQ.
|
||||||
var modifiedRenderables = renderables;
|
var renderables = Renderables(wr);
|
||||||
foreach (var modifier in renderModifiers)
|
foreach (var modifier in renderModifiers)
|
||||||
modifiedRenderables = modifier.ModifyRender(this, wr, modifiedRenderables);
|
renderables = modifier.ModifyRender(this, wr, renderables);
|
||||||
return modifiedRenderables;
|
return renderables;
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerable<IRenderable> Renderables(WorldRenderer wr)
|
IEnumerable<IRenderable> Renderables(WorldRenderer wr)
|
||||||
@@ -514,7 +504,7 @@ namespace OpenRA
|
|||||||
if (!visibilityModifier.IsVisible(this, player))
|
if (!visibilityModifier.IsVisible(this, player))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return defaultVisibility?.IsVisible(this, player) ?? true;
|
return defaultVisibility.IsVisible(this, player);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BitSet<TargetableType> GetAllTargetTypes()
|
public BitSet<TargetableType> GetAllTargetTypes()
|
||||||
@@ -551,7 +541,7 @@ namespace OpenRA
|
|||||||
if (EnabledTargetablePositions.Any())
|
if (EnabledTargetablePositions.Any())
|
||||||
return enabledTargetableWorldPositions;
|
return enabledTargetableWorldPositions;
|
||||||
|
|
||||||
return [CenterPosition];
|
return new[] { CenterPosition };
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Conditions
|
#region Conditions
|
||||||
|
|||||||
@@ -78,9 +78,17 @@ namespace OpenRA
|
|||||||
return new MPos(X, Y);
|
return new MPos(X, Y);
|
||||||
|
|
||||||
// Convert from RectangularIsometric cell (x, y) position to rectangular map position (u, v)
|
// Convert from RectangularIsometric cell (x, y) position to rectangular map position (u, v)
|
||||||
// The staggered rows make this fiddly.
|
// - The staggered rows make this fiddly (hint: draw a diagram!)
|
||||||
|
// (a) Consider the relationships:
|
||||||
|
// - +1x (even -> odd) adds (0, 1) to (u, v)
|
||||||
|
// - +1x (odd -> even) adds (1, 1) to (u, v)
|
||||||
|
// - +1y (even -> odd) adds (-1, 1) to (u, v)
|
||||||
|
// - +1y (odd -> even) adds (0, 1) to (u, v)
|
||||||
|
// (b) Therefore:
|
||||||
|
// - ax + by adds (a - b)/2 to u (only even increments count)
|
||||||
|
// - ax + by adds a + b to v
|
||||||
|
var u = (X - Y) / 2;
|
||||||
var v = X + Y;
|
var v = X + Y;
|
||||||
var u = (v - (v & 1)) / 2 - Y;
|
|
||||||
return new MPos(u, v);
|
return new MPos(u, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ namespace OpenRA
|
|||||||
public override string ToString() { return X + "," + Y; }
|
public override string ToString() { return X + "," + Y; }
|
||||||
|
|
||||||
public static readonly CVec[] Directions =
|
public static readonly CVec[] Directions =
|
||||||
[
|
{
|
||||||
new(-1, -1),
|
new(-1, -1),
|
||||||
new(-1, 0),
|
new(-1, 0),
|
||||||
new(-1, 1),
|
new(-1, 1),
|
||||||
@@ -71,7 +71,7 @@ namespace OpenRA
|
|||||||
new(1, -1),
|
new(1, -1),
|
||||||
new(1, 0),
|
new(1, 0),
|
||||||
new(1, 1),
|
new(1, 1),
|
||||||
];
|
};
|
||||||
|
|
||||||
#region Scripting interface
|
#region Scripting interface
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ namespace OpenRA
|
|||||||
public static class CryptoUtil
|
public static class CryptoUtil
|
||||||
{
|
{
|
||||||
// Fixed byte pattern for the OID header
|
// 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 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[] HexUpperAlphabet = "0123456789ABCDEF".ToArray();
|
||||||
static readonly char[] HexLowerAlphabet = "0123456789abcdef".ToArray();
|
static readonly char[] HexLowerAlphabet = "0123456789abcdef".ToArray();
|
||||||
@@ -210,7 +210,8 @@ namespace OpenRA
|
|||||||
using (var rsa = new RSACryptoServiceProvider())
|
using (var rsa = new RSACryptoServiceProvider())
|
||||||
{
|
{
|
||||||
rsa.ImportParameters(parameters);
|
rsa.ImportParameters(parameters);
|
||||||
return Convert.ToBase64String(rsa.SignHash(SHA1.HashData(data), CryptoConfig.MapNameToOID("SHA1")));
|
using (var csp = SHA1.Create())
|
||||||
|
return Convert.ToBase64String(rsa.SignHash(csp.ComputeHash(data), CryptoConfig.MapNameToOID("SHA1")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@@ -235,7 +236,8 @@ namespace OpenRA
|
|||||||
using (var rsa = new RSACryptoServiceProvider())
|
using (var rsa = new RSACryptoServiceProvider())
|
||||||
{
|
{
|
||||||
rsa.ImportParameters(parameters);
|
rsa.ImportParameters(parameters);
|
||||||
return rsa.VerifyHash(SHA1.HashData(data), CryptoConfig.MapNameToOID("SHA1"), Convert.FromBase64String(signature));
|
using (var csp = SHA1.Create())
|
||||||
|
return rsa.VerifyHash(csp.ComputeHash(data), CryptoConfig.MapNameToOID("SHA1"), Convert.FromBase64String(signature));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@@ -250,12 +252,14 @@ namespace OpenRA
|
|||||||
|
|
||||||
public static string SHA1Hash(Stream data)
|
public static string SHA1Hash(Stream data)
|
||||||
{
|
{
|
||||||
return ToHex(SHA1.HashData(data), true);
|
using var csp = SHA1.Create();
|
||||||
|
return ToHex(csp.ComputeHash(data), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string SHA1Hash(byte[] data)
|
public static string SHA1Hash(byte[] data)
|
||||||
{
|
{
|
||||||
return ToHex(SHA1.HashData(data), true);
|
using var csp = SHA1.Create();
|
||||||
|
return ToHex(csp.ComputeHash(data), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string SHA1Hash(string data)
|
public static string SHA1Hash(string data)
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OpenRA.FileFormats;
|
using OpenRA.FileFormats;
|
||||||
@@ -29,7 +28,7 @@ namespace OpenRA
|
|||||||
public readonly string Id;
|
public readonly string Id;
|
||||||
public readonly string Version;
|
public readonly string Version;
|
||||||
public readonly string LaunchPath;
|
public readonly string LaunchPath;
|
||||||
public readonly ImmutableArray<string> LaunchArgs;
|
public readonly string[] LaunchArgs;
|
||||||
public Sprite Icon { get; internal set; }
|
public Sprite Icon { get; internal set; }
|
||||||
public Sprite Icon2x { get; internal set; }
|
public Sprite Icon2x { get; internal set; }
|
||||||
public Sprite Icon3x { get; internal set; }
|
public Sprite Icon3x { get; internal set; }
|
||||||
@@ -41,7 +40,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
public class ExternalMods : IReadOnlyDictionary<string, ExternalMod>
|
public class ExternalMods : IReadOnlyDictionary<string, ExternalMod>
|
||||||
{
|
{
|
||||||
readonly Dictionary<string, ExternalMod> mods = [];
|
readonly Dictionary<string, ExternalMod> mods = new();
|
||||||
readonly SheetBuilder sheetBuilder;
|
readonly SheetBuilder sheetBuilder;
|
||||||
|
|
||||||
Sheet CreateSheet()
|
Sheet CreateSheet()
|
||||||
@@ -123,13 +122,13 @@ namespace OpenRA
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var key = ExternalMod.MakeKey(mod);
|
var key = ExternalMod.MakeKey(mod);
|
||||||
var yaml = new MiniYamlNode("Registration", new MiniYaml("",
|
var yaml = new MiniYamlNode("Registration", new MiniYaml("", new[]
|
||||||
[
|
{
|
||||||
new MiniYamlNode("Id", mod.Id),
|
new MiniYamlNode("Id", mod.Id),
|
||||||
new MiniYamlNode("Version", mod.Metadata.Version),
|
new MiniYamlNode("Version", mod.Metadata.Version),
|
||||||
new MiniYamlNode("LaunchPath", launchPath),
|
new MiniYamlNode("LaunchPath", launchPath),
|
||||||
new MiniYamlNode("LaunchArgs", new[] { "Game.Mod=" + mod.Id }.Concat(launchArgs).JoinWith(", "))
|
new MiniYamlNode("LaunchArgs", new[] { "Game.Mod=" + mod.Id }.Concat(launchArgs).JoinWith(", "))
|
||||||
]));
|
}));
|
||||||
|
|
||||||
var iconNodes = new List<MiniYamlNode>();
|
var iconNodes = new List<MiniYamlNode>();
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,9 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
using OpenRA.Support;
|
using OpenRA.Support;
|
||||||
@@ -79,7 +77,7 @@ namespace OpenRA
|
|||||||
return Math.Sign((v1.X - v0.X) * (p.Y - v0.Y) - (p.X - v0.X) * (v1.Y - v0.Y));
|
return Math.Sign((v1.X - v0.X) * (p.Y - v0.Y) - (p.X - v0.X) * (v1.Y - v0.Y));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool PolygonContains(this ImmutableArray<int2> polygon, int2 p)
|
public static bool PolygonContains(this int2[] polygon, int2 p)
|
||||||
{
|
{
|
||||||
var windingNumber = 0;
|
var windingNumber = 0;
|
||||||
|
|
||||||
@@ -121,11 +119,17 @@ namespace OpenRA
|
|||||||
|
|
||||||
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k, V v)
|
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.
|
// SAFETY: Dictionary cannot be modified whilst the ref is alive.
|
||||||
ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(d, k, out var exists);
|
ref var value = ref System.Runtime.InteropServices.CollectionsMarshal.GetValueRefOrAddDefault(d, k, out var exists);
|
||||||
if (!exists)
|
if (!exists)
|
||||||
value = v;
|
value = v;
|
||||||
return value;
|
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)
|
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k, Func<K, V> createFn)
|
||||||
@@ -414,6 +418,11 @@ namespace OpenRA
|
|||||||
return ts.Except(exclusions);
|
return ts.Except(exclusions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source)
|
||||||
|
{
|
||||||
|
return new HashSet<T>(source);
|
||||||
|
}
|
||||||
|
|
||||||
public static Dictionary<TKey, TSource> ToDictionaryWithConflictLog<TSource, TKey>(
|
public static Dictionary<TKey, TSource> ToDictionaryWithConflictLog<TSource, TKey>(
|
||||||
this IEnumerable<TSource> source, Func<TSource, TKey> keySelector,
|
this IEnumerable<TSource> source, Func<TSource, TKey> keySelector,
|
||||||
string debugName, Func<TKey, string> logKey, Func<TSource, string> logValue)
|
string debugName, Func<TKey, string> logKey, Func<TSource, string> logValue)
|
||||||
@@ -456,14 +465,14 @@ namespace OpenRA
|
|||||||
// Check for a key conflict:
|
// Check for a key conflict:
|
||||||
if (!output.TryAdd(key, element))
|
if (!output.TryAdd(key, element))
|
||||||
{
|
{
|
||||||
dupKeys ??= [];
|
dupKeys ??= new Dictionary<TKey, List<string>>();
|
||||||
if (!dupKeys.TryGetValue(key, out var dupKeyMessages))
|
if (!dupKeys.TryGetValue(key, out var dupKeyMessages))
|
||||||
{
|
{
|
||||||
// Log the initial conflicting value already inserted:
|
// Log the initial conflicting value already inserted:
|
||||||
dupKeyMessages =
|
dupKeyMessages = new List<string>
|
||||||
[
|
{
|
||||||
logValue(output[key])
|
logValue(output[key])
|
||||||
];
|
};
|
||||||
dupKeys.Add(key, dupKeyMessages);
|
dupKeys.Add(key, dupKeyMessages);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,7 +487,7 @@ namespace OpenRA
|
|||||||
var badKeysFormatted = new StringBuilder(
|
var badKeysFormatted = new StringBuilder(
|
||||||
$"{debugName}, duplicate values found for the following keys: ");
|
$"{debugName}, duplicate values found for the following keys: ");
|
||||||
foreach (var p in dupKeys)
|
foreach (var p in dupKeys)
|
||||||
badKeysFormatted.Append(CultureInfo.InvariantCulture, $"{logKey(p.Key)}: [{string.Join(",", p.Value)}]");
|
badKeysFormatted.Append($"{logKey(p.Key)}: [{string.Join(",", p.Value)}]");
|
||||||
throw new ArgumentException(badKeysFormatted.ToString());
|
throw new ArgumentException(badKeysFormatted.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -501,16 +510,44 @@ namespace OpenRA
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static T[,] ResizeArray<T>(T[,] ts, T t, int width, int height)
|
||||||
|
{
|
||||||
|
var result = new T[width, height];
|
||||||
|
for (var i = 0; i < width; i++)
|
||||||
|
{
|
||||||
|
for (var j = 0; j < height; j++)
|
||||||
|
{
|
||||||
|
// Workaround for broken ternary operators in certain versions of mono
|
||||||
|
// (3.10 and certain versions of the 3.8 series): https://bugzilla.xamarin.com/show_bug.cgi?id=23319
|
||||||
|
if (i <= ts.GetUpperBound(0) && j <= ts.GetUpperBound(1))
|
||||||
|
result[i, j] = ts[i, j];
|
||||||
|
else
|
||||||
|
result[i, j] = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int ToBits(this IEnumerable<bool> bits)
|
||||||
|
{
|
||||||
|
var i = 0;
|
||||||
|
var result = 0;
|
||||||
|
foreach (var b in bits)
|
||||||
|
if (b)
|
||||||
|
result |= 1 << i++;
|
||||||
|
else
|
||||||
|
i++;
|
||||||
|
if (i > 33)
|
||||||
|
throw new InvalidOperationException("ToBits only accepts up to 32 values.");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public static byte ParseByteInvariant(string s)
|
public static byte ParseByteInvariant(string s)
|
||||||
{
|
{
|
||||||
return byte.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
|
return byte.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ushort ParseUshortInvariant(string s)
|
|
||||||
{
|
|
||||||
return ushort.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static short ParseInt16Invariant(string s)
|
public static short ParseInt16Invariant(string s)
|
||||||
{
|
{
|
||||||
return short.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
|
return short.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
|
||||||
@@ -521,22 +558,6 @@ namespace OpenRA
|
|||||||
return int.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
|
return int.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static float ParseFloatOrPercentInvariant(string s)
|
|
||||||
{
|
|
||||||
var f = float.Parse(s.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo);
|
|
||||||
return f * (s.Contains('%') ? 0.01f : 1f);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool TryParseByteInvariant(string s, out byte i)
|
|
||||||
{
|
|
||||||
return byte.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool TryParseUshortInvariant(string s, out ushort i)
|
|
||||||
{
|
|
||||||
return ushort.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool TryParseInt32Invariant(string s, out int i)
|
public static bool TryParseInt32Invariant(string s, out int i)
|
||||||
{
|
{
|
||||||
return int.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
|
return int.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
|
||||||
@@ -547,17 +568,6 @@ namespace OpenRA
|
|||||||
return long.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
|
return long.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryParseFloatOrPercentInvariant(string s, out float f)
|
|
||||||
{
|
|
||||||
if (float.TryParse(s?.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out f))
|
|
||||||
{
|
|
||||||
f *= s.Contains('%') ? 0.01f : 1f;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string ToStringInvariant(this byte i)
|
public static string ToStringInvariant(this byte i)
|
||||||
{
|
{
|
||||||
return i.ToString(NumberFormatInfo.InvariantInfo);
|
return i.ToString(NumberFormatInfo.InvariantInfo);
|
||||||
@@ -573,16 +583,6 @@ namespace OpenRA
|
|||||||
return i.ToString(NumberFormatInfo.InvariantInfo);
|
return i.ToString(NumberFormatInfo.InvariantInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ToStringInvariant(this uint i)
|
|
||||||
{
|
|
||||||
return i.ToString(NumberFormatInfo.InvariantInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string ToStringInvariant(this float f)
|
|
||||||
{
|
|
||||||
return f.ToString(NumberFormatInfo.InvariantInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string ToStringInvariant(this int i, string format)
|
public static string ToStringInvariant(this int i, string format)
|
||||||
{
|
{
|
||||||
return i.ToString(format, NumberFormatInfo.InvariantInfo);
|
return i.ToString(format, NumberFormatInfo.InvariantInfo);
|
||||||
@@ -665,7 +665,7 @@ namespace OpenRA
|
|||||||
if (index == -1)
|
if (index == -1)
|
||||||
{
|
{
|
||||||
// The remaining string is an empty string
|
// The remaining string is an empty string
|
||||||
str = [];
|
str = ReadOnlySpan<char>.Empty;
|
||||||
Current = span;
|
Current = span;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -677,4 +677,27 @@ namespace OpenRA
|
|||||||
|
|
||||||
public ReadOnlySpan<char> Current { get; private set; }
|
public ReadOnlySpan<char> Current { get; private set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Enum<T>
|
||||||
|
{
|
||||||
|
public static T Parse(string s) { return (T)Enum.Parse(typeof(T), s); }
|
||||||
|
public static T[] GetValues() { return (T[])Enum.GetValues(typeof(T)); }
|
||||||
|
|
||||||
|
public static bool TryParse(string s, bool ignoreCase, out T value)
|
||||||
|
{
|
||||||
|
// The string may be a comma delimited list of values
|
||||||
|
var names = ignoreCase ? Enum.GetNames(typeof(T)).Select(x => x.ToLowerInvariant()) : Enum.GetNames(typeof(T));
|
||||||
|
var values = ignoreCase ? s.Split(',').Select(x => x.Trim().ToLowerInvariant()) : s.Split(',').Select(x => x.Trim());
|
||||||
|
|
||||||
|
if (values.Any(x => !names.Contains(x)))
|
||||||
|
{
|
||||||
|
value = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = (T)Enum.Parse(typeof(T), s, ignoreCase);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,14 +10,13 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Frozen;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
using OpenRA.Support;
|
using OpenRA.Support;
|
||||||
|
|
||||||
@@ -25,8 +24,9 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
public static class FieldLoader
|
public static class FieldLoader
|
||||||
{
|
{
|
||||||
const char Comma = ',';
|
const char SplitComma = ',';
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
public class MissingFieldsException : YamlException
|
public class MissingFieldsException : YamlException
|
||||||
{
|
{
|
||||||
public readonly string[] Missing;
|
public readonly string[] Missing;
|
||||||
@@ -46,10 +46,17 @@ namespace OpenRA
|
|||||||
Header = missing.Length > 1 ? header : headerSingle ?? header;
|
Header = missing.Length > 1 ? header : headerSingle ?? header;
|
||||||
Missing = missing;
|
Missing = missing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||||
|
{
|
||||||
|
base.GetObjectData(info, context);
|
||||||
|
info.AddValue("Missing", Missing);
|
||||||
|
info.AddValue("Header", Header);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Func<string, Type, string, object> InvalidValueAction = (s, t, f) =>
|
public static Func<string, Type, string, object> InvalidValueAction = (s, t, f) =>
|
||||||
throw new YamlException($"FieldLoader: Cannot parse `{s}` into `{f}.{t}`");
|
throw new YamlException($"FieldLoader: Cannot parse `{s}` into `{f}.{t}` ");
|
||||||
|
|
||||||
public static Action<string, Type> UnknownFieldAction = (s, f) =>
|
public static Action<string, Type> UnknownFieldAction = (s, f) =>
|
||||||
throw new NotImplementedException($"FieldLoader: Missing field `{s}` on `{f.Name}`");
|
throw new NotImplementedException($"FieldLoader: Missing field `{s}` on `{f.Name}`");
|
||||||
@@ -61,8 +68,8 @@ namespace OpenRA
|
|||||||
static readonly ConcurrentCache<string, IntegerExpression> IntegerExpressionCache =
|
static readonly ConcurrentCache<string, IntegerExpression> IntegerExpressionCache =
|
||||||
new(expression => new IntegerExpression(expression));
|
new(expression => new IntegerExpression(expression));
|
||||||
|
|
||||||
static readonly FrozenDictionary<Type, Func<string, Type, string, object>> TypeParsers =
|
static readonly Dictionary<Type, Func<string, Type, string, MemberInfo, object>> TypeParsers =
|
||||||
new Dictionary<Type, Func<string, Type, string, object>>
|
new()
|
||||||
{
|
{
|
||||||
{ typeof(int), ParseInt },
|
{ typeof(int), ParseInt },
|
||||||
{ typeof(ushort), ParseUShort },
|
{ typeof(ushort), ParseUShort },
|
||||||
@@ -85,6 +92,7 @@ namespace OpenRA
|
|||||||
{ typeof(CVec[]), ParseCVecArray },
|
{ typeof(CVec[]), ParseCVecArray },
|
||||||
{ typeof(BooleanExpression), ParseBooleanExpression },
|
{ typeof(BooleanExpression), ParseBooleanExpression },
|
||||||
{ typeof(IntegerExpression), ParseIntegerExpression },
|
{ typeof(IntegerExpression), ParseIntegerExpression },
|
||||||
|
{ typeof(Enum), ParseEnum },
|
||||||
{ typeof(bool), ParseBool },
|
{ typeof(bool), ParseBool },
|
||||||
{ typeof(int2[]), ParseInt2Array },
|
{ typeof(int2[]), ParseInt2Array },
|
||||||
{ typeof(Size), ParseSize },
|
{ typeof(Size), ParseSize },
|
||||||
@@ -93,44 +101,23 @@ namespace OpenRA
|
|||||||
{ typeof(float3), ParseFloat3 },
|
{ typeof(float3), ParseFloat3 },
|
||||||
{ typeof(Rectangle), ParseRectangle },
|
{ typeof(Rectangle), ParseRectangle },
|
||||||
{ typeof(DateTime), ParseDateTime }
|
{ typeof(DateTime), ParseDateTime }
|
||||||
}.ToFrozenDictionary();
|
};
|
||||||
|
|
||||||
static readonly FrozenDictionary<Type, Func<string, Type, string, MiniYaml, object>> GenericTypeParsers =
|
static readonly Dictionary<Type, Func<string, Type, string, MiniYaml, MemberInfo, object>> GenericTypeParsers =
|
||||||
new Dictionary<Type, Func<string, Type, string, MiniYaml, object>>
|
new()
|
||||||
{
|
{
|
||||||
{ typeof(HashSet<>), ParseHashSetOrList },
|
{ typeof(HashSet<>), ParseHashSetOrList },
|
||||||
{ typeof(List<>), ParseHashSetOrList },
|
{ typeof(List<>), ParseHashSetOrList },
|
||||||
{ typeof(Dictionary<,>), ParseDictionary },
|
{ typeof(Dictionary<,>), ParseDictionary },
|
||||||
{ typeof(ImmutableArray<>), ParseImmutableArray },
|
|
||||||
{ typeof(FrozenSet<>), ParseFrozenSet },
|
|
||||||
{ typeof(FrozenDictionary<,>), ParseFrozenDictionary },
|
|
||||||
{ typeof(BitSet<>), ParseBitSet },
|
{ typeof(BitSet<>), ParseBitSet },
|
||||||
{ typeof(Nullable<>), ParseNullable },
|
{ typeof(Nullable<>), ParseNullable },
|
||||||
}.ToFrozenDictionary();
|
};
|
||||||
|
|
||||||
static readonly object BoxedTrue = true;
|
static readonly object BoxedTrue = true;
|
||||||
static readonly object BoxedFalse = false;
|
static readonly object BoxedFalse = false;
|
||||||
static readonly object[] BoxedInts = Exts.MakeArray(33, i => (object)i);
|
static readonly object[] BoxedInts = Exts.MakeArray(33, i => (object)i);
|
||||||
|
|
||||||
static readonly MethodInfo ToImmutableArray =
|
static object ParseInt(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
typeof(ImmutableArray)
|
|
||||||
.GetMethods()
|
|
||||||
.Single(m =>
|
|
||||||
m.Name == nameof(ImmutableArray.ToImmutableArray) &&
|
|
||||||
m.GetParameters()?.First().ParameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>));
|
|
||||||
|
|
||||||
static readonly MethodInfo ToFrozenSet =
|
|
||||||
typeof(FrozenSet)
|
|
||||||
.GetMethod(nameof(FrozenSet.ToFrozenSet));
|
|
||||||
|
|
||||||
static readonly MethodInfo ToFrozenDictionary =
|
|
||||||
typeof(FrozenDictionary)
|
|
||||||
.GetMethods()
|
|
||||||
.Single(m =>
|
|
||||||
m.Name == nameof(FrozenDictionary.ToFrozenDictionary) &&
|
|
||||||
m.GetParameters().Length == 2);
|
|
||||||
|
|
||||||
static object ParseInt(string fieldName, Type fieldType, string value)
|
|
||||||
{
|
{
|
||||||
if (Exts.TryParseInt32Invariant(value, out var res))
|
if (Exts.TryParseInt32Invariant(value, out var res))
|
||||||
{
|
{
|
||||||
@@ -142,48 +129,48 @@ namespace OpenRA
|
|||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseUShort(string fieldName, Type fieldType, string value)
|
static object ParseUShort(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (Exts.TryParseUshortInvariant(value, out var res))
|
if (ushort.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var res))
|
||||||
return res;
|
return res;
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseLong(string fieldName, Type fieldType, string value)
|
static object ParseLong(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (Exts.TryParseInt64Invariant(value, out var res))
|
if (long.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var res))
|
||||||
return res;
|
return res;
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseFloat(string fieldName, Type fieldType, string value)
|
static object ParseFloat(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (Exts.TryParseFloatOrPercentInvariant(value, out var res))
|
if (value != null && float.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
|
||||||
return res;
|
return res * (value.Contains('%') ? 0.01f : 1f);
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseDecimal(string fieldName, Type fieldType, string value)
|
static object ParseDecimal(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (value != null && decimal.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
|
if (value != null && decimal.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
|
||||||
return res * (value.Contains('%') ? 0.01m : 1m);
|
return res * (value.Contains('%') ? 0.01m : 1m);
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseString(string fieldName, Type fieldType, string value)
|
static object ParseString(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseColor(string fieldName, Type fieldType, string value)
|
static object ParseColor(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (Color.TryParse(value, out var color))
|
if (value != null && Color.TryParse(value, out var color))
|
||||||
return color;
|
return color;
|
||||||
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseHotkey(string fieldName, Type fieldType, string value)
|
static object ParseHotkey(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (Hotkey.TryParse(value, out var res))
|
if (Hotkey.TryParse(value, out var res))
|
||||||
return res;
|
return res;
|
||||||
@@ -191,12 +178,12 @@ namespace OpenRA
|
|||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseHotkeyReference(string fieldName, Type fieldType, string value)
|
static object ParseHotkeyReference(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
return Game.ModData.Hotkeys[value];
|
return Game.ModData.Hotkeys[value];
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseWDist(string fieldName, Type fieldType, string value)
|
static object ParseWDist(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (WDist.TryParse(value, out var res))
|
if (WDist.TryParse(value, out var res))
|
||||||
return res;
|
return res;
|
||||||
@@ -204,11 +191,11 @@ namespace OpenRA
|
|||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseWVec(string fieldName, Type fieldType, string value)
|
static object ParseWVec(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
var parts = value.Split(SplitComma);
|
||||||
if (parts.Length == 3
|
if (parts.Length == 3
|
||||||
&& WDist.TryParse(parts[0], out var rx)
|
&& WDist.TryParse(parts[0], out var rx)
|
||||||
&& WDist.TryParse(parts[1], out var ry)
|
&& WDist.TryParse(parts[1], out var ry)
|
||||||
@@ -219,11 +206,11 @@ namespace OpenRA
|
|||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseWVecArray(string fieldName, Type fieldType, string value)
|
static object ParseWVecArray(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
var parts = value.Split(SplitComma);
|
||||||
|
|
||||||
if (parts.Length % 3 != 0)
|
if (parts.Length % 3 != 0)
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
@@ -236,8 +223,6 @@ namespace OpenRA
|
|||||||
&& WDist.TryParse(parts[3 * i + 1], out var ry)
|
&& WDist.TryParse(parts[3 * i + 1], out var ry)
|
||||||
&& WDist.TryParse(parts[3 * i + 2], out var rz))
|
&& WDist.TryParse(parts[3 * i + 2], out var rz))
|
||||||
vecs[i] = new WVec(rx, ry, rz);
|
vecs[i] = new WVec(rx, ry, rz);
|
||||||
else
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return vecs;
|
return vecs;
|
||||||
@@ -246,11 +231,11 @@ namespace OpenRA
|
|||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseWPos(string fieldName, Type fieldType, string value)
|
static object ParseWPos(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
var parts = value.Split(SplitComma);
|
||||||
if (parts.Length == 3
|
if (parts.Length == 3
|
||||||
&& WDist.TryParse(parts[0], out var rx)
|
&& WDist.TryParse(parts[0], out var rx)
|
||||||
&& WDist.TryParse(parts[1], out var ry)
|
&& WDist.TryParse(parts[1], out var ry)
|
||||||
@@ -261,18 +246,18 @@ namespace OpenRA
|
|||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseWAngle(string fieldName, Type fieldType, string value)
|
static object ParseWAngle(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (Exts.TryParseInt32Invariant(value, out var res))
|
if (Exts.TryParseInt32Invariant(value, out var res))
|
||||||
return new WAngle(res);
|
return new WAngle(res);
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseWRot(string fieldName, Type fieldType, string value)
|
static object ParseWRot(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
var parts = value.Split(SplitComma);
|
||||||
if (parts.Length == 3
|
if (parts.Length == 3
|
||||||
&& Exts.TryParseInt32Invariant(parts[0], out var rr)
|
&& Exts.TryParseInt32Invariant(parts[0], out var rr)
|
||||||
&& Exts.TryParseInt32Invariant(parts[1], out var rp)
|
&& Exts.TryParseInt32Invariant(parts[1], out var rp)
|
||||||
@@ -283,31 +268,27 @@ namespace OpenRA
|
|||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseCPos(string fieldName, Type fieldType, string value)
|
static object ParseCPos(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||||
if (parts.Length == 3
|
if (parts.Length == 3)
|
||||||
&& Exts.TryParseInt32Invariant(parts[0], out var x)
|
return new CPos(
|
||||||
&& Exts.TryParseInt32Invariant(parts[1], out var y)
|
Exts.ParseInt32Invariant(parts[0]),
|
||||||
&& Exts.TryParseByteInvariant(parts[2], out var layer))
|
Exts.ParseInt32Invariant(parts[1]),
|
||||||
return new CPos(x, y, layer);
|
Exts.ParseByteInvariant(parts[2]));
|
||||||
|
return new CPos(Exts.ParseInt32Invariant(parts[0]), Exts.ParseInt32Invariant(parts[1]));
|
||||||
if (parts.Length == 2
|
|
||||||
&& Exts.TryParseInt32Invariant(parts[0], out x)
|
|
||||||
&& Exts.TryParseInt32Invariant(parts[1], out y))
|
|
||||||
return new CPos(x, y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseCPosArray(string fieldName, Type fieldType, string value)
|
static object ParseCPosArray(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
var parts = value.Split(SplitComma);
|
||||||
|
|
||||||
if (parts.Length % 2 != 0)
|
if (parts.Length % 2 != 0)
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
@@ -315,11 +296,9 @@ namespace OpenRA
|
|||||||
var vecs = new CPos[parts.Length / 2];
|
var vecs = new CPos[parts.Length / 2];
|
||||||
for (var i = 0; i < vecs.Length; i++)
|
for (var i = 0; i < vecs.Length; i++)
|
||||||
{
|
{
|
||||||
if (Exts.TryParseInt32Invariant(parts[2 * i], out var rx)
|
if (int.TryParse(parts[2 * i], out var rx)
|
||||||
&& Exts.TryParseInt32Invariant(parts[2 * i + 1], out var ry))
|
&& int.TryParse(parts[2 * i + 1], out var ry))
|
||||||
vecs[i] = new CPos(rx, ry);
|
vecs[i] = new CPos(rx, ry);
|
||||||
else
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return vecs;
|
return vecs;
|
||||||
@@ -328,25 +307,22 @@ namespace OpenRA
|
|||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseCVec(string fieldName, Type fieldType, string value)
|
static object ParseCVec(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||||
if (parts.Length == 2
|
return new CVec(Exts.ParseInt32Invariant(parts[0]), Exts.ParseInt32Invariant(parts[1]));
|
||||||
&& Exts.TryParseInt32Invariant(parts[0], out var x)
|
|
||||||
&& Exts.TryParseInt32Invariant(parts[1], out var y))
|
|
||||||
return new CVec(x, y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseCVecArray(string fieldName, Type fieldType, string value)
|
static object ParseCVecArray(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
var parts = value.Split(SplitComma);
|
||||||
|
|
||||||
if (parts.Length % 2 != 0)
|
if (parts.Length % 2 != 0)
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
@@ -354,11 +330,9 @@ namespace OpenRA
|
|||||||
var vecs = new CVec[parts.Length / 2];
|
var vecs = new CVec[parts.Length / 2];
|
||||||
for (var i = 0; i < vecs.Length; i++)
|
for (var i = 0; i < vecs.Length; i++)
|
||||||
{
|
{
|
||||||
if (Exts.TryParseInt32Invariant(parts[2 * i], out var rx)
|
if (int.TryParse(parts[2 * i], out var rx)
|
||||||
&& Exts.TryParseInt32Invariant(parts[2 * i + 1], out var ry))
|
&& int.TryParse(parts[2 * i + 1], out var ry))
|
||||||
vecs[i] = new CVec(rx, ry);
|
vecs[i] = new CVec(rx, ry);
|
||||||
else
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return vecs;
|
return vecs;
|
||||||
@@ -367,7 +341,7 @@ namespace OpenRA
|
|||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseBooleanExpression(string fieldName, Type fieldType, string value)
|
static object ParseBooleanExpression(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
@@ -377,14 +351,14 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
catch (InvalidDataException e)
|
catch (InvalidDataException e)
|
||||||
{
|
{
|
||||||
throw new YamlException($"FieldLoader: Cannot parse `{value}` into `{fieldName}.{fieldType}`: {e.Message}");
|
throw new YamlException(e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseIntegerExpression(string fieldName, Type fieldType, string value)
|
static object ParseIntegerExpression(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
@@ -394,50 +368,44 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
catch (InvalidDataException e)
|
catch (InvalidDataException e)
|
||||||
{
|
{
|
||||||
throw new YamlException($"FieldLoader: Cannot parse `{value}` into `{fieldName}.{fieldType}`: {e.Message}");
|
throw new YamlException(e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseEnum(string fieldName, Type fieldType, string value)
|
static object ParseEnum(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
// Will allow numeric values that fit the underlying type of the enum, even if they aren't defined enumeration members.
|
try
|
||||||
if (Enum.TryParse(fieldType, value, true, out var enumValue))
|
|
||||||
{
|
{
|
||||||
return enumValue;
|
return Enum.Parse(fieldType, value, true);
|
||||||
}
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static object ParseBool(string fieldName, Type fieldType, string value)
|
static object ParseBool(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (bool.TryParse(value, out var result))
|
if (bool.TryParse(value.ToLowerInvariant(), out var result))
|
||||||
return result ? BoxedTrue : BoxedFalse;
|
return result ? BoxedTrue : BoxedFalse;
|
||||||
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseInt2Array(string fieldName, Type fieldType, string value)
|
static object ParseInt2Array(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||||
if (parts.Length % 2 != 0)
|
if (parts.Length % 2 != 0)
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
|
|
||||||
var ints = new int2[parts.Length / 2];
|
var ints = new int2[parts.Length / 2];
|
||||||
|
|
||||||
for (var i = 0; i < ints.Length; i++)
|
for (var i = 0; i < ints.Length; i++)
|
||||||
{
|
ints[i] = new int2(Exts.ParseInt32Invariant(parts[2 * i]), Exts.ParseInt32Invariant(parts[2 * i + 1]));
|
||||||
if (Exts.TryParseInt32Invariant(parts[2 * i], out var x)
|
|
||||||
&& Exts.TryParseInt32Invariant(parts[2 * i + 1], out var y))
|
|
||||||
ints[i] = new int2(x, y);
|
|
||||||
else
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ints;
|
return ints;
|
||||||
}
|
}
|
||||||
@@ -445,130 +413,109 @@ namespace OpenRA
|
|||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseSize(string fieldName, Type fieldType, string value)
|
static object ParseSize(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||||
if (parts.Length == 2
|
return new Size(Exts.ParseInt32Invariant(parts[0]), Exts.ParseInt32Invariant(parts[1]));
|
||||||
&& Exts.TryParseInt32Invariant(parts[0], out var width)
|
|
||||||
&& Exts.TryParseInt32Invariant(parts[1], out var height))
|
|
||||||
return new Size(width, height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseInt2(string fieldName, Type fieldType, string value)
|
static object ParseInt2(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||||
if (parts.Length == 2
|
if (parts.Length != 2)
|
||||||
&& Exts.TryParseInt32Invariant(parts[0], out var x)
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
&& Exts.TryParseInt32Invariant(parts[1], out var y))
|
|
||||||
return new int2(x, y);
|
return new int2(Exts.ParseInt32Invariant(parts[0]), Exts.ParseInt32Invariant(parts[1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseFloat2(string fieldName, Type fieldType, string value)
|
static object ParseFloat2(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||||
if (parts.Length == 2
|
float xx = 0;
|
||||||
&& Exts.TryParseFloatOrPercentInvariant(parts[0], out var x)
|
float yy = 0;
|
||||||
&& Exts.TryParseFloatOrPercentInvariant(parts[1], out var y))
|
if (float.TryParse(parts[0].Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
|
||||||
return new float2(x, y);
|
xx = res * (parts[0].Contains('%') ? 0.01f : 1f);
|
||||||
|
if (float.TryParse(parts[1].Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out res))
|
||||||
|
yy = res * (parts[1].Contains('%') ? 0.01f : 1f);
|
||||||
|
return new float2(xx, yy);
|
||||||
}
|
}
|
||||||
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseFloat3(string fieldName, Type fieldType, string value)
|
static object ParseFloat3(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||||
if (parts.Length == 3
|
float.TryParse(parts[0], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var x);
|
||||||
&& Exts.TryParseFloatOrPercentInvariant(parts[0], out var x)
|
float.TryParse(parts[1], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var y);
|
||||||
&& Exts.TryParseFloatOrPercentInvariant(parts[1], out var y)
|
|
||||||
&& Exts.TryParseFloatOrPercentInvariant(parts[2], out var z))
|
|
||||||
return new float3(x, y, z);
|
|
||||||
|
|
||||||
// z component is optional for compatibility with older float2 definitions
|
// z component is optional for compatibility with older float2 definitions
|
||||||
if (parts.Length == 2
|
float z = 0;
|
||||||
&& Exts.TryParseFloatOrPercentInvariant(parts[0], out x)
|
if (parts.Length > 2)
|
||||||
&& Exts.TryParseFloatOrPercentInvariant(parts[1], out y))
|
float.TryParse(parts[2], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out z);
|
||||||
return new float3(x, y, 0);
|
|
||||||
|
return new float3(x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseRectangle(string fieldName, Type fieldType, string value)
|
static object ParseRectangle(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||||
if (parts.Length == 4
|
return new Rectangle(
|
||||||
&& Exts.TryParseInt32Invariant(parts[0], out var x)
|
Exts.ParseInt32Invariant(parts[0]),
|
||||||
&& Exts.TryParseInt32Invariant(parts[1], out var y)
|
Exts.ParseInt32Invariant(parts[1]),
|
||||||
&& Exts.TryParseInt32Invariant(parts[2], out var width)
|
Exts.ParseInt32Invariant(parts[2]),
|
||||||
&& Exts.TryParseInt32Invariant(parts[3], out var height))
|
Exts.ParseInt32Invariant(parts[3]));
|
||||||
return new Rectangle(x, y, width, height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseDateTime(string fieldName, Type fieldType, string value)
|
static object ParseDateTime(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (DateTime.TryParseExact(value, "yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var dt))
|
if (DateTime.TryParseExact(value, "yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var dt))
|
||||||
return dt;
|
return dt;
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseArray(string fieldName, Type fieldType, string value)
|
static object ParseHashSetOrList(string fieldName, Type fieldType, string value, MiniYaml yaml, MemberInfo field)
|
||||||
{
|
|
||||||
var elementType = fieldType.GetElementType();
|
|
||||||
|
|
||||||
if (value == null)
|
|
||||||
return typeof(Array)
|
|
||||||
.GetMethod(nameof(Array.Empty))
|
|
||||||
.MakeGenericMethod(elementType)
|
|
||||||
.Invoke(null, null);
|
|
||||||
|
|
||||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
|
||||||
|
|
||||||
var ret = Array.CreateInstance(elementType, parts.Length);
|
|
||||||
for (var i = 0; i < parts.Length; i++)
|
|
||||||
ret.SetValue(GetValue(fieldName, elementType, parts[i]), i);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static object ParseHashSetOrList(string fieldName, Type fieldType, string value, MiniYaml yaml)
|
|
||||||
{
|
{
|
||||||
if (value == null)
|
if (value == null)
|
||||||
return Activator.CreateInstance(fieldType);
|
return Activator.CreateInstance(fieldType);
|
||||||
|
|
||||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||||
var set = Activator.CreateInstance(fieldType, parts.Length);
|
var set = Activator.CreateInstance(fieldType, parts.Length);
|
||||||
var arguments = fieldType.GetGenericArguments();
|
var arguments = fieldType.GetGenericArguments();
|
||||||
var addMethod = fieldType.GetMethod(nameof(List<object>.Add), arguments);
|
var addMethod = fieldType.GetMethod(nameof(List<object>.Add), arguments);
|
||||||
var addArgs = new object[1];
|
var addArgs = new object[1];
|
||||||
for (var i = 0; i < parts.Length; i++)
|
for (var i = 0; i < parts.Length; i++)
|
||||||
{
|
{
|
||||||
addArgs[0] = GetValue(fieldName, arguments[0], parts[i]);
|
addArgs[0] = GetValue(fieldName, arguments[0], parts[i].Trim(), field);
|
||||||
addMethod.Invoke(set, addArgs);
|
addMethod.Invoke(set, addArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
return set;
|
return set;
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseDictionary(string fieldName, Type fieldType, string value, MiniYaml yaml)
|
static object ParseDictionary(string fieldName, Type fieldType, string value, MiniYaml yaml, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (yaml == null)
|
if (yaml == null)
|
||||||
return Activator.CreateInstance(fieldType);
|
return Activator.CreateInstance(fieldType);
|
||||||
@@ -579,97 +526,34 @@ namespace OpenRA
|
|||||||
var addArgs = new object[2];
|
var addArgs = new object[2];
|
||||||
foreach (var node in yaml.Nodes)
|
foreach (var node in yaml.Nodes)
|
||||||
{
|
{
|
||||||
addArgs[0] = GetValue(fieldName, arguments[0], node.Key);
|
addArgs[0] = GetValue(fieldName, arguments[0], node.Key, field);
|
||||||
addArgs[1] = GetValue(fieldName, arguments[1], node.Value);
|
addArgs[1] = GetValue(fieldName, arguments[1], node.Value, field);
|
||||||
addMethod.Invoke(dict, addArgs);
|
addMethod.Invoke(dict, addArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
return dict;
|
return dict;
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseImmutableArray(string fieldName, Type fieldType, string value, MiniYaml yaml)
|
static object ParseBitSet(string fieldName, Type fieldType, string value, MiniYaml yaml, MemberInfo field)
|
||||||
{
|
|
||||||
var typeArgs = fieldType.GenericTypeArguments;
|
|
||||||
|
|
||||||
if (value == null)
|
|
||||||
return typeof(ImmutableArray<>).MakeGenericType(typeArgs)
|
|
||||||
.GetField(nameof(ImmutableArray<object>.Empty))
|
|
||||||
.GetValue(null);
|
|
||||||
|
|
||||||
object array;
|
|
||||||
if (typeArgs[0] == typeof(WVec))
|
|
||||||
array = ParseWVecArray(fieldName, typeArgs[0].MakeArrayType(), value);
|
|
||||||
else if (typeArgs[0] == typeof(CPos))
|
|
||||||
array = ParseCPosArray(fieldName, typeArgs[0].MakeArrayType(), value);
|
|
||||||
else if (typeArgs[0] == typeof(CVec))
|
|
||||||
array = ParseCVecArray(fieldName, typeArgs[0].MakeArrayType(), value);
|
|
||||||
else if (typeArgs[0] == typeof(int2))
|
|
||||||
array = ParseInt2Array(fieldName, typeArgs[0].MakeArrayType(), value);
|
|
||||||
else
|
|
||||||
array = ParseArray(fieldName, typeArgs[0].MakeArrayType(), value);
|
|
||||||
|
|
||||||
var toImmutableArray = ToImmutableArray.MakeGenericMethod(typeArgs);
|
|
||||||
|
|
||||||
return toImmutableArray.Invoke(null, [array]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static object ParseFrozenSet(string fieldName, Type fieldType, string value, MiniYaml yaml)
|
|
||||||
{
|
|
||||||
var typeArgs = fieldType.GenericTypeArguments;
|
|
||||||
|
|
||||||
if (value == null)
|
|
||||||
return typeof(FrozenSet<>).MakeGenericType(typeArgs)
|
|
||||||
.GetProperty(nameof(FrozenSet<object>.Empty))
|
|
||||||
.GetValue(null);
|
|
||||||
|
|
||||||
var set =
|
|
||||||
ParseHashSetOrList(fieldName, typeof(HashSet<>).MakeGenericType(typeArgs), value, yaml);
|
|
||||||
|
|
||||||
var toFrozenSet = ToFrozenSet.MakeGenericMethod(typeArgs);
|
|
||||||
|
|
||||||
return toFrozenSet.Invoke(null, [set, null]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static object ParseFrozenDictionary(string fieldName, Type fieldType, string value, MiniYaml yaml)
|
|
||||||
{
|
|
||||||
var typeArgs = fieldType.GenericTypeArguments;
|
|
||||||
|
|
||||||
if (yaml == null)
|
|
||||||
return typeof(FrozenDictionary<,>).MakeGenericType(typeArgs)
|
|
||||||
.GetProperty(nameof(FrozenDictionary<object, object>.Empty))
|
|
||||||
.GetValue(null);
|
|
||||||
|
|
||||||
var dict =
|
|
||||||
ParseDictionary(fieldName, typeof(Dictionary<,>).MakeGenericType(typeArgs), value, yaml);
|
|
||||||
|
|
||||||
var toFrozenDict = ToFrozenDictionary.MakeGenericMethod(typeArgs);
|
|
||||||
|
|
||||||
return toFrozenDict.Invoke(null, [dict, null]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static object ParseBitSet(string fieldName, Type fieldType, string value, MiniYaml yaml)
|
|
||||||
{
|
{
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
var parts = value.Split(Comma, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||||
var ctor = fieldType.GetConstructor([typeof(string[])]);
|
var ctor = fieldType.GetConstructor(new[] { typeof(string[]) });
|
||||||
return ctor.Invoke([parts]);
|
return ctor.Invoke(new object[] { parts.Select(p => p.Trim()).ToArray() });
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var ctor = fieldType.GetConstructor([typeof(string[])]);
|
|
||||||
return ctor.Invoke([Array.Empty<string>()]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static object ParseNullable(string fieldName, Type fieldType, string value, MiniYaml yaml)
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
static object ParseNullable(string fieldName, Type fieldType, string value, MiniYaml yaml, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(value))
|
if (string.IsNullOrEmpty(value))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var innerType = fieldType.GetGenericArguments()[0];
|
var innerType = fieldType.GetGenericArguments()[0];
|
||||||
var innerValue = GetValue("Nullable<T>", innerType, value);
|
var innerValue = GetValue("Nullable<T>", innerType, value, field);
|
||||||
return fieldType.GetConstructor([innerType]).Invoke([innerValue]);
|
return fieldType.GetConstructor(new[] { innerType }).Invoke(new[] { innerValue });
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Load(object self, MiniYaml my)
|
public static void Load(object self, MiniYaml my)
|
||||||
@@ -690,7 +574,7 @@ namespace OpenRA
|
|||||||
val = fli.Loader(my);
|
val = fli.Loader(my);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
missing ??= [];
|
missing ??= new List<string>();
|
||||||
missing.Add(fli.YamlName);
|
missing.Add(fli.YamlName);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -701,7 +585,7 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
if (fli.Attribute.Required)
|
if (fli.Attribute.Required)
|
||||||
{
|
{
|
||||||
missing ??= [];
|
missing ??= new List<string>();
|
||||||
missing.Add(fli.YamlName);
|
missing.Add(fli.YamlName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -723,7 +607,7 @@ namespace OpenRA
|
|||||||
if (!md.TryGetValue(yamlName, out var yaml))
|
if (!md.TryGetValue(yamlName, out var yaml))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
ret = GetValue(field.Name, field.FieldType, yaml);
|
ret = GetValue(field.Name, field.FieldType, yaml, field);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -734,7 +618,7 @@ namespace OpenRA
|
|||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void LoadFieldOrProperty(object target, string key, string value)
|
public static void LoadField(object target, string key, string value)
|
||||||
{
|
{
|
||||||
const BindingFlags Flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
const BindingFlags Flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
||||||
|
|
||||||
@@ -743,14 +627,18 @@ namespace OpenRA
|
|||||||
var field = target.GetType().GetField(key, Flags);
|
var field = target.GetType().GetField(key, Flags);
|
||||||
if (field != null)
|
if (field != null)
|
||||||
{
|
{
|
||||||
field.SetValue(target, GetValue(field.Name, field.FieldType, value));
|
var sa = field.GetCustomAttributes<SerializeAttribute>(false).DefaultIfEmpty(SerializeAttribute.Default).First();
|
||||||
|
if (!sa.FromYamlKey)
|
||||||
|
field.SetValue(target, GetValue(field.Name, field.FieldType, value, field));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var prop = target.GetType().GetProperty(key, Flags);
|
var prop = target.GetType().GetProperty(key, Flags);
|
||||||
if (prop != null)
|
if (prop != null)
|
||||||
{
|
{
|
||||||
prop.SetValue(target, GetValue(prop.Name, prop.PropertyType, value), null);
|
var sa = prop.GetCustomAttributes<SerializeAttribute>(false).DefaultIfEmpty(SerializeAttribute.Default).First();
|
||||||
|
if (!sa.FromYamlKey)
|
||||||
|
prop.SetValue(target, GetValue(prop.Name, prop.PropertyType, value, prop), null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -762,34 +650,48 @@ namespace OpenRA
|
|||||||
return (T)GetValue(field, typeof(T), value, null);
|
return (T)GetValue(field, typeof(T), value, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object GetValue(string fieldName, Type fieldType, string value)
|
public static object GetValue(string fieldName, Type fieldType, string value)
|
||||||
{
|
{
|
||||||
return GetValue(fieldName, fieldType, value, null);
|
return GetValue(fieldName, fieldType, value, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object GetValue(string fieldName, Type fieldType, MiniYaml yaml)
|
public static object GetValue(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
return GetValue(fieldName, fieldType, yaml.Value, yaml);
|
return GetValue(fieldName, fieldType, value, null, field);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object GetValue(string fieldName, Type fieldType, string value, MiniYaml yaml)
|
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();
|
value = value?.Trim();
|
||||||
if (fieldType.IsGenericType)
|
if (fieldType.IsGenericType)
|
||||||
{
|
{
|
||||||
if (GenericTypeParsers.TryGetValue(fieldType.GetGenericTypeDefinition(), out var parseFuncGeneric))
|
if (GenericTypeParsers.TryGetValue(fieldType.GetGenericTypeDefinition(), out var parseFuncGeneric))
|
||||||
return parseFuncGeneric(fieldName, fieldType, value, yaml);
|
return parseFuncGeneric(fieldName, fieldType, value, yaml, field);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (TypeParsers.TryGetValue(fieldType, out var parseFunc))
|
if (TypeParsers.TryGetValue(fieldType, out var parseFunc))
|
||||||
return parseFunc(fieldName, fieldType, value);
|
return parseFunc(fieldName, fieldType, value, field);
|
||||||
|
|
||||||
if (fieldType.IsArray && fieldType.GetArrayRank() == 1)
|
if (fieldType.IsArray && fieldType.GetArrayRank() == 1)
|
||||||
return ParseArray(fieldName, fieldType, value);
|
{
|
||||||
|
if (value == null)
|
||||||
|
return Array.CreateInstance(fieldType.GetElementType(), 0);
|
||||||
|
|
||||||
if (fieldType.IsEnum)
|
var options = field != null && field.HasAttribute<AllowEmptyEntriesAttribute>() ?
|
||||||
return ParseEnum(fieldName, fieldType, value);
|
StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries;
|
||||||
|
var parts = value.Split(SplitComma, options);
|
||||||
|
|
||||||
|
var ret = Array.CreateInstance(fieldType.GetElementType(), parts.Length);
|
||||||
|
for (var i = 0; i < parts.Length; i++)
|
||||||
|
ret.SetValue(GetValue(fieldName, fieldType.GetElementType(), parts[i].Trim(), field), i);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var conv = TypeDescriptor.GetConverter(fieldType);
|
var conv = TypeDescriptor.GetConverter(fieldType);
|
||||||
@@ -813,20 +715,21 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
public readonly FieldInfo Field;
|
public readonly FieldInfo Field;
|
||||||
public readonly SerializeAttribute Attribute;
|
public readonly SerializeAttribute Attribute;
|
||||||
|
public readonly string YamlName;
|
||||||
public readonly Func<MiniYaml, object> Loader;
|
public readonly Func<MiniYaml, object> Loader;
|
||||||
public string YamlName => Field.Name;
|
|
||||||
|
|
||||||
public FieldLoadInfo(FieldInfo field, SerializeAttribute attr, Func<MiniYaml, object> loader = null)
|
internal FieldLoadInfo(FieldInfo field, SerializeAttribute attr, string yamlName, Func<MiniYaml, object> loader = null)
|
||||||
{
|
{
|
||||||
Field = field;
|
Field = field;
|
||||||
Attribute = attr;
|
Attribute = attr;
|
||||||
|
YamlName = yamlName;
|
||||||
Loader = loader;
|
Loader = loader;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<FieldLoadInfo> GetTypeLoadInfo(Type type)
|
public static IEnumerable<FieldLoadInfo> GetTypeLoadInfo(Type type, bool includePrivateByDefault = false)
|
||||||
{
|
{
|
||||||
return TypeLoadInfo[type].Where(fli => fli.Field.IsPublic || (fli.Attribute.Serialize && !fli.Attribute.IsDefault));
|
return TypeLoadInfo[type].Where(fli => includePrivateByDefault || fli.Field.IsPublic || (fli.Attribute.Serialize && !fli.Attribute.IsDefault));
|
||||||
}
|
}
|
||||||
|
|
||||||
static FieldLoadInfo[] BuildTypeLoadInfo(Type type)
|
static FieldLoadInfo[] BuildTypeLoadInfo(Type type)
|
||||||
@@ -841,9 +744,13 @@ namespace OpenRA
|
|||||||
if (!sa.Serialize)
|
if (!sa.Serialize)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var loader = sa.GetLoader(type);
|
var yamlName = string.IsNullOrEmpty(sa.YamlName) ? field.Name : sa.YamlName;
|
||||||
|
|
||||||
var fli = new FieldLoadInfo(field, sa, loader);
|
var loader = sa.GetLoader(type);
|
||||||
|
if (loader == null && sa.FromYamlKey)
|
||||||
|
loader = yaml => GetValue(yamlName, field.FieldType, yaml, field);
|
||||||
|
|
||||||
|
var fli = new FieldLoadInfo(field, sa, yamlName, loader);
|
||||||
ret.Add(fli);
|
ret.Add(fli);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -854,21 +761,31 @@ namespace OpenRA
|
|||||||
public sealed class IgnoreAttribute : SerializeAttribute
|
public sealed class IgnoreAttribute : SerializeAttribute
|
||||||
{
|
{
|
||||||
public IgnoreAttribute()
|
public IgnoreAttribute()
|
||||||
: base(serialize: false) { }
|
: base(false) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Field)]
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
public sealed class RequireAttribute : SerializeAttribute
|
public sealed class RequireAttribute : SerializeAttribute
|
||||||
{
|
{
|
||||||
public RequireAttribute()
|
public RequireAttribute()
|
||||||
: base(serialize: true, required: true) { }
|
: base(true, true) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public sealed class AllowEmptyEntriesAttribute : SerializeAttribute
|
||||||
|
{
|
||||||
|
public AllowEmptyEntriesAttribute()
|
||||||
|
: base(allowEmptyEntries: true) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Field)]
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
public sealed class LoadUsingAttribute : SerializeAttribute
|
public sealed class LoadUsingAttribute : SerializeAttribute
|
||||||
{
|
{
|
||||||
public LoadUsingAttribute(string loader, bool required = false)
|
public LoadUsingAttribute(string loader, bool required = false)
|
||||||
: base(serialize: true, required, loader) { }
|
{
|
||||||
|
Loader = loader;
|
||||||
|
Required = required;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Field)]
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
@@ -879,14 +796,18 @@ namespace OpenRA
|
|||||||
public bool IsDefault => this == Default;
|
public bool IsDefault => this == Default;
|
||||||
|
|
||||||
public readonly bool Serialize;
|
public readonly bool Serialize;
|
||||||
public readonly bool Required;
|
public string YamlName;
|
||||||
public readonly string Loader;
|
public string Loader;
|
||||||
|
public bool FromYamlKey;
|
||||||
|
public bool DictionaryFromYamlKey;
|
||||||
|
public bool Required;
|
||||||
|
public bool AllowEmptyEntries;
|
||||||
|
|
||||||
protected SerializeAttribute(bool serialize = true, bool required = false, string loader = null)
|
public SerializeAttribute(bool serialize = true, bool required = false, bool allowEmptyEntries = false)
|
||||||
{
|
{
|
||||||
Serialize = serialize;
|
Serialize = serialize;
|
||||||
Required = required;
|
Required = required;
|
||||||
Loader = loader;
|
AllowEmptyEntries = allowEmptyEntries;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Func<MiniYaml, object> GetLoader(Type type)
|
internal Func<MiniYaml, object> GetLoader(Type type)
|
||||||
@@ -906,4 +827,32 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public sealed class FieldFromYamlKeyAttribute : FieldLoader.SerializeAttribute
|
||||||
|
{
|
||||||
|
public FieldFromYamlKeyAttribute()
|
||||||
|
{
|
||||||
|
FromYamlKey = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special-cases FieldFromYamlKeyAttribute for use with Dictionary<K,V>.
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public sealed class DictionaryFromYamlKeyAttribute : FieldLoader.SerializeAttribute
|
||||||
|
{
|
||||||
|
public DictionaryFromYamlKeyAttribute()
|
||||||
|
{
|
||||||
|
FromYamlKey = true;
|
||||||
|
DictionaryFromYamlKey = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mirrors DescriptionAttribute from System.ComponentModel but we don't want to have to use that everywhere.
|
||||||
|
[AttributeUsage(AttributeTargets.All)]
|
||||||
|
public sealed class DescAttribute : Attribute
|
||||||
|
{
|
||||||
|
public readonly string[] Lines;
|
||||||
|
public DescAttribute(params string[] lines) { Lines = lines; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,7 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Frozen;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -24,38 +22,39 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
public static class FieldSaver
|
public static class FieldSaver
|
||||||
{
|
{
|
||||||
public static MiniYaml Save(object o)
|
public static MiniYaml Save(object o, bool includePrivateByDefault = false)
|
||||||
{
|
{
|
||||||
var nodes = new List<MiniYamlNode>();
|
var nodes = new List<MiniYamlNode>();
|
||||||
|
string root = null;
|
||||||
|
|
||||||
foreach (var fieldInfo in FieldLoader.GetTypeLoadInfo(o.GetType()))
|
foreach (var info in FieldLoader.GetTypeLoadInfo(o.GetType(), includePrivateByDefault))
|
||||||
{
|
{
|
||||||
if (fieldInfo.Field.FieldType.IsGenericType && fieldInfo.Field.FieldType.IsAssignableTo(typeof(System.Collections.IDictionary)))
|
if (info.Attribute.DictionaryFromYamlKey)
|
||||||
{
|
{
|
||||||
var dict = (System.Collections.IDictionary)fieldInfo.Field.GetValue(o);
|
var dict = (System.Collections.IDictionary)info.Field.GetValue(o);
|
||||||
var dictNodes = new List<MiniYamlNode>();
|
|
||||||
foreach (var kvp in dict)
|
foreach (var kvp in dict)
|
||||||
{
|
{
|
||||||
var key = ((System.Collections.DictionaryEntry)kvp).Key;
|
var key = ((System.Collections.DictionaryEntry)kvp).Key;
|
||||||
var value = ((System.Collections.DictionaryEntry)kvp).Value;
|
var value = ((System.Collections.DictionaryEntry)kvp).Value;
|
||||||
dictNodes.Add(new MiniYamlNode(FormatValue(key), FormatValue(value)));
|
|
||||||
}
|
|
||||||
|
|
||||||
nodes.Add(new MiniYamlNode(fieldInfo.YamlName, "", dictNodes));
|
nodes.Add(new MiniYamlNode(FormatValue(key), FormatValue(value)));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else if (info.Attribute.FromYamlKey)
|
||||||
|
root = FormatValue(o, info.Field);
|
||||||
else
|
else
|
||||||
nodes.Add(new MiniYamlNode(fieldInfo.YamlName, FormatValue(o, fieldInfo.Field)));
|
nodes.Add(new MiniYamlNode(info.YamlName, FormatValue(o, info.Field)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MiniYaml(null, nodes);
|
return new MiniYaml(root, nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MiniYaml SaveDifferences(object o, object from)
|
public static MiniYaml SaveDifferences(object o, object from, bool includePrivateByDefault = false)
|
||||||
{
|
{
|
||||||
if (o.GetType() != from.GetType())
|
if (o.GetType() != from.GetType())
|
||||||
throw new InvalidOperationException("FieldSaver: can't diff objects of different types");
|
throw new InvalidOperationException("FieldLoader: can't diff objects of different types");
|
||||||
|
|
||||||
var fields = FieldLoader.GetTypeLoadInfo(o.GetType())
|
var fields = FieldLoader.GetTypeLoadInfo(o.GetType(), includePrivateByDefault)
|
||||||
.Where(info => FormatValue(o, info.Field) != FormatValue(from, info.Field));
|
.Where(info => FormatValue(o, info.Field) != FormatValue(from, info.Field));
|
||||||
|
|
||||||
return new MiniYaml(
|
return new MiniYaml(
|
||||||
@@ -80,35 +79,11 @@ namespace OpenRA
|
|||||||
if (t.IsArray && t.GetArrayRank() == 1)
|
if (t.IsArray && t.GetArrayRank() == 1)
|
||||||
return ((Array)v).Cast<object>().Select(FormatValue).JoinWith(", ");
|
return ((Array)v).Cast<object>().Select(FormatValue).JoinWith(", ");
|
||||||
|
|
||||||
if (t.IsGenericType &&
|
if (t.IsGenericType && (t.GetGenericTypeDefinition() == typeof(HashSet<>) || t.GetGenericTypeDefinition() == typeof(List<>)))
|
||||||
t.GetGenericTypeDefinition() == typeof(ImmutableArray<>))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return ((System.Collections.IEnumerable)v).Cast<object>().Select(FormatValue).JoinWith(", ");
|
|
||||||
}
|
|
||||||
catch (InvalidOperationException)
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (t.IsGenericType &&
|
|
||||||
(t.GetGenericTypeDefinition() == typeof(List<>) ||
|
|
||||||
t.GetGenericTypeDefinition() == typeof(HashSet<>) ||
|
|
||||||
t.GetGenericTypeDefinition()
|
|
||||||
.BaseTypes()
|
|
||||||
.Select(bt => bt.IsGenericType ? bt.GetGenericTypeDefinition() : null)
|
|
||||||
.Any(bt => bt == typeof(FrozenSet<>))))
|
|
||||||
return ((System.Collections.IEnumerable)v).Cast<object>().Select(FormatValue).JoinWith(", ");
|
return ((System.Collections.IEnumerable)v).Cast<object>().Select(FormatValue).JoinWith(", ");
|
||||||
|
|
||||||
// This is only for documentation generation
|
// This is only for documentation generation
|
||||||
if (t.IsGenericType &&
|
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
||||||
(t.GetGenericTypeDefinition() == typeof(Dictionary<,>) ||
|
|
||||||
t.GetGenericTypeDefinition()
|
|
||||||
.BaseTypes()
|
|
||||||
.Select(bt => bt.IsGenericType ? bt.GetGenericTypeDefinition() : null)
|
|
||||||
.Any(bt => bt == typeof(FrozenDictionary<,>))))
|
|
||||||
{
|
{
|
||||||
var result = new StringBuilder();
|
var result = new StringBuilder();
|
||||||
var dict = (System.Collections.IDictionary)v;
|
var dict = (System.Collections.IDictionary)v;
|
||||||
@@ -120,7 +95,7 @@ namespace OpenRA
|
|||||||
var formattedKey = FormatValue(key);
|
var formattedKey = FormatValue(key);
|
||||||
var formattedValue = FormatValue(value);
|
var formattedValue = FormatValue(value);
|
||||||
|
|
||||||
result.Append(CultureInfo.InvariantCulture, $"{formattedKey}: {formattedValue}{Environment.NewLine}");
|
result.Append($"{formattedKey}: {formattedValue}{Environment.NewLine}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.ToString();
|
return result.ToString();
|
||||||
|
|||||||
@@ -10,105 +10,29 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
|
||||||
using System.Buffers.Binary;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using ICSharpCode.SharpZipLib.Checksum;
|
||||||
|
using ICSharpCode.SharpZipLib.Zip.Compression;
|
||||||
|
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
||||||
using OpenRA.Graphics;
|
using OpenRA.Graphics;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
|
|
||||||
namespace OpenRA.FileFormats
|
namespace OpenRA.FileFormats
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Used to connect several IDAT chunks into a continuous stream.
|
|
||||||
/// </summary>
|
|
||||||
sealed class PngIdatStream : Stream
|
|
||||||
{
|
|
||||||
readonly Stream baseStream;
|
|
||||||
int remainingInChunk;
|
|
||||||
bool eof;
|
|
||||||
|
|
||||||
public PngIdatStream(Stream baseStream, int initialLen)
|
|
||||||
{
|
|
||||||
this.baseStream = baseStream;
|
|
||||||
remainingInChunk = initialLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int Read(byte[] buffer, int offset, int count) => Read(buffer.AsSpan(offset, count));
|
|
||||||
|
|
||||||
public override int Read(Span<byte> buffer)
|
|
||||||
{
|
|
||||||
if (eof || buffer.Length == 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
var totalRead = 0;
|
|
||||||
Span<byte> header = stackalloc byte[8];
|
|
||||||
while (buffer.Length > 0)
|
|
||||||
{
|
|
||||||
if (remainingInChunk == 0)
|
|
||||||
{
|
|
||||||
// Skip CRC and read next chunk header.
|
|
||||||
var r = baseStream.Seek(4, SeekOrigin.Current) + baseStream.Read(header);
|
|
||||||
if (r < 12)
|
|
||||||
throw new EndOfStreamException("Invalid PNG file - no end chunk found.");
|
|
||||||
|
|
||||||
// The PNG spec states that IDAT chunks must be chained together.
|
|
||||||
if (BinaryPrimitives.ReadUInt32BigEndian(header[4..]) == Png.ChunkIDAT)
|
|
||||||
remainingInChunk = BinaryPrimitives.ReadInt32BigEndian(header[..4]);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// This is not an IDAT chunk. Discontinue reading.
|
|
||||||
baseStream.Seek(-8, SeekOrigin.Current);
|
|
||||||
eof = true;
|
|
||||||
return totalRead;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var toRead = Math.Min(buffer.Length, remainingInChunk);
|
|
||||||
var read = baseStream.Read(buffer[..toRead]);
|
|
||||||
if (read == 0)
|
|
||||||
throw new EndOfStreamException("Unexpected end of stream in IDAT chunk.");
|
|
||||||
|
|
||||||
remainingInChunk -= read;
|
|
||||||
totalRead += read;
|
|
||||||
buffer = buffer[read..];
|
|
||||||
}
|
|
||||||
|
|
||||||
return totalRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool CanRead => true;
|
|
||||||
public override bool CanSeek => false;
|
|
||||||
public override bool CanWrite => false;
|
|
||||||
public override long Length => throw new NotSupportedException();
|
|
||||||
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
|
|
||||||
public override void Flush() { }
|
|
||||||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
|
||||||
public override void SetLength(long value) => throw new NotSupportedException();
|
|
||||||
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Png
|
public class Png
|
||||||
{
|
{
|
||||||
public const uint ChunkIHDR = 0x49484452;
|
static readonly byte[] Signature = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
|
||||||
public const uint ChunkPLTE = 0x504C5445;
|
|
||||||
public const uint ChunkIDAT = 0x49444154;
|
|
||||||
public const uint ChunkIEND = 0x49454E44;
|
|
||||||
public const uint ChunkTRNS = 0x74524E53;
|
|
||||||
public const uint ChunkTEXT = 0x74455874;
|
|
||||||
|
|
||||||
static readonly byte[] Signature = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
|
|
||||||
|
|
||||||
public int Width { get; }
|
public int Width { get; }
|
||||||
public int Height { get; }
|
public int Height { get; }
|
||||||
public Color[] Palette { get; }
|
public Color[] Palette { get; }
|
||||||
public byte[] Data { get; }
|
public byte[] Data { get; }
|
||||||
public SpriteFrameType Type { get; }
|
public SpriteFrameType Type { get; }
|
||||||
public Dictionary<string, string> EmbeddedData = [];
|
public Dictionary<string, string> EmbeddedData = new();
|
||||||
|
|
||||||
public int PixelStride => Type == SpriteFrameType.Indexed8 ? 1 : Type == SpriteFrameType.Rgb24 ? 3 : 4;
|
public int PixelStride => Type == SpriteFrameType.Indexed8 ? 1 : Type == SpriteFrameType.Rgb24 ? 3 : 4;
|
||||||
|
|
||||||
@@ -119,50 +43,43 @@ namespace OpenRA.FileFormats
|
|||||||
|
|
||||||
s.Position += 8;
|
s.Position += 8;
|
||||||
var headerParsed = false;
|
var headerParsed = false;
|
||||||
var dataParsed = false;
|
var data = new List<byte>();
|
||||||
byte bitDepth = 8;
|
|
||||||
Type = SpriteFrameType.Rgba32;
|
Type = SpriteFrameType.Rgba32;
|
||||||
|
|
||||||
// Use a reusable 8-byte buffer for Length + Type.
|
byte bitDepth = 8;
|
||||||
Span<byte> chunkHeader = stackalloc byte[8];
|
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (s.Read(chunkHeader) < 8)
|
var length = IPAddress.NetworkToHostOrder(s.ReadInt32());
|
||||||
throw new EndOfStreamException("Invalid PNG file - no end chunk found.");
|
var type = s.ReadASCII(4);
|
||||||
|
var content = s.ReadBytes(length);
|
||||||
|
s.ReadInt32(); // crc
|
||||||
|
|
||||||
var length = BinaryPrimitives.ReadInt32BigEndian(chunkHeader[..4]);
|
if (!headerParsed && type != "IHDR")
|
||||||
var type = BinaryPrimitives.ReadUInt32BigEndian(chunkHeader[4..]);
|
|
||||||
|
|
||||||
if (!headerParsed && type != ChunkIHDR)
|
|
||||||
throw new InvalidDataException("Invalid PNG file - header does not appear first.");
|
throw new InvalidDataException("Invalid PNG file - header does not appear first.");
|
||||||
|
|
||||||
|
using (var ms = new MemoryStream(content))
|
||||||
|
{
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case ChunkIHDR:
|
case "IHDR":
|
||||||
{
|
{
|
||||||
if (headerParsed)
|
if (headerParsed)
|
||||||
throw new InvalidDataException("Invalid PNG file - duplicate header.");
|
throw new InvalidDataException("Invalid PNG file - duplicate header.");
|
||||||
|
Width = IPAddress.NetworkToHostOrder(ms.ReadInt32());
|
||||||
|
Height = IPAddress.NetworkToHostOrder(ms.ReadInt32());
|
||||||
|
|
||||||
#pragma warning disable CA2014 // Do not use stackalloc in loops
|
bitDepth = ms.ReadUInt8();
|
||||||
Span<byte> buffer = stackalloc byte[13];
|
var colorType = (PngColorType)ms.ReadUInt8();
|
||||||
#pragma warning restore CA2014 // Do not use stackalloc in loops
|
|
||||||
s.ReadBytes(buffer);
|
|
||||||
|
|
||||||
Width = BinaryPrimitives.ReadInt32BigEndian(buffer[..4]);
|
|
||||||
Height = BinaryPrimitives.ReadInt32BigEndian(buffer[4..8]);
|
|
||||||
bitDepth = buffer[8];
|
|
||||||
var colorType = (PngColorType)buffer[9];
|
|
||||||
|
|
||||||
if (IsPaletted(bitDepth, colorType))
|
if (IsPaletted(bitDepth, colorType))
|
||||||
Type = SpriteFrameType.Indexed8;
|
Type = SpriteFrameType.Indexed8;
|
||||||
else if (colorType == PngColorType.Color)
|
else if (colorType == PngColorType.Color)
|
||||||
Type = SpriteFrameType.Rgb24;
|
Type = SpriteFrameType.Rgb24;
|
||||||
|
|
||||||
var compression = buffer[10];
|
Data = new byte[Width * Height * PixelStride];
|
||||||
|
|
||||||
// filter = buffer[11]
|
var compression = ms.ReadUInt8();
|
||||||
var interlace = buffer[12];
|
ms.ReadUInt8(); // filter
|
||||||
|
var interlace = ms.ReadUInt8();
|
||||||
|
|
||||||
if (compression != 0)
|
if (compression != 0)
|
||||||
throw new InvalidDataException("Compression method not supported");
|
throw new InvalidDataException("Compression method not supported");
|
||||||
@@ -170,156 +87,73 @@ namespace OpenRA.FileFormats
|
|||||||
if (interlace != 0)
|
if (interlace != 0)
|
||||||
throw new InvalidDataException("Interlacing not supported");
|
throw new InvalidDataException("Interlacing not supported");
|
||||||
|
|
||||||
Data = new byte[Width * Height * PixelStride];
|
|
||||||
|
|
||||||
// Skip CRC.
|
|
||||||
s.Seek(4, SeekOrigin.Current);
|
|
||||||
headerParsed = true;
|
headerParsed = true;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case ChunkPLTE:
|
case "PLTE":
|
||||||
{
|
{
|
||||||
if (length % 3 != 0)
|
Palette = new Color[length / 3];
|
||||||
throw new InvalidDataException("Invalid PLTE chunk length.");
|
for (var i = 0; i < Palette.Length; i++)
|
||||||
|
|
||||||
var count = length / 3;
|
|
||||||
Palette = new Color[count];
|
|
||||||
|
|
||||||
var buffer = ArrayPool<byte>.Shared.Rent(length);
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
s.ReadBytes(buffer.AsSpan(0, length));
|
var r = ms.ReadUInt8(); var g = ms.ReadUInt8(); var b = ms.ReadUInt8();
|
||||||
for (var i = 0; i < count; i++)
|
Palette[i] = Color.FromArgb(r, g, b);
|
||||||
{
|
|
||||||
var offset = i * 3;
|
|
||||||
Palette[i] = Color.FromArgb(buffer[offset], buffer[offset + 1], buffer[offset + 2]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ArrayPool<byte>.Shared.Return(buffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip CRC.
|
|
||||||
s.Seek(4, SeekOrigin.Current);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case ChunkTRNS:
|
case "tRNS":
|
||||||
{
|
{
|
||||||
if (Palette == null)
|
if (Palette == null)
|
||||||
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
|
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
|
||||||
|
|
||||||
var buffer = ArrayPool<byte>.Shared.Rent(length);
|
for (var i = 0; i < length; i++)
|
||||||
try
|
Palette[i] = Color.FromArgb(ms.ReadUInt8(), Palette[i]);
|
||||||
{
|
|
||||||
s.ReadBytes(buffer.AsSpan(0, length));
|
|
||||||
for (var i = 0; i < length && i < Palette.Length; i++)
|
|
||||||
Palette[i] = Color.FromArgb(buffer[i], Palette[i]);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ArrayPool<byte>.Shared.Return(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip CRC.
|
|
||||||
s.Seek(4, SeekOrigin.Current);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case ChunkIDAT:
|
case "IDAT":
|
||||||
if (dataParsed)
|
|
||||||
throw new InvalidDataException("Invalid PNG file - discontinuous IDAT chunks.");
|
|
||||||
|
|
||||||
dataParsed = true;
|
|
||||||
|
|
||||||
ProcessIDAT(s, length, bitDepth);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ChunkTEXT:
|
|
||||||
{
|
{
|
||||||
var buffer = ArrayPool<byte>.Shared.Rent(length);
|
data.AddRange(content);
|
||||||
try
|
|
||||||
{
|
|
||||||
var span = buffer.AsSpan(0, length);
|
|
||||||
s.ReadBytes(span);
|
|
||||||
|
|
||||||
// Find Null Terminator for ASCIIZ (Keyword).
|
|
||||||
var nullIndex = span.IndexOf((byte)0);
|
|
||||||
if (nullIndex == -1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var key = Encoding.ASCII.GetString(span[..nullIndex]);
|
|
||||||
var value = Encoding.ASCII.GetString(span[(nullIndex + 1)..]);
|
|
||||||
|
|
||||||
EmbeddedData[key] = value;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ArrayPool<byte>.Shared.Return(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip CRC.
|
|
||||||
s.Seek(4, SeekOrigin.Current);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case ChunkIEND:
|
case "tEXt":
|
||||||
{
|
{
|
||||||
if (Type == SpriteFrameType.Indexed8 && Palette == null)
|
var key = ms.ReadASCIIZ();
|
||||||
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
|
EmbeddedData.Add(key, ms.ReadASCII(length - key.Length - 1));
|
||||||
|
|
||||||
// Skip CRC.
|
|
||||||
s.Seek(4, SeekOrigin.Current);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
// Skip unknown chunk + CRC.
|
|
||||||
s.Seek(length + 4, SeekOrigin.Current);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProcessIDAT(Stream s, int firstChunkLength, byte bitDepth)
|
case "IEND":
|
||||||
|
{
|
||||||
|
using (var ns = new MemoryStream(data.ToArray()))
|
||||||
|
{
|
||||||
|
using (var ds = new InflaterInputStream(ns))
|
||||||
{
|
{
|
||||||
var pxStride = PixelStride;
|
var pxStride = PixelStride;
|
||||||
var rowStride = Width * pxStride;
|
var rowStride = Width * pxStride;
|
||||||
var pixelsPerByte = 8 / bitDepth;
|
var pixelsPerByte = 8 / bitDepth;
|
||||||
var sourceRowStride = Exts.IntegerDivisionRoundingAwayFromZero(rowStride, pixelsPerByte);
|
var sourceRowStride = Exts.IntegerDivisionRoundingAwayFromZero(rowStride, pixelsPerByte);
|
||||||
|
|
||||||
// Custom stream that stitches IDAT chunks together without copying.
|
Span<byte> prevLine = new byte[rowStride];
|
||||||
using var idatStream = new PngIdatStream(s, firstChunkLength);
|
|
||||||
using var ds = new ZLibStream(idatStream, CompressionMode.Decompress);
|
|
||||||
|
|
||||||
// Rent buffer from pool to avoid allocation churn.
|
|
||||||
var prevLineBuffer = ArrayPool<byte>.Shared.Rent(rowStride);
|
|
||||||
var prevLine = prevLineBuffer.AsSpan(0, rowStride);
|
|
||||||
prevLine.Clear();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
for (var y = 0; y < Height; y++)
|
for (var y = 0; y < Height; y++)
|
||||||
{
|
{
|
||||||
var filterByte = ds.ReadByte();
|
var filter = (PngFilter)ds.ReadUInt8();
|
||||||
if (filterByte == -1)
|
ds.ReadBytes(Data, y * rowStride, sourceRowStride);
|
||||||
break;
|
|
||||||
|
|
||||||
var filter = (PngFilter)filterByte;
|
|
||||||
var line = Data.AsSpan(y * rowStride, rowStride);
|
var line = Data.AsSpan(y * rowStride, rowStride);
|
||||||
|
|
||||||
ds.ReadBytes(line[..sourceRowStride]);
|
|
||||||
|
|
||||||
// If the source has a bit depth of 1, 2 or 4 it packs multiple pixels per byte.
|
// 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.
|
// Unpack to bit depth of 8, yielding 1 pixel per byte.
|
||||||
// This makes life easier for consumers of palleted data.
|
// This makes life easier for consumers of palleted data.
|
||||||
if (bitDepth < 8)
|
if (bitDepth < 8)
|
||||||
{
|
{
|
||||||
var mask = (1 << bitDepth) - 1;
|
var mask = 0xFF >> (8 - bitDepth);
|
||||||
for (var i = sourceRowStride - 1; i >= 0; i--)
|
for (var i = sourceRowStride - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
var packed = line[i];
|
var packed = line[i];
|
||||||
@@ -327,7 +161,7 @@ namespace OpenRA.FileFormats
|
|||||||
{
|
{
|
||||||
var dest = i * pixelsPerByte + j;
|
var dest = i * pixelsPerByte + j;
|
||||||
if (dest < line.Length) // Guard against last byte being only partially packed
|
if (dest < line.Length) // Guard against last byte being only partially packed
|
||||||
line[dest] = (byte)((packed >> (8 - (j + 1) * bitDepth)) & mask);
|
line[dest] = (byte)(packed >> (8 - (j + 1) * bitDepth) & mask);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -337,23 +171,23 @@ namespace OpenRA.FileFormats
|
|||||||
case PngFilter.None:
|
case PngFilter.None:
|
||||||
break;
|
break;
|
||||||
case PngFilter.Sub:
|
case PngFilter.Sub:
|
||||||
for (var i = pxStride; i < line.Length; i++)
|
for (var i = pxStride; i < rowStride; i++)
|
||||||
line[i] += line[i - pxStride];
|
line[i] += line[i - pxStride];
|
||||||
break;
|
break;
|
||||||
case PngFilter.Up:
|
case PngFilter.Up:
|
||||||
for (var i = 0; i < line.Length; i++)
|
for (var i = 0; i < rowStride; i++)
|
||||||
line[i] += prevLine[i];
|
line[i] += prevLine[i];
|
||||||
break;
|
break;
|
||||||
case PngFilter.Average:
|
case PngFilter.Average:
|
||||||
for (var i = 0; i < pxStride; i++)
|
for (var i = 0; i < pxStride; i++)
|
||||||
line[i] += Average(0, prevLine[i]);
|
line[i] += Average(0, prevLine[i]);
|
||||||
for (var i = pxStride; i < line.Length; i++)
|
for (var i = pxStride; i < rowStride; i++)
|
||||||
line[i] += Average(line[i - pxStride], prevLine[i]);
|
line[i] += Average(line[i - pxStride], prevLine[i]);
|
||||||
break;
|
break;
|
||||||
case PngFilter.Paeth:
|
case PngFilter.Paeth:
|
||||||
for (var i = 0; i < pxStride; i++)
|
for (var i = 0; i < pxStride; i++)
|
||||||
line[i] += Paeth(0, prevLine[i], 0);
|
line[i] += Paeth(0, prevLine[i], 0);
|
||||||
for (var i = pxStride; i < line.Length; i++)
|
for (var i = pxStride; i < rowStride; i++)
|
||||||
line[i] += Paeth(line[i - pxStride], prevLine[i], prevLine[i - pxStride]);
|
line[i] += Paeth(line[i - pxStride], prevLine[i], prevLine[i - pxStride]);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -363,15 +197,6 @@ namespace OpenRA.FileFormats
|
|||||||
prevLine = line;
|
prevLine = line;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drain remaining Zlib footer bytes if necessary.
|
|
||||||
Span<byte> drain = stackalloc byte[16];
|
|
||||||
while (ds.Read(drain) > 0) { }
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ArrayPool<byte>.Shared.Return(prevLineBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
static byte Average(byte a, byte b) => (byte)((a + b) / 2);
|
static byte Average(byte a, byte b) => (byte)((a + b) / 2);
|
||||||
|
|
||||||
static byte Paeth(byte a, byte b, byte c)
|
static byte Paeth(byte a, byte b, byte c)
|
||||||
@@ -385,6 +210,17 @@ namespace OpenRA.FileFormats
|
|||||||
(pb <= pc) ? b : c;
|
(pb <= pc) ? b : c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Type == SpriteFrameType.Indexed8 && Palette == null)
|
||||||
|
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Png(byte[] data, SpriteFrameType type, int width, int height, Color[] palette = null,
|
public Png(byte[] data, SpriteFrameType type, int width, int height, Color[] palette = null,
|
||||||
Dictionary<string, string> embeddedData = null)
|
Dictionary<string, string> embeddedData = null)
|
||||||
@@ -420,27 +256,14 @@ namespace OpenRA.FileFormats
|
|||||||
// Convert to big endian
|
// Convert to big endian
|
||||||
Data = new byte[data.Length];
|
Data = new byte[data.Length];
|
||||||
var stride = PixelStride;
|
var stride = PixelStride;
|
||||||
var elements = width * height * stride;
|
for (var i = 0; i < width * height; i++)
|
||||||
var src = data.AsSpan();
|
{
|
||||||
var dst = Data.AsSpan();
|
Data[stride * i] = data[stride * i + 2];
|
||||||
|
Data[stride * i + 1] = data[stride * i + 1];
|
||||||
|
Data[stride * i + 2] = data[stride * i + 0];
|
||||||
|
|
||||||
if (type == SpriteFrameType.Bgra32)
|
if (type == SpriteFrameType.Bgra32)
|
||||||
{
|
Data[stride * i + 3] = data[stride * i + 3];
|
||||||
for (var i = 0; i < elements; i += 4)
|
|
||||||
{
|
|
||||||
dst[i + 0] = src[i + 2];
|
|
||||||
dst[i + 1] = src[i + 1];
|
|
||||||
dst[i + 2] = src[i + 0];
|
|
||||||
dst[i + 3] = src[i + 3];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for (var i = 0; i < elements; i += 3)
|
|
||||||
{
|
|
||||||
dst[i + 0] = src[i + 2];
|
|
||||||
dst[i + 1] = src[i + 1];
|
|
||||||
dst[i + 2] = src[i + 0];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -457,8 +280,7 @@ namespace OpenRA.FileFormats
|
|||||||
public static bool Verify(Stream s)
|
public static bool Verify(Stream s)
|
||||||
{
|
{
|
||||||
var pos = s.Position;
|
var pos = s.Position;
|
||||||
Span<byte> sigBuffer = stackalloc byte[8];
|
var isPng = Signature.Aggregate(true, (current, t) => current && s.ReadUInt8() == t);
|
||||||
var isPng = s.Read(sigBuffer) == 8 && sigBuffer.SequenceEqual(Signature);
|
|
||||||
s.Position = pos;
|
s.Position = pos;
|
||||||
return isPng;
|
return isPng;
|
||||||
}
|
}
|
||||||
@@ -481,29 +303,24 @@ namespace OpenRA.FileFormats
|
|||||||
throw new InvalidDataException("Unknown pixel format");
|
throw new InvalidDataException("Unknown pixel format");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void WritePngChunk(MemoryStream output, uint type, MemoryStream input)
|
static void WritePngChunk(Stream output, string type, Stream input)
|
||||||
{
|
{
|
||||||
Span<byte> header = stackalloc byte[8];
|
input.Position = 0;
|
||||||
BinaryPrimitives.WriteInt32BigEndian(header[..4], (int)input.Length);
|
|
||||||
BinaryPrimitives.WriteUInt32BigEndian(header[4..], type);
|
|
||||||
|
|
||||||
if (!input.TryGetBuffer(out var dataSegment))
|
var typeBytes = Encoding.ASCII.GetBytes(type);
|
||||||
dataSegment = new ArraySegment<byte>(input.ToArray());
|
output.Write(IPAddress.HostToNetworkOrder((int)input.Length));
|
||||||
|
output.Write(typeBytes);
|
||||||
|
|
||||||
ReadOnlySpan<byte> data = dataSegment.AsSpan(0, (int)input.Length);
|
var data = input.ReadAllBytes();
|
||||||
|
|
||||||
output.Write(header);
|
|
||||||
output.Write(data);
|
output.Write(data);
|
||||||
|
|
||||||
var crc = 0xFFFFFFFF;
|
var crc32 = new Crc32();
|
||||||
crc = CRC32.Update(crc, header[4..]);
|
crc32.Update(typeBytes);
|
||||||
crc = CRC32.Update(crc, data);
|
crc32.Update(data);
|
||||||
var finalCrc = CRC32.Finish(crc);
|
output.Write(IPAddress.NetworkToHostOrder((int)crc32.Value));
|
||||||
|
|
||||||
output.Write(IPAddress.NetworkToHostOrder((int)finalCrc));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] Save(CompressionLevel compression = CompressionLevel.SmallestSize)
|
public byte[] Save()
|
||||||
{
|
{
|
||||||
using (var output = new MemoryStream())
|
using (var output = new MemoryStream())
|
||||||
{
|
{
|
||||||
@@ -522,7 +339,7 @@ namespace OpenRA.FileFormats
|
|||||||
header.WriteByte(0); // Filter
|
header.WriteByte(0); // Filter
|
||||||
header.WriteByte(0); // Interlacing
|
header.WriteByte(0); // Interlacing
|
||||||
|
|
||||||
WritePngChunk(output, ChunkIHDR, header);
|
WritePngChunk(output, "IHDR", header);
|
||||||
}
|
}
|
||||||
|
|
||||||
var alphaPalette = false;
|
var alphaPalette = false;
|
||||||
@@ -538,7 +355,7 @@ namespace OpenRA.FileFormats
|
|||||||
alphaPalette |= c.A > 0;
|
alphaPalette |= c.A > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
WritePngChunk(output, ChunkPLTE, palette);
|
WritePngChunk(output, "PLTE", palette);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,13 +366,13 @@ namespace OpenRA.FileFormats
|
|||||||
foreach (var c in Palette)
|
foreach (var c in Palette)
|
||||||
alpha.WriteByte(c.A);
|
alpha.WriteByte(c.A);
|
||||||
|
|
||||||
WritePngChunk(output, ChunkTRNS, alpha);
|
WritePngChunk(output, "tRNS", alpha);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var data = new MemoryStream())
|
using (var data = new MemoryStream())
|
||||||
{
|
{
|
||||||
using (var compressed = new ZLibStream(data, compression, true))
|
using (var compressed = new DeflaterOutputStream(data, new Deflater(Deflater.BEST_COMPRESSION)))
|
||||||
{
|
{
|
||||||
var rowStride = Width * PixelStride;
|
var rowStride = Width * PixelStride;
|
||||||
for (var y = 0; y < Height; y++)
|
for (var y = 0; y < Height; y++)
|
||||||
@@ -565,9 +382,12 @@ namespace OpenRA.FileFormats
|
|||||||
compressed.WriteByte(FilterType);
|
compressed.WriteByte(FilterType);
|
||||||
compressed.Write(Data, y * rowStride, rowStride);
|
compressed.Write(Data, y * rowStride, rowStride);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
WritePngChunk(output, ChunkIDAT, data);
|
compressed.Flush();
|
||||||
|
compressed.Finish();
|
||||||
|
|
||||||
|
WritePngChunk(output, "IDAT", data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var kv in EmbeddedData)
|
foreach (var kv in EmbeddedData)
|
||||||
@@ -575,18 +395,18 @@ namespace OpenRA.FileFormats
|
|||||||
using (var text = new MemoryStream())
|
using (var text = new MemoryStream())
|
||||||
{
|
{
|
||||||
text.Write(Encoding.ASCII.GetBytes(kv.Key + (char)0 + kv.Value));
|
text.Write(Encoding.ASCII.GetBytes(kv.Key + (char)0 + kv.Value));
|
||||||
WritePngChunk(output, ChunkTEXT, text);
|
WritePngChunk(output, "tEXt", text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WritePngChunk(output, ChunkIEND, new MemoryStream());
|
WritePngChunk(output, "IEND", new MemoryStream());
|
||||||
return output.ToArray();
|
return output.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Save(string path, CompressionLevel compression = CompressionLevel.SmallestSize)
|
public void Save(string path)
|
||||||
{
|
{
|
||||||
File.WriteAllBytes(path, Save(compression));
|
File.WriteAllBytes(path, Save());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ namespace OpenRA.FileFormats
|
|||||||
|
|
||||||
public ReplayMetadata(GameInformation info)
|
public ReplayMetadata(GameInformation info)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(info);
|
if (info == null)
|
||||||
|
throw new ArgumentNullException(nameof(info));
|
||||||
|
|
||||||
GameInfo = info;
|
GameInfo = info;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,16 +29,16 @@ namespace OpenRA.FileSystem
|
|||||||
public class FileSystem : IReadOnlyFileSystem
|
public class FileSystem : IReadOnlyFileSystem
|
||||||
{
|
{
|
||||||
public IEnumerable<IReadOnlyPackage> MountedPackages => mountedPackages.Keys;
|
public IEnumerable<IReadOnlyPackage> MountedPackages => mountedPackages.Keys;
|
||||||
readonly Dictionary<IReadOnlyPackage, int> mountedPackages = [];
|
readonly Dictionary<IReadOnlyPackage, int> mountedPackages = new();
|
||||||
readonly Dictionary<string, IReadOnlyPackage> explicitMounts = [];
|
readonly Dictionary<string, IReadOnlyPackage> explicitMounts = new();
|
||||||
readonly string modID;
|
readonly string modID;
|
||||||
|
|
||||||
// Mod packages that should not be disposed
|
// Mod packages that should not be disposed
|
||||||
readonly List<IReadOnlyPackage> modPackages = [];
|
readonly List<IReadOnlyPackage> modPackages = new();
|
||||||
readonly IReadOnlyDictionary<string, Manifest> installedMods;
|
readonly IReadOnlyDictionary<string, Manifest> installedMods;
|
||||||
readonly IPackageLoader[] packageLoaders;
|
readonly IPackageLoader[] packageLoaders;
|
||||||
|
|
||||||
Cache<string, List<IReadOnlyPackage>> fileIndex = new(_ => []);
|
Cache<string, List<IReadOnlyPackage>> fileIndex = new(_ => new List<IReadOnlyPackage>());
|
||||||
|
|
||||||
public FileSystem(string modID, IReadOnlyDictionary<string, Manifest> installedMods, IPackageLoader[] packageLoaders)
|
public FileSystem(string modID, IReadOnlyDictionary<string, Manifest> installedMods, IPackageLoader[] packageLoaders)
|
||||||
{
|
{
|
||||||
@@ -178,7 +178,7 @@ namespace OpenRA.FileSystem
|
|||||||
explicitMounts.Clear();
|
explicitMounts.Clear();
|
||||||
modPackages.Clear();
|
modPackages.Clear();
|
||||||
|
|
||||||
fileIndex = new Cache<string, List<IReadOnlyPackage>>(_ => []);
|
fileIndex = new Cache<string, List<IReadOnlyPackage>>(_ => new List<IReadOnlyPackage>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TrimExcess()
|
public void TrimExcess()
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ namespace OpenRA.FileSystem
|
|||||||
return Directory.GetFiles(Name, "*", SearchOption.TopDirectoryOnly)
|
return Directory.GetFiles(Name, "*", SearchOption.TopDirectoryOnly)
|
||||||
.Concat(Directory.GetDirectories(Name))
|
.Concat(Directory.GetDirectories(Name))
|
||||||
.Select(Path.GetFileName)
|
.Select(Path.GetFileName)
|
||||||
.Order();
|
.OrderBy(f => f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,11 +98,11 @@ namespace OpenRA.FileSystem
|
|||||||
{
|
{
|
||||||
readonly MemoryStream pkgStream = new();
|
readonly MemoryStream pkgStream = new();
|
||||||
|
|
||||||
public ReadWriteZipFile(string filename = null, bool create = false)
|
public ReadWriteZipFile(string filename, bool create = false)
|
||||||
{
|
{
|
||||||
// SharpZipLib breaks when asked to update archives loaded from outside streams or files
|
// SharpZipLib breaks when asked to update archives loaded from outside streams or files
|
||||||
// We can work around this by creating a clean in-memory-only file, cutting all outside references
|
// We can work around this by creating a clean in-memory-only file, cutting all outside references
|
||||||
if (!string.IsNullOrEmpty(filename) && !create)
|
if (!create)
|
||||||
{
|
{
|
||||||
using (var copy = new MemoryStream(File.ReadAllBytes(filename)))
|
using (var copy = new MemoryStream(File.ReadAllBytes(filename)))
|
||||||
{
|
{
|
||||||
@@ -120,26 +120,8 @@ namespace OpenRA.FileSystem
|
|||||||
entry.ExtraData = null;
|
entry.ExtraData = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadWriteZipFile(byte[] data)
|
|
||||||
{
|
|
||||||
using (var copy = new MemoryStream(data))
|
|
||||||
{
|
|
||||||
pkgStream.Capacity = (int)copy.Length;
|
|
||||||
copy.CopyTo(pkgStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
pkgStream.Position = 0;
|
|
||||||
pkg = new ZipFile(pkgStream);
|
|
||||||
Name = null;
|
|
||||||
|
|
||||||
// Remove subfields that can break ZIP updating.
|
|
||||||
foreach (ZipEntry entry in pkg)
|
|
||||||
entry.ExtraData = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Commit()
|
void Commit()
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(Name))
|
|
||||||
File.WriteAllBytes(Name, pkgStream.ToArray());
|
File.WriteAllBytes(Name, pkgStream.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Linguini.Bundle;
|
using Linguini.Bundle;
|
||||||
@@ -49,58 +48,23 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Class)]
|
|
||||||
public sealed class IncludeStaticFluentReferencesAttribute : Attribute
|
|
||||||
{
|
|
||||||
public readonly Type[] Types;
|
|
||||||
|
|
||||||
public IncludeStaticFluentReferencesAttribute(params Type[] types)
|
|
||||||
{
|
|
||||||
Types = types;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Class)]
|
|
||||||
public sealed class IncludeChromeLogicArgsFluentReferencesAttribute : Attribute
|
|
||||||
{
|
|
||||||
public readonly string[] MethodNames;
|
|
||||||
|
|
||||||
public IncludeChromeLogicArgsFluentReferencesAttribute(params string[] methodNames)
|
|
||||||
{
|
|
||||||
MethodNames = methodNames;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Field)]
|
|
||||||
public sealed class IncludeFluentReferencesAttribute : Attribute
|
|
||||||
{
|
|
||||||
public readonly LintDictionaryReference DictionaryReference;
|
|
||||||
|
|
||||||
public IncludeFluentReferencesAttribute() { }
|
|
||||||
|
|
||||||
public IncludeFluentReferencesAttribute(LintDictionaryReference dictionaryReference = LintDictionaryReference.None)
|
|
||||||
{
|
|
||||||
DictionaryReference = dictionaryReference;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class FluentBundle
|
public class FluentBundle
|
||||||
{
|
{
|
||||||
readonly Linguini.Bundle.FluentBundle bundle;
|
readonly Linguini.Bundle.FluentBundle bundle;
|
||||||
|
|
||||||
public FluentBundle(string culture, ImmutableArray<string> paths, IReadOnlyFileSystem fileSystem)
|
public FluentBundle(string culture, string[] paths, IReadOnlyFileSystem fileSystem)
|
||||||
: this(culture, paths, fileSystem, error => Log.Write("debug", error.Message)) { }
|
: this(culture, paths, fileSystem, error => Log.Write("debug", error.Message)) { }
|
||||||
|
|
||||||
public FluentBundle(string culture, ImmutableArray<string> paths, IReadOnlyFileSystem fileSystem, string text)
|
public FluentBundle(string culture, string[] paths, IReadOnlyFileSystem fileSystem, string text)
|
||||||
: this(culture, paths, fileSystem, text, error => Log.Write("debug", error.Message)) { }
|
: this(culture, paths, fileSystem, text, error => Log.Write("debug", error.Message)) { }
|
||||||
|
|
||||||
public FluentBundle(string culture, ImmutableArray<string> paths, IReadOnlyFileSystem fileSystem, Action<ParseError> onError)
|
public FluentBundle(string culture, string[] paths, IReadOnlyFileSystem fileSystem, Action<ParseError> onError)
|
||||||
: this(culture, paths, fileSystem, null, onError) { }
|
: this(culture, paths, fileSystem, null, onError) { }
|
||||||
|
|
||||||
public FluentBundle(string culture, string text, Action<ParseError> onError)
|
public FluentBundle(string culture, string text, Action<ParseError> onError)
|
||||||
: this(culture, default, null, text, onError) { }
|
: this(culture, null, null, text, onError) { }
|
||||||
|
|
||||||
public FluentBundle(string culture, ImmutableArray<string> paths, IReadOnlyFileSystem fileSystem, string text, Action<ParseError> onError)
|
public FluentBundle(string culture, string[] paths, IReadOnlyFileSystem fileSystem, string text, Action<ParseError> onError)
|
||||||
{
|
{
|
||||||
bundle = LinguiniBuilder.Builder()
|
bundle = LinguiniBuilder.Builder()
|
||||||
.CultureInfo(new CultureInfo(culture))
|
.CultureInfo(new CultureInfo(culture))
|
||||||
@@ -147,7 +111,8 @@ namespace OpenRA
|
|||||||
|
|
||||||
public bool TryGetMessage(string key, out string value, object[] args = null)
|
public bool TryGetMessage(string key, out string value, object[] args = null)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(key);
|
if (key == null)
|
||||||
|
throw new ArgumentNullException(nameof(key));
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -164,7 +129,7 @@ namespace OpenRA
|
|||||||
throw new ArgumentException("Expected a comma separated list of name, value arguments " +
|
throw new ArgumentException("Expected a comma separated list of name, value arguments " +
|
||||||
"but the number of arguments is not a multiple of two", nameof(args));
|
"but the number of arguments is not a multiple of two", nameof(args));
|
||||||
|
|
||||||
fluentArgs = [];
|
fluentArgs = new Dictionary<string, IFluentType>();
|
||||||
for (var i = 0; i < args.Length; i += 2)
|
for (var i = 0; i < args.Length; i += 2)
|
||||||
{
|
{
|
||||||
var argKey = args[i] as string;
|
var argKey = args[i] as string;
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using OpenRA.FileSystem;
|
using OpenRA.FileSystem;
|
||||||
|
|
||||||
@@ -23,16 +22,16 @@ namespace OpenRA
|
|||||||
static FluentBundle modFluentBundle;
|
static FluentBundle modFluentBundle;
|
||||||
static FluentBundle mapFluentBundle;
|
static FluentBundle mapFluentBundle;
|
||||||
|
|
||||||
public static void Initialize(Manifest manifest, IReadOnlyFileSystem fileSystem)
|
public static void Initialize(ModData modData, IReadOnlyFileSystem fileSystem)
|
||||||
{
|
{
|
||||||
lock (SyncObject)
|
lock (SyncObject)
|
||||||
{
|
{
|
||||||
modFluentBundle = new FluentBundle(manifest.FluentCulture, manifest.FluentMessages, fileSystem);
|
modFluentBundle = new FluentBundle(modData.Manifest.FluentCulture, modData.Manifest.FluentMessages, fileSystem);
|
||||||
if (fileSystem is Map map && map.FluentMessageDefinitions != null)
|
if (fileSystem is Map map && map.FluentMessageDefinitions != null)
|
||||||
{
|
{
|
||||||
var files = ImmutableArray<string>.Empty;
|
var files = Array.Empty<string>();
|
||||||
if (map.FluentMessageDefinitions.Value != null)
|
if (map.FluentMessageDefinitions.Value != null)
|
||||||
files = FieldLoader.GetValue<ImmutableArray<string>>("value", map.FluentMessageDefinitions.Value);
|
files = FieldLoader.GetValue<string[]>("value", map.FluentMessageDefinitions.Value);
|
||||||
|
|
||||||
string text = null;
|
string text = null;
|
||||||
if (map.FluentMessageDefinitions.Nodes.Length > 0)
|
if (map.FluentMessageDefinitions.Nodes.Length > 0)
|
||||||
@@ -45,7 +44,7 @@ namespace OpenRA
|
|||||||
text = builder.ToString();
|
text = builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
mapFluentBundle = new FluentBundle(manifest.FluentCulture, files, fileSystem, text);
|
mapFluentBundle = new FluentBundle(modData.Manifest.FluentCulture, files, fileSystem, text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
*/
|
*/
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System.Collections.Frozen;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
@@ -24,15 +23,15 @@ namespace OpenRA
|
|||||||
public class Fonts : IGlobalModData
|
public class Fonts : IGlobalModData
|
||||||
{
|
{
|
||||||
[FieldLoader.LoadUsing(nameof(LoadFonts))]
|
[FieldLoader.LoadUsing(nameof(LoadFonts))]
|
||||||
public readonly FrozenDictionary<string, FontData> FontList;
|
public readonly Dictionary<string, FontData> FontList;
|
||||||
|
|
||||||
static object LoadFonts(MiniYaml y)
|
static object LoadFonts(MiniYaml y)
|
||||||
{
|
{
|
||||||
var ret = new Dictionary<string, FontData>(y.Nodes.Length);
|
var ret = new Dictionary<string, FontData>();
|
||||||
foreach (var node in y.Nodes)
|
foreach (var node in y.Nodes)
|
||||||
ret.Add(node.Key, FieldLoader.Load<FontData>(node.Value));
|
ret.Add(node.Key, FieldLoader.Load<FontData>(node.Value));
|
||||||
|
|
||||||
return ret.ToFrozenDictionary();
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -28,7 +27,6 @@ using OpenRA.Widgets;
|
|||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
{
|
{
|
||||||
[IncludeStaticFluentReferences(typeof(Server.Server), typeof(Player), typeof(UnitOrders), typeof(OrderManager))]
|
|
||||||
public static class Game
|
public static class Game
|
||||||
{
|
{
|
||||||
[FluentReference("filename")]
|
[FluentReference("filename")]
|
||||||
@@ -168,7 +166,6 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
return ModData.WidgetLoader.LoadWidget(new WidgetArgs(args)
|
return ModData.WidgetLoader.LoadWidget(new WidgetArgs(args)
|
||||||
{
|
{
|
||||||
{ "modData", ModData },
|
|
||||||
{ "world", world },
|
{ "world", world },
|
||||||
{ "orderManager", OrderManager },
|
{ "orderManager", OrderManager },
|
||||||
{ "worldRenderer", worldRenderer },
|
{ "worldRenderer", worldRenderer },
|
||||||
@@ -184,16 +181,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
public static event Action BeforeGameStart = () => { };
|
public static event Action BeforeGameStart = () => { };
|
||||||
public static event Action AfterGameStart = () => { };
|
public static event Action AfterGameStart = () => { };
|
||||||
internal static void StartGame(string uid, WorldType type)
|
internal static void StartGame(string mapUID, WorldType type)
|
||||||
{
|
|
||||||
var preview = ModData.MapCache[uid];
|
|
||||||
if (preview.Status != MapStatus.Available)
|
|
||||||
throw new InvalidDataException($"Invalid map uid: {uid}");
|
|
||||||
|
|
||||||
StartGame(preview.ToMap(), type);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static void StartGame(Map map, WorldType type)
|
|
||||||
{
|
{
|
||||||
// Dispose of the old world before creating a new one.
|
// Dispose of the old world before creating a new one.
|
||||||
worldRenderer?.Dispose();
|
worldRenderer?.Dispose();
|
||||||
@@ -202,23 +190,7 @@ namespace OpenRA
|
|||||||
BeforeGameStart();
|
BeforeGameStart();
|
||||||
|
|
||||||
using (new PerfTimer("NewWorld"))
|
using (new PerfTimer("NewWorld"))
|
||||||
{
|
OrderManager.World = new World(mapUID, ModData, OrderManager, type);
|
||||||
ModData.PrepareMap(map);
|
|
||||||
|
|
||||||
// The depth buffer needs to be initialized with enough range to cover:
|
|
||||||
// - the height of the screen
|
|
||||||
// - the z-offset of tiles from MaxTerrainHeight below the bottom of the screen (pushed into view)
|
|
||||||
// - additional z-offset from actors on top of MaxTerrainHeight terrain
|
|
||||||
// - a small margin so that tiles rendered partially above the top edge of the screen aren't pushed behind the clip plane
|
|
||||||
// We need an offset of mapGrid.MaximumTerrainHeight * mapGrid.TileSize.Height / 2 to cover the terrain height
|
|
||||||
// and choose to use mapGrid.MaximumTerrainHeight * mapGrid.TileSize.Height / 4 for each of the actor and top-edge cases
|
|
||||||
var margin = 0;
|
|
||||||
if (map.Grid.EnableDepthBuffer)
|
|
||||||
margin = map.Rules.TerrainInfo.TileSize.Height * map.Grid.MaximumTerrainHeight;
|
|
||||||
|
|
||||||
Renderer.SetDepthMargin(margin);
|
|
||||||
OrderManager.World = new World(map, ModData, OrderManager, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
OrderManager.World.GameOver += FinishBenchmark;
|
OrderManager.World.GameOver += FinishBenchmark;
|
||||||
|
|
||||||
@@ -372,7 +344,7 @@ namespace OpenRA
|
|||||||
var explicitModPaths = Array.Empty<string>();
|
var explicitModPaths = Array.Empty<string>();
|
||||||
if (modID != null && (File.Exists(modID) || Directory.Exists(modID)))
|
if (modID != null && (File.Exists(modID) || Directory.Exists(modID)))
|
||||||
{
|
{
|
||||||
explicitModPaths = [modID];
|
explicitModPaths = new[] { modID };
|
||||||
modID = Path.GetFileNameWithoutExtension(modID);
|
modID = Path.GetFileNameWithoutExtension(modID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,48 +359,6 @@ namespace OpenRA
|
|||||||
Log.AddChannel("nat", "nat.log");
|
Log.AddChannel("nat", "nat.log");
|
||||||
Log.AddChannel("client", "client.log");
|
Log.AddChannel("client", "client.log");
|
||||||
|
|
||||||
Nat.Initialize();
|
|
||||||
|
|
||||||
var modSearchArg = args.GetValue("Engine.ModSearchPaths", null);
|
|
||||||
var modSearchPaths = modSearchArg != null ?
|
|
||||||
FieldLoader.GetValue<ImmutableArray<string>>("Engine.ModsPath", modSearchArg) :
|
|
||||||
[Path.Combine(Platform.EngineDir, "mods")];
|
|
||||||
|
|
||||||
Mods = new InstalledMods(modSearchPaths, explicitModPaths);
|
|
||||||
Console.WriteLine("Internal mods:");
|
|
||||||
foreach (var mod in Mods)
|
|
||||||
Console.WriteLine($"\t{mod.Key} ({mod.Value.Metadata.Version})");
|
|
||||||
|
|
||||||
modLaunchWrapper = args.GetValue("Engine.LaunchWrapper", null);
|
|
||||||
|
|
||||||
ExternalMods = new ExternalMods();
|
|
||||||
|
|
||||||
if (modID == null)
|
|
||||||
throw new InvalidOperationException("Game.Mod argument missing.");
|
|
||||||
|
|
||||||
if (Mods.TryGetValue(modID, out var manifest))
|
|
||||||
{
|
|
||||||
var launchPath = args.GetValue("Engine.LaunchPath", null);
|
|
||||||
var launchArgs = new List<string>();
|
|
||||||
|
|
||||||
// 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[^1] == '"')
|
|
||||||
launchPath = launchPath[1..^1];
|
|
||||||
|
|
||||||
// Metadata registration requires an explicit launch path
|
|
||||||
if (launchPath != null)
|
|
||||||
ExternalMods.Register(Mods[modID], launchPath, launchArgs, ModRegistration.User);
|
|
||||||
|
|
||||||
ExternalMods.ClearInvalidRegistrations(ModRegistration.User);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
throw new InvalidOperationException($"Unknown or invalid mod '{modID}'.");
|
|
||||||
|
|
||||||
Console.WriteLine("External mods:");
|
|
||||||
foreach (var mod in ExternalMods)
|
|
||||||
Console.WriteLine($"\t{mod.Key} ({mod.Value.Version})");
|
|
||||||
|
|
||||||
var platforms = new[] { Settings.Game.Platform, "Default", null };
|
var platforms = new[] { Settings.Game.Platform, "Default", null };
|
||||||
foreach (var p in platforms)
|
foreach (var p in platforms)
|
||||||
{
|
{
|
||||||
@@ -439,7 +369,7 @@ namespace OpenRA
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var platform = CreatePlatform(p);
|
var platform = CreatePlatform(p);
|
||||||
Renderer = new Renderer(platform, Settings.Graphics, manifest.RendererConstants.VertexBatchSize);
|
Renderer = new Renderer(platform, Settings.Graphics);
|
||||||
Sound = new Sound(platform, Settings.Sound);
|
Sound = new Sound(platform, Settings.Sound);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -455,23 +385,67 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InitializeMod(manifest, args);
|
Nat.Initialize();
|
||||||
|
|
||||||
|
var modSearchArg = args.GetValue("Engine.ModSearchPaths", null);
|
||||||
|
var modSearchPaths = modSearchArg != null ?
|
||||||
|
FieldLoader.GetValue<string[]>("Engine.ModsPath", modSearchArg) :
|
||||||
|
new[] { Path.Combine(Platform.EngineDir, "mods") };
|
||||||
|
|
||||||
|
Mods = new InstalledMods(modSearchPaths, explicitModPaths);
|
||||||
|
Console.WriteLine("Internal mods:");
|
||||||
|
foreach (var mod in Mods)
|
||||||
|
Console.WriteLine($"\t{mod.Key} ({mod.Value.Metadata.Version})");
|
||||||
|
|
||||||
|
modLaunchWrapper = args.GetValue("Engine.LaunchWrapper", null);
|
||||||
|
|
||||||
|
ExternalMods = new ExternalMods();
|
||||||
|
|
||||||
|
if (modID != null && Mods.TryGetValue(modID, out _))
|
||||||
|
{
|
||||||
|
var launchPath = args.GetValue("Engine.LaunchPath", null);
|
||||||
|
var launchArgs = new List<string>();
|
||||||
|
|
||||||
|
// 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() == '"')
|
||||||
|
launchPath = launchPath[1..^1];
|
||||||
|
|
||||||
|
// Metadata registration requires an explicit launch path
|
||||||
|
if (launchPath != null)
|
||||||
|
ExternalMods.Register(Mods[modID], launchPath, launchArgs, ModRegistration.User);
|
||||||
|
|
||||||
|
ExternalMods.ClearInvalidRegistrations(ModRegistration.User);
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("External mods:");
|
||||||
|
foreach (var mod in ExternalMods)
|
||||||
|
Console.WriteLine($"\t{mod.Key} ({mod.Value.Version})");
|
||||||
|
|
||||||
|
InitializeMod(modID, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IPlatform CreatePlatform(string platformName)
|
public static IPlatform CreatePlatform(string platformName)
|
||||||
{
|
{
|
||||||
var rendererPath = Path.Combine(Platform.BinDir, "OpenRA.Platforms." + platformName + ".dll");
|
var rendererPath = Path.Combine(Platform.BinDir, "OpenRA.Platforms." + platformName + ".dll");
|
||||||
|
|
||||||
|
#if NET5_0_OR_GREATER
|
||||||
var loader = new AssemblyLoader(rendererPath);
|
var loader = new AssemblyLoader(rendererPath);
|
||||||
var platformType = loader.LoadDefaultAssembly().GetTypes().SingleOrDefault(t => typeof(IPlatform).IsAssignableFrom(t));
|
var platformType = loader.LoadDefaultAssembly().GetTypes().SingleOrDefault(t => typeof(IPlatform).IsAssignableFrom(t));
|
||||||
|
|
||||||
|
#else
|
||||||
|
// NOTE: This is currently the only use of System.Reflection in this file, so would give an unused using error if we import it above
|
||||||
|
var assembly = System.Reflection.Assembly.LoadFile(rendererPath);
|
||||||
|
var platformType = assembly.GetTypes().SingleOrDefault(t => typeof(IPlatform).IsAssignableFrom(t));
|
||||||
|
#endif
|
||||||
|
|
||||||
if (platformType == null)
|
if (platformType == null)
|
||||||
throw new InvalidOperationException("Platform dll must include exactly one IPlatform implementation.");
|
throw new InvalidOperationException("Platform dll must include exactly one IPlatform implementation.");
|
||||||
|
|
||||||
return (IPlatform)platformType.GetConstructor(Type.EmptyTypes).Invoke(null);
|
return (IPlatform)platformType.GetConstructor(Type.EmptyTypes).Invoke(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void InitializeMod(Manifest manifest, Arguments args)
|
public static void InitializeMod(string mod, Arguments args)
|
||||||
{
|
{
|
||||||
// Clear static state if we have switched mods
|
// Clear static state if we have switched mods
|
||||||
LobbyInfoChanged = () => { };
|
LobbyInfoChanged = () => { };
|
||||||
@@ -495,25 +469,34 @@ namespace OpenRA
|
|||||||
|
|
||||||
ModData = null;
|
ModData = null;
|
||||||
|
|
||||||
Console.WriteLine($"Loading mod: {manifest.Id}");
|
if (mod == null)
|
||||||
|
throw new InvalidOperationException("Game.Mod argument missing.");
|
||||||
|
|
||||||
|
if (!Mods.ContainsKey(mod))
|
||||||
|
throw new InvalidOperationException($"Unknown or invalid mod '{mod}'.");
|
||||||
|
|
||||||
|
Console.WriteLine($"Loading mod: {mod}");
|
||||||
|
|
||||||
Sound.StopVideo();
|
Sound.StopVideo();
|
||||||
|
|
||||||
ModData = new ModData(manifest, Mods, true);
|
ModData = new ModData(Mods[mod], Mods, true);
|
||||||
|
|
||||||
LocalPlayerProfile = new LocalPlayerProfile(Path.Combine(Platform.SupportDir, Settings.Game.AuthProfile), ModData.GetOrCreate<PlayerDatabase>());
|
LocalPlayerProfile = new LocalPlayerProfile(Path.Combine(Platform.SupportDir, Settings.Game.AuthProfile), ModData.Manifest.Get<PlayerDatabase>());
|
||||||
|
|
||||||
if (!ModData.LoadScreen.BeforeLoad(ModData))
|
if (!ModData.LoadScreen.BeforeLoad())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ModData.InitializeLoaders(ModData.DefaultFileSystem);
|
ModData.InitializeLoaders(ModData.DefaultFileSystem);
|
||||||
Renderer.InitializeFonts(ModData);
|
Renderer.InitializeFonts(ModData);
|
||||||
|
|
||||||
using (new PerfTimer("LoadMaps"))
|
using (new PerfTimer("LoadMaps"))
|
||||||
ModData.MapCache.LoadMaps(ModData);
|
ModData.MapCache.LoadMaps();
|
||||||
|
|
||||||
|
var grid = ModData.Manifest.Contains<MapGrid>() ? ModData.Manifest.Get<MapGrid>() : null;
|
||||||
|
Renderer.InitializeDepthBuffer(grid);
|
||||||
|
|
||||||
Cursor?.Dispose();
|
Cursor?.Dispose();
|
||||||
Cursor = new CursorManager(ModData);
|
Cursor = new CursorManager(ModData.CursorProvider, ModData.Manifest.CursorSheetSize);
|
||||||
|
|
||||||
var metadata = ModData.Manifest.Metadata;
|
var metadata = ModData.Manifest.Metadata;
|
||||||
if (!string.IsNullOrEmpty(metadata.WindowTitleTranslated))
|
if (!string.IsNullOrEmpty(metadata.WindowTitleTranslated))
|
||||||
@@ -531,16 +514,10 @@ namespace OpenRA
|
|||||||
ModData.LoadScreen.StartGame(args);
|
ModData.LoadScreen.StartGame(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void LoadEditor(string uid)
|
public static void LoadEditor(string mapUid)
|
||||||
{
|
{
|
||||||
JoinLocal();
|
JoinLocal();
|
||||||
StartGame(uid, WorldType.Editor);
|
StartGame(mapUid, WorldType.Editor);
|
||||||
}
|
|
||||||
|
|
||||||
public static void LoadEditor(Map map)
|
|
||||||
{
|
|
||||||
JoinLocal();
|
|
||||||
StartGame(map, WorldType.Editor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void LoadShellMap()
|
public static void LoadShellMap()
|
||||||
@@ -614,7 +591,7 @@ namespace OpenRA
|
|||||||
Directory.CreateDirectory(directory);
|
Directory.CreateDirectory(directory);
|
||||||
|
|
||||||
var filename = TimestampedFilename(true);
|
var filename = TimestampedFilename(true);
|
||||||
var path = Path.Combine(directory, $"{filename}.png");
|
var path = Path.Combine(directory, string.Concat(filename, ".png"));
|
||||||
Log.Write("debug", "Taking screenshot " + path);
|
Log.Write("debug", "Taking screenshot " + path);
|
||||||
|
|
||||||
Renderer.SaveScreenshot(path);
|
Renderer.SaveScreenshot(path);
|
||||||
@@ -661,7 +638,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
world.Tick();
|
world.Tick();
|
||||||
|
|
||||||
PerfHistory.Tick(!world.Paused);
|
PerfHistory.Tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait until we have done our first world Tick before TickRendering
|
// Wait until we have done our first world Tick before TickRendering
|
||||||
@@ -725,7 +702,7 @@ namespace OpenRA
|
|||||||
// Use worldRenderer.World instead of OrderManager.World to avoid a rendering mismatch while processing orders
|
// Use worldRenderer.World instead of OrderManager.World to avoid a rendering mismatch while processing orders
|
||||||
if (worldRenderer != null && !worldRenderer.World.IsLoadingGameSave)
|
if (worldRenderer != null && !worldRenderer.World.IsLoadingGameSave)
|
||||||
{
|
{
|
||||||
Renderer.BeginWorld(worldRenderer.Viewport.CenterLocation, worldRenderer.Viewport.ViewportSize);
|
Renderer.BeginWorld(worldRenderer.Viewport.Rectangle);
|
||||||
Sound.SetListenerPosition(worldRenderer.Viewport.CenterPosition);
|
Sound.SetListenerPosition(worldRenderer.Viewport.CenterPosition);
|
||||||
using (new PerfSample("render_world"))
|
using (new PerfSample("render_world"))
|
||||||
worldRenderer.Draw();
|
worldRenderer.Draw();
|
||||||
@@ -740,12 +717,15 @@ namespace OpenRA
|
|||||||
|
|
||||||
Ui.Draw();
|
Ui.Draw();
|
||||||
|
|
||||||
|
if (ModData != null && ModData.CursorProvider != null)
|
||||||
|
{
|
||||||
if (HideCursor)
|
if (HideCursor)
|
||||||
Cursor?.SetCursor(null);
|
Cursor.SetCursor(null);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Cursor?.SetCursor(Ui.Root.GetCursorOuter(Viewport.LastMousePos) ?? "default");
|
Cursor.SetCursor(Ui.Root.GetCursorOuter(Viewport.LastMousePos) ?? "default");
|
||||||
Cursor?.Render(Renderer);
|
Cursor.Render(Renderer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -759,13 +739,12 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var isActive = !(worldRenderer?.World.Paused ?? true);
|
PerfHistory.Items["render"].Tick();
|
||||||
PerfHistory.Items["render"].Tick(isActive);
|
PerfHistory.Items["batches"].Tick();
|
||||||
PerfHistory.Items["batches"].Tick(isActive);
|
PerfHistory.Items["render_world"].Tick();
|
||||||
PerfHistory.Items["render_world"].Tick(isActive);
|
PerfHistory.Items["render_widgets"].Tick();
|
||||||
PerfHistory.Items["render_widgets"].Tick(isActive);
|
PerfHistory.Items["render_flip"].Tick();
|
||||||
PerfHistory.Items["render_flip"].Tick(isActive);
|
PerfHistory.Items["terrain_lighting"].Tick();
|
||||||
PerfHistory.Items["terrain_lighting"].Tick(isActive);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Loop()
|
static void Loop()
|
||||||
@@ -862,13 +841,8 @@ namespace OpenRA
|
|||||||
|
|
||||||
var haveSomeTimeUntilNextLogic = now < nextLogic;
|
var haveSomeTimeUntilNextLogic = now < nextLogic;
|
||||||
var isTimeToRender = now >= nextRender;
|
var isTimeToRender = now >= nextRender;
|
||||||
if (!Renderer.WindowIsSuspended)
|
if (!Renderer.WindowIsSuspended && ((isTimeToRender && haveSomeTimeUntilNextLogic) || forceRender))
|
||||||
{
|
{
|
||||||
if (isTimeToRender || forceRender)
|
|
||||||
{
|
|
||||||
if (haveSomeTimeUntilNextLogic || forceRender)
|
|
||||||
RenderTick();
|
|
||||||
|
|
||||||
nextRender = now + renderInterval;
|
nextRender = now + renderInterval;
|
||||||
|
|
||||||
// Pick the minimum allowed FPS (the lower between 'minReplayFPS'
|
// Pick the minimum allowed FPS (the lower between 'minReplayFPS'
|
||||||
@@ -879,13 +853,12 @@ namespace OpenRA
|
|||||||
var maxRenderInterval = Math.Max(1000 / MinReplayFps, renderInterval);
|
var maxRenderInterval = Math.Max(1000 / MinReplayFps, renderInterval);
|
||||||
forcedNextRender = now + maxRenderInterval;
|
forcedNextRender = now + maxRenderInterval;
|
||||||
|
|
||||||
|
RenderTick();
|
||||||
renderBeforeNextTick = false;
|
renderBeforeNextTick = false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Simulate a render tick if it was time to render but we skip actually rendering
|
// Simulate a render tick if it was time to render but we skip actually rendering
|
||||||
if (isTimeToRender || forceRender)
|
if (Renderer.WindowIsSuspended && isTimeToRender)
|
||||||
{
|
{
|
||||||
// Make sure that nextUpdate is set to a proper minimum interval
|
// Make sure that nextUpdate is set to a proper minimum interval
|
||||||
nextRender = now + renderInterval;
|
nextRender = now + renderInterval;
|
||||||
@@ -896,12 +869,6 @@ namespace OpenRA
|
|||||||
// Ensure that we still logic tick despite not rendering
|
// Ensure that we still logic tick despite not rendering
|
||||||
renderBeforeNextTick = false;
|
renderBeforeNextTick = false;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// Avoid busy wait.
|
|
||||||
Thread.Sleep((int)(nextRender - now));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
Thread.Sleep((int)(nextUpdate - now));
|
Thread.Sleep((int)(nextUpdate - now));
|
||||||
@@ -980,8 +947,7 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
Name = "Skirmish Game",
|
Name = "Skirmish Game",
|
||||||
Map = map,
|
Map = map,
|
||||||
AdvertiseOnline = false,
|
AdvertiseOnline = false
|
||||||
AdvertiseOnLocalNetwork = !isSkirmish
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Always connect to local games using the same loopback connection
|
// Always connect to local games using the same loopback connection
|
||||||
@@ -1019,7 +985,7 @@ namespace OpenRA
|
|||||||
Order.Command($"state {Session.ClientState.Ready}")
|
Order.Command($"state {Session.ClientState.Ready}")
|
||||||
};
|
};
|
||||||
|
|
||||||
var map = ModData.MapCache.SingleOrDefault(m => m.Uid == launchMap || Path.GetFileName(m.Path) == launchMap);
|
var map = ModData.MapCache.SingleOrDefault(m => m.Uid == launchMap || Path.GetFileName(m.PackageName) == launchMap);
|
||||||
if (map == null)
|
if (map == null)
|
||||||
throw new ArgumentException($"Could not find map '{launchMap}'.");
|
throw new ArgumentException($"Could not find map '{launchMap}'.");
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Frozen;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OpenRA.Network;
|
using OpenRA.Network;
|
||||||
@@ -40,32 +39,17 @@ namespace OpenRA
|
|||||||
public TimeSpan Duration => EndTimeUtc > StartTimeUtc ? EndTimeUtc - StartTimeUtc : TimeSpan.Zero;
|
public TimeSpan Duration => EndTimeUtc > StartTimeUtc ? EndTimeUtc - StartTimeUtc : TimeSpan.Zero;
|
||||||
|
|
||||||
public IList<Player> Players { get; }
|
public IList<Player> Players { get; }
|
||||||
public FrozenSet<int> DisabledSpawnPoints = FrozenSet<int>.Empty;
|
public HashSet<int> DisabledSpawnPoints = new();
|
||||||
|
public MapPreview MapPreview => Game.ModData.MapCache[MapUid];
|
||||||
public IEnumerable<Player> HumanPlayers { get { return Players.Where(p => p.IsHuman); } }
|
public IEnumerable<Player> HumanPlayers { get { return Players.Where(p => p.IsHuman); } }
|
||||||
public bool IsSinglePlayer => HumanPlayers.Count() == 1;
|
public bool IsSinglePlayer => HumanPlayers.Count() == 1;
|
||||||
|
|
||||||
[FieldLoader.Ignore]
|
|
||||||
public MapGenerationArgs MapGenerationArgs;
|
|
||||||
|
|
||||||
readonly Dictionary<OpenRA.Player, Player> playersByRuntime;
|
readonly Dictionary<OpenRA.Player, Player> playersByRuntime;
|
||||||
|
|
||||||
public GameInformation()
|
public GameInformation()
|
||||||
{
|
{
|
||||||
Players = [];
|
Players = new List<Player>();
|
||||||
playersByRuntime = [];
|
playersByRuntime = new Dictionary<OpenRA.Player, Player>();
|
||||||
}
|
|
||||||
|
|
||||||
public MapPreview GetMapPreview(ModData modData)
|
|
||||||
{
|
|
||||||
var preview = modData.MapCache[MapUid];
|
|
||||||
if (preview.Status != MapStatus.Available && MapGenerationArgs != null)
|
|
||||||
{
|
|
||||||
preview.UpdateFromGenerationArgs(MapGenerationArgs);
|
|
||||||
preview.Generate();
|
|
||||||
}
|
|
||||||
|
|
||||||
return preview;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GameInformation Deserialize(string data, string path)
|
public static GameInformation Deserialize(string data, string path)
|
||||||
@@ -88,10 +72,6 @@ namespace OpenRA
|
|||||||
case "Player":
|
case "Player":
|
||||||
info.Players.Add(FieldLoader.Load<Player>(node.Value));
|
info.Players.Add(FieldLoader.Load<Player>(node.Value));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "MapGenerationArgs":
|
|
||||||
info.MapGenerationArgs = FieldLoader.Load<MapGenerationArgs>(node.Value);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,18 +94,17 @@ namespace OpenRA
|
|||||||
for (var i = 0; i < Players.Count; i++)
|
for (var i = 0; i < Players.Count; i++)
|
||||||
nodes.Add(new MiniYamlNode($"Player@{i}", FieldSaver.Save(Players[i])));
|
nodes.Add(new MiniYamlNode($"Player@{i}", FieldSaver.Save(Players[i])));
|
||||||
|
|
||||||
if (MapGenerationArgs != null)
|
|
||||||
nodes.Add(new MiniYamlNode("MapGenerationArgs", new MiniYaml("", MapGenerationArgs.Serialize())));
|
|
||||||
|
|
||||||
return nodes.WriteToString();
|
return nodes.WriteToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Adds the player information at start-up.</summary>
|
/// <summary>Adds the player information at start-up.</summary>
|
||||||
public void AddPlayer(OpenRA.Player runtimePlayer, Session lobbyInfo)
|
public void AddPlayer(OpenRA.Player runtimePlayer, Session lobbyInfo)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(runtimePlayer);
|
if (runtimePlayer == null)
|
||||||
|
throw new ArgumentNullException(nameof(runtimePlayer));
|
||||||
|
|
||||||
ArgumentNullException.ThrowIfNull(lobbyInfo);
|
if (lobbyInfo == null)
|
||||||
|
throw new ArgumentNullException(nameof(lobbyInfo));
|
||||||
|
|
||||||
// We don't care about spectators and map players
|
// We don't care about spectators and map players
|
||||||
if (runtimePlayer.NonCombatant || !runtimePlayer.Playable)
|
if (runtimePlayer.NonCombatant || !runtimePlayer.Playable)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ namespace OpenRA
|
|||||||
/// You can remove inherited traits by adding a - in front of them as in -TraitName: to inherit everything, but this trait.
|
/// You can remove inherited traits by adding a - in front of them as in -TraitName: to inherit everything, but this trait.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly string Name;
|
public readonly string Name;
|
||||||
readonly TypeDictionary traits = [];
|
readonly TypeDictionary traits = new();
|
||||||
TraitInfo[] constructOrderCache = null;
|
TraitInfo[] constructOrderCache = null;
|
||||||
|
|
||||||
public ActorInfo(ObjectCreator creator, string name, MiniYaml node)
|
public ActorInfo(ObjectCreator creator, string name, MiniYaml node)
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using OpenRA.FileSystem;
|
using OpenRA.FileSystem;
|
||||||
@@ -269,7 +268,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
if (mapRules.Value != null)
|
if (mapRules.Value != null)
|
||||||
{
|
{
|
||||||
var mapFiles = FieldLoader.GetValue<ImmutableArray<string>>("value", mapRules.Value);
|
var mapFiles = FieldLoader.GetValue<string[]>("value", mapRules.Value);
|
||||||
foreach (var f in mapFiles)
|
foreach (var f in mapFiles)
|
||||||
if (AnyFlaggedTraits(modData, MiniYaml.FromStream(fileSystem.Open(f), f)))
|
if (AnyFlaggedTraits(modData, MiniYaml.FromStream(fileSystem.Open(f), f)))
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -10,38 +10,37 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Frozen;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Linq;
|
||||||
|
|
||||||
namespace OpenRA.GameRules
|
namespace OpenRA.GameRules
|
||||||
{
|
{
|
||||||
public class SoundInfo
|
public class SoundInfo
|
||||||
{
|
{
|
||||||
public readonly FrozenDictionary<string, ImmutableArray<string>> Variants = FrozenDictionary<string, ImmutableArray<string>>.Empty;
|
public readonly Dictionary<string, string[]> Variants = new();
|
||||||
public readonly FrozenDictionary<string, ImmutableArray<string>> Prefixes = FrozenDictionary<string, ImmutableArray<string>>.Empty;
|
public readonly Dictionary<string, string[]> Prefixes = new();
|
||||||
public readonly FrozenDictionary<string, ImmutableArray<string>> Voices = FrozenDictionary<string, ImmutableArray<string>>.Empty;
|
public readonly Dictionary<string, string[]> Voices = new();
|
||||||
public readonly FrozenDictionary<string, ImmutableArray<string>> Notifications = FrozenDictionary<string, ImmutableArray<string>>.Empty;
|
public readonly Dictionary<string, string[]> Notifications = new();
|
||||||
public readonly string DefaultVariant = ".aud";
|
public readonly string DefaultVariant = ".aud";
|
||||||
public readonly string DefaultPrefix = "";
|
public readonly string DefaultPrefix = "";
|
||||||
public readonly FrozenSet<string> DisableVariants = FrozenSet<string>.Empty;
|
public readonly HashSet<string> DisableVariants = new();
|
||||||
public readonly FrozenSet<string> DisablePrefixes = FrozenSet<string>.Empty;
|
public readonly HashSet<string> DisablePrefixes = new();
|
||||||
|
|
||||||
public readonly Lazy<FrozenDictionary<string, SoundPool>> VoicePools;
|
public readonly Lazy<Dictionary<string, SoundPool>> VoicePools;
|
||||||
public readonly Lazy<FrozenDictionary<string, SoundPool>> NotificationsPools;
|
public readonly Lazy<Dictionary<string, SoundPool>> NotificationsPools;
|
||||||
|
|
||||||
public SoundInfo(MiniYaml y)
|
public SoundInfo(MiniYaml y)
|
||||||
{
|
{
|
||||||
FieldLoader.Load(this, y);
|
FieldLoader.Load(this, y);
|
||||||
|
|
||||||
VoicePools = Exts.Lazy(() => Voices.ToFrozenDictionary(a => a.Key, a => new SoundPool(1f, SoundPool.DefaultInterruptType, a.Value)));
|
VoicePools = Exts.Lazy(() => Voices.ToDictionary(a => a.Key, a => new SoundPool(1f, SoundPool.DefaultInterruptType, a.Value)));
|
||||||
NotificationsPools = Exts.Lazy(() => ParseSoundPool(y, "Notifications"));
|
NotificationsPools = Exts.Lazy(() => ParseSoundPool(y, "Notifications"));
|
||||||
}
|
}
|
||||||
|
|
||||||
static FrozenDictionary<string, SoundPool> ParseSoundPool(MiniYaml y, string key)
|
static Dictionary<string, SoundPool> ParseSoundPool(MiniYaml y, string key)
|
||||||
{
|
{
|
||||||
|
var ret = new Dictionary<string, SoundPool>();
|
||||||
var classifiction = y.NodeWithKey(key);
|
var classifiction = y.NodeWithKey(key);
|
||||||
var ret = new Dictionary<string, SoundPool>(classifiction.Value.Nodes.Length);
|
|
||||||
foreach (var t in classifiction.Value.Nodes)
|
foreach (var t in classifiction.Value.Nodes)
|
||||||
{
|
{
|
||||||
var volumeModifier = 1f;
|
var volumeModifier = 1f;
|
||||||
@@ -54,12 +53,12 @@ namespace OpenRA.GameRules
|
|||||||
if (interruptTypeNode != null)
|
if (interruptTypeNode != null)
|
||||||
interruptType = FieldLoader.GetValue<SoundPool.InterruptType>(interruptTypeNode.Key, interruptTypeNode.Value.Value);
|
interruptType = FieldLoader.GetValue<SoundPool.InterruptType>(interruptTypeNode.Key, interruptTypeNode.Value.Value);
|
||||||
|
|
||||||
var names = FieldLoader.GetValue<ImmutableArray<string>>(t.Key, t.Value.Value);
|
var names = FieldLoader.GetValue<string[]>(t.Key, t.Value.Value);
|
||||||
var sp = new SoundPool(volumeModifier, interruptType, names);
|
var sp = new SoundPool(volumeModifier, interruptType, names);
|
||||||
ret.Add(t.Key, sp);
|
ret.Add(t.Key, sp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret.ToFrozenDictionary();
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,10 +68,10 @@ namespace OpenRA.GameRules
|
|||||||
public const InterruptType DefaultInterruptType = InterruptType.DoNotPlay;
|
public const InterruptType DefaultInterruptType = InterruptType.DoNotPlay;
|
||||||
public readonly float VolumeModifier;
|
public readonly float VolumeModifier;
|
||||||
public readonly InterruptType Type;
|
public readonly InterruptType Type;
|
||||||
readonly ImmutableArray<string> clips;
|
readonly string[] clips;
|
||||||
readonly List<string> liveclips = [];
|
readonly List<string> liveclips = new();
|
||||||
|
|
||||||
public SoundPool(float volumeModifier, InterruptType interruptType, ImmutableArray<string> clips)
|
public SoundPool(float volumeModifier, InterruptType interruptType, params string[] clips)
|
||||||
{
|
{
|
||||||
VolumeModifier = volumeModifier;
|
VolumeModifier = volumeModifier;
|
||||||
Type = interruptType;
|
Type = interruptType;
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OpenRA.Effects;
|
using OpenRA.Effects;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
@@ -37,7 +36,7 @@ namespace OpenRA.GameRules
|
|||||||
public class WarheadArgs
|
public class WarheadArgs
|
||||||
{
|
{
|
||||||
public WeaponInfo Weapon;
|
public WeaponInfo Weapon;
|
||||||
public int[] DamageModifiers = [];
|
public int[] DamageModifiers = Array.Empty<int>();
|
||||||
public WPos? Source;
|
public WPos? Source;
|
||||||
public WRot ImpactOrientation;
|
public WRot ImpactOrientation;
|
||||||
public WPos ImpactPosition;
|
public WPos ImpactPosition;
|
||||||
@@ -83,13 +82,13 @@ namespace OpenRA.GameRules
|
|||||||
public readonly WVec FollowingBurstTargetOffset = WVec.Zero;
|
public readonly WVec FollowingBurstTargetOffset = WVec.Zero;
|
||||||
|
|
||||||
[Desc("The sound played each time the weapon is fired.")]
|
[Desc("The sound played each time the weapon is fired.")]
|
||||||
public readonly ImmutableArray<string> Report = default;
|
public readonly string[] Report = null;
|
||||||
|
|
||||||
[Desc("Sound played only on first burst in a salvo.")]
|
[Desc("Sound played only on first burst in a salvo.")]
|
||||||
public readonly ImmutableArray<string> StartBurstReport = default;
|
public readonly string[] StartBurstReport = null;
|
||||||
|
|
||||||
[Desc("The sound played when the weapon is reloaded.")]
|
[Desc("The sound played when the weapon is reloaded.")]
|
||||||
public readonly ImmutableArray<string> AfterFireSound = default;
|
public readonly string[] AfterFireSound = null;
|
||||||
|
|
||||||
[Desc("Delay in ticks to play reloading sound.")]
|
[Desc("Delay in ticks to play reloading sound.")]
|
||||||
public readonly int AfterFireSoundDelay = 0;
|
public readonly int AfterFireSoundDelay = 0;
|
||||||
@@ -117,7 +116,7 @@ namespace OpenRA.GameRules
|
|||||||
|
|
||||||
[Desc("Delay in ticks between firing shots from the same ammo magazine. If one entry, it will be used for all bursts.",
|
[Desc("Delay in ticks between firing shots from the same ammo magazine. If one entry, it will be used for all bursts.",
|
||||||
"If multiple entries, their number needs to match Burst - 1.")]
|
"If multiple entries, their number needs to match Burst - 1.")]
|
||||||
public readonly ImmutableArray<int> BurstDelays = [5];
|
public readonly int[] BurstDelays = { 5 };
|
||||||
|
|
||||||
[Desc("The minimum range the weapon can fire.")]
|
[Desc("The minimum range the weapon can fire.")]
|
||||||
public readonly WDist MinRange = WDist.Zero;
|
public readonly WDist MinRange = WDist.Zero;
|
||||||
@@ -129,7 +128,7 @@ namespace OpenRA.GameRules
|
|||||||
public readonly IProjectileInfo Projectile;
|
public readonly IProjectileInfo Projectile;
|
||||||
|
|
||||||
[FieldLoader.LoadUsing(nameof(LoadWarheads))]
|
[FieldLoader.LoadUsing(nameof(LoadWarheads))]
|
||||||
public readonly ImmutableArray<IWarhead> Warheads = [];
|
public readonly List<IWarhead> Warheads = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This constructor is used solely for documentation generation.
|
/// This constructor is used solely for documentation generation.
|
||||||
@@ -140,7 +139,7 @@ namespace OpenRA.GameRules
|
|||||||
{
|
{
|
||||||
// Resolve any weapon-level yaml inheritance or removals
|
// Resolve any weapon-level yaml inheritance or removals
|
||||||
// HACK: The "Defaults" sequence syntax prevents us from doing this generally during yaml parsing
|
// HACK: The "Defaults" sequence syntax prevents us from doing this generally during yaml parsing
|
||||||
content = content.WithNodes(MiniYaml.Merge([content.Nodes]));
|
content = content.WithNodes(MiniYaml.Merge(new IReadOnlyCollection<MiniYamlNode>[] { content.Nodes }));
|
||||||
FieldLoader.Load(this, content);
|
FieldLoader.Load(this, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +170,7 @@ namespace OpenRA.GameRules
|
|||||||
retList.Add(ret);
|
retList.Add(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
return retList.ToImmutableArray();
|
return retList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsValidTarget(BitSet<TargetableType> targetTypes)
|
public bool IsValidTarget(BitSet<TargetableType> targetTypes)
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using OpenRA.Traits;
|
|
||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
{
|
{
|
||||||
@@ -32,7 +31,6 @@ namespace OpenRA
|
|||||||
[FieldLoader.Require]
|
[FieldLoader.Require]
|
||||||
public readonly string DefaultSpeed;
|
public readonly string DefaultSpeed;
|
||||||
|
|
||||||
[IncludeFluentReferences(LintDictionaryReference.Values)]
|
|
||||||
[FieldLoader.LoadUsing(nameof(LoadSpeeds))]
|
[FieldLoader.LoadUsing(nameof(LoadSpeeds))]
|
||||||
public readonly Dictionary<string, GameSpeed> Speeds;
|
public readonly Dictionary<string, GameSpeed> Speeds;
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
using OpenRA.Support;
|
using OpenRA.Support;
|
||||||
@@ -74,10 +73,10 @@ namespace OpenRA.Graphics
|
|||||||
shadow, pos, offset - new WVec(0, 0, height), CurrentSequence.ShadowZOffset + zOffset + height, palette,
|
shadow, pos, offset - new WVec(0, 0, height), CurrentSequence.ShadowZOffset + zOffset + height, palette,
|
||||||
CurrentSequence.Scale, 1f, float3.Ones, tintModifiers,
|
CurrentSequence.Scale, 1f, float3.Ones, tintModifiers,
|
||||||
true, rotation);
|
true, rotation);
|
||||||
return [shadowRenderable, imageRenderable];
|
return new IRenderable[] { shadowRenderable, imageRenderable };
|
||||||
}
|
}
|
||||||
|
|
||||||
return [imageRenderable];
|
return new IRenderable[] { imageRenderable };
|
||||||
}
|
}
|
||||||
|
|
||||||
public IRenderable[] RenderUI(WorldRenderer wr, int2 pos, in WVec offset, int zOffset, PaletteReference palette, float scale = 1f, float rotation = 0f)
|
public IRenderable[] RenderUI(WorldRenderer wr, int2 pos, in WVec offset, int zOffset, PaletteReference palette, float scale = 1f, float rotation = 0f)
|
||||||
@@ -93,10 +92,10 @@ namespace OpenRA.Graphics
|
|||||||
{
|
{
|
||||||
var shadowPos = pos - new int2((int)(scale * shadow.Size.X / 2), (int)(scale * shadow.Size.Y / 2));
|
var shadowPos = pos - new int2((int)(scale * shadow.Size.X / 2), (int)(scale * shadow.Size.Y / 2));
|
||||||
var shadowRenderable = new UISpriteRenderable(shadow, WPos.Zero + offset, shadowPos, CurrentSequence.ShadowZOffset + zOffset, palette, scale, 1f, rotation);
|
var shadowRenderable = new UISpriteRenderable(shadow, WPos.Zero + offset, shadowPos, CurrentSequence.ShadowZOffset + zOffset, palette, scale, 1f, rotation);
|
||||||
return [shadowRenderable, imageRenderable];
|
return new IRenderable[] { shadowRenderable, imageRenderable };
|
||||||
}
|
}
|
||||||
|
|
||||||
return [imageRenderable];
|
return new IRenderable[] { imageRenderable };
|
||||||
}
|
}
|
||||||
|
|
||||||
public Rectangle ScreenBounds(WorldRenderer wr, WPos pos, in WVec offset)
|
public Rectangle ScreenBounds(WorldRenderer wr, WPos pos, in WVec offset)
|
||||||
@@ -251,9 +250,9 @@ namespace OpenRA.Graphics
|
|||||||
return sequences.GetSequence(Name, sequenceName);
|
return sequences.GetSequence(Name, sequenceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetRandomExistingSequence(ImmutableArray<string> sequences, MersenneTwister random)
|
public string GetRandomExistingSequence(string[] sequences, MersenneTwister random)
|
||||||
{
|
{
|
||||||
return sequences.Where(HasSequence).RandomOrDefault(random);
|
return sequences.Where(s => HasSequence(s)).RandomOrDefault(random);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,7 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Frozen;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OpenRA.FileSystem;
|
using OpenRA.FileSystem;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
@@ -49,9 +47,9 @@ namespace OpenRA.Graphics
|
|||||||
public readonly string Image2x = null;
|
public readonly string Image2x = null;
|
||||||
public readonly string Image3x = null;
|
public readonly string Image3x = null;
|
||||||
|
|
||||||
public readonly ImmutableArray<int> PanelRegion = default;
|
public readonly int[] PanelRegion = null;
|
||||||
public readonly PanelSides PanelSides = PanelSides.All;
|
public readonly PanelSides PanelSides = PanelSides.All;
|
||||||
public readonly FrozenDictionary<string, Rectangle> Regions = FrozenDictionary<string, Rectangle>.Empty;
|
public readonly Dictionary<string, Rectangle> Regions = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IReadOnlyDictionary<string, Collection> Collections => collections;
|
public static IReadOnlyDictionary<string, Collection> Collections => collections;
|
||||||
@@ -73,11 +71,11 @@ namespace OpenRA.Graphics
|
|||||||
dpiScale = Game.Renderer.WindowScale;
|
dpiScale = Game.Renderer.WindowScale;
|
||||||
|
|
||||||
fileSystem = modData.DefaultFileSystem;
|
fileSystem = modData.DefaultFileSystem;
|
||||||
collections = [];
|
collections = new Dictionary<string, Collection>();
|
||||||
cachedSheets = [];
|
cachedSheets = new Dictionary<string, (Sheet, int)>();
|
||||||
cachedSprites = [];
|
cachedSprites = new Dictionary<string, Dictionary<string, Sprite>>();
|
||||||
cachedPanelSprites = [];
|
cachedPanelSprites = new Dictionary<string, Sprite[]>();
|
||||||
cachedCollectionSheets = [];
|
cachedCollectionSheets = new Dictionary<Collection, (Sheet, int)>();
|
||||||
|
|
||||||
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
||||||
var chrome = MiniYaml.Merge(modData.Manifest.Chrome
|
var chrome = MiniYaml.Merge(modData.Manifest.Chrome
|
||||||
@@ -172,7 +170,7 @@ namespace OpenRA.Graphics
|
|||||||
var sheetDensity = SheetForCollection(collection);
|
var sheetDensity = SheetForCollection(collection);
|
||||||
if (cachedCollection == null)
|
if (cachedCollection == null)
|
||||||
{
|
{
|
||||||
cachedCollection = [];
|
cachedCollection = new Dictionary<string, Sprite>();
|
||||||
cachedSprites.Add(collectionName, cachedCollection);
|
cachedSprites.Add(collectionName, cachedCollection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,11 +240,11 @@ namespace OpenRA.Graphics
|
|||||||
// PERF: We don't need to search for images if there are no definitions.
|
// 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.
|
// 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.Count == 0)
|
||||||
return [];
|
return Array.Empty<Sprite>();
|
||||||
|
|
||||||
// Support manual definitions for unusual dialog layouts
|
// Support manual definitions for unusual dialog layouts
|
||||||
sprites =
|
sprites = new[]
|
||||||
[
|
{
|
||||||
TryGetImage(collectionName, "corner-tl"),
|
TryGetImage(collectionName, "corner-tl"),
|
||||||
TryGetImage(collectionName, "border-t"),
|
TryGetImage(collectionName, "border-t"),
|
||||||
TryGetImage(collectionName, "corner-tr"),
|
TryGetImage(collectionName, "corner-tr"),
|
||||||
@@ -256,7 +254,7 @@ namespace OpenRA.Graphics
|
|||||||
TryGetImage(collectionName, "corner-bl"),
|
TryGetImage(collectionName, "corner-bl"),
|
||||||
TryGetImage(collectionName, "border-b"),
|
TryGetImage(collectionName, "border-b"),
|
||||||
TryGetImage(collectionName, "corner-br")
|
TryGetImage(collectionName, "corner-br")
|
||||||
];
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
cachedPanelSprites.Add(collectionName, sprites);
|
cachedPanelSprites.Add(collectionName, sprites);
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
using OpenRA.Traits;
|
|
||||||
|
|
||||||
namespace OpenRA.Graphics
|
namespace OpenRA.Graphics
|
||||||
{
|
{
|
||||||
@@ -30,8 +29,8 @@ namespace OpenRA.Graphics
|
|||||||
public IHardwareCursor[] Cursors;
|
public IHardwareCursor[] Cursors;
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly Dictionary<string, Cursor> cursors = [];
|
readonly Dictionary<string, Cursor> cursors = new();
|
||||||
public readonly SheetBuilder SheetBuilder;
|
readonly SheetBuilder sheetBuilder;
|
||||||
readonly GraphicSettings graphicSettings;
|
readonly GraphicSettings graphicSettings;
|
||||||
|
|
||||||
Cursor cursor;
|
Cursor cursor;
|
||||||
@@ -40,35 +39,19 @@ namespace OpenRA.Graphics
|
|||||||
readonly bool hardwareCursorsDisabled = false;
|
readonly bool hardwareCursorsDisabled = false;
|
||||||
bool hardwareCursorsDoubled = false;
|
bool hardwareCursorsDoubled = false;
|
||||||
|
|
||||||
public CursorManager(ModData modData)
|
public CursorManager(CursorProvider cursorProvider, int cursorSheetSize)
|
||||||
{
|
{
|
||||||
|
hardwareCursorsDisabled = Game.Settings.Graphics.DisableHardwareCursors;
|
||||||
|
|
||||||
graphicSettings = Game.Settings.Graphics;
|
graphicSettings = Game.Settings.Graphics;
|
||||||
hardwareCursorsDisabled = graphicSettings.DisableHardwareCursors;
|
sheetBuilder = new SheetBuilder(SheetType.BGRA, cursorSheetSize);
|
||||||
SheetBuilder = new SheetBuilder(SheetType.BGRA, modData.Manifest.RendererConstants.CursorSheetSize);
|
|
||||||
|
|
||||||
// Overwrite previous definitions if there are duplicates
|
|
||||||
var pals = new Dictionary<string, IProvidesCursorPaletteInfo>();
|
|
||||||
foreach (var p in modData.DefaultRules.Actors[SystemActors.World].TraitInfos<IProvidesCursorPaletteInfo>())
|
|
||||||
if (p.Palette != null)
|
|
||||||
pals[p.Palette] = p;
|
|
||||||
|
|
||||||
var paletteCache = new Cache<string, ImmutablePalette>(p => pals[p].ReadPalette(modData.DefaultFileSystem));
|
|
||||||
var frameCache = new FrameCache(modData.DefaultFileSystem, modData.SpriteLoaders);
|
|
||||||
|
|
||||||
// Sort the cursors for better packing onto the sheet.
|
// Sort the cursors for better packing onto the sheet.
|
||||||
foreach (var kv in modData.Cursors)
|
foreach (var kv in cursorProvider.Cursors
|
||||||
|
.OrderBy(kvp => kvp.Value.Frames.Max(f => f.Size.Height)))
|
||||||
{
|
{
|
||||||
var cursorSprites = frameCache[kv.Value.Src];
|
var frames = kv.Value.Frames;
|
||||||
var length = kv.Value.Length ?? cursorSprites.Length - kv.Value.Start;
|
var palette = !string.IsNullOrEmpty(kv.Value.Palette) ? cursorProvider.Palettes[kv.Value.Palette] : null;
|
||||||
|
|
||||||
if (kv.Value.Start > cursorSprites.Length)
|
|
||||||
throw new YamlException($"Cursor {kv.Value.Name}: {nameof(kv.Value.Start)} is greater than the length of the sprite sequence.");
|
|
||||||
|
|
||||||
if (kv.Value.Length > cursorSprites.Length)
|
|
||||||
throw new YamlException($"Cursor {kv.Value.Name}: {nameof(kv.Value.Length)} is greater than the length of the sprite sequence.");
|
|
||||||
|
|
||||||
var frames = cursorSprites.Skip(kv.Value.Start).Take(length).ToArray();
|
|
||||||
var palette = !string.IsNullOrEmpty(kv.Value.Palette) ? paletteCache[kv.Value.Palette] : null;
|
|
||||||
|
|
||||||
var c = new Cursor
|
var c = new Cursor
|
||||||
{
|
{
|
||||||
@@ -99,7 +82,7 @@ namespace OpenRA.Graphics
|
|||||||
type = SpriteFrameType.Bgra32;
|
type = SpriteFrameType.Bgra32;
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Sprites[c.Length++] = SheetBuilder.Add(data, type, f.Size, 0, hotspot);
|
c.Sprites[c.Length++] = sheetBuilder.Add(data, type, f.Size, 0, hotspot);
|
||||||
|
|
||||||
// Bounds relative to the hotspot
|
// Bounds relative to the hotspot
|
||||||
c.Bounds = Rectangle.Union(c.Bounds, new Rectangle(hotspot, f.Size));
|
c.Bounds = Rectangle.Union(c.Bounds, new Rectangle(hotspot, f.Size));
|
||||||
@@ -111,13 +94,9 @@ namespace OpenRA.Graphics
|
|||||||
cursors.Add(kv.Key, c);
|
cursors.Add(kv.Key, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow the utility to create a cursor manager.
|
|
||||||
if (Game.Renderer != null)
|
|
||||||
{
|
|
||||||
CreateOrUpdateHardwareCursors();
|
CreateOrUpdateHardwareCursors();
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void CreateOrUpdateHardwareCursors()
|
void CreateOrUpdateHardwareCursors()
|
||||||
{
|
{
|
||||||
@@ -149,7 +128,7 @@ namespace OpenRA.Graphics
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SheetBuilder.Current?.ReleaseBuffer();
|
sheetBuilder.Current.ReleaseBuffer();
|
||||||
|
|
||||||
hardwareCursorsDoubled = graphicSettings.CursorDouble;
|
hardwareCursorsDoubled = graphicSettings.CursorDouble;
|
||||||
}
|
}
|
||||||
@@ -254,7 +233,7 @@ namespace OpenRA.Graphics
|
|||||||
var height = frame.Size.Height;
|
var height = frame.Size.Height;
|
||||||
|
|
||||||
if (width == 0 || height == 0)
|
if (width == 0 || height == 0)
|
||||||
return [];
|
return Array.Empty<byte>();
|
||||||
|
|
||||||
var data = new byte[4 * width * height];
|
var data = new byte[4 * width * height];
|
||||||
unsafe
|
unsafe
|
||||||
@@ -300,18 +279,21 @@ namespace OpenRA.Graphics
|
|||||||
{
|
{
|
||||||
for (var i = 0; i < c.Cursors.Length; i++)
|
for (var i = 0; i < c.Cursors.Length; i++)
|
||||||
{
|
{
|
||||||
c.Cursors[i]?.Dispose();
|
if (c.Cursors[i] != null)
|
||||||
|
{
|
||||||
|
c.Cursors[i].Dispose();
|
||||||
c.Cursors[i] = null;
|
c.Cursors[i] = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
ClearHardwareCursors();
|
ClearHardwareCursors();
|
||||||
|
|
||||||
cursors.Clear();
|
cursors.Clear();
|
||||||
SheetBuilder.Dispose();
|
sheetBuilder.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
67
OpenRA.Game/Graphics/CursorProvider.cs
Normal file
67
OpenRA.Game/Graphics/CursorProvider.cs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#region Copyright & License Information
|
||||||
|
/*
|
||||||
|
* Copyright (c) The OpenRA Developers and Contributors
|
||||||
|
* This file is part of OpenRA, which is free software. It is made
|
||||||
|
* available to you under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation, either version 3 of
|
||||||
|
* the License, or (at your option) any later version. For more
|
||||||
|
* information, see COPYING.
|
||||||
|
*/
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using OpenRA.Traits;
|
||||||
|
|
||||||
|
namespace OpenRA.Graphics
|
||||||
|
{
|
||||||
|
public sealed class CursorProvider
|
||||||
|
{
|
||||||
|
public readonly IReadOnlyDictionary<string, CursorSequence> Cursors;
|
||||||
|
public readonly IReadOnlyDictionary<string, ImmutablePalette> Palettes;
|
||||||
|
|
||||||
|
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)));
|
||||||
|
|
||||||
|
var cursorsYaml = new MiniYaml(null, sequenceYaml).NodeWithKey("Cursors").Value;
|
||||||
|
|
||||||
|
// Overwrite previous definitions if there are duplicates
|
||||||
|
var pals = new Dictionary<string, IProvidesCursorPaletteInfo>();
|
||||||
|
foreach (var p in modData.DefaultRules.Actors[SystemActors.World].TraitInfos<IProvidesCursorPaletteInfo>())
|
||||||
|
if (p.Palette != null)
|
||||||
|
pals[p.Palette] = p;
|
||||||
|
|
||||||
|
Palettes = cursorsYaml.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 sequence in s.Value.Nodes)
|
||||||
|
cursors.Add(sequence.Key, new CursorSequence(frameCache, sequence.Key, s.Key, s.Value.Value, sequence.Value));
|
||||||
|
|
||||||
|
Cursors = cursors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasCursorSequence(string cursor)
|
||||||
|
{
|
||||||
|
return Cursors.ContainsKey(cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CursorSequence GetCursorSequence(string cursor)
|
||||||
|
{
|
||||||
|
try { return Cursors[cursor]; }
|
||||||
|
catch (KeyNotFoundException)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Cursor does not have a sequence `{cursor}`");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,26 +9,50 @@
|
|||||||
*/
|
*/
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace OpenRA.Graphics
|
namespace OpenRA.Graphics
|
||||||
{
|
{
|
||||||
public class CursorSequence
|
public class CursorSequence
|
||||||
{
|
{
|
||||||
public readonly string Name;
|
public readonly string Name;
|
||||||
public readonly string Src;
|
|
||||||
public readonly int Start;
|
public readonly int Start;
|
||||||
public readonly int? Length;
|
public readonly int Length;
|
||||||
public readonly string Palette;
|
public readonly string Palette;
|
||||||
public readonly int2 Hotspot;
|
public readonly int2 Hotspot;
|
||||||
|
|
||||||
public CursorSequence(string name, string cursorSrc, string palette, MiniYaml info)
|
public readonly ISpriteFrame[] Frames;
|
||||||
|
|
||||||
|
public CursorSequence(FrameCache cache, string name, string cursorSrc, string palette, MiniYaml info)
|
||||||
{
|
{
|
||||||
var d = info.ToDictionary();
|
var d = info.ToDictionary();
|
||||||
|
|
||||||
|
Start = Exts.ParseInt32Invariant(d["Start"].Value);
|
||||||
Palette = palette;
|
Palette = palette;
|
||||||
Name = name;
|
Name = name;
|
||||||
Src = cursorSrc;
|
|
||||||
|
|
||||||
if (d.TryGetValue("X", out var yaml))
|
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 == "*"))
|
||||||
|
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
|
||||||
|
Length = 1;
|
||||||
|
|
||||||
|
Frames = Frames.Take(Length).ToArray();
|
||||||
|
|
||||||
|
if (Start > cursorSprites.Length)
|
||||||
|
throw new YamlException($"Cursor {name}: {nameof(Start)} is greater than the length of the sprite sequence.");
|
||||||
|
|
||||||
|
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))
|
||||||
{
|
{
|
||||||
Exts.TryParseInt32Invariant(yaml.Value, out var x);
|
Exts.TryParseInt32Invariant(yaml.Value, out var x);
|
||||||
Hotspot = Hotspot.WithX(x);
|
Hotspot = Hotspot.WithX(x);
|
||||||
@@ -39,14 +63,6 @@ namespace OpenRA.Graphics
|
|||||||
Exts.TryParseInt32Invariant(yaml.Value, out var y);
|
Exts.TryParseInt32Invariant(yaml.Value, out var y);
|
||||||
Hotspot = Hotspot.WithY(y);
|
Hotspot = Hotspot.WithY(y);
|
||||||
}
|
}
|
||||||
|
|
||||||
Start = Exts.ParseInt32Invariant(d["Start"].Value);
|
|
||||||
if (d.TryGetValue("Length", out yaml))
|
|
||||||
Length = yaml.Value != "*" ? Exts.ParseInt32Invariant(yaml.Value) : null;
|
|
||||||
else if (d.TryGetValue("End", out yaml) && yaml.Value == "*")
|
|
||||||
Length = yaml.Value != "*" ? Exts.ParseInt32Invariant(yaml.Value) - Start : null;
|
|
||||||
else
|
|
||||||
Length = 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ namespace OpenRA.Graphics
|
|||||||
public ITexture ColorShifts { get; }
|
public ITexture ColorShifts { get; }
|
||||||
|
|
||||||
public int Height { get; private set; }
|
public int Height { get; private set; }
|
||||||
readonly Dictionary<string, ImmutablePalette> palettes = [];
|
readonly Dictionary<string, ImmutablePalette> palettes = new();
|
||||||
readonly Dictionary<string, MutablePalette> mutablePalettes = [];
|
readonly Dictionary<string, MutablePalette> mutablePalettes = new();
|
||||||
readonly Dictionary<string, int> indices = [];
|
readonly Dictionary<string, int> indices = new();
|
||||||
byte[] buffer = [];
|
byte[] buffer = Array.Empty<byte>();
|
||||||
float[] colorShiftBuffer = [];
|
float[] colorShiftBuffer = Array.Empty<float>();
|
||||||
|
|
||||||
public HardwarePalette()
|
public HardwarePalette()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -31,13 +31,25 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
public interface IModelWidget
|
public interface IModelWidget
|
||||||
{
|
{
|
||||||
string Palette { get; }
|
public string Palette { get; }
|
||||||
float Scale { get; }
|
public float Scale { get; }
|
||||||
void Setup(Func<bool> isVisible, Func<string> getPalette, Func<string> getPlayerPalette,
|
public void Setup(Func<bool> isVisible, Func<string> getPalette, Func<string> getPlayerPalette,
|
||||||
Func<float> getScale, Func<IModel> getVoxel, Func<WRot> getRotation);
|
Func<float> getScale, Func<IModel> getVoxel, Func<WRot> getRotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly record struct ModelRenderData(int Start, int Count, Sheet Sheet);
|
public readonly struct ModelRenderData
|
||||||
|
{
|
||||||
|
public readonly int Start;
|
||||||
|
public readonly int Count;
|
||||||
|
public readonly Sheet Sheet;
|
||||||
|
|
||||||
|
public ModelRenderData(int start, int count, Sheet sheet)
|
||||||
|
{
|
||||||
|
Start = start;
|
||||||
|
Count = count;
|
||||||
|
Sheet = sheet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public interface IModelCacheInfo : ITraitInfoInterface { }
|
public interface IModelCacheInfo : ITraitInfoInterface { }
|
||||||
|
|
||||||
|
|||||||
@@ -43,11 +43,11 @@ namespace OpenRA.Graphics
|
|||||||
: base("model")
|
: base("model")
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
public override ShaderVertexAttribute[] Attributes { get; } =
|
public override ShaderVertexAttribute[] Attributes { get; } = new[]
|
||||||
[
|
{
|
||||||
new ShaderVertexAttribute("aVertexPosition", ShaderVertexAttributeType.Float, 3, 0),
|
new ShaderVertexAttribute("aVertexPosition", ShaderVertexAttributeType.Float, 3, 0),
|
||||||
new ShaderVertexAttribute("aVertexTexCoord", ShaderVertexAttributeType.Float, 4, 12),
|
new ShaderVertexAttribute("aVertexTexCoord", ShaderVertexAttributeType.Float, 4, 12),
|
||||||
new ShaderVertexAttribute("aVertexTexMetadata", ShaderVertexAttributeType.Float, 2, 28),
|
new ShaderVertexAttribute("aVertexTexMetadata", ShaderVertexAttributeType.Float, 2, 28),
|
||||||
];
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
|
|
||||||
@@ -65,18 +64,18 @@ namespace OpenRA.Graphics
|
|||||||
Buffer.BlockCopy(colors, 0, destination, destinationOffset * 4, Palette.Size * 4);
|
Buffer.BlockCopy(colors, 0, destination, destinationOffset * 4, Palette.Size * 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImmutablePalette(string filename, ImmutableArray<int> remapTransparent, ImmutableArray<int> remap)
|
public ImmutablePalette(string filename, int[] remapTransparent, int[] remap)
|
||||||
{
|
{
|
||||||
using (var s = File.OpenRead(filename))
|
using (var s = File.OpenRead(filename))
|
||||||
LoadFromStream(s, remapTransparent, remap);
|
LoadFromStream(s, remapTransparent, remap);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImmutablePalette(Stream s, ImmutableArray<int> remapTransparent, ImmutableArray<int> remapShadow)
|
public ImmutablePalette(Stream s, int[] remapTransparent, int[] remapShadow)
|
||||||
{
|
{
|
||||||
LoadFromStream(s, remapTransparent, remapShadow);
|
LoadFromStream(s, remapTransparent, remapShadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoadFromStream(Stream s, ImmutableArray<int> remapTransparent, ImmutableArray<int> remapShadow)
|
void LoadFromStream(Stream s, int[] remapTransparent, int[] remapShadow)
|
||||||
{
|
{
|
||||||
using (var reader = new BinaryReader(s))
|
using (var reader = new BinaryReader(s))
|
||||||
for (var i = 0; i < Palette.Size; i++)
|
for (var i = 0; i < Palette.Size; i++)
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ namespace OpenRA
|
|||||||
void PumpInput(IInputHandler inputHandler);
|
void PumpInput(IInputHandler inputHandler);
|
||||||
string GetClipboardText();
|
string GetClipboardText();
|
||||||
bool SetClipboardText(string text);
|
bool SetClipboardText(string text);
|
||||||
bool TryOpenUrl(string url);
|
|
||||||
|
|
||||||
void GrabWindowMouseFocus();
|
void GrabWindowMouseFocus();
|
||||||
void ReleaseWindowMouseFocus();
|
void ReleaseWindowMouseFocus();
|
||||||
@@ -84,8 +83,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
public interface IGraphicsContext : IDisposable
|
public interface IGraphicsContext : IDisposable
|
||||||
{
|
{
|
||||||
IVertexBuffer<T> CreateEmptyVertexBuffer<T>(int size) where T : struct;
|
IVertexBuffer<T> CreateVertexBuffer<T>(int size) where T : struct;
|
||||||
IVertexBuffer<T> CreateVertexBuffer<T>(T[] data, bool dynamic = true) where T : struct;
|
|
||||||
T[] CreateVertices<T>(int size) where T : struct;
|
T[] CreateVertices<T>(int size) where T : struct;
|
||||||
IIndexBuffer CreateIndexBuffer(uint[] indices);
|
IIndexBuffer CreateIndexBuffer(uint[] indices);
|
||||||
ITexture CreateTexture();
|
ITexture CreateTexture();
|
||||||
@@ -136,7 +134,7 @@ namespace OpenRA
|
|||||||
void SetVec(string name, float x);
|
void SetVec(string name, float x);
|
||||||
void SetVec(string name, float x, float y);
|
void SetVec(string name, float x, float y);
|
||||||
void SetVec(string name, float x, float y, float z);
|
void SetVec(string name, float x, float y, float z);
|
||||||
void SetVec(string name, ReadOnlyMemory<float> vec, int length);
|
void SetVec(string name, float[] vec, int length);
|
||||||
void SetTexture(string param, ITexture texture);
|
void SetTexture(string param, ITexture texture);
|
||||||
void SetMatrix(string param, float[] mtx);
|
void SetMatrix(string param, float[] mtx);
|
||||||
void PrepareRender();
|
void PrepareRender();
|
||||||
@@ -181,6 +179,12 @@ namespace OpenRA
|
|||||||
TriangleList,
|
TriangleList,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public readonly struct Range<T>
|
||||||
|
{
|
||||||
|
public readonly T Start, End;
|
||||||
|
public Range(T start, T end) { Start = start; End = end; }
|
||||||
|
}
|
||||||
|
|
||||||
public enum WindowMode
|
public enum WindowMode
|
||||||
{
|
{
|
||||||
Windowed,
|
Windowed,
|
||||||
|
|||||||
@@ -10,19 +10,19 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Immutable;
|
using System.Linq;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
|
|
||||||
namespace OpenRA.Graphics
|
namespace OpenRA.Graphics
|
||||||
{
|
{
|
||||||
public class PlayerColorRemap : IPaletteRemap
|
public class PlayerColorRemap : IPaletteRemap
|
||||||
{
|
{
|
||||||
readonly ImmutableArray<int> remapIndices;
|
readonly int[] remapIndices;
|
||||||
readonly float hue;
|
readonly float hue;
|
||||||
readonly float saturation;
|
readonly float saturation;
|
||||||
readonly float value;
|
readonly float value;
|
||||||
|
|
||||||
public PlayerColorRemap(ImmutableArray<int> remapIndices, Color color)
|
public PlayerColorRemap(int[] remapIndices, Color color)
|
||||||
{
|
{
|
||||||
this.remapIndices = remapIndices;
|
this.remapIndices = remapIndices;
|
||||||
|
|
||||||
|
|||||||
@@ -14,20 +14,39 @@ using System.Runtime.InteropServices;
|
|||||||
namespace OpenRA.Graphics
|
namespace OpenRA.Graphics
|
||||||
{
|
{
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public readonly record struct RenderPostProcessPassVertex(float X, float Y);
|
public readonly struct RenderPostProcessPassVertex
|
||||||
|
{
|
||||||
|
public readonly float X, Y;
|
||||||
|
|
||||||
|
public RenderPostProcessPassVertex(float x, float y)
|
||||||
|
{
|
||||||
|
X = x; Y = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public readonly record struct RenderPostProcessPassTexturedVertex(float X, float Y, float S, float T);
|
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 sealed class RenderPostProcessPassShaderBindings : ShaderBindings
|
||||||
{
|
{
|
||||||
public RenderPostProcessPassShaderBindings(string name)
|
public RenderPostProcessPassShaderBindings(string name)
|
||||||
: base("postprocess", "postprocess_" + name) { }
|
: base("postprocess", "postprocess_" + name) { }
|
||||||
|
|
||||||
public override ShaderVertexAttribute[] Attributes { get; } =
|
public override ShaderVertexAttribute[] Attributes { get; } = new[]
|
||||||
[
|
{
|
||||||
new ShaderVertexAttribute("aVertexPosition", ShaderVertexAttributeType.Float, 2, 0)
|
new ShaderVertexAttribute("aVertexPosition", ShaderVertexAttributeType.Float, 2, 0)
|
||||||
];
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class RenderPostProcessPassTexturedShaderBindings : ShaderBindings
|
public sealed class RenderPostProcessPassTexturedShaderBindings : ShaderBindings
|
||||||
@@ -36,10 +55,10 @@ namespace OpenRA.Graphics
|
|||||||
: base("postprocess_textured", "postprocess_textured_" + name)
|
: base("postprocess_textured", "postprocess_textured_" + name)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
public override ShaderVertexAttribute[] Attributes { get; } =
|
public override ShaderVertexAttribute[] Attributes { get; } = new[]
|
||||||
[
|
{
|
||||||
new ShaderVertexAttribute("aVertexPosition", ShaderVertexAttributeType.Float, 2, 0),
|
new ShaderVertexAttribute("aVertexPosition", ShaderVertexAttributeType.Float, 2, 0),
|
||||||
new ShaderVertexAttribute("aVertexTexCoord", ShaderVertexAttributeType.Float, 2, 8),
|
new ShaderVertexAttribute("aVertexTexCoord", ShaderVertexAttributeType.Float, 2, 8),
|
||||||
];
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ namespace OpenRA.Graphics
|
|||||||
{
|
{
|
||||||
var tr = new float3(br.X, tl.Y, tl.Z);
|
var tr = new float3(br.X, tl.Y, tl.Z);
|
||||||
var bl = new float3(tl.X, br.Y, br.Z);
|
var bl = new float3(tl.X, br.Y, br.Z);
|
||||||
DrawPolygon([tl, tr, br, bl], width, color, blendMode);
|
DrawPolygon(new[] { tl, tr, br, bl }, width, color, blendMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FillRect(in float3 tl, in float3 br, Color color, BlendMode blendMode = BlendMode.Alpha)
|
public void FillRect(in float3 tl, in float3 br, Color color, BlendMode blendMode = BlendMode.Alpha)
|
||||||
|
|||||||
@@ -37,23 +37,23 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
public interface ISpriteSequenceLoader
|
public interface ISpriteSequenceLoader
|
||||||
{
|
{
|
||||||
|
int BgraSheetSize { get; }
|
||||||
|
int IndexedSheetSize { get; }
|
||||||
IReadOnlyDictionary<string, ISpriteSequence> ParseSequences(ModData modData, string tileSet, SpriteCache cache, MiniYamlNode node);
|
IReadOnlyDictionary<string, ISpriteSequence> ParseSequences(ModData modData, string tileSet, SpriteCache cache, MiniYamlNode node);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class SequenceSet : IDisposable
|
public sealed class SequenceSet : IDisposable
|
||||||
{
|
{
|
||||||
public readonly string TileSet;
|
|
||||||
readonly ModData modData;
|
readonly ModData modData;
|
||||||
|
readonly string tileSet;
|
||||||
readonly IReadOnlyDictionary<string, IReadOnlyDictionary<string, ISpriteSequence>> images;
|
readonly IReadOnlyDictionary<string, IReadOnlyDictionary<string, ISpriteSequence>> images;
|
||||||
public SpriteCache SpriteCache { get; }
|
public SpriteCache SpriteCache { get; }
|
||||||
|
|
||||||
public SequenceSet(IReadOnlyFileSystem fileSystem, ModData modData, string tileSet, MiniYaml additionalSequences)
|
public SequenceSet(IReadOnlyFileSystem fileSystem, ModData modData, string tileSet, MiniYaml additionalSequences)
|
||||||
{
|
{
|
||||||
this.modData = modData;
|
this.modData = modData;
|
||||||
TileSet = tileSet;
|
this.tileSet = tileSet;
|
||||||
|
SpriteCache = new SpriteCache(fileSystem, modData.SpriteLoaders, modData.SpriteSequenceLoader.BgraSheetSize, modData.SpriteSequenceLoader.IndexedSheetSize);
|
||||||
var rc = modData.Manifest.RendererConstants;
|
|
||||||
SpriteCache = new SpriteCache(fileSystem, modData.SpriteLoaders, rc.SequenceBgraSheetSize, rc.SequenceIndexedSheetSize);
|
|
||||||
using (new Support.PerfTimer("LoadSequences"))
|
using (new Support.PerfTimer("LoadSequences"))
|
||||||
images = Load(fileSystem, additionalSequences);
|
images = Load(fileSystem, additionalSequences);
|
||||||
}
|
}
|
||||||
@@ -97,7 +97,7 @@ namespace OpenRA.Graphics
|
|||||||
if (node.Key.StartsWith(ActorInfo.AbstractActorPrefix))
|
if (node.Key.StartsWith(ActorInfo.AbstractActorPrefix))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
images[node.Key] = modData.SpriteSequenceLoader.ParseSequences(modData, TileSet, SpriteCache, node);
|
images[node.Key] = modData.SpriteSequenceLoader.ParseSequences(modData, tileSet, SpriteCache, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
return images;
|
return images;
|
||||||
|
|||||||
@@ -23,7 +23,21 @@ namespace OpenRA.Graphics
|
|||||||
UInt = 0x1405 // GL_UNSIGNED_INT
|
UInt = 0x1405 // GL_UNSIGNED_INT
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly record struct ShaderVertexAttribute(string Name, ShaderVertexAttributeType Type, int Components, int Offset);
|
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 abstract class ShaderBindings : IShaderBindings
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ namespace OpenRA.Graphics
|
|||||||
var buffer = data;
|
var buffer = data;
|
||||||
ReleaseBuffer();
|
ReleaseBuffer();
|
||||||
|
|
||||||
// We aren't committing data to the GPU, so let's not delete our data.
|
// We aren't commiting data to the GPU, so let's not delete our data.
|
||||||
if (Game.Renderer == null)
|
if (Game.Renderer == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ using OpenRA.Primitives;
|
|||||||
|
|
||||||
namespace OpenRA.Graphics
|
namespace OpenRA.Graphics
|
||||||
{
|
{
|
||||||
|
[Serializable]
|
||||||
public class SheetOverflowException : Exception
|
public class SheetOverflowException : Exception
|
||||||
{
|
{
|
||||||
public SheetOverflowException(string message)
|
public SheetOverflowException(string message)
|
||||||
@@ -33,7 +34,7 @@ namespace OpenRA.Graphics
|
|||||||
public sealed class SheetBuilder : IDisposable
|
public sealed class SheetBuilder : IDisposable
|
||||||
{
|
{
|
||||||
public readonly SheetType Type;
|
public readonly SheetType Type;
|
||||||
readonly List<Sheet> sheets = [];
|
readonly List<Sheet> sheets = new();
|
||||||
readonly Func<Sheet> allocateSheet;
|
readonly Func<Sheet> allocateSheet;
|
||||||
readonly int margin;
|
readonly int margin;
|
||||||
int rowHeight = 0;
|
int rowHeight = 0;
|
||||||
@@ -65,6 +66,9 @@ namespace OpenRA.Graphics
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SheetBuilder(SheetType t)
|
||||||
|
: this(t, Game.Settings.Graphics.SheetSize) { }
|
||||||
|
|
||||||
public SheetBuilder(SheetType t, int sheetSize, int margin = 1)
|
public SheetBuilder(SheetType t, int sheetSize, int margin = 1)
|
||||||
: this(t, () => AllocateSheet(t, sheetSize), margin) { }
|
: this(t, () => AllocateSheet(t, sheetSize), margin) { }
|
||||||
|
|
||||||
@@ -72,6 +76,8 @@ namespace OpenRA.Graphics
|
|||||||
{
|
{
|
||||||
CurrentChannel = t == SheetType.Indexed ? TextureChannel.Red : TextureChannel.RGBA;
|
CurrentChannel = t == SheetType.Indexed ? TextureChannel.Red : TextureChannel.RGBA;
|
||||||
Type = t;
|
Type = t;
|
||||||
|
Current = allocateSheet();
|
||||||
|
sheets.Add(Current);
|
||||||
this.allocateSheet = allocateSheet;
|
this.allocateSheet = allocateSheet;
|
||||||
this.margin = margin;
|
this.margin = margin;
|
||||||
}
|
}
|
||||||
@@ -80,12 +86,6 @@ namespace OpenRA.Graphics
|
|||||||
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, 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(byte[] src, SpriteFrameType type, Size size, float zRamp, in float3 spriteOffset, bool premultiplied = false)
|
||||||
{
|
{
|
||||||
if (Current == null)
|
|
||||||
{
|
|
||||||
Current = allocateSheet();
|
|
||||||
sheets.Add(Current);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't bother allocating empty sprites
|
// Don't bother allocating empty sprites
|
||||||
if (size.Width == 0 || size.Height == 0)
|
if (size.Width == 0 || size.Height == 0)
|
||||||
return new Sprite(Current, Rectangle.Empty, 0, spriteOffset, CurrentChannel, BlendMode.Alpha);
|
return new Sprite(Current, Rectangle.Empty, 0, spriteOffset, CurrentChannel, BlendMode.Alpha);
|
||||||
@@ -116,12 +116,6 @@ namespace OpenRA.Graphics
|
|||||||
public Sprite Allocate(Size imageSize, float scale = 1f) { return Allocate(imageSize, 0, float3.Zero, scale); }
|
public Sprite Allocate(Size imageSize, float scale = 1f) { return Allocate(imageSize, 0, float3.Zero, scale); }
|
||||||
public Sprite Allocate(Size imageSize, float zRamp, in float3 spriteOffset, float scale = 1f)
|
public Sprite Allocate(Size imageSize, float zRamp, in float3 spriteOffset, float scale = 1f)
|
||||||
{
|
{
|
||||||
if (Current == null)
|
|
||||||
{
|
|
||||||
Current = allocateSheet();
|
|
||||||
sheets.Add(Current);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imageSize.Width + p.X + margin > Current.Size.Width)
|
if (imageSize.Width + p.X + margin > Current.Size.Width)
|
||||||
{
|
{
|
||||||
p = new int2(0, p.Y + rowHeight + margin);
|
p = new int2(0, p.Y + rowHeight + margin);
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OpenRA.FileSystem;
|
using OpenRA.FileSystem;
|
||||||
@@ -28,12 +27,12 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
readonly Dictionary<
|
readonly Dictionary<
|
||||||
int,
|
int,
|
||||||
(ImmutableArray<int> Frames, MiniYamlNode.SourceLocation Location, AdjustFrame AdjustFrame, bool Premultiplied)> spriteReservations = [];
|
(int[] Frames, MiniYamlNode.SourceLocation Location, AdjustFrame AdjustFrame, bool Premultiplied)> spriteReservations = new();
|
||||||
readonly Dictionary<string, List<int>> reservationsByFilename = [];
|
readonly Dictionary<string, List<int>> reservationsByFilename = new();
|
||||||
|
|
||||||
readonly Dictionary<int, Sprite[]> resolvedSprites = [];
|
readonly Dictionary<int, Sprite[]> resolvedSprites = new();
|
||||||
|
|
||||||
readonly Dictionary<int, (string Filename, MiniYamlNode.SourceLocation Location)> missingFiles = [];
|
readonly Dictionary<int, (string Filename, MiniYamlNode.SourceLocation Location)> missingFiles = new();
|
||||||
|
|
||||||
int nextReservationToken = 1;
|
int nextReservationToken = 1;
|
||||||
|
|
||||||
@@ -50,12 +49,12 @@ namespace OpenRA.Graphics
|
|||||||
this.loaders = loaders;
|
this.loaders = loaders;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int ReserveSprites(string filename, ImmutableArray<int> frames, MiniYamlNode.SourceLocation location,
|
public int ReserveSprites(string filename, IEnumerable<int> frames, MiniYamlNode.SourceLocation location,
|
||||||
AdjustFrame adjustFrame = null, bool premultiplied = false)
|
AdjustFrame adjustFrame = null, bool premultiplied = false)
|
||||||
{
|
{
|
||||||
var token = nextReservationToken++;
|
var token = nextReservationToken++;
|
||||||
spriteReservations[token] = (frames, location, adjustFrame, premultiplied);
|
spriteReservations[token] = (frames?.ToArray(), location, adjustFrame, premultiplied);
|
||||||
reservationsByFilename.GetOrAdd(filename, _ => []).Add(token);
|
reservationsByFilename.GetOrAdd(filename, _ => new List<int>()).Add(token);
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,8 +103,8 @@ namespace OpenRA.Graphics
|
|||||||
throw new InvalidOperationException($"{rs.Location}: {filename} does not contain frames: " +
|
throw new InvalidOperationException($"{rs.Location}: {filename} does not contain frames: " +
|
||||||
string.Join(',', rs.Frames.Where(f => f >= loadedFrames.Length)));
|
string.Join(',', rs.Frames.Where(f => f >= loadedFrames.Length)));
|
||||||
|
|
||||||
var frames = rs.Frames != null ? rs.Frames : Enumerable.Range(0, loadedFrames.Length);
|
var frames = rs.Frames ?? Enumerable.Range(0, loadedFrames.Length);
|
||||||
var total = rs.Frames != null ? rs.Frames.Length : loadedFrames.Length;
|
var total = rs.Frames?.Length ?? loadedFrames.Length;
|
||||||
|
|
||||||
var j = 0;
|
var j = 0;
|
||||||
foreach (var i in frames)
|
foreach (var i in frames)
|
||||||
@@ -156,7 +155,7 @@ namespace OpenRA.Graphics
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach (var sb in SheetBuilders.Values)
|
foreach (var sb in SheetBuilders.Values)
|
||||||
sb.Current?.ReleaseBuffer();
|
sb.Current.ReleaseBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Sprite[] ResolveSprites(int token)
|
public Sprite[] ResolveSprites(int token)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
*/
|
*/
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ namespace OpenRA.Graphics
|
|||||||
{
|
{
|
||||||
public class SpriteRenderable : IPalettedRenderable, IModifyableRenderable, IFinalizedRenderable
|
public class SpriteRenderable : IPalettedRenderable, IModifyableRenderable, IFinalizedRenderable
|
||||||
{
|
{
|
||||||
public static readonly IEnumerable<IRenderable> None = [];
|
public static readonly IEnumerable<IRenderable> None = Array.Empty<IRenderable>();
|
||||||
|
|
||||||
readonly Sprite sprite;
|
readonly Sprite sprite;
|
||||||
readonly WPos pos;
|
readonly WPos pos;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace OpenRA.Graphics
|
|||||||
public sealed class TerrainSpriteLayer : IDisposable
|
public sealed class TerrainSpriteLayer : IDisposable
|
||||||
{
|
{
|
||||||
// PERF: we can reuse the IndexBuffer as all layers have the same size.
|
// PERF: we can reuse the IndexBuffer as all layers have the same size.
|
||||||
static readonly ConditionalWeakTable<World, IndexBufferRc> IndexBuffers = [];
|
static readonly ConditionalWeakTable<World, IndexBufferRc> IndexBuffers = new();
|
||||||
readonly IndexBufferRc indexBufferWrapper;
|
readonly IndexBufferRc indexBufferWrapper;
|
||||||
|
|
||||||
public readonly BlendMode BlendMode;
|
public readonly BlendMode BlendMode;
|
||||||
@@ -30,7 +30,7 @@ namespace OpenRA.Graphics
|
|||||||
readonly IVertexBuffer<Vertex> vertexBuffer;
|
readonly IVertexBuffer<Vertex> vertexBuffer;
|
||||||
readonly Vertex[] vertices;
|
readonly Vertex[] vertices;
|
||||||
readonly bool[] ignoreTint;
|
readonly bool[] ignoreTint;
|
||||||
readonly HashSet<int> dirtyRows = [];
|
readonly HashSet<int> dirtyRows = new();
|
||||||
readonly int indexRowStride;
|
readonly int indexRowStride;
|
||||||
readonly int vertexRowStride;
|
readonly int vertexRowStride;
|
||||||
readonly bool restrictToBounds;
|
readonly bool restrictToBounds;
|
||||||
@@ -49,23 +49,23 @@ namespace OpenRA.Graphics
|
|||||||
BlendMode = blendMode;
|
BlendMode = blendMode;
|
||||||
map = world.Map;
|
map = world.Map;
|
||||||
|
|
||||||
vertexRowStride = 4 * map.MapSize.Width;
|
vertexRowStride = 4 * map.MapSize.X;
|
||||||
vertices = new Vertex[vertexRowStride * map.MapSize.Height];
|
vertices = new Vertex[vertexRowStride * map.MapSize.Y];
|
||||||
vertexBuffer = Game.Renderer.Context.CreateEmptyVertexBuffer<Vertex>(vertices.Length);
|
vertexBuffer = Game.Renderer.Context.CreateVertexBuffer<Vertex>(vertices.Length);
|
||||||
|
|
||||||
indexRowStride = 6 * map.MapSize.Width;
|
indexRowStride = 6 * map.MapSize.X;
|
||||||
lock (IndexBuffers)
|
lock (IndexBuffers)
|
||||||
{
|
{
|
||||||
indexBufferWrapper = IndexBuffers.GetValue(world, world => new IndexBufferRc(world));
|
indexBufferWrapper = IndexBuffers.GetValue(world, world => new IndexBufferRc(world));
|
||||||
indexBufferWrapper.AddRef();
|
indexBufferWrapper.AddRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
palettes = new PaletteReference[map.MapSize.Width * map.MapSize.Height];
|
palettes = new PaletteReference[map.MapSize.X * map.MapSize.Y];
|
||||||
wr.PaletteInvalidated += UpdatePaletteIndices;
|
wr.PaletteInvalidated += UpdatePaletteIndices;
|
||||||
|
|
||||||
if (wr.TerrainLighting != null)
|
if (wr.TerrainLighting != null)
|
||||||
{
|
{
|
||||||
ignoreTint = new bool[vertexRowStride * map.MapSize.Height];
|
ignoreTint = new bool[vertexRowStride * map.MapSize.Y];
|
||||||
wr.TerrainLighting.CellChanged += UpdateTint;
|
wr.TerrainLighting.CellChanged += UpdateTint;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@ namespace OpenRA.Graphics
|
|||||||
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);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var row = 0; row < map.MapSize.Height; row++)
|
for (var row = 0; row < map.MapSize.Y; row++)
|
||||||
dirtyRows.Add(row);
|
dirtyRows.Add(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +193,7 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
var offset = vertexRowStride * uv.V + 4 * uv.U;
|
var offset = vertexRowStride * uv.V + 4 * uv.U;
|
||||||
Util.FastCreateQuad(vertices, pos, sprite, samplers, palette?.TextureIndex ?? 0, offset, scale * sprite.Size, alpha * float3.Ones, alpha);
|
Util.FastCreateQuad(vertices, pos, sprite, samplers, palette?.TextureIndex ?? 0, offset, scale * sprite.Size, alpha * float3.Ones, alpha);
|
||||||
palettes[uv.V * map.MapSize.Width + uv.U] = palette;
|
palettes[uv.V * map.MapSize.X + uv.U] = palette;
|
||||||
|
|
||||||
if (worldRenderer.TerrainLighting != null)
|
if (worldRenderer.TerrainLighting != null)
|
||||||
{
|
{
|
||||||
@@ -209,8 +209,8 @@ namespace OpenRA.Graphics
|
|||||||
var cells = restrictToBounds ? viewport.VisibleCellsInsideBounds : viewport.AllVisibleCells;
|
var cells = restrictToBounds ? viewport.VisibleCellsInsideBounds : viewport.AllVisibleCells;
|
||||||
|
|
||||||
// Only draw the rows that are visible.
|
// Only draw the rows that are visible.
|
||||||
var firstRow = cells.CandidateMapCoords.TopLeft.V.Clamp(0, map.MapSize.Height);
|
var firstRow = cells.CandidateMapCoords.TopLeft.V.Clamp(0, map.MapSize.Y);
|
||||||
var lastRow = (cells.CandidateMapCoords.BottomRight.V + 1).Clamp(firstRow, map.MapSize.Height);
|
var lastRow = (cells.CandidateMapCoords.BottomRight.V + 1).Clamp(firstRow, map.MapSize.Y);
|
||||||
|
|
||||||
Game.Renderer.Flush();
|
Game.Renderer.Flush();
|
||||||
|
|
||||||
@@ -250,8 +250,7 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
public IndexBufferRc(World world)
|
public IndexBufferRc(World world)
|
||||||
{
|
{
|
||||||
Buffer = Game.Renderer.Context.CreateIndexBuffer(
|
Buffer = Game.Renderer.Context.CreateIndexBuffer(Util.CreateQuadIndices(world.Map.MapSize.X * world.Map.MapSize.Y));
|
||||||
Util.CreateQuadIndices(world.Map.MapSize.Width * world.Map.MapSize.Height));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddRef() { count++; }
|
public void AddRef() { count++; }
|
||||||
|
|||||||
@@ -19,12 +19,12 @@ namespace OpenRA.Graphics
|
|||||||
public static class Util
|
public static class Util
|
||||||
{
|
{
|
||||||
// yes, our channel order is nuts.
|
// yes, our channel order is nuts.
|
||||||
static readonly int[] ChannelMasks = [2, 1, 0, 3];
|
static readonly int[] ChannelMasks = { 2, 1, 0, 3 };
|
||||||
|
|
||||||
public static uint[] CreateQuadIndices(int quads)
|
public static uint[] CreateQuadIndices(int quads)
|
||||||
{
|
{
|
||||||
var indices = new uint[quads * 6];
|
var indices = new uint[quads * 6];
|
||||||
ReadOnlySpan<uint> cornerVertexMap = [0, 1, 2, 2, 3, 0];
|
ReadOnlySpan<uint> cornerVertexMap = stackalloc uint[] { 0, 1, 2, 2, 3, 0 };
|
||||||
for (var i = 0; i < indices.Length; i++)
|
for (var i = 0; i < indices.Length; i++)
|
||||||
indices[i] = cornerVertexMap[i % 6] + (uint)(4 * (i / 6));
|
indices[i] = cornerVertexMap[i % 6] + (uint)(4 * (i / 6));
|
||||||
|
|
||||||
@@ -277,13 +277,13 @@ namespace OpenRA.Graphics
|
|||||||
size.X * angleSin - size.Y * angleCos,
|
size.X * angleSin - size.Y * angleCos,
|
||||||
(size.X * angleSin - size.Y * angleCos) * size.Z / size.Y);
|
(size.X * angleSin - size.Y * angleCos) * size.Z / size.Y);
|
||||||
|
|
||||||
return
|
return new float3[]
|
||||||
[
|
{
|
||||||
center - ra,
|
center - ra,
|
||||||
center + rb,
|
center + rb,
|
||||||
center + ra,
|
center + ra,
|
||||||
center - rb
|
center - rb
|
||||||
];
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -53,12 +53,12 @@ namespace OpenRA.Graphics
|
|||||||
: base("combined")
|
: base("combined")
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
public override ShaderVertexAttribute[] Attributes { get; } =
|
public override ShaderVertexAttribute[] Attributes { get; } = new[]
|
||||||
[
|
{
|
||||||
new ShaderVertexAttribute("aVertexPosition", ShaderVertexAttributeType.Float, 3, 0),
|
new ShaderVertexAttribute("aVertexPosition", ShaderVertexAttributeType.Float, 3, 0),
|
||||||
new ShaderVertexAttribute("aVertexTexCoord", ShaderVertexAttributeType.Float, 4, 12),
|
new ShaderVertexAttribute("aVertexTexCoord", ShaderVertexAttributeType.Float, 4, 12),
|
||||||
new ShaderVertexAttribute("aVertexAttributes", ShaderVertexAttributeType.UInt, 1, 28),
|
new ShaderVertexAttribute("aVertexAttributes", ShaderVertexAttributeType.UInt, 1, 28),
|
||||||
new ShaderVertexAttribute("aVertexTint", ShaderVertexAttributeType.Float, 4, 32)
|
new ShaderVertexAttribute("aVertexTint", ShaderVertexAttributeType.Float, 4, 32)
|
||||||
];
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
|
|
||||||
@@ -50,13 +49,14 @@ namespace OpenRA.Graphics
|
|||||||
readonly Size tileSize;
|
readonly Size tileSize;
|
||||||
|
|
||||||
// Viewport geometry (world-px)
|
// Viewport geometry (world-px)
|
||||||
public float2 CenterLocation { get; private set; }
|
public int2 CenterLocation { get; private set; }
|
||||||
|
|
||||||
public WPos CenterPosition => worldRenderer.ProjectedPosition(CenterLocation.ToInt2());
|
public WPos CenterPosition => worldRenderer.ProjectedPosition(CenterLocation);
|
||||||
|
|
||||||
public int2 TopLeft => CenterLocation.ToInt2() - ViewportSize.ToInt2() / 2;
|
public Rectangle Rectangle => new(TopLeft, new Size(viewportSize.X, viewportSize.Y));
|
||||||
public int2 BottomRight => CenterLocation.ToInt2() + ViewportSize.ToInt2() / 2;
|
public int2 TopLeft => CenterLocation - viewportSize / 2;
|
||||||
public Size ViewportSize { get; private set; }
|
public int2 BottomRight => CenterLocation + viewportSize / 2;
|
||||||
|
int2 viewportSize;
|
||||||
ProjectedCellRegion cells;
|
ProjectedCellRegion cells;
|
||||||
bool cellsDirty = true;
|
bool cellsDirty = true;
|
||||||
|
|
||||||
@@ -69,10 +69,6 @@ namespace OpenRA.Graphics
|
|||||||
bool unlockMinZoom;
|
bool unlockMinZoom;
|
||||||
float unlockedMinZoomScale;
|
float unlockedMinZoomScale;
|
||||||
float unlockedMinZoom = 1f;
|
float unlockedMinZoom = 1f;
|
||||||
float defaultScale;
|
|
||||||
bool overrideUserScale;
|
|
||||||
|
|
||||||
public Func<float2> ViewportCenterProvider;
|
|
||||||
|
|
||||||
public float Zoom
|
public float Zoom
|
||||||
{
|
{
|
||||||
@@ -81,7 +77,7 @@ namespace OpenRA.Graphics
|
|||||||
private set
|
private set
|
||||||
{
|
{
|
||||||
zoom = value;
|
zoom = value;
|
||||||
ViewportSize = Size.FromInt2((1f / zoom * new float2(Game.Renderer.NativeResolution)).ToInt2());
|
viewportSize = (1f / zoom * new float2(Game.Renderer.NativeResolution)).ToInt2();
|
||||||
cellsDirty = true;
|
cellsDirty = true;
|
||||||
allCellsDirty = true;
|
allCellsDirty = true;
|
||||||
}
|
}
|
||||||
@@ -90,13 +86,6 @@ namespace OpenRA.Graphics
|
|||||||
public float MinZoom { get; private set; } = 1f;
|
public float MinZoom { get; private set; } = 1f;
|
||||||
public float MaxZoom { get; private set; } = 2f;
|
public float MaxZoom { get; private set; } = 2f;
|
||||||
|
|
||||||
public void OverrideDefaultHeight(float height)
|
|
||||||
{
|
|
||||||
defaultScale = viewportSizes.DefaultScale * Game.Renderer.NativeResolution.Height / height;
|
|
||||||
overrideUserScale = true;
|
|
||||||
UpdateViewportZooms(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AdjustZoom(float dz)
|
public void AdjustZoom(float dz)
|
||||||
{
|
{
|
||||||
// Exponential ensures that equal positive and negative steps have the same effect
|
// Exponential ensures that equal positive and negative steps have the same effect
|
||||||
@@ -108,9 +97,7 @@ namespace OpenRA.Graphics
|
|||||||
var oldCenter = worldRenderer.Viewport.ViewToWorldPx(center);
|
var oldCenter = worldRenderer.Viewport.ViewToWorldPx(center);
|
||||||
AdjustZoom(dz);
|
AdjustZoom(dz);
|
||||||
var newCenter = worldRenderer.Viewport.ViewToWorldPx(center);
|
var newCenter = worldRenderer.Viewport.ViewToWorldPx(center);
|
||||||
|
CenterLocation += oldCenter - newCenter;
|
||||||
var candidateCenterLocation = CenterLocation + oldCenter - newCenter;
|
|
||||||
CenterLocation = candidateCenterLocation.Clamp(mapBounds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ToggleZoom()
|
public void ToggleZoom()
|
||||||
@@ -150,17 +137,16 @@ namespace OpenRA.Graphics
|
|||||||
public Viewport(WorldRenderer wr, Map map)
|
public Viewport(WorldRenderer wr, Map map)
|
||||||
{
|
{
|
||||||
worldRenderer = wr;
|
worldRenderer = wr;
|
||||||
tileSize = map.Rules.TerrainInfo.TileSize;
|
var grid = Game.ModData.Manifest.Get<MapGrid>();
|
||||||
viewportSizes = Game.ModData.GetOrCreate<WorldViewportSizes>();
|
viewportSizes = Game.ModData.Manifest.Get<WorldViewportSizes>();
|
||||||
graphicSettings = Game.Settings.Graphics;
|
graphicSettings = Game.Settings.Graphics;
|
||||||
defaultScale = viewportSizes.DefaultScale;
|
|
||||||
|
|
||||||
// Calculate map bounds in world-px
|
// Calculate map bounds in world-px
|
||||||
if (wr.World.Type == WorldType.Editor)
|
if (wr.World.Type == WorldType.Editor)
|
||||||
{
|
{
|
||||||
// The full map is visible in the editor
|
// The full map is visible in the editor
|
||||||
var width = map.MapSize.Width * tileSize.Width;
|
var width = map.MapSize.X * grid.TileSize.Width;
|
||||||
var height = map.MapSize.Height * tileSize.Height;
|
var height = map.MapSize.Y * grid.TileSize.Height;
|
||||||
if (wr.World.Map.Grid.Type == MapGridType.RectangularIsometric)
|
if (wr.World.Map.Grid.Type == MapGridType.RectangularIsometric)
|
||||||
height /= 2;
|
height /= 2;
|
||||||
|
|
||||||
@@ -175,6 +161,8 @@ namespace OpenRA.Graphics
|
|||||||
CenterLocation = (tl + br) / 2;
|
CenterLocation = (tl + br) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tileSize = grid.TileSize;
|
||||||
|
|
||||||
UpdateViewportZooms();
|
UpdateViewportZooms();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,9 +170,6 @@ namespace OpenRA.Graphics
|
|||||||
{
|
{
|
||||||
if (lastViewportDistance != graphicSettings.ViewportDistance)
|
if (lastViewportDistance != graphicSettings.ViewportDistance)
|
||||||
UpdateViewportZooms();
|
UpdateViewportZooms();
|
||||||
|
|
||||||
if (ViewportCenterProvider != null)
|
|
||||||
Center(ViewportCenterProvider());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static float CalculateMinimumZoom(float minHeight, float maxHeight)
|
static float CalculateMinimumZoom(float minHeight, float maxHeight)
|
||||||
@@ -222,17 +207,17 @@ namespace OpenRA.Graphics
|
|||||||
lastViewportDistance = graphicSettings.ViewportDistance;
|
lastViewportDistance = graphicSettings.ViewportDistance;
|
||||||
|
|
||||||
var vd = graphicSettings.ViewportDistance;
|
var vd = graphicSettings.ViewportDistance;
|
||||||
if (overrideUserScale || (viewportSizes.AllowNativeZoom && vd == WorldViewport.Native))
|
if (viewportSizes.AllowNativeZoom && vd == WorldViewport.Native)
|
||||||
MinZoom = defaultScale;
|
MinZoom = viewportSizes.DefaultScale;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var range = viewportSizes.GetSizeRange(vd);
|
var range = viewportSizes.GetSizeRange(vd);
|
||||||
MinZoom = CalculateMinimumZoom(range.X, range.Y) * defaultScale;
|
MinZoom = CalculateMinimumZoom(range.X, range.Y) * viewportSizes.DefaultScale;
|
||||||
}
|
}
|
||||||
|
|
||||||
MaxZoom = Math.Min(
|
MaxZoom = Math.Min(
|
||||||
MinZoom * viewportSizes.MaxZoomScale,
|
MinZoom * viewportSizes.MaxZoomScale,
|
||||||
Game.Renderer.NativeResolution.Height * defaultScale / viewportSizes.MaxZoomWindowHeight);
|
Game.Renderer.NativeResolution.Height * viewportSizes.DefaultScale / viewportSizes.MaxZoomWindowHeight);
|
||||||
|
|
||||||
if (unlockMinZoom)
|
if (unlockMinZoom)
|
||||||
{
|
{
|
||||||
@@ -258,7 +243,7 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
public CPos ViewToWorld(int2 view)
|
public CPos ViewToWorld(int2 view)
|
||||||
{
|
{
|
||||||
var world = ViewToWorldPx(view);
|
var world = worldRenderer.Viewport.ViewToWorldPx(view);
|
||||||
var map = worldRenderer.World.Map;
|
var map = worldRenderer.World.Map;
|
||||||
var candidates = CandidateMouseoverCells(world).ToList();
|
var candidates = CandidateMouseoverCells(world).ToList();
|
||||||
|
|
||||||
@@ -271,7 +256,7 @@ namespace OpenRA.Graphics
|
|||||||
{
|
{
|
||||||
var ramp = map.Grid.Ramps[map.Ramp.Contains(uv) ? map.Ramp[uv] : 0];
|
var ramp = map.Grid.Ramps[map.Ramp.Contains(uv) ? map.Ramp[uv] : 0];
|
||||||
var pos = map.CenterOfCell(uv.ToCPos(map)) - new WVec(0, 0, ramp.CenterHeightOffset);
|
var pos = map.CenterOfCell(uv.ToCPos(map)) - new WVec(0, 0, ramp.CenterHeightOffset);
|
||||||
var screen = ramp.Corners.Select(c => worldRenderer.ScreenPxPosition(pos + c)).ToImmutableArray();
|
var screen = ramp.Corners.Select(c => worldRenderer.ScreenPxPosition(pos + c)).ToArray();
|
||||||
if (screen.PolygonContains(world))
|
if (screen.PolygonContains(world))
|
||||||
return uv.ToCPos(map);
|
return uv.ToCPos(map);
|
||||||
}
|
}
|
||||||
@@ -323,9 +308,9 @@ namespace OpenRA.Graphics
|
|||||||
yield return new MPos(u, v);
|
yield return new MPos(u, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int2 ViewToWorldPx(int2 view) => (graphicSettings.UIScale / Zoom * view.ToFloat2() + CenterLocation - ViewportSize.ToInt2() / 2).ToInt2();
|
public int2 ViewToWorldPx(int2 view) { return (graphicSettings.UIScale / Zoom * view.ToFloat2()).ToInt2() + TopLeft; }
|
||||||
public int2 WorldToViewPx(int2 world) => (Zoom / graphicSettings.UIScale * (world - CenterLocation + ViewportSize.ToInt2() / 2)).ToInt2();
|
public int2 WorldToViewPx(int2 world) { return (Zoom / graphicSettings.UIScale * (world - TopLeft).ToFloat2()).ToInt2(); }
|
||||||
public int2 WorldToViewPx(in float3 world) => (Zoom / graphicSettings.UIScale * (world - CenterLocation + ViewportSize.ToInt2() / 2).XY).ToInt2();
|
public int2 WorldToViewPx(in float3 world) { return (Zoom / graphicSettings.UIScale * (world - TopLeft).XY).ToInt2(); }
|
||||||
|
|
||||||
public void Center(IEnumerable<Actor> actors)
|
public void Center(IEnumerable<Actor> actors)
|
||||||
{
|
{
|
||||||
@@ -345,17 +330,10 @@ namespace OpenRA.Graphics
|
|||||||
allCellsDirty = true;
|
allCellsDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Center(float2 pos)
|
|
||||||
{
|
|
||||||
CenterLocation = worldRenderer.ScreenPosition(pos).Clamp(mapBounds);
|
|
||||||
cellsDirty = true;
|
|
||||||
allCellsDirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Scroll(float2 delta, bool ignoreBorders)
|
public void Scroll(float2 delta, bool ignoreBorders)
|
||||||
{
|
{
|
||||||
// Convert scroll delta from world-px to viewport-px
|
// Convert scroll delta from world-px to viewport-px
|
||||||
CenterLocation += 1f / Zoom * delta;
|
CenterLocation += (1f / Zoom * delta).ToInt2();
|
||||||
cellsDirty = true;
|
cellsDirty = true;
|
||||||
allCellsDirty = true;
|
allCellsDirty = true;
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using OpenRA.Effects;
|
using OpenRA.Effects;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
using OpenRA.Traits;
|
using OpenRA.Traits;
|
||||||
@@ -32,33 +31,32 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
public event Action PaletteInvalidated = null;
|
public event Action PaletteInvalidated = null;
|
||||||
|
|
||||||
readonly HashSet<Actor> onScreenActors = [];
|
readonly HashSet<Actor> onScreenActors = new();
|
||||||
readonly HardwarePalette palette = new();
|
readonly HardwarePalette palette = new();
|
||||||
readonly Dictionary<string, PaletteReference> palettes = [];
|
readonly Dictionary<string, PaletteReference> palettes = new();
|
||||||
readonly IRenderTerrain terrainRenderer;
|
readonly IRenderTerrain terrainRenderer;
|
||||||
readonly Lazy<DebugVisualizations> debugVis;
|
readonly Lazy<DebugVisualizations> debugVis;
|
||||||
readonly Func<string, PaletteReference> createPaletteReference;
|
readonly Func<string, PaletteReference> createPaletteReference;
|
||||||
readonly bool enableDepthBuffer;
|
readonly bool enableDepthBuffer;
|
||||||
|
|
||||||
readonly List<IFinalizedRenderable> preparedRenderables = [];
|
readonly List<IFinalizedRenderable> preparedRenderables = new();
|
||||||
readonly List<IFinalizedRenderable> preparedOverlayRenderables = [];
|
readonly List<IFinalizedRenderable> preparedOverlayRenderables = new();
|
||||||
readonly List<IFinalizedRenderable> preparedAnnotationRenderables = [];
|
readonly List<IFinalizedRenderable> preparedAnnotationRenderables = new();
|
||||||
|
|
||||||
readonly List<IRenderable> renderablesBuffer = [];
|
readonly List<IRenderable> renderablesBuffer = new();
|
||||||
readonly IRenderer[] renderers;
|
readonly IRenderer[] renderers;
|
||||||
readonly IRenderPostProcessPass[] postProcessPasses;
|
readonly IRenderPostProcessPass[] postProcessPasses;
|
||||||
long[] renderablesKeysBuffer = [];
|
|
||||||
|
|
||||||
internal WorldRenderer(ModData modData, World world)
|
internal WorldRenderer(ModData modData, World world)
|
||||||
{
|
{
|
||||||
World = world;
|
World = world;
|
||||||
TileSize = World.Map.Rules.TerrainInfo.TileSize;
|
TileSize = World.Map.Grid.TileSize;
|
||||||
TileScale = World.Map.Grid.TileScale;
|
TileScale = World.Map.Grid.TileScale;
|
||||||
Viewport = new Viewport(this, world.Map);
|
Viewport = new Viewport(this, world.Map);
|
||||||
|
|
||||||
createPaletteReference = CreatePaletteReference;
|
createPaletteReference = CreatePaletteReference;
|
||||||
|
|
||||||
var mapGrid = modData.GetOrCreate<MapGrid>();
|
var mapGrid = modData.Manifest.Get<MapGrid>();
|
||||||
enableDepthBuffer = mapGrid.EnableDepthBuffer;
|
enableDepthBuffer = mapGrid.EnableDepthBuffer;
|
||||||
|
|
||||||
foreach (var pal in world.TraitDict.ActorsWithTrait<ILoadsPalettes>())
|
foreach (var pal in world.TraitDict.ActorsWithTrait<ILoadsPalettes>())
|
||||||
@@ -72,7 +70,7 @@ namespace OpenRA.Graphics
|
|||||||
renderers = world.WorldActor.TraitsImplementing<IRenderer>().ToArray();
|
renderers = world.WorldActor.TraitsImplementing<IRenderer>().ToArray();
|
||||||
terrainRenderer = world.WorldActor.TraitOrDefault<IRenderTerrain>();
|
terrainRenderer = world.WorldActor.TraitOrDefault<IRenderTerrain>();
|
||||||
|
|
||||||
debugVis = Exts.Lazy(world.WorldActor.TraitOrDefault<DebugVisualizations>);
|
debugVis = Exts.Lazy(() => world.WorldActor.TraitOrDefault<DebugVisualizations>());
|
||||||
|
|
||||||
postProcessPasses = world.WorldActor.TraitsImplementing<IRenderPostProcessPass>().ToArray();
|
postProcessPasses = world.WorldActor.TraitsImplementing<IRenderPostProcessPass>().ToArray();
|
||||||
}
|
}
|
||||||
@@ -159,14 +157,7 @@ namespace OpenRA.Graphics
|
|||||||
renderablesBuffer.AddRange(e.Render(this));
|
renderablesBuffer.AddRange(e.Render(this));
|
||||||
|
|
||||||
// Renderables must be ordered using a stable sorting algorithm to avoid flickering artefacts
|
// Renderables must be ordered using a stable sorting algorithm to avoid flickering artefacts
|
||||||
if (renderablesKeysBuffer.Length < renderablesBuffer.Count)
|
foreach (var renderable in renderablesBuffer.OrderBy(RenderableZPositionComparisonKey))
|
||||||
renderablesKeysBuffer = new long[Exts.NextPowerOf2(renderablesBuffer.Count)];
|
|
||||||
for (var i = 0; i < renderablesBuffer.Count; i++)
|
|
||||||
renderablesKeysBuffer[i] = ((long)RenderableZPositionComparisonKey(renderablesBuffer[i]) << 32) + i;
|
|
||||||
var keys = renderablesKeysBuffer.AsSpan(0, renderablesBuffer.Count);
|
|
||||||
keys.Sort(CollectionsMarshal.AsSpan(renderablesBuffer));
|
|
||||||
|
|
||||||
foreach (var renderable in renderablesBuffer)
|
|
||||||
preparedRenderables.Add(renderable.PrepareRender(this));
|
preparedRenderables.Add(renderable.PrepareRender(this));
|
||||||
|
|
||||||
// PERF: Reuse collection to avoid allocations.
|
// PERF: Reuse collection to avoid allocations.
|
||||||
@@ -380,8 +371,6 @@ namespace OpenRA.Graphics
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplyPostProcessing(PostProcessPassType.AfterAnnotations);
|
|
||||||
|
|
||||||
Game.Renderer.Flush();
|
Game.Renderer.Flush();
|
||||||
|
|
||||||
preparedRenderables.Clear();
|
preparedRenderables.Clear();
|
||||||
@@ -395,22 +384,12 @@ namespace OpenRA.Graphics
|
|||||||
Game.Renderer.SetPalette(palette);
|
Game.Renderer.SetPalette(palette);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
// Conversion between world and screen coordinates
|
||||||
/// Converts a world position to a screen position.
|
|
||||||
/// </summary>
|
|
||||||
public float2 ScreenPosition(WPos pos)
|
public float2 ScreenPosition(WPos pos)
|
||||||
{
|
{
|
||||||
return new float2((float)TileSize.Width * pos.X / TileScale, (float)TileSize.Height * (pos.Y - pos.Z) / TileScale);
|
return new float2((float)TileSize.Width * pos.X / TileScale, (float)TileSize.Height * (pos.Y - pos.Z) / TileScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts a world position to a screen position.
|
|
||||||
/// </summary>
|
|
||||||
public float2 ScreenPosition(float2 pos)
|
|
||||||
{
|
|
||||||
return new float2(TileSize.Width * pos.X / TileScale, TileSize.Height * pos.Y / TileScale);
|
|
||||||
}
|
|
||||||
|
|
||||||
public float3 Screen3DPosition(WPos pos)
|
public float3 Screen3DPosition(WPos pos)
|
||||||
{
|
{
|
||||||
// The projection from world coordinates to screen coordinates has
|
// The projection from world coordinates to screen coordinates has
|
||||||
@@ -450,7 +429,7 @@ namespace OpenRA.Graphics
|
|||||||
public float[] ScreenVector(in WVec vec)
|
public float[] ScreenVector(in WVec vec)
|
||||||
{
|
{
|
||||||
var xyz = ScreenVectorComponents(vec);
|
var xyz = ScreenVectorComponents(vec);
|
||||||
return [xyz.X, xyz.Y, xyz.Z, 1f];
|
return new[] { xyz.X, xyz.Y, xyz.Z, 1f };
|
||||||
}
|
}
|
||||||
|
|
||||||
public int2 ScreenPxOffset(in WVec vec)
|
public int2 ScreenPxOffset(in WVec vec)
|
||||||
|
|||||||
@@ -9,8 +9,7 @@
|
|||||||
*/
|
*/
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System.Collections.Frozen;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
@@ -25,10 +24,10 @@ namespace OpenRA
|
|||||||
[FluentReference]
|
[FluentReference]
|
||||||
public readonly string Description = "";
|
public readonly string Description = "";
|
||||||
|
|
||||||
public readonly FrozenSet<string> Types = FrozenSet<string>.Empty;
|
public readonly HashSet<string> Types = new();
|
||||||
|
|
||||||
[FluentReference]
|
[FluentReference]
|
||||||
public readonly FrozenSet<string> Contexts = FrozenSet<string>.Empty;
|
public readonly HashSet<string> Contexts = new();
|
||||||
|
|
||||||
public readonly bool Readonly = false;
|
public readonly bool Readonly = false;
|
||||||
public bool HasDuplicates { get; internal set; }
|
public bool HasDuplicates { get; internal set; }
|
||||||
@@ -46,11 +45,11 @@ namespace OpenRA
|
|||||||
Description = descriptionYaml.Value;
|
Description = descriptionYaml.Value;
|
||||||
|
|
||||||
if (nodeDict.TryGetValue("Types", out var typesYaml))
|
if (nodeDict.TryGetValue("Types", out var typesYaml))
|
||||||
Types = FieldLoader.GetValue<FrozenSet<string>>("Types", typesYaml.Value);
|
Types = FieldLoader.GetValue<HashSet<string>>("Types", typesYaml.Value);
|
||||||
|
|
||||||
if (nodeDict.TryGetValue("Contexts", out var contextYaml))
|
if (nodeDict.TryGetValue("Contexts", out var contextYaml))
|
||||||
Contexts = FieldLoader.GetValue<ImmutableArray<string>>("Contexts", contextYaml.Value)
|
Contexts = FieldLoader.GetValue<HashSet<string>>("Contexts", contextYaml.Value)
|
||||||
.Select(c => ContextFluentPrefix + "." + c).ToFrozenSet();
|
.Select(c => ContextFluentPrefix + "." + c).ToHashSet();
|
||||||
|
|
||||||
if (nodeDict.TryGetValue("Platform", out var platformYaml))
|
if (nodeDict.TryGetValue("Platform", out var platformYaml))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,16 +17,13 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
public sealed class HotkeyManager
|
public sealed class HotkeyManager
|
||||||
{
|
{
|
||||||
[YamlNode("Keys", shared: true)]
|
readonly Dictionary<string, Hotkey> settings;
|
||||||
sealed class HotkeySettings : SettingsModule { }
|
readonly Dictionary<string, HotkeyDefinition> definitions = new();
|
||||||
|
readonly Dictionary<string, Hotkey> keys = new();
|
||||||
|
|
||||||
readonly Dictionary<string, HotkeyDefinition> definitions = [];
|
public HotkeyManager(IReadOnlyFileSystem fileSystem, Dictionary<string, Hotkey> settings, Manifest manifest)
|
||||||
readonly Dictionary<string, Hotkey> keys = [];
|
|
||||||
readonly HotkeySettings hotkeySettings;
|
|
||||||
|
|
||||||
public HotkeyManager(IReadOnlyFileSystem fileSystem, ObjectCreator objectCreator, Manifest manifest)
|
|
||||||
{
|
{
|
||||||
hotkeySettings = Game.Settings.GetOrCreate<HotkeySettings>(objectCreator, manifest.Id);
|
this.settings = settings;
|
||||||
|
|
||||||
var keyDefinitions = MiniYaml.Load(fileSystem, manifest.Hotkeys, null);
|
var keyDefinitions = MiniYaml.Load(fileSystem, manifest.Hotkeys, null);
|
||||||
foreach (var kd in keyDefinitions)
|
foreach (var kd in keyDefinitions)
|
||||||
@@ -36,9 +33,11 @@ namespace OpenRA
|
|||||||
keys[kd.Key] = definition.Default;
|
keys[kd.Key] = definition.Default;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var node in hotkeySettings.Yaml.Nodes)
|
foreach (var kv in settings)
|
||||||
if (definitions.TryGetValue(node.Key, out var definition) && !definition.Readonly)
|
{
|
||||||
keys[node.Key] = FieldLoader.GetValue<Hotkey>(node.Key, node.Value.Value);
|
if (definitions.TryGetValue(kv.Key, out var definition) && !definition.Readonly)
|
||||||
|
keys[kv.Key] = kv.Value;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var hd in definitions)
|
foreach (var hd in definitions)
|
||||||
hd.Value.HasDuplicates = GetFirstDuplicate(hd.Value, this[hd.Value.Name].GetValue()) != null;
|
hd.Value.HasDuplicates = GetFirstDuplicate(hd.Value, this[hd.Value.Name].GetValue()) != null;
|
||||||
@@ -69,9 +68,10 @@ namespace OpenRA
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
keys[name] = value;
|
keys[name] = value;
|
||||||
hotkeySettings.Yaml.Nodes.RemoveAll(n => n.Key == name);
|
|
||||||
if (value != definition.Default)
|
if (value != definition.Default)
|
||||||
hotkeySettings.Yaml.Nodes.Add(new MiniYamlNodeBuilder(name, FieldSaver.FormatValue(value)));
|
settings[name] = value;
|
||||||
|
else
|
||||||
|
settings.Remove(name);
|
||||||
|
|
||||||
var hadDuplicates = definition.HasDuplicates;
|
var hadDuplicates = definition.HasDuplicates;
|
||||||
definition.HasDuplicates = GetFirstDuplicate(definition, this[definition.Name].GetValue()) != null;
|
definition.HasDuplicates = GetFirstDuplicate(definition, this[definition.Name].GetValue()) != null;
|
||||||
@@ -108,10 +108,5 @@ namespace OpenRA
|
|||||||
public HotkeyReference this[string name] => new(GetHotkeyReference(name));
|
public HotkeyReference this[string name] => new(GetHotkeyReference(name));
|
||||||
|
|
||||||
public IEnumerable<HotkeyDefinition> Definitions => definitions.Values;
|
public IEnumerable<HotkeyDefinition> Definitions => definitions.Values;
|
||||||
|
|
||||||
public void Save()
|
|
||||||
{
|
|
||||||
hotkeySettings.Save();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,11 @@ namespace OpenRA
|
|||||||
var total = response.Content.Headers.ContentLength ?? -1;
|
var total = response.Content.Headers.ContentLength ?? -1;
|
||||||
var canReportProgress = total > 0;
|
var canReportProgress = total > 0;
|
||||||
|
|
||||||
await using (var contentStream = await response.Content.ReadAsStreamAsync(token))
|
#if NET5_0_OR_GREATER
|
||||||
|
using (var contentStream = await response.Content.ReadAsStreamAsync(token))
|
||||||
|
#else
|
||||||
|
using (var contentStream = await response.Content.ReadAsStreamAsync())
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
var totalRead = 0L;
|
var totalRead = 0L;
|
||||||
var buffer = new byte[8192];
|
var buffer = new byte[8192];
|
||||||
|
|||||||
@@ -27,21 +27,25 @@ namespace OpenRA
|
|||||||
public static bool TryParse(string s, out Hotkey result)
|
public static bool TryParse(string s, out Hotkey result)
|
||||||
{
|
{
|
||||||
result = Invalid;
|
result = Invalid;
|
||||||
if (s == null)
|
if (string.IsNullOrWhiteSpace(s))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Span<Range> ranges = stackalloc Range[2];
|
var parts = s.Split(' ');
|
||||||
var span = s.AsSpan();
|
|
||||||
var count = span.Split(ranges, ' ');
|
|
||||||
if (count == 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!Enum.TryParse(span[ranges[0]], true, out Keycode key))
|
if (!Enum<Keycode>.TryParse(parts[0], true, out var key))
|
||||||
|
{
|
||||||
|
if (!int.TryParse(parts[0], out var c))
|
||||||
return false;
|
return false;
|
||||||
|
key = (Keycode)c;
|
||||||
|
}
|
||||||
|
|
||||||
var mods = Modifiers.None;
|
var mods = Modifiers.None;
|
||||||
if (count == 2 && !Enum.TryParse(span[ranges[1]], true, out mods))
|
if (parts.Length >= 2)
|
||||||
|
{
|
||||||
|
var modString = s[s.IndexOf(' ')..];
|
||||||
|
if (!Enum<Modifiers>.TryParse(modString, true, out mods))
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
result = new Hotkey(key, mods);
|
result = new Hotkey(key, mods);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -23,7 +23,25 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum MouseInputEvent { Down, Move, Up, Scroll }
|
public enum MouseInputEvent { Down, Move, Up, Scroll }
|
||||||
public record struct MouseInput(MouseInputEvent Event, MouseButton Button, int2 Location, int2 Delta, Modifiers Modifiers, int MultiTapCount);
|
public struct MouseInput
|
||||||
|
{
|
||||||
|
public MouseInputEvent Event;
|
||||||
|
public MouseButton Button;
|
||||||
|
public int2 Location;
|
||||||
|
public int2 Delta;
|
||||||
|
public Modifiers Modifiers;
|
||||||
|
public int MultiTapCount;
|
||||||
|
|
||||||
|
public MouseInput(MouseInputEvent ev, MouseButton button, int2 location, int2 delta, Modifiers mods, int multiTapCount)
|
||||||
|
{
|
||||||
|
Event = ev;
|
||||||
|
Button = button;
|
||||||
|
Location = location;
|
||||||
|
Delta = delta;
|
||||||
|
Modifiers = mods;
|
||||||
|
MultiTapCount = multiTapCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
public enum MouseButton
|
public enum MouseButton
|
||||||
@@ -47,7 +65,7 @@ namespace OpenRA
|
|||||||
public static class ModifiersExts
|
public static class ModifiersExts
|
||||||
{
|
{
|
||||||
[FluentReference]
|
[FluentReference]
|
||||||
const string Cmd = "keycode-modifier.cmd";
|
public const string Cmd = "keycode-modifier.cmd";
|
||||||
|
|
||||||
[FluentReference(Traits.LintDictionaryReference.Values)]
|
[FluentReference(Traits.LintDictionaryReference.Values)]
|
||||||
public static readonly IReadOnlyDictionary<Modifiers, string> ModifierFluentKeys = new Dictionary<Modifiers, string>()
|
public static readonly IReadOnlyDictionary<Modifiers, string> ModifierFluentKeys = new Dictionary<Modifiers, string>()
|
||||||
|
|||||||
@@ -50,4 +50,11 @@ namespace OpenRA
|
|||||||
Sync.RunUnsynced(world, () => Ui.HandleInput(input));
|
Sync.RunUnsynced(world, () => Ui.HandleInput(input));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class MouseButtonPreference
|
||||||
|
{
|
||||||
|
public MouseButton Action => Game.Settings.Game.UseClassicMouseStyle ? MouseButton.Left : MouseButton.Right;
|
||||||
|
|
||||||
|
public MouseButton Cancel => Game.Settings.Game.UseClassicMouseStyle ? MouseButton.Right : MouseButton.Left;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,8 @@ namespace OpenRA
|
|||||||
// (b) Therefore:
|
// (b) Therefore:
|
||||||
// - au + 2bv adds (a + b) to (x, y)
|
// - au + 2bv adds (a + b) to (x, y)
|
||||||
// - a correction factor is added if v is odd
|
// - a correction factor is added if v is odd
|
||||||
var y = (V - (V & 1)) / 2 - U;
|
var offset = (V & 1) == 1 ? 1 : 0;
|
||||||
|
var y = (V - offset) / 2 - U;
|
||||||
var x = V - y;
|
var x = V - y;
|
||||||
return new CPos(x, y);
|
return new CPos(x, y);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,27 +9,51 @@
|
|||||||
*/
|
*/
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System.Collections.Frozen;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.ObjectModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OpenRA.FileSystem;
|
using OpenRA.FileSystem;
|
||||||
|
using OpenRA.Primitives;
|
||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
{
|
{
|
||||||
|
public interface IGlobalModData { }
|
||||||
|
|
||||||
|
public sealed class TerrainFormat : IGlobalModData
|
||||||
|
{
|
||||||
|
public readonly string Type;
|
||||||
|
public readonly IReadOnlyDictionary<string, MiniYaml> Metadata;
|
||||||
|
public TerrainFormat(MiniYaml yaml)
|
||||||
|
{
|
||||||
|
Type = yaml.Value;
|
||||||
|
Metadata = new ReadOnlyDictionary<string, MiniYaml>(yaml.ToDictionary());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class SpriteSequenceFormat : IGlobalModData
|
||||||
|
{
|
||||||
|
public readonly string Type;
|
||||||
|
public readonly IReadOnlyDictionary<string, MiniYaml> Metadata;
|
||||||
|
public SpriteSequenceFormat(MiniYaml yaml)
|
||||||
|
{
|
||||||
|
Type = yaml.Value;
|
||||||
|
Metadata = new ReadOnlyDictionary<string, MiniYaml>(yaml.ToDictionary());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class ModMetadata
|
public class ModMetadata
|
||||||
{
|
{
|
||||||
// FieldLoader used here, must matching naming in YAML.
|
// FieldLoader used here, must matching naming in YAML.
|
||||||
#pragma warning disable IDE1006 // Naming Styles
|
#pragma warning disable IDE1006 // Naming Styles
|
||||||
[FluentReference]
|
[FluentReference]
|
||||||
public readonly string Title;
|
readonly string Title;
|
||||||
public readonly string Version;
|
public readonly string Version;
|
||||||
public readonly string Website;
|
public readonly string Website;
|
||||||
public readonly string WebIcon32;
|
public readonly string WebIcon32;
|
||||||
|
[FluentReference]
|
||||||
[FluentReference(optional: true)]
|
readonly string WindowTitle;
|
||||||
public readonly string WindowTitle;
|
|
||||||
public readonly bool Hidden;
|
public readonly bool Hidden;
|
||||||
#pragma warning restore IDE1006 // Naming Styles
|
#pragma warning restore IDE1006 // Naming Styles
|
||||||
|
|
||||||
@@ -37,56 +61,48 @@ namespace OpenRA
|
|||||||
public string WindowTitleTranslated => WindowTitle != null ? FluentProvider.GetMessage(WindowTitle) : null;
|
public string WindowTitleTranslated => WindowTitle != null ? FluentProvider.GetMessage(WindowTitle) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RendererConstants
|
|
||||||
{
|
|
||||||
public readonly int FontSheetSize = 512;
|
|
||||||
public readonly int CursorSheetSize = 512;
|
|
||||||
public readonly int MapPreviewSheetSize = 2048;
|
|
||||||
public readonly int SequenceBgraSheetSize = 2048;
|
|
||||||
public readonly int SequenceIndexedSheetSize = 2048;
|
|
||||||
public readonly int VertexBatchSize = 8192;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Describes what is to be loaded in order to run a mod.</summary>
|
/// <summary>Describes what is to be loaded in order to run a mod.</summary>
|
||||||
public sealed class Manifest
|
public sealed class Manifest : IDisposable
|
||||||
{
|
{
|
||||||
public readonly string Id;
|
public readonly string Id;
|
||||||
public readonly IReadOnlyPackage Package;
|
public readonly IReadOnlyPackage Package;
|
||||||
public readonly ModMetadata Metadata;
|
public readonly ModMetadata Metadata;
|
||||||
public readonly ImmutableArray<string>
|
public readonly string[]
|
||||||
Rules, ServerTraits,
|
Rules, ServerTraits,
|
||||||
Sequences, ModelSequences, Cursors, Chrome, ChromeLayout,
|
Sequences, ModelSequences, Cursors, Chrome, ChromeLayout,
|
||||||
Weapons, Voices, Notifications, Music, FluentMessages, TileSets,
|
Weapons, Voices, Notifications, Music, FluentMessages, TileSets,
|
||||||
ChromeMetrics, MapCompatibility, Missions, Hotkeys;
|
ChromeMetrics, MapCompatibility, Missions, Hotkeys;
|
||||||
|
|
||||||
public readonly FrozenDictionary<string, string> MapFolders;
|
public readonly IReadOnlyDictionary<string, string> MapFolders;
|
||||||
public readonly MiniYaml FileSystem;
|
public readonly MiniYaml FileSystem;
|
||||||
public readonly MiniYaml LoadScreen;
|
public readonly MiniYaml LoadScreen;
|
||||||
public readonly string DefaultOrderGenerator;
|
public readonly string DefaultOrderGenerator;
|
||||||
public readonly RendererConstants RendererConstants;
|
|
||||||
|
|
||||||
public readonly ImmutableArray<string> Assemblies = [];
|
public readonly string[] Assemblies = Array.Empty<string>();
|
||||||
public readonly ImmutableArray<string> SoundFormats = [];
|
public readonly string[] SoundFormats = Array.Empty<string>();
|
||||||
public readonly ImmutableArray<string> SpriteFormats = [];
|
public readonly string[] SpriteFormats = Array.Empty<string>();
|
||||||
public readonly ImmutableArray<string> PackageFormats = [];
|
public readonly string[] PackageFormats = Array.Empty<string>();
|
||||||
public readonly ImmutableArray<string> VideoFormats = [];
|
public readonly string[] VideoFormats = Array.Empty<string>();
|
||||||
public readonly string SpriteSequenceFormat;
|
public readonly int FontSheetSize = 512;
|
||||||
public readonly string TerrainFormat;
|
public readonly int CursorSheetSize = 512;
|
||||||
|
|
||||||
// TODO: This should be controlled by a user-selected translation bundle!
|
// TODO: This should be controlled by a user-selected translation bundle!
|
||||||
public readonly string FluentCulture = "en";
|
public readonly string FluentCulture = "en";
|
||||||
public readonly bool AllowUnusedFluentMessagesInExternalPackages = true;
|
public readonly bool AllowUnusedFluentMessagesInExternalPackages = true;
|
||||||
|
|
||||||
static readonly FrozenSet<string> ReservedModuleNames = new HashSet<string>
|
readonly string[] reservedModuleNames =
|
||||||
{
|
{
|
||||||
"Include", "Metadata", "FileSystem", "MapFolders", "Rules",
|
"Include", "Metadata", "FileSystem", "MapFolders", "Rules",
|
||||||
"Sequences", "ModelSequences", "Cursors", "Chrome", "Assemblies", "ChromeLayout", "Weapons",
|
"Sequences", "ModelSequences", "Cursors", "Chrome", "Assemblies", "ChromeLayout", "Weapons",
|
||||||
"Voices", "Notifications", "Music", "FluentMessages", "TileSets", "ChromeMetrics", "Missions", "Hotkeys",
|
"Voices", "Notifications", "Music", "FluentMessages", "TileSets", "ChromeMetrics", "Missions", "Hotkeys",
|
||||||
"ServerTraits", "LoadScreen", "DefaultOrderGenerator", "SupportsMapsFrom", "SoundFormats", "SpriteFormats", "VideoFormats",
|
"ServerTraits", "LoadScreen", "DefaultOrderGenerator", "SupportsMapsFrom", "SoundFormats", "SpriteFormats", "VideoFormats",
|
||||||
"SpriteSequenceFormat", "TerrainFormat", "RequiresMods", "PackageFormats", "AllowUnusedFluentMessagesInExternalPackages", "RendererConstants"
|
"RequiresMods", "PackageFormats", "AllowUnusedFluentMessagesInExternalPackages", "FontSheetSize", "CursorSheetSize"
|
||||||
}.ToFrozenSet();
|
};
|
||||||
|
|
||||||
public readonly FrozenDictionary<string, MiniYaml> GlobalModData;
|
readonly TypeDictionary modules = new();
|
||||||
|
readonly Dictionary<string, MiniYaml> yaml;
|
||||||
|
|
||||||
|
bool customDataLoaded;
|
||||||
|
|
||||||
public Manifest(string modId, IReadOnlyPackage package)
|
public Manifest(string modId, IReadOnlyPackage package)
|
||||||
{
|
{
|
||||||
@@ -94,7 +110,7 @@ namespace OpenRA
|
|||||||
Package = package;
|
Package = package;
|
||||||
|
|
||||||
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
||||||
var nodes = MiniYaml.FromStream(package.GetStream("mod.yaml"), $"{package.Name}:mod.yaml", stringPool: stringPool).ToList();
|
var nodes = MiniYaml.FromStream(package.GetStream("mod.yaml"), $"{package.Name}:mod.yaml", stringPool: stringPool);
|
||||||
for (var i = nodes.Count - 1; i >= 0; i--)
|
for (var i = nodes.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
if (nodes[i].Key != "Include")
|
if (nodes[i].Key != "Include")
|
||||||
@@ -111,7 +127,7 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Merge inherited overrides
|
// Merge inherited overrides
|
||||||
var yaml = new MiniYaml(null, MiniYaml.Merge([nodes])).ToDictionary();
|
yaml = new MiniYaml(null, MiniYaml.Merge(new[] { nodes })).ToDictionary();
|
||||||
|
|
||||||
Metadata = FieldLoader.Load<ModMetadata>(yaml["Metadata"]);
|
Metadata = FieldLoader.Load<ModMetadata>(yaml["Metadata"]);
|
||||||
|
|
||||||
@@ -148,59 +164,144 @@ namespace OpenRA
|
|||||||
if (yaml.TryGetValue("SupportsMapsFrom", out var entry))
|
if (yaml.TryGetValue("SupportsMapsFrom", out var entry))
|
||||||
compat.AddRange(entry.Value.Split(',').Select(c => c.Trim()));
|
compat.AddRange(entry.Value.Split(',').Select(c => c.Trim()));
|
||||||
|
|
||||||
MapCompatibility = compat.ToImmutableArray();
|
MapCompatibility = compat.ToArray();
|
||||||
|
|
||||||
if (yaml.TryGetValue("DefaultOrderGenerator", out entry))
|
if (yaml.TryGetValue("DefaultOrderGenerator", out entry))
|
||||||
DefaultOrderGenerator = entry.Value;
|
DefaultOrderGenerator = entry.Value;
|
||||||
|
|
||||||
if (yaml.TryGetValue("Assemblies", out entry))
|
if (yaml.TryGetValue("Assemblies", out entry))
|
||||||
Assemblies = FieldLoader.GetValue<ImmutableArray<string>>("Assemblies", entry.Value);
|
Assemblies = FieldLoader.GetValue<string[]>("Assemblies", entry.Value);
|
||||||
|
|
||||||
if (yaml.TryGetValue("PackageFormats", out entry))
|
if (yaml.TryGetValue("PackageFormats", out entry))
|
||||||
PackageFormats = FieldLoader.GetValue<ImmutableArray<string>>("PackageFormats", entry.Value);
|
PackageFormats = FieldLoader.GetValue<string[]>("PackageFormats", entry.Value);
|
||||||
|
|
||||||
if (yaml.TryGetValue("SoundFormats", out entry))
|
if (yaml.TryGetValue("SoundFormats", out entry))
|
||||||
SoundFormats = FieldLoader.GetValue<ImmutableArray<string>>("SoundFormats", entry.Value);
|
SoundFormats = FieldLoader.GetValue<string[]>("SoundFormats", entry.Value);
|
||||||
|
|
||||||
if (yaml.TryGetValue("SpriteFormats", out entry))
|
if (yaml.TryGetValue("SpriteFormats", out entry))
|
||||||
SpriteFormats = FieldLoader.GetValue<ImmutableArray<string>>("SpriteFormats", entry.Value);
|
SpriteFormats = FieldLoader.GetValue<string[]>("SpriteFormats", entry.Value);
|
||||||
|
|
||||||
if (yaml.TryGetValue("VideoFormats", out entry))
|
if (yaml.TryGetValue("VideoFormats", out entry))
|
||||||
VideoFormats = FieldLoader.GetValue<ImmutableArray<string>>("VideoFormats", entry.Value);
|
VideoFormats = FieldLoader.GetValue<string[]>("VideoFormats", entry.Value);
|
||||||
|
|
||||||
if (yaml.TryGetValue("SpriteSequenceFormat", out entry))
|
|
||||||
SpriteSequenceFormat = entry.Value;
|
|
||||||
|
|
||||||
if (yaml.TryGetValue("TerrainFormat", out entry))
|
|
||||||
TerrainFormat = entry.Value;
|
|
||||||
|
|
||||||
if (yaml.TryGetValue("AllowUnusedFluentMessagesInExternalPackages", out entry))
|
if (yaml.TryGetValue("AllowUnusedFluentMessagesInExternalPackages", out entry))
|
||||||
AllowUnusedFluentMessagesInExternalPackages =
|
AllowUnusedFluentMessagesInExternalPackages =
|
||||||
FieldLoader.GetValue<bool>("AllowUnusedFluentMessagesInExternalPackages", entry.Value);
|
FieldLoader.GetValue<bool>("AllowUnusedFluentMessagesInExternalPackages", entry.Value);
|
||||||
|
|
||||||
if (yaml.TryGetValue("RendererConstants", out entry))
|
if (yaml.TryGetValue("FontSheetSize", out entry))
|
||||||
RendererConstants = FieldLoader.Load<RendererConstants>(entry);
|
FontSheetSize = FieldLoader.GetValue<int>("FontSheetSize", entry.Value);
|
||||||
|
|
||||||
|
if (yaml.TryGetValue("CursorSheetSize", out entry))
|
||||||
|
CursorSheetSize = FieldLoader.GetValue<int>("CursorSheetSize", entry.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadCustomData(ObjectCreator oc)
|
||||||
|
{
|
||||||
|
foreach (var kv in yaml)
|
||||||
|
{
|
||||||
|
if (reservedModuleNames.Contains(kv.Key))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var t = oc.FindType(kv.Key);
|
||||||
|
if (t == null || !typeof(IGlobalModData).IsAssignableFrom(t))
|
||||||
|
throw new InvalidDataException($"`{kv.Key}` is not a valid mod manifest entry.");
|
||||||
|
|
||||||
|
IGlobalModData module;
|
||||||
|
var ctor = t.GetConstructor(new[] { typeof(MiniYaml) });
|
||||||
|
if (ctor != null)
|
||||||
|
{
|
||||||
|
// Class has opted-in to DIY initialization
|
||||||
|
module = (IGlobalModData)ctor.Invoke(new object[] { kv.Value });
|
||||||
|
}
|
||||||
else
|
else
|
||||||
RendererConstants = new RendererConstants();
|
{
|
||||||
|
// Automatically load the child nodes using FieldLoader
|
||||||
GlobalModData = yaml.Where(n => !ReservedModuleNames.Contains(n.Key))
|
module = oc.CreateObject<IGlobalModData>(kv.Key);
|
||||||
.ToFrozenDictionary(n => n.Key, n => n.Value);
|
FieldLoader.Load(module, kv.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ImmutableArray<string> YamlList(Dictionary<string, MiniYaml> yaml, string key)
|
modules.Add(module);
|
||||||
{
|
|
||||||
if (!yaml.TryGetValue(key, out var value))
|
|
||||||
return [];
|
|
||||||
|
|
||||||
return value.Nodes.Select(n => n.Key).ToImmutableArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static FrozenDictionary<string, string> YamlDictionary(Dictionary<string, MiniYaml> yaml, string key)
|
customDataLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static string[] YamlList(Dictionary<string, MiniYaml> yaml, string key)
|
||||||
{
|
{
|
||||||
if (!yaml.TryGetValue(key, out var value))
|
if (!yaml.TryGetValue(key, out var value))
|
||||||
return FrozenDictionary<string, string>.Empty;
|
return Array.Empty<string>();
|
||||||
|
|
||||||
return value.ToDictionary(my => my.Value).ToFrozenDictionary();
|
return value.Nodes.Select(n => n.Key).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
static IReadOnlyDictionary<string, string> YamlDictionary(Dictionary<string, MiniYaml> yaml, string key)
|
||||||
|
{
|
||||||
|
if (!yaml.TryGetValue(key, out var value))
|
||||||
|
return new Dictionary<string, string>();
|
||||||
|
|
||||||
|
return value.ToDictionary(my => my.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains<T>() where T : IGlobalModData
|
||||||
|
{
|
||||||
|
return modules.Contains<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Load a cached IGlobalModData instance.</summary>
|
||||||
|
public T Get<T>() where T : IGlobalModData
|
||||||
|
{
|
||||||
|
if (!customDataLoaded)
|
||||||
|
throw new InvalidOperationException("Attempted to call Manifest.Get() before loading custom data!");
|
||||||
|
|
||||||
|
var module = modules.GetOrDefault<T>();
|
||||||
|
|
||||||
|
// Lazily create the default values if not explicitly defined.
|
||||||
|
if (module == null)
|
||||||
|
{
|
||||||
|
module = (T)Game.ModData.ObjectCreator.CreateBasic(typeof(T));
|
||||||
|
modules.Add(module);
|
||||||
|
}
|
||||||
|
|
||||||
|
return module;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load an uncached IGlobalModData instance directly from the manifest yaml.
|
||||||
|
/// This should only be used by external mods that want to query data from this mod.
|
||||||
|
/// </summary>
|
||||||
|
public T Get<T>(ObjectCreator oc) where T : IGlobalModData
|
||||||
|
{
|
||||||
|
var t = typeof(T);
|
||||||
|
if (!yaml.TryGetValue(t.Name, out var data))
|
||||||
|
{
|
||||||
|
// Lazily create the default values if not explicitly defined.
|
||||||
|
return (T)oc.CreateBasic(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
IGlobalModData module;
|
||||||
|
var ctor = t.GetConstructor(new[] { typeof(MiniYaml) });
|
||||||
|
if (ctor != null)
|
||||||
|
{
|
||||||
|
// Class has opted-in to DIY initialization
|
||||||
|
module = (IGlobalModData)ctor.Invoke(new object[] { data.Value });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Automatically load the child nodes using FieldLoader
|
||||||
|
module = oc.CreateObject<IGlobalModData>(t.Name);
|
||||||
|
FieldLoader.Load(module, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (T)module;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
foreach (var module in modules)
|
||||||
|
{
|
||||||
|
var disposableModule = module as IDisposable;
|
||||||
|
disposableModule?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -216,8 +216,10 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class LocationInit(CPos value) : ValueActorInit<CPos>(value), ISingleInstanceInit
|
public class LocationInit : ValueActorInit<CPos>, ISingleInstanceInit
|
||||||
{
|
{
|
||||||
|
public LocationInit(CPos value)
|
||||||
|
: base(value) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class OwnerInit : ActorInit, ISingleInstanceInit
|
public class OwnerInit : ActorInit, ISingleInstanceInit
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ using System.Collections;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.Serialization;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
using OpenRA.Traits;
|
using OpenRA.Traits;
|
||||||
|
|
||||||
@@ -30,15 +30,15 @@ namespace OpenRA
|
|||||||
internal TypeDictionary InitDict => initDict.Value;
|
internal TypeDictionary InitDict => initDict.Value;
|
||||||
|
|
||||||
public ActorReference(string type)
|
public ActorReference(string type)
|
||||||
: this(type, new MiniYaml("")) { }
|
: this(type, new Dictionary<string, MiniYaml>()) { }
|
||||||
|
|
||||||
public ActorReference(string type, MiniYaml inits)
|
public ActorReference(string type, Dictionary<string, MiniYaml> inits)
|
||||||
{
|
{
|
||||||
Type = type;
|
Type = type;
|
||||||
initDict = Exts.Lazy(() =>
|
initDict = Exts.Lazy(() =>
|
||||||
{
|
{
|
||||||
var dict = new TypeDictionary();
|
var dict = new TypeDictionary();
|
||||||
foreach (var i in inits.Nodes)
|
foreach (var i in inits)
|
||||||
{
|
{
|
||||||
var init = LoadInit(i.Key, i.Value);
|
var init = LoadInit(i.Key, i.Value);
|
||||||
if (init is ISingleInstanceInit && dict.Contains(init.GetType()))
|
if (init is ISingleInstanceInit && dict.Contains(init.GetType()))
|
||||||
@@ -54,8 +54,13 @@ namespace OpenRA
|
|||||||
public ActorReference(string type, TypeDictionary inits)
|
public ActorReference(string type, TypeDictionary inits)
|
||||||
{
|
{
|
||||||
Type = type;
|
Type = type;
|
||||||
var initsClone = new TypeDictionary(inits);
|
initDict = new Lazy<TypeDictionary>(() =>
|
||||||
initDict = Exts.Lazy(() => initsClone);
|
{
|
||||||
|
var dict = new TypeDictionary();
|
||||||
|
foreach (var i in inits)
|
||||||
|
dict.Add(i);
|
||||||
|
return dict;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static ActorInit LoadInit(string initName, MiniYaml initYaml)
|
static ActorInit LoadInit(string initName, MiniYaml initYaml)
|
||||||
@@ -65,15 +70,15 @@ namespace OpenRA
|
|||||||
if (type == null)
|
if (type == null)
|
||||||
throw new InvalidDataException($"Unknown initializer type '{initInstance[0]}Init'");
|
throw new InvalidDataException($"Unknown initializer type '{initInstance[0]}Init'");
|
||||||
|
|
||||||
var init = (ActorInit)RuntimeHelpers.GetUninitializedObject(type);
|
var init = (ActorInit)FormatterServices.GetUninitializedObject(type);
|
||||||
if (initInstance.Length > 1)
|
if (initInstance.Length > 1)
|
||||||
type.GetField(nameof(ActorInit.InstanceName)).SetValue(init, initInstance[1]);
|
type.GetField(nameof(ActorInit.InstanceName)).SetValue(init, initInstance[1]);
|
||||||
|
|
||||||
var loader = type.GetMethod("Initialize", [typeof(MiniYaml)]);
|
var loader = type.GetMethod("Initialize", new[] { typeof(MiniYaml) });
|
||||||
if (loader == null)
|
if (loader == null)
|
||||||
throw new InvalidDataException($"{initInstance[0]}Init does not define a yaml-assignable type.");
|
throw new InvalidDataException($"{initInstance[0]}Init does not define a yaml-assignable type.");
|
||||||
|
|
||||||
loader.Invoke(init, [initYaml]);
|
loader.Invoke(init, new[] { initYaml });
|
||||||
return init;
|
return init;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +110,11 @@ namespace OpenRA
|
|||||||
|
|
||||||
public ActorReference Clone()
|
public ActorReference Clone()
|
||||||
{
|
{
|
||||||
return new ActorReference(Type, initDict.Value);
|
var clone = new ActorReference(Type);
|
||||||
|
foreach (var init in initDict.Value)
|
||||||
|
clone.initDict.Value.Add(init);
|
||||||
|
|
||||||
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Add(ActorInit init)
|
public void Add(ActorInit init)
|
||||||
@@ -116,15 +125,6 @@ namespace OpenRA
|
|||||||
InitDict.Add(init);
|
InitDict.Add(init);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Replace<T>(T init) where T : ActorInit, ISingleInstanceInit
|
|
||||||
{
|
|
||||||
var original = GetOrDefault<T>();
|
|
||||||
if (original != null)
|
|
||||||
Remove(original);
|
|
||||||
|
|
||||||
Add(init);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Remove(ActorInit o) { initDict.Value.Remove(o); }
|
public void Remove(ActorInit o) { initDict.Value.Remove(o); }
|
||||||
|
|
||||||
public int RemoveAll<T>() where T : ActorInit
|
public int RemoveAll<T>() where T : ActorInit
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
*/
|
*/
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
@@ -90,31 +89,6 @@ namespace OpenRA
|
|||||||
return GetEnumerator();
|
return GetEnumerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Returns the minimal region that covers at least the specified cells.</summary>
|
|
||||||
public static CellCoordsRegion BoundingRegion(IReadOnlyCollection<CPos> cells)
|
|
||||||
{
|
|
||||||
if (cells == null || cells.Count == 0)
|
|
||||||
throw new ArgumentException("cells must not be null or empty.", nameof(cells));
|
|
||||||
|
|
||||||
var minX = int.MaxValue;
|
|
||||||
var minY = int.MaxValue;
|
|
||||||
var maxX = int.MinValue;
|
|
||||||
var maxY = int.MinValue;
|
|
||||||
foreach (var cell in cells)
|
|
||||||
{
|
|
||||||
if (minX > cell.X)
|
|
||||||
minX = cell.X;
|
|
||||||
if (maxX < cell.X)
|
|
||||||
maxX = cell.X;
|
|
||||||
if (minY > cell.Y)
|
|
||||||
minY = cell.Y;
|
|
||||||
if (maxY < cell.Y)
|
|
||||||
maxY = cell.Y;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new CellCoordsRegion(new CPos(minX, minY), new CPos(maxX, maxY));
|
|
||||||
}
|
|
||||||
|
|
||||||
public CPos TopLeft { get; }
|
public CPos TopLeft { get; }
|
||||||
public CPos BottomRight { get; }
|
public CPos BottomRight { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,16 +63,12 @@ namespace OpenRA
|
|||||||
|
|
||||||
var u = (x - y) / 2;
|
var u = (x - y) / 2;
|
||||||
var v = x + y;
|
var v = x + y;
|
||||||
if (!Bounds.Contains(u, v))
|
|
||||||
throw new IndexOutOfRangeException();
|
|
||||||
return v * Size.Width + u;
|
return v * Size.Width + u;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve an array index from map coordinates
|
// Resolve an array index from map coordinates
|
||||||
int Index(MPos uv)
|
int Index(MPos uv)
|
||||||
{
|
{
|
||||||
if (!Bounds.Contains(uv.U, uv.V))
|
|
||||||
throw new IndexOutOfRangeException();
|
|
||||||
return uv.V * Size.Width + uv.U;
|
return uv.V * Size.Width + uv.U;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,9 +145,6 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
return uv.Clamp(new Rectangle(0, 0, Size.Width - 1, Size.Height - 1));
|
return uv.Clamp(new Rectangle(0, 0, Size.Width - 1, Size.Height - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
public CellRegion CellRegion =>
|
|
||||||
new(GridType, new MPos(0, 0), new MPos(Size.Width - 1, Size.Height - 1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper functions
|
// Helper functions
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ namespace OpenRA
|
|||||||
protected readonly Rectangle Bounds;
|
protected readonly Rectangle Bounds;
|
||||||
|
|
||||||
protected CellLayerBase(Map map)
|
protected CellLayerBase(Map map)
|
||||||
: this(map.Grid.Type, map.MapSize) { }
|
: this(map.Grid.Type, new Size(map.MapSize.X, map.MapSize.Y)) { }
|
||||||
|
|
||||||
protected CellLayerBase(MapGridType gridType, Size size)
|
protected CellLayerBase(MapGridType gridType, Size size)
|
||||||
{
|
{
|
||||||
@@ -40,19 +40,19 @@ namespace OpenRA
|
|||||||
if (Size != anotherLayer.Size || GridType != anotherLayer.GridType)
|
if (Size != anotherLayer.Size || GridType != anotherLayer.GridType)
|
||||||
throw new ArgumentException("Layers must have a matching size and shape (grid type).", nameof(anotherLayer));
|
throw new ArgumentException("Layers must have a matching size and shape (grid type).", nameof(anotherLayer));
|
||||||
|
|
||||||
anotherLayer.Entries.AsSpan().CopyTo(Entries);
|
Array.Copy(anotherLayer.Entries, Entries, Entries.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Clears the layer contents with their default value.</summary>
|
/// <summary>Clears the layer contents with their default value.</summary>
|
||||||
public virtual void Clear()
|
public virtual void Clear()
|
||||||
{
|
{
|
||||||
Entries.AsSpan().Clear();
|
Array.Clear(Entries, 0, Entries.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Clears the layer contents with a known value.</summary>
|
/// <summary>Clears the layer contents with a known value.</summary>
|
||||||
public virtual void Clear(T clearValue)
|
public virtual void Clear(T clearValue)
|
||||||
{
|
{
|
||||||
Entries.AsSpan().Fill(clearValue);
|
Array.Fill(Entries, clearValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerator<T> GetEnumerator()
|
public IEnumerator<T> GetEnumerator()
|
||||||
@@ -64,15 +64,5 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
return GetEnumerator();
|
return GetEnumerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlyMemory<T> AsReadOnlyMemory()
|
|
||||||
{
|
|
||||||
return Entries.AsMemory();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Memory<T> AsMemory()
|
|
||||||
{
|
|
||||||
return Entries.AsMemory();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,12 +33,12 @@ namespace OpenRA
|
|||||||
public readonly uint HeightsOffset;
|
public readonly uint HeightsOffset;
|
||||||
public readonly uint ResourcesOffset;
|
public readonly uint ResourcesOffset;
|
||||||
|
|
||||||
public BinaryDataHeader(Stream s, Size expectedSize)
|
public BinaryDataHeader(Stream s, int2 expectedSize)
|
||||||
{
|
{
|
||||||
Format = s.ReadUInt8();
|
Format = s.ReadUInt8();
|
||||||
var width = s.ReadUInt16();
|
var width = s.ReadUInt16();
|
||||||
var height = s.ReadUInt16();
|
var height = s.ReadUInt16();
|
||||||
if (width != expectedSize.Width || height != expectedSize.Height)
|
if (width != expectedSize.X || height != expectedSize.Y)
|
||||||
throw new InvalidDataException("Invalid tile data");
|
throw new InvalidDataException("Invalid tile data");
|
||||||
|
|
||||||
if (Format == 1)
|
if (Format == 1)
|
||||||
@@ -112,7 +112,7 @@ namespace OpenRA
|
|||||||
else if (type == Type.MiniYaml)
|
else if (type == Type.MiniYaml)
|
||||||
field.SetValue(map, node.Value);
|
field.SetValue(map, node.Value);
|
||||||
else
|
else
|
||||||
FieldLoader.LoadFieldOrProperty(map, fieldName, node.Value.Value);
|
FieldLoader.LoadField(map, fieldName, node.Value.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (property != null)
|
if (property != null)
|
||||||
@@ -122,7 +122,7 @@ namespace OpenRA
|
|||||||
else if (type == Type.MiniYaml)
|
else if (type == Type.MiniYaml)
|
||||||
property.SetValue(map, node.Value, null);
|
property.SetValue(map, node.Value, null);
|
||||||
else
|
else
|
||||||
FieldLoader.LoadFieldOrProperty(map, fieldName, node.Value.Value);
|
FieldLoader.LoadField(map, fieldName, node.Value.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +158,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
/// <summary>Defines the order of the fields in map.yaml.</summary>
|
/// <summary>Defines the order of the fields in map.yaml.</summary>
|
||||||
static readonly MapField[] YamlFields =
|
static readonly MapField[] YamlFields =
|
||||||
[
|
{
|
||||||
new("MapFormat"),
|
new("MapFormat"),
|
||||||
new("RequiresMod"),
|
new("RequiresMod"),
|
||||||
new("Title"),
|
new("Title"),
|
||||||
@@ -179,7 +179,7 @@ namespace OpenRA
|
|||||||
new("Voices", nameof(VoiceDefinitions), required: false),
|
new("Voices", nameof(VoiceDefinitions), required: false),
|
||||||
new("Music", nameof(MusicDefinitions), required: false),
|
new("Music", nameof(MusicDefinitions), required: false),
|
||||||
new("Notifications", nameof(NotificationDefinitions), required: false),
|
new("Notifications", nameof(NotificationDefinitions), required: false),
|
||||||
];
|
};
|
||||||
|
|
||||||
// Format versions
|
// Format versions
|
||||||
public int MapFormat { get; private set; }
|
public int MapFormat { get; private set; }
|
||||||
@@ -193,13 +193,13 @@ namespace OpenRA
|
|||||||
public bool LockPreview;
|
public bool LockPreview;
|
||||||
public Rectangle Bounds;
|
public Rectangle Bounds;
|
||||||
public MapVisibility Visibility = MapVisibility.Lobby;
|
public MapVisibility Visibility = MapVisibility.Lobby;
|
||||||
public ImmutableArray<string> Categories = ["Conquest"];
|
public string[] Categories = { "Conquest" };
|
||||||
|
|
||||||
public Size MapSize { get; private set; }
|
public int2 MapSize { get; private set; }
|
||||||
|
|
||||||
// Player and actor yaml. Public for access by the map importers and lint checks.
|
// Player and actor yaml. Public for access by the map importers and lint checks.
|
||||||
public IReadOnlyCollection<MiniYamlNode> PlayerDefinitions = [];
|
public IReadOnlyCollection<MiniYamlNode> PlayerDefinitions = ImmutableArray<MiniYamlNode>.Empty;
|
||||||
public IReadOnlyCollection<MiniYamlNode> ActorDefinitions = [];
|
public IReadOnlyCollection<MiniYamlNode> ActorDefinitions = ImmutableArray<MiniYamlNode>.Empty;
|
||||||
|
|
||||||
// Custom map yaml. Public for access by the map importers and lint checks
|
// Custom map yaml. Public for access by the map importers and lint checks
|
||||||
public MiniYaml RuleDefinitions;
|
public MiniYaml RuleDefinitions;
|
||||||
@@ -211,7 +211,7 @@ namespace OpenRA
|
|||||||
public MiniYaml MusicDefinitions;
|
public MiniYaml MusicDefinitions;
|
||||||
public MiniYaml NotificationDefinitions;
|
public MiniYaml NotificationDefinitions;
|
||||||
|
|
||||||
public readonly Dictionary<CPos, TerrainTile> ReplacedInvalidTerrainTiles = [];
|
public readonly Dictionary<CPos, TerrainTile> ReplacedInvalidTerrainTiles = new();
|
||||||
|
|
||||||
// Generated data
|
// Generated data
|
||||||
public readonly MapGrid Grid;
|
public readonly MapGrid Grid;
|
||||||
@@ -283,7 +283,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
// Take the SHA1
|
// Take the SHA1
|
||||||
if (streams.Count == 0)
|
if (streams.Count == 0)
|
||||||
return CryptoUtil.SHA1Hash([]);
|
return CryptoUtil.SHA1Hash(Array.Empty<byte>());
|
||||||
|
|
||||||
var merged = streams[0];
|
var merged = streams[0];
|
||||||
for (var i = 1; i < streams.Count; i++)
|
for (var i = 1; i < streams.Count; i++)
|
||||||
@@ -321,25 +321,26 @@ namespace OpenRA
|
|||||||
/// Initializes a new map created by the editor or importer.
|
/// Initializes a new map created by the editor or importer.
|
||||||
/// The map will not receive a valid UID until after it has been saved and reloaded.
|
/// The map will not receive a valid UID until after it has been saved and reloaded.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Map(ModData modData, ITerrainInfo terrainInfo, Size size)
|
public Map(ModData modData, ITerrainInfo terrainInfo, int width, int height)
|
||||||
{
|
{
|
||||||
this.modData = modData;
|
this.modData = modData;
|
||||||
MapSize = size;
|
var size = new Size(width, height);
|
||||||
Grid = modData.GetOrCreate<MapGrid>();
|
Grid = modData.Manifest.Get<MapGrid>();
|
||||||
|
|
||||||
Title = "Name your map here";
|
Title = "Name your map here";
|
||||||
Author = "Your name here";
|
Author = "Your name here";
|
||||||
|
|
||||||
|
MapSize = new int2(size);
|
||||||
Tileset = terrainInfo.Id;
|
Tileset = terrainInfo.Id;
|
||||||
|
|
||||||
// Empty rules that can be added to by the importers.
|
// Empty rules that can be added to by the importers.
|
||||||
// Will be dropped on save if nothing is added to it
|
// Will be dropped on save if nothing is added to it
|
||||||
RuleDefinitions = new MiniYaml("");
|
RuleDefinitions = new MiniYaml("");
|
||||||
|
|
||||||
Tiles = new CellLayer<TerrainTile>(Grid.Type, MapSize);
|
Tiles = new CellLayer<TerrainTile>(Grid.Type, size);
|
||||||
Resources = new CellLayer<ResourceTile>(Grid.Type, MapSize);
|
Resources = new CellLayer<ResourceTile>(Grid.Type, size);
|
||||||
Height = new CellLayer<byte>(Grid.Type, MapSize);
|
Height = new CellLayer<byte>(Grid.Type, size);
|
||||||
Ramp = new CellLayer<byte>(Grid.Type, MapSize);
|
Ramp = new CellLayer<byte>(Grid.Type, size);
|
||||||
Tiles.Clear(terrainInfo.DefaultTerrainTile);
|
Tiles.Clear(terrainInfo.DefaultTerrainTile);
|
||||||
|
|
||||||
if (Grid.MaximumTerrainHeight > 0)
|
if (Grid.MaximumTerrainHeight > 0)
|
||||||
@@ -367,15 +368,16 @@ namespace OpenRA
|
|||||||
if (MapFormat < SupportedMapFormat)
|
if (MapFormat < SupportedMapFormat)
|
||||||
throw new InvalidDataException($"Map format {MapFormat} is not supported.\n File: {package.Name}");
|
throw new InvalidDataException($"Map format {MapFormat} is not supported.\n File: {package.Name}");
|
||||||
|
|
||||||
PlayerDefinitions = yaml.NodeWithKeyOrDefault("Players")?.Value.Nodes ?? [];
|
PlayerDefinitions = yaml.NodeWithKeyOrDefault("Players")?.Value.Nodes ?? ImmutableArray<MiniYamlNode>.Empty;
|
||||||
ActorDefinitions = yaml.NodeWithKeyOrDefault("Actors")?.Value.Nodes ?? [];
|
ActorDefinitions = yaml.NodeWithKeyOrDefault("Actors")?.Value.Nodes ?? ImmutableArray<MiniYamlNode>.Empty;
|
||||||
|
|
||||||
Grid = modData.GetOrCreate<MapGrid>();
|
Grid = modData.Manifest.Get<MapGrid>();
|
||||||
|
|
||||||
Tiles = new CellLayer<TerrainTile>(Grid.Type, MapSize);
|
var size = new Size(MapSize.X, MapSize.Y);
|
||||||
Resources = new CellLayer<ResourceTile>(Grid.Type, MapSize);
|
Tiles = new CellLayer<TerrainTile>(Grid.Type, size);
|
||||||
Height = new CellLayer<byte>(Grid.Type, MapSize);
|
Resources = new CellLayer<ResourceTile>(Grid.Type, size);
|
||||||
Ramp = new CellLayer<byte>(Grid.Type, MapSize);
|
Height = new CellLayer<byte>(Grid.Type, size);
|
||||||
|
Ramp = new CellLayer<byte>(Grid.Type, size);
|
||||||
|
|
||||||
using (var s = Package.GetStream("map.bin"))
|
using (var s = Package.GetStream("map.bin"))
|
||||||
{
|
{
|
||||||
@@ -383,9 +385,9 @@ namespace OpenRA
|
|||||||
if (header.TilesOffset > 0)
|
if (header.TilesOffset > 0)
|
||||||
{
|
{
|
||||||
s.Position = header.TilesOffset;
|
s.Position = header.TilesOffset;
|
||||||
for (var i = 0; i < MapSize.Width; i++)
|
for (var i = 0; i < MapSize.X; i++)
|
||||||
{
|
{
|
||||||
for (var j = 0; j < MapSize.Height; j++)
|
for (var j = 0; j < MapSize.Y; j++)
|
||||||
{
|
{
|
||||||
var tile = s.ReadUInt16();
|
var tile = s.ReadUInt16();
|
||||||
var index = s.ReadUInt8();
|
var index = s.ReadUInt8();
|
||||||
@@ -402,9 +404,9 @@ namespace OpenRA
|
|||||||
if (header.ResourcesOffset > 0)
|
if (header.ResourcesOffset > 0)
|
||||||
{
|
{
|
||||||
s.Position = header.ResourcesOffset;
|
s.Position = header.ResourcesOffset;
|
||||||
for (var i = 0; i < MapSize.Width; i++)
|
for (var i = 0; i < MapSize.X; i++)
|
||||||
{
|
{
|
||||||
for (var j = 0; j < MapSize.Height; j++)
|
for (var j = 0; j < MapSize.Y; j++)
|
||||||
{
|
{
|
||||||
var type = s.ReadUInt8();
|
var type = s.ReadUInt8();
|
||||||
var density = s.ReadUInt8();
|
var density = s.ReadUInt8();
|
||||||
@@ -416,8 +418,8 @@ namespace OpenRA
|
|||||||
if (header.HeightsOffset > 0)
|
if (header.HeightsOffset > 0)
|
||||||
{
|
{
|
||||||
s.Position = header.HeightsOffset;
|
s.Position = header.HeightsOffset;
|
||||||
for (var i = 0; i < MapSize.Width; i++)
|
for (var i = 0; i < MapSize.X; i++)
|
||||||
for (var j = 0; j < MapSize.Height; j++)
|
for (var j = 0; j < MapSize.Y; j++)
|
||||||
Height[new MPos(i, j)] = s.ReadUInt8().Clamp((byte)0, Grid.MaximumTerrainHeight);
|
Height[new MPos(i, j)] = s.ReadUInt8().Clamp((byte)0, Grid.MaximumTerrainHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -453,7 +455,7 @@ namespace OpenRA
|
|||||||
Sequences = new SequenceSet(this, modData, Tileset, SequenceDefinitions);
|
Sequences = new SequenceSet(this, modData, Tileset, SequenceDefinitions);
|
||||||
|
|
||||||
var tl = new MPos(0, 0).ToCPos(this);
|
var tl = new MPos(0, 0).ToCPos(this);
|
||||||
var br = new MPos(MapSize.Width - 1, MapSize.Height - 1).ToCPos(this);
|
var br = new MPos(MapSize.X - 1, MapSize.Y - 1).ToCPos(this);
|
||||||
AllCells = new CellRegion(Grid.Type, tl, br);
|
AllCells = new CellRegion(Grid.Type, tl, br);
|
||||||
|
|
||||||
var btl = new PPos(Bounds.Left, Bounds.Top);
|
var btl = new PPos(Bounds.Left, Bounds.Top);
|
||||||
@@ -514,7 +516,7 @@ namespace OpenRA
|
|||||||
foreach (var cell in AllCells)
|
foreach (var cell in AllCells)
|
||||||
{
|
{
|
||||||
var uv = cell.ToMPos(Grid.Type);
|
var uv = cell.ToMPos(Grid.Type);
|
||||||
cellProjection[uv] = [];
|
cellProjection[uv] = Array.Empty<PPos>();
|
||||||
inverseCellProjection[uv] = new List<MPos>(1);
|
inverseCellProjection[uv] = new List<MPos>(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,7 +532,7 @@ namespace OpenRA
|
|||||||
if (Grid.MaximumTerrainHeight == 0)
|
if (Grid.MaximumTerrainHeight == 0)
|
||||||
{
|
{
|
||||||
uv = cell.ToMPos(Grid.Type);
|
uv = cell.ToMPos(Grid.Type);
|
||||||
cellProjection[cell] = [(PPos)uv];
|
cellProjection[cell] = new[] { (PPos)uv };
|
||||||
var inverse = inverseCellProjection[uv];
|
var inverse = inverseCellProjection[uv];
|
||||||
inverse.Clear();
|
inverse.Clear();
|
||||||
inverse.Add(uv);
|
inverse.Add(uv);
|
||||||
@@ -605,7 +607,7 @@ namespace OpenRA
|
|||||||
// Any changes to this function should be reflected when setting projectionSafeBounds.
|
// Any changes to this function should be reflected when setting projectionSafeBounds.
|
||||||
var height = mapHeight[uv];
|
var height = mapHeight[uv];
|
||||||
if (height == 0)
|
if (height == 0)
|
||||||
return [(PPos)uv];
|
return new[] { (PPos)uv };
|
||||||
|
|
||||||
// Odd-height ramps get bumped up a level to the next even height layer
|
// Odd-height ramps get bumped up a level to the next even height layer
|
||||||
if ((height & 1) == 1 && Ramp[uv] != 0)
|
if ((height & 1) == 1 && Ramp[uv] != 0)
|
||||||
@@ -682,13 +684,13 @@ namespace OpenRA
|
|||||||
writer.Write(TileFormat);
|
writer.Write(TileFormat);
|
||||||
|
|
||||||
// Size
|
// Size
|
||||||
writer.Write((ushort)MapSize.Width);
|
writer.Write((ushort)MapSize.X);
|
||||||
writer.Write((ushort)MapSize.Height);
|
writer.Write((ushort)MapSize.Y);
|
||||||
|
|
||||||
// Data offsets
|
// Data offsets
|
||||||
const int TilesOffset = 17;
|
const int TilesOffset = 17;
|
||||||
var heightsOffset = Grid.MaximumTerrainHeight > 0 ? 3 * MapSize.Width * MapSize.Height + 17 : 0;
|
var heightsOffset = Grid.MaximumTerrainHeight > 0 ? 3 * MapSize.X * MapSize.Y + 17 : 0;
|
||||||
var resourcesOffset = (Grid.MaximumTerrainHeight > 0 ? 4 : 3) * MapSize.Width * MapSize.Height + 17;
|
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)heightsOffset);
|
||||||
@@ -697,9 +699,9 @@ namespace OpenRA
|
|||||||
// Tile data
|
// Tile data
|
||||||
if (TilesOffset != 0)
|
if (TilesOffset != 0)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < MapSize.Width; i++)
|
for (var i = 0; i < MapSize.X; i++)
|
||||||
{
|
{
|
||||||
for (var j = 0; j < MapSize.Height; j++)
|
for (var j = 0; j < MapSize.Y; j++)
|
||||||
{
|
{
|
||||||
var tile = Tiles[new MPos(i, j)];
|
var tile = Tiles[new MPos(i, j)];
|
||||||
writer.Write(tile.Type);
|
writer.Write(tile.Type);
|
||||||
@@ -710,16 +712,16 @@ namespace OpenRA
|
|||||||
|
|
||||||
// Height data
|
// Height data
|
||||||
if (heightsOffset != 0)
|
if (heightsOffset != 0)
|
||||||
for (var i = 0; i < MapSize.Width; i++)
|
for (var i = 0; i < MapSize.X; i++)
|
||||||
for (var j = 0; j < MapSize.Height; j++)
|
for (var j = 0; j < MapSize.Y; j++)
|
||||||
writer.Write(Height[new MPos(i, j)]);
|
writer.Write(Height[new MPos(i, j)]);
|
||||||
|
|
||||||
// Resource data
|
// Resource data
|
||||||
if (resourcesOffset != 0)
|
if (resourcesOffset != 0)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < MapSize.Width; i++)
|
for (var i = 0; i < MapSize.X; i++)
|
||||||
{
|
{
|
||||||
for (var j = 0; j < MapSize.Height; j++)
|
for (var j = 0; j < MapSize.Y; j++)
|
||||||
{
|
{
|
||||||
var tile = Resources[new MPos(i, j)];
|
var tile = Resources[new MPos(i, j)];
|
||||||
writer.Write(tile.Type);
|
writer.Write(tile.Type);
|
||||||
@@ -756,7 +758,7 @@ namespace OpenRA
|
|||||||
var positions = new List<(MPos Uv, Color Color)>();
|
var positions = new List<(MPos Uv, Color Color)>();
|
||||||
foreach (var actor in actors)
|
foreach (var actor in actors)
|
||||||
{
|
{
|
||||||
var s = new ActorReference(actor.Value.Value, actor.Value);
|
var s = new ActorReference(actor.Value.Value, actor.Value.ToDictionary());
|
||||||
|
|
||||||
var ai = Rules.Actors[actor.Value.Value];
|
var ai = Rules.Actors[actor.Value.Value];
|
||||||
var impsis = ai.TraitInfos<IMapPreviewSignatureInfo>();
|
var impsis = ai.TraitInfos<IMapPreviewSignatureInfo>();
|
||||||
@@ -1042,7 +1044,7 @@ namespace OpenRA
|
|||||||
return (PPos)CellContaining(projectedPos).ToMPos(Grid.Type);
|
return (PPos)CellContaining(projectedPos).ToMPos(Grid.Type);
|
||||||
}
|
}
|
||||||
|
|
||||||
static readonly PPos[] NoProjectedCells = [];
|
static readonly PPos[] NoProjectedCells = Array.Empty<PPos>();
|
||||||
public PPos[] ProjectedCellsCovering(MPos uv)
|
public PPos[] ProjectedCellsCovering(MPos uv)
|
||||||
{
|
{
|
||||||
if (!initializedCellProjection)
|
if (!initializedCellProjection)
|
||||||
@@ -1062,7 +1064,7 @@ namespace OpenRA
|
|||||||
InitializeCellProjection();
|
InitializeCellProjection();
|
||||||
|
|
||||||
if (!inverseCellProjection.Contains(uv))
|
if (!inverseCellProjection.Contains(uv))
|
||||||
return [];
|
return new List<MPos>();
|
||||||
|
|
||||||
return inverseCellProjection[uv];
|
return inverseCellProjection[uv];
|
||||||
}
|
}
|
||||||
@@ -1087,15 +1089,16 @@ namespace OpenRA
|
|||||||
var oldMapResources = Resources;
|
var oldMapResources = Resources;
|
||||||
var oldMapHeight = Height;
|
var oldMapHeight = Height;
|
||||||
var oldMapRamp = Ramp;
|
var oldMapRamp = Ramp;
|
||||||
|
var newSize = new Size(width, height);
|
||||||
|
|
||||||
MapSize = new Size(width, height);
|
Tiles = CellLayer.Resize(oldMapTiles, newSize, oldMapTiles[MPos.Zero]);
|
||||||
Tiles = CellLayer.Resize(oldMapTiles, MapSize, oldMapTiles[MPos.Zero]);
|
Resources = CellLayer.Resize(oldMapResources, newSize, oldMapResources[MPos.Zero]);
|
||||||
Resources = CellLayer.Resize(oldMapResources, MapSize, oldMapResources[MPos.Zero]);
|
Height = CellLayer.Resize(oldMapHeight, newSize, oldMapHeight[MPos.Zero]);
|
||||||
Height = CellLayer.Resize(oldMapHeight, MapSize, oldMapHeight[MPos.Zero]);
|
Ramp = CellLayer.Resize(oldMapRamp, newSize, oldMapRamp[MPos.Zero]);
|
||||||
Ramp = CellLayer.Resize(oldMapRamp, MapSize, oldMapRamp[MPos.Zero]);
|
MapSize = new int2(newSize);
|
||||||
|
|
||||||
var tl = new MPos(0, 0);
|
var tl = new MPos(0, 0);
|
||||||
var br = new MPos(MapSize.Width - 1, MapSize.Height - 1);
|
var br = new MPos(MapSize.X - 1, MapSize.Y - 1);
|
||||||
AllCells = new CellRegion(Grid.Type, tl.ToCPos(this), br.ToCPos(this));
|
AllCells = new CellRegion(Grid.Type, tl.ToCPos(this), br.ToCPos(this));
|
||||||
SetBounds(new PPos(tl.U + 1, tl.V + 1), new PPos(br.U - 1, br.V - 1));
|
SetBounds(new PPos(tl.U + 1, tl.V + 1), new PPos(br.U - 1, br.V - 1));
|
||||||
}
|
}
|
||||||
@@ -1142,11 +1145,6 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
public byte GetTerrainIndex(CPos cell)
|
public byte GetTerrainIndex(CPos cell)
|
||||||
{
|
|
||||||
return GetTerrainIndex(cell.ToMPos(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte GetTerrainIndex(MPos uv)
|
|
||||||
{
|
{
|
||||||
// Lazily initialize a cache for terrain indexes.
|
// Lazily initialize a cache for terrain indexes.
|
||||||
if (cachedTerrainIndexes == null)
|
if (cachedTerrainIndexes == null)
|
||||||
@@ -1155,6 +1153,7 @@ namespace OpenRA
|
|||||||
cachedTerrainIndexes.Clear(InvalidCachedTerrainIndex);
|
cachedTerrainIndexes.Clear(InvalidCachedTerrainIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var uv = cell.ToMPos(this);
|
||||||
var terrainIndex = cachedTerrainIndexes[uv];
|
var terrainIndex = cachedTerrainIndexes[uv];
|
||||||
|
|
||||||
// PERF: Cache terrain indexes per cell on demand.
|
// PERF: Cache terrain indexes per cell on demand.
|
||||||
@@ -1169,12 +1168,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
public TerrainTypeInfo GetTerrainInfo(CPos cell)
|
public TerrainTypeInfo GetTerrainInfo(CPos cell)
|
||||||
{
|
{
|
||||||
return GetTerrainInfo(cell.ToMPos(this));
|
return Rules.TerrainInfo.TerrainTypes[GetTerrainIndex(cell)];
|
||||||
}
|
|
||||||
|
|
||||||
public TerrainTypeInfo GetTerrainInfo(MPos uv)
|
|
||||||
{
|
|
||||||
return Rules.TerrainInfo.TerrainTypes[GetTerrainIndex(uv)];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CPos Clamp(CPos cell)
|
public CPos Clamp(CPos cell)
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ using System.Collections;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using OpenRA.FileSystem;
|
using OpenRA.FileSystem;
|
||||||
using OpenRA.Graphics;
|
using OpenRA.Graphics;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
using OpenRA.Support;
|
using OpenRA.Support;
|
||||||
using FS = OpenRA.FileSystem.FileSystem;
|
|
||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
{
|
{
|
||||||
@@ -28,27 +28,26 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
public static readonly MapPreview UnknownMap = new(null, null, MapGridType.Rectangular, null);
|
public static readonly MapPreview UnknownMap = new(null, null, MapGridType.Rectangular, null);
|
||||||
public IReadOnlyDictionary<IReadOnlyPackage, MapClassification> MapLocations => mapLocations;
|
public IReadOnlyDictionary<IReadOnlyPackage, MapClassification> MapLocations => mapLocations;
|
||||||
readonly Dictionary<IReadOnlyPackage, MapClassification> mapLocations = [];
|
readonly Dictionary<IReadOnlyPackage, MapClassification> mapLocations = new();
|
||||||
public bool LoadPreviewImages = true;
|
public bool LoadPreviewImages = true;
|
||||||
|
|
||||||
readonly Manifest manifest;
|
readonly Cache<string, MapPreview> previews;
|
||||||
readonly FS modFiles;
|
readonly ModData modData;
|
||||||
Cache<string, MapPreview> previews;
|
|
||||||
readonly SheetBuilder sheetBuilder;
|
readonly SheetBuilder sheetBuilder;
|
||||||
Thread previewLoaderThread;
|
Thread previewLoaderThread;
|
||||||
bool previewLoaderThreadShutDown = true;
|
bool previewLoaderThreadShutDown = true;
|
||||||
readonly object syncRoot = new();
|
readonly object syncRoot = new();
|
||||||
readonly Queue<MapPreview> generateMinimap = [];
|
readonly Queue<MapPreview> generateMinimap = new();
|
||||||
|
|
||||||
public HashSet<string> StringPool { get; } = [];
|
public HashSet<string> StringPool { get; } = new();
|
||||||
|
|
||||||
readonly List<MapDirectoryTracker> mapDirectoryTrackers = [];
|
readonly List<MapDirectoryTracker> mapDirectoryTrackers = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The most recently modified or loaded map at runtime.
|
/// The most recently modified or loaded map at runtime.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string LastModifiedMap { get; private set; } = null;
|
public string LastModifiedMap { get; private set; } = null;
|
||||||
readonly Dictionary<string, string> mapUpdates = [];
|
readonly Dictionary<string, string> mapUpdates = new();
|
||||||
|
|
||||||
string lastLoadedLastModifiedMap;
|
string lastLoadedLastModifiedMap;
|
||||||
|
|
||||||
@@ -68,11 +67,13 @@ namespace OpenRA
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MapCache(Manifest manifest, FS modFiles)
|
public MapCache(ModData modData)
|
||||||
{
|
{
|
||||||
this.manifest = manifest;
|
this.modData = modData;
|
||||||
this.modFiles = modFiles;
|
|
||||||
sheetBuilder = new SheetBuilder(SheetType.BGRA, manifest.RendererConstants.MapPreviewSheetSize);
|
var gridType = Exts.Lazy(() => modData.Manifest.Get<MapGrid>().Type);
|
||||||
|
previews = new Cache<string, MapPreview>(uid => new MapPreview(modData, uid, gridType.Value, this));
|
||||||
|
sheetBuilder = new SheetBuilder(SheetType.BGRA);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateMaps()
|
public void UpdateMaps()
|
||||||
@@ -81,21 +82,20 @@ namespace OpenRA
|
|||||||
tracker.UpdateMaps(this);
|
tracker.UpdateMaps(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadMaps(ModData modData)
|
public void LoadMaps()
|
||||||
{
|
{
|
||||||
// Utility mod that does not support maps
|
// Utility mod that does not support maps
|
||||||
if (manifest.MapFolders.Count == 0)
|
if (!modData.Manifest.Contains<MapGrid>())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var gridType = modData.GetOrCreate<MapGrid>().Type;
|
var mapGrid = modData.Manifest.Get<MapGrid>();
|
||||||
previews = new Cache<string, MapPreview>(uid => new MapPreview(modData, uid, gridType, this));
|
|
||||||
|
|
||||||
// Enumerate map directories
|
// Enumerate map directories
|
||||||
foreach (var kv in manifest.MapFolders)
|
foreach (var kv in modData.Manifest.MapFolders)
|
||||||
{
|
{
|
||||||
var name = kv.Key;
|
var name = kv.Key;
|
||||||
var classification = string.IsNullOrEmpty(kv.Value)
|
var classification = string.IsNullOrEmpty(kv.Value)
|
||||||
? MapClassification.Unknown : Enum.Parse<MapClassification>(kv.Value);
|
? MapClassification.Unknown : Enum<MapClassification>.Parse(kv.Value);
|
||||||
|
|
||||||
IReadOnlyPackage package;
|
IReadOnlyPackage package;
|
||||||
var optional = name.StartsWith('~');
|
var optional = name.StartsWith('~');
|
||||||
@@ -110,7 +110,7 @@ namespace OpenRA
|
|||||||
if (resolved.StartsWith(Platform.SupportDir, StringComparison.Ordinal) && !File.Exists(resolved))
|
if (resolved.StartsWith(Platform.SupportDir, StringComparison.Ordinal) && !File.Exists(resolved))
|
||||||
Directory.CreateDirectory(resolved);
|
Directory.CreateDirectory(resolved);
|
||||||
|
|
||||||
package = modFiles.OpenPackage(name);
|
package = modData.ModFiles.OpenPackage(name);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -121,38 +121,42 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
mapLocations.Add(package, classification);
|
mapLocations.Add(package, classification);
|
||||||
mapDirectoryTrackers.Add(new MapDirectoryTracker(package, classification));
|
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.
|
// PERF: Load the mod YAML once outside the loop, and reuse it when resolving each maps custom YAML.
|
||||||
var modDataRules = modData.GetRulesYaml();
|
var modDataRules = modData.GetRulesYaml();
|
||||||
foreach (var kv in MapLocations)
|
foreach (var kv in MapLocations)
|
||||||
|
{
|
||||||
foreach (var map in kv.Key.Contents)
|
foreach (var map in kv.Key.Contents)
|
||||||
LoadMapInternal(map, kv.Key, kv.Value, null, gridType, modDataRules);
|
LoadMapInternal(map, kv.Key, kv.Value, mapGrid, null, modDataRules);
|
||||||
|
}
|
||||||
|
|
||||||
// We only want to track maps in runtime, not at loadtime
|
// We only want to track maps in runtime, not at loadtime
|
||||||
LastModifiedMap = null;
|
LastModifiedMap = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadMap(string map, IReadOnlyPackage package, MapClassification classification, string oldMap)
|
public void LoadMap(string map, IReadOnlyPackage package, MapClassification classification, MapGrid mapGrid, string oldMap)
|
||||||
{
|
{
|
||||||
LoadMapInternal(map, package, classification, oldMap);
|
LoadMapInternal(map, package, classification, mapGrid, oldMap, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoadMapInternal(string map, IReadOnlyPackage package, MapClassification classification, string oldMap,
|
void LoadMapInternal(string map, IReadOnlyPackage package, MapClassification classification, MapGrid mapGrid, string oldMap,
|
||||||
MapGridType? gridType = null, MiniYamlNode[][] modDataRules = null)
|
IEnumerable<List<MiniYamlNode>> modDataRules)
|
||||||
{
|
{
|
||||||
IReadOnlyPackage mapPackage = null;
|
IReadOnlyPackage mapPackage = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (new PerfTimer(map))
|
using (new PerfTimer(map))
|
||||||
{
|
{
|
||||||
mapPackage = package.OpenPackage(map, modFiles);
|
mapPackage = package.OpenPackage(map, modData.ModFiles);
|
||||||
if (mapPackage != null)
|
if (mapPackage != null)
|
||||||
{
|
{
|
||||||
var uid = Map.ComputeUID(mapPackage);
|
var uid = Map.ComputeUID(mapPackage);
|
||||||
previews[uid].UpdateFromMapWithoutOwningPackage(mapPackage, package, classification, gridType, modDataRules);
|
previews[uid].UpdateFromMap(mapPackage, package, classification, modData.Manifest.MapCompatibility, mapGrid.Type, modDataRules);
|
||||||
mapPackage.Dispose();
|
|
||||||
|
// Freeing the package to save memory if there is a lot of Maps
|
||||||
|
previews[uid].DisposePackage();
|
||||||
|
|
||||||
if (oldMap != uid)
|
if (oldMap != uid)
|
||||||
{
|
{
|
||||||
@@ -177,8 +181,12 @@ namespace OpenRA
|
|||||||
|
|
||||||
public IEnumerable<IReadWritePackage> EnumerateMapDirPackages(MapClassification classification = MapClassification.System)
|
public IEnumerable<IReadWritePackage> EnumerateMapDirPackages(MapClassification classification = MapClassification.System)
|
||||||
{
|
{
|
||||||
|
// Utility mod that does not support maps
|
||||||
|
if (!modData.Manifest.Contains<MapGrid>())
|
||||||
|
yield break;
|
||||||
|
|
||||||
// Enumerate map directories
|
// Enumerate map directories
|
||||||
foreach (var kv in manifest.MapFolders)
|
foreach (var kv in modData.Manifest.MapFolders)
|
||||||
{
|
{
|
||||||
if (!Enum.TryParse(kv.Value, out MapClassification packageClassification))
|
if (!Enum.TryParse(kv.Value, out MapClassification packageClassification))
|
||||||
continue;
|
continue;
|
||||||
@@ -196,7 +204,7 @@ namespace OpenRA
|
|||||||
if (resolved.StartsWith(Platform.SupportDir, StringComparison.Ordinal) && (!Directory.Exists(resolved) || !File.Exists(resolved)))
|
if (resolved.StartsWith(Platform.SupportDir, StringComparison.Ordinal) && (!Directory.Exists(resolved) || !File.Exists(resolved)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
using (var package = (IReadWritePackage)modFiles.OpenPackage(name))
|
using (var package = (IReadWritePackage)modData.ModFiles.OpenPackage(name))
|
||||||
yield return package;
|
yield return package;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -216,7 +224,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
foreach (var mapDirPackage in mapDirPackages)
|
foreach (var mapDirPackage in mapDirPackages)
|
||||||
foreach (var map in mapDirPackage.Contents)
|
foreach (var map in mapDirPackage.Contents)
|
||||||
if (mapDirPackage.OpenPackage(map, modFiles) is IReadWritePackage mapPackage)
|
if (mapDirPackage.OpenPackage(map, modData.ModFiles) is IReadWritePackage mapPackage)
|
||||||
yield return mapPackage;
|
yield return mapPackage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,37 +239,67 @@ namespace OpenRA
|
|||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
foreach (var uid in queryUids)
|
foreach (var uid in queryUids)
|
||||||
previews[uid].BeginRemoteSearch();
|
previews[uid].UpdateRemoteSearch(MapStatus.Searching, null, null);
|
||||||
|
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
var client = HttpClientFactory.Create();
|
var client = HttpClientFactory.Create();
|
||||||
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
||||||
|
var buffer = new StringBuilder();
|
||||||
|
|
||||||
// Limit each query to 50 maps at a time to avoid request size limits
|
// Limit each query to 50 maps at a time to avoid request size limits
|
||||||
foreach (var batchUids in queryUids.Chunk(50))
|
for (var i = 0; i < queryUids.Count; i += 50)
|
||||||
{
|
{
|
||||||
|
var batchUids = queryUids.Skip(i).Take(50).ToList();
|
||||||
var url = repositoryUrl + "hash/" + string.Join(",", batchUids) + "/yaml";
|
var url = repositoryUrl + "hash/" + string.Join(",", batchUids) + "/yaml";
|
||||||
|
|
||||||
using (new PerfTimer("RemoteMapDetails"))
|
using (new PerfTimer("RemoteMapDetails"))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = await client.GetStreamAsync(url);
|
await using (var resultStream = await client.GetStreamAsync(url))
|
||||||
foreach (var kv in MiniYaml.FromStream(result, url, stringPool: stringPool))
|
{
|
||||||
previews[kv.Key].CompleteRemoteSearch(kv.Value, mapDetailsReceived);
|
using (var resultReader = new StreamReader(resultStream))
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var line = await resultReader.ReadLineAsync();
|
||||||
|
if (line == null || !line.StartsWith('\t'))
|
||||||
|
{
|
||||||
|
var yaml = MiniYaml.FromString(buffer.ToString(), url, stringPool: stringPool);
|
||||||
|
buffer.Clear();
|
||||||
|
foreach (var kv in yaml)
|
||||||
|
previews[kv.Key].UpdateRemoteSearch(MapStatus.DownloadAvailable, kv.Value, modData.Manifest.MapCompatibility, mapDetailsReceived);
|
||||||
|
|
||||||
|
if (line == null)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.Append(line);
|
||||||
|
buffer.Append('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var uid in batchUids)
|
||||||
|
{
|
||||||
|
var p = previews[uid];
|
||||||
|
if (p.Status != MapStatus.DownloadAvailable)
|
||||||
|
p.UpdateRemoteSearch(MapStatus.Unavailable, null, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Log.Write("debug", "Remote map query failed with error:");
|
Log.Write("debug", "Remote map query failed with error:");
|
||||||
Log.Write("debug", e);
|
Log.Write("debug", e);
|
||||||
Log.Write("debug", $"URL was: {url}");
|
Log.Write("debug", $"URL was: {url}");
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var uid in batchUids)
|
foreach (var uid in batchUids)
|
||||||
{
|
{
|
||||||
var p = previews[uid];
|
var p = previews[uid];
|
||||||
if (p.Status == MapStatus.Searching)
|
p.UpdateRemoteSearch(MapStatus.Unavailable, null, null);
|
||||||
p.CompleteRemoteSearch(null, mapQueryFailed);
|
mapQueryFailed?.Invoke(p);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -328,9 +366,7 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Release the buffer by forcing changes to be written out to the texture, allowing the buffer to be reclaimed by GC.
|
// Release the buffer by forcing changes to be written out to the texture, allowing the buffer to be reclaimed by GC.
|
||||||
if (sheetBuilder.Current != null)
|
|
||||||
Game.RunAfterTick(sheetBuilder.Current.ReleaseBuffer);
|
Game.RunAfterTick(sheetBuilder.Current.ReleaseBuffer);
|
||||||
|
|
||||||
Log.Write("debug", "MapCache.LoadAsyncInternal ended");
|
Log.Write("debug", "MapCache.LoadAsyncInternal ended");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,24 +20,26 @@ namespace OpenRA
|
|||||||
public sealed class MapDirectoryTracker : IDisposable
|
public sealed class MapDirectoryTracker : IDisposable
|
||||||
{
|
{
|
||||||
readonly FileSystemWatcher watcher;
|
readonly FileSystemWatcher watcher;
|
||||||
|
readonly MapGrid mapGrid;
|
||||||
readonly IReadOnlyPackage package;
|
readonly IReadOnlyPackage package;
|
||||||
readonly MapClassification classification;
|
readonly MapClassification classification;
|
||||||
|
|
||||||
enum MapAction { Add, Delete, Update }
|
enum MapAction { Add, Delete, Update }
|
||||||
readonly Dictionary<string, MapAction> mapActionQueue = [];
|
readonly Dictionary<string, MapAction> mapActionQueue = new();
|
||||||
|
|
||||||
bool dirty = false;
|
bool dirty = false;
|
||||||
|
|
||||||
public MapDirectoryTracker(IReadOnlyPackage package, MapClassification classification)
|
public MapDirectoryTracker(MapGrid mapGrid, IReadOnlyPackage package, MapClassification classification)
|
||||||
{
|
{
|
||||||
|
this.mapGrid = mapGrid;
|
||||||
this.package = package;
|
this.package = package;
|
||||||
this.classification = classification;
|
this.classification = classification;
|
||||||
|
|
||||||
watcher = new FileSystemWatcher(package.Name);
|
watcher = new FileSystemWatcher(package.Name);
|
||||||
watcher.Changed += (_, e) => AddMapAction(MapAction.Update, e.FullPath);
|
watcher.Changed += (object sender, FileSystemEventArgs e) => AddMapAction(MapAction.Update, e.FullPath);
|
||||||
watcher.Created += (_, e) => AddMapAction(MapAction.Add, e.FullPath);
|
watcher.Created += (object sender, FileSystemEventArgs e) => AddMapAction(MapAction.Add, e.FullPath);
|
||||||
watcher.Deleted += (_, e) => AddMapAction(MapAction.Delete, e.FullPath);
|
watcher.Deleted += (object sender, FileSystemEventArgs e) => AddMapAction(MapAction.Delete, e.FullPath);
|
||||||
watcher.Renamed += (_, e) => AddMapAction(MapAction.Add, e.FullPath, e.OldFullPath);
|
watcher.Renamed += (object sender, RenamedEventArgs e) => AddMapAction(MapAction.Add, e.FullPath, e.OldFullPath);
|
||||||
|
|
||||||
watcher.IncludeSubdirectories = true;
|
watcher.IncludeSubdirectories = true;
|
||||||
watcher.EnableRaisingEvents = true;
|
watcher.EnableRaisingEvents = true;
|
||||||
@@ -84,7 +86,7 @@ namespace OpenRA
|
|||||||
dirty = false;
|
dirty = false;
|
||||||
foreach (var mapAction in mapActionQueue)
|
foreach (var mapAction in mapActionQueue)
|
||||||
{
|
{
|
||||||
var map = mapcache.FirstOrDefault(x => x.Path == mapAction.Key && x.Status == MapStatus.Available);
|
var map = mapcache.FirstOrDefault(x => x.PackageName == mapAction.Key && x.Status == MapStatus.Available);
|
||||||
if (map != null)
|
if (map != null)
|
||||||
{
|
{
|
||||||
if (mapAction.Value == MapAction.Delete)
|
if (mapAction.Value == MapAction.Delete)
|
||||||
@@ -96,7 +98,7 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
Console.WriteLine(mapAction.Key + " was updated");
|
Console.WriteLine(mapAction.Key + " was updated");
|
||||||
map.Invalidate();
|
map.Invalidate();
|
||||||
mapcache.LoadMap(mapAction.Key.Replace(package.Name + Path.DirectorySeparatorChar, ""), package, classification, map.Uid);
|
mapcache.LoadMap(mapAction.Key.Replace(package.Name + Path.DirectorySeparatorChar, ""), package, classification, mapGrid, map.Uid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -104,7 +106,7 @@ namespace OpenRA
|
|||||||
if (mapAction.Value != MapAction.Delete)
|
if (mapAction.Value != MapAction.Delete)
|
||||||
{
|
{
|
||||||
Console.WriteLine(mapAction.Key + " was added");
|
Console.WriteLine(mapAction.Key + " was added");
|
||||||
mapcache.LoadMap(mapAction.Key.Replace(package?.Name + Path.DirectorySeparatorChar, ""), package, classification, null);
|
mapcache.LoadMap(mapAction.Key.Replace(package?.Name + Path.DirectorySeparatorChar, ""), package, classification, mapGrid, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,58 +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.Generic;
|
|
||||||
using OpenRA.Primitives;
|
|
||||||
|
|
||||||
namespace OpenRA
|
|
||||||
{
|
|
||||||
public class MapGenerationArgs
|
|
||||||
{
|
|
||||||
[FieldLoader.Require]
|
|
||||||
public string Uid = null;
|
|
||||||
|
|
||||||
[FieldLoader.Require]
|
|
||||||
public string Generator = null;
|
|
||||||
|
|
||||||
[FieldLoader.Require]
|
|
||||||
public string Tileset = null;
|
|
||||||
|
|
||||||
[FieldLoader.Require]
|
|
||||||
public Size Size = default;
|
|
||||||
|
|
||||||
[FieldLoader.Require]
|
|
||||||
public string Title = null;
|
|
||||||
|
|
||||||
[FieldLoader.Require]
|
|
||||||
public string Author = null;
|
|
||||||
|
|
||||||
[FieldLoader.LoadUsing(nameof(LoadSettings))]
|
|
||||||
public MiniYaml Settings = null;
|
|
||||||
static MiniYaml LoadSettings(MiniYaml yaml)
|
|
||||||
{
|
|
||||||
return yaml.NodeWithKey("Settings").Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<MiniYamlNode> Serialize()
|
|
||||||
{
|
|
||||||
return
|
|
||||||
[
|
|
||||||
new("Uid", Uid),
|
|
||||||
new("Generator", Generator),
|
|
||||||
new("Tileset", Tileset),
|
|
||||||
new("Size", FieldSaver.FormatValue(Size)),
|
|
||||||
new("Settings", Settings),
|
|
||||||
new("Title", Title),
|
|
||||||
new("Author", Author)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,9 +10,9 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using OpenRA.Primitives;
|
||||||
using OpenRA.Traits;
|
using OpenRA.Traits;
|
||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
@@ -25,8 +25,8 @@ namespace OpenRA
|
|||||||
public readonly struct CellRamp
|
public readonly struct CellRamp
|
||||||
{
|
{
|
||||||
public readonly int CenterHeightOffset;
|
public readonly int CenterHeightOffset;
|
||||||
public readonly ImmutableArray<WVec> Corners;
|
public readonly WVec[] Corners;
|
||||||
public readonly ImmutableArray<ImmutableArray<WVec>> Polygons;
|
public readonly WVec[][] Polygons;
|
||||||
public readonly WRot Orientation;
|
public readonly WRot Orientation;
|
||||||
|
|
||||||
public CellRamp(MapGridType type, WRot orientation,
|
public CellRamp(MapGridType type, WRot orientation,
|
||||||
@@ -37,43 +37,43 @@ namespace OpenRA
|
|||||||
Orientation = orientation;
|
Orientation = orientation;
|
||||||
if (type == MapGridType.RectangularIsometric)
|
if (type == MapGridType.RectangularIsometric)
|
||||||
{
|
{
|
||||||
Corners =
|
Corners = new[]
|
||||||
[
|
{
|
||||||
new WVec(0, -724, 724 * (int)tl),
|
new WVec(0, -724, 724 * (int)tl),
|
||||||
new WVec(724, 0, 724 * (int)tr),
|
new WVec(724, 0, 724 * (int)tr),
|
||||||
new WVec(0, 724, 724 * (int)br),
|
new WVec(0, 724, 724 * (int)br),
|
||||||
new WVec(-724, 0, 724 * (int)bl),
|
new WVec(-724, 0, 724 * (int)bl),
|
||||||
];
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Corners =
|
Corners = new[]
|
||||||
[
|
{
|
||||||
new WVec(-512, -512, 512 * (int)tl),
|
new WVec(-512, -512, 512 * (int)tl),
|
||||||
new WVec(512, -512, 512 * (int)tr),
|
new WVec(512, -512, 512 * (int)tr),
|
||||||
new WVec(512, 512, 512 * (int)br),
|
new WVec(512, 512, 512 * (int)br),
|
||||||
new WVec(-512, 512, 512 * (int)bl)
|
new WVec(-512, 512, 512 * (int)bl)
|
||||||
];
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (split == RampSplit.X)
|
if (split == RampSplit.X)
|
||||||
{
|
{
|
||||||
Polygons =
|
Polygons = new[]
|
||||||
[
|
{
|
||||||
[Corners[0], Corners[1], Corners[3]],
|
new[] { Corners[0], Corners[1], Corners[3] },
|
||||||
[Corners[1], Corners[2], Corners[3]]
|
new[] { Corners[1], Corners[2], Corners[3] }
|
||||||
];
|
};
|
||||||
}
|
}
|
||||||
else if (split == RampSplit.Y)
|
else if (split == RampSplit.Y)
|
||||||
{
|
{
|
||||||
Polygons =
|
Polygons = new[]
|
||||||
[
|
{
|
||||||
[Corners[0], Corners[1], Corners[2]],
|
new[] { Corners[0], Corners[1], Corners[2] },
|
||||||
[Corners[0], Corners[2], Corners[3]]
|
new[] { Corners[0], Corners[2], Corners[3] }
|
||||||
];
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
Polygons = [Corners];
|
Polygons = new[] { Corners };
|
||||||
|
|
||||||
// Initial value must be assigned before HeightOffset can be called
|
// Initial value must be assigned before HeightOffset can be called
|
||||||
CenterHeightOffset = 0;
|
CenterHeightOffset = 0;
|
||||||
@@ -84,11 +84,10 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
// Enumerate over the polygons, assuming that they are triangles
|
// Enumerate over the polygons, assuming that they are triangles
|
||||||
// If the ramp is not split we will take the first three vertices of the corners as a valid triangle
|
// If the ramp is not split we will take the first three vertices of the corners as a valid triangle
|
||||||
int u;
|
WVec[] p = null;
|
||||||
int v;
|
var u = 0;
|
||||||
ImmutableArray<WVec> p;
|
var v = 0;
|
||||||
var i = 0;
|
for (var i = 0; i < Polygons.Length; i++)
|
||||||
do
|
|
||||||
{
|
{
|
||||||
p = Polygons[i];
|
p = Polygons[i];
|
||||||
u = ((p[1].Y - p[2].Y) * (dX - p[2].X) - (p[1].X - p[2].X) * (dY - p[2].Y)) / 1024;
|
u = ((p[1].Y - p[2].Y) * (dX - p[2].X) - (p[1].X - p[2].X) * (dY - p[2].Y)) / 1024;
|
||||||
@@ -97,10 +96,7 @@ namespace OpenRA
|
|||||||
// Point is within the triangle if 0 <= u,v <= 1024
|
// Point is within the triangle if 0 <= u,v <= 1024
|
||||||
if (u >= 0 && u <= 1024 && v >= 0 && v <= 1024)
|
if (u >= 0 && u <= 1024 && v >= 0 && v <= 1024)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
}
|
||||||
while (i < Polygons.Length);
|
|
||||||
|
|
||||||
// Calculate w from u,v and interpolate height
|
// Calculate w from u,v and interpolate height
|
||||||
return (u * p[0].Z + v * p[1].Z + (1024 - u - v) * p[2].Z) / 1024;
|
return (u * p[0].Z + v * p[1].Z + (1024 - u - v) * p[2].Z) / 1024;
|
||||||
@@ -110,6 +106,7 @@ namespace OpenRA
|
|||||||
public class MapGrid : IGlobalModData
|
public class MapGrid : IGlobalModData
|
||||||
{
|
{
|
||||||
public readonly MapGridType Type = MapGridType.Rectangular;
|
public readonly MapGridType Type = MapGridType.Rectangular;
|
||||||
|
public readonly Size TileSize = new(24, 24);
|
||||||
public readonly byte MaximumTerrainHeight = 0;
|
public readonly byte MaximumTerrainHeight = 0;
|
||||||
public readonly SubCell DefaultSubCell = (SubCell)byte.MaxValue;
|
public readonly SubCell DefaultSubCell = (SubCell)byte.MaxValue;
|
||||||
|
|
||||||
@@ -117,19 +114,19 @@ namespace OpenRA
|
|||||||
|
|
||||||
public readonly bool EnableDepthBuffer = false;
|
public readonly bool EnableDepthBuffer = false;
|
||||||
|
|
||||||
public readonly ImmutableArray<WVec> SubCellOffsets =
|
public readonly WVec[] SubCellOffsets =
|
||||||
[
|
{
|
||||||
new(0, 0, 0), // full cell - index 0
|
new(0, 0, 0), // full cell - index 0
|
||||||
new(-299, -256, 0), // top left - index 1
|
new(-299, -256, 0), // top left - index 1
|
||||||
new(256, -256, 0), // top right - index 2
|
new(256, -256, 0), // top right - index 2
|
||||||
new(0, 0, 0), // center - index 3
|
new(0, 0, 0), // center - index 3
|
||||||
new(-299, 256, 0), // bottom left - index 4
|
new(-299, 256, 0), // bottom left - index 4
|
||||||
new(256, 256, 0), // bottom right - index 5
|
new(256, 256, 0), // bottom right - index 5
|
||||||
];
|
};
|
||||||
|
|
||||||
public ImmutableArray<CellRamp> Ramps { get; }
|
public CellRamp[] Ramps { get; }
|
||||||
|
|
||||||
internal readonly ImmutableArray<ImmutableArray<CVec>> TilesByDistance;
|
internal readonly CVec[][] TilesByDistance;
|
||||||
|
|
||||||
public int TileScale { get; }
|
public int TileScale { get; }
|
||||||
|
|
||||||
@@ -162,9 +159,8 @@ namespace OpenRA
|
|||||||
var halfBackward = -halfForward;
|
var halfBackward = -halfForward;
|
||||||
|
|
||||||
// Slope types are hardcoded following the convention from the TS and RA2 map format
|
// Slope types are hardcoded following the convention from the TS and RA2 map format
|
||||||
Ramps =
|
Ramps = new[]
|
||||||
[
|
{
|
||||||
|
|
||||||
// Flat
|
// Flat
|
||||||
new CellRamp(Type, WRot.None),
|
new CellRamp(Type, WRot.None),
|
||||||
|
|
||||||
@@ -197,16 +193,16 @@ namespace OpenRA
|
|||||||
new CellRamp(Type, WRot.None, tl: RampCornerHeight.Half, br: RampCornerHeight.Half, split: RampSplit.Y),
|
new CellRamp(Type, WRot.None, tl: RampCornerHeight.Half, br: RampCornerHeight.Half, split: RampSplit.Y),
|
||||||
new CellRamp(Type, WRot.None, tr: RampCornerHeight.Half, bl: RampCornerHeight.Half, split: RampSplit.X),
|
new CellRamp(Type, WRot.None, tr: RampCornerHeight.Half, bl: RampCornerHeight.Half, split: RampSplit.X),
|
||||||
new CellRamp(Type, WRot.None, tl: RampCornerHeight.Half, br: RampCornerHeight.Half, split: RampSplit.X),
|
new CellRamp(Type, WRot.None, tl: RampCornerHeight.Half, br: RampCornerHeight.Half, split: RampSplit.X),
|
||||||
];
|
};
|
||||||
|
|
||||||
TilesByDistance = CreateTilesByDistance();
|
TilesByDistance = CreateTilesByDistance();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImmutableArray<ImmutableArray<CVec>> CreateTilesByDistance()
|
CVec[][] CreateTilesByDistance()
|
||||||
{
|
{
|
||||||
var ts = new List<CVec>[MaximumTileSearchRange + 1];
|
var ts = new List<CVec>[MaximumTileSearchRange + 1];
|
||||||
for (var i = 0; i < MaximumTileSearchRange + 1; i++)
|
for (var i = 0; i < MaximumTileSearchRange + 1; i++)
|
||||||
ts[i] = [];
|
ts[i] = new List<CVec>();
|
||||||
|
|
||||||
for (var j = -MaximumTileSearchRange; j <= MaximumTileSearchRange; j++)
|
for (var j = -MaximumTileSearchRange; j <= MaximumTileSearchRange; j++)
|
||||||
for (var i = -MaximumTileSearchRange; i <= MaximumTileSearchRange; i++)
|
for (var i = -MaximumTileSearchRange; i <= MaximumTileSearchRange; i++)
|
||||||
@@ -238,7 +234,7 @@ namespace OpenRA
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return ts.Select(list => list.ToImmutableArray()).ToImmutableArray();
|
return ts.Select(list => list.ToArray()).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public WVec OffsetOfSubCell(SubCell subCell)
|
public WVec OffsetOfSubCell(SubCell subCell)
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OpenRA.Traits;
|
using OpenRA.Traits;
|
||||||
|
|
||||||
@@ -25,7 +24,7 @@ namespace OpenRA
|
|||||||
public readonly Dictionary<string, PlayerReference> Players;
|
public readonly Dictionary<string, PlayerReference> Players;
|
||||||
|
|
||||||
public MapPlayers()
|
public MapPlayers()
|
||||||
: this([]) { }
|
: this(new List<MiniYamlNode>()) { }
|
||||||
|
|
||||||
public MapPlayers(IEnumerable<MiniYamlNode> playerDefinitions)
|
public MapPlayers(IEnumerable<MiniYamlNode> playerDefinitions)
|
||||||
{
|
{
|
||||||
@@ -55,7 +54,7 @@ namespace OpenRA
|
|||||||
Name = "Creeps",
|
Name = "Creeps",
|
||||||
Faction = firstFaction,
|
Faction = firstFaction,
|
||||||
NonCombatant = true,
|
NonCombatant = true,
|
||||||
Enemies = Exts.MakeArray(playerCount, i => $"Multi{i}").ToImmutableArray()
|
Enemies = Exts.MakeArray(playerCount, i => $"Multi{i}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -67,7 +66,7 @@ namespace OpenRA
|
|||||||
Name = $"Multi{index}",
|
Name = $"Multi{index}",
|
||||||
Faction = "Random",
|
Faction = "Random",
|
||||||
Playable = true,
|
Playable = true,
|
||||||
Enemies = ["Creeps"]
|
Enemies = new[] { "Creeps" }
|
||||||
};
|
};
|
||||||
Players.Add(p.Name, p);
|
Players.Add(p.Name, p);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -24,11 +23,10 @@ using OpenRA.FileSystem;
|
|||||||
using OpenRA.Graphics;
|
using OpenRA.Graphics;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
using OpenRA.Support;
|
using OpenRA.Support;
|
||||||
using OpenRA.Traits;
|
|
||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
{
|
{
|
||||||
public enum MapStatus { Available, Unavailable, Searching, DownloadAvailable, Downloading, DownloadError, Generatable, Generating }
|
public enum MapStatus { Available, Unavailable, Searching, DownloadAvailable, Downloading, DownloadError }
|
||||||
|
|
||||||
// Used for grouping maps in the UI
|
// Used for grouping maps in the UI
|
||||||
[Flags]
|
[Flags]
|
||||||
@@ -37,8 +35,7 @@ namespace OpenRA
|
|||||||
Unknown = 0,
|
Unknown = 0,
|
||||||
System = 1,
|
System = 1,
|
||||||
User = 2,
|
User = 2,
|
||||||
Remote = 4,
|
Remote = 4
|
||||||
Generated = 8
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage("StyleCop.CSharp.NamingRules",
|
[SuppressMessage("StyleCop.CSharp.NamingRules",
|
||||||
@@ -51,10 +48,10 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
public readonly string title;
|
public readonly string title;
|
||||||
public readonly string author;
|
public readonly string author;
|
||||||
public readonly ImmutableArray<string> categories;
|
public readonly string[] categories;
|
||||||
public readonly int players;
|
public readonly int players;
|
||||||
public readonly Rectangle bounds;
|
public readonly Rectangle bounds;
|
||||||
public readonly ImmutableArray<short> spawnpoints = [];
|
public readonly short[] spawnpoints = Array.Empty<short>();
|
||||||
public readonly MapGridType map_grid_type;
|
public readonly MapGridType map_grid_type;
|
||||||
public readonly string minimap;
|
public readonly string minimap;
|
||||||
public readonly bool downloading;
|
public readonly bool downloading;
|
||||||
@@ -72,12 +69,12 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
public int MapFormat;
|
public int MapFormat;
|
||||||
public string Title;
|
public string Title;
|
||||||
public ImmutableArray<string> Categories;
|
public string[] Categories;
|
||||||
public string Author;
|
public string Author;
|
||||||
public string TileSet;
|
public string TileSet;
|
||||||
public MapPlayers Players;
|
public MapPlayers Players;
|
||||||
public int PlayerCount;
|
public int PlayerCount;
|
||||||
public ImmutableArray<CPos> SpawnPoints;
|
public CPos[] SpawnPoints;
|
||||||
public MapGridType GridType;
|
public MapGridType GridType;
|
||||||
public Rectangle Bounds;
|
public Rectangle Bounds;
|
||||||
public Png Preview;
|
public Png Preview;
|
||||||
@@ -85,7 +82,6 @@ namespace OpenRA
|
|||||||
public MapClassification Class;
|
public MapClassification Class;
|
||||||
public MapVisibility Visibility;
|
public MapVisibility Visibility;
|
||||||
public DateTime ModifiedDate;
|
public DateTime ModifiedDate;
|
||||||
public MapGenerationArgs GenerationArgs;
|
|
||||||
|
|
||||||
public MiniYaml RuleDefinitions;
|
public MiniYaml RuleDefinitions;
|
||||||
public MiniYaml WeaponDefinitions;
|
public MiniYaml WeaponDefinitions;
|
||||||
@@ -117,7 +113,7 @@ namespace OpenRA
|
|||||||
return key == "world" || key == "player";
|
return key == "world" || key == "player";
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetCustomRules(ModData modData, IReadOnlyFileSystem fileSystem, Dictionary<string, MiniYaml> yaml, MiniYamlNode[][] modDataRules)
|
public void SetCustomRules(ModData modData, IReadOnlyFileSystem fileSystem, Dictionary<string, MiniYaml> yaml, IEnumerable<List<MiniYamlNode>> modDataRules)
|
||||||
{
|
{
|
||||||
RuleDefinitions = LoadRuleSection(yaml, "Rules");
|
RuleDefinitions = LoadRuleSection(yaml, "Rules");
|
||||||
WeaponDefinitions = LoadRuleSection(yaml, "Weapons");
|
WeaponDefinitions = LoadRuleSection(yaml, "Weapons");
|
||||||
@@ -132,9 +128,9 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
if (FluentMessageDefinitions != null)
|
if (FluentMessageDefinitions != null)
|
||||||
{
|
{
|
||||||
var files = ImmutableArray<string>.Empty;
|
var files = Array.Empty<string>();
|
||||||
if (FluentMessageDefinitions.Value != null)
|
if (FluentMessageDefinitions.Value != null)
|
||||||
files = FieldLoader.GetValue<ImmutableArray<string>>("value", FluentMessageDefinitions.Value);
|
files = FieldLoader.GetValue<string[]>("value", FluentMessageDefinitions.Value);
|
||||||
|
|
||||||
string text = null;
|
string text = null;
|
||||||
if (FluentMessageDefinitions.Nodes.Length > 0)
|
if (FluentMessageDefinitions.Nodes.Length > 0)
|
||||||
@@ -160,8 +156,8 @@ namespace OpenRA
|
|||||||
var files = Enumerable.Empty<string>();
|
var files = Enumerable.Empty<string>();
|
||||||
if (RuleDefinitions.Value != null)
|
if (RuleDefinitions.Value != null)
|
||||||
{
|
{
|
||||||
var mapFiles = FieldLoader.GetValue<ImmutableArray<string>>("value", RuleDefinitions.Value);
|
var mapFiles = FieldLoader.GetValue<string[]>("value", RuleDefinitions.Value);
|
||||||
files = files.Concat(mapFiles);
|
files = files.Append(mapFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
||||||
@@ -199,26 +195,22 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly object syncRoot = new();
|
static readonly CPos[] NoSpawns = Array.Empty<CPos>();
|
||||||
readonly MapCache cache;
|
readonly MapCache cache;
|
||||||
readonly ModData modData;
|
readonly ModData modData;
|
||||||
IReadOnlyPackage package;
|
|
||||||
|
|
||||||
public readonly string Uid;
|
public readonly string Uid;
|
||||||
|
public string PackageName { get; private set; }
|
||||||
public string Path { get; private set; }
|
IReadOnlyPackage package;
|
||||||
|
public IReadOnlyPackage Package
|
||||||
void LoadPackage()
|
|
||||||
{
|
{
|
||||||
if (package == null && parentPackage != null)
|
get
|
||||||
package = parentPackage.OpenPackage(Path, modData.ModFiles);
|
{
|
||||||
|
package ??= parentPackage.OpenPackage(PackageName, modData.ModFiles);
|
||||||
|
return package;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map ToMap()
|
private set => package = value;
|
||||||
{
|
|
||||||
LoadPackage();
|
|
||||||
using (new PerfTimer("Map"))
|
|
||||||
return new Map(modData, package);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IReadOnlyPackage parentPackage;
|
IReadOnlyPackage parentPackage;
|
||||||
@@ -227,12 +219,12 @@ namespace OpenRA
|
|||||||
|
|
||||||
public int MapFormat => innerData.MapFormat;
|
public int MapFormat => innerData.MapFormat;
|
||||||
public string Title => innerData.Title;
|
public string Title => innerData.Title;
|
||||||
public ImmutableArray<string> Categories => innerData.Categories;
|
public string[] Categories => innerData.Categories;
|
||||||
public string Author => innerData.Author;
|
public string Author => innerData.Author;
|
||||||
public string TileSet => innerData.TileSet;
|
public string TileSet => innerData.TileSet;
|
||||||
public MapPlayers Players => innerData.Players;
|
public MapPlayers Players => innerData.Players;
|
||||||
public int PlayerCount => innerData.PlayerCount;
|
public int PlayerCount => innerData.PlayerCount;
|
||||||
public ImmutableArray<CPos> SpawnPoints => innerData.SpawnPoints;
|
public CPos[] SpawnPoints => innerData.SpawnPoints;
|
||||||
public MapGridType GridType => innerData.GridType;
|
public MapGridType GridType => innerData.GridType;
|
||||||
public Rectangle Bounds => innerData.Bounds;
|
public Rectangle Bounds => innerData.Bounds;
|
||||||
public Png Preview => innerData.Preview;
|
public Png Preview => innerData.Preview;
|
||||||
@@ -248,8 +240,6 @@ namespace OpenRA
|
|||||||
public ActorInfo PlayerActorInfo => innerData.PlayerActorInfo;
|
public ActorInfo PlayerActorInfo => innerData.PlayerActorInfo;
|
||||||
public DateTime ModifiedDate => innerData.ModifiedDate;
|
public DateTime ModifiedDate => innerData.ModifiedDate;
|
||||||
|
|
||||||
public MapGenerationArgs GenerationArgs => innerData.GenerationArgs;
|
|
||||||
|
|
||||||
public long DownloadBytes { get; private set; }
|
public long DownloadBytes { get; private set; }
|
||||||
public int DownloadPercentage { get; private set; }
|
public int DownloadPercentage { get; private set; }
|
||||||
|
|
||||||
@@ -327,12 +317,12 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
MapFormat = 0,
|
MapFormat = 0,
|
||||||
Title = "Unknown Map",
|
Title = "Unknown Map",
|
||||||
Categories = ["Unknown"],
|
Categories = new[] { "Unknown" },
|
||||||
Author = "Unknown Author",
|
Author = "Unknown Author",
|
||||||
TileSet = "unknown",
|
TileSet = "unknown",
|
||||||
Players = null,
|
Players = null,
|
||||||
PlayerCount = 0,
|
PlayerCount = 0,
|
||||||
SpawnPoints = [],
|
SpawnPoints = NoSpawns,
|
||||||
GridType = gridType,
|
GridType = gridType,
|
||||||
Bounds = Rectangle.Empty,
|
Bounds = Rectangle.Empty,
|
||||||
Preview = null,
|
Preview = null,
|
||||||
@@ -342,28 +332,57 @@ namespace OpenRA
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
// For linting purposes only!
|
||||||
/// Updates internal state from a map without taking ownership of its package.
|
public MapPreview(Map map, ModData modData)
|
||||||
/// A new copy of the map package will be opened lazily when needed.
|
|
||||||
/// </summary>
|
|
||||||
public void UpdateFromMapWithoutOwningPackage(IReadOnlyPackage p, IReadOnlyPackage parent, MapClassification classification,
|
|
||||||
MapGridType? gridType = null, MiniYamlNode[][] modDataRules = null)
|
|
||||||
{
|
{
|
||||||
UpdateFromMap(p, classification, gridType, modDataRules);
|
this.modData = modData;
|
||||||
parentPackage = parent;
|
cache = modData.MapCache;
|
||||||
package = null;
|
|
||||||
|
Uid = map.Uid;
|
||||||
|
PackageName = map.Package.Name;
|
||||||
|
|
||||||
|
var mapPlayers = new MapPlayers(map.PlayerDefinitions);
|
||||||
|
var spawns = new List<CPos>();
|
||||||
|
foreach (var kv in map.ActorDefinitions.Where(d => d.Value.Value == "mpspawn"))
|
||||||
|
{
|
||||||
|
var s = new ActorReference(kv.Value.Value, kv.Value.ToDictionary());
|
||||||
|
spawns.Add(s.Get<LocationInit>().Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
innerData = new InnerData
|
||||||
/// Updates internal state from a map and takes ownership of its package.
|
|
||||||
/// The package remains in memory and must not be disposed.
|
|
||||||
/// </summary>
|
|
||||||
public void UpdateFromMap(IReadOnlyPackage p, MapClassification classification,
|
|
||||||
MapGridType? gridType = null, MiniYamlNode[][] modDataRules = null)
|
|
||||||
{
|
{
|
||||||
Path = p.Name;
|
MapFormat = map.MapFormat,
|
||||||
package = p;
|
Title = map.Title,
|
||||||
|
Categories = map.Categories,
|
||||||
|
Author = map.Author,
|
||||||
|
TileSet = map.Tileset,
|
||||||
|
Players = mapPlayers,
|
||||||
|
PlayerCount = mapPlayers.Players.Count(x => x.Value.Playable),
|
||||||
|
SpawnPoints = spawns.ToArray(),
|
||||||
|
GridType = map.Grid.Type,
|
||||||
|
Bounds = map.Bounds,
|
||||||
|
Preview = null,
|
||||||
|
Status = MapStatus.Available,
|
||||||
|
Class = MapClassification.Unknown,
|
||||||
|
Visibility = map.Visibility,
|
||||||
|
};
|
||||||
|
|
||||||
|
innerData.SetCustomRules(modData, this, new Dictionary<string, MiniYaml>()
|
||||||
|
{
|
||||||
|
{ "Rules", map.RuleDefinitions },
|
||||||
|
{ "FluentMessages", map.FluentMessageDefinitions },
|
||||||
|
{ "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)
|
||||||
|
{
|
||||||
Dictionary<string, MiniYaml> yaml;
|
Dictionary<string, MiniYaml> yaml;
|
||||||
using (var yamlStream = p.GetStream("map.yaml"))
|
using (var yamlStream = p.GetStream("map.yaml"))
|
||||||
{
|
{
|
||||||
@@ -373,9 +392,12 @@ namespace OpenRA
|
|||||||
yaml = new MiniYaml(null, MiniYaml.FromStream(yamlStream, $"{p.Name}:map.yaml", stringPool: cache.StringPool)).ToDictionary();
|
yaml = new MiniYaml(null, MiniYaml.FromStream(yamlStream, $"{p.Name}:map.yaml", stringPool: cache.StringPool)).ToDictionary();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PackageName = p.Name;
|
||||||
|
parentPackage = parent;
|
||||||
|
|
||||||
var newData = innerData.Clone();
|
var newData = innerData.Clone();
|
||||||
|
newData.GridType = gridType;
|
||||||
newData.Class = classification;
|
newData.Class = classification;
|
||||||
newData.GridType = gridType ?? modData.GetOrCreate<MapGrid>().Type;
|
|
||||||
|
|
||||||
if (yaml.TryGetValue("MapFormat", out var temp))
|
if (yaml.TryGetValue("MapFormat", out var temp))
|
||||||
{
|
{
|
||||||
@@ -388,7 +410,7 @@ namespace OpenRA
|
|||||||
newData.Title = temp.Value;
|
newData.Title = temp.Value;
|
||||||
|
|
||||||
if (yaml.TryGetValue("Categories", out temp))
|
if (yaml.TryGetValue("Categories", out temp))
|
||||||
newData.Categories = FieldLoader.GetValue<ImmutableArray<string>>("Categories", temp.Value);
|
newData.Categories = FieldLoader.GetValue<string[]>("Categories", temp.Value);
|
||||||
|
|
||||||
if (yaml.TryGetValue("Tileset", out temp))
|
if (yaml.TryGetValue("Tileset", out temp))
|
||||||
newData.TileSet = temp.Value;
|
newData.TileSet = temp.Value;
|
||||||
@@ -409,7 +431,7 @@ namespace OpenRA
|
|||||||
if (yaml.TryGetValue("MapFormat", out temp))
|
if (yaml.TryGetValue("MapFormat", out temp))
|
||||||
newData.MapFormat = FieldLoader.GetValue<int>("MapFormat", temp.Value);
|
newData.MapFormat = FieldLoader.GetValue<int>("MapFormat", temp.Value);
|
||||||
|
|
||||||
newData.Status = modData.Manifest.MapCompatibility.Contains(requiresMod) ?
|
newData.Status = mapCompatibility == null || mapCompatibility.Contains(requiresMod) ?
|
||||||
MapStatus.Available : MapStatus.Unavailable;
|
MapStatus.Available : MapStatus.Unavailable;
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -420,18 +442,18 @@ namespace OpenRA
|
|||||||
var spawns = new List<CPos>();
|
var spawns = new List<CPos>();
|
||||||
foreach (var kv in actorDefinitions.Nodes.Where(d => d.Value.Value == "mpspawn"))
|
foreach (var kv in actorDefinitions.Nodes.Where(d => d.Value.Value == "mpspawn"))
|
||||||
{
|
{
|
||||||
var s = new ActorReference(kv.Value.Value, kv.Value);
|
var s = new ActorReference(kv.Value.Value, kv.Value.ToDictionary());
|
||||||
spawns.Add(s.Get<LocationInit>().Value);
|
spawns.Add(s.Get<LocationInit>().Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
newData.SpawnPoints = spawns.ToImmutableArray();
|
newData.SpawnPoints = spawns.ToArray();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
newData.SpawnPoints = [];
|
newData.SpawnPoints = Array.Empty<CPos>();
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
newData.SpawnPoints = [];
|
newData.SpawnPoints = Array.Empty<CPos>();
|
||||||
newData.Status = MapStatus.Unavailable;
|
newData.Status = MapStatus.Unavailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,122 +477,31 @@ namespace OpenRA
|
|||||||
using (var dataStream = p.GetStream("map.png"))
|
using (var dataStream = p.GetStream("map.png"))
|
||||||
newData.Preview = new Png(dataStream);
|
newData.Preview = new Png(dataStream);
|
||||||
|
|
||||||
newData.ModifiedDate = p.Name != null ? File.GetLastWriteTime(p.Name) : DateTime.Now;
|
newData.ModifiedDate = File.GetLastWriteTime(p.Name);
|
||||||
|
|
||||||
// Assign the new data atomically
|
// Assign the new data atomically
|
||||||
// Local maps have higher precedence than remote/generated maps,
|
|
||||||
// so should always replace their metadata
|
|
||||||
lock (syncRoot)
|
|
||||||
innerData = newData;
|
innerData = newData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateFromGenerationArgs(MapGenerationArgs args)
|
public void UpdateRemoteSearch(MapStatus status, MiniYaml yaml, string[] mapCompatibility, Action<MapPreview> parseMetadata = null)
|
||||||
{
|
|
||||||
var newData = innerData.Clone();
|
|
||||||
newData.Class = MapClassification.Generated;
|
|
||||||
newData.Status = MapStatus.Generatable;
|
|
||||||
newData.Title = args.Title;
|
|
||||||
newData.Author = args.Author;
|
|
||||||
newData.TileSet = args.Tileset;
|
|
||||||
newData.GenerationArgs = args;
|
|
||||||
newData.MapFormat = Map.CurrentMapFormat;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var generator = modData.DefaultRules.Actors[SystemActors.EditorWorld]
|
|
||||||
.TraitInfos<IMapGeneratorInfo>()
|
|
||||||
.FirstOrDefault(info => info.Type == args.Generator);
|
|
||||||
|
|
||||||
if (generator == null)
|
|
||||||
throw new Exception($"Unknown map generator type {args.Generator}");
|
|
||||||
|
|
||||||
if (!generator.TryGenerateMetadata(modData, args, out var players, out var ruleDefinitions))
|
|
||||||
throw new Exception("Failed to generate map metadata");
|
|
||||||
|
|
||||||
newData.Players = players;
|
|
||||||
newData.PlayerCount = newData.Players.Players.Count(x => x.Value.Playable);
|
|
||||||
newData.SetCustomRules(modData, this, ruleDefinitions, null);
|
|
||||||
|
|
||||||
// Placeholder to satisfy server-side lint checks
|
|
||||||
newData.SpawnPoints = Exts.MakeArray(newData.PlayerCount, i => new CPos(i, i)).ToImmutableArray();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Write("debug", "Map generation failed with error:");
|
|
||||||
Log.Write("debug", e);
|
|
||||||
|
|
||||||
newData.Status = MapStatus.Unavailable;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (syncRoot)
|
|
||||||
innerData = newData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Generate()
|
|
||||||
{
|
|
||||||
if (Class != MapClassification.Generated || Status != MapStatus.Generatable)
|
|
||||||
return;
|
|
||||||
|
|
||||||
lock (syncRoot)
|
|
||||||
innerData.Status = MapStatus.Generating;
|
|
||||||
|
|
||||||
Task.Run(() =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var generator = modData.DefaultRules.Actors[SystemActors.EditorWorld]
|
|
||||||
.TraitInfos<IMapGeneratorInfo>()
|
|
||||||
.FirstOrDefault(info => info.Type == GenerationArgs.Generator);
|
|
||||||
|
|
||||||
if (generator == null)
|
|
||||||
throw new Exception($"Unknown map generator type {GenerationArgs.Generator}");
|
|
||||||
|
|
||||||
var map = generator.Generate(modData, GenerationArgs);
|
|
||||||
|
|
||||||
// Uid is generated when the map is saved
|
|
||||||
map.Save(new ZipFileLoader.ReadWriteZipFile());
|
|
||||||
|
|
||||||
if (map.Uid != GenerationArgs.Uid)
|
|
||||||
throw new InvalidOperationException("Map generation UID mismatch");
|
|
||||||
|
|
||||||
Game.RunAfterTick(() => UpdateFromMap(map.Package, MapClassification.Generated));
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Write("debug", "Map generation failed with error:");
|
|
||||||
Log.Write("debug", e);
|
|
||||||
|
|
||||||
lock (syncRoot)
|
|
||||||
innerData.Status = MapStatus.Unavailable;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void BeginRemoteSearch()
|
|
||||||
{
|
{
|
||||||
var newData = innerData.Clone();
|
var newData = innerData.Clone();
|
||||||
|
newData.Status = status;
|
||||||
newData.Class = MapClassification.Remote;
|
newData.Class = MapClassification.Remote;
|
||||||
newData.Status = MapStatus.Searching;
|
|
||||||
|
|
||||||
// We may have been resolved to a local/generated map by another
|
if (status == MapStatus.DownloadAvailable)
|
||||||
// async task. Make sure we don't stomp over their state!
|
|
||||||
lock (syncRoot)
|
|
||||||
if (innerData.Class == MapClassification.Unknown || innerData.Class == MapClassification.Remote)
|
|
||||||
innerData = newData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CompleteRemoteSearch(MiniYaml yaml, Action<MapPreview> parseMetadata = null)
|
|
||||||
{
|
|
||||||
var newData = innerData.Clone();
|
|
||||||
newData.Class = MapClassification.Remote;
|
|
||||||
newData.Status = MapStatus.Unavailable;
|
|
||||||
|
|
||||||
if (yaml != null)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var r = FieldLoader.Load<RemoteMapData>(yaml);
|
var r = FieldLoader.Load<RemoteMapData>(yaml);
|
||||||
newData.Status = r.downloading ? MapStatus.DownloadAvailable : MapStatus.Unavailable;
|
|
||||||
|
// Map download has been disabled server side
|
||||||
|
if (!r.downloading)
|
||||||
|
{
|
||||||
|
newData.Status = MapStatus.Unavailable;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
newData.Title = r.title;
|
newData.Title = r.title;
|
||||||
newData.Categories = r.categories;
|
newData.Categories = r.categories;
|
||||||
newData.Author = r.author;
|
newData.Author = r.author;
|
||||||
@@ -582,7 +513,7 @@ namespace OpenRA
|
|||||||
var spawns = new CPos[r.spawnpoints.Length / 2];
|
var spawns = new CPos[r.spawnpoints.Length / 2];
|
||||||
for (var j = 0; j < r.spawnpoints.Length; j += 2)
|
for (var j = 0; j < r.spawnpoints.Length; j += 2)
|
||||||
spawns[j / 2] = new CPos(r.spawnpoints[j], r.spawnpoints[j + 1]);
|
spawns[j / 2] = new CPos(r.spawnpoints[j], r.spawnpoints[j + 1]);
|
||||||
newData.SpawnPoints = spawns.ToImmutableArray();
|
newData.SpawnPoints = spawns;
|
||||||
newData.GridType = r.map_grid_type;
|
newData.GridType = r.map_grid_type;
|
||||||
if (cache.LoadPreviewImages)
|
if (cache.LoadPreviewImages)
|
||||||
{
|
{
|
||||||
@@ -610,37 +541,29 @@ namespace OpenRA
|
|||||||
// Map is for a different mod: update its information so it can be displayed
|
// 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
|
// 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.
|
// be selected in a server for the current mod.
|
||||||
if (!modData.Manifest.MapCompatibility.Contains(r.game_mod))
|
if (!mapCompatibility.Contains(r.game_mod))
|
||||||
newData.Status = MapStatus.Unavailable;
|
newData.Status = MapStatus.Unavailable;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Log.Write("debug", "Failed parsing mapserver response:");
|
Log.Write("debug", "Failed parsing mapserver response:");
|
||||||
Log.Write("debug", e);
|
Log.Write("debug", e);
|
||||||
newData.Status = MapStatus.Unavailable;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We may have been resolved to a local/generated map by another
|
// Commit updated data before running the callbacks
|
||||||
// async task. Make sure we don't stomp over their state!
|
|
||||||
MapClassification mapClassification;
|
|
||||||
lock (syncRoot)
|
|
||||||
{
|
|
||||||
mapClassification = innerData.Class;
|
|
||||||
if (mapClassification == MapClassification.Remote)
|
|
||||||
innerData = newData;
|
innerData = newData;
|
||||||
}
|
|
||||||
|
|
||||||
if (mapClassification == MapClassification.Remote)
|
|
||||||
{
|
|
||||||
if (innerData.Preview != null)
|
if (innerData.Preview != null)
|
||||||
cache.CacheMinimap(this);
|
cache.CacheMinimap(this);
|
||||||
|
|
||||||
parseMetadata?.Invoke(this);
|
parseMetadata?.Invoke(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the status and class unconditionally
|
||||||
|
innerData = newData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Install(string mapRepositoryUrl)
|
public void Install(string mapRepositoryUrl, Action onSuccess)
|
||||||
{
|
{
|
||||||
if ((Status != MapStatus.DownloadError && Status != MapStatus.DownloadAvailable) || !Game.Settings.Game.AllowDownloading)
|
if ((Status != MapStatus.DownloadError && Status != MapStatus.DownloadAvailable) || !Game.Settings.Game.AllowDownloading)
|
||||||
return;
|
return;
|
||||||
@@ -693,11 +616,14 @@ namespace OpenRA
|
|||||||
mapInstallPackage.Update(mapFilename, fileStream.ToArray());
|
mapInstallPackage.Update(mapFilename, fileStream.ToArray());
|
||||||
Log.Write("debug", $"Downloaded map to '{mapFilename}'");
|
Log.Write("debug", $"Downloaded map to '{mapFilename}'");
|
||||||
|
|
||||||
var p = mapInstallPackage.OpenPackage(mapFilename, modData.ModFiles);
|
var package = mapInstallPackage.OpenPackage(mapFilename, modData.ModFiles);
|
||||||
if (p == null)
|
if (package == null)
|
||||||
innerData.Status = MapStatus.DownloadError;
|
innerData.Status = MapStatus.DownloadError;
|
||||||
else
|
else
|
||||||
UpdateFromMapWithoutOwningPackage(p, mapInstallPackage, MapClassification.User, GridType);
|
{
|
||||||
|
UpdateFromMap(package, mapInstallPackage, MapClassification.User, null, GridType, null);
|
||||||
|
Game.RunAfterTick(onSuccess);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -710,31 +636,34 @@ namespace OpenRA
|
|||||||
|
|
||||||
public void Invalidate()
|
public void Invalidate()
|
||||||
{
|
{
|
||||||
lock (syncRoot)
|
|
||||||
{
|
|
||||||
innerData.Class = MapClassification.Unknown;
|
|
||||||
innerData.Status = MapStatus.Unavailable;
|
innerData.Status = MapStatus.Unavailable;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
package?.Dispose();
|
DisposePackage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DisposePackage()
|
||||||
|
{
|
||||||
|
if (package != null)
|
||||||
|
{
|
||||||
|
package.Dispose();
|
||||||
package = null;
|
package = null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Delete()
|
public void Delete()
|
||||||
{
|
{
|
||||||
Invalidate();
|
Invalidate();
|
||||||
(parentPackage as IReadWritePackage)?.Delete(Path);
|
(parentPackage as IReadWritePackage)?.Delete(Package.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream IReadOnlyFileSystem.Open(string filename)
|
Stream IReadOnlyFileSystem.Open(string filename)
|
||||||
{
|
{
|
||||||
// Explicit package paths never refer to a map
|
// Explicit package paths never refer to a map
|
||||||
LoadPackage();
|
if (!filename.Contains('|') && Package.Contains(filename))
|
||||||
if (!filename.Contains('|') && package.Contains(filename))
|
return Package.GetStream(filename);
|
||||||
return package.GetStream(filename);
|
|
||||||
|
|
||||||
return modData.DefaultFileSystem.Open(filename);
|
return modData.DefaultFileSystem.Open(filename);
|
||||||
}
|
}
|
||||||
@@ -750,8 +679,7 @@ namespace OpenRA
|
|||||||
// Explicit package paths never refer to a map
|
// Explicit package paths never refer to a map
|
||||||
if (!filename.Contains('|'))
|
if (!filename.Contains('|'))
|
||||||
{
|
{
|
||||||
LoadPackage();
|
s = Package.GetStream(filename);
|
||||||
s = package.GetStream(filename);
|
|
||||||
if (s != null)
|
if (s != null)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -762,8 +690,7 @@ namespace OpenRA
|
|||||||
bool IReadOnlyFileSystem.Exists(string filename)
|
bool IReadOnlyFileSystem.Exists(string filename)
|
||||||
{
|
{
|
||||||
// Explicit package paths never refer to a map
|
// Explicit package paths never refer to a map
|
||||||
LoadPackage();
|
if (!filename.Contains('|') && Package.Contains(filename))
|
||||||
if (!filename.Contains('|') && package.Contains(filename))
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return modData.DefaultFileSystem.Exists(filename);
|
return modData.DefaultFileSystem.Exists(filename);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
*/
|
*/
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System.Collections.Immutable;
|
using System;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
@@ -31,7 +31,7 @@ namespace OpenRA
|
|||||||
public string Faction;
|
public string Faction;
|
||||||
|
|
||||||
public bool LockColor = false;
|
public bool LockColor = false;
|
||||||
public Color Color = Game.ModData.GetOrCreate<DefaultPlayer>().Color;
|
public Color Color = Game.ModData.Manifest.Get<DefaultPlayer>().Color;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the "Home" location, which can be used by traits and scripts to e.g. set the initial camera
|
/// Sets the "Home" location, which can be used by traits and scripts to e.g. set the initial camera
|
||||||
@@ -54,8 +54,8 @@ namespace OpenRA
|
|||||||
public bool LockHandicap = false;
|
public bool LockHandicap = false;
|
||||||
public int Handicap = 0;
|
public int Handicap = 0;
|
||||||
|
|
||||||
public ImmutableArray<string> Allies = [];
|
public string[] Allies = Array.Empty<string>();
|
||||||
public ImmutableArray<string> Enemies = [];
|
public string[] Enemies = Array.Empty<string>();
|
||||||
|
|
||||||
public PlayerReference() { }
|
public PlayerReference() { }
|
||||||
public PlayerReference(MiniYaml my) { FieldLoader.Load(this, my); }
|
public PlayerReference(MiniYaml my) { FieldLoader.Load(this, my); }
|
||||||
|
|||||||
@@ -55,9 +55,14 @@ namespace OpenRA
|
|||||||
return Bounds.Contains(uv.U, uv.V);
|
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)
|
public void SetAll(T value)
|
||||||
{
|
{
|
||||||
Entries.AsSpan().Fill(value);
|
Array.Fill(Entries, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using OpenRA.FileSystem;
|
using OpenRA.FileSystem;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
using OpenRA.Support;
|
using OpenRA.Support;
|
||||||
@@ -26,16 +25,14 @@ namespace OpenRA
|
|||||||
public interface ITerrainInfo
|
public interface ITerrainInfo
|
||||||
{
|
{
|
||||||
string Id { get; }
|
string Id { get; }
|
||||||
string Name { get; }
|
TerrainTypeInfo[] TerrainTypes { get; }
|
||||||
Size TileSize { get; }
|
|
||||||
ImmutableArray<TerrainTypeInfo> TerrainTypes { get; }
|
|
||||||
TerrainTileInfo GetTerrainInfo(TerrainTile r);
|
TerrainTileInfo GetTerrainInfo(TerrainTile r);
|
||||||
bool TryGetTerrainInfo(TerrainTile r, out TerrainTileInfo info);
|
bool TryGetTerrainInfo(TerrainTile r, out TerrainTileInfo info);
|
||||||
byte GetTerrainIndex(string type);
|
byte GetTerrainIndex(string type);
|
||||||
byte GetTerrainIndex(TerrainTile r);
|
byte GetTerrainIndex(TerrainTile r);
|
||||||
TerrainTile DefaultTerrainTile { get; }
|
TerrainTile DefaultTerrainTile { get; }
|
||||||
|
|
||||||
ImmutableArray<Color> HeightDebugColors { get; }
|
Color[] HeightDebugColors { get; }
|
||||||
IEnumerable<Color> RestrictedPlayerColors { get; }
|
IEnumerable<Color> RestrictedPlayerColors { get; }
|
||||||
float MinHeightColorBrightness { get; }
|
float MinHeightColorBrightness { get; }
|
||||||
float MaxHeightColorBrightness { get; }
|
float MaxHeightColorBrightness { get; }
|
||||||
@@ -63,7 +60,7 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
public readonly string Type;
|
public readonly string Type;
|
||||||
public readonly BitSet<TargetableType> TargetTypes;
|
public readonly BitSet<TargetableType> TargetTypes;
|
||||||
public readonly ImmutableArray<string> AcceptsSmudgeType = [];
|
public readonly HashSet<string> AcceptsSmudgeType = new();
|
||||||
public readonly Color Color;
|
public readonly Color Color;
|
||||||
public readonly bool RestrictPlayerColor = false;
|
public readonly bool RestrictPlayerColor = false;
|
||||||
|
|
||||||
|
|||||||
@@ -11,35 +11,32 @@
|
|||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
{
|
{
|
||||||
public readonly struct TerrainTile(ushort type, byte index)
|
public readonly struct TerrainTile
|
||||||
{
|
{
|
||||||
public readonly ushort Type = type;
|
public readonly ushort Type;
|
||||||
public readonly byte Index = index;
|
public readonly byte Index;
|
||||||
|
|
||||||
|
public TerrainTile(ushort type, byte index)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
Index = index;
|
||||||
|
}
|
||||||
|
|
||||||
public override int GetHashCode() { return Type.GetHashCode() ^ Index.GetHashCode(); }
|
public override int GetHashCode() { return Type.GetHashCode() ^ Index.GetHashCode(); }
|
||||||
|
|
||||||
public override string ToString() { return Type + "," + Index; }
|
public override string ToString() { return Type + "," + Index; }
|
||||||
|
|
||||||
public static bool TryParse(string s, out TerrainTile tt)
|
|
||||||
{
|
|
||||||
var split = s.Split(',');
|
|
||||||
if (split.Length == 2 &&
|
|
||||||
Exts.TryParseUshortInvariant(split[0], out var type) &&
|
|
||||||
Exts.TryParseByteInvariant(split[1], out var index))
|
|
||||||
{
|
|
||||||
tt = new TerrainTile(type, index);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tt = default;
|
public readonly struct ResourceTile
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly struct ResourceTile(byte type, byte index)
|
|
||||||
{
|
{
|
||||||
public readonly byte Type = type;
|
public readonly byte Type;
|
||||||
public readonly byte Index = index;
|
public readonly byte Index;
|
||||||
|
|
||||||
|
public ResourceTile(byte type, byte index)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
Index = index;
|
||||||
|
}
|
||||||
|
|
||||||
public override int GetHashCode() { return Type.GetHashCode() ^ Index.GetHashCode(); }
|
public override int GetHashCode() { return Type.GetHashCode() ^ Index.GetHashCode(); }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,10 +59,16 @@ namespace OpenRA
|
|||||||
|
|
||||||
public sealed class MiniYamlNode
|
public sealed class MiniYamlNode
|
||||||
{
|
{
|
||||||
public readonly struct SourceLocation(string name, int line)
|
public readonly struct SourceLocation
|
||||||
{
|
{
|
||||||
public readonly string Name = name;
|
public readonly string Name;
|
||||||
public readonly int Line = line;
|
public readonly int Line;
|
||||||
|
|
||||||
|
public SourceLocation(string name, int line)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Line = line;
|
||||||
|
}
|
||||||
|
|
||||||
public override string ToString() { return $"{Name}:{Line}"; }
|
public override string ToString() { return $"{Name}:{Line}"; }
|
||||||
}
|
}
|
||||||
@@ -93,7 +99,7 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
public MiniYamlNode(string k, string v, string c = null)
|
public MiniYamlNode(string k, string v, string c = null)
|
||||||
: this(k, new MiniYaml(v, []), c) { }
|
: this(k, new MiniYaml(v, Enumerable.Empty<MiniYamlNode>()), c) { }
|
||||||
|
|
||||||
public MiniYamlNode(string k, string v, IEnumerable<MiniYamlNode> n)
|
public MiniYamlNode(string k, string v, IEnumerable<MiniYamlNode> n)
|
||||||
: this(k, new MiniYaml(v, n), null) { }
|
: this(k, new MiniYaml(v, n), null) { }
|
||||||
@@ -109,7 +115,7 @@ namespace OpenRA
|
|||||||
const int SpacesPerLevel = 4;
|
const int SpacesPerLevel = 4;
|
||||||
static readonly Func<string, string> StringIdentity = s => s;
|
static readonly Func<string, string> StringIdentity = s => s;
|
||||||
static readonly Func<MiniYaml, MiniYaml> MiniYamlIdentity = my => my;
|
static readonly Func<MiniYaml, MiniYaml> MiniYamlIdentity = my => my;
|
||||||
static readonly Dictionary<string, MiniYamlNode> ConflictScratch = [];
|
static readonly Dictionary<string, MiniYamlNode> ConflictScratch = new();
|
||||||
|
|
||||||
public readonly string Value;
|
public readonly string Value;
|
||||||
public readonly ImmutableArray<MiniYamlNode> Nodes;
|
public readonly ImmutableArray<MiniYamlNode> Nodes;
|
||||||
@@ -190,22 +196,21 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
public MiniYaml(string value)
|
public MiniYaml(string value)
|
||||||
: this(value, []) { }
|
: this(value, Enumerable.Empty<MiniYamlNode>()) { }
|
||||||
|
|
||||||
public MiniYaml(string value, IEnumerable<MiniYamlNode> nodes)
|
public MiniYaml(string value, IEnumerable<MiniYamlNode> nodes)
|
||||||
{
|
{
|
||||||
Value = value;
|
Value = value;
|
||||||
Nodes = nodes.ToImmutableArray();
|
Nodes = ImmutableArray.CreateRange(nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
static IEnumerable<MiniYamlNode> FromLines(
|
static List<MiniYamlNode> FromLines(IEnumerable<ReadOnlyMemory<char>> lines, string name, bool discardCommentsAndWhitespace, HashSet<string> stringPool)
|
||||||
IEnumerable<ReadOnlyMemory<char>> lines, string name, bool discardCommentsAndWhitespace, HashSet<string> stringPool)
|
|
||||||
{
|
{
|
||||||
// YAML config often contains repeated strings for key, values, comments.
|
// YAML config often contains repeated strings for key, values, comments.
|
||||||
// Pool these strings so we only need one copy of each unique string.
|
// 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.
|
// 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.
|
// A caller can also provide a pool as input, allowing de-duplication across multiple parses.
|
||||||
stringPool ??= [];
|
stringPool ??= new HashSet<string>();
|
||||||
|
|
||||||
var result = new List<List<MiniYamlNode>>
|
var result = new List<List<MiniYamlNode>>
|
||||||
{
|
{
|
||||||
@@ -336,25 +341,18 @@ namespace OpenRA
|
|||||||
|
|
||||||
parsedLines.Add((level, keyString, valueString, commentString, location));
|
parsedLines.Add((level, keyString, valueString, commentString, location));
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var topLevelNode in result[0])
|
|
||||||
yield return topLevelNode;
|
|
||||||
result[0].Clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsedLines.Count > 0)
|
if (parsedLines.Count > 0)
|
||||||
{
|
|
||||||
BuildCompletedSubNode(0);
|
BuildCompletedSubNode(0);
|
||||||
foreach (var topLevelNode in result[0])
|
|
||||||
yield return topLevelNode;
|
return result[0];
|
||||||
result[0].Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void BuildCompletedSubNode(int level)
|
void BuildCompletedSubNode(int level)
|
||||||
{
|
{
|
||||||
var lastLevel = parsedLines[^1].Level;
|
var lastLevel = parsedLines[^1].Level;
|
||||||
while (lastLevel >= result.Count)
|
while (lastLevel >= result.Count)
|
||||||
result.Add([]);
|
result.Add(new List<MiniYamlNode>());
|
||||||
|
|
||||||
while (parsedLines.Count > 0 && parsedLines[^1].Level >= level)
|
while (parsedLines.Count > 0 && parsedLines[^1].Level >= level)
|
||||||
{
|
{
|
||||||
@@ -383,30 +381,29 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<MiniYamlNode> FromFile(string path, bool discardCommentsAndWhitespace = true, HashSet<string> stringPool = null)
|
public static List<MiniYamlNode> FromFile(string path, bool discardCommentsAndWhitespace = true, HashSet<string> stringPool = null)
|
||||||
{
|
{
|
||||||
return FromStream(File.OpenRead(path), path, discardCommentsAndWhitespace, stringPool);
|
return FromStream(File.OpenRead(path), path, discardCommentsAndWhitespace, stringPool);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<MiniYamlNode> FromStream(Stream s, string name, bool discardCommentsAndWhitespace = true, HashSet<string> stringPool = null)
|
public static List<MiniYamlNode> FromStream(Stream s, string name, bool discardCommentsAndWhitespace = true, HashSet<string> stringPool = null)
|
||||||
{
|
{
|
||||||
return FromLines(s.ReadAllLinesAsMemory(), name, discardCommentsAndWhitespace, stringPool);
|
return FromLines(s.ReadAllLinesAsMemory(), name, discardCommentsAndWhitespace, stringPool);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<MiniYamlNode> FromString(string text, string name, bool discardCommentsAndWhitespace = true, HashSet<string> stringPool = null)
|
public static List<MiniYamlNode> FromString(string text, string name, bool discardCommentsAndWhitespace = true, HashSet<string> stringPool = null)
|
||||||
{
|
{
|
||||||
return FromLines(text.Split(["\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()), name, discardCommentsAndWhitespace, stringPool);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<MiniYamlNode> Merge(IEnumerable<IEnumerable<MiniYamlNode>> sources)
|
public static List<MiniYamlNode> Merge(IEnumerable<IReadOnlyCollection<MiniYamlNode>> sources)
|
||||||
{
|
{
|
||||||
var sourcesList = sources.ToList();
|
var sourcesList = sources.ToList();
|
||||||
if (sourcesList.Count == 0)
|
if (sourcesList.Count == 0)
|
||||||
return [];
|
return new List<MiniYamlNode>();
|
||||||
|
|
||||||
var tree = sourcesList
|
var tree = sourcesList
|
||||||
.Where(s => s != null)
|
.Where(s => s != null)
|
||||||
.Select(s => s as IReadOnlyCollection<MiniYamlNode> ?? s.ToList())
|
|
||||||
.Select(MergeSelfPartial)
|
.Select(MergeSelfPartial)
|
||||||
.Aggregate(MergePartial)
|
.Aggregate(MergePartial)
|
||||||
.Where(n => n.Key != null)
|
.Where(n => n.Key != null)
|
||||||
@@ -506,9 +503,6 @@ namespace OpenRA
|
|||||||
var ret = new List<MiniYamlNode>(existingNodes.Count);
|
var ret = new List<MiniYamlNode>(existingNodes.Count);
|
||||||
foreach (var n in existingNodes)
|
foreach (var n in existingNodes)
|
||||||
{
|
{
|
||||||
if (n.Key == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (keys.Add(n.Key))
|
if (keys.Add(n.Key))
|
||||||
ret.Add(n);
|
ret.Add(n);
|
||||||
else
|
else
|
||||||
@@ -523,60 +517,17 @@ namespace OpenRA
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static IReadOnlyList<MiniYamlNode> WeakResolveRemovals(IReadOnlyList<MiniYamlNode> nodes)
|
|
||||||
{
|
|
||||||
if (nodes == null || nodes.Count == 0)
|
|
||||||
return nodes;
|
|
||||||
|
|
||||||
List<MiniYamlNode> ret = null;
|
|
||||||
for (var i = 0; i < nodes.Count; i++)
|
|
||||||
{
|
|
||||||
var node = nodes[i];
|
|
||||||
if (node.Key == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (node.Key.StartsWith('-'))
|
|
||||||
{
|
|
||||||
if (ret == null)
|
|
||||||
{
|
|
||||||
ret ??= new List<MiniYamlNode>(nodes.Count);
|
|
||||||
ret.AddRange(nodes.Take(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply the removal node - but "weakly" - don't throw if there is no prior node to remove.
|
|
||||||
var removed = node.Key[1..];
|
|
||||||
ret.RemoveAll(r => r.Key == removed);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ret?.Add(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret ?? nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
static MiniYaml MergePartial(MiniYaml existingNodes, MiniYaml overrideNodes)
|
static MiniYaml MergePartial(MiniYaml existingNodes, MiniYaml overrideNodes)
|
||||||
{
|
{
|
||||||
var resolvedExistingNodes = WeakResolveRemovals(existingNodes?.Nodes);
|
|
||||||
var resolvedOverrideNodes = WeakResolveRemovals(overrideNodes?.Nodes);
|
|
||||||
|
|
||||||
lock (ConflictScratch)
|
lock (ConflictScratch)
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
// PERF: Reuse ConflictScratch for all conflict checks to avoid allocations.
|
// PERF: Reuse ConflictScratch for all conflict checks to avoid allocations.
|
||||||
resolvedExistingNodes?.IntoDictionaryWithConflictLog(
|
existingNodes?.Nodes.IntoDictionaryWithConflictLog(
|
||||||
n => n.Key, n => n, "MiniYaml.Merge", ConflictScratch, k => k, n => $"{n.Key} (at {n.Location})");
|
n => n.Key, n => n, "MiniYaml.Merge", ConflictScratch, k => k, n => $"{n.Key} (at {n.Location})");
|
||||||
resolvedOverrideNodes?.IntoDictionaryWithConflictLog(
|
overrideNodes?.Nodes.IntoDictionaryWithConflictLog(
|
||||||
n => n.Key, n => n, "MiniYaml.Merge", ConflictScratch, k => k, n => $"{n.Key} (at {n.Location})");
|
n => n.Key, n => n, "MiniYaml.Merge", ConflictScratch, k => k, n => $"{n.Key} (at {n.Location})");
|
||||||
ConflictScratch.Clear();
|
ConflictScratch.Clear();
|
||||||
}
|
}
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
throw new YamlException(ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existingNodes == null)
|
if (existingNodes == null)
|
||||||
return overrideNodes;
|
return overrideNodes;
|
||||||
@@ -605,9 +556,6 @@ namespace OpenRA
|
|||||||
|
|
||||||
void MergeNode(MiniYamlNode node)
|
void MergeNode(MiniYamlNode node)
|
||||||
{
|
{
|
||||||
if (node.Key == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Append Removal nodes to the result.
|
// Append Removal nodes to the result.
|
||||||
// Therefore: we know the remainder of the method deals with a plain node.
|
// Therefore: we know the remainder of the method deals with a plain node.
|
||||||
if (node.Key.StartsWith('-'))
|
if (node.Key.StartsWith('-'))
|
||||||
@@ -678,12 +626,12 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
if (mapRules != null && mapRules.Value != null)
|
if (mapRules != null && mapRules.Value != null)
|
||||||
{
|
{
|
||||||
var mapFiles = FieldLoader.GetValue<ImmutableArray<string>>("value", mapRules.Value);
|
var mapFiles = FieldLoader.GetValue<string[]>("value", mapRules.Value);
|
||||||
files = files.Concat(mapFiles);
|
files = files.Append(mapFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
||||||
var yaml = files.Select(s => FromStream(fileSystem.Open(s), s, stringPool: stringPool));
|
IEnumerable<IReadOnlyCollection<MiniYamlNode>> yaml = files.Select(s => FromStream(fileSystem.Open(s), s, stringPool: stringPool));
|
||||||
if (mapRules != null && mapRules.Nodes.Length > 0)
|
if (mapRules != null && mapRules.Nodes.Length > 0)
|
||||||
yaml = yaml.Append(mapRules.Nodes);
|
yaml = yaml.Append(mapRules.Nodes);
|
||||||
|
|
||||||
@@ -748,7 +696,7 @@ namespace OpenRA
|
|||||||
public MiniYamlBuilder(string value, List<MiniYamlNode> nodes)
|
public MiniYamlBuilder(string value, List<MiniYamlNode> nodes)
|
||||||
{
|
{
|
||||||
Value = value;
|
Value = value;
|
||||||
Nodes = nodes == null ? [] : nodes.ConvertAll(x => new MiniYamlNodeBuilder(x));
|
Nodes = nodes == null ? new List<MiniYamlNodeBuilder>() : nodes.ConvertAll(x => new MiniYamlNodeBuilder(x));
|
||||||
}
|
}
|
||||||
|
|
||||||
public MiniYaml Build()
|
public MiniYaml Build()
|
||||||
@@ -776,6 +724,7 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
public class YamlException : Exception
|
public class YamlException : Exception
|
||||||
{
|
{
|
||||||
public YamlException(string s)
|
public YamlException(string s)
|
||||||
|
|||||||
@@ -10,22 +10,18 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Frozen;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OpenRA.FileSystem;
|
using OpenRA.FileSystem;
|
||||||
using OpenRA.Graphics;
|
using OpenRA.Graphics;
|
||||||
using OpenRA.Primitives;
|
|
||||||
using OpenRA.Video;
|
using OpenRA.Video;
|
||||||
using OpenRA.Widgets;
|
using OpenRA.Widgets;
|
||||||
using FS = OpenRA.FileSystem.FileSystem;
|
using FS = OpenRA.FileSystem.FileSystem;
|
||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
{
|
{
|
||||||
public interface IGlobalModData { }
|
|
||||||
|
|
||||||
public sealed class ModData : IDisposable
|
public sealed class ModData : IDisposable
|
||||||
{
|
{
|
||||||
public readonly Manifest Manifest;
|
public readonly Manifest Manifest;
|
||||||
@@ -35,13 +31,14 @@ namespace OpenRA
|
|||||||
public readonly IPackageLoader[] PackageLoaders;
|
public readonly IPackageLoader[] PackageLoaders;
|
||||||
public readonly ISoundLoader[] SoundLoaders;
|
public readonly ISoundLoader[] SoundLoaders;
|
||||||
public readonly ISpriteLoader[] SpriteLoaders;
|
public readonly ISpriteLoader[] SpriteLoaders;
|
||||||
|
public readonly ITerrainLoader TerrainLoader;
|
||||||
public readonly ISpriteSequenceLoader SpriteSequenceLoader;
|
public readonly ISpriteSequenceLoader SpriteSequenceLoader;
|
||||||
public readonly IVideoLoader[] VideoLoaders;
|
public readonly IVideoLoader[] VideoLoaders;
|
||||||
public readonly HotkeyManager Hotkeys;
|
public readonly HotkeyManager Hotkeys;
|
||||||
public readonly IFileSystemLoader FileSystemLoader;
|
public readonly IFileSystemLoader FileSystemLoader;
|
||||||
public readonly FrozenDictionary<string, CursorSequence> Cursors;
|
|
||||||
|
|
||||||
public ILoadScreen LoadScreen { get; }
|
public ILoadScreen LoadScreen { get; }
|
||||||
|
public CursorProvider CursorProvider { get; private set; }
|
||||||
public FS ModFiles;
|
public FS ModFiles;
|
||||||
public IReadOnlyFileSystem DefaultFileSystem => ModFiles;
|
public IReadOnlyFileSystem DefaultFileSystem => ModFiles;
|
||||||
|
|
||||||
@@ -51,11 +48,9 @@ namespace OpenRA
|
|||||||
readonly Lazy<IReadOnlyDictionary<string, ITerrainInfo>> defaultTerrainInfo;
|
readonly Lazy<IReadOnlyDictionary<string, ITerrainInfo>> defaultTerrainInfo;
|
||||||
public IReadOnlyDictionary<string, ITerrainInfo> DefaultTerrainInfo => defaultTerrainInfo.Value;
|
public IReadOnlyDictionary<string, ITerrainInfo> DefaultTerrainInfo => defaultTerrainInfo.Value;
|
||||||
|
|
||||||
readonly TypeDictionary modules = [];
|
|
||||||
|
|
||||||
public ModData(Manifest mod, InstalledMods mods, bool useLoadScreen = false)
|
public ModData(Manifest mod, InstalledMods mods, bool useLoadScreen = false)
|
||||||
{
|
{
|
||||||
Languages = [];
|
Languages = Array.Empty<string>();
|
||||||
|
|
||||||
// Take a local copy of the manifest
|
// Take a local copy of the manifest
|
||||||
Manifest = new Manifest(mod.Id, mod.Package);
|
Manifest = new Manifest(mod.Id, mod.Package);
|
||||||
@@ -65,64 +60,53 @@ namespace OpenRA
|
|||||||
|
|
||||||
FileSystemLoader = ObjectCreator.GetLoader<IFileSystemLoader>(Manifest.FileSystem.Value, "filesystem");
|
FileSystemLoader = ObjectCreator.GetLoader<IFileSystemLoader>(Manifest.FileSystem.Value, "filesystem");
|
||||||
FieldLoader.Load(FileSystemLoader, Manifest.FileSystem);
|
FieldLoader.Load(FileSystemLoader, Manifest.FileSystem);
|
||||||
FileSystemLoader.Mount(Manifest, ModFiles, ObjectCreator);
|
FileSystemLoader.Mount(ModFiles, ObjectCreator);
|
||||||
ModFiles.TrimExcess();
|
ModFiles.TrimExcess();
|
||||||
|
|
||||||
foreach (var kv in Manifest.GlobalModData)
|
Manifest.LoadCustomData(ObjectCreator);
|
||||||
{
|
|
||||||
var t = ObjectCreator.FindType(kv.Key);
|
|
||||||
if (t == null || !typeof(IGlobalModData).IsAssignableFrom(t))
|
|
||||||
throw new InvalidDataException($"`{kv.Key}` is not a valid mod manifest entry.");
|
|
||||||
|
|
||||||
IGlobalModData module;
|
FluentProvider.Initialize(this, DefaultFileSystem);
|
||||||
var ctor = t.GetConstructor([typeof(MiniYaml)]);
|
|
||||||
if (ctor != null)
|
|
||||||
{
|
|
||||||
// Class has opted-in to DIY initialization
|
|
||||||
module = (IGlobalModData)ctor.Invoke([kv.Value]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Automatically load the child nodes using FieldLoader
|
|
||||||
module = ObjectCreator.CreateObject<IGlobalModData>(kv.Key);
|
|
||||||
FieldLoader.Load(module, kv.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
modules.Add(module);
|
|
||||||
}
|
|
||||||
|
|
||||||
FluentProvider.Initialize(Manifest, DefaultFileSystem);
|
|
||||||
|
|
||||||
if (useLoadScreen)
|
if (useLoadScreen)
|
||||||
{
|
{
|
||||||
LoadScreen = ObjectCreator.CreateObject<ILoadScreen>(Manifest.LoadScreen.Value);
|
LoadScreen = ObjectCreator.CreateObject<ILoadScreen>(Manifest.LoadScreen.Value);
|
||||||
LoadScreen.Init(Manifest, DefaultFileSystem);
|
LoadScreen.Init(this, Manifest.LoadScreen.ToDictionary(my => my.Value));
|
||||||
LoadScreen.Display();
|
LoadScreen.Display();
|
||||||
}
|
}
|
||||||
|
|
||||||
WidgetLoader = new WidgetLoader(Manifest, DefaultFileSystem);
|
WidgetLoader = new WidgetLoader(this);
|
||||||
MapCache = new MapCache(Manifest, ModFiles);
|
MapCache = new MapCache(this);
|
||||||
|
|
||||||
SoundLoaders = ObjectCreator.GetLoaders<ISoundLoader>(Manifest.SoundFormats, "sound");
|
SoundLoaders = ObjectCreator.GetLoaders<ISoundLoader>(Manifest.SoundFormats, "sound");
|
||||||
SpriteLoaders = ObjectCreator.GetLoaders<ISpriteLoader>(Manifest.SpriteFormats, "sprite");
|
SpriteLoaders = ObjectCreator.GetLoaders<ISpriteLoader>(Manifest.SpriteFormats, "sprite");
|
||||||
VideoLoaders = ObjectCreator.GetLoaders<IVideoLoader>(Manifest.VideoFormats, "video");
|
VideoLoaders = ObjectCreator.GetLoaders<IVideoLoader>(Manifest.VideoFormats, "video");
|
||||||
SpriteSequenceLoader = ObjectCreator.GetLoader<ISpriteSequenceLoader>(Manifest.SpriteSequenceFormat, "sequence");
|
|
||||||
Hotkeys = new HotkeyManager(ModFiles, ObjectCreator, Manifest);
|
var terrainFormat = Manifest.Get<TerrainFormat>();
|
||||||
Cursors = ParseCursors(Manifest, DefaultFileSystem);
|
var terrainLoader = ObjectCreator.FindType(terrainFormat.Type + "Loader");
|
||||||
|
var terrainCtor = terrainLoader?.GetConstructor(new[] { typeof(ModData) });
|
||||||
|
if (terrainLoader == null || !terrainLoader.GetInterfaces().Contains(typeof(ITerrainLoader)) || terrainCtor == null)
|
||||||
|
throw new InvalidOperationException($"Unable to find a terrain loader for type '{terrainFormat.Type}'.");
|
||||||
|
|
||||||
|
TerrainLoader = (ITerrainLoader)terrainCtor.Invoke(new[] { this });
|
||||||
|
|
||||||
|
var sequenceFormat = Manifest.Get<SpriteSequenceFormat>();
|
||||||
|
var sequenceLoader = ObjectCreator.FindType(sequenceFormat.Type + "Loader");
|
||||||
|
var sequenceCtor = sequenceLoader?.GetConstructor(new[] { typeof(ModData) });
|
||||||
|
if (sequenceLoader == null || !sequenceLoader.GetInterfaces().Contains(typeof(ISpriteSequenceLoader)) || sequenceCtor == null)
|
||||||
|
throw new InvalidOperationException($"Unable to find a sequence loader for type '{sequenceFormat.Type}'.");
|
||||||
|
|
||||||
|
SpriteSequenceLoader = (ISpriteSequenceLoader)sequenceCtor.Invoke(new[] { this });
|
||||||
|
|
||||||
|
Hotkeys = new HotkeyManager(ModFiles, Game.Settings.Keys, Manifest);
|
||||||
|
|
||||||
defaultRules = Exts.Lazy(() => Ruleset.LoadDefaults(this));
|
defaultRules = Exts.Lazy(() => Ruleset.LoadDefaults(this));
|
||||||
defaultTerrainInfo = Exts.Lazy(() =>
|
defaultTerrainInfo = Exts.Lazy(() =>
|
||||||
{
|
{
|
||||||
var terrainType = ObjectCreator.FindType(Manifest.TerrainFormat + "Loader");
|
|
||||||
var terrainCtor = terrainType?.GetConstructor([typeof(ModData)]);
|
|
||||||
if (terrainType == null || !terrainType.GetInterfaces().Contains(typeof(ITerrainLoader)) || terrainCtor == null)
|
|
||||||
throw new InvalidOperationException($"Unable to find a terrain loader for type '{Manifest.TerrainFormat}'.");
|
|
||||||
|
|
||||||
var items = new Dictionary<string, ITerrainInfo>();
|
var items = new Dictionary<string, ITerrainInfo>();
|
||||||
var terrainLoader = (ITerrainLoader)terrainCtor.Invoke([this]);
|
|
||||||
foreach (var file in Manifest.TileSets)
|
foreach (var file in Manifest.TileSets)
|
||||||
{
|
{
|
||||||
var t = terrainLoader.ParseTerrain(DefaultFileSystem, file);
|
var t = TerrainLoader.ParseTerrain(DefaultFileSystem, file);
|
||||||
items.Add(t.Id, t);
|
items.Add(t.Id, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,23 +116,6 @@ namespace OpenRA
|
|||||||
initialThreadId = Environment.CurrentManagedThreadId;
|
initialThreadId = Environment.CurrentManagedThreadId;
|
||||||
}
|
}
|
||||||
|
|
||||||
static FrozenDictionary<string, CursorSequence> ParseCursors(Manifest manifest, IReadOnlyFileSystem fileSystem)
|
|
||||||
{
|
|
||||||
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
|
||||||
var sequenceYaml = MiniYaml.Merge(manifest.Cursors.Select(
|
|
||||||
s => MiniYaml.FromStream(fileSystem.Open(s), s, stringPool: stringPool)));
|
|
||||||
|
|
||||||
var cursors = new Dictionary<string, CursorSequence>();
|
|
||||||
foreach (var node in sequenceYaml)
|
|
||||||
if (node.Key == "Cursors")
|
|
||||||
foreach (var fileNode in node.Value.Nodes)
|
|
||||||
foreach (var sequenceNode in fileNode.Value.Nodes)
|
|
||||||
cursors.Add(sequenceNode.Key, new CursorSequence(
|
|
||||||
sequenceNode.Key, fileNode.Key, fileNode.Value.Value, sequenceNode.Value));
|
|
||||||
|
|
||||||
return cursors.ToFrozenDictionary();
|
|
||||||
}
|
|
||||||
|
|
||||||
// HACK: Only update the loading screen if we're in the main thread.
|
// HACK: Only update the loading screen if we're in the main thread.
|
||||||
readonly int initialThreadId;
|
readonly int initialThreadId;
|
||||||
internal void HandleLoadingProgress()
|
internal void HandleLoadingProgress()
|
||||||
@@ -165,18 +132,26 @@ namespace OpenRA
|
|||||||
// horribly when you use ModData in unexpected ways.
|
// horribly when you use ModData in unexpected ways.
|
||||||
ChromeMetrics.Initialize(this);
|
ChromeMetrics.Initialize(this);
|
||||||
ChromeProvider.Initialize(this);
|
ChromeProvider.Initialize(this);
|
||||||
Ui.Initialize(this);
|
FluentProvider.Initialize(this, fileSystem);
|
||||||
FluentProvider.Initialize(Manifest, fileSystem);
|
|
||||||
|
|
||||||
Game.Sound.Initialize(SoundLoaders, fileSystem);
|
Game.Sound.Initialize(SoundLoaders, fileSystem);
|
||||||
|
|
||||||
|
CursorProvider = new CursorProvider(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<string> Languages { get; }
|
public IEnumerable<string> Languages { get; }
|
||||||
|
|
||||||
public void PrepareMap(Map map)
|
public Map PrepareMap(string uid)
|
||||||
{
|
{
|
||||||
LoadScreen?.Display();
|
LoadScreen?.Display();
|
||||||
|
|
||||||
|
if (MapCache[uid].Status != MapStatus.Available)
|
||||||
|
throw new InvalidDataException($"Invalid map uid: {uid}");
|
||||||
|
|
||||||
|
Map map;
|
||||||
|
using (new Support.PerfTimer("Map"))
|
||||||
|
map = new Map(this, MapCache[uid].Package);
|
||||||
|
|
||||||
// Reinitialize all our assets
|
// Reinitialize all our assets
|
||||||
InitializeLoaders(map);
|
InitializeLoaders(map);
|
||||||
map.Sequences.LoadSprites();
|
map.Sequences.LoadSprites();
|
||||||
@@ -185,37 +160,14 @@ namespace OpenRA
|
|||||||
using (new Support.PerfTimer("Map.Music"))
|
using (new Support.PerfTimer("Map.Music"))
|
||||||
foreach (var entry in map.Rules.Music)
|
foreach (var entry in map.Rules.Music)
|
||||||
entry.Value.Load(map);
|
entry.Value.Load(map);
|
||||||
|
|
||||||
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MiniYamlNode[][] GetRulesYaml()
|
public List<MiniYamlNode>[] GetRulesYaml()
|
||||||
{
|
{
|
||||||
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
||||||
return Manifest.Rules.Select(s => MiniYaml.FromStream(DefaultFileSystem.Open(s), s, stringPool: stringPool).ToArray()).ToArray();
|
return Manifest.Rules.Select(s => MiniYaml.FromStream(DefaultFileSystem.Open(s), s, stringPool: stringPool)).ToArray();
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Load a cached IGlobalModData instance.</summary>
|
|
||||||
public T GetOrCreate<T>() where T : IGlobalModData
|
|
||||||
{
|
|
||||||
var module = modules.GetOrDefault<T>();
|
|
||||||
|
|
||||||
// Lazily create the default values if not explicitly defined.
|
|
||||||
if (module == null)
|
|
||||||
{
|
|
||||||
module = (T)ObjectCreator.CreateBasic(typeof(T));
|
|
||||||
modules.Add(module);
|
|
||||||
}
|
|
||||||
|
|
||||||
return module;
|
|
||||||
}
|
|
||||||
|
|
||||||
public T GetOrNull<T>() where T : IGlobalModData
|
|
||||||
{
|
|
||||||
return modules.GetOrDefault<T>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public T GetSettings<T>() where T : SettingsModule
|
|
||||||
{
|
|
||||||
return Game.Settings.GetOrCreate<T>(ObjectCreator, Manifest.Id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@@ -225,18 +177,14 @@ namespace OpenRA
|
|||||||
|
|
||||||
ObjectCreator?.Dispose();
|
ObjectCreator?.Dispose();
|
||||||
|
|
||||||
foreach (var module in modules)
|
Manifest.Dispose();
|
||||||
{
|
|
||||||
var disposableModule = module as IDisposable;
|
|
||||||
disposableModule?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface ILoadScreen : IDisposable
|
public interface ILoadScreen : IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>Initializes the loadscreen with yaml data from the LoadScreen block in mod.yaml.</summary>
|
/// <summary>Initializes the loadscreen with yaml data from the LoadScreen block in mod.yaml.</summary>
|
||||||
void Init(Manifest manifest, IReadOnlyFileSystem fileSystem);
|
void Init(ModData m, Dictionary<string, string> info);
|
||||||
|
|
||||||
/// <summary>Called at arbitrary times during mod load to rerender the loadscreen.</summary>
|
/// <summary>Called at arbitrary times during mod load to rerender the loadscreen.</summary>
|
||||||
void Display();
|
void Display();
|
||||||
@@ -245,7 +193,7 @@ namespace OpenRA
|
|||||||
/// Called before loading the mod assets.
|
/// Called before loading the mod assets.
|
||||||
/// Returns false if mod loading should be aborted (e.g. switching to another mod instead).
|
/// Returns false if mod loading should be aborted (e.g. switching to another mod instead).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool BeforeLoad(ModData modData);
|
bool BeforeLoad();
|
||||||
|
|
||||||
/// <summary>Called when the engine expects to connect to a server/replay or load the shellmap.</summary>
|
/// <summary>Called when the engine expects to connect to a server/replay or load the shellmap.</summary>
|
||||||
void StartGame(Arguments args);
|
void StartGame(Arguments args);
|
||||||
@@ -253,6 +201,6 @@ namespace OpenRA
|
|||||||
|
|
||||||
public interface IFileSystemLoader
|
public interface IFileSystemLoader
|
||||||
{
|
{
|
||||||
void Mount(Manifest manifest, FS fileSystem, ObjectCreator objectCreator);
|
void Mount(FS fileSystem, ObjectCreator objectCreator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,9 +42,9 @@ namespace OpenRA.Network
|
|||||||
public sealed class EchoConnection : IConnection
|
public sealed class EchoConnection : IConnection
|
||||||
{
|
{
|
||||||
const int LocalClientId = 1;
|
const int LocalClientId = 1;
|
||||||
readonly Queue<(int Frame, int SyncHash, ulong DefeatState)> sync = [];
|
readonly Queue<(int Frame, int SyncHash, ulong DefeatState)> sync = new();
|
||||||
readonly Queue<(int Frame, OrderPacket Orders)> orders = [];
|
readonly Queue<(int Frame, OrderPacket Orders)> orders = new();
|
||||||
readonly Queue<OrderPacket> immediateOrders = [];
|
readonly Queue<OrderPacket> immediateOrders = new();
|
||||||
bool disposed;
|
bool disposed;
|
||||||
|
|
||||||
int IConnection.LocalClientId => LocalClientId;
|
int IConnection.LocalClientId => LocalClientId;
|
||||||
@@ -52,7 +52,7 @@ namespace OpenRA.Network
|
|||||||
void IConnection.StartGame()
|
void IConnection.StartGame()
|
||||||
{
|
{
|
||||||
// Inject an empty frame to fill the gap we are making by projecting forward orders
|
// Inject an empty frame to fill the gap we are making by projecting forward orders
|
||||||
orders.Enqueue((0, new OrderPacket([])));
|
orders.Enqueue((0, new OrderPacket(Array.Empty<Order>())));
|
||||||
}
|
}
|
||||||
|
|
||||||
void IConnection.Send(int frame, IEnumerable<Order> o)
|
void IConnection.Send(int frame, IEnumerable<Order> o)
|
||||||
@@ -100,12 +100,12 @@ namespace OpenRA.Network
|
|||||||
{
|
{
|
||||||
public readonly ConnectionTarget Target;
|
public readonly ConnectionTarget Target;
|
||||||
internal ReplayRecorder Recorder { get; private set; }
|
internal ReplayRecorder Recorder { get; private set; }
|
||||||
readonly Queue<(int Frame, int SyncHash, ulong DefeatState)> sentSync = [];
|
readonly Queue<(int Frame, int SyncHash, ulong DefeatState)> sentSync = new();
|
||||||
readonly Queue<(int Frame, int SyncHash, ulong DefeatState)> queuedSyncPackets = [];
|
readonly Queue<(int Frame, int SyncHash, ulong DefeatState)> queuedSyncPackets = new();
|
||||||
|
|
||||||
readonly Queue<(int Frame, OrderPacket Orders)> sentOrders = [];
|
readonly Queue<(int Frame, OrderPacket Orders)> sentOrders = new();
|
||||||
readonly Queue<OrderPacket> sentImmediateOrders = [];
|
readonly Queue<OrderPacket> sentImmediateOrders = new();
|
||||||
readonly ConcurrentQueue<(int FromClient, byte[] Data)> receivedPackets = [];
|
readonly ConcurrentQueue<(int FromClient, byte[] Data)> receivedPackets = new();
|
||||||
TcpClient tcp;
|
TcpClient tcp;
|
||||||
volatile ConnectionState connectionState = ConnectionState.Connecting;
|
volatile ConnectionState connectionState = ConnectionState.Connecting;
|
||||||
volatile int clientId;
|
volatile int clientId;
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ namespace OpenRA.Network
|
|||||||
|
|
||||||
public ConnectionTarget()
|
public ConnectionTarget()
|
||||||
{
|
{
|
||||||
endpoints = [new DnsEndPoint("invalid", 0)];
|
endpoints = new[] { new DnsEndPoint("invalid", 0) };
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConnectionTarget(string host, int port)
|
public ConnectionTarget(string host, int port)
|
||||||
{
|
{
|
||||||
endpoints = [new DnsEndPoint(host, port)];
|
endpoints = new[] { new DnsEndPoint(host, port) };
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConnectionTarget(IEnumerable<DnsEndPoint> endpoints)
|
public ConnectionTarget(IEnumerable<DnsEndPoint> endpoints)
|
||||||
@@ -48,7 +48,7 @@ namespace OpenRA.Network
|
|||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
return [];
|
return Enumerable.Empty<IPEndPoint>();
|
||||||
}
|
}
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using Linguini.Shared.Types.Bundle;
|
using Linguini.Shared.Types.Bundle;
|
||||||
|
|
||||||
namespace OpenRA.Network
|
namespace OpenRA.Network
|
||||||
@@ -56,16 +55,14 @@ namespace OpenRA.Network
|
|||||||
public readonly string Key = string.Empty;
|
public readonly string Key = string.Empty;
|
||||||
|
|
||||||
[FieldLoader.LoadUsing(nameof(LoadArguments))]
|
[FieldLoader.LoadUsing(nameof(LoadArguments))]
|
||||||
public readonly ImmutableArray<object> Arguments;
|
public readonly object[] Arguments;
|
||||||
|
|
||||||
static object LoadArguments(MiniYaml yaml)
|
static object LoadArguments(MiniYaml yaml)
|
||||||
{
|
{
|
||||||
|
var arguments = new List<object>();
|
||||||
var argumentsNode = yaml.NodeWithKeyOrDefault("Arguments");
|
var argumentsNode = yaml.NodeWithKeyOrDefault("Arguments");
|
||||||
|
if (argumentsNode != null)
|
||||||
if (argumentsNode == null)
|
{
|
||||||
return ImmutableArray<object>.Empty;
|
|
||||||
|
|
||||||
var arguments = new List<object>(argumentsNode.Value.Nodes.Length * 2);
|
|
||||||
foreach (var argumentNode in argumentsNode.Value.Nodes)
|
foreach (var argumentNode in argumentsNode.Value.Nodes)
|
||||||
{
|
{
|
||||||
var argument = FieldLoader.Load<FluentArgument>(argumentNode.Value);
|
var argument = FieldLoader.Load<FluentArgument>(argumentNode.Value);
|
||||||
@@ -80,8 +77,9 @@ namespace OpenRA.Network
|
|||||||
else
|
else
|
||||||
arguments.Add(argument.Value);
|
arguments.Add(argument.Value);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return arguments.ToImmutableArray();
|
return arguments.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public FluentMessage(MiniYaml yaml)
|
public FluentMessage(MiniYaml yaml)
|
||||||
|
|||||||
@@ -86,23 +86,22 @@ namespace OpenRA.Network
|
|||||||
// Loaded from file and updated during gameplay
|
// Loaded from file and updated during gameplay
|
||||||
public int LastOrdersFrame { get; private set; }
|
public int LastOrdersFrame { get; private set; }
|
||||||
public int LastSyncFrame { get; private set; }
|
public int LastSyncFrame { get; private set; }
|
||||||
byte[] lastSyncPacket = [];
|
byte[] lastSyncPacket = Array.Empty<byte>();
|
||||||
|
|
||||||
// Loaded from file or set on game start
|
// Loaded from file or set on game start
|
||||||
public Session.Global GlobalSettings { get; private set; }
|
public Session.Global GlobalSettings { get; private set; }
|
||||||
public Dictionary<string, Session.Slot> Slots { get; private set; }
|
public Dictionary<string, Session.Slot> Slots { get; private set; }
|
||||||
public Dictionary<string, SlotClient> SlotClients { get; private set; }
|
public Dictionary<string, SlotClient> SlotClients { get; private set; }
|
||||||
public Dictionary<int, MiniYaml> TraitData = [];
|
public Dictionary<int, MiniYaml> TraitData = new();
|
||||||
public MapGenerationArgs MapGenerationArgs;
|
|
||||||
|
|
||||||
// Set on game start
|
// Set on game start
|
||||||
int[] clientsBySlotIndex = [];
|
int[] clientsBySlotIndex = Array.Empty<int>();
|
||||||
int firstBotSlotIndex = -1;
|
int firstBotSlotIndex = -1;
|
||||||
|
|
||||||
public GameSave()
|
public GameSave()
|
||||||
{
|
{
|
||||||
LastOrdersFrame = -1;
|
LastOrdersFrame = -1;
|
||||||
Slots = [];
|
Slots = new Dictionary<string, Session.Slot>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public GameSave(string filepath)
|
public GameSave(string filepath)
|
||||||
@@ -124,10 +123,10 @@ namespace OpenRA.Network
|
|||||||
lastSyncPacket = rs.ReadBytes(Order.SyncHashOrderLength);
|
lastSyncPacket = rs.ReadBytes(Order.SyncHashOrderLength);
|
||||||
|
|
||||||
var globalSettings = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength), $"{filepath}:globalSettings");
|
var globalSettings = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength), $"{filepath}:globalSettings");
|
||||||
GlobalSettings = Session.Global.Deserialize(globalSettings.First().Value);
|
GlobalSettings = Session.Global.Deserialize(globalSettings[0].Value);
|
||||||
|
|
||||||
var slots = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength), $"{filepath}:slots");
|
var slots = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength), $"{filepath}:slots");
|
||||||
Slots = [];
|
Slots = new Dictionary<string, Session.Slot>();
|
||||||
foreach (var s in slots)
|
foreach (var s in slots)
|
||||||
{
|
{
|
||||||
var slot = Session.Slot.Deserialize(s.Value);
|
var slot = Session.Slot.Deserialize(s.Value);
|
||||||
@@ -135,17 +134,13 @@ namespace OpenRA.Network
|
|||||||
}
|
}
|
||||||
|
|
||||||
var slotClients = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength), $"{filepath}:slotClients");
|
var slotClients = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength), $"{filepath}:slotClients");
|
||||||
SlotClients = [];
|
SlotClients = new Dictionary<string, SlotClient>();
|
||||||
foreach (var s in slotClients)
|
foreach (var s in slotClients)
|
||||||
{
|
{
|
||||||
var slotClient = SlotClient.Deserialize(s.Value);
|
var slotClient = SlotClient.Deserialize(s.Value);
|
||||||
SlotClients.Add(slotClient.Slot, slotClient);
|
SlotClients.Add(slotClient.Slot, slotClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
var mapGenerationArgs = rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength);
|
|
||||||
if (!string.IsNullOrEmpty(mapGenerationArgs))
|
|
||||||
MapGenerationArgs = FieldLoader.Load<MapGenerationArgs>(new MiniYaml("", MiniYaml.FromString(mapGenerationArgs, $"{filepath}:mapGenerationArgs")));
|
|
||||||
|
|
||||||
if (rs.Position != traitDataOffset || rs.ReadInt32() != TraitDataMarker)
|
if (rs.Position != traitDataOffset || rs.ReadInt32() != TraitDataMarker)
|
||||||
throw new InvalidDataException("Invalid orasav file");
|
throw new InvalidDataException("Invalid orasav file");
|
||||||
|
|
||||||
@@ -160,9 +155,6 @@ namespace OpenRA.Network
|
|||||||
|
|
||||||
public void StartGame(Session lobbyInfo, MapPreview map)
|
public void StartGame(Session lobbyInfo, MapPreview map)
|
||||||
{
|
{
|
||||||
if (map.Class == MapClassification.Generated)
|
|
||||||
MapGenerationArgs = map.GenerationArgs;
|
|
||||||
|
|
||||||
// Game orders are mapped from a client index to the slot that they occupy
|
// Game orders are mapped from a client index to the slot that they occupy
|
||||||
// Orders from spectators are ignored, which is not a problem in practice
|
// Orders from spectators are ignored, which is not a problem in practice
|
||||||
// because all immediate orders are also ignored
|
// because all immediate orders are also ignored
|
||||||
@@ -174,8 +166,8 @@ namespace OpenRA.Network
|
|||||||
|
|
||||||
// Perform a deep clone by round-tripping the data
|
// Perform a deep clone by round-tripping the data
|
||||||
GlobalSettings = Session.Global.Deserialize(lobbyInfo.GlobalSettings.Serialize().Value);
|
GlobalSettings = Session.Global.Deserialize(lobbyInfo.GlobalSettings.Serialize().Value);
|
||||||
Slots = [];
|
Slots = new Dictionary<string, Session.Slot>();
|
||||||
SlotClients = [];
|
SlotClients = new Dictionary<string, SlotClient>();
|
||||||
foreach (var s in lobbyInfo.Slots)
|
foreach (var s in lobbyInfo.Slots)
|
||||||
{
|
{
|
||||||
Slots[s.Key] = Session.Slot.Deserialize(s.Value.Serialize().Value);
|
Slots[s.Key] = Session.Slot.Deserialize(s.Value.Serialize().Value);
|
||||||
@@ -314,8 +306,6 @@ namespace OpenRA.Network
|
|||||||
.ToList();
|
.ToList();
|
||||||
file.WriteLengthPrefixedString(Encoding.UTF8, slotClientNodes.WriteToString());
|
file.WriteLengthPrefixedString(Encoding.UTF8, slotClientNodes.WriteToString());
|
||||||
|
|
||||||
file.WriteLengthPrefixedString(Encoding.UTF8, MapGenerationArgs?.Serialize().WriteToString() ?? string.Empty);
|
|
||||||
|
|
||||||
var traitDataOffset = file.Length;
|
var traitDataOffset = file.Length;
|
||||||
file.Write(TraitDataMarker);
|
file.Write(TraitDataMarker);
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,7 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Frozen;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
@@ -49,9 +47,8 @@ namespace OpenRA.Network
|
|||||||
|
|
||||||
public class GameServer
|
public class GameServer
|
||||||
{
|
{
|
||||||
static readonly ImmutableArray<string> SerializeFields =
|
static readonly string[] SerializeFields =
|
||||||
[
|
{
|
||||||
|
|
||||||
// Server information
|
// Server information
|
||||||
"Name", "Address",
|
"Name", "Address",
|
||||||
|
|
||||||
@@ -60,7 +57,7 @@ namespace OpenRA.Network
|
|||||||
|
|
||||||
// Current server state
|
// Current server state
|
||||||
"Map", "State", "MaxPlayers", "Protected", "Authentication", "DisabledSpawnPoints"
|
"Map", "State", "MaxPlayers", "Protected", "Authentication", "DisabledSpawnPoints"
|
||||||
];
|
};
|
||||||
|
|
||||||
public const int ProtocolVersion = 2;
|
public const int ProtocolVersion = 2;
|
||||||
|
|
||||||
@@ -133,10 +130,10 @@ namespace OpenRA.Network
|
|||||||
public readonly bool IsJoinable = false;
|
public readonly bool IsJoinable = false;
|
||||||
|
|
||||||
[FieldLoader.LoadUsing(nameof(LoadClients))]
|
[FieldLoader.LoadUsing(nameof(LoadClients))]
|
||||||
public readonly ImmutableArray<GameClient> Clients;
|
public readonly GameClient[] Clients;
|
||||||
|
|
||||||
/// <summary>The list of spawnpoints that are disabled for this game.</summary>
|
/// <summary>The list of spawnpoints that are disabled for this game.</summary>
|
||||||
public readonly FrozenSet<int> DisabledSpawnPoints = FrozenSet<int>.Empty;
|
public readonly int[] DisabledSpawnPoints = Array.Empty<int>();
|
||||||
|
|
||||||
public string ModLabel => $"{ModTitle} ({Version})";
|
public string ModLabel => $"{ModTitle} ({Version})";
|
||||||
|
|
||||||
@@ -152,7 +149,7 @@ namespace OpenRA.Network
|
|||||||
clients.Add(FieldLoader.Load<GameClient>(client.Value));
|
clients.Add(FieldLoader.Load<GameClient>(client.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
return clients.ToImmutableArray();
|
return clients.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public GameServer(MiniYaml yaml)
|
public GameServer(MiniYaml yaml)
|
||||||
@@ -229,9 +226,9 @@ namespace OpenRA.Network
|
|||||||
ModWebsite = manifest.Metadata.Website;
|
ModWebsite = manifest.Metadata.Website;
|
||||||
ModIcon32 = manifest.Metadata.WebIcon32;
|
ModIcon32 = manifest.Metadata.WebIcon32;
|
||||||
Protected = !string.IsNullOrEmpty(server.Settings.Password);
|
Protected = !string.IsNullOrEmpty(server.Settings.Password);
|
||||||
Authentication = server.Settings.RequireAuthentication || server.Settings.ProfileIDWhitelist.Count > 0;
|
Authentication = server.Settings.RequireAuthentication || server.Settings.ProfileIDWhitelist.Length > 0;
|
||||||
Clients = server.LobbyInfo.Clients.Select(c => new GameClient(c)).ToImmutableArray();
|
Clients = server.LobbyInfo.Clients.Select(c => new GameClient(c)).ToArray();
|
||||||
DisabledSpawnPoints = server.LobbyInfo.DisabledSpawnPoints?.ToFrozenSet() ?? FrozenSet<int>.Empty;
|
DisabledSpawnPoints = server.LobbyInfo.DisabledSpawnPoints?.ToArray() ?? Array.Empty<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ToPOSTData(bool lanGame)
|
public string ToPOSTData(bool lanGame)
|
||||||
|
|||||||
@@ -269,11 +269,6 @@ namespace OpenRA
|
|||||||
return new Order(order, null, false) { IsImmediate = isImmediate, TargetString = targetString };
|
return new Order(order, null, false) { IsImmediate = isImmediate, TargetString = targetString };
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Order FromTargetString(string order, string targetString, bool isImmediate, uint extraData)
|
|
||||||
{
|
|
||||||
return new Order(order, null, false) { IsImmediate = isImmediate, TargetString = targetString, ExtraData = extraData };
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Order FromGroupedOrder(Order grouped, Actor subject)
|
public static Order FromGroupedOrder(Order grouped, Actor subject)
|
||||||
{
|
{
|
||||||
return new Order(
|
return new Order(
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ namespace OpenRA.Network
|
|||||||
|
|
||||||
public static class OrderIO
|
public static class OrderIO
|
||||||
{
|
{
|
||||||
static readonly OrderPacket NoOrders = new([]);
|
static readonly OrderPacket NoOrders = new(Array.Empty<Order>());
|
||||||
|
|
||||||
public static byte[] SerializeSync((int Frame, int SyncHash, ulong DefeatState) data)
|
public static byte[] SerializeSync((int Frame, int SyncHash, ulong DefeatState) data)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Frozen;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -27,8 +26,8 @@ namespace OpenRA.Network
|
|||||||
const string DesyncCompareLogs = "notification-desync-compare-logs";
|
const string DesyncCompareLogs = "notification-desync-compare-logs";
|
||||||
|
|
||||||
readonly SyncReport syncReport;
|
readonly SyncReport syncReport;
|
||||||
readonly Dictionary<int, Queue<(int Frame, OrderPacket Orders)>> pendingOrders = [];
|
readonly Dictionary<int, Queue<(int Frame, OrderPacket Orders)>> pendingOrders = new();
|
||||||
readonly Dictionary<int, (int SyncHash, ulong DefeatState)> syncForFrame = [];
|
readonly Dictionary<int, (int SyncHash, ulong DefeatState)> syncForFrame = new();
|
||||||
|
|
||||||
public Session LobbyInfo = new();
|
public Session LobbyInfo = new();
|
||||||
|
|
||||||
@@ -41,7 +40,7 @@ namespace OpenRA.Network
|
|||||||
public bool AuthenticationFailed = false;
|
public bool AuthenticationFailed = false;
|
||||||
|
|
||||||
// The default null means "no map restriction" while an empty set means "all maps restricted"
|
// The default null means "no map restriction" while an empty set means "all maps restricted"
|
||||||
public FrozenSet<string> ServerMapPool = null;
|
public HashSet<string> ServerMapPool = null;
|
||||||
|
|
||||||
public int NetFrameNumber { get; private set; }
|
public int NetFrameNumber { get; private set; }
|
||||||
public int LocalFrameNumber;
|
public int LocalFrameNumber;
|
||||||
@@ -54,11 +53,11 @@ namespace OpenRA.Network
|
|||||||
internal int GameSaveLastFrame = -1;
|
internal int GameSaveLastFrame = -1;
|
||||||
internal int GameSaveLastSyncFrame = -1;
|
internal int GameSaveLastSyncFrame = -1;
|
||||||
|
|
||||||
readonly List<Order> localOrders = [];
|
readonly List<Order> localOrders = new();
|
||||||
readonly List<Order> localImmediateOrders = [];
|
readonly List<Order> localImmediateOrders = new();
|
||||||
|
|
||||||
readonly List<ClientOrder> processClientOrders = [];
|
readonly List<ClientOrder> processClientOrders = new();
|
||||||
readonly List<int> processClientsToRemove = [];
|
readonly List<int> processClientsToRemove = new();
|
||||||
|
|
||||||
bool disposed;
|
bool disposed;
|
||||||
bool generateSyncReport = false;
|
bool generateSyncReport = false;
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ namespace OpenRA.Network
|
|||||||
public (int ClientId, byte[] Packet)[] Packets;
|
public (int ClientId, byte[] Packet)[] Packets;
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly Queue<Chunk> chunks = [];
|
readonly Queue<Chunk> chunks = new();
|
||||||
readonly Queue<(int Frame, int SyncHash, ulong DefeatState)> sync = [];
|
readonly Queue<(int Frame, int SyncHash, ulong DefeatState)> sync = new();
|
||||||
readonly int orderLatency;
|
readonly int orderLatency;
|
||||||
|
|
||||||
public readonly int TickCount;
|
public readonly int TickCount;
|
||||||
@@ -87,7 +87,7 @@ namespace OpenRA.Network
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var gameSpeeds = Game.ModData.GetOrCreate<GameSpeeds>();
|
var gameSpeeds = Game.ModData.Manifest.Get<GameSpeeds>();
|
||||||
var gameSpeedName = LobbyInfo.GlobalSettings.OptionOrDefault("gamespeed", gameSpeeds.DefaultSpeed);
|
var gameSpeedName = LobbyInfo.GlobalSettings.OptionOrDefault("gamespeed", gameSpeeds.DefaultSpeed);
|
||||||
orderLatency = gameSpeeds.Speeds[gameSpeedName].OrderLatency;
|
orderLatency = gameSpeeds.Speeds[gameSpeedName].OrderLatency;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ namespace OpenRA.Network
|
|||||||
{
|
{
|
||||||
public class Session
|
public class Session
|
||||||
{
|
{
|
||||||
public List<Client> Clients = [];
|
public List<Client> Clients = new();
|
||||||
|
|
||||||
// Keyed by the PlayerReference id that the slot corresponds to
|
// Keyed by the PlayerReference id that the slot corresponds to
|
||||||
public Dictionary<string, Slot> Slots = [];
|
public Dictionary<string, Slot> Slots = new();
|
||||||
|
|
||||||
public HashSet<int> DisabledSpawnPoints = [];
|
public HashSet<int> DisabledSpawnPoints = new();
|
||||||
|
|
||||||
public Global GlobalSettings = new();
|
public Global GlobalSettings = new();
|
||||||
|
|
||||||
@@ -213,16 +213,15 @@ namespace OpenRA.Network
|
|||||||
public bool AllowSpectators = true;
|
public bool AllowSpectators = true;
|
||||||
public string GameUid;
|
public string GameUid;
|
||||||
public bool EnableSingleplayer;
|
public bool EnableSingleplayer;
|
||||||
public bool EnableMapGeneration;
|
|
||||||
public bool EnableGameSaves;
|
|
||||||
public bool EnableSyncReports;
|
public bool EnableSyncReports;
|
||||||
public bool Dedicated;
|
public bool Dedicated;
|
||||||
|
public bool GameSavesEnabled;
|
||||||
|
|
||||||
// 120ms network frame interval for 40ms local tick
|
// 120ms network frame interval for 40ms local tick
|
||||||
public int NetFrameInterval = 3;
|
public int NetFrameInterval = 3;
|
||||||
|
|
||||||
[FieldLoader.Ignore]
|
[FieldLoader.Ignore]
|
||||||
public Dictionary<string, LobbyOptionState> LobbyOptions = [];
|
public Dictionary<string, LobbyOptionState> LobbyOptions = new();
|
||||||
|
|
||||||
public static Global Deserialize(MiniYaml data)
|
public static Global Deserialize(MiniYaml data)
|
||||||
{
|
{
|
||||||
@@ -241,7 +240,7 @@ namespace OpenRA.Network
|
|||||||
var data = new MiniYamlNode("GlobalSettings", FieldSaver.Save(this));
|
var data = new MiniYamlNode("GlobalSettings", FieldSaver.Save(this));
|
||||||
var options = LobbyOptions.Select(kv => new MiniYamlNode(kv.Key, FieldSaver.Save(kv.Value)));
|
var options = LobbyOptions.Select(kv => new MiniYamlNode(kv.Key, FieldSaver.Save(kv.Value)));
|
||||||
data = data.WithValue(data.Value.WithNodesAppended(
|
data = data.WithValue(data.Value.WithNodesAppended(
|
||||||
[new MiniYamlNode("Options", new MiniYaml(null, options))]));
|
new[] { new MiniYamlNode("Options", new MiniYaml(null, options)) }));
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -166,9 +166,9 @@ namespace OpenRA.Network
|
|||||||
public int Frame;
|
public int Frame;
|
||||||
public int SyncedRandom;
|
public int SyncedRandom;
|
||||||
public int TotalCount;
|
public int TotalCount;
|
||||||
public readonly List<TraitReport> Traits = [];
|
public readonly List<TraitReport> Traits = new();
|
||||||
public readonly List<EffectReport> Effects = [];
|
public readonly List<EffectReport> Effects = new();
|
||||||
public readonly List<OrderManager.ClientOrder> Orders = [];
|
public readonly List<OrderManager.ClientOrder> Orders = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TraitReport
|
struct TraitReport
|
||||||
@@ -202,10 +202,10 @@ namespace OpenRA.Network
|
|||||||
{
|
{
|
||||||
const BindingFlags Flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
const BindingFlags Flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
||||||
var fields = type.GetFields(Flags)
|
var fields = type.GetFields(Flags)
|
||||||
.Where(fi => !fi.IsLiteral && !fi.IsStatic && fi.HasAttribute<VerifySyncAttribute>())
|
.Where(fi => !fi.IsLiteral && !fi.IsStatic && fi.HasAttribute<SyncAttribute>())
|
||||||
.ToList();
|
.ToList();
|
||||||
var properties = type.GetProperties(Flags)
|
var properties = type.GetProperties(Flags)
|
||||||
.Where(pi => pi.HasAttribute<VerifySyncAttribute>())
|
.Where(pi => pi.HasAttribute<SyncAttribute>())
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
foreach (var prop in properties)
|
foreach (var prop in properties)
|
||||||
@@ -238,11 +238,11 @@ namespace OpenRA.Network
|
|||||||
// PERF: If the member is a Boolean, we can also avoid the allocation caused by boxing it.
|
// PERF: If the member is a Boolean, we can also avoid the allocation caused by boxing it.
|
||||||
// Instead, we can just return the resulting strings directly.
|
// Instead, we can just return the resulting strings directly.
|
||||||
var getBoolString = Expression.Condition(getMember, TrueString, FalseString);
|
var getBoolString = Expression.Condition(getMember, TrueString, FalseString);
|
||||||
return Expression.Lambda<Func<ISync, string>>(getBoolString, name, [SyncParam]).Compile();
|
return Expression.Lambda<Func<ISync, string>>(getBoolString, name, new[] { SyncParam }).Compile();
|
||||||
}
|
}
|
||||||
|
|
||||||
var boxedCopy = Expression.Convert(getMember, typeof(object));
|
var boxedCopy = Expression.Convert(getMember, typeof(object));
|
||||||
return Expression.Lambda<Func<ISync, object>>(boxedCopy, name, [SyncParam]).Compile();
|
return Expression.Lambda<Func<ISync, object>>(boxedCopy, name, new[] { SyncParam }).Compile();
|
||||||
}
|
}
|
||||||
|
|
||||||
// For reference types, we have to call ToString right away to get a snapshot of the value. We cannot
|
// For reference types, we have to call ToString right away to get a snapshot of the value. We cannot
|
||||||
@@ -266,13 +266,13 @@ namespace OpenRA.Network
|
|||||||
// (ISync sync) => { var foo = ((TSync)sync).Foo; return foo == null ? null : foo.ToString(); }
|
// (ISync sync) => { var foo = ((TSync)sync).Foo; return foo == null ? null : foo.ToString(); }
|
||||||
var memberVariable = Expression.Variable(memberType, getMember.Member.Name);
|
var memberVariable = Expression.Variable(memberType, getMember.Member.Name);
|
||||||
var assignMemberVariable = Expression.Assign(memberVariable, getMember);
|
var assignMemberVariable = Expression.Assign(memberVariable, getMember);
|
||||||
var member = Expression.Block([memberVariable], assignMemberVariable);
|
var member = Expression.Block(new[] { memberVariable }, assignMemberVariable);
|
||||||
getString = Expression.Call(member, toString);
|
getString = Expression.Call(member, toString);
|
||||||
var nullMember = Expression.Constant(null, memberType);
|
var nullMember = Expression.Constant(null, memberType);
|
||||||
getString = Expression.Condition(Expression.Equal(member, nullMember), NullString, getString);
|
getString = Expression.Condition(Expression.Equal(member, nullMember), NullString, getString);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Expression.Lambda<Func<ISync, string>>(getString, name, [SyncParam]).Compile();
|
return Expression.Lambda<Func<ISync, string>>(getString, name, new[] { SyncParam }).Compile();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
*/
|
*/
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System.Collections.Frozen;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OpenRA.Server;
|
using OpenRA.Server;
|
||||||
@@ -30,6 +29,9 @@ namespace OpenRA.Network
|
|||||||
[FluentReference]
|
[FluentReference]
|
||||||
const string GameStarted = "notification-game-has-started";
|
const string GameStarted = "notification-game-has-started";
|
||||||
|
|
||||||
|
[FluentReference]
|
||||||
|
const string GameSaved = "notification-game-saved";
|
||||||
|
|
||||||
[FluentReference("player")]
|
[FluentReference("player")]
|
||||||
const string GamePaused = "notification-game-paused";
|
const string GamePaused = "notification-game-paused";
|
||||||
|
|
||||||
@@ -65,11 +67,11 @@ namespace OpenRA.Network
|
|||||||
{
|
{
|
||||||
var message = new FluentMessage(node.Value);
|
var message = new FluentMessage(node.Value);
|
||||||
if (message.Key == Joined)
|
if (message.Key == Joined)
|
||||||
TextNotificationsManager.AddPlayerJoinedLine(message.Key, message.Arguments.ToArray());
|
TextNotificationsManager.AddPlayerJoinedLine(message.Key, message.Arguments);
|
||||||
else if (message.Key == Left)
|
else if (message.Key == Left)
|
||||||
TextNotificationsManager.AddPlayerLeftLine(message.Key, message.Arguments.ToArray());
|
TextNotificationsManager.AddPlayerLeftLine(message.Key, message.Arguments);
|
||||||
else
|
else
|
||||||
TextNotificationsManager.AddSystemLine(message.Key, message.Arguments.ToArray());
|
TextNotificationsManager.AddSystemLine(message.Key, message.Arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -181,7 +183,7 @@ namespace OpenRA.Network
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(order.TargetString))
|
if (!string.IsNullOrEmpty(order.TargetString))
|
||||||
{
|
{
|
||||||
var data = MiniYaml.FromString(order.TargetString, order.OrderString).ToList();
|
var data = MiniYaml.FromString(order.TargetString, order.OrderString);
|
||||||
var saveLastOrdersFrame = data.FirstOrDefault(n => n.Key == "SaveLastOrdersFrame");
|
var saveLastOrdersFrame = data.FirstOrDefault(n => n.Key == "SaveLastOrdersFrame");
|
||||||
if (saveLastOrdersFrame != null)
|
if (saveLastOrdersFrame != null)
|
||||||
orderManager.GameSaveLastFrame =
|
orderManager.GameSaveLastFrame =
|
||||||
@@ -201,7 +203,7 @@ namespace OpenRA.Network
|
|||||||
|
|
||||||
case "SaveTraitData":
|
case "SaveTraitData":
|
||||||
{
|
{
|
||||||
var data = MiniYaml.FromString(order.TargetString, order.OrderString).First();
|
var data = MiniYaml.FromString(order.TargetString, order.OrderString)[0];
|
||||||
var traitIndex = Exts.ParseInt32Invariant(data.Key);
|
var traitIndex = Exts.ParseInt32Invariant(data.Key);
|
||||||
|
|
||||||
world?.AddGameSaveTraitData(traitIndex, data.Value);
|
world?.AddGameSaveTraitData(traitIndex, data.Value);
|
||||||
@@ -210,8 +212,11 @@ namespace OpenRA.Network
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "GameSaved":
|
case "GameSaved":
|
||||||
|
if (!orderManager.World.IsReplay)
|
||||||
|
TextNotificationsManager.AddSystemLine(GameSaved);
|
||||||
|
|
||||||
foreach (var nsr in orderManager.World.WorldActor.TraitsImplementing<INotifyGameSaved>())
|
foreach (var nsr in orderManager.World.WorldActor.TraitsImplementing<INotifyGameSaved>())
|
||||||
nsr.GameSaved(orderManager.World, order.ExtraData != 0);
|
nsr.GameSaved(orderManager.World);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "PauseGame":
|
case "PauseGame":
|
||||||
@@ -380,21 +385,7 @@ namespace OpenRA.Network
|
|||||||
|
|
||||||
case "SyncMapPool":
|
case "SyncMapPool":
|
||||||
{
|
{
|
||||||
orderManager.ServerMapPool = FieldLoader.GetValue<FrozenSet<string>>("SyncMapPool", order.TargetString);
|
orderManager.ServerMapPool = FieldLoader.GetValue<HashSet<string>>("SyncMapPool", order.TargetString);
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "GenerateMap":
|
|
||||||
{
|
|
||||||
var yaml = new MiniYaml(order.OrderString, MiniYaml.FromString(order.TargetString, order.OrderString));
|
|
||||||
var args = FieldLoader.Load<MapGenerationArgs>(yaml);
|
|
||||||
var preview = Game.ModData.MapCache[args.Uid];
|
|
||||||
if (preview.Status != MapStatus.Available && preview.Class != MapClassification.Generated)
|
|
||||||
{
|
|
||||||
preview.UpdateFromGenerationArgs(args);
|
|
||||||
preview.Generate();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
// .NET does not support unloading assemblies, so mod libraries will leak across mod changes.
|
// .NET does not support unloading assemblies, so mod libraries will leak across mod changes.
|
||||||
// This tracks the assemblies that have been loaded since game start so that we don't load multiple copies
|
// This tracks the assemblies that have been loaded since game start so that we don't load multiple copies
|
||||||
static readonly Dictionary<string, Assembly> ResolvedAssemblies = [];
|
static readonly Dictionary<string, Assembly> ResolvedAssemblies = new();
|
||||||
|
|
||||||
readonly Cache<string, Type> typeCache;
|
readonly Cache<string, Type> typeCache;
|
||||||
readonly Cache<Type, ConstructorInfo> ctorCache;
|
readonly Cache<Type, ConstructorInfo> ctorCache;
|
||||||
@@ -55,9 +55,26 @@ namespace OpenRA
|
|||||||
|
|
||||||
if (!ResolvedAssemblies.TryGetValue(hash, out var assembly))
|
if (!ResolvedAssemblies.TryGetValue(hash, out var assembly))
|
||||||
{
|
{
|
||||||
|
#if NET5_0_OR_GREATER
|
||||||
var loader = new Support.AssemblyLoader(resolvedPath);
|
var loader = new Support.AssemblyLoader(resolvedPath);
|
||||||
assembly = loader.LoadDefaultAssembly();
|
assembly = loader.LoadDefaultAssembly();
|
||||||
ResolvedAssemblies.Add(hash, assembly);
|
ResolvedAssemblies.Add(hash, assembly);
|
||||||
|
#else
|
||||||
|
assembly = Assembly.LoadFile(resolvedPath);
|
||||||
|
ResolvedAssemblies.Add(hash, assembly);
|
||||||
|
|
||||||
|
// Allow mods to use libraries.
|
||||||
|
var assemblyPath = Path.GetDirectoryName(resolvedPath);
|
||||||
|
if (assemblyPath != null)
|
||||||
|
{
|
||||||
|
foreach (var referencedAssembly in assembly.GetReferencedAssemblies())
|
||||||
|
{
|
||||||
|
var depedencyPath = Path.Combine(assemblyPath, referencedAssembly.Name + ".dll");
|
||||||
|
if (File.Exists(depedencyPath))
|
||||||
|
LoadAssembly(assemblyList, depedencyPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
assemblyList.Add(assembly);
|
assemblyList.Add(assembly);
|
||||||
@@ -77,7 +94,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
public T CreateObject<T>(string className)
|
public T CreateObject<T>(string className)
|
||||||
{
|
{
|
||||||
return CreateObject<T>(className, []);
|
return CreateObject<T>(className, new Dictionary<string, object>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public T CreateObject<T>(string className, Dictionary<string, object> args)
|
public T CreateObject<T>(string className, Dictionary<string, object> args)
|
||||||
@@ -119,7 +136,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
public object CreateBasic(Type type)
|
public object CreateBasic(Type type)
|
||||||
{
|
{
|
||||||
return type.GetConstructor([]).Invoke([]);
|
return type.GetConstructor(Array.Empty<Type>()).Invoke(Array.Empty<object>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public object CreateUsingArgs(ConstructorInfo ctor, Dictionary<string, object> args)
|
public object CreateUsingArgs(ConstructorInfo ctor, Dictionary<string, object> args)
|
||||||
|
|||||||
@@ -2,13 +2,18 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<RootNamespace>OpenRA</RootNamespace>
|
<RootNamespace>OpenRA</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup Condition="'$(MSBuildRuntimeType)'!='Mono'">
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="6.0.2" />
|
||||||
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
|
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup Condition="'$(MSBuildRuntimeType)'=='Mono'">
|
||||||
|
<PackageReference Include="System.Collections.Immutable" Version="6.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
<PackageReference Include="Linguini.Bundle" Version="0.8.1" />
|
<PackageReference Include="Linguini.Bundle" Version="0.8.1" />
|
||||||
<PackageReference Include="OpenRA-Eluant" Version="1.0.22" />
|
<PackageReference Include="OpenRA-Eluant" Version="1.0.22" />
|
||||||
<PackageReference Include="Mono.NAT" Version="3.0.4" />
|
<PackageReference Include="Mono.NAT" Version="3.0.4" />
|
||||||
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||||
<PackageReference Include="System.Threading.Channels" Version="9.0.0" />
|
<PackageReference Include="System.Threading.Channels" Version="6.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user