Compare commits

..

1 Commits

Author SHA1 Message Date
Matthias Mailänder
157db79ae7 Enable itch app auto updates on Windows. 2020-09-06 13:50:50 +02:00
2980 changed files with 80633 additions and 139254 deletions

View File

@@ -8,915 +8,99 @@ 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/
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = true
# 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.
csharp_style_var_elsewhere = true:suggestion
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
### Language Rules
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/language-rules
csharp_prefer_braces = when_multiline:suggestion
csharp_using_directive_placement = outside_namespace:suggestion
csharp_new_line_before_open_brace = all
csharp_space_around_binary_operators = before_and_after
## 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
#### 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
# Symbol specifications
## Naming Symbols
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal
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_private_field.applicable_kinds = field
dotnet_naming_symbols.const_private_field.required_modifiers = const
dotnet_naming_symbols.const_private_field.applicable_accessibilities = private
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.internal_field.applicable_kinds = field
dotnet_naming_symbols.internal_field.applicable_accessibilities = internal
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.static_private_or_internal_field.required_modifiers = static
dotnet_naming_symbols.static_private_or_internal_field.applicable_accessibilities = internal, private
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_internal_field.applicable_kinds = field
dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private
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.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal
dotnet_naming_symbols.interfaces.applicable_kinds = interface
dotnet_naming_symbols.interfaces.applicable_accessibilities = *
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal
dotnet_naming_symbols.parameters_and_locals.applicable_kinds = parameter, local
dotnet_naming_symbols.parameters_and_locals.applicable_accessibilities = *
# Naming rules
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 = *
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
## 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.static_private_or_internal_field_should_be_pascal_case.severity = none
dotnet_naming_rule.static_private_or_internal_field_should_be_pascal_case.symbols = static_private_or_internal_field
dotnet_naming_rule.static_private_or_internal_field_should_be_pascal_case.style = pascal_case
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.const_private_field_should_be_pascal_case.severity = warning
dotnet_naming_rule.const_private_field_should_be_pascal_case.symbols = const_private_field
dotnet_naming_rule.const_private_field_should_be_pascal_case.style = pascal_case
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.const_private_or_internal_field_should_be_pascal_case.severity = warning
dotnet_naming_rule.const_private_or_internal_field_should_be_pascal_case.symbols = internal_field
dotnet_naming_rule.const_private_or_internal_field_should_be_pascal_case.style = pascal_case
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_internal_field_should_be_camel_case.severity = warning
dotnet_naming_rule.private_or_internal_field_should_be_camel_case.symbols = private_or_internal_field
dotnet_naming_rule.private_or_internal_field_should_be_camel_case.style = camel_case
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
# Naming rules
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
#require a space before the colon for bases or interfaces in a type declaration
csharp_space_after_colon_in_inheritance_clause = true
#require a space after a keyword in a control flow statement such as a for loop
csharp_space_after_keywords_in_control_flow_statements = true
#require a space before the colon for bases or interfaces in a type declaration
csharp_space_before_colon_in_inheritance_clause = true
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
#Formatting - wrapping options
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
#leave code block on single line
csharp_preserve_single_line_blocks = true
#leave statements and member declarations on the same line
csharp_preserve_single_line_statements = true
#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them
dotnet_style_predefined_type_for_member_access = true:suggestion
### StyleCop.Analyzers
### https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/DOCUMENTATION.md
#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
# 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
# Collections should implement generic interface.
#dotnet_code_quality.CA1010.additional_required_generic_interfaces =
dotnet_diagnostic.CA1010.severity = warning
# Abstract types should not have public constructors.
dotnet_diagnostic.CA1012.severity = warning
# Mark attributes with 'AttributeUsageAttribute'.
dotnet_diagnostic.CA1018.severity = warning
# Override methods on comparable types.
dotnet_diagnostic.CA1036.severity = warning
# 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
# Static holder types should be 'Static' or 'NotInheritable'.
dotnet_diagnostic.CA1052.severity = warning
# Do not hide base class methods.
dotnet_diagnostic.CA1061.severity = warning
# Exceptions should be public.
dotnet_diagnostic.CA1064.severity = warning
# Implement 'IEquatable' when overriding 'Equals'.
dotnet_diagnostic.CA1066.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
# Use Literals Where Appropriate.
#dotnet_code_quality.CA1802.required_modifiers = static
dotnet_diagnostic.CA1802.severity = warning
# Remove empty finalizers.
dotnet_diagnostic.CA1821.severity = warning
# Mark members as static.
dotnet_code_quality.CA1822.api_surface = private,internal
dotnet_diagnostic.CA1822.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
# Use StringBuilder.Append(char) for single character strings.
dotnet_diagnostic.CA1834.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.ProcessPath instead of Process.GetCurrentProcess().MainModule.FileName.
dotnet_diagnostic.CA1839.severity = warning
# Use Environment.CurrentManagedThreadId instead of Thread.CurrentThread.ManagedThreadId.
dotnet_diagnostic.CA1840.severity = warning
# Prefer Dictionary Contains methods.
dotnet_diagnostic.CA1841.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'. (Not available on mono)
dotnet_diagnostic.CA1845.severity = none
# Prefer AsSpan over Substring.
dotnet_diagnostic.CA1846.severity = warning
# Use string.Contains(char) instead of string.Contains(string) with single characters.
dotnet_diagnostic.CA1847.severity = warning
# Call async methods when in an async method.
dotnet_diagnostic.CA1849.severity = warning
# Prefer static HashData method over ComputeHash. (Not available on mono)
dotnet_diagnostic.CA1850.severity = none
# Seal internal types.
dotnet_diagnostic.CA1852.severity = warning
# Unnecessary call to 'Dictionary.ContainsKey(key)'.
dotnet_diagnostic.CA1853.severity = warning
# Prefer the IDictionary.TryGetValue(TKey, out TValue) method.
dotnet_diagnostic.CA1854.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
# ThreadStatic fields should not use inline initialization.
dotnet_diagnostic.CA2019.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
# Call GC.SuppressFinalize correctly.
dotnet_diagnostic.CA1816.severity = warning
# Rethrow to preserve stack details.
dotnet_diagnostic.CA2200.severity = warning
# Initialize value type static fields inline.
dotnet_diagnostic.CA2207.severity = warning
# Instantiate argument exceptions correctly.
dotnet_diagnostic.CA2208.severity = warning
# Dispose methods should call base class dispose.
dotnet_diagnostic.CA2215.severity = warning
# Disposable types should declare finalizer.
dotnet_diagnostic.CA2216.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

View File

@@ -1,14 +1,11 @@
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.
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.
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.
url: https://webchat.freenode.net/#openra
about: "Join our development IRC channel on freenode for discussion of development topics."

View File

@@ -1,6 +1,6 @@
---
name: Crash report
about: Report a game crash. Check the FAQ first https://github.com/OpenRA/OpenRA/wiki/FAQ#common-issues
about: Report a game crash.
title: My game crashed
labels: Crash
assignees: ''

View File

@@ -13,4 +13,4 @@ You can help speed up the review process by following a few steps:
* 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).
If you need any help you can ask in the #openra IRC channel on freenode (most active during European evenings).

View File

@@ -1,81 +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
make tests
- name: Check Mods
run: |
sudo apt-get install lua5.1
make check-scripts
make TREAT_WARNINGS_AS_ERRORS=true 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 TREAT_WARNINGS_AS_ERRORS=true 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
.\make.ps1 tests
- name: Check Mods
run: |
choco install lua --version 5.1.5.52
$ENV:Path = $ENV:Path + ";C:\Program Files (x86)\Lua\5.1\"
$ENV:TREAT_WARNINGS_AS_ERRORS = "true"
.\make.ps1 check-scripts
.\make.ps1 test

View File

@@ -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

View File

@@ -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

View File

