Compare commits
83 Commits
devtest-20
...
release-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c00ed84b25 | ||
|
|
7f7f037ffd | ||
|
|
bd8dc18bad | ||
|
|
dd50004d91 | ||
|
|
b4033c609e | ||
|
|
9800b3513e | ||
|
|
047f94613b | ||
|
|
74c69f835c | ||
|
|
bea6523f5e | ||
|
|
38dab424fb | ||
|
|
605f3b65a9 | ||
|
|
1f7a483fd6 | ||
|
|
d82549ec00 | ||
|
|
d003885bca | ||
|
|
f907403ca7 | ||
|
|
29c13a4c26 | ||
|
|
379ece7592 | ||
|
|
5b815ca2cf | ||
|
|
391c378118 | ||
|
|
bd8aeb3cae | ||
|
|
bab4cac2d7 | ||
|
|
98664ce3e5 | ||
|
|
57065de55e | ||
|
|
2dd0e05423 | ||
|
|
9b158b459c | ||
|
|
80b06d1dd2 | ||
|
|
a33321390f | ||
|
|
1607173769 | ||
|
|
bf957f09ff | ||
|
|
dbc8b0a99a | ||
|
|
799b07cd1e | ||
|
|
c83b8b712e | ||
|
|
c7adfd43a0 | ||
|
|
55c1b6a663 | ||
|
|
2693f9de1c | ||
|
|
dc684e244e | ||
|
|
8334e9adeb | ||
|
|
e55f2f3bee | ||
|
|
59ee9bcb96 | ||
|
|
b3ba4f5ca2 | ||
|
|
e628d756fb | ||
|
|
4f55edad74 | ||
|
|
43a30f6497 | ||
|
|
a84815a832 | ||
|
|
d3dae5c34f | ||
|
|
8a666b973f | ||
|
|
4a039166e4 | ||
|
|
25a63530d3 | ||
|
|
5349db0384 | ||
|
|
60244d4910 | ||
|
|
a7251b7e95 | ||
|
|
6e56349a26 | ||
|
|
cb553fbe15 | ||
|
|
955f39620b | ||
|
|
af5402d41c | ||
|
|
aaa1484643 | ||
|
|
fb7b79e8fc | ||
|
|
1f0d98344b | ||
|
|
37155ec9dd | ||
|
|
5e3f0721ce | ||
|
|
90a7d1f3e9 | ||
|
|
3971b99363 | ||
|
|
b99c4903a6 | ||
|
|
f7e5cce0ab | ||
|
|
19983e9e7f | ||
|
|
b84bd1762b | ||
|
|
9623bbebc8 | ||
|
|
7becb92a00 | ||
|
|
a812b937f3 | ||
|
|
8bbced1582 | ||
|
|
a4eb40efe4 | ||
|
|
214d7325bd | ||
|
|
be09392c35 | ||
|
|
f6e892a2dd | ||
|
|
cdd70e7daf | ||
|
|
4cf98effa4 | ||
|
|
1d8048c133 | ||
|
|
ecbedad308 | ||
|
|
31c99d8071 | ||
|
|
303a2be612 | ||
|
|
ece2f8494e | ||
|
|
23f9998c7e | ||
|
|
fcab4e9eed |
845
.editorconfig
845
.editorconfig
@@ -1,853 +1,16 @@
|
||||
; Top-most http://editorconfig.org/ file
|
||||
root = true
|
||||
charset=utf-8
|
||||
|
||||
; Unix-style newlines
|
||||
[*]
|
||||
end_of_line = LF
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
; 4-column tab indentation
|
||||
[*.yaml]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
; 4-column tab indentation and .NET coding conventions
|
||||
[*.cs]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
#### Code Style Rules
|
||||
#### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/
|
||||
|
||||
# Severity Levels: https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/configuration-options#severity-level
|
||||
# Below we enable specific rules by setting severity to warning.
|
||||
# Rules are disabled by setting severity to silent (to still allow use in IDE) or none (to prevent all use).
|
||||
# Rules are listed below with any options available.
|
||||
# Options are commented out if they match the defaults.
|
||||
|
||||
### Language Rules
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/language-rules
|
||||
|
||||
## this and Me preferences
|
||||
|
||||
# IDE0003/IDE0009 Remove 'this' or 'Me' qualification/Add 'this' or 'Me' qualification
|
||||
#dotnet_style_qualification_for_field = false
|
||||
#dotnet_style_qualification_for_property = false
|
||||
#dotnet_style_qualification_for_method = false
|
||||
#dotnet_style_qualification_for_event = false
|
||||
dotnet_diagnostic.IDE0003.severity = warning
|
||||
dotnet_diagnostic.IDE0009.severity = warning
|
||||
|
||||
## Use languages keywords for types
|
||||
|
||||
# IDE0049 Use language keywords instead of framework type names for type references
|
||||
#dotnet_style_predefined_type_for_locals_parameters_members = true
|
||||
#dotnet_style_predefined_type_for_member_access = true
|
||||
dotnet_diagnostic.IDE0049.severity = warning
|
||||
|
||||
## Modifier preferences
|
||||
|
||||
# IDE0036 Order modifiers
|
||||
#csharp_preferred_modifier_order = public, private, protected, internal, file, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, required, volatile, async
|
||||
dotnet_diagnostic.IDE0036.severity = warning
|
||||
|
||||
# IDE0040 Add accessibility modifiers
|
||||
dotnet_style_require_accessibility_modifiers = omit_if_default
|
||||
dotnet_diagnostic.IDE0040.severity = warning
|
||||
|
||||
# IDE0044 Add readonly modifier
|
||||
#dotnet_style_readonly_field = true
|
||||
dotnet_diagnostic.IDE0044.severity = warning
|
||||
|
||||
# IDE0062 Make local function static
|
||||
#csharp_prefer_static_local_function = true
|
||||
dotnet_diagnostic.IDE0062.severity = warning
|
||||
|
||||
# IDE0064 Make struct fields writable
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0064.severity = warning
|
||||
|
||||
## Parentheses preferences
|
||||
|
||||
# IDE0047/IDE0048 Remove unnecessary parentheses/Add parentheses for clarity
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary
|
||||
dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary
|
||||
#dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
|
||||
#dotnet_style_parentheses_in_other_operators = never_if_unnecessary
|
||||
dotnet_diagnostic.IDE0047.severity = warning
|
||||
dotnet_diagnostic.IDE0048.severity = warning
|
||||
|
||||
## Expression-level preferences
|
||||
|
||||
# IDE0010 Add missing cases to switch statement
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0010.severity = silent
|
||||
|
||||
# IDE0017 Use object initializers
|
||||
#dotnet_style_object_initializer = true
|
||||
dotnet_diagnostic.IDE0017.severity = warning
|
||||
|
||||
# IDE0018 Inline variable declaration
|
||||
#csharp_style_inlined_variable_declaration = true
|
||||
dotnet_diagnostic.IDE0018.severity = warning
|
||||
|
||||
# IDE0028 Use collection initializers
|
||||
#dotnet_style_collection_initializer = true
|
||||
dotnet_diagnostic.IDE0028.severity = warning
|
||||
|
||||
# IDE0032 Use auto-implemented property
|
||||
#dotnet_style_prefer_auto_properties = true
|
||||
dotnet_diagnostic.IDE0032.severity = warning
|
||||
|
||||
# IDE0033 Use explicitly provided tuple name
|
||||
#dotnet_style_explicit_tuple_names = true
|
||||
dotnet_diagnostic.IDE0033.severity = warning
|
||||
|
||||
# IDE0034 Simplify 'default' expression
|
||||
#csharp_prefer_simple_default_expression = true
|
||||
dotnet_diagnostic.IDE0034.severity = warning
|
||||
|
||||
# IDE0037 Use inferred member name
|
||||
#dotnet_style_prefer_inferred_tuple_names = true
|
||||
#dotnet_style_prefer_inferred_anonymous_type_member_names = true
|
||||
dotnet_diagnostic.IDE0037.severity = silent
|
||||
|
||||
# IDE0039 Use local function instead of lambda
|
||||
#csharp_style_prefer_local_over_anonymous_function = true
|
||||
dotnet_diagnostic.IDE0039.severity = warning
|
||||
|
||||
# IDE0042 Deconstruct variable declaration
|
||||
#csharp_style_deconstructed_variable_declaration = true
|
||||
dotnet_diagnostic.IDE0042.severity = warning
|
||||
|
||||
# IDE0045 Use conditional expression for assignment
|
||||
#dotnet_style_prefer_conditional_expression_over_assignment = true
|
||||
dotnet_diagnostic.IDE0045.severity = silent
|
||||
|
||||
# IDE0046 Use conditional expression for return
|
||||
#dotnet_style_prefer_conditional_expression_over_return = true
|
||||
dotnet_diagnostic.IDE0046.severity = silent
|
||||
|
||||
# IDE0050 Convert anonymous type to tuple
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0050.severity = silent
|
||||
|
||||
# IDE0054/IDE0074 Use compound assignment/Use coalesce compound assignment
|
||||
#dotnet_style_prefer_compound_assignment = true
|
||||
dotnet_diagnostic.IDE0054.severity = warning
|
||||
dotnet_diagnostic.IDE0074.severity = warning
|
||||
|
||||
# IDE0056 Use index operator
|
||||
#csharp_style_prefer_index_operator = true
|
||||
dotnet_diagnostic.IDE0056.severity = warning
|
||||
|
||||
# IDE0057 Use range operator
|
||||
#csharp_style_prefer_range_operator = true
|
||||
dotnet_diagnostic.IDE0057.severity = warning
|
||||
|
||||
# IDE0070 Use 'System.HashCode.Combine'
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0070.severity = warning
|
||||
|
||||
# IDE0071 Simplify interpolation
|
||||
#dotnet_style_prefer_simplified_interpolation = true
|
||||
dotnet_diagnostic.IDE0071.severity = warning
|
||||
|
||||
# IDE0072 Add missing cases to switch expression
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0072.severity = silent
|
||||
|
||||
# IDE0075 Simplify conditional expression
|
||||
#dotnet_style_prefer_simplified_boolean_expressions = true
|
||||
dotnet_diagnostic.IDE0075.severity = warning
|
||||
|
||||
# IDE0082 Convert 'typeof' to 'nameof'
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0082.severity = warning
|
||||
|
||||
# IDE0090 Simplify 'new' expression
|
||||
#csharp_style_implicit_object_creation_when_type_is_apparent = true
|
||||
dotnet_diagnostic.IDE0090.severity = warning
|
||||
|
||||
# IDE0180 Use tuple to swap values
|
||||
#csharp_style_prefer_tuple_swap = true
|
||||
dotnet_diagnostic.IDE0180.severity = warning
|
||||
|
||||
## Namespace declaration preferences
|
||||
|
||||
# IDE0160/IDE0161 Use block-scoped namespace/Use file-scoped namespace
|
||||
#csharp_style_namespace_declarations = block_scoped
|
||||
dotnet_diagnostic.IDE0160.severity = warning
|
||||
dotnet_diagnostic.IDE0161.severity = warning
|
||||
|
||||
## Null-checking preferences
|
||||
|
||||
# IDE0016 Use throw expression
|
||||
#csharp_style_throw_expression = true
|
||||
dotnet_diagnostic.IDE0016.severity = silent
|
||||
|
||||
# IDE0029/IDE0030/IDE0270 Use coalesce expression (non-nullable types)/Use coalesce expression (nullable types)/Use coalesce expression (if null)
|
||||
#dotnet_style_coalesce_expression = true
|
||||
dotnet_diagnostic.IDE0029.severity = warning
|
||||
dotnet_diagnostic.IDE0030.severity = warning
|
||||
dotnet_diagnostic.IDE0270.severity = silent
|
||||
|
||||
# IDE0031 Use null propagation
|
||||
#dotnet_style_null_propagation = true
|
||||
dotnet_diagnostic.IDE0031.severity = warning
|
||||
|
||||
# IDE0041 Use 'is null' check
|
||||
#dotnet_style_prefer_is_null_check_over_reference_equality_method = true
|
||||
dotnet_diagnostic.IDE0041.severity = warning
|
||||
|
||||
# IDE0150 Prefer 'null' check over type check
|
||||
#csharp_style_prefer_null_check_over_type_check = true
|
||||
dotnet_diagnostic.IDE0150.severity = warning
|
||||
|
||||
# IDE1005 Use conditional delegate call
|
||||
csharp_style_conditional_delegate_call = true # true is the default, but the rule is not triggered if this is not specified.
|
||||
dotnet_diagnostic.IDE1005.severity = warning
|
||||
|
||||
## var preferences
|
||||
|
||||
# IDE0007/IDE0008 Use 'var' instead of explicit type/Use explicit type instead of 'var'
|
||||
csharp_style_var_for_built_in_types = true
|
||||
csharp_style_var_when_type_is_apparent = true
|
||||
csharp_style_var_elsewhere = true
|
||||
dotnet_diagnostic.IDE0007.severity = warning
|
||||
dotnet_diagnostic.IDE0008.severity = warning
|
||||
|
||||
## Expression-bodied-members
|
||||
|
||||
# IDE0021 Use expression body for constructors
|
||||
#csharp_style_expression_bodied_constructors = false
|
||||
dotnet_diagnostic.IDE0021.severity = silent
|
||||
|
||||
# IDE0022 Use expression body for methods
|
||||
#csharp_style_expression_bodied_methods = false
|
||||
dotnet_diagnostic.IDE0022.severity = silent
|
||||
|
||||
# IDE0023/IDE0024 Use expression body for conversion operators/Use expression body for operators
|
||||
#csharp_style_expression_bodied_operators = false
|
||||
dotnet_diagnostic.IDE0023.severity = silent
|
||||
dotnet_diagnostic.IDE0024.severity = silent
|
||||
|
||||
# IDE0025 Use expression body for properties
|
||||
#csharp_style_expression_bodied_properties = true
|
||||
dotnet_diagnostic.IDE0025.severity = silent
|
||||
|
||||
# IDE0026 Use expression body for indexers
|
||||
#csharp_style_expression_bodied_indexers = true
|
||||
dotnet_diagnostic.IDE0026.severity = silent
|
||||
|
||||
# IDE0027 Use expression body for accessors
|
||||
#csharp_style_expression_bodied_accessors = true
|
||||
dotnet_diagnostic.IDE0027.severity = warning
|
||||
|
||||
# IDE0053 Use expression body for lambdas
|
||||
# This rule is buggy and not enforced for builds. ':warning' will at least enforce it in the IDE.
|
||||
csharp_style_expression_bodied_lambdas = when_on_single_line:warning
|
||||
dotnet_diagnostic.IDE0053.severity = warning
|
||||
|
||||
# IDE0061 Use expression body for local functions
|
||||
csharp_style_expression_bodied_local_functions = when_on_single_line
|
||||
dotnet_diagnostic.IDE0061.severity = warning
|
||||
|
||||
## Pattern matching preferences
|
||||
|
||||
# IDE0019 Use pattern matching to avoid 'as' followed by a 'null' check
|
||||
#csharp_style_pattern_matching_over_as_with_null_check = true
|
||||
dotnet_diagnostic.IDE0019.severity = warning
|
||||
|
||||
# IDE0020/IDE0038 Use pattern matching to avoid 'is' check followed by a cast (with variable)/Use pattern matching to avoid 'is' check followed by a cast (without variable)
|
||||
#csharp_style_pattern_matching_over_is_with_cast_check = true
|
||||
dotnet_diagnostic.IDE0020.severity = warning
|
||||
dotnet_diagnostic.IDE0038.severity = warning
|
||||
|
||||
# IDE0066 Use switch expression
|
||||
#csharp_style_prefer_switch_expression = true
|
||||
dotnet_diagnostic.IDE0066.severity = silent
|
||||
|
||||
# IDE0078 Use pattern matching
|
||||
#csharp_style_prefer_pattern_matching = true
|
||||
dotnet_diagnostic.IDE0078.severity = silent
|
||||
|
||||
# IDE0083 Use pattern matching ('not' operator)
|
||||
#csharp_style_prefer_not_pattern = true
|
||||
dotnet_diagnostic.IDE0083.severity = warning
|
||||
|
||||
# IDE0170 Simplify property pattern
|
||||
#csharp_style_prefer_extended_property_pattern = true
|
||||
dotnet_diagnostic.IDE0170.severity = silent # Requires C# 10
|
||||
|
||||
## Code block preferences
|
||||
|
||||
# IDE0011 Add braces
|
||||
#csharp_prefer_braces = true
|
||||
# No options match the style used in OpenRA.
|
||||
dotnet_diagnostic.IDE0011.severity = none
|
||||
|
||||
# IDE0063 Use simple 'using' statement
|
||||
#csharp_prefer_simple_using_statement = true
|
||||
dotnet_diagnostic.IDE0063.severity = silent
|
||||
|
||||
## 'using' directive preferences
|
||||
|
||||
# IDE0065 'using' directive placement
|
||||
#csharp_using_directive_placement = outside_namespace
|
||||
dotnet_diagnostic.IDE0065.severity = silent
|
||||
|
||||
## File header preferences
|
||||
|
||||
# IDE0073 Require file header
|
||||
#file_header_template = unset
|
||||
# This rule does not allow us to enforce our desired header, as it prefixes the header lines with // comments, meaning we can't apply a region.
|
||||
dotnet_diagnostic.IDE0073.severity = none
|
||||
|
||||
## Namespace naming preferences
|
||||
|
||||
# IDE0130 Namespace does not match folder structure
|
||||
#dotnet_style_namespace_match_folder = true
|
||||
# This rule doesn't appear to work (never reports violations)
|
||||
dotnet_diagnostic.IDE0130.severity = none
|
||||
|
||||
### Unnecessary Code Rules
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/unnecessary-code-rules
|
||||
|
||||
# IDE0001 Simplify name
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0001.severity = warning
|
||||
|
||||
# IDE0002 Simplify member access
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0002.severity = warning
|
||||
|
||||
# IDE0004 Remove unnecessary cast
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0004.severity = warning
|
||||
|
||||
# IDE0005 Remove unnecessary import
|
||||
# No options
|
||||
# IDE0005 is only enabled in the IDE by default. https://github.com/dotnet/roslyn/issues/41640
|
||||
# To enable it for builds outside the IDE the 'GenerateDocumentationFile' property must be enabled on the build.
|
||||
# GenerateDocumentationFile generates additional warnings about XML docs, so disable any we don't care about.
|
||||
dotnet_diagnostic.CS1591.severity = none # Missing XML comment for publicly visible type or member
|
||||
dotnet_diagnostic.IDE0005.severity = warning
|
||||
|
||||
# IDE0035 Remove unreachable code
|
||||
# No options
|
||||
# Duplicates compiler warning CS0162
|
||||
dotnet_diagnostic.IDE0035.severity = none
|
||||
|
||||
# IDE0051 Remove unused private member
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0051.severity = warning
|
||||
|
||||
# IDE0052 Remove unread private member
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0052.severity = warning
|
||||
|
||||
# IDE0058 Remove unnecessary expression value
|
||||
#csharp_style_unused_value_expression_statement_preference = discard_variable
|
||||
dotnet_diagnostic.IDE0058.severity = silent
|
||||
|
||||
# IDE0059 Remove unnecessary value assignment
|
||||
#csharp_style_unused_value_assignment_preference = discard_variable
|
||||
dotnet_diagnostic.IDE0059.severity = warning
|
||||
|
||||
# IDE0060 Remove unused parameter
|
||||
dotnet_code_quality_unused_parameters = non_public
|
||||
dotnet_diagnostic.IDE0060.severity = warning
|
||||
|
||||
# IDE0079 Remove unnecessary suppression
|
||||
#dotnet_remove_unnecessary_suppression_exclusions = none
|
||||
dotnet_diagnostic.IDE0079.severity = warning
|
||||
|
||||
# IDE0080 Remove unnecessary suppression operator
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0080.severity = warning
|
||||
|
||||
# IDE0100 Remove unnecessary equality operator
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0100.severity = warning
|
||||
|
||||
# IDE0110 Remove unnecessary discard
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0110.severity = warning
|
||||
|
||||
### Miscellaneous Rules
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/miscellaneous-rules
|
||||
|
||||
# IDE0076 Remove invalid global 'SuppressMessageAttribute'
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0076.severity = warning
|
||||
|
||||
# IDE0077 Avoid legacy format target in global 'SuppressMessageAttribute'
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0077.severity = warning
|
||||
|
||||
### Formatting Rules (IDE0055)
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0055
|
||||
|
||||
# We may eventually wish to enforce this rule, however some existing formatting conflicts with the rule despite being reasonable.
|
||||
# Additionally, the rule is buggy and likes to report spuriously after invoking Format Document in the IDE.
|
||||
dotnet_diagnostic.IDE0055.severity = none
|
||||
|
||||
#dotnet_sort_system_directives_first = true
|
||||
#dotnet_separate_import_directive_groups = false
|
||||
#dotnet_style_namespace_match_folder = true
|
||||
|
||||
#csharp_new_line_before_open_brace = all
|
||||
#csharp_new_line_before_else = true
|
||||
#csharp_new_line_before_catch = true
|
||||
#csharp_new_line_before_finally = true
|
||||
#csharp_new_line_before_members_in_object_initializers = true
|
||||
#csharp_new_line_before_members_in_anonymous_types = true
|
||||
#csharp_new_line_between_query_expression_clauses = true
|
||||
|
||||
#csharp_indent_case_contents = true
|
||||
#csharp_indent_switch_labels = true
|
||||
#csharp_indent_labels = one_less_than_current
|
||||
#csharp_indent_block_contents = true
|
||||
#csharp_indent_braces = false
|
||||
#csharp_indent_case_contents_when_block = true
|
||||
|
||||
#csharp_space_after_cast = false
|
||||
#csharp_space_after_keywords_in_control_flow_statements = true
|
||||
#csharp_space_between_parentheses =
|
||||
#csharp_space_before_colon_in_inheritance_clause = true
|
||||
#csharp_space_after_colon_in_inheritance_clause = true
|
||||
#csharp_space_around_binary_operators = before_and_after
|
||||
#csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
#csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
#csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||
#csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
#csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
#csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
#csharp_space_after_comma = true
|
||||
#csharp_space_before_comma = false
|
||||
#csharp_space_after_dot = false
|
||||
#csharp_space_before_dot = false
|
||||
#csharp_space_after_semicolon_in_for_statement = true
|
||||
#csharp_space_before_semicolon_in_for_statement = false
|
||||
#csharp_space_around_declaration_statements = false
|
||||
#csharp_space_before_open_square_brackets = false
|
||||
#csharp_space_between_empty_square_brackets = false
|
||||
#csharp_space_between_square_brackets = false
|
||||
|
||||
#csharp_preserve_single_line_statements = true
|
||||
#csharp_preserve_single_line_blocks = true
|
||||
|
||||
|
||||
### Naming Rules (IDE1006)
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/naming-rules
|
||||
dotnet_diagnostic.IDE1006.severity = warning
|
||||
|
||||
## Naming styles
|
||||
|
||||
dotnet_naming_style.camel_case.capitalization = camel_case
|
||||
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.i_prefix_pascal_case.capitalization = pascal_case
|
||||
dotnet_naming_style.i_prefix_pascal_case.required_prefix = I
|
||||
|
||||
## Naming Symbols
|
||||
|
||||
dotnet_naming_symbols.const_locals.applicable_kinds = local
|
||||
dotnet_naming_symbols.const_locals.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.const_locals.required_modifiers = const
|
||||
|
||||
dotnet_naming_symbols.const_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.const_fields.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.const_fields.required_modifiers = const
|
||||
|
||||
dotnet_naming_symbols.static_readonly_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.static_readonly_fields.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.static_readonly_fields.required_modifiers = static, readonly
|
||||
|
||||
dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, internal, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly
|
||||
|
||||
dotnet_naming_symbols.private_or_protected_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_or_protected_fields.applicable_accessibilities = private, protected, private_protected
|
||||
|
||||
dotnet_naming_symbols.interfaces.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interfaces.applicable_accessibilities = *
|
||||
|
||||
dotnet_naming_symbols.parameters_and_locals.applicable_kinds = parameter, local
|
||||
dotnet_naming_symbols.parameters_and_locals.applicable_accessibilities = *
|
||||
|
||||
dotnet_naming_symbols.most_symbols.applicable_kinds = namespace, class, struct, enum, field, property, method, local_function, event, delegate, type_parameter
|
||||
dotnet_naming_symbols.most_symbols.applicable_accessibilities = *
|
||||
|
||||
## Naming Rules
|
||||
|
||||
dotnet_naming_rule.const_locals_should_be_pascal_case.symbols = const_locals
|
||||
dotnet_naming_rule.const_locals_should_be_pascal_case.style = pascal_case
|
||||
dotnet_naming_rule.const_locals_should_be_pascal_case.severity = warning
|
||||
|
||||
dotnet_naming_rule.const_fields_should_be_pascal_case.symbols = const_fields
|
||||
dotnet_naming_rule.const_fields_should_be_pascal_case.style = pascal_case
|
||||
dotnet_naming_rule.const_fields_should_be_pascal_case.severity = warning
|
||||
|
||||
dotnet_naming_rule.static_readonly_fields_should_be_pascal_case.symbols = static_readonly_fields
|
||||
dotnet_naming_rule.static_readonly_fields_should_be_pascal_case.style = pascal_case
|
||||
dotnet_naming_rule.static_readonly_fields_should_be_pascal_case.severity = warning
|
||||
|
||||
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.symbols = non_private_readonly_fields
|
||||
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.style = pascal_case
|
||||
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.severity = warning
|
||||
|
||||
dotnet_naming_rule.private_or_protected_fields_should_be_camel_case.symbols = private_or_protected_fields
|
||||
dotnet_naming_rule.private_or_protected_fields_should_be_camel_case.style = camel_case
|
||||
dotnet_naming_rule.private_or_protected_fields_should_be_camel_case.severity = warning
|
||||
|
||||
dotnet_naming_rule.interfaces_should_be_i_prefix_pascal_case.symbols = interfaces
|
||||
dotnet_naming_rule.interfaces_should_be_i_prefix_pascal_case.style = i_prefix_pascal_case
|
||||
dotnet_naming_rule.interfaces_should_be_i_prefix_pascal_case.severity = warning
|
||||
|
||||
dotnet_naming_rule.parameters_and_locals_should_be_camel_case.symbols = parameters_and_locals
|
||||
dotnet_naming_rule.parameters_and_locals_should_be_camel_case.style = camel_case
|
||||
dotnet_naming_rule.parameters_and_locals_should_be_camel_case.severity = warning
|
||||
|
||||
dotnet_naming_rule.most_symbols_should_be_pascal_case.symbols = most_symbols
|
||||
dotnet_naming_rule.most_symbols_should_be_pascal_case.style = pascal_case
|
||||
dotnet_naming_rule.most_symbols_should_be_pascal_case.severity = warning
|
||||
|
||||
|
||||
### StyleCop.Analyzers
|
||||
### https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/DOCUMENTATION.md
|
||||
|
||||
# Below we enable rule categories by setting severity to warning.
|
||||
# We'll only list rules to disable.
|
||||
# Individual rules we wish to disable are typically set to none severity.
|
||||
|
||||
# Covers SAxxxx and SXxxxx rules
|
||||
dotnet_analyzer_diagnostic.category-StyleCop.CSharp.DocumentationRules.severity = warning
|
||||
dotnet_analyzer_diagnostic.category-StyleCop.CSharp.LayoutRules.severity = warning
|
||||
dotnet_analyzer_diagnostic.category-StyleCop.CSharp.MaintainabilityRules.severity = warning
|
||||
dotnet_analyzer_diagnostic.category-StyleCop.CSharp.NamingRules.severity = warning
|
||||
dotnet_analyzer_diagnostic.category-StyleCop.CSharp.OrderingRules.severity = warning
|
||||
dotnet_analyzer_diagnostic.category-StyleCop.CSharp.ReadabilityRules.severity = warning
|
||||
dotnet_analyzer_diagnostic.category-StyleCop.CSharp.SpacingRules.severity = warning
|
||||
dotnet_analyzer_diagnostic.category-StyleCop.CSharp.SpecialRules.severity = warning
|
||||
|
||||
# Rules that are covered by IDE0001 Simplify name
|
||||
dotnet_diagnostic.SA1125.severity = none # UseShorthandForNullableTypes
|
||||
|
||||
# Rules that are covered by IDE0047 Remove unnecessary parentheses
|
||||
dotnet_diagnostic.SA1119.severity = none # StatementMustNotUseUnnecessaryParenthesis
|
||||
|
||||
# Rules that are covered by IDE0055 Formatting Rules
|
||||
dotnet_diagnostic.SA1027.severity = none # UseTabsCorrectly
|
||||
|
||||
# Rules that are covered by IDE1006 Naming Rules
|
||||
dotnet_diagnostic.SA1300.severity = none # ElementMustBeginWithUpperCaseLetter
|
||||
dotnet_diagnostic.SA1302.severity = none # InterfaceNamesMustBeginWithI
|
||||
dotnet_diagnostic.SA1303.severity = none # ConstFieldNamesMustBeginWithUpperCaseLetter
|
||||
dotnet_diagnostic.SA1304.severity = none # NonPrivateReadonlyFieldsMustBeginWithUpperCaseLetter
|
||||
dotnet_diagnostic.SA1306.severity = none # FieldNamesMustBeginWithLowerCaseLetter
|
||||
dotnet_diagnostic.SA1307.severity = none # AccessibleFieldsMustBeginWithUpperCaseLetter
|
||||
dotnet_diagnostic.SA1311.severity = none # StaticReadonlyFieldsMustBeginWithUpperCaseLetter
|
||||
dotnet_diagnostic.SA1312.severity = none # VariableNamesMustBeginWithLowerCaseLetter
|
||||
dotnet_diagnostic.SA1313.severity = none # ParameterNamesMustBeginWithLowerCaseLetter
|
||||
|
||||
# Rules that conflict with OpenRA project style conventions
|
||||
dotnet_diagnostic.SA1101.severity = none # PrefixLocalCallsWithThis
|
||||
dotnet_diagnostic.SA1107.severity = none # CodeMustNotContainMultipleStatementsOnOneLine
|
||||
dotnet_diagnostic.SA1116.severity = none # SplitParametersMustStartOnLineAfterDeclaration
|
||||
dotnet_diagnostic.SA1117.severity = none # ParametersMustBeOnSameLineOrSeparateLines
|
||||
dotnet_diagnostic.SA1118.severity = none # ParameterMustNotSpanMultipleLines
|
||||
dotnet_diagnostic.SA1122.severity = none # UseStringEmptyForEmptyStrings
|
||||
dotnet_diagnostic.SA1124.severity = none # DoNotUseRegions
|
||||
dotnet_diagnostic.SA1127.severity = none # GenericTypeConstraintsMustBeOnOwnLine
|
||||
dotnet_diagnostic.SA1132.severity = none # DoNotCombineFields
|
||||
dotnet_diagnostic.SA1135.severity = none # UsingDirectivesMustBeQualified
|
||||
dotnet_diagnostic.SA1136.severity = none # EnumValuesShouldBeOnSeparateLines
|
||||
dotnet_diagnostic.SA1200.severity = none # UsingDirectivesMustBePlacedCorrectly
|
||||
dotnet_diagnostic.SA1201.severity = none # ElementsMustAppearInTheCorrectOrder
|
||||
dotnet_diagnostic.SA1202.severity = none # ElementsMustBeOrderedByAccess
|
||||
dotnet_diagnostic.SA1204.severity = none # StaticElementsMustAppearBeforeInstanceElements
|
||||
dotnet_diagnostic.SA1214.severity = none # ReadonlyElementsMustAppearBeforeNonReadonlyElements
|
||||
dotnet_diagnostic.SX1309.severity = none # FieldNamesMustBeginWithUnderscore
|
||||
dotnet_diagnostic.SX1309S.severity = none # StaticFieldNamesMustBeginWithUnderscore
|
||||
dotnet_diagnostic.SA1314.severity = none # TypeParameterNamesMustBeginWithT
|
||||
dotnet_diagnostic.SA1400.severity = none # AccessModifierMustBeDeclared
|
||||
dotnet_diagnostic.SA1401.severity = none # FieldsMustBePrivate
|
||||
dotnet_diagnostic.SA1402.severity = none # FileMayOnlyContainASingleType
|
||||
dotnet_diagnostic.SA1407.severity = none # ArithmeticExpressionsMustDeclarePrecedence
|
||||
dotnet_diagnostic.SA1413.severity = none # UseTrailingCommasInMultiLineInitializers
|
||||
dotnet_diagnostic.SA1501.severity = none # StatementMustNotBeOnSingleLine
|
||||
dotnet_diagnostic.SA1502.severity = none # ElementMustNotBeOnSingleLine
|
||||
dotnet_diagnostic.SA1503.severity = none # BracesMustNotBeOmitted
|
||||
dotnet_diagnostic.SA1516.severity = none # ElementsMustBeSeparatedByBlankLine
|
||||
dotnet_diagnostic.SA1519.severity = none # BracesMustNotBeOmittedFromMultiLineChildStatement
|
||||
dotnet_diagnostic.SA1520.severity = none # UseBracesConsistently
|
||||
dotnet_diagnostic.SA1600.severity = none # ElementsMustBeDocumented
|
||||
dotnet_diagnostic.SA1601.severity = none # PartialElementsMustBeDocumented
|
||||
dotnet_diagnostic.SA1602.severity = none # EnumerationItemsMustBeDocumented
|
||||
dotnet_diagnostic.SA1611.severity = none # ElementParametersShouldBeDocumented
|
||||
dotnet_diagnostic.SA1615.severity = none # ElementReturnValueShouldBeDocumented
|
||||
dotnet_diagnostic.SA1618.severity = none # GenericTypeParametersShouldBeDocumented
|
||||
dotnet_diagnostic.SA1623.severity = none # PropertySummaryDocumentationShouldMatchAccessors
|
||||
dotnet_diagnostic.SA1633.severity = none # FileMustHaveHeader
|
||||
dotnet_diagnostic.SA1642.severity = none # ConstructorSummaryDocumentationShouldBeginWithStandardText
|
||||
dotnet_diagnostic.SA1649.severity = none # FileNameMustMatchTypeName
|
||||
|
||||
#### Code Quality Rules
|
||||
#### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/
|
||||
|
||||
# Below we enable specific rules by setting severity to warning.
|
||||
# Rules are listed below with any options available.
|
||||
# Options are commented out if they match the defaults.
|
||||
|
||||
# Rule options that apply over multiple rules are set here.
|
||||
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/code-quality-rule-options
|
||||
dotnet_code_quality.api_surface = all
|
||||
|
||||
### Design Rules
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/design-warnings
|
||||
|
||||
# Provide ObsoleteAttribute message.
|
||||
dotnet_diagnostic.CA1041.severity = warning
|
||||
|
||||
# Do not declare protected members in sealed types.
|
||||
dotnet_diagnostic.CA1047.severity = warning
|
||||
|
||||
# Declare types in namespaces.
|
||||
dotnet_diagnostic.CA1050.severity = warning
|
||||
|
||||
# Do not hide base class methods.
|
||||
dotnet_diagnostic.CA1061.severity = warning
|
||||
|
||||
# Override 'Equals' when implementing 'IEquatable'.
|
||||
dotnet_diagnostic.CA1067.severity = warning
|
||||
|
||||
# 'CancellationToken' parameters must come last.
|
||||
dotnet_diagnostic.CA1068.severity = warning
|
||||
|
||||
# Do not declare event fields as virtual.
|
||||
dotnet_diagnostic.CA1070.severity = warning
|
||||
|
||||
### Documentation Rules
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/documentation-warnings
|
||||
|
||||
# Avoid using 'cref' tags with a prefix.
|
||||
dotnet_diagnostic.CA1200.severity = warning
|
||||
|
||||
### Globalization Rules
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/globalization-warnings
|
||||
|
||||
### Portability and Interoperability Rules
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/interoperability-warnings
|
||||
|
||||
# Do not use 'OutAttribute' on string parameters for P/Invokes.
|
||||
dotnet_diagnostic.CA1417.severity = warning
|
||||
|
||||
### Maintainability Rules
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/maintainability-warnings
|
||||
|
||||
# Use 'nameof' in place of string.
|
||||
dotnet_diagnostic.CA1507.severity = warning
|
||||
|
||||
### Naming Rules
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/naming-warnings
|
||||
|
||||
# Do not prefix enum values with type name.
|
||||
dotnet_code_quality.CA1712.enum_values_prefix_trigger = AnyEnumValue
|
||||
dotnet_diagnostic.CA1712.severity = warning
|
||||
|
||||
# Flags enums should have plural names.
|
||||
dotnet_diagnostic.CA1714.severity = warning
|
||||
|
||||
# Only 'FlagsAttribute' enums should have plural names.
|
||||
dotnet_diagnostic.CA1717.severity = warning
|
||||
|
||||
### Performance Rules
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/performance-warnings
|
||||
|
||||
# Remove empty finalizers.
|
||||
dotnet_diagnostic.CA1821.severity = warning
|
||||
|
||||
# Avoid unused private fields.
|
||||
dotnet_diagnostic.CA1823.severity = warning
|
||||
|
||||
# Avoid zero-length array allocations.
|
||||
dotnet_diagnostic.CA1825.severity = warning
|
||||
|
||||
# Use property instead of Linq Enumerable method.
|
||||
#dotnet_code_quality.CA1826.exclude_ordefault_methods = false
|
||||
dotnet_diagnostic.CA1826.severity = warning
|
||||
|
||||
# Do not use Count/LongCount when Any can be used.
|
||||
dotnet_diagnostic.CA1827.severity = warning
|
||||
|
||||
# Do not use CountAsync/LongCountAsync when AnyAsync can be used.
|
||||
dotnet_diagnostic.CA1828.severity = warning
|
||||
|
||||
# Use Length/Count property instead of Enumerable.Count method.
|
||||
dotnet_diagnostic.CA1829.severity = warning
|
||||
|
||||
# Prefer strongly-typed Append and Insert method overloads on StringBuilder.
|
||||
dotnet_diagnostic.CA1830.severity = warning
|
||||
|
||||
# Use AsSpan instead of Range-based indexers for string when appropriate.
|
||||
dotnet_diagnostic.CA1831.severity = warning
|
||||
|
||||
# Use AsSpan or AsMemory instead of Range-based indexers for getting ReadOnlySpan or ReadOnlyMemory portion of an array.
|
||||
dotnet_diagnostic.CA1832.severity = warning
|
||||
|
||||
# Use AsSpan or AsMemory instead of Range-based indexers for getting Span or Memory portion of an array.
|
||||
dotnet_diagnostic.CA1833.severity = warning
|
||||
|
||||
# Prefer the memory-based overloads of ReadAsync/WriteAsync methods in stream-based classes.
|
||||
dotnet_diagnostic.CA1835.severity = warning
|
||||
|
||||
# Prefer IsEmpty over Count when available.
|
||||
dotnet_diagnostic.CA1836.severity = warning
|
||||
|
||||
# Use Environment.ProcessId instead of Process.GetCurrentProcess().Id.
|
||||
dotnet_diagnostic.CA1837.severity = warning
|
||||
|
||||
# Avoid StringBuilder parameters for P/Invokes.
|
||||
dotnet_diagnostic.CA1838.severity = warning
|
||||
|
||||
# Use Environment.CurrentManagedThreadId instead of Thread.CurrentThread.ManagedThreadId.
|
||||
dotnet_diagnostic.CA1840.severity = warning
|
||||
|
||||
# Do not use 'WhenAll' with a single task.
|
||||
dotnet_diagnostic.CA1842.severity = warning
|
||||
|
||||
# Do not use 'WaitAll' with a single task.
|
||||
dotnet_diagnostic.CA1843.severity = warning
|
||||
|
||||
# Provide memory-based overrides of async methods when subclassing 'Stream'.
|
||||
dotnet_diagnostic.CA1844.severity = warning
|
||||
|
||||
# Use span-based 'string.Concat'.
|
||||
dotnet_diagnostic.CA1845.severity = warning
|
||||
|
||||
# Use string.Contains(char) instead of string.Contains(string) with single characters.
|
||||
dotnet_diagnostic.CA1847.severity = warning
|
||||
|
||||
# Unnecessary call to 'Dictionary.ContainsKey(key)'.
|
||||
dotnet_diagnostic.CA1853.severity = warning
|
||||
|
||||
# Use Span<T>.Clear() instead of Span<T>.Fill().
|
||||
dotnet_diagnostic.CA1855.severity = warning
|
||||
|
||||
# Use StartsWith instead of IndexOf.
|
||||
dotnet_diagnostic.CA1858.severity = warning
|
||||
|
||||
# Avoid using 'Enumerable.Any()' extension method.
|
||||
dotnet_diagnostic.CA1860.severity = warning
|
||||
|
||||
### Reliability Rules
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/reliability-warnings
|
||||
|
||||
# Do not assign property within its setter.
|
||||
dotnet_diagnostic.CA2011.severity = warning
|
||||
|
||||
# Use ValueTasks correctly.
|
||||
dotnet_diagnostic.CA2012.severity = warning
|
||||
|
||||
# Do not use ReferenceEquals with value types.
|
||||
dotnet_diagnostic.CA2013.severity = warning
|
||||
|
||||
# Do not use stackalloc in loops.
|
||||
dotnet_diagnostic.CA2014.severity = warning
|
||||
|
||||
# Forward the CancellationToken parameter to methods that take one.
|
||||
dotnet_diagnostic.CA2016.severity = warning
|
||||
|
||||
# The 'count' argument to Buffer.BlockCopy should specify the number of bytes to copy.
|
||||
dotnet_diagnostic.CA2018.severity = warning
|
||||
|
||||
### Security Rules
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/security-warnings
|
||||
|
||||
# Do Not Use Broken Cryptographic Algorithms.
|
||||
dotnet_diagnostic.CA5351.severity = warning
|
||||
|
||||
### Usage Rules
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/usage-warnings
|
||||
|
||||
# Rethrow to preserve stack details.
|
||||
dotnet_diagnostic.CA2200.severity = warning
|
||||
|
||||
# Initialize value type static fields inline.
|
||||
dotnet_diagnostic.CA2207.severity = warning
|
||||
|
||||
# Override GetHashCode on overriding Equals.
|
||||
dotnet_diagnostic.CA2218.severity = warning
|
||||
|
||||
# Overload operator equals on overriding ValueType.Equals.
|
||||
dotnet_diagnostic.CA2231.severity = warning
|
||||
|
||||
# Provide correct arguments to formatting methods.
|
||||
#dotnet_code_quality.CA2241.additional_string_formatting_methods =
|
||||
dotnet_code_quality.CA2241.try_determine_additional_string_formatting_methods_automatically = true
|
||||
dotnet_diagnostic.CA2241.severity = warning
|
||||
|
||||
# Test for NaN correctly.
|
||||
dotnet_diagnostic.CA2242.severity = warning
|
||||
|
||||
# Attribute string literals should parse correctly.
|
||||
dotnet_diagnostic.CA2243.severity = warning
|
||||
|
||||
# Do not duplicate indexed element initializations.
|
||||
dotnet_diagnostic.CA2244.severity = warning
|
||||
|
||||
# Do not assign a property to itself.
|
||||
dotnet_diagnostic.CA2245.severity = warning
|
||||
|
||||
# Argument passed to TaskCompletionSource constructor should be TaskCreationOptions enum instead of TaskContinuationOptions enum.
|
||||
dotnet_diagnostic.CA2247.severity = warning
|
||||
|
||||
# Provide correct enum argument to Enum.HasFlag.
|
||||
dotnet_diagnostic.CA2248.severity = warning
|
||||
|
||||
# Use ThrowIfCancellationRequested.
|
||||
dotnet_diagnostic.CA2250.severity = warning
|
||||
|
||||
# Ensure ThreadStatic is only used with static fields.
|
||||
dotnet_diagnostic.CA2259.severity = warning
|
||||
|
||||
### Roslynator
|
||||
### https://github.com/JosefPihrt/Roslynator/tree/main/docs/analyzers
|
||||
|
||||
# Below we enable specific rules by setting severity to warning.
|
||||
|
||||
# Use 'Count' property instead of 'Any' method.
|
||||
dotnet_diagnostic.RCS1080.severity = warning
|
||||
|
||||
# Use read-only auto-implemented property.
|
||||
dotnet_diagnostic.RCS1170.severity = warning
|
||||
|
||||
# Unnecessary interpolated string.
|
||||
dotnet_diagnostic.RCS1214.severity = warning
|
||||
|
||||
# Unnecessary usage of verbatim string literal.
|
||||
dotnet_diagnostic.RCS1192.severity = warning
|
||||
|
||||
# Use pattern matching instead of combination of 'as' operator and null check.
|
||||
dotnet_diagnostic.RCS1221.severity = warning
|
||||
|
||||
# Expression is always equal to 'true'.
|
||||
dotnet_diagnostic.RCS1215.severity = warning
|
||||
|
||||
# Use StringComparison when comparing strings.
|
||||
dotnet_diagnostic.RCS1155.severity = warning
|
||||
|
||||
# Abstract type should not have public constructors.
|
||||
dotnet_diagnostic.RCS1160.severity = warning
|
||||
|
||||
# Optimize 'Dictionary<TKey, TValue>.ContainsKey' call.
|
||||
dotnet_diagnostic.RCS1235.severity = warning
|
||||
|
||||
# Call extension method as instance method.
|
||||
dotnet_diagnostic.RCS1196.severity = warning
|
||||
; 4-column tab indentation
|
||||
[*.yaml]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
8
.gitattributes
vendored
8
.gitattributes
vendored
@@ -1,10 +1,12 @@
|
||||
# Enforce LF normalization on Windows
|
||||
*.yaml eol=lf
|
||||
*.lua eol=lf
|
||||
*.cs eol=lf
|
||||
*.csproj eol=lf
|
||||
*.sln eol=lf
|
||||
* text=lf
|
||||
|
||||
# Custom for Visual Studio
|
||||
*.cs diff=csharp
|
||||
*.sln merge=union
|
||||
*.csproj merge=union
|
||||
*.vbproj merge=union
|
||||
*.fsproj merge=union
|
||||
*.dbproj merge=union
|
||||
33
.github/ISSUE_TEMPLATE/bug-report.md
vendored
33
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@@ -1,33 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report unexpected behavior or any issues you experienced in OpenRA.
|
||||
title: ''
|
||||
labels: Bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- This is a guideline that shall help you to include information we need to understand and fix the issue you experienced. Please follow the instructions and replace any placeholders that are written in capital letters. Instructions like this comment will not be visible in your report. -->
|
||||
|
||||
<!-- Important: Help us to avoid duplicates and use the search function to find existing reports for your issue. Please do not submit duplicate reports! Try to help others to find your report by using a precise title. -->
|
||||
|
||||
## Issue Summary
|
||||
<!-- Please provide a a clear and concise description of what the issue is below. -->
|
||||
|
||||
DESCRIPTION
|
||||
|
||||
|
||||
## Reproduction
|
||||
<!-- Please provide information about how the issue can be reproduced below. -->
|
||||
|
||||
STEPS TO REPRODUCE THE ISSUE
|
||||
|
||||
|
||||
## Expected behavior
|
||||
<!-- Please explain what you expected to happen below. -->
|
||||
|
||||
EXPECTED BEHAVIOR
|
||||
|
||||
|
||||
## Screenshots / Screen recordings / Replays
|
||||
<!-- If applicable, attach screenshots, screen recordings or replays to help explain your problem. -->
|
||||
14
.github/ISSUE_TEMPLATE/config.yml
vendored
14
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,14 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Frequently Asked Questions
|
||||
url: https://github.com/OpenRA/OpenRA/wiki/FAQ#frequently-asked-questions
|
||||
about: Explanations for common problems and questions.
|
||||
- name: OpenRA Forum
|
||||
url: https://forum.openra.net/
|
||||
about: Please ask questions about modding here.
|
||||
- name: OpenRA Discord server
|
||||
url: https://discord.openra.net/
|
||||
about: Join the community Discord server for community discussion and support.
|
||||
- name: OpenRA IRC Channel
|
||||
url: https://web.libera.chat/#openra
|
||||
about: Join our development IRC channel on Libera for discussion of development topics.
|
||||
35
.github/ISSUE_TEMPLATE/crash-report.md
vendored
35
.github/ISSUE_TEMPLATE/crash-report.md
vendored
@@ -1,35 +0,0 @@
|
||||
---
|
||||
name: Crash report
|
||||
about: Report a game crash. Check the FAQ first https://github.com/OpenRA/OpenRA/wiki/FAQ#common-issues
|
||||
title: My game crashed
|
||||
labels: Crash
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- This is a guideline that shall help you to include all the required information we depend on to investigate and fix game-breaking bugs. Please follow the instructions and replace any placeholders that are written in capital letters. Instructions like this comment will not be visible in your report. -->
|
||||
|
||||
## System Information
|
||||
<!-- Information about the operating system, engine version, game mod and package source are mandatory for investigating and fixing crashes. -->
|
||||
|
||||
- Operating System: OPERATING SYSTEM
|
||||
- OpenRA Version: ENGINE VERSION
|
||||
- OpenRA Mod: GAME MOD
|
||||
- Source: Official download package OR self-compiled OR third-party package
|
||||
- For self-compiled or third-party packages: Mono version
|
||||
|
||||
|
||||
## Exception log
|
||||
<!-- Please replace the placeholder below with the content of the exception.log file. The three backticks before and after the placeholder are used for formatting, so don't remove them. If you don't find the log folder consult https://github.com/OpenRA/OpenRA/wiki/FAQ#my-game-just-crashed. -->
|
||||
|
||||
```
|
||||
PASTE LOG HERE
|
||||
```
|
||||
|
||||
|
||||
## Replay
|
||||
<!-- If you have a replay file for the game that crashed, and it crashes again when you play it back, it will be a great help for us to fix the issue. Please compress the replay into a zip file and drag it here to include it in the report. -->
|
||||
|
||||
|
||||
## Additional information
|
||||
<!-- Please tell us below everything that you think is important for us to know about the crash. Specifically, what you were doing in the moment before the crash or ideally steps to reproduce it are very valuable information. -->
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
name: Feature / Enhancement request
|
||||
about: Describe what you think is missing or could be improved in OpenRA.
|
||||
title: ''
|
||||
labels: Idea/Wishlist
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- This is a guideline that shall help you to describe your idea for a missing feature or enhancement. Please follow the instructions and replace any placeholders that are written in capital letters. Instructions like this comment will not be visible in your report. -->
|
||||
|
||||
<!-- Important: Help us to avoid duplicates and use the search function to find existing requests. Please do not submit duplicate requests! Try to help others to find your request by using a precise title. -->
|
||||
|
||||
## Motivation
|
||||
<!-- Please provide a clear and concise description of the motivation behind the request. If your request is related to a problem or limitation, describe it below. -->
|
||||
|
||||
REQUEST MOTIVATION
|
||||
|
||||
|
||||
## Proposed solution
|
||||
<!-- Please describe your idea and how it is intended to address the motivation of your request. Provide a clear and concise description of the proposed changes. -->
|
||||
|
||||
PROPOSED SOLUTION
|
||||
|
||||
|
||||
## Side effects
|
||||
<!-- Changes often have side effects or unintended consequences. If you expect that your solution has any side effects, please describe them below. -->
|
||||
|
||||
EXPECTED SIDE EFFECTS
|
||||
|
||||
|
||||
## Alternatives
|
||||
<!-- Please outline any alternative solutions you have considered. -->
|
||||
|
||||
ALTERNATIVES
|
||||
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,16 +0,0 @@
|
||||
Thank you for your contribution to OpenRA!
|
||||
|
||||
Please be aware that we do not have enough project maintainers to match the rate of contributions, so it may take several days before somebody is able to respond to your Pull Request.
|
||||
|
||||
You can help speed up the review process by following a few steps:
|
||||
|
||||
* Make sure that you have read and understand the OpenRA Coding Standard (see https://github.com/OpenRA/OpenRA/wiki/Coding-Standard).
|
||||
* Write quality commit messages (see https://chris.beams.io/posts/git-commit/).
|
||||
* Only commit changes that directly relate to your Pull Request. Use your Git interface to unstage any unrelated changes to project files, line endings, whitespace, or other files.
|
||||
* Review the code diff view below to double check that your changes satisfy the above three points.
|
||||
* Use the `make test` and `make check` commands to check for (and fix!) any issues that are reported by our automated tests.
|
||||
* If you are changing shared mod or engine code, make sure that you have tested your changes in all four default mods.
|
||||
* Respond to review comments as soon as you reasonably can. Reviewers will usually prioritize Pull Requests that are still fresh in their minds. Make sure to leave a comment when you push new changes, otherwise GitHub does not automatically notify reviewers!
|
||||
* Leave a polite comment asking for reviews if a week or more has passed without feedback.
|
||||
|
||||
If you need any help you can ask on Discord (https://discord.openra.net) or in the #openra IRC channel on Libera (not as active as Discord).
|
||||
80
.github/workflows/ci.yml
vendored
80
.github/workflows/ci.yml
vendored
@@ -1,80 +0,0 @@
|
||||
name: Continuous Integration
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
branches: [ bleed, 'prep-*' ]
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
name: Linux (.NET 6.0)
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: Check Code
|
||||
run: |
|
||||
make check
|
||||
|
||||
- name: Check Mods
|
||||
run: |
|
||||
sudo apt-get install lua5.1
|
||||
make check-scripts
|
||||
make test
|
||||
|
||||
linux-mono:
|
||||
name: Linux (mono)
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Check Code
|
||||
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 test
|
||||
|
||||
windows:
|
||||
name: Windows (.NET 6.0)
|
||||
runs-on: windows-2019
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: Check Code
|
||||
shell: powershell
|
||||
run: |
|
||||
# Work around runtime failures on the GH Actions runner
|
||||
dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org
|
||||
.\make.ps1 check
|
||||
dotnet build OpenRA.Test\OpenRA.Test.csproj -c Debug --nologo -p:TargetPlatform=win-x64
|
||||
dotnet test bin\OpenRA.Test.dll --test-adapter-path:.
|
||||
|
||||
- name: Check Mods
|
||||
run: |
|
||||
chocolatey install lua --version 5.1.5.52
|
||||
$ENV:Path = $ENV:Path + ";C:\Program Files (x86)\Lua\5.1\"
|
||||
.\make.ps1 check-scripts
|
||||
.\make.ps1 test
|
||||
143
.github/workflows/documentation.yml
vendored
143
.github/workflows/documentation.yml
vendored
@@ -1,143 +0,0 @@
|
||||
name: Deploy Documentation
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Git Tag'
|
||||
required: true
|
||||
default: 'release-xxxxxxxx'
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
wiki:
|
||||
name: Update Wiki
|
||||
if: github.repository == 'openra/openra'
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.inputs.tag }}
|
||||
|
||||
- name: Install .NET 6
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
make all
|
||||
|
||||
- name: Clone Wiki
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: openra/openra.wiki
|
||||
token: ${{ secrets.DOCS_TOKEN }}
|
||||
path: wiki
|
||||
|
||||
- name: Update Wiki (Playtest)
|
||||
if: startsWith(github.event.inputs.tag, 'playtest-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --settings-docs "${GIT_TAG}" > "wiki/Settings (playtest).md"
|
||||
|
||||
- name: Update Wiki (Release)
|
||||
if: startsWith(github.event.inputs.tag, 'release-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --settings-docs "${GIT_TAG}" > "wiki/Settings.md"
|
||||
|
||||
- name: Push Wiki
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
cd wiki
|
||||
git config --local user.email "actions@github.com"
|
||||
git config --local user.name "GitHub Actions"
|
||||
git add --all
|
||||
git commit -m "Update auto-generated documentation for ${GIT_TAG}"
|
||||
git push origin master
|
||||
|
||||
docs:
|
||||
name: Update docs.openra.net
|
||||
if: github.repository == 'openra/openra'
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.inputs.tag }}
|
||||
|
||||
- name: Install .NET 6
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
make all
|
||||
|
||||
- name: Clone docs.openra.net (Playtest)
|
||||
if: startsWith(github.event.inputs.tag, 'playtest-')
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: openra/docs
|
||||
token: ${{ secrets.DOCS_TOKEN }}
|
||||
path: docs
|
||||
ref: playtest
|
||||
|
||||
- name: Clone docs.openra.net (Release)
|
||||
if: startsWith(github.event.inputs.tag, 'release-')
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: openra/docs
|
||||
token: ${{ secrets.DOCS_TOKEN }}
|
||||
path: docs
|
||||
ref: release
|
||||
|
||||
- name: Update docs.openra.net (Playtest)
|
||||
if: startsWith(github.event.inputs.tag, 'playtest-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/traits.md"
|
||||
./utility.sh all --weapon-docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/weapons.md"
|
||||
./utility.sh all --sprite-sequence-docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/sprite-sequences.md"
|
||||
./utility.sh all --lua-docs "${GIT_TAG}" > "docs/api/lua.md"
|
||||
|
||||
- name: Update docs.openra.net (Release)
|
||||
if: startsWith(github.event.inputs.tag, 'release-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/traits.md"
|
||||
./utility.sh all --weapon-docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/weapons.md"
|
||||
./utility.sh all --sprite-sequence-docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/sprite-sequences.md"
|
||||
./utility.sh all --lua-docs "${GIT_TAG}" > "docs/api/lua.md"
|
||||
|
||||
- name: Commit docs.openra.net
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
cd docs
|
||||
git config --local user.email "actions@github.com"
|
||||
git config --local user.name "GitHub Actions"
|
||||
git add api/*.md
|
||||
git commit -m "Update auto-generated documentation for ${GIT_TAG}"
|
||||
|
||||
- name: Push docs.openra.net (Release)
|
||||
if: startsWith(github.event.inputs.tag, 'release-')
|
||||
run: |
|
||||
cd docs
|
||||
git push origin release
|
||||
|
||||
- name: Push docs.openra.net (Playtest)
|
||||
if: startsWith(github.event.inputs.tag, 'playtest-')
|
||||
run: |
|
||||
cd docs
|
||||
git push origin playtest
|
||||
87
.github/workflows/itch.yml
vendored
87
.github/workflows/itch.yml
vendored
@@ -1,87 +0,0 @@
|
||||
name: Deploy itch.io Packages
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Git Tag'
|
||||
required: true
|
||||
default: 'release-xxxxxxxx'
|
||||
|
||||
permissions: {}
|
||||
jobs:
|
||||
itch:
|
||||
name: Deploy to itch.io
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.repository == 'openra/openra'
|
||||
steps:
|
||||
- name: Download Packages
|
||||
run: |
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}-x64.exe"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}-x64-winportable.zip" -O "OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}.dmg"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Dune-2000-x86_64.AppImage"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Red-Alert-x86_64.AppImage"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Tiberian-Dawn-x86_64.AppImage"
|
||||
wget -q "https://raw.githubusercontent.com/${{ github.repository }}/${{ github.event.inputs.tag }}/packaging/.itch.toml"
|
||||
zip -u "OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip" .itch.toml
|
||||
|
||||
- name: Publish Windows Installer
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: win
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-${{ github.event.inputs.tag }}-x64.exe
|
||||
|
||||
- name: Publish Windows Itch Bundle
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: itch
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip
|
||||
|
||||
- name: Publish macOS Package
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: macos
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-${{ github.event.inputs.tag }}.dmg
|
||||
|
||||
- name: Publish RA AppImage
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: linux-ra
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-Red-Alert-x86_64.AppImage
|
||||
|
||||
- name: Publish TD AppImage
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: linux-cnc
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-Tiberian-Dawn-x86_64.AppImage
|
||||
|
||||
- name: Publish D2k AppImage
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: linux-d2k
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-Dune-2000-x86_64.AppImage
|
||||
133
.github/workflows/packaging.yml
vendored
133
.github/workflows/packaging.yml
vendored
@@ -1,133 +0,0 @@
|
||||
name: Release Packaging
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'release-*'
|
||||
- 'playtest-*'
|
||||
- 'devtest-*'
|
||||
|
||||
permissions:
|
||||
contents: write # for release creation (svenstaro/upload-release-action)
|
||||
|
||||
jobs:
|
||||
source:
|
||||
name: Source Tarball
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare Environment
|
||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
|
||||
- name: Package Source
|
||||
run: |
|
||||
mkdir -p build/source
|
||||
./packaging/source/buildpackage.sh "${GIT_TAG}" "${PWD}/build/source"
|
||||
|
||||
- name: Upload Packages
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
file: build/source/*
|
||||
|
||||
linux:
|
||||
name: Linux AppImages
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: Prepare Environment
|
||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
|
||||
- name: Package AppImages
|
||||
run: |
|
||||
mkdir -p build/linux
|
||||
sudo apt install libfuse2
|
||||
./packaging/linux/buildpackage.sh "${GIT_TAG}" "${PWD}/build/linux"
|
||||
|
||||
- name: Upload Packages
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
file: build/linux/*
|
||||
|
||||
macos:
|
||||
name: macOS Disk Image
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: Prepare Environment
|
||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
|
||||
- name: Package Disk Image
|
||||
env:
|
||||
MACOS_DEVELOPER_IDENTITY: ${{ secrets.MACOS_DEVELOPER_IDENTITY }}
|
||||
MACOS_DEVELOPER_CERTIFICATE_BASE64: ${{ secrets.MACOS_DEVELOPER_CERTIFICATE_BASE64 }}
|
||||
MACOS_DEVELOPER_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_DEVELOPER_CERTIFICATE_PASSWORD }}
|
||||
MACOS_DEVELOPER_USERNAME: ${{ secrets.MACOS_DEVELOPER_USERNAME }}
|
||||
MACOS_DEVELOPER_PASSWORD: ${{ secrets.MACOS_DEVELOPER_PASSWORD }}
|
||||
run: |
|
||||
mkdir -p build/macos
|
||||
./packaging/macos/buildpackage.sh "${GIT_TAG}" "${PWD}/build/macos"
|
||||
|
||||
- name: Upload Package
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
file: build/macos/*
|
||||
|
||||
windows:
|
||||
name: Windows Installers
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
sudo apt-get update
|
||||
sudo apt-get install nsis wine64
|
||||
|
||||
- name: Package Installers
|
||||
run: |
|
||||
mkdir -p build/windows
|
||||
./packaging/windows/buildpackage.sh "${GIT_TAG}" "${PWD}/build/windows"
|
||||
|
||||
- name: Upload Packages
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
file: build/windows/*
|
||||
136
.gitignore
vendored
136
.gitignore
vendored
@@ -1,56 +1,80 @@
|
||||
# Visual Studio
|
||||
Release
|
||||
bin
|
||||
obj
|
||||
*.ncb
|
||||
*.vcproj*
|
||||
*.suo
|
||||
*.user
|
||||
*.sln.cache
|
||||
*.manifest
|
||||
*.CodeAnalysisLog.xml
|
||||
*.lastcodeanalysissucceeded
|
||||
_ReSharper.*/
|
||||
/.vs
|
||||
|
||||
# Visual Studio Code
|
||||
/.vscode/settings.json
|
||||
|
||||
# binaries
|
||||
IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
|
||||
|
||||
# backup files by various editors
|
||||
*~
|
||||
*.orig
|
||||
\#*
|
||||
.*.sw?
|
||||
|
||||
# Monodevelop
|
||||
*.pidb
|
||||
*.userprefs
|
||||
|
||||
# Mac OS X
|
||||
.DS_Store
|
||||
|
||||
# auto-generated documentation
|
||||
DOCUMENTATION.md
|
||||
WEAPONS.md
|
||||
Lua-API.md
|
||||
Settings.md
|
||||
*.html
|
||||
openra.6
|
||||
update.log
|
||||
|
||||
# SublimeText
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
|
||||
# NUnit
|
||||
/TestResult.xml
|
||||
/lib/
|
||||
|
||||
# Support directory
|
||||
/Support
|
||||
|
||||
# IntelliJ files
|
||||
.idea
|
||||
# Visual Studio
|
||||
Release
|
||||
bin
|
||||
obj
|
||||
*.ncb
|
||||
*.vcproj*
|
||||
*.suo
|
||||
*.user
|
||||
*.sln.cache
|
||||
*.manifest
|
||||
*.CodeAnalysisLog.xml
|
||||
*.lastcodeanalysissucceeded
|
||||
_ReSharper.*/
|
||||
|
||||
# movies
|
||||
*.vqa
|
||||
|
||||
# archives
|
||||
*.mix
|
||||
|
||||
# binaries
|
||||
mods/*/*.dll
|
||||
mods/*/*.mdb
|
||||
/*.dll
|
||||
/*.dll.config
|
||||
/*.so
|
||||
/*.dylib
|
||||
/*.pdb
|
||||
/*.mdb
|
||||
/*.exe
|
||||
thirdparty/download/*
|
||||
*.mmdb.gz
|
||||
|
||||
# backup files by various editors
|
||||
*~
|
||||
*.orig
|
||||
\#*
|
||||
.*.sw?
|
||||
|
||||
# Monodevelop
|
||||
*.pidb
|
||||
*.userprefs
|
||||
|
||||
# Mac OS X
|
||||
.DS_Store
|
||||
|
||||
# XCode
|
||||
packaging/osx/launcher/build/
|
||||
packaging/osx/launcher/OpenRA.xcodeproj/*.pbxuser
|
||||
packaging/osx/launcher/OpenRA.xcodeproj/*.perspectivev3
|
||||
packaging/osx/launcher/OpenRA.xcodeproj/*.mode1v3
|
||||
temp.c
|
||||
temp.o
|
||||
temp.s
|
||||
OpenRA.Launcher.Mac/build/
|
||||
OpenRA.Launcher.Mac/OpenRA.xcodeproj/*.pbxuser
|
||||
OpenRA.Launcher.Mac/OpenRA.xcodeproj/*.perspectivev3
|
||||
OpenRA.Launcher.Mac/OpenRA.xcodeproj/*.mode1v3
|
||||
*.resources
|
||||
|
||||
# auto-generated documentation
|
||||
DOCUMENTATION.md
|
||||
Lua-API.md
|
||||
*.html
|
||||
openra.6
|
||||
|
||||
# StyleCop
|
||||
*.Cache
|
||||
StyleCopViolations.xml
|
||||
|
||||
# SublimeText
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
|
||||
# NUnit
|
||||
/TestResult.xml
|
||||
/lib/
|
||||
|
||||
# Support directory
|
||||
/Support
|
||||
|
||||
81
.travis.yml
Normal file
81
.travis.yml
Normal file
@@ -0,0 +1,81 @@
|
||||
# Travis-CI Build for OpenRA
|
||||
# see travis-ci.org for details
|
||||
|
||||
language: csharp
|
||||
mono: 4.4.2
|
||||
|
||||
# http://docs.travis-ci.com/user/migrating-from-legacy
|
||||
sudo: false
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- thirdparty/download
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- lua5.1
|
||||
- nsis
|
||||
- nsis-common
|
||||
- dpkg
|
||||
- markdown
|
||||
|
||||
# Environment variables
|
||||
env:
|
||||
secure: "C0+Hlfa0YGErxUuWV00Tj6p45otC/D3YwYFuLpi2mj1rDFn/4dgh5WRngjvdDBVbXJ3duaZ78jPHWm1jr7vn2jqj9yETsCIK9psWd38ep/FEBM0SDr6MUD89OuXk/YyvxJAE+UXF6bXg7giey09g/CwBigjMW7ynET3wNAWPHPs="
|
||||
|
||||
# Fetch dependencies
|
||||
# Run the build script
|
||||
# Check source code with StyleCop
|
||||
# call OpenRA to check for YAML errors
|
||||
# Run the NUnit tests
|
||||
script:
|
||||
- travis_retry make all-dependencies
|
||||
- make all SDK="-sdk:4.5"
|
||||
- make check
|
||||
- make check-scripts
|
||||
- make test
|
||||
- make nunit
|
||||
|
||||
# Automatically update the trait documentation and Lua API
|
||||
after_success:
|
||||
- test $TRAVIS_PULL_REQUEST == "false" && make docs && cd packaging && ./update-wiki.sh $TRAVIS_BRANCH; cd ..
|
||||
|
||||
# Only watch the development branch and tagged release.
|
||||
branches:
|
||||
only:
|
||||
- /^release-.*$/
|
||||
- /^playtest-.*$/
|
||||
- /^pkgtest-.*$/
|
||||
- /^prep-.*$/
|
||||
- bleed
|
||||
|
||||
# Notify developers when build passed/failed.
|
||||
notifications:
|
||||
irc:
|
||||
template:
|
||||
- "%{repository}#%{build_number} %{commit} %{author}: %{message} %{build_url}"
|
||||
channels:
|
||||
- "irc.freenode.net#openra"
|
||||
use_notice: true
|
||||
skip_join: true
|
||||
|
||||
before_deploy:
|
||||
- export PATH=$PATH:$HOME/usr/bin
|
||||
- DOTVERSION=`echo ${TRAVIS_TAG} | sed "s/-/\\./g"`
|
||||
- cd packaging
|
||||
- mkdir build
|
||||
- ./package-all.sh ${TRAVIS_TAG} ${PWD}/build/
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: "g/LU11f+mjqv+lj0sR1UliHwogXL4ofJUwoG5Dbqlvdf5UTLWytw/OWSCv8RGyuh10miyWeaoqHh1cn2C1IFhUEqN1sSeKKKOWOTvJ2FR5mzi9uH3d/MOBzG5icQ7Qh0fZ1YPz5RaJJhYu6bmfvA/1gD49GoaX2kxQL4J5cEBgg="
|
||||
file:
|
||||
- build/OpenRA-${TRAVIS_TAG}.exe
|
||||
- build/OpenRA-${TRAVIS_TAG}.zip
|
||||
- build/openra_${DOTVERSION}_all.deb
|
||||
skip_cleanup: true
|
||||
on:
|
||||
all_branches: true
|
||||
tags: true
|
||||
repo: OpenRA/OpenRA
|
||||
9
.vscode/extensions.json
vendored
9
.vscode/extensions.json
vendored
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"ms-dotnettools.csharp",
|
||||
"openra.oraide-vscode",
|
||||
"openra.vscode-openra-lua",
|
||||
"EditorConfig.EditorConfig",
|
||||
"macabeus.vscode-fluent",
|
||||
]
|
||||
}
|
||||
63
.vscode/launch.json
vendored
63
.vscode/launch.json
vendored
@@ -1,63 +0,0 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch (TD)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/bin/OpenRA.dll",
|
||||
"windows": {
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
},
|
||||
"args": ["Game.Mod=cnc", "Engine.EngineDir=.."],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
{
|
||||
"name": "Launch (RA)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/bin/OpenRA.dll",
|
||||
"windows": {
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
},
|
||||
"args": ["Game.Mod=ra", "Engine.EngineDir=.."],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
{
|
||||
"name": "Launch (D2k)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/bin/OpenRA.dll",
|
||||
"windows": {
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
},
|
||||
"args": ["Game.Mod=d2k", "Engine.EngineDir=.."],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
{
|
||||
"name": "Launch (TS)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/bin/OpenRA.dll",
|
||||
"windows": {
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
},
|
||||
"args": ["Game.Mod=ts", "Engine.EngineDir=.."],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
{
|
||||
"name": "Launch Utility",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/bin/OpenRA.Utility.dll",
|
||||
"windows": {
|
||||
"program": "${workspaceRoot}/bin/OpenRA.Utility.exe",
|
||||
},
|
||||
"args": ["all", "--docs", "{DEV_VERSION}"],
|
||||
"env": {
|
||||
"ENGINE_DIR": ".."
|
||||
},
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
]
|
||||
}
|
||||
36
.vscode/tasks.json
vendored
36
.vscode/tasks.json
vendored
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"options": {
|
||||
"env": {
|
||||
"ENGINE_DIR": ".."
|
||||
}
|
||||
},
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"command": "make",
|
||||
"args": ["all", "CONFIGURATION=Debug"],
|
||||
"windows": {
|
||||
"command": "make.cmd"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Run Utility",
|
||||
"command": "dotnet ${workspaceRoot}/bin/OpenRA.Utility.dll ${input:modId} ${input:command}",
|
||||
"type": "shell",
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"id": "modId",
|
||||
"description": "ID of the mod to run",
|
||||
"default": "all",
|
||||
"type": "promptString"
|
||||
}, {
|
||||
"id": "command",
|
||||
"description": "Name of the command + parameters",
|
||||
"default": "",
|
||||
"type": "promptString"
|
||||
},
|
||||
]
|
||||
}
|
||||
107
AUTHORS
107
AUTHORS
@@ -2,30 +2,27 @@ OpenRA wouldn't be where it is today without the
|
||||
hard work of many contributors.
|
||||
|
||||
The OpenRA developers are:
|
||||
* Gustas Kažukauskas (PunkPun)
|
||||
* Chris Forbes (chrisf)
|
||||
* Igor Popov (ihptru)
|
||||
* Lukas Franke (abcdefg30)
|
||||
* Matthias Mailänder (Mailaender)
|
||||
|
||||
Previous developers included:
|
||||
* Alli Witheford (alzeih)
|
||||
* Caleb Anderson (RobotCaleb)
|
||||
* Chris Forbes (chrisf)
|
||||
* Curtis Shmyr (hamb)
|
||||
* Daniel Hernandez (Mancano)
|
||||
* Igor Popov (ihptru)
|
||||
* Megan Bowra-Dean (beedee)
|
||||
* Mike Bundy (kehaar)
|
||||
* Oliver Brakmann (obrakmann)
|
||||
* Paul Chote (pchote)
|
||||
* Pavel Penev (penev92)
|
||||
* Reaperrr
|
||||
* Robert Pepperell (ytinasni)
|
||||
* ScottNZ
|
||||
* Tom Roostan (RoosterDragon)
|
||||
|
||||
Previous developers included:
|
||||
* Alli Witheford (alzeih)
|
||||
* Caleb Anderson (RobotCaleb)
|
||||
* Curtis Shmyr (hamb)
|
||||
* Daniel Hernandez (Mancano)
|
||||
* Megan Bowra-Dean (beedee)
|
||||
* Mike Bundy (kehaar)
|
||||
* Robert Pepperell (ytinasni)
|
||||
* ScottNZ
|
||||
|
||||
Also thanks to:
|
||||
* abmyii
|
||||
* anvilvapre (anvilvapre)
|
||||
* Adam Valy (Tschokky)
|
||||
* Akseli Virtanen (RAGEQUIT)
|
||||
* Alexander Fast (mizipzor)
|
||||
@@ -39,20 +36,14 @@ Also thanks to:
|
||||
* Arik Lirette (Angusm3)
|
||||
* Barnaby Smith (mvi)
|
||||
* Bellator
|
||||
* Bernd Stellwag (burned42)
|
||||
* Biofreak
|
||||
* Braxton Williams (Buddytex)
|
||||
* Brendan Gluth (Mechanical_Man)
|
||||
* Brent Gardner (bggardner)
|
||||
* Bryan Wilbur
|
||||
* Bugra Cuhadaroglu (BugraC)
|
||||
* Chris Cameron (Vesuvian)
|
||||
* Chris Grant (Unit158)
|
||||
* Christer Ulfsparre (Holloweye)
|
||||
* Christoph Lahner (chlah)
|
||||
* Chris Grant (Unit158)
|
||||
* clem
|
||||
* Cody Brittain (Generalcamo)
|
||||
* Constantin Helmig (CH4Code)
|
||||
* D2k Sardaukar
|
||||
* D'Arcy Rush (r34ch)
|
||||
* Daniel Derejvanik (Harisson)
|
||||
@@ -61,8 +52,6 @@ Also thanks to:
|
||||
* David Russell (DavidARussell)
|
||||
* DeadlySurprise
|
||||
* Dmitri Suvorov (suvjunmd)
|
||||
* dtluna
|
||||
* Eduardo Cáceres (eduherminio)
|
||||
* Erasmus Schroder (rasco)
|
||||
* Eric Bajumpaa (SteelPhase)
|
||||
* Evgeniy Sergeev (evgeniysergeev)
|
||||
@@ -70,28 +59,24 @@ Also thanks to:
|
||||
* Florian Wiesbauer (FiveAces)
|
||||
* Frank Razenberg (zzattack)
|
||||
* Gareth Needham (Ripley`)
|
||||
* Glen Anderson (glen7)
|
||||
* Glen Anderson (GlenAnderson)
|
||||
* Glenn Martin Jensen (Baxxster)
|
||||
* Gordon Martin (Happy0)
|
||||
* Guido Lipke (LipkeGu)
|
||||
* Guillermo Cuenca (Wylli)
|
||||
* Gyula Zimmermann (Graion Dilach)
|
||||
* Hervé Matysiak (Herve-M)
|
||||
* Huw Pascoe
|
||||
* Ian T. Jacobsen (Smilex)
|
||||
* Imago
|
||||
* Iran
|
||||
* Ishan Bhargava (ishantheperson)
|
||||
* Ivaylo Draganov (dragunoff)
|
||||
* Jacob Dufault (jacobdufault)
|
||||
* James Dunne (jsd)
|
||||
* James Gilbert (DSUK)
|
||||
* Jan-Willem Buurlage (jwbuurlage)
|
||||
* Jason (atlimit8)
|
||||
* Jeff Harris (jeff_1amstudios)
|
||||
* Jefri Sevkin (Arular)
|
||||
* Jes
|
||||
* Joakim Lindberg (booom3)
|
||||
* Joe Alam (joealam)
|
||||
* John Turner (whinis)
|
||||
* Jonas A. Lind (SoScared)
|
||||
* Joppy Furr
|
||||
@@ -99,29 +84,21 @@ Also thanks to:
|
||||
* Kenny Hoxworth (hoxworth)
|
||||
* Kevin Azzam (ChaoticMind)
|
||||
* Krishnakanth Mallik
|
||||
* Kyle Smith (Smitty)
|
||||
* Kyrre Soerensen (zypres)
|
||||
* Lawrence Wang
|
||||
* Lesueur Benjamin (Valkirie)
|
||||
* Maarten Meuris (Nyerguds)
|
||||
* Manuel Geiger (Ectras)
|
||||
* Mark Olson (markolson)
|
||||
* Markus Hartung (hartmark)
|
||||
* Matija Hustic (matija-hustic)
|
||||
* Matthew Gatland (mgatland)
|
||||
* Matthew Uzzell (MUzzell)
|
||||
* Matthijs Benschop (Nerdie)
|
||||
* Max621
|
||||
* Max Ugrumov (katzsmile)
|
||||
* Mazar Farran (mazarf)
|
||||
* Michael Rätzel
|
||||
* Michael Silber (frühstück)
|
||||
* Michael Sztolcman (s1w_)
|
||||
* Mike Gagné (AngryBirdz)
|
||||
* Muh
|
||||
* Mustafa Alperen Seki (MustaphaTR)
|
||||
* Neil Shivkar (havok13888)
|
||||
* Nikolay Fomin (netnazgul)
|
||||
* Nooze
|
||||
* Nukem
|
||||
* Okunev Yu Dmitry (xaionaro)
|
||||
@@ -130,9 +107,8 @@ Also thanks to:
|
||||
* Paul Dovydaitis (pdovy)
|
||||
* Pavlos Touboulidis (pav)
|
||||
* Pedro Ferreira Ramos (bateramos)
|
||||
* Peter Amrehn (jongleur1983)
|
||||
* Pizzaoverhead
|
||||
* Pi Delport (pjdelport)
|
||||
* Piët Delport (pjdelport)
|
||||
* Psydev
|
||||
* Raphael Vogt (TheRaffy, Yellow)
|
||||
* Raymond Bedrossian (Squiggles211)
|
||||
@@ -142,22 +118,16 @@ Also thanks to:
|
||||
* Rikhardur Bjarni Einarsson (WolfGaming)
|
||||
* Sascha Biedermann (bidifx)
|
||||
* Sean Hunt (coppro)
|
||||
* Sebastien Kerguen (xanax)
|
||||
* Shawn Collins (UberWaffe)
|
||||
* Simon Verbeke (Saticmotion)
|
||||
* Stuart McHattie (SDJMcHattie)
|
||||
* Taryn Hill (Phrohdoh)
|
||||
* Teemu Nieminen (Temeez)
|
||||
* Thomas Christlieb (ThomasChr)
|
||||
* Tim Mylemans (gecko)
|
||||
* Tirili
|
||||
* Tomas Einarsson (Mesacer)
|
||||
* Tom van Leth (tovl)
|
||||
* Trevor Nichols (ocdi)
|
||||
* Tristan Keating (Kilkakon)
|
||||
* Tristan Mühlbacher (MicroBit)
|
||||
* UnknownProgrammer
|
||||
* Vladimir Komarov (VrKomarov)
|
||||
* Wojciech Walaszek (Voidwalker)
|
||||
* Wuschel
|
||||
|
||||
Using GNU FreeFont distributed under the GNU GPL
|
||||
@@ -171,6 +141,15 @@ FreeType License.
|
||||
|
||||
Using OpenAL Soft distributed under the GNU LGPL.
|
||||
|
||||
Using MaxMind GeoIP2 .NET API distributed under
|
||||
the Apache 2.0 license.
|
||||
|
||||
Using GeoLite2 data created by MaxMind and
|
||||
distributed under the CC BY-SA 3.0 license.
|
||||
|
||||
Using SharpFont created by Robert Rouhani and
|
||||
distributed under the MIT license.
|
||||
|
||||
Using SDL2-CS and OpenAL-CS created by Ethan
|
||||
Lee and released under the zlib license.
|
||||
|
||||
@@ -180,40 +159,16 @@ under the MIT license.
|
||||
Using FuzzyLogicLibrary (fuzzynet) by Dmitry
|
||||
Kaluzhny and released under the GNU GPL terms.
|
||||
|
||||
Using Mono.Nat by Alan McGovern, Ben Motmans,
|
||||
Nicholas Terry distributed under the MIT license.
|
||||
|
||||
Using MP3Sharp by Robert Bruke and Zane Wagner
|
||||
licensed under the GNU LGPL Version 3.
|
||||
|
||||
Using TagLib# by Stephen Shaw licensed under the
|
||||
GNU LGPL Version 2.1.
|
||||
|
||||
Using NVorbis by Andrew Ward distributed under
|
||||
the MIT license.
|
||||
Using Open.Nat by Lucas Ontivero, based on the work
|
||||
of Alan McGovern and Ben Motmans and distributed
|
||||
under the MIT license.
|
||||
|
||||
Using ICSharpCode.SharpZipLib initially by Mike
|
||||
Krueger and distributed under the GNU GPL terms.
|
||||
|
||||
Using rix0rrr.BeaconLib developed by Rico Huijbers
|
||||
distributed under MIT License.
|
||||
Using SmartIrc4Net developed by Mirco Bauer
|
||||
distributed under the LGPL version 2.1 or later.
|
||||
|
||||
Using DiscordRichPresence developed by Lachee
|
||||
distributed under MIT License.
|
||||
|
||||
Using Json.NET developed by James Newton-King
|
||||
distributed under MIT License.
|
||||
|
||||
Using ANGLE distributed under the BS3 3-Clause license.
|
||||
|
||||
Using Pfim developed by Nick Babcock
|
||||
distributed under the MIT license.
|
||||
|
||||
Using Linguini by the Space Station 14 team
|
||||
licensed under Apache and MIT terms.
|
||||
|
||||
This site or product includes IP2Location LITE data
|
||||
available from http://www.ip2location.com.
|
||||
|
||||
Finally, special thanks goes to the original teams
|
||||
at Westwood Studios and EA for creating the classic
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by private-messaging a project team member (users with a + in front
|
||||
of their name) via our IRC channel (#openra on Libera –
|
||||
[webchat](https://web.libera.chat/#openra)). All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
@@ -1,8 +1,5 @@
|
||||
# OpenRA Contributing Guidelines
|
||||
|
||||
## Participating
|
||||
Help us keep OpenRA open and inclusive. Please read and follow our [Code of Conduct](https://github.com/OpenRA/OpenRA/blob/bleed/CODE_OF_CONDUCT.md).
|
||||
|
||||
## Bug reports
|
||||
|
||||
* Have you read the [FAQ](https://github.com/OpenRA/OpenRA/wiki/FAQ)?
|
||||
@@ -20,7 +17,7 @@ Help us keep OpenRA open and inclusive. Please read and follow our [Code of Cond
|
||||
|
||||
Please `git rebase` to the latest revision of the bleed branch.
|
||||
|
||||
Don't forget to add yourself to [AUTHORS](https://github.com/OpenRA/OpenRA/blob/bleed/AUTHORS).
|
||||
Don't forget to add youself to [AUTHORS](https://github.com/OpenRA/OpenRA/blob/bleed/AUTHORS).
|
||||
|
||||
Please propose a [CHANGELOG](https://github.com/OpenRA/OpenRA/wiki/CHANGELOG) entry in the pull-request comments.
|
||||
|
||||
|
||||
65
ConvertFrom-Markdown.ps1
Normal file
65
ConvertFrom-Markdown.ps1
Normal file
File diff suppressed because one or more lines are too long
@@ -1,58 +0,0 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Optimize>true</Optimize>
|
||||
<LangVersion>9</LangVersion>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EngineRootPath Condition="'$(EngineRootPath)' == ''">..</EngineRootPath>
|
||||
<OutputPath>$(EngineRootPath)/bin</OutputPath>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<ExternalConsole>false</ExternalConsole>
|
||||
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
||||
<Nullable>disable</Nullable>
|
||||
<Product>OpenRA</Product>
|
||||
<Copyright>Copyright (c) The OpenRA Developers and Contributors</Copyright>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework Condition="'$(MSBuildRuntimeType)'!='Mono'">net6.0</TargetFramework>
|
||||
<TargetFramework Condition="'$(MSBuildRuntimeType)'=='Mono'">netstandard2.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetPlatform Condition="$([MSBuild]::IsOsPlatform('Windows'))">win-x64</TargetPlatform>
|
||||
<TargetPlatform Condition="$([MSBuild]::IsOsPlatform('Linux'))">linux-x64</TargetPlatform>
|
||||
<TargetPlatform Condition="$([MSBuild]::IsOsPlatform('OSX'))">osx-x64</TargetPlatform>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<Optimize>false</Optimize>
|
||||
<!-- Enable only for Debug builds to improve compile-time performance for Release builds -->
|
||||
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||
<!-- Enabling GenerateDocumentationFile is required for IDE0005 (Remove unnecessary import)
|
||||
rule to run in command line builds. https://github.com/dotnet/roslyn/issues/41640
|
||||
Enable only for Debug builds to improve compile-time performance for Release builds -->
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Work around an issue where Rider does not detect files in the project root using the default glob -->
|
||||
<Compile Include="**/*.cs" Exclude="$(DefaultItemExcludes)" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="DisableAnalyzers" BeforeTargets="CoreCompile" Condition="'$(Configuration)'=='Release'">
|
||||
<!-- Disable code style analysis on Release builds to improve compile-time performance -->
|
||||
<ItemGroup Condition="'$(Configuration)'=='Release'">
|
||||
<Analyzer Remove="@(Analyzer)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<!-- StyleCop -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
112
INSTALL.md
112
INSTALL.md
@@ -6,78 +6,78 @@ The following lists per-platform dependencies required to build from source.
|
||||
Windows
|
||||
=======
|
||||
|
||||
Compiling OpenRA requires the following dependencies:
|
||||
* [Windows PowerShell >= 4.0](http://microsoft.com/powershell) (included by default in recent Windows 10 versions)
|
||||
* [.NET 6 SDK](https://dotnet.microsoft.com/download/dotnet/6.0) (or via Visual Studio)
|
||||
* [Windows PowerShell >= 4.0](http://microsoft.com/powershell)
|
||||
* [.NET Framework >= 4.5 (Client Profile)](http://www.microsoft.com/en-us/download/details.aspx?id=30653)
|
||||
* [SDL 2](http://www.libsdl.org/download-2.0.php) (included)
|
||||
* [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm) (included)
|
||||
* [zlib](http://gnuwin32.sourceforge.net/packages/zlib.htm) (included)
|
||||
* [OpenAL](http://kcat.strangesoft.net/openal.html) (included)
|
||||
* [liblua 5.1](http://luabinaries.sourceforge.net/download.html) (included)
|
||||
|
||||
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.
|
||||
You need to fetch the thirdparty dependencies using [NuGet](http://www.nuget.org) and place them at the appropriate places by typing `make dependencies` in a command terminal.
|
||||
|
||||
Run the game with `launch-game.cmd`. It can be handed arguments that specify the exact mod one wishes to run, for example, run `launch-game.cmd Game.Mod=ra` to launch Red Alert, `launch-game.cmd Game.Mod=cnc` to start Tiberian dawn or `launch-game.cmd Game.Mod=d2k` to launch Dune 2000.
|
||||
To compile OpenRA, open the `OpenRA.sln` solution in the main folder, build it from the command-line with MSBuild or use the Makefile analogue command `make all` scripted in PowerShell syntax.
|
||||
|
||||
Run the game with `OpenRA.Game.exe Game.Mod=ra` for Red Alert or `OpenRA.Game.exe Game.Mod=cnc` for Tiberian Dawn.
|
||||
|
||||
Linux
|
||||
=====
|
||||
|
||||
.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.
|
||||
Use `make dependencies` to map the native libraries to your system, fetch the remaining CLI dependencies using [NuGet](http://www.nuget.org) and place them at the appropriate places.
|
||||
|
||||
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 all` from the command line.
|
||||
|
||||
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.
|
||||
Run with either `launch-game.sh` or `mono --debug OpenRA.Game.exe`.
|
||||
|
||||
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`.
|
||||
Type `sudo make install-all` for system wide installation. Run `make install-linux-shortcuts` to get startup scripts, icons and desktop files. You can then run from the `openra` shortcut.
|
||||
|
||||
If you choose to use system libraries, or your system is not x86_64, you will need to install [SDL 2](https://www.libsdl.org/download-2.0.php), [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm), [OpenAL](https://openal-soft.org/), and [liblua 5.1](http://luabinaries.sourceforge.net/download.html) before compiling OpenRA.
|
||||
Debian/Ubuntu
|
||||
-------------
|
||||
|
||||
These can be installed using your package manager on various distros:
|
||||
* nuget
|
||||
* mono-devel
|
||||
* libfreetype6
|
||||
* libopenal1
|
||||
* liblua5.1-0
|
||||
* libsdl2-2.0-0
|
||||
* xdg-utils
|
||||
* zenity
|
||||
* curl
|
||||
|
||||
<details><summary>Arch Linux</summary>
|
||||
openSUSE
|
||||
--------
|
||||
|
||||
```
|
||||
sudo pacman -S openal libgl freetype2 sdl2 lua51
|
||||
```
|
||||
</details>
|
||||
<details><summary>Debian/Ubuntu</summary>
|
||||
* mono-devel
|
||||
* nuget
|
||||
* openal
|
||||
* freetype2
|
||||
* SDL2
|
||||
* lua51
|
||||
* xdg-utils
|
||||
* zenity
|
||||
* curl
|
||||
|
||||
```
|
||||
sudo apt install libfreetype6 libopenal1 liblua5.1-0 libsdl2-2.0-0
|
||||
```
|
||||
</details>
|
||||
<details><summary>Fedora</summary>
|
||||
Gentoo
|
||||
------
|
||||
|
||||
```
|
||||
sudo dnf install SDL2 freetype "lua = 5.1" openal-soft
|
||||
```
|
||||
</details>
|
||||
<details><summary>Gentoo</summary>
|
||||
* dev-lang/mono
|
||||
* dev-dotnet/libgdiplus
|
||||
* dev-dotnet/nuget
|
||||
* media-libs/freetype:2
|
||||
* media-libs/libsdl2
|
||||
* media-libs/openal
|
||||
* virtual/jpeg
|
||||
* virtual/opengl
|
||||
* dev-lang/lua-5.1.5
|
||||
* x11-misc/xdg-utils
|
||||
* gnome-extra/zenity
|
||||
* net-misc/curl
|
||||
|
||||
```
|
||||
sudo emerge -av media-libs/freetype:2 media-libs/libsdl2 media-libs/openal virtual/opengl '=dev-lang/lua-5.1.5*'
|
||||
```
|
||||
</details>
|
||||
<details><summary>Mageia</summary>
|
||||
|
||||
```
|
||||
sudo dnf install SDL2 freetype "lib*lua5.1" "lib*freetype2" "lib*sdl2.0_0" openal-soft
|
||||
```
|
||||
</details>
|
||||
<details><summary>openSUSE</summary>
|
||||
|
||||
```
|
||||
sudo zypper in openal-soft freetype2 SDL2 lua51
|
||||
```
|
||||
</details>
|
||||
<details><summary>Red Hat Enterprise Linux (and rebuilds, e.g. CentOS)</summary>
|
||||
The EPEL repository is required in order for the following command to run properly.
|
||||
|
||||
```
|
||||
sudo yum install SDL2 freetype "lua = 5.1" openal-soft
|
||||
```
|
||||
</details>
|
||||
|
||||
Type `sudo make install` for system-wide installation. Run `sudo make install-linux-shortcuts` to get startup scripts, icons and desktop files. You can then run the Red Alert by executing the `openra-ra` command, the Dune 2000 mod by running the `openra-d2k` command and Tiberian Dawn by the `openra-cnc` command. Alternatively, you can also run these mods by clicking on their desktop shortcuts if you ran `sudo make install-linux-shortcuts`.
|
||||
|
||||
macOS
|
||||
OSX
|
||||
=====
|
||||
|
||||
[.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).
|
||||
Use `make dependencies` to map the native libraries to your system.
|
||||
|
||||
To compile OpenRA, run `make` from the command line (or `make RUNTIME=mono` if using Mono). Run with `./launch-game.sh`.
|
||||
To compile OpenRA, run `make` from the command line.
|
||||
|
||||
Run with `mono --debug OpenRA.Game.exe`.
|
||||
|
||||
591
Makefile
591
Makefile
@@ -1,43 +1,68 @@
|
||||
############################# INSTRUCTIONS #############################
|
||||
#
|
||||
# to compile, run:
|
||||
# make
|
||||
# make [DEBUG=false]
|
||||
#
|
||||
# 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:
|
||||
# make [RUNTIME=net6] TARGETPLATFORM=unix-generic
|
||||
# to compile with development tools, run:
|
||||
# make all [DEBUG=false]
|
||||
#
|
||||
# to check unit tests (requires NUnit version >= 2.6), run:
|
||||
# make nunit [NUNIT_CONSOLE=<path-to/nunit[2]-console>] [NUNIT_LIBS_PATH=<path-to-libs-dir>] [NUNIT_LIBS=<nunit-libs>]
|
||||
# Use NUNIT_CONSOLE if nunit[3|2]-console was not downloaded by `make dependencies` nor is it in bin search paths
|
||||
# Use NUNIT_LIBS_PATH if NUnit libs are not in search paths. Include trailing /
|
||||
# Use NUNIT_LIBS if NUnit libs have different names (such as including a prefix or suffix)
|
||||
# to check the official mods for erroneous yaml files, run:
|
||||
# make [RUNTIME=net6] test
|
||||
# make test
|
||||
#
|
||||
# to check the engine and official mod dlls for code style violations, run:
|
||||
# make [RUNTIME=net6] check
|
||||
# to check the official mod dlls for StyleCop violations, run:
|
||||
# make check
|
||||
#
|
||||
# to compile and install Red Alert, Tiberian Dawn, and Dune 2000, run:
|
||||
# make [RUNTIME=net6] [prefix=/foo] [bindir=/bar/bin] install
|
||||
# to generate documentation aimed at modders, run:
|
||||
# make docs
|
||||
#
|
||||
# to compile and install Red Alert, Tiberian Dawn, and Dune 2000
|
||||
# using system libraries for native dependencies, run:
|
||||
# make [prefix=/foo] [bindir=/bar/bin] TARGETPLATFORM=unix-generic install
|
||||
# to install, run:
|
||||
# make [prefix=/foo] [bindir=/bar/bin] install
|
||||
#
|
||||
# to install FreeDesktop startup scripts, desktop files, icons, and MIME metadata
|
||||
# make install-linux-shortcuts
|
||||
# to install with development tools, run:
|
||||
# make [prefix=/foo] [bindir=/bar/bin] install-all
|
||||
#
|
||||
# to install FreeDesktop AppStream metadata
|
||||
# make install-linux-appdata
|
||||
# to install Linux startup scripts, desktop files and icons:
|
||||
# make install-linux-shortcuts [DEBUG=false]
|
||||
#
|
||||
# to install the Unix man page
|
||||
# make install-man
|
||||
# to uninstall, run:
|
||||
# make uninstall
|
||||
#
|
||||
# for help, run:
|
||||
# make help
|
||||
#
|
||||
# to start the game, run:
|
||||
# openra
|
||||
|
||||
|
||||
|
||||
############################## TOOLCHAIN ###############################
|
||||
#
|
||||
SDK ?=
|
||||
CSC = mcs $(SDK)
|
||||
CSFLAGS = -nologo -warn:4 -codepage:utf8 -unsafe -warnaserror
|
||||
DEFINE = TRACE
|
||||
COMMON_LIBS = System.dll System.Core.dll System.Data.dll System.Data.DataSetExtensions.dll System.Drawing.dll System.Xml.dll thirdparty/download/ICSharpCode.SharpZipLib.dll thirdparty/download/FuzzyLogicLibrary.dll thirdparty/download/MaxMind.Db.dll thirdparty/download/MaxMind.GeoIP2.dll thirdparty/download/Eluant.dll thirdparty/download/SmarIrc4net.dll
|
||||
NUNIT_LIBS_PATH :=
|
||||
NUNIT_LIBS := $(NUNIT_LIBS_PATH)nunit.framework.dll
|
||||
|
||||
DEBUG = true
|
||||
ifeq ($(DEBUG), $(filter $(DEBUG),false no n off 0))
|
||||
CSFLAGS += -debug:pdbonly -optimize+
|
||||
else
|
||||
CSFLAGS += -debug:full -optimize-
|
||||
DEFINE := DEBUG;$(DEFINE)
|
||||
endif
|
||||
|
||||
|
||||
|
||||
######################### UTILITIES/SETTINGS ###########################
|
||||
#
|
||||
# Install locations for local installs and downstream packaging
|
||||
# install locations
|
||||
prefix ?= /usr/local
|
||||
datarootdir ?= $(prefix)/share
|
||||
datadir ?= $(datarootdir)
|
||||
@@ -46,165 +71,473 @@ bindir ?= $(prefix)/bin
|
||||
libdir ?= $(prefix)/lib
|
||||
gameinstalldir ?= $(libdir)/openra
|
||||
|
||||
# Toolchain
|
||||
CWD = $(shell pwd)
|
||||
MSBUILD = msbuild -verbosity:m -nologo
|
||||
DOTNET = dotnet
|
||||
MONO = mono
|
||||
BIN_INSTALL_DIR = $(DESTDIR)$(bindir)
|
||||
DATA_INSTALL_DIR = $(DESTDIR)$(gameinstalldir)
|
||||
|
||||
# install tools
|
||||
RM = rm
|
||||
RM_R = $(RM) -r
|
||||
RM_F = $(RM) -f
|
||||
RM_RF = $(RM) -rf
|
||||
CP = cp
|
||||
CP_R = $(CP) -r
|
||||
INSTALL = install
|
||||
INSTALL_DIR = $(INSTALL) -d
|
||||
INSTALL_PROGRAM = $(INSTALL) -m755
|
||||
INSTALL_DATA = $(INSTALL) -m644
|
||||
|
||||
RUNTIME ?= net6
|
||||
CONFIGURATION ?= Release
|
||||
DOTNET_RID = $(shell ${DOTNET} --info | grep RID: | cut -w -f3)
|
||||
ARCH_X64 = $(shell echo ${DOTNET_RID} | grep x64)
|
||||
# program targets
|
||||
CORE = pdefault game utility server
|
||||
TOOLS = gamemonitor
|
||||
VERSION = $(shell git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || echo git-`git rev-parse --short HEAD`)
|
||||
|
||||
# Only for use in target version:
|
||||
VERSION := $(shell git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || (c=$$(git rev-parse --short HEAD 2>/dev/null) && echo git-$$c))
|
||||
|
||||
# Detect target platform for dependencies if not given by the user
|
||||
ifndef TARGETPLATFORM
|
||||
# dependencies
|
||||
UNAME_S := $(shell uname -s)
|
||||
UNAME_M := $(shell uname -m)
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
ifeq ($(ARCH_X64),)
|
||||
TARGETPLATFORM = osx-arm64
|
||||
os-dependencies = osx-dependencies
|
||||
else
|
||||
TARGETPLATFORM = osx-x64
|
||||
endif
|
||||
else
|
||||
ifeq ($(UNAME_M),x86_64)
|
||||
TARGETPLATFORM = linux-x64
|
||||
else
|
||||
ifeq ($(UNAME_M),aarch64)
|
||||
TARGETPLATFORM = linux-arm64
|
||||
else
|
||||
TARGETPLATFORM = unix-generic
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
os-dependencies = linux-dependencies
|
||||
endif
|
||||
|
||||
##################### DEVELOPMENT BUILDS AND TESTS #####################
|
||||
|
||||
|
||||
######################## PROGRAM TARGET RULES ##########################
|
||||
#
|
||||
all:
|
||||
@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)
|
||||
endif
|
||||
ifeq ($(TARGETPLATFORM), unix-generic)
|
||||
@./configure-system-libraries.sh
|
||||
endif
|
||||
@./fetch-geoip.sh
|
||||
# Core binaries
|
||||
|
||||
# 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
|
||||
clean:
|
||||
@-$(RM_RF) ./bin ./*/obj
|
||||
@-$(RM_F) IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
|
||||
game_SRCS := $(shell find OpenRA.Game/ -iname '*.cs')
|
||||
game_TARGET = OpenRA.Game.exe
|
||||
game_KIND = winexe
|
||||
game_LIBS = $(COMMON_LIBS) $(game_DEPS) thirdparty/download/SharpFont.dll thirdparty/download/Open.Nat.dll
|
||||
game_FLAGS = -win32icon:OpenRA.Game/OpenRA.ico
|
||||
PROGRAMS += game
|
||||
game: $(game_TARGET)
|
||||
|
||||
check:
|
||||
@echo
|
||||
@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) build -c Debug -nologo -warnaserror -p:TargetPlatform=$(TARGETPLATFORM)
|
||||
endif
|
||||
ifeq ($(TARGETPLATFORM), unix-generic)
|
||||
@./configure-system-libraries.sh
|
||||
endif
|
||||
@echo
|
||||
@echo "Checking for explicit interface violations..."
|
||||
@./utility.sh all --check-explicit-interfaces
|
||||
@echo
|
||||
@echo "Checking for incorrect conditional trait interface overrides..."
|
||||
@./utility.sh all --check-conditional-trait-interface-overrides
|
||||
# Platform dlls
|
||||
pdefault_SRCS := $(shell find OpenRA.Platforms.Default/ -iname '*.cs')
|
||||
pdefault_TARGET = OpenRA.Platforms.Default.dll
|
||||
pdefault_KIND = library
|
||||
pdefault_DEPS = $(game_TARGET)
|
||||
pdefault_LIBS = $(COMMON_LIBS) thirdparty/download/SDL2-CS.dll thirdparty/download/OpenAL-CS.dll $(pdefault_DEPS)
|
||||
PROGRAMS += pdefault
|
||||
platforms: $(pdefault_TARGET)
|
||||
|
||||
# Mods Common
|
||||
mod_common_SRCS := $(shell find OpenRA.Mods.Common/ -iname '*.cs')
|
||||
mod_common_TARGET = mods/common/OpenRA.Mods.Common.dll
|
||||
mod_common_KIND = library
|
||||
mod_common_DEPS = $(game_TARGET)
|
||||
mod_common_LIBS = $(COMMON_LIBS) $(STD_MOD_LIBS) thirdparty/download/StyleCop.dll thirdparty/download/StyleCop.CSharp.dll thirdparty/download/StyleCop.CSharp.Rules.dll
|
||||
PROGRAMS += mod_common
|
||||
mod_common: $(mod_common_TARGET)
|
||||
|
||||
# NUnit testing
|
||||
test_dll_SRCS := $(shell find OpenRA.Test/ -iname '*.cs')
|
||||
test_dll_TARGET = OpenRA.Test.dll
|
||||
test_dll_KIND = library
|
||||
test_dll_DEPS = $(game_TARGET) $(mod_common_TARGET)
|
||||
test_dll_FLAGS = -warn:1
|
||||
test_dll_LIBS = $(COMMON_LIBS) $(game_TARGET) $(mod_common_TARGET) $(NUNIT_LIBS)
|
||||
PROGRAMS += test_dll
|
||||
test_dll: $(test_dll_TARGET)
|
||||
|
||||
##### Official Mods #####
|
||||
|
||||
STD_MOD_LIBS = $(game_TARGET)
|
||||
STD_MOD_DEPS = $(STD_MOD_LIBS)
|
||||
|
||||
# Red Alert
|
||||
mod_ra_SRCS := $(shell find OpenRA.Mods.RA/ -iname '*.cs')
|
||||
mod_ra_TARGET = mods/ra/OpenRA.Mods.RA.dll
|
||||
mod_ra_KIND = library
|
||||
mod_ra_DEPS = $(STD_MOD_DEPS) $(mod_common_TARGET)
|
||||
mod_ra_LIBS = $(COMMON_LIBS) $(STD_MOD_LIBS) $(mod_common_TARGET)
|
||||
PROGRAMS += mod_ra
|
||||
mod_ra: $(mod_ra_TARGET)
|
||||
|
||||
# Command and Conquer
|
||||
mod_cnc_SRCS := $(shell find OpenRA.Mods.Cnc/ -iname '*.cs')
|
||||
mod_cnc_TARGET = mods/cnc/OpenRA.Mods.Cnc.dll
|
||||
mod_cnc_KIND = library
|
||||
mod_cnc_DEPS = $(STD_MOD_DEPS) $(mod_common_TARGET)
|
||||
mod_cnc_LIBS = $(COMMON_LIBS) $(STD_MOD_LIBS) $(mod_common_TARGET)
|
||||
PROGRAMS += mod_cnc
|
||||
mod_cnc: $(mod_cnc_TARGET)
|
||||
|
||||
# Dune 2000
|
||||
mod_d2k_SRCS := $(shell find OpenRA.Mods.D2k/ -iname '*.cs')
|
||||
mod_d2k_TARGET = mods/d2k/OpenRA.Mods.D2k.dll
|
||||
mod_d2k_KIND = library
|
||||
mod_d2k_DEPS = $(STD_MOD_DEPS) $(mod_common_TARGET)
|
||||
mod_d2k_LIBS = $(COMMON_LIBS) $(STD_MOD_LIBS) $(mod_common_TARGET)
|
||||
PROGRAMS += mod_d2k
|
||||
mod_d2k: $(mod_d2k_TARGET)
|
||||
|
||||
# Tiberian Sun
|
||||
mod_ts_SRCS := $(shell find OpenRA.Mods.TS/ -iname '*.cs')
|
||||
mod_ts_TARGET = mods/ts/OpenRA.Mods.TS.dll
|
||||
mod_ts_KIND = library
|
||||
mod_ts_DEPS = $(STD_MOD_DEPS) $(mod_common_TARGET)
|
||||
mod_ts_LIBS = $(COMMON_LIBS) $(STD_MOD_LIBS) $(mod_common_TARGET)
|
||||
PROGRAMS += mod_ts
|
||||
mod_ts: $(mod_ts_TARGET)
|
||||
|
||||
check-scripts:
|
||||
@echo
|
||||
@echo "Checking for Lua syntax errors..."
|
||||
@find lua/ mods/*/{maps,scripts}/ -iname "*.lua" -print0 | xargs -0n1 luac -p
|
||||
@luac -p $(shell find mods/*/maps/* -iname '*.lua')
|
||||
@luac -p $(shell find lua/* -iname '*.lua')
|
||||
|
||||
test: all
|
||||
check: utility mods
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Game..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Game
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Platforms.Default..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Platforms.Default
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.GameMonitor..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.GameMonitor
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Mods.Common..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Mods.Common
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Mods.RA..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Mods.RA
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Mods.Cnc..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Mods.Cnc
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Mods.D2k..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Mods.D2k
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Mods.TS..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Mods.TS
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Utility..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Utility
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Test..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Test
|
||||
@echo
|
||||
@echo "Checking for explicit interface violations..."
|
||||
@mono --debug OpenRA.Utility.exe all --check-explicit-interfaces
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Server..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Server
|
||||
|
||||
NUNIT_CONSOLE := $(shell test -f thirdparty/download/nunit3-console.exe && echo mono thirdparty/download/nunit3-console.exe || \
|
||||
which nunit3-console 2>/dev/null || which nunit2-console 2>/dev/null || which nunit-console 2>/dev/null)
|
||||
nunit: test_dll
|
||||
@echo
|
||||
@echo "Checking unit tests..."
|
||||
@if [ "$(NUNIT_CONSOLE)" = "" ] ; then \
|
||||
echo 'nunit[3|2]-console not found!'; \
|
||||
echo 'Was "make dependencies" called or is NUnit installed?'>&2; \
|
||||
echo 'See "make help".'; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if $(NUNIT_CONSOLE) --help | head -n 1 | grep -E "NUnit version (1|2\.[0-5])";then \
|
||||
echo 'NUnit version >= 2.6 required'>&2; \
|
||||
echo 'Try "make dependencies" first to use NUnit from NuGet.'>&2; \
|
||||
echo 'See "make help".'; \
|
||||
exit 1; \
|
||||
fi
|
||||
@$(NUNIT_CONSOLE) --noresult OpenRA.Test.nunit
|
||||
|
||||
test: utility mods
|
||||
@echo
|
||||
@echo "Testing Tiberian Sun mod MiniYAML..."
|
||||
@./utility.sh ts --check-yaml
|
||||
@mono --debug OpenRA.Utility.exe ts --check-yaml
|
||||
@echo
|
||||
@echo "Testing Dune 2000 mod MiniYAML..."
|
||||
@./utility.sh d2k --check-yaml
|
||||
@mono --debug OpenRA.Utility.exe d2k --check-yaml
|
||||
@echo
|
||||
@echo "Testing Tiberian Dawn mod MiniYAML..."
|
||||
@./utility.sh cnc --check-yaml
|
||||
@mono --debug OpenRA.Utility.exe cnc --check-yaml
|
||||
@echo
|
||||
@echo "Testing Red Alert mod MiniYAML..."
|
||||
@./utility.sh ra --check-yaml
|
||||
@mono --debug OpenRA.Utility.exe ra --check-yaml
|
||||
|
||||
############# LOCAL INSTALLATION AND DOWNSTREAM PACKAGING ##############
|
||||
|
||||
##### Launchers / Utilities #####
|
||||
|
||||
gamemonitor_SRCS := $(shell find OpenRA.GameMonitor/ -iname '*.cs')
|
||||
gamemonitor_TARGET = OpenRA.exe
|
||||
gamemonitor_KIND = winexe
|
||||
gamemonitor_DEPS = $(game_TARGET)
|
||||
gamemonitor_LIBS = $(COMMON_LIBS) $(gamemonitor_DEPS) System.Windows.Forms.dll
|
||||
gamemonitor_FLAGS = -win32icon:OpenRA.Game/OpenRA.ico
|
||||
PROGRAMS += gamemonitor
|
||||
gamemonitor: $(gamemonitor_TARGET)
|
||||
|
||||
# Backend for the launcher apps - queries game/mod info and applies actions to an install
|
||||
utility_SRCS := $(shell find OpenRA.Utility/ -iname '*.cs')
|
||||
utility_TARGET = OpenRA.Utility.exe
|
||||
utility_KIND = exe
|
||||
utility_DEPS = $(game_TARGET)
|
||||
utility_LIBS = $(COMMON_LIBS) $(utility_DEPS) thirdparty/download/ICSharpCode.SharpZipLib.dll
|
||||
PROGRAMS += utility
|
||||
utility: $(utility_TARGET)
|
||||
|
||||
# Dedicated server
|
||||
server_SRCS := $(shell find OpenRA.Server/ -iname '*.cs')
|
||||
server_TARGET = OpenRA.Server.exe
|
||||
server_KIND = exe
|
||||
server_DEPS = $(game_TARGET)
|
||||
server_LIBS = $(COMMON_LIBS) $(server_DEPS)
|
||||
PROGRAMS += server
|
||||
server: $(server_TARGET)
|
||||
|
||||
# Patches binary headers to work around a mono bug
|
||||
fixheader.exe: packaging/fixheader.cs
|
||||
@echo CSC fixheader.exe
|
||||
@$(CSC) packaging/fixheader.cs $(CSFLAGS) -out:fixheader.exe -t:exe $(COMMON_LIBS:%=-r:%)
|
||||
|
||||
# Generate build rules for each target defined above in PROGRAMS
|
||||
define BUILD_ASSEMBLY
|
||||
|
||||
$$($(1)_TARGET): $$($(1)_SRCS) Makefile $$($(1)_DEPS) fixheader.exe
|
||||
@echo CSC $$(@)
|
||||
@$(CSC) $$($(1)_LIBS:%=-r:%) \
|
||||
-out:$$(@) $(CSFLAGS) $$($(1)_FLAGS) \
|
||||
-define:"$(DEFINE)" \
|
||||
-t:"$$($(1)_KIND)" \
|
||||
$$($(1)_EXTRA) \
|
||||
$$($(1)_SRCS)
|
||||
@mono fixheader.exe $$(@) > /dev/null
|
||||
@test `echo $$(@) | sed 's/^.*\.//'` = "dll" && chmod a-x $$(@) || ``
|
||||
@$$($(1)_EXTRA_CMDS)
|
||||
endef
|
||||
|
||||
$(foreach prog,$(PROGRAMS),$(eval $(call BUILD_ASSEMBLY,$(prog))))
|
||||
|
||||
|
||||
|
||||
########################## MAKE/INSTALL RULES ##########################
|
||||
#
|
||||
version: VERSION mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modcontent/mod.yaml mods/all/mod.yaml
|
||||
ifeq ($(VERSION),)
|
||||
$(error Unable to determine new version (requires git or override of variable VERSION))
|
||||
default: core
|
||||
|
||||
core: game platforms mods utility server
|
||||
|
||||
tools: gamemonitor
|
||||
|
||||
package: all-dependencies core tools docs version
|
||||
|
||||
mods: mod_common mod_ra mod_cnc mod_d2k mod_ts
|
||||
|
||||
all: dependencies core tools
|
||||
|
||||
clean:
|
||||
@-$(RM_F) *.exe *.dll *.dylib *.dll.config ./OpenRA*/*.dll ./OpenRA*/*.mdb *.mdb mods/**/*.dll mods/**/*.mdb *.resources
|
||||
@-$(RM_RF) ./*/bin ./*/obj
|
||||
@-$(RM_RF) ./thirdparty/download
|
||||
|
||||
distclean: clean
|
||||
|
||||
cli-dependencies:
|
||||
@./thirdparty/fetch-thirdparty-deps.sh
|
||||
@ $(CP_R) thirdparty/download/*.dll .
|
||||
@ $(CP_R) thirdparty/download/*.dll.config .
|
||||
|
||||
linux-dependencies: cli-dependencies linux-native-dependencies
|
||||
|
||||
linux-native-dependencies:
|
||||
@./thirdparty/configure-native-deps.sh
|
||||
|
||||
windows-dependencies:
|
||||
@./thirdparty/fetch-thirdparty-deps-windows.sh
|
||||
|
||||
osx-dependencies: cli-dependencies
|
||||
@./thirdparty/fetch-thirdparty-deps-osx.sh
|
||||
@ $(CP_R) thirdparty/download/osx/*.dylib .
|
||||
@ $(CP_R) thirdparty/download/osx/*.dll.config .
|
||||
|
||||
dependencies: $(os-dependencies)
|
||||
@./thirdparty/fetch-geoip-db.sh
|
||||
@ $(CP) thirdparty/download/GeoLite2-Country.mmdb.gz .
|
||||
|
||||
all-dependencies: cli-dependencies windows-dependencies osx-dependencies
|
||||
|
||||
version: mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modchooser/mod.yaml mods/all/mod.yaml
|
||||
@for i in $? ; do \
|
||||
awk '{sub("Version:.*$$","Version: $(VERSION)"); print $0}' $${i} > $${i}.tmp && \
|
||||
awk '{sub("\tmodchooser:.*$$","\tmodchooser: $(VERSION)"); print $0}' $${i}.tmp > $${i}.tmp2 && \
|
||||
awk '{sub("/[^/]*: User$$", "/$(VERSION): User"); print $0}' $${i}.tmp2 > $${i} && \
|
||||
rm $${i}.tmp $${i}.tmp2; \
|
||||
done
|
||||
|
||||
docs: utility mods version
|
||||
@mono --debug OpenRA.Utility.exe all --docs > DOCUMENTATION.md
|
||||
@mono --debug OpenRA.Utility.exe all --lua-docs > Lua-API.md
|
||||
|
||||
man-page: utility mods
|
||||
@mono --debug OpenRA.Utility.exe all --man-page > openra.6
|
||||
|
||||
install: install-core
|
||||
|
||||
install-all: install-core install-tools
|
||||
|
||||
install-linux-shortcuts: install-linux-scripts install-linux-icons install-linux-desktop
|
||||
|
||||
install-core: default
|
||||
@-echo "Installing OpenRA to $(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) $(foreach prog,$(CORE),$($(prog)_TARGET)) "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)/mods"
|
||||
@$(CP_R) mods/common "$(DATA_INSTALL_DIR)/mods/"
|
||||
@$(INSTALL_PROGRAM) $(mod_common_TARGET) "$(DATA_INSTALL_DIR)/mods/common"
|
||||
@$(CP_R) mods/cnc "$(DATA_INSTALL_DIR)/mods/"
|
||||
@$(INSTALL_PROGRAM) $(mod_cnc_TARGET) "$(DATA_INSTALL_DIR)/mods/cnc"
|
||||
@$(CP_R) mods/ra "$(DATA_INSTALL_DIR)/mods/"
|
||||
@$(INSTALL_PROGRAM) $(mod_ra_TARGET) "$(DATA_INSTALL_DIR)/mods/ra"
|
||||
@$(CP_R) mods/d2k "$(DATA_INSTALL_DIR)/mods/"
|
||||
@$(INSTALL_PROGRAM) $(mod_d2k_TARGET) "$(DATA_INSTALL_DIR)/mods/d2k"
|
||||
@$(CP_R) mods/modchooser "$(DATA_INSTALL_DIR)/mods/"
|
||||
|
||||
@$(INSTALL_DATA) "global mix database.dat" "$(DATA_INSTALL_DIR)/global mix database.dat"
|
||||
@$(INSTALL_DATA) "GeoLite2-Country.mmdb.gz" "$(DATA_INSTALL_DIR)/GeoLite2-Country.mmdb.gz"
|
||||
@$(INSTALL_DATA) AUTHORS "$(DATA_INSTALL_DIR)/AUTHORS"
|
||||
@$(INSTALL_DATA) COPYING "$(DATA_INSTALL_DIR)/COPYING"
|
||||
|
||||
@$(CP_R) glsl "$(DATA_INSTALL_DIR)"
|
||||
@$(CP_R) lua "$(DATA_INSTALL_DIR)"
|
||||
@$(CP) SDL2-CS* "$(DATA_INSTALL_DIR)"
|
||||
@$(CP) OpenAL-CS* "$(DATA_INSTALL_DIR)"
|
||||
@$(CP) Eluant* "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) ICSharpCode.SharpZipLib.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) FuzzyLogicLibrary.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) SharpFont.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(CP) SharpFont.dll.config "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) Open.Nat.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) MaxMind.Db.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) MaxMind.GeoIP2.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) Newtonsoft.Json.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) SmarIrc4net.dll "$(DATA_INSTALL_DIR)"
|
||||
|
||||
ifneq ($(UNAME_S),Darwin)
|
||||
@$(CP) *.sh "$(DATA_INSTALL_DIR)"
|
||||
endif
|
||||
@sh -c '. ./packaging/functions.sh; set_engine_version "$(VERSION)" .'
|
||||
@sh -c '. ./packaging/functions.sh; set_mod_version "$(VERSION)" mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modcontent/mod.yaml mods/all/mod.yaml'
|
||||
|
||||
install:
|
||||
@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'
|
||||
install-tools: tools
|
||||
@-echo "Installing OpenRA tools to $(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) $(foreach prog,$(TOOLS),$($(prog)_TARGET)) "$(DATA_INSTALL_DIR)"
|
||||
|
||||
install-linux-shortcuts:
|
||||
@sh -c '. ./packaging/functions.sh; install_linux_shortcuts $(CWD) "$(DESTDIR)" "$(gameinstalldir)" "$(bindir)" "$(datadir)" "$(shell head -n1 VERSION)" cnc d2k ra'
|
||||
install-linux-icons:
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/icons/"
|
||||
@$(CP_R) packaging/linux/hicolor "$(DESTDIR)$(datadir)/icons/"
|
||||
|
||||
install-linux-desktop:
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/applications"
|
||||
@$(INSTALL_DATA) packaging/linux/openra.desktop "$(DESTDIR)$(datadir)/applications"
|
||||
|
||||
install-linux-mime:
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/mime/packages/"
|
||||
@$(INSTALL_DATA) packaging/linux/openra-mimeinfo.xml "$(DESTDIR)$(datadir)/mime/packages/openra.xml"
|
||||
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/applications"
|
||||
@$(INSTALL_DATA) packaging/linux/openra-join-servers.desktop "$(DESTDIR)$(datadir)/applications"
|
||||
@$(INSTALL_DATA) packaging/linux/openra-replays.desktop "$(DESTDIR)$(datadir)/applications"
|
||||
@$(INSTALL_DATA) packaging/linux/openra-launch-mod.desktop "$(DESTDIR)$(datadir)/applications"
|
||||
|
||||
install-linux-appdata:
|
||||
@sh -c '. ./packaging/functions.sh; install_linux_appdata $(CWD) "$(DESTDIR)" "$(datadir)" cnc d2k ra'
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/appdata/"
|
||||
@$(INSTALL_DATA) packaging/linux/openra.appdata.xml "$(DESTDIR)$(datadir)/appdata/"
|
||||
|
||||
install-man: all
|
||||
@mkdir -p $(DESTDIR)$(mandir)/man6/
|
||||
@./utility.sh all --man-page > $(DESTDIR)$(mandir)/man6/openra.6
|
||||
install-man-page: man-page
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man6/"
|
||||
@$(INSTALL_DATA) openra.6 "$(DESTDIR)$(mandir)/man6/"
|
||||
|
||||
install-linux-scripts:
|
||||
@echo "#!/bin/sh" > openra
|
||||
@echo 'cd "$(gameinstalldir)"' >> openra
|
||||
ifeq ($(DEBUG), $(filter $(DEBUG),false no n off 0))
|
||||
@echo 'mono OpenRA.Game.exe "$$@"' >> openra
|
||||
else
|
||||
@echo 'mono --debug OpenRA.Game.exe "$$@"' >> openra
|
||||
endif
|
||||
@echo 'if [ $$? != 0 -a $$? != 1 ]' >> openra
|
||||
@echo 'then' >> openra
|
||||
@echo 'ZENITY=`which zenity` || echo "OpenRA needs zenity installed to display a graphical error dialog. See ~/.openra. for log files."' >> openra
|
||||
@echo '$$ZENITY --question --title "OpenRA" --text "OpenRA has encountered a fatal error.\nLog Files are available in ~/.openra." --ok-label "Quit" --cancel-label "View FAQ" || xdg-open https://github.com/OpenRA/OpenRA/wiki/FAQ' >> openra
|
||||
@echo 'exit 1' >> openra
|
||||
@echo 'fi' >> openra
|
||||
|
||||
@$(INSTALL_DIR) "$(BIN_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) -m +rx openra "$(BIN_INSTALL_DIR)"
|
||||
@-$(RM) openra
|
||||
|
||||
@echo "#!/bin/sh" > openra-server
|
||||
@echo 'cd "$(gameinstalldir)"' >> openra-server
|
||||
ifeq ($(DEBUG), $(filter $(DEBUG),false no n off 0))
|
||||
@echo 'mono OpenRA.Server.exe "$$@"' >> openra-server
|
||||
else
|
||||
@echo 'mono --debug OpenRA.Server.exe "$$@"' >> openra-server
|
||||
endif
|
||||
|
||||
@$(INSTALL_DIR) "$(BIN_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) -m +rx openra-server "$(BIN_INSTALL_DIR)"
|
||||
@-$(RM) openra-server
|
||||
|
||||
uninstall:
|
||||
@-$(RM_R) "$(DATA_INSTALL_DIR)"
|
||||
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra"
|
||||
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-server"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra.desktop"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra-join-servers.desktop"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra-launch-mod.desktop"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra-join-servers.desktop"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/16x16/apps/openra.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/32x32/apps/openra.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/48x48/apps/openra.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/64x64/apps/openra.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/128x128/apps/openra.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/mime/packages/openra.xml"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/appdata/openra.appdata.xml"
|
||||
@-$(RM_F) "$(DESTDIR)$(mandir)/man6/openra.6"
|
||||
|
||||
help:
|
||||
@echo 'to compile, run:'
|
||||
@echo ' make'
|
||||
@echo ' make [DEBUG=false]'
|
||||
@echo
|
||||
@echo 'to compile using Mono (version 6.12 or greater) instead of .NET 6, run:'
|
||||
@echo ' make RUNTIME=mono'
|
||||
@echo 'to compile with development tools, run:'
|
||||
@echo ' make all [DEBUG=false]'
|
||||
@echo
|
||||
@echo 'to compile using system libraries for native dependencies, run:'
|
||||
@echo ' make [RUNTIME=net6] TARGETPLATFORM=unix-generic'
|
||||
@echo 'to check unit tests (requires NUnit version >= 2.6), run:'
|
||||
@echo ' make nunit [NUNIT_CONSOLE=<path-to/nunit[3|2]-console>] [NUNIT_LIBS_PATH=<path-to-libs-dir>] [NUNIT_LIBS=<nunit-libs>]'
|
||||
@echo ' Use NUNIT_CONSOLE if nunit[3|2]-console was not downloaded by `make dependencies` nor is it in bin search paths'
|
||||
@echo ' Use NUNIT_LIBS_PATH if NUnit libs are not in search paths. Include trailing /'
|
||||
@echo ' Use NUNIT_LIBS if NUnit libs have different names (such as including a prefix or suffix)'
|
||||
@echo
|
||||
@echo 'to check the official mods for erroneous yaml files, run:'
|
||||
@echo ' make [RUNTIME=net6] test'
|
||||
@echo ' make test'
|
||||
@echo
|
||||
@echo 'to check the engine and official mod dlls for code style violations, run:'
|
||||
@echo ' make [RUNTIME=net6] check'
|
||||
@echo 'to generate documentation aimed at modders, run:'
|
||||
@echo ' make docs'
|
||||
@echo
|
||||
@echo 'to compile and install Red Alert, Tiberian Dawn, and Dune 2000 run:'
|
||||
@echo ' make [RUNTIME=net6] [prefix=/foo] [TARGETPLATFORM=unix-generic] install'
|
||||
@echo 'to install, run:'
|
||||
@echo ' make [prefix=/foo] [bindir=/bar/bin] install'
|
||||
@echo
|
||||
@echo 'to compile and install Red Alert, Tiberian Dawn, and Dune 2000'
|
||||
@echo 'using system libraries for native dependencies, run:'
|
||||
@echo ' make [RUNTIME=net6] [prefix=/foo] [bindir=/bar/bin] TARGETPLATFORM=unix-generic install'
|
||||
@echo 'to install with development tools, run:'
|
||||
@echo ' make [prefix=/foo] [bindir=/bar/bin] install-all'
|
||||
@echo
|
||||
@echo 'to install FreeDesktop startup scripts, desktop files, icons, and MIME metadata'
|
||||
@echo ' make install-linux-shortcuts'
|
||||
@echo 'to install Linux startup scripts, desktop files and icons'
|
||||
@echo ' make install-linux-shortcuts [DEBUG=false]'
|
||||
@echo
|
||||
@echo 'to install FreeDesktop AppStream metadata'
|
||||
@echo ' make install-linux-appdata'
|
||||
@echo 'to uninstall, run:'
|
||||
@echo ' make uninstall'
|
||||
@echo
|
||||
@echo 'to install a Unix man page'
|
||||
@echo ' make install-man'
|
||||
@echo 'to start the game, run:'
|
||||
@echo ' openra'
|
||||
|
||||
|
||||
|
||||
|
||||
########################### MAKEFILE SETTINGS ##########################
|
||||
#
|
||||
.DEFAULT_GOAL := all
|
||||
.DEFAULT_GOAL := default
|
||||
|
||||
.SUFFIXES:
|
||||
|
||||
.PHONY: all clean check check-scripts test version install install-linux-shortcuts install-linux-appdata install-man help
|
||||
.PHONY: core tools package all mods clean distclean dependencies version $(PROGRAMS) nunit
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<runtime>
|
||||
<loadFromRemoteSources enabled="true" />
|
||||
</runtime>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<runtime>
|
||||
<loadFromRemoteSources enabled="true" />
|
||||
</runtime>
|
||||
</configuration>
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -9,283 +9,47 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Activities
|
||||
{
|
||||
public enum ActivityState { Queued, Active, Canceling, Done }
|
||||
|
||||
public class TargetLineNode
|
||||
public abstract class Activity
|
||||
{
|
||||
public readonly Target Target;
|
||||
public readonly Color Color;
|
||||
public readonly Sprite Tile;
|
||||
public Activity NextActivity { get; set; }
|
||||
protected bool IsCanceled { get; private set; }
|
||||
|
||||
public TargetLineNode(in Target target, Color color, Sprite tile = null)
|
||||
public abstract Activity Tick(Actor self);
|
||||
|
||||
public virtual void Cancel(Actor self)
|
||||
{
|
||||
// Note: Not all activities are drawable. In that case, pass Target.Invalid as target,
|
||||
// if "yield break" in TargetLineNode(Actor self) is not feasible.
|
||||
Target = target;
|
||||
Color = color;
|
||||
Tile = tile;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Things to be aware of when writing activities:
|
||||
*
|
||||
* - Use "return true" at least once somewhere in the tick method.
|
||||
* - Do not "reuse" activity objects (by queuing them as next or child, for example) that have already started running.
|
||||
* Queue a new instance instead.
|
||||
* - Avoid calling actor.CancelActivity(). It is almost always a bug. Call activity.Cancel() instead.
|
||||
* - Do not evaluate dynamic state (an actor's location, health, conditions, etc.) in the activity's constructor,
|
||||
* as that might change before the activity gets to tick for the first time. Use the OnFirstRun() method instead.
|
||||
*/
|
||||
public abstract class Activity : IActivityInterface
|
||||
{
|
||||
public ActivityState State { get; private set; }
|
||||
|
||||
Activity childActivity;
|
||||
protected Activity ChildActivity
|
||||
{
|
||||
get => SkipDoneActivities(childActivity);
|
||||
private set => childActivity = value;
|
||||
IsCanceled = true;
|
||||
NextActivity = null;
|
||||
}
|
||||
|
||||
Activity nextActivity;
|
||||
public Activity NextActivity
|
||||
public virtual void Queue(Activity activity)
|
||||
{
|
||||
get => SkipDoneActivities(nextActivity);
|
||||
private set => nextActivity = value;
|
||||
}
|
||||
|
||||
internal static Activity SkipDoneActivities(Activity first)
|
||||
{
|
||||
// If first.Cancel() was called while it was queued (i.e. before it first ticked), its state will be Done
|
||||
// rather than Queued (the activity system guarantees that it cannot be Active or Canceling).
|
||||
// An unknown number of ticks may have elapsed between the Cancel() call and now,
|
||||
// so we cannot make any assumptions on the value of first.NextActivity.
|
||||
// We must not return first (ticking it would be bogus), but returning null would potentially
|
||||
// drop valid activities queued after it. Walk the queue until we find a valid activity or
|
||||
// (more likely) run out of activities.
|
||||
while (first != null && first.State == ActivityState.Done)
|
||||
first = first.nextActivity;
|
||||
|
||||
return first;
|
||||
}
|
||||
|
||||
public bool IsInterruptible { get; protected set; }
|
||||
public bool ChildHasPriority { get; protected set; }
|
||||
public bool IsCanceling => State == ActivityState.Canceling;
|
||||
bool finishing;
|
||||
bool firstRunCompleted;
|
||||
bool lastRun;
|
||||
|
||||
public Activity()
|
||||
{
|
||||
IsInterruptible = true;
|
||||
ChildHasPriority = true;
|
||||
}
|
||||
|
||||
public Activity TickOuter(Actor self)
|
||||
{
|
||||
if (State == ActivityState.Done)
|
||||
throw new InvalidOperationException($"Actor {self} attempted to tick activity {GetType()} after it had already completed.");
|
||||
|
||||
if (State == ActivityState.Queued)
|
||||
{
|
||||
OnFirstRun(self);
|
||||
firstRunCompleted = true;
|
||||
State = ActivityState.Active;
|
||||
}
|
||||
|
||||
if (!firstRunCompleted)
|
||||
throw new InvalidOperationException($"Actor {self} attempted to tick activity {GetType()} before running its OnFirstRun method.");
|
||||
|
||||
// Only run the parent tick when the child is done.
|
||||
// We must always let the child finish on its own before continuing.
|
||||
if (ChildHasPriority)
|
||||
{
|
||||
lastRun = TickChild(self) && (finishing || Tick(self));
|
||||
finishing |= lastRun;
|
||||
}
|
||||
|
||||
// The parent determines whether the child gets a chance at ticking.
|
||||
if (NextActivity != null)
|
||||
NextActivity.Queue(activity);
|
||||
else
|
||||
lastRun = Tick(self);
|
||||
|
||||
// Avoid a single tick delay if the childactivity was just queued.
|
||||
var ca = ChildActivity;
|
||||
if (ca != null && ca.State == ActivityState.Queued)
|
||||
{
|
||||
if (ChildHasPriority)
|
||||
lastRun = TickChild(self) && finishing;
|
||||
else
|
||||
TickChild(self);
|
||||
}
|
||||
|
||||
if (lastRun)
|
||||
{
|
||||
State = ActivityState.Done;
|
||||
OnLastRun(self);
|
||||
return NextActivity;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
protected bool TickChild(Actor self)
|
||||
{
|
||||
ChildActivity = ActivityUtils.RunActivity(self, ChildActivity);
|
||||
return ChildActivity == null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called every tick to run activity logic. Returns false if the activity should
|
||||
/// remain active, or true if it is complete. Cancelled activities must ensure they
|
||||
/// return the actor to a consistent state before returning true.
|
||||
///
|
||||
/// Child activities can be queued using QueueChild, and these will be ticked
|
||||
/// instead of the parent while they are active. Activities that need to run logic
|
||||
/// in parallel with child activities should set ChildHasPriority to false and
|
||||
/// manually call TickChildren.
|
||||
///
|
||||
/// Queuing one or more child activities and returning true is valid, and causes
|
||||
/// the activity to be completed immediately (without ticking again) once the
|
||||
/// children have completed.
|
||||
/// </summary>
|
||||
public virtual bool Tick(Actor self)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs once immediately before the first Tick() execution.
|
||||
/// </summary>
|
||||
protected virtual void OnFirstRun(Actor self) { }
|
||||
|
||||
/// <summary>
|
||||
/// Runs once immediately after the last Tick() execution.
|
||||
/// </summary>
|
||||
protected virtual void OnLastRun(Actor self) { }
|
||||
|
||||
/// <summary>
|
||||
/// Runs once on Actor.Dispose() (through OnActorDisposeOuter) and can be used to perform activity clean-up on actor death/disposal,
|
||||
/// for example by force-triggering OnLastRun (which would otherwise be skipped).
|
||||
/// </summary>
|
||||
protected virtual void OnActorDispose(Actor self) { }
|
||||
|
||||
/// <summary>
|
||||
/// Runs once on Actor.Dispose().
|
||||
/// Main purpose is to ensure ChildActivity.OnActorDispose runs as well (which isn't otherwise accessible due to protection level).
|
||||
/// </summary>
|
||||
internal void OnActorDisposeOuter(Actor self)
|
||||
{
|
||||
ChildActivity?.OnActorDisposeOuter(self);
|
||||
|
||||
OnActorDispose(self);
|
||||
}
|
||||
|
||||
public virtual void Cancel(Actor self, bool keepQueue = false)
|
||||
{
|
||||
if (!keepQueue)
|
||||
NextActivity = null;
|
||||
|
||||
if (!IsInterruptible)
|
||||
return;
|
||||
|
||||
ChildActivity?.Cancel(self);
|
||||
|
||||
// Directly mark activities that are queued and therefore didn't run yet as done
|
||||
State = State == ActivityState.Queued ? ActivityState.Done : ActivityState.Canceling;
|
||||
}
|
||||
|
||||
public void Queue(Activity activity)
|
||||
{
|
||||
var it = this;
|
||||
while (it.nextActivity != null)
|
||||
it = it.nextActivity;
|
||||
it.nextActivity = activity;
|
||||
}
|
||||
|
||||
public void QueueChild(Activity activity)
|
||||
{
|
||||
if (childActivity != null)
|
||||
childActivity.Queue(activity);
|
||||
else
|
||||
childActivity = activity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints the activity tree, starting from the top or optionally from a given origin.
|
||||
///
|
||||
/// Call this method from any place that's called during a tick, such as the Tick() method itself or
|
||||
/// the Before(First|Last)Run() methods. The origin activity will be marked in the output.
|
||||
/// </summary>
|
||||
/// <param name="self">The actor performing this activity.</param>
|
||||
/// <param name="origin">Activity from which to start traversing, and which to mark. If null, mark the calling activity, and start traversal from the top.</param>
|
||||
/// <param name="level">Initial level of indentation.</param>
|
||||
protected void PrintActivityTree(Actor self, Activity origin = null, int level = 0)
|
||||
{
|
||||
if (origin == null)
|
||||
self.CurrentActivity.PrintActivityTree(self, this);
|
||||
else
|
||||
{
|
||||
Console.Write(new string(' ', level * 2));
|
||||
if (origin == this)
|
||||
Console.Write("*");
|
||||
|
||||
Console.WriteLine(GetType().ToString().Split('.').Last());
|
||||
|
||||
ChildActivity?.PrintActivityTree(self, origin, level + 1);
|
||||
|
||||
NextActivity?.PrintActivityTree(self, origin, level);
|
||||
}
|
||||
NextActivity = activity;
|
||||
}
|
||||
|
||||
public virtual IEnumerable<Target> GetTargets(Actor self)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IEnumerable<TargetLineNode> TargetLineNodes(Actor self)
|
||||
public static class ActivityExts
|
||||
{
|
||||
public static IEnumerable<Target> GetTargetQueue(this Actor self)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
public IEnumerable<string> DebugLabelComponents()
|
||||
{
|
||||
var act = this;
|
||||
while (act != null)
|
||||
{
|
||||
yield return act.GetType().Name;
|
||||
act = act.ChildActivity;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<T> ActivitiesImplementing<T>(bool includeChildren = true) where T : IActivityInterface
|
||||
{
|
||||
// Skips Done child and next activities
|
||||
if (includeChildren)
|
||||
{
|
||||
var ca = ChildActivity;
|
||||
if (ca != null)
|
||||
foreach (var a in ca.ActivitiesImplementing<T>())
|
||||
yield return a;
|
||||
}
|
||||
|
||||
if (this is T)
|
||||
yield return (T)(object)this;
|
||||
|
||||
var na = NextActivity;
|
||||
if (na != null)
|
||||
foreach (var a in na.ActivitiesImplementing<T>())
|
||||
yield return a;
|
||||
return self.GetCurrentActivity()
|
||||
.Iterate(u => u.NextActivity)
|
||||
.TakeWhile(u => u != null)
|
||||
.SelectMany(u => u.GetTargets(self));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -16,18 +16,25 @@ namespace OpenRA.Activities
|
||||
public class CallFunc : Activity
|
||||
{
|
||||
public CallFunc(Action a) { this.a = a; }
|
||||
public CallFunc(Action a, bool interruptible)
|
||||
public CallFunc(Action a, bool interruptable)
|
||||
{
|
||||
this.a = a;
|
||||
IsInterruptible = interruptible;
|
||||
this.interruptable = interruptable;
|
||||
}
|
||||
|
||||
readonly Action a;
|
||||
Action a;
|
||||
bool interruptable;
|
||||
|
||||
public override bool Tick(Actor self)
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
a.Invoke();
|
||||
return true;
|
||||
if (a != null) a();
|
||||
return NextActivity;
|
||||
}
|
||||
|
||||
public override void Cancel(Actor self)
|
||||
{
|
||||
if (interruptable)
|
||||
base.Cancel(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -11,8 +11,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using Eluant;
|
||||
using Eluant.ObjectBinding;
|
||||
@@ -24,23 +23,13 @@ using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
[Flags]
|
||||
public enum SystemActors
|
||||
{
|
||||
Player = 0,
|
||||
EditorPlayer = 1,
|
||||
World = 2,
|
||||
EditorWorld = 4
|
||||
}
|
||||
|
||||
public sealed class Actor : IScriptBindable, IScriptNotifyBind, ILuaTableBinding, ILuaEqualityBinding, ILuaToStringBinding, IEquatable<Actor>, IDisposable
|
||||
{
|
||||
internal readonly struct SyncHash
|
||||
internal struct SyncHash
|
||||
{
|
||||
public readonly ISync Trait;
|
||||
readonly Func<object, int> hashFunction;
|
||||
public SyncHash(ISync trait) { Trait = trait; hashFunction = Sync.GetHashFunction(trait); }
|
||||
public int Hash() { return hashFunction(Trait); }
|
||||
public readonly int Hash;
|
||||
public SyncHash(ISync trait, int hash) { Trait = trait; Hash = hash; }
|
||||
}
|
||||
|
||||
public readonly ActorInfo Info;
|
||||
@@ -49,92 +38,56 @@ namespace OpenRA
|
||||
|
||||
public readonly uint ActorID;
|
||||
|
||||
public Player Owner { get; internal set; }
|
||||
public Player Owner { get; set; }
|
||||
|
||||
public bool IsInWorld { get; internal set; }
|
||||
public bool WillDispose { get; private set; }
|
||||
public bool Disposed { get; private set; }
|
||||
|
||||
Activity currentActivity;
|
||||
public Activity CurrentActivity
|
||||
{
|
||||
get => Activity.SkipDoneActivities(currentActivity);
|
||||
private set => currentActivity = value;
|
||||
}
|
||||
|
||||
public Group Group;
|
||||
public int Generation;
|
||||
public Actor ReplacedByActor;
|
||||
|
||||
public IEffectiveOwner EffectiveOwner { get; }
|
||||
public IOccupySpace OccupiesSpace { get; }
|
||||
public ITargetable[] Targetables { get; }
|
||||
public IEnumerable<ITargetablePositions> EnabledTargetablePositions { get; private set; }
|
||||
public Rectangle Bounds { get; private set; }
|
||||
public Rectangle VisualBounds { get; private set; }
|
||||
public IEffectiveOwner EffectiveOwner { get; private set; }
|
||||
public IOccupySpace OccupiesSpace { get; private set; }
|
||||
public ITargetable[] Targetables { get; private set; }
|
||||
|
||||
public bool IsIdle => CurrentActivity == null;
|
||||
public bool IsDead => Disposed || (health != null && health.IsDead);
|
||||
public bool IsIdle { get { return currentActivity == null; } }
|
||||
public bool IsDead { get { return Disposed || (health != null && health.IsDead); } }
|
||||
|
||||
public CPos Location => OccupiesSpace.TopLeft;
|
||||
public WPos CenterPosition => OccupiesSpace.CenterPosition;
|
||||
public CPos Location { get { return OccupiesSpace.TopLeft; } }
|
||||
public WPos CenterPosition { get { return OccupiesSpace.CenterPosition; } }
|
||||
|
||||
public WRot Orientation => facing?.Orientation ?? WRot.None;
|
||||
|
||||
/// <summary>Value used to represent an invalid token.</summary>
|
||||
public static readonly int InvalidConditionToken = -1;
|
||||
|
||||
class ConditionState
|
||||
public WRot Orientation
|
||||
{
|
||||
/// <summary>Delegates that have registered to be notified when this condition changes.</summary>
|
||||
public readonly List<VariableObserverNotifier> Notifiers = new();
|
||||
|
||||
/// <summary>Unique integers identifying granted instances of the condition.</summary>
|
||||
public readonly HashSet<int> Tokens = new();
|
||||
get
|
||||
{
|
||||
// TODO: Support non-zero pitch/roll in IFacing (IOrientation?)
|
||||
var facingValue = facing != null ? facing.Facing : 0;
|
||||
return new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(facingValue));
|
||||
}
|
||||
}
|
||||
|
||||
readonly Dictionary<string, ConditionState> conditionStates = new();
|
||||
|
||||
/// <summary>Each granted condition receives a unique token that is used when revoking.</summary>
|
||||
readonly Dictionary<int, string> conditionTokens = new();
|
||||
|
||||
int nextConditionToken = 1;
|
||||
|
||||
/// <summary>Cache of condition -> enabled state for quick evaluation of token counter conditions.</summary>
|
||||
readonly Dictionary<string, int> conditionCache = new();
|
||||
|
||||
/// <summary>Read-only version of conditionCache that is passed to IConditionConsumers.</summary>
|
||||
readonly IReadOnlyDictionary<string, int> readOnlyConditionCache;
|
||||
|
||||
internal SyncHash[] SyncHashes { get; }
|
||||
internal IEnumerable<SyncHash> SyncHashes { get; private set; }
|
||||
|
||||
readonly IFacing facing;
|
||||
readonly IHealth health;
|
||||
readonly IResolveOrder[] resolveOrders;
|
||||
readonly IRenderModifier[] renderModifiers;
|
||||
readonly IRender[] renders;
|
||||
readonly IMouseBounds[] mouseBounds;
|
||||
readonly IDisable[] disables;
|
||||
readonly IVisibilityModifier[] visibilityModifiers;
|
||||
readonly IDefaultVisibility defaultVisibility;
|
||||
readonly INotifyBecomingIdle[] becomingIdles;
|
||||
readonly INotifyIdle[] tickIdles;
|
||||
readonly IEnumerable<WPos> enabledTargetableWorldPositions;
|
||||
bool created;
|
||||
|
||||
internal Actor(World world, string name, TypeDictionary initDict)
|
||||
{
|
||||
var duplicateInit = initDict.WithInterface<ISingleInstanceInit>().GroupBy(i => i.GetType())
|
||||
.FirstOrDefault(i => i.Count() > 1);
|
||||
|
||||
if (duplicateInit != null)
|
||||
throw new InvalidDataException($"Duplicate initializer '{duplicateInit.Key.Name}'");
|
||||
|
||||
var init = new ActorInitializer(this, initDict);
|
||||
|
||||
readOnlyConditionCache = new ReadOnlyDictionary<string, int>(conditionCache);
|
||||
|
||||
World = world;
|
||||
ActorID = world.NextAID();
|
||||
var ownerInit = init.GetOrDefault<OwnerInit>();
|
||||
if (ownerInit != null)
|
||||
Owner = ownerInit.Value(world);
|
||||
if (initDict.Contains<OwnerInit>())
|
||||
Owner = init.Get<OwnerInit, Player>();
|
||||
|
||||
if (name != null)
|
||||
{
|
||||
@@ -144,137 +97,75 @@ namespace OpenRA
|
||||
throw new NotImplementedException("No rules definition for unit " + name);
|
||||
|
||||
Info = world.Map.Rules.Actors[name];
|
||||
|
||||
var resolveOrdersList = new List<IResolveOrder>();
|
||||
var renderModifiersList = new List<IRenderModifier>();
|
||||
var rendersList = new List<IRender>();
|
||||
var mouseBoundsList = new List<IMouseBounds>();
|
||||
var visibilityModifiersList = new List<IVisibilityModifier>();
|
||||
var becomingIdlesList = new List<INotifyBecomingIdle>();
|
||||
var tickIdlesList = new List<INotifyIdle>();
|
||||
var targetablesList = new List<ITargetable>();
|
||||
var targetablePositionsList = new List<ITargetablePositions>();
|
||||
var syncHashesList = new List<SyncHash>();
|
||||
|
||||
foreach (var traitInfo in Info.TraitsInConstructOrder())
|
||||
foreach (var trait in Info.TraitsInConstructOrder())
|
||||
{
|
||||
var trait = traitInfo.Create(init);
|
||||
AddTrait(trait);
|
||||
AddTrait(trait.Create(init));
|
||||
|
||||
// PERF: Cache all these traits as soon as the actor is created. This is a fairly cheap one-off cost per
|
||||
// actor that allows us to provide some fast implementations of commonly used methods that are relied on by
|
||||
// performance-sensitive parts of the core game engine, such as pathfinding, visibility and rendering.
|
||||
// Note: The blocks are required to limit the scope of the t's, so we make an exception to our normal style
|
||||
// rules for spacing in order to keep these assignments compact and readable.
|
||||
{ if (trait is IOccupySpace t) OccupiesSpace = t; }
|
||||
{ if (trait is IEffectiveOwner t) EffectiveOwner = t; }
|
||||
{ if (trait is IFacing t) facing = t; }
|
||||
{ if (trait is IHealth t) health = t; }
|
||||
{ if (trait is IResolveOrder t) resolveOrdersList.Add(t); }
|
||||
{ if (trait is IRenderModifier t) renderModifiersList.Add(t); }
|
||||
{ if (trait is IRender t) rendersList.Add(t); }
|
||||
{ if (trait is IMouseBounds t) mouseBoundsList.Add(t); }
|
||||
{ if (trait is IVisibilityModifier t) visibilityModifiersList.Add(t); }
|
||||
{ if (trait is IDefaultVisibility t) defaultVisibility = t; }
|
||||
{ if (trait is INotifyBecomingIdle t) becomingIdlesList.Add(t); }
|
||||
{ if (trait is INotifyIdle t) tickIdlesList.Add(t); }
|
||||
{ if (trait is ITargetable t) targetablesList.Add(t); }
|
||||
{ if (trait is ITargetablePositions t) targetablePositionsList.Add(t); }
|
||||
{ if (trait is ISync t) syncHashesList.Add(new SyncHash(t)); }
|
||||
// Some traits rely on properties provided by IOccupySpace in their initialization,
|
||||
// so we must ready it now, we cannot wait until all traits have finished construction.
|
||||
if (trait is IOccupySpaceInfo)
|
||||
OccupiesSpace = Trait<IOccupySpace>();
|
||||
}
|
||||
|
||||
resolveOrders = resolveOrdersList.ToArray();
|
||||
renderModifiers = renderModifiersList.ToArray();
|
||||
renders = rendersList.ToArray();
|
||||
mouseBounds = mouseBoundsList.ToArray();
|
||||
visibilityModifiers = visibilityModifiersList.ToArray();
|
||||
becomingIdles = becomingIdlesList.ToArray();
|
||||
tickIdles = tickIdlesList.ToArray();
|
||||
Targetables = targetablesList.ToArray();
|
||||
var targetablePositions = targetablePositionsList.ToArray();
|
||||
EnabledTargetablePositions = targetablePositions.Where(Exts.IsTraitEnabled);
|
||||
enabledTargetableWorldPositions = EnabledTargetablePositions.SelectMany(tp => tp.TargetablePositions(this));
|
||||
SyncHashes = syncHashesList.ToArray();
|
||||
}
|
||||
|
||||
// PERF: Cache all these traits as soon as the actor is created. This is a fairly cheap one-off cost per
|
||||
// actor that allows us to provide some fast implementations of commonly used methods that are relied on by
|
||||
// performance-sensitive parts of the core game engine, such as pathfinding, visibility and rendering.
|
||||
Bounds = DetermineBounds();
|
||||
VisualBounds = DetermineVisualBounds();
|
||||
EffectiveOwner = TraitOrDefault<IEffectiveOwner>();
|
||||
facing = TraitOrDefault<IFacing>();
|
||||
health = TraitOrDefault<IHealth>();
|
||||
renderModifiers = TraitsImplementing<IRenderModifier>().ToArray();
|
||||
renders = TraitsImplementing<IRender>().ToArray();
|
||||
disables = TraitsImplementing<IDisable>().ToArray();
|
||||
visibilityModifiers = TraitsImplementing<IVisibilityModifier>().ToArray();
|
||||
defaultVisibility = Trait<IDefaultVisibility>();
|
||||
Targetables = TraitsImplementing<ITargetable>().ToArray();
|
||||
|
||||
SyncHashes =
|
||||
TraitsImplementing<ISync>()
|
||||
.Select(sync => Pair.New(sync, Sync.GetHashFunction(sync)))
|
||||
.ToArray()
|
||||
.Select(pair => new SyncHash(pair.First, pair.Second(pair.First)));
|
||||
}
|
||||
|
||||
internal void Initialize(bool addToWorld = true)
|
||||
Rectangle DetermineBounds()
|
||||
{
|
||||
created = true;
|
||||
var si = Info.TraitInfoOrDefault<SelectableInfo>();
|
||||
var size = (si != null && si.Bounds != null) ? new int2(si.Bounds[0], si.Bounds[1]) :
|
||||
TraitsImplementing<IAutoSelectionSize>().Select(x => x.SelectionSize(this)).FirstOrDefault();
|
||||
|
||||
// Make sure traits are usable for condition notifiers
|
||||
foreach (var t in TraitsImplementing<INotifyCreated>())
|
||||
t.Created(this);
|
||||
var offset = -size / 2;
|
||||
if (si != null && si.Bounds != null && si.Bounds.Length > 2)
|
||||
offset += new int2(si.Bounds[2], si.Bounds[3]);
|
||||
|
||||
var allObserverNotifiers = new HashSet<VariableObserverNotifier>();
|
||||
foreach (var provider in TraitsImplementing<IObservesVariables>())
|
||||
{
|
||||
foreach (var variableUser in provider.GetVariableObservers())
|
||||
{
|
||||
allObserverNotifiers.Add(variableUser.Notifier);
|
||||
foreach (var variable in variableUser.Variables)
|
||||
{
|
||||
var cs = conditionStates.GetOrAdd(variable);
|
||||
cs.Notifiers.Add(variableUser.Notifier);
|
||||
return new Rectangle(offset.X, offset.Y, size.X, size.Y);
|
||||
}
|
||||
|
||||
// Initialize conditions that have not yet been granted to 0
|
||||
// NOTE: Some conditions may have already been granted by INotifyCreated calling GrantCondition,
|
||||
// and we choose to assign the token count to safely cover both cases instead of adding an if branch.
|
||||
conditionCache[variable] = cs.Tokens.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle DetermineVisualBounds()
|
||||
{
|
||||
var sd = Info.TraitInfoOrDefault<ISelectionDecorationsInfo>();
|
||||
if (sd == null || sd.SelectionBoxBounds == null)
|
||||
return Bounds;
|
||||
|
||||
// Update all traits with their initial condition state
|
||||
foreach (var notify in allObserverNotifiers)
|
||||
notify(this, readOnlyConditionCache);
|
||||
var size = new int2(sd.SelectionBoxBounds[0], sd.SelectionBoxBounds[1]);
|
||||
|
||||
// TODO: Other traits may need initialization after being notified of initial condition state.
|
||||
var offset = -size / 2;
|
||||
if (sd.SelectionBoxBounds.Length > 2)
|
||||
offset += new int2(sd.SelectionBoxBounds[2], sd.SelectionBoxBounds[3]);
|
||||
|
||||
// TODO: A post condition initialization notification phase may allow queueing activities instead.
|
||||
// The initial activity should run before any activities queued by INotifyCreated.Created
|
||||
// However, we need to know which traits are enabled (via conditions), so wait for after the calls and insert the activity as the first
|
||||
ICreationActivity creationActivity = null;
|
||||
foreach (var ica in TraitsImplementing<ICreationActivity>())
|
||||
{
|
||||
if (!ica.IsTraitEnabled())
|
||||
continue;
|
||||
|
||||
if (creationActivity != null)
|
||||
throw new InvalidOperationException($"More than one enabled ICreationActivity trait: {creationActivity.GetType().Name} and {ica.GetType().Name}");
|
||||
|
||||
var activity = ica.GetCreationActivity();
|
||||
if (activity == null)
|
||||
continue;
|
||||
|
||||
creationActivity = ica;
|
||||
|
||||
activity.Queue(CurrentActivity);
|
||||
CurrentActivity = activity;
|
||||
}
|
||||
|
||||
if (addToWorld)
|
||||
World.Add(this);
|
||||
return new Rectangle(offset.X, offset.Y, size.X, size.Y);
|
||||
}
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
var wasIdle = IsIdle;
|
||||
CurrentActivity = ActivityUtils.RunActivity(this, CurrentActivity);
|
||||
currentActivity = ActivityUtils.RunActivity(this, currentActivity);
|
||||
|
||||
if (!wasIdle && IsIdle)
|
||||
{
|
||||
foreach (var n in becomingIdles)
|
||||
foreach (var n in TraitsImplementing<INotifyBecomingIdle>())
|
||||
n.OnBecomingIdle(this);
|
||||
|
||||
// If IsIdle is true, it means the last CurrentActivity.Tick returned null.
|
||||
// If a next activity has been queued via OnBecomingIdle, we need to start running it now,
|
||||
// to avoid an 'empty' null tick where the actor will (visibly, if moving) do nothing.
|
||||
CurrentActivity = ActivityUtils.RunActivity(this, CurrentActivity);
|
||||
}
|
||||
else if (wasIdle)
|
||||
foreach (var tickIdle in tickIdles)
|
||||
tickIdle.TickIdle(this);
|
||||
}
|
||||
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer wr)
|
||||
@@ -300,57 +191,30 @@ namespace OpenRA
|
||||
yield return renderable;
|
||||
}
|
||||
|
||||
public IEnumerable<Rectangle> ScreenBounds(WorldRenderer wr)
|
||||
{
|
||||
var bounds = Bounds(wr);
|
||||
foreach (var modifier in renderModifiers)
|
||||
bounds = modifier.ModifyScreenBounds(this, wr, bounds);
|
||||
return bounds;
|
||||
}
|
||||
|
||||
IEnumerable<Rectangle> Bounds(WorldRenderer wr)
|
||||
{
|
||||
// PERF: Avoid LINQ. See comments for Renderables
|
||||
foreach (var render in renders)
|
||||
foreach (var r in render.ScreenBounds(this, wr))
|
||||
if (!r.IsEmpty)
|
||||
yield return r;
|
||||
}
|
||||
|
||||
public Polygon MouseBounds(WorldRenderer wr)
|
||||
{
|
||||
foreach (var mb in mouseBounds)
|
||||
{
|
||||
var bounds = mb.MouseoverBounds(this, wr);
|
||||
if (!bounds.IsEmpty)
|
||||
return bounds;
|
||||
}
|
||||
|
||||
return Polygon.Empty;
|
||||
}
|
||||
|
||||
public void QueueActivity(bool queued, Activity nextActivity)
|
||||
{
|
||||
if (!queued)
|
||||
CancelActivity();
|
||||
|
||||
QueueActivity(nextActivity);
|
||||
}
|
||||
|
||||
public void QueueActivity(Activity nextActivity)
|
||||
{
|
||||
if (!created)
|
||||
throw new InvalidOperationException("An activity was queued before the actor was created. Queue it inside the INotifyCreated.Created callback instead.");
|
||||
|
||||
if (CurrentActivity == null)
|
||||
CurrentActivity = nextActivity;
|
||||
if (currentActivity == null)
|
||||
currentActivity = nextActivity;
|
||||
else
|
||||
CurrentActivity.Queue(nextActivity);
|
||||
currentActivity.Queue(nextActivity);
|
||||
}
|
||||
|
||||
public void CancelActivity()
|
||||
{
|
||||
CurrentActivity?.Cancel(this);
|
||||
if (currentActivity != null)
|
||||
currentActivity.Cancel(this);
|
||||
}
|
||||
|
||||
public Activity GetCurrentActivity()
|
||||
{
|
||||
return currentActivity;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
@@ -360,7 +224,8 @@ namespace OpenRA
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Actor o && Equals(o);
|
||||
var o = obj as Actor;
|
||||
return o != null && Equals(o);
|
||||
}
|
||||
|
||||
public bool Equals(Actor other)
|
||||
@@ -399,13 +264,6 @@ namespace OpenRA
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// If CurrentActivity isn't null, run OnActorDisposeOuter in case some cleanups are needed.
|
||||
// This should be done before the FrameEndTask to avoid dependency issues.
|
||||
CurrentActivity?.OnActorDisposeOuter(this);
|
||||
|
||||
// Allow traits/activities to prevent a race condition when they depend on disposing the actor (e.g. Transforms)
|
||||
WillDispose = true;
|
||||
|
||||
World.AddFrameEndTask(w =>
|
||||
{
|
||||
if (Disposed)
|
||||
@@ -420,49 +278,35 @@ namespace OpenRA
|
||||
World.TraitDict.RemoveActor(this);
|
||||
Disposed = true;
|
||||
|
||||
luaInterface?.Value.OnActorDestroyed();
|
||||
if (luaInterface != null)
|
||||
luaInterface.Value.OnActorDestroyed();
|
||||
});
|
||||
}
|
||||
|
||||
public void ResolveOrder(Order order)
|
||||
{
|
||||
foreach (var r in resolveOrders)
|
||||
r.ResolveOrder(this, order);
|
||||
}
|
||||
|
||||
// TODO: move elsewhere.
|
||||
public void ChangeOwner(Player newOwner)
|
||||
{
|
||||
World.AddFrameEndTask(_ => ChangeOwnerSync(newOwner));
|
||||
}
|
||||
World.AddFrameEndTask(w =>
|
||||
{
|
||||
if (Disposed)
|
||||
return;
|
||||
|
||||
/// <summary>
|
||||
/// Change the actors owner without queuing a FrameEndTask.
|
||||
/// This must only be called from inside an existing FrameEndTask.
|
||||
/// </summary>
|
||||
public void ChangeOwnerSync(Player newOwner)
|
||||
{
|
||||
if (Disposed)
|
||||
return;
|
||||
var oldOwner = Owner;
|
||||
var wasInWorld = IsInWorld;
|
||||
|
||||
var oldOwner = Owner;
|
||||
var wasInWorld = IsInWorld;
|
||||
// momentarily remove from world so the ownership queries don't get confused
|
||||
if (wasInWorld)
|
||||
w.Remove(this);
|
||||
|
||||
// momentarily remove from world so the ownership queries don't get confused
|
||||
if (wasInWorld)
|
||||
World.Remove(this);
|
||||
Owner = newOwner;
|
||||
Generation++;
|
||||
|
||||
Owner = newOwner;
|
||||
Generation++;
|
||||
foreach (var t in TraitsImplementing<INotifyOwnerChanged>())
|
||||
t.OnOwnerChanged(this, oldOwner, newOwner);
|
||||
|
||||
foreach (var t in TraitsImplementing<INotifyOwnerChanged>())
|
||||
t.OnOwnerChanged(this, oldOwner, newOwner);
|
||||
|
||||
foreach (var t in World.WorldActor.TraitsImplementing<INotifyOwnerChanged>())
|
||||
t.OnOwnerChanged(this, oldOwner, newOwner);
|
||||
|
||||
if (wasInWorld)
|
||||
World.Add(this);
|
||||
if (wasInWorld)
|
||||
w.Add(this);
|
||||
});
|
||||
}
|
||||
|
||||
public DamageState GetDamageState()
|
||||
@@ -481,12 +325,21 @@ namespace OpenRA
|
||||
health.InflictDamage(this, attacker, damage, false);
|
||||
}
|
||||
|
||||
public void Kill(Actor attacker, BitSet<DamageType> damageTypes = default)
|
||||
public void Kill(Actor attacker)
|
||||
{
|
||||
if (Disposed || health == null)
|
||||
return;
|
||||
|
||||
health.Kill(this, attacker, damageTypes);
|
||||
health.Kill(this, attacker);
|
||||
}
|
||||
|
||||
public bool IsDisabled()
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
foreach (var disable in disables)
|
||||
if (disable.Disabled)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanBeViewedByPlayer(Player player)
|
||||
@@ -499,119 +352,52 @@ namespace OpenRA
|
||||
return defaultVisibility.IsVisible(this, player);
|
||||
}
|
||||
|
||||
public BitSet<TargetableType> GetAllTargetTypes()
|
||||
public IEnumerable<string> GetAllTargetTypes()
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
var targetTypes = default(BitSet<TargetableType>);
|
||||
foreach (var targetable in Targetables)
|
||||
targetTypes = targetTypes.Union(targetable.TargetTypes);
|
||||
return targetTypes;
|
||||
foreach (var targetType in targetable.TargetTypes)
|
||||
yield return targetType;
|
||||
}
|
||||
|
||||
public BitSet<TargetableType> GetEnabledTargetTypes()
|
||||
public IEnumerable<string> GetEnabledTargetTypes()
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
var targetTypes = default(BitSet<TargetableType>);
|
||||
foreach (var targetable in Targetables)
|
||||
if (targetable.IsTraitEnabled())
|
||||
targetTypes = targetTypes.Union(targetable.TargetTypes);
|
||||
return targetTypes;
|
||||
foreach (var targetType in targetable.TargetTypes)
|
||||
yield return targetType;
|
||||
}
|
||||
|
||||
public bool IsTargetableBy(Actor byActor)
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
foreach (var targetable in Targetables)
|
||||
if (targetable.TargetableBy(this, byActor))
|
||||
if (targetable.IsTraitEnabled() && targetable.TargetableBy(this, byActor))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public IEnumerable<WPos> GetTargetablePositions()
|
||||
{
|
||||
if (EnabledTargetablePositions.Any())
|
||||
return enabledTargetableWorldPositions;
|
||||
|
||||
return new[] { CenterPosition };
|
||||
}
|
||||
|
||||
#region Conditions
|
||||
|
||||
void UpdateConditionState(string condition, int token, bool isRevoke)
|
||||
{
|
||||
var conditionState = conditionStates.GetOrAdd(condition);
|
||||
|
||||
if (isRevoke)
|
||||
conditionState.Tokens.Remove(token);
|
||||
else
|
||||
conditionState.Tokens.Add(token);
|
||||
|
||||
conditionCache[condition] = conditionState.Tokens.Count;
|
||||
|
||||
// Conditions may be granted or revoked before the state is initialized.
|
||||
// These notifications will be processed after INotifyCreated.Created.
|
||||
if (created)
|
||||
foreach (var notify in conditionState.Notifiers)
|
||||
notify(this, readOnlyConditionCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grants a specified condition if it is valid.
|
||||
/// Otherwise, just returns InvalidConditionToken.
|
||||
/// </summary>
|
||||
/// <returns>The token that is used to revoke this condition.</returns>
|
||||
public int GrantCondition(string condition)
|
||||
{
|
||||
if (string.IsNullOrEmpty(condition))
|
||||
return InvalidConditionToken;
|
||||
|
||||
var token = nextConditionToken++;
|
||||
conditionTokens.Add(token, condition);
|
||||
UpdateConditionState(condition, token, false);
|
||||
return token;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Revokes a previously granted condition.
|
||||
/// </summary>
|
||||
/// <param name="token">The token ID returned by GrantCondition.</param>
|
||||
/// <returns>The invalid token ID.</returns>
|
||||
public int RevokeCondition(int token)
|
||||
{
|
||||
if (!conditionTokens.TryGetValue(token, out var condition))
|
||||
throw new InvalidOperationException($"Attempting to revoke condition with invalid token {token} for {this}.");
|
||||
|
||||
conditionTokens.Remove(token);
|
||||
UpdateConditionState(condition, token, true);
|
||||
return InvalidConditionToken;
|
||||
}
|
||||
|
||||
/// <summary>Returns whether the specified token is valid for RevokeCondition.</summary>
|
||||
public bool TokenValid(int token)
|
||||
{
|
||||
return conditionTokens.ContainsKey(token);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Scripting interface
|
||||
|
||||
Lazy<ScriptActorInterface> luaInterface;
|
||||
public void OnScriptBind(ScriptContext context)
|
||||
{
|
||||
luaInterface ??= Exts.Lazy(() => new ScriptActorInterface(context, this));
|
||||
if (luaInterface == null)
|
||||
luaInterface = Exts.Lazy(() => new ScriptActorInterface(context, this));
|
||||
}
|
||||
|
||||
public LuaValue this[LuaRuntime runtime, LuaValue keyValue]
|
||||
{
|
||||
get => luaInterface.Value[runtime, keyValue];
|
||||
set => luaInterface.Value[runtime, keyValue] = value;
|
||||
get { return luaInterface.Value[runtime, keyValue]; }
|
||||
set { luaInterface.Value[runtime, keyValue] = value; }
|
||||
}
|
||||
|
||||
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
if (!left.TryGetClrValue(out Actor a) || !right.TryGetClrValue(out Actor b))
|
||||
Actor a, b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
return false;
|
||||
|
||||
return a == b;
|
||||
@@ -619,7 +405,7 @@ namespace OpenRA
|
||||
|
||||
public LuaValue ToString(LuaRuntime runtime)
|
||||
{
|
||||
return $"Actor ({this})";
|
||||
return "Actor ({0})".F(this);
|
||||
}
|
||||
|
||||
public bool HasScriptProperty(string name)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -16,55 +16,32 @@ using OpenRA.Scripting;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public readonly struct CPos : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaEqualityBinding, ILuaTableBinding, IEquatable<CPos>
|
||||
public struct CPos : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaEqualityBinding, ILuaTableBinding, IEquatable<CPos>
|
||||
{
|
||||
// Coordinates are packed in a 32 bit signed int
|
||||
// X and Y are 12 bits (signed): -2048...2047
|
||||
// Layer is an unsigned byte
|
||||
// Packing is XXXX XXXX XXXX YYYY YYYY YYYY LLLL LLLL
|
||||
public readonly int Bits;
|
||||
public readonly int X, Y;
|
||||
|
||||
// X is padded to MSB, so bit shift does the correct sign extension
|
||||
public int X => Bits >> 20;
|
||||
|
||||
// Align Y with a short, cast, then shift the rest of the way
|
||||
// The signed short bit shift does the correct sign extension
|
||||
public int Y => ((short)(Bits >> 4)) >> 4;
|
||||
|
||||
public byte Layer => (byte)Bits;
|
||||
|
||||
public CPos(int bits) { Bits = bits; }
|
||||
public CPos(int x, int y)
|
||||
: this(x, y, 0) { }
|
||||
public CPos(int x, int y, byte layer)
|
||||
{
|
||||
Bits = (x & 0xFFF) << 20 | (y & 0xFFF) << 8 | layer;
|
||||
}
|
||||
|
||||
public static readonly CPos Zero = new(0, 0, 0);
|
||||
public CPos(int x, int y) { X = x; Y = y; }
|
||||
public static readonly CPos Zero = new CPos(0, 0);
|
||||
|
||||
public static explicit operator CPos(int2 a) { return new CPos(a.X, a.Y); }
|
||||
|
||||
public static CPos operator +(CVec a, CPos b) { return new CPos(a.X + b.X, a.Y + b.Y, b.Layer); }
|
||||
public static CPos operator +(CPos a, CVec b) { return new CPos(a.X + b.X, a.Y + b.Y, a.Layer); }
|
||||
public static CPos operator -(CPos a, CVec b) { return new CPos(a.X - b.X, a.Y - b.Y, a.Layer); }
|
||||
public static CPos operator +(CVec a, CPos b) { return new CPos(a.X + b.X, a.Y + b.Y); }
|
||||
public static CPos operator +(CPos a, CVec b) { return new CPos(a.X + b.X, a.Y + b.Y); }
|
||||
public static CPos operator -(CPos a, CVec b) { return new CPos(a.X - b.X, a.Y - b.Y); }
|
||||
public static CVec operator -(CPos a, CPos b) { return new CVec(a.X - b.X, a.Y - b.Y); }
|
||||
|
||||
public static bool operator ==(CPos me, CPos other) { return me.Bits == other.Bits; }
|
||||
public static bool operator ==(CPos me, CPos other) { return me.X == other.X && me.Y == other.Y; }
|
||||
public static bool operator !=(CPos me, CPos other) { return !(me == other); }
|
||||
|
||||
public override int GetHashCode() { return Bits.GetHashCode(); }
|
||||
public static CPos Max(CPos a, CPos b) { return new CPos(Math.Max(a.X, b.X), Math.Max(a.Y, b.Y)); }
|
||||
public static CPos Min(CPos a, CPos b) { return new CPos(Math.Min(a.X, b.X), Math.Min(a.Y, b.Y)); }
|
||||
|
||||
public bool Equals(CPos other) { return Bits == other.Bits; }
|
||||
public override bool Equals(object obj) { return obj is CPos cell && Equals(cell); }
|
||||
public override int GetHashCode() { return X.GetHashCode() ^ Y.GetHashCode(); }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (Layer == 0)
|
||||
return X + "," + Y;
|
||||
public bool Equals(CPos other) { return X == other.X && Y == other.Y; }
|
||||
public override bool Equals(object obj) { return obj is CPos && Equals((CPos)obj); }
|
||||
|
||||
return X + "," + Y + "," + Layer;
|
||||
}
|
||||
public override string ToString() { return X + "," + Y; }
|
||||
|
||||
public MPos ToMPos(Map map)
|
||||
{
|
||||
@@ -95,35 +72,28 @@ namespace OpenRA
|
||||
|
||||
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
if (!left.TryGetClrValue(out CPos a) || !right.TryGetClrValue(out CVec b))
|
||||
throw new LuaException($"Attempted to call CPos.Add(CPos, CVec) with invalid arguments ({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
|
||||
CPos a;
|
||||
CVec b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
throw new LuaException("Attempted to call CPos.Add(CPos, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
|
||||
|
||||
return new LuaCustomClrObject(a + b);
|
||||
}
|
||||
|
||||
public LuaValue Subtract(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
var rightType = right.WrappedClrType();
|
||||
if (!left.TryGetClrValue(out CPos a))
|
||||
throw new LuaException($"Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments ({left.WrappedClrType().Name}, {rightType.Name})");
|
||||
CPos a;
|
||||
CVec b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
throw new LuaException("Attempted to call CPos.Subtract(CPos, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
|
||||
|
||||
if (rightType == typeof(CPos))
|
||||
{
|
||||
right.TryGetClrValue(out CPos b);
|
||||
return new LuaCustomClrObject(a - b);
|
||||
}
|
||||
else if (rightType == typeof(CVec))
|
||||
{
|
||||
right.TryGetClrValue(out CVec b);
|
||||
return new LuaCustomClrObject(a - b);
|
||||
}
|
||||
|
||||
throw new LuaException($"Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments ({left.WrappedClrType().Name}, {rightType.Name})");
|
||||
return new LuaCustomClrObject(a - b);
|
||||
}
|
||||
|
||||
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
if (!left.TryGetClrValue(out CPos a) || !right.TryGetClrValue(out CPos b))
|
||||
CPos a, b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
return false;
|
||||
|
||||
return a == b;
|
||||
@@ -137,14 +107,16 @@ namespace OpenRA
|
||||
{
|
||||
case "X": return X;
|
||||
case "Y": return Y;
|
||||
case "Layer": return Layer;
|
||||
default: throw new LuaException($"CPos does not define a member '{key}'");
|
||||
default: throw new LuaException("CPos does not define a member '{0}'".F(key));
|
||||
}
|
||||
}
|
||||
|
||||
set => throw new LuaException("CPos is read-only. Use CPos.New to create a new value");
|
||||
set
|
||||
{
|
||||
throw new LuaException("CPos is read-only. Use CPos.New to create a new value");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -10,19 +10,19 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using Eluant;
|
||||
using Eluant.ObjectBinding;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Scripting;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public readonly struct CVec : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaUnaryMinusBinding, ILuaEqualityBinding, ILuaTableBinding, IEquatable<CVec>
|
||||
public struct CVec : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaUnaryMinusBinding, ILuaEqualityBinding, ILuaTableBinding, IEquatable<CVec>
|
||||
{
|
||||
public readonly int X, Y;
|
||||
|
||||
public CVec(int x, int y) { X = x; Y = y; }
|
||||
public static readonly CVec Zero = new(0, 0);
|
||||
public static readonly CVec Zero = new CVec(0, 0);
|
||||
|
||||
public static CVec operator +(CVec a, CVec b) { return new CVec(a.X + b.X, a.Y + b.Y); }
|
||||
public static CVec operator -(CVec a, CVec b) { return new CVec(a.X - b.X, a.Y - b.Y); }
|
||||
@@ -42,8 +42,8 @@ namespace OpenRA
|
||||
|
||||
public CVec Sign() { return new CVec(Math.Sign(X), Math.Sign(Y)); }
|
||||
public CVec Abs() { return new CVec(Math.Abs(X), Math.Abs(Y)); }
|
||||
public int LengthSquared => X * X + Y * Y;
|
||||
public int Length => Exts.ISqrt(LengthSquared);
|
||||
public int LengthSquared { get { return X * X + Y * Y; } }
|
||||
public int Length { get { return Exts.ISqrt(LengthSquared); } }
|
||||
|
||||
public CVec Clamp(Rectangle r)
|
||||
{
|
||||
@@ -55,7 +55,7 @@ namespace OpenRA
|
||||
public override int GetHashCode() { return X.GetHashCode() ^ Y.GetHashCode(); }
|
||||
|
||||
public bool Equals(CVec other) { return other == this; }
|
||||
public override bool Equals(object obj) { return obj is CVec vec && Equals(vec); }
|
||||
public override bool Equals(object obj) { return obj is CVec && Equals((CVec)obj); }
|
||||
|
||||
public override string ToString() { return X + "," + Y; }
|
||||
|
||||
@@ -75,16 +75,18 @@ namespace OpenRA
|
||||
|
||||
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
|
||||
throw new LuaException($"Attempted to call CVec.Add(CVec, CVec) with invalid arguments ({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
|
||||
CVec a, b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
throw new LuaException("Attempted to call CVec.Add(CVec, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
|
||||
|
||||
return new LuaCustomClrObject(a + b);
|
||||
}
|
||||
|
||||
public LuaValue Subtract(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
|
||||
throw new LuaException($"Attempted to call CVec.Subtract(CVec, CVec) with invalid arguments ({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
|
||||
CVec a, b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
throw new LuaException("Attempted to call CVec.Subtract(CVec, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
|
||||
|
||||
return new LuaCustomClrObject(a - b);
|
||||
}
|
||||
@@ -96,7 +98,8 @@ namespace OpenRA
|
||||
|
||||
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
|
||||
CVec a, b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
return false;
|
||||
|
||||
return a == b;
|
||||
@@ -110,11 +113,14 @@ namespace OpenRA
|
||||
{
|
||||
case "X": return X;
|
||||
case "Y": return Y;
|
||||
default: throw new LuaException($"CVec does not define a member '{key}'");
|
||||
default: throw new LuaException("CVec does not define a member '{0}'".F(key));
|
||||
}
|
||||
}
|
||||
|
||||
set => throw new LuaException("CVec is read-only. Use CVec.New to create a new value");
|
||||
set
|
||||
{
|
||||
throw new LuaException("CVec is read-only. Use CVec.New to create a new value");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -9,12 +9,12 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class DefaultPlayer : IGlobalModData
|
||||
public interface ICacheStorage<T>
|
||||
{
|
||||
public readonly Color Color = Color.FromArgb(0xEE, 0xEE, 0xEE);
|
||||
void Remove(string key);
|
||||
void Store(string key, T data);
|
||||
T Retrieve(string key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,261 +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;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public static class CryptoUtil
|
||||
{
|
||||
// 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 };
|
||||
|
||||
public static string PublicKeyFingerprint(RSAParameters parameters)
|
||||
{
|
||||
// Public key fingerprint is defined as the SHA1 of the modulus + exponent bytes
|
||||
return SHA1Hash(parameters.Modulus.Append(parameters.Exponent).ToArray());
|
||||
}
|
||||
|
||||
public static string EncodePEMPublicKey(RSAParameters parameters)
|
||||
{
|
||||
var data = Convert.ToBase64String(EncodePublicKey(parameters));
|
||||
var output = new StringBuilder();
|
||||
output.AppendLine("-----BEGIN PUBLIC KEY-----");
|
||||
for (var i = 0; i < data.Length; i += 64)
|
||||
output.AppendLine(data.Substring(i, Math.Min(64, data.Length - i)));
|
||||
output.Append("-----END PUBLIC KEY-----");
|
||||
|
||||
return output.ToString();
|
||||
}
|
||||
|
||||
public static RSAParameters DecodePEMPublicKey(string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Reconstruct original key data
|
||||
var lines = key.Split('\n');
|
||||
var data = Convert.FromBase64String(lines.Skip(1).Take(lines.Length - 2).JoinWith(""));
|
||||
|
||||
// Pull the modulus and exponent bytes out of the ASN.1 tree
|
||||
// Expect this to blow up if the key is not correctly formatted
|
||||
using (var s = new MemoryStream(data))
|
||||
{
|
||||
// SEQUENCE
|
||||
s.ReadByte();
|
||||
ReadTLVLength(s);
|
||||
|
||||
// SEQUENCE -> fixed header junk
|
||||
s.ReadByte();
|
||||
var headerLength = ReadTLVLength(s);
|
||||
s.Position += headerLength;
|
||||
|
||||
// SEQUENCE -> BIT_STRING
|
||||
s.ReadByte();
|
||||
ReadTLVLength(s);
|
||||
s.ReadByte();
|
||||
|
||||
// SEQUENCE -> BIT_STRING -> SEQUENCE
|
||||
s.ReadByte();
|
||||
ReadTLVLength(s);
|
||||
|
||||
// SEQUENCE -> BIT_STRING -> SEQUENCE -> INTEGER (modulus)
|
||||
s.ReadByte();
|
||||
var modulusLength = ReadTLVLength(s);
|
||||
s.ReadByte();
|
||||
var modulus = s.ReadBytes(modulusLength - 1);
|
||||
|
||||
// SEQUENCE -> BIT_STRING -> SEQUENCE -> INTEGER (exponent)
|
||||
s.ReadByte();
|
||||
var exponentLength = ReadTLVLength(s);
|
||||
s.ReadByte();
|
||||
var exponent = s.ReadBytes(exponentLength - 1);
|
||||
|
||||
return new RSAParameters
|
||||
{
|
||||
Modulus = modulus,
|
||||
Exponent = exponent
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidDataException("Invalid PEM public key", e);
|
||||
}
|
||||
}
|
||||
|
||||
static byte[] EncodePublicKey(RSAParameters parameters)
|
||||
{
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
var writer = new BinaryWriter(stream);
|
||||
|
||||
var modExpLength = TripletFullLength(parameters.Modulus.Length + 1) + TripletFullLength(parameters.Exponent.Length + 1);
|
||||
var bitStringLength = TripletFullLength(modExpLength + 1);
|
||||
var sequenceLength = TripletFullLength(bitStringLength + OIDHeader.Length);
|
||||
|
||||
// SEQUENCE
|
||||
writer.Write((byte)0x30);
|
||||
WriteTLVLength(writer, sequenceLength);
|
||||
|
||||
// SEQUENCE -> fixed header junk
|
||||
writer.Write(OIDHeader);
|
||||
|
||||
// SEQUENCE -> BIT_STRING
|
||||
writer.Write((byte)0x03);
|
||||
WriteTLVLength(writer, bitStringLength);
|
||||
writer.Write((byte)0x00);
|
||||
|
||||
// SEQUENCE -> BIT_STRING -> SEQUENCE
|
||||
writer.Write((byte)0x30);
|
||||
WriteTLVLength(writer, modExpLength);
|
||||
|
||||
// SEQUENCE -> BIT_STRING -> SEQUENCE -> INTEGER
|
||||
// Modulus is padded with a zero to avoid issues with the sign bit
|
||||
writer.Write((byte)0x02);
|
||||
WriteTLVLength(writer, parameters.Modulus.Length + 1);
|
||||
writer.Write((byte)0);
|
||||
writer.Write(parameters.Modulus);
|
||||
|
||||
// SEQUENCE -> BIT_STRING -> SEQUENCE -> INTEGER
|
||||
// Exponent is padded with a zero to avoid issues with the sign bit
|
||||
writer.Write((byte)0x02);
|
||||
WriteTLVLength(writer, parameters.Exponent.Length + 1);
|
||||
writer.Write((byte)0);
|
||||
writer.Write(parameters.Exponent);
|
||||
|
||||
return stream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
static void WriteTLVLength(BinaryWriter writer, int length)
|
||||
{
|
||||
if (length < 0x80)
|
||||
{
|
||||
// Length < 128 is stored in a single byte
|
||||
writer.Write((byte)length);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If 128 <= length < 256**128 first byte encodes number of bytes required to hold the length
|
||||
// High-bit is set as a flag to use this long-form encoding
|
||||
var lengthBytes = BitConverter.GetBytes(length).Reverse().SkipWhile(b => b == 0).ToArray();
|
||||
writer.Write((byte)(0x80 | lengthBytes.Length));
|
||||
writer.Write(lengthBytes);
|
||||
}
|
||||
}
|
||||
|
||||
static int ReadTLVLength(Stream s)
|
||||
{
|
||||
var length = s.ReadByte();
|
||||
if (length < 0x80)
|
||||
return length;
|
||||
|
||||
var data = new byte[4];
|
||||
s.ReadBytes(data, 0, Math.Min(length & 0x7F, 4));
|
||||
return BitConverter.ToInt32(data.ToArray(), 0);
|
||||
}
|
||||
|
||||
static int TripletFullLength(int dataLength)
|
||||
{
|
||||
if (dataLength < 0x80)
|
||||
return 2 + dataLength;
|
||||
|
||||
return 2 + dataLength + BitConverter.GetBytes(dataLength).Reverse().SkipWhile(b => b == 0).Count();
|
||||
}
|
||||
|
||||
public static string DecryptString(RSAParameters parameters, string data)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var rsa = new RSACryptoServiceProvider())
|
||||
{
|
||||
rsa.ImportParameters(parameters);
|
||||
return Encoding.UTF8.GetString(rsa.Decrypt(Convert.FromBase64String(data), false));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to decrypt string with exception: {0}", e);
|
||||
Console.WriteLine("String decryption failed: {0}", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static string Sign(RSAParameters parameters, string data)
|
||||
{
|
||||
return Sign(parameters, Encoding.UTF8.GetBytes(data));
|
||||
}
|
||||
|
||||
public static string Sign(RSAParameters parameters, byte[] data)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var rsa = new RSACryptoServiceProvider())
|
||||
{
|
||||
rsa.ImportParameters(parameters);
|
||||
using (var csp = SHA1.Create())
|
||||
return Convert.ToBase64String(rsa.SignHash(csp.ComputeHash(data), CryptoConfig.MapNameToOID("SHA1")));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to sign string with exception: {0}", e);
|
||||
Console.WriteLine("String signing failed: {0}", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool VerifySignature(RSAParameters parameters, string data, string signature)
|
||||
{
|
||||
return VerifySignature(parameters, Encoding.UTF8.GetBytes(data), signature);
|
||||
}
|
||||
|
||||
public static bool VerifySignature(RSAParameters parameters, byte[] data, string signature)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var rsa = new RSACryptoServiceProvider())
|
||||
{
|
||||
rsa.ImportParameters(parameters);
|
||||
using (var csp = SHA1.Create())
|
||||
return rsa.VerifyHash(csp.ComputeHash(data), CryptoConfig.MapNameToOID("SHA1"), Convert.FromBase64String(signature));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to verify signature with exception: {0}", e);
|
||||
Console.WriteLine("Signature validation failed: {0}", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static string SHA1Hash(Stream data)
|
||||
{
|
||||
using (var csp = SHA1.Create())
|
||||
return new string(csp.ComputeHash(data).SelectMany(a => a.ToString("x2")).ToArray());
|
||||
}
|
||||
|
||||
public static string SHA1Hash(byte[] data)
|
||||
{
|
||||
using (var csp = SHA1.Create())
|
||||
return new string(csp.ComputeHash(data).SelectMany(a => a.ToString("x2")).ToArray());
|
||||
}
|
||||
|
||||
public static string SHA1Hash(string data)
|
||||
{
|
||||
return SHA1Hash(Encoding.UTF8.GetBytes(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
84
OpenRA.Game/Download.cs
Normal file
84
OpenRA.Game/Download.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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.ComponentModel;
|
||||
using System.Net;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class Download
|
||||
{
|
||||
readonly object syncObject = new object();
|
||||
WebClient wc;
|
||||
|
||||
public static string FormatErrorMessage(Exception e)
|
||||
{
|
||||
var ex = e as WebException;
|
||||
if (ex == null)
|
||||
return e.Message;
|
||||
|
||||
switch (ex.Status)
|
||||
{
|
||||
case WebExceptionStatus.RequestCanceled:
|
||||
return "Cancelled";
|
||||
case WebExceptionStatus.NameResolutionFailure:
|
||||
return "DNS lookup failed";
|
||||
case WebExceptionStatus.Timeout:
|
||||
return "Connection timeout";
|
||||
case WebExceptionStatus.ConnectFailure:
|
||||
return "Cannot connect to remote server";
|
||||
case WebExceptionStatus.ProtocolError:
|
||||
return "File not found on remote server";
|
||||
default:
|
||||
return ex.Message;
|
||||
}
|
||||
}
|
||||
|
||||
public Download(string url, string path, Action<DownloadProgressChangedEventArgs> onProgress, Action<AsyncCompletedEventArgs> onComplete)
|
||||
{
|
||||
lock (syncObject)
|
||||
{
|
||||
wc = new WebClient { Proxy = null };
|
||||
wc.DownloadProgressChanged += (_, a) => onProgress(a);
|
||||
wc.DownloadFileCompleted += (_, a) => { DisposeWebClient(); onComplete(a); };
|
||||
wc.DownloadFileAsync(new Uri(url), path);
|
||||
}
|
||||
}
|
||||
|
||||
public Download(string url, Action<DownloadProgressChangedEventArgs> onProgress, Action<DownloadDataCompletedEventArgs> onComplete)
|
||||
{
|
||||
lock (syncObject)
|
||||
{
|
||||
wc = new WebClient { Proxy = null };
|
||||
wc.DownloadProgressChanged += (_, a) => onProgress(a);
|
||||
wc.DownloadDataCompleted += (_, a) => { DisposeWebClient(); onComplete(a); };
|
||||
wc.DownloadDataAsync(new Uri(url));
|
||||
}
|
||||
}
|
||||
|
||||
void DisposeWebClient()
|
||||
{
|
||||
lock (syncObject)
|
||||
{
|
||||
wc.Dispose();
|
||||
wc = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void CancelAsync()
|
||||
{
|
||||
lock (syncObject)
|
||||
if (wc != null)
|
||||
wc.CancelAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
39
OpenRA.Game/Effects/AsyncAction.cs
Normal file
39
OpenRA.Game/Effects/AsyncAction.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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 OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA.Effects
|
||||
{
|
||||
public class AsyncAction : IEffect
|
||||
{
|
||||
Action a;
|
||||
IAsyncResult ar;
|
||||
|
||||
public AsyncAction(IAsyncResult ar, Action a)
|
||||
{
|
||||
this.a = a;
|
||||
this.ar = ar;
|
||||
}
|
||||
|
||||
public void Tick(World world)
|
||||
{
|
||||
if (ar.IsCompleted)
|
||||
{
|
||||
world.AddFrameEndTask(w => { w.Remove(this); a(); });
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer r) { yield break; }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -17,7 +17,7 @@ namespace OpenRA.Effects
|
||||
{
|
||||
public class DelayedAction : IEffect
|
||||
{
|
||||
readonly Action a;
|
||||
Action a;
|
||||
int delay;
|
||||
|
||||
public DelayedAction(int delay, Action a)
|
||||
|
||||
@@ -1,43 +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.GameRules;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Effects
|
||||
{
|
||||
public class DelayedImpact : IEffect
|
||||
{
|
||||
readonly Target target;
|
||||
readonly IWarhead wh;
|
||||
readonly WarheadArgs args;
|
||||
|
||||
int delay;
|
||||
|
||||
public DelayedImpact(int delay, IWarhead wh, Target target, WarheadArgs args)
|
||||
{
|
||||
this.wh = wh;
|
||||
this.delay = delay;
|
||||
this.target = target;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public void Tick(World world)
|
||||
{
|
||||
if (--delay <= 0)
|
||||
world.AddFrameEndTask(w => { w.Remove(this); wh.DoImpact(target, args); });
|
||||
}
|
||||
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer wr) { yield break; }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -20,9 +20,5 @@ namespace OpenRA.Effects
|
||||
IEnumerable<IRenderable> Render(WorldRenderer r);
|
||||
}
|
||||
|
||||
// Identifier interface for effects that are added to ScreenMap
|
||||
public interface ISpatiallyPartitionable { }
|
||||
|
||||
public interface IEffectAboveShroud { IEnumerable<IRenderable> RenderAboveShroud(WorldRenderer wr); }
|
||||
public interface IEffectAnnotation { IEnumerable<IRenderable> RenderAnnotation(WorldRenderer wr); }
|
||||
}
|
||||
|
||||
@@ -1,292 +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;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
[Flags]
|
||||
enum ModRegistration { User = 1, System = 2 }
|
||||
|
||||
public class ExternalMod
|
||||
{
|
||||
public readonly string Id;
|
||||
public readonly string Version;
|
||||
public readonly string Title;
|
||||
public readonly string LaunchPath;
|
||||
public readonly string[] LaunchArgs;
|
||||
public Sprite Icon { get; internal set; }
|
||||
public Sprite Icon2x { get; internal set; }
|
||||
public Sprite Icon3x { get; internal set; }
|
||||
|
||||
public static string MakeKey(Manifest mod) { return MakeKey(mod.Id, mod.Metadata.Version); }
|
||||
public static string MakeKey(ExternalMod mod) { return MakeKey(mod.Id, mod.Version); }
|
||||
public static string MakeKey(string modId, string modVersion) { return modId + "-" + modVersion; }
|
||||
}
|
||||
|
||||
public class ExternalMods : IReadOnlyDictionary<string, ExternalMod>
|
||||
{
|
||||
readonly Dictionary<string, ExternalMod> mods = new();
|
||||
readonly SheetBuilder sheetBuilder;
|
||||
|
||||
Sheet CreateSheet()
|
||||
{
|
||||
var sheet = new Sheet(SheetType.BGRA, new Size(512, 512));
|
||||
|
||||
// We must manually force the buffer creation to avoid a crash
|
||||
// that is indirectly triggered by rendering from a Sheet that
|
||||
// has not yet been written to.
|
||||
sheet.CreateBuffer();
|
||||
sheet.GetTexture().ScaleFilter = TextureScaleFilter.Linear;
|
||||
|
||||
return sheet;
|
||||
}
|
||||
|
||||
public ExternalMods()
|
||||
{
|
||||
// Don't try to load mod icons if we don't have a texture to put them in
|
||||
if (Game.Renderer != null)
|
||||
sheetBuilder = new SheetBuilder(SheetType.BGRA, CreateSheet);
|
||||
|
||||
// Several types of support directory types are available, depending on
|
||||
// how the player has installed and launched the game.
|
||||
// Read registration metadata from all of them
|
||||
foreach (var source in GetSupportDirs(ModRegistration.User | ModRegistration.System))
|
||||
{
|
||||
var metadataPath = Path.Combine(source, "ModMetadata");
|
||||
if (!Directory.Exists(metadataPath))
|
||||
continue;
|
||||
|
||||
foreach (var path in Directory.GetFiles(metadataPath, "*.yaml"))
|
||||
{
|
||||
try
|
||||
{
|
||||
var yaml = MiniYaml.FromStream(File.OpenRead(path), path).First().Value;
|
||||
LoadMod(yaml, path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to parse mod metadata file '{0}'", path);
|
||||
Log.Write("debug", e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoadMod(MiniYaml yaml, string path = null, bool forceRegistration = false)
|
||||
{
|
||||
var mod = FieldLoader.Load<ExternalMod>(yaml);
|
||||
|
||||
if (sheetBuilder != null)
|
||||
{
|
||||
var iconNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon");
|
||||
if (iconNode != null && !string.IsNullOrEmpty(iconNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(iconNode.Value.Value)))
|
||||
mod.Icon = sheetBuilder.Add(new Png(stream));
|
||||
|
||||
var icon2xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon2x");
|
||||
if (icon2xNode != null && !string.IsNullOrEmpty(icon2xNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(icon2xNode.Value.Value)))
|
||||
mod.Icon2x = sheetBuilder.Add(new Png(stream), 1f / 2);
|
||||
|
||||
var icon3xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon3x");
|
||||
if (icon3xNode != null && !string.IsNullOrEmpty(icon3xNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(icon3xNode.Value.Value)))
|
||||
mod.Icon3x = sheetBuilder.Add(new Png(stream), 1f / 3);
|
||||
}
|
||||
|
||||
// Avoid possibly overwriting a valid mod with an obviously bogus one
|
||||
var key = ExternalMod.MakeKey(mod);
|
||||
if ((forceRegistration || File.Exists(mod.LaunchPath)) && (path == null || Path.GetFileNameWithoutExtension(path) == key))
|
||||
mods[key] = mod;
|
||||
}
|
||||
|
||||
internal void Register(Manifest mod, string launchPath, IEnumerable<string> launchArgs, ModRegistration registration)
|
||||
{
|
||||
if (mod.Metadata.Hidden)
|
||||
return;
|
||||
|
||||
var key = ExternalMod.MakeKey(mod);
|
||||
var yaml = new MiniYamlNode("Registration", new MiniYaml("", new List<MiniYamlNode>()
|
||||
{
|
||||
new MiniYamlNode("Id", mod.Id),
|
||||
new MiniYamlNode("Version", mod.Metadata.Version),
|
||||
new MiniYamlNode("Title", mod.Metadata.Title),
|
||||
new MiniYamlNode("LaunchPath", launchPath),
|
||||
new MiniYamlNode("LaunchArgs", new[] { "Game.Mod=" + mod.Id }.Concat(launchArgs).JoinWith(", "))
|
||||
}));
|
||||
|
||||
using (var stream = mod.Package.GetStream("icon.png"))
|
||||
if (stream != null)
|
||||
yaml.Value.Nodes.Add(new MiniYamlNode("Icon", Convert.ToBase64String(stream.ReadAllBytes())));
|
||||
|
||||
using (var stream = mod.Package.GetStream("icon-2x.png"))
|
||||
if (stream != null)
|
||||
yaml.Value.Nodes.Add(new MiniYamlNode("Icon2x", Convert.ToBase64String(stream.ReadAllBytes())));
|
||||
|
||||
using (var stream = mod.Package.GetStream("icon-3x.png"))
|
||||
if (stream != null)
|
||||
yaml.Value.Nodes.Add(new MiniYamlNode("Icon3x", Convert.ToBase64String(stream.ReadAllBytes())));
|
||||
|
||||
var sources = new HashSet<string>();
|
||||
if (registration.HasFlag(ModRegistration.System))
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.System));
|
||||
|
||||
if (registration.HasFlag(ModRegistration.User))
|
||||
{
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.User));
|
||||
|
||||
// If using the modern support dir we must also write the registration
|
||||
// to the legacy support dir for older engine versions, but ONLY if it exists
|
||||
var legacyPath = Platform.GetSupportDir(SupportDirType.LegacyUser);
|
||||
if (Directory.Exists(legacyPath))
|
||||
sources.Add(legacyPath);
|
||||
}
|
||||
|
||||
// Make sure the mod is available for this session, even if saving it fails
|
||||
LoadMod(yaml.Value, forceRegistration: true);
|
||||
|
||||
var lines = new List<MiniYamlNode> { yaml }.ToLines().ToArray();
|
||||
foreach (var source in sources)
|
||||
{
|
||||
var metadataPath = Path.Combine(source, "ModMetadata");
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(metadataPath);
|
||||
File.WriteAllLines(Path.Combine(metadataPath, key + ".yaml"), lines);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to register current mod metadata");
|
||||
Log.Write("debug", e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes invalid mod registrations:
|
||||
/// <list type="bullet">
|
||||
/// <item>LaunchPath no longer exists.</item>
|
||||
/// <item>LaunchPath and mod id matches the active mod, but the version is different.</item>
|
||||
/// <item>Filename doesn't match internal key.</item>
|
||||
/// <item>Fails to parse as a mod registration.</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
internal void ClearInvalidRegistrations(ModRegistration registration)
|
||||
{
|
||||
foreach (var source in GetSupportDirs(registration))
|
||||
{
|
||||
var metadataPath = Path.Combine(source, "ModMetadata");
|
||||
if (!Directory.Exists(metadataPath))
|
||||
continue;
|
||||
|
||||
foreach (var path in Directory.GetFiles(metadataPath, "*.yaml"))
|
||||
{
|
||||
string modKey = null;
|
||||
try
|
||||
{
|
||||
var yaml = MiniYaml.FromStream(File.OpenRead(path), path).First().Value;
|
||||
var m = FieldLoader.Load<ExternalMod>(yaml);
|
||||
modKey = ExternalMod.MakeKey(m);
|
||||
|
||||
// Continue to the next entry if this one is valid
|
||||
// HACK: Explicitly invalidate paths to OpenRA.dll to clean up bogus metadata files
|
||||
// that were created after the initial migration from .NET Framework to Core/5.
|
||||
if (File.Exists(m.LaunchPath) && Path.GetFileNameWithoutExtension(path) == modKey && Path.GetExtension(m.LaunchPath) != ".dll")
|
||||
continue;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to parse mod metadata file '{0}'", path);
|
||||
Log.Write("debug", e.ToString());
|
||||
}
|
||||
|
||||
// Remove from the ingame mod switcher
|
||||
if (Path.GetFileNameWithoutExtension(path) == modKey)
|
||||
mods.Remove(modKey);
|
||||
|
||||
// Remove stale or corrupted metadata
|
||||
try
|
||||
{
|
||||
File.Delete(path);
|
||||
Log.Write("debug", "Removed invalid mod metadata file '{0}'", path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to remove mod metadata file '{0}'", path);
|
||||
Log.Write("debug", e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void Unregister(Manifest mod, ModRegistration registration)
|
||||
{
|
||||
var key = ExternalMod.MakeKey(mod);
|
||||
mods.Remove(key);
|
||||
|
||||
foreach (var source in GetSupportDirs(registration))
|
||||
{
|
||||
var path = Path.Combine(source, "ModMetadata", key + ".yaml");
|
||||
try
|
||||
{
|
||||
if (File.Exists(path))
|
||||
File.Delete(path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to remove mod metadata file '{0}'", path);
|
||||
Log.Write("debug", e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<string> GetSupportDirs(ModRegistration registration)
|
||||
{
|
||||
var sources = new HashSet<string>(4);
|
||||
if (registration.HasFlag(ModRegistration.System))
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.System));
|
||||
|
||||
if (registration.HasFlag(ModRegistration.User))
|
||||
{
|
||||
// User support dir may be using the modern or legacy value, or overridden by the user
|
||||
// Add all the possibilities and let the HashSet ignore the duplicates
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.User));
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.ModernUser));
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.LegacyUser));
|
||||
}
|
||||
|
||||
return sources;
|
||||
}
|
||||
|
||||
public ExternalMod this[string key] => mods[key];
|
||||
public int Count => mods.Count;
|
||||
public ICollection<string> Keys => mods.Keys;
|
||||
public ICollection<ExternalMod> Values => mods.Values;
|
||||
|
||||
IEnumerable<string> IReadOnlyDictionary<string, ExternalMod>.Keys => ((IReadOnlyDictionary<string, ExternalMod>)mods).Keys;
|
||||
|
||||
IEnumerable<ExternalMod> IReadOnlyDictionary<string, ExternalMod>.Values => ((IReadOnlyDictionary<string, ExternalMod>)mods).Values;
|
||||
|
||||
public bool ContainsKey(string key) { return mods.ContainsKey(key); }
|
||||
public IEnumerator<KeyValuePair<string, ExternalMod>> GetEnumerator() { return mods.GetEnumerator(); }
|
||||
public bool TryGetValue(string key, out ExternalMod value) { return mods.TryGetValue(key, out value); }
|
||||
IEnumerator IEnumerable.GetEnumerator() { return mods.GetEnumerator(); }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -11,10 +11,11 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Support;
|
||||
using OpenRA.Traits;
|
||||
|
||||
@@ -27,9 +28,9 @@ namespace OpenRA
|
||||
return string.Compare(str.ToUpperInvariant(), str, false) == 0;
|
||||
}
|
||||
|
||||
public static string F(this string format, params object[] args)
|
||||
public static string F(this string fmt, params object[] args)
|
||||
{
|
||||
return string.Format(format, args);
|
||||
return string.Format(fmt, args);
|
||||
}
|
||||
|
||||
public static T WithDefault<T>(T def, Func<T> f)
|
||||
@@ -38,6 +39,12 @@ namespace OpenRA
|
||||
catch { return def; }
|
||||
}
|
||||
|
||||
public static void Do<T>(this IEnumerable<T> e, Action<T> fn)
|
||||
{
|
||||
foreach (var ee in e)
|
||||
fn(ee);
|
||||
}
|
||||
|
||||
public static Lazy<T> Lazy<T>(Func<T> p) { return new Lazy<T>(p); }
|
||||
|
||||
public static IEnumerable<string> GetNamespaces(this Assembly a)
|
||||
@@ -45,16 +52,21 @@ namespace OpenRA
|
||||
return a.GetTypes().Select(t => t.Namespace).Distinct().Where(n => n != null);
|
||||
}
|
||||
|
||||
public static bool HasAttribute<TAttribute>(this MemberInfo mi)
|
||||
where TAttribute : Attribute
|
||||
public static bool HasAttribute<T>(this MemberInfo mi)
|
||||
{
|
||||
return Attribute.IsDefined(mi, typeof(TAttribute));
|
||||
return mi.GetCustomAttributes(typeof(T), true).Length != 0;
|
||||
}
|
||||
|
||||
public static TAttribute[] GetCustomAttributes<TAttribute>(this MemberInfo mi, bool inherit)
|
||||
where TAttribute : Attribute
|
||||
public static T[] GetCustomAttributes<T>(this MemberInfo mi, bool inherit)
|
||||
where T : class
|
||||
{
|
||||
return (TAttribute[])mi.GetCustomAttributes(typeof(TAttribute), inherit);
|
||||
return (T[])mi.GetCustomAttributes(typeof(T), inherit);
|
||||
}
|
||||
|
||||
public static T[] GetCustomAttributes<T>(this ParameterInfo mi)
|
||||
where T : class
|
||||
{
|
||||
return (T[])mi.GetCustomAttributes(typeof(T), true);
|
||||
}
|
||||
|
||||
public static T Clamp<T>(this T val, T min, T max) where T : IComparable<T>
|
||||
@@ -67,9 +79,19 @@ namespace OpenRA
|
||||
return val;
|
||||
}
|
||||
|
||||
public static bool Contains(this Rectangle r, int2 p)
|
||||
{
|
||||
return r.Contains(p.ToPoint());
|
||||
}
|
||||
|
||||
public static bool Contains(this RectangleF r, int2 p)
|
||||
{
|
||||
return r.Contains(p.ToPointF());
|
||||
}
|
||||
|
||||
static int WindingDirectionTest(int2 v0, int2 v1, int2 p)
|
||||
{
|
||||
return Math.Sign((v1.X - v0.X) * (p.Y - v0.Y) - (p.X - v0.X) * (v1.Y - v0.Y));
|
||||
return (v1.X - v0.X) * (p.Y - v0.Y) - (p.X - v0.X) * (v1.Y - v0.Y);
|
||||
}
|
||||
|
||||
public static bool PolygonContains(this int2[] polygon, int2 p)
|
||||
@@ -90,16 +112,6 @@ namespace OpenRA
|
||||
return windingNumber != 0;
|
||||
}
|
||||
|
||||
public static bool LinesIntersect(int2 a, int2 b, int2 c, int2 d)
|
||||
{
|
||||
// If line segments AB and CD intersect:
|
||||
// - the triangles ACD and BCD must have opposite sense (clockwise or anticlockwise)
|
||||
// - the triangles CAB and DAB must have opposite sense
|
||||
// Segments intersect if the orientation (clockwise or anticlockwise) of the two points in each line segment are opposite with respect to the other
|
||||
// Assumes that lines are not collinear
|
||||
return WindingDirectionTest(c, d, a) != WindingDirectionTest(c, d, b) && WindingDirectionTest(a, b, c) != WindingDirectionTest(a, b, d);
|
||||
}
|
||||
|
||||
public static bool HasModifier(this Modifiers k, Modifiers mod)
|
||||
{
|
||||
// PERF: Enum.HasFlag is slower and requires allocations.
|
||||
@@ -109,19 +121,13 @@ namespace OpenRA
|
||||
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k)
|
||||
where V : new()
|
||||
{
|
||||
return d.GetOrAdd(k, new V());
|
||||
}
|
||||
|
||||
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k, V v)
|
||||
{
|
||||
if (!d.TryGetValue(k, out var ret))
|
||||
d.Add(k, ret = v);
|
||||
return ret;
|
||||
return d.GetOrAdd(k, _ => new V());
|
||||
}
|
||||
|
||||
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k, Func<K, V> createFn)
|
||||
{
|
||||
if (!d.TryGetValue(k, out var ret))
|
||||
V ret;
|
||||
if (!d.TryGetValue(k, out ret))
|
||||
d.Add(k, ret = createFn(k));
|
||||
return ret;
|
||||
}
|
||||
@@ -144,38 +150,18 @@ namespace OpenRA
|
||||
static T Random<T>(IEnumerable<T> ts, MersenneTwister r, bool throws)
|
||||
{
|
||||
var xs = ts as ICollection<T>;
|
||||
xs ??= ts.ToList();
|
||||
xs = xs ?? ts.ToList();
|
||||
if (xs.Count == 0)
|
||||
{
|
||||
if (throws)
|
||||
throw new ArgumentException("Collection must not be empty.", nameof(ts));
|
||||
throw new ArgumentException("Collection must not be empty.", "ts");
|
||||
else
|
||||
return default;
|
||||
return default(T);
|
||||
}
|
||||
else
|
||||
return xs.ElementAt(r.Next(xs.Count));
|
||||
}
|
||||
|
||||
public static Rectangle Union(this IEnumerable<Rectangle> rects)
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
var first = true;
|
||||
var result = Rectangle.Empty;
|
||||
foreach (var rect in rects)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
result = rect;
|
||||
continue;
|
||||
}
|
||||
|
||||
result = Rectangle.Union(rect, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static float Product(this IEnumerable<float> xs)
|
||||
{
|
||||
return xs.Aggregate(1f, (a, x) => a * x);
|
||||
@@ -189,11 +175,7 @@ namespace OpenRA
|
||||
|
||||
public static IEnumerable<T> Iterate<T>(this T t, Func<T, T> f)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
yield return t;
|
||||
t = f(t);
|
||||
}
|
||||
for (;;) { yield return t; t = f(t); }
|
||||
}
|
||||
|
||||
public static T MinBy<T, U>(this IEnumerable<T> ts, Func<T, U> selector)
|
||||
@@ -225,9 +207,9 @@ namespace OpenRA
|
||||
{
|
||||
if (!e.MoveNext())
|
||||
if (throws)
|
||||
throw new ArgumentException("Collection must not be empty.", nameof(ts));
|
||||
throw new ArgumentException("Collection must not be empty.", "ts");
|
||||
else
|
||||
return default;
|
||||
return default(T);
|
||||
t = e.Current;
|
||||
u = selector(t);
|
||||
while (e.MoveNext())
|
||||
@@ -267,7 +249,7 @@ namespace OpenRA
|
||||
public static int ISqrt(int number, ISqrtRoundMode round = ISqrtRoundMode.Floor)
|
||||
{
|
||||
if (number < 0)
|
||||
throw new InvalidOperationException($"Attempted to calculate the square root of a negative integer: {number}");
|
||||
throw new InvalidOperationException("Attempted to calculate the square root of a negative integer: {0}".F(number));
|
||||
|
||||
return (int)ISqrt((uint)number, round);
|
||||
}
|
||||
@@ -308,7 +290,7 @@ namespace OpenRA
|
||||
public static long ISqrt(long number, ISqrtRoundMode round = ISqrtRoundMode.Floor)
|
||||
{
|
||||
if (number < 0)
|
||||
throw new InvalidOperationException($"Attempted to calculate the square root of a negative integer: {number}");
|
||||
throw new InvalidOperationException("Attempted to calculate the square root of a negative integer: {0}".F(number));
|
||||
|
||||
return (long)ISqrt((ulong)number, round);
|
||||
}
|
||||
@@ -346,14 +328,10 @@ namespace OpenRA
|
||||
return root;
|
||||
}
|
||||
|
||||
public static int MultiplyBySqrtTwo(short number)
|
||||
{
|
||||
return number * 46341 / 32768;
|
||||
}
|
||||
|
||||
public static int IntegerDivisionRoundingAwayFromZero(int dividend, int divisor)
|
||||
{
|
||||
var quotient = Math.DivRem(dividend, divisor, out var remainder);
|
||||
int remainder;
|
||||
var quotient = Math.DivRem(dividend, divisor, out remainder);
|
||||
if (remainder == 0)
|
||||
return quotient;
|
||||
return quotient + (Math.Sign(dividend) == Math.Sign(divisor) ? 1 : -1);
|
||||
@@ -369,11 +347,6 @@ namespace OpenRA
|
||||
return ts.Concat(moreTs);
|
||||
}
|
||||
|
||||
public static IEnumerable<T> Exclude<T>(this IEnumerable<T> ts, params T[] exclusions)
|
||||
{
|
||||
return ts.Except(exclusions);
|
||||
}
|
||||
|
||||
public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source)
|
||||
{
|
||||
return new HashSet<T>(source);
|
||||
@@ -391,45 +364,42 @@ namespace OpenRA
|
||||
string debugName, Func<TKey, string> logKey = null, Func<TElement, string> logValue = null)
|
||||
{
|
||||
// Fall back on ToString() if null functions are provided:
|
||||
logKey ??= s => s.ToString();
|
||||
logValue ??= s => s.ToString();
|
||||
logKey = logKey ?? (s => s.ToString());
|
||||
logValue = logValue ?? (s => s.ToString());
|
||||
|
||||
// Try to build a dictionary and log all duplicates found (if any):
|
||||
var dupKeys = new Dictionary<TKey, List<string>>();
|
||||
var capacity = source is ICollection<TSource> collection ? collection.Count : 0;
|
||||
var d = new Dictionary<TKey, TElement>(capacity);
|
||||
var d = new Dictionary<TKey, TElement>();
|
||||
foreach (var item in source)
|
||||
{
|
||||
var key = keySelector(item);
|
||||
var element = elementSelector(item);
|
||||
|
||||
// Discard elements with null keys
|
||||
if (!typeof(TKey).IsValueType && key == null)
|
||||
continue;
|
||||
|
||||
// Check for a key conflict:
|
||||
if (!d.TryAdd(key, element))
|
||||
if (d.ContainsKey(key))
|
||||
{
|
||||
if (!dupKeys.TryGetValue(key, out var dupKeyMessages))
|
||||
List<string> dupKeyMessages;
|
||||
if (!dupKeys.TryGetValue(key, out dupKeyMessages))
|
||||
{
|
||||
// Log the initial conflicting value already inserted:
|
||||
dupKeyMessages = new List<string>
|
||||
{
|
||||
logValue(d[key])
|
||||
};
|
||||
dupKeyMessages = new List<string>();
|
||||
dupKeyMessages.Add(logValue(d[key]));
|
||||
dupKeys.Add(key, dupKeyMessages);
|
||||
}
|
||||
|
||||
// Log this conflicting value:
|
||||
dupKeyMessages.Add(logValue(element));
|
||||
continue;
|
||||
}
|
||||
|
||||
d.Add(key, element);
|
||||
}
|
||||
|
||||
// If any duplicates were found, throw a descriptive error
|
||||
if (dupKeys.Count > 0)
|
||||
{
|
||||
var badKeysFormatted = string.Join(", ", dupKeys.Select(p => $"{logKey(p.Key)}: [{string.Join(",", p.Value)}]"));
|
||||
var msg = $"{debugName}, duplicate values found for the following keys: {badKeysFormatted}";
|
||||
var badKeysFormatted = string.Join(", ", dupKeys.Select(p => "{0}: [{1}]".F(logKey(p.Key), string.Join(",", p.Value))));
|
||||
var msg = "{0}, duplicate values found for the following keys: {1}".F(debugName, badKeysFormatted);
|
||||
throw new ArgumentException(msg);
|
||||
}
|
||||
|
||||
@@ -474,6 +444,27 @@ namespace OpenRA
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Rectangle Bounds(this Bitmap b) { return new Rectangle(0, 0, b.Width, b.Height); }
|
||||
|
||||
public static Bitmap CloneWith32bbpArgbPixelFormat(this Bitmap original)
|
||||
{
|
||||
// Note: We would use original.Clone(original.Bounds(), PixelFormat.Format32bppArgb)
|
||||
// but this doesn't work on mono.
|
||||
var clone = new Bitmap(original.Width, original.Height, PixelFormat.Format32bppArgb);
|
||||
try
|
||||
{
|
||||
using (var g = System.Drawing.Graphics.FromImage(clone))
|
||||
g.DrawImage(original, original.Bounds());
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
clone.Dispose();
|
||||
throw;
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
public static int ToBits(this IEnumerable<bool> bits)
|
||||
{
|
||||
var i = 0;
|
||||
@@ -493,11 +484,6 @@ namespace OpenRA
|
||||
return int.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
public static byte ParseByte(string s)
|
||||
{
|
||||
return byte.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
public static bool TryParseIntegerInvariant(string s, out int i)
|
||||
{
|
||||
return int.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
|
||||
@@ -508,94 +494,15 @@ namespace OpenRA
|
||||
return long.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
|
||||
}
|
||||
|
||||
public static bool IsTraitEnabled<T>(this T trait)
|
||||
public static bool IsTraitEnabled(this object trait)
|
||||
{
|
||||
return trait is not IDisabledTrait disabledTrait || !disabledTrait.IsTraitDisabled;
|
||||
return trait as IDisabledTrait == null || !(trait as IDisabledTrait).IsTraitDisabled;
|
||||
}
|
||||
|
||||
public static T FirstEnabledTraitOrDefault<T>(this IEnumerable<T> ts)
|
||||
public static bool IsTraitEnabled<T>(T t)
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
foreach (var t in ts)
|
||||
if (t.IsTraitEnabled())
|
||||
return t;
|
||||
|
||||
return default;
|
||||
return IsTraitEnabled(t as object);
|
||||
}
|
||||
|
||||
public static T FirstEnabledTraitOrDefault<T>(this T[] ts)
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
foreach (var t in ts)
|
||||
if (t.IsTraitEnabled())
|
||||
return t;
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public static T FirstEnabledConditionalTraitOrDefault<T>(this IEnumerable<T> ts) where T : IDisabledTrait
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
foreach (var t in ts)
|
||||
if (!t.IsTraitDisabled)
|
||||
return t;
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public static T FirstEnabledConditionalTraitOrDefault<T>(this T[] ts) where T : IDisabledTrait
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
foreach (var t in ts)
|
||||
if (!t.IsTraitDisabled)
|
||||
return t;
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public static LineSplitEnumerator SplitLines(this string str, char separator)
|
||||
{
|
||||
return new LineSplitEnumerator(str.AsSpan(), separator);
|
||||
}
|
||||
}
|
||||
|
||||
public ref struct LineSplitEnumerator
|
||||
{
|
||||
ReadOnlySpan<char> str;
|
||||
readonly char separator;
|
||||
|
||||
public LineSplitEnumerator(ReadOnlySpan<char> str, char separator)
|
||||
{
|
||||
this.str = str;
|
||||
this.separator = separator;
|
||||
Current = default;
|
||||
}
|
||||
|
||||
public LineSplitEnumerator GetEnumerator() => this;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
var span = str;
|
||||
|
||||
// Reach the end of the string
|
||||
if (span.Length == 0)
|
||||
return false;
|
||||
|
||||
var index = span.IndexOf(separator);
|
||||
if (index == -1)
|
||||
{
|
||||
// The remaining string is an empty string
|
||||
str = ReadOnlySpan<char>.Empty;
|
||||
Current = span;
|
||||
return true;
|
||||
}
|
||||
|
||||
Current = span[..index];
|
||||
str = span[(index + 1)..];
|
||||
return true;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<char> Current { get; private set; }
|
||||
}
|
||||
|
||||
public static class Enum<T>
|
||||
@@ -611,7 +518,7 @@ namespace OpenRA
|
||||
|
||||
if (values.Any(x => !names.Contains(x)))
|
||||
{
|
||||
value = default;
|
||||
value = default(T);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -12,10 +12,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
@@ -72,14 +74,39 @@ namespace OpenRA
|
||||
return "";
|
||||
|
||||
var t = v.GetType();
|
||||
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(BitSet<>))
|
||||
return ((IEnumerable<string>)v).Select(FormatValue).JoinWith(", ");
|
||||
|
||||
// Color.ToString() does the wrong thing; force it to format as rgb[a] hex
|
||||
if (t == typeof(Color))
|
||||
{
|
||||
return HSLColor.ToHexString((Color)v);
|
||||
}
|
||||
|
||||
// HSLColor.ToString() does the wrong thing; force it to format as rgb[a] hex
|
||||
if (t == typeof(HSLColor))
|
||||
{
|
||||
return ((HSLColor)v).ToHexString();
|
||||
}
|
||||
|
||||
if (t == typeof(ImageFormat))
|
||||
{
|
||||
return ((ImageFormat)v).ToString();
|
||||
}
|
||||
|
||||
if (t == typeof(Rectangle))
|
||||
{
|
||||
var r = (Rectangle)v;
|
||||
return "{0},{1},{2},{3}".F(r.X, r.Y, r.Width, r.Height);
|
||||
}
|
||||
|
||||
if (t.IsArray && t.GetArrayRank() == 1)
|
||||
{
|
||||
return ((Array)v).Cast<object>().Select(FormatValue).JoinWith(", ");
|
||||
}
|
||||
|
||||
if (t.IsGenericType && (t.GetGenericTypeDefinition() == typeof(HashSet<>) || t.GetGenericTypeDefinition() == typeof(List<>)))
|
||||
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(HashSet<>))
|
||||
{
|
||||
return ((System.Collections.IEnumerable)v).Cast<object>().Select(FormatValue).JoinWith(", ");
|
||||
}
|
||||
|
||||
// This is only for documentation generation
|
||||
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
||||
@@ -94,14 +121,17 @@ namespace OpenRA
|
||||
var formattedKey = FormatValue(key);
|
||||
var formattedValue = FormatValue(value);
|
||||
|
||||
result += $"{formattedKey}: {formattedValue}{Environment.NewLine}";
|
||||
result += "{0}: {1}{2}".F(formattedKey, formattedValue, Environment.NewLine);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (v is DateTime d)
|
||||
return d.ToString("yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture);
|
||||
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Primitives.Cache<,>))
|
||||
return ""; // TODO
|
||||
|
||||
if (t == typeof(DateTime))
|
||||
return ((DateTime)v).ToString("yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture);
|
||||
|
||||
// Try the TypeConverter
|
||||
var conv = TypeDescriptor.GetConverter(t);
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
#region Additional Copyright & License Information
|
||||
/*
|
||||
*
|
||||
* This file is based on the blast routines (version 1.1 by Mark Adler)
|
||||
* included in zlib/contrib
|
||||
*/
|
||||
@@ -18,15 +15,14 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenRA.Mods.Common.FileFormats
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public static class Blast
|
||||
{
|
||||
public static readonly int MAXBITS = 13; // maximum code length
|
||||
public static readonly int MAXWIN = 4096; // maximum window size
|
||||
|
||||
static readonly byte[] LitLen =
|
||||
{
|
||||
static byte[] litlen = new byte[] {
|
||||
11, 124, 8, 7, 28, 7, 188, 13, 76, 4,
|
||||
10, 8, 12, 10, 12, 10, 8, 23, 8, 9,
|
||||
7, 6, 7, 8, 7, 6, 55, 8, 23, 24,
|
||||
@@ -40,28 +36,26 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
};
|
||||
|
||||
// bit lengths of length codes 0..15
|
||||
static readonly byte[] LenLen = { 2, 35, 36, 53, 38, 23 };
|
||||
static byte[] lenlen = new byte[] { 2, 35, 36, 53, 38, 23 };
|
||||
|
||||
// bit lengths of distance codes 0..63
|
||||
static readonly byte[] DistLen = { 2, 20, 53, 230, 247, 151, 248 };
|
||||
static byte[] distlen = new byte[] { 2, 20, 53, 230, 247, 151, 248 };
|
||||
|
||||
// base for length codes
|
||||
static readonly short[] LengthBase =
|
||||
{
|
||||
static short[] lengthbase = new short[] {
|
||||
3, 2, 4, 5, 6, 7, 8, 9, 10, 12,
|
||||
16, 24, 40, 72, 136, 264
|
||||
};
|
||||
|
||||
// extra bits for length codes
|
||||
static readonly byte[] Extra =
|
||||
{
|
||||
static byte[] extra = new byte[] {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 2,
|
||||
3, 4, 5, 6, 7, 8
|
||||
};
|
||||
|
||||
static readonly Huffman LitCode = new(LitLen, 256);
|
||||
static readonly Huffman LenCode = new(LenLen, 16);
|
||||
static readonly Huffman DistCode = new(DistLen, 64);
|
||||
static Huffman litcode = new Huffman(litlen, litlen.Length, 256);
|
||||
static Huffman lencode = new Huffman(lenlen, lenlen.Length, 16);
|
||||
static Huffman distcode = new Huffman(distlen, distlen.Length, 64);
|
||||
|
||||
/// <summary>PKWare Compression Library stream.</summary>
|
||||
/// <param name="input">Compressed input stream.</param>
|
||||
@@ -98,8 +92,8 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
if (br.ReadBits(1) == 1)
|
||||
{
|
||||
// Length
|
||||
var symbol = Decode(LenCode, br);
|
||||
var len = LengthBase[symbol] + br.ReadBits(Extra[symbol]);
|
||||
var symbol = Decode(lencode, br);
|
||||
var len = lengthbase[symbol] + br.ReadBits(extra[symbol]);
|
||||
|
||||
// Magic number for "done"
|
||||
if (len == 519)
|
||||
@@ -107,13 +101,14 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
for (var i = 0; i < next; i++)
|
||||
output.WriteByte(outBuffer[i]);
|
||||
|
||||
onProgress?.Invoke(input.Position - inputStart, output.Position - outputStart);
|
||||
if (onProgress != null)
|
||||
onProgress(input.Position - inputStart, output.Position - outputStart);
|
||||
break;
|
||||
}
|
||||
|
||||
// Distance
|
||||
symbol = len == 2 ? 2 : dict;
|
||||
var dist = Decode(DistCode, br) << symbol;
|
||||
var dist = Decode(distcode, br) << symbol;
|
||||
dist += br.ReadBits(symbol);
|
||||
dist++;
|
||||
|
||||
@@ -154,15 +149,15 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
next = 0;
|
||||
first = false;
|
||||
|
||||
onProgress?.Invoke(input.Position - inputStart, output.Position - outputStart);
|
||||
if (onProgress != null)
|
||||
onProgress(input.Position - inputStart, output.Position - outputStart);
|
||||
}
|
||||
}
|
||||
while (len != 0);
|
||||
} while (len != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// literal value
|
||||
var symbol = encodedLiterals ? Decode(LitCode, br) : br.ReadBits(8);
|
||||
var symbol = encodedLiterals ? Decode(litcode, br) : br.ReadBits(8);
|
||||
outBuffer[next++] = (byte)symbol;
|
||||
if (next == MAXWIN)
|
||||
{
|
||||
@@ -171,11 +166,11 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
next = 0;
|
||||
first = false;
|
||||
|
||||
onProgress?.Invoke(input.Position - inputStart, output.Position - outputStart);
|
||||
if (onProgress != null)
|
||||
onProgress(input.Position - inputStart, output.Position - outputStart);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (true);
|
||||
} while (true);
|
||||
}
|
||||
|
||||
// Decode a code using Huffman table h.
|
||||
@@ -247,7 +242,7 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
public short[] Count; // number of symbols of each length
|
||||
public short[] Symbol; // canonically ordered symbols
|
||||
|
||||
public Huffman(byte[] rep, short symbolCount)
|
||||
public Huffman(byte[] rep, int n, short symbolCount)
|
||||
{
|
||||
var length = new short[256]; // code lengths
|
||||
var s = 0; // current symbol
|
||||
@@ -262,7 +257,7 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
while (--num > 0);
|
||||
}
|
||||
|
||||
var n = s;
|
||||
n = s;
|
||||
|
||||
// count number of codes of each length
|
||||
Count = new short[Blast.MAXBITS + 1];
|
||||
@@ -277,8 +272,8 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
var left = 1; // one possible code of zero length
|
||||
for (var len = 1; len <= Blast.MAXBITS; len++)
|
||||
{
|
||||
left <<= 1; // one more bit, double codes left
|
||||
left -= Count[len]; // deduct count from possible codes
|
||||
left <<= 1; // one more bit, double codes left
|
||||
left -= Count[len]; // deduct count from possible codes
|
||||
if (left < 0)
|
||||
throw new InvalidDataException("over subscribed code set");
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
class Blowfish
|
||||
{
|
||||
@@ -130,8 +130,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
return i;
|
||||
}
|
||||
|
||||
readonly uint[] lookupMfromP =
|
||||
{
|
||||
uint[] lookupMfromP = new uint[] {
|
||||
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
|
||||
0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
|
||||
0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
|
||||
@@ -139,8 +138,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
0x9216d5d9, 0x8979fb1b
|
||||
};
|
||||
|
||||
readonly uint[,] lookupMfromS =
|
||||
{
|
||||
uint[,] lookupMfromS = new uint[,] {
|
||||
{
|
||||
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
|
||||
0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -12,7 +12,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
/* TODO: Convert this direct C port into readable code. */
|
||||
|
||||
@@ -22,18 +22,18 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
|
||||
class PublicKey
|
||||
{
|
||||
public readonly uint[] KeyOne = new uint[64];
|
||||
public readonly uint[] KeyTwo = new uint[64];
|
||||
public uint[] KeyOne = new uint[64];
|
||||
public uint[] KeyTwo = new uint[64];
|
||||
public uint Len;
|
||||
}
|
||||
|
||||
readonly PublicKey pubkey = new();
|
||||
PublicKey pubkey = new PublicKey();
|
||||
|
||||
readonly uint[] globOne = new uint[64];
|
||||
uint[] globOne = new uint[64];
|
||||
uint globOneBitLen, globOneLenXTwo;
|
||||
readonly uint[] globTwo = new uint[130];
|
||||
readonly uint[] globOneHigh = new uint[4];
|
||||
readonly uint[] globOneHighInv = new uint[4];
|
||||
uint[] globTwo = new uint[130];
|
||||
uint[] globOneHigh = new uint[4];
|
||||
uint[] globOneHighInv = new uint[4];
|
||||
uint globOneHighBitLen;
|
||||
uint globOneHighInvLow, globOneHighInvHigh;
|
||||
|
||||
@@ -90,8 +90,9 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
|
||||
static uint LenBigNum(uint[] n, uint len)
|
||||
{
|
||||
var i = len - 1;
|
||||
while (n[i] == 0) i--;
|
||||
uint i;
|
||||
i = len - 1;
|
||||
while ((i >= 0) && (n[i] == 0)) i--;
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
@@ -144,7 +145,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
{
|
||||
for (i = 0; i < len - i2; i++) n[i] = n[i + i2];
|
||||
for (; i < len; i++) n[i] = 0;
|
||||
bits %= 32;
|
||||
bits = bits % 32;
|
||||
}
|
||||
|
||||
if (bits == 0) return;
|
||||
@@ -161,7 +162,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
{
|
||||
for (i = len - 1; i > i2; i--) n[i] = n[i - i2];
|
||||
for (; i > 0; i--) n[i] = 0;
|
||||
bits %= 32;
|
||||
bits = bits % 32;
|
||||
}
|
||||
|
||||
if (bits == 0) return;
|
||||
@@ -224,13 +225,15 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
uint nTwoByteLen, bit;
|
||||
int nTwoBitLen;
|
||||
|
||||
var j = 0;
|
||||
|
||||
InitBigNum(nTmp, 0, len);
|
||||
InitBigNum(n1, 0, len);
|
||||
nTwoBitLen = (int)BitLenBigNum(n2, len);
|
||||
bit = 1U << (nTwoBitLen % 32);
|
||||
var j = (nTwoBitLen + 32) / 32 - 1;
|
||||
bit = ((uint)1) << (nTwoBitLen % 32);
|
||||
j = ((nTwoBitLen + 32) / 32) - 1;
|
||||
nTwoByteLen = (uint)((nTwoBitLen - 1) / 32) * 4;
|
||||
nTmp[nTwoByteLen / 4] |= 1U << ((nTwoBitLen - 1) & 0x1f);
|
||||
nTmp[nTwoByteLen / 4] |= ((uint)1) << ((nTwoBitLen - 1) & 0x1f);
|
||||
|
||||
while (nTwoBitLen > 0)
|
||||
{
|
||||
@@ -293,13 +296,12 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
tmp = 0;
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
tmp = mul * *pn2 + *pn1 + tmp;
|
||||
tmp = mul * (*pn2) + (*pn1) + tmp;
|
||||
*pn1 = (ushort)tmp;
|
||||
pn1++;
|
||||
pn2++;
|
||||
tmp >>= 16;
|
||||
}
|
||||
|
||||
*pn1 += (ushort)tmp;
|
||||
}
|
||||
}
|
||||
@@ -371,8 +373,8 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
IncrementBigNum(globTwo, len * 2 + 1);
|
||||
NegBigNum(globTwo, len * 2 + 1);
|
||||
lenDiff = globTwoXtwo + 1 - globOneLenXTwo;
|
||||
var esi = (ushort*)g2 + (1 + globTwoXtwo - globOneLenXTwo);
|
||||
var edi = (ushort*)g2 + (globTwoXtwo + 1);
|
||||
var esi = ((ushort*)g2) + (1 + globTwoXtwo - globOneLenXTwo);
|
||||
var edi = ((ushort*)g2) + (globTwoXtwo + 1);
|
||||
for (; lenDiff != 0; lenDiff--)
|
||||
{
|
||||
edi--;
|
||||
@@ -383,7 +385,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
MulBignumWord(esi, globOne, tmp, 2 * len);
|
||||
if ((*edi & 0x8000) == 0)
|
||||
{
|
||||
if (SubBigNum((uint*)esi, (uint*)esi, g1, 0, (int)len) != 0)
|
||||
if (0 != SubBigNum((uint*)esi, (uint*)esi, g1, 0, (int)len))
|
||||
(*edi)--;
|
||||
}
|
||||
}
|
||||
@@ -429,7 +431,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
InitTwoDw(n4, n4_len);
|
||||
n3_bitlen = (int)BitLenBigNum(n3, n4_len);
|
||||
n3_len = (uint)((n3_bitlen + 31) / 32);
|
||||
bit_mask = (1U << ((n3_bitlen - 1) % 32)) >> 1;
|
||||
bit_mask = (((uint)1) << ((n3_bitlen - 1) % 32)) >> 1;
|
||||
pn3 += n3_len - 1;
|
||||
n3_bitlen--;
|
||||
MoveBigNum(n1, n2, n4_len);
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -9,9 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
/// <summary>
|
||||
/// Static class that uses a lookup table to calculates CRC32
|
||||
@@ -20,9 +18,9 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
public static class CRC32
|
||||
{
|
||||
/// <summary>
|
||||
/// The CRC32 lookup table.
|
||||
/// The CRC32 lookup table
|
||||
/// </summary>
|
||||
static readonly uint[] LookUp = new uint[256]
|
||||
static uint[] lookUp = new uint[256]
|
||||
{
|
||||
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
|
||||
0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
|
||||
@@ -91,32 +89,47 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// A CRC32 implementation that can be used on spans of bytes.
|
||||
/// A fast (native) CRC32 implementation that can be used on a regular byte arrays.
|
||||
/// </summary>
|
||||
/// <param name="data">The data from which to calculate the checksum.</param>
|
||||
/// <param name="polynomial">The polynomial to XOR with.</param>
|
||||
/// <param name="polynomial">The polynomial.</param>
|
||||
/// <returns>
|
||||
/// The calculated checksum.
|
||||
/// </returns>
|
||||
public static uint Calculate(ReadOnlySpan<byte> data, uint polynomial)
|
||||
public static uint Calculate(byte[] data, uint polynomial)
|
||||
{
|
||||
var crc = polynomial;
|
||||
for (var i = 0; i < data.Length; i++)
|
||||
crc = (crc >> 8) ^ LookUp[(crc & 0xFF) ^ data[i]];
|
||||
crc = (crc >> 8) ^ lookUp[(crc & 0xFF) ^ data[i]];
|
||||
crc ^= polynomial;
|
||||
return crc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A CRC32 implementation that can be used on spans of bytes, with default polynomial.
|
||||
/// </summary>
|
||||
/// <param name="data">The data from which to calculate the checksum.</param>
|
||||
/// <returns>
|
||||
/// The calculated checksum.
|
||||
/// </returns>
|
||||
public static uint Calculate(ReadOnlySpan<byte> data)
|
||||
public static uint Calculate(byte[] data)
|
||||
{
|
||||
return Calculate(data, 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A fast (native) CRC32 implementation that can be used on a pinned byte array using
|
||||
/// default polynomial.
|
||||
/// </summary>
|
||||
/// <param name="data"> [in,out] If non-null, the.</param>
|
||||
/// <param name="len"> The length of the data.</param>
|
||||
/// <param name="polynomial">The polynomial to XOR with.</param>
|
||||
/// <returns>The calculated checksum.</returns>
|
||||
public static unsafe uint Calculate(byte* data, uint len, uint polynomial)
|
||||
{
|
||||
var crc = polynomial;
|
||||
for (var i = 0; i < len; i++)
|
||||
crc = (crc >> 8) ^ lookUp[(crc & 0xFF) ^ *data++];
|
||||
crc ^= polynomial;
|
||||
return crc;
|
||||
}
|
||||
|
||||
public static unsafe uint Calculate(byte* data, uint len)
|
||||
{
|
||||
return Calculate(data, len, 0xFFFFFFFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,20 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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.IO;
|
||||
using OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class HvaReader
|
||||
{
|
||||
@@ -48,9 +50,10 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
Transforms[c + ids[k]] = s.ReadFloat();
|
||||
|
||||
Array.Copy(Transforms, 16 * (LimbCount * j + i), testMatrix, 0, 16);
|
||||
if (OpenRA.Graphics.Util.MatrixInverse(testMatrix) == null)
|
||||
if (Util.MatrixInverse(testMatrix) == null)
|
||||
throw new InvalidDataException(
|
||||
$"The transformation matrix for HVA file `{fileName}` section {i} frame {j} is invalid because it is not invertible!");
|
||||
"The transformation matrix for HVA file `{0}` section {1} frame {2} is invalid because it is not invertible!"
|
||||
.F(fileName, i, j));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -11,8 +11,9 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using OpenRA.FileSystem;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class IdxReader
|
||||
{
|
||||
@@ -26,12 +27,12 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
var id = s.ReadASCII(4);
|
||||
|
||||
if (id != "GABA")
|
||||
throw new InvalidDataException($"Unable to load Idx file, did not find magic id, found {id} instead");
|
||||
throw new InvalidDataException("Unable to load Idx file, did not find magic id, found {0} instead".F(id));
|
||||
|
||||
var two = s.ReadInt32();
|
||||
|
||||
if (two != 2)
|
||||
throw new InvalidDataException($"Unable to load Idx file, did not find magic number 2, found {two} instead");
|
||||
throw new InvalidDataException("Unable to load Idx file, did not find magic number 2, found {0} instead".F(two));
|
||||
|
||||
SoundCount = s.ReadInt32();
|
||||
|
||||
@@ -41,4 +42,4 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
Entries.Add(new IdxEntry(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,379 +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;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using ICSharpCode.SharpZipLib.Checksum;
|
||||
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class Png
|
||||
{
|
||||
static readonly byte[] Signature = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
|
||||
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
public Color[] Palette { get; }
|
||||
public byte[] Data { get; }
|
||||
public SpriteFrameType Type { get; }
|
||||
public Dictionary<string, string> EmbeddedData = new();
|
||||
|
||||
public int PixelStride => Type == SpriteFrameType.Indexed8 ? 1 : Type == SpriteFrameType.Rgb24 ? 3 : 4;
|
||||
|
||||
public Png(Stream s)
|
||||
{
|
||||
if (!Verify(s))
|
||||
throw new InvalidDataException("PNG Signature is bogus");
|
||||
|
||||
s.Position += 8;
|
||||
var headerParsed = false;
|
||||
var data = new List<byte>();
|
||||
Type = SpriteFrameType.Rgba32;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var length = IPAddress.NetworkToHostOrder(s.ReadInt32());
|
||||
var type = Encoding.UTF8.GetString(s.ReadBytes(4));
|
||||
var content = s.ReadBytes(length);
|
||||
/*var crc = */s.ReadInt32();
|
||||
|
||||
if (!headerParsed && type != "IHDR")
|
||||
throw new InvalidDataException("Invalid PNG file - header does not appear first.");
|
||||
|
||||
using (var ms = new MemoryStream(content))
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case "IHDR":
|
||||
{
|
||||
if (headerParsed)
|
||||
throw new InvalidDataException("Invalid PNG file - duplicate header.");
|
||||
Width = IPAddress.NetworkToHostOrder(ms.ReadInt32());
|
||||
Height = IPAddress.NetworkToHostOrder(ms.ReadInt32());
|
||||
|
||||
var bitDepth = ms.ReadUInt8();
|
||||
var colorType = (PngColorType)ms.ReadByte();
|
||||
if (IsPaletted(bitDepth, colorType))
|
||||
Type = SpriteFrameType.Indexed8;
|
||||
else if (colorType == PngColorType.Color)
|
||||
Type = SpriteFrameType.Rgb24;
|
||||
|
||||
Data = new byte[Width * Height * PixelStride];
|
||||
|
||||
var compression = ms.ReadByte();
|
||||
/*var filter = */ms.ReadByte();
|
||||
var interlace = ms.ReadByte();
|
||||
|
||||
if (compression != 0)
|
||||
throw new InvalidDataException("Compression method not supported");
|
||||
|
||||
if (interlace != 0)
|
||||
throw new InvalidDataException("Interlacing not supported");
|
||||
|
||||
headerParsed = true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "PLTE":
|
||||
{
|
||||
Palette = new Color[256];
|
||||
for (var i = 0; i < length / 3; i++)
|
||||
{
|
||||
var r = ms.ReadByte(); var g = ms.ReadByte(); var b = ms.ReadByte();
|
||||
Palette[i] = Color.FromArgb(r, g, b);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "tRNS":
|
||||
{
|
||||
if (Palette == null)
|
||||
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
Palette[i] = Color.FromArgb(ms.ReadByte(), Palette[i]);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "IDAT":
|
||||
{
|
||||
data.AddRange(content);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "tEXt":
|
||||
{
|
||||
var key = ms.ReadASCIIZ();
|
||||
EmbeddedData.Add(key, ms.ReadASCII(length - key.Length - 1));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "IEND":
|
||||
{
|
||||
using (var ns = new MemoryStream(data.ToArray()))
|
||||
{
|
||||
using (var ds = new InflaterInputStream(ns))
|
||||
{
|
||||
var pxStride = PixelStride;
|
||||
var rowStride = Width * pxStride;
|
||||
|
||||
var prevLine = new byte[rowStride];
|
||||
for (var y = 0; y < Height; y++)
|
||||
{
|
||||
var filter = (PngFilter)ds.ReadByte();
|
||||
var line = ds.ReadBytes(rowStride);
|
||||
|
||||
for (var i = 0; i < rowStride; i++)
|
||||
line[i] = i < pxStride
|
||||
? UnapplyFilter(filter, line[i], 0, prevLine[i], 0)
|
||||
: UnapplyFilter(filter, line[i], line[i - pxStride], prevLine[i], prevLine[i - pxStride]);
|
||||
|
||||
Array.Copy(line, 0, Data, y * rowStride, rowStride);
|
||||
|
||||
prevLine = line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
Dictionary<string, string> embeddedData = null)
|
||||
{
|
||||
var expectLength = width * height;
|
||||
if (palette == null)
|
||||
expectLength *= 4;
|
||||
|
||||
if (data.Length != expectLength)
|
||||
throw new InvalidDataException("Input data does not match expected length");
|
||||
|
||||
Type = type;
|
||||
Width = width;
|
||||
Height = height;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case SpriteFrameType.Indexed8:
|
||||
case SpriteFrameType.Rgba32:
|
||||
case SpriteFrameType.Rgb24:
|
||||
{
|
||||
// Data is already in a compatible format
|
||||
Data = data;
|
||||
if (type == SpriteFrameType.Indexed8)
|
||||
Palette = palette;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SpriteFrameType.Bgra32:
|
||||
case SpriteFrameType.Bgr24:
|
||||
{
|
||||
// Convert to big endian
|
||||
Data = new byte[data.Length];
|
||||
var stride = PixelStride;
|
||||
for (var i = 0; i < width * height; i++)
|
||||
{
|
||||
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)
|
||||
Data[stride * i + 3] = data[stride * i + 3];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new InvalidDataException($"Unhandled SpriteFrameType {type}");
|
||||
}
|
||||
|
||||
if (embeddedData != null)
|
||||
EmbeddedData = embeddedData;
|
||||
}
|
||||
|
||||
public static bool Verify(Stream s)
|
||||
{
|
||||
var pos = s.Position;
|
||||
var isPng = Signature.Aggregate(true, (current, t) => current && s.ReadUInt8() == t);
|
||||
s.Position = pos;
|
||||
return isPng;
|
||||
}
|
||||
|
||||
static byte UnapplyFilter(PngFilter f, byte x, byte a, byte b, byte c)
|
||||
{
|
||||
switch (f)
|
||||
{
|
||||
case PngFilter.None: return x;
|
||||
case PngFilter.Sub: return (byte)(x + a);
|
||||
case PngFilter.Up: return (byte)(x + b);
|
||||
case PngFilter.Average: return (byte)(x + (a + b) / 2);
|
||||
case PngFilter.Paeth: return (byte)(x + Paeth(a, b, c));
|
||||
default:
|
||||
throw new InvalidOperationException("Unsupported Filter");
|
||||
}
|
||||
}
|
||||
|
||||
static byte Paeth(byte a, byte b, byte c)
|
||||
{
|
||||
var p = a + b - c;
|
||||
var pa = Math.Abs(p - a);
|
||||
var pb = Math.Abs(p - b);
|
||||
var pc = Math.Abs(p - c);
|
||||
|
||||
return (pa <= pb && pa <= pc) ? a :
|
||||
(pb <= pc) ? b : c;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum PngColorType { Indexed = 1, Color = 2, Alpha = 4 }
|
||||
enum PngFilter { None, Sub, Up, Average, Paeth }
|
||||
|
||||
static bool IsPaletted(byte bitDepth, PngColorType colorType)
|
||||
{
|
||||
if (bitDepth == 8 && colorType == (PngColorType.Indexed | PngColorType.Color))
|
||||
return true;
|
||||
|
||||
if (bitDepth == 8 && colorType == (PngColorType.Color | PngColorType.Alpha))
|
||||
return false;
|
||||
|
||||
if (bitDepth == 8 && colorType == PngColorType.Color)
|
||||
return false;
|
||||
|
||||
throw new InvalidDataException("Unknown pixel format");
|
||||
}
|
||||
|
||||
void WritePngChunk(Stream output, string type, Stream input)
|
||||
{
|
||||
input.Position = 0;
|
||||
|
||||
var typeBytes = Encoding.ASCII.GetBytes(type);
|
||||
output.Write(IPAddress.HostToNetworkOrder((int)input.Length));
|
||||
output.WriteArray(typeBytes);
|
||||
|
||||
var data = input.ReadAllBytes();
|
||||
output.WriteArray(data);
|
||||
|
||||
var crc32 = new Crc32();
|
||||
crc32.Update(typeBytes);
|
||||
crc32.Update(data);
|
||||
output.Write(IPAddress.NetworkToHostOrder((int)crc32.Value));
|
||||
}
|
||||
|
||||
public byte[] Save()
|
||||
{
|
||||
using (var output = new MemoryStream())
|
||||
{
|
||||
output.WriteArray(Signature);
|
||||
using (var header = new MemoryStream())
|
||||
{
|
||||
header.Write(IPAddress.HostToNetworkOrder(Width));
|
||||
header.Write(IPAddress.HostToNetworkOrder(Height));
|
||||
header.WriteByte(8); // Bit depth
|
||||
|
||||
var colorType = Type == SpriteFrameType.Indexed8 ? PngColorType.Indexed | PngColorType.Color :
|
||||
Type == SpriteFrameType.Rgb24 ? PngColorType.Color : PngColorType.Color | PngColorType.Alpha;
|
||||
header.WriteByte((byte)colorType);
|
||||
|
||||
header.WriteByte(0); // Compression
|
||||
header.WriteByte(0); // Filter
|
||||
header.WriteByte(0); // Interlacing
|
||||
|
||||
WritePngChunk(output, "IHDR", header);
|
||||
}
|
||||
|
||||
var alphaPalette = false;
|
||||
if (Palette != null)
|
||||
{
|
||||
using (var palette = new MemoryStream())
|
||||
{
|
||||
foreach (var c in Palette)
|
||||
{
|
||||
palette.WriteByte(c.R);
|
||||
palette.WriteByte(c.G);
|
||||
palette.WriteByte(c.B);
|
||||
alphaPalette |= c.A > 0;
|
||||
}
|
||||
|
||||
WritePngChunk(output, "PLTE", palette);
|
||||
}
|
||||
}
|
||||
|
||||
if (alphaPalette)
|
||||
{
|
||||
using (var alpha = new MemoryStream())
|
||||
{
|
||||
foreach (var c in Palette)
|
||||
alpha.WriteByte(c.A);
|
||||
|
||||
WritePngChunk(output, "tRNS", alpha);
|
||||
}
|
||||
}
|
||||
|
||||
using (var data = new MemoryStream())
|
||||
{
|
||||
using (var compressed = new DeflaterOutputStream(data))
|
||||
{
|
||||
var rowStride = Width * PixelStride;
|
||||
for (var y = 0; y < Height; y++)
|
||||
{
|
||||
// Write uncompressed scanlines for simplicity
|
||||
compressed.WriteByte(0);
|
||||
compressed.Write(Data, y * rowStride, rowStride);
|
||||
}
|
||||
|
||||
compressed.Flush();
|
||||
compressed.Finish();
|
||||
|
||||
WritePngChunk(output, "IDAT", data);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var kv in EmbeddedData)
|
||||
{
|
||||
using (var text = new MemoryStream())
|
||||
{
|
||||
text.WriteArray(Encoding.ASCII.GetBytes(kv.Key + (char)0 + kv.Value));
|
||||
WritePngChunk(output, "tEXt", text);
|
||||
}
|
||||
}
|
||||
|
||||
WritePngChunk(output, "IEND", new MemoryStream());
|
||||
return output.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public void Save(string path)
|
||||
{
|
||||
File.WriteAllBytes(path, Save());
|
||||
}
|
||||
}
|
||||
}
|
||||
206
OpenRA.Game/FileFormats/PngLoader.cs
Normal file
206
OpenRA.Game/FileFormats/PngLoader.cs
Normal file
@@ -0,0 +1,206 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public static class PngLoader
|
||||
{
|
||||
public static Bitmap Load(string filename)
|
||||
{
|
||||
using (var s = File.OpenRead(filename))
|
||||
return Load(s);
|
||||
}
|
||||
|
||||
public static Bitmap Load(Stream s)
|
||||
{
|
||||
using (var br = new BinaryReader(s))
|
||||
{
|
||||
var signature = new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 };
|
||||
foreach (var b in signature)
|
||||
if (br.ReadByte() != b)
|
||||
throw new InvalidDataException("PNG Signature is bogus");
|
||||
|
||||
Bitmap bitmap = null;
|
||||
Color[] palette = null;
|
||||
var data = new List<byte>();
|
||||
|
||||
try
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
var length = IPAddress.NetworkToHostOrder(br.ReadInt32());
|
||||
var type = Encoding.UTF8.GetString(br.ReadBytes(4));
|
||||
var content = br.ReadBytes(length);
|
||||
/*var crc = */br.ReadInt32();
|
||||
|
||||
if (bitmap == null && type != "IHDR")
|
||||
throw new InvalidDataException("Invalid PNG file - header does not appear first.");
|
||||
|
||||
using (var ms = new MemoryStream(content))
|
||||
using (var cr = new BinaryReader(ms))
|
||||
switch (type)
|
||||
{
|
||||
case "IHDR":
|
||||
{
|
||||
if (bitmap != null)
|
||||
throw new InvalidDataException("Invalid PNG file - duplicate header.");
|
||||
|
||||
var width = IPAddress.NetworkToHostOrder(cr.ReadInt32());
|
||||
var height = IPAddress.NetworkToHostOrder(cr.ReadInt32());
|
||||
var bitDepth = cr.ReadByte();
|
||||
var colorType = (PngColorType)cr.ReadByte();
|
||||
var compression = cr.ReadByte();
|
||||
/*var filter = */cr.ReadByte();
|
||||
var interlace = cr.ReadByte();
|
||||
|
||||
if (compression != 0) throw new InvalidDataException("Compression method not supported");
|
||||
if (interlace != 0) throw new InvalidDataException("Interlacing not supported");
|
||||
|
||||
bitmap = new Bitmap(width, height, MakePixelFormat(bitDepth, colorType));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "PLTE":
|
||||
{
|
||||
palette = new Color[256];
|
||||
for (var i = 0; i < 256; i++)
|
||||
{
|
||||
var r = cr.ReadByte(); var g = cr.ReadByte(); var b = cr.ReadByte();
|
||||
palette[i] = Color.FromArgb(r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "tRNS":
|
||||
{
|
||||
if (palette == null)
|
||||
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
palette[i] = Color.FromArgb(cr.ReadByte(), palette[i]);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "IDAT":
|
||||
{
|
||||
data.AddRange(content);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "IEND":
|
||||
{
|
||||
var bits = bitmap.LockBits(bitmap.Bounds(),
|
||||
ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
|
||||
|
||||
using (var ns = new MemoryStream(data.ToArray()))
|
||||
{
|
||||
// 'zlib' flags bytes; confuses the DeflateStream.
|
||||
/*var flags = (byte)*/ns.ReadByte();
|
||||
/*var moreFlags = (byte)*/ns.ReadByte();
|
||||
|
||||
using (var ds = new DeflateStream(ns, CompressionMode.Decompress))
|
||||
using (var dr = new BinaryReader(ds))
|
||||
{
|
||||
var prevLine = new byte[bitmap.Width]; // all zero
|
||||
for (var y = 0; y < bitmap.Height; y++)
|
||||
{
|
||||
var filter = (PngFilter)dr.ReadByte();
|
||||
var line = dr.ReadBytes(bitmap.Width);
|
||||
|
||||
for (var i = 0; i < bitmap.Width; i++)
|
||||
line[i] = i > 0
|
||||
? UnapplyFilter(filter, line[i], line[i - 1], prevLine[i], prevLine[i - 1])
|
||||
: UnapplyFilter(filter, line[i], 0, prevLine[i], 0);
|
||||
|
||||
Marshal.Copy(line, 0, new IntPtr(bits.Scan0.ToInt64() + y * bits.Stride), line.Length);
|
||||
prevLine = line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bitmap.UnlockBits(bits);
|
||||
|
||||
if (palette == null)
|
||||
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
|
||||
|
||||
using (var temp = new Bitmap(1, 1, PixelFormat.Format8bppIndexed))
|
||||
{
|
||||
var cp = temp.Palette;
|
||||
for (var i = 0; i < 256; i++)
|
||||
cp.Entries[i] = palette[i]; // finalize the palette.
|
||||
bitmap.Palette = cp;
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (bitmap != null)
|
||||
bitmap.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static byte UnapplyFilter(PngFilter f, byte x, byte a, byte b, byte c)
|
||||
{
|
||||
switch (f)
|
||||
{
|
||||
case PngFilter.None: return x;
|
||||
case PngFilter.Sub: return (byte)(x + a);
|
||||
case PngFilter.Up: return (byte)(x + b);
|
||||
case PngFilter.Average: return (byte)(x + (a + b) / 2);
|
||||
case PngFilter.Paeth: return (byte)(x + Paeth(a, b, c));
|
||||
default:
|
||||
throw new InvalidOperationException("Unsupported Filter");
|
||||
}
|
||||
}
|
||||
|
||||
static byte Paeth(byte a, byte b, byte c)
|
||||
{
|
||||
var p = a + b - c;
|
||||
var pa = Math.Abs(p - a);
|
||||
var pb = Math.Abs(p - b);
|
||||
var pc = Math.Abs(p - c);
|
||||
|
||||
return (pa <= pb && pa <= pc) ? a :
|
||||
(pb <= pc) ? b : c;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum PngColorType { Indexed = 1, Color = 2, Alpha = 4 }
|
||||
enum PngFilter { None, Sub, Up, Average, Paeth }
|
||||
|
||||
static PixelFormat MakePixelFormat(byte bitDepth, PngColorType colorType)
|
||||
{
|
||||
if (bitDepth == 8 && colorType == (PngColorType.Indexed | PngColorType.Color))
|
||||
return PixelFormat.Format8bppIndexed;
|
||||
|
||||
throw new InvalidDataException("Unknown pixel format");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -28,7 +28,7 @@ namespace OpenRA.FileFormats
|
||||
public ReplayMetadata(GameInformation info)
|
||||
{
|
||||
if (info == null)
|
||||
throw new ArgumentNullException(nameof(info));
|
||||
throw new ArgumentNullException("info");
|
||||
|
||||
GameInfo = info;
|
||||
}
|
||||
@@ -44,7 +44,7 @@ namespace OpenRA.FileFormats
|
||||
// Read version
|
||||
var version = fs.ReadInt32();
|
||||
if (version != MetaVersion)
|
||||
throw new NotSupportedException($"Metadata version {version} is not supported");
|
||||
throw new NotSupportedException("Metadata version {0} is not supported".F(version));
|
||||
|
||||
// Read game info (max 100K limit as a safeguard against corrupted files)
|
||||
var data = fs.ReadString(Encoding.UTF8, 1024 * 100);
|
||||
@@ -79,27 +79,45 @@ namespace OpenRA.FileFormats
|
||||
|
||||
public static ReplayMetadata Read(string path)
|
||||
{
|
||||
using (var fs = new FileStream(path, FileMode.Open))
|
||||
return Read(fs, path);
|
||||
}
|
||||
|
||||
static ReplayMetadata Read(FileStream fs, string path)
|
||||
{
|
||||
if (!fs.CanSeek)
|
||||
return null;
|
||||
|
||||
if (fs.Length < 20)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
using (var fs = new FileStream(path, FileMode.Open))
|
||||
fs.Seek(-(4 + 4), SeekOrigin.End);
|
||||
var dataLength = fs.ReadInt32();
|
||||
if (fs.ReadInt32() == MetaEndMarker)
|
||||
{
|
||||
if (!fs.CanSeek)
|
||||
return null;
|
||||
|
||||
if (fs.Length < 20)
|
||||
return null;
|
||||
|
||||
fs.Seek(-(4 + 4), SeekOrigin.End);
|
||||
var dataLength = fs.ReadInt32();
|
||||
if (fs.ReadInt32() == MetaEndMarker)
|
||||
// go back by (end marker + length storage + data + version + start marker) bytes
|
||||
fs.Seek(-(4 + 4 + dataLength + 4 + 4), SeekOrigin.Current);
|
||||
try
|
||||
{
|
||||
// Go back by (end marker + length storage + data + version + start marker) bytes
|
||||
fs.Seek(-(4 + 4 + dataLength + 4 + 4), SeekOrigin.Current);
|
||||
return new ReplayMetadata(fs, path);
|
||||
}
|
||||
catch (YamlException ex)
|
||||
{
|
||||
Log.Write("debug", ex.ToString());
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
Log.Write("debug", ex.ToString());
|
||||
}
|
||||
catch (NotSupportedException ex)
|
||||
{
|
||||
Log.Write("debug", ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (IOException ex)
|
||||
{
|
||||
Log.Write("debug", ex.ToString());
|
||||
}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public enum NormalType { TiberianSun = 2, RedAlert2 = 4 }
|
||||
public class VxlElement
|
||||
@@ -38,7 +39,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
public readonly uint LimbCount;
|
||||
public VxlLimb[] Limbs;
|
||||
|
||||
readonly uint bodySize;
|
||||
uint bodySize;
|
||||
|
||||
static void ReadVoxelData(Stream s, VxlLimb l)
|
||||
{
|
||||
@@ -66,8 +67,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
z += count;
|
||||
l.VoxelCount += count;
|
||||
s.Seek(2 * count + 1, SeekOrigin.Current);
|
||||
}
|
||||
while (z < l.Size[2]);
|
||||
} while (z < l.Size[2]);
|
||||
}
|
||||
|
||||
// Read the data
|
||||
@@ -90,11 +90,9 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
var count = s.ReadUInt8();
|
||||
for (var j = 0; j < count; j++)
|
||||
{
|
||||
var v = new VxlElement
|
||||
{
|
||||
Color = s.ReadUInt8(),
|
||||
Normal = s.ReadUInt8()
|
||||
};
|
||||
var v = new VxlElement();
|
||||
v.Color = s.ReadUInt8();
|
||||
v.Normal = s.ReadUInt8();
|
||||
|
||||
l.VoxelMap[x, y].Add(z, v);
|
||||
z++;
|
||||
@@ -102,8 +100,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
|
||||
// Skip duplicate count
|
||||
s.ReadUInt8();
|
||||
}
|
||||
while (z < l.Size[2]);
|
||||
} while (z < l.Size[2]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,11 +119,8 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
Limbs = new VxlLimb[LimbCount];
|
||||
for (var i = 0; i < LimbCount; i++)
|
||||
{
|
||||
Limbs[i] = new VxlLimb
|
||||
{
|
||||
Name = s.ReadASCII(16)
|
||||
};
|
||||
|
||||
Limbs[i] = new VxlLimb();
|
||||
Limbs[i].Name = s.ReadASCII(16);
|
||||
s.Seek(12, SeekOrigin.Current);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -13,9 +13,9 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public sealed class XccGlobalDatabase : IDisposable
|
||||
public class XccGlobalDatabase : IDisposable
|
||||
{
|
||||
public readonly string[] Entries;
|
||||
readonly Stream s;
|
||||
@@ -25,27 +25,21 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
s = stream;
|
||||
|
||||
var entries = new List<string>();
|
||||
var chars = new char[32];
|
||||
while (s.Peek() > -1)
|
||||
{
|
||||
var count = s.ReadInt32();
|
||||
entries.Capacity += count;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
// Read filename
|
||||
var chars = new List<char>();
|
||||
byte c;
|
||||
var charsIndex = 0;
|
||||
while ((c = s.ReadUInt8()) != 0)
|
||||
{
|
||||
if (charsIndex >= chars.Length)
|
||||
Array.Resize(ref chars, chars.Length * 2);
|
||||
chars[charsIndex++] = (char)c;
|
||||
}
|
||||
|
||||
entries.Add(new string(chars, 0, charsIndex));
|
||||
// Read filename
|
||||
while ((c = s.ReadUInt8()) != 0)
|
||||
chars.Add((char)c);
|
||||
entries.Add(new string(chars.ToArray()));
|
||||
|
||||
// Skip comment
|
||||
while (s.ReadUInt8() != 0) { }
|
||||
while ((c = s.ReadUInt8()) != 0) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,4 +51,4 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -14,7 +14,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class XccLocalDatabase
|
||||
{
|
||||
@@ -26,15 +26,14 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
var reader = new BinaryReader(s);
|
||||
var count = reader.ReadInt32();
|
||||
Entries = new string[count];
|
||||
var chars = new List<char>();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var chars = new List<char>();
|
||||
char c;
|
||||
while ((c = reader.ReadChar()) != 0)
|
||||
chars.Add(c);
|
||||
|
||||
Entries[i] = new string(chars.ToArray());
|
||||
chars.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +50,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
writer.Write(Encoding.ASCII.GetBytes("XCC by Olaf van der Spek"));
|
||||
writer.Write(new byte[] { 0x1A, 0x04, 0x17, 0x27, 0x10, 0x19, 0x80, 0x00 });
|
||||
|
||||
writer.Write(Entries.Sum(e => e.Length) + Entries.Length + 52); // Size
|
||||
writer.Write(Entries.Aggregate(Entries.Length, (a, b) => a + b.Length) + 52); // Size
|
||||
writer.Write(0); // Type
|
||||
writer.Write(0); // Version
|
||||
writer.Write(0); // Game/Format (0 == TD)
|
||||
127
OpenRA.Game/FileSystem/BagFile.cs
Normal file
127
OpenRA.Game/FileSystem/BagFile.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class BagFile : IReadOnlyPackage
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public IEnumerable<string> Contents { get { return index.Keys; } }
|
||||
|
||||
readonly Stream s;
|
||||
readonly Dictionary<string, IdxEntry> index;
|
||||
|
||||
public BagFile(FileSystem context, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
|
||||
// A bag file is always accompanied with an .idx counterpart
|
||||
// For example: audio.bag requires the audio.idx file
|
||||
var indexFilename = Path.ChangeExtension(filename, ".idx");
|
||||
|
||||
// Build the index and dispose the stream, it is no longer needed after this
|
||||
List<IdxEntry> entries;
|
||||
using (var indexStream = context.Open(indexFilename))
|
||||
entries = new IdxReader(indexStream).Entries;
|
||||
|
||||
index = entries.ToDictionaryWithConflictLog(x => x.Filename,
|
||||
"{0} (bag format)".F(filename),
|
||||
null, x => "(offs={0}, len={1})".F(x.Offset, x.Length));
|
||||
|
||||
s = context.Open(filename);
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
IdxEntry entry;
|
||||
if (!index.TryGetValue(filename, out entry))
|
||||
return null;
|
||||
|
||||
s.Seek(entry.Offset, SeekOrigin.Begin);
|
||||
|
||||
var waveHeaderMemoryStream = new MemoryStream();
|
||||
|
||||
var channels = (entry.Flags & 1) > 0 ? 2 : 1;
|
||||
|
||||
if ((entry.Flags & 2) > 0)
|
||||
{
|
||||
// PCM
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("RIFF"));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.Length + 36));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("WAVE"));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("fmt "));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(16));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)1));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)channels));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.SampleRate));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(2 * channels * entry.SampleRate));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)(2 * channels)));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)16));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("data"));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.Length));
|
||||
}
|
||||
|
||||
if ((entry.Flags & 8) > 0)
|
||||
{
|
||||
// IMA ADPCM
|
||||
var samplesPerChunk = (2 * (entry.ChunkSize - 4)) + 1;
|
||||
var bytesPerSec = (int)Math.Floor(((double)(2 * entry.ChunkSize) / samplesPerChunk) * ((double)entry.SampleRate / 2));
|
||||
var chunkSize = entry.ChunkSize > entry.Length ? entry.Length : entry.ChunkSize;
|
||||
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("RIFF"));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.Length + 52));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("WAVE"));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("fmt "));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(20));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)17));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)channels));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.SampleRate));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(bytesPerSec));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)chunkSize));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)4));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)2));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)samplesPerChunk));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("fact"));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(4));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(4 * entry.Length));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("data"));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.Length));
|
||||
}
|
||||
|
||||
waveHeaderMemoryStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
// Construct a merged stream
|
||||
var mergedStream = new MergedStream(waveHeaderMemoryStream, s);
|
||||
mergedStream.SetLength(waveHeaderMemoryStream.Length + entry.Length);
|
||||
|
||||
return mergedStream;
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
105
OpenRA.Game/FileSystem/BigFile.cs
Normal file
105
OpenRA.Game/FileSystem/BigFile.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class BigFile : IReadOnlyPackage
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public IEnumerable<string> Contents { get { return index.Keys; } }
|
||||
|
||||
readonly Dictionary<string, Entry> index = new Dictionary<string, Entry>();
|
||||
readonly Stream s;
|
||||
|
||||
public BigFile(FileSystem context, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
|
||||
s = context.Open(filename);
|
||||
try
|
||||
{
|
||||
if (s.ReadASCII(4) != "BIGF")
|
||||
throw new InvalidDataException("Header is not BIGF");
|
||||
|
||||
// Total archive size.
|
||||
s.ReadUInt32();
|
||||
|
||||
var entryCount = s.ReadUInt32();
|
||||
if (BitConverter.IsLittleEndian)
|
||||
entryCount = int2.Swap(entryCount);
|
||||
|
||||
// First entry offset? This is apparently bogus for EA's .big files
|
||||
// and we don't have to try seeking there since the entries typically start next in EA's .big files.
|
||||
s.ReadUInt32();
|
||||
|
||||
for (var i = 0; i < entryCount; i++)
|
||||
{
|
||||
var entry = new Entry(s);
|
||||
index.Add(entry.Path, entry);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
class Entry
|
||||
{
|
||||
readonly Stream s;
|
||||
readonly uint offset;
|
||||
readonly uint size;
|
||||
public readonly string Path;
|
||||
|
||||
public Entry(Stream s)
|
||||
{
|
||||
this.s = s;
|
||||
|
||||
offset = s.ReadUInt32();
|
||||
size = s.ReadUInt32();
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
offset = int2.Swap(offset);
|
||||
size = int2.Swap(size);
|
||||
}
|
||||
|
||||
Path = s.ReadASCIIZ();
|
||||
}
|
||||
|
||||
public Stream GetData()
|
||||
{
|
||||
s.Position = offset;
|
||||
return new MemoryStream(s.ReadBytes((int)size));
|
||||
}
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
return index[filename].GetData();
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
81
OpenRA.Game/FileSystem/D2kSoundResources.cs
Normal file
81
OpenRA.Game/FileSystem/D2kSoundResources.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class D2kSoundResources : IReadOnlyPackage
|
||||
{
|
||||
struct Entry
|
||||
{
|
||||
public readonly uint Offset;
|
||||
public readonly uint Length;
|
||||
|
||||
public Entry(uint offset, uint length)
|
||||
{
|
||||
Offset = offset;
|
||||
Length = length;
|
||||
}
|
||||
}
|
||||
|
||||
public string Name { get; private set; }
|
||||
public IEnumerable<string> Contents { get { return index.Keys; } }
|
||||
|
||||
readonly Stream s;
|
||||
readonly Dictionary<string, Entry> index = new Dictionary<string, Entry>();
|
||||
|
||||
public D2kSoundResources(FileSystem context, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
|
||||
s = context.Open(filename);
|
||||
try
|
||||
{
|
||||
var headerLength = s.ReadUInt32();
|
||||
while (s.Position < headerLength + 4)
|
||||
{
|
||||
var name = s.ReadASCIIZ();
|
||||
var offset = s.ReadUInt32();
|
||||
var length = s.ReadUInt32();
|
||||
index.Add(name, new Entry(offset, length));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
Entry e;
|
||||
if (!index.TryGetValue(filename, out e))
|
||||
return null;
|
||||
|
||||
s.Seek(e.Offset, SeekOrigin.Begin);
|
||||
return new MemoryStream(s.ReadBytes((int)e.Length));
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -23,89 +23,105 @@ namespace OpenRA.FileSystem
|
||||
bool TryGetPackageContaining(string path, out IReadOnlyPackage package, out string filename);
|
||||
bool TryOpen(string filename, out Stream s);
|
||||
bool Exists(string filename);
|
||||
bool IsExternalModFile(string filename);
|
||||
}
|
||||
|
||||
public class FileSystem : IReadOnlyFileSystem
|
||||
{
|
||||
public IEnumerable<IReadOnlyPackage> MountedPackages => mountedPackages.Keys;
|
||||
readonly Dictionary<IReadOnlyPackage, int> mountedPackages = new();
|
||||
readonly Dictionary<string, IReadOnlyPackage> explicitMounts = new();
|
||||
readonly string modID;
|
||||
public IEnumerable<IReadOnlyPackage> MountedPackages { get { return mountedPackages.Keys; } }
|
||||
readonly Dictionary<IReadOnlyPackage, int> mountedPackages = new Dictionary<IReadOnlyPackage, int>();
|
||||
readonly Dictionary<string, IReadOnlyPackage> explicitMounts = new Dictionary<string, IReadOnlyPackage>();
|
||||
|
||||
// Mod packages that should not be disposed
|
||||
readonly List<IReadOnlyPackage> modPackages = new();
|
||||
readonly List<IReadOnlyPackage> modPackages = new List<IReadOnlyPackage>();
|
||||
readonly IReadOnlyDictionary<string, Manifest> installedMods;
|
||||
readonly IPackageLoader[] packageLoaders;
|
||||
|
||||
Cache<string, List<IReadOnlyPackage>> fileIndex = new(_ => new List<IReadOnlyPackage>());
|
||||
Cache<string, List<IReadOnlyPackage>> fileIndex = new Cache<string, List<IReadOnlyPackage>>(_ => new List<IReadOnlyPackage>());
|
||||
|
||||
public FileSystem(string modID, IReadOnlyDictionary<string, Manifest> installedMods, IPackageLoader[] packageLoaders)
|
||||
public FileSystem(IReadOnlyDictionary<string, Manifest> installedMods)
|
||||
{
|
||||
this.modID = modID;
|
||||
this.installedMods = installedMods;
|
||||
this.packageLoaders = packageLoaders
|
||||
.Append(new ZipFileLoader())
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public bool TryParsePackage(Stream stream, string filename, out IReadOnlyPackage package)
|
||||
{
|
||||
package = null;
|
||||
foreach (var packageLoader in packageLoaders)
|
||||
if (packageLoader.TryParsePackage(stream, filename, this, out package))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename)
|
||||
{
|
||||
// Raw directories are the easiest and one of the most common cases, so try these first
|
||||
var resolvedPath = Platform.ResolvePath(filename);
|
||||
if (!resolvedPath.Contains('|') && Directory.Exists(resolvedPath))
|
||||
return new Folder(resolvedPath);
|
||||
if (filename.EndsWith(".mix", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new MixFile(this, filename);
|
||||
if (filename.EndsWith(".zip", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new ZipFile(this, filename);
|
||||
if (filename.EndsWith(".oramap", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new ZipFile(this, filename);
|
||||
if (filename.EndsWith(".oramod", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new ZipFile(this, filename);
|
||||
if (filename.EndsWith(".RS", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new D2kSoundResources(this, filename);
|
||||
if (filename.EndsWith(".Z", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new InstallShieldPackage(this, filename);
|
||||
if (filename.EndsWith(".PAK", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new PakFile(this, filename);
|
||||
if (filename.EndsWith(".big", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new BigFile(this, filename);
|
||||
if (filename.EndsWith(".bag", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new BagFile(this, filename);
|
||||
|
||||
// Children of another package require special handling
|
||||
if (TryGetPackageContaining(filename, out var parent, out var subPath))
|
||||
return parent.OpenPackage(subPath, this);
|
||||
IReadOnlyPackage parent;
|
||||
string subPath = null;
|
||||
if (TryGetPackageContaining(filename, out parent, out subPath))
|
||||
return OpenPackage(subPath, parent);
|
||||
|
||||
// Try and open it normally
|
||||
var stream = Open(filename);
|
||||
if (TryParsePackage(stream, filename, out var package))
|
||||
return package;
|
||||
return new Folder(Platform.ResolvePath(filename));
|
||||
}
|
||||
|
||||
// No package loaders took ownership of the stream, so clean it up
|
||||
stream.Dispose();
|
||||
public IReadOnlyPackage OpenPackage(string filename, IReadOnlyPackage parent)
|
||||
{
|
||||
// HACK: limit support to zip and folder until we generalize the PackageLoader support
|
||||
if (filename.EndsWith(".zip", StringComparison.InvariantCultureIgnoreCase) ||
|
||||
filename.EndsWith(".oramap", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
using (var s = parent.GetStream(filename))
|
||||
return new ZipFile(s, filename, parent);
|
||||
}
|
||||
|
||||
if (parent is ZipFile)
|
||||
return new ZipFolder(this, (ZipFile)parent, filename, filename);
|
||||
|
||||
if (parent is ZipFolder)
|
||||
{
|
||||
var folder = (ZipFolder)parent;
|
||||
return new ZipFolder(this, folder.Parent, folder.Name + "/" + filename, filename);
|
||||
}
|
||||
|
||||
if (parent is Folder)
|
||||
{
|
||||
var subFolder = Platform.ResolvePath(Path.Combine(parent.Name, filename));
|
||||
if (Directory.Exists(subFolder))
|
||||
return new Folder(subFolder);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Mount(string name, string explicitName = null)
|
||||
{
|
||||
var optional = name.StartsWith("~", StringComparison.Ordinal);
|
||||
var optional = name.StartsWith("~");
|
||||
if (optional)
|
||||
name = name[1..];
|
||||
name = name.Substring(1);
|
||||
|
||||
try
|
||||
{
|
||||
IReadOnlyPackage package;
|
||||
if (name.StartsWith("$", StringComparison.Ordinal))
|
||||
if (name.StartsWith("$"))
|
||||
{
|
||||
name = name[1..];
|
||||
name = name.Substring(1);
|
||||
|
||||
if (!installedMods.TryGetValue(name, out var mod))
|
||||
throw new InvalidOperationException($"Could not load mod '{name}'. Available mods: {installedMods.Keys.JoinWith(", ")}");
|
||||
Manifest mod;
|
||||
if (!installedMods.TryGetValue(name, out mod))
|
||||
throw new InvalidOperationException("Could not load mod '{0}'. Available mods: {1}".F(name, installedMods.Keys.JoinWith(", ")));
|
||||
|
||||
package = mod.Package;
|
||||
modPackages.Add(package);
|
||||
}
|
||||
else
|
||||
{
|
||||
package = OpenPackage(name);
|
||||
if (package == null)
|
||||
throw new InvalidOperationException($"Could not open package '{name}', file not found or its format is not supported.");
|
||||
}
|
||||
|
||||
Mount(package, explicitName);
|
||||
}
|
||||
@@ -118,7 +134,8 @@ namespace OpenRA.FileSystem
|
||||
|
||||
public void Mount(IReadOnlyPackage package, string explicitName = null)
|
||||
{
|
||||
if (mountedPackages.TryGetValue(package, out var mountCount))
|
||||
var mountCount = 0;
|
||||
if (mountedPackages.TryGetValue(package, out mountCount))
|
||||
{
|
||||
// Package is already mounted
|
||||
// Increment the mount count and bump up the file loading priority
|
||||
@@ -144,7 +161,8 @@ namespace OpenRA.FileSystem
|
||||
|
||||
public bool Unmount(IReadOnlyPackage package)
|
||||
{
|
||||
if (!mountedPackages.TryGetValue(package, out var mountCount))
|
||||
var mountCount = 0;
|
||||
if (!mountedPackages.TryGetValue(package, out mountCount))
|
||||
return false;
|
||||
|
||||
if (--mountCount <= 0)
|
||||
@@ -197,13 +215,17 @@ namespace OpenRA.FileSystem
|
||||
var package = fileIndex[filename]
|
||||
.LastOrDefault(x => x.Contains(filename));
|
||||
|
||||
return package?.GetStream(filename);
|
||||
if (package != null)
|
||||
return package.GetStream(filename);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Stream Open(string filename)
|
||||
{
|
||||
if (!TryOpen(filename, out var s))
|
||||
throw new FileNotFoundException($"File not found: {filename}", filename);
|
||||
Stream s;
|
||||
if (!TryOpen(filename, out s))
|
||||
throw new FileNotFoundException("File not found: {0}".F(filename), filename);
|
||||
|
||||
return s;
|
||||
}
|
||||
@@ -211,9 +233,9 @@ namespace OpenRA.FileSystem
|
||||
public bool TryGetPackageContaining(string path, out IReadOnlyPackage package, out string filename)
|
||||
{
|
||||
var explicitSplit = path.IndexOf('|');
|
||||
if (explicitSplit > 0 && explicitMounts.TryGetValue(path[..explicitSplit], out package))
|
||||
if (explicitSplit > 0 && explicitMounts.TryGetValue(path.Substring(0, explicitSplit), out package))
|
||||
{
|
||||
filename = path[(explicitSplit + 1)..];
|
||||
filename = path.Substring(explicitSplit + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -228,9 +250,10 @@ namespace OpenRA.FileSystem
|
||||
var explicitSplit = filename.IndexOf('|');
|
||||
if (explicitSplit > 0)
|
||||
{
|
||||
if (explicitMounts.TryGetValue(filename[..explicitSplit], out var explicitPackage))
|
||||
IReadOnlyPackage explicitPackage;
|
||||
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out explicitPackage))
|
||||
{
|
||||
s = explicitPackage.GetStream(filename[(explicitSplit + 1)..]);
|
||||
s = explicitPackage.GetStream(filename.Substring(explicitSplit + 1));
|
||||
if (s != null)
|
||||
return true;
|
||||
}
|
||||
@@ -240,15 +263,9 @@ namespace OpenRA.FileSystem
|
||||
if (s != null)
|
||||
return true;
|
||||
|
||||
// The file should be in an explicit package (but we couldn't find it)
|
||||
// Thus don't try to find it using the filename (which contains the invalid '|' char)
|
||||
// This can be removed once the TODO below is resolved
|
||||
if (explicitSplit > 0)
|
||||
return false;
|
||||
|
||||
// Ask each package individually
|
||||
// TODO: This fallback can be removed once the filesystem cleanups are complete
|
||||
var package = mountedPackages.Keys.LastOrDefault(x => x.Contains(filename));
|
||||
var package = mountedPackages.Keys.LastOrDefault(x => x.Contains(filename));
|
||||
if (package != null)
|
||||
{
|
||||
s = package.GetStream(filename);
|
||||
@@ -263,90 +280,14 @@ namespace OpenRA.FileSystem
|
||||
{
|
||||
var explicitSplit = filename.IndexOf('|');
|
||||
if (explicitSplit > 0)
|
||||
if (explicitMounts.TryGetValue(filename[..explicitSplit], out var explicitPackage))
|
||||
if (explicitPackage.Contains(filename[(explicitSplit + 1)..]))
|
||||
{
|
||||
IReadOnlyPackage explicitPackage;
|
||||
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out explicitPackage))
|
||||
if (explicitPackage.Contains(filename.Substring(explicitSplit + 1)))
|
||||
return true;
|
||||
}
|
||||
|
||||
return fileIndex.ContainsKey(filename);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given filename references an external mod via an explicit mount.
|
||||
/// </summary>
|
||||
public bool IsExternalModFile(string filename)
|
||||
{
|
||||
var explicitSplit = filename.IndexOf('|');
|
||||
if (explicitSplit < 0)
|
||||
return false;
|
||||
|
||||
if (!explicitMounts.TryGetValue(filename[..explicitSplit], out var explicitPackage))
|
||||
return false;
|
||||
|
||||
if (installedMods[modID].Package == explicitPackage)
|
||||
return false;
|
||||
|
||||
return modPackages.Contains(explicitPackage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a filesystem for an assembly, accounting for explicit and mod mounts.
|
||||
/// Assemblies must exist in the native OS file system (not inside an OpenRA-defined package).
|
||||
/// </summary>
|
||||
public static string ResolveAssemblyPath(string path, Manifest manifest, InstalledMods installedMods)
|
||||
{
|
||||
var explicitSplit = path.IndexOf('|');
|
||||
if (explicitSplit > 0 && !path.StartsWith("^"))
|
||||
{
|
||||
var parent = path[..explicitSplit];
|
||||
var filename = path[(explicitSplit + 1)..];
|
||||
|
||||
var parentPath = manifest.Packages.FirstOrDefault(kv => kv.Value == parent).Key;
|
||||
if (parentPath == null)
|
||||
return null;
|
||||
|
||||
if (parentPath.StartsWith("$", StringComparison.Ordinal))
|
||||
{
|
||||
if (!installedMods.TryGetValue(parentPath[1..], out var mod))
|
||||
return null;
|
||||
|
||||
if (mod.Package is not Folder)
|
||||
return null;
|
||||
|
||||
path = Path.Combine(mod.Package.Name, filename);
|
||||
}
|
||||
else
|
||||
path = Path.Combine(parentPath, filename);
|
||||
}
|
||||
|
||||
var resolvedPath = Platform.ResolvePath(path);
|
||||
return File.Exists(resolvedPath) ? resolvedPath : null;
|
||||
}
|
||||
|
||||
public static string ResolveCaseInsensitivePath(string path)
|
||||
{
|
||||
var resolved = Path.GetPathRoot(path);
|
||||
|
||||
if (resolved == null)
|
||||
return null;
|
||||
|
||||
foreach (var name in path[resolved.Length..].Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))
|
||||
{
|
||||
// Filter out paths of the form /foo/bar/./baz
|
||||
if (name == ".")
|
||||
continue;
|
||||
|
||||
resolved = Directory.GetFileSystemEntries(resolved).FirstOrDefault(e => e.Equals(Path.Combine(resolved, name), StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
if (resolved == null)
|
||||
return null;
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
public string GetPrefix(IReadOnlyPackage package)
|
||||
{
|
||||
return explicitMounts.ContainsValue(package) ? explicitMounts.First(f => f.Value == package).Key : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -9,68 +9,45 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class Folder : IReadWritePackage
|
||||
{
|
||||
public string Name { get; }
|
||||
readonly string path;
|
||||
|
||||
public Folder(string path)
|
||||
{
|
||||
Name = path;
|
||||
this.path = path;
|
||||
if (!Directory.Exists(path))
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
|
||||
public string Name { get { return path; } }
|
||||
|
||||
public IEnumerable<string> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
// Order may vary on different file systems and it matters for hashing.
|
||||
return Directory.GetFiles(Name, "*", SearchOption.TopDirectoryOnly)
|
||||
.Concat(Directory.GetDirectories(Name))
|
||||
.Select(Path.GetFileName)
|
||||
.OrderBy(f => f);
|
||||
foreach (var filename in Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly))
|
||||
yield return Path.GetFileName(filename);
|
||||
foreach (var filename in Directory.GetDirectories(path))
|
||||
yield return Path.GetFileName(filename);
|
||||
}
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
try { return File.OpenRead(Path.Combine(Name, filename)); }
|
||||
try { return File.OpenRead(Path.Combine(path, filename)); }
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
var combined = Path.Combine(Name, filename);
|
||||
return combined.StartsWith(Name, StringComparison.Ordinal) && File.Exists(combined);
|
||||
}
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
|
||||
{
|
||||
var resolvedPath = Platform.ResolvePath(Path.Combine(Name, filename));
|
||||
if (Directory.Exists(resolvedPath))
|
||||
return new Folder(resolvedPath);
|
||||
|
||||
// Zip files loaded from Folders (and *only* from Folders) can be read-write
|
||||
if (ZipFileLoader.TryParseReadWritePackage(resolvedPath, out var readWritePackage))
|
||||
return readWritePackage;
|
||||
|
||||
// Other package types can be loaded normally
|
||||
var s = GetStream(filename);
|
||||
if (s == null)
|
||||
return null;
|
||||
|
||||
if (context.TryParsePackage(s, filename, out var package))
|
||||
return package;
|
||||
|
||||
s.Dispose();
|
||||
return null;
|
||||
var combined = Path.Combine(path, filename);
|
||||
return combined.StartsWith(path) && File.Exists(combined);
|
||||
}
|
||||
|
||||
public void Update(string filename, byte[] contents)
|
||||
@@ -80,7 +57,7 @@ namespace OpenRA.FileSystem
|
||||
// in FileSystem.OpenPackage. Their internal name therefore contains the
|
||||
// full parent path too. We need to be careful to not add a second path
|
||||
// prefix to these hacked packages.
|
||||
var filePath = filename.StartsWith(Name) ? filename : Path.Combine(Name, filename);
|
||||
var filePath = filename.StartsWith(path) ? filename : Path.Combine(path, filename);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
|
||||
using (var s = File.Create(filePath))
|
||||
@@ -94,7 +71,7 @@ namespace OpenRA.FileSystem
|
||||
// in FileSystem.OpenPackage. Their internal name therefore contains the
|
||||
// full parent path too. We need to be careful to not add a second path
|
||||
// prefix to these hacked packages.
|
||||
var filePath = filename.StartsWith(Name) ? filename : Path.Combine(Name, filename);
|
||||
var filePath = filename.StartsWith(path) ? filename : Path.Combine(path, filename);
|
||||
if (Directory.Exists(filePath))
|
||||
Directory.Delete(filePath, true);
|
||||
else if (File.Exists(filePath))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -15,23 +15,12 @@ using System.IO;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public interface IPackageLoader
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempt to parse a stream as this type of package.
|
||||
/// If successful, the loader is expected to take ownership of `s` and dispose it once done.
|
||||
/// If unsuccessful, the loader is expected to return the stream position to where it started.
|
||||
/// </summary>
|
||||
bool TryParsePackage(Stream s, string filename, FileSystem context, out IReadOnlyPackage package);
|
||||
}
|
||||
|
||||
public interface IReadOnlyPackage : IDisposable
|
||||
{
|
||||
string Name { get; }
|
||||
IEnumerable<string> Contents { get; }
|
||||
Stream GetStream(string filename);
|
||||
bool Contains(string filename);
|
||||
IReadOnlyPackage OpenPackage(string filename, FileSystem context);
|
||||
}
|
||||
|
||||
public interface IReadWritePackage : IReadOnlyPackage
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -9,9 +9,10 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public class IdxEntry
|
||||
{
|
||||
@@ -27,7 +28,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
var name = s.ReadASCII(16);
|
||||
var pos = name.IndexOf('\0');
|
||||
if (pos != 0)
|
||||
name = name[..pos];
|
||||
name = name.Substring(0, pos);
|
||||
|
||||
Filename = string.Concat(name, ".wav");
|
||||
Offset = s.ReadUInt32();
|
||||
@@ -39,7 +40,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Filename} - offset 0x{Offset:x8} - length 0x{Length:x8}";
|
||||
return "{0} - offset 0x{1:x8} - length 0x{2:x8}".F(Filename, Offset, Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
137
OpenRA.Game/FileSystem/InstallShieldPackage.cs
Normal file
137
OpenRA.Game/FileSystem/InstallShieldPackage.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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.IO;
|
||||
using OpenRA.FileFormats;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class InstallShieldPackage : IReadOnlyPackage
|
||||
{
|
||||
public struct Entry
|
||||
{
|
||||
public readonly uint Offset;
|
||||
public readonly uint Length;
|
||||
|
||||
public Entry(uint offset, uint length)
|
||||
{
|
||||
Offset = offset;
|
||||
Length = length;
|
||||
}
|
||||
}
|
||||
|
||||
public string Name { get; private set; }
|
||||
public IEnumerable<string> Contents { get { return index.Keys; } }
|
||||
|
||||
readonly Dictionary<string, Entry> index = new Dictionary<string, Entry>();
|
||||
readonly Stream s;
|
||||
readonly long dataStart = 255;
|
||||
|
||||
public InstallShieldPackage(FileSystem context, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
|
||||
s = context.Open(filename);
|
||||
try
|
||||
{
|
||||
// Parse package header
|
||||
var signature = s.ReadUInt32();
|
||||
if (signature != 0x8C655D13)
|
||||
throw new InvalidDataException("Not an Installshield package");
|
||||
|
||||
s.Position += 8;
|
||||
/*var FileCount = */s.ReadUInt16();
|
||||
s.Position += 4;
|
||||
/*var ArchiveSize = */s.ReadUInt32();
|
||||
s.Position += 19;
|
||||
var tocAddress = s.ReadInt32();
|
||||
s.Position += 4;
|
||||
var dirCount = s.ReadUInt16();
|
||||
|
||||
// Parse the directory list
|
||||
s.Position = tocAddress;
|
||||
|
||||
// Parse directories
|
||||
var directories = new Dictionary<string, uint>();
|
||||
for (var i = 0; i < dirCount; i++)
|
||||
{
|
||||
// Parse directory header
|
||||
var fileCount = s.ReadUInt16();
|
||||
var chunkSize = s.ReadUInt16();
|
||||
var nameLength = s.ReadUInt16();
|
||||
var dirName = s.ReadASCII(nameLength);
|
||||
|
||||
// Skip to the end of the chunk
|
||||
s.ReadBytes(chunkSize - nameLength - 6);
|
||||
directories.Add(dirName, fileCount);
|
||||
}
|
||||
|
||||
// Parse files
|
||||
foreach (var dir in directories)
|
||||
for (var i = 0; i < dir.Value; i++)
|
||||
ParseFile(s, dir.Key);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
uint accumulatedData = 0;
|
||||
void ParseFile(Stream s, string dirName)
|
||||
{
|
||||
s.Position += 7;
|
||||
var compressedSize = s.ReadUInt32();
|
||||
s.Position += 12;
|
||||
var chunkSize = s.ReadUInt16();
|
||||
s.Position += 4;
|
||||
var nameLength = s.ReadByte();
|
||||
var fileName = dirName + "\\" + s.ReadASCII(nameLength);
|
||||
|
||||
// Use index syntax to overwrite any duplicate entries with the last value
|
||||
index[fileName] = new Entry(accumulatedData, compressedSize);
|
||||
accumulatedData += compressedSize;
|
||||
|
||||
// Skip to the end of the chunk
|
||||
s.Position += chunkSize - nameLength - 30;
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
Entry e;
|
||||
if (!index.TryGetValue(filename, out e))
|
||||
return null;
|
||||
|
||||
s.Seek(dataStart + e.Offset, SeekOrigin.Begin);
|
||||
|
||||
var ret = new MemoryStream();
|
||||
Blast.Decompress(s, ret);
|
||||
ret.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, Entry> Index { get { return new ReadOnlyDictionary<string, Entry>(index); } }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
227
OpenRA.Game/FileSystem/MixFile.cs
Normal file
227
OpenRA.Game/FileSystem/MixFile.cs
Normal file
@@ -0,0 +1,227 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class MixFile : IReadOnlyPackage
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public IEnumerable<string> Contents { get { return index.Keys; } }
|
||||
|
||||
readonly Dictionary<string, PackageEntry> index;
|
||||
readonly long dataStart;
|
||||
readonly Stream s;
|
||||
readonly FileSystem context;
|
||||
|
||||
public MixFile(FileSystem context, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
this.context = context;
|
||||
|
||||
s = context.Open(filename);
|
||||
try
|
||||
{
|
||||
// Detect format type
|
||||
var isCncMix = s.ReadUInt16() != 0;
|
||||
|
||||
// The C&C mix format doesn't contain any flags or encryption
|
||||
var isEncrypted = false;
|
||||
if (!isCncMix)
|
||||
isEncrypted = (s.ReadUInt16() & 0x2) != 0;
|
||||
|
||||
List<PackageEntry> entries;
|
||||
if (isEncrypted)
|
||||
{
|
||||
long unused;
|
||||
entries = ParseHeader(DecryptHeader(s, 4, out dataStart), 0, out unused);
|
||||
}
|
||||
else
|
||||
entries = ParseHeader(s, isCncMix ? 0 : 4, out dataStart);
|
||||
|
||||
index = ParseIndex(entries.ToDictionaryWithConflictLog(x => x.Hash,
|
||||
"{0} ({1} format, Encrypted: {2}, DataStart: {3})".F(filename, isCncMix ? "C&C" : "RA/TS/RA2", isEncrypted, dataStart),
|
||||
null, x => "(offs={0}, len={1})".F(x.Offset, x.Length)));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<string, PackageEntry> ParseIndex(Dictionary<uint, PackageEntry> entries)
|
||||
{
|
||||
var classicIndex = new Dictionary<string, PackageEntry>();
|
||||
var crcIndex = new Dictionary<string, PackageEntry>();
|
||||
var allPossibleFilenames = new HashSet<string>();
|
||||
|
||||
// Try and find a local mix database
|
||||
var dbNameClassic = PackageEntry.HashFilename("local mix database.dat", PackageHashType.Classic);
|
||||
var dbNameCRC = PackageEntry.HashFilename("local mix database.dat", PackageHashType.CRC32);
|
||||
foreach (var kv in entries)
|
||||
{
|
||||
if (kv.Key == dbNameClassic || kv.Key == dbNameCRC)
|
||||
{
|
||||
var db = new XccLocalDatabase(GetContent(kv.Value));
|
||||
foreach (var e in db.Entries)
|
||||
allPossibleFilenames.Add(e);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Load the global mix database
|
||||
// TODO: This should be passed to the mix file ctor
|
||||
if (context.Exists("global mix database.dat"))
|
||||
{
|
||||
using (var db = new XccGlobalDatabase(context.Open("global mix database.dat")))
|
||||
{
|
||||
foreach (var e in db.Entries)
|
||||
allPossibleFilenames.Add(e);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var filename in allPossibleFilenames)
|
||||
{
|
||||
var classicHash = PackageEntry.HashFilename(filename, PackageHashType.Classic);
|
||||
var crcHash = PackageEntry.HashFilename(filename, PackageHashType.CRC32);
|
||||
PackageEntry e;
|
||||
|
||||
if (entries.TryGetValue(classicHash, out e))
|
||||
classicIndex.Add(filename, e);
|
||||
|
||||
if (entries.TryGetValue(crcHash, out e))
|
||||
crcIndex.Add(filename, e);
|
||||
}
|
||||
|
||||
var bestIndex = crcIndex.Count > classicIndex.Count ? crcIndex : classicIndex;
|
||||
|
||||
var unknown = entries.Count - bestIndex.Count;
|
||||
if (unknown > 0)
|
||||
Log.Write("debug", "{0}: failed to resolve filenames for {1} unknown hashes".F(Name, unknown));
|
||||
|
||||
return bestIndex;
|
||||
}
|
||||
|
||||
static List<PackageEntry> ParseHeader(Stream s, long offset, out long headerEnd)
|
||||
{
|
||||
s.Seek(offset, SeekOrigin.Begin);
|
||||
var numFiles = s.ReadUInt16();
|
||||
/*uint dataSize = */s.ReadUInt32();
|
||||
|
||||
var items = new List<PackageEntry>();
|
||||
for (var i = 0; i < numFiles; i++)
|
||||
items.Add(new PackageEntry(s));
|
||||
|
||||
headerEnd = offset + 6 + numFiles * PackageEntry.Size;
|
||||
return items;
|
||||
}
|
||||
|
||||
static MemoryStream DecryptHeader(Stream s, long offset, out long headerEnd)
|
||||
{
|
||||
s.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Decrypt blowfish key
|
||||
var keyblock = s.ReadBytes(80);
|
||||
var blowfishKey = new BlowfishKeyProvider().DecryptKey(keyblock);
|
||||
var fish = new Blowfish(blowfishKey);
|
||||
|
||||
// Decrypt first block to work out the header length
|
||||
var ms = Decrypt(ReadBlocks(s, offset + 80, 1), fish);
|
||||
var numFiles = ms.ReadUInt16();
|
||||
|
||||
// Decrypt the full header - round bytes up to a full block
|
||||
var blockCount = (13 + numFiles * PackageEntry.Size) / 8;
|
||||
headerEnd = offset + 80 + blockCount * 8;
|
||||
|
||||
return Decrypt(ReadBlocks(s, offset + 80, blockCount), fish);
|
||||
}
|
||||
|
||||
static MemoryStream Decrypt(uint[] h, Blowfish fish)
|
||||
{
|
||||
var decrypted = fish.Decrypt(h);
|
||||
|
||||
var ms = new MemoryStream();
|
||||
var writer = new BinaryWriter(ms);
|
||||
foreach (var t in decrypted)
|
||||
writer.Write(t);
|
||||
writer.Flush();
|
||||
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
return ms;
|
||||
}
|
||||
|
||||
static uint[] ReadBlocks(Stream s, long offset, int count)
|
||||
{
|
||||
if (offset < 0)
|
||||
throw new ArgumentOutOfRangeException("offset", "Non-negative number required.");
|
||||
|
||||
if (count < 0)
|
||||
throw new ArgumentOutOfRangeException("count", "Non-negative number required.");
|
||||
|
||||
if (offset + (count * 2) > s.Length)
|
||||
throw new ArgumentException("Bytes to read {0} and offset {1} greater than stream length {2}.".F(count * 2, offset, s.Length));
|
||||
|
||||
s.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// A block is a single encryption unit (represented as two 32-bit integers)
|
||||
var ret = new uint[2 * count];
|
||||
for (var i = 0; i < ret.Length; i++)
|
||||
ret[i] = s.ReadUInt32();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public Stream GetContent(PackageEntry entry)
|
||||
{
|
||||
Stream parentStream;
|
||||
var offset = dataStart + entry.Offset + SegmentStream.GetOverallNestedOffset(s, out parentStream);
|
||||
var path = ((FileStream)parentStream).Name;
|
||||
return new SegmentStream(File.OpenRead(path), offset, entry.Length);
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
PackageEntry e;
|
||||
if (!index.TryGetValue(filename, out e))
|
||||
return null;
|
||||
|
||||
return GetContent(e);
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, PackageEntry> Index
|
||||
{
|
||||
get
|
||||
{
|
||||
var absoluteIndex = index.ToDictionary(e => e.Key, e => new PackageEntry(e.Value.Hash, (uint)(e.Value.Offset + dataStart), e.Value.Length));
|
||||
return new ReadOnlyDictionary<string, PackageEntry>(absoluteIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
111
OpenRA.Game/FileSystem/PackageEntry.cs
Normal file
111
OpenRA.Game/FileSystem/PackageEntry.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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.IO;
|
||||
using System.Text;
|
||||
using OpenRA.FileFormats;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public enum PackageHashType { Classic, CRC32 }
|
||||
|
||||
public class PackageEntry
|
||||
{
|
||||
public const int Size = 12;
|
||||
public readonly uint Hash;
|
||||
public readonly uint Offset;
|
||||
public readonly uint Length;
|
||||
|
||||
public PackageEntry(uint hash, uint offset, uint length)
|
||||
{
|
||||
Hash = hash;
|
||||
Offset = offset;
|
||||
Length = length;
|
||||
}
|
||||
|
||||
public PackageEntry(Stream s)
|
||||
{
|
||||
Hash = s.ReadUInt32();
|
||||
Offset = s.ReadUInt32();
|
||||
Length = s.ReadUInt32();
|
||||
}
|
||||
|
||||
public void Write(BinaryWriter w)
|
||||
{
|
||||
w.Write(Hash);
|
||||
w.Write(Offset);
|
||||
w.Write(Length);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
string filename;
|
||||
if (names.TryGetValue(Hash, out filename))
|
||||
return "{0} - offset 0x{1:x8} - length 0x{2:x8}".F(filename, Offset, Length);
|
||||
else
|
||||
return "0x{0:x8} - offset 0x{1:x8} - length 0x{2:x8}".F(Hash, Offset, Length);
|
||||
}
|
||||
|
||||
public static uint HashFilename(string name, PackageHashType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case PackageHashType.Classic:
|
||||
{
|
||||
name = name.ToUpperInvariant();
|
||||
if (name.Length % 4 != 0)
|
||||
name = name.PadRight(name.Length + (4 - name.Length % 4), '\0');
|
||||
|
||||
using (var ms = new MemoryStream(Encoding.ASCII.GetBytes(name)))
|
||||
{
|
||||
var len = name.Length >> 2;
|
||||
uint result = 0;
|
||||
|
||||
while (len-- != 0)
|
||||
result = ((result << 1) | (result >> 31)) + ms.ReadUInt32();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
case PackageHashType.CRC32:
|
||||
{
|
||||
name = name.ToUpperInvariant();
|
||||
var l = name.Length;
|
||||
var a = l >> 2;
|
||||
if ((l & 3) != 0)
|
||||
{
|
||||
name += (char)(l - (a << 2));
|
||||
var i = 3 - (l & 3);
|
||||
while (i-- != 0)
|
||||
name += name[a << 2];
|
||||
}
|
||||
|
||||
return CRC32.Calculate(Encoding.ASCII.GetBytes(name));
|
||||
}
|
||||
|
||||
default: throw new NotImplementedException("Unknown hash type `{0}`".F(type));
|
||||
}
|
||||
}
|
||||
|
||||
static Dictionary<uint, string> names = new Dictionary<uint, string>();
|
||||
|
||||
public static void AddStandardName(string s)
|
||||
{
|
||||
var hash = HashFilename(s, PackageHashType.Classic); // RA1 and TD
|
||||
names.Add(hash, s);
|
||||
var crcHash = HashFilename(s, PackageHashType.CRC32); // TS
|
||||
names.Add(crcHash, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
84
OpenRA.Game/FileSystem/Pak.cs
Normal file
84
OpenRA.Game/FileSystem/Pak.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
struct Entry
|
||||
{
|
||||
public uint Offset;
|
||||
public uint Length;
|
||||
public string Filename;
|
||||
}
|
||||
|
||||
public sealed class PakFile : IReadOnlyPackage
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public IEnumerable<string> Contents { get { return index.Keys; } }
|
||||
|
||||
readonly Dictionary<string, Entry> index;
|
||||
readonly Stream stream;
|
||||
|
||||
public PakFile(FileSystem context, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
index = new Dictionary<string, Entry>();
|
||||
|
||||
stream = context.Open(filename);
|
||||
try
|
||||
{
|
||||
index = new Dictionary<string, Entry>();
|
||||
var offset = stream.ReadUInt32();
|
||||
while (offset != 0)
|
||||
{
|
||||
var file = stream.ReadASCIIZ();
|
||||
var next = stream.ReadUInt32();
|
||||
var length = (next == 0 ? (uint)stream.Length : next) - offset;
|
||||
|
||||
// Ignore duplicate files
|
||||
if (index.ContainsKey(file))
|
||||
continue;
|
||||
|
||||
index.Add(file, new Entry { Offset = offset, Length = length, Filename = file });
|
||||
offset = next;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
Entry entry;
|
||||
if (!index.TryGetValue(filename, out entry))
|
||||
return null;
|
||||
|
||||
stream.Seek(entry.Offset, SeekOrigin.Begin);
|
||||
var data = stream.ReadBytes((int)entry.Length);
|
||||
return new MemoryStream(data);
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
stream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -9,234 +9,152 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using SZipFile = ICSharpCode.SharpZipLib.Zip.ZipFile;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public class ZipFileLoader : IPackageLoader
|
||||
public sealed class ZipFile : IReadWritePackage
|
||||
{
|
||||
static readonly uint ZipSignature = 0x04034b50;
|
||||
public IReadWritePackage Parent { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
readonly Stream pkgStream;
|
||||
readonly SZipFile pkg;
|
||||
|
||||
class ReadOnlyZipFile : IReadOnlyPackage
|
||||
static ZipFile()
|
||||
{
|
||||
public string Name { get; protected set; }
|
||||
protected ZipFile pkg;
|
||||
ZipConstants.DefaultCodePage = Encoding.UTF8.CodePage;
|
||||
}
|
||||
|
||||
// Dummy constructor for use with ReadWriteZipFile
|
||||
protected ReadOnlyZipFile() { }
|
||||
public ZipFile(Stream stream, string name, IReadOnlyPackage parent = null)
|
||||
{
|
||||
// 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
|
||||
pkgStream = new MemoryStream();
|
||||
stream.CopyTo(pkgStream);
|
||||
pkgStream.Position = 0;
|
||||
|
||||
public ReadOnlyZipFile(Stream s, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
pkg = new ZipFile(s);
|
||||
}
|
||||
Name = name;
|
||||
Parent = parent as IReadWritePackage;
|
||||
pkg = new SZipFile(pkgStream);
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
var entry = pkg.GetEntry(filename);
|
||||
if (entry == null)
|
||||
return null;
|
||||
public ZipFile(IReadOnlyFileSystem context, string filename)
|
||||
{
|
||||
string name;
|
||||
IReadOnlyPackage p;
|
||||
if (!context.TryGetPackageContaining(filename, out p, out name))
|
||||
throw new FileNotFoundException("Unable to find parent package for " + filename);
|
||||
|
||||
using (var z = pkg.GetInputStream(entry))
|
||||
{
|
||||
var ms = new MemoryStream((int)entry.Size);
|
||||
z.CopyTo(ms);
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
return ms;
|
||||
}
|
||||
}
|
||||
Name = name;
|
||||
Parent = p as IReadWritePackage;
|
||||
|
||||
public IEnumerable<string> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (ZipEntry entry in pkg)
|
||||
if (entry.IsFile)
|
||||
yield return entry.Name;
|
||||
}
|
||||
}
|
||||
// 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
|
||||
pkgStream = new MemoryStream();
|
||||
using (var sourceStream = p.GetStream(name))
|
||||
sourceStream.CopyTo(pkgStream);
|
||||
pkgStream.Position = 0;
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return pkg.GetEntry(filename) != null;
|
||||
}
|
||||
pkg = new SZipFile(pkgStream);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
pkg?.Close();
|
||||
}
|
||||
ZipFile(string filename, IReadWritePackage parent)
|
||||
{
|
||||
pkgStream = new MemoryStream();
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
|
||||
{
|
||||
// Directories are stored with a trailing "/" in the index
|
||||
var entry = pkg.GetEntry(filename) ?? pkg.GetEntry(filename + "/");
|
||||
if (entry == null)
|
||||
return null;
|
||||
Name = filename;
|
||||
Parent = parent;
|
||||
pkg = SZipFile.Create(pkgStream);
|
||||
}
|
||||
|
||||
if (entry.IsDirectory)
|
||||
return new ZipFolder(this, filename);
|
||||
public static ZipFile Create(string filename, IReadWritePackage parent)
|
||||
{
|
||||
return new ZipFile(filename, parent);
|
||||
}
|
||||
|
||||
// Other package types can be loaded normally
|
||||
var s = GetStream(filename);
|
||||
if (s == null)
|
||||
return null;
|
||||
|
||||
if (context.TryParsePackage(s, filename, out var package))
|
||||
return package;
|
||||
|
||||
s.Dispose();
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
var entry = pkg.GetEntry(filename);
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
using (var z = pkg.GetInputStream(entry))
|
||||
{
|
||||
var ms = new MemoryStream();
|
||||
z.CopyTo(ms);
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
return ms;
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ReadWriteZipFile : ReadOnlyZipFile, IReadWritePackage
|
||||
public IEnumerable<string> Contents
|
||||
{
|
||||
readonly MemoryStream pkgStream = new();
|
||||
|
||||
public ReadWriteZipFile(string filename, bool create = false)
|
||||
get
|
||||
{
|
||||
// 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
|
||||
if (!create)
|
||||
{
|
||||
using (var copy = new MemoryStream(File.ReadAllBytes(filename)))
|
||||
{
|
||||
pkgStream.Capacity = (int)copy.Length;
|
||||
copy.CopyTo(pkgStream);
|
||||
}
|
||||
}
|
||||
|
||||
pkgStream.Position = 0;
|
||||
pkg = new ZipFile(pkgStream);
|
||||
Name = filename;
|
||||
}
|
||||
|
||||
void Commit()
|
||||
{
|
||||
var pos = pkgStream.Position;
|
||||
pkgStream.Position = 0;
|
||||
File.WriteAllBytes(Name, pkgStream.ReadBytes((int)pkgStream.Length));
|
||||
pkgStream.Position = pos;
|
||||
}
|
||||
|
||||
public void Update(string filename, byte[] contents)
|
||||
{
|
||||
pkg.BeginUpdate();
|
||||
pkg.Add(new StaticStreamDataSource(new MemoryStream(contents)), filename);
|
||||
pkg.CommitUpdate();
|
||||
Commit();
|
||||
}
|
||||
|
||||
public void Delete(string filename)
|
||||
{
|
||||
pkg.BeginUpdate();
|
||||
pkg.Delete(filename);
|
||||
pkg.CommitUpdate();
|
||||
Commit();
|
||||
foreach (ZipEntry entry in pkg)
|
||||
yield return entry.Name;
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ZipFolder : IReadOnlyPackage
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
public string Name { get; }
|
||||
public ReadOnlyZipFile Parent { get; }
|
||||
|
||||
public ZipFolder(ReadOnlyZipFile parent, string path)
|
||||
{
|
||||
if (path.EndsWith("/", StringComparison.Ordinal))
|
||||
path = path[..^1];
|
||||
|
||||
Name = path;
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
// Zip files use '/' as a path separator
|
||||
return Parent.GetStream(Name + '/' + filename);
|
||||
}
|
||||
|
||||
public IEnumerable<string> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var entry in Parent.Contents)
|
||||
{
|
||||
if (entry.StartsWith(Name, StringComparison.Ordinal) && entry != Name)
|
||||
{
|
||||
var filename = entry[(Name.Length + 1)..];
|
||||
var dirLevels = filename.Split('/').Count(c => !string.IsNullOrEmpty(c));
|
||||
if (dirLevels == 1)
|
||||
yield return filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return Parent.Contains(Name + '/' + filename);
|
||||
}
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
|
||||
{
|
||||
return Parent.OpenPackage(Name + '/' + filename, context);
|
||||
}
|
||||
|
||||
public void Dispose() { /* nothing to do */ }
|
||||
return pkg.GetEntry(filename) != null;
|
||||
}
|
||||
|
||||
class StaticStreamDataSource : IStaticDataSource
|
||||
void Commit()
|
||||
{
|
||||
readonly Stream s;
|
||||
public StaticStreamDataSource(Stream s)
|
||||
{
|
||||
this.s = s;
|
||||
}
|
||||
if (Parent == null)
|
||||
throw new InvalidDataException("Cannot update ZipFile without writable parent");
|
||||
|
||||
public Stream GetSource()
|
||||
{
|
||||
return s;
|
||||
}
|
||||
var pos = pkgStream.Position;
|
||||
pkgStream.Position = 0;
|
||||
Parent.Update(Name, pkgStream.ReadBytes((int)pkgStream.Length));
|
||||
pkgStream.Position = pos;
|
||||
}
|
||||
|
||||
public bool TryParsePackage(Stream s, string filename, FileSystem context, out IReadOnlyPackage package)
|
||||
public void Update(string filename, byte[] contents)
|
||||
{
|
||||
var readSignature = s.ReadUInt32();
|
||||
s.Position -= 4;
|
||||
|
||||
if (readSignature != ZipSignature)
|
||||
{
|
||||
package = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
package = new ReadOnlyZipFile(s, filename);
|
||||
return true;
|
||||
pkg.BeginUpdate();
|
||||
pkg.Add(new StaticStreamDataSource(new MemoryStream(contents)), filename);
|
||||
pkg.CommitUpdate();
|
||||
Commit();
|
||||
}
|
||||
|
||||
public static bool TryParseReadWritePackage(string filename, out IReadWritePackage package)
|
||||
public void Delete(string filename)
|
||||
{
|
||||
using (var s = File.OpenRead(filename))
|
||||
{
|
||||
if (s.ReadUInt32() != ZipSignature)
|
||||
{
|
||||
package = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
package = new ReadWriteZipFile(filename);
|
||||
return true;
|
||||
pkg.BeginUpdate();
|
||||
pkg.Delete(filename);
|
||||
pkg.CommitUpdate();
|
||||
Commit();
|
||||
}
|
||||
|
||||
public static IReadWritePackage Create(string filename)
|
||||
public void Dispose()
|
||||
{
|
||||
return new ReadWriteZipFile(filename, true);
|
||||
if (pkg != null)
|
||||
pkg.Close();
|
||||
|
||||
if (pkgStream != null)
|
||||
pkgStream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class StaticStreamDataSource : IStaticDataSource
|
||||
{
|
||||
readonly Stream s;
|
||||
public StaticStreamDataSource(Stream s)
|
||||
{
|
||||
this.s = s;
|
||||
}
|
||||
|
||||
public Stream GetSource()
|
||||
{
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
76
OpenRA.Game/FileSystem/ZipFolder.cs
Normal file
76
OpenRA.Game/FileSystem/ZipFolder.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using SZipFile = ICSharpCode.SharpZipLib.Zip.ZipFile;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class ZipFolder : IReadOnlyPackage
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public ZipFile Parent { get; private set; }
|
||||
readonly string path;
|
||||
|
||||
static ZipFolder()
|
||||
{
|
||||
ZipConstants.DefaultCodePage = Encoding.UTF8.CodePage;
|
||||
}
|
||||
|
||||
public ZipFolder(FileSystem context, ZipFile parent, string path, string filename)
|
||||
{
|
||||
if (filename.EndsWith("/"))
|
||||
filename = filename.Substring(0, filename.Length - 1);
|
||||
|
||||
Name = filename;
|
||||
Parent = parent;
|
||||
if (path.EndsWith("/"))
|
||||
path = path.Substring(0, path.Length - 1);
|
||||
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
// Zip files use '/' as a path separator
|
||||
return Parent.GetStream(path + '/' + filename);
|
||||
}
|
||||
|
||||
public IEnumerable<string> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var entry in Parent.Contents)
|
||||
{
|
||||
if (entry.StartsWith(path) && entry != path)
|
||||
{
|
||||
var filename = entry.Substring(path.Length + 1);
|
||||
var dirLevels = filename.Split('/').Count(c => !string.IsNullOrEmpty(c));
|
||||
if (dirLevels == 1)
|
||||
yield return filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return Parent.Contains(path + '/' + filename);
|
||||
}
|
||||
|
||||
public void Dispose() { /* nothing to do */ }
|
||||
}
|
||||
}
|
||||
@@ -1,37 +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;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class FontData
|
||||
{
|
||||
public readonly string Font;
|
||||
public readonly int Size;
|
||||
public readonly int Ascender;
|
||||
}
|
||||
|
||||
public class Fonts : IGlobalModData
|
||||
{
|
||||
[FieldLoader.LoadUsing(nameof(LoadFonts))]
|
||||
public readonly Dictionary<string, FontData> FontList;
|
||||
|
||||
static object LoadFonts(MiniYaml y)
|
||||
{
|
||||
var ret = new Dictionary<string, FontData>();
|
||||
foreach (var node in y.Nodes)
|
||||
ret.Add(node.Key, FieldLoader.Load<FontData>(node.Value));
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -12,8 +12,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Network;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
@@ -24,7 +24,6 @@ namespace OpenRA
|
||||
|
||||
public string MapUid;
|
||||
public string MapTitle;
|
||||
public int FinalGameTick;
|
||||
|
||||
/// <summary>Game start timestamp (when the recoding started).</summary>
|
||||
public DateTime StartTimeUtc;
|
||||
@@ -33,13 +32,11 @@ namespace OpenRA
|
||||
public DateTime EndTimeUtc;
|
||||
|
||||
/// <summary>Gets the game's duration, from the time the game started until the replay recording stopped.</summary>
|
||||
public TimeSpan Duration => EndTimeUtc > StartTimeUtc ? EndTimeUtc - StartTimeUtc : TimeSpan.Zero;
|
||||
|
||||
public IList<Player> Players { get; }
|
||||
public HashSet<int> DisabledSpawnPoints = new();
|
||||
public MapPreview MapPreview => Game.ModData.MapCache[MapUid];
|
||||
public TimeSpan Duration { get { return EndTimeUtc > StartTimeUtc ? EndTimeUtc - StartTimeUtc : TimeSpan.Zero; } }
|
||||
public IList<Player> Players { get; private set; }
|
||||
public MapPreview MapPreview { get { return Game.ModData.MapCache[MapUid]; } }
|
||||
public IEnumerable<Player> HumanPlayers { get { return Players.Where(p => p.IsHuman); } }
|
||||
public bool IsSinglePlayer => HumanPlayers.Count() == 1;
|
||||
public bool IsSinglePlayer { get { return HumanPlayers.Count() == 1; } }
|
||||
|
||||
readonly Dictionary<OpenRA.Player, Player> playersByRuntime;
|
||||
|
||||
@@ -76,7 +73,7 @@ namespace OpenRA
|
||||
}
|
||||
catch (YamlException)
|
||||
{
|
||||
Log.Write("debug", $"GameInformation deserialized invalid MiniYaml:\n{data}");
|
||||
Log.Write("debug", "GameInformation deserialized invalid MiniYaml:\n{0}".F(data));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -89,7 +86,7 @@ namespace OpenRA
|
||||
};
|
||||
|
||||
for (var i = 0; i < Players.Count; i++)
|
||||
nodes.Add(new MiniYamlNode($"Player@{i}", FieldSaver.Save(Players[i])));
|
||||
nodes.Add(new MiniYamlNode("Player@{0}".F(i), FieldSaver.Save(Players[i])));
|
||||
|
||||
return nodes.WriteToString();
|
||||
}
|
||||
@@ -98,10 +95,10 @@ namespace OpenRA
|
||||
public void AddPlayer(OpenRA.Player runtimePlayer, Session lobbyInfo)
|
||||
{
|
||||
if (runtimePlayer == null)
|
||||
throw new ArgumentNullException(nameof(runtimePlayer));
|
||||
throw new ArgumentNullException("runtimePlayer");
|
||||
|
||||
if (lobbyInfo == null)
|
||||
throw new ArgumentNullException(nameof(lobbyInfo));
|
||||
throw new ArgumentNullException("lobbyInfo");
|
||||
|
||||
// We don't care about spectators and map players
|
||||
if (runtimePlayer.NonCombatant || !runtimePlayer.Playable)
|
||||
@@ -120,15 +117,11 @@ namespace OpenRA
|
||||
IsBot = runtimePlayer.IsBot,
|
||||
FactionName = runtimePlayer.Faction.Name,
|
||||
FactionId = runtimePlayer.Faction.InternalName,
|
||||
DisplayFactionName = runtimePlayer.DisplayFaction.Name,
|
||||
DisplayFactionId = runtimePlayer.DisplayFaction.InternalName,
|
||||
Color = runtimePlayer.Color,
|
||||
Team = client.Team,
|
||||
Handicap = client.Handicap,
|
||||
SpawnPoint = runtimePlayer.SpawnPoint,
|
||||
IsRandomFaction = runtimePlayer.Faction.InternalName != client.Faction,
|
||||
IsRandomSpawnPoint = runtimePlayer.DisplaySpawnPoint == 0,
|
||||
Fingerprint = client.Fingerprint
|
||||
IsRandomSpawnPoint = runtimePlayer.SpawnPoint != client.SpawnPoint
|
||||
};
|
||||
|
||||
playersByRuntime.Add(runtimePlayer, player);
|
||||
@@ -138,7 +131,9 @@ namespace OpenRA
|
||||
/// <summary>Gets the player information for the specified runtime player instance.</summary>
|
||||
public Player GetPlayer(OpenRA.Player runtimePlayer)
|
||||
{
|
||||
playersByRuntime.TryGetValue(runtimePlayer, out var player);
|
||||
Player player;
|
||||
|
||||
playersByRuntime.TryGetValue(runtimePlayer, out player);
|
||||
|
||||
return player;
|
||||
}
|
||||
@@ -159,16 +154,11 @@ namespace OpenRA
|
||||
|
||||
/// <summary>The faction ID, a.k.a. the faction's internal name.</summary>
|
||||
public string FactionId;
|
||||
public Color Color;
|
||||
|
||||
/// <summary>The faction (including Random, etc.) that was selected in the lobby.</summary>
|
||||
public string DisplayFactionName;
|
||||
public string DisplayFactionId;
|
||||
public HSLColor Color;
|
||||
|
||||
/// <summary>The team ID on start-up, or 0 if the player is not part of a team.</summary>
|
||||
public int Team;
|
||||
public int SpawnPoint;
|
||||
public int Handicap;
|
||||
|
||||
/// <summary>True if the faction was chosen at random; otherwise, false.</summary>
|
||||
public bool IsRandomFaction;
|
||||
@@ -176,9 +166,6 @@ namespace OpenRA
|
||||
/// <summary>True if the spawn point was chosen at random; otherwise, false.</summary>
|
||||
public bool IsRandomSpawnPoint;
|
||||
|
||||
/// <summary>Player authentication fingerprint for the OpenRA forum.</summary>
|
||||
public string Fingerprint;
|
||||
|
||||
#endregion
|
||||
|
||||
#region
|
||||
@@ -189,9 +176,6 @@ namespace OpenRA
|
||||
/// <summary>The time when this player won or lost the game.</summary>
|
||||
public DateTime OutcomeTimestampUtc;
|
||||
|
||||
/// <summary>The frame at which this player disconnected.</summary>
|
||||
public int DisconnectFrame;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -22,9 +22,6 @@ namespace OpenRA
|
||||
/// </summary>
|
||||
public class ActorInfo
|
||||
{
|
||||
public const string AbstractActorPrefix = "^";
|
||||
public const char TraitInstanceSeparator = '@';
|
||||
|
||||
/// <summary>
|
||||
/// The actor name can be anything, but the sprites used in the Render*: traits default to this one.
|
||||
/// If you add an ^ in front of the name, the engine will recognize this as a collection of traits
|
||||
@@ -32,8 +29,8 @@ namespace OpenRA
|
||||
/// You can remove inherited traits by adding a - in front of them as in -TraitName: to inherit everything, but this trait.
|
||||
/// </summary>
|
||||
public readonly string Name;
|
||||
readonly TypeDictionary traits = new();
|
||||
List<TraitInfo> constructOrderCache = null;
|
||||
readonly TypeDictionary traits = new TypeDictionary();
|
||||
List<ITraitInfo> constructOrderCache = null;
|
||||
|
||||
public ActorInfo(ObjectCreator creator, string name, MiniYaml node)
|
||||
{
|
||||
@@ -41,55 +38,41 @@ namespace OpenRA
|
||||
{
|
||||
Name = name;
|
||||
|
||||
var abstractActorType = name.StartsWith("^");
|
||||
foreach (var t in node.Nodes)
|
||||
{
|
||||
try
|
||||
{
|
||||
// HACK: The linter does not want to crash when a trait doesn't exist but only print an error instead
|
||||
// LoadTraitInfo will only return null to signal us to abort here if the linter is running
|
||||
var trait = LoadTraitInfo(creator, t.Key, t.Value);
|
||||
if (trait != null)
|
||||
traits.Add(trait);
|
||||
traits.Add(LoadTraitInfo(creator, t.Key.Split('@')[0], t.Value));
|
||||
}
|
||||
catch (FieldLoader.MissingFieldsException e)
|
||||
{
|
||||
throw new YamlException(e.Message);
|
||||
if (!abstractActorType)
|
||||
throw new YamlException(e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
traits.TrimExcess();
|
||||
}
|
||||
catch (YamlException e)
|
||||
{
|
||||
throw new YamlException($"Actor type {name}: {e.Message}");
|
||||
throw new YamlException("Actor type {0}: {1}".F(name, e.Message));
|
||||
}
|
||||
}
|
||||
|
||||
public ActorInfo(string name, params TraitInfo[] traitInfos)
|
||||
public ActorInfo(string name, params ITraitInfo[] traitInfos)
|
||||
{
|
||||
Name = name;
|
||||
foreach (var t in traitInfos)
|
||||
traits.Add(t);
|
||||
traits.TrimExcess();
|
||||
}
|
||||
|
||||
static TraitInfo LoadTraitInfo(ObjectCreator creator, string traitName, MiniYaml my)
|
||||
static ITraitInfo LoadTraitInfo(ObjectCreator creator, string traitName, MiniYaml my)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(my.Value))
|
||||
throw new YamlException($"Junk value `{my.Value}` on trait node {traitName}");
|
||||
|
||||
// HACK: The linter does not want to crash when a trait doesn't exist but only print an error instead
|
||||
// ObjectCreator will only return null to signal us to abort here if the linter is running
|
||||
var traitInstance = traitName.Split(TraitInstanceSeparator);
|
||||
var info = creator.CreateObject<TraitInfo>(traitInstance[0] + "Info");
|
||||
if (info == null)
|
||||
return null;
|
||||
|
||||
throw new YamlException("Junk value `{0}` on trait node {1}"
|
||||
.F(my.Value, traitName));
|
||||
var info = creator.CreateObject<ITraitInfo>(traitName + "Info");
|
||||
try
|
||||
{
|
||||
if (traitInstance.Length > 1)
|
||||
info.GetType().GetField(nameof(info.InstanceName)).SetValue(info, traitInstance[1]);
|
||||
|
||||
FieldLoader.Load(info, my);
|
||||
}
|
||||
catch (FieldLoader.MissingFieldsException e)
|
||||
@@ -101,48 +84,38 @@ namespace OpenRA
|
||||
return info;
|
||||
}
|
||||
|
||||
public IEnumerable<TraitInfo> TraitsInConstructOrder()
|
||||
public IEnumerable<ITraitInfo> TraitsInConstructOrder()
|
||||
{
|
||||
if (constructOrderCache != null)
|
||||
return constructOrderCache;
|
||||
|
||||
var source = traits.WithInterface<TraitInfo>().Select(i => new
|
||||
var source = traits.WithInterface<ITraitInfo>().Select(i => new
|
||||
{
|
||||
Trait = i,
|
||||
Type = i.GetType(),
|
||||
Dependencies = PrerequisitesOf(i).ToList(),
|
||||
OptionalDependencies = OptionalPrerequisitesOf(i).ToList()
|
||||
Dependencies = PrerequisitesOf(i).ToList()
|
||||
}).ToList();
|
||||
|
||||
var resolved = source.Where(s => s.Dependencies.Count == 0 && s.OptionalDependencies.Count == 0).ToList();
|
||||
var unresolved = source.ToHashSet();
|
||||
unresolved.ExceptWith(resolved);
|
||||
var resolved = source.Where(s => !s.Dependencies.Any()).ToList();
|
||||
var unresolved = source.Except(resolved);
|
||||
|
||||
static bool AreResolvable(Type a, Type b) => a.IsAssignableFrom(b);
|
||||
var testResolve = new Func<Type, Type, bool>((a, b) => a == b || a.IsAssignableFrom(b));
|
||||
|
||||
// This query detects which unresolved traits can be immediately resolved as all their direct dependencies are met.
|
||||
var more = unresolved.Where(u =>
|
||||
u.Dependencies.All(d => // To be resolvable, all dependencies must be satisfied according to the following conditions:
|
||||
resolved.Exists(r => AreResolvable(d, r.Type)) && // There must exist a resolved trait that meets the dependency.
|
||||
!unresolved.Any(u1 => AreResolvable(d, u1.Type))) && // All matching traits that meet this dependency must be resolved first.
|
||||
u.OptionalDependencies.All(d => // To be resolvable, all optional dependencies must be satisfied according to the following condition:
|
||||
!unresolved.Any(u1 => AreResolvable(d, u1.Type)))); // All matching traits that meet this optional dependencies must be resolved first.
|
||||
resolved.Exists(r => testResolve(d, r.Type)) && // There must exist a resolved trait that meets the dependency.
|
||||
!unresolved.Any(u1 => testResolve(d, u1.Type)))); // All matching traits that meet this dependency must be resolved first.
|
||||
|
||||
// Continue resolving traits as long as possible.
|
||||
// Each time we resolve some traits, this means dependencies for other traits may then be possible to satisfy in the next pass.
|
||||
var readyToResolve = more.ToList();
|
||||
while (readyToResolve.Count != 0)
|
||||
{
|
||||
resolved.AddRange(readyToResolve);
|
||||
unresolved.ExceptWith(readyToResolve);
|
||||
readyToResolve.Clear();
|
||||
readyToResolve.AddRange(more);
|
||||
}
|
||||
while (more.Any())
|
||||
resolved.AddRange(more);
|
||||
|
||||
if (unresolved.Count != 0)
|
||||
if (unresolved.Any())
|
||||
{
|
||||
var exceptionString = "ActorInfo(\"" + Name + "\") failed to initialize because of the following:\r\n";
|
||||
var missing = unresolved.SelectMany(u => u.Dependencies.Where(d => !source.Any(s => AreResolvable(d, s.Type)))).Distinct();
|
||||
var missing = unresolved.SelectMany(u => u.Dependencies.Where(d => !source.Any(s => testResolve(d, s.Type)))).Distinct();
|
||||
|
||||
exceptionString += "Missing:\r\n";
|
||||
foreach (var m in missing)
|
||||
@@ -152,9 +125,7 @@ namespace OpenRA
|
||||
foreach (var u in unresolved)
|
||||
{
|
||||
var deps = u.Dependencies.Where(d => !resolved.Exists(r => r.Type == d));
|
||||
var optDeps = u.OptionalDependencies.Where(d => !resolved.Exists(r => r.Type == d));
|
||||
var allDeps = string.Join(", ", deps.Select(o => o.ToString()).Concat(optDeps.Select(o => $"[{o}]")));
|
||||
exceptionString += $"{u.Type}: {{ {allDeps} }}\r\n";
|
||||
exceptionString += u.Type + ": { " + string.Join(", ", deps) + " }\r\n";
|
||||
}
|
||||
|
||||
throw new YamlException(exceptionString);
|
||||
@@ -164,7 +135,7 @@ namespace OpenRA
|
||||
return constructOrderCache;
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> PrerequisitesOf(TraitInfo info)
|
||||
public static IEnumerable<Type> PrerequisitesOf(ITraitInfo info)
|
||||
{
|
||||
return info
|
||||
.GetType()
|
||||
@@ -173,27 +144,23 @@ namespace OpenRA
|
||||
.Select(t => t.GetGenericArguments()[0]);
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> OptionalPrerequisitesOf(TraitInfo info)
|
||||
public IEnumerable<Pair<string, Type>> GetInitKeys()
|
||||
{
|
||||
return info
|
||||
.GetType()
|
||||
.GetInterfaces()
|
||||
.Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(NotBefore<>))
|
||||
.Select(t => t.GetGenericArguments()[0]);
|
||||
var inits = traits.WithInterface<ITraitInfo>().SelectMany(
|
||||
t => t.GetType().GetInterfaces()
|
||||
.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(UsesInit<>))
|
||||
.Select(i => i.GetGenericArguments()[0])).ToList();
|
||||
|
||||
inits.Add(typeof(OwnerInit)); /* not exposed by a trait; this is used by the Actor itself */
|
||||
|
||||
return inits.Select(
|
||||
i => Pair.New(
|
||||
i.Name.Replace("Init", ""), i));
|
||||
}
|
||||
|
||||
public bool HasTraitInfo<T>() where T : ITraitInfoInterface { return traits.Contains<T>(); }
|
||||
public T TraitInfo<T>() where T : ITraitInfoInterface { return traits.Get<T>(); }
|
||||
public T TraitInfoOrDefault<T>() where T : ITraitInfoInterface { return traits.GetOrDefault<T>(); }
|
||||
public IEnumerable<T> TraitInfos<T>() where T : ITraitInfoInterface { return traits.WithInterface<T>(); }
|
||||
|
||||
public BitSet<TargetableType> GetAllTargetTypes()
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
var targetTypes = default(BitSet<TargetableType>);
|
||||
foreach (var targetable in TraitInfos<ITargetableInfo>())
|
||||
targetTypes = targetTypes.Union(targetable.GetTargetTypes());
|
||||
return targetTypes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -9,6 +9,8 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.IO;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.FileSystem;
|
||||
|
||||
namespace OpenRA.GameRules
|
||||
@@ -18,7 +20,6 @@ namespace OpenRA.GameRules
|
||||
public readonly string Filename;
|
||||
public readonly string Title;
|
||||
public readonly bool Hidden;
|
||||
public readonly float VolumeModifier = 1f;
|
||||
|
||||
public int Length { get; private set; } // seconds
|
||||
public bool Exists { get; private set; }
|
||||
@@ -31,35 +32,28 @@ namespace OpenRA.GameRules
|
||||
if (nd.ContainsKey("Hidden"))
|
||||
bool.TryParse(nd["Hidden"].Value, out Hidden);
|
||||
|
||||
if (nd.ContainsKey("VolumeModifier"))
|
||||
VolumeModifier = FieldLoader.GetValue<float>("VolumeModifier", nd["VolumeModifier"].Value);
|
||||
|
||||
var ext = nd.ContainsKey("Extension") ? nd["Extension"].Value : "aud";
|
||||
Filename = (nd.ContainsKey("Filename") ? nd["Filename"].Value : key) + "." + ext;
|
||||
}
|
||||
|
||||
public void Load(IReadOnlyFileSystem fileSystem)
|
||||
{
|
||||
if (!fileSystem.TryOpen(Filename, out var stream))
|
||||
Stream stream;
|
||||
if (!fileSystem.TryOpen(Filename, out stream))
|
||||
return;
|
||||
|
||||
try
|
||||
Exists = true;
|
||||
ISoundFormat soundFormat;
|
||||
foreach (var loader in Game.ModData.SoundLoaders)
|
||||
{
|
||||
Exists = true;
|
||||
foreach (var loader in Game.ModData.SoundLoaders)
|
||||
if (loader.TryParseSound(stream, out soundFormat))
|
||||
{
|
||||
if (loader.TryParseSound(stream, out var soundFormat))
|
||||
{
|
||||
Length = (int)soundFormat.LengthInSeconds;
|
||||
soundFormat.Dispose();
|
||||
break;
|
||||
}
|
||||
Length = (int)soundFormat.LengthInSeconds;
|
||||
break;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
stream.Dispose();
|
||||
}
|
||||
|
||||
stream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -15,19 +15,20 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class Ruleset
|
||||
{
|
||||
public readonly ActorInfoDictionary Actors;
|
||||
public readonly IReadOnlyDictionary<string, ActorInfo> Actors;
|
||||
public readonly IReadOnlyDictionary<string, WeaponInfo> Weapons;
|
||||
public readonly IReadOnlyDictionary<string, SoundInfo> Voices;
|
||||
public readonly IReadOnlyDictionary<string, SoundInfo> Notifications;
|
||||
public readonly IReadOnlyDictionary<string, MusicInfo> Music;
|
||||
public readonly ITerrainInfo TerrainInfo;
|
||||
public readonly IReadOnlyDictionary<string, MiniYamlNode> ModelSequences;
|
||||
public readonly TileSet TileSet;
|
||||
public readonly SequenceProvider Sequences;
|
||||
|
||||
public Ruleset(
|
||||
IReadOnlyDictionary<string, ActorInfo> actors,
|
||||
@@ -35,16 +36,16 @@ namespace OpenRA
|
||||
IReadOnlyDictionary<string, SoundInfo> voices,
|
||||
IReadOnlyDictionary<string, SoundInfo> notifications,
|
||||
IReadOnlyDictionary<string, MusicInfo> music,
|
||||
ITerrainInfo terrainInfo,
|
||||
IReadOnlyDictionary<string, MiniYamlNode> modelSequences)
|
||||
TileSet tileSet,
|
||||
SequenceProvider sequences)
|
||||
{
|
||||
Actors = new ActorInfoDictionary(actors);
|
||||
Actors = actors;
|
||||
Weapons = weapons;
|
||||
Voices = voices;
|
||||
Notifications = notifications;
|
||||
Music = music;
|
||||
TerrainInfo = terrainInfo;
|
||||
ModelSequences = modelSequences;
|
||||
TileSet = tileSet;
|
||||
Sequences = sequences;
|
||||
|
||||
foreach (var a in Actors.Values)
|
||||
{
|
||||
@@ -56,28 +57,17 @@ namespace OpenRA
|
||||
}
|
||||
catch (YamlException e)
|
||||
{
|
||||
throw new YamlException($"Actor type {a.Name}: {e.Message}");
|
||||
throw new YamlException("Actor type {0}: {1}".F(a.Name, e.Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var weapon in Weapons)
|
||||
{
|
||||
if (weapon.Value.Projectile is IRulesetLoaded<WeaponInfo> projectileLoaded)
|
||||
{
|
||||
try
|
||||
{
|
||||
projectileLoaded.RulesetLoaded(this, weapon.Value);
|
||||
}
|
||||
catch (YamlException e)
|
||||
{
|
||||
throw new YamlException($"Projectile type {weapon.Key}: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var warhead in weapon.Value.Warheads)
|
||||
{
|
||||
if (warhead is IRulesetLoaded<WeaponInfo> cacher)
|
||||
var cacher = warhead as IRulesetLoaded<WeaponInfo>;
|
||||
if (cacher != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -85,7 +75,7 @@ namespace OpenRA
|
||||
}
|
||||
catch (YamlException e)
|
||||
{
|
||||
throw new YamlException($"Weapon type {weapon.Key}: {e.Message}");
|
||||
throw new YamlException("Weapon type {0}: {1}".F(weapon.Key, e.Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,24 +84,16 @@ namespace OpenRA
|
||||
|
||||
public IEnumerable<KeyValuePair<string, MusicInfo>> InstalledMusic { get { return Music.Where(m => m.Value.Exists); } }
|
||||
|
||||
static IReadOnlyDictionary<string, T> MergeOrDefault<T>(string name,
|
||||
IReadOnlyFileSystem fileSystem,
|
||||
IEnumerable<string> files,
|
||||
MiniYaml additional,
|
||||
IReadOnlyDictionary<string, T> defaults,
|
||||
Func<MiniYamlNode, T> makeObject,
|
||||
Func<MiniYamlNode, bool> filterNode = null)
|
||||
static IReadOnlyDictionary<string, T> MergeOrDefault<T>(string name, IReadOnlyFileSystem fileSystem, IEnumerable<string> files, MiniYaml additional,
|
||||
IReadOnlyDictionary<string, T> defaults, Func<MiniYamlNode, T> makeObject)
|
||||
{
|
||||
if (additional == null && defaults != null)
|
||||
return defaults;
|
||||
|
||||
IEnumerable<MiniYamlNode> yamlNodes = MiniYaml.Load(fileSystem, files, additional);
|
||||
var result = MiniYaml.Load(fileSystem, files, additional)
|
||||
.ToDictionaryWithConflictLog(k => k.Key.ToLowerInvariant(), makeObject, "LoadFromManifest<" + name + ">");
|
||||
|
||||
// Optionally, the caller can filter out elements from the loaded set of nodes. Default behavior is unfiltered.
|
||||
if (filterNode != null)
|
||||
yamlNodes = yamlNodes.Where(k => !filterNode(k));
|
||||
|
||||
return yamlNodes.ToDictionaryWithConflictLog(k => k.Key.ToLowerInvariant(), makeObject, "LoadFromManifest<" + name + ">");
|
||||
return new ReadOnlyDictionary<string, T>(result);
|
||||
}
|
||||
|
||||
public static Ruleset LoadDefaults(ModData modData)
|
||||
@@ -120,14 +102,13 @@ namespace OpenRA
|
||||
var fs = modData.DefaultFileSystem;
|
||||
|
||||
Ruleset ruleset = null;
|
||||
void LoadRuleset()
|
||||
Action f = () =>
|
||||
{
|
||||
var actors = MergeOrDefault("Manifest,Rules", fs, m.Rules, null, null,
|
||||
k => new ActorInfo(modData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value),
|
||||
filterNode: n => n.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal));
|
||||
k => new ActorInfo(modData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value));
|
||||
|
||||
var weapons = MergeOrDefault("Manifest,Weapons", fs, m.Weapons, null, null,
|
||||
k => new WeaponInfo(k.Value));
|
||||
k => new WeaponInfo(k.Key.ToLowerInvariant(), k.Value));
|
||||
|
||||
var voices = MergeOrDefault("Manifest,Voices", fs, m.Voices, null, null,
|
||||
k => new SoundInfo(k.Value));
|
||||
@@ -138,18 +119,15 @@ namespace OpenRA
|
||||
var music = MergeOrDefault("Manifest,Music", fs, m.Music, null, null,
|
||||
k => new MusicInfo(k.Key, k.Value));
|
||||
|
||||
var modelSequences = MergeOrDefault("Manifest,ModelSequences", fs, m.ModelSequences, null, null,
|
||||
k => k);
|
||||
|
||||
// The default ruleset does not include a preferred tileset
|
||||
ruleset = new Ruleset(actors, weapons, voices, notifications, music, null, modelSequences);
|
||||
}
|
||||
// The default ruleset does not include a preferred tileset or sequence set
|
||||
ruleset = new Ruleset(actors, weapons, voices, notifications, music, null, null);
|
||||
};
|
||||
|
||||
if (modData.IsOnMainThread)
|
||||
{
|
||||
modData.HandleLoadingProgress();
|
||||
|
||||
var loader = new Task(LoadRuleset);
|
||||
var loader = new Task(f);
|
||||
loader.Start();
|
||||
|
||||
// Animate the loadscreen while we wait
|
||||
@@ -157,7 +135,7 @@ namespace OpenRA
|
||||
modData.HandleLoadingProgress();
|
||||
}
|
||||
else
|
||||
LoadRuleset();
|
||||
f();
|
||||
|
||||
return ruleset;
|
||||
}
|
||||
@@ -165,27 +143,26 @@ namespace OpenRA
|
||||
public static Ruleset LoadDefaultsForTileSet(ModData modData, string tileSet)
|
||||
{
|
||||
var dr = modData.DefaultRules;
|
||||
var terrainInfo = modData.DefaultTerrainInfo[tileSet];
|
||||
|
||||
return new Ruleset(dr.Actors, dr.Weapons, dr.Voices, dr.Notifications, dr.Music, terrainInfo, dr.ModelSequences);
|
||||
var ts = modData.DefaultTileSets[tileSet];
|
||||
var sequences = modData.DefaultSequences[tileSet];
|
||||
return new Ruleset(dr.Actors, dr.Weapons, dr.Voices, dr.Notifications, dr.Music, ts, sequences);
|
||||
}
|
||||
|
||||
public static Ruleset Load(ModData modData, IReadOnlyFileSystem fileSystem, string tileSet,
|
||||
MiniYaml mapRules, MiniYaml mapWeapons, MiniYaml mapVoices, MiniYaml mapNotifications,
|
||||
MiniYaml mapMusic, MiniYaml mapModelSequences)
|
||||
MiniYaml mapMusic, MiniYaml mapSequences)
|
||||
{
|
||||
var m = modData.Manifest;
|
||||
var dr = modData.DefaultRules;
|
||||
|
||||
Ruleset ruleset = null;
|
||||
void LoadRuleset()
|
||||
Action f = () =>
|
||||
{
|
||||
var actors = MergeOrDefault("Rules", fileSystem, m.Rules, mapRules, dr.Actors,
|
||||
k => new ActorInfo(modData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value),
|
||||
filterNode: n => n.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal));
|
||||
k => new ActorInfo(modData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value));
|
||||
|
||||
var weapons = MergeOrDefault("Weapons", fileSystem, m.Weapons, mapWeapons, dr.Weapons,
|
||||
k => new WeaponInfo(k.Value));
|
||||
k => new WeaponInfo(k.Key.ToLowerInvariant(), k.Value));
|
||||
|
||||
var voices = MergeOrDefault("Voices", fileSystem, m.Voices, mapVoices, dr.Voices,
|
||||
k => new SoundInfo(k.Value));
|
||||
@@ -196,22 +173,22 @@ namespace OpenRA
|
||||
var music = MergeOrDefault("Music", fileSystem, m.Music, mapMusic, dr.Music,
|
||||
k => new MusicInfo(k.Key, k.Value));
|
||||
|
||||
// TODO: Add support for merging custom terrain modifications
|
||||
var terrainInfo = modData.DefaultTerrainInfo[tileSet];
|
||||
// TODO: Add support for merging custom tileset modifications
|
||||
var ts = modData.DefaultTileSets[tileSet];
|
||||
|
||||
var modelSequences = dr.ModelSequences;
|
||||
if (mapModelSequences != null)
|
||||
modelSequences = MergeOrDefault("ModelSequences", fileSystem, m.ModelSequences, mapModelSequences, dr.ModelSequences,
|
||||
k => k);
|
||||
// TODO: Top-level dictionary should be moved into the Ruleset instead of in its own object
|
||||
var sequences = mapSequences == null ? modData.DefaultSequences[tileSet] :
|
||||
new SequenceProvider(fileSystem, modData, ts, mapSequences);
|
||||
|
||||
ruleset = new Ruleset(actors, weapons, voices, notifications, music, terrainInfo, modelSequences);
|
||||
}
|
||||
// TODO: Add support for custom voxel sequences
|
||||
ruleset = new Ruleset(actors, weapons, voices, notifications, music, ts, sequences);
|
||||
};
|
||||
|
||||
if (modData.IsOnMainThread)
|
||||
{
|
||||
modData.HandleLoadingProgress();
|
||||
|
||||
var loader = new Task(LoadRuleset);
|
||||
var loader = new Task(f);
|
||||
loader.Start();
|
||||
|
||||
// Animate the loadscreen while we wait
|
||||
@@ -219,14 +196,14 @@ namespace OpenRA
|
||||
modData.HandleLoadingProgress();
|
||||
}
|
||||
else
|
||||
LoadRuleset();
|
||||
f();
|
||||
|
||||
return ruleset;
|
||||
}
|
||||
|
||||
static bool AnyCustomYaml(MiniYaml yaml)
|
||||
{
|
||||
return yaml != null && (yaml.Value != null || yaml.Nodes.Count > 0);
|
||||
return yaml != null && (yaml.Value != null || yaml.Nodes.Any());
|
||||
}
|
||||
|
||||
static bool AnyFlaggedTraits(ModData modData, List<MiniYamlNode> actors)
|
||||
@@ -239,13 +216,10 @@ namespace OpenRA
|
||||
{
|
||||
var traitName = traitNode.Key.Split('@')[0];
|
||||
var traitType = modData.ObjectCreator.FindType(traitName + "Info");
|
||||
if (traitType != null && traitType.GetInterface(nameof(ILobbyCustomRulesIgnore)) == null)
|
||||
if (traitType.GetInterface("ILobbyCustomRulesIgnore") == null)
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Write("debug", "Error in AnyFlaggedTraits\r\n" + ex.ToString());
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,7 +230,7 @@ namespace OpenRA
|
||||
MiniYaml mapRules, MiniYaml mapWeapons, MiniYaml mapVoices, MiniYaml mapNotifications, MiniYaml mapSequences)
|
||||
{
|
||||
// Maps that define any weapon, voice, notification, or sequence overrides are always flagged
|
||||
if (AnyCustomYaml(mapWeapons) || AnyCustomYaml(mapVoices) || AnyCustomYaml(mapNotifications) || AnyCustomYaml(mapSequences))
|
||||
if (AnyCustomYaml(mapWeapons) || AnyCustomYaml(mapVoices) || AnyCustomYaml(mapNotifications) || AnyCustomYaml(mapSequences))
|
||||
return true;
|
||||
|
||||
// Any trait overrides that aren't explicitly whitelisted are flagged
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -17,14 +17,14 @@ namespace OpenRA.GameRules
|
||||
{
|
||||
public class SoundInfo
|
||||
{
|
||||
public readonly Dictionary<string, string[]> Variants = new();
|
||||
public readonly Dictionary<string, string[]> Prefixes = new();
|
||||
public readonly Dictionary<string, string[]> Voices = new();
|
||||
public readonly Dictionary<string, string[]> Notifications = new();
|
||||
public readonly Dictionary<string, string[]> Variants = new Dictionary<string, string[]>();
|
||||
public readonly Dictionary<string, string[]> Prefixes = new Dictionary<string, string[]>();
|
||||
public readonly Dictionary<string, string[]> Voices = new Dictionary<string, string[]>();
|
||||
public readonly Dictionary<string, string[]> Notifications = new Dictionary<string, string[]>();
|
||||
public readonly string DefaultVariant = ".aud";
|
||||
public readonly string DefaultPrefix = "";
|
||||
public readonly HashSet<string> DisableVariants = new();
|
||||
public readonly HashSet<string> DisablePrefixes = new();
|
||||
public readonly HashSet<string> DisableVariants = new HashSet<string>();
|
||||
public readonly HashSet<string> DisablePrefixes = new HashSet<string>();
|
||||
|
||||
public readonly Lazy<Dictionary<string, SoundPool>> VoicePools;
|
||||
public readonly Lazy<Dictionary<string, SoundPool>> NotificationsPools;
|
||||
@@ -33,48 +33,18 @@ namespace OpenRA.GameRules
|
||||
{
|
||||
FieldLoader.Load(this, y);
|
||||
|
||||
VoicePools = Exts.Lazy(() => Voices.ToDictionary(a => a.Key, a => new SoundPool(1f, SoundPool.DefaultInterruptType, a.Value)));
|
||||
NotificationsPools = Exts.Lazy(() => ParseSoundPool(y, "Notifications"));
|
||||
}
|
||||
|
||||
static Dictionary<string, SoundPool> ParseSoundPool(MiniYaml y, string key)
|
||||
{
|
||||
var ret = new Dictionary<string, SoundPool>();
|
||||
var classifiction = y.Nodes.First(x => x.Key == key);
|
||||
foreach (var t in classifiction.Value.Nodes)
|
||||
{
|
||||
var volumeModifier = 1f;
|
||||
var volumeModifierNode = t.Value.Nodes.FirstOrDefault(x => x.Key == nameof(SoundPool.VolumeModifier));
|
||||
if (volumeModifierNode != null)
|
||||
volumeModifier = FieldLoader.GetValue<float>(volumeModifierNode.Key, volumeModifierNode.Value.Value);
|
||||
|
||||
var interruptType = SoundPool.DefaultInterruptType;
|
||||
var interruptTypeNode = t.Value.Nodes.FirstOrDefault(x => x.Key == nameof(SoundPool.InterruptType));
|
||||
if (interruptTypeNode != null)
|
||||
interruptType = FieldLoader.GetValue<SoundPool.InterruptType>(interruptTypeNode.Key, interruptTypeNode.Value.Value);
|
||||
|
||||
var names = FieldLoader.GetValue<string[]>(t.Key, t.Value.Value);
|
||||
var sp = new SoundPool(volumeModifier, interruptType, names);
|
||||
ret.Add(t.Key, sp);
|
||||
}
|
||||
|
||||
return ret;
|
||||
VoicePools = Exts.Lazy(() => Voices.ToDictionary(a => a.Key, a => new SoundPool(a.Value)));
|
||||
NotificationsPools = Exts.Lazy(() => Notifications.ToDictionary(a => a.Key, a => new SoundPool(a.Value)));
|
||||
}
|
||||
}
|
||||
|
||||
public class SoundPool
|
||||
{
|
||||
public enum InterruptType { DoNotPlay, Interrupt, Overlap }
|
||||
public const InterruptType DefaultInterruptType = InterruptType.DoNotPlay;
|
||||
public readonly float VolumeModifier;
|
||||
public readonly InterruptType Type;
|
||||
readonly string[] clips;
|
||||
readonly List<string> liveclips = new();
|
||||
readonly List<string> liveclips = new List<string>();
|
||||
|
||||
public SoundPool(float volumeModifier, InterruptType interruptType, params string[] clips)
|
||||
public SoundPool(params string[] clips)
|
||||
{
|
||||
VolumeModifier = volumeModifier;
|
||||
Type = interruptType;
|
||||
this.clips = clips;
|
||||
}
|
||||
|
||||
@@ -83,9 +53,8 @@ namespace OpenRA.GameRules
|
||||
if (liveclips.Count == 0)
|
||||
liveclips.AddRange(clips);
|
||||
|
||||
// Avoid crashing if there's no clips at all
|
||||
if (liveclips.Count == 0)
|
||||
return null;
|
||||
return null; /* avoid crashing if there's no clips at all */
|
||||
|
||||
var i = Game.CosmeticRandom.Next(liveclips.Count);
|
||||
var s = liveclips[i];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -13,7 +13,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Effects;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.GameRules
|
||||
@@ -24,8 +23,7 @@ namespace OpenRA.GameRules
|
||||
public int[] DamageModifiers;
|
||||
public int[] InaccuracyModifiers;
|
||||
public int[] RangeModifiers;
|
||||
public WAngle Facing;
|
||||
public Func<WAngle> CurrentMuzzleFacing;
|
||||
public int Facing;
|
||||
public WPos Source;
|
||||
public Func<WPos> CurrentSource;
|
||||
public Actor SourceActor;
|
||||
@@ -33,40 +31,6 @@ namespace OpenRA.GameRules
|
||||
public Target GuidedTarget;
|
||||
}
|
||||
|
||||
public class WarheadArgs
|
||||
{
|
||||
public WeaponInfo Weapon;
|
||||
public int[] DamageModifiers = Array.Empty<int>();
|
||||
public WPos? Source;
|
||||
public WRot ImpactOrientation;
|
||||
public WPos ImpactPosition;
|
||||
public Actor SourceActor;
|
||||
public Target WeaponTarget;
|
||||
|
||||
public WarheadArgs(ProjectileArgs args)
|
||||
{
|
||||
Weapon = args.Weapon;
|
||||
DamageModifiers = args.DamageModifiers;
|
||||
ImpactPosition = args.PassiveTarget;
|
||||
Source = args.Source;
|
||||
SourceActor = args.SourceActor;
|
||||
WeaponTarget = args.GuidedTarget;
|
||||
}
|
||||
|
||||
// For places that only want to update some of the fields (usually DamageModifiers)
|
||||
public WarheadArgs(WarheadArgs args)
|
||||
{
|
||||
Weapon = args.Weapon;
|
||||
DamageModifiers = args.DamageModifiers;
|
||||
Source = args.Source;
|
||||
SourceActor = args.SourceActor;
|
||||
WeaponTarget = args.WeaponTarget;
|
||||
}
|
||||
|
||||
// Default empty constructor for callers that want to initialize fields themselves
|
||||
public WarheadArgs() { }
|
||||
}
|
||||
|
||||
public interface IProjectile : IEffect { }
|
||||
public interface IProjectileInfo { IProjectile Create(ProjectileArgs args); }
|
||||
|
||||
@@ -75,83 +39,44 @@ namespace OpenRA.GameRules
|
||||
[Desc("The maximum range the weapon can fire.")]
|
||||
public readonly WDist Range = WDist.Zero;
|
||||
|
||||
[Desc("First burst is aimed at this offset relative to target position.")]
|
||||
public readonly WVec FirstBurstTargetOffset = WVec.Zero;
|
||||
|
||||
[Desc("Each burst after the first lands by this offset away from the previous burst.")]
|
||||
public readonly WVec FollowingBurstTargetOffset = WVec.Zero;
|
||||
|
||||
[Desc("The sound played each time the weapon is fired.")]
|
||||
[Desc("The sound played when the weapon is fired.")]
|
||||
public readonly string[] Report = null;
|
||||
|
||||
[Desc("Sound played only on first burst in a salvo.")]
|
||||
public readonly string[] StartBurstReport = null;
|
||||
|
||||
[Desc("The sound played when the weapon is reloaded.")]
|
||||
public readonly string[] AfterFireSound = null;
|
||||
|
||||
[Desc("Delay in ticks to play reloading sound.")]
|
||||
public readonly int AfterFireSoundDelay = 0;
|
||||
|
||||
[Desc("Delay in ticks between reloading ammo magazines.")]
|
||||
public readonly int ReloadDelay = 1;
|
||||
|
||||
[Desc("Number of shots in a single ammo magazine.")]
|
||||
public readonly int Burst = 1;
|
||||
|
||||
[Desc("Can this weapon target the attacker itself?")]
|
||||
public readonly bool CanTargetSelf = false;
|
||||
|
||||
[Desc("What types of targets are affected.")]
|
||||
public readonly BitSet<TargetableType> ValidTargets = new("Ground", "Water");
|
||||
public readonly HashSet<string> ValidTargets = new HashSet<string> { "Ground", "Water" };
|
||||
|
||||
[Desc("What types of targets are unaffected.", "Overrules ValidTargets.")]
|
||||
public readonly BitSet<TargetableType> InvalidTargets;
|
||||
public readonly HashSet<string> InvalidTargets = new HashSet<string>();
|
||||
|
||||
static readonly BitSet<TargetableType> TargetTypeAir = new("Air");
|
||||
|
||||
[Desc("If weapon is not directly targeting an actor and targeted position is above this altitude,",
|
||||
"the weapon will ignore terrain target types and only check TargetTypeAir for validity.")]
|
||||
public readonly WDist AirThreshold = new(128);
|
||||
|
||||
[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.")]
|
||||
public readonly int[] BurstDelays = { 5 };
|
||||
[Desc("Delay in ticks between firing shots from the same ammo magazine.")]
|
||||
public readonly int BurstDelay = 5;
|
||||
|
||||
[Desc("The minimum range the weapon can fire.")]
|
||||
public readonly WDist MinRange = WDist.Zero;
|
||||
|
||||
[Desc("Does this weapon aim at the target's center regardless of other targetable offsets?")]
|
||||
public readonly bool TargetActorCenter = false;
|
||||
|
||||
[FieldLoader.LoadUsing(nameof(LoadProjectile))]
|
||||
[FieldLoader.LoadUsing("LoadProjectile")]
|
||||
public readonly IProjectileInfo Projectile;
|
||||
|
||||
[FieldLoader.LoadUsing(nameof(LoadWarheads))]
|
||||
public readonly List<IWarhead> Warheads = new();
|
||||
[FieldLoader.LoadUsing("LoadWarheads")]
|
||||
public readonly List<IWarhead> Warheads = new List<IWarhead>();
|
||||
|
||||
/// <summary>
|
||||
/// This constructor is used solely for documentation generation.
|
||||
/// </summary>
|
||||
public WeaponInfo() { }
|
||||
|
||||
public WeaponInfo(MiniYaml content)
|
||||
public WeaponInfo(string name, MiniYaml content)
|
||||
{
|
||||
// Resolve any weapon-level yaml inheritance or removals
|
||||
// HACK: The "Defaults" sequence syntax prevents us from doing this generally during yaml parsing
|
||||
content.Nodes = MiniYaml.Merge(new[] { content.Nodes });
|
||||
FieldLoader.Load(this, content);
|
||||
}
|
||||
|
||||
static object LoadProjectile(MiniYaml yaml)
|
||||
{
|
||||
if (!yaml.ToDictionary().TryGetValue("Projectile", out var proj))
|
||||
MiniYaml proj;
|
||||
if (!yaml.ToDictionary().TryGetValue("Projectile", out proj))
|
||||
return null;
|
||||
|
||||
var ret = Game.CreateObject<IProjectileInfo>(proj.Value + "Info");
|
||||
if (ret == null)
|
||||
return null;
|
||||
|
||||
FieldLoader.Load(ret, proj);
|
||||
return ret;
|
||||
}
|
||||
@@ -162,9 +87,6 @@ namespace OpenRA.GameRules
|
||||
foreach (var node in yaml.Nodes.Where(n => n.Key.StartsWith("Warhead")))
|
||||
{
|
||||
var ret = Game.CreateObject<IWarhead>(node.Value.Value + "Warhead");
|
||||
if (ret == null)
|
||||
continue;
|
||||
|
||||
FieldLoader.Load(ret, node.Value);
|
||||
retList.Add(ret);
|
||||
}
|
||||
@@ -172,13 +94,13 @@ namespace OpenRA.GameRules
|
||||
return retList;
|
||||
}
|
||||
|
||||
public bool IsValidTarget(BitSet<TargetableType> targetTypes)
|
||||
public bool IsValidTarget(IEnumerable<string> targetTypes)
|
||||
{
|
||||
return ValidTargets.Overlaps(targetTypes) && !InvalidTargets.Overlaps(targetTypes);
|
||||
}
|
||||
|
||||
/// <summary>Checks if the weapon is valid against (can target) the target.</summary>
|
||||
public bool IsValidAgainst(in Target target, World world, Actor firedBy)
|
||||
public bool IsValidAgainst(Target target, World world, Actor firedBy)
|
||||
{
|
||||
if (target.Type == TargetType.Actor)
|
||||
return IsValidAgainst(target.Actor, firedBy);
|
||||
@@ -188,10 +110,6 @@ namespace OpenRA.GameRules
|
||||
|
||||
if (target.Type == TargetType.Terrain)
|
||||
{
|
||||
var dat = world.Map.DistanceAboveTerrain(target.CenterPosition);
|
||||
if (dat > AirThreshold)
|
||||
return IsValidTarget(TargetTypeAir);
|
||||
|
||||
var cell = world.Map.CellContaining(target.CenterPosition);
|
||||
if (!world.Map.Contains(cell))
|
||||
return false;
|
||||
@@ -209,58 +127,44 @@ namespace OpenRA.GameRules
|
||||
/// <summary>Checks if the weapon is valid against (can target) the actor.</summary>
|
||||
public bool IsValidAgainst(Actor victim, Actor firedBy)
|
||||
{
|
||||
if (!CanTargetSelf && victim == firedBy)
|
||||
return false;
|
||||
|
||||
var targetTypes = victim.GetEnabledTargetTypes();
|
||||
|
||||
return IsValidTarget(targetTypes);
|
||||
if (!IsValidTarget(targetTypes))
|
||||
return false;
|
||||
|
||||
// PERF: Avoid LINQ.
|
||||
foreach (var warhead in Warheads)
|
||||
if (warhead.IsValidAgainst(victim, firedBy))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Checks if the weapon is valid against (can target) the frozen actor.</summary>
|
||||
public bool IsValidAgainst(FrozenActor victim, Actor firedBy)
|
||||
{
|
||||
if (!victim.IsValid)
|
||||
if (!IsValidTarget(victim.TargetTypes))
|
||||
return false;
|
||||
|
||||
if (!CanTargetSelf && victim.Actor == firedBy)
|
||||
if (!Warheads.Any(w => w.IsValidAgainst(victim, firedBy)))
|
||||
return false;
|
||||
|
||||
return IsValidTarget(victim.TargetTypes);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Applies all the weapon's warheads to the target.</summary>
|
||||
public void Impact(in Target target, WarheadArgs args)
|
||||
public void Impact(Target target, Actor firedBy, IEnumerable<int> damageModifiers)
|
||||
{
|
||||
var world = args.SourceActor.World;
|
||||
foreach (var warhead in Warheads)
|
||||
{
|
||||
if (warhead.Delay > 0)
|
||||
{
|
||||
// Lambdas can't use 'in' variables, so capture a copy for later
|
||||
var delayedTarget = target;
|
||||
world.AddFrameEndTask(w => w.Add(new DelayedImpact(warhead.Delay, warhead, delayedTarget, args)));
|
||||
}
|
||||
var wh = warhead; // force the closure to bind to the current warhead
|
||||
|
||||
Action a = () => wh.DoImpact(target, firedBy, damageModifiers);
|
||||
if (wh.Delay > 0)
|
||||
firedBy.World.AddFrameEndTask(w => w.Add(new DelayedAction(wh.Delay, a)));
|
||||
else
|
||||
warhead.DoImpact(target, args);
|
||||
a();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Applies all the weapon's warheads to the target. Only use for projectile-less, special-case impacts.</summary>
|
||||
public void Impact(in Target target, Actor firedBy)
|
||||
{
|
||||
// The impact will happen immediately at target.CenterPosition.
|
||||
var args = new WarheadArgs
|
||||
{
|
||||
Weapon = this,
|
||||
SourceActor = firedBy,
|
||||
WeaponTarget = target
|
||||
};
|
||||
|
||||
if (firedBy.OccupiesSpace != null)
|
||||
args.Source = firedBy.CenterPosition;
|
||||
|
||||
Impact(target, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -10,50 +10,27 @@
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class GameSpeed
|
||||
{
|
||||
[TranslationReference]
|
||||
[FieldLoader.Require]
|
||||
public readonly string Name;
|
||||
|
||||
[FieldLoader.Require]
|
||||
public readonly int Timestep;
|
||||
|
||||
[FieldLoader.Require]
|
||||
public readonly int OrderLatency;
|
||||
[Translate]
|
||||
public readonly string Name = "Default";
|
||||
public readonly int Timestep = 40;
|
||||
public readonly int OrderLatency = 3;
|
||||
}
|
||||
|
||||
public class GameSpeeds : IGlobalModData
|
||||
{
|
||||
[FieldLoader.Require]
|
||||
public readonly string DefaultSpeed;
|
||||
|
||||
[FieldLoader.LoadUsing(nameof(LoadSpeeds))]
|
||||
[FieldLoader.LoadUsing("LoadSpeeds")]
|
||||
public readonly Dictionary<string, GameSpeed> Speeds;
|
||||
|
||||
static object LoadSpeeds(MiniYaml y)
|
||||
{
|
||||
var ret = new Dictionary<string, GameSpeed>();
|
||||
var speedsNode = y.Nodes.FirstOrDefault(n => n.Key == "Speeds");
|
||||
if (speedsNode == null)
|
||||
throw new YamlException("Error parsing GameSpeeds: Missing Speeds node!");
|
||||
|
||||
foreach (var node in speedsNode.Value.Nodes)
|
||||
{
|
||||
try
|
||||
{
|
||||
ret.Add(node.Key, FieldLoader.Load<GameSpeed>(node.Value));
|
||||
}
|
||||
catch (FieldLoader.MissingFieldsException e)
|
||||
{
|
||||
var label = e.Missing.Length > 1 ? "Required properties missing" : "Required property missing";
|
||||
throw new YamlException($"Error parsing GameSpeed {node.Key}: {label}: {e.Missing.JoinWith(", ")}");
|
||||
}
|
||||
}
|
||||
foreach (var node in y.Nodes)
|
||||
ret.Add(node.Key, FieldLoader.Load<GameSpeed>(node.Value));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
391
OpenRA.Game/GlobalChat.cs
Normal file
391
OpenRA.Game/GlobalChat.cs
Normal file
@@ -0,0 +1,391 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Meebey.SmartIrc4net;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Chat
|
||||
{
|
||||
public enum ChatConnectionStatus { Disconnected, Connecting, Connected, Disconnecting, Joined, Error }
|
||||
public enum ChatMessageType { Message, Notification }
|
||||
|
||||
public sealed class ChatUser
|
||||
{
|
||||
public readonly string Name;
|
||||
public bool IsOp;
|
||||
public bool IsVoiced;
|
||||
|
||||
public ChatUser(string name, bool isOp, bool isVoice)
|
||||
{
|
||||
Name = name;
|
||||
IsOp = isOp;
|
||||
IsVoiced = isVoice;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ChatMessage
|
||||
{
|
||||
static long nextUID;
|
||||
|
||||
public readonly DateTime Time;
|
||||
public readonly ChatMessageType Type;
|
||||
public readonly string Nick;
|
||||
public readonly string Message;
|
||||
public readonly string UID;
|
||||
|
||||
public ChatMessage(DateTime time, ChatMessageType type, string nick, string message)
|
||||
{
|
||||
Time = time;
|
||||
Type = type;
|
||||
Nick = nick;
|
||||
Message = message;
|
||||
|
||||
UID = Interlocked.Increment(ref nextUID).ToString();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var time = Time.ToString(Game.Settings.Chat.TimestampFormat);
|
||||
if (Type == ChatMessageType.Notification)
|
||||
return "{0} {1}".F(time, Message);
|
||||
|
||||
return "{0} {1}: {2}".F(time, Nick, Message);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GlobalChat : IDisposable
|
||||
{
|
||||
readonly IrcClient client = new IrcClient();
|
||||
volatile Channel channel;
|
||||
|
||||
public readonly ObservableSortedDictionary<string, ChatUser> Users = new ObservableSortedDictionary<string, ChatUser>(StringComparer.InvariantCultureIgnoreCase);
|
||||
public readonly ObservableList<ChatMessage> History = new ObservableList<ChatMessage>();
|
||||
|
||||
volatile string topic;
|
||||
public string Topic { get { return topic; } }
|
||||
|
||||
volatile ChatConnectionStatus connectionStatus = ChatConnectionStatus.Disconnected;
|
||||
public ChatConnectionStatus ConnectionStatus { get { return connectionStatus; } }
|
||||
|
||||
public GlobalChat()
|
||||
{
|
||||
client.Encoding = System.Text.Encoding.UTF8;
|
||||
client.SendDelay = 100;
|
||||
client.ActiveChannelSyncing = true;
|
||||
|
||||
client.OnConnecting += OnConnecting;
|
||||
client.OnConnected += OnConnected;
|
||||
client.OnDisconnecting += OnDisconnecting;
|
||||
client.OnDisconnected += OnDisconnected;
|
||||
client.OnError += OnError;
|
||||
client.OnKick += OnKick;
|
||||
|
||||
client.OnRawMessage += (_, e) => Game.RunAfterTick(() => Log.Write("irc", e.Data.RawMessage));
|
||||
client.OnJoin += OnJoin;
|
||||
client.OnChannelActiveSynced += OnChannelActiveSynced;
|
||||
client.OnTopic += (_, e) => topic = e.Topic;
|
||||
client.OnTopicChange += (_, e) => topic = e.NewTopic;
|
||||
client.OnNickChange += OnNickChange;
|
||||
|
||||
client.OnChannelMessage += (_, e) => AddMessage(e.Data.Nick, e.Data.Message);
|
||||
client.OnOp += (_, e) => SetUserOp(e.Whom, true);
|
||||
client.OnDeop += (_, e) => SetUserOp(e.Whom, false);
|
||||
client.OnVoice += (_, e) => SetUserVoiced(e.Whom, true);
|
||||
client.OnDevoice += (_, e) => SetUserVoiced(e.Whom, false);
|
||||
client.OnPart += OnPart;
|
||||
client.OnQuit += OnQuit;
|
||||
|
||||
TrySetNickname(Game.Settings.Player.Name);
|
||||
}
|
||||
|
||||
void SetUserOp(string whom, bool isOp)
|
||||
{
|
||||
Game.RunAfterTick(() =>
|
||||
{
|
||||
ChatUser user;
|
||||
if (Users.TryGetValue(whom, out user))
|
||||
user.IsOp = isOp;
|
||||
});
|
||||
}
|
||||
|
||||
void SetUserVoiced(string whom, bool isVoiced)
|
||||
{
|
||||
Game.RunAfterTick(() =>
|
||||
{
|
||||
ChatUser user;
|
||||
if (Users.TryGetValue(whom, out user))
|
||||
user.IsVoiced = isVoiced;
|
||||
});
|
||||
}
|
||||
|
||||
public void Connect()
|
||||
{
|
||||
if (client.IsConnected)
|
||||
return;
|
||||
|
||||
new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
client.Connect(Game.Settings.Chat.Hostname, Game.Settings.Chat.Port);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
connectionStatus = ChatConnectionStatus.Error;
|
||||
AddNotification(e.Message);
|
||||
Game.RunAfterTick(() => Log.Write("irc", e.ToString()));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
client.Listen();
|
||||
}) { Name = "IrcListenThread", IsBackground = true }.Start();
|
||||
}
|
||||
|
||||
void AddNotification(string text)
|
||||
{
|
||||
var message = new ChatMessage(DateTime.Now, ChatMessageType.Notification, null, text);
|
||||
Game.RunAfterTick(() =>
|
||||
{
|
||||
History.Add(message);
|
||||
Log.Write("irc", text);
|
||||
});
|
||||
}
|
||||
|
||||
void AddMessage(string nick, string text)
|
||||
{
|
||||
var message = new ChatMessage(DateTime.Now, ChatMessageType.Message, nick, text);
|
||||
Game.RunAfterTick(() =>
|
||||
{
|
||||
History.Add(message);
|
||||
Log.Write("irc", text);
|
||||
});
|
||||
}
|
||||
|
||||
void OnConnecting(object sender, EventArgs e)
|
||||
{
|
||||
AddNotification("Connecting to {0}:{1}...".F(Game.Settings.Chat.Hostname, Game.Settings.Chat.Port));
|
||||
connectionStatus = ChatConnectionStatus.Connecting;
|
||||
}
|
||||
|
||||
void OnConnected(object sender, EventArgs e)
|
||||
{
|
||||
AddNotification("Connected.");
|
||||
connectionStatus = ChatConnectionStatus.Connected;
|
||||
|
||||
// Guard against settings.yaml modification
|
||||
var nick = SanitizedName(Game.Settings.Chat.Nickname);
|
||||
if (nick != Game.Settings.Chat.Nickname)
|
||||
Game.RunAfterTick(() => Game.Settings.Chat.Nickname = nick);
|
||||
|
||||
client.Login(nick, "in-game IRC client", 0, "OpenRA");
|
||||
client.RfcJoin("#" + Game.Settings.Chat.Channel);
|
||||
}
|
||||
|
||||
void OnDisconnecting(object sender, EventArgs e)
|
||||
{
|
||||
if (connectionStatus != ChatConnectionStatus.Error)
|
||||
connectionStatus = ChatConnectionStatus.Disconnecting;
|
||||
}
|
||||
|
||||
void OnDisconnected(object sender, EventArgs e)
|
||||
{
|
||||
Game.RunAfterTick(Users.Clear);
|
||||
|
||||
// Keep the chat window open if there is an error
|
||||
// It will be cleared by the Disconnect button
|
||||
if (connectionStatus != ChatConnectionStatus.Error)
|
||||
{
|
||||
Game.RunAfterTick(History.Clear);
|
||||
topic = null;
|
||||
connectionStatus = ChatConnectionStatus.Disconnected;
|
||||
}
|
||||
}
|
||||
|
||||
void OnError(object sender, ErrorEventArgs e)
|
||||
{
|
||||
// Ignore any errors that happen during disconnect
|
||||
if (connectionStatus != ChatConnectionStatus.Disconnecting)
|
||||
{
|
||||
connectionStatus = ChatConnectionStatus.Error;
|
||||
AddNotification("Error: " + e.ErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
void OnKick(object sender, KickEventArgs e)
|
||||
{
|
||||
if (e.Whom == client.Nickname)
|
||||
{
|
||||
Disconnect();
|
||||
connectionStatus = ChatConnectionStatus.Error;
|
||||
AddNotification("You were kicked from the chat by {0}. ({1})".F(e.Who, e.KickReason));
|
||||
}
|
||||
else
|
||||
{
|
||||
Users.Remove(e.Whom);
|
||||
AddNotification("{0} was kicked from the chat by {1}. ({2})".F(e.Whom, e.Who, e.KickReason));
|
||||
}
|
||||
}
|
||||
|
||||
void OnJoin(object sender, JoinEventArgs e)
|
||||
{
|
||||
if (e.Who == client.Nickname || channel == null || e.Channel != channel.Name)
|
||||
return;
|
||||
|
||||
AddNotification("{0} joined the chat.".F(e.Who));
|
||||
Game.RunAfterTick(() => Users.Add(e.Who, new ChatUser(e.Who, false, false)));
|
||||
}
|
||||
|
||||
void OnChannelActiveSynced(object sender, IrcEventArgs e)
|
||||
{
|
||||
channel = client.GetChannel(e.Data.Channel);
|
||||
AddNotification("{0} users online".F(channel.Users.Count));
|
||||
connectionStatus = ChatConnectionStatus.Joined;
|
||||
|
||||
foreach (DictionaryEntry user in channel.Users)
|
||||
{
|
||||
var u = (ChannelUser)user.Value;
|
||||
Game.RunAfterTick(() => Users.Add(u.Nick, new ChatUser(u.Nick, u.IsOp, u.IsVoice)));
|
||||
}
|
||||
}
|
||||
|
||||
void OnNickChange(object sender, NickChangeEventArgs e)
|
||||
{
|
||||
AddNotification("{0} is now known as {1}.".F(e.OldNickname, e.NewNickname));
|
||||
|
||||
Game.RunAfterTick(() =>
|
||||
{
|
||||
ChatUser user;
|
||||
if (!Users.TryGetValue(e.OldNickname, out user))
|
||||
return;
|
||||
|
||||
Users.Remove(e.OldNickname);
|
||||
Users.Add(e.NewNickname, new ChatUser(e.NewNickname, user.IsOp, user.IsVoiced));
|
||||
});
|
||||
}
|
||||
|
||||
void OnQuit(object sender, QuitEventArgs e)
|
||||
{
|
||||
AddNotification("{0} left the chat.".F(e.Who));
|
||||
Game.RunAfterTick(() => Users.Remove(e.Who));
|
||||
}
|
||||
|
||||
void OnPart(object sender, PartEventArgs e)
|
||||
{
|
||||
if (channel == null || e.Data.Channel != channel.Name)
|
||||
return;
|
||||
|
||||
AddNotification("{0} left the chat.".F(e.Who));
|
||||
Game.RunAfterTick(() => Users.Remove(e.Who));
|
||||
}
|
||||
|
||||
public string SanitizedName(string dirty)
|
||||
{
|
||||
if (string.IsNullOrEmpty(dirty))
|
||||
return null;
|
||||
|
||||
// There is no need to mangle the nick if it is already valid
|
||||
if (Rfc2812.IsValidNickname(dirty))
|
||||
return dirty;
|
||||
|
||||
// TODO: some special chars are allowed as well, but not at every position
|
||||
var clean = new string(dirty.Where(c => char.IsLetterOrDigit(c)).ToArray());
|
||||
|
||||
if (string.IsNullOrEmpty(clean))
|
||||
return null;
|
||||
|
||||
if (char.IsDigit(clean[0]))
|
||||
return SanitizedName(clean.Substring(1));
|
||||
|
||||
// Source: https://tools.ietf.org/html/rfc2812#section-1.2.1
|
||||
if (clean.Length > 9)
|
||||
clean = clean.Substring(0, 9);
|
||||
|
||||
return clean;
|
||||
}
|
||||
|
||||
public bool IsValidNickname(string name)
|
||||
{
|
||||
return Rfc2812.IsValidNickname(name);
|
||||
}
|
||||
|
||||
public void SendMessage(string text)
|
||||
{
|
||||
if (connectionStatus != ChatConnectionStatus.Joined)
|
||||
return;
|
||||
|
||||
// Guard against a last-moment disconnection
|
||||
try
|
||||
{
|
||||
client.SendMessage(SendType.Message, channel.Name, text);
|
||||
AddMessage(client.Nickname, text);
|
||||
}
|
||||
catch (NotConnectedException) { }
|
||||
}
|
||||
|
||||
public bool TrySetNickname(string nick)
|
||||
{
|
||||
// TODO: This is inconsistent with the other check
|
||||
if (Rfc2812.IsValidNickname(nick))
|
||||
{
|
||||
client.RfcNick(nick);
|
||||
Game.Settings.Chat.Nickname = nick;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Disconnect()
|
||||
{
|
||||
// Error is an alias for disconnect, but keeps the panel open
|
||||
// so that clients can see the error
|
||||
if (connectionStatus == ChatConnectionStatus.Error)
|
||||
{
|
||||
Game.RunAfterTick(History.Clear);
|
||||
topic = null;
|
||||
connectionStatus = ChatConnectionStatus.Disconnected;
|
||||
}
|
||||
else
|
||||
connectionStatus = ChatConnectionStatus.Disconnecting;
|
||||
|
||||
if (!client.IsConnected)
|
||||
return;
|
||||
|
||||
client.RfcQuit(Game.Settings.Chat.QuitMessage);
|
||||
|
||||
AddNotification("Disconnecting from {0}...".F(client.Address));
|
||||
|
||||
Game.RunAfterTick(() => Game.Settings.Chat.ConnectAutomatically = false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// HACK: The IRC library we are using has terrible thread-handling code that relies on Thread.Abort.
|
||||
// There is a thread reading from the network socket which is aborted, however on Windows this is inside
|
||||
// native code so this abort call hangs until the network socket reads something and returns to managed
|
||||
// code where it can then be aborted.
|
||||
//
|
||||
// This means we may hang for several seconds during shutdown (until we receive something over IRC!) before
|
||||
// closing.
|
||||
//
|
||||
// Since our IRC client currently lives forever, the only time we call this Dispose method is during the
|
||||
// shutdown of our process. Therefore, we can work around the problem by just not bothering to disconnect
|
||||
// properly. Since our process is about to die anyway, it's not like anyone will care.
|
||||
////if (client.IsConnected)
|
||||
//// client.Disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -10,8 +10,8 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Support;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
@@ -22,8 +22,8 @@ namespace OpenRA.Graphics
|
||||
public string Name { get; private set; }
|
||||
public bool IsDecoration { get; set; }
|
||||
|
||||
readonly SequenceSet sequences;
|
||||
readonly Func<WAngle> facingFunc;
|
||||
readonly SequenceProvider sequenceProvider;
|
||||
readonly Func<int> facingFunc;
|
||||
readonly Func<bool> paused;
|
||||
|
||||
int frame;
|
||||
@@ -33,79 +33,42 @@ namespace OpenRA.Graphics
|
||||
Action tickFunc = () => { };
|
||||
|
||||
public Animation(World world, string name)
|
||||
: this(world, name, () => WAngle.Zero) { }
|
||||
: this(world, name, () => 0) { }
|
||||
|
||||
public Animation(World world, string name, Func<WAngle> facingFunc)
|
||||
public Animation(World world, string name, Func<int> facingFunc)
|
||||
: this(world, name, facingFunc, null) { }
|
||||
|
||||
public Animation(World world, string name, Func<bool> paused)
|
||||
: this(world, name, () => WAngle.Zero, paused) { }
|
||||
: this(world, name, () => 0, paused) { }
|
||||
|
||||
public Animation(World world, string name, Func<WAngle> facingFunc, Func<bool> paused)
|
||||
public Animation(World world, string name, Func<int> facingFunc, Func<bool> paused)
|
||||
{
|
||||
sequences = world.Map.Sequences;
|
||||
sequenceProvider = world.Map.Rules.Sequences;
|
||||
Name = name.ToLowerInvariant();
|
||||
this.facingFunc = facingFunc;
|
||||
this.paused = paused;
|
||||
}
|
||||
|
||||
public int CurrentFrame => backwards ? CurrentSequence.Length - frame - 1 : frame;
|
||||
public int CurrentFrame { get { return backwards ? CurrentSequence.Start + CurrentSequence.Length - frame - 1 : frame; } }
|
||||
public Sprite Image { get { return CurrentSequence.GetSprite(CurrentFrame, facingFunc()); } }
|
||||
|
||||
public Sprite Image => CurrentSequence.GetSprite(CurrentFrame, facingFunc());
|
||||
|
||||
public IRenderable[] Render(WPos pos, in WVec offset, int zOffset, PaletteReference palette)
|
||||
public IEnumerable<IRenderable> Render(WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale)
|
||||
{
|
||||
var tintModifiers = CurrentSequence.IgnoreWorldTint ? TintModifiers.IgnoreWorldTint : TintModifiers.None;
|
||||
var alpha = CurrentSequence.GetAlpha(CurrentFrame);
|
||||
var (image, rotation) = CurrentSequence.GetSpriteWithRotation(CurrentFrame, facingFunc());
|
||||
var imageRenderable = new SpriteRenderable(image, pos, offset, CurrentSequence.ZOffset + zOffset, palette, CurrentSequence.Scale, alpha, float3.Ones, tintModifiers, IsDecoration,
|
||||
rotation);
|
||||
var imageRenderable = new SpriteRenderable(Image, pos, offset, CurrentSequence.ZOffset + zOffset, palette, scale, IsDecoration);
|
||||
|
||||
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
|
||||
if (shadow != null)
|
||||
if (CurrentSequence.ShadowStart >= 0)
|
||||
{
|
||||
var shadowRenderable = new SpriteRenderable(shadow, pos, offset, CurrentSequence.ShadowZOffset + zOffset, palette, CurrentSequence.Scale, 1f, float3.Ones, tintModifiers,
|
||||
true, rotation);
|
||||
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
|
||||
var shadowRenderable = new SpriteRenderable(shadow, pos, offset, CurrentSequence.ShadowZOffset + zOffset, palette, scale, true);
|
||||
return new IRenderable[] { shadowRenderable, 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 IEnumerable<IRenderable> Render(WPos pos, PaletteReference palette)
|
||||
{
|
||||
scale *= CurrentSequence.Scale;
|
||||
var screenOffset = (scale * wr.ScreenVectorComponents(offset)).XY.ToInt2();
|
||||
var imagePos = pos + screenOffset - new int2((int)(scale * Image.Size.X / 2), (int)(scale * Image.Size.Y / 2));
|
||||
var alpha = CurrentSequence.GetAlpha(CurrentFrame);
|
||||
var imageRenderable = new UISpriteRenderable(Image, WPos.Zero + offset, imagePos, CurrentSequence.ZOffset + zOffset, palette, scale, alpha, rotation);
|
||||
|
||||
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
|
||||
if (shadow != null)
|
||||
{
|
||||
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);
|
||||
return new IRenderable[] { shadowRenderable, imageRenderable };
|
||||
}
|
||||
|
||||
return new IRenderable[] { imageRenderable };
|
||||
}
|
||||
|
||||
public Rectangle ScreenBounds(WorldRenderer wr, WPos pos, in WVec offset)
|
||||
{
|
||||
var scale = CurrentSequence.Scale;
|
||||
var xy = wr.ScreenPxPosition(pos) + wr.ScreenPxOffset(offset);
|
||||
var cb = CurrentSequence.Bounds;
|
||||
return Rectangle.FromLTRB(
|
||||
xy.X + (int)(cb.Left * scale),
|
||||
xy.Y + (int)(cb.Top * scale),
|
||||
xy.X + (int)(cb.Right * scale),
|
||||
xy.Y + (int)(cb.Bottom * scale));
|
||||
}
|
||||
|
||||
public IRenderable[] Render(WPos pos, PaletteReference palette)
|
||||
{
|
||||
return Render(pos, WVec.Zero, 0, palette);
|
||||
return Render(pos, WVec.Zero, 0, palette, 1f);
|
||||
}
|
||||
|
||||
public void Play(string sequenceName)
|
||||
@@ -116,7 +79,7 @@ namespace OpenRA.Graphics
|
||||
int CurrentSequenceTickOrDefault()
|
||||
{
|
||||
const int DefaultTick = 40; // 25 fps == 40 ms
|
||||
return CurrentSequence?.Tick ?? DefaultTick;
|
||||
return CurrentSequence != null ? CurrentSequence.Tick : DefaultTick;
|
||||
}
|
||||
|
||||
void PlaySequence(string sequenceName)
|
||||
@@ -165,7 +128,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
frame = CurrentSequence.Length - 1;
|
||||
tickFunc = () => { };
|
||||
after?.Invoke();
|
||||
if (after != null) after();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -236,11 +199,11 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasSequence(string seq) { return sequences.HasSequence(Name, seq); }
|
||||
public bool HasSequence(string seq) { return sequenceProvider.HasSequence(Name, seq); }
|
||||
|
||||
public ISpriteSequence GetSequence(string sequenceName)
|
||||
{
|
||||
return sequences.GetSequence(Name, sequenceName);
|
||||
return sequenceProvider.GetSequence(Name, sequenceName);
|
||||
}
|
||||
|
||||
public string GetRandomExistingSequence(string[] sequences, MersenneTwister random)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -10,7 +10,7 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.Primitives;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
@@ -35,21 +35,13 @@ namespace OpenRA.Graphics
|
||||
ZOffset = zOffset;
|
||||
}
|
||||
|
||||
public IRenderable[] Render(Actor self, PaletteReference pal)
|
||||
public IEnumerable<IRenderable> Render(Actor self, WorldRenderer wr, PaletteReference pal, float scale)
|
||||
{
|
||||
var center = self.CenterPosition;
|
||||
var offset = OffsetFunc?.Invoke() ?? WVec.Zero;
|
||||
var offset = OffsetFunc != null ? OffsetFunc() : WVec.Zero;
|
||||
|
||||
var z = ZOffset?.Invoke(center + offset) ?? 0;
|
||||
return Animation.Render(center, offset, z, pal);
|
||||
}
|
||||
|
||||
public Rectangle ScreenBounds(Actor self, WorldRenderer wr)
|
||||
{
|
||||
var center = self.CenterPosition;
|
||||
var offset = OffsetFunc?.Invoke() ?? WVec.Zero;
|
||||
|
||||
return Animation.ScreenBounds(wr, center, offset);
|
||||
var z = (ZOffset != null) ? ZOffset(center + offset) : 0;
|
||||
return Animation.Render(center, offset, z, pal, scale);
|
||||
}
|
||||
|
||||
public static implicit operator AnimationWithOffset(Animation a)
|
||||
@@ -57,4 +49,4 @@ namespace OpenRA.Graphics
|
||||
return new AnimationWithOffset(a, null, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -9,290 +9,128 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
[Flags]
|
||||
public enum PanelSides
|
||||
{
|
||||
Left = 1,
|
||||
Top = 2,
|
||||
Right = 4,
|
||||
Bottom = 8,
|
||||
Center = 16,
|
||||
|
||||
Edges = Left | Top | Right | Bottom,
|
||||
All = Edges | Center,
|
||||
}
|
||||
|
||||
public static class PanelSidesExts
|
||||
{
|
||||
public static bool HasSide(this PanelSides self, PanelSides m)
|
||||
{
|
||||
// PERF: Enum.HasFlag is slower and requires allocations.
|
||||
return (self & m) == m;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ChromeProvider
|
||||
{
|
||||
public class Collection
|
||||
struct Collection
|
||||
{
|
||||
public readonly string Image = null;
|
||||
public readonly string Image2x = null;
|
||||
public readonly string Image3x = null;
|
||||
|
||||
public readonly int[] PanelRegion = null;
|
||||
public readonly PanelSides PanelSides = PanelSides.All;
|
||||
public readonly Dictionary<string, Rectangle> Regions = new();
|
||||
public string Src;
|
||||
public Dictionary<string, MappedImage> Regions;
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<string, Collection> Collections => collections;
|
||||
static Dictionary<string, Collection> collections;
|
||||
static Dictionary<string, (Sheet Sheet, int Density)> cachedSheets;
|
||||
static Dictionary<string, Sheet> cachedSheets;
|
||||
static Dictionary<string, Dictionary<string, Sprite>> cachedSprites;
|
||||
static Dictionary<string, Sprite[]> cachedPanelSprites;
|
||||
static Dictionary<Collection, (Sheet Sheet, int)> cachedCollectionSheets;
|
||||
|
||||
static IReadOnlyFileSystem fileSystem;
|
||||
static float dpiScale = 1;
|
||||
|
||||
public static void Initialize(ModData modData)
|
||||
{
|
||||
Deinitialize();
|
||||
|
||||
// Load higher resolution images if available on HiDPI displays
|
||||
if (Game.Renderer != null)
|
||||
dpiScale = Game.Renderer.WindowScale;
|
||||
|
||||
fileSystem = modData.DefaultFileSystem;
|
||||
collections = new Dictionary<string, Collection>();
|
||||
cachedSheets = new Dictionary<string, (Sheet, int)>();
|
||||
cachedSheets = new Dictionary<string, Sheet>();
|
||||
cachedSprites = new Dictionary<string, Dictionary<string, Sprite>>();
|
||||
cachedPanelSprites = new Dictionary<string, Sprite[]>();
|
||||
cachedCollectionSheets = new Dictionary<Collection, (Sheet, int)>();
|
||||
|
||||
var chrome = MiniYaml.Merge(modData.Manifest.Chrome
|
||||
.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s)));
|
||||
|
||||
foreach (var c in chrome)
|
||||
if (!c.Key.StartsWith("^", StringComparison.Ordinal))
|
||||
LoadCollection(c.Key, c.Value);
|
||||
LoadCollection(c.Key, c.Value);
|
||||
}
|
||||
|
||||
public static void Deinitialize()
|
||||
{
|
||||
if (cachedSheets != null)
|
||||
foreach (var sheet in cachedSheets.Values)
|
||||
sheet.Sheet.Dispose();
|
||||
sheet.Dispose();
|
||||
|
||||
collections = null;
|
||||
cachedSheets = null;
|
||||
cachedSprites = null;
|
||||
cachedPanelSprites = null;
|
||||
cachedCollectionSheets = null;
|
||||
}
|
||||
|
||||
public static void Save(string file)
|
||||
{
|
||||
var root = new List<MiniYamlNode>();
|
||||
foreach (var kv in collections)
|
||||
root.Add(new MiniYamlNode(kv.Key, SaveCollection(kv.Value)));
|
||||
|
||||
root.WriteToFile(file);
|
||||
}
|
||||
|
||||
static MiniYaml SaveCollection(Collection collection)
|
||||
{
|
||||
var root = new List<MiniYamlNode>();
|
||||
foreach (var kv in collection.Regions)
|
||||
root.Add(new MiniYamlNode(kv.Key, kv.Value.Save(collection.Src)));
|
||||
|
||||
return new MiniYaml(collection.Src, root);
|
||||
}
|
||||
|
||||
static void LoadCollection(string name, MiniYaml yaml)
|
||||
{
|
||||
Game.ModData.LoadScreen?.Display();
|
||||
collections.Add(name, FieldLoader.Load<Collection>(yaml));
|
||||
}
|
||||
|
||||
static (Sheet Sheet, int Density) SheetForCollection(Collection c)
|
||||
{
|
||||
// Outer cache avoids recalculating image names
|
||||
if (!cachedCollectionSheets.TryGetValue(c, out (Sheet, int) sheetDensity))
|
||||
if (Game.ModData.LoadScreen != null)
|
||||
Game.ModData.LoadScreen.Display();
|
||||
var collection = new Collection()
|
||||
{
|
||||
var image = c.Image;
|
||||
var density = 1;
|
||||
if (dpiScale > 2 && !string.IsNullOrEmpty(c.Image3x))
|
||||
{
|
||||
image = c.Image3x;
|
||||
density = 3;
|
||||
}
|
||||
else if (dpiScale > 1 && !string.IsNullOrEmpty(c.Image2x))
|
||||
{
|
||||
image = c.Image2x;
|
||||
density = 2;
|
||||
}
|
||||
Src = yaml.Value,
|
||||
Regions = yaml.Nodes.ToDictionary(n => n.Key, n => new MappedImage(yaml.Value, n.Value))
|
||||
};
|
||||
|
||||
// Inner cache makes sure we share sheets between collections
|
||||
if (!cachedSheets.TryGetValue(image, out sheetDensity))
|
||||
{
|
||||
Sheet sheet;
|
||||
using (var stream = fileSystem.Open(image))
|
||||
sheet = new Sheet(SheetType.BGRA, stream);
|
||||
|
||||
sheet.GetTexture().ScaleFilter = TextureScaleFilter.Linear;
|
||||
|
||||
sheetDensity = (sheet, density);
|
||||
cachedSheets.Add(image, sheetDensity);
|
||||
}
|
||||
|
||||
cachedCollectionSheets.Add(c, sheetDensity);
|
||||
}
|
||||
|
||||
return sheetDensity;
|
||||
collections.Add(name, collection);
|
||||
}
|
||||
|
||||
public static Sprite GetImage(string collectionName, string imageName)
|
||||
{
|
||||
var image = TryGetImage(collectionName, imageName);
|
||||
if (image == null)
|
||||
throw new ArgumentException($"Sprite `{collectionName}/{imageName}` was not found.");
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
public static Sprite TryGetImage(string collectionName, string imageName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(collectionName))
|
||||
return null;
|
||||
|
||||
// Cached sprite
|
||||
if (cachedSprites.TryGetValue(collectionName, out var cachedCollection) && cachedCollection.TryGetValue(imageName, out var sprite))
|
||||
Dictionary<string, Sprite> cachedCollection;
|
||||
Sprite sprite;
|
||||
if (cachedSprites.TryGetValue(collectionName, out cachedCollection) && cachedCollection.TryGetValue(imageName, out sprite))
|
||||
return sprite;
|
||||
|
||||
if (!collections.TryGetValue(collectionName, out var collection))
|
||||
Collection collection;
|
||||
if (!collections.TryGetValue(collectionName, out collection))
|
||||
{
|
||||
Log.Write("debug", "Could not find collection '{0}'", collectionName);
|
||||
return null;
|
||||
}
|
||||
|
||||
MappedImage mi;
|
||||
if (!collection.Regions.TryGetValue(imageName, out mi))
|
||||
return null;
|
||||
|
||||
if (!collection.Regions.TryGetValue(imageName, out var mi))
|
||||
return null;
|
||||
// Cached sheet
|
||||
Sheet sheet;
|
||||
if (cachedSheets.ContainsKey(mi.Src))
|
||||
sheet = cachedSheets[mi.Src];
|
||||
else
|
||||
{
|
||||
using (var stream = fileSystem.Open(mi.Src))
|
||||
sheet = new Sheet(SheetType.BGRA, stream);
|
||||
|
||||
cachedSheets.Add(mi.Src, sheet);
|
||||
}
|
||||
|
||||
// Cache the sprite
|
||||
var sheetDensity = SheetForCollection(collection);
|
||||
if (cachedCollection == null)
|
||||
{
|
||||
cachedCollection = new Dictionary<string, Sprite>();
|
||||
cachedSprites.Add(collectionName, cachedCollection);
|
||||
}
|
||||
|
||||
var image = new Sprite(sheetDensity.Sheet, sheetDensity.Density * mi, TextureChannel.RGBA, 1f / sheetDensity.Density);
|
||||
var image = mi.GetImage(sheet);
|
||||
cachedCollection.Add(imageName, image);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
public static Sprite[] GetPanelImages(string collectionName)
|
||||
{
|
||||
var panel = TryGetPanelImages(collectionName);
|
||||
if (panel == null)
|
||||
throw new ArgumentException($"Panel `{collectionName}` was not found.");
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
public static Sprite[] TryGetPanelImages(string collectionName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(collectionName))
|
||||
return null;
|
||||
|
||||
// Cached sprite
|
||||
if (cachedPanelSprites.TryGetValue(collectionName, out var cachedSprites))
|
||||
return cachedSprites;
|
||||
|
||||
if (!collections.TryGetValue(collectionName, out var collection))
|
||||
return null;
|
||||
|
||||
Sprite[] sprites;
|
||||
if (collection.PanelRegion != null)
|
||||
{
|
||||
if (collection.PanelRegion.Length != 8)
|
||||
{
|
||||
Log.Write("debug", $"Collection '{collectionName}' does not define a valid PanelRegion");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Cache the sprites
|
||||
var sheetDensity = SheetForCollection(collection);
|
||||
var pr = collection.PanelRegion;
|
||||
var ps = collection.PanelSides;
|
||||
|
||||
var sides = new (PanelSides PanelSides, Rectangle Bounds)[]
|
||||
{
|
||||
(PanelSides.Top | PanelSides.Left, new Rectangle(pr[0], pr[1], pr[2], pr[3])),
|
||||
(PanelSides.Top, new Rectangle(pr[0] + pr[2], pr[1], pr[4], pr[3])),
|
||||
(PanelSides.Top | PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1], pr[6], pr[3])),
|
||||
(PanelSides.Left, new Rectangle(pr[0], pr[1] + pr[3], pr[2], pr[5])),
|
||||
(PanelSides.Center, new Rectangle(pr[0] + pr[2], pr[1] + pr[3], pr[4], pr[5])),
|
||||
(PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1] + pr[3], pr[6], pr[5])),
|
||||
(PanelSides.Bottom | PanelSides.Left, new Rectangle(pr[0], pr[1] + pr[3] + pr[5], pr[2], pr[7])),
|
||||
(PanelSides.Bottom, new Rectangle(pr[0] + pr[2], pr[1] + pr[3] + pr[5], pr[4], pr[7])),
|
||||
(PanelSides.Bottom | PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1] + pr[3] + pr[5], pr[6], pr[7]))
|
||||
};
|
||||
|
||||
sprites = sides.Select(x => ps.HasSide(x.PanelSides) ? new Sprite(sheetDensity.Sheet, sheetDensity.Density * x.Bounds, TextureChannel.RGBA, 1f / sheetDensity.Density) : null)
|
||||
.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
// PERF: We don't need to search for images if there are no definitions.
|
||||
// PERF: It's more efficient to send an empty array rather than an array of 9 nulls.
|
||||
if (!collection.Regions.Any())
|
||||
return Array.Empty<Sprite>();
|
||||
|
||||
// Support manual definitions for unusual dialog layouts
|
||||
sprites = new[]
|
||||
{
|
||||
TryGetImage(collectionName, "corner-tl"),
|
||||
TryGetImage(collectionName, "border-t"),
|
||||
TryGetImage(collectionName, "corner-tr"),
|
||||
TryGetImage(collectionName, "border-l"),
|
||||
TryGetImage(collectionName, "background"),
|
||||
TryGetImage(collectionName, "border-r"),
|
||||
TryGetImage(collectionName, "corner-bl"),
|
||||
TryGetImage(collectionName, "border-b"),
|
||||
TryGetImage(collectionName, "corner-br")
|
||||
};
|
||||
}
|
||||
|
||||
cachedPanelSprites.Add(collectionName, sprites);
|
||||
return sprites;
|
||||
}
|
||||
|
||||
public static Size GetMinimumPanelSize(string collectionName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(collectionName))
|
||||
return new Size(0, 0);
|
||||
|
||||
if (!collections.TryGetValue(collectionName, out var collection))
|
||||
{
|
||||
Log.Write("debug", "Could not find collection '{0}'", collectionName);
|
||||
return new Size(0, 0);
|
||||
}
|
||||
|
||||
if (collection.PanelRegion == null || collection.PanelRegion.Length != 8)
|
||||
{
|
||||
Log.Write("debug", "Collection '{0}' does not define a valid PanelRegion", collectionName);
|
||||
return new Size(0, 0);
|
||||
}
|
||||
|
||||
var pr = collection.PanelRegion;
|
||||
return new Size(pr[2] + pr[6], pr[3] + pr[7]);
|
||||
}
|
||||
|
||||
public static void SetDPIScale(float scale)
|
||||
{
|
||||
if (dpiScale == scale)
|
||||
return;
|
||||
|
||||
dpiScale = scale;
|
||||
|
||||
// Clear the sprite caches so the new artwork can be loaded
|
||||
// Sheets are not cleared: we assume that the extra memory overhead
|
||||
// of having the same sheet in memory in multiple DPIs is better than
|
||||
// the overhead of having to dispose and reload everything.
|
||||
// Changing the DPI scale is rare, but if it does happen then there
|
||||
// is a reasonable chance that it may happen again this session.
|
||||
cachedSprites.Clear();
|
||||
cachedPanelSprites.Clear();
|
||||
cachedCollectionSheets.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,293 +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;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public sealed class CursorManager
|
||||
{
|
||||
class Cursor
|
||||
{
|
||||
public string Name;
|
||||
public int2 PaddedSize;
|
||||
public Rectangle Bounds;
|
||||
|
||||
public int Length;
|
||||
public Sprite[] Sprites;
|
||||
public IHardwareCursor[] Cursors;
|
||||
}
|
||||
|
||||
readonly Dictionary<string, Cursor> cursors = new();
|
||||
readonly SheetBuilder sheetBuilder;
|
||||
readonly GraphicSettings graphicSettings;
|
||||
|
||||
Cursor cursor;
|
||||
bool isLocked = false;
|
||||
int2 lockedPosition;
|
||||
readonly bool hardwareCursorsDisabled = false;
|
||||
bool hardwareCursorsDoubled = false;
|
||||
|
||||
public CursorManager(CursorProvider cursorProvider)
|
||||
{
|
||||
hardwareCursorsDisabled = Game.Settings.Graphics.DisableHardwareCursors;
|
||||
|
||||
graphicSettings = Game.Settings.Graphics;
|
||||
sheetBuilder = new SheetBuilder(SheetType.BGRA);
|
||||
foreach (var kv in cursorProvider.Cursors)
|
||||
{
|
||||
var frames = kv.Value.Frames;
|
||||
var palette = !string.IsNullOrEmpty(kv.Value.Palette) ? cursorProvider.Palettes[kv.Value.Palette] : null;
|
||||
|
||||
var c = new Cursor
|
||||
{
|
||||
Name = kv.Key,
|
||||
Bounds = Rectangle.FromLTRB(0, 0, 1, 1),
|
||||
|
||||
Length = 0,
|
||||
Sprites = new Sprite[frames.Length],
|
||||
Cursors = new IHardwareCursor[frames.Length]
|
||||
};
|
||||
|
||||
// Hardware cursors have a number of odd platform-specific bugs/limitations.
|
||||
// Reduce the number of edge cases by padding the individual frames such that:
|
||||
// - the hotspot is inside the frame bounds (enforced by SDL)
|
||||
// - all frames within a sequence have the same size (needed for macOS 10.15)
|
||||
// - the frame size is a multiple of 8 (needed for Windows)
|
||||
foreach (var f in frames)
|
||||
{
|
||||
// Hotspot is specified relative to the center of the frame
|
||||
var hotspot = f.Offset.ToInt2() - kv.Value.Hotspot - new int2(f.Size) / 2;
|
||||
|
||||
// Resolve indexed data to real colours
|
||||
var data = f.Data;
|
||||
var type = f.Type;
|
||||
if (type == SpriteFrameType.Indexed8)
|
||||
{
|
||||
data = ConvertIndexedToBgra(kv.Key, f, palette);
|
||||
type = SpriteFrameType.Bgra32;
|
||||
}
|
||||
|
||||
c.Sprites[c.Length++] = sheetBuilder.Add(data, type, f.Size, 0, hotspot);
|
||||
|
||||
// Bounds relative to the hotspot
|
||||
c.Bounds = Rectangle.Union(c.Bounds, new Rectangle(hotspot, f.Size));
|
||||
}
|
||||
|
||||
// Pad bottom-right edge to make the frame size a multiple of 8
|
||||
c.PaddedSize = 8 * new int2((c.Bounds.Width + 7) / 8, (c.Bounds.Height + 7) / 8);
|
||||
|
||||
cursors.Add(kv.Key, c);
|
||||
}
|
||||
|
||||
CreateOrUpdateHardwareCursors();
|
||||
|
||||
foreach (var s in sheetBuilder.AllSheets)
|
||||
s.ReleaseBuffer();
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
void CreateOrUpdateHardwareCursors()
|
||||
{
|
||||
if (hardwareCursorsDisabled)
|
||||
return;
|
||||
|
||||
// Dispose any existing cursors to avoid leaking native resources
|
||||
ClearHardwareCursors();
|
||||
|
||||
foreach (var kv in cursors)
|
||||
{
|
||||
var template = kv.Value;
|
||||
for (var i = 0; i < template.Sprites.Length; i++)
|
||||
{
|
||||
template.Cursors[i]?.Dispose();
|
||||
|
||||
// Calculate the padding to position the frame within sequenceBounds
|
||||
var paddingTL = -(template.Bounds.Location - template.Sprites[i].Offset.XY.ToInt2());
|
||||
var paddingBR = template.PaddedSize - new int2(template.Sprites[i].Bounds.Size) - paddingTL;
|
||||
|
||||
var hardwareCursor = CreateHardwareCursor(kv.Key, template.Sprites[i], paddingTL, paddingBR, -template.Bounds.Location);
|
||||
if (hardwareCursor != null)
|
||||
template.Cursors[i] = hardwareCursor;
|
||||
else
|
||||
{
|
||||
Log.Write("debug", $"Failed to initialize hardware cursor for {template.Name}.");
|
||||
Console.WriteLine($"Failed to initialize hardware cursor for {template.Name}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hardwareCursorsDoubled = graphicSettings.CursorDouble;
|
||||
}
|
||||
|
||||
public void SetCursor(string cursorName)
|
||||
{
|
||||
if ((cursorName == null && cursor == null) || (cursor != null && cursorName == cursor.Name))
|
||||
return;
|
||||
|
||||
if (cursorName == null || !cursors.TryGetValue(cursorName, out cursor))
|
||||
cursor = null;
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
int frame;
|
||||
int ticks;
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
if (hardwareCursorsDoubled != graphicSettings.CursorDouble)
|
||||
{
|
||||
CreateOrUpdateHardwareCursors();
|
||||
Update();
|
||||
}
|
||||
|
||||
if (cursor == null || cursor.Cursors.Length == 1)
|
||||
return;
|
||||
|
||||
if (++ticks > 2)
|
||||
{
|
||||
ticks -= 2;
|
||||
frame++;
|
||||
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (cursor != null && frame >= cursor.Cursors.Length)
|
||||
frame %= cursor.Cursors.Length;
|
||||
|
||||
var hardwareCursor = cursor?.Cursors[frame];
|
||||
if (hardwareCursor == null || isLocked)
|
||||
Game.Renderer.Window.SetHardwareCursor(null);
|
||||
else
|
||||
Game.Renderer.Window.SetHardwareCursor(hardwareCursor);
|
||||
}
|
||||
|
||||
public void Render(Renderer renderer)
|
||||
{
|
||||
// Cursor is hidden
|
||||
if (cursor == null)
|
||||
return;
|
||||
|
||||
// Hardware cursor is enabled
|
||||
if (!isLocked && cursor.Cursors[frame % cursor.Length] != null)
|
||||
return;
|
||||
|
||||
// Render cursor in software
|
||||
var doubleCursor = graphicSettings.CursorDouble;
|
||||
var cursorSprite = cursor.Sprites[frame % cursor.Length];
|
||||
var cursorScale = doubleCursor ? 2 : 1;
|
||||
|
||||
// Cursor is rendered in native window coordinates
|
||||
// Apply same scaling rules as hardware cursors
|
||||
if (Game.Renderer.NativeWindowScale > 1.5f)
|
||||
cursorScale *= 2;
|
||||
|
||||
var mousePos = isLocked ? lockedPosition : Viewport.LastMousePos;
|
||||
renderer.RgbaSpriteRenderer.DrawSprite(cursorSprite,
|
||||
mousePos,
|
||||
cursorScale / Game.Renderer.WindowScale);
|
||||
}
|
||||
|
||||
public void Lock()
|
||||
{
|
||||
lockedPosition = Viewport.LastMousePos;
|
||||
Game.Renderer.Window.SetRelativeMouseMode(true);
|
||||
isLocked = true;
|
||||
Update();
|
||||
}
|
||||
|
||||
public void Unlock()
|
||||
{
|
||||
Game.Renderer.Window.SetRelativeMouseMode(false);
|
||||
isLocked = false;
|
||||
Update();
|
||||
}
|
||||
|
||||
public static byte[] ConvertIndexedToBgra(string name, ISpriteFrame frame, ImmutablePalette palette)
|
||||
{
|
||||
if (frame.Type != SpriteFrameType.Indexed8)
|
||||
throw new ArgumentException("ConvertIndexedToBgra requires input frames to be indexed.", nameof(frame));
|
||||
|
||||
// All palettes must be explicitly referenced, even if they are embedded in the sprite.
|
||||
if (palette == null)
|
||||
throw new InvalidOperationException($"Cursor sequence `{name}` attempted to load an indexed sprite but does not define Palette");
|
||||
|
||||
var width = frame.Size.Width;
|
||||
var height = frame.Size.Height;
|
||||
var data = new byte[4 * width * height];
|
||||
unsafe
|
||||
{
|
||||
// Cast the data to an int array so we can copy the src data directly
|
||||
fixed (byte* bd = &data[0])
|
||||
{
|
||||
var rgba = (uint*)bd;
|
||||
for (var j = 0; j < height; j++)
|
||||
for (var i = 0; i < width; i++)
|
||||
rgba[j * width + i] = palette[frame.Data[j * width + i]];
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
IHardwareCursor CreateHardwareCursor(string name, Sprite data, int2 paddingTL, int2 paddingBR, int2 hotspot)
|
||||
{
|
||||
var size = data.Bounds.Size;
|
||||
var srcStride = data.Sheet.Size.Width;
|
||||
var srcData = data.Sheet.GetData();
|
||||
var newWidth = paddingTL.X + size.Width + paddingBR.X;
|
||||
var newHeight = paddingTL.Y + size.Height + paddingBR.Y;
|
||||
var rgbaData = new byte[4 * newWidth * newHeight];
|
||||
|
||||
for (var j = 0; j < size.Height; j++)
|
||||
{
|
||||
for (var i = 0; i < size.Width; i++)
|
||||
{
|
||||
var src = 4 * ((j + data.Bounds.Top) * srcStride + data.Bounds.Left + i);
|
||||
var dest = 4 * ((j + paddingTL.Y) * newWidth + i + paddingTL.X);
|
||||
Array.Copy(srcData, src, rgbaData, dest, 4);
|
||||
}
|
||||
}
|
||||
|
||||
return Game.Renderer.Window.CreateHardwareCursor(name, new Size(newWidth, newHeight), rgbaData, hotspot, graphicSettings.CursorDouble);
|
||||
}
|
||||
|
||||
void ClearHardwareCursors()
|
||||
{
|
||||
foreach (var c in cursors.Values)
|
||||
{
|
||||
for (var i = 0; i < c.Cursors.Length; i++)
|
||||
{
|
||||
if (c.Cursors[i] != null)
|
||||
{
|
||||
c.Cursors[i].Dispose();
|
||||
c.Cursors[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ClearHardwareCursors();
|
||||
|
||||
cursors.Clear();
|
||||
sheetBuilder.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -12,7 +12,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
@@ -27,18 +26,21 @@ namespace OpenRA.Graphics
|
||||
var sequenceYaml = MiniYaml.Merge(modData.Manifest.Cursors.Select(
|
||||
s => MiniYaml.FromStream(fileSystem.Open(s), s)));
|
||||
|
||||
var shadowIndex = new int[] { };
|
||||
|
||||
var nodesDict = new MiniYaml(null, sequenceYaml).ToDictionary();
|
||||
if (nodesDict.ContainsKey("ShadowIndex"))
|
||||
{
|
||||
Array.Resize(ref shadowIndex, shadowIndex.Length + 1);
|
||||
Exts.TryParseIntegerInvariant(nodesDict["ShadowIndex"].Value,
|
||||
out shadowIndex[shadowIndex.Length - 1]);
|
||||
}
|
||||
|
||||
// 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 palettes = new Dictionary<string, ImmutablePalette>();
|
||||
foreach (var p in nodesDict["Palettes"].Nodes)
|
||||
palettes.Add(p.Key, new ImmutablePalette(fileSystem.Open(p.Value.Value), shadowIndex));
|
||||
|
||||
Palettes = nodesDict["Cursors"].Nodes.Select(n => n.Value.Value)
|
||||
.Where(p => p != null)
|
||||
.Distinct()
|
||||
.ToDictionary(p => p, p => pals[p].ReadPalette(modData.DefaultFileSystem));
|
||||
Palettes = palettes.AsReadOnly();
|
||||
|
||||
var frameCache = new FrameCache(fileSystem, modData.SpriteLoaders);
|
||||
var cursors = new Dictionary<string, CursorSequence>();
|
||||
@@ -46,9 +48,11 @@ namespace OpenRA.Graphics
|
||||
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;
|
||||
Cursors = cursors.AsReadOnly();
|
||||
}
|
||||
|
||||
public static bool CursorViewportZoomed { get { return Game.Settings.Graphics.CursorDouble && Game.Settings.Graphics.PixelDouble; } }
|
||||
|
||||
public bool HasCursorSequence(string cursor)
|
||||
{
|
||||
return Cursors.ContainsKey(cursor);
|
||||
@@ -59,7 +63,7 @@ namespace OpenRA.Graphics
|
||||
try { return Cursors[cursor]; }
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
throw new InvalidOperationException($"Cursor does not have a sequence `{cursor}`");
|
||||
throw new InvalidOperationException("Cursor does not have a sequence `{0}`".F(cursor));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -31,11 +31,8 @@ namespace OpenRA.Graphics
|
||||
Palette = palette;
|
||||
Name = name;
|
||||
|
||||
var cursorSprites = cache[cursorSrc];
|
||||
Frames = cursorSprites.Skip(Start).ToArray();
|
||||
|
||||
if ((d.ContainsKey("Length") && d["Length"].Value == "*") || (d.ContainsKey("End") && d["End"].Value == "*"))
|
||||
Length = Frames.Length;
|
||||
Length = Frames.Length - Start;
|
||||
else if (d.ContainsKey("Length"))
|
||||
Length = Exts.ParseIntegerInvariant(d["Length"].Value);
|
||||
else if (d.ContainsKey("End"))
|
||||
@@ -43,23 +40,22 @@ namespace OpenRA.Graphics
|
||||
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.");
|
||||
Frames = cache[cursorSrc]
|
||||
.Skip(Start)
|
||||
.Take(Length)
|
||||
.ToArray();
|
||||
|
||||
if (d.ContainsKey("X"))
|
||||
{
|
||||
Exts.TryParseIntegerInvariant(d["X"].Value, out var x);
|
||||
int x;
|
||||
Exts.TryParseIntegerInvariant(d["X"].Value, out x);
|
||||
Hotspot = Hotspot.WithX(x);
|
||||
}
|
||||
|
||||
if (d.ContainsKey("Y"))
|
||||
{
|
||||
Exts.TryParseIntegerInvariant(d["Y"].Value, out var y);
|
||||
int y;
|
||||
Exts.TryParseIntegerInvariant(d["Y"].Value, out y);
|
||||
Hotspot = Hotspot.WithY(y);
|
||||
}
|
||||
}
|
||||
|
||||
146
OpenRA.Game/Graphics/HSLColor.cs
Normal file
146
OpenRA.Game/Graphics/HSLColor.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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.Drawing;
|
||||
using System.Globalization;
|
||||
using OpenRA.Scripting;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public struct HSLColor : IScriptBindable
|
||||
{
|
||||
public readonly byte H;
|
||||
public readonly byte S;
|
||||
public readonly byte L;
|
||||
public readonly Color RGB;
|
||||
|
||||
public static HSLColor FromHSV(float h, float s, float v)
|
||||
{
|
||||
var ll = 0.5f * (2 - s) * v;
|
||||
var ss = (ll >= 1 || v <= 0) ? 0 : 0.5f * s * v / (ll <= 0.5f ? ll : 1 - ll);
|
||||
return new HSLColor((byte)(255 * h), (byte)(255 * ss), (byte)(255 * ll));
|
||||
}
|
||||
|
||||
public HSLColor(Color color)
|
||||
{
|
||||
RGB = color;
|
||||
H = (byte)((color.GetHue() / 360.0f) * 255);
|
||||
S = (byte)(color.GetSaturation() * 255);
|
||||
L = (byte)(color.GetBrightness() * 255);
|
||||
}
|
||||
|
||||
public static HSLColor FromRGB(int r, int g, int b)
|
||||
{
|
||||
return new HSLColor(Color.FromArgb(r, g, b));
|
||||
}
|
||||
|
||||
public static Color RGBFromHSL(float h, float s, float l)
|
||||
{
|
||||
// Convert from HSL to RGB
|
||||
var q = (l < 0.5f) ? l * (1 + s) : l + s - (l * s);
|
||||
var p = 2 * l - q;
|
||||
|
||||
float[] trgb = { h + 1 / 3.0f, h, h - 1 / 3.0f };
|
||||
float[] rgb = { 0, 0, 0 };
|
||||
|
||||
for (var k = 0; k < 3; k++)
|
||||
{
|
||||
while (trgb[k] < 0) trgb[k] += 1.0f;
|
||||
while (trgb[k] > 1) trgb[k] -= 1.0f;
|
||||
}
|
||||
|
||||
for (var k = 0; k < 3; k++)
|
||||
{
|
||||
if (trgb[k] < 1 / 6.0f)
|
||||
rgb[k] = p + ((q - p) * 6 * trgb[k]);
|
||||
else if (trgb[k] >= 1 / 6.0f && trgb[k] < 0.5)
|
||||
rgb[k] = q;
|
||||
else if (trgb[k] >= 0.5f && trgb[k] < 2.0f / 3)
|
||||
rgb[k] = p + ((q - p) * 6 * (2.0f / 3 - trgb[k]));
|
||||
else
|
||||
rgb[k] = p;
|
||||
}
|
||||
|
||||
return Color.FromArgb((int)(rgb[0] * 255), (int)(rgb[1] * 255), (int)(rgb[2] * 255));
|
||||
}
|
||||
|
||||
public static bool TryParseRGB(string value, out Color color)
|
||||
{
|
||||
color = new Color();
|
||||
value = value.Trim();
|
||||
if (value.Length != 6 && value.Length != 8)
|
||||
return false;
|
||||
|
||||
byte red, green, blue, alpha = 255;
|
||||
if (!byte.TryParse(value.Substring(0, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out red)
|
||||
|| !byte.TryParse(value.Substring(2, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out green)
|
||||
|| !byte.TryParse(value.Substring(4, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out blue))
|
||||
return false;
|
||||
|
||||
if (value.Length == 8
|
||||
&& !byte.TryParse(value.Substring(6, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out alpha))
|
||||
return false;
|
||||
|
||||
color = Color.FromArgb(alpha, red, green, blue);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool operator ==(HSLColor me, HSLColor other)
|
||||
{
|
||||
return me.H == other.H && me.S == other.S && me.L == other.L;
|
||||
}
|
||||
|
||||
public static bool operator !=(HSLColor me, HSLColor other) { return !(me == other); }
|
||||
|
||||
public HSLColor(byte h, byte s, byte l)
|
||||
{
|
||||
H = h;
|
||||
S = s;
|
||||
L = l;
|
||||
RGB = RGBFromHSL(H / 255f, S / 255f, L / 255f);
|
||||
}
|
||||
|
||||
public void ToHSV(out float h, out float s, out float v)
|
||||
{
|
||||
var ll = 2 * L / 255f;
|
||||
var ss = S / 255f * ((ll <= 1) ? ll : 2 - ll);
|
||||
|
||||
h = H / 255f;
|
||||
s = (2 * ss) / (ll + ss);
|
||||
v = (ll + ss) / 2;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "{0},{1},{2}".F(H, S, L);
|
||||
}
|
||||
|
||||
public static string ToHexString(Color color)
|
||||
{
|
||||
if (color.A == 255)
|
||||
return color.R.ToString("X2") + color.G.ToString("X2") + color.B.ToString("X2");
|
||||
return color.R.ToString("X2") + color.G.ToString("X2") + color.B.ToString("X2") + color.A.ToString("X2");
|
||||
}
|
||||
|
||||
public string ToHexString()
|
||||
{
|
||||
return ToHexString(RGB);
|
||||
}
|
||||
|
||||
public override int GetHashCode() { return H.GetHashCode() ^ S.GetHashCode() ^ L.GetHashCode(); }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var o = obj as HSLColor?;
|
||||
return o != null && o == this;
|
||||
}
|
||||
}
|
||||
}
|
||||
137
OpenRA.Game/Graphics/HardwareCursor.cs
Normal file
137
OpenRA.Game/Graphics/HardwareCursor.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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.Drawing;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public sealed class HardwareCursor : ICursor
|
||||
{
|
||||
readonly Dictionary<string, IHardwareCursor[]> hardwareCursors = new Dictionary<string, IHardwareCursor[]>();
|
||||
readonly CursorProvider cursorProvider;
|
||||
CursorSequence cursor;
|
||||
|
||||
public HardwareCursor(CursorProvider cursorProvider)
|
||||
{
|
||||
this.cursorProvider = cursorProvider;
|
||||
|
||||
foreach (var kv in cursorProvider.Cursors)
|
||||
{
|
||||
var palette = cursorProvider.Palettes[kv.Value.Palette];
|
||||
var hc = kv.Value.Frames
|
||||
.Select(f => CreateCursor(f, palette, kv.Key, kv.Value))
|
||||
.ToArray();
|
||||
|
||||
hardwareCursors.Add(kv.Key, hc);
|
||||
}
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
IHardwareCursor CreateCursor(ISpriteFrame f, ImmutablePalette palette, string name, CursorSequence sequence)
|
||||
{
|
||||
var hotspot = sequence.Hotspot - f.Offset.ToInt2() + new int2(f.Size) / 2;
|
||||
|
||||
// Expand the frame if required to include the hotspot
|
||||
var frameWidth = f.Size.Width;
|
||||
var dataWidth = f.Size.Width;
|
||||
var dataX = 0;
|
||||
if (hotspot.X < 0)
|
||||
{
|
||||
dataX = -hotspot.X;
|
||||
dataWidth += dataX;
|
||||
hotspot = hotspot.WithX(0);
|
||||
}
|
||||
else if (hotspot.X >= frameWidth)
|
||||
dataWidth = hotspot.X + 1;
|
||||
|
||||
var frameHeight = f.Size.Height;
|
||||
var dataHeight = f.Size.Height;
|
||||
var dataY = 0;
|
||||
if (hotspot.Y < 0)
|
||||
{
|
||||
dataY = -hotspot.Y;
|
||||
dataHeight += dataY;
|
||||
hotspot = hotspot.WithY(0);
|
||||
}
|
||||
else if (hotspot.Y >= frameHeight)
|
||||
dataHeight = hotspot.Y + 1;
|
||||
|
||||
var data = new byte[4 * dataWidth * dataHeight];
|
||||
for (var j = 0; j < frameHeight; j++)
|
||||
{
|
||||
for (var i = 0; i < frameWidth; i++)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes(palette[f.Data[j * frameWidth + i]]);
|
||||
var start = 4 * ((j + dataY) * dataWidth + dataX + i);
|
||||
for (var k = 0; k < 4; k++)
|
||||
data[start + k] = bytes[k];
|
||||
}
|
||||
}
|
||||
|
||||
return Game.Renderer.Device.CreateHardwareCursor(name, new Size(dataWidth, dataHeight), data, hotspot);
|
||||
}
|
||||
|
||||
public void SetCursor(string cursorName)
|
||||
{
|
||||
if ((cursorName == null && cursor == null) || (cursor != null && cursorName == cursor.Name))
|
||||
return;
|
||||
|
||||
if (cursorName == null || !cursorProvider.Cursors.TryGetValue(cursorName, out cursor))
|
||||
cursor = null;
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
int frame;
|
||||
int ticks;
|
||||
public void Tick()
|
||||
{
|
||||
if (cursor == null || cursor.Length == 1)
|
||||
return;
|
||||
|
||||
if (++ticks > 2)
|
||||
{
|
||||
ticks -= 2;
|
||||
frame++;
|
||||
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (cursor == null)
|
||||
Game.Renderer.Device.SetHardwareCursor(null);
|
||||
else
|
||||
{
|
||||
if (frame >= cursor.Length)
|
||||
frame = frame % cursor.Length;
|
||||
|
||||
Game.Renderer.Device.SetHardwareCursor(hardwareCursors[cursor.Name][frame]);
|
||||
}
|
||||
}
|
||||
|
||||
public void Render(Renderer renderer) { }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var cursors in hardwareCursors)
|
||||
foreach (var cursor in cursors.Value)
|
||||
cursor.Dispose();
|
||||
|
||||
hardwareCursors.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -17,95 +17,76 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
public sealed class HardwarePalette : IDisposable
|
||||
{
|
||||
public ITexture Texture { get; }
|
||||
public ITexture ColorShifts { get; }
|
||||
|
||||
public ITexture Texture { get; private set; }
|
||||
public int Height { get; private set; }
|
||||
readonly Dictionary<string, ImmutablePalette> palettes = new();
|
||||
readonly Dictionary<string, MutablePalette> mutablePalettes = new();
|
||||
readonly Dictionary<string, int> indices = new();
|
||||
byte[] buffer = Array.Empty<byte>();
|
||||
float[] colorShiftBuffer = Array.Empty<float>();
|
||||
readonly Dictionary<string, ImmutablePalette> palettes = new Dictionary<string, ImmutablePalette>();
|
||||
readonly Dictionary<string, MutablePalette> modifiablePalettes = new Dictionary<string, MutablePalette>();
|
||||
readonly IReadOnlyDictionary<string, MutablePalette> readOnlyModifiablePalettes;
|
||||
readonly Dictionary<string, int> indices = new Dictionary<string, int>();
|
||||
byte[] buffer = new byte[0];
|
||||
|
||||
public HardwarePalette()
|
||||
{
|
||||
Texture = Game.Renderer.Context.CreateTexture();
|
||||
ColorShifts = Game.Renderer.Context.CreateTexture();
|
||||
Texture = Game.Renderer.Device.CreateTexture();
|
||||
readOnlyModifiablePalettes = modifiablePalettes.AsReadOnly();
|
||||
}
|
||||
|
||||
public bool Contains(string name)
|
||||
{
|
||||
return mutablePalettes.ContainsKey(name) || palettes.ContainsKey(name);
|
||||
return modifiablePalettes.ContainsKey(name) || palettes.ContainsKey(name);
|
||||
}
|
||||
|
||||
public IPalette GetPalette(string name)
|
||||
{
|
||||
if (mutablePalettes.TryGetValue(name, out var mutable))
|
||||
MutablePalette mutable;
|
||||
if (modifiablePalettes.TryGetValue(name, out mutable))
|
||||
return mutable.AsReadOnly();
|
||||
if (palettes.TryGetValue(name, out var immutable))
|
||||
ImmutablePalette immutable;
|
||||
if (palettes.TryGetValue(name, out immutable))
|
||||
return immutable;
|
||||
throw new InvalidOperationException($"Palette `{name}` does not exist");
|
||||
throw new InvalidOperationException("Palette `{0}` does not exist".F(name));
|
||||
}
|
||||
|
||||
public int GetPaletteIndex(string name)
|
||||
{
|
||||
if (!indices.TryGetValue(name, out var ret))
|
||||
throw new InvalidOperationException($"Palette `{name}` does not exist");
|
||||
int ret;
|
||||
if (!indices.TryGetValue(name, out ret))
|
||||
throw new InvalidOperationException("Palette `{0}` does not exist".F(name));
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void AddPalette(string name, ImmutablePalette p, bool allowModifiers)
|
||||
{
|
||||
if (palettes.ContainsKey(name))
|
||||
throw new InvalidOperationException($"Palette {name} has already been defined");
|
||||
throw new InvalidOperationException("Palette {0} has already been defined".F(name));
|
||||
|
||||
// PERF: the first row in the palette textures is reserved as a placeholder for non-indexed sprites
|
||||
// that do not have a color-shift applied. This provides a quick shortcut to avoid querying the
|
||||
// color-shift texture for every pixel only to find that most are not shifted.
|
||||
var index = palettes.Count + 1;
|
||||
int index = palettes.Count;
|
||||
indices.Add(name, index);
|
||||
palettes.Add(name, p);
|
||||
|
||||
if (index >= Height)
|
||||
if (palettes.Count > Height)
|
||||
{
|
||||
Height = Exts.NextPowerOf2(index + 1);
|
||||
Height = Exts.NextPowerOf2(palettes.Count);
|
||||
Array.Resize(ref buffer, Height * Palette.Size * 4);
|
||||
Array.Resize(ref colorShiftBuffer, Height * 8);
|
||||
}
|
||||
|
||||
if (allowModifiers)
|
||||
mutablePalettes.Add(name, new MutablePalette(p));
|
||||
modifiablePalettes.Add(name, new MutablePalette(p));
|
||||
else
|
||||
CopyPaletteToBuffer(index, p);
|
||||
}
|
||||
|
||||
public void ReplacePalette(string name, IPalette p)
|
||||
{
|
||||
if (mutablePalettes.ContainsKey(name))
|
||||
CopyPaletteToBuffer(indices[name], mutablePalettes[name] = new MutablePalette(p));
|
||||
if (modifiablePalettes.ContainsKey(name))
|
||||
CopyPaletteToBuffer(indices[name], modifiablePalettes[name] = new MutablePalette(p));
|
||||
else if (palettes.ContainsKey(name))
|
||||
CopyPaletteToBuffer(indices[name], palettes[name] = new ImmutablePalette(p));
|
||||
else
|
||||
throw new InvalidOperationException($"Palette `{name}` does not exist");
|
||||
throw new InvalidOperationException("Palette `{0}` does not exist".F(name));
|
||||
CopyBufferToTexture();
|
||||
}
|
||||
|
||||
public void SetColorShift(string name, float hueOffset, float satOffset, float valueMultiplier, float minHue, float maxHue)
|
||||
{
|
||||
var index = GetPaletteIndex(name);
|
||||
colorShiftBuffer[8 * index + 0] = minHue;
|
||||
colorShiftBuffer[8 * index + 1] = maxHue;
|
||||
colorShiftBuffer[8 * index + 4] = hueOffset;
|
||||
colorShiftBuffer[8 * index + 5] = satOffset;
|
||||
colorShiftBuffer[8 * index + 6] = valueMultiplier;
|
||||
}
|
||||
|
||||
public bool HasColorShift(string name)
|
||||
{
|
||||
var index = GetPaletteIndex(name);
|
||||
return colorShiftBuffer[8 * index] != 0 || colorShiftBuffer[8 * index + 1] != 0;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
CopyModifiablePalettesToBuffer();
|
||||
@@ -119,27 +100,26 @@ namespace OpenRA.Graphics
|
||||
|
||||
void CopyModifiablePalettesToBuffer()
|
||||
{
|
||||
foreach (var kvp in mutablePalettes)
|
||||
foreach (var kvp in modifiablePalettes)
|
||||
CopyPaletteToBuffer(indices[kvp.Key], kvp.Value);
|
||||
}
|
||||
|
||||
void CopyBufferToTexture()
|
||||
{
|
||||
Texture.SetData(buffer, Palette.Size, Height);
|
||||
ColorShifts.SetFloatData(colorShiftBuffer, 2, Height);
|
||||
}
|
||||
|
||||
public void ApplyModifiers(IEnumerable<IPaletteModifier> paletteMods)
|
||||
{
|
||||
foreach (var mod in paletteMods)
|
||||
mod.AdjustPalette(mutablePalettes);
|
||||
mod.AdjustPalette(readOnlyModifiablePalettes);
|
||||
|
||||
// Update our texture with the changes.
|
||||
CopyModifiablePalettesToBuffer();
|
||||
CopyBufferToTexture();
|
||||
|
||||
// Reset modified palettes back to their original colors, ready for next time.
|
||||
foreach (var kvp in mutablePalettes)
|
||||
foreach (var kvp in modifiablePalettes)
|
||||
{
|
||||
var originalPalette = palettes[kvp.Key];
|
||||
var modifiedPalette = kvp.Value;
|
||||
@@ -150,7 +130,6 @@ namespace OpenRA.Graphics
|
||||
public void Dispose()
|
||||
{
|
||||
Texture.Dispose();
|
||||
ColorShifts.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
44
OpenRA.Game/Graphics/MappedImage.cs
Normal file
44
OpenRA.Game/Graphics/MappedImage.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
class MappedImage
|
||||
{
|
||||
readonly Rectangle rect = Rectangle.Empty;
|
||||
public readonly string Src;
|
||||
|
||||
public MappedImage(string defaultSrc, MiniYaml info)
|
||||
{
|
||||
FieldLoader.LoadField(this, "rect", info.Value);
|
||||
FieldLoader.Load(this, info);
|
||||
if (Src == null)
|
||||
Src = defaultSrc;
|
||||
}
|
||||
|
||||
public Sprite GetImage(Sheet s)
|
||||
{
|
||||
return new Sprite(s, rect, TextureChannel.Alpha);
|
||||
}
|
||||
|
||||
public MiniYaml Save(string defaultSrc)
|
||||
{
|
||||
var root = new List<MiniYamlNode>();
|
||||
if (defaultSrc != Src)
|
||||
root.Add(new MiniYamlNode("Src", Src));
|
||||
|
||||
return new MiniYaml(FieldSaver.FormatValue(this, GetType().GetField("rect")), root);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,94 +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;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public interface IModel
|
||||
{
|
||||
uint Frames { get; }
|
||||
uint Sections { get; }
|
||||
|
||||
float[] TransformationMatrix(uint section, uint frame);
|
||||
float[] Size { get; }
|
||||
float[] Bounds(uint frame);
|
||||
ModelRenderData RenderData(uint section);
|
||||
|
||||
/// <summary>Returns the smallest rectangle that covers all rotations of all frames in a model.</summary>
|
||||
Rectangle AggregateBounds { get; }
|
||||
}
|
||||
|
||||
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 IModelCache : IDisposable
|
||||
{
|
||||
IModel GetModel(string model);
|
||||
IModel GetModelSequence(string model, string sequence);
|
||||
bool HasModelSequence(string model, string sequence);
|
||||
IVertexBuffer<Vertex> VertexBuffer { get; }
|
||||
}
|
||||
|
||||
public interface IModelSequenceLoader
|
||||
{
|
||||
Action<string> OnMissingModelError { get; set; }
|
||||
IModelCache CacheModels(IReadOnlyFileSystem fileSystem, ModData modData, IReadOnlyDictionary<string, MiniYamlNode> modelDefinitions);
|
||||
}
|
||||
|
||||
public class PlaceholderModelSequenceLoader : IModelSequenceLoader
|
||||
{
|
||||
public Action<string> OnMissingModelError { get; set; }
|
||||
|
||||
class PlaceholderModelCache : IModelCache
|
||||
{
|
||||
public IVertexBuffer<Vertex> VertexBuffer => throw new NotImplementedException();
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public IModel GetModel(string model)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IModel GetModelSequence(string model, string sequence)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool HasModelSequence(string model, string sequence)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public PlaceholderModelSequenceLoader(ModData modData) { }
|
||||
|
||||
public IModelCache CacheModels(IReadOnlyFileSystem fileSystem, ModData modData, IReadOnlyDictionary<string, MiniYamlNode> modelDefinitions)
|
||||
{
|
||||
return new PlaceholderModelCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +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;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public readonly struct ModelAnimation
|
||||
{
|
||||
public readonly IModel Model;
|
||||
public readonly Func<WVec> OffsetFunc;
|
||||
public readonly Func<WRot> RotationFunc;
|
||||
public readonly Func<bool> DisableFunc;
|
||||
public readonly Func<uint> FrameFunc;
|
||||
public readonly bool ShowShadow;
|
||||
|
||||
public ModelAnimation(IModel model, Func<WVec> offset, Func<WRot> rotation, Func<bool> disable, Func<uint> frame, bool showshadow)
|
||||
{
|
||||
Model = model;
|
||||
OffsetFunc = offset;
|
||||
RotationFunc = rotation;
|
||||
DisableFunc = disable;
|
||||
FrameFunc = frame;
|
||||
ShowShadow = showshadow;
|
||||
}
|
||||
|
||||
public Rectangle ScreenBounds(WPos pos, WorldRenderer wr, float scale)
|
||||
{
|
||||
var r = Model.AggregateBounds;
|
||||
var offset = OffsetFunc != null ? OffsetFunc() : WVec.Zero;
|
||||
var xy = wr.ScreenPxPosition(pos) + wr.ScreenPxOffset(offset);
|
||||
|
||||
return Rectangle.FromLTRB(
|
||||
xy.X + (int)(r.Left * scale),
|
||||
xy.Y + (int)(r.Top * scale),
|
||||
xy.X + (int)(r.Right * scale),
|
||||
xy.Y + (int)(r.Bottom * scale));
|
||||
}
|
||||
|
||||
public bool IsVisible => DisableFunc == null || !DisableFunc();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -11,8 +11,11 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using OpenRA.Primitives;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
@@ -33,6 +36,35 @@ namespace OpenRA.Graphics
|
||||
return Color.FromArgb((int)palette[index]);
|
||||
}
|
||||
|
||||
public static ColorPalette AsSystemPalette(this IPalette palette)
|
||||
{
|
||||
ColorPalette pal;
|
||||
using (var b = new Bitmap(1, 1, PixelFormat.Format8bppIndexed))
|
||||
pal = b.Palette;
|
||||
|
||||
for (var i = 0; i < Size; i++)
|
||||
pal.Entries[i] = palette.GetColor(i);
|
||||
|
||||
// hack around a mono bug -- the palette flags get set wrong.
|
||||
if (Platform.CurrentPlatform != PlatformType.Windows)
|
||||
typeof(ColorPalette).GetField("flags",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic).SetValue(pal, 1);
|
||||
|
||||
return pal;
|
||||
}
|
||||
|
||||
public static Bitmap AsBitmap(this IPalette palette)
|
||||
{
|
||||
var b = new Bitmap(Size, 1, PixelFormat.Format32bppArgb);
|
||||
var data = b.LockBits(new Rectangle(0, 0, b.Width, b.Height),
|
||||
ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
|
||||
var temp = new uint[Size];
|
||||
palette.CopyToArray(temp, 0);
|
||||
Marshal.Copy((int[])(object)temp, 0, data.Scan0, Size);
|
||||
b.UnlockBits(data);
|
||||
return b;
|
||||
}
|
||||
|
||||
public static IPalette AsReadOnly(this IPalette palette)
|
||||
{
|
||||
if (palette is ImmutablePalette)
|
||||
@@ -42,10 +74,9 @@ namespace OpenRA.Graphics
|
||||
|
||||
class ReadOnlyPalette : IPalette
|
||||
{
|
||||
readonly IPalette palette;
|
||||
IPalette palette;
|
||||
public ReadOnlyPalette(IPalette palette) { this.palette = palette; }
|
||||
public uint this[int index] => palette[index];
|
||||
|
||||
public uint this[int index] { get { return palette[index]; } }
|
||||
public void CopyToArray(Array destination, int destinationOffset)
|
||||
{
|
||||
palette.CopyToArray(destination, destinationOffset);
|
||||
@@ -57,25 +88,28 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
readonly uint[] colors = new uint[Palette.Size];
|
||||
|
||||
public uint this[int index] => colors[index];
|
||||
public uint this[int index]
|
||||
{
|
||||
get { return colors[index]; }
|
||||
}
|
||||
|
||||
public void CopyToArray(Array destination, int destinationOffset)
|
||||
{
|
||||
Buffer.BlockCopy(colors, 0, destination, destinationOffset * 4, Palette.Size * 4);
|
||||
}
|
||||
|
||||
public ImmutablePalette(string filename, int[] remapTransparent, int[] remap)
|
||||
public ImmutablePalette(string filename, int[] remap)
|
||||
{
|
||||
using (var s = File.OpenRead(filename))
|
||||
LoadFromStream(s, remapTransparent, remap);
|
||||
LoadFromStream(s, remap);
|
||||
}
|
||||
|
||||
public ImmutablePalette(Stream s, int[] remapTransparent, int[] remapShadow)
|
||||
public ImmutablePalette(Stream s, int[] remapShadow)
|
||||
{
|
||||
LoadFromStream(s, remapTransparent, remapShadow);
|
||||
LoadFromStream(s, remapShadow);
|
||||
}
|
||||
|
||||
void LoadFromStream(Stream s, int[] remapTransparent, int[] remapShadow)
|
||||
void LoadFromStream(Stream s, int[] remapShadow)
|
||||
{
|
||||
using (var reader = new BinaryReader(s))
|
||||
for (var i = 0; i < Palette.Size; i++)
|
||||
@@ -83,18 +117,10 @@ namespace OpenRA.Graphics
|
||||
var r = (byte)(reader.ReadByte() << 2);
|
||||
var g = (byte)(reader.ReadByte() << 2);
|
||||
var b = (byte)(reader.ReadByte() << 2);
|
||||
|
||||
// Replicate high bits into the (currently zero) low bits.
|
||||
r |= (byte)(r >> 6);
|
||||
g |= (byte)(g >> 6);
|
||||
b |= (byte)(b >> 6);
|
||||
|
||||
colors[i] = (uint)((255 << 24) | (r << 16) | (g << 8) | b);
|
||||
}
|
||||
|
||||
foreach (var i in remapTransparent)
|
||||
colors[i] = 0;
|
||||
|
||||
colors[0] = 0; // Convert black background to transparency.
|
||||
foreach (var i in remapShadow)
|
||||
colors[i] = 140u << 24;
|
||||
}
|
||||
@@ -108,7 +134,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public ImmutablePalette(IPalette p)
|
||||
{
|
||||
for (var i = 0; i < Palette.Size; i++)
|
||||
for (int i = 0; i < Palette.Size; i++)
|
||||
colors[i] = p[i];
|
||||
}
|
||||
|
||||
@@ -126,8 +152,8 @@ namespace OpenRA.Graphics
|
||||
|
||||
public uint this[int index]
|
||||
{
|
||||
get => colors[index];
|
||||
set => colors[index] = value;
|
||||
get { return colors[index]; }
|
||||
set { colors[index] = value; }
|
||||
}
|
||||
|
||||
public void CopyToArray(Array destination, int destinationOffset)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -18,8 +18,8 @@ namespace OpenRA.Graphics
|
||||
|
||||
public readonly string Name;
|
||||
public IPalette Palette { get; internal set; }
|
||||
public float TextureIndex => index / hardwarePalette.Height;
|
||||
public float TextureMidIndex => (index + 0.5f) / hardwarePalette.Height;
|
||||
public float TextureIndex { get { return index / hardwarePalette.Height; } }
|
||||
public float TextureMidIndex { get { return (index + 0.5f) / hardwarePalette.Height; } }
|
||||
|
||||
public PaletteReference(string name, int index, IPalette palette, HardwarePalette hardwarePalette)
|
||||
{
|
||||
@@ -28,7 +28,5 @@ namespace OpenRA.Graphics
|
||||
this.index = index;
|
||||
this.hardwarePalette = hardwarePalette;
|
||||
}
|
||||
|
||||
public bool HasColorShift => hardwarePalette.HasColorShift(Name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -10,25 +10,15 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public enum GLProfile
|
||||
{
|
||||
Automatic,
|
||||
ANGLE,
|
||||
Modern,
|
||||
Embedded,
|
||||
Legacy
|
||||
}
|
||||
|
||||
public interface IPlatform
|
||||
{
|
||||
IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay, GLProfile profile, bool enableLegacyGL);
|
||||
IGraphicsDevice CreateGraphics(Size size, WindowMode windowMode);
|
||||
ISoundEngine CreateSound(string device);
|
||||
IFont CreateFont(byte[] data);
|
||||
}
|
||||
|
||||
public interface IHardwareCursor : IDisposable { }
|
||||
@@ -41,64 +31,42 @@ namespace OpenRA
|
||||
Subtractive,
|
||||
Multiply,
|
||||
Multiplicative,
|
||||
DoubleMultiplicative,
|
||||
LowAdditive,
|
||||
Screen,
|
||||
Translucent
|
||||
DoubleMultiplicative
|
||||
}
|
||||
|
||||
public interface IPlatformWindow : IDisposable
|
||||
public interface IGraphicsDevice : IDisposable
|
||||
{
|
||||
IGraphicsContext Context { get; }
|
||||
IVertexBuffer<Vertex> CreateVertexBuffer(int length);
|
||||
ITexture CreateTexture(Bitmap bitmap);
|
||||
ITexture CreateTexture();
|
||||
IFrameBuffer CreateFrameBuffer(Size s);
|
||||
IShader CreateShader(string name);
|
||||
|
||||
Size NativeWindowSize { get; }
|
||||
Size EffectiveWindowSize { get; }
|
||||
float NativeWindowScale { get; }
|
||||
float EffectiveWindowScale { get; }
|
||||
Size SurfaceSize { get; }
|
||||
int DisplayCount { get; }
|
||||
int CurrentDisplay { get; }
|
||||
bool HasInputFocus { get; }
|
||||
bool IsSuspended { get; }
|
||||
|
||||
event Action<float, float, float, float> OnWindowScaleChanged;
|
||||
Size WindowSize { get; }
|
||||
|
||||
void Clear();
|
||||
void Present();
|
||||
Bitmap TakeScreenshot();
|
||||
void PumpInput(IInputHandler inputHandler);
|
||||
string GetClipboardText();
|
||||
bool SetClipboardText(string text);
|
||||
void DrawPrimitives(PrimitiveType type, int firstVertex, int numVertices);
|
||||
|
||||
void EnableScissor(int left, int top, int width, int height);
|
||||
void DisableScissor();
|
||||
|
||||
void EnableDepthBuffer();
|
||||
void DisableDepthBuffer();
|
||||
void ClearDepthBuffer();
|
||||
|
||||
void SetBlendMode(BlendMode mode);
|
||||
|
||||
void GrabWindowMouseFocus();
|
||||
void ReleaseWindowMouseFocus();
|
||||
|
||||
IHardwareCursor CreateHardwareCursor(string name, Size size, byte[] data, int2 hotspot, bool pixelDouble);
|
||||
IHardwareCursor CreateHardwareCursor(string name, Size size, byte[] data, int2 hotspot);
|
||||
void SetHardwareCursor(IHardwareCursor cursor);
|
||||
void SetWindowTitle(string title);
|
||||
void SetRelativeMouseMode(bool mode);
|
||||
void SetScaleModifier(float scale);
|
||||
|
||||
GLProfile GLProfile { get; }
|
||||
|
||||
GLProfile[] SupportedGLProfiles { get; }
|
||||
}
|
||||
|
||||
public interface IGraphicsContext : IDisposable
|
||||
{
|
||||
IVertexBuffer<Vertex> CreateVertexBuffer(int size);
|
||||
Vertex[] CreateVertices(int size);
|
||||
ITexture CreateTexture();
|
||||
IFrameBuffer CreateFrameBuffer(Size s);
|
||||
IFrameBuffer CreateFrameBuffer(Size s, Color clearColor);
|
||||
IShader CreateShader(string name);
|
||||
void EnableScissor(int x, int y, int width, int height);
|
||||
void DisableScissor();
|
||||
void Present();
|
||||
void DrawPrimitives(PrimitiveType pt, int firstVertex, int numVertices);
|
||||
void Clear();
|
||||
void EnableDepthBuffer();
|
||||
void DisableDepthBuffer();
|
||||
void ClearDepthBuffer();
|
||||
void SetBlendMode(BlendMode mode);
|
||||
void SetVSyncEnabled(bool enabled);
|
||||
string GLVersion { get; }
|
||||
}
|
||||
|
||||
@@ -106,12 +74,8 @@ namespace OpenRA
|
||||
{
|
||||
void Bind();
|
||||
void SetData(T[] vertices, int length);
|
||||
|
||||
/// <summary>
|
||||
/// Upon return `vertices` may reference another array object of at least the same size - containing random values.
|
||||
/// </summary>
|
||||
void SetData(ref T[] vertices, int length);
|
||||
void SetData(T[] vertices, int offset, int start, int length);
|
||||
void SetData(T[] vertices, int start, int length);
|
||||
void SetData(IntPtr data, int start, int length);
|
||||
}
|
||||
|
||||
public interface IShader
|
||||
@@ -123,15 +87,16 @@ namespace OpenRA
|
||||
void SetVec(string name, float[] vec, int length);
|
||||
void SetTexture(string param, ITexture texture);
|
||||
void SetMatrix(string param, float[] mtx);
|
||||
void PrepareRender();
|
||||
void Render(Action a);
|
||||
}
|
||||
|
||||
public enum TextureScaleFilter { Nearest, Linear }
|
||||
|
||||
public interface ITexture : IDisposable
|
||||
{
|
||||
void SetData(Bitmap bitmap);
|
||||
void SetData(uint[,] colors);
|
||||
void SetData(byte[] colors, int width, int height);
|
||||
void SetFloatData(float[] data, int width, int height);
|
||||
byte[] GetData();
|
||||
Size Size { get; }
|
||||
TextureScaleFilter ScaleFilter { get; set; }
|
||||
@@ -141,8 +106,6 @@ namespace OpenRA
|
||||
{
|
||||
void Bind();
|
||||
void Unbind();
|
||||
void EnableScissor(Rectangle rect);
|
||||
void DisableScissor();
|
||||
ITexture Texture { get; }
|
||||
}
|
||||
|
||||
@@ -153,7 +116,7 @@ namespace OpenRA
|
||||
TriangleList,
|
||||
}
|
||||
|
||||
public readonly struct Range<T>
|
||||
public struct Range<T>
|
||||
{
|
||||
public readonly T Start, End;
|
||||
public Range(T start, T end) { Start = start; End = end; }
|
||||
@@ -165,17 +128,4 @@ namespace OpenRA
|
||||
Fullscreen,
|
||||
PseudoFullscreen,
|
||||
}
|
||||
|
||||
public interface IFont : IDisposable
|
||||
{
|
||||
FontGlyph CreateGlyph(char c, int size, float deviceScale);
|
||||
}
|
||||
|
||||
public struct FontGlyph
|
||||
{
|
||||
public int2 Offset;
|
||||
public Size Size;
|
||||
public float Advance;
|
||||
public byte[] Data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -10,6 +10,8 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
@@ -17,37 +19,40 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
public class PlayerColorRemap : IPaletteRemap
|
||||
{
|
||||
readonly int[] remapIndices;
|
||||
readonly float hue;
|
||||
readonly float saturation;
|
||||
readonly float value;
|
||||
Dictionary<int, Color> remapColors;
|
||||
|
||||
public PlayerColorRemap(int[] remapIndices, Color color)
|
||||
public static int GetRemapIndex(int[] ramp, int i)
|
||||
{
|
||||
this.remapIndices = remapIndices;
|
||||
return ramp[i];
|
||||
}
|
||||
|
||||
var (r, g, b) = color.ToLinear();
|
||||
(hue, saturation, value) = Color.RgbToHsv(r, g, b);
|
||||
public PlayerColorRemap(int[] ramp, HSLColor c, float rampFraction)
|
||||
{
|
||||
// Increase luminosity if required to represent the full ramp
|
||||
var rampRange = (byte)((1 - rampFraction) * c.L);
|
||||
var c1 = new HSLColor(c.H, c.S, Math.Max(rampRange, c.L)).RGB;
|
||||
var c2 = new HSLColor(c.H, c.S, (byte)Math.Max(0, c.L - rampRange)).RGB;
|
||||
var baseIndex = ramp[0];
|
||||
var remapRamp = ramp.Select(r => r - ramp[0]);
|
||||
var rampMaxIndex = ramp.Length - 1;
|
||||
|
||||
// reversed remapping
|
||||
if (ramp[0] > ramp[rampMaxIndex])
|
||||
{
|
||||
baseIndex = ramp[rampMaxIndex];
|
||||
for (var i = rampMaxIndex; i > 0; i--)
|
||||
remapRamp = ramp.Select(r => r - ramp[rampMaxIndex]);
|
||||
}
|
||||
|
||||
remapColors = remapRamp.Select((x, i) => Pair.New(baseIndex + i, Exts.ColorLerp(x / (float)ramp.Length, c1, c2)))
|
||||
.ToDictionary(u => u.First, u => u.Second);
|
||||
}
|
||||
|
||||
public Color GetRemappedColor(Color original, int index)
|
||||
{
|
||||
if (!remapIndices.Contains(index))
|
||||
return original;
|
||||
|
||||
// Color remapping is applied in a linear color space, so start
|
||||
// by undoing the pre-multiplied alpha and gamma corrections
|
||||
var (r, g, b) = original.ToLinear();
|
||||
|
||||
// Calculate the brightness (i.e HSV value) of the original colour
|
||||
// This inlines the single line of Color.RgbToHsv() that we need
|
||||
var value = Math.Max(Math.Max(r, g), b);
|
||||
|
||||
// Construct the new RGB color
|
||||
(r, g, b) = Color.HsvToRgb(hue, saturation, value * this.value);
|
||||
|
||||
// Convert linear back to SRGB and pre-multiply by the alpha
|
||||
return Color.FromLinear(original.A, r, g, b);
|
||||
Color c;
|
||||
return remapColors.TryGetValue(index, out c)
|
||||
? c : original;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -9,48 +9,25 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.Primitives;
|
||||
using System.Drawing;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public interface IRenderable
|
||||
{
|
||||
WPos Pos { get; }
|
||||
PaletteReference Palette { get; }
|
||||
int ZOffset { get; }
|
||||
bool IsDecoration { get; }
|
||||
|
||||
IRenderable WithPalette(PaletteReference newPalette);
|
||||
IRenderable WithZOffset(int newOffset);
|
||||
IRenderable OffsetBy(in WVec offset);
|
||||
IRenderable OffsetBy(WVec offset);
|
||||
IRenderable AsDecoration();
|
||||
|
||||
IFinalizedRenderable PrepareRender(WorldRenderer wr);
|
||||
}
|
||||
|
||||
public interface IPalettedRenderable : IRenderable
|
||||
{
|
||||
PaletteReference Palette { get; }
|
||||
IPalettedRenderable WithPalette(PaletteReference newPalette);
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum TintModifiers
|
||||
{
|
||||
None = 0,
|
||||
IgnoreWorldTint = 1,
|
||||
ReplaceColor = 2
|
||||
}
|
||||
|
||||
public interface IModifyableRenderable : IRenderable
|
||||
{
|
||||
float Alpha { get; }
|
||||
float3 Tint { get; }
|
||||
TintModifiers TintModifiers { get; }
|
||||
|
||||
IModifyableRenderable WithAlpha(float newAlpha);
|
||||
IModifyableRenderable WithTint(in float3 newTint, TintModifiers newTintModifiers);
|
||||
}
|
||||
|
||||
public interface IFinalizedRenderable
|
||||
{
|
||||
void Render(WorldRenderer wr);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -11,25 +11,49 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public class RgbaColorRenderer
|
||||
public class RgbaColorRenderer : Renderer.IBatchRenderer
|
||||
{
|
||||
static readonly float3 Offset = new(0.5f, 0.5f, 0f);
|
||||
static readonly float2 Offset = new float2(0.5f, 0.5f);
|
||||
|
||||
readonly SpriteRenderer parent;
|
||||
readonly Vertex[] vertices = new Vertex[6];
|
||||
readonly Renderer renderer;
|
||||
readonly IShader shader;
|
||||
readonly Action renderAction;
|
||||
|
||||
public RgbaColorRenderer(SpriteRenderer parent)
|
||||
readonly Vertex[] vertices;
|
||||
int nv = 0;
|
||||
|
||||
public RgbaColorRenderer(Renderer renderer, IShader shader)
|
||||
{
|
||||
this.parent = parent;
|
||||
this.renderer = renderer;
|
||||
this.shader = shader;
|
||||
vertices = new Vertex[renderer.TempBufferSize];
|
||||
renderAction = () => renderer.DrawBatch(vertices, nv, PrimitiveType.TriangleList);
|
||||
}
|
||||
|
||||
public void DrawLine(in float3 start, in float3 end, float width, Color startColor, Color endColor, BlendMode blendMode = BlendMode.Alpha)
|
||||
public void Flush()
|
||||
{
|
||||
if (nv > 0)
|
||||
{
|
||||
renderer.Device.SetBlendMode(BlendMode.Alpha);
|
||||
shader.Render(renderAction);
|
||||
renderer.Device.SetBlendMode(BlendMode.None);
|
||||
|
||||
nv = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawLine(float3 start, float3 end, float width, Color startColor, Color endColor)
|
||||
{
|
||||
renderer.CurrentBatchRenderer = this;
|
||||
|
||||
if (nv + 6 > renderer.TempBufferSize)
|
||||
Flush();
|
||||
|
||||
var delta = (end - start) / (end - start).XY.Length;
|
||||
var corner = width / 2 * new float3(-delta.Y, delta.X, delta.Z);
|
||||
|
||||
@@ -45,18 +69,21 @@ namespace OpenRA.Graphics
|
||||
var eb = endColor.B / 255.0f;
|
||||
var ea = endColor.A / 255.0f;
|
||||
|
||||
vertices[0] = new Vertex(start - corner + Offset, sr, sg, sb, sa, 0, 0);
|
||||
vertices[1] = new Vertex(start + corner + Offset, sr, sg, sb, sa, 0, 0);
|
||||
vertices[2] = new Vertex(end + corner + Offset, er, eg, eb, ea, 0, 0);
|
||||
vertices[3] = new Vertex(end + corner + Offset, er, eg, eb, ea, 0, 0);
|
||||
vertices[4] = new Vertex(end - corner + Offset, er, eg, eb, ea, 0, 0);
|
||||
vertices[5] = new Vertex(start - corner + Offset, sr, sg, sb, sa, 0, 0);
|
||||
|
||||
parent.DrawRGBAVertices(vertices, blendMode);
|
||||
vertices[nv++] = new Vertex(start - corner + Offset, sr, sg, sb, sa, 0, 0);
|
||||
vertices[nv++] = new Vertex(start + corner + Offset, sr, sg, sb, sa, 0, 0);
|
||||
vertices[nv++] = new Vertex(end + corner + Offset, er, eg, eb, ea, 0, 0);
|
||||
vertices[nv++] = new Vertex(end + corner + Offset, er, eg, eb, ea, 0, 0);
|
||||
vertices[nv++] = new Vertex(end - corner + Offset, er, eg, eb, ea, 0, 0);
|
||||
vertices[nv++] = new Vertex(start - corner + Offset, sr, sg, sb, sa, 0, 0);
|
||||
}
|
||||
|
||||
public void DrawLine(in float3 start, in float3 end, float width, Color color, BlendMode blendMode = BlendMode.Alpha)
|
||||
public void DrawLine(float3 start, float3 end, float width, Color color)
|
||||
{
|
||||
renderer.CurrentBatchRenderer = this;
|
||||
|
||||
if (nv + 6 > renderer.TempBufferSize)
|
||||
Flush();
|
||||
|
||||
var delta = (end - start) / (end - start).XY.Length;
|
||||
var corner = width / 2 * new float2(-delta.Y, delta.X);
|
||||
|
||||
@@ -66,21 +93,20 @@ namespace OpenRA.Graphics
|
||||
var b = color.B / 255.0f;
|
||||
var a = color.A / 255.0f;
|
||||
|
||||
vertices[0] = new Vertex(start - corner + Offset, r, g, b, a, 0, 0);
|
||||
vertices[1] = new Vertex(start + corner + Offset, r, g, b, a, 0, 0);
|
||||
vertices[2] = new Vertex(end + corner + Offset, r, g, b, a, 0, 0);
|
||||
vertices[3] = new Vertex(end + corner + Offset, r, g, b, a, 0, 0);
|
||||
vertices[4] = new Vertex(end - corner + Offset, r, g, b, a, 0, 0);
|
||||
vertices[5] = new Vertex(start - corner + Offset, r, g, b, a, 0, 0);
|
||||
parent.DrawRGBAVertices(vertices, blendMode);
|
||||
vertices[nv++] = new Vertex(start - corner + Offset, r, g, b, a, 0, 0);
|
||||
vertices[nv++] = new Vertex(start + corner + Offset, r, g, b, a, 0, 0);
|
||||
vertices[nv++] = new Vertex(end + corner + Offset, r, g, b, a, 0, 0);
|
||||
vertices[nv++] = new Vertex(end + corner + Offset, r, g, b, a, 0, 0);
|
||||
vertices[nv++] = new Vertex(end - corner + Offset, r, g, b, a, 0, 0);
|
||||
vertices[nv++] = new Vertex(start - corner + Offset, r, g, b, a, 0, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the 2D intersection of two lines.
|
||||
/// Will behave badly if the lines are parallel.
|
||||
/// Z position is the average of a and b (ignores actual intersection point if it exists).
|
||||
/// Z position is the average of a and b (ignores actual intersection point if it exists)
|
||||
/// </summary>
|
||||
float3 IntersectionOf(in float3 a, in float3 da, in float3 b, in float3 db)
|
||||
float3 IntersectionOf(float3 a, float3 da, float3 b, float3 db)
|
||||
{
|
||||
var crossA = a.X * (a.Y + da.Y) - a.Y * (a.X + da.X);
|
||||
var crossB = b.X * (b.Y + db.Y) - b.Y * (b.X + db.X);
|
||||
@@ -90,7 +116,7 @@ namespace OpenRA.Graphics
|
||||
return new float3(x / d, y / d, 0.5f * (a.Z + b.Z));
|
||||
}
|
||||
|
||||
void DrawDisconnectedLine(IEnumerable<float3> points, float width, Color color, BlendMode blendMode)
|
||||
void DrawDisconnectedLine(IEnumerable<float3> points, float width, Color color)
|
||||
{
|
||||
using (var e = points.GetEnumerator())
|
||||
{
|
||||
@@ -101,13 +127,13 @@ namespace OpenRA.Graphics
|
||||
while (e.MoveNext())
|
||||
{
|
||||
var point = e.Current;
|
||||
DrawLine(lastPoint, point, width, color, blendMode);
|
||||
DrawLine(lastPoint, point, width, color);
|
||||
lastPoint = point;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DrawConnectedLine(float3[] points, float width, Color color, bool closed, BlendMode blendMode)
|
||||
void DrawConnectedLine(float3[] points, float width, Color color, bool closed)
|
||||
{
|
||||
// Not a line
|
||||
if (points.Length < 2)
|
||||
@@ -116,10 +142,11 @@ namespace OpenRA.Graphics
|
||||
// Single segment
|
||||
if (points.Length == 2)
|
||||
{
|
||||
DrawLine(points[0], points[1], width, color, blendMode);
|
||||
DrawLine(points[0], points[1], width, color);
|
||||
return;
|
||||
}
|
||||
|
||||
renderer.CurrentBatchRenderer = this;
|
||||
color = Util.PremultiplyAlpha(color);
|
||||
var r = color.R / 255.0f;
|
||||
var g = color.G / 255.0f;
|
||||
@@ -138,7 +165,7 @@ namespace OpenRA.Graphics
|
||||
// Segment is part of closed loop
|
||||
if (closed)
|
||||
{
|
||||
var prev = points[^1];
|
||||
var prev = points[points.Length - 1];
|
||||
var prevDir = (start - prev) / (start - prev).XY.Length;
|
||||
var prevCorner = width / 2 * new float3(-prevDir.Y, prevDir.X, prevDir.Z);
|
||||
ca = IntersectionOf(start - prevCorner, prevDir, start - corner, dir);
|
||||
@@ -153,17 +180,19 @@ namespace OpenRA.Graphics
|
||||
var nextCorner = width / 2 * new float3(-nextDir.Y, nextDir.X, nextDir.Z);
|
||||
|
||||
// Vertices for the corners joining start-end to end-next
|
||||
var cc = closed || i < limit - 1 ? IntersectionOf(end + corner, dir, end + nextCorner, nextDir) : end + corner;
|
||||
var cd = closed || i < limit - 1 ? IntersectionOf(end - corner, dir, end - nextCorner, nextDir) : end - corner;
|
||||
var cc = closed || i < limit ? IntersectionOf(end + corner, dir, end + nextCorner, nextDir) : end + corner;
|
||||
var cd = closed || i < limit ? IntersectionOf(end - corner, dir, end - nextCorner, nextDir) : end - corner;
|
||||
|
||||
// Fill segment
|
||||
vertices[0] = new Vertex(ca + Offset, r, g, b, a, 0, 0);
|
||||
vertices[1] = new Vertex(cb + Offset, r, g, b, a, 0, 0);
|
||||
vertices[2] = new Vertex(cc + Offset, r, g, b, a, 0, 0);
|
||||
vertices[3] = new Vertex(cc + Offset, r, g, b, a, 0, 0);
|
||||
vertices[4] = new Vertex(cd + Offset, r, g, b, a, 0, 0);
|
||||
vertices[5] = new Vertex(ca + Offset, r, g, b, a, 0, 0);
|
||||
parent.DrawRGBAVertices(vertices, blendMode);
|
||||
if (nv + 6 > renderer.TempBufferSize)
|
||||
Flush();
|
||||
|
||||
vertices[nv++] = new Vertex(ca + Offset, r, g, b, a, 0, 0);
|
||||
vertices[nv++] = new Vertex(cb + Offset, r, g, b, a, 0, 0);
|
||||
vertices[nv++] = new Vertex(cc + Offset, r, g, b, a, 0, 0);
|
||||
vertices[nv++] = new Vertex(cc + Offset, r, g, b, a, 0, 0);
|
||||
vertices[nv++] = new Vertex(cd + Offset, r, g, b, a, 0, 0);
|
||||
vertices[nv++] = new Vertex(ca + Offset, r, g, b, a, 0, 0);
|
||||
|
||||
// Advance line segment
|
||||
end = next;
|
||||
@@ -175,93 +204,65 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawLine(IEnumerable<float3> points, float width, Color color, bool connectSegments = false, BlendMode blendMode = BlendMode.Alpha)
|
||||
public void DrawLine(IEnumerable<float2> points, float width, Color color, bool connectSegments = false)
|
||||
{
|
||||
DrawLine(points.Select(p => new float3(p, 0)), width, color, connectSegments);
|
||||
}
|
||||
|
||||
public void DrawLine(IEnumerable<float3> points, float width, Color color, bool connectSegments = false)
|
||||
{
|
||||
if (!connectSegments)
|
||||
DrawDisconnectedLine(points, width, color, blendMode);
|
||||
DrawDisconnectedLine(points, width, color);
|
||||
else
|
||||
DrawConnectedLine(points as float3[] ?? points.ToArray(), width, color, false, blendMode);
|
||||
DrawConnectedLine(points as float3[] ?? points.ToArray(), width, color, false);
|
||||
}
|
||||
|
||||
public void DrawPolygon(float3[] vertices, float width, Color color, BlendMode blendMode = BlendMode.Alpha)
|
||||
public void DrawPolygon(float3[] vertices, float width, Color color)
|
||||
{
|
||||
DrawConnectedLine(vertices, width, color, true, blendMode);
|
||||
DrawConnectedLine(vertices, width, color, true);
|
||||
}
|
||||
|
||||
public void DrawPolygon(float2[] vertices, float width, Color color, BlendMode blendMode = BlendMode.Alpha)
|
||||
public void DrawPolygon(float2[] vertices, float width, Color color)
|
||||
{
|
||||
DrawConnectedLine(vertices.Select(v => new float3(v, 0)).ToArray(), width, color, true, blendMode);
|
||||
DrawConnectedLine(vertices.Select(v => new float3(v, 0)).ToArray(), width, color, true);
|
||||
}
|
||||
|
||||
public void DrawRect(in float3 tl, in float3 br, float width, Color color, BlendMode blendMode = BlendMode.Alpha)
|
||||
public void DrawRect(float3 tl, float3 br, float width, Color color)
|
||||
{
|
||||
var tr = new float3(br.X, tl.Y, tl.Z);
|
||||
var bl = new float3(tl.X, br.Y, br.Z);
|
||||
DrawPolygon(new[] { tl, tr, br, bl }, width, color, blendMode);
|
||||
DrawPolygon(new[] { tl, tr, br, bl }, width, color);
|
||||
}
|
||||
|
||||
public void FillTriangle(in float3 a, in float3 b, in float3 c, Color color, BlendMode blendMode = BlendMode.Alpha)
|
||||
{
|
||||
color = Util.PremultiplyAlpha(color);
|
||||
var cr = color.R / 255.0f;
|
||||
var cg = color.G / 255.0f;
|
||||
var cb = color.B / 255.0f;
|
||||
var ca = color.A / 255.0f;
|
||||
|
||||
vertices[0] = new Vertex(a + Offset, cr, cg, cb, ca, 0, 0);
|
||||
vertices[1] = new Vertex(b + Offset, cr, cg, cb, ca, 0, 0);
|
||||
vertices[2] = new Vertex(c + Offset, cr, cg, cb, ca, 0, 0);
|
||||
parent.DrawRGBAVertices(vertices, blendMode);
|
||||
}
|
||||
|
||||
public void FillRect(in float3 tl, in float3 br, Color color, BlendMode blendMode = BlendMode.Alpha)
|
||||
public void FillRect(float3 tl, float3 br, Color color)
|
||||
{
|
||||
var tr = new float3(br.X, tl.Y, tl.Z);
|
||||
var bl = new float3(tl.X, br.Y, br.Z);
|
||||
FillRect(tl, tr, br, bl, color, blendMode);
|
||||
FillRect(tl, tr, br, bl, color);
|
||||
}
|
||||
|
||||
public void FillRect(in float3 a, in float3 b, in float3 c, in float3 d, Color color, BlendMode blendMode = BlendMode.Alpha)
|
||||
public void FillRect(float3 a, float3 b, float3 c, float3 d, Color color)
|
||||
{
|
||||
renderer.CurrentBatchRenderer = this;
|
||||
|
||||
if (nv + 6 > renderer.TempBufferSize)
|
||||
Flush();
|
||||
|
||||
color = Util.PremultiplyAlpha(color);
|
||||
var cr = color.R / 255.0f;
|
||||
var cg = color.G / 255.0f;
|
||||
var cb = color.B / 255.0f;
|
||||
var ca = color.A / 255.0f;
|
||||
|
||||
vertices[0] = new Vertex(a + Offset, cr, cg, cb, ca, 0, 0);
|
||||
vertices[1] = new Vertex(b + Offset, cr, cg, cb, ca, 0, 0);
|
||||
vertices[2] = new Vertex(c + Offset, cr, cg, cb, ca, 0, 0);
|
||||
vertices[3] = new Vertex(c + Offset, cr, cg, cb, ca, 0, 0);
|
||||
vertices[4] = new Vertex(d + Offset, cr, cg, cb, ca, 0, 0);
|
||||
vertices[5] = new Vertex(a + Offset, cr, cg, cb, ca, 0, 0);
|
||||
parent.DrawRGBAVertices(vertices, blendMode);
|
||||
vertices[nv++] = new Vertex(a + Offset, cr, cg, cb, ca, 0, 0);
|
||||
vertices[nv++] = new Vertex(b + Offset, cr, cg, cb, ca, 0, 0);
|
||||
vertices[nv++] = new Vertex(c + Offset, cr, cg, cb, ca, 0, 0);
|
||||
vertices[nv++] = new Vertex(c + Offset, cr, cg, cb, ca, 0, 0);
|
||||
vertices[nv++] = new Vertex(d + Offset, cr, cg, cb, ca, 0, 0);
|
||||
vertices[nv++] = new Vertex(a + Offset, cr, cg, cb, ca, 0, 0);
|
||||
}
|
||||
|
||||
public void FillRect(in float3 a, in float3 b, in float3 c, in float3 d, Color topLeftColor, Color topRightColor, Color bottomRightColor, Color bottomLeftColor, BlendMode blendMode = BlendMode.Alpha)
|
||||
{
|
||||
vertices[0] = VertexWithColor(a + Offset, topLeftColor);
|
||||
vertices[1] = VertexWithColor(b + Offset, topRightColor);
|
||||
vertices[2] = VertexWithColor(c + Offset, bottomRightColor);
|
||||
vertices[3] = VertexWithColor(c + Offset, bottomRightColor);
|
||||
vertices[4] = VertexWithColor(d + Offset, bottomLeftColor);
|
||||
vertices[5] = VertexWithColor(a + Offset, topLeftColor);
|
||||
|
||||
parent.DrawRGBAVertices(vertices, blendMode);
|
||||
}
|
||||
|
||||
static Vertex VertexWithColor(in float3 xyz, Color color)
|
||||
{
|
||||
color = Util.PremultiplyAlpha(color);
|
||||
var cr = color.R / 255.0f;
|
||||
var cg = color.G / 255.0f;
|
||||
var cb = color.B / 255.0f;
|
||||
var ca = color.A / 255.0f;
|
||||
|
||||
return new Vertex(xyz, cr, cg, cb, ca, 0, 0);
|
||||
}
|
||||
|
||||
public void FillEllipse(in float3 tl, in float3 br, Color color, BlendMode blendMode = BlendMode.Alpha)
|
||||
public void FillEllipse(float3 tl, float3 br, Color color, int vertices = 32)
|
||||
{
|
||||
// TODO: Create an ellipse polygon instead
|
||||
var a = (br.X - tl.X) / 2;
|
||||
@@ -272,8 +273,23 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
var z = float2.Lerp(tl.Z, br.Z, (y - tl.Y) / (br.Y - tl.Y));
|
||||
var dx = a * (float)Math.Sqrt(1 - (y - yc) * (y - yc) / b / b);
|
||||
DrawLine(new float3(xc - dx, y, z), new float3(xc + dx, y, z), 1, color, blendMode);
|
||||
DrawLine(new float3(xc - dx, y, z), new float3(xc + dx, y, z), 1, color);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetViewportParams(Size screen, float depthScale, float depthOffset, float zoom, int2 scroll)
|
||||
{
|
||||
shader.SetVec("Scroll", scroll.X, scroll.Y, scroll.Y);
|
||||
shader.SetVec("r1",
|
||||
zoom * 2f / screen.Width,
|
||||
-zoom * 2f / screen.Height,
|
||||
-depthScale * zoom / screen.Height);
|
||||
shader.SetVec("r2", -1, 1, 1 - depthOffset);
|
||||
}
|
||||
|
||||
public void SetDepthPreviewEnabled(bool enabled)
|
||||
{
|
||||
shader.SetBool("EnableDepthPreview", enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +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;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public class RgbaSpriteRenderer
|
||||
{
|
||||
readonly SpriteRenderer parent;
|
||||
|
||||
public RgbaSpriteRenderer(SpriteRenderer parent)
|
||||
{
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, in float3 location, in float3 scale, float rotation = 0f)
|
||||
{
|
||||
if (s.Channel != TextureChannel.RGBA)
|
||||
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
|
||||
|
||||
parent.DrawSprite(s, 0, location, scale, rotation);
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, in float3 location, float scale = 1f, float rotation = 0f)
|
||||
{
|
||||
if (s.Channel != TextureChannel.RGBA)
|
||||
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
|
||||
|
||||
parent.DrawSprite(s, 0, location, scale, rotation);
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, in float3 location, float scale, in float3 tint, float alpha, float rotation = 0f)
|
||||
{
|
||||
if (s.Channel != TextureChannel.RGBA)
|
||||
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
|
||||
|
||||
parent.DrawSprite(s, 0, location, scale, tint, alpha, rotation);
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint, float alpha)
|
||||
{
|
||||
if (s.Channel != TextureChannel.RGBA)
|
||||
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
|
||||
|
||||
parent.DrawSprite(s, 0, a, b, c, d, tint, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
168
OpenRA.Game/Graphics/SelectionBarsRenderable.cs
Normal file
168
OpenRA.Game/Graphics/SelectionBarsRenderable.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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.Drawing;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public struct SelectionBarsRenderable : IRenderable, IFinalizedRenderable
|
||||
{
|
||||
readonly WPos pos;
|
||||
readonly Actor actor;
|
||||
readonly bool displayHealth;
|
||||
readonly bool displayExtra;
|
||||
|
||||
public SelectionBarsRenderable(Actor actor, bool displayHealth, bool displayExtra)
|
||||
: this(actor.CenterPosition, actor)
|
||||
{
|
||||
this.displayHealth = displayHealth;
|
||||
this.displayExtra = displayExtra;
|
||||
}
|
||||
|
||||
public SelectionBarsRenderable(WPos pos, Actor actor)
|
||||
: this()
|
||||
{
|
||||
this.pos = pos;
|
||||
this.actor = actor;
|
||||
}
|
||||
|
||||
public WPos Pos { get { return pos; } }
|
||||
public bool DisplayHealth { get { return displayHealth; } }
|
||||
public bool DisplayExtra { get { return displayExtra; } }
|
||||
|
||||
public PaletteReference Palette { get { return null; } }
|
||||
public int ZOffset { get { return 0; } }
|
||||
public bool IsDecoration { get { return true; } }
|
||||
|
||||
public IRenderable WithPalette(PaletteReference newPalette) { return this; }
|
||||
public IRenderable WithZOffset(int newOffset) { return this; }
|
||||
public IRenderable OffsetBy(WVec vec) { return new SelectionBarsRenderable(pos + vec, actor); }
|
||||
public IRenderable AsDecoration() { return this; }
|
||||
|
||||
void DrawExtraBars(WorldRenderer wr, float3 start, float3 end)
|
||||
{
|
||||
foreach (var extraBar in actor.TraitsImplementing<ISelectionBar>())
|
||||
{
|
||||
var value = extraBar.GetValue();
|
||||
if (value != 0 || extraBar.DisplayWhenEmpty)
|
||||
{
|
||||
var offset = new float3(0, (int)(4 / wr.Viewport.Zoom), 0);
|
||||
start += offset;
|
||||
end += offset;
|
||||
DrawSelectionBar(wr, start, end, extraBar.GetValue(), extraBar.GetColor());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DrawSelectionBar(WorldRenderer wr, float3 start, float3 end, float value, Color barColor)
|
||||
{
|
||||
var iz = 1 / wr.Viewport.Zoom;
|
||||
var c = Color.FromArgb(128, 30, 30, 30);
|
||||
var c2 = Color.FromArgb(128, 10, 10, 10);
|
||||
var p = new float2(0, -4 * iz);
|
||||
var q = new float2(0, -3 * iz);
|
||||
var r = new float2(0, -2 * iz);
|
||||
|
||||
var barColor2 = Color.FromArgb(255, barColor.R / 2, barColor.G / 2, barColor.B / 2);
|
||||
|
||||
var z = float3.Lerp(start, end, value);
|
||||
var wcr = Game.Renderer.WorldRgbaColorRenderer;
|
||||
wcr.DrawLine(start + p, end + p, iz, c);
|
||||
wcr.DrawLine(start + q, end + q, iz, c2);
|
||||
wcr.DrawLine(start + r, end + r, iz, c);
|
||||
|
||||
wcr.DrawLine(start + p, z + p, iz, barColor2);
|
||||
wcr.DrawLine(start + q, z + q, iz, barColor);
|
||||
wcr.DrawLine(start + r, z + r, iz, barColor2);
|
||||
}
|
||||
|
||||
Color GetHealthColor(IHealth health)
|
||||
{
|
||||
if (Game.Settings.Game.UsePlayerStanceColors)
|
||||
return actor.Owner.PlayerStanceColor(actor);
|
||||
else
|
||||
return health.DamageState == DamageState.Critical ? Color.Red :
|
||||
health.DamageState == DamageState.Heavy ? Color.Yellow : Color.LimeGreen;
|
||||
}
|
||||
|
||||
void DrawHealthBar(WorldRenderer wr, IHealth health, float3 start, float3 end)
|
||||
{
|
||||
if (health == null || health.IsDead)
|
||||
return;
|
||||
|
||||
var c = Color.FromArgb(128, 30, 30, 30);
|
||||
var c2 = Color.FromArgb(128, 10, 10, 10);
|
||||
var iz = 1 / wr.Viewport.Zoom;
|
||||
var p = new float2(0, -4 * iz);
|
||||
var q = new float2(0, -3 * iz);
|
||||
var r = new float2(0, -2 * iz);
|
||||
|
||||
var healthColor = GetHealthColor(health);
|
||||
var healthColor2 = Color.FromArgb(
|
||||
255,
|
||||
healthColor.R / 2,
|
||||
healthColor.G / 2,
|
||||
healthColor.B / 2);
|
||||
|
||||
var z = float3.Lerp(start, end, (float)health.HP / health.MaxHP);
|
||||
|
||||
var wcr = Game.Renderer.WorldRgbaColorRenderer;
|
||||
wcr.DrawLine(start + p, end + p, iz, c);
|
||||
wcr.DrawLine(start + q, end + q, iz, c2);
|
||||
wcr.DrawLine(start + r, end + r, iz, c);
|
||||
|
||||
wcr.DrawLine(start + p, z + p, iz, healthColor2);
|
||||
wcr.DrawLine(start + q, z + q, iz, healthColor);
|
||||
wcr.DrawLine(start + r, z + r, iz, healthColor2);
|
||||
|
||||
if (health.DisplayHP != health.HP)
|
||||
{
|
||||
var deltaColor = Color.OrangeRed;
|
||||
var deltaColor2 = Color.FromArgb(
|
||||
255,
|
||||
deltaColor.R / 2,
|
||||
deltaColor.G / 2,
|
||||
deltaColor.B / 2);
|
||||
var zz = float3.Lerp(start, end, (float)health.DisplayHP / health.MaxHP);
|
||||
|
||||
wcr.DrawLine(z + p, zz + p, iz, deltaColor2);
|
||||
wcr.DrawLine(z + q, zz + q, iz, deltaColor);
|
||||
wcr.DrawLine(z + r, zz + r, iz, deltaColor2);
|
||||
}
|
||||
}
|
||||
|
||||
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
|
||||
public void Render(WorldRenderer wr)
|
||||
{
|
||||
if (!actor.IsInWorld || actor.IsDead)
|
||||
return;
|
||||
|
||||
var health = actor.TraitOrDefault<IHealth>();
|
||||
|
||||
var screenPos = wr.Screen3DPxPosition(pos);
|
||||
var bounds = actor.VisualBounds;
|
||||
bounds.Offset((int)screenPos.X, (int)screenPos.Y);
|
||||
|
||||
var start = new float3(bounds.Left + 1, bounds.Top, screenPos.Z);
|
||||
var end = new float3(bounds.Right - 1, bounds.Top, screenPos.Z);
|
||||
|
||||
if (DisplayHealth)
|
||||
DrawHealthBar(wr, health, start, end);
|
||||
|
||||
if (DisplayExtra)
|
||||
DrawExtraBars(wr, start, end);
|
||||
}
|
||||
|
||||
public void RenderDebugGeometry(WorldRenderer wr) { }
|
||||
public Rectangle ScreenBounds(WorldRenderer wr) { return Rectangle.Empty; }
|
||||
}
|
||||
}
|
||||
144
OpenRA.Game/Graphics/SequenceProvider.cs
Normal file
144
OpenRA.Game/Graphics/SequenceProvider.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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.FileSystem;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
using Sequences = IReadOnlyDictionary<string, Lazy<IReadOnlyDictionary<string, ISpriteSequence>>>;
|
||||
using UnitSequences = Lazy<IReadOnlyDictionary<string, ISpriteSequence>>;
|
||||
|
||||
public interface ISpriteSequence
|
||||
{
|
||||
string Name { get; }
|
||||
int Start { get; }
|
||||
int Length { get; }
|
||||
int Stride { get; }
|
||||
int Facings { get; }
|
||||
int Tick { get; }
|
||||
int ZOffset { get; }
|
||||
int ShadowStart { get; }
|
||||
int ShadowZOffset { get; }
|
||||
int[] Frames { get; }
|
||||
|
||||
Sprite GetSprite(int frame);
|
||||
Sprite GetSprite(int frame, int facing);
|
||||
Sprite GetShadow(int frame, int facing);
|
||||
}
|
||||
|
||||
public interface ISpriteSequenceLoader
|
||||
{
|
||||
Action<string> OnMissingSpriteError { get; set; }
|
||||
IReadOnlyDictionary<string, ISpriteSequence> ParseSequences(ModData modData, TileSet tileSet, SpriteCache cache, MiniYamlNode node);
|
||||
}
|
||||
|
||||
public class SequenceProvider : IDisposable
|
||||
{
|
||||
readonly ModData modData;
|
||||
readonly TileSet tileSet;
|
||||
readonly Lazy<Sequences> sequences;
|
||||
readonly Lazy<SpriteCache> spriteCache;
|
||||
public SpriteCache SpriteCache { get { return spriteCache.Value; } }
|
||||
|
||||
readonly Dictionary<string, UnitSequences> sequenceCache = new Dictionary<string, UnitSequences>();
|
||||
|
||||
public SequenceProvider(IReadOnlyFileSystem fileSystem, ModData modData, TileSet tileSet, MiniYaml additionalSequences)
|
||||
{
|
||||
this.modData = modData;
|
||||
this.tileSet = tileSet;
|
||||
sequences = Exts.Lazy(() =>
|
||||
{
|
||||
using (new Support.PerfTimer("LoadSequences"))
|
||||
return Load(fileSystem, additionalSequences);
|
||||
});
|
||||
|
||||
spriteCache = Exts.Lazy(() => new SpriteCache(fileSystem, modData.SpriteLoaders, new SheetBuilder(SheetType.Indexed)));
|
||||
}
|
||||
|
||||
public ISpriteSequence GetSequence(string unitName, string sequenceName)
|
||||
{
|
||||
UnitSequences unitSeq;
|
||||
if (!sequences.Value.TryGetValue(unitName, out unitSeq))
|
||||
throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
|
||||
|
||||
ISpriteSequence seq;
|
||||
if (!unitSeq.Value.TryGetValue(sequenceName, out seq))
|
||||
throw new InvalidOperationException("Unit `{0}` does not have a sequence named `{1}`".F(unitName, sequenceName));
|
||||
|
||||
return seq;
|
||||
}
|
||||
|
||||
public bool HasSequence(string unitName)
|
||||
{
|
||||
return sequences.Value.ContainsKey(unitName);
|
||||
}
|
||||
|
||||
public bool HasSequence(string unitName, string sequenceName)
|
||||
{
|
||||
UnitSequences unitSeq;
|
||||
if (!sequences.Value.TryGetValue(unitName, out unitSeq))
|
||||
throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
|
||||
|
||||
return unitSeq.Value.ContainsKey(sequenceName);
|
||||
}
|
||||
|
||||
public IEnumerable<string> Sequences(string unitName)
|
||||
{
|
||||
UnitSequences unitSeq;
|
||||
if (!sequences.Value.TryGetValue(unitName, out unitSeq))
|
||||
throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
|
||||
|
||||
return unitSeq.Value.Keys;
|
||||
}
|
||||
|
||||
Sequences Load(IReadOnlyFileSystem fileSystem, MiniYaml additionalSequences)
|
||||
{
|
||||
var nodes = MiniYaml.Load(fileSystem, modData.Manifest.Sequences, additionalSequences);
|
||||
var items = new Dictionary<string, UnitSequences>();
|
||||
foreach (var n in nodes)
|
||||
{
|
||||
// Work around the loop closure issue in older versions of C#
|
||||
var node = n;
|
||||
|
||||
var key = node.Value.ToLines(node.Key).JoinWith("|");
|
||||
|
||||
UnitSequences t;
|
||||
if (sequenceCache.TryGetValue(key, out t))
|
||||
items.Add(node.Key, t);
|
||||
else
|
||||
{
|
||||
t = Exts.Lazy(() => modData.SpriteSequenceLoader.ParseSequences(modData, tileSet, SpriteCache, node));
|
||||
sequenceCache.Add(key, t);
|
||||
items.Add(node.Key, t);
|
||||
}
|
||||
}
|
||||
|
||||
return new ReadOnlyDictionary<string, UnitSequences>(items);
|
||||
}
|
||||
|
||||
public void Preload()
|
||||
{
|
||||
SpriteCache.SheetBuilder.Current.CreateBuffer();
|
||||
foreach (var unitSeq in sequences.Value.Values)
|
||||
foreach (var seq in unitSeq.Value.Values) { }
|
||||
SpriteCache.SheetBuilder.Current.ReleaseBuffer();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (spriteCache.IsValueCreated)
|
||||
spriteCache.Value.SheetBuilder.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,119 +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;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public interface ISpriteSequence
|
||||
{
|
||||
string Name { get; }
|
||||
int Length { get; }
|
||||
int Facings { get; }
|
||||
int Tick { get; }
|
||||
int ZOffset { get; }
|
||||
int ShadowZOffset { get; }
|
||||
Rectangle Bounds { get; }
|
||||
bool IgnoreWorldTint { get; }
|
||||
float Scale { get; }
|
||||
void ResolveSprites(SpriteCache cache);
|
||||
Sprite GetSprite(int frame);
|
||||
Sprite GetSprite(int frame, WAngle facing);
|
||||
(Sprite Sprite, WAngle Rotation) GetSpriteWithRotation(int frame, WAngle facing);
|
||||
Sprite GetShadow(int frame, WAngle facing);
|
||||
float GetAlpha(int frame);
|
||||
}
|
||||
|
||||
public interface ISpriteSequenceLoader
|
||||
{
|
||||
int BgraSheetSize { get; }
|
||||
int IndexedSheetSize { get; }
|
||||
IReadOnlyDictionary<string, ISpriteSequence> ParseSequences(ModData modData, string tileSet, SpriteCache cache, MiniYamlNode node);
|
||||
}
|
||||
|
||||
public sealed class SequenceSet : IDisposable
|
||||
{
|
||||
readonly ModData modData;
|
||||
readonly string tileSet;
|
||||
readonly IReadOnlyDictionary<string, IReadOnlyDictionary<string, ISpriteSequence>> images;
|
||||
public SpriteCache SpriteCache { get; }
|
||||
|
||||
public SequenceSet(IReadOnlyFileSystem fileSystem, ModData modData, string tileSet, MiniYaml additionalSequences)
|
||||
{
|
||||
this.modData = modData;
|
||||
this.tileSet = tileSet;
|
||||
SpriteCache = new SpriteCache(fileSystem, modData.SpriteLoaders, modData.SpriteSequenceLoader.BgraSheetSize, modData.SpriteSequenceLoader.IndexedSheetSize);
|
||||
using (new Support.PerfTimer("LoadSequences"))
|
||||
images = Load(fileSystem, additionalSequences);
|
||||
}
|
||||
|
||||
public ISpriteSequence GetSequence(string image, string sequence)
|
||||
{
|
||||
if (!images.TryGetValue(image, out var sequences))
|
||||
throw new InvalidOperationException($"Image `{image}` does not have any sequences defined.");
|
||||
|
||||
if (!sequences.TryGetValue(sequence, out var seq))
|
||||
throw new InvalidOperationException($"Image `{image}` does not have a sequence named `{sequence}`.");
|
||||
|
||||
return seq;
|
||||
}
|
||||
|
||||
public IEnumerable<string> Images => images.Keys;
|
||||
|
||||
public bool HasSequence(string image, string sequence)
|
||||
{
|
||||
if (!images.TryGetValue(image, out var sequences))
|
||||
throw new InvalidOperationException($"Image `{image}` does not have any sequences defined.");
|
||||
|
||||
return sequences.ContainsKey(sequence);
|
||||
}
|
||||
|
||||
public IEnumerable<string> Sequences(string image)
|
||||
{
|
||||
if (!images.TryGetValue(image, out var sequences))
|
||||
throw new InvalidOperationException($"Image `{image}` does not have any sequences defined.");
|
||||
|
||||
return sequences.Keys;
|
||||
}
|
||||
|
||||
IReadOnlyDictionary<string, IReadOnlyDictionary<string, ISpriteSequence>> Load(IReadOnlyFileSystem fileSystem, MiniYaml additionalSequences)
|
||||
{
|
||||
var nodes = MiniYaml.Load(fileSystem, modData.Manifest.Sequences, additionalSequences);
|
||||
var images = new Dictionary<string, IReadOnlyDictionary<string, ISpriteSequence>>();
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
// Nodes starting with ^ are inheritable but never loaded directly
|
||||
if (node.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
images[node.Key] = modData.SpriteSequenceLoader.ParseSequences(modData, tileSet, SpriteCache, node);
|
||||
}
|
||||
|
||||
return images;
|
||||
}
|
||||
|
||||
public void LoadSprites()
|
||||
{
|
||||
SpriteCache.LoadReservations(modData);
|
||||
foreach (var sequences in images.Values)
|
||||
foreach (var sequence in sequences)
|
||||
sequence.Value.ResolveSprites(SpriteCache);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
SpriteCache.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -10,9 +10,10 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Primitives;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
@@ -32,7 +33,7 @@ namespace OpenRA.Graphics
|
||||
return data;
|
||||
}
|
||||
|
||||
public bool Buffered => data != null || texture == null;
|
||||
public bool Buffered { get { return data != null || texture == null; } }
|
||||
|
||||
public Sheet(SheetType type, Size size)
|
||||
{
|
||||
@@ -49,10 +50,13 @@ namespace OpenRA.Graphics
|
||||
|
||||
public Sheet(SheetType type, Stream stream)
|
||||
{
|
||||
var png = new Png(stream);
|
||||
Size = new Size(png.Width, png.Height);
|
||||
data = new byte[4 * Size.Width * Size.Height];
|
||||
Util.FastCopyIntoSprite(new Sprite(this, new Rectangle(0, 0, png.Width, png.Height), TextureChannel.Red), png);
|
||||
using (var bitmap = (Bitmap)Image.FromStream(stream))
|
||||
{
|
||||
Size = bitmap.Size;
|
||||
data = new byte[4 * Size.Width * Size.Height];
|
||||
|
||||
Util.FastCopyIntoSprite(new Sprite(this, bitmap.Bounds(), TextureChannel.Red), bitmap);
|
||||
}
|
||||
|
||||
Type = type;
|
||||
ReleaseBuffer();
|
||||
@@ -62,7 +66,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
if (texture == null)
|
||||
{
|
||||
texture = Game.Renderer.Context.CreateTexture();
|
||||
texture = Game.Renderer.Device.CreateTexture();
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
@@ -77,33 +81,48 @@ namespace OpenRA.Graphics
|
||||
return texture;
|
||||
}
|
||||
|
||||
public Png AsPng()
|
||||
public Bitmap AsBitmap()
|
||||
{
|
||||
if (Type == SheetType.Indexed)
|
||||
throw new InvalidOperationException("AsPng() cannot be called on Indexed sheets.");
|
||||
var d = GetData();
|
||||
var dataStride = 4 * Size.Width;
|
||||
var bitmap = new Bitmap(Size.Width, Size.Height);
|
||||
|
||||
return new Png(GetData(), SpriteFrameType.Bgra32, Size.Width, Size.Height);
|
||||
var bd = bitmap.LockBits(bitmap.Bounds(),
|
||||
ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
|
||||
for (var y = 0; y < Size.Height; y++)
|
||||
Marshal.Copy(d, y * dataStride, IntPtr.Add(bd.Scan0, y * bd.Stride), dataStride);
|
||||
bitmap.UnlockBits(bd);
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public Png AsPng(TextureChannel channel, IPalette pal)
|
||||
public Bitmap AsBitmap(TextureChannel channel, IPalette pal)
|
||||
{
|
||||
if (Type != SheetType.Indexed)
|
||||
throw new InvalidOperationException("AsPng(TextureChannel, IPalette) can only be called on Indexed sheets.");
|
||||
|
||||
var d = GetData();
|
||||
var plane = new byte[Size.Width * Size.Height];
|
||||
var dataStride = 4 * Size.Width;
|
||||
var bitmap = new Bitmap(Size.Width, Size.Height);
|
||||
var channelOffset = (int)channel;
|
||||
|
||||
for (var y = 0; y < Size.Height; y++)
|
||||
for (var x = 0; x < Size.Width; x++)
|
||||
plane[y * Size.Width + x] = d[y * dataStride + channelOffset + 4 * x];
|
||||
var bd = bitmap.LockBits(bitmap.Bounds(),
|
||||
ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
|
||||
unsafe
|
||||
{
|
||||
var colors = (uint*)bd.Scan0;
|
||||
for (var y = 0; y < Size.Height; y++)
|
||||
{
|
||||
var dataRowIndex = y * dataStride + channelOffset;
|
||||
var bdRowIndex = y * bd.Stride / 4;
|
||||
for (var x = 0; x < Size.Width; x++)
|
||||
{
|
||||
var paletteIndex = d[dataRowIndex + 4 * x];
|
||||
colors[bdRowIndex + x] = pal[paletteIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var palColors = new Color[Palette.Size];
|
||||
for (var i = 0; i < Palette.Size; i++)
|
||||
palColors[i] = pal.GetColor(i);
|
||||
bitmap.UnlockBits(bd);
|
||||
|
||||
return new Png(plane, SpriteFrameType.Indexed8, Size.Width, Size.Height, palColors);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public void CreateBuffer()
|
||||
@@ -134,15 +153,12 @@ namespace OpenRA.Graphics
|
||||
return;
|
||||
dirty = true;
|
||||
releaseBufferOnCommit = true;
|
||||
|
||||
// Commit data from the buffer to the texture, allowing the buffer to be released and reclaimed by GC.
|
||||
if (Game.Renderer != null)
|
||||
GetTexture();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
texture?.Dispose();
|
||||
if (texture != null)
|
||||
texture.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -11,8 +11,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Primitives;
|
||||
using System.Drawing;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
@@ -23,87 +22,74 @@ namespace OpenRA.Graphics
|
||||
: base(message) { }
|
||||
}
|
||||
|
||||
// The enum values indicate the number of channels used by the type
|
||||
// They are not arbitrary IDs!
|
||||
public enum SheetType
|
||||
{
|
||||
Indexed = 1,
|
||||
BGRA = 4,
|
||||
BGRA = 2,
|
||||
}
|
||||
|
||||
public sealed class SheetBuilder : IDisposable
|
||||
{
|
||||
public readonly SheetType Type;
|
||||
readonly List<Sheet> sheets = new();
|
||||
readonly List<Sheet> sheets = new List<Sheet>();
|
||||
readonly Func<Sheet> allocateSheet;
|
||||
readonly int margin;
|
||||
int rowHeight = 0;
|
||||
int2 p;
|
||||
|
||||
public Sheet Current { get; private set; }
|
||||
public TextureChannel CurrentChannel { get; private set; }
|
||||
public IEnumerable<Sheet> AllSheets => sheets;
|
||||
Sheet current;
|
||||
TextureChannel channel;
|
||||
int rowHeight = 0;
|
||||
Point p;
|
||||
|
||||
public static Sheet AllocateSheet(SheetType type, int sheetSize)
|
||||
{
|
||||
return new Sheet(type, new Size(sheetSize, sheetSize));
|
||||
}
|
||||
|
||||
public static SheetType FrameTypeToSheetType(SpriteFrameType t)
|
||||
{
|
||||
switch (t)
|
||||
{
|
||||
case SpriteFrameType.Indexed8:
|
||||
return SheetType.Indexed;
|
||||
|
||||
// Util.FastCopyIntoChannel will automatically convert these to BGRA
|
||||
case SpriteFrameType.Bgra32:
|
||||
case SpriteFrameType.Bgr24:
|
||||
case SpriteFrameType.Rgba32:
|
||||
case SpriteFrameType.Rgb24:
|
||||
return SheetType.BGRA;
|
||||
default: throw new NotImplementedException($"Unknown SpriteFrameType {t}");
|
||||
}
|
||||
}
|
||||
|
||||
public SheetBuilder(SheetType t)
|
||||
: this(t, Game.Settings.Graphics.SheetSize) { }
|
||||
|
||||
public SheetBuilder(SheetType t, int sheetSize, int margin = 1)
|
||||
: this(t, () => AllocateSheet(t, sheetSize), margin) { }
|
||||
public SheetBuilder(SheetType t, int sheetSize)
|
||||
: this(t, () => AllocateSheet(t, sheetSize)) { }
|
||||
|
||||
public SheetBuilder(SheetType t, Func<Sheet> allocateSheet, int margin = 1)
|
||||
public SheetBuilder(SheetType t, Func<Sheet> allocateSheet)
|
||||
{
|
||||
CurrentChannel = t == SheetType.Indexed ? TextureChannel.Red : TextureChannel.RGBA;
|
||||
channel = TextureChannel.Red;
|
||||
Type = t;
|
||||
Current = allocateSheet();
|
||||
sheets.Add(Current);
|
||||
current = allocateSheet();
|
||||
sheets.Add(current);
|
||||
this.allocateSheet = allocateSheet;
|
||||
this.margin = margin;
|
||||
}
|
||||
|
||||
public Sprite Add(ISpriteFrame frame) { return Add(frame.Data, frame.Type, frame.Size, 0, frame.Offset); }
|
||||
public Sprite Add(byte[] src, SpriteFrameType type, Size size) { return Add(src, type, size, 0, float3.Zero); }
|
||||
public Sprite Add(byte[] src, SpriteFrameType type, Size size, float zRamp, in float3 spriteOffset)
|
||||
public Sprite Add(ISpriteFrame frame) { return Add(frame.Data, frame.Size, 0, frame.Offset); }
|
||||
public Sprite Add(byte[] src, Size size) { return Add(src, size, 0, float3.Zero); }
|
||||
public Sprite Add(byte[] src, Size size, float zRamp, float3 spriteOffset)
|
||||
{
|
||||
// Don't bother allocating empty sprites
|
||||
if (size.Width == 0 || size.Height == 0)
|
||||
return new Sprite(Current, Rectangle.Empty, 0, spriteOffset, CurrentChannel, BlendMode.Alpha);
|
||||
return new Sprite(current, Rectangle.Empty, 0, spriteOffset, channel, BlendMode.Alpha);
|
||||
|
||||
var rect = Allocate(size, zRamp, spriteOffset);
|
||||
Util.FastCopyIntoChannel(rect, src, type);
|
||||
Current.CommitBufferedData();
|
||||
Util.FastCopyIntoChannel(rect, src);
|
||||
current.CommitBufferedData();
|
||||
return rect;
|
||||
}
|
||||
|
||||
public Sprite Add(Png src, float scale = 1f)
|
||||
public Sprite Add(Bitmap src)
|
||||
{
|
||||
var rect = Allocate(new Size(src.Width, src.Height), scale);
|
||||
var rect = Allocate(src.Size);
|
||||
Util.FastCopyIntoSprite(rect, src);
|
||||
Current.CommitBufferedData();
|
||||
current.CommitBufferedData();
|
||||
return rect;
|
||||
}
|
||||
|
||||
public Sprite Add(Size size, byte paletteIndex)
|
||||
{
|
||||
var data = new byte[size.Width * size.Height];
|
||||
for (var i = 0; i < data.Length; i++)
|
||||
data[i] = paletteIndex;
|
||||
|
||||
return Add(data, size);
|
||||
}
|
||||
|
||||
TextureChannel? NextChannel(TextureChannel t)
|
||||
{
|
||||
var nextChannel = (int)t + (int)Type;
|
||||
@@ -113,41 +99,43 @@ namespace OpenRA.Graphics
|
||||
return (TextureChannel)nextChannel;
|
||||
}
|
||||
|
||||
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) { return Allocate(imageSize, 0, float3.Zero); }
|
||||
public Sprite Allocate(Size imageSize, float zRamp, float3 spriteOffset)
|
||||
{
|
||||
if (imageSize.Width + p.X + margin > Current.Size.Width)
|
||||
if (imageSize.Width + p.X > current.Size.Width)
|
||||
{
|
||||
p = new int2(0, p.Y + rowHeight + margin);
|
||||
p = new Point(0, p.Y + rowHeight);
|
||||
rowHeight = imageSize.Height;
|
||||
}
|
||||
|
||||
if (imageSize.Height > rowHeight)
|
||||
rowHeight = imageSize.Height;
|
||||
|
||||
if (p.Y + imageSize.Height + margin > Current.Size.Height)
|
||||
if (p.Y + imageSize.Height > current.Size.Height)
|
||||
{
|
||||
var next = NextChannel(CurrentChannel);
|
||||
var next = NextChannel(channel);
|
||||
if (next == null)
|
||||
{
|
||||
Current.ReleaseBuffer();
|
||||
Current = allocateSheet();
|
||||
sheets.Add(Current);
|
||||
CurrentChannel = Type == SheetType.Indexed ? TextureChannel.Red : TextureChannel.RGBA;
|
||||
current.ReleaseBuffer();
|
||||
current = allocateSheet();
|
||||
sheets.Add(current);
|
||||
channel = TextureChannel.Red;
|
||||
}
|
||||
else
|
||||
CurrentChannel = next.Value;
|
||||
channel = next.Value;
|
||||
|
||||
rowHeight = imageSize.Height;
|
||||
p = int2.Zero;
|
||||
p = new Point(0, 0);
|
||||
}
|
||||
|
||||
var rect = new Sprite(Current, new Rectangle(p.X + margin, p.Y + margin, imageSize.Width, imageSize.Height), zRamp, spriteOffset, CurrentChannel, BlendMode.Alpha, scale);
|
||||
p += new int2(imageSize.Width + margin, 0);
|
||||
var rect = new Sprite(current, new Rectangle(p, imageSize), zRamp, spriteOffset, channel, BlendMode.Alpha);
|
||||
p.X += imageSize.Width;
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
public Sheet Current { get { return current; } }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var sheet in sheets)
|
||||
|
||||
100
OpenRA.Game/Graphics/SoftwareCursor.cs
Normal file
100
OpenRA.Game/Graphics/SoftwareCursor.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* 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.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public interface ICursor : IDisposable
|
||||
{
|
||||
void Render(Renderer renderer);
|
||||
void SetCursor(string cursor);
|
||||
void Tick();
|
||||
}
|
||||
|
||||
public sealed class SoftwareCursor : ICursor
|
||||
{
|
||||
readonly HardwarePalette palette = new HardwarePalette();
|
||||
readonly Cache<string, PaletteReference> paletteReferences;
|
||||
readonly Dictionary<string, Sprite[]> sprites = new Dictionary<string, Sprite[]>();
|
||||
readonly CursorProvider cursorProvider;
|
||||
readonly SheetBuilder sheetBuilder;
|
||||
|
||||
public SoftwareCursor(CursorProvider cursorProvider)
|
||||
{
|
||||
this.cursorProvider = cursorProvider;
|
||||
|
||||
paletteReferences = new Cache<string, PaletteReference>(CreatePaletteReference);
|
||||
foreach (var p in cursorProvider.Palettes)
|
||||
palette.AddPalette(p.Key, p.Value, false);
|
||||
|
||||
palette.Initialize();
|
||||
|
||||
sheetBuilder = new SheetBuilder(SheetType.Indexed);
|
||||
foreach (var kv in cursorProvider.Cursors)
|
||||
{
|
||||
var s = kv.Value.Frames.Select(a => sheetBuilder.Add(a)).ToArray();
|
||||
sprites.Add(kv.Key, s);
|
||||
}
|
||||
|
||||
sheetBuilder.Current.ReleaseBuffer();
|
||||
|
||||
Game.Renderer.Device.SetHardwareCursor(null);
|
||||
}
|
||||
|
||||
PaletteReference CreatePaletteReference(string name)
|
||||
{
|
||||
var pal = palette.GetPalette(name);
|
||||
return new PaletteReference(name, palette.GetPaletteIndex(name), pal, palette);
|
||||
}
|
||||
|
||||
string cursorName;
|
||||
public void SetCursor(string cursor)
|
||||
{
|
||||
cursorName = cursor;
|
||||
}
|
||||
|
||||
float cursorFrame;
|
||||
public void Tick()
|
||||
{
|
||||
cursorFrame += 0.5f;
|
||||
}
|
||||
|
||||
public void Render(Renderer renderer)
|
||||
{
|
||||
if (cursorName == null)
|
||||
return;
|
||||
|
||||
var cursorSequence = cursorProvider.GetCursorSequence(cursorName);
|
||||
var cursorSprite = sprites[cursorName][((int)cursorFrame % cursorSequence.Length)];
|
||||
var cursorSize = CursorProvider.CursorViewportZoomed ? 2.0f * cursorSprite.Size : cursorSprite.Size;
|
||||
|
||||
var cursorOffset = CursorProvider.CursorViewportZoomed ?
|
||||
(2 * cursorSequence.Hotspot) + cursorSprite.Size.XY.ToInt2() :
|
||||
cursorSequence.Hotspot + (0.5f * cursorSprite.Size.XY).ToInt2();
|
||||
|
||||
renderer.SetPalette(palette);
|
||||
renderer.SpriteRenderer.DrawSprite(cursorSprite,
|
||||
Viewport.LastMousePos - cursorOffset,
|
||||
paletteReferences[cursorSequence.Palette],
|
||||
cursorSize);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
palette.Dispose();
|
||||
sheetBuilder.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user