diff --git a/.editorconfig b/.editorconfig index 45c91558..0dea209e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,5 +1,438 @@ -[*.{cs,vb}] +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +#### Core EditorConfig Options #### + +# General Whitespace Settings +[*] +charset = utf-8 +end_of_line = crlf +insert_final_newline = true +trim_trailing_whitespace = true + +# C# files +[*.csproj] + +# Indentation and spacing +indent_style = space +indent_size = 2 +tab_width = 2 + +# C# files +[*.cs] + +# Indentation and spacing indent_style = tab +indent_size = 4 +tab_width = 4 + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +file_header_template = unset + +# this. and Me. preferences +dotnet_style_qualification_for_event = false:suggestion +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_property = false:suggestion + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# Expression-level preferences +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true:suggestion +dotnet_style_object_initializer = true:suggestion +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_return = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_simplified_interpolation = true + +# Field preferences +dotnet_style_readonly_field = true + +# Parameter preferences +dotnet_code_quality_unused_parameters = all + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = none + +# New line preferences +dotnet_style_allow_multiple_blank_lines_experimental = false:suggestion +dotnet_style_allow_statement_immediately_after_block_experimental = false:suggestion + +#### C# Coding Conventions #### + +# var preferences +csharp_style_var_elsewhere = true:suggestion +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true:suggestion +csharp_style_expression_bodied_constructors = true:suggestion +csharp_style_expression_bodied_indexers = true:suggestion +csharp_style_expression_bodied_lambdas = when_on_single_line:suggestion +csharp_style_expression_bodied_local_functions = when_on_single_line:suggestion +csharp_style_expression_bodied_methods = true:suggestion +csharp_style_expression_bodied_operators = true:suggestion +csharp_style_expression_bodied_properties = true:suggestion + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_prefer_extended_property_pattern = true:suggestion +csharp_style_prefer_not_pattern = true:suggestion +csharp_style_prefer_pattern_matching = true:suggestion +csharp_style_prefer_switch_expression = true:suggestion + +# Null-checking preferences +csharp_style_conditional_delegate_call = true:suggestion +csharp_style_prefer_parameter_null_checking = true:suggestion + +# Modifier preferences +csharp_prefer_static_local_function = true:suggestion +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async + +# Code-block preferences +csharp_prefer_braces = true:suggestion +csharp_prefer_simple_using_statement = false:suggestion +csharp_style_namespace_declarations = block_scoped:suggestion +csharp_style_prefer_method_group_conversion = true:suggestion + +# Expression-level preferences +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:suggestion +csharp_style_prefer_null_check_over_type_check = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_prefer_tuple_swap = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:suggestion + +# New line preferences +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false:suggestion +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:suggestion +csharp_style_allow_embedded_statements_on_same_line_experimental = false:warning + +csharp_style_prefer_top_level_statements = true:silent + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = false +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_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_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +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 + +dotnet_naming_rule.class_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.class_should_be_pascal_case.symbols = class +dotnet_naming_rule.class_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.interface_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.interface_should_be_pascal_case.symbols = interface +dotnet_naming_rule.interface_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.struct_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.struct_should_be_pascal_case.symbols = struct +dotnet_naming_rule.struct_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.enum_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.enum_should_be_pascal_case.symbols = enum +dotnet_naming_rule.enum_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.delegate_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.delegate_should_be_pascal_case.symbols = delegate +dotnet_naming_rule.delegate_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.event_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.event_should_be_pascal_case.symbols = event +dotnet_naming_rule.event_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.method_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.method_should_be_pascal_case.symbols = method +dotnet_naming_rule.method_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.property_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.property_should_be_pascal_case.symbols = property +dotnet_naming_rule.property_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.class.applicable_kinds = class +dotnet_naming_symbols.class.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.class.required_modifiers = + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.struct.applicable_kinds = struct +dotnet_naming_symbols.struct.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.struct.required_modifiers = + +dotnet_naming_symbols.enum.applicable_kinds = enum +dotnet_naming_symbols.enum.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.enum.required_modifiers = + +dotnet_naming_symbols.delegate.applicable_kinds = delegate +dotnet_naming_symbols.delegate.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.delegate.required_modifiers = + +dotnet_naming_symbols.event.applicable_kinds = event +dotnet_naming_symbols.event.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.event.required_modifiers = + +dotnet_naming_symbols.method.applicable_kinds = method +dotnet_naming_symbols.method.applicable_accessibilities = public +dotnet_naming_symbols.method.required_modifiers = + +dotnet_naming_symbols.property.applicable_kinds = property +dotnet_naming_symbols.property.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.property.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +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, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +# Other + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_diagnostic.CA1002.severity = suggestion +dotnet_diagnostic.CA1062.severity = none +dotnet_diagnostic.CA1309.severity = suggestion +dotnet_diagnostic.CA1507.severity = warning +dotnet_diagnostic.CA1711.severity = none +dotnet_diagnostic.CA1716.severity = none +dotnet_diagnostic.CA1805.severity = suggestion +dotnet_diagnostic.CA1815.severity = none +dotnet_diagnostic.CA1825.severity = none +dotnet_diagnostic.CA2201.severity = warning +dotnet_diagnostic.CA2208.severity = none +dotnet_diagnostic.CA2225.severity = none +dotnet_diagnostic.CA2227.severity = suggestion + +dotnet_diagnostic.CS8032.severity = none +dotnet_diagnostic.CS8602.severity = warning # do not change: DAT treats this as error so we need to match it + +dotnet_diagnostic.IDE0004.severity = suggestion +dotnet_diagnostic.IDE0005.severity = suggestion +dotnet_diagnostic.IDE0009.severity = suggestion +dotnet_diagnostic.IDE0010.severity = suggestion +dotnet_diagnostic.IDE0011.severity = suggestion +dotnet_diagnostic.IDE0016.severity = suggestion +dotnet_diagnostic.IDE0017.severity = suggestion +dotnet_diagnostic.IDE0018.severity = suggestion +dotnet_diagnostic.IDE0019.severity = suggestion +dotnet_diagnostic.IDE0020.severity = suggestion +dotnet_diagnostic.IDE0021.severity = suggestion +dotnet_diagnostic.IDE0022.severity = suggestion +dotnet_diagnostic.IDE0023.severity = suggestion +dotnet_diagnostic.IDE0024.severity = suggestion +dotnet_diagnostic.IDE0025.severity = suggestion +dotnet_diagnostic.IDE0026.severity = suggestion +dotnet_diagnostic.IDE0027.severity = suggestion +dotnet_diagnostic.IDE0028.severity = suggestion +dotnet_diagnostic.IDE0029.severity = suggestion +dotnet_diagnostic.IDE0030.severity = suggestion +dotnet_diagnostic.IDE0031.severity = suggestion +dotnet_diagnostic.IDE0032.severity = suggestion +dotnet_diagnostic.IDE0034.severity = suggestion +dotnet_diagnostic.IDE0036.severity = suggestion +dotnet_diagnostic.IDE0037.severity = suggestion +dotnet_diagnostic.IDE0039.severity = suggestion +dotnet_diagnostic.IDE0040.severity = silent +dotnet_diagnostic.IDE0041.severity = suggestion +dotnet_diagnostic.IDE0042.severity = suggestion +dotnet_diagnostic.IDE0043.severity = warning +dotnet_diagnostic.IDE0045.severity = suggestion +dotnet_diagnostic.IDE0046.severity = suggestion +dotnet_diagnostic.IDE0047.severity = suggestion +dotnet_diagnostic.IDE0048.severity = suggestion +dotnet_diagnostic.IDE0054.severity = suggestion +dotnet_diagnostic.IDE0055.severity = suggestion +dotnet_diagnostic.IDE0056.severity = suggestion +dotnet_diagnostic.IDE0057.severity = suggestion +dotnet_diagnostic.IDE0058.severity = suggestion +dotnet_diagnostic.IDE0059.severity = suggestion +dotnet_diagnostic.IDE0060.severity = suggestion +dotnet_diagnostic.IDE0063.severity = suggestion +dotnet_diagnostic.IDE0062.severity = suggestion +dotnet_diagnostic.IDE0061.severity = suggestion +dotnet_diagnostic.IDE0065.severity = suggestion +dotnet_diagnostic.IDE0066.severity = suggestion +dotnet_diagnostic.IDE0071.severity = suggestion +dotnet_diagnostic.IDE0072.severity = suggestion +dotnet_diagnostic.IDE0073.severity = suggestion +dotnet_diagnostic.IDE0074.severity = suggestion +dotnet_diagnostic.IDE0075.severity = suggestion +dotnet_diagnostic.IDE0078.severity = suggestion +dotnet_diagnostic.IDE0080.severity = suggestion +dotnet_diagnostic.IDE0082.severity = suggestion +dotnet_diagnostic.IDE0083.severity = suggestion +dotnet_diagnostic.IDE0090.severity = suggestion +dotnet_diagnostic.IDE0100.severity = suggestion +dotnet_diagnostic.IDE0110.severity = suggestion +dotnet_diagnostic.IDE0120.severity = suggestion +dotnet_diagnostic.IDE0130.severity = silent +dotnet_diagnostic.IDE0150.severity = suggestion +dotnet_diagnostic.IDE0160.severity = suggestion +dotnet_diagnostic.IDE0161.severity = silent +dotnet_diagnostic.IDE0170.severity = suggestion +dotnet_diagnostic.IDE0180.severity = suggestion +dotnet_diagnostic.IDE0190.severity = suggestion +dotnet_diagnostic.IDE0200.severity = suggestion +dotnet_diagnostic.IDE1005.severity = suggestion +dotnet_diagnostic.IDE1006.severity = suggestion +dotnet_diagnostic.IDE2000.severity = suggestion +dotnet_diagnostic.IDE2001.severity = suggestion +dotnet_diagnostic.IDE2002.severity = suggestion +dotnet_diagnostic.IDE2003.severity = suggestion +dotnet_diagnostic.IDE2004.severity = suggestion + +dotnet_diagnostic.SA0001.severity = none +dotnet_diagnostic.SA1000.severity = none +dotnet_diagnostic.SA1005.severity = suggestion +dotnet_diagnostic.SA1027.severity = silent +dotnet_diagnostic.SA1101.severity = none +dotnet_diagnostic.SA1108.severity = none +dotnet_diagnostic.SA1124.severity = none +dotnet_diagnostic.SA1200.severity = none +dotnet_diagnostic.SA1201.severity = none +dotnet_diagnostic.SA1202.severity = none +dotnet_diagnostic.SA1204.severity = none +dotnet_diagnostic.SA1208.severity = none +dotnet_diagnostic.SA1209.severity = none +dotnet_diagnostic.SA1210.severity = none +dotnet_diagnostic.SA1313.severity = none +dotnet_diagnostic.SA1314.severity = none +dotnet_diagnostic.SA1400.severity = none +dotnet_diagnostic.SA1502.severity = none +dotnet_diagnostic.SA1508.severity = warning +dotnet_diagnostic.SA1512.severity = suggestion +dotnet_diagnostic.SA1513.severity = none +dotnet_diagnostic.SA1515.severity = suggestion +dotnet_diagnostic.SA1516.severity = none +dotnet_diagnostic.SA1600.severity = none +dotnet_diagnostic.SA1602.severity = none +dotnet_diagnostic.SA1649.severity = none +dotnet_diagnostic.SA1633.severity = none + +dotnet_diagnostic.WTG1001.severity = warning +dotnet_diagnostic.WTG1002.severity = warning +dotnet_diagnostic.WTG1011.severity = warning +dotnet_diagnostic.WTG1009.severity = none +dotnet_diagnostic.WTG1012.severity = warning +dotnet_diagnostic.WTG1013.severity = none +dotnet_diagnostic.WTG3001.severity = warning +dotnet_diagnostic.WTG3002.severity = warning +dotnet_diagnostic.WTG3003.severity = warning +dotnet_diagnostic.WTG3005.severity = warning +dotnet_diagnostic.WTG3006.severity = warning +dotnet_diagnostic.WTG3008.severity = warning +dotnet_diagnostic.WTG3011.severity = warning -# IDE0040: Add accessibility modifiers -dotnet_style_require_accessibility_modifiers = always +dotnet_diagnostic.RS0030.severity = error diff --git a/OpenLocoTool/Annotation.cs b/Core/Annotation.cs similarity index 86% rename from OpenLocoTool/Annotation.cs rename to Core/Annotation.cs index 744a3a85..862f172b 100644 --- a/OpenLocoTool/Annotation.cs +++ b/Core/Annotation.cs @@ -1,10 +1,10 @@ -namespace OpenLocoTool +namespace OpenLoco.ObjectEditor { public class Annotation { - private int start = 0; - private int end = 0; - private int length = 0; + private int start; + private int end; + private int length; public Annotation(string name, Annotation? parent, int start, int length) { diff --git a/Core/Core.csproj b/Core/Core.csproj new file mode 100644 index 00000000..81276d8b --- /dev/null +++ b/Core/Core.csproj @@ -0,0 +1,20 @@ + + + + Library + net8.0 + enable + enable + latest + true + + + + + + + + + + + diff --git a/OpenLocoTool/DatFileParsing/AttributeHelper.cs b/Core/DatFileParsing/AttributeHelper.cs similarity index 83% rename from OpenLocoTool/DatFileParsing/AttributeHelper.cs rename to Core/DatFileParsing/AttributeHelper.cs index 032577ae..8a001a6c 100644 --- a/OpenLocoTool/DatFileParsing/AttributeHelper.cs +++ b/Core/DatFileParsing/AttributeHelper.cs @@ -1,14 +1,14 @@ -using System.Reflection; -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor.Data; +using System.Reflection; -namespace OpenLocoTool.DatFileParsing +namespace OpenLoco.ObjectEditor.DatFileParsing { public static class AttributeHelper { public static T? Get(PropertyInfo p) where T : Attribute { - var attrs = p.GetCustomAttributes(typeof(T), inherit: false); - return attrs.Length == 1 ? attrs[0] as T : null; + var attributes = p.GetCustomAttributes(typeof(T), inherit: false); + return attributes.Length == 1 ? attributes[0] as T : null; } public static T? Get(Type t) where T : Attribute diff --git a/OpenLocoTool/DatFileParsing/ByteHelpers.cs b/Core/DatFileParsing/ByteHelpers.cs similarity index 79% rename from OpenLocoTool/DatFileParsing/ByteHelpers.cs rename to Core/DatFileParsing/ByteHelpers.cs index 4a4f0314..b6d0ea78 100644 --- a/OpenLocoTool/DatFileParsing/ByteHelpers.cs +++ b/Core/DatFileParsing/ByteHelpers.cs @@ -1,4 +1,6 @@ -namespace OpenLocoTool.DatFileParsing +using Zenith.Core; + +namespace OpenLoco.ObjectEditor.DatFileParsing { public static class ByteHelpers { @@ -23,10 +25,7 @@ public static int GetObjectSize(Type type) size = sizeAttr.Size; } - if (size == 0) - { - throw new ArgumentException("unknown primitive type with no size"); - } + Verify.Positive(size, message: $"type {type.Name} has no size data associated with it"); return size; } diff --git a/OpenLocoTool/DatFileParsing/ByteReader.cs b/Core/DatFileParsing/ByteReader.cs similarity index 77% rename from OpenLocoTool/DatFileParsing/ByteReader.cs rename to Core/DatFileParsing/ByteReader.cs index 85f845a3..e561a417 100644 --- a/OpenLocoTool/DatFileParsing/ByteReader.cs +++ b/Core/DatFileParsing/ByteReader.cs @@ -1,4 +1,6 @@ -namespace OpenLocoTool.DatFileParsing +using Zenith.Core; + +namespace OpenLoco.ObjectEditor.DatFileParsing { public static class ByteReader { @@ -41,7 +43,7 @@ public static object ReadT(ReadOnlySpan data, Type t, int offset, int arrL if (t.IsArray) { - var elementType = t.GetElementType(); + var elementType = t.GetElementType() ?? throw new ArgumentNullException(t.Name); var size = ByteHelpers.GetObjectSize(elementType); var arr = Array.CreateInstance(elementType, arrLength); @@ -65,7 +67,7 @@ public static object ReadT(ReadOnlySpan data, Type t, int offset, int arrL foreach (var enumValue in enumValues) { - var enumValueInt = Convert.ToInt32(Enum.Parse(t, enumValue.ToString())); // Convert to int + var enumValueInt = Convert.ToInt32(Enum.Parse(t, enumValue.ToString()!)); // Convert to int if ((enumValueInt & Convert.ToInt32(underlyingValue)) != 0) // Convert to int { combinedValue |= enumValueInt; @@ -127,13 +129,39 @@ public static ILocoStruct ReadLocoStruct(ReadOnlySpan data, Type t) var variableAttr = AttributeHelper.Get(p); if (variableAttr != null) { - if (p.PropertyType.IsArray && p.PropertyType.GetElementType() == typeof(uint8_t)) + if (p.PropertyType.IsArray) { - args.Add(new uint8_t[arrLength]); + // todo: find a generic way to do this + if (p.PropertyType.GetElementType() == typeof(uint8_t)) + { + args.Add(new uint8_t[arrLength]); + } + else if (p.PropertyType.GetElementType() == typeof(int8_t)) + { + args.Add(new int8_t[arrLength]); + } + else if (p.PropertyType.GetElementType() == typeof(uint16_t)) + { + args.Add(new uint16_t[arrLength]); + } + else if (p.PropertyType.GetElementType() == typeof(int16_t)) + { + args.Add(new int16_t[arrLength]); + } + else if (p.PropertyType.GetElementType() == typeof(uint32_t)) + { + args.Add(new uint32_t[arrLength]); + } + else if (p.PropertyType.GetElementType() == typeof(int32_t)) + { + args.Add(new int32_t[arrLength]); + } } else { - args.Add(Activator.CreateInstance(p.PropertyType)); + var newInstance = Activator.CreateInstance(p.PropertyType); + Verify.NotNull(newInstance, paramName: p.PropertyType.Name); + args.Add(newInstance!); } continue; diff --git a/OpenLocoTool/DatFileParsing/ByteReaderT.cs b/Core/DatFileParsing/ByteReaderT.cs similarity index 97% rename from OpenLocoTool/DatFileParsing/ByteReaderT.cs rename to Core/DatFileParsing/ByteReaderT.cs index 2397c9b7..acb73b61 100644 --- a/OpenLocoTool/DatFileParsing/ByteReaderT.cs +++ b/Core/DatFileParsing/ByteReaderT.cs @@ -1,4 +1,4 @@ -namespace OpenLocoTool.DatFileParsing +namespace OpenLoco.ObjectEditor.DatFileParsing { public static class ByteReaderT { diff --git a/OpenLocoTool/DatFileParsing/ByteWriter.cs b/Core/DatFileParsing/ByteWriter.cs similarity index 89% rename from OpenLocoTool/DatFileParsing/ByteWriter.cs rename to Core/DatFileParsing/ByteWriter.cs index 5a3fd0d8..6c626701 100644 --- a/OpenLocoTool/DatFileParsing/ByteWriter.cs +++ b/Core/DatFileParsing/ByteWriter.cs @@ -1,4 +1,6 @@ -namespace OpenLocoTool.DatFileParsing +using Zenith.Core; + +namespace OpenLoco.ObjectEditor.DatFileParsing { public static class ByteWriter { @@ -36,13 +38,13 @@ public static void WriteT(Span data, Type t, int offset, object val) } else if (t.IsArray) { - var elementType = t.GetElementType() ?? throw new NullReferenceException(); + var elementType = t.GetElementType() ?? throw new ArgumentNullException(t.Name); var size = ByteHelpers.GetObjectSize(elementType); var arr = (Array)val; for (var i = 0; i < arr.Length; i++) { - var value = arr.GetValue(i) ?? throw new NullReferenceException(); + var value = arr.GetValue(i) ?? throw new ArgumentNullException($"{t.Name}[{i}]"); WriteT(data, elementType, offset + (i * size), value); } } @@ -72,10 +74,7 @@ public static void WriteT(Span data, Type t, int offset, object val) public static ReadOnlySpan WriteLocoStruct(ILocoStruct obj) { - if (obj == null) - { - throw new NullReferenceException(); - } + Verify.NotNull(obj); var t = obj.GetType(); var objSize = ByteHelpers.GetObjectSize(t); @@ -113,7 +112,7 @@ public static ReadOnlySpan WriteLocoStruct(ILocoStruct obj) arrLength = arrLengthAttr.Length; } - var propVal = p.GetValue(obj) ?? throw new NullReferenceException(); + var propVal = p.GetValue(obj) ?? throw new ArgumentNullException($"{p.Name} for {obj} was null"); WriteT(buf, p.PropertyType, offsetAttr.Offset, propVal); } diff --git a/OpenLocoTool/DatFileParsing/ByteWriterT.cs b/Core/DatFileParsing/ByteWriterT.cs similarity index 97% rename from OpenLocoTool/DatFileParsing/ByteWriterT.cs rename to Core/DatFileParsing/ByteWriterT.cs index a25669b6..d34401d6 100644 --- a/OpenLocoTool/DatFileParsing/ByteWriterT.cs +++ b/Core/DatFileParsing/ByteWriterT.cs @@ -1,4 +1,4 @@ -namespace OpenLocoTool.DatFileParsing +namespace OpenLoco.ObjectEditor.DatFileParsing { public static class ByteWriterT { diff --git a/OpenLocoTool/DatFileParsing/LocoAttributes.cs b/Core/DatFileParsing/LocoAttributes.cs similarity index 96% rename from OpenLocoTool/DatFileParsing/LocoAttributes.cs rename to Core/DatFileParsing/LocoAttributes.cs index 2923c247..0e072158 100644 --- a/OpenLocoTool/DatFileParsing/LocoAttributes.cs +++ b/Core/DatFileParsing/LocoAttributes.cs @@ -1,6 +1,6 @@ -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor.Data; -namespace OpenLocoTool.DatFileParsing +namespace OpenLoco.ObjectEditor.DatFileParsing { [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false)] public class LocoArrayLengthAttribute(int length) : Attribute diff --git a/OpenLocoTool/DatFileParsing/ObjectAnnotator.cs b/Core/DatFileParsing/ObjectAnnotator.cs similarity index 88% rename from OpenLocoTool/DatFileParsing/ObjectAnnotator.cs rename to Core/DatFileParsing/ObjectAnnotator.cs index fce38712..1796e8c4 100644 --- a/OpenLocoTool/DatFileParsing/ObjectAnnotator.cs +++ b/Core/DatFileParsing/ObjectAnnotator.cs @@ -1,9 +1,12 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Reflection; +using System.Runtime.InteropServices; using System.Text; -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.Headers; +using Zenith.Core; -namespace OpenLocoTool.DatFileParsing +namespace OpenLoco.ObjectEditor.DatFileParsing { public static class ObjectAnnotator { @@ -38,11 +41,7 @@ .. SawyerStreamReader.Decode(objectHeader.Encoding, bytelist[runningCount..(int) ]; var locoStruct = SawyerStreamReader.GetLocoStruct(s5Header.ObjectType, fullData.AsSpan()[runningCount..]); - if (locoStruct == null) - { - Debugger.Break(); - throw new NullReferenceException("loco object was null"); - } + Verify.NotNull(locoStruct); var structSize = AttributeHelper.Get(locoStruct.GetType()); var locoStructSize = structSize!.Size; @@ -201,11 +200,42 @@ static List AnnotateProperties(object o, int runningCount = 0, Annot if (offset != null) { var location = runningCount + offset!.Offset; - annotations.Add(new Annotation(p.Name, root, location, 1)); + + var propType = p.PropertyType; + + // should probably use recursion + while (propType.IsGenericType) + { + propType = propType.GenericTypeArguments[0]; + } + + while (propType.IsArray) + { + propType = propType.GetElementType(); + } + + if (propType.IsEnum) + { + propType = propType.GetEnumUnderlyingType(); + } + + var locoSize = propType.GetCustomAttribute(); + + var length = locoSize != null + ? locoSize.Size + : Marshal.SizeOf(propType); + + var lengthAttr = p.GetCustomAttribute(); + if (lengthAttr != null) + { + length *= lengthAttr.Length; + } + + annotations.Add(new Annotation(p.Name, root, location, length)); } } return annotations; } } -} \ No newline at end of file +} diff --git a/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs b/Core/DatFileParsing/SawyerStreamReader.cs similarity index 89% rename from OpenLocoTool/DatFileParsing/SawyerStreamReader.cs rename to Core/DatFileParsing/SawyerStreamReader.cs index c4877ea0..d8d4cf8b 100644 --- a/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs +++ b/Core/DatFileParsing/SawyerStreamReader.cs @@ -1,11 +1,14 @@ -using System.Diagnostics; -using System.Text; -using OpenLocoTool.Headers; -using OpenLocoTool.Objects; -using OpenLocoTool.Types; -using OpenLocoToolCommon; - -namespace OpenLocoTool.DatFileParsing +using System.Text; +using OpenLoco.ObjectEditor.Headers; +using OpenLoco.ObjectEditor.Objects; +using OpenLoco.ObjectEditor.Types; +using OpenLoco.ObjectEditor.Logging; +using OpenLoco.ObjectEditor.Data; +using Core.Objects; +using Core.Objects.Sound; +using Zenith.Core; + +namespace OpenLoco.ObjectEditor.DatFileParsing { public static class SawyerStreamReader { @@ -56,16 +59,26 @@ public static byte[] LoadBytesFromFile(string filename, ILogger? logger = null) } public static (S5Header s5Header, ObjectHeader objHeader, byte[] decodedData) LoadAndDecodeFromFile(string filename, ILogger? logger = null) - { - ReadOnlySpan fullData = LoadBytesFromFile(filename); + => LoadAndDecodeFromStream(LoadBytesFromFile(filename), logger); + public static (S5Header s5Header, ObjectHeader objHeader, byte[] decodedData) LoadAndDecodeFromStream(ReadOnlySpan fullData, ILogger? logger = null) + { var s5Header = S5Header.Read(fullData[0..S5Header.StructLength]); var remainingData = fullData[S5Header.StructLength..]; var objectHeader = ObjectHeader.Read(remainingData[0..ObjectHeader.StructLength]); remainingData = remainingData[ObjectHeader.StructLength..]; - var decodedData = Decode(objectHeader.Encoding, remainingData.ToArray()); + var decodedData = new byte[0]; + try + { + decodedData = Decode(objectHeader.Encoding, remainingData.ToArray()); + } + catch (InvalidDataException ex) + { + logger?.Error(ex); + return (s5Header, objectHeader, []); + } //remainingData = decodedData; var headerFlag = BitConverter.GetBytes(s5Header.Flags).AsSpan()[0..1]; @@ -80,21 +93,26 @@ public static (S5Header s5Header, ObjectHeader objHeader, byte[] decodedData) Lo } // load file - public static (DatFileInfo DatFileInfo, ILocoObject LocoObject) LoadFullObjectFromFile(string filename, bool loadExtra = true, ILogger? logger = null) + public static (DatFileInfo DatFileInfo, ILocoObject? LocoObject) LoadFullObjectFromFile(string filename, bool loadExtra = true, ILogger? logger = null) + => LoadFullObjectFromStream(File.ReadAllBytes(filename), filename, loadExtra, logger); + + public static (DatFileInfo DatFileInfo, ILocoObject? LocoObject) LoadFullObjectFromStream(ReadOnlySpan data, string filename = "", bool loadExtra = true, ILogger? logger = null) { logger?.Info($"Full-loading \"{filename}\" with loadExtra={loadExtra}"); - var (s5Header, objectHeader, decodedData) = LoadAndDecodeFromFile(filename, logger); - ReadOnlySpan remainingData = decodedData; + var (s5Header, objectHeader, decodedData) = LoadAndDecodeFromStream(data, logger); - var locoStruct = GetLocoStruct(s5Header.ObjectType, remainingData); - - if (locoStruct == null) + if (decodedData.Length == 0) { - Debugger.Break(); - throw new NullReferenceException($"{filename} was unable to be decoded"); + logger?.Warning($"No data was decoded from {filename}, file is malformed."); + return new(new DatFileInfo(s5Header, objectHeader), null); } + ReadOnlySpan remainingData = decodedData; + + var locoStruct = GetLocoStruct(s5Header.ObjectType, remainingData); + Verify.NotNull(locoStruct, paramName: filename); + var structSize = AttributeHelper.Get(locoStruct.GetType()); var locoStructSize = structSize!.Size; remainingData = remainingData[locoStructSize..]; @@ -114,7 +132,7 @@ public static (DatFileInfo DatFileInfo, ILocoObject LocoObject) LoadFullObjectFr { // some objects have graphics data var (_, imageTable, imageTableBytesRead) = LoadImageTable(remainingData); - logger?.Info($"FileLength={new FileInfo(filename).Length} HeaderLength={S5Header.StructLength} DataLength={objectHeader.DataLength} StringTableLength={stringTableBytesRead} ImageTableLength={imageTableBytesRead}"); + logger?.Info($"HeaderLength={S5Header.StructLength} DataLength={objectHeader.DataLength} StringTableLength={stringTableBytesRead} ImageTableLength={imageTableBytesRead}"); newObj = new LocoObject(locoStruct, stringTable, imageTable); } @@ -163,7 +181,10 @@ public static (StringTable table, int bytesRead) LoadStringTable(ReadOnlySpan fullData = LoadBytesFromFile(filename); var (g1Header, imageTable, imageTableBytesRead) = LoadImageTable(fullData); logger?.Info($"FileLength={new FileInfo(filename).Length} NumEntries={g1Header.NumEntries} TotalSize={g1Header.TotalSize} ImageTableLength={imageTableBytesRead}"); @@ -244,7 +267,7 @@ public static (G1Header header, List table, int bytesRead) LoadImag } } - return (g1Header, g1Element32s, g1ElementHeaders.Length + imageData.Length); + return (g1Header, g1Element32s, G1Header.StructLength + g1ElementHeaders.Length + imageData.Length); } public static byte[] DecodeRLEImageData(G1Element32 img) @@ -387,7 +410,7 @@ public static (RiffWavHeader header, byte[] data) LoadWavFile(byte[] data) var header = ByteReader.ReadLocoStruct(headerBytes); var pcmData = new byte[header.DataLength]; - br.Read(pcmData); + _ = br.Read(pcmData); return (header, pcmData); } } diff --git a/OpenLocoTool/DatFileParsing/SawyerStreamUtils.cs b/Core/DatFileParsing/SawyerStreamUtils.cs similarity index 93% rename from OpenLocoTool/DatFileParsing/SawyerStreamUtils.cs rename to Core/DatFileParsing/SawyerStreamUtils.cs index fa24849f..19c823ed 100644 --- a/OpenLocoTool/DatFileParsing/SawyerStreamUtils.cs +++ b/Core/DatFileParsing/SawyerStreamUtils.cs @@ -1,6 +1,6 @@ using System.Numerics; -namespace OpenLocoTool.DatFileParsing +namespace OpenLoco.ObjectEditor.DatFileParsing { public static class SawyerStreamUtils { diff --git a/OpenLocoTool/DatFileParsing/SawyerStreamWriter.cs b/Core/DatFileParsing/SawyerStreamWriter.cs similarity index 88% rename from OpenLocoTool/DatFileParsing/SawyerStreamWriter.cs rename to Core/DatFileParsing/SawyerStreamWriter.cs index fe2aa7f0..92fba025 100644 --- a/OpenLocoTool/DatFileParsing/SawyerStreamWriter.cs +++ b/Core/DatFileParsing/SawyerStreamWriter.cs @@ -1,25 +1,14 @@ using System.Text; -using OpenLocoTool.Data; -using OpenLocoTool.Headers; -using OpenLocoTool.Objects; -using OpenLocoTool.Types; -using OpenLocoToolCommon; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.Headers; +using OpenLoco.ObjectEditor.Types; +using OpenLoco.ObjectEditor.Logging; +using Core.Objects.Sound; -namespace OpenLocoTool.DatFileParsing +namespace OpenLoco.ObjectEditor.DatFileParsing { public static class SawyerStreamWriter { - //public static void ExportSoundEffectAsWave(string filename, WaveFormatEx header, byte[] pcmData) - //{ - // using (var stream = File.Create(filename)) - // { - // stream.Write(ByteWriter.WriteLocoStruct(header)); - // stream.Write(pcmData); - // stream.Flush(); - // stream.Close(); - // } - //} - public static RiffWavHeader WaveFormatExToRiff(WaveFormatEx hdr, int pcmDataLength) => new( 0x46464952, // "RIFF" @@ -65,19 +54,19 @@ public static byte[] SaveSoundEffectsToCSS(List<(RiffWavHeader header, byte[] da var currOffset = 4 + (sounds.Count * 4); // 4 for sound count, then 32 sounds each have a 4-byte offset. its always 33 * 4 = 132 to start. // sound offsets - foreach (var sfx in sounds) + foreach (var (header, data) in sounds) { br.Write((uint)currOffset); - currOffset += 4 + sfx.data.Length + ObjectAttributes.StructSize(); + currOffset += 4 + data.Length + ObjectAttributes.StructSize(); } // pcm data - foreach (var sfx in sounds) + foreach (var (header, data) in sounds) { - var waveHdr = RiffToWaveFormatEx(sfx.header); - br.Write((uint)sfx.data.Length); + var waveHdr = RiffToWaveFormatEx(header); + br.Write((uint)data.Length); br.Write(ByteWriter.WriteLocoStruct(waveHdr)); - br.Write(sfx.data); + br.Write(data); } ms.Flush(); @@ -113,8 +102,11 @@ public static void Save(string filepath, string objName, ILocoObject locoObject, } public static ReadOnlySpan WriteLocoObject(string objName, ILocoObject obj) + => WriteLocoObjectStream(objName, obj).ToArray(); + + public static MemoryStream WriteLocoObjectStream(string objName, ILocoObject obj) { - var objStream = new MemoryStream(); + using var objStream = new MemoryStream(); // obj var objBytes = ByteWriter.WriteLocoStruct(obj.Object); @@ -146,8 +138,8 @@ public static ReadOnlySpan WriteLocoObject(string objName, ILocoObject obj if (obj.G1Elements != null && obj.G1Elements.Count != 0) { // write G1Header - objStream.Write(BitConverter.GetBytes(obj.G1Elements.Count)); - objStream.Write(BitConverter.GetBytes(obj.G1Elements.Sum(x => G1Element32.StructLength + x.ImageData.Length))); + objStream.Write(BitConverter.GetBytes((uint32_t)obj.G1Elements.Count)); + objStream.Write(BitConverter.GetBytes((uint32_t)obj.G1Elements.Sum(x => G1Element32.StructLength + x.ImageData.Length))); var offsetBytesIntoImageData = 0; // write G1Element headers @@ -202,7 +194,7 @@ public static ReadOnlySpan WriteLocoObject(string objName, ILocoObject obj // obj header headerStream.Write(objHeader.Write()); - // loco object itself + // loco object itself, including string and graphics table headerStream.Write(objStream.ToArray()); // stream cleanup @@ -211,7 +203,7 @@ public static ReadOnlySpan WriteLocoObject(string objName, ILocoObject obj headerStream.Close(); objStream.Close(); - return headerStream.ToArray(); + return headerStream; } //public static ReadOnlySpan Encode(SawyerEncoding encoding, ReadOnlySpan data, ILogger? logger = null) diff --git a/OpenLocoTool/Data/Colour.cs b/Core/Data/Colour.cs similarity index 98% rename from OpenLocoTool/Data/Colour.cs rename to Core/Data/Colour.cs index 749cb194..63893f47 100644 --- a/OpenLocoTool/Data/Colour.cs +++ b/Core/Data/Colour.cs @@ -1,4 +1,4 @@ -namespace OpenLocoTool.Data +namespace OpenLoco.ObjectEditor.Data { public enum Colour : uint8_t { diff --git a/OpenLocoTool/Data/Constants.cs b/Core/Data/Constants.cs similarity index 80% rename from OpenLocoTool/Data/Constants.cs rename to Core/Data/Constants.cs index 4ce2ef54..42591ac2 100644 --- a/OpenLocoTool/Data/Constants.cs +++ b/Core/Data/Constants.cs @@ -1,4 +1,4 @@ -namespace OpenLocoTool +namespace OpenLoco.ObjectEditor.Data { public static class Constants { diff --git a/OpenLocoTool/Data/ImageIds.cs b/Core/Data/ImageIds.cs similarity index 76% rename from OpenLocoTool/Data/ImageIds.cs rename to Core/Data/ImageIds.cs index 6f1a6717..6f14cad5 100644 --- a/OpenLocoTool/Data/ImageIds.cs +++ b/Core/Data/ImageIds.cs @@ -1,4 +1,4 @@ -namespace OpenLocoTool +namespace OpenLoco.ObjectEditor.Data { public static class ImageIds { diff --git a/OpenLocoTool/Data/LanguageId.cs b/Core/Data/LanguageId.cs similarity index 86% rename from OpenLocoTool/Data/LanguageId.cs rename to Core/Data/LanguageId.cs index db66bac9..44fb6bec 100644 --- a/OpenLocoTool/Data/LanguageId.cs +++ b/Core/Data/LanguageId.cs @@ -1,4 +1,4 @@ -namespace OpenLocoTool +namespace OpenLoco.ObjectEditor.Data { public enum LanguageId : uint8_t { diff --git a/OpenLocoTool/Data/ObjectType.cs b/Core/Data/ObjectType.cs similarity index 91% rename from OpenLocoTool/Data/ObjectType.cs rename to Core/Data/ObjectType.cs index dcbf0e1d..232b21b4 100644 --- a/OpenLocoTool/Data/ObjectType.cs +++ b/Core/Data/ObjectType.cs @@ -1,4 +1,4 @@ -namespace OpenLocoTool.Headers +namespace OpenLoco.ObjectEditor.Data { public enum ObjectType : byte { diff --git a/OpenLocoTool/Data/OriginalDataFiles.cs b/Core/Data/OriginalDataFiles.cs similarity index 98% rename from OpenLocoTool/Data/OriginalDataFiles.cs rename to Core/Data/OriginalDataFiles.cs index e0b367de..2f0ea400 100644 --- a/OpenLocoTool/Data/OriginalDataFiles.cs +++ b/Core/Data/OriginalDataFiles.cs @@ -1,4 +1,4 @@ -namespace OpenLocoTool +namespace OpenLoco.ObjectEditor.Data { public static class OriginalDataFiles { diff --git a/OpenLocoTool/Data/OriginalDataPathIds.cs b/Core/Data/OriginalDataPathIds.cs similarity index 96% rename from OpenLocoTool/Data/OriginalDataPathIds.cs rename to Core/Data/OriginalDataPathIds.cs index fa1a2896..c7e17149 100644 --- a/OpenLocoTool/Data/OriginalDataPathIds.cs +++ b/Core/Data/OriginalDataPathIds.cs @@ -1,4 +1,4 @@ -namespace OpenLocoTool +namespace OpenLoco.ObjectEditor.Data { // these are indexed to the strings in OriginalDateFiles.cs public enum OriginalDataPathIds : uint8_t diff --git a/OpenLocoTool/Data/OriginalObjectFiles.cs b/Core/Data/OriginalObjectFiles.cs similarity index 99% rename from OpenLocoTool/Data/OriginalObjectFiles.cs rename to Core/Data/OriginalObjectFiles.cs index 61453869..bfc76f2e 100644 --- a/OpenLocoTool/Data/OriginalObjectFiles.cs +++ b/Core/Data/OriginalObjectFiles.cs @@ -1,4 +1,4 @@ -namespace OpenLocoTool +namespace OpenLoco.ObjectEditor.Data { public static class OriginalObjectFiles { diff --git a/OpenLocoTool/Data/SawyerEncoding.cs b/Core/Data/SawyerEncoding.cs similarity index 75% rename from OpenLocoTool/Data/SawyerEncoding.cs rename to Core/Data/SawyerEncoding.cs index 7d2ac2db..c4fbb2ea 100644 --- a/OpenLocoTool/Data/SawyerEncoding.cs +++ b/Core/Data/SawyerEncoding.cs @@ -1,4 +1,4 @@ -namespace OpenLocoTool +namespace OpenLoco.ObjectEditor.Data { public enum SawyerEncoding : byte { diff --git a/OpenLocoTool/Data/SoundId.cs b/Core/Data/SoundId.cs similarity index 94% rename from OpenLocoTool/Data/SoundId.cs rename to Core/Data/SoundId.cs index 322fc95e..c4cae24d 100644 --- a/OpenLocoTool/Data/SoundId.cs +++ b/Core/Data/SoundId.cs @@ -1,4 +1,4 @@ -namespace OpenLocoTool +namespace OpenLoco.ObjectEditor.Data { public enum SoundId : uint16_t { diff --git a/OpenLocoTool/Data/SourceGame.cs b/Core/Data/SourceGame.cs similarity index 68% rename from OpenLocoTool/Data/SourceGame.cs rename to Core/Data/SourceGame.cs index 5138246a..771887f5 100644 --- a/OpenLocoTool/Data/SourceGame.cs +++ b/Core/Data/SourceGame.cs @@ -1,4 +1,4 @@ -namespace OpenLocoTool.Data +namespace OpenLoco.ObjectEditor.Data { public enum SourceGame : byte { diff --git a/OpenLocoTool/IEnumerableExtensions.cs b/Core/IEnumerableExtensions.cs similarity index 60% rename from OpenLocoTool/IEnumerableExtensions.cs rename to Core/IEnumerableExtensions.cs index 8c56bc44..f73159dc 100644 --- a/OpenLocoTool/IEnumerableExtensions.cs +++ b/Core/IEnumerableExtensions.cs @@ -1,8 +1,8 @@ -namespace OpenLocoTool +namespace OpenLoco.ObjectEditor { public static class IEnumerableExtensions { - public static IEnumerable Fill(this IEnumerable source, int minLength, T fillValue = default) + public static IEnumerable Fill(this IEnumerable source, int minLength, T? fillValue = default) { var i = 0; foreach (var item in source) diff --git a/OpenLocoTool/ObjectManager.cs b/Core/ObjectManager.cs similarity index 81% rename from OpenLocoTool/ObjectManager.cs rename to Core/ObjectManager.cs index ba90699a..a21c4404 100644 --- a/OpenLocoTool/ObjectManager.cs +++ b/Core/ObjectManager.cs @@ -1,7 +1,7 @@ -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool +namespace OpenLoco.ObjectEditor { public static class SObjectManager { diff --git a/OpenLocoTool/Objects/AirportObject.cs b/Core/Objects/Airport/AirportObject.cs similarity index 81% rename from OpenLocoTool/Objects/AirportObject.cs rename to Core/Objects/Airport/AirportObject.cs index 0ec18c69..ffb8af5f 100644 --- a/OpenLocoTool/Objects/AirportObject.cs +++ b/Core/Objects/Airport/AirportObject.cs @@ -1,8 +1,8 @@ using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Objects +namespace Core.Objects { public enum AirportMovementNodeFlags : uint16_t { @@ -18,26 +18,6 @@ public enum AirportMovementNodeFlags : uint16_t Touchdown = 1 << 8, }; - [TypeConverter(typeof(ExpandableObjectConverter))] - [LocoStructSize(0x08)] - public record MovementNode( - [property: LocoStructOffset(0x00)] int16_t X, - [property: LocoStructOffset(0x02)] int16_t Y, - [property: LocoStructOffset(0x04)] int16_t Z, - [property: LocoStructOffset(0x06)] AirportMovementNodeFlags Flags - ) : ILocoStruct; - - [TypeConverter(typeof(ExpandableObjectConverter))] - [LocoStructSize(0x0C)] - public record MovementEdge( - [property: LocoStructOffset(0x00)] uint8_t var_00, - [property: LocoStructOffset(0x01)] uint8_t CurrNode, - [property: LocoStructOffset(0x02)] uint8_t NextNode, - [property: LocoStructOffset(0x03)] uint8_t var_03, - [property: LocoStructOffset(0x04)] uint32_t MustBeClearEdges, // Which edges must be clear to use the transition edge. should probably be some kind of flags? - [property: LocoStructOffset(0x08)] uint32_t AtLeastOneClearEdges // Which edges must have at least one clear to use transition edge. should probably be some kind of flags? - ) : ILocoStruct; - [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0xBA)] [LocoStructType(ObjectType.Airport)] @@ -67,8 +47,8 @@ public record AirportObject( [property: LocoStructOffset(0xAC)] uint8_t NumMovementNodes, [property: LocoStructOffset(0xAD)] uint8_t NumMovementEdges, [property: LocoStructOffset(0xAE), LocoStructVariableLoad] List MovementNodes, - [property: LocoStructOffset(0xB2), LocoStructVariableLoad] List MovementEdges - //[property: LocoStructOffset(0xB6), LocoArrayLength(0xBA - 0xB6)] uint8_t[] pad_B6 + [property: LocoStructOffset(0xB2), LocoStructVariableLoad] List MovementEdges, + [property: LocoStructOffset(0xB6), LocoArrayLength(0xBA - 0xB6)] uint8_t[] pad_B6 ) : ILocoStruct, ILocoStructVariableData { public const int VariationPartCount = 32; @@ -91,7 +71,11 @@ public ReadOnlySpan Load(ReadOnlySpan remainingData) for (var i = 0; i < NumBuildingVariations; ++i) { var ptr_1C = 0; - while (remainingData[++ptr_1C] != 0xFF) ; + while (remainingData[++ptr_1C] != 0xFF) + { + ; + } + BuildingVariationParts.Add(remainingData[..ptr_1C].ToArray()); ptr_1C++; remainingData = remainingData[ptr_1C..]; diff --git a/Core/Objects/Airport/MovementEdge.cs b/Core/Objects/Airport/MovementEdge.cs new file mode 100644 index 00000000..f03269e6 --- /dev/null +++ b/Core/Objects/Airport/MovementEdge.cs @@ -0,0 +1,16 @@ +using System.ComponentModel; +using OpenLoco.ObjectEditor.DatFileParsing; + +namespace Core.Objects +{ + [TypeConverter(typeof(ExpandableObjectConverter))] + [LocoStructSize(0x0C)] + public record MovementEdge( + [property: LocoStructOffset(0x00)] uint8_t var_00, + [property: LocoStructOffset(0x01)] uint8_t CurrNode, + [property: LocoStructOffset(0x02)] uint8_t NextNode, + [property: LocoStructOffset(0x03)] uint8_t var_03, + [property: LocoStructOffset(0x04)] uint32_t MustBeClearEdges, // Which edges must be clear to use the transition edge. should probably be some kind of flags? + [property: LocoStructOffset(0x08)] uint32_t AtLeastOneClearEdges // Which edges must have at least one clear to use transition edge. should probably be some kind of flags? + ) : ILocoStruct; +} diff --git a/Core/Objects/Airport/MovementNode.cs b/Core/Objects/Airport/MovementNode.cs new file mode 100644 index 00000000..7bd02d4e --- /dev/null +++ b/Core/Objects/Airport/MovementNode.cs @@ -0,0 +1,14 @@ +using System.ComponentModel; +using OpenLoco.ObjectEditor.DatFileParsing; + +namespace Core.Objects +{ + [TypeConverter(typeof(ExpandableObjectConverter))] + [LocoStructSize(0x08)] + public record MovementNode( + [property: LocoStructOffset(0x00)] int16_t X, + [property: LocoStructOffset(0x02)] int16_t Y, + [property: LocoStructOffset(0x04)] int16_t Z, + [property: LocoStructOffset(0x06)] AirportMovementNodeFlags Flags + ) : ILocoStruct; +} diff --git a/OpenLocoTool/Objects/BridgeObject.cs b/Core/Objects/BridgeObject.cs similarity index 94% rename from OpenLocoTool/Objects/BridgeObject.cs rename to Core/Objects/BridgeObject.cs index 314ba14f..2be3f8f9 100644 --- a/OpenLocoTool/Objects/BridgeObject.cs +++ b/Core/Objects/BridgeObject.cs @@ -1,8 +1,9 @@ using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; +using OpenLoco.ObjectEditor.Headers; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0x2C)] diff --git a/OpenLocoTool/Objects/BuildingObject.cs b/Core/Objects/BuildingObject.cs similarity index 94% rename from OpenLocoTool/Objects/BuildingObject.cs rename to Core/Objects/BuildingObject.cs index 09fbe3e1..ce5eb93a 100644 --- a/OpenLocoTool/Objects/BuildingObject.cs +++ b/Core/Objects/BuildingObject.cs @@ -1,9 +1,10 @@ using System.ComponentModel; -using OpenLocoTool.Data; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; +using Core.Objects; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; +using OpenLoco.ObjectEditor.Headers; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [Flags] public enum BuildingObjectFlags : uint8_t @@ -79,7 +80,11 @@ public ReadOnlySpan Load(ReadOnlySpan remainingData) for (var i = 0; i < NumVariations; ++i) { var ptr_10 = 0; - while (remainingData[++ptr_10] != 0xFF) ; + while (remainingData[++ptr_10] != 0xFF) + { + ; + } + VariationParts.Add(remainingData[..ptr_10].ToArray()); ptr_10++; remainingData = remainingData[ptr_10..]; @@ -126,6 +131,13 @@ public ReadOnlySpan Save() ms.WriteByte(x.AnimationSpeed); } + // variation parts + foreach (var x in VariationParts) + { + ms.Write(x); + ms.WriteByte(0xFF); + } + // produced cargo foreach (var obj in ProducedCargo.Fill(MaxProducedCargoType, S5Header.NullHeader)) { diff --git a/OpenLocoTool/Objects/CargoObject.cs b/Core/Objects/CargoObject.cs similarity index 94% rename from OpenLocoTool/Objects/CargoObject.cs rename to Core/Objects/CargoObject.cs index 35267057..09057d41 100644 --- a/OpenLocoTool/Objects/CargoObject.cs +++ b/Core/Objects/CargoObject.cs @@ -1,8 +1,8 @@ using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { public enum CargoObjectFlags : uint8_t { diff --git a/OpenLocoTool/Objects/CliffEdgeObject.cs b/Core/Objects/CliffEdgeObject.cs similarity index 57% rename from OpenLocoTool/Objects/CliffEdgeObject.cs rename to Core/Objects/CliffEdgeObject.cs index ec78bc17..2ca06c7d 100644 --- a/OpenLocoTool/Objects/CliffEdgeObject.cs +++ b/Core/Objects/CliffEdgeObject.cs @@ -1,14 +1,15 @@ using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0x06)] [LocoStructType(ObjectType.CliffEdge)] [LocoStringTable("Name")] public record CliffEdgeObject( - [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name + [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, + [property: LocoStructOffset(0x02), LocoString, Browsable(false)] image_id Image ) : ILocoStruct; } diff --git a/OpenLocoTool/Objects/ClimateObject.cs b/Core/Objects/ClimateObject.cs similarity index 85% rename from OpenLocoTool/Objects/ClimateObject.cs rename to Core/Objects/ClimateObject.cs index d44a7fe2..5c4c9b23 100644 --- a/OpenLocoTool/Objects/ClimateObject.cs +++ b/Core/Objects/ClimateObject.cs @@ -1,8 +1,8 @@ using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0x0A)] diff --git a/OpenLocoTool/Objects/CompetitorObject.cs b/Core/Objects/CompetitorObject.cs similarity index 89% rename from OpenLocoTool/Objects/CompetitorObject.cs rename to Core/Objects/CompetitorObject.cs index c81cb48d..cbe9feb0 100644 --- a/OpenLocoTool/Objects/CompetitorObject.cs +++ b/Core/Objects/CompetitorObject.cs @@ -1,8 +1,8 @@ using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0x38)] diff --git a/OpenLocoTool/Objects/CurrencyObject.cs b/Core/Objects/CurrencyObject.cs similarity index 85% rename from OpenLocoTool/Objects/CurrencyObject.cs rename to Core/Objects/CurrencyObject.cs index c3a4299b..273eb3fe 100644 --- a/OpenLocoTool/Objects/CurrencyObject.cs +++ b/Core/Objects/CurrencyObject.cs @@ -1,8 +1,8 @@ using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0x0C)] diff --git a/Core/Objects/DockObject.cs b/Core/Objects/DockObject.cs new file mode 100644 index 00000000..6c6ceaf3 --- /dev/null +++ b/Core/Objects/DockObject.cs @@ -0,0 +1,82 @@ +using System.ComponentModel; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; +using OpenLoco.ObjectEditor.Types; + +namespace OpenLoco.ObjectEditor.Objects +{ + [Flags] + public enum DockObjectFlags : uint16_t + { + None = 0, + unk01 = 1 << 0, + }; + + [TypeConverter(typeof(ExpandableObjectConverter))] + [LocoStructSize(0x28)] + [LocoStructType(ObjectType.Dock)] + [LocoStringTable("Name")] + public record DockObject( + [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, + [property: LocoStructOffset(0x02)] int16_t BuildCostFactor, + [property: LocoStructOffset(0x04)] int16_t SellCostFactor, + [property: LocoStructOffset(0x06)] uint8_t CostIndex, + [property: LocoStructOffset(0x07), LocoPropertyMaybeUnused] uint8_t var_07, + [property: LocoStructOffset(0x08), Browsable(false)] image_id Image, + [property: LocoStructOffset(0x0C), Browsable(false)] image_id UnkImage, + [property: LocoStructOffset(0x10)] DockObjectFlags Flags, + [property: LocoStructOffset(0x12), LocoPropertyMaybeUnused] uint8_t NumAux01, + [property: LocoStructOffset(0x13), LocoPropertyMaybeUnused] uint8_t NumAux02Ent, + [property: LocoStructOffset(0x14), LocoStructVariableLoad] List var_14, + [property: LocoStructOffset(0x18), LocoStructVariableLoad] List var_18, + [property: LocoStructOffset(0x1C), LocoStructVariableLoad] List var_1C, + [property: LocoStructOffset(0x20)] + uint16_t DesignedYear, + [property: LocoStructOffset(0x22)] uint16_t ObsoleteYear, + [property: LocoStructOffset(0x24)] Pos2 BoatPosition + ) : ILocoStruct, ILocoStructVariableData + { + public ReadOnlySpan Load(ReadOnlySpan remainingData) + { + var_14.Clear(); + var_18.Clear(); + var_1C.Clear(); + + // var_14 - a list of uint8_t + var_14.AddRange(remainingData[..(NumAux01 * 1)]); + remainingData = remainingData[(NumAux01 * 1)..]; // sizeof(uint8_t) + + // var_18 - a list of uint16_t + var bytearr = remainingData[..(NumAux01 * 2)].ToArray(); + for (var i = 0; i < NumAux01; ++i) + { + var_18.Add(BitConverter.ToUInt16(bytearr, i * 2)); // sizeof(uint16_t) + } + + remainingData = remainingData[(NumAux01 * 2)..]; // sizeof(uint16_t) + + // parts + for (var i = 0; i < NumAux02Ent; ++i) + { + var ptr_1C = 0; + while (remainingData[ptr_1C] != 0xFF) + { + var_1C.Add(remainingData[ptr_1C]); + ptr_1C++; + } + + ptr_1C++; + remainingData = remainingData[ptr_1C..]; + } + + return remainingData; + } + + public ReadOnlySpan Save() + => var_14 + .Concat(var_18.SelectMany(BitConverter.GetBytes)) + .Concat(var_1C) + .Concat(new byte[] { 0xFF }) + .ToArray(); + } +} diff --git a/OpenLocoTool/Objects/HillShapesObject.cs b/Core/Objects/HillShapesObject.cs similarity index 85% rename from OpenLocoTool/Objects/HillShapesObject.cs rename to Core/Objects/HillShapesObject.cs index 12bde258..d5fd885d 100644 --- a/OpenLocoTool/Objects/HillShapesObject.cs +++ b/Core/Objects/HillShapesObject.cs @@ -1,8 +1,8 @@ using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0x0E)] diff --git a/Core/Objects/Industry/BuildingPartAnimation.cs b/Core/Objects/Industry/BuildingPartAnimation.cs new file mode 100644 index 00000000..31b537fe --- /dev/null +++ b/Core/Objects/Industry/BuildingPartAnimation.cs @@ -0,0 +1,13 @@ +using System.ComponentModel; +using OpenLoco.ObjectEditor.DatFileParsing; + +namespace Core.Objects +{ + [TypeConverter(typeof(ExpandableObjectConverter))] + [LocoStructSize(0x02)] + + public record BuildingPartAnimation( + [property: LocoStructOffset(0x00)] uint8_t NumFrames, // Must be a power of 2 (0 = no part animation, could still have animation sequence) + [property: LocoStructOffset(0x01)] uint8_t AnimationSpeed // Also encodes in bit 7 if the animation is position modified + ) : ILocoStruct; +} diff --git a/OpenLocoTool/Objects/IndustryObject.cs b/Core/Objects/Industry/IndustryObject.cs similarity index 76% rename from OpenLocoTool/Objects/IndustryObject.cs rename to Core/Objects/Industry/IndustryObject.cs index 569520a9..c59aba1c 100644 --- a/OpenLocoTool/Objects/IndustryObject.cs +++ b/Core/Objects/Industry/IndustryObject.cs @@ -1,9 +1,10 @@ using System.ComponentModel; -using OpenLocoTool.Data; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; +using OpenLoco.ObjectEditor.Headers; -namespace OpenLocoTool.Objects +namespace Core.Objects { [Flags] public enum IndustryObjectFlags : uint32_t @@ -40,47 +41,23 @@ public enum IndustryObjectFlags : uint32_t unk28 = 1 << 28, } - [TypeConverter(typeof(ExpandableObjectConverter))] - [LocoStructSize(0x02)] - - public record BuildingPartAnimation( - [property: LocoStructOffset(0x00)] uint8_t NumFrames, // Must be a power of 2 (0 = no part animation, could still have animation sequence) - [property: LocoStructOffset(0x01)] uint8_t AnimationSpeed // Also encodes in bit 7 if the animation is position modified - ) : ILocoStruct; - - [TypeConverter(typeof(ExpandableObjectConverter))] - [LocoStructSize(0x02)] - - public record IndustryObjectUnk38( - [property: LocoStructOffset(0x00)] uint8_t var_00, - [property: LocoStructOffset(0x01)] uint8_t var_01 - ) : ILocoStruct; - - [TypeConverter(typeof(ExpandableObjectConverter))] - [LocoStructSize(0x04)] - - public record IndustryObjectProductionRateRange( - [property: LocoStructOffset(0x00)] uint16_t Min, - [property: LocoStructOffset(0x02)] uint16_t Max - ) : ILocoStruct; - [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0xF4)] [LocoStructType(ObjectType.Industry)] [LocoStringTable("Name", "var_02", "", "NameClosingDown", "NameUpProduction", "NameDownProduction", "NameSingular", "NamePlural")] public record IndustryObject( - //[property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, - //[property: LocoStructOffset(0x02), LocoString, Browsable(false)] string_id var_02, - //[property: LocoStructOffset(0x02), LocoString, Browsable(false)] string_id _unused, - //[property: LocoStructOffset(0x04), LocoString, Browsable(false)] string_id NameClosingDown, - //[property: LocoStructOffset(0x06), LocoString, Browsable(false)] string_id NameUpProduction, - //[property: LocoStructOffset(0x08), LocoString, Browsable(false)] string_id NameDownProduction, - //[property: LocoStructOffset(0x0A), LocoString, Browsable(false)] string_id NameSingular, - //[property: LocoStructOffset(0x0C), LocoString, Browsable(false)] string_id NamePlural, - //[property: LocoStructOffset(0x0E)] image_id var_0E, // shadows image id base - //[property: LocoStructOffset(0x12)] image_id var_12, // Base image id for building 0 - //[property: LocoStructOffset(0x16)] image_id var_16, - //[property: LocoStructOffset(0x1A)] image_id var_1A, + [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, + [property: LocoStructOffset(0x02), LocoString, Browsable(false)] string_id var_02, + [property: LocoStructOffset(0x02), LocoString, Browsable(false)] string_id _unused, + [property: LocoStructOffset(0x04), LocoString, Browsable(false)] string_id NameClosingDown, + [property: LocoStructOffset(0x06), LocoString, Browsable(false)] string_id NameUpProduction, + [property: LocoStructOffset(0x08), LocoString, Browsable(false)] string_id NameDownProduction, + [property: LocoStructOffset(0x0A), LocoString, Browsable(false)] string_id NameSingular, + [property: LocoStructOffset(0x0C), LocoString, Browsable(false)] string_id NamePlural, + [property: LocoStructOffset(0x0E), Browsable(false)] image_id var_0E, // shadows image id base + [property: LocoStructOffset(0x12), Browsable(false)] image_id var_12, // Base image id for building 0 + [property: LocoStructOffset(0x16), Browsable(false)] image_id var_16, + [property: LocoStructOffset(0x1A), Browsable(false)] image_id var_1A, [property: LocoStructOffset(0x1E)] uint8_t NumBuildingAnimations, [property: LocoStructOffset(0x1F)] uint8_t NumBuildingVariations, [property: LocoStructOffset(0x20), LocoStructVariableLoad] List BuildingVariationHeights, // This is the height of a building image @@ -104,7 +81,7 @@ public record IndustryObject( [property: LocoStructOffset(0xD6), LocoArrayLength(IndustryObject.InitialProductionRateCount)] IndustryObjectProductionRateRange[] InitialProductionRate, [property: LocoStructOffset(0xDE), LocoStructVariableLoad, LocoArrayLength(IndustryObject.MaxProducedCargoType)] List ProducedCargo, // (0xFF = null) [property: LocoStructOffset(0xE0), LocoStructVariableLoad, LocoArrayLength(IndustryObject.MaxRequiredCargoType)] List RequiredCargo, // (0xFF = null) - //[property: LocoStructOffset(0xE3)] uint8_t pad_E3, + [property: LocoStructOffset(0xE3), Browsable(false)] uint8_t pad_E3, [property: LocoStructOffset(0xE4)] IndustryObjectFlags Flags, [property: LocoStructOffset(0xE8)] uint8_t var_E8, [property: LocoStructOffset(0xE9)] uint8_t var_E9, @@ -112,8 +89,8 @@ public record IndustryObject( [property: LocoStructOffset(0xEB)] uint8_t var_EB, [property: LocoStructOffset(0xEC)] uint8_t var_EC, [property: LocoStructOffset(0xED), LocoStructVariableLoad, LocoArrayLength(IndustryObject.WallTypeCount)] List WallTypes, // There can be up to 4 different wall types for an industry - //[property: LocoStructOffset(0xF1), LocoStructVariableLoad] object_id BuildingWall, // Selection of wall types isn't completely random from the 4 it is biased into 2 groups of 2 (wall and entrance) - //[property: LocoStructOffset(0xF2), LocoStructVariableLoad] object_id BuildingWallEntrance, // An alternative wall type that looks like a gate placed at random places in building perimeter + [property: LocoStructOffset(0xF1), LocoStructVariableLoad] object_id _BuildingWall, // Selection of wall types isn't completely random from the 4 it is biased into 2 groups of 2 (wall and entrance) + [property: LocoStructOffset(0xF2), LocoStructVariableLoad] object_id _BuildingWallEntrance, // An alternative wall type that looks like a gate placed at random places in building perimeter [property: LocoStructOffset(0xF3)] uint8_t var_F3 ) : ILocoStruct, ILocoStructVariableData { @@ -124,7 +101,7 @@ public record IndustryObject( public const int VariationPartCount = 32; public const int WallTypeCount = 4; - public List UnkIndustry38 { get; set; } = []; + //public List UnkIndustry38 { get; set; } = []; public S5Header BuildingWall { get; set; } @@ -160,11 +137,11 @@ public ReadOnlySpan Load(ReadOnlySpan remainingData) } // unk animation related - UnkIndustry38.Clear(); + var_38.Clear(); var structSize = ObjectAttributes.StructSize(); while (remainingData[0] != 0xFF) { - UnkIndustry38.Add(ByteReader.ReadLocoStruct(remainingData[..structSize])); + var_38.Add(ByteReader.ReadLocoStruct(remainingData[..structSize])); remainingData = remainingData[structSize..]; } @@ -175,7 +152,11 @@ public ReadOnlySpan Load(ReadOnlySpan remainingData) for (var i = 0; i < NumBuildingVariations; ++i) { var ptr_1F = 0; - while (remainingData[++ptr_1F] != 0xFF) ; + while (remainingData[++ptr_1F] != 0xFF) + { + ; + } + BuildingVariationParts.Add(remainingData[..ptr_1F].ToArray()); ptr_1F++; remainingData = remainingData[ptr_1F..]; @@ -237,7 +218,7 @@ public ReadOnlySpan Save() } // unk animation related - foreach (var x in UnkIndustry38) + foreach (var x in var_38) { ms.WriteByte(x.var_00); ms.WriteByte(x.var_01); diff --git a/Core/Objects/Industry/IndustryObjectProductionRateRange.cs b/Core/Objects/Industry/IndustryObjectProductionRateRange.cs new file mode 100644 index 00000000..52483812 --- /dev/null +++ b/Core/Objects/Industry/IndustryObjectProductionRateRange.cs @@ -0,0 +1,13 @@ +using System.ComponentModel; +using OpenLoco.ObjectEditor.DatFileParsing; + +namespace Core.Objects +{ + [TypeConverter(typeof(ExpandableObjectConverter))] + [LocoStructSize(0x04)] + + public record IndustryObjectProductionRateRange( + [property: LocoStructOffset(0x00)] uint16_t Min, + [property: LocoStructOffset(0x02)] uint16_t Max + ) : ILocoStruct; +} diff --git a/Core/Objects/Industry/IndustryObjectUnk38.cs b/Core/Objects/Industry/IndustryObjectUnk38.cs new file mode 100644 index 00000000..4e1fe82f --- /dev/null +++ b/Core/Objects/Industry/IndustryObjectUnk38.cs @@ -0,0 +1,13 @@ +using System.ComponentModel; +using OpenLoco.ObjectEditor.DatFileParsing; + +namespace Core.Objects +{ + [TypeConverter(typeof(ExpandableObjectConverter))] + [LocoStructSize(0x02)] + + public record IndustryObjectUnk38( + [property: LocoStructOffset(0x00)] uint8_t var_00, + [property: LocoStructOffset(0x01)] uint8_t var_01 + ) : ILocoStruct; +} diff --git a/OpenLocoTool/Objects/InterfaceSkinObject.cs b/Core/Objects/InterfaceSkinObject.cs similarity index 99% rename from OpenLocoTool/Objects/InterfaceSkinObject.cs rename to Core/Objects/InterfaceSkinObject.cs index e22ff8ce..40e64bd6 100644 --- a/OpenLocoTool/Objects/InterfaceSkinObject.cs +++ b/Core/Objects/InterfaceSkinObject.cs @@ -1,10 +1,9 @@ using System.ComponentModel; -using OpenLocoTool.Data; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; -using OpenLocoTool.Types; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; +using OpenLoco.ObjectEditor.Types; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0x18)] diff --git a/Core/Objects/LandObject.cs b/Core/Objects/LandObject.cs new file mode 100644 index 00000000..75fe578d --- /dev/null +++ b/Core/Objects/LandObject.cs @@ -0,0 +1,76 @@ + +using System.ComponentModel; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; +using OpenLoco.ObjectEditor.Headers; + +namespace OpenLoco.ObjectEditor.Objects +{ + [Flags] + public enum LandObjectFlags : uint8_t + { + None = 0, + unk0 = 1 << 0, + unk1 = 1 << 1, + IsDesert = 1 << 2, + NoTrees = 1 << 3, + }; + + [TypeConverter(typeof(ExpandableObjectConverter))] + [LocoStructSize(0x1E)] + [LocoStructType(ObjectType.Land)] + [LocoStringTable("Name")] + public record LandObject( + [property: LocoStructOffset(0x02)] uint8_t CostIndex, + [property: LocoStructOffset(0x03)] uint8_t var_03, + [property: LocoStructOffset(0x04), LocoPropertyMaybeUnused] uint8_t var_04, + [property: LocoStructOffset(0x05)] LandObjectFlags Flags, + [property: LocoStructOffset(0x06), Browsable(false)] object_id CliffEdgeHeader1, + [property: LocoStructOffset(0x07), Browsable(false), LocoPropertyMaybeUnused] object_id CliffEdgeHeader2, + [property: LocoStructOffset(0x08)] int8_t CostFactor, + [property: LocoStructOffset(0x09), Browsable(false)] uint8_t pad_09, + [property: LocoStructOffset(0x0A), Browsable(false)] image_id Image, + [property: LocoStructOffset(0x0E), Browsable(false)] image_id var_0E, + [property: LocoStructOffset(0x12), Browsable(false)] image_id CliffEdgeImage, + [property: LocoStructOffset(0x16), Browsable(false)] image_id MapPixelImage, + [property: LocoStructOffset(0x1A), Browsable(false)] uint8_t pad_1A, + [property: LocoStructOffset(0x1B)] uint8_t NumVariations, + [property: LocoStructOffset(0x1C)] uint8_t VariationLikelihood, + [property: LocoStructOffset(0x1D), Browsable(false)] uint8_t pad_1D + ) : ILocoStruct, ILocoStructVariableData + { + public S5Header CliffEdgeHeader { get; set; } + public S5Header UnkObjHeader { get; set; } + + public ReadOnlySpan Load(ReadOnlySpan remainingData) + { + // cliff edge header + CliffEdgeHeader = S5Header.Read(remainingData[..S5Header.StructLength]); + remainingData = remainingData[S5Header.StructLength..]; + + // unused obj + if (Flags.HasFlag(LandObjectFlags.unk1)) + { + UnkObjHeader = S5Header.Read(remainingData[..S5Header.StructLength]); + remainingData = remainingData[S5Header.StructLength..]; + } + + return remainingData; + } + + public ReadOnlySpan Save() + { + var variableDataSize = S5Header.StructLength + (Flags.HasFlag(LandObjectFlags.unk1) ? S5Header.StructLength : 0); + + var data = new byte[variableDataSize]; + data = [.. CliffEdgeHeader.Write()]; + + if (Flags.HasFlag(LandObjectFlags.unk1)) + { + UnkObjHeader.Write().CopyTo(data.AsSpan()[S5Header.StructLength..]); + } + + return data; + } + } +} diff --git a/OpenLocoTool/Objects/LevelCrossingObject.cs b/Core/Objects/LevelCrossingObject.cs similarity index 88% rename from OpenLocoTool/Objects/LevelCrossingObject.cs rename to Core/Objects/LevelCrossingObject.cs index b41bf6c5..36502f48 100644 --- a/OpenLocoTool/Objects/LevelCrossingObject.cs +++ b/Core/Objects/LevelCrossingObject.cs @@ -1,9 +1,9 @@  using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0x12)] diff --git a/OpenLocoTool/Objects/RegionObject.cs b/Core/Objects/RegionObject.cs similarity index 59% rename from OpenLocoTool/Objects/RegionObject.cs rename to Core/Objects/RegionObject.cs index 2af5fd9a..259aac59 100644 --- a/OpenLocoTool/Objects/RegionObject.cs +++ b/Core/Objects/RegionObject.cs @@ -1,26 +1,24 @@ - -using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; +using System.ComponentModel; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; +using OpenLoco.ObjectEditor.Headers; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0x12)] [LocoStructType(ObjectType.Region)] [LocoStringTable("Name")] - public class RegionObject( - uint8_t requiredObjectCount) - : ILocoStruct, ILocoStructVariableData + public record RegionObject( + [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, + [property: LocoStructOffset(0x02), Browsable(false)] image_id Image, + [property: LocoStructOffset(0x06), LocoArrayLength(0x8 - 0x6), Browsable(false)] uint8_t[] pad_06, + [property: LocoStructOffset(0x08), Browsable(false)] uint8_t RequiredObjectCount, + [property: LocoStructOffset(0x09), LocoArrayLength(RegionObject.MaxRequiredObjects), LocoStructVariableLoad, Browsable(false)] object_id[] _RequiredObjects, + [property: LocoStructOffset(0x0D), LocoArrayLength(0x12 - 0xD), Browsable(false)] uint8_t[] pad_0D + ) : ILocoStruct, ILocoStructVariableData { - - //[property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, - //[property: LocoStructOffset(0x02)] image_id Image, - //[property: LocoStructOffset(0x06), LocoArrayLength(0x8 - 0x6)] uint8_t[] pad_06, - [property: LocoStructOffset(0x08)] public uint8_t RequiredObjectCount { get; set; } = requiredObjectCount; - //[property: LocoStructOffset(0x09), LocoArrayLength(4)] public object_index[] requiredObjects { get; set; } - //[property: LocoStructOffset(0x0D), LocoArrayLength(0x12 - 0xD)] uint8_t[] pad_0D - + public const int MaxRequiredObjects = 4; public List RequiredObjects { get; set; } = []; public List DependentObjects { get; set; } = []; diff --git a/Core/Objects/RoadExtraObject.cs b/Core/Objects/RoadExtraObject.cs new file mode 100644 index 00000000..ea9be815 --- /dev/null +++ b/Core/Objects/RoadExtraObject.cs @@ -0,0 +1,21 @@ +using System.ComponentModel; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; + +namespace OpenLoco.ObjectEditor.Objects +{ + [TypeConverter(typeof(ExpandableObjectConverter))] + [LocoStructSize(0x12)] + [LocoStructType(ObjectType.RoadExtra)] + [LocoStringTable("Name")] + public record RoadExtraObject( + [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, + [property: LocoStructOffset(0x02)] uint16_t RoadPieces, + [property: LocoStructOffset(0x04)] uint8_t PaintStyle, + [property: LocoStructOffset(0x05)] uint8_t CostIndex, + [property: LocoStructOffset(0x06)] int16_t BuildCostFactor, + [property: LocoStructOffset(0x08)] int16_t SellCostFactor, + [property: LocoStructOffset(0x0A), Browsable(false)] image_id Image, + [property: LocoStructOffset(0x0E), Browsable(false)] image_id var_0E + ) : ILocoStruct; +} diff --git a/Core/Objects/RoadObject.cs b/Core/Objects/RoadObject.cs new file mode 100644 index 00000000..252eda9d --- /dev/null +++ b/Core/Objects/RoadObject.cs @@ -0,0 +1,118 @@ +using System.ComponentModel; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; +using OpenLoco.ObjectEditor.Headers; + +namespace OpenLoco.ObjectEditor.Objects +{ + [Flags] + public enum RoadObjectFlags : uint16_t + { + none = 0, + unk_00 = 1 << 0, + unk_01 = 1 << 1, + unk_02 = 1 << 2, + unk_03 = 1 << 3, // Likely isTram + unk_04 = 1 << 4, + unk_05 = 1 << 5, + IsRoad = 1 << 6, // If not set this is tram track + }; + + [Flags] + public enum RoadObjectPieceFlags : uint16_t + { + None = 0, + OneWay = 1 << 0, + Track = 1 << 1, + Slope = 1 << 2, + SteepSlope = 1 << 3, + Intersection = 1 << 4, + OneSided = 1 << 5, + Overtake = 1 << 6, + StreetLights = 1 << 8, + }; + + [TypeConverter(typeof(ExpandableObjectConverter))] + [LocoStructSize(0x30)] + [LocoStructType(ObjectType.Road)] + [LocoStringTable("Name")] + public record RoadObject( + [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, + [property: LocoStructOffset(0x02)] RoadObjectPieceFlags RoadPieces, + [property: LocoStructOffset(0x04)] int16_t BuildCostFactor, + [property: LocoStructOffset(0x06)] int16_t SellCostFactor, + [property: LocoStructOffset(0x08)] int16_t TunnelCostFactor, + [property: LocoStructOffset(0x0A)] uint8_t CostIndex, + [property: LocoStructOffset(0x0B), Browsable(false)] object_id _Tunnel, + [property: LocoStructOffset(0x0C)] Speed16 MaxSpeed, + [property: LocoStructOffset(0x0E), Browsable(false)] image_id Image, + [property: LocoStructOffset(0x12)] RoadObjectFlags Flags, + [property: LocoStructOffset(0x14)] uint8_t NumBridges, + [property: LocoStructOffset(0x15), LocoArrayLength(RoadObject.MaxBridges), Browsable(false)] object_id[] _Bridges, + [property: LocoStructOffset(0x1C)] uint8_t NumStations, + [property: LocoStructOffset(0x1D), LocoArrayLength(RoadObject.MaxStations), Browsable(false)] object_id[] _Stations, + [property: LocoStructOffset(0x24)] uint8_t PaintStyle, + [property: LocoStructOffset(0x25)] uint8_t NumMods, + [property: LocoStructOffset(0x26), LocoArrayLength(RoadObject.MaxMods), Browsable(false)] object_id[] _Mods, + [property: LocoStructOffset(0x28)] uint8_t NumCompatible, + [property: LocoStructOffset(0x29)] uint8_t pad_29, + [property: LocoStructOffset(0x2A), Browsable(false)] uint16_t _CompatibleRoads, // bitset + [property: LocoStructOffset(0x2C), Browsable(false)] uint16_t _CompatibleTracks, // bitset + [property: LocoStructOffset(0x2E)] uint8_t TargetTownSize, + [property: LocoStructOffset(0x2F)] uint8_t pad_2F + ) : ILocoStruct, ILocoStructVariableData + { + + public List Compatible { get; set; } = []; + public List Mods { get; set; } = []; + public S5Header Tunnel { get; set; } + public List Bridges { get; set; } = []; + public List Stations { get; set; } = []; + + public const int MaxTunnels = 1; + public const int MaxBridges = 7; + public const int MaxStations = 7; + public const int MaxMods = 2; + + public ReadOnlySpan Load(ReadOnlySpan remainingData) + { + // compatible roads/tracks + Compatible = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumCompatible); + remainingData = remainingData[(S5Header.StructLength * NumCompatible)..]; + + // mods + Mods = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumMods); + remainingData = remainingData[(S5Header.StructLength * NumMods)..]; + + // tunnel + Tunnel = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, MaxTunnels)[0]; + remainingData = remainingData[(S5Header.StructLength * MaxTunnels)..]; + + // bridges + Bridges = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumBridges); + remainingData = remainingData[(S5Header.StructLength * NumBridges)..]; + + // stations + Stations = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumStations); + remainingData = remainingData[(S5Header.StructLength * NumStations)..]; + + // set _CompatibleRoads? + // set _CompatibleTracks? + + return remainingData; + } + + public ReadOnlySpan Save() + { + //var data = new byte[S5Header.StructLength * (NumCompatible + NumMods + 1 + NumBridges + NumStations)]; + + var headers = Compatible + .Concat(Mods) + .Concat(Enumerable.Repeat(Tunnel, 1)) + .Concat(Bridges) + .Concat(Stations); + + return headers.SelectMany(h => h.Write().ToArray()).ToArray(); + } + } +} diff --git a/Core/Objects/RoadStationObject.cs b/Core/Objects/RoadStationObject.cs new file mode 100644 index 00000000..cb527c26 --- /dev/null +++ b/Core/Objects/RoadStationObject.cs @@ -0,0 +1,119 @@ +using System.ComponentModel; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; +using OpenLoco.ObjectEditor.Headers; + +namespace OpenLoco.ObjectEditor.Objects +{ + [Flags] + public enum RoadStationObjectFlags : uint8_t + { + None = 0, + Recolourable = 1 << 0, + Passenger = 1 << 1, + Freight = 1 << 2, + RoadEnd = 1 << 3, + }; + + [TypeConverter(typeof(ExpandableObjectConverter))] + [LocoStructSize(0x6E)] + [LocoStructType(ObjectType.RoadStation)] + [LocoStringTable("Name")] + public record RoadStationObject( + [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, + [property: LocoStructOffset(0x02)] uint8_t PaintStyle, + [property: LocoStructOffset(0x03)] uint8_t pad_03, + [property: LocoStructOffset(0x04)] uint16_t RoadPieces, + [property: LocoStructOffset(0x06)] int16_t BuildCostFactor, + [property: LocoStructOffset(0x08)] int16_t SellCostFactor, + [property: LocoStructOffset(0x0A)] uint8_t CostIndex, + [property: LocoStructOffset(0x0B)] RoadStationObjectFlags Flags, + [property: LocoStructOffset(0x0C), LocoStructVariableLoad, Browsable(false)] image_id Image, + [property: LocoStructOffset(0x10), LocoStructVariableLoad, LocoArrayLength(RoadStationObject.MaxImageOffsets)] uint32_t[] ImageOffsets, + [property: LocoStructOffset(0x20)] uint8_t NumCompatible, + [property: LocoStructOffset(0x21), LocoStructVariableLoad, LocoArrayLength(RoadStationObject.MaxNumMods)] uint8_t[] _Mods, + [property: LocoStructOffset(0x28)] uint16_t DesignedYear, + [property: LocoStructOffset(0x2A)] uint16_t ObsoleteYear, + [property: LocoStructOffset(0x2C), LocoStructVariableLoad, Browsable(false)] object_id _CargoTypeId, + [property: LocoStructOffset(0x2D)] uint8_t pad_2D, + [property: LocoStructOffset(0x2E), LocoStructVariableLoad, LocoArrayLength(RoadStationObject.CargoOffsetBytesSize), Browsable(false)] uint8_t[] _CargoOffsetBytes + ) : ILocoStruct, ILocoStructVariableData + { + public const int MaxImageOffsets = 4; + public const int MaxNumMods = 7; + public const int CargoOffsetBytesSize = 16; + public List Compatible { get; set; } = []; + + public S5Header CargoType { get; set; } + + public uint8_t[][][] CargoOffsetBytes { get; set; } + + public ReadOnlySpan Load(ReadOnlySpan remainingData) + { + // compatible + Compatible = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumCompatible); + remainingData = remainingData[(S5Header.StructLength * NumCompatible)..]; + + // cargo + if (Flags.HasFlag(RoadStationObjectFlags.Passenger) || Flags.HasFlag(RoadStationObjectFlags.Freight)) + { + CargoType = S5Header.Read(remainingData[..S5Header.StructLength]); + remainingData = remainingData[(S5Header.StructLength * 1)..]; + } + + // cargo offsets (for drawing the cargo on the station) + CargoOffsetBytes = new byte[4][][]; + for (var i = 0; i < 4; ++i) + { + CargoOffsetBytes[i] = new byte[4][]; + for (var j = 0; j < 4; ++j) + { + var bytes = 0; + bytes++; + var length = 1; + + while (remainingData[bytes] != 0xFF) + { + length += 4; // x, y, x, y + bytes += 4; + } + + length += 4; + CargoOffsetBytes[i][j] = remainingData[..length].ToArray(); + remainingData = remainingData[length..]; + } + } + + return remainingData; + } + + public ReadOnlySpan Save() + { + using (var ms = new MemoryStream()) + { + // compatible + foreach (var co in Compatible) + { + ms.Write(co.Write()); + } + + // cargo + if (Flags.HasFlag(RoadStationObjectFlags.Passenger) || Flags.HasFlag(RoadStationObjectFlags.Freight)) + { + ms.Write(CargoType.Write()); + } + + // cargo offsets + for (var i = 0; i < 4; ++i) + { + for (var j = 0; j < 4; ++j) + { + ms.Write(CargoOffsetBytes[i][j]); + } + } + + return ms.ToArray(); + } + } + } +} diff --git a/OpenLocoTool/Objects/ScaffoldingObject.cs b/Core/Objects/ScaffoldingObject.cs similarity index 77% rename from OpenLocoTool/Objects/ScaffoldingObject.cs rename to Core/Objects/ScaffoldingObject.cs index 73ec0ee7..e1f52bfd 100644 --- a/OpenLocoTool/Objects/ScaffoldingObject.cs +++ b/Core/Objects/ScaffoldingObject.cs @@ -1,9 +1,8 @@ - -using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; +using System.ComponentModel; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0x12)] diff --git a/OpenLocoTool/Objects/ScenarioTextObject.cs b/Core/Objects/ScenarioTextObject.cs similarity index 80% rename from OpenLocoTool/Objects/ScenarioTextObject.cs rename to Core/Objects/ScenarioTextObject.cs index 336aaf43..5da17bbf 100644 --- a/OpenLocoTool/Objects/ScenarioTextObject.cs +++ b/Core/Objects/ScenarioTextObject.cs @@ -1,9 +1,9 @@  using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0x06)] diff --git a/OpenLocoTool/Objects/SnowObject.cs b/Core/Objects/SnowObject.cs similarity index 69% rename from OpenLocoTool/Objects/SnowObject.cs rename to Core/Objects/SnowObject.cs index 25511287..80461fbc 100644 --- a/OpenLocoTool/Objects/SnowObject.cs +++ b/Core/Objects/SnowObject.cs @@ -1,9 +1,8 @@ - -using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; +using System.ComponentModel; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0x06)] diff --git a/OpenLocoTool/Objects/SoundObject.cs b/Core/Objects/Sound/SoundObject.cs similarity index 65% rename from OpenLocoTool/Objects/SoundObject.cs rename to Core/Objects/Sound/SoundObject.cs index 0c323f50..08998f49 100644 --- a/OpenLocoTool/Objects/SoundObject.cs +++ b/Core/Objects/Sound/SoundObject.cs @@ -1,30 +1,9 @@ using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Objects +namespace Core.Objects.Sound { - [TypeConverter(typeof(ExpandableObjectConverter))] - [LocoStructSize(0x12)] - public record WaveFormatEx( - [property: LocoStructOffset(0x00)] int16_t WaveFormatTag, - [property: LocoStructOffset(0x02)] int16_t NumberOfChannels, - [property: LocoStructOffset(0x04)] int32_t SampleRate, - [property: LocoStructOffset(0x08)] int32_t AverageBytesPerSecond, - [property: LocoStructOffset(0x0B)] int16_t BlockAlign, - [property: LocoStructOffset(0x0D)] int16_t BitsPerSample, - [property: LocoStructOffset(0x010)] int16_t CBSize - ) : ILocoStruct; - - [TypeConverter(typeof(ExpandableObjectConverter))] - [LocoStructSize(0x1E)] - public record SoundObjectData( - [property: LocoStructOffset(0x00)] int32_t var_00, - [property: LocoStructOffset(0x04)] int32_t Offset, - [property: LocoStructOffset(0x08)] uint32_t Length, - [property: LocoStructOffset(0x0C)] WaveFormatEx PcmHeader - ) : ILocoStruct; - [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0x0C)] [LocoStructType(ObjectType.Sound)] @@ -84,7 +63,6 @@ public ReadOnlySpan Save() return ms.ToArray(); } - } } } diff --git a/Core/Objects/Sound/SoundObjectData.cs b/Core/Objects/Sound/SoundObjectData.cs new file mode 100644 index 00000000..29bf4934 --- /dev/null +++ b/Core/Objects/Sound/SoundObjectData.cs @@ -0,0 +1,14 @@ +using System.ComponentModel; +using OpenLoco.ObjectEditor.DatFileParsing; + +namespace Core.Objects.Sound +{ + [TypeConverter(typeof(ExpandableObjectConverter))] + [LocoStructSize(0x1E)] + public record SoundObjectData( + [property: LocoStructOffset(0x00)] int32_t var_00, + [property: LocoStructOffset(0x04)] int32_t Offset, + [property: LocoStructOffset(0x08)] uint32_t Length, + [property: LocoStructOffset(0x0C)] WaveFormatEx PcmHeader + ) : ILocoStruct; +} diff --git a/Core/Objects/Sound/WaveFormatEx.cs b/Core/Objects/Sound/WaveFormatEx.cs new file mode 100644 index 00000000..f5f262d0 --- /dev/null +++ b/Core/Objects/Sound/WaveFormatEx.cs @@ -0,0 +1,17 @@ +using System.ComponentModel; +using OpenLoco.ObjectEditor.DatFileParsing; + +namespace Core.Objects.Sound +{ + [TypeConverter(typeof(ExpandableObjectConverter))] + [LocoStructSize(0x12)] + public record WaveFormatEx( + [property: LocoStructOffset(0x00)] int16_t WaveFormatTag, + [property: LocoStructOffset(0x02)] int16_t NumberOfChannels, + [property: LocoStructOffset(0x04)] int32_t SampleRate, + [property: LocoStructOffset(0x08)] int32_t AverageBytesPerSecond, + [property: LocoStructOffset(0x0B)] int16_t BlockAlign, + [property: LocoStructOffset(0x0D)] int16_t BitsPerSample, + [property: LocoStructOffset(0x010)] int16_t CBSize + ) : ILocoStruct; +} diff --git a/Core/Objects/SteamObject.cs b/Core/Objects/SteamObject.cs new file mode 100644 index 00000000..d6e780b6 --- /dev/null +++ b/Core/Objects/SteamObject.cs @@ -0,0 +1,100 @@ +using System.ComponentModel; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; +using OpenLoco.ObjectEditor.Headers; + +namespace OpenLoco.ObjectEditor.Objects +{ + [Flags] + public enum SteamObjectFlags : uint16_t + { + None = 0, + ApplyWind = 1 << 0, + DisperseOnCollision = 1 << 1, + unk2 = 1 << 2, + unk3 = 1 << 3, + }; + + [TypeConverter(typeof(ExpandableObjectConverter))] + [LocoStructSize(0x02)] + public record ImageAndHeight( + [property: LocoStructOffset(0x00)] uint8_t ImageOffset, + [property: LocoStructOffset(0x01)] uint8_t Height + ) : ILocoStruct + { + public static ImageAndHeight Read(ReadOnlySpan data) + => new(data[0], data[1]); + } + + [TypeConverter(typeof(ExpandableObjectConverter))] + [LocoStructSize(0x28)] + [LocoStructType(ObjectType.Steam)] + [LocoStringTable("Name")] + public record SteamObject( + [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, + [property: LocoStructOffset(0x02), LocoPropertyMaybeUnused] uint16_t NumImages, // this is simply the count of images in the graphics table + [property: LocoStructOffset(0x04)] uint8_t NumStationaryTicks, + [property: LocoStructOffset(0x05), LocoStructVariableLoad, Browsable(false)] uint8_t SpriteWidth, + [property: LocoStructOffset(0x06), LocoStructVariableLoad, Browsable(false)] uint8_t SpriteHeightNegative, + [property: LocoStructOffset(0x07), LocoStructVariableLoad, Browsable(false)] uint8_t SpriteHeightPositive, + [property: LocoStructOffset(0x08)] SteamObjectFlags Flags, + [property: LocoStructOffset(0x0A)] uint32_t var_0A, + [property: LocoStructOffset(0x0E), Browsable(false)] image_id BaseImageId, + [property: LocoStructOffset(0x12), LocoStructVariableLoad, Browsable(false)] uint16_t _TotalNumFramesType0, + [property: LocoStructOffset(0x14), LocoStructVariableLoad, Browsable(false)] uint16_t _TotalNumFramesType1, + [property: LocoStructOffset(0x16)] uint32_t _FrameInfoType0Ptr, + [property: LocoStructOffset(0x1A)] uint32_t _FrameInfoType1Ptr, + [property: LocoStructOffset(0x1E)] uint8_t NumSoundEffects, + [property: LocoStructOffset(0x01F), LocoArrayLength(SteamObject.MaxSoundEffects), LocoStructVariableLoad, Browsable(false)] object_id[] _SoundEffects + ) : ILocoStruct, ILocoStructVariableData + { + public const int MaxSoundEffects = 9; + + public List FrameInfoType0 { get; set; } = []; + public List FrameInfoType1 { get; set; } = []; + + public List SoundEffects { get; set; } = []; + + public ReadOnlySpan Load(ReadOnlySpan remainingData) + { + // frameInfoType0 + FrameInfoType0.Clear(); + while (remainingData[0] != 0xFF) + { + FrameInfoType0.Add(ImageAndHeight.Read(remainingData[..ObjectAttributes.StructSize()])); + remainingData = remainingData[ObjectAttributes.StructSize()..]; + } + + remainingData = remainingData[1..]; + + // frameInfoType1 + FrameInfoType1.Clear(); + while (remainingData[0] != 0xFF) + { + FrameInfoType1.Add(ImageAndHeight.Read(remainingData[..ObjectAttributes.StructSize()])); + remainingData = remainingData[ObjectAttributes.StructSize()..]; + } + + remainingData = remainingData[1..]; + + // sounds effects + SoundEffects.Clear(); + for (var i = 0; i < NumSoundEffects; ++i) + { + SoundEffects.Add(S5Header.Read(remainingData[..S5Header.StructLength])); + remainingData = remainingData[S5Header.StructLength..]; + } + + return remainingData; + } + + // todo: optimise this with streams - this is quite slow as is + public ReadOnlySpan Save() + => FrameInfoType0.SelectMany(x => new byte[] { x.ImageOffset, x.Height }) + .Concat(new byte[] { 0xFF }) + .Concat(FrameInfoType1.SelectMany(x => new byte[] { x.ImageOffset, x.Height })) + .Concat(new byte[] { 0xFF }) + .Concat(SoundEffects.SelectMany(sfx => sfx.Write().ToArray())) + .ToArray(); + } +} diff --git a/OpenLocoTool/Objects/StreetLightObject.cs b/Core/Objects/StreetLightObject.cs similarity index 80% rename from OpenLocoTool/Objects/StreetLightObject.cs rename to Core/Objects/StreetLightObject.cs index 2319d81c..fe761b24 100644 --- a/OpenLocoTool/Objects/StreetLightObject.cs +++ b/Core/Objects/StreetLightObject.cs @@ -1,8 +1,8 @@ using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0x0C)] diff --git a/OpenLocoTool/Objects/TownNamesObject.cs b/Core/Objects/TownNamesObject.cs similarity index 90% rename from OpenLocoTool/Objects/TownNamesObject.cs rename to Core/Objects/TownNamesObject.cs index fa9495a5..9e74a004 100644 --- a/OpenLocoTool/Objects/TownNamesObject.cs +++ b/Core/Objects/TownNamesObject.cs @@ -1,9 +1,9 @@  using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0x04)] diff --git a/OpenLocoTool/Objects/TrackExtraObject.cs b/Core/Objects/TrackExtraObject.cs similarity index 97% rename from OpenLocoTool/Objects/TrackExtraObject.cs rename to Core/Objects/TrackExtraObject.cs index b8fda31b..cb073892 100644 --- a/OpenLocoTool/Objects/TrackExtraObject.cs +++ b/Core/Objects/TrackExtraObject.cs @@ -1,10 +1,9 @@ - -using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; -using OpenLocoTool.Types; +using System.ComponentModel; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; +using OpenLoco.ObjectEditor.Types; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0x12)] diff --git a/OpenLocoTool/Objects/TrackObject.cs b/Core/Objects/TrackObject.cs similarity index 94% rename from OpenLocoTool/Objects/TrackObject.cs rename to Core/Objects/TrackObject.cs index 186c1813..49eb070a 100644 --- a/OpenLocoTool/Objects/TrackObject.cs +++ b/Core/Objects/TrackObject.cs @@ -1,10 +1,12 @@ using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; -using OpenLocoTool.Types; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; +using OpenLoco.ObjectEditor.Headers; +using OpenLoco.ObjectEditor.Types; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { + [Flags] public enum TrackObjectPieceFlags : uint16_t { None = 0, @@ -19,6 +21,7 @@ public enum TrackObjectPieceFlags : uint16_t SlopedCurve = 1 << 8, SBend = 1 << 9, Junction = 1 << 10, + }; public enum TrackObjectFlags : uint16_t @@ -41,10 +44,10 @@ public record TrackObject( [property: LocoStructOffset(0x07)] uint8_t NumCompatible, [property: LocoStructOffset(0x08)] uint8_t NumMods, [property: LocoStructOffset(0x09)] uint8_t NumSignals, - [property: LocoStructOffset(0x0A), LocoArrayLength(4), LocoStructVariableLoad, Browsable(false)] object_id[] _Mods, + [property: LocoStructOffset(0x0A), LocoArrayLength(TrackObject.MaxMods), LocoStructVariableLoad, Browsable(false)] object_id[] _Mods, [property: LocoStructOffset(0x0E), LocoStructVariableLoad, Browsable(false)] uint16_t _Signals, // bitset - [property: LocoStructOffset(0x10)] uint16_t CompatibleTracks, // bitset - [property: LocoStructOffset(0x12)] uint16_t CompatibleRoads, // bitset + [property: LocoStructOffset(0x10), Browsable(false)] uint16_t _CompatibleTracks, // bitset + [property: LocoStructOffset(0x12), Browsable(false)] uint16_t _CompatibleRoads, // bitset [property: LocoStructOffset(0x14)] int16_t BuildCostFactor, [property: LocoStructOffset(0x16)] int16_t SellCostFactor, [property: LocoStructOffset(0x18)] int16_t TunnelCostFactor, @@ -54,9 +57,9 @@ public record TrackObject( [property: LocoStructOffset(0x1E), Browsable(false)] image_id Image, [property: LocoStructOffset(0x22)] TrackObjectFlags Flags, [property: LocoStructOffset(0x24)] uint8_t NumBridges, - [property: LocoStructOffset(0x25), LocoArrayLength(7), Browsable(false)] object_id[] _Bridges, // 0x25 + [property: LocoStructOffset(0x25), LocoArrayLength(TrackObject.MaxBridges), Browsable(false)] object_id[] _Bridges, // 0x25 [property: LocoStructOffset(0x2C)] uint8_t NumStations, - [property: LocoStructOffset(0x2D), LocoArrayLength(7), Browsable(false)] object_id[] _Stations, // 0x2D + [property: LocoStructOffset(0x2D), LocoArrayLength(TrackObject.MaxStations), Browsable(false)] object_id[] _Stations, // 0x2D [property: LocoStructOffset(0x34)] uint8_t DisplayOffset, [property: LocoStructOffset(0x35), Browsable(false)] uint8_t pad_35 ) : ILocoStruct, ILocoStructVariableData, IImageTableStrings @@ -68,16 +71,13 @@ public record TrackObject( public List Bridges { get; set; } = []; public List Stations { get; set; } = []; - public const int NumTunnels = 1; + public const int MaxTunnels = 1; + public const int MaxBridges = 7; + public const int MaxStations = 7; + public const int MaxMods = 4; public ReadOnlySpan Load(ReadOnlySpan remainingData) { - Compatible.Clear(); - Mods.Clear(); - Signals.Clear(); - Bridges.Clear(); - Stations.Clear(); - // compatible roads/tracks Compatible = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumCompatible); remainingData = remainingData[(S5Header.StructLength * NumCompatible)..]; @@ -91,8 +91,8 @@ public ReadOnlySpan Load(ReadOnlySpan remainingData) remainingData = remainingData[(S5Header.StructLength * NumSignals)..]; // tunnel - Tunnel = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumTunnels)[0]; - remainingData = remainingData[(S5Header.StructLength * NumTunnels)..]; + Tunnel = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, MaxTunnels)[0]; + remainingData = remainingData[(S5Header.StructLength * MaxTunnels)..]; // bridges Bridges = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumBridges); @@ -102,6 +102,9 @@ public ReadOnlySpan Load(ReadOnlySpan remainingData) Stations = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumStations); remainingData = remainingData[(S5Header.StructLength * NumStations)..]; + // set _CompatibleRoads? + // set _CompatibleTracks? + return remainingData; } @@ -109,6 +112,7 @@ public ReadOnlySpan Save() { var headers = Compatible .Concat(Mods) + .Concat(Signals) .Concat(Enumerable.Repeat(Tunnel, 1)) .Concat(Bridges) .Concat(Stations); diff --git a/OpenLocoTool/Objects/TrainSignalObject.cs b/Core/Objects/TrainSignalObject.cs similarity index 92% rename from OpenLocoTool/Objects/TrainSignalObject.cs rename to Core/Objects/TrainSignalObject.cs index 62303df4..81ee2e4d 100644 --- a/OpenLocoTool/Objects/TrainSignalObject.cs +++ b/Core/Objects/TrainSignalObject.cs @@ -1,8 +1,9 @@ using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; +using OpenLoco.ObjectEditor.Headers; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [Flags] public enum TrainSignalObjectFlags : uint16_t diff --git a/OpenLocoTool/Objects/TrainStationObject.cs b/Core/Objects/TrainStationObject.cs similarity index 53% rename from OpenLocoTool/Objects/TrainStationObject.cs rename to Core/Objects/TrainStationObject.cs index c8cda884..09325057 100644 --- a/OpenLocoTool/Objects/TrainStationObject.cs +++ b/Core/Objects/TrainStationObject.cs @@ -1,12 +1,13 @@  using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; +using OpenLoco.ObjectEditor.Headers; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [Flags] - public enum TrainStationFlags : uint8_t + public enum TrainStationObjectFlags : uint8_t { None = 0, Recolourable = 1 << 0, @@ -17,44 +18,31 @@ public enum TrainStationFlags : uint8_t [LocoStructSize(0xAE)] [LocoStructType(ObjectType.TrainStation)] [LocoStringTable("Name")] - public class TrainStationObject( - uint8_t paintStyle, - uint8_t var_03, - uint16_t trackPieces, - int16_t buildCostFactor, - int16_t sellCostFactor, - uint8_t costIndex, - uint8_t var_0B, - RoadStationFlags flags, - uint8_t var_0D, - uint8_t numCompatible, - uint16_t designedYear, - uint16_t obsoleteYear) - : ILocoStruct, ILocoStructVariableData + public record TrainStationObject( + [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, + [property: LocoStructOffset(0x02)] uint8_t PaintStyle, + [property: LocoStructOffset(0x03)] uint8_t var_03, + [property: LocoStructOffset(0x04)] uint16_t TrackPieces, + [property: LocoStructOffset(0x06)] int16_t BuildCostFactor, + [property: LocoStructOffset(0x08)] int16_t SellCostFactor, + [property: LocoStructOffset(0x0A)] uint8_t CostIndex, + [property: LocoStructOffset(0x0B)] uint8_t var_0B, + [property: LocoStructOffset(0x0C)] TrainStationObjectFlags Flags, + [property: LocoStructOffset(0x0D)] uint8_t var_0D, + [property: LocoStructOffset(0x0E)] image_id Image, + [property: LocoStructOffset(0x12), LocoArrayLength(4)] uint32_t[] ImageOffsets, + [property: LocoStructOffset(0x22)] uint8_t NumCompatible, + [property: LocoStructOffset(0x23), LocoArrayLength(7)] uint8_t[] Mods, + [property: LocoStructOffset(0x2A)] uint16_t DesignedYear, + [property: LocoStructOffset(0x2C)] uint16_t ObsoleteYear, + [property: LocoStructOffset(0x2E), LocoStructVariableLoad, LocoArrayLength(TrainStationObject.CargoOffsetBytesSize), Browsable(false)] uint8_t[] _CargoOffsetBytes, + [property: LocoStructOffset(0x3E), LocoStructVariableLoad, LocoArrayLength(TrainStationObject.ManualPowerLength), Browsable(false)] uint8_t[] _ManualPower + ) : ILocoStruct, ILocoStructVariableData { - //[LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, - [LocoStructOffset(0x02)] public uint8_t PaintStyle { get; set; } = paintStyle; - [LocoStructOffset(0x03)] public uint8_t var_03 { get; set; } = var_03; - [LocoStructOffset(0x04)] public uint16_t TrackPieces { get; set; } = trackPieces; - [LocoStructOffset(0x06)] public int16_t BuildCostFactor { get; set; } = buildCostFactor; - [LocoStructOffset(0x08)] public int16_t SellCostFactor { get; set; } = sellCostFactor; - [LocoStructOffset(0x0A)] public uint8_t CostIndex { get; set; } = costIndex; - [LocoStructOffset(0x0B)] public uint8_t var_0B { get; set; } = var_0B; - [LocoStructOffset(0x0C)] public RoadStationFlags Flags { get; set; } = flags; - [LocoStructOffset(0x0D)] public uint8_t var_0D { get; set; } = var_0D; - //[LocoStructOffset(0x0E)] image_id Image, - //[LocoStructOffset(0x12), LocoArrayLength(4)] public uint32_t[] ImageOffsets { get; set; } - [LocoStructOffset(0x22)] public uint8_t NumCompatible { get; set; } = numCompatible; - //[LocoStructOffset(0x23), LocoArrayLength(7)] uint8_t[] Mods, - [LocoStructOffset(0x2A)] public uint16_t DesignedYear { get; set; } = designedYear; - [LocoStructOffset(0x2C)] public uint16_t ObsoleteYear { get; set; } = obsoleteYear; - //[LocoStructProperty(0x2E)] const std::byte* CargoOffsetBytes[4][4] - //[LocoStructProperty(0x??)] const std::byte* ManualPower[ManualPowerLength] - public List Compatible { get; set; } = []; public const int ManualPowerLength = 16; - + public const int CargoOffsetBytesSize = 16; public uint8_t[][][] CargoOffsetBytes { get; set; } public uint8_t[][] ManualPower { get; set; } diff --git a/OpenLocoTool/Objects/TreeObject.cs b/Core/Objects/TreeObject.cs similarity index 93% rename from OpenLocoTool/Objects/TreeObject.cs rename to Core/Objects/TreeObject.cs index af326e48..12b38112 100644 --- a/OpenLocoTool/Objects/TreeObject.cs +++ b/Core/Objects/TreeObject.cs @@ -1,8 +1,8 @@ using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [Flags] public enum TreeObjectFlags : uint16_t diff --git a/OpenLocoTool/Objects/TunnelObject.cs b/Core/Objects/TunnelObject.cs similarity index 75% rename from OpenLocoTool/Objects/TunnelObject.cs rename to Core/Objects/TunnelObject.cs index 8941de2d..7ac41d41 100644 --- a/OpenLocoTool/Objects/TunnelObject.cs +++ b/Core/Objects/TunnelObject.cs @@ -1,9 +1,9 @@  using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0x06)] diff --git a/OpenLocoTool/Objects/Vehicle/BodySprite.cs b/Core/Objects/Vehicle/BodySprite.cs similarity index 97% rename from OpenLocoTool/Objects/Vehicle/BodySprite.cs rename to Core/Objects/Vehicle/BodySprite.cs index 21d67622..eb22d699 100644 --- a/OpenLocoTool/Objects/Vehicle/BodySprite.cs +++ b/Core/Objects/Vehicle/BodySprite.cs @@ -1,7 +1,7 @@ using System.ComponentModel; -using OpenLocoTool.DatFileParsing; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { public enum BodySpriteSlopeType { Flat, Gentle, Sloped, Steep, Unk1, Unk2 } diff --git a/OpenLocoTool/Objects/Vehicle/BodySpriteFlags.cs b/Core/Objects/Vehicle/BodySpriteFlags.cs similarity index 91% rename from OpenLocoTool/Objects/Vehicle/BodySpriteFlags.cs rename to Core/Objects/Vehicle/BodySpriteFlags.cs index c93ab6a0..14b268c4 100644 --- a/OpenLocoTool/Objects/Vehicle/BodySpriteFlags.cs +++ b/Core/Objects/Vehicle/BodySpriteFlags.cs @@ -1,4 +1,4 @@ -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [Flags] public enum BodySpriteFlags : uint8_t diff --git a/OpenLocoTool/Objects/Vehicle/BogieSprite.cs b/Core/Objects/Vehicle/BogieSprite.cs similarity index 86% rename from OpenLocoTool/Objects/Vehicle/BogieSprite.cs rename to Core/Objects/Vehicle/BogieSprite.cs index fbb57cbf..682c48b2 100644 --- a/OpenLocoTool/Objects/Vehicle/BogieSprite.cs +++ b/Core/Objects/Vehicle/BogieSprite.cs @@ -1,7 +1,7 @@ using System.ComponentModel; -using OpenLocoTool.DatFileParsing; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { public enum BogieSpriteSlopeType { Flat, Gentle, Steep } @@ -20,9 +20,6 @@ public record BogieSprite( ) : ILocoStruct { public uint8_t NumRollSprites { get; set; } - //public uint32_t FlatImageIds { get; set; } - //public uint32_t GentleImageIds { get; set; } - //public uint32_t SteepImageIds { get; set; } public Dictionary> ImageIds = []; public int NumImages { get; set; } diff --git a/OpenLocoTool/Objects/Vehicle/BogieSpriteFlags.cs b/Core/Objects/Vehicle/BogieSpriteFlags.cs similarity index 89% rename from OpenLocoTool/Objects/Vehicle/BogieSpriteFlags.cs rename to Core/Objects/Vehicle/BogieSpriteFlags.cs index ecd671e2..7a2c9f18 100644 --- a/OpenLocoTool/Objects/Vehicle/BogieSpriteFlags.cs +++ b/Core/Objects/Vehicle/BogieSpriteFlags.cs @@ -1,4 +1,4 @@ -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [Flags] public enum BogieSpriteFlags : uint8_t diff --git a/OpenLocoTool/Objects/Vehicle/DrivingSoundType.cs b/Core/Objects/Vehicle/DrivingSoundType.cs similarity index 68% rename from OpenLocoTool/Objects/Vehicle/DrivingSoundType.cs rename to Core/Objects/Vehicle/DrivingSoundType.cs index dfed1fbf..b31105df 100644 --- a/OpenLocoTool/Objects/Vehicle/DrivingSoundType.cs +++ b/Core/Objects/Vehicle/DrivingSoundType.cs @@ -1,4 +1,4 @@ -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { public enum DrivingSoundType : uint8_t { diff --git a/OpenLocoTool/Objects/Vehicle/Engine1Sound.cs b/Core/Objects/Vehicle/Engine1Sound.cs similarity index 91% rename from OpenLocoTool/Objects/Vehicle/Engine1Sound.cs rename to Core/Objects/Vehicle/Engine1Sound.cs index 6d3cc4fb..ba247cdd 100644 --- a/OpenLocoTool/Objects/Vehicle/Engine1Sound.cs +++ b/Core/Objects/Vehicle/Engine1Sound.cs @@ -1,7 +1,7 @@ using System.ComponentModel; -using OpenLocoTool.DatFileParsing; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0x11)] diff --git a/OpenLocoTool/Objects/Vehicle/Engine2Sound.cs b/Core/Objects/Vehicle/Engine2Sound.cs similarity index 93% rename from OpenLocoTool/Objects/Vehicle/Engine2Sound.cs rename to Core/Objects/Vehicle/Engine2Sound.cs index 140b23a5..f4718fef 100644 --- a/OpenLocoTool/Objects/Vehicle/Engine2Sound.cs +++ b/Core/Objects/Vehicle/Engine2Sound.cs @@ -1,7 +1,7 @@ using System.ComponentModel; -using OpenLocoTool.DatFileParsing; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0x1B)] diff --git a/OpenLocoTool/Objects/Vehicle/FrictionSound.cs b/Core/Objects/Vehicle/FrictionSound.cs similarity index 88% rename from OpenLocoTool/Objects/Vehicle/FrictionSound.cs rename to Core/Objects/Vehicle/FrictionSound.cs index f5ef10b2..34cce78d 100644 --- a/OpenLocoTool/Objects/Vehicle/FrictionSound.cs +++ b/Core/Objects/Vehicle/FrictionSound.cs @@ -1,7 +1,7 @@ using System.ComponentModel; -using OpenLocoTool.DatFileParsing; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0x0B)] diff --git a/OpenLocoTool/Objects/Vehicle/SimpleAnimation.cs b/Core/Objects/Vehicle/SimpleAnimation.cs similarity index 59% rename from OpenLocoTool/Objects/Vehicle/SimpleAnimation.cs rename to Core/Objects/Vehicle/SimpleAnimation.cs index 2b810f39..b10f1597 100644 --- a/OpenLocoTool/Objects/Vehicle/SimpleAnimation.cs +++ b/Core/Objects/Vehicle/SimpleAnimation.cs @@ -1,12 +1,12 @@ using System.ComponentModel; -using OpenLocoTool.DatFileParsing; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0x03)] public record SimpleAnimation( - //[property: LocoStructOffset(0x00)] uint8_t ObjectId, // object loader fills this in - not necessary for openlocotool + [property: LocoStructOffset(0x00), Browsable(false)] object_id ObjectId, [property: LocoStructOffset(0x01)] uint8_t Height, [property: LocoStructOffset(0x02)] SimpleAnimationType Type ) : ILocoStruct; diff --git a/OpenLocoTool/Objects/Vehicle/SimpleAnimationType.cs b/Core/Objects/Vehicle/SimpleAnimationType.cs similarity index 81% rename from OpenLocoTool/Objects/Vehicle/SimpleAnimationType.cs rename to Core/Objects/Vehicle/SimpleAnimationType.cs index 9bf2ecda..a1197c45 100644 --- a/OpenLocoTool/Objects/Vehicle/SimpleAnimationType.cs +++ b/Core/Objects/Vehicle/SimpleAnimationType.cs @@ -1,4 +1,4 @@ -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { public enum SimpleAnimationType : uint8_t { diff --git a/OpenLocoTool/Objects/Vehicle/TransportMode.cs b/Core/Objects/Vehicle/TransportMode.cs similarity index 68% rename from OpenLocoTool/Objects/Vehicle/TransportMode.cs rename to Core/Objects/Vehicle/TransportMode.cs index 0481d65f..bc16f501 100644 --- a/OpenLocoTool/Objects/Vehicle/TransportMode.cs +++ b/Core/Objects/Vehicle/TransportMode.cs @@ -1,4 +1,4 @@ -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { public enum TransportMode : uint8_t { diff --git a/OpenLocoTool/Objects/Vehicle/VehicleObject.cs b/Core/Objects/Vehicle/VehicleObject.cs similarity index 95% rename from OpenLocoTool/Objects/Vehicle/VehicleObject.cs rename to Core/Objects/Vehicle/VehicleObject.cs index ca155d79..1595e59d 100644 --- a/OpenLocoTool/Objects/Vehicle/VehicleObject.cs +++ b/Core/Objects/Vehicle/VehicleObject.cs @@ -1,8 +1,9 @@ using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; +using OpenLoco.ObjectEditor.Headers; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0x15E)] @@ -353,7 +354,7 @@ public void PostLoad() var curr = offset; bodySprite.FlatYawAccuracy = getYawAccuracyFlat(bodySprite.NumFlatRotationFrames); - bodySprite.NumFramesPerRotation = (byte)(bodySprite.NumAnimationFrames * bodySprite.NumCargoFrames * bodySprite.NumRollFrames + (bodySprite.Flags.HasFlag(BodySpriteFlags.HasBrakingLights) ? 1 : 0)); // be careful of overflow here... + bodySprite.NumFramesPerRotation = (byte)((bodySprite.NumAnimationFrames * bodySprite.NumCargoFrames * bodySprite.NumRollFrames) + (bodySprite.Flags.HasFlag(BodySpriteFlags.HasBrakingLights) ? 1 : 0)); // be careful of overflow here... var numFlatFrames = (byte)(bodySprite.NumFramesPerRotation * bodySprite.NumFlatRotationFrames); offset += numFlatFrames / (bodySprite.Flags.HasFlag(BodySpriteFlags.RotationalSymmetry) ? 2 : 1); bodySprite.ImageIds[BodySpriteSlopeType.Flat] = Enumerable.Range(curr, offset - curr).ToList(); @@ -429,12 +430,12 @@ public void PostLoad() bogieSprite.NumRollSprites = bogieSprite.RollStates; - //bogieSprite.FlatImageIds = offset; var initial = offset; var curr = offset; var numRollFrames = bogieSprite.NumRollSprites * 32; offset += numRollFrames / (bogieSprite.Flags.HasFlag(BogieSpriteFlags.RotationalSymmetry) ? 2 : 1); + bogieSprite.ImageIds[BogieSpriteSlopeType.Flat] = Enumerable.Range(curr, offset - curr).ToList(); if (bogieSprite.Flags.HasFlag(BogieSpriteFlags.HasGentleSprites)) { @@ -442,6 +443,7 @@ public void PostLoad() curr = offset; var numGentleFrames = bogieSprite.NumRollSprites * 64; offset += numGentleFrames / (bogieSprite.Flags.HasFlag(BogieSpriteFlags.RotationalSymmetry) ? 2 : 1); + bogieSprite.ImageIds[BogieSpriteSlopeType.Gentle] = Enumerable.Range(curr, offset - curr).ToList(); if (bogieSprite.Flags.HasFlag(BogieSpriteFlags.HasSteepSprites)) { @@ -449,6 +451,8 @@ public void PostLoad() curr = offset; var numSteepFrames = bogieSprite.NumRollSprites * 64; offset += numSteepFrames / (bogieSprite.Flags.HasFlag(BogieSpriteFlags.RotationalSymmetry) ? 2 : 1); + bogieSprite.ImageIds[BogieSpriteSlopeType.Steep] = Enumerable.Range(curr, offset - curr).ToList(); + } } diff --git a/OpenLocoTool/Objects/Vehicle/VehicleObjectFlags.cs b/Core/Objects/Vehicle/VehicleObjectFlags.cs similarity index 90% rename from OpenLocoTool/Objects/Vehicle/VehicleObjectFlags.cs rename to Core/Objects/Vehicle/VehicleObjectFlags.cs index c399c562..34f8a290 100644 --- a/OpenLocoTool/Objects/Vehicle/VehicleObjectFlags.cs +++ b/Core/Objects/Vehicle/VehicleObjectFlags.cs @@ -1,4 +1,4 @@ -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [Flags] public enum VehicleObjectFlags : uint16_t diff --git a/OpenLocoTool/Objects/Vehicle/VehicleObjectUnk.cs b/Core/Objects/Vehicle/VehicleObjectUnk.cs similarity index 88% rename from OpenLocoTool/Objects/Vehicle/VehicleObjectUnk.cs rename to Core/Objects/Vehicle/VehicleObjectUnk.cs index d5e266d8..ca9af242 100644 --- a/OpenLocoTool/Objects/Vehicle/VehicleObjectUnk.cs +++ b/Core/Objects/Vehicle/VehicleObjectUnk.cs @@ -1,7 +1,7 @@ using System.ComponentModel; -using OpenLocoTool.DatFileParsing; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0x06)] diff --git a/OpenLocoTool/Objects/Vehicle/VehicleType.cs b/Core/Objects/Vehicle/VehicleType.cs similarity index 70% rename from OpenLocoTool/Objects/Vehicle/VehicleType.cs rename to Core/Objects/Vehicle/VehicleType.cs index fd4ac63e..a767f671 100644 --- a/OpenLocoTool/Objects/Vehicle/VehicleType.cs +++ b/Core/Objects/Vehicle/VehicleType.cs @@ -1,4 +1,4 @@ -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { public enum VehicleType : uint8_t { diff --git a/OpenLocoTool/Objects/WallObject.cs b/Core/Objects/WallObject.cs similarity index 85% rename from OpenLocoTool/Objects/WallObject.cs rename to Core/Objects/WallObject.cs index ddb31871..f8333040 100644 --- a/OpenLocoTool/Objects/WallObject.cs +++ b/Core/Objects/WallObject.cs @@ -1,8 +1,8 @@ using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [Flags] public enum WallObjectFlags : uint8_t @@ -22,7 +22,7 @@ public enum WallObjectFlags : uint8_t [LocoStructSize(0x0A)] [LocoStructType(ObjectType.Wall)] [LocoStringTable("Name")] - public class WallObject( + public record WallObject( [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, [property: LocoStructOffset(0x02), Browsable(false)] image_id Image, [property: LocoStructOffset(0x06), LocoPropertyMaybeUnused] uint8_t var_06, diff --git a/OpenLocoTool/Objects/WaterObject.cs b/Core/Objects/WaterObject.cs similarity index 85% rename from OpenLocoTool/Objects/WaterObject.cs rename to Core/Objects/WaterObject.cs index 40bd6dac..cf59dded 100644 --- a/OpenLocoTool/Objects/WaterObject.cs +++ b/Core/Objects/WaterObject.cs @@ -1,9 +1,9 @@  using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Objects +namespace OpenLoco.ObjectEditor.Objects { [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0x0E)] diff --git a/Core/Types/DatFileInfo.cs b/Core/Types/DatFileInfo.cs new file mode 100644 index 00000000..fc5f11eb --- /dev/null +++ b/Core/Types/DatFileInfo.cs @@ -0,0 +1,9 @@ +using System.ComponentModel; +using OpenLoco.ObjectEditor.Headers; + +namespace OpenLoco.ObjectEditor.DatFileParsing +{ + [TypeConverter(typeof(ExpandableObjectConverter))] + [Category("Header")] + public record DatFileInfo(S5Header S5Header, ObjectHeader ObjectHeader); +} diff --git a/OpenLocoTool/Types/G1Dat.cs b/Core/Types/G1Dat.cs similarity index 77% rename from OpenLocoTool/Types/G1Dat.cs rename to Core/Types/G1Dat.cs index ca01b0e7..7d84f1d1 100644 --- a/OpenLocoTool/Types/G1Dat.cs +++ b/Core/Types/G1Dat.cs @@ -1,7 +1,7 @@ using System.ComponentModel; -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor.Headers; -namespace OpenLocoTool.DatFileParsing +namespace OpenLoco.ObjectEditor.DatFileParsing { [TypeConverter(typeof(ExpandableObjectConverter))] public class G1Dat(G1Header g1Header, List g1Elements) diff --git a/OpenLocoTool/Types/G1Element32.cs b/Core/Types/G1Element32.cs similarity index 96% rename from OpenLocoTool/Types/G1Element32.cs rename to Core/Types/G1Element32.cs index 132da5a7..af5d29f9 100644 --- a/OpenLocoTool/Types/G1Element32.cs +++ b/Core/Types/G1Element32.cs @@ -1,7 +1,7 @@ using System.ComponentModel; -using OpenLocoTool.DatFileParsing; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Headers +namespace OpenLoco.ObjectEditor.Headers { [Flags] public enum G1ElementFlags : uint16_t diff --git a/OpenLocoTool/Types/G1Header.cs b/Core/Types/G1Header.cs similarity index 81% rename from OpenLocoTool/Types/G1Header.cs rename to Core/Types/G1Header.cs index d7ec68c2..abb728bb 100644 --- a/OpenLocoTool/Types/G1Header.cs +++ b/Core/Types/G1Header.cs @@ -1,7 +1,7 @@ using System.ComponentModel; -using OpenLocoTool.DatFileParsing; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Headers +namespace OpenLoco.ObjectEditor.Headers { [TypeConverter(typeof(ExpandableObjectConverter))] [Category("Header")] diff --git a/OpenLocoTool/Types/IImageTableStrings.cs b/Core/Types/IImageTableStrings.cs similarity index 71% rename from OpenLocoTool/Types/IImageTableStrings.cs rename to Core/Types/IImageTableStrings.cs index 9041c611..12621814 100644 --- a/OpenLocoTool/Types/IImageTableStrings.cs +++ b/Core/Types/IImageTableStrings.cs @@ -1,4 +1,4 @@ -namespace OpenLocoTool.Types +namespace OpenLoco.ObjectEditor.Types { public interface IImageTableStrings { diff --git a/OpenLocoTool/Types/ILocoObject.cs b/Core/Types/ILocoObject.cs similarity index 67% rename from OpenLocoTool/Types/ILocoObject.cs rename to Core/Types/ILocoObject.cs index 6e799627..e6f38443 100644 --- a/OpenLocoTool/Types/ILocoObject.cs +++ b/Core/Types/ILocoObject.cs @@ -1,8 +1,8 @@ using System.ComponentModel; -using OpenLocoTool.Headers; -using OpenLocoTool.Types; +using OpenLoco.ObjectEditor.Headers; +using OpenLoco.ObjectEditor.Types; -namespace OpenLocoTool.DatFileParsing +namespace OpenLoco.ObjectEditor.DatFileParsing { [TypeConverter(typeof(ExpandableObjectConverter))] public interface ILocoObject diff --git a/OpenLocoTool/Types/ILocoStruct.cs b/Core/Types/ILocoStruct.cs similarity index 72% rename from OpenLocoTool/Types/ILocoStruct.cs rename to Core/Types/ILocoStruct.cs index e1932ee5..20840cf5 100644 --- a/OpenLocoTool/Types/ILocoStruct.cs +++ b/Core/Types/ILocoStruct.cs @@ -1,6 +1,6 @@ using System.ComponentModel; -namespace OpenLocoTool.DatFileParsing +namespace OpenLoco.ObjectEditor.DatFileParsing { [TypeConverter(typeof(ExpandableObjectConverter))] public interface ILocoStruct diff --git a/OpenLocoTool/Types/ILocoStructVariableData.cs b/Core/Types/ILocoStructVariableData.cs similarity index 88% rename from OpenLocoTool/Types/ILocoStructVariableData.cs rename to Core/Types/ILocoStructVariableData.cs index a7448594..73409eb3 100644 --- a/OpenLocoTool/Types/ILocoStructVariableData.cs +++ b/Core/Types/ILocoStructVariableData.cs @@ -1,6 +1,6 @@ using System.ComponentModel; -namespace OpenLocoTool.DatFileParsing +namespace OpenLoco.ObjectEditor.DatFileParsing { [TypeConverter(typeof(ExpandableObjectConverter))] public interface ILocoStructVariableData diff --git a/OpenLocoTool/Types/LocoObject.cs b/Core/Types/LocoObject.cs similarity index 83% rename from OpenLocoTool/Types/LocoObject.cs rename to Core/Types/LocoObject.cs index 7566c2f2..997832dd 100644 --- a/OpenLocoTool/Types/LocoObject.cs +++ b/Core/Types/LocoObject.cs @@ -1,8 +1,8 @@ using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor.DatFileParsing; +using OpenLoco.ObjectEditor.Headers; -namespace OpenLocoTool.Types +namespace OpenLoco.ObjectEditor.Types { [TypeConverter(typeof(ExpandableObjectConverter))] diff --git a/OpenLocoTool/Types/ObjectHeader.cs b/Core/Types/ObjectHeader.cs similarity index 85% rename from OpenLocoTool/Types/ObjectHeader.cs rename to Core/Types/ObjectHeader.cs index 02347cc1..e9875b6e 100644 --- a/OpenLocoTool/Types/ObjectHeader.cs +++ b/Core/Types/ObjectHeader.cs @@ -1,8 +1,9 @@ using System.ComponentModel; -using OpenLocoTool.DatFileParsing; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; using Zenith.Core; -namespace OpenLocoTool.Headers +namespace OpenLoco.ObjectEditor.Headers { [TypeConverter(typeof(ExpandableObjectConverter))] [Category("Header")] diff --git a/OpenLocoTool/Types/RiffWavHeader.cs b/Core/Types/RiffWavHeader.cs similarity index 95% rename from OpenLocoTool/Types/RiffWavHeader.cs rename to Core/Types/RiffWavHeader.cs index 0e19e6a1..24be7b8d 100644 --- a/OpenLocoTool/Types/RiffWavHeader.cs +++ b/Core/Types/RiffWavHeader.cs @@ -1,6 +1,6 @@ -using OpenLocoTool.DatFileParsing; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Types +namespace OpenLoco.ObjectEditor.Types { // http://www.topherlee.com/software/pcm-tut-wavformat.html#:~:text=The%20header%20is%20used%20to,well%20as%20its%20overall%20length.&text=Marks%20the%20file%20as%20a,are%20each%201%20byte%20long.&text=Size%20of%20the%20overall%20file,(32%2Dbit%20integer) [LocoStructSize(0x2C)] diff --git a/OpenLocoTool/Types/S5Header.cs b/Core/Types/S5Header.cs similarity index 92% rename from OpenLocoTool/Types/S5Header.cs rename to Core/Types/S5Header.cs index 4c2347ea..aa33606e 100644 --- a/OpenLocoTool/Types/S5Header.cs +++ b/Core/Types/S5Header.cs @@ -1,11 +1,10 @@ using System.ComponentModel; -using OpenLocoTool.Data; -using OpenLocoTool.DatFileParsing; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; using Zenith.Core; -namespace OpenLocoTool.Headers +namespace OpenLoco.ObjectEditor.Headers { - [TypeConverter(typeof(ExpandableObjectConverter))] [Category("Header")] [LocoStructSize(0x10)] diff --git a/OpenLocoTool/Types/StringTable.cs b/Core/Types/StringTable.cs similarity index 73% rename from OpenLocoTool/Types/StringTable.cs rename to Core/Types/StringTable.cs index f279b21b..039682c5 100644 --- a/OpenLocoTool/Types/StringTable.cs +++ b/Core/Types/StringTable.cs @@ -1,6 +1,7 @@ -using System.ComponentModel; +using OpenLoco.ObjectEditor.Data; +using System.ComponentModel; -namespace OpenLocoTool.Types +namespace OpenLoco.ObjectEditor.Types { [TypeConverter(typeof(ExpandableObjectConverter))] public class StringTable diff --git a/OpenLocoTool/Types/Typedefs.cs b/Core/Types/Typedefs.cs similarity index 90% rename from OpenLocoTool/Types/Typedefs.cs rename to Core/Types/Typedefs.cs index 4d3512f9..b4945f0b 100644 --- a/OpenLocoTool/Types/Typedefs.cs +++ b/Core/Types/Typedefs.cs @@ -12,9 +12,9 @@ global using MicroZ = System.Byte; global using SoundObjectId = System.Byte; using System.ComponentModel; -using OpenLocoTool.DatFileParsing; +using OpenLoco.ObjectEditor.DatFileParsing; -namespace OpenLocoTool.Types +namespace OpenLoco.ObjectEditor.Types { [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0x04)] diff --git a/DatFileRenamer/Program.cs b/DatFileRenamer/Program.cs index 128c0edd..9dc7ce23 100644 --- a/DatFileRenamer/Program.cs +++ b/DatFileRenamer/Program.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; Console.WriteLine("Dat File Renamer v0.1"); @@ -6,7 +6,7 @@ var currFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); currFolder = "Q:\\Games\\Locomotion\\ttd russia"; var datFiles = Directory.GetFiles(currFolder) - .Where(f => Path.GetExtension(f).ToLower() == ".dat"); + .Where(f => Path.GetExtension(f).Equals(".dat", StringComparison.OrdinalIgnoreCase)); var count = datFiles.Count(); Console.WriteLine($"Checking {count} files in {currFolder}"); diff --git a/OpenLocoToolGui/OpenLocoToolGui.csproj b/Gui/Gui.csproj similarity index 64% rename from OpenLocoToolGui/OpenLocoToolGui.csproj rename to Gui/Gui.csproj index 7bce48fd..5f081271 100644 --- a/OpenLocoToolGui/OpenLocoToolGui.csproj +++ b/Gui/Gui.csproj @@ -1,4 +1,4 @@ - + WinExe @@ -7,11 +7,20 @@ true enable true - loco_icon.ico + ..\loco_icon.ico - + + + + + + Always + + + Always + @@ -19,8 +28,8 @@ - - + + @@ -38,10 +47,4 @@ - - - PreserveNewest - - - \ No newline at end of file diff --git a/OpenLocoToolGui/GuiSettings.cs b/Gui/GuiSettings.cs similarity index 89% rename from OpenLocoToolGui/GuiSettings.cs rename to Gui/GuiSettings.cs index 7a430a55..3c8257b1 100644 --- a/OpenLocoToolGui/GuiSettings.cs +++ b/Gui/GuiSettings.cs @@ -1,4 +1,7 @@ -namespace OpenLocoToolGui +using System.Runtime.Serialization; +using System.Text.Json.Serialization; + +namespace OpenLoco.ObjectEditor.Gui { public class GuiSettings { @@ -33,9 +36,7 @@ public string DataDirectory public string PaletteFile { get; set; } = "palette.png"; public string IndexFileName { get; set; } = "objectIndex.json"; - public string G1DatFileName { get; set; } = "g1.DAT"; - public string GetObjDataFullPath(string fileName) => Path.Combine(ObjDataDirectory, fileName); public string GetDataFullPath(string fileName) => Path.Combine(DataDirectory, fileName); } diff --git a/OpenLocoToolGui/ImageHelpers.cs b/Gui/ImageHelpers.cs similarity index 95% rename from OpenLocoToolGui/ImageHelpers.cs rename to Gui/ImageHelpers.cs index 193527a5..544ec1db 100644 --- a/OpenLocoToolGui/ImageHelpers.cs +++ b/Gui/ImageHelpers.cs @@ -1,6 +1,6 @@ using System.Drawing.Imaging; -namespace OpenLocoToolGui +namespace OpenLoco.ObjectEditor.Gui { public static class ImageHelpers { diff --git a/OpenLocoToolGui/IndexObjectHeader.cs b/Gui/IndexObjectHeader.cs similarity index 51% rename from OpenLocoToolGui/IndexObjectHeader.cs rename to Gui/IndexObjectHeader.cs index 31bb06bf..1443d737 100644 --- a/OpenLocoToolGui/IndexObjectHeader.cs +++ b/Gui/IndexObjectHeader.cs @@ -1,7 +1,7 @@ -using OpenLocoTool.Headers; -using OpenLocoTool.Objects; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.Objects; -namespace OpenLocoToolGui +namespace OpenLoco.ObjectEditor.Gui { public record IndexObjectHeader(string Name, ObjectType ObjectType, UInt32 Checksum, VehicleType? VehicleType); } \ No newline at end of file diff --git a/OpenLocoToolGui/MainForm.Designer.cs b/Gui/MainForm.Designer.cs similarity index 95% rename from OpenLocoToolGui/MainForm.Designer.cs rename to Gui/MainForm.Designer.cs index 3c8bb431..cb0b0d32 100644 --- a/OpenLocoToolGui/MainForm.Designer.cs +++ b/Gui/MainForm.Designer.cs @@ -1,6 +1,6 @@ -using OpenLocoTool.Types; +using OpenLoco.ObjectEditor.Types; -namespace OpenLocoToolGui +namespace OpenLoco.ObjectEditor.Gui { partial class MainForm { @@ -40,7 +40,7 @@ private void InitializeComponent() flpImageTable = new FlowLayoutPanel(); scObjectAndLogs = new SplitContainer(); tcObjectOverview = new TabControl(); - tpObjectView = new TabPage(); + tpObjectEditor = new TabPage(); tcSubObjectView = new TabControl(); tpObjectObject = new TabPage(); pgObject = new PropertyGrid(); @@ -84,7 +84,7 @@ private void InitializeComponent() scHeaders = new SplitContainer(); pgS5Header = new PropertyGrid(); pgObjHeader = new PropertyGrid(); - tpObjectDump = new TabPage(); + tpObjectAnnotator = new TabPage(); scAnnnotationDump = new SplitContainer(); tvDATDumpAnnotations = new TreeView(); rtbDATDumpView = new RichTextBox(); @@ -106,8 +106,8 @@ private void InitializeComponent() dataDirectoriesToolStripMenuItem = new ToolStripMenuItem(); setDataDirectoryToolStripMenuItem = new ToolStripMenuItem(); toolStripSeparator2 = new ToolStripSeparator(); - setPaletteToolStripMenuItem = new ToolStripMenuItem(); recreateIndexToolStripMenuItem = new ToolStripMenuItem(); + setPaletteToolStripMenuItem = new ToolStripMenuItem(); imgContextMenu = new ContextMenuStrip(components); imgContextMenuSave = new ToolStripMenuItem(); goToHeaderInDumpToolStripMenuItem = new ToolStripMenuItem(); @@ -119,7 +119,7 @@ private void InitializeComponent() scObjectAndLogs.Panel2.SuspendLayout(); scObjectAndLogs.SuspendLayout(); tcObjectOverview.SuspendLayout(); - tpObjectView.SuspendLayout(); + tpObjectEditor.SuspendLayout(); tcSubObjectView.SuspendLayout(); tpObjectObject.SuspendLayout(); tpObjectStringTable.SuspendLayout(); @@ -141,7 +141,7 @@ private void InitializeComponent() scHeaders.Panel1.SuspendLayout(); scHeaders.Panel2.SuspendLayout(); scHeaders.SuspendLayout(); - tpObjectDump.SuspendLayout(); + tpObjectAnnotator.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)scAnnnotationDump).BeginInit(); scAnnnotationDump.Panel1.SuspendLayout(); scAnnnotationDump.Panel2.SuspendLayout(); @@ -229,8 +229,8 @@ private void InitializeComponent() // // tcObjectOverview // - tcObjectOverview.Controls.Add(tpObjectView); - tcObjectOverview.Controls.Add(tpObjectDump); + tcObjectOverview.Controls.Add(tpObjectEditor); + tcObjectOverview.Controls.Add(tpObjectAnnotator); tcObjectOverview.Dock = DockStyle.Fill; tcObjectOverview.Location = new Point(0, 25); tcObjectOverview.Name = "tcObjectOverview"; @@ -238,26 +238,24 @@ private void InitializeComponent() tcObjectOverview.Size = new Size(1415, 794); tcObjectOverview.TabIndex = 34; // - // tpObjectView + // tpObjectEditor // - tpObjectView.Controls.Add(tcSubObjectView); - tpObjectView.Controls.Add(scHeaders); - tpObjectView.Location = new Point(4, 24); - tpObjectView.Name = "tpObjectView"; - tpObjectView.Padding = new Padding(3); - tpObjectView.Size = new Size(1407, 766); - tpObjectView.TabIndex = 0; - tpObjectView.Text = "Object View"; - tpObjectView.UseVisualStyleBackColor = true; + tpObjectEditor.Controls.Add(tcSubObjectView); + tpObjectEditor.Controls.Add(scHeaders); + tpObjectEditor.Location = new Point(4, 24); + tpObjectEditor.Name = "tpObjectEditor"; + tpObjectEditor.Padding = new Padding(3); + tpObjectEditor.Size = new Size(1407, 766); + tpObjectEditor.TabIndex = 0; + tpObjectEditor.Text = "Editor"; + tpObjectEditor.UseVisualStyleBackColor = true; // // tcSubObjectView // tcSubObjectView.Controls.Add(tpObjectObject); tcSubObjectView.Controls.Add(tpObjectStringTable); tcSubObjectView.Controls.Add(tpObjectGraphicsTable); -#if DEBUG - tcSubObjectView.Controls.Add(tpObjectPreview); // whilst its WIP, let's leave this out -#endif + tcSubObjectView.Controls.Add(tpObjectPreview); tcSubObjectView.Dock = DockStyle.Fill; tcSubObjectView.Location = new Point(3, 123); tcSubObjectView.Margin = new Padding(2, 1, 2, 1); @@ -708,16 +706,16 @@ private void InitializeComponent() pgObjHeader.TabIndex = 23; pgObjHeader.ToolbarVisible = false; // - // tpObjectDump + // tpObjectAnnotator // - tpObjectDump.Controls.Add(scAnnnotationDump); - tpObjectDump.Location = new Point(4, 24); - tpObjectDump.Name = "tpObjectDump"; - tpObjectDump.Padding = new Padding(3); - tpObjectDump.Size = new Size(1407, 766); - tpObjectDump.TabIndex = 1; - tpObjectDump.Text = "Object Dump"; - tpObjectDump.UseVisualStyleBackColor = true; + tpObjectAnnotator.Controls.Add(scAnnnotationDump); + tpObjectAnnotator.Location = new Point(4, 24); + tpObjectAnnotator.Name = "tpObjectAnnotator"; + tpObjectAnnotator.Padding = new Padding(3); + tpObjectAnnotator.Size = new Size(1407, 766); + tpObjectAnnotator.TabIndex = 1; + tpObjectAnnotator.Text = "Hex Dump Annotator"; + tpObjectAnnotator.UseVisualStyleBackColor = true; // // scAnnnotationDump // @@ -885,7 +883,7 @@ private void InitializeComponent() // // fileToolStripMenuItem // - fileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { objectDirectoriesToolStripMenuItem, dataDirectoriesToolStripMenuItem, setPaletteToolStripMenuItem, recreateIndexToolStripMenuItem }); + fileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { objectDirectoriesToolStripMenuItem, dataDirectoriesToolStripMenuItem, recreateIndexToolStripMenuItem }); fileToolStripMenuItem.Name = "fileToolStripMenuItem"; fileToolStripMenuItem.Size = new Size(37, 20); fileToolStripMenuItem.Text = "File"; @@ -894,7 +892,7 @@ private void InitializeComponent() // objectDirectoriesToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { setObjectDirectoryToolStripMenuItem, toolStripSeparator1 }); objectDirectoriesToolStripMenuItem.Name = "objectDirectoriesToolStripMenuItem"; - objectDirectoriesToolStripMenuItem.Size = new Size(180, 22); + objectDirectoriesToolStripMenuItem.Size = new Size(176, 22); objectDirectoriesToolStripMenuItem.Text = "ObjData Directories"; // // setObjectDirectoryToolStripMenuItem @@ -913,7 +911,7 @@ private void InitializeComponent() // dataDirectoriesToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { setDataDirectoryToolStripMenuItem, toolStripSeparator2 }); dataDirectoriesToolStripMenuItem.Name = "dataDirectoriesToolStripMenuItem"; - dataDirectoriesToolStripMenuItem.Size = new Size(180, 22); + dataDirectoriesToolStripMenuItem.Size = new Size(176, 22); dataDirectoriesToolStripMenuItem.Text = "Data Directories"; // // setDataDirectoryToolStripMenuItem @@ -928,20 +926,19 @@ private void InitializeComponent() toolStripSeparator2.Name = "toolStripSeparator2"; toolStripSeparator2.Size = new Size(120, 6); // - // setPaletteToolStripMenuItem - // - setPaletteToolStripMenuItem.Name = "setPaletteToolStripMenuItem"; - setPaletteToolStripMenuItem.Size = new Size(180, 22); - setPaletteToolStripMenuItem.Text = "Load Palette Bitmap"; - setPaletteToolStripMenuItem.Click += setPaletteToolStripMenuItem_Click; - // // recreateIndexToolStripMenuItem // recreateIndexToolStripMenuItem.Name = "recreateIndexToolStripMenuItem"; - recreateIndexToolStripMenuItem.Size = new Size(180, 22); + recreateIndexToolStripMenuItem.Size = new Size(176, 22); recreateIndexToolStripMenuItem.Text = "Recreate Index"; recreateIndexToolStripMenuItem.Click += recreateIndexToolStripMenuItem_Click; // + // setPaletteToolStripMenuItem + // + setPaletteToolStripMenuItem.Name = "setPaletteToolStripMenuItem"; + setPaletteToolStripMenuItem.Size = new Size(180, 22); + setPaletteToolStripMenuItem.Text = "Load Palette Bitmap"; + // // imgContextMenu // imgContextMenu.ImageScalingSize = new Size(32, 32); @@ -992,7 +989,7 @@ private void InitializeComponent() MainMenuStrip = menuStrip; Name = "MainForm"; Padding = new Padding(4); - Text = "OpenLocoTool"; + Text = "OpenLoco Object Editor"; Load += MainForm_Load; scObjectAndLogs.Panel1.ResumeLayout(false); scObjectAndLogs.Panel1.PerformLayout(); @@ -1000,7 +997,7 @@ private void InitializeComponent() ((System.ComponentModel.ISupportInitialize)scObjectAndLogs).EndInit(); scObjectAndLogs.ResumeLayout(false); tcObjectOverview.ResumeLayout(false); - tpObjectView.ResumeLayout(false); + tpObjectEditor.ResumeLayout(false); tcSubObjectView.ResumeLayout(false); tpObjectObject.ResumeLayout(false); tpObjectStringTable.ResumeLayout(false); @@ -1029,7 +1026,7 @@ private void InitializeComponent() scHeaders.Panel2.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)scHeaders).EndInit(); scHeaders.ResumeLayout(false); - tpObjectDump.ResumeLayout(false); + tpObjectAnnotator.ResumeLayout(false); scAnnnotationDump.Panel1.ResumeLayout(false); scAnnnotationDump.Panel2.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)scAnnnotationDump).EndInit(); @@ -1076,8 +1073,8 @@ private void InitializeComponent() private CheckBox cbVanillaObjects; private TextBox tbCurrentPage; private TabControl tcObjectOverview; - private TabPage tpObjectView; - private TabPage tpObjectDump; + private TabPage tpObjectEditor; + private TabPage tpObjectAnnotator; private TabControl tcFileSelector; private TabPage tpCategory; private TreeView tvObjType; @@ -1127,16 +1124,14 @@ private void InitializeComponent() private GroupBox gbTilt; private GroupBox gbRotation; private GroupBox gbBogieSprites; - private TrackBar trackBar2; private Button btnBogieSpritePrevious; private Button btnBogieSpriteNext; private TextBox tbCurrentBogieSprite; private GroupBox gbBodySprites; - private TrackBar trackBar1; private Button btnBodySpritePrevious; private Button btnBodySpriteNext; private TextBox tbCurrentBodySprite; private TrackBar trbBogieSprites; private TrackBar trbBodySprites; } -} \ No newline at end of file +} diff --git a/OpenLocoToolGui/MainForm.cs b/Gui/MainForm.cs similarity index 77% rename from OpenLocoToolGui/MainForm.cs rename to Gui/MainForm.cs index 5eb2851b..4cbc34d4 100644 --- a/OpenLocoToolGui/MainForm.cs +++ b/Gui/MainForm.cs @@ -1,22 +1,26 @@ using NAudio.Gui; using NAudio.Wave; -using OpenLocoTool; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; -using OpenLocoTool.Objects; -using OpenLocoTool.Types; -using OpenLocoToolCommon; +using OpenLoco.ObjectEditor; +using OpenLoco.ObjectEditor.DatFileParsing; +using OpenLoco.ObjectEditor.Headers; +using OpenLoco.ObjectEditor.Objects; +using OpenLoco.ObjectEditor.Types; +using OpenLoco.ObjectEditor.Logging; using System.Data; using System.Drawing.Imaging; +using System.Reflection; +using OpenLoco.ObjectEditor.Data; +using Core.Objects.Sound; +using Zenith.Core; +using System.Text; -namespace OpenLocoToolGui +namespace OpenLoco.ObjectEditor.Gui { public partial class MainForm : Form { readonly MainFormModel model; readonly ILogger logger; - // could use pgObject.SelectedObjectsChanged event, but we'll just do this for now public IUiObject? CurrentUIObject { get => currentUIObject; @@ -28,7 +32,7 @@ public IUiObject? CurrentUIObject } IUiObject? currentUIObject; - IList CurrentUIImages + List CurrentUIImages { get => currentUIImages; set @@ -37,9 +41,9 @@ IList CurrentUIImages CurrentUIImagePageNumber = 0; } } - IList currentUIImages = new List(); + List currentUIImages = []; - IList currentUIObjectImages = new List(); + List currentUIObjectImages = []; int CurrentUIImagePageNumber { @@ -84,10 +88,25 @@ public MainForm() Level = LogLevel.Debug2 }; - model = new MainFormModel(logger, SettingsFile); + var assembly = Assembly.GetExecutingAssembly(); + var paletteFilename = "Gui.palette.png"; + using (var stream = assembly.GetManifestResourceStream(paletteFilename)) + { + var paletteBitmap = (Bitmap)Image.FromStream(stream!); + var palette = PaletteHelpers.PaletteFromBitmap(paletteBitmap); + model = new MainFormModel(logger, SettingsFile, palette); + } + + var versionFilename = "Gui.version.txt"; + using (var stream = assembly.GetManifestResourceStream(versionFilename)) + { + var buf = new byte[5]; + var arr = stream!.Read(buf); + Text = $"OpenLoco Object Editor - {Encoding.ASCII.GetString(buf)}"; + } } - private void MainForm_Load(object sender, EventArgs e) + void MainForm_Load(object sender, EventArgs e) { // pre-add any existing log lines lbLogs.Items.AddRange(((Logger)logger).Logs.ToArray()); @@ -120,7 +139,7 @@ void InitUI(bool vanillaOnly, string filter) // required to load the object type images from g1.dat if (Directory.Exists(model.Settings.DataDirectory)) { - model.LoadDataDirectory(model.Settings.DataDirectory); + _ = model.LoadDataDirectory(model.Settings.DataDirectory); } InitFileTreeView(vanillaOnly, filter); @@ -148,7 +167,7 @@ bool LoadObjDataDirectory(string directory, bool useExistingIndex) progressForm.CloseForm(); }); thread.Start(); - progressForm.ShowDialog(); + _ = progressForm.ShowDialog(); } return true; @@ -167,7 +186,7 @@ static Bitmap MakeOriginalLocoIcon(bool isOriginal) return bitmap; } - ImageList MakeImageList(MainFormModel model) + static ImageList MakeImageList(MainFormModel model, ILogger? logger = null) { var imageList = new ImageList(); var blankImage = MakeOriginalLocoIcon(false); @@ -180,7 +199,7 @@ ImageList MakeImageList(MainFormModel model) var objectTypes = Enum.GetValues().Length; var g1TabElements = model.G1.G1Elements.Skip(Constants.G1ObjectTabsOffset).Take(objectTypes).ToList(); - var images = CreateImages(g1TabElements, model.Palette, true).ToArray(); + var images = CreateImages(g1TabElements, model.Palette, true, logger).ToArray(); imageList.Images.AddRange(images); } @@ -200,7 +219,7 @@ static void AddObjectNode(string key, string text, string objName, uint objCheck } static bool IsOriginalFile(string name, uint checksum) - => OriginalObjectFiles.Names.TryGetValue(name.Trim(), out uint expectedChecksum) && expectedChecksum == checksum; + => OriginalObjectFiles.Names.TryGetValue(name.Trim(), out var expectedChecksum) && expectedChecksum == checksum; void InitFileTreeView(bool vanillaOnly, string fileFilter) { @@ -243,7 +262,7 @@ void InitCategoryTreeView(bool vanillaOnly, string fileFilter) tvObjType.ResumeLayout(true); } - private void InitDataCategoryTree() + void InitDataCategoryTree() { var dataNode = new TreeNode("Data"); @@ -253,42 +272,40 @@ private void InitDataCategoryTree() var tutorialsNode = new TreeNode("Tutorials"); var miscNode = new TreeNode("Uncategorised"); - //var unknownNode = new TreeNode("Unknown"); - var allDataFiles = Directory.GetFiles(model.Settings.DataDirectory).Select(f => Path.GetFileName(f).ToLower()); // music foreach (var music in model.Music) { var displayName = $"{OriginalDataFiles.Music[music.Key]} ({music.Key})"; - musicNode.Nodes.Add(music.Key, displayName, 1, 1); + _ = musicNode.Nodes.Add(music.Key, displayName, 1, 1); } //misc tracks foreach (var miscTrack in model.MiscellaneousTracks) { var displayName = $"{OriginalDataFiles.MiscellaneousTracks[miscTrack.Key]} ({miscTrack.Key})"; - miscTrackNode.Nodes.Add(miscTrack.Key, displayName, 1, 1); + _ = miscTrackNode.Nodes.Add(miscTrack.Key, displayName, 1, 1); } // sound effects //foreach (var sfx in model.SoundEffects) { - var displayName = OriginalDataFiles.SoundEffect; //$"{OriginalDataFiles.SoundEffect} ({sfx.Key})"; - sfxNode.Nodes.Add(displayName, displayName, 1, 1); + var displayName = OriginalDataFiles.SoundEffect; + _ = sfxNode.Nodes.Add(displayName, displayName, 1, 1); } // tutorials foreach (var tut in model.Tutorials) { - tutorialsNode.Nodes.Add(tut.Key, tut.Key, 1, 1); + _ = tutorialsNode.Nodes.Add(tut.Key, tut.Key, 1, 1); tvUniqueLoadValues[tut.Key] = LoadNull; } // uncategorised foreach (var misc in model.MiscFiles) { - miscNode.Nodes.Add(misc, misc, 1, 1); + _ = miscNode.Nodes.Add(misc, misc, 1, 1); if (misc == "g1.dat") { @@ -300,16 +317,16 @@ private void InitDataCategoryTree() } } - dataNode.Nodes.Add(musicNode); - dataNode.Nodes.Add(miscTrackNode); - dataNode.Nodes.Add(sfxNode); - dataNode.Nodes.Add(tutorialsNode); - dataNode.Nodes.Add(miscNode); + _ = dataNode.Nodes.Add(musicNode); + _ = dataNode.Nodes.Add(miscTrackNode); + _ = dataNode.Nodes.Add(sfxNode); + _ = dataNode.Nodes.Add(tutorialsNode); + _ = dataNode.Nodes.Add(miscNode); - tvObjType.Nodes.Add(dataNode); + _ = tvObjType.Nodes.Add(dataNode); } - private void InitObjectCategoryTree(bool vanillaOnly, string fileFilter) + void InitObjectCategoryTree(bool vanillaOnly, string fileFilter) { var filteredFiles = string.IsNullOrEmpty(fileFilter) ? model.HeaderIndex @@ -342,7 +359,7 @@ private void InitObjectCategoryTree(bool vanillaOnly, string fileFilter) AddObjectNode(veh.Key, veh.Value.Name, veh.Value.Name, veh.Value.Checksum, vehicleTypeNode); } - objTypeNode.Nodes.Add(vehicleTypeNode); + _ = objTypeNode.Nodes.Add(vehicleTypeNode); } } @@ -351,7 +368,7 @@ private void InitObjectCategoryTree(bool vanillaOnly, string fileFilter) var objDataNode = new TreeNode("ObjData"); objDataNode.Nodes.AddRange([.. nodesToAdd]); - tvObjType.Nodes.Add(objDataNode); + _ = tvObjType.Nodes.Add(objDataNode); tvObjType.Sort(); } @@ -398,7 +415,7 @@ void InitToolStripMenuItems() } } - private void setObjectDirectoryToolStripMenuItem_Click(object sender, EventArgs e) + void setObjectDirectoryToolStripMenuItem_Click(object sender, EventArgs e) { if (objectDirBrowser.ShowDialog(this) == DialogResult.OK) { @@ -406,7 +423,7 @@ private void setObjectDirectoryToolStripMenuItem_Click(object sender, EventArgs } } - private void setObjectDirectoryToolStripMenuItem_ClickCore(string path) + void setObjectDirectoryToolStripMenuItem_ClickCore(string path) { if (LoadObjDataDirectory(path, true)) { @@ -414,7 +431,7 @@ private void setObjectDirectoryToolStripMenuItem_ClickCore(string path) } } - private void setDataDirectoryToolStripMenuItem_Click(object sender, EventArgs e) + void setDataDirectoryToolStripMenuItem_Click(object sender, EventArgs e) { if (objectDirBrowser.ShowDialog(this) == DialogResult.OK) { @@ -422,7 +439,7 @@ private void setDataDirectoryToolStripMenuItem_Click(object sender, EventArgs e) } } - private void setDataDirectoryToolStripMenuItem_ClickCore(string path) + void setDataDirectoryToolStripMenuItem_ClickCore(string path) { if (model.LoadDataDirectory(path)) { @@ -432,7 +449,7 @@ private void setDataDirectoryToolStripMenuItem_ClickCore(string path) IEnumerable GetPictureBoxesForPage(int page) => CurrentUIImages.Skip(page * imagesPerPage).Take(imagesPerPage); - private void recreateIndexToolStripMenuItem_Click(object sender, EventArgs e) + void recreateIndexToolStripMenuItem_Click(object sender, EventArgs e) { if (LoadObjDataDirectory(model.Settings.ObjDataDirectory, false)) { @@ -445,6 +462,19 @@ void tbFileFilter_TextChanged(object sender, EventArgs e) void LoadDataDump(string path, bool isG1 = false) { + try + { + LoadDataDumpCore(path, isG1); + } + catch (Exception ex) + { + logger.Error(ex); + } + } + + void LoadDataDumpCore(string path, bool isG1 = false) + { + if (File.Exists(path)) { var byteList = File.ReadAllBytes(path); @@ -484,12 +514,12 @@ static string constructAnnotationText(Annotation annotation) DATDumpAnnotationIdentifiers[annotationText] = (annotation.Start, annotation.End); if (annotation.Parent == null) { - tvDATDumpAnnotations.Nodes.Add(parents[constructAnnotationText(annotation)]); + _ = tvDATDumpAnnotations.Nodes.Add(parents[constructAnnotationText(annotation)]); } else if (parents.ContainsKey(constructAnnotationText(annotation.Parent))) { var parentText = constructAnnotationText(annotation.Parent); - parents[parentText].Nodes.Add(parents[annotationText]); + _ = parents[parentText].Nodes.Add(parents[annotationText]); if (annotation.Parent.Name == "Headers") { @@ -508,9 +538,9 @@ static string constructAnnotationText(Annotation annotation) } } - static bool MusicIsPlaying { get; set; } = false; + static bool MusicIsPlaying { get; set; } - private void LoadAndPlaySound(byte[] data, string soundName) + void LoadAndPlaySound(byte[] data, string soundName) { var (header, pcmData) = SawyerStreamReader.LoadWavFile(data); var uiSoundObj = new UiSoundObject { Data = pcmData, Header = header, SoundName = soundName }; @@ -542,7 +572,7 @@ void LoadSoundEffectFile(byte[] data) CurrentUIObject = uiSoundObjectList; } - FlowLayoutPanel GetNewSoundUIFLP() + static FlowLayoutPanel GetNewSoundUIFLP() { var flp = new FlowLayoutPanel { @@ -581,40 +611,7 @@ public void ImportWave(string soundNameToUpdate) if (OriginalDataFiles.Music.ContainsKey(soundNameToUpdate)) { logger.Info($"Replacing music track {soundNameToUpdate} with {openFileDialog.FileName}"); - //CurrentUIObject = new UiSoundObject { Header = header, Data = pcmData, SoundName = ((UiSoundObject)CurrentUIObject).SoundName }; - - //var flp = flpImageTable.Controls.OfType().Single(); - //var pn = flpImageTable.Controls.OfType().Single(pn => pn.Tag == soundNameToUpdate); - //flp.Controls.Remove(pn); - //flp.Controls.Add(CreateSoundUI(pcmData, header, soundNameToUpdate)); } - //else if (Enum.GetValues().Select(sid => sid.ToString()).Contains(soundNameToUpdate)) - //{ - // logger.Info($"Replacing sound effect {soundNameToUpdate} with {openFileDialog.FileName}"); - // var wavHeader = new WaveFormatEx(1, (short)header.NumberOfChannels, (int)header.SampleRate, (int)header.ByteRate, 512, 4096, 0); - - // var newSoundUi = CreateSoundUI(pcmData, wavHeader, soundNameToUpdate); - // var newFlp = GetNewSoundUIFLP(); - // var oldFlp = flpImageTable.Controls.OfType().Single(); - // var oldPn = oldFlp.Controls.OfType().Single(pn => pn.Tag == soundNameToUpdate); - // var oldIndex = oldFlp.Controls.IndexOf(oldPn); - - // while (oldFlp.Controls.Count > 0) - // { - // if (newFlp.Controls.Count == oldIndex) - // { - // oldFlp.Controls.RemoveAt(0); - // newFlp.Controls.Add(newSoundUi); - // } - // else - // { - // newFlp.Controls.Add(oldFlp.Controls[0]); - // } - // } - - // flpImageTable.Controls.Clear(); - // flpImageTable.Controls.Add(newFlp); - //} else { logger.Warning($"Sound name {soundNameToUpdate} was not recognised - no action will be taken."); @@ -646,9 +643,7 @@ public void ImportImages() } public void RefreshImageControls() - { - CurrentUIImages = CreateImageControls(currentUIObjectImages).ToList(); - } + => CurrentUIImages = CreateImageControls(currentUIObjectImages).ToList(); public void ExportImages() { @@ -678,11 +673,12 @@ public string GetImageName(IUiObject? uiObj, int counter) { if (uiLocoObj.LocoObject.Object is IImageTableStrings its) { - if (!its.TryGetImageName(counter, out string? value) || value == null) + if (!its.TryGetImageName(counter, out var value) || value == null) { logger.Warning($"Object \"{uiLocoObj.DatFileInfo.S5Header.Name}\" does not have an image for id {counter}"); return $"{uiLocoObj.DatFileInfo.S5Header.Name}-{counter}"; } + return value; } } @@ -700,14 +696,9 @@ public void ExportMusic(UiSoundObject uiSoundObj) sfDialog.RestoreDirectory = true; // suggested filename for the save dialog - if (OriginalDataFiles.Music.TryGetValue(tvObjType.SelectedNode.Name, out string? value)) - { - sfDialog.FileName = value; - } - else - { - sfDialog.FileName = "export.wav"; - } + sfDialog.FileName = OriginalDataFiles.Music.TryGetValue(tvObjType.SelectedNode.Name, out var value) + ? value + : "export.wav"; if (sfDialog.ShowDialog() == DialogResult.OK) { @@ -750,15 +741,13 @@ Panel CreateSoundUI(UiSoundObject uiSoundObj) { Size = new Size(64, 64), Text = "Export", - //Tag = soundName, }; - exportButton.Click += (args, sender) => ExportMusic(uiSoundObj); // isRiff ? ExportMusic(pcmData, waveHeader) : ExportSoundEffect(pcmData, waveHeader); + exportButton.Click += (args, sender) => ExportMusic(uiSoundObj); var importButton = new Button { Size = new Size(64, 64), Text = "Import", - //Tag = soundName, }; importButton.Click += (args, sender) => ImportWave(uiSoundObj.SoundName); @@ -793,6 +782,7 @@ Panel CreateSoundUI(UiSoundObject uiSoundObj) { Thread.Sleep(100); // give time to wait until previous sound is disposed } + CurrentWOEvent?.Dispose(); using (var ms = new MemoryStream(uiSoundObj.Data)) @@ -823,7 +813,7 @@ Panel CreateSoundUI(UiSoundObject uiSoundObj) var percentPlayed = progressInBytes / (double)uiSoundObj.Data.Length; var newX = (int)(percentPlayed * waveViewer.Width); var diff = newX - prevX; - g.FillRectangle(transparentBrush, new Rectangle(prevX, 0, (int)diff, waveViewer.Height)); + g.FillRectangle(transparentBrush, new Rectangle(prevX, 0, diff, waveViewer.Height)); prevX = newX; Thread.Sleep(50); @@ -832,24 +822,29 @@ Panel CreateSoundUI(UiSoundObject uiSoundObj) // complete overlay to the very end g.FillRectangle(transparentBrush, new Rectangle(prevX, 0, waveViewer.Width - prevX, waveViewer.Height)); } + CurrentWOEvent = null; MusicIsPlaying = false; }); }; // text - var tb = new TextBox(); - tb.Text = uiSoundObj.SoundName; - tb.Enabled = false; - tb.Width = 128; - tb.Height = 32; - tb.Dock = DockStyle.Top; + var tb = new TextBox + { + Text = uiSoundObj.SoundName, + Enabled = false, + Width = 128, + Height = 32, + Dock = DockStyle.Top + }; // object controls - var flp = new FlowLayoutPanel(); - flp.FlowDirection = FlowDirection.LeftToRight; - flp.Dock = DockStyle.Fill; - flp.AutoSize = true; + var flp = new FlowLayoutPanel + { + FlowDirection = FlowDirection.LeftToRight, + Dock = DockStyle.Fill, + AutoSize = true + }; flp.Controls.Add(tb); flp.Controls.Add(waveViewer); @@ -859,11 +854,13 @@ Panel CreateSoundUI(UiSoundObject uiSoundObj) flp.Controls.Add(exportButton); flp.Controls.Add(importButton); - var pn = new Panel(); - pn.Dock = DockStyle.Fill; - pn.BorderStyle = BorderStyle.Fixed3D; - pn.AutoSize = true; - pn.Tag = uiSoundObj.SoundName; + var pn = new Panel + { + Dock = DockStyle.Fill, + BorderStyle = BorderStyle.Fixed3D, + AutoSize = true, + Tag = uiSoundObj.SoundName + }; pn.Controls.Add(flp); pn.Controls.Add(tb); @@ -875,7 +872,7 @@ Panel CreateSoundUI(UiSoundObject uiSoundObj) void LoadNull(string dataKey) { CurrentUIObject = null; - CurrentUIImages = new List(); + CurrentUIImages = []; } void LoadG1(string filename) @@ -916,26 +913,30 @@ void tv_AfterSelect(object sender, TreeViewEventArgs e) var misc = model.MiscellaneousTracks[e.Node.Name]; LoadAndPlaySound(misc, e.Node.Text); } - else if (OriginalDataFiles.SoundEffect.Equals(e.Node.Name, StringComparison.InvariantCultureIgnoreCase)) + else if (OriginalDataFiles.SoundEffect.Equals(e.Node.Name, StringComparison.OrdinalIgnoreCase)) { logger.Debug($"Loading sound effects for {e.Node.Name}"); var sfx = model.SoundEffects[e.Node.Name]; LoadSoundEffectFile(sfx); } - else if (Path.GetExtension(e.Node.Name).Equals(".dat", StringComparison.CurrentCultureIgnoreCase)) + else if (Path.GetExtension(e.Node.Name).Equals(".dat", StringComparison.OrdinalIgnoreCase)) { logger.Debug($"Loading object {e.Node.Name}"); var filename = e.Node.Name; CurrentUIObject = model.LoadAndCacheObject(filename); - //try +#if DEBUG + LoadDataDump(filename); +#else + try { LoadDataDump(filename); } - //catch (Exception ex) + catch (Exception ex) { - // logger?.Error(ex, $"Unable to annotate file \"{filename}\""); + logger?.Error(ex, $"Unable to annotate file \"{filename}\""); } +#endif } flpImageTable.ResumeLayout(true); @@ -950,6 +951,7 @@ public int ImageScale { value = 1; } + if (value != imageScale) { imageScale = value; @@ -1002,7 +1004,7 @@ IEnumerable CreateImageControls(IEnumerable images) } } - IEnumerable CreateImages(List G1Elements, Color[] palette, bool useTransparency = false) + static IEnumerable CreateImages(List G1Elements, Color[] palette, bool useTransparency = false, ILogger? logger = null) { if (palette is null) { @@ -1015,7 +1017,7 @@ IEnumerable CreateImages(List G1Elements, Color[] palette, var currElement = G1Elements[i]; if (currElement.ImageData.Length == 0) { - logger.Info($"skipped loading g1 element {i} with 0 length"); + logger?.Info($"skipped loading g1 element {i} with 0 length"); continue; } @@ -1082,27 +1084,27 @@ IEnumerable CreateImages(List G1Elements, Color[] palette, return dstImg; } - void SelectNewPalette() - { - using (var openFileDialog = new OpenFileDialog()) - { - openFileDialog.InitialDirectory = Directory.GetCurrentDirectory(); - openFileDialog.Filter = "Palette Image Files(*.png)|*.png|All files (*.*)|*.*"; - openFileDialog.FilterIndex = 1; - openFileDialog.RestoreDirectory = true; + //void SelectNewPalette() + //{ + // using (var openFileDialog = new OpenFileDialog()) + // { + // openFileDialog.InitialDirectory = Directory.GetCurrentDirectory(); + // openFileDialog.Filter = "Palette Image Files(*.png)|*.png|All files (*.*)|*.*"; + // openFileDialog.FilterIndex = 1; + // openFileDialog.RestoreDirectory = true; - if (openFileDialog.ShowDialog() == DialogResult.OK) - { - model.PaletteFile = openFileDialog.FileName; - RefreshObjectUI(); - } - } - } + // if (openFileDialog.ShowDialog() == DialogResult.OK) + // { + // model.PaletteFile = openFileDialog.FileName; + // RefreshObjectUI(); + // } + // } + //} - private void setPaletteToolStripMenuItem_Click(object sender, EventArgs e) - => SelectNewPalette(); + //void setPaletteToolStripMenuItem_Click(object sender, EventArgs e) + // => SelectNewPalette(); - private void RefreshObjectUI() + void RefreshObjectUI() { MusicIsPlaying = false; @@ -1119,7 +1121,6 @@ private void RefreshObjectUI() } var selectedFile = tvFileTree.SelectedNode.Text; - MessageBox.Show($"File \"{selectedFile}\" couldn't be loaded. Does it exist? Suggest recreating object index."); logger.Error($"File \"{selectedFile}\" couldn't be loaded. Does it exist? Suggest recreating object index."); return; } @@ -1127,43 +1128,45 @@ private void RefreshObjectUI() flpImageTable.SuspendLayout(); flpImageTable.Controls.Clear(); - CurrentUIImages = new List(); + CurrentUIImages = []; if (CurrentUIObject is UiLocoObject uiLocoObj) { tsImageTable.Enabled = true; tsImageTable.Visible = true; - if (uiLocoObj.LocoObject.G1Elements != null && uiLocoObj.LocoObject.G1Elements.Count != 0) + pgS5Header.SelectedObject = uiLocoObj.DatFileInfo.S5Header; + pgObjHeader.SelectedObject = uiLocoObj.DatFileInfo.ObjectHeader; + + if (uiLocoObj.LocoObject != null) { - if (model.Palette is null) + if (uiLocoObj.LocoObject.G1Elements != null && uiLocoObj.LocoObject.G1Elements.Count != 0) { - MessageBox.Show("No palette file loaded - please load one from File -> Load Palette. You can use palette.png in the top level folder of this repo."); - logger.Error("No palette file loaded - please load one from File -> Load Palette. You can use palette.png in the top level folder of this repo."); - return; + //if (model.Palette is null) + //{ + // logger.Error("No palette file loaded - please load one from File -> Load Palette. You can use palette.png in the top level folder of this repo."); + // return; + //} + + currentUIObjectImages = CreateImages(uiLocoObj.LocoObject.G1Elements, model.Palette, logger: logger).ToList(); + RefreshImageControls(); + RefreshVehiclePreview(uiLocoObj.LocoObject); } - currentUIObjectImages = CreateImages(uiLocoObj.LocoObject.G1Elements, model.Palette).ToList(); - RefreshImageControls(); - RefreshVehiclePreview(uiLocoObj.LocoObject); - } + if (uiLocoObj.LocoObject.Object is SoundObject soundObject) + { + tsImageTable.Enabled = false; + tsImageTable.Visible = false; - if (uiLocoObj.LocoObject.Object is SoundObject soundObject) - { - tsImageTable.Enabled = false; - tsImageTable.Visible = false; + var hdr = soundObject.SoundObjectData.PcmHeader; + var text = uiLocoObj.LocoObject.StringTable.Table["Name"][LanguageId.english_uk] ?? ""; + var pn = CreateSoundUI(new UiSoundObject { Data = soundObject.PcmData, Header = SawyerStreamWriter.WaveFormatExToRiff(hdr, soundObject.PcmData.Length), SoundName = text }); + flpImageTable.Controls.Add(pn); + } - var hdr = soundObject.SoundObjectData.PcmHeader; - var text = uiLocoObj.LocoObject.StringTable.Table["Name"][LanguageId.english_uk] ?? ""; - var pn = CreateSoundUI(new UiSoundObject { Data = soundObject.PcmData, Header = SawyerStreamWriter.WaveFormatExToRiff(hdr, soundObject.PcmData.Length), SoundName = text }); - flpImageTable.Controls.Add(pn); + pgObject.SelectedObject = uiLocoObj.LocoObject.Object; + ucStringTable.SetDataBinding(uiLocoObj.LocoObject.StringTable); } - - pgS5Header.SelectedObject = uiLocoObj.DatFileInfo.S5Header; - pgObjHeader.SelectedObject = uiLocoObj.DatFileInfo.ObjectHeader; - pgObject.SelectedObject = uiLocoObj.LocoObject.Object; - ucStringTable.SetDataBinding(uiLocoObj.LocoObject.StringTable); - //pgS5Header.SelectedObject = CurrentUIObject; // done above with flpImageTable } if (CurrentUIObject is UiSoundObjectList uiSoundObjList) @@ -1177,8 +1180,6 @@ private void RefreshObjectUI() foreach (var x in uiSoundObjList.Audio) { var pn = CreateSoundUI(x); - //flpImageTable.Controls.Add(pn); - //var pn = CreateSoundUI(pcmData, header, $"{(SoundId)i++}"); flp.Controls.Add(pn); } @@ -1188,7 +1189,7 @@ private void RefreshObjectUI() flpImageTable.ResumeLayout(true); } - private void imgContextMenuSave_Click(object sender, EventArgs e) + void imgContextMenuSave_Click(object sender, EventArgs e) { if (imgContextMenu.SourceControl is PictureBox pb) { @@ -1208,10 +1209,10 @@ private void imgContextMenuSave_Click(object sender, EventArgs e) } } - private void btnPagePrevious_Click(object sender, EventArgs e) + void btnPagePrevious_Click(object sender, EventArgs e) => CurrentUIImagePageNumber = Math.Max(CurrentUIImagePageNumber - 1, 0); - private void btnPageNext_Click(object sender, EventArgs e) + void btnPageNext_Click(object sender, EventArgs e) { if (currentUIImages?.Count > 0) { @@ -1219,7 +1220,7 @@ private void btnPageNext_Click(object sender, EventArgs e) } } - private void dataDumpAnnotations_AfterSelect(object sender, TreeViewEventArgs e) + void dataDumpAnnotations_AfterSelect(object sender, TreeViewEventArgs e) { int dumpPositionToRTBPosition(int position) => rtbDATDumpView.GetFirstCharIndexFromLine( position / bytesPerDumpLine) @@ -1238,7 +1239,7 @@ int dumpPositionToRTBPosition(int position) => rtbDATDumpView.GetFirstCharIndexF } } - private void headerToolStripMenuItem_Click(object sender, EventArgs e) + void headerToolStripMenuItem_Click(object sender, EventArgs e) { if (imgContextMenu.SourceControl is PictureBox pb) { @@ -1249,12 +1250,12 @@ private void headerToolStripMenuItem_Click(object sender, EventArgs e) tcObjectOverview.SelectedIndex = 1; tvDATDumpAnnotations.SelectedNode = value; dataDumpAnnotations_AfterSelect(sender, new TreeViewEventArgs(value)); - tvDATDumpAnnotations.Focus(); + _ = tvDATDumpAnnotations.Focus(); } } } - private void pictureDataToolStripMenuItem_Click(object sender, EventArgs e) + void pictureDataToolStripMenuItem_Click(object sender, EventArgs e) { if (imgContextMenu.SourceControl is PictureBox pb) { @@ -1265,19 +1266,22 @@ private void pictureDataToolStripMenuItem_Click(object sender, EventArgs e) tcObjectOverview.SelectedIndex = 1; tvDATDumpAnnotations.SelectedNode = value; dataDumpAnnotations_AfterSelect(sender, new TreeViewEventArgs(value)); - tvDATDumpAnnotations.Focus(); + _ = tvDATDumpAnnotations.Focus(); } } } - private void cbVanillaObjects_CheckedChanged(object sender, EventArgs e) => InitUI(cbVanillaObjects.Checked, tbFileFilter.Text); + void cbVanillaObjects_CheckedChanged(object sender, EventArgs e) + => InitUI(cbVanillaObjects.Checked, tbFileFilter.Text); - private void lbLogs_DrawItem(object sender, DrawItemEventArgs e) + void lbLogs_DrawItem(object sender, DrawItemEventArgs e) { e.DrawBackground(); if (e.Index < 0) + { return; + } var item = (LogLine)lbLogs.Items[e.Index]; var backgroundColour = item.Level switch @@ -1304,12 +1308,15 @@ private void lbLogs_DrawItem(object sender, DrawItemEventArgs e) LogLevel.Error => Brushes.Red, _ => throw new NotImplementedException(), }; - e.Graphics.DrawString(item.ToString(), e.Font, foregroundBrush, e.Bounds.Left, e.Bounds.Y); + + Verify.NotNull(e.Font); + + e.Graphics.DrawString(item.ToString(), e.Font!, foregroundBrush, e.Bounds.Left, e.Bounds.Y); //e.DrawFocusRectangle(); } - private void btnSave_Click(object sender, EventArgs e) + void btnSave_Click(object sender, EventArgs e) { if (CurrentUIObject is null) { @@ -1324,64 +1331,56 @@ private void btnSave_Click(object sender, EventArgs e) { var filename = saveFileDialog1.FileName; - //try - //{ - var exists = File.Exists(filename); - - if (currentUIObject is UiLocoObject obj) - { - MainFormModel.SaveFile(filename, obj); - } - else if (currentUIObject is UiSoundObjectList uiSoundObjList) + try { - if (tvObjType.SelectedNode.Name == "css1.dat") - { - var rawBytes = SawyerStreamWriter.SaveSoundEffectsToCSS(uiSoundObjList.Audio.Select(uis => (uis.Header, uis.Data)).ToList()); - File.WriteAllBytes(filename, rawBytes); + var exists = File.Exists(filename); + if (currentUIObject is UiLocoObject obj) + { + model.SaveFile(filename, obj); } - else + else if (currentUIObject is UiSoundObjectList uiSoundObjList) + { + if (tvObjType.SelectedNode.Name == "css1.dat") + { + var rawBytes = SawyerStreamWriter.SaveSoundEffectsToCSS(uiSoundObjList.Audio.Select(uis => (uis.Header, uis.Data)).ToList()); + File.WriteAllBytes(filename, rawBytes); + } + else + { + // save regular sound + } + } + + if (!exists) { - // save regular sound + // we made a new file (as opposed to overwriting an existing one) so lets update the UI to show it + InitUI(cbVanillaObjects.Checked, tbFileFilter.Text); } - } - if (!exists) + logger.Info($"File \"{filename}\" saved successfully"); + } + catch (Exception ex) { - // we made a new file (as opposed to overwriting an existing one) so lets update the UI to show it - InitUI(cbVanillaObjects.Checked, tbFileFilter.Text); + logger.Error($"Error saving \"{filename}\": {ex.Message}"); } - - logger.Info($"File \"{filename}\" saved successfully"); - //} - //catch (Exception ex) - //{ - // logger.Error($"Error saving \"{filename}\": {ex.Message}"); - // MessageBox.Show($"Error saving \"{filename}\": {ex.Message}"); - //} } } - private void tsbImportFromDirectory_Click(object sender, EventArgs e) - { - ImportImages(); - } + void tsbImportFromDirectory_Click(object sender, EventArgs e) => ImportImages(); - private void tsbExportToDirectory_Click(object sender, EventArgs e) - { - ExportImages(); - } + void tsbExportToDirectory_Click(object sender, EventArgs e) => ExportImages(); - private void tstbImageScaling_TextChanged(object sender, EventArgs e) + void tstbImageScaling_TextChanged(object sender, EventArgs e) { - var parsed = int.TryParse(tstbImageScaling.Text, out int scale); + var parsed = int.TryParse(tstbImageScaling.Text, out var scale); if (parsed) { ImageScale = scale; } } - int GetTiltCount(VehicleObject veh, int bodySprite) + static int GetTiltCount(VehicleObject veh, int bodySprite) { var count = 0; if (veh.BodySprites[bodySprite].Flags.HasFlag(BodySpriteFlags.HasSprites)) @@ -1404,7 +1403,7 @@ int GetTiltCount(VehicleObject veh, int bodySprite) void RefreshVehiclePreview(ILocoObject obj) { - if (obj.Object is not VehicleObject veh) + if (obj.Object is not VehicleObject) { return; } @@ -1426,17 +1425,20 @@ public void SetBodyAndBogieImages() var veh = (CurrentUIObject as UiLocoObject)?.LocoObject.Object as VehicleObject; if (veh == null) + { return; + } var currentBody = veh.BodySprites[CurrentBodySprite]; var tilt = (BodySpriteSlopeType)trbTilt.Value; - if (currentBody.NumImages == 0 || !currentBody.ImageIds.TryGetValue(tilt, out List? value) || value == null) + if (currentBody.NumImages == 0 || !currentBody.ImageIds.TryGetValue(tilt, out var value) || value == null) { pbVehicleBodyPreview.Image = null; return; } + trbRotate.Maximum = value.Count; pbVehicleBodyPreview.Image = currentUIObjectImages[value[0] + vehicleRotateIndex]; @@ -1479,10 +1481,14 @@ public int CurrentBodySprite set { if (value < 0) + { value = 0; + } if (value > trbBodySprites.Maximum) + { value = trbBodySprites.Maximum; + } currentBodySprite = value; @@ -1494,22 +1500,13 @@ public int CurrentBodySprite SetBodyAndBogieImages(); } } - public int currentBodySprite = 0; + public int currentBodySprite; - private void trbBodySprites_Scroll(object sender, EventArgs e) - { - CurrentBodySprite = trbBodySprites.Value; - } + void trbBodySprites_Scroll(object sender, EventArgs e) => CurrentBodySprite = trbBodySprites.Value; - private void btnBodySpriteNext_Click(object sender, EventArgs e) - { - CurrentBodySprite++; - } + void btnBodySpriteNext_Click(object sender, EventArgs e) => CurrentBodySprite++; - private void btnBodySpritePrevious_Click(object sender, EventArgs e) - { - CurrentBodySprite--; - } + void btnBodySpritePrevious_Click(object sender, EventArgs e) => CurrentBodySprite--; #endregion @@ -1521,10 +1518,14 @@ public int CurrentBogieSprite set { if (value < 0) + { value = 0; + } if (value > trbBogieSprites.Maximum) + { value = trbBogieSprites.Maximum; + } currentBogieSprite = value; @@ -1533,22 +1534,13 @@ public int CurrentBogieSprite SetBodyAndBogieImages(); } } - public int currentBogieSprite = 0; + public int currentBogieSprite; - private void trbBogieSprites_Scroll(object sender, EventArgs e) - { - CurrentBogieSprite = trbBogieSprites.Value; - } + void trbBogieSprites_Scroll(object sender, EventArgs e) => CurrentBogieSprite = trbBogieSprites.Value; - private void btnBogieSpriteNext_Click(object sender, EventArgs e) - { - CurrentBogieSprite++; - } + void btnBogieSpriteNext_Click(object sender, EventArgs e) => CurrentBogieSprite++; - private void btnBogieSpritePrevious_Click(object sender, EventArgs e) - { - CurrentBogieSprite--; - } + void btnBogieSpritePrevious_Click(object sender, EventArgs e) => CurrentBogieSprite--; #endregion @@ -1560,10 +1552,14 @@ public int VehicleRotateIndex set { if (value > trbRotate.Maximum) + { value = trbRotate.Maximum; + } if (value < 0) + { value = 0; + } vehicleRotateIndex = value; @@ -1573,21 +1569,12 @@ public int VehicleRotateIndex SetBodyAndBogieImages(); } } - public int vehicleRotateIndex = 0; + public int vehicleRotateIndex; - private void trbRotate_Scroll(object sender, EventArgs e) - { - VehicleRotateIndex = trbRotate.Value; - } + void trbRotate_Scroll(object sender, EventArgs e) => VehicleRotateIndex = trbRotate.Value; - private void btnRotateRight_Click(object sender, EventArgs e) - { - VehicleRotateIndex++; - } - private void btnRotateLeft_Click(object sender, EventArgs e) - { - VehicleRotateIndex--; - } + void btnRotateRight_Click(object sender, EventArgs e) => VehicleRotateIndex++; + void btnRotateLeft_Click(object sender, EventArgs e) => VehicleRotateIndex--; #endregion @@ -1599,10 +1586,14 @@ public int VehicleTiltIndex set { if (value < 0) + { value = 0; + } if (value > trbTilt.Maximum) + { value = trbTilt.Maximum; + } vehicleTiltIndex = value; @@ -1622,22 +1613,13 @@ public int VehicleTiltIndex SetBodyAndBogieImages(); } } - public int vehicleTiltIndex = 0; + public int vehicleTiltIndex; - private void trbTilt_Scroll(object sender, EventArgs e) - { - VehicleTiltIndex = trbTilt.Value; - } + void trbTilt_Scroll(object sender, EventArgs e) => VehicleTiltIndex = trbTilt.Value; - private void btnTiltUp_Click(object sender, EventArgs e) - { - VehicleTiltIndex++; - } + void btnTiltUp_Click(object sender, EventArgs e) => VehicleTiltIndex++; - private void btnTiltDown_Click(object sender, EventArgs e) - { - VehicleTiltIndex--; - } + void btnTiltDown_Click(object sender, EventArgs e) => VehicleTiltIndex--; #endregion diff --git a/OpenLocoToolGui/MainForm.resx b/Gui/MainForm.resx similarity index 99% rename from OpenLocoToolGui/MainForm.resx rename to Gui/MainForm.resx index b76dc9e2..6b3514a0 100644 --- a/OpenLocoToolGui/MainForm.resx +++ b/Gui/MainForm.resx @@ -123,6 +123,15 @@ 162, 17 + + 859, 11 + + + True + + + 964, 11 + 964, 11 @@ -148,9 +157,6 @@ True - - 859, 11 - iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 diff --git a/OpenLocoToolGui/MainFormModel.cs b/Gui/MainFormModel.cs similarity index 73% rename from OpenLocoToolGui/MainFormModel.cs rename to Gui/MainFormModel.cs index a61e0351..5dc76a56 100644 --- a/OpenLocoToolGui/MainFormModel.cs +++ b/Gui/MainFormModel.cs @@ -1,17 +1,17 @@ -global using HeaderIndex = System.Collections.Generic.Dictionary; -global using ObjectCache = System.Collections.Generic.Dictionary; +global using HeaderIndex = System.Collections.Generic.Dictionary; +global using ObjectCache = System.Collections.Generic.Dictionary; using System.Collections.Concurrent; using System.Text.Json; using System.Text.Json.Serialization; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Objects; -using OpenLocoToolCommon; -using OpenLocoTool.Headers; +using OpenLoco.ObjectEditor.DatFileParsing; +using OpenLoco.ObjectEditor.Objects; +using OpenLoco.ObjectEditor.Logging; using System.Diagnostics; -using OpenLocoTool; +using OpenLoco.ObjectEditor.Data; +using Zenith.Core; -namespace OpenLocoToolGui +namespace OpenLoco.ObjectEditor.Gui { class MainFormModel { @@ -21,44 +21,44 @@ class MainFormModel public ObjectCache ObjectCache { get; private set; } = []; - //public OpenLocoTool.ObjectManager ObjectManager { get; private set; } = new(); - - public string PaletteFile - { - get => Settings.PaletteFile; - set - { - Settings.PaletteFile = value; - LoadPalette(); - } - } - - private void LoadPalette() - { - //if (G1 == null) - { - try - { - var paletteBitmap = new Bitmap(Settings.PaletteFile); - Palette = PaletteHelpers.PaletteFromBitmap(paletteBitmap); - SaveSettings(); - logger.Debug($"Successfully loaded palette file {Settings.PaletteFile}"); - } - catch (ArgumentException ex) - { - logger.Error(ex); - } - } - //else - //{ - // var g1PaletteElement = G1.G1Elements[ImageIds.MainPalette]; - // Palette = g1PaletteElement.ImageData - // .Take(256 * 3) - // .Chunk(3) - // .Select(x => Color.FromArgb(x[2], x[1], x[0])) - // .ToArray(); - //} - } + //public OpenLocoObjectEditor.ObjectManager ObjectManager { get; private set; } = new(); + + //public string PaletteFile + //{ + // get => Settings.PaletteFile; + // set + // { + // Settings.PaletteFile = value; + // LoadPalette(); + // } + //} + + //private void LoadPalette() + //{ + // //if (G1 == null) + // { + // try + // { + // var paletteBitmap = new Bitmap(Settings.PaletteFile); + // Palette = PaletteHelpers.PaletteFromBitmap(paletteBitmap); + // SaveSettings(); + // logger.Debug($"Successfully loaded palette file {Settings.PaletteFile}"); + // } + // catch (ArgumentException ex) + // { + // logger.Error(ex); + // } + // } + // //else + // //{ + // // var g1PaletteElement = G1.G1Elements[ImageIds.MainPalette]; + // // Palette = g1PaletteElement.ImageData + // // .Take(256 * 3) + // // .Chunk(3) + // // .Select(x => Color.FromArgb(x[2], x[1], x[0])) + // // .ToArray(); + // //} + //} public Color[] Palette { get; private set; } @@ -74,9 +74,10 @@ private void LoadPalette() public List MiscFiles { get; set; } = []; - public MainFormModel(ILogger logger, string settingsFile) + public MainFormModel(ILogger logger, string settingsFile, Color[] palette) { this.logger = logger; + Palette = palette; LoadSettings(settingsFile); @@ -92,14 +93,18 @@ public MainFormModel(ILogger logger, string settingsFile) foreach (var dep in HeaderIndex.Where(kvp => dependentObjectTypes.Contains(kvp.Value.ObjectType))) { - //try - //{ +#if DEBUG SawyerStreamReader.LoadFullObjectFromFile(dep.Key); - //} - //catch (Exception ex) - //{ - // logger.Error($"File=\"{dep}\" Message=\"{ex.Message}\""); - //} +#else + try + { + SawyerStreamReader.LoadFullObjectFromFile(dep.Key); + } + catch (Exception ex) + { + logger.Error($"File=\"{dep}\" Message=\"{ex.Message}\""); + } +#endif } } @@ -119,7 +124,10 @@ public void LoadSettings(string settingsFile) } var text = File.ReadAllText(settingsFile); - Settings = JsonSerializer.Deserialize(text); + var settings = JsonSerializer.Deserialize(text); + Verify.NotNull(settings); + + Settings = settings!; if (!ValidateSettings(Settings, logger)) { @@ -131,8 +139,6 @@ public void LoadSettings(string settingsFile) logger.Info($"Loading header index from \"{Settings.IndexFileName}\""); LoadObjDirectory(Settings.ObjDataDirectory, new Progress(), true); } - - LoadPalette(); } static bool ValidateSettings(GuiSettings settings, ILogger logger) @@ -160,9 +166,13 @@ static bool ValidateSettings(GuiSettings settings, ILogger logger) public void SaveSettings() { - var options = GetOptions(); - var text = JsonSerializer.Serialize(Settings, options); - File.WriteAllText(SettingsFile, text); + //if (Settings.HasChanges) + { + var options = GetOptions(); + var text = JsonSerializer.Serialize(Settings, options); + File.WriteAllText(SettingsFile, text); + //Settings.HasChanges = false; + } } // this method loads every single object entirely. it takes a long time to run @@ -179,13 +189,20 @@ void CreateIndex(string[] allFiles, IProgress? progress) var sw = new Stopwatch(); sw.Start(); - Parallel.ForEach(allFiles, new ParallelOptions() { MaxDegreeOfParallelism = 100 }, (file) => + _ = Parallel.ForEach(allFiles, new ParallelOptions() { MaxDegreeOfParallelism = 100 }, (file) => //foreach (var file in allFiles) { try { var startTime = sw.Elapsed; var (fileInfo, locoObject) = SawyerStreamReader.LoadFullObjectFromFile(file); + + if (locoObject == null) + { + logger.Error($"Unable to load {file}. FileInfo={fileInfo}"); + return; + } + if (!ccObjectCache.TryAdd(file, new UiLocoObject { DatFileInfo = fileInfo, LocoObject = locoObject })) { logger.Warning($"Didn't add file {file} to cache - already exists (how???)"); @@ -205,7 +222,7 @@ void CreateIndex(string[] allFiles, IProgress? progress) else { var elapsed = sw.Elapsed - startTime; - timePerFile.TryAdd(fileInfo.S5Header.Name, elapsed); + _ = timePerFile.TryAdd(fileInfo.S5Header.Name, elapsed); } } catch (Exception ex) @@ -214,11 +231,11 @@ void CreateIndex(string[] allFiles, IProgress? progress) var obj = SawyerStreamReader.LoadS5HeaderFromFile(file); var indexObjectHeader = new IndexObjectHeader(obj.Name, obj.ObjectType, obj.Checksum, null); - ccHeaderIndex.TryAdd(file, indexObjectHeader); + _ = ccHeaderIndex.TryAdd(file, indexObjectHeader); } finally { - Interlocked.Increment(ref count); + _ = Interlocked.Increment(ref count); progress?.Report(count / (float)allFiles.Length); } //} @@ -232,6 +249,11 @@ void CreateIndex(string[] allFiles, IProgress? progress) logger.Debug($"Time time={sw.Elapsed}"); + if (timePerFile.IsEmpty) + { + _ = timePerFile.TryAdd("", TimeSpan.Zero); + } + var slowest = timePerFile.MaxBy(x => x.Value.Ticks); logger.Debug($"Slowest file={slowest.Key} Time={slowest.Value}"); @@ -242,8 +264,16 @@ void CreateIndex(string[] allFiles, IProgress? progress) logger.Debug($"Median time={median.Value}ms"); } - public static void SaveFile(string path, UiLocoObject obj) - => SawyerStreamWriter.Save(path, obj.DatFileInfo.S5Header.Name, obj.LocoObject); + public void SaveFile(string path, UiLocoObject obj) + { + if (obj.LocoObject == null) + { + logger.Error($"Cannot save an object with a null loco object - the file would be empty!"); + return; + } + + SawyerStreamWriter.Save(path, obj.DatFileInfo.S5Header.Name, obj.LocoObject); + } public bool LoadDataDirectory(string directory) { @@ -267,7 +297,7 @@ void LoadKnownData(HashSet allFilesInDir, HashSet knownFilenames if (matching.Any()) { dict.Add(music, File.ReadAllBytes(Path.Combine(Settings.DataDirectory, music))); - allFilesInDir.RemoveWhere(f => f.EndsWith(music)); + _ = allFilesInDir.RemoveWhere(f => f.EndsWith(music)); } } } @@ -282,7 +312,7 @@ void LoadKnownData(HashSet allFilesInDir, HashSet knownFilenames // load G1 only for now since we need it for palette G1 = SawyerStreamReader.LoadG1(Settings.GetDataFullPath(Settings.G1DatFileName)); - LoadPalette(); // update palette from g1 + //LoadPalette(); // update palette from g1 SaveSettings(); @@ -333,18 +363,21 @@ public void LoadObjDirectory(string directory, IProgress? progress, bool private static JsonSerializerOptions GetOptions() => new() { WriteIndented = true, Converters = { new JsonStringEnumConverter() }, }; - static void SerialiseHeaderIndexToFile(string filename, HeaderIndex headerIndex, JsonSerializerOptions options) + static void SerialiseHeaderIndexToFile(string filename, HeaderIndex headerIndex, JsonSerializerOptions options, ILogger? logger = null) { + logger?.Info($"Saved settings to {filename}"); var json = JsonSerializer.Serialize(headerIndex, options); File.WriteAllText(filename, json); } - static HeaderIndex? DeserialiseHeaderIndexFromFile(string filename) + static HeaderIndex? DeserialiseHeaderIndexFromFile(string filename, ILogger? logger = null) { if (!File.Exists(filename)) { + logger?.Info($"Settings file {filename} does not exist"); return null; } + logger?.Info($"Loading settings from {filename}"); var json = File.ReadAllText(filename); @@ -365,8 +398,8 @@ static void SerialiseHeaderIndexToFile(string filename, HeaderIndex headerIndex, else { var obj = SawyerStreamReader.LoadFullObjectFromFile(filename); - var uiObj = new UiLocoObject { DatFileInfo = obj.Item1, LocoObject = obj.Item2 }; - ObjectCache.TryAdd(filename, uiObj); + var uiObj = new UiLocoObject { DatFileInfo = obj.DatFileInfo, LocoObject = obj.LocoObject }; + _ = ObjectCache.TryAdd(filename, uiObj); return uiObj; } } diff --git a/OpenLocoToolGui/PaletteHelpers.cs b/Gui/PaletteHelpers.cs similarity index 96% rename from OpenLocoToolGui/PaletteHelpers.cs rename to Gui/PaletteHelpers.cs index 695a9c28..4af66e99 100644 --- a/OpenLocoToolGui/PaletteHelpers.cs +++ b/Gui/PaletteHelpers.cs @@ -1,6 +1,6 @@ using System.Drawing.Imaging; -namespace OpenLocoToolGui +namespace OpenLoco.ObjectEditor.Gui { public static class PaletteHelpers { diff --git a/OpenLocoToolGui/PictureBoxWithInterpolationMode.cs b/Gui/PictureBoxWithInterpolationMode.cs similarity index 93% rename from OpenLocoToolGui/PictureBoxWithInterpolationMode.cs rename to Gui/PictureBoxWithInterpolationMode.cs index c74c91c2..ce185c03 100644 --- a/OpenLocoToolGui/PictureBoxWithInterpolationMode.cs +++ b/Gui/PictureBoxWithInterpolationMode.cs @@ -1,6 +1,6 @@ using System.Drawing.Drawing2D; -namespace OpenLocoToolGui +namespace OpenLoco.ObjectEditor.Gui { // https://stackoverflow.com/questions/29157/how-do-i-make-a-picturebox-use-nearest-neighbor-resampling public class PictureBoxWithInterpolationMode : PictureBox diff --git a/OpenLocoToolGui/PictureBoxWithInterpolationMode.resx b/Gui/PictureBoxWithInterpolationMode.resx similarity index 100% rename from OpenLocoToolGui/PictureBoxWithInterpolationMode.resx rename to Gui/PictureBoxWithInterpolationMode.resx diff --git a/OpenLocoToolGui/Program.cs b/Gui/Program.cs similarity index 91% rename from OpenLocoToolGui/Program.cs rename to Gui/Program.cs index a4a4f545..4c2a8ffa 100644 --- a/OpenLocoToolGui/Program.cs +++ b/Gui/Program.cs @@ -1,4 +1,4 @@ -namespace OpenLocoToolGui +namespace OpenLoco.ObjectEditor.Gui { internal static class Program { diff --git a/OpenLocoToolGui/ProgressBarForm.Designer.cs b/Gui/ProgressBarForm.Designer.cs similarity index 97% rename from OpenLocoToolGui/ProgressBarForm.Designer.cs rename to Gui/ProgressBarForm.Designer.cs index b09dd863..86e4833c 100644 --- a/OpenLocoToolGui/ProgressBarForm.Designer.cs +++ b/Gui/ProgressBarForm.Designer.cs @@ -1,4 +1,4 @@ -namespace OpenLocoToolGui +namespace OpenLoco.ObjectEditor.Gui { partial class ProgressBarForm { diff --git a/OpenLocoToolGui/ProgressBarForm.cs b/Gui/ProgressBarForm.cs similarity index 84% rename from OpenLocoToolGui/ProgressBarForm.cs rename to Gui/ProgressBarForm.cs index 4bdbf604..5a453193 100644 --- a/OpenLocoToolGui/ProgressBarForm.cs +++ b/Gui/ProgressBarForm.cs @@ -1,4 +1,4 @@ -namespace OpenLocoToolGui +namespace OpenLoco.ObjectEditor.Gui { public partial class ProgressBarForm : Form { @@ -9,7 +9,7 @@ public void SetProgress(int value) if (InvokeRequired) { // Update progress bar using Invoke if not on the UI thread - Invoke(new Action(SetProgress), value); + _ = Invoke(new Action(SetProgress), value); } else { diff --git a/OpenLocoToolGui/ProgressBarForm.resx b/Gui/ProgressBarForm.resx similarity index 100% rename from OpenLocoToolGui/ProgressBarForm.resx rename to Gui/ProgressBarForm.resx diff --git a/OpenLocoToolGui/Properties/Resources.Designer.cs b/Gui/Properties/Resources.Designer.cs similarity index 94% rename from OpenLocoToolGui/Properties/Resources.Designer.cs rename to Gui/Properties/Resources.Designer.cs index bb5fc7d2..2a4ea77c 100644 --- a/OpenLocoToolGui/Properties/Resources.Designer.cs +++ b/Gui/Properties/Resources.Designer.cs @@ -8,7 +8,7 @@ // //------------------------------------------------------------------------------ -namespace OpenLocoToolGui.Properties { +namespace Gui.Properties { using System; @@ -39,7 +39,7 @@ internal Resources() { internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("OpenLocoToolGui.Properties.Resources", typeof(Resources).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Gui.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; diff --git a/OpenLocoToolGui/Properties/Resources.resx b/Gui/Properties/Resources.resx similarity index 100% rename from OpenLocoToolGui/Properties/Resources.resx rename to Gui/Properties/Resources.resx diff --git a/OpenLocoToolGui/StringTableUserControl.Designer.cs b/Gui/StringTableUserControl.Designer.cs similarity index 98% rename from OpenLocoToolGui/StringTableUserControl.Designer.cs rename to Gui/StringTableUserControl.Designer.cs index 46f92f85..d84f45d3 100644 --- a/OpenLocoToolGui/StringTableUserControl.Designer.cs +++ b/Gui/StringTableUserControl.Designer.cs @@ -1,4 +1,4 @@ -namespace OpenLocoToolGui +namespace OpenLoco.ObjectEditor.Gui { partial class StringTableUserControl { diff --git a/OpenLocoToolGui/StringTableUserControl.cs b/Gui/StringTableUserControl.cs similarity index 76% rename from OpenLocoToolGui/StringTableUserControl.cs rename to Gui/StringTableUserControl.cs index 3f798597..00c3879c 100644 --- a/OpenLocoToolGui/StringTableUserControl.cs +++ b/Gui/StringTableUserControl.cs @@ -1,19 +1,18 @@ -using OpenLocoTool; -using OpenLocoTool.Types; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.Types; -namespace OpenLocoToolGui +namespace OpenLoco.ObjectEditor.Gui { public partial class StringTableUserControl : UserControl { - public StringTableUserControl() => InitializeComponent(); + public StringTableUserControl() + => InitializeComponent(); - StringTable _data { get; set; } + StringTable _data { get; set; } = new StringTable(); - public void SetDataBinding(StringTable data) + public void SetDataBinding(StringTable? data) { lbStringSelector.SuspendLayout(); - - _data = data; lbStringSelector.DataSource = null; if (data == null) @@ -21,6 +20,8 @@ public void SetDataBinding(StringTable data) return; } + _data = data; + // Set up data binding for the outer dictionary DataGridView. lbStringSelector.DataSource = data.Table.Keys.ToList(); @@ -38,10 +39,9 @@ void UpdateDGVSource() flpLanguageStrings.SuspendLayout(); flpLanguageStrings.Controls.Clear(); - var sel = lbStringSelector.SelectedValue as string; - if (sel != null && _data.Table.ContainsKey(sel)) + if (lbStringSelector.SelectedValue is string sel && _data.Table.TryGetValue(sel, out var value)) { - foreach (var language in _data.Table[sel]) + foreach (var language in value) { var lblLanguage = new Label { @@ -72,7 +72,7 @@ void UpdateDGVSource() Width = pn.Width - lblLanguage.Width - 4, TextAlign = HorizontalAlignment.Left, }; - tbText.TextChanged += (a, b) => _data.Table[sel][Enum.Parse(lblLanguage.Text)] = tbText.Text; + tbText.TextChanged += (a, b) => value[Enum.Parse(lblLanguage.Text)] = tbText.Text; pn.Controls.Add(tbText); pn.Controls.Add(lblLanguage); diff --git a/OpenLocoToolGui/StringTableUserControl.resx b/Gui/StringTableUserControl.resx similarity index 100% rename from OpenLocoToolGui/StringTableUserControl.resx rename to Gui/StringTableUserControl.resx diff --git a/OpenLocoToolGui/UiLocoObject.cs b/Gui/UiLocoObject.cs similarity index 51% rename from OpenLocoToolGui/UiLocoObject.cs rename to Gui/UiLocoObject.cs index 6deade88..d11840dd 100644 --- a/OpenLocoToolGui/UiLocoObject.cs +++ b/Gui/UiLocoObject.cs @@ -1,16 +1,20 @@ -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Types; +using OpenLoco.ObjectEditor.DatFileParsing; +using OpenLoco.ObjectEditor.Types; +using System.ComponentModel; -namespace OpenLocoToolGui +namespace OpenLoco.ObjectEditor.Gui { + [TypeConverter(typeof(ExpandableObjectConverter))] public interface IUiObject { } + [TypeConverter(typeof(ExpandableObjectConverter))] public class UiLocoObject : IUiObject { public DatFileInfo DatFileInfo { get; set; } - public ILocoObject LocoObject { get; set; } + public ILocoObject? LocoObject { get; set; } } + [TypeConverter(typeof(ExpandableObjectConverter))] public class UiSoundObject { public string SoundName { get; set; } @@ -18,6 +22,7 @@ public class UiSoundObject public byte[] Data { get; set; } } + [TypeConverter(typeof(ExpandableObjectConverter))] public class UiSoundObjectList : IUiObject { public string FileName { get; set; } diff --git a/OpenLocoTool.sln b/OpenLocoObjectEditor.sln similarity index 86% rename from OpenLocoTool.sln rename to OpenLocoObjectEditor.sln index 439107ef..3ac31669 100644 --- a/OpenLocoTool.sln +++ b/OpenLocoObjectEditor.sln @@ -3,19 +3,30 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.1.32210.238 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenLocoTool", "OpenLocoTool\OpenLocoTool.csproj", "{95C3FA06-59A8-4A95-8FB7-F722106D6071}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core", "Core\Core.csproj", "{95C3FA06-59A8-4A95-8FB7-F722106D6071}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenLocoToolGui", "OpenLocoToolGui\OpenLocoToolGui.csproj", "{3AE3C370-4870-48ED-A906-770AE62B00DE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gui", "Gui\Gui.csproj", "{3AE3C370-4870-48ED-A906-770AE62B00DE}" ProjectSection(ProjectDependencies) = postProject {95C3FA06-59A8-4A95-8FB7-F722106D6071} = {95C3FA06-59A8-4A95-8FB7-F722106D6071} EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenLocoToolCommon", "OpenLocoToolCommon\OpenLocoToolCommon.csproj", "{BCD93536-D322-4C14-B193-1F643D03C788}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared", "Shared\Shared.csproj", "{BCD93536-D322-4C14-B193-1F643D03C788}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenLocoToolTests", "OpenLocoToolTests\OpenLocoToolTests.csproj", "{55293DEB-00FA-45AD-814D-CB37383BE0D5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj", "{55293DEB-00FA-45AD-814D-CB37383BE0D5}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DatFileRenamer", "DatFileRenamer\DatFileRenamer.csproj", "{AD079FD2-EC1C-459C-BDE6-8D0C527767AB}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B25793AF-BB94-4E13-818D-DBCBA521F467}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + .gitignore = .gitignore + dat-object-layout.md = dat-object-layout.md + loco_icon.ico = loco_icon.ico + palette.png = palette.png + README.md = README.md + version.txt = version.txt + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/OpenLocoTool/Objects/DockObject.cs b/OpenLocoTool/Objects/DockObject.cs deleted file mode 100644 index d8ebdc94..00000000 --- a/OpenLocoTool/Objects/DockObject.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; -using OpenLocoTool.Types; - -namespace OpenLocoTool.Objects -{ - [Flags] - public enum DockObjectFlags : uint16_t - { - None = 0, - unk01 = 1 << 0, - }; - - [TypeConverter(typeof(ExpandableObjectConverter))] - [LocoStructSize(0x28)] - [LocoStructType(ObjectType.Dock)] - [LocoStringTable("Name")] - public class DockObject( - int16_t buildCostFactor, - int16_t sellCostFactor, - uint8_t costIndex, - uint8_t var_07, - DockObjectFlags flags, - uint8_t numAux01, - uint8_t numAux02Ent, - uint16_t designedYear, - uint16_t obsoleteYear, - Pos2 boatPosition) - : ILocoStruct, ILocoStructVariableData - { - - //[LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, - [LocoStructOffset(0x02)] public int16_t BuildCostFactor { get; set; } = buildCostFactor; - [LocoStructOffset(0x04)] public int16_t SellCostFactor { get; set; } = sellCostFactor; - [LocoStructOffset(0x06)] public uint8_t CostIndex { get; set; } = costIndex; - [LocoStructOffset(0x07), LocoPropertyMaybeUnused] public uint8_t var_07 { get; set; } = var_07; - //[LocoStructOffset(0x08)] image_id Image { get; set; } - //[LocoStructOffset(0x0C)] public image_id UnkImage { get; set; }; - [LocoStructOffset(0x10)] public DockObjectFlags Flags { get; set; } = flags; - [LocoStructOffset(0x12), LocoPropertyMaybeUnused] public uint8_t NumAux01 { get; set; } = numAux01; - [LocoStructOffset(0x13), LocoPropertyMaybeUnused] public uint8_t NumAux02Ent { get; set; } = numAux02Ent; - //LocoStructOffset(0x14)] const uint8_t* var_14 { get; set; } - //LocoStructOffset(0x18)] const uint16_t* var_18 { get; set; } - //LocoStructOffset(0x1C)] const uint8_t* var_1C[1] { get; set; } // odd that this is size 1 but that is how its used - [LocoStructOffset(0x20)] public uint16_t DesignedYear { get; set; } = designedYear; - [LocoStructOffset(0x22)] public uint16_t ObsoleteYear { get; set; } = obsoleteYear; - [LocoStructOffset(0x24)] public Pos2 BoatPosition { get; set; } = boatPosition; - - [LocoPropertyMaybeUnused] public List UnknownAuxData1A { get; set; } = []; - [LocoPropertyMaybeUnused] public List UnknownAuxData1B { get; set; } = []; - [LocoPropertyMaybeUnused] public List UnknownAuxData2 { get; set; } = []; - - public ReadOnlySpan Load(ReadOnlySpan remainingData) - { - UnknownAuxData1A.Clear(); - UnknownAuxData1B.Clear(); - UnknownAuxData2.Clear(); - - // var_14 - a list of uint8_t - UnknownAuxData1A.AddRange(remainingData[..(NumAux01 * 1)]); - remainingData = remainingData[(NumAux01 * 1)..]; // sizeof(uint8_t) - - // var_18 - a list of uint16_t - var bytearr = remainingData[..(NumAux01 * 2)].ToArray(); - for (var i = 0; i < NumAux01; ++i) - { - UnknownAuxData1B.Add(BitConverter.ToUInt16(bytearr, i * 2)); // sizeof(uint16_t) - } - - remainingData = remainingData[(NumAux01 * 2)..]; // sizeof(uint16_t) - - // parts - for (var i = 0; i < NumAux02Ent; ++i) - { - var ptr_1C = 0; - while (remainingData[ptr_1C] != 0xFF) - { - UnknownAuxData2.Add(remainingData[ptr_1C]); - ptr_1C++; - } - - ptr_1C++; - remainingData = remainingData[ptr_1C..]; - } - - return remainingData; - } - - public ReadOnlySpan Save() - => UnknownAuxData1A - .Concat(UnknownAuxData1B.SelectMany(BitConverter.GetBytes)) - .Concat(UnknownAuxData2) - .Concat(new byte[] { 0xFF }) - .ToArray(); - } -} diff --git a/OpenLocoTool/Objects/LandObject.cs b/OpenLocoTool/Objects/LandObject.cs deleted file mode 100644 index 5a24db87..00000000 --- a/OpenLocoTool/Objects/LandObject.cs +++ /dev/null @@ -1,82 +0,0 @@ - -using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; - -namespace OpenLocoTool.Objects -{ - [Flags] - public enum LandObjectFlags : uint8_t - { - None = 0, - unk0 = 1 << 0, - unk1 = 1 << 1, - IsDesert = 1 << 2, - NoTrees = 1 << 3, - }; - - [TypeConverter(typeof(ExpandableObjectConverter))] - [LocoStructSize(0x1E)] - [LocoStructType(ObjectType.Land)] - [LocoStringTable("Name")] - public class LandObject( - uint8_t costIndex, - uint8_t var_03, - uint8_t var_04, - LandObjectFlags flags, - int8_t costFactor, - uint8_t numVariations, - uint8_t variationLikelihood) : ILocoStruct, ILocoStructVariableData - { - [LocoStructOffset(0x02)] public uint8_t CostIndex { get; set; } = costIndex; - [LocoStructOffset(0x03)] public uint8_t var_03 { get; set; } = var_03; - [LocoStructOffset(0x04), LocoPropertyMaybeUnused] public uint8_t var_04 { get; set; } = var_04; - [LocoStructOffset(0x05)] public LandObjectFlags Flags { get; set; } = flags; - //[LocoStructOffset(0x06)] public object_index CliffEdgeHeader1 { get; set; } - //[LocoStructOffset(0x07)] public object_index CliffEdgeHeader2 { get; set; } // unused - [LocoStructOffset(0x08)] public int8_t CostFactor { get; set; } = costFactor; - //[LocoStructOffset(0x09)] public uint8_t pad_09 { get; set; } - //[LocoStructOffset(0x0A)] public image_id Image { get; set; } - //[LocoStructOffset(0x0E)] public image_id var_0E { get; set; } - //[LocoStructOffset(0x12)] public image_id CliffEdgeImage { get; set; } - //[LocoStructOffset(0x16)] public image_id MapPixelImage { get; set; } - //[LocoStructOffset(0x1A)] public uint8_t pad_1A { get; set; } - [LocoStructOffset(0x1B)] public uint8_t NumVariations { get; set; } = numVariations; - [LocoStructOffset(0x1C)] public uint8_t VariationLikelihood { get; set; } = variationLikelihood; - //[LocoStructOffset(0x1D)] public uint8_t pad_1D { get; set; } - - public S5Header? CliffEdgeHeader { get; set; } - public S5Header? UnkObjHeader { get; set; } - - public ReadOnlySpan Load(ReadOnlySpan remainingData) - { - // cliff edge header - CliffEdgeHeader = S5Header.Read(remainingData[..S5Header.StructLength]); - remainingData = remainingData[S5Header.StructLength..]; - - // unused obj - if (Flags.HasFlag(LandObjectFlags.unk1)) - { - UnkObjHeader = S5Header.Read(remainingData[..S5Header.StructLength]); - remainingData = remainingData[S5Header.StructLength..]; - } - - return remainingData; - } - - public ReadOnlySpan Save() - { - var variableDataSize = S5Header.StructLength + (Flags.HasFlag(LandObjectFlags.unk1) ? S5Header.StructLength : 0); - - var data = new byte[variableDataSize]; - data = [.. CliffEdgeHeader.Write()]; - - if (Flags.HasFlag(LandObjectFlags.unk1)) - { - UnkObjHeader.Write().CopyTo(data.AsSpan()[S5Header.StructLength..]); - } - - return data; - } - } -} diff --git a/OpenLocoTool/Objects/RoadExtraObject.cs b/OpenLocoTool/Objects/RoadExtraObject.cs deleted file mode 100644 index 54395cf9..00000000 --- a/OpenLocoTool/Objects/RoadExtraObject.cs +++ /dev/null @@ -1,29 +0,0 @@ - -using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; - -namespace OpenLocoTool.Objects -{ - [TypeConverter(typeof(ExpandableObjectConverter))] - [LocoStructSize(0x12)] - [LocoStructType(ObjectType.RoadExtra)] - [LocoStringTable("Name")] - public class RoadExtraObject( - uint16_t roadPieces, - uint8_t paintStyle, - uint8_t costIndex, - int16_t buildCostFactor, - int16_t sellCostFactor) - : ILocoStruct - { - //[property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, - [LocoStructOffset(0x02)] public uint16_t RoadPieces { get; set; } = roadPieces; - [LocoStructOffset(0x04)] public uint8_t PaintStyle { get; set; } = paintStyle; - [LocoStructOffset(0x05)] public uint8_t CostIndex { get; set; } = costIndex; - [LocoStructOffset(0x06)] public int16_t BuildCostFactor { get; set; } = buildCostFactor; - [LocoStructOffset(0x08)] public int16_t SellCostFactor { get; set; } = sellCostFactor; - //[LocoStructOffset(0x0A)] public image_id Image { get; set; } - //[LocoStructOffset(0x0E)] public image_id var_0E { get; set; } - } -} diff --git a/OpenLocoTool/Objects/RoadObject.cs b/OpenLocoTool/Objects/RoadObject.cs deleted file mode 100644 index bc4cf6de..00000000 --- a/OpenLocoTool/Objects/RoadObject.cs +++ /dev/null @@ -1,130 +0,0 @@ - -using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; - -namespace OpenLocoTool.Objects -{ - [Flags] - public enum RoadObjectFlags : uint16_t - { - none = 0, - unk_00 = 1 << 0, - unk_01 = 1 << 1, - unk_02 = 1 << 2, - unk_03 = 1 << 3, // Likely isTram - unk_04 = 1 << 4, - unk_05 = 1 << 5, - IsRoad = 1 << 6, // If not set this is tram track - }; - - [Flags] - public enum RoadObjectPieceFlags : uint16_t - { - None = 0, - OneWay = 1 << 0, - Track = 1 << 1, - Slope = 1 << 2, - SteepSlope = 1 << 3, - Intersection = 1 << 4, - OneSided = 1 << 5, - Overtake = 1 << 6, - StreetLights = 1 << 8, - }; - - [TypeConverter(typeof(ExpandableObjectConverter))] - [LocoStructSize(0x30)] - [LocoStructType(ObjectType.Road)] - [LocoStringTable("Name")] - public class RoadObject( - RoadObjectPieceFlags roadPieces, - int16_t buildCostFactor, - int16_t sellCostFactor, - int16_t tunnelCostFactor, - uint8_t costIndex, - int16_t maxSpeed, - RoadObjectFlags flags, - uint8_t numBridges, - uint8_t numStations, - uint8_t paintStyle, - uint8_t numMods, - uint8_t numCompatible, - uint8_t targetTownSize) - : ILocoStruct, ILocoStructVariableData - { - //[LocoStructOffset(0x00), LocoString, Browsable(false)] public string_id Name, - [LocoStructOffset(0x02)] public RoadObjectPieceFlags RoadPieces { get; set; } = roadPieces; - [LocoStructOffset(0x04)] public int16_t BuildCostFactor { get; set; } = buildCostFactor; - [LocoStructOffset(0x06)] public int16_t SellCostFactor { get; set; } = sellCostFactor; - [LocoStructOffset(0x08)] public int16_t TunnelCostFactor { get; set; } = tunnelCostFactor; - [LocoStructOffset(0x0A)] public uint8_t CostIndex { get; set; } = costIndex; - //[LocoStructOffset(0x0B)] public object_index Tunnel { get; set; } - [LocoStructOffset(0x0C)] public Speed16 MaxSpeed { get; set; } = maxSpeed; - //[LocoStructOffset(0x0E)] public image_id Image, - [LocoStructOffset(0x12)] public RoadObjectFlags Flags { get; set; } = flags; - [LocoStructOffset(0x14)] public uint8_t NumBridges { get; set; } = numBridges; - //[LocoStructOffset(0x15), LocoArrayLength(7)] object_index[] Bridges { get; set; } - [LocoStructOffset(0x1C)] public uint8_t NumStations { get; set; } = numStations; - //[LocoStructOffset(0x1D), LocoArrayLength(7)] object_index[] Stations { get; set; } - [LocoStructOffset(0x24)] public uint8_t PaintStyle { get; set; } = paintStyle; - [LocoStructOffset(0x25)] public uint8_t NumMods { get; set; } = numMods; - //[LocoStructOffset(0x26), LocoArrayLength(2)] object_index[] Mods { get; set; } - [LocoStructOffset(0x28)] public uint8_t NumCompatible { get; set; } = numCompatible; - //[LocoStructOffset(0x29)] public uint8_t pad_29 { get; set; } = pad_29; - //[LocoStructOffset(0x2A)] public uint16_t CompatibleRoads { get; set; } // bitset - //[LocoStructOffset(0x2C)] public uint16_t CompatibleTracks { get; set; } // bitset - [LocoStructOffset(0x2E)] public uint8_t TargetTownSize { get; set; } = targetTownSize; - //[LocoStructOffset(0x2F)] public uint8_t pad_2F { get; set; } = pad_2F; - - public List Compatible { get; set; } = []; - public List Mods { get; set; } = []; - public S5Header Tunnel { get; set; } - public List Bridges { get; set; } = []; - public List Stations { get; set; } = []; - - public const int NumTunnels = 1; - - public ReadOnlySpan Load(ReadOnlySpan remainingData) - { - Compatible.Clear(); - Mods.Clear(); - Bridges.Clear(); - Stations.Clear(); - - // compatible roads/tracks - Compatible = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumCompatible); - remainingData = remainingData[(S5Header.StructLength * NumCompatible)..]; - - // mods - Mods = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumMods); - remainingData = remainingData[(S5Header.StructLength * NumMods)..]; - - // tunnel - Tunnel = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumTunnels)[0]; - remainingData = remainingData[(S5Header.StructLength * NumTunnels)..]; - - // bridges - Bridges = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumBridges); - remainingData = remainingData[(S5Header.StructLength * NumBridges)..]; - - // stations - Stations = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumStations); - remainingData = remainingData[(S5Header.StructLength * NumStations)..]; - - return remainingData; - } - - public ReadOnlySpan Save() - { - //var data = new byte[S5Header.StructLength * (NumCompatible + NumMods + 1 + NumBridges + NumStations)]; - - var headers = Compatible - .Concat(Mods) - .Concat(Enumerable.Repeat(Tunnel, 1)) - .Concat(Bridges) - .Concat(Stations); - - return headers.SelectMany(h => h.Write().ToArray()).ToArray(); - } - } -} diff --git a/OpenLocoTool/Objects/RoadStationObject.cs b/OpenLocoTool/Objects/RoadStationObject.cs deleted file mode 100644 index 592f31cc..00000000 --- a/OpenLocoTool/Objects/RoadStationObject.cs +++ /dev/null @@ -1,125 +0,0 @@ -using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; - -namespace OpenLocoTool.Objects -{ - [Flags] - public enum RoadStationFlags : uint8_t - { - None = 0, - Recolourable = 1 << 0, - Passenger = 1 << 1, - Freight = 1 << 2, - RoadEnd = 1 << 3, - }; - - [TypeConverter(typeof(ExpandableObjectConverter))] - [LocoStructSize(0x6E)] - [LocoStructType(ObjectType.RoadStation)] - [LocoStringTable("Name")] - public class RoadStationObject( - uint8_t paintStyle, - uint16_t roadPieces, - int16_t buildCostFactor, - int16_t sellCostFactor, - uint8_t costIndex, - RoadStationFlags flags, - uint8_t numCompatible, - uint16_t designedYear, - uint16_t obsoleteYear) - : ILocoStruct, ILocoStructVariableData - { - //[LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, - [LocoStructOffset(0x02)] public uint8_t PaintStyle { get; set; } = paintStyle; - //[LocoStructOffset(0x03)] uint8_t pad_03, - [LocoStructOffset(0x04)] public uint16_t RoadPieces { get; set; } = roadPieces; - [LocoStructOffset(0x06)] public int16_t BuildCostFactor { get; set; } = buildCostFactor; - [LocoStructOffset(0x08)] public int16_t SellCostFactor { get; set; } = sellCostFactor; - [LocoStructOffset(0x0A)] public uint8_t CostIndex { get; set; } = costIndex; - [LocoStructOffset(0x0B)] public RoadStationFlags Flags { get; set; } = flags; - //[LocoStructOffset(0x0C)] image_id Image, - //[LocoStructOffset(0x10), LocoArrayLength(4)] public uint32_t[] ImageOffsets { get; set; } - [LocoStructOffset(0x20)] public uint8_t NumCompatible { get; set; } = numCompatible; - //[LocoStructOffset(0x21), LocoArrayLength(7)] uint8_t[] Mods, - [LocoStructOffset(0x28)] public uint16_t DesignedYear { get; set; } = designedYear; - [LocoStructOffset(0x2A)] public uint16_t ObsoleteYear { get; set; } = obsoleteYear; - //[LocoStructOffset(0x2C)] object_index CargoType - //[LocoStructOffset(0x2D)] uint8_t pad_2D - //[LocoStructProperty(0x2E)] uint8_t CargoOffsetBytes[4][4] - - public List Compatible { get; set; } = []; - - public S5Header CargoType { get; set; } - - public uint8_t[][][] CargoOffsetBytes { get; set; } - - public ReadOnlySpan Load(ReadOnlySpan remainingData) - { - // compatible - Compatible = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumCompatible); - remainingData = remainingData[(S5Header.StructLength * NumCompatible)..]; - - // cargo - if (Flags.HasFlag(RoadStationFlags.Passenger) || Flags.HasFlag(RoadStationFlags.Freight)) - { - CargoType = S5Header.Read(remainingData[..S5Header.StructLength]); - remainingData = remainingData[(S5Header.StructLength * 1)..]; - } - - // cargo offsets (for drawing the cargo on the station) - CargoOffsetBytes = new byte[4][][]; - for (var i = 0; i < 4; ++i) - { - CargoOffsetBytes[i] = new byte[4][]; - for (var j = 0; j < 4; ++j) - { - var bytes = 0; - bytes++; - var length = 1; - - while (remainingData[bytes] != 0xFF) - { - length += 4; // x, y, x, y - bytes += 4; - } - - length += 4; - CargoOffsetBytes[i][j] = remainingData[..length].ToArray(); - remainingData = remainingData[length..]; - } - } - - return remainingData; - } - - public ReadOnlySpan Save() - { - using (var ms = new MemoryStream()) - { - // compatible - foreach (var co in Compatible) - { - ms.Write(co.Write()); - } - - // cargo - if (Flags.HasFlag(RoadStationFlags.Passenger) || Flags.HasFlag(RoadStationFlags.Freight)) - { - ms.Write(CargoType.Write()); - } - - // cargo offsets - for (var i = 0; i < 4; ++i) - { - for (var j = 0; j < 4; ++j) - { - ms.Write(CargoOffsetBytes[i][j]); - } - } - - return ms.ToArray(); - } - } - } -} diff --git a/OpenLocoTool/Objects/SteamObject.cs b/OpenLocoTool/Objects/SteamObject.cs deleted file mode 100644 index e8806598..00000000 --- a/OpenLocoTool/Objects/SteamObject.cs +++ /dev/null @@ -1,107 +0,0 @@ - -using System.ComponentModel; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; - -namespace OpenLocoTool.Objects -{ - [Flags] - public enum SteamObjectFlags : uint16_t - { - None = 0, - ApplyWind = 1 << 0, - DisperseOnCollision = 1 << 1, - unk2 = 1 << 2, - unk3 = 1 << 3, - }; - - [TypeConverter(typeof(ExpandableObjectConverter))] - [LocoStructSize(0x02)] - public class ImageAndHeight( - uint8_t imageOffset, - uint8_t height) - : ILocoStruct - { - [LocoStructOffset(0x00)] public uint8_t ImageOffset { get; set; } = imageOffset; - [LocoStructOffset(0x01)] public uint8_t Height { get; set; } = height; - - public static ImageAndHeight Read(ReadOnlySpan data) - => new ImageAndHeight(data[0], data[1]); - } - - [TypeConverter(typeof(ExpandableObjectConverter))] - [LocoStructSize(0x28)] - [LocoStructType(ObjectType.Steam)] - [LocoStringTable("Name")] - public class SteamObject( - uint16_t numImages, - uint8_t numStationaryTicks, - SteamObjectFlags flags, - uint32_t var_0A, - uint8_t numSoundEffects) - : ILocoStruct, ILocoStructVariableData - { - //[property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, - [LocoStructOffset(0x02), LocoPropertyMaybeUnused] public uint16_t NumImages { get; set; } = numImages; // this is simply the count of images in the graphics table - [LocoStructOffset(0x04)] public uint8_t NumStationaryTicks { get; set; } = numStationaryTicks; - //[LocoStructOffset(0x05)] public uint8_t SpriteWidth { get; set; } = spriteWidth; - //[LocoStructOffset(0x06)] public uint8_t SpriteHeightNegative { get; set; } = spriteHeightNegative; - //[LocoStructOffset(0x07)] public uint8_t SpriteHeightPositive { get; set; } = spriteHeightPositive; - [LocoStructOffset(0x08)] public SteamObjectFlags Flags { get; set; } = flags; - [LocoStructOffset(0x0A)] public uint32_t var_0A { get; set; } = var_0A; - //[LocoStructOffset(0x0E)] public image_id BaseImageId, - [LocoStructOffset(0x12), LocoStructSkipRead] public uint16_t TotalNumFramesType0 => (uint16_t)FrameInfoType0.Count; - [LocoStructOffset(0x14), LocoStructSkipRead] public uint16_t TotalNumFramesType1 => (uint16_t)FrameInfoType1.Count; - //[property: LocoStructProperty(0x16)] public const ImageAndHeight* FrameInfoType0, - //[property: LocoStructProperty(0x1A)] public const ImageAndHeight* FrameInfoType1, - [LocoStructOffset(0x1E)] public uint8_t NumSoundEffects { get; set; } = numSoundEffects; - //[LocoStructOffset(0x01F), LocoArrayLength(9)] public object_index[] SoundEffects { get; set; } = soundEffects; - - public List FrameInfoType0 { get; set; } = []; - public List FrameInfoType1 { get; set; } = []; - - public List SoundEffects { get; set; } = []; - - public ReadOnlySpan Load(ReadOnlySpan remainingData) - { - // frameInfoType0 - FrameInfoType0.Clear(); - while (remainingData[0] != 0xFF) - { - FrameInfoType0.Add(ImageAndHeight.Read(remainingData[..ObjectAttributes.StructSize()])); - remainingData = remainingData[ObjectAttributes.StructSize()..]; - } - - remainingData = remainingData[1..]; - - // frameInfoType1 - FrameInfoType1.Clear(); - while (remainingData[0] != 0xFF) - { - FrameInfoType1.Add(ImageAndHeight.Read(remainingData[..ObjectAttributes.StructSize()])); - remainingData = remainingData[ObjectAttributes.StructSize()..]; - } - - remainingData = remainingData[1..]; - - // sounds effects - SoundEffects.Clear(); - for (var i = 0; i < NumSoundEffects; ++i) - { - SoundEffects.Add(S5Header.Read(remainingData[..S5Header.StructLength])); - remainingData = remainingData[S5Header.StructLength..]; - } - - return remainingData; - } - - // todo: optimise this with streams - this is quite slow as is - public ReadOnlySpan Save() - => FrameInfoType0.SelectMany(x => new byte[] { x.ImageOffset, x.Height }) - .Concat(new byte[] { 0xFF }) - .Concat(FrameInfoType1.SelectMany(x => new byte[] { x.ImageOffset, x.Height })) - .Concat(new byte[] { 0xFF }) - .Concat(SoundEffects.SelectMany(sfx => sfx.Write().ToArray())) - .ToArray(); - } -} diff --git a/OpenLocoTool/OpenLocoTool.csproj b/OpenLocoTool/OpenLocoTool.csproj deleted file mode 100644 index 59a1d7b6..00000000 --- a/OpenLocoTool/OpenLocoTool.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - Library - net8.0 - enable - enable - latest - true - - - - - - - - - - - diff --git a/OpenLocoTool/Types/DatFileInfo.cs b/OpenLocoTool/Types/DatFileInfo.cs deleted file mode 100644 index 68473bbc..00000000 --- a/OpenLocoTool/Types/DatFileInfo.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.ComponentModel; -using OpenLocoTool.Headers; - -namespace OpenLocoTool.DatFileParsing -{ - [TypeConverter(typeof(ExpandableObjectConverter))] - public class DatFileInfo(S5Header s5Header, ObjectHeader objectHeader) - { - public S5Header S5Header { get; set; } = s5Header; - public ObjectHeader ObjectHeader { get; set; } = objectHeader; - } -} diff --git a/OpenLocoToolTests/ObjectLoadingTests.cs b/OpenLocoToolTests/ObjectLoadingTests.cs deleted file mode 100644 index 734fded7..00000000 --- a/OpenLocoToolTests/ObjectLoadingTests.cs +++ /dev/null @@ -1,658 +0,0 @@ -using NUnit.Framework; -using NUnit.Framework.Internal; -using OpenLocoTool; -using OpenLocoTool.Data; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; -using OpenLocoTool.Objects; - -namespace OpenLocoToolTests -{ - [TestFixture] - public class ObjectLoadingTests - { - static (ILocoObject, T) LoadObject(string filename) where T : ILocoStruct - { - var fileSize = new FileInfo(filename).Length; - var logger = new OpenLocoToolCommon.Logger(); - var loaded = SawyerStreamReader.LoadFullObjectFromFile(filename, logger: logger); - - Assert.That(loaded.DatFileInfo.ObjectHeader.DataLength, Is.EqualTo(fileSize - S5Header.StructLength - ObjectHeader.StructLength), "ObjectHeader.Length didn't match actual size of struct"); - - return (loaded.LocoObject, (T)loaded.LocoObject.Object); - } - - //[Test] - //public void DebuggingLoadObject() - //{ - // const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\260RENFE.DAT"; - // var (obj, struc) = LoadObject(testFile); - // Assert.Multiple(() => - - // { - // Assert.That(struc.Name, Is.EqualTo(0), nameof(struc.Name)); - // }); - //} - - [Test] - public void LoadAirportObject() - { - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\AIRPORT1.DAT"; - var (obj, struc) = LoadObject(testFile); - - Assert.Multiple(() => - { - Assert.That(struc.BuildCostFactor, Is.EqualTo(256), nameof(struc.BuildCostFactor)); - Assert.That(struc.SellCostFactor, Is.EqualTo(-192), nameof(struc.SellCostFactor)); - Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex)); - Assert.That(struc.var_07, Is.EqualTo(0), nameof(struc.var_07)); - Assert.That(struc.var_0C, Is.EqualTo(0), nameof(struc.var_0C)); - Assert.That(struc.AllowedPlaneTypes, Is.EqualTo(24), nameof(struc.AllowedPlaneTypes)); - Assert.That(struc.NumSpriteSets, Is.EqualTo(94), nameof(struc.NumSpriteSets)); - Assert.That(struc.NumTiles, Is.EqualTo(23), nameof(struc.NumTiles)); - - //Assert.That(struc.var_14, Is.EqualTo(0), nameof(struc.var_14)); - //Assert.That(struc.var_18, Is.EqualTo(0), nameof(struc.var_18)); - //Assert.That(struc.var_1C, Is.EqualTo(0), nameof(struc.var_1C)); - //Assert.That(struc.var_9C, Is.EqualTo(0), nameof(struc.var_9C)); - - Assert.That(struc.LargeTiles, Is.EqualTo(917759), nameof(struc.LargeTiles)); - Assert.That(struc.MinX, Is.EqualTo(-4), nameof(struc.MinX)); - Assert.That(struc.MinY, Is.EqualTo(-4), nameof(struc.MinY)); - Assert.That(struc.MaxX, Is.EqualTo(5), nameof(struc.MaxX)); - Assert.That(struc.MaxY, Is.EqualTo(5), nameof(struc.MaxY)); - Assert.That(struc.DesignedYear, Is.EqualTo(1970), nameof(struc.DesignedYear)); - Assert.That(struc.ObsoleteYear, Is.EqualTo(65535), nameof(struc.ObsoleteYear)); - Assert.That(struc.NumMovementNodes, Is.EqualTo(26), nameof(struc.NumMovementNodes)); - Assert.That(struc.NumMovementEdges, Is.EqualTo(30), nameof(struc.NumMovementEdges)); - - //Assert.That(struc.MovementNodes, Is.EqualTo(0), nameof(struc.MovementNodes)); - //Assert.That(struc.MovementEdges, Is.EqualTo(0), nameof(struc.MovementEdges)); - - Assert.That(struc.pad_B6[0], Is.EqualTo(0), nameof(struc.pad_B6) + "[0]"); - Assert.That(struc.pad_B6[1], Is.EqualTo(19), nameof(struc.pad_B6) + "[1]"); - Assert.That(struc.pad_B6[2], Is.EqualTo(0), nameof(struc.pad_B6) + "[2]"); - Assert.That(struc.pad_B6[3], Is.EqualTo(0), nameof(struc.pad_B6) + "[3]"); - }); - } - - [Test] - public void LoadBridgeObject() - { - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\BRDGBRCK.DAT"; - var (obj, struc) = LoadObject(testFile); - - Assert.Multiple(() => - { - Assert.That(struc.NoRoof, Is.EqualTo(0), nameof(struc.NoRoof)); - - // Assert.That(struc.pad_03[0], Is.EqualTo(0), nameof(struc.pad_03) + "[0]"); - // Assert.That(struc.pad_03[1], Is.EqualTo(0), nameof(struc.pad_03) + "[1]"); - // Assert.That(struc.pad_03[2], Is.EqualTo(0), nameof(struc.pad_03) + "[2]"); - - Assert.That(struc.var_06, Is.EqualTo(16), nameof(struc.var_06)); - Assert.That(struc.SpanLength, Is.EqualTo(1), nameof(struc.SpanLength)); - Assert.That(struc.PillarSpacing, Is.EqualTo(255), nameof(struc.PillarSpacing)); - Assert.That(struc.MaxSpeed, Is.EqualTo(60), nameof(struc.MaxSpeed)); - Assert.That(struc.MaxHeight, Is.EqualTo(10), nameof(struc.MaxHeight)); - Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex)); - Assert.That(struc.BaseCostFactor, Is.EqualTo(16), nameof(struc.BaseCostFactor)); - Assert.That(struc.HeightCostFactor, Is.EqualTo(8), nameof(struc.HeightCostFactor)); - Assert.That(struc.SellCostFactor, Is.EqualTo(-12), nameof(struc.SellCostFactor)); - Assert.That(struc.DisabledTrackCfg, Is.EqualTo(0), nameof(struc.DisabledTrackCfg)); - Assert.That(struc.TrackNumCompatible, Is.EqualTo(0), nameof(struc.TrackNumCompatible)); - //CollectionAssert.AreEqual(struc.TrackMods, Array.CreateInstance(typeof(byte), 7), nameof(struc.TrackMods)); - Assert.That(struc.RoadNumCompatible, Is.EqualTo(0), nameof(struc.RoadNumCompatible)); - //CollectionAssert.AreEqual(struc.RoadMods, Array.CreateInstance(typeof(byte), 7), nameof(struc.RoadMods)); - Assert.That(struc.DesignedYear, Is.EqualTo(0), nameof(struc.DesignedYear)); - }); - } - - [Test] - public void LoadBuildingObject() - { - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\HQ1.DAT"; - var (obj, struc) = LoadObject(testFile); - - Assert.Multiple(() => - { - Assert.That(struc.var_06, Is.EqualTo(16), nameof(struc.var_06)); - Assert.That(struc.NumVariations, Is.EqualTo(5), nameof(struc.NumVariations)); - CollectionAssert.AreEqual(struc.VariationHeights, Array.CreateInstance(typeof(byte), 4), nameof(struc.VariationHeights)); - CollectionAssert.AreEqual(struc.var_0C, Array.CreateInstance(typeof(byte), 2), nameof(struc.var_0C)); - Assert.That(struc.Colours, Is.EqualTo(0), nameof(struc.Colours)); - Assert.That(struc.DesignedYear, Is.EqualTo(0), nameof(struc.DesignedYear)); - Assert.That(struc.ObsoleteYear, Is.EqualTo(65535), nameof(struc.ObsoleteYear)); - Assert.That(struc.Flags, Is.EqualTo(BuildingObjectFlags.LargeTile | BuildingObjectFlags.MiscBuilding | BuildingObjectFlags.IsHeadquarters), nameof(struc.Flags)); - Assert.That(struc.ClearCostIndex, Is.EqualTo(1), nameof(struc.ClearCostIndex)); - Assert.That(struc.ClearCostFactor, Is.EqualTo(0), nameof(struc.ClearCostFactor)); - Assert.That(struc.ScaffoldingSegmentType, Is.EqualTo(1), nameof(struc.ScaffoldingSegmentType)); - Assert.That(struc.ScaffoldingColour, Is.EqualTo(Colour.yellow), nameof(struc.ScaffoldingColour)); - - Assert.That(struc.pad_9E[0], Is.EqualTo(3), nameof(struc.pad_9E) + "[0]"); - Assert.That(struc.pad_9E[1], Is.EqualTo(3), nameof(struc.pad_9E) + "[1]"); - - CollectionAssert.AreEqual(struc.ProducedQuantity, Array.CreateInstance(typeof(byte), 2), nameof(struc.ProducedQuantity)); - CollectionAssert.AreEqual(struc.ProducedCargoType, Array.CreateInstance(typeof(byte), 2), nameof(struc.ProducedCargoType)); - CollectionAssert.AreEqual(struc.var_A6, Array.CreateInstance(typeof(byte), 2), nameof(struc.var_A6)); - CollectionAssert.AreEqual(struc.var_A8, Array.CreateInstance(typeof(byte), 2), nameof(struc.var_A8)); - CollectionAssert.AreEqual(struc.var_A4, Array.CreateInstance(typeof(byte), 2), nameof(struc.var_A4)); - Assert.That(struc.DemolishRatingReduction, Is.EqualTo(0), nameof(struc.DemolishRatingReduction)); - Assert.That(struc.var_AC, Is.EqualTo(255), nameof(struc.var_AC)); - Assert.That(struc.var_AD, Is.EqualTo(0), nameof(struc.var_AD)); - }); - } - - [Test] - public void LoadCargoObject() - { - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\CHEMICAL.DAT"; - var (obj, struc) = LoadObject(testFile); - - Assert.Multiple(() => - { - Assert.That(struc.var_02, Is.EqualTo(256), nameof(struc.var_02)); - Assert.That(struc.CargoTransferTime, Is.EqualTo(64), nameof(struc.CargoTransferTime)); - Assert.That(struc.UnitInlineSprite, Is.EqualTo(0), nameof(struc.UnitInlineSprite)); - Assert.That(struc.MatchFlags, Is.EqualTo(4), nameof(struc.MatchFlags)); - Assert.That(struc.Flags, Is.EqualTo(CargoObjectFlags.Delivering), nameof(struc.Flags)); - Assert.That(struc.NumPlatformVariations, Is.EqualTo(1), nameof(struc.NumPlatformVariations)); - Assert.That(struc.var_14, Is.EqualTo(4), nameof(struc.var_14)); - Assert.That(struc.PremiumDays, Is.EqualTo(10), nameof(struc.PremiumDays)); - Assert.That(struc.MaxNonPremiumDays, Is.EqualTo(30), nameof(struc.MaxNonPremiumDays)); - Assert.That(struc.MaxPremiumRate, Is.EqualTo(128), nameof(struc.MaxPremiumRate)); - Assert.That(struc.PenaltyRate, Is.EqualTo(256), nameof(struc.PenaltyRate)); - Assert.That(struc.PaymentFactor, Is.EqualTo(62), nameof(struc.PaymentFactor)); - Assert.That(struc.PaymentIndex, Is.EqualTo(10), nameof(struc.PaymentIndex)); - Assert.That(struc.UnitSize, Is.EqualTo(10), nameof(struc.UnitSize)); - }); - } - - [Test] - public void LoadCliffEdgeObject() - { - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\LSBROWN.DAT"; - var (obj, struc) = LoadObject(testFile); - } - - [Test] - public void LoadClimateObject() - { - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\CLIM1.DAT"; - var (obj, struc) = LoadObject(testFile); - - Assert.Multiple(() => - { - Assert.That(struc.FirstSeason, Is.EqualTo(1), nameof(struc.FirstSeason)); - Assert.That(struc.SeasonLengths[0], Is.EqualTo(57), nameof(struc.SeasonLengths) + "[0]"); - Assert.That(struc.SeasonLengths[1], Is.EqualTo(80), nameof(struc.SeasonLengths) + "[1]"); - Assert.That(struc.SeasonLengths[2], Is.EqualTo(100), nameof(struc.SeasonLengths) + "[2]"); - Assert.That(struc.SeasonLengths[3], Is.EqualTo(80), nameof(struc.SeasonLengths) + "[3]"); - Assert.That(struc.WinterSnowLine, Is.EqualTo(48), nameof(struc.WinterSnowLine)); - Assert.That(struc.SummerSnowLine, Is.EqualTo(76), nameof(struc.SummerSnowLine)); - Assert.That(struc.pad_09, Is.EqualTo(0), nameof(struc.pad_09)); - }); - } - - [Test] - public void LoadCompetitorObject() - { - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\COMP1.DAT"; - var (obj, struc) = LoadObject(testFile); - - Assert.Multiple(() => - { - Assert.That(struc.var_04, Is.EqualTo(6672), nameof(struc.var_04)); - Assert.That(struc.var_08, Is.EqualTo(2053), nameof(struc.var_08)); - Assert.That(struc.Emotions, Is.EqualTo(511), nameof(struc.Emotions)); - CollectionAssert.AreEqual(struc.Images, Array.CreateInstance(typeof(byte), 9), nameof(struc.Images)); - Assert.That(struc.Intelligence, Is.EqualTo(7), nameof(struc.Intelligence)); - Assert.That(struc.Aggressiveness, Is.EqualTo(5), nameof(struc.Aggressiveness)); - Assert.That(struc.Competitiveness, Is.EqualTo(6), nameof(struc.Competitiveness)); - Assert.That(struc.var_37, Is.EqualTo(0), nameof(struc.var_37)); - }); - } - - [Test] - public void LoadCurrencyObject() - { - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\CURRDOLL.DAT"; - var (obj, struc) = LoadObject(testFile); - - Assert.Multiple(() => - { - Assert.That(struc.ObjectIcon, Is.EqualTo(0), nameof(struc.ObjectIcon)); - Assert.That(struc.Separator, Is.EqualTo(0), nameof(struc.Separator)); - Assert.That(struc.Factor, Is.EqualTo(1), nameof(struc.Factor)); - }); - } - - [Test] - public void LoadDockObject() - { - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\SHIPST1.DAT"; - var (obj, struc) = LoadObject(testFile); - - Assert.Multiple(() => - { - Assert.That(struc.BuildCostFactor, Is.EqualTo(38), nameof(struc.BuildCostFactor)); - Assert.That(struc.SellCostFactor, Is.EqualTo(-35), nameof(struc.SellCostFactor)); - Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex)); - Assert.That(struc.var_07, Is.EqualTo(0), nameof(struc.var_07)); - Assert.That(struc.UnkImage, Is.EqualTo(0), nameof(struc.UnkImage)); - Assert.That(struc.Flags, Is.EqualTo(DockObjectFlags.None), nameof(struc.Flags)); - Assert.That(struc.NumAux01, Is.EqualTo(2), nameof(struc.NumAux01)); - Assert.That(struc.NumAux02Ent, Is.EqualTo(1), nameof(struc.NumAux02Ent)); - - //Assert.That(struc.var_14, Is.EqualTo(1), nameof(struc.var_14)); - //Assert.That(struc.var_14, Is.EqualTo(1), nameof(struc.var_18)); - //Assert.That(struc.var_1C[0], Is.EqualTo(1), nameof(struc.var_1C[0])); - - Assert.That(struc.DesignedYear, Is.EqualTo(0), nameof(struc.DesignedYear)); - Assert.That(struc.ObsoleteYear, Is.EqualTo(65535), nameof(struc.ObsoleteYear)); - Assert.That(struc.BoatPosition, Is.EqualTo(new Pos2(48, 0)), nameof(struc.BoatPosition)); - }); - } - - [Test] - public void LoadHillShapesObject() - { - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\HS1.DAT"; - var (obj, struc) = LoadObject(testFile); - - Assert.Multiple(() => - { - Assert.That(struc.HillHeightMapCount, Is.EqualTo(2), nameof(struc.HillHeightMapCount)); - Assert.That(struc.MountainHeightMapCount, Is.EqualTo(2), nameof(struc.MountainHeightMapCount)); - Assert.That(struc.var_08, Is.EqualTo(0), nameof(struc.var_08)); - CollectionAssert.AreEqual(struc.pad_0C, Array.CreateInstance(typeof(byte), 2), nameof(struc.pad_0C)); - }); - } - - [Test] - public void LoadIndustryObject() => Assert.Fail(); - - [Test] - public void LoadInterfaceSkinObject() => Assert.Fail(); - - [Test] - public void LoadLandObject() - { - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\GRASS1.DAT"; - var (obj, struc) = LoadObject(testFile); - - Assert.Multiple(() => - { - Assert.That(struc.CostIndex, Is.EqualTo(2), nameof(struc.CostIndex)); - Assert.That(struc.var_03, Is.EqualTo(5), nameof(struc.var_03)); - Assert.That(struc.var_04, Is.EqualTo(1), nameof(struc.var_04)); - Assert.That(struc.Flags, Is.EqualTo(LandObjectFlags.unk0), nameof(struc.Flags)); - Assert.That(struc.var_06, Is.EqualTo(0), nameof(struc.var_06)); - Assert.That(struc.var_07, Is.EqualTo(0), nameof(struc.var_07)); - Assert.That(struc.CostFactor, Is.EqualTo(20), nameof(struc.CostFactor)); - Assert.That(struc.pad_09, Is.EqualTo(0), nameof(struc.pad_09)); - Assert.That(struc.var_0E, Is.EqualTo(0), nameof(struc.var_0E)); - Assert.That(struc.CliffEdgeImage, Is.EqualTo(0), nameof(struc.CliffEdgeImage)); - Assert.That(struc.mapPixelImage, Is.EqualTo(0), nameof(struc.mapPixelImage)); - Assert.That(struc.pad_1A, Is.EqualTo(0), nameof(struc.pad_1A)); - Assert.That(struc.NumVariations, Is.EqualTo(3), nameof(struc.NumVariations)); - Assert.That(struc.VariationLikelihood, Is.EqualTo(10), nameof(struc.VariationLikelihood)); - Assert.That(struc.pad_1D, Is.EqualTo(0), nameof(struc.pad_1D)); - }); - } - - [Test] - public void LoadLevelCrossingObject() - { - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\LCROSS1.DAT"; - var (obj, struc) = LoadObject(testFile); - - Assert.Multiple(() => - { - Assert.That(struc.CostFactor, Is.EqualTo(30), nameof(struc.CostFactor)); - Assert.That(struc.SellCostFactor, Is.EqualTo(-10), nameof(struc.SellCostFactor)); - Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex)); - - Assert.That(struc.AnimationSpeed, Is.EqualTo(3), nameof(struc.AnimationSpeed)); - Assert.That(struc.ClosingFrames, Is.EqualTo(4), nameof(struc.ClosingFrames)); - Assert.That(struc.ClosedFrames, Is.EqualTo(11), nameof(struc.ClosedFrames)); - - Assert.That(struc.pad_0A[0], Is.EqualTo(3), nameof(struc.pad_0A) + "[0]"); - Assert.That(struc.pad_0A[1], Is.EqualTo(0), nameof(struc.pad_0A) + "[1]"); - - Assert.That(struc.DesignedYear, Is.EqualTo(1955), nameof(struc.DesignedYear)); - }); - } - - [Test] - public void LoadRegionObject() - { - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\REGUK.DAT"; - var (obj, struc) = LoadObject(testFile); - - Assert.Multiple(() => - { - CollectionAssert.AreEqual(struc.pad_06, Array.CreateInstance(typeof(byte), 2), nameof(struc.pad_06)); - Assert.That(struc.RequiredObjectCount, Is.EqualTo(1), nameof(struc.RequiredObjectCount)); - CollectionAssert.AreEqual(struc.requiredObjects, Array.CreateInstance(typeof(byte), 4), nameof(struc.requiredObjects)); - CollectionAssert.AreEqual(struc.pad_0D, Array.CreateInstance(typeof(byte), 5), nameof(struc.pad_0D)); - }); - } - - [Test] - public void LoadRoadExtraObject() - { - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\RDEXCAT1.DAT"; - var (obj, struc) = LoadObject(testFile); - - Assert.Multiple(() => - { - Assert.That(struc.RoadPieces, Is.EqualTo(127), nameof(struc.RoadPieces)); - Assert.That(struc.PaintStyle, Is.EqualTo(1), nameof(struc.PaintStyle)); - Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex)); - Assert.That(struc.BuildCostFactor, Is.EqualTo(4), nameof(struc.BuildCostFactor)); - Assert.That(struc.SellCostFactor, Is.EqualTo(-3), nameof(struc.SellCostFactor)); - Assert.That(struc.var_0E, Is.EqualTo(0), nameof(struc.var_0E)); - }); - } - - [Test] - public void LoadRoadObject() => Assert.Fail(); - - [Test] - public void LoadRoadStationObject() => Assert.Fail(); - - [Test] - public void LoadScaffoldingObject() - { - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\SCAFDEF.DAT"; - var (obj, struc) = LoadObject(testFile); - - Assert.Multiple(() => - { - Assert.That(struc.SegmentHeights[0], Is.EqualTo(16), nameof(struc.SegmentHeights) + "[0]"); - Assert.That(struc.SegmentHeights[1], Is.EqualTo(16), nameof(struc.SegmentHeights) + "[1]"); - Assert.That(struc.SegmentHeights[2], Is.EqualTo(32), nameof(struc.SegmentHeights) + "[2]"); - - Assert.That(struc.RoofHeights[0], Is.EqualTo(0), nameof(struc.RoofHeights) + "[0]"); - Assert.That(struc.RoofHeights[1], Is.EqualTo(0), nameof(struc.RoofHeights) + "[1]"); - Assert.That(struc.RoofHeights[2], Is.EqualTo(14), nameof(struc.RoofHeights) + "[2]"); - }); - } - - [Test] - public void LoadScenarioTextObject() - { - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\STEX000.DAT"; - var (obj, struc) = LoadObject(testFile); - - Assert.That(struc.pad_04, Is.EqualTo(0), nameof(struc.pad_04)); - } - - [Test] - public void LoadSnowObject() - { - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\SNOW.DAT"; - var (obj, struc) = LoadObject(testFile); - Assert.Pass(); - } - - [Test] - public void LoadSoundObject() => Assert.Fail(); - - [Test] - public void LoadSteamObject() => Assert.Fail(); - - [Test] - public void LoadStreetLightObject() - { - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\SLIGHT1.DAT"; - var (obj, struc) = LoadObject(testFile); - - Assert.Multiple(() => - { - Assert.That(struc.DesignedYear[0], Is.EqualTo(1900), nameof(struc.DesignedYear) + "[0]"); - Assert.That(struc.DesignedYear[1], Is.EqualTo(1950), nameof(struc.DesignedYear) + "[1]"); - Assert.That(struc.DesignedYear[2], Is.EqualTo(1985), nameof(struc.DesignedYear) + "[2]"); - - //Assert.That(struc.Image, Is.EqualTo(0)); - - Assert.That(obj.StringTable["Name"].Count, Is.EqualTo(2)); - Assert.That(obj.StringTable["Name"][LanguageId.english_uk], Is.EqualTo("Street Lights")); - Assert.That(obj.StringTable["Name"][LanguageId.english_us], Is.EqualTo("Street Lights")); - }); - } - - [Test] - public void SaveStreetLightObject() - { - // load - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\SLIGHT1.DAT"; - - var (obj, struc) = LoadObject(testFile); - - // struct write only - //var bytes = SawyerStreamWriter.WriteLocoObject(obj); - //CollectionAssert.AreEqual((byte[])[0, 0, 108, 7, 158, 7, 193, 7, 0, 0, 0, 0], bytes.ToArray()); - - // save - var tempFile = Path.GetTempFileName(); - SawyerStreamWriter.Save(tempFile, "ObjName_", obj); - - // load the saved object - var (obj2, struc2) = LoadObject(tempFile); - - // this is just the asserts from LoadStreetLightObject - Assert.Multiple(() => - { - Assert.That(struc2.DesignedYear[0], Is.EqualTo(1900), nameof(struc2.DesignedYear) + "[0]"); - Assert.That(struc2.DesignedYear[1], Is.EqualTo(1950), nameof(struc2.DesignedYear) + "[1]"); - Assert.That(struc2.DesignedYear[2], Is.EqualTo(1985), nameof(struc2.DesignedYear) + "[2]"); - - //Assert.That(struc2.Image, Is.EqualTo(0)); - - Assert.That(obj2.StringTable["Name"].Count, Is.EqualTo(2)); - Assert.That(obj2.StringTable["Name"][LanguageId.english_uk], Is.EqualTo("Street Lights")); - Assert.That(obj2.StringTable["Name"][LanguageId.english_us], Is.EqualTo("Street Lights")); - }); - } - - //[Test] - //public void SaveStreetLightObject2() - //{ - // // load - // const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\SLIGHT1.DAT"; - - // var (obj, struc) = LoadObject(testFile); - - // // save - // var bytesSource = SawyerStreamReader.LoadDecode(testFile); - // var bytesDest = SawyerStreamWriter.WriteLocoObject("ObjName_", obj).ToArray(); - - // var saveA = "Q:\\Games\\Locomotion\\ExperimentalObjects\\original.dat"; - // var saveB = "Q:\\Games\\Locomotion\\ExperimentalObjects\\saved.dat"; - - // File.WriteAllBytes(saveA, bytesSource); - // File.WriteAllBytes(saveB, bytesDest); - - // var headerA = SawyerStreamReader.LoadHeader(saveA); - // var headerB = SawyerStreamReader.LoadHeader(saveB); - - // //Assert.Multiple(() => - // //{ - // // for (int i = 0; i < Math.Min(bytesSource.Length, bytesDest.Length); ++i) - // // { - // // Assert.AreEqual(bytesSource[i], bytesDest[i], $"[{i}] {bytesSource[i]} {bytesDest[i]}"); - // // } - // //}); - - // CollectionAssert.AreEqual(bytesSource[0..16], bytesDest[0..16]); - // // skip object header - // CollectionAssert.AreEqual(bytesSource[21..], bytesDest[21..]); - //} - - [Test] - public void LoadTownNamesObject() => Assert.Fail(); - - [Test] - public void LoadTrackExtraObject() - { - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\TREXCAT1.DAT"; - var (obj, struc) = LoadObject(testFile); - - Assert.Multiple(() => - { - Assert.That(struc.TrackPieces, Is.EqualTo(1023), nameof(struc.TrackPieces)); - Assert.That(struc.PaintStyle, Is.EqualTo(1), nameof(struc.PaintStyle)); - Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex)); - Assert.That(struc.BuildCostFactor, Is.EqualTo(2), nameof(struc.BuildCostFactor)); - Assert.That(struc.SellCostFactor, Is.EqualTo(-1), nameof(struc.SellCostFactor)); - Assert.That(struc.var_0E, Is.EqualTo(0), nameof(struc.var_0E)); - }); - } - - [Test] - public void LoadTrackObject() => Assert.Fail(); - - [Test] - public void LoadTrainSignalObject() => Assert.Fail(); - - [Test] - public void LoadTrainStationObject() => Assert.Fail(); - - [Test] - public void LoadTreeObject() - { - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\BEECH.DAT"; - var (obj, struc) = LoadObject(testFile); - - Assert.Multiple(() => - { - Assert.That(struc.var_02, Is.EqualTo(40), nameof(struc.var_02)); - Assert.That(struc.Height, Is.EqualTo(131), nameof(struc.Height)); - Assert.That(struc.var_04, Is.EqualTo(27), nameof(struc.var_04)); - Assert.That(struc.var_05, Is.EqualTo(83), nameof(struc.var_05)); - Assert.That(struc.NumRotations, Is.EqualTo(1), nameof(struc.NumRotations)); - Assert.That(struc.Growth, Is.EqualTo(4), nameof(struc.Growth)); - Assert.That(struc.Flags, Is.EqualTo(TreeObjectFlags.HighAltitude | TreeObjectFlags.RequiresWater | TreeObjectFlags.HasShadow), nameof(struc.Flags)); - CollectionAssert.AreEqual(struc.Sprites, Array.CreateInstance(typeof(byte), 6), nameof(struc.Sprites)); - CollectionAssert.AreEqual(struc.SnowSprites, Array.CreateInstance(typeof(byte), 6), nameof(struc.SnowSprites)); - Assert.That(struc.ShadowImageOffset, Is.EqualTo(0), nameof(struc.ShadowImageOffset)); - Assert.That(struc.var_3C, Is.EqualTo(15), nameof(struc.var_3C)); - Assert.That(struc.SeasonState, Is.EqualTo(3), nameof(struc.SeasonState)); - Assert.That(struc.var_3E, Is.EqualTo(2), nameof(struc.var_3E)); - Assert.That(struc.CostIndex, Is.EqualTo(3), nameof(struc.CostIndex)); - Assert.That(struc.BuildCostFactor, Is.EqualTo(8), nameof(struc.BuildCostFactor)); - Assert.That(struc.ClearCostFactor, Is.EqualTo(4), nameof(struc.ClearCostFactor)); - Assert.That(struc.Colours, Is.EqualTo(0), nameof(struc.Colours)); - Assert.That(struc.Rating, Is.EqualTo(10), nameof(struc.Rating)); - Assert.That(struc.DemolishRatingReduction, Is.EqualTo(-15), nameof(struc.DemolishRatingReduction)); - }); - } - - [Test] - public void LoadTunnelObject() - { - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\TUNNEL1.DAT"; - var (obj, struc) = LoadObject(testFile); - Assert.Pass(); - } - - [Test] - public void LoadVehicleAircraftObject() - { - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\707.DAT"; - var (obj, struc) = LoadObject(testFile); - - //var s5header = obj.S5Header; - //var objHeader = obj.ObjectHeader; - - //Assert.Multiple(() => - //{ - // Assert.That(s5header.Flags, Is.EqualTo(283680407), nameof(s5header.Flags)); - // Assert.That(s5header.Name, Is.EqualTo("707 "), nameof(s5header.Name)); - // Assert.That(s5header.Checksum, Is.EqualTo(1331114877), nameof(s5header.Checksum)); - // Assert.That(s5header.ObjectType, Is.EqualTo(ObjectType.Vehicle), nameof(s5header.ObjectType)); - - // Assert.That(objHeader.Encoding, Is.EqualTo(SawyerEncoding.RunLengthSingle), nameof(objHeader.Encoding)); - // Assert.That(objHeader.DataLength, Is.EqualTo(159566), nameof(objHeader.DataLength)); - //}); - - Assert.Multiple(() => - { - Assert.That(struc.Mode, Is.EqualTo(TransportMode.Air), nameof(struc.Mode)); - Assert.That(struc.Type, Is.EqualTo(VehicleType.Aircraft), nameof(struc.Type)); - Assert.That(struc.var_04, Is.EqualTo(1), nameof(struc.var_04)); - // Assert.That(struc.TrackType, Is.EqualTo(0xFF), nameof(struc.TrackType)); // is changed after load from 0 to 255 - Assert.That(struc.NumTrackExtras, Is.EqualTo(0), nameof(struc.NumTrackExtras)); - Assert.That(struc.CostIndex, Is.EqualTo(8), nameof(struc.CostIndex)); - Assert.That(struc.CostFactor, Is.EqualTo(345), nameof(struc.CostFactor)); - Assert.That(struc.Reliability, Is.EqualTo(88), nameof(struc.Reliability)); - Assert.That(struc.RunCostIndex, Is.EqualTo(4), nameof(struc.RunCostIndex)); - Assert.That(struc.RunCostFactor, Is.EqualTo(55), nameof(struc.RunCostFactor)); - Assert.That(struc.ColourType, Is.EqualTo(9), nameof(struc.ColourType)); - Assert.That(struc.NumCompatibleVehicles, Is.EqualTo(0), nameof(struc.NumCompatibleVehicles)); - CollectionAssert.AreEqual(Enumerable.Repeat(0, 8).ToArray(), struc.CompatibleVehicles, nameof(struc.CompatibleVehicles)); - CollectionAssert.AreEqual(Enumerable.Repeat(0, 4).ToArray(), struc.RequiredTrackExtras, nameof(struc.RequiredTrackExtras)); - //Assert.That(struc.var_24, Is.EqualTo(0), nameof(struc.var_24)); - //Assert.That(struc.BodySprites, Is.EqualTo(0), nameof(struc.BodySprites)); - //Assert.That(struc.BogieSprites, Is.EqualTo(1), nameof(struc.BogieSprites)); - Assert.That(struc.Power, Is.EqualTo(3000), nameof(struc.Power)); - Assert.That(struc.Speed, Is.EqualTo(604), nameof(struc.Speed)); - Assert.That(struc.RackSpeed, Is.EqualTo(120), nameof(struc.RackSpeed)); - Assert.That(struc.Weight, Is.EqualTo(141), nameof(struc.Weight)); - Assert.That(struc.Flags, Is.EqualTo((VehicleObjectFlags)16384), nameof(struc.Flags)); - // CollectionAssert.AreEqual(struc.MaxCargo, Enumerable.Repeat(0, 2).ToArray(), nameof(struc.MaxCargo)); // this is changed after load from 0 to 24 - CollectionAssert.AreEqual(Enumerable.Repeat(0, 2).ToArray(), struc.CompatibleCargoCategories, nameof(struc.CompatibleCargoCategories)); - CollectionAssert.AreEqual(Enumerable.Repeat(0, 32).ToArray(), struc.CargoTypeSpriteOffsets, nameof(struc.CargoTypeSpriteOffsets)); - Assert.That(struc.NumSimultaneousCargoTypes, Is.EqualTo(1), nameof(struc.NumSimultaneousCargoTypes)); - Assert.That(struc.Animation[0].ObjectId, Is.EqualTo(0), nameof(struc.Animation)); - Assert.That(struc.Animation[0].Height, Is.EqualTo(24), nameof(struc.Animation)); - Assert.That(struc.Animation[0].Type, Is.EqualTo(SimpleAnimationType.None), nameof(struc.Animation)); - Assert.That(struc.Animation[1].ObjectId, Is.EqualTo(0), nameof(struc.Animation)); - Assert.That(struc.Animation[1].Height, Is.EqualTo(0), nameof(struc.Animation)); - Assert.That(struc.Animation[1].Type, Is.EqualTo(SimpleAnimationType.None), nameof(struc.Animation)); - Assert.That(struc.var_113, Is.EqualTo(0), nameof(struc.var_113)); - Assert.That(struc.Designed, Is.EqualTo(1957), nameof(struc.Designed)); - Assert.That(struc.Obsolete, Is.EqualTo(1987), nameof(struc.Obsolete)); - Assert.That(struc.RackRailType, Is.EqualTo(0), nameof(struc.RackRailType)); - Assert.That(struc.DrivingSoundType, Is.EqualTo(DrivingSoundType.Engine1), nameof(struc.DrivingSoundType)); - //Assert.That(struc.Sound, Is.EqualTo(0), nameof(struc.Sound)); - //Assert.That(struc.pad_135, Is.EqualTo(0), nameof(struc.pad_135)); - Assert.That(struc.NumStartSounds, Is.EqualTo(2), nameof(struc.NumStartSounds)); - CollectionAssert.AreEqual(Enumerable.Repeat(0, 3).ToArray(), struc.StartSounds, nameof(struc.StartSounds)); - }); - } - - [Test] - public void LoadWallObject() - { - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\FENCE1.DAT"; - var (obj, struc) = LoadObject(testFile); - - Assert.Multiple(() => - { - Assert.That(struc.var_06, Is.EqualTo(15), nameof(struc.var_06)); - Assert.That(struc.Flags, Is.EqualTo(WallObjectFlags.None), nameof(struc.Flags)); - Assert.That(struc.Height, Is.EqualTo(2), nameof(struc.Height)); - Assert.That(struc.var_09, Is.EqualTo(8), nameof(struc.var_09)); - }); - } - - [Test] - public void LoadWaterObject() - { - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\WATER1.DAT"; - var (obj, struc) = LoadObject(testFile); - - Assert.Multiple(() => - { - Assert.That(struc.CostIndex, Is.EqualTo(2), nameof(struc.CostIndex)); - Assert.That(struc.var_03, Is.EqualTo(0), nameof(struc.var_03)); - Assert.That(struc.CostFactor, Is.EqualTo(51), nameof(struc.CostFactor)); - Assert.That(struc.var_05, Is.EqualTo(0), nameof(struc.var_05)); - Assert.That(struc.var_0A, Is.EqualTo(0), nameof(struc.var_0A)); - }); - } - } -} \ No newline at end of file diff --git a/OpenLocoToolTests/ObjectSavingTests.cs b/OpenLocoToolTests/ObjectSavingTests.cs deleted file mode 100644 index c8c02129..00000000 --- a/OpenLocoToolTests/ObjectSavingTests.cs +++ /dev/null @@ -1,76 +0,0 @@ -using NUnit.Framework; -using OpenLocoTool.DatFileParsing; -using OpenLocoTool.Headers; -using OpenLocoTool.Objects; -using OpenLocoToolCommon; - -namespace OpenLocoToolTests -{ - [TestFixture] - public class ObjectSavingTests - { - [Test] - public void WriteLocoStruct() - { - // arrange - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\SIGC3.DAT"; - var fileSize = new FileInfo(testFile).Length; - var logger = new Logger(); - var loaded = SawyerStreamReader.LoadFullObjectFromFile(testFile); - - // load data in raw bytes for test - ReadOnlySpan fullData = SawyerStreamReader.LoadBytesFromFile(testFile); - - // make openlocotool useful objects - var s5Header = S5Header.Read(fullData[0..S5Header.StructLength]); - var remainingData = fullData[S5Header.StructLength..]; - - var objectHeader = ObjectHeader.Read(remainingData[0..ObjectHeader.StructLength]); - remainingData = remainingData[ObjectHeader.StructLength..]; - - var originalEncodedData = remainingData.ToArray(); - var decodedData = SawyerStreamReader.Decode(objectHeader.Encoding, originalEncodedData); - remainingData = decodedData; - - var originalObjectData = decodedData[..ObjectAttributes.StructSize()]; - - // act - var bytes = ByteWriter.WriteLocoStruct(loaded.LocoObject.Object); - - // assert - CollectionAssert.AreEqual(originalObjectData, bytes.ToArray()); - } - - [Test] - public void Industry() - { - // arrange - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\BREWERY.DAT"; - var fileSize = new FileInfo(testFile).Length; - var logger = new Logger(); - var loaded = SawyerStreamReader.LoadFullObjectFromFile(testFile); - - // load data in raw bytes for test - ReadOnlySpan fullData = SawyerStreamReader.LoadBytesFromFile(testFile); - - // make openlocotool useful objects - var s5Header = S5Header.Read(fullData[0..S5Header.StructLength]); - var remainingData = fullData[S5Header.StructLength..]; - - var objectHeader = ObjectHeader.Read(remainingData[0..ObjectHeader.StructLength]); - remainingData = remainingData[ObjectHeader.StructLength..]; - - var originalEncodedData = remainingData.ToArray(); - var decodedData = SawyerStreamReader.Decode(objectHeader.Encoding, originalEncodedData); - remainingData = decodedData; - - var originalObjectData = decodedData[..ObjectAttributes.StructSize()]; - - // act - var bytes = ByteWriter.WriteLocoStruct(loaded.LocoObject.Object); - - // assert - CollectionAssert.AreEqual(originalObjectData, bytes.ToArray()); - } - } -} \ No newline at end of file diff --git a/OpenLocoToolTests/OpenLocoToolTests.csproj b/OpenLocoToolTests/OpenLocoToolTests.csproj deleted file mode 100644 index c25eeda3..00000000 --- a/OpenLocoToolTests/OpenLocoToolTests.csproj +++ /dev/null @@ -1,25 +0,0 @@ - - - - net8.0 - enable - enable - - false - true - - - - - - - - - - - - - - - - diff --git a/OpenLocoToolTests/Usings.cs b/OpenLocoToolTests/Usings.cs deleted file mode 100644 index e69de29b..00000000 diff --git a/README.md b/README.md index fcf3fdb6..bbde8cad 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,77 @@ -# OpenLocoTool -A modern implementation of 'LocoTool' for dat file parsing - -# GUI - -1. open UI, no loading -2. user selects a directory -3. if no open-loco-tool index file exists, open-loco-tool fully loads all dat files in directory, creates an index and writes it to `objectIndex.json` in that folder. this is SLOW (currently) -4. next time that directory is opened, the index is read instead of loading all files. this is FAST - -# Dat Object Layout - -|-File-------------------------------| -|-S5Header-|-DatHeader--|-ObjectData-| - -====================================================================================== - -|-S5Header----------------| -|-Flags-|-Name-|-Checksum-| - -|-DatHeader-------------| -|-Encoding-|-Datalength-| - -|-ObjectData-----------------------------------------| -|-Object-|-StringTable-|-VariableData-|-GraphicsData-| - -====================================================================================== - -|-Object-| --- - -|-StringTable-| -|-String{n}---| - -|-VariableData-| --- - -|-GraphicsData------------------------------| -|-G1Header-|-G1Element32{n}-|-ImageBytes{n}-| - -====================================================================================== - -|-String-----------------| -|-Language-|-StringBytes-| - -|-G1Header---------------| -|-NumEntries-|-TotalSize-| - -|-G1Element32------------------------------------------------------| -|-Offset-|-Width-|-Height-|-xOffset-|-yOffset-|-Flags-|-ZoomOffset-| - -|-ImageBytes-| --- offset by G1Element32.Offset - +# OpenLoco Object Editor +A modern implementation of 'LocoTool' for Locomotion `dat` file parsing and editing + +# How to use + +## 1. Load an object folder +1. Click `File` -> `ObjData Directories` -> `Add New`, which will open a folder browser window +2. Navigate to a folder that contains Locomotion object files +3. Click `Select Folder` to close the folder browser window +4. The tool will load all objects in that folder and display them in the tree view on the left of the tool + +## 3. Select an object to view/edit +1. Expand the tree-view of objects to one you wish to edit +2. Click on the object +3. It will appear in the editor view on the right side of the tool + +# Features + +- Locomotion `ObjData` folder: + - Can open and edit all object types + - Can save object types back to `dat` file format (albeit with no encoding) + - Can display image table and string table of all objects (and sound data for SoundObject), and allow editing of them +- Locomotion `Data` folder: + - Supported + - `G1.dat` - the graphics file + - display its graphics + - export all images to folder + - import all images from folder + - `css1.dat` - the sound effects file + - play each sound effect individually + - export a sound effect to wav + - import a sound effect from wav + - write a new `css1.dat` file with your new sound effects + - `css{2-5}.dat` - the 'system' music tracks + - play each tracks + - export a track to wav + - import a track from wav + - write a new `css{2-5}.dat` file with your new track + - Unsupported + - Tutorial files, eg `tut1024_1.dat` + - Extra language files, eg `kanji.dat` + - Map loading, eg `title.dat`, or any other save gave + +# Misc + +## Settings +- In the same folder as the tool executable, a `settings.json` file will be created on first startup +- This is where the users' object folder paths are saved, and other program data + +## Indexing +- When the tool first loads an objdata directory it will scan every file to make an index and save that into `objectIndex.json` in that folder +- This indexing is relatively slow, but only needs to run once/when the folder contents change +- On subsequent uses of the tool, the index file will be loaded instead, and this is fast +- The tool will print a log message if it detects changes in the folder and thinks you need to reindex it + +## Unit Testing +- All object types have a unit test which + - Loads a pre-determined object file of that type + - Asserts all (or most) of its values are correct + - Saves the file to memory + - Reloads the just-saved version of the file + - Checks that the reloaded file is the same as the originally-loaded file, byte for byte. This is a byte comparison of the *decoded and decompressed* bytes, not the *on-disk* bytes + +# Future Plans/Features +- Better flag editing support +- Validation of object limits/sane values +- Detection of bugged objects +- Support/edit tutorials +- Support/edit maps/savegames/scenarios +- Support language files +- Implement vehicle previewer +- Better G1 support including palette file editing +- Export/convert object to a future modern OpenLoco file format +- Dark modern +- Cross-platform support +- Full unit-testing suite +- ...many more things diff --git a/OpenLocoToolCommon/ILogger.cs b/Shared/ILogger.cs similarity index 96% rename from OpenLocoToolCommon/ILogger.cs rename to Shared/ILogger.cs index 8b11fa8d..e57d4ed2 100644 --- a/OpenLocoToolCommon/ILogger.cs +++ b/Shared/ILogger.cs @@ -1,6 +1,6 @@ using System.Runtime.CompilerServices; -namespace OpenLocoToolCommon +namespace OpenLoco.ObjectEditor.Logging { public enum LogLevel { Debug2, Debug, Info, Warning, Error }; @@ -11,16 +11,16 @@ public interface ILogger void Debug2(string message, [CallerMemberName] string callerMemberName = "") => Log(LogLevel.Debug2, message, callerMemberName); - + void Debug(string message, [CallerMemberName] string callerMemberName = "") => Log(LogLevel.Debug, message, callerMemberName); - + void Info(string message, [CallerMemberName] string callerMemberName = "") => Log(LogLevel.Info, message, callerMemberName); - + void Warning(string message, [CallerMemberName] string callerMemberName = "") => Log(LogLevel.Warning, message, callerMemberName); - + void Error(string message, [CallerMemberName] string callerMemberName = "") => Log(LogLevel.Error, message, callerMemberName); diff --git a/OpenLocoToolCommon/Logger.cs b/Shared/Logger.cs similarity index 94% rename from OpenLocoToolCommon/Logger.cs rename to Shared/Logger.cs index 9e81571f..804d299d 100644 --- a/OpenLocoToolCommon/Logger.cs +++ b/Shared/Logger.cs @@ -1,4 +1,4 @@ -namespace OpenLocoToolCommon +namespace OpenLoco.ObjectEditor.Logging { public class LogAddedEventArgs(LogLine log) : EventArgs { diff --git a/OpenLocoToolCommon/ReflectionLogger.cs b/Shared/ReflectionLogger.cs similarity index 76% rename from OpenLocoToolCommon/ReflectionLogger.cs rename to Shared/ReflectionLogger.cs index a3b2ae1f..ee6182bb 100644 --- a/OpenLocoToolCommon/ReflectionLogger.cs +++ b/Shared/ReflectionLogger.cs @@ -1,7 +1,7 @@ using System.Reflection; using System.Text; -namespace OpenLocoToolCommon +namespace OpenLoco.ObjectEditor.Logging { public static class ReflectionLogger { @@ -15,7 +15,7 @@ static StringBuilder ToString(T obj, StringBuilder sb) { if (obj == null) { - sb.Append(""); + _ = sb.Append(""); return sb; } @@ -30,7 +30,7 @@ static StringBuilder ToString(T obj, StringBuilder sb) // For primitive types, use their ToString representation directly if (type.IsPrimitive || obj is string) { - sb.Append(obj.ToString()); + _ = sb.Append(obj.ToString()); return sb; } @@ -38,11 +38,11 @@ static StringBuilder ToString(T obj, StringBuilder sb) var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance); - sb.Append(type.Name); + _ = sb.Append(type.Name); if (properties.Length > 0 || fields.Length > 0) { - sb.Append(" { "); + _ = sb.Append(" { "); for (var i = 0; i < fields.Length; i++) { @@ -50,13 +50,13 @@ static StringBuilder ToString(T obj, StringBuilder sb) var fieldName = field.Name; var fieldValue = field.GetValue(obj); - sb.Append(fieldName); - sb.Append('='); - sb.Append(ToString(fieldValue)); + _ = sb.Append(fieldName); + _ = sb.Append('='); + _ = sb.Append(ToString(fieldValue)); if (i < fields.Length - 1) { - sb.Append(", "); + _ = sb.Append(", "); } } @@ -66,17 +66,17 @@ static StringBuilder ToString(T obj, StringBuilder sb) var propertyName = property.Name; var propertyValue = property.GetValue(obj); - sb.Append(propertyName); - sb.Append('='); - sb.Append(ToString(propertyValue)); + _ = sb.Append(propertyName); + _ = sb.Append('='); + _ = sb.Append(ToString(propertyValue)); if (i < properties.Length - 1) { - sb.Append(", "); + _ = sb.Append(", "); } } - sb.Append(" } "); + _ = sb.Append(" } "); } return sb; diff --git a/OpenLocoToolCommon/OpenLocoToolCommon.csproj b/Shared/Shared.csproj similarity index 100% rename from OpenLocoToolCommon/OpenLocoToolCommon.csproj rename to Shared/Shared.csproj diff --git a/Tests/LoadSaveTests.cs b/Tests/LoadSaveTests.cs new file mode 100644 index 00000000..778cf10b --- /dev/null +++ b/Tests/LoadSaveTests.cs @@ -0,0 +1,887 @@ +using Core.Objects; +using Core.Objects.Sound; +using NUnit.Framework; +using NUnit.Framework.Internal; +using OpenLoco.ObjectEditor.Data; +using OpenLoco.ObjectEditor.DatFileParsing; +using OpenLoco.ObjectEditor.Headers; +using OpenLoco.ObjectEditor.Objects; +using OpenLoco.ObjectEditor.Types; +using Logger = OpenLoco.ObjectEditor.Logging.Logger; + +namespace OpenLoco.ObjectEditor.Tests +{ + [TestFixture] + public class LoadSaveTests + { + const string BaseObjDataPath = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\"; + + // TODO: find a way to not have to hardcode a path here (but this may be impossible as it will depend on a user's PC and Loco install path) + // TODO: find a nicer (and more automated) way to check Name+Image fields, StringTable and G1Table + + static (ILocoObject, T) LoadObject(string filename) where T : ILocoStruct + { + filename = Path.Combine(BaseObjDataPath, filename); + var fileSize = new FileInfo(filename).Length; + var logger = new Logger(); + var loaded = SawyerStreamReader.LoadFullObjectFromFile(filename, logger: logger); + Assert.Multiple(() => + { + Assert.That(loaded.LocoObject, Is.Not.Null); + Assert.That(loaded.DatFileInfo.ObjectHeader.DataLength, Is.EqualTo(fileSize - S5Header.StructLength - ObjectHeader.StructLength), "ObjectHeader.Length didn't match actual size of struct"); + }); + return (loaded.LocoObject!, (T)loaded.LocoObject!.Object); + } + + static (ILocoObject, T) LoadObject(ReadOnlySpan data) where T : ILocoStruct + { + var logger = new Logger(); + var loaded = SawyerStreamReader.LoadFullObjectFromStream(data, logger: logger); + + Assert.That(loaded.LocoObject, Is.Not.Null); + Assert.That(loaded.DatFileInfo.ObjectHeader.DataLength, Is.EqualTo(data.Length - S5Header.StructLength - ObjectHeader.StructLength), "ObjectHeader.Length didn't match actual size of struct"); + + return (loaded.LocoObject!, (T)loaded.LocoObject!.Object); + } + + public void LoadSaveGenericTest(string objectName, Action assertFunc) where T : ILocoStruct + { + var (obj1, struc1) = LoadObject(objectName); + assertFunc(obj1, struc1); + + var bytes1 = SawyerStreamWriter.WriteLocoObject(objectName, obj1); + + var (obj2, struc2) = LoadObject(bytes1); + assertFunc(obj2, struc2); + + var bytes2 = SawyerStreamWriter.WriteLocoObject(objectName, obj2); + + // we could just simply combare byte arrays and be done, but i wanted something that makes it easier to diagnose problems + + // grab headers first + var bytes1S5Header = S5Header.Read(bytes1[0..S5Header.StructLength]); + var bytes2S5Header = S5Header.Read(bytes2[0..S5Header.StructLength]); + + var bytes1ObjHeader = ObjectHeader.Read(bytes1[S5Header.StructLength..(S5Header.StructLength + ObjectHeader.StructLength)]); + var bytes2ObjHeader = ObjectHeader.Read(bytes2[S5Header.StructLength..(S5Header.StructLength + ObjectHeader.StructLength)]); + + // then grab object bytes + var bytes1ObjArr = bytes1[21..].ToArray(); + var bytes2ObjArr = bytes2[21..].ToArray(); + + Assert.Multiple(() => + { + Assert.That(bytes2S5Header, Is.EqualTo(bytes1S5Header)); + Assert.That(bytes2ObjHeader, Is.EqualTo(bytes1ObjHeader)); + CollectionAssert.AreEqual(bytes1ObjArr.ToArray(), bytes2ObjArr.ToArray()); + }); + } + + [TestCase("AIRPORT1.DAT")] + public void AirportObject(string objectName) + { + void assertFunc(ILocoObject obj, AirportObject struc) => Assert.Multiple(() => + { + Assert.That(struc.Name, Is.EqualTo(0), nameof(struc.Name)); + Assert.That(struc.BuildCostFactor, Is.EqualTo(256), nameof(struc.BuildCostFactor)); + Assert.That(struc.SellCostFactor, Is.EqualTo(-192), nameof(struc.SellCostFactor)); + Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex)); + Assert.That(struc.Image, Is.EqualTo(0), nameof(struc.Image)); + Assert.That(struc.ImageOffset, Is.EqualTo(0), nameof(struc.ImageOffset)); + Assert.That(struc.AllowedPlaneTypes, Is.EqualTo(24), nameof(struc.AllowedPlaneTypes)); + Assert.That(struc.NumBuildingAnimations, Is.EqualTo(94), nameof(struc.NumBuildingAnimations)); + Assert.That(struc.NumBuildingVariations, Is.EqualTo(23), nameof(struc.NumBuildingVariations)); + + //Assert.That(struc.var_14, Is.EqualTo(0), nameof(struc.var_14)); + //Assert.That(struc.var_18, Is.EqualTo(0), nameof(struc.var_18)); + //Assert.That(struc.var_1C, Is.EqualTo(0), nameof(struc.var_1C)); + //Assert.That(struc.var_9C, Is.EqualTo(0), nameof(struc.var_9C)); + + Assert.That(struc.LargeTiles, Is.EqualTo(917759), nameof(struc.LargeTiles)); + Assert.That(struc.MinX, Is.EqualTo(-4), nameof(struc.MinX)); + Assert.That(struc.MinY, Is.EqualTo(-4), nameof(struc.MinY)); + Assert.That(struc.MaxX, Is.EqualTo(5), nameof(struc.MaxX)); + Assert.That(struc.MaxY, Is.EqualTo(5), nameof(struc.MaxY)); + Assert.That(struc.DesignedYear, Is.EqualTo(1970), nameof(struc.DesignedYear)); + Assert.That(struc.ObsoleteYear, Is.EqualTo(65535), nameof(struc.ObsoleteYear)); + Assert.That(struc.NumMovementNodes, Is.EqualTo(26), nameof(struc.NumMovementNodes)); + Assert.That(struc.NumMovementEdges, Is.EqualTo(30), nameof(struc.NumMovementEdges)); + + //Assert.That(struc.MovementNodes, Is.EqualTo(0), nameof(struc.MovementNodes)); + //Assert.That(struc.MovementEdges, Is.EqualTo(0), nameof(struc.MovementEdges)); + + Assert.That(struc.pad_B6[0], Is.EqualTo(0), nameof(struc.pad_B6) + "[0]"); + Assert.That(struc.pad_B6[1], Is.EqualTo(19), nameof(struc.pad_B6) + "[1]"); + Assert.That(struc.pad_B6[2], Is.EqualTo(0), nameof(struc.pad_B6) + "[2]"); + Assert.That(struc.pad_B6[3], Is.EqualTo(0), nameof(struc.pad_B6) + "[3]"); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("BRDGBRCK.DAT")] + public void BridgeObject(string objectName) + { + void assertFunc(ILocoObject obj, BridgeObject struc) => Assert.Multiple(() => + { + Assert.That(struc.NoRoof, Is.EqualTo(0), nameof(struc.NoRoof)); + + // Assert.That(struc.pad_03[0], Is.EqualTo(0), nameof(struc.pad_03) + "[0]"); + // Assert.That(struc.pad_03[1], Is.EqualTo(0), nameof(struc.pad_03) + "[1]"); + // Assert.That(struc.pad_03[2], Is.EqualTo(0), nameof(struc.pad_03) + "[2]"); + + Assert.That(struc.var_06, Is.EqualTo(16), nameof(struc.var_06)); + Assert.That(struc.SpanLength, Is.EqualTo(1), nameof(struc.SpanLength)); + Assert.That(struc.PillarSpacing, Is.EqualTo(255), nameof(struc.PillarSpacing)); + Assert.That(struc.MaxSpeed, Is.EqualTo(60), nameof(struc.MaxSpeed)); + Assert.That(struc.MaxHeight, Is.EqualTo(10), nameof(struc.MaxHeight)); + Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex)); + Assert.That(struc.BaseCostFactor, Is.EqualTo(16), nameof(struc.BaseCostFactor)); + Assert.That(struc.HeightCostFactor, Is.EqualTo(8), nameof(struc.HeightCostFactor)); + Assert.That(struc.SellCostFactor, Is.EqualTo(-12), nameof(struc.SellCostFactor)); + Assert.That(struc.DisabledTrackCfg, Is.EqualTo(0), nameof(struc.DisabledTrackCfg)); + //Assert.That(struc.TrackNumCompatible, Is.EqualTo(0), nameof(struc.TrackNumCompatible)); + //CollectionAssert.AreEqual(struc.TrackMods, Array.CreateInstance(typeof(byte), 7), nameof(struc.TrackMods)); + //Assert.That(struc.RoadNumCompatible, Is.EqualTo(0), nameof(struc.RoadNumCompatible)); + //CollectionAssert.AreEqual(struc.RoadMods, Array.CreateInstance(typeof(byte), 7), nameof(struc.RoadMods)); + Assert.That(struc.DesignedYear, Is.EqualTo(0), nameof(struc.DesignedYear)); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("HQ1.DAT")] + public void BuildingObject(string objectName) + { + void assertFunc(ILocoObject obj, BuildingObject struc) => Assert.Multiple(() => + { + Assert.That(struc.Name, Is.EqualTo(0)); + Assert.That(struc.Image, Is.EqualTo(0)); + + Assert.That(struc.NumVariations, Is.EqualTo(5), nameof(struc.NumVariations)); + // CollectionAssert.AreEqual(struc.VariationHeights, Array.CreateInstance(typeof(byte), 4), nameof(struc.VariationHeights)); + // VariationHeights + // VariationAnimations + // VariationParts + Assert.That(struc.Colours, Is.EqualTo(0), nameof(struc.Colours)); + Assert.That(struc.DesignedYear, Is.EqualTo(0), nameof(struc.DesignedYear)); + Assert.That(struc.ObsoleteYear, Is.EqualTo(65535), nameof(struc.ObsoleteYear)); + Assert.That(struc.Flags, Is.EqualTo(BuildingObjectFlags.LargeTile | BuildingObjectFlags.MiscBuilding | BuildingObjectFlags.IsHeadquarters), nameof(struc.Flags)); + Assert.That(struc.ClearCostIndex, Is.EqualTo(1), nameof(struc.ClearCostIndex)); + Assert.That(struc.ClearCostFactor, Is.EqualTo(0), nameof(struc.ClearCostFactor)); + Assert.That(struc.ScaffoldingSegmentType, Is.EqualTo(1), nameof(struc.ScaffoldingSegmentType)); + Assert.That(struc.ScaffoldingColour, Is.EqualTo(Colour.yellow), nameof(struc.ScaffoldingColour)); + Assert.That(struc.GeneratorFunction, Is.EqualTo(3), nameof(struc.GeneratorFunction)); + Assert.That(struc.var_9F, Is.EqualTo(3), nameof(struc.var_9F)); + // ProducedQuantity + // ProducedCargoType + // RequiredCargoType + // CollectionAssert.AreEqual(struc.var_A6, Array.CreateInstance(typeof(byte), 2), nameof(struc.var_A6)); + // CollectionAssert.AreEqual(struc.var_A8, Array.CreateInstance(typeof(byte), 2), nameof(struc.var_A8)); + // CollectionAssert.AreEqual(struc.var_A4, Array.CreateInstance(typeof(byte), 2), nameof(struc.var_A4)); + Assert.That(struc.DemolishRatingReduction, Is.EqualTo(0), nameof(struc.DemolishRatingReduction)); + Assert.That(struc.var_AC, Is.EqualTo(255), nameof(struc.var_AC)); + Assert.That(struc.var_AD, Is.EqualTo(0), nameof(struc.var_AD)); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("CHEMICAL.DAT")] + public void CargoObject(string objectName) + { + void assertFunc(ILocoObject obj, CargoObject struc) => Assert.Multiple(() => + { + Assert.That(struc.var_02, Is.EqualTo(256), nameof(struc.var_02)); + Assert.That(struc.CargoTransferTime, Is.EqualTo(64), nameof(struc.CargoTransferTime)); + Assert.That(struc.UnitInlineSprite, Is.EqualTo(0), nameof(struc.UnitInlineSprite)); + Assert.That(struc.CargoCategory, Is.EqualTo(CargoCategory.liquids), nameof(struc.CargoCategory)); + Assert.That(struc.Flags, Is.EqualTo(CargoObjectFlags.Delivering), nameof(struc.Flags)); + Assert.That(struc.NumPlatformVariations, Is.EqualTo(1), nameof(struc.NumPlatformVariations)); + Assert.That(struc.var_14, Is.EqualTo(4), nameof(struc.var_14)); + Assert.That(struc.PremiumDays, Is.EqualTo(10), nameof(struc.PremiumDays)); + Assert.That(struc.MaxNonPremiumDays, Is.EqualTo(30), nameof(struc.MaxNonPremiumDays)); + Assert.That(struc.MaxPremiumRate, Is.EqualTo(128), nameof(struc.MaxPremiumRate)); + Assert.That(struc.PenaltyRate, Is.EqualTo(256), nameof(struc.PenaltyRate)); + Assert.That(struc.PaymentFactor, Is.EqualTo(62), nameof(struc.PaymentFactor)); + Assert.That(struc.PaymentIndex, Is.EqualTo(10), nameof(struc.PaymentIndex)); + Assert.That(struc.UnitSize, Is.EqualTo(10), nameof(struc.UnitSize)); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("LSBROWN.DAT")] + public void CliffEdgeObject(string objectName) + { + void assertFunc(ILocoObject obj, CliffEdgeObject struc) => Assert.Multiple(() => + { + var strTable = obj.StringTable; + + Assert.That(strTable.Table, Has.Count.EqualTo(1)); + Assert.That(strTable.Table.ContainsKey("Name"), Is.True); + + var entry = strTable.Table["Name"]; + + Assert.That(entry[LanguageId.english_uk], Is.EqualTo("Brown Rock")); + Assert.That(entry[LanguageId.english_us], Is.EqualTo("Brown Rock")); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("CLIM1.DAT")] + public void ClimateObject(string objectName) + { + void assertFunc(ILocoObject obj, ClimateObject struc) => Assert.Multiple(() => + { + Assert.That(struc.FirstSeason, Is.EqualTo(1), nameof(struc.FirstSeason)); + Assert.That(struc.SeasonLengths[0], Is.EqualTo(57), nameof(struc.SeasonLengths) + "[0]"); + Assert.That(struc.SeasonLengths[1], Is.EqualTo(80), nameof(struc.SeasonLengths) + "[1]"); + Assert.That(struc.SeasonLengths[2], Is.EqualTo(100), nameof(struc.SeasonLengths) + "[2]"); + Assert.That(struc.SeasonLengths[3], Is.EqualTo(80), nameof(struc.SeasonLengths) + "[3]"); + Assert.That(struc.WinterSnowLine, Is.EqualTo(48), nameof(struc.WinterSnowLine)); + Assert.That(struc.SummerSnowLine, Is.EqualTo(76), nameof(struc.SummerSnowLine)); + Assert.That(struc.pad_09, Is.EqualTo(0), nameof(struc.pad_09)); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("COMP1.DAT")] + public void CompetitorObject(string objectName) + { + void assertFunc(ILocoObject obj, CompetitorObject struc) => Assert.Multiple(() => + { + Assert.That(struc.var_04, Is.EqualTo(6672), nameof(struc.var_04)); + Assert.That(struc.var_08, Is.EqualTo(2053), nameof(struc.var_08)); + Assert.That(struc.Emotions, Is.EqualTo(255), nameof(struc.Emotions)); + CollectionAssert.AreEqual(struc.Images, Array.CreateInstance(typeof(byte), 9), nameof(struc.Images)); + Assert.That(struc.Intelligence, Is.EqualTo(7), nameof(struc.Intelligence)); + Assert.That(struc.Aggressiveness, Is.EqualTo(5), nameof(struc.Aggressiveness)); + Assert.That(struc.Competitiveness, Is.EqualTo(6), nameof(struc.Competitiveness)); + Assert.That(struc.var_37, Is.EqualTo(0), nameof(struc.var_37)); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("CURRDOLL.DAT")] + public void CurrencyObject(string objectName) + { + void assertFunc(ILocoObject obj, CurrencyObject struc) => Assert.Multiple(() => + { + Assert.That(struc.ObjectIcon, Is.EqualTo(0), nameof(struc.ObjectIcon)); + Assert.That(struc.Separator, Is.EqualTo(0), nameof(struc.Separator)); + Assert.That(struc.Factor, Is.EqualTo(1), nameof(struc.Factor)); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("SHIPST1.DAT")] + public void DockObject(string objectName) + { + void assertFunc(ILocoObject obj, DockObject struc) => Assert.Multiple(() => + { + Assert.That(struc.BuildCostFactor, Is.EqualTo(38), nameof(struc.BuildCostFactor)); + Assert.That(struc.SellCostFactor, Is.EqualTo(-35), nameof(struc.SellCostFactor)); + Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex)); + Assert.That(struc.var_07, Is.EqualTo(0), nameof(struc.var_07)); + Assert.That(struc.UnkImage, Is.EqualTo(0), nameof(struc.UnkImage)); + Assert.That(struc.Flags, Is.EqualTo(DockObjectFlags.None), nameof(struc.Flags)); + Assert.That(struc.NumAux01, Is.EqualTo(2), nameof(struc.NumAux01)); + Assert.That(struc.NumAux02Ent, Is.EqualTo(1), nameof(struc.NumAux02Ent)); + + //Assert.That(struc.var_14, Is.EqualTo(1), nameof(struc.var_14)); + //Assert.That(struc.var_14, Is.EqualTo(1), nameof(struc.var_18)); + //Assert.That(struc.var_1C[0], Is.EqualTo(1), nameof(struc.var_1C[0])); + + Assert.That(struc.DesignedYear, Is.EqualTo(0), nameof(struc.DesignedYear)); + Assert.That(struc.ObsoleteYear, Is.EqualTo(65535), nameof(struc.ObsoleteYear)); + Assert.That(struc.BoatPosition, Is.EqualTo(new Pos2(48, 0)), nameof(struc.BoatPosition)); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("HS1.DAT")] + public void HillShapesObject(string objectName) + { + void assertFunc(ILocoObject obj, HillShapesObject struc) => Assert.Multiple(() => + { + Assert.That(struc.HillHeightMapCount, Is.EqualTo(2), nameof(struc.HillHeightMapCount)); + Assert.That(struc.MountainHeightMapCount, Is.EqualTo(2), nameof(struc.MountainHeightMapCount)); + //Assert.That(struc.var_08, Is.EqualTo(0), nameof(struc.var_08)); + CollectionAssert.AreEqual(struc.pad_0C, Array.CreateInstance(typeof(byte), 2), nameof(struc.pad_0C)); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("BREWERY.DAT")] + public void IndustryObject(string objectName) + { + void assertFunc(ILocoObject obj, IndustryObject struc) => Assert.Multiple(() => + { + Assert.That(struc.Name, Is.EqualTo(0), nameof(struc.Name)); + + // AnimationSequences + Assert.That(struc.AvailableColours, Is.EqualTo(4), nameof(struc.AvailableColours)); + // Buildings + Assert.That(struc.BuildingSizeFlags, Is.EqualTo(1), nameof(struc.BuildingSizeFlags)); + Assert.That(struc._BuildingWall, Is.EqualTo(0), nameof(struc._BuildingWall)); + Assert.That(struc._BuildingWallEntrance, Is.EqualTo(0), nameof(struc._BuildingWallEntrance)); + // BuildingVariationAnimations + // BuildingVariationHeights + // BuildingVariationParts + Assert.That(struc.ClearCostFactor, Is.EqualTo(240), nameof(struc.ClearCostFactor)); + Assert.That(struc.CostFactor, Is.EqualTo(320), nameof(struc.CostFactor)); + Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex)); + Assert.That(struc.DesignedYear, Is.EqualTo(0), nameof(struc.DesignedYear)); + Assert.That(struc.Flags, Is.EqualTo(IndustryObjectFlags.BuiltOnLowGround | IndustryObjectFlags.BuiltNearWater | IndustryObjectFlags.BuiltNearTown | IndustryObjectFlags.CanBeFoundedByPlayer | IndustryObjectFlags.BuiltNearDesert), nameof(struc.Flags)); + //Assert.That(struc.InitialProductionRate, Is.EqualTo(1), nameof(struc.InitialProductionRate)); + Assert.That(struc.MaxNumBuildings, Is.EqualTo(8), nameof(struc.MaxNumBuildings)); + Assert.That(struc.MinNumBuildings, Is.EqualTo(4), nameof(struc.MinNumBuildings)); + Assert.That(struc.NumBuildingAnimations, Is.EqualTo(4), nameof(struc.NumBuildingAnimations)); + Assert.That(struc.NumBuildingVariations, Is.EqualTo(2), nameof(struc.NumBuildingVariations)); + Assert.That(struc.ObsoleteYear, Is.EqualTo(65535), nameof(struc.ObsoleteYear)); + Assert.That(struc.pad_E3, Is.EqualTo(4), nameof(struc.pad_E3)); + // ProducedCargo + // RequiredCargo + Assert.That(struc.ScaffoldingColour, Is.EqualTo(Colour.grey), nameof(struc.ScaffoldingColour)); + Assert.That(struc.ScaffoldingSegmentType, Is.EqualTo(0), nameof(struc.ScaffoldingSegmentType)); + Assert.That(struc.TotalOfTypeInScenario, Is.EqualTo(2), nameof(struc.TotalOfTypeInScenario)); + Assert.That(struc.var_E8, Is.EqualTo(1), nameof(struc.var_E8)); + Assert.That(struc.var_E9, Is.EqualTo(1), nameof(struc.var_E9)); + Assert.That(struc.var_EA, Is.EqualTo(0), nameof(struc.var_EA)); + Assert.That(struc.var_EB, Is.EqualTo(0), nameof(struc.var_EB)); + Assert.That(struc.var_EC, Is.EqualTo(0), nameof(struc.var_EC)); + Assert.That(struc.var_F3, Is.EqualTo(1), nameof(struc.var_F3)); + // WallTypes + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("INTERDEF.DAT")] + public void InterfaceSkinObject(string objectName) + { + void assertFunc(ILocoObject obj, InterfaceSkinObject struc) => Assert.Multiple(() => + { + Assert.That(struc.Name, Is.EqualTo(0), nameof(struc.Name)); + Assert.That(struc.Image, Is.EqualTo(0), nameof(struc.Image)); + + Assert.That(struc.Colour_06, Is.EqualTo(Colour.orange), nameof(struc.Colour_06)); + Assert.That(struc.Colour_07, Is.EqualTo(Colour.darkOrange), nameof(struc.Colour_07)); + Assert.That(struc.TooltipColour, Is.EqualTo(Colour.orange), nameof(struc.TooltipColour)); + Assert.That(struc.ErrorColour, Is.EqualTo(Colour.darkRed), nameof(struc.ErrorColour)); + Assert.That(struc.Colour_0A, Is.EqualTo(Colour.grey), nameof(struc.Colour_0A)); + Assert.That(struc.Colour_0B, Is.EqualTo(Colour.black), nameof(struc.Colour_0B)); + Assert.That(struc.Colour_0C, Is.EqualTo(Colour.grey), nameof(struc.Colour_0C)); + Assert.That(struc.Colour_0D, Is.EqualTo(Colour.brown), nameof(struc.Colour_0D)); + Assert.That(struc.Colour_0E, Is.EqualTo(Colour.brown), nameof(struc.Colour_0E)); + Assert.That(struc.Colour_0F, Is.EqualTo(Colour.mutedSeaGreen), nameof(struc.Colour_0F)); + Assert.That(struc.Colour_10, Is.EqualTo(Colour.blue), nameof(struc.Colour_10)); + Assert.That(struc.Colour_11, Is.EqualTo(Colour.black), nameof(struc.Colour_11)); + Assert.That(struc.Colour_12, Is.EqualTo(Colour.blue), nameof(struc.Colour_12)); + Assert.That(struc.Colour_13, Is.EqualTo(Colour.mutedSeaGreen), nameof(struc.Colour_13)); + Assert.That(struc.Colour_14, Is.EqualTo(Colour.brown), nameof(struc.Colour_14)); + Assert.That(struc.Colour_15, Is.EqualTo(Colour.grey), nameof(struc.Colour_15)); + Assert.That(struc.Colour_16, Is.EqualTo(Colour.grey), nameof(struc.Colour_16)); + Assert.That(struc.Colour_17, Is.EqualTo(Colour.grey), nameof(struc.Colour_17)); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("GRASS1.DAT")] + public void LandObject(string objectName) + { + void assertFunc(ILocoObject obj, LandObject struc) => Assert.Multiple(() => + { + Assert.That(struc.CostIndex, Is.EqualTo(2), nameof(struc.CostIndex)); + Assert.That(struc.var_03, Is.EqualTo(5), nameof(struc.var_03)); + Assert.That(struc.var_04, Is.EqualTo(1), nameof(struc.var_04)); + Assert.That(struc.Flags, Is.EqualTo(LandObjectFlags.unk0), nameof(struc.Flags)); + Assert.That(struc.CliffEdgeHeader1, Is.EqualTo(0), nameof(struc.CliffEdgeHeader1)); + Assert.That(struc.CliffEdgeHeader2, Is.EqualTo(0), nameof(struc.CliffEdgeHeader2)); + Assert.That(struc.CostFactor, Is.EqualTo(20), nameof(struc.CostFactor)); + Assert.That(struc.pad_09, Is.EqualTo(0), nameof(struc.pad_09)); + Assert.That(struc.var_0E, Is.EqualTo(0), nameof(struc.var_0E)); + Assert.That(struc.CliffEdgeImage, Is.EqualTo(0), nameof(struc.CliffEdgeImage)); + Assert.That(struc.MapPixelImage, Is.EqualTo(0), nameof(struc.MapPixelImage)); + Assert.That(struc.pad_1A, Is.EqualTo(0), nameof(struc.pad_1A)); + Assert.That(struc.NumVariations, Is.EqualTo(3), nameof(struc.NumVariations)); + Assert.That(struc.VariationLikelihood, Is.EqualTo(10), nameof(struc.VariationLikelihood)); + Assert.That(struc.pad_1D, Is.EqualTo(0), nameof(struc.pad_1D)); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("LCROSS1.DAT")] + public void LevelCrossingObject(string objectName) + { + void assertFunc(ILocoObject obj, LevelCrossingObject struc) => Assert.Multiple(() => + { + Assert.That(struc.CostFactor, Is.EqualTo(30), nameof(struc.CostFactor)); + Assert.That(struc.SellCostFactor, Is.EqualTo(-10), nameof(struc.SellCostFactor)); + Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex)); + + Assert.That(struc.AnimationSpeed, Is.EqualTo(3), nameof(struc.AnimationSpeed)); + Assert.That(struc.ClosingFrames, Is.EqualTo(4), nameof(struc.ClosingFrames)); + Assert.That(struc.ClosedFrames, Is.EqualTo(11), nameof(struc.ClosedFrames)); + + Assert.That(struc.pad_0A[0], Is.EqualTo(3), nameof(struc.pad_0A) + "[0]"); + Assert.That(struc.pad_0A[1], Is.EqualTo(0), nameof(struc.pad_0A) + "[1]"); + + Assert.That(struc.DesignedYear, Is.EqualTo(1955), nameof(struc.DesignedYear)); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("REGUK.DAT")] + public void RegionObject(string objectName) + { + void assertFunc(ILocoObject obj, RegionObject struc) => Assert.Multiple(() => + { + CollectionAssert.AreEqual(struc.pad_06, Array.CreateInstance(typeof(byte), 2), nameof(struc.pad_06)); + Assert.That(struc.RequiredObjectCount, Is.EqualTo(1), nameof(struc.RequiredObjectCount)); + //CollectionAssert.AreEqual(struc.requiredObjects, Array.CreateInstance(typeof(byte), 4), nameof(struc.requiredObjects)); + CollectionAssert.AreEqual(struc.pad_0D, Array.CreateInstance(typeof(byte), 5), nameof(struc.pad_0D)); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("ROADONE.DAT")] + public void RoadObject(string objectName) + { + void assertFunc(ILocoObject obj, RoadObject struc) => Assert.Multiple(() => + { + // Bridges + Assert.That(struc.BuildCostFactor, Is.EqualTo(22), nameof(struc.BuildCostFactor)); + // Compatible + Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex)); + Assert.That(struc.Flags, Is.EqualTo(RoadObjectFlags.unk_00 | RoadObjectFlags.unk_02 | RoadObjectFlags.unk_03 | RoadObjectFlags.unk_04 | RoadObjectFlags.IsRoad), nameof(struc.Flags)); + Assert.That(struc.MaxSpeed, Is.EqualTo(400), nameof(struc.MaxSpeed)); + // Mods + Assert.That(struc.NumBridges, Is.EqualTo(5), nameof(struc.NumBridges)); + Assert.That(struc.NumCompatible, Is.EqualTo(1), nameof(struc.NumCompatible)); + Assert.That(struc.NumMods, Is.EqualTo(0), nameof(struc.NumMods)); + Assert.That(struc.PaintStyle, Is.EqualTo(0), nameof(struc.PaintStyle)); + Assert.That(struc.RoadPieces, Is.EqualTo(RoadObjectPieceFlags.OneWay | RoadObjectPieceFlags.Track | RoadObjectPieceFlags.Slope | RoadObjectPieceFlags.SteepSlope | RoadObjectPieceFlags.Intersection | RoadObjectPieceFlags.Overtake), nameof(struc.RoadPieces)); + Assert.That(struc.SellCostFactor, Is.EqualTo(-20), nameof(struc.SellCostFactor)); + // Stations + Assert.That(struc.TargetTownSize, Is.EqualTo(2), nameof(struc.TargetTownSize)); + Assert.That(struc.TunnelCostFactor, Is.EqualTo(27), nameof(struc.TunnelCostFactor)); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("RDEXCAT1.DAT")] + public void RoadExtraObject(string objectName) + { + void assertFunc(ILocoObject obj, RoadExtraObject struc) => Assert.Multiple(() => + { + Assert.That(struc.BuildCostFactor, Is.EqualTo(4), nameof(struc.BuildCostFactor)); + Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex)); + Assert.That(struc.PaintStyle, Is.EqualTo(1), nameof(struc.PaintStyle)); + Assert.That(struc.RoadPieces, Is.EqualTo(127), nameof(struc.RoadPieces)); + Assert.That(struc.SellCostFactor, Is.EqualTo(-3), nameof(struc.SellCostFactor)); + Assert.That(struc.var_0E, Is.EqualTo(0), nameof(struc.var_0E)); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("RDSTAT1.DAT")] + public void RoadStationObject(string objectName) + { + void assertFunc(ILocoObject obj, RoadStationObject struc) => Assert.Multiple(() => + { + Assert.That(struc.BuildCostFactor, Is.EqualTo(24), nameof(struc.BuildCostFactor)); + // Cargo + Assert.That(struc._CargoOffsetBytes, Is.All.EqualTo(0), nameof(struc._CargoOffsetBytes)); + // Compatible + Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex)); + Assert.That(struc.DesignedYear, Is.EqualTo(0), nameof(struc.DesignedYear)); + Assert.That(struc.Flags, Is.EqualTo(RoadStationObjectFlags.Passenger | RoadStationObjectFlags.RoadEnd), nameof(struc.Flags)); + Assert.That(struc.NumCompatible, Is.EqualTo(0), nameof(struc.NumCompatible)); + Assert.That(struc.ObsoleteYear, Is.EqualTo(1945), nameof(struc.ObsoleteYear)); + Assert.That(struc.PaintStyle, Is.EqualTo(0), nameof(struc.PaintStyle)); + Assert.That(struc.RoadPieces, Is.EqualTo(0), nameof(struc.RoadPieces)); + Assert.That(struc.SellCostFactor, Is.EqualTo(-17), nameof(struc.SellCostFactor)); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("SCAFDEF.DAT")] + public void ScaffoldingObject(string objectName) + { + void assertFunc(ILocoObject obj, ScaffoldingObject struc) => Assert.Multiple(() => + { + Assert.That(struc.SegmentHeights[0], Is.EqualTo(16), nameof(struc.SegmentHeights) + "[0]"); + Assert.That(struc.SegmentHeights[1], Is.EqualTo(16), nameof(struc.SegmentHeights) + "[1]"); + Assert.That(struc.SegmentHeights[2], Is.EqualTo(32), nameof(struc.SegmentHeights) + "[2]"); + + Assert.That(struc.RoofHeights[0], Is.EqualTo(0), nameof(struc.RoofHeights) + "[0]"); + Assert.That(struc.RoofHeights[1], Is.EqualTo(0), nameof(struc.RoofHeights) + "[1]"); + Assert.That(struc.RoofHeights[2], Is.EqualTo(14), nameof(struc.RoofHeights) + "[2]"); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("STEX000.DAT")] + public void ScenarioTextObject(string objectName) + { + void assertFunc(ILocoObject obj, ScenarioTextObject struc) => Assert.Multiple(() => + { + Assert.That(struc.pad_04, Is.EqualTo(0), nameof(struc.pad_04)); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("SNOW.DAT")] + public void SnowObject(string objectName) + { + void assertFunc(ILocoObject obj, SnowObject struc) => Assert.Multiple(() => + { + Assert.That(struc.Name, Is.EqualTo(0)); + Assert.That(struc.Image, Is.EqualTo(0)); + + Assert.That(obj.StringTable.Table, Has.Count.EqualTo(1), nameof(obj.StringTable.Table)); + Assert.That(obj.G1Elements, Has.Count.EqualTo(139), nameof(obj.G1Elements)); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("SNDA1.DAT")] + public void SoundObject(string objectName) + { + void assertFunc(ILocoObject obj, SoundObject struc) => Assert.Multiple(() => + { + Assert.That(struc.Name, Is.EqualTo(0), nameof(struc.Name)); + Assert.That(struc.pad_07, Is.EqualTo(0), nameof(struc.pad_07)); + Assert.That(struc.var_06, Is.EqualTo(1), nameof(struc.var_06)); + Assert.That(struc.Volume, Is.EqualTo(0), nameof(struc.Volume)); + + Assert.That(struc.PcmData, Has.Length.EqualTo(119666), nameof(struc.PcmData.Length)); + + Assert.That(struc.SoundObjectData.Length, Is.EqualTo(119662), nameof(struc.SoundObjectData.Length)); + Assert.That(struc.SoundObjectData.Offset, Is.EqualTo(8), nameof(struc.SoundObjectData.Offset)); + Assert.That(struc.SoundObjectData.var_00, Is.EqualTo(1), nameof(struc.SoundObjectData.var_00)); + + Assert.That(struc.SoundObjectData.PcmHeader.AverageBytesPerSecond, Is.EqualTo(44100), nameof(struc.SoundObjectData.PcmHeader.AverageBytesPerSecond)); + Assert.That(struc.SoundObjectData.PcmHeader.BitsPerSample, Is.EqualTo(4096), nameof(struc.SoundObjectData.PcmHeader.BitsPerSample)); + Assert.That(struc.SoundObjectData.PcmHeader.BlockAlign, Is.EqualTo(512), nameof(struc.SoundObjectData.PcmHeader.BlockAlign)); + Assert.That(struc.SoundObjectData.PcmHeader.CBSize, Is.EqualTo(0), nameof(struc.SoundObjectData.PcmHeader.CBSize)); + Assert.That(struc.SoundObjectData.PcmHeader.NumberOfChannels, Is.EqualTo(1), nameof(struc.SoundObjectData.PcmHeader.NumberOfChannels)); + Assert.That(struc.SoundObjectData.PcmHeader.SampleRate, Is.EqualTo(22050), nameof(struc.SoundObjectData.PcmHeader.SampleRate)); + Assert.That(struc.SoundObjectData.PcmHeader.WaveFormatTag, Is.EqualTo(1), nameof(struc.SoundObjectData.PcmHeader.WaveFormatTag)); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("STEAM.DAT")] + public void SteamObject(string objectName) + { + void assertFunc(ILocoObject obj, SteamObject struc) => Assert.Multiple(() => + { + Assert.That(struc.Name, Is.EqualTo(0), nameof(struc.Name)); + + Assert.That(struc.Flags, Is.EqualTo(SteamObjectFlags.ApplyWind | SteamObjectFlags.DisperseOnCollision | SteamObjectFlags.unk2), nameof(struc.Flags)); + // FrameInfoType0 contents + // FrameInfoType1 contents + Assert.That(struc.FrameInfoType0, Has.Count.EqualTo(47), nameof(struc.FrameInfoType0)); + Assert.That(struc.FrameInfoType1, Has.Count.EqualTo(30), nameof(struc.FrameInfoType1)); + Assert.That(struc.NumImages, Is.EqualTo(57), nameof(struc.NumImages)); + Assert.That(struc.NumSoundEffects, Is.EqualTo(8), nameof(struc.NumSoundEffects)); + Assert.That(struc.NumStationaryTicks, Is.EqualTo(2), nameof(struc.NumStationaryTicks)); + + // these aren't currently calculated in this tool + //Assert.That(struc.SpriteWidth, Is.EqualTo(0), nameof(struc.SpriteWidth)); + //Assert.That(struc.SpriteHeightNegative, Is.EqualTo(0), nameof(struc.SpriteHeightNegative)); + //Assert.That(struc.SpriteHeightPositive, Is.EqualTo(0), nameof(struc.SpriteHeightPositive)); + + Assert.That(struc._TotalNumFramesType0, Is.EqualTo(0), nameof(struc._TotalNumFramesType0)); + Assert.That(struc._TotalNumFramesType1, Is.EqualTo(0), nameof(struc._TotalNumFramesType1)); + Assert.That(struc.var_0A, Is.EqualTo(0), nameof(struc.var_0A)); + // SoundEffects + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("SLIGHT1.DAT")] + public void StreetLightObject(string objectName) + { + void assertFunc(ILocoObject obj, StreetLightObject struc) => Assert.Multiple(() => + { + Assert.That(struc.Name, Is.EqualTo(0)); + + Assert.That(struc.DesignedYear[0], Is.EqualTo(1900), nameof(struc.DesignedYear) + "[0]"); + Assert.That(struc.DesignedYear[1], Is.EqualTo(1950), nameof(struc.DesignedYear) + "[1]"); + Assert.That(struc.DesignedYear[2], Is.EqualTo(1985), nameof(struc.DesignedYear) + "[2]"); + + Assert.That(obj.StringTable["Name"][LanguageId.english_uk], Is.EqualTo("Street Lights")); + Assert.That(obj.StringTable["Name"][LanguageId.english_us], Is.EqualTo("Street Lights")); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("ATOWNNAM.DAT")] + public void TownNamesObject(string objectName) + { + void assertFunc(ILocoObject obj, TownNamesObject struc) => Assert.Multiple(() => + { + Assert.That(struc.UnknownTownNameStructs[0].Count, Is.EqualTo(2), nameof(struc.UnknownTownNameStructs) + "[0] Count"); + Assert.That(struc.UnknownTownNameStructs[0].Fill, Is.EqualTo(30), nameof(struc.UnknownTownNameStructs) + "[0] Fill"); + Assert.That(struc.UnknownTownNameStructs[0].Offset, Is.EqualTo(93), nameof(struc.UnknownTownNameStructs) + "[0] Offset"); + + Assert.That(struc.UnknownTownNameStructs[1].Count, Is.EqualTo(94), nameof(struc.UnknownTownNameStructs) + "[1] Count"); + Assert.That(struc.UnknownTownNameStructs[1].Fill, Is.EqualTo(0), nameof(struc.UnknownTownNameStructs) + "[1] Fill"); + Assert.That(struc.UnknownTownNameStructs[1].Offset, Is.EqualTo(110), nameof(struc.UnknownTownNameStructs) + "[1] Offset"); + + Assert.That(struc.UnknownTownNameStructs[2].Count, Is.EqualTo(0), nameof(struc.UnknownTownNameStructs) + "[2] Count"); + Assert.That(struc.UnknownTownNameStructs[2].Fill, Is.EqualTo(0), nameof(struc.UnknownTownNameStructs) + "[2] Fill"); + Assert.That(struc.UnknownTownNameStructs[2].Offset, Is.EqualTo(0), nameof(struc.UnknownTownNameStructs) + "[2] Offset"); + + Assert.That(struc.UnknownTownNameStructs[3].Count, Is.EqualTo(0), nameof(struc.UnknownTownNameStructs) + "[3] Count"); + Assert.That(struc.UnknownTownNameStructs[3].Fill, Is.EqualTo(0), nameof(struc.UnknownTownNameStructs) + "[3] Fill"); + Assert.That(struc.UnknownTownNameStructs[3].Offset, Is.EqualTo(0), nameof(struc.UnknownTownNameStructs) + "[3] Offset"); + + Assert.That(struc.UnknownTownNameStructs[4].Count, Is.EqualTo(18), nameof(struc.UnknownTownNameStructs) + "[4] Count"); + Assert.That(struc.UnknownTownNameStructs[4].Fill, Is.EqualTo(0), nameof(struc.UnknownTownNameStructs) + "[4] Fill"); + Assert.That(struc.UnknownTownNameStructs[4].Offset, Is.EqualTo(923), nameof(struc.UnknownTownNameStructs) + "[4] Offset"); + + Assert.That(struc.UnknownTownNameStructs[5].Count, Is.EqualTo(6), nameof(struc.UnknownTownNameStructs) + "[5] Count"); + Assert.That(struc.UnknownTownNameStructs[5].Fill, Is.EqualTo(20), nameof(struc.UnknownTownNameStructs) + "[5] Fill"); + Assert.That(struc.UnknownTownNameStructs[5].Offset, Is.EqualTo(1071), nameof(struc.UnknownTownNameStructs) + "[5] Offset"); + + Assert.That(obj.StringTable["Name"][LanguageId.english_uk], Is.EqualTo("North-American style town names")); + Assert.That(obj.StringTable["Name"][LanguageId.english_us], Is.EqualTo("North-American style town names")); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("TRACKST.DAT")] + public void TrackObject(string objectName) + { + void assertFunc(ILocoObject obj, TrackObject struc) => Assert.Multiple(() => + { + // Bridges + Assert.That(struc.BuildCostFactor, Is.EqualTo(11), nameof(struc.BuildCostFactor)); + // Compatible + Assert.That(struc._CompatibleRoads, Is.EqualTo(0), nameof(struc._CompatibleRoads)); + Assert.That(struc._CompatibleTracks, Is.EqualTo(0), nameof(struc._CompatibleTracks)); + Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex)); + Assert.That(struc.CurveSpeed, Is.EqualTo(400), nameof(struc.CurveSpeed)); + Assert.That(struc.DisplayOffset, Is.EqualTo(3), nameof(struc.DisplayOffset)); + Assert.That(struc.Flags, Is.EqualTo(TrackObjectFlags.unk_00), nameof(struc.Flags)); + // Mods + Assert.That(struc.NumBridges, Is.EqualTo(5), nameof(struc.NumBridges)); + Assert.That(struc.NumCompatible, Is.EqualTo(7), nameof(struc.NumCompatible)); + Assert.That(struc.NumMods, Is.EqualTo(2), nameof(struc.NumMods)); + Assert.That(struc.NumSignals, Is.EqualTo(10), nameof(struc.NumSignals)); + Assert.That(struc.NumStations, Is.EqualTo(5), nameof(struc.NumStations)); + Assert.That(struc.SellCostFactor, Is.EqualTo(-10), nameof(struc.SellCostFactor)); + // Signals + // Stations + Assert.That(struc.StationTrackPieces, Is.EqualTo(525), nameof(struc.StationTrackPieces)); + Assert.That(struc.TrackPieces, Is.EqualTo(TrackObjectPieceFlags.Diagonal | TrackObjectPieceFlags.LargeCurve | TrackObjectPieceFlags.NormalCurve | TrackObjectPieceFlags.SmallCurve | TrackObjectPieceFlags.Slope | TrackObjectPieceFlags.SlopedCurve | TrackObjectPieceFlags.SBend | TrackObjectPieceFlags.Junction), nameof(struc.TrackPieces)); + Assert.That(struc.TunnelCostFactor, Is.EqualTo(24), nameof(struc.TunnelCostFactor)); + Assert.That(struc.var_06, Is.EqualTo(0), nameof(struc.var_06)); + + Assert.That(obj.StringTable.Table, Has.Count.EqualTo(1), nameof(obj.StringTable.Table)); + Assert.That(obj.G1Elements, Is.Not.Null); + Assert.That(obj.G1Elements, Has.Count.EqualTo(400), nameof(obj.G1Elements)); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("TREXCAT1.DAT")] + public void TrackExtraObject(string objectName) + { + void assertFunc(ILocoObject obj, TrackExtraObject struc) => Assert.Multiple(() => + { + Assert.That(struc.TrackPieces, Is.EqualTo(1023), nameof(struc.TrackPieces)); + Assert.That(struc.PaintStyle, Is.EqualTo(1), nameof(struc.PaintStyle)); + Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex)); + Assert.That(struc.BuildCostFactor, Is.EqualTo(2), nameof(struc.BuildCostFactor)); + Assert.That(struc.SellCostFactor, Is.EqualTo(-1), nameof(struc.SellCostFactor)); + Assert.That(struc.var_0E, Is.EqualTo(0), nameof(struc.var_0E)); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("SIGSUS.DAT")] + public void TrainSignalObject(string objectName) + { + void assertFunc(ILocoObject obj, TrainSignalObject struc) => Assert.Multiple(() => + { + Assert.That(struc.Name, Is.EqualTo(0)); + Assert.That(struc.Image, Is.EqualTo(0)); + + Assert.That(struc.AnimationSpeed, Is.EqualTo(1), nameof(struc.AnimationSpeed)); + Assert.That(struc.CostFactor, Is.EqualTo(4), nameof(struc.CostFactor)); + Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex)); + Assert.That(struc.DesignedYear, Is.EqualTo(0), nameof(struc.DesignedYear)); + Assert.That(struc.Flags, Is.EqualTo(TrainSignalObjectFlags.IsLeft), nameof(struc.Flags)); + // Mods + Assert.That(struc.NumCompatible, Is.EqualTo(0), nameof(struc.NumCompatible)); + Assert.That(struc.NumFrames, Is.EqualTo(7), nameof(struc.NumFrames)); + Assert.That(struc.ObsoleteYear, Is.EqualTo(1955), nameof(struc.ObsoleteYear)); + Assert.That(struc.SellCostFactor, Is.EqualTo(-3), nameof(struc.SellCostFactor)); + Assert.That(struc.var_0B, Is.EqualTo(0), nameof(struc.var_0B)); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("TRSTAT1.DAT")] + public void TrainStationObject(string objectName) + { + void assertFunc(ILocoObject obj, TrainStationObject struc) => Assert.Multiple(() => + { + Assert.That(struc.BuildCostFactor, Is.EqualTo(7), nameof(struc.BuildCostFactor)); + // CargoOffsetBytes + // Compatible + Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex)); + Assert.That(struc.DesignedYear, Is.EqualTo(1960), nameof(struc.DesignedYear)); + Assert.That(struc.Flags, Is.EqualTo(TrainStationObjectFlags.None), nameof(struc.Flags)); + // ManualPower + Assert.That(struc.NumCompatible, Is.EqualTo(0), nameof(struc.NumCompatible)); + Assert.That(struc.ObsoleteYear, Is.EqualTo(65535), nameof(struc.ObsoleteYear)); + Assert.That(struc.PaintStyle, Is.EqualTo(0), nameof(struc.PaintStyle)); + Assert.That(struc.SellCostFactor, Is.EqualTo(-7), nameof(struc.SellCostFactor)); + Assert.That(struc.TrackPieces, Is.EqualTo(0), nameof(struc.TrackPieces)); + Assert.That(struc.var_03, Is.EqualTo(0), nameof(struc.var_03)); + Assert.That(struc.var_0B, Is.EqualTo(2), nameof(struc.var_0B)); + Assert.That(struc.var_0D, Is.EqualTo(0), nameof(struc.var_0D)); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("BEECH.DAT")] + public void TreeObject(string objectName) + { + void assertFunc(ILocoObject obj, TreeObject struc) => Assert.Multiple(() => + { + //Assert.That(struc.var_02, Is.EqualTo(40), nameof(struc.var_02)); + Assert.That(struc.Height, Is.EqualTo(131), nameof(struc.Height)); + Assert.That(struc.var_04, Is.EqualTo(27), nameof(struc.var_04)); + Assert.That(struc.var_05, Is.EqualTo(83), nameof(struc.var_05)); + Assert.That(struc.NumRotations, Is.EqualTo(1), nameof(struc.NumRotations)); + Assert.That(struc.Growth, Is.EqualTo(4), nameof(struc.Growth)); + Assert.That(struc.Flags, Is.EqualTo(TreeObjectFlags.HighAltitude | TreeObjectFlags.RequiresWater | TreeObjectFlags.HasShadow), nameof(struc.Flags)); + CollectionAssert.AreEqual(struc.Sprites, Array.CreateInstance(typeof(byte), 6), nameof(struc.Sprites)); + CollectionAssert.AreEqual(struc.SnowSprites, Array.CreateInstance(typeof(byte), 6), nameof(struc.SnowSprites)); + Assert.That(struc.ShadowImageOffset, Is.EqualTo(0), nameof(struc.ShadowImageOffset)); + Assert.That(struc.var_3C, Is.EqualTo(15), nameof(struc.var_3C)); + Assert.That(struc.SeasonState, Is.EqualTo(3), nameof(struc.SeasonState)); + Assert.That(struc.var_3E, Is.EqualTo(2), nameof(struc.var_3E)); + Assert.That(struc.CostIndex, Is.EqualTo(3), nameof(struc.CostIndex)); + Assert.That(struc.BuildCostFactor, Is.EqualTo(8), nameof(struc.BuildCostFactor)); + Assert.That(struc.ClearCostFactor, Is.EqualTo(4), nameof(struc.ClearCostFactor)); + Assert.That(struc.Colours, Is.EqualTo(0), nameof(struc.Colours)); + Assert.That(struc.Rating, Is.EqualTo(10), nameof(struc.Rating)); + Assert.That(struc.DemolishRatingReduction, Is.EqualTo(-15), nameof(struc.DemolishRatingReduction)); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("TUNNEL1.DAT")] + public void TunnelObject(string objectName) + { + void assertFunc(ILocoObject obj, TunnelObject struc) => Assert.Multiple(() => + { + Assert.That(struc.Name, Is.EqualTo(0)); + Assert.That(struc.Image, Is.EqualTo(0)); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("707.DAT")] + public void VehicleAircraftObject(string objectName) + { + void assertFunc(ILocoObject obj, VehicleObject struc) => Assert.Multiple(() => + { + //var s5header = obj.S5Header; + //Assert.That(s5header.Flags, Is.EqualTo(283680407), nameof(s5header.Flags)); + //Assert.That(s5header.Name, Is.EqualTo("707 "), nameof(s5header.Name)); + //Assert.That(s5header.Checksum, Is.EqualTo(1331114877), nameof(s5header.Checksum)); + //Assert.That(s5header.ObjectType, Is.EqualTo(ObjectType.Vehicle), nameof(s5header.ObjectType)); + + //var objHeader = obj.ObjectHeader; + //Assert.That(objHeader.Encoding, Is.EqualTo(SawyerEncoding.RunLengthSingle), nameof(objHeader.Encoding)); + //Assert.That(objHeader.DataLength, Is.EqualTo(159566), nameof(objHeader.DataLength)); + + Assert.That(struc.Mode, Is.EqualTo(TransportMode.Air), nameof(struc.Mode)); + Assert.That(struc.Type, Is.EqualTo(VehicleType.Aircraft), nameof(struc.Type)); + Assert.That(struc.var_04, Is.EqualTo(1), nameof(struc.var_04)); + // Assert.That(struc.TrackType, Is.EqualTo(0xFF), nameof(struc.TrackType)); // is changed after load from 0 to 255 + Assert.That(struc.NumTrackExtras, Is.EqualTo(0), nameof(struc.NumTrackExtras)); + Assert.That(struc.CostIndex, Is.EqualTo(8), nameof(struc.CostIndex)); + Assert.That(struc.CostFactor, Is.EqualTo(345), nameof(struc.CostFactor)); + Assert.That(struc.Reliability, Is.EqualTo(88), nameof(struc.Reliability)); + Assert.That(struc.RunCostIndex, Is.EqualTo(4), nameof(struc.RunCostIndex)); + Assert.That(struc.RunCostFactor, Is.EqualTo(55), nameof(struc.RunCostFactor)); + Assert.That(struc.ColourType, Is.EqualTo(9), nameof(struc.ColourType)); + Assert.That(struc.NumCompatibleVehicles, Is.EqualTo(0), nameof(struc.NumCompatibleVehicles)); + //CollectionAssert.AreEqual(Enumerable.Repeat(0, 8).ToArray(), struc.CompatibleVehicles, nameof(struc.CompatibleVehicles)); + //CollectionAssert.AreEqual(Enumerable.Repeat(0, 4).ToArray(), struc.RequiredTrackExtras, nameof(struc.RequiredTrackExtras)); + //Assert.That(struc.var_24, Is.EqualTo(0), nameof(struc.var_24)); + //Assert.That(struc.BodySprites, Is.EqualTo(0), nameof(struc.BodySprites)); + //Assert.That(struc.BogieSprites, Is.EqualTo(1), nameof(struc.BogieSprites)); + Assert.That(struc.Power, Is.EqualTo(3000), nameof(struc.Power)); + Assert.That(struc.Speed, Is.EqualTo(604), nameof(struc.Speed)); + Assert.That(struc.RackSpeed, Is.EqualTo(120), nameof(struc.RackSpeed)); + Assert.That(struc.Weight, Is.EqualTo(141), nameof(struc.Weight)); + Assert.That(struc.Flags, Is.EqualTo((VehicleObjectFlags)16384), nameof(struc.Flags)); + // CollectionAssert.AreEqual(struc.MaxCargo, Enumerable.Repeat(0, 2).ToArray(), nameof(struc.MaxCargo)); // this is changed after load from 0 to 24 + //CollectionAssert.AreEqual(Enumerable.Repeat(0, 2).ToArray(), struc.CompatibleCargoCategories, nameof(struc.CompatibleCargoCategories)); + //CollectionAssert.AreEqual(Enumerable.Repeat(0, 32).ToArray(), struc.CargoTypeSpriteOffsets, nameof(struc.CargoTypeSpriteOffsets)); + Assert.That(struc.NumSimultaneousCargoTypes, Is.EqualTo(1), nameof(struc.NumSimultaneousCargoTypes)); + Assert.That(struc.Animation[0].ObjectId, Is.EqualTo(0), nameof(struc.Animation)); + Assert.That(struc.Animation[0].Height, Is.EqualTo(24), nameof(struc.Animation)); + Assert.That(struc.Animation[0].Type, Is.EqualTo(SimpleAnimationType.None), nameof(struc.Animation)); + Assert.That(struc.Animation[1].ObjectId, Is.EqualTo(0), nameof(struc.Animation)); + Assert.That(struc.Animation[1].Height, Is.EqualTo(0), nameof(struc.Animation)); + Assert.That(struc.Animation[1].Type, Is.EqualTo(SimpleAnimationType.None), nameof(struc.Animation)); + Assert.That(struc.var_113, Is.EqualTo(0), nameof(struc.var_113)); + Assert.That(struc.Designed, Is.EqualTo(1957), nameof(struc.Designed)); + Assert.That(struc.Obsolete, Is.EqualTo(1987), nameof(struc.Obsolete)); + Assert.That(struc.RackRailType, Is.EqualTo(0), nameof(struc.RackRailType)); + //Assert.That(struc.DrivingSoundType, Is.EqualTo(DrivingSoundType.Engine1), nameof(struc.DrivingSoundType)); + //Assert.That(struc.Sound, Is.EqualTo(0), nameof(struc.Sound)); + //Assert.That(struc.pad_135, Is.EqualTo(0), nameof(struc.pad_135)); + Assert.That(struc.NumStartSounds, Is.EqualTo(2), nameof(struc.NumStartSounds)); + + Assert.That(struc.StartSounds[0].Name, Is.EqualTo("SNDTD1 "), nameof(struc.StartSounds) + "[0]Name"); + Assert.That(struc.StartSounds[0].Checksum, Is.EqualTo(0), nameof(struc.StartSounds) + "[0]Checksum"); + Assert.That(struc.StartSounds[0].Flags, Is.EqualTo(1), nameof(struc.StartSounds) + "[0]Flags"); + Assert.That(struc.StartSounds[0].SourceGame, Is.EqualTo(SourceGame.Custom), nameof(struc.StartSounds) + "[0]Checksum"); + Assert.That(struc.StartSounds[0].ObjectType, Is.EqualTo(ObjectType.Sound), nameof(struc.StartSounds) + "[0]Flags"); + + Assert.That(struc.StartSounds[1].Name, Is.EqualTo("SNDTD2 "), nameof(struc.StartSounds) + "[1]Name"); + Assert.That(struc.StartSounds[1].Checksum, Is.EqualTo(0), nameof(struc.StartSounds) + "[1]Checksum"); + Assert.That(struc.StartSounds[1].Flags, Is.EqualTo(1), nameof(struc.StartSounds) + "[1]Flags"); + Assert.That(struc.StartSounds[1].SourceGame, Is.EqualTo(SourceGame.Custom), nameof(struc.StartSounds) + "[1]Checksum"); + Assert.That(struc.StartSounds[1].ObjectType, Is.EqualTo(ObjectType.Sound), nameof(struc.StartSounds) + "[1]Flags"); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("FENCE1.DAT")] + public void WallObject(string objectName) + { + void assertFunc(ILocoObject obj, WallObject struc) => Assert.Multiple(() => + { + Assert.That(struc.var_06, Is.EqualTo(15), nameof(struc.var_06)); + Assert.That(struc.Flags, Is.EqualTo(WallObjectFlags.None), nameof(struc.Flags)); + Assert.That(struc.Height, Is.EqualTo(2), nameof(struc.Height)); + Assert.That(struc.var_09, Is.EqualTo(8), nameof(struc.var_09)); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + + [TestCase("WATER1.DAT")] + public void WaterObject(string objectName) + { + void assertFunc(ILocoObject obj, WaterObject struc) => Assert.Multiple(() => + { + Assert.That(struc.CostIndex, Is.EqualTo(2), nameof(struc.CostIndex)); + Assert.That(struc.var_03, Is.EqualTo(0), nameof(struc.var_03)); + Assert.That(struc.CostFactor, Is.EqualTo(51), nameof(struc.CostFactor)); + Assert.That(struc.var_05, Is.EqualTo(0), nameof(struc.var_05)); + //Assert.That(struc.var_0A, Is.EqualTo(0), nameof(struc.var_0A)); + }); + LoadSaveGenericTest(objectName, assertFunc); + } + } +} diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj new file mode 100644 index 00000000..7da61d6b --- /dev/null +++ b/Tests/Tests.csproj @@ -0,0 +1,25 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + diff --git a/dat-object-layout.md b/dat-object-layout.md new file mode 100644 index 00000000..2ca64861 --- /dev/null +++ b/dat-object-layout.md @@ -0,0 +1,58 @@ +# Dat Object Layout + +`=================================================================` + +``` +|-File-------------------------------| +|-S5Header-|-DatHeader--|-ObjectData-| +``` + +`=================================================================` +`=================================================================` + +``` +|-S5Header----------------| +|-Flags-|-Name-|-Checksum-| + +|-DatHeader-------------| +|-Encoding-|-Datalength-| + +|-ObjectData-----------------------------------------| +|-Object-|-StringTable-|-VariableData-|-GraphicsData-| +``` + +`=================================================================` +`=================================================================` + +``` +|-Object-| +-- + +|-StringTable-| +|-String{n}---| + +|-VariableData-| +-- + +|-GraphicsData------------------------------| +|-G1Header-|-G1Element32{n}-|-ImageBytes{n}-| +``` + +`=================================================================` +`=================================================================` + +``` +|-String-----------------| +|-Language-|-StringBytes-| + +|-G1Header---------------| +|-NumEntries-|-TotalSize-| + +|-G1Element32------------------------------------------------------| +|-Offset-|-Width-|-Height-|-xOffset-|-yOffset-|-Flags-|-ZoomOffset-| + +|-ImageBytes-| +-- offset by G1Element32.Offset +``` + +`=================================================================` \ No newline at end of file diff --git a/OpenLocoToolGui/loco_icon.ico b/loco_icon.ico similarity index 100% rename from OpenLocoToolGui/loco_icon.ico rename to loco_icon.ico diff --git a/version.txt b/version.txt new file mode 100644 index 00000000..afaf360d --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +1.0.0 \ No newline at end of file