@@ -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/*

19
.gitignore vendored
View File

@@ -13,10 +13,19 @@ obj
_ReSharper.*/
/.vs
# Visual Studio Code
/.vscode/settings.json
# binaries
mods/*/*.dll
mods/*/*.mdb
mods/*/*.pdb
/*.dll
/*.dll.config
/*.so
/*.dylib
/*.pdb
/*.mdb
/*.exe
/*.exe.config
thirdparty/download/*
IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
# backup files by various editors
@@ -41,6 +50,10 @@ Settings.md
openra.6
update.log
# StyleCop
*.Cache
StyleCopViolations.xml
# SublimeText
*.sublime-project
*.sublime-workspace

92
.travis.yml Normal file
View File

@@ -0,0 +1,92 @@
# Travis-CI Build for OpenRA
# see travis-ci.org for details
language: csharp
mono: 6.4.0
os: linux
dist: xenial
jobs:
include:
- os: linux
dist: xenial
- os: osx
if: tag IS present
osx_image: xcode10
addons:
apt:
packages:
- lua5.1
- dpkg
- zsync
- imagemagick
# 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:
- make all
- |
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
make check || travis_terminate 1;
make check-scripts || travis_terminate 1;
make test || travis_terminate 1;
mono ~/.nuget/packages/nunit.consolerunner/3.11.1/tools/nunit3-console.exe --noresult OpenRA.Test.dll || travis_terminate 1;
fi
# Only watch the development branch and tagged release.
branches:
only:
- /^release-.*$/
- /^playtest-.*$/
- /^devtest-.*$/
- /^prep-.*$/
- bleed
# Notify developers when build passed/failed.
notifications:
irc:
if: repo = OpenRA/OpenRA
template:
- "%{repository}#%{build_number} %{commit} %{author}: %{message} %{build_url}"
channels:
- "irc.freenode.net#openra"
use_notice: true
skip_join: true
before_deploy:
- if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then
wget https://mirrors.edge.kernel.org/ubuntu/pool/universe/n/nsis/nsis-common_3.04-1_all.deb;
wget https://mirrors.edge.kernel.org/ubuntu/pool/universe/n/nsis/nsis_3.04-1_amd64.deb;
sudo dpkg -i nsis-common_3.04-1_all.deb;
sudo dpkg -i nsis_3.04-1_amd64.deb;
echo ${TRAVIS_REPO_SLUG};
if [[ "${TRAVIS_REPO_SLUG}" == "OpenRA/OpenRA" ]]; then
cd packaging && ./update-wiki.sh ${TRAVIS_TAG} && cd ..;
fi;
fi
- export PATH=${PATH}:${HOME}/usr/bin
- DOTVERSION=`echo ${TRAVIS_TAG} | sed "s/-/\\./g"`
- cd packaging
- mkdir build
- ./package-all.sh ${TRAVIS_TAG} ${PWD}/build/
- if [[ "${TRAVIS_REPO_SLUG}" == "OpenRA/OpenRA" ]]; then
./upload-itch.sh ${TRAVIS_TAG} ${PWD}/build/;
fi
deploy:
provider: releases
token: ${GH_DEPLOY_API_KEY}
file_glob: true
file: build/*
skip_cleanup: true
on:
all_branches: true
tags: true

View File

@@ -1,9 +1,7 @@
{
"recommendations": [
"ms-dotnettools.csharp",
"openra.oraide-vscode",
"openra.vscode-openra-lua",
"EditorConfig.EditorConfig",
"macabeus.vscode-fluent",
"ms-vscode.mono-debug"
]
}

82
.vscode/launch.json vendored
View File

@@ -3,61 +3,59 @@
"configurations": [
{
"name": "Launch (TD)",
"type": "coreclr",
"request": "launch",
"program": "${workspaceRoot}/bin/OpenRA.dll",
"windows": {
"program": "${workspaceRoot}/bin/OpenRA.exe",
"type": "clr",
"linux": {
"type": "mono"
},
"args": ["Game.Mod=cnc", "Engine.EngineDir=.."],
"preLaunchTask": "build",
"osx": {
"type": "mono"
},
"request": "launch",
"program": "${workspaceRoot}/OpenRA.Game.exe",
"cwd": "${workspaceRoot}",
"args": ["Game.Mod=cnc"]
},
{
"name": "Launch (RA)",
"type": "coreclr",
"request": "launch",
"program": "${workspaceRoot}/bin/OpenRA.dll",
"windows": {
"program": "${workspaceRoot}/bin/OpenRA.exe",
"type": "clr",
"linux": {
"type": "mono"
},
"args": ["Game.Mod=ra", "Engine.EngineDir=.."],
"preLaunchTask": "build",
"osx": {
"type": "mono"
},
"request": "launch",
"program": "${workspaceRoot}/OpenRA.Game.exe",
"cwd": "${workspaceRoot}",
"args": ["Game.Mod=ra"]
},
{
"name": "Launch (D2k)",
"type": "coreclr",
"request": "launch",
"program": "${workspaceRoot}/bin/OpenRA.dll",
"windows": {
"program": "${workspaceRoot}/bin/OpenRA.exe",
"type": "clr",
"linux": {
"type": "mono"
},
"args": ["Game.Mod=d2k", "Engine.EngineDir=.."],
"preLaunchTask": "build",
"osx": {
"type": "mono"
},
"request": "launch",
"program": "${workspaceRoot}/OpenRA.Game.exe",
"cwd": "${workspaceRoot}",
"args": ["Game.Mod=d2k"]
},
{
"name": "Launch (TS)",
"type": "coreclr",
"type": "clr",
"linux": {
"type": "mono"
},
"osx": {
"type": "mono"
},
"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",
"program": "${workspaceRoot}/OpenRA.Game.exe",
"cwd": "${workspaceRoot}",
"args": ["Game.Mod=ts"]
},
]
}

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"omnisharp.enableRoslynAnalyzers": true
}

25
.vscode/tasks.json vendored
View File

@@ -1,36 +1,13 @@
{
"version": "2.0.0",
"options": {
"env": {
"ENGINE_DIR": ".."
}
},
"tasks": [
{
"label": "build",
"command": "make",
"args": ["all", "CONFIGURATION=Debug"],
"args": ["all"],
"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"
},
]
}

41
AUTHORS
View File

@@ -2,30 +2,28 @@ 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)
* Lukas Franke (abcdefg30)
* Matthias Mailänder (Mailaender)
* Paul Chote (pchote)
* Reaperrr
Previous developers included:
* Alli Witheford (alzeih)
* Caleb Anderson (RobotCaleb)
* Chris Forbes (chrisf)
* Curtis Shmyr (hamb)
* Daniel Hernandez (Mancano)
* Igor Popov (ihptru)
* Matthias Mailänder (Mailaender)
* Megan Bowra-Dean (beedee)
* Mike Bundy (kehaar)
* Oliver Brakmann (obrakmann)
* Paul Chote (pchote)
* Pavel Penev (penev92)
* Reaperrr
* Robert Pepperell (ytinasni)
* ScottNZ
* Tom Roostan (RoosterDragon)
Also thanks to:
* abmyii
* anvilvapre (anvilvapre)
* Adam Valy (Tschokky)
* Akseli Virtanen (RAGEQUIT)
* Alexander Fast (mizipzor)
@@ -39,7 +37,6 @@ Also thanks to:
* Arik Lirette (Angusm3)
* Barnaby Smith (mvi)
* Bellator
* Bernd Stellwag (burned42)
* Biofreak
* Braxton Williams (Buddytex)
* Brendan Gluth (Mechanical_Man)
@@ -49,7 +46,6 @@ Also thanks to:
* Chris Cameron (Vesuvian)
* Chris Grant (Unit158)
* Christer Ulfsparre (Holloweye)
* Christoph Lahner (chlah)
* clem
* Cody Brittain (Generalcamo)
* Constantin Helmig (CH4Code)
@@ -62,7 +58,6 @@ Also thanks to:
* DeadlySurprise
* Dmitri Suvorov (suvjunmd)
* dtluna
* Eduardo Cáceres (eduherminio)
* Erasmus Schroder (rasco)
* Eric Bajumpaa (SteelPhase)
* Evgeniy Sergeev (evgeniysergeev)
@@ -74,14 +69,12 @@ Also thanks to:
* Glenn Martin Jensen (Baxxster)
* Gordon Martin (Happy0)
* Guido Lipke (LipkeGu)
* Guillermo Cuenca (Wylli)
* 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)
@@ -120,7 +113,6 @@ Also thanks to:
* Mike Gagné (AngryBirdz)
* Muh
* Mustafa Alperen Seki (MustaphaTR)
* Nathan Nichols (cracksmoka420)
* Neil Shivkar (havok13888)
* Nikolay Fomin (netnazgul)
* Nooze
@@ -143,17 +135,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
@@ -181,17 +172,9 @@ 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.
@@ -205,14 +188,6 @@ 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.

View File

@@ -56,8 +56,8 @@ further defined and clarified by project maintainers.
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
of their name) via our IRC channel (#openra on freenode
[webchat](http://webchat.freenode.net/?channels=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.

View File

@@ -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>

View File

@@ -8,76 +8,104 @@ 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)
* [.NET Framework 4.7.2 (Developer Pack)](https://dotnet.microsoft.com/download/dotnet-framework/net472) (or via Visual Studio 2017)
* [.NET Core 2.2 SDK](https://dotnet.microsoft.com/download/dotnet-core/2.2) (or via Visual Studio 2017)
To compile OpenRA, open the `OpenRA.sln` solution in the main folder, build it from the command-line with `dotnet` or use the Makefile analogue command `make all` scripted in PowerShell syntax.
To compile OpenRA, open the `OpenRA.sln` solution in the main folder, build it from the command-line with MSBuild or use the Makefile analogue command `make all` scripted in PowerShell syntax.
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.
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.
Mono, version 5.18 or later, is required to compile OpenRA. You can add the [upstream mono repository](https://www.mono-project.com/download/stable/#download-lin) for your distro to obtain the latest version if your system packages are not sufficient.
The [.NET 6 download page](https://dotnet.microsoft.com/download/dotnet/6.0) provides repositories for various package managers and binary releases for several architectures. If you prefer to use Mono, we suggest adding the [upstream repository](https://www.mono-project.com/download/stable/#download-lin) for your distro to obtain the latest version and the `msbuild` toolchain.
To compile OpenRA, run `make` from the command line (or `make RUNTIME=mono` if using Mono). After this one can run the game with `./launch-game.sh`. It is also possible to specify the mod you wish to run from the command line, e.g. with `./launch-game.sh Game.Mod=ts` if you wish to try the experimental Tiberian Sun mod.
To compile OpenRA, run `make` from the command line. After this one can run the game with `./launch-game.sh`. It is also possible to specify the mod you wish to run from the command line, e.g. with `./launch-game.sh Game.Mod=ts` if you wish to try the experimental Tiberian Sun mod.
The 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`.
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.
If you choose to use system libraries, or your system is not x86_64, you will need to install the following using your system package manager:
* [SDL 2](http://www.libsdl.org/download-2.0.php)
* [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm)
* [OpenAL](http://kcat.strangesoft.net/openal.html)
* [liblua 5.1](http://luabinaries.sourceforge.net/download.html)
These can be installed using your package manager on various distros:
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`.
<details><summary>Arch Linux</summary>
Arch Linux
----------
It is important to note there is an unofficial [`openra-git`](https://aur.archlinux.org/packages/openra-git) package in the Arch User Repository (AUR) of Arch Linux. If manually compiling is the way you wish to go the build and runtime dependencies can be installed with:
```
sudo pacman -S openal libgl freetype2 sdl2 lua51
sudo pacman -S mono openal libgl freetype2 sdl2 lua51 xdg-utils zenity
```
</details>
<details><summary>Debian/Ubuntu</summary>
Debian/Ubuntu
-------------
:warning: The `mono` packages in the Ubuntu < 19.04 and Debian < 10 repositories are too old to support OpenRA. :warning:
See the instructions under the *Linux* section above to upgrade `mono` using the upstream releases if needed.
```
sudo apt install libfreetype6 libopenal1 liblua5.1-0 libsdl2-2.0-0
sudo apt install mono-devel libfreetype6 libopenal1 liblua5.1-0 libsdl2-2.0-0 xdg-utils zenity wget
```
</details>
<details><summary>Fedora</summary>
Fedora
------
:warning: The `mono` packages in the Fedora repositories are too old to support OpenRA. :warning:
See the instructions under the *Linux* section above to upgrade `mono` using the upstream releases.
```
sudo dnf install SDL2 freetype "lua = 5.1" openal-soft
sudo dnf install "pkgconfig(mono)" SDL2 freetype "lua = 5.1" openal-soft xdg-utils zenity
```
</details>
<details><summary>Gentoo</summary>
Gentoo
------
```
sudo emerge -av media-libs/freetype:2 media-libs/libsdl2 media-libs/openal virtual/opengl '=dev-lang/lua-5.1.5*'
sudo emerge -av dev-lang/mono dev-dotnet/libgdiplus 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
```
</details>
<details><summary>Mageia</summary>
Mageia
------
```
sudo dnf install SDL2 freetype "lib*lua5.1" "lib*freetype2" "lib*sdl2.0_0" openal-soft
sudo dnf install "pkgconfig(mono)" SDL2 freetype "lib*lua5.1" "lib*freetype2" "lib*sdl2.0_0" openal-soft xdg-utils zenity
```
</details>
<details><summary>openSUSE</summary>
openSUSE
--------
```
sudo zypper in openal-soft freetype2 SDL2 lua51
sudo zypper in mono-devel openal-soft freetype2 SDL2 lua51 xdg-utils zenity
```
</details>
<details><summary>Red Hat Enterprise Linux (and rebuilds, e.g. CentOS)</summary>
Red Hat Enterprise Linux (and rebuilds, e.g. CentOS)
----------------------------------------------------
The EPEL repository is required in order for the following command to run properly.
```
sudo yum install SDL2 freetype "lua = 5.1" openal-soft
sudo yum install "pkgconfig(mono)" SDL2 freetype "lua = 5.1" openal-soft xdg-utils zenity
```
</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
=====
[.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).
Before compiling OpenRA you must install the following dependencies:
* [Mono >= 5.18](https://www.mono-project.com/download/stable/#download-mac)
To compile OpenRA, run `make` from the command line. Run with `./launch-game.sh`.
The default behaviour 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`. If you choose to use system libraries you will need to install:
* [SDL 2](http://www.libsdl.org/download-2.0.php) (`brew install sdl2`)
* [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm) (`brew install freetype`)
* [OpenAL](http://kcat.strangesoft.net/openal.html) (`brew install openal-soft`)
* [liblua 5.1](http://luabinaries.sourceforge.net/download.html) (`brew install lua@5.1`)
To compile OpenRA, run `make` from the command line (or `make RUNTIME=mono` if using Mono). Run with `./launch-game.sh`.

416
Makefile
View File

@@ -1,43 +1,56 @@
############################# INSTRUCTIONS #############################
#
# to compile, run:
# make
#
# to compile using Mono (version 6.12 or greater) instead of .NET 6, run:
# make RUNTIME=mono
# make [DEBUG=true]
#
# to compile using system libraries for native dependencies, run:
# make [RUNTIME=net6] TARGETPLATFORM=unix-generic
# make [DEBUG=true] TARGETPLATFORM=unix-generic
#
# 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
# make check
#
# to compile and install Red Alert, Tiberian Dawn, and Dune 2000, run:
# make [RUNTIME=net6] [prefix=/foo] [bindir=/bar/bin] install
# to install, run:
# make [prefix=/foo] [bindir=/bar/bin] install
#
# 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 Linux startup scripts, desktop files and icons:
# make install-linux-shortcuts [DEBUG=false]
#
# to install FreeDesktop startup scripts, desktop files, icons, and MIME metadata
# make install-linux-shortcuts
# to install the engine and common mod files (omitting the default mods):
# make install-engine
# make install-dependencies
# make install-common-mod-files
#
# to install FreeDesktop AppStream metadata
# make install-linux-appdata
#
# 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 ###############################
#
# List of .NET assemblies that we can guarantee exist
# OpenRA.Game.dll is a harmless false positive that we can ignore
WHITELISTED_OPENRA_ASSEMBLIES = OpenRA.Game.exe OpenRA.Utility.exe OpenRA.Platforms.Default.dll OpenRA.Mods.Common.dll OpenRA.Mods.Cnc.dll OpenRA.Mods.D2k.dll OpenRA.Game.dll
# These are explicitly shipped alongside our core files by the packaging script
WHITELISTED_THIRDPARTY_ASSEMBLIES = ICSharpCode.SharpZipLib.dll FuzzyLogicLibrary.dll Eluant.dll BeaconLib.dll Open.Nat.dll SDL2-CS.dll OpenAL-CS.Core.dll DiscordRPC.dll Newtonsoft.Json.dll
# These are shipped in our custom minimal mono runtime and also available in the full system-installed .NET/mono stack
# This list *must* be kept in sync with the files packaged by the AppImageSupport and OpenRALauncherOSX repositories
WHITELISTED_CORE_ASSEMBLIES = mscorlib.dll System.dll System.Configuration.dll System.Core.dll System.Numerics.dll System.Security.dll System.Xml.dll Mono.Security.dll netstandard.dll
NUNIT_LIBS_PATH :=
NUNIT_LIBS := $(NUNIT_LIBS_PATH)nunit.framework.dll
######################### UTILITIES/SETTINGS ###########################
#
# Install locations for local installs and downstream packaging
# install locations
prefix ?= /usr/local
datarootdir ?= $(prefix)/share
datadir ?= $(datarootdir)
@@ -46,165 +59,308 @@ 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)
# Toolchain
MSBUILD = msbuild -verbosity:m -nologo
# 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))
# Enable 32 bit builds while generating the windows installer
WIN32 = false
# Detect target platform for dependencies if not given by the user
# dependencies
ifndef TARGETPLATFORM
UNAME_S := $(shell uname -s)
UNAME_M := $(shell uname -m)
ifeq ($(UNAME_S),Darwin)
ifeq ($(ARCH_X64),)
TARGETPLATFORM = osx-arm64
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
endif
##################### DEVELOPMENT BUILDS AND TESTS #####################
# program targets
VERSION = $(shell git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || echo git-`git rev-parse --short HEAD`)
check-scripts:
@echo
@echo "Checking for Lua syntax errors..."
@luac -p $(shell find mods/*/maps/* -iname '*.lua')
@luac -p $(shell find lua/* -iname '*.lua')
check:
@echo
@echo "Compiling in debug mode..."
@$(MSBUILD) -t:build -p:Configuration=Debug
@echo
@echo "Checking runtime assemblies..."
@mono --debug OpenRA.Utility.exe all --check-runtime-assemblies $(WHITELISTED_OPENRA_ASSEMBLIES) $(WHITELISTED_THIRDPARTY_ASSEMBLIES) $(WHITELISTED_CORE_ASSEMBLIES)
@echo
@echo "Checking for explicit interface violations..."
@mono --debug OpenRA.Utility.exe all --check-explicit-interfaces
@echo
@echo "Checking for incorrect conditional trait interface overrides..."
@mono --debug OpenRA.Utility.exe all --check-conditional-trait-interface-overrides
test: core
@echo
@echo "Testing Tiberian Sun mod MiniYAML..."
@mono --debug OpenRA.Utility.exe ts --check-yaml
@echo
@echo "Testing Dune 2000 mod MiniYAML..."
@mono --debug OpenRA.Utility.exe d2k --check-yaml
@echo
@echo "Testing Tiberian Dawn mod MiniYAML..."
@mono --debug OpenRA.Utility.exe cnc --check-yaml
@echo
@echo "Testing Red Alert mod MiniYAML..."
@mono --debug OpenRA.Utility.exe ra --check-yaml
########################## MAKE/INSTALL 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
all: core
core:
@command -v $(firstword $(MSBUILD)) >/dev/null || (echo "OpenRA requires the '$(MSBUILD)' tool provided by Mono >= 5.18."; exit 1)
@$(MSBUILD) -t:Build -restore -p:Configuration=Release -p:TargetPlatform=$(TARGETPLATFORM)
ifeq ($(TARGETPLATFORM), unix-generic)
@./configure-system-libraries.sh
endif
@./fetch-geoip.sh
# dotnet clean and msbuild -t:Clean leave files that cause problems when switching between mono/dotnet
# Deleting the intermediate / output directories ensures the build directory is actually clean
clean:
@-$(RM_RF) ./bin ./*/obj
@-$(RM_F) IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
@-$(RM_F) *.config IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
@-$(RM_F) *.exe *.dll *.dll.config *.so *.dylib ./OpenRA*/*.dll *.pdb mods/**/*.dll mods/**/*.pdb *.resources
@-$(RM_RF) ./*/bin ./*/obj
@ $(MSBUILD) -t:clean
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
check-scripts:
@echo
@echo "Checking for Lua syntax errors..."
@find mods/*/maps/ mods/*/scripts/ -iname "*.lua" -print0 | xargs -0n1 luac -p
test: all
@echo
@echo "Testing Tiberian Sun mod MiniYAML..."
@./utility.sh ts --check-yaml
@echo
@echo "Testing Dune 2000 mod MiniYAML..."
@./utility.sh d2k --check-yaml
@echo
@echo "Testing Tiberian Dawn mod MiniYAML..."
@./utility.sh cnc --check-yaml
@echo
@echo "Testing Red Alert mod MiniYAML..."
@./utility.sh ra --check-yaml
tests:
@dotnet build OpenRA.Test/OpenRA.Test.csproj -c Debug --nologo -p:TargetPlatform=$(TARGETPLATFORM)
@echo
@dotnet test bin/OpenRA.Test.dll --test-adapter-path:.
############# LOCAL INSTALLATION AND DOWNSTREAM PACKAGING ##############
#
version: VERSION mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modcontent/mod.yaml mods/all/mod.yaml
ifeq ($(VERSION),)
$(error Unable to determine new version (requires git or override of variable VERSION))
@echo "$(VERSION)" > VERSION
@for i in $? ; do \
awk '{sub("Version:.*$$","Version: $(VERSION)"); print $0}' $${i} > $${i}.tmp && \
awk '{sub("/[^/]*: User$$", "/$(VERSION): User"); print $0}' $${i}.tmp > $${i} && \
rm $${i}.tmp; \
done
install: core install-engine install-common-mod-files install-default-mods
@$(CP) *.sh "$(DATA_INSTALL_DIR)"
install-linux-shortcuts: install-linux-scripts install-linux-icons install-linux-desktop
install-dependencies:
ifeq ($(TARGETPLATFORM), $(filter $(TARGETPLATFORM),win-x86 win-x64))
@-echo "Installing OpenRA dependencies to $(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) soft_oal.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) SDL2.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) freetype6.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) lua51.dll "$(DATA_INSTALL_DIR)"
endif
ifeq ($(TARGETPLATFORM), linux-x64)
@-echo "Installing OpenRA dependencies to $(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) soft_oal.so "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) SDL2.so "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) freetype6.so "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) lua51.so "$(DATA_INSTALL_DIR)"
endif
ifeq ($(TARGETPLATFORM), osx-x64)
@-echo "Installing OpenRA dependencies to $(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) soft_oal.dylib "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) SDL2.dylib "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) freetype6.dylib "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) lua51.dylib "$(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-engine:
@-echo "Installing OpenRA engine to $(DATA_INSTALL_DIR)"
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) OpenRA.Game.exe "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) OpenRA.Server.exe "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) OpenRA.Utility.exe "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) OpenRA.Platforms.Default.dll "$(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'
ifneq ($(TARGETPLATFORM), $(filter $(TARGETPLATFORM),win-x86 win-x64))
@$(INSTALL_DATA) OpenRA.Platforms.Default.dll.config "$(DATA_INSTALL_DIR)"
endif
@$(INSTALL_DATA) VERSION "$(DATA_INSTALL_DIR)/VERSION"
@$(INSTALL_DATA) AUTHORS "$(DATA_INSTALL_DIR)/AUTHORS"
@$(INSTALL_DATA) COPYING "$(DATA_INSTALL_DIR)/COPYING"
@$(INSTALL_DATA) IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP "$(DATA_INSTALL_DIR)/IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP"
@$(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) Open.Nat.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) BeaconLib.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) DiscordRPC.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) Newtonsoft.Json.dll "$(DATA_INSTALL_DIR)"
install-common-mod-files:
@-echo "Installing OpenRA common mod files to $(DATA_INSTALL_DIR)"
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)/mods"
@$(CP_R) mods/common "$(DATA_INSTALL_DIR)/mods/"
@$(INSTALL_PROGRAM) mods/common/OpenRA.Mods.Common.dll "$(DATA_INSTALL_DIR)/mods/common"
@$(INSTALL_PROGRAM) mods/common/OpenRA.Mods.Cnc.dll "$(DATA_INSTALL_DIR)/mods/common"
@$(INSTALL_DATA) "global mix database.dat" "$(DATA_INSTALL_DIR)/global mix database.dat"
install-default-mods:
@-echo "Installing OpenRA default mods to $(DATA_INSTALL_DIR)"
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)/mods"
@$(CP_R) mods/cnc "$(DATA_INSTALL_DIR)/mods/"
@$(CP_R) mods/ra "$(DATA_INSTALL_DIR)/mods/"
@$(CP_R) mods/d2k "$(DATA_INSTALL_DIR)/mods/"
@$(INSTALL_PROGRAM) mods/d2k/OpenRA.Mods.D2k.dll "$(DATA_INSTALL_DIR)/mods/d2k"
@$(CP_R) mods/modcontent "$(DATA_INSTALL_DIR)/mods/"
install-linux-icons:
for SIZE in 16x16 32x32 48x48 64x64 128x128; do \
$(INSTALL_DIR) "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps"; \
$(INSTALL_DATA) packaging/artwork/ra_$$SIZE.png "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-ra.png"; \
$(INSTALL_DATA) packaging/artwork/cnc_$$SIZE.png "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-cnc.png"; \
$(INSTALL_DATA) packaging/artwork/d2k_$$SIZE.png "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-d2k.png"; \
done
$(INSTALL_DIR) "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps"
$(INSTALL_DATA) packaging/artwork/ra_scalable.svg "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-ra.svg"
$(INSTALL_DATA) packaging/artwork/cnc_scalable.svg "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-cnc.svg"
install-linux-desktop:
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/applications"
@sed 's/{MODID}/ra/g' packaging/linux/openra.desktop.in | sed 's/{MODNAME}/Red Alert/g' | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-ra.desktop
@$(INSTALL_DATA) packaging/linux/openra-ra.desktop "$(DESTDIR)$(datadir)/applications"
@sed 's/{MODID}/cnc/g' packaging/linux/openra.desktop.in | sed 's/{MODNAME}/Tiberian Dawn/g' | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-cnc.desktop
@$(INSTALL_DATA) packaging/linux/openra-cnc.desktop "$(DESTDIR)$(datadir)/applications"
@sed 's/{MODID}/d2k/g' packaging/linux/openra.desktop.in | sed 's/{MODNAME}/Dune 2000/g' | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-d2k.desktop
@$(INSTALL_DATA) packaging/linux/openra-d2k.desktop "$(DESTDIR)$(datadir)/applications"
@-$(RM) packaging/linux/openra-ra.desktop packaging/linux/openra-cnc.desktop packaging/linux/openra-d2k.desktop
install-linux-mime:
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/mime/packages/"
@sed 's/{MODID}/ra/g' packaging/linux/openra-mimeinfo.xml.in | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-mimeinfo.xml
@$(INSTALL_DATA) packaging/linux/openra-mimeinfo.xml "$(DESTDIR)$(datadir)/mime/packages/openra-ra.xml"
@sed 's/{MODID}/cnc/g' packaging/linux/openra-mimeinfo.xml.in | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-mimeinfo.xml
@$(INSTALL_DATA) packaging/linux/openra-mimeinfo.xml "$(DESTDIR)$(datadir)/mime/packages/openra-cnc.xml"
@sed 's/{MODID}/d2k/g' packaging/linux/openra-mimeinfo.xml.in | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-mimeinfo.xml
@$(INSTALL_DATA) packaging/linux/openra-mimeinfo.xml "$(DESTDIR)$(datadir)/mime/packages/openra-d2k.xml"
install-linux-appdata:
@sh -c '. ./packaging/functions.sh; install_linux_appdata $(CWD) "$(DESTDIR)" "$(datadir)" cnc d2k ra'
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/appdata/"
@sed 's/{MODID}/ra/g' packaging/linux/openra.appdata.xml.in | sed 's/{MOD_NAME}/Red Alert/g' | sed 's/{SCREENSHOT_RA}/ type="default"/g' | sed 's/{SCREENSHOT_CNC}//g' | sed 's/{SCREENSHOT_D2K}//g'> packaging/linux/openra-ra.appdata.xml
@$(INSTALL_DATA) packaging/linux/openra-ra.appdata.xml "$(DESTDIR)$(datadir)/appdata/"
@sed 's/{MODID}/cnc/g' packaging/linux/openra.appdata.xml.in | sed 's/{MOD_NAME}/Tiberian Dawn/g' | sed 's/{SCREENSHOT_RA}//g' | sed 's/{SCREENSHOT_CNC}/ type="default"/g' | sed 's/{SCREENSHOT_D2K}//g'> packaging/linux/openra-cnc.appdata.xml
@$(INSTALL_DATA) packaging/linux/openra-cnc.appdata.xml "$(DESTDIR)$(datadir)/appdata/"
@sed 's/{MODID}/d2k/g' packaging/linux/openra.appdata.xml.in | sed 's/{MOD_NAME}/Dune 2000/g' | sed 's/{SCREENSHOT_RA}//g' | sed 's/{SCREENSHOT_CNC}//g' | sed 's/{SCREENSHOT_D2K}/ type="default"/g'> packaging/linux/openra-d2k.appdata.xml
@$(INSTALL_DATA) packaging/linux/openra-d2k.appdata.xml "$(DESTDIR)$(datadir)/appdata/"
@-$(RM) packaging/linux/openra-ra.appdata.xml packaging/linux/openra-cnc.appdata.xml packaging/linux/openra-d2k.appdata.xml
install-man: all
@mkdir -p $(DESTDIR)$(mandir)/man6/
@./utility.sh all --man-page > $(DESTDIR)$(mandir)/man6/openra.6
install-man-page:
@$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man6/"
@mono --debug OpenRA.Utility.exe all --man-page > openra.6
@$(INSTALL_DATA) openra.6 "$(DESTDIR)$(mandir)/man6/"
@-$(RM) openra.6
install-linux-scripts:
ifeq ($(DEBUG), $(filter $(DEBUG),false no n off 0))
@sed 's/{DEBUG}//' packaging/linux/openra.in | sed 's|{GAME_INSTALL_DIR}|$(gameinstalldir)|' | sed 's|{BIN_DIR}|$(bindir)|' > packaging/linux/openra.debug.in
@sed 's/{DEBUG}//' packaging/linux/openra-server.in | sed 's|{GAME_INSTALL_DIR}|$(gameinstalldir)|' | sed 's|{BIN_DIR}|$(bindir)|' > packaging/linux/openra-server.debug.in
else
@sed 's/{DEBUG}/--debug/' packaging/linux/openra.in | sed 's|{GAME_INSTALL_DIR}|$(gameinstalldir)|' | sed 's|{BIN_DIR}|$(bindir)|' > packaging/linux/openra.debug.in
@sed 's/{DEBUG}/--debug/' packaging/linux/openra-server.in | sed 's|{GAME_INSTALL_DIR}|$(gameinstalldir)|' | sed 's|{BIN_DIR}|$(bindir)|' > packaging/linux/openra-server.debug.in
endif
@sed 's/{MODID}/ra/g' packaging/linux/openra.debug.in | sed 's/{TAG}/$(VERSION)/g' | sed 's/{MODNAME}/Red Alert/g' > packaging/linux/openra-ra
@sed 's/{MODID}/cnc/g' packaging/linux/openra.debug.in | sed 's/{TAG}/$(VERSION)/g' | sed 's/{MODNAME}/Tiberian Dawn/g' > packaging/linux/openra-cnc
@sed 's/{MODID}/d2k/g' packaging/linux/openra.debug.in | sed 's/{TAG}/$(VERSION)/g' | sed 's/{MODNAME}/Dune 2000/g' > packaging/linux/openra-d2k
@$(INSTALL_DIR) "$(BIN_INSTALL_DIR)"
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-ra "$(BIN_INSTALL_DIR)"
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-cnc "$(BIN_INSTALL_DIR)"
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-d2k "$(BIN_INSTALL_DIR)"
@-$(RM) packaging/linux/openra-ra packaging/linux/openra-cnc packaging/linux/openra-d2k packaging/linux/openra.debug.in
@sed 's/{MODID}/ra/g' packaging/linux/openra-server.debug.in | sed 's/{MODNAME}/Red Alert/g' > packaging/linux/openra-ra-server
@sed 's/{MODID}/cnc/g' packaging/linux/openra-server.debug.in | sed 's/{MODNAME}/Tiberian Dawn/g' > packaging/linux/openra-cnc-server
@sed 's/{MODID}/d2k/g' packaging/linux/openra-server.debug.in | sed 's/{MODNAME}/Dune 2000/g' > packaging/linux/openra-d2k-server
@$(INSTALL_DIR) "$(BIN_INSTALL_DIR)"
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-ra-server "$(BIN_INSTALL_DIR)"
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-cnc-server "$(BIN_INSTALL_DIR)"
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-d2k-server "$(BIN_INSTALL_DIR)"
@-$(RM) packaging/linux/openra-ra-server packaging/linux/openra-cnc-server packaging/linux/openra-d2k-server packaging/linux/openra-server.debug.in
uninstall:
@-$(RM_R) "$(DATA_INSTALL_DIR)"
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-ra"
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-ra-server"
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-cnc"
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-cnc-server"
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-d2k"
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-d2k-server"
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra-ra.desktop"
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra-cnc.desktop"
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra-d2k.desktop"
@-for SIZE in 16x16 32x32 48x48 64x64 128x128; do \
$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-ra.png"; \
$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-cnc.png"; \
$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-d2k.png"; \
done
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-ra.svg"
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-cnc.svg"
@-$(RM_F) "$(DESTDIR)$(datadir)/mime/packages/openra-ra.xml"
@-$(RM_F) "$(DESTDIR)$(datadir)/mime/packages/openra-cnc.xml"
@-$(RM_F) "$(DESTDIR)$(datadir)/mime/packages/openra-d2k.xml"
@-$(RM_F) "$(DESTDIR)$(datadir)/appdata/openra-ra.appdata.xml"
@-$(RM_F) "$(DESTDIR)$(datadir)/appdata/openra-cnc.appdata.xml"
@-$(RM_F) "$(DESTDIR)$(datadir)/appdata/openra-d2k.appdata.xml"
@-$(RM_F) "$(DESTDIR)$(mandir)/man6/openra.6"
help:
@echo 'to compile, run:'
@echo ' make'
@echo
@echo 'to compile using Mono (version 6.12 or greater) instead of .NET 6, run:'
@echo ' make RUNTIME=mono'
@echo ' make [DEBUG=true]'
@echo
@echo 'to compile using system libraries for native dependencies, run:'
@echo ' make [RUNTIME=net6] TARGETPLATFORM=unix-generic'
@echo ' make [DEBUG=true] TARGETPLATFORM=unix-generic'
@echo
@echo 'to check the official mods for erroneous yaml files, run:'
@echo ' make [RUNTIME=net6] [TREAT_WARNINGS_AS_ERRORS=false] test'
@echo ' make test'
@echo
@echo 'to check the engine and official mod dlls for code style violations, run:'
@echo ' make [RUNTIME=net6] check'
@echo ' make test'
@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 Linux startup scripts, desktop files and icons'
@echo ' make install-linux-shortcuts [DEBUG=false]'
@echo
@echo 'to install FreeDesktop startup scripts, desktop files, icons, and MIME metadata'
@echo ' make install-linux-shortcuts'
@echo ' to install the engine and common mod files (omitting the default mods):'
@echo ' make install-engine'
@echo ' make install-dependencies'
@echo ' make install-common-mod-files'
@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 ##########################
#
@@ -212,4 +368,4 @@ help:
.SUFFIXES:
.PHONY: all clean check check-scripts test version install install-linux-shortcuts install-linux-appdata install-man help
.PHONY: check-scripts check test all core clean version install install-linux-shortcuts install-dependencies install-engine install-common-mod-files install-default-mods install-linux-icons install-linux-desktop install-linux-mime install-linux-appdata install-man-page install-linux-scripts uninstall help

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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
@@ -26,7 +26,7 @@ namespace OpenRA.Activities
public readonly Color Color;
public readonly Sprite Tile;
public TargetLineNode(in Target target, Color color, Sprite tile = null)
public TargetLineNode(Target target, Color color, Sprite tile = null)
{
// Note: Not all activities are drawable. In that case, pass Target.Invalid as target,
// if "yield break" in TargetLineNode(Actor self) is not feasible.
@@ -53,15 +53,15 @@ namespace OpenRA.Activities
Activity childActivity;
protected Activity ChildActivity
{
get => SkipDoneActivities(childActivity);
private set => childActivity = value;
get { return SkipDoneActivities(childActivity); }
private set { childActivity = value; }
}
Activity nextActivity;
public Activity NextActivity
{
get => SkipDoneActivities(nextActivity);
private set => nextActivity = value;
get { return SkipDoneActivities(nextActivity); }
private set { nextActivity = value; }
}
internal static Activity SkipDoneActivities(Activity first)
@@ -74,19 +74,19 @@ namespace OpenRA.Activities
// 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;
first = first.NextActivity;
return first;
}
public bool IsInterruptible { get; protected set; }
public bool ChildHasPriority { get; protected set; }
public bool IsCanceling => State == ActivityState.Canceling;
public bool IsCanceling { get { return State == ActivityState.Canceling; } }
bool finishing;
bool firstRunCompleted;
bool lastRun;
protected Activity()
public Activity()
{
IsInterruptible = true;
ChildHasPriority = true;
@@ -95,7 +95,7 @@ namespace OpenRA.Activities
public Activity TickOuter(Actor self)
{
if (State == ActivityState.Done)
throw new InvalidOperationException($"Actor {self} attempted to tick activity {GetType()} after it had already completed.");
throw new InvalidOperationException("Actor {0} attempted to tick activity {1} after it had already completed.".F(self, GetType()));
if (State == ActivityState.Queued)
{
@@ -105,7 +105,7 @@ namespace OpenRA.Activities
}
if (!firstRunCompleted)
throw new InvalidOperationException($"Actor {self} attempted to tick activity {GetType()} before running its OnFirstRun method.");
throw new InvalidOperationException("Actor {0} attempted to tick activity {1} before running its OnFirstRun method.".F(self, GetType()));
// Only run the parent tick when the child is done.
// We must always let the child finish on its own before continuing.
@@ -120,8 +120,7 @@ namespace OpenRA.Activities
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 (ChildActivity != null && ChildActivity.State == ActivityState.Queued)
{
if (ChildHasPriority)
lastRun = TickChild(self) && finishing;
@@ -207,18 +206,18 @@ namespace OpenRA.Activities
public void Queue(Activity activity)
{
var it = this;
while (it.nextActivity != null)
it = it.nextActivity;
it.nextActivity = activity;
if (NextActivity != null)
NextActivity.Queue(activity);
else
NextActivity = activity;
}
public void QueueChild(Activity activity)
{
if (childActivity != null)
childActivity.Queue(activity);
if (ChildActivity != null)
ChildActivity.Queue(activity);
else
childActivity = activity;
ChildActivity = activity;
}
/// <summary>
@@ -270,21 +269,15 @@ namespace OpenRA.Activities
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 (includeChildren && ChildActivity != null)
foreach (var a in ChildActivity.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>())
if (NextActivity != null)
foreach (var a in NextActivity.ActivitiesImplementing<T>())
yield return a;
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,11 +22,11 @@ namespace OpenRA.Activities
IsInterruptible = interruptible;
}
readonly Action a;
Action a;
public override bool Tick(Actor self)
{
a.Invoke();
a?.Invoke();
return true;
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,7 +11,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using Eluant;
@@ -24,21 +23,9 @@ 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
{
/// <summary>Value used to represent an invalid token.</summary>
public const int InvalidConditionToken = -1;
internal readonly struct SyncHash
internal struct SyncHash
{
public readonly ISync Trait;
readonly Func<object, int> hashFunction;
@@ -61,53 +48,60 @@ namespace OpenRA
Activity currentActivity;
public Activity CurrentActivity
{
get => Activity.SkipDoneActivities(currentActivity);
private set => currentActivity = value;
get { return Activity.SkipDoneActivities(currentActivity); }
private set { currentActivity = value; }
}
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 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;
sealed 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
{
return facing != null ? facing.Orientation : WRot.None;
}
}
readonly Dictionary<string, ConditionState> conditionStates = new();
/// <summary>Value used to represent an invalid token.</summary>
public static readonly int InvalidConditionToken = -1;
class ConditionState
{
/// <summary>Delegates that have registered to be notified when this condition changes.</summary>
public readonly List<VariableObserverNotifier> Notifiers = new List<VariableObserverNotifier>();
/// <summary>Unique integers identifying granted instances of the condition.</summary>
public readonly HashSet<int> Tokens = new HashSet<int>();
}
readonly Dictionary<string, ConditionState> conditionStates = new Dictionary<string, ConditionState>();
/// <summary>Each granted condition receives a unique token that is used when revoking.</summary>
readonly Dictionary<int, string> conditionTokens = new();
readonly Dictionary<int, string> conditionTokens = new Dictionary<int, string>();
int nextConditionToken = 1;
/// <summary>Cache of condition -> enabled state for quick evaluation of token counter conditions.</summary>
readonly Dictionary<string, int> conditionCache = new();
readonly Dictionary<string, int> conditionCache = new Dictionary<string, int>();
/// <summary>Read-only version of conditionCache that is passed to IConditionConsumers.</summary>
readonly IReadOnlyDictionary<string, int> readOnlyConditionCache;
internal SyncHash[] SyncHashes { get; }
internal SyncHash[] SyncHashes { get; private set; }
readonly IFacing facing;
readonly IHealth health;
readonly IResolveOrder[] resolveOrders;
readonly IRenderModifier[] renderModifiers;
readonly IRender[] renders;
readonly IMouseBounds[] mouseBounds;
@@ -115,7 +109,8 @@ namespace OpenRA
readonly IDefaultVisibility defaultVisibility;
readonly INotifyBecomingIdle[] becomingIdles;
readonly INotifyIdle[] tickIdles;
readonly IEnumerable<WPos> enabledTargetableWorldPositions;
readonly ITargetablePositions[] targetablePositions;
WPos[] staticTargetablePositions;
bool created;
internal Actor(World world, string name, TypeDictionary initDict)
@@ -124,7 +119,7 @@ namespace OpenRA
.FirstOrDefault(i => i.Count() > 1);
if (duplicateInit != null)
throw new InvalidDataException($"Duplicate initializer '{duplicateInit.Key.Name}'");
throw new InvalidDataException("Duplicate initializer '{0}'".F(duplicateInit.Key.Name));
var init = new ActorInitializer(this, initDict);
@@ -144,58 +139,42 @@ 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.
EffectiveOwner = TraitOrDefault<IEffectiveOwner>();
facing = TraitOrDefault<IFacing>();
health = TraitOrDefault<IHealth>();
renderModifiers = TraitsImplementing<IRenderModifier>().ToArray();
renders = TraitsImplementing<IRender>().ToArray();
mouseBounds = TraitsImplementing<IMouseBounds>().ToArray();
visibilityModifiers = TraitsImplementing<IVisibilityModifier>().ToArray();
defaultVisibility = Trait<IDefaultVisibility>();
becomingIdles = TraitsImplementing<INotifyBecomingIdle>().ToArray();
tickIdles = TraitsImplementing<INotifyIdle>().ToArray();
Targetables = TraitsImplementing<ITargetable>().ToArray();
targetablePositions = TraitsImplementing<ITargetablePositions>().ToArray();
world.AddFrameEndTask(w =>
{
// Caching this in a AddFrameEndTask, because trait construction order might cause problems if done directly at creation time.
// All actors that can move or teleport should have IPositionable, if not it's pretty safe to assume the actor is completely immobile and
// all targetable positions can be cached if all ITargetablePositions have no conditional requirements.
if (!Info.HasTraitInfo<IPositionableInfo>() && targetablePositions.Any() && targetablePositions.All(tp => tp.AlwaysEnabled))
staticTargetablePositions = targetablePositions.SelectMany(tp => tp.TargetablePositions(this)).ToArray();
});
SyncHashes = TraitsImplementing<ISync>().Select(sync => new SyncHash(sync)).ToArray();
}
internal void Initialize(bool addToWorld = true)
@@ -229,7 +208,7 @@ namespace OpenRA
foreach (var notify in allObserverNotifiers)
notify(this, readOnlyConditionCache);
// TODO: Other traits may need initialization after being notified of initial condition state.
// TODO: Some traits may need initialization after being notified of initial condition state.
// TODO: A post condition initialization notification phase may allow queueing activities instead.
// The initial activity should run before any activities queued by INotifyCreated.Created
@@ -241,7 +220,7 @@ namespace OpenRA
continue;
if (creationActivity != null)
throw new InvalidOperationException($"More than one enabled ICreationActivity trait: {creationActivity.GetType().Name} and {ica.GetType().Name}");
throw new InvalidOperationException("More than one enabled ICreationActivity trait: {0} and {1}".F(creationActivity.GetType().Name, ica.GetType().Name));
var activity = ica.GetCreationActivity();
if (activity == null)
@@ -360,7 +339,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)
@@ -424,12 +404,6 @@ namespace OpenRA
});
}
public void ResolveOrder(Order order)
{
foreach (var r in resolveOrders)
r.ResolveOrder(this, order);
}
// TODO: move elsewhere.
public void ChangeOwner(Player newOwner)
{
@@ -481,7 +455,7 @@ namespace OpenRA
health.InflictDamage(this, attacker, damage, false);
}
public void Kill(Actor attacker, BitSet<DamageType> damageTypes = default)
public void Kill(Actor attacker, BitSet<DamageType> damageTypes = default(BitSet<DamageType>))
{
if (Disposed || health == null)
return;
@@ -522,7 +496,7 @@ namespace OpenRA
{
// PERF: Avoid LINQ.
foreach (var targetable in Targetables)
if (targetable.TargetableBy(this, byActor))
if (targetable.IsTraitEnabled() && targetable.TargetableBy(this, byActor))
return true;
return false;
@@ -530,8 +504,12 @@ namespace OpenRA
public IEnumerable<WPos> GetTargetablePositions()
{
if (EnabledTargetablePositions.Any())
return enabledTargetableWorldPositions;
if (staticTargetablePositions != null)
return staticTargetablePositions;
var enabledTargetablePositionTraits = targetablePositions.Where(Exts.IsTraitEnabled);
if (enabledTargetablePositionTraits.Any())
return enabledTargetablePositionTraits.SelectMany(tp => tp.TargetablePositions(this));
return new[] { CenterPosition };
}
@@ -540,7 +518,7 @@ namespace OpenRA
void UpdateConditionState(string condition, int token, bool isRevoke)
{
var conditionState = conditionStates.GetOrAdd(condition);
ConditionState conditionState = conditionStates.GetOrAdd(condition);
if (isRevoke)
conditionState.Tokens.Remove(token);
@@ -580,14 +558,14 @@ namespace OpenRA
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}.");
throw new InvalidOperationException("Attempting to revoke condition with invalid token {0} for {1}.".F(token, this));
conditionTokens.Remove(token);
UpdateConditionState(condition, token, true);
return InvalidConditionToken;
}
/// <summary>Returns whether the specified token is valid for RevokeCondition.</summary>
/// <summary>Returns whether the specified token is valid for RevokeCondition</summary>
public bool TokenValid(int token)
{
return conditionTokens.ContainsKey(token);
@@ -600,13 +578,14 @@ namespace OpenRA
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)
@@ -619,7 +598,7 @@ namespace OpenRA
public LuaValue ToString(LuaRuntime runtime)
{
return $"Actor ({this})";
return "Actor ({0})".F(this);
}
public bool HasScriptProperty(string name)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,7 +16,7 @@ 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
@@ -25,13 +25,13 @@ namespace OpenRA
public readonly int Bits;
// X is padded to MSB, so bit shift does the correct sign extension
public int X => Bits >> 20;
public int X { get { return 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 int Y { get { return ((short)(Bits >> 4)) >> 4; } }
public byte Layer => (byte)Bits;
public byte Layer { get { return (byte)Bits; } }
public CPos(int bits) { Bits = bits; }
public CPos(int x, int y)
@@ -41,7 +41,7 @@ namespace OpenRA
Bits = (x & 0xFFF) << 20 | (y & 0xFFF) << 8 | layer;
}
public static readonly CPos Zero = new(0, 0, 0);
public static readonly CPos Zero = new CPos(0, 0, 0);
public static explicit operator CPos(int2 a) { return new CPos(a.X, a.Y); }
@@ -56,15 +56,9 @@ namespace OpenRA
public override int GetHashCode() { return Bits.GetHashCode(); }
public bool Equals(CPos other) { return Bits == other.Bits; }
public override bool Equals(object obj) { return obj is CPos cell && Equals(cell); }
public override bool Equals(object obj) { return obj is CPos && Equals((CPos)obj); }
public override string ToString()
{
if (Layer == 0)
return X + "," + Y;
return X + "," + Y + "," + Layer;
}
public override string ToString() { return X + "," + Y; }
public MPos ToMPos(Map map)
{
@@ -96,7 +90,7 @@ 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})");
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);
}
@@ -105,7 +99,7 @@ namespace OpenRA
{
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})");
throw new LuaException("Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, rightType.Name));
if (rightType == typeof(CPos))
{
@@ -118,7 +112,7 @@ namespace OpenRA
return new LuaCustomClrObject(a - b);
}
throw new LuaException($"Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments ({left.WrappedClrType().Name}, {rightType.Name})");
throw new LuaException("Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, rightType.Name));
}
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
@@ -138,11 +132,14 @@ 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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,12 +17,12 @@ 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; }
@@ -76,7 +76,7 @@ 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})");
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);
}
@@ -84,7 +84,7 @@ namespace OpenRA
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})");
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);
}
@@ -110,11 +110,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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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
namespace OpenRA.Mods.Common.Installer
namespace OpenRA
{
public enum Availability
public interface ICacheStorage<T>
{
Unavailable,
GameSource,
DigitalInstall
void Remove(string key);
void Store(string key, T data);
T Retrieve(string key);
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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
@@ -187,10 +187,8 @@ namespace OpenRA
}
catch (Exception e)
{
Log.Write("debug", "Failed to decrypt string with exception:");
Log.Write("debug", e);
Console.WriteLine("String decryption failed:");
Console.WriteLine(e);
Log.Write("debug", "Failed to decrypt string with exception: {0}", e);
Console.WriteLine("String decryption failed: {0}", e);
return null;
}
}
@@ -213,10 +211,8 @@ namespace OpenRA
}
catch (Exception e)
{
Log.Write("debug", "Failed to sign string with exception");
Log.Write("debug", e);
Console.WriteLine("String signing failed:");
Console.WriteLine(e);
Log.Write("debug", "Failed to sign string with exception: {0}", e);
Console.WriteLine("String signing failed: {0}", e);
return null;
}
}
@@ -239,10 +235,8 @@ namespace OpenRA
}
catch (Exception e)
{
Log.Write("debug", "Failed to verify signature with exception:");
Log.Write("debug", e);
Console.WriteLine("Signature validation failed:");
Console.WriteLine(e);
Log.Write("debug", "Failed to verify signature with exception: {0}", e);
Console.WriteLine("Signature validation failed: {0}", e);
return false;
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,6 +15,6 @@ namespace OpenRA
{
public class DefaultPlayer : IGlobalModData
{
public readonly Color Color = Color.FromArgb(0xEE, 0xEE, 0xEE);
public readonly Color Color = Color.FromAhsl(0, 0, 238);
}
}

96
OpenRA.Game/Download.cs Normal file
View File

@@ -0,0 +1,96 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 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;
}
}
void EnableTLS12OnWindows()
{
// Enable TLS 1.2 on Windows: .NET 4.7 on Windows 10 only supports obsolete protocols by default
// SecurityProtocolType.Tls12 is not defined in the .NET 4.5 reference dlls used by mono,
// so we must use the enum's constant value directly
if (Platform.CurrentPlatform == PlatformType.Windows)
ServicePointManager.SecurityProtocol |= (SecurityProtocolType)3072;
}
public Download(string url, string path, Action<DownloadProgressChangedEventArgs> onProgress, Action<AsyncCompletedEventArgs> onComplete)
{
EnableTLS12OnWindows();
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)
{
EnableTLS12OnWindows();
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)
wc?.CancelAsync();
}
}
}

View File

@@ -0,0 +1,39 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 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; }
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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
@@ -41,7 +41,7 @@ namespace OpenRA
public class ExternalMods : IReadOnlyDictionary<string, ExternalMod>
{
readonly Dictionary<string, ExternalMod> mods = new();
readonly Dictionary<string, ExternalMod> mods = new Dictionary<string, ExternalMod>();
readonly SheetBuilder sheetBuilder;
Sheet CreateSheet()
@@ -66,7 +66,12 @@ namespace OpenRA
// Several types of support directory types are available, depending on
// how the player has installed and launched the game.
// Read registration metadata from all of them
foreach (var source in GetSupportDirs(ModRegistration.User | ModRegistration.System))
var sources = Enum.GetValues(typeof(SupportDirType))
.Cast<SupportDirType>()
.Select(t => Platform.GetSupportDir(t))
.Distinct();
foreach (var source in sources)
{
var metadataPath = Path.Combine(source, "ModMetadata");
if (!Directory.Exists(metadataPath))
@@ -81,8 +86,8 @@ namespace OpenRA
}
catch (Exception e)
{
Log.Write("debug", $"Failed to parse mod metadata file '{path}'");
Log.Write("debug", e);
Log.Write("debug", "Failed to parse mod metadata file '{0}'", path);
Log.Write("debug", e.ToString());
}
}
}
@@ -116,7 +121,7 @@ namespace OpenRA
mods[key] = mod;
}
internal void Register(Manifest mod, string launchPath, IEnumerable<string> launchArgs, ModRegistration registration)
internal void Register(Manifest mod, string launchPath, ModRegistration registration)
{
if (mod.Metadata.Hidden)
return;
@@ -128,7 +133,7 @@ namespace OpenRA
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(", "))
new MiniYamlNode("LaunchArgs", "Game.Mod=" + mod.Id)
}));
using (var stream = mod.Package.GetStream("icon.png"))
@@ -143,7 +148,7 @@ namespace OpenRA
if (stream != null)
yaml.Value.Nodes.Add(new MiniYamlNode("Icon3x", Convert.ToBase64String(stream.ReadAllBytes())));
var sources = new HashSet<string>();
var sources = new List<string>();
if (registration.HasFlag(ModRegistration.System))
sources.Add(Platform.GetSupportDir(SupportDirType.System));
@@ -162,7 +167,7 @@ namespace OpenRA
LoadMod(yaml.Value, forceRegistration: true);
var lines = new List<MiniYamlNode> { yaml }.ToLines().ToArray();
foreach (var source in sources)
foreach (var source in sources.Distinct())
{
var metadataPath = Path.Combine(source, "ModMetadata");
@@ -174,23 +179,35 @@ namespace OpenRA
catch (Exception e)
{
Log.Write("debug", "Failed to register current mod metadata");
Log.Write("debug", e);
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>
/// * LaunchPath no longer exists
/// * LaunchPath and mod id matches the active mod, but the version is different
/// * Filename doesn't match internal key
/// * Fails to parse as a mod registration
/// </summary>
internal void ClearInvalidRegistrations(ModRegistration registration)
internal void ClearInvalidRegistrations(ExternalMod activeMod, ModRegistration registration)
{
foreach (var source in GetSupportDirs(registration))
var sources = new List<string>();
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 .Distinct() below ignore the duplicates
sources.Add(Platform.GetSupportDir(SupportDirType.User));
sources.Add(Platform.GetSupportDir(SupportDirType.ModernUser));
sources.Add(Platform.GetSupportDir(SupportDirType.LegacyUser));
}
var activeModKey = ExternalMod.MakeKey(activeMod);
foreach (var source in sources.Distinct())
{
var metadataPath = Path.Combine(source, "ModMetadata");
if (!Directory.Exists(metadataPath))
@@ -205,16 +222,19 @@ namespace OpenRA
var m = FieldLoader.Load<ExternalMod>(yaml);
modKey = ExternalMod.MakeKey(m);
// Continue to the next entry if it is the active mod (even if the LaunchPath is bogus)
if (modKey == activeModKey)
continue;
// 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")
if (File.Exists(m.LaunchPath) && Path.GetFileNameWithoutExtension(path) == modKey &&
!(activeMod != null && m.LaunchPath == activeMod.LaunchPath && m.Id == activeMod.Id && m.Version != activeMod.Version))
continue;
}
catch (Exception e)
{
Log.Write("debug", $"Failed to parse mod metadata file '{path}'");
Log.Write("debug", e);
Log.Write("debug", "Failed to parse mod metadata file '{0}'", path);
Log.Write("debug", e.ToString());
}
// Remove from the ingame mod switcher
@@ -225,12 +245,12 @@ namespace OpenRA
try
{
File.Delete(path);
Log.Write("debug", $"Removed invalid mod metadata file '{path}'");
Log.Write("debug", "Removed invalid mod metadata file '{0}'", path);
}
catch (Exception e)
{
Log.Write("debug", $"Failed to remove mod metadata file '{path}'");
Log.Write("debug", e);
Log.Write("debug", "Failed to remove mod metadata file '{0}'", path);
Log.Write("debug", e.ToString());
}
}
}
@@ -238,10 +258,23 @@ namespace OpenRA
internal void Unregister(Manifest mod, ModRegistration registration)
{
var sources = new List<string>();
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 .Distinct() below ignore the duplicates
sources.Add(Platform.GetSupportDir(SupportDirType.User));
sources.Add(Platform.GetSupportDir(SupportDirType.ModernUser));
sources.Add(Platform.GetSupportDir(SupportDirType.LegacyUser));
}
var key = ExternalMod.MakeKey(mod);
mods.Remove(key);
foreach (var source in GetSupportDirs(registration))
foreach (var source in sources.Distinct())
{
var path = Path.Combine(source, "ModMetadata", key + ".yaml");
try
@@ -251,39 +284,16 @@ namespace OpenRA
}
catch (Exception e)
{
Log.Write("debug", $"Failed to remove mod metadata file '{path}'");
Log.Write("debug", e);
Log.Write("debug", "Failed to remove mod metadata file '{0}'", path);
Log.Write("debug", e.ToString());
}
}
}
static IEnumerable<string> GetSupportDirs(ModRegistration registration)
{
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 ExternalMod this[string key] { get { return mods[key]; } }
public int Count { get { return mods.Count; } }
public ICollection<string> Keys { get { return mods.Keys; } }
public ICollection<ExternalMod> Values { get { return 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); }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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
@@ -27,12 +27,23 @@ namespace OpenRA
return string.Compare(str.ToUpperInvariant(), str, false) == 0;
}
public static string F(this string fmt, params object[] args)
{
return string.Format(fmt, args);
}
public static T WithDefault<T>(T def, Func<T> f)
{
try { return f(); }
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)
@@ -40,16 +51,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>
@@ -91,7 +107,7 @@ namespace OpenRA
// - 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
// Assumes that lines are not colinear
return WindingDirectionTest(c, d, a) != WindingDirectionTest(c, d, b) && WindingDirectionTest(a, b, c) != WindingDirectionTest(a, b, d);
}
@@ -104,28 +120,11 @@ 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 NET5_0_OR_GREATER
// SAFETY: Dictionary cannot be modified whilst the ref is alive.
ref var value = ref System.Runtime.InteropServices.CollectionsMarshal.GetValueRefOrAddDefault(d, k, out var exists);
if (!exists)
value = v;
return value;
#else
if (!d.TryGetValue(k, out var ret))
d.Add(k, ret = v);
return ret;
#endif
return d.GetOrAdd(k, _ => new V());
}
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k, Func<K, V> createFn)
{
// Cannot use CollectionsMarshal.GetValueRefOrAddDefault here,
// the creation function could mutate the dictionary which would invalidate the ref.
if (!d.TryGetValue(k, out var ret))
d.Add(k, ret = createFn(k));
return ret;
@@ -149,13 +148,13 @@ 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));
@@ -230,9 +229,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())
@@ -272,7 +271,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);
}
@@ -313,7 +312,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);
}
@@ -351,11 +350,6 @@ 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);
@@ -374,11 +368,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);
@@ -396,13 +385,12 @@ 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);
@@ -413,28 +401,29 @@ namespace OpenRA
continue;
// Check for a key conflict:
if (!d.TryAdd(key, element))
if (d.ContainsKey(key))
{
if (!dupKeys.TryGetValue(key, out var 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);
}
@@ -515,7 +504,8 @@ namespace OpenRA
public static bool IsTraitEnabled<T>(this T trait)
{
return trait is not IDisabledTrait disabledTrait || !disabledTrait.IsTraitDisabled;
var disabledTrait = trait as IDisabledTrait;
return disabledTrait == null || !disabledTrait.IsTraitDisabled;
}
public static T FirstEnabledTraitOrDefault<T>(this IEnumerable<T> ts)
@@ -525,7 +515,7 @@ namespace OpenRA
if (t.IsTraitEnabled())
return t;
return default;
return default(T);
}
public static T FirstEnabledTraitOrDefault<T>(this T[] ts)
@@ -535,77 +525,8 @@ namespace OpenRA
if (t.IsTraitEnabled())
return t;
return default;
return default(T);
}
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 static bool TryParseInt32Invariant(string s, out int i)
{
return int.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
}
}
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>
@@ -621,7 +542,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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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
@@ -72,14 +72,32 @@ namespace OpenRA
return "";
var t = v.GetType();
if (t == typeof(Color))
{
return ((Color)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.IsGenericType && t.GetGenericTypeDefinition() == typeof(BitSet<>))
{
return ((IEnumerable<string>)v).Select(FormatValue).JoinWith(", ");
}
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 +112,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);

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,6 @@ 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
@@ -26,14 +25,11 @@ namespace OpenRA.FileFormats
{
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 int Width { get; set; }
public int Height { get; set; }
public Color[] Palette { get; set; }
public byte[] Data { get; set; }
public Dictionary<string, string> EmbeddedData = new Dictionary<string, string>();
public Png(Stream s)
{
@@ -42,8 +38,9 @@ namespace OpenRA.FileFormats
s.Position += 8;
var headerParsed = false;
var isPaletted = false;
var is24Bit = false;
var data = new List<byte>();
Type = SpriteFrameType.Rgba32;
while (true)
{
@@ -67,17 +64,19 @@ namespace OpenRA.FileFormats
Height = IPAddress.NetworkToHostOrder(ms.ReadInt32());
var bitDepth = ms.ReadUInt8();
var colorType = (PngColorType)ms.ReadUInt8();
if (IsPaletted(bitDepth, colorType))
Type = SpriteFrameType.Indexed8;
else if (colorType == PngColorType.Color)
Type = SpriteFrameType.Rgb24;
var colorType = (PngColorType)ms.ReadByte();
isPaletted = IsPaletted(bitDepth, colorType);
is24Bit = colorType == PngColorType.Color;
Data = new byte[Width * Height * PixelStride];
var dataLength = Width * Height;
if (!isPaletted)
dataLength *= 4;
var compression = ms.ReadUInt8();
/*var filter = */ms.ReadUInt8();
var interlace = ms.ReadUInt8();
Data = new byte[dataLength];
var compression = ms.ReadByte();
/*var filter = */ms.ReadByte();
var interlace = ms.ReadByte();
if (compression != 0)
throw new InvalidDataException("Compression method not supported");
@@ -95,7 +94,7 @@ namespace OpenRA.FileFormats
Palette = new Color[256];
for (var i = 0; i < length / 3; i++)
{
var r = ms.ReadUInt8(); var g = ms.ReadUInt8(); var b = ms.ReadUInt8();
var r = ms.ReadByte(); var g = ms.ReadByte(); var b = ms.ReadByte();
Palette[i] = Color.FromArgb(r, g, b);
}
@@ -108,7 +107,7 @@ namespace OpenRA.FileFormats
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
for (var i = 0; i < length; i++)
Palette[i] = Color.FromArgb(ms.ReadUInt8(), Palette[i]);
Palette[i] = Color.FromArgb(ms.ReadByte(), Palette[i]);
break;
}
@@ -134,63 +133,39 @@ namespace OpenRA.FileFormats
{
using (var ds = new InflaterInputStream(ns))
{
var pxStride = PixelStride;
var rowStride = Width * pxStride;
var pxStride = isPaletted ? 1 : is24Bit ? 3 : 4;
var srcStride = Width * pxStride;
var destStride = Width * (isPaletted ? 1 : 4);
Span<byte> prevLine = new byte[rowStride];
var prevLine = new byte[srcStride];
for (var y = 0; y < Height; y++)
{
var filter = (PngFilter)ds.ReadUInt8();
ds.ReadBytes(Data, y * rowStride, rowStride);
var line = Data.AsSpan(y * rowStride, rowStride);
var filter = (PngFilter)ds.ReadByte();
var line = ds.ReadBytes(srcStride);
switch (filter)
for (var i = 0; i < srcStride; 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]);
if (is24Bit)
{
case PngFilter.None:
break;
case PngFilter.Sub:
for (var i = pxStride; i < rowStride; i++)
line[i] += line[i - pxStride];
break;
case PngFilter.Up:
for (var i = 0; i < rowStride; i++)
line[i] += prevLine[i];
break;
case PngFilter.Average:
for (var i = 0; i < pxStride; i++)
line[i] += Average(0, prevLine[i]);
for (var i = pxStride; i < rowStride; i++)
line[i] += Average(line[i - pxStride], prevLine[i]);
break;
case PngFilter.Paeth:
for (var i = 0; i < pxStride; i++)
line[i] += Paeth(0, prevLine[i], 0);
for (var i = pxStride; i < rowStride; i++)
line[i] += Paeth(line[i - pxStride], prevLine[i], prevLine[i - pxStride]);
break;
default:
throw new InvalidOperationException("Unsupported Filter");
// Fold alpha channel into RGB data
for (var i = 0; i < line.Length / 3; i++)
{
Array.Copy(line, 3 * i, Data, y * destStride + 4 * i, 3);
Data[y * destStride + 4 * i + 3] = 255;
}
}
else
Array.Copy(line, 0, Data, y * destStride, line.Length);
prevLine = line;
}
static byte Average(byte a, byte b) => (byte)((a + b) / 2);
static byte Paeth(byte a, byte b, byte c)
{
var p = a + b - c;
var pa = Math.Abs(p - a);
var pb = Math.Abs(p - b);
var pc = Math.Abs(p - c);
return (pa <= pb && pa <= pc) ? a :
(pb <= pc) ? b : c;
}
}
}
if (Type == SpriteFrameType.Indexed8 && Palette == null)
if (isPaletted && Palette == null)
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
return;
@@ -200,7 +175,7 @@ namespace OpenRA.FileFormats
}
}
public Png(byte[] data, SpriteFrameType type, int width, int height, Color[] palette = null,
public Png(byte[] data, int width, int height, Color[] palette = null,
Dictionary<string, string> embeddedData = null)
{
var expectLength = width * height;
@@ -210,46 +185,11 @@ namespace OpenRA.FileFormats
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}");
}
Palette = palette;
Data = data;
if (embeddedData != null)
EmbeddedData = embeddedData;
@@ -263,9 +203,34 @@ namespace OpenRA.FileFormats
return isPng;
}
static byte UnapplyFilter(PngFilter f, byte x, byte a, byte b, byte c)
{
switch (f)
{
case PngFilter.None: return x;
case PngFilter.Sub: return (byte)(x + a);
case PngFilter.Up: return (byte)(x + b);
case PngFilter.Average: return (byte)(x + (a + b) / 2);
case PngFilter.Paeth: return (byte)(x + Paeth(a, b, c));
default:
throw new InvalidOperationException("Unsupported Filter");
}
}
static byte Paeth(byte a, byte b, byte c)
{
var p = a + b - c;
var pa = Math.Abs(p - a);
var pb = Math.Abs(p - b);
var pc = Math.Abs(p - c);
return (pa <= pb && pa <= pc) ? a :
(pb <= pc) ? b : c;
}
[Flags]
enum PngColorType : byte { Indexed = 1, Color = 2, Alpha = 4 }
enum PngFilter : byte { None, Sub, Up, Average, Paeth }
enum PngColorType { Indexed = 1, Color = 2, Alpha = 4 }
enum PngFilter { None, Sub, Up, Average, Paeth }
static bool IsPaletted(byte bitDepth, PngColorType colorType)
{
@@ -281,7 +246,7 @@ namespace OpenRA.FileFormats
throw new InvalidDataException("Unknown pixel format");
}
static void WritePngChunk(Stream output, string type, Stream input)
void WritePngChunk(Stream output, string type, Stream input)
{
input.Position = 0;
@@ -309,8 +274,9 @@ namespace OpenRA.FileFormats
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;
var colorType = Palette != null
? PngColorType.Indexed | PngColorType.Color
: PngColorType.Color | PngColorType.Alpha;
header.WriteByte((byte)colorType);
header.WriteByte(0); // Compression
@@ -320,7 +286,7 @@ namespace OpenRA.FileFormats
WritePngChunk(output, "IHDR", header);
}
var alphaPalette = false;
bool alphaPalette = false;
if (Palette != null)
{
using (var palette = new MemoryStream())
@@ -352,12 +318,12 @@ namespace OpenRA.FileFormats
{
using (var compressed = new DeflaterOutputStream(data))
{
var rowStride = Width * PixelStride;
var stride = Width * (Palette != null ? 1 : 4);
for (var y = 0; y < Height; y++)
{
// Write uncompressed scanlines for simplicity
compressed.WriteByte(0);
compressed.Write(Data, y * rowStride, rowStride);
compressed.Write(Data, y * stride, stride);
}
compressed.Flush();

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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);

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,17 +28,17 @@ namespace OpenRA.FileSystem
public class FileSystem : IReadOnlyFileSystem
{
public IEnumerable<IReadOnlyPackage> MountedPackages => mountedPackages.Keys;
readonly Dictionary<IReadOnlyPackage, int> mountedPackages = new();
readonly Dictionary<string, IReadOnlyPackage> explicitMounts = new();
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>();
readonly string modID;
// 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)
{
@@ -63,7 +63,7 @@ namespace OpenRA.FileSystem
{
// 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))
if (!filename.Contains("|") && Directory.Exists(resolvedPath))
return new Folder(resolvedPath);
// Children of another package require special handling
@@ -85,17 +85,17 @@ namespace OpenRA.FileSystem
{
var optional = name.StartsWith("~", StringComparison.Ordinal);
if (optional)
name = name[1..];
name = name.Substring(1);
try
{
IReadOnlyPackage package;
if (name.StartsWith("$", StringComparison.Ordinal))
{
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(", ")}");
throw new InvalidOperationException("Could not load mod '{0}'. Available mods: {1}".F(name, installedMods.Keys.JoinWith(", ")));
package = mod.Package;
modPackages.Add(package);
@@ -104,7 +104,7 @@ namespace OpenRA.FileSystem
{
package = OpenPackage(name);
if (package == null)
throw new InvalidOperationException($"Could not open package '{name}', file not found or its format is not supported.");
throw new InvalidOperationException("Could not open package '{0}', file not found or its format is not supported.".F(name));
}
Mount(package, explicitName);
@@ -203,7 +203,7 @@ namespace OpenRA.FileSystem
public Stream Open(string filename)
{
if (!TryOpen(filename, out var s))
throw new FileNotFoundException($"File not found: {filename}", filename);
throw new FileNotFoundException("File not found: {0}".F(filename), filename);
return s;
}
@@ -211,9 +211,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 +228,9 @@ namespace OpenRA.FileSystem
var explicitSplit = filename.IndexOf('|');
if (explicitSplit > 0)
{
if (explicitMounts.TryGetValue(filename[..explicitSplit], out var explicitPackage))
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out var explicitPackage))
{
s = explicitPackage.GetStream(filename[(explicitSplit + 1)..]);
s = explicitPackage.GetStream(filename.Substring(explicitSplit + 1));
if (s != null)
return true;
}
@@ -263,15 +263,15 @@ namespace OpenRA.FileSystem
{
var explicitSplit = filename.IndexOf('|');
if (explicitSplit > 0)
if (explicitMounts.TryGetValue(filename[..explicitSplit], out var explicitPackage))
if (explicitPackage.Contains(filename[(explicitSplit + 1)..]))
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out var 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.
/// Returns true if the given filename references an external mod via an explicit mount
/// </summary>
public bool IsExternalModFile(string filename)
{
@@ -279,7 +279,7 @@ namespace OpenRA.FileSystem
if (explicitSplit < 0)
return false;
if (!explicitMounts.TryGetValue(filename[..explicitSplit], out var explicitPackage))
if (!explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out var explicitPackage))
return false;
if (installedMods[modID].Package == explicitPackage)
@@ -295,10 +295,10 @@ namespace OpenRA.FileSystem
public static string ResolveAssemblyPath(string path, Manifest manifest, InstalledMods installedMods)
{
var explicitSplit = path.IndexOf('|');
if (explicitSplit > 0 && !path.StartsWith("^"))
if (explicitSplit > 0)
{
var parent = path[..explicitSplit];
var filename = path[(explicitSplit + 1)..];
var parent = path.Substring(0, explicitSplit);
var filename = path.Substring(explicitSplit + 1);
var parentPath = manifest.Packages.FirstOrDefault(kv => kv.Value == parent).Key;
if (parentPath == null)
@@ -306,10 +306,10 @@ namespace OpenRA.FileSystem
if (parentPath.StartsWith("$", StringComparison.Ordinal))
{
if (!installedMods.TryGetValue(parentPath[1..], out var mod))
if (!installedMods.TryGetValue(parentPath.Substring(1), out var mod))
return null;
if (mod.Package is not Folder)
if (!(mod.Package is Folder))
return null;
path = Path.Combine(mod.Package.Name, filename);
@@ -322,28 +322,6 @@ namespace OpenRA.FileSystem
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;

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,47 +12,43 @@
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)
{
var combined = Path.Combine(Name, filename);
if (!File.Exists(combined))
return null;
try { return File.OpenRead(combined); }
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);
var combined = Path.Combine(path, filename);
return combined.StartsWith(path, StringComparison.Ordinal) && File.Exists(combined);
}
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
@@ -84,7 +80,7 @@ namespace OpenRA.FileSystem
// in FileSystem.OpenPackage. Their internal name therefore contains the
// full parent path too. We need to be careful to not add a second path
// prefix to these hacked packages.
var filePath = filename.StartsWith(Name) ? 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))
@@ -98,7 +94,7 @@ namespace OpenRA.FileSystem
// in FileSystem.OpenPackage. Their internal name therefore contains the
// full parent path too. We need to be careful to not add a second path
// prefix to these hacked packages.
var filePath = filename.StartsWith(Name) ? 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))

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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
@@ -19,7 +19,7 @@ namespace OpenRA.FileSystem
{
public class ZipFileLoader : IPackageLoader
{
const uint ZipSignature = 0x04034b50;
static readonly string[] Extensions = { ".zip", ".oramap" };
class ReadOnlyZipFile : IReadOnlyPackage
{
@@ -55,8 +55,7 @@ namespace OpenRA.FileSystem
get
{
foreach (ZipEntry entry in pkg)
if (entry.IsFile)
yield return entry.Name;
yield return entry.Name;
}
}
@@ -95,7 +94,7 @@ namespace OpenRA.FileSystem
sealed class ReadWriteZipFile : ReadOnlyZipFile, IReadWritePackage
{
readonly MemoryStream pkgStream = new();
readonly MemoryStream pkgStream = new MemoryStream();
public ReadWriteZipFile(string filename, bool create = false)
{
@@ -142,22 +141,23 @@ namespace OpenRA.FileSystem
sealed class ZipFolder : IReadOnlyPackage
{
public string Name { get; }
public ReadOnlyZipFile Parent { get; }
public string Name { get { return path; } }
public ReadOnlyZipFile Parent { get; private set; }
readonly string path;
public ZipFolder(ReadOnlyZipFile parent, string path)
{
if (path.EndsWith("/", StringComparison.Ordinal))
path = path[..^1];
path = path.Substring(0, path.Length - 1);
Name = path;
Parent = parent;
this.path = path;
}
public Stream GetStream(string filename)
{
// Zip files use '/' as a path separator
return Parent.GetStream(Name + '/' + filename);
return Parent.GetStream(path + '/' + filename);
}
public IEnumerable<string> Contents
@@ -166,9 +166,9 @@ namespace OpenRA.FileSystem
{
foreach (var entry in Parent.Contents)
{
if (entry.StartsWith(Name, StringComparison.Ordinal) && entry != Name)
if (entry.StartsWith(path, StringComparison.Ordinal) && entry != path)
{
var filename = entry[(Name.Length + 1)..];
var filename = entry.Substring(path.Length + 1);
var dirLevels = filename.Split('/').Count(c => !string.IsNullOrEmpty(c));
if (dirLevels == 1)
yield return filename;
@@ -179,18 +179,18 @@ namespace OpenRA.FileSystem
public bool Contains(string filename)
{
return Parent.Contains(Name + '/' + filename);
return Parent.Contains(path + '/' + filename);
}
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
{
return Parent.OpenPackage(Name + '/' + filename, context);
return Parent.OpenPackage(path + '/' + filename, context);
}
public void Dispose() { /* nothing to do */ }
}
sealed class StaticStreamDataSource : IStaticDataSource
class StaticStreamDataSource : IStaticDataSource
{
readonly Stream s;
public StaticStreamDataSource(Stream s)
@@ -206,10 +206,7 @@ namespace OpenRA.FileSystem
public bool TryParsePackage(Stream s, string filename, FileSystem context, out IReadOnlyPackage package)
{
var readSignature = s.ReadUInt32();
s.Position -= 4;
if (readSignature != ZipSignature)
if (!Extensions.Any(e => filename.EndsWith(e, StringComparison.InvariantCultureIgnoreCase)))
{
package = null;
return false;
@@ -221,13 +218,10 @@ namespace OpenRA.FileSystem
public static bool TryParseReadWritePackage(string filename, out IReadWritePackage package)
{
using (var s = File.OpenRead(filename))
if (!Extensions.Any(e => filename.EndsWith(e, StringComparison.InvariantCultureIgnoreCase)))
{
if (s.ReadUInt32() != ZipSignature)
{
package = null;
return false;
}
package = null;
return false;
}
package = new ReadWriteZipFile(filename);

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,7 +22,7 @@ namespace OpenRA
public class Fonts : IGlobalModData
{
[FieldLoader.LoadUsing(nameof(LoadFonts))]
[FieldLoader.LoadUsing("LoadFonts")]
public readonly Dictionary<string, FontData> FontList;
static object LoadFonts(MiniYaml y)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,8 +16,9 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using OpenRA.Graphics;
using OpenRA.Network;
using OpenRA.Primitives;
@@ -29,9 +30,8 @@ namespace OpenRA
{
public static class Game
{
[TranslationReference("filename")]
const string SavedScreenshot = "notification-saved-screenshot";
public const int NetTickScale = 3; // 120 ms net tick for 40 ms local tick
public const int Timestep = 40;
public const int TimestepJankThreshold = 250; // Don't catch up for delays larger than 250ms
public static InstalledMods Mods { get; private set; }
@@ -41,14 +41,13 @@ namespace OpenRA
public static Settings Settings;
public static CursorManager Cursor;
public static bool HideCursor;
static WorldRenderer worldRenderer;
static string modLaunchWrapper;
internal static OrderManager OrderManager;
static Server.Server server;
public static MersenneTwister CosmeticRandom = new(); // not synced
public static MersenneTwister CosmeticRandom = new MersenneTwister(); // not synced
public static Renderer Renderer;
public static Sound Sound;
@@ -56,6 +55,7 @@ namespace OpenRA
public static string EngineVersion { get; private set; }
public static LocalPlayerProfile LocalPlayerProfile;
static Task discoverNat;
static bool takeScreenshot = false;
static Benchmark benchmark = null;
@@ -63,80 +63,51 @@ namespace OpenRA
public static OrderManager JoinServer(ConnectionTarget endpoint, string password, bool recordReplay = true)
{
var newConnection = new NetworkConnection(endpoint);
var connection = new NetworkConnection(endpoint);
if (recordReplay)
newConnection.StartRecording(() => TimestampedFilename());
connection.StartRecording(() => { return TimestampedFilename(); });
var om = new OrderManager(newConnection);
var om = new OrderManager(endpoint, password, connection);
JoinInner(om);
CurrentServerSettings.Password = password;
CurrentServerSettings.Target = endpoint;
lastConnectionState = ConnectionState.PreConnecting;
ConnectionStateChanged(OrderManager, password, newConnection);
return om;
}
public static string TimestampedFilename(bool includemilliseconds = false, string extra = "")
static string TimestampedFilename(bool includemilliseconds = false)
{
var format = includemilliseconds ? "yyyy-MM-ddTHHmmssfffZ" : "yyyy-MM-ddTHHmmssZ";
return ModData.Manifest.Id + extra + "-" + DateTime.UtcNow.ToString(format, CultureInfo.InvariantCulture);
return ModData.Manifest.Id + "-" + DateTime.UtcNow.ToString(format, CultureInfo.InvariantCulture);
}
static void JoinInner(OrderManager om)
{
// Refresh static classes before the game starts.
TextNotificationsManager.Clear();
UnitOrders.Clear();
// HACK: The shellmap World and OrderManager are owned by the main menu's WorldRenderer instead of Game.
// This allows us to switch Game.OrderManager from the shellmap to the new network connection when joining
// a lobby, while keeping the OrderManager that runs the shellmap intact.
// A matching check in World.Dispose (which is called by WorldRenderer.Dispose) makes sure that we dispose
// the shellmap's OM when a lobby game actually starts.
if (OrderManager?.World == null || OrderManager.World.Type != WorldType.Shellmap)
OrderManager?.Dispose();
OrderManager?.Dispose();
OrderManager = om;
lastConnectionState = ConnectionState.PreConnecting;
ConnectionStateChanged(OrderManager);
}
public static void JoinReplay(string replayFile)
{
JoinInner(new OrderManager(new ReplayConnection(replayFile)));
JoinInner(new OrderManager(new ConnectionTarget(), "", new ReplayConnection(replayFile)));
}
static void JoinLocal()
{
JoinInner(new OrderManager(new EchoConnection()));
// Add a spectator client for the local player
// On the shellmap this player is controlling the map via scripted orders
OrderManager.LobbyInfo.Clients.Add(new Session.Client
{
Index = OrderManager.Connection.LocalClientId,
Name = Settings.Player.Name,
PreferredColor = Settings.Player.Color,
Color = Settings.Player.Color,
Faction = "Random",
SpawnPoint = 0,
Team = 0,
State = Session.ClientState.Ready
});
JoinInner(new OrderManager(new ConnectionTarget(), "", new EchoConnection()));
}
// More accurate replacement for Environment.TickCount
static readonly Stopwatch Stopwatch = Stopwatch.StartNew();
public static long RunTime => Stopwatch.ElapsedMilliseconds;
static Stopwatch stopwatch = Stopwatch.StartNew();
public static long RunTime { get { return stopwatch.ElapsedMilliseconds; } }
public static int RenderFrame = 0;
public static int NetFrameNumber => OrderManager.NetFrameNumber;
public static int LocalTick => OrderManager.LocalFrameNumber;
public static int NetFrameNumber { get { return OrderManager.NetFrameNumber; } }
public static int LocalTick { get { return OrderManager.LocalFrameNumber; } }
public static event Action<ConnectionTarget> OnRemoteDirectConnect = _ => { };
public static event Action<OrderManager, string, NetworkConnection> ConnectionStateChanged = (om, pass, conn) => { };
public static event Action<OrderManager> ConnectionStateChanged = _ => { };
static ConnectionState lastConnectionState = ConnectionState.PreConnecting;
public static int LocalClientId => OrderManager.Connection.LocalClientId;
public static int LocalClientId { get { return OrderManager.Connection.LocalClientId; } }
public static void RemoteDirectConnect(ConnectionTarget endpoint)
{
@@ -188,20 +159,22 @@ namespace OpenRA
Cursor.SetCursor(null);
BeforeGameStart();
Map map;
using (new PerfTimer("PrepareMap"))
map = ModData.PrepareMap(mapUID);
using (new PerfTimer("NewWorld"))
OrderManager.World = new World(mapUID, ModData, OrderManager, type);
OrderManager.World = new World(ModData, map, OrderManager, type);
OrderManager.World.GameOver += FinishBenchmark;
worldRenderer = new WorldRenderer(ModData, OrderManager.World);
// Proactively collect memory during loading to reduce peak memory.
GC.Collect();
using (new PerfTimer("LoadComplete"))
OrderManager.World.LoadComplete(worldRenderer);
// Proactively collect memory during loading to reduce peak memory.
GC.Collect();
if (OrderManager.GameStarted)
@@ -210,44 +183,27 @@ namespace OpenRA
Ui.MouseFocusWidget = null;
Ui.KeyboardFocusWidget = null;
OrderManager.LocalFrameNumber = 0;
OrderManager.LastTickTime = RunTime;
OrderManager.StartGame();
worldRenderer.RefreshPalette();
Cursor.SetCursor(ChromeMetrics.Get<string>("DefaultCursor"));
Cursor.SetCursor("default");
// Now loading is completed, now is the ideal time to run a GC and compact the LOH.
// - All the temporary garbage created during loading can be collected.
// - Live objects are likely to live for the length of the game or longer,
// thus promoting them into a higher generation is not an issue.
// - We can remove any fragmentation in the LOH caused by temporary loading garbage.
// - A loading screen is visible, so a delay won't matter to the user.
// Much better to clean up now then to drop frames during gameplay for GC pauses.
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();
}
public static void RestartGame()
{
var replay = OrderManager.Connection as ReplayConnection;
var replayName = replay?.Filename;
var replayName = replay != null ? replay.Filename : null;
var lobbyInfo = OrderManager.LobbyInfo;
// Reseed the RNG so this isn't an exact repeat of the last game
lobbyInfo.GlobalSettings.RandomSeed = CosmeticRandom.Next();
// Note: the map may have been changed on disk outside the game, changing its UID.
// Use the updated UID if we have tracked the update instead of failing.
lobbyInfo.GlobalSettings.Map = ModData.MapCache.GetUpdatedMap(lobbyInfo.GlobalSettings.Map);
if (lobbyInfo.GlobalSettings.Map == null)
{
Disconnect();
Ui.ResetAll();
LoadShellMap();
return;
}
var orders = new[]
{
Order.Command($"sync_lobby {lobbyInfo.Serialize()}"),
Order.Command("sync_lobby {0}".F(lobbyInfo.Serialize())),
Order.Command("startgame")
};
@@ -266,14 +222,15 @@ namespace OpenRA
{
OrderManager om = null;
void LobbyReady()
Action lobbyReady = null;
lobbyReady = () =>
{
LobbyInfoChanged -= LobbyReady;
LobbyInfoChanged -= lobbyReady;
foreach (var o in setupOrders)
om.IssueOrder(o);
}
};
LobbyInfoChanged += LobbyReady;
LobbyInfoChanged += lobbyReady;
om = JoinServer(CreateLocalServer(mapUID), "");
}
@@ -294,47 +251,40 @@ namespace OpenRA
public static void InitializeSettings(Arguments args)
{
Settings = new Settings(Path.Combine(Platform.SupportDir, "settings.yaml"), args);
Settings = new Settings(Platform.ResolvePath(Path.Combine(Platform.SupportDirPrefix, "settings.yaml")), args);
}
public static RunStatus InitializeAndRun(string[] args)
{
Initialize(new Arguments(args));
// Proactively collect memory during loading to reduce peak memory.
GC.Collect();
return Run();
}
static void Initialize(Arguments args)
{
var engineDirArg = args.GetValue("Engine.EngineDir", null);
if (!string.IsNullOrEmpty(engineDirArg))
Platform.OverrideEngineDir(engineDirArg);
var supportDirArg = args.GetValue("Engine.SupportDir", null);
if (!string.IsNullOrEmpty(supportDirArg))
Platform.OverrideSupportDir(supportDirArg);
Console.WriteLine($"Platform is {Platform.CurrentPlatform} ({Platform.CurrentArchitecture})");
Console.WriteLine("Platform is {0}", Platform.CurrentPlatform);
// Load the engine version as early as possible so it can be written to exception logs
try
{
EngineVersion = File.ReadAllText(Path.Combine(Platform.EngineDir, "VERSION")).Trim();
EngineVersion = File.ReadAllText(Platform.ResolvePath(Path.Combine(".", "VERSION"))).Trim();
}
catch { }
if (string.IsNullOrEmpty(EngineVersion))
EngineVersion = "Unknown";
Console.WriteLine($"Engine version is {EngineVersion}");
Console.WriteLine($"Runtime: {Platform.RuntimeVersion}");
Console.WriteLine("Engine version is {0}", EngineVersion);
// Special case handling of Game.Mod argument: if it matches a real filesystem path
// then we use this to override the mod search path, and replace it with the mod id
var modID = args.GetValue("Game.Mod", null);
var explicitModPaths = Array.Empty<string>();
var explicitModPaths = new string[0];
if (modID != null && (File.Exists(modID) || Directory.Exists(modID)))
{
explicitModPaths = new[] { modID };
@@ -361,18 +311,10 @@ namespace OpenRA
Settings.Game.Platform = p;
try
{
var rendererPath = Path.Combine(Platform.BinDir, "OpenRA.Platforms." + p + ".dll");
var rendererPath = Platform.ResolvePath(Path.Combine(".", "OpenRA.Platforms." + p + ".dll"));
var assembly = Assembly.LoadFile(rendererPath);
#if NET5_0_OR_GREATER
var loader = new AssemblyLoader(rendererPath);
var platformType = loader.LoadDefaultAssembly().GetTypes().SingleOrDefault(t => typeof(IPlatform).IsAssignableFrom(t));
#else
// NOTE: This is currently the only use of System.Reflection in this file, so would give an unused using error if we import it above
var assembly = System.Reflection.Assembly.LoadFile(rendererPath);
var platformType = assembly.GetTypes().SingleOrDefault(t => typeof(IPlatform).IsAssignableFrom(t));
#endif
if (platformType == null)
throw new InvalidOperationException("Platform dll must include exactly one IPlatform implementation.");
@@ -384,7 +326,7 @@ namespace OpenRA
}
catch (Exception e)
{
Log.Write("graphics", $"{e}");
Log.Write("graphics", "{0}", e);
Console.WriteLine("Renderer initialization failed. Check graphics.log for details.");
Renderer?.Dispose();
@@ -393,17 +335,18 @@ namespace OpenRA
}
}
Nat.Initialize();
if (Settings.Server.DiscoverNatDevices)
discoverNat = UPnP.DiscoverNatDevices(Settings.Server.NatDiscoveryTimeout);
var modSearchArg = args.GetValue("Engine.ModSearchPaths", null);
var modSearchPaths = modSearchArg != null ?
FieldLoader.GetValue<string[]>("Engine.ModsPath", modSearchArg) :
new[] { Path.Combine(Platform.EngineDir, "mods") };
new[] { Path.Combine(".", "mods") };
Mods = new InstalledMods(modSearchPaths, explicitModPaths);
Console.WriteLine("Internal mods:");
foreach (var mod in Mods)
Console.WriteLine($"\t{mod.Key}: {mod.Value.Metadata.Title} ({mod.Value.Metadata.Version})");
Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Metadata.Title, mod.Value.Metadata.Version);
modLaunchWrapper = args.GetValue("Engine.LaunchWrapper", null);
@@ -411,24 +354,22 @@ namespace OpenRA
if (modID != null && Mods.TryGetValue(modID, out _))
{
var launchPath = args.GetValue("Engine.LaunchPath", null);
var launchArgs = new List<string>();
var launchPath = args.GetValue("Engine.LaunchPath", Assembly.GetEntryAssembly().Location);
// Sanitize input from platform-specific launchers
// Process.Start requires paths to not be quoted, even if they contain spaces
if (launchPath != null && launchPath.First() == '"' && launchPath.Last() == '"')
launchPath = launchPath[1..^1];
if (launchPath.First() == '"' && launchPath.Last() == '"')
launchPath = launchPath.Substring(1, launchPath.Length - 2);
// Metadata registration requires an explicit launch path
if (launchPath != null)
ExternalMods.Register(Mods[modID], launchPath, launchArgs, ModRegistration.User);
ExternalMods.Register(Mods[modID], launchPath, ModRegistration.User);
ExternalMods.ClearInvalidRegistrations(ModRegistration.User);
if (ExternalMods.TryGetValue(ExternalMod.MakeKey(Mods[modID]), out var activeMod))
ExternalMods.ClearInvalidRegistrations(activeMod, ModRegistration.User);
}
Console.WriteLine("External mods:");
foreach (var mod in ExternalMods)
Console.WriteLine($"\t{mod.Key}: {mod.Value.Title} ({mod.Value.Version})");
Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Title, mod.Value.Version);
InitializeMod(modID, args);
}
@@ -437,7 +378,7 @@ namespace OpenRA
{
// Clear static state if we have switched mods
LobbyInfoChanged = () => { };
ConnectionStateChanged = (om, p, conn) => { };
ConnectionStateChanged = om => { };
BeforeGameStart = () => { };
OnRemoteDirectConnect = endpoint => { };
delayedActions = new ActionQueue();
@@ -461,34 +402,31 @@ namespace OpenRA
throw new InvalidOperationException("Game.Mod argument missing.");
if (!Mods.ContainsKey(mod))
throw new InvalidOperationException($"Unknown or invalid mod '{mod}'.");
throw new InvalidOperationException("Unknown or invalid mod '{0}'.".F(mod));
Console.WriteLine($"Loading mod: {mod}");
Console.WriteLine("Loading mod: {0}", mod);
Sound.StopVideo();
ModData = new ModData(Mods[mod], Mods, true);
LocalPlayerProfile = new LocalPlayerProfile(Path.Combine(Platform.SupportDir, Settings.Game.AuthProfile), ModData.Manifest.Get<PlayerDatabase>());
LocalPlayerProfile = new LocalPlayerProfile(Platform.ResolvePath(Path.Combine("^", Settings.Game.AuthProfile)), ModData.Manifest.Get<PlayerDatabase>());
if (!ModData.LoadScreen.BeforeLoad())
return;
ModData.InitializeLoaders(ModData.DefaultFileSystem);
Renderer.InitializeFonts(ModData);
using (new PerfTimer("LoadMaps"))
ModData.MapCache.LoadMaps();
ModData.InitializeLoaders(ModData.DefaultFileSystem);
Renderer.InitializeFonts(ModData);
var grid = ModData.Manifest.Contains<MapGrid>() ? ModData.Manifest.Get<MapGrid>() : null;
Renderer.InitializeDepthBuffer(grid);
Cursor?.Dispose();
Cursor = new CursorManager(ModData.CursorProvider);
var metadata = ModData.Manifest.Metadata;
if (!string.IsNullOrEmpty(metadata.WindowTitle))
Renderer.Window.SetWindowTitle(metadata.WindowTitle);
Cursor = new CursorManager(ModData.CursorProvider);
PerfHistory.Items["render"].HasNormalTick = false;
PerfHistory.Items["batches"].HasNormalTick = false;
@@ -499,18 +437,31 @@ namespace OpenRA
JoinLocal();
try
{
discoverNat?.Wait();
}
catch (Exception e)
{
Console.WriteLine("NAT discovery failed: {0}", e.Message);
Log.Write("nat", e.ToString());
}
ChromeMetrics.TryGet("ChatMessageColor", out chatMessageColor);
ChromeMetrics.TryGet("SystemMessageColor", out systemMessageColor);
ModData.LoadScreen.StartGame(args);
}
public static void LoadEditor(string mapUid)
{
JoinLocal();
StartGame(mapUid, WorldType.Editor);
}
public static void LoadShellMap()
{
var shellmap = ChooseShellmap();
using (new PerfTimer("StartGame"))
{
StartGame(shellmap, WorldType.Shellmap);
@@ -564,8 +515,9 @@ namespace OpenRA
// Note: These delayed actions should only be used by widgets or disposing objects
// - things that depend on a particular world should be queuing them on the world actor.
static volatile ActionQueue delayedActions = new();
static volatile ActionQueue delayedActions = new ActionQueue();
static Color systemMessageColor = Color.White;
static Color chatMessageColor = Color.White;
public static void RunAfterTick(Action a) { delayedActions.Add(a, RunTime); }
public static void RunAfterDelay(int delayMilliseconds, Action a) { delayedActions.Add(a, RunTime + delayMilliseconds); }
@@ -574,7 +526,7 @@ namespace OpenRA
using (new PerfTimer("Renderer.SaveScreenshot"))
{
var mod = ModData.Manifest.Metadata;
var directory = Path.Combine(Platform.SupportDir, "Screenshots", ModData.Manifest.Id, mod.Version);
var directory = Platform.ResolvePath(Platform.SupportDirPrefix, "Screenshots", ModData.Manifest.Id, mod.Version);
Directory.CreateDirectory(directory);
var filename = TimestampedFilename(true);
@@ -582,7 +534,7 @@ namespace OpenRA
Log.Write("debug", "Taking screenshot " + path);
Renderer.SaveScreenshot(path);
TextNotificationsManager.Debug(TranslationProvider.GetString(SavedScreenshot, Translation.Arguments("filename", filename)));
Debug("Saved screenshot " + filename);
}
}
@@ -592,38 +544,62 @@ namespace OpenRA
var world = orderManager.World;
if (Ui.LastTickTime.ShouldAdvance(tick))
var uiTickDelta = tick - Ui.LastTickTime;
if (uiTickDelta >= Timestep)
{
Ui.LastTickTime.AdvanceTickTime(tick);
Sync.RunUnsynced(world, Ui.Tick);
// Explained below for the world tick calculation
var integralTickTimestep = (uiTickDelta / Timestep) * Timestep;
Ui.LastTickTime += integralTickTimestep >= TimestepJankThreshold ? integralTickTimestep : Timestep;
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, Ui.Tick);
Cursor.Tick();
}
if (orderManager.LastTickTime.ShouldAdvance(tick))
var worldTimestep = world == null ? Timestep : world.IsLoadingGameSave ? 1 : world.Timestep;
var worldTickDelta = tick - orderManager.LastTickTime;
if (worldTimestep != 0 && worldTickDelta >= worldTimestep)
{
using (new PerfSample("tick_time"))
{
orderManager.LastTickTime.AdvanceTickTime(tick);
// Tick the world to advance the world time to match real time:
// If dt < TickJankThreshold then we should try and catch up by repeatedly ticking
// If dt >= TickJankThreshold then we should accept the jank and progress at the normal rate
// dt is rounded down to an integer tick count in order to preserve fractional tick components.
var integralTickTimestep = (worldTickDelta / worldTimestep) * worldTimestep;
orderManager.LastTickTime += integralTickTimestep >= TimestepJankThreshold ? integralTickTimestep : worldTimestep;
Sound.Tick();
Sync.RunUnsynced(world, orderManager.TickImmediate);
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, orderManager.TickImmediate);
if (world == null)
return;
if (orderManager.TryTick())
var isNetTick = LocalTick % NetTickScale == 0;
if (!isNetTick || orderManager.IsReadyForNextFrame)
{
Sync.RunUnsynced(world, () => world.OrderGenerator.Tick(world));
++orderManager.LocalFrameNumber;
Log.Write("debug", "--Tick: {0} ({1})", LocalTick, isNetTick ? "net" : "local");
if (isNetTick)
orderManager.Tick();
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, () =>
{
world.OrderGenerator.Tick(world);
});
world.Tick();
PerfHistory.Tick();
}
else if (orderManager.NetFrameNumber == 0)
orderManager.LastTickTime = RunTime;
// Wait until we have done our first world Tick before TickRendering
if (orderManager.LocalFrameNumber > 0)
Sync.RunUnsynced(world, () => world.TickRender(worldRenderer));
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, () => world.TickRender(worldRenderer));
}
benchmark?.Tick(LocalTick);
@@ -634,10 +610,10 @@ namespace OpenRA
{
PerformDelayedActions();
if (OrderManager.Connection is NetworkConnection nc && nc.ConnectionState != lastConnectionState)
if (OrderManager.Connection.ConnectionState != lastConnectionState)
{
lastConnectionState = nc.ConnectionState;
ConnectionStateChanged(OrderManager, null, nc);
lastConnectionState = OrderManager.Connection.ConnectionState;
ConnectionStateChanged(OrderManager);
}
InnerLogicTick(OrderManager);
@@ -774,20 +750,13 @@ namespace OpenRA
while (state == RunStatus.Running)
{
var logicInterval = Ui.Timestep;
var logicWorld = worldRenderer?.World;
// ReplayTimestep = 0 means the replay is paused: we need to keep logicInterval as UI.Timestep to avoid breakage
if (logicWorld != null && !(logicWorld.IsReplay && logicWorld.ReplayTimestep == 0))
logicInterval = logicWorld == OrderManager.World ? OrderManager.SuggestedTimestep : logicWorld.Timestep;
// Ideal time between logic updates. Timestep = 0 means the game is paused
// but we still call LogicTick() because it handles pausing internally.
var logicInterval = worldRenderer != null && worldRenderer.World.Timestep != 0 ? worldRenderer.World.Timestep : Timestep;
// Ideal time between screen updates
var renderInterval = logicInterval;
if (!Settings.Graphics.CapFramerateToGameFps)
{
var maxFramerate = Settings.Graphics.CapFramerate ? Settings.Graphics.MaxFramerate.Clamp(1, 1000) : 1000;
renderInterval = 1000 / maxFramerate;
}
var maxFramerate = Settings.Graphics.CapFramerate ? Settings.Graphics.MaxFramerate.Clamp(1, 1000) : 1000;
var renderInterval = 1000 / maxFramerate;
// Tick as fast as possible while restoring game saves, capping rendering at 5 FPS
if (OrderManager.World != null && OrderManager.World.IsLoadingGameSave)
@@ -821,7 +790,8 @@ namespace OpenRA
var haveSomeTimeUntilNextLogic = now < nextLogic;
var isTimeToRender = now >= nextRender;
if (!Renderer.WindowIsSuspended && ((isTimeToRender && haveSomeTimeUntilNextLogic) || forceRender))
if ((isTimeToRender && haveSomeTimeUntilNextLogic) || forceRender)
{
nextRender = now + renderInterval;
@@ -836,19 +806,6 @@ namespace OpenRA
RenderTick();
renderBeforeNextTick = false;
}
// Simulate a render tick if it was time to render but we skip actually rendering
if (Renderer.WindowIsSuspended && isTimeToRender)
{
// Make sure that nextUpdate is set to a proper minimum interval
nextRender = now + renderInterval;
// Still process SDL events to allow a restore to come through
Renderer.Window.PumpInput(new NullInputHandler());
// Ensure that we still logic tick despite not rendering
renderBeforeNextTick = false;
}
}
else
Thread.Sleep((int)(nextUpdate - now));
@@ -890,6 +847,26 @@ namespace OpenRA
state = RunStatus.Success;
}
public static void AddSystemLine(string text)
{
AddSystemLine("Battlefield Control", text);
}
public static void AddSystemLine(string name, string text)
{
OrderManager.AddChatLine(name, systemMessageColor, text, systemMessageColor);
}
public static void AddChatLine(string name, Color nameColor, string text)
{
OrderManager.AddChatLine(name, nameColor, text, chatMessageColor);
}
public static void Debug(string s, params object[] args)
{
AddSystemLine("Debug", string.Format(s, args));
}
public static void Disconnect()
{
OrderManager.World?.TraitDict.PrintReport();
@@ -930,11 +907,9 @@ namespace OpenRA
AdvertiseOnline = false
};
// Always connect to local games using the same loopback connection
// Exposing multiple endpoints introduces a race condition on the client's PlayerIndex (sometimes 0, sometimes 1)
// This would break the Restart button, which relies on the PlayerIndex always being the same for local servers
var endpoints = new List<IPEndPoint>
{
new IPEndPoint(IPAddress.IPv6Loopback, 0),
new IPEndPoint(IPAddress.Loopback, 0)
};
server = new Server.Server(endpoints, settings, ModData, ServerType.Local);
@@ -961,13 +936,16 @@ namespace OpenRA
{
var orders = new List<Order>
{
Order.Command("option gamespeed default"),
Order.Command($"state {Session.ClientState.Ready}")
Order.Command("option gamespeed {0}".F("default")),
Order.Command("state {0}".F(Session.ClientState.Ready))
};
var map = ModData.MapCache.SingleOrDefault(m => m.Uid == launchMap || Path.GetFileName(m.Package.Name) == launchMap);
var path = Platform.ResolvePath(launchMap);
var map = ModData.MapCache.SingleOrDefault(m => m.Uid == launchMap) ??
ModData.MapCache.SingleOrDefault(m => m.Package.Name == path);
if (map == null)
throw new ArgumentException($"Could not find map '{launchMap}'.");
throw new InvalidOperationException("Could not find map '{0}'.".F(launchMap));
CreateAndStartLocalServer(map.Uid, orders);
}
@@ -981,11 +959,4 @@ namespace OpenRA
}
}
}
public static class CurrentServerSettings
{
public static string Password;
public static ConnectionTarget Target;
public static ExternalMod ServerExternalMod;
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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
@@ -33,13 +33,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 +74,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 +87,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 +96,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,14 +118,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,
IsRandomSpawnPoint = runtimePlayer.SpawnPoint != client.SpawnPoint,
Fingerprint = client.Fingerprint
};
@@ -161,14 +156,9 @@ namespace OpenRA
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;
/// <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;
@@ -189,9 +179,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
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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
@@ -32,7 +32,7 @@ namespace OpenRA
/// You can remove inherited traits by adding a - in front of them as in -TraitName: to inherit everything, but this trait.
/// </summary>
public readonly string Name;
readonly TypeDictionary traits = new();
readonly TypeDictionary traits = new TypeDictionary();
List<TraitInfo> constructOrderCache = null;
public ActorInfo(ObjectCreator creator, string name, MiniYaml node)
@@ -61,7 +61,7 @@ namespace OpenRA
}
catch (YamlException e)
{
throw new YamlException($"Actor type {name}: {e.Message}");
throw new YamlException("Actor type {0}: {1}".F(name, e.Message));
}
}
@@ -76,7 +76,8 @@ namespace OpenRA
static TraitInfo LoadTraitInfo(ObjectCreator creator, string traitName, MiniYaml my)
{
if (!string.IsNullOrEmpty(my.Value))
throw new YamlException($"Junk value `{my.Value}` on trait node {traitName}");
throw new YamlException("Junk value `{0}` on trait node {1}"
.F(my.Value, 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
@@ -88,7 +89,7 @@ namespace OpenRA
try
{
if (traitInstance.Length > 1)
info.GetType().GetField(nameof(info.InstanceName)).SetValue(info, traitInstance[1]);
info.GetType().GetField("InstanceName").SetValue(info, traitInstance[1]);
FieldLoader.Load(info, my);
}
@@ -110,51 +111,39 @@ namespace OpenRA
{
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:\n";
var missing = unresolved.SelectMany(u => u.Dependencies.Where(d => !source.Any(s => AreResolvable(d, s.Type)))).Distinct();
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 => testResolve(d, s.Type)))).Distinct();
exceptionString += "Missing:\n";
exceptionString += "Missing:\r\n";
foreach (var m in missing)
exceptionString += m + " \n";
exceptionString += m + " \r\n";
exceptionString += "Unresolved:\n";
exceptionString += "Unresolved:\r\n";
foreach (var u in unresolved)
{
var deps = u.Dependencies.Where(d => !resolved.Exists(r => r.Type == d));
var optDeps = u.OptionalDependencies.Where(d => !resolved.Exists(r => r.Type == d));
var allDeps = string.Join(", ", deps.Select(o => o.ToString()).Concat(optDeps.Select(o => $"[{o}]")));
exceptionString += $"{u.Type}: {{ {allDeps} }}\n";
exceptionString += u.Type + ": { " + string.Join(", ", deps) + " }\r\n";
}
throw new YamlException(exceptionString);
@@ -173,15 +162,6 @@ namespace OpenRA
.Select(t => t.GetGenericArguments()[0]);
}
public static IEnumerable<Type> OptionalPrerequisitesOf(TraitInfo info)
{
return info
.GetType()
.GetInterfaces()
.Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(NotBefore<>))
.Select(t => t.GetGenericArguments()[0]);
}
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>(); }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,7 @@
*/
#endregion
using System.IO;
using OpenRA.FileSystem;
namespace OpenRA.GameRules
@@ -28,14 +29,14 @@ namespace OpenRA.GameRules
Title = value.Value;
var nd = value.ToDictionary();
if (nd.TryGetValue("Hidden", out var yaml))
bool.TryParse(yaml.Value, out Hidden);
if (nd.ContainsKey("Hidden"))
bool.TryParse(nd["Hidden"].Value, out Hidden);
if (nd.TryGetValue("VolumeModifier", out yaml))
VolumeModifier = FieldLoader.GetValue<float>("VolumeModifier", yaml.Value);
if (nd.ContainsKey("VolumeModifier"))
VolumeModifier = FieldLoader.GetValue<float>("VolumeModifier", nd["VolumeModifier"].Value);
var ext = nd.TryGetValue("Extension", out yaml) ? yaml.Value : "aud";
Filename = (nd.TryGetValue("Filename", out yaml) ? yaml.Value : key) + "." + ext;
var ext = nd.ContainsKey("Extension") ? nd["Extension"].Value : "aud";
Filename = (nd.ContainsKey("Filename") ? nd["Filename"].Value : key) + "." + ext;
}
public void Load(IReadOnlyFileSystem fileSystem)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,18 +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 TileSet TileSet;
public readonly SequenceProvider Sequences;
public readonly IReadOnlyDictionary<string, MiniYamlNode> ModelSequences;
public Ruleset(
@@ -35,15 +37,17 @@ namespace OpenRA
IReadOnlyDictionary<string, SoundInfo> voices,
IReadOnlyDictionary<string, SoundInfo> notifications,
IReadOnlyDictionary<string, MusicInfo> music,
ITerrainInfo terrainInfo,
TileSet tileSet,
SequenceProvider sequences,
IReadOnlyDictionary<string, MiniYamlNode> modelSequences)
{
Actors = new ActorInfoDictionary(actors);
Actors = actors;
Weapons = weapons;
Voices = voices;
Notifications = notifications;
Music = music;
TerrainInfo = terrainInfo;
TileSet = tileSet;
Sequences = sequences;
ModelSequences = modelSequences;
foreach (var a in Actors.Values)
@@ -56,14 +60,15 @@ 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)
var projectileLoaded = weapon.Value.Projectile as IRulesetLoaded<WeaponInfo>;
if (projectileLoaded != null)
{
try
{
@@ -71,13 +76,14 @@ namespace OpenRA
}
catch (YamlException e)
{
throw new YamlException($"Projectile type {weapon.Key}: {e.Message}");
throw new YamlException("Projectile type {0}: {1}".F(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 +91,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));
}
}
}
@@ -111,7 +117,7 @@ namespace OpenRA
if (filterNode != null)
yamlNodes = yamlNodes.Where(k => !filterNode(k));
return yamlNodes.ToDictionaryWithConflictLog(k => k.Key.ToLowerInvariant(), makeObject, "LoadFromManifest<" + name + ">");
return new ReadOnlyDictionary<string, T>(yamlNodes.ToDictionaryWithConflictLog(k => k.Key.ToLowerInvariant(), makeObject, "LoadFromManifest<" + name + ">"));
}
public static Ruleset LoadDefaults(ModData modData)
@@ -120,14 +126,14 @@ 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));
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));
@@ -141,15 +147,15 @@ namespace OpenRA
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, modelSequences);
};
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 +163,7 @@ namespace OpenRA
modData.HandleLoadingProgress();
}
else
LoadRuleset();
f();
return ruleset;
}
@@ -165,27 +171,28 @@ namespace OpenRA
public static Ruleset LoadDefaultsForTileSet(ModData modData, string tileSet)
{
var dr = modData.DefaultRules;
var terrainInfo = modData.DefaultTerrainInfo[tileSet];
var ts = modData.DefaultTileSets[tileSet];
var sequences = modData.DefaultSequences[tileSet];
return new Ruleset(dr.Actors, dr.Weapons, dr.Voices, dr.Notifications, dr.Music, terrainInfo, dr.ModelSequences);
return new Ruleset(dr.Actors, dr.Weapons, dr.Voices, dr.Notifications, dr.Music, ts, sequences, dr.ModelSequences);
}
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, MiniYaml mapModelSequences)
{
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));
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 +203,26 @@ 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];
// 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);
var modelSequences = dr.ModelSequences;
if (mapModelSequences != null)
modelSequences = MergeOrDefault("ModelSequences", fileSystem, m.ModelSequences, mapModelSequences, dr.ModelSequences,
k => k);
ruleset = new Ruleset(actors, weapons, voices, notifications, music, terrainInfo, modelSequences);
}
ruleset = new Ruleset(actors, weapons, voices, notifications, music, ts, sequences, modelSequences);
};
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 +230,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,12 +250,12 @@ 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 != null && traitType.GetInterface("ILobbyCustomRulesIgnore") == null)
return true;
}
catch (Exception ex)
{
Log.Write("debug", "Error in AnyFlaggedTraits\n" + ex.ToString());
Log.Write("debug", "Error in AnyFlaggedTraits\r\n" + ex.ToString());
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,28 +33,23 @@ namespace OpenRA.GameRules
{
FieldLoader.Load(this, y);
VoicePools = Exts.Lazy(() => Voices.ToDictionary(a => a.Key, a => new SoundPool(1f, SoundPool.DefaultInterruptType, a.Value)));
VoicePools = Exts.Lazy(() => Voices.ToDictionary(a => a.Key, a => new SoundPool(1f, a.Value)));
NotificationsPools = Exts.Lazy(() => ParseSoundPool(y, "Notifications"));
}
static Dictionary<string, SoundPool> ParseSoundPool(MiniYaml y, string key)
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));
var volumeModifierNode = t.Value.Nodes.FirstOrDefault(x => x.Key == "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);
var sp = new SoundPool(volumeModifier, names);
ret.Add(t.Key, sp);
}
@@ -64,17 +59,13 @@ namespace OpenRA.GameRules
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(float volumeModifier, params string[] clips)
{
VolumeModifier = volumeModifier;
Type = interruptType;
this.clips = clips;
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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
@@ -36,7 +36,7 @@ namespace OpenRA.GameRules
public class WarheadArgs
{
public WeaponInfo Weapon;
public int[] DamageModifiers = Array.Empty<int>();
public int[] DamageModifiers = { };
public WPos? Source;
public WRot ImpactOrientation;
public WPos ImpactPosition;
@@ -99,20 +99,17 @@ namespace OpenRA.GameRules
[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 BitSet<TargetableType> ValidTargets = new BitSet<TargetableType>("Ground", "Water");
[Desc("What types of targets are unaffected.", "Overrules ValidTargets.")]
public readonly BitSet<TargetableType> InvalidTargets;
static readonly BitSet<TargetableType> TargetTypeAir = new("Air");
static readonly BitSet<TargetableType> TargetTypeAir = new BitSet<TargetableType>("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);
public readonly WDist AirThreshold = new WDist(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.")]
@@ -124,18 +121,13 @@ namespace OpenRA.GameRules
[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
@@ -147,11 +139,7 @@ namespace OpenRA.GameRules
{
if (!yaml.ToDictionary().TryGetValue("Projectile", out var proj))
return null;
var ret = Game.CreateObject<IProjectileInfo>(proj.Value + "Info");
if (ret == null)
return null;
FieldLoader.Load(ret, proj);
return ret;
}
@@ -162,9 +150,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);
}
@@ -178,7 +163,7 @@ namespace OpenRA.GameRules
}
/// <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);
@@ -209,45 +194,46 @@ 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, WarheadArgs args)
{
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)));
}
world.AddFrameEndTask(w => w.Add(new DelayedImpact(warhead.Delay, warhead, target, args)));
else
warhead.DoImpact(target, args);
}
}
/// <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)
public void Impact(Target target, Actor firedBy)
{
// The impact will happen immediately at target.CenterPosition.
var args = new WarheadArgs

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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;
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,7 +22,7 @@ namespace OpenRA.Graphics
public string Name { get; private set; }
public bool IsDecoration { get; set; }
readonly SequenceSet sequences;
readonly SequenceProvider sequenceProvider;
readonly Func<WAngle> facingFunc;
readonly Func<bool> paused;
@@ -30,7 +30,7 @@ namespace OpenRA.Graphics
bool backwards;
bool tickAlways;
int timeUntilNextFrame;
Action tickFunc;
Action tickFunc = () => { };
public Animation(World world, string name)
: this(world, name, () => WAngle.Zero) { }
@@ -43,57 +43,48 @@ namespace OpenRA.Graphics
public Animation(World world, string name, Func<WAngle> 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.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 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, CurrentSequence.IgnoreWorldTint);
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, CurrentSequence.IgnoreWorldTint);
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 IRenderable[] RenderUI(WorldRenderer wr, int2 pos, WVec offset, int zOffset, PaletteReference palette, float scale)
{
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 imageRenderable = new UISpriteRenderable(Image, WPos.Zero + offset, imagePos, CurrentSequence.ZOffset + zOffset, palette, scale);
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
if (shadow != null)
if (CurrentSequence.ShadowStart >= 0)
{
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
var shadowPos = pos - new int2((int)(scale * shadow.Size.X / 2), (int)(scale * shadow.Size.Y / 2));
var shadowRenderable = new UISpriteRenderable(shadow, WPos.Zero + offset, shadowPos, CurrentSequence.ShadowZOffset + zOffset, palette, scale, 1f, rotation);
var shadowRenderable = new UISpriteRenderable(shadow, WPos.Zero + offset, shadowPos, CurrentSequence.ShadowZOffset + zOffset, palette, scale);
return new IRenderable[] { shadowRenderable, imageRenderable };
}
return new IRenderable[] { imageRenderable };
}
public Rectangle ScreenBounds(WorldRenderer wr, WPos pos, in WVec offset)
public Rectangle ScreenBounds(WorldRenderer wr, WPos pos, WVec offset, float scale)
{
var scale = CurrentSequence.Scale;
var xy = wr.ScreenPxPosition(pos) + wr.ScreenPxOffset(offset);
var cb = CurrentSequence.Bounds;
return Rectangle.FromLTRB(
@@ -105,7 +96,7 @@ namespace OpenRA.Graphics
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 +107,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)
@@ -164,7 +155,7 @@ namespace OpenRA.Graphics
if (frame >= CurrentSequence.Length)
{
frame = CurrentSequence.Length - 1;
tickFunc = null;
tickFunc = () => { };
after?.Invoke();
}
};
@@ -212,13 +203,13 @@ namespace OpenRA.Graphics
public void Tick(int t)
{
if (tickAlways)
tickFunc?.Invoke();
tickFunc();
else
{
timeUntilNextFrame -= t;
while (timeUntilNextFrame <= 0)
{
tickFunc?.Invoke();
tickFunc();
timeUntilNextFrame += CurrentSequenceTickOrDefault();
}
}
@@ -236,11 +227,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)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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
@@ -35,21 +35,21 @@ namespace OpenRA.Graphics
ZOffset = zOffset;
}
public IRenderable[] Render(Actor self, PaletteReference pal)
public 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);
var z = (ZOffset != null) ? ZOffset(center + offset) : 0;
return Animation.Render(center, offset, z, pal, scale);
}
public Rectangle ScreenBounds(Actor self, WorldRenderer wr)
public Rectangle ScreenBounds(Actor self, WorldRenderer wr, float scale)
{
var center = self.CenterPosition;
var offset = OffsetFunc?.Invoke() ?? WVec.Zero;
var offset = OffsetFunc != null ? OffsetFunc() : WVec.Zero;
return Animation.ScreenBounds(wr, center, offset);
return Animation.ScreenBounds(wr, center, offset, scale);
}
public static implicit operator AnimationWithOffset(Animation a)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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
@@ -49,10 +49,10 @@ namespace OpenRA.Graphics
public readonly int[] PanelRegion = null;
public readonly PanelSides PanelSides = PanelSides.All;
public readonly Dictionary<string, Rectangle> Regions = new();
public readonly Dictionary<string, Rectangle> Regions = new Dictionary<string, Rectangle>();
}
public static IReadOnlyDictionary<string, Collection> Collections => collections;
public static IReadOnlyDictionary<string, Collection> Collections { get; private set; }
static Dictionary<string, Collection> collections;
static Dictionary<string, (Sheet Sheet, int Density)> cachedSheets;
static Dictionary<string, Dictionary<string, Sprite>> cachedSprites;
@@ -77,6 +77,8 @@ namespace OpenRA.Graphics
cachedPanelSprites = new Dictionary<string, Sprite[]>();
cachedCollectionSheets = new Dictionary<Collection, (Sheet, int)>();
Collections = new ReadOnlyDictionary<string, Collection>(collections);
var chrome = MiniYaml.Merge(modData.Manifest.Chrome
.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s)));
@@ -100,7 +102,9 @@ namespace OpenRA.Graphics
static void LoadCollection(string name, MiniYaml yaml)
{
Game.ModData.LoadScreen?.Display();
if (Game.ModData.LoadScreen != null)
Game.ModData.LoadScreen.Display();
collections.Add(name, FieldLoader.Load<Collection>(yaml));
}
@@ -142,15 +146,6 @@ namespace OpenRA.Graphics
}
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;
@@ -160,7 +155,10 @@ namespace OpenRA.Graphics
return sprite;
if (!collections.TryGetValue(collectionName, out var collection))
{
Log.Write("debug", "Could not find collection '{0}'", collectionName);
return null;
}
if (!collection.Regions.TryGetValue(imageName, out var mi))
return null;
@@ -180,15 +178,6 @@ namespace OpenRA.Graphics
}
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;
@@ -198,14 +187,17 @@ namespace OpenRA.Graphics
return cachedSprites;
if (!collections.TryGetValue(collectionName, out var collection))
{
Log.Write("debug", "Could not find collection '{0}'", collectionName);
return null;
}
Sprite[] sprites;
if (collection.PanelRegion != null)
{
if (collection.PanelRegion.Length != 8)
{
Log.Write("debug", $"Collection '{collectionName}' does not define a valid PanelRegion");
Log.Write("debug", "Collection '{0}' does not define a valid PanelRegion", collectionName);
return null;
}
@@ -232,23 +224,18 @@ namespace OpenRA.Graphics
}
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")
GetImage(collectionName, "corner-tl"),
GetImage(collectionName, "border-t"),
GetImage(collectionName, "corner-tr"),
GetImage(collectionName, "border-l"),
GetImage(collectionName, "background"),
GetImage(collectionName, "border-r"),
GetImage(collectionName, "corner-bl"),
GetImage(collectionName, "border-b"),
GetImage(collectionName, "corner-br")
};
}
@@ -263,13 +250,13 @@ namespace OpenRA.Graphics
if (!collections.TryGetValue(collectionName, out var collection))
{
Log.Write("debug", $"Could not find collection '{collectionName}'");
Log.Write("debug", "Could not find collection '{0}'", collectionName);
return new Size(0, 0);
}
if (collection.PanelRegion == null || collection.PanelRegion.Length != 8)
{
Log.Write("debug", $"Collection '{collectionName}' does not define a valid PanelRegion");
Log.Write("debug", "Collection '{0}' does not define a valid PanelRegion", collectionName);
return new Size(0, 0);
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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.Graphics
{
public sealed class CursorManager
{
sealed class Cursor
class Cursor
{
public string Name;
public int2 PaddedSize;
@@ -28,14 +28,14 @@ namespace OpenRA.Graphics
public IHardwareCursor[] Cursors;
}
readonly Dictionary<string, Cursor> cursors = new();
readonly Dictionary<string, Cursor> cursors = new Dictionary<string, Cursor>();
readonly SheetBuilder sheetBuilder;
readonly GraphicSettings graphicSettings;
Cursor cursor;
bool isLocked = false;
int2 lockedPosition;
readonly bool hardwareCursorsDisabled = false;
bool hardwareCursorsDisabled = false;
bool hardwareCursorsDoubled = false;
public CursorManager(CursorProvider cursorProvider)
@@ -69,16 +69,9 @@ namespace OpenRA.Graphics
// 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);
// SheetBuilder expects data in BGRA
var data = FrameToBGRA(kv.Key, f, palette);
c.Sprites[c.Length++] = sheetBuilder.Add(data, f.Size, 0, hotspot);
// Bounds relative to the hotspot
c.Bounds = Rectangle.Union(c.Bounds, new Rectangle(hotspot, f.Size));
@@ -106,27 +99,34 @@ namespace OpenRA.Graphics
// Dispose any existing cursors to avoid leaking native resources
ClearHardwareCursors();
foreach (var kv in cursors)
try
{
var template = kv.Value;
for (var i = 0; i < template.Sprites.Length; i++)
foreach (var kv in cursors)
{
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
var template = kv.Value;
for (var i = 0; i < template.Sprites.Length; i++)
{
Log.Write("debug", $"Failed to initialize hardware cursor for {template.Name}.");
Console.WriteLine($"Failed to initialize hardware cursor for {template.Name}.");
if (template.Cursors[i] != null)
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;
template.Cursors[i] = CreateHardwareCursor(kv.Key, template.Sprites[i], paddingTL, paddingBR, -template.Bounds.Location);
}
}
}
catch (Exception e)
{
Log.Write("debug", "Failed to initialize hardware cursors. Falling back to software cursors.");
Log.Write("debug", "Error was: " + e.Message);
Console.WriteLine("Failed to initialize hardware cursors. Falling back to software cursors.");
Console.WriteLine("Error was: " + e.Message);
ClearHardwareCursors();
}
hardwareCursorsDoubled = graphicSettings.CursorDouble;
}
@@ -170,11 +170,10 @@ namespace OpenRA.Graphics
if (cursor != null && frame >= cursor.Cursors.Length)
frame %= cursor.Cursors.Length;
var hardwareCursor = cursor?.Cursors[frame];
if (hardwareCursor == null || isLocked)
if (cursor == null || isLocked)
Game.Renderer.Window.SetHardwareCursor(null);
else
Game.Renderer.Window.SetHardwareCursor(hardwareCursor);
Game.Renderer.Window.SetHardwareCursor(cursor.Cursors[frame]);
}
public void Render(Renderer renderer)
@@ -190,17 +189,17 @@ namespace OpenRA.Graphics
// Render cursor in software
var doubleCursor = graphicSettings.CursorDouble;
var cursorSprite = cursor.Sprites[frame % cursor.Length];
var cursorScale = doubleCursor ? 2 : 1;
var cursorSize = doubleCursor ? 2.0f * cursorSprite.Size : cursorSprite.Size;
// Cursor is rendered in native window coordinates
// Apply same scaling rules as hardware cursors
if (Game.Renderer.NativeWindowScale > 1.5f)
cursorScale *= 2;
cursorSize = 2 * cursorSize;
var mousePos = isLocked ? lockedPosition : Viewport.LastMousePos;
renderer.RgbaSpriteRenderer.DrawSprite(cursorSprite,
mousePos,
cursorScale / Game.Renderer.WindowScale);
cursorSize / Game.Renderer.WindowScale);
}
public void Lock()
@@ -218,27 +217,34 @@ namespace OpenRA.Graphics
Update();
}
public static byte[] ConvertIndexedToBgra(string name, ISpriteFrame frame, ImmutablePalette palette)
public static byte[] FrameToBGRA(string name, ISpriteFrame frame, ImmutablePalette palette)
{
if (frame.Type != SpriteFrameType.Indexed8)
throw new ArgumentException("ConvertIndexedToBgra requires input frames to be indexed.", nameof(frame));
// Data is already in BGRA format
if (frame.Type == SpriteFrameType.BGRA)
return frame.Data;
// Cursors may be either native BGRA or Indexed.
// Indexed sprites are converted to BGRA using the referenced palette.
// 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");
if (frame.Type == SpriteFrameType.Indexed && palette == null)
throw new InvalidOperationException("Cursor sequence `{0}` attempted to load an indexed sprite but does not define Palette".F(name));
var width = frame.Size.Width;
var height = frame.Size.Height;
var data = new byte[4 * width * height];
unsafe
for (var j = 0; j < height; j++)
{
// Cast the data to an int array so we can copy the src data directly
fixed (byte* bd = &data[0])
for (var i = 0; i < width; i++)
{
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]];
var bytes = BitConverter.GetBytes(palette[frame.Data[j * width + i]]);
var c = palette[frame.Data[j * width + i]];
var k = 4 * (j * width + i);
// Convert RGBA to BGRA
data[k] = bytes[2];
data[k + 1] = bytes[1];
data[k + 2] = bytes[0];
data[k + 3] = bytes[3];
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,14 +31,15 @@ namespace OpenRA.Graphics
// Overwrite previous definitions if there are duplicates
var pals = new Dictionary<string, IProvidesCursorPaletteInfo>();
foreach (var p in modData.DefaultRules.Actors[SystemActors.World].TraitInfos<IProvidesCursorPaletteInfo>())
foreach (var p in modData.DefaultRules.Actors["world"].TraitInfos<IProvidesCursorPaletteInfo>())
if (p.Palette != null)
pals[p.Palette] = p;
Palettes = nodesDict["Cursors"].Nodes.Select(n => n.Value.Value)
.Where(p => p != null)
.Distinct()
.ToDictionary(p => p, p => pals[p].ReadPalette(modData.DefaultFileSystem));
.ToDictionary(p => p, p => pals[p].ReadPalette(modData.DefaultFileSystem))
.AsReadOnly();
var frameCache = new FrameCache(fileSystem, modData.SpriteLoaders);
var cursors = new Dictionary<string, CursorSequence>();
@@ -46,7 +47,7 @@ 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 bool HasCursorSequence(string cursor)
@@ -59,7 +60,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));
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,36 +31,29 @@ namespace OpenRA.Graphics
Palette = palette;
Name = name;
var cursorSprites = cache[cursorSrc];
Frames = cursorSprites.Skip(Start).ToArray();
if ((d.TryGetValue("Length", out var yaml) && yaml.Value == "*") ||
(d.TryGetValue("End", out yaml) && yaml.Value == "*"))
Length = Frames.Length;
else if (d.TryGetValue("Length", out yaml))
Length = Exts.ParseIntegerInvariant(yaml.Value);
else if (d.TryGetValue("End", out yaml))
Length = Exts.ParseIntegerInvariant(yaml.Value) - Start;
if ((d.ContainsKey("Length") && d["Length"].Value == "*") || (d.ContainsKey("End") && d["End"].Value == "*"))
Length = Frames.Length - Start;
else if (d.ContainsKey("Length"))
Length = Exts.ParseIntegerInvariant(d["Length"].Value);
else if (d.ContainsKey("End"))
Length = Exts.ParseIntegerInvariant(d["End"].Value) - Start;
else
Length = 1;
Frames = Frames.Take(Length).ToArray();
Frames = cache[cursorSrc]
.Skip(Start)
.Take(Length)
.ToArray();
if (Start > cursorSprites.Length)
throw new YamlException($"Cursor {name}: {nameof(Start)} is greater than the length of the sprite sequence.");
if (Length > cursorSprites.Length)
throw new YamlException($"Cursor {name}: {nameof(Length)} is greater than the length of the sprite sequence.");
if (d.TryGetValue("X", out yaml))
if (d.ContainsKey("X"))
{
Exts.TryParseIntegerInvariant(yaml.Value, out var x);
Exts.TryParseIntegerInvariant(d["X"].Value, out var x);
Hotspot = Hotspot.WithX(x);
}
if (d.TryGetValue("Y", out yaml))
if (d.ContainsKey("Y"))
{
Exts.TryParseIntegerInvariant(yaml.Value, out var y);
Exts.TryParseIntegerInvariant(d["Y"].Value, out var y);
Hotspot = Hotspot.WithY(y);
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,98 +17,73 @@ 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();
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))
if (modifiablePalettes.TryGetValue(name, out var mutable))
return mutable.AsReadOnly();
if (palettes.TryGetValue(name, out var 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");
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);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Performance", "CA1854:Prefer the 'IDictionary.TryGetValue(TKey, out TValue)' method",
Justification = "False positive - indexer is a set not a get.")]
public void ReplacePalette(string name, IPalette p)
{
if (mutablePalettes.ContainsKey(name))
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();
@@ -122,27 +97,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;
@@ -153,7 +127,6 @@ namespace OpenRA.Graphics
public void Dispose()
{
Texture.Dispose();
ColorShifts.Dispose();
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,6 @@
#endregion
using System;
using System.Collections.Generic;
using OpenRA.FileSystem;
using OpenRA.Primitives;
@@ -26,11 +25,11 @@ namespace OpenRA.Graphics
float[] Bounds(uint frame);
ModelRenderData RenderData(uint section);
/// <summary>Returns the smallest rectangle that covers all rotations of all frames in a model.</summary>
/// <summary>Returns the smallest rectangle that covers all rotations of all frames in a model</summary>
Rectangle AggregateBounds { get; }
}
public readonly struct ModelRenderData
public struct ModelRenderData
{
public readonly int Start;
public readonly int Count;
@@ -46,7 +45,6 @@ namespace OpenRA.Graphics
public interface IModelCache : IDisposable
{
IModel GetModel(string model);
IModel GetModelSequence(string model, string sequence);
bool HasModelSequence(string model, string sequence);
IVertexBuffer<Vertex> VertexBuffer { get; }
@@ -62,17 +60,12 @@ namespace OpenRA.Graphics
{
public Action<string> OnMissingModelError { get; set; }
sealed class PlaceholderModelCache : IModelCache
class PlaceholderModelCache : IModelCache
{
public IVertexBuffer<Vertex> VertexBuffer => throw new NotImplementedException();
public IVertexBuffer<Vertex> VertexBuffer { get { throw new NotImplementedException(); } }
public void Dispose() { }
public IModel GetModel(string model)
{
throw new NotImplementedException();
}
public IModel GetModelSequence(string model, string sequence)
{
throw new NotImplementedException();

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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 OpenRA.Primitives;
namespace OpenRA.Graphics
{
public readonly struct ModelAnimation
public struct ModelAnimation
{
public readonly IModel Model;
public readonly Func<WVec> OffsetFunc;
@@ -46,6 +46,12 @@ namespace OpenRA.Graphics
xy.Y + (int)(r.Bottom * scale));
}
public bool IsVisible => DisableFunc == null || !DisableFunc();
public bool IsVisible
{
get
{
return DisableFunc == null || !DisableFunc();
}
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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
@@ -37,19 +37,18 @@ namespace OpenRA.Graphics
// Static constants
static readonly float[] ShadowDiffuse = new float[] { 0, 0, 0 };
static readonly float[] ShadowAmbient = new float[] { 1, 1, 1 };
static readonly float2 SpritePadding = new(2, 2);
static readonly float2 SpritePadding = new float2(2, 2);
static readonly float[] ZeroVector = new float[] { 0, 0, 0, 1 };
static readonly float[] ZVector = new float[] { 0, 0, 1, 1 };
static readonly float[] FlipMtx = Util.ScaleMatrix(1, -1, 1);
static readonly float[] ShadowScaleFlipMtx = Util.ScaleMatrix(2, -2, 2);
static readonly float[] GroundNormal = { 0, 0, 1, 1 };
readonly Renderer renderer;
readonly IShader shader;
readonly Dictionary<Sheet, IFrameBuffer> mappedBuffers = new();
readonly Stack<KeyValuePair<Sheet, IFrameBuffer>> unmappedBuffers = new();
readonly List<(Sheet Sheet, Action Func)> doRender = new();
readonly Dictionary<Sheet, IFrameBuffer> mappedBuffers = new Dictionary<Sheet, IFrameBuffer>();
readonly Stack<KeyValuePair<Sheet, IFrameBuffer>> unmappedBuffers = new Stack<KeyValuePair<Sheet, IFrameBuffer>>();
readonly List<(Sheet Sheet, Action Func)> doRender = new List<(Sheet, Action)>();
SheetBuilder sheetBuilderForFrame;
bool isInFrame;
@@ -65,7 +64,7 @@ namespace OpenRA.Graphics
shader.SetTexture("Palette", palette);
}
public void SetViewportParams()
public void SetViewportParams(Size screen, int2 scroll)
{
var a = 2f / renderer.SheetSize;
var view = new[]
@@ -80,8 +79,8 @@ namespace OpenRA.Graphics
}
public ModelRenderProxy RenderAsync(
WorldRenderer wr, IEnumerable<ModelAnimation> models, in WRot camera, float scale,
in WRot groundOrientation, in WRot lightSource, float[] lightAmbientColor, float[] lightDiffuseColor,
WorldRenderer wr, IEnumerable<ModelAnimation> models, WRot camera, float scale,
float[] groundNormal, WRot lightSource, float[] lightAmbientColor, float[] lightDiffuseColor,
PaletteReference color, PaletteReference normals, PaletteReference shadowPalette)
{
if (!isInFrame)
@@ -93,10 +92,7 @@ namespace OpenRA.Graphics
// Correct for bogus light source definition
var lightYaw = Util.MakeFloatMatrix(new WRot(WAngle.Zero, WAngle.Zero, -lightSource.Yaw).AsMatrix());
var lightPitch = Util.MakeFloatMatrix(new WRot(WAngle.Zero, -lightSource.Pitch, WAngle.Zero).AsMatrix());
var ground = Util.MakeFloatMatrix(groundOrientation.AsMatrix());
var shadowTransform = Util.MatrixMultiply(Util.MatrixMultiply(lightPitch, lightYaw), Util.MatrixInverse(ground));
var groundNormal = Util.MatrixVectorMultiply(ground, GroundNormal);
var shadowTransform = Util.MatrixMultiply(lightPitch, lightYaw);
var invShadowTransform = Util.MatrixInverse(shadowTransform);
var cameraTransform = Util.MakeFloatMatrix(camera.AsMatrix());
@@ -167,7 +163,8 @@ namespace OpenRA.Graphics
CalculateSpriteGeometry(tl, br, 1, out var spriteSize, out var spriteOffset);
CalculateSpriteGeometry(stl, sbr, 2, out var shadowSpriteSize, out var shadowSpriteOffset);
sheetBuilderForFrame ??= new SheetBuilder(SheetType.BGRA, AllocateSheet);
if (sheetBuilderForFrame == null)
sheetBuilderForFrame = new SheetBuilder(SheetType.BGRA, AllocateSheet);
var sprite = sheetBuilderForFrame.Allocate(spriteSize, 0, spriteOffset);
var shadowSprite = sheetBuilderForFrame.Allocate(shadowSpriteSize, 0, shadowSpriteOffset);
@@ -208,7 +205,7 @@ namespace OpenRA.Graphics
var t = m.Model.TransformationMatrix(i, frame);
var it = Util.MatrixInverse(t);
if (it == null)
throw new InvalidOperationException($"Failed to invert the transformed matrix of frame {i} during RenderAsync.");
throw new InvalidOperationException("Failed to invert the transformed matrix of frame {0} during RenderAsync.".F(i));
// Transform light vector from shadow -> world -> limb coords
var lightDirection = ExtractRotationVector(Util.MatrixMultiply(it, lightTransform));
@@ -302,7 +299,7 @@ namespace OpenRA.Graphics
return fbo;
}
static void DisableFrameBuffer(IFrameBuffer fbo)
void DisableFrameBuffer(IFrameBuffer fbo)
{
Game.Renderer.Flush();
Game.Renderer.Context.DisableDepthBuffer();

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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
@@ -40,12 +40,11 @@ namespace OpenRA.Graphics
return new ReadOnlyPalette(palette);
}
sealed class ReadOnlyPalette : IPalette
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 +56,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++)
@@ -92,9 +94,7 @@ namespace OpenRA.Graphics
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 +108,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 +126,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)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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);
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,8 +17,6 @@ namespace OpenRA
{
public enum GLProfile
{
Automatic,
ANGLE,
Modern,
Embedded,
Legacy
@@ -26,7 +24,7 @@ namespace OpenRA
public interface IPlatform
{
IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay, GLProfile profile, bool enableLegacyGL);
IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay, GLProfile profile);
ISoundEngine CreateSound(string device);
IFont CreateFont(byte[] data);
}
@@ -59,7 +57,6 @@ namespace OpenRA
int DisplayCount { get; }
int CurrentDisplay { get; }
bool HasInputFocus { get; }
bool IsSuspended { get; }
event Action<float, float, float, float> OnWindowScaleChanged;
@@ -72,7 +69,6 @@ namespace OpenRA
IHardwareCursor CreateHardwareCursor(string name, Size size, byte[] data, int2 hotspot, bool pixelDouble);
void SetHardwareCursor(IHardwareCursor cursor);
void SetWindowTitle(string title);
void SetRelativeMouseMode(bool mode);
void SetScaleModifier(float scale);
@@ -84,7 +80,6 @@ namespace OpenRA
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);
@@ -106,12 +101,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
@@ -130,8 +121,8 @@ namespace OpenRA
public interface ITexture : IDisposable
{
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; }
@@ -153,7 +144,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; }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,7 @@
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Primitives;
@@ -17,37 +18,43 @@ 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, Color c, float rampFraction)
{
var h = c.GetHue() / 360.0f;
var s = c.GetSaturation();
var l = c.GetBrightness();
// Increase luminosity if required to represent the full ramp
var rampRange = (byte)((1 - rampFraction) * l);
var c1 = Color.FromAhsl(h, s, Math.Max(rampRange, l));
var c2 = Color.FromAhsl(h, s, (byte)Math.Max(0, l - rampRange));
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) => (baseIndex + i, Exts.ColorLerp(x / (float)ramp.Length, c1, c2)))
.ToDictionary(u => u.Item1, u => u.Item2);
}
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);
return remapColors.TryGetValue(index, out var c)
? c : original;
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,6 @@
*/
#endregion
using System;
using OpenRA.Primitives;
namespace OpenRA.Graphics
@@ -17,38 +16,21 @@ 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
public interface ITintableRenderable
{
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);
IRenderable WithTint(float3 newTint);
}
public interface IFinalizedRenderable

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,7 +18,7 @@ namespace OpenRA.Graphics
{
public class RgbaColorRenderer
{
static readonly float3 Offset = new(0.5f, 0.5f, 0f);
static readonly float3 Offset = new float3(0.5f, 0.5f, 0f);
readonly SpriteRenderer parent;
readonly Vertex[] vertices = new Vertex[6];
@@ -28,7 +28,7 @@ namespace OpenRA.Graphics
this.parent = parent;
}
public void DrawLine(in float3 start, in float3 end, float width, Color startColor, Color endColor, BlendMode blendMode = BlendMode.Alpha)
public void DrawLine(float3 start, float3 end, float width, Color startColor, Color endColor)
{
var delta = (end - start) / (end - start).XY.Length;
var corner = width / 2 * new float3(-delta.Y, delta.X, delta.Z);
@@ -52,10 +52,10 @@ namespace OpenRA.Graphics
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);
parent.DrawRGBAVertices(vertices);
}
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)
{
var delta = (end - start) / (end - start).XY.Length;
var corner = width / 2 * new float2(-delta.Y, delta.X);
@@ -72,15 +72,15 @@ namespace OpenRA.Graphics
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);
parent.DrawRGBAVertices(vertices);
}
/// <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>
static 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 +90,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 +101,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,7 +116,7 @@ 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;
}
@@ -138,7 +138,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);
@@ -163,7 +163,7 @@ namespace OpenRA.Graphics
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);
parent.DrawRGBAVertices(vertices);
// Advance line segment
end = next;
@@ -175,32 +175,32 @@ namespace OpenRA.Graphics
}
}
public void DrawLine(IEnumerable<float3> points, float width, Color color, bool connectSegments = false, BlendMode blendMode = BlendMode.Alpha)
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)
public void FillTriangle(float3 a, float3 b, float3 c, Color color)
{
color = Util.PremultiplyAlpha(color);
var cr = color.R / 255.0f;
@@ -211,17 +211,17 @@ namespace OpenRA.Graphics
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);
parent.DrawRGBAVertices(vertices);
}
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)
{
color = Util.PremultiplyAlpha(color);
var cr = color.R / 255.0f;
@@ -235,10 +235,10 @@ namespace OpenRA.Graphics
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);
parent.DrawRGBAVertices(vertices);
}
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)
public void FillRect(float3 a, float3 b, float3 c, float3 d, Color topLeftColor, Color topRightColor, Color bottomRightColor, Color bottomLeftColor)
{
vertices[0] = VertexWithColor(a + Offset, topLeftColor);
vertices[1] = VertexWithColor(b + Offset, topRightColor);
@@ -247,10 +247,10 @@ namespace OpenRA.Graphics
vertices[4] = VertexWithColor(d + Offset, bottomLeftColor);
vertices[5] = VertexWithColor(a + Offset, topLeftColor);
parent.DrawRGBAVertices(vertices, blendMode);
parent.DrawRGBAVertices(vertices);
}
static Vertex VertexWithColor(in float3 xyz, Color color)
static Vertex VertexWithColor(float3 xyz, Color color)
{
color = Util.PremultiplyAlpha(color);
var cr = color.R / 255.0f;
@@ -261,7 +261,7 @@ namespace OpenRA.Graphics
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,7 +272,7 @@ 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);
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,36 +22,44 @@ namespace OpenRA.Graphics
this.parent = parent;
}
public void DrawSprite(Sprite s, in float3 location, in float3 scale, float rotation = 0f)
public void DrawSprite(Sprite s, float3 location, float3 size)
{
if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
parent.DrawSprite(s, 0, location, scale, rotation);
parent.DrawSprite(s, location, 0, size);
}
public void DrawSprite(Sprite s, in float3 location, float scale = 1f, float rotation = 0f)
public void DrawSprite(Sprite s, float3 location)
{
if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
parent.DrawSprite(s, 0, location, scale, rotation);
parent.DrawSprite(s, location, 0, s.Size);
}
public void DrawSprite(Sprite s, in float3 location, float scale, in float3 tint, float alpha, float rotation = 0f)
public void DrawSprite(Sprite s, float3 a, float3 b, float3 c, float3 d)
{
if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
parent.DrawSprite(s, 0, location, scale, tint, alpha, rotation);
parent.DrawSprite(s, a, b, c, d);
}
public void DrawSprite(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint, float alpha)
public void DrawSpriteWithTint(Sprite s, float3 location, float3 size, float3 tint)
{
if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
parent.DrawSprite(s, 0, a, b, c, d, tint, alpha);
parent.DrawSpriteWithTint(s, location, 0, size, tint);
}
public void DrawSpriteWithTint(Sprite s, float3 a, float3 b, float3 c, float3 d, float3 tint)
{
if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
parent.DrawSpriteWithTint(s, a, b, c, d, tint);
}
}
}

View File

@@ -0,0 +1,146 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 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.FileSystem;
using OpenRA.Primitives;
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; }
Rectangle Bounds { get; }
bool IgnoreWorldTint { get; }
Sprite GetSprite(int frame);
Sprite GetSprite(int frame, WAngle facing);
Sprite GetShadow(int frame, WAngle 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));
}
public ISpriteSequence GetSequence(string unitName, string sequenceName)
{
if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
if (!unitSeq.Value.TryGetValue(sequenceName, out var 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)
{
if (!sequences.Value.TryGetValue(unitName, out var 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)
{
if (!sequences.Value.TryGetValue(unitName, out var 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("|");
if (sequenceCache.TryGetValue(key, out var 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()
{
foreach (var sb in SpriteCache.SheetBuilders.Values)
sb.Current.CreateBuffer();
foreach (var unitSeq in sequences.Value.Values)
foreach (var seq in unitSeq.Value.Values) { }
foreach (var sb in SpriteCache.SheetBuilders.Values)
sb.Current.ReleaseBuffer();
}
public void Dispose()
{
if (spriteCache.IsValueCreated)
foreach (var sb in SpriteCache.SheetBuilders.Values)
sb.Dispose();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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
@@ -32,7 +32,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)
{
@@ -79,17 +79,21 @@ namespace OpenRA.Graphics
public Png AsPng()
{
if (Type == SheetType.Indexed)
throw new InvalidOperationException("AsPng() cannot be called on Indexed sheets.");
var data = GetData();
return new Png(GetData(), SpriteFrameType.Bgra32, Size.Width, Size.Height);
// Convert BGRA to RGBA
for (var i = 0; i < Size.Width * Size.Height; i++)
{
var temp = data[i * 4];
data[i * 4] = data[i * 4 + 2];
data[i * 4 + 2] = temp;
}
return new Png(data, Size.Width, Size.Height);
}
public Png AsPng(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;
@@ -103,7 +107,7 @@ namespace OpenRA.Graphics
for (var i = 0; i < Palette.Size; i++)
palColors[i] = pal.GetColor(i);
return new Png(plane, SpriteFrameType.Indexed8, Size.Width, Size.Height, palColors);
return new Png(plane, Size.Width, Size.Height, palColors);
}
public void CreateBuffer()

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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
@@ -34,16 +34,15 @@ namespace OpenRA.Graphics
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;
Sheet current;
TextureChannel channel;
int rowHeight = 0;
int2 p;
public Sheet Current { get; private set; }
public TextureChannel CurrentChannel { get; private set; }
public IEnumerable<Sheet> AllSheets => sheets;
public static Sheet AllocateSheet(SheetType type, int sheetSize)
{
return new Sheet(type, new Size(sheetSize, sheetSize));
@@ -53,16 +52,9 @@ namespace OpenRA.Graphics
{
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}");
case SpriteFrameType.Indexed: return SheetType.Indexed;
case SpriteFrameType.BGRA: return SheetType.BGRA;
default: throw new NotImplementedException("Unknown SpriteFrameType {0}".F(t));
}
}
@@ -74,36 +66,45 @@ namespace OpenRA.Graphics
public SheetBuilder(SheetType t, Func<Sheet> allocateSheet, int margin = 1)
{
CurrentChannel = t == SheetType.Indexed ? TextureChannel.Red : TextureChannel.RGBA;
channel = t == SheetType.Indexed ? TextureChannel.Red : TextureChannel.RGBA;
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)
{
var rect = Allocate(new Size(src.Width, src.Height), scale);
var rect = Allocate(new Size(src.Width, src.Height), scale);
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;
@@ -114,9 +115,9 @@ namespace OpenRA.Graphics
}
public Sprite Allocate(Size imageSize, float scale = 1f) { return Allocate(imageSize, 0, float3.Zero, scale); }
public Sprite Allocate(Size imageSize, float zRamp, in float3 spriteOffset, float scale = 1f)
public Sprite Allocate(Size imageSize, float zRamp, float3 spriteOffset, float scale = 1f)
{
if (imageSize.Width + p.X + margin > Current.Size.Width)
if (imageSize.Width + p.X + margin > current.Size.Width)
{
p = new int2(0, p.Y + rowHeight + margin);
rowHeight = imageSize.Height;
@@ -125,29 +126,33 @@ namespace OpenRA.Graphics
if (imageSize.Height > rowHeight)
rowHeight = imageSize.Height;
if (p.Y + imageSize.Height + margin > Current.Size.Height)
if (p.Y + imageSize.Height + margin > 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 = Type == SheetType.Indexed ? TextureChannel.Red : TextureChannel.RGBA;
}
else
CurrentChannel = next.Value;
channel = next.Value;
rowHeight = imageSize.Height;
p = int2.Zero;
}
var rect = new Sprite(Current, new Rectangle(p.X + margin, p.Y + margin, imageSize.Width, imageSize.Height), zRamp, spriteOffset, CurrentChannel, BlendMode.Alpha, scale);
var rect = new Sprite(current, new Rectangle(p.X + margin, p.Y + margin, imageSize.Width, imageSize.Height), zRamp, spriteOffset, channel, BlendMode.Alpha, scale);
p += new int2(imageSize.Width + margin, 0);
return rect;
}
public Sheet Current { get { return current; } }
public TextureChannel CurrentChannel { get { return channel; } }
public IEnumerable<Sheet> AllSheets { get { return sheets; } }
public void Dispose()
{
foreach (var sheet in sheets)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,12 +23,13 @@ namespace OpenRA.Graphics
public readonly float ZRamp;
public readonly float3 Size;
public readonly float3 Offset;
public readonly float3 FractionalOffset;
public readonly float Top, Left, Bottom, Right;
public Sprite(Sheet sheet, Rectangle bounds, TextureChannel channel, float scale = 1)
: this(sheet, bounds, 0, float2.Zero, channel, BlendMode.Alpha, scale) { }
public Sprite(Sheet sheet, Rectangle bounds, float zRamp, in float3 offset, TextureChannel channel, BlendMode blendMode = BlendMode.Alpha, float scale = 1f)
public Sprite(Sheet sheet, Rectangle bounds, float zRamp, float3 offset, TextureChannel channel, BlendMode blendMode = BlendMode.Alpha, float scale = 1f)
{
Sheet = sheet;
Bounds = bounds;
@@ -37,16 +38,13 @@ namespace OpenRA.Graphics
Channel = channel;
Size = scale * new float3(bounds.Size.Width, bounds.Size.Height, bounds.Size.Height * zRamp);
BlendMode = blendMode;
FractionalOffset = Size.Z != 0 ? offset / Size :
new float3(offset.X / Size.X, offset.Y / Size.Y, 0);
// Some GPUs suffer from precision issues when rendering into non 1:1 framebuffers that result
// in rendering a line of texels that sample outside the sprite rectangle.
// Insetting the texture coordinates by a small fraction of a pixel avoids this
// with negligible impact on the 1:1 rendering case.
var inset = 1 / 128f;
Left = (Math.Min(bounds.Left, bounds.Right) + inset) / sheet.Size.Width;
Top = (Math.Min(bounds.Top, bounds.Bottom) + inset) / sheet.Size.Height;
Right = (Math.Max(bounds.Left, bounds.Right) - inset) / sheet.Size.Width;
Bottom = (Math.Max(bounds.Top, bounds.Bottom) - inset) / sheet.Size.Height;
Left = (float)Math.Min(bounds.Left, bounds.Right) / sheet.Size.Width;
Top = (float)Math.Min(bounds.Top, bounds.Bottom) / sheet.Size.Height;
Right = (float)Math.Max(bounds.Left, bounds.Right) / sheet.Size.Width;
Bottom = (float)Math.Max(bounds.Top, bounds.Bottom) / sheet.Size.Height;
}
}

View File

@@ -1,174 +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 OpenRA.FileSystem;
using OpenRA.Primitives;
namespace OpenRA.Graphics
{
public sealed class SpriteCache : IDisposable
{
public readonly Dictionary<SheetType, SheetBuilder> SheetBuilders;
readonly ISpriteLoader[] loaders;
readonly IReadOnlyFileSystem fileSystem;
readonly Dictionary<int, (int[] Frames, MiniYamlNode.SourceLocation Location)> spriteReservations = new();
readonly Dictionary<int, (int[] Frames, MiniYamlNode.SourceLocation Location)> frameReservations = new();
readonly Dictionary<string, List<int>> reservationsByFilename = new();
readonly Dictionary<int, ISpriteFrame[]> resolvedFrames = new();
readonly Dictionary<int, Sprite[]> resolvedSprites = new();
readonly Dictionary<int, (string Filename, MiniYamlNode.SourceLocation Location)> missingFiles = new();
int nextReservationToken = 1;
public SpriteCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders, int bgraSheetSize, int indexedSheetSize, int bgraSheetMargin = 1, int indexedSheetMargin = 1)
{
SheetBuilders = new Dictionary<SheetType, SheetBuilder>
{
{ SheetType.Indexed, new SheetBuilder(SheetType.Indexed, indexedSheetSize, indexedSheetMargin) },
{ SheetType.BGRA, new SheetBuilder(SheetType.BGRA, bgraSheetSize, bgraSheetMargin) }
};
this.fileSystem = fileSystem;
this.loaders = loaders;
}
public int ReserveSprites(string filename, IEnumerable<int> frames, MiniYamlNode.SourceLocation location)
{
var token = nextReservationToken++;
spriteReservations[token] = (frames?.ToArray(), location);
reservationsByFilename.GetOrAdd(filename, _ => new List<int>()).Add(token);
return token;
}
public int ReserveFrames(string filename, IEnumerable<int> frames, MiniYamlNode.SourceLocation location)
{
var token = nextReservationToken++;
frameReservations[token] = (frames?.ToArray(), location);
reservationsByFilename.GetOrAdd(filename, _ => new List<int>()).Add(token);
return token;
}
static ISpriteFrame[] GetFrames(IReadOnlyFileSystem fileSystem, string filename, ISpriteLoader[] loaders, out TypeDictionary metadata)
{
metadata = null;
if (!fileSystem.TryOpen(filename, out var stream))
return null;
using (stream)
{
foreach (var loader in loaders)
if (loader.TryParseSprite(stream, filename, out var frames, out metadata))
return frames;
return null;
}
}
public void LoadReservations(ModData modData)
{
foreach (var sb in SheetBuilders.Values)
sb.Current.CreateBuffer();
var spriteCache = new Dictionary<int, Sprite>();
foreach (var (filename, tokens) in reservationsByFilename)
{
modData.LoadScreen?.Display();
var loadedFrames = GetFrames(fileSystem, filename, loaders, out _);
foreach (var token in tokens)
{
if (frameReservations.TryGetValue(token, out var r))
{
if (loadedFrames != null)
{
if (r.Frames != null)
{
var resolved = new ISpriteFrame[loadedFrames.Length];
foreach (var i in r.Frames)
resolved[i] = loadedFrames[i];
resolvedFrames[token] = resolved;
}
else
resolvedFrames[token] = loadedFrames;
}
else
{
resolvedFrames[token] = null;
missingFiles[token] = (filename, r.Location);
}
}
if (spriteReservations.TryGetValue(token, out r))
{
if (loadedFrames != null)
{
var resolved = new Sprite[loadedFrames.Length];
var frames = r.Frames ?? Enumerable.Range(0, loadedFrames.Length);
foreach (var i in frames)
resolved[i] = spriteCache.GetOrAdd(i,
f => SheetBuilders[SheetBuilder.FrameTypeToSheetType(loadedFrames[f].Type)].Add(loadedFrames[f]));
resolvedSprites[token] = resolved;
}
else
{
resolvedSprites[token] = null;
missingFiles[token] = (filename, r.Location);
}
}
}
spriteCache.Clear();
}
spriteReservations.Clear();
frameReservations.Clear();
reservationsByFilename.Clear();
foreach (var sb in SheetBuilders.Values)
sb.Current.ReleaseBuffer();
}
public Sprite[] ResolveSprites(int token)
{
var resolved = resolvedSprites[token];
resolvedSprites.Remove(token);
if (missingFiles.TryGetValue(token, out var r))
throw new FileNotFoundException($"{r.Location}: {r.Filename} not found", r.Filename);
return resolved;
}
public ISpriteFrame[] ResolveFrames(int token)
{
var resolved = resolvedFrames[token];
resolvedFrames.Remove(token);
if (missingFiles.TryGetValue(token, out var r))
throw new FileNotFoundException($"{r.Location}: {r.Filename} not found", r.Filename);
return resolved;
}
public IEnumerable<(string Filename, MiniYamlNode.SourceLocation Location)> MissingFiles => missingFiles.Values.ToHashSet();
public void Dispose()
{
foreach (var sb in SheetBuilders.Values)
sb.Dispose();
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,7 @@
#endregion
using System;
using System.Linq;
using OpenRA.Primitives;
using OpenRA.Support;
@@ -17,9 +18,10 @@ namespace OpenRA.Graphics
{
public sealed class SpriteFont : IDisposable
{
public int TopOffset { get; }
public int TopOffset { get; private set; }
readonly int size;
readonly SheetBuilder builder;
readonly Func<string, float> lineWidth;
readonly IFont font;
readonly Cache<char, GlyphInfo> glyphs;
readonly Cache<(char C, int Radius), Sprite> contrastGlyphs;
@@ -30,7 +32,7 @@ namespace OpenRA.Graphics
public SpriteFont(string name, byte[] data, int size, int ascender, float scale, SheetBuilder builder)
{
if (builder.Type != SheetType.BGRA)
throw new ArgumentException("The sheet builder must create BGRA sheets.", nameof(builder));
throw new ArgumentException("The sheet builder must create BGRA sheets.", "builder");
deviceScale = scale;
this.size = size;
@@ -41,9 +43,13 @@ namespace OpenRA.Graphics
contrastGlyphs = new Cache<(char, int), Sprite>(CreateContrastGlyph);
dilationElements = new Cache<int, float[]>(CreateCircularWeightMap);
// PERF: Cache these delegates for Measure calls.
Func<char, float> characterWidth = character => glyphs[character].Advance;
lineWidth = line => line.Sum(characterWidth) / deviceScale;
// Pre-cache small font sizes so glyphs are immediately available when we need them
if (size <= 24)
using (new PerfTimer($"Precache {name} {size}px"))
using (new PerfTimer("Precache {0} {1}px".F(name, size)))
for (var n = (char)0x20; n < (char)0x7f; n++)
if (glyphs[n] == null)
throw new InvalidOperationException();
@@ -83,10 +89,10 @@ namespace OpenRA.Graphics
if (g.Sprite != null)
{
var contrastSprite = contrastGlyphs[(s, screenContrast)];
Game.Renderer.RgbaSpriteRenderer.DrawSprite(contrastSprite,
Game.Renderer.RgbaSpriteRenderer.DrawSpriteWithTint(contrastSprite,
(screen + g.Offset - contrastVector) / deviceScale,
1f / deviceScale,
tint, 1f);
contrastSprite.Size / deviceScale,
tint);
}
screen += new int2((int)(g.Advance + 0.5f), 0);
@@ -114,16 +120,16 @@ namespace OpenRA.Graphics
// Convert screen coordinates back to UI coordinates for drawing
if (g.Sprite != null)
Game.Renderer.RgbaSpriteRenderer.DrawSprite(g.Sprite,
Game.Renderer.RgbaSpriteRenderer.DrawSpriteWithTint(g.Sprite,
(screen + g.Offset).ToFloat2() / deviceScale,
1f / deviceScale,
tint, 1f);
g.Sprite.Size / deviceScale,
tint);
screen += new int2((int)(g.Advance + 0.5f), 0);
}
}
static float2 Rotate(float2 v, float sina, float cosa, float2 offset)
float2 Rotate(float2 v, float sina, float cosa, float2 offset)
{
return new float2(
v.X * cosa - v.Y * sina + offset.X,
@@ -166,12 +172,12 @@ namespace OpenRA.Graphics
// Offset rotated glyph to align the top-left corner with the screen pixel grid
var screenOffset = new float2((int)(ra.X * deviceScale + 0.5f), (int)(ra.Y * deviceScale + 0.5f)) / deviceScale - ra;
Game.Renderer.RgbaSpriteRenderer.DrawSprite(g.Sprite,
Game.Renderer.RgbaSpriteRenderer.DrawSpriteWithTint(g.Sprite,
ra + screenOffset,
rb + screenOffset,
rc + screenOffset,
rd + screenOffset,
tint, 1f);
tint);
}
p += new float2(g.Advance / deviceScale, 0);
@@ -232,26 +238,8 @@ namespace OpenRA.Graphics
if (string.IsNullOrEmpty(text))
return new int2(0, size);
var lines = text.SplitLines('\n');
var maxWidth = 0f;
var rows = 0;
foreach (var line in lines)
{
rows++;
maxWidth = Math.Max(maxWidth, LineWidth(line));
}
return new int2((int)Math.Ceiling(maxWidth), rows * size);
}
float LineWidth(ReadOnlySpan<char> line)
{
var result = 0f;
foreach (var c in line)
result += glyphs[c].Advance;
return result / deviceScale;
var lines = text.Split('\n');
return new int2((int)Math.Ceiling(lines.Max(lineWidth)), lines.Length * size);
}
GlyphInfo CreateGlyph(char c)
@@ -427,7 +415,7 @@ namespace OpenRA.Graphics
}
}
sealed class GlyphInfo
class GlyphInfo
{
public float Advance;
public int2 Offset;

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,39 +9,20 @@
*/
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenRA.FileSystem;
using OpenRA.Primitives;
namespace OpenRA.Graphics
{
/// <summary>
/// Describes the format of the pixel data in a ISpriteFrame.
/// Note that the channel order is defined for little-endian bytes, so BGRA corresponds
/// to a 32bit ARGB value, such as that returned by Color.ToArgb().
/// </summary>
public enum SpriteFrameType
{
// 8 bit index into an external palette
Indexed8,
// 32 bit color such as returned by Color.ToArgb() or the bmp file format
// (remember that little-endian systems place the little bits in the first byte!)
Bgra32,
// Like BGRA, but without an alpha channel
Bgr24,
// 32 bit color in big-endian format, like png
Rgba32,
// Like RGBA, but without an alpha channel
Rgb24
}
public enum SpriteFrameType { Indexed, BGRA }
public interface ISpriteLoader
{
bool TryParseSprite(Stream s, string filename, out ISpriteFrame[] frames, out TypeDictionary metadata);
bool TryParseSprite(Stream s, out ISpriteFrame[] frames, out TypeDictionary metadata);
}
public interface ISpriteFrame
@@ -64,6 +45,94 @@ namespace OpenRA.Graphics
bool DisableExportPadding { get; }
}
public class SpriteCache
{
public readonly Cache<SpriteFrameType, SheetBuilder> SheetBuilders;
readonly ISpriteLoader[] loaders;
readonly IReadOnlyFileSystem fileSystem;
readonly Dictionary<string, List<Sprite[]>> sprites = new Dictionary<string, List<Sprite[]>>();
readonly Dictionary<string, ISpriteFrame[]> unloadedFrames = new Dictionary<string, ISpriteFrame[]>();
readonly Dictionary<string, TypeDictionary> metadata = new Dictionary<string, TypeDictionary>();
public SpriteCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders)
{
SheetBuilders = new Cache<SpriteFrameType, SheetBuilder>(t => new SheetBuilder(SheetBuilder.FrameTypeToSheetType(t)));
this.fileSystem = fileSystem;
this.loaders = loaders;
}
/// <summary>
/// Returns the first set of sprites with the given filename.
/// If getUsedFrames is defined then the indices returned by the function call
/// are guaranteed to be loaded. The value of other indices in the returned
/// array are undefined and should never be accessed.
/// </summary>
public Sprite[] this[string filename, Func<int, IEnumerable<int>> getUsedFrames = null]
{
get
{
var allSprites = sprites.GetOrAdd(filename);
var sprite = allSprites.FirstOrDefault();
if (!unloadedFrames.TryGetValue(filename, out var unloaded))
unloaded = null;
// This is the first time that the file has been requested
// Load all of the frames into the unused buffer and initialize
// the loaded cache (initially empty)
if (sprite == null)
{
unloaded = FrameLoader.GetFrames(fileSystem, filename, loaders, out var fileMetadata);
unloadedFrames[filename] = unloaded;
metadata[filename] = fileMetadata;
sprite = new Sprite[unloaded.Length];
allSprites.Add(sprite);
}
// HACK: The sequence code relies on side-effects from getUsedFrames
var indices = getUsedFrames != null ? getUsedFrames(sprite.Length) :
Enumerable.Range(0, sprite.Length);
// Load any unused frames into the SheetBuilder
if (unloaded != null)
{
foreach (var i in indices)
{
if (unloaded[i] != null)
{
sprite[i] = SheetBuilders[unloaded[i].Type].Add(unloaded[i]);
unloaded[i] = null;
}
}
// All frames have been loaded
if (unloaded.All(f => f == null))
unloadedFrames.Remove(filename);
}
return sprite;
}
}
/// <summary>
/// Returns a TypeDictionary containing any metadata defined by the frame
/// or null if the frame does not define metadata.
/// </summary>
public TypeDictionary FrameMetadata(string filename)
{
if (!metadata.TryGetValue(filename, out var fileMetadata))
{
FrameLoader.GetFrames(fileSystem, filename, loaders, out fileMetadata);
metadata[filename] = fileMetadata;
}
return fileMetadata;
}
}
public class FrameCache
{
readonly Cache<string, ISpriteFrame[]> frames;
@@ -73,7 +142,7 @@ namespace OpenRA.Graphics
frames = new Cache<string, ISpriteFrame[]>(filename => FrameLoader.GetFrames(fileSystem, filename, loaders, out _));
}
public ISpriteFrame[] this[string filename] => frames[filename];
public ISpriteFrame[] this[string filename] { get { return frames[filename]; } }
}
public static class FrameLoader
@@ -82,7 +151,7 @@ namespace OpenRA.Graphics
{
using (var stream = fileSystem.Open(filename))
{
var spriteFrames = GetFrames(stream, loaders, filename, out metadata);
var spriteFrames = GetFrames(stream, loaders, out metadata);
if (spriteFrames == null)
throw new InvalidDataException(filename + " is not a valid sprite file!");
@@ -90,12 +159,12 @@ namespace OpenRA.Graphics
}
}
public static ISpriteFrame[] GetFrames(Stream stream, ISpriteLoader[] loaders, string filename, out TypeDictionary metadata)
public static ISpriteFrame[] GetFrames(Stream stream, ISpriteLoader[] loaders, out TypeDictionary metadata)
{
metadata = null;
foreach (var loader in loaders)
if (loader.TryParseSprite(stream, filename, out var frames, out metadata))
if (loader.TryParseSprite(stream, out var frames, out metadata))
return frames;
return null;

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,107 +9,79 @@
*/
#endregion
using System;
using System.Collections.Generic;
using OpenRA.Primitives;
namespace OpenRA.Graphics
{
public class SpriteRenderable : IPalettedRenderable, IModifyableRenderable, IFinalizedRenderable
public struct SpriteRenderable : IRenderable, ITintableRenderable, IFinalizedRenderable
{
public static readonly IEnumerable<IRenderable> None = Array.Empty<IRenderable>();
public static readonly IEnumerable<IRenderable> None = new IRenderable[0];
readonly Sprite sprite;
readonly WPos pos;
readonly WVec offset;
readonly int zOffset;
readonly PaletteReference palette;
readonly float scale;
readonly WAngle rotation = WAngle.Zero;
readonly float3 tint;
readonly bool isDecoration;
readonly bool ignoreWorldTint;
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, float alpha,
float3 tint, TintModifiers tintModifiers, bool isDecoration, WAngle rotation)
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, bool isDecoration)
: this(sprite, pos, offset, zOffset, palette, scale, float3.Ones, isDecoration, false) { }
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, bool isDecoration, bool ignoreWorldTint)
: this(sprite, pos, offset, zOffset, palette, scale, float3.Ones, isDecoration, ignoreWorldTint) { }
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, float3 tint, bool isDecoration, bool ignoreWorldTint)
{
this.sprite = sprite;
this.pos = pos;
Offset = offset;
ZOffset = zOffset;
Palette = palette;
this.offset = offset;
this.zOffset = zOffset;
this.palette = palette;
this.scale = scale;
this.rotation = rotation;
Tint = tint;
IsDecoration = isDecoration;
TintModifiers = tintModifiers;
Alpha = alpha;
// PERF: Remove useless palette assignments for RGBA sprites
// HACK: This is working around the fact that palettes are defined on traits rather than sequences
// and can be removed once this has been fixed
if (sprite.Channel == TextureChannel.RGBA && !(palette?.HasColorShift ?? false))
Palette = null;
this.tint = tint;
this.isDecoration = isDecoration;
this.ignoreWorldTint = ignoreWorldTint;
}
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, float alpha,
float3 tint, TintModifiers tintModifiers, bool isDecoration)
: this(sprite, pos, offset, zOffset, palette, scale, alpha, tint, tintModifiers, isDecoration, WAngle.Zero) { }
public WPos Pos { get { return pos + offset; } }
public WVec Offset { get { return offset; } }
public PaletteReference Palette { get { return palette; } }
public int ZOffset { get { return zOffset; } }
public bool IsDecoration { get { return isDecoration; } }
public WPos Pos => pos + Offset;
public WVec Offset { get; }
public PaletteReference Palette { get; }
public int ZOffset { get; }
public bool IsDecoration { get; }
public IRenderable WithPalette(PaletteReference newPalette) { return new SpriteRenderable(sprite, pos, offset, zOffset, newPalette, scale, tint, isDecoration, ignoreWorldTint); }
public IRenderable WithZOffset(int newOffset) { return new SpriteRenderable(sprite, pos, offset, newOffset, palette, scale, tint, isDecoration, ignoreWorldTint); }
public IRenderable OffsetBy(WVec vec) { return new SpriteRenderable(sprite, pos + vec, offset, zOffset, palette, scale, tint, isDecoration, ignoreWorldTint); }
public IRenderable AsDecoration() { return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, tint, true, ignoreWorldTint); }
public float Alpha { get; }
public float3 Tint { get; }
public TintModifiers TintModifiers { get; }
public IPalettedRenderable WithPalette(PaletteReference newPalette)
{
return new SpriteRenderable(sprite, pos, Offset, ZOffset, newPalette, scale, Alpha, Tint, TintModifiers, IsDecoration, rotation);
}
public IRenderable WithZOffset(int newOffset)
{
return new SpriteRenderable(sprite, pos, Offset, newOffset, Palette, scale, Alpha, Tint, TintModifiers, IsDecoration, rotation);
}
public IRenderable OffsetBy(in WVec vec)
{
return new SpriteRenderable(sprite, pos + vec, Offset, ZOffset, Palette, scale, Alpha, Tint, TintModifiers, IsDecoration, rotation);
}
public IRenderable AsDecoration()
{
return new SpriteRenderable(sprite, pos, Offset, ZOffset, Palette, scale, Alpha, Tint, TintModifiers, true, rotation);
}
public IModifyableRenderable WithAlpha(float newAlpha)
{
return new SpriteRenderable(sprite, pos, Offset, ZOffset, Palette, scale, newAlpha, Tint, TintModifiers, IsDecoration, rotation);
}
public IModifyableRenderable WithTint(in float3 newTint, TintModifiers newTintModifiers)
{
return new SpriteRenderable(sprite, pos, Offset, ZOffset, Palette, scale, Alpha, newTint, newTintModifiers, IsDecoration, rotation);
}
public IRenderable WithTint(float3 newTint) { return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, newTint, isDecoration, ignoreWorldTint); }
float3 ScreenPosition(WorldRenderer wr)
{
var s = 0.5f * scale * sprite.Size;
return wr.Screen3DPxPosition(pos) + wr.ScreenPxOffset(Offset) - new float3((int)s.X, (int)s.Y, s.Z);
var xy = wr.ScreenPxPosition(pos) + wr.ScreenPxOffset(offset) - (0.5f * scale * sprite.Size.XY).ToInt2();
// HACK: The z offset needs to be applied somewhere, but this probably is the wrong place.
return new float3(xy, sprite.Offset.Z + wr.ScreenZPosition(pos, 0) - 0.5f * scale * sprite.Size.Z);
}
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
public void Render(WorldRenderer wr)
{
var wsr = Game.Renderer.WorldSpriteRenderer;
var t = Alpha * Tint;
if (wr.TerrainLighting != null && (TintModifiers & TintModifiers.IgnoreWorldTint) == 0)
t *= wr.TerrainLighting.TintAt(pos);
if (ignoreWorldTint)
wsr.DrawSprite(sprite, ScreenPosition(wr), palette, scale * sprite.Size);
else
{
var t = tint;
if (wr.TerrainLighting != null)
t *= wr.TerrainLighting.TintAt(pos);
// Shader interprets negative alpha as a flag to use the tint colour directly instead of multiplying the sprite colour
var a = Alpha;
if ((TintModifiers & TintModifiers.ReplaceColor) != 0)
a *= -1;
wsr.DrawSprite(sprite, Palette, ScreenPosition(wr), scale, t, a, rotation.RendererRadians());
wsr.DrawSpriteWithTint(sprite, ScreenPosition(wr), palette, scale * sprite.Size, t);
}
}
public void RenderDebugGeometry(WorldRenderer wr)
@@ -117,16 +89,13 @@ namespace OpenRA.Graphics
var pos = ScreenPosition(wr) + sprite.Offset;
var tl = wr.Viewport.WorldToViewPx(pos);
var br = wr.Viewport.WorldToViewPx(pos + sprite.Size);
if (rotation == WAngle.Zero)
Game.Renderer.RgbaColorRenderer.DrawRect(tl, br, 1, Color.Red);
else
Game.Renderer.RgbaColorRenderer.DrawPolygon(Util.RotateQuad(tl, br - tl, rotation.RendererRadians()), 1, Color.Red);
Game.Renderer.RgbaColorRenderer.DrawRect(tl, br, 1, Color.Red);
}
public Rectangle ScreenBounds(WorldRenderer wr)
{
var screenOffset = ScreenPosition(wr) + sprite.Offset;
return Util.BoundingRectangle(screenOffset, sprite.Size, rotation.RendererRadians());
return new Rectangle((int)screenOffset.X, (int)screenOffset.Y, (int)sprite.Size.X, (int)sprite.Size.Y);
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,21 +10,17 @@
#endregion
using System;
using System.Collections.Generic;
using OpenRA.Primitives;
namespace OpenRA.Graphics
{
public class SpriteRenderer : Renderer.IBatchRenderer
{
public const int SheetCount = 8;
static readonly string[] SheetIndexToTextureName = Exts.MakeArray(SheetCount, i => $"Texture{i}");
readonly Renderer renderer;
readonly IShader shader;
Vertex[] vertices;
readonly Sheet[] sheets = new Sheet[SheetCount];
readonly Vertex[] vertices;
readonly Sheet[] sheets = new Sheet[7];
BlendMode currentBlend = BlendMode.Alpha;
int nv = 0;
@@ -34,7 +30,7 @@ namespace OpenRA.Graphics
{
this.renderer = renderer;
this.shader = shader;
vertices = renderer.Context.CreateVertices(renderer.TempBufferSize);
vertices = new Vertex[renderer.TempBufferSize];
}
public void Flush()
@@ -43,15 +39,13 @@ namespace OpenRA.Graphics
{
for (var i = 0; i < ns; i++)
{
shader.SetTexture(SheetIndexToTextureName[i], sheets[i].GetTexture());
shader.SetTexture("Texture{0}".F(i), sheets[i].GetTexture());
sheets[i] = null;
}
renderer.Context.SetBlendMode(currentBlend);
shader.PrepareRender();
// PERF: The renderer may choose to replace vertices with a different temporary buffer.
renderer.DrawBatch(ref vertices, nv, PrimitiveType.TriangleList);
renderer.DrawBatch(vertices, nv, PrimitiveType.TriangleList);
renderer.Context.SetBlendMode(BlendMode.None);
nv = 0;
@@ -83,170 +77,119 @@ namespace OpenRA.Graphics
for (; secondarySheetIndex < ns; secondarySheetIndex++)
if (sheets[secondarySheetIndex] == secondarySheet)
break;
// If neither sheet has been mapped both index values will be set to ns.
// This is fine if they both reference the same texture, but if they don't
// we must increment the secondary sheet index to the next free sampler.
if (secondarySheetIndex == sheetIndex && secondarySheet != sheet)
secondarySheetIndex++;
}
// Make sure that we have enough free samplers to map both if needed, otherwise flush
if (Math.Max(sheetIndex, secondarySheetIndex) >= sheets.Length)
var needSamplers = (sheetIndex == ns ? 1 : 0) + (secondarySheetIndex == ns ? 1 : 0);
if (ns + needSamplers >= sheets.Length)
{
Flush();
sheetIndex = 0;
secondarySheetIndex = ss != null && ss.SecondarySheet != sheet ? 1 : 0;
if (ss != null)
secondarySheetIndex = 1;
}
if (sheetIndex >= ns)
{
sheets[sheetIndex] = sheet;
ns++;
ns += 1;
}
if (secondarySheetIndex >= ns && ss != null)
{
sheets[secondarySheetIndex] = ss.SecondarySheet;
ns++;
ns += 1;
}
return new int2(sheetIndex, secondarySheetIndex);
}
static float ResolveTextureIndex(Sprite s, PaletteReference pal)
{
if (pal == null)
return 0;
// PERF: Remove useless palette assignments for RGBA sprites
// HACK: This is working around the limitation that palettes are defined on traits rather than on sequences,
// and can be removed once this has been fixed
if (s.Channel == TextureChannel.RGBA && !pal.HasColorShift)
return 0;
return pal.TextureIndex;
}
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, in float3 scale, float rotation = 0f)
internal void DrawSprite(Sprite s, float3 location, float paletteTextureIndex, float3 size)
{
var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, float3.Ones,
1f, rotation);
Util.FastCreateQuad(vertices, location + s.FractionalOffset * size, s, samplers, paletteTextureIndex, nv, size, float3.Ones);
nv += 6;
}
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, float scale, float rotation = 0f)
public void DrawSprite(Sprite s, float3 location, PaletteReference pal)
{
DrawSprite(s, location, pal.TextureIndex, s.Size);
}
public void DrawSprite(Sprite s, float3 location, PaletteReference pal, float3 size)
{
DrawSprite(s, location, pal.TextureIndex, size);
}
public void DrawSprite(Sprite s, float3 a, float3 b, float3 c, float3 d)
{
var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, float3.Ones,
1f, rotation);
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, 0, float3.Ones, nv);
nv += 6;
}
public void DrawSprite(Sprite s, PaletteReference pal, in float3 location, float scale = 1f, float rotation = 0f)
{
DrawSprite(s, ResolveTextureIndex(s, pal), location, scale, rotation);
}
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, float scale, in float3 tint, float alpha,
float rotation = 0f)
internal void DrawSpriteWithTint(Sprite s, float3 location, float paletteTextureIndex, float3 size, float3 tint)
{
var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, tint, alpha,
rotation);
Util.FastCreateQuad(vertices, location + s.FractionalOffset * size, s, samplers, paletteTextureIndex, nv, size, tint);
nv += 6;
}
public void DrawSprite(Sprite s, PaletteReference pal, in float3 location, float scale, in float3 tint, float alpha,
float rotation = 0f)
public void DrawSpriteWithTint(Sprite s, float3 location, PaletteReference pal, float3 size, float3 tint)
{
DrawSprite(s, ResolveTextureIndex(s, pal), location, scale, tint, alpha, rotation);
DrawSpriteWithTint(s, location, pal.TextureIndex, size, tint);
}
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint, float alpha)
public void DrawSpriteWithTint(Sprite s, float3 a, float3 b, float3 c, float3 d, float3 tint)
{
var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, paletteTextureIndex, tint, alpha, nv);
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, 0, tint, nv);
nv += 6;
}
public void DrawVertexBuffer(IVertexBuffer<Vertex> buffer, int start, int length, PrimitiveType type, IEnumerable<Sheet> sheets, BlendMode blendMode)
public void DrawVertexBuffer(IVertexBuffer<Vertex> buffer, int start, int length, PrimitiveType type, Sheet sheet, BlendMode blendMode)
{
var i = 0;
foreach (var s in sheets)
{
if (i >= SheetCount)
ThrowSheetOverflow(nameof(sheets));
if (s != null)
shader.SetTexture(SheetIndexToTextureName[i++], s.GetTexture());
}
shader.SetTexture("Texture0", sheet.GetTexture());
renderer.Context.SetBlendMode(blendMode);
shader.PrepareRender();
renderer.DrawBatch(buffer, start, length, type);
renderer.Context.SetBlendMode(BlendMode.None);
}
// PERF: methods that throw won't be inlined by the JIT, so extract a static helper for use on hot paths
static void ThrowSheetOverflow(string paramName)
{
throw new ArgumentException($"SpriteRenderer only supports {SheetCount} simultaneous textures", paramName);
}
// For RGBAColorRenderer
internal void DrawRGBAVertices(Vertex[] v, BlendMode blendMode)
internal void DrawRGBAVertices(Vertex[] v)
{
renderer.CurrentBatchRenderer = this;
if (currentBlend != blendMode || nv + v.Length > renderer.TempBufferSize)
if (currentBlend != BlendMode.Alpha || nv + v.Length > renderer.TempBufferSize)
Flush();
currentBlend = blendMode;
currentBlend = BlendMode.Alpha;
Array.Copy(v, 0, vertices, nv, v.Length);
nv += v.Length;
}
public void SetPalette(ITexture palette, ITexture colorShifts)
public void SetPalette(ITexture palette)
{
shader.SetTexture("Palette", palette);
shader.SetTexture("ColorShifts", colorShifts);
}
public void SetViewportParams(Size sheetSize, int downscale, float depthMargin, int2 scroll)
public void SetViewportParams(Size screen, float depthScale, float depthOffset, int2 scroll)
{
// Calculate the scale (r1) and offset (r2) that convert from OpenRA viewport pixels
// to OpenGL normalized device coordinates (NDC). OpenGL expects coordinates to vary from [-1, 1],
// so we rescale viewport pixels to the range [0, 2] using r1 then subtract 1 using r2.
var width = 2f / (downscale * sheetSize.Width);
var height = 2f / (downscale * sheetSize.Height);
shader.SetVec("Scroll", scroll.X, scroll.Y, scroll.Y);
shader.SetVec("r1",
2f / screen.Width,
2f / screen.Height,
-depthScale / screen.Height);
shader.SetVec("r2", -1, -1, 1 - depthOffset);
// Depth is more complicated:
// * The OpenGL z axis is inverted (negative is closer) relative to OpenRA (positive is closer).
// * We want to avoid clipping pixels that are behind the nominal z == y plane at the
// top of the map, or above the nominal z == y plane at the bottom of the map.
// We therefore expand the depth range by an extra margin that is calculated based on
// the maximum expected world height (see Renderer.InitializeDepthBuffer).
// * Sprites can specify an additional per-pixel depth offset map, which is applied in the
// fragment shader. The fragment shader operates in OpenGL window coordinates, not NDC,
// with a depth range [0, 1] corresponding to the NDC [-1, 1]. We must therefore multiply the
// sprite channel value [0, 1] by 255 to find the pixel depth offset, then by our depth scale
// to find the equivalent NDC offset, then divide by 2 to find the window coordinate offset.
// * If depthMargin == 0 (which indicates per-pixel depth testing is disabled) sprites that
// extend beyond the top of bottom edges of the screen may be pushed outside [-1, 1] and
// culled by the GPU. We avoid this by forcing everything into the z = 0 plane.
var depth = depthMargin != 0f ? 2f / (downscale * (sheetSize.Height + depthMargin)) : 0;
shader.SetVec("DepthTextureScale", 128 * depth);
shader.SetVec("Scroll", scroll.X, scroll.Y, depthMargin != 0f ? scroll.Y : 0);
shader.SetVec("r1", width, height, -depth);
shader.SetVec("r2", -1, -1, depthMargin != 0f ? 1 : 0);
// Texture index is sampled as a float, so convert to pixels then scale
shader.SetVec("DepthTextureScale", 128 * depthScale / screen.Height);
}
public void SetDepthPreview(bool enabled, float contrast, float offset)
public void SetDepthPreviewEnabled(bool enabled)
{
shader.SetBool("EnableDepthPreview", enabled);
shader.SetVec("DepthPreviewParams", contrast, offset);
}
public void SetAntialiasingPixelsPerTexel(float pxPerTx)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,14 +15,14 @@ using OpenRA.Primitives;
namespace OpenRA.Graphics
{
public class TargetLineRenderable : IRenderable, IFinalizedRenderable
public struct TargetLineRenderable : IRenderable, IFinalizedRenderable
{
readonly IEnumerable<WPos> waypoints;
readonly Color color;
readonly int width;
readonly int markerSize;
public TargetLineRenderable(IEnumerable<WPos> waypoints, Color color, int width, int markerSize)
public TargetLineRenderable(IEnumerable<WPos> waypoints, Color color, int width = 1, int markerSize = 1)
{
this.waypoints = waypoints;
this.color = color;
@@ -30,19 +30,14 @@ namespace OpenRA.Graphics
this.markerSize = markerSize;
}
public WPos Pos => waypoints.First();
public int ZOffset => 0;
public bool IsDecoration => true;
public IRenderable WithZOffset(int newOffset) { return this; }
public IRenderable OffsetBy(in WVec vec)
{
// Lambdas can't use 'in' variables, so capture a copy for later
var offset = vec;
return new TargetLineRenderable(waypoints.Select(w => w + offset), color, width, markerSize);
}
public WPos Pos { get { return waypoints.First(); } }
public PaletteReference Palette { get { return null; } }
public int ZOffset { get { return 0; } }
public bool IsDecoration { get { return true; } }
public IRenderable WithPalette(PaletteReference newPalette) { return new TargetLineRenderable(waypoints, color); }
public IRenderable WithZOffset(int newOffset) { return new TargetLineRenderable(waypoints, color); }
public IRenderable OffsetBy(WVec vec) { return new TargetLineRenderable(waypoints.Select(w => w + vec), color); }
public IRenderable AsDecoration() { return this; }
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
@@ -56,14 +51,14 @@ namespace OpenRA.Graphics
foreach (var b in waypoints.Skip(1).Select(pos => wr.Viewport.WorldToViewPx(wr.Screen3DPosition(pos))))
{
Game.Renderer.RgbaColorRenderer.DrawLine(a, b, width, color);
DrawTargetMarker(color, b, markerSize);
DrawTargetMarker(wr, color, b, markerSize);
a = b;
}
DrawTargetMarker(color, first);
DrawTargetMarker(wr, color, first);
}
public static void DrawTargetMarker(Color color, int2 screenPos, int size = 1)
public static void DrawTargetMarker(WorldRenderer wr, Color color, int2 screenPos, int size = 1)
{
var offset = new int2(size, size);
var tl = screenPos - offset;

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,6 +12,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using OpenRA.Primitives;
namespace OpenRA.Graphics
{
@@ -19,37 +20,37 @@ namespace OpenRA.Graphics
{
static readonly int[] CornerVertexMap = { 0, 1, 2, 2, 3, 0 };
public readonly Sheet Sheet;
public readonly BlendMode BlendMode;
readonly Sheet[] sheets;
readonly Sprite emptySprite;
readonly IVertexBuffer<Vertex> vertexBuffer;
readonly Vertex[] vertices;
readonly bool[] ignoreTint;
readonly HashSet<int> dirtyRows = new();
readonly HashSet<int> dirtyRows = new HashSet<int>();
readonly int rowStride;
readonly bool restrictToBounds;
readonly WorldRenderer worldRenderer;
readonly Map map;
readonly PaletteReference[] palettes;
readonly PaletteReference palette;
public TerrainSpriteLayer(World world, WorldRenderer wr, Sprite emptySprite, BlendMode blendMode, bool restrictToBounds)
public TerrainSpriteLayer(World world, WorldRenderer wr, Sheet sheet, BlendMode blendMode, PaletteReference palette, bool restrictToBounds)
{
worldRenderer = wr;
this.restrictToBounds = restrictToBounds;
this.emptySprite = emptySprite;
sheets = new Sheet[SpriteRenderer.SheetCount];
Sheet = sheet;
BlendMode = blendMode;
this.palette = palette;
map = world.Map;
rowStride = 6 * map.MapSize.X;
vertices = new Vertex[rowStride * map.MapSize.Y];
palettes = new PaletteReference[map.MapSize.X * map.MapSize.Y];
vertexBuffer = Game.Renderer.Context.CreateVertexBuffer(vertices.Length);
emptySprite = new Sprite(sheet, Rectangle.Empty, TextureChannel.Alpha);
wr.PaletteInvalidated += UpdatePaletteIndices;
@@ -62,11 +63,12 @@ namespace OpenRA.Graphics
void UpdatePaletteIndices()
{
// Everything in the layer uses the same palette,
// so we can fix the indices in one pass
for (var i = 0; i < vertices.Length; i++)
{
var v = vertices[i];
var p = palettes[i / 6]?.TextureIndex ?? 0;
vertices[i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, p, v.C, v.R, v.G, v.B, v.A);
vertices[i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, palette.TextureIndex, v.C, v.R, v.G, v.B);
}
for (var row = 0; row < map.MapSize.Y; row++)
@@ -75,24 +77,24 @@ namespace OpenRA.Graphics
public void Clear(CPos cell)
{
Update(cell, null, null, 1f, 1f, true);
Update(cell, null, true);
}
public void Update(CPos cell, ISpriteSequence sequence, PaletteReference palette, int frame)
public void Update(CPos cell, ISpriteSequence sequence, int frame)
{
Update(cell, sequence.GetSprite(frame), palette, sequence.Scale, sequence.GetAlpha(frame), sequence.IgnoreWorldTint);
Update(cell, sequence.GetSprite(frame), sequence.IgnoreWorldTint);
}
public void Update(CPos cell, Sprite sprite, PaletteReference palette, float scale = 1f, float alpha = 1f, bool ignoreTint = false)
public void Update(CPos cell, Sprite sprite, bool ignoreTint)
{
var xyz = float3.Zero;
if (sprite != null)
{
var cellOrigin = map.CenterOfCell(cell) - new WVec(0, 0, map.Grid.Ramps[map.Ramp[cell]].CenterHeightOffset);
xyz = worldRenderer.Screen3DPosition(cellOrigin) + scale * (sprite.Offset - 0.5f * sprite.Size);
xyz = worldRenderer.Screen3DPosition(cellOrigin) + sprite.Offset - 0.5f * sprite.Size;
}
Update(cell.ToMPos(map.Grid.Type), sprite, palette, xyz, scale, alpha, ignoreTint);
Update(cell.ToMPos(map.Grid.Type), sprite, xyz, ignoreTint);
}
void UpdateTint(MPos uv)
@@ -100,10 +102,11 @@ namespace OpenRA.Graphics
var offset = rowStride * uv.V + 6 * uv.U;
if (ignoreTint[offset])
{
var noTint = float3.Ones;
for (var i = 0; i < 6; i++)
{
var v = vertices[offset + i];
vertices[offset + i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, v.P, v.C, v.A * float3.Ones, v.A);
vertices[offset + i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, palette.TextureIndex, v.C, noTint);
}
return;
@@ -114,7 +117,7 @@ namespace OpenRA.Graphics
// transparent for isometric tiles
var tl = worldRenderer.TerrainLighting;
var pos = map.CenterOfCell(uv.ToCPos(map));
var step = map.Grid.TileScale / 2;
var step = map.Grid.Type == MapGridType.RectangularIsometric ? 724 : 512;
var weights = new[]
{
tl.TintAt(pos + new WVec(-step, -step, 0)),
@@ -128,61 +131,31 @@ namespace OpenRA.Graphics
for (var i = 0; i < 6; i++)
{
var v = vertices[offset + i];
vertices[offset + i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, v.P, v.C, v.A * weights[CornerVertexMap[i]], v.A);
vertices[offset + i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, palette.TextureIndex, v.C, weights[CornerVertexMap[i]]);
}
dirtyRows.Add(uv.V);
}
int GetOrAddSheetIndex(Sheet sheet)
public void Update(MPos uv, Sprite sprite, float3 pos, bool ignoreTint)
{
if (sheet == null)
return 0;
for (var i = 0; i < sheets.Length; i++)
{
if (sheets[i] == sheet)
return i;
if (sheets[i] == null)
{
sheets[i] = sheet;
return i;
}
}
throw new InvalidDataException("Sheet overflow");
}
public void Update(MPos uv, Sprite sprite, PaletteReference palette, in float3 pos, float scale, float alpha, bool ignoreTint)
{
int2 samplers;
if (sprite != null)
{
if (sprite.Sheet != Sheet)
throw new InvalidDataException("Attempted to add sprite from a different sheet");
if (sprite.BlendMode != BlendMode)
throw new InvalidDataException("Attempted to add sprite with a different blend mode");
samplers = new int2(GetOrAddSheetIndex(sprite.Sheet), GetOrAddSheetIndex((sprite as SpriteWithSecondaryData)?.SecondarySheet));
// PERF: Remove useless palette assignments for RGBA sprites
// HACK: This is working around the limitation that palettes are defined on traits rather than on sequences,
// and can be removed once this has been fixed
if (sprite.Channel == TextureChannel.RGBA && !(palette?.HasColorShift ?? false))
palette = null;
}
else
{
sprite = emptySprite;
samplers = int2.Zero;
}
// The vertex buffer does not have geometry for cells outside the map
if (!map.Tiles.Contains(uv))
return;
var offset = rowStride * uv.V + 6 * uv.U;
Util.FastCreateQuad(vertices, pos, sprite, samplers, palette?.TextureIndex ?? 0, offset, scale * sprite.Size, alpha * float3.Ones, alpha);
palettes[uv.V * map.MapSize.X + uv.U] = palette;
Util.FastCreateQuad(vertices, pos, sprite, int2.Zero, palette.TextureIndex, offset, sprite.Size, float3.Ones);
if (worldRenderer.TerrainLighting != null)
{
@@ -210,12 +183,20 @@ namespace OpenRA.Graphics
continue;
var rowOffset = rowStride * row;
vertexBuffer.SetData(vertices, rowOffset, rowOffset, rowStride);
unsafe
{
// The compiler / language spec won't let us calculate a pointer to
// an offset inside a generic array T[], and so we are forced to
// calculate the start-of-row pointer here to pass in to SetData.
fixed (Vertex* vPtr = &vertices[0])
vertexBuffer.SetData((IntPtr)(vPtr + rowOffset), rowOffset, rowStride);
}
}
Game.Renderer.WorldSpriteRenderer.DrawVertexBuffer(
vertexBuffer, rowStride * firstRow, rowStride * (lastRow - firstRow),
PrimitiveType.TriangleList, sheets, BlendMode);
PrimitiveType.TriangleList, Sheet, BlendMode);
Game.Renderer.Flush();
}

View File

@@ -0,0 +1,167 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 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 OpenRA.Primitives;
using OpenRA.Support;
namespace OpenRA.Graphics
{
class TheaterTemplate
{
public readonly Sprite[] Sprites;
public readonly int Stride;
public readonly int Variants;
public TheaterTemplate(Sprite[] sprites, int stride, int variants)
{
Sprites = sprites;
Stride = stride;
Variants = variants;
}
}
public sealed class Theater : IDisposable
{
readonly Dictionary<ushort, TheaterTemplate> templates = new Dictionary<ushort, TheaterTemplate>();
SheetBuilder sheetBuilder;
readonly Sprite missingTile;
readonly MersenneTwister random;
TileSet tileset;
public Theater(TileSet tileset)
{
this.tileset = tileset;
var allocated = false;
Func<Sheet> allocate = () =>
{
if (allocated)
throw new SheetOverflowException("Terrain sheet overflow. Try increasing the tileset SheetSize parameter.");
allocated = true;
return new Sheet(SheetType.Indexed, new Size(tileset.SheetSize, tileset.SheetSize));
};
random = new MersenneTwister();
var frameCache = new FrameCache(Game.ModData.DefaultFileSystem, Game.ModData.SpriteLoaders);
foreach (var t in tileset.Templates)
{
var variants = new List<Sprite[]>();
foreach (var i in t.Value.Images)
{
var allFrames = frameCache[i];
var frameCount = tileset.EnableDepth ? allFrames.Length / 2 : allFrames.Length;
var indices = t.Value.Frames != null ? t.Value.Frames : Enumerable.Range(0, frameCount);
variants.Add(indices.Select(j =>
{
var f = allFrames[j];
var tile = t.Value.Contains(j) ? t.Value[j] : null;
// The internal z axis is inverted from expectation (negative is closer)
var zOffset = tile != null ? -tile.ZOffset : 0;
var zRamp = tile != null ? tile.ZRamp : 1f;
var offset = new float3(f.Offset, zOffset);
var type = SheetBuilder.FrameTypeToSheetType(f.Type);
// Defer SheetBuilder creation until we know what type of frames we are loading!
// TODO: Support mixed indexed and BGRA frames
if (sheetBuilder == null)
sheetBuilder = new SheetBuilder(SheetBuilder.FrameTypeToSheetType(f.Type), allocate);
else if (type != sheetBuilder.Type)
throw new InvalidDataException("Sprite type mismatch. Terrain sprites must all be either Indexed or RGBA.");
var s = sheetBuilder.Allocate(f.Size, zRamp, offset);
Util.FastCopyIntoChannel(s, f.Data);
if (tileset.EnableDepth)
{
var ss = sheetBuilder.Allocate(f.Size, zRamp, offset);
Util.FastCopyIntoChannel(ss, allFrames[j + frameCount].Data);
// s and ss are guaranteed to use the same sheet
// because of the custom terrain sheet allocation
s = new SpriteWithSecondaryData(s, s.Sheet, ss.Bounds, ss.Channel);
}
return s;
}).ToArray());
}
var allSprites = variants.SelectMany(s => s);
// Ignore the offsets baked into R8 sprites
if (tileset.IgnoreTileSpriteOffsets)
allSprites = allSprites.Select(s => new Sprite(s.Sheet, s.Bounds, s.ZRamp, new float3(float2.Zero, s.Offset.Z), s.Channel, s.BlendMode));
templates.Add(t.Value.Id, new TheaterTemplate(allSprites.ToArray(), variants.First().Count(), t.Value.Images.Length));
}
// 1x1px transparent tile
missingTile = sheetBuilder.Add(new byte[sheetBuilder.Type == SheetType.BGRA ? 4 : 1], new Size(1, 1));
Sheet.ReleaseBuffer();
}
public Sprite TileSprite(TerrainTile r, int? variant = null)
{
if (!templates.TryGetValue(r.Type, out var template))
return missingTile;
if (r.Index >= template.Stride)
return missingTile;
var start = template.Variants > 1 ? variant.HasValue ? variant.Value : random.Next(template.Variants) : 0;
return template.Sprites[start * template.Stride + r.Index];
}
public Rectangle TemplateBounds(TerrainTemplateInfo template, Size tileSize, MapGridType mapGrid)
{
Rectangle? templateRect = null;
var i = 0;
for (var y = 0; y < template.Size.Y; y++)
{
for (var x = 0; x < template.Size.X; x++)
{
var tile = new TerrainTile(template.Id, (byte)(i++));
var tileInfo = tileset.GetTileInfo(tile);
// Empty tile
if (tileInfo == null)
continue;
var sprite = TileSprite(tile);
var u = mapGrid == MapGridType.Rectangular ? x : (x - y) / 2f;
var v = mapGrid == MapGridType.Rectangular ? y : (x + y) / 2f;
var tl = new float2(u * tileSize.Width, (v - 0.5f * tileInfo.Height) * tileSize.Height) - 0.5f * sprite.Size;
var rect = new Rectangle((int)(tl.X + sprite.Offset.X), (int)(tl.Y + sprite.Offset.Y), (int)sprite.Size.X, (int)sprite.Size.Y);
templateRect = templateRect.HasValue ? Rectangle.Union(templateRect.Value, rect) : rect;
}
}
return templateRect.HasValue ? templateRect.Value : Rectangle.Empty;
}
public Sheet Sheet { get { return sheetBuilder.Current; } }
public void Dispose()
{
sheetBuilder.Dispose();
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,64 +13,54 @@ using OpenRA.Primitives;
namespace OpenRA.Graphics
{
public class UISpriteRenderable : IRenderable, IPalettedRenderable, IFinalizedRenderable
public struct UISpriteRenderable : IRenderable, IFinalizedRenderable
{
readonly Sprite sprite;
readonly WPos effectiveWorldPos;
readonly int2 screenPos;
readonly int zOffset;
readonly PaletteReference palette;
readonly float scale;
readonly float alpha;
readonly float rotation = 0f;
public UISpriteRenderable(Sprite sprite, WPos effectiveWorldPos, int2 screenPos, int zOffset, PaletteReference palette, float scale = 1f, float alpha = 1f, float rotation = 0f)
public UISpriteRenderable(Sprite sprite, WPos effectiveWorldPos, int2 screenPos, int zOffset, PaletteReference palette, float scale)
{
this.sprite = sprite;
Pos = effectiveWorldPos;
this.effectiveWorldPos = effectiveWorldPos;
this.screenPos = screenPos;
ZOffset = zOffset;
Palette = palette;
this.zOffset = zOffset;
this.palette = palette;
this.scale = scale;
this.alpha = alpha;
this.rotation = rotation;
// PERF: Remove useless palette assignments for RGBA sprites
// HACK: This is working around the fact that palettes are defined on traits rather than sequences
// and can be removed once this has been fixed
if (sprite.Channel == TextureChannel.RGBA && !(palette?.HasColorShift ?? false))
Palette = null;
}
// Does not exist in the world, so a world positions don't make sense
public WPos Pos { get; }
public WVec Offset => WVec.Zero;
public bool IsDecoration => true;
public WPos Pos { get { return effectiveWorldPos; } }
public WVec Offset { get { return WVec.Zero; } }
public bool IsDecoration { get { return true; } }
public PaletteReference Palette { get; }
public int ZOffset { get; }
public PaletteReference Palette { get { return palette; } }
public int ZOffset { get { return zOffset; } }
public IPalettedRenderable WithPalette(PaletteReference newPalette) { return new UISpriteRenderable(sprite, Pos, screenPos, ZOffset, newPalette, scale, alpha, rotation); }
public IRenderable WithPalette(PaletteReference newPalette) { return new UISpriteRenderable(sprite, effectiveWorldPos, screenPos, zOffset, newPalette, scale); }
public IRenderable WithZOffset(int newOffset) { return this; }
public IRenderable OffsetBy(in WVec vec) { return this; }
public IRenderable OffsetBy(WVec vec) { return this; }
public IRenderable AsDecoration() { return this; }
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
public void Render(WorldRenderer wr)
{
Game.Renderer.SpriteRenderer.DrawSprite(sprite, Palette, screenPos, scale, float3.Ones, alpha, rotation);
Game.Renderer.SpriteRenderer.DrawSprite(sprite, screenPos, palette, scale * sprite.Size);
}
public void RenderDebugGeometry(WorldRenderer wr)
{
var offset = screenPos + sprite.Offset.XY;
if (rotation == 0f)
Game.Renderer.RgbaColorRenderer.DrawRect(offset, offset + sprite.Size.XY, 1, Color.Red);
else
Game.Renderer.RgbaColorRenderer.DrawPolygon(Util.RotateQuad(offset, sprite.Size, rotation), 1, Color.Red);
Game.Renderer.RgbaColorRenderer.DrawRect(offset, offset + sprite.Size.XY, 1, Color.Red);
}
public Rectangle ScreenBounds(WorldRenderer wr)
{
var offset = screenPos + sprite.Offset;
return Util.BoundingRectangle(offset, sprite.Size, rotation);
return new Rectangle((int)offset.X, (int)offset.Y, (int)sprite.Size.X, (int)sprite.Size.Y);
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,60 +20,29 @@ namespace OpenRA.Graphics
// yes, our channel order is nuts.
static readonly int[] ChannelMasks = { 2, 1, 0, 3 };
public static void FastCreateQuad(Vertex[] vertices, in float3 o, Sprite r, int2 samplers, float paletteTextureIndex, int nv,
in float3 size, in float3 tint, float alpha, float rotation = 0f)
public static void FastCreateQuad(Vertex[] vertices, float3 o, Sprite r, int2 samplers, float paletteTextureIndex, int nv, float3 size, float3 tint)
{
float3 a, b, c, d;
// Rotate sprite if rotation angle is not equal to 0
if (rotation != 0f)
{
var center = o + 0.5f * size;
var angleSin = (float)Math.Sin(-rotation);
var angleCos = (float)Math.Cos(-rotation);
// Rotated offset for +/- x with +/- y
var ra = 0.5f * new float3(
size.X * angleCos - size.Y * angleSin,
size.X * angleSin + size.Y * angleCos,
(size.X * angleSin + size.Y * angleCos) * size.Z / size.Y);
// Rotated offset for +/- x with -/+ y
var rb = 0.5f * new float3(
size.X * angleCos + size.Y * angleSin,
size.X * angleSin - size.Y * angleCos,
(size.X * angleSin - size.Y * angleCos) * size.Z / size.Y);
a = center - ra;
b = center + rb;
c = center + ra;
d = center - rb;
}
else
{
a = o;
b = new float3(o.X + size.X, o.Y, o.Z);
c = new float3(o.X + size.X, o.Y + size.Y, o.Z + size.Z);
d = new float3(o.X, o.Y + size.Y, o.Z + size.Z);
}
FastCreateQuad(vertices, a, b, c, d, r, samplers, paletteTextureIndex, tint, alpha, nv);
var b = new float3(o.X + size.X, o.Y, o.Z);
var c = new float3(o.X + size.X, o.Y + size.Y, o.Z + size.Z);
var d = new float3(o.X, o.Y + size.Y, o.Z + size.Z);
FastCreateQuad(vertices, o, b, c, d, r, samplers, paletteTextureIndex, tint, nv);
}
public static void FastCreateQuad(Vertex[] vertices,
in float3 a, in float3 b, in float3 c, in float3 d,
float3 a, float3 b, float3 c, float3 d,
Sprite r, int2 samplers, float paletteTextureIndex,
in float3 tint, float alpha, int nv)
float3 tint, int nv)
{
float sl = 0;
float st = 0;
float sr = 0;
float sb = 0;
// See combined.vert for documentation on the channel attribute format
// See shp.vert for documentation on the channel attribute format
var attribC = r.Channel == TextureChannel.RGBA ? 0x02 : ((byte)r.Channel) << 1 | 0x01;
attribC |= samplers.X << 6;
if (r is SpriteWithSecondaryData ss)
var ss = r as SpriteWithSecondaryData;
if (ss != null)
{
sl = ss.SecondaryLeft;
st = ss.SecondaryTop;
@@ -85,15 +54,15 @@ namespace OpenRA.Graphics
}
var fAttribC = (float)attribC;
vertices[nv] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC, tint, alpha);
vertices[nv + 1] = new Vertex(b, r.Right, r.Top, sr, st, paletteTextureIndex, fAttribC, tint, alpha);
vertices[nv + 2] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC, tint, alpha);
vertices[nv + 3] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC, tint, alpha);
vertices[nv + 4] = new Vertex(d, r.Left, r.Bottom, sl, sb, paletteTextureIndex, fAttribC, tint, alpha);
vertices[nv + 5] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC, tint, alpha);
vertices[nv] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC, tint);
vertices[nv + 1] = new Vertex(b, r.Right, r.Top, sr, st, paletteTextureIndex, fAttribC, tint);
vertices[nv + 2] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC, tint);
vertices[nv + 3] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC, tint);
vertices[nv + 4] = new Vertex(d, r.Left, r.Bottom, sl, sb, paletteTextureIndex, fAttribC, tint);
vertices[nv + 5] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC, tint);
}
public static void FastCopyIntoChannel(Sprite dest, byte[] src, SpriteFrameType srcType)
public static void FastCopyIntoChannel(Sprite dest, byte[] src)
{
var destData = dest.Sheet.GetData();
var width = dest.Bounds.Width;
@@ -116,34 +85,12 @@ namespace OpenRA.Graphics
{
for (var i = 0; i < width; i++)
{
byte r, g, b, a;
switch (srcType)
{
case SpriteFrameType.Bgra32:
case SpriteFrameType.Bgr24:
{
b = src[k++];
g = src[k++];
r = src[k++];
a = srcType == SpriteFrameType.Bgra32 ? src[k++] : (byte)255;
break;
}
case SpriteFrameType.Rgba32:
case SpriteFrameType.Rgb24:
{
r = src[k++];
g = src[k++];
b = src[k++];
a = srcType == SpriteFrameType.Rgba32 ? src[k++] : (byte)255;
break;
}
default:
throw new InvalidOperationException($"Unknown SpriteFrameType {srcType}");
}
var r = src[k++];
var g = src[k++];
var b = src[k++];
var a = src[k++];
var cc = Color.FromArgb(a, r, g, b);
data[(y + j) * destStride + x + i] = PremultiplyAlpha(cc).ToArgb();
}
}
@@ -192,29 +139,16 @@ namespace OpenRA.Graphics
for (var i = 0; i < width; i++)
{
Color cc;
switch (src.Type)
if (src.Palette == null)
{
case SpriteFrameType.Indexed8:
{
cc = src.Palette[src.Data[k++]];
break;
}
case SpriteFrameType.Rgba32:
case SpriteFrameType.Rgb24:
{
var r = src.Data[k++];
var g = src.Data[k++];
var b = src.Data[k++];
var a = src.Type == SpriteFrameType.Rgba32 ? src.Data[k++] : (byte)255;
cc = Color.FromArgb(a, r, g, b);
break;
}
// Pngs don't support BGR[A], so no need to include them here
default:
throw new InvalidOperationException($"Unknown SpriteFrameType {src.Type}");
var r = src.Data[k++];
var g = src.Data[k++];
var b = src.Data[k++];
var a = src.Data[k++];
cc = Color.FromArgb(a, r, g, b);
}
else
cc = src.Palette[src.Data[k++]];
data[(y + j) * destStride + x + i] = PremultiplyAlpha(cc).ToArgb();
}
@@ -223,69 +157,6 @@ namespace OpenRA.Graphics
}
}
/// <summary>Rotates a quad about its center in the x-y plane.</summary>
/// <param name="tl">The top left vertex of the quad.</param>
/// <param name="size">A float3 containing the X, Y, and Z lengths of the quad.</param>
/// <param name="rotation">The number of radians to rotate by.</param>
/// <returns>An array of four vertices representing the rotated quad (top-left, top-right, bottom-right, bottom-left).</returns>
public static float3[] RotateQuad(float3 tl, float3 size, float rotation)
{
var center = tl + 0.5f * size;
var angleSin = (float)Math.Sin(-rotation);
var angleCos = (float)Math.Cos(-rotation);
// Rotated offset for +/- x with +/- y
var ra = 0.5f * new float3(
size.X * angleCos - size.Y * angleSin,
size.X * angleSin + size.Y * angleCos,
(size.X * angleSin + size.Y * angleCos) * size.Z / size.Y);
// Rotated offset for +/- x with -/+ y
var rb = 0.5f * new float3(
size.X * angleCos + size.Y * angleSin,
size.X * angleSin - size.Y * angleCos,
(size.X * angleSin - size.Y * angleCos) * size.Z / size.Y);
return new float3[]
{
center - ra,
center + rb,
center + ra,
center - rb
};
}
/// <summary>
/// Returns the bounds of an object. Used for determining which objects need to be rendered on screen, and which do not.
/// </summary>
/// <param name="offset">The top left vertex of the object.</param>
/// <param name="size">A float 3 containing the X, Y, and Z lengths of the object.</param>
/// <param name="rotation">The angle to rotate the object by (use 0f if there is no rotation).</param>
public static Rectangle BoundingRectangle(float3 offset, float3 size, float rotation)
{
if (rotation == 0f)
return new Rectangle((int)offset.X, (int)offset.Y, (int)size.X, (int)size.Y);
var rotatedQuad = RotateQuad(offset, size, rotation);
var minX = rotatedQuad[0].X;
var maxX = rotatedQuad[0].X;
var minY = rotatedQuad[0].Y;
var maxY = rotatedQuad[0].Y;
for (var i = 1; i < rotatedQuad.Length; i++)
{
minX = Math.Min(rotatedQuad[i].X, minX);
maxX = Math.Max(rotatedQuad[i].X, maxX);
minY = Math.Min(rotatedQuad[i].Y, minY);
maxY = Math.Max(rotatedQuad[i].Y, maxY);
}
return new Rectangle(
(int)minX,
(int)minY,
(int)Math.Ceiling(maxX) - (int)minX,
(int)Math.Ceiling(maxY) - (int)minY);
}
public static Color PremultiplyAlpha(Color c)
{
if (c.A == byte.MaxValue)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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.Runtime.InteropServices;
namespace OpenRA.Graphics
{
[StructLayout(LayoutKind.Sequential)]
public readonly struct Vertex
public struct Vertex
{
// 3d position
public readonly float X, Y, Z;
@@ -26,24 +26,24 @@ namespace OpenRA.Graphics
public readonly float P, C;
// Color tint
public readonly float R, G, B, A;
public readonly float R, G, B;
public Vertex(in float3 xyz, float s, float t, float u, float v, float p, float c)
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c, float3.Ones, 1f) { }
public Vertex(float3 xyz, float s, float t, float u, float v, float p, float c)
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c, float3.Ones) { }
public Vertex(in float3 xyz, float s, float t, float u, float v, float p, float c, in float3 tint, float a)
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c, tint.X, tint.Y, tint.Z, a) { }
public Vertex(float3 xyz, float s, float t, float u, float v, float p, float c, float3 tint)
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c, tint.X, tint.Y, tint.Z) { }
public Vertex(float x, float y, float z, float s, float t, float u, float v, float p, float c, in float3 tint, float a)
: this(x, y, z, s, t, u, v, p, c, tint.X, tint.Y, tint.Z, a) { }
public Vertex(float x, float y, float z, float s, float t, float u, float v, float p, float c, float3 tint)
: this(x, y, z, s, t, u, v, p, c, tint.X, tint.Y, tint.Z) { }
public Vertex(float x, float y, float z, float s, float t, float u, float v, float p, float c, float r, float g, float b, float a)
public Vertex(float x, float y, float z, float s, float t, float u, float v, float p, float c, float r, float g, float b)
{
X = x; Y = y; Z = z;
S = s; T = t;
U = u; V = v;
P = p; C = c;
R = r; G = g; B = b; A = a;
R = r; G = g; B = b;
}
}
}

View File

@@ -1,36 +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
namespace OpenRA.Video
{
public interface IVideo
{
ushort FrameCount { get; }
byte Framerate { get; }
ushort Width { get; }
ushort Height { get; }
/// <summary>
/// Current frame color data in 32-bit BGRA.
/// </summary>
byte[] CurrentFrameData { get; }
int CurrentFrameIndex { get; }
void AdvanceFrame();
bool HasAudio { get; }
byte[] AudioData { get; }
int AudioChannels { get; }
int SampleBits { get; }
int SampleRate { get; }
void Reset();
}
}

View File

@@ -1,32 +0,0 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System.IO;
namespace OpenRA.Video
{
public interface IVideoLoader
{
bool TryParseVideo(Stream s, bool useFramePadding, out IVideo video);
}
public static class VideoLoader
{
public static IVideo GetVideo(Stream stream, bool useFramePadding, IVideoLoader[] loaders)
{
foreach (var loader in loaders)
if (loader.TryParseVideo(stream, useFramePadding, out var video))
return video;
return null;
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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
@@ -51,11 +51,11 @@ namespace OpenRA.Graphics
// Viewport geometry (world-px)
public int2 CenterLocation { get; private set; }
public WPos CenterPosition => worldRenderer.ProjectedPosition(CenterLocation);
public WPos CenterPosition { get { return worldRenderer.ProjectedPosition(CenterLocation); } }
public Rectangle Rectangle => new(TopLeft, new Size(viewportSize.X, viewportSize.Y));
public int2 TopLeft => CenterLocation - viewportSize / 2;
public int2 BottomRight => CenterLocation + viewportSize / 2;
public Rectangle Rectangle { get { return new Rectangle(TopLeft, new Size(viewportSize.X, viewportSize.Y)); } }
public int2 TopLeft { get { return CenterLocation - viewportSize / 2; } }
public int2 BottomRight { get { return CenterLocation + viewportSize / 2; } }
int2 viewportSize;
ProjectedCellRegion cells;
bool cellsDirty = true;
@@ -66,13 +66,19 @@ namespace OpenRA.Graphics
WorldViewport lastViewportDistance;
float zoom = 1f;
float minZoom = 1f;
float maxZoom = 2f;
bool unlockMinZoom;
float unlockedMinZoomScale;
float unlockedMinZoom = 1f;
public float Zoom
{
get => zoom;
get
{
return zoom;
}
private set
{
@@ -83,13 +89,12 @@ namespace OpenRA.Graphics
}
}
public float MinZoom { get; private set; } = 1f;
public float MaxZoom { get; private set; } = 2f;
public float MinZoom { get { return minZoom; } }
public void AdjustZoom(float dz)
{
// Exponential ensures that equal positive and negative steps have the same effect
Zoom = (zoom * (float)Math.Exp(dz)).Clamp(unlockMinZoom ? unlockedMinZoom : MinZoom, MaxZoom);
Zoom = (zoom * (float)Math.Exp(dz)).Clamp(unlockMinZoom ? unlockedMinZoom : minZoom, maxZoom);
}
public void AdjustZoom(float dz, int2 center)
@@ -103,10 +108,10 @@ namespace OpenRA.Graphics
public void ToggleZoom()
{
// Unlocked zooms always reset to the default zoom
if (zoom < MinZoom)
Zoom = MinZoom;
if (zoom < minZoom)
Zoom = minZoom;
else
Zoom = zoom > MinZoom ? MinZoom : MaxZoom;
Zoom = zoom > minZoom ? minZoom : maxZoom;
}
public void UnlockMinimumZoom(float scale)
@@ -119,6 +124,23 @@ namespace OpenRA.Graphics
public static long LastMoveRunTime = 0;
public static int2 LastMousePos;
float ClosestTo(float[] collection, float target)
{
var closestValue = collection.First();
var subtractResult = Math.Abs(closestValue - target);
foreach (var element in collection)
{
if (Math.Abs(element - target) < subtractResult)
{
subtractResult = Math.Abs(element - target);
closestValue = element;
}
}
return closestValue;
}
public ScrollDirection GetBlockedDirections()
{
var ret = ScrollDirection.None;
@@ -172,7 +194,7 @@ namespace OpenRA.Graphics
UpdateViewportZooms();
}
static float CalculateMinimumZoom(float minHeight, float maxHeight)
float CalculateMinimumZoom(float minHeight, float maxHeight)
{
var h = Game.Renderer.NativeResolution.Height;
@@ -208,34 +230,31 @@ namespace OpenRA.Graphics
var vd = graphicSettings.ViewportDistance;
if (viewportSizes.AllowNativeZoom && vd == WorldViewport.Native)
MinZoom = viewportSizes.DefaultScale;
minZoom = 1;
else
{
var range = viewportSizes.GetSizeRange(vd);
MinZoom = CalculateMinimumZoom(range.X, range.Y) * viewportSizes.DefaultScale;
minZoom = CalculateMinimumZoom(range.X, range.Y);
}
MaxZoom = Math.Min(MinZoom * viewportSizes.MaxZoomScale, Game.Renderer.NativeResolution.Height * viewportSizes.DefaultScale / viewportSizes.MaxZoomWindowHeight);
maxZoom = Math.Min(minZoom * viewportSizes.MaxZoomScale, Game.Renderer.NativeResolution.Height * 1f / viewportSizes.MaxZoomWindowHeight);
if (unlockMinZoom)
{
// Spectators and the map editor support zooming out by an extra factor of two.
// Specators and the map editor support zooming out by an extra factor of two.
// TODO: Allow zooming out until the full map is visible
// We need to improve our viewport scroll handling to center the map as we zoom out
// before this will work well enough to enable
unlockedMinZoom = MinZoom * unlockedMinZoomScale;
unlockedMinZoom = minZoom * unlockedMinZoomScale;
}
if (resetCurrentZoom)
Zoom = MinZoom;
Zoom = minZoom;
else
Zoom = Zoom.Clamp(MinZoom, MaxZoom);
var maxSize = 1f / (unlockMinZoom ? unlockedMinZoom : MinZoom) * new float2(Game.Renderer.NativeResolution);
Game.Renderer.SetMaximumViewportSize(new Size((int)maxSize.X, (int)maxSize.Y));
Zoom = Zoom.Clamp(minZoom, maxZoom);
foreach (var t in worldRenderer.World.WorldActor.TraitsImplementing<INotifyViewportZoomExtentsChanged>())
t.ViewportZoomExtentsChanged(MinZoom, MaxZoom);
t.ViewportZoomExtentsChanged(minZoom, maxZoom);
}
public CPos ViewToWorld(int2 view)
@@ -263,7 +282,7 @@ namespace OpenRA.Graphics
// Try and find the closest cell
if (candidates.Count > 0)
{
return candidates.MinBy(uv =>
return candidates.OrderBy(uv =>
{
var p = map.CenterOfCell(uv.ToCPos(map.Grid.Type));
var s = worldRenderer.ScreenPxPosition(p);
@@ -271,14 +290,14 @@ namespace OpenRA.Graphics
var dy = Math.Abs(s.Y - world.Y);
return dx * dx + dy * dy;
}).ToCPos(map);
}).First().ToCPos(map);
}
// Something is very wrong, but lets return something that isn't completely bogus and hope the caller can recover
return worldRenderer.World.Map.CellContaining(worldRenderer.ProjectedPosition(ViewToWorldPx(view)));
}
/// <summary>Returns an unfiltered list of all cells that could potentially contain the mouse cursor.</summary>
/// <summary> Returns an unfiltered list of all cells that could potentially contain the mouse cursor</summary>
IEnumerable<MPos> CandidateMouseoverCells(int2 world)
{
var map = worldRenderer.World.Map;
@@ -294,8 +313,8 @@ namespace OpenRA.Graphics
}
public int2 ViewToWorldPx(int2 view) { return (graphicSettings.UIScale / Zoom * view.ToFloat2()).ToInt2() + TopLeft; }
public int2 WorldToViewPx(int2 world) { return (Zoom / graphicSettings.UIScale * (world - TopLeft).ToFloat2()).ToInt2(); }
public int2 WorldToViewPx(in float3 world) { return (Zoom / graphicSettings.UIScale * (world - TopLeft).XY).ToInt2(); }
public int2 WorldToViewPx(int2 world) { return ((Zoom / graphicSettings.UIScale) * (world - TopLeft).ToFloat2()).ToInt2(); }
public int2 WorldToViewPx(float3 world) { return ((Zoom / graphicSettings.UIScale) * (world - TopLeft).XY).ToInt2(); }
public void Center(IEnumerable<Actor> actors)
{

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,36 +20,39 @@ namespace OpenRA.Graphics
{
public sealed class WorldRenderer : IDisposable
{
public static readonly Func<IRenderable, int> RenderableZPositionComparisonKey =
r => r.Pos.Y + r.Pos.Z + r.ZOffset;
public static readonly Func<IRenderable, int> RenderableScreenZPositionComparisonKey =
r => ZPosition(r.Pos, r.ZOffset);
public readonly Size TileSize;
public readonly int TileScale;
public readonly World World;
public Viewport Viewport { get; }
public readonly Theater Theater;
public Viewport Viewport { get; private set; }
public readonly ITerrainLighting TerrainLighting;
public event Action PaletteInvalidated = null;
readonly HashSet<Actor> onScreenActors = new();
readonly HardwarePalette palette = new();
readonly Dictionary<string, PaletteReference> palettes = new();
readonly HashSet<Actor> onScreenActors = new HashSet<Actor>();
readonly HardwarePalette palette = new HardwarePalette();
readonly Dictionary<string, PaletteReference> palettes = new Dictionary<string, PaletteReference>();
readonly IRenderTerrain terrainRenderer;
readonly Lazy<DebugVisualizations> debugVis;
readonly Func<string, PaletteReference> createPaletteReference;
readonly bool enableDepthBuffer;
readonly List<IFinalizedRenderable> preparedRenderables = new();
readonly List<IFinalizedRenderable> preparedOverlayRenderables = new();
readonly List<IFinalizedRenderable> preparedAnnotationRenderables = new();
readonly List<IFinalizedRenderable> preparedRenderables = new List<IFinalizedRenderable>();
readonly List<IFinalizedRenderable> preparedOverlayRenderables = new List<IFinalizedRenderable>();
readonly List<IFinalizedRenderable> preparedAnnotationRenderables = new List<IFinalizedRenderable>();
readonly List<IRenderable> renderablesBuffer = new();
readonly List<IRenderable> renderablesBuffer = new List<IRenderable>();
bool lastDepthPreviewEnabled;
internal WorldRenderer(ModData modData, World world)
{
World = world;
TileSize = World.Map.Grid.TileSize;
TileScale = World.Map.Grid.TileScale;
TileScale = World.Map.Grid.Type == MapGridType.RectangularIsometric ? 1448 : 1024;
Viewport = new Viewport(this, world.Map);
createPaletteReference = CreatePaletteReference;
@@ -65,6 +68,7 @@ namespace OpenRA.Graphics
palette.Initialize();
Theater = new Theater(world.Map.Rules.TileSet);
TerrainLighting = world.WorldActor.TraitOrDefault<ITerrainLighting>();
terrainRenderer = world.WorldActor.TraitOrDefault<IRenderTerrain>();
@@ -83,13 +87,7 @@ namespace OpenRA.Graphics
return new PaletteReference(name, palette.GetPaletteIndex(name), pal, palette);
}
public PaletteReference Palette(string name)
{
// HACK: This is working around the fact that palettes are defined on traits rather than sequences
// and can be removed once this has been fixed.
return string.IsNullOrEmpty(name) ? null : palettes.GetOrAdd(name, createPaletteReference);
}
public PaletteReference Palette(string name) { return palettes.GetOrAdd(name, createPaletteReference); }
public void AddPalette(string name, ImmutablePalette pal, bool allowModifiers = false, bool allowOverwrite = false)
{
if (allowOverwrite && palette.Contains(name))
@@ -109,13 +107,8 @@ namespace OpenRA.Graphics
palette.ReplacePalette(name, pal);
// Update cached PlayerReference if one exists
if (palettes.TryGetValue(name, out var paletteReference))
paletteReference.Palette = pal;
}
public void SetPaletteColorShift(string name, float hueOffset, float satOffset, float valueModifier, float minHue, float maxHue)
{
palette.SetColorShift(name, hueOffset, satOffset, valueModifier, minHue, maxHue);
if (palettes.ContainsKey(name))
palettes[name].Palette = pal;
}
// PERF: Avoid LINQ.
@@ -140,8 +133,7 @@ namespace OpenRA.Graphics
foreach (var e in World.ScreenMap.RenderableEffectsInBox(Viewport.TopLeft, Viewport.BottomRight))
renderablesBuffer.AddRange(e.Render(this));
// Renderables must be ordered using a stable sorting algorithm to avoid flickering artefacts
foreach (var renderable in renderablesBuffer.OrderBy(RenderableZPositionComparisonKey))
foreach (var renderable in renderablesBuffer.OrderBy(RenderableScreenZPositionComparisonKey))
preparedRenderables.Add(renderable.PrepareRender(this));
// PERF: Reuse collection to avoid allocations.
@@ -151,14 +143,14 @@ namespace OpenRA.Graphics
// PERF: Avoid LINQ.
void GenerateOverlayRenderables()
{
World.ApplyToActorsWithTrait<IRenderAboveShroud>((actor, trait) =>
foreach (var a in World.ActorsWithTrait<IRenderAboveShroud>())
{
if (!actor.IsInWorld || actor.Disposed || (trait.SpatiallyPartitionable && !onScreenActors.Contains(actor)))
return;
if (!a.Actor.IsInWorld || a.Actor.Disposed || (a.Trait.SpatiallyPartitionable && !onScreenActors.Contains(a.Actor)))
continue;
foreach (var renderable in trait.RenderAboveShroud(actor, this))
foreach (var renderable in a.Trait.RenderAboveShroud(a.Actor, this))
preparedOverlayRenderables.Add(renderable.PrepareRender(this));
});
}
foreach (var a in World.Selection.Actors)
{
@@ -177,7 +169,8 @@ namespace OpenRA.Graphics
foreach (var e in World.Effects)
{
if (e is not IEffectAboveShroud ea)
var ea = e as IEffectAboveShroud;
if (ea == null)
continue;
foreach (var renderable in ea.RenderAboveShroud(this))
@@ -192,14 +185,14 @@ namespace OpenRA.Graphics
// PERF: Avoid LINQ.
void GenerateAnnotationRenderables()
{
World.ApplyToActorsWithTrait<IRenderAnnotations>((actor, trait) =>
foreach (var a in World.ActorsWithTrait<IRenderAnnotations>())
{
if (!actor.IsInWorld || actor.Disposed || (trait.SpatiallyPartitionable && !onScreenActors.Contains(actor)))
return;
if (!a.Actor.IsInWorld || a.Actor.Disposed || (a.Trait.SpatiallyPartitionable && !onScreenActors.Contains(a.Actor)))
continue;
foreach (var renderAnnotation in trait.RenderAnnotations(actor, this))
foreach (var renderAnnotation in a.Trait.RenderAnnotations(a.Actor, this))
preparedAnnotationRenderables.Add(renderAnnotation.PrepareRender(this));
});
}
foreach (var a in World.Selection.Actors)
{
@@ -218,7 +211,8 @@ namespace OpenRA.Graphics
foreach (var e in World.Effects)
{
if (e is not IEffectAnnotation ea)
var ea = e as IEffectAnnotation;
if (ea == null)
continue;
foreach (var renderAnnotation in ea.RenderAnnotation(this))
@@ -252,7 +246,11 @@ namespace OpenRA.Graphics
if (World.WorldActor.Disposed)
return;
debugVis.Value?.UpdateDepthBuffer();
if (debugVis.Value != null && lastDepthPreviewEnabled != debugVis.Value.DepthBuffer)
{
lastDepthPreviewEnabled = debugVis.Value.DepthBuffer;
Game.Renderer.WorldSpriteRenderer.SetDepthPreviewEnabled(lastDepthPreviewEnabled);
}
var bounds = Viewport.GetScissorBounds(World.Type != WorldType.Editor);
Game.Renderer.EnableScissor(bounds);
@@ -270,16 +268,15 @@ namespace OpenRA.Graphics
if (enableDepthBuffer)
Game.Renderer.ClearDepthBuffer();
World.ApplyToActorsWithTrait<IRenderAboveWorld>((actor, trait) =>
{
if (actor.IsInWorld && !actor.Disposed)
trait.RenderAboveWorld(actor, this);
});
foreach (var a in World.ActorsWithTrait<IRenderAboveWorld>())
if (a.Actor.IsInWorld && !a.Actor.Disposed)
a.Trait.RenderAboveWorld(a.Actor, this);
if (enableDepthBuffer)
Game.Renderer.ClearDepthBuffer();
World.ApplyToActorsWithTrait<IRenderShroud>((actor, trait) => trait.RenderShroud(this));
foreach (var a in World.ActorsWithTrait<IRenderShroud>())
a.Trait.RenderShroud(this);
if (enableDepthBuffer)
Game.Renderer.Context.DisableDepthBuffer();
@@ -358,13 +355,7 @@ namespace OpenRA.Graphics
public float3 Screen3DPosition(WPos pos)
{
// The projection from world coordinates to screen coordinates has
// a non-obvious relationship between the y and z coordinates:
// * A flat surface with constant y (e.g. a vertical wall) in world coordinates
// transforms into a flat surface with constant z (depth) in screen coordinates.
// * Increasing the world y coordinate increases screen y and z coordinates equally.
// * Increases the world z coordinate decreases screen y but doesn't change screen z.
var z = pos.Y * (float)TileSize.Height / TileScale;
var z = ZPosition(pos, 0) * (float)TileSize.Height / TileScale;
return new float3((float)TileSize.Width * pos.X / TileScale, (float)TileSize.Height * (pos.Y - pos.Z) / TileScale, z);
}
@@ -383,7 +374,7 @@ namespace OpenRA.Graphics
}
// For scaling vectors to pixel sizes in the model renderer
public float3 ScreenVectorComponents(in WVec vec)
public float3 ScreenVectorComponents(WVec vec)
{
return new float3(
(float)TileSize.Width * vec.X / TileScale,
@@ -392,19 +383,29 @@ namespace OpenRA.Graphics
}
// For scaling vectors to pixel sizes in the model renderer
public float[] ScreenVector(in WVec vec)
public float[] ScreenVector(WVec vec)
{
var xyz = ScreenVectorComponents(vec);
return new[] { xyz.X, xyz.Y, xyz.Z, 1f };
}
public int2 ScreenPxOffset(in WVec vec)
public int2 ScreenPxOffset(WVec vec)
{
// Round to nearest pixel
var xyz = ScreenVectorComponents(vec);
return new int2((int)Math.Round(xyz.X), (int)Math.Round(xyz.Y));
}
public float ScreenZPosition(WPos pos, int offset)
{
return ZPosition(pos, offset) * (float)TileSize.Height / TileScale;
}
static int ZPosition(WPos pos, int offset)
{
return pos.Y + pos.Z + offset;
}
/// <summary>
/// Returns a position in the world that is projected to the given screen position.
/// There are many possible world positions, and the returned value chooses the value with no elevation.
@@ -423,6 +424,7 @@ namespace OpenRA.Graphics
World.Dispose();
palette.Dispose();
Theater.Dispose();
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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
@@ -19,9 +19,7 @@ namespace OpenRA
public readonly string Name;
public readonly Hotkey Default = Hotkey.Invalid;
public readonly string Description = "";
public readonly HashSet<string> Types = new();
public readonly HashSet<string> Contexts = new();
public readonly bool Readonly = false;
public readonly HashSet<string> Types = new HashSet<string>();
public bool HasDuplicates { get; internal set; }
public HotkeyDefinition(string name, MiniYaml node)
@@ -38,22 +36,6 @@ namespace OpenRA
var typesNode = node.Nodes.FirstOrDefault(n => n.Key == "Types");
if (typesNode != null)
Types = FieldLoader.GetValue<HashSet<string>>("Types", typesNode.Value.Value);
var contextsNode = node.Nodes.FirstOrDefault(n => n.Key == "Contexts");
if (contextsNode != null)
Contexts = FieldLoader.GetValue<HashSet<string>>("Contexts", contextsNode.Value.Value);
var platformNode = node.Nodes.FirstOrDefault(n => n.Key == "Platform");
if (platformNode != null)
{
var platformOverride = platformNode.Value.Nodes.FirstOrDefault(n => n.Key == Platform.CurrentPlatform.ToString());
if (platformOverride != null)
Default = FieldLoader.GetValue<Hotkey>("value", platformOverride.Value.Value);
}
var readonlyNode = node.Nodes.FirstOrDefault(n => n.Key == "Readonly");
if (readonlyNode != null)
Readonly = FieldLoader.GetValue<bool>("Readonly", readonlyNode.Value.Value);
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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
public sealed class HotkeyManager
{
readonly Dictionary<string, Hotkey> settings;
readonly Dictionary<string, HotkeyDefinition> definitions = new();
readonly Dictionary<string, Hotkey> keys = new();
readonly Dictionary<string, HotkeyDefinition> definitions = new Dictionary<string, HotkeyDefinition>();
readonly Dictionary<string, Hotkey> keys = new Dictionary<string, Hotkey>();
public HotkeyManager(IReadOnlyFileSystem fileSystem, Dictionary<string, Hotkey> settings, Manifest manifest)
{
@@ -35,17 +35,14 @@ namespace OpenRA
foreach (var kv in settings)
{
if (definitions.TryGetValue(kv.Key, out var definition) && !definition.Readonly)
if (definitions.ContainsKey(kv.Key))
keys[kv.Key] = kv.Value;
}
foreach (var hd in definitions)
hd.Value.HasDuplicates = GetFirstDuplicate(hd.Value, this[hd.Value.Name].GetValue()) != null;
hd.Value.HasDuplicates = GetFirstDuplicate(hd.Value.Name, this[hd.Value.Name].GetValue(), hd.Value) != null;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Performance", "CA1854:Prefer the 'IDictionary.TryGetValue(TKey, out TValue)' method",
Justification = "Func must perform a live lookup in the collection, as the lookup value can change.")]
internal Func<Hotkey> GetHotkeyReference(string name)
{
// Is this a mod-defined hotkey?
@@ -64,9 +61,6 @@ namespace OpenRA
if (!definitions.TryGetValue(name, out var definition))
return;
if (definition.Readonly)
return;
keys[name] = value;
if (value != definition.Default)
settings[name] = value;
@@ -74,7 +68,7 @@ namespace OpenRA
settings.Remove(name);
var hadDuplicates = definition.HasDuplicates;
definition.HasDuplicates = GetFirstDuplicate(definition, this[definition.Name].GetValue()) != null;
definition.HasDuplicates = GetFirstDuplicate(definition.Name, this[definition.Name].GetValue(), definition) != null;
if (hadDuplicates || definition.HasDuplicates)
{
@@ -83,30 +77,33 @@ namespace OpenRA
if (hd.Value == definition)
continue;
hd.Value.HasDuplicates = GetFirstDuplicate(hd.Value, this[hd.Value.Name].GetValue()) != null;
hd.Value.HasDuplicates = GetFirstDuplicate(hd.Value.Name, this[hd.Value.Name].GetValue(), hd.Value) != null;
}
}
}
public HotkeyDefinition GetFirstDuplicate(HotkeyDefinition definition, Hotkey value)
public HotkeyDefinition GetFirstDuplicate(string name, Hotkey value, HotkeyDefinition definition)
{
if (definition == null)
return null;
foreach (var kv in keys)
{
if (kv.Key == definition.Name)
if (kv.Key == name)
continue;
if (kv.Value == value && definitions[kv.Key].Contexts.Overlaps(definition.Contexts))
if (kv.Value == value && definitions[kv.Key].Types.Overlaps(definition.Types))
return definitions[kv.Key];
}
return null;
}
public HotkeyReference this[string name] => new(GetHotkeyReference(name));
public HotkeyReference this[string name]
{
get
{
return new HotkeyReference(GetHotkeyReference(name));
}
}
public IEnumerable<HotkeyDefinition> Definitions => definitions.Values;
public IEnumerable<HotkeyDefinition> Definitions { get { return definitions.Values; } }
}
}

View File

@@ -1,63 +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.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace OpenRA
{
public delegate void OnProgress(long total, long totalRead, int progressPercentage);
public static class HttpExtension
{
public static async Task ReadAsStreamWithProgress(this HttpResponseMessage response, Stream outputStream, OnProgress onProgress, CancellationToken token)
{
var total = response.Content.Headers.ContentLength ?? -1;
var canReportProgress = total > 0;
#if NET5_0_OR_GREATER
using (var contentStream = await response.Content.ReadAsStreamAsync(token))
#else
using (var contentStream = await response.Content.ReadAsStreamAsync())
#endif
{
var totalRead = 0L;
var buffer = new byte[8192];
var hasMoreToRead = true;
do
{
var read = await contentStream.ReadAsync(buffer.AsMemory(0, buffer.Length), token);
if (read == 0)
hasMoreToRead = false;
else
{
await outputStream.WriteAsync(buffer.AsMemory(0, read), token);
totalRead += read;
if (canReportProgress)
{
var progressPercentage = (int)((double)totalRead / total * 100);
onProgress?.Invoke(total, totalRead, progressPercentage);
}
}
}
while (hasMoreToRead && !token.IsCancellationRequested);
onProgress?.Invoke(total, totalRead, 100);
}
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,41 +9,12 @@
*/
#endregion
using System;
using System.Reflection;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA
{
public class Utility
{
static readonly ConcurrentCache<Type, FieldInfo[]> TypeFields =
new(type => type.GetFields());
static readonly ConcurrentCache<(MemberInfo Member, Type AttributeType), bool> MemberHasAttribute =
new(x => Attribute.IsDefined(x.Member, x.AttributeType));
static readonly ConcurrentCache<(MemberInfo Member, Type AttributeType, bool Inherit), object[]> MemberCustomAttributes =
new(x => x.Member.GetCustomAttributes(x.AttributeType, x.Inherit));
public static FieldInfo[] GetFields(Type type)
{
return TypeFields[type];
}
public static bool HasAttribute<TAttribute>(MemberInfo member)
where TAttribute : Attribute
{
return MemberHasAttribute[(member, typeof(TAttribute))];
}
public static TAttribute[] GetCustomAttributes<TAttribute>(MemberInfo member, bool inherit)
where TAttribute : Attribute
{
return (TAttribute[])MemberCustomAttributes[(member, typeof(TAttribute), inherit)];
}
public readonly ModData ModData;
public readonly InstalledMods Mods;

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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;
namespace OpenRA
{
public readonly struct Hotkey : IEquatable<Hotkey>
public struct Hotkey : IEquatable<Hotkey>
{
public static Hotkey Invalid = new(Keycode.UNKNOWN, Modifiers.None);
public static Hotkey Invalid = new Hotkey(Keycode.UNKNOWN, Modifiers.None);
public bool IsValid()
{
return Key != Keycode.UNKNOWN;
@@ -42,7 +42,7 @@ namespace OpenRA
var mods = Modifiers.None;
if (parts.Length >= 2)
{
var modString = s[s.IndexOf(' ')..];
var modString = s.Substring(s.IndexOf(' '));
if (!Enum<Modifiers>.TryParse(modString, true, out mods))
return false;
}
@@ -81,10 +81,11 @@ namespace OpenRA
public override bool Equals(object obj)
{
return obj is Hotkey o && (Hotkey?)o == this;
var o = obj as Hotkey?;
return o != null && o == this;
}
public override string ToString() { return $"{Key} {Modifiers:F}"; }
public override string ToString() { return "{0} {1}".F(Key, Modifiers.ToString("F")); }
public string DisplayString()
{

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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;
namespace OpenRA
{
/// <summary>
/// A reference to either a named hotkey (defined in the game settings) or a statically assigned hotkey.
/// A reference to either a named hotkey (defined in the game settings) or a statically assigned hotkey
/// </summary>
public class HotkeyReference
{

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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
@@ -37,24 +37,36 @@ namespace OpenRA
public void OnKeyInput(KeyInput input)
{
Sync.RunUnsynced(world, () => Ui.HandleKeyPress(input));
Sync.RunUnsynced(Game.Settings.Debug.SyncCheckUnsyncedCode, world, () => Ui.HandleKeyPress(input));
}
public void OnTextInput(string text)
{
Sync.RunUnsynced(world, () => Ui.HandleTextInput(text));
Sync.RunUnsynced(Game.Settings.Debug.SyncCheckUnsyncedCode, world, () => Ui.HandleTextInput(text));
}
public void OnMouseInput(MouseInput input)
{
Sync.RunUnsynced(world, () => Ui.HandleInput(input));
Sync.RunUnsynced(Game.Settings.Debug.SyncCheckUnsyncedCode, world, () => Ui.HandleInput(input));
}
}
public class MouseButtonPreference
{
public MouseButton Action => Game.Settings.Game.UseClassicMouseStyle ? MouseButton.Left : MouseButton.Right;
public MouseButton Action
{
get
{
return Game.Settings.Game.UseClassicMouseStyle ? MouseButton.Left : MouseButton.Right;
}
}
public MouseButton Cancel => Game.Settings.Game.UseClassicMouseStyle ? MouseButton.Right : MouseButton.Left;
public MouseButton Cancel
{
get
{
return Game.Settings.Game.UseClassicMouseStyle ? MouseButton.Right : MouseButton.Left;
}
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,8 +13,7 @@ using System.Collections.Generic;
namespace OpenRA
{
// List of keycodes. Duplicated from SDL 2.0.1, with the addition
// of MOUSE4 and MOUSE5.
// List of keycodes, duplicated from SDL 2.0.1
public enum Keycode
{
UNKNOWN = 0,
@@ -253,13 +252,11 @@ namespace OpenRA
KBDILLUMUP = 280 | (1 << 30),
EJECT = 281 | (1 << 30),
SLEEP = 282 | (1 << 30),
MOUSE4 = 283 | (1 << 30),
MOUSE5 = 284 | (1 << 30)
}
public static class KeycodeExts
{
static readonly Dictionary<Keycode, string> KeyNames = new()
static readonly Dictionary<Keycode, string> KeyNames = new Dictionary<Keycode, string>
{
{ Keycode.UNKNOWN, "Undefined" },
{ Keycode.RETURN, "Return" },
@@ -497,8 +494,6 @@ namespace OpenRA
{ Keycode.KBDILLUMUP, "KBDIllumUp" },
{ Keycode.EJECT, "Eject" },
{ Keycode.SLEEP, "Sleep" },
{ Keycode.MOUSE4, "Mouse 4" },
{ Keycode.MOUSE5, "Mouse 5" },
};
public static string DisplayString(Keycode k)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,18 +15,22 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenRA.FileSystem;
using OpenRA.Graphics;
using OpenRA.Primitives;
namespace OpenRA
{
public class InstalledMods : IReadOnlyDictionary<string, Manifest>
{
readonly Dictionary<string, Manifest> mods;
readonly SheetBuilder sheetBuilder;
/// <summary>Initializes the collection of locally installed mods.</summary>
/// <param name="searchPaths">Filesystem paths to search for mod packages.</param>
/// <param name="explicitPaths">Filesystem paths to additional mod packages.</param>
public InstalledMods(IEnumerable<string> searchPaths, IEnumerable<string> explicitPaths)
{
sheetBuilder = new SheetBuilder(SheetType.BGRA, 256);
mods = GetInstalledMods(searchPaths, explicitPaths);
}
@@ -54,7 +58,7 @@ namespace OpenRA
return mods;
}
static Manifest LoadMod(string id, string path)
Manifest LoadMod(string id, string path)
{
IReadOnlyPackage package = null;
try
@@ -71,7 +75,7 @@ namespace OpenRA
}
catch (Exception e)
{
Log.Write("debug", $"Load mod '{path}': {e}");
Log.Write("debug", "Load mod '{0}': {1}".F(path, e));
}
package?.Dispose();
@@ -79,7 +83,7 @@ namespace OpenRA
return null;
}
static Dictionary<string, Manifest> GetInstalledMods(IEnumerable<string> searchPaths, IEnumerable<string> explicitPaths)
Dictionary<string, Manifest> GetInstalledMods(IEnumerable<string> searchPaths, IEnumerable<string> explicitPaths)
{
var ret = new Dictionary<string, Manifest>();
var candidates = GetCandidateMods(searchPaths)
@@ -95,10 +99,10 @@ namespace OpenRA
return ret;
}
public Manifest this[string key] => mods[key];
public IEnumerable<string> Keys => mods.Keys;
public IEnumerable<Manifest> Values => mods.Values;
public int Count => mods.Count;
public Manifest this[string key] { get { return mods[key]; } }
public int Count { get { return mods.Count; } }
public ICollection<string> Keys { get { return mods.Keys; } }
public ICollection<Manifest> Values { get { return mods.Values; } }
public bool ContainsKey(string key) { return mods.ContainsKey(key); }
public IEnumerator<KeyValuePair<string, Manifest>> GetEnumerator() { return mods.GetEnumerator(); }
public bool TryGetValue(string key, out Manifest value) { return mods.TryGetValue(key, out value); }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2020 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,10 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using OpenRA.Support;
namespace OpenRA
{
@@ -24,11 +24,11 @@ namespace OpenRA
const int AuthKeySize = 2048;
public enum LinkState { Uninitialized, GeneratingKeys, Unlinked, CheckingLink, ConnectionFailed, Linked }
public LinkState State => innerState;
public string Fingerprint => innerFingerprint;
public string PublicKey => innerPublicKey;
public LinkState State { get { return innerState; } }
public string Fingerprint { get { return innerFingerprint; } }
public string PublicKey { get { return innerPublicKey; } }
public PlayerProfile ProfileData => innerData;
public PlayerProfile ProfileData { get { return innerData; } }
volatile LinkState innerState;
volatile PlayerProfile innerData;
@@ -66,10 +66,8 @@ namespace OpenRA
}
catch (Exception e)
{
Console.WriteLine("Failed to load keys:");
Console.WriteLine(e);
Log.Write("debug", $"Failed to load player keypair from `{filePath}` with exception:");
Log.Write("debug", e);
Console.WriteLine("Failed to load keys: {0}", e);
Log.Write("debug", "Failed to load player keypair from `{0}` with exception: {1}", filePath, e);
}
}
@@ -78,22 +76,23 @@ namespace OpenRA
if (State != LinkState.Unlinked && State != LinkState.Linked && State != LinkState.ConnectionFailed)
return;
Task.Run(async () =>
Action<DownloadDataCompletedEventArgs> onQueryComplete = i =>
{
try
{
var client = HttpClientFactory.Create();
if (i.Error != null)
{
innerState = LinkState.ConnectionFailed;
return;
}
var httpResponseMessage = await client.GetAsync(playerDatabase.Profile + Fingerprint);
var result = await httpResponseMessage.Content.ReadAsStreamAsync();
var yaml = MiniYaml.FromStream(result).First();
var yaml = MiniYaml.FromString(Encoding.UTF8.GetString(i.Result)).First();
if (yaml.Key == "Player")
{
innerData = FieldLoader.Load<PlayerProfile>(yaml.Value);
if (innerData.KeyRevoked)
{
Log.Write("debug", $"Revoking key with fingerprint {Fingerprint}");
Log.Write("debug", "Revoking key with fingerprint {0}", Fingerprint);
DeleteKeypair();
}
else
@@ -104,17 +103,17 @@ namespace OpenRA
}
catch (Exception e)
{
Log.Write("debug", "Failed to parse player data result with exception:");
Log.Write("debug", e);
Log.Write("debug", "Failed to parse player data result with exception: {0}", e);
innerState = LinkState.ConnectionFailed;
}
finally
{
onComplete?.Invoke();
}
});
};
innerState = LinkState.CheckingLink;
new Download(playerDatabase.Profile + Fingerprint, _ => { }, onQueryComplete);
}
public void GenerateKeypair()
@@ -139,10 +138,8 @@ namespace OpenRA
}
catch (Exception e)
{
Log.Write("debug", "Failed to generate keypair with exception:");
Log.Write("debug", e);
Console.WriteLine("Key generation failed:");
Console.WriteLine(e);
Log.Write("debug", "Failed to generate keypair with exception: {1}", e);
Console.WriteLine("Key generation failed: {0}", e);
innerState = LinkState.Uninitialized;
}
@@ -157,14 +154,12 @@ namespace OpenRA
}
catch (Exception e)
{
Log.Write("debug", "Failed to delete keypair with exception:");
Log.Write("debug", e);
Console.WriteLine("Key deletion failed:");
Console.WriteLine(e);
Log.Write("debug", "Failed to delete keypair with exception: {1}", e);
Console.WriteLine("Key deletion failed: {0}", e);
}
innerState = LinkState.Uninitialized;
parameters = default;
parameters = default(RSAParameters);
innerFingerprint = null;
innerData = null;
}

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