diff --git a/.editorconfig b/.editorconfig index 40d50bd70..0f6ad48a5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -43,31 +43,31 @@ dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case 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.interface.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.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 = +dotnet_naming_symbols.non_field_members.required_modifiers = # Naming styles 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.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = dotnet_naming_style.begins_with_i.capitalization = pascal_case -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.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case -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.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case dotnet_style_operator_placement_when_wrapping = beginning_of_line tab_width = 4 @@ -81,7 +81,97 @@ dotnet_style_prefer_auto_properties = true:silent [*] # Microsoft .NET properties +csharp_new_line_before_members_in_object_initializers = false +csharp_preferred_modifier_order = public, private, protected, internal, file, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:suggestion +csharp_prefer_braces = false:suggestion csharp_preserve_single_line_blocks = true +csharp_style_expression_bodied_accessors = true:suggestion +csharp_style_expression_bodied_constructors = true:none +csharp_style_expression_bodied_methods = true:suggestion +csharp_style_expression_bodied_properties = true:suggestion +csharp_style_namespace_declarations = block_scoped:none +csharp_style_prefer_utf8_string_literals = true:suggestion +csharp_style_var_for_built_in_types = true:suggestion +dotnet_naming_rule.constants_rule.import_to_resharper = as_predefined +dotnet_naming_rule.constants_rule.severity = suggestion +dotnet_naming_rule.constants_rule.style = pascal_case +dotnet_naming_rule.constants_rule.symbols = constants_symbols +dotnet_naming_rule.interface_should_be_begins_with_i_rule.import_to_resharper = True +dotnet_naming_rule.interface_should_be_begins_with_i_rule.resharper_description = interface_should_be_begins_with_i +dotnet_naming_rule.interface_should_be_begins_with_i_rule.resharper_guid = 336fd165-eb04-40c2-ae13-5363789422ca +dotnet_naming_rule.interface_should_be_begins_with_i_rule.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i_rule.style = begins_with_i +dotnet_naming_rule.interface_should_be_begins_with_i_rule.symbols = interface_should_be_begins_with_i_symbols +dotnet_naming_rule.non_field_members_should_be_pascal_case_rule.import_to_resharper = True +dotnet_naming_rule.non_field_members_should_be_pascal_case_rule.resharper_description = non_field_members_should_be_pascal_case +dotnet_naming_rule.non_field_members_should_be_pascal_case_rule.resharper_guid = bfe4a45b-02bb-4987-9b46-2c7a34bb09e8 +dotnet_naming_rule.non_field_members_should_be_pascal_case_rule.resharper_style = AaBb, aaBb +dotnet_naming_rule.non_field_members_should_be_pascal_case_rule.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case_rule.style = pascal_case +dotnet_naming_rule.non_field_members_should_be_pascal_case_rule.symbols = non_field_members_should_be_pascal_case_symbols +dotnet_naming_rule.private_constants_rule.import_to_resharper = as_predefined +dotnet_naming_rule.private_constants_rule.severity = suggestion +dotnet_naming_rule.private_constants_rule.style = pascal_case +dotnet_naming_rule.private_constants_rule.symbols = private_constants_symbols +dotnet_naming_rule.private_static_readonly_rule.import_to_resharper = as_predefined +dotnet_naming_rule.private_static_readonly_rule.resharper_style = AaBb, _ + aaBb +dotnet_naming_rule.private_static_readonly_rule.severity = suggestion +dotnet_naming_rule.private_static_readonly_rule.style = pascal_case +dotnet_naming_rule.private_static_readonly_rule.symbols = private_static_readonly_symbols +dotnet_naming_rule.public_fields_rule.import_to_resharper = as_predefined +dotnet_naming_rule.public_fields_rule.resharper_style = AaBb, aaBb +dotnet_naming_rule.public_fields_rule.severity = suggestion +dotnet_naming_rule.public_fields_rule.style = pascal_case +dotnet_naming_rule.public_fields_rule.symbols = public_fields_symbols +dotnet_naming_rule.static_readonly_rule.import_to_resharper = as_predefined +dotnet_naming_rule.static_readonly_rule.severity = suggestion +dotnet_naming_rule.static_readonly_rule.style = pascal_case +dotnet_naming_rule.static_readonly_rule.symbols = static_readonly_symbols +dotnet_naming_rule.types_should_be_pascal_case_rule.import_to_resharper = True +dotnet_naming_rule.types_should_be_pascal_case_rule.resharper_description = types_should_be_pascal_case +dotnet_naming_rule.types_should_be_pascal_case_rule.resharper_guid = 46acfd3a-c646-4b13-9908-9d4a8e3bc8ac +dotnet_naming_rule.types_should_be_pascal_case_rule.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case_rule.style = pascal_case +dotnet_naming_rule.types_should_be_pascal_case_rule.symbols = types_should_be_pascal_case_symbols +dotnet_naming_style.i_upper_camel_case_style.capitalization = pascal_case +dotnet_naming_style.i_upper_camel_case_style.required_prefix = I +dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case +dotnet_naming_symbols.constants_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected +dotnet_naming_symbols.constants_symbols.applicable_kinds = field +dotnet_naming_symbols.constants_symbols.required_modifiers = const +dotnet_naming_symbols.interface_should_be_begins_with_i_symbols.applicable_accessibilities = local, public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface_should_be_begins_with_i_symbols.applicable_kinds = interface +dotnet_naming_symbols.interface_should_be_begins_with_i_symbols.resharper_applicable_kinds = interface +dotnet_naming_symbols.interface_should_be_begins_with_i_symbols.resharper_required_modifiers = any +dotnet_naming_symbols.non_field_members_should_be_pascal_case_symbols.applicable_accessibilities = local, public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members_should_be_pascal_case_symbols.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members_should_be_pascal_case_symbols.resharper_applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members_should_be_pascal_case_symbols.resharper_required_modifiers = any +dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field +dotnet_naming_symbols.private_constants_symbols.required_modifiers = const +dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field +dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static, readonly +dotnet_naming_symbols.public_fields_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected +dotnet_naming_symbols.public_fields_symbols.applicable_kinds = field +dotnet_naming_symbols.static_readonly_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected +dotnet_naming_symbols.static_readonly_symbols.applicable_kinds = field +dotnet_naming_symbols.static_readonly_symbols.required_modifiers = static, readonly +dotnet_naming_symbols.types_should_be_pascal_case_symbols.applicable_accessibilities = local, public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types_should_be_pascal_case_symbols.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types_should_be_pascal_case_symbols.resharper_applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types_should_be_pascal_case_symbols.resharper_required_modifiers = any +dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none +dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion +dotnet_style_qualification_for_event = false:suggestion +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion # ReSharper properties resharper_align_first_arg_by_paren = false @@ -97,6 +187,7 @@ resharper_align_multline_type_parameter_constrains = true resharper_align_multline_type_parameter_list = true resharper_align_tuple_components = true resharper_allow_comment_after_lbrace = true +resharper_blank_lines_after_block_statements = 0 resharper_blank_lines_around_single_line_type = 0 resharper_constructor_or_destructor_body = expression_body resharper_csharp_align_first_arg_by_paren = false @@ -106,11 +197,13 @@ resharper_csharp_empty_block_style = together resharper_csharp_indent_invocation_pars = outside_and_inside resharper_csharp_indent_method_decl_pars = outside_and_inside resharper_csharp_indent_statement_pars = outside +resharper_csharp_naming_rule.other = AaBb, aaBb resharper_csharp_stick_comment = false resharper_csharp_wrap_arguments_style = chop_if_long resharper_csharp_wrap_before_binary_opsign = true resharper_csharp_wrap_lines = false resharper_csharp_wrap_parameters_style = chop_if_long +resharper_enforce_line_ending_style = true resharper_indent_anonymous_method_block = false resharper_indent_nested_fixed_stmt = true resharper_indent_nested_foreach_stmt = true @@ -131,11 +224,39 @@ resharper_nested_ternary_style = expanded resharper_outdent_statement_labels = true resharper_place_linq_into_on_new_line = false resharper_place_simple_case_statement_on_same_line = true +resharper_show_autodetect_configure_formatting_tip = false +resharper_use_indent_from_vs = false resharper_wrap_chained_binary_expressions = chop_if_long resharper_wrap_chained_binary_patterns = chop_if_long -csharp_new_line_before_else = true +csharp_new_line_before_else = false dotnet_style_object_initializer = true:suggestion dotnet_style_collection_initializer = true:suggestion dotnet_style_prefer_simplified_boolean_expressions = true:suggestion -csharp_new_line_before_catch = true +csharp_new_line_before_catch = false csharp_new_line_before_open_brace = none + +# Standard properties +end_of_line = crlf + +# ReSharper inspection severities +resharper_arrange_redundant_parentheses_highlighting = hint +resharper_arrange_this_qualifier_highlighting = hint +resharper_arrange_type_member_modifiers_highlighting = hint +resharper_arrange_type_modifiers_highlighting = hint +resharper_built_in_type_reference_style_for_member_access_highlighting = hint +resharper_built_in_type_reference_style_highlighting = hint +resharper_check_namespace_highlighting = none +resharper_inconsistent_naming_highlighting = suggestion +resharper_redundant_base_qualifier_highlighting = warning +resharper_suggest_var_or_type_built_in_types_highlighting = hint +resharper_suggest_var_or_type_elsewhere_highlighting = none +resharper_suggest_var_or_type_simple_types_highlighting = none +csharp_style_prefer_primary_constructors = true:suggestion + +[*.{appxmanifest,asax,ascx,aspx,axaml,axml,build,c,c++,cc,cginc,compute,config,cp,cpp,cs,cshtml,csproj,cu,cuh,cxx,dbml,discomap,dtd,fx,fxh,h,hh,hlsl,hlsli,hlslinc,hpp,htm,html,hxx,inc,inl,ino,ipp,ixx,jsproj,lsproj,master,mpp,mq4,mq5,mqh,njsproj,nuspec,paml,proj,props,razor,resw,resx,skin,StyleCop,targets,tasks,tpp,usf,ush,vb,vbproj,xaml,xamlx,xml,xoml,xsd}] +indent_style = space +indent_size = 4 +tab_width = 4 +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_explicit_tuple_names = true:suggestion diff --git a/.github/ISSUE_TEMPLATE/Broken-Feature.yml b/.github/ISSUE_TEMPLATE/Broken-Feature.yml new file mode 100644 index 000000000..50962dc47 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Broken-Feature.yml @@ -0,0 +1,106 @@ +name: Broken Feature +description: Create a report if one of Toybox's features doesn't work +title: "" +labels: ["bug", "broken feature"] +body: +- type: textarea + id: bugdescription + attributes: + label: Description of the bug + description: Please give a clear and consise description of the bug you are experiencing + validations: + required: true +- type: textarea + id: reproductionsteps + attributes: + label: Steps To Reproduce + description: Steps to reproduce the behavior. + placeholder: | + 1. Go to... + 2. Click on... + 3. Scroll down to... + 4. See error... + validations: + required: true +- type: textarea + id: expectedbehavior + attributes: + label: Expected Behavior + description: A concise description of what you expected to happen. + validations: + required: false +- type: textarea + id: logfiles + attributes: + label: Log Files + description: Find your log files from the FAQ in the wiki + placeholder: Drag and drop your log files here! + validations: + required: false +- type: textarea + id: savefile + attributes: + label: Save File + description: Zip your named save file (not an auto save!) + placeholder: Drag and drop your zipped save file here! + validations: + required: false +- type: textarea + id: screenshots + attributes: + label: Screenshots + description: Screenshot of the bug + placeholder: Drag and drop screenshots of the bug here! + validations: + required: false +- type: textarea + id: settings + attributes: + label: Settings + description: Please provide your settings file + placeholder: Drag and drop your settings.xml file here! + validations: + required: false +- type: input + id: toyboxversion + attributes: + label: Toybox Version + description: Please provide the version of Toybox you are using + placeholder: Example - 1.5.4c + validations: + required: true +- type: input + id: os + attributes: + label: Operating System + description: Please provide what operating system you use + placeholder: Examples - Windows, MacOS + validations: + required: false +- type: dropdown + id: whatgame + attributes: + label: What game are you using Toybox on? + options: + - Pathfinder Wrath of the Righteous + - Rogue Trader + validations: + required: true +- type: input + id: gameversion + attributes: + label: Game Version + description: Please provide what version of the game you are on + placeholder: Example - 2.1.4w + validations: + required: false +- type: textarea + id: extrainfo + attributes: + label: Anything else? + description: | + Links? References? Anything that will give us more context about the issue you are encountering! + + Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/Broken-Gameplay.yml b/.github/ISSUE_TEMPLATE/Broken-Gameplay.yml new file mode 100644 index 000000000..dbc5baf88 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Broken-Gameplay.yml @@ -0,0 +1,106 @@ +name: Broken Gameplay +description: Create a report if Toybox is breaking gameplay while enabled or a feature is turned on +title: "<title>" +labels: ["bug", "broken gameplay"] +body: +- type: textarea + id: bugdescription + attributes: + label: Description of the bug + description: Please give a clear and consise description of the bug you are experiencing + validations: + required: true +- type: textarea + id: reproductionsteps + attributes: + label: Steps To Reproduce + description: Steps to reproduce the behavior. + placeholder: | + 1. Go to... + 2. Click on... + 3. Scroll down to... + 4. See error... + validations: + required: true +- type: textarea + id: expectedbehavior + attributes: + label: Expected Behavior + description: A concise description of what you expected to happen. + validations: + required: false +- type: textarea + id: logfiles + attributes: + label: Log Files + description: Find your log files from the FAQ in the wiki + placeholder: Drag and drop your log files here! + validations: + required: false +- type: textarea + id: savefile + attributes: + label: Save File + description: Zip your named save file (not an auto save!) + placeholder: Drag and drop your zipped save file here! + validations: + required: false +- type: textarea + id: screenshots + attributes: + label: Screenshots + description: Screenshot of the bug + placeholder: Drag and drop screenshots of the bug here! + validations: + required: false +- type: textarea + id: settings + attributes: + label: Settings + description: Please provide your settings file + placeholder: Drag and drop your settings.xml file here! + validations: + required: false +- type: input + id: toyboxversion + attributes: + label: Toybox Version + description: Please provide the version of Toybox you are using + placeholder: Example - 1.5.4c + validations: + required: true +- type: input + id: os + attributes: + label: Operating System + description: Please provide what operating system you use + placeholder: Examples - Windows, MacOS + validations: + required: false +- type: dropdown + id: whatgame + attributes: + label: What game are you using Toybox on? + options: + - Pathfinder Wrath of the Righteous + - Rogue Trader + validations: + required: true +- type: input + id: gameversion + attributes: + label: Game Version + description: Please provide what version of the game you are on + placeholder: Example - 2.1.4w + validations: + required: false +- type: textarea + id: extrainfo + attributes: + label: Anything else? + description: | + Links? References? Anything that will give us more context about the issue you are encountering! + + Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/Crashes.yml b/.github/ISSUE_TEMPLATE/Crashes.yml new file mode 100644 index 000000000..d927748ea --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Crashes.yml @@ -0,0 +1,106 @@ +name: Crashes or Data Loss +description: Create a report if Toybox is crashing or lossing data +title: "<title>" +labels: ["bug", "crashes"] +body: +- type: textarea + id: bugdescription + attributes: + label: Description of the bug + description: Please give a clear and consise description of the bug you are experiencing + validations: + required: true +- type: textarea + id: reproductionsteps + attributes: + label: Steps To Reproduce + description: Steps to reproduce the behavior. + placeholder: | + 1. Go to... + 2. Click on... + 3. Scroll down to... + 4. See error... + validations: + required: true +- type: textarea + id: expectedbehavior + attributes: + label: Expected Behavior + description: A concise description of what you expected to happen. + validations: + required: false +- type: textarea + id: logfiles + attributes: + label: Log Files + description: Find your log files from the FAQ in the wiki + placeholder: Drag and drop your log files here! + validations: + required: false +- type: textarea + id: savefile + attributes: + label: Save File + description: Zip your named save file (not an auto save!) + placeholder: Drag and drop your zipped save file here! + validations: + required: false +- type: textarea + id: screenshots + attributes: + label: Screenshots + description: Screenshot of the bug + placeholder: Drag and drop screenshots of the bug here! + validations: + required: false +- type: textarea + id: settings + attributes: + label: Settings + description: Please provide your settings file + placeholder: Drag and drop your settings.xml file here! + validations: + required: false +- type: input + id: toyboxversion + attributes: + label: Toybox Version + description: Please provide the version of Toybox you are using + placeholder: Example - 1.5.4c + validations: + required: true +- type: input + id: os + attributes: + label: Operating System + description: Please provide what operating system you use + placeholder: Examples - Windows, MacOS + validations: + required: false +- type: dropdown + id: whatgame + attributes: + label: What game are you using Toybox on? + options: + - Pathfinder Wrath of the Righteous + - Rogue Trader + validations: + required: true +- type: input + id: gameversion + attributes: + label: Game Version + description: Please provide what version of the game you are on + placeholder: Example - 2.1.4w + validations: + required: false +- type: textarea + id: extrainfo + attributes: + label: Anything else? + description: | + Links? References? Anything that will give us more context about the issue you are encountering! + + Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/Gestalt-Issue.yml b/.github/ISSUE_TEMPLATE/Gestalt-Issue.yml new file mode 100644 index 000000000..9f15d10b2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Gestalt-Issue.yml @@ -0,0 +1,106 @@ +name: Gestalt Issue +description: Create a report if you are having issues with Toyboxs Gestalt feature +title: "<title>" +labels: ["bug", "gestalt-bugs"] +body: +- type: textarea + id: bugdescription + attributes: + label: Description of the bug + description: Please give a clear and consise description of the bug you are experiencing + validations: + required: true +- type: textarea + id: reproductionsteps + attributes: + label: Steps To Reproduce + description: Steps to reproduce the behavior. + placeholder: | + 1. Go to... + 2. Click on... + 3. Scroll down to... + 4. See error... + validations: + required: true +- type: textarea + id: expectedbehavior + attributes: + label: Expected Behavior + description: A concise description of what you expected to happen. + validations: + required: false +- type: textarea + id: logfiles + attributes: + label: Log Files + description: Find your log files from the FAQ in the wiki + placeholder: Drag and drop your log files here! + validations: + required: false +- type: textarea + id: savefile + attributes: + label: Save File + description: Zip your named save file (not an auto save!) + placeholder: Drag and drop your zipped save file here! + validations: + required: false +- type: textarea + id: screenshots + attributes: + label: Screenshots + description: Screenshot of the bug + placeholder: Drag and drop screenshots of the bug here! + validations: + required: false +- type: textarea + id: settings + attributes: + label: Settings + description: Please provide your settings file + placeholder: Drag and drop your settings.xml file here! + validations: + required: false +- type: input + id: toyboxversion + attributes: + label: Toybox Version + description: Please provide the version of Toybox you are using + placeholder: Example - 1.5.4c + validations: + required: true +- type: input + id: os + attributes: + label: Operating System + description: Please provide what operating system you use + placeholder: Examples - Windows, MacOS + validations: + required: false +- type: dropdown + id: whatgame + attributes: + label: What game are you using Toybox on? + options: + - Pathfinder Wrath of the Righteous + - Rogue Trader + validations: + required: true +- type: input + id: gameversion + attributes: + label: Game Version + description: Please provide what version of the game you are on + placeholder: Example - 2.1.4w + validations: + required: false +- type: textarea + id: extrainfo + attributes: + label: Anything else? + description: | + Links? References? Anything that will give us more context about the issue you are encountering! + + Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/UI-Issue.yml b/.github/ISSUE_TEMPLATE/UI-Issue.yml new file mode 100644 index 000000000..992388c5e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/UI-Issue.yml @@ -0,0 +1,106 @@ +name: UI Issue +description: Create a report if Toybox UI is messed up +title: "<title>" +labels: ["bug", "ui issues"] +body: +- type: textarea + id: bugdescription + attributes: + label: Description of the bug + description: Please give a clear and consise description of the bug you are experiencing + validations: + required: true +- type: textarea + id: reproductionsteps + attributes: + label: Steps To Reproduce + description: Steps to reproduce the behavior. + placeholder: | + 1. Go to... + 2. Click on... + 3. Scroll down to... + 4. See error... + validations: + required: true +- type: textarea + id: expectedbehavior + attributes: + label: Expected Behavior + description: A concise description of what you expected to happen. + validations: + required: false +- type: textarea + id: logfiles + attributes: + label: Log Files + description: Find your log files from the FAQ in the wiki + placeholder: Drag and drop your log files here! + validations: + required: false +- type: textarea + id: savefile + attributes: + label: Save File + description: Zip your named save file (not an auto save!) + placeholder: Drag and drop your zipped save file here! + validations: + required: false +- type: textarea + id: screenshots + attributes: + label: Screenshots + description: Screenshot of the bug + placeholder: Drag and drop screenshots of the bug here! + validations: + required: true +- type: textarea + id: settings + attributes: + label: Settings + description: Please provide your settings file + placeholder: Drag and drop your settings.xml file here! + validations: + required: false +- type: input + id: toyboxversion + attributes: + label: Toybox Version + description: Please provide the version of Toybox you are using + placeholder: Example - 1.5.4c + validations: + required: true +- type: input + id: os + attributes: + label: Operating System + description: Please provide what operating system you use + placeholder: Examples - Windows, MacOS + validations: + required: false +- type: dropdown + id: whatgame + attributes: + label: What game are you using Toybox on? + options: + - Pathfinder Wrath of the Righteous + - Rogue Trader + validations: + required: true +- type: input + id: gameversion + attributes: + label: Game Version + description: Please provide what version of the game you are on + placeholder: Example - 2.1.4w + validations: + required: false +- type: textarea + id: extrainfo + attributes: + label: Anything else? + description: | + Links? References? Anything that will give us more context about the issue you are encountering! + + Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 40481fe7f..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: bug -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Save Files** -To help us reproduce your issue, please attach a named *save file* -Please zip it or GitHub will not accept it. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Settings** -Please attach your settings.xml file from the mod folder - -**Version Info:** - - ToyBox Version: [e.g. 1.3.4] - - OS: [e.g. iOS] - - Version [e.g. 22] - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..29ad29f14 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Discord Community + url: https://discord.com/invite/owlcat + about: Join mod-user-general in the Discord for further support diff --git a/.github/ISSUE_TEMPLATE/enhancement-request.md b/.github/ISSUE_TEMPLATE/enhancement-request.md deleted file mode 100644 index b764e3299..000000000 --- a/.github/ISSUE_TEMPLATE/enhancement-request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Enhancement Request -about: Suggest an idea for this project -title: '' -labels: enhancement -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/enhancement.yml b/.github/ISSUE_TEMPLATE/enhancement.yml new file mode 100644 index 000000000..99427c34c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/enhancement.yml @@ -0,0 +1,33 @@ +name: Enhancement Request +description: Suggest an idea for this project +title: '<title>' +labels: enhancement +body: +- type: textarea + id: relatedtobug + attributes: + label: Is your feature request related to a problem? + description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + validations: + required: true +- type: textarea + id: proposedsolution + attributes: + label: Describe the solution you'd like + description: A clear and concise description of what you want to happen + validations: + required: true +- type: textarea + id: altsolutions + attributes: + label: Describe alternatives you've considered + description: A clear and concise description of any alternative solutions or features you've considered + validations: + required: false +- type: textarea + id: extrainfo + attributes: + label: Additional context + description: Add any other context or screenshots about the feature request here + validations: + required: false diff --git a/.github/workflows/Build Mod on PR.yml b/.github/workflows/Build Mod on PR.yml new file mode 100644 index 000000000..a09838533 --- /dev/null +++ b/.github/workflows/Build Mod on PR.yml @@ -0,0 +1,39 @@ +name: Build Mod on PR + +on: + pull_request_target: + types: [labeled] + +jobs: + + build: + if: ${{ contains(github.event.pull_request.labels.*.name, 'safe to test') }} + runs-on: windows-latest + + steps: + - name: Build + id: build-action + uses: xADDBx/BuildOwlcatMod@v2 + with: + GAME_NAME: RogueTrader + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_NAME: ${{ github.repository_owner }} + PACKAGE_OWNER: xADDBx + BRANCH_REF: ${{ github.event.pull_request.head.sha }} + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ env.zipFile }} + path: ${{ env.outDir }} + + - name: Remove Test Label + if: always() + shell: bash + run: | + curl --silent --fail-with-body \ + -X DELETE \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.WRITE_PR_TOKEN }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + 'https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.number }}/labels/safe%20to%20test' diff --git a/.github/workflows/Build Mod on Push.yml b/.github/workflows/Build Mod on Push.yml new file mode 100644 index 000000000..aa4b3a72a --- /dev/null +++ b/.github/workflows/Build Mod on Push.yml @@ -0,0 +1,26 @@ +name: Build Mod on Push + +on: + push: + +jobs: + + build: + if: ${{ !startsWith(github.ref, 'refs/tags/') }} + runs-on: windows-latest + + steps: + - name: Build + id: build-action + uses: xADDBx/BuildOwlcatMod@v2 + with: + GAME_NAME: RogueTrader + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_NAME: ${{ github.repository_owner }} + PACKAGE_OWNER: xADDBx + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ env.zipFile }} + path: ${{ env.outDir }} diff --git a/.github/workflows/Create Release for new Tag.yml b/.github/workflows/Create Release for new Tag.yml new file mode 100644 index 000000000..3132581cb --- /dev/null +++ b/.github/workflows/Create Release for new Tag.yml @@ -0,0 +1,36 @@ +name: Create Release for new Tag + +on: + push: + tags: + - '**' + +jobs: + + build: + runs-on: windows-latest + permissions: + contents: write + packages: read + + steps: + - name: Build + id: build-action + uses: xADDBx/BuildOwlcatMod@v2 + with: + GAME_NAME: RogueTrader + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_NAME: ${{ github.repository_owner }} + PACKAGE_OWNER: xADDBx + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ env.zipFile }} + path: ${{ env.outDir }} + + - name: Create Release + uses: ncipollo/release-action@v1 + with: + artifacts: ${{ env.zipFilePath }}\*.zip + name: ${{ env.ZipFile }} built for Rogue Trader ${{ env.gameVersionNum }}${{ env.gameVersionSuffix }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 85976aba6..154613e12 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ ## files generated by popular Visual Studio add-ons. ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore - +GamePath.props # Rider .idea/ @@ -368,4 +368,5 @@ MigrationBackup/ .ionide/ # Fody - auto-generated XML schema -FodyWeavers.xsd \ No newline at end of file +FodyWeavers.xsd +/GitDiff diff --git a/Directory.Build.props b/Directory.Build.props deleted file mode 100644 index 7b5392e14..000000000 --- a/Directory.Build.props +++ /dev/null @@ -1,6 +0,0 @@ -<Project> - <PropertyGroup> - <LangVersion>10.0</LangVersion> - <TargetFrameworkVersion>v6.0</TargetFrameworkVersion> - </PropertyGroup> -</Project> \ No newline at end of file diff --git a/ModKit/DataViewer/ObjectSet.cs b/ModKit/DataViewer/ObjectSet.cs deleted file mode 100644 index 3cff6a3a0..000000000 --- a/ModKit/DataViewer/ObjectSet.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.Serialization; -using System.Collections.Generic; - - -namespace ModKit.DataViewer { - public interface IObjectSet { - /// <summary> check the existence of an object. </summary> - /// <returns> true if object is exist, false otherwise. </returns> - bool IsExist(object obj); - - /// <summary> if the object is not in the set, add it in. else do nothing. </summary> - /// <returns> true if successfully added, false otherwise. </returns> - bool Add(object obj); - } - - public sealed class ObjectSetUsingConditionalWeakTable : IObjectSet { - /// <summary> unit test on object set. </summary> - internal static void Main() { - Stopwatch sw = new Stopwatch(); - sw.Start(); - ObjectSetUsingConditionalWeakTable objSet = new ObjectSetUsingConditionalWeakTable(); - for (int i = 0; i < 10000000; ++i) { - object obj = new object(); - if (objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); } - if (!objSet.Add(obj)) { Console.WriteLine("bug!!!"); } - if (!objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); } - } - sw.Stop(); - Console.WriteLine(sw.ElapsedMilliseconds); - } - - public bool IsExist(object obj) { - return objectSet.TryGetValue(obj, out tryGetValue_out0); - } - - public bool Add(object obj) { - if (IsExist(obj)) { - return false; - } - else { - objectSet.Add(obj, null); - return true; - } - } - - /// <summary> internal representation of the set. (only use the key) </summary> - private ConditionalWeakTable<object, object> objectSet = new ConditionalWeakTable<object, object>(); - - /// <summary> used to fill the out parameter of ConditionalWeakTable.TryGetValue(). </summary> - private static object tryGetValue_out0 = null; - } - - [Obsolete("It will crash if there are too many objects and ObjectSetUsingConditionalWeakTable get a better performance.")] - public sealed class ObjectSetUsingObjectIDGenerator : IObjectSet { - /// <summary> unit test on object set. </summary> - internal static void Main() { - Stopwatch sw = new Stopwatch(); - sw.Start(); - ObjectSetUsingObjectIDGenerator objSet = new ObjectSetUsingObjectIDGenerator(); - for (int i = 0; i < 10000000; ++i) { - object obj = new object(); - if (objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); } - if (!objSet.Add(obj)) { Console.WriteLine("bug!!!"); } - if (!objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); } - } - sw.Stop(); - Console.WriteLine(sw.ElapsedMilliseconds); - } - - - public bool IsExist(object obj) { - bool firstTime; - idGenerator.HasId(obj, out firstTime); - return !firstTime; - } - - public bool Add(object obj) { - bool firstTime; - idGenerator.GetId(obj, out firstTime); - return firstTime; - } - - - /// <summary> internal representation of the set. </summary> - private ObjectIDGenerator idGenerator = new ObjectIDGenerator(); - } -} diff --git a/ModKit/DataViewer/UnsafeForceCast.cs b/ModKit/DataViewer/UnsafeForceCast.cs deleted file mode 100644 index 2efdad150..000000000 --- a/ModKit/DataViewer/UnsafeForceCast.cs +++ /dev/null @@ -1,39 +0,0 @@ -using ModKit.Utility; -using System; -using System.Reflection.Emit; - -namespace ModKit.DataViewer { - internal static class UnsafeForceCast - { - private static readonly DoubleDictionary<Type, Type, WeakReference> _cache = new DoubleDictionary<Type, Type, WeakReference>(); - - public static Func<TInput, TOutput> GetDelegate<TInput, TOutput>() - { - Func<TInput, TOutput> cache = default; - if (_cache.TryGetValue(typeof(TInput), typeof(TOutput), out WeakReference weakRef)) - cache = weakRef.Target as Func<TInput, TOutput>; - if (cache == null) - { - cache = CreateDelegate<TInput, TOutput>(); - _cache[typeof(TInput), typeof(TOutput)] = new WeakReference(cache); - } - return cache; - } - - private static Func<TInput, TOutput> CreateDelegate<TInput, TOutput>() - { - DynamicMethod method = new DynamicMethod( - name: "UnsafeForceCast", - returnType: typeof(TOutput), - parameterTypes: new[] { typeof(TInput) }); - - ILGenerator il = method.GetILGenerator(); - il.Emit(OpCodes.Ldarg_0); - if (typeof(TInput) == typeof(object) && typeof(TOutput).IsValueType) - il.Emit(OpCodes.Unbox_Any, typeof(TOutput)); - il.Emit(OpCodes.Ret); - - return method.CreateDelegate(typeof(Func<TInput, TOutput>)) as Func<TInput, TOutput>; - } - } -} diff --git a/ModKit/ModKit.csproj b/ModKit/ModKit.csproj deleted file mode 100644 index 3434e42aa..000000000 --- a/ModKit/ModKit.csproj +++ /dev/null @@ -1,151 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <TargetFramework>net4.7.2</TargetFramework> - <AssemblyName>ModKit</AssemblyName> - <PackageId>ModKit</PackageId> - <Authors>Narria</Authors> - <Company>Cabarius</Company> - <Product>ModKit</Product> - <Description>A game agnostic toolkit for building Mod UI for unity mod manager based mods. Builds on ModMaker (https://github.com/cabarius/WrathModMaker and all past repos it was forked from)</Description> - <Copyright>Copyright © 2021</Copyright> - <PackageLicenseFile>LICENSE.txt</PackageLicenseFile> - <RepositoryUrl></RepositoryUrl> - <PackageProjectUrl>https://github.com/cabarius/ToyBox</PackageProjectUrl> - <PackageTags>Unity, Mod, UnityModManager, UMM</PackageTags> - </PropertyGroup> - - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> - <WarningLevel>5</WarningLevel> - <AllowUnsafeBlocks>true</AllowUnsafeBlocks> - <LangVersion>9</LangVersion> - </PropertyGroup> - - <ItemGroup> - <None Include="..\LICENSE.txt"> - <Pack>True</Pack> - <PackagePath></PackagePath> - </None> - <None Include="ModKitSrc.shproj"> - <Pack>True</Pack> - <PackagePath></PackagePath> - </None> - <None Include="ModKitSrc.projitems"> - <Pack>True</Pack> - <PackagePath></PackagePath> - </None> - <None Include="ModKit\**"> - <Pack>True</Pack> - <PackagePath>ModKit</PackagePath> - </None> - <None Include="UI\**"> - <Pack>True</Pack> - <PackagePath>UI</PackagePath> - </None> - <None Include="Utility\**"> - <Pack>True</Pack> - <PackagePath>Utility</PackagePath> - </None> - </ItemGroup> - - <ItemGroup> - <PackageReference Include="Lib.Harmony" Version="2.0.4" /> - <PackageReference Include="Newtonsoft.Json" Version="13.0.2" /> - </ItemGroup> - - <ItemGroup> - <Reference Include="System.Net.Http" /> - <Reference Include="System.Web" /> - <Reference Include="UnityEngine"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\UnityEngine.dll</HintPath> - <Private>false</Private> - </Reference> - <Reference Include="UnityEngine.AssetBundleModule"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\UnityEngine.AssetBundleModule.dll</HintPath> - <Private>false</Private> - </Reference> - <Reference Include="UnityEngine.CoreModule"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\UnityEngine.CoreModule.dll</HintPath> - <Private>false</Private> - </Reference> - <Reference Include="UnityEngine.IMGUIModule"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\UnityEngine.IMGUIModule.dll</HintPath> - <Private>false</Private> - </Reference> - <Reference Include="UnityEngine.InputLegacyModule"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\UnityEngine.InputLegacyModule.dll</HintPath> - </Reference> - <Reference Include="UnityEngine.InputModule"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\UnityEngine.InputModule.dll</HintPath> - <Private>false</Private> - </Reference> - <Reference Include="UnityEngine.TextRenderingModule"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\UnityEngine.TextRenderingModule.dll</HintPath> - <Private>false</Private> - </Reference> - <Reference Include="UnityEngine.UI"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\UnityEngine.UI.dll</HintPath> - <Private>false</Private> - </Reference> - <Reference Include="UnityModManager"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\UnityModManager\UnityModManager.dll</HintPath> - <Private>false</Private> - </Reference> - </ItemGroup> - - <Import Project="ModKitSrc.projitems" Label="Shared" /> - - <ItemGroup> - <Reference Update="System"> - <Private>false</Private> - </Reference> - </ItemGroup> - - <ItemGroup> - <Reference Update="System.Core"> - <Private>false</Private> - </Reference> - </ItemGroup> - - <ItemGroup> - <Reference Update="System.Data"> - <Private>false</Private> - </Reference> - </ItemGroup> - - <ItemGroup> - <Reference Update="System.Drawing"> - <Private>false</Private> - </Reference> - </ItemGroup> - - <ItemGroup> - <Reference Update="System.IO.Compression.FileSystem"> - <Private>false</Private> - </Reference> - </ItemGroup> - - <ItemGroup> - <Reference Update="System.Numerics"> - <Private>false</Private> - </Reference> - </ItemGroup> - - <ItemGroup> - <Reference Update="System.Runtime.Serialization"> - <Private>false</Private> - </Reference> - </ItemGroup> - - <ItemGroup> - <Reference Update="System.Xml"> - <Private>false</Private> - </Reference> - </ItemGroup> - - <ItemGroup> - <Reference Update="System.Xml.Linq"> - <Private>false</Private> - </Reference> - </ItemGroup> -</Project> diff --git a/ModKit/ModKit.sln b/ModKit/ModKit.sln deleted file mode 100644 index 5c7b16169..000000000 --- a/ModKit/ModKit.sln +++ /dev/null @@ -1,30 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31205.134 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ModKit", "C:\Users\PC\source\repos\ToyBox\ModKit\ModKit.csproj", "{BD28D55C-3A8D-4078-9C56-D8FA20526C7E}" -EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "ModKitSrc", "ModKitSrc.shproj", "{7D3F9428-C134-47FE-9D04-305B4D330401}" -EndProject -Global - GlobalSection(SharedMSBuildProjectFiles) = preSolution - ModKitSrc.projitems*{7d3f9428-c134-47fe-9d04-305b4d330401}*SharedItemsImports = 13 - ModKitSrc.projitems*{bd28d55c-3a8d-4078-9c56-d8fa20526c7e}*SharedItemsImports = 5 - EndGlobalSection - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {BD28D55C-3A8D-4078-9C56-D8FA20526C7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BD28D55C-3A8D-4078-9C56-D8FA20526C7E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BD28D55C-3A8D-4078-9C56-D8FA20526C7E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BD28D55C-3A8D-4078-9C56-D8FA20526C7E}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {2F8600CB-2139-46EF-B0F5-36C36F060BAD} - EndGlobalSection -EndGlobal diff --git a/ModKit/ModKit/MenuManager.cs b/ModKit/ModKit/MenuManager.cs deleted file mode 100644 index 4c660ace3..000000000 --- a/ModKit/ModKit/MenuManager.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; -using UnityEngine; -using UnityModManagerNet; -using ModKit.Utility; - -namespace ModKit { - public interface IMenuPage { - string Name { get; } - - int Priority { get; } - - void OnGUI(UnityModManager.ModEntry modEntry); - } - - public interface IMenuTopPage : IMenuPage { } - - public interface IMenuSelectablePage : IMenuPage { } - - public interface IMenuBottomPage : IMenuPage { } - - public class MenuManager : INotifyPropertyChanged { - public event PropertyChangedEventHandler PropertyChanged; - - // This method is called by the Set accessor of each property. - // The CallerMemberName attribute that is applied to the optional propertyName - // parameter causes the property name of the caller to be substituted as an argument. - private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - - #region Fields - private int _tabIndex; - public int tabIndex { - get { return _tabIndex; } - set { _tabIndex = value; NotifyPropertyChanged(); } - } - private readonly List<IMenuTopPage> _topPages = new(); - private readonly List<IMenuSelectablePage> _selectablePages = new(); - private readonly List<IMenuBottomPage> _bottomPages = new(); - private static Exception caughtException = null; - - #endregion - - #region Toggle - - public void Enable(UnityModManager.ModEntry modEntry, Assembly _assembly) { - foreach (var type in _assembly.GetTypes() - .Where(type => !type.IsInterface && !type.IsAbstract && typeof(IMenuPage).IsAssignableFrom(type))) { - if (typeof(IMenuTopPage).IsAssignableFrom(type)) - _topPages.Add(Activator.CreateInstance(type, true) as IMenuTopPage); - - if (typeof(IMenuSelectablePage).IsAssignableFrom(type)) - _selectablePages.Add(Activator.CreateInstance(type, true) as IMenuSelectablePage); - - if (typeof(IMenuBottomPage).IsAssignableFrom(type)) - _bottomPages.Add(Activator.CreateInstance(type, true) as IMenuBottomPage); - } - - static int comparison(IMenuPage x, IMenuPage y) => x.Priority - y.Priority; - _topPages.Sort(comparison); - _selectablePages.Sort(comparison); - _bottomPages.Sort(comparison); - - modEntry.OnGUI += OnGUI; - } - - public void Disable(UnityModManager.ModEntry modEntry) { - modEntry.OnGUI -= OnGUI; - - _topPages.Clear(); - _selectablePages.Clear(); - _bottomPages.Clear(); - } - - #endregion - - private void OnGUI(UnityModManager.ModEntry modEntry) { - var hasPriorPage = false; - try { - if (caughtException != null) { - GUILayout.Label("ERROR".Red().Bold() + $": caught exception {caughtException}"); - if (GUILayout.Button("Reset".Orange().Bold(), GUILayout.ExpandWidth(false))) { - caughtException = null; - } - return; - } - var e = Event.current; - UI.userHasHitReturn = e.keyCode == KeyCode.Return; - UI.focusedControlName = GUI.GetNameOfFocusedControl(); - - - if (_topPages.Count > 0) { - foreach (var page in _topPages) { - if (hasPriorPage) - GUILayout.Space(10f); - page.OnGUI(modEntry); - hasPriorPage = true; - } - } - - if (_selectablePages.Count > 0) { - if (_selectablePages.Count > 1) { - if (hasPriorPage) - GUILayout.Space(10f); - tabIndex = GUILayout.Toolbar(tabIndex, _selectablePages.Select(page => page.Name).ToArray()); - - GUILayout.Space(10f); - } - - _selectablePages[tabIndex].OnGUI(modEntry); - hasPriorPage = true; - } - - if (_bottomPages.Count > 0) { - foreach (var page in _bottomPages) { - if (hasPriorPage) - GUILayout.Space(10f); - page.OnGUI(modEntry); - hasPriorPage = true; - } - } - } - catch (Exception e) { - Console.Write($"{e}"); - caughtException = e; - } - } - } -} \ No newline at end of file diff --git a/ModKit/ModKit/ModManager.cs b/ModKit/ModKit/ModManager.cs deleted file mode 100644 index f00caafcb..000000000 --- a/ModKit/ModKit/ModManager.cs +++ /dev/null @@ -1,234 +0,0 @@ -using HarmonyLib; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using UnityModManagerNet; - -namespace ModKit { - public interface IModEventHandler { - int Priority { get; } - - void HandleModEnable(); - - void HandleModDisable(); - } - - public partial class Mod { - public delegate void ShowGUINotifierMethod(); - public static ShowGUINotifierMethod NotifyOnShowGUI; - - public static void OnShowGUI() { - if (NotifyOnShowGUI != null) { - NotifyOnShowGUI(); - } - } - } - - public class ModManager<TCore, TSettings> - where TCore : class, new() - where TSettings : UnityModManager.ModSettings, new() { - #region Fields & Properties - - private UnityModManager.ModEntry.ModLogger _logger; - private List<IModEventHandler> _eventHandlers; - - public TCore Core { get; private set; } - - public TSettings Settings { get; private set; } - - public Version Version { get; private set; } - - public bool Enabled { get; private set; } - - public bool Patched { get; private set; } - - #endregion - - #region Toggle - - public void Enable(UnityModManager.ModEntry modEntry, Assembly assembly) { - _logger = modEntry.Logger; - - if (Enabled) { - Debug("Already enabled."); - return; - } - - using ProcessLogger process = new(_logger); - try { - Mod.modEntry = modEntry; - process.Log("Enabling."); - var dict = Harmony.VersionInfo(out var myVersion); - process.Log($"Harmony version: {myVersion}"); - foreach (var entry in dict) { - process.Log($"Mod {entry.Key} loaded with Harmony version {entry.Value}"); - } - - process.Log("Loading settings."); - modEntry.OnSaveGUI += HandleSaveGUI; - Version = modEntry.Version; - Settings = UnityModManager.ModSettings.Load<TSettings>(modEntry); - ModKitSettings.Load(); - Core = new TCore(); - - var types = assembly.GetTypes(); - - if (!Patched) { - Harmony harmonyInstance = new(modEntry.Info.Id); - foreach (var type in types) { - var harmonyMethods = HarmonyMethodExtensions.GetFromType(type); - if (harmonyMethods != null && harmonyMethods.Count() > 0) { - process.Log($"Patching: {type.FullName}"); - try { - var patchProcessor = harmonyInstance.CreateClassProcessor(type); - patchProcessor.Patch(); - } - catch (Exception e) { - Error(e); - } - } - } - Patched = true; - } - - Enabled = true; - - process.Log("Registering events."); - _eventHandlers = types.Where(type => type != typeof(TCore) && - !type.IsInterface && !type.IsAbstract && typeof(IModEventHandler).IsAssignableFrom(type)) - .Select(type => Activator.CreateInstance(type, true) as IModEventHandler).ToList(); - if (Core is IModEventHandler core) { - _eventHandlers.Add(core); - } - _eventHandlers.Sort((x, y) => x.Priority - y.Priority); - - process.Log("Raising events: OnEnable()"); - for (var i = 0; i < _eventHandlers.Count; i++) { - _eventHandlers[i].HandleModEnable(); - } - } - catch (Exception e) { - Error(e); - Disable(modEntry, true); - throw; - } - - process.Log("Enabled."); - } - - public void Disable(UnityModManager.ModEntry modEntry, bool unpatch = false) { - _logger = modEntry.Logger; - - using ProcessLogger process = new(_logger); - process.Log("Disabling."); - - Enabled = false; - - // use try-catch to prevent the progression being disrupt by exceptions - if (_eventHandlers != null) { - process.Log("Raising events: OnDisable()"); - for (var i = _eventHandlers.Count - 1; i >= 0; i--) { - try { _eventHandlers[i].HandleModDisable(); } - catch (Exception e) { Error(e); } - } - _eventHandlers = null; - } - - if (unpatch) { - Harmony harmonyInstance = new(modEntry.Info.Id); - foreach (var method in harmonyInstance.GetPatchedMethods().ToList()) { - var patchInfo = Harmony.GetPatchInfo(method); - var patches = - patchInfo.Transpilers.Concat(patchInfo.Postfixes).Concat(patchInfo.Prefixes) - .Where(patch => patch.owner == modEntry.Info.Id); - if (patches.Any()) { - process.Log($"Unpatching: {patches.First().PatchMethod.DeclaringType.FullName} from {method.DeclaringType.FullName}.{method.Name}"); - foreach (var patch in patches) { - try { harmonyInstance.Unpatch(method, patch.PatchMethod); } - catch (Exception e) { Error(e); } - } - } - } - Patched = false; - } - - modEntry.OnSaveGUI -= HandleSaveGUI; - Core = null; - Settings = null; - Version = null; - _logger = null; - - process.Log("Disabled."); - } - - #endregion - - #region Settings - - public void ResetSettings() { - if (Enabled) { - Settings = new TSettings(); - Mod.ModKitSettings = new ModKitSettings(); - } - } - - private void HandleSaveGUI(UnityModManager.ModEntry modEntry) { - UnityModManager.ModSettings.Save(Settings, modEntry); - ModKitSettings.Save(); - } - - #endregion - - #region Loggers - - public void Critical(string str) => _logger.Critical(str); - - public void Critical(object obj) => _logger.Critical(obj?.ToString() ?? "null"); - - public void Error(Exception e) { - _logger.Error($"{e.Message}\n{e.StackTrace}"); - if (e.InnerException != null) - Error(e.InnerException); - } - - public void Error(string str) => _logger.Error(str); - - public void Error(object obj) => _logger.Error(obj?.ToString() ?? "null"); - - public void Log(string str) => _logger.Log(str); - - public void Log(object obj) => _logger.Log(obj?.ToString() ?? "null"); - - public void Warning(string str) => _logger.Warning(str); - - public void Warning(object obj) => _logger.Warning(obj?.ToString() ?? "null"); - - [Conditional("DEBUG")] - public void Debug(MethodBase method, params object[] parameters) => _logger.Log($"{method.DeclaringType.Name}.{method.Name}({string.Join(", ", parameters)})"); - - [Conditional("DEBUG")] - public void Debug(string str) => _logger.Log(str); - - [Conditional("DEBUG")] - public void Debug(object obj) => _logger.Log(obj?.ToString() ?? "null"); - - #endregion - - private class ProcessLogger : IDisposable { - private readonly Stopwatch _stopWatch = new(); - private readonly UnityModManager.ModEntry.ModLogger _logger; - - public ProcessLogger(UnityModManager.ModEntry.ModLogger logger) { - _logger = logger; - _stopWatch.Start(); - } - - public void Dispose() => _stopWatch.Stop(); - - [Conditional("DEBUG")] - public void Log(string status) => _logger.Log($"[{_stopWatch.Elapsed:ss\\.ff}] {status}"); - } - } -} \ No newline at end of file diff --git a/ModKit/ModKitSrc.projitems b/ModKit/ModKitSrc.projitems deleted file mode 100644 index 4c984ea1d..000000000 --- a/ModKit/ModKitSrc.projitems +++ /dev/null @@ -1,68 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <PropertyGroup> - <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects> - <HasSharedItems>true</HasSharedItems> - <SharedGUID>7d3f9428-c134-47fe-9d04-305b4d330401</SharedGUID> - <TargetFramework>net472</TargetFramework> - <GeneratePackageOnBuild>true</GeneratePackageOnBuild> - <PackageLicenseFile>LICENSE.txt</PackageLicenseFile> - </PropertyGroup> - <PropertyGroup Label="Configuration"> - <Import_RootNamespace>ModKit</Import_RootNamespace> - <Version>1.0.8</Version> - <Description>A game agnostic toolkit for building Mod UI for unity mod manager based mods. Builds on ModMaker (https://github.com/cabarius/WrathModMaker and all past repos it was forked from)</Description> - </PropertyGroup> - <ItemGroup> - <Compile Include="$(MSBuildThisFileDirectory)DataViewer\Indexer.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)DataViewer\ObjectSet.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)DataViewer\ReflectionSearch.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)DataViewer\ReflectionSearchResult.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)DataViewer\ReflectionTree.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)DataViewer\ReflectionTreeView.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)DataViewer\UnsafeForceCast.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)ModKit\LocalizationManager.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)ModKit\MenuManager.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)ModKit\ModKit.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)ModKit\ModManager.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)ModKit\ModKitSettings.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)ModKit\SettingsController.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)UI\Browser\BrowserModel.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)UI\ColorUtils.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)UI\Glyphs.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)UI\KeyBindings\KeyBindings.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)UI\Private\Toggle.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)UI\RichText.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)UI\Styles.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)UI\UI+Browser.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)UI\UI+Builders.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)UI\UI+HTML.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)UI\UI+Controls.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)UI\UI+Elements.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)UI\KeyBindings\KeyBind.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)UI\UI+Pickers.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)UI\UI+Toggles.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)UI\UI+Wrappers.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)Utility\EmbeddedResourceUtils.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)Utility\Extensions\CodeInstructionExtensions.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)Utility\Dictionary\DoubleDictionary.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)Utility\Extensions\RichTextExtensions.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)Utility\Extensions\UnityExtensions.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)Utility\GUISubScope.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)UI\KeyBindings\UI+KeyBindings.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)Utility\NamedTypes.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)Utility\Dictionary\SerializableDictionary.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)Utility\Dictionary\TripleDictionary.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)Utility\Reflection\ReflectionCache.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)Utility\Reflection\ReflectionFieldCache.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)Utility\Reflection\ReflectionMethodCache.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)Utility\Reflection\ReflectionPropertyCache.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)Utility\Search.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)Utility\Translator.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)Utility\Utilities.cs" /> - </ItemGroup> - <ItemGroup> - <Folder Include="$(MSBuildThisFileDirectory)UI\KeyBindings\" /> - <Folder Include="$(MSBuildThisFileDirectory)Utility\Extensions\" /> - </ItemGroup> -</Project> \ No newline at end of file diff --git a/ModKit/ModKitSrc.shproj b/ModKit/ModKitSrc.shproj deleted file mode 100644 index a5f5fff0c..000000000 --- a/ModKit/ModKitSrc.shproj +++ /dev/null @@ -1,13 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <PropertyGroup Label="Globals"> - <ProjectGuid>7d3f9428-c134-47fe-9d04-305b4d330401</ProjectGuid> - <MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion> - </PropertyGroup> - <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> - <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" /> - <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" /> - <PropertyGroup /> - <Import Project="ModKitSrc.projitems" Label="Shared" /> - <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" /> -</Project> diff --git a/ModKit/UI/Browser/BrowserModel.cs b/ModKit/UI/Browser/BrowserModel.cs deleted file mode 100644 index bf60af29d..000000000 --- a/ModKit/UI/Browser/BrowserModel.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using ModKit.Utility; - -namespace ModKit { - public class Entry { - private Type[] Inheritance; // this may be able to become type - private string Title; - private string Subtitle; - private string Description; - private string Identifier; - private SerializableDictionary<string, string> extras; - private string SearchKey; - private string SortKey; - private string[] Categories; - private SerializableDictionary<string, string[]> Tags; // indexed by category - } - public class Category<T> { - private string Name; - public delegate bool CategoryChecker(T obj); - public delegate string[] Tagger(T obj); - public CategoryChecker IsCategory; - public Tagger GetTags; - } - - public abstract class DataSource<Data> { - public delegate void Updater(List<Entry> entries, int total, bool done = false); - public delegate Entry DataTransformer(Data data); - public Updater UpdateProgress; - public DataTransformer Transformer; - public bool IsLoading { get; private set; } = false; - public bool IsLoaded { get; private set; } = false; - - private CancellationTokenSource _cancelToken; - - public void Start() { - if (IsLoading) { - _cancelToken.Cancel(); - IsLoading = false; - } - _cancelToken = new(); - IsLoading = true; - Task.Run(() => LoadData()); - } - - public void Stop() { - if (IsLoading) { - IsLoading = false; - _cancelToken.Cancel(); - } - } - - protected abstract void LoadData(); - - } - - interface IBrowserViewModel { - public List<Entry> Entries { get; } - public HashSet<string> Tags { get; } - } - public class BrowserModel<Item, Def> : IBrowserViewModel { - public class Category<Item, Def> : Category<Def> { - public delegate void OnGUI(Item item, Def def); - - public OnGUI OnHeaderGUI; - public OnGUI OnRowGUI; - public OnGUI OnDetailGUI; - } - public List<Category<Item, Def>> Categories; - public List<Entry> Entries { get; } - public HashSet<string> Tags { get; } - } - - public class BrowserViewModel<Item, Def> : IBrowserViewModel { - public BrowserModel<Item, Def>.Category<Item, Def> SelectedCategory { get; } - public HashSet<string> SelectedTags; - public string SearchText { get; } - public List<Entry> Entries { get; } - public HashSet<string> Tags { get; } - - } -} diff --git a/ModKit/UI/ColorUtils.cs b/ModKit/UI/ColorUtils.cs deleted file mode 100644 index e0ba9e6a3..000000000 --- a/ModKit/UI/ColorUtils.cs +++ /dev/null @@ -1,72 +0,0 @@ -using UnityEngine; - -namespace ModKit { - // https://docs.unity3d.com/Manual/StyledText.html - public enum RGBA : uint { - aqua = 0x00ffffff, - blue = 0x8080ffff, - brown = 0xC09050ff, //0xa52a2aff, - crimson = 0x7b0340ff, - cyan = 0x00ffffff, - darkblue = 0x0000a0ff, - charcoal = 0x202020ff, - darkgrey = 0x808080ff, - darkred = 0xa0333bff, - fuchsia = 0xff40ffff, - green = 0x40C040ff, - gold = 0xED9B1Aff, - lightblue = 0xd8e6ffff, - lightgrey = 0xE8E8E8ff, - lime = 0x40ff40ff, - magenta = 0xff40ffff, - maroon = 0xFF6060ff, - medred = 0xd03333ff, - navy = 0x3b5681ff, - olive = 0xb0b000ff, - orange = 0xffa500ff, // 0xffa500ff, - darkorange = 0xb1521fff, - pink = 0xf03399ff, - purple = 0xC060F0ff, - red = 0xFF4040ff, - black = 0x000000ff, - medgrey = 0xA8A8A8ff, - grey = 0xC0C0C0ff, - silver = 0xD0D0D0ff, - teal = 0x80f0c0ff, - yellow = 0xffff00ff, - white = 0xffffffff, - none = silver, - trash = 0x808080ff, // 0x686868ff, 0x787878ff, // 0x734d26ff, // 0x86592dff, //0xA07040ff, // brown, 0x606060FF, - common = 0xd8d8d8a0, // 0x505050ff, // 0xd8d8d8a0, //0xe8e8e8a0, - uncommon = 0x086c26ff, // 0x00882bff, //0x00802bff, //0x68b020ff, // 0x60B020ff, - rare = 0x103080ff, // 0x2060ffff, - epic = 0x481ca0ff, //0x5020c0ff, 0x6030F0ff, 0xc260f1ff, // 0x79297bff, //0x9f608cff, // 0x885278ff, // 0xc260f1ff, //0xc860fff, - legendary = 0xb2593aff, // 0xad5537ff, 0xb8603dff, 0xaa623dff, 0x9a5a3dff,0x9a4a2dff, (1.4.23 and older) 0xe67821e0, // 0x9a4a2dff, // 0x984c31ff, //0xe67821ff, //* 0xe67821e0, // 0xe67821ff, // 0xe68019ff // 0xEDCB1Aff, - mythic = 0xb02369ff, //0xc02369ff, 0xf03399ff, 0xc260f1ff, 0x8080ffff, //0x60ffffff, // 0x84e2d4ff, // 0x2cd8d4ff, // * 0x60ffffff, - primal = 0x2cb8b4ff, //pink, red - godly = 0x990033ff, // darkred, - notable = 0x98761fff, //0xb1821fff, 0xffe000ff, // 0xC08020ff //0xffd840ff, // 0x40ff40c0, // 0xf03399ff, // 0xff3399ff, - uncommon_dark = 0x00a000ff, // 0x00882bff, //0x00802bff, //0x68b020ff, // 0x60B020ff, - rare_dark = 0x1030e0ff, // 0x2060ffff, - epic_dark = 0x6030F0ff, //0x5020c0ff, 0x6030F0ff, 0xc260f1ff, // 0x79297bff, //0x9f608cff, // 0x885278ff, // 0xc260f1ff, //0xc860fff, - legendary_dark = 0xe67821e0, // 0xad5537ff, 0xb8603dff, 0xaa623dff, 0x9a5a3dff,0x9a4a2dff, (1.4.23 and older) 0xe67821e0, // 0x9a4a2dff, // 0x984c31ff, //0xe67821ff, //* 0xe67821e0, // 0xe67821ff, // 0xe68019ff // 0xEDCB1Aff, - mythic_dark = 0xA000A0ff, //0xc02369ff, 0xf03399ff, 0xc260f1ff, 0x8080ffff, //0x60ffffff, // 0x84e2d4ff, // 0x2cd8d4ff, // * 0x60ffffff, - primal_dark = 0x60ffffff, //pink, red - godly_dark = 0xa00000ff, // darkred, - notable_dark = 0x98761fff, //0xb1821fff, 0xffe000ff, // 0xC08020ff //0xffd840ff, // 0x40ff40c0, // 0xf03399ff, // 0xff3399ff, - } - public static class ColorUtils { - public static Color color(this RGBA rga, float adjust = 0) { - var red = (float)((long)rga >> 24) / 256f; - var green = (float)(0xFF & ((long)rga >> 16)) / 256f; - var blue = (float)(0xFF & ((long)rga >> 8)) / 256f; - var alpha = (float)(0xFF & ((long)rga)) / 256f; - var color = new Color(red, green, blue, alpha); - if (adjust < 0) - color = Color.Lerp(color, Color.black, -adjust); - if (adjust > 0) - color = Color.Lerp(color, Color.white, adjust); - return color; - } - } -} diff --git a/ModKit/UI/GUIHelper.cs b/ModKit/UI/GUIHelper.cs deleted file mode 100644 index 0d6f58de0..000000000 --- a/ModKit/UI/GUIHelper.cs +++ /dev/null @@ -1,226 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; -namespace ModKit.Utility { - public static class GUIHelper { - public const string onMark = $"<color=green><b>{Glyphs.CheckOn()}!</b></color>"; - public const string offMark = $"<color=#A0A0A0E0>{Glyphs.CheckOff()}!</color>"; - - public static string FormatOn = Glyphs.DisclosureOn().color(RGBA.white).Bold() + " {0}"; - public static string FormatOff = Glyphs.DisclosureOff().color(RGBA.lime).Bold() + " {0}"; - public static string FormatNone = $" {Glyphs.DisclosureEmpty()}!".color(RGBA.white) + " {0}"; - - public static string GetToggleText(ToggleState toggleState, string text) { - return toggleState switch { - ToggleState.Off => string.Format(FormatOff, text), - ToggleState.On => string.Format(FormatOn, text), - ToggleState.None => string.Format(FormatNone, text), - _ => string.Format(FormatNone), - }; - } - - public static int AdjusterButton(int value, string text, int min = int.MinValue, int max = int.MaxValue) { - AdjusterButton(ref value, text, min, max); - return value; - } - - public static bool AdjusterButton(ref int value, string text, int min = int.MinValue, int max = int.MaxValue) { - var oldValue = value; - GUILayout.Label(text, GUILayout.ExpandWidth(false)); - if (GUILayout.Button("-", GUILayout.ExpandWidth(false)) && value > min) - value--; - GUILayout.Label(value.ToString(), GUILayout.ExpandWidth(false)); - if (GUILayout.Button("+", GUILayout.ExpandWidth(false)) && value < max) - value++; - return value != oldValue; - } - - public static void Hyperlink(string url, Color normalColor, Color hoverColor, GUIStyle style) => Hyperlink(url, url, normalColor, hoverColor, style); - - public static void Hyperlink(string text, string url, Color normalColor, Color hoverColor, GUIStyle style) { - var color = GUI.color; - GUI.color = Color.clear; - GUILayout.Label(text, style, GUILayout.ExpandWidth(false)); - var lastRect = GUILayoutUtility.GetLastRect(); - GUI.color = lastRect.Contains(Event.current.mousePosition) ? hoverColor : normalColor; - if (GUI.Button(lastRect, text, style)) - Application.OpenURL(url); - lastRect.y += lastRect.height - 2; - lastRect.height = 1; - GUI.DrawTexture(lastRect, Texture2D.whiteTexture, ScaleMode.StretchToFill); - GUI.color = color; - } - - public static void TextField(ref string value, GUIStyle style = null, params GUILayoutOption[] options) => value = GUILayout.TextField(value, style ?? GUI.skin.textField, options); - - public static void TextField(ref string value, Action onChanged, GUIStyle style = null, params GUILayoutOption[] options) => TextField(ref value, null, onChanged, style, options); - - public static void TextField(ref string value, Action onClear, Action onChanged, GUIStyle style = null, params GUILayoutOption[] options) { - var old = value; - TextField(ref value, style, options); - if (value != old) { - if (onClear != null && string.IsNullOrEmpty(value)) - onClear(); - else - onChanged(); - } - } -#if false - static bool CheckboxPrivate( - ref bool value, - string title, - GUIStyle style = null, - params GUILayoutOption[] options - ) { - bool changed = false; - title = value ? title.Bold() : title.color(RGBA.lightgrey); - if (UI.Toggle(title, ref value, 0, options)) changed = true; - //if (GUILayout.Button("" + (value ? onMark : offMark) + " " + title, style, options)) { value = !value; } - return changed; - } - public static bool Checkbox( - ref bool value, - String title, - GUIStyle style = null, - params GUILayoutOption[] options) { - return CheckboxPrivate(ref value, title, style, options); - } -#endif - public static ToggleState ToggleButton(ToggleState toggle, string text, GUIStyle style = null, params GUILayoutOption[] options) { - UI.ToggleButton(ref toggle, text, style, options); - return toggle; - } - - public static ToggleState ToggleButton(ToggleState toggle, string text, Action on, Action off, GUIStyle style = null, params GUILayoutOption[] options) { - ToggleButton(ref toggle, text, on, off, style, options); - return toggle; - } - - public static void ToggleButton(ref ToggleState toggle, string text, Action on, Action off, GUIStyle style = null, params GUILayoutOption[] options) { - var old = toggle; - UI.ToggleButton(ref toggle, text, style, options); - if (toggle != old) { - if (toggle.IsOn()) - on?.Invoke(); - else - off?.Invoke(); - } - } - - public static void ToggleButton(ref ToggleState toggle, string text, ref float minWidth, GUIStyle style = null, params GUILayoutOption[] options) { - GUIContent content = new(GetToggleText(toggle, text)); - style ??= GUI.skin.button; - minWidth = Math.Max(minWidth, style.CalcSize(content).x); - if (GUILayout.Button(content, style, options?.Concat(new[] { GUILayout.Width(minWidth) }).ToArray() ?? new[] { GUILayout.Width(minWidth) })) - toggle = toggle.Flip(); - } - - public static void ToggleButton(ref ToggleState toggle, string text, ref float minWidth, Action on, Action off, GUIStyle style = null, params GUILayoutOption[] options) { - var old = toggle; - ToggleButton(ref toggle, text, ref minWidth, style, options); - if (toggle != old) { - if (toggle.IsOn()) - on?.Invoke(); - else - off?.Invoke(); - } - } - - public static ToggleState ToggleTypeList(ToggleState toggle, string text, HashSet<string> selectedTypes, HashSet<Type> allTypes, GUIStyle style = null, params GUILayoutOption[] options) { - GUILayout.BeginHorizontal(); - - UI.ToggleButton(ref toggle, text, style, options); - - if (toggle.IsOn()) { - using (new GUILayout.VerticalScope()) { - using (new GUILayout.HorizontalScope()) { - if (GUILayout.Button("Select All")) { - foreach (var type in allTypes) { - selectedTypes.Add(type.FullName); - } - } - if (GUILayout.Button("Deselect All")) { - selectedTypes.Clear(); - } - } - - foreach (var type in allTypes) { - ToggleButton(selectedTypes.Contains(type.FullName) ? ToggleState.On : ToggleState.Off, type.Name.ToSentence(), - () => selectedTypes.Add(type.FullName), - () => selectedTypes.Remove(type.FullName), - style, options); - } - } - } - - GUILayout.EndHorizontal(); - - return toggle; - } - - public static void Toolbar(ref int selected, string[] texts, GUIStyle style = null, params GUILayoutOption[] options) => selected = GUILayout.Toolbar(selected, texts, style ?? GUI.skin.button, options); - - public static void SelectionGrid(ref int selected, string[] texts, int xCount, GUIStyle style = null, params GUILayoutOption[] options) => selected = GUILayout.SelectionGrid(selected, texts, xCount, style ?? GUI.skin.button, options); - - public static void SelectionGrid(ref int selected, string[] texts, int xCount, Action onChanged, GUIStyle style = null, params GUILayoutOption[] options) { - var old = selected; - SelectionGrid(ref selected, texts, xCount, style, options); - if (selected != old) { - onChanged?.Invoke(); - } - } - - public static float RoundedHorizontalSlider(float value, int digits, float leftValue, float rightValue, params GUILayoutOption[] options) { - if (digits < 0) { - var num = (float)Math.Pow(10d, -digits); - return (float)Math.Round(GUILayout.HorizontalSlider(value, leftValue, rightValue, options) / num, 0) * num; - } - else { - return (float)Math.Round(GUILayout.HorizontalSlider(value, leftValue, rightValue, options), digits); - } - } - - private static Texture2D fillTexture = null; - private static GUIStyle fillStyle = null; - private static Color fillColor = new(1f, 1f, 1f, 0.65f); - private static readonly Color color = new(1f, 1f, 1f, 0.35f); - private static Color fillColor2 = color; - - public static Color FillColor2 { get => fillColor2; set => fillColor2 = value; } - - public static GUIStyle FillStyle(Color color) { - if (fillTexture == null) - fillTexture = new Texture2D(1, 1); - if (fillStyle == null) - fillStyle = new GUIStyle(); - fillTexture.SetPixel(0, 0, color); - fillTexture.Apply(); - fillStyle.normal.background = fillTexture; - return fillStyle; - } - public static void GUIDrawRect(Rect position, Color color) => GUI.Box(position, GUIContent.none, FillStyle(color)); - //private static GUIStyle divStyle; - public static void Div(Color color, float indent = 0, float height = 0, float width = 0) { - if (fillTexture == null) - fillTexture = new Texture2D(1, 1); - var divStyle = new GUIStyle { - fixedHeight = 1 - }; - fillTexture.SetPixel(0, 0, color); - fillTexture.Apply(); - divStyle.normal.background = fillTexture; - divStyle.margin = new RectOffset((int)indent, 0, 4, 4); - if (width > 0) - divStyle.fixedWidth = width; - else - divStyle.fixedWidth = 0; - GUILayout.Space((1f * height) / 2f); - GUILayout.Box(GUIContent.none, divStyle); - GUILayout.Space(height / 2f); - } - - public static void Div(float indent = 0, float height = 25, float width = 0) => Div(fillColor, indent, height, width); - - } -} diff --git a/ModKit/UI/Private/Toggle.cs b/ModKit/UI/Private/Toggle.cs deleted file mode 100644 index 288aa51b9..000000000 --- a/ModKit/UI/Private/Toggle.cs +++ /dev/null @@ -1,138 +0,0 @@ -using UnityEngine; - -namespace ModKit.Private { - public static partial class UI { - - // Helper functionality. - - private static readonly GUIContent _LabelContent = new(); - public static readonly GUIContent CheckOn = new(ModKit.UI.ChecklyphOn); - public static readonly GUIContent CheckOff = new(ModKit.UI.CheckGlyphOff); - public static readonly GUIContent DisclosureOn = new(ModKit.UI.DisclosureGlyphOn); - public static readonly GUIContent DisclosureOff = new(ModKit.UI.DisclosureGlyphOff); - public static readonly GUIContent DisclosureEmpty = new(ModKit.UI.DisclosureGlyphEmpty); - private static GUIContent LabelContent(string text) { - _LabelContent.text = text; - _LabelContent.image = null; - _LabelContent.tooltip = null; - return _LabelContent; - } - - private static readonly int s_ButtonHint = "MyGUI.Button".GetHashCode(); - - public static bool Toggle(Rect rect, GUIContent label, bool value, bool isEmpty, GUIContent on, GUIContent off, GUIStyle stateStyle, GUIStyle labelStyle) { - var controlID = GUIUtility.GetControlID(s_ButtonHint, FocusType.Passive, rect); - var result = false; - switch (Event.current.GetTypeForControl(controlID)) { - case EventType.MouseDown: - if (GUI.enabled && rect.Contains(Event.current.mousePosition)) { - GUIUtility.hotControl = controlID; - Event.current.Use(); - } - break; - - case EventType.MouseDrag: - if (GUIUtility.hotControl == controlID) { - Event.current.Use(); - } - break; - - case EventType.MouseUp: - if (GUIUtility.hotControl == controlID) { - GUIUtility.hotControl = 0; - - if (rect.Contains(Event.current.mousePosition)) { - result = true; - Event.current.Use(); - } - } - break; - - case EventType.KeyDown: - if (GUIUtility.hotControl == controlID) { - if (Event.current.keyCode == KeyCode.Escape) { - GUIUtility.hotControl = 0; - Event.current.Use(); - } - } - break; - - case EventType.Repaint: { - //bool leftAlign = stateStyle.alignment == TextAnchor.MiddleLeft - // || stateStyle.alignment == TextAnchor.UpperLeft - // || stateStyle.alignment == TextAnchor.LowerLeft - // ; - var rightAlign = stateStyle.alignment == TextAnchor.MiddleRight - || stateStyle.alignment == TextAnchor.UpperRight - || stateStyle.alignment == TextAnchor.LowerRight - ; - // stateStyle.alignment determines position of state element - var state = isEmpty ? DisclosureEmpty : value ? on : off; - var stateSize = stateStyle.CalcSize(value ? on : off); // don't use the empty content to calculate size so titles line up in lists - var x = rightAlign ? rect.xMax - stateSize.x : rect.x; - Rect stateRect = new(x, rect.y, stateSize.x, stateSize.y); - - // layout state before or after following alignment - var labelSize = labelStyle.CalcSize(label); - x = rightAlign ? stateRect.x - stateSize.x - 5 : stateRect.xMax + 5; - Rect labelRect = new(x, rect.y, labelSize.x, labelSize.y); - - stateStyle.Draw(stateRect, state, controlID); - labelStyle.Draw(labelRect, label, controlID); - } - break; - } - return result; - } - - // Button Control - Layout Version - -#if false - static Vector2 cachedArrowSize = new Vector2(0, 0); - public static bool Toggle(GUIContent label, bool value, GUIContent on, GUIContent off, GUIStyle stateStyle, GUIStyle labelStyle, params GUILayoutOption[] options) { - var style = new GUIStyle(labelStyle); - if (cachedArrowSize.x == 0) - cachedArrowSize = style.CalcSize(off); - RectOffset padding = new RectOffset(0, (int)cachedArrowSize.x + 10, 0, 0); - style.padding = padding; - Rect rect = GUILayoutUtility.GetRect(label, style, options); - return Toggle(rect, label, value, on, off, stateStyle, style); - } -#else - public static bool Toggle(GUIContent label, bool value, GUIContent on, GUIContent off, GUIStyle stateStyle, GUIStyle labelStyle, bool isEmpty = false, params GUILayoutOption[] options) { - var state = value ? on : off; - var sStyle = new GUIStyle(stateStyle); - var lStyle = new GUIStyle(labelStyle) { - wordWrap = false - }; - var stateSize = sStyle.CalcSize(state); - lStyle.fixedHeight = stateSize.y - 2; - var padding = new RectOffset(0, (int)stateSize.x + 5, 0, 0); - lStyle.padding = padding; - var rect = GUILayoutUtility.GetRect(label, lStyle, options); -#if false - var labelSize = lStyle.CalcSize(label); - var width = stateSize.x + 10 + stateSize.x; - var height = Mathf.Max(stateSize.y, labelSize.y); - var rect = GUILayoutUtility.GetRect(width, height); - int controlID = GUIUtility.GetControlID(s_ButtonHint, FocusType.Passive, rect); - var eventType = Event.current.GetTypeForControl(controlID); - - Logger.Log($"event: {eventType.ToString()} label: {label.text} w: {width} h: {height} rect: {rect} options: {options.Length}"); -#endif - return Toggle(rect, label, value, isEmpty, on, off, stateStyle, labelStyle); - } -#endif - public static bool Toggle(string label, bool value, string on, string off, GUIStyle stateStyle, GUIStyle labelStyle, params GUILayoutOption[] options) => Toggle(LabelContent(label), value, new GUIContent(on), new GUIContent(off), stateStyle, labelStyle, false, options); - // Disclosure Toggles - public static bool DisclosureToggle(GUIContent label, bool value, bool isEmpty = false, params GUILayoutOption[] options) => Toggle(label, value, DisclosureOn, DisclosureOff, GUI.skin.textArea, GUI.skin.label, isEmpty, options); - public static bool DisclosureToggle(string label, bool value, GUIStyle stateStyle, GUIStyle labelStyle, bool isEmpty = false, params GUILayoutOption[] options) => Toggle(LabelContent(label), value, DisclosureOn, DisclosureOff, stateStyle, labelStyle, isEmpty, options); - public static bool DisclosureToggle(string label, bool value, bool isEmpty = false, params GUILayoutOption[] options) => DisclosureToggle(label, value, GUI.skin.box, GUI.skin.label, isEmpty, options); - // CheckBox - public static bool CheckBox(GUIContent label, bool value, bool isEmpty, params GUILayoutOption[] options) => Toggle(label, value, CheckOn, CheckOff, GUI.skin.textArea, GUI.skin.label, isEmpty, options); - - public static bool CheckBox(string label, bool value, bool isEmpty, GUIStyle style, params GUILayoutOption[] options) => Toggle(LabelContent(label), value, CheckOn, CheckOff, GUI.skin.box, style, isEmpty, options); - - public static bool CheckBox(string label, bool value, bool isEmpty, params GUILayoutOption[] options) => CheckBox(label, value, isEmpty, GUI.skin.label, options); - } -} \ No newline at end of file diff --git a/ModKit/UI/RichText.cs b/ModKit/UI/RichText.cs deleted file mode 100644 index 0aea56185..000000000 --- a/ModKit/UI/RichText.cs +++ /dev/null @@ -1,47 +0,0 @@ -// borrowed shamelessly and enhanced from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/26, which is under the MIT License -using System.Linq; -using System.Text.RegularExpressions; -using UnityEngine; - -namespace ModKit { - public static class RichText { - public static string ToHtmlString(this RGBA color) => $"{color:X}"; - public static string ToAcronym(this string s) => string.Concat(s.Where((c, i) => char.IsUpper(c) || (char.IsLetter(c) && i == 0))); - public static string size(this string s, int size) => _ = $"<size={size}>{s}</size>"; - public static string mainCategory(this string s) => s.size(16).bold(); - - public static string bold(this string s) => _ = $"<b>{s}</b>"; - public static string italic(this string s) => _ = $"<i>{s}</i>"; - public static string color(this string s, string color) => _ = $"<color={color}>{s}</color>"; - public static string color(this string str, RGBA color) => $"<color=#{color:X}>{str}</color>"; - public static string color(this string str, Color32 color) => $"<color=#{color.r:X}{color.g:X}{color.b:X}{color.a:X}>{str}</color>"; - public static string color(this string str, Color color) => $"<color=#{(int)(color.r*256):X}{(int)(color.g*256):X}{(int)(color.b*256):X}{(int)(color.a*256):X}>{str}</color>"; - public static string colorCaps(this string str, RGBA color) => Regex.Replace(str, @"([A-Z])([A-Za-z]+)", - "$1".color(color) + "$2"); - public static string white(this string s) => s.color("white"); - - public static string grey(this string s) => s.color("#A0A0A0FF"); - public static string darkGrey(this string s) => s.color("#505050FF"); - - public static string red(this string s) => s.color("#C04040E0"); - - public static string pink(this string s) => s.color("#FFA0A0E0"); - - public static string green(this string s) => s.color("#00ff00ff"); - - public static string blue(this string s) => s.color("blue"); - - public static string cyan(this string s) => s.color("cyan"); - - public static string magenta(this string s) => s.color("magenta"); - - public static string yellow(this string s) => s.color("yellow"); - - public static string orange(this string s) => s.color("orange"); - - public static string warningLargeRedFormat(this string s) => _ = s.red().size(16).bold(); - - public static string sizePercent(this string s, int percent) => _ = $"<size={percent}%>{s}</size>"; - } -} - diff --git a/ModKit/UI/UI+Browser.cs b/ModKit/UI/UI+Browser.cs deleted file mode 100644 index 37b637dc7..000000000 --- a/ModKit/UI/UI+Browser.cs +++ /dev/null @@ -1,355 +0,0 @@ -using JetBrains.Annotations; -using ModKit.Utility; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using UnityEngine; - -namespace ModKit { - - public static partial class UI { - public class Browser { - - private static readonly Dictionary<object, object> ShowDetails = new(); - public static void ClearDetails() => ShowDetails.Clear(); - public static bool DetailToggle(string title, object key, object target = null, int width = 600) { - var changed = false; - if (target == null) target = key; - var expanded = ShowDetails.ContainsKey(key); - if (DisclosureToggle(title, ref expanded, width)) { - changed = true; - ShowDetails.Clear(); - if (expanded) { - ShowDetails[key] = target; - } - } - return changed; - } - public static bool OnDetailGUI(object key, Action<object> onDetailGUI) { - - ShowDetails.TryGetValue(key, out var target); - if (target != null) { - onDetailGUI(target); - return true; - } - else { - return false; - } - } - } - public class Browser<Definition, Item> : Browser { - // Simple browser that displays a searchable collection of items along with a collection of available definitions. - // It provides a toggle to show the definitions mixed in with the items. - // By default it shows just the title but you can provide an optional RowUI to show more complex row UI including buttons to add and remove items and so forth. This will be layed out after the title - // You an also provide UI that renders children below the row - public ModKitSettings Settings => Mod.ModKitSettings; - private IEnumerable<Definition> _pagedResults = new List<Definition>(); - private Queue<Definition> cachedSearchResults; - public List<Definition> filteredDefinitions; - private Dictionary<Definition, Item> _currentDict; - - private CancellationTokenSource _cancellationTokenSource; - private string _searchText = ""; - public string SearchText => _searchText; - public bool SearchAsYouType; - public bool ShowAll; - public bool IsDetailBrowser; - - public int SearchLimit { - get => IsDetailBrowser ? Settings.browserDetailSearchLimit : Settings.browserSearchLimit; - set { - var oldValue = SearchLimit; - if (IsDetailBrowser) - Settings.browserDetailSearchLimit = value; - else - Settings.browserSearchLimit = value; - if (value != oldValue) ModKitSettings.Save(); - } - } - private int _pageCount; - private int _matchCount; - private int _currentPage = 1; - private bool _searchQueryChanged = true; - public void ResetSearch() { - _searchQueryChanged = true; - ReloadData(); - } - public bool needsReloadData = true; - public void ReloadData() => needsReloadData = true; - private bool _updatePages = false; - private bool _finishedSearch = false; - public bool isSearching = false; - public bool startedLoadingAvailable = false; - public bool availableIsStatic { get; private set; } - private List<Definition> _availableCache; - public void OnShowGUI() => needsReloadData = true; - public Browser(bool searchAsYouType = true, bool availableIsStatic = false, bool isDetailBrowser = false) { - SearchAsYouType = searchAsYouType; - this.availableIsStatic = availableIsStatic; - IsDetailBrowser = isDetailBrowser; - Mod.NotifyOnShowGUI += OnShowGUI; - } - - public void OnGUI( - IEnumerable<Item> current, - Func<IEnumerable<Definition>> available, // Func because available may be slow - Func<Item, Definition> definition, - Func<Definition, string> searchKey, - Func<Definition, string> sortKey, - Action onHeaderGUI = null, - Action<Definition, Item> onRowGUI = null, - Action<Definition, Item> onDetailGUI = null, - int indent = 50, - bool showDiv = true, - bool search = true, - float titleMinWidth = 100, - float titleMaxWidth = 300, - string searchTextPassedFromParent = "", - bool showItemDiv = false - ) { - current ??= new List<Item>(); - List<Definition> definitions = Update(current, available, search, searchKey, sortKey, definition); - if (search || SearchLimit < _matchCount) { - if (search) { - using (HorizontalScope()) { - indent.space(); - ActionTextField(ref _searchText, "searchText", (text) => { - if (!SearchAsYouType) return; - needsReloadData = true; - _searchQueryChanged = true; - }, () => { needsReloadData = true; }, MinWidth(320), AutoWidth()); - 25.space(); - Label("Limit", ExpandWidth(false)); - var searchLimit = SearchLimit; - ActionIntTextField(ref searchLimit, "Search Limit", (i) => { _updatePages = true; }, () => { _updatePages = true; }, width(175)); - if (searchLimit > 1000) { searchLimit = 1000; } - SearchLimit = searchLimit; - 25.space(); - if (DisclosureToggle("Show All".Orange().Bold(), ref ShowAll)) { - startedLoadingAvailable |= ShowAll; - ResetSearch(); - } - 25.space(); - // if (isSearching && false) { // ADDB - Please add a delay timer before this appears because having it flash on very short searches is distracting or let's just get rid of it - // Label("Searching...", AutoWidth()); - // 25.space(); - // } - } - } - else { - if (_searchText != searchTextPassedFromParent) { - needsReloadData = true; - _searchText = searchTextPassedFromParent; - if (_searchText == null) { - _searchText = ""; - } - } - } - using (HorizontalScope()) { - if (search) { - space(indent); - ActionButton("Search", () => { needsReloadData = true; }, AutoWidth()); - } - space(25); - if (_matchCount > 0 || _searchText.Length > 0) { - var matchesText = "Matches: ".Green().Bold() + $"{_matchCount}".Orange().Bold(); - if (_matchCount > SearchLimit) { matchesText += " => ".Cyan() + $"{SearchLimit}".Cyan().Bold(); } - - Label(matchesText, ExpandWidth(false)); - } - if (_matchCount > SearchLimit) { - string pageLabel = "Page: ".orange() + _currentPage.ToString().cyan() + " / " + _pageCount.ToString().cyan(); - 25.space(); - Label(pageLabel, ExpandWidth(false)); - ActionButton("-", () => { - if (_currentPage >= 1) { - if (_currentPage == 1) { - _currentPage = _pageCount; - } - else { - _currentPage -= 1; - } - _updatePages = true; - } - }, AutoWidth()); - ActionButton("+", () => { - if (_currentPage > _pageCount) return; - if (_currentPage == _pageCount) { - _currentPage = 1; - } - else { - _currentPage += 1; - } - _updatePages = true; - }, AutoWidth()); - } - } - } - if (showDiv) - Div(indent); - if (onHeaderGUI != null) { - using (HorizontalScope(AutoWidth())) { - space(indent); - onHeaderGUI(); - } - } - foreach (var def in definitions) { - if (showItemDiv) { - Div(indent); - } - _currentDict.TryGetValue(def, out var item); - if (onRowGUI != null) { - using (HorizontalScope(AutoWidth())) { - space(indent); - onRowGUI(def, item); - } - } - onDetailGUI?.Invoke(def, item); - } - } - - private List<Definition> Update(IEnumerable<Item> current, Func<IEnumerable<Definition>> available, bool search, - Func<Definition, string> searchKey, Func<Definition, string> sortKey, Func<Item, Definition> definition) { - if (Event.current.type == EventType.Layout) { - if (startedLoadingAvailable) { - _availableCache = available()?.ToList(); - if (_availableCache?.Count() > 0) { - startedLoadingAvailable = false; - needsReloadData = true; - if (!availableIsStatic) { - _availableCache = null; - } - } - } - if (_finishedSearch || isSearching) { - bool nothingToSearch = (!ShowAll && current.Count() == 0) || (ShowAll && (availableIsStatic ? _availableCache : available()).Count() == 0); - // If the search has at least one result - if ((cachedSearchResults.Count > 0 || nothingToSearch) && (_searchQueryChanged || _finishedSearch)) { - if (_finishedSearch && !_searchQueryChanged) { - filteredDefinitions = new List<Definition>(); - } - // Lock the search results - lock (cachedSearchResults) { - // Go through every item in the queue - while (cachedSearchResults.Count > 0) { - // Add the item into the OrderedSet filteredDefinitions - filteredDefinitions.Add(cachedSearchResults.Dequeue()); - } - } - filteredDefinitions.Sort(Comparer<Definition>.Create((x, y) => sortKey(x).CompareTo(sortKey(y)))); - } - _matchCount = filteredDefinitions.Count; - UpdatePageCount(); - UpdatePaginatedResults(); - if (_finishedSearch) { - isSearching = false; - _updatePages = false; - _finishedSearch = false; - _searchQueryChanged = false; - cachedSearchResults = null; - } - } - if (needsReloadData) { - _currentDict = current.ToDictionaryIgnoringDuplicates(definition, c => c); - IEnumerable<Definition> definitions; - if (ShowAll) { - if (startedLoadingAvailable) { - definitions = _currentDict.Keys.ToList(); - } - else if (availableIsStatic) { - definitions = _availableCache; - } - else { - definitions = available(); - } - } - else { - definitions = _currentDict.Keys.ToList(); - } - if (!isSearching) { - _cancellationTokenSource = new(); - Task.Run(() => UpdateSearchResults(_searchText, definitions, searchKey, sortKey, search)); - if (_searchQueryChanged) { - filteredDefinitions = new List<Definition>(); - } - isSearching = true; - needsReloadData = false; - } - else { - _cancellationTokenSource.Cancel(); - } - } - if (_updatePages) { - _updatePages = false; - UpdatePageCount(); - UpdatePaginatedResults(); - } - } - return _pagedResults?.ToList(); - } - - [UsedImplicitly] - public void UpdateSearchResults(string searchTextParam, - IEnumerable<Definition> definitions, - Func<Definition, string> searchKey, - Func<Definition, string> sortKey, - bool search - ) { - if (definitions == null) { - return; - } - cachedSearchResults = new(); - var terms = searchTextParam.Split(' ').Select(s => s.ToLower()).ToHashSet(); - if (search) { - foreach (var def in definitions) { - if (_cancellationTokenSource.IsCancellationRequested) { - isSearching = false; - return; - } - if (def.GetType().ToString().Contains(searchTextParam) - ) { - lock (cachedSearchResults) { - cachedSearchResults.Enqueue(def); - } - } - else if (searchKey != null) { - var text = searchKey(def).ToLower(); - if (terms.All(term => text.Matches(term))) { - lock (cachedSearchResults) { - cachedSearchResults.Enqueue(def); - } - } - } - } - } - else { - lock (cachedSearchResults) { - cachedSearchResults = new Queue<Definition>(definitions); - } - } - _finishedSearch = true; - } - public void UpdatePageCount() { - if (SearchLimit > 0) { - _pageCount = (int)Math.Ceiling((double)_matchCount / SearchLimit); - _currentPage = Math.Min(_currentPage, _pageCount); - _currentPage = Math.Max(1, _currentPage); - } - else { - _pageCount = 1; - _currentPage = 1; - } - } - public void UpdatePaginatedResults() { - var limit = SearchLimit; - var count = _matchCount; - var offset = Math.Min(count, (_currentPage - 1) * limit); - limit = Math.Min(limit, Math.Max(count, count - limit)); - Mod.Trace($"{_currentPage} / {_pageCount} count: {count} => offset: {offset} limit: {limit} "); - _pagedResults = filteredDefinitions.Skip(offset).Take(limit).ToArray(); - } - } - } -} \ No newline at end of file diff --git a/ModKit/Utility/Dictionary/DoubleDictionary.cs b/ModKit/Utility/Dictionary/DoubleDictionary.cs deleted file mode 100644 index eacffe0ef..000000000 --- a/ModKit/Utility/Dictionary/DoubleDictionary.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace ModKit.Utility { - public class DoubleDictionary<TKey1, TKey2, TValue> { - private readonly Dictionary<TKey1, Dictionary<TKey2, TValue>> _dictionary - = new(); - - public TValue this[TKey1 key1, TKey2 key2] { - get { - return _dictionary[key1][key2]; - } - set { - _dictionary[key1][key2] = value; - } - } - - public void Add(TKey1 key1, TKey2 key2, TValue value) { - if (!_dictionary.TryGetValue(key1, out var innerDictionary)) { - _dictionary.Add(key1, innerDictionary = new Dictionary<TKey2, TValue>()); - } - innerDictionary.Add(key2, value); - } - - public void Clear() => _dictionary.Clear(); - - public bool TryGetValue(TKey1 key1, TKey2 key2, out TValue value) { - if (!_dictionary.TryGetValue(key1, out var innerDictionary)) { - lock (_dictionary) { - if (!_dictionary.TryGetValue(key1, out innerDictionary)) { - _dictionary.Add(key1, innerDictionary = new Dictionary<TKey2, TValue>()); - } - } - } - return innerDictionary.TryGetValue(key2, out value); - } - - public TValue GetValueOrDefault(TKey1 key1, TKey2 key2, Func<TValue> getDefault) { - lock (_dictionary) { - if (!_dictionary.TryGetValue(key1, out var innerDictionary)) { - _dictionary.Add(key1, innerDictionary = new Dictionary<TKey2, TValue>()); - } - if (!innerDictionary.TryGetValue(key2, out var value)) { - innerDictionary.Add(key2, value = getDefault()); - } - return value; - } - } - } -} diff --git a/ModKit/Utility/Dictionary/TripleDictionary.cs b/ModKit/Utility/Dictionary/TripleDictionary.cs deleted file mode 100644 index dad6665d0..000000000 --- a/ModKit/Utility/Dictionary/TripleDictionary.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace ModKit.Utility { - public class TripleDictionary<TKey1, TKey2, TKey3, TValue> { - private readonly Dictionary<TKey1, Dictionary<TKey2, Dictionary<TKey3, TValue>>> _dictionary - = new(); - - public TValue this[TKey1 key1, TKey2 key2, TKey3 key3] { - get { - return _dictionary[key1][key2][key3]; - } - set { - _dictionary[key1][key2][key3] = value; - } - } - - public void Add(TKey1 key1, TKey2 key2, TKey3 key3, TValue value) { - if (!_dictionary.TryGetValue(key1, out var innerDictionary1)) { - _dictionary.Add(key1, innerDictionary1 = new Dictionary<TKey2, Dictionary<TKey3, TValue>>()); - } - if (!innerDictionary1.TryGetValue(key2, out var innerDictionary2)) { - innerDictionary1.Add(key2, innerDictionary2 = new Dictionary<TKey3, TValue>()); - } - innerDictionary2.Add(key3, value); - } - - public void Clear() => _dictionary.Clear(); - - public bool TryGetValue(TKey1 key1, TKey2 key2, TKey3 key3, out TValue value) { - if (!_dictionary.TryGetValue(key1, out var innerDictionary1)) { - _dictionary.Add(key1, innerDictionary1 = new Dictionary<TKey2, Dictionary<TKey3, TValue>>()); - } - if (!innerDictionary1.TryGetValue(key2, out var innerDictionary2)) { - innerDictionary1.Add(key2, innerDictionary2 = new Dictionary<TKey3, TValue>()); - } - return innerDictionary2.TryGetValue(key3, out value); - } - - public TValue GetValueOrDefault(TKey1 key1, TKey2 key2, TKey3 key3, Func<TValue> getDefault) { - if (!_dictionary.TryGetValue(key1, out var innerDictionary1)) { - _dictionary.Add(key1, innerDictionary1 = new Dictionary<TKey2, Dictionary<TKey3, TValue>>()); - } - if (!innerDictionary1.TryGetValue(key2, out var innerDictionary2)) { - innerDictionary1.Add(key2, innerDictionary2 = new Dictionary<TKey3, TValue>()); - } - if (!innerDictionary2.TryGetValue(key3, out var value)) { - innerDictionary2.Add(key3, value = getDefault()); - } - return value; - } - } -} diff --git a/ModKit/Utility/EmbeddedResourceUtils.cs b/ModKit/Utility/EmbeddedResourceUtils.cs deleted file mode 100644 index ca07f03d3..000000000 --- a/ModKit/Utility/EmbeddedResourceUtils.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; - -namespace ModKit { - public static class EmbeddedResourceUtils { - public static Stream StreamForResourceFile(string endingFileName) { - var assembly = Assembly.GetExecutingAssembly(); - var manifestResourceNames = assembly.GetManifestResourceNames(); - - foreach (var resourceName in manifestResourceNames) { - var fileNameFromResourceName = _GetFileNameFromResourceName(resourceName); - if (!fileNameFromResourceName.EndsWith(endingFileName)) { - continue; - } - - using var manifestResourceStream = assembly.GetManifestResourceStream(resourceName); - if (manifestResourceStream == null) { - continue; - } - return manifestResourceStream; - } - return null; - } - - // https://stackoverflow.com/a/32176198/3764804 - private static string _GetFileNameFromResourceName(string resourceName) { - var stringBuilder = new StringBuilder(); - var escapeDot = false; - var haveExtension = false; - - for (var resourceNameIndex = resourceName.Length - 1; - resourceNameIndex >= 0; - resourceNameIndex--) { - if (resourceName[resourceNameIndex] == '_') { - escapeDot = true; - continue; - } - - if (resourceName[resourceNameIndex] == '.') { - if (!escapeDot) { - if (haveExtension) { - stringBuilder.Append('\\'); - continue; - } - - haveExtension = true; - } - } - else { - escapeDot = false; - } - - stringBuilder.Append(resourceName[resourceNameIndex]); - } - - var fileName = Path.GetDirectoryName(stringBuilder.ToString()); - return fileName == null ? null : new string(fileName.Reverse().ToArray()); - } - } -} \ No newline at end of file diff --git a/ModKit/Utility/Extensions/RichTextExtensions.cs b/ModKit/Utility/Extensions/RichTextExtensions.cs deleted file mode 100644 index 08dd693ec..000000000 --- a/ModKit/Utility/Extensions/RichTextExtensions.cs +++ /dev/null @@ -1,166 +0,0 @@ -//#define MARK_DEBUG -using System; -using System.Text; -using System.Text.RegularExpressions; -using UnityEngine; - -namespace ModKit.Utility { - public static class StringExtensions { - public static bool Matches(this string source, string query) { - if (source == null || query == null) - return false; -#if false - return source.IndexOf(other, 0, StringComparison.InvariantCulture) != -1; -#else - return source.IndexOf(query, 0, StringComparison.InvariantCultureIgnoreCase) != -1; -#endif - } - public static bool Matches(this string source, string[] queryTerms) { - var matchCount = 0; - foreach (var term in queryTerms) { - if (source.IndexOf(term, 0, StringComparison.InvariantCultureIgnoreCase) != -1) - matchCount += 1; - } - return matchCount >= queryTerms.Length; - } - public static string MarkedSubstringNoHTML(this string source, string sub) { - if (string.IsNullOrWhiteSpace(source) || string.IsNullOrWhiteSpace(sub)) - return source; - var index = source.IndexOf(sub, StringComparison.InvariantCultureIgnoreCase); - if (index != -1) { - var substr = source.Substring(index, sub.Length); - source = source.Replace(substr, substr.yellow().Bold()); - } - return source; - } - public static string MarkedSubstring(this string source, string[] queryTerms) { - foreach (var term in queryTerms) { - source = source.MarkedSubstring(term); - } - return source; - } - public static string MarkedSubstring(this string source, string query) { - if (string.IsNullOrWhiteSpace(source) || string.IsNullOrWhiteSpace(query)) - return source; - var htmlStart = source.IndexOf('<'); - if (htmlStart == -1) - return source.MarkedSubstringNoHTML(query); - var result = new StringBuilder(); - var len = source.Length; - var segment = source.Substring(0, htmlStart); - #if MARK_DEBUG - bool detail = source.Contains("More"); - if (detail) Mod.Debug($"{query} in {source}"); - #endif - var cnt = 0; - segment = segment.MarkedSubstringNoHTML(query); - #if MARK_DEBUG - if (detail) Mod.Log($"{(cnt++)} - segment - (0, {htmlStart}) {segment} "); - #endif - result.Append(segment); - var htmlEnd = source.IndexOf('>', htmlStart); - while (htmlStart != -1 && htmlEnd != -1) { - var tag = source.Substring(htmlStart, htmlEnd + 1 - htmlStart); - #if MARK_DEBUG - if (detail) Mod.Log($"{(cnt++)} - tag - ({htmlStart}, {htmlEnd}) {tag} "); - #endif - result.Append(tag); - htmlStart = source.IndexOf('<', htmlEnd); - if (htmlStart != -1) { - segment = source.Substring(htmlEnd + 1, htmlStart - htmlEnd - 1); - segment = segment.MarkedSubstringNoHTML(query); - #if MARK_DEBUG - if (detail) Mod.Log($"{(cnt++)} - segment - ({htmlEnd+1}, {htmlStart}) {segment} "); - #endif - result.Append(segment); - htmlEnd = source.IndexOf('>', htmlStart); - } - } - if (htmlStart != -1) { - var malformedTag = source.Substring(htmlStart, len + 1 - htmlStart); - result.Append(malformedTag); - #if MARK_DEBUG - if (detail) Mod.Log($"{(cnt++)} - badtag - ({htmlEnd + 1}, {htmlStart}) {malformedTag} "); - #endif - } - else if (htmlEnd < len) { - segment = source.Substring(htmlEnd + 1, len - htmlEnd - 1); - #if MARK_DEBUG - if (detail) Mod.Log($"{(cnt++)} - segment - ({htmlEnd + 1}, {len}) {segment} "); - #endif - result.Append(segment.MarkedSubstringNoHTML(query)); - } - return result.ToString(); - } - public static string Repeat(this string s, int n) { - if (n < 0 || s == null || s.Length == 0) - return s; - return new StringBuilder(s.Length * n).Insert(0, s, n).ToString(); - } - public static string Indent(this string s, int n) => " ".Repeat(n) + s; - - } - - public static class RichTextExtensions { - // https://docs.unity3d.com/Manual/StyledText.html - - public enum RGBA : uint { - aqua = 0x00ffffff, - blue = 0x8080ffff, - brown = 0xC09050ff, //0xa52a2aff, - cyan = 0x00ffffff, - darkblue = 0x0000a0ff, - fuchsia = 0xff40ffff, - green = 0x40C040ff, - lightblue = 0xd8e6ff, - lime = 0x40ff40ff, - magenta = 0xff40ffff, - maroon = 0xFF6060ff, - navy = 0x000080ff, - olive = 0xB0B000ff, - orange = 0xffa500ff, // 0xffa500ff, - purple = 0xC060F0ff, - red = 0xFF4040ff, - teal = 0x80f0c0ff, - yellow = 0xffff00ff, - black = 0x000000ff, - darkgrey = 0x808080ff, - silver = 0xD0D0D0ff, - grey = 0xC0C0C0ff, - lightgrey = 0xE8E8E8ff, - white = 0xffffffff, - } - - public static string ToHtmlString(this RGBA color) => $"{color:X}"; - - public static string Bold(this string str) => $"<b>{str}</b>"; - - public static string Color(this string str, Color color) => $"<color=#{ColorUtility.ToHtmlStringRGBA(color)}>{str}</color>"; - - public static string Color(this string str, RGBA color) => $"<color=#{color:X}>{str}</color>"; - - public static string Color(this string str, string rrggbbaa) => $"<color=#{rrggbbaa}>{str}</color>"; - - public static string color(this string s, string color) => _ = $"<color={color}>{s}</color>"; - public static string White(this string s) => _ = s.color("white"); - public static string Grey(this string s) => _ = s.color("#A0A0A0FF"); - public static string Red(this string s) => _ = s.color("#C04040E0"); - public static string Pink(this string s) => _ = s.color("#FFA0A0E0"); - public static string Green(this string s) => _ = s.color("#00ff00ff"); - public static string Blue(this string s) => _ = s.color("blue"); - public static string Cyan(this string s) => _ = s.color("cyan"); - public static string Magenta(this string s) => _ = s.color("magenta"); - public static string Yellow(this string s) => _ = s.color("yellow"); - public static string Orange(this string s) => _ = s.color("orange"); - - - - public static string Italic(this string str) => $"<i>{str}</i>"; - - public static string ToSentence(this string str) => Regex.Replace(str, @"((?<=\p{Ll})\p{Lu})|\p{Lu}(?=\p{Ll})", " $0").TrimStart();//return string.Concat(str.Select(c => char.IsUpper(c) ? " " + c : c.ToString())).TrimStart(' '); - - public static string Size(this string str, int size) => $"<size={size}>{str}</size>"; - - public static string SizePercent(this string str, int percent) => $"<size={percent}%>{str}</size>"; - } -} diff --git a/ModKit/Utility/Extensions/UnityExtensions.cs b/ModKit/Utility/Extensions/UnityExtensions.cs deleted file mode 100644 index 82515efd7..000000000 --- a/ModKit/Utility/Extensions/UnityExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -using UnityEngine; - -namespace ModKit.Utility { - public static class UnityExtensions { - private static void SafeDestroyInternal(GameObject obj) { - obj.transform.SetParent(null, false); - obj.SetActive(false); - Object.Destroy(obj); - } - - public static void SafeDestroy(this GameObject obj) { - if (obj) { - SafeDestroyInternal(obj); - } - } - - public static void SafeDestroy(this Component obj) { - if (obj) { - SafeDestroyInternal(obj.gameObject); - } - } - } -} diff --git a/ModKit/Utility/GUISubScope.cs b/ModKit/Utility/GUISubScope.cs deleted file mode 100644 index fbbdaa697..000000000 --- a/ModKit/Utility/GUISubScope.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using UnityEngine; - -namespace ModKit.Utility { - public class GUISubScope : IDisposable { - public GUISubScope() : this(null) { } - - public GUISubScope(string subtitle) { - if (!string.IsNullOrEmpty(subtitle)) - GUILayout.Label(subtitle.Bold()); - GUILayout.BeginHorizontal(); - GUILayout.Space(10f); - GUILayout.BeginVertical(); - } - - public void Dispose() { - GUILayout.EndVertical(); - GUILayout.EndHorizontal(); - } - } -} diff --git a/ModKit/Utility/Search.cs b/ModKit/Utility/Search.cs deleted file mode 100644 index 0b898146f..000000000 --- a/ModKit/Utility/Search.cs +++ /dev/null @@ -1,220 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace ModKit { - - using System; - using System.Collections.Generic; - using System.Reflection; - using System.Linq; - - namespace BlueprintExplorer { - public interface ISearchable { - Dictionary<string, Func<string>> Providers { get; } // named functions to extract different text out of the target - Dictionary<string, MatchResult> Matches { get; set; } // place to store search results - } - public static class MatchHelpers { - public static bool HasMatches(this ISearchable searchable, float scoreThreshold = 10) => searchable.Matches == null || searchable.Matches.Where(m => m.Value.IsMatch && m.Value.Score >= scoreThreshold).Any(); - } - public class MatchResult { - public struct Span { - public UInt16 From; - public UInt16 Length; - - public Span(int start, int length = -1) { - From = (ushort)start; - Length = (ushort)length; - } - public void End(int end) { - Length = (UInt16)(end - From); - } - } - - public ISearchable Target; - public string Key; - public string Text; - public MatchQuery Context; - - public bool IsMatch => TotalMatched > 0; - public List<Span> spans = new(); - public int MatchedCharacters; - public int BestRun; - public int SingleRuns; - public int TotalMatched; - public float Penalty; - public float Bonus; - public float MatchRatio => TotalMatched / (float)Context.SearchText.Length; - public float TargetRatio; - public int GoodRuns => spans.Count(x => x.Length > 2); //> 2); - - public float Score => (TargetRatio * MatchRatio * 1.0f) + (BestRun * 4) + (GoodRuns * 2) - Penalty + Bonus; - - public MatchResult(ISearchable target, string key, string text, MatchQuery context) { - Target = target; - Key = key; - Text = text; - this.Context = context; - } - public void AddSpan(Span span) { - spans.Add(span); - - //update some stats that get used for scoring - if (span.Length > BestRun) - BestRun = span.Length; - if (span.Length == 1) - SingleRuns++; - TotalMatched += span.Length; - } - } - public class MatchQuery { - public string SearchText; // general search text - public Dictionary<string, string> RestrictedSearchTexts; // restricted to certain provider keys - private MatchResult Match(string searchText, ISearchable searchable, string key, string text) { - var result = new MatchResult(searchable, key, text, this); - var index = text.IndexOf(searchText); - if (index >= 0) { - var span = new MatchResult.Span(index, searchText.Length); - result.AddSpan(span); - result.TargetRatio = result.TotalMatched / (float)text.Length; - } - return result; - } - private MatchResult FuzzyMatch(ISearchable searchable, string key, string text) { - var result = new MatchResult(searchable, key, text, this); - - var searchTextIndex = 0; - var targetIndex = -1; - - var searchText = result.Context.SearchText; - var target = result.Text; - - // find a common prefix if any, so n:cat h:catsgrace is better than n:cat h:blahcatsgrace - targetIndex = target.IndexOf(searchText[searchTextIndex]); - if (targetIndex == 0) - result.Bonus = 2.0f; - - // penalise matches that don't have a common prefix, while increasing searchTextIndex and targetIndex to the first match, so: - // n:bOb h:hellOworldbob - // ^ ^ - while (targetIndex == -1 && searchTextIndex < searchText.Length) { - if (searchTextIndex == 0) - result.Penalty = 2; - else - result.Penalty += result.Penalty * .5f; - targetIndex = target.IndexOf(searchText[searchTextIndex]); - searchTextIndex++; - } - - // continue to match the next searchTextIndex greedily in target - while (searchTextIndex < searchText.Length) { - // find the next point in target that matches searchIndex: - // n:bOb h:helloworldBob - // ^ ^ - targetIndex = target.IndexOf(searchText[searchTextIndex], targetIndex); - if (targetIndex == -1) - break; - - //continue matching while both are in sync - var span = new MatchResult.Span(targetIndex); - while (targetIndex < target.Length && searchTextIndex < searchText.Length && searchText[searchTextIndex] == target[targetIndex]) { - //if this span is rooted at the start of the word give a bonus because start is most importatn - if (span.From == 0 && searchTextIndex > 0) - result.Bonus += result.Bonus; - searchTextIndex++; - targetIndex++; - } - - //record the end of the span - span.End(targetIndex); - result.AddSpan(span); - } - result.TargetRatio = result.TotalMatched / (float)target.Length; - return result; - } - - public MatchQuery(string queryText) { - var unrestricted = new List<string>(); - RestrictedSearchTexts = new(); - var terms = queryText.Split(' '); - foreach (var term in terms) { - if (term.Contains(':')) { - var pair = term.Split(':'); - RestrictedSearchTexts[pair[0]] = pair[1]; - } - else - unrestricted.Add(term); - } - SearchText = string.Join(" ", unrestricted); - } - - public ISearchable Evaluate(ISearchable searchable) { - if (SearchText?.Length > 0 || RestrictedSearchTexts.Count > 0) { - searchable.Matches = new(); - foreach (var provider in searchable.Providers) { - var key = provider.Key; - var text = provider.Value(); - var foundRestricted = false; - foreach (var entry in RestrictedSearchTexts) { - if (key.StartsWith(entry.Key)) { - searchable.Matches[key] = Match(entry.Value, searchable, key, text); - foundRestricted = true; - break; - } - } - if (!foundRestricted && SearchText?.Length > 0) - searchable.Matches[key] = FuzzyMatch(searchable, key, text); - } - } - else - searchable.Matches = null; - return searchable; - } - public void UpdateSearchResults(IEnumerable<ISearchable> searchables) { - foreach (var searchable in searchables) - this.Evaluate(searchable); - } - } - -#if false - public static class FuzzyMatcher { - public static IEnumerable<T> FuzzyMatch<T>(this IEnumerable<(T, string)> input, string needle, float scoreThreshold = 10) { - var result = new MatchQuery<T>(needle.ToLower()); - return input.Select(i => result.Match(i.Item2, i.Item1)).Where(match => match.Score > scoreThreshold).OrderByDescending(match => match.Score).Select(m => m.Handle); - } - /// <summary> - /// Fuzzy Match all items in input against the needle - /// </summary> - /// <typeparam name="T"></typeparam> - /// <param name="input">input items</param> - /// <param name="haystack">function to get the 'string' key of an input item to match against</param> - /// <param name="needle">value to match against</param> - /// <param name="scoreThreshold">discard all results under this score (default: 10)</param> - /// <returns>An IEnumerable<out T> that contains elements from the input sequence that score above the threshold, sorted by score</out></returns> - public static IEnumerable<T> FuzzyMatch<T>(this IEnumerable<T> input, Func<T, string> haystack, string needle, float scoreThreshold = 10) { - return input.Select(i => (i, haystack(i))).FuzzyMatch(needle, scoreThreshold); - } - - - public class ExampleType { - int Foo; - string Bar; - - public string Name => $"{Bar}.{Foo}"; - } - - public static void Example() { - //Assume some input list (or enumerable) - List<ExampleType> inputList = new(); - - //Get an enumerable of all the matches (above a score threshold, default = 10) - var matches = inputList.FuzzyMatch(type => type.Name, "string_to_search"); - - //Get top 20 results - var top20 = matches.Take(20).ToList(); - } - - } -#endif - } -} diff --git a/ModKit/Utility/Translator.cs b/ModKit/Utility/Translator.cs deleted file mode 100644 index f3f2687a2..000000000 --- a/ModKit/Utility/Translator.cs +++ /dev/null @@ -1,178 +0,0 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; - -namespace ModKit { - public static class Translater { - public static Dictionary<string, string> cachedTranslations = new(); - -#if true - public static async Task MassTranslate(List<string> strings) { - using (var client = new HttpClient()) { - var fromLanguage = "ru";//Russian - var toLanguage = "en";//English - var text = String.Join(" | ", strings); - Mod.Log($"{text}"); - var url = $"https://translate.googleapis.com/translate_a/single?client=gtx&sl={fromLanguage}&tl={toLanguage}&dt=t"; - // Serialize our concrete class into a JSON String - var stringPayload = JsonConvert.SerializeObject(text); - var content = new StringContent(stringPayload, Encoding.UTF8, "application/json"); - var response = await client.PostAsync(url, content); - var result = await response.Content.ReadAsStringAsync(); - // how do I best ask for this ^^^ and parse it out? - // store the text => translation in the cachedTranslations dict - } - } - -#else - - private const int maxQuerySize = 2000; - public static async Task MassTranslate(List<string> strings) { - using (var client = new HttpClient()) { - string accum = ""; - foreach (var text in strings) { - if (accum.Length + text.Length < maxQuerySize - 4) { - accum += $"\\{text}//"; - } - else { - RawMassTranslate(accum); - accum = ""; - } - } - if (accum.Length > 0) { - RawMassTranslate(accum); - accum = ""; - } - } - } - private static void RawMassTranslate(string text) { - var fromLanguage = "ru";//Russian - var toLanguage = "en";//English - var url = $"https://translate.googleapis.com/translate_a/single?client=gtx&sl={fromLanguage}&tl={toLanguage}&dt=t&q={Uri.EscapeDataString(text)}"; - var webClient = new WebClient { - Encoding = System.Text.Encoding.UTF8 - }; - try { - var result = webClient.DownloadString(url); - Mod.Log($"response: {result}"); - result = result.Substring(4, result.IndexOf("\"", 4, StringComparison.Ordinal) - 4); - - cachedTranslations[text] = result; - } - catch (Exception e) { - Mod.Log(url); - Mod.Error(e); - } - } -#endif - public static String Translate(this string text) { - if (cachedTranslations.TryGetValue(text, out var value)) - return value; -#if true - var fromLanguage = "ru";//Russian - var toLanguage = "en";//English - var url = $"https://translate.googleapis.com/translate_a/single?client=gtx&sl={fromLanguage}&tl={toLanguage}&dt=t&q={Uri.EscapeDataString(text)}"; - var webClient = new WebClient { - Encoding = Encoding.UTF8 - }; - try { - var result = webClient.DownloadString(url); - result = result.Substring(4, result.IndexOf("\"", 4, StringComparison.Ordinal) - 4); - cachedTranslations[text] = result; - return result; - } - catch (Exception e) { - Mod.Log(url); - Mod.Error(e); - return text; - } -#else - var toLanguage = "en"; - var fromLanguage = "ru"; - var uriBuilder = new UriBuilder { - Scheme = "https", - Host = "translate.googleapis.com", - Path = "translate_a/single", - Query = $"client=gtx&sl={fromLanguage}&tl={toLanguage}&dt=t&q={Uri.EscapeDataString(text)}" - }; - var request = (HttpWebRequest)WebRequest.Create(uriBuilder.Uri); - - var response = (HttpWebResponse)request.GetResponse(); - Mod.Log($"response: {response}"); - var translation = new StreamReader(response.GetResponseStream()).ReadToEnd(); - Mod.Log($"{text} => {translation}"); - return translation; -#endif - -#if true -#else - var toLanguage = "en"; - var fromLanguage = "ru"; - var inputText = "Все люди рождаются свободными и равными в своем достоинстве и правах. Они наделены разумом и совестью и должны поступать в отношении друг друга в духе братства."; - - var uriBuilder = new UriBuilder - { - Scheme = "https", - Host = "translate.googleapis.com", - Path = "translate_a/single", - Query = $"client=gtx&sl={fromLanguage}&tl={toLanguage}&dt=t&q={Uri.EscapeDataString(inputText)}" - }; - var request = (HttpWebRequest)WebRequest.Create(uriBuilder.Uri); - - var response = (HttpWebResponse)request.GetResponse(); - - var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd(); - - Console.WriteLine(responseString); - - - ----------------- - var fromLanguage = "ru";//Russian - var toLanguage = "en";//English - var url = $"https://translate.googleapis.com/translate_a/single?client=gtx&sl={fromLanguage}&tl={toLanguage}&dt=t&q={Uri.EscapeDataString(text)}"; - var webClient = new WebClient { - Encoding = System.Text.Encoding.UTF8 - }; - try { - var result = webClient.DownloadString(url); - result = result.Substring(4, result.IndexOf("\"", 4, StringComparison.Ordinal) - 4); - cachedTranslations[text] = result; - return result; - } - catch (Exception e) { - Mod.Log(url); - Mod.Error(e); - return text; - } - - ----------------- - - var request = (HttpWebRequest)WebRequest.Create("http://www.example.com/recepticle.aspx"); - -var response = (HttpWebResponse)request.GetResponse(); - -var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd(); - - - var toLanguage = "en";//English - var fromLanguage = "ru";//Russian - var url = $"https://translate.googleapis.com/translate_a/single?client=gtx&sl={fromLanguage}&tl={toLanguage}&dt=t&q={HttpUtility.UrlEncode(text)}"; - var webClient = new WebClient { - Encoding = System.Text.Encoding.UTF8 - }; - var result = webClient.DownloadString(url); - try { - result = result.Substring(4, result.IndexOf("\"", 4, StringComparison.Ordinal) - 4); - return result; - } - catch { - return "Error"; - } -#endif - } - } -} diff --git a/ModKit/Utility/Utilities.cs b/ModKit/Utility/Utilities.cs deleted file mode 100644 index 9924decfa..000000000 --- a/ModKit/Utility/Utilities.cs +++ /dev/null @@ -1,327 +0,0 @@ -// Copyright < 2021 > Narria (github user Cabarius) - License: MIT -using HarmonyLib; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; - -namespace ModKit -{ - public static class Utilities - { - public static TValue GetValueOrDefault<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, TValue defaultValue = default) - { - if (dictionary == null) { throw new ArgumentNullException(nameof(dictionary)); } // using C# 6 - if (key == null) { throw new ArgumentNullException(nameof(key)); } // using C# 6 - - return dictionary.TryGetValue(key, out var value) ? value : defaultValue; - } - public static Dictionary<TKey, TElement> ToDictionaryIgnoringDuplicates<TSource, TKey, TElement>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey> comparer = null) { - if (source == null) - throw new ArgumentException("source"); - if (keySelector == null) - throw new ArgumentException("keySelector"); - if (elementSelector == null) - throw new ArgumentException("elementSelector"); - Dictionary<TKey, TElement> d = new Dictionary<TKey, TElement>(comparer); - foreach (TSource element in source) { - if (!d.ContainsKey(keySelector(element))) - d.Add(keySelector(element), elementSelector(element)); - } - return d; - } - public static object GetPropValue(this object obj, string name) - { - foreach (var part in name.Split('.')) - { - if (obj == null) { return null; } - - var type = obj.GetType(); - var info = type.GetProperty(part); - if (info == null) { return null; } - - obj = info.GetValue(obj, null); - } - return obj; - } - public static T GetPropValue<T>(this object obj, string name) - { - var retval = GetPropValue(obj, name); - if (retval == null) { return default; } - // throws InvalidCastException if types are incompatible - return (T)retval; - } - public static object SetPropValue(this object obj, string name, object value) - { - var parts = name.Split('.'); - var final = parts.Last(); - if (final == null) - return null; - foreach (var part in parts) - { - if (obj == null) { return null; } - var type = obj.GetType(); - var info = type.GetProperty(part); - if (info == null) { return null; } - if (part == final) - { - info.SetValue(obj, value); - return value; - } - else - { - obj = info.GetValue(obj, null); - } - } - return null; - } - public static T SetPropValue<T>(this object obj, string name, T value) - { - object retval = SetPropValue(obj, name, value); - if (retval == null) { return default; } - // throws InvalidCastException if types are incompatible - return (T)retval; - } - public static string StripHTML(this string s) => Regex.Replace(s, "<.*?>", string.Empty); - public static string UnityRichTextToHtml(string s) - { - s = s.Replace("<color=", "<font color="); - s = s.Replace("</color>", "</font>"); - s = s.Replace("<size=", "<size size="); - s = s.Replace("</size>", "</font>"); - s += "<br/>"; - - return s; - } - public static string MergeSpaces(this string str, bool trim = false) { - if (str == null) - return null; - else { - StringBuilder stringBuilder = new StringBuilder(str.Length); - - int i = 0; - foreach (char c in str) { - if (c != ' ' || i == 0 || str[i - 1] != ' ') - stringBuilder.Append(c); - i++; - } - if (trim) - return stringBuilder.ToString().Trim(); - else - return stringBuilder.ToString(); - } - } - public static string ReplaceLastOccurrence(this string Source, string Find, string Replace) - { - int place = Source.LastIndexOf(Find); - - if (place == -1) - return Source; - - string result = Source.Remove(place, Find.Length).Insert(place, Replace); - return result; - } - public static string[] getObjectInfo(object o) - { - - var fields = ""; - foreach (var field in Traverse.Create(o).Fields()) - { - fields = fields + field + ", "; - } - var methods = ""; - foreach (var method in Traverse.Create(o).Methods()) - { - methods = methods + method + ", "; - } - var properties = ""; - foreach (var property in Traverse.Create(o).Properties()) - { - properties = properties + property + ", "; - } - return new string[] { fields, methods, properties }; - } - public static string SubstringBetweenCharacters(this string input, char charFrom, char charTo) - { - var posFrom = input.IndexOf(charFrom); - if (posFrom != -1) //if found char - { - var posTo = input.IndexOf(charTo, posFrom + 1); - if (posTo != -1) //if found char - { - return input.Substring(posFrom + 1, posTo - posFrom - 1); - } - } - - return string.Empty; - } - public static string[] TrimCommonPrefix(this string[] values) - { - var prefix = string.Empty; - int? resultLength = null; - - if (values != null) - { - if (values.Length > 1) - { - var min = values.Min(value => value.Length); - for (var charIndex = 0; charIndex < min; charIndex++) - { - for (var valueIndex = 1; valueIndex < values.Length; valueIndex++) - { - if (values[0][charIndex] != values[valueIndex][charIndex]) - { - resultLength = charIndex; - break; - } - } - if (resultLength.HasValue) - { - break; - } - } - if (resultLength.HasValue && - resultLength.Value > 0) - { - prefix = values[0].Substring(0, resultLength.Value); - } - } - else if (values.Length > 0) - { - prefix = values[0]; - } - } - return prefix.Length > 0 ? values.Select(s => s.Replace(prefix, "")).ToArray() : values; - } - - public static Dictionary<string, TEnum> NameToValueDictionary<TEnum>(this TEnum enumValue) where TEnum : struct - { - var enumType = enumValue.GetType(); - return Enum.GetValues(enumType) - .Cast<TEnum>() - .ToDictionary(e => Enum.GetName(enumType, e), e => e); - } - public static Dictionary<TEnum, string> ValueToNameDictionary<TEnum>(this TEnum enumValue) where TEnum : struct - { - var enumType = enumValue.GetType(); - return Enum.GetValues(enumType) - .Cast<TEnum>() - .ToDictionary(e => e, e => Enum.GetName(enumType, e)); - } - public static Dictionary<K, V> Filter<K, V>(this Dictionary<K, V> dict, - Predicate<KeyValuePair<K, V>> predicate) => dict.Where(it => predicate(it)).ToDictionary(it => it.Key, it => it.Value); - public static List<List<T>> Partition<T>(this List<T> values, int chunkSize) { - return values.Select((x, i) => new { Index = i, Value = x }) - .GroupBy(x => x.Index / chunkSize) - .Select(x => x.Select(v => v.Value).ToList()) - .ToList(); - } - public static List<List<T>> BuildChunksWithIteration<T>(this List<T> fullList, int batchSize) { - var chunkedList = new List<List<T>>(); - var temporary = new List<T>(); - for (int i = 0; i < fullList.Count; i++) { - //Mod.Log($"{i}/{fullList.Count}"); - var e = fullList[i]; - if (temporary.Count < batchSize) { - temporary.Add(e); - } - else { - //Mod.Log("new row"); - chunkedList.Add(temporary); - temporary = new List<T>() { e }; - } - - if (i == fullList.Count - 1) { - //Mod.Log("last row"); - chunkedList.Add(temporary); - } - } - //Mod.Log($"result - rows: {chunkedList.Count}"); - return chunkedList; - } - // Chunk - public static IEnumerable<TSource[]> Chunk<TSource>(this IEnumerable<TSource> source, int size) { - if (source == null) { - throw new ArgumentNullException("source"); - // ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); - } - - if (size < 1) { - throw new ArgumentOutOfRangeException("size < 1"); - // ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.size); - } - - return ChunkIterator(source, size); - } - - private static IEnumerable<TSource[]> ChunkIterator<TSource>(IEnumerable<TSource> source, int size) { - using IEnumerator<TSource> e = source.GetEnumerator(); - - // Before allocating anything, make sure there's at least one element. - if (e.MoveNext()) { - // Now that we know we have at least one item, allocate an initial storage array. This is not - // the array we'll yield. It starts out small in order to avoid significantly overallocating - // when the source has many fewer elements than the chunk size. - int arraySize = Math.Min(size, 4); - int i; - do { - var array = new TSource[arraySize]; - - // Store the first item. - array[0] = e.Current; - i = 1; - - if (size != array.Length) { - // This is the first chunk. As we fill the array, grow it as needed. - for (; i < size && e.MoveNext(); i++) { - if (i >= array.Length) { - arraySize = (int)Math.Min((uint)size, 2 * (uint)array.Length); - Array.Resize(ref array, arraySize); - } - - array[i] = e.Current; - } - } - else { - // For all but the first chunk, the array will already be correctly sized. - // We can just store into it until either it's full or MoveNext returns false. - TSource[] local = array; // avoid bounds checks by using cached local (`array` is lifted to iterator object as a field) - //Debug.Assert(local.Length == size); - for (; (uint)i < (uint)local.Length && e.MoveNext(); i++) { - local[i] = e.Current; - } - } - - if (i != array.Length) { - Array.Resize(ref array, i); - } - - yield return array; - } - while (i >= size && e.MoveNext()); - } - } - } - public static class MK - { - public static bool IsKindOf(this Type type, Type baseType) => type.IsSubclassOf(baseType) || type == baseType; - } - public static class CloneUtil<T> - { - private static readonly Func<T, object> clone; - - static CloneUtil() - { - var cloneMethod = typeof(T).GetMethod("MemberwiseClone", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); - clone = (Func<T, object>)cloneMethod.CreateDelegate(typeof(Func<T, object>)); - } - - public static T ShallowClone(T obj) => (T)clone(obj); - } - - public static class CloneUtil - { - public static T ShallowClone<T>(this T obj) => CloneUtil<T>.ShallowClone(obj); - } -} diff --git a/ModKit/packages.config b/ModKit/packages.config deleted file mode 100644 index f0f60a9e4..000000000 --- a/ModKit/packages.config +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<packages> - <package id="Lib.Harmony" version="2.0.4" targetFramework="net472" /> -</packages> \ No newline at end of file diff --git a/ToyBox.sln b/ToyBox.sln index 57c468929..0cc90ca39 100644 --- a/ToyBox.sln +++ b/ToyBox.sln @@ -1,31 +1,25 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31105.61 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33627.172 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ToyBox", "ToyBox\ToyBox.csproj", "{22DCB4E1-D979-4EA9-913A-4EE1634B4DED}" -EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "ModKitSrc", "ModKit\ModKitSrc.shproj", "{7D3F9428-C134-47FE-9D04-305B4D330401}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4AA27760-A0FF-4EFA-B330-CCE957361EDD}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ToyBoxRT", "ToyBox\ToyBoxRT.csproj", "{4D9CDA59-3942-46EF-9EBE-FF0498DF5ABB}" +EndProject Global - GlobalSection(SharedMSBuildProjectFiles) = preSolution - ModKit\ModKitSrc.projitems*{22dcb4e1-d979-4ea9-913a-4ee1634b4ded}*SharedItemsImports = 4 - ModKit\ModKitSrc.projitems*{7d3f9428-c134-47fe-9d04-305b4d330401}*SharedItemsImports = 13 - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {22DCB4E1-D979-4EA9-913A-4EE1634B4DED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {22DCB4E1-D979-4EA9-913A-4EE1634B4DED}.Debug|Any CPU.Build.0 = Debug|Any CPU - {22DCB4E1-D979-4EA9-913A-4EE1634B4DED}.Release|Any CPU.ActiveCfg = Release|Any CPU - {22DCB4E1-D979-4EA9-913A-4EE1634B4DED}.Release|Any CPU.Build.0 = Release|Any CPU + {4D9CDA59-3942-46EF-9EBE-FF0498DF5ABB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D9CDA59-3942-46EF-9EBE-FF0498DF5ABB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D9CDA59-3942-46EF-9EBE-FF0498DF5ABB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D9CDA59-3942-46EF-9EBE-FF0498DF5ABB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ToyBox/Art/Texture2D/areshkagal_puzzle_cian_d.png b/ToyBox/Art/Texture2D/areshkagal_puzzle_cian_d.png deleted file mode 100644 index a0a1f5b6e..000000000 Binary files a/ToyBox/Art/Texture2D/areshkagal_puzzle_cian_d.png and /dev/null differ diff --git a/ToyBox/Art/Texture2D/areshkagal_puzzle_cian_d.pxd b/ToyBox/Art/Texture2D/areshkagal_puzzle_cian_d.pxd deleted file mode 100644 index 287e243b6..000000000 Binary files a/ToyBox/Art/Texture2D/areshkagal_puzzle_cian_d.pxd and /dev/null differ diff --git a/ToyBox/Art/Texture2D/areshkagal_puzzle_cian_dark_d.png b/ToyBox/Art/Texture2D/areshkagal_puzzle_cian_dark_d.png deleted file mode 100644 index 7cd0af642..000000000 Binary files a/ToyBox/Art/Texture2D/areshkagal_puzzle_cian_dark_d.png and /dev/null differ diff --git a/ToyBox/Art/Texture2D/areshkagal_puzzle_cian_dark_d.pxd b/ToyBox/Art/Texture2D/areshkagal_puzzle_cian_dark_d.pxd deleted file mode 100644 index 1bc842eb0..000000000 Binary files a/ToyBox/Art/Texture2D/areshkagal_puzzle_cian_dark_d.pxd and /dev/null differ diff --git a/ToyBox/Art/Texture2D/areshkagal_puzzle_cian_m.png b/ToyBox/Art/Texture2D/areshkagal_puzzle_cian_m.png deleted file mode 100644 index 819b55b44..000000000 Binary files a/ToyBox/Art/Texture2D/areshkagal_puzzle_cian_m.png and /dev/null differ diff --git a/ToyBox/Art/Texture2D/areshkagal_puzzle_cian_m.pxd b/ToyBox/Art/Texture2D/areshkagal_puzzle_cian_m.pxd deleted file mode 100644 index 91b836477..000000000 Binary files a/ToyBox/Art/Texture2D/areshkagal_puzzle_cian_m.pxd and /dev/null differ diff --git a/ToyBox/Art/Texture2D/areshkagal_puzzle_cian_n.png b/ToyBox/Art/Texture2D/areshkagal_puzzle_cian_n.png deleted file mode 100644 index c2e8c352e..000000000 Binary files a/ToyBox/Art/Texture2D/areshkagal_puzzle_cian_n.png and /dev/null differ diff --git a/ToyBox/Art/Texture2D/areshkagal_puzzle_cian_n.pxd b/ToyBox/Art/Texture2D/areshkagal_puzzle_cian_n.pxd deleted file mode 100644 index 2f52da8d1..000000000 Binary files a/ToyBox/Art/Texture2D/areshkagal_puzzle_cian_n.pxd and /dev/null differ diff --git a/ToyBox/Art/Texture2D/areshkagal_puzzle_green_d.png b/ToyBox/Art/Texture2D/areshkagal_puzzle_green_d.png deleted file mode 100644 index 02fcb4485..000000000 Binary files a/ToyBox/Art/Texture2D/areshkagal_puzzle_green_d.png and /dev/null differ diff --git a/ToyBox/Art/Texture2D/areshkagal_puzzle_green_d.pxd b/ToyBox/Art/Texture2D/areshkagal_puzzle_green_d.pxd deleted file mode 100644 index 501ff5992..000000000 Binary files a/ToyBox/Art/Texture2D/areshkagal_puzzle_green_d.pxd and /dev/null differ diff --git a/ToyBox/Art/Texture2D/areshkagal_puzzle_green_dark_d.png b/ToyBox/Art/Texture2D/areshkagal_puzzle_green_dark_d.png deleted file mode 100644 index e1a9248df..000000000 Binary files a/ToyBox/Art/Texture2D/areshkagal_puzzle_green_dark_d.png and /dev/null differ diff --git a/ToyBox/Art/Texture2D/areshkagal_puzzle_green_dark_d.pxd b/ToyBox/Art/Texture2D/areshkagal_puzzle_green_dark_d.pxd deleted file mode 100644 index 73090f1ae..000000000 Binary files a/ToyBox/Art/Texture2D/areshkagal_puzzle_green_dark_d.pxd and /dev/null differ diff --git a/ToyBox/Art/Texture2D/areshkagal_puzzle_purple_d.png b/ToyBox/Art/Texture2D/areshkagal_puzzle_purple_d.png deleted file mode 100644 index 7b5c9b8ff..000000000 Binary files a/ToyBox/Art/Texture2D/areshkagal_puzzle_purple_d.png and /dev/null differ diff --git a/ToyBox/Art/Texture2D/areshkagal_puzzle_purple_d.pxd b/ToyBox/Art/Texture2D/areshkagal_puzzle_purple_d.pxd deleted file mode 100644 index 1f4d51d2f..000000000 Binary files a/ToyBox/Art/Texture2D/areshkagal_puzzle_purple_d.pxd and /dev/null differ diff --git a/ToyBox/Art/Texture2D/areshkagal_puzzle_purple_dark_d.png b/ToyBox/Art/Texture2D/areshkagal_puzzle_purple_dark_d.png deleted file mode 100644 index a40b82c88..000000000 Binary files a/ToyBox/Art/Texture2D/areshkagal_puzzle_purple_dark_d.png and /dev/null differ diff --git a/ToyBox/Art/Texture2D/areshkagal_puzzle_purple_dark_d.pxd b/ToyBox/Art/Texture2D/areshkagal_puzzle_purple_dark_d.pxd deleted file mode 100644 index 49f7c3e84..000000000 Binary files a/ToyBox/Art/Texture2D/areshkagal_puzzle_purple_dark_d.pxd and /dev/null differ diff --git a/ToyBox/Art/Texture2D/areshkagal_puzzle_red_d.png b/ToyBox/Art/Texture2D/areshkagal_puzzle_red_d.png deleted file mode 100644 index d77b83513..000000000 Binary files a/ToyBox/Art/Texture2D/areshkagal_puzzle_red_d.png and /dev/null differ diff --git a/ToyBox/Art/Texture2D/areshkagal_puzzle_red_d.pxd b/ToyBox/Art/Texture2D/areshkagal_puzzle_red_d.pxd deleted file mode 100644 index 11c0473a9..000000000 Binary files a/ToyBox/Art/Texture2D/areshkagal_puzzle_red_d.pxd and /dev/null differ diff --git a/ToyBox/Art/Texture2D/areshkagal_puzzle_red_dark_d.png b/ToyBox/Art/Texture2D/areshkagal_puzzle_red_dark_d.png deleted file mode 100644 index b927c908b..000000000 Binary files a/ToyBox/Art/Texture2D/areshkagal_puzzle_red_dark_d.png and /dev/null differ diff --git a/ToyBox/Art/Texture2D/areshkagal_puzzle_red_dark_d.pxd b/ToyBox/Art/Texture2D/areshkagal_puzzle_red_dark_d.pxd deleted file mode 100644 index eae052e2e..000000000 Binary files a/ToyBox/Art/Texture2D/areshkagal_puzzle_red_dark_d.pxd and /dev/null differ diff --git a/ToyBox/Art/Texture2D/areshkagal_puzzle_yellow_d.png b/ToyBox/Art/Texture2D/areshkagal_puzzle_yellow_d.png deleted file mode 100644 index 11e3998c1..000000000 Binary files a/ToyBox/Art/Texture2D/areshkagal_puzzle_yellow_d.png and /dev/null differ diff --git a/ToyBox/Art/Texture2D/areshkagal_puzzle_yellow_d.pxd b/ToyBox/Art/Texture2D/areshkagal_puzzle_yellow_d.pxd deleted file mode 100644 index 71eef7435..000000000 Binary files a/ToyBox/Art/Texture2D/areshkagal_puzzle_yellow_d.pxd and /dev/null differ diff --git a/ToyBox/Art/Texture2D/areshkagal_puzzle_yellow_dark_d.png b/ToyBox/Art/Texture2D/areshkagal_puzzle_yellow_dark_d.png deleted file mode 100644 index 7c2bf0abc..000000000 Binary files a/ToyBox/Art/Texture2D/areshkagal_puzzle_yellow_dark_d.png and /dev/null differ diff --git a/ToyBox/Art/Texture2D/areshkagal_puzzle_yellow_dark_d.pxd b/ToyBox/Art/Texture2D/areshkagal_puzzle_yellow_dark_d.pxd deleted file mode 100644 index 79e13489b..000000000 Binary files a/ToyBox/Art/Texture2D/areshkagal_puzzle_yellow_dark_d.pxd and /dev/null differ diff --git a/ToyBox/classes/Infrastructure/AssetLoader.cs b/ToyBox/Classes/Infrastructure/AssetLoader.cs similarity index 57% rename from ToyBox/classes/Infrastructure/AssetLoader.cs rename to ToyBox/Classes/Infrastructure/AssetLoader.cs index 19dcd2eef..bc88da08d 100644 --- a/ToyBox/classes/Infrastructure/AssetLoader.cs +++ b/ToyBox/Classes/Infrastructure/AssetLoader.cs @@ -1,10 +1,12 @@ using System.IO; -using TMPro; using UnityEngine; using ModKit; +using HarmonyLib; +using System; namespace ToyBox { class AssetLoader { + private static Lazy<Func<Texture2D, byte[], Texture2D>> LoadImage = new(() => AccessTools.MethodDelegate<Func<Texture2D, byte[], Texture2D>>(AccessTools.Method(typeof(ImageConversion), nameof(ImageConversion.LoadImage), [typeof(Texture2D), typeof(byte[])]))); public static Sprite LoadInternal(string folder, string file, Vector2Int size) { return Image2Sprite.Create($"{Mod.modEntry.Path}Assets{Path.DirectorySeparatorChar}{folder}{Path.DirectorySeparatorChar}{file}", size); } @@ -14,29 +16,11 @@ public static class Image2Sprite { public static Sprite Create(string filePath, Vector2Int size) { var bytes = File.ReadAllBytes(icons_folder + filePath); var texture = new Texture2D(size.x, size.y, TextureFormat.ARGB32, false); - _ = texture.LoadImage(bytes); + // After the latest Unity update, UnityEngine.ImageConversionModule is targetting .NET standard 2.1 and using System.ReadOnlySpan<T> + // Referencing System.Memory does not help; so I just created this runtime delegate. Since this method is (as of now) unused anyways it's a good enough fix. + _ = LoadImage.Value(texture, bytes); return Sprite.Create(texture, new Rect(0, 0, size.x, size.y), new Vector2(0, 0)); } } } - - public struct ModIcon { - private readonly string name; - private readonly Vector2Int size; - - public ModIcon(string name, int w, int h) { - _Sprite = null; - this.name = name; - this.size = new Vector2Int(w, h); - } - public ModIcon(string name, Vector2Int size) { - _Sprite = null; - this.name = name; - this.size = size; - } - - private Sprite _Sprite; - public Sprite Sprite => _Sprite ??= (AssetLoader.LoadInternal("icons", name + ".png", size) ?? AssetLoader.LoadInternal("icons", "missing", new Vector2Int(32, 32))); - - } } diff --git a/ToyBox/classes/Infrastructure/UnitEntityDataUtils.cs b/ToyBox/Classes/Infrastructure/BaseUnitDataUtils.cs similarity index 53% rename from ToyBox/classes/Infrastructure/UnitEntityDataUtils.cs rename to ToyBox/Classes/Infrastructure/BaseUnitDataUtils.cs index 3409ee46f..9ac290a4b 100644 --- a/ToyBox/classes/Infrastructure/UnitEntityDataUtils.cs +++ b/ToyBox/Classes/Infrastructure/BaseUnitDataUtils.cs @@ -1,30 +1,32 @@ // borrowed shamelessly and enhanced from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/26, which is under the MIT Licenseusing Kingmaker; -using System.Collections.Generic; -using System.Linq; using Kingmaker; -using Kingmaker.Blueprints.Classes.Selection; +using Kingmaker.Blueprints; +using Kingmaker.Blueprints.Classes; using Kingmaker.Blueprints.Facts; +using Kingmaker.Blueprints.Items.Components; +using Kingmaker.Blueprints.Items.Equipment; +using Kingmaker.Blueprints.Root.Strings; using Kingmaker.Cheats; +using Kingmaker.Controllers.Combat; using Kingmaker.Designers; +using Kingmaker.ElementsSystem; using Kingmaker.EntitySystem.Entities; using Kingmaker.GameModes; +using Kingmaker.Items; +using Kingmaker.UI.Common; using Kingmaker.UnitLogic; using Kingmaker.UnitLogic.Buffs.Blueprints; using Kingmaker.UnitLogic.Mechanics; -using Kingmaker.Utility; using Kingmaker.UnitLogic.Parts; -using Kingmaker.ElementsSystem; +using Kingmaker.Utility; using ModKit; -using Kingmaker.Blueprints.Classes; -using Kingmaker.Items; -using Kingmaker.Controllers.Combat; -using Utilities = Kingmaker.Cheats.Utilities; -using Kingmaker.Blueprints.Items.Components; -using Kingmaker.Blueprints; using ModKit.Utility; -using Kingmaker.Blueprints.Items.Equipment; -using Kingmaker.Blueprints.Root.Strings; -using Kingmaker.UI.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using Warhammer.SpaceCombat.StarshipLogic; +using Utilities = Kingmaker.Cheats.Utilities; namespace ToyBox { public enum UnitSelectType { @@ -34,188 +36,120 @@ public enum UnitSelectType { Friendly, Enemies, Everyone, + Ship, + EnemyShip, } - public static class UnitEntityDataUtils { + public static class BaseUnitDataUtils { public static Settings settings => Main.Settings; - public static float GetMaxSpeed(List<UnitEntityData> data) => data.Select(u => u.ModifiedSpeedMps).Max(); - - public static bool CheckUnitEntityData(UnitEntityData unitEntityData, UnitSelectType selectType) { - if (unitEntityData == null) return false; + public static float GetMaxSpeed(List<BaseUnitEntity> data) => Shodan.GetMaxSpeed(data); + public static bool CheckUnitEntityData(BaseUnitEntity baseUnitEntity, UnitSelectType selectType) { + if (baseUnitEntity == null) return false; switch (selectType) { case UnitSelectType.Everyone: return true; case UnitSelectType.Party: - if (unitEntityData.IsPlayerFaction) { + if (baseUnitEntity.IsPlayerFaction) { return true; } return false; case UnitSelectType.You: - if (unitEntityData.IsMainCharacter) { + if (baseUnitEntity.IsMainCharacter) { return true; } return false; case UnitSelectType.Friendly: - return !unitEntityData.IsEnemy(GameHelper.GetPlayerCharacter()); + return !baseUnitEntity.IsEnemy(); case UnitSelectType.Enemies: - // TODO - should this be IsEnemy instead? - if (!unitEntityData.IsPlayerFaction && unitEntityData.Descriptor.AttackFactions.Contains(Game.Instance.BlueprintRoot.PlayerFaction)) { - return true; - } - return false; + return baseUnitEntity.IsEnemy(); + case UnitSelectType.Ship: + return baseUnitEntity.IsStarship() && !baseUnitEntity.IsEnemy(); + case UnitSelectType.EnemyShip: + return baseUnitEntity.IsStarship() && baseUnitEntity.IsEnemy(); default: return false; } } - public static void Kill(UnitEntityData unit) => unit.Descriptor.Damage = unit.Descriptor.Stats.HitPoints.ModifiedValue + unit.Descriptor.Stats.TemporaryHitPoints.ModifiedValue; - - public static void ForceKill(UnitEntityData unit) => unit.Descriptor.State.ForceKill = true; + public static void Kill(BaseUnitEntity unit) => unit.Health.Damage = unit.Stats.GetStat(StatType.HitPoints) + unit.Stats.GetStat(StatType.TemporaryHitPoints); - public static void ResurrectAndFullRestore(UnitEntityData unit) => unit.Descriptor.ResurrectAndFullRestore(); - - public static void Buff(UnitEntityData unit, string buffGuid) => unit.Descriptor.AddFact((BlueprintUnitFact)Utilities.GetBlueprintByGuid<BlueprintBuff>(buffGuid), - (MechanicsContext)null, - new FeatureParam()); - - public static void Charm(UnitEntityData unit) { - if (unit != null) - unit.Descriptor.SwitchFactions(Game.Instance.BlueprintRoot.PlayerFaction, true); - else + public static void Charm(BaseUnitEntity unit) { + if (unit != null) { + // TODO: can we still do this? + // unit.SetFaction() = Game.Instance.BlueprintRoot.PlayerFaction; + } else Mod.Warn("Unit is null!"); } - - public static void AddToParty(UnitEntityData unit) { + public static void AddToParty(BaseUnitEntity unit) { Charm(unit); Game.Instance.Player.AddCompanion(unit); } -#if true - public static void AddCompanion(UnitEntityData unit) { + public static void AddCompanion(BaseUnitEntity unit) { var currentMode = Game.Instance.CurrentMode; Game.Instance.Player.AddCompanion(unit); if (currentMode == GameModeType.Default || currentMode == GameModeType.Pause) { - var pets = unit.Pets; unit.IsInGame = true; - unit.Position = Game.Instance.Player.MainCharacter.Value.Position; - unit.LeaveCombat(); + unit.Position = Game.Instance.Player.MainCharacter.Entity.Position; + unit.CombatState.LeaveCombat(); Charm(unit); - var unitPartCompanion = unit.Get<UnitPartCompanion>(); - unitPartCompanion.State = CompanionState.InParty; + var unitPartCompanion = unit.GetAll<UnitPartCompanion>(); + Game.Instance.Player.AddCompanion(unit); if (unit.IsDetached) { Game.Instance.Player.AttachPartyMember(unit); } - foreach (var pet in pets) { - pet.Entity.Position = unit.Position; - } } } - public static void RecruitCompanion(UnitEntityData unit) { + public static void RecruitCompanion(BaseUnitEntity unit) { var currentMode = Game.Instance.CurrentMode; - unit = Game.Instance.EntityCreator.RecruitNPC(unit, unit.Blueprint); + unit = GameHelper.RecruitNPC(unit, unit.Blueprint); // this line worries me but the dev said I should do it //unit.HoldingState.RemoveEntityData(unit); //player.AddCompanion(unit); if (currentMode == GameModeType.Default || currentMode == GameModeType.Pause) { - var pets = unit.Pets; + var pets = Game.Instance.Player.PartyAndPets.Where(u => u.IsPet && u.OwnerEntity == unit); unit.IsInGame = true; - unit.Position = Game.Instance.Player.MainCharacter.Value.Position; - unit.LeaveCombat(); + unit.Position = Shodan.MainCharacter.Position; + unit.CombatState.LeaveCombat(); Charm(unit); - unit.SwitchFactions(Game.Instance.Player.MainCharacter.Value.Faction); //unit.GroupId = Game.Instance.Player.MainCharacter.Value.GroupId; //Game.Instance.Player.CrossSceneState.AddEntityData(unit); if (unit.IsDetached) { Game.Instance.Player.AttachPartyMember(unit); } foreach (var pet in pets) { - pet.Entity.Position = unit.Position; - } - } - } - public static void maybeKill(UnitCombatState unitCombatState) { - - if (settings.togglekillOnEngage) { - List<UnitEntityData> partyUnits = Game.Instance.Player.m_PartyAndPets; - UnitEntityData unit = unitCombatState.Unit; - if (unit.IsPlayersEnemy && !partyUnits.Contains(unit)) { - CheatsCombat.KillUnit(unit); - } - } - } -#else -note this code from Owlcat - private static void RecruitCompanion(string parameters) - { - string paramString = Utilities.GetParamString(parameters, 1, (string) null); - bool? paramBool = Utilities.GetParamBool(parameters, 2, (string) null); - BlueprintUnit blueprint = Utilities.GetBlueprint<BlueprintUnit>(paramString); - if (blueprint == null) - PFLog.SmartConsole.Log("Cant get companion with name '" + paramString + "'", (object[]) Array.Empty<object>()); - else if (!paramBool.HasValue || paramBool.Value) - { - UnitEntityData unitVacuum = Game.Instance.CreateUnitVacuum(blueprint); - Game.Instance.State.PlayerState.CrossSceneState.AddEntityData((EntityDataBase) unitVacuum); - unitVacuum.IsInGame = false; - unitVacuum.Ensure<UnitPartCompanion>().SetState(CompanionState.ExCompanion); - } - else - { - Vector3 position = Game.Instance.Player.MainCharacter.Value.Position; - SceneEntitiesState crossSceneState = Game.Instance.State.PlayerState.CrossSceneState; - UnitEntityData unit = Game.Instance.EntityCreator.SpawnUnit(blueprint, position, Quaternion.identity, crossSceneState); - Game.Instance.Player.AddCompanion(unit); - EventBus.RaiseEvent<IPartyHandler>((Action<IPartyHandler>) (h => h.HandleAddCompanion(unit))); - } - } - - - public static void AddCompanion(UnitEntityData unit) { - Player player = Game.Instance.Player; - player.AddCompanion(unit); - GameModeType currentMode = Game.Instance.CurrentMode; - if (currentMode == GameModeType.Default || currentMode == GameModeType.Pause) { - var pets = unit.Pets; - unit.IsInGame = true; - unit.Position = Game.Instance.Player.MainCharacter.Value.Position; - unit.LeaveCombat(); - Charm(unit); - UnitPartCompanion unitPartCompanion = unit.Get<UnitPartCompanion>(); - unit.Ensure<UnitPartCompanion>().SetState(CompanionState.InParty); - unit.SwitchFactions(Game.Instance.Player.MainCharacter.Value.Faction); - unit.GroupId = Game.Instance.Player.MainCharacter.Value.GroupId; - unit.HoldingState.RemoveEntityData(unit); - Game.Instance.Player.CrossSceneState.AddEntityData(unit); - foreach (var pet in pets) { - pet.Entity.Position = unit.Position; + pet + .Position = unit.Position; } } } -#endif - - public static void RemoveCompanion(UnitEntityData unit) { - _ = Game.Instance.CurrentMode; - Game.Instance.Player.RemoveCompanion(unit); + public static bool IsPartyOrPetInterface(this IBaseUnitEntity unit) { + return (unit as BaseUnitEntity)?.IsPartyOrPet() ?? false; } - - public static bool IsPartyOrPet(this UnitDescriptor unit) { - if (unit?.Unit?.OriginalBlueprint == null || Game.Instance.Player?.AllCharacters == null || Game.Instance.Player?.AllCharacters.Count == 0) { + public static bool IsPartyOrPet(this MechanicEntity unit) { + if (unit? + .OriginalBlueprint == null + || Game.Instance.Player?.AllCharacters == null + || Game.Instance.Player?.AllCharacters.Count == 0) { return false; } return Game.Instance.Player.AllCharacters - .Any(x => x.OriginalBlueprint == unit.Unit.OriginalBlueprint && (x.Master == null || x.Master.OriginalBlueprint == null || Game.Instance.Player.AllCharacters.Any(y => y.OriginalBlueprint == x.Master.OriginalBlueprint))); - } - - public static bool TryGetPartyMemberForLevelUpVersion(this UnitDescriptor levelUpUnit, out UnitEntityData ch) { - ch = Game.Instance?.Player?.AllCharacters.Find(c => c.CharacterName == levelUpUnit.CharacterName); - return ch != null; + .Any(x => x.OriginalBlueprint == unit + .OriginalBlueprint + && (x.Master == null + || x.Master.OriginalBlueprint == null + || Game.Instance.Player.AllCharacters.Any( + y => y.OriginalBlueprint == x.Master.OriginalBlueprint) + ) + ); } - public static bool TryGetClass(this UnitEntityData unit, BlueprintCharacterClass cl, out ClassData cd) { - cd = unit.Progression.Classes.Find(c => c.CharacterClass == cl); - return cd != null; + public static void RemoveCompanion(BaseUnitEntity unit) { + _ = Game.Instance.CurrentMode; + Game.Instance.Player.RemoveCompanion(unit); } +#if false public static string GetEquipmentRestrictionCaption(this EquipmentRestriction restriction) { switch (restriction) { case EquipmentRestrictionAlignment era: return $"Alignment must be {era.Alignment}"; @@ -283,5 +217,6 @@ public static string GetCantEquipReasonText(this ItemEntity item) { } return null; } +#endif } } diff --git a/ToyBox/Classes/Infrastructure/Blueprints/BlueprintExtensions.cs b/ToyBox/Classes/Infrastructure/Blueprints/BlueprintExtensions.cs new file mode 100644 index 000000000..23d6dcf3a --- /dev/null +++ b/ToyBox/Classes/Infrastructure/Blueprints/BlueprintExtensions.cs @@ -0,0 +1,362 @@ +// Copyright < 2021 > Narria (github user Cabarius) - License: MIT +using HarmonyLib; +using Kingmaker.AreaLogic.Etudes; +using Kingmaker.Blueprints; +using Kingmaker.Blueprints.Area; +using Kingmaker.Blueprints.Items; +using Kingmaker.Blueprints.Items.Ecnchantments; +using Kingmaker.Code.Blueprints.Quests; +using Kingmaker.ElementsSystem; +using Kingmaker.UnitLogic.Buffs.Blueprints; +using Kingmaker.UnitLogic.Mechanics.Blueprints; +using ModKit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Linq.Expressions; +using System.Reflection; + +namespace ToyBox { + + public static partial class BlueprintExtensions { + public static Settings Settings => Main.Settings; + + private static ConditionalWeakTable<object, List<string>> _cachedCollationNames = new() { }; + private static readonly HashSet<string> BadList = new(); + public static Dictionary<string, string> descriptionCache = new(); + internal static bool wasIncludeInternalNameForTitle = false; + public static Dictionary<string, string> titleCache = new(); + internal static bool wasIncludeInternalNameForSearchKey = false; + public static Dictionary<string, string> searchKeyCache = new(); + internal static bool wasIncludeInternalNameForSortKey = false; + public static Dictionary<string, string> sortKeyCache = new(); + public static void ResetCollationCache() => _cachedCollationNames = new ConditionalWeakTable<object, List<string>> { }; + private static void AddOrUpdateCachedNames(SimpleBlueprint bp, List<string> names) { + names = names.Distinct().ToList(); + if (_cachedCollationNames.TryGetValue(bp, out _)) { + _cachedCollationNames.Remove(bp); + //Mod.Log($"removing: {bp.NameSafe()}"); + } + _cachedCollationNames.Add(bp, names); + //Mod.Log($"adding: {bp.NameSafe()} - {names.Count} - {String.Join(", ", names)}"); + } + + public static string GetDisplayName(this SimpleBlueprint bp) => bp switch { + BlueprintAbilityResource abilityResource => abilityResource.Name, + BlueprintArchetype archetype => archetype.Name, +#pragma warning disable CS0612 // Type or member is obsolete + BlueprintCharacterClass charClass => charClass.Name, +#pragma warning restore CS0612 // Type or member is obsolete + BlueprintItem item => item.Name, + BlueprintItemEnchantment enchant => enchant.Name, + BlueprintMechanicEntityFact fact => fact.NameSafe(), + SimpleBlueprint blueprint => blueprint.name, + _ => "n/a" + }; + public static string GetDisplayName(this BlueprintSpellbook bp) { + var name = bp.DisplayName; + if (string.IsNullOrEmpty(name)) name = bp.name.Replace("Spellbook", ""); + return name; + } + public static string GetTitle(SimpleBlueprint blueprint, Func<string, string> formatter = null) { + if (titleCache.TryGetValue(blueprint.AssetGuid, out var ret) && (wasIncludeInternalNameForTitle == Settings.showDisplayAndInternalNames)) { + return ret; + } else { + wasIncludeInternalNameForTitle = Settings.showDisplayAndInternalNames; + } + if (formatter == null) formatter = s => s; + if (blueprint is IUIDataProvider uiDataProvider) { + string name; + bool isEmpty = true; + try { + isEmpty = string.IsNullOrEmpty(uiDataProvider.Name); + } catch (Exception ex) { + Mod.Debug($"Error while getting name for {uiDataProvider}: {ex}"); + } + if (isEmpty) { + name = blueprint.name; + } else { + if (blueprint is BlueprintSpellbook spellbook) { + titleCache[blueprint.AssetGuid] = $"{spellbook.Name} - {spellbook.name}"; + return $"{spellbook.Name} - {spellbook.name}"; + } + name = formatter(uiDataProvider.Name); + if (name == "<null>" || name.StartsWith("[unknown key: ")) { + name = formatter(blueprint.name); + } else if (Settings.showDisplayAndInternalNames) { + name += $" : {blueprint.name.Color(RGBA.darkgrey)}"; + } + } + titleCache[blueprint.AssetGuid] = name; + return name; + } else if (blueprint is BlueprintItemEnchantment enchantment) { + string name; + var isEmpty = string.IsNullOrEmpty(enchantment.Name); + if (isEmpty) { + name = formatter(blueprint.name); + } else { + name = formatter(enchantment.Name); + if (name == "<null>" || name.StartsWith("[unknown key: ")) { + name = formatter(blueprint.name); + } else if (Settings.showDisplayAndInternalNames) { + name += $" : {blueprint.name.Color(RGBA.darkgrey)}"; + } + } + titleCache[blueprint.AssetGuid] = name; + return name; + } + titleCache[blueprint.AssetGuid] = formatter(blueprint.name); + return formatter(blueprint.name); + } + public static string GetSearchKey(SimpleBlueprint blueprint, bool forceDisplayInternalName = false) { + if (searchKeyCache.TryGetValue(blueprint.AssetGuid, out var ret) && (wasIncludeInternalNameForSearchKey == (Settings.showDisplayAndInternalNames || forceDisplayInternalName))) { + return ret; + } else { + wasIncludeInternalNameForSearchKey = Settings.showDisplayAndInternalNames || forceDisplayInternalName; + } + try { + if (blueprint is IUIDataProvider uiDataProvider) { + string name; + bool isEmpty = true; + try { + isEmpty = string.IsNullOrEmpty(uiDataProvider.Name); + } catch (Exception ex) { + Mod.Debug($"Error while getting name for {uiDataProvider}:\n{ex}"); + } + if (isEmpty) { + name = blueprint.name; + } else { + if (uiDataProvider is BlueprintSpellbook spellbook) { + searchKeyCache[blueprint.AssetGuid] = $"{spellbook.Name} {spellbook.name} {spellbook.AssetGuid}"; + return searchKeyCache[blueprint.AssetGuid]; + } + name = uiDataProvider.Name; + if (name == "<null>" || name.StartsWith("[unknown key: ")) { + name = blueprint.name; + } else if (Settings.showDisplayAndInternalNames || forceDisplayInternalName) { + name += $" : {blueprint.name}"; + } + } + searchKeyCache[blueprint.AssetGuid] = name.StripHTML() + $" {blueprint.AssetGuid}"; + return searchKeyCache[blueprint.AssetGuid]; + } else if (blueprint is BlueprintItemEnchantment enchantment) { + string name; + var isEmpty = string.IsNullOrEmpty(enchantment.Name); + if (isEmpty) { + name = blueprint.name; + } else { + name = enchantment.Name; + if (name == "<null>" || name.StartsWith("[unknown key: ")) { + name = blueprint.name; + } else if (Settings.showDisplayAndInternalNames) { + name += $" : {blueprint.name}"; + } + } + searchKeyCache[blueprint.AssetGuid] = name.StripHTML() + $" {blueprint.AssetGuid}"; + return searchKeyCache[blueprint.AssetGuid]; + } + searchKeyCache[blueprint.AssetGuid] = blueprint.name.StripHTML() + $" {blueprint.AssetGuid}"; + return searchKeyCache[blueprint.AssetGuid]; + } catch (Exception ex) { + Mod.Debug(ex.ToString()); + Mod.Debug($"-------{blueprint}-----{blueprint.AssetGuid}"); + return ""; + } + } + public static string GetSortKey(SimpleBlueprint blueprint) { + if (sortKeyCache.TryGetValue(blueprint.AssetGuid, out var ret) && (wasIncludeInternalNameForSortKey == Settings.showDisplayAndInternalNames)) { + return ret; + } else { + wasIncludeInternalNameForSortKey = Settings.showDisplayAndInternalNames; + } + try { + if (blueprint is IUIDataProvider uiDataProvider) { + string name; + bool isEmpty = true; + try { + isEmpty = string.IsNullOrEmpty(uiDataProvider.Name); + } catch (Exception ex) { + Mod.Debug($"Error while getting name for {uiDataProvider}:\n{ex}"); + } + if (isEmpty) { + name = blueprint.name; + } else { + if (blueprint is BlueprintSpellbook spellbook) { + sortKeyCache[blueprint.AssetGuid] = $"{spellbook.Name} - {spellbook.name}"; + return $"{spellbook.Name} - {spellbook.name}"; + } + name = uiDataProvider.Name; + if (name == "<null>" || name.StartsWith("[unknown key: ")) { + name = blueprint.name; + } else if (Settings.showDisplayAndInternalNames) { + name += blueprint.name; + } + } + sortKeyCache[blueprint.AssetGuid] = name; + return name; + } else if (blueprint is BlueprintItemEnchantment enchantment) { + string name; + var isEmpty = string.IsNullOrEmpty(enchantment.Name); + if (isEmpty) { + name = blueprint.name; + } else { + name = enchantment.Name; + if (name == "<null>" || name.StartsWith("[unknown key: ")) { + name = blueprint.name; + } else if (Settings.showDisplayAndInternalNames) { + name += blueprint.name; + } + } + sortKeyCache[blueprint.AssetGuid] = name; + return name; + } + sortKeyCache[blueprint.AssetGuid] = blueprint.name; + return blueprint.name; + } catch (Exception ex) { + Mod.Debug(ex.ToString()); + Mod.Debug($"-------{blueprint}-----{blueprint.AssetGuid}"); + return ""; + } + } + private static Dictionary<Type, List<(Func<SimpleBlueprint, bool>, string)>> PropertyAccessors = new(); + private static Dictionary<Type, string> TypeNamesCache = new(); + public static void CacheTypeProperties(Type type) { + var accessors = new List<(Func<SimpleBlueprint, bool>, string)>(); + // When get_IsContinuous is called, this will cause a chain rection which crashes the game... + foreach (var prop in type.GetProperties(AccessTools.allDeclared).Where(p => p.Name.StartsWith("Is") && p.PropertyType == typeof(bool) && !p.Name.StartsWith("IsContinuous"))) { + var mi = prop.GetGetMethod(true); + if (mi == null) continue; + if (mi.IsStatic) { + Func<bool> staticDelegate = (Func<bool>)Delegate.CreateDelegate(typeof(Func<bool>), mi); + accessors.Add((bp => staticDelegate(), prop.Name)); + } else { + var parameter = Expression.Parameter(typeof(SimpleBlueprint), "bp"); + var propertyAccess = Expression.Property(Expression.Convert(parameter, type), prop); + var lambda = Expression.Lambda<Func<SimpleBlueprint, bool>>(propertyAccess, parameter); + Func<SimpleBlueprint, bool> compiled = lambda.Compile(); + accessors.Add((compiled, prop.Name)); + } + } + + PropertyAccessors[type] = accessors; + } + public static IEnumerable<string> Attributes(this SimpleBlueprint bp) { + if (BadList.Contains(bp.AssetGuid)) return Enumerable.Empty<string>(); + if (!PropertyAccessors.TryGetValue(bp.GetType(), out var accessors)) { + CacheTypeProperties(bp.GetType()); + accessors = PropertyAccessors[bp.GetType()]; + } + + List<string> modifiers = new List<string>(); + foreach (var accessor in accessors) { + try { + if (accessor.Item1(bp)) { + modifiers.Add(accessor.Item2); + } + } catch (Exception e) { + Mod.Warn($"Error accessing property on {bp.name}: {e.Message}"); + BadList.Add(bp.AssetGuid); + break; + } + } + return modifiers; + } + private static List<string> DefaultCollationNames(this SimpleBlueprint bp, string[] extras) { + _cachedCollationNames.TryGetValue(bp, out var names); + if (names == null) { + var namesSet = new HashSet<string>(); + string typeName; + var type = bp.GetType(); + if (!TypeNamesCache.TryGetValue(type, out typeName)) { + typeName = type.Name; + typeName = typeName.Replace("Blueprint", ""); + + TypeNamesCache[type] = typeName; + } + namesSet.Add(typeName); + + foreach (var attribute in bp.Attributes()) { + namesSet.Add(attribute.Orange()); + } + names = namesSet.ToList(); + _cachedCollationNames.Add(bp, names); + } + + return [.. names, .. extras]; + } + public static List<string> CollationNames(this SimpleBlueprint bp, params string[] extras) => DefaultCollationNames(bp, extras); + [Obsolete] + public static List<string> CollationNames(this BlueprintCharacterClass bp, params string[] extras) { + var names = DefaultCollationNames(bp, extras); + if (bp.IsArcaneCaster) names.Add("Arcane"); + if (bp.IsDivineCaster) names.Add("Divine"); + if (bp.IsMythic) names.Add("Mythic"); + return names; + } + public static List<string> CollationNames(this BlueprintSpellbook bp, params string[] extras) { + var names = DefaultCollationNames(bp, extras); + if (bp.CharacterClass.IsDivineCaster) names.Add("Divine"); + AddOrUpdateCachedNames(bp, names); + return names; + } + public static List<string> CollationNames(this BlueprintBuff bp, params string[] extras) { + var names = DefaultCollationNames(bp, extras); + if (bp.Ranks > 0) names.Add($"{bp.Ranks} Ranks"); + + AddOrUpdateCachedNames(bp, names); + return names; + } + public static List<string> CollationNames(this BlueprintArea bp, params string[] extras) { + var names = DefaultCollationNames(bp, extras); + var typeName = bp.GetType().Name.Replace("Blueprint", ""); + if (typeName == "Area") names.Add($"Area CR{bp.m_CR}"); + AddOrUpdateCachedNames(bp, names); + return names; + } + public static List<string> CollationNames(this BlueprintEtude bp, params string[] extras) { + var names = DefaultCollationNames(bp, extras); + //foreach (var item in bp.ActivationCondition) { + // names.Add(item.name.yellow()); + //} + //names.Add(bp.ValidationStatus.ToString().yellow()); + //if (bp.HasParent) names.Add($"P:".yellow() + bp.Parent.NameSafe()); + //foreach (var sibling in bp.StartsWith) { + // names.Add($"W:".yellow() + bp.Parent.NameSafe()); + //} + //if (bp.HasLinkedAreaPart) names.Add($"area {bp.LinkedAreaPart.name}".yellow()); + //foreach (var condition in bp.ActivationCondition?.Conditions) + // names.Add(condition.GetCaption().yellow()); + AddOrUpdateCachedNames(bp, names); + return names; + } + public static string[] CaptionNames(this SimpleBlueprint bp) => bp.m_AllElements?.OfType<Condition>()?.Select(e => e.GetCaption() ?? "")?.ToArray() ?? new string[] { }; + public static List<String> CaptionCollationNames(this SimpleBlueprint bp) => bp.CollationNames(bp.CaptionNames()); + + public static readonly HashSet<string> badBP = new() { "b60252a8ae028ba498340199f48ead67", "fb379e61500421143b52c739823b4082" }; + public static string GetDescription(this SimpleBlueprint bp) + // borrowed shamelessly and enhanced from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/26, which is under the MIT License + { + try { + // avoid exceptions on known broken items + var guid = bp.AssetGuid; + if (descriptionCache.TryGetValue(guid, out var desc)) { + return desc; + } + if (badBP.Contains(guid)) return null; + var associatedBlueprint = bp as IUIDataProvider; + desc = associatedBlueprint?.Description?.StripHTML(); + descriptionCache[guid] = desc; + return desc; + + } catch (Exception e) { + Mod.Debug(e.ToString()); +#if DEBUG + return "ERROR".Red().Bold() + $": caught exception {e}"; +#else + return ""; +#endif + } + } + } +} \ No newline at end of file diff --git a/ToyBox/classes/Infrastructure/Blueprints/BlueprintExtensionsQuest.cs b/ToyBox/Classes/Infrastructure/Blueprints/BlueprintExtensionsQuest.cs similarity index 72% rename from ToyBox/classes/Infrastructure/Blueprints/BlueprintExtensionsQuest.cs rename to ToyBox/Classes/Infrastructure/Blueprints/BlueprintExtensionsQuest.cs index aab098680..add98f138 100644 --- a/ToyBox/classes/Infrastructure/Blueprints/BlueprintExtensionsQuest.cs +++ b/ToyBox/Classes/Infrastructure/Blueprints/BlueprintExtensionsQuest.cs @@ -1,59 +1,64 @@ // Copyright < 2021 > Narria (github user Cabarius) - License: MIT using HarmonyLib; +using Kingmaker; using Kingmaker.AreaLogic.Etudes; +using Kingmaker.AreaLogic.QuestSystem; using Kingmaker.Blueprints; using Kingmaker.Blueprints.Area; using Kingmaker.Blueprints.Classes; -using Kingmaker.Blueprints.Classes.Selection; -using Kingmaker.Blueprints.Classes.Spells; using Kingmaker.Blueprints.Facts; using Kingmaker.Blueprints.Items; using Kingmaker.Blueprints.Items.Ecnchantments; -using Kingmaker.Craft; +using Kingmaker.Designers.EventConditionActionSystem.Actions; +using Kingmaker.Designers.EventConditionActionSystem.Conditions; +using Kingmaker.DialogSystem.Blueprints; using Kingmaker.ElementsSystem; +using Kingmaker.EntitySystem; using Kingmaker.EntitySystem.Entities; using Kingmaker.UI; using Kingmaker.UnitLogic; using Kingmaker.UnitLogic.Buffs.Blueprints; +using Kingmaker.UnitLogic.Interaction; using Kingmaker.UnitLogic.Mechanics; +using Kingmaker.UnitLogic.Parts; using Kingmaker.Utility; +using Kingmaker.View.MapObjects; using ModKit; using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -using Kingmaker.AreaLogic.QuestSystem; -using Kingmaker.Designers.EventConditionActionSystem.Conditions; -using Kingmaker.UnitLogic.Parts; using static Kingmaker.UnitLogic.Interaction.SpawnerInteractionPart; -using Kingmaker.UnitLogic.Interaction; using static ToyBox.BlueprintExtensions; -using Kingmaker.Designers.EventConditionActionSystem.Actions; -using Kingmaker; -using Kingmaker.EntitySystem; -using Kingmaker.View.MapObjects; namespace ToyBox { public static partial class BlueprintExtensions { public class IntrestingnessEntry { - public UnitEntityData unit { get; set; } + public BaseUnitEntity unit { get; set; } public object source { get; set; } public ConditionsChecker checker { get; set; } - public List<Element> elements { get; set; } - public bool HasConditins => checker?.Conditions.Length > 0; + public List<Element>? elements { get; set; } + public bool HasConditions => checker?.Conditions.Length > 0; public bool HasElements => elements?.Count > 0; - public IntrestingnessEntry(UnitEntityData unit, object source, ConditionsChecker checker, List<Element> elements = null) { + public IntrestingnessEntry(BaseUnitEntity unit, object source, ConditionsChecker checker, List<Element>? elements = null) { this.unit = unit; this.source = source; this.checker = checker; this.elements = elements; } } - public static bool IsActive(this IntrestingnessEntry entry) => + public static bool IsActive(this IntrestingnessEntry entry) => (entry.checker?.IsActive() ?? false) - || (entry?.elements.Any(element => element.IsActive()) ?? false) - || (entry.elements?.Count > 0 && entry.source is ActionsHolder) // Kludge until we get more clever about analyzing dialog state. This lets Lathimas show up as active + || (entry?.elements.Any(element => { + try { + return element.IsActive(); + } catch (Exception ex) { + Mod.Debug(ex.ToString()); + return false; + } + }) ?? false) + || (entry?.elements?.Count > 0 && entry.source is ActionsHolder) // Kludge until we get more clever about analyzing dialog state. This lets Lathimas show up as active ; public static bool IsActive(this Element element) => element switch { Conditional conditional => conditional.ConditionsChecker.Check(), @@ -62,8 +67,8 @@ public static bool IsActive(this IntrestingnessEntry entry) => }; public static bool IsActive(this ConditionsChecker checker) => checker.Conditions.Any(c => c.CheckCondition()); public static string CaptionString(this Condition condition) => - $"{condition.GetCaption().orange()} -> {(condition.CheckCondition() ? "True".green() : "False".yellow())}"; - public static string CaptionString(this Element element) => $"{element.GetCaption().orange()}"; + $"{condition.GetCaption().Orange()} -> {(condition.CheckCondition() ? "True".Green() : "False".Yellow())}"; + public static string CaptionString(this Element element) => $"{element.GetCaption().Orange()}"; public static bool IsQuestRelated(this Element element) => element is GiveObjective || element is SetObjectiveStatus @@ -75,17 +80,35 @@ public static bool IsQuestRelated(this Element element) => element is GiveObject || element is ItemsEnough || element is Conditional ; - public static int InterestingnessCoefficent(this UnitEntityData unit) => unit.GetUnitInteractionConditions().Count(entry => entry.IsActive()); - - public static IEnumerable<IntrestingnessEntry> GetUnitInteractionConditions(this UnitEntityData unit) { - var spawnInterations = unit.Parts.Parts + public static int InterestingnessCoefficent(this MechanicEntity entity) + => entity is BaseUnitEntity unit ? unit.InterestingnessCoefficent() : 0; + public static int InterestingnessCoefficent(this BaseUnitEntity unit) => unit.GetUnitInteractionConditions().Count(entry => { + try { + return entry.IsActive(); + } catch (Exception ex) { + Mod.Debug(ex.ToString()); + return false; + } + }); + public static List<BlueprintDialog> GetDialog(this BaseUnitEntity unit) { + var dialogs = unit.Parts.m_Parts + .OfType<UnitPartInteractions>() + .SelectMany(p => p.m_Interactions) + .OfType<Wrapper>() + .Select(w => w.Source) + .OfType<SpawnerInteractionDialog>() + .Select(sid => sid.Dialog).ToList(); + return dialogs; + } + public static IEnumerable<IntrestingnessEntry> GetUnitInteractionConditions(this BaseUnitEntity unit) { + var spawnInterations = unit.Parts.m_Parts .OfType<UnitPartInteractions>() .SelectMany(p => p.m_Interactions) .OfType<Wrapper>() .Select(w => w.Source); var result = new HashSet<IntrestingnessEntry>(); var elements = new HashSet<IntrestingnessEntry>(); - + // dialog var dialogInteractions = spawnInterations.OfType<SpawnerInteractionDialog>().ToList(); // dialog interation conditions @@ -108,7 +131,7 @@ public static IEnumerable<IntrestingnessEntry> GetUnitInteractionConditions(this .Where(cueRef => cueRef.Get() != null) .Select(cueRef => new IntrestingnessEntry(unit, cueRef.Get(), cueRef.Get().Conditions))); result.UnionWith(dialogCueConditions.ToHashSet()); - + // actions var actionInteractions = spawnInterations.OfType<SpawnerInteractionActions>(); // action interaction conditions @@ -118,15 +141,17 @@ public static IEnumerable<IntrestingnessEntry> GetUnitInteractionConditions(this result.UnionWith(actionInteractionConditions.ToHashSet()); // action conditions var actionConditions = actionInteractions - .Where(ai => ai.Actions?.Get() != null) - .SelectMany(ai => ai.Actions.Get().Actions.Actions + .SelectMany(ai => ai.ActionHolders) + .Where(ai => ai?.Get() != null) + .SelectMany(ai => ai.Get().Actions.Actions .Where(a => a is Conditional) - .Select(a => new IntrestingnessEntry(unit, ai.Actions.Get(), (a as Conditional).ConditionsChecker))); + .Select(a => new IntrestingnessEntry(unit, ai.Get(), (a as Conditional).ConditionsChecker))); result.Union(actionConditions.ToHashSet()); // action elements var actionElements = actionInteractions - .Where(ai => ai.Actions?.Get() != null) - .Select(ai => new IntrestingnessEntry(unit, ai.Actions.Get(), null, ai.Actions.Get().ElementsArray.Where(e => e.IsQuestRelated()).ToList())); + .SelectMany(ai => ai.ActionHolders) + .Where(ai => ai?.Get() != null) + .Select(ai => new IntrestingnessEntry(unit, ai.Get(), null, ai.Get().ElementsArray.Where(e => e.IsQuestRelated()).ToList())); elements.UnionWith(actionElements.ToHashSet()); foreach (var entry in elements) { //Mod.Debug($"checking {entry}"); @@ -146,11 +171,11 @@ public static IEnumerable<IntrestingnessEntry> GetUnitInteractionConditions(this return result; } public static void RevealInterestingNPCs() { - if (Game.Instance?.State?.Units is { } unitsPool) { + if (Shodan.AllUnits + is { } unitsPool) { var inerestingUnits = unitsPool.Where(u => u.InterestingnessCoefficent() > 0); foreach (var unit in inerestingUnits) { Mod.Debug($"Revealing {unit.CharacterName}"); - unit.SetIsRevealedSilent(true); } } } diff --git a/ToyBox/Classes/Infrastructure/Blueprints/BlueprintIdCache.cs b/ToyBox/Classes/Infrastructure/Blueprints/BlueprintIdCache.cs new file mode 100644 index 000000000..10d85322c --- /dev/null +++ b/ToyBox/Classes/Infrastructure/Blueprints/BlueprintIdCache.cs @@ -0,0 +1,202 @@ +using Kingmaker.Blueprints; +using Kingmaker; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Kingmaker.Modding; +using Kingmaker.AreaLogic.Etudes; +using Kingmaker.Blueprints.Area; +using Kingmaker.Blueprints.Items.Ecnchantments; +using Kingmaker.UnitLogic.Buffs.Blueprints; +using Kingmaker.UnitLogic.Abilities.Blueprints; +using Kingmaker.AI.Blueprints; +using System.IO; +using System.Runtime.Serialization; +using Kingmaker.Blueprints.Facts; +using Kingmaker.GameInfo; +using Kingmaker.Globalmap.Blueprints.SystemMap; +using Kingmaker.Globalmap.Blueprints.Colonization; +using Kingmaker.Globalmap.Blueprints; +using Kingmaker.Visual.Sound; +using Kingmaker.Blueprints.Items; +using Kingmaker.Blueprints.Items.Weapons; +using Kingmaker.Blueprints.Items.Armors; +using Kingmaker.Blueprints.Items.Equipment; + +namespace ToyBox.classes.Infrastructure.Blueprints { + public class BlueprintIdCache { + public string CachedGameVersion = ""; + public HashSet<(string, string)> UmmList = new(); + public HashSet<(string, string)> OmmList = new(); + public Dictionary<Type, HashSet<string>> IdsByType = new(); + + public static HashSet<Type> CachedIdTypes = new() { + typeof(BlueprintItem), typeof(BlueprintItemWeapon), typeof(BlueprintItemArmor), + typeof(BlueprintEtude), typeof(BlueprintArea), typeof(BlueprintItemEnchantment), + typeof(BlueprintBuff), typeof(BlueprintPortrait), typeof(BlueprintSpellbook), + typeof(BlueprintAbility), typeof(BlueprintAreaEnterPoint), typeof(BlueprintUnit), + typeof(BlueprintBrain), typeof(BlueprintFeature), typeof(BlueprintUnitFact), + typeof(BlueprintPlanet), typeof(BlueprintColony), typeof(BlueprintStarSystemMap), + typeof(BlueprintColonyTrait), typeof(BlueprintResource), typeof(BlueprintColonyEventResult), + typeof(BlueprintUnitAsksList), typeof(BlueprintAbilityFXSettings), typeof(BlueprintAreaPreset), + typeof(BlueprintItemMechadendrite) + }; + + private static BlueprintIdCache _instance; + public static BlueprintIdCache Instance { + get { + if (_instance == null) { + Load(); + } + return _instance; + } + } + + private static bool? _needsCacheRebuilt = null; + public static bool NeedsCacheRebuilt { + get { + if (_needsCacheRebuilt.HasValue) return _needsCacheRebuilt.Value && !isRebuilding; + + bool gameVersionChanged = GameVersion.GetVersion() != Instance.CachedGameVersion; + + var ummSet = Instance.UmmList.ToHashSet(); + bool ummModsChanged = !(ummSet.Count == UnityModManagerNet.UnityModManager.ModEntries.Count); + if (!ummModsChanged) { + foreach (var modEntry in UnityModManagerNet.UnityModManager.ModEntries) { + if (!ummSet.Contains(new(modEntry.Info.Id, modEntry.Info.Version))) { + ummModsChanged = true; + break; + } + } + } + + var ommSet = Instance.OmmList.ToHashSet(); + bool ommModsChanged = !(ommSet.Count == OwlcatModificationsManager.s_Instance.AppliedModifications.Count()); + if (!ommModsChanged) { + foreach (var modEntry in OwlcatModificationsManager.s_Instance.AppliedModifications) { + if (!ommSet.Contains(new(modEntry.Manifest.UniqueName, modEntry.Manifest.Version))) { + ommModsChanged = true; + break; + } + } + } + ModKit.Mod.Log($"Test for BPId Cache constincy: Game Version Changed: {gameVersionChanged}; UMM Mod Changed: {ummModsChanged}; OMM Mod Changed: {ommModsChanged}"); + _needsCacheRebuilt = gameVersionChanged || ummModsChanged || ommModsChanged; + return _needsCacheRebuilt.Value && !isRebuilding; + } + } + + public static bool isRebuilding = false; + internal static void RebuildCache(List<SimpleBlueprint> blueprints) { + if (!Main.Settings.toggleUseBPIdCache) return; + ModKit.Mod.Log("Starting to build BPId Cache"); + isRebuilding = true; + try { + // Header + Instance.CachedGameVersion = BlueprintLoader.GameVersion; + + Instance.UmmList.Clear(); + foreach (var modEntry in UnityModManagerNet.UnityModManager.ModEntries) { + Instance.UmmList.Add(new(modEntry.Info.Id, modEntry.Info.Version)); + } + + Instance.OmmList.Clear(); + foreach (var modEntry in OwlcatModificationsManager.s_Instance.AppliedModifications) { + Instance.OmmList.Add(new(modEntry.Manifest.UniqueName, modEntry.Manifest.Version)); + } + + //Ids + Instance.IdsByType.Clear(); + foreach (var type in CachedIdTypes) { + HashSet<string> idsForType = new(); + foreach (var bp in blueprints) { + if (type.IsInstanceOfType(bp)) { + idsForType.Add(bp.AssetGuid); + } + } + Instance.IdsByType[type] = idsForType; + } + + Instance.Save(); + } catch (Exception ex) { + ModKit.Mod.Error(ex.ToString()); + } + _needsCacheRebuilt = false; + isRebuilding = false; + ModKit.Mod.Log("Finished rebuilding BPId Cache"); + } + internal void Save() { + using var stream = new FileStream(EnsureFilePath(), FileMode.Create, FileAccess.Write); + using var writer = new BinaryWriter(stream, Encoding.UTF8); + + writer.Write(CachedGameVersion); + + writer.Write(UmmList.Count); + foreach (var (item1, item2) in UmmList) { + writer.Write(item1); + writer.Write(item2); + } + + writer.Write(OmmList.Count); + foreach (var (item1, item2) in OmmList) { + writer.Write(item1); + writer.Write(item2); + } + + writer.Write(IdsByType.Count); + foreach (var kvp in IdsByType) { + writer.Write(kvp.Key.AssemblyQualifiedName); + writer.Write(kvp.Value.Count); + foreach (var guid in kvp.Value) { + writer.Write(guid.ToString()); + } + } + } + private static void Load() { + try { + using var stream = new FileStream(EnsureFilePath(), FileMode.Open, FileAccess.Read); + using var reader = new BinaryReader(stream, Encoding.UTF8); + + BlueprintIdCache result = new(); + result.CachedGameVersion = reader.ReadString(); + + int ummCount = reader.ReadInt32(); + for (int i = 0; i < ummCount; i++) { + result.UmmList.Add((reader.ReadString(), reader.ReadString())); + } + + int ommCount = reader.ReadInt32(); + for (int i = 0; i < ommCount; i++) { + result.OmmList.Add((reader.ReadString(), reader.ReadString())); + } + + int dictCount = reader.ReadInt32(); + for (int i = 0; i < dictCount; i++) { + + string typeName = reader.ReadString(); + Type type = Type.GetType(typeName); + if (type == null) ModKit.Mod.Error(new SerializationException($"Type {typeName} not found.").ToString()); + + int listCount = reader.ReadInt32(); + var guidList = new HashSet<string>(); + for (int j = 0; j < listCount; j++) { + guidList.Add(reader.ReadString()); + } + result.IdsByType[type] = guidList; + } + _instance = result; + } catch (FileNotFoundException) { + _instance = new(); + _instance.Save(); + } + } + + private static string EnsureFilePath() { + var userConfigFolder = Path.Combine(Main.ModEntry.Path, "UserSettings"); + Directory.CreateDirectory(userConfigFolder); + return Path.Combine(userConfigFolder, "BlueprintIdCache.bin"); + } + } +} \ No newline at end of file diff --git a/ToyBox/Classes/Infrastructure/Blueprints/BlueprintLoader.cs b/ToyBox/Classes/Infrastructure/Blueprints/BlueprintLoader.cs new file mode 100644 index 000000000..c2d1242c0 --- /dev/null +++ b/ToyBox/Classes/Infrastructure/Blueprints/BlueprintLoader.cs @@ -0,0 +1,398 @@ +// Copyright < 2021 > Narria(github user Cabarius) - License: MIT +using HarmonyLib; +using Kingmaker; +using Kingmaker.Blueprints; +using Kingmaker.Blueprints.JsonSystem; +using Kingmaker.Blueprints.JsonSystem.BinaryFormat; +using Kingmaker.Blueprints.JsonSystem.Converters; +using Kingmaker.Localization; +using Kingmaker.Modding; +using ModKit; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Owlcat.Runtime.Core.Utility; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using ToyBox.classes.Infrastructure.Blueprints; + +namespace ToyBox { + public class SharedStringAssetPool : MonoSingleton<SharedStringAssetPool> { + public const int Size = 100; + private readonly ConcurrentQueue<SharedStringAsset> m_Pool = new(); + private SemaphoreSlim m_Available; + private void Awake() { + m_Available = new SemaphoreSlim(0, int.MaxValue); + for (var i = 0; i < Size; i++) { + var inst = UnityEngine.ScriptableObject.CreateInstance<SharedStringAsset>(); + m_Pool.Enqueue(inst); + m_Available.Release(); + } + } + private void OnDestroy() { + while (m_Pool.TryDequeue(out var result)) { + Destroy(result); + } + m_Available.Dispose(); + } + private void Update() { + var toAdd = Size - m_Pool.Count; + for (var i = 0; i < toAdd; i++) { + var inst = UnityEngine.ScriptableObject.CreateInstance<SharedStringAsset>(); + m_Pool.Enqueue(inst); + m_Available.Release(); + } + } + public SharedStringAsset Request() { + if (UnityEngine.Object.CurrentThreadIsMainThread()) { + return UnityEngine.ScriptableObject.CreateInstance<SharedStringAsset>(); + } else { + m_Available.Wait(); + if (m_Pool.TryDequeue(out var requested)) { + return requested; + } else { + Mod.Log($"[Critical] SharedStringAssetPool had no instance in pool despite signalling availability! {new StackTrace()}"); + throw new InvalidOperationException($"SharedStringAssetPool had no instance in pool"); + } + } + } + } + public class BlueprintLoader { + public static string GameVersion; + public delegate void LoadBlueprintsCallback(List<SimpleBlueprint> blueprints); + private List<SimpleBlueprint> blueprints; + private Dictionary<Type, List<SimpleBlueprint>> bpsByType = new(); + private HashSet<SimpleBlueprint> bpsToAdd = new(); + internal bool CanStart = false; + public float progress = 0; + private static BlueprintLoader loader; + public static BlueprintLoader Shared { + get { + if (GameVersion.IsNullOrEmpty()) { + GameVersion = Kingmaker.GameInfo.GameVersion.GetVersion(); + } + loader ??= new(); + return loader; + } + } + internal readonly HashSet<string> BadBlueprints = new() { "ce0842546b73aa34b8fcf40a970ede68", "2e3280bf21ec832418f51bee5136ec7a", + "b60252a8ae028ba498340199f48ead67", "fb379e61500421143b52c739823b4082", "5d2b9742ce82457a9ae7209dce770071" }; + private void Load(LoadBlueprintsCallback callback, ISet<string> toLoad = null) { + lock (loader) { + if (IsLoading || (!CanStart && ResourcesLibrary.BlueprintsCache.m_PackFile == null) || blueprints != null) return; + loader.Init(callback, toLoad); + } + } + public bool IsLoading => loader.IsRunning; + public List<SimpleBlueprint> GetBlueprints() { + if (blueprints == null) { + lock (loader) { + if (Shared.IsLoading) { + return null; + } else { + Mod.Debug($"Calling BlueprintLoader.Load"); + Shared.Load((bps) => { + lock (bpsToAdd) { + bps.AddRange(bpsToAdd); + bpsToAdd.Clear(); + } + blueprints = bps; + if (BlueprintIdCache.NeedsCacheRebuilt) BlueprintIdCache.RebuildCache(blueprints); + bpsByType.Clear(); + }, toLoad); + return null; + } + } + } + lock (bpsToAdd) { + if (bpsToAdd.Count > 0) { + blueprints.AddRange(bpsToAdd); + bpsToAdd.Clear(); + } + } + return blueprints; + } + public static IEnumerable<SimpleBlueprint> BlueprintsOfType(Type type) { + return (IEnumerable<SimpleBlueprint>)AccessTools.Method(typeof(BlueprintLoader), nameof(GetBlueprintsOfType)).MakeGenericMethod(type).Invoke(Shared, null); + } + public IEnumerable<BPType> GetBlueprintsOfType<BPType>() where BPType : SimpleBlueprint { + if (blueprints == null) { + if (Main.Settings.toggleUseBPIdCache && !BlueprintIdCache.NeedsCacheRebuilt) { + if (bpsByType.TryGetValue(typeof(BPType), out var bps)) { + return bps.Cast<BPType>(); + } else if (BlueprintIdCache.Instance.IdsByType.TryGetValue(typeof(BPType), out var ids)) { + if (!Shared.IsLoading) { + Load(bps => { + IEnumerable<BPType> toAdd; + toAdd = bpsToAdd.OfType<BPType>() ?? new List<BPType>(); + bps.AddRange(toAdd); + bpsByType[typeof(BPType)] = bps; + }, ids); + } + return new List<BPType>(); + } + } + } else { + return blueprints.OfType<BPType>(); + } + return GetBlueprints()?.OfType<BPType>() ?? new List<BPType>(); + } + public IEnumerable<BPType> GetBlueprintsByGuids<BPType>(IEnumerable<string> guids) where BPType : SimpleBlueprint { + foreach (var guid in guids) { + var bp = ResourcesLibrary.TryGetBlueprint(guid) as BPType; + if (bp != null) { + yield return bp; + } + } + } + public bool IsRunning = false; + private LoadBlueprintsCallback _callback; + private List<Task> _workerTasks; + private ConcurrentQueue<IEnumerable<(string, int)>> _chunkQueue; + private List<SimpleBlueprint> _blueprints; + private List<ConcurrentDictionary<string, Object>> _startedLoadingShards = new(); + private int closeCount; + private int total; + private ISet<string> toLoad; + public void Init(LoadBlueprintsCallback callback, ISet<string> toLoad) { + closeCount = 0; + _startedLoadingShards.Clear(); + for (int i = 0; i < Main.Settings.BlueprintsLoaderNumShards; i++) { + _startedLoadingShards.Add(new()); + } + _callback = callback; + _workerTasks = new(); + this.toLoad = toLoad; + IsRunning = true; + Task.Run(Run); + } + public void Run() { + try { + var watch = Stopwatch.StartNew(); + var bpCache = ResourcesLibrary.BlueprintsCache; + IEnumerable<string> allEntries; + var toc = bpCache.m_LoadedBlueprints; + if (toLoad == null) { + allEntries = toc.OrderBy(e => e.Value.Offset).Select(e => e.Key); + } else { + allEntries = toc.Where(item => toLoad.Contains(item.Key)).OrderBy(e => e.Value.Offset).Select(e => e.Key); + } + total = allEntries.Count(); + Mod.Log($"Loading {total} Blueprints"); + _blueprints = new(total); + _blueprints.AddRange(Enumerable.Repeat<SimpleBlueprint>(null, total)); + var memStream = new MemoryStream(); + lock (bpCache.m_Lock) { + bpCache.m_PackFile.Position = 0; + bpCache.m_PackFile.CopyTo(memStream); + } + var chunks = allEntries.Select((entry, index) => (entry, index)).Chunk(Main.Settings.BlueprintsLoaderChunkSize); + _chunkQueue = new(chunks); + var bytes = memStream.GetBuffer(); + for (int i = 0; i < Main.Settings.BlueprintsLoaderNumThreads; i++) { + var t = Task.Run(() => HandleChunks(bytes)); + _workerTasks.Add(t); + } + Task.Run(Progressor); + foreach (var task in _workerTasks) { + task.Wait(); + } + _blueprints.RemoveAll(b => b is null); + watch.Stop(); + Mod.Log($"Threaded loaded roughly {_blueprints.Count + bpsToAdd.Count + BlueprintLoaderPatches.BlueprintsCache_Patches.IsLoading.Value.Count} blueprints in {watch.ElapsedMilliseconds} milliseconds"); + toLoad = null; + lock (loader) { + _callback(_blueprints); + IsRunning = false; + } + } catch (Exception ex) { + Mod.Log($"$[Critical] BP Loader method threw! {ex}"); + throw; + } + } + public void HandleChunks(byte[] bytes) { + try { + Stream stream = new MemoryStream(bytes); + stream.Position = 0; + var seralizer = new ReflectionBasedSerializer(new PrimitiveSerializer(new BinaryReader(stream), UnityObjectConverter.AssetList)); + int closeCountLocal = 0; + while (_chunkQueue.TryDequeue(out var entries)) { + if (closeCountLocal > 250) { + lock (_blueprints) { + closeCount += closeCountLocal; + } + closeCountLocal = 0; + } + foreach (var entryPairA in entries) { + var guid = entryPairA.Item1; + try { + Object @lock = new(); + lock (@lock) { + int shardIndex = Math.Abs(guid.GetHashCode()) % Main.Settings.BlueprintsLoaderNumShards; + var startedLoading = _startedLoadingShards[shardIndex]; + if (!startedLoading.TryAdd(guid, @lock)) continue; + if (ResourcesLibrary.BlueprintsCache.m_LoadedBlueprints.TryGetValue(guid, out var entry)) { + if (entry.Blueprint != null) { + closeCountLocal++; + _blueprints[entryPairA.Item2] = entry.Blueprint; + continue; + } + } else { + continue; + } + if (Shared.BadBlueprints.Contains(guid.ToString()) || entry.Offset == 0U) continue; + OnBeforeBPLoad(guid); + stream.Seek(entry.Offset, SeekOrigin.Begin); + SimpleBlueprint simpleBlueprint = null; + seralizer.Blueprint(ref simpleBlueprint); + if (simpleBlueprint == null) { + closeCountLocal++; + continue; + } + object obj = ResourcesLibrary.BlueprintsCache.m_resourceReplacementProvider?.OnResourceLoaded(simpleBlueprint, guid) ?? null; + if (obj != null) { + simpleBlueprint = (obj as SimpleBlueprint) ?? simpleBlueprint; + } + entry.Blueprint = simpleBlueprint; + simpleBlueprint.OnEnable(); + _blueprints[entryPairA.Item2] = simpleBlueprint; + ResourcesLibrary.BlueprintsCache.m_LoadedBlueprints[guid] = entry; + closeCountLocal++; + OnAfterBPLoad(guid); + } + } catch (Exception ex) { + Mod.Warn($"Exception loading blueprint {guid}:\n{ex}"); + closeCountLocal++; + } + } + } + } catch (Exception ex) { + Mod.Error($"Exception loading blueprints:\n{ex}"); + } + } + // These methods exist to allow external mods some interfacing since the bp load bypasses the regular BlueprintsCache.Load. + // Not using delegate since those would have problems with reloading during runtime. + [MethodImpl(MethodImplOptions.NoInlining)] + public static void OnBeforeBPLoad(string bp) { + + } + [MethodImpl(MethodImplOptions.NoInlining)] + public static void OnAfterBPLoad(string bp) { + + } + public void Progressor() { + while (loader.IsRunning) { + progress = closeCount / (float)total; + progress = Math.Min(Math.Max(progress, 0), 1); + Thread.Sleep(200); + } + } + [HarmonyPatch] + internal static class BlueprintLoaderPatches { + [HarmonyPatch(typeof(SharedStringConverter), nameof(SharedStringConverter.ReadJson)), HarmonyPrefix] + internal static bool SharedStringConverter_ReadJson_Patch(ref object __result, Newtonsoft.Json.JsonReader reader) { + if (reader.TokenType == JsonToken.Null) { + __result = null; + return false; + } + string text = (string)JObject.Load(reader)["stringkey"]; + SharedStringAsset sharedStringAsset = SharedStringAssetPool.Instance.Request(); + sharedStringAsset.String = new LocalizedString { + Key = text + }; + __result = sharedStringAsset; + return false; + } + [HarmonyPatch(typeof(BlueprintsCache))] + internal static class BlueprintsCache_Patches { + [HarmonyPatch(nameof(BlueprintsCache.AddCachedBlueprint)), HarmonyPostfix] + internal static void AddCachedBlueprint(string guid, SimpleBlueprint bp) { + if (Shared.IsLoading || Shared.blueprints != null) { + lock (Shared.bpsToAdd) { + Shared.bpsToAdd.Add(bp); + } + } + if (Shared.IsRunning) { + int shardIndex = Math.Abs(guid.GetHashCode()) % Main.Settings.BlueprintsLoaderNumShards; + Shared._startedLoadingShards[shardIndex].TryAdd(guid, Shared); + } + } + [HarmonyPatch(nameof(BlueprintsCache.RemoveCachedBlueprint)), HarmonyPostfix] + internal static void RemoveCachedBlueprint(string guid) { + lock (Shared.bpsToAdd) { + Shared.bpsToAdd.RemoveWhere(bp => bp.AssetGuid == guid); + } + } + internal static ThreadLocal<HashSet<string>> IsLoading = new(() => []); + [HarmonyPatch(nameof(BlueprintsCache.Load)), HarmonyPrefix] + public static bool Pre_Load(string guid, ref SimpleBlueprint __result) { + if (!Shared.IsRunning) return true; + int shardIndex = Math.Abs(guid.GetHashCode()) % Main.Settings.BlueprintsLoaderNumShards; + var startedLoading = Shared._startedLoadingShards[shardIndex]; + if (startedLoading.TryAdd(guid, Shared)) { + IsLoading.Value.Add(guid); + return true; + } + lock (startedLoading[guid]) { + if (ResourcesLibrary.BlueprintsCache.m_LoadedBlueprints.TryGetValue(guid, out var entry)) { + __result = entry.Blueprint; + } else { + __result = null; + } + } + return false; + } + [HarmonyPatch(nameof(BlueprintsCache.Load)), HarmonyPostfix] + public static void Post_Load(string guid, ref SimpleBlueprint __result) { + if (IsLoading.Value.Remove(guid)) { + lock (Shared.bpsToAdd) { + if (__result != null) Shared.bpsToAdd.Add(__result); + } + } + } + } + [HarmonyPatch(typeof(BlueprintsCache), nameof(BlueprintsCache.Init)), HarmonyFinalizer] + internal static void BlueprintsCache_Init_Patch() { + Shared.CanStart = true; + if (Main.Settings.togglePreloadBlueprints || (Main.Settings.toggleUseBPIdCache && Main.Settings.toggleAutomaticallyBuildBPIdCache && BlueprintIdCache.NeedsCacheRebuilt)) Shared.GetBlueprints(); + } + [HarmonyPatch(typeof(OwlcatModificationBlueprintPatcher), nameof(OwlcatModificationBlueprintPatcher.ApplyPatchEntry)), HarmonyPrefix] + private static bool OwlcatModificationBlueprintPatcher_ApplyPatchEntry(JObject jsonBlueprint, JObject patchEntry) { + JsonMergeSettings settings = new() { + MergeArrayHandling = OwlcatModificationBlueprintPatcher.ExtractMergeArraySettings(patchEntry), + MergeNullValueHandling = OwlcatModificationBlueprintPatcher.ExtractNullArraySettings(patchEntry) + }; + jsonBlueprint.Merge(patchEntry, settings); + return false; + } + private static readonly ConcurrentDictionary<SimpleBlueprint, JObject> m_JsonBlueprintsCache = []; + [ThreadStatic] + private static StringBuilder? m_Builder; + [HarmonyPatch(typeof(OwlcatModificationBlueprintPatcher), nameof(OwlcatModificationBlueprintPatcher.GetJObject)), HarmonyPrefix] + private static bool OwlcatModificationBlueprintPatcher_GetJObject(SimpleBlueprint blueprint, ref JObject __result) { + if (m_JsonBlueprintsCache.TryGetValue(blueprint, out JObject jsonBlueprint)) { + __result = jsonBlueprint; + return false; + } + var blueprintJsonWrapper = new BlueprintJsonWrapper(blueprint) { + AssetId = blueprint.AssetGuid.ToString() + }; + m_Builder ??= new(64); + using var stringWriter = new StringWriter(m_Builder); + Json.Serializer.Serialize(stringWriter, blueprintJsonWrapper); + var jobject = JObject.Parse(m_Builder.ToString()); + m_JsonBlueprintsCache[blueprint] = jobject; + __result = jobject; + m_Builder.Clear(); + return false; + } + } + } +} diff --git a/ToyBox/classes/Infrastructure/Borrowed/Accessors.cs b/ToyBox/Classes/Infrastructure/Borrowed/Accessors.cs similarity index 100% rename from ToyBox/classes/Infrastructure/Borrowed/Accessors.cs rename to ToyBox/Classes/Infrastructure/Borrowed/Accessors.cs diff --git a/ToyBox/classes/Infrastructure/Borrowed/PartyUtils.cs b/ToyBox/Classes/Infrastructure/Borrowed/PartyUtils.cs similarity index 70% rename from ToyBox/classes/Infrastructure/Borrowed/PartyUtils.cs rename to ToyBox/Classes/Infrastructure/Borrowed/PartyUtils.cs index 7efb8de90..84c8f54c0 100644 --- a/ToyBox/classes/Infrastructure/Borrowed/PartyUtils.cs +++ b/ToyBox/Classes/Infrastructure/Borrowed/PartyUtils.cs @@ -1,16 +1,16 @@ // borrowed shamelessly and enhanced from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/26, which is under the MIT License -using System.Collections.Generic; -using System.Linq; using Kingmaker; using Kingmaker.EntitySystem.Entities; using Kingmaker.UnitLogic; +using System.Collections.Generic; +using System.Linq; namespace ToyBox { internal class PartyUtils { - public static List<UnitEntityData> GetCustomCompanions() { + public static List<BaseUnitEntity> GetCustomCompanions() { var unitEntityData = Game.Instance.Player.AllCharacters; - List<UnitEntityData> unitEntityDataNew = new(); + List<BaseUnitEntity> unitEntityDataNew = new(); foreach (var unit in unitEntityData) { if (unit.IsCustomCompanion()) { @@ -20,16 +20,16 @@ public static List<UnitEntityData> GetCustomCompanions() { return unitEntityDataNew; } - public static List<UnitEntityData> GetPets() { + public static List<BaseUnitEntity> GetPets() { var unitEntityData = Game.Instance.Player.AllCharacters; - List<UnitEntityData> unitEntityDataNew = new(); + List<BaseUnitEntity> unitEntityDataNew = new(); foreach (var unit in unitEntityData) { - if (unit.IsPet) { + if (unit.Master != null && (unit.Master.IsInPlayerParty || unit.Master.IsPlayerFaction())) { unitEntityDataNew.Add(unit); } } return unitEntityDataNew; } - public static List<UnitEntityData> GetRemoteCompanions() => Game.Instance.Player.AllCharacters.Except(Game.Instance.Player.Party).Where(unit => !unit.IsPet).ToList(); + public static List<BaseUnitEntity> GetRemoteCompanions() => Game.Instance.Player.AllCharacters.Except(Game.Instance.Player.Party).Where(unit => !unit.IsPet).ToList(); } } diff --git a/ToyBox/Classes/Infrastructure/CharacterPicker.cs b/ToyBox/Classes/Infrastructure/CharacterPicker.cs new file mode 100644 index 000000000..930ba1442 --- /dev/null +++ b/ToyBox/Classes/Infrastructure/CharacterPicker.cs @@ -0,0 +1,102 @@ +// Copyright < 2021 > Narria (github user Cabarius) - License: MIT +using Kingmaker; +using Kingmaker.Designers; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.Mechanics.Entities; +using ModKit; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using static ModKit.UI; + +namespace ToyBox { + public class CharacterPicker { + public static NamedFunc<List<BaseUnitEntity>>[] PartyFilterChoices = null; + private static readonly Player partyFilterPlayer = null; + public static float nearbyRange = 25; + public static IEnumerable<BaseUnitEntity> GetTargetsAround(Vector3 point, int radius, bool checkLOS = true, bool includeDead = false) { + foreach (AbstractUnitEntity abstractUnitEntity in Game.Instance.State.AllUnits.OfType<BaseUnitEntity>()) { + BaseUnitEntity baseUnitEntity = (BaseUnitEntity)abstractUnitEntity; + if ((!baseUnitEntity.LifeState.IsDead || includeDead) && !baseUnitEntity.Features.IsUntargetable && baseUnitEntity.IsUnitInRangeCells(point, radius, checkLOS)) { + yield return baseUnitEntity; + } + } + yield break; + } + public static NamedFunc<List<BaseUnitEntity>>[] GetPartyFilterChoices() { + if (partyFilterPlayer != Game.Instance.Player) PartyFilterChoices = null; + if (Game.Instance.Player != null && PartyFilterChoices == null) { + PartyFilterChoices = new NamedFunc<List<BaseUnitEntity>>[] { + new NamedFunc<List<BaseUnitEntity>>("Party".localize(), () => Game.Instance.Player.Party), + new NamedFunc<List<BaseUnitEntity>>("Party & Pets".localize(), () => Game.Instance.Player.m_PartyAndPets), + new NamedFunc<List<BaseUnitEntity>>("All".localize(), () => Game.Instance.Player.AllCharactersAndStarships), + new NamedFunc<List<BaseUnitEntity>>("Active".localize(), () => Game.Instance.Player.ActiveCompanions), + new NamedFunc<List<BaseUnitEntity>>("Remote".localize(), () => Game.Instance.Player.m_RemoteCompanions), + new NamedFunc<List<BaseUnitEntity>>("Custom".localize(), PartyUtils.GetCustomCompanions), + new NamedFunc<List<BaseUnitEntity>>("Pets".localize(), PartyUtils.GetPets), + new NamedFunc<List<BaseUnitEntity>>("Starships".localize(), () => Game.Instance.Player.AllStarships), + //new NamedFunc<List<UnitEntityData>>("Familiars", Game.Instance.Player.Party.SelectMany(ch => ch.Familiars), + new NamedFunc<List<BaseUnitEntity>>("Nearby".localize(), () => { + var player = GameHelper.GetPlayerCharacter(); + return (player == null) ? new() : GetTargetsAround(player.Position, (int)nearbyRange , false, false).ToList(); + }), + new NamedFunc<List<BaseUnitEntity>>("Friendly".localize(), () => Shodan.AllBaseUnits.Where((u) => u != null && !u.IsEnemy(GameHelper.GetPlayerCharacter())).ToList()), + new NamedFunc<List<BaseUnitEntity>>("Enemies".localize(), () => Shodan.AllBaseUnits.Where((u) => u != null && u.IsEnemy(GameHelper.GetPlayerCharacter())).ToList()), + new NamedFunc<List<BaseUnitEntity>>("All Units".localize(), () => Shodan.AllBaseUnits.m_Entities), + }; + } + return PartyFilterChoices; + } + public static List<BaseUnitEntity> GetCharacterList() { + var partyFilterChoices = GetPartyFilterChoices(); + return partyFilterChoices?[Main.Settings.selectedPartyFilter].func(); + } + + private static int _selectedIndex = 0; + public static BaseUnitEntity GetSelectedCharacter() { + var characters = GetCharacterList(); + if (characters == null || characters.Count == 0) { + return Game.Instance.Player.MainCharacterEntity; + } + if (_selectedIndex >= characters.Count) _selectedIndex = 0; + return characters[_selectedIndex]; + } + public static void ResetGUI() => _selectedIndex = 0; + + public static NamedFunc<List<BaseUnitEntity>> OnFilterPickerGUI() { + var filterChoices = GetPartyFilterChoices(); + if (filterChoices == null) { return null; } + + var characterListFunc = TypePicker( + null, + ref Main.Settings.selectedPartyFilter, + filterChoices, + true + ); + return characterListFunc; + } + public static void OnCharacterPickerGUI(float indent = 0) { + + var characters = GetCharacterList(); + if (characters == null) { return; } + using (HorizontalScope(AutoWidth())) { + Space(indent); + ActionSelectionGrid(ref _selectedIndex, + characters.Select((ch) => ch.CharacterName).ToArray(), + 8, + null, + AutoWidth()); + } + var selectedCharacter = GetSelectedCharacter(); + if (selectedCharacter != null) { + using (HorizontalScope(AutoWidth())) { + Space(indent); + Label($"{selectedCharacter.CharacterName}".Orange().Bold(), AutoWidth()); + Space(5); + Label("will be used for editing ".localize().Green()); + } + } + } + } +} \ No newline at end of file diff --git a/ToyBox/Classes/Infrastructure/Compatibility.cs b/ToyBox/Classes/Infrastructure/Compatibility.cs new file mode 100644 index 000000000..23bfc1e25 --- /dev/null +++ b/ToyBox/Classes/Infrastructure/Compatibility.cs @@ -0,0 +1,124 @@ +// global statics +// common alternate using +global using Kingmaker.Blueprints.Base; +global using Kingmaker.Code.UI.MVVM; +global using Kingmaker.Code.UI.MVVM.VM.Loot; +global using Kingmaker.EntitySystem; +global using Kingmaker.EntitySystem.Entities.Base; +global using Kingmaker.EntitySystem.Stats.Base; +global using Kingmaker.PubSubSystem.Core; +global using Kingmaker.UI.Models.Tooltip.Base; +global using Kingmaker.UnitLogic.Levelup.Obsolete.Blueprints; +global using Kingmaker.UnitLogic.Levelup.Obsolete.Blueprints.Spells; +global using Kingmaker.UnitLogic.Progression.Features; +global using Kingmaker.Utility.DotNetExtensions; +global using Kingmaker.Utility.UnityExtensions; +// global using Owlcat.Runtime.Core; +// global using Owlcat.Runtime.Core.Utility; +global using static ModKit.UI; +// Type Aliases +using Kingmaker; +// Local using +using Kingmaker.AreaLogic.Etudes; +using Kingmaker.Blueprints; +using Kingmaker.Blueprints.Area; +using Kingmaker.Blueprints.Items; +using Kingmaker.Blueprints.Root; +using Kingmaker.Cheats; +using Kingmaker.Code.UI.MVVM.View.ServiceWindows.CharacterInfo; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.EntitySystem.Persistence; +using Kingmaker.Localization; +using Kingmaker.Mechanics.Entities; +using Kingmaker.PubSubSystem; +using Kingmaker.UI; +using Kingmaker.UnitLogic.Levelup.Components; +using Kingmaker.UnitLogic.Parts; +using ModKit; +using System; +using System.Collections.Generic; +using System.Linq; +using UniRx; + +namespace ToyBox { + public static partial class Shodan { + + // General Stuff + public static string StringValue(this LocalizedString locStr) => locStr.Text; + public static bool IsNullOrEmpty(this string str) => str == null || str.Length == 0; + public static BaseUnitEntity Descriptor(this BaseUnitEntity entity) => entity; + public static float GetCost(this BlueprintItem item) => item.ProfitFactorCost; + public static Etude GetFact(this EtudesTree tree, BlueprintEtude blueprint) => tree.RawFacts.FirstItem(i => i.Blueprint == blueprint); + + // Hacks to get around ambiguity in Description due to te ILootable interface in BaseUnitEntity + public static Gender? GetCustomGender(this BaseUnitEntity unit) { + return unit.Description.CustomGender; + } + public static void SetCustomGender(this BaseUnitEntity unit, Gender gender) { + unit.Description.CustomGender = gender; + } + public static bool CanPlay(this BlueprintEtude etudeBP) { + try { + var etudesTree = Game.Instance.Player.EtudesSystem.Etudes; + var etude = etudesTree.Get(etudeBP); + if (etude != null) + return etudesTree.EtudeCanPlay(etude); + } catch (Exception ex) { + Mod.Error(ex); + } + return true; + } + public static Dictionary<string, object> GetInGameSettingsList() => Game.Instance?.State?.InGameSettings?.List; + + // Unit Entity Utils + public static BaseUnitEntity MainCharacter => Game.Instance.Player.MainCharacterEntity; + public static EntityPool<AbstractUnitEntity>? AllUnits => Game.Instance?.State?.AllUnits; + public static EntityPool<BaseUnitEntity>? AllBaseUnits => Game.Instance?.State?.AllBaseUnits; + public static List<BaseUnitEntity> SelectedUnits => UIAccess.SelectionManager.SelectedUnits.ToList(); + public static ReactiveCollection<BaseUnitEntity> SelectedUnitsReactive() => UIAccess.SelectionManager.SelectedUnits; + public static bool IsEnemy(this BaseUnitEntity unit) { + PartFaction factionOptional = unit.GetFactionOptional(); + return factionOptional != null && factionOptional.IsPlayerEnemy; + } + public static bool IsPlayerFaction(this BaseUnitEntity unit) { + PartFaction factionOptional = unit.GetFactionOptional(); + return factionOptional != null && factionOptional.IsPlayer; + } + public static void KillUnit(BaseUnitEntity unit) => CheatsCombat.KillUnit(unit); + public static bool ToyBoxIsPartyOrPet(this MechanicEntity entity) => Game.Instance.Player.PartyAndPets.Contains(entity); + public static bool HasBonusForLevel(this BlueprintStatProgression xpTable, int level) => level >= 0 && level < xpTable.Bonuses.Length; + public static float GetMaxSpeed(List<BaseUnitEntity> data) => data.Select(u => u.OwnerEntity.Movable.ModifiedSpeedMps).Max(); + + public static void EnterToArea(BlueprintAreaEnterPoint enterPoint) => Game.Instance.LoadArea(enterPoint, AutoSaveMode.None, null); + + public static bool CanRespec(this BaseUnitEntity ch) { + bool ret = ch != null && !ch.LifeState.IsDead && !ch.IsPet; + if (ret) { + CharacterLevelLimit component = ch.OriginalBlueprint.GetComponent<CharacterLevelLimit>(); + int num = ((component != null) ? component.LevelLimit : 0); + if (Main.Settings.toggleSetDefaultRespecLevelZero) { + return ch.Progression.CharacterLevel > 0; + } else { + return ch.Progression.CharacterLevel > num; + } + } + return ret; + } + public static void DoRespec(this BaseUnitEntity ch) { + var pet = ch.Pet; + ch.Progression.Respec(); + EventBus.RaiseEvent<IRespecHandler>(ch, delegate (IRespecHandler h) { + h.HandleRespecFinished(); + }, true); + EventBus.RaiseEvent<INewServiceWindowUIHandler>(delegate (INewServiceWindowUIHandler h) { + h.HandleOpenCharacterInfoPage(CharInfoPageType.LevelProgression, ch); + }, true); + if (pet != null && ch.Pet == null) { + // Not doing the following lines will cause saving coroutine to fail after respeccing a unit with a pet, kicking the user back to main menu. + Game.Instance.Player.CrossSceneState.RemoveEntityData(pet); + Game.Instance.Player.InvalidateCharacterLists(); + Game.Instance.SelectionCharacter.UpdateSelectedUnits(); + } + } + } +} diff --git a/ToyBox/Classes/Infrastructure/OwlLogging.cs b/ToyBox/Classes/Infrastructure/OwlLogging.cs new file mode 100644 index 000000000..b4109588e --- /dev/null +++ b/ToyBox/Classes/Infrastructure/OwlLogging.cs @@ -0,0 +1,232 @@ +using HarmonyLib; +using Kingmaker; +using Kingmaker.EntitySystem.Persistence; +using ModKit; +using ModKit.Utility; +using Newtonsoft.Json; +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace ToyBox; +public static class OwlLogging { + private static FieldInfo[] generalFields = null; + private static FieldInfo[] perSaveFields = null; + private static Dictionary<FieldInfo, object> generalSettings; + private static Dictionary<FieldInfo, object> perSaveSettings; + public static void PopulateGeneralSettings() { + generalSettings = new(); + if (Main.Settings != null) { + if (generalFields == null) generalFields = typeof(Settings).GetFields(BindingFlags.Instance | BindingFlags.Public); + foreach (var field in generalFields) { + generalSettings[field] = field.GetValue(Main.Settings); + } + } + } + public static void PopulatePerSaveSettings() { + perSaveSettings = new(); + Settings.ClearCachedPerSave(); + if (Main.Settings.perSave != null) { + if (perSaveFields == null) perSaveFields = typeof(PerSaveSettings).GetFields(BindingFlags.Instance | BindingFlags.Public); + foreach (var field in perSaveFields) { + perSaveSettings[field] = field.GetValue(Main.Settings.perSave); + } + } + } + public static void OnChange() { + try { + var info = SaveInfo.Instance; + var settings = new Settings(); + var perSave = new PerSaveSettings(); + if (info == null) { + info = new(); + SaveInfo.Instance = info; + } + PopulateGeneralSettings(); + foreach (var pair in generalSettings) { + var defaultVal = pair.Key.GetValue(settings); + if (info.ChangedSettings.TryGetValue(pair.Key.Name, out var val)) { + bool isSimple = TypeManager.IsSimpleType(pair.Key.FieldType); + bool areEqual = false; + if (isSimple) { + var method = AccessTools.Method(pair.Key.FieldType, "TryParse", [typeof(string), pair.Key.FieldType.MakeByRefType()]); + object[] parameters = [val, Activator.CreateInstance(pair.Key.FieldType)]; + bool success = (bool)(method?.Invoke(null, parameters) ?? false); + if (success) { + areEqual = TypeManager.AreObjectsEqual(parameters[1], pair.Value); + } + } + if (!areEqual) { + if (TypeManager.AreObjectsEqual(defaultVal, pair.Value)) { + info.ChangedSettings.Remove(pair.Key.Name); + string toLog = "Mod Setting set to default: " + pair.Key.Name; + Log(toLog); + } else { + if (isSimple) { + if (isSimple) { + string toLog = "Mod Setting changed: " + pair.Key.Name; + toLog += $"; prev value: {val}; new value: {pair.Value}"; + Log(toLog); + } + info.ChangedSettings[pair.Key.Name] = pair.Value.ToString(); + } else { + info.ChangedSettings[pair.Key.Name] = "!!Non-Primitive Type!!"; + } + } + } + } else { + if (!TypeManager.AreObjectsEqual(defaultVal, pair.Value)) { + string toLog = "Mod Setting changed: " + pair.Key.Name; + if (TypeManager.IsSimpleType(pair.Key.FieldType)) { + toLog += $"; prev value: {defaultVal}; new value: {pair.Value}"; + } + Log(toLog); + if (TypeManager.IsSimpleType(pair.Key.FieldType)) { + info.ChangedSettings[pair.Key.Name] = pair.Value.ToString(); + } else { + info.ChangedSettings[pair.Key.Name] = "!!Non-Primitive Type!!"; + } + } + } + } + PopulatePerSaveSettings(); + foreach (var pair in perSaveSettings) { + var defaultVal = pair.Key.GetValue(perSave); + if (info.ChangedSettings.TryGetValue(pair.Key.Name, out var val)) { + bool isSimple = TypeManager.IsSimpleType(pair.Key.FieldType); + bool areEqual = false; + if (isSimple) { + var method = AccessTools.Method(pair.Key.FieldType, "TryParse", [typeof(string), pair.Key.FieldType.MakeByRefType()]); + object[] parameters = [val, Activator.CreateInstance(pair.Key.FieldType)]; + bool success = (bool)(method?.Invoke(null, parameters) ?? false); + if (success) { + areEqual = TypeManager.AreObjectsEqual(parameters[1], pair.Value); + } + } + if (!areEqual) { + if (TypeManager.AreObjectsEqual(defaultVal, pair.Value)) { + info.ChangedSettings.Remove(pair.Key.Name); + string toLog = "Per-Save Setting removed: " + pair.Key.Name; + Log(toLog); + } else { + if (isSimple) { + if (isSimple) { + string toLog = "Per-Save Setting changed: " + pair.Key.Name; + toLog += $"; prev value: {val}; new value: {pair.Value}"; + Log(toLog); + } + info.ChangedSettings[pair.Key.Name] = pair.Value.ToString(); + } else { + info.ChangedSettings[pair.Key.Name] = "!!Non-Primitive Type!!"; + } + } + } + } else { + if (!TypeManager.AreObjectsEqual(defaultVal, pair.Value)) { + string toLog = "Per-Save Setting changed: " + pair.Key.Name; + if (TypeManager.IsSimpleType(pair.Key.FieldType)) { + toLog += $"; prev value: {defaultVal}; new value: {pair.Value}"; + } + Log(toLog); + if (TypeManager.IsSimpleType(pair.Key.FieldType)) { + info.ChangedSettings[pair.Key.Name] = pair.Value.ToString(); + } else { + info.ChangedSettings[pair.Key.Name] = "Non-Primitive Type"; + } + } + } + } + } catch (Exception ex) { + Mod.Error(ex.ToString()); + } + } + public static void OnHideGUI() { + if (Game.Instance?.Player != null && Main.Settings?.perSave != null) { + OnChange(); + } + } + public static void Log(string toAdd) { + if (SaveInfo.Instance != null) { + Mod.Debug(toAdd); + var timeString = DateTimeOffset.Now.ToString("[dd.MM.yyyy HH:mm:ss:ffff]"); + SaveInfo.Instance.History.Add(timeString + ": " + toAdd); + } + } + [Serializable] + public class SaveInfo { + [JsonIgnore] + public static SaveInfo Instance; + [JsonProperty] + public SerializableDictionary<string, string> ChangedSettings = new(); + [JsonProperty] + public List<string> History = new(); + } + public static class TypeManager { + private static readonly ConcurrentDictionary<Type, bool> IsSimpleTypeCache = new ConcurrentDictionary<Type, bool>(); + + public static bool IsSimpleType(Type type) { + return IsSimpleTypeCache.GetOrAdd(type, t => + type.IsPrimitive || + type.IsEnum || + type == typeof(string) || + type == typeof(decimal) || + type == typeof(DateTime) || + type == typeof(DateTimeOffset) || + type == typeof(TimeSpan) || + type == typeof(Guid) || + IsNullableSimpleType(type)); + + static bool IsNullableSimpleType(Type t) { + var underlyingType = Nullable.GetUnderlyingType(t); + return underlyingType != null && IsSimpleType(underlyingType); + } + } + public static bool AreObjectsEqual(object obj1, object obj2) { + if (obj1 == null || obj2 == null) { + return obj1 == obj2; + } + + Type type1 = obj1.GetType(); + Type type2 = obj2.GetType(); + + if (type1 != type2) { + return false; + } + + if (IsSimpleType(type1)) { + return obj1.Equals(obj2); + } else if (typeof(IEnumerable).IsAssignableFrom(type1)) { + return CompareEnumerables((IEnumerable)obj1, (IEnumerable)obj2); + } else { + return obj1.Equals(obj2); + } + } + private static bool CompareEnumerables(IEnumerable enum1, IEnumerable enum2) { + var enum1List = new List<object>(); + var enum2List = new List<object>(); + + foreach (var item in enum1) { + enum1List.Add(item); + } + foreach (var item in enum2) { + enum2List.Add(item); + } + + if (enum1List.Count != enum2List.Count) { + return false; + } + + for (int i = 0; i < enum1List.Count; i++) { + if (!AreObjectsEqual(enum1List[i], enum2List[i])) { + return false; + } + } + return true; + } + } +} diff --git a/ToyBox/Classes/Infrastructure/OwlLoggingSaveHooker.cs b/ToyBox/Classes/Infrastructure/OwlLoggingSaveHooker.cs new file mode 100644 index 000000000..1884ea057 --- /dev/null +++ b/ToyBox/Classes/Infrastructure/OwlLoggingSaveHooker.cs @@ -0,0 +1,89 @@ +using HarmonyLib; +using Kingmaker.EntitySystem.Persistence; +using Kingmaker; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Kingmaker.Blueprints.JsonSystem; +using System.IO.Compression; +using ModKit; +using Kingmaker.GameInfo; + +namespace ToyBox; +[HarmonyPatch] +internal static class SaveHooker { + [HarmonyPatch(typeof(ZipSaver))] + [HarmonyPatch("SaveJson"), HarmonyPrefix] + //[HarmonyPatch("SaveJson"), HarmonyPostfix] + private static void Zip_Saver(string name, ZipSaver __instance) { + if (!name.StartsWith("header")) + return; + + OwlLogging.OnChange(); + try { + var serializer = new JsonSerializer(); + serializer.Formatting = Formatting.Indented; + var writer = new StringWriter(); + serializer.Serialize(writer, OwlLogging.SaveInfo.Instance); + writer.Flush(); + /* + ZipArchiveEntry zipArchiveEntry = __instance.FindEntry(LoadHooker.FileName); + if (zipArchiveEntry == null) { + zipArchiveEntry = __instance.ZipFile.CreateEntry(LoadHooker.FileName); + } + using (Stream stream = zipArchiveEntry.Open()) { + using (StreamWriter streamWriter = new StreamWriter(stream)) { + streamWriter.Write(writer.ToString()); + stream.SetLength(stream.Position); + } + } + */ + Game.Instance.State.InGameSettings.List[LoadHooker.FileName] = writer.ToString(); + } catch (Exception e) { + Main.logger.Log(e.ToString()); + } + } +} + +[HarmonyPatch(typeof(Game))] +internal static class LoadHooker { + public const string FileName = "ToyBox.log"; + + [HarmonyPatch(nameof(Game.LoadGame)), HarmonyPostfix] + private static void LoadGame(SaveInfo saveInfo) { + try { + using (saveInfo) { + using (saveInfo.GetReadScope()) { + string raw = null; + /* + * using (ZipSaver saver = saveInfo.Saver.Clone() as ZipSaver) { + using var stream = saver?.FindEntry(FileName)?.Open(); + if (stream != null) { + raw = new StreamReader(stream).ReadToEnd(); + } + } + */ + object rawObj = null; + Game.Instance?.State?.InGameSettings?.List.TryGetValue(FileName, out rawObj); + raw = (string)rawObj; + if (raw != null) { + var serializer = new JsonSerializer(); + var rawReader = new StringReader(raw); + var jsonReader = new JsonTextReader(rawReader); + OwlLogging.SaveInfo.Instance = serializer.Deserialize<OwlLogging.SaveInfo>(jsonReader); + } else { + OwlLogging.SaveInfo.Instance = new OwlLogging.SaveInfo(); + } + OwlLogging.Log($"Safe loaded with ToyBox v{Main.ModEntry.Version} and Game v{GameVersion.GetVersion()}"); + } + } + } catch (Exception e) { + Main.logger.Error(e.ToString()); + } + OwlLogging.OnChange(); + } +} \ No newline at end of file diff --git a/ToyBox/Classes/Infrastructure/Performance.cs b/ToyBox/Classes/Infrastructure/Performance.cs new file mode 100644 index 000000000..e381d7893 --- /dev/null +++ b/ToyBox/Classes/Infrastructure/Performance.cs @@ -0,0 +1,187 @@ +using HarmonyLib; +using Kingmaker.Blueprints; +using Kingmaker.Blueprints.JsonSystem; +using Kingmaker.Blueprints.JsonSystem.BinaryFormat; +using Kingmaker.Blueprints.JsonSystem.Helpers; +using Kingmaker.EntitySystem.Persistence; +using Kingmaker.EntitySystem.Stats; +using Kingmaker.UnitLogic; +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace ToyBox { + //[HarmonyPatch] + public static class Performance { + [HarmonyPatch] + internal static class ReflectionBasedSerializer_PerformancePatches { + private static ConcurrentDictionary<(Type, Type), bool> HasAttributeCache = new(); + private static ConcurrentDictionary<(Type, Type), bool> IsListOfCache = new(); + private static ConcurrentDictionary<(Type, Type), bool> IsListCache = new(); + private static ConcurrentDictionary<(Type, Type), bool> IsOrSubclassOfCache = new(); + private static ConcurrentDictionary<Type, Func<object>> TypeConstructorCache = new(); + private static ConcurrentDictionary<Type, Func<int, Array>> ArrayTypeConstructorCache = new(); + private static MethodInfo Activator_CreateInstance = AccessTools.Method(typeof(Activator), nameof(Activator.CreateInstance), [typeof(Type)]); + private static MethodInfo Array_CreateInstance = AccessTools.Method(typeof(Array), nameof(Array.CreateInstance), [typeof(Type), typeof(int)]); + private static MethodInfo ReflectionBasedSerializer_CreateObject = AccessTools.Method(typeof(ReflectionBasedSerializer), nameof(ReflectionBasedSerializer.CreateObject)); + [HarmonyTargetMethods] + internal static IEnumerable<MethodBase> GetMethods() { + var ret = new List<MethodBase>(); + foreach (var method in typeof(ReflectionBasedSerializer).GetMethods(AccessTools.all).Concat(typeof(PrimitiveSerializer).GetMethods(AccessTools.all)).Concat(typeof(BlueprintFieldsTraverser).GetMethods(AccessTools.all)).Concat(typeof(FieldsContractResolver).GetMethods(AccessTools.all))) { + try { + foreach (var instruction in PatchProcessor.GetCurrentInstructions(method) ?? new()) { + if (instruction.Calls(Activator_CreateInstance)) { + ret.Add(method); + break; + } else if (instruction.Calls(Array_CreateInstance)) { + ret.Add(method); + break; + } else if (instruction.Calls(ReflectionBasedSerializer_CreateObject)) { + ret.Add(method); + break; + } else if (instruction.opcode == OpCodes.Call && instruction.operand is MethodInfo methodInfo) { + if (methodInfo.Name == "HasAttribute" && methodInfo.IsGenericMethod) { + ret.Add(method); + break; + } else if (methodInfo.Name == "IsListOf" && methodInfo.IsGenericMethod) { + ret.Add(method); + break; + } else if (methodInfo.Name == "IsList" && methodInfo.IsGenericMethod) { + ret.Add(method); + break; + } else if (methodInfo.Name == "IsOrSubclassOf" && methodInfo.IsGenericMethod) { + ret.Add(method); + break; + } + } + } + } catch { } + } + return ret; + } + [HarmonyTranspiler] + internal static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { + var method1 = AccessTools.Method(typeof(ReflectionBasedSerializer_PerformancePatches), nameof(HasAttribute)); + var method2 = AccessTools.Method(typeof(ReflectionBasedSerializer_PerformancePatches), nameof(IsListOf)); + var method3 = AccessTools.Method(typeof(ReflectionBasedSerializer_PerformancePatches), nameof(IsList)); + var method4 = AccessTools.Method(typeof(ReflectionBasedSerializer_PerformancePatches), nameof(IsOrSubclassOf)); + foreach (var instruction in instructions) { + if (instruction.Calls(Activator_CreateInstance)) { + yield return CodeInstruction.Call((Type t) => CreateInstance(t)); + continue; + } else if (instruction.Calls(Array_CreateInstance)) { + yield return CodeInstruction.Call((Type t, int n) => CreateArrayInstance(t, n)); + continue; + } else if (instruction.Calls(ReflectionBasedSerializer_CreateObject)) { + yield return CodeInstruction.Call((Type t) => CreateObject(t)); + continue; + } else if (instruction.opcode == OpCodes.Call && instruction.operand is MethodInfo methodInfo) { + if (methodInfo.Name == "HasAttribute" && methodInfo.IsGenericMethod) { + var genericArguments = methodInfo.GetGenericArguments(); + yield return new CodeInstruction(OpCodes.Ldtoken, genericArguments[0]); + yield return new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(Type), nameof(Type.GetTypeFromHandle))); + yield return new CodeInstruction(OpCodes.Call, method1); + continue; + } else if (methodInfo.Name == "IsListOf" && methodInfo.IsGenericMethod) { + var genericArguments = methodInfo.GetGenericArguments(); + yield return new CodeInstruction(OpCodes.Ldtoken, genericArguments[0]); + yield return new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(Type), nameof(Type.GetTypeFromHandle))); + yield return new CodeInstruction(OpCodes.Call, method2); + continue; + } else if (methodInfo.Name == "IsList" && methodInfo.IsGenericMethod) { + var genericArguments = methodInfo.GetGenericArguments(); + yield return new CodeInstruction(OpCodes.Ldtoken, genericArguments[0]); + yield return new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(Type), nameof(Type.GetTypeFromHandle))); + yield return new CodeInstruction(OpCodes.Call, method3); + continue; + } else if (methodInfo.Name == "IsOrSubclassOf" && methodInfo.IsGenericMethod) { + var genericArguments = methodInfo.GetGenericArguments(); + yield return new CodeInstruction(OpCodes.Ldtoken, genericArguments[0]); + yield return new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(Type), nameof(Type.GetTypeFromHandle))); + yield return new CodeInstruction(OpCodes.Call, method4); + continue; + } + } + yield return instruction; + } + } + public static object CreateInstance(Type type) { + if (!TypeConstructorCache.TryGetValue(type, out var f)) { + if (type.GetConstructor(Type.EmptyTypes) is { } constructor) { + f = TypeConstructorCache[type] = + Expression.Lambda<Func<object>>( + Expression.Convert( + Expression.New(constructor), typeof(object))) + .Compile(); + } else { + f = TypeConstructorCache[type] = null; + } + } + if (f == null) return Activator.CreateInstance(type); + return f(); + } + public static object CreateObject(Type type) { + if (!TypeConstructorCache.TryGetValue(type, out var f)) { + if (type.GetConstructor(Type.EmptyTypes) is { } constructor) { + f = TypeConstructorCache[type] = + Expression.Lambda<Func<object>>( + Expression.Convert( + Expression.New(constructor), typeof(object))) + .Compile(); + } else { + f = TypeConstructorCache[type] = null; + } + } + if (f == null) return FormatterServices.GetUninitializedObject(type); + return f(); + } + public static object CreateArrayInstance(Type type, int length) { + if (!ArrayTypeConstructorCache.TryGetValue(type, out var f)) { + var param = Expression.Parameter(typeof(int), "length"); + var newArrayExpression = Expression.NewArrayBounds(type, param); + var lambda = Expression.Lambda<Func<int, Array>>(newArrayExpression, param); + + f = ArrayTypeConstructorCache[type] = lambda.Compile(); + } + if (f == null) return Array.CreateInstance(type, length); + return f(length); + } + public static bool HasAttribute(Type t, Type T) { + if (!HasAttributeCache.TryGetValue((t, T), out var hasAttr)) { + hasAttr = t.GetCustomAttributes(T, true).Length != 0; + HasAttributeCache[(t, T)] = hasAttr; + } + return hasAttr; + } + public static bool IsListOf(Type t, Type T) { + if (!IsListOfCache.TryGetValue((t, T), out var isListOf)) { + isListOf = t.IsGenericType && t.GetGenericTypeDefinition() == typeof(List<>) && t.GenericTypeArguments[0] == T; + IsListOfCache[(t, T)] = isListOf; + } + return isListOf; + } + public static bool IsList(Type t, Type T) { + if (!IsListCache.TryGetValue((t, T), out var isList)) { + isList = t.IsGenericType && t.GetGenericTypeDefinition() == typeof(List<>); + IsListCache[(t, T)] = isList; + } + return isList; + } + public static bool IsOrSubclassOf(Type t, Type T) { + if (!IsOrSubclassOfCache.TryGetValue((t, T), out var isSubclassOf)) { + isSubclassOf = t == T || t.IsSubclassOf(T); + IsOrSubclassOfCache[(t, T)] = isSubclassOf; + } + return isSubclassOf; + } + } + } +} diff --git a/ToyBox/Classes/Infrastructure/RTExtensions.cs b/ToyBox/Classes/Infrastructure/RTExtensions.cs new file mode 100644 index 000000000..1d5c3b606 --- /dev/null +++ b/ToyBox/Classes/Infrastructure/RTExtensions.cs @@ -0,0 +1,35 @@ +// Copyright < 2021 > Narria (github user Cabarius) - License: MIT +using HarmonyLib; +using Kingmaker; +using Kingmaker.Blueprints; +using Kingmaker.Blueprints.Classes; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.Localization; +using Kingmaker.UI; +using Kingmaker.UI.Common; +//using Kingmaker.UI.LevelUp.Phase; +using Kingmaker.UnitLogic; +using Kingmaker.UnitLogic.Alignments; +using Kingmaker.UnitLogic.Mechanics; +using Kingmaker.Utility; +using ModKit; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using static Kingmaker.Items.WeaponStatsHelper; +using Alignment = Kingmaker.Enums.Alignment; + +namespace ToyBox { + public static class RTExtensions { + public static string HashKey(this BaseUnitEntity ch) => ch.UniqueId; + public static string HashKey(this MechanicEntity entity) => entity.UniqueId; + [Obsolete] + public static string HashKey(this BlueprintCharacterClass cl) => cl.NameSafe(); + public static string HashKey(this BlueprintArchetype arch) => arch.NameSafe(); + public static BaseUnitEntity GetCurrentCharacter() { + var firstSelectedUnit = Game.Instance.SelectionCharacter.FirstSelectedUnit; + return (object)firstSelectedUnit != null ? firstSelectedUnit : (BaseUnitEntity)Game.Instance.Player.MainCharacterEntity; + } + } +} \ No newline at end of file diff --git a/ToyBox/Classes/Infrastructure/SettingsDefaults.cs b/ToyBox/Classes/Infrastructure/SettingsDefaults.cs new file mode 100644 index 000000000..1ce1c17c9 --- /dev/null +++ b/ToyBox/Classes/Infrastructure/SettingsDefaults.cs @@ -0,0 +1,123 @@ +using Kingmaker.Enums.Damage; +using ModKit.Utility; +using System; +using System.Collections.Generic; + +namespace ToyBox { + internal static class SettingsDefaults { + public static SerializableDictionary<StatType, float> DefaultEnemyStatMods(float defaultVal) { + var ret = new SerializableDictionary<StatType, float>(); + foreach (StatType stat in Enum.GetValues(typeof(StatType))) { + ret[stat] = defaultVal; + } + return ret; + } + public static readonly HashSet<string> DefaultBuffsToIgnoreForDurationMultiplier = new() { + "24cf3deb078d3df4d92ba24b176bda97", //Prone + "e6f2fc5d73d88064583cb828801212f4", //Fatigued + "bb1b849f30e6464284c1efd0e812d626", //Army Nauseated + "f59aa0658cda4c7b82bf73c632a39650", //Army Stinking Cloud + "6179bbe7a7b4b674c813dedbca121799", //Summoned Unit Appear Buff (causes inaction for summoned units) + "12f2f2cf326dfd743b2cce5b14e99b3c", //Resurrection Buff + "4e9ddf0456c4d65498ad90fe6e621c3b", //Ranged Legerdemain Buff + /* Cooldown Buffs */ + "03b2f5afd3d54131967312a97fa5462e", // WitchHexIceTombCooldownBuff + "75f14be8cb1d4cf6abe5ec4abd598381", // WitchHexWitheringCooldownBuff + "e9a4c63018894996a2133b640d7c6dab", // WitchHexDireProphecyBuffCooldownBuff + "d051ff5949794ca78ca3fbaeb26bd1a2", // TrickRidingCooldownBuff + "4bc4450170dc4c2782ced03240e2009d", // ArmyBardAurasCooldownBuff + "8328890b12294dd79c82f3818369e0a5", // ArmyLayOnHandsSelfCooldownBuff + "3801e343e55b47ddbdce3b537c32eef6", // ArmyRetrieverEyerRay_CooldownBuff + "fbfdca2aca3bb45458b6c0af4df7bb90", // ArmyChargeAbilityCooldown + "a37592486280448dba8f78af33690ffe", // FighterAttackFormationCooldownBuff + "178ed2b0b04b4f5cb890009638ade3ea", // FighterChargeFormationCooldownBuff + "4840e9fb2bcd4c68b78e313d905e2132", // FighterDefenceFormationCooldownBuff + "fa037b990d8c49ffa8977479a7a3eed7", // FighterFearsomeFormationCooldownBuff + "a474c827d6e1bfd46956b8d841a496c7", // CompanionKTC_Cooldown + "f469d4c7e7a34c3984ce15f0f4431bbc", // DogBark_Cooldown + "222829a37640ceb47a810be9996b6159", // HepzamirahSummonsBE_Cooldown + "13b6a92f2bc47da4994f09bdb2a4a9e0", // NocticulaSummonsBE_Cooldown + "89b501348d054d6408a930efd6105200", // SpontaneousHealingCooldown + "0a52573d3076420b8ccdde7db49b4fcb", // CelestialTotemLesserBuffCooldown + "a115039fdc47c7848a05390a294dbcbf", // DeadlyPerformanceCooldownBuff + "f6164abbbbe246d3b9ac4c8a93e52456", // FearsomeInspirationHiddenBuffCooldown + "c3ac324ad4725434790d9544ab9d73ee", // ArmigerArdentRerollCooldown + "65058aafc91a12042b158527f9d0506a", // TrueJudgmentCooldownBuff + "f80bdf69ef8bc3743a9b18667ba9684e", // MasterSpyCooldownBuff + "077f4430a10d3504b9078ab717334972", // MasterHunterCooldownBuff + "6f33cd117281834468ee73e79a0367c6", // MasterStrikeCooldownBuff + "faa0697caa542f34688f82244ccc2c5f", // ShamanHexAmelioratingCooldownBuffI + "9906228821acb2a49ad1a013751de037", // ShamanHexAmelioratingCooldownBuffII + "98fa71575440d8a4d8ce15b70ece5568", // ShamanHexDraconicResilienceCooldownBuff + "6382987b74a30884e83197077b2a5148", // ShamanHexFortuneCooldownBuff + "002b4f05558eec844ab1c0cf5d0e714a", // ShamanHexFuryCooldownBuff + "efa4562d6a8161e48bf8b9498bac90f6", // ShamanHexHealingCooldownBuff + "b20644cbc292adf4ca2007a5ccd27c65", // ShamanHexMisfortuneCooldownBuff + "27ae9c5de3920794fbca334bd131f085", // ShamanHexSlumberCooldownBuff + "20d695886f0c0f24185ebd645c110298", // ShamanHexBattleWardCooldownBuff + "21ad57f770cad7c4eb5f91510120f170", // ShamanHexHamperingHexCooldownBuff + "a0d09af0a86f78d4b9778e0b4ce0f069", // ShamanHexBoneWardCooldownBuff + "22e2eb141d3925c419932407f2d61217", // ShamanHexFearfulGazeCooldownBuff + "76bdae64447923a45853911f2abbdf05", // ShamanHexFireNimbusCooldownBuff + "abb89285366b2c94898c1125a1a3747e", // ShamanHexFlameCurseCooldownBuff + "61851fcdb9e95304e861e3843896282d", // ShamanHexWardOfFlamesCooldownBuff + "547ec39778e37e3419ed67c887231241", // ShamanHexHypothermiaCooldownBuff + "e6e98105f86eb904c9af0a2c1f5a6647", // ShamanHexEntanglingCurseCooldownBuff + "cca470fbc764ba645b07d855c6af431b", // ShamanHexMetalCurseCooldownBuff + "c2303ebc0d83671468644f7b42a420c0", // ShamanHexBeckoningChillCooldownBuff + "80c3307481e58fc45a56f21e3b5ec70c", // ShamanHexWindWardCooldownBuff + "2778a8154f1e4e3a87b5bce12ec35ac8", // GreaterWyrmshifterBreathWeaponCooldownBuff + "beb1f5e3c7e84de08d095af1be154b47", // RageshaperGroundSlamCooldownBuff + "e24693e33559eb8448ef27385e6c5a9c", // BloodlineCelestialHeavenlyFireCooldownBuff + "34dad0518f8eb4040938346f7b78ff8e", // BloodlineFeyLaughingTouchCooldownBuff + "d1a4d8957788d8f4ead60f09aa228382", // HagboundWitchVileCurseCooldownBuff + "b00cd2d31d2d87148a6dad31caa697a8", // WitchHexAmelioratingCooldownBuffI + "a94647a0e45c86445adcbc91ac25b0bf", // WitchHexAmelioratingCooldownBuffII + "d03be82c4ffd14840a89e600e910ae7d", // WitchHexFortuneCooldownBuff + "2a09996f7f5f9f845a1b58d0a759621d", // WitchHexHealingCooldownBuff + "f3dced28095610f459fef4441012ffc1", // WitchHexMisfortuneCooldownBuff + "1ad64500bb3364442a9c7d28cf9a24d3", // WitchHexSlumberCooldownBuff + "6f3da77a44fa7304fac61c07a01964a5", // WitchHexVulnerabilityCurseCooldownBuff + "c1cbd350af845214b8ef11b0da9dd304", // WitchHexAnimalServantCooldownBuff + "ba2a266ffb7fb6246831ae72eee08d20", // WitchHexDeathCurseCooldownBuff + "12fd1d05f54fe07479ac48eff49265dc", // WitchHexLayToRestCooldownBuff + "73921d85ed0d684408c5b5f23b6f4360", // WitchHexAgonyCooldownBuff + "69d4d478357f1fc49bb38c6c64b0bb48", // WitchHexHoarfrostCooldownBuff + "a009621bc2d137c4d89f15eff9f10792", // WitchHexMajorAmelioratingCooldownBuffI + "34e1e519e3dca424ab9914256acc79b1", // WitchHexMajorAmelioratingCooldownBuffII + "245d0006a26235a4f9d8bfdb5d5e303d", // WitchHexMajorHealingCooldownBuff + "6d1f2d9d46484ba4ab052f65f5d0b422", // WitchHexRegenerativeSinewCooldownBuff + "05f4111baf9924a4c9efa35835c1e302", // WitchHexRestlessSlumberCooldownBuff + "5616b7189359a784396275b470e6dd8b", // FaithStealingStrikeCooldownBuff + "7e20d7440c9b4872b44fa3b9fd0d125e", // AnomalyDistortionCooldownBuff + "24775ab6c0c7403082b3fedeff76d28d", // MaskOfTheFastBitesCooldownBuff + "cfc52ecc6fb34cdfac78fa3986aeb221", // MaskOfAreshkagalHeadband_TabulaRasaCooldownBuff + "d868546877b2de24388fbdd5741d0c95", // CleavingFinishCooldown + "8b765e0ef53b4e8e9b495a685e00b8d7", // FrightfulShapeCooldownBuff + "34762bab68ec86c45a15884b9a9929fc", // IndomitableMountCooldownBuff + "5c9ef8224acdbab4fbaf59c710d0ef23", // MountedCombatCooldownBuff + "0d02b41741498e8478695d77ab527b03", // VolleyFireCooldownBuff + "a98394128e4c41509c1a873e4faf914a", // DragonAzataBreathCooldown + "99d08a20ac1f4da3b9db2ed3db38a898", // DragonAzataFrightfulPresenceCooldownBuff + "72f7f1a64a634b84b64c4cd59ef19ad5", // VavakiaAspectDamageCooldown + "4e9ddf0456c4d65498ad90fe6e621c3b", // GoldenDragonBreathCooldown + "2936f138e42c6bd459c4bbd300cddc78", // FearControlCooldown + "6f47e92597a27384bbfe3bcdf2d05566", // LastStandBuffCooldown + "d4fe4b361568cbf4c816fcd6f39b79be", // BeggarDialogueCooldown + "7039b533f248a9a4aa3abffd6e83c9ca", // SoothingMudCooldownBuff + "69bb26014fdb0484d9d3e711b874f853", // SerenityCooldownBuff + "d50c08a3c00775241ac781932fc3aa02", // EyebiteCooldownBuff + "a7a5d1143490dae49b8603810866cf4d", // FormOfTheDragonIIBreathWeaponCooldownBuff + "4934c3a12cfa4f4488834bdcea3b6fbc", // JoltingPortentBuffCooldown + "08af6d8af6241c84ea66baeafc8222b9", // FormOfTheDragonIIIFrightfulPresenceCooldownBuff + "85e11efe92991694882cfdb417a941bd", // FrightfulAspectCooldownBuff + "f4cb0148083231745a9cd971fc6ee9c9", // BlackDragonFrightfulPresenceCooldownBuff + "e6f07f58f07b47b1b83d595c5a7a3baf", // TerendelevUndead_Buff_FrightfulPresenceCooldown + "3e75d2d398aa60f44849757d011a27f8", // ZachariusFearAuraCooldown + "2730acd272e412e47a32f680a68d0a1f" // WhispersOfMadnessEffectCooldown + }; + + public static void InitializeDefaultDamageTypes() { + } + } +} diff --git a/ToyBox/Classes/Infrastructure/Teleport.cs b/ToyBox/Classes/Infrastructure/Teleport.cs new file mode 100644 index 000000000..1cbd5ea4f --- /dev/null +++ b/ToyBox/Classes/Infrastructure/Teleport.cs @@ -0,0 +1,99 @@ +// Copyright < 2021 > Narria (github user Cabarius) - License: MIT +// based on code by hambeard (thank you ^_^) +using JetBrains.Annotations; +using Kingmaker; +using Kingmaker.Blueprints.Area; +using Kingmaker.Cheats; +using Kingmaker.Code.UI.MVVM.View.ServiceWindows.LocalMap.PC; +using Kingmaker.Code.UI.MVVM.VM.ServiceWindows.LocalMap.Utils; +using Kingmaker.Designers; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.EntitySystem.Persistence; +using Kingmaker.GameModes; +using Kingmaker.Globalmap; +using Kingmaker.Globalmap.Blueprints; +using Kingmaker.Globalmap.View; +using Kingmaker.Mechanics.Entities; +using Kingmaker.PubSubSystem; +using Kingmaker.PubSubSystem.Core; +using Kingmaker.Utility; +using Kingmaker.View; +using Kingmaker.View.Mechanics.Entities; +using Kingmaker.Visual.LocalMap; +using ModKit; +using System; +using System.Linq; +using UnityEngine; +using UnityModManagerNet; + +namespace ToyBox { + public static partial class Teleport { + public static Settings Settings => Main.Settings; + //private static readonly HoverHandler _hover = new(); + + public static void TeleportSelected() { + foreach (var unit in Shodan.SelectedUnits) { + TeleportUnit(unit, Utils.PointerPosition()); + } + } + + public static void TeleportParty() { + foreach (var unit in Game.Instance.Player.m_PartyAndPets) { + TeleportUnit(unit, Utils.PointerPosition()); + } + } + public static void TeleportPartyToPlayer() { + var currentMode = Game.Instance.CurrentMode; + var partyMembers = Game.Instance.Player.m_PartyAndPets; + if (currentMode == GameModeType.Default || currentMode == GameModeType.Pause) { + foreach (var unit in partyMembers) { + if (unit != Shodan.MainCharacter) { + unit.Commands.InterruptMove(); + unit.Commands.InterruptMove(); + unit.Position = Shodan.MainCharacter.Position; + } + } + } + } + public static void TeleportEveryoneToPlayer() { + var currentMode = Game.Instance.CurrentMode; + if (currentMode == GameModeType.Default || currentMode == GameModeType.Pause) { + foreach (var unit in Shodan.AllUnits) { + if (unit != Shodan.MainCharacter) { + unit.Commands.InterruptMove(); + unit.Commands.InterruptMove(); + unit.Position = Shodan.MainCharacter.Position; + } + } + } + } + + public static void To(this BlueprintAreaEnterPoint enterPoint) => Shodan.EnterToArea(enterPoint); + public static void To(this BlueprintArea area) { + var areaEnterPoints = BlueprintLoader.Shared.GetBlueprintsOfType<BlueprintAreaEnterPoint>(); + var blueprint = areaEnterPoints.FirstOrDefault(bp => bp is BlueprintAreaEnterPoint ep && ep.Area == area); + if (blueprint is BlueprintAreaEnterPoint enterPoint) { + ; Shodan.EnterToArea(enterPoint); + } + } + + internal class HoverHandler : IUnitDirectHoverUIHandler, IDisposable { + public AbstractUnitEntity Unit { get; private set; } + private AbstractUnitEntity _currentUnit; + + public HoverHandler() { + EventBus.Subscribe(this); + } + public void Dispose() => throw new NotImplementedException(); + + public void HandleHoverChange([NotNull] AbstractUnitEntityView unitEntityView, bool isHover) { + if (isHover) _currentUnit = unitEntityView.Data; + } + + public void LockUnit() { + if (_currentUnit != null) + this.Unit = _currentUnit; + } + } + } +} \ No newline at end of file diff --git a/ToyBox/Classes/Infrastructure/TeleportRT.cs b/ToyBox/Classes/Infrastructure/TeleportRT.cs new file mode 100644 index 000000000..02b778d50 --- /dev/null +++ b/ToyBox/Classes/Infrastructure/TeleportRT.cs @@ -0,0 +1,69 @@ +// Copyright < 2021 > Narria (github user Cabarius) - License: MIT +// based on code by hambeard (thank you ^_^) +using JetBrains.Annotations; +using Kingmaker; +using Kingmaker.Blueprints.Area; +using Kingmaker.Blueprints.Root; +using Kingmaker.Cheats; +using Kingmaker.Code.UI.MVVM.View.ServiceWindows.LocalMap.PC; +using Kingmaker.Code.UI.MVVM.VM.ServiceWindows.LocalMap.Utils; +using Kingmaker.Designers; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.EntitySystem.Persistence; +using Kingmaker.GameCommands; +using Kingmaker.GameModes; +using Kingmaker.Globalmap; +using Kingmaker.Globalmap.Blueprints; +using Kingmaker.Globalmap.Blueprints.SectorMap; +using Kingmaker.Globalmap.View; +using Kingmaker.PubSubSystem; +using Kingmaker.PubSubSystem.Core; +using Kingmaker.Utility; +using Kingmaker.View; +using Kingmaker.Visual.LocalMap; +using ModKit; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityModManagerNet; +namespace ToyBox { + public static partial class Teleport { + + public static void TeleportUnis(IEnumerable<BaseUnitEntity> units, Vector3 position) + => CheatsTransfer.LocalTeleport(position, units); + public static void TeleportUnit(BaseUnitEntity unit, Vector3 position) + => CheatsTransfer.LocalTeleport(position, new List<BaseUnitEntity>() { unit }); + + public static void TeleportTo( + [NotNull] BlueprintAreaEnterPoint areaEnterPoint, + bool includeFollowers = false, + Action callback = null) { + if (areaEnterPoint == null) + throw new ArgumentException("areaEnterPoint is null", nameof(areaEnterPoint)); + if (Game.Instance.CurrentlyLoadedArea != areaEnterPoint.Area) + throw new InvalidOperationException(string.Format( + "Cant teleport to {0}. Target zone {1} should be same as current {2}", areaEnterPoint, + areaEnterPoint.Area, Game.Instance.CurrentlyLoadedArea)); + LoadingProcess.Instance.StartLoadingProcess(Game.Instance.TeleportPartyCoroutine(areaEnterPoint, includeFollowers), + () => Game.ExecuteSafe(callback), LoadingProcessTag.TeleportParty); + EventBus.RaiseEvent((Action<IAreaTransitionHandler>)(h => h.HandleAreaTransition())); + } + public static void TeleportToGlobalMap(Action callback = null) { + var globalMap = BlueprintRoot.Instance.SectorMapArea; + var areaEnterPoint = globalMap.SectorMapEnterPoint; + //var areaEnterPoint = globalMap.All.FindOrDefault(i => i.Get().GlobalMapEnterPoint != null)?.Get().GlobalMapEnterPoint; + Game.LoadArea(globalMap, areaEnterPoint, AutoSaveMode.None, callback: callback ?? (() => { })); + } +#if false + public static void To(this BlueprintSectorMapPoint mapPoint) { + Game.Instance.LoadArea(mapPoint. + .GlobalMap.GlobalMapEnterPoint, AutoSaveMode.None, () => { TeleportToGlobalMapPoint(mapPoint); }); + //if (!Teleport.TeleportToGlobalMapPoint(globalMapPoint)) { + // Teleport.TeleportToGlobalMap(() => Teleport.TeleportToGlobalMapPoint(globalMapPoint)); + //} + } +#endif + + } +} \ No newline at end of file diff --git a/ToyBox/Classes/Infrastructure/Update/IntegrityChecker.cs b/ToyBox/Classes/Infrastructure/Update/IntegrityChecker.cs new file mode 100644 index 000000000..d662f2ddb --- /dev/null +++ b/ToyBox/Classes/Infrastructure/Update/IntegrityChecker.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using UnityModManagerNet; + +namespace ToyBox { + public static class IntegrityChecker { + private const string ChecksumFileName = "checksum"; + public static bool Check(UnityModManager.ModEntry.ModLogger logger) { + try { + var curFile = Assembly.GetExecutingAssembly().Location; + var curDir = Path.GetDirectoryName(curFile); + var providedChecksum = File.ReadAllLines(Path.Combine(curDir, ChecksumFileName))[0]; + using var sha256 = SHA256.Create(); + var calculatedChecksum = BitConverter.ToString(sha256.ComputeHash(File.ReadAllBytes(curFile))).Replace("-", ""); + + bool isValid = providedChecksum.Equals(calculatedChecksum, StringComparison.OrdinalIgnoreCase); + if (!isValid) { + logger.Log($"Checksum mismatch! expected: {providedChecksum}, calculated: {calculatedChecksum}"); + } + return isValid; + } catch (Exception ex) { + logger.Log($"Encountered exception while trying to verify checksum: {ex.ToString()}"); + return false; + } + } + } +} diff --git a/ToyBox/Classes/Infrastructure/Update/Updater.cs b/ToyBox/Classes/Infrastructure/Update/Updater.cs new file mode 100644 index 000000000..65d470e7b --- /dev/null +++ b/ToyBox/Classes/Infrastructure/Update/Updater.cs @@ -0,0 +1,101 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO.Compression; +using System.IO; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using UnityModManagerNet; + +namespace ToyBox { + public static class Updater { + private static string GetReleaseName(string version) => $"ToyBox-{version}.zip"; + private static string GetDownloadLink(string repoLink, string version) => $"{repoLink}/releases/download/v{version}/{GetReleaseName(version)}"; + public static bool Update(UnityModManager.ModEntry modEntry, bool force = false, bool reinstallCurrentVersion = false) { + var logger = modEntry.Logger; + var curVersion = modEntry.Info.Version; + FileInfo file = null; + DirectoryInfo tmpDir = null; + try { + using var web = new WebClient(); + var curDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + file = new FileInfo(Path.Combine(curDir, "TmpUpdate.zip")); + tmpDir = new DirectoryInfo(Path.Combine(curDir, "TmpExtract")); + if (file.Exists) { + file.Delete(); + } + if (tmpDir.Exists) { + tmpDir.Delete(true); + } + var definition = new { + Releases = new[] { + new { + Id = "", + Version = "" + } + } + }; + + var raw = web.DownloadString(modEntry.Info.Repository); + var result = JsonConvert.DeserializeAnonymousType(raw, definition); + string remoteVersion = result.Releases[0].Version; + bool repoHasNewVersion = VersionChecker.IsVersionGreaterThan(VersionChecker.GetNumifiedVersion(logger, remoteVersion), VersionChecker.GetNumifiedVersion(logger, curVersion)); + + if (force || repoHasNewVersion) { + var version = reinstallCurrentVersion ? modEntry.Info.Version : remoteVersion; + string downloadUrl = GetDownloadLink(modEntry.Info.HomePage, version); + logger.Log($"Downloading: {downloadUrl}"); + web.DownloadFile(downloadUrl, file.FullName); + using var zipFile = ZipFile.OpenRead(file.FullName); + + // Dry run + foreach (ZipArchiveEntry entry in zipFile.Entries) { + string fullPath = Path.GetFullPath(Path.Combine(tmpDir.FullName, entry.FullName)); + + if (Path.GetFileName(fullPath).Length == 0) { + Directory.CreateDirectory(fullPath); + } else { + Directory.CreateDirectory(Path.GetDirectoryName(fullPath)); + entry.ExtractToFile(fullPath, overwrite: true); + } + } + + // Extract successfully? Then do it again for real + foreach (ZipArchiveEntry entry in zipFile.Entries) { + string fullPath = Path.GetFullPath(Path.Combine(curDir, entry.FullName)); + + if (Path.GetFileName(fullPath).Length == 0) { + Directory.CreateDirectory(fullPath); + } else { + Directory.CreateDirectory(Path.GetDirectoryName(fullPath)); + entry.ExtractToFile(fullPath, overwrite: true); + } + } + + zipFile.Dispose(); + + file.Delete(); + tmpDir.Delete(true); + + logger.Log($"Successfully updated mod to version {remoteVersion}!"); + return true; + } else { + logger.Log($"Already up-to-data! Remote ({remoteVersion}) <= Local ({curVersion})"); + } + } catch (Exception ex) { + logger.Log($"Error trying to update mod: \n{ex.ToString()}"); + } finally { + if (file?.Exists ?? false) { + file.Delete(); + } + if (tmpDir?.Exists ?? false) { + tmpDir.Delete(true); + } + } + return false; + } + } +} diff --git a/ToyBox/Classes/Infrastructure/Update/VersionChecker.cs b/ToyBox/Classes/Infrastructure/Update/VersionChecker.cs new file mode 100644 index 000000000..77f10828f --- /dev/null +++ b/ToyBox/Classes/Infrastructure/Update/VersionChecker.cs @@ -0,0 +1,71 @@ +using Kingmaker.GameInfo; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; +using UnityModManagerNet; + +namespace ToyBox { + public static class VersionChecker { + // Find first entry where Mod Version of entry >= current mod version + // Using that entry, the mod is compatible if the current game version is < than the game version in the entry + // Meaning, an entry [x, y] will, for every mod with version <= x mark every version >= y as incompatible + public static bool IsGameVersionSupported(Version modVersion, UnityModManager.ModEntry.ModLogger logger, string linkToIncompatibilitiesFile) { + try { + var gameVersion = GameVersion.GetVersion(); + logger.Log($"Testing compatability with Game Version: {gameVersion}"); + using var web = new WebClient(); + var raw = web.DownloadString(linkToIncompatibilitiesFile); + var definition = new[] { new[] { "", "" } }; + var versions = JsonConvert.DeserializeAnonymousType(raw, definition); + var currentOrNewer = versions.FirstOrDefault(v => new Version(v[0]) >= modVersion); + if (currentOrNewer == null) return true; + return IsVersionGreaterThan(GetNumifiedVersion(logger, currentOrNewer[1]), GetNumifiedVersion(logger, gameVersion)); + } catch (Exception ex) { + logger.Log(ex.ToString()); + } + return true; + } + public static bool IsVersionGreaterThan(List<BigInteger> a, List<BigInteger> b) { + int maxLen = Math.Max(a.Count, b.Count); + for (int i = 0; i < maxLen; i++) { + BigInteger t = (i < a.Count) ? a[i] : 0; + BigInteger g = (i < b.Count) ? b[i] : 0; + if (t > g) { + return true; + } + if (t < g) { + return false; + } + } + return false; + } + public static List<BigInteger> GetNumifiedVersion(UnityModManager.ModEntry.ModLogger logger, string version) { + var comps = version.Split('.'); + var newComps = new List<BigInteger>(); + foreach (var comp in comps) { + BigInteger num = 0; + foreach (var c in comp) { + try { + if (uint.TryParse(c.ToString(), out var n)) { + num = num * 10u + n; + } else { + int signedCharNumber = char.ToUpper(c) - ' '; + uint unsignedCharNumber = (uint)Math.Max(0, Math.Min(signedCharNumber, 99)); + num = num * 100u + unsignedCharNumber; + } + } catch (Exception ex) { + logger.Log($"Error while trying to numify version component {comp}, continuing with {num}.\n{ex}"); + break; + } + } + newComps.Add(num); + } + return newComps; + } + } +} diff --git a/ToyBox/classes/Infrastructure/Utils.cs b/ToyBox/Classes/Infrastructure/Utils.cs similarity index 67% rename from ToyBox/classes/Infrastructure/Utils.cs rename to ToyBox/Classes/Infrastructure/Utils.cs index b96c5370f..dad81c5b0 100644 --- a/ToyBox/classes/Infrastructure/Utils.cs +++ b/ToyBox/Classes/Infrastructure/Utils.cs @@ -1,17 +1,4 @@ -using Epic.OnlineServices.Lobby; -using Kingmaker; -using Kingmaker.Blueprints; -using Kingmaker.Blueprints.Area; -using Kingmaker.Blueprints.Items; -using Kingmaker.Items; -using Kingmaker.UI.MVVM._PCView.ServiceWindows.LocalMap; -using Kingmaker.UI.MVVM._VM.ServiceWindows.LocalMap.Utils; -using Kingmaker.Utility; -using Kingmaker.Visual.LocalMap; -using ModKit; -using Newtonsoft.Json; -using Owlcat.Runtime.Core.Utils; -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; @@ -19,9 +6,18 @@ using System.Linq; using System.Reflection; using System.Text.RegularExpressions; -using System.Web.UI; +using Kingmaker; +using Kingmaker.Blueprints; +using Kingmaker.Blueprints.Items; +using Kingmaker.Items; +using Kingmaker.Localization; +using Kingmaker.Utility; +using ModKit; +using Newtonsoft.Json; +using Owlcat.Runtime.Core.Utility; using UnityEngine; using Attribute = System.Attribute; +using LocalizationManager = Kingmaker.Localization.LocalizationManager; namespace ToyBox { @@ -37,7 +33,7 @@ public static Vector3 PointerPosition() { return result; } - public static void SaveToFile<T>(this T obj, string filename = null) { + public static void SaveToFile<T>(this T obj, string? filename = null) { if (filename == null) filename = $"{obj.GetType().Name}.json"; var toyboxFolder = ToyBoxUserPath; Directory.CreateDirectory(toyboxFolder); @@ -55,8 +51,7 @@ public static T LoadFromFile<T>(string filename) { using StreamReader reader = new(path); var text = reader.ReadToEnd(); obj = JsonConvert.DeserializeObject<T>(text); - } - catch (Exception e) { + } catch (Exception e) { Mod.Error($"{filename} could not be read: {e}"); } return obj; @@ -77,7 +72,7 @@ public static void Import(this ItemsCollection items, string filename, bool repl } foreach (var guid in guids) { var bp = ResourcesLibrary.TryGetBlueprint<BlueprintItem>(guid); - if (bp != null) + if (bp != null) items.Add(bp); } } @@ -100,28 +95,37 @@ public static Dictionary<string, string> ReadTranslations() { //Mod.Debug($"'{key}' => '{value}'"); } return result; - } - catch (DirectoryNotFoundException) { - Mod.Error("Unable to load localization directory."); + } catch (DirectoryNotFoundException) { + Mod.Warn("Unable to load localization directory."); return new(); - } - catch (FileNotFoundException) { - Mod.Error("Unable to load localization file."); + } catch (FileNotFoundException) { + Mod.Warn("Unable to load Etude localization file."); return new(); } } - public static string ToKM(this float v, string units = "") { + public static string ToKM(this float v, string? units = "") { if (v < 1000) { return $"{v:0}{units}"; - } - else if (v < 1000000) { + } else if (v < 1000000) { v = Mathf.Floor(v / 1000); return $"{v:0.#}k{units}"; } v = Mathf.Floor(v / 1000000); return $"{v:0.#}m{units}"; } - public static string ToBinString(this int v, string units = "", float binSize = 2f) { + public static string ToBinString(this int v, string? units = "", float binSize = 2f) { + if (v < 0) return "< 0"; + binSize = Mathf.Clamp(binSize, 1.1f, 20f); + var logv = Mathf.Log(v) / Mathf.Log(binSize); + var floorLogV = Mathf.Floor(logv); + var min = Mathf.Pow(binSize, floorLogV); + var minStr = min.ToKM(units); + var max = Mathf.Pow(binSize, floorLogV + 1); + if (min == max) return $"{min:0}{units}"; + var maxStr = max.ToKM(units); + return $"{minStr} - {maxStr}"; + } + public static string ToBinString(this float v, string? units = "", float binSize = 2f) { if (v < 0) return "< 0"; binSize = Mathf.Clamp(binSize, 1.1f, 20f); var logv = Mathf.Log(v) / Mathf.Log(binSize); @@ -146,10 +150,9 @@ public static long LongSortKey(this string s) { } return v; } - public static string CollectionToString(this IEnumerable<object> col) => $"{{{string.Join(", ", col.Select(i => i.ToString()))}}}"; // Object to Dictionary public static IDictionary<string, object> ToDictionary(this object source) => source.ToDictionary<object>(); - + public static IDictionary<string, T> ToDictionary<T>(this object source) { if (source == null) ThrowExceptionWhenSourceArgumentIsNull(); @@ -182,12 +185,16 @@ public static string StringValue(this FieldInfo field, object obj) { } public static Dictionary<string, string> ToStringDictionary(this object obj) { var propDict = obj.GetType() - .GetProperties(BindingFlags.Instance | BindingFlags.NonPublic) + .GetProperties(BindingFlags.Instance + | BindingFlags.NonPublic + | BindingFlags.Public) .Where(field => field.GetValue(obj) is string) .ToDictionary(prop => prop.Name, prop => prop.StringValue(obj) ); var fieldDict = obj.GetType() - .GetFields(BindingFlags.Instance | BindingFlags.NonPublic) + .GetFields(BindingFlags.Instance + | BindingFlags.NonPublic + | BindingFlags.Public) .Where(field => field.GetValue(obj) is string) .ToDictionary(field => field.Name, field => field.StringValue(obj) ); @@ -196,55 +203,6 @@ public static Dictionary<string, string> ToStringDictionary(this object obj) { public static Dictionary<string, string> GetCustomAttributes<T>(this T model) where T : class { Dictionary<string, string> result = new Dictionary<string, string>(); -#if true - PropertyInfo[] props = typeof(T).GetProperties(BindingFlags.NonPublic); - foreach (var prop in props) { - var text = prop.GetCustomAttributes(true).OfType<InfoBoxAttribute>().Select(info => info.Text); - if (text != null) { - result[prop.Name] = String.Join(", ", text); - } - } - FieldInfo[] fields = typeof(T).GetFields(BindingFlags.NonPublic); - foreach (var field in fields) { - var text = field.GetCustomAttributes(true).OfType<InfoBoxAttribute>().Select(info => info.Text); - if (text != null) { - result[field.Name] = String.Join(", ", text); - } - } - -#else - PropertyInfo[] props = typeof(T).GetProperties(BindingFlags.NonPublic); - foreach (PropertyInfo prop in props) { - var attributes = prop.GetCustomAttributes(true); - if (attributes.Count() > 0) { - foreach (var attribute in attributes) { - var strings = attribute.ToStringDictionary(); - foreach (var pair in strings) { - try { - result.Add($"{prop.Name}.{pair.Key}", pair.Value); - } - catch { } - } - - } - } - } - FieldInfo[] fields = typeof(T).GetFields(BindingFlags.NonPublic); - foreach (FieldInfo field in fields) { - var attributes = field.GetCustomAttributes(true); - if (attributes.Count() > 0) { - foreach (var attribute in attributes) { - var strings = attribute.ToStringDictionary(); - foreach (var pair in strings) { - try { - result.Add($"{field.Name}.{pair.Key}", pair.Value); - } - catch { } - } - } - } - } -#endif return result; } public static T GetAttributeFrom<T>(this object instance, string propertyName) where T : Attribute { @@ -264,6 +222,97 @@ public int Compare(string left, string right) { return left.CompareTo(right); } } +#if false + public static class TempListExtension { + public static List<T> ToTempList<T>([CanBeNull] this List<T> _this) { + List<T> list = TempList.Get<T>(); + if (_this == null) { + return list; + } + + list.Capacity = System.Math.Max(list.Capacity, _this.Capacity); + foreach (T _thi in _this) { + list.Add(_thi); + } + + return list; + } + + public static List<KeyValuePair<TKey, TValue>> ToTempList<TKey, TValue>([CanBeNull] this Dictionary<TKey, TValue> _this) { + List<KeyValuePair<TKey, TValue>> list = TempList.Get<KeyValuePair<TKey, TValue>>(); + if (_this == null) { + return list; + } + + foreach (KeyValuePair<TKey, TValue> _thi in _this) { + list.Add(_thi); + } + + return list; + } + + public static List<T> ToTempList<T>([CanBeNull] this IEnumerable<T> _this) { + List<T> list = TempList.Get<T>(); + if (_this == null) { + return list; + } + + foreach (T _thi in _this) { + list.Add(_thi); + } + + return list; + } + } + public class TempList { + private abstract class Releasable { + public abstract void ReleaseInternal(); + } + + private class PoolHolder<T> : Releasable { + public static readonly PoolHolder<T> Instance; + + public readonly Stack<List<T>> Pool = new Stack<List<T>>(); + + public readonly Stack<List<T>> Claimed = new Stack<List<T>>(); + + static PoolHolder() { + s_Pools.Add(Instance = new PoolHolder<T>()); + } + + public override void ReleaseInternal() { + while (Claimed.Count > 0) { + List<T> list = Claimed.Pop(); + list.Clear(); + Pool.Push(list); + } + } + } + + private static readonly List<Releasable> s_Pools = new List<Releasable>(); + + public static List<T> Get<T>() { + if (!UnityThreadHolder.IsMainThread) { + return new List<T>(); + } + + List<T> list = ((PoolHolder<T>.Instance.Pool.Count > 0) ? PoolHolder<T>.Instance.Pool.Pop() : new List<T>()); + if (list.Count > 0) { + Mod.Error("Templist (of " + typeof(T).Name + ") is not empty on claim! Someone is storing a reference to templist."); + return Get<T>(); + } + + PoolHolder<T>.Instance.Claimed.Push(list); + return list; + } + + public static void Release() { + foreach (Releasable s_Pool in s_Pools) { + s_Pool.ReleaseInternal(); + } + } + } +#endif public static class DictionaryExtensions { // Works in C#3/VS2008: // Returns a new dictionary of this ... others merged leftward. @@ -283,4 +332,10 @@ public static T MergeLeft<T, K, V>(this T me, params IDictionary<K, V>[] others) return newMap; } } + + public static class LocalizationUtils { + public static void AddLocalizedString(this string value) => Kingmaker.Localization.LocalizationManager.Instance.CurrentPack.PutString(value, value); + public static LocalizedString LocalizedStringInGame(this string key) => new LocalizedString() { Key = key }; + + } } \ No newline at end of file diff --git a/ToyBox/classes/MainUI/ActionButtons.cs b/ToyBox/Classes/MainUI/ActionButtons.cs similarity index 82% rename from ToyBox/classes/MainUI/ActionButtons.cs rename to ToyBox/Classes/MainUI/ActionButtons.cs index 46f1d0d81..01553b2db 100644 --- a/ToyBox/classes/MainUI/ActionButtons.cs +++ b/ToyBox/Classes/MainUI/ActionButtons.cs @@ -1,18 +1,19 @@ // Copyright < 2021 > Narria (github user Cabarius) - License: MIT -using System; -using System.Collections.Generic; +using JetBrains.Annotations; using Kingmaker.Blueprints; using Kingmaker.EntitySystem.Entities; using ModKit; +using System; +using System.Collections.Generic; namespace ToyBox { public class NamedTypeFilter { public string name { get; } public Type type { get; } public Func<SimpleBlueprint, bool> filter; - public Func<SimpleBlueprint, List<string>> collator; + public Func<SimpleBlueprint, List<string?>> collator; public Func<IEnumerable<SimpleBlueprint>> blueprintSource; - protected NamedTypeFilter(string name, Type type, Func<SimpleBlueprint, bool> filter = null, Func<SimpleBlueprint, List<string>> collator = null, Func<IEnumerable<SimpleBlueprint>> blueprintSource = null) { + protected NamedTypeFilter(string name, Type type, Func<SimpleBlueprint, bool>? filter = null, Func<SimpleBlueprint, List<string>>? collator = null, Func<IEnumerable<SimpleBlueprint>>? blueprintSource = null) { this.name = name; this.type = type; this.filter = filter ?? ((bp) => true); @@ -21,7 +22,7 @@ protected NamedTypeFilter(string name, Type type, Func<SimpleBlueprint, bool> fi } } public class NamedTypeFilter<TBlueprint> : NamedTypeFilter where TBlueprint : SimpleBlueprint { - public NamedTypeFilter(string name, Func<TBlueprint, bool> filter = null, Func<TBlueprint, List<string>> collator = null, Func<IEnumerable<SimpleBlueprint>> blueprintSource = null) + public NamedTypeFilter(string name, Func<TBlueprint, bool>? filter = null, Func<TBlueprint, List<string?>>? collator = null, Func<IEnumerable<SimpleBlueprint>>? blueprintSource = null) : base(name, typeof(TBlueprint), null, null, blueprintSource) { if (filter != null) this.filter = (bp) => filter((TBlueprint)bp); if (collator != null) this.collator = (bp) => collator((TBlueprint)bp); @@ -35,24 +36,21 @@ public static void ResetGUI() { } public static void ActionButton<T>(this NamedAction<T> namedAction, T value, Action buttonAction, float width = 0) { if (namedAction != null && namedAction.canPerform(value)) { UI.ActionButton(namedAction.name, buttonAction, width == 0 ? UI.AutoWidth() : UI.Width(width)); - } - else { + } else { UI.Space(width + 3); } } public static void MutatorButton<U, T>(this NamedMutator<U, T> mutator, U unit, T value, Action buttonAction, float width = 0) { if (mutator != null && mutator.canPerform(unit, value)) { UI.ActionButton(mutator.name, buttonAction, width == 0 ? UI.AutoWidth() : UI.Width(width)); - } - else { + } else { UI.Space(width + 3); } } - public static void BlueprintActionButton(this BlueprintAction action, UnitEntityData unit, SimpleBlueprint bp, Action buttonAction, float width) { + public static void BlueprintActionButton(this BlueprintAction action, BaseUnitEntity unit, SimpleBlueprint bp, Action buttonAction, float width) { if (action != null && action.canPerform(bp, unit)) { UI.ActionButton(action.name, buttonAction, width == 0 ? UI.AutoWidth() : UI.Width(width)); - } - else { + } else { UI.Space(width + 3); } } diff --git a/ToyBox/Classes/MainUI/Actions.cs b/ToyBox/Classes/MainUI/Actions.cs new file mode 100644 index 000000000..37f4db870 --- /dev/null +++ b/ToyBox/Classes/MainUI/Actions.cs @@ -0,0 +1,116 @@ +// Copyright < 2021 > Narria (github user Cabarius) - License: MIT +using Kingmaker; +using Kingmaker.AreaLogic.Etudes; +using Kingmaker.Blueprints; +using Kingmaker.Controllers.Rest; +using Kingmaker.Designers; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.GameModes; +using Kingmaker.Globalmap.View; +using Kingmaker.PubSubSystem; +using Kingmaker.RuleSystem; +using Kingmaker.RuleSystem.Rules.Damage; +using Kingmaker.UI.Common; +using Kingmaker.UI.Selection; +using Kingmaker.UnitLogic; +using Kingmaker.UnitLogic.Abilities.Blueprints; +using Kingmaker.UnitLogic.Buffs; +using Kingmaker.Utility; +using Kingmaker.View; +using Kingmaker.View.MapObjects; +using ModKit; +//using Owlcat.Runtime.Core.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityModManagerNet; +namespace ToyBox { + public static partial class Actions { + public static Settings Settings => Main.Settings; + public static void KillAll() { + foreach (BaseUnitEntity unit in Shodan.AllBaseUnits) { + if (unit.CombatState.IsInCombat && Shodan.IsEnemy(unit)) { + Shodan.KillUnit(unit); + } + } + if (Game.Instance.IsPaused) { + Game.Instance.StopMode(GameModeType.Pause); + } + if (!Game.Instance.IsPaused) + return; + Game.Instance.StopMode(GameModeType.Pause); + } + public static void RemoveAllBuffs() { + foreach (var target in Game.Instance.Player.PartyAndPets) { + foreach (var buff in new List<Buff>(target.Descriptor().Buffs.Enumerable)) { + if (buff.Blueprint.IsHiddenInUI) { + continue; + } + + if (buff.Blueprint.StayOnDeath) { // Not a spell and persists through death, generally seems to be items + continue; + } + + target.Descriptor().Facts.Remove(buff); + } + } + } + public static void LobotomizeAllEnemies() { + foreach (var unit in Shodan.AllBaseUnits) { + if (unit.CombatState.IsInCombat && Shodan.IsEnemy(unit)) { + // removing the brain works better in RTWP, but gets stuck in turn based + //AccessTools.DeclaredProperty(descriptor.GetType(), "Brain")?.SetValue(descriptor, null); + // add a bunch of conditions and hope for the best + //var currentCharacter = WrathExtensions.GetCurrentCharacter(); + var fact = new EntityFact(); + unit.State.AddCondition(Kingmaker.UnitLogic.Enums.UnitCondition.DisableAttacksOfOpportunity, fact); + unit.State.AddCondition(Kingmaker.UnitLogic.Enums.UnitCondition.CantAct, fact); + unit.State.AddCondition(Kingmaker.UnitLogic.Enums.UnitCondition.CantMove, fact); + } + } + } + + public static void MaximizeModWindow() { + var modUI = BagOfPatches.ModUI.UnityModManagerUIPatch.UnityModMangerUI; + var screenWidth = Screen.width; + var screenHeight = Screen.height; + modUI.mWindowSize = new Vector2(screenWidth, screenHeight); + modUI.mWindowSize = modUI.ClampWindowSize(modUI.mWindowSize); + modUI.mExpectedWindowSize = modUI.mWindowSize; + modUI.mWindowRect = new Rect( + (screenWidth - modUI.mWindowSize.x) / 2.0f, + (screenHeight - modUI.mWindowSize.y) / 2.0f, + 0.0f, + 0.0f + ); + var newScale = screenWidth switch { + >= 3840 => 1.8f, + >= 2560 => 1.5f, + >= 1920 => 1.25f, + _ => 1.0f + }; + modUI.mUIScale = newScale; + modUI.mExpectedUIScale = newScale; + modUI.mUIScaleChanged = true; + UnityModManager.Params.WindowWidth = modUI.mWindowSize.x; + UnityModManager.Params.WindowHeight = modUI.mWindowSize.y; + UnityModManager.Params.UIScale = newScale; + UnityModManager.SaveSettingsAndParams(); + } + public static void ToggleModWindow() => UnityModManager.UI.Instance.ToggleWindow(); + public static void IdentifyAll() { + var inventory = Game.Instance?.Player?.Inventory; + if (inventory == null) return; + foreach (var item in inventory) { + item.Identify(); + } + foreach (var ch in Game.Instance.Player.AllCharacters) { + foreach (var item in ch.Body.GetAllItemsInternal()) { + item.Identify(); + //Main.Log($"{ch.CharacterName} - {item.Name} - {item.IsIdentified}"); + } + } + } + } +} diff --git a/ToyBox/classes/MainUI/Actions.cs b/ToyBox/Classes/MainUI/ActionsRT.cs similarity index 57% rename from ToyBox/classes/MainUI/Actions.cs rename to ToyBox/Classes/MainUI/ActionsRT.cs index aed42d53a..8bfded6b4 100644 --- a/ToyBox/classes/MainUI/Actions.cs +++ b/ToyBox/Classes/MainUI/ActionsRT.cs @@ -1,172 +1,77 @@ // Copyright < 2021 > Narria (github user Cabarius) - License: MIT using Kingmaker; +using Kingmaker.AI.BehaviourTrees; using Kingmaker.AreaLogic.Etudes; -using Kingmaker.Armies; -using Kingmaker.Armies.Blueprints; using Kingmaker.Blueprints; +using Kingmaker.Blueprints.Area; +using Kingmaker.Cheats; +using Kingmaker.Code.UI.MVVM.VM.Vendor; using Kingmaker.Controllers.Rest; +using Kingmaker.Designers; +using Kingmaker.Designers.EventConditionActionSystem.Actions; +using Kingmaker.DialogSystem.Blueprints; +using Kingmaker.ElementsSystem; +using Kingmaker.EntitySystem; using Kingmaker.EntitySystem.Entities; +using Kingmaker.EntitySystem.Persistence; using Kingmaker.GameModes; using Kingmaker.Globalmap.View; -using Kingmaker.Kingdom; +using Kingmaker.Items; +using Kingmaker.Mechanics.Entities; using Kingmaker.PubSubSystem; -using Kingmaker.RuleSystem.Rules.Damage; using Kingmaker.RuleSystem; +using Kingmaker.RuleSystem.Rules.Damage; +using Kingmaker.UI; using Kingmaker.UI.Common; +using Kingmaker.UI.Models.UnitSettings; using Kingmaker.UI.Selection; using Kingmaker.UnitLogic; using Kingmaker.UnitLogic.Abilities.Blueprints; using Kingmaker.UnitLogic.Buffs; +using Kingmaker.UnitLogic.Parts; using Kingmaker.Utility; using Kingmaker.View; using Kingmaker.View.MapObjects; +using Kingmaker.View.Spawners; using ModKit; -using Owlcat.Runtime.Core.Utils; +using ModKit.Utility; +using Newtonsoft.Json; +using Owlcat.Runtime.Core.Physics.PositionBasedDynamics.Forces; +using Owlcat.Runtime.Core.Utility; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using ToyBox.BagOfPatches; using UnityEngine; using UnityModManagerNet; -using Kingmaker.Designers; -using Kingmaker.Armies.TacticalCombat.Parts; - +using UnityUIControls; +using static Kingmaker.Designers.EventConditionActionSystem.Actions.OpenVendorSelectingWindow; +using static UnityModManagerNet.UnityModManager; namespace ToyBox { - public static class Actions { + public static partial class Actions { public static Settings settings => Main.Settings; public static void RestSelected() { - foreach (var selectedUnit in Game.Instance.UI.SelectionManager.SelectedUnits) { - if (selectedUnit.Descriptor.State.IsFinallyDead) { - selectedUnit.Descriptor.Resurrect(); - selectedUnit.Position = Game.Instance.Player.MainCharacter.Value.Position; - } - - RestController.ApplyRest(selectedUnit.Descriptor); - Rulebook.Trigger(new RuleHealDamage(selectedUnit, selectedUnit, default, selectedUnit.Descriptor.Stats.HitPoints.ModifiedValue)); - foreach (var attribute in selectedUnit.Stats.Attributes) { - attribute.Damage = 0; - attribute.Drain = 0; - } - } - } - public static void UnlockAllBasicMythicPaths() { - // TODO - do this right once I build the etude browser and understand this better - UnlockAeon(); - UnlockAzata(); - UnlockLich(); - UnlockTrickster(); - // The following two block progression so better not to - //UnlockDevil(); - //UnockSwarm(); - UnlockGoldDragon(); -#if false - var mythicInfos = BlueprintRoot.Instance.MythicsSettings.m_MythicsInfos; - foreach (var infoRef in mythicInfos) { - var info = infoRef.Get(); - var etudeGUID = info.EtudeGuid; - var etudeBp = ResourcesLibrary.TryGetBlueprint<BlueprintEtude>(etudeGUID); - Main.Log($"mythicInfo: {info} {etudeGUID} {etudeBp}"); - Game.Instance.Player.EtudesSystem.StartEtude(etudeBp); + foreach (var selectedUnit in UIAccess.SelectionManager.SelectedUnits) { + PartHealth.RestUnit(selectedUnit); } -#endif - Main.SetNeedsResetGameUI(); - } - public static void UnlockAngel() { - Game.Instance.Player.EtudesSystem.StartEtude(ResourcesLibrary.TryGetBlueprint<BlueprintEtude>("d85f7367b453b7b468b77e5e708297ae")); - Main.SetNeedsResetGameUI(); - } - public static void UnlockDemon() { - Game.Instance.Player.EtudesSystem.StartEtude(ResourcesLibrary.TryGetBlueprint<BlueprintEtude>("e6669aad304206c4d969f6602e6b412e")); - Main.SetNeedsResetGameUI(); } - public static void UnlockAeon() { - Game.Instance.Player.EtudesSystem.StartEtude(ResourcesLibrary.TryGetBlueprint<BlueprintEtude>("d85f7367b453b7b468b77e5e708297ae")); - Main.SetNeedsResetGameUI(); - } - public static void UnlockAzata() { - Game.Instance.Player.EtudesSystem.StartEtude(ResourcesLibrary.TryGetBlueprint<BlueprintEtude>("e6669aad304206c4d969f6602e6b412e")); - Main.SetNeedsResetGameUI(); - } - public static void UnlockLich() { - Game.Instance.Player.EtudesSystem.StartEtude(ResourcesLibrary.TryGetBlueprint<BlueprintEtude>("8f2f0ea65ef3a3f48948d27a39b37db1")); - Main.SetNeedsResetGameUI(); - } - public static void UnlockTrickster() { - Game.Instance.Player.EtudesSystem.StartEtude(ResourcesLibrary.TryGetBlueprint<BlueprintEtude>("f6dce66b61f98eb4dbe6388e16b1de11")); - Main.SetNeedsResetGameUI(); - } - public static void UnlockLegend() { - Game.Instance.Player.EtudesSystem.StartEtude(ResourcesLibrary.TryGetBlueprint<BlueprintEtude>("2943a647eb4017c49b4c121b15841d07")); - Game.Instance.Player.EtudesSystem.StartEtude(ResourcesLibrary.TryGetBlueprint<BlueprintEtude>("230552776ff941e1b054596bf589f9a9")); - Main.SetNeedsResetGameUI(); - } - public static void UnlockDevil() { - Game.Instance.Player.EtudesSystem.StartEtude(ResourcesLibrary.TryGetBlueprint<BlueprintEtude>("26028ff893925ef44aa1179906ac9265")); - Main.SetNeedsResetGameUI(); - } - public static void UnlockSwarm() { - Game.Instance.Player.EtudesSystem.StartEtude(ResourcesLibrary.TryGetBlueprint<BlueprintEtude>("6248db4784b301945b67b52143386b55")); - Game.Instance.Player.EtudesSystem.StartEtude(ResourcesLibrary.TryGetBlueprint<BlueprintEtude>("544443943d917e14ca72583d0357d4ad")); - Main.SetNeedsResetGameUI(); - } - public static void UnlockGoldDragon() { - Game.Instance.Player.EtudesSystem.StartEtude(ResourcesLibrary.TryGetBlueprint<BlueprintEtude>("067212d277e846a4f9ff96aee6138f0b")); - Main.SetNeedsResetGameUI(); - } - - public static void ToggleModWindow() => UnityModManager.UI.Instance.ToggleWindow(); - public static void RunPerceptionTriggers() { - // On the local map - foreach (var obj in Game.Instance.State.MapObjects) { - obj.LastPerceptionRollRank = new Dictionary<UnitReference, int>(); - } - // On the global map - foreach (var obj in Game.Instance.Player.AllGlobalMaps[0].Points.Values) { - obj.LastPerceptionRolled = 0; - } - - Tweaks.UnitEntityData_CanRollPerception_Extension.TriggerReroll = true; + public static void SpawnEnemyUnderCursor( + BlueprintUnit bp = null, + BlueprintFaction factionBp = null, + Vector3 position = default(Vector3)) { + Vector3 position1 = position != new Vector3() ? position : Game.Instance.ClickEventsController.WorldPosition; + if (bp == null) + bp = Game.Instance.BlueprintRoot.Cheats.Enemy; + Mod.Log("Summoning: " + Kingmaker.Cheats.Utilities.GetBlueprintPath((BlueprintScriptableObject)bp)); + BaseUnitEntity baseUnitEntity = Game.Instance.EntitySpawner.SpawnUnit(bp, position1, Quaternion.identity, Game.Instance.State.LoadedAreaState.MainState); + if (factionBp == null) + return; + baseUnitEntity.Faction.Set(factionBp); } - public static void RemoveAllBuffs() { - foreach (var target in Game.Instance.Player.PartyAndPets) { - foreach (var buff in new List<Buff>(target.Descriptor.Buffs.Enumerable)) { - if (buff.Blueprint.IsClassFeature || buff.Blueprint.IsHiddenInUI) { - continue; - } - - if (buff.Blueprint.IsFromSpell) { - target.Descriptor.RemoveFact(buff); // Always remove spell effects, even if they'd persist - continue; - } - - if (buff.Blueprint.StayOnDeath) { // Not a spell and persists through death, generally seems to be items - continue; - } - target.Descriptor.RemoveFact(buff); - } - } - } - public static void KillAllTacticalUnits() { - foreach (var unitRef in Game.Instance.TacticalCombat?.Data?.UnitRefs) { - if (unitRef.Entity.Get<UnitPartTacticalCombat>().Faction != ArmyFaction.Crusaders) { - GameHelper.KillUnit(unitRef.Entity); - } - } - } - public static void KillAll() { - foreach (UnitEntityData unit in Game.Instance.State.Units) { - if (unit.CombatState.IsInCombat && unit.IsPlayersEnemy && unit != GameHelper.GetPlayerCharacter()) { - GameHelper.KillUnit(unit); - } - } - KillAllTacticalUnits(); - if (Game.Instance.IsPaused) { - Game.Instance.StopMode(GameModeType.Pause); - } - } public static void SpawnUnit(BlueprintUnit unit, int count) { var worldPosition = Game.Instance.ClickEventsController.WorldPosition; // var worldPosition = Game.Instance.Player.MainCharacter.Value.Position; @@ -177,24 +82,22 @@ public static void SpawnUnit(BlueprintUnit unit, int count) { worldPosition.x + offset.x, worldPosition.y, worldPosition.z + offset.z); - Game.Instance.EntityCreator.SpawnUnit(unit, spawnPosition, Quaternion.identity, Game.Instance.State.LoadedAreaState.MainState); + Game.Instance.EntitySpawner.SpawnUnit(unit, spawnPosition, Quaternion.identity, Game.Instance.State.LoadedAreaState.MainState); } } } public static void HandleChangeParty() { if (Game.Instance.CurrentMode == GameModeType.GlobalMap) { - var partyCharacters = Game.Instance.Player.Party.Select(u => (UnitReference)u).ToList(); ; - if ((partyCharacters != null ? (partyCharacters.Select(r => r.Value).SequenceEqual(Game.Instance.Player.Party) ? 1 : 0) : 1) != 0) + var partyCharacters = Game.Instance.Player.Party.Select(u => new UnitReference(u)).ToList(); ; + if ((partyCharacters != null ? (partyCharacters.Select(r => r.Entity).SequenceEqual(Game.Instance.Player.Party) ? 1 : 0) : 1) != 0) return; - GlobalMapView.Instance.ChangePartyOnMap(); - } - else { + } else { foreach (var temp in Game.Instance.Player.RemoteCompanions.ToTempList()) temp.IsInGame = false; Game.Instance.Player.FixPartyAfterChange(); - Game.Instance.UI.SelectionManager.UpdateSelectedUnits(); + UIAccess.SelectionManager.UpdateSelectedUnits(); var tempList = Game.Instance.Player.Party.Select(character => character.View).ToTempList<UnitEntityView>(); - if (Game.Instance.UI.SelectionManager is SelectionManagerPC selectionManager) + if (UIAccess.SelectionManager is SelectionManagerPC selectionManager) selectionManager.MultiSelect((IEnumerable<UnitEntityView>)tempList); } } @@ -206,19 +109,143 @@ public static void ChangeParty() { EventBus.RaiseEvent<IGroupChangerHandler>(h => h.HandleCall(new Action(HandleChangeParty), (Action)null, true)); } } - public static void IdentifyAll() { - var inventory = Game.Instance?.Player?.Inventory; - if (inventory == null) return; - foreach (var item in inventory) { - item.Identify(); + public static void ApplyTimeScale() { + var timeScale = settings.useAlternateTimeScaleMultiplier + ? settings.alternateTimeScaleMultiplier + : settings.timeScaleMultiplier; + Game.Instance.TimeController.DebugTimeScale = timeScale; + } + + // called when changing highlight settings so they take immediate effect + public static void UpdateHighlights(bool on) { + foreach (var mapObjectEntityData in Game.Instance.State.MapObjects) { + mapObjectEntityData.View.UpdateHighlight(); + } + foreach (var unitEntityData in Game.Instance.State.AllUnits) { + unitEntityData.View.UpdateHighlight(false); } - foreach (var ch in Game.Instance.Player.AllCharacters) { - foreach (var item in ch.Body.GetAllItemsInternal()) { - item.Identify(); - //Main.Log($"{ch.CharacterName} - {item.Name} - {item.IsIdentified}"); + } + public static void resetClassLevel(this BaseUnitEntity ch) { + var level = 55; + var xp = ch.Descriptor().Progression.Experience; + var xpTable = ch.Descriptor().Progression.ExperienceTable; + + for (var i = 55; i >= 1; i--) { + var xpBonus = xpTable.GetBonus(i); + + Mod.Trace(i + ": " + xpBonus + " | " + xp); + + if ((xp - xpBonus) >= 0) { + Mod.Trace(i + ": " + (xp - xpBonus)); + level = i; + break; } } + ch.Descriptor().Progression.m_CharacterLevel = level; } + public static void RunPerceptionTriggers() { + // On the local map + foreach (var obj in Game.Instance.State.MapObjects) { + obj.LastAwarenessRollRank = new Dictionary<UnitReference, int>(); + } + Tweaks.UnitEntityDataCanRollPerceptionExtension.TriggerReroll = true; + } + + public static void RerollInteractionSkillChecks() { + foreach (var obj in Game.Instance.State.MapObjects) { + foreach (var part in obj.Parts.GetAll<InteractionSkillCheckPart>()) { + if (part.AlreadyUsed && !part.CheckPassed) { + part.AlreadyUsed = false; + part.Enabled = true; + } + } + } + } + + public static void GoToTradeWindow() { + + //Trade window should not be available in the Dark City and in Chapter 5. The game already disables it in the prologue. + string[] blockedEtudes = ["725db1ff1322445c8185506f4f6d242e", "6571856eb6c0459cba30e13adc5c6314"]; + var etudes = EtudesTreeModel.Instance.loadedEtudes; + + foreach (var etude in Game.Instance.Player.EtudesSystem.Etudes.RawFacts) { + if (blockedEtudes.Contains(etude.Blueprint.AssetGuid)) { + if (etude.IsPlaying) { + return; + } + } + } + + var area = Game.Instance.State.LoadedAreaState; + var currentArea = area.Blueprint; + var bridgeArea = ResourcesLibrary.TryGetBlueprint<BlueprintArea>("255859109cec4a042ade1613d80b25a4"); + var factotum = ResourcesLibrary.TryGetBlueprint<BlueprintAnswer>("d9307ba41f354ad2be00085eca5d0264"); + + if (currentArea == bridgeArea) { + (factotum.OnSelect.Actions[0] as Conditional).IfTrue.Actions[0].Run(); + } else { + List<Entity> entities = new(); + List<UnitSpawnerBase.MyData> spawners = new(); + List<AbstractUnitEntity> units = new(); + var action = factotum.ElementsArray.OfType<OpenVendorSelectingWindow>().FirstOrDefault(); + + try { + var areaState = ResourcesLibrary.TryGetBlueprint<BlueprintArea>("255859109cec4a042ade1613d80b25a4"); + AreaPersistentState state = Game.Instance.State.GetStateForArea(areaState); + AreaPersistentState areaPersistentState; + using (var jsonStreamForArea = AreaDataStash.GetJsonStreamForArea(state, state.MainState)) { + areaPersistentState = AreaDataStash.Serializer.Deserialize<AreaPersistentState>(jsonStreamForArea); + } + + var vendorScene = state.GetStateForScene("VoidshipBridge_Vendors_Mechanics"); + { + using (JsonTextReader jsonStreamForArea2 = AreaDataStash.GetJsonStreamForArea(state, vendorScene)) { + if (jsonStreamForArea2 != null) { + try { + SceneEntitiesState deserializedSceneState = AreaDataStash.Serializer.Deserialize<SceneEntitiesState>(jsonStreamForArea2); + areaPersistentState.SetDeserializedSceneState(deserializedSceneState); + spawners.AddRange(deserializedSceneState.AllEntityData.OfType<UnitSpawnerBase.MyData>()); + units.AddRange(deserializedSceneState.AllEntityData.OfType<AbstractUnitEntity>()); + } catch (IOException ex) { + Mod.Log($"Exception occured while loading area state: {area.Blueprint.AssetGuidThreadSafe} {vendorScene.SceneName} " + ex); + } + } + } + } + + foreach (var data in spawners) { + var reference = data.SpawnedUnit; + var Id = reference.GetId(); + var unit = units.FirstOrDefault(u => u.UniqueId == Id); + reference.m_Proxy.Entity = unit; + if (unit != null) { + var parts = unit.Parts; + if (parts != null && parts.Owner == null) { + parts.Owner = unit; + } + var vendor = unit.Parts.GetOptional<PartVendor>(); + vendor.SetSharedInventory(vendor.m_SharedInventory); + + var uiSettings = unit.Parts.GetOptional<PartUnitUISettings>(); + (uiSettings as EntityPart).Owner = unit; + } + area.MainState.AllEntityData.Add(data); + entities.Add(data); + } + action.Run(); + + } catch (Exception ex) { + Mod.Log(ex.ToString()); + } + finally { + foreach (var data in spawners.NotNull()) + area.MainState.m_EntityData.Remove(data); + } + } + UnityModManager.UI.Instance.ToggleWindow(false); + } + +#if false public static void ClearActionBar() { var selectedChar = Game.Instance?.SelectionCharacter?.CurrentSelectedCharacter; var uiSettings = selectedChar?.UISettings; @@ -231,7 +258,7 @@ public static bool HasAbility(this UnitEntityData ch, BlueprintAbility ability) return UIUtilityUnit.SpellbookHasSpell(selectedSpellbook, ability); } } - return ch.Spellbooks.Any(spellbook => spellbook.IsKnown(ability)) || ch.Descriptor.Abilities.HasFact(ability); + return ch.Spellbooks.Any(spellbook => spellbook.IsKnown(ability)) || ch.Descriptor().Abilities.HasFact(ability); } public static bool CanAddAbility(this UnitEntityData ch, BlueprintAbility ability) { if (ability.IsSpell) { @@ -254,7 +281,7 @@ public static bool CanAddAbility(this UnitEntityData ch, BlueprintAbility abilit } } else { - if (!ch.Descriptor.Abilities.HasFact(ability)) return true; + if (!ch.Descriptor().Abilities.HasFact(ability)) return true; } return false; } @@ -288,11 +315,11 @@ public static void AddAbility(this UnitEntityData ch, BlueprintAbility ability) } } else { - ch.Descriptor.AddFact(ability); + ch.Descriptor().AddFact(ability); } } - public static bool CanAddSpellAsAbility(this UnitEntityData ch, BlueprintAbility ability) => ability.IsSpell && !ch.Descriptor.HasFact(ability) && !PartyEditor.IsOnPartyEditor(); - public static void AddSpellAsAbility(this UnitEntityData ch, BlueprintAbility ability) => ch.Descriptor.AddFact(ability); + public static bool CanAddSpellAsAbility(this UnitEntityData ch, BlueprintAbility ability) => ability.IsSpell && !ch.Descriptor().HasFact(ability) && !PartyEditor.IsOnPartyEditor(); + public static void AddSpellAsAbility(this UnitEntityData ch, BlueprintAbility ability) => ch.Descriptor().AddFact(ability); public static void RemoveAbility(this UnitEntityData ch, BlueprintAbility ability) { if (ability.IsSpell) { if (PartyEditor.IsOnPartyEditor() && PartyEditor.SelectedSpellbook.TryGetValue(ch.HashKey(), out var selectedSpellbook)) { @@ -307,18 +334,18 @@ public static void RemoveAbility(this UnitEntityData ch, BlueprintAbility abilit } } } - var abilities = ch.Descriptor.Abilities; + var abilities = ch.Descriptor().Abilities; if (abilities.HasFact(ability)) abilities.RemoveFact(ability); } public static void ResetMythicPath(this UnitEntityData ch) { - // ch.Descriptor.Progression.RemoveMythicLevel + // ch.Descriptor().Progression.RemoveMythicLevel } public static void resetClassLevel(this UnitEntityData ch) { - var level = ch.Descriptor.Progression.MaxCharacterLevel; - var xp = ch.Descriptor.Progression.Experience; - var xpTable = ch.Descriptor.Progression.ExperienceTable; + var level = ch.Descriptor().Progression.MaxCharacterLevel; + var xp = ch.Descriptor().Progression.Experience; + var xpTable = ch.Descriptor().Progression.ExperienceTable; - for (var i = ch.Descriptor.Progression.MaxCharacterLevel; i >= 1; i--) { + for (var i = ch.Descriptor().Progression.MaxCharacterLevel; i >= 1; i--) { var xpBonus = xpTable.GetBonus(i); Mod.Trace(i + ": " + xpBonus + " | " + xp); @@ -329,7 +356,7 @@ public static void resetClassLevel(this UnitEntityData ch) { break; } } - ch.Descriptor.Progression.CharacterLevel = level; + ch.Descriptor().Progression.CharacterLevel = level; } public static void CreateArmy(BlueprintArmyPreset bp, bool friendlyorhostile) { @@ -379,32 +406,7 @@ public static bool LeaderSelected(BlueprintLeaderSkill bp) { } return true; } - public static void ApplyTimeScale() { - var timeScale = settings.useAlternateTimeScaleMultiplier - ? settings.alternateTimeScaleMultiplier - : settings.timeScaleMultiplier; - Game.Instance.TimeController.DebugTimeScale = timeScale; - } - public static void LobotomizeAllEnemies() { - foreach (var unit in Game.Instance.State.Units) { - if (unit.CombatState.IsInCombat && - unit.IsPlayersEnemy && - unit != Kingmaker.Designers.GameHelper.GetPlayerCharacter()) { - var descriptor = unit.Descriptor; - if (descriptor != null) { - // removing the brain works better in RTWP, but gets stuck in turn based - //AccessTools.DeclaredProperty(descriptor.GetType(), "Brain")?.SetValue(descriptor, null); - - // add a bunch of conditions and hope for the best - descriptor.State.AddCondition(UnitCondition.DisableAttacksOfOpportunity); - descriptor.State.AddCondition(UnitCondition.CantAct); - descriptor.State.AddCondition(UnitCondition.CanNotAttack); - descriptor.State.AddCondition(UnitCondition.CantMove); - descriptor.State.AddCondition(UnitCondition.MovementBan); - } - } - } - } + // can potentially go back in time but some parts of the game don't expect it public static void KingdomTimelineAdvanceDays(int days) { var kingdom = KingdomState.Instance; @@ -430,26 +432,6 @@ public static void KingdomTimelineAdvanceDays(int days) { timelineManager.UpdateTimeline(); } - - // called when changing highlight settings so they take immediate effect - public static void UpdateHighlights(bool on) { - foreach (var mapObjectEntityData in Game.Instance.State.MapObjects) { - mapObjectEntityData.View.UpdateHighlight(); - } - foreach (var unitEntityData in Game.Instance.State.Units) { - unitEntityData.View.UpdateHighlight(false); - } - } - - public static void RerollInteractionSkillChecks() { - foreach (var obj in Game.Instance.State.MapObjects) { - foreach (var part in obj.Parts.GetAll<InteractionSkillCheckPart>()) { - if (part.AlreadyUsed && !part.CheckPassed) { - part.AlreadyUsed = false; - part.Enabled = true; - } - } - } - } +#endif } } diff --git a/ToyBox/Classes/MainUI/BagOfTricks.cs b/ToyBox/Classes/MainUI/BagOfTricks.cs new file mode 100644 index 000000000..fd407a21a --- /dev/null +++ b/ToyBox/Classes/MainUI/BagOfTricks.cs @@ -0,0 +1,574 @@ +// Copyright < 2021 > Narria (github user Cabarius) - License: MIT + +using Kingmaker; +using Kingmaker.AreaLogic.Etudes; +using Kingmaker.Blueprints; +using Kingmaker.Blueprints.Facts; +using Kingmaker.Cheats; +using Kingmaker.Controllers; +using Kingmaker.Controllers.Dialog; +using Kingmaker.DLC; +using Kingmaker.ElementsSystem; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.GameModes; +using Kingmaker.Mechanics.Entities; +using Kingmaker.PubSubSystem; +using Kingmaker.UnitLogic; +using Kingmaker.View; +using Kingmaker.Visual.Sound; +using ModKit; +using ModKit.Utility.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using ToyBox.BagOfPatches; +using ToyBox.classes.MainUI; +using UnityEngine; +using UnityModManagerNet; +namespace ToyBox { + public static class BagOfTricks { + public static Settings Settings => Main.Settings; + // AstraMilitarum , Commissar , Criminal + private static readonly string[] OccupationIds = [ "4b908491051a4f36b9703b95e048a5a3", "00b183680643424abe015263aac81c5b", "8fab55c9130a4ae0a745f4fa1674c5df", + // MinistorumCrusader , NavyOfficer , Nobility , SanctionedPsyker + "d840a5dc947546e0b4ac939287191fd8", "962c310fd1664ae996c759e4d11a2d88", "06180233245249eea90d222bb1c13f00", "1518d1434ed646039215da3fdda6b096", + // Arbitrator + "cd1baf99dad544168bbf4962b3389d94"]; + private static IEnumerable<BlueprintUnitFact> Occupations; + private static Browser<BlueprintUnitFact, BlueprintUnitFact> OccupationBrowser = new(true, true, false, false) { DisplayShowAllGUI = false }; + // cheats combat + private const string RestAll = "Rest All"; + private const string RestSelected = "Rest Selected"; + private const string Empowered = "Empowered"; + private const string FullBuffPlease = "Common Buffs"; + private const string GoddesBuffs = "Buff Like A Goddess"; + private const string RemoveBuffs = "Remove Buffs"; + private const string RemoveDeathsDoor = "Remove Deaths Door"; + private const string KillAllEnemies = "Kill All Enemies"; + //private const string SummonZoo = "Summon Zoo" + private const string LobotomizeAllEnemies = "Lobotomize Enemies"; + private const string ToggleMurderHobo = "Toggle Murder Hobo"; + + // cheats common + private const string TeleportPartyToYou = "Teleport Party To You"; + private const string GoToGlobalMap = "Go To Global Map"; + private const string RerollPerception = "Reroll Perception"; + private const string RerollInteractionSkillChecks = "Reset Interactables"; + private const string ChangeParty = "Change Party"; + private const string ChangWeather = "Change Weather"; + private const string GoToTradeWindow = "Open Trade Window"; + + // other + private const string TimeScaleMultToggle = "Main/Alt Timescale"; + private const string PreviewDialogResults = "Preview Results"; + + + //For buffs exceptions + private static bool showBuffDurationExceptions = false; + + public static void OnLoad() { + // Combat + KeyBindings.RegisterAction(RestAll, () => CheatsCombat.RestAll()); + KeyBindings.RegisterAction(RestSelected, () => Actions.RestSelected()); + KeyBindings.RegisterAction(Empowered, () => CheatsCombat.Empowered("")); + KeyBindings.RegisterAction(FullBuffPlease, () => CheatsCombat.FullBuffPlease("")); + KeyBindings.RegisterAction(GoddesBuffs, () => CheatsCombat.Iddqd("")); + KeyBindings.RegisterAction(RemoveBuffs, () => Actions.RemoveAllBuffs()); + //KeyBindings.RegisterAction(RemoveDeathsDoor, () => CheatsCombat.DetachAllBuffs()); + KeyBindings.RegisterAction(KillAllEnemies, () => Actions.KillAll()); + //KeyBindings.RegisterAction(SummonZoo, () => CheatsCombat.SpawnInspectedEnemiesUnderCursor("")); + KeyBindings.RegisterAction(LobotomizeAllEnemies, () => Actions.LobotomizeAllEnemies()); + // Common + KeyBindings.RegisterAction(TeleportPartyToYou, () => Teleport.TeleportPartyToPlayer()); + KeyBindings.RegisterAction(GoToGlobalMap, () => Teleport.TeleportToGlobalMap()); + KeyBindings.RegisterAction(RerollPerception, () => Actions.RunPerceptionTriggers()); + KeyBindings.RegisterAction(RerollInteractionSkillChecks, () => Actions.RerollInteractionSkillChecks()); + KeyBindings.RegisterAction(ChangeParty, () => { Actions.ChangeParty(); }); + KeyBindings.RegisterAction(ChangWeather, () => CheatsCommon.ChangeWeather("")); + // Other + KeyBindings.RegisterAction(TimeScaleMultToggle, + () => { + Settings.useAlternateTimeScaleMultiplier = !Settings.useAlternateTimeScaleMultiplier; + Actions.ApplyTimeScale(); + }, + title => ToggleTranscriptForState(title, Settings.useAlternateTimeScaleMultiplier) + ); + KeyBindings.RegisterAction(PreviewDialogResults, () => { + Settings.previewDialogResults = !Settings.previewDialogResults; + var dialogController = Game.Instance.DialogController; + }); + KeyBindings.RegisterAction(ToggleMurderHobo, + () => Settings.togglekillOnEngage = !Settings.togglekillOnEngage, + title => ToggleTranscriptForState(title, Settings.togglekillOnEngage) + ); + KeyBindings.RegisterAction(GoToTradeWindow, () => { Actions.GoToTradeWindow(); }); + } + public static void ResetGUI() { } + + public static void OnGUI() { +#if BUILD_CRUI + ActionButton("Demo crUI", () => ModKit.crUI.Demo()); +#endif + if (Main.IsInGame) { + using (HorizontalScope()) { + Space(25); + Label("increment".localize().Cyan(), AutoWidth()); + IntTextField(ref Settings.increment, null, Width(150)); + } + var increment = Settings.increment; + var mainChar = Game.Instance.Player.MainCharacter.Entity; + HStack("Resources".localize(), + 1, + () => { + var money = Game.Instance.Player.Money; + Label("Gold".localize().Cyan(), Width(150)); + Label(money.ToString().Orange().Bold(), Width(200)); + ActionButton("Gain ".localize() + $"{increment}", () => Game.Instance.Player.GainMoney(increment), AutoWidth()); + ActionButton("Lose ".localize() + $"{increment}", + () => { + var loss = Math.Min(money, increment); + Game.Instance.Player.GainMoney(-loss); + }, + AutoWidth()); + }, + () => { + var exp = ((BaseUnitEntity)mainChar).Progression.Experience; + Label("Experience".localize().Cyan(), Width(150)); + Label(exp.ToString().Orange().Bold(), Width(200)); + ActionButton("Gain ".localize() + $"{increment}", () => { Game.Instance.Player.GainPartyExperience(increment); }, AutoWidth()); + }, + () => { } + ); + Div(0, 25); + } + HStack("Combat".localize(), + 2, + () => BindableActionButton(RestAll, true), + () => BindableActionButton(RestSelected, true), + () => BindableActionButton(FullBuffPlease, true), + () => BindableActionButton(Empowered, true), + () => BindableActionButton(GoddesBuffs, true), + () => BindableActionButton(RemoveBuffs, true), + () => BindableActionButton(RemoveDeathsDoor, true), + () => BindableActionButton(KillAllEnemies, true), + () => BindableActionButton(LobotomizeAllEnemies, true), + () => { }, + () => { + using (VerticalScope()) { + using (HorizontalScope()) { + using (VerticalScope(220.width())) { + using (HorizontalScope()) { + Toggle(("Be a " + "Murder".Red().Bold() + " Hobo".Orange()).localize(), ref Settings.togglekillOnEngage, 222.width()); + KeyBindPicker(ToggleMurderHobo, "", 50); + } + } + 158.space(); + Label(("If ticked, this will " + "MURDER".Red().Bold() + " all who dare to engage you!".Green()).localize(), AutoWidth()); + } + using (HorizontalScope()) { + if (Toggle("Log ToyBox Keyboard Commands In Game".localize(), ref Mod.ModKitSettings.toggleKeyBindingsOutputToTranscript, 450.width())) + ModKitSettings.Save(); + 50.space(); + HelpLabel("When ticked this shows ToyBox commands in the combat log which is helpful for you to know when you used the shortcut".localize()); + } + } + } + ); + Div(0, 25); + HStack("Teleport".localize(), + 2, + () => BindableActionButton(TeleportPartyToYou, true), + () => { + Toggle("Enable Teleport Keys".localize(), ref Settings.toggleTeleportKeysEnabled); + Space(100); + if (Settings.toggleTeleportKeysEnabled) { + using (VerticalScope()) { + KeyBindPicker("TeleportMain", "Main Character".localize(), 0, 200); + KeyBindPicker("TeleportSelected", "Selected Chars".localize(), 0, 200); + KeyBindPicker("TeleportParty", "Whole Party".localize(), 0, 200); + } + } + Space(25); + Label("You can enable hot keys to teleport members of your party to your mouse cursor on Area or the Global Map".localize().Green()); + }); + Div(0, 25); + HStack("Common".localize(), + 2, + () => BindableActionButton(GoToGlobalMap, true), + () => { + BindableActionButton(ChangeParty, true); + Space(-75); + HelpLabel("Change the party without advancing time (good to bind)".localize()); + }, + () => BindableActionButton(RerollPerception, true), + () => { + BindableActionButton(RerollInteractionSkillChecks, true); + Space(-75); + Label("This resets all the skill check rolls for all interactable objects in the area".localize().Green()); + }, + () => BindableActionButton(ChangWeather, true), + () => NonBindableActionButton("Give All Items".localize(), () => CheatsUnlock.CreateAllItems("")), + () => NonBindableActionButton("Identify All".localize(), () => Actions.IdentifyAll()), + () => BindableActionButton(GoToTradeWindow, true), + () => { } + ); + Div(0, 25); + HStack("Preview".localize(), + 0, + () => { + Toggle("Dialog Results".localize(), ref Settings.previewDialogResults); + 25.space(); + Toggle("Dialog Conditions".localize(), ref Settings.previewDialogConditions); + 25.space(); + Toggle("Dialog Alignment".localize(), ref Settings.previewAlignmentRestrictedDialog); + BindableActionButton(PreviewDialogResults, true); + }); + Div(0, 25); + HStack("Dialog".localize(), + 1, + () => { + Toggle(("♥♥ ".Red() + "Love is Free".Bold() + " ♥♥".Red()).localize(), ref Settings.toggleAllowAnyGenderRomance, 300.width()); + 25.space(); + Label(("Allow ".Green() + "any gender".Color(RGBA.purple) + " " + "for any ".Green() + "R".Color(RGBA.red) + "o".Orange() + "m".Yellow() + "a".Green() + "n".Cyan() + "c".Color(RGBA.blue) + "e".Color(RGBA.purple)).localize()); + }, + () => { + Toggle("Jealousy Begone!".localize().Bold(), ref Settings.toggleMultipleRomance, 300.width()); + 25.space(); + Label(("Allow ".Green() + "multiple".Color(RGBA.purple) + " romances at the same time".Green()).localize()); + }, + () => { + Toggle("Previously Chosen Dialog Is Smaller ".localize(), ref Settings.toggleMakePreviousAnswersMoreClear, 300.width()); + 200.space(); + Label("Draws dialog choices that you have previously selected in smaller type".localize().Green()); + }, + () => { + Toggle("Expand Dialog To Include Remote Companions".localize(), ref Settings.toggleRemoteCompanionDialog, 300.width()); + 200.space(); + Label(" Allow remote companions to make comments on dialog you are having.".localize().Green()); + }, + () => { + if (Settings.toggleRemoteCompanionDialog) { + 50.space(); + Toggle("Include Former Companions".localize(), ref Settings.toggleExCompanionDialog, 300.width()); + 150.space(); + Label("This also includes companions who left the party".localize().Green()); + } + }, + () => { + Toggle("Override Story Occupation (e.g. Sanctioned Psyker)".localize(), ref Settings.toggleOverrideOccupation, 300.width()); + }, + () => { + if (Settings.toggleOverrideOccupation) { + if ((Occupations?.Count() ?? 0) == 0) { + Occupations = BlueprintLoader.Shared.GetBlueprintsByGuids<BlueprintUnitFact>(OccupationIds).Where(bp => !bp.IsDlcRestricted()); + } + if (Occupations?.Count() > 0) { + using (VerticalScope()) { + OccupationBrowser.OnGUI(Occupations, () => Occupations, o => o, o => BlueprintExtensions.GetSearchKey(o), o => [BlueprintExtensions.GetSortKey(o)], null, (o, maybeO) => { + Label(BlueprintExtensions.GetSortKey(o), Width(500)); + if (Settings.usedOccupations.Contains(o.AssetGuid)) { + ActionButton("Remove", () => Settings.usedOccupations.Remove(o.AssetGuid)); + } else { + ActionButton("Add", () => Settings.usedOccupations.Add(o.AssetGuid)); + } + }); + } + } + } + }, + () => { + // TODO: BROKEN! + Settings.toggleShowAnswersForEachConditionalResponse = false; + /* + using (VerticalScope(300.width())) { + Toggle("Expand Answers For Conditional Responses".localize(), ref Settings.toggleShowAnswersForEachConditionalResponse, 300.width()); + } + 200.space(); + Label("Some responses such as comments about your mythic powers will always choose the first one by default. This will show a copy of the answer and the condition for each possible response that an NPC might make to you based on".localize().green()); + */ + }, + () => Toggle("Disable Dialog Restrictions (SoulMark)".localize(), ref Settings.toggleDialogRestrictions), + () => { + if (Settings.toggleRiskyToggles) { + Toggle("Disable Dialog Restrictions (Everything, Experimental)".localize(), ref Settings.toggleDialogRestrictionsEverything); + } + }, + () => { } + ); + Div(0, 25); + HStack("Quality of Life".localize(), + 1, + () => { + Toggle("Allow Achievements While Using Mods".localize(), ref Settings.toggleAllowAchievementsDuringModdedGame, 500.width()); + Label("This is intended for you to be able to enjoy the game while using mods that enhance your quality of life. Please be mindful of the player community and avoid using this mod to trivialize earning prestige achievements like Sadistic Gamer.".localize().Orange()); + }, + () => { + Toggle("Skip Splash Screen".localize(), ref Settings.toggleSkipSplashScreen, 500.width()); + Label("This skips the splash screen that appears when the game starts. Helpful if you need to frequently restart the game".localize().Green()); + }, + () => Toggle("Object Highlight Toggle Mode (Out of Combat!)".localize(), ref Settings.highlightObjectsToggle), + () => { + Toggle("Mark Interesting NPCs".localize(), ref Settings.toggleShowInterestingNPCsOnLocalMap, 500.width()); + HelpLabel("This will change the color of NPC names on the highlike makers and change the color map markers to indicate that they have interesting or conditional interactions".localize()); + }, + () => { + Toggle("Auto load Last Save on launch".localize(), ref Settings.toggleAutomaticallyLoadLastSave, 500.width()); + HelpLabel("Hold down shift during launch to bypass".localize()); + }, + () => { + Toggle("Don't wait for keypress when loading saves".localize(), ref Settings.toggleSkipAnyKeyToContinueWhenLoadingSaves, 500.width()); + HelpLabel("When loading a game this will go right into the game without having to 'Press any key to continue'".localize()); + }, + () => Toggle("Refill consumables in belt slots if in inventory".localize(), ref Settings.togglAutoEquipConsumables), + () => { + var modifier = KeyBindings.GetBinding("InventoryUseModifier"); + var modifierText = modifier.Key == KeyCode.None ? "Modifer" : modifier.ToString(); + Toggle("Allow ".localize() + $"{modifierText}".Cyan() + (" + Click".Cyan() + " To Use Items In Inventory").localize(), ref Settings.toggleShiftClickToUseInventorySlot, 470.width()); + if (Settings.toggleShiftClickToUseInventorySlot) { + ModifierPicker("InventoryUseModifier", "", 0); + } + }, + () => { + var modifier = KeyBindings.GetBinding("ClickToTransferModifier"); + var modifierText = modifier.Key == KeyCode.None ? "Modifer" : modifier.ToString(); + Toggle("Allow ".localize() + $"{modifierText}".Cyan() + (" + Click".Cyan() + " To Transfer Entire Stack").localize(), ref Settings.toggleShiftClickToFastTransfer, 470.width()); + if (Settings.toggleShiftClickToFastTransfer) { + ModifierPicker("ClickToTransferModifier", "", 0); + } + }, + () => ActionButton("Fix Incorrect Main Character".localize(), + () => { + var probablyPlayer = Game.Instance.Player?.Party? + .Where(x => !x.IsCustomCompanion()) + .Where(x => !x.IsStoryCompanion()) + .ToList(); + if (probablyPlayer is { Count: 1 }) { + var newMainCharacter = probablyPlayer.First(); + Mod.Warn($"Promoting {newMainCharacter.CharacterName} to main character!"); + if (Game.Instance != null) Game.Instance.Player.MainCharacter = new UnitReference(newMainCharacter); + } + }, + AutoWidth()), + () => { + using (VerticalScope()) { + Div(0, 25, 1280); + var useAlt = Settings.useAlternateTimeScaleMultiplier; + var mainTimeScaleTitle = "Game Time Scale".localize(); + if (useAlt) mainTimeScaleTitle = mainTimeScaleTitle.Grey(); + var altTimeScaleTitle = "Alternate Time Scale".localize(); + if (!useAlt) altTimeScaleTitle = altTimeScaleTitle.Grey(); + using (HorizontalScope()) { + LogSlider(mainTimeScaleTitle, ref Settings.timeScaleMultiplier, 0f, 20, 1, 1, "", Width(450)); + Space(25); + Label("Speeds up or slows down the entire game (movement, animation, everything)".localize().Green()); + } + using (HorizontalScope()) { + LogSlider(altTimeScaleTitle, ref Settings.alternateTimeScaleMultiplier, 0f, 20, 5, 1, "", Width(450)); + } + using (HorizontalScope()) { + BindableActionButton(TimeScaleMultToggle, true); + Space(-95); + Label("Bindable hot key to swap between main and alternate time scale multipliers".localize().Green()); + } + Div(0, 25, 1280); + } + }, + () => Toggle("Disable end turn HotKey".localize(), ref Settings.disableEndTurnHotkey, 500.width()), + () => { + Toggle("Enable Loading with Blueprint Errors".localize().Color(RGBA.maroon), ref Settings.enableLoadWithMissingBlueprints); + 25.space(); + Label($"This {"incredibly dangerous".Bold()} setting overrides the default behavior of failing to load saves depending on missing blueprint mods. This desperate action can potentially enable you to recover your saved game, though you'll have to respec at minimum.".localize().Orange()); + }, + () => { + if (Settings.enableLoadWithMissingBlueprints) { + Label("To permanently remove these modded blueprint dependencies, load the damaged saved game, change areas, and then save the game. You can then respec any characters that were impacted.".localize().Orange()); + } + }, + () => { } + ); + + Div(0, 25); + HStack("RT Specific".localize(), + 1, + () => { + using (VerticalScope()) { + RogueCheats.OnGUI(); + } + } + ); + Div(0, 25); + EnhancedCamera.OnGUI(); + Div(0, 25); + HStack("Cheats".localize(), 1, + () => { + Toggle("Prevent Traps from triggering".localize(), ref Settings.disableTraps, 500.width()); + Label("Enterint a Trap Zone while having Traps disabled will prevent that Trap from triggering even if you deactivate this option in the future".localize().Green()); + }, + () => Toggle("Unlimited Stacking of Modifiers (Stat/AC/Hit/Damage/Etc)".localize(), ref Settings.toggleUnlimitedStatModifierStacking), + () => { + using (HorizontalScope()) { + ToggleCallback("Highlight Hidden Objects".localize(), ref Settings.highlightHiddenObjects, Actions.UpdateHighlights); + if (Settings.highlightHiddenObjects) { + Space(100); + ToggleCallback("In Fog Of War ".localize(), ref Settings.highlightHiddenObjectsInFog, Actions.UpdateHighlights); + } + } + }, + () => Toggle("Infinite Abilities (No cooldowns no cost)".localize(), ref Settings.toggleInfiniteAbilities), + () => Toggle("No attack/spell cooldowns".localize(), ref Settings.toggleNoAttackCooldowns), + () => { + using (HorizontalScope()) { + Toggle("Don't use AP (except abilities which consume all AP) During Turn".localize(), ref Settings.toggleUnlimitedActionsPerTurn); + if (Settings.toggleUnlimitedActionsPerTurn) { + Space(100); + Toggle("Don't use any AP during your turn.".localize(), ref Settings.toggleReallyUnlimitedActionsPerTurn); + } + } + }, + () => Toggle("Infinite Charges On Items".localize(), ref Settings.toggleInfiniteItems), + () => Toggle("ignore Equipment Restrictions".localize(), ref Settings.toggleEquipmentRestrictions), + () => Toggle("Restore Spells & Skills After Combat".localize(), ref Settings.toggleRestoreSpellsAbilitiesAfterCombat), + () => Toggle("Instant Rest After Combat".localize(), ref Settings.toggleInstantRestAfterCombat), + () => Toggle("Allow Equipment Change During Combat".localize(), ref Settings.toggleEquipItemsDuringCombat), + () => Toggle("Allow Item Use From Inventory During Combat".localize(), ref Settings.toggleUseItemsDuringCombat), + () => Toggle("Ignore all Requirements for Abilities".localize(), ref Settings.toggleIgnoreAbilityAnyRestriction), + () => Toggle("Ignore Ability Requirement - AOE Overlap".localize(), ref Settings.toggleIgnoreAbilityAoeOverlap), + () => Toggle("Ignore Ability Requirement - Line of Sight".localize(), ref Settings.toggleIgnoreAbilityLineOfSight), + () => Toggle("Ignore Ability Requirement - Max Range".localize(), ref Settings.toggleIgnoreAbilityTargetTooFar), + () => Toggle("Ignore Ability Requirement - Min Range".localize(), ref Settings.toggleIgnoreAbilityTargetTooClose), + () => { } + ); + Div(0, 25); + HStack("Experience Multipliers".localize(), 1, + () => LogSlider("All Experience".localize(), ref Settings.experienceMultiplier, 0f, 100f, 1, 1, "", AutoWidth()), + () => { + using (HorizontalScope()) { + Toggle("Override for Combat".localize(), ref Settings.useCombatExpSlider, Width(275)); + if (Settings.useCombatExpSlider) { + Space(10); + LogSliderCustomLabelWidth("", ref Settings.experienceMultiplierCombat, 0f, 100f, 1, 1, "", 12, AutoWidth()); + } + } + }, + () => { + using (HorizontalScope()) { + Toggle("Override for Quests".localize(), ref Settings.useQuestsExpSlider, Width(275)); + if (Settings.useQuestsExpSlider) { + Space(10); + LogSliderCustomLabelWidth("", ref Settings.experienceMultiplierQuests, 0f, 100f, 1, 1, "", 12, AutoWidth()); + } + } + }, + () => { + using (HorizontalScope()) { + Toggle("Override for Skill Checks".localize(), ref Settings.useSkillChecksExpSlider, Width(275)); + if (Settings.useSkillChecksExpSlider) { + Space(10); + LogSliderCustomLabelWidth("", ref Settings.experienceMultiplierSkillChecks, 0f, 100f, 1, 1, "", 12, AutoWidth()); + } + } + }, + () => { + using (HorizontalScope()) { + Toggle("Override for Challenges".localize(), ref Settings.useChallengesExpSlider, Width(275)); + if (Settings.useChallengesExpSlider) { + Space(10); + LogSliderCustomLabelWidth("", ref Settings.experienceMultiplierChallenges, 0f, 100f, 1, 1, "", 12, AutoWidth()); + } + } + }, + () => { + using (HorizontalScope()) { + Toggle("Override for Space Combat".localize(), ref Settings.useSpaceExpSlider, Width(275)); + if (Settings.useSpaceExpSlider) { + Space(10); + LogSliderCustomLabelWidth("", ref Settings.experienceMultiplierSpace, 0f, 100f, 1, 1, "", 12, AutoWidth()); + } + } + } + ); + Div(0, 25); + HStack("Other Multipliers".localize(), 1, + () => { + LogSlider("Vision Range".localize(), ref Settings.fowMultiplier, 0f, 100f, 1, 1, "", AutoWidth()); + List<BaseUnitEntity> units = Game.Instance?.Player?.m_PartyAndPets; + if (units != null) { + foreach (var unit in units) { + // TODO: do we need this for RT? + // TODO: who knows? + FogOfWarRevealerSettings revealer = unit.View?.FogOfWarRevealer; + if (revealer != null) { + if (Settings.fowMultiplier == 1) { + revealer.DefaultRadius = true; + revealer.Radius = 1.0f; + } else { + revealer.DefaultRadius = false; + // TODO: is this right? + revealer.Radius = Settings.fowMultiplier; + } + } + } + } + }, + () => { + LogSlider("Max walk distance".localize(), ref Settings.walkRangeMultiplier, 0, 100, 1, 1, "", Width(600)); + Space(25); + Label("Adjusts how far of your character you can click and still cause your character to walk instead of run".localize().Green()); + }, + () => { + LogSlider("Min sprint distance".localize(), ref Settings.sprintRangeMultiplier, 0, 100, 1, 1, "", Width(600)); + Space(25); + Label("Adjusts how far of your character you have to click and still cause your character to spring. If this area overlaps with walk distance then this has priority.".localize().Green()); + }, + () => { + LogSlider("Movement Speed".localize(), ref Settings.partyMovementSpeedMultiplier, 0f, 20, 1, 1, "", Width(600)); + Space(25); + Label("Adjusts the movement speed of your party in area maps".localize().Green()); + }, + () => LogSlider("Buff Duration".localize(), ref Settings.buffDurationMultiplierValue, 0f, 9999, 1, 1, "", AutoWidth()), + () => DisclosureToggle("Exceptions to Buff Duration Multiplier (Advanced; will cause blueprints to load)".localize(), ref showBuffDurationExceptions), + () => { + if (!showBuffDurationExceptions) return; + + BuffExclusionEditor.OnGUI(); + }, + () => { } + ); + Actions.ApplyTimeScale(); + Div(0, 25); + DiceRollsGUI.OnGUI(); + Div(0, 25); + HStack("Enemy Stat Multipliers".localize(), 1, + () => Toggle("Enable Multipliers (e.g. Enemy Health x2)".localize(), ref Main.Settings.toggleAddMultiplierEnemyMods), + () => { + if (Main.Settings.toggleAddMultiplierEnemyMods) { + using (VerticalScope()) { + foreach (StatType stat in Enum.GetValues(typeof(StatType))) { + if (Difficulty.BadStats.Contains(stat)) { + continue; + } + Slider(Enum.GetName(typeof(StatType), stat), () => Settings.multiplierEnemyMods[stat], newStat => Settings.multiplierEnemyMods[stat] = newStat, -100, 100, 1, 2); + } + } + } + } + ); + Div(0, 25); + HStack("Flat Enemy Stat Boosts".localize(), 1, + () => Toggle("Enable Flat Boosts (e.g. Enemy Health +20)".localize(), ref Main.Settings.toggleAddFlatEnemyMods), + () => { + if (Main.Settings.toggleAddFlatEnemyMods) { + using (VerticalScope()) { + foreach (StatType stat in Enum.GetValues(typeof(StatType))) { + if (Difficulty.BadStats.Contains(stat)) { + continue; + } + Slider(Enum.GetName(typeof(StatType), stat), () => Settings.flatEnemyMods[stat], newStat => Settings.flatEnemyMods[stat] = newStat, -100, 100, 0, 0); + } + } + } + } + ); + } + } +} diff --git a/ToyBox/Classes/MainUI/Browser/AchievementsUnlocker.cs b/ToyBox/Classes/MainUI/Browser/AchievementsUnlocker.cs new file mode 100644 index 000000000..1ac9959b8 --- /dev/null +++ b/ToyBox/Classes/MainUI/Browser/AchievementsUnlocker.cs @@ -0,0 +1,92 @@ +using Kingmaker; +using Kingmaker.Achievements; +using ModKit; +using ModKit.DataViewer; +using ModKit.Utility; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace ToyBox { + public class AchievementsUnlocker { + public static Browser<AchievementEntity, AchievementEntity> AchievementBrowser = new(true); + public static List<AchievementEntity> availableAchievements = new(); + public static List<AchievementEntity> unlocked = new(); + public static Settings Settings => Main.Settings; + public static void OnShowGUI() { + try { + justInit = true; + availableAchievements.Clear(); + unlocked.Clear(); + } catch (Exception ex) { + Mod.Debug(ex.ToString()); + } + } + //TODO: Check in RT release version whether there is a good heuristic to check if an achievement is blocked on the platform + private static bool justInit = false; + public static void OnGUI() { + if (availableAchievements == null || availableAchievements?.Count == 0 || justInit) { + UI.Label(RichText.Bold(RichText.Yellow("Achievements not available until you load a save.".localize()))); + availableAchievements = Game.Instance?.Player? + .Achievements? + .m_Achievements? + .Where(ach => !ach.Data.SteamId.IsNullOrEmpty()) + .ToList(); + if (availableAchievements != null && availableAchievements?.Count > 0) + unlocked = availableAchievements.Where(ach => ach.IsUnlocked).ToList(); + justInit = true; + } + if (justInit) { + if (Event.current.type == EventType.Repaint) { + justInit = false; + } + return; + } + AchievementBrowser.OnGUI(unlocked, + () => availableAchievements, + current => current, + achievement => $"{achievement.Data.SteamId} {achievement.Data.GetDescription()} {achievement.Data.name}", + achievement => (new[] { achievement.Data.name }), + () => { + using (VerticalScope()) { + Toggle("Show GUIDs".localize(), ref Main.Settings.showAssetIDs); + Div(0, 25); + } + }, + (achievement, maybeAchievement) => { + var remainingWidth = ummWidth; + // Indent + remainingWidth -= 50; + var titleWidth = (remainingWidth / (IsWide ? 3.5f : 4.0f)) - 100; + remainingWidth -= titleWidth; + + var text = achievement.Data.name.MarkedSubstring(AchievementBrowser.SearchText); + if (Main.Settings.showAssetIDs) text += $" ({achievement.Data.AssetGuid})"; + if (maybeAchievement != null) { + text = RichText.Bold(text.Cyan()); + } + Label(text, Width((int)titleWidth)); + + if (maybeAchievement == null) { + ActionButton("Unlock".localize(), () => { + if (!achievement.IsUnlocked) { + achievement.IsUnlocked = true; + achievement.NeedCommit = true; + achievement.Manager.OnAchievementUnlocked(achievement); + } + }, Width(116)); + Space(70); + } else { + Space(190); + } + remainingWidth -= 190; + Space(20); remainingWidth -= 20; + ReflectionTreeView.DetailToggle("", achievement, achievement, 0); + }, + (achievement, maybeAchievement) => { + ReflectionTreeView.OnDetailGUI(achievement); + }, 50, false); + } + } +} \ No newline at end of file diff --git a/ToyBox/Classes/MainUI/Browser/BlueprintAction.cs b/ToyBox/Classes/MainUI/Browser/BlueprintAction.cs new file mode 100644 index 000000000..94c2cb190 --- /dev/null +++ b/ToyBox/Classes/MainUI/Browser/BlueprintAction.cs @@ -0,0 +1,277 @@ +// Copyright < 2021 > Narria (github user Cabarius) - License: MIT + +using Kingmaker; +using Kingmaker.AreaLogic.Cutscenes; +using Kingmaker.AreaLogic.Etudes; +using Kingmaker.AreaLogic.QuestSystem; +using Kingmaker.Blueprints; +using Kingmaker.Blueprints.Area; +using Kingmaker.Blueprints.Classes; +using Kingmaker.Blueprints.Facts; +using Kingmaker.Blueprints.Items; +using Kingmaker.Blueprints.Quests; +using Kingmaker.Cheats; +using Kingmaker.Designers; +using Kingmaker.Designers.EventConditionActionSystem.ContextData; +using Kingmaker.ElementsSystem; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.EntitySystem.Persistence; +using Kingmaker.Globalmap.Blueprints; +using Kingmaker.Mechanics.Entities; +using Kingmaker.UnitLogic; +using Kingmaker.UnitLogic.Abilities.Blueprints; +using Kingmaker.UnitLogic.ActivatableAbilities; +using Kingmaker.UnitLogic.Buffs.Blueprints; +using Kingmaker.UnitLogic.Levelup.Obsolete.Blueprints.Selection; +using Kingmaker.UnitLogic.Mechanics.Blueprints; +using Kingmaker.Utility; +using ModKit; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace ToyBox { + public abstract class BlueprintAction { + public delegate void Perform(SimpleBlueprint bp, BaseUnitEntity? ch = null, int count = 1, int listValue = 0); + + public delegate bool CanPerform(SimpleBlueprint bp, BaseUnitEntity? ch = null, int listValue = 0); + + private static Dictionary<Type, BlueprintAction[]> actionsForType; + + public static BlueprintAction[] ActionsForType(Type type) { + if (actionsForType == null) { + actionsForType = new Dictionary<Type, BlueprintAction[]>(); + BlueprintActions.InitializeActions(); + BlueprintActions.InitializeActionsRT(); + } + + actionsForType.TryGetValue(type, out var result); + + if (result == null) { + var baseType = type.BaseType; + + if (baseType != null) { + result = ActionsForType(baseType); + } + + result ??= new BlueprintAction[] { }; + + actionsForType[type] = result; + } + + return result; + } + + public static IEnumerable<BlueprintAction> ActionsForBlueprint(SimpleBlueprint bp) => ActionsForType(bp.GetType()); + public static void Register<T>(string? name, BlueprintAction<T>.Perform perform, BlueprintAction<T>.CanPerform? canPerform = null, bool isRepeatable = false, bool worksInMainMenu = false) where T : SimpleBlueprint { + var action = new BlueprintAction<T>(name, perform, canPerform, isRepeatable, worksInMainMenu); + var type = action.BlueprintType; + actionsForType.TryGetValue(type, out var existing); + existing ??= new BlueprintAction[] { }; + var list = existing.ToList(); + list.Add(action); + actionsForType[type] = list.ToArray(); + } + + public string? name { get; protected set; } + + public Perform action; + + public CanPerform canPerform; + + protected BlueprintAction(string? name, bool isRepeatable) { + this.name = name; + this.isRepeatable = isRepeatable; + } + + public bool isRepeatable; + + public abstract Type BlueprintType { get; } + } + + public class BlueprintAction<BPType> : BlueprintAction where BPType : SimpleBlueprint { + public new delegate void Perform(BPType bp, BaseUnitEntity ch, int count = 1, int listValue = 0); + + public new delegate bool CanPerform(BPType bp, BaseUnitEntity ch, int listValue = 0); + + public BlueprintAction(string? name, Perform action, CanPerform? canPerform = null, bool isRepeatable = false, bool worksInMainMenu = false) : base(name, isRepeatable) { + this.action = (bp, ch, n, index) => action((BPType)bp, ch, n, index); + this.canPerform = (bp, ch, index) => { + try { + return (worksInMainMenu || Main.IsInGame) && bp is BPType bpt && (canPerform?.Invoke(bpt, ch, index) ?? true); + } catch (Exception) { + return false; + } + }; + } + + public override Type BlueprintType => typeof(BPType); + } + + public static partial class BlueprintActions { + public static IEnumerable<BlueprintAction> GetActions(this SimpleBlueprint bp) => BlueprintAction.ActionsForBlueprint(bp); + + private static Dictionary<BlueprintFeatureSelection_Obsolete, BlueprintFeature[]> featureSelectionItems = new(); + public static BlueprintFeature FeatureSelectionItems(this BlueprintFeatureSelection_Obsolete feature, int index) { + if (featureSelectionItems.TryGetValue(feature, out var value)) return index < value.Length ? value[index] : null; + value = feature.AllFeatures.OrderBy(x => x.NameSafe()).ToArray(); + if (value == null) return null; + featureSelectionItems[feature] = value; + return index < value.Length ? value[index] : null; + } + + public static void InitializeActions() { + BlueprintAction.Register<BlueprintAreaPreset>("Load Preset".localize(), (bp, ch, n, l) => { + CheatsTransfer.StartNewGame(bp); + }, null, false, true); + BlueprintAction.Register<BlueprintItem>("Add".localize(), + (bp, ch, n, index) => { + Game.Instance.Player.Inventory.Add(bp, n); + OwlLogging.Log($"Add Item {n} x {bp}"); + }, isRepeatable: true); + + BlueprintAction.Register<BlueprintItem>("Remove".localize(), + (bp, ch, n, index) => { + Game.Instance.Player.Inventory.Remove(bp, n); + OwlLogging.Log($"Remove Item {n} x {bp}"); + }, + (bp, ch, index) => Game.Instance.Player.Inventory.Contains(bp), true); + + BlueprintAction.Register<BlueprintUnit>("Spawn".localize(), + (bp, ch, n, index) => { + Actions.SpawnUnit(bp, n); + OwlLogging.Log($"Spawn {n} x {bp}"); + }, isRepeatable: true); + + // Facts + BlueprintAction.Register<BlueprintMechanicEntityFact>("Add".localize(), + (bp, ch, n, index) => { + ch.AddFact(bp); + OwlLogging.Log($"Add MechanicEntityFact {bp} to {ch}"); + }, + (bp, ch, index) => !ch.Facts.List.Select(f => f.Blueprint).Contains(bp)); + + BlueprintAction.Register<BlueprintMechanicEntityFact>("Remove".localize(), + (bp, ch, n, index) => { + ch.Facts.Remove(bp); + OwlLogging.Log($"Remove MechanicEntityFact {bp} from {ch}"); + }, + (bp, ch, index) => ch.Facts.List.Select(f => f.Blueprint).Contains(bp)); + + //BlueprintAction.Register<BlueprintArchetype>( + // "Add", + // (bp, ch, n, index) => ch.Progression.AddArchetype(ch.Progression.Classes.First().CharacterClass, bp), + // (bp, ch, index) => ch.Progression.CanAddArchetype(ch.Progression.Classes.First().CharacterClass, bp) + // ); + //BlueprintAction.Register<BlueprintArchetype>("Remove", + // (bp, ch, n, index) => ch.Progression.AddArchetype(ch.Progression.Classes.First().CharacterClass, bp), + // (bp, ch, index) => ch.Progression.Classes.First().Archetypes.Contains(bp) + // ); + + // Teleport + BlueprintAction.Register<BlueprintAreaEnterPoint>("Teleport".localize(), (enterPoint, ch, n, index) => { + Teleport.To(enterPoint); + OwlLogging.Log($"Teleport to {enterPoint}"); + }); + BlueprintAction.Register<BlueprintArea>("Teleport".localize(), (area, ch, n, index) => { + Teleport.To(area); + OwlLogging.Log($"Teleport to {area}"); + }); + + // Quests + BlueprintAction.Register<BlueprintQuest>("Start".localize(), + (bp, ch, n, index) => { + Game.Instance.Player.QuestBook.GiveObjective(bp.Objectives.First()); + OwlLogging.Log($"Start Quest {bp} by giving first objective {bp.Objectives.First()}"); + }, + (bp, ch, index) => Game.Instance.Player.QuestBook.GetQuest(bp) == null); + + BlueprintAction.Register<BlueprintQuest>("Complete".localize(), + (bp, ch, n, index) => { + OwlLogging.Log($"Complete Quest {bp} by completing all the following objectives"); + foreach (var objective in bp.Objectives) { + Game.Instance.Player.QuestBook.CompleteObjective(objective); + OwlLogging.Log($"Complete Quest Objective {objective}"); + } + }, (bp, ch, index) => Game.Instance.Player.QuestBook.GetQuest(bp)?.State == QuestState.Started); + + // Quests Objectives + BlueprintAction.Register<BlueprintQuestObjective>("Start".localize(), + (bp, ch, n, index) => { + Game.Instance.Player.QuestBook.GiveObjective(bp); + OwlLogging.Log($"Start Quest Objective {bp}"); + }, + (bp, ch, index) => Game.Instance.Player.QuestBook.GetQuest(bp.Quest) == null); + + BlueprintAction.Register<BlueprintQuestObjective>("Complete".localize(), + (bp, ch, n, index) => { + Game.Instance.Player.QuestBook.CompleteObjective(bp); + OwlLogging.Log($"Complete Quest Objective {bp}"); + }, + (bp, ch, index) => Game.Instance.Player.QuestBook.GetQuest(bp.Quest)?.State == QuestState.Started); + + // Etudes + BlueprintAction.Register<BlueprintEtude>("Start".localize(), + (bp, ch, n, index) => { + Game.Instance.Player.EtudesSystem.StartEtude(bp); + OwlLogging.Log($"Start Etude {bp}"); + }, + (bp, ch, index) => Game.Instance.Player.EtudesSystem.EtudeIsNotStarted(bp)); + BlueprintAction.Register<BlueprintEtude>("Complete".localize(), + (bp, ch, n, index) => { + Game.Instance.Player.EtudesSystem.MarkEtudeCompleted(bp); + OwlLogging.Log($"Complete Etude {bp}"); + }, + (bp, ch, index) => !Game.Instance.Player.EtudesSystem.EtudeIsNotStarted(bp) && + !Game.Instance.Player.EtudesSystem.EtudeIsCompleted(bp)); + BlueprintAction.Register<BlueprintEtude>("Unstart".localize(), + (bp, ch, n, index) => { + Game.Instance.Player.EtudesSystem.UnstartEtude(bp); + OwlLogging.Log($"Unstart Etude {bp}"); + }, + (bp, ch, index) => !Game.Instance.Player.EtudesSystem.EtudeIsNotStarted(bp)); + // Flags + BlueprintAction.Register<BlueprintUnlockableFlag>("Unlock".localize(), + (bp, ch, n, index) => { + bp.Unlock(); + OwlLogging.Log($"Unlock UnlockableFlag {bp}"); + }, + (bp, ch, index) => !bp.IsUnlocked); + + BlueprintAction.Register<BlueprintUnlockableFlag>("Lock".localize(), + (bp, ch, n, index) => { + bp.Lock(); + OwlLogging.Log($"Lock UnlockableFlag {bp}"); + }, + (bp, ch, index) => bp.IsUnlocked); + + BlueprintAction.Register<BlueprintUnlockableFlag>(">".localize(), + (bp, ch, n, index) => { + bp.Value = bp.Value + n; + OwlLogging.Log($"Increase UnlockableFlag {bp} by {n}"); + }, + (bp, ch, index) => bp.IsUnlocked); + + BlueprintAction.Register<BlueprintUnlockableFlag>("<".localize(), + (bp, ch, n, index) => { + bp.Value = bp.Value - n; + OwlLogging.Log($"Decrease UnlockableFlag {bp} by {n}"); + }, + (bp, ch, index) => bp.IsUnlocked); + // Cutscenes + BlueprintAction.Register<Cutscene>("Play".localize(), (bp, ch, n, index) => { + OwlLogging.Log($"Play cutscene {bp}"); + Actions.ToggleModWindow(); + var cutscenePlayerData = CutscenePlayerData.Queue.FirstOrDefault(c => c.PlayActionId == bp.name); + + if (cutscenePlayerData != null) { + cutscenePlayerData.PreventDestruction = true; + cutscenePlayerData.Stop(); + cutscenePlayerData.PreventDestruction = false; + } + SceneEntitiesState state = null; // TODO: do we need this? + CutscenePlayerView.Play(bp, null, true, state).PlayerData.PlayActionId = bp.name; + }); + } + } +} \ No newline at end of file diff --git a/ToyBox/Classes/MainUI/Browser/BlueprintActionRT.cs b/ToyBox/Classes/MainUI/Browser/BlueprintActionRT.cs new file mode 100644 index 000000000..767df98bf --- /dev/null +++ b/ToyBox/Classes/MainUI/Browser/BlueprintActionRT.cs @@ -0,0 +1,206 @@ +// Copyright < 2021 > Narria (github user Cabarius) - License: MIT + +using Kingmaker; +using Kingmaker.AreaLogic.Cutscenes; +using Kingmaker.AreaLogic.Etudes; +using Kingmaker.AreaLogic.QuestSystem; +using Kingmaker.Blueprints; +using Kingmaker.Blueprints.Area; +using Kingmaker.Blueprints.Classes; +using Kingmaker.Blueprints.Facts; +using Kingmaker.Blueprints.Items; +using Kingmaker.Blueprints.Quests; +using Kingmaker.Cheats; +using Kingmaker.Designers; +using Kingmaker.Designers.EventConditionActionSystem.ContextData; +using Kingmaker.ElementsSystem; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.EntitySystem.Persistence; +using Kingmaker.GameModes; +using Kingmaker.Globalmap.Blueprints; +using Kingmaker.Globalmap.Blueprints.Colonization; +using Kingmaker.Globalmap.Blueprints.SectorMap; +using Kingmaker.Globalmap.Blueprints.SystemMap; +using Kingmaker.Globalmap.SectorMap; +using Kingmaker.PubSubSystem.Core; +using Kingmaker.UnitLogic; +using Kingmaker.UnitLogic.Abilities.Blueprints; +using Kingmaker.UnitLogic.ActivatableAbilities; +using Kingmaker.UnitLogic.Buffs.Blueprints; +using Kingmaker.UnitLogic.Progression.Features.Advancements; +using Kingmaker.Utility; +using Kingmaker.View; +using Kingmaker.Visual.CharacterSystem; +using ModKit; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace ToyBox { + + public static partial class BlueprintActions { + public static void InitializeActionsRT() { + // Features + BlueprintAction.Register<BlueprintFeature>("Add".localize(), + (bp, ch, n, index) => { + ch.Progression.Features.Add(bp); + OwlLogging.Log($"Add Feature {bp} to {ch}"); + }, + (bp, ch, index) => !ch.Progression.Features.Contains(bp)); + + BlueprintAction.Register<BlueprintFeature>("Remove".localize(), + (bp, ch, n, index) => { + ch.Progression.Features.Remove(bp); + OwlLogging.Log($"Remove Feature {bp} from {ch}"); + }, + (bp, ch, index) => ch.Progression.Features.Contains(bp)); + BlueprintAction.Register<BlueprintFeature>("<".localize(), + (bp, ch, n, index) => { + ch.Progression.Features.Get(bp)?.RemoveRank(); + OwlLogging.Log($"Remove rank from Feature {bp} for {ch}"); + }, + (bp, ch, index) => { + var feature = ch.Progression.Features.Get(bp); + return feature?.GetRank() > 1; + }); + + BlueprintAction.Register<BlueprintFeature>(">".localize(), + (bp, ch, n, index) => { + ch.Progression.Features.Get(bp)?.AddRank(); + OwlLogging.Log($"Add rank to Feature {bp} for {ch}"); + }, + (bp, ch, index) => { + var feature = ch.Progression.Features.Get(bp); + if (bp is BlueprintStatAdvancement) { + return feature != null; + } + return feature != null && feature.GetRank() < feature.Blueprint.Ranks; + }); + // Buffs + BlueprintAction.Register<BlueprintBuff>("Add".localize(), + (bp, ch, n, index) => { + GameHelper.ApplyBuff(ch, bp); + OwlLogging.Log($"Add Buff {bp} to {ch}"); + }, + (bp, ch, index) => !ch.Descriptor().Buffs.Contains(bp)); + + BlueprintAction.Register<BlueprintBuff>("Remove".localize(), + (bp, ch, n, index) => { + ch.Descriptor().Facts.Remove(bp); + OwlLogging.Log($"Remove Buff {bp} to {ch}"); + }, + (bp, ch, index) => ch.Descriptor().Buffs.Contains(bp)); + BlueprintAction.Register<BlueprintBuff>("<".localize(), + (bp, ch, n, index) => { + ch.Descriptor().Buffs.Get(bp)?.RemoveRank(); + OwlLogging.Log($"Remove rank from Buff {bp} for {ch}"); + }, + (bp, ch, index) => { + var buff = ch.Descriptor().Buffs.Get(bp); + return buff?.GetRank() > 1; + }); + + BlueprintAction.Register<BlueprintBuff>(">".localize(), + (bp, ch, n, index) => { + ch.Descriptor().Buffs.Get(bp)?.AddRank(); + OwlLogging.Log($"Add rank to Buff {bp} for {ch}"); + }, + (bp, ch, index) => { + var buff = ch.Descriptor().Buffs.Get(bp); + return buff != null && buff?.GetRank() < buff.Blueprint.MaxRank; + }); + // Kingdom Bufs + // Abilities + BlueprintAction.Register<BlueprintAbility>("Add".localize(), + (bp, ch, n, index) => { + var fact = ch.Abilities.Add(bp); + fact.AddSource(new EntityFactSource(ch)); + OwlLogging.Log($"Add Ability {bp} to {ch}"); + }, + (bp, ch, index) => !ch.Abilities.Contains(bp)); + + BlueprintAction.Register<BlueprintAbility>("Remove".localize(), + (bp, ch, n, index) => { + ch.Abilities.Remove(bp); + OwlLogging.Log($"Remove Ability {bp} to {ch}"); + }, + (bp, ch, index) => ch.Abilities.Contains(bp)); + + + // BlueprintActivatableAbility + BlueprintAction.Register<BlueprintActivatableAbility>("Add".localize(), + (bp, ch, n, index) => { + ch.Descriptor().AddFact(bp); + OwlLogging.Log($"Add ActivatableAbility {bp} to {ch}"); + }, + (bp, ch, index) => !ch.Descriptor().Facts.Contains(bp)); + + BlueprintAction.Register<BlueprintActivatableAbility>("Remove".localize(), + (bp, ch, n, index) => { + ch.Descriptor().Facts.Remove(bp); + OwlLogging.Log($"Remove ActivatableAbility {bp} to {ch}"); + }, + (bp, ch, index) => ch.Descriptor().Facts.Contains(bp)); + + // Teleport + BlueprintAction.Register<BlueprintStarSystemMap>("Teleport".localize(), (map, ch, n, index) => { + Teleport.To(map); + OwlLogging.Log($"Teleport to {map}"); + }); + BlueprintAction.Register<BlueprintSectorMapPoint>("Teleport".localize(), + (globalMapPoint, ch, n, index) => { }); //Teleport.To(globalMapPoint) +#if false // TODO: implement this + // Teleport + BlueprintAction.Register<BlueprintAreaEnterPoint>("Teleport", (enterPoint, ch, n, index) => Teleport.To(enterPoint)); + BlueprintAction.Register<BlueprintGlobalMap>("Teleport", (map, ch, n, index) => Teleport.To(map)); + BlueprintAction.Register<BlueprintArea>("Teleport", (area, ch, n, index) => Teleport.To(area)); + BlueprintAction.Register<BlueprintGlobalMapPoint>("Teleport", (globalMapPoint, ch, n, index) => Teleport.To(globalMapPoint)); + + //Army + BlueprintAction.Register<BlueprintArmyPreset>("Add Friendly", (bp, ch, n, l) => { + Actions.CreateArmy(bp,true); + }); + BlueprintAction.Register<BlueprintArmyPreset>("Add Hostile", (bp, ch, n, l) => { + Actions.CreateArmy(bp,false); + }); +#endif + BlueprintAction.Register<BlueprintPlanet>("Colonize".localize(), (bp, ch, n, index) => { + try { + CheatsColonization.ColonizePlanet(bp); + OwlLogging.Log($"Colonize Planet {bp}"); + } catch (Exception ex) { + throw new Exception("Error trying to colonize Planet. Are you in the correct Star System?\n".localize().Orange().Bold() + ex.Message + ex.StackTrace.ToString()); + } + }, (bp, ch, index) => { + var system = bp.ConnectedAreas.FirstOrDefault(f => f is BlueprintStarSystemMap) as BlueprintStarSystemMap; + return bp.GetComponent<ColonyComponent>() != null && Game.Instance.CurrentlyLoadedArea is BlueprintStarSystemMap && (system == null || Game.Instance.Player.CurrentStarSystem == system); + + }); + BlueprintAction.Register<BlueprintColony>("Colonize".localize(), (bp, ch, n, index) => { + + try { + CheatsColonization.ColonizePlanet(ColonyToPlanet[bp]); + OwlLogging.Log($"Colonize Colony {bp}"); + } catch (Exception ex) { + throw new Exception("Error trying to colonize Planet. Are you in the correct Star System?\n".localize().Orange().Bold() + ex.Message + ex.StackTrace.ToString()); + } + }, (bp, ch, index) => { + if (ColonyToPlanet == null) { + ColonyToPlanet = new(); + foreach (var planet in BlueprintLoader.Shared.GetBlueprintsOfType<BlueprintPlanet>()) { + var colonyComponent = planet.GetComponent<ColonyComponent>(); + if (colonyComponent != null) { + if (colonyComponent.ColonyBlueprint != null) { + ColonyToPlanet[colonyComponent.ColonyBlueprint] = planet; + } + } + } + } + var system = ColonyToPlanet[bp].ConnectedAreas.FirstOrDefault(f => f is BlueprintStarSystemMap) as BlueprintStarSystemMap; + return ColonyToPlanet[bp] != null && Game.Instance.CurrentlyLoadedArea is BlueprintStarSystemMap && (system == null || Game.Instance.Player.CurrentStarSystem == system); + }); + } + private static Dictionary<BlueprintColony, BlueprintPlanet> ColonyToPlanet = null; + } +} \ No newline at end of file diff --git a/ToyBox/classes/MainUI/Browser/BlueprintListUI.cs b/ToyBox/Classes/MainUI/Browser/BlueprintListUI.cs similarity index 67% rename from ToyBox/classes/MainUI/Browser/BlueprintListUI.cs rename to ToyBox/Classes/MainUI/Browser/BlueprintListUI.cs index 55ce1de16..0e0b862d9 100644 --- a/ToyBox/classes/MainUI/Browser/BlueprintListUI.cs +++ b/ToyBox/Classes/MainUI/Browser/BlueprintListUI.cs @@ -1,20 +1,20 @@ // Copyright < 2021 > Narria (github user Cabarius) - License: MIT -using UnityEngine; -using System; -using System.Collections.Generic; -using System.Linq; using Kingmaker; using Kingmaker.Blueprints; +using Kingmaker.Blueprints.Items; using Kingmaker.Blueprints.Items.Weapons; using Kingmaker.EntitySystem.Entities; using Kingmaker.RuleSystem; +using Kingmaker.UnitLogic.Levelup.Obsolete.Blueprints.Selection; using Kingmaker.Utility; using ModKit; -using static ModKit.UI; -using Kingmaker.Blueprints.Classes.Selection; -using Kingmaker.Blueprints.Items; using ModKit.DataViewer; using ModKit.Utility; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using static ModKit.UI; using static ToyBox.BlueprintExtensions; namespace ToyBox { @@ -27,18 +27,17 @@ public class BlueprintListUI { public static int maxActions = 0; public static bool needsLayout = true; public static int[] ParamSelected = new int[1000]; - public static Dictionary<BlueprintParametrizedFeature, string[]> paramBPValueNames = new() { }; - public static Dictionary<BlueprintFeatureSelection, string[]> selectionBPValuesNames = new() { }; + public static Dictionary<BlueprintFeatureSelection_Obsolete, string[]> selectionBPValuesNames = new() { }; - public static List<Action> OnGUI(UnitEntityData unit, + public static List<Action> OnGUI(BaseUnitEntity unit, IEnumerable<SimpleBlueprint> blueprints, float indent = 0, float remainingWidth = 0, - Func<string, string> titleFormater = null, - NamedTypeFilter typeFilter = null, - NavigateTo navigateTo = null + Func<string, string?>? titleFormatter = null, + NamedTypeFilter? typeFilter = null, + NavigateTo? navigateTo = null ) { List<Action> todo = new(); - if (titleFormater == null) titleFormater = (t) => t.orange().bold(); + if (titleFormatter == null) titleFormatter = (t) => RichText.Bold(RichText.Orange(t)); if (remainingWidth == 0) remainingWidth = ummWidth - indent; var index = 0; IEnumerable<SimpleBlueprint> simpleBlueprints = blueprints.ToList(); @@ -46,7 +45,7 @@ public static List<Action> OnGUI(UnitEntityData unit, foreach (var blueprint in simpleBlueprints) { var actions = blueprint.GetActions(); if (actions.Any(a => a.isRepeatable)) hasRepeatableAction = true; - + // FIXME - perf bottleneck var actionCount = actions.Sum(action => action.canPerform(blueprint, unit) ? 1 : 0); maxActions = Math.Max(actionCount, maxActions); @@ -63,7 +62,7 @@ public static List<Action> OnGUI(UnitEntityData unit, () => { }, Width(160)); Space(40); - Label("Parameter".cyan() + ": " + $"{repeatCount}".orange(), ExpandWidth(false)); + Label(RichText.Cyan("Parameter") + ": " + RichText.Orange($"{repeatCount}"), ExpandWidth(false)); repeatCount = Math.Max(1, repeatCount); repeatCount = Math.Min(100, repeatCount); EndHorizontal(); @@ -74,7 +73,7 @@ public static List<Action> OnGUI(UnitEntityData unit, var currentCount = count++; var description = blueprint.GetDescription().MarkedSubstring(Settings.searchText); if (blueprint is BlueprintItem itemBlueprint && itemBlueprint.FlavorText?.Length > 0) - description = $"{itemBlueprint.FlavorText.StripHTML().color(RGBA.notable).MarkedSubstring(Settings.searchText)}\n{description}"; + description = $"{itemBlueprint.FlavorText.StripHTML().Color(RGBA.notable).MarkedSubstring(Settings.searchText)}\n{description}"; float titleWidth = 0; var remWidth = remainingWidth - indent; using (HorizontalScope()) { @@ -84,24 +83,21 @@ public static List<Action> OnGUI(UnitEntityData unit, var titles = actions.Select(a => a.name); var name = GetTitle(blueprint); var displayName = blueprint.GetDisplayName(); - string title; + string? title; if (Settings.showDisplayAndInternalNames && displayName.Length > 0 && displayName != name) { // FIXME - horrible perf bottleneck - if (titles.Contains("Remove") || titles.Contains("Lock")) { - title = displayName.cyan().bold(); - } - else { - title = titleFormater(displayName); + if (titles.Contains("Remove".localize()) || titles.Contains("Lock".localize())) { + title = RichText.Bold(RichText.Cyan(displayName)); + } else { + title = titleFormatter(displayName); } - title = $"{title} : {name.color(RGBA.darkgrey)}"; - } - else { + title = $"{title} : {name.Color(RGBA.darkgrey)}"; + } else { // FIXME - horrible perf bottleneck - if (titles.Contains("Remove") || titles.Contains("Lock")) { - title = name.cyan().bold(); - } - else { - title = titleFormater(name); + if (titles.Contains("Remove".localize()) || titles.Contains("Lock".localize())) { + title = RichText.Bold(RichText.Cyan(name)); + } else { + title = titleFormatter(name); } } titleWidth = (remainingWidth / (IsWide ? 3 : 4)) - indent; @@ -120,16 +116,15 @@ public static List<Action> OnGUI(UnitEntityData unit, var lockAction = actions.ElementAt(lockIndex); ActionButton("<", () => { flags.SetFlagValue(flagBP, flags.GetFlagValue(flagBP) - 1); }, Width(50)); Space(25); - Label($"{flags.GetFlagValue(flagBP)}".orange().bold(), MinWidth(50)); + Label(RichText.Bold(RichText.Orange($"{flags.GetFlagValue(flagBP)}")), MinWidth(50)); ActionButton(">", () => { flags.SetFlagValue(flagBP, flags.GetFlagValue(flagBP) + 1); }, Width(50)); Space(50); ActionButton(lockAction.name, () => { lockAction.action(blueprint, unit, repeatCount); }, Width(120)); Space(100); #if DEBUG - Label(flagBP.GetDescription().green()); + Label(flagBP.GetDescription().Green()); #endif - } - else { + } else { // FIXME - perf bottleneck var unlockIndex = titles.IndexOf("Unlock"); if (unlockIndex >= 0) { @@ -140,8 +135,7 @@ public static List<Action> OnGUI(UnitEntityData unit, } } remWidth -= 300; - } - else { + } else { for (var ii = 0; ii < maxActions; ii++) { if (ii < actionCount) { var action = actions.ElementAt(ii); @@ -159,8 +153,7 @@ public static List<Action> OnGUI(UnitEntityData unit, Space(10); remWidth -= 174.0f + extraSpace; - } - else { + } else { Space(174); } } @@ -177,7 +170,7 @@ public static List<Action> OnGUI(UnitEntityData unit, typeString = $"{typeString} - {rarity}".Rarity(rarity); } if (!typeString.Contains(collatorString)) { - typeString += $" : {collatorString}".yellow(); + typeString += RichText.Yellow($" : {collatorString}"); navigateStrings.Add(collatorString); } } @@ -189,18 +182,18 @@ public static List<Action> OnGUI(UnitEntityData unit, attributes = attr; } - if (attributes.Length > 1) typeString += $" - {attributes.orange()}"; + if (attributes.Length > 1) typeString += $" - {RichText.Orange(attributes)}"; if (description != null && description.Length > 0) description = $"{description}"; else description = ""; if (blueprint is BlueprintScriptableObject bpso) { if (Settings.showComponents && bpso.ComponentsArray?.Length > 0) { - var componentStr = string.Join<object>(", ", bpso.ComponentsArray).color(RGBA.brown); + var componentStr = string.Join<object>(", ", bpso.ComponentsArray).Color(RGBA.brown); if (description.Length == 0) description = componentStr; else description = description + "\n" + componentStr; } if (Settings.showElements && bpso.ElementsArray?.Count > 0) { - var elementsStr = string.Join<object>("\n", bpso.ElementsArray.Select(e => $"{e.GetType().Name.cyan()} {e.GetCaption()}")).yellow(); + var elementsStr = RichText.Yellow(string.Join<object>("\n", bpso.ElementsArray.Select(e => $"{e.GetType().Name.Cyan()} {e.GetCaption()}"))); if (description.Length == 0) description = elementsStr; else description = description + "\n" + elementsStr; } @@ -209,59 +202,26 @@ public static List<Action> OnGUI(UnitEntityData unit, using (HorizontalScope(Width(remWidth))) { ReflectionTreeView.DetailToggle("", blueprint, blueprint, 0); Space(-17); - if (Settings.showAssetIDs) { + if (Settings.showAssetIDs) { ActionButton(typeString, () => navigateTo?.Invoke(navigateStrings.ToArray()), rarityButtonStyle); ClipboardLabel(blueprint.AssetGuid.ToString(), ExpandWidth(false)); - } - else ActionButton(typeString, () => navigateTo?.Invoke(navigateStrings.ToArray()), rarityButtonStyle); + } else ActionButton(typeString, () => navigateTo?.Invoke(navigateStrings.ToArray()), rarityButtonStyle); Space(17); } - if (description.Length > 0) Label(description.green(), Width(remWidth)); - } - } - if (blueprint is BlueprintParametrizedFeature paramBP) { - using (HorizontalScope()) { - Space(titleWidth); - using (VerticalScope()) { - using (HorizontalScope(GUI.skin.button)) { - var content = new GUIContent($"{paramBP.Name.yellow()}"); - var labelWidth = GUI.skin.label.CalcSize(content).x; - Space(indent); - //UI.Space(indent + titleWidth - labelWidth - 25); - Label(content, Width(labelWidth)); - Space(25); - var nameStrings = paramBPValueNames.GetValueOrDefault(paramBP, null); - if (nameStrings == null) { - nameStrings = paramBP.Items.Select(x => x.Name).OrderBy(x => x).ToArray().TrimCommonPrefix(); - paramBPValueNames[paramBP] = nameStrings; - } - ActionSelectionGrid( - ref ParamSelected[currentCount], - nameStrings, - 6, - (selected) => { }, - GUI.skin.toggle, - Width(remWidth) - ); - //UI.SelectionGrid(ref ParamSelected[currentCount], nameStrings, 6, UI.Width(remWidth + titleWidth)); // UI.Width(remWidth)); - } - Space(15); - } + if (description.Length > 0) Label(RichText.Green(description), Width(remWidth)); } } - if (blueprint is BlueprintFeatureSelection selectionBP) { + if (blueprint is BlueprintFeatureSelection_Obsolete selectionBP) { using (HorizontalScope()) { Space(titleWidth); using (VerticalScope()) { - var needsSelection = false; var nameStrings = selectionBPValuesNames.GetValueOrDefault(selectionBP, null); if (nameStrings == null) { - needsSelection = true; nameStrings = selectionBP.AllFeatures.Select(x => x.NameSafe()).OrderBy(x => x).ToArray().TrimCommonPrefix(); selectionBPValuesNames[selectionBP] = nameStrings; } using (HorizontalScope(GUI.skin.button)) { - var content = new GUIContent($"{selectionBP.Name.yellow()}"); + var content = new GUIContent($"{RichText.Yellow(selectionBP.Name)}"); var labelWidth = GUI.skin.label.CalcSize(content).x; Space(indent); //UI.Space(indent + titleWidth - labelWidth - 25); @@ -278,25 +238,6 @@ public static List<Action> OnGUI(UnitEntityData unit, ); //UI.SelectionGrid(ref ParamSelected[currentCount], nameStrings, 6, UI.Width(remWidth + titleWidth)); // UI.Width(remWidth)); } - if (unit.Progression.Selections.TryGetValue(selectionBP, out var selectionData)) { - foreach (var entry in selectionData.SelectionsByLevel) { - foreach (var selection in entry.Value) { - if (needsSelection) { - ParamSelected[currentCount] = selectionBP.AllFeatures.IndexOf(selection); - needsSelection = false; - } - using (HorizontalScope()) { - ActionButton("Remove", () => { - - }, Width(160)); - Space(25); - Label($"{entry.Key} ".yellow() + selection.Name.orange(), Width(250)); - Space(25); - Label(selection.Description.StripHTML().green()); - } - } - } - } Space(15); } } diff --git a/ToyBox/classes/MainUI/Browser/BuffExclusionEditor.cs b/ToyBox/Classes/MainUI/Browser/BuffExclusionEditor.cs similarity index 66% rename from ToyBox/classes/MainUI/Browser/BuffExclusionEditor.cs rename to ToyBox/Classes/MainUI/Browser/BuffExclusionEditor.cs index 5fbaefd15..1ade2496d 100644 --- a/ToyBox/classes/MainUI/Browser/BuffExclusionEditor.cs +++ b/ToyBox/Classes/MainUI/Browser/BuffExclusionEditor.cs @@ -12,6 +12,7 @@ public class BuffExclusionEditor { public static Settings settings => Main.Settings; //We'll use this to add new buffs + private static List<BlueprintBuff> _defaultBuffExceptions; private static List<BlueprintBuff> _buffExceptions; private static List<BlueprintBuff> _allBuffs; private static string _searchString; @@ -19,20 +20,23 @@ public class BuffExclusionEditor { private static IEnumerable<BlueprintBuff> _displayedBuffs; private static int _pageSize = 10; private static int _currentPage = 0; - private static string _paginationString; + private static string? _paginationString; private static string _goToPage = "1"; private static bool _showCurrentExceptions = true; + private static bool _showDefaultExceptions = false; private static bool _showBuffsToAdd = false; - public static void OnGUI( - ) { - if (_buffExceptions == null) { + public static void OnGUI() { + if ((_allBuffs?.Count ?? 0) == 0) { _buffExceptions = BlueprintLoader.Shared.GetBlueprintsByGuids<BlueprintBuff>(settings.buffsToIgnoreForDurationMultiplier) ?.OrderBy(b => b.GetDisplayName()) ?.ToList(); - _allBuffs = BlueprintLoader.Shared.GetBlueprints<BlueprintBuff>() - ?.Where(bp => !bp.IsHiddenInUI && !bp.HiddenInInspector && !bp.GetDisplayName().StartsWith("[unknown key") && !bp.IsClassFeature && !bp.Harmful) + _defaultBuffExceptions = BlueprintLoader.Shared.GetBlueprintsByGuids<BlueprintBuff>(SettingsDefaults.DefaultBuffsToIgnoreForDurationMultiplier) + ?.OrderBy(b => b.GetDisplayName()) + ?.ToList(); + _allBuffs = BlueprintLoader.Shared.GetBlueprintsOfType<BlueprintBuff>() + //?.Where(bp => !bp.IsHiddenInUI) ?.OrderBy(b => b.GetDisplayName()) ?.ToList(); _searchResults = GetValidBuffsToAdd(); @@ -40,24 +44,25 @@ public static void OnGUI( SetPaginationString(); } VStack(null, - () => { if (BlueprintLoader.Shared.IsLoading) { - Label(("Blueprints".orange().bold() + " loading: ").localize() + BlueprintLoader.Shared.progress.ToString("P2").cyan().bold()); - } - else Space(25); + Label(("Blueprints".Orange().Bold() + " loading: ").localize() + BlueprintLoader.Shared.progress.ToString("P2").Cyan().Bold()); + } else Space(25); }, () => { if (BlueprintLoader.Shared.IsLoading || _searchResults == null) return; using (VerticalScope()) { - Func<bool, string> hideOrShowString = (bool isShown) => isShown ? "Hide".localize() : "Show".localize(); + Func<bool, string?> hideOrShowString = (bool isShown) => isShown ? "Hide".localize() : "Show".localize(); - DisclosureToggle($"{hideOrShowString(_showCurrentExceptions)} " + "current list".localize(), ref _showCurrentExceptions); + DisclosureToggle($"{hideOrShowString(_showCurrentExceptions)} " + "custom exceptions".localize(), ref _showCurrentExceptions); if (_showCurrentExceptions) { BuffList(_buffExceptions); } - + DisclosureToggle($"{hideOrShowString(_showDefaultExceptions)} " + "default exceptions".localize(), ref _showDefaultExceptions); + if (_showDefaultExceptions) { + BuffList(_defaultBuffExceptions, true); + } DisclosureToggle($"{hideOrShowString(_showBuffsToAdd)} " + "buffs to add to list".localize(), ref _showBuffsToAdd, 175, () => FilterBuffList(_searchString)); if (_showBuffsToAdd) { using (HorizontalScope()) { @@ -101,7 +106,7 @@ private static void FilterBuffList(string search) { ? buffList : buffList.Where(b => b.AssetGuidThreadSafe.ToLowerInvariant() == searchLower || - b.GetDisplayName().ToLowerInvariant().Contains(searchLower) || + BlueprintExtensions.GetSearchKey(b, true).ToLowerInvariant().Contains(searchLower) || b.NameSafe().ToLowerInvariant().Contains(searchLower)); _displayedBuffs = GetPaginatedBuffs(); SetPaginationString(); @@ -132,44 +137,45 @@ private static int GetMaxPages() { return (int)Math.Ceiling((decimal)_searchResults.Count() / _pageSize); } - private static void BuffList(IEnumerable<BlueprintBuff> buffs) { + private static void BuffList(IEnumerable<BlueprintBuff> buffs, bool showDefaults = false) { if (buffs == null) return; var divisor = IsWide ? 6 : 4; var titleWidth = ummWidth / divisor; var complexNameWidth = ummWidth / divisor; var guidWidth = ummWidth / divisor / 2; - VStack(null, buffs?.OrderBy(b => b.GetDisplayName()).Select<BlueprintBuff, Action>(bp => () => { - using (HorizontalScope()) { - Label(bp.GetDisplayName().cyan().bold(), Width(titleWidth)); - Label(bp.NameSafe().orange().bold(), Width(complexNameWidth)); - if (settings.showAssetIDs) { - ClipboardLabel(bp.AssetGuidThreadSafe, ExpandWidth(false), Width(guidWidth)); - } - //It seems that if you specify defaults, saving settings without the defaults won't actually - //remove the items from the list. This just prevents confusion by removing the button altogether. - if (!settings.buffsToIgnoreForDurationMultiplier.Contains(bp.AssetGuidThreadSafe)) { - ActionButton("Add".localize(), () => { - AddBuff(bp.AssetGuidThreadSafe); - }); - } - if (settings.buffsToIgnoreForDurationMultiplier.Contains(bp.AssetGuidThreadSafe) - && !SettingsDefaults.DefaultBuffsToIgnoreForDurationMultiplier.Contains(bp.AssetGuidThreadSafe)) { - ActionButton("Remove".localize(), () => { - RemoveBuff(bp.AssetGuidThreadSafe); - }); + VStack(null, buffs?.Where(bp => showDefaults || !SettingsDefaults.DefaultBuffsToIgnoreForDurationMultiplier.Contains(bp.AssetGuidThreadSafe)) + ?.OrderBy(BlueprintExtensions.GetSortKey).Select<BlueprintBuff, Action>(bp => () => { + using (HorizontalScope()) { + Label(BlueprintExtensions.GetTitle(bp).Cyan().Bold(), Width(titleWidth)); + Label(bp.NameSafe().Orange().Bold(), Width(complexNameWidth)); + if (settings.showAssetIDs) { + ClipboardLabel(bp.AssetGuidThreadSafe, ExpandWidth(false), Width(guidWidth)); + } + //It seems that if you specify defaults, saving settings without the defaults won't actually + //remove the items from the list. This just prevents confusion by removing the button altogether. + if (!settings.buffsToIgnoreForDurationMultiplier.Contains(bp.AssetGuidThreadSafe)) { + ActionButton("Add".localize(), () => { + AddBuff(bp.AssetGuidThreadSafe); + }); + } + if (settings.buffsToIgnoreForDurationMultiplier.Contains(bp.AssetGuidThreadSafe) + && !SettingsDefaults.DefaultBuffsToIgnoreForDurationMultiplier.Contains(bp.AssetGuidThreadSafe)) { + ActionButton("Remove".localize(), () => { + RemoveBuff(bp.AssetGuidThreadSafe); + }); + } + Label(bp.GetDescription().Green()); } - Label(bp.GetDescription().green()); - } - Space(25); - }) + Space(25); + }) .Prepend(() => { using (HorizontalScope()) { - Label("In-Game Name".localize().red().bold(), Width(titleWidth)); - Label("Internal Name".localize().red().bold(), Width(complexNameWidth)); + Label("In-Game Name".localize().Red().Bold(), Width(titleWidth)); + Label("Internal Name".localize().Red().Bold(), Width(complexNameWidth)); if (settings.showAssetIDs) { - Label("Guid".localize().red().bold(), Width(guidWidth)); + Label("Guid".localize().Red().Bold(), Width(guidWidth)); } - Label("Description".localize().red().bold()); + Label("Description".localize().Red().Bold()); } }) .Append(() => { }) @@ -197,14 +203,14 @@ private static void RemoveBuff(string buffGuid) { } private static void TriggerReload() { + // TODO: Very expensive; we should probably port all of this to Browser _buffExceptions = null; } private static void LogCurrentlyIgnoredBuffs() => Mod.Log($"Currently ignored buffs: {string.Join(", ", settings.buffsToIgnoreForDurationMultiplier)}. There are {_allBuffs.Count} total buffs."); - - public static bool IsValidBuff(string buffGuid) => BlueprintLoader.Shared.GetBlueprintsByGuids<BlueprintBuff>(new[] { buffGuid }).Count() > 0; + public static bool IsValidBuff(string buffGuid) => ResourcesLibrary.TryGetBlueprint(buffGuid) is BlueprintBuff buff; } } \ No newline at end of file diff --git a/ToyBox/classes/MainUI/Browser/Editor.cs b/ToyBox/Classes/MainUI/Browser/Editor.cs similarity index 100% rename from ToyBox/classes/MainUI/Browser/Editor.cs rename to ToyBox/Classes/MainUI/Browser/Editor.cs diff --git a/ToyBox/Classes/MainUI/Browser/FactsEditor.cs b/ToyBox/Classes/MainUI/Browser/FactsEditor.cs new file mode 100644 index 000000000..34aa87c25 --- /dev/null +++ b/ToyBox/Classes/MainUI/Browser/FactsEditor.cs @@ -0,0 +1,195 @@ +// Copyright < 2021 > Narria (github user Cabarius) - License: MIT +using Kingmaker.Blueprints; +using Kingmaker.Blueprints.Classes; +using Kingmaker.Blueprints.Facts; +using Kingmaker.EntitySystem; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.PubSubSystem; +using Kingmaker.UI; +using Kingmaker.UnitLogic; +using Kingmaker.UnitLogic.Abilities; +using Kingmaker.UnitLogic.Abilities.Blueprints; +using Kingmaker.UnitLogic.ActivatableAbilities; +using Kingmaker.UnitLogic.Buffs; +using Kingmaker.UnitLogic.Buffs.Blueprints; +using Kingmaker.UnitLogic.Levelup.Obsolete.Blueprints.Selection; +using Kingmaker.UnitLogic.Mechanics.Blueprints; +using Kingmaker.UnitLogic.Mechanics.Facts; +using Kingmaker.Utility; +using ModKit; +using ModKit.DataViewer; +using ModKit.Utility; +using System; +using System.Collections.Generic; +using System.Linq; +using ToyBox.BagOfPatches; +using UnityEngine; +using static ModKit.UI; +using static ToyBox.BlueprintExtensions; + +namespace ToyBox { + public class FactsEditor { + private static Settings Settings => Main.Settings; + private static bool _showTree = false; + private static readonly int repeatCount = 1; + private static readonly FeaturesTreeEditor treeEditor = new(); + + private static readonly Dictionary<BaseUnitEntity, Browser<BlueprintFeature, Feature>> FeatureBrowserDict = new(); + private static readonly Dictionary<BaseUnitEntity, Browser<BlueprintBuff, Buff>> BuffBrowserDict = new(); + private static readonly Dictionary<BaseUnitEntity, Browser<BlueprintMechanicEntityFact, MechanicEntityFact>> AbilityBrowserDict = new(); + public static void BlueprintRowGUI<Item, Definition>(Browser<Definition, Item> browser, + Item feature, + Definition blueprint, + BaseUnitEntity ch, + List<Action> todo + ) where Definition : BlueprintScriptableObject, IUIDataProvider { + var remainingWidth = ummWidth; + // Indent + remainingWidth -= 50; + var titleWidth = (remainingWidth / (IsWide ? 3.5f : 4.0f)) - 100; + remainingWidth -= titleWidth; + + var text = GetTitle(blueprint).MarkedSubstring(browser.SearchText); + var titleKey = $"{blueprint.AssetGuid}"; + if (feature != null) { + text = RichText.Bold(text.Cyan()); + } + if (blueprint is BlueprintFeatureSelection_Obsolete featureSelection) { + if (Browser.DetailToggle(text, blueprint, feature != null ? feature : blueprint, (int)titleWidth)) + browser.ReloadData(); + } else + Label(text, Width((int)titleWidth)); + + var lastRect = GUILayoutUtility.GetLastRect(); + + var mutatorLookup = BlueprintAction.ActionsForType(blueprint.GetType()) + .GroupBy(a => a.name).Select(g => g.FirstOrDefault()) + .ToDictionary(a => a.name, a => a); + var add = mutatorLookup.GetValueOrDefault("Add".localize(), null); + var remove = mutatorLookup.GetValueOrDefault("Remove".localize(), null); + var decrease = mutatorLookup.GetValueOrDefault("<", null); + var increase = mutatorLookup.GetValueOrDefault(">", null); + + mutatorLookup.Remove("Add".localize()); + mutatorLookup.Remove("Remove".localize()); + mutatorLookup.Remove("<"); + mutatorLookup.Remove(">"); + if (feature != null) { + bool canDecrease = decrease?.canPerform(blueprint, ch) ?? false; + bool canIncrease = increase?.canPerform(blueprint, ch) ?? false; + if ((canDecrease || canIncrease) && feature is MechanicEntityFact rankFeature) { + var v = rankFeature.GetRank(); + decrease.BlueprintActionButton(ch, blueprint, () => todo.Add(() => decrease!.action(blueprint, ch, repeatCount)), 60); + Space(10f); + Label(RichText.Bold(RichText.Orange($"{v}")), Width(30)); + increase.BlueprintActionButton(ch, blueprint, () => todo.Add(() => increase!.action(blueprint, ch, repeatCount)), 60); + Space(17); + remainingWidth -= 190; + } else { + Space(190); + remainingWidth -= 190; + } + } else { + Space(190); + remainingWidth -= 190; + } + var canAdd = add?.canPerform(blueprint, ch) ?? false; + var canRemove = remove?.canPerform(blueprint, ch) ?? false; + if (canRemove) { + remove.BlueprintActionButton(ch, blueprint, () => todo.Add(() => { browser.needsReloadData = !browser.ShowAll; remove.action(blueprint, ch, repeatCount); }), 150); + } + if (canAdd) { + add.BlueprintActionButton(ch, blueprint, () => todo.Add(() => { add.action(blueprint, ch, repeatCount); }), 150); + } + remainingWidth -= 178; + Space(20); remainingWidth -= 20; + ReflectionTreeView.DetailToggle("", blueprint, feature != null ? feature : blueprint, 0); + using (VerticalScope(Width(remainingWidth - 100))) { + try { + if (Settings.showAssetIDs) + ClipboardLabel(blueprint.AssetGuid.ToString(), AutoWidth()); + Label(RichText.Green(blueprint.Description.StripHTML().MarkedSubstring(browser.SearchText)), Width(remainingWidth - 100)); + } catch (Exception e) { + Mod.Warn($"Error in blueprint: {blueprint.AssetGuid}"); + Mod.Warn($" name: {blueprint.name}"); + Mod.Error(e); + } + } + } + public static void BlueprintDetailGUI<Item, Definition, K, V>(Definition blueprint, Item feature, BaseUnitEntity ch, Browser<K, V> browser) + where Item : MechanicEntityFact + where Definition : BlueprintMechanicEntityFact { + // TODO: RT + } + public static List<Action> OnGUI<Item, Definition>(BaseUnitEntity ch, Browser<Definition, Item> browser, List<Item> fact, string name) + where Item : MechanicEntityFact + where Definition : BlueprintMechanicEntityFact { + bool updateTree = false; + List<Action> todo = new(); + if (_showTree) { + using (HorizontalScope()) { + Space(670); + Toggle("Show Tree".localize(), ref _showTree, Width(250)); + } + treeEditor.OnGUI(ch, updateTree); + } else { + browser.OnGUI( + fact, + BlueprintLoader.Shared.GetBlueprintsOfType<Definition>, + (feature) => (Definition)feature.Blueprint, + (blueprint) => $"{GetSearchKey(blueprint)}" + (Settings.searchDescriptions ? $"{blueprint.Description}" : ""), + blueprint => new[] { GetSortKey(blueprint) }, + () => { + using (HorizontalScope()) { + var reloadData = false; + Toggle("Show GUIDs".localize(), ref Main.Settings.showAssetIDs); + 20.space(); + reloadData |= Toggle("Show Internal Names".localize(), ref Settings.showDisplayAndInternalNames); + 20.space(); + updateTree |= Toggle("Show Tree".localize(), ref _showTree); + 20.space(); + //Toggle("Show Inspector", ref Settings.factEditorShowInspector); + //20.space(); + reloadData |= Toggle("Search Descriptions".localize(), ref Settings.searchDescriptions); + if (reloadData) { + browser.ResetSearch(); + } + } + }, + (blueprint, feature) => BlueprintRowGUI(browser, feature, blueprint, ch, todo), + (blueprint, feature) => { + ReflectionTreeView.OnDetailGUI(blueprint); + BlueprintDetailGUI(blueprint, feature, ch, browser); + }, 50, false, true, 100, 300, "", true); + } + return todo; + } + public static List<Action> OnGUI(BaseUnitEntity ch, List<Feature> feature) { + var featureBrowser = FeatureBrowserDict.GetValueOrDefault(ch, null); + if (featureBrowser == null) { + featureBrowser = new Browser<BlueprintFeature, Feature>(Mod.ModKitSettings.searchAsYouType, true) { }; + FeatureBrowserDict[ch] = featureBrowser; + } + return OnGUI(ch, featureBrowser, feature, "Features"); + } + public static List<Action> OnGUI(BaseUnitEntity ch, List<Buff> buff) { + var buffBrowser = BuffBrowserDict.GetValueOrDefault(ch, null); + if (buffBrowser == null) { + buffBrowser = new Browser<BlueprintBuff, Buff>(Mod.ModKitSettings.searchAsYouType, true); + BuffBrowserDict[ch] = buffBrowser; + } + return OnGUI(ch, buffBrowser, buff, "Buffs"); + } + public static List<Action> OnGUI(BaseUnitEntity ch, List<Ability> ability, List<ActivatableAbility> activatable) { + var abilityBrowser = AbilityBrowserDict.GetValueOrDefault(ch, null); + var combined = new List<MechanicEntityFact>(); + if (abilityBrowser == null) { + abilityBrowser = new Browser<BlueprintMechanicEntityFact, MechanicEntityFact>(Mod.ModKitSettings.searchAsYouType, true); + AbilityBrowserDict[ch] = abilityBrowser; + } + combined.AddRange(ability); + combined.AddRange(activatable); + return OnGUI(ch, abilityBrowser, combined, "Abilities"); + } + } +} diff --git a/ToyBox/Classes/MainUI/Browser/SearchAndPick.cs b/ToyBox/Classes/MainUI/Browser/SearchAndPick.cs new file mode 100644 index 000000000..6f82ff686 --- /dev/null +++ b/ToyBox/Classes/MainUI/Browser/SearchAndPick.cs @@ -0,0 +1,505 @@ +// Copyright < 2021 > Narria (github user Cabarius) - License: MIT +using Kingmaker; +using Kingmaker.AI.Blueprints; +using Kingmaker.AreaLogic.Cutscenes; +using Kingmaker.AreaLogic.Etudes; +using Kingmaker.Blueprints; +using Kingmaker.Blueprints.Area; +using Kingmaker.Blueprints.Classes; +using Kingmaker.Blueprints.Facts; +using Kingmaker.Blueprints.Items; +using Kingmaker.Blueprints.Items.Armors; +using Kingmaker.Blueprints.Items.Equipment; +using Kingmaker.Blueprints.Items.Weapons; +using Kingmaker.Blueprints.Quests; +using Kingmaker.Designers.EventConditionActionSystem.Actions; +using Kingmaker.DialogSystem.Blueprints; +using Kingmaker.ElementsSystem; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.Globalmap.Blueprints; +using Kingmaker.Globalmap.Blueprints.Colonization; +using Kingmaker.Globalmap.Blueprints.SectorMap; +using Kingmaker.Globalmap.Blueprints.SystemMap; +using Kingmaker.UnitLogic; +using Kingmaker.UnitLogic.Abilities.Blueprints; +using Kingmaker.UnitLogic.Buffs.Blueprints; +using Kingmaker.UnitLogic.Levelup.Obsolete.Blueprints.Selection; +using Kingmaker.UnitLogic.Mechanics.Blueprints; +using Kingmaker.UnitLogic.Mechanics.Facts; +using Kingmaker.UnitLogic.Progression.Paths; +using Kingmaker.Utility; +using ModKit; +using ModKit.DataViewer; +using ModKit.Utility; +using ModKit.Utility.Extensions; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using static ModKit.UI; +using static ToyBox.BlueprintExtensions; + +namespace ToyBox { + public static class SearchAndPick { + public static Settings Settings => Main.Settings; + + public static IEnumerable<SimpleBlueprint> bps = null; + public static bool hasRepeatableAction; + public static int maxActions = 0; + public static int collationPickerPageSize = 30; + // Need to cache the collators; if not then certain Category changes can lead to Cast Errors + // Example All -> Spellbooks + public static Dictionary<Type, Func<SimpleBlueprint, List<string>>> collatorCache = new(); + public static Dictionary<string, string> keyToDisplayName = new(); + public static int collationPickerPageCount => (int)Math.Ceiling((double)collationKeys?.Count / collationPickerPageSize); + public static int collationPickerCurrentPage = 1; + public static int repeatCount = 1; + public static int selectedCollationIndex = 0; + public static string collationSearchText = ""; + public static string parameter = ""; + public static bool needsRedoKeys = true; + public static int bpCount = 0; + public static List<string> collationKeys; + public static BaseUnitEntity selectedUnit; + public static Browser<SimpleBlueprint, SimpleBlueprint> SearchAndPickBrowser = new(Mod.ModKitSettings.searchAsYouType); + public static int[] ParamSelected = new int[1000]; + public static Dictionary<BlueprintFeatureSelection_Obsolete, string[]> selectionBPValuesNames = new() { }; + + private static readonly NamedTypeFilter[] blueprintTypeFilters = new NamedTypeFilter[] { + new NamedTypeFilter<SimpleBlueprint>("All", null, bp => bp.CollationNames( +#if DEBUG + // Whatever is collated here results in roughly 14k Collation Keys in Wrath. + bp.m_AllElements?.OfType<Condition>()?.Select(e => e.GetCaption() ?? "")?.ToArray() ?? new string[] {} +#endif + )), + //new NamedTypeFilter<SimpleBlueprint>("All", null, bp => bp.Collat`ionNames(bp.m_AllElements?.Select(e => e.GetType().Name).ToArray() ?? new string[] {})), + //new NamedTypeFilter<SimpleBlueprint>("All", null, bp => bp.CollationNames(bp.m_AllElements?.Select(e => e.ToString().TrimEnd(digits)).ToArray() ?? new string[] {})), + //new NamedTypeFilter<SimpleBlueprint>("All", null, bp => bp.CollationNames(bp.m_AllElements?.Select(e => e.name.Split('$')[1].TrimEnd(digits)).ToArray() ?? new string[] {})), + new NamedTypeFilter<BlueprintFact>("Facts", null, bp => bp.CollationNames()), + new NamedTypeFilter<BlueprintFeature>("Features", null, bp => bp.CollationNames( + )), + new NamedTypeFilter<BlueprintCareerPath>("Careers", null, bp => bp.CollationNames()), + new NamedTypeFilter<BlueprintProgression>("Progression", null, bp => bp.Classes.NotNull().Select(cl => cl.Name).ToList()), + new NamedTypeFilter<BlueprintArchetype>("Archetypes", null, bp => bp.CollationNames()), + new NamedTypeFilter<BlueprintAbility>("Abilities", null, bp => bp.CollationNames()), + new NamedTypeFilter<BlueprintAbility>("Spells", bp => bp.IsSpell, bp => bp.CollationNames(bp.School.ToString())), + new NamedTypeFilter<BlueprintBrain>("Brains", null, bp => bp.CollationNames()), + new NamedTypeFilter<BlueprintAbilityResource>("Ability Rsrc", null, bp => bp.CollationNames()), + new NamedTypeFilter<BlueprintSpellbook>("Spellbooks", null, bp => bp.CollationNames(bp.CharacterClass.Name.ToString())), + new NamedTypeFilter<BlueprintBuff>("Buffs", null, bp => bp.CollationNames()), + new NamedTypeFilter<BlueprintItem>("Item", null, (bp) => { + if (bp.m_NonIdentifiedNameText?.ToString().Length > 0) return bp.CollationNames(bp.m_NonIdentifiedNameText); + return bp.CollationNames(bp.ItemType.ToString()); + }), + new NamedTypeFilter<BlueprintItemEquipment>("Equipment", null, (bp) => bp.CollationNames(bp.ItemType.ToString(), $"{bp.GetCost().ToBinString(RichText.Yellow("⊙"))}")), + new NamedTypeFilter<BlueprintItemEquipment>("Equip (rarity)", null, (bp) => new List<string?> {bp.Rarity().GetString() }), + new NamedTypeFilter<BlueprintItemWeapon>("Weapons", null, (bp) => { + var family = bp.Family; + var category = bp.Category; + return bp.CollationNames(family.ToString(), category.ToString(), $"{bp.GetCost().ToBinString(RichText.Yellow("⊙"))}"); + // return bp.CollationNames("?", $"{bp.GetCost().ToBinString("⊙".yellow())}"); + }), + new NamedTypeFilter<BlueprintItemArmor>("Armor", null, (bp) => { + var type = bp.Type; + if (type != null) return bp.CollationNames(type.DefaultName, $"{bp.GetCost().ToBinString(RichText.Yellow("⊙"))}"); + return bp.CollationNames("?", $"{bp.GetCost().ToBinString(RichText.Yellow("⊙"))}"); + }), + new NamedTypeFilter<BlueprintItemEquipmentUsable>("Usable", null, bp => bp.CollationNames(bp.SubtypeName, $"{bp.GetCost().ToBinString(RichText.Yellow("⊙"))}")), + new NamedTypeFilter<BlueprintUnit>("Units", null, bp => bp.CollationNames(bp.Race?.Name ?? "?", $"CR{bp.CR}")), + new NamedTypeFilter<BlueprintUnit>("Units CR", null, bp => bp.CollationNames($"CR {bp.CR}")), + new NamedTypeFilter<BlueprintRace>("Races", null, bp => bp.CollationNames()), + new NamedTypeFilter<BlueprintArea>("Areas", null, bp => bp.CollationNames()), + //new NamedTypeFilter<BlueprintAreaPart>("Area Parts", null, bp => bp.CollationName()), + new NamedTypeFilter<BlueprintAreaEnterPoint>("Area Entry", null, bp =>bp.CollationNames(bp.m_Area.NameSafe())), + //new NamedTypeFilter<BlueprintAreaEnterPoint>("AreaEntry ", null, bp => bp.m_Tooltip.ToString()), + new NamedTypeFilter<BlueprintStarSystemMap>( + "System Map", null, + bp => { + var starNames = bp.Stars.Select(r => $"☀ {r.Star.Get().NameForAcronym}"); + var planetNames = bp.Planets.Select(p => $"◍ {p.Get().Name}"); + return bp.CollationNames(starNames.Concat(planetNames).ToArray()); + } + ), + new NamedTypeFilter<BlueprintSectorMapPoint>("Sector Map Points", null, bp => bp.CollationNames(bp.Name.Split(' '))), + + new NamedTypeFilter<Cutscene>("Cut Scenes", null, bp => bp.CollationNames(bp.Priority.ToString())), + //new NamedTypeFilter<BlueprintMythicInfo>("Mythic Info"), + new NamedTypeFilter<BlueprintQuest>("Quests", null, bp => bp.CollationNames(bp.m_Type.ToString())), + new NamedTypeFilter<BlueprintQuestObjective>("QuestObj", null, bp => bp.CaptionCollationNames()), + new NamedTypeFilter<BlueprintEtude>("Etudes", null, bp =>bp.CollationNames(bp.Parent?.GetBlueprint().NameSafe() ?? "" )), + new NamedTypeFilter<BlueprintUnlockableFlag>("Flags", null, bp => bp.CaptionCollationNames()), + new NamedTypeFilter<BlueprintDialog>("Dialog",null, bp => bp.CaptionCollationNames()), + new NamedTypeFilter<BlueprintCue>("Cues", null, bp => { + if (bp.Conditions.HasConditions) { + return bp.CollationNames(bp.Conditions.Conditions.First().NameSafe().SubstringBetweenCharacters('$', '$')); + } + return new List<string?> { "-" }; + }), + new NamedTypeFilter<BlueprintAnswer>("Answer", null, bp => bp.CaptionCollationNames()), + new NamedTypeFilter<BlueprintPlanet>("Planets", null, bp => bp.CaptionCollationNames()), + new NamedTypeFilter<BlueprintColony>("Colonies", null, bp => bp.CaptionCollationNames()), + new NamedTypeFilter<BlueprintAreaPreset>("AreaPresets", null, bp => bp.CollationNames()), +#if false + new NamedTypeFilter<BlueprintItemEquipment>("Equip (ench)", null, (bp) => { + try { + var enchants = bp.CollectEnchantments(); + var value = enchants.Sum((e) => e.EnchantmentCost); + return new List<string> { value.ToString() }; + } + catch { + return new List<string> { "0" }; + } + }), + new NamedTypeFilter<BlueprintItemEquipment>("Equip (cost)", null, (bp) => new List<string> {bp.Cost.ToBinString() }), +#endif + //new NamedTypeFilter<SimpleBlueprint>("In Memory", null, bp => bp.CollationName(), () => ResourcesLibrary.s_LoadedBlueprints.Values.Where(bp => bp != null)), + + }; + + public static NamedTypeFilter selectedTypeFilter = null; + + public static IEnumerable<SimpleBlueprint> blueprints = null; + public static bool showCharacterFilterCategories = false; + + public static void RedoLayout() { + if (bps == null) return; + if (selectedUnit?.IsDisposed ?? true) selectedUnit = Shodan.MainCharacter; + foreach (var blueprint in bps) { + var actions = blueprint.GetActions(); + if (actions.Any(a => a.isRepeatable)) hasRepeatableAction = true; + var actionCount = actions.Sum(action => action.canPerform(blueprint, selectedUnit) ? 1 : 0); + maxActions = Math.Max(actionCount, maxActions); + } + } + + public static void OnGUI() { + if (blueprints == null) { + SearchAndPickBrowser.DisplayShowAllGUI = false; + SearchAndPickBrowser.doCollation = true; + if (Event.current.type == EventType.Layout) { + blueprints = BlueprintLoader.Shared.GetBlueprints(); + if (blueprints != null) { + InitType(); + } + } + } + if (Event.current.type == EventType.Layout && needsRedoKeys) { + needsRedoKeys = SearchAndPickBrowser.isCollating || SearchAndPickBrowser._needsRedoCollation; + var count = SearchAndPickBrowser.collatedDefinitions.Keys.Count; + var tmp = new string[(int)(1.1 * count) + 10]; + SearchAndPickBrowser.collatedDefinitions.Keys.CopyTo(tmp, 0); + collationKeys = tmp.Where(s => !string.IsNullOrEmpty(s) && SearchAndPickBrowser.collatedDefinitions[s].Count > 0).ToList(); + if (Settings.sortCollationByEntries) { + collationKeys.Sort(Comparer<string>.Create((x, y) => { + return SearchAndPickBrowser.collatedDefinitions[y].Count.CompareTo(SearchAndPickBrowser.collatedDefinitions[x].Count); + })); + } else { + collationKeys.Sort(Comparer<string>.Create((x, y) => { + if (char.IsNumber(x[x.Length - 1]) && char.IsNumber(y[y.Length - 1])) { + int numberOfDigitsAtEndx = 0; + int numberOfDigitsAtEndy = 0; + for (var i = x.Length - 1; i >= 0; i--) { + if (!char.IsNumber(x[i])) { + break; + } + + numberOfDigitsAtEndx++; + } + for (var i = y.Length - 1; i >= 0; i--) { + if (!char.IsNumber(y[i])) { + break; + } + + numberOfDigitsAtEndy++; + } + var result = x.Take(x.Length - numberOfDigitsAtEndx).ToString().CompareTo(y.Take(y.Length - numberOfDigitsAtEndy).ToString()); + if (result != 0) return result; + var resultx = int.Parse(string.Join("", x.TakeLast(numberOfDigitsAtEndx))); + var resulty = int.Parse(string.Join("", y.TakeLast(numberOfDigitsAtEndy))); + return resultx.CompareTo(resulty); + + } + return x.CompareTo(y); + })); + } + keyToDisplayName.Clear(); + collationKeys.ForEach(s => keyToDisplayName[s] = $"{s} ({SearchAndPickBrowser.collatedDefinitions[s]?.Count})"); + collationKeys.Insert(0, "All"); + keyToDisplayName["All"] = $"All ({bpCount})"; + } + using (HorizontalScope(Width(350))) { + var remainingWidth = ummWidth; + using (VerticalScope(GUI.skin.box)) { + ActionSelectionGrid(ref Settings.selectedBPTypeFilter, + blueprintTypeFilters.Select(tf => tf.name.localize()).ToArray(), + 1, + (selected) => { + InitType(); + }, + buttonStyle, + Width(200)); + } + remainingWidth -= 350; + using (VerticalScope()) { + if (Toggle("Sort By Count".localize(), ref Settings.sortCollationByEntries)) { + needsRedoKeys = true; + } + if (collationKeys?.Count > 0) { + if (PagedVPicker("Categories".localize(), ref SearchAndPickBrowser.collationKey, collationKeys.ToList(), null, s => keyToDisplayName[s], ref collationSearchText, ref collationPickerPageSize, ref collationPickerCurrentPage, Width(300))) { + Mod.Debug($"collationKey: {SearchAndPickBrowser.collationKey}"); + } + remainingWidth -= 450; + } + } + using (VerticalScope(MinWidth(remainingWidth))) { + List<Action> todo = new(); + int count = 0; + if (selectedTypeFilter != null) { + collatorCache[selectedTypeFilter.type] = selectedTypeFilter.collator; + } + using (HorizontalScope()) { + 50.space(); + using (VerticalScope()) { + Toggle("Show Character filter choices".localize(), ref showCharacterFilterCategories); + if (showCharacterFilterCategories) { + CharacterPicker.OnFilterPickerGUI(); + } + CharacterPicker.OnCharacterPickerGUI(); + var tmp = CharacterPicker.GetSelectedCharacter(); + if (tmp != selectedUnit) { + selectedUnit = tmp; + RedoLayout(); + } + } + } + SearchAndPickBrowser.OnGUI(bps, () => bps, bp => bp, bp => GetSearchKey(bp) + (Settings.searchDescriptions ? bp.GetDescription() : ""), bp => new[] { GetSortKey(bp) }, + () => { + using (VerticalScope()) { + using (HorizontalScope()) { + if (hasRepeatableAction) { + Label(RichText.Cyan("Parameter".localize()) + ": ", ExpandWidth(false)); + ActionIntTextField( + ref repeatCount, + "repeatCount", + (limit) => { }, + () => { }, + Width(100)); + Space(40); + repeatCount = Math.Max(1, repeatCount); + repeatCount = Math.Min(100, repeatCount); + } + 25.space(); + if (Toggle("Search Descriptions".localize(), ref Settings.searchDescriptions, AutoWidth())) SearchAndPickBrowser.ReloadData(); + 25.space(); + if (Toggle("Attributes".localize(), ref Settings.showAttributes, AutoWidth())) SearchAndPickBrowser.ReloadData(); + 25.space(); + Toggle("Show GUIDs".localize(), ref Settings.showAssetIDs, AutoWidth()); + 25.space(); + Toggle("Components".localize(), ref Settings.showComponents, AutoWidth()); + 25.space(); + Toggle("Elements".localize(), ref Settings.showElements, AutoWidth()); + 25.space(); + if (Toggle("Show Display & Internal Names".localize(), ref Settings.showDisplayAndInternalNames, AutoWidth())) SearchAndPickBrowser.ReloadData(); + } + } + }, + (bp, maybeBP) => { + GetTitle(bp); + Func<string, string> titleFormatter = (t) => RichText.Bold(RichText.Orange(t)); + if (remainingWidth == 0) remainingWidth = ummWidth; + var description = bp.GetDescription().MarkedSubstring(Settings.searchText); + if (bp is BlueprintItem itemBlueprint && itemBlueprint.FlavorText?.Length > 0) + description = $"{itemBlueprint.FlavorText.StripHTML().Color(RGBA.notable).MarkedSubstring(Settings.searchText)}\n{description}"; + float titleWidth = 0; + var remWidth = remainingWidth; + using (HorizontalScope()) { + var actions = bp.GetActions() + .Where(action => action.canPerform(bp, selectedUnit)); + var titles = actions.Select(a => a.name); + string title = null; + + // FIXME - perf bottleneck + var actionCount = actions != null ? actions.Count() : 0; + // FIXME - horrible perf bottleneck + // I mean it's an improvement? + int removeIndex = -1; + int lockIndex = -1; + int actionIndex = 0; + foreach (var action in titles) { + if (action == "Remove".localize()) { + removeIndex = actionIndex; + } + if (action == "Lock".localize()) { + lockIndex = actionIndex; + } + actionIndex++; + } + // var removeIndex = titles.IndexOf("Remove".localize()); + // var lockIndex = titles.IndexOf("Lock".localize()); + if (removeIndex > -1 || lockIndex > -1) { + title = GetTitle(bp, name => RichText.Bold(RichText.Cyan(name))); + } else { + title = GetTitle(bp, name => RichText.Bold(RichText.Orange(name))); + } + titleWidth = (remainingWidth / (IsWide ? 3 : 4)); + var text = title.MarkedSubstring(Settings.searchText); + if (bp is BlueprintFeatureSelection_Obsolete featureSelection + ) { + if (Browser.DetailToggle(text, bp, bp, (int)titleWidth)) + SearchAndPickBrowser.ReloadData(); + } else + Label(text, Width((int)titleWidth)); + remWidth -= titleWidth; + + if (bp is BlueprintUnlockableFlag flagBP) { + // special case this for now + if (lockIndex >= 0) { + var lockAction = actions.ElementAt(lockIndex); + ActionButton("<", () => { flagBP.Value = flagBP.Value - 1; }, Width(50)); + Space(25); + Label(RichText.Bold(RichText.Orange($"{flagBP.Value}")), MinWidth(50)); + ActionButton(">", () => { flagBP.Value = flagBP.Value + 1; }, Width(50)); + Space(50); + ActionButton(lockAction.name, () => { lockAction.action(bp, selectedUnit, repeatCount); }, Width(120)); + Space(100); +#if DEBUG + Label(flagBP.GetDescription().Green()); +#endif + } else { + // FIXME - perf bottleneck + var unlockIndex = titles.IndexOf("Unlock".localize()); + if (unlockIndex >= 0) { + var unlockAction = actions.ElementAt(unlockIndex); + Space(240); + ActionButton(unlockAction.name, () => { unlockAction.action(bp, selectedUnit, repeatCount); }, Width(120)); + Space(100); + } + } + remWidth -= 300; + } else { + for (var ii = 0; ii < maxActions; ii++) { + if (ii < actionCount) { + var action = actions.ElementAt(ii); + // TODO -don't show increase or decrease actions until we redo actions into a proper value editor that gives us Add/Remove and numeric item with the ability to show values. For now users can edit ranks in the Facts Editor + if (action.name == "<" || action.name == ">") { + Space(174); continue; + } + var actionName = action.name; + float extraSpace = 0; + if (action.isRepeatable) { + actionName += action.isRepeatable ? $" {repeatCount}" : ""; + extraSpace = 20 * (float)Math.Ceiling(Math.Log10((double)repeatCount)); + } + ActionButton(actionName, () => todo.Add(() => action.action(bp, selectedUnit, repeatCount)), Width(160 + extraSpace)); + Space(10); + remWidth -= 174.0f + extraSpace; + + } else { + Space(174); + } + } + } + Space(10); + var type = bp.GetType(); + var typeString = type.Name; + Func<SimpleBlueprint, List<string>> collator; + collatorCache.TryGetValue(type, out collator); + if (collator != null) { + List<string> names = new(); + try { + names = collator(bp); + } catch (Exception ex) { + Mod.Warn($"Error trying to collate for Blueprint: {bp?.name ?? "null"} (Id: {bp?.AssetGuid.ToString() ?? "null"}):\n{ex.ToString()}"); + } + if (names.Count > 0) { + var collatorString = names.First(); + if (bp is BlueprintItem itemBP) { + var rarity = itemBP.Rarity(); + typeString = $"{typeString} - {rarity}".Rarity(rarity); + } + if (!typeString.Contains(collatorString)) { + typeString += RichText.Yellow($" : {collatorString}"); + } + } + } + var attributes = ""; + if (Settings.showAttributes) { + var attr = string.Join(" ", bp.Attributes()); + if (!typeString.Contains(attr)) + attributes = attr; + } + + if (attributes.Length > 1) typeString += $" - {RichText.Orange(attributes)}"; + + if (description != null && description.Length > 0) description = $"{description}"; + else description = ""; + if (bp is BlueprintScriptableObject bpso) { + if (Settings.showComponents && bpso.ComponentsArray?.Length > 0) { + var componentStr = string.Join<object>(", ", bpso.ComponentsArray).Color(RGBA.brown); + if (description.Length == 0) description = componentStr; + else description = description + "\n" + componentStr; + } + if (Settings.showElements && bpso.ElementsArray?.Count > 0) { + string GetCaptionSafe(Element e) { + try { + return e.GetCaption(); + } catch { + Mod.Warn($"Element.GetCaption throws: {e.name}, owner: {e.Owner.name}, id {e.Owner.AssetGuid}"); + return "<exception>".Red(); + } + } + var elementsStr = RichText.Yellow(string.Join<object>("\n", bpso.ElementsArray.Select(e => $"{e.GetType().Name.Cyan()} {GetCaptionSafe(e)}"))); + if (description.Length == 0) description = elementsStr; + else description = description + "\n" + elementsStr; + } + } + using (VerticalScope(Width(remWidth))) { + using (HorizontalScope(Width(remWidth))) { + ReflectionTreeView.DetailToggle("", bp, bp, 0); + Space(-17); + if (Settings.showAssetIDs) { + Label(typeString, rarityButtonStyle); + ClipboardLabel(bp.AssetGuid.ToString(), ExpandWidth(false)); + } else Label(typeString, rarityButtonStyle); + Space(17); + } + if (description.Length > 0) Label(RichText.Green(description), Width(remWidth)); + } + } + count++; + }, + (bp, maybeBP) => { + ReflectionTreeView.OnDetailGUI(bp); + if (bp is BlueprintMechanicEntityFact buf) { + FactsEditor.BlueprintDetailGUI<MechanicEntityFact, BlueprintMechanicEntityFact, SimpleBlueprint, SimpleBlueprint>(buf, null, selectedUnit, SearchAndPickBrowser); + } + }, 50, true, true, 100, 300, "", false, selectedTypeFilter?.collator); + foreach (var action in todo) { + action(); + } + } + Space(25); + } + } + + public static void InitType() { + collationPickerCurrentPage = 1; + selectedTypeFilter = blueprintTypeFilters[Settings.selectedBPTypeFilter]; + if (selectedTypeFilter.blueprintSource != null) bps = selectedTypeFilter.blueprintSource(); + else bps = from bp in BlueprintLoader.BlueprintsOfType(selectedTypeFilter.type) + where selectedTypeFilter.filter(bp) + select bp; + RedoLayout(); + bpCount = bps.Count(); + SearchAndPickBrowser.RedoCollation(); + needsRedoKeys = true; + } + + public static void ResetGUI() { + RedoLayout(); + SearchAndPickBrowser.RedoCollation(); + needsRedoKeys = true; + } + } +} \ No newline at end of file diff --git a/ToyBox/Classes/MainUI/ColonyEditor.cs b/ToyBox/Classes/MainUI/ColonyEditor.cs new file mode 100644 index 000000000..fdabefce1 --- /dev/null +++ b/ToyBox/Classes/MainUI/ColonyEditor.cs @@ -0,0 +1,235 @@ +using DG.Tweening; +using Kingmaker; +using Kingmaker.Cheats; +using Kingmaker.Globalmap.Blueprints.Colonization; +using Kingmaker.Globalmap.Blueprints.SystemMap; +using Kingmaker.Globalmap.Colonization; +using Kingmaker.Globalmap.SystemMap; +using ModKit; +using ModKit.DataViewer; +using ModKit.Utility; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using static ToyBox.BlueprintExtensions; + +namespace ToyBox.classes.MainUI { + public static class ColonyEditor { + public static Settings Settings => Main.Settings; + public static Dictionary<Colony, Browser<BlueprintColonyTrait, BlueprintColonyTrait>> colonyTraitBrowser = new(); + public static IEnumerable<BlueprintColonyTrait> ColonyTraits; + public static Browser<BlueprintResource, BlueprintResource> resourceBrowser = new(Mod.ModKitSettings.searchAsYouType, true); + public static IEnumerable<BlueprintResource> ColonyResources; + public static string selectedStat; + public static int statIndex; + public static List<string> selections = new List<string>() { "Contentment", "Efficiency", "Security" }; + public static int statAdjustment = 1; + public static ColoniesState.ColonyData selectedColony; + public static string searchText = ""; + public static int pageSize = 20; + public static int currentPage = 1; + public static bool editResources = false; + public static int resourceAdjustment = 10; + public static IEnumerable<BlueprintColonyEventResult> EventResults; + public static BlueprintColonyEventResult selectedResult; + public static string resultSearchText; + public static void OnGUI() { + var colonies = Game.Instance.Player.ColoniesState.Colonies; + using (HorizontalScope()) { + using (HorizontalScope(Width(300))) { + PagedVPicker("Colonies".localize(), ref selectedColony, colonies, "None".localize(), t => t.Colony.Blueprint.Name, ref searchText, ref pageSize, ref currentPage, Width(300)); + } + using (VerticalScope()) { + if (selectedColony != null) { + /* + if (eventResults == null) { + eventResults = BlueprintLoader.Shared.GetBlueprints<BlueprintColonyEventResult>(); + } + else { + GridPicker("Result".localize(), ref selectedResult, eventResults, "None".localize(), t => t.Name, ref resultSearchText); + } + */ + var colony = selectedColony.Colony; + Label("Ongoing Events:".localize()); + using (HorizontalScope()) { + 25.space(); + using (VerticalScope()) { + foreach (var evt in colony.StartedEvents) { + Label(evt.Name, Width(500)); + // ActionButton("Finish".localize(), () => colony.FinishEvent(evt, selectedResult)); + } + } + } + Label("Ongoing Projects:".localize()); + using (HorizontalScope()) { + 25.space(); + using (VerticalScope()) { + /* Adding a multiplier seems possible enough. Colony.Tick() is responsible for completing projects with the following code + TimeSpan gameTime = Game.Instance.TimeController.GameTime; + foreach (ColonyProject colonyProject in this.Projects) { + if (!colonyProject.IsFinished && this.SegmentsToBuildProject(colonyProject.Blueprint) <= (gameTime - colonyProject.StartTime).TotalSegments()) { + this.FinishProject(colonyProject); + } + } */ + foreach (var proj in colony.Projects) { + if (!proj.IsFinished) { + using (HorizontalScope()) { + Label(proj.Blueprint.Name, Width(500)); + ActionButton("Finish".localize(), () => colony.FinishProject(proj)); + } + } + } + } + } + using (HorizontalScope()) { + Label("Contentment".localize() + $": {colony.Contentment.Value}"); + Label("Efficiency".localize() + $": {colony.Efficiency.Value}"); + Label("Security".localize() + $": {colony.Security.Value}"); + } + using (HorizontalScope()) { + Label("Change Colony Stat".localize(), Width(300)); + ActionSelectionGrid(ref statIndex, selections.ToArray(), 3, null, Width(500)); + selectedStat = selections[statIndex]; + } + if (selectedStat != null) { + using (HorizontalScope()) { + Label("Adjust ".localize() + selectedStat.localize() + " by the following amount:".localize()); + IntTextField(ref statAdjustment, null, MinWidth(200), AutoWidth()); + statAdjustment = Math.Max(0, statAdjustment); + 10.space(); + ActionButton("Add".localize(), () => CheatsColonization.AddColonyStat(colony.Blueprint, selectedStat.ToLower(), statAdjustment)); + 10.space(); + ActionButton("Remove".localize(), () => CheatsColonization.AddColonyStat(colony.Blueprint, selectedStat.ToLower(), -statAdjustment)); + } + } + if (!colonyTraitBrowser.ContainsKey(colony)) { + colonyTraitBrowser[colony] = new(Mod.ModKitSettings.searchAsYouType); + } + if ((ColonyTraits?.Count() ?? 0) == 0 && colonyTraitBrowser[colony].ShowAll) { + ColonyTraits = BlueprintLoader.Shared.GetBlueprintsOfType<BlueprintColonyTrait>(); + } + var traitBrowser = colonyTraitBrowser[colony]; + 25.space(); + traitBrowser.OnGUI(colony.ColonyTraits.Keys, + () => ColonyTraits, trait => trait, + trait => $"{GetSearchKey(trait)}" + (Settings.searchDescriptions ? $"{trait.Description}" : ""), + trait => new[] { GetSortKey(trait) }, + () => { + using (HorizontalScope()) { + var reloadData = false; + Toggle("Show GUIDs".localize(), ref Main.Settings.showAssetIDs); + 20.space(); + reloadData |= Toggle("Show Internal Names".localize(), ref Settings.showDisplayAndInternalNames); + 20.space(); + reloadData |= Toggle("Search Descriptions".localize(), ref Settings.searchDescriptions); + if (reloadData) { + traitBrowser.ResetSearch(); + } + } + }, + (trait, maybeTrait) => { + bool isAdded = colony.ColonyTraits.ContainsKey(trait); + var remainingWidth = ummWidth; + remainingWidth -= 50; + var titleWidth = (remainingWidth / (IsWide ? 3.5f : 4.0f)) - 100; + remainingWidth -= titleWidth; + + var text = GetTitle(trait).MarkedSubstring(traitBrowser.SearchText); + var titleKey = $"{trait.AssetGuid}"; + if (isAdded) { + text = text.Cyan().Bold(); + } + Label(text, Width((int)titleWidth)); + Space(190); + remainingWidth -= 190; + if (isAdded) { + ActionButton("Remove".localize(), () => colony.RemoveTrait(trait), Width(150)); + } else { + ActionButton("Add".localize(), () => colony.AddTrait(trait), Width(150)); + } + remainingWidth -= 178; + Space(20); remainingWidth -= 20; + ReflectionTreeView.DetailToggle("", trait, trait, 0); + using (VerticalScope(Width(remainingWidth - 100))) { + try { + if (Settings.showAssetIDs) + ClipboardLabel(trait.AssetGuid.ToString(), AutoWidth()); + Label(RichText.Green(trait.Description.StripHTML().MarkedSubstring(traitBrowser.SearchText)), Width(remainingWidth - 100)); + } catch (Exception e) { + Mod.Warn($"Error in blueprint: {trait.AssetGuid}"); + Mod.Warn($" name: {trait.name}"); + Mod.Error(e); + } + } + }, + (trait, maybeTrait) => { + ReflectionTreeView.OnDetailGUI(trait); + }, 0); + } + } + } + DisclosureToggle("Edit Resources".localize(), ref editResources); + if (editResources) { + if ((ColonyResources?.Count() ?? 0) == 0) { + if (Event.current.type == EventType.Layout) { + ColonyResources = BlueprintLoader.Shared.GetBlueprintsOfType<BlueprintResource>(); + resourceBrowser.DisplayShowAllGUI = false; + } + } + if ((ColonyResources?.Count() ?? 0) > 0) { + using (HorizontalScope()) { + Label("Adjust Resource Factor by the following amount:".localize()); + IntTextField(ref resourceAdjustment, null, MinWidth(200), AutoWidth()); + } + resourceBrowser.OnGUI(ColonyResources, + () => ColonyResources, + c => c, + cr => $"{GetSearchKey(cr)} {cr.Description}", + cr => new[] { GetSearchKey(cr) }, + () => { + Label("Resource".localize(), Width((int)((ummWidth / (IsWide ? 3.5f : 4.0f)) - 150))); + Label("Current Amount".localize(), Width(200)); + }, + (cr, maybeCR) => { + var remainingWidth = ummWidth; + remainingWidth -= 50; + var titleWidth = (remainingWidth / (IsWide ? 3.5f : 4.0f)) - 100; + remainingWidth -= titleWidth; + + var text = GetTitle(cr).MarkedSubstring(resourceBrowser.SearchText); + var titleKey = $"{cr.AssetGuid}"; + if (cr) { + text = text.Cyan().Bold(); + } + Label(text, Width((int)titleWidth)); + int amount; + Game.Instance.Player.ColoniesState.ResourcesNotFromColonies.TryGetValue(cr, out amount); + Label(amount.ToString(), Width(100)); + remainingWidth -= 100; + 25.space(); + ActionButton("Add".localize(), () => Game.Instance.ColonizationController.AddResourceNotFromColonyToPool(cr, resourceAdjustment), Width(100)); + 25.space(); + ActionButton("Remove".localize(), () => Game.Instance.ColonizationController.UseResourceFromPool(cr, resourceAdjustment), Width(100)); + remainingWidth -= 250; + ReflectionTreeView.DetailToggle("", cr, cr, 0); + using (VerticalScope(Width(remainingWidth - 100))) { + try { + if (Settings.showAssetIDs) + ClipboardLabel(cr.AssetGuid.ToString(), AutoWidth()); + Label(RichText.Green(cr.Description.StripHTML().MarkedSubstring(resourceBrowser.SearchText)), Width(remainingWidth - 100)); + } catch (Exception e) { + Mod.Warn($"Error in blueprint: {cr.AssetGuid}"); + Mod.Warn($" name: {cr.name}"); + Mod.Error(e); + } + } + }, + (cr, maybeCR) => { + ReflectionTreeView.OnDetailGUI(cr); + }, 0); + } + } + } + } +} diff --git a/ToyBox/Classes/MainUI/Dialog+NPCs.cs b/ToyBox/Classes/MainUI/Dialog+NPCs.cs new file mode 100644 index 000000000..088f9a5a1 --- /dev/null +++ b/ToyBox/Classes/MainUI/Dialog+NPCs.cs @@ -0,0 +1,245 @@ +// Copyright < 2023 > - Narria (github user Cabarius) - License: MIT +using HarmonyLib; +using Kingmaker; +using Kingmaker.AreaLogic.QuestSystem; +using Kingmaker.Designers; +using Kingmaker.Designers.EventConditionActionSystem.Actions; +using Kingmaker.Designers.EventConditionActionSystem.Conditions; +using Kingmaker.ElementsSystem; +using Kingmaker.EntitySystem.Entities; +using ModKit; +using ModKit.DataViewer; +using ModKit.Utility; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using static ModKit.UI; + +namespace ToyBox { + + + public class DialogAndNPCs { + public static Settings Settings => Main.Settings; + public static bool ShowInactive => Settings.toggleIntrestingNPCsShowFalseConditions; + public static Player player => Game.Instance.Player; + private static readonly Browser<BaseUnitEntity, BaseUnitEntity> ConditionsBrowser = new(Mod.ModKitSettings.searchAsYouType); + + public static void ResetGUI() { } + + public static void OnGUI() { + if (!Main.IsInGame) return; + DialogEditor.OnGUI(); + Div(); + 10.space(); + using (HorizontalScope()) { + Toggle("Mark Interesting NPCs on Map".localize(), ref Settings.toggleShowInterestingNPCsOnLocalMap, 375.width()); + HelpLabel("This will change the color of NPC names on the highlight makers and change the color map markers to indicate that they have interesting or conditional interactions".localize()); + } + using (HorizontalScope()) { + DisclosureToggle(RichText.Cyan("Interesting NPCs in the local area".localize()), ref Settings.toogleShowInterestingNPCsOnQuestTab); + 200.space(); + HelpLabel(("Show a list of NPCs that may have quest objectives or other interesting features " + RichText.Yellow("(Warning: Spoilers)")).localize()); + } + if (Settings.toogleShowInterestingNPCsOnQuestTab) { + using (HorizontalScope()) { + 50.space(); + using (VerticalScope(GUI.skin.box)) { + if (Shodan.AllBaseUnits is { } unitsPool) { + var units = Settings.toggleInterestingNPCsShowHidden ? unitsPool.All : unitsPool.ToList(); + ConditionsBrowser.OnGUI( + (IEnumerable<BaseUnitEntity>)units.Where(u => u.InterestingnessCoefficent() >= 1), + () => units, + i => i, + u => u.CharacterName, + u => (new[] { u.CharacterName }), + () => { + Toggle("Show Inactive Conditions", ref Settings.toggleIntrestingNPCsShowFalseConditions); + if (ConditionsBrowser.ShowAll) { + 25.space(); + if (Toggle("Show other versions of NPCs", ref Settings.toggleInterestingNPCsShowHidden)) + ConditionsBrowser.ReloadData(); + } +#if DEBUG + 25.space(); + ActionButton("Reveal All On Map", BlueprintExtensions.RevealInterestingNPCs); +#endif + }, + (u, _) => { + var name = u.CharacterName; + var coefficient = u.InterestingnessCoefficent(); + if (coefficient > 0) + name = RichText.Orange(name); + else + name = RichText.Grey(name); + Label(name, 600.width()); + 175.space(); + Label(RichText.Grey($"Interestingness Coefficient: ") + RichText.Cyan(coefficient.ToString())); + 50.space(); + ReflectionTreeView.DetailToggle("Unit", u.Parts.m_Parts, u.Parts.m_Parts, 100); + 25.space(); + var dialogs = u.GetDialog(); + if (dialogs.Any()) + ReflectionTreeView.DetailToggle("Dialog", u, dialogs.Count == 1 ? dialogs.First() : dialogs, 100); + }, + (u, _) => { + ReflectionTreeView.OnDetailGUI(u.Parts.m_Parts); + ReflectionTreeView.OnDetailGUI(u); + var entries = u.GetUnitInteractionConditions(); + var checkerEntries = entries.Where(e => e.HasConditions && (ShowInactive || e.IsActive())); + var conditions = + from entry in checkerEntries + from condition in entry.checker.Conditions + group (condition, entry) by condition.GetCaption() + into g + select g.Select(p => (p.condition, new object[] { p.entry.source } as IEnumerable<object>)) + .Aggregate(((Condition condition, IEnumerable<object>) p, (Condition condition, IEnumerable<object>) q) + => (p.condition, (IEnumerable<object>)p.Item2.Concat(q.Item2)) + ); + var elementEntries = entries.Where(e => e.HasElements && (ShowInactive || e.IsActive())); + if (conditions.Any()) { + using (HorizontalScope()) { + 115.space(); + Label(RichText.Yellow("Conditions")); + } + } + foreach (var entry in conditions) { + OnGUI(entry.condition, + string.Join(", ", (IEnumerable<string>)entry.Item2.Select(source => source.ToString())), + 150 + ); + } + if (elementEntries.Any()) { + using (HorizontalScope()) { + 115.space(); + Label(RichText.Yellow("Elements")); + } + } + foreach (var entry in elementEntries) { + foreach (var element in entry.elements.OrderBy(e => e.GetType().Name)) { + OnGUI(element, entry.source); + } + } + }, + 50, + false, + true, + 100, + 300, + "", + true); + } + } + } + } + + } + + public static void OnGUI(Element element, object source, int indent = 150, bool forceShow = false) { + if (!element.IsActive() + && source is not ActionsHolder // kludge again for Actions holder for Lathimas + && !Settings.toggleIntrestingNPCsShowFalseConditions + && !forceShow + ) return; + using (HorizontalScope()) { + Space(indent); + switch (element) { + case ObjectiveStatus objectiveStatus: + OnGUI(objectiveStatus, source); + break; + case QuestStatus questStatus: + OnGUI(questStatus, source); + break; + case EtudeStatus etudeStatus: + OnGUI(etudeStatus, source); + break; + case Conditional conditional: + OnGUI(conditional, source); + break; + case Condition condition: + OnGUI(condition, source); + break; + default: + OnOtherElementGUI(element, source); + break; + } + } + } + public static void OnGUI(ConditionsChecker checker, object source, int indent = 150, bool forceShow = false) { + foreach (var condition in checker.Conditions.OrderBy(c => c.GetType().Name)) { + OnGUI(condition, source, indent, forceShow); + } + } + public static void OnGUI(Conditional conditional, object source) { + if (conditional.ConditionsChecker.Conditions.Any()) { + Label(RichText.Cyan("Conditional:".localize()), 150.width()); + //Label(string.Join(", ", conditional.ConditionsChecker.Conditions.Select(c => c.GetCaption()))); + Label(conditional.Comment, 375.width()); + using (VerticalScope()) { + OnGUI(conditional.ConditionsChecker, source, 0, true); + } + } + } + public static void OnGUI(QuestStatus questStatus, object source) { + Label(RichText.Cyan("Quest Status: ".localize()), 150.width()); + var quest = questStatus.Quest; + var state = GameHelper.Quests.GetQuestState(quest); + var title = $"{RichText.Bold(RichText.Orange(quest.Title.StringValue()))}"; + Label(title, 500.width()); + 22.space(); + using (VerticalScope()) { + HelpLabel(quest.Description); + Label(RichText.Cyan($"status: ".localize()) + state.ToString()); + Label(RichText.Cyan("condition: ".localize()) + questStatus.CaptionString()); + Label(RichText.Cyan("source: ".localize()) + RichText.Yellow(source.ToString())); + } + } + public static void OnGUI(ObjectiveStatus objectiveStatus, object source) { + Label(RichText.Cyan("Objective Status: ".localize()), 150.width()); + + var objectiveBP = objectiveStatus.QuestObjective; + var objective = Game.Instance.Player.QuestBook.GetObjective(objectiveBP); + var quest = objectiveBP.Quest; + var state = objective?.State ?? QuestObjectiveState.None; + var title = $"{RichText.Bold(RichText.Orange(quest.Title.StringValue()))} : {objective.titleColored(objectiveBP)}"; + Label(title, 500.width()); + 22.space(); + using (VerticalScope()) { + HelpLabel(objectiveBP.Description); + Label(RichText.Cyan($"status: ".localize()) + state.ToString().titleColored(state)); + Label(RichText.Cyan("condition: ".localize()) + objectiveStatus.CaptionString()); + Label(RichText.Cyan("source: ".localize()) + RichText.Yellow(source.ToString())); + } + } + public static void OnGUI(EtudeStatus etudeStatus, object source) { + Label(RichText.Cyan("Etude Status: ".localize()), 150.width()); + var etudeBP = etudeStatus.Etude; + Label(RichText.Orange(etudeBP.name), 500.width()); + var etudeState = Game.Instance.Player.EtudesSystem.GetSavedState(etudeBP); + var debugInfo = Game.Instance.Player.EtudesSystem.GetDebugInfo(etudeBP); + 22.space(); + using (VerticalScope()) { + HelpLabel(debugInfo); + Label(RichText.Cyan($"status: ".localize()) + etudeState.ToString()); + Label(RichText.Cyan("condition: ".localize()) + etudeStatus.CaptionString()); + Label(RichText.Cyan("source: ".localize()) + RichText.Yellow(source.ToString())); + } + } + public static void OnGUI(Condition condition, object source) { + Label(RichText.Cyan($"{condition.GetType().Name}:"), 150.width()); + Label(RichText.Yellow(source.ToString()), 500.width()); + 22.space(); + using (VerticalScope()) { + Label(RichText.Cyan("condition: ".localize()) + condition.CaptionString()); + } + } + public static void OnOtherElementGUI(Element element, object source) { + Label(RichText.Cyan($"{element.GetType().Name}:"), 150.width()); + Label(RichText.Yellow(source.ToString()), 500.width()); + 22.space(); + using (VerticalScope()) { + Label(RichText.Cyan("caption: ".localize()) + element.CaptionString()); + } + } + } +} diff --git a/ToyBox/Classes/MainUI/DialogEditor.cs b/ToyBox/Classes/MainUI/DialogEditor.cs new file mode 100644 index 000000000..d64150bbd --- /dev/null +++ b/ToyBox/Classes/MainUI/DialogEditor.cs @@ -0,0 +1,204 @@ +// Copyright < 2023 > - Narria (github user Cabarius) - License: MIT +using HarmonyLib; +using Kingmaker; +using Kingmaker.AreaLogic.QuestSystem; +using Kingmaker.Assets.Designers.EventConditionActionSystem.Conditions; +using Kingmaker.Blueprints; +using Kingmaker.Blueprints.Quests; +using Kingmaker.Controllers.Dialog; +using Kingmaker.Designers; +using Kingmaker.Designers.EventConditionActionSystem.Actions; +using Kingmaker.Designers.EventConditionActionSystem.Conditions; +using Kingmaker.DialogSystem; +using Kingmaker.DialogSystem.Blueprints; +using Kingmaker.ElementsSystem; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.UI; +using Kingmaker.UnitLogic.Parts; +using ModKit; +using ModKit.DataViewer; +using ModKit.Utility; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.AccessControl; +using UnityEngine; +using static Kingmaker.UnitLogic.Interaction.SpawnerInteractionPart; +using static ModKit.UI; +using static ToyBox.BlueprintExtensions; + +namespace ToyBox { + public static class DialogEditor { + public static Settings Settings => Main.Settings; + public static Player player => Game.Instance.Player; + private const int Indent = 75; + private static HashSet<BlueprintScriptableObject> Visited = new(); + + public static void ResetGUI() { } + + public static void OnGUI() { + if (!Main.IsInGame) return; + if (Game.Instance?.DialogController is { } dialogController) { + Visited.Clear(); + dialogController.OnGUI(); + ReflectionTreeView.DetailToggle("Inspect Dialog Controller".localize(), dialogController); + ReflectionTreeView.OnDetailGUI(dialogController); + 25.space(); + } + } + + public static void OnGUI(this DialogController dialogController) { + if (dialogController.CurrentCue == null) { + Label(RichText.Cyan("No Active Dialog".localize())); + } + dialogController.CurrentCue?.OnGUI("Current".localize()); + dialogController.Answers?.OnGUI("Answer".localize()); + //if (dialogController.m_ContinueCue is BlueprintCue cue) cue.OnGUI("Continue:"); + dialogController?.Dialog.OnGUI(); + } + private static void OnGUI(this BlueprintDialog dialog) { + + } + private static void OnGUI(this Dialog dialog) { + + } + private static void OnGUI(this BlueprintCue cue, string? title = null) { + bool visited = Visited.Contains(cue); + using (HorizontalScope()) { + OnTitleGUI(title); + using (VerticalScope()) { + var displayText = cue.DisplayText; + if (visited && displayText.Length > 50) + displayText = displayText.StripHTML().Substring(0, 50) + "..."; + Label($"{RichText.Yellow(cue.GetDisplayName())} {RichText.Orange(displayText)}"); + var resultsText = cue.ResultsText().StripHTML().Trim(); + if (!resultsText.IsNullOrEmpty()) { + using (HorizontalScope()) { + Label("", Indent.width()); + Label(RichText.Yellow(resultsText)); + } + } + if (cue.Conditions?.Conditions?.Count() > 0) { + using (HorizontalScope()) { + Label("Cond".localize().Color(RGBA.teal), Indent.width()); + Label(PreviewUtilities.FormatConditions(cue.Conditions).Color(RGBA.teal)); + } + } + if (visited) { + Label(RichText.Yellow($"[Repeat]".localize())); + return; + } + Visited.Add(cue); + var index = 1; + foreach (var answerBaseRef in cue.Answers) { + var answerBase = answerBaseRef.Get(); + switch (answerBase) { + case BlueprintAnswer answer: + answer.OnGUI("Answer".localize() + $" {index}"); + index++; + break; + case BlueprintAnswersList answersList: { + var subIndex = 1; + foreach (var subAnswerBaseRef in answersList.Answers) { + var subAnswerBase = subAnswerBaseRef.Get(); + if (subAnswerBase is BlueprintAnswer subAnswer) { + subAnswer.OnGUI($"{index}-{subIndex}"); + subIndex++; + } + } + index++; + break; + } + } + } + if (cue.Continue is { } cueSelection) { + cueSelection.OnGUI("Selection".localize()); + } + } + } + } + private static void OnGUI(this CueSelection cueSelection, string? title = null) { + var cues = cueSelection.Cues; + if (cues.Count(cbr => cbr.Get() is BlueprintCue) <= 0) return; + using (HorizontalScope()) { + OnTitleGUI((title)); + using (VerticalScope()) { + foreach (var cueBaseRef in cues) { + var index = 1; + if (cueBaseRef.Get() is BlueprintCue cue) { + cue.OnGUI("Cue".localize() + $" {index}"); + index++; + } + } + } + } + } + private static void OnGUI(this BlueprintAnswer answer, string? title = null) { + using (HorizontalScope()) { + OnTitleGUI(title); + using (VerticalScope()) { + var text = $"{RichText.Yellow(answer.GetDisplayName())} {answer.DisplayText}"; + if (answer.NextCue is CueSelection nextCueSelection && nextCueSelection.Cues.Any()) { + Browser.DetailToggle(text, nextCueSelection, nextCueSelection); + Browser.OnDetailGUI(nextCueSelection, (_) => nextCueSelection.OnGUI("Next".localize())); + } else + Label(text); + var checkStrings = PreviewUtilities.FormatConditionsAsList(answer); + foreach (var checkString in checkStrings) { + Label(checkString.Color(RGBA.teal)); + } +#if false + if (answer.HasShowCheck) { + using (HorizontalScope()) { + Label("Check".color(RGBA.teal), Indent.width()); + Label($"{answer.ShowCheck.Type} DC: {answer.ShowCheck.DC}".color(RGBA.teal)); + } + } + if (answer.ShowConditions.Conditions.Length > 0) { + using (HorizontalScope()) { + Label("Show".color(RGBA.teal), Indent.width()); + Label(PreviewUtilities.FormatConditions(answer.ShowConditions).color(RGBA.teal)); + } + } + if (answer.SelectConditions is ConditionsChecker selectChecker && selectChecker.Conditions.Count() > 0) { + Label("Select".color(RGBA.teal), Indent.width()); + Label(PreviewUtilities.FormatConditions(selectChecker).color(RGBA.teal)); + } +#endif + var resultsText = answer.ResultsText().StripHTML(); + if (!resultsText.IsNullOrEmpty()) { + using (HorizontalScope()) { + Label("", Indent.width()); + Label(RichText.Yellow(resultsText)); + } + } + } + } + } + private static void OnGUI(this BlueprintAnswersList answersList) { + if (answersList?.Answers?.Count <= 0) return; + if (answersList.Answers.Select(ar => ar.Get() as BlueprintAnswer) is { } answers) { + answers.OnGUI(); + } + } + private static void OnGUI(this IEnumerable<BlueprintAnswer> answersList, string? title = null) { + if (answersList?.Count() <= 0) return; + using (HorizontalScope()) { + OnTitleGUI(title); + using (VerticalScope()) { + var index = 1; + foreach (var answer in answersList) { + answer.OnGUI($"{index}"); + index++; + } + } + } + } + private static void OnTitleGUI(string? title) { + if (title != null) { + Label(RichText.Cyan(title), Indent.width()); + } else + Indent.space(); + } + } +} diff --git a/ToyBox/Classes/MainUI/DiceRollsGUI.cs b/ToyBox/Classes/MainUI/DiceRollsGUI.cs new file mode 100644 index 000000000..7ecb8d98e --- /dev/null +++ b/ToyBox/Classes/MainUI/DiceRollsGUI.cs @@ -0,0 +1,32 @@ +// Copyright < 2021 > Narria (github user Cabarius) - License: MIT + +using ModKit; +using System; + +namespace ToyBox { + public static class DiceRollsGUI { + public static Settings Settings => Main.Settings; + public static void OnGUI() { + HStack("Dice Rolls".localize(), 1, + () => EnumGrid("All Attacks Hit".localize(), ref Settings.allAttacksHit, AutoWidth()), + () => EnumGrid("All Hits Critical".localize(), ref Settings.allHitsCritical, AutoWidth()), + () => EnumGrid("Roll With Advantage (take lower roll)".localize(), ref Settings.rollWithAdvantage, AutoWidth()), + () => EnumGrid("Roll With Disadvantage (take higher roll)".localize(), ref Settings.rollWithDisadvantage, AutoWidth()), + () => EnumGrid("Always Roll 100".localize(), ref Settings.alwaysRoll100, AutoWidth()), + () => EnumGrid("Always Roll 50".localize(), ref Settings.alwaysRoll50, AutoWidth()), + () => EnumGrid("Always Roll 1".localize(), ref Settings.alwaysRoll1, AutoWidth()), + () => EnumGrid("Never Roll 100".localize(), ref Settings.neverRoll100, AutoWidth()), + () => EnumGrid("Never Roll 1".localize(), ref Settings.neverRoll1, AutoWidth()), + () => EnumGrid("Initiative: Always Roll 10".localize(), ref Settings.roll10Initiative, AutoWidth()), + () => EnumGrid("Initiative: Always Roll 5".localize(), ref Settings.roll5Initiative, AutoWidth()), + () => EnumGrid("Initiative: Always Roll 1".localize(), ref Settings.roll1Initiative, AutoWidth()), + () => EnumGrid("Non Combat: Take 1".localize(), ref Settings.alwaysRoll1OutOfCombat, AutoWidth()), + () => { 330.space(); Label("The following skill check adjustments apply only out of combat".localize().Green()); }, + () => EnumGrid("Skill Checks: Take 50".localize(), ref Settings.skillsTake50, AutoWidth()), + () => EnumGrid("Skill Checks: Take 25".localize(), ref Settings.skillsTake25, AutoWidth()), + () => EnumGrid("Skill Checks: Take 1".localize(), ref Settings.skillsTake1, AutoWidth()), + () => { } + ); + } + } +} \ No newline at end of file diff --git a/ToyBox/Classes/MainUI/EnchantmentEditor.cs b/ToyBox/Classes/MainUI/EnchantmentEditor.cs new file mode 100644 index 000000000..43a328553 --- /dev/null +++ b/ToyBox/Classes/MainUI/EnchantmentEditor.cs @@ -0,0 +1,499 @@ +using DG.Tweening; +using Kingmaker; +using Kingmaker.Blueprints; +using Kingmaker.Blueprints.Classes; +using Kingmaker.Blueprints.Items.Armors; +using Kingmaker.Blueprints.Items.Ecnchantments; +using Kingmaker.Blueprints.Items.Shields; +using Kingmaker.Blueprints.Items.Weapons; +using Kingmaker.Designers.Mechanics.Facts; +using Kingmaker.EntitySystem.Persistence.JsonUtility; +using Kingmaker.Items; +using Kingmaker.UI.Common; +using Kingmaker.UnitLogic.Mechanics; +using Kingmaker.Utility; +using ModKit; +using ModKit.DataViewer; +using ModKit.Utility; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using UnityEngine; +using static ModKit.UI; +namespace ToyBox.classes.MainUI { + public static class EnchantmentEditor { + public static Settings Settings => Main.Settings; + + #region GUI + public static BlueprintItemWeapon basicSpikeShield = ResourcesLibrary.TryGetBlueprint<BlueprintItemWeapon>("62c90581f9892e9468f0d8229c7321c4"); //StandardWeaponLightShield + public static Browser<BlueprintItemEnchantment, BlueprintItemEnchantment> EnchantmentBrowser = new(Mod.ModKitSettings.searchAsYouType); + + public static int selectedItemType; + public static int selectedItemIndex; + public static int selectedEnchantIndex; + public static int selectedPageItemIndex; + public static string itemSearchText = ""; + public static (string, string) renameState = (null, null); + public static ItemEntity selectedItem = null; + public static ItemEntity editedItem = null; + public static string[] ItemTypeNames = null; + private static List<ItemEntity> inventory; + private static List<BlueprintItemEnchantment> enchantments; + private static string collationSearchText; + private static int searchLimit = 30; + private static int _currentPage = 1; + private static int _pageCount => (int)Math.Ceiling((double)inventory?.Count / searchLimit); + + public static void ResetGUI() { } + + public static void OnShowGUI() => UpdateItems(); + public static void OnGUI() { + if (!Main.IsInGame) return; + if (ItemTypeNames == null) { + ItemTypeNames = Enum.GetNames(typeof(ItemsItemType)).ToList().Prepend("All").Select(item => item.localize()).ToArray(); + EnchantmentBrowser.DisplayShowAllGUI = false; + EnchantmentBrowser.doCollation = true; + EnchantmentBrowser.SortDirection = Browser.sortDirection.Descending; + } + Label((RichText.Orange("Sandal says '") + RichText.Bold(RichText.Cyan("Enchantment'"))).localize()); + // load blueprints + if (enchantments == null) { + var blueprints = BlueprintLoader.Shared.GetBlueprintsOfType<BlueprintItemEnchantment>(); + if ((blueprints?.Count() ?? 0) == 0) return; + + enchantments = blueprints.ToList(); + enchantments.Sort((l, r) => { + return r.Rarity().CompareTo(l.Rarity()); + //if (l.Description != null && r.Description != null || l.Description == null && r.Description == null) { + // return r.Rarity().CompareTo(r.Rarity()); + //} else if (l.Description != null) return 1; + //return -1; + }); + enchantments.TrimExcess(); + UpdateItems(); + EnchantmentBrowser.RedoCollation(); + } + + // Stackable browser + using (HorizontalScope(Width(350))) { + var remainingWidth = ummWidth; + + // First column - Type Selection Grid + using (VerticalScope()) { + ActionSelectionGrid( + ref selectedItemType, + ItemTypeNames, + 1, + index => { + selectedItemIndex = index; + UpdateItems(); + }, + buttonStyle, + Width(175)); + Space(25); + if (VPicker(RichText.Cyan("Ench. Types".localize()), ref EnchantmentBrowser.collationKey, (List<string>)(EnchantmentBrowser.collatedDefinitions?.Keys.ToList() ?? new()), "All".localize(), (s) => s.localize(), ref collationSearchText, Width(175))) { + Mod.Debug($"collationKey: {EnchantmentBrowser.collationKey}"); + } + } + var itemTypeName = ItemTypeNames[selectedItemType]; + remainingWidth -= 250; + Space(10); + // Second column - Item Selection Grid + using (VerticalScope(GUI.skin.box)) { + ActionTextField( + ref itemSearchText, + "itemSearchText", + (text) => UpdateItems(), + () => UpdateItems(), + Width(375)); + using (HorizontalScope()) { + 10.space(); + Label("Limit".localize(), ExpandWidth(false)); + ActionIntTextField(ref searchLimit, "Search Limit".localize(), (i) => searchLimit = i < 1 ? 1 : i, null, 100.width()); + if (searchLimit > 1000) { searchLimit = 1000; } + if (inventory.Count > searchLimit) { + string pageLabel = RichText.Orange("Page: ".localize()) + RichText.Cyan(_currentPage.ToString()) + " / " + RichText.Cyan(_pageCount.ToString()); + 25.space(); + Label(pageLabel, ExpandWidth(false)); + ActionButton("-", () => { + if (_currentPage >= 1) { + if (_currentPage == 1) { + _currentPage = _pageCount; + } else { + _currentPage -= 1; + } + } + var offset = Math.Min(inventory.Count, (_currentPage - 1) * searchLimit); + selectedItemIndex = offset + selectedPageItemIndex; + selectedItem = inventory[selectedItemIndex]; + }, AutoWidth()); + ActionButton("+", () => { + if (_currentPage > _pageCount) _currentPage = 1; + if (_currentPage == _pageCount) { + _currentPage = 1; + } else { + _currentPage += 1; + } + var offset = Math.Min(inventory.Count, (_currentPage - 1) * searchLimit); + selectedItemIndex = offset + selectedPageItemIndex; + selectedItem = inventory[selectedItemIndex]; + }, AutoWidth()); + } + } + using (HorizontalScope()) { + 10.space(); + Toggle("Ratings".localize(), ref Settings.showRatingForEnchantmentInventoryItems, 147.width()); + 10.space(); + ActionButton("Export".localize(), () => inventory.Export(itemTypeName + ".json"), 100.width()); + ActionButton("Import".localize(), () => { + Game.Instance.Player.Inventory.Import(itemTypeName + ".json"); + UpdateItems(); + }, 100.width()); + } + if (inventory.Count > 0) { + var offset = Math.Min(inventory.Count, (_currentPage - 1) * searchLimit); + var limit = Math.Min(searchLimit, Math.Max(inventory.Count, inventory.Count - searchLimit)); + ActionSelectionGrid( + ref selectedPageItemIndex, + inventory.Select(item => item.NameAndOwner(true)).Skip(offset).Take(limit).ToArray(), + 1, + index => { + selectedItemIndex = offset + selectedPageItemIndex; + selectedItem = inventory[selectedItemIndex]; + }, + rarityButtonStyle, + Width(375)); + } else { + Label(RichText.Grey("No Items".localize()), Width(375)); + } + } + remainingWidth -= 400; + Space(10); + // Section Column - Main Area + using (VerticalScope(MinWidth(remainingWidth))) { + Label(RichText.Green("Import/Export allows you to save and add a list of items to a file based on the type (e.g. Weapon.json). These files live in a new ToyBox folder that in the same folder that contains your saved games ".localize())); + if (selectedItem != null) { + var item = selectedItem; + //UI.Label("Target".cyan()); + Div(); + using (HorizontalScope(GUI.skin.box, MinHeight(125))) { + var rarity = item.Rarity(); + //Main.Log($"item.Name - {item.Name.ToString().Rarity(rarity)} rating: {item.Blueprint.Rating(item)}"); + Space(25); + using (VerticalScope(Width(320))) { + Label(RichText.Bold(item.NameAndOwner(false)), Width(320)); + 25.space(); + var bp = item.Blueprint; + Label("rating: ".localize() + $"{RichText.Bold(RichText.Orange(item.Rating().ToString()))} (" + "bp".localize() + RichText.Cyan($":{RichText.Bold(RichText.Orange(item.Blueprint.Rating().ToString()))})")); + using (HorizontalScope()) { + var modifers = bp.Attributes(); + Label(RichText.Cyan(string.Join(" ", modifers)), AutoWidth()); + //if (bp is BlueprintItemWeapon bpW) { + // if (bpW.IsMagic) UI.Label("magic ".cyan(), UI.AutoWidth()); + // if (bpW.IsNotable) UI.Label("notable ".Rarity(RarityType.Notable), UI.AutoWidth()); + // if (bpW.IsNatural) UI.Label("natural ".grey(), UI.AutoWidth()); + // if (bpW.IsMelee) UI.Label("melee ".grey(), UI.AutoWidth()); + // if (bpW.IsRanged) UI.Label("ranged ".grey(), UI.AutoWidth()); + //} + //if (bp is BlueprintItemArmor bpA) { + // if (bpA.IsMagic) UI.Label("magic ".cyan(), UI.AutoWidth()); + // if (bpA.IsNotable) UI.Label("notable ".Rarity(RarityType.Notable), UI.AutoWidth()); + // if (bpA.IsShield) UI.Label("shield ".grey(), UI.AutoWidth()); + //} + } + } + Space(25); + if (item is ItemEntityShield shield) { + using (VerticalScope()) { + using (HorizontalScope()) { + Label(RichText.Orange("Shield"), Width(100)); + TargetItemGUI(shield); + } + Div(); + if (shield.WeaponComponent != null) { + using (HorizontalScope()) { + Label(RichText.Orange("Spikes"), Width(100)); + TargetItemGUI(shield.WeaponComponent); + } + ActionButton("Remove ", () => shield.WeaponComponent = null, AutoWidth()); + } else { + var compTitle = shield.Blueprint.WeaponComponent?.name; + compTitle = compTitle != null ? " from " + RichText.Yellow(compTitle) : ""; + ActionButton("Add " + RichText.Orange("Spikes") + compTitle, () => shield.WeaponComponent = new ItemEntityWeapon(shield.Blueprint.WeaponComponent ?? basicSpikeShield, shield), AutoWidth()); + } + } + } else if (item is ItemEntityWeapon weapon && weapon.Second != null) { + using (VerticalScope()) { + using (HorizontalScope()) { + Label(RichText.Orange("Main"), Width(100)); + TargetItemGUI(weapon); + } + Div(); + using (HorizontalScope()) { + Label(RichText.Orange("2nd"), Width(100)); + TargetItemGUI(weapon.Second); + } + } + } else { + TargetItemGUI(item); + } + } + Div(); + } + + Space(10); + Div(); + // List of enchantments with buttons to add to item + EnchantmentsListGUI(); + } + } + } + + public static void TargetItemGUI(ItemEntity item) { + var enchantements = GetEnchantments(item); + if (enchantements.Count > 0) { + using (VerticalScope()) { + var index = 0; + foreach (var entry in enchantements) { + if (index++ > 0) Div(); + var enchant = entry.Key; + var enchantBP = enchant.Blueprint; + var name = enchantBP.name; + if (name != null && name.Length > 0) { + name = name.DarkModeRarity(enchantBP.Rarity()); + using (HorizontalScope()) { + Label(name, Width(100)); + Space(25); + Label(RichText.Cyan($"{RichText.Bold(RichText.Orange(enchantBP.Rating().ToString()))}"), 30.width()); + Space(25); + Label(entry.Value ? RichText.Yellow("Custom".localize()) : RichText.Orange("Perm".localize()), Width(100)); + Space(25); + ActionButton("Remove".localize(), () => RemoveEnchantment(item, enchant), AutoWidth()); + var description = enchantBP.Description; + if (description != null) { + Space(25); + Label(RichText.Green(description.StripHTML())); + } + } + } + } + } + } else { + Label(RichText.Orange("No Enchantments")); + } + } + public static void EnchantmentsListGUI() { + Div(5); + using (HorizontalScope()) { + Space(-50); + using (VerticalScope()) { + EnchantmentBrowser.OnGUI(enchantments, null, e => e, + (BlueprintItemEnchantment blueprint) => $"{BlueprintExtensions.GetTitle(blueprint)} {blueprint.AssetGuid} {blueprint.GetType()}" + (Settings.searchDescriptions ? blueprint.Description.ToString() : ""), + (BlueprintItemEnchantment blueprint) => new IComparable[] { blueprint.Rating(), BlueprintExtensions.GetTitle(blueprint) }, + () => { + // Search Field and modifiers + using (VerticalScope()) { + using (HorizontalScope()) { + var reloadData = false; + Toggle("Show GUIDs".localize(), ref Settings.showAssetIDs); + 20.space(); + reloadData |= Toggle("Show Internal Names".localize(), ref Settings.showDisplayAndInternalNames); + 20.space(); + reloadData |= Toggle("Search Descriptions".localize(), ref Settings.searchDescriptions); + if (reloadData) { + EnchantmentBrowser.ResetSearch(); + } + } + using (HorizontalScope()) { + Space(5); + Label(RichText.Blue("Enchantment".localize()), Width(320)); + Space(275); + Label(RichText.Blue("Rating".localize()), Width(75)); + Space(10); + Label(RichText.Blue("Ench. Type".localize()), Width(140)); + Space(130); + Label(RichText.Blue("Description".localize())); + + } + } + }, + (enchant, maybeEnchantment) => { + var title = BlueprintExtensions.GetTitle(enchant).DarkModeRarity(enchant.Rarity()); + using (HorizontalScope()) { + Space(5); + Label(title, Width(320)); + if (selectedItem is ItemEntityShield shield) { + using (VerticalScope(Width(260))) { + using (HorizontalScope()) { + ActionButton("+ " + RichText.Orange("Armor".localize()), () => AddClicked(enchant), Width(130)); + if (shield.Enchantments.Any(e => e.Blueprint == enchant)) + ActionButton("- " + RichText.Orange("Armor".localize()), () => RemoveClicked(enchant), Width(130)); + else + Space(130); + } + if (shield.WeaponComponent != null) { + using (HorizontalScope()) { + ActionButton("+ " + RichText.Orange("Spikes".localize()), () => AddClicked(enchant, true), Width(130)); + if (shield.WeaponComponent.Enchantments.Any(e => e.Blueprint == enchant)) + ActionButton("- " + RichText.Orange("Spikes".localize()), () => RemoveClicked(enchant, true), Width(130)); + else + Space(130); + } + } + } + } else if (selectedItem is ItemEntityWeapon weapon && weapon?.Second != null) { + using (VerticalScope()) { + using (HorizontalScope()) { + ActionButton("+ " + RichText.Orange("Main".localize()), () => AddClicked(enchant), Width(130)); + if (weapon.Enchantments.Any(e => e.Blueprint == enchant)) + ActionButton("- " + RichText.Orange("Main".localize()), () => RemoveClicked(enchant), Width(130)); + else + Space(130); + } + using (HorizontalScope()) { + ActionButton("+ " + RichText.Orange("Offhand".localize()), () => AddClicked(enchant, true), Width(130)); + if (weapon.Second.Enchantments.Any(e => e.Blueprint == enchant)) + ActionButton("- " + RichText.Orange("Offhand".localize()), () => RemoveClicked(enchant, true), Width(130)); + else + Space(130); + } + } + } else { + ActionButton("Add".localize(), () => AddClicked(enchant), Width(130)); + if (selectedItem?.Enchantments.Any(e => e.Blueprint == enchant) ?? false) + ActionButton("Remove".localize(), () => RemoveClicked(enchant), Width(130)); + else + Space(133); + } + Space(15); + Label(RichText.Yellow($"{enchant.Rating()}"), 75.width()); // ⊙ + Space(10); + var description = RichText.Green(enchant.Description.StripHTML()); + if (enchant.Comment?.Length > 0) description = RichText.Orange(enchant.Comment) + " " + description; + if (enchant.Prefix?.Length > 0) description = RichText.Yellow(enchant.Prefix) + " " + description; + if (enchant.Suffix?.Length > 0) description = RichText.Yellow(enchant.Suffix) + " " + description; + Label(RichText.Cyan(enchant.CollationNames().First().Replace("Enchantment", "")), Width(150)); + using (VerticalScope()) { + if (Settings.showAssetIDs) ClipboardLabel(enchant.AssetGuid.ToString(), AutoWidth()); + using (HorizontalScope()) { + ReflectionTreeView.DetailToggle("", enchant, enchant, 0); + Label(description); + } + } + } + }, + (enchant, maybeEnchantment) => { + ReflectionTreeView.OnDetailGUI(enchant); + }, 50, true, true, 100, 300, "", false, bp => bp.CollationNames().Select(n => n.Replace("Enchantment", ""))); + } + } + } + public static void UpdateItems() { + var selectedItemTypeEnumIndex = selectedItemType - 1; + var searchText = itemSearchText.ToLower(); + if (Game.Instance?.Player?.Inventory == null) return; + inventory = (from item in Game.Instance.Player.Inventory + where BlueprintExtensions.GetSearchKey(item.Blueprint, true).ToLower().Contains(searchText) + && (selectedItemType == 0 + || (int)item.Blueprint.ItemType == selectedItemTypeEnumIndex + ) + orderby item.Rating() descending, item.Name + select item + ).ToList(); + if (editedItem != null) { + selectedItemIndex = inventory.IndexOf(editedItem); + editedItem = null; + } + if (selectedItemIndex >= inventory.Count || selectedItemIndex < 0) { + selectedItemIndex = 0; + } + _currentPage = 1; + selectedItem = selectedItemIndex < inventory.Count ? inventory.ElementAt(selectedItemIndex) : null; + } + public static void AddClicked(BlueprintItemEnchantment ench, bool second = false) { + if (selectedItemIndex < 0 || selectedItemIndex >= inventory.Count) return; + if (ench == null) return; + var selected = inventory.ElementAt(selectedItemIndex); + if (selected is ItemEntityShield shield) { + if (!second) + AddEnchantment(shield, ench); + else + AddEnchantment(shield.WeaponComponent, ench); + editedItem = shield; + } else if (second && selected is ItemEntityWeapon weapon) { + AddEnchantment(weapon.Second, ench); + editedItem = weapon; + } else { + AddEnchantment(selected, ench); + editedItem = selected; + } + } + public static void RemoveClicked(BlueprintItemEnchantment ench, bool second = false) { + if (selectedItemIndex < 0 || selectedItemIndex >= inventory.Count) return; + if (ench == null) return; + var selected = inventory.ElementAt(selectedItemIndex); + if (selected is ItemEntityShield shield) { + if (!second) + RemoveEnchantment(shield, ench); + else + RemoveEnchantment(shield.WeaponComponent, ench); + editedItem = shield; + } + if (second && selected is ItemEntityWeapon weapon) { + RemoveEnchantment(weapon.Second, ench); + editedItem = weapon; + } else { + RemoveEnchantment(selected, ench); + editedItem = selected; + } + } + + #endregion + + #region Code + public static void AddEnchantment(ItemEntity item, BlueprintItemEnchantment enchantment, Rounds? duration = null) { + if (item?.m_Enchantments == null) + Mod.Trace("item.m_Enchantments is null"); + + var fake_context = new MechanicsContext(default); // if context is null, items may stack which could cause bugs + + //var fi = AccessTools.Field(typeof(MechanicsContext), nameof(MechanicsContext.AssociatedBlueprint)); + //fi.SetValue(fake_context, enchantment); // check if AssociatedBlueprint must be set; I think not + + item.AddEnchantment(enchantment, fake_context, duration); + } + + public static void RemoveEnchantment(ItemEntity item, BlueprintItemEnchantment enchantment) { + if (item == null) return; + item.RemoveEnchantment(item.GetEnchantment(enchantment)); + } + + public static void RemoveEnchantment(ItemEntity item, ItemEnchantment enchantment) { + if (item == null) return; + item.RemoveEnchantment(enchantment); + } + /// <summary>definitely not useless</summary> + /// <returns>Key is ItemEnchantments of given item. Value is true, if it is a temporary enchantment.</returns> + public static Dictionary<ItemEnchantment, bool> GetEnchantments(ItemEntity item) { + Dictionary<ItemEnchantment, bool> enchantments = new(); + if (item == null) return enchantments; + var base_enchantments = item.Blueprint.Enchantments; + foreach (var enchantment in item.Enchantments) { + enchantments.Add(enchantment, !base_enchantments.Contains(enchantment.Blueprint)); + } + return enchantments; + } + #endregion + + #region Classes + public enum Source { + Not, // enchantment is not on the item + Removed, // enchantment is removed by toybox; removing enchantments which should be on an item, might cause stacking bugs + Timed, // enchantment is temporarily added by ability/spell (like Magic Weapon) + Added, // enchantment is added by toybox + Blueprint, // enchantment is part of item's blueprint + } + #endregion + } +} diff --git a/ToyBox/Classes/MainUI/EnhancedUI/EnhancedCamera.cs b/ToyBox/Classes/MainUI/EnhancedUI/EnhancedCamera.cs new file mode 100644 index 000000000..e04af0775 --- /dev/null +++ b/ToyBox/Classes/MainUI/EnhancedUI/EnhancedCamera.cs @@ -0,0 +1,74 @@ +using ModKit; +using ModKit.Utility; +using System; +using System.Linq; +using UnityEngine; +using static ModKit.UI; + +namespace ToyBox { + public static class EnhancedCamera { + public static Settings Settings => Main.Settings; + internal const string? ResetAdditionalCameraAngles = "Fix Camera"; + public static void OnLoad() { + KeyBindings.RegisterAction(ResetAdditionalCameraAngles, () => { + Main.resetExtraCameraAngles = true; + }); + } + public static void ResetGUI() { } + public static void OnGUI() { + HStack("Camera".localize(), + 1, + () => Toggle("Enable Zoom on all maps and cutscenes".localize(), ref Settings.toggleZoomOnAllMaps), + () => { + Toggle("Enable Rotate on all maps and cutscenes".localize(), ref Settings.toggleRotateOnAllMaps, 400.width()); + 153.space(); + Label((RichText.Orange("Note:") + RichText.Green(" For cutscenes and some situations the rotation keys are disabled so you have to hold down Mouse3 to drag in order to get rotation")).localize()); + }, + () => { + if (Toggle("Enable Mouse3 Dragging To Aim The Camera".localize(), ref Settings.toggleCameraPitch, 400.width())) { + Main.resetExtraCameraAngles = true; + } + 153.space(); + + HelpLabel("This allows you to adjust pitch (Camera Tilt) by holding down Mouse3 (which previously just rotated)".localize()); + }, + () => { + Toggle("Ctrl + Mouse3 Drag To Adjust Camera Elevation".localize(), ref Settings.toggleCameraElevation); + 25.space(); + Toggle("Free Camera".localize(), ref Settings.toggleFreeCamera); + }, + () => Label(RichText.Cyan("Rotation Options".localize())), + () => { + 50.space(); + Label(RichText.Cyan("Mouse:".localize()), 125.width()); + 25.space(); + Toggle("Invert X Axis".localize(), ref Settings.toggleInvertXAxis); + if (Settings.toggleCameraPitch) { + 25.space(); + Toggle("Invert Y Axis".localize(), ref Settings.toggleInvertYAxis); + } + }, + () => { + 50.space(); + Label(RichText.Cyan("Keyboard:".localize()), 125.width()); + 25.space(); + Toggle("Invert X Axis".localize(), ref Settings.toggleInvertKeyboardXAxis); + }, + () => { + 50.space(); + BindableActionButton(ResetAdditionalCameraAngles, true); + }, + () => LogSlider("Field Of View".localize(), ref Settings.fovMultiplier, 0.4f, 5.0f, 1, 2, "", AutoWidth()), + () => Toggle("Add Camera Elevation Offset".localize(), ref Settings.toggleOffsetCameraHeight), + () => { + if (Settings.toggleOffsetCameraHeight) { + Space(50); + Slider(ref Settings.CameraElevationOffset, -10.0f, 100.0f, 0, 1); + } + }, + () => { } + ); + } + } + +} \ No newline at end of file diff --git a/ToyBox/classes/MainUI/EnhancedUI/EnhancedInventory.cs b/ToyBox/Classes/MainUI/EnhancedUI/EnhancedInventory.cs similarity index 71% rename from ToyBox/classes/MainUI/EnhancedUI/EnhancedInventory.cs rename to ToyBox/Classes/MainUI/EnhancedUI/EnhancedInventory.cs index 7ddf7c3a2..e9569d587 100644 --- a/ToyBox/classes/MainUI/EnhancedUI/EnhancedInventory.cs +++ b/ToyBox/Classes/MainUI/EnhancedUI/EnhancedInventory.cs @@ -1,31 +1,31 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using HarmonyLib; +using HarmonyLib; using Kingmaker.PubSubSystem; using Kingmaker.UI.Common; using Kingmaker.UnitLogic.Abilities; using ModKit; -using ToyBox.Inventory; +using System; +using System.Collections.Generic; +using System.Linq; namespace ToyBox { + public enum InventoryType { + InventoryStash, + Vendor, + LootCollector, + LootInventoryStash + } + public static class EnhancedInventory { public static Settings Settings => Main.Settings; - private static Harmony m_harmony; - private static OnAreaLoad m_area_load_handler; public static readonly Dictionary<ItemSortCategories, (int index, string title)> SorterCategoryMap = new Dictionary<ItemSortCategories, (int index, string title)> { - [ItemSortCategories.NotSorted] = ((int)ItemsFilter.SorterType.NotSorted, null), - [ItemSortCategories.TypeUp] = ((int)ItemsFilter.SorterType.TypeUp, null), - [ItemSortCategories.TypeDown] = ((int)ItemsFilter.SorterType.TypeDown, null), - [ItemSortCategories.PriceUp] = ((int)ItemsFilter.SorterType.PriceUp, null), - [ItemSortCategories.PriceDown] = ((int)ItemsFilter.SorterType.PriceDown, null), - [ItemSortCategories.NameUp] = ((int)ItemsFilter.SorterType.NameUp, null), - [ItemSortCategories.NameDown] = ((int)ItemsFilter.SorterType.NameDown, null), - [ItemSortCategories.DateUp] = ((int)ItemsFilter.SorterType.DateUp, null), - [ItemSortCategories.DateDown] = ((int)ItemsFilter.SorterType.DateDown, null), - [ItemSortCategories.WeightUp] = ((int)ItemsFilter.SorterType.WeightUp, null), - [ItemSortCategories.WeightDown] = ((int)ItemsFilter.SorterType.WeightDown, null), + [ItemSortCategories.NotSorted] = ((int)ItemsSorterType.NotSorted, null), + [ItemSortCategories.TypeUp] = ((int)ItemsSorterType.TypeUp, null), + [ItemSortCategories.TypeDown] = ((int)ItemsSorterType.TypeDown, null), + [ItemSortCategories.NameUp] = ((int)ItemsSorterType.NameUp, null), + [ItemSortCategories.NameDown] = ((int)ItemsSorterType.NameDown, null), + [ItemSortCategories.DateUp] = ((int)ItemsSorterType.DateUp, null), + [ItemSortCategories.DateDown] = ((int)ItemsSorterType.DateDown, null), [ItemSortCategories.WeightValueUp] = ((int)ExpandedSorterType.WeightValueUp, "Price / Weight (in ascending order)"), [ItemSortCategories.WeightValueDown] = ((int)ExpandedSorterType.WeightValueDown, "Price / Weight (in descending order)"), [ItemSortCategories.RarityUp] = ((int)ExpandedSorterType.RarityUp, "Rarity (ascending order)"), @@ -33,20 +33,19 @@ public static class EnhancedInventory { }; public static readonly Dictionary<FilterCategories, (int index, string title)> FilterCategoryMap = new Dictionary<FilterCategories, (int index, string title)> { - [FilterCategories.NoFilter] = ((int)ItemsFilter.FilterType.NoFilter, null), - [FilterCategories.Weapon] = ((int)ItemsFilter.FilterType.Weapon, null), - [FilterCategories.Armor] = ((int)ItemsFilter.FilterType.Armor, null), - [FilterCategories.Accessories] = ((int)ItemsFilter.FilterType.Accessories, null), - [FilterCategories.Ingredients] = ((int)ItemsFilter.FilterType.Ingredients, null), - [FilterCategories.Usable] = ((int)ItemsFilter.FilterType.Usable, null), - [FilterCategories.Notable] = ((int)ItemsFilter.FilterType.Notable, null), - [FilterCategories.NonUsable] = ((int)ItemsFilter.FilterType.NonUsable, null), - [FilterCategories.Scroll] = ((int)ItemsFilter.FilterType.Scroll, null), - [FilterCategories.Wand] = ((int)ItemsFilter.FilterType.Wand, null), - [FilterCategories.Utility] = ((int)ItemsFilter.FilterType.Utility, null), - [FilterCategories.Potion] = ((int)ItemsFilter.FilterType.Potion, null), - [FilterCategories.Recipe] = ((int)ItemsFilter.FilterType.Recipe, null), - [FilterCategories.Unlearned] = ((int)ItemsFilter.FilterType.Unlearned, null), + [FilterCategories.NoFilter] = ((int)ItemsFilterType.NoFilter, null), + [FilterCategories.Weapon] = ((int)ItemsFilterType.Weapon, null), + [FilterCategories.Armor] = ((int)ItemsFilterType.Armor, null), + [FilterCategories.Accessories] = ((int)ItemsFilterType.Accessories, null), + [FilterCategories.Usable] = ((int)ItemsFilterType.Usable, null), + [FilterCategories.Notable] = ((int)ItemsFilterType.Notable, null), + [FilterCategories.NonUsable] = ((int)ItemsFilterType.NonUsable, null), + [FilterCategories.Scroll] = ((int)ItemsFilterType.Scroll, null), + [FilterCategories.Wand] = ((int)ItemsFilterType.Wand, null), + [FilterCategories.Utility] = ((int)ItemsFilterType.Utility, null), + [FilterCategories.Potion] = ((int)ItemsFilterType.Potion, null), + [FilterCategories.Recipe] = ((int)ItemsFilterType.Recipe, null), + [FilterCategories.Unlearned] = ((int)ItemsFilterType.Unlearned, null), [FilterCategories.QuickslotUtils] = ((int)ExpandedFilterType.QuickslotUtilities, "Quickslot Usable"), [FilterCategories.UnlearnedRecipes] = ((int)ExpandedFilterType.UnlearnedRecipes, "Unlearned Recipes"), [FilterCategories.UnreadDocuments] = ((int)ExpandedFilterType.UnreadDocuments, "Unread Documents"), @@ -59,12 +58,9 @@ public static class EnhancedInventory { public static readonly RemappableInt FilterMapper = new RemappableInt(); public static readonly RemappableInt SorterMapper = new RemappableInt(); public static void OnLoad() { - m_area_load_handler = new OnAreaLoad(); - EventBus.Subscribe(m_area_load_handler); RefreshRemappers(); } - public static void OnUnLoad() { - EventBus.Unsubscribe(m_area_load_handler); + public static void OnUnload() { } public static void RefreshRemappers() { FilterMapper.Clear(); @@ -82,8 +78,7 @@ public static void RefreshRemappers() { SorterMapper.Add(SorterCategoryMap[flag].index); } } - ItemsFilterPCView_.ReloadFilterViews(); - ItemsFilterSearchPCView_Initialize_Patch.ReloadFilterViews(); + // TODO: bring this back once we implement this for RT } } @@ -145,7 +140,7 @@ public enum FilterCategories { Notable | NonUsable | Scroll | - Wand | + Wand | Potion | Recipe | Unlearned | @@ -154,7 +149,7 @@ public enum FilterCategories { UnreadDocuments | UsableWithoutUMD | CurrentEquipped | - NonZeroPW | + NonZeroPW | UnlearnedScrolls, } @@ -183,8 +178,7 @@ public enum ItemSortCategories { WeightValueUp | RarityDown } - public enum ExpandedFilterType - { + public enum ExpandedFilterType { QuickslotUtilities = 14, UnlearnedRecipes = 15, UnreadDocuments = 16, @@ -194,16 +188,14 @@ public enum ExpandedFilterType UnlearnedScrolls = 20, } - public enum ExpandedSorterType - { + public enum ExpandedSorterType { WeightValueUp = 11, WeightValueDown = 12, RarityUp = 13, RarityDown = 14 } - public enum SpellbookFilter - { + public enum SpellbookFilter { NoFilter, AOE, Touch, @@ -213,8 +205,7 @@ public enum SpellbookFilter SupportsMetamagic } - public static class EnumHelper - { + public static class EnumHelper { public static IEnumerable<InventorySearchCriteria> ValidInventorySearchCriteria = Enum.GetValues(typeof(InventorySearchCriteria)).Cast<InventorySearchCriteria>().Where(i => i != InventorySearchCriteria.Default); @@ -229,7 +220,7 @@ public static IEnumerable<FilterCategories> ValidFilterCategories public static bool IsRarityCategory(this ItemSortCategories category) => category == ItemSortCategories.RarityUp || category == ItemSortCategories.RarityDown; public static bool IsValid(this ItemSortCategories category) => category != ItemSortCategories.Default && (Main.Settings.UsingLootRarity || !category.IsRarityCategory()); - public static IEnumerable<ItemSortCategories> ValidSorterCategories + public static IEnumerable<ItemSortCategories> ValidSorterCategories = Enum.GetValues(typeof(ItemSortCategories)).Cast<ItemSortCategories>().Where(category => category.IsValid()); } } diff --git a/ToyBox/Classes/MainUI/EnhancedUI/EnhancedUI.cs b/ToyBox/Classes/MainUI/EnhancedUI/EnhancedUI.cs new file mode 100644 index 000000000..f8ddba2d7 --- /dev/null +++ b/ToyBox/Classes/MainUI/EnhancedUI/EnhancedUI.cs @@ -0,0 +1,115 @@ +using Kingmaker; +using Kingmaker.Blueprints; +using Kingmaker.Designers.EventConditionActionSystem.Evaluators; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.PubSubSystem; +using Kingmaker.UnitLogic; +using Kingmaker.View.MapObjects; +using Kingmaker.View.MapObjects.InteractionRestrictions; +using ModKit; +using ModKit.Utility; +using System; +using System.Linq; +using UnityEngine; +using UnityModManagerNet; +using static ModKit.UI; + +namespace ToyBox { + public static class EnhancedUI { + public static Settings Settings => Main.Settings; + public static void ResetGUI() { } + public static void OnGUI() { + HStack("Common Tweaks".localize(), + 1, + () => { + ActionButton("Maximize Window".localize(), Actions.MaximizeModWindow, 200.width()); + 300.space(); + HelpLabel("Maximize the ModManager window for best ToyBox user experience".localize()); + }, + () => { + Toggle("Enhanced Map View".localize(), ref Settings.toggleZoomableLocalMaps, 500.width()); + HelpLabel("Makes mouse zoom work for the local map (cities, dungeons, etc). Game restart required if you turn it off".localize()); + }, + () => { + var modifier = KeyBindings.GetBinding("InventoryUseModifier"); + var modifierText = modifier.Key == KeyCode.None ? "Modifer" : modifier.ToString(); + Toggle("Allow ".localize() + RichText.Cyan($"{modifierText}") + (RichText.Cyan(" + Click") + " To Use Items In Inventory").localize(), ref Settings.toggleShiftClickToUseInventorySlot, 470.width()); + if (Settings.toggleShiftClickToUseInventorySlot) { + ModifierPicker("InventoryUseModifier", "", 0); + } + }, + () => { + var modifier = KeyBindings.GetBinding("ClickToTransferModifier"); + var modifierText = modifier.Key == KeyCode.None ? "Modifer" : modifier.ToString(); + Toggle("Allow ".localize() + RichText.Cyan($"{modifierText}") + (RichText.Cyan(" + Click") + " To Transfer Entire Stack").localize(), ref Settings.toggleShiftClickToFastTransfer, 470.width()); + if (Settings.toggleShiftClickToFastTransfer) { + ModifierPicker("ClickToTransferModifier", "", 0); + } + }, + () => Toggle("Object Highlight Toggle Mode".localize(), ref Settings.highlightObjectsToggle), + () => { + Toggle("Mark Interesting NPCs".localize(), ref Settings.toggleShowInterestingNPCsOnLocalMap, 500.width()); + HelpLabel("This will change the color of NPC names on the highlike makers and change the color map markers to indicate that they have interesting or conditional interactions".localize()); + }, + () => ActionButton("Fix Incorrect Main Character".localize(), + () => { + var probablyPlayer = Game.Instance.Player?.Party? + .Where(x => !x.IsCustomCompanion()) + .Where(x => !x.IsStoryCompanion()).ToList(); + if (probablyPlayer is { Count: 1 }) { + var newMainCharacter = probablyPlayer.First(); + Mod.Warn($"Promoting {newMainCharacter.CharacterName} to main character!"); + if (Game.Instance != null) Game.Instance.Player.MainCharacter = new UnitReference(newMainCharacter); + } + }, + AutoWidth()), + () => { } + ); + Div(0, 25); + HStack("Loot Rarity Coloring".localize(), + 1, + () => { + using (VerticalScope(300.width())) { + Toggle("Show Rarity Tags".localize(), ref Settings.toggleShowRarityTags); + Toggle("Color Item Names".localize(), ref Settings.toggleColorLootByRarity); + } + using (VerticalScope()) { + Label(RichText.Green($"This makes loot function like Diablo or Borderlands. {RichText.Orange("Note: turning this off requires you to save and reload for it to take effect.")}".localize() +)); + } + }, + () => { + if (Settings.UsingLootRarity) { + using (VerticalScope(400.width())) { + Label(RichText.Cyan("Minimum Rarity For Loot Rarity Tags/Colors".localize()), AutoWidth()); + RarityGrid(ref Settings.minRarityToColor, 4, AutoWidth()); + } + } + }, + () => { + if (Settings.UsingLootRarity) { + using (VerticalScope(300)) { + using (HorizontalScope(300)) { + using (VerticalScope()) { + Label(RichText.Cyan("Maximum Rarity To Hide:".localize()), AutoWidth()); + RarityGrid(ref Settings.maxRarityToHide, 4, AutoWidth()); + } + } + } + 50.space(); + using (VerticalScope()) { + Label(""); + HelpLabel($"This hides map pins of loot containers containing at most the selected rarity. {RichText.Orange("Note: Changing settings requires reopening the map.")}".localize()); + } + } + }, + () => { } + ); + Div(0, 25); + EnhancedCamera.OnGUI(); + Div(0, 25); + // TODO: Update EnumHelper.ValidFilterCategories for RT + } + } + +} \ No newline at end of file diff --git a/ToyBox/Classes/MainUI/EnhancedUI/GameSaves.cs b/ToyBox/Classes/MainUI/EnhancedUI/GameSaves.cs new file mode 100644 index 000000000..835b42c79 --- /dev/null +++ b/ToyBox/Classes/MainUI/EnhancedUI/GameSaves.cs @@ -0,0 +1,100 @@ +using Kingmaker; +using Kingmaker.EntitySystem.Persistence; +using ModKit; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace ToyBox { + // To be clear this is an editor of your list of saves + // ToyBox already takes care of the role of the actual save editor + public static class GameSavesBrowser { + public static Settings Settings => Main.Settings; + private static Browser<SaveInfo, SaveInfo> savesBrowser = new(true, true); + private static (string, string) nameEditState = (null, null); + private static IEnumerable<SaveInfo> _allSaves = null; + private static IEnumerable<SaveInfo> _currentSaves = null; + public static string? SearchKey(this SaveInfo info) => + $"{info.Name}{info.Area.AreaName.ToString()}{info.Description}{info.FileName}"; + public static IComparable?[] SortKey(this SaveInfo info) => new IComparable[] { + info.PlayerCharacterName, + info.GameSaveTime + }; + + public static void OnGUI() { + var saveManager = Game.Instance?.SaveManager; + string? currentGameID = Game.Instance?.Player?.GameId; + + Div(0, 25); + HStack("Saves".localize(), + 1, + () => { + Toggle("Auto load Last Save on launch".localize(), ref Settings.toggleAutomaticallyLoadLastSave, 500.width()); + HelpLabel("Hold down shift during launch to bypass".localize()); + }, + () => { + using (HorizontalScope()) { + Label("Save ID: ".localize()); + if (currentGameID != null) { + if (EditableLabel(ref currentGameID, ref nameEditState, 100)) { + Game.Instance.Player.GameId = currentGameID; + } + } else { + currentGameID = "N/A".localize(); + Label(currentGameID); + } + } + }, + () => { } + ); + if (Main.IsInGame) { + Div(50, 25); + //var currentSave = Game.Instance.SaveManager.GetLatestSave(); + // TODO: add refresh + if (_currentSaves == null || _allSaves == null) { + saveManager?.UpdateSaveListIfNeeded(true); + _currentSaves = saveManager?.Where(info => info?.GameId == currentGameID); + _allSaves = saveManager?.Where(info => info != null); + } + if (_currentSaves == null || _allSaves == null) { + return; + } + using (VerticalScope()) { + savesBrowser.OnGUI(_currentSaves, + () => _allSaves, + info => info, + info => info.SearchKey(), + info => info.SortKey(), + () => { + Toggle("Show GameID".localize(), ref Settings.toggleShowGameIDs); + }, + (info, _) => { + var isCurrent = _currentSaves.Contains(info); + var characterName = isCurrent ? info.PlayerCharacterName.Orange() : info.PlayerCharacterName; + Label(characterName, 400.width()); + 25.space(); + Label($"Level: {info.PlayerCharacterRank}"); + 25.space(); + Label($"{info.Area.AreaName.StringValue()}".Cyan(), 400.width()); + if (Settings.toggleShowGameIDs) { + 25.space(); + ClipboardLabel(info.GameId, 400.width()); + } + 25.space(); + HelpLabel(info.Name.ToString()); + }, + null, + 50, + true, + true, + 100, + 400, + "", + false + ); + } + } + + } + } +} diff --git a/ToyBox/classes/MainUI/EnhancedUI/ItemRarity.cs b/ToyBox/Classes/MainUI/EnhancedUI/ItemRarity.cs similarity index 58% rename from ToyBox/classes/MainUI/EnhancedUI/ItemRarity.cs rename to ToyBox/Classes/MainUI/EnhancedUI/ItemRarity.cs index ff222e6c4..e38613f4f 100644 --- a/ToyBox/classes/MainUI/EnhancedUI/ItemRarity.cs +++ b/ToyBox/Classes/MainUI/EnhancedUI/ItemRarity.cs @@ -10,9 +10,6 @@ using Kingmaker.EntitySystem.Entities; using Kingmaker.Items; using Kingmaker.UI.Common; -using Kingmaker.UI.MVVM._PCView.ServiceWindows.LocalMap.Markers; -using Kingmaker.UI.MVVM._VM.ServiceWindows.LocalMap.Markers; -using Kingmaker.UI.MVVM._VM.ServiceWindows.LocalMap.Utils; using Kingmaker.View; using Kingmaker.View.MapObjects; using ModKit; @@ -78,18 +75,7 @@ public static RarityType Rarity(this int rating) { return rarity; } public static int Rating(this BlueprintItemEnchantment bp) { - int rating; - var modifierRating = RarityScaling * bp.Components?.Sum( - c => c is AddStatBonusEquipment sbe ? sbe.Value - : c is AllSavesBonusEquipment asbe ? asbe.Value - : 0 - ) ?? 0; - if (bp is BlueprintWeaponEnchantment || bp is BlueprintArmorEnchantment) - rating = Math.Max(5, bp.EnchantmentCost * RarityScaling); - else { - rating = (bp.IdentifyDC * 5) / 2; - } - return Math.Max(modifierRating, rating); + return 0; } public static int Rating(this ItemEntity item) => item.Blueprint.Rating(item); public static int Rating(this BlueprintItem bp) { @@ -97,10 +83,10 @@ public static int Rating(this BlueprintItem bp) { var bpEnchantmentRating = bp.CollectEnchantments().Sum((e) => e.Rating()); return Math.Max(bpRating, bpEnchantmentRating); } - public static int Rating(this BlueprintItem bp, ItemEntity item = null) { + public static int Rating(this BlueprintItem bp, ItemEntity? item = null) { var rating = 0; var itemRating = 0; - var cost = bp.Cost; + var cost = 0; var logCost = cost > 1 ? Math.Log(cost) / Math.Log(5) : 0; var costRating = (int)(2.5f * Math.Floor(logCost)); try { @@ -108,20 +94,13 @@ public static int Rating(this BlueprintItem bp, ItemEntity item = null) { itemRating = item.Enchantments.Sum(e => e.Blueprint.Rating()); var itemEnchantmentRating = item.Enchantments.Sum(e => e.Blueprint.Rating()); //Mod.Log($"item itemRating: {itemRating} - {itemEnchRating}"); - if (Game.Instance?.SelectionCharacter?.CurrentSelectedCharacter is var currentCharacter) { - var component = bp.GetComponent<CopyItem>(); - if (component != null && component.CanCopy(item, currentCharacter)) { - itemRating = Math.Max(itemRating, RarityScaling); - } - } itemRating = Math.Max(itemRating, itemEnchantmentRating); } var bpRating = bp.Rating(); //if (enchantValue > 0) Main.Log($"blueprint enchantValue: {enchantValue}"); rating = Math.Max(itemRating, bpRating); rating = Math.Max(rating, costRating); - } - catch { + } catch { // ignored } //var rating = item.EnchantmentValue * rarityScaling; @@ -134,22 +113,12 @@ public static int Rating(this BlueprintItem bp, ItemEntity item = null) { #if false Mod.Log($"{bp.Name} : {bp.GetType().Name.grey().bold()} - itemRating: {itemRating} bpRating: {bpRating} logCost: {logCost} - rating: {rating}"); #endif - rating = bp switch { - BlueprintItemWeapon bpWeap when !bpWeap.IsMagic => Math.Min(rating, RarityScaling - 1), - BlueprintItemArmor bpArmor when !bpArmor.IsMagic => Math.Min(rating, RarityScaling - 1), - _ => rating - }; - return rating; } public static RarityType Rarity(this BlueprintItem bp) { if (bp == null) return RarityType.None; if (bp.IsNotable) return RarityType.Notable; if (bp is not BlueprintItemNote noteBP) return Rarity(bp.Rating()); - var component = noteBP.GetComponent<AddItemShowInfoCallback>(); - if (component != null) { - return RarityType.Notable; - } return Rarity(bp.Rating()); } public static RarityType Rarity(this ItemEntity item) { @@ -157,55 +126,22 @@ public static RarityType Rarity(this ItemEntity item) { if (bp == null) return RarityType.None; if (bp.IsNotable) return RarityType.Notable; if (bp is not BlueprintItemNote noteBP) return Rarity(bp.Rating(item)); - var component = noteBP.GetComponent<AddItemShowInfoCallback>(); - return component != null ? RarityType.Notable : Rarity(bp.Rating(item)); + return Rarity(bp.Rating()); } public static RarityType Rarity(this BlueprintItemEnchantment bp) => bp.Rating().Rarity(); - public static Color Color(this RarityType rarity, float adjust = 0) => RarityColors[(int)rarity].color(adjust); - public static string Rarity(this string s, RarityType rarity, float adjust = 0) => s.color(RarityColors[(int)rarity]); - public static string DarkModeRarity(this string s, RarityType rarity, float adjust = 0) => s.color(DarkModeRarityColors[(int)rarity]); + public static Color Color(this RarityType rarity, float adjust = 0) => RarityColors[(int)rarity].Color(adjust); + public static string? Rarity(this string s, RarityType rarity, float adjust = 0) => s.Color(RarityColors[(int)rarity]); + public static string? DarkModeRarity(this string s, RarityType rarity, float adjust = 0) => s.Color(DarkModeRarityColors[(int)rarity]); - public static string RarityInGame(this string s, RarityType rarity, float adjust = 0) { - var name = Settings.toggleColorLootByRarity ? s.color(RarityColors[(int)rarity]) : s; + public static string? RarityInGame(this string? s, RarityType rarity, float adjust = 0) { + var name = Settings.toggleColorLootByRarity ? s.Color(RarityColors[(int)rarity]) : s; if (!Settings.toggleShowRarityTags) return name; if (Settings.toggleColorLootByRarity) - return name + " " + $"[{rarity}]".darkGrey().bold(); //.SizePercent(75); + return name + " " + $"[{rarity}]".DarkGrey().Bold(); //.SizePercent(75); else - return name + " " + $"[{rarity}]".Rarity(rarity).bold(); //.SizePercent(75); - } - public static string GetString(this RarityType rarity, float adjust = 0) => rarity.ToString().Rarity(rarity, adjust); - public static void Hide(this LocalMapLootMarkerPCView localMapLootMarkerPCView) { - LocalMapCommonMarkerVM markerVm = localMapLootMarkerPCView.ViewModel as LocalMapCommonMarkerVM; - LocalMapMarkerPart mapPart = markerVm.m_Marker as LocalMapMarkerPart; - if (mapPart?.GetMarkerType() == LocalMapMarkType.Loot) { - MapObjectView MOV = mapPart.Owner.View as MapObjectView; - InteractionLootPart lootPart = (MOV.Data.Interactions[0] as InteractionLootPart); - DoHide(lootPart.Loot, localMapLootMarkerPCView); - } - else if (mapPart == null) { - if (markerVm.m_Marker is not UnitLocalMapMarker unitMarker) return; - UnitEntityView unit = unitMarker.m_Unit; - UnitEntityData data = unit.Data; - DoHide(data.Inventory, localMapLootMarkerPCView); - } - } - public static void DoHide(ItemsCollection loot, LocalMapLootMarkerPCView localMapLootMarkerPCView) { - if (loot == Game.Instance.Player.SharedStash) return; - RarityType highest = RarityType.None; - foreach (ItemEntity item in loot) { - if (!item.IsLootable) continue; - RarityType itemRarity = item.Rarity(); - if (itemRarity > highest) { - highest = itemRarity; - } - } - if (highest <= Settings.maxRarityToHide) { - localMapLootMarkerPCView.transform.localScale = new Vector3(0, 0, 0); - } - else { - localMapLootMarkerPCView.transform.localScale = new Vector3(1, 1, 1); - } + return name + " " + $"[{rarity}]".Rarity(rarity).Bold(); //.SizePercent(75); } + public static string? GetString(this RarityType rarity, float adjust = 0) => rarity.ToString().Rarity(rarity, adjust); // Compare function for item rarity public static float RaritySortScore(this ItemEntity item) { var rarity = item.Rarity(); @@ -215,8 +151,8 @@ public static int RarityCompare( ItemEntity a, ItemEntity b, bool invert, - ItemsFilter.FilterType filterType, - Func<ItemEntity, ItemEntity, ItemsFilter.FilterType, int> otherCompare + ItemsFilterType filterType, + Func<ItemEntity, ItemEntity, ItemsFilterType, int> otherCompare ) { var result = a.RaritySortScore().CompareTo(b.RaritySortScore()); if (invert) result *= -1; diff --git a/ToyBox/Classes/MainUI/EnhancedUI/LootHelper.cs b/ToyBox/Classes/MainUI/EnhancedUI/LootHelper.cs new file mode 100644 index 000000000..c2483181d --- /dev/null +++ b/ToyBox/Classes/MainUI/EnhancedUI/LootHelper.cs @@ -0,0 +1,148 @@ +using Kingmaker; +using Kingmaker.Blueprints.Loot; +using Kingmaker.Designers.EventConditionActionSystem.Actions; +using Kingmaker.ElementsSystem; +using Kingmaker.EntitySystem; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.EntitySystem.Interfaces; +using Kingmaker.EntitySystem.Stats; +using Kingmaker.Items; +using Kingmaker.PubSubSystem; +using Kingmaker.UI.MVVM; +using Kingmaker.UnitLogic; +using Kingmaker.Utility; +using Kingmaker.View; +using Kingmaker.View.MapObjects; +using ModKit; +using Newtonsoft.Json; +using Owlcat.Runtime.Core.Utility; +using Owlcat.Runtime.UI.Controls.Button; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization.Formatters.Binary; +using UnityEngine; +using UnityModManagerNet; + +namespace ToyBox { + public static class LootHelper { + public static string NameAndOwner(this ItemEntity u, bool showRating, bool darkmode = false) { + var ret = (showRating ? $"{u.Rating()} ".Orange().Bold() : ""); + try { + ret += (u.Owner != null ? $"({u.Owner.Name}) ".Orange() : ""); + } catch (Exception e) { + Mod.Error(e.ToString()); + } + ret += (darkmode ? u.Name.StripHTML().DarkModeRarity(u.Rarity()) : u.Name); + return ret; + } + public static string NameAndOwner(this ItemEntity u, bool darkmode = false) => u.NameAndOwner(Main.Settings.showRatingForEnchantmentInventoryItems, darkmode); + public static bool IsLootable(this ItemEntity item, RarityType filter = RarityType.None) { + var rarity = item.Rarity(); + if ((int)rarity < (int)filter) return false; + return item.IsLootable; + } + public static List<ItemEntity> Lootable(this List<ItemEntity> loots, RarityType filter = RarityType.None) => loots.Where(l => l.IsLootable(filter)).ToList(); + public static string GetName(this LootWrapper present) { + if (present.InteractionLoot != null) { + // var name = present.InteractionLoot.Owner.View.name; + var name = present.InteractionLoot.Source.ToString(); + if (name == null || name.Length == 0) name = "Ground"; + return name; + } + if (present.Unit != null) return present.Unit.CharacterName; + return null; + } + + public static List<ItemEntity> GetInteraction(this LootWrapper present) { + if (present.InteractionLoot != null) return present.InteractionLoot.Loot.Items + .ToList() + ; + if (present.Unit != null) return present.Unit.Inventory.Items + .ToList() + ; + return null; + } + public static IEnumerable<ItemEntity> Search(this IEnumerable<ItemEntity> items, string searchText) => items.Where(i => searchText.Length > 0 ? i.Name.ToLower().Contains(searchText.ToLower()) : true); + public static List<ItemEntity> GetLewtz(this LootWrapper present, string searchText = "") { + if (present.InteractionLoot != null) return present.InteractionLoot.Loot.Items.Search(searchText).ToList(); + if (present.Unit != null) return present.Unit.Inventory.Items.Search(searchText).ToList(); + return null; + } + // TODO: implement ToyBox improvements + public static IEnumerable<LootWrapper> GetMassLootFromCurrentArea() { + var lootFromCurrentArea = new List<LootWrapper>(); + foreach (var baseUnitEntity in Shodan.AllBaseUnits.Where(u => u.IsRevealed && u.IsDeadAndHasLoot)) + lootFromCurrentArea.Add(new LootWrapper { + Unit = baseUnitEntity + }); + var interactionLootParts = Game.Instance.State.MapObjects.Select(i => i.GetOptional<InteractionLootPart>()) + .Concat(Game.Instance.State.AllUnits.Select(i => i.GetOptional<InteractionLootPart>())).NotNull(); + var source = TempList.Get<InteractionLootPart>(); + foreach (var interactionLootPart in interactionLootParts) + if ((interactionLootPart.Owner.IsRevealed || Main.Settings.toggleShowHiddenLoot) && interactionLootPart.Loot.HasLoot && + (interactionLootPart.LootViewed || Main.Settings.toggleShowHiddenLoot || + (interactionLootPart.View is DroppedLoot && !(bool)(EntityPart)interactionLootPart.Owner + .GetOptional<DroppedLoot.EntityPartBreathOfMoney>()) || + (bool)(UnityEngine.Object)interactionLootPart.View.GetComponent<SkinnedMeshRenderer>())) + source.Add(interactionLootPart); + var collection = source.Distinct(new MassLootHelper.LootDuplicateCheck()).Select(i => new LootWrapper { + InteractionLoot = i + }); + lootFromCurrentArea.AddRange(collection); + return lootFromCurrentArea; + } + public static void OpenMassLoot() { + var loot = MassLootHelper.GetMassLootFromCurrentArea(); + if (loot == null) return; + var count = loot.Count(); + var count2 = loot.Count(present => present.InteractionLoot != null); + Mod.Debug($"MassLoot: Count = {loot.Count()}"); + Mod.Debug($"MassLoot: Count2 = {count}"); + if (count == 0) return; + // Access to LootContextVM + var contextVM = RootUIContext.Instance + .SurfaceVM? + .StaticPartVM?.LootContextVM; + if (contextVM == null) return; + // Add new loot... + var lootVM = new LootVM(LootContextVM.LootWindowMode.ZoneExit, loot, null, () => contextVM.DisposeAndRemove(contextVM.LootVM)); + + // Open window add lootVM int contextVM + contextVM.LootVM.Value = lootVM; + + //EventBus.RaiseEvent((Action<ILootInterractionHandler>)(e => e.HandleZoneLootInterraction(null))); + UnityModManager.UI.Instance.ToggleWindow(false); + } + private static DroppedLoot m_FakePlayerChest; + // TODO: Verify this actually works + public static void OpenPlayerChest() { + var contextVM = RootUIContext.Instance.SurfaceVM?.StaticPartVM?.LootContextVM; + if (contextVM == null) return; + if (m_FakePlayerChest == null) { + var wow = new GameObject("._."); + // Need to disable the GO to prevent DroppedLoot.OnEnable from running since that causes an NRE that can't be caught properly + wow.SetActive(false); + UnityEngine.Object.DontDestroyOnLoad(wow); + var a = wow.EnsureComponent<DroppedLoot>(); + var il = wow.EnsureComponent<InteractionLoot>(); + il.Settings = new() { LootContainerType = LootContainerType.PlayerChest, + PutItemTrigger = new() { Action = new() { guid = null } }, + CloseTrigger = new() { Action = new() { guid = null } }, + TakeItemTrigger = new() { Action = new() { guid = null } } }; + ((EntityViewBase)a).Data = Entity.Initialize(new DroppedLoot.EntityData(a)); + var ilp = a.Data.ToEntity().GetOrCreate<InteractionLootPart>(); + a.Data.AttachView(a); + ilp.SetSettings(il.Settings); + wow.SetActive(true); + m_FakePlayerChest = a; + } + m_FakePlayerChest.Data.ToEntity().GetOptional<InteractionLootPart>().Loot = Game.Instance.Player.SharedStash; + var lootVM = new LootVM(LootContextVM.LootWindowMode.PlayerChest, [m_FakePlayerChest], () => contextVM.DisposeAndRemove(contextVM.LootVM)); + + contextVM.LootVM.Value = lootVM; + } + } +} \ No newline at end of file diff --git a/ToyBox/Classes/MainUI/EnhancedUI/PhatLoot.cs b/ToyBox/Classes/MainUI/EnhancedUI/PhatLoot.cs new file mode 100644 index 000000000..334b303b1 --- /dev/null +++ b/ToyBox/Classes/MainUI/EnhancedUI/PhatLoot.cs @@ -0,0 +1,208 @@ +using Kingmaker; +using Kingmaker.Designers.EventConditionActionSystem.Evaluators; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.View.MapObjects; +using Kingmaker.View.MapObjects.InteractionRestrictions; +using ModKit; +using ModKit.Utility; +using System; +using System.Linq; +using UnityEngine; +using static ModKit.UI; + +namespace ToyBox { + public class PhatLoot { + public static Settings Settings => Main.Settings; + public static string searchText = ""; + + // + private const string MassLootBox = "Open Mass Loot Window"; + private const string OpenPlayerChest = "Open Cargo"; + private const string RevealGroundLoot = "Reveal Ground Loot"; + private const string RevealHiddenGroundLoot = "Reveal Hidden Ground Loot"; + private const string RevealInevitableLoot = "Reveal Inevitable Loot"; + public static void ResetGUI() { } + + public static void OnLoad() { + KeyBindings.RegisterAction(MassLootBox, LootHelper.OpenMassLoot); + } + + public static void OnGUI() { + if (Game.Instance?.Player?.Inventory == null) return; +#if false + Div(0, 25); + var inventory = Game.Instance.Player.Inventory; + var items = inventory.ToList(); + HStack("Inventory", 1, + () => { + ActionButton("Export", () => items.Export("inventory.json"), Width(150)); + Space(25); + ActionButton("Import", () => inventory.Import("inventory.json"), Width(150)); + Space(25); + ActionButton("Replace", () => inventory.Import("inventory.json", true), Width(150)); + }, + () => { } + ); +#endif + Div(0, 25); + HStack("Loot".localize(), 1, + () => { + BindableActionButton(MassLootBox, true, Width(400)); + Space(95 - 150); + Label(RichText.Green("Lets you open up the area's mass loot screen to grab goodies whenever you want. Normally shown only when you exit the area".localize())); + }, + () => { } + ); + Div(0, 25); + HStack("Mass Loot".localize(), 1, + () => { + Toggle("Show Everything When Leaving Map".localize(), ref Settings.toggleMassLootEverything, 400.width()); + 150.space(); + Label(RichText.Green("Some items might be invisible until looted".localize())); + }, + () => { + Toggle("Steal from living NPCs".localize(), ref Settings.toggleLootAliveUnits, 400.width()); + 150.space(); + Label(RichText.Green("Allow Mass Loot to steal from living NPCs".localize())); + }, + () => { } + ); + Div(0, 25); +#if DEBUG + HStack("Loot Rarity Coloring".localize(), 1, + () => { + using (VerticalScope(300.width())) { + Toggle("Show Rarity Tags".localize(), ref Settings.toggleShowRarityTags, 300.width()); + Toggle("Color Item Names".localize(), ref Settings.toggleColorLootByRarity, 300.width()); + } + using (VerticalScope()) { + Label((RichText.Green($"This makes loot function like Diablo or Borderlands. {RichText.Orange("Note: turning this off requires you to save and reload for it to take effect.")}" +)).localize()); + } + }, + () => { + using (VerticalScope(400.width())) { + Label(RichText.Cyan("Minimum Rarity For Loot Rarity Tags/Colors".localize()), AutoWidth()); + RarityGrid(ref Settings.minRarityToColor, 4, AutoWidth()); + } + }); + Div(0, 25); + HStack("Loot Rarity Filtering".localize(), 1, + () => { + using (VerticalScope(300)) { + using (HorizontalScope(300)) { + using (VerticalScope()) { + Label(RichText.Cyan("Maximum Rarity To Hide:".localize()), AutoWidth()); + RarityGrid(ref Settings.maxRarityToHide, 4, AutoWidth()); + } + } + } + 50.space(); + using (VerticalScope()) { + Label(""); + HelpLabel($"This hides map pins of loot containers containing at most the selected rarity. {RichText.Orange("Note: Changing settings requires reopening the map.")}".localize()); + } + }, + // The following options let you configure loot filtering and auto sell levels:".green()); + () => { } + ); + Div(0, 25); +#endif + if (Game.Instance.CurrentlyLoadedArea == null) return; + var isEmpty = true; + HStack("Loot Checklist".localize(), 1, + () => { + Toggle("Show hidden loot in Checklist".localize(), ref Settings.toggleShowHiddenLoot, 400.width()); + 150.space(); + Label(RichText.Green("Also lists containers that are still hidden.".localize())); + }, + () => { + var areaName = ""; + if (Main.IsInGame) { + try { + areaName = Game.Instance.CurrentlyLoadedArea.AreaDisplayName; + } catch { } + var areaPrivateName = Game.Instance.CurrentlyLoadedArea.name; + if (areaPrivateName != areaName) areaName += RichText.Yellow($"\n({areaPrivateName})"); + } + Label(RichText.Bold(RichText.Orange(areaName)), Width(300)); + Label(RichText.Cyan("Rarity: ".localize()), AutoWidth()); + RarityGrid(ref Settings.lootChecklistFilterRarity, 4, AutoWidth()); + }, + () => { + ActionTextField( + ref searchText, + "itemSearchText", + (text) => { }, + () => { }, + Width(300)); + Space(25); Toggle("Blueprint".localize(), ref Settings.toggleLootChecklistFilterBlueprint, AutoWidth()); + Space(25); Toggle("Description".localize(), ref Settings.toggleLootChecklistFilterDescription, AutoWidth()); + }, + () => { + if (!Main.IsInGame) { Label(RichText.Orange("Not available in the Main Menu".localize())); return; } + var presentGroups = LootHelper.GetMassLootFromCurrentArea().GroupBy(p => p.InteractionLoot != null ? "Containers" : "Units"); + var indent = 3; + using (VerticalScope()) { + foreach (var group in presentGroups.Reverse()) { + var presents = group.AsEnumerable().OrderByDescending(p => { + var loot = p.GetLewtz(searchText); + if (loot.Count == 0) return 0; + else return (int)loot.Max(l => l.Rarity()); + }).ToList(); + var rarity = Settings.lootChecklistFilterRarity; + var count = presents + .Where(p => + p.Unit == null + ).Count(p => p.GetLewtz(searchText).Lootable(rarity).Count() > 0); + Label($"{RichText.Cyan(group.Key.localize())}: {count}"); + Div(indent); + foreach (var present in presents) { + var phatLewtz = present.GetLewtz(searchText).Lootable(rarity).OrderByDescending(l => l.Rarity()).ToList(); + var unit = present.Unit; + if (phatLewtz.Any() + && (unit == null + ) + ) { + isEmpty = false; + Div(); + using (HorizontalScope()) { + Space(indent); + Label(RichText.Bold(RichText.Orange($"{present.GetName()}")), Width(325)); + if (present.InteractionLoot != null) { + } + Space(25); + using (VerticalScope()) { + foreach (var lewt in phatLewtz) { + var description = lewt.Blueprint.Description; + var showBP = Settings.toggleLootChecklistFilterBlueprint; + var showDesc = Settings.toggleLootChecklistFilterDescription && description != null && description.Length > 0; + using (HorizontalScope()) { + //Main.Log($"rarity: {lewt.Blueprint.Rarity()} - color: {lewt.Blueprint.Rarity().color()}"); + Label(lewt.Name.StripHTML().Rarity(lewt.Blueprint.Rarity()), showDesc || showBP ? Width(350) : AutoWidth()); + if (showBP) { + Space(100); Label(RichText.Grey(lewt.Blueprint.GetDisplayName()), showDesc ? Width(350) : AutoWidth()); + } + if (!showDesc) continue; + Space(100); Label(RichText.Green(description.StripHTML())); + } + } + } + } + Space(25); + } + } + Space(25); + } + } + }, + () => { + if (!isEmpty) return; + using (HorizontalScope()) { + Label(RichText.Orange("No Loot Available".localize()), AutoWidth()); + } + } + ); + } + } +} diff --git a/ToyBox/classes/MainUI/EnhancedUI/RemappableInt.cs b/ToyBox/Classes/MainUI/EnhancedUI/RemappableInt.cs similarity index 56% rename from ToyBox/classes/MainUI/EnhancedUI/RemappableInt.cs rename to ToyBox/Classes/MainUI/EnhancedUI/RemappableInt.cs index bc82c0168..d303c8723 100644 --- a/ToyBox/classes/MainUI/EnhancedUI/RemappableInt.cs +++ b/ToyBox/Classes/MainUI/EnhancedUI/RemappableInt.cs @@ -1,28 +1,22 @@ using System.Collections.Generic; -namespace ToyBox -{ - public class RemappableInt - { +namespace ToyBox { + public class RemappableInt { private readonly List<int> m_mapping = new List<int>(); - public void Add(int to) - { + public void Add(int to) { m_mapping.Add(to); } - public void Clear() - { + public void Clear() { m_mapping.Clear(); } - public int To(int idx) - { + public int To(int idx) { return m_mapping[idx]; } - public int From(int idx) - { + public int From(int idx) { return m_mapping.IndexOf(idx); } } diff --git a/ToyBox/classes/MainUI/Etudes/EtudeInfo.cs b/ToyBox/Classes/MainUI/Etudes/EtudeInfo.cs similarity index 61% rename from ToyBox/classes/MainUI/Etudes/EtudeInfo.cs rename to ToyBox/Classes/MainUI/Etudes/EtudeInfo.cs index b0db2c949..c987e818b 100644 --- a/ToyBox/classes/MainUI/Etudes/EtudeInfo.cs +++ b/ToyBox/Classes/MainUI/Etudes/EtudeInfo.cs @@ -7,10 +7,9 @@ using ModKit; namespace ToyBox { - public class ConflictingGroupIdReferences - { + public class ConflictingGroupIdReferences { public string Name; - public List<BlueprintGuid> Etudes = new(); + public List<string> Etudes = new(); } public class EtudeInfo { public enum EtudeState { @@ -22,17 +21,17 @@ public enum EtudeState { CompletionBlocked = 5 } - public string Name; + public string? Name; public BlueprintEtude Blueprint; - public BlueprintGuid ParentId; - public List<BlueprintGuid> LinkedId = new(); - public List<BlueprintGuid> ChainedId = new(); - public BlueprintGuid LinkedTo; - public BlueprintGuid ChainedTo; - public List<BlueprintGuid> ChildrenId = new(); + public string ParentId; + public List<string> LinkedId = new(); + public List<string> ChainedId = new(); + public string LinkedTo; + public string ChainedTo; + public List<string> ChildrenId = new(); public bool AllowActionStart; public EtudeState State; - public BlueprintGuid LinkedArea; + public string LinkedArea; public bool CompleteParent; public string Comment; public ToggleState ShowChildren; @@ -40,12 +39,12 @@ public enum EtudeState { public ToggleState ShowConflicts; public ToggleState ShowActions; public bool hasSearchResults; - public List<BlueprintGuid> ConflictingGroups = new(); + public List<string> ConflictingGroups = new(); public int Priority; } public class EtudeDrawerData { public bool ShowChildren; - public Dictionary<BlueprintGuid, EtudeInfo> ChainStarts = new Dictionary<BlueprintGuid, EtudeInfo>(); + public Dictionary<string, EtudeInfo> ChainStarts = new Dictionary<string, EtudeInfo>(); public bool NeedToPaint; public int Depth; } diff --git a/ToyBox/classes/MainUI/Etudes/EtudeTreeModel.cs b/ToyBox/Classes/MainUI/Etudes/EtudeTreeModel.cs similarity index 78% rename from ToyBox/classes/MainUI/Etudes/EtudeTreeModel.cs rename to ToyBox/Classes/MainUI/Etudes/EtudeTreeModel.cs index 181fc92e8..31ba33140 100644 --- a/ToyBox/classes/MainUI/Etudes/EtudeTreeModel.cs +++ b/ToyBox/Classes/MainUI/Etudes/EtudeTreeModel.cs @@ -1,4 +1,5 @@ -using Kingmaker.AreaLogic.Etudes; +using Kingmaker; +using Kingmaker.AreaLogic.Etudes; using Kingmaker.Blueprints; using Kingmaker.Blueprints.JsonSystem.EditorDatabase; using ModKit; @@ -10,12 +11,12 @@ using System.Threading.Tasks; using System.Web; -namespace ToyBox { +namespace ToyBox { public class EtudesTreeModel { - public List<BlueprintEtude> etudes; + public IEnumerable<BlueprintEtude> etudes; public NamedTypeFilter<BlueprintEtude> etudeFilter = new("Etudes", null, bp => bp.CollationNames(bp.Parent?.GetBlueprint().NameSafe() ?? "")); - public Dictionary<BlueprintGuid, EtudeInfo> loadedEtudes = new(); - public Dictionary<BlueprintGuid, ConflictingGroupIdReferences> conflictingGroups = new(); + public Dictionary<string, EtudeInfo> loadedEtudes = new(); + public Dictionary<string, ConflictingGroupIdReferences> conflictingGroups = new(); public Dictionary<string, string> commentTranslations; private EtudesTreeModel() { @@ -23,21 +24,14 @@ private EtudesTreeModel() { Mod.Debug($"loaded {commentTranslations.Count} key/value pairs"); } - private static EtudesTreeModel instance; + private static EtudesTreeModel _instance; - public static EtudesTreeModel Instance { - get { - if (instance == null) { - instance = new EtudesTreeModel(); - } - return instance; - } - } + public static EtudesTreeModel Instance => _instance ??= new EtudesTreeModel(); public void ReloadBlueprintsTree() { - etudes = BlueprintLoader.Shared.GetBlueprints<BlueprintEtude>(); - if (etudes == null) return; - loadedEtudes = new Dictionary<BlueprintGuid, EtudeInfo>(); + etudes = BlueprintLoader.Shared.GetBlueprintsOfType<BlueprintEtude>(); + if ((etudes?.Count() ?? 0) == 0) return; + loadedEtudes = new Dictionary<string, EtudeInfo>(); var filteredEtudes = (from bp in etudes where etudeFilter.filter(bp) select bp).ToList(); @@ -59,8 +53,7 @@ where etudeFilter.filter(bp) public void UpdateEtude(BlueprintEtude blueprintEtude) { if (loadedEtudes.ContainsKey(blueprintEtude.AssetGuid)) { UpdateEtudeData(blueprintEtude); - } - else { + } else { AddEtudeToLoaded(blueprintEtude); } } @@ -69,22 +62,22 @@ private void UpdateEtudeData(BlueprintEtude blueprintEtude) { var etudeInfo = PrepareNewEtudeData(blueprintEtude); var oldEtude = loadedEtudes[blueprintEtude.AssetGuid]; //Remove old data - if (etudeInfo.ChainedTo != oldEtude.ChainedTo && oldEtude.ChainedTo != BlueprintGuid.Empty && loadedEtudes[oldEtude.ChainedTo].ChainedId.Contains(blueprintEtude.AssetGuid)) + if (etudeInfo.ChainedTo != oldEtude.ChainedTo && oldEtude.ChainedTo != string.Empty && loadedEtudes[oldEtude.ChainedTo].ChainedId.Contains(blueprintEtude.AssetGuid)) loadedEtudes[oldEtude.ChainedTo].ChainedId.Remove(blueprintEtude.AssetGuid); - if (etudeInfo.LinkedTo != oldEtude.LinkedTo && oldEtude.LinkedTo != BlueprintGuid.Empty && loadedEtudes[oldEtude.LinkedTo].LinkedId.Contains(blueprintEtude.AssetGuid)) + if (etudeInfo.LinkedTo != oldEtude.LinkedTo && oldEtude.LinkedTo != string.Empty && loadedEtudes[oldEtude.LinkedTo].LinkedId.Contains(blueprintEtude.AssetGuid)) loadedEtudes[oldEtude.LinkedTo].LinkedId.Remove(blueprintEtude.AssetGuid); - if (etudeInfo.ParentId != oldEtude.ParentId && oldEtude.ParentId != BlueprintGuid.Empty && loadedEtudes[oldEtude.ParentId].ChildrenId.Contains(blueprintEtude.AssetGuid)) + if (etudeInfo.ParentId != oldEtude.ParentId && oldEtude.ParentId != string.Empty && loadedEtudes[oldEtude.ParentId].ChildrenId.Contains(blueprintEtude.AssetGuid)) loadedEtudes[oldEtude.ParentId].ChildrenId.Remove(blueprintEtude.AssetGuid); foreach (var etude in oldEtude.ChainedId) { if (!etudeInfo.ChainedId.Contains(etude)) { - loadedEtudes[etude].ChainedTo = BlueprintGuid.Empty; + loadedEtudes[etude].ChainedTo = string.Empty; } } foreach (var etude in oldEtude.LinkedId) { if (!etudeInfo.LinkedId.Contains(etude)) { - loadedEtudes[etude].LinkedTo = BlueprintGuid.Empty; + loadedEtudes[etude].LinkedTo = string.Empty; } } @@ -93,10 +86,10 @@ private void UpdateEtudeData(BlueprintEtude blueprintEtude) { etudeInfo.ChildrenId = oldEtude.ChildrenId; etudeInfo.ChainedTo = oldEtude.ChainedTo; etudeInfo.LinkedTo = oldEtude.LinkedTo; - if (oldEtude.ChainedTo != BlueprintGuid.Empty) + if (oldEtude.ChainedTo != string.Empty) loadedEtudes[etudeInfo.ChainedTo].ChainedId.Add(blueprintEtude.AssetGuid); - if (oldEtude.LinkedTo != BlueprintGuid.Empty) + if (oldEtude.LinkedTo != string.Empty) loadedEtudes[etudeInfo.LinkedTo].LinkedId.Add(blueprintEtude.AssetGuid); loadedEtudes[blueprintEtude.AssetGuid] = etudeInfo; @@ -117,38 +110,40 @@ private void AddEtudeToLoaded(BlueprintEtude blueprintEtude) { } } - public void RemoveEtudeData(BlueprintGuid SelectedId) { + public void RemoveEtudeData(string SelectedId) { if (!loadedEtudes.ContainsKey(SelectedId)) return; var etudeToRemove = loadedEtudes[SelectedId]; loadedEtudes[etudeToRemove.ParentId].ChildrenId.Remove(SelectedId); - if (etudeToRemove.LinkedTo != BlueprintGuid.Empty) { + if (etudeToRemove.LinkedTo != string.Empty) { loadedEtudes[etudeToRemove.LinkedTo].LinkedId.Remove(SelectedId); } - if (etudeToRemove.ChainedTo != BlueprintGuid.Empty) { + if (etudeToRemove.ChainedTo != string.Empty) { loadedEtudes[etudeToRemove.ChainedTo].ChainedId.Remove(SelectedId); } foreach (var linkedTo in etudeToRemove.LinkedId) { - loadedEtudes[linkedTo].LinkedTo = BlueprintGuid.Empty; + loadedEtudes[linkedTo].LinkedTo = string.Empty; } foreach (var chainedTo in etudeToRemove.ChainedId) { - loadedEtudes[chainedTo].ChainedTo = BlueprintGuid.Empty; + loadedEtudes[chainedTo].ChainedTo = string.Empty; } loadedEtudes.Remove(SelectedId); } private EtudeInfo PrepareNewEtudeData(BlueprintEtude blueprintEtude) { + if (blueprintEtude.Parent == null) + EtudesEditor.rootEtudeId = blueprintEtude.AssetGuid; var etudeInfo = new EtudeInfo { Name = blueprintEtude.name, Blueprint = blueprintEtude, - ParentId = blueprintEtude.Parent?.Get()?.AssetGuid ?? BlueprintGuid.Empty, - AllowActionStart = blueprintEtude.AllowActionStart, + ParentId = blueprintEtude.Parent?.Get()?.AssetGuid ?? string.Empty, + AllowActionStart = blueprintEtude.CanPlay(), CompleteParent = blueprintEtude.CompletesParent, Comment = blueprintEtude.Comment, Priority = blueprintEtude.Priority @@ -181,7 +176,7 @@ private EtudeInfo PrepareNewEtudeData(BlueprintEtude blueprintEtude) { etudeInfo.LinkedArea = blueprintEtude.LinkedAreaPart.AssetGuid; } - if (etudeInfo.ParentId != BlueprintGuid.Empty) { + if (etudeInfo.ParentId != string.Empty) { if (!loadedEtudes.ContainsKey(blueprintEtude.Parent.Get().AssetGuid)) AddEtudeToLoaded(blueprintEtude.Parent.Get()); @@ -206,8 +201,8 @@ private EtudeInfo PrepareNewEtudeData(BlueprintEtude blueprintEtude) { return etudeInfo; } - public List<BlueprintGuid> GetConflictingEtudes(BlueprintGuid etudeID) { - var result = new List<BlueprintGuid>(); + public List<string> GetConflictingEtudes(string etudeID) { + var result = new List<string>(); foreach (var conflictingGroup in loadedEtudes[etudeID].ConflictingGroups) { foreach (var etude in Instance.conflictingGroups[conflictingGroup].Etudes) { diff --git a/ToyBox/classes/MainUI/Etudes/EtudesEditor.cs b/ToyBox/Classes/MainUI/Etudes/EtudesEditor.cs similarity index 61% rename from ToyBox/classes/MainUI/Etudes/EtudesEditor.cs rename to ToyBox/Classes/MainUI/Etudes/EtudesEditor.cs index f1c637296..047bdcde1 100644 --- a/ToyBox/classes/MainUI/Etudes/EtudesEditor.cs +++ b/ToyBox/Classes/MainUI/Etudes/EtudesEditor.cs @@ -1,44 +1,47 @@ -using System; -using System.Collections.Generic; -using System.IO; +using Kingmaker; using Kingmaker.AreaLogic.Etudes; using Kingmaker.Blueprints; -using UnityEditor; -using UnityEngine; -using Application = UnityEngine.Application; -using System.Linq; using Kingmaker.Blueprints.Area; using Kingmaker.Blueprints.JsonSystem.EditorDatabase; -using ModKit; -using Kingmaker; -using Kingmaker.Designers.EventConditionActionSystem.Conditions; using Kingmaker.Designers.EventConditionActionSystem.Actions; +using Kingmaker.Designers.EventConditionActionSystem.Conditions; using Kingmaker.ElementsSystem; +using ModKit; +using ModKit.DataViewer; using ModKit.Utility; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Reflection; +using UnityEditor; +using UnityEngine; +using static ModKit.UI; +using Application = UnityEngine.Application; namespace ToyBox { public static class EtudesEditor { - private static BlueprintGuid parent; - private static BlueprintGuid selected; - private static Dictionary<BlueprintGuid, EtudeInfo> loadedEtudes => EtudesTreeModel.Instance.loadedEtudes; - private static Dictionary<BlueprintGuid, EtudeInfo> filteredEtudes = new(); - private static readonly BlueprintGuid rootEtudeId = BlueprintGuid.Parse("f0e6f6b732c40284ab3c103cad2455cc"); + private static string _parent; + private static string _selected; + private static Dictionary<string, EtudeInfo> loadedEtudes => EtudesTreeModel.Instance.loadedEtudes; + private static Dictionary<string, EtudeInfo> _filteredEtudes = new(); + + // TODO: is this still the right root etude? + internal static string rootEtudeId = + "4f66e8b792ecfad46ae1d9ecfd7ecbc2"; public static string searchText = ""; - public static string searrchTextInput = ""; - private static bool showOnlyFlagLikes; + public static string searchTextInput = ""; + private static bool _showOnlyFlagLikes; private static bool showComments => Main.Settings.showEtudeComments; - private static BlueprintEtude selectedEtude; - - private static List<BlueprintArea> areas; - private static BlueprintArea selectedArea; - private static string areaSearchText = ""; + private static List<BlueprintArea> _areas; + private static BlueprintArea _selectedArea; + private static string _areaSearchText = ""; //private EtudeChildrenDrawer etudeChildrenDrawer; - public static Dictionary<string, SimpleBlueprint> toValues = new(); - public static Dictionary<string, BlueprintAction> actionLookup = new(); + public static Dictionary<string?, SimpleBlueprint> toValues = new(); + public static Dictionary<string?, BlueprintAction> actionLookup = new(); public static void OnShowGUI() => UpdateEtudeStates(); public static int lineNumber = 0; public static Rect firstRect; @@ -57,26 +60,26 @@ public static void OnGUI() { if (loadedEtudes?.Count == 0) { ReloadEtudes(); } - if (areas == null) areas = BlueprintLoader.Shared.GetBlueprints<BlueprintArea>()?.OrderBy(a => a.name).ToList(); - if (areas == null) return; - if (parent == BlueprintGuid.Empty) { - parent = rootEtudeId; - selected = parent; + if ((_areas?.Count ?? 0) == 0) _areas = BlueprintLoader.Shared.GetBlueprintsOfType<BlueprintArea>()?.OrderBy(a => a.name).ToList(); + if ((_areas?.Count ?? 0) == 0) return; + if (_parent == string.Empty) { + _parent = rootEtudeId; + _selected = _parent; } - UI.Label("Note".orange().bold() + " this is a new and exciting feature that allows you to see for the first time the structure and some basic relationships of ".green() + "Etudes".cyan().bold() + " and other ".green() + "Elements".cyan().bold() + " that control the progression of your game story. Etudes are hierarchical in structure and additionally contain a set of ".green() + "Elements".cyan().bold() + " that can both conditions to check and actions to execute when the etude is started. As you browe you will notice there is a disclosure triangle next to the name which will show the children of the Etude. Etudes that have ".green() + "Elements".cyan().bold() + " will offer a second disclosure triangle next to the status that will show them to you.".green()); - UI.Label("WARNING".yellow().bold() + " this tool can both miraculously fix your broken progression or it can break it even further. Save and back up your save before using.".orange()); - using (UI.HorizontalScope(UI.ExpandWidth(true))) { - if (parent == BlueprintGuid.Empty) + Label((RichText.Bold(RichText.Orange("Note")) + RichText.Green(" this is a new and exciting feature that allows you to see for the first time the structure and some basic relationships of ") + RichText.Bold(RichText.Cyan("Etudes")) + RichText.Green(" and other ") + RichText.Bold(RichText.Cyan("Elements")) + RichText.Green(" that control the progression of your game story. Etudes are hierarchical in structure and additionally contain a set of ") + RichText.Bold(RichText.Cyan("Elements")) + RichText.Green(" that can both conditions to check and actions to execute when the etude is started. As you browe you will notice there is a disclosure triangle next to the name which will show the children of the Etude. Etudes that have ") + RichText.Bold(RichText.Cyan("Elements")) + RichText.Green(" will offer a second disclosure triangle next to the status that will show them to you.")).localize()); + Label((RichText.Bold(RichText.Yellow("WARNING")) + RichText.Orange(" this tool can both miraculously fix your broken progression or it can break it even further. Save and back up your save before using.")).localize()); + using (HorizontalScope(AutoWidth())) { + if (_parent == string.Empty) return; - UI.Label("Search"); - UI.Space(25); - UI.ActionTextField(ref searrchTextInput, "Search", (s) => { }, () => { searchText = searrchTextInput; UpdateSearchResults(); }, UI.Width(200)); - UI.Space(25); - if (UI.Toggle("Flags Only", ref showOnlyFlagLikes)) ApplyFilter(); + Label("Search".localize()); + Space(25); + ActionTextField(ref searchTextInput, "Search", (s) => { }, () => { searchText = searchTextInput; UpdateSearchResults(); }, 400.width()); + Space(25); + if (Toggle("Flags Only".localize(), ref _showOnlyFlagLikes)) ApplyFilter(); 25.space(); - UI.Toggle("Show GUIDs", ref Main.Settings.showAssetIDs); + Toggle("Show GUIDs".localize(), ref Main.Settings.showAssetIDs); 25.space(); - UI.Toggle("Show Comments (some in Russian)", ref Main.Settings.showEtudeComments); + Toggle("Show Comments (some in Russian)".localize(), ref Main.Settings.showEtudeComments); //UI.Label($"Etude Hierarchy : {(loadedEtudes.Count == 0 ? "" : loadedEtudes[parent].Name)}", UI.AutoWidth()); //UI.Label($"H : {(loadedEtudes.Count == 0 ? "" : loadedEtudes[selected].Name)}"); @@ -91,7 +94,7 @@ public static void OnGUI() { // etudeChildrenDrawer.ReferenceGraph.Save(); //} } - using (UI.HorizontalScope(GUI.skin.box, UI.AutoWidth())) { + using (HorizontalScope(GUI.skin.box, AutoWidth())) { //if (etudeChildrenDrawer != null) { // using (UI.VerticalScope(GUI.skin.box, UI.MinHeight(60), // UI.MinWidth(300))) { @@ -120,29 +123,29 @@ public static void OnGUI() { // } //} } - var remainingWidth = UI.ummWidth; - using (UI.HorizontalScope()) { - UI.Label(""); firstRect = GUILayoutUtility.GetLastRect(); - using (UI.VerticalScope(GUI.skin.box)) { - if (UI.VPicker<BlueprintArea>("Areas".orange().bold(), ref selectedArea, areas, "All", bp => { + var remainingWidth = ummWidth; + using (HorizontalScope()) { + Label(""); firstRect = GUILayoutUtility.GetLastRect(); + using (VerticalScope(GUI.skin.box)) { + if (VPicker(RichText.Bold(RichText.Orange("Areas".localize())), ref _selectedArea, _areas, "All".localize(), bp => { var name = bp.name; // bp.AreaDisplayName; if (name?.Length == 0) name = bp.AreaName; if (name?.Length == 0) name = bp.NameSafe(); return name; - }, ref areaSearchText, + }, ref _areaSearchText, () => { }, - UI.rarityButtonStyle, - UI.Width(300))) { + rarityButtonStyle, + Width(300))) { ApplyFilter(); } } remainingWidth -= 300; - using (UI.VerticalScope(GUI.skin.box)) { //, UI.Width(remainingWidth))) { + using (VerticalScope(GUI.skin.box)) { //, UI.Width(remainingWidth))) { //using (var scope = UI.ScrollViewScope(m_ScrollPos, GUI.skin.box)) { //UI.Label($"Hierarchy tree : {(loadedEtudes.Count == 0 ? "" : loadedEtudes[parent].Name)}", UI.MinHeight(50)); - if (filteredEtudes.Count == 0) { - UI.Label("No Etudes", UI.AutoWidth()); + if (_filteredEtudes.Count == 0) { + Label("No Etudes".localize(), AutoWidth()); //UI.ActionButton("Refresh", () => ReloadEtudes(), UI.AutoWidth()); return; } @@ -168,14 +171,10 @@ public static void OnGUI() { //etudeChildrenDrawer.OnGUI(); //} } -#if DEBUG - UI.ActionButton("Generate Comment Translation Table", () => { }); -#endif foreach (var item in toValues) { var mutator = actionLookup[item.Key]; if (mutator != null) - try { mutator.action(item.Value, null); } - catch (Exception e) { Mod.Error(e); } + try { mutator.action(item.Value, null); } catch (Exception e) { Mod.Error(e); } } if (toValues.Count > 0) { UpdateEtudeStates(); @@ -183,13 +182,13 @@ public static void OnGUI() { toValues.Clear(); } - private static HashSet<BlueprintGuid> enclosingEtudes = new(); - private static void DrawEtude(BlueprintGuid etudeID, EtudeInfo etude, int indent) { + private static HashSet<string> enclosingEtudes = new(); + private static void DrawEtude(string etudeID, EtudeInfo etude, int indent) { if (enclosingEtudes.Contains(etudeID)) return; - var viewPort = UI.ummRect; + var viewPort = ummRect; var topLines = firstRect.y / 30; var linesVisible = 1 + viewPort.height / 30; - var scrollOffset = UI.ummScrollPosition[0].y / 30 - topLines; + var scrollOffset = ummScrollPosition[0].y / 30 - topLines; var viewPortLine = lineNumber - scrollOffset; var isVisible = viewPortLine >= 0 && viewPortLine < linesVisible; #if false @@ -207,33 +206,33 @@ private static void DrawEtude(BlueprintGuid etudeID, EtudeInfo etude, int indent //}).ToList(); var conflicts = EtudesTreeModel.Instance.GetConflictingEtudes(etudeID); var conflictCount = conflicts.Count - 1; - using (UI.HorizontalScope(UI.ExpandWidth(true))) { - using (UI.HorizontalScope(UI.Width(310))) { + using (HorizontalScope(ExpandWidth(true))) { + using (HorizontalScope(Width(310))) { var actions = etude.Blueprint.GetActions().Where(action => action.canPerform(etude.Blueprint, null)); foreach (var action in actions) { actionLookup[action.name] = action; - UI.ActionButton(action.name, () => toValues[action.name] = etude.Blueprint, UI.Width(150)); + ActionButton(action.name, () => toValues[action.name] = etude.Blueprint, Width(150)); } } - UI.Indent(indent); + Indent(indent); var style = GUIStyle.none; style.fontStyle = FontStyle.Normal; - if (selected == etudeID) name = name.orange().bold(); + if (_selected == etudeID) name = RichText.Bold(RichText.Orange(name)); - using (UI.HorizontalScope(UI.Width(825))) { + using (HorizontalScope(Width(825))) { if (etude.ChildrenId.Count == 0) etude.ShowChildren = ToggleState.None; - UI.ToggleButton(ref etude.ShowChildren, name.orange().bold(), (state) => OpenCloseAllChildren(etude, state)); - UI.Space(25); + ToggleButton(ref etude.ShowChildren, RichText.Bold(RichText.Orange(name)), (state) => OpenCloseAllChildren(etude, state)); + Space(25); var eltCount = etude.Blueprint.m_AllElements.Count; if (eltCount > 0) - UI.ToggleButton(ref etude.ShowElements, $"{eltCount} elements", UI.Width(175)); + ToggleButton(ref etude.ShowElements, eltCount.ToString() + " " + "elements".localize(), Width(175)); else - UI.Space(178); + Space(178); //UI.Space(126); if (conflictCount > 0) - UI.ToggleButton(ref etude.ShowConflicts, $"{conflictCount} conflicts", UI.Width(175)); + ToggleButton(ref etude.ShowConflicts, conflictCount.ToString() + " " + "conflicts".localize(), Width(175)); else - UI.Space(178); + Space(178); //UI.Space(126); //if (gameActions.Count > 0) @@ -253,76 +252,78 @@ private static void DrawEtude(BlueprintGuid etudeID, EtudeInfo etude, int indent // selectedEtude = ResourcesLibrary.TryGetBlueprint<BlueprintEtude>(etudeID); //} - UI.Space(100); - UI.Label(etude.State.ToString().yellow(), UI.Width(125)); - UI.Space(-2); - UI.Space(25); - if (EtudeValidationProblem(etudeID, etude)) { - UI.Label("ValidationProblem".yellow(), UI.AutoWidth()); + Space(100); + Label(RichText.Yellow(etude.State.ToString()), Width(125)); + Space(-2); + Space(25); + if (EtudeValidationProblem(etudeID, etude) is { } reason) { + UI.Label(RichText.Yellow($"{RichText.Cyan(reason)}"), 300.width()); UI.Space(25); } - - if (etude.LinkedArea != BlueprintGuid.Empty) - UI.Label("🔗", UI.AutoWidth()); + Label("🔗", AutoWidth()); if (etude.CompleteParent) - UI.Label("⎌", UI.AutoWidth()); + Label("⎌", AutoWidth()); if (etude.AllowActionStart) { - UI.Space(25); - UI.Label("Can Start", UI.AutoWidth()); + Space(25); + Label("Can Start".localize(), 100.width()); } + ReflectionTreeView.DetailToggle("Inspect".localize(), etude, etude, 100); if (Main.Settings.showAssetIDs) { var guid = etudeID.ToString(); - UI.TextField(ref guid); + TextField(ref guid); } if (showComments && !Main.Settings.showAssetIDs && !string.IsNullOrEmpty(etude.Comment)) { - UI.Label(etude.Comment.green()); + Label(RichText.Green(etude.Comment), ExpandWidth(true)); } + Label("", AutoWidth()); } + ReflectionTreeView.OnDetailGUI(etude); if (showComments && Main.Settings.showAssetIDs && !string.IsNullOrEmpty(etude.Comment)) { - UI.Space(-15); - using (UI.HorizontalScope(UI.Width(10000))) { - UI.Space(310); - UI.Indent(indent); - UI.Space(933); - UI.Label(etude.Comment.green()); + Space(-15); + using (HorizontalScope(Width(200))) { + Space(310); + Indent(indent); + Space(933); + Label(RichText.Green(etude.Comment), ExpandWidth(true)); + Label("", AutoWidth()); } } indent += 2; if (etude.ShowElements.IsOn()) { - using (UI.HorizontalScope(UI.ExpandWidth(true))) { - UI.Space(310); - UI.Indent(indent); - using (UI.VerticalScope()) { + using (HorizontalScope(ExpandWidth(true))) { + Space(310); + Indent(indent); + using (VerticalScope()) { foreach (var element in etude.Blueprint.m_AllElements) { - using (UI.HorizontalScope(UI.Width(10000))) { + using (HorizontalScope(Width(10000))) { // UI.Label(element.NameSafe().orange()); -- this is useless at the moment - using (UI.HorizontalScope(450)) { + using (HorizontalScope(450)) { if (element is GameAction gameAction) { try { - UI.ActionButton(gameAction.GetCaption().yellow(), gameAction.RunAction); - } - catch (Exception e) { - Mod.Warn($"{gameAction.GetCaption()} failed to run {e.ToString().yellow()}"); + ActionButton(RichText.Yellow(gameAction.GetCaption()), gameAction.RunAction); + } catch (Exception e) { + Mod.Warn($"{gameAction.GetCaption()} failed to run {RichText.Yellow(e.ToString())}"); } - } - else - UI.Label(element.GetCaption().yellow() ?? "?"); - UI.Space(0); + } else + Label(RichText.Yellow(element.GetCaption()) ?? "?"); + Space(25); + ReflectionTreeView.DetailToggle("Inspect".localize(), element, element, 100); + Space(0); } - UI.Space(25); + Space(25); if (element is Condition condition) - UI.Label($"{element.GetType().Name.cyan()} : {condition.CheckCondition().ToString().orange()}", UI.Width(250)); + Label($"{RichText.Cyan(element.GetType().Name)} : {RichText.Orange(condition.CheckCondition().ToString())}", Width(250)); else if (element is Conditional conditional) - UI.Label($"{element.GetType().Name.cyan()} : {conditional.ConditionsChecker.Check().ToString().orange()} - {string.Join(", ", conditional.ConditionsChecker.Conditions.Select(c => c.GetCaption())).yellow()}", UI.Width(250)); + Label($"{RichText.Cyan(element.GetType().Name)} : {RichText.Orange(conditional.ConditionsChecker.Check().ToString())} - {RichText.Yellow(string.Join(", ", (IEnumerable<string>)conditional.ConditionsChecker.Conditions.Select(c => c.GetCaption())))}", Width(250)); else - UI.Label(element.GetType().Name.cyan(), UI.Width(250)); + Label(RichText.Cyan(element.GetType().Name), Width(250)); if (element is AnotherEtudeOfGroupIsPlaying otherGroup) - UI.Label($"{conflictCount}", UI.Width(50)); + Label($"{conflictCount}", Width(50)); else - UI.Width(53); - UI.Space(25); + Width(53); + Space(25); if (showComments) - UI.Label(element.GetDescription().green()); + Label(RichText.Green(element.GetDescription())); } if (element is StartEtude started) { @@ -334,17 +335,17 @@ private static void DrawEtude(BlueprintGuid etudeID, EtudeInfo etude, int indent if (element is CompleteEtude completed) { DrawEtudeTree(completed.Etude.Guid, 2, true); } - UI.Div(); + Div(); } } } } if (etude.ShowConflicts.IsOn()) { - using (UI.HorizontalScope(UI.Width(10000))) { - UI.Space(310); - UI.Indent(indent); - using (UI.VerticalScope()) { + using (HorizontalScope(Width(10000))) { + Space(310); + Indent(indent); + using (VerticalScope()) { foreach (var conflict in conflicts) { DrawEtudeTree(conflict, 2, true); } @@ -368,15 +369,15 @@ private static void DrawEtude(BlueprintGuid etudeID, EtudeInfo etude, int indent } } private static void ShowBlueprintsTree() { - using (UI.VerticalScope()) { + using (VerticalScope()) { DrawEtude(rootEtudeId, loadedEtudes[rootEtudeId], 0); - using (UI.VerticalScope(GUI.skin.box)) { + using (VerticalScope(GUI.skin.box)) { ShowParentTree(loadedEtudes[rootEtudeId], 1); } } } - private static void DrawEtudeTree(BlueprintGuid etudeID, int indent, bool ignoreFilter = false) { + private static void DrawEtudeTree(string etudeID, int indent, bool ignoreFilter = false) { var etude = loadedEtudes[etudeID]; DrawEtude(etudeID, etude, indent); @@ -386,19 +387,20 @@ private static void DrawEtudeTree(BlueprintGuid etudeID, int indent, bool ignore } private static void ShowParentTree(EtudeInfo etude, int indent, bool ignoreFilter = false) { foreach (var childID in etude.ChildrenId) { - if (!ignoreFilter && !filteredEtudes.ContainsKey(childID)) + if (!ignoreFilter && !_filteredEtudes.ContainsKey(childID)) continue; DrawEtudeTree(childID, indent, ignoreFilter); } } private static void UpdateSearchResults() { - var searchTextLower = searchText.ToLower(); foreach (var entry in loadedEtudes) entry.Value.hasSearchResults = false; if (searchText.Length != 0) { foreach (var entry in loadedEtudes) { var etude = entry.Value; - if (etude.Name.ToLower().Contains(searchTextLower)) { + if (etude.Name.Matches(searchText) + || etude.Blueprint.AssetGuid.ToString().Matches(searchText)) { + etude.hasSearchResults = true; etude.TraverseParents(e => e.hasSearchResults = true); } } @@ -406,21 +408,21 @@ private static void UpdateSearchResults() { } private static void ApplyFilter() { UpdateSearchResults(); - var etudesOfArea = new Dictionary<BlueprintGuid, EtudeInfo>(); + var etudesOfArea = new Dictionary<string, EtudeInfo>(); - filteredEtudes = loadedEtudes; + _filteredEtudes = loadedEtudes; - if (selectedArea != null) { + if (_selectedArea != null) { etudesOfArea = GetAreaEtudes(); - filteredEtudes = etudesOfArea; + _filteredEtudes = etudesOfArea; } - var flaglikeEtudes = new Dictionary<BlueprintGuid, EtudeInfo>(); + var flaglikeEtudes = new Dictionary<string, EtudeInfo>(); - if (showOnlyFlagLikes) { + if (_showOnlyFlagLikes) { flaglikeEtudes = GetFlaglikeEtudes(); - filteredEtudes = filteredEtudes.Keys.Intersect(flaglikeEtudes.Keys) - .ToDictionary(t => t, t => filteredEtudes[t]); + _filteredEtudes = _filteredEtudes.Keys.Intersect(flaglikeEtudes.Keys) + .ToDictionary(t => t, t => _filteredEtudes[t]); } } @@ -434,14 +436,14 @@ private static void ApplyFilter() { //} - private static Dictionary<BlueprintGuid, EtudeInfo> GetFlaglikeEtudes() { - var etudesFlaglike = new Dictionary<BlueprintGuid, EtudeInfo>(); + private static Dictionary<string, EtudeInfo> GetFlaglikeEtudes() { + var etudesFlaglike = new Dictionary<string, EtudeInfo>(); foreach (var etude in loadedEtudes) { - var flaglike = etude.Value.ChainedTo == BlueprintGuid.Empty && + var flaglike = etude.Value.ChainedTo == string.Empty && // (etude.Value.ChainedId.Count == 0) && - etude.Value.LinkedTo == BlueprintGuid.Empty && - etude.Value.LinkedArea == BlueprintGuid.Empty && !ParentHasArea(etude.Value); + etude.Value.LinkedTo == string.Empty && + etude.Value.LinkedArea == string.Empty && !ParentHasArea(etude.Value); if (flaglike) { etudesFlaglike.Add(etude.Key, etude.Value); @@ -453,21 +455,21 @@ private static Dictionary<BlueprintGuid, EtudeInfo> GetFlaglikeEtudes() { } public static bool ParentHasArea(EtudeInfo etude) { - if (etude.ParentId == BlueprintGuid.Empty) + if (etude.ParentId == string.Empty) return false; - if (loadedEtudes[etude.ParentId].LinkedArea == BlueprintGuid.Empty) { + if (loadedEtudes[etude.ParentId].LinkedArea == string.Empty) { return ParentHasArea(loadedEtudes[etude.ParentId]); } return true; } - private static Dictionary<BlueprintGuid, EtudeInfo> GetAreaEtudes() { - var etudesWithAreaLink = new Dictionary<BlueprintGuid, EtudeInfo>(); + private static Dictionary<string, EtudeInfo> GetAreaEtudes() { + var etudesWithAreaLink = new Dictionary<string, EtudeInfo>(); foreach (var etude in loadedEtudes) { - if (etude.Value.LinkedArea == selectedArea.AssetGuid) { + if (etude.Value.LinkedArea == _selectedArea.AssetGuid) { if (!etudesWithAreaLink.ContainsKey(etude.Key)) etudesWithAreaLink.Add(etude.Key, etude.Value); @@ -480,7 +482,7 @@ private static Dictionary<BlueprintGuid, EtudeInfo> GetAreaEtudes() { return etudesWithAreaLink; } - private static void AddChildsToDictionary(Dictionary<BlueprintGuid, EtudeInfo> dictionary, EtudeInfo etude) { + private static void AddChildsToDictionary(Dictionary<string, EtudeInfo> dictionary, EtudeInfo etude) { foreach (var children in etude.ChildrenId) { if (dictionary.ContainsKey(children)) continue; @@ -490,8 +492,8 @@ private static void AddChildsToDictionary(Dictionary<BlueprintGuid, EtudeInfo> d } } - private static void AddParentsToDictionary(Dictionary<BlueprintGuid, EtudeInfo> dictionary, EtudeInfo etude) { - if (etude.ParentId == BlueprintGuid.Empty) + private static void AddParentsToDictionary(Dictionary<string, EtudeInfo> dictionary, EtudeInfo etude) { + if (etude.ParentId == string.Empty) return; if (dictionary.ContainsKey(etude.ParentId)) @@ -519,27 +521,29 @@ private static void UpdateStateInRef(Etude etude, EtudeInfo etudeIdReferences) { if (etude.IsPlaying) { etudeIdReferences.State = EtudeInfo.EtudeState.Active; - } - else { + } else { etudeIdReferences.State = EtudeInfo.EtudeState.Started; } } - private static bool EtudeValidationProblem(BlueprintGuid etudeID, EtudeInfo etude) { - if (etude.ChainedTo != BlueprintGuid.Empty && etude.LinkedTo != BlueprintGuid.Empty) - return true; + // Not localizing this beacuse I doubt doing that is meaningful in any way. + private static string EtudeValidationProblem(string etudeID, EtudeInfo etude) { + if (etude.ChainedTo == string.Empty && etude.LinkedTo == string.Empty) + return "Chained/Linked to Nothing"; foreach (var chained in etude.ChainedId) { - if (loadedEtudes[chained].ParentId != etude.ParentId) - return true; + var chainedEtude = loadedEtudes[chained]; + if (chainedEtude.ParentId != etude.ParentId) + return $"Chained etude {chainedEtude.Name} ({chainedEtude.Blueprint.AssetGuid}) has different parent: {chainedEtude.ParentId} than {etude.Name} parent: {etude.ParentId}"; } foreach (var linked in etude.LinkedId) { - if (loadedEtudes[linked].ParentId != etude.ParentId && loadedEtudes[linked].ParentId != etudeID) - return true; + var linkedEtude = loadedEtudes[linked]; + if (linkedEtude.ParentId != etude.ParentId && loadedEtudes[linked].ParentId != etudeID) + return $"Linked to child {linkedEtude.Name} ({linkedEtude.Blueprint.AssetGuid}) with different parent: {linkedEtude.ParentId} than {etude.Name} parent {etude.ParentId}"; } - return false; + return null; } private static void UpdateEtudeStates() { if (Application.isPlaying) { @@ -547,7 +551,7 @@ private static void UpdateEtudeStates() { UpdateEtudeState(etude.Key, etude.Value); } } - public static void UpdateEtudeState(BlueprintGuid etudeID, EtudeInfo etude) { + public static void UpdateEtudeState(string etudeID, EtudeInfo etude) { var blueprintEtude = (BlueprintEtude)ResourcesLibrary.TryGetBlueprint(etudeID); var item = Game.Instance.Player.EtudesSystem.Etudes.GetFact(blueprintEtude); diff --git a/ToyBox/classes/MainUI/Etudes/ReferenceGraph.cs b/ToyBox/Classes/MainUI/Etudes/ReferenceGraph.cs similarity index 74% rename from ToyBox/classes/MainUI/Etudes/ReferenceGraph.cs rename to ToyBox/Classes/MainUI/Etudes/ReferenceGraph.cs index 967a5baf1..d6af961d3 100644 --- a/ToyBox/classes/MainUI/Etudes/ReferenceGraph.cs +++ b/ToyBox/Classes/MainUI/Etudes/ReferenceGraph.cs @@ -2,17 +2,14 @@ using System.Linq; namespace ToyBox { - public class ReferenceGraph - { - public enum ValidationStateType - { + public class ReferenceGraph { + public enum ValidationStateType { Normal, Warning, Error } - public class Entry - { + public class Entry { public string ObjectGuid; public string ObjectName; public string ObjectType; @@ -28,14 +25,12 @@ public class Entry public ValidationStateType ValidationState; } - public class SceneEntity - { + public class SceneEntity { public string GUID; public List<Ref> Refs = new List<Ref>(); } - public class Ref - { + public class Ref { public string AssetPath; public string AssetType; public string ReferencingObjectName; @@ -53,8 +48,7 @@ public string AssetGuid #endif } - public class EntityRef - { + public class EntityRef { public string AssetPath; public string AssetName; public string UsagesType; @@ -62,11 +56,5 @@ public class EntityRef public readonly List<Entry> Entries = new List<Entry>(); public readonly List<SceneEntity> SceneEntitys = new List<SceneEntity>(); - private List<string> m_ReferencingBlueprintPaths; - private List<string> m_ReferencingScenesPaths; - private Dictionary<string, Entry> m_EntriesByGuid; - private Dictionary<string, SceneEntity> m_SceneObjectRefs; - private readonly Dictionary<string, string> m_TypeNamesByGuid = new Dictionary<string, string>(); - } } diff --git a/ToyBox/Classes/MainUI/LevelUp.cs b/ToyBox/Classes/MainUI/LevelUp.cs new file mode 100644 index 000000000..f99cd5af2 --- /dev/null +++ b/ToyBox/Classes/MainUI/LevelUp.cs @@ -0,0 +1,49 @@ +using Kingmaker; +using Kingmaker.EntitySystem.Entities; +using ModKit; +using System; +using System.Linq; +using static ModKit.UI; + +namespace ToyBox { + public class LevelUp { + public static Settings Settings => Main.Settings; + public static void ResetGUI() { } + public static void OnGUI() { + Label("This area is under construction.\n".Yellow().Bold() + "As I play the game more it will get flushed out. For now you see some of the anticipated features along side ones that work".Orange()); + Div(0, 25); + HStack("Create & Level Up".localize(), 1, + () => { }, + () => { + if (Toggle("Respec from Level 0".localize(), ref Settings.toggleSetDefaultRespecLevelZero, 300.width())) { + Settings.toggleSetDefaultRespecLevelFifteen &= !Settings.toggleSetDefaultRespecLevelZero; + Settings.toggleSetDefaultRespecLevelThirtyfive &= !Settings.toggleSetDefaultRespecLevelZero; + } + Label("This allows rechosing the first arcehtype. Also makes Companion respec start from level 0.".Green().localize()); + }, + () => { + if (Toggle("Respec from Level 15".localize(), ref Settings.toggleSetDefaultRespecLevelFifteen, 300.width())) { + Settings.toggleSetDefaultRespecLevelZero &= !Settings.toggleSetDefaultRespecLevelFifteen; + Settings.toggleSetDefaultRespecLevelThirtyfive &= !Settings.toggleSetDefaultRespecLevelFifteen; + } + Label("This allows rechosing the second archetype.".Green()); + }, + () => { + if (Toggle("Respec from Level 35".localize(), ref Settings.toggleSetDefaultRespecLevelThirtyfive, 300.width())) { + Settings.toggleSetDefaultRespecLevelZero &= !Settings.toggleSetDefaultRespecLevelThirtyfive; + Settings.toggleSetDefaultRespecLevelFifteen &= !Settings.toggleSetDefaultRespecLevelThirtyfive; + } + Label("This allows rechosing the third archetype.".Green()); + }, + () => { + Toggle("Ignore Archetypes Prerequisites".localize(), ref Settings.toggleIgnoreCareerPrerequisites, 300.width()); + Label("Slightly Buggy UI. This allows picking any one career per stage regardless of prerequisites.".Green().localize() + " Warning: Picking e.g. Exemplar as second archetype will cause issues because you won't have a second archetype ability to upgrade.".localize().Yellow().Bold()); + }, + () => Toggle("Ignore Talent Prerequisites".localize(), ref Settings.toggleFeaturesIgnorePrerequisites), + () => Toggle("Ignore Required Stat Values".localize(), ref Settings.toggleIgnorePrerequisiteStatValue), + () => Toggle("Ignore Required Class Levels".localize(), ref Settings.toggleIgnorePrerequisiteClassLevel), + () => { } + ); + } + } +} diff --git a/ToyBox/Classes/MainUI/Main.cs b/ToyBox/Classes/MainUI/Main.cs new file mode 100644 index 000000000..192081234 --- /dev/null +++ b/ToyBox/Classes/MainUI/Main.cs @@ -0,0 +1,389 @@ +// Copyright < 2021 > Narria (github user Cabarius) - License: MIT +// Special thanks to @SpaceHampster and @Velk17 from Pathfinder: Wrath of the Rightous Discord server for teaching me how to mod Unity games +using HarmonyLib; +using Kingmaker; +using Kingmaker.AI.BehaviourTrees.Nodes; +using Kingmaker.GameInfo; +using Kingmaker.GameModes; +using Kingmaker.Localization; +using Kingmaker.UI.Common; +using Kingmaker.UI.Models.Log.CombatLog_ThreadSystem; +using Kingmaker.UI.Models.Log.CombatLog_ThreadSystem.LogThreads.Common; +using Kingmaker.UI.Models.Log.CombatLog_ThreadSystem.LogThreads.LifeEvents; +using Kingmaker.UI.Models.Log.Enums; +using Kingmaker.UI.Models.Log.GameLogCntxt; +using ModKit; +using ModKit.DataViewer; +using Newtonsoft.Json; +using Owlcat.Runtime.Core.Logging; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Reflection.Emit; +using System.Security.Policy; +using System.Threading; +using ToyBox.classes.Infrastructure; +using ToyBox.classes.MainUI; +using ToyBox.classes.MonkeyPatchin; +using ToyBox.PatchTool; +using UniRx; +using UnityEngine; +using UnityModManagerNet; +using LocalizationManager = ModKit.LocalizationManager; + +namespace ToyBox { +#if DEBUG + [EnableReloading] +#endif + internal static class Main { + private static Thread UnityThread; + public static bool IsOnUnityThread => Thread.CurrentThread == UnityThread; + internal const string LinkToIncompatibilitiesFile = "https://raw.githubusercontent.com/xADDBx/ToyBox-RogueTrader/main/ToyBox/ModDetails/Incompatibilities.json"; + internal static Harmony HarmonyInstance; + internal static UnityModManager.ModEntry ModEntry; + public static readonly LogChannel logger = LogChannelFactory.GetOrCreate("Respec"); + public static Settings Settings; + public static NamedAction[] tabs = { + new NamedAction("Bag of Tricks", BagOfTricks.OnGUI), + new NamedAction("Enhanced UI", EnhancedUI.OnGUI), + new NamedAction("Level Up", LevelUp.OnGUI), + new NamedAction("Party", PartyEditor.OnGUI), + new NamedAction("Loot", PhatLoot.OnGUI), +#if false + new NamedAction("Enchantment", EnchantmentEditor.OnGUI), + new NamedAction("Playground", () => Playground.OnGUI()), +#endif + new NamedAction("Search 'n Pick", SearchAndPick.OnGUI), + new NamedAction("Colonies", ColonyEditor.OnGUI), + new NamedAction("Etudes", EtudesEditor.OnGUI), + new NamedAction("Quests", QuestEditor.OnGUI), + new NamedAction("Dialog & NPCs", DialogAndNPCs.OnGUI), + new NamedAction("Saves", GameSavesBrowser.OnGUI), + new NamedAction("Achievements", AchievementsUnlocker.OnGUI), + new NamedAction("Patch Tool", PatchToolUIManager.OnGUI), + new NamedAction("Settings", SettingsUI.OnGUI) + }; + private static int partyTabID = -1; + public static bool Enabled; + public static bool IsModGUIShown = false; + public static bool freshlyLaunched = true; + public static bool NeedsActionInit = true; + private static bool _needsResetGameUI = false; + private static bool _resetRequested = false; + private static DateTime _resetRequestTime = DateTime.Now; + public static bool resetExtraCameraAngles = false; + internal static string path; + public static void SetNeedsResetGameUI() { + _resetRequested = true; + _resetRequestTime = DateTime.Now; + Mod.Debug($"resetRequested - {_resetRequestTime}"); + } + public static bool IsInGame => Game.Instance.Player?.Party.Any() ?? false; + private static Exception _caughtException = null; + + public static List<GameObject> Objects; + private static void FindPatchesWithoutGuardClause() { + return; + foreach (var type in typeof(Main).Assembly.GetTypes()) { + if (type.GetCustomAttributes<HarmonyPatch>().FirstOrDefault() != null) { + foreach (var method in type.GetMethods()) { + System.Attribute CustomAttribute = method.GetCustomAttributes<HarmonyPrefix>().FirstOrDefault(); + CustomAttribute ??= method.GetCustomAttributes<HarmonyPostfix>().FirstOrDefault(); + CustomAttribute ??= method.GetCustomAttributes<HarmonyFinalizer>().FirstOrDefault(); + CustomAttribute ??= method.GetCustomAttributes<HarmonyTranspiler>().FirstOrDefault(); + List<string> names = ["Prefix", "Postfix", "Transpiler", "Finalizer"]; + if (CustomAttribute != null || names.Contains(method.Name)) { + var foundSettings = false; + foreach (var inst in PatchProcessor.GetCurrentInstructions(method)) { + if (inst.opcode == OpCodes.Ldfld + && (inst.operand is FieldInfo field && field.DeclaringType == typeof(Settings))) { + foundSettings = true; + break; + } + } + if (!foundSettings) { + Mod.Log($"Found unconditional patch method: {type.Name}.{method.Name}"); + } + } + } + } + } + } + private static bool Load(UnityModManager.ModEntry modEntry) { + try { + Settings = UnityModManager.ModSettings.Load<Settings>(modEntry); + + if (Settings.toggleIntegrityCheck) { + modEntry.Logger.Log("Starting Integrity Check."); + if (IntegrityChecker.Check(modEntry.Logger)) { + modEntry.Logger.Log("Integrity Check succeeded."); + } else { + modEntry.Info.DisplayName = "ToyBox" + " Checksum verification failed!".localize().Yellow().Bold().SizePercent(80) + "\nMod files are likely corrupted...".localize().Yellow().Bold().SizePercent(50); + if (Settings.updateOnChecksumFail) { + if (Updater.Update(modEntry, true, true)) { + modEntry.Info.DisplayName = "ToyBox" + " Restart the game to finish the update!".localize().Green().Bold().SizePercent(80); + return false; + } + } + if (Settings.disableOnChecksumFail) { + modEntry.Info.DisplayName = "ToyBox" + " Checksum verification failed!".localize().Red().Bold().SizePercent(80); + return false; + } + } + } + + if (Settings.toggleVersionCompatability) { + if (VersionChecker.IsGameVersionSupported(modEntry.Version, modEntry.Logger, LinkToIncompatibilitiesFile)) { + modEntry.Logger.Log("Compatability Check succeeded"); + } else { + modEntry.Logger.Log("Fatal! The current Game Version has known incompatabilities with your current ToyBox version! Please Update."); + if (Settings.shouldTryUpdate) { + modEntry.Info.DisplayName = "ToyBox" + " Trying to update the mod...".localize().Red().Bold().SizePercent(80); + if (Updater.Update(modEntry, true)) { + modEntry.Info.DisplayName = "ToyBox" + " Restart the game to finish the update!".localize().Green().Bold().SizePercent(80); + return false; + } + } + modEntry.Info.DisplayName = "ToyBox" + " Update the mod manually!".localize().Red().Bold().SizePercent(100); + return false; + } + } + + if (Settings.toggleAlwaysUpdate) { + modEntry.Logger.Log("Auto Updater enabled, trying to update..."); + if (Updater.Update(modEntry)) { + modEntry.Info.DisplayName = "ToyBox" + " Restart the game to finish the update!".localize().Green().Bold().SizePercent(40); + } + } + Main.ModEntry = modEntry; +#if DEBUG + modEntry.OnUnload = OnUnload; + _modId = modEntry.Info.Id; +#endif + + Mod.OnLoad(modEntry); + path = modEntry.Path; + SettingsDefaults.InitializeDefaultDamageTypes(); + EventBus.Subscribe(new HighlightObjectToggle()); + _ = SharedStringAssetPool.Instance; + + HarmonyInstance = new Harmony(modEntry.Info.Id); + HarmonyInstance.PatchAll(Assembly.GetExecutingAssembly()); + + LocalizationManager.Enable(); + + modEntry.OnToggle = OnToggle; + modEntry.OnShowGUI = OnShowGUI; + modEntry.OnHideGUI = OnHideGUI; + modEntry.OnGUI = OnGUI; + modEntry.OnUpdate = OnUpdate; + modEntry.OnSaveGUI = OnSaveGUI; + Objects = new List<GameObject>(); + KeyBindings.OnLoad(modEntry); + HumanFriendlyStats.EnsureFriendlyTypesContainAll(); + Mod.logLevel = Settings.loggingLevel; + Mod.InGameTranscriptLogger = text => { + Mod.Log("CombatLog - " + text); + var messageText = "ToyBox".Blue() + " - " + text; + var message = new CombatLogMessage(messageText, Color.black, PrefixIcon.RightArrow); + var messageLog = LogThreadService.Instance.m_Logs[LogChannelType.Dialog].FirstOrDefault(x => x is DialogLogThread); + using (GameLogContext.Scope) { + messageLog?.AddMessage(message); + } + }; + RogueCheats.PatchPsychicTranspiler(!Settings.customizePsychicPhenomena); + FindPatchesWithoutGuardClause(); + } catch (Exception e) { + Mod.Error(e.ToString()); + HarmonyInstance?.UnpatchAll(modEntry.Info.Id); + throw; + } + return true; + } +#if DEBUG + internal static string _modId; + private static bool OnUnload(UnityModManager.ModEntry modEntry) { + foreach (var obj in Objects) { + UnityEngine.Object.DestroyImmediate(obj); + } + BlueprintExtensions.ResetCollationCache(); + HarmonyInstance.UnpatchAll(_modId); + EnhancedInventory.OnUnload(); + NeedsActionInit = true; + return true; + } +#endif + private static bool OnToggle(UnityModManager.ModEntry modEntry, bool value) { + Enabled = value; + return true; + } + + private static void ResetGUI(UnityModManager.ModEntry modEntry) { + Settings = UnityModManager.ModSettings.Load<Settings>(modEntry); + Settings.searchText = ""; + Settings.searchLimit = 100; + Mod.ModKitSettings.browserSearchLimit = 25; + ModKitSettings.Save(); + BagOfTricks.ResetGUI(); + EnhancedCamera.ResetGUI(); + LevelUp.ResetGUI(); + PartyEditor.ResetGUI(); + CharacterPicker.ResetGUI(); + SearchAndPick.ResetGUI(); + QuestEditor.ResetGUI(); + BlueprintExtensions.ResetCollationCache(); + _caughtException = null; + } + private static bool IsFirstOnGUI = true; + private static void OnGUI(UnityModManager.ModEntry modEntry) { + if (IsFirstOnGUI) { + UnityThread = Thread.CurrentThread; + IsFirstOnGUI = false; + Glyphs.CheckGlyphSupport(); + } + if (!Enabled) return; + IsModGUIShown = true; + if (Settings.hasSeenUpdatePage) { + if (!IsInGame) { + Label("ToyBox has limited functionality from the main menu".localize().Yellow().Bold()); + } + if (!IsWide) { + using (HorizontalScope()) { + ActionButton("Maximize Window".localize(), Actions.MaximizeModWindow); + Label(("Note ".Magenta().Bold() + "ToyBox was designed to offer the best user experience at widths of 1920 or higher. Please consider increasing your resolution up of at least 1920x1080 (ideally 4k) and go to Unity Mod Manager 'Settings' tab to change the mod window width to at least 1920. Increasing the UI scale is nice too when running at 4k".Orange().Bold()).localize()); + } + } + try { + var e = Event.current; + userHasHitReturn = e.keyCode == KeyCode.Return; + focusedControlName = GUI.GetNameOfFocusedControl(); + if (_caughtException != null) { + Label("ERROR".Red().Bold() + $": caught exception {_caughtException}"); + ActionButton("Reset".Orange().Bold(), () => { ResetGUI(modEntry); }, AutoWidth()); + return; + } +#if false + using (UI.HorizontalScope()) { + UI.Label("Suggestions or issues click ".green(), UI.AutoWidth()); + UI.LinkButton("here", "https://github.com/cabarius/ToyBox/issues"); + UI.Space(50); + UI.Label("Chat with the Authors, Narria et all on the ".green(), UI.AutoWidth()); + UI.LinkButton("WoTR Discord", "https://discord.gg/wotr"); + } +#endif + TabBar(ref Settings.selectedTab, + () => { + if (BlueprintLoader.Shared.IsLoading) { + Label("Blueprints".Orange().Bold() + " loading: " + BlueprintLoader.Shared.progress.ToString("P2").Cyan().Bold()); + } else Space(25); + }, + (oldTab, newTab) => { + if (partyTabID == -1) { + for (int i = 0; i < tabs.Length; i++) { + if (tabs[i].action == PartyEditor.OnGUI) { + partyTabID = i; + break; + } + } + } + if (partyTabID != -1) { + if (oldTab == partyTabID) { + PartyEditor.UnloadPortraits(); + } + } + }, + s => s.localize(), + tabs + ); + } catch (Exception e) { + Console.Write($"{e}"); + _caughtException = e; + ReflectionSearch.Shared.Stop(); + } + } else { + Label("This mod will automatically conntect to the internet for various tasks. Here are the respective options (in the future found in the Settings tab).".localize().Green().Bold(), AutoWidth()); + SettingsUI.UpdateAndVerificationGUI(); + Label(""); + bool shouldChange = false; + Button("I understand".localize().Green().Bold(), ref shouldChange); + Settings.hasSeenUpdatePage = shouldChange; + } + } + + private static void OnSaveGUI(UnityModManager.ModEntry modEntry) { + Settings.Save(modEntry); + ModKitSettings.Save(); + } + private static void OnShowGUI(UnityModManager.ModEntry modEntry) { + IsModGUIShown = true; + EnchantmentEditor.OnShowGUI(); + AchievementsUnlocker.OnShowGUI(); + EtudesEditor.OnShowGUI(); + Mod.OnShowGUI(); + } + + private static void OnHideGUI(UnityModManager.ModEntry modEntry) { + IsModGUIShown = false; + PartyEditor.UnloadPortraits(); + BlueprintExtensions.descriptionCache.Clear(); + BlueprintExtensions.titleCache.Clear(); + BlueprintExtensions.sortKeyCache.Clear(); + BlueprintExtensions.searchKeyCache.Clear(); + OwlLogging.OnHideGUI(); + } + private static IEnumerator ResetGUI() { + _needsResetGameUI = false; + Game.ResetUI(); + Mod.InGameTranscriptLogger?.Invoke("ResetUI"); + yield return null; + } + private static void OnUpdate(UnityModManager.ModEntry modEntry, float z) { + if (Game.Instance?.Player != null) { + } + Mod.logLevel = Settings.loggingLevel; + if (NeedsActionInit) { + EnhancedCamera.OnLoad(); + BagOfTricks.OnLoad(); + PhatLoot.OnLoad(); + EnhancedInventory.OnLoad(); + NeedsActionInit = false; + } + //if (resetExtraCameraAngles) { + // Game.Instance.UI.GetCameraRig().TickRotate(); // Kludge - TODO: do something better... + //} + if (_resetRequested) { + var timeSinceRequest = DateTime.Now.Subtract(_resetRequestTime).TotalMilliseconds; + //Main.Log($"timeSinceRequest - {timeSinceRequest}"); + if (timeSinceRequest > 1000) { + Mod.Debug($"resetExecuted - {timeSinceRequest}".Cyan()); + _needsResetGameUI = true; + _resetRequested = false; + } + } + if (_needsResetGameUI) { + MainThreadDispatcher.StartCoroutine(ResetGUI()); + } + var currentMode = Game.Instance.CurrentMode; + if (IsModGUIShown || Event.current == null || !Event.current.isKey) return; + KeyBindings.OnUpdate(); + if (IsInGame + && Settings.toggleTeleportKeysEnabled + && (currentMode == GameModeType.Default + || currentMode == GameModeType.Pause + || currentMode == GameModeType.GlobalMap + ) + ) { + if (KeyBindings.IsActive("TeleportMain")) + Teleport.TeleportUnit(Shodan.MainCharacter, Utils.PointerPosition()); + if (KeyBindings.IsActive("TeleportSelected")) + Teleport.TeleportSelected(); + if (KeyBindings.IsActive("TeleportParty")) + Teleport.TeleportParty(); + } + } + } +} \ No newline at end of file diff --git a/ToyBox/Classes/MainUI/PartyEditor/CareersEditor.cs b/ToyBox/Classes/MainUI/PartyEditor/CareersEditor.cs new file mode 100644 index 000000000..6c36324b5 --- /dev/null +++ b/ToyBox/Classes/MainUI/PartyEditor/CareersEditor.cs @@ -0,0 +1,138 @@ +using Kingmaker; +using Kingmaker.Blueprints; +using Kingmaker.Blueprints.Classes; +using Kingmaker.Designers; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.EntitySystem.Stats; +using Kingmaker.PubSubSystem; +using Kingmaker.UnitLogic; +using Kingmaker.UnitLogic.FactLogic; +using Kingmaker.UnitLogic.Parts; +using Kingmaker.UnitLogic.Progression.Paths; +using ModKit; +using ModKit.Utility; +using System; +using System.Collections.Generic; +using System.Linq; +using ToyBox.classes.Infrastructure; +using UnityEngine; +using static Kingmaker.Utility.UnitDescription.UnitDescription; +using static ModKit.UI; +using Alignment = Kingmaker.Enums.Alignment; + +namespace ToyBox { + public partial class PartyEditor { + public static void OnClassesGUI(BaseUnitEntity ch, List<(BlueprintCareerPath path, int level)> careerPaths, BaseUnitEntity selectedCharacter) { + using (HorizontalScope()) { + } + Div(100, 20); + + if (editMultiClass) { + } else { + var prog = ch.Descriptor().Progression; + using (HorizontalScope()) { + using (HorizontalScope(Width(600))) { + Space(100); + if (prog.Owner.Master != null) { + Label(RichText.Orange("Can't modify classes of units with a master!".localize().Bold())); + return; + } + Label(RichText.Cyan("Character Level".localize()), Width(250)); + if ( prog.CharacterLevel > 1) { + ActionButton("<", () => prog.m_CharacterLevel = Math.Max(0, prog.CharacterLevel - 1), AutoWidth()); + } + Space(25); + Label(RichText.Green("level".localize()) + $": {prog.CharacterLevel}", Width(100f)); + if ( prog.CharacterLevel < 55) { + ActionButton(">", () => prog.m_CharacterLevel = Math.Min(55, prog.CharacterLevel + 1), AutoWidth()); + } + } + ActionButton("Reset".localize(), ch.resetClassLevel, Width(150)); + Space(23); + using (VerticalScope()) { + Label(RichText.Green("This directly changes your character level but will not change exp or adjust any features associated with your character. To do a normal level up use +1 Lvl above. This gets recalculated when you reload the game. ".localize())); + } + } + using (HorizontalScope()) { + using (HorizontalScope(Width(600))) { + Space(100); + Label(RichText.Cyan("Experience".localize()), Width(250)); + Space(25); + int tmpExp = prog.Experience; + IntTextField(ref tmpExp, null, Width(150f)); + prog.Experience = tmpExp; + } + } + using (HorizontalScope()) { + using (HorizontalScope(Width(781))) { + Space(100); + ActionButton("Adjust based on Level".localize(), () => { + var xpTable = prog.ExperienceTable; + prog.Experience = xpTable.GetBonus(prog.CharacterLevel); + }, AutoWidth()); + Space(27); + } + Label(RichText.Green("This sets your experience to match the current value of character level".localize())); + } +#if false + Div(100, 25); + using (HorizontalScope()) { + using (HorizontalScope(Width(600))) { + Space(100); + Label("Mythic Level".cyan(), Width(250)); + ActionButton("<", () => prog.MythicLevel = Math.Max(0, prog.MythicLevel - 1), AutoWidth()); + Space(25); + Label("my lvl".green() + $": {prog.MythicLevel}", Width(100f)); + ActionButton(">", () => prog.MythicLevel = Math.Min(10, prog.MythicLevel + 1), AutoWidth()); + } + Space(181); + Label("This directly changes your mythic level but will not adjust any features associated with your character. To do a normal mythic level up use +1 my above".green()); + } + + using (HorizontalScope()) { + using (HorizontalScope(Width(600))) { + Space(100); + Label("Experience".cyan(), Width(250)); + Space(25); + int tmpMythicExp = prog.MythicExperience; + IntTextField(ref tmpMythicExp, null, Width(150f)); + if (0 <= tmpMythicExp && tmpMythicExp <= 10) { + prog.MythicExperience = tmpMythicExp; + } // If Mythic experience is 0, entering any number besides 1 is > 10, meaning the number would be overwritten with; this is to prevent that + else if (tmpMythicExp % 10 == 0) { + prog.MythicExperience = tmpMythicExp / 10; + } + } + } + using (HorizontalScope()) { + using (HorizontalScope(Width(781))) { + Space(100); + ActionButton("Adjust based on Level", () => { + prog.MythicExperience = prog.MythicLevel; + }, AutoWidth()); + Space(27); + } + Label("This sets your mythic experience to match the current value of mythic level. Note that mythic experience is 1 point per level".green()); + } +#endif + var classCount = careerPaths.Count(); + foreach (var cd in careerPaths) { + Div(100, 20); + using (HorizontalScope()) { + Space(100); + using (VerticalScope(Width(250))) { + var className = cd.path.Name; + Label(RichText.Orange(className), Width(250)); + } + // ActionButton("<", () => cd.level = Math.Max(0, cd.level - 1), AutoWidth()); + Space(25); + Label(RichText.Green("level".localize()) + $": {cd.level}", Width(100f)); + //var maxLevel = 20; + //ActionButton(">", () => cd.level = Math.Min(maxLevel, cd.level + 1), AutoWidth()); + Space(23); + } + } + } + } + } +} \ No newline at end of file diff --git a/ToyBox/classes/MainUI/PartyEditor/FeaturesTreeEditor.cs b/ToyBox/Classes/MainUI/PartyEditor/FeaturesTreeEditor.cs similarity index 65% rename from ToyBox/classes/MainUI/PartyEditor/FeaturesTreeEditor.cs rename to ToyBox/Classes/MainUI/PartyEditor/FeaturesTreeEditor.cs index 904b0d2f0..0a2e3ea15 100644 --- a/ToyBox/classes/MainUI/PartyEditor/FeaturesTreeEditor.cs +++ b/ToyBox/Classes/MainUI/PartyEditor/FeaturesTreeEditor.cs @@ -1,7 +1,6 @@ using Kingmaker; using Kingmaker.Blueprints; using Kingmaker.Blueprints.Classes; -using Kingmaker.Blueprints.Classes.Selection; using Kingmaker.EntitySystem.Entities; using Kingmaker.UnitLogic; using ModKit; @@ -14,7 +13,7 @@ namespace ToyBox { public class FeaturesTreeEditor { - private UnitEntityData _selectedCharacter = null; + private BaseUnitEntity _selectedCharacter = null; private FeaturesTree _featuresTree; private GUIStyle _buttonStyle; @@ -23,11 +22,11 @@ public class FeaturesTreeEditor { public int Priority => 500; - public void OnGUI(UnitEntityData character, bool refresh) { + public void OnGUI(BaseUnitEntity character, bool refresh) { if (!Main.IsInGame) return; var activeScene = SceneManager.GetActiveScene().name; if (Game.Instance?.Player == null || activeScene == "MainMenu" || activeScene == "Start") { - UI.Label(" * Please start or load the game first.".color(RGBA.yellow)); + UI.Label(" * Please start or load the game first.".localize().Color(RGBA.yellow)); return; } if (_buttonStyle == null) @@ -36,7 +35,7 @@ public void OnGUI(UnitEntityData character, bool refresh) { try { if (character != _selectedCharacter || refresh) { _selectedCharacter = character; - _featuresTree = new FeaturesTree(_selectedCharacter.Descriptor.Progression); ; + _featuresTree = new FeaturesTree(_selectedCharacter.Descriptor().Progression); } using (UI.HorizontalScope()) { // features tree @@ -47,9 +46,11 @@ public void OnGUI(UnitEntityData character, bool refresh) { // draw tool bar using (UI.HorizontalScope()) { - UI.ActionButton("Refresh", () => _featuresTree = new FeaturesTree(_selectedCharacter.Descriptor.Progression), UI.Width(200)); - UI.Button("Expand All", ref expandAll, UI.Width(200)); - UI.Button("Collapse All", ref collapseAll, UI.Width(200)); + UI.ActionButton("Refresh".localize(), () => _featuresTree = + new FeaturesTree(_selectedCharacter + .Progression), UI.Width(200)); + UI.Button("Expand All".localize(), ref expandAll, UI.Width(200)); + UI.Button("Collapse All".localize(), ref collapseAll, UI.Width(200)); } UI.Space(10f); @@ -62,15 +63,14 @@ public void OnGUI(UnitEntityData character, bool refresh) { void draw(FeaturesTree.FeatureNode node) { using (UI.HorizontalScope()) { var levelText = node.Level == 0 ? "" : $" {node.Level} - "; - var blueprintName = $"[{node.Blueprint.name}]".color(node.IsMissing ? RGBA.maroon : RGBA.aqua); + var blueprintName = $"[{node.Blueprint.name}]".Color(node.IsMissing ? RGBA.maroon : RGBA.aqua); var titleText = $"{levelText}{node.Name.Bold()} {blueprintName}"; if (node.ChildNodes.Count > 0) { if (node.Expanded == ToggleState.None) { node.Expanded = ToggleState.Off; } node.Expanded = expandAll ? ToggleState.On : collapseAll ? ToggleState.Off : node.Expanded; - } - else { + } else { node.Expanded = ToggleState.None; } Mod.Trace($"{node.Expanded} {titleText}"); @@ -80,27 +80,25 @@ void draw(FeaturesTree.FeatureNode node) { foreach (var child in node.ChildNodes.OrderBy(n => n.Level)) draw(child); } - } - else { + } else { GUILayout.FlexibleSpace(); } } } } } - } - catch (Exception e) { + } catch (Exception e) { _selectedCharacter = null; _featuresTree = null; Mod.Error(e); - throw e; + throw; } } private class FeaturesTree { public readonly List<FeatureNode> RootNodes = new(); - public FeaturesTree(UnitProgressionData progression) { + public FeaturesTree(PartUnitProgression progression) { Dictionary<BlueprintScriptableObject, FeatureNode> normalNodes = new(); List<FeatureNode> parametrizedNodes = new(); @@ -111,53 +109,21 @@ public FeaturesTree(UnitProgressionData progression) { if (name == null || name.Length == 0) name = feature.Blueprint.name; //Main.Log($"feature: {name}"); - var source = feature.m_Source; - //Main.Log($"source: {source}"); - if (feature.Blueprint is BlueprintParametrizedFeature) - parametrizedNodes.Add(new FeatureNode(name, feature.SourceLevel, feature.Blueprint, source)); - else - normalNodes.Add(feature.Blueprint, new FeatureNode(name, feature.SourceLevel, feature.Blueprint, source)); } // get nodes (classes) - foreach (var characterClass in progression.Classes.Select(item => item.CharacterClass)) { + foreach (var characterClass in progression.AllCareerPaths.Select(item => item.Blueprint)) { normalNodes.Add(characterClass, new FeatureNode(characterClass.Name, 0, characterClass, null)); } - // set source selection - var selectionNodes = normalNodes.Values - .Where(item => item.Blueprint is BlueprintFeatureSelection).ToList(); - for (var level = 0; level <= 100; level++) { - foreach (var selection in selectionNodes) { - foreach (var feature in progression.GetSelections(selection.Blueprint as BlueprintFeatureSelection, level)) { - FeatureNode node = default; - if (feature is BlueprintParametrizedFeature) { - node = parametrizedNodes - .FirstOrDefault(item => item.Source != null && item.Source == selection.Source); - } - - if (node != null || normalNodes.TryGetValue(feature, out node)) { - node.Source = selection.Blueprint; - node.Level = level; - } - else { - // missing child - normalNodes.Add(feature, - new FeatureNode(string.Empty, level, feature, selection.Blueprint) { IsMissing = true }); - } - } - } - } // build tree foreach (var node in normalNodes.Values.Concat(parametrizedNodes).ToList()) { if (node.Source == null) { RootNodes.Add(node); - } - else if (normalNodes.TryGetValue(node.Source, out var parent)) { + } else if (normalNodes.TryGetValue(node.Source, out var parent)) { parent.ChildNodes.Add(node); - } - else { + } else { // missing parent parent = new FeatureNode(string.Empty, 0, node.Source, null) { IsMissing = true }; parent.ChildNodes.Add(node); diff --git a/ToyBox/Classes/MainUI/PartyEditor/HumanFriendlyStats.cs b/ToyBox/Classes/MainUI/PartyEditor/HumanFriendlyStats.cs new file mode 100644 index 000000000..da4c79adc --- /dev/null +++ b/ToyBox/Classes/MainUI/PartyEditor/HumanFriendlyStats.cs @@ -0,0 +1,45 @@ +using Kingmaker.EntitySystem.Stats; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace ToyBox.classes.Infrastructure { + public static class HumanFriendlyStats { + public static void EnsureFriendlyTypesContainAll() { + if (Enum.GetValues(typeof(StatType)).Length != StatTypes.Count) { + HashSet<int> friendlyTypes = new(StatTypes.Cast<int>().ToList()); + var missingTypes = Enum.GetValues(typeof(StatType)).Cast<int>().ToList() + .Where(orig => friendlyTypes.Contains(orig) == false) + .Select(x => (StatType)x); + StatTypes.AddRange(missingTypes); + } + } + + public static List<StatType> StatTypes = new() { + StatType.WarhammerWeaponSkill, + StatType.WarhammerBallisticSkill, + StatType.WarhammerStrength, + StatType.WarhammerToughness, + StatType.WarhammerAgility, + StatType.WarhammerIntelligence, + StatType.WarhammerPerception, + StatType.WarhammerWillpower, + StatType.WarhammerFellowship, + StatType.WarhammerInitialAPBlue, + StatType.WarhammerInitialAPYellow, + StatType.HitPoints, + StatType.Initiative, + StatType.Speed, + StatType.SkillAthletics, + StatType.SkillAwareness, + StatType.SkillCarouse, + StatType.SkillPersuasion, + StatType.SkillDemolition, + StatType.SkillMedicae, + StatType.SkillLogic, + StatType.CheckBluff, + StatType.CheckDiplomacy, + StatType.CheckIntimidate + }; + } +} diff --git a/ToyBox/Classes/MainUI/PartyEditor/PartyEditor.cs b/ToyBox/Classes/MainUI/PartyEditor/PartyEditor.cs new file mode 100644 index 000000000..374755804 --- /dev/null +++ b/ToyBox/Classes/MainUI/PartyEditor/PartyEditor.cs @@ -0,0 +1,322 @@ +// Copyright < 2021 > Narria (github user Cabarius) - License: MIT +using Kingmaker; +using Kingmaker.Blueprints.Items.Equipment; +using Kingmaker.Cheats; +using Kingmaker.Code.UnitLogic; +using Kingmaker.Designers; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.Items; +using Kingmaker.UnitLogic; +using Kingmaker.UnitLogic.Parts; +using ModKit; +using ModKit.DataViewer; +using System; +using System.Collections.Generic; +using System.Linq; +using static ModKit.UI; + +namespace ToyBox { + public partial class PartyEditor { + public static Settings Settings => Main.Settings; + + private enum ToggleChoice { + Classes, + Stats, + Facts, + Features, + Buffs, + Abilities, + Spells, + Mechadendrites, + None, + }; + private const int NarrowIndent = 413; + + private static ToggleChoice selectedToggle = ToggleChoice.None; + private static BaseUnitEntity charToAdd = null; + private static BaseUnitEntity charToRecruit = null; + private static Browser<BlueprintItemMechadendrite, ItemEntity> m_MechadendriteBrowser = new(true, true); + private static BaseUnitEntity charToRemove = null; + private static BaseUnitEntity charToUnrecruit = null; + private static BaseUnitEntity selectedCharacter = null; + private static bool editMultiClass = false; + private static BaseUnitEntity multiclassEditCharacter = null; + private static int respecableCount = 0; + private static int recruitableCount = 0; + public static int selectedSpellbookLevel = 0; + public static int SelectedNewSpellLvl = 0; + private static (string, string) nameEditState = (null, null); + internal static readonly Dictionary<string, int> statEditorStorage = new(); + public static Dictionary<string, Spellbook> SelectedSpellbook = new(); + private static BaseUnitEntity GetEditCharacter() { + var characterList = CharacterPicker.GetCharacterList(); + if (characterList == null || characterList.Count == 0) return null; + if (!characterList.Contains(selectedCharacter)) return null; + else return selectedCharacter; + } + + public static void ResetGUI() { + selectedCharacter = null; + selectedSpellbookLevel = 0; + CharacterPicker.PartyFilterChoices = null; + Main.Settings.selectedPartyFilter = 0; + } + + // This bit of kludge is added in order to tell whether our generic actions are being accessed from this screen or the Search n' Pick + public static bool IsOnPartyEditor() => Main.Settings.selectedTab == 3; + + public static void ActionsGUI(BaseUnitEntity ch, List<Action> todo) { + var player = Game.Instance.Player; + Space(25); + var buttonCount = 0; + if (!player.PartyAndPets.Contains(ch) && player.AllCharacters.Contains(ch)) { + ActionButton("Add".localize(), () => { charToAdd = ch; }, Width(150)); + Space(25); + buttonCount++; + } else if (player.ActiveCompanions.Contains(ch)) { + ActionButton("Remove".localize(), () => { charToRemove = ch; }, Width(150)); + Space(25); + buttonCount++; + } else if (!player.AllCharactersAndStarships.Contains(ch)) { + recruitableCount++; + ActionButton("Recruit".localize().Cyan(), () => { charToRecruit = ch; }, Width(150)); + Space(25); + buttonCount++; + } + if (player.AllCharacters.Contains(ch) && !ch.IsMainCharacter && !ch.IsStoryCompanion()) { + ActionButton("Unrecruit".Cyan(), + () => { + charToUnrecruit = ch; + charToRemove = ch; + }, + Width(150)); + Space(25); + buttonCount++; + } + if (ch.CanRespec()) { + respecableCount++; + ActionButton("Respec".localize().Cyan(), () => todo.Add(() => { Actions.ToggleModWindow(); ch.DoRespec(); }), Width(150)); + } else { + Space(153); + } + if (buttonCount >= 0) + Space(178 * (2 - buttonCount)); +#if false + Space(25); + ActionButton("Log Caster Info", () => CasterHelpers.GetOriginalCasterLevel(ch.Descriptor()), + AutoWidth()); +#endif + ActionButton("Kill".localize().Cyan(), () => CheatsCombat.KillUnit(ch)); + Label("", AutoWidth()); + } + public static void OnGUI() { + var player = Game.Instance.Player; + if (player == null) return; + charToAdd = null; + charToRecruit = null; + charToRemove = null; + charToUnrecruit = null; + var characterListFunc = CharacterPicker.OnFilterPickerGUI(); + var characterList = characterListFunc.func(); + var mainChar = GameHelper.GetPlayerCharacter(); + if (characterListFunc.name == "Nearby") { + Slider("Nearby Distance".localize(), ref CharacterPicker.nearbyRange, 1f, 200, 25, 0, " meters".localize(), Width(250)); + characterList = characterList.OrderBy((ch) => ch.DistanceTo(mainChar)).ToList(); + } + Space(20); + var chIndex = 0; + recruitableCount = 0; + respecableCount = 0; + selectedCharacter = GetEditCharacter(); + var isWide = IsWide; + if (Main.IsInGame) { + using (HorizontalScope()) { + Label($"Party Level ".localize().Cyan() + $"{Game.Instance.Player.PartyLevel}".Orange().Bold(), AutoWidth()); + Space(110); + ReflectionTreeView.DetailToggle($"Inspect Party {"(for modders)".Orange()}".localize(), "All", characterList, 0); +#if false // disabled until we fix performance + var encounterCR = CheatsCombat.GetEncounterCr(); + if (encounterCR > 0) { + UI.Label($"Encounter CR ".cyan() + $"{encounterCR}".orange().bold(), UI.AutoWidth()); + } +#endif + } + } + ReflectionTreeView.OnDetailGUI("All"); + List<Action> todo = new(); + foreach (var ch in characterList) { + var classData = ch.Progression.AllCareerPaths.ToList(); + // TODO - understand the difference between ch.Progression and ch.Descriptor().Progression + var progression = ch.Descriptor().Progression; + var xpTable = progression.ExperienceTable; + var level = progression.CharacterLevel; + var spellbooks = ch.Spellbooks.ToList(); + var spellCount = spellbooks.Sum((sb) => sb.GetAllKnownSpells().Count()); + var isOnTeam = player.AllCharacters.Contains(ch); + using (HorizontalScope()) { + var name = ch.CharacterName; + if (Game.Instance.Player.AllCharacters.Contains(ch) + || Game.Instance.Player.m_AllCharactersAndStarships.Contains(ch)) { + var oldEditState = nameEditState; + if (isWide) { + if (EditableLabel(ref name, ref nameEditState, 200, n => n.Orange().Bold(), MinWidth(100), MaxWidth(400))) { + ch.Description.CustomName = name; + Main.SetNeedsResetGameUI(); + } + } else + if (EditableLabel(ref name, ref nameEditState, 200, n => n.Orange().Bold(), Width(230))) { + ch.Description.CustomName = name; + Main.SetNeedsResetGameUI(); + } + if (nameEditState != oldEditState) { + Mod.Log($"EditState changed: {oldEditState} -> {nameEditState}"); + } + } else { + if (isWide) + Label(ch.CharacterName.Orange().Bold(), MinWidth(100), MaxWidth(400)); + else + Label(ch.CharacterName.Orange().Bold(), Width(230)); + } + Space(5); + var distance = mainChar.DistanceTo(ch); ; + Label(distance < 1 ? "" : distance.ToString("0") + "m", Width(75)); + Space(5); + int nextLevel; + for (nextLevel = level; xpTable.HasBonusForLevel(nextLevel + 1) && progression.Experience >= xpTable.GetBonus(nextLevel + 1); nextLevel++) { } + if (nextLevel <= level || !isOnTeam) + Label((level < 10 ? " lvl" : " lv").Green() + $" {level}", Width(90)); + else + Label((level < 10 ? " " : "") + $"{level} > " + $"{nextLevel}".Cyan(), Width(90)); + // Level up code adapted from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/2 + if (player.AllCharacters.Contains(ch)) { + if (xpTable.HasBonusForLevel(nextLevel + 1)) { + ActionButton("+1", () => { + progression.AdvanceExperienceTo(xpTable.GetBonus(nextLevel + 1), true); + }, Width(63)); + } else { Label("max".localize(), Width(63)); } + } else { Space(66); } + Space(30); + Wrap(IsNarrow, NarrowIndent, 0); + var prevSelectedChar = selectedCharacter; + var showClasses = ch == selectedCharacter && selectedToggle == ToggleChoice.Classes; + if (DisclosureToggle($"{classData.Count} " + "Classes".localize(), ref showClasses, 140)) { + if (showClasses) { + selectedCharacter = ch; selectedToggle = ToggleChoice.Classes; Mod.Trace($"selected {ch.CharacterName}"); + } else { selectedToggle = ToggleChoice.None; } + } + var showStats = ch == selectedCharacter && selectedToggle == ToggleChoice.Stats; + if (DisclosureToggle("Stats".localize(), ref showStats, 95)) { + if (showStats) { selectedCharacter = ch; selectedToggle = ToggleChoice.Stats; } else { selectedToggle = ToggleChoice.None; } + } + //var showFacts = ch == selectedCharacter && selectedToggle == ToggleChoice.Facts; + //if (UI.DisclosureToggle("Facts", ref showFacts, 125)) { + // if (showFacts) { selectedCharacter = ch; selectedToggle = ToggleChoice.Facts; } + // else { selectedToggle = ToggleChoice.None; } + //} + var showFeatures = ch == selectedCharacter && selectedToggle == ToggleChoice.Features; + if (DisclosureToggle("Features".localize(), ref showFeatures, 125)) { + if (showFeatures) { selectedCharacter = ch; selectedToggle = ToggleChoice.Features; } else { selectedToggle = ToggleChoice.None; } + } + Wrap(!IsWide, NarrowIndent, 0); + var showBuffs = ch == selectedCharacter && selectedToggle == ToggleChoice.Buffs; + if (DisclosureToggle("Buffs".localize(), ref showBuffs, 90)) { + if (showBuffs) { selectedCharacter = ch; selectedToggle = ToggleChoice.Buffs; } else { selectedToggle = ToggleChoice.None; } + } + var showAbilities = ch == selectedCharacter && selectedToggle == ToggleChoice.Abilities; + if (DisclosureToggle("Abilities".localize(), ref showAbilities, 80 * UnityModManagerNet.UnityModManager.UI.Instance.mUIScale)) { + if (showAbilities) { selectedCharacter = ch; selectedToggle = ToggleChoice.Abilities; } else { selectedToggle = ToggleChoice.None; } + } + var showDendrites = ch == selectedCharacter && selectedToggle == ToggleChoice.Mechadendrites; + if (DisclosureToggle("Mechadendrites".localize(), ref showDendrites, 125 * UnityModManagerNet.UnityModManager.UI.Instance.mUIScale)) { + if (showDendrites) { selectedCharacter = ch; selectedToggle = ToggleChoice.Mechadendrites; m_MechadendriteBrowser = new(true, true); } else { selectedToggle = ToggleChoice.None; } + } + ReflectionTreeView.DetailToggle("Inspect".localize(), ch, ch, 75); + Wrap(!isWide, NarrowIndent - 20); + ActionsGUI(ch, todo); + } + if (!isWide) Div(00, 10); + 5.space(); + ReflectionTreeView.OnDetailGUI(ch); + if (selectedCharacter != multiclassEditCharacter) { + editMultiClass = false; + multiclassEditCharacter = null; + } + if (ch == selectedCharacter) { + if (selectedToggle == ToggleChoice.Classes) { + OnClassesGUI(ch, classData, selectedCharacter); + } else if (selectedToggle == ToggleChoice.Stats) { + todo = OnStatsGUI(ch); + } else if (selectedToggle == ToggleChoice.Features) { + todo = FactsEditor.OnGUI(ch, ch.Progression.Features.Enumerable.ToList()); + } else if (selectedToggle == ToggleChoice.Buffs) { + todo = FactsEditor.OnGUI(ch, ch.Descriptor().Buffs.Enumerable.ToList()); + } else if (selectedToggle == ToggleChoice.Abilities) { + todo = FactsEditor.OnGUI(ch, ch.Descriptor().Abilities.Enumerable, ch.Descriptor().ActivatableAbilities.Enumerable); + } else if (selectedToggle == ToggleChoice.Mechadendrites) { + Label("Warning: This feature is still not very-well tested so it is suggested to save before using it!".Orange().Bold()); + m_MechadendriteBrowser.OnGUI(ch.Body.Mechadendrites.Select(m => m.Item), () => BlueprintLoader.Shared.GetBlueprintsOfType<BlueprintItemMechadendrite>(), i => i.Blueprint as BlueprintItemMechadendrite, s => BlueprintExtensions.GetSearchKey(s), s => [BlueprintExtensions.GetSortKey(s)], null, + (bp, maybeItem) => { + Label(BlueprintExtensions.GetTitle(bp)); + Space(15); + if (maybeItem != null) { + ActionButton("Remove".localize(), () => { + var slot = ch.Body.Mechadendrites.First(slot => slot.Item == maybeItem); + slot.RemoveItem(true, true); + ch.Body.Mechadendrites.Remove(slot); + ch.View.Mechadendrites.Remove(maybeItem); + try { + Game.Instance.Player.Inventory.Remove(maybeItem); + } catch (Exception ex) { + Mod.Trace($"Exception while removing Mechadendrite Entity from inventory:\n{ex}"); + } + m_MechadendriteBrowser.ReloadData(); + }); + } else { + ActionButton("Add".localize(), () => { + var slot = new Kingmaker.Items.Slots.EquipmentSlot<BlueprintItemMechadendrite>(ch); + ch.Body.Mechadendrites.Add(slot); + ch.Body.TryInsertItem(bp, slot); + ch.View.Mechadendrites.Add(slot.Item); + m_MechadendriteBrowser.ReloadData(); + }); + } + }); + } + } + chIndex += 1; + } + Space(25); + if (recruitableCount > 0) { + Label($"{recruitableCount} " + ("character(s) can be ".Orange().Bold() + "Recruited".Cyan() + ". This allows you to add non party NPCs to your party as if they were mercenaries".Green()).localize()); + } + if (respecableCount > 0) { + Label($"{respecableCount} " + ("character(s) can be ".Orange().Bold() + "Respecced".Cyan() + ". Pressing Respec will close the mod window and take you to character level up".Green()).localize()); + Toggle("Respec from Level 0".localize().Green(), ref Settings.toggleSetDefaultRespecLevelZero, 500.width()); + } + Space(25); + foreach (var action in todo) + action(); + bool needsFix = false; + if (charToAdd != null) { + BaseUnitDataUtils.AddCompanion(charToAdd); + needsFix = true; + } + if (charToRecruit != null) { + BaseUnitDataUtils.RecruitCompanion(charToRecruit); + needsFix = true; + } + if (charToRemove != null) { + BaseUnitDataUtils.RemoveCompanion(charToRemove); + needsFix = true; + } + if (charToUnrecruit != null) { + charToUnrecruit.GetCompanionOptional()?.SetState(CompanionState.None); + charToUnrecruit.Remove<UnitPartCompanion>(); + needsFix = true; + } + if (needsFix) { + Game.Instance.Player.FixPartyAfterChange(); + } + } + } +} \ No newline at end of file diff --git a/ToyBox/Classes/MainUI/PartyEditor/StatsEditor.cs b/ToyBox/Classes/MainUI/PartyEditor/StatsEditor.cs new file mode 100644 index 000000000..5c92f3785 --- /dev/null +++ b/ToyBox/Classes/MainUI/PartyEditor/StatsEditor.cs @@ -0,0 +1,687 @@ +using HarmonyLib; +using Kingmaker; +using Kingmaker.Blueprints; +using Kingmaker.Blueprints.Root; +using Kingmaker.DialogSystem.Blueprints; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.Enums; +using Kingmaker.PubSubSystem; +using Kingmaker.UnitLogic; +using Kingmaker.UnitLogic.Alignments; +using Kingmaker.UnitLogic.Buffs.Blueprints; +using Kingmaker.Visual.CharacterSystem; +using Kingmaker.Visual.Sound; +using ModKit; +using ModKit.Utility; +using ModKit.Utility.Extensions; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using ToyBox.classes.Infrastructure; +using Unity.Collections; +using UnityEngine; + +namespace ToyBox { + + public partial class PartyEditor { + + public class ToyBoxAlignmentProvider : IAlignmentShiftProvider { + AlignmentShift IAlignmentShiftProvider.AlignmentShift => new() { Description = "ToyBox Party Editor".LocalizedStringInGame() }; + } + + public static IAlignmentShiftProvider ToyboxAlignmentProvider => new ToyBoxAlignmentProvider(); + + public static Dictionary<string, SkeletonReplacer> skeletonReplacers = new(); + private static readonly Dictionary<string, PortraitData> _portraitsByID = new(); + private static bool _portraitsLoaded = false; + private static Browser<string, string> portraitBrowser; + private static Browser<BlueprintPortrait, BlueprintPortrait> blueprintPortraitBrowser; + private static Browser<BlueprintUnitAsksList, BlueprintUnitAsksList> blueprintVoiceBrowser; + private static bool listCustomPortraits = false; + private static bool listCustomVoices = false; + private static bool listBlueprintPortraits = false; + private static bool listSkeletonPartsOffsets = false; + private static bool listSkeletonPartsScales = false; + private static bool listSkeletonPartsSizes = false; + private static bool listSkeletonItemsOffsets = false; + private static bool listSkeletonItemsSizes = false; + private static IEnumerable<BlueprintPortrait> blueprintPortraitBps = null; + private static IEnumerable<BlueprintUnitAsksList> blueprintVoiceBps = null; + private static string newPortraitName = ""; + private static BlueprintPortrait newBlueprintPortrait = null; + private static bool unknownID = false; + + public static void UnloadPortraits(bool force = false) { + if (!force && !_portraitsLoaded) return; + _portraitsByID.Clear(); + _portraitsLoaded = false; + portraitBrowser = null; + blueprintPortraitBrowser = null; + CustomPortraitsManager.Instance.Cleanup(); + } + public static PortraitData LoadCustomPortrait(string customID, out bool loaded) { + loaded = false; + try { + PortraitData portraitData; + if (!_portraitsByID.TryGetValue(customID, out portraitData)) { + portraitData = new PortraitData(customID); + if (portraitData.DirectoryExists()) { + _portraitsByID[customID] = CustomPortraitsManager.CreatePortraitData(customID); + loaded = true; + return _portraitsByID[customID]; + } + } else { + loaded = true; + return portraitData; + } + } catch (Exception e) { + Mod.Log(e.ToString()); + } + return null; + } + public static void OnPortraitGUI(string customID, float scaling = 0.5f, bool isButton = true, int targetWidth = 0) { + PortraitData portraitData = LoadCustomPortrait(customID, out var loaded); + if (loaded) { + var sprite = portraitData.FullLengthPortrait; + int w, h; + if (targetWidth == 0) { + w = (int)(sprite.rect.width * scaling); + h = (int)(sprite.rect.height * scaling); + } else { + w = targetWidth; + h = (int)(targetWidth * (sprite.rect.height / sprite.rect.width)); + } + using (VerticalScope((w + 10).width())) { + if (isButton) { + if (GUILayout.Button(sprite.texture, rarityStyle, w.width(), h.height())) { + newPortraitName = customID; + } + } else { + GUILayout.Label(sprite.texture, rarityStyle, w.width(), h.height()); + } + Label(customID); + } + } + } + public static void OnPortraitGUI(BlueprintPortrait portrait, float scaling = 0.5f, bool isButton = true, int targetWidth = 0) { + if (portrait != null) { + var sprite = portrait.FullLengthPortrait; + if (sprite == null) return; + int w, h; + if (targetWidth == 0) { + w = (int)(sprite.rect.width * scaling); + h = (int)(sprite.rect.height * scaling); + } else { + w = targetWidth; + h = (int)(targetWidth * (sprite.rect.height / sprite.rect.width)); + } + using (VerticalScope((w + 10).width())) { + if (isButton) { + if (GUILayout.Button(sprite.texture, rarityStyle, w.width(), h.height())) { + newBlueprintPortrait = portrait; + } + } else { + GUILayout.Label(sprite.texture, rarityStyle, w.width(), h.height()); + } + ActionButton("Save as png".localize(), () => { + try { + var portraitDir = new DirectoryInfo(Path.Combine(Main.path, "Portraits", portrait.name)); + if (!portraitDir.Exists) { + portraitDir.Create(); + } + var outFile = new FileInfo(Path.Combine(portraitDir.FullName, BlueprintRoot.Instance.CharGenRoot.PortraitSmallName + BlueprintRoot.Instance.CharGenRoot.PortraitsFormat)); + portrait.SmallPortrait.texture.SaveTextureToFile(outFile.FullName, -1, -1, MiscExtensions.SaveTextureFileFormat.PNG, 100, false); + outFile = new FileInfo(Path.Combine(portraitDir.FullName, BlueprintRoot.Instance.CharGenRoot.PortraitMediumName + BlueprintRoot.Instance.CharGenRoot.PortraitsFormat)); + portrait.HalfLengthPortrait.texture.SaveTextureToFile(outFile.FullName, -1, -1, MiscExtensions.SaveTextureFileFormat.PNG, 100, false); + outFile = new FileInfo(Path.Combine(portraitDir.FullName, BlueprintRoot.Instance.CharGenRoot.PortraitBigName + BlueprintRoot.Instance.CharGenRoot.PortraitsFormat)); + portrait.FullLengthPortrait.texture.SaveTextureToFile(outFile.FullName, -1, -1, MiscExtensions.SaveTextureFileFormat.PNG, 100, false); + Process.Start(portraitDir.FullName); + } catch (Exception ex) { + Mod.Error(ex.ToString()); + } + }); + Label(BlueprintExtensions.GetTitle(portrait), MinWidth(200), AutoWidth()); + } + } + } + public static List<Action> OnStatsGUI(BaseUnitEntity ch) { + List<Action> todo = new(); + using (HorizontalScope()) { + 100.space(); + using (VerticalScope()) { + if (ch.UISettings.Portrait.IsCustom) { + Label("Current Custom Portrait".localize()); + OnPortraitGUI(ch.UISettings.Portrait.CustomId, 0.25f, false); + } else { + Label("Current Blueprint Portrait".localize()); + OnPortraitGUI(ch.UISettings.PortraitBlueprint, 0.25f, false, (int)(0.25f * 692)); + } + using (HorizontalScope()) { + Label("Gender".localize(), Width(180)); + Space(25); + var gender = ch.Descriptor().GetCustomGender() ?? ch.Descriptor().Gender; + var isFemale = gender == Gender.Female; + using (HorizontalScope(Width(100))) { + if (Toggle(isFemale ? "Female".localize() : "Male".localize(), ref isFemale, + RichText.Bold("♀".Color(RGBA.magenta)), + RichText.Bold("♂".Color(RGBA.aqua)), + 0, largeStyle, GUI.skin.box, Width(200), Height(20))) { + ch.Descriptor().SetCustomGender(isFemale ? Gender.Female : Gender.Male); + } + } + Label(RichText.Green("Changing your gender may cause visual glitches".localize())); + } + Div(0, 20, 755); + DisclosureToggle("Show Custom Portrait Picker".localize(), ref listCustomPortraits); + if (listCustomPortraits) { + using (HorizontalScope()) { + Label("Name of the new Custom Portrait: ".localize(), Width(425)); + TextField(ref newPortraitName, null, MinWidth(200), AutoWidth()); + ActionButton("Change Portrait".localize(), () => todo.Add(() => { + if (CustomPortraitsManager.Instance.GetExistingCustomPortraitIds().Contains(newPortraitName)) { + ch.UISettings.SetPortrait(new PortraitData(newPortraitName)); + Mod.Debug($"Changed portrait of {ch.CharacterName} to {newPortraitName}"); + unknownID = false; + } else { + Mod.Warn($"No portrait with name {newPortraitName}"); + unknownID = true; + } + })); + if (unknownID) { + 25.space(); + Label(RichText.Red("Unknown ID!".localize())); + } + } + if (CustomPortraitsManager.Instance.GetExistingCustomPortraitIds() is string[] customIDs) { + if (portraitBrowser == null) { + portraitBrowser = new(true, true, false, true); + _portraitsLoaded = true; + portraitBrowser.SearchLimit = 18; + portraitBrowser.DisplayShowAllGUI = false; + } + portraitBrowser.OnGUI(customIDs, () => customIDs, ID => ID, ID => ID, ID => new[] { ID }, null, null, null, 0, true, true, 100, 300, "", false, null, + (definitions, _currentDict) => { + var count = definitions.Count; + using (VerticalScope()) { + for (var ii = 0; ii < count;) { + var tmp = ii; + using (HorizontalScope()) { + for (; ii < Math.Min(tmp + 6, count); ii++) { + var customID = definitions[ii]; + // 6 Portraits per row; 692px per image + buffer + OnPortraitGUI(customID, (ummWidth - 100) / (6 * 780)); + } + } + } + } + }); + } + } + DisclosureToggle("Show Blueprint Portrait Picker".localize(), ref listBlueprintPortraits); + if (listBlueprintPortraits) { + using (HorizontalScope()) { + Label("Name of the new Blueprintportrait: ".localize(), Width(425)); + if (newBlueprintPortrait != null) + Label(BlueprintExtensions.GetTitle(newBlueprintPortrait), MinWidth(200), AutoWidth()); + else + 200.space(); + ActionButton("Change Portrait".localize(), () => todo.Add(() => { + if (newBlueprintPortrait != null) { + ch.UISettings.SetPortrait(newBlueprintPortrait); + Mod.Debug($"Changed portrait of {ch.CharacterName} to {BlueprintExtensions.GetTitle(newBlueprintPortrait)}"); + } + })); + } + if (Event.current.type == EventType.Layout && (blueprintPortraitBps?.Count() ?? 0) == 0) { + blueprintPortraitBps = BlueprintLoader.Shared.GetBlueprintsOfType<BlueprintPortrait>(); + } + if ((blueprintPortraitBps?.Count() ?? 0) > 0) { + if (blueprintPortraitBrowser == null) { + blueprintPortraitBrowser = new(true, true, false, true); + blueprintPortraitBrowser.SearchLimit = 18; + blueprintPortraitBrowser.DisplayShowAllGUI = false; + } + blueprintPortraitBrowser.OnGUI(blueprintPortraitBps, () => blueprintPortraitBps, ID => ID, ID => BlueprintExtensions.GetSearchKey(ID), ID => new[] { BlueprintExtensions.GetSortKey(ID) }, null, null, null, 0, true, true, 100, 300, "", false, null, + (definitions, _currentDict) => { + var count = definitions.Count; + using (VerticalScope()) { + for (var ii = 0; ii < count;) { + var tmp = ii; + using (HorizontalScope()) { + for (; ii < Math.Min(tmp + 6, count); ii++) { + // 6 Portraits per row; 692px per image + buffer + OnPortraitGUI(definitions[ii], 3.76f * ((ummWidth - 100) / (6 * 780)), true, (int)(692 * (ummWidth - 100) / (6 * 780))); + } + } + } + } + }); + } + } + DisclosureToggle("Show Blueprint Voice Picker".localize(), ref listCustomVoices); + if (listCustomVoices) { + if (ch != null) { + if (ch.Asks.List != null) { + if (!(ch.IsCustomCompanion() || ch.IsMainCharacter)) { + Label(RichText.Bold(RichText.Red("You're about to change the voice of a non-custom character. That's untested.".localize()))); + } else if (!BlueprintExtensions.GetTitle(ch.Asks.List).StartsWith("RT")) { + Label(RichText.Bold(RichText.Red("You have given a custom character a non-default voice. That's untested.".localize()))); + } + } + if (blueprintVoiceBrowser?.ShowAll ?? false) { + Label(RichText.Bold(RichText.Red("Giving characters voices besides the default ones is untested.".localize()))); + } + if (Event.current.type == EventType.Layout && (blueprintVoiceBps?.Count() ?? 0) == 0) { + blueprintVoiceBps = BlueprintLoader.Shared.GetBlueprintsOfType<BlueprintUnitAsksList>(); + } + if ((blueprintVoiceBps?.Count() ?? 0) > 0) { + if (blueprintVoiceBrowser == null) { + blueprintVoiceBrowser = new(true, true); + blueprintVoiceBrowser.SearchLimit = 18; + } + blueprintVoiceBrowser.OnGUI(blueprintVoiceBps.Where(v => BlueprintExtensions.GetTitle(v).StartsWith("RT")).ToList(), () => blueprintVoiceBps, ID => ID, ID => BlueprintExtensions.GetSearchKey(ID), ID => new[] { BlueprintExtensions.GetSortKey(ID) }, null, + (definition, _currentDict) => { + bool isCurrentVoice = definition == ch.Asks.List; + if (isCurrentVoice) { + Label(RichText.Green(BlueprintExtensions.GetTitle(definition)), 300.width()); + ActionButton("Play Example".localize(), () => { + new BarkWrapper(definition.GetComponent<UnitAsksComponent>().PartyMemberUnconscious, ch.View.Asks).Schedule(); + }, 150.width()); + } else { + Label(BlueprintExtensions.GetTitle(definition), 300.width()); + Space(150); + } + Space(200); + if (isCurrentVoice) { + Label("This is the current voice!".localize()); + } else { + ActionButton("Change Voice".localize(), () => { + if (definition != null) { + todo.Add(() => { + ch.Asks.SetCustom(definition); + ch.View.UpdateAsks(); + }); + Mod.Debug($"Changed voice of {ch.CharacterName} to {BlueprintExtensions.GetTitle(definition)}"); + } + }); + } + }); + } + } + } + var cName = ch.Blueprint?.CharacterName?.ToLower() ?? ch.Blueprint.AssetGuid.ToString(); + bool DisableVO = Settings.namesToDisableVoiceOver.Contains(cName); + if (Toggle("Disable Voice Over and Barks for this character".localize(), ref DisableVO, Width(425))) { + if (DisableVO) Settings.namesToDisableVoiceOver.Add(cName); + else Settings.namesToDisableVoiceOver.Remove(cName); + } + using (HorizontalScope()) { + if (!Main.Settings.perSave.doOverrideEnableAiForCompanions.TryGetValue(ch.HashKey(), out var valuePair)) { + valuePair = new(false, false); + } + var temp = valuePair.Item1; + if (Toggle("Override AI Control Behaviour".localize(), ref temp)) { + if (temp) { + Main.Settings.perSave.doOverrideEnableAiForCompanions[ch.HashKey()] = new(temp, valuePair.Item2); + Settings.SavePerSaveSettings(); + } else { + Main.Settings.perSave.doOverrideEnableAiForCompanions.Remove(ch.HashKey()); + Settings.SavePerSaveSettings(); + } + } + if (temp) { + Space(50); + var temp2 = valuePair.Item2; + if (Toggle("Make Character AI Controlled".localize(), ref temp2)) { + Main.Settings.perSave.doOverrideEnableAiForCompanions[ch.HashKey()] = new(temp, temp2); + Settings.SavePerSaveSettings(); + } + } + } + } + } + Div(100, 20, 755); + using (HorizontalScope()) { + Space(100); + + using (VerticalScope()) { + + DisclosureToggle("Body parts global offsets".localize(), ref listSkeletonPartsOffsets); + if (listSkeletonPartsOffsets) { + Space(6); + if (skeletonReplacers.ContainsKey(ch.HashKey())) { + + foreach (string key in skeletonReplacers[ch.HashKey()].groupOF.Keys) { + + using (HorizontalScope()) { + + using (VerticalScope(Width(325))) { + + Label(key.localize().Color(RGBA.none), Width(325)); + Space(-3.point()); + } + + var bodyPart = skeletonReplacers[ch.HashKey()].groupOF[key]; + var min = bodyPart.min; + var max = bodyPart.max; + + if (Slider(ref bodyPart.parameter, min, max, 0, 2, "", AutoWidth())) { + + skeletonReplacers[ch.HashKey()].ApplyBonesModification(ch, false, key); + Settings.SavePerSaveSettings(); + } + } + } + Space(10); + + } else { + + skeletonReplacers[ch.HashKey()] = new SkeletonReplacer(ch); + } + } + + DisclosureToggle("Body parts global scales".localize(), ref listSkeletonPartsScales); + if (listSkeletonPartsScales) { + Space(6); + if (skeletonReplacers.ContainsKey(ch.HashKey())) { + + foreach (string key in skeletonReplacers[ch.HashKey()].groupSC.Keys) { + + using (HorizontalScope()) { + + var bodyPart = skeletonReplacers[ch.HashKey()].groupSC[key]; + var min = bodyPart.min; + var max = bodyPart.max; + + if (LogSliderCustomLabelWidth(key.localize().Color(RGBA.none), ref bodyPart.parameter, min, max, 1, 2, "", 300, AutoWidth())) { + + skeletonReplacers[ch.HashKey()].ApplyBonesModification(ch, false, key); + Settings.SavePerSaveSettings(); + } + } + } + Space(10); + + } else { + + skeletonReplacers[ch.HashKey()] = new SkeletonReplacer(ch); + } + } + + DisclosureToggle("Body parts local sizes".localize(), ref listSkeletonPartsSizes); + if (listSkeletonPartsSizes) { + Space(6); + if (skeletonReplacers.ContainsKey(ch.HashKey())) { + + foreach (string key in skeletonReplacers[ch.HashKey()].groupSZ.Keys) { + + using (HorizontalScope()) { + + var bodyPart = skeletonReplacers[ch.HashKey()].groupSZ[key]; + var min = bodyPart.min; + var max = bodyPart.max; + + if (LogSliderCustomLabelWidth(key.localize().Color(RGBA.none), ref bodyPart.parameter, min, max, 1, 2, "", 300, AutoWidth())) { + + skeletonReplacers[ch.HashKey()].ApplyBonesModification(ch, false, key); + Settings.SavePerSaveSettings(); + } + } + } + Space(10); + + } else { + + skeletonReplacers[ch.HashKey()] = new SkeletonReplacer(ch); + } + } + + DisclosureToggle("Equipment elements offsets".localize(), ref listSkeletonItemsOffsets); + if (listSkeletonItemsOffsets) { + Space(6); + if (skeletonReplacers.ContainsKey(ch.HashKey())) { + + foreach (string key in skeletonReplacers[ch.HashKey()].groupIO.Keys) { + + using (HorizontalScope()) { + + using (VerticalScope(Width(325))) { + + Label(key.localize().Color(RGBA.none), Width(325)); + Space(-3.point()); + } + + var bodyPart = skeletonReplacers[ch.HashKey()].groupIO[key]; + var min = bodyPart.min; + var max = bodyPart.max; + + if (Slider(ref bodyPart.parameter, min, max, 0, 2, "", AutoWidth())) { + + skeletonReplacers[ch.HashKey()].ApplyBonesModification(ch, false, key); + Settings.SavePerSaveSettings(); + } + } + } + Space(10); + + } else { + + skeletonReplacers[ch.HashKey()] = new SkeletonReplacer(ch); + } + } + + DisclosureToggle("Equipment elements sizes".localize(), ref listSkeletonItemsSizes); + if (listSkeletonItemsSizes) { + Space(6); + if (skeletonReplacers.ContainsKey(ch.HashKey())) { + + foreach (string key in skeletonReplacers[ch.HashKey()].groupIS.Keys) { + + using (HorizontalScope()) { + + var bodyPart = skeletonReplacers[ch.HashKey()].groupIS[key]; + var min = bodyPart.min; + var max = bodyPart.max; + + if (LogSliderCustomLabelWidth(key.localize().Color(RGBA.none), ref bodyPart.parameter, min, max, 1, 2, "", 300, AutoWidth())) { + + skeletonReplacers[ch.HashKey()].ApplyBonesModification(ch, false, key); + Settings.SavePerSaveSettings(); + } + } + } + Space(10); + + } else { + + skeletonReplacers[ch.HashKey()] = new SkeletonReplacer(ch); + } + } + } + } + Div(100, 20, 755); + if (ch != null && ch.HashKey() != null) { + using (HorizontalScope()) { + Space(100); + using (VerticalScope()) { + using (HorizontalScope()) { + Label("Size".localize(), Width(325)); + var size = ch.Descriptor().State.Size; + Label(RichText.Bold(RichText.Orange($"{size}")), Width(175)); + } + Label("Pick size modifier to overwrite default.".localize()); + Label("Pick none to stop overwriting.".localize()); + using (HorizontalScope()) { + Space(328); + int tmp = 0; + if (Main.Settings.perSave.characterSizeModifier.TryGetValue(ch.HashKey(), out var tmpSize)) { + tmp = ((int)tmpSize) + 1; + // Applying again in case the game decided to change the modifier. Since this is an OnGUI it'll still only happen if the GUI is open though. + ch.Descriptor().State.Size = tmpSize; + } + var names = Enum.GetNames(typeof(Size)).Prepend("None").Select(name => name.localize()).ToArray(); + ActionSelectionGrid( + ref tmp, + names, + 3, + (s) => { + // if == 0 then "None" is selected + if (tmp > 0) { + var newSize = (Size)(tmp - 1); + Main.Settings.perSave.characterSizeModifier[ch.HashKey()] = newSize; + Settings.SavePerSaveSettings(); + ch.Descriptor().State.Size = newSize; + } else { + Main.Settings.perSave.characterSizeModifier.Remove(ch.HashKey()); + Settings.SavePerSaveSettings(); + ch.Descriptor().State.Size = ch.Descriptor().OriginalSize; + } + ch.ViewTransform.localScale = ch.View.m_OriginalScale * (ch.View.m_Scale = ch.View.GetSizeScale()); + }, + Width(420)); + } + } + } + Space(20); + using (HorizontalScope()) { + Space(100); + if (ch.View?.gameObject?.transform?.localScale[0] is float scaleMultiplier) { + var lastScale = Main.Settings.perSave.characterModelSizeMultiplier.GetValueOrDefault(ch.HashKey(), 1); + if (LogSliderCustomLabelWidth("Visual Character Size Multiplier".localize().Color(RGBA.none), ref lastScale, 0.01f, 40f, 1, 2, "", 300, AutoWidth())) { + Main.Settings.perSave.characterModelSizeMultiplier[ch.HashKey()] = lastScale; + Settings.SavePerSaveSettings(); + + ch.Descriptor().State.Size = ch.Descriptor().Size; + ch.ViewTransform.localScale = ch.View.m_OriginalScale * (ch.View.m_Scale = ch.View.GetSizeScale()); + } + } + } + Space(10); + Div(100, 20, 755); + } + // TODO: Actually implement this for companions. + if (ch.IsMainCharacter) { + var soulMarks = ch.GetSoulMarks(); + using (HorizontalScope()) { + 100.space(); + Label("Soul Marks".localize(), Width(300)); + using (VerticalScope()) { + foreach (SoulMarkDirection dir in Enum.GetValues(typeof(SoulMarkDirection))) { + if (dir == SoulMarkDirection.None || dir == SoulMarkDirection.Reason) continue; + SoulMark soulMark = null; + try { + soulMark = SoulMarkShiftExtension.GetSoulMarkFor(ch, dir); + if (soulMark == null) continue; + /*{ + var f = Shodan.MainCharacter.Blueprint.m_AddFacts.Select(f => f.Get()).OfType<BlueprintSoulMark>().Where(f => f == SoulMarkShiftExtension.GetBaseSoulMarkFor(dir)).First(); + ch.AddFact(f); + soulMark = SoulMarkShiftExtension.GetSoulMarkFor(ch, dir); + }*/ + } catch (Exception ex) { + Mod.Error(ex); + continue; + } + using (HorizontalScope()) { + Label(RichText.Bold(RichText.Orange(dir.ToString().localize())), 95.width()); + ActionButton(" < ", + () => modifySoulmark(dir, soulMark, ch, soulMark.Rank - 1, soulMark.Rank - 2), + GUI.skin.box, + AutoWidth()); + Space(20); + var val = soulMark.Rank - 1; + Label(RichText.Bold(RichText.Orange($"{val}")), Width(50f)); + ActionButton(" > ", + () => modifySoulmark(dir, soulMark, ch, soulMark.Rank - 1, soulMark.Rank), + GUI.skin.box, + AutoWidth()); + Space(25); + val = soulMark.Rank - 1; + ActionIntTextField(ref val, (v) => { + if (v > 0) { + modifySoulmark(dir, soulMark, ch, soulMark.Rank - 1, v); + } + }, + Width(75)); + } + } + } + } + } + Div(100, 20, 755); + foreach (var obj in HumanFriendlyStats.StatTypes) { + try { + var statType = (StatType)obj; + Mod.Debug($"stat: {statType}"); + var modifiableValue = ch.Stats.GetStatOptional(statType); + if (modifiableValue == null) { + continue; + } + + var key = $"{ch.CharacterName}-{statType}"; + var storedValue = statEditorStorage.ContainsKey(key) ? statEditorStorage[key] : modifiableValue.ModifiedValue; + var statName = statType.ToString(); + if (statName == "BaseAttackBonus" || statName == "SkillAthletics" || statName == "HitPoints") { + Div(100, 20, 755); + } + using (HorizontalScope()) { + Space(100); + Label(statName.localize(), Width(374f)); + Space(25); + ActionButton(" < ", + () => { + modifiableValue.BaseValue -= 1; + storedValue = modifiableValue.ModifiedValue; + }, + GUI.skin.box, + AutoWidth()); + Space(20); + var val = modifiableValue.ModifiedValue; + Label(RichText.Bold(RichText.Orange($"{val}")), Width(50f)); + ActionButton(" > ", + () => { + modifiableValue.BaseValue += 1; + storedValue = modifiableValue.ModifiedValue; + }, + GUI.skin.box, + AutoWidth()); + Space(25); + ActionIntTextField(ref storedValue, (v) => { + + modifiableValue.BaseValue += v - modifiableValue.ModifiedValue; + storedValue = modifiableValue.ModifiedValue; + }, Width(75)); + statEditorStorage[key] = storedValue; + } + } catch (Exception ex) { + Mod.Trace(ex.ToString()); + } + } + return todo; + } + + private static void modifySoulmark(SoulMarkDirection dir, SoulMark soulMark, BaseUnitEntity ch, int oldRank, int v) { + var change = v - oldRank; + if (change > 0) { + var soulMarkShift = new SoulMarkShift() { CheckByRank = false, Direction = dir, Value = change }; + new BlueprintAnswer() { SoulMarkShift = soulMarkShift }.ApplyShiftDialog(); + } else if (change < 0) { + var soulMarkShift = new SoulMarkShift() { CheckByRank = false, Direction = dir, Value = change }; + var provider = new BlueprintAnswer() { SoulMarkShift = soulMarkShift }; + var source = provider as BlueprintScriptableObject; + if (source != null) { + EntityFactSource entityFactSource = new EntityFactSource(source, new int?(change)); + if (!soulMark.Sources.ToList().HasItem(entityFactSource)) { + soulMark.AddSource(source, change); + soulMark.RemoveRank(-change); + } + } + Game.Instance.DialogController.SoulMarkShifts.Add(provider.SoulMarkShift); + EventBus.RaiseEvent<ISoulMarkShiftHandler>(delegate (ISoulMarkShiftHandler h) { + h.HandleSoulMarkShift(provider); + }, true); + } + } + } +} diff --git a/ToyBox/Classes/MainUI/PatchTool/Infrastructure/Patch.cs b/ToyBox/Classes/MainUI/PatchTool/Infrastructure/Patch.cs new file mode 100644 index 000000000..b6df1a03d --- /dev/null +++ b/ToyBox/Classes/MainUI/PatchTool/Infrastructure/Patch.cs @@ -0,0 +1,22 @@ +using Kingmaker.Blueprints; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ToyBox.PatchTool; +public class Patch { + public string PatchId = Guid.NewGuid().ToString(); + public string BlueprintGuid; + [Obsolete("Added early in development where there was no 1 Patch per Blueprint limit")] + public List<string> PreviousPatches; + public List<PatchOperation> Operations; + public Version PatchVersion = new(1, 0, 0, 0); + public bool DangerousOperationsEnabled = false; + public Patch(string blueprintGuid, List<PatchOperation> operations, bool dangerousOperationsEnabled) { + BlueprintGuid = blueprintGuid; + Operations = operations; + DangerousOperationsEnabled = dangerousOperationsEnabled; + } +} diff --git a/ToyBox/Classes/MainUI/PatchTool/Infrastructure/PatchOperation.cs b/ToyBox/Classes/MainUI/PatchTool/Infrastructure/PatchOperation.cs new file mode 100644 index 000000000..12796d044 --- /dev/null +++ b/ToyBox/Classes/MainUI/PatchTool/Infrastructure/PatchOperation.cs @@ -0,0 +1,280 @@ +using HarmonyLib; +using Kingmaker.Blueprints; +using Kingmaker.ElementsSystem; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace ToyBox.PatchTool; +public class PatchOperation { + public enum PatchOperationType { + NoOperation = -1, + ModifyPrimitive, + ModifyUnityReference, + ModifyBlueprintReference, + ModifyComplex, + ModifyCollection, + NullField, + InitializeField + } + public enum CollectionPatchOperationType { + NoOperation = -1, + AddAtIndex, + RemoveAtIndex, + ModifyAtIndex + } + public string FieldName; + public Type NewValueType; + public object NewValue; + public int CollectionIndex; + public PatchOperation NestedOperation; + public Type PatchedObjectType; + public PatchOperationType OperationType = PatchOperationType.NoOperation; + public CollectionPatchOperationType CollectionOperationType = CollectionPatchOperationType.NoOperation; + public PatchOperation() { } + public PatchOperation(PatchOperationType operationType, string fieldName, Type newValueType, object newValue, Type patchedObjectType, PatchOperation nestedOperation = null) { + OperationType = operationType; + FieldName = fieldName; + NewValue = newValue; + PatchedObjectType = patchedObjectType; + NestedOperation = nestedOperation; + NewValueType = newValueType; + } + public PatchOperation(PatchOperationType operationType, string fieldName, Type newValueType, object newValue, Type patchedObjectType, CollectionPatchOperationType collectionOperationType, int collectionIndex, PatchOperation nestedOperation = null) { + OperationType = operationType; + FieldName = fieldName; + NewValue = newValue; + PatchedObjectType = patchedObjectType; + CollectionOperationType = collectionOperationType; + CollectionIndex = collectionIndex; + NestedOperation = nestedOperation; + NewValueType = newValueType; + } + public FieldInfo GetFieldInfo(Type type) { + return AccessTools.Field(type, FieldName); + } + public object Apply(object instance) { + var field = GetFieldInfo(PatchedObjectType); + if (PatchToolUtils.IsListOrArray(field.FieldType) && (OperationType != PatchOperationType.ModifyCollection) && (OperationType != PatchOperationType.NullField) && (OperationType != PatchOperationType.InitializeField)) { + // We're in a collection, so the patched field will point to a collection, meaning we will need to work on the instance itself. + // By returning the changed instance, the ModifyCollection operation will set the returned value itself. + field = null; + } + if (!(OperationType == PatchOperationType.ModifyCollection || OperationType == PatchOperationType.ModifyComplex) && (field != null && !field.FieldType.IsAssignableFrom(NewValueType))) throw new ArgumentException($"Type to patch {PatchedObjectType}, field {field.Name} with type {field.FieldType} is not assignable from instance type {instance.GetType()}\nField: {FieldName ?? null}, OperationType: {OperationType}, NestedOperationType: {NestedOperation?.OperationType.ToString() ?? "Null"} "); + + switch (OperationType) { + case PatchOperationType.ModifyCollection: { + object collection; + if (field == null) { + collection = instance; + } else { + collection = field.GetValue(instance); + } + switch (CollectionOperationType) { + case CollectionPatchOperationType.AddAtIndex: { + var newInst = PatchToolUtils.CreateObjectOfType(NewValueType); + if (collection.GetType() is Type type && type.IsArray) { + Array array = collection as Array; + if (CollectionIndex == -1) CollectionIndex = array.Length; + var elementType = type.GetElementType(); + Array newArray = Array.CreateInstance(elementType, array.Length + 1); + Array.Copy(array, 0, newArray, 0, CollectionIndex); + newArray.SetValue(newInst, CollectionIndex); + Array.Copy(array, CollectionIndex, newArray, CollectionIndex + 1, array.Length - CollectionIndex); + collection = newArray; + } else if (collection is IList list) { + if (CollectionIndex == -1) CollectionIndex = list.Count; + list.Insert(CollectionIndex, newInst); + collection = list; + } else if (PatchToolUtils.IsListOrArray(collection.GetType())) { + var interfaceType = collection.GetType().GetInterfaces().Where(i => i.IsGenericType).FirstOrDefault(i => i.GetGenericTypeDefinition() == typeof(IList<>)); + if (interfaceType != null) { + var m = collection.GetType().GetInterfaceMethodImplementation(interfaceType.GetMethod("Insert")); + if (m != null) { + m.Invoke(collection, [CollectionIndex, newInst]); + } else { + throw new ArgumentException($"Error while trying to use ArrayPatchOperation. Weird error. {field?.Name ?? "Null Field"}, {collection.GetType()}, {PatchedObjectType}"); + } + } else { + throw new ArgumentException($"Error while trying to use ArrayPatchOperation. Weird error2. {field?.Name ?? "Null Field"}, {collection.GetType()}, {PatchedObjectType}"); + } + } else { + throw new ArgumentException($"Error while trying to use patch \"AddAtIndex\". Collection is not Array, IList or ListOrArray. {field?.Name ?? "Null Field"}, {collection.GetType()}, {PatchedObjectType}"); + } + if (newInst is Element e && FieldName != nameof(SimpleBlueprint.m_AllElements)) { + Patcher.CurrentlyPatching.AddToElementsList(e); + } + } + break; + case CollectionPatchOperationType.RemoveAtIndex: { + object oldInst = null; + if (collection.GetType() is Type type && type.IsArray) { + Array array = collection as Array; + var elementType = type.GetElementType(); + var tmpList = PatchToolUtils.CreateObjectOfType(typeof(List<>).MakeGenericType(elementType)) as IList; + foreach (var item in array) + tmpList.Add(item); + oldInst = tmpList[CollectionIndex]; + tmpList.RemoveAt(CollectionIndex); + Array resizedArray = Array.CreateInstance(elementType, tmpList.Count); + tmpList.CopyTo(resizedArray, 0); + collection = resizedArray; + } else if (collection is IList list) { + oldInst = list[CollectionIndex]; + list.RemoveAt(CollectionIndex); + collection = list; + } else if (PatchToolUtils.IsListOrArray(collection.GetType())) { + var interfaceType = collection.GetType().GetInterfaces().Where(i => i.IsGenericType).FirstOrDefault(i => i.GetGenericTypeDefinition() == typeof(IList<>)); + if (interfaceType != null) { + var index_getter = collection.GetType().GetInterfaceMethodImplementation(interfaceType.GetProperties().First().GetGetMethod()); + var m = collection.GetType().GetInterfaceMethodImplementation(interfaceType.GetMethod("RemoveAt")); + if (m != null && index_getter != null) { + oldInst = index_getter.Invoke(collection, [CollectionIndex]); + m.Invoke(collection, [CollectionIndex]); + } else { + throw new ArgumentException($"Error while trying to use ArrayPatchOperation. Weird error. {field?.Name ?? "Null Field"}, {collection.GetType()}, {PatchedObjectType}"); + } + } else { + throw new ArgumentException($"Error while trying to use ArrayPatchOperation. Weird error2. {field?.Name ?? "Null Field"}, {collection.GetType()}, {PatchedObjectType}"); + } + } else { + throw new ArgumentException($"Error while trying to use patch \"ModifyAtIndex\". Collection is not Array, IList or ListOrArray. {field?.Name ?? "Null Field"}, {collection.GetType()}, {PatchedObjectType}"); + } + if (oldInst is Element e && FieldName != nameof(SimpleBlueprint.m_AllElements)) { + Patcher.CurrentlyPatching.RemoveFromElementsList(e); + } + } + break; + case CollectionPatchOperationType.ModifyAtIndex: { + object orig = null; + object modified = null; + if (collection.GetType() is Type type && type.IsArray) { + Array array = collection as Array; + orig = array.GetValue(CollectionIndex); + modified = NestedOperation.Apply(orig); + array.SetValue(modified, CollectionIndex); + collection = array; + } else if (collection is IList list) { + orig = list[CollectionIndex]; + modified = NestedOperation.Apply(orig); + list[CollectionIndex] = modified; + collection = list; + } else if (PatchToolUtils.IsListOrArray(collection.GetType())) { + var interfaceType = collection.GetType().GetInterfaces().Where(i => i.IsGenericType).FirstOrDefault(i => i.GetGenericTypeDefinition() == typeof(IList<>)); + if (interfaceType != null) { + var index_getter = collection.GetType().GetInterfaceMethodImplementation(interfaceType.GetProperties().First().GetGetMethod()); + var index_setter = collection.GetType().GetInterfaceMethodImplementation(interfaceType.GetProperties().First().GetSetMethod()); + if (index_getter != null && index_setter != null) { + orig = index_getter.Invoke(collection, [CollectionIndex]); + modified = NestedOperation.Apply(orig); + index_setter.Invoke(collection, [CollectionIndex, modified]); + } else { + throw new ArgumentException($"Error while trying to use ArrayPatchOperation. Weird error. {field?.Name ?? "Null Field"}, {collection.GetType()}, {PatchedObjectType}"); + } + } else { + throw new ArgumentException($"Error while trying to use ArrayPatchOperation. Weird error2. {field?.Name ?? "Null Field"}, {collection.GetType()}, {PatchedObjectType}"); + } + } else { + throw new ArgumentException($"Error while trying to use patch \"ModifyAtIndex\". Collection is not Array, IList or ListOrArray. {field?.Name ?? "Null Field"}, {collection.GetType()}, {PatchedObjectType}"); + } + if (orig is Element e && FieldName != nameof(SimpleBlueprint.m_AllElements)) { + Patcher.CurrentlyPatching.RemoveFromElementsList(e); + } + if (modified is Element e2 && FieldName != nameof(SimpleBlueprint.m_AllElements)) { + Patcher.CurrentlyPatching.AddToElementsList(e2); + } + } + break; + default: throw new NotImplementedException($"Unknown CollectionOperation: {CollectionOperationType}"); + } + field.SetValue(instance, collection); + } + break; + case PatchOperationType.ModifyUnityReference: { + throw new NotImplementedException("Modifying Unity Objects is not supported."); + } +#pragma warning disable CS0162 // Unreachable code detected + break; +#pragma warning restore CS0162 // Unreachable code detected + case PatchOperationType.ModifyComplex: { + object @object; + if (field == null) { + @object = instance; + } else { + @object = field.GetValue(instance); + } + var patched = NestedOperation.Apply(@object); + + if (PatchToolUtils.IsNullableStruct(NestedOperation.PatchedObjectType)) { + Type t = Nullable.GetUnderlyingType(NestedOperation.PatchedObjectType); + patched = Activator.CreateInstance(NestedOperation.PatchedObjectType, patched); + } + if (field != null) { + field.SetValue(instance, patched); + } else { + return patched; + } + } break; + case PatchOperationType.ModifyPrimitive: { + object patched; + if (typeof(Enum).IsAssignableFrom(NewValueType)) { + var tmp = Convert.ChangeType(NewValue, Enum.GetUnderlyingType(NewValueType)); + patched = Enum.ToObject(NewValueType, tmp); + } else { + patched = Convert.ChangeType(NewValue, NewValueType); + } + if (field != null) { + field.SetValue(instance, patched); + } else { + return patched; + } + } break; + case PatchOperationType.ModifyBlueprintReference: { + var bpRef = PatchToolUtils.CreateObjectOfType(NewValueType) as BlueprintReferenceBase; + bpRef.ReadGuidFromJson(NewValue as string); + var patched = Convert.ChangeType(bpRef, NewValueType); + if (field != null) { + field.SetValue(instance, patched); + } else { + return patched; + } + } + break; + case PatchOperationType.NullField: { + object patched = NewValueType.IsValueType ? PatchToolUtils.CreateObjectOfType(NewValueType) : null; + if (field != null) { + field.SetValue(instance, patched); + } else { + return patched; + } + } + break; + case PatchOperationType.InitializeField: { + object patched; + if (NewValueType.IsArray) { + patched = Array.CreateInstance(NewValueType.GetElementType(), 0); + } else if (PatchToolUtils.IsNullableStruct(NewValueType)) { + Type t = Nullable.GetUnderlyingType(NewValueType); + object @default = PatchToolUtils.CreateObjectOfType(t); + patched = Activator.CreateInstance(NewValueType, @default); + } else { + patched = PatchToolUtils.CreateObjectOfType(NewValueType); + } + if (patched is Element e) { + Patcher.CurrentlyPatching.AddToElementsList(e); + } + if (field != null) { + field.SetValue(instance, patched); + } else { + return patched; + } + } + break; + default: throw new NotImplementedException($"Unknown PatchOperation: {OperationType}"); + } + return instance; + } +} diff --git a/ToyBox/Classes/MainUI/PatchTool/Infrastructure/PatchState.cs b/ToyBox/Classes/MainUI/PatchTool/Infrastructure/PatchState.cs new file mode 100644 index 000000000..933e90995 --- /dev/null +++ b/ToyBox/Classes/MainUI/PatchTool/Infrastructure/PatchState.cs @@ -0,0 +1,70 @@ +using HarmonyLib; +using Kingmaker.Blueprints; +using ModKit; +using ModKit.Utility.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace ToyBox.PatchTool; +public class PatchState { + public SimpleBlueprint Blueprint; + public List<PatchOperation> Operations = new(); + public bool DangerousOperationsEnabled = false; + private Patch UnderlyingPatch; + public bool IsDirty = false; + public PatchState(SimpleBlueprint blueprint) { + SetupFromBlueprint(blueprint); + } + public PatchState(Patch patch) { + UnderlyingPatch = patch; + var bp = ResourcesLibrary.TryGetBlueprint(patch.BlueprintGuid); + if (!Patcher.AppliedPatches.ContainsKey(patch.BlueprintGuid)) { + patch.ApplyPatch(); + } + Operations = patch.Operations; + SetupFromBlueprint(bp); + } + public void SetupFromBlueprint(SimpleBlueprint blueprint) { + Blueprint = blueprint; + if (Patcher.KnownPatches.TryGetValue(blueprint.AssetGuid, out UnderlyingPatch)) { + Operations = UnderlyingPatch.Operations; + } + } + public void CreateAndRegisterPatch() { + if ((Operations?.Count ?? 0) == 0) { + if (Patcher.AppliedPatches.TryGetValue(Blueprint.AssetGuid, out var patch)) { + PatchListUI.DeletePatch(patch); + IsDirty = true; + } + return; + } + CreatePatch().RegisterPatch(); + IsDirty = true; + } + public Patch CreatePatch() { + try { + IsDirty = true; + if (UnderlyingPatch != null) { + UnderlyingPatch.Operations = Operations; + UnderlyingPatch.DangerousOperationsEnabled |= DangerousOperationsEnabled; + return UnderlyingPatch; + } else { + return new(Blueprint.AssetGuid.ToString(), Operations, DangerousOperationsEnabled); + } + } catch (Exception ex) { + Mod.Log($"Error trying to create patch for blueprint {Blueprint.AssetGuid}:\n{ex.ToString()}"); + } + return null; + } + public void AddOp(PatchOperation op) { + var foD = Operations.FirstOrDefault(i => i.OperationType == PatchOperation.PatchOperationType.ModifyPrimitive && i.PatchedObjectType == op.PatchedObjectType && i.FieldName == op.FieldName); + if (foD != default) { + Operations.Remove(foD); + } + Operations.Add(op); + } +} diff --git a/ToyBox/Classes/MainUI/PatchTool/Infrastructure/PatchToolJsonConverter.cs b/ToyBox/Classes/MainUI/PatchTool/Infrastructure/PatchToolJsonConverter.cs new file mode 100644 index 000000000..672c85c3d --- /dev/null +++ b/ToyBox/Classes/MainUI/PatchTool/Infrastructure/PatchToolJsonConverter.cs @@ -0,0 +1,47 @@ +using Kingmaker.Blueprints; +using ModKit; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ToyBox.PatchTool; +public class PatchToolJsonConverter : JsonConverter { + public override bool CanConvert(Type objectType) { + return objectType == typeof(PatchOperation); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { + var jsonObject = Newtonsoft.Json.Linq.JObject.Load(reader); + var operation = jsonObject.ToObject<PatchOperation>(); + + var typeString = (string)jsonObject["NewValueType"]; + if (!string.IsNullOrEmpty(typeString)) { + var targetType = Type.GetType(typeString); + if (targetType != null && !((string)jsonObject["NewValue"]).IsNullOrEmpty()) { + if (typeof(BlueprintReferenceBase).IsAssignableFrom(targetType)) { + operation.NewValue = jsonObject["NewValue"].ToObject<string>(serializer); + } else if (typeof(Enum).IsAssignableFrom(targetType)) { + operation.NewValue = Enum.Parse(targetType, jsonObject["NewValue"].ToObject<string>(serializer)); + } else { + operation.NewValue = jsonObject["NewValue"].ToObject(targetType, serializer); + } + } + } + if (jsonObject["NestedOperation"] != null && jsonObject["NestedOperation"].Type != Newtonsoft.Json.Linq.JTokenType.Null) { + operation.NestedOperation = jsonObject["NestedOperation"].ToObject<PatchOperation>(serializer); + } + + + return operation; + } + public override bool CanWrite { + get { return false; } + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { + throw new NotImplementedException("Unnecessary because CanWrite is false. The type will skip the converter."); + } +} diff --git a/ToyBox/Classes/MainUI/PatchTool/Infrastructure/PatchToolPatches.cs b/ToyBox/Classes/MainUI/PatchTool/Infrastructure/PatchToolPatches.cs new file mode 100644 index 000000000..acbe0c8d0 --- /dev/null +++ b/ToyBox/Classes/MainUI/PatchTool/Infrastructure/PatchToolPatches.cs @@ -0,0 +1,30 @@ +using HarmonyLib; +using Kingmaker.Blueprints.JsonSystem; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ToyBox.PatchTool; + +[HarmonyPatch] +public static class PatchToolPatches { + private static bool Initialized = false; + [HarmonyPriority(Priority.LowerThanNormal)] + [HarmonyPatch(typeof(StartGameLoader), nameof(StartGameLoader.LoadPackTOC)), HarmonyPostfix] + public static void Init_Postfix() { + try { + if (Initialized) { + ModKit.Mod.Log("Already initialized blueprints cache."); + return; + } + Initialized = true; + + ModKit.Mod.Log("Patching blueprints."); + Patcher.PatchAll(); + } catch (Exception e) { + ModKit.Mod.Log(string.Concat("Failed to initialize.", e)); + } + } +} diff --git a/ToyBox/Classes/MainUI/PatchTool/Infrastructure/Patcher.cs b/ToyBox/Classes/MainUI/PatchTool/Infrastructure/Patcher.cs new file mode 100644 index 000000000..46b2a01f1 --- /dev/null +++ b/ToyBox/Classes/MainUI/PatchTool/Infrastructure/Patcher.cs @@ -0,0 +1,140 @@ +using HarmonyLib; +using Kingmaker.Blueprints; +using ModKit; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; + +namespace ToyBox.PatchTool; +public static class Patcher { + public static readonly Version CurrentPatchVersion = new(1, 1, 1, 0); + public static Dictionary<string, SimpleBlueprint> OriginalBps = new(); + public static Dictionary<string, Patch> AppliedPatches = new(); + public static Dictionary<string, Patch> KnownPatches = new(); + public static HashSet<Patch> FailedPatches = new(); + public static SimpleBlueprint CurrentlyPatching = null; + public static bool IsInitialized = false; + public static string PatchDirectoryPath => Path.Combine(Main.ModEntry.Path, "Patches"); + public static string PatchFilePath(Patch patch) => Path.Combine(PatchDirectoryPath, $"{patch.BlueprintGuid}_{patch.PatchId}.json"); + public static void PatchAll() { + if (!IsInitialized) { + Directory.CreateDirectory(PatchDirectoryPath); + var settings = new JsonSerializerSettings(); + settings.Converters.Add(new PatchToolJsonConverter()); + foreach (var file in Directory.GetFiles(PatchDirectoryPath)) { + try { + var patch = JsonConvert.DeserializeObject<Patch>(File.ReadAllText(file), settings); + + // Update old patches; 1.0 => 1.1: Serialize enums as strings + if ((patch.PatchVersion ?? new(1, 0)) < CurrentPatchVersion) { + patch.RegisterPatch(true); + } + + KnownPatches[patch.BlueprintGuid] = patch; + } catch (Exception ex) { + Mod.Log($"Error trying to load patch file {file}:\n{ex.ToString()}"); + } + } + IsInitialized = true; + } + Stopwatch watch = new(); + watch.Start(); + int applied = 0; + foreach (var patch in KnownPatches.Values) { + if (!Main.Settings.disabledPatches.Contains(patch.PatchId)) { + if (patch.ApplyPatch()) { + applied++; + } + } + } + watch.Stop(); + Mod.Log($"Successfully applied {applied} of {KnownPatches.Values.Count} patches in {watch.ElapsedMilliseconds}ms"); + } + private static SimpleBlueprint ApplyPatch(this SimpleBlueprint blueprint, Patch patch) { + CurrentlyPatching = blueprint; + foreach (var operation in patch.Operations) { + try { + operation.Apply(blueprint); + blueprint.OnEnable(); + } catch (Exception ex) { + Mod.Warn($"Error trying to patch blueprint {patch.BlueprintGuid} with patch {patch.PatchId}:\n{ex.ToString()}, Operation {patch.Operations.IndexOf(operation) + 1}/{patch.Operations.Count}"); + throw; + } + } + CurrentlyPatching = null; + AppliedPatches[blueprint.AssetGuid.ToString()] = patch; + return blueprint; + } + public static bool ApplyPatch(this Patch patch) { + if (patch == null) return false; + if (patch.DangerousOperationsEnabled && !Main.Settings.toggleEnableDangerousPatchToolPatches) { + Mod.Warn($"Tried to apply patch {patch.PatchId} to Blueprint {patch.BlueprintGuid}, but dangerous patches are disabled!"); + return false; + } + Mod.Log($"Patching Blueprint {patch.BlueprintGuid} with Patch {(patch.DangerousOperationsEnabled ? "!Dangerous Patch! " : "")}{patch.PatchId}."); + FailedPatches.Remove(patch); + var current = ResourcesLibrary.TryGetBlueprint(patch.BlueprintGuid); + if (current == null) { + Mod.Warn($"Target blueprint {patch.BlueprintGuid} for patch {patch.PatchId} does not exist!"); + FailedPatches.Add(patch); + return false; + } + + // Consideration: DeepCopies are only necessary to allow reverting actions; meaning they are only needed if users plan to change patches in the current session + // By adding a "Dev Mode" setting, it would be possible to completely drop DeepCopies, making this pretty performant. + + if (!OriginalBps.ContainsKey(current.AssetGuid)) { + OriginalBps[current.AssetGuid] = DeepBlueprintCopy(current); + } else { + current = DeepBlueprintCopy(OriginalBps[current.AssetGuid], current); + } + try { + current.ApplyPatch(patch); + } catch (Exception) { + RestoreOriginal(patch.BlueprintGuid); + FailedPatches.Add(patch); + return false; + } + return true; + } + public static void RestoreOriginal(string blueprintGuid) { + Mod.Log($"Trying to restore original Blueprint {blueprintGuid}"); + if (OriginalBps.TryGetValue(blueprintGuid, out var copy)) { + Mod.Log($"Found original blueprint; reverting."); + var bp = ResourcesLibrary.TryGetBlueprint(blueprintGuid); + DeepBlueprintCopy(copy, bp); + AppliedPatches.Remove(blueprintGuid); + } else { + Mod.Error("No original blueprint found! Was it never patched?"); + } + } + public static void RegisterPatch(this Patch patch, bool isPatchUpdate = false) { + if (patch == null) return; + try { + if (isPatchUpdate) { + Mod.Log($"Updating patch {patch.PatchId} for blueprint {patch.BlueprintGuid}\nVersion {patch.PatchVersion} to {CurrentPatchVersion}"); + } + var userPatchesFolder = + Directory.CreateDirectory(PatchDirectoryPath); + var settings = new JsonSerializerSettings() { Formatting = Formatting.Indented }; + settings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter()); + patch.PatchVersion = CurrentPatchVersion; + File.WriteAllText(PatchFilePath(patch), JsonConvert.SerializeObject(patch, settings)); + KnownPatches[patch.BlueprintGuid] = patch; + if (!isPatchUpdate) { + patch.ApplyPatch(); + } + } catch (Exception ex) { + if (isPatchUpdate) { + Mod.Log($"Error updating patch {patch.PatchId}:\n{ex.ToString()}"); + } else { + Mod.Log($"Error registering patch for blueprint {patch.BlueprintGuid} with patch {patch.PatchId}:\n{ex.ToString()}"); + } + } + } + public static SimpleBlueprint DeepBlueprintCopy(SimpleBlueprint blueprint, SimpleBlueprint target = null) { + return blueprint.Copy(target, true); + } +} diff --git a/ToyBox/Classes/MainUI/PatchTool/UI/AddItemState.cs b/ToyBox/Classes/MainUI/PatchTool/UI/AddItemState.cs new file mode 100644 index 000000000..33523e238 --- /dev/null +++ b/ToyBox/Classes/MainUI/PatchTool/UI/AddItemState.cs @@ -0,0 +1,123 @@ +using Kingmaker.Utility; +using ModKit; +using ModKit.Utility.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UniRx; +using UnityEngine; + +namespace ToyBox.PatchTool; +public class AddItemState { + internal static Dictionary<Type, List<Type>> compatibleTypes = new(); + internal static Dictionary<Type, List<Type>> allowedTypes = new(); + public Browser<Type, Type> ToAddBrowser = new(true, true, false, false) { DisplayShowAllGUI = false }; + private Action<Type> confirmAction; + public static AddItemState CreateComplexOrList(object parent, FieldInfo info, PatchOperation wouldBePatch, PatchToolTabUI ui, string path) { + Type elementType = info.FieldType; + var state = new AddItemState() { + Parent = parent, + Info = info, + ElementType = elementType, + Item = null, + IsExpanded = true, + WouldBePatch = wouldBePatch, + Path = path + }; + state.confirmAction = (Type t) => { + PatchOperation op = new(PatchOperation.PatchOperationType.InitializeField, state.Info.Name, t, null, parent?.GetType()); + ui.CurrentState.AddOp(state.WouldBePatch.AddOperation(op)); + ui.CurrentState.CreateAndRegisterPatch(); + ui.addItemStates.Remove(state.Path); + }; + ui.addItemStates[path] = state; + + if (!compatibleTypes.ContainsKey(elementType)) { + (var all, var allowed) = PatchToolUtils.GetInstantiableTypes(elementType, parent); + if (allowed != null) { + state.ToAddBrowser.DisplayShowAllGUI = true; + } + allowedTypes[elementType] = allowed?.ToList(); + compatibleTypes[elementType] = all.ToList(); + } + + return state; + } + public static AddItemState CreateArrayElement(object parent, FieldInfo info, object @object, int index, PatchOperation wouldBePatch, PatchToolTabUI ui, string path) { + Type elementType = null; + Type type = @object.GetType() ?? info.FieldType; + if (type.IsArray) { + elementType = type.GetElementType(); + } else { + try { + elementType = type.GetInterfaces()?.FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IList<>).GetGenericArguments()?[0]); + elementType ??= type.GetGenericArguments()?[0]; + } catch (Exception ex) { + Mod.Log($"Error while trying to create AddItemProcess:\n{ex.ToString()}"); + } + } + if (elementType == null) { + Mod.Log($"Error while trying to create AddItemProcess:\nCan't find element type for type {type}"); + return null; + } + var state = new AddItemState() { + Parent = parent, + Info = info, + Index = index, + ElementType = elementType, + Collection = @object, + Item = null, + IsExpanded = true, + WouldBePatch = wouldBePatch, + Path = path + }; + state.confirmAction = (Type t) => { + PatchOperation op = new(PatchOperation.PatchOperationType.ModifyCollection, state.Info.Name, t, null, state.Parent.GetType(), PatchOperation.CollectionPatchOperationType.AddAtIndex, state.Index); + ui.CurrentState.AddOp(state.WouldBePatch.AddOperation(op)); + ui.CurrentState.CreateAndRegisterPatch(); + ui.addItemStates.Remove(state.Path); + }; + ui.addItemStates[path] = state; + + if (!compatibleTypes.ContainsKey(elementType)) { + (var all, var allowed) = PatchToolUtils.GetInstantiableTypes(elementType, parent); + if (allowed != null) { + state.ToAddBrowser.DisplayShowAllGUI = true; + } + allowedTypes[elementType] = allowed?.ToList(); + compatibleTypes[elementType] = all.ToList(); + } + + return state; + } + public void AddItemGUI() { + using (VerticalScope()) { + ToAddBrowser.OnGUI(allowedTypes[ElementType] ?? compatibleTypes[ElementType], () => compatibleTypes[ElementType], d => d, t => $"{t.ToString()}", t => [$"{t.ToString()} {t.Name}"], null, + (type, maybeType) => { + string generics = ""; + if (type.IsGenericType) { + generics = type.GetGenericArguments().Select(t => t.Name).ToContentString().Replace("\"", ""); + } + Label($"{type.Name}{generics}", Width(500)); + Space(200); + ActionButton("Add as new entry".localize(), () => { + Confirm(type); + }); + } + ); + } + } + public void Confirm(Type type) { + confirmAction(type); + } + public object Parent; + public FieldInfo Info; + public int Index; + public object Collection; + public Type ElementType; + public object Item; + public bool IsExpanded; + public PatchOperation WouldBePatch; + public string Path; +} \ No newline at end of file diff --git a/ToyBox/Classes/MainUI/PatchTool/UI/BlueprintPickerGUI.cs b/ToyBox/Classes/MainUI/PatchTool/UI/BlueprintPickerGUI.cs new file mode 100644 index 000000000..66d85db11 --- /dev/null +++ b/ToyBox/Classes/MainUI/PatchTool/UI/BlueprintPickerGUI.cs @@ -0,0 +1,104 @@ +using Kingmaker; +using Kingmaker.Blueprints; +using Kingmaker.Blueprints.Items; +using Kingmaker.UnitLogic.Levelup.Obsolete.Blueprints.Selection; +using ModKit; +using ModKit.DataViewer; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ToyBox.classes.Infrastructure.Blueprints; +using UnityEngine; + +namespace ToyBox.PatchTool; +public class BlueprintPickerGUI { + private string pickerText = ""; + private bool noSuchBP = false; + private bool showBrowserPicker = false; + private bool showBrowser = false; + private Browser<SimpleBlueprint, SimpleBlueprint> browser = new(true) { DisplayShowAllGUI = false }; + private Type category = null; + private string categorySearchString = ""; + private List<Type> categories = BlueprintIdCache.CachedIdTypes.Concat(typeof(SimpleBlueprint)).OrderBy(a => a.Name).ToList(); + private IEnumerable<SimpleBlueprint> bps; + public void OnGUI(Action<string> callback, Type setCategory = null) { + using (HorizontalScope()) { + Space(20); + using (VerticalScope()) { + if (setCategory == null) DisclosureToggle("Show Browser Picker".localize(), ref showBrowserPicker); + else category = setCategory; + if (showBrowserPicker || setCategory != null) { + if (setCategory == null) { + if (GridPicker("BP Category", ref category, categories, "Nothing", t => t.Name, ref categorySearchString)) { + browser.ReloadData(); + bps = null; + } + Space(20); + } + if (category != null) { + DisclosureToggle("Show Browser (starts loading all blueprints of the selected type)".localize(), ref showBrowser); + if (showBrowser) { + if ((bps?.Count() ?? 0) > 0) { + browser.OnGUI(bps, () => bps, bp => bp, bp => BlueprintExtensions.GetSearchKey(bp), bp => [BlueprintExtensions.GetSortKey(bp)], null, + (bp, maybeBp) => { + BlueprintExtensions.GetTitle(bp); + string description = ""; + Func<string, string> titleFormatter = (t) => RichText.Bold(RichText.Orange(t)); + if (bp is BlueprintItem itemBlueprint && itemBlueprint.FlavorText?.Length > 0) + description = $"{itemBlueprint.FlavorText.StripHTML().Color(RGBA.notable)}\n{description}"; + else description = bp.GetDescription() ?? ""; + using (HorizontalScope()) { + string title = BlueprintExtensions.GetTitle(bp, name => RichText.Bold(RichText.Cyan(name))); + Space(10); + var typeString = bp.GetType().Name; + using (HorizontalScope()) { + ActionButton("Pick Blueprint".localize(), () => { + callback(bp.AssetGuid); + }); + Space(17); + Label(title, Width(300)); + ReflectionTreeView.DetailToggle("", bp, bp, 0); + Space(-17); + Label(typeString, rarityButtonStyle, AutoWidth()); + Space(17); + ClipboardLabel(bp.AssetGuid.ToString(), ExpandWidth(false), Width(300)); + Space(17); + if (description.Length > 0) Label(RichText.Green(description), Width(1000)); + } + } + }, + (bp, maybeBp) => { + ReflectionTreeView.OnDetailGUI(bp); + }); + } else if (Event.current.type == EventType.Repaint) { + bps = BlueprintLoader.BlueprintsOfType(category).NotNull(); + } + } + } + } + Space(20); + using (HorizontalScope()) { + Label("Enter target blueprint id".localize(), Width(200)); + var before = pickerText; + TextField(ref pickerText, null, Width(350)); + if (before != pickerText) { + noSuchBP = false; + } + ActionButton("Pick Blueprint".localize(), () => { + if (ResourcesLibrary.BlueprintsCache.m_LoadedBlueprints.ContainsKey(pickerText)) { + callback(pickerText); + } else { + noSuchBP = true; + } + }); + if (noSuchBP) { + Space(20); + Label("No blueprint with that guid found.".localize().Yellow(), Width(300)); + } + } + } + } + } +} \ No newline at end of file diff --git a/ToyBox/Classes/MainUI/PatchTool/UI/PatchListUI.cs b/ToyBox/Classes/MainUI/PatchTool/UI/PatchListUI.cs new file mode 100644 index 000000000..f19b40f85 --- /dev/null +++ b/ToyBox/Classes/MainUI/PatchTool/UI/PatchListUI.cs @@ -0,0 +1,74 @@ +using Kingmaker.Blueprints; +using ModKit; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ToyBox.PatchTool; +public static class PatchListUI { + private static Browser<Patch, Patch> _patchBrowser = new(true) { DisplayShowAllGUI = false }; + public static void OnGUI() { + if (!Patcher.IsInitialized) { + Label("Patches not loaded yet...".localize()); + } else { + _patchBrowser.OnGUI(Patcher.KnownPatches.Values, () => Patcher.KnownPatches.Values, p => p, p => $"{ResourcesLibrary.BlueprintsCache.Load(p.BlueprintGuid).NameSafe()} {p.BlueprintGuid} {p.PatchId}", p => [$"{ResourcesLibrary.BlueprintsCache.Load(p.BlueprintGuid).name}", p.BlueprintGuid], + () => { + Label("Blueprint".localize().Green(), Width(600)); + Space(50); + Label("PatchId".localize().Green(), Width(300)); + Space(50); + Label("Applied?".Green()); + }, + (patch, maybePatch) => { + var bp = ResourcesLibrary.BlueprintsCache.Load(patch.BlueprintGuid); + Label($"{bp.NameSafe()} ({patch.BlueprintGuid})", Width(600)); + Space(50); + Label($"{patch.PatchId}", Width(300)); + Space(50); + if (Patcher.AppliedPatches.TryGetValue(patch.BlueprintGuid, out var patch2) && patch2.PatchId == patch.PatchId) { + Label("Yes".localize(), Width(50)); + } else if (Patcher.FailedPatches.Contains(patch)) { + Label("Failed!".localize().Red(), Width(50)); + } else { + Label("No".localize(), Width(50)); + } + Space(50); + if (Main.Settings.disabledPatches.Contains(patch.PatchId)) { + ActionButton("Enable".localize(), () => { + Main.Settings.disabledPatches.Remove(patch.PatchId); + patch.ApplyPatch(); + }, Width(100)); + } else { + ActionButton("Disable".localize(), () => { + Main.Settings.disabledPatches.Add(patch.PatchId); + if (Patcher.AppliedPatches.Values.Contains(patch)) { + Patcher.RestoreOriginal(patch.BlueprintGuid); + } + }, Width(100)); + } + Space(50); + ActionButton("Open in Tab".localize(), () => { + PatchToolUIManager.OpenBlueprintInTab(patch.BlueprintGuid); + }); + Space(100); + ActionButton("Delete".localize(), () => { + DeletePatch(patch); + }); + }); + } + } + public static void DeletePatch(Patch patch) { + Patcher.KnownPatches.Remove(patch.BlueprintGuid); + _patchBrowser.ReloadData(); + var patchFile = Patcher.PatchFilePath(patch); + if (File.Exists(patchFile)) { + File.Delete(patchFile); + } + if (Patcher.AppliedPatches.ContainsKey(patch.BlueprintGuid)) { + Patcher.RestoreOriginal(patch.BlueprintGuid); + } + } +} diff --git a/ToyBox/Classes/MainUI/PatchTool/UI/PatchToolTabUI.cs b/ToyBox/Classes/MainUI/PatchTool/UI/PatchToolTabUI.cs new file mode 100644 index 000000000..0e5a4d991 --- /dev/null +++ b/ToyBox/Classes/MainUI/PatchTool/UI/PatchToolTabUI.cs @@ -0,0 +1,580 @@ +using HarmonyLib; +using Kingmaker.Blueprints; +using Kingmaker.Localization; +using Kingmaker.UnitLogic; +using Kingmaker.Utility; +using ModKit; +using ModKit.DataViewer; +using ModKit.Utility.Extensions; +using Newtonsoft.Json; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using UniRx; +using UnityEngine; +using UnityEngine.UI; + +namespace ToyBox.PatchTool; +public class PatchToolTabUI { + public PatchState CurrentState; + private Dictionary<string, BlueprintPickerGUI> pickerGUIs = new(); + private Dictionary<string, object> editStates = new(); + private Dictionary<string, Dictionary<FieldInfo, object>> fieldsByObject = new(); + private Dictionary<string, bool> toggleStates = new(); + private Dictionary<string, bool> listToggleStates = new(); + internal Dictionary<string, AddItemState> addItemStates = new(); + private HashSet<object> visited = new(); + private bool showBlueprintPicker = false; + private bool showPatchManager = false; + private bool showFieldsEditor = false; + internal string Target = ""; + public int IndentPerLevel = 25; + internal static readonly HashSet<Type> primitiveTypes = new() { typeof(string), typeof(bool), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(sbyte), typeof(byte), typeof(short), typeof(ushort) }; + public PatchToolTabUI() { + pickerGUIs[""] = new(); + } + public PatchToolTabUI(string guid) : this() { + Target = guid; + } + public void SetTarget(string guid) { + CurrentState = null; + ClearCache(); + Target = guid; + showBlueprintPicker = false; + } + public void OnGUI() { + visited.Clear(); + DisclosureToggle("Show Blueprint Picker", ref showBlueprintPicker); + if (showBlueprintPicker) { + pickerGUIs[""].OnGUI(SetTarget); + } + if ((CurrentState == null || CurrentState.IsDirty) && !Target.IsNullOrEmpty()) { + if (Event.current.type == EventType.Layout) { + ClearCache(Main.Settings.togglePatchToolCollapseAllPathsOnPatch); + var bp = ResourcesLibrary.TryGetBlueprint(Target); + if (bp != null) { + CurrentState = new(bp); + } + } + } + if (CurrentState != null) { + Space(15); + #region PatchManager + Div(); + Space(15); + DisclosureToggle("Show Patch Manager".localize(), ref showPatchManager); + if (showPatchManager) { + using (HorizontalScope()) { + Space(20); + using (VerticalScope()) { + using (HorizontalScope()) { + Label($"Current Patch targets bp: {BlueprintExtensions.GetTitle(CurrentState.Blueprint).Cyan()}({CurrentState.Blueprint.name ?? CurrentState.Blueprint.AssetGuid}) and has {CurrentState.Operations.Count.ToString().Cyan()} operations."); + Space(30); + using (VerticalScope()) { + int count = 0; + foreach (var op in CurrentState.Operations.ToList()) { + count++; + using (HorizontalScope()) { + Label($"Operation: {op.OperationType}", Width(200)); + Space(20); + Label($"Field: {op.FieldName}", Width(300)); + Space(20); + ReflectionTreeView.DetailToggle("Inspect".localize(), op, op, 0); + Space(20); + if (count == CurrentState.Operations.Count) { + ActionButton("Remove".localize(), () => { + CurrentState.Operations.Remove(op); + }); + } + } + ReflectionTreeView.OnDetailGUI(op); + } + } + } + Space(10); + ActionButton("Apply Changes".localize(), () => { + CurrentState.CreateAndRegisterPatch(); + }); + } + } + } + Space(15); + #endregion + #region Settings + Div(); + Space(15); + Label("Configure which types of fields to show:".localize()); + using (HorizontalScope()) { + Toggle("Primitives".localize(), ref Main.Settings.showPatchToolPrimitiveTypes); + Space(10); + Toggle("Enums".localize(), ref Main.Settings.showPatchToolEnums); + Space(10); + Toggle("Blueprint References".localize(), ref Main.Settings.showPatchToolBlueprintReferences); + Space(10); + Toggle("Collections".localize(), ref Main.Settings.showPatchToolCollections); + Space(10); + Toggle("Complex Types".localize(), ref Main.Settings.showPatchToolComplexTypes); + Space(10); + Toggle("Show Unity Objects".localize(), ref Main.Settings.showPatchToolUnityObjects); + } + + Space(15); + Label("Other Settings:".localize()); + using (HorizontalScope()) { + Toggle("Show Delete Button".localize(), ref Main.Settings.showPatchToolDeleteButtons); + Space(10); + Toggle("Show Create Button".localize(), ref Main.Settings.showPatchToolCreateButtons); + Space(10); + Toggle("Close all opened fields on patch".localize(), ref Main.Settings.togglePatchToolCollapseAllPathsOnPatch); + Space(10); + if (!CurrentState.DangerousOperationsEnabled && Main.Settings.toggleEnableDangerousPatchToolPatches) { + ActionButton("Enable Dangerous Patches for this Patch".localize(), () => { + CurrentState.DangerousOperationsEnabled = true; + }); + } + } + Space(15); + #endregion + Div(); + Space(15); + DisclosureToggle("Show Fields Editor".localize(), ref showFieldsEditor); + if (showFieldsEditor) { + using (HorizontalScope()) { + Space(20); + Space(-IndentPerLevel); + NestedGUI(CurrentState.Blueprint); + } + } + } + } + public void ClearCache(bool resetToggleStates = true) { + pickerGUIs.Clear(); + pickerGUIs[""] = new(); + editStates.Clear(); + fieldsByObject.Clear(); + addItemStates.Clear(); + if (resetToggleStates) { + toggleStates.Clear(); + listToggleStates.Clear(); + } + AddItemState.compatibleTypes.Clear(); + AddItemState.allowedTypes.Clear(); + } + #region PerField + private void NestedGUI(object o, string path = "", PatchOperation wouldBePatch = null, Type overridenType = null) { + if (visited.Contains(o)) { + if (!(o?.GetType()?.IsValueType ?? false)) { + Label("Already opened on another level!".localize().Green()); + return; + } + } else { + visited.Add(o); + } + Dictionary<FieldInfo, object> fbo; + var oType = o?.GetType(); + Type type = overridenType ?? oType; + if (oType != null && overridenType != null && overridenType.IsAssignableFrom(oType)) { + type = oType; + } + if (!fieldsByObject.ContainsKey(path)) { + PopulateFieldsAndObjects(o, path, type); + } + fbo = fieldsByObject[path]; + using (VerticalScope()) { + foreach (var field in fbo) { + var path2 = path + field.Key.Name; + using (HorizontalScope()) { + if (ShouldDisplayField(field.Key.FieldType)) { + bool isEnum = typeof(Enum).IsAssignableFrom(field.Key.FieldType); + bool isFlagEnum = field.Key.FieldType.IsDefined(typeof(FlagsAttribute), false); + string generics = ""; + if (field.Key.FieldType.IsGenericType) { + generics = field.Key.FieldType.GetGenericArguments().Select(t => t.Name).ToContentString().Replace("\"", ""); + } + Space(IndentPerLevel); + if (Main.Settings.showPatchToolDeleteButtons && field.Value != null) { + using (HorizontalScope(Width(100))) { + ActionButton("Delete".localize().Red().Bold(), () => { + PatchOperation tmpOp = new(PatchOperation.PatchOperationType.NullField, field.Key.Name, field.Key.FieldType, null, type); + PatchOperation op = wouldBePatch.AddOperation(tmpOp); + CurrentState.AddOp(op); + CurrentState.CreateAndRegisterPatch(); + }, AutoWidth()); + } + } else if (Main.Settings.showPatchToolCreateButtons && field.Value == null && !PatchToolUtils.TypeOrBaseIsDirectlyInUnityDLL(field.Key.FieldType)) { + using (HorizontalScope(Width(100))) { + ActionButton("Create".localize().Green().Bold(), () => { + AddItemState.CreateComplexOrList(o, field.Key, wouldBePatch, this, path2 + "2"); + }, AutoWidth()); + } + } + if (toggleStates.TryGetValue(path2, out var shouldPaint) && shouldPaint) { + Label($"{field.Key.Name} ({(isFlagEnum ? "Flag " : "")}{(isEnum ? "Enum: " : "")}{field.Key.FieldType.Name}{generics})".Cyan(), Width(500)); + } else { + Label($"{field.Key.Name} ({(isFlagEnum ? "Flag " : "")}{(isEnum ? "Enum: " : "")}{field.Key.FieldType.Name}{generics})", Width(500)); + } + FieldGUI(o, type, wouldBePatch, field.Key.FieldType, field.Value, field.Key, path2); + } + } + if (addItemStates.TryGetValue(path2 + "2", out var activeAddItemState)) { + using (HorizontalScope()) { + Label("New Item:".localize(), Width(500)); + activeAddItemState.AddItemGUI(); + } + } + } + } + } + private bool ShouldDisplayField(Type fieldType) { + if (primitiveTypes.Contains(fieldType)) { + return Main.Settings.showPatchToolPrimitiveTypes; + } else if (typeof(Enum).IsAssignableFrom(fieldType)) { + return Main.Settings.showPatchToolEnums; + } else if (typeof(BlueprintReferenceBase).IsAssignableFrom(fieldType)) { + return Main.Settings.showPatchToolBlueprintReferences; + } else if (PatchToolUtils.IsListOrArray(fieldType)) { + return Main.Settings.showPatchToolCollections; + } else if (typeof(UnityEngine.Object).IsAssignableFrom(fieldType)) { + return Main.Settings.showPatchToolUnityObjects; + } else { + return Main.Settings.showPatchToolComplexTypes; + } + } + #endregion + #region PerRow + private void FieldGUI(object parent, Type parentType, PatchOperation wouldBePatch, Type type, object @object, FieldInfo info, string path) { + if (typeof(Enum).IsAssignableFrom(type)) { + var isFlagEnum = type.IsDefined(typeof(FlagsAttribute), false); + if (!toggleStates.TryGetValue(path, out var state)) { + state = false; + } + if (state) { + Label(@object.ToString().Cyan(), Width(500)); + } else { + Label(@object.ToString(), Width(500)); + } + DisclosureToggle("Show Values".localize(), ref state, 800); + Space(-800); + toggleStates[path] = state; + if (state) { + using (VerticalScope()) { + Label(""); + using (HorizontalScope()) { + if (!editStates.TryGetValue(path, out var curValue)) { + if (isFlagEnum) curValue = Convert.ChangeType(@object, Enum.GetUnderlyingType(type)); + else curValue = 0; + } + var vals = Enum.GetValues(type).Cast<object>(); + var enumNames = vals.Select(val => val.ToString()).ToArray(); + var enumValues = vals.Select(Convert.ToInt64).ToArray(); + var cellsPerRow = Math.Min(4, enumNames.Length); + if (isFlagEnum) { + var tmp = Convert.ToInt64(curValue); + int totalFlags = vals.Count(); + int rows = (totalFlags + cellsPerRow - 1) / cellsPerRow; + + using (VerticalScope()) { + int flagIndex = 0; + for (int row = 0; row < rows; row++) { + using (HorizontalScope()) { + for (int col = 0; col < cellsPerRow && flagIndex < totalFlags; col++, flagIndex++) { + var flagName = enumNames[flagIndex]; + var flagValue = enumValues[flagIndex]; + var isSet = (tmp & flagValue) != 0; + bool newIsSet = isSet; + + Toggle(flagName, ref newIsSet, Width(200)); + + if (newIsSet != isSet) { + if (newIsSet) { + tmp |= flagValue; + } else { + tmp &= ~flagValue; + } + } + } + } + } + editStates[path] = tmp; + ActionButton("Change".localize(), () => { + var underlyingType = Enum.GetUnderlyingType(type); + var convertedValue = Convert.ChangeType(tmp, underlyingType); + var newValue = Enum.ToObject(type, convertedValue); + PatchOperation tmpOp = new(PatchOperation.PatchOperationType.ModifyPrimitive, info.Name, type, newValue, parentType); + PatchOperation op = wouldBePatch.AddOperation(tmpOp); + CurrentState.AddOp(op); + CurrentState.CreateAndRegisterPatch(); + }); + } + } else { + var tmp = (int)curValue; + SelectionGrid(ref tmp, enumNames, cellsPerRow, Width(200 * cellsPerRow)); + editStates[path] = tmp; + Space(20); + ActionButton("Change".localize(), () => { + PatchOperation tmpOp = new(PatchOperation.PatchOperationType.ModifyPrimitive, info.Name, type, Enum.Parse(type, enumNames[tmp]), parentType); + PatchOperation op = wouldBePatch.AddOperation(tmpOp); + CurrentState.AddOp(op); + CurrentState.CreateAndRegisterPatch(); + }); + } + } + } + } + } else if (PatchToolUtils.TypeOrBaseIsDirectlyInUnityDLL(type) && type != typeof(SharedStringAsset) && !CurrentState.DangerousOperationsEnabled) { + if (@object == null) { + Label("Null", Width(500)); + return; + } + string label; + try { + label = @object.ToString(); + } catch (Exception ex) { + Mod.Trace($"Error in FieldGUI ToString for field {info.Name}:\n{ex.ToString()}"); + label = "Exception in ToString".Orange(); + } + Label(label, Width(500)); + Label("Unity Object".localize(), Width(300)); + Space(20); + if (@object is Sprite sprite) { + ActionButton("Dump to ToyBox folder", () => { + var dumpDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + sprite.texture.SaveTextureToFile(Path.Combine(dumpDir, sprite.name + ".png"), -1, -1, MiscExtensions.SaveTextureFileFormat.PNG, 100, false); + try { + Application.OpenURL($"file://{dumpDir}"); + } catch { } + }); + } + } else if (typeof(BlueprintReferenceBase).IsAssignableFrom(type)) { + if (!toggleStates.TryGetValue(path, out var state)) { + state = false; + } + var label = (@object as BlueprintReferenceBase)?.Guid?.ToString(); + if (label.IsNullOrEmpty()) label = "Null or Empty Reference"; + else { + var bp = (@object as BlueprintReferenceBase)?.GetBlueprint(); + if (bp != null) { + label = BlueprintExtensions.GetTitle(bp) + $" ({label})"; + } else { + label = "Invalid Reference".Orange() + $" ({label})"; + } + } + if (state) { + Label(label.Cyan(), Width(500)); + } else { + Label(label, Width(500)); + } + DisclosureToggle("Edit Reference".localize(), ref state, 200); + toggleStates[path] = state; + if (state) { + if (!pickerGUIs.TryGetValue(path, out var gui)) { + gui = new(); + pickerGUIs[path] = gui; + } + var t = PatchToolUtils.GetBlueprintReferenceKind(type); + if (t != null) { + Space(-1200); + using (VerticalScope()) { + Label(""); + gui.OnGUI(newGuid => { + PatchOperation tmpOp = new(PatchOperation.PatchOperationType.ModifyBlueprintReference, info.Name, type, newGuid, parentType); + PatchOperation op = wouldBePatch.AddOperation(tmpOp); + CurrentState.AddOp(op); + CurrentState.CreateAndRegisterPatch(); + }, t); + } + } else { + Label("Non-Generic Reference. If you're seeing this please report to mod author!".Yellow().Bold()); + } + } + } else if (primitiveTypes.Contains(type)) { + var n = $"{RuntimeHelpers.GetHashCode(this)}{RuntimeHelpers.GetHashCode(parent)}{RuntimeHelpers.GetHashCode(info)}{RuntimeHelpers.GetHashCode(@object)}"; + if (GUI.GetNameOfFocusedControl() == n) { + Label((@object?.ToString() ?? "<Field is null>").Cyan(), Width(500)); + toggleStates[path] = true; + } else { + Label(@object?.ToString() ?? "<Field is null>", Width(500)); + toggleStates[path] = false; + } + if (!editStates.TryGetValue(path, out var curValue)) { + curValue = ""; + } + string tmp = (string)curValue; + TextField(ref tmp, n, Width(300)); + editStates[path] = tmp; + Space(20); + ActionButton("Change".localize(), () => { + object result = null; + if (type == typeof(string)) { + result = tmp; + } else { + var method = AccessTools.Method(type, "TryParse", [typeof(string), type.MakeByRefType()]); + object[] parameters = [tmp, Activator.CreateInstance(type)]; + bool success = (bool)(method?.Invoke(null, parameters) ?? false); + if (success) { + result = parameters[1]; + } else { + Space(20); + Label($"Failed to parse value {tmp} to type {type.Name}".Orange()); + } + } + if (result != null) { + PatchOperation tmpOp = new(PatchOperation.PatchOperationType.ModifyPrimitive, info.Name, type, result, parentType); + PatchOperation op = wouldBePatch.AddOperation(tmpOp); + CurrentState.AddOp(op); + CurrentState.CreateAndRegisterPatch(); + } + }); + } else if (PatchToolUtils.IsListOrArray(type)) { + if (@object == null) { + Label("Null", Width(500)); + return; + } + Type defaultType = null; + if (!toggleStates.TryGetValue(path, out var state)) { + state = false; + } + int elementCount = 0; + if (type.IsArray) { + Array array = @object as Array; + elementCount = array.Length; + defaultType = type.GetElementType(); + } else { + IList list = @object as IList; + if (list != null) { + elementCount = list.Count; + } else { + var list2 = @object as IEnumerable<object>; + elementCount = list2.Count(); + defaultType = type.GetInterfaces().FirstOrDefault(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>))?.GetGenericArguments()[0] + ?? list2?.NotNull()?.FirstOrDefault()?.GetType(); + } + } + if (state) { + Label(($"{elementCount} " + "Entries".localize()).Cyan(), Width(500)); + } else { + Label($"{elementCount} " + "Entries".localize(), Width(500)); + } + DisclosureToggle("Show Entries".localize(), ref state, 200); + toggleStates[path] = state; + if (state) { + int localIndex = 0; + Space(-1200); + using (VerticalScope()) { + Label(""); + foreach (var elem in (@object as IEnumerable).Cast<object>().ToList()) { + ListItemGUI(wouldBePatch, parent, info, elem, localIndex, @object, path, defaultType); + localIndex += 1; + } + using (HorizontalScope()) { + Space(1220); + ActionButton("Add Item".localize(), () => { + AddItemState.CreateArrayElement(parent, info, @object, -1, wouldBePatch, this, path); + }); + } + if (addItemStates.TryGetValue(path, out var activeAddItemState)) { + Label("New Item:".localize(), Width(500)); + activeAddItemState.AddItemGUI(); + } + } + } + } else { + if (@object == null) { + if (!type.IsValueType) { + Label("Null", Width(500)); + } else { + Label("Null (value)", Width(500)); + } + return; + } + if (!toggleStates.TryGetValue(path, out var state)) { + state = false; + } + string label; + try { + label = @object?.ToString() ?? "Null (value)"; + } catch (Exception ex) { + Mod.Trace($"Error in FieldGUI ToString for field {info.Name}:\n{ex.ToString()}"); + label = "Exception in ToString".Orange(); + } + Label(label, Width(500)); + if (state) { + Label(label.Cyan(), Width(500)); + } else { + Label(label, Width(500)); + } + DisclosureToggle("Show fields".localize(), ref state, 200); + toggleStates[path] = state; + if (state) { + PatchOperation tmpOp = new(PatchOperation.PatchOperationType.ModifyComplex, info.Name, null, null, parentType); + PatchOperation op = wouldBePatch.AddOperation(tmpOp); + Space(-1200); + using (VerticalScope()) { + Label(""); + NestedGUI(@object, path, op, type); + } + } + } + } + #endregion + private void ListItemGUI(PatchOperation wouldBePatch, object parent, FieldInfo info, object elem, int index, object collection, string path, Type defaultType = null) { + PatchOperation tmpOp = new(PatchOperation.PatchOperationType.ModifyCollection, info.Name, null, null, parent.GetType(), PatchOperation.CollectionPatchOperationType.ModifyAtIndex, index); + PatchOperation op = wouldBePatch.AddOperation(tmpOp); + using (HorizontalScope()) { + Space(-13); + if (toggleStates.TryGetValue(path, out var shouldPaint) && shouldPaint) { + Label($"[{index}] ({elem?.GetType().Name ?? "Null"})".Cyan(), Width(500)); + } else { + Label($"[{index}] ({elem?.GetType().Name ?? "Null"})", Width(500)); + } + FieldGUI(parent, parent.GetType(), op, elem?.GetType() ?? defaultType, elem, info, path + $"[{index}]"); + + Space(20); + ActionButton("Add Before".localize(), () => { + AddItemState.CreateArrayElement(parent, info, collection, index, wouldBePatch, this, path); + }); + Space(10); + ActionButton("Add After".localize(), () => { + AddItemState.CreateArrayElement(parent, info, collection, index + 1, wouldBePatch, this, path); + }); + Space(10); + ActionButton("Remove".localize(), () => { + PatchOperation removeOp = new(PatchOperation.PatchOperationType.ModifyCollection, info.Name, null, null, parent.GetType(), PatchOperation.CollectionPatchOperationType.RemoveAtIndex, index); + PatchOperation opRemove = wouldBePatch.AddOperation(removeOp); + CurrentState.AddOp(opRemove); + CurrentState.CreateAndRegisterPatch(); + }); + } + } + private void PopulateFieldsAndObjects(object o, string path, Type type) { + Dictionary<FieldInfo, object> result = new(); + if (PatchToolUtils.IsNullableStruct(type)) { + foreach (var field in PatchToolUtils.GetFields(type)) { + if (field.Name == "value") { + if (o == null) { + result[field] = null; + } else { + result[field] = field.GetValue(o); + } + } + } + } else { + foreach (var field in PatchToolUtils.GetFields(type)) { + result[field] = field.GetValue(o); + } + } + if (type == typeof(SharedStringAsset)) { + var toRemove = result.Where(f => f.Key.Name == "m_CachedPtr").FirstOrDefault().Key; + if (toRemove != null) { + result.Remove(toRemove); + } + } + fieldsByObject[path] = result; + } +} diff --git a/ToyBox/Classes/MainUI/PatchTool/UI/PatchToolUIManager.cs b/ToyBox/Classes/MainUI/PatchTool/UI/PatchToolUIManager.cs new file mode 100644 index 000000000..bf5d8cf5a --- /dev/null +++ b/ToyBox/Classes/MainUI/PatchTool/UI/PatchToolUIManager.cs @@ -0,0 +1,89 @@ +using ModKit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace ToyBox.PatchTool; +public static class PatchToolUIManager { + private static List<PatchToolTabUI> instances = new(); + private static int selectedIndex = -1; + private static bool showExistingPatchesUI = false; + public static void OnGUI() { + Space(-25); + Label("Warning:".localize().Yellow().Bold() + " " + "This is a very powerful feature. You won't break your game by simply changing the damage of a weapon,".localize().Yellow() + " " + "but this feature allows doing a lot of things that could potentially causes issues.".localize().Orange().Bold() + " " + "Beware of that and always keep a backup.".localize().Yellow()); + Label("Note:".localize().Green().Bold() + " " + "After finishing creating a patch, it is advised to restart the game before playing on a proper save.".localize().Green()); + Label("Warning:".localize().Yellow().Bold() + " " + "This feature is new. Please reach out if anything seems buggy or you encounter any issues.".localize().Yellow()); + Space(15); + Div(); + Space(15); + DisclosureToggle("Manage existing patches".localize(), ref showExistingPatchesUI, 200); + if (showExistingPatchesUI) { + PatchListUI.OnGUI(); + } + Space(15); + Div(); + Space(15); + using (HorizontalScope()) { + Label("Tabs".localize().Bold(), AutoWidth()); + Space(50); + ActionButton("Create New Tab".localize(), () => { + instances.Add(new PatchToolTabUI()); + selectedIndex = instances.Count - 1; + }, AutoWidth()); + } + Label(""); + using (VerticalScope()) { + for (int j = 0; j < instances.Count; j += 4) { + using (HorizontalScope()) { + for (int i = j; (i < instances.Count) && (i < j + 4); i++) { + string tabName = "New Tab".localize(); + bool hasNoName = true; + if (!instances[i].Target.IsNullOrEmpty()) { + tabName = instances[i].Target; + hasNoName = false; + } + if (i == selectedIndex) { + if (hasNoName) { + Label(tabName, Width(300)); + } else { + ClipboardLabel(tabName, Width(300)); + } + } else { + ActionButton(tabName, () => { + selectedIndex = i; + }, Width(300)); + } + ActionButton("Close".localize(), () => { + instances.RemoveAt(i); + if (selectedIndex >= instances.Count) { + selectedIndex = instances.Count - 1; + } + }, Width(70)); + Space(50); + } + } + Label(""); + } + } + Space(15); + Div(); + Space(15); + if (selectedIndex >= 0 && selectedIndex < instances.Count) { + instances[selectedIndex].OnGUI(); + } else { + Label("No tabs open.".localize()); + } + } + public static void OpenBlueprintInTab(string guid) { + var existing = instances.FirstOrDefault(tab => tab.Target.Equals(guid, StringComparison.InvariantCultureIgnoreCase)); + if (existing != default) { + selectedIndex = instances.IndexOf(existing); + } else { + instances.Add(new PatchToolTabUI(guid)); + selectedIndex = instances.Count - 1; + } + } +} diff --git a/ToyBox/Classes/MainUI/PatchTool/Utils/DeepCopy.cs b/ToyBox/Classes/MainUI/PatchTool/Utils/DeepCopy.cs new file mode 100644 index 000000000..c66c04fb1 --- /dev/null +++ b/ToyBox/Classes/MainUI/PatchTool/Utils/DeepCopy.cs @@ -0,0 +1,128 @@ +using System.Collections.Generic; +using System.Reflection; +using System.ArrayExtensions; +using System.Runtime.CompilerServices; +using Kingmaker.Blueprints; +using ToyBox.PatchTool; + +namespace System { + public static class ObjectExtensions { + private static readonly MethodInfo CloneMethod = typeof(Object).GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance); + private static readonly Dictionary<(Type, BindingFlags), FieldInfo[]> FieldsByTypeCache = new(); + + public static bool IsPrimitive(this Type type) { + if (type == typeof(String)) return true; + return (type.IsValueType & type.IsPrimitive); + } + + public static Object DeepCopy(Object originalObject, Object targetObject = null, bool cloneTopBlueprint = false) { + return InternalCopy(originalObject, new Dictionary<Object, Object>(new ReferenceEqualityComparer()), targetObject, cloneTopBlueprint); + } + private static Object InternalCopy(Object originalObject, IDictionary<Object, Object> visited, Object targetObject = null, bool cloneTopBlueprint = false) { + if (originalObject == null) return null; + var typeToReflect = originalObject.GetType(); + if (IsPrimitive(typeToReflect)) return originalObject; + if (visited.ContainsKey(originalObject)) return visited[originalObject]; + + // Not copying this would result in weird side effects, like the m_Factory of a StaticCache being lost. + // if (typeof(Delegate).IsAssignableFrom(typeToReflect)) return null; + if (typeof(Delegate).IsAssignableFrom(typeToReflect)) return originalObject; + + // Prevent messing up references by copying the cached instance of the blueprints. + if (!cloneTopBlueprint && typeof(SimpleBlueprint).IsAssignableFrom(typeToReflect)) return originalObject; + if (PatchToolUtils.TypeOrBaseIsDirectlyInUnityDLL(typeToReflect)) return originalObject; + + var cloneObject = targetObject ?? CloneMethod.Invoke(originalObject, null); + visited.Add(originalObject, cloneObject); + if (typeToReflect.IsArray) { + var arrayType = typeToReflect.GetElementType(); + if (IsPrimitive(arrayType) == false) { + Array clonedArray = (Array)cloneObject; + clonedArray.ForEach((array, indices) => array.SetValue(InternalCopy(clonedArray.GetValue(indices), visited), indices)); + } + } + CopyFields(originalObject, visited, cloneObject, typeToReflect); + RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect); + return cloneObject; + } + + private static void RecursiveCopyBaseTypePrivateFields(object originalObject, IDictionary<object, object> visited, object cloneObject, Type typeToReflect) { + if (typeToReflect.BaseType != null) { + RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect.BaseType); + CopyFields(originalObject, visited, cloneObject, typeToReflect.BaseType, BindingFlags.Instance | BindingFlags.NonPublic, info => info.IsPrivate); + } + } + + private static void CopyFields(object originalObject, IDictionary<object, object> visited, object cloneObject, Type typeToReflect, BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy, Func<FieldInfo, bool> filter = null) { + if (!FieldsByTypeCache.TryGetValue((typeToReflect, bindingFlags), out var fields)) { + fields = FieldsByTypeCache[(typeToReflect, bindingFlags)] = typeToReflect.GetFields(bindingFlags); + } + foreach (FieldInfo fieldInfo in fields) { + if (filter != null && filter(fieldInfo) == false) continue; + if (IsPrimitive(fieldInfo.FieldType)) { + fieldInfo.SetValue(cloneObject, fieldInfo.GetValue(originalObject)); + continue; + } + var originalFieldValue = fieldInfo.GetValue(originalObject); + var clonedFieldValue = InternalCopy(originalFieldValue, visited); + fieldInfo.SetValue(cloneObject, clonedFieldValue); + } + } + public static T Copy<T>(this T original, T target = null, bool cloneTopBlueprint = false) where T : class { + return (T)DeepCopy(original, target, cloneTopBlueprint); + } + public static T Copy<T>(this T original) { + return (T)DeepCopy(original); + } + } + + public class ReferenceEqualityComparer : EqualityComparer<Object> { + public override bool Equals(object x, object y) { + return ReferenceEquals(x, y); + } + public override int GetHashCode(object obj) { + if (obj == null) return 0; + // E.g. WeakResourceLink can throw on GetHashCode() + // return obj.GetHashCode(); + return RuntimeHelpers.GetHashCode(obj); + } + } + + namespace ArrayExtensions { + public static class ArrayExtensions { + public static void ForEach(this Array array, Action<Array, int[]> action) { + if (array.LongLength == 0) return; + ArrayTraverse walker = new ArrayTraverse(array); + do action(array, walker.Position); + while (walker.Step()); + } + } + + internal class ArrayTraverse { + public int[] Position; + private int[] maxLengths; + + public ArrayTraverse(Array array) { + maxLengths = new int[array.Rank]; + for (int i = 0; i < array.Rank; ++i) { + maxLengths[i] = array.GetLength(i) - 1; + } + Position = new int[array.Rank]; + } + + public bool Step() { + for (int i = 0; i < Position.Length; ++i) { + if (Position[i] < maxLengths[i]) { + Position[i]++; + for (int j = 0; j < i; j++) { + Position[j] = 0; + } + return true; + } + } + return false; + } + } + } + +} \ No newline at end of file diff --git a/ToyBox/Classes/MainUI/PatchTool/Utils/InstantiationHelper.cs b/ToyBox/Classes/MainUI/PatchTool/Utils/InstantiationHelper.cs new file mode 100644 index 000000000..7896f072d --- /dev/null +++ b/ToyBox/Classes/MainUI/PatchTool/Utils/InstantiationHelper.cs @@ -0,0 +1,127 @@ +using Kingmaker.Blueprints; +using Kingmaker.Blueprints.Attributes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace ToyBox.PatchTool; +public static partial class PatchToolUtils { + public static (HashSet<Type>, HashSet<Type>) GetInstantiableTypes(Type elementType, object maybeParent, bool skipRecursion = false, bool canBeNonInstantiable = false) { + if (!skipRecursion) { + if (elementType.IsArray) { + return (GetInstantiableArrayTypes(elementType), null); + } + if (elementType.IsGenericType) { + return (GetInstantiableGenericTypes(elementType), null); + } + } + HashSet<Type> allowedinstantiableTypes = typeof(BlueprintComponent).IsAssignableFrom(elementType) ? new() : null; + HashSet<Type> allinstantiableTypes = new(); + Type parentType = maybeParent?.GetType(); + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { + Type[] types; + try { + types = assembly.GetTypes(); + } catch (ReflectionTypeLoadException ex) { + types = ex.Types.Where(t => t != null).ToArray(); + } + + foreach (var type in types) { + if (type == null) continue; + + if (elementType.IsAssignableFrom(type) && ((!type.IsAbstract && !type.IsInterface && !TypeOrBaseIsDirectlyInUnityDLL(type)) || canBeNonInstantiable)) { + if (parentType != null && allowedinstantiableTypes != null) { + var attributes = type.GetCustomAttributes(typeof(AllowedOnAttribute), inherit: false); + if (attributes.Length > 0) { + foreach (AllowedOnAttribute attr in attributes) { + if (attr.Type.IsAssignableFrom(parentType)) { + allowedinstantiableTypes.Add(type); + } + } + } + } + allinstantiableTypes.Add(type); + } + } + } + return (allinstantiableTypes, allowedinstantiableTypes); + } + public static HashSet<Type> GetInstantiableArrayTypes(Type elementType) { + if (!elementType.IsArray) return new HashSet<Type>(); + + Type arrayElementType = elementType.GetElementType(); + if (arrayElementType == null) return new HashSet<Type>(); + + HashSet<Type> elementInstantiableTypes = GetInstantiableTypes(arrayElementType, null, false, true).Item1; + HashSet<Type> arrayTypes = new(); + foreach (var type in elementInstantiableTypes) { + arrayTypes.Add(type.MakeArrayType()); + } + + return arrayTypes; + } + public static HashSet<Type> GetInstantiableGenericTypes(Type elementType) { + if (!elementType.IsGenericType) return new HashSet<Type>(); + + HashSet<Type> genericTypes = new(); + + var genericArguments = elementType.GetGenericArguments(); + List<HashSet<Type>> possibleArguments = new(); + + foreach (var arg in genericArguments) { + possibleArguments.Add(GetValidTypesForGenericParameter(arg)); + } + + Type typeToModify = elementType.IsGenericTypeDefinition ? elementType : elementType.GetGenericTypeDefinition(); + foreach (var combination in GenerateCombinations(possibleArguments)) { + try { + var concreteType = typeToModify.MakeGenericType(combination.ToArray()); + if (concreteType != null) { + genericTypes.Add(concreteType); + } + } catch { + } + } + + return genericTypes; + } + private static HashSet<Type> GetValidTypesForGenericParameter(Type genericParameter) { + HashSet<Type> validTypes = new(); + + if (genericParameter.IsGenericParameter) { + var constraints = genericParameter.GetGenericParameterConstraints(); + + foreach (var type in GetInstantiableTypes(typeof(object), null, true, true).Item1) { + + bool satisfiesConstraints = true; + foreach (var constraint in constraints) { + if (!constraint.IsAssignableFrom(type)) { + satisfiesConstraints = false; + break; + } + } + + if (satisfiesConstraints) validTypes.Add(type); + } + + return validTypes; + } else { + return GetInstantiableTypes(genericParameter, null, false, true).Item1; + } + } + private static IEnumerable<List<Type>> GenerateCombinations(List<HashSet<Type>> lists) { + if (lists.Count == 0) { + yield return new List<Type>(); + } else { + var first = lists[0]; + var rest = lists.Skip(1).ToList(); + + foreach (var type in first) { + foreach (var combination in GenerateCombinations(rest)) { + yield return [type, .. combination]; + } + } + } + } +} \ No newline at end of file diff --git a/ToyBox/Classes/MainUI/PatchTool/Utils/PatchToolUtils.cs b/ToyBox/Classes/MainUI/PatchTool/Utils/PatchToolUtils.cs new file mode 100644 index 000000000..8153a2c61 --- /dev/null +++ b/ToyBox/Classes/MainUI/PatchTool/Utils/PatchToolUtils.cs @@ -0,0 +1,160 @@ +using HarmonyLib; +using Kingmaker.Blueprints; +using Kingmaker.Blueprints.Attributes; +using Kingmaker.Blueprints.JsonSystem; +using Kingmaker.ElementsSystem; +using ModKit; +using ModKit.Utility.Extensions; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace ToyBox.PatchTool; +public static partial class PatchToolUtils { + public static MethodInfo? GetInterfaceMethodImplementation(this Type declaringType, MethodInfo interfaceMethod) { + var map = declaringType.GetInterfaceMap(interfaceMethod.DeclaringType); + return map.InterfaceMethods + ?.Zip(map.TargetMethods, (i, t) => (i, t)) + .FirstOrDefault(pair => pair.i == interfaceMethod) + .t; + } + public static bool IsListOrArray(Type t) { + return t.IsArray || typeof(IList<>).IsAssignableFrom(t) || t.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IList<>)); + } + private static Dictionary<Type, List<FieldInfo>> _fieldsCache = new(); + public static List<FieldInfo> GetFields(Type t) { + List<FieldInfo> fields; + if (!_fieldsCache.TryGetValue(t, out fields)) { + fields = new(); + HashSet<string> tmp = new(); + var t2 = t; + do { + foreach (var field in t2.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)) { + if (!tmp.Contains(field.Name)) { + tmp.Add(field.Name); + fields.Add(field); + } + } + t2 = t2.BaseType; + } while (t2 != null); + fields.Sort((a, b) => { + return a.Name.CompareTo(b.Name); + }); + _fieldsCache[t] = fields; + } + return fields; + } + public static bool IsNullableStruct(Type type) => Nullable.GetUnderlyingType(type) != null; + private static Dictionary<SimpleBlueprint, Dictionary<Type, int>> m_ComponentNameCounter = new(); + public static object CreateObjectOfType(Type type, bool isForBlueprintPatch = true) { + object result; + try { + if (TypeOrBaseIsDirectlyInUnityDLL(type)) { + if (typeof(ScriptableObject).IsAssignableFrom(type)) { + result = ScriptableObject.CreateInstance(type); + } else { + // Mod.Error("Trying to instantiate a non-scriptable object Unity Object. In general this means someone messed up somewhere. Make sure you really know what you're doing!"); + // result = Activator.CreateInstance(type); + throw new Exception("Trying to instantiate a non-scriptable object Unity Object. In general this means someone messed up somewhere."); + } + } else { + result = Activator.CreateInstance(type); + } + } catch (Exception ex) { + result = FormatterServices.GetUninitializedObject(type); + Mod.Debug($"Exception while trying to Activator.CreateInstance {type.FullName}, falling back to FormatterServices.GetUninitializedObject. Exception:\n{ex}"); + } + if (isForBlueprintPatch) { + if (result is BlueprintComponent || result is Element) { + if (!m_ComponentNameCounter.TryGetValue(Patcher.CurrentlyPatching, out var dict)) { + dict = new(); + } + if (!dict.TryGetValue(type, out var occurences)) { + occurences = 0; + } + occurences += 1; + dict[type] = occurences; + m_ComponentNameCounter[Patcher.CurrentlyPatching] = dict; + if (result is BlueprintComponent comp) { + comp.name = $"{Patcher.CurrentlyPatching.AssetGuid}#{type.FullName}#{occurences}"; + } else if (result is Element elem) { + elem.name = $"{Patcher.CurrentlyPatching.AssetGuid}#{type.FullName}#{occurences}"; + } + } + } + return result; + } + public static PatchOperation AddOperation(this PatchOperation head, PatchOperation leaf) { + if (head == null) { + return leaf; + } else { + var copy = head.Copy(); + PatchOperation cur = copy; + while (cur.NestedOperation != null) { + cur = cur.NestedOperation; + } + cur.NestedOperation = leaf; + return copy; + } + } + public static Type GetBlueprintReferenceKind(Type type) { + Type currentType = type; + + while (currentType != null && currentType != typeof(BlueprintReferenceBase)) { + if (currentType.IsGenericType) { + Type genericTypeDefinition = currentType.GetGenericTypeDefinition(); + if (genericTypeDefinition == typeof(BlueprintReference<>)) { + return currentType.GetGenericArguments()[0]; + } + } + currentType = currentType.BaseType; + } + return null; + } + private static Dictionary<Type, bool> m_TypeIsDirectlyInUnityDLL = new(); + private static Dictionary<Type, bool> m_TypeIsInUnityDLL = new(); + private static HashSet<Type> m_SafeExceptions = [typeof(Vector2), typeof(Vector2Int), typeof(Vector3), typeof(Vector3Int), typeof(Vector4), typeof(Color), typeof(Color32), typeof(Rect), typeof(RectInt)]; + public static bool TypeOrBaseIsDirectlyInUnityDLL(Type type) { + if (m_TypeIsDirectlyInUnityDLL.TryGetValue(type, out var val)) { + return val; + } + if (m_SafeExceptions.Contains(type)) { + return m_TypeIsDirectlyInUnityDLL[type] = false; + } + if (type.BaseType != null) { + if (TypeOrBaseIsDirectlyInUnityDLL(type.BaseType)) { + return m_TypeIsDirectlyInUnityDLL[type] = true; + } + } + if (type.Assembly.FullName.StartsWith("Unity")) { + return m_TypeIsDirectlyInUnityDLL[type] = true; + } + return m_TypeIsDirectlyInUnityDLL[type] = false; + } + public static bool TypeOrBaseIsInUnityDLL(Type type) { + if (m_TypeIsInUnityDLL.TryGetValue(type, out var val)) { + return val; + } + if (type.BaseType != null) { + if (TypeOrBaseIsInUnityDLL(type.BaseType)) { + return m_TypeIsInUnityDLL[type] = true; + } + } + if (TypeOrBaseIsDirectlyInUnityDLL(type)) { + return m_TypeIsInUnityDLL[type] = true; + } + if (type.IsGenericType) { + return m_TypeIsInUnityDLL[type] = type.GenericTypeArguments.Any(TypeOrBaseIsInUnityDLL); + } + if (type.IsArray) { + return m_TypeIsInUnityDLL[type] = TypeOrBaseIsInUnityDLL(type.GetElementType()); + } + return m_TypeIsInUnityDLL[type] = false; + } +} diff --git a/ToyBox/Classes/MainUI/QuestEditor.cs b/ToyBox/Classes/MainUI/QuestEditor.cs new file mode 100644 index 000000000..8a84e8c1e --- /dev/null +++ b/ToyBox/Classes/MainUI/QuestEditor.cs @@ -0,0 +1,217 @@ +// borrowed shamelessly and enhanced from Kingdom Resolution Mod +// "Author": "spacehamster", +// "HomePage": "https://www.nexusmods.com/pathfinderkingmaker/mods/36", +// "Repository": "https://raw.githubusercontent.com/spacehamster/KingmakerKingdomResolutionMod/master/KingdomResolution/Repository.json" +// Copyright < 2018 > Spacehamster +// Copyright < 2021 > Ported version - Narria (github user Cabarius) - License: MIT +using HarmonyLib; +using Kingmaker; +using Kingmaker.AreaLogic.QuestSystem; +using Kingmaker.Blueprints.Quests; +using Kingmaker.Designers; +using Kingmaker.Designers.EventConditionActionSystem.Actions; +using Kingmaker.Designers.EventConditionActionSystem.Conditions; +using Kingmaker.ElementsSystem; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.UnitLogic.Parts; +using ModKit; +using ModKit.DataViewer; +using ModKit.Utility; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.AccessControl; +using UnityEngine; +using static Kingmaker.UnitLogic.Interaction.SpawnerInteractionPart; +using static ModKit.UI; +using static ToyBox.BlueprintExtensions; + +namespace ToyBox { + public static class QuestExtensions { + private static readonly RGBA[] titleColors = new RGBA[] { + RGBA.brown, + RGBA.cyan, + RGBA.darkgrey, + + RGBA.yellow, + RGBA.lime, + RGBA.red + }; + private static readonly string[] questColors = new string[] { + "gray", + "cyan", + "white", + "yellow", + "lime", + "red" + }; + + public static bool IsRevealed(this QuestObjective objective) => objective.State == QuestObjectiveState.Started || objective.State == QuestObjectiveState.Completed; + public static string? stateColored(this string? text, Quest quest) => RichText.Color(text, questColors[(int)quest.State]); + public static string? stateColored(this string? text, QuestObjective objective) => RichText.Color(text, questColors[(int)objective.State]); + public static string? titleColored(this Quest quest) => quest.Blueprint.Title.StringValue().Color(titleColors[(int)quest.State]); + public static string? titleColored(this QuestObjective objective, BlueprintQuestObjective? bp = null) { + var blueprint = objective?.Blueprint ?? bp; + var state = objective?.State ?? QuestObjectiveState.None; + var title = blueprint.Title.StringValue(); + if (title.Length == 0) title = blueprint.ToString(); + if (blueprint.IsAddendum) + title = "Addendum: ".localize().Color(RGBA.white) + title; + if (blueprint.name.Contains("_Fail")) + return RichText.Red(title); + else + return title.Color(titleColors[(int)state]); + } + public static string? titleColored(this string title, QuestObjectiveState state) => title.Color(titleColors[(int)state]); + public static string? stateString(this Quest quest) => quest.State == QuestState.None ? "" : RichText.Bold($"{quest.State}".stateColored(quest)); + public static string? stateString(this QuestObjective objective) => objective.State == QuestObjectiveState.None ? "" : RichText.Bold($"{objective.State}".stateColored(objective)); + } + + public class QuestEditor { + public static Settings Settings => Main.Settings; + public static Player player => Game.Instance.Player; + private static bool[] _selectedQuests = Array.Empty<bool>(); + public static void ResetGUI() { } + + public static void OnGUI() { + if (!Main.IsInGame) return; + var quests = Game.Instance?.Player?.QuestBook.Quests.ToArray(); + if (quests == null) return; + _selectedQuests = (_selectedQuests.Length != quests.Length) ? new bool[quests.Length] : _selectedQuests; + var index = 0; + var contentColor = GUI.contentColor; + using (HorizontalScope()) { + Label(RichText.Cyan("Quests".localize())); + } + var split = quests.GroupBy(q => q.State == QuestState.Completed).OrderBy(g => g.Key); + using (HorizontalScope()) { + Toggle("Hide Completed".localize(), ref Settings.toggleQuestHideCompleted); + 25.space(); + Toggle("Show Unrevealed Steps".localize(), ref Settings.toggleQuestsShowUnrevealedObjectives); + 25.space(); + Toggle("Inspect Quests and Objectives".localize(), ref Settings.toggleQuestInspector); + if (Settings.toggleQuestInspector) { + 25.space(); + ReflectionTreeView.DetailToggle("Inspect".localize(), _selectedQuests, split, 0); + } + } + if (Settings.toggleQuestInspector) { + ReflectionTreeView.OnDetailGUI(_selectedQuests); + } + foreach (var group in split) { + foreach (var quest in group.ToList()) { + if (Settings.toggleQuestHideCompleted && quest.State == QuestState.Completed && _selectedQuests[index]) { + _selectedQuests[index] = false; + } + if (!Settings.toggleQuestHideCompleted || quest.State != QuestState.Completed || _selectedQuests[index]) { + using (HorizontalScope()) { + 50.space(); + Label(RichText.Bold(RichText.Orange(quest.Blueprint.Title.StringValue())), Width(600)); + 50.space(); + DisclosureToggle(quest.stateString(), ref _selectedQuests[index]); + if (Settings.toggleQuestInspector) + ReflectionTreeView.DetailToggle("Inspect".localize(), quest, quest, 0); + 50.space(); + Label(RichText.Green(quest.Blueprint.Description.StringValue().StripHTML())); + } + if (Settings.toggleQuestInspector) { + ReflectionTreeView.OnDetailGUI(quest); + } + if (_selectedQuests[index]) { + var objectiveIndex = 0; + foreach (var questObjective in quest.Objectives) { + if (Settings.toggleQuestsShowUnrevealedObjectives || questObjective.IsRevealed()) { + if (questObjective.ParentObjective == null) { + Div(100, 25); + using (HorizontalScope(AutoWidth())) { + Space(50); + objectiveIndex = questObjective.Order == 0 ? objectiveIndex + 1 : questObjective.Order; + Label($"{objectiveIndex}", Width(50)); + Label(questObjective.titleColored(), Width(600)); + 25.space(); + Label(questObjective.stateString(), Width(150)); + if (Settings.toggleQuestInspector) + ReflectionTreeView.DetailToggle("Inspect".localize(), questObjective, questObjective, 0); + Space(25); + using (HorizontalScope(300)) { + Space(0); + if (questObjective.State == QuestObjectiveState.None && quest.State == QuestState.Started) { + ActionButton("Start".localize(), () => { questObjective.Start(); }, Width(150)); + } else if (questObjective.State == QuestObjectiveState.Started) { + ActionButton(questObjective.Blueprint.IsFinishParent ? "Finish".localize() : "Complete".localize(), () => { + questObjective.Complete(); + }, Width(150)); + if (questObjective.Blueprint.AutoFailDays > 0) { + ActionButton("Reset Time".localize(), () => { + Traverse.Create(questObjective).Field("m_ObjectiveStartTime").SetValue(Game.Instance.Player.GameTime); + }, Width(150)); + } + } else if (questObjective.State == QuestObjectiveState.Failed && (questObjective.Blueprint.IsFinishParent || quest.State == QuestState.Started)) { + ActionButton("Restart".localize(), () => { + if (quest.State == QuestState.Completed || quest.State == QuestState.Failed) { + Traverse.Create(quest).Field("m_State").SetValue(QuestState.Started); + } + questObjective.Reset(); + questObjective.Start(); + }, Width(50)); + } + } + DrawTeleports(questObjective); + Label(RichText.Green(questObjective.Blueprint.Description.StringValue().StripHTML()), 1000.width()); + Label("", AutoWidth()); + } + if (Settings.toggleQuestInspector) { + ReflectionTreeView.OnDetailGUI(questObjective); + } + if (questObjective.State == QuestObjectiveState.Started) { + var childIndex = 0; + foreach (var childObjective in quest.Objectives) { + if (Settings.toggleQuestsShowUnrevealedObjectives || childObjective.IsRevealed()) { + if (childObjective.ParentObjective == questObjective) { + Div(100, 25); + using (HorizontalScope(AutoWidth())) { + Space(100); + childIndex = childObjective.Order == 0 ? childIndex + 1 : questObjective.Order; + Label($"{childIndex}", Width(50)); + Space(10); + Label(childObjective.titleColored(), Width(600)); + 25.space(); + Label(childObjective.stateString(), Width(150)); + if (Settings.toggleQuestInspector) + ReflectionTreeView.DetailToggle("Inspect".localize(), questObjective, questObjective, 0); + Space(25); + using (HorizontalScope(300)) { + if (childObjective.State == QuestObjectiveState.None) { + ActionButton("Start".localize(), () => { childObjective.Start(); }, Width(150)); + } else if (childObjective.State == QuestObjectiveState.Started) { + ActionButton(childObjective.Blueprint.IsFinishParent ? "Complete (Final)".localize() : "Complete".localize(), () => { + childObjective.Complete(); + }, Width(150)); + } else 153.space(); + } + DrawTeleports(childObjective); + Label(RichText.Green(childObjective.Blueprint.Description.StringValue().StripHTML()), 1000.width()); + Label("", AutoWidth()); + } + if (Settings.toggleQuestInspector) { + ReflectionTreeView.OnDetailGUI(childObjective); + } + } + } + } + } + } + } + } + } + Div(50, 25); + } + index++; + } + } + Space(25); + } + public static void DrawTeleports(QuestObjective objective) { + } + } +} diff --git a/ToyBox/Classes/MainUI/RogueCheats.cs b/ToyBox/Classes/MainUI/RogueCheats.cs new file mode 100644 index 000000000..89b84910c --- /dev/null +++ b/ToyBox/Classes/MainUI/RogueCheats.cs @@ -0,0 +1,257 @@ +using HarmonyLib; +using Kingmaker; +using Kingmaker.Blueprints; +using Kingmaker.Cheats; +using Kingmaker.Code.UI.MVVM.VM.NavigatorResource; +using Kingmaker.Controllers; +using Kingmaker.Designers.WarhammerSurfaceCombatPrototype; +using Kingmaker.Enums; +using Kingmaker.RuleSystem.Rules; +using Kingmaker.UnitLogic; +using ModKit; +using ModKit.DataViewer; +using System; +using System.Collections.Generic; +using ToyBox.BagOfPatches; +using UnityEngine; + +namespace ToyBox { + // Made for Rogue Trader specific stuff which I have no idea where to put + public static class RogueCheats { + public static Settings Settings => Main.Settings; + private static int selectedFaction = 0; + public static NamedFunc<FactionType>[] factionsToPick; + private static Browser<BlueprintPsychicPhenomenaRoot.PsychicPhenomenaData, BlueprintPsychicPhenomenaRoot.PsychicPhenomenaData> PsychicPhenomenaBrowser = new(true, true) { DisplayShowAllGUI = false }; + private static Browser<BlueprintAbilityReference, BlueprintAbilityReference> PerilsOfTheWarpMinorBrowser = new(true, true) { DisplayShowAllGUI = false }; + private static Browser<BlueprintAbilityReference, BlueprintAbilityReference> PerilsOfTheWarpMajorBrowser = new(true, true) { DisplayShowAllGUI = false }; + private static int reputationAdjustment = 100; + private static int navigatorInsightAdjustment = 100; + private static int scrapAdjustment = 100; + private static int profitFactorAdjustment = 1; + private static int veilThicknessAdjustment = 1; + private static int startingWidth = 250; + public static void OnGUI() { + if (factionsToPick == null) { + List<NamedFunc<FactionType>> tmp = new(); + foreach (FactionType @enum in Enum.GetValues(typeof(FactionType))) { + tmp.Add(new NamedFunc<FactionType>(@enum.ToString(), () => @enum)); + } + factionsToPick = tmp.ToArray(); + } + if (Game.Instance?.Player == null) { + Label("Load a save to find options like modifying Faction Reputation, Scrap and more.".localize()); + return; + } + var selected = TypePicker("Faction Selector".localize().Bold(), ref selectedFaction, factionsToPick, true); + 15.space(); + var faction = selected.func(); + using (HorizontalScope()) { + Label("Current Reputation".localize().Bold() + ": ", Width(startingWidth)); + using (VerticalScope()) { + using (HorizontalScope()) { + Label("Level".localize() + ": ", Width(100)); + Label(ReputationHelper.GetCurrentReputationLevel(faction).ToString()); + } + using (HorizontalScope()) { + Label("Experience".localize() + ": ", Width(100)); + Label($"{ReputationHelper.GetCurrentReputationPoints(faction)}/{ReputationHelper.GetNextLevelReputationPoints(faction)}"); + } + using (HorizontalScope()) { + Label("Adjust Reputation by the following amount:".localize()); + IntTextField(ref reputationAdjustment, null, MinWidth(200), AutoWidth()); + reputationAdjustment = Math.Max(0, reputationAdjustment); + 10.space(); + ActionButton("Add".localize(), () => ReputationHelper.GainFactionReputation(faction, reputationAdjustment)); + 10.space(); + ActionButton("Remove".localize(), () => ReputationHelper.GainFactionReputation(faction, -reputationAdjustment)); + } + } + } + 15.space(); + Div(); + 15.space(); + bool warpInit = Game.Instance.Player.WarpTravelState?.IsInitialized ?? false; + VStack("Resources".localize().Bold(), + () => { + if (warpInit) { + using (HorizontalScope()) { + Label("Current Navigator Insight".localize().Bold() + ": ", Width(startingWidth)); + using (VerticalScope()) { + Label(Game.Instance.Player.WarpTravelState.NavigatorResource.ToString()); + using (HorizontalScope()) { + Label("Adjust Navigator Insight by the following amount:".localize()); + IntTextField(ref navigatorInsightAdjustment, null, MinWidth(200), AutoWidth()); + navigatorInsightAdjustment = Math.Max(0, navigatorInsightAdjustment); + 10.space(); + ActionButton("Add".localize(), () => { CheatsGlobalMap.AddNavigatorResource(navigatorInsightAdjustment); SectorMapBottomHudVM.Instance?.SetCurrentValue(); }); + 10.space(); + ActionButton("Remove".localize(), () => { CheatsGlobalMap.AddNavigatorResource(-navigatorInsightAdjustment); SectorMapBottomHudVM.Instance?.SetCurrentValue(); }); + } + } + } + } + }, + () => { + using (HorizontalScope()) { + Label("Current Scrap".localize().Bold() + ": ", Width(startingWidth)); + using (VerticalScope()) { + Label(Game.Instance.Player.Scrap.m_Value.ToString()); + using (HorizontalScope()) { + Label("Adjust Scrap by the following amount:".localize()); + IntTextField(ref scrapAdjustment, null, MinWidth(200), AutoWidth()); + scrapAdjustment = Math.Max(0, scrapAdjustment); + 10.space(); + ActionButton("Add".localize(), () => Game.Instance.Player.Scrap.Receive(scrapAdjustment)); + 10.space(); + ActionButton("Remove".localize(), () => Game.Instance.Player.Scrap.Receive(-scrapAdjustment)); + } + } + } + }, + () => { + using (HorizontalScope()) { + Label("Current Profit Factor".localize().Bold() + ": ", Width(startingWidth)); + using (VerticalScope()) { + Label(Game.Instance.Player.ProfitFactor.Total.ToString()); + using (HorizontalScope()) { + Label("Adjust Profit Factor by the following amount:".localize()); + IntTextField(ref profitFactorAdjustment, null, MinWidth(200), AutoWidth()); + profitFactorAdjustment = Math.Max(0, profitFactorAdjustment); + 10.space(); + ActionButton("Add".localize(), () => CheatsColonization.AddPF(profitFactorAdjustment)); + 10.space(); + ActionButton("Remove".localize(), () => CheatsColonization.AddPF(-profitFactorAdjustment)); + } + } + } + }, + () => { + using (HorizontalScope()) { + var VeilThicknessCounter = Game.Instance.TurnController?.VeilThicknessCounter; + if (VeilThicknessCounter != null && Game.Instance.LoadedAreaState?.AreaVailPart != null) { + Label("Current Veil Thickness".localize().Bold() + ": ", Width(startingWidth)); + using (VerticalScope()) { + Label(VeilThicknessCounter.Value.ToString()); + using (HorizontalScope()) { + Label("Set Veil Thickness to the following amount:".localize()); + IntTextField(ref veilThicknessAdjustment, null, MinWidth(200), AutoWidth()); + veilThicknessAdjustment = Math.Max(0, veilThicknessAdjustment); + 10.space(); + ActionButton("Set".localize(), () => VeilThicknessCounter.Value = veilThicknessAdjustment); + } + } + } + } + }); + VStack("Tweaks".localize().Bold(), + () => { + using (HorizontalScope()) { + if (Toggle("Disable Random Encounters in Warp".localize().Bold(), ref Settings.disableWarpRandomEncounter, Width(startingWidth))) { + if (warpInit && !Settings.disableWarpRandomEncounter) { + CheatsRE.TurnOnRandomEncounters(); + } + } + if (warpInit && Settings.disableWarpRandomEncounter) { + if (!Game.Instance.Player.WarpTravelState.ForbidRE.Value) { + CheatsRE.TurnOffRandomEncounters(); + } + } + } + }, + () => Toggle("Prevent Psychic Phenomena".localize(), ref Settings.toggleNoPsychicPhenomena), + () => Toggle("Prevent Veil Thickness from changing".localize(), ref Settings.freezeVeilThickness), + () => { + if (Toggle("Customize Psychic Phenomena/Perils of the Warp".localize(), ref Settings.customizePsychicPhenomena)) { + PatchPsychicTranspiler(!Settings.customizePsychicPhenomena); + } + }); + if (Settings.customizePsychicPhenomena) { + 15.space(); + Div(); + 15.space(); + VStack("Customization".localize().Bold(), + () => { + Label("Psychic Phenomena".localize()); + PsychicPhenomenaBrowser.OnGUI(RuleCalculatePsychicPhenomenaEffect.PsychicPhenomenaRoot.PsychicPhenomena, () => RuleCalculatePsychicPhenomenaEffect.PsychicPhenomenaRoot.PsychicPhenomena, + i => i, i => i.Bark.Entries[0].Text.String + i.Bark.Entries[0].Text.name, i => [i.Bark.Entries[0].Text.String.ToString(), i.Bark.Entries[0].Text.name], null, + (psychicphenomena, _) => { + using (HorizontalScope()) { + var internalName = psychicphenomena.Bark.Entries[0].Text.name; + string desc = psychicphenomena.Bark.Entries[0].Text.String; + Label(internalName.Cyan(), Width(200)); + Space(50); + Label(desc.Green(), Width(400)); + if (Settings.excludedRandomPhenomena.Contains(internalName)) { + ActionButton("Allow".localize(), () => { + Settings.excludedRandomPhenomena.Remove(internalName); + Misc.InvalidateFilteredWarpPhenomenaArrays(); + }, AutoWidth()); + } else { + ActionButton("Disable".localize(), () => { + Settings.excludedRandomPhenomena.Add(internalName); + Misc.InvalidateFilteredWarpPhenomenaArrays(); + }, AutoWidth()); + } + } + ReflectionTreeView.DetailToggle("", psychicphenomena, psychicphenomena, 0); + }, + (psychicphenomena, _) => { ReflectionTreeView.OnDetailGUI(psychicphenomena); }); + }, + () => { + Label("MinorPerils".localize()); + PerilsOfTheWarpMinorBrowser.OnGUI(RuleCalculatePsychicPhenomenaEffect.PsychicPhenomenaRoot.PerilsOfTheWarpMinor, () => RuleCalculatePsychicPhenomenaEffect.PsychicPhenomenaRoot.PerilsOfTheWarpMinor, + i => i, i => BlueprintExtensions.GetSearchKey(i.Get(), true), i => [BlueprintExtensions.GetSortKey(i.Get())], null, + (minorPeril, _) => { + using (HorizontalScope()) { + Label(BlueprintExtensions.GetSearchKey(minorPeril.Get(), true).Cyan(), Width(650)); + if (Settings.excludedPerilsMinor.Contains(minorPeril.guid)) { + ActionButton("Allow".localize(), () => { + Settings.excludedPerilsMinor.Remove(minorPeril.guid); + Misc.InvalidateFilteredWarpPhenomenaArrays(); + }, AutoWidth()); + } else { + ActionButton("Disable".localize(), () => { + Settings.excludedPerilsMinor.Add(minorPeril.guid); + Misc.InvalidateFilteredWarpPhenomenaArrays(); + }, AutoWidth()); + } + } + ReflectionTreeView.DetailToggle("", minorPeril, minorPeril, 0); + }, + (minorPeril, _) => { ReflectionTreeView.OnDetailGUI(minorPeril); }); + }, + () => { + Label("MajorPerils".localize()); + PerilsOfTheWarpMajorBrowser.OnGUI(RuleCalculatePsychicPhenomenaEffect.PsychicPhenomenaRoot.PerilsOfTheWarpMajor, () => RuleCalculatePsychicPhenomenaEffect.PsychicPhenomenaRoot.PerilsOfTheWarpMajor, + i => i, i => BlueprintExtensions.GetSearchKey(i.Get(), true), i => [BlueprintExtensions.GetSortKey(i.Get())], null, + (majorPeril, _) => { + using (HorizontalScope()) { + Label(BlueprintExtensions.GetSearchKey(majorPeril.Get(), true).Cyan(), Width(650)); + if (Settings.excludedPerilsMajor.Contains(majorPeril.guid)) { + ActionButton("Allow".localize(), () => { + Settings.excludedPerilsMajor.Remove(majorPeril.guid); + Misc.InvalidateFilteredWarpPhenomenaArrays(); + }, AutoWidth()); + } else { + ActionButton("Disable".localize(), () => { + Settings.excludedPerilsMajor.Add(majorPeril.guid); + Misc.InvalidateFilteredWarpPhenomenaArrays(); + }, AutoWidth()); + } + } + ReflectionTreeView.DetailToggle("", majorPeril, majorPeril, 0); + }, + (majorPeril, _) => { ReflectionTreeView.OnDetailGUI(majorPeril); }); + }); + } + } + public static void PatchPsychicTranspiler(bool unpatch) { + var target = AccessTools.Method(typeof(RuleCalculatePsychicPhenomenaEffect), nameof(RuleCalculatePsychicPhenomenaEffect.OnTrigger)); + if (unpatch) { + Main.HarmonyInstance.Unpatch(target, HarmonyPatchType.Transpiler, Main.ModEntry.Info.Id); + } else { + Main.HarmonyInstance.Patch(target, transpiler: new(AccessTools.Method(typeof(Misc), nameof(Misc.RuleCalculatePsychicPhenomenaEffect_OnTrigger_Transpiler)))); + } + } + } +} \ No newline at end of file diff --git a/ToyBox/Classes/MainUI/SkeletonReplacer.cs b/ToyBox/Classes/MainUI/SkeletonReplacer.cs new file mode 100644 index 000000000..823a8c8ca --- /dev/null +++ b/ToyBox/Classes/MainUI/SkeletonReplacer.cs @@ -0,0 +1,517 @@ +using Kingmaker.EntitySystem.Entities; +using Kingmaker.Visual.CharacterSystem; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Unity.Collections; +using UnityEngine; + +namespace ToyBox { + + public class SkeletonReplacer { + + private string owner; + private Skeleton oldSkeleton; + private Skeleton newSkeleton; + + public Dictionary<string, BodyPart> bodyParts; + public Dictionary<string, BodyPart> groupOF; + public Dictionary<string, BodyPart> groupSC; + public Dictionary<string, BodyPart> groupSZ; + public Dictionary<string, BodyPart> groupIO; + public Dictionary<string, BodyPart> groupIS; + + private Dictionary<string, Func<Skeleton.BoneData, BoneDataStruct, float, Skeleton.BoneData>> boneActions; + + public SkeletonReplacer(BaseUnitEntity character) { + + if (character?.View?.CharacterAvatar?.Skeleton is Skeleton skeleton) { + + owner = character.HashKey(); + + oldSkeleton = skeleton; + newSkeleton = DuplicateSkeleton(skeleton); + bodyParts = new Dictionary<string, BodyPart>(); + groupOF = new Dictionary<string, BodyPart>(); + groupSC = new Dictionary<string, BodyPart>(); + groupSZ = new Dictionary<string, BodyPart>(); + groupIO = new Dictionary<string, BodyPart>(); + groupIS = new Dictionary<string, BodyPart>(); + + var partsTable = new Dictionary<string, PartDataStruct> { + + { "OF_positionZ", + new PartDataStruct{ value = 0, min = -10, max = 10, bones = new List<string>{"Position"}}}, + + { "OF_shouldersX", + new PartDataStruct{ value = 0, min = -2, max = 2, bones = new List<string>{"R_Clavicle", "L_Clavicle"}}}, + + { "OF_shouldersZ", + new PartDataStruct{ value = 0, min = -2, max = 2, bones = new List<string>{"R_Clavicle", "L_Clavicle"}}}, + + { "OF_upper_armsX", + new PartDataStruct{ value = 0, min = -2, max = 2, bones = new List<string>{"R_Up_arm", "L_Up_arm"}}}, + + { "OF_upper_legsX", + new PartDataStruct{ value = 0, min = -2, max = 2, bones = new List<string>{"R_Pre_Up_Leg", "L_Pre_Up_Leg"}}}, + + { "SC_pelvisX", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"Pelvis"}}}, + + { "SC_pelvisY", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"Pelvis"}}}, + + { "SC_pelvisZ", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"Pelvis"}}}, + + { "SC_neck", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"Neck"}}}, + + { "SC_shoulders", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"R_Clavicle", "L_Clavicle"}}}, + + { "SC_upper_arms", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"R_Up_arm", "L_Up_arm"}}}, + + { "SC_fore_arms", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"R_ForeArm", "L_ForeArm"}}}, + + { "SC_upper_torso", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"Spine_3"}}}, + + { "SC_middle_torso", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"Spine_2"}}}, + + { "SC_lower_torso", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"Spine_1"}}}, + + { "SC_stomach", + new PartDataStruct{ value = 1, min = 0.2f, max = 5, bones = new List<string>{"Stomach"}}}, + + { "SC_upper_legs", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"R_Pre_Up_Leg", "L_Pre_Up_Leg"}}}, + + { "SC_lower_legs", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"R_Up_leg", "L_Up_leg"}}}, + + { "SC_foots", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"R_foot", "L_foot"}}}, + + { "SC_toes", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"R_toe", "L_toe"}}}, + + { "SZ_head", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"Head"}}}, + + { "SZ_neck", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"Neck_ADJ"}}}, + + { "SZ_shoulders", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"R_Clavicle_ADJ", "L_Clavicle_ADJ"}}}, + + { "SZ_upper_arms", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"R_Up_arm_ADJ", "L_Up_arm_ADJ"}}}, + + { "SZ_fore_arms", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"R_ForeArm_ADJ", "R_ForeArm_Twist_ADJ", "L_ForeArm_ADJ", "L_ForeArm_Twist_ADJ"}}}, + + { "SZ_hands", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"R_Hand", "L_Hand"}}}, + + { "SZ_upper_torso", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"Spine_3_ADJ"}}}, + + { "SZ_middle_torso", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"Spine_2_ADJ"}}}, + + { "SZ_lower_torso", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"Spine_1_ADJ"}}}, + + { "SZ_stomach", + new PartDataStruct{ value = 1, min = 0.2f, max = 5, bones = new List<string>{"Stomach_ADJ"}}}, + + { "SZ_pelvis", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"Pelvis_ADJ"}}}, + + { "SZ_upper_legs", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"R_Up_leg_ADJ", "L_Up_leg_ADJ"}}}, + + { "SZ_middle_legs", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"R_leg_ADJ", "L_leg_ADJ"}}}, + + { "SZ_lower_legs", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"R_Ankle_ADJ", "L_Ankle_ADJ"}}}, + + { "SZ_foots", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"R_foot_ADJ", "L_foot_ADJ"}}}, + + { "SZ_toes", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"R_toe_ADJ", "L_toe_ADJ"}}}, + + { "IO_cloakX", + new PartDataStruct{ value = 0, min = -2, max = 2, bones = new List<string>{"C_back_weapon_slot_08_ADJ"}}}, + + { "IO_cloakY", + new PartDataStruct{ value = 0, min = -2, max = 2, bones = new List<string>{"C_back_weapon_slot_08_ADJ"}}}, + + { "IO_cloakZ", + new PartDataStruct{ value = 0, min = -2, max = 2, bones = new List<string>{"C_back_weapon_slot_08_ADJ"}}}, + + { "IO_backpackX", + new PartDataStruct{ value = 0, min = -2, max = 2, bones = new List<string>{"C_back_w_____slot_08"}}}, + + { "IO_backpackY", + new PartDataStruct{ value = 0, min = -2, max = 2, bones = new List<string>{"C_back_w_____slot_08"}}}, + + { "IO_backpackZ", + new PartDataStruct{ value = 0, min = -2, max = 2, bones = new List<string>{"C_back_w_____slot_08"}}}, + + { "IO_weapon_in_holstersRX", + new PartDataStruct{ value = 0, min = -2, max = 2, bones = new List<string>{"R_front_weapon_slot_01_ADJ", "R_front_weapon_slot_02_ADJ"}}}, + + { "IO_weapon_in_holstersRY", + new PartDataStruct{ value = 0, min = -2, max = 2, bones = new List<string>{"R_front_weapon_slot_01_ADJ", "R_front_weapon_slot_02_ADJ"}}}, + + { "IO_weapon_in_holstersRZ", + new PartDataStruct{ value = 0, min = -2, max = 2, bones = new List<string>{"R_front_weapon_slot_01_ADJ", "R_front_weapon_slot_02_ADJ"}}}, + + { "IO_weapon_in_holstersLX", + new PartDataStruct{ value = 0, min = -2, max = 2, bones = new List<string>{"L_front_weapon_slot_04_ADJ", "L_front_weapon_slot_05_ADJ"}}}, + + { "IO_weapon_in_holstersLY", + new PartDataStruct{ value = 0, min = -2, max = 2, bones = new List<string>{"L_front_weapon_slot_04_ADJ", "L_front_weapon_slot_05_ADJ"}}}, + + { "IO_weapon_in_holstersLZ", + new PartDataStruct{ value = 0, min = -2, max = 2, bones = new List<string>{"L_front_weapon_slot_04_ADJ", "L_front_weapon_slot_05_ADJ"}}}, + + { "IS_cloak", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"C_back_weapon_slot_08_ADJ"}}}, + + { "IS_backpack", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"C_back_w_____slot_08"}}}, + + { "IS_weapon_in_hand", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"R_WeaponBone", "L_WeaponBone"}}}, + + { "IS_weapon_in_holsters", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"R_front_weapon_slot_01", "R_front_weapon_slot_02", "C_front_weapon_slot_03", "L_front_weapon_slot_04", "L_front_weapon_slot_05"}}}, + + { "IS_back_weapon_R", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"R_back_weapon_slot_06", "R_back_weapon_slot_09"}}}, + + { "IS_back_weapon_L", + new PartDataStruct{ value = 1, min = 0.5f, max = 2, bones = new List<string>{"L_back_weapon_slot_07", "L_back_weapon_slot_10"}}}, + }; + + boneActions = new Dictionary<string, Func<Skeleton.BoneData, BoneDataStruct, float, Skeleton.BoneData>> { + + { "OF_positionZ", + (bone, data, param) => { bone.Offset.x = data.originalValue.x + param * -0.1f; return bone; }}, + + { "OF_shouldersX", + (bone, data, param) => { bone.Offset.z = data.originalValue.z + (data.boneName.StartsWith("R_") ? param * 0.1f : param * -0.1f); return bone; }}, + + { "OF_shouldersZ", + (bone, data, param) => { bone.Offset.x = data.originalValue.x + (data.boneName.StartsWith("R_") ? param * -0.1f : param * -0.1f); return bone; }}, + + { "OF_upper_armsX", + (bone, data, param) => { bone.Offset.x = data.originalValue.x + (data.boneName.StartsWith("R_") ? param * -0.1f : param * 0.1f); return bone; }}, + + { "OF_upper_legsX", + (bone, data, param) => { bone.Offset.x = data.originalValue.x + (data.boneName.StartsWith("R_") ? param * 0.1f : param * -0.1f); return bone; }}, + + { "SC_pelvisX", + (bone, data, param) => { bone.Scale.x = data.originalValue.x * param; return bone; }}, + + { "SC_pelvisY", + (bone, data, param) => { bone.Scale.y = data.originalValue.y * param; return bone; }}, + + { "SC_pelvisZ", + (bone, data, param) => { bone.Scale.z = data.originalValue.z * param; return bone; }}, + + { "IO_cloakX", + (bone, data, param) => { bone.Offset.x = data.originalValue.x + param * -0.1f; return bone; }}, + + { "IO_cloakY", + (bone, data, param) => { bone.Offset.y = data.originalValue.y + param * -0.1f; return bone; }}, + + { "IO_cloakZ", + (bone, data, param) => { bone.Offset.z = data.originalValue.z + param * -0.1f; return bone; }}, + + { "IO_backpackX", + (bone, data, param) => { bone.Offset.z = data.originalValue.z + param * -0.1f; return bone; }}, + + { "IO_backpackY", + (bone, data, param) => { bone.Offset.y = data.originalValue.y + param * -0.1f; return bone; }}, + + { "IO_backpackZ", + (bone, data, param) => { bone.Offset.x = data.originalValue.x + param * -0.1f; return bone; }}, + + { "IO_weapon_in_holstersRX", + (bone, data, param) => { bone.Offset.y = data.originalValue.y + param * -0.1f; return bone; }}, + + { "IO_weapon_in_holstersRY", + (bone, data, param) => { bone.Offset.x = data.originalValue.x + param * 0.1f; return bone; }}, + + { "IO_weapon_in_holstersRZ", + (bone, data, param) => { bone.Offset.z = data.originalValue.z + param * -0.1f; return bone; }}, + + { "IO_weapon_in_holstersLX", + (bone, data, param) => { bone.Offset.y = data.originalValue.y + param * -0.1f; return bone; }}, + + { "IO_weapon_in_holstersLY", + (bone, data, param) => { bone.Offset.x = data.originalValue.x + param * -0.1f; return bone; }}, + + { "IO_weapon_in_holstersLZ", + (bone, data, param) => { bone.Offset.z = data.originalValue.z + param * -0.1f; return bone; }}, + + { "default", + (bone, data, param) => { bone.Scale = data.originalValue * param; return bone; }}, + }; + + CreateBodyParts(partsTable, bodyParts); + } + } + + private void CreateBodyParts(Dictionary<string, PartDataStruct> bodyPartsTable, Dictionary<string, BodyPart> bodyParts) { + + foreach (string key in bodyPartsTable.Keys) { + + switch (key) { + + case string s when s.StartsWith("OF_"): + + groupOF[key] = new BodyPart(bodyPartsTable[key].value, bodyPartsTable[key].min, bodyPartsTable[key].max); + bodyParts[key] = groupOF[key]; + break; + + case string s when s.StartsWith("SC_"): + + groupSC[key] = new BodyPart(bodyPartsTable[key].value, bodyPartsTable[key].min, bodyPartsTable[key].max); + bodyParts[key] = groupSC[key]; + break; + + case string s when s.StartsWith("SZ_"): + + groupSZ[key] = new BodyPart(bodyPartsTable[key].value, bodyPartsTable[key].min, bodyPartsTable[key].max); + bodyParts[key] = groupSZ[key]; + break; + + case string s when s.StartsWith("IO_"): + + groupIO[key] = new BodyPart(bodyPartsTable[key].value, bodyPartsTable[key].min, bodyPartsTable[key].max); + bodyParts[key] = groupIO[key]; + break; + + case string s when s.StartsWith("IS_"): + + groupIS[key] = new BodyPart(bodyPartsTable[key].value, bodyPartsTable[key].min, bodyPartsTable[key].max); + bodyParts[key] = groupIS[key]; + break; + + default: + + bodyParts[key] = new BodyPart(bodyPartsTable[key].value, bodyPartsTable[key].min, bodyPartsTable[key].max); + break; + } + + var isOffsetPart = groupOF.ContainsKey(key) || groupIO.ContainsKey(key); + + foreach (string value in bodyPartsTable[key].bones) { + + if (oldSkeleton.BonesByName.ContainsKey(value)) { + + bodyParts[key].isEmpty = false; + + var name = value; + var index = oldSkeleton.Bones.IndexOf(oldSkeleton.BonesByName[name]); + var offset = oldSkeleton.Bones[index].ApplyOffset = isOffsetPart; + var oldValue = isOffsetPart ? oldSkeleton.Bones[index].Offset : oldSkeleton.Bones[index].Scale; + var boneData = new BoneDataStruct { boneName = name, boneIndex = index, applyOffset = offset, originalValue = oldValue }; + + bodyParts[key].bonesData.Add(boneData); + } + } + } + } + + private Skeleton DuplicateSkeleton(Skeleton skeleton) { + + var tempSkeleton = new Skeleton(); + var newBoneJobArray = new NativeArray<Skeleton.BoneData>(skeleton.Bones.Count, Allocator.Persistent); + + for (int i = 0; i < skeleton.Bones.Count; i++) { + + newBoneJobArray[i] = new Skeleton.BoneData { ApplyOffset = skeleton.Bones[i].ApplyOffset, Offset = skeleton.Bones[i].Offset, Scale = skeleton.Bones[i].Scale }; + } + + tempSkeleton.m_BoneDataForJob = newBoneJobArray; + tempSkeleton.name = skeleton.name; + tempSkeleton.Bones = skeleton.Bones; + tempSkeleton.hideFlags = skeleton.hideFlags; + tempSkeleton.AnimationSetOverride = skeleton.AnimationSetOverride; + tempSkeleton.CharacterFxBonesMap = skeleton.CharacterFxBonesMap; + tempSkeleton.RaceBoneHierarchyObject = skeleton.RaceBoneHierarchyObject; + tempSkeleton.m_DollRoomZoomPreset = skeleton.m_DollRoomZoomPreset; + tempSkeleton.m_BonesByName = skeleton.m_BonesByName; + tempSkeleton.m_IsDirty = skeleton.m_IsDirty; + + return tempSkeleton; + } + + private void UpdateWeaponSizes() { + + var m1 = bodyParts.ContainsKey("SC_lower_torso") ? bodyParts["SC_lower_torso"].parameter : 1; + var m2 = bodyParts.ContainsKey("SC_middle_torso") ? bodyParts["SC_middle_torso"].parameter : 1; + var m3 = bodyParts.ContainsKey("SC_upper_torso") ? bodyParts["SC_upper_torso"].parameter : 1; + var m4 = bodyParts.ContainsKey("SC_shoulders") ? bodyParts["SC_shoulders"].parameter : 1; + var m5 = bodyParts.ContainsKey("SC_upper_arms") ? bodyParts["SC_upper_arms"].parameter : 1; + var m6 = bodyParts.ContainsKey("SC_fore_arms") ? bodyParts["SC_fore_arms"].parameter : 1; + var m7 = bodyParts.ContainsKey("SZ_hands") ? bodyParts["SZ_hands"].parameter : 1; + + var M1 = m1 * m2 * m3; + var M2 = M1 * m4 * m5 * m6 * m7; + + UpdateWeapon("IS_weapon_in_hand", M2); + UpdateWeapon("IS_back_weapon_R", M1); + UpdateWeapon("IS_back_weapon_L", M1); + } + + private void UpdateWeapon(string part, float multiplier) { + + if (bodyParts.ContainsKey(part)) { + + foreach (BoneDataStruct bone in bodyParts[part].bonesData) { + + var tarrgetBone = newSkeleton.m_BoneDataForJob[bone.boneIndex]; + + tarrgetBone.Scale = (bone.originalValue * bodyParts[part].parameter) / multiplier; + newSkeleton.m_BoneDataForJob[bone.boneIndex] = tarrgetBone; + } + } + } + + private void BonesModification(Dictionary<string, float> loadedData, bool load, string part) { + + float parameter; + + if (load && loadedData.ContainsKey(part)) { + + parameter = loadedData[part]; + bodyParts[part].parameter = parameter; + + } else { + + parameter = bodyParts[part].parameter; + loadedData[part] = parameter; + } + + foreach (BoneDataStruct bone in bodyParts[part].bonesData) { + + var targetBone = newSkeleton.m_BoneDataForJob[bone.boneIndex]; + + if (boneActions.ContainsKey(part)) { + + targetBone.ApplyOffset = bone.applyOffset; + targetBone = boneActions[part](targetBone, bone, parameter); + + } else { + + targetBone = boneActions["default"](targetBone, bone, parameter); + } + + newSkeleton.m_BoneDataForJob[bone.boneIndex] = targetBone; + } + } + + public void ApplyBonesModification(BaseUnitEntity character, bool loadPerSaveData = true, string whichPart = "all") { + + if (character?.HashKey() == owner && character?.View?.CharacterAvatar?.Skeleton is Skeleton skeleton) { + + if (skeleton != newSkeleton) { + + character.View.CharacterAvatar.Skeleton = newSkeleton; + + foreach (EquipmentEntity item in character.View.CharacterAvatar.EquipmentEntities) { + + foreach (EquipmentEntity.OutfitPart part in item.OutfitParts) { + + if (part.Special == EquipmentEntity.OutfitPartSpecialType.Backpack) { + + part.m_BoneName = "C_back_weapon_slot_08"; + character.View.CharacterAvatar.RebuildOutfit(); + } + } + } + } + + var loadedPartsData = Main.Settings.perSave.characterSkeletonReplacers; + + if (!loadedPartsData.ContainsKey(character.HashKey())) { + + loadedPartsData[character.HashKey()] = new Dictionary<string, float>(); + } + + if (bodyParts.ContainsKey(whichPart)) { + + BonesModification(loadedPartsData[character.HashKey()], loadPerSaveData, whichPart); + + } else { + + foreach (string key in bodyParts.Keys) { + + BonesModification(loadedPartsData[character.HashKey()], loadPerSaveData, key); + } + } + + UpdateWeaponSizes(); + + if (loadPerSaveData) { + + foreach (string key in loadedPartsData[character.HashKey()].Keys) { + + if (!bodyParts.ContainsKey(key)) { + + loadedPartsData[character.HashKey()].Remove(key); + } + } + } + } + } + + public class BodyPart { + + public bool isEmpty = true; + public float parameter; + public float min; + public float max; + public List<BoneDataStruct> bonesData; + + public BodyPart(float defaultParameter, float minParameter, float maxParameter) { + + parameter = defaultParameter; + min = minParameter; + max = maxParameter; + bonesData = new List<BoneDataStruct>(); + } + } + + public struct PartDataStruct { + + public float value; + public float min; + public float max; + public List<string> bones; + } + + public struct BoneDataStruct { + + public string boneName; + public int boneIndex; + public bool applyOffset; + public Vector3 originalValue; + } + } +} diff --git a/ModKit/DataViewer/Indexer.cs b/ToyBox/Classes/ModKit/DataViewer/Indexer.cs similarity index 62% rename from ModKit/DataViewer/Indexer.cs rename to ToyBox/Classes/ModKit/DataViewer/Indexer.cs index db6e0f47d..d17a3ca76 100644 --- a/ModKit/DataViewer/Indexer.cs +++ b/ToyBox/Classes/ModKit/DataViewer/Indexer.cs @@ -3,14 +3,14 @@ using System.Text; namespace ModKit.DataViewer { - public static class Extensions { - } + public static class Extensions { } + public class Indexer<TKey, TItem> { // TKey must be unique or crashy crashy - public static Func<TItem, TKey> GetKey { get; set; } - public static Func<TKey, TItem> GetItem { get; set; } - public static TKey Key(TItem item) => GetKey(item); - public static TItem Item(TKey key) => GetItem(key); - public static Entry GetEntry(TKey key) => _lookup.GetValueOrDefault(key); + public static Func<TItem, TKey>? GetKey { get; set; } + public static Func<TKey, TItem>? GetItem { get; set; } + public static TKey Key(TItem item) => GetKey!(item); + public static TItem Item(TKey key) => GetItem!(key); + public static Entry? GetEntry(TKey key) => _lookup.GetValueOrDefault(key); private static readonly Dictionary<TKey, Entry> _lookup = new(); @@ -19,36 +19,36 @@ public class KeyPath { public string[] Path { get; private set; } public KeyPath(TKey key, params string[] path) { Key = key; - this.Path = path; + Path = path; } public KeyPath(TKey key, string path) { Key = key; - this.Path = path.Split('.'); + Path = path.Split('.'); } public KeyPath(TItem item, params string[] path) { Key = Key(item); - this.Path = path; + Path = path; } public KeyPath(TItem item, string path) { Key = Key(item); - this.Path = path.Split('.'); + Path = path.Split('.'); } } + public class Entry { public TKey Key { get; private set; } - public HashSet<KeyPath> ReferencedBy { get; private set; } = new HashSet<KeyPath> { }; - public HashSet<KeyPath> SubItems { get; private set; } = new HashSet<KeyPath> { }; - public Dictionary<KeyPath, string> Properties { get; private set; } - private TItem _item { get; set; } = default; + public HashSet<KeyPath> ReferencedBy { get; private set; } = new() { }; + public HashSet<KeyPath> SubItems { get; private set; } = new() { }; + public Dictionary<KeyPath, string>? Properties { get; private set; } + private TItem? _item { get; set; } = default; + public TItem Item { get { - _item ??= GetItem(Key); + _item ??= GetItem!(Key); return _item; } } - public Entry(TKey key) { - Key = key; - } + public Entry(TItem item, TKey key) { _item = item; Key = key; @@ -60,4 +60,4 @@ public void Add(TItem item) { _lookup[key] = new Entry(item, key); } } -} +} \ No newline at end of file diff --git a/ModKit/DataViewer/ReflectionSearch.cs b/ToyBox/Classes/ModKit/DataViewer/ReflectionSearch.cs similarity index 86% rename from ModKit/DataViewer/ReflectionSearch.cs rename to ToyBox/Classes/ModKit/DataViewer/ReflectionSearch.cs index 556bba0f1..81eed8f7d 100644 --- a/ModKit/DataViewer/ReflectionSearch.cs +++ b/ToyBox/Classes/ModKit/DataViewer/ReflectionSearch.cs @@ -11,11 +11,9 @@ using HarmonyLib; using UnityEngine; using static ModKit.Utility.ReflectionCache; -using static ModKit.Utility.StringExtensions; -using static ModKit.Utility.RichTextExtensions; +using static ModKit.StringExtensions; namespace ModKit.DataViewer { - /** * Strategy For Async Deep Search * @@ -70,37 +68,33 @@ public partial class ReflectionSearch { public delegate void SearchProgress(int visitCount, int depth, int breadth); private CancellationTokenSource _cancellationTokenSource; public bool isSearching { get; private set; } = false; - private static HashSet<int> VisitedInstanceIDs = new HashSet<int> { }; + private static HashSet<int> VisitedInstanceIDs = new() { }; public static int SequenceNumber = 0; private static ReflectionSearch _shared; public static int maxSearchDepth = 1; private static Queue<Action> _updates = new(); public static int ApplyUpdates() { lock (_updates) { - int count = _updates.Count; + var count = _updates.Count; foreach (var update in _updates) update.Invoke(); _updates.Clear(); return count; } } public static void AddUpdate(Action update) { - lock (_updates) { - _updates.Enqueue(update); - } + lock (_updates) _updates.Enqueue(update); } public static void ClearUpdates() { - lock (_updates) { - _updates.Clear(); - } + lock (_updates) _updates.Clear(); } + public static ReflectionSearch Shared { get { - if (_shared == null) { - _shared = new ReflectionSearch(); - } + if (_shared == null) _shared = new ReflectionSearch(); return _shared; } } + // Task.Run(() => UpdateSearchResults(_searchText, definitions, searchKey, sortKey, search)); public void StartSearch(Node node, string[] searchTerms, SearchProgress updater, ReflectionSearchResult resultRoot) { if (isSearching) { @@ -112,7 +106,7 @@ public void StartSearch(Node node, string[] searchTerms, SearchProgress updater, resultRoot.Clear(); resultRoot.Node = node; } - _cancellationTokenSource = new(); + _cancellationTokenSource = new CancellationTokenSource(); isSearching = true; AddUpdate(() => updater(0, 0, 1)); if (node == null) return; @@ -120,7 +114,7 @@ public void StartSearch(Node node, string[] searchTerms, SearchProgress updater, Mod.Log($"seq: {SequenceNumber} - search for: {searchTerms}"); if (searchTerms.Length != 0) { var todo = new List<Node> { node }; - Task.Run(() => Search(searchTerms, todo , 0, 0, SequenceNumber, updater, resultRoot)); + Task.Run(() => Search(searchTerms, todo, 0, 0, SequenceNumber, updater, resultRoot)); } } public void Stop() { @@ -148,15 +142,14 @@ private void Search(string[] searchTerms, List<Node> todo, int depth, int visitC isSearching = false; return; } - bool foundMatch = false; + var foundMatch = false; var instanceID = node.InstanceID; - bool alreadyVisted = false; + var alreadyVisted = false; if (instanceID is int instID) { if (VisitedInstanceIDs.Contains(instID)) alreadyVisted = true; - else { + else VisitedInstanceIDs.Add(instID); - } } visitCount++; //Main.Log(depth, $"node: {node.Name} - {node.GetPath()}"); @@ -164,7 +157,7 @@ private void Search(string[] searchTerms, List<Node> todo, int depth, int visitC var matchCount = 0; foreach (var term in searchTerms) { var nodeToCheck = node; - bool found = false; + var found = false; while (nodeToCheck != null && !found) { if (nodeToCheck.Name.Matches(term) || nodeToCheck.ValueText.Matches(term)) { found = true; @@ -185,57 +178,47 @@ private void Search(string[] searchTerms, List<Node> todo, int depth, int visitC } }); } - } - catch (Exception e) { + } catch (Exception e) { Mod.Log(depth, $"caught - {e}"); } node.Matches = foundMatch; - if (!foundMatch) { + if (!foundMatch) //Main.Log(depth, $"NOT matched: {node.Name} - {node.ValueText}"); //if (node.Expanded == ToggleState.On && node.GetParent() != null) { // node.Expanded = ToggleState.Off; //} - if (visitCount % 100 == 0) AddUpdate(() => updater(visitCount, depth, breadth)); - - } + if (visitCount % 100 == 0) + AddUpdate(() => updater(visitCount, depth, breadth)); if (node.hasChildren && !alreadyVisted) { //if (node.Name == "SyncRoot") break; //if (node.Name == "normalized") break; try { - foreach (var child in node.GetItemNodes()) { + foreach (var child in node.GetItemNodes()) //Main.Log(depth + 1, $"item: {child.Name}"); newTodo.Add(child); - } - } - catch (Exception e) { + } catch (Exception e) { Mod.Log(depth, $"caught - {e}"); } try { - foreach (var child in node.GetComponentNodes()) { + foreach (var child in node.GetComponentNodes()) //Main.Log(depth + 1, $"comp: {child.Name}"); newTodo.Add(child); - } - } - catch (Exception e) { + } catch (Exception e) { Mod.Log(depth, $"caught - {e}"); } try { - foreach (var child in node.GetPropertyNodes()) { + foreach (var child in node.GetPropertyNodes()) //Main.Log(depth + 1, $"prop: {child.Name}"); newTodo.Add(child); - } - } - catch (Exception e) { + } catch (Exception e) { Mod.Log(depth, $"caught - {e}"); } try { - foreach (var child in node.GetFieldNodes()) { + foreach (var child in node.GetFieldNodes()) //Main.Log(depth + 1, $"field: {child.Name}"); newTodo.Add(child); - } - } - catch (Exception e) { + } catch (Exception e) { Mod.Log(depth, $"caught - {e}"); } } @@ -246,4 +229,4 @@ private void Search(string[] searchTerms, List<Node> todo, int depth, int visitC Stop(); } } -} +} \ No newline at end of file diff --git a/ModKit/DataViewer/ReflectionSearchResult.cs b/ToyBox/Classes/ModKit/DataViewer/ReflectionSearchResult.cs similarity index 56% rename from ModKit/DataViewer/ReflectionSearchResult.cs rename to ToyBox/Classes/ModKit/DataViewer/ReflectionSearchResult.cs index be1f6b3dd..d04e505fa 100644 --- a/ModKit/DataViewer/ReflectionSearchResult.cs +++ b/ToyBox/Classes/ModKit/DataViewer/ReflectionSearchResult.cs @@ -9,36 +9,33 @@ namespace ModKit.DataViewer { public abstract class ResultNode { - public virtual string Name { get; } - public virtual Type Type { get; } - public virtual string NodeTypePrefix { get; } - public virtual string ValueText { get; } + public virtual string? Name { get; } + public virtual Type? Type { get; } + public virtual string? NodeTypePrefix { get; } + public virtual string? ValueText { get; } } + public class ResultNode<TNode> : ResultNode where TNode : class { protected const BindingFlags ALL_FLAGS = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; public delegate bool TraversalCallback(ResultNode<TNode> node, int depth); public TNode Node; - public List<ResultNode<TNode>> children = new List<ResultNode<TNode>>(); - public HashSet<TNode> matches { get { return children.Select(c => c.Node).ToHashSet(); } } + public List<ResultNode<TNode>> children = new(); + public HashSet<TNode> matches => children.Select(c => c.Node).ToHashSet(); public ToggleState ToggleState = ToggleState.Off; public ToggleState ShowSiblings = ToggleState.Off; - public bool isMatch; // flag indicating that this node is an actual matching node and not a parent of a matching node + public bool isMatch; // flag indicating that this node is an actual matching node and not a parent of a matching node public int Count; public void Traverse(TraversalCallback callback, int depth = 0) { - if (callback(this, depth)) { - foreach (var child in children) { + if (callback(this, depth)) + foreach (var child in children) child.Traverse(callback, depth + 1); - } - } - } - public ResultNode<TNode> FindChild(TNode node) { - return children.Find(rn => rn.Node == node); } + public ResultNode<TNode> FindChild(TNode node) => children.Find(rn => rn.Node == node); public ResultNode<TNode> FindOrAddChild(TNode node) { var rnode = children.Find(rn => rn.Node == node); if (rnode == null) { - rnode = Activator.CreateInstance(this.GetType(), ALL_FLAGS, null, null, null) as ResultNode<TNode>; + rnode = Activator.CreateInstance(GetType(), ALL_FLAGS, null, null, null) as ResultNode<TNode>; rnode.Node = node; children.Add(rnode); } @@ -59,29 +56,23 @@ public void Clear() { } private StringBuilder BuildString(StringBuilder builder, int depth) { builder.Append($"{NodeTypePrefix} {Name}:{Type.ToString()} - {ValueText}\n".Indent(depth)); - foreach (var child in children) { - builder = child.BuildString(builder, depth + 1); - } + foreach (var child in children) builder = child.BuildString(builder, depth + 1); return builder; } - override public string ToString() { - return BuildString(new StringBuilder().Append("\n"), 0).ToString(); - } + public override string? ToString() => BuildString(new StringBuilder().Append("\n"), 0).ToString(); } + public class ReflectionSearchResult : ResultNode<Node> { - public override string Name { get { return Node.Name; } } - public override Type Type { get { return Node.Type; } } - public override string NodeTypePrefix { get { return Node.NodeTypePrefix; } } - public override string ValueText { get { return Node.ValueText; } } + public override string? Name => Node.Name; + public override Type Type => Node.Type; + public override string NodeTypePrefix => Node.NodeTypePrefix; + public override string ValueText => Node.ValueText; public void AddSearchResult(Node node) { if (node == null) return; var path = new List<Node>(); - for (var n = node; node != null && node != this.Node; node = node.GetParent()) { - path.Add(node); - } + for (var n = node; node != null && node != Node; node = node.GetParent()) path.Add(node); AddSearchResult(path.Reverse<Node>()); } } -} - +} \ No newline at end of file diff --git a/ModKit/DataViewer/ReflectionTree.cs b/ToyBox/Classes/ModKit/DataViewer/ReflectionTree.cs similarity index 66% rename from ModKit/DataViewer/ReflectionTree.cs rename to ToyBox/Classes/ModKit/DataViewer/ReflectionTree.cs index ee09370cf..13893782f 100644 --- a/ModKit/DataViewer/ReflectionTree.cs +++ b/ToyBox/Classes/ModKit/DataViewer/ReflectionTree.cs @@ -18,21 +18,20 @@ public enum NodeType { Field, Property } + // This structure has evolved into a reflection graph or DAG but for the sake of continuity we will stick with calling it a tree public class ReflectionTree : ReflectionTree<object> { public ReflectionTree(object root) : base(root) { } } public class ReflectionTree<TRoot> { - private RootNode<TRoot> _root; + private RootNode<TRoot>? _root; - public TRoot Root => _root.Value; + public TRoot Root => _root!.Value; - public Node RootNode => _root; + public Node? RootNode => _root; - public ReflectionTree(TRoot root) { - SetRoot(root); - } + public ReflectionTree(TRoot root) => SetRoot(root); public void SetRoot(TRoot root) { if (_root != null) @@ -46,7 +45,7 @@ public abstract class Node { // this allows us to avoid duplicated nodes for the same value //public static ConditionalWeakTable<object, Node> ValueToNodeLookup = new ConditionalWeakTable<object, Node>(); - protected const BindingFlags ALL_FLAGS = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; + protected const BindingFlags AllFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy; public readonly NodeType NodeType; public readonly Type Type; @@ -56,10 +55,13 @@ protected Node(Type type, NodeType nodeType) { Type = type; IsNullable = Type.IsGenericType && !Type.IsGenericTypeDefinition && Type.GetGenericTypeDefinition() == typeof(Nullable<>); } - [ObsoleteAttribute("TODO - move this into a proper view model", false)] + + //[ObsoleteAttribute("TODO - move this into a proper view model", false)] public ToggleState Expanded { get; set; } - [ObsoleteAttribute("TODO - move this into a proper view model", false)] + + //[ObsoleteAttribute("TODO - move this into a proper view model", false)] public bool Matches { get; set; } + public string NodeTypePrefix { get { switch (NodeType) { @@ -76,40 +78,36 @@ public string NodeTypePrefix { } } } + public int ExpandedNodeCount { get { - int count = 1; + var count = 1; if (IsBaseType) return count; - if (this.Expanded == ToggleState.On) { - foreach (var child in GetItemNodes()) { - count += child.ExpandedNodeCount; - } - foreach (var child in GetComponentNodes()) { - count += child.ExpandedNodeCount; - } - foreach (var child in GetPropertyNodes()) { - count += child.ExpandedNodeCount; - } - foreach (var child in GetFieldNodes()) { - count += child.ExpandedNodeCount; - } + if (Expanded == ToggleState.On) { + foreach (var child in GetItemNodes()) count += child.ExpandedNodeCount; + foreach (var child in GetComponentNodes()) count += child.ExpandedNodeCount; + foreach (var child in GetPropertyNodes()) count += child.ExpandedNodeCount; + foreach (var child in GetFieldNodes()) count += child.ExpandedNodeCount; } return Count; } } + public int ChildrenCount { get { if (IsBaseType) return 0; return GetItemNodes().Count + GetComponentNodes().Count + GetFieldNodes().Count + GetPropertyNodes().Count; } } + public bool hasChildren { get { if (IsBaseType) return false; return ChildrenCount > 0; } } - public string Name { get; protected set; } + + public string? Name { get; protected set; } public abstract string ValueText { get; } public abstract Type InstType { get; } public abstract bool IsBaseType { get; } @@ -120,24 +118,19 @@ public bool hasChildren { public abstract bool IsNull { get; } public abstract int? InstanceID { get; } public static IEnumerable<FieldInfo> GetFields(Type type) { - HashSet<string> names = new HashSet<string>(); - foreach (FieldInfo field in (Nullable.GetUnderlyingType(type) ?? type).GetFields(ALL_FLAGS)) { - if (!field.IsStatic && - !field.IsDefined(typeof(CompilerGeneratedAttribute), false) && // ignore backing field - names.Add(field.Name)) { + var names = new HashSet<string>(); + foreach (var field in (Nullable.GetUnderlyingType(type) ?? type).GetFields(AllFlags)) + if (!field.IsStatic + && !field.IsDefined(typeof(CompilerGeneratedAttribute), false) + && // ignore backing field + names.Add(field.Name)) yield return field; - } - } } public static IEnumerable<PropertyInfo> GetProperties(Type type) { - HashSet<string> names = new HashSet<string>(); - foreach (PropertyInfo property in (Nullable.GetUnderlyingType(type) ?? type).GetProperties(ALL_FLAGS)) { - if (property.GetMethod != null && - !property.GetMethod.IsStatic && - property.GetMethod.GetParameters().Length == 0 && - names.Add(property.Name)) + var names = new HashSet<string>(); + foreach (var property in (Nullable.GetUnderlyingType(type) ?? type).GetProperties(AllFlags)) + if (property.GetMethod != null && !property.GetMethod.IsStatic && property.GetMethod.GetParameters().Length == 0 && names.Add(property.Name)) yield return property; - } } public abstract IReadOnlyCollection<Node> GetItemNodes(); public abstract IReadOnlyCollection<Node> GetComponentNodes(); @@ -150,9 +143,9 @@ private void AppendPathFromRoot(StringBuilder sb) { parent.AppendPathFromRoot(sb); sb.Append("."); } - sb.Append(this.Name); + sb.Append(Name); } - public String GetPath() { + public string GetPath() { var sb = new StringBuilder(); AppendPathFromRoot(sb); return sb.ToString(); @@ -165,8 +158,7 @@ public String GetPath() { internal abstract class GenericNode<TNode> : Node { // the graph will not show any child nodes of following types - private static readonly HashSet<Type> BASE_TYPES = new HashSet<Type>() - { + private static readonly HashSet<Type> BaseTypes = new() { typeof(object), typeof(DBNull), typeof(bool), @@ -188,28 +180,27 @@ internal abstract class GenericNode<TNode> : Node { typeof(UIntPtr) }; - private Type _instType; + private Type? _instType; private bool? _isBaseType; private bool? _isEnumerable; private bool? _isGameObject; - private TNode _value; + private TNode? _value; private int _enumerableCount = -1; private bool _valueIsDirty = true; - private List<Node> _componentNodes; - private List<Node> _itemNodes; - private List<Node> _fieldNodes; - private List<Node> _propertyNodes; + private List<Node>? _componentNodes; + private List<Node>? _itemNodes; + private List<Node>? _fieldNodes; + private List<Node>? _propertyNodes; private bool _componentIsDirty; private bool _itemIsDirty; private bool _fieldIsDirty; private bool _propertyIsDirty; protected GenericNode(NodeType nodeType) : base(typeof(TNode), nodeType) { - if (Type.IsValueType && !IsNullable) { - _instType = Type; - } + if (Type.IsValueType && !IsNullable) _instType = Type; } + public TNode Value { get { UpdateValue(); @@ -218,11 +209,11 @@ public TNode Value { protected set { if (!value?.Equals(_value) ?? _value != null) { _value = value; - if (value is BlueprintReferenceBase bpRefBase && bpRefBase.Cached is null) + if (value is BlueprintReferenceBase { Cached: null } bpRefBase) bpRefBase.GetBlueprint(); _enumerableCount = -1; if (!Type.IsValueType || IsNullable) { - Type oldType = _instType; + var oldType = _instType; _instType = value?.GetType(); if (_instType != oldType) { _isBaseType = null; @@ -235,37 +226,48 @@ protected set { } } } - public override string ValueText => IsException ? "<exception>" : IsNull ? "<null>" : Value.ToString(); + + public override string ValueText => IsException + ? "<exception>" + : IsNull + ? "<null>" + : Value.ToString(); + public override Type InstType { get { UpdateValue(); return _instType; } } + public override bool IsBaseType { get { UpdateValue(); - return _isBaseType ?? (_isBaseType = BASE_TYPES.Contains(Nullable.GetUnderlyingType(InstType ?? Type) ?? InstType ?? Type)).Value; + return _isBaseType ?? (_isBaseType = BaseTypes.Contains(Nullable.GetUnderlyingType(InstType ?? Type) ?? InstType ?? Type)).Value; } } + public override bool IsEnumerable { get { UpdateValue(); return _isEnumerable ?? (_isEnumerable = (InstType ?? Type).GetInterfaces().Contains(typeof(IEnumerable))).Value; } } + public override int EnumerableCount { get { UpdateValue(); return _enumerableCount; } } + public override bool IsGameObject { get { UpdateValue(); return _isGameObject ?? (_isGameObject = typeof(GameObject).IsAssignableFrom(InstType ?? Type)).Value; } } + public override int? InstanceID { get { int? result = null; @@ -274,7 +276,8 @@ public override int? InstanceID { return result; } } - public override bool IsNull => Value == null || ((Value is UnityEngine.Object unityObject) && !unityObject); + + public override bool IsNull => Value == null || (Value is UnityEngine.Object unityObject && !unityObject); public override IReadOnlyCollection<Node> GetComponentNodes() { UpdateComponentNodes(); return _componentNodes.AsReadOnly(); @@ -291,98 +294,78 @@ public override IReadOnlyCollection<Node> GetPropertyNodes() { UpdatePropertyNodes(); return _propertyNodes.AsReadOnly(); } - public override Node GetParent() { - return null; - } - public override void SetDirty() { - _valueIsDirty = true; - } - public override bool IsDirty() { - return _valueIsDirty; - } + public override Node GetParent() => null; + public override void SetDirty() => _valueIsDirty = true; + public override bool IsDirty() => _valueIsDirty; private Node FindOrCreateChildForValue(object item, Type type, params object[] childArgs) { Node node = null; //if (item != null) // ValueToNodeLookup.TryGetValue(item, out node); - if (node == null) { - node = (Activator.CreateInstance(type, ALL_FLAGS, null, childArgs, null) as Node); - //if (item != null) - // ValueToNodeLookup.Add(item, node); - } + if (node == null) node = Activator.CreateInstance(type, AllFlags, null, childArgs, null) as Node; + //if (item != null) + // ValueToNodeLookup.Add(item, node); return node; } private void UpdateComponentNodes() { UpdateValue(); - if (!_componentIsDirty && _componentNodes != null) { - return; - } + if (!_componentIsDirty && _componentNodes != null) return; _componentIsDirty = false; - if (_componentNodes == null) { - _componentNodes = new List<Node>(); - } + if (_componentNodes == null) _componentNodes = new List<Node>(); _componentNodes.Clear(); - if (IsException || IsNull || !IsGameObject) { - return; - } + if (IsException || IsNull || !IsGameObject) return; - Type nodeType = typeof(ComponentNode); - int i = 0; - foreach (Component item in (Value as GameObject).GetComponents<Component>()) { + var nodeType = typeof(ComponentNode); + var i = 0; + foreach (var item in (Value as GameObject).GetComponents<Component>()) { _componentNodes.Add(FindOrCreateChildForValue(item, nodeType, this, "<component_" + i + ">", item)); i++; } - this._enumerableCount = i; + _enumerableCount = i; } private void UpdateItemNodes() { UpdateValue(); - if (!_itemIsDirty && _itemNodes != null) { - return; - } + if (!_itemIsDirty && _itemNodes != null) return; _itemIsDirty = false; - if (_itemNodes == null) { - _itemNodes = new List<Node>(); - } + if (_itemNodes == null) _itemNodes = new List<Node>(); _itemNodes.Clear(); - if (IsException || IsNull || !IsEnumerable) { - return; - } - - IEnumerable<Type> itemTypes = InstType.GetInterfaces() - .Where(item => item.IsGenericType && item.GetGenericTypeDefinition() == typeof(IEnumerable<>)) - .Select(item => item.GetGenericArguments()[0]); - Type itemType = itemTypes.Count() == 1 ? itemTypes.First() : typeof(object); - Type nodeType = typeof(ItemNode<>).MakeGenericType(itemType); - int i = 0; - foreach (object item in Value as IEnumerable) { - _itemNodes.Add(FindOrCreateChildForValue(item, nodeType, this, "<item_" + i + ">", item)); + if (IsException || IsNull || !IsEnumerable) return; + + var itemTypes = InstType.GetInterfaces() + .Where(item => item.IsGenericType && item.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + .Select(item => item.GetGenericArguments()[0]); + var itemType = itemTypes.Count() == 1 ? itemTypes.First() : typeof(object); + if (itemType == typeof(BlueprintReferenceBase) + || itemType.IsSubclassOf(typeof(BlueprintReferenceBase))) + itemType = typeof(BlueprintScriptableObject); + var nodeType = typeof(ItemNode<>).MakeGenericType(itemType); + var i = 0; + foreach (var item in Value as IEnumerable) { + var resolvedItem = item; + if (item is BlueprintReferenceBase bpRefBase) resolvedItem = bpRefBase.GetBlueprint(); + _itemNodes.Add(FindOrCreateChildForValue(resolvedItem, nodeType, this, "<item_" + i + ">", resolvedItem)); i++; } - this._enumerableCount = i; + _enumerableCount = i; } private void UpdateFieldNodes() { UpdateValue(); - if (!_fieldIsDirty && _fieldNodes != null) { - return; - } + if (!_fieldIsDirty && _fieldNodes != null) return; _fieldIsDirty = false; if (_fieldNodes == null) _fieldNodes = new List<Node>(); _fieldNodes.Clear(); - if (IsException || IsNull) { - return; - } + if (IsException || IsNull) return; - Type nodeType = InstType.IsValueType ? !IsNullable ? - typeof(FieldOfStructNode<,,>) : typeof(FieldOfNullableNode<,,>) : typeof(FieldOfClassNode<,,>); + var nodeType = InstType.IsValueType ? !IsNullable ? typeof(FieldOfStructNode<,,>) : typeof(FieldOfNullableNode<,,>) : typeof(FieldOfClassNode<,,>); _fieldNodes = GetFields(InstType).Select(child => FindOrCreateChildForValue(child, nodeType.MakeGenericType(Type, InstType, child.FieldType), this, child.Name)).ToList(); //_fieldNodes = GetFields(InstType).Select(child => Activator.CreateInstance( @@ -394,24 +377,25 @@ private void UpdateFieldNodes() { private void UpdatePropertyNodes() { UpdateValue(); - if (!_propertyIsDirty && _propertyNodes != null) { - return; - } + if (!_propertyIsDirty && _propertyNodes != null) return; _propertyIsDirty = false; if (_propertyNodes == null) _propertyNodes = new List<Node>(); _propertyNodes.Clear(); - if (IsException || IsNull) { - return; - } - Type nodeType = InstType.IsValueType ? !IsNullable ? - typeof(PropertyOfStructNode<,,>) : typeof(PropertyOfNullableNode<,,>) : typeof(PropertyOfClassNode<,,>); + if (IsException || IsNull) return; + var nodeType = InstType.IsValueType ? !IsNullable ? typeof(PropertyOfStructNode<,,>) : typeof(PropertyOfNullableNode<,,>) : typeof(PropertyOfClassNode<,,>); _propertyNodes = GetProperties(InstType).Select(child => FindOrCreateChildForValue(child, - nodeType.MakeGenericType(Type, InstType, child.PropertyType), this, child.Name)).ToList(); - + nodeType.MakeGenericType(Type, InstType, child.PropertyType), + this, + child.Name)).ToList(); + // TODO: generalize this and implement custom data extractors + if (Value is BlueprintReferenceBase bpRefBase) { + var customNode = new CustomNode<SimpleBlueprint>("Cached", bpRefBase.GetBlueprint(), NodeType.Property); + _propertyNodes.Add(customNode); + } _propertyNodes.Sort((x, y) => x.Name.CompareTo(y.Name)); } protected override void UpdateValue() { @@ -422,11 +406,11 @@ protected override void UpdateValue() { _itemIsDirty = true; if (_fieldNodes != null) - foreach (Node child in _fieldNodes) + foreach (var child in _fieldNodes) child.SetDirty(); if (_propertyNodes != null) - foreach (Node child in _propertyNodes) + foreach (var child in _propertyNodes) child.SetDirty(); UpdateValueImpl(); } @@ -437,7 +421,7 @@ protected override void UpdateValue() { internal abstract class PassiveNode<TNode> : GenericNode<TNode> { public override bool IsException => false; - public PassiveNode(string name, TNode value, NodeType nodeType) : base(nodeType) { + public PassiveNode(string? name, TNode value, NodeType nodeType) : base(nodeType) { Name = name; Value = value; } @@ -453,31 +437,28 @@ protected override void UpdateValueImpl() { } } internal class RootNode<TNode> : PassiveNode<TNode> { - public RootNode(string name, TNode value) : base(name, value, NodeType.Root) { } + public RootNode(string? name, TNode value) : base(name, value, NodeType.Root) { } + } + + // This is a node that was created by some custom data extraction mechanism + internal class CustomNode<TNode> : PassiveNode<TNode> { + public CustomNode(string? name, TNode value, NodeType nodeType) : base(name, value, nodeType) { } } internal class ComponentNode : PassiveNode<Component> { protected readonly WeakReference<Node> _parentNode; - protected ComponentNode(Node parentNode, string name, Component value) : base(name, value, NodeType.Component) { - _parentNode = new WeakReference<Node>(parentNode); - } + protected ComponentNode(Node parentNode, string? name, Component value) : base(name, value, NodeType.Component) => _parentNode = new WeakReference<Node>(parentNode); public override Node GetParent() { - if (_parentNode.TryGetTarget(out Node parent)) { - return parent; - } + if (_parentNode.TryGetTarget(out var parent)) return parent; return null; } } internal class ItemNode<TNode> : PassiveNode<TNode> { protected readonly WeakReference<Node> _parentNode; - protected ItemNode(Node parentNode, string name, TNode value) : base(name, value, NodeType.Item) { - _parentNode = new WeakReference<Node>(parentNode); - } + protected ItemNode(Node parentNode, string? name, TNode value) : base(name, value, NodeType.Item) => _parentNode = new WeakReference<Node>(parentNode); public override Node GetParent() { - if (_parentNode.TryGetTarget(out Node parent)) { - return parent; - } + if (_parentNode.TryGetTarget(out var parent)) return parent; return null; } } @@ -485,23 +466,21 @@ public override Node GetParent() { internal abstract class ChildNode<TParent, TNode> : GenericNode<TNode> { protected bool _isException; protected readonly WeakReference<GenericNode<TParent>> _parentNode; + public override bool IsException { get { UpdateValue(); return _isException; } } - protected ChildNode(GenericNode<TParent> parentNode, string name, NodeType nodeType) : base(nodeType) { + + protected ChildNode(GenericNode<TParent> parentNode, string? name, NodeType nodeType) : base(nodeType) { _parentNode = new WeakReference<GenericNode<TParent>>(parentNode); Name = name; } - internal override void SetValue(object value) { - throw new NotImplementedException(); - } + internal override void SetValue(object value) => throw new NotImplementedException(); public override Node GetParent() { - if (_parentNode.TryGetTarget(out GenericNode<TParent> parent)) { - return parent; - } + if (_parentNode.TryGetTarget(out var parent)) return parent; return null; } } @@ -509,9 +488,9 @@ public override Node GetParent() { internal abstract class ChildOfStructNode<TParent, TParentInst, TNode> : ChildNode<TParent, TNode> where TParentInst : struct { private readonly Func<TParent, TParentInst> _forceCast = UnsafeForceCast.GetDelegate<TParent, TParentInst>(); - protected ChildOfStructNode(GenericNode<TParent> parentNode, string name, NodeType nodeType) : base(parentNode, name, nodeType) { } + protected ChildOfStructNode(GenericNode<TParent> parentNode, string? name, NodeType nodeType) : base(parentNode, name, nodeType) { } protected bool TryGetParentValue(out TParentInst value) { - if (_parentNode.TryGetTarget(out GenericNode<TParent> parent) && parent.InstType == typeof(TParentInst)) { + if (_parentNode.TryGetTarget(out var parent) && parent.InstType == typeof(TParentInst)) { value = _forceCast(parent.Value); return true; } @@ -523,10 +502,10 @@ protected bool TryGetParentValue(out TParentInst value) { internal abstract class ChildOfNullableNode<TParent, TUnderlying, TNode> : ChildNode<TParent, TNode> where TUnderlying : struct { private readonly Func<TParent, TUnderlying?> _forceCast = UnsafeForceCast.GetDelegate<TParent, TUnderlying?>(); - protected ChildOfNullableNode(GenericNode<TParent> parentNode, string name, NodeType nodeType) : base(parentNode, name, nodeType) { } + protected ChildOfNullableNode(GenericNode<TParent> parentNode, string? name, NodeType nodeType) : base(parentNode, name, nodeType) { } protected bool TryGetParentValue(out TUnderlying value) { - if (_parentNode.TryGetTarget(out GenericNode<TParent> parent)) { - TUnderlying? parentValue = _forceCast(parent.Value); + if (_parentNode.TryGetTarget(out var parent)) { + var parentValue = _forceCast(parent.Value); if (parentValue.HasValue) { value = parentValue.Value; return true; @@ -539,11 +518,9 @@ protected bool TryGetParentValue(out TUnderlying value) { internal abstract class ChildOfClassNode<TParent, TParentInst, TNode> : ChildNode<TParent, TNode> where TParentInst : class { - protected ChildOfClassNode(GenericNode<TParent> parentNode, string name, NodeType nodeType) : base(parentNode, name, nodeType) { } + protected ChildOfClassNode(GenericNode<TParent> parentNode, string? name, NodeType nodeType) : base(parentNode, name, nodeType) { } protected bool TryGetParentValue(out TParentInst value) { - if (_parentNode.TryGetTarget(out GenericNode<TParent> parent) && (value = parent.Value as TParentInst) != null) { - return true; - } + if (_parentNode.TryGetTarget(out var parent) && (value = parent.Value as TParentInst) != null) return true; value = null; return false; } @@ -551,13 +528,12 @@ protected bool TryGetParentValue(out TParentInst value) { internal class FieldOfStructNode<TParent, TParentInst, TNode> : ChildOfStructNode<TParent, TParentInst, TNode> where TParentInst : struct { - protected FieldOfStructNode(GenericNode<TParent> parentNode, string name) : base(parentNode, name, NodeType.Field) { } + protected FieldOfStructNode(GenericNode<TParent> parentNode, string? name) : base(parentNode, name, NodeType.Field) { } protected override void UpdateValueImpl() { - if (TryGetParentValue(out TParentInst parentValue)) { + if (TryGetParentValue(out var parentValue)) { _isException = false; Value = parentValue.GetFieldValue<TParentInst, TNode>(Name); - } - else { + } else { _isException = true; Value = default; } @@ -566,18 +542,16 @@ protected override void UpdateValueImpl() { internal class PropertyOfStructNode<TParent, TParentInst, TNode> : ChildOfStructNode<TParent, TParentInst, TNode> where TParentInst : struct { - protected PropertyOfStructNode(GenericNode<TParent> parentNode, string name) : base(parentNode, name, NodeType.Property) { } + protected PropertyOfStructNode(GenericNode<TParent> parentNode, string? name) : base(parentNode, name, NodeType.Property) { } protected override void UpdateValueImpl() { - if (TryGetParentValue(out TParentInst parentValue)) { + if (TryGetParentValue(out var parentValue)) try { _isException = false; Value = parentValue.GetPropertyValue<TParentInst, TNode>(Name); - } - catch { + } catch { _isException = true; Value = default; } - } else { _isException = true; Value = default; @@ -587,13 +561,12 @@ protected override void UpdateValueImpl() { internal class FieldOfNullableNode<TParent, TUnderlying, TNode> : ChildOfNullableNode<TParent, TUnderlying, TNode> where TUnderlying : struct { - protected FieldOfNullableNode(GenericNode<TParent> parentNode, string name) : base(parentNode, name, NodeType.Field) { } + protected FieldOfNullableNode(GenericNode<TParent> parentNode, string? name) : base(parentNode, name, NodeType.Field) { } protected override void UpdateValueImpl() { - if (TryGetParentValue(out TUnderlying parentValue)) { + if (TryGetParentValue(out var parentValue)) { _isException = false; Value = parentValue.GetFieldValue<TUnderlying, TNode>(Name); - } - else { + } else { _isException = true; Value = default; } @@ -602,18 +575,16 @@ protected override void UpdateValueImpl() { internal class PropertyOfNullableNode<TParent, TUnderlying, TNode> : ChildOfNullableNode<TParent, TUnderlying, TNode> where TUnderlying : struct { - protected PropertyOfNullableNode(GenericNode<TParent> parentNode, string name) : base(parentNode, name, NodeType.Property) { } + protected PropertyOfNullableNode(GenericNode<TParent> parentNode, string? name) : base(parentNode, name, NodeType.Property) { } protected override void UpdateValueImpl() { - if (TryGetParentValue(out TUnderlying parentValue)) { + if (TryGetParentValue(out var parentValue)) try { _isException = false; Value = parentValue.GetPropertyValue<TUnderlying, TNode>(Name); - } - catch { + } catch { _isException = true; Value = default; } - } else { _isException = true; Value = default; @@ -623,13 +594,12 @@ protected override void UpdateValueImpl() { internal class FieldOfClassNode<TParent, TParentInst, TNode> : ChildOfClassNode<TParent, TParentInst, TNode> where TParentInst : class { - protected FieldOfClassNode(GenericNode<TParent> parentNode, string name) : base(parentNode, name, NodeType.Field) { } + protected FieldOfClassNode(GenericNode<TParent> parentNode, string? name) : base(parentNode, name, NodeType.Field) { } protected override void UpdateValueImpl() { - if (TryGetParentValue(out TParentInst parentValue)) { + if (TryGetParentValue(out var parentValue)) { _isException = false; Value = parentValue.GetFieldValue<TParentInst, TNode>(Name); - } - else { + } else { _isException = true; Value = default; } @@ -638,22 +608,20 @@ protected override void UpdateValueImpl() { internal class PropertyOfClassNode<TParent, TParentInst, TNode> : ChildOfClassNode<TParent, TParentInst, TNode> where TParentInst : class { - protected PropertyOfClassNode(GenericNode<TParent> parentNode, string name) : base(parentNode, name, NodeType.Property) { } + protected PropertyOfClassNode(GenericNode<TParent> parentNode, string? name) : base(parentNode, name, NodeType.Property) { } protected override void UpdateValueImpl() { - if (TryGetParentValue(out TParentInst parentValue)) { + if (TryGetParentValue(out var parentValue)) try { _isException = false; Value = parentValue.GetPropertyValue<TParentInst, TNode>(Name); - } - catch { + } catch { _isException = true; Value = default; } - } else { _isException = true; Value = default; } } } -} +} \ No newline at end of file diff --git a/ModKit/DataViewer/ReflectionTreeView.cs b/ToyBox/Classes/ModKit/DataViewer/ReflectionTreeView.cs similarity index 77% rename from ModKit/DataViewer/ReflectionTreeView.cs rename to ToyBox/Classes/ModKit/DataViewer/ReflectionTreeView.cs index d3f1da61f..8daa9a319 100644 --- a/ModKit/DataViewer/ReflectionTreeView.cs +++ b/ToyBox/Classes/ModKit/DataViewer/ReflectionTreeView.cs @@ -1,11 +1,13 @@ -using ModKit.Utility; +using JetBrains.Annotations; +using Kingmaker.Blueprints; +using ModKit.Utility; using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using UnityEngine; using static ModKit.UI; -using static ModKit.Utility.StringExtensions; +using static ModKit.StringExtensions; using Object = System.Object; namespace ModKit.DataViewer { @@ -20,16 +22,14 @@ private static void AddOrUpdateCachedReflectionTree(object key, ReflectionTreeVi _cachedReflectionTrees.Add(key, tree); } #endif - private static readonly Dictionary<object, ReflectionTreeView> ExpandedObjects = new(); + private static readonly Dictionary<object, ReflectionTreeView> ExpandedObjects = []; public static void ClearExpanded() => ExpandedObjects.Clear(); - public static void DetailToggle(string title, object key, object target = null, int width = 600) { + public static void DetailToggle(string? title, object key, object? target = null, int width = 600) { target ??= key; var expanded = ExpandedObjects.ContainsKey(key); if (DisclosureToggle(title, ref expanded, width)) { ExpandedObjects.Clear(); - if (expanded) { - ExpandedObjects[key] = new ReflectionTreeView(target); - } + if (expanded) ExpandedObjects[key] = new ReflectionTreeView(target); } } public static bool OnDetailGUI(object key, int indent = 50) { @@ -38,39 +38,38 @@ public static bool OnDetailGUI(object key, int indent = 50) { reflectionTreeView.Indent = indent; using (HorizontalScope()) { Space(indent); - Label("Inspecting: ".grey() + reflectionTreeView.Root.ToString().orange().bold()); + Label("Inspecting: ".localize().Grey() + reflectionTreeView.Root.ToString().Orange().Bold()); } reflectionTreeView.OnGUI(false); return true; - } else { - return false; } + else + return false; } private ReflectionTree _tree; - private ReflectionSearchResult _searchResults = new ReflectionSearchResult(); - private float _height; + private readonly ReflectionSearchResult _searchResults = new(); private bool _mouseOver; private GUIStyle _buttonStyle; private GUIStyle _valueStyle; private int _totalNodeCount; private int _nodesCount; private int _startIndex; - private int _skipLevels; + private readonly int _skipLevels = default; private string _searchText = ""; internal string[] SearchTerms => _searchText.Length == 0 - ? Array.Empty<string>() + ? [] : _searchText.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - private bool enableCopy = false; + private int visitCount = 0; private int searchDepth = 0; private int searchBreadth = 0; private void updateCounts(int visitCount, int depth, int breadth) { this.visitCount = visitCount; - this.searchDepth = depth; - this.searchBreadth = breadth; + searchDepth = depth; + searchBreadth = breadth; } private Rect _viewerRect; @@ -85,15 +84,11 @@ private void updateCounts(int visitCount, int depth, int breadth) { public float TitleMinWidth { get; set; } = 300f; public ReflectionTreeView() { } - public ReflectionTreeView(object root) { - SetRoot(root); - } + public ReflectionTreeView(object root) => SetRoot(root); public void Clear() { _tree = null; - lock (_searchResults) { - _searchResults.Clear(); - } + lock (_searchResults) _searchResults.Clear(); } public void SetRoot(object root) { @@ -101,9 +96,7 @@ public void SetRoot(object root) { _tree.SetRoot(root); else _tree = new ReflectionTree(root); - lock (_searchResults) { - _searchResults.Node = null; - } + lock (_searchResults) _searchResults.Node = null; _tree.RootNode.Expanded = ToggleState.On; // ReflectionSearch.Shared.StartSearch(_tree.RootNode, searchText, updateCounts, _searchResults); } @@ -111,17 +104,14 @@ public void SetRoot(object root) { public void OnGUI(bool drawRoot = true, bool collapse = false) { if (_tree == null) return; - if (_buttonStyle == null) - _buttonStyle = new GUIStyle(GUI.skin.button) - { alignment = TextAnchor.MiddleLeft, stretchHeight = false }; - if (_valueStyle == null) - _valueStyle = new GUIStyle(GUI.skin.box) { alignment = TextAnchor.MiddleLeft, stretchHeight = false }; + _buttonStyle ??= new GUIStyle(GUI.skin.button) { alignment = TextAnchor.MiddleLeft, stretchHeight = false }; + _valueStyle ??= new GUIStyle(GUI.skin.box) { alignment = TextAnchor.MiddleLeft, stretchHeight = false }; if (Event.current.type == EventType.Layout) { var count = ReflectionSearch.ApplyUpdates(); if (count > 0) Mod.Log($"ReflectionTreeView.OnGUI - {count} Search Updates Applied"); } - int startIndexUBound = Math.Max(0, _nodesCount - MaxRows); + var startIndexUBound = Math.Max(0, _nodesCount - MaxRows); // mouse wheel & fix scroll position if (Event.current.type == EventType.Layout) { @@ -134,13 +124,10 @@ public void OnGUI(bool drawRoot = true, bool collapse = false) { else if (delta.y < 0 && _startIndex < startIndexUBound) _startIndex++; } - if (_startIndex > startIndexUBound) { - _startIndex = startIndexUBound; - } + if (_startIndex > startIndexUBound) _startIndex = startIndexUBound; } - else { + else _startIndex = 0; - } } using (new GUILayout.VerticalScope()) { // tool-bar @@ -160,12 +147,11 @@ public void OnGUI(bool drawRoot = true, bool collapse = false) { }, Width(250)); GUILayout.Space(10f); - bool isSearching = ReflectionSearch.Shared.isSearching; + var isSearching = ReflectionSearch.Shared.isSearching; ActionButton(isSearching ? "Stop" : "Search", () => { - if (isSearching) { + if (isSearching) ReflectionSearch.Shared.Stop(); - } else { _searchText = _searchText.Trim(); ReflectionSearch.Shared.StartSearch(_tree.RootNode, @@ -176,14 +162,12 @@ public void OnGUI(bool drawRoot = true, bool collapse = false) { }, AutoWidth()); 10.space(); - if (ValueAdjuster("Max Depth:", ref ReflectionSearch.maxSearchDepth)) { - ReflectionSearch.Shared.StartSearch(_tree.RootNode, SearchTerms, updateCounts, _searchResults); - } + if (ValueAdjuster("Max Depth:", ref ReflectionSearch.maxSearchDepth)) ReflectionSearch.Shared.StartSearch(_tree.RootNode, SearchTerms, updateCounts, _searchResults); 10.space(); - if (visitCount > 0) { + if (visitCount > 0) Label($"found {_searchResults.Count}".Cyan() + $" visited: {visitCount} (d: {searchDepth} b: {searchBreadth})".Orange()); - } + Toggle("Show Nulls/Empties", ref Mod.ModKitSettings.toggleDataViewerShowNullAndEmpties); GUILayout.FlexibleSpace(); // 10.space(); // Toggle("Enable Value Selection For Copy", ref enableCopy); @@ -248,7 +232,7 @@ public void OnGUI(bool drawRoot = true, bool collapse = false) { _nodesCount = 0; if (SearchTerms.Length > 0) { Div(); - lock (_searchResults) { + lock (_searchResults) _searchResults.Traverse((node, depth) => { if (node.Node == null) return true; var toggleState = node.ToggleState; @@ -262,16 +246,12 @@ public void OnGUI(bool drawRoot = true, bool collapse = false) { } else DrawNodePrivate(node.Node, depth, ref toggleState); - if (node.ToggleState != toggleState) { - Mod.Log(node.ToString()); - } + if (node.ToggleState != toggleState) Mod.Log(node.ToString()); node.ToggleState = toggleState; - if (toggleState.IsOn()) { - DrawChildren(node.Node, depth + 1, collapse); - } + if (toggleState.IsOn()) DrawChildren(node.Node, depth + 1, collapse); return true; // toggleState == ToggleState.On; - }, 0); - } + }, + 0); Div(); } if (drawRoot) @@ -291,7 +271,6 @@ public void OnGUI(bool drawRoot = true, bool collapse = false) { _mouseOver = _viewerRect.Contains(Event.current.mousePosition); //Main.Log($"mousePos: {mousePos} Rect: {_viewerRect} --> {_mouseOver}"); _viewerRect = GUILayoutUtility.GetLastRect(); - _height = _viewerRect.height + 5f; } // } } @@ -302,56 +281,67 @@ public void OnGUI(bool drawRoot = true, bool collapse = false) { private void DrawNodePrivate(Node node, int depth, ref ToggleState expanded) { _nodesCount++; - if (_nodesCount > _startIndex && _nodesCount <= _startIndex + MaxRows) { - + if (_nodesCount > _startIndex && _nodesCount <= _startIndex + MaxRows) using (HorizontalScope()) { // title Space(DepthDelta * (depth - _skipLevels)); var name = node.Name; - var instText = ""; // if (node.InstanceID is int instID) instText = "@" + instID.ToString(); + var instText = ""; // if (node.InstanceID is int instID) instText = "@" + instID.ToString(); name = name.MarkedSubstring(SearchTerms); var enumerableCount = node.EnumerableCount; - if (enumerableCount == 0 || node.IsNull) return; // TODO - make this a config option - if (enumerableCount >= 0) name = name + $"[{enumerableCount}]".yellow(); + if (!Mod.ModKitSettings.toggleDataViewerShowNullAndEmpties + && (enumerableCount == 0 || node.IsNull)) + return; + if (enumerableCount >= 0) name += $"[{enumerableCount}]".Yellow(); var typeName = node.InstType?.Name ?? node.Type?.Name; ToggleButton(ref expanded, - $"[{node.NodeTypePrefix}] ".color(RGBA.grey) + - name + " : " + typeName.color( - node.IsBaseType ? RGBA.grey : - node.IsGameObject ? RGBA.magenta : - node.IsEnumerable ? RGBA.cyan : RGBA.orange) - + instText, - _buttonStyle, GUILayout.ExpandWidth(false), GUILayout.MinWidth(TitleMinWidth)); + $"[{node.NodeTypePrefix}] ".Color(RGBA.grey) + + name + + " : " + + typeName.Color( + node.IsBaseType + ? RGBA.grey + : node.IsGameObject + ? RGBA.magenta + : node.IsEnumerable + ? RGBA.cyan + : RGBA.orange) + + instText, + _buttonStyle, + GUILayout.ExpandWidth(false), + GUILayout.MinWidth(TitleMinWidth)); // value - Color originalColor = GUI.contentColor; - GUI.contentColor = node.IsException ? Color.red : node.IsNull ? Color.grey : originalColor; + var originalColor = GUI.contentColor; + GUI.contentColor = node.IsException + ? Color.red + : node.IsNull + ? Color.grey + : originalColor; var valueText = node.ValueText; if (SearchTerms.Length == 0 || !SearchTerms.Any(term => valueText.Matches(term))) ClipboardLabel(valueText); // + " " + node.GetPath().green(), _valueStyle); - else { + else //if (valueText.Matches("mor")) // Mod.Log($"{valueText}/[{string.Join(", ", SearchTerms)}]"); Label(valueText.MarkedSubstring(SearchTerms), ExpandWidth(true)); - } GUI.contentColor = originalColor; // instance type var text = ""; var style = GUI.skin.label; if (node.InstType != null && node.InstType != node.Type) { - text = node.InstType.Name.color(RGBA.yellow); + text = node.InstType.Name.Color(RGBA.yellow); style = _buttonStyle; Label(text, _buttonStyle, GUILayout.ExpandWidth(false)); } - else + else Label("", ExpandWidth(false)); } - } } private void DrawNode(Node node, int depth, bool collapse) { try { - ToggleState expanded = node.Expanded; + var expanded = node.Expanded; if (depth >= _skipLevels && !(collapse && depth > 0)) { if (!node.hasChildren) expanded = ToggleState.None; @@ -364,39 +354,45 @@ private void DrawNode(Node node, int depth, bool collapse) { node.Expanded = ToggleState.Off; // children - if (expanded.IsOn()) { - DrawChildren(node, depth + 1, collapse); - } - } catch (Exception e) { + if (expanded.IsOn()) DrawChildren(node, depth + 1, collapse); } + catch (Exception) { } } - private void DrawChildren(Node node, int depth, bool collapse, Func<Node, bool> hoist = null) { + private void DrawChildren(Node node, int depth, bool collapse, Func<Node, bool>? hoist = null) { if (node.IsBaseType) return; - if (hoist == null) hoist = (n) => n.Matches; + hoist ??= (n) => n.Matches; var toHoist = new List<Node>(); var others = new List<Node>(); var nodesCount = _nodesCount; var maxNodeCount = _startIndex + MaxRows * 2; foreach (var child in node.GetItemNodes()) { - if (nodesCount > maxNodeCount) break; nodesCount++; - if (hoist(child)) toHoist.Add(child); else others.Add(child); + if (nodesCount > maxNodeCount) break; + nodesCount++; + if (hoist(child)) toHoist.Add(child); + else others.Add(child); } foreach (var child in node.GetComponentNodes()) { - if (nodesCount > maxNodeCount) break; nodesCount++; - if (hoist(child)) toHoist.Add(child); else others.Add(child); + if (nodesCount > maxNodeCount) break; + nodesCount++; + if (hoist(child)) toHoist.Add(child); + else others.Add(child); } foreach (var child in node.GetPropertyNodes()) { - if (nodesCount > maxNodeCount) break; nodesCount++; - if (hoist(child)) toHoist.Add(child); else others.Add(child); + if (nodesCount > maxNodeCount) break; + nodesCount++; + if (hoist(child)) toHoist.Add(child); + else others.Add(child); } foreach (var child in node.GetFieldNodes()) { - if (nodesCount > maxNodeCount) break; nodesCount++; - if (hoist(child)) toHoist.Add(child); else others.Add(child); + if (nodesCount > maxNodeCount) break; + nodesCount++; + if (hoist(child)) toHoist.Add(child); + else others.Add(child); } - foreach (var child in toHoist) { DrawNode(child, depth, collapse); } - foreach (var child in others) { DrawNode(child, depth, collapse); } + foreach (var child in toHoist) DrawNode(child, depth, collapse); + foreach (var child in others) DrawNode(child, depth, collapse); _totalNodeCount = Math.Max(_nodesCount, _totalNodeCount); } } @@ -424,4 +420,4 @@ private void DrawChildren(Node node, int depth, bool collapse, Func<Node, bool> } catch (Exception e) { } } -#endif +#endif \ No newline at end of file diff --git a/ToyBox/Classes/ModKit/DataViewer/UnsafeForceCast.cs b/ToyBox/Classes/ModKit/DataViewer/UnsafeForceCast.cs new file mode 100644 index 000000000..f47e6ed3b --- /dev/null +++ b/ToyBox/Classes/ModKit/DataViewer/UnsafeForceCast.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Reflection.Emit; + +namespace ModKit.DataViewer { + internal static class UnsafeForceCast { + private static readonly Dictionary<(Type, Type), WeakReference> _cache = new(); + + public static Func<TInput, TOutput>? GetDelegate<TInput, TOutput>() { + Func<TInput, TOutput>? cache = default; + if (_cache.TryGetValue((typeof(TInput), typeof(TOutput)), out var weakRef)) + cache = weakRef.Target as Func<TInput, TOutput>; + if (cache == null) { + cache = CreateDelegate<TInput, TOutput>(); + _cache[(typeof(TInput), typeof(TOutput))] = new WeakReference(cache); + } + return cache; + } + + private static Func<TInput, TOutput>? CreateDelegate<TInput, TOutput>() { + var method = new DynamicMethod( + "UnsafeForceCast", + typeof(TOutput), + new[] { typeof(TInput) }); + + var il = method.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + if (typeof(TInput) == typeof(object) && typeof(TOutput).IsValueType) + il.Emit(OpCodes.Unbox_Any, typeof(TOutput)); + il.Emit(OpCodes.Ret); + + return method.CreateDelegate(typeof(Func<TInput, TOutput>)) as Func<TInput, TOutput>; + } + } +} \ No newline at end of file diff --git a/ModKit/ModKit/LocalizationManager.cs b/ToyBox/Classes/ModKit/ModKit/LocalizationManager.cs similarity index 63% rename from ModKit/ModKit/LocalizationManager.cs rename to ToyBox/Classes/ModKit/ModKit/LocalizationManager.cs index 9ef4565ad..b918ebd95 100644 --- a/ModKit/ModKit/LocalizationManager.cs +++ b/ToyBox/Classes/ModKit/ModKit/LocalizationManager.cs @@ -1,8 +1,8 @@ -using Kingmaker.Utility; -using Newtonsoft.Json; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; +using System.Linq; namespace ModKit { public class Language { @@ -35,25 +35,33 @@ public static class LocalizationManager { private static Language _localDefault; private static Language _local; private static bool IsDefault; + internal static bool isEnabled = false; public static string FilePath { get; private set; } public static void Enable() { - IsDefault = true; - _local = null; - _localDefault = null; - var separator = Path.DirectorySeparatorChar; - _localFolderPath = Mod.modEntry.Path + "Localization" + separator; - FilePath = _localFolderPath + "en"; - _localDefault = Import(); - if (_localDefault == null) { - _localDefault = new(); - _localDefault.Strings = new(); - } - var chosenLangauge = Mod.ModKitSettings.uiCultureCode; - FilePath = _localFolderPath + chosenLangauge; - if (chosenLangauge != "en") { - _local = Import(); - IsDefault = _local != null; + try { + isEnabled = true; + IsDefault = true; + _local = null; + _localDefault = null; + var separator = Path.DirectorySeparatorChar; + _localFolderPath = Mod.modEntry.Path + "Localization" + separator; + FilePath = _localFolderPath + "en"; + _localDefault = Import(); + if (_localDefault == null) { + _localDefault = new(); + _localDefault.Strings = new(); + } + var chosenLangauge = Mod.ModKitSettings.uiCultureCode; + FilePath = _localFolderPath + chosenLangauge; + if (chosenLangauge != "en") { + _local = Import(); + IsDefault = _local == null; + } + + } catch (Exception ex) { + Mod.Error("Could not load localization files!"); + Mod.Warn(ex.ToString()); } } public static void Update() { @@ -62,8 +70,7 @@ public static void Update() { FilePath = _localFolderPath + "en"; IsDefault = true; _local = null; - } - else { + } else { if (!(_local?.LanguageCode == locale)) { FilePath = _localFolderPath + Mod.ModKitSettings.uiCultureCode; _local = Import(); @@ -73,22 +80,22 @@ public static void Update() { } public static string localize(this string key) { - if (key == null || key == "") return key; + if (string.IsNullOrEmpty(key) || !isEnabled) return key; else { - string localizedString = ""; + string localizedString = null; if (!IsDefault) { if (!(_local?.Strings.TryGetValue(key, out localizedString)) ?? true) { _local?.Strings.Add(key, ""); - Mod.Debug("Unknown Key in current locale: " + key); + Mod.Debug($"Unlocalized Key: '{key.Orange().Bold()}' in current locale: " + key); } } if (IsDefault || localizedString == "") { if (!(_localDefault?.Strings.TryGetValue(key, out localizedString)) ?? true) { _localDefault?.Strings.Add(key, key); - Mod.Debug("Unknown Key in default: key"); + Mod.Warn($"Unlocalized Key: '{key.Orange().Bold()}' in default locale"); } } - return localizedString != "" ? localizedString : key; + return localizedString != null ? localizedString : key; } } public static Language Import(Action<Exception> onError = null) { @@ -97,13 +104,20 @@ public static Language Import(Action<Exception> onError = null) { Language lang; lang = Language.Deserialize(FilePath + _fileEnding); return lang; + } // If default is missing recreate empty default + else if (FilePath.ToLower().EndsWith("en")) { + Language lang = new(); + lang.Strings = new(); + lang.LanguageCode = "en"; + lang.Version = Mod.modEntry.Version.ToString(); + lang.Contributors = "The ToyBox Team"; + lang.HomePage = "https://github.com/cabarius/ToyBox/"; + Language.Serialize(lang, FilePath + _fileEnding); } - } - catch (Exception e) { + } catch (Exception e) { if (onError != null) { onError(e); - } - else { + } else { Mod.Error(e.ToString()); } } @@ -115,7 +129,7 @@ public static HashSet<string> getLanguagesWithFile() { if (!_cacheFiles) { if (Directory.Exists(_localFolderPath)) { foreach (var file in Directory.GetFiles(_localFolderPath)) { - var parts = file.Split(Path.DirectorySeparatorChar).LastItem().Split('.'); + var parts = file.Split(Path.DirectorySeparatorChar).Last().Split('.'); if (parts[1] == "json") { _LanguageCache.Add(parts[0]); } @@ -133,8 +147,7 @@ public static bool Export(Action<Exception> onError = null) { } if (File.Exists(FilePath + _fileEnding)) { File.Delete(FilePath + _fileEnding); - } - else { + } else { _LanguageCache.Add(Mod.ModKitSettings.uiCultureCode); } var toSerialize = Mod.ModKitSettings.uiCultureCode == "en" ? _localDefault : _local; @@ -144,19 +157,26 @@ public static bool Export(Action<Exception> onError = null) { foreach (var k in _localDefault.Strings.Keys) { toSerialize.Strings.Add(k, ""); } + } else { + var notToSerialize = Mod.ModKitSettings.uiCultureCode == "en" ? _local : _localDefault; + if (notToSerialize != null) { + foreach (var k in notToSerialize.Strings.Keys) { + if (!toSerialize.Strings.ContainsKey(k)) { + toSerialize.Strings.Add(k, ""); + } + } + } } - toSerialize.LanguageCode = toSerialize.LanguageCode = Mod.ModKitSettings.uiCultureCode; + toSerialize.LanguageCode = Mod.ModKitSettings.uiCultureCode; toSerialize.Version = Mod.modEntry.Version.ToString(); - if (toSerialize.Contributors.IsNullOrEmpty()) toSerialize.Contributors = "The ToyBox Team"; + if (string.IsNullOrEmpty(toSerialize.Contributors)) toSerialize.Contributors = "The ToyBox Team"; toSerialize.HomePage = "https://github.com/cabarius/ToyBox/"; Language.Serialize(toSerialize, FilePath + _fileEnding); return true; - } - catch (Exception e) { + } catch (Exception e) { if (onError != null) { onError(e); - } - else { + } else { Mod.Error(e.ToString()); } } diff --git a/ModKit/ModKit/ModKit.cs b/ToyBox/Classes/ModKit/ModKit/ModKit.cs similarity index 59% rename from ModKit/ModKit/ModKit.cs rename to ToyBox/Classes/ModKit/ModKit/ModKit.cs index 5352e16b6..9e8c7e06a 100644 --- a/ModKit/ModKit/ModKit.cs +++ b/ToyBox/Classes/ModKit/ModKit/ModKit.cs @@ -1,4 +1,5 @@ // some stuff borrowed shamelessly and enhanced from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/26, which is under the MIT License + using ModKit.Utility; using System; using UnityModManagerNet; @@ -12,49 +13,59 @@ public enum LogLevel : int { Debug, Trace } + public static partial class Mod { public static ModEntry modEntry { get; set; } = null; public static string modEntryPath { get; set; } = null; - private static UnityModManager.ModEntry.ModLogger modLogger; + private static ModEntry.ModLogger modLogger; public static LogLevel logLevel = LogLevel.Info; public delegate void UITranscriptLogger(string text); public static UITranscriptLogger InGameTranscriptLogger; - public static void OnLoad(UnityModManager.ModEntry modEntry) { + public static void OnLoad(ModEntry modEntry) { modEntry.OnSaveGUI -= OnSaveGUI; modEntry.OnSaveGUI += OnSaveGUI; Mod.modEntry = modEntry; modLogger = modEntry.Logger; modEntryPath = modEntry.Path; ModKitSettings.Load(); - Mod.Debug($"ModKitSettings.browserSearchLimit: {Mod.ModKitSettings.browserDetailSearchLimit}"); + Debug($"ModKitSettings.browserSearchLimit: {ModKitSettings.browserDetailSearchLimit}"); } public static void OnSaveGUI(ModEntry entry) { ModKitSettings.Save(); - LocalizationManager.Export(); + if (LocalizationManager.isEnabled) { + LocalizationManager.Export(); + } } - private static void ResetGUI(UnityModManager.ModEntry modEntry) => ModKitSettings.Load(); - public static void Error(string str) { - str = str.yellow().bold(); - modLogger?.Error(str + "\n" + Environment.StackTrace); + private static void ResetGUI(ModEntry modEntry) => ModKitSettings.Load(); + public static void Error(string str, int skip = 1) { + str = str.Yellow().Bold(); + modLogger?.Error(str + "\n" + new System.Diagnostics.StackTrace(skip, true).ToString()); } - public static void Error(Exception ex) => Error(ex.ToString()); + public static void Error(Exception ex) => Error(ex.ToString(), 2); public static void Warn(string str) { if (logLevel >= LogLevel.Warning) - modLogger?.Log("[Warn] ".orange().bold() + str); + modLogger?.Log("[Warn] ".Orange().Bold() + str); } public static void Log(string str) { if (logLevel >= LogLevel.Info) modLogger?.Log("[Info] " + str); } - public static void Log(int indent, string s) { Log(" ".Repeat(indent) + s); } + public static void Log(int indent, string s) => Log(" ".Repeat(indent) + s); public static void Debug(string str) { if (logLevel >= LogLevel.Debug) - modLogger?.Log("[Debug] ".green() + str); + modLogger?.Log("[Debug] ".Green() + str); } public static void Trace(string str) { if (logLevel >= LogLevel.Trace) - modLogger?.Log("[Trace] ".color(RGBA.lightblue) + str); + modLogger?.Log("[Trace] ".Color(RGBA.lightblue) + str); + } + + public delegate void ShowGUINotifierMethod(); + public static ShowGUINotifierMethod NotifyOnShowGUI; + + public static void OnShowGUI() { + if (NotifyOnShowGUI != null) NotifyOnShowGUI(); } } -} +} \ No newline at end of file diff --git a/ModKit/ModKit/ModKitSettings.cs b/ToyBox/Classes/ModKit/ModKit/ModKitSettings.cs similarity index 74% rename from ModKit/ModKit/ModKitSettings.cs rename to ToyBox/Classes/ModKit/ModKit/ModKitSettings.cs index 56aa2c4c9..67de3b672 100644 --- a/ModKit/ModKit/ModKitSettings.cs +++ b/ToyBox/Classes/ModKit/ModKit/ModKitSettings.cs @@ -2,15 +2,20 @@ public partial class Mod { public static ModKitSettings ModKitSettings; } + public class ModKitSettings { public static void Save() => Mod.modEntry.SaveSettings("ModKitSettings.json", Mod.ModKitSettings); public static void Load() => Mod.modEntry.LoadSettings("ModKitSettings.json", ref Mod.ModKitSettings); public int browserSearchLimit = 20; public int browserDetailSearchLimit = 10; + public bool searchAsYouType = true; public bool toggleKeyBindingsOutputToTranscript = true; + public bool toggleDataViewerShowNullAndEmpties = false; + public bool UseDefaultGlyphs = true; + public bool CheckForGlyphSupport = true; // Localization public string uiCultureCode = "en"; } -} +} \ No newline at end of file diff --git a/ModKit/ModKit/SettingsController.cs b/ToyBox/Classes/ModKit/ModKit/SettingsController.cs similarity index 85% rename from ModKit/ModKit/SettingsController.cs rename to ToyBox/Classes/ModKit/ModKit/SettingsController.cs index 611f331bf..f0f39d60d 100644 --- a/ModKit/ModKit/SettingsController.cs +++ b/ToyBox/Classes/ModKit/ModKit/SettingsController.cs @@ -1,9 +1,9 @@ using Newtonsoft.Json; +using System; using System.IO; using System.Reflection; -using static UnityModManagerNet.UnityModManager; -using System; using System.Xml.Serialization; +using static UnityModManagerNet.UnityModManager; namespace ModKit { public interface IUpdatableSettings { @@ -24,7 +24,7 @@ public static void SaveSettings<T>(this ModEntry modEntry, string fileName, T se Directory.CreateDirectory(userConfigFolder); var userPath = $"{userConfigFolder}{Path.DirectorySeparatorChar}{fileName}"; try { - foreach (var res in assembly.GetManifestResourceNames()) { + foreach (var res in assembly.GetManifestResourceNames()) //Logger.Log("found resource: " + res); if (res.Contains(fileName)) { var stream = assembly.GetManifestResourceStream(res); @@ -32,29 +32,27 @@ public static void SaveSettings<T>(this ModEntry modEntry, string fileName, T se var text = reader.ReadToEnd(); //Logger.Log($"read: {text}"); settings = JsonConvert.DeserializeObject<T>(text); - //Logger.Log($"read settings: {string.Join(Environment.NewLine, settings)}"); } - } - } - catch (Exception e) { + } catch (Exception e) { Mod.Error($"{fileName} resource is not present or is malformed. exception: {e}"); } if (File.Exists(userPath)) { using var reader = File.OpenText(userPath); try { var userSettings = JsonConvert.DeserializeObject<T>(reader.ReadToEnd()); - if (userSettings is IUpdatableSettings updatableSettings) { - updatableSettings.AddMissingKeys((IUpdatableSettings)settings); - } + if (userSettings is IUpdatableSettings updatableSettings) updatableSettings?.AddMissingKeys((IUpdatableSettings)settings); settings = userSettings; - } - catch { + } catch { Mod.Error("Failed to load user settings. Settings will be rebuilt."); - try { File.Copy(userPath, userConfigFolder + $"{Path.DirectorySeparatorChar}BROKEN_{fileName}", true); } - catch { Mod.Error("Failed to archive broken settings."); } + try { + File.Copy(userPath, userConfigFolder + $"{Path.DirectorySeparatorChar}BROKEN_{fileName}", true); + } catch { + Mod.Error("Failed to archive broken settings."); + } } } + settings ??= new(); File.WriteAllText(userPath, JsonConvert.SerializeObject(settings, Formatting.Indented)); } } diff --git a/ModKit/UI/Glyphs.cs b/ToyBox/Classes/ModKit/UI/Glyphs.cs similarity index 55% rename from ModKit/UI/Glyphs.cs rename to ToyBox/Classes/ModKit/UI/Glyphs.cs index afdf5ef52..a38938e83 100644 --- a/ModKit/UI/Glyphs.cs +++ b/ToyBox/Classes/ModKit/UI/Glyphs.cs @@ -12,15 +12,32 @@ public static partial class Glyphs { public static string DefaultDisclosureOff = "▶"; public static string DefaultDisclosureEmpty = "▪"; public static string DefaultEdit = "✎"; - public static string CharCodeCheckOn = "[x]"; - public static string CharCodeCheckOff = "<b><color=green>[</color><color=red>o</color><color=green>]</color></b>"; + public static string CharCodeCheckOn = "<b><color=green>[X]</color></b>"; + public static string CharCodeCheckOff = "<b>[ ]</b>"; public static string CharCodeCheckEmpty = "<b> <color=yellow>-</color> </b>"; public static string CharCodeDisclosureOn = "v"; public static string CharCodeDisclosureOff = ">"; public static string CharCodeDisclosureEmpty = "-"; public static string CharCodeEdit = "edit"; - - private static bool UseDefaultGlyphs = !RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + private static bool UseDefaultGlyphs => Mod.ModKitSettings.UseDefaultGlyphs; + public static void CheckGlyphSupport() { + if (Mod.ModKitSettings.CheckForGlyphSupport) { + if (GUI.skin.font.HasCharacter(DefaultCheckOn[0]) && + GUI.skin.font.HasCharacter(DefaultCheckOff[0]) && + GUI.skin.font.HasCharacter(DefaultCheckEmpty[0]) && + GUI.skin.font.HasCharacter(DefaultDisclosureOn[0]) && + GUI.skin.font.HasCharacter(DefaultDisclosureOff[0]) && + GUI.skin.font.HasCharacter(DefaultDisclosureEmpty[0]) && + GUI.skin.font.HasCharacter(DefaultEdit[0])) { + Mod.ModKitSettings.UseDefaultGlyphs = true; + } else { + Mod.ModKitSettings.UseDefaultGlyphs = false; + } + Mod.Log($"Glyph Support Check returned: {Mod.ModKitSettings.UseDefaultGlyphs}"); + } else { + Mod.Log("Skip Glyph Support Check because disabled."); + } + } public static string CheckOn => UseDefaultGlyphs ? DefaultCheckOn : CharCodeCheckOn; public static string CheckOff => UseDefaultGlyphs ? DefaultCheckOff : CharCodeCheckOff; public static string CheckEmpty => UseDefaultGlyphs ? DefaultCheckEmpty : CharCodeCheckEmpty; diff --git a/ModKit/UI/KeyBindings/KeyBind.cs b/ToyBox/Classes/ModKit/UI/KeyBindings/KeyBind.cs similarity index 64% rename from ModKit/UI/KeyBindings/KeyBind.cs rename to ToyBox/Classes/ModKit/UI/KeyBindings/KeyBind.cs index 3aa5d9b1e..0d55dbad7 100644 --- a/ModKit/UI/KeyBindings/KeyBind.cs +++ b/ToyBox/Classes/ModKit/UI/KeyBindings/KeyBind.cs @@ -12,6 +12,7 @@ public enum ClickModifier { Alt, Command } + public static bool IsActive(this ClickModifier modifier) => modifier switch { ClickModifier.Disabled => false, ClickModifier.Shift => Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift), @@ -22,12 +23,15 @@ public enum ClickModifier { }; private static readonly HashSet<KeyCode> allowedMouseButtons = new() { KeyCode.Mouse3, KeyCode.Mouse4, KeyCode.Mouse5, KeyCode.Mouse6 }; - public static bool IsModifier(this KeyCode code) { - return code == KeyCode.LeftControl || code == KeyCode.RightControl - || code == KeyCode.LeftAlt || code == KeyCode.RightAlt - || code == KeyCode.LeftShift || code == KeyCode.RightShift - || code == KeyCode.LeftCommand || code == KeyCode.RightCommand; - } + public static bool IsModifier(this KeyCode code) => + code == KeyCode.LeftControl + || code == KeyCode.RightControl + || code == KeyCode.LeftAlt + || code == KeyCode.RightAlt + || code == KeyCode.LeftShift + || code == KeyCode.RightShift + || code == KeyCode.LeftCommand + || code == KeyCode.RightCommand; public static bool IsControl(this KeyCode code) => code == KeyCode.LeftControl || code == KeyCode.RightControl; public static bool IsAlt(this KeyCode code) => code == KeyCode.LeftAlt || code == KeyCode.RightAlt; @@ -37,6 +41,7 @@ public static bool IsModifier(this KeyCode code) { public static bool IsShift(this KeyCode code) => code == KeyCode.LeftShift || code == KeyCode.RightShift; private static GUIStyle _hotkeyStyle; + public static GUIStyle hotkeyStyle { get { if (_hotkeyStyle == null) @@ -50,23 +55,17 @@ public static GUIStyle hotkeyStyle { return _hotkeyStyle; } } + [JsonObject(MemberSerialization.OptIn)] public class KeyBind { - [JsonProperty] - public string ID; - [JsonProperty] - public KeyCode Key; - [JsonProperty] - public bool Ctrl; - [JsonProperty] - public bool Alt; - [JsonProperty] - public bool Cmd; - [JsonProperty] - public bool Shift; - [JsonProperty] - public bool IsModifierOnly; - public KeyBind(string identifer, KeyCode key = KeyCode.None, bool ctrl = false, bool alt = false, bool cmd = false, bool shift = false, bool isModifierOnly = false) { + [JsonProperty] public string? ID; + [JsonProperty] public KeyCode Key; + [JsonProperty] public bool Ctrl; + [JsonProperty] public bool Alt; + [JsonProperty] public bool Cmd; + [JsonProperty] public bool Shift; + [JsonProperty] public bool IsModifierOnly; + public KeyBind(string? identifer, KeyCode key = KeyCode.None, bool ctrl = false, bool alt = false, bool cmd = false, bool shift = false, bool isModifierOnly = false) { ID = identifer; Key = key; Ctrl = ctrl; @@ -76,84 +75,75 @@ public KeyBind(string identifer, KeyCode key = KeyCode.None, bool ctrl = false, IsModifierOnly = isModifierOnly; } public bool Conflicts(KeyBind kb) { - Mod.Log($"kb: {this} {this.IsModifierOnly} vs {kb} {kb.IsModifierOnly}"); + Mod.Log($"kb: {this} {IsModifierOnly} vs {kb} {kb.IsModifierOnly}"); if (IsModifierOnly || kb.IsModifierOnly) return false; return Key == kb.Key && Ctrl == kb.Ctrl && Alt == kb.Alt && Cmd == kb.Cmd && Shift == kb.Shift; - } public override bool Equals(object o) { - if (o is KeyBind kb) { + if (o is KeyBind kb) return ID == kb.ID && Conflicts(kb); - } else return false; } - public override int GetHashCode() { - return ID.GetHashCode() - + (int)Key - + (Ctrl ? 1 : 0) - + (Cmd ? 1 : 0) - + (Shift ? 1 : 0); - } - [JsonIgnore] - public bool IsEmpty => Key == KeyCode.None; + public override int GetHashCode() => + ID.GetHashCode() + + (int)Key + + (Ctrl ? 1 : 0) + + (Cmd ? 1 : 0) + + (Shift ? 1 : 0); + + [JsonIgnore] public bool IsEmpty => Key == KeyCode.None; + [JsonIgnore] public bool IsKeyCodeActive { get { - if (Key == KeyCode.None) { - return false; - } - if (allowedMouseButtons.Contains(Key)) { - return Input.GetKey(Key); - } + if (Key == KeyCode.None) return false; + if (allowedMouseButtons.Contains(Key)) return Input.GetKey(Key); var active = Key == Event.current.keyCode; return active; } } + [JsonIgnore] public bool IsActive { get { - if (Event.current == null) { - return false; - } - if (!IsKeyCodeActive) { - return false; - } + if (Event.current == null) return false; + if (!IsKeyCodeActive) return false; var ctrlDown = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl); var altDown = Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt); var cmdDown = Input.GetKey(KeyCode.LeftCommand) || Input.GetKey(KeyCode.RightCommand); var shiftDown = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); // note we already checked Key above var active = ctrlDown == Ctrl - && altDown == Alt - && cmdDown == Cmd - && shiftDown == Shift; + && altDown == Alt + && cmdDown == Cmd + && shiftDown == Shift; return active; } } + public bool IsModifierActive { get { - if (Event.current == null) { - return false; - } + if (Event.current == null) return false; return Input.GetKey(Key); } } + public string bindCode => ToString(); public override string ToString() { // Why can't Unity display these ⌥⌃⇧⌘ ??? ⌗⌃⌥⇧⇑⌂©ăåâ var result = ""; if (Ctrl) - result += "^".cyan(); + result += "^".Cyan(); if (Shift) - result += "⇑".cyan(); + result += "⇑".Cyan(); if (Alt || Cmd) - result += "Alt".cyan(); - return result + (Ctrl || Shift || Alt ? "+".cyan() : "") + Key.ToString(); + result += "Alt".Cyan(); + return result + (Ctrl || Shift || Alt ? "+".Cyan() : "") + Key.ToString(); } } } -} +} \ No newline at end of file diff --git a/ModKit/UI/KeyBindings/KeyBindings.cs b/ToyBox/Classes/ModKit/UI/KeyBindings/KeyBindings.cs similarity index 78% rename from ModKit/UI/KeyBindings/KeyBindings.cs rename to ToyBox/Classes/ModKit/UI/KeyBindings/KeyBindings.cs index 0fde271c0..392d6f872 100644 --- a/ModKit/UI/KeyBindings/KeyBindings.cs +++ b/ToyBox/Classes/ModKit/UI/KeyBindings/KeyBindings.cs @@ -7,37 +7,38 @@ namespace ModKit { public static partial class UI { - public static IEnumerable<string> Conflicts(this KeyBind keyBind) => KeyBindings.conflicts - .GetValueOrDefault(keyBind.bindCode, new List<string> { }).Where(id => id != keyBind.ID); + public static IEnumerable<string?> Conflicts(this KeyBind keyBind) => KeyBindings.conflicts + .GetValueOrDefault(keyBind.bindCode, new List<string> { }).Where(id => id != keyBind.ID); public static void RemoveConflicts(this KeyBind keyBind) => KeyBindings.RemoveConflicts(keyBind); - public static string ToggleTranscriptForState(string identifier, bool state) - => $"Toggle: {identifier.bold()} -> {(state ? "True".blue() : "False".red())}"; + public static string ToggleTranscriptForState(string identifier, bool state) + => $"Toggle: {identifier.Blue()} -> {(state ? "True".Blue() : "False".Red())}"; + // This maintains the association of actions and KeyBinds associate with a specific identifier. Since we can not persist the action we persist the keybind and the client needs to register the action with the identifier each time the mod is initialized. This also contains logic to detect conflicts. // NOTE: This also provides an OnUpdate call and any client of this must manually call it during an OnUpdate block in their mod for KeyBindings to function correctly public static class KeyBindings { private static ModEntry modEntry = null; - private static SerializableDictionary<string, KeyBind> bindings = null; - private static readonly Dictionary<string, (Action action, Func<string,string> description)> actions = new() { }; + private static SerializableDictionary<string?, KeyBind> bindings = null; + private static readonly Dictionary<string?, (Action action, Func<string, string> description)> actions = new() { }; internal static Dictionary<string, List<string>> conflicts = new() { }; internal static bool BindingsDidChange = false; - public static bool IsActive(string identifier) => GetBinding(identifier).IsActive; - public static (Action action, Func<string, string> description)? GetAction(string identifier) { + public static bool IsActive(string? identifier) => GetBinding(identifier).IsActive; + public static (Action action, Func<string, string> description)? GetAction(string? identifier) { if (actions.ContainsKey(identifier)) return actions[identifier]; return null; } - public static void RegisterAction(string identifier, Action action, Func<string, string> description = null) + public static void RegisterAction(string? identifier, Action action, Func<string, string>? description = null) => actions[identifier] = (action, description); - internal static KeyBind GetBinding(string identifier) { + internal static KeyBind GetBinding(string? identifier) { BindingsDidChange = true; return bindings.GetValueOrDefault(identifier, new KeyBind(identifier)); } - internal static void SetBinding(string identifier, KeyBind binding) { + internal static void SetBinding(string? identifier, KeyBind binding) { bindings[identifier] = binding; modEntry.SaveSettings("bindings.json", bindings); BindingsDidChange = true; } - internal static void ClearBinding(string identifier) { + internal static void ClearBinding(string? identifier) { if (bindings.ContainsKey(identifier)) bindings.Remove(identifier); modEntry.SaveSettings("bindings.json", bindings); @@ -55,15 +56,13 @@ public static void UpdateConflicts() { conflicts[bindCode] = conflict; } } - conflicts = conflicts.Filter(kvp => kvp.Value.Count > 1); + conflicts = conflicts.Where(kvp => kvp.Value.Count > 1).ToDictionary(it => it.Key, it => it.Value); //Logger.Log($"conflicts: {String.Join(", ", conflicts.Select(kvp => $"{kvp.Key.orange()} : {kvp.Value.Count}".cyan())).yellow()}"); } public static void RemoveConflicts(KeyBind keyBind) { UpdateConflicts(); var doomed = keyBind.Conflicts(); - foreach (var condemnedIdentifier in doomed) { - ClearBinding(condemnedIdentifier); - } + foreach (var condemnedIdentifier in doomed) ClearBinding(condemnedIdentifier); } public static void OnLoad(ModEntry modEntry) { if (KeyBindings.modEntry == null) @@ -83,15 +82,13 @@ public static void OnGUI() { private static KeyBind lastTriggered = null; public static void OnUpdate() { - if (lastTriggered != null) { + if (lastTriggered != null) //if (debugKeyBind) // Logger.Log($" lastTriggered: {lastTriggered} - IsActive: {lastTriggered.IsActive}"); - if (!lastTriggered.IsActive) { + if (!lastTriggered.IsActive) //if (debugKeyBind) // Logger.Log($" lastTriggered: {lastTriggered} - Finished".green()); lastTriggered = null; - } - } //if (debugKeyBind) // Logger.Log($"looking for {Event.current.keyCode}"); foreach (var item in bindings) { @@ -100,7 +97,7 @@ public static void OnUpdate() { var active = binding.IsActive; //if (debugKeyBind) // Logger.Log($" checking: {binding.ToString()} - IsActive: {(active ? "True".cyan() : "False")} action: {actions.ContainsKey(identifier)}"); - if (active && actions.ContainsKey(identifier)) { + if (active && actions.ContainsKey(identifier)) //if (debugKeyBind) // Logger.Log($" binding: {binding.ToString()} - lastTriggered: {lastTriggered}"); if (binding != lastTriggered) { @@ -110,11 +107,10 @@ public static void OnUpdate() { entry.action(); lastTriggered = binding; if (!Mod.ModKitSettings.toggleKeyBindingsOutputToTranscript) continue; - Mod.InGameTranscriptLogger?.Invoke(entry.description != null ? entry.description(identifier) : $"Action " + identifier.blue()); + Mod.InGameTranscriptLogger?.Invoke(entry.description != null ? entry.description(identifier) : $"Action " + identifier.Blue()); } - } } } } } -} +} \ No newline at end of file diff --git a/ModKit/UI/KeyBindings/UI+KeyBindings.cs b/ToyBox/Classes/ModKit/UI/KeyBindings/UI+KeyBindings.cs similarity index 78% rename from ModKit/UI/KeyBindings/UI+KeyBindings.cs rename to ToyBox/Classes/ModKit/UI/KeyBindings/UI+KeyBindings.cs index 16710f767..65a87abfd 100644 --- a/ModKit/UI/KeyBindings/UI+KeyBindings.cs +++ b/ToyBox/Classes/ModKit/UI/KeyBindings/UI+KeyBindings.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using UnityEngine; using GL = UnityEngine.GUILayout; @@ -6,15 +7,15 @@ namespace ModKit { public static partial class UI { // Here we provide UI elements for managing KeyBinds. We provide a low level UI to set the keys for a key binding as well as some built in controls. - private static string selectedIdentifier = null; + private static string? selectedIdentifier = null; private static KeyBind oldValue = null; - public static KeyBind EditKeyBind(string identifier, bool showHint = true, bool allowModifierOnly = false, params GUILayoutOption[] options) { + public static KeyBind EditKeyBind(string? identifier, bool showHint = true, bool allowModifierOnly = false, params GUILayoutOption[] options) { if (Event.current.type == EventType.Layout) KeyBindings.OnGUI(); var keyBind = KeyBindings.GetBinding(identifier); var isEditing = identifier == selectedIdentifier; var isEditingOther = selectedIdentifier != null && identifier != selectedIdentifier && oldValue != null; - var label = keyBind.IsEmpty ? (isEditing ? "Cancel" : "Bind") : keyBind.ToString().orange().bold(); + var label = keyBind.IsEmpty ? isEditing ? "Cancel".localize() : "Bind".localize() : keyBind.ToString().Orange().Bold(); showHint = showHint && isEditing; var conflicts = keyBind.Conflicts(); using (VerticalScope(options)) { @@ -35,17 +36,17 @@ public static KeyBind EditKeyBind(string identifier, bool showHint = true, bool } var bind = keyBind; if (conflicts.Count() > 0) { - ActionButton("Replace", () => { bind.RemoveConflicts(); }); - ActionButton("Clear", () => { KeyBindings.ClearBinding(bind.ID); }); - Label("conflicts".orange().bold() + "\n" + string.Join("\n", conflicts)); + ActionButton("Replace".localize(), () => { bind.RemoveConflicts(); }); + ActionButton("Clear".localize(), () => { KeyBindings.ClearBinding(bind.ID); }); + Label("conflicts".localize().Orange().Bold() + "\n" + string.Join("\n", conflicts)); } if (showHint) { var hint = ""; if (keyBind.IsEmpty) - hint = oldValue == null ? "set key binding".green() : "press key".green(); + hint = oldValue == null ? "set key binding".localize().Green() : "press key".localize().Green(); Label(hint); if (oldValue != null) - ActionButton("Clear", + ActionButton("Clear".localize(), () => { KeyBindings.ClearBinding(oldValue.ID); selectedIdentifier = null; @@ -86,7 +87,7 @@ public static KeyBind EditKeyBind(string identifier, bool showHint = true, bool return keyBind; } - foreach (var mouseButton in allowedMouseButtons) { + foreach (var mouseButton in allowedMouseButtons) if (Input.GetKey(mouseButton)) { keyBind = new KeyBind(identifier, mouseButton, isCtrlDown, isAltDown, isCmdDown, isShiftDown); KeyBindings.SetBinding(identifier, keyBind); @@ -95,24 +96,23 @@ public static KeyBind EditKeyBind(string identifier, bool showHint = true, bool Input.ResetInputAxes(); return keyBind; } - } } return keyBind; } // Standalone control that lets the user set and edit a KeyBind associated with an identifier. Note you must call KeyBindings.RegisterAction to have this keybind fire an action. You also need to call KeyBindings.OnUpdate from your mod's OnUpdate delegate in order for ModKit to detect the keys and fire the action - public static void KeyBindPicker(string identifier, string title, float indent = 0, float titleWidth = 0) { + public static void KeyBindPicker(string? identifier, string? title, float indent = 0, float titleWidth = 0) { using (HorizontalScope()) { Space(indent); - Label(title.bold(), titleWidth == 0 ? ExpandWidth(false) : Width(titleWidth)); + Label(title.Bold(), titleWidth == 0 ? ExpandWidth(false) : Width(titleWidth)); Space(25); EditKeyBind(identifier, true); } } // This is a special helper that lets you choose only modifiers like ctrl, shift, alt, etc. This is useful for checking for modifiers on a mouse click. To check for the presence of a registered set of modifiers do this // UI.KeyBindings.GetBinding(<identifier>).IsModifierActive - public static void ModifierPicker(string identifier, string title, float indent = 0, float titleWidth = 0) { + public static void ModifierPicker(string? identifier, string title, float indent = 0, float titleWidth = 0) { using (HorizontalScope()) { - Label(title.bold(), titleWidth == 0 ? ExpandWidth(false) : Width(titleWidth)); + Label(title.Bold(), titleWidth == 0 ? ExpandWidth(false) : Width(titleWidth)); Space(25); EditKeyBind(identifier, true, true); } @@ -121,27 +121,21 @@ public static void ModifierPicker(string identifier, string title, float indent // One stop shopping for making controls that allow the player to bind to a key in game. Note you must call KeyBindings.RegisterAction with the title of this control to have this keybind fire an action. You also need to call KeyBindings.OnUpdate from your mod's OnUpdate delegate in order for ModKit to detect the keys and fire the action // Basic Action Button with HotKey - public static void BindableActionButton(string title, params GUILayoutOption[] options) { - BindableActionButton(title, false, options); - } - public static void BindableActionButton(string title, bool shouldLocalize = false, params GUILayoutOption[] options) { - if (options.Length == 0) { options = new GUILayoutOption[] { GL.Width(300) }; } + public static void BindableActionButton(string? title, params GUILayoutOption[] options) => BindableActionButton(title, false, options); + public static void BindableActionButton(string? title, bool shouldLocalize = false, params GUILayoutOption[] options) { + if (options.Length == 0) options = new GUILayoutOption[] { GL.Width(300) }; var actionEntry = KeyBindings.GetAction(title); - if (GL.Button(shouldLocalize ? title.localize() : title, options)) { - actionEntry?.action(); - } + if (GL.Button(shouldLocalize ? title.localize() : title, options)) actionEntry?.action(); EditKeyBind(title, true, false, Width(200)); } // Action button designed to live in a collection with a BindableActionButton - public static void NonBindableActionButton(string title, Action action, params GUILayoutOption[] options) { - if (options.Length == 0) { options = new GUILayoutOption[] { GL.Width(300) }; } - if (GL.Button(title, options)) { - action(); - } + public static void NonBindableActionButton(string? title, Action action, params GUILayoutOption[] options) { + if (options.Length == 0) options = new GUILayoutOption[] { GL.Width(300) }; + if (GL.Button(title, options)) action(); Space(204); if (Event.current.type == EventType.Layout) KeyBindings.RegisterAction(title, action); } } -} +} \ No newline at end of file diff --git a/ToyBox/Classes/ModKit/UI/Private/Toggle.cs b/ToyBox/Classes/ModKit/UI/Private/Toggle.cs new file mode 100644 index 000000000..acc01468e --- /dev/null +++ b/ToyBox/Classes/ModKit/UI/Private/Toggle.cs @@ -0,0 +1,114 @@ +using UnityEngine; +using UnityModManagerNet; + +namespace ModKit.Private { + public static partial class UI { + // Helper functionality. + + public static readonly GUIContent _LabelContent = new(); + public static readonly GUIContent CheckOnContent = new(CheckGlyphOn); + public static readonly GUIContent CheckOffContent = new(CheckGlyphOff); + public static readonly GUIContent DisclosureOnContent = new(DisclosureGlyphOn); + public static readonly GUIContent DisclosureOffContent = new(DisclosureGlyphOff); + public static readonly GUIContent DisclosureEmptyContent = new(DisclosureGlyphEmpty); + private static GUIContent LabelContent(string? text) { + _LabelContent.text = text; + _LabelContent.image = null; + _LabelContent.tooltip = null; + return _LabelContent; + } + + private static readonly int s_ButtonHint = "MyGUI.Button".GetHashCode(); + + public static bool Toggle(Rect rect, GUIContent label, bool value, bool isEmpty, GUIContent on, GUIContent off, GUIStyle stateStyle, GUIStyle labelStyle) { + var controlID = GUIUtility.GetControlID(s_ButtonHint, FocusType.Passive, rect); + var result = false; + switch (Event.current.GetTypeForControl(controlID)) { + case EventType.MouseDown: + if (GUI.enabled && rect.Contains(Event.current.mousePosition)) { + GUIUtility.hotControl = controlID; + Event.current.Use(); + } + break; + + case EventType.MouseDrag: + if (GUIUtility.hotControl == controlID) Event.current.Use(); + break; + + case EventType.MouseUp: + if (GUIUtility.hotControl == controlID) { + GUIUtility.hotControl = 0; + + if (rect.Contains(Event.current.mousePosition)) { + result = true; + Event.current.Use(); + } + } + break; + + case EventType.KeyDown: + if (GUIUtility.hotControl == controlID) + if (Event.current.keyCode == KeyCode.Escape) { + GUIUtility.hotControl = 0; + Event.current.Use(); + } + break; + + case EventType.Repaint: { + var rightAlign = stateStyle.alignment == TextAnchor.MiddleRight + || stateStyle.alignment == TextAnchor.UpperRight + || stateStyle.alignment == TextAnchor.LowerRight + ; + // stateStyle.alignment determines position of state element + var state = isEmpty + ? DisclosureEmptyContent + : value + ? on + : off; + var stateSize = stateStyle.CalcSize(value ? on : off); // don't use the empty content to calculate size so titles line up in lists + var x = rightAlign ? rect.xMax - stateSize.x : rect.x; + Rect stateRect = new(x, rect.y, stateSize.x, stateSize.y); + + // layout state before or after following alignment + var labelSize = labelStyle.CalcSize(label); + x = rightAlign ? stateRect.x - stateSize.x - 5 : stateRect.xMax + 5; + Rect labelRect = new(x, rect.y, labelSize.x, labelSize.y); + + stateStyle.Draw(stateRect, state, controlID); + labelStyle.Draw(labelRect, label, controlID); + } + break; + } + return result; + } + + // Button Control - Layout Version + + public static bool Toggle(GUIContent label, bool value, GUIContent on, GUIContent off, GUIStyle stateStyle, GUIStyle labelStyle, bool isEmpty = false, params GUILayoutOption[] options) { + var state = value ? on : off; + var sStyle = new GUIStyle(stateStyle); + var lStyle = new GUIStyle(labelStyle) { + wordWrap = false + }; + var stateSize = sStyle.CalcSize(state); + lStyle.fixedHeight = stateSize.y - 2; + // The top padding is totally not a hack that looks good enough on most ui scales. + var padding = new RectOffset(0, (int)stateSize.x + 5, stateStyle.padding.top + Mathf.RoundToInt(2 * UnityModManager.UI.Instance.mUIScale), 0); + lStyle.padding = padding; + lStyle.alignment = TextAnchor.MiddleCenter; + var rect = GUILayoutUtility.GetRect(label, lStyle, options); + return Toggle(rect, label, value, isEmpty, on, off, stateStyle, lStyle); + } + public static bool Toggle(string? label, bool value, string? on, string? off, GUIStyle stateStyle, GUIStyle labelStyle, params GUILayoutOption[] options) => Toggle(LabelContent(label), value, new GUIContent(on), new GUIContent(off), stateStyle, labelStyle, false, options); + // Disclosure Toggles + public static bool DisclosureToggle(GUIContent label, bool value, bool isEmpty = false, params GUILayoutOption[] options) => Toggle(label, value, DisclosureOnContent, DisclosureOffContent, GUI.skin.textArea, GUI.skin.label, isEmpty, options); + public static bool DisclosureToggle(string? label, bool value, GUIStyle stateStyle, GUIStyle labelStyle, bool isEmpty = false, params GUILayoutOption[] options) => Toggle(LabelContent(label), value, DisclosureOnContent, DisclosureOffContent, stateStyle, labelStyle, isEmpty, options); + public static bool DisclosureToggle(string? label, bool value, bool isEmpty = false, params GUILayoutOption[] options) => DisclosureToggle(label, value, GUI.skin.box, GUI.skin.label, isEmpty, options); + // CheckBox + public static bool CheckBox(GUIContent label, bool value, bool isEmpty, params GUILayoutOption[] options) => Toggle(label, value, CheckOnContent, CheckOffContent, GUI.skin.textArea, GUI.skin.label, isEmpty, options); + + public static bool CheckBox(string? label, bool value, bool isEmpty, GUIStyle style, params GUILayoutOption[] options) => Toggle(LabelContent(label), value, CheckOnContent, CheckOffContent, GUI.skin.box, style, isEmpty, options); + + public static bool CheckBox(string? label, bool value, bool isEmpty, params GUILayoutOption[] options) => CheckBox(label, value, isEmpty, GUI.skin.label, options); + } +} \ No newline at end of file diff --git a/ToyBox/Classes/ModKit/UI/RichText.cs b/ToyBox/Classes/ModKit/UI/RichText.cs new file mode 100644 index 000000000..2920fd087 --- /dev/null +++ b/ToyBox/Classes/ModKit/UI/RichText.cs @@ -0,0 +1,166 @@ +using System.Text; +using System; +using UnityEngine; +using System.Threading; +using ToyBox; + +namespace ModKit { + // https://docs.unity3d.com/Manual/StyledText.html + public enum RGBA : uint { + aqua = 0x00ffffff, + blue = 0x8080ffff, + brown = 0xC09050ff, //0xa52a2aff, + crimson = 0x7b0340ff, + cyan = 0x00ffffff, + darkblue = 0x0000a0ff, + charcoal = 0x202020ff, + darkgrey = 0x808080ff, + darkred = 0xa0333bff, + fuchsia = 0xff40ffff, + green = 0x40C040ff, + gold = 0xED9B1Aff, + lightblue = 0xd8e6ffff, + lightgrey = 0xE8E8E8ff, + lime = 0x40ff40ff, + magenta = 0xff40ffff, + maroon = 0xFF6060ff, + medred = 0xd03333ff, + navy = 0x3b5681ff, + olive = 0xb0b000ff, + orange = 0xffa500ff, // 0xffa500ff, + darkorange = 0xb1521fff, + pink = 0xf03399ff, + purple = 0xC060F0ff, + red = 0xFF4040ff, + black = 0x000000ff, + medgrey = 0xA8A8A8ff, + grey = 0xC0C0C0ff, + silver = 0xD0D0D0ff, + teal = 0x80f0c0ff, + yellow = 0xffff00ff, + white = 0xffffffff, + none = silver, + trash = 0x808080ff, // 0x686868ff, 0x787878ff, // 0x734d26ff, // 0x86592dff, //0xA07040ff, // brown, 0x606060FF, + common = 0xd8d8d8a0, // 0x505050ff, // 0xd8d8d8a0, //0xe8e8e8a0, + uncommon = 0x086c26ff, // 0x00882bff, //0x00802bff, //0x68b020ff, // 0x60B020ff, + rare = 0x103080ff, // 0x2060ffff, + epic = 0x481ca0ff, //0x5020c0ff, 0x6030F0ff, 0xc260f1ff, // 0x79297bff, //0x9f608cff, // 0x885278ff, // 0xc260f1ff, //0xc860fff, + legendary = 0xb2593aff, // 0xad5537ff, 0xb8603dff, 0xaa623dff, 0x9a5a3dff,0x9a4a2dff, (1.4.23 and older) 0xe67821e0, // 0x9a4a2dff, // 0x984c31ff, //0xe67821ff, //* 0xe67821e0, // 0xe67821ff, // 0xe68019ff // 0xEDCB1Aff, + mythic = 0xb02369ff, //0xc02369ff, 0xf03399ff, 0xc260f1ff, 0x8080ffff, //0x60ffffff, // 0x84e2d4ff, // 0x2cd8d4ff, // * 0x60ffffff, + primal = 0x2cb8b4ff, //pink, red + godly = 0x990033ff, // darkred, + notable = 0x98761fff, //0xb1821fff, 0xffe000ff, // 0xC08020ff //0xffd840ff, // 0x40ff40c0, // 0xf03399ff, // 0xff3399ff, + uncommon_dark = 0x00a000ff, // 0x00882bff, //0x00802bff, //0x68b020ff, // 0x60B020ff, + rare_dark = 0x1030e0ff, // 0x2060ffff, + epic_dark = 0x6030F0ff, //0x5020c0ff, 0x6030F0ff, 0xc260f1ff, // 0x79297bff, //0x9f608cff, // 0x885278ff, // 0xc260f1ff, //0xc860fff, + legendary_dark = 0xe67821e0, // 0xad5537ff, 0xb8603dff, 0xaa623dff, 0x9a5a3dff,0x9a4a2dff, (1.4.23 and older) 0xe67821e0, // 0x9a4a2dff, // 0x984c31ff, //0xe67821ff, //* 0xe67821e0, // 0xe67821ff, // 0xe68019ff // 0xEDCB1Aff, + mythic_dark = 0xA000A0ff, //0xc02369ff, 0xf03399ff, 0xc260f1ff, 0x8080ffff, //0x60ffffff, // 0x84e2d4ff, // 0x2cd8d4ff, // * 0x60ffffff, + primal_dark = 0x60ffffff, //pink, red + godly_dark = 0xa00000ff, // darkred, + notable_dark = 0x98761fff, //0xb1821fff, 0xffe000ff, // 0xC08020ff //0xffd840ff, // 0x40ff40c0, // 0xf03399ff, // 0xff3399ff, + } + public static class RichText { + public static Color Color(this RGBA rga, float adjust = 0) { + var red = (float)((long)rga >> 24) / 256f; + var green = (float)(0xFF & ((long)rga >> 16)) / 256f; + var blue = (float)(0xFF & ((long)rga >> 8)) / 256f; + var alpha = (float)(0xFF & ((long)rga)) / 256f; + var color = new Color(red, green, blue, alpha); + if (adjust < 0) + color = UnityEngine.Color.Lerp(color, UnityEngine.Color.black, -adjust); + if (adjust > 0) + color = UnityEngine.Color.Lerp(color, UnityEngine.Color.white, adjust); + return color; + } + public static string Size(this string s, int size) => $"<size={size}>{s}</size>"; + public static string Bold(this string s) => $"<b>{s}</b>"; + public static string CorrectColorHex(Color color) { + if (Event.current != null && Main.IsOnUnityThread && QualitySettings.activeColorSpace == ColorSpace.Linear) { + color = color.gamma; + } + return "#" + ColorUtility.ToHtmlStringRGBA(color); + } + public static string CorrectColorHex(string hex) { + if (Event.current == null || !Main.IsOnUnityThread || !ColorUtility.TryParseHtmlString(hex, out var color)) { + return hex; + } + return CorrectColorHex(color); + } + public static string Color(this string s, string color) => $"<color={CorrectColorHex(color)}>{s}</color>"; + //public static string Color(this string str, RGBA color) => $"<color=#{color:X}>{str}</color>"; + public static string Color(this string str, RGBA color) => str.Color(color.Color()); + public static string Color(this string str, Color color) => $"<color=#{CorrectColorHex(color)}>{str}</color>"; + public static string White(this string s) => s.Color("#ededed"); + public static string Grey(this string s) => s.Color("#A0A0A0FF"); + public static string DarkGrey(this string s) => s.Color("#505050FF"); + public static string Red(this string s) => s.Color("#C04040E0"); + public static string Green(this string s) => s.Color("#00ff00ff"); + public static string Blue(this string s) => s.Color("#0a1ce9"); + public static string Cyan(this string s) => s.Color("#6aebee"); + public static string Magenta(this string s) => s.Color("#dd3eea"); + public static string Yellow(this string s) => s.Color("#efee3e"); + public static string Orange(this string s) => s.Color("#f2a92d"); + public static string SizePercent(this string s, int percent) => $"<size={100 + percent}%>{s}</size>"; + } + public static class StringExtensions { + public static bool Matches(this string? source, string query) { + if (source == null || query == null) + return false; + return source.IndexOf(query, 0, StringComparison.InvariantCultureIgnoreCase) != -1; + } + public static string? MarkedSubstringNoHTML(this string? source, string sub) { + if (string.IsNullOrWhiteSpace(source) || string.IsNullOrWhiteSpace(sub)) + return source; + var index = source.IndexOf(sub, StringComparison.InvariantCultureIgnoreCase); + if (index != -1) { + var substr = source.Substring(index, sub.Length); + source = source.Replace(substr, RichText.Yellow(substr).Bold()); + } + return source; + } + public static string? MarkedSubstring(this string? source, string[] queryTerms) { + foreach (var term in queryTerms) { + source = source.MarkedSubstring(term); + } + return source; + } + public static string? MarkedSubstring(this string? source, string query) { + if (string.IsNullOrWhiteSpace(source) || string.IsNullOrWhiteSpace(query)) + return source; + var htmlStart = source.IndexOf('<'); + if (htmlStart == -1) + return source.MarkedSubstringNoHTML(query); + var result = new StringBuilder(); + var len = source.Length; + var segment = source.Substring(0, htmlStart); + segment = segment.MarkedSubstringNoHTML(query); + result.Append(segment); + var htmlEnd = source.IndexOf('>', htmlStart); + while (htmlStart != -1 && htmlEnd != -1) { + var tag = source.Substring(htmlStart, htmlEnd + 1 - htmlStart); + result.Append(tag); + htmlStart = source.IndexOf('<', htmlEnd); + if (htmlStart != -1) { + segment = source.Substring(htmlEnd + 1, htmlStart - htmlEnd - 1); + segment = segment.MarkedSubstringNoHTML(query); + result.Append(segment); + htmlEnd = source.IndexOf('>', htmlStart); + } + } + if (htmlStart != -1) { + var malformedTag = source.Substring(htmlStart, len + 1 - htmlStart); + result.Append(malformedTag); + } else if (htmlEnd < len) { + segment = source.Substring(htmlEnd + 1, len - htmlEnd - 1); + result.Append(segment.MarkedSubstringNoHTML(query)); + } + return result.ToString(); + } + public static string Repeat(this string s, int n) { + if (n < 0 || s == null || s.Length == 0) + return s; + return new StringBuilder(s.Length * n).Insert(0, s, n).ToString(); + } + public static string Indent(this string s, int n) => " ".Repeat(n) + s; + } +} diff --git a/ModKit/UI/Styles.cs b/ToyBox/Classes/ModKit/UI/Styles.cs similarity index 95% rename from ModKit/UI/Styles.cs rename to ToyBox/Classes/ModKit/UI/Styles.cs index 87cd35089..685dbcccb 100644 --- a/ModKit/UI/Styles.cs +++ b/ToyBox/Classes/ModKit/UI/Styles.cs @@ -1,5 +1,4 @@ -using Kingmaker.UI.ServiceWindow; -using UnityEngine; +using UnityEngine; using UnityModManagerNet; namespace ModKit { @@ -92,8 +91,7 @@ public static void DrawDiv(Color color, float indent = 0, float height = 0, floa divStyle.normal.background = fillTexture; if (divStyle.margin == null) { divStyle.margin = new RectOffset((int)indent, 0, 4, 4); - } - else { + } else { divStyle.margin.left = (int)indent + 3; } if (width > 0) @@ -110,13 +108,13 @@ public static Texture2D RarityTexture { get { if (_rarityTexture == null) _rarityTexture = new Texture2D(1, 1); - _rarityTexture.SetPixel(0, 0, RGBA.black.color()); + _rarityTexture.SetPixel(0, 0, RGBA.black.Color()); _rarityTexture.Apply(); return _rarityTexture; } } - private static GUIStyle _rarityStyle; - public static GUIStyle rarityStyle { + private static GUIStyle? _rarityStyle; + public static GUIStyle? rarityStyle { get { if (_rarityStyle == null) { _rarityStyle = new GUIStyle(GUI.skin.button); diff --git a/ToyBox/Classes/ModKit/UI/UI+Browser.cs b/ToyBox/Classes/ModKit/UI/UI+Browser.cs new file mode 100644 index 000000000..0161a4273 --- /dev/null +++ b/ToyBox/Classes/ModKit/UI/UI+Browser.cs @@ -0,0 +1,520 @@ +using ModKit.Utility; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using ToyBox; +using UnityEngine; + +namespace ModKit { + + public static partial class UI { + public class Browser { + public enum sortDirection { + Ascending = 1, + Descending = -1 + }; + + private static readonly Dictionary<object, object> ShowDetails = new(); + public static void ClearDetails() => ShowDetails.Clear(); + public static bool DetailToggle(string? title, object key, object? target = null, int width = 600) { + var changed = false; + if (target == null) target = key; + var expanded = ShowDetails.ContainsKey(key); + if (DisclosureToggle(title, ref expanded, width)) { + changed = true; + ShowDetails.Clear(); + if (expanded) { + ShowDetails[key] = target; + } + } + return changed; + } + public static bool OnDetailGUI(object key, Action<object> onDetailGUI) { + + ShowDetails.TryGetValue(key, out var target); + if (target != null) { + onDetailGUI(target); + return true; + } else { + return false; + } + } + } + public class Browser<Definition, Item> : Browser { + // Simple browser that displays a searchable collection of items along with a collection of available definitions. + // It provides a toggle to show the definitions mixed in with the items. + // By default it shows just the title but you can provide an optional RowUI to show more complex row UI including buttons to add and remove items and so forth. This will be layed out after the title + // You an also provide UI that renders children below the row + public ModKitSettings Settings => Mod.ModKitSettings; + private IEnumerable<Definition> _pagedResults = new List<Definition>(); + private Queue<Definition> cachedSearchResults; + public IEnumerable<Definition> filteredDefinitions = new List<Definition>(); + public IEnumerable<Definition> tempFilteredDefinitions; + public Dictionary<string, List<Definition>> collatedDefinitions = new(); + private Dictionary<Definition, Item> _currentDict; + public string prevCollationKey; + public string collationKey; + private CancellationTokenSource _searchCancellationTokenSource; + private CancellationTokenSource _collationCancellationTokenSource; + private string _searchText = ""; + public string SearchText => _searchText; + public sortDirection SortDirection = sortDirection.Ascending; + public bool doCollation = false; + private bool _collationKeyIsNullOrAllOrDoesNotExist => collationKey == null || collationKey?.ToLower() == "all" || (!collatedDefinitions?.ContainsKey(collationKey) ?? true); + public bool SearchAsYouType; + public bool ShowAll; + public bool DisplayShowAllGUI = true; + public bool IsDetailBrowser; + public bool _doCopyToEnd = false; + public bool _finishedCopyToEnd = false; + + public int SearchLimit { + get => IsDetailBrowser ? Settings.browserDetailSearchLimit : Settings.browserSearchLimit; + set { + var oldValue = SearchLimit; + if (IsDetailBrowser) + Settings.browserDetailSearchLimit = value; + else + Settings.browserSearchLimit = value; + if (value != oldValue) ModKitSettings.Save(); + } + } + private int _pageCount; + private int _matchCount; + private int _currentPage = 1; + public bool searchQueryChanged = true; + public void ResetSearch() { + searchQueryChanged = true; + ReloadData(); + } + public bool needsReloadData = true; + internal bool _needsRedoCollation = true; + internal bool _collationFinished = false; + public void RedoCollation() { + _needsRedoCollation = true; + _collationFinished = false; + _searchCancellationTokenSource?.Cancel(); + _collationCancellationTokenSource?.Cancel(); + ResetSearch(); + } + public void ReloadData() => needsReloadData = true; + private bool _updatePages = false; + private bool _finishedSearch = false; + private bool _resultsAreAllBPs = false; + public bool isCollating = false; + public bool isSearching = false; + public bool startedLoadingAvailable = false; + public bool availableIsStatic { get; private set; } + public bool useCustomNotRowGUI; + private List<Definition> _availableCache; + public void OnShowGUI() => RedoCollation(); + public Browser(bool searchAsYouType, bool availableIsStatic = false, bool isDetailBrowser = false, bool useCustomNotRowGUI = false) { + SearchAsYouType = searchAsYouType; + this.availableIsStatic = availableIsStatic; + IsDetailBrowser = isDetailBrowser; + this.useCustomNotRowGUI = useCustomNotRowGUI; + Mod.NotifyOnShowGUI += OnShowGUI; + } + + public void OnGUI( + IEnumerable<Item> current, + Func<IEnumerable<Definition>> available, // Func because available may be slow + Func<Item, Definition> definition, + Func<Definition, string?> searchKey, + Func<Definition, IComparable?[]> sortKeys, + Action? onHeaderGUI = null, + Action<Definition, Item>? onRowGUI = null, + Action<Definition, Item>? onDetailGUI = null, + int indent = 50, + bool showDiv = true, + bool search = true, + float titleMinWidth = 100, + float titleMaxWidth = 300, + string searchTextPassedFromParent = "", + bool showItemDiv = false, + Func<Definition, IEnumerable<string>> collator = null, + Action<List<Definition>, Dictionary<Definition, Item>> customNotRowGUI = null + ) { + current ??= new List<Item>(); + if (collationKey != prevCollationKey) { + prevCollationKey = collationKey; + ResetSearch(); + } + List<Definition> definitions = Update(current, available, search, searchKey, sortKeys, definition, collator); + if (search || SearchLimit < _matchCount) { + if (search) { + using (HorizontalScope()) { + indent.space(); + ActionTextField(ref _searchText, "searchText", (text) => { + if (!SearchAsYouType) return; + needsReloadData = true; + searchQueryChanged = true; + }, () => { needsReloadData = true; searchQueryChanged = true; }, MinWidth(320), AutoWidth()); + 25.space(); + Label("Limit".localize(), ExpandWidth(false)); + var searchLimit = SearchLimit; + ActionIntTextField(ref searchLimit, "Search Limit", (i) => { _updatePages = true; }, () => { _updatePages = true; }, width(175)); + if (searchLimit > 1000) { searchLimit = 1000; } + SearchLimit = searchLimit; + 25.space(); + if (DisplayShowAllGUI) { + if (DisclosureToggle("Show All".localize().Orange().Bold(), ref ShowAll)) { + startedLoadingAvailable |= ShowAll; + RedoCollation(); + } + 25.space(); + } + if (isCollating) { + Label("Collating...".localize().Cyan().Bold(), AutoWidth()); + 25.space(); + } else if (_doCopyToEnd) { + Label("Copying...".localize().Cyan().Bold(), AutoWidth()); + 25.space(); + } + } + } else { + if (_searchText != searchTextPassedFromParent) { + needsReloadData = true; + _searchText = searchTextPassedFromParent; + if (_searchText == null) { + _searchText = ""; + } + } + } + using (HorizontalScope()) { + if (search) { + space(indent); + ActionButton("Search".localize(), () => { needsReloadData = true; }, AutoWidth()); + } + space(25); + if (_matchCount > 0 || _searchText.Length > 0) { + string? matchesText = "Matches: ".localize().Green().Bold() + $"{_matchCount}".Orange().Bold(); + if (_matchCount > SearchLimit) { matchesText += " => ".Cyan() + $"{SearchLimit}".Cyan().Bold(); } + + Label(matchesText, ExpandWidth(false)); + } + if (_matchCount > SearchLimit) { + string? pageLabel = "Page: ".localize().Orange() + _currentPage.ToString().Cyan() + " / " + _pageCount.ToString().Cyan(); + 25.space(); + Label(pageLabel, ExpandWidth(false)); + ActionButton("-", () => { + if (_currentPage >= 1) { + if (_currentPage == 1) { + _currentPage = _pageCount; + } else { + _currentPage -= 1; + } + _updatePages = true; + } + }, AutoWidth()); + ActionButton("+", () => { + if (_currentPage > _pageCount) return; + if (_currentPage == _pageCount) { + _currentPage = 1; + } else { + _currentPage += 1; + } + _updatePages = true; + }, AutoWidth()); + } + } + } + if (showDiv) + Div(indent); + if (onHeaderGUI != null) { + using (HorizontalScope(AutoWidth())) { + space(indent); + onHeaderGUI(); + } + } + if (useCustomNotRowGUI) { + customNotRowGUI(definitions, _currentDict); + } else { + foreach (var def in definitions) { + if (showItemDiv) { + Div(indent); + } + _currentDict.TryGetValue(def, out var item); + if (onRowGUI != null) { + using (HorizontalScope(AutoWidth())) { + space(indent); + onRowGUI(def, item); + } + } + onDetailGUI?.Invoke(def, item); + } + } + } + + private List<Definition> Update( + IEnumerable<Item> current, + Func<IEnumerable<Definition>> available, + bool search, + Func<Definition, string?> searchKey, + Func<Definition, IComparable?[]> sortKeys, + Func<Item, Definition> definition, + Func<Definition, IEnumerable<string>> collator + ) { + if (Event.current.type == EventType.Layout) { + if (startedLoadingAvailable) { + _availableCache = available()?.ToList(); + if (_availableCache?.Count() > 0) { + startedLoadingAvailable = false; + ReloadData(); + if (!availableIsStatic) { + _availableCache = null; + } + } + } + if (_finishedSearch || isSearching) { + bool nothingToSearch = (!ShowAll && current.Count() == 0) || (ShowAll && (availableIsStatic ? _availableCache : available()).Count() == 0); + // If the search has at least one result + if (((cachedSearchResults?.Count ?? 0) > 0 || nothingToSearch || _resultsAreAllBPs) && (searchQueryChanged || _finishedSearch)) { + Comparer<Definition> comparer = Comparer<Definition>.Create((x, y) => { + var xKeys = sortKeys(x); + var yKeys = sortKeys(y); + for (int i = 0; i < Math.Min(xKeys.Length, yKeys.Length); i++) { + var xKey = xKeys[i] ?? ""; + var yKey = yKeys[i] ?? ""; + var compare = xKey.CompareTo(yKey); + if (compare != 0) + return (int)SortDirection * compare; + } + return (int)SortDirection * (xKeys.Length.CompareTo(yKeys.Length)); + }); + if (_finishedSearch && !searchQueryChanged) { + filteredDefinitions = new List<Definition>(); + } + if ((_doCopyToEnd && _finishedCopyToEnd) || (_resultsAreAllBPs && cachedSearchResults.Count == 0)) { + _doCopyToEnd = false; + _finishedCopyToEnd = false; + cachedSearchResults.Clear(); + filteredDefinitions = tempFilteredDefinitions; + if (_resultsAreAllBPs) { + filteredDefinitions.OrderBy(i => i, comparer); + } + } else { + // If the search already finished we want to copy all results as fast as possible + if (_finishedSearch && cachedSearchResults.Count < 1000) { + // This shouldn't be necessary but somehow it is + if (!(filteredDefinitions is List<Definition>)) filteredDefinitions = filteredDefinitions.ToList(); + ((List<Definition>)filteredDefinitions).AddRange(cachedSearchResults); + cachedSearchResults.Clear(); + ((List<Definition>)filteredDefinitions).OrderBy(i => i, comparer); + } // If it's too much then even the above approach will take up to ~10 seconds on decent setups + else if (_finishedSearch && !_doCopyToEnd) { + _doCopyToEnd = true; + _finishedCopyToEnd = false; + Task.Run(() => CopyToEnd(filteredDefinitions, cachedSearchResults, comparer)); + } // If it's not finished then we shouldn't have too many results anyway + else if (!_doCopyToEnd) { + // Lock the search results + // This shouldn't be necessary but somehow it is + if (!(filteredDefinitions is List<Definition>)) filteredDefinitions = filteredDefinitions.ToList(); + lock (cachedSearchResults) { + // Go through every item in the queue + while (cachedSearchResults.Count > 0) { + // Add the item into the OrderedSet filteredDefinitions + ((List<Definition>)filteredDefinitions).Add(cachedSearchResults.Dequeue()); + } + } + ((List<Definition>)filteredDefinitions).Sort(comparer); + } + } + } + _matchCount = filteredDefinitions.Count(); + UpdatePageCount(); + UpdatePaginatedResults(); + if (_finishedSearch && cachedSearchResults?.Count == 0) { + isSearching = false; + _updatePages = false; + _finishedSearch = false; + searchQueryChanged = false; + cachedSearchResults = null; + } + } + if (_needsRedoCollation && doCollation && collator != null) { + _collationFinished = false; + _currentDict = current.ToDictionaryIgnoringDuplicates(definition, c => c); + IEnumerable<Definition> definitions; + if (ShowAll) { + if (startedLoadingAvailable) { + definitions = _currentDict.Keys.ToList(); + } else if (availableIsStatic) { + definitions = _availableCache; + } else { + definitions = available(); + } + } else { + definitions = _currentDict.Keys.ToList(); + } + if (!isCollating) { + _collationCancellationTokenSource = new(); + _searchCancellationTokenSource?.Cancel(); + isCollating = true; + _needsRedoCollation = false; + Task.Run(() => Collate(definitions, collator, sortKeys)); + } else { + _collationCancellationTokenSource.Cancel(); + } + } + if (needsReloadData && !isCollating && ((doCollation && _collationFinished) || !doCollation || collator == null)) { + _currentDict = current.ToDictionaryIgnoringDuplicates(definition, c => c); + IEnumerable<Definition> definitions; + if (doCollation && collator != null && !_collationKeyIsNullOrAllOrDoesNotExist) { + definitions = collatedDefinitions[collationKey]; + } else { + if (ShowAll) { + if (startedLoadingAvailable) { + definitions = _currentDict.Keys.ToList(); + } else if (availableIsStatic) { + definitions = _availableCache; + } else { + definitions = available(); + } + } else { + definitions = _currentDict.Keys.ToList(); + } + } + if (!isSearching) { + _searchCancellationTokenSource = new(); + Task.Run(() => UpdateSearchResults(_searchText, definitions, searchKey, search)); + if (searchQueryChanged) { + filteredDefinitions = new List<Definition>(); + } + isSearching = true; + needsReloadData = false; + } else { + _searchCancellationTokenSource.Cancel(); + } + } + if (_updatePages) { + _updatePages = false; + UpdatePageCount(); + UpdatePaginatedResults(); + } + } + return _pagedResults?.ToList(); + } + + public void CopyToEnd(IEnumerable<Definition> filteredDefinitions, Queue<Definition> cachedSearchResults, Comparer<Definition> comparer) { + tempFilteredDefinitions = filteredDefinitions.Concat(cachedSearchResults); + if ((_collationCancellationTokenSource?.IsCancellationRequested ?? false) || (_searchCancellationTokenSource?.IsCancellationRequested ?? false)) { + tempFilteredDefinitions = new List<Definition>(); + } + tempFilteredDefinitions.OrderBy(i => i, comparer); + _finishedCopyToEnd = true; + } + + public void UpdateSearchResults(string searchTextParam, + IEnumerable<Definition> definitions, + Func<Definition, string>? searchKey, + bool search + ) { + _resultsAreAllBPs = false; + if (definitions == null) { + return; + } + cachedSearchResults = new(); + var terms = searchTextParam.Split(' ').Select(s => s.ToLower()).ToHashSet(); + if (search && !string.IsNullOrEmpty(searchTextParam)) { + foreach (var def in definitions) { + if (_searchCancellationTokenSource.IsCancellationRequested) { + isSearching = false; + return; + } + if (def.GetType().ToString().Contains(searchTextParam) + ) { + lock (cachedSearchResults) { + cachedSearchResults.Enqueue(def); + } + } else if (searchKey != null) { + var text = searchKey(def).ToLower(); + if (terms.All(term => text.Matches(term))) { + lock (cachedSearchResults) { + cachedSearchResults.Enqueue(def); + } + } + } + } + } else { + lock (cachedSearchResults) { + tempFilteredDefinitions = definitions; + _resultsAreAllBPs = true; + } + } + _finishedSearch = true; + } + public void Collate(IEnumerable<Definition> definitions, + Func<Definition, IEnumerable<string>> collator, + Func<Definition, IComparable[]> sortKeys) { + var timer = new Stopwatch(); + timer.Start(); + collatedDefinitions = new(); + + var definitionsWithKeys = definitions.ToDictionary(d => d, d => sortKeys(d)); + + foreach (var item in definitionsWithKeys) { + if (_collationCancellationTokenSource.IsCancellationRequested) { + isCollating = false; + return; + } + try { + foreach (var key in collator(item.Key)) { + List<Definition> group; + lock (collatedDefinitions) { + if (!collatedDefinitions.TryGetValue(key, out group)) { + group = new List<Definition>(); + collatedDefinitions[key] = group; + } + } + group.Add(item.Key); + } + } catch (Exception ex) { + Mod.Error($"Error collation definition {item.Key}:\n{ex}"); + } + } + foreach (var group in collatedDefinitions.Values) { + group.Sort((x, y) => { + var xKeys = definitionsWithKeys[x]; + var yKeys = definitionsWithKeys[y]; + for (int i = 0; i < Math.Min(xKeys.Length, yKeys.Length); i++) { + var xKey = xKeys[i] ?? ""; + var yKey = yKeys[i] ?? ""; + var compare = xKey.CompareTo(yKey); + if (compare != 0) + return (int)SortDirection * compare; + } + return (int)SortDirection * (xKeys.Length.CompareTo(yKeys.Length)); + }); + } + _collationFinished = true; + isCollating = false; + timer.Stop(); + Mod.Log($"Collation finished in {timer.ElapsedMilliseconds} milliseconds"); + } + public void UpdatePageCount() { + if (SearchLimit > 0) { + _pageCount = (int)Math.Ceiling((double)_matchCount / SearchLimit); + _currentPage = Math.Min(_currentPage, _pageCount); + _currentPage = Math.Max(1, _currentPage); + } else { + _pageCount = 1; + _currentPage = 1; + } + } + public void UpdatePaginatedResults() { + var limit = SearchLimit; + var count = _matchCount; + var offset = Math.Min(count, (_currentPage - 1) * limit); + limit = Math.Min(limit, Math.Max(count, count - limit)); + Mod.Trace($"{_currentPage} / {_pageCount} count: {count} => offset: {offset} limit: {limit} "); + _pagedResults = filteredDefinitions.Skip(offset).Take(limit).ToArray(); + } + } + } +} \ No newline at end of file diff --git a/ModKit/UI/UI+Builders.cs b/ToyBox/Classes/ModKit/UI/UI+Builders.cs similarity index 76% rename from ModKit/UI/UI+Builders.cs rename to ToyBox/Classes/ModKit/UI/UI+Builders.cs index d2a47dcf8..25895bb8f 100644 --- a/ModKit/UI/UI+Builders.cs +++ b/ToyBox/Classes/ModKit/UI/UI+Builders.cs @@ -1,11 +1,9 @@ // Copyright < 2021 > Narria (github user Cabarius) - License: MIT -using UnityEngine; using System; -using System.Collections; +using System.Collections.Generic; using System.Linq; -using Kingmaker.Designers.EventConditionActionSystem.Actions; +using UnityEngine; using GL = UnityEngine.GUILayout; -using System.Collections.Generic; namespace ModKit { public static partial class UI { @@ -60,7 +58,7 @@ public static void Group(params Action[] actions) { action(); } } - public static void HStack(string title = null, int stride = 0, params Action[] actions) { + public static void HStack(string? title = null, int stride = 0, params Action[] actions) { var length = actions.Length; if (stride < 1) { stride = length; } if (IsNarrow) @@ -69,9 +67,8 @@ public static void HStack(string title = null, int stride = 0, params Action[] a using (HorizontalScope()) { if (title != null) { if (ii == 0) { - Label(title.bold(), Width(150f)); - } - else { + Label(title.Bold(), Width(150f)); + } else { Space(153); } } @@ -82,26 +79,25 @@ public static void HStack(string title = null, int stride = 0, params Action[] a } } } - public static void VStack(string title = null, params Action[] actions) { + public static void VStack(string? title = null, params Action[] actions) { using (VerticalScope()) { if (title != null) Label(title); Group(actions); } } - public static void Column<T>(List<T> items, Action<T> action, string title = null, params GUILayoutOption[] options) { + public static void Column<T>(List<T> items, Action<T> action, string? title = null, params GUILayoutOption[] options) { var length = items.Count(); using (VerticalScope(options)) { if (title != null) Label(title); - var ii = 0; foreach (var item in items) { //Mod.Log($" row: {ii++} - {item.GetType().ToString()}"); action(item); } } } - public static void Row<T>(List<T> items, Action<T> action , string title = null, params GUILayoutOption[] options) { + public static void Row<T>(List<T> items, Action<T> action, string? title = null, params GUILayoutOption[] options) { var length = items.Count(); using (HorizontalScope()) { if (title != null) { @@ -109,7 +105,6 @@ public static void Row<T>(List<T> items, Action<T> action , string title = null, Label(title); } } - var ii = 0; foreach (var item in items) { using (VerticalScope(options)) { //Mod.Log($" col: {ii++} - {item.GetType().ToString()}"); @@ -119,14 +114,14 @@ public static void Row<T>(List<T> items, Action<T> action , string title = null, } } - public static void Table<T>(List<T> items, Action<T> action, int numColumns = 2, string title = null, params GUILayoutOption[] options) { + public static void Table<T>(List<T> items, Action<T> action, int numColumns = 2, string? title = null, params GUILayoutOption[] options) { var length = items.Count(); if (numColumns < 1) { numColumns = length; } if (IsNarrow) numColumns = Math.Min(3, numColumns); - var splitItems = items.ToList().Partition(numColumns); + var splitItems = items.ToList().Chunk(numColumns).Select(i => i.ToList()).ToList(); Column(splitItems, rowItems => { Row(rowItems, rowItem => action(rowItem), title, options); @@ -135,22 +130,39 @@ public static void Table<T>(List<T> items, Action<T> action, int numColumns = 2, public static void Section(string title, params Action[] actions) { Space(25); - Label($"====== {title} ======".bold(), GL.ExpandWidth(true)); + Label($"====== {title} ======".Bold(), GL.ExpandWidth(true)); Space(25); foreach (var action in actions) { action(); } Space(10); } - public static void TabBar(ref int selected, Action header = null, params NamedAction[] actions) { + public static void TabBar(ref int selected, Action? header = null, params NamedAction[] actions) { if (selected >= actions.Count()) selected = 0; var sel = selected; - var titles = actions.Select((a, i) => i == sel ? a.name.orange().bold() : a.name); + var titles = actions.Select((a, i) => i == sel ? a.name.Orange().Bold() : a.name); SelectionGrid(ref selected, titles.ToArray(), 8, Width(ummWidth - 60)); GL.BeginVertical("box"); header?.Invoke(); actions[selected].action(); GL.EndVertical(); } + + public static void TabBar(ref int selected, Action? header = null, Action<int, int> onChangeTab = null, Func<string, string> titleFormatter = null, params NamedAction[] actions) { + if (selected >= actions.Count()) + selected = 0; + var sel = selected; + IEnumerable<string> titles; + if (titleFormatter != null) { + titles = actions.Select((a, i) => i == sel ? titleFormatter(a.name).Orange().Bold() : titleFormatter(a.name)); + } else { + titles = actions.Select((a, i) => i == sel ? a.name.Orange().Bold() : a.name); + } + if (SelectionGrid(ref selected, titles.ToArray(), 8, Width(ummWidth - 60))) onChangeTab(sel, selected); + GL.BeginVertical("box"); + header?.Invoke(); + actions[selected].action(); + GL.EndVertical(); + } } } diff --git a/ModKit/UI/UI+Controls.cs b/ToyBox/Classes/ModKit/UI/UI+Controls.cs similarity index 78% rename from ModKit/UI/UI+Controls.cs rename to ToyBox/Classes/ModKit/UI/UI+Controls.cs index 13aa111bd..fca1720f6 100644 --- a/ModKit/UI/UI+Controls.cs +++ b/ToyBox/Classes/ModKit/UI/UI+Controls.cs @@ -27,10 +27,10 @@ public static GUILayoutOption[] AddDefaults(this GUILayoutOption[] options, para // Labels - public static void Label(string title, params GUILayoutOption[] options) => + public static void Label(string? title, params GUILayoutOption[] options) => // var content = tooltip == null ? new GUIContent(title) : new GUIContent(title, tooltip); GL.Label(title, options.AddDefaults()); - public static void Label(string title, GUIStyle style, params GUILayoutOption[] options) => + public static void Label(string? title, GUIStyle style, params GUILayoutOption[] options) => // var content = tooltip == null ? new GUIContent(title) : new GUIContent(title, tooltip); // if (options.Length == 0) { options = new GUILayoutOption[] { GL.Width(150f) }; } GL.Label(title, style, options.AddDefaults()); @@ -38,20 +38,20 @@ public static void Label(GUIContent content, params GUILayoutOption[] options) = // var content = tooltip == null ? new GUIContent(title) : new GUIContent(title, tooltip); // if (options.Length == 0) { options = new GUILayoutOption[] { GL.Width(150f) }; } GL.Label(content, options); - public static void TitleLabel(string title, params GUILayoutOption[] options) { + public static void TitleLabel(string? title, params GUILayoutOption[] options) { using (VerticalScope(options.AddDefaults())) { Rect lastRect; using (HorizontalScope(options.AddDefaults())) { - GL.Label(title,AutoWidth()); + GL.Label(title, AutoWidth()); lastRect = GUILayoutUtility.GetLastRect(); } DivLast(lastRect); } } public static void ClipboardLabel(string title, params GUILayoutOption[] options) => GUILayout.TextArea(title, options); - public static void HelpLabel(string title, params GUILayoutOption[] options) => Label(title.green(), options); - public static void HelpLabel(string title, GUIStyle style, params GUILayoutOption[] options) => Label(title.green(), style, options); - public static void DescriptiveLabel(string title, string description, params GUILayoutOption[] options) { + public static void HelpLabel(string? title, params GUILayoutOption[] options) => Label(title.Green(), options); + public static void HelpLabel(string title, GUIStyle style, params GUILayoutOption[] options) => Label(title.Green(), style, options); + public static void DescriptiveLabel(string? title, string? description, params GUILayoutOption[] options) { options = options.AddDefaults(Width(300)); using (HorizontalScope()) { Label(title, options); @@ -59,7 +59,7 @@ public static void DescriptiveLabel(string title, string description, params GUI Label(description); } } - public static bool EditableLabel(ref string label, ref (string, string) editState, float minWidth, GUIStyle style, Func<string, string> formatter = null, params GUILayoutOption[] options) { + public static bool EditableLabel(ref string? label, ref (string, string) editState, float minWidth, GUIStyle style, Func<string, string?>? formatter = null, params GUILayoutOption[] options) { var changed = false; if (editState.Item1 != label) { using (HorizontalScope(options.AddDefaults())) { @@ -71,17 +71,16 @@ public static bool EditableLabel(ref string label, ref (string, string) editStat Mod.Log($"Edit: {editState}"); } } - } - else { + } else { GUI.SetNextControlName(label); using (HorizontalScope(options)) { TextField(ref editState.Item2, null, MinWidth(minWidth), AutoWidth()); Space(15); - if (GL.Button(Glyphs.CheckOff.red(), GUI.skin.box, AutoWidth())) { + if (GL.Button(Glyphs.CheckOff.Red(), GUI.skin.box, AutoWidth())) { editState = (null, null); } // ReSharper disable once InvertIf - if (GL.Button(Glyphs.CheckOn.green(), GUI.skin.box, AutoWidth()) + if (GL.Button(Glyphs.CheckOn.Green(), GUI.skin.box, AutoWidth()) || userHasHitReturn && focusedControlName == label) { label = editState.Item2; changed = true; @@ -91,16 +90,16 @@ public static bool EditableLabel(ref string label, ref (string, string) editStat } return changed; } - public static bool EditableLabel(ref string label, ref (string, string) editState, float minWidth, Func<string, string> formatter = null, params GUILayoutOption[] options) => EditableLabel(ref label, ref editState, minWidth, GUI.skin.label, formatter, options); + public static bool EditableLabel(ref string? label, ref (string, string) editState, float minWidth, Func<string, string?>? formatter = null, params GUILayoutOption[] options) => EditableLabel(ref label, ref editState, minWidth, GUI.skin.label, formatter, options); // Text Fields - public static string TextField(ref string text, string name = null, params GUILayoutOption[] options) { + public static string TextField(ref string text, string? name = null, params GUILayoutOption[] options) { if (name != null) { GUI.SetNextControlName(name); } text = GL.TextField(text, options.AddDefaults()); return text; } - public static int IntTextField(ref int value, string name = null, params GUILayoutOption[] options) { + public static int IntTextField(ref int value, string? name = null, params GUILayoutOption[] options) { var text = $"{value}"; TextField(ref text, name, options); if (int.TryParse(text, out var val)) { @@ -110,7 +109,7 @@ public static int IntTextField(ref int value, string name = null, params GUILayo } return value; } - public static float FloatTextField(ref float value, string name = null, params GUILayoutOption[] options) { + public static float FloatTextField(ref float value, string? name = null, params GUILayoutOption[] options) { var text = $"{value}"; TextField(ref text, name, options); if (float.TryParse(text, out var val)) { @@ -178,18 +177,18 @@ public static void ActionIntTextField( // Buttons - public static bool Button(string title, ref bool pressed, params GUILayoutOption[] options) { + public static bool Button(string? title, ref bool pressed, params GUILayoutOption[] options) { if (GL.Button(title, options.AddDefaults())) { pressed = true; } return pressed; } - public static bool Button(string title, ref bool pressed, GUIStyle style, params GUILayoutOption[] options) { + public static bool Button(string? title, ref bool pressed, GUIStyle style, params GUILayoutOption[] options) { if (GL.Button(title, style, options.AddDefaults())) { pressed = true; } return pressed; } // Action Buttons - public static bool ActionButton(string title, Action action, params GUILayoutOption[] options) { + public static bool ActionButton(string? title, Action action, params GUILayoutOption[] options) { if (!GL.Button(title, options.AddDefaults())) { return false; } @@ -199,7 +198,7 @@ public static bool ActionButton(string title, Action action, params GUILayoutOpt return true; } - public static bool ActionButton(string title, Action action, GUIStyle style, params GUILayoutOption[] options) { + public static bool ActionButton(string? title, Action action, GUIStyle style, params GUILayoutOption[] options) { if (!GL.Button(title, style, options.AddDefaults())) { return false; } @@ -209,19 +208,19 @@ public static bool ActionButton(string title, Action action, GUIStyle style, par return true; } - public static void DangerousActionButton(string title, string warning, ref bool areYouSureState, Action action, params GUILayoutOption[] options) { + public static void DangerousActionButton(string? title, string? warning, ref bool areYouSureState, Action action, params GUILayoutOption[] options) { using (HorizontalScope()) { var areYouSure = areYouSureState; ActionButton(title, () => { areYouSure = !areYouSure; }); if (areYouSureState) { Space(25); - Label("Are you sure?".yellow()); + Label("Are you sure?".localize().Yellow()); Space(25); - ActionButton("YES".yellow().bold(), action); + ActionButton("YES".localize().Yellow().Bold(), action); Space(10); - ActionButton("NO".green(), () => areYouSure = false); + ActionButton("NO".localize().Green(), () => areYouSure = false); Space(25); - Label(warning.orange()); + Label(warning.Orange()); } areYouSureState = areYouSure; } @@ -235,16 +234,16 @@ public static bool ValueAdjuster(ref int value, int increment = 1, int min = 0, ActionButton(" < ", () => { v = Math.Max(v - increment, min); }, textBoxStyle, AutoWidth()); else if (showMinMax) { Space(-21); - ActionButton("min ".cyan(), () => { }, textBoxStyle, AutoWidth()); + ActionButton("min".localize().Cyan() + " ", () => { }, textBoxStyle, AutoWidth()); } else { 34.space(); } var temp = false; - Button($"{v}".orange().bold(), ref temp, textBoxStyle, AutoWidth()); + Button($"{v}".Orange().Bold(), ref temp, textBoxStyle, AutoWidth()); if (v < max) ActionButton(" > ", () => { v = Math.Min(v + increment, max); }, textBoxStyle, AutoWidth()); else if (showMinMax) { - ActionButton(" max".cyan(), () => { }, textBoxStyle, AutoWidth()); + ActionButton(" " + "max".localize().Cyan(), () => { }, textBoxStyle, AutoWidth()); Space(-27); } else 34.space(); @@ -262,7 +261,7 @@ public static bool ValueAdjuster(Func<int> get, Action<int> set, int increment = return changed; } - public static bool ValueAdjuster(string title, ref int value, int increment = 1, int min = 0, int max = int.MaxValue, params GUILayoutOption[] options) { + public static bool ValueAdjuster(string? title, ref int value, int increment = 1, int min = 0, int max = int.MaxValue, params GUILayoutOption[] options) { var changed = false; using (HorizontalScope(options)) { Label(title); @@ -273,7 +272,7 @@ public static bool ValueAdjuster(string title, ref int value, int increment = 1, public static bool ValueAdjuster(string title, Func<int> get, Action<int> set, int increment = 1, int min = 0, int max = int.MaxValue, bool showMinMax = true) { var changed = false; using (HorizontalScope(Width(400))) { - Label(title.cyan(), Width(300)); + Label(title.Cyan(), Width(300)); Space(15); var value = get(); changed = ValueAdjuster(ref value, increment, min, max, showMinMax); @@ -282,10 +281,10 @@ public static bool ValueAdjuster(string title, Func<int> get, Action<int> set, i } return changed; } - public static bool ValueAdjuster(string title, Func<int> get, Action<int> set, int increment = 1, int min = 0, int max = int.MaxValue, params GUILayoutOption[] options) { + public static bool ValueAdjuster(string? title, Func<int> get, Action<int> set, int increment = 1, int min = 0, int max = int.MaxValue, params GUILayoutOption[] options) { var changed = false; using (HorizontalScope()) { - Label(title.cyan(), options); + Label(title.Cyan(), options); Space(15); var value = get(); changed = ValueAdjuster(ref value, increment, min, max); @@ -297,10 +296,10 @@ public static bool ValueAdjuster(string title, Func<int> get, Action<int> set, i // Value Editors - public static bool ValueAdjustorEditable(string title, Func<int> get, Action<int> set, int increment = 1, int min = 0, int max = int.MaxValue, params GUILayoutOption[] options) { + public static bool ValueAdjustorEditable(string? title, Func<int> get, Action<int> set, int increment = 1, int min = 0, int max = int.MaxValue, params GUILayoutOption[] options) { var changed = false; using (HorizontalScope()) { - Label(title.cyan(), options); + Label(title.Cyan(), options); Space(15); var value = get(); changed = ValueAdjuster(ref value, increment, min, max); @@ -321,7 +320,7 @@ public static bool ValueEditor(string title, Func<int> get, Action<int> set, ref var value = get(); var inc = increment; using (HorizontalScope(options)) { - Label(title.cyan(), ExpandWidth(true)); + Label(title.Cyan(), ExpandWidth(true)); Space(25); var fieldWidth = GUI.skin.textField.CalcSize(new GUIContent(max.ToString())).x; if (ValueAdjuster(ref value, inc, min, max)) { @@ -344,9 +343,9 @@ public static bool Slider(ref float value, float min, float max, float defaultVa Space(25); FloatTextField(ref newValue, null, Width(75)); if (units.Length > 0) - Label($"{units}".orange().bold(), Width(25 + GUI.skin.label.CalcSize(new GUIContent(units)).x)); + Label($"{units}".Orange().Bold(), Width(25 + GUI.skin.label.CalcSize(new GUIContent(units)).x)); Space(25); - ActionButton("Reset", () => { newValue = defaultValue; }, AutoWidth()); + ActionButton("Reset".localize(), () => { newValue = defaultValue; }, AutoWidth()); } var changed = Math.Abs(value - newValue) > float.Epsilon; value = newValue; @@ -355,13 +354,14 @@ public static bool Slider(ref float value, float min, float max, float defaultVa private const int SliderTop = 3; private const int SliderBottom = -7; - public static bool Slider(string title, ref float value, float min, float max, float defaultValue = 1.0f, int decimals = 0, string units = "", params GUILayoutOption[] options) { + + public static bool Slider(string? title, ref float value, float min, float max, float defaultValue = 1.0f, int decimals = 0, string? units = "", params GUILayoutOption[] options) { value = Math.Max(min, Math.Min(max, value)); // clamp it var newValue = value; using (HorizontalScope(options)) { using (VerticalScope(Width(300))) { Space((SliderTop - 1).point()); - Label(title.cyan(), Width(300)); + Label(title.Cyan(), Width(300)); Space(SliderBottom.point()); } Space(25); @@ -377,11 +377,11 @@ public static bool Slider(string title, ref float value, float min, float max, f Space(SliderBottom.point()); } if (units.Length > 0) - Label($"{units}".orange().bold(), Width(25 + GUI.skin.label.CalcSize(new GUIContent(units)).x)); + Label($"{units}".Orange().Bold(), Width(25 + GUI.skin.label.CalcSize(new GUIContent(units)).x)); Space(25); using (VerticalScope(AutoWidth())) { Space((SliderTop - 0).point()); - ActionButton("Reset", () => { newValue = defaultValue; }, AutoWidth()); + ActionButton("Reset".localize(), () => { newValue = defaultValue; }, AutoWidth()); Space(SliderBottom.point()); } } @@ -390,40 +390,39 @@ public static bool Slider(string title, ref float value, float min, float max, f value = newValue; return changed; } - public static bool Slider(string title, Func<float> get, Action<float> set, float min, float max, float defaultValue = 1.0f, int decimals = 0, string units = "", params GUILayoutOption[] options) { + public static bool Slider(string? title, Func<float> get, Action<float> set, float min, float max, float defaultValue = 1.0f, int decimals = 0, string? units = "", params GUILayoutOption[] options) { var value = get(); var changed = Slider(title, ref value, min, max, defaultValue, decimals, units, options); if (changed) set(value); return changed; } - public static bool Slider(string title, ref int value, int min, int max, int defaultValue = 1, string units = "", params GUILayoutOption[] options) { + public static bool Slider(string? title, ref int value, int min, int max, int defaultValue = 1, string? units = "", params GUILayoutOption[] options) { float floatValue = value; var changed = Slider(title, ref floatValue, min, max, (float)defaultValue, 0, units, options); value = (int)floatValue; return changed; } - public static bool Slider(string title, Func<int> get, Action<int> set, int min, int max, int defaultValue = 1, string units = "", params GUILayoutOption[] options) { + public static bool Slider(string? title, Func<int> get, Action<int> set, int min, int max, int defaultValue = 1, string? units = "", params GUILayoutOption[] options) { float floatValue = get(); var changed = Slider(title, ref floatValue, min, max, (float)defaultValue, 0, units, options); if (changed) set((int)floatValue); return changed; } - public static bool Slider(ref int value, int min, int max, int defaultValue = 1, string units = "", params GUILayoutOption[] options) { float floatValue = value; var changed = Slider(ref floatValue, min, max, (float)defaultValue, 0, units, options); value = (int)floatValue; return changed; } - public static bool LogSlider(string title, ref float value, float min, float max, float defaultValue = 1.0f, int decimals = 0, string units = "", params GUILayoutOption[] options) { + public static bool LogSlider(string? title, ref float value, float min, float max, float defaultValue = 1.0f, int decimals = 0, string units = "", params GUILayoutOption[] options) { if (min < 0) throw new Exception("LogSlider - min value: {min} must be >= 0"); BeginHorizontal(options); using (VerticalScope(Width(300))) { Space((SliderTop - 1).point()); - Label(title.cyan(), Width(300)); + Label(title.Cyan(), Width(300)); Space(SliderBottom.point()); } Space(25); @@ -447,11 +446,11 @@ public static bool LogSlider(string title, ref float value, float min, float max Space(SliderBottom.point()); } if (units.Length > 0) - Label($"{units}".orange().bold(), Width(25 + GUI.skin.label.CalcSize(new GUIContent(units)).x)); + Label($"{units}".Orange().Bold(), Width(25 + GUI.skin.label.CalcSize(new GUIContent(units)).x)); Space(25); using (VerticalScope(AutoWidth())) { Space((SliderTop + 0).point()); - ActionButton("Reset", () => { newValue = defaultValue; }, AutoWidth()); + ActionButton("Reset".localize(), () => { newValue = defaultValue; }, AutoWidth()); Space(SliderBottom.point()); } EndHorizontal(); @@ -459,7 +458,8 @@ public static bool LogSlider(string title, ref float value, float min, float max value = Math.Min(max, Math.Max(min, newValue)); return changed; } - public static bool LogSlider(string title, Func<float> get, Action<float> set, float min, float max, float defaultValue = 1.0f, int decimals = 0, string units = "", params GUILayoutOption[] options) { + + public static bool LogSlider(string? title, Func<float> get, Action<float> set, float min, float max, float defaultValue = 1.0f, int decimals = 0, string units = "", params GUILayoutOption[] options) { var value = get(); var changed = LogSlider(title, ref value, min, max, defaultValue, decimals, units, options); if (changed) @@ -473,7 +473,7 @@ public static bool LogSliderCustomLabelWidth(string title, ref float value, floa BeginHorizontal(options); using (VerticalScope(Width(labelWidth))) { Space((SliderTop - 1).point()); - Label(title.cyan(), Width(labelWidth)); + Label(title.Cyan(), Width(labelWidth)); Space(SliderBottom.point()); } Space(25); @@ -497,11 +497,11 @@ public static bool LogSliderCustomLabelWidth(string title, ref float value, floa Space(SliderBottom.point()); } if (units.Length > 0) - Label($"{units}".orange().bold(), Width(25 + GUI.skin.label.CalcSize(new GUIContent(units)).x)); + Label($"{units}".Orange().Bold(), Width(25 + GUI.skin.label.CalcSize(new GUIContent(units)).x)); Space(25); using (VerticalScope(AutoWidth())) { Space((SliderTop + 0).point()); - ActionButton("Reset", () => { newValue = defaultValue; }, AutoWidth()); + ActionButton("Reset".localize(), () => { newValue = defaultValue; }, AutoWidth()); Space(SliderBottom.point()); } EndHorizontal(); diff --git a/ModKit/UI/UI+Elements.cs b/ToyBox/Classes/ModKit/UI/UI+Elements.cs similarity index 95% rename from ModKit/UI/UI+Elements.cs rename to ToyBox/Classes/ModKit/UI/UI+Elements.cs index 5e51abc7f..190c9e5b1 100644 --- a/ModKit/UI/UI+Elements.cs +++ b/ToyBox/Classes/ModKit/UI/UI+Elements.cs @@ -4,7 +4,7 @@ namespace ModKit { public static partial class UI { - public static string ChecklyphOn = $"<color=green><b>{Glyphs.CheckOn}</b></color>"; + public static string CheckGlyphOn = $"<color=green><b>{Glyphs.CheckOn}</b></color>"; public static string CheckGlyphOff = $"<color=#B8B8B8FF>{Glyphs.CheckOff}</color>"; // #A0A0A0E0 public static string CheckGlyphEmpty = $" <color=#B8B8B8FF>{Glyphs.CheckEmpty}</color> "; public static string DisclosureGlyphOn = $"<color=orange><b>{Glyphs.DisclosureOn}</b></color>"; // ▼▲∧⋀ diff --git a/ModKit/UI/UI+HTML.cs b/ToyBox/Classes/ModKit/UI/UI+HTML.cs similarity index 65% rename from ModKit/UI/UI+HTML.cs rename to ToyBox/Classes/ModKit/UI/UI+HTML.cs index fbed4d0d9..ec7934286 100644 --- a/ModKit/UI/UI+HTML.cs +++ b/ToyBox/Classes/ModKit/UI/UI+HTML.cs @@ -7,17 +7,17 @@ namespace ModKit { public static partial class UI { private static GUIStyle linkStyle = null; - public static bool LinkButton(string title, string url, Action action = null, params GUILayoutOption[] options) { + public static bool LinkButton(string? title, string url, Action? action = null, params GUILayoutOption[] options) { if (options.Length == 0) { options = new GUILayoutOption[] { AutoWidth() }; } if (linkStyle == null) { - linkStyle = new GUIStyle(GUI.skin.toggle) { + linkStyle = new GUIStyle(rarityStyle) { wordWrap = false }; //linkStyle.normal.background = RarityTexture; // Match selection color which works nicely for both light and dark skins linkStyle.padding = new RectOffset(-3.point(), 0, 0, 0); #pragma warning disable CS0618 // Type or member is obsolete - linkStyle.clipOffset = new Vector2(3.point(), 0); + linkStyle.clipOffset = new Vector2(0.point(), 0); #pragma warning restore CS0618 // Type or member is obsolete linkStyle.normal.textColor = new Color(0f, 0.75f, 1f); linkStyle.stretchWidth = false; @@ -25,12 +25,14 @@ public static bool LinkButton(string title, string url, Action action = null, pa } bool result; Rect rect; - using (HorizontalScope()) { - Space(4.point()); - result = GL.Button(title, linkStyle, options); - rect = GUILayoutUtility.GetLastRect(); + using (VerticalScope()) { + using (HorizontalScope()) { + Space(6.point()); + result = GL.Button(title, linkStyle, options); + rect = GUILayoutUtility.GetLastRect(); + } + DrawDiv(linkStyle.normal.textColor, 0, 0, rect.width + 4.point()); } - DrawDiv(linkStyle.normal.textColor, 0 , 0, rect.width + 4.point()); if (result) { Application.OpenURL(url); action?.Invoke(); diff --git a/ModKit/UI/UI+Pickers.cs b/ToyBox/Classes/ModKit/UI/UI+Pickers.cs similarity index 66% rename from ModKit/UI/UI+Pickers.cs rename to ToyBox/Classes/ModKit/UI/UI+Pickers.cs index 96b9b3677..eb8898d59 100644 --- a/ModKit/UI/UI+Pickers.cs +++ b/ToyBox/Classes/ModKit/UI/UI+Pickers.cs @@ -1,6 +1,7 @@ // Copyright < 2021 > Narria (github user Cabarius) - License: MIT using System; using System.Collections.Generic; +using System.Diagnostics.Eventing.Reader; using System.Linq; using UnityEngine; using GL = UnityEngine.GUILayout; @@ -17,7 +18,7 @@ public static bool SelectionGrid(ref int selected, string[] texts, int xCols, pa if (IsNarrow) xCols = Math.Min(4, xCols); var sel = selected; - var titles = texts.Select((a, i) => i == sel ? a.orange().bold() : a); + var titles = texts.Select((a, i) => i == sel ? a.Orange().Bold() : a); if (xCols <= 0) xCols = texts.Count(); selected = GL.SelectionGrid(selected, titles.ToArray(), xCols, options); @@ -25,7 +26,7 @@ public static bool SelectionGrid(ref int selected, string[] texts, int xCols, pa } public static bool SelectionGrid(string title, ref int selected, string[] texts, int xCols, params GUILayoutOption[] options) { using (HorizontalScope()) { - Label(title.cyan(), Width(300)); + Label(title.Cyan(), Width(300)); Space(25); return SelectionGrid(ref selected, texts, xCols, options); } @@ -36,7 +37,7 @@ public static bool SelectionGrid(ref int selected, string[] texts, int xCols, GU if (IsNarrow) xCols = Math.Min(4, xCols); var sel = selected; - var titles = texts.Select((a, i) => i == sel ? a.orange().bold() : a); + var titles = texts.Select((a, i) => i == sel ? a.Orange().Bold() : a); if (xCols <= 0) xCols = texts.Count(); selected = GL.SelectionGrid(selected, titles.ToArray(), xCols, style, options); @@ -48,7 +49,7 @@ public static bool SelectionGrid<T>(ref int selected, T[] items, int xCols, para if (IsNarrow) xCols = Math.Min(4, xCols); var sel = selected; - var titles = items.Select((a, i) => i == sel ? $"{a}".orange().bold() : $"{a}"); + var titles = items.Select((a, i) => i == sel ? $"{a}".Orange().Bold() : $"{a}"); if (xCols <= 0) xCols = items.Count(); selected = GL.SelectionGrid(selected, titles.ToArray(), xCols, options); @@ -60,7 +61,7 @@ public static bool SelectionGrid<T>(ref int selected, T[] items, int xCols, GUIS if (IsNarrow) xCols = Math.Min(4, xCols); var sel = selected; - var titles = items.Select((a, i) => i == sel ? $"{a}".orange().bold() : $"{a}"); + var titles = items.Select((a, i) => i == sel ? $"{a}".Orange().Bold() : $"{a}"); if (xCols <= 0) xCols = items.Count(); selected = GL.SelectionGrid(selected, titles.ToArray(), xCols, style, options); @@ -68,18 +69,22 @@ public static bool SelectionGrid<T>(ref int selected, T[] items, int xCols, GUIS } public static void ActionSelectionGrid(ref int selected, string[] texts, int xCols, Action<int> action, params GUILayoutOption[] options) { var sel = selected; - var titles = texts.Select((a, i) => i == sel ? a.orange().bold() : a); + var titles = texts.Select((a, i) => i == sel ? a.Orange().Bold() : a); if (xCols <= 0) xCols = texts.Count(); sel = GL.SelectionGrid(selected, titles.ToArray(), xCols, options); if (selected != sel) { selected = sel; - action(selected); + try { + action(selected); + } catch (NullReferenceException) { + //Needed for CharacterPicker.OnCharacterPickerGUI for whatever reason + } } } public static void ActionSelectionGrid(ref int selected, string[] texts, int xCols, Action<int> action, GUIStyle style, params GUILayoutOption[] options) { var sel = selected; - var titles = texts.Select((a, i) => i == sel ? a.orange().bold() : a); + var titles = texts.Select((a, i) => i == sel ? a.Orange().Bold() : a); if (xCols <= 0) xCols = texts.Count(); sel = GL.SelectionGrid(selected, titles.ToArray(), xCols, style, options); @@ -93,7 +98,7 @@ public static void ActionSelectionGrid(ref int selected, string[] texts, int xCo public static void EnumGrid<TEnum>(Func<TEnum> get, Action<TEnum> set, int xCols, params GUILayoutOption[] options) where TEnum : struct { var value = get(); - var names = Enum.GetNames(typeof(TEnum)); + var names = Enum.GetNames(typeof(TEnum)).Select(name => name.localize()).ToArray(); var index = Array.IndexOf(names, value.ToString()); if (SelectionGrid(ref index, names, xCols, options)) { if (Enum.TryParse(names[index], out TEnum newValue)) { @@ -101,14 +106,18 @@ public static void EnumGrid<TEnum>(Func<TEnum> get, Action<TEnum> set, int xCols } } } - public static bool EnumGrid<TEnum>(ref TEnum value, int xCols, Func<string, TEnum, string> titleFormater = null, GUIStyle style = null, params GUILayoutOption[] options) where TEnum : struct { + public static bool EnumGrid<TEnum>(ref TEnum value, int xCols, Func<string, TEnum, string?>? titleFormater = null, GUIStyle? style = null, params GUILayoutOption[] options) where TEnum : struct { var changed = false; options = options.AddDefaults(); var names = Enum.GetNames(typeof(TEnum)); var formatedNames = names; - var nameToEnum = value.NameToValueDictionary(); + var enumType = value.GetType(); + var nameToEnum = Enum.GetValues(enumType) + .Cast<TEnum>() + .ToDictionary(e => Enum.GetName(enumType, e), e => e); if (titleFormater != null) formatedNames = names.Select((n) => titleFormater(n, nameToEnum[n])).ToArray(); + formatedNames = formatedNames.Select(n => n.localize()).ToArray(); var index = Array.IndexOf(names, value.ToString()); var oldIndex = index; if (style == null ? SelectionGrid(ref index, formatedNames, xCols, options) : SelectionGrid(ref index, formatedNames, xCols, style, options)) { @@ -119,55 +128,51 @@ public static bool EnumGrid<TEnum>(ref TEnum value, int xCols, Func<string, TEnu } return changed; } - public static bool EnumGrid<TEnum>(ref TEnum value, int xCols, Func<string, TEnum, string> titleFormater = null, params GUILayoutOption[] options) where TEnum : struct => EnumGrid(ref value, xCols, titleFormater, null, options); + public static bool EnumGrid<TEnum>(ref TEnum value, int xCols, Func<string, TEnum, string?>? titleFormater = null, params GUILayoutOption[] options) where TEnum : struct => EnumGrid(ref value, xCols, titleFormater, null, options); public static bool EnumGrid<TEnum>(ref TEnum value, int xCols, params GUILayoutOption[] options) where TEnum : struct => EnumGrid(ref value, xCols, null, options); public static bool EnumGrid<TEnum>(ref TEnum value, params GUILayoutOption[] options) where TEnum : struct => EnumGrid(ref value, 0, null, options); - public static bool EnumGrid<TEnum>(string title, ref TEnum value, int xCols, params GUILayoutOption[] options) where TEnum : struct { + public static bool EnumGrid<TEnum>(string? title, ref TEnum value, int xCols, params GUILayoutOption[] options) where TEnum : struct { var changed = false; using (HorizontalScope()) { - Label(title.cyan(), Width(300)); + Label(title.Cyan(), Width(300)); Space(25); changed = EnumGrid(ref value, xCols, null, options); } return changed; } - public static bool EnumGrid<TEnum>(string title, ref TEnum value, params GUILayoutOption[] options) where TEnum : struct { - return EnumGrid(title, ref value, false, options); - } - public static bool EnumGrid<TEnum>(string title, ref TEnum value, bool shouldLocalize = false, params GUILayoutOption[] options) where TEnum : struct { + public static bool EnumGrid<TEnum>(string? title, ref TEnum value, params GUILayoutOption[] options) where TEnum : struct { var changed = false; - Func<string, TEnum, string> titleFormatter = shouldLocalize ? (name, en) => name.localize() : null; using (HorizontalScope()) { - Label(title.cyan(), Width(300)); + Label(title.Cyan(), Width(300)); Space(25); - changed = EnumGrid(ref value, 0, titleFormatter, options); + changed = EnumGrid(ref value, 0, null, options); } return changed; } - public static bool EnumGrid<TEnum>(string title, ref TEnum value, int xCols, GUIStyle style = null, params GUILayoutOption[] options) where TEnum : struct { + public static bool EnumGrid<TEnum>(string title, ref TEnum value, int xCols, GUIStyle? style = null, params GUILayoutOption[] options) where TEnum : struct { var changed = false; using (HorizontalScope()) { - Label(title.cyan(), Width(300)); + Label(title.Cyan(), Width(300)); Space(25); changed = EnumGrid(ref value, xCols, null, style, options); } return changed; } - public static bool EnumGrid<TEnum>(string title, ref TEnum value, int xCols, Func<string, TEnum, string> titleFormater = null, params GUILayoutOption[] options) where TEnum : struct { + public static bool EnumGrid<TEnum>(string title, ref TEnum value, int xCols, Func<string, TEnum, string?>? titleFormater = null, params GUILayoutOption[] options) where TEnum : struct { var changed = false; using (HorizontalScope()) { - Label(title.cyan(), Width(300)); + Label(title.Cyan(), Width(300)); Space(25); changed = EnumGrid(ref value, xCols, titleFormater, options); } return changed; } - public static bool EnumGrid<TEnum>(string title, ref TEnum value, int xCols, Func<string, TEnum, string> titleFormater = null, GUIStyle style = null, params GUILayoutOption[] options) where TEnum : struct { + public static bool EnumGrid<TEnum>(string title, ref TEnum value, int xCols, Func<string, TEnum, string?>? titleFormater = null, GUIStyle? style = null, params GUILayoutOption[] options) where TEnum : struct { var changed = false; using (HorizontalScope()) { - Label(title.cyan(), Width(300)); + Label(title.Cyan(), Width(300)); Space(25); changed = EnumGrid(ref value, xCols, titleFormater, style, options); } @@ -176,7 +181,7 @@ public static bool EnumGrid<TEnum>(string title, ref TEnum value, int xCols, Fun public static bool EnumGrid<TEnum>(string title, Func<TEnum> get, Action<TEnum> set, params GUILayoutOption[] options) where TEnum : struct { var changed = false; using (HorizontalScope()) { - Label(title.cyan(), Width(300)); + Label(title.Cyan(), Width(300)); Space(25); var value = get(); changed = EnumGrid(ref value, 0, null, options); @@ -189,11 +194,11 @@ public static bool EnumGrid<TEnum>(string title, Func<TEnum> get, Action<TEnum> // EnumerablePicker public static void EnumerablePicker<T>( - string title, + string? title, ref int selected, IEnumerable<T> range, int xCols, - Func<T, string> titleFormater = null, + Func<T, string>? titleFormater = null, params GUILayoutOption[] options ) { if (titleFormater == null) @@ -201,7 +206,7 @@ params GUILayoutOption[] options if (selected > range.Count()) selected = 0; var sel = selected; - var titles = range.Select((a, i) => i == sel ? titleFormater(a).orange().bold() : titleFormater(a)); + var titles = range.Select((a, i) => i == sel ? titleFormater(a).Orange().Bold() : titleFormater(a)); if (xCols > range.Count()) xCols = range.Count(); if (xCols <= 0) @@ -210,9 +215,14 @@ params GUILayoutOption[] options Space(25); selected = GL.SelectionGrid(selected, titles.ToArray(), xCols, options); } - public static NamedFunc<T> TypePicker<T>(string title, ref int selectedIndex, NamedFunc<T>[] items) where T : class { + public static NamedFunc<T> TypePicker<T>(string? title, ref int selectedIndex, NamedFunc<T>[] items, bool shouldLocalize = false) { var sel = selectedIndex; - var titles = items.Select((item, i) => i == sel ? item.name.orange().bold() : item.name).ToArray(); + string[] titles; + if (shouldLocalize) { + titles = items.Select((item, i) => i == sel ? item.name.localize().Orange().Bold() : item.name.localize()).ToArray(); + } else { + titles = items.Select((item, i) => i == sel ? item.name.Orange().Bold() : item.name).ToArray(); + } if (title?.Length > 0) { Label(title); } selectedIndex = GL.SelectionGrid(selectedIndex, titles, 6); return items[selectedIndex]; @@ -221,11 +231,11 @@ public static NamedFunc<T> TypePicker<T>(string title, ref int selectedIndex, Na // GridPicker public static bool GridPicker<T>( - string title, + string? title, ref T selected, List<T> items, string unselectedTitle, - Func<T, string> titler, + Func<T, string?> titler, ref string searchText, int xCols, GUIStyle style, @@ -272,27 +282,26 @@ params GUILayoutOption[] options selectedItemIndex -= 1; selected = selectedItemIndex >= 0 ? items[selectedItemIndex] : null; //if (changed) Mod.Log($"sel index: {selectedItemIndex} sel: {selected}"); - } - else { - Label("No Items".grey(), options); + } else { + Label("No Items".localize().Grey(), options); } return changed; } public static bool GridPicker<T>( - string title, + string? title, ref T selected, List<T> items, string unselectedTitle, - Func<T, string> titler, + Func<T, string?> titler, ref string searchText, int xCols, params GUILayoutOption[] options ) where T : class => GridPicker(title, ref selected, items, unselectedTitle, titler, ref searchText, xCols, buttonStyle, options); public static bool GridPicker<T>( - string title, + string? title, ref T selected, List<T> items, string unselectedTitle, - Func<T, string> titler, + Func<T, string?> titler, ref string searchText, params GUILayoutOption[] options ) where T : class @@ -300,10 +309,10 @@ params GUILayoutOption[] options // VPicker public static bool VPicker<T>( - string title, + string? title, ref T selected, List<T> items, string unselectedTitle, - Func<T, string> titler, + Func<T, string?> titler, ref string searchText, Action extras, GUIStyle style, @@ -320,23 +329,81 @@ params GUILayoutOption[] options return changed; } public static bool VPicker<T>( - string title, + string? title, ref T selected, List<T> items, string unselectedTitle, - Func<T, string> titler, + Func<T, string?> titler, ref string searchText, Action extras, params GUILayoutOption[] options ) where T : class => VPicker(title, ref selected, items, unselectedTitle, titler, ref searchText, extras, buttonStyle, options); public static bool VPicker<T>( - string title, + string? title, ref T selected, List<T> items, string unselectedTitle, - Func<T, string> titler, + Func<T, string?> titler, ref string searchText, params GUILayoutOption[] options ) where T : class => VPicker(title, ref selected, items, unselectedTitle, titler, ref searchText, () => { }, buttonStyle, options); + public static bool PagedVPicker<T>(string? title, + ref T selected, List<T> items, + string unselectedTitle, + Func<T, string?> titler, + ref string searchText, + ref int pageSize, + ref int currentPage, + params GUILayoutOption[] options + ) where T : class { + var text = searchText; + using (VerticalScope(GUI.skin.box)) { + 10.space(); + if (items != null) { + var fittingItems = items.Where(i => i.ToString().ToLower().Contains(text.ToLower())); + var itemCount = fittingItems.Count(); + var totalPages = (int)Math.Ceiling((double)itemCount / pageSize); + using (HorizontalScope()) { + Label("Limit".localize(), ExpandWidth(false)); + ActionIntTextField(ref pageSize, "Search Limit".localize(), null, null, 80.width()); + } + using (HorizontalScope()) { + if (pageSize < 1) { + pageSize = 1; + } else if (pageSize > 1000) { + pageSize = 1000; + } + if (currentPage > totalPages || currentPage < 1) currentPage = 1; + string pageLabel = "Page: ".localize().Orange() + currentPage.ToString().Cyan() + " / " + totalPages.ToString().Cyan(); + Label(pageLabel, ExpandWidth(false)); + var maybeNewPage = currentPage; + ActionButton("-", () => { + if (maybeNewPage >= 1) { + if (maybeNewPage == 1) { + maybeNewPage = totalPages; + } else { + maybeNewPage -= 1; + } + } + }, AutoWidth()); + ActionButton("+", () => { + if (maybeNewPage >= totalPages) { + maybeNewPage = 1; + } else { + maybeNewPage += 1; + } + }, AutoWidth()); + currentPage = maybeNewPage; + } + var offset = Math.Min(itemCount, (currentPage - 1) * pageSize); + var limit = Math.Min(pageSize, Math.Max(itemCount, itemCount - pageSize)); + var empty = string.Empty; + return VPicker(title, ref selected, fittingItems.ToList().Skip(offset).Take(limit).ToList(), unselectedTitle, titler, ref searchText, options); + } else { + return VPicker(title, ref selected, items, unselectedTitle, titler, ref searchText, options); + } + } + + } } } diff --git a/ModKit/UI/UI+Toggles.cs b/ToyBox/Classes/ModKit/UI/UI+Toggles.cs similarity index 82% rename from ModKit/UI/UI+Toggles.cs rename to ToyBox/Classes/ModKit/UI/UI+Toggles.cs index ec4bfbfda..3c90b24c0 100644 --- a/ModKit/UI/UI+Toggles.cs +++ b/ToyBox/Classes/ModKit/UI/UI+Toggles.cs @@ -24,7 +24,7 @@ public static ToggleState Flip(this ToggleState state) { }; } private static bool TogglePrivate( - string title, + string? title, ref bool value, bool isEmpty, bool disclosureStyle = false, @@ -34,19 +34,18 @@ params GUILayoutOption[] options options = options.AddDefaults(); var changed = false; if (width == 0 && !disclosureStyle) { - width = toggleStyle.CalcSize(new GUIContent(title.bold())).x + GUI.skin.box.CalcSize(Private.UI.CheckOn).x + 10; + width = toggleStyle.CalcSize(new GUIContent(title.Bold())).x + GUI.skin.box.CalcSize(Private.UI.CheckOnContent).x + 10; } options = options.AddItem(width == 0 ? AutoWidth() : Width(width)).ToArray(); if (!disclosureStyle) { - title = value ? title.bold() : title.color(RGBA.medgrey).bold(); + title = value ? title.Bold() : title.Color(RGBA.medgrey).Bold(); if (Private.UI.CheckBox(title, value, isEmpty, toggleStyle, options)) { value = !value; changed = true; } - } - else { + } else { if (Private.UI.DisclosureToggle(title, value, isEmpty, options)) { value = !value; changed = true; } } return changed; } - public static bool ToggleButton(ref ToggleState toggle, string title, GUIStyle style = null, params GUILayoutOption[] options) { + public static bool ToggleButton(ref ToggleState toggle, string? title, GUIStyle? style = null, params GUILayoutOption[] options) { var isOn = toggle.IsOn(); var isEmpty = toggle == ToggleState.None; var changed = false; @@ -56,7 +55,7 @@ public static bool ToggleButton(ref ToggleState toggle, string title, GUIStyle s } return changed; } - public static bool ToggleButton(ref ToggleState toggle, string title, params GUILayoutOption[] options) { + public static bool ToggleButton(ref ToggleState toggle, string? title, params GUILayoutOption[] options) { var isOn = toggle.IsOn(); var isEmpty = toggle == ToggleState.None; var changed = false; @@ -66,7 +65,7 @@ public static bool ToggleButton(ref ToggleState toggle, string title, params GUI } return changed; } - public static void ToggleButton(ref ToggleState toggle, string title, Action<ToggleState> applyToChildren, params GUILayoutOption[] options) { + public static void ToggleButton(ref ToggleState toggle, string? title, Action<ToggleState> applyToChildren, params GUILayoutOption[] options) { var isOn = toggle.IsOn(); var isEmpty = toggle == ToggleState.None; var state = toggle; @@ -90,28 +89,28 @@ public static void ToggleButton(ref ToggleState toggle, string title, Action<Tog toggle = state; } - public static bool Toggle(string title, ref bool value, string on, string off, float width = 0, GUIStyle stateStyle = null, GUIStyle labelStyle = null, params GUILayoutOption[] options) { + public static bool Toggle(string? title, ref bool value, string? on, string? off, float width = 0, GUIStyle? stateStyle = null, GUIStyle? labelStyle = null, params GUILayoutOption[] options) { var changed = false; if (stateStyle == null) stateStyle = GUI.skin.box; if (labelStyle == null) labelStyle = GUI.skin.box; if (width == 0) { - width = toggleStyle.CalcSize(new GUIContent(title.bold())).x + GUI.skin.box.CalcSize(Private.UI.CheckOn).x + 10; + width = toggleStyle.CalcSize(new GUIContent(title.Bold())).x + GUI.skin.box.CalcSize(Private.UI.CheckOnContent).x + 10; } options = options.AddItem(width == 0 ? AutoWidth() : Width(width)).ToArray(); - title = value ? title.bold() : title.color(RGBA.medgrey).bold(); + title = value ? title.Bold() : title.Color(RGBA.medgrey).Bold(); if (Private.UI.Toggle(title, value, on, off, stateStyle, labelStyle, options)) { value = !value; changed = true; } return changed; } - public static bool Toggle(string title, ref bool value, params GUILayoutOption[] options) { + public static bool Toggle(string? title, ref bool value, params GUILayoutOption[] options) { options = options.AddDefaults(); var changed = false; if (Private.UI.CheckBox(title, value, false, toggleStyle, options)) { value = !value; changed = true; } return changed; } public static bool ActionToggle( - string title, + string? title, Func<bool> get, Action<bool> set, float width = 0, @@ -124,7 +123,7 @@ public static bool ActionToggle( } public static bool ActionToggle( - string title, + string? title, Func<bool> get, Action<bool> set, Func<bool> isEmpty, @@ -139,7 +138,7 @@ public static bool ActionToggle( return value; } public static bool ToggleCallback( - string title, + string? title, ref bool value, Action<bool> callback, float width = 0, @@ -152,7 +151,7 @@ public static bool ToggleCallback( return result; } public static bool BitFieldToggle( - string title, + string? title, ref int bitfield, int offset, float width = 0, @@ -164,25 +163,24 @@ params GUILayoutOption[] options if (bit != newBit) { bitfield ^= 1 << offset; } return bit != newBit; } - public static bool DisclosureToggle(string title, ref bool value, float width = 175, params Action[] actions) { + public static bool DisclosureToggle(string? title, ref bool value, float width = 175, params Action[] actions) { var changed = TogglePrivate(title, ref value, false, true, width); If(value, actions); return changed; } - public static bool DisclosureToggle(string title, ref bool value, params Action[] actions) { + public static bool DisclosureToggle(string? title, ref bool value, params Action[] actions) { var changed = TogglePrivate(title, ref value, false, true, 175); If(value, actions); return changed; } - public static bool DisclosureBitFieldToggle(string title, ref int bitfield, int offset, bool exclusive = true, float width = 175, params Action[] actions) { + public static bool DisclosureBitFieldToggle(string? title, ref int bitfield, int offset, bool exclusive = true, float width = 175, params Action[] actions) { var bit = ((1 << offset) & bitfield) != 0; var newBit = bit; TogglePrivate(title, ref newBit, false, true, width); if (bit != newBit) { if (exclusive) { bitfield = (newBit ? 1 << offset : 0); - } - else { + } else { bitfield ^= (1 << offset); } } diff --git a/ModKit/UI/UI+Wrappers.cs b/ToyBox/Classes/ModKit/UI/UI+Wrappers.cs similarity index 98% rename from ModKit/UI/UI+Wrappers.cs rename to ToyBox/Classes/ModKit/UI/UI+Wrappers.cs index 4a1e1c733..fd0017fc3 100644 --- a/ModKit/UI/UI+Wrappers.cs +++ b/ToyBox/Classes/ModKit/UI/UI+Wrappers.cs @@ -19,6 +19,7 @@ public static partial class UI { public static GUILayoutOption[] Width(float min, float max) => new GUILayoutOption[] { GL.MinWidth(min), GL.MaxWidth(max) }; public static GUILayoutOption[] Height(float min, float max) => new GUILayoutOption[] { GL.MinHeight(min), GL.MaxHeight(max) }; public static GUILayoutOption Height(float v) => GL.Height(v); + public static GUILayoutOption height(this int v) => GL.Height(v); public static GUILayoutOption MaxWidth(float v) => GL.MaxWidth(v); public static GUILayoutOption MaxHeight(float v) => GL.MaxHeight(v); public static GUILayoutOption MinWidth(float v) => GL.MinWidth(v); diff --git a/ToyBox/classes/Infrastructure/UIHelpers.cs b/ToyBox/Classes/ModKit/UI/UIHelpers.cs similarity index 92% rename from ToyBox/classes/Infrastructure/UIHelpers.cs rename to ToyBox/Classes/ModKit/UI/UIHelpers.cs index 5b2c57b52..fd19e3ccc 100644 --- a/ToyBox/classes/Infrastructure/UIHelpers.cs +++ b/ToyBox/Classes/ModKit/UI/UIHelpers.cs @@ -1,6 +1,4 @@ -using Kingmaker.Localization; -using Kingmaker.Utility; -using System; +using System; using System.Linq; using TMPro; using UnityEngine; @@ -43,10 +41,12 @@ public static GameObject[] ChildObjects(this GameObject obj, params string[] pat } public static void DestroyChildren(this GameObject obj, params string[] paths) { - obj.ChildObjects(paths).ForEach(UnityEngine.Object.Destroy); + foreach (var doomed in obj.ChildObjects(paths)) + UnityEngine.Object.Destroy(doomed); } public static void DestroyChildrenImmediate(this GameObject obj, params string[] paths) { - obj.ChildObjects(paths).ForEach(UnityEngine.Object.DestroyImmediate); + foreach (var doomed in obj.ChildObjects(paths)) + UnityEngine.Object.DestroyImmediate(doomed); } public static void DestroyComponents<T>(this GameObject obj) where T : UnityEngine.Object { @@ -97,7 +97,7 @@ public static void FillParent(this GameObject obj) { public static void AddTo(this GameObject obj, Transform parent) { obj.transform.AddTo(parent); } public static void AddTo(this GameObject obj, GameObject parent) { obj.transform.AddTo(parent.transform); } - public static (GameObject, RectTransform) Create(string name, Transform parent = null) { + public static (GameObject, RectTransform) Create(string name, Transform? parent = null) { var obj = new GameObject(name, typeof(RectTransform)); if (parent != null) obj.AddTo(parent); @@ -151,8 +151,5 @@ public static void AddSuffix(this TextMeshProUGUI label, string suffix, char del label.text = label.text.Split('(').FirstOrDefault().Trim(); } - public static void AddLocalizedString(this string value) => LocalizationManager.CurrentPack.PutString(value, value); - public static LocalizedString LocalizedStringInGame(this string key) => new LocalizedString() { Key = key }; - } } diff --git a/ModKit/Utility/Extensions/CodeInstructionExtensions.cs b/ToyBox/Classes/ModKit/Utility/Extensions/CodeInstructionExtensions.cs similarity index 95% rename from ModKit/Utility/Extensions/CodeInstructionExtensions.cs rename to ToyBox/Classes/ModKit/Utility/Extensions/CodeInstructionExtensions.cs index bd9965962..cd320a821 100644 --- a/ModKit/Utility/Extensions/CodeInstructionExtensions.cs +++ b/ToyBox/Classes/ModKit/Utility/Extensions/CodeInstructionExtensions.cs @@ -140,19 +140,19 @@ public static IEnumerable<CodeInstruction> ReplaceRange(this IEnumerable<CodeIns public static IEnumerable<CodeInstruction> ReplaceAll(this IEnumerable<CodeInstruction> codes, CodeInstruction findingCode, CodeInstruction newCode, - bool moveLabelsFromIndex = false, IEqualityComparer<CodeInstruction> comparer = null) => codes.ReplaceAll(findingCode, newCode, out _, moveLabelsFromIndex, comparer); + bool moveLabelsFromIndex = false, IEqualityComparer<CodeInstruction>? comparer = null) => codes.ReplaceAll(findingCode, newCode, out _, moveLabelsFromIndex, comparer); public static IEnumerable<CodeInstruction> ReplaceAll(this IEnumerable<CodeInstruction> codes, CodeInstruction findingCode, CodeInstruction newCode, - out int replaced, bool moveLabelsFromIndex = false, IEqualityComparer<CodeInstruction> comparer = null) => codes.ReplaceAll(new CodeInstruction[] { findingCode }, new CodeInstruction[] { newCode }, out replaced, moveLabelsFromIndex, comparer); + out int replaced, bool moveLabelsFromIndex = false, IEqualityComparer<CodeInstruction>? comparer = null) => codes.ReplaceAll(new CodeInstruction[] { findingCode }, new CodeInstruction[] { newCode }, out replaced, moveLabelsFromIndex, comparer); public static IEnumerable<CodeInstruction> ReplaceAll(this IEnumerable<CodeInstruction> codes, IEnumerable<CodeInstruction> findingCodes, IEnumerable<CodeInstruction> newCodes, - bool moveLabelsFromIndex = false, IEqualityComparer<CodeInstruction> comparer = null) => codes.ReplaceAll(findingCodes, newCodes, out _, moveLabelsFromIndex, comparer); + bool moveLabelsFromIndex = false, IEqualityComparer<CodeInstruction>? comparer = null) => codes.ReplaceAll(findingCodes, newCodes, out _, moveLabelsFromIndex, comparer); public static IEnumerable<CodeInstruction> ReplaceAll(this IEnumerable<CodeInstruction> codes, IEnumerable<CodeInstruction> findingCodes, IEnumerable<CodeInstruction> newCodes, - out int replaced, bool moveLabelsFromIndex = false, IEqualityComparer<CodeInstruction> comparer = null) { + out int replaced, bool moveLabelsFromIndex = false, IEqualityComparer<CodeInstruction>? comparer = null) { replaced = 0; if (comparer == null) comparer = new CodeInstructionMatchComparer(); @@ -169,8 +169,7 @@ public static IEnumerable<CodeInstruction> ReplaceAll(this IEnumerable<CodeInstr codes.RemoveRange(i, findingCodesCount, moveLabelsFromIndex); replaced++; i -= findingCodesCount; - } - else { + } else { i--; } } @@ -218,8 +217,7 @@ public static void MoveLabels(this IEnumerable<CodeInstruction> codes, while (i < source.Count) { if (skip.Contains(source[i])) { i++; - } - else { + } else { target.Add(source[i]); source.RemoveAt(i); } @@ -237,22 +235,22 @@ public static void RemoveLabel(this IEnumerable<CodeInstruction> codes, int inde #region Exception Block - public static CodeInstruction BeginCatchBlock(this CodeInstruction code, Type catchType = null) { + public static CodeInstruction BeginCatchBlock(this CodeInstruction code, Type? catchType = null) { code.blocks.Add(new ExceptionBlock(ExceptionBlockType.BeginCatchBlock, catchType ?? typeof(object))); return code; } - public static CodeInstruction BeginExceptionBlock(this CodeInstruction code, Type catchType = null) { + public static CodeInstruction BeginExceptionBlock(this CodeInstruction code, Type? catchType = null) { code.blocks.Add(new ExceptionBlock(ExceptionBlockType.BeginExceptionBlock, catchType ?? typeof(object))); return code; } - public static CodeInstruction BeginFinallyBlock(this CodeInstruction code, Type catchType = null) { + public static CodeInstruction BeginFinallyBlock(this CodeInstruction code, Type? catchType = null) { code.blocks.Add(new ExceptionBlock(ExceptionBlockType.BeginFinallyBlock, catchType ?? typeof(object))); return code; } - public static CodeInstruction EndExceptionBlock(this CodeInstruction code, Type catchType = null) { + public static CodeInstruction EndExceptionBlock(this CodeInstruction code, Type? catchType = null) { code.blocks.Add(new ExceptionBlock(ExceptionBlockType.EndExceptionBlock, catchType ?? typeof(object))); return code; } @@ -308,7 +306,7 @@ public bool Equals(CodeInstruction x, CodeInstruction y) { return true; else if (x == null) return false; - else if ((y.opcode == null || OpCodeEquals(y.opcode, x.opcode)) && + else if ((y.opcode == default || OpCodeEquals(y.opcode, x.opcode)) && (y.operand == null || (y.operand is ValueType ? y.operand.Equals(x.operand) : y.operand == x.operand)) && (y.labels.Count == 0 || y.labels.TrueForAll(label => x.labels.Contains(label)))) return true; diff --git a/ToyBox/Classes/ModKit/Utility/Extensions/MiscExtensions.cs b/ToyBox/Classes/ModKit/Utility/Extensions/MiscExtensions.cs new file mode 100644 index 000000000..47fb691ca --- /dev/null +++ b/ToyBox/Classes/ModKit/Utility/Extensions/MiscExtensions.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Unity.Collections; +using UnityEngine.Rendering; +using UnityEngine; + +namespace ModKit.Utility.Extensions { + public static class MiscExtensions { + // Takes the last N objects of the source collection + public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> source, int N) { + return source.Skip(Math.Max(0, source.Count() - N)); + } + // Creates readable collection content string + public static string ToContentString(this IEnumerable enumerable) { + return InternalToContentString(enumerable); + } + private static string InternalToContentString(object obj) { + if (obj == null) { + return "null"; + } + + if (obj is string str) { + return $"\"{str}\""; + } + + if (obj is IEnumerable enumerable && !(obj is IDictionary)) { + var elements = new List<string>(); + + foreach (var item in enumerable) { + elements.Add(InternalToContentString(item)); + } + + return "[" + string.Join(", ", elements) + "]"; + } + + if (obj is IDictionary dictionary) { + var elements = new List<string>(); + + foreach (DictionaryEntry entry in dictionary) { + elements.Add($"{InternalToContentString(entry.Key)}: {InternalToContentString(entry.Value)}"); + } + + return "{" + string.Join(", ", elements) + "}"; + } + + return obj.ToString(); + } + public enum SaveTextureFileFormat { + PNG, + JPG, + EXR, + TGA + } + public static void SaveTextureToFile(this Texture source, string filePath, int width, int height, SaveTextureFileFormat fileFormat = SaveTextureFileFormat.PNG, int jpgQuality = 95, bool asynchronous = true, System.Action<bool> done = null) { + // check that the input we're getting is something we can handle: + if (!(source is Texture2D || source is RenderTexture)) { + done?.Invoke(false); + return; + } + + // use the original texture size in case the input is negative: + if (width < 0 || height < 0) { + width = source.width; + height = source.height; + } + + // resize the original image: + var resizeRT = RenderTexture.GetTemporary(width, height, 0); + Graphics.Blit(source, resizeRT); + + // create a native array to receive data from the GPU: + var narray = new NativeArray<byte>(width * height * 4, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); + + // request the texture data back from the GPU: + var request = AsyncGPUReadback.RequestIntoNativeArray(ref narray, resizeRT, 0, (AsyncGPUReadbackRequest request) => { + // if the readback was successful, encode and write the results to disk + if (!request.hasError) { + NativeArray<byte> encoded; + + switch (fileFormat) { + case SaveTextureFileFormat.EXR: + encoded = ImageConversion.EncodeNativeArrayToEXR(narray, resizeRT.graphicsFormat, (uint)width, (uint)height); + break; + case SaveTextureFileFormat.JPG: + encoded = ImageConversion.EncodeNativeArrayToJPG(narray, resizeRT.graphicsFormat, (uint)width, (uint)height, 0, jpgQuality); + break; + case SaveTextureFileFormat.TGA: + encoded = ImageConversion.EncodeNativeArrayToTGA(narray, resizeRT.graphicsFormat, (uint)width, (uint)height); + break; + default: + encoded = ImageConversion.EncodeNativeArrayToPNG(narray, resizeRT.graphicsFormat, (uint)width, (uint)height); + break; + } + + System.IO.File.WriteAllBytes(filePath, encoded.ToArray()); + encoded.Dispose(); + } + + narray.Dispose(); + + // notify the user that the operation is done, and its outcome. + done?.Invoke(!request.hasError); + }); + + if (!asynchronous) + request.WaitForCompletion(); + } + } +} diff --git a/ModKit/Utility/NamedTypes.cs b/ToyBox/Classes/ModKit/Utility/NamedTypes.cs similarity index 74% rename from ModKit/Utility/NamedTypes.cs rename to ToyBox/Classes/ModKit/Utility/NamedTypes.cs index a22906a05..e81767dd0 100644 --- a/ModKit/Utility/NamedTypes.cs +++ b/ToyBox/Classes/ModKit/Utility/NamedTypes.cs @@ -3,20 +3,20 @@ namespace ModKit { public class NamedAction { - public string name { get; } + public string? name { get; } public Action action { get; } public Func<bool> canPerform { get; } - public NamedAction(string name, Action action, Func<bool> canPerform = null) { + public NamedAction(string? name, Action action, Func<bool>? canPerform = null) { this.name = name; this.action = action; this.canPerform = canPerform ?? (() => { return true; }); } } public class NamedAction<T> { - public string name { get; } + public string? name { get; } public Action<T> action { get; } public Func<T, bool> canPerform { get; } - public NamedAction(string name, Action<T> action, Func<T, bool> canPerform = null) { + public NamedAction(string? name, Action<T> action, Func<T, bool>? canPerform = null) { this.name = name; this.action = action; this.canPerform = canPerform ?? ((T) => { return true; }); @@ -24,10 +24,10 @@ public NamedAction(string name, Action<T> action, Func<T, bool> canPerform = nul } public class NamedFunc<T> { - public string name { get; } + public string? name { get; } public Func<T> func { get; } public Func<bool> canPerform { get; } - public NamedFunc(string name, Func<T> func, Func<bool> canPerform = null) { + public NamedFunc(string? name, Func<T> func, Func<bool>? canPerform = null) { this.name = name; this.func = func; this.canPerform = canPerform ?? (() => { return true; }); @@ -35,14 +35,14 @@ public NamedFunc(string name, Func<T> func, Func<bool> canPerform = null) { } public class NamedMutator<Target, T> { - public string name { get; } + public string? name { get; } public Action<Target, T, int> action { get; } public Func<Target, T, bool> canPerform { get; } public bool isRepeatable { get; } public NamedMutator( - string name, + string? name, Action<Target, T, int> action, - Func<Target, T, bool> canPerform = null, + Func<Target, T, bool>? canPerform = null, bool isRepeatable = false ) { this.name = name; diff --git a/ModKit/Utility/Reflection/ReflectionCache.cs b/ToyBox/Classes/ModKit/Utility/Reflection/ReflectionCache.cs similarity index 100% rename from ModKit/Utility/Reflection/ReflectionCache.cs rename to ToyBox/Classes/ModKit/Utility/Reflection/ReflectionCache.cs diff --git a/ModKit/Utility/Reflection/ReflectionFieldCache.cs b/ToyBox/Classes/ModKit/Utility/Reflection/ReflectionFieldCache.cs similarity index 81% rename from ModKit/Utility/Reflection/ReflectionFieldCache.cs rename to ToyBox/Classes/ModKit/Utility/Reflection/ReflectionFieldCache.cs index 38bcccf71..2043eef3e 100644 --- a/ModKit/Utility/Reflection/ReflectionFieldCache.cs +++ b/ToyBox/Classes/ModKit/Utility/Reflection/ReflectionFieldCache.cs @@ -1,30 +1,31 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; namespace ModKit.Utility { public static partial class ReflectionCache { - private static readonly DoubleDictionary<Type, string, WeakReference> _fieldCache = new(); + private static readonly Dictionary<(Type, string), WeakReference> _fieldCache = new(); - private static CachedField<TField> GetFieldCache<T, TField>(string name) { + private static CachedField<TField> GetFieldCache<T, TField>(string? name) { object cache = default; - if (_fieldCache.TryGetValue(typeof(T), name, out var weakRef)) + if (_fieldCache.TryGetValue((typeof(T), name), out var weakRef)) cache = weakRef.Target; if (cache == null) { if (typeof(T).IsValueType) cache = new CachedFieldOfStruct<T, TField>(name); else cache = new CachedFieldOfClass<T, TField>(name); - _fieldCache[typeof(T), name] = new WeakReference(cache); + _fieldCache[(typeof(T), name)] = new WeakReference(cache); EnqueueCache(cache); } return cache as CachedField<TField>; } - private static CachedField<TField> GetFieldCache<TField>(Type type, string name) { + private static CachedField<TField> GetFieldCache<TField>(Type type, string? name) { object cache = null; - if (_fieldCache.TryGetValue(type, name, out var weakRef)) + if (_fieldCache.TryGetValue((type, name), out var weakRef)) cache = weakRef.Target; if (cache == null) { cache = @@ -33,40 +34,40 @@ private static CachedField<TField> GetFieldCache<TField>(Type type, string name) type.IsValueType ? Activator.CreateInstance(typeof(CachedFieldOfStruct<,>).MakeGenericType(type, typeof(TField)), name) : Activator.CreateInstance(typeof(CachedFieldOfClass<,>).MakeGenericType(type, typeof(TField)), name); - _fieldCache[type, name] = new WeakReference(cache); + _fieldCache[(type, name)] = new WeakReference(cache); EnqueueCache(cache); } return cache as CachedField<TField>; } - public static FieldInfo GetFieldInfo<T, TField>(string name) => GetFieldCache<T, TField>(name).Info; + public static FieldInfo GetFieldInfo<T, TField>(string? name) => GetFieldCache<T, TField>(name).Info; - public static FieldInfo GetFieldInfo<TField>(this Type type, string name) => GetFieldCache<TField>(type, name).Info; + public static FieldInfo GetFieldInfo<TField>(this Type type, string? name) => GetFieldCache<TField>(type, name).Info; - public static ref TField GetFieldRef<T, TField>(this ref T instance, string name) where T : struct => ref (GetFieldCache<T, TField>(name) as CachedFieldOfStruct<T, TField>).GetRef(ref instance); + public static ref TField GetFieldRef<T, TField>(this ref T instance, string? name) where T : struct => ref (GetFieldCache<T, TField>(name) as CachedFieldOfStruct<T, TField>).GetRef(ref instance); - public static ref TField GetFieldRef<T, TField>(this T instance, string name) where T : class => ref (GetFieldCache<T, TField>(name) as CachedFieldOfClass<T, TField>).GetRef(instance); + public static ref TField GetFieldRef<T, TField>(this T instance, string? name) where T : class => ref (GetFieldCache<T, TField>(name) as CachedFieldOfClass<T, TField>).GetRef(instance); - public static TField GetFieldValue<T, TField>(this ref T instance, string name) where T : struct => (GetFieldCache<T, TField>(name) as CachedFieldOfStruct<T, TField>).Get(ref instance); + public static TField GetFieldValue<T, TField>(this ref T instance, string? name) where T : struct => (GetFieldCache<T, TField>(name) as CachedFieldOfStruct<T, TField>).Get(ref instance); - public static TField GetFieldValue<T, TField>(this T instance, string name) where T : class => (GetFieldCache<T, TField>(name) as CachedFieldOfClass<T, TField>).Get(instance); + public static TField GetFieldValue<T, TField>(this T instance, string? name) where T : class => (GetFieldCache<T, TField>(name) as CachedFieldOfClass<T, TField>).Get(instance); - public static TField GetFieldValue<T, TField>(string name) => GetFieldCache<T, TField>(name).Get(); + public static TField GetFieldValue<T, TField>(string? name) => GetFieldCache<T, TField>(name).Get(); - public static TField GetFieldValue<TField>(this Type type, string name) => GetFieldCache<TField>(type, name).Get(); + public static TField GetFieldValue<TField>(this Type type, string? name) => GetFieldCache<TField>(type, name).Get(); - public static void SetFieldValue<T, TField>(this ref T instance, string name, TField value) where T : struct => (GetFieldCache<T, TField>(name) as CachedFieldOfStruct<T, TField>).Set(ref instance, value); + public static void SetFieldValue<T, TField>(this ref T instance, string? name, TField value) where T : struct => (GetFieldCache<T, TField>(name) as CachedFieldOfStruct<T, TField>).Set(ref instance, value); - public static void SetFieldValue<T, TField>(this T instance, string name, TField value) where T : class => (GetFieldCache<T, TField>(name) as CachedFieldOfClass<T, TField>).Set(instance, value); + public static void SetFieldValue<T, TField>(this T instance, string? name, TField value) where T : class => (GetFieldCache<T, TField>(name) as CachedFieldOfClass<T, TField>).Set(instance, value); - public static void SetFieldValue<T, TField>(string name, TField value) => GetFieldCache<T, TField>(name).Set(value); + public static void SetFieldValue<T, TField>(string? name, TField value) => GetFieldCache<T, TField>(name).Set(value); - public static void SetFieldValue<TField>(this Type type, string name, TField value) => GetFieldCache<TField>(type, name).Set(value); + public static void SetFieldValue<TField>(this Type type, string? name, TField value) => GetFieldCache<TField>(type, name).Set(value); private abstract class CachedField<TField> { public readonly FieldInfo Info; - public CachedField(Type type, string name) { + public CachedField(Type type, string? name) { Info = type.GetFields(ALL_FLAGS).FirstOrDefault(item => item.Name == name); if (Info == null || Info.FieldType != typeof(TField)) @@ -91,8 +92,7 @@ protected Delegate CreateGetter(Type delType, bool isInstByRef) { var il = method.GetILGenerator(); if (Info.IsStatic) { il.Emit(OpCodes.Ldsfld, Info); - } - else { + } else { il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldfld, Info); } @@ -114,8 +114,7 @@ protected Delegate CreateRefGetter(Type delType, bool isInstByRef) { var il = methodBuilder.GetILGenerator(); if (Info.IsStatic) { il.Emit(OpCodes.Ldsflda, Info); - } - else { + } else { il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldflda, Info); } @@ -141,8 +140,7 @@ protected Delegate CreateSetter(Type delType, bool isInstByRef) { if (Info.IsStatic) { il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Stsfld, Info); - } - else { + } else { il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Stfld, Info); @@ -163,7 +161,7 @@ private class CachedFieldOfStruct<T, TField> : CachedField<TField> { private RefGetter _refGetter; private Setter _setter; - public CachedFieldOfStruct(string name) : base(typeof(T), name) { } + public CachedFieldOfStruct(string? name) : base(typeof(T), name) { } public override TField Get() => (_getter ??= CreateGetter(typeof(Getter), true) as Getter)(ref _dummy); @@ -186,7 +184,7 @@ private class CachedFieldOfClass<T, TField> : CachedField<TField> { private RefGetter _refGetter; private Setter _setter; - public CachedFieldOfClass(string name) : base(typeof(T), name) { } + public CachedFieldOfClass(string? name) : base(typeof(T), name) { } public override TField Get() => (_getter ??= CreateGetter(typeof(Getter), false) as Getter)(_dummy); @@ -206,7 +204,7 @@ private class CachedFieldOfStatic<TField> : CachedField<TField> { private Getter _getter; private Setter _setter; - public CachedFieldOfStatic(Type type, string name) : base(type, name) { + public CachedFieldOfStatic(Type type, string? name) : base(type, name) { //if (!IsStatic(type)) // throw new InvalidOperationException(); } diff --git a/ModKit/Utility/Reflection/ReflectionMethodCache.cs b/ToyBox/Classes/ModKit/Utility/Reflection/ReflectionMethodCache.cs similarity index 93% rename from ModKit/Utility/Reflection/ReflectionMethodCache.cs rename to ToyBox/Classes/ModKit/Utility/Reflection/ReflectionMethodCache.cs index ea845f509..536007d0e 100644 --- a/ModKit/Utility/Reflection/ReflectionMethodCache.cs +++ b/ToyBox/Classes/ModKit/Utility/Reflection/ReflectionMethodCache.cs @@ -43,15 +43,15 @@ public static partial class ReflectionCache { typeof(Func<,,,,,,,,,,,,,,,,>) }; - private static readonly TripleDictionary<Type, string, Type, WeakReference> _methodCache = new(); + private static readonly Dictionary<(Type, string, Type), WeakReference> _methodCache = new(); private static CachedMethod<TMethod> GetMethodCache<T, TMethod>(string name) where TMethod : Delegate { object cache = null; - if (_methodCache.TryGetValue(typeof(T), name, typeof(TMethod), out var weakRef)) + if (_methodCache.TryGetValue((typeof(T), name, typeof(TMethod)), out var weakRef)) cache = weakRef.Target; if (cache == null) { cache = new CachedMethodOfNonStatic<T, TMethod>(name); - _methodCache[typeof(T), name, typeof(TMethod)] = new WeakReference(cache); + _methodCache[(typeof(T), name, typeof(TMethod))] = new WeakReference(cache); EnqueueCache(cache); } return cache as CachedMethod<TMethod>; @@ -59,14 +59,14 @@ private static CachedMethod<TMethod> GetMethodCache<T, TMethod>(string name) whe private static CachedMethod<TMethod> GetMethodCache<TMethod>(Type type, string name) where TMethod : Delegate { object cache = null; - if (_methodCache.TryGetValue(type, name, typeof(TMethod), out var weakRef)) + if (_methodCache.TryGetValue((type, name, typeof(TMethod)), out var weakRef)) cache = weakRef.Target; if (cache == null) { cache = IsStatic(type) ? Activator.CreateInstance(typeof(CachedMethodOfStatic<>).MakeGenericType(typeof(TMethod)), type, name) : Activator.CreateInstance(typeof(CachedMethodOfNonStatic<,>).MakeGenericType(type, typeof(TMethod)), name); - _methodCache[type, name, typeof(TMethod)] = new WeakReference(cache); + _methodCache[(type, name, typeof(TMethod))] = new WeakReference(cache); EnqueueCache(cache); } return cache as CachedMethod<TMethod>; @@ -99,8 +99,7 @@ protected CachedMethod(Type type, string name, bool hasThis) { if (type.IsValueType) { if (!delParams[0].ParameterType.IsByRef || delParams[0].ParameterType.GetElementType() != type) throw new InvalidOperationException(); - } - else if (delParams[0].ParameterType.IsByRef || delParams[0].ParameterType != type) + } else if (delParams[0].ParameterType.IsByRef || delParams[0].ParameterType != type) throw new InvalidOperationException(); } @@ -118,8 +117,7 @@ protected CachedMethod(Type type, string name, bool hasThis) { if (methods.Count() > 1) throw new AmbiguousMatchException(); Info = methods.FirstOrDefault()?.MakeGenericMethod(delGenericArgs); - } - else { + } else { var delParamTypes = hasThis ? delParams.Select(p => p.ParameterType).Skip(1) : delParams.Select(p => p.ParameterType); @@ -148,8 +146,7 @@ private static bool CheckParamsOfGenericMethod(ParameterInfo[] @params, Paramete if (@params[i].ParameterType != delParams[i].ParameterType) { return false; } - } - else { + } else { if (delGenericArgs[@params[i].ParameterType.GenericParameterPosition] != delParams[i].ParameterType) { return false; } @@ -207,8 +204,7 @@ protected override TMethod CreateDelegate() { for (var i = 1; i <= parameters.Length; i++) il.Emit(OpCodes.Ldarg, i); il.Emit(OpCodes.Call, Info); - } - else { + } else { il.Emit(OpCodes.Ldarg_0); for (var i = 1; i <= parameters.Length; i++) il.Emit(OpCodes.Ldarg, i); diff --git a/ModKit/Utility/Reflection/ReflectionPropertyCache.cs b/ToyBox/Classes/ModKit/Utility/Reflection/ReflectionPropertyCache.cs similarity index 79% rename from ModKit/Utility/Reflection/ReflectionPropertyCache.cs rename to ToyBox/Classes/ModKit/Utility/Reflection/ReflectionPropertyCache.cs index 24d90d761..69ed126a8 100644 --- a/ModKit/Utility/Reflection/ReflectionPropertyCache.cs +++ b/ToyBox/Classes/ModKit/Utility/Reflection/ReflectionPropertyCache.cs @@ -1,30 +1,31 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; namespace ModKit.Utility { public static partial class ReflectionCache { - private static readonly DoubleDictionary<Type, string, WeakReference> _propertieCache = new(); + private static readonly Dictionary<(Type, string), WeakReference> _propertieCache = new(); - private static CachedProperty<TProperty> GetPropertyCache<T, TProperty>(string name) { + private static CachedProperty<TProperty> GetPropertyCache<T, TProperty>(string? name) { object cache = null; - if (_propertieCache.TryGetValue(typeof(T), name, out var weakRef)) + if (_propertieCache.TryGetValue((typeof(T), name), out var weakRef)) cache = weakRef.Target; if (cache == null) { if (typeof(T).IsValueType) cache = new CachedPropertyOfStruct<T, TProperty>(name); else cache = new CachedPropertyOfClass<T, TProperty>(name); - _propertieCache[typeof(T), name] = new WeakReference(cache); + _propertieCache[(typeof(T), name)] = new WeakReference(cache); EnqueueCache(cache); } return cache as CachedProperty<TProperty>; } - private static CachedProperty<TProperty> GetPropertyCache<TProperty>(Type type, string name) { + private static CachedProperty<TProperty> GetPropertyCache<TProperty>(Type type, string? name) { object cache = null; - if (_propertieCache.TryGetValue(type, name, out var weakRef)) + if (_propertieCache.TryGetValue((type, name), out var weakRef)) cache = weakRef.Target; if (cache == null) { cache = @@ -33,36 +34,36 @@ private static CachedProperty<TProperty> GetPropertyCache<TProperty>(Type type, type.IsValueType ? Activator.CreateInstance(typeof(CachedPropertyOfStruct<,>).MakeGenericType(type, typeof(TProperty)), name) : Activator.CreateInstance(typeof(CachedPropertyOfClass<,>).MakeGenericType(type, typeof(TProperty)), name); - _propertieCache[type, name] = new WeakReference(cache); + _propertieCache[(type, name)] = new WeakReference(cache); EnqueueCache(cache); } return cache as CachedProperty<TProperty>; } - public static PropertyInfo GetPropertyInfo<T, TProperty>(string name) => GetPropertyCache<T, TProperty>(name).Info; + public static PropertyInfo GetPropertyInfo<T, TProperty>(string? name) => GetPropertyCache<T, TProperty>(name).Info; - public static PropertyInfo GetPropertyInfo<TProperty>(this Type type, string name) => GetPropertyCache<TProperty>(type, name).Info; + public static PropertyInfo GetPropertyInfo<TProperty>(this Type type, string? name) => GetPropertyCache<TProperty>(type, name).Info; - public static TProperty GetPropertyValue<T, TProperty>(this ref T instance, string name) where T : struct => (GetPropertyCache<T, TProperty>(name) as CachedPropertyOfStruct<T, TProperty>).Get(ref instance); + public static TProperty GetPropertyValue<T, TProperty>(this ref T instance, string? name) where T : struct => (GetPropertyCache<T, TProperty>(name) as CachedPropertyOfStruct<T, TProperty>).Get(ref instance); - public static TProperty GetPropertyValue<T, TProperty>(this T instance, string name) where T : class => (GetPropertyCache<T, TProperty>(name) as CachedPropertyOfClass<T, TProperty>).Get(instance); + public static TProperty GetPropertyValue<T, TProperty>(this T instance, string? name) where T : class => (GetPropertyCache<T, TProperty>(name) as CachedPropertyOfClass<T, TProperty>).Get(instance); - public static TProperty GetPropertyValue<T, TProperty>(string name) => GetPropertyCache<T, TProperty>(name).Get(); + public static TProperty GetPropertyValue<T, TProperty>(string? name) => GetPropertyCache<T, TProperty>(name).Get(); - public static TProperty GetPropertyValue<TProperty>(this Type type, string name) => GetPropertyCache<TProperty>(type, name).Get(); + public static TProperty GetPropertyValue<TProperty>(this Type type, string? name) => GetPropertyCache<TProperty>(type, name).Get(); - public static void SetPropertyValue<T, TProperty>(this ref T instance, string name, TProperty value) where T : struct => (GetPropertyCache<T, TProperty>(name) as CachedPropertyOfStruct<T, TProperty>).Set(ref instance, value); + public static void SetPropertyValue<T, TProperty>(this ref T instance, string? name, TProperty value) where T : struct => (GetPropertyCache<T, TProperty>(name) as CachedPropertyOfStruct<T, TProperty>).Set(ref instance, value); - public static void SetPropertyValue<T, TProperty>(this T instance, string name, TProperty value) where T : class => (GetPropertyCache<T, TProperty>(name) as CachedPropertyOfClass<T, TProperty>).Set(instance, value); + public static void SetPropertyValue<T, TProperty>(this T instance, string? name, TProperty value) where T : class => (GetPropertyCache<T, TProperty>(name) as CachedPropertyOfClass<T, TProperty>).Set(instance, value); - public static void SetPropertyValue<T, TProperty>(string name, TProperty value) => GetPropertyCache<T, TProperty>(name).Set(value); + public static void SetPropertyValue<T, TProperty>(string? name, TProperty value) => GetPropertyCache<T, TProperty>(name).Set(value); - public static void SetPropertyValue<TProperty>(this Type type, string name, TProperty value) => GetPropertyCache<TProperty>(type, name).Set(value); + public static void SetPropertyValue<TProperty>(this Type type, string? name, TProperty value) => GetPropertyCache<TProperty>(type, name).Set(value); private abstract class CachedProperty<TProperty> { public readonly PropertyInfo Info; - protected CachedProperty(Type type, string name) { + protected CachedProperty(Type type, string? name) { Info = type.GetProperties(ALL_FLAGS).FirstOrDefault(item => item.Name == name); if (Info == null || Info.PropertyType != typeof(TProperty)) @@ -90,8 +91,7 @@ protected Delegate CreateGetter(Type delType, MethodInfo getter, bool isInstByRe il.Emit(OpCodes.Call, getter); il.Emit(OpCodes.Ret); return method.CreateDelegate(delType); - } - else { + } else { return Delegate.CreateDelegate(delType, getter); } } @@ -111,8 +111,7 @@ protected Delegate CreateSetter(Type delType, MethodInfo setter, bool isInstByRe il.Emit(OpCodes.Call, setter); il.Emit(OpCodes.Ret); return method.CreateDelegate(delType); - } - else { + } else { return Delegate.CreateDelegate(delType, setter); } } @@ -126,7 +125,7 @@ private class CachedPropertyOfStruct<T, TProperty> : CachedProperty<TProperty> { private Getter _getter; private Setter _setter; - public CachedPropertyOfStruct(string name) : base(typeof(T), name) { } + public CachedPropertyOfStruct(string? name) : base(typeof(T), name) { } public override TProperty Get() => (_getter ??= CreateGetter(typeof(Getter), Info.GetMethod, true) as Getter)(ref _dummy); @@ -145,7 +144,7 @@ private class CachedPropertyOfClass<T, TProperty> : CachedProperty<TProperty> { private Getter _getter; private Setter _setter; - public CachedPropertyOfClass(string name) : base(typeof(T), name) { } + public CachedPropertyOfClass(string? name) : base(typeof(T), name) { } public override TProperty Get() => (_getter ??= CreateGetter(typeof(Getter), Info.GetMethod, false) as Getter)(_dummy); @@ -163,7 +162,7 @@ private class CachedPropertyOfStatic<TProperty> : CachedProperty<TProperty> { private Getter _getter; private Setter _setter; - public CachedPropertyOfStatic(Type type, string name) : base(type, name) { + public CachedPropertyOfStatic(Type type, string? name) : base(type, name) { //if (!IsStatic(type)) // throw new InvalidOperationException(); } diff --git a/ModKit/Utility/Dictionary/SerializableDictionary.cs b/ToyBox/Classes/ModKit/Utility/SerializableDictionary.cs similarity index 100% rename from ModKit/Utility/Dictionary/SerializableDictionary.cs rename to ToyBox/Classes/ModKit/Utility/SerializableDictionary.cs diff --git a/ToyBox/Classes/ModKit/Utility/Utilities.cs b/ToyBox/Classes/ModKit/Utility/Utilities.cs new file mode 100644 index 000000000..1a3c26fa9 --- /dev/null +++ b/ToyBox/Classes/ModKit/Utility/Utilities.cs @@ -0,0 +1,126 @@ +// Copyright < 2021 > Narria (github user Cabarius) - License: MIT +using HarmonyLib; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace ModKit { + public static class Utilities { + public static TValue? GetValueOrDefault<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, TValue defaultValue = default) { + if (dictionary == null) { throw new ArgumentNullException(nameof(dictionary)); } // using C# 6 + if (key == null) { throw new ArgumentNullException(nameof(key)); } // using C# 6 + + return dictionary.TryGetValue(key, out var value) ? value : defaultValue; + } + public static Dictionary<TKey, TElement> ToDictionaryIgnoringDuplicates<TSource, TKey, TElement>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey>? comparer = null) { + if (source == null) + throw new ArgumentException("source"); + if (keySelector == null) + throw new ArgumentException("keySelector"); + if (elementSelector == null) + throw new ArgumentException("elementSelector"); + Dictionary<TKey, TElement> d = new Dictionary<TKey, TElement>(comparer); + foreach (TSource element in source) { + if (!d.ContainsKey(keySelector(element))) + d.Add(keySelector(element), elementSelector(element)); + } + return d; + } + public static string StripHTML(this string s) => Regex.Replace(s, "<.*?>", string.Empty); + public static string MergeSpaces(this string str, bool trim = false) { + if (str == null) + return null; + else { + StringBuilder stringBuilder = new StringBuilder(str.Length); + + int i = 0; + foreach (char c in str) { + if (c != ' ' || i == 0 || str[i - 1] != ' ') + stringBuilder.Append(c); + i++; + } + if (trim) + return stringBuilder.ToString().Trim(); + else + return stringBuilder.ToString(); + } + } + public static string SubstringBetweenCharacters(this string input, char charFrom, char charTo) { + var posFrom = input.IndexOf(charFrom); + if (posFrom != -1) //if found char + { + var posTo = input.IndexOf(charTo, posFrom + 1); + if (posTo != -1) //if found char + { + return input.Substring(posFrom + 1, posTo - posFrom - 1); + } + } + return string.Empty; + } + public static string[] TrimCommonPrefix(this string[] values) { + var prefix = string.Empty; + int? resultLength = null; + + if (values != null) { + if (values.Length > 1) { + var min = values.Min(value => value.Length); + for (var charIndex = 0; charIndex < min; charIndex++) { + for (var valueIndex = 1; valueIndex < values.Length; valueIndex++) { + if (values[0][charIndex] != values[valueIndex][charIndex]) { + resultLength = charIndex; + break; + } + } + if (resultLength.HasValue) { + break; + } + } + if (resultLength.HasValue && + resultLength.Value > 0) { + prefix = values[0].Substring(0, resultLength.Value); + } + } else if (values.Length > 0) { + prefix = values[0]; + } + } + return prefix.Length > 0 ? values.Select(s => s.Replace(prefix, "")).ToArray() : values; + } + // Credits to https://github.com/microsoftenator2022 + /// <summary> + /// Divides input sequence into chunks of at most <paramref name="chunkSize"/> + /// </summary> + /// <returns>Sequence of sequences of <paramref name="chunkSize"/> elements. The last chunk will contain at most <paramref name="chunkSize"/> elements</returns> + public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunkSize) { + var chunk = new T[chunkSize]; + var i = 0; + + foreach (var element in source) { + chunk[i] = element; + + i++; + if (i == chunkSize) { + yield return chunk; + chunk = new T[chunkSize]; + i = 0; + } + } + + if (i > 0 && i < chunkSize) yield return chunk.Take(i); + } + } + public static class CloneUtil<T> { + private static readonly Func<T, object> clone; + + static CloneUtil() { + var cloneMethod = typeof(T).GetMethod("MemberwiseClone", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + clone = (Func<T, object>)cloneMethod.CreateDelegate(typeof(Func<T, object>)); + } + + public static T ShallowClone(T obj) => (T)clone(obj); + } + public static class CloneUtil { + public static T ShallowClone<T>(this T obj) => CloneUtil<T>.ShallowClone(obj); + } +} diff --git a/ToyBox/Classes/Models/Settings+UI.cs b/ToyBox/Classes/Models/Settings+UI.cs new file mode 100644 index 000000000..5108e0aad --- /dev/null +++ b/ToyBox/Classes/Models/Settings+UI.cs @@ -0,0 +1,117 @@ +using ModKit; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using UnityEngine; + +namespace ToyBox { + public partial class SettingsUI { + public static string cultureSearchText = ""; + public static CultureInfo? uiCulture; + public static List<CultureInfo> cultures = new(); + public static void UpdateAndVerificationGUI() { + + HStack("Checks & Updates".localize(), 1, + () => Label(""), + () => Toggle("Verify whether the mod files are corrupted.".localize(), ref Main.Settings.toggleIntegrityCheck, AutoWidth()), + () => { + if (Main.Settings.toggleIntegrityCheck) { + Toggle("Update if the mod files are corrupted.".localize(), ref Main.Settings.updateOnChecksumFail, AutoWidth()); + } + }, + () => { + if (Main.Settings.toggleIntegrityCheck) { + Toggle("Disable the mod if files are corrupted.".localize(), ref Main.Settings.disableOnChecksumFail, AutoWidth()); + } + }, + () => Toggle("Check if the local version has known issues.".localize(), ref Main.Settings.toggleVersionCompatability, AutoWidth()), + () => { + if (Main.Settings.toggleVersionCompatability) { + Toggle("Update if the local version has known issues.".localize(), ref Main.Settings.shouldTryUpdate, AutoWidth()); + } + }, + () => Toggle("Always update to the latest mod version.".localize(), ref Main.Settings.toggleAlwaysUpdate, AutoWidth()) + ); + } + public static void OnGUI() { + HStack("Settings".localize(), 1, + () => Label("Mono Version".localize() + $": {Type.GetType("Mono.Runtime")?.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static)?.Invoke(null, null)?.ToString()}"), + () => { + ActionButton("Reset UI".localize(), Main.SetNeedsResetGameUI); + 25.space(); + Label(("Tells the game to reset the in game UI.".Green() + " Warning".Yellow() + " Using this in dialog or the book will dismiss that dialog which may break progress so use with care".Orange()).localize()); + }, + () => { + Toggle("Enable Game Development Mode".localize(), ref Main.Settings.toggleDevopmentMode); + Space(25); + HelpLabel($"This turns on the developer console which lets you access cheat commands, shows a FPS window (hide with F11), etc.\n{"Warning: ".Yellow().Bold()}{"You may need to restart the game for this to fully take effect".Orange()}".localize()); + }, + () => Label(""), + () => EnumGrid("Log Level".localize(), ref Main.Settings.loggingLevel, AutoWidth()), + () => Label(""), + () => Toggle("Strip HTML (colors) from Native Console".localize(), ref Main.Settings.stripHtmlTagsFromNativeConsole, AutoWidth()), + () => Toggle("Enable Search as you type for Browsers (needs restart)".localize(), ref Mod.ModKitSettings.searchAsYouType, AutoWidth()), +#if DEBUG + () => Toggle("Strip HTML (colors) from Logs Tab in Unity Mod Manager".localize(), ref Main.Settings.stripHtmlTagsFromUMMLogsTab), +#endif + () => Toggle("Display guids in most tooltips, use shift + left click on items/abilities to copy guid to clipboard".localize(), ref Main.Settings.toggleGuidsClipboard, AutoWidth()), + () => Toggle("Allow dangerous PatchTool patches".localize(), ref Main.Settings.toggleEnableDangerousPatchToolPatches, AutoWidth()), + () => Toggle("Display risky options".localize(), ref Main.Settings.toggleRiskyToggles, AutoWidth()), + () => Toggle("Check for Glyph Support".localize(), ref Mod.ModKitSettings.CheckForGlyphSupport, AutoWidth()), + () => { + if (!Mod.ModKitSettings.CheckForGlyphSupport) Toggle("Use default Glyphs".localize(), ref Mod.ModKitSettings.UseDefaultGlyphs, AutoWidth()); + }, + () => Toggle("Use BPId Cache to speed up loading of specific types of Blueprints".localize(), ref Main.Settings.toggleUseBPIdCache, AutoWidth()), + () => Toggle("Automatically rebuild the BPId Cache if necessary".localize(), ref Main.Settings.toggleAutomaticallyBuildBPIdCache, AutoWidth()), + () => Toggle("Preload Blueprints".localize(), ref Main.Settings.togglePreloadBlueprints, AutoWidth()), + () => Slider("Blueprint Loader Chunk Size".localize(), ref Main.Settings.BlueprintsLoaderChunkSize, 1, 50000, 100, "", AutoWidth()), + () => Slider("Blueprint Loader Threads".localize(), ref Main.Settings.BlueprintsLoaderNumThreads, 1, 128, 3, "", AutoWidth()), + () => Slider("Blueprint Loader Amount of Shards".localize(), ref Main.Settings.BlueprintsLoaderNumShards, 1, 1024, 32, "", AutoWidth()), + () => { } + ); +#if true + Div(0, 25); + UpdateAndVerificationGUI(); + Div(0, 25); + HStack("Localization".localize(), 1, + () => { + if (Event.current.type != EventType.Repaint) { + uiCulture = CultureInfo.GetCultureInfo(Mod.ModKitSettings.uiCultureCode); + cultures = CultureInfo.GetCultures(CultureTypes.AllCultures).OrderBy(ci => ci.DisplayName).ToList(); + if (Main.Settings.onlyShowLanguagesWithFiles) { + var languages = LocalizationManager.getLanguagesWithFile().ToHashSet(); + cultures = cultures + .Where(ci => languages.Contains(ci.Name)) + .OrderBy(ci => ci.DisplayName). + ToList(); + } + } + using (VerticalScope()) { + using (HorizontalScope()) { + Label("Current Culture".localize().Cyan(), Width(275)); + Space(25); + Label($"{uiCulture.DisplayName}({uiCulture.Name})".Orange()); + Space(25); + ActionButton("Export current locale to file".localize().Cyan(), () => LocalizationManager.Export()); + Space(25); + LinkButton("Open the Localization Guide".localize(), "https://github.com/cabarius/ToyBox/wiki/Localization-Guide"); + } + 15.space(); + using (HorizontalScope()) { + Toggle("Only show languages with existing localization files".localize(), ref Main.Settings.onlyShowLanguagesWithFiles); + } + Div(0, 25); + if (GridPicker<CultureInfo>("Culture", ref uiCulture, cultures, null, ci => $"{ci.Name.Cyan().Bold()} {ci.DisplayName.Orange()}", ref cultureSearchText, 6, rarityButtonStyle, Width(ummWidth - 350))) { + Mod.ModKitSettings.uiCultureCode = uiCulture.Name; + LocalizationManager.Update(); + } + } + }, + () => { } + ); +#endif + } + } +} diff --git a/ToyBox/Classes/Models/Settings.cs b/ToyBox/Classes/Models/Settings.cs new file mode 100644 index 000000000..c7b6bb454 --- /dev/null +++ b/ToyBox/Classes/Models/Settings.cs @@ -0,0 +1,333 @@ +// Copyright < 2021 > Narria (github user Cabarius) - License: MIT +using Kingmaker; +using Kingmaker.Blueprints; +using Kingmaker.EntitySystem; +using Kingmaker.EntitySystem.Persistence; +using Kingmaker.Enums; +using Kingmaker.RuleSystem; +using ModKit; +using ModKit.Utility; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityModManagerNet; + +namespace ToyBox { + public class PerSaveSettings : EntityPart { + internal const string ID = "ToyBox.PerSaveSettings"; + internal delegate void Changed(PerSaveSettings perSave); + [JsonIgnore] + internal static Changed observers; + + // schema for storing multiclass settings + // Dictionary<CharacterName, + // Dictionary<ClassID, HashSet<ArchetypeIDs> + + // Dictionary<Character Hashcode, Dictionary<bodyPartName, universalParameter>> + [JsonProperty] + public Dictionary<string, Dictionary<string, float>> characterSkeletonReplacers = new(); + // This is the scaling modifier which is applied to the visual model of each character + [JsonProperty] + public Dictionary<string, float> characterModelSizeMultiplier = new(); + // Dictionary<Character Hashcode, + // { doOverride, OverrideValue } + [JsonProperty] + public Dictionary<string, Tuple<bool, bool>> doOverrideEnableAiForCompanions = new(); + // Dictioanry<Character Hashcode, + // Kingmaker.Enums.Size which will override default size + [JsonProperty] + public Dictionary<string, Size> characterSizeModifier = new(); + } + + public class Settings : UnityModManager.ModSettings { + private static PerSaveSettings cachedPerSave = null; + internal const string PerSaveKey = "ToyBox"; + public static void ClearCachedPerSave() => cachedPerSave = null; + public static void SetPerSaveSettings(InGameSettings settings) => ReloadPerSaveSettings(settings); + public static void ReloadPerSaveSettings(InGameSettings? maybeSettings = null) { + var settingsList = maybeSettings?.List ?? Game.Instance?.State?.InGameSettings?.List; + if (settingsList == null) { + return; + } + Mod.Debug($"reloading per save settings from Player.SettingsList[{PerSaveKey}]"); + if (settingsList.TryGetValue(PerSaveKey, out var obj) && obj is string json) { + try { + cachedPerSave = JsonConvert.DeserializeObject<PerSaveSettings>(json); + Mod.Debug($"read successfully from Player.SettingsList[{PerSaveKey}]"); + } catch (Exception e) { + Mod.Error($"failed to read from Player.SettingsList[{PerSaveKey}]"); + Mod.Error(e); + } + } + if (cachedPerSave == null) { + Mod.Warn("per save settings not found, creating new..."); + cachedPerSave = new PerSaveSettings(); + SavePerSaveSettings(); + } + } + public static void SavePerSaveSettings() { + var player = Game.Instance?.Player; + if (player == null) return; + if (cachedPerSave == null) + return; + var json = JsonConvert.SerializeObject(cachedPerSave); + Shodan.GetInGameSettingsList()[PerSaveKey] = json; + try { + Mod.Debug($"saved to Player.SettingsList[{PerSaveKey}]"); + if (PerSaveSettings.observers is MulticastDelegate mcdel) { + var doomed = new List<PerSaveSettings.Changed>(); + foreach (var inv in mcdel.GetInvocationList()) { + if (inv.Target == null && inv is PerSaveSettings.Changed changed) + doomed.Add(changed); + } + foreach (var del in doomed) { + Mod.Debug("removing observer: {del} from PerSaveSettings"); + PerSaveSettings.observers -= del; + } + } + if (cachedPerSave) + PerSaveSettings.observers?.Invoke(cachedPerSave); + } catch (Exception e) { + Mod.Error(e); + } + } + internal PerSaveSettings perSave { + get { + if (cachedPerSave != null) return cachedPerSave; + ReloadPerSaveSettings(); + return cachedPerSave; + } + } + + // Main + public int selectedTab = 0; + public int increment = 10000; + + // Quality of Life + public bool toggleContinueAudioOnLostFocus = false; + public bool highlightObjectsToggle = false; + public bool toggleShiftClickToUseInventorySlot = false; + public bool toggleShiftClickToFastTransfer = false; + // TODO: Public Interface? UI? + public bool enableLoadWithMissingBlueprints = false; + public bool toggleZoomableLocalMaps = false; + public bool toogleShowInterestingNPCsOnQuestTab = false; + public bool toggleShowInterestingNPCsOnLocalMap = false; + public bool toggleSkipAnyKeyToContinueWhenLoadingSaves = false; + + // Camera + public bool toggleZoomOnAllMaps = false; + public bool toggleRotateOnAllMaps = false; + // TODO: Public Interface? UI? + public bool toggleScrollOnAllMaps = false; + public bool toggleCameraPitch = false; + public bool toggleCameraElevation = false; + public bool toggleFreeCamera = false; + public bool toggleInvertXAxis = false; + public bool toggleInvertKeyboardXAxis = false; + public bool toggleInvertYAxis = false; + public bool toggleOffsetCameraHeight = false; + public float CameraElevationOffset = 0f; + public float fovMultiplier = 1; + internal float AdjustedFovMultiplier => Math.Max(fovMultiplier, toggleZoomableLocalMaps ? 1.25f : 0.4f); + + // Tweaks + public bool toggleNoPsychicPhenomena = false; + public bool customizePsychicPhenomena = false; + public bool toggleInfiniteAbilities = false; + public bool toggleNoAttackCooldowns = false; + public bool toggleUnlimitedActionsPerTurn = false; + public bool toggleReallyUnlimitedActionsPerTurn = false; + public bool toggleEquipmentRestrictions = false; + public bool toggleDialogRestrictions = false; + // TODO: Should this stay experimental? + public bool toggleDialogRestrictionsEverything = false; + public bool toggleRestoreSpellsAbilitiesAfterCombat = false; + public bool toggleInstantRestAfterCombat = false; + public bool toggleInfiniteItems = false; + public bool toggleAutomaticallyLoadLastSave = false; + public bool toggleAllowAchievementsDuringModdedGame = true; + public bool toggleSkipSplashScreen = false; + public bool toggleDLC1Theme = false; + public bool togglAutoEquipConsumables = false; + public bool toggleEquipItemsDuringCombat = false; + public bool toggleUseItemsDuringCombat = false; + public bool toggleTeleportKeysEnabled = false; + public bool highlightHiddenObjects = false; + public bool highlightHiddenObjectsInFog = false; + public bool toggleUnlimitedStatModifierStacking = false; + public bool disableTraps = false; + public bool togglekillOnEngage = false; + public bool disableWarpRandomEncounter = false; + public HashSet<string> excludedRandomPhenomena = new(); + public HashSet<string> excludedPerilsMinor = new(); + public HashSet<string> excludedPerilsMajor = new(); + public bool freezeVeilThickness = false; + public bool disableEndTurnHotkey = false; + + // Loot + public bool toggleColorLootByRarity = false; + public bool toggleShowRarityTags = false; + public bool UsingLootRarity => toggleColorLootByRarity || toggleShowRarityTags; + public RarityType minRarityToColor = 0; + public bool toggleMassLootEverything = false; + public bool toggleLootAliveUnits = false; + public bool toggleShowHiddenLoot = false; + public bool toggleLootChecklistFilterBlueprint = false; + public bool toggleLootChecklistFilterDescription = false; + public RarityType lootChecklistFilterRarity = RarityType.None; + public RarityType maxRarityToHide = RarityType.None; + + // Enhanced Inventory + // TODO: Public Interface? UI? + public FilterCategories SearchFilterCategories = FilterCategories.Default; + public ItemSortCategories InventoryItemSorterOptions = ItemSortCategories.Default; + + // level up + public bool toggleIgnorePrerequisiteStatValue = false; + public bool toggleIgnorePrerequisiteClassLevel = false; + public bool toggleIgnoreCareerPrerequisites = false; + public bool toggleFeaturesIgnorePrerequisites = false; + public bool toggleSetDefaultRespecLevelZero = false; + public bool toggleSetDefaultRespecLevelFifteen = false; + public bool toggleSetDefaultRespecLevelThirtyfive = false; + + // Multipliers + public float experienceMultiplier = 1; + public float experienceMultiplierCombat = 1; + public float experienceMultiplierQuests = 1; + public float experienceMultiplierSkillChecks = 1; + public float experienceMultiplierChallenges = 1; + public float experienceMultiplierSpace = 1; + public bool useCombatExpSlider = false; + public bool useQuestsExpSlider = false; + public bool useSkillChecksExpSlider = false; + public bool useChallengesExpSlider = false; + public bool useSpaceExpSlider = false; + public float fowMultiplier = 1; + public float walkRangeMultiplier = 1; + public float sprintRangeMultiplier = 1; + public float partyMovementSpeedMultiplier = 1; + public float buffDurationMultiplierValue = 1; + public float timeScaleMultiplier = 1; + public float alternateTimeScaleMultiplier = 3; + public bool useAlternateTimeScaleMultiplier = false; + + // Dice Rolls + public UnitSelectType allAttacksHit = UnitSelectType.Off; + public UnitSelectType allHitsCritical = UnitSelectType.Off; + public UnitSelectType rollWithAdvantage = UnitSelectType.Off; + public UnitSelectType rollWithDisadvantage = UnitSelectType.Off; + public UnitSelectType alwaysRoll1 = UnitSelectType.Off; + public UnitSelectType neverRoll1 = UnitSelectType.Off; + public UnitSelectType alwaysRoll1OutOfCombat = UnitSelectType.Off; + public UnitSelectType alwaysRoll50 = UnitSelectType.Off; + public UnitSelectType alwaysRoll100 = UnitSelectType.Off; + public UnitSelectType neverRoll100 = UnitSelectType.Off; + public UnitSelectType roll1Initiative = UnitSelectType.Off; + public UnitSelectType roll5Initiative = UnitSelectType.Off; + public UnitSelectType roll10Initiative = UnitSelectType.Off; + public UnitSelectType skillsTake1 = UnitSelectType.Off; + public UnitSelectType skillsTake25 = UnitSelectType.Off; + public UnitSelectType skillsTake50 = UnitSelectType.Off; + + // Party Editor + public int selectedPartyFilter = 0; + public HashSet<string> namesToDisableVoiceOver = new(); + + // Blueprint Browser + public int searchLimit = 100; + public int selectedBPTypeFilter = 1; + public string searchText = ""; + public bool searchDescriptions = true; + public bool showAttributes = false; + public bool showAssetIDs = false; + public bool showComponents = false; + public bool showElements = false; + public bool showDisplayAndInternalNames = false; + public bool sortCollationByEntries = false; + + // Enchantment (Sandal) + public bool showRatingForEnchantmentInventoryItems = true; + + // Dialog & Previews (Dialogs, Events ,etc) + public bool previewDialogResults = false; + public bool previewDialogConditions = false; + public bool previewAlignmentRestrictedDialog = false; + public bool toggleAllowAnyGenderRomance = false; + public bool toggleMultipleRomance = false; + public bool toggleRemoteCompanionDialog = false; + public bool toggleExCompanionDialog = false; + public bool toggleOverrideOccupation = false; + public HashSet<string> usedOccupations = new(); + public bool toggleShowAnswersForEachConditionalResponse = false; + public bool toggleMakePreviousAnswersMoreClear = false; + + // Etudes + public bool showEtudeComments = true; + + // Quests + public bool toggleQuestHideCompleted = true; + public bool toggleQuestsShowUnrevealedObjectives = false; + public bool toggleQuestInspector = false; + public bool toggleIntrestingNPCsShowFalseConditions = false; + public bool toggleInterestingNPCsShowHidden = false; + + // Saves + public bool toggleShowGameIDs = false; + + public bool toggleIgnoreAbilityAnyRestriction = false; + public bool toggleIgnoreAbilityAoeOverlap = false; + public bool toggleIgnoreAbilityLineOfSight = false; + public bool toggleIgnoreAbilityTargetTooFar = false; + public bool toggleIgnoreAbilityTargetTooClose = false; + + public HashSet<string> buffsToIgnoreForDurationMultiplier = new(SettingsDefaults.DefaultBuffsToIgnoreForDurationMultiplier); + public bool toggleAddFlatEnemyMods = false; + public bool toggleAddMultiplierEnemyMods = false; + public SerializableDictionary<StatType, float> flatEnemyMods = SettingsDefaults.DefaultEnemyStatMods(0); + public SerializableDictionary<StatType, float> multiplierEnemyMods = SettingsDefaults.DefaultEnemyStatMods(1); + + // Development + public LogLevel loggingLevel = LogLevel.Info; + public bool stripHtmlTagsFromNativeConsole = true; + public bool stripHtmlTagsFromUMMLogsTab = true; + public bool toggleDevopmentMode = false; + public bool toggleAlwaysUpdate = false; + public bool toggleGuidsClipboard = false; + public bool toggleEnableDangerousPatchToolPatches = false; + public bool toggleRiskyToggles = false; + public bool onlyShowLanguagesWithFiles = true; + public int BlueprintsLoaderNumThreads = 3; + public int BlueprintsLoaderChunkSize = 100; + public int BlueprintsLoaderNumShards = 32; + public bool togglePreloadBlueprints = false; + public bool toggleUseBPIdCache = true; + public bool toggleAutomaticallyBuildBPIdCache = true; + public bool shouldTryUpdate = true; + public bool updateOnChecksumFail = true; + public bool disableOnChecksumFail = false; + public bool hasSeenUpdatePage = false; + public bool toggleVersionCompatability = true; + public bool toggleIntegrityCheck = true; + + // Patch Tool + public HashSet<string> disabledPatches = new(); + public bool showPatchToolEnums = true; + public bool showPatchToolComplexTypes = true; + public bool showPatchToolBlueprintReferences = true; + public bool showPatchToolCollections = true; + public bool showPatchToolPrimitiveTypes = true; + public bool showPatchToolUnityObjects = false; + public bool showPatchToolDeleteButtons = false; + public bool showPatchToolCreateButtons = false; + public bool togglePatchToolCollapseAllPathsOnPatch = false; + + + // Save + public override void Save(UnityModManager.ModEntry modEntry) => Save(this, modEntry); + + } +} + diff --git a/ToyBox/Classes/MonkeyPatchin/BagOfPatches/CamereRT.cs b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/CamereRT.cs new file mode 100644 index 000000000..537cea560 --- /dev/null +++ b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/CamereRT.cs @@ -0,0 +1,234 @@ +// borrowed shamelessly and enhanced from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/26, which is under the MIT License + +using HarmonyLib; +using Kingmaker; +using Kingmaker.GameModes; +using Kingmaker.View; +using System; +using System.Linq; +using System.Reflection; +using UnityEngine; +using UnityModManager = UnityModManagerNet.UnityModManager; +using Kingmaker.Settings; +using ModKit; +using System.Collections.Generic; +using System.Globalization; +using DG.Tweening; +using Kingmaker.Blueprints; +using Kingmaker.Blueprints.Root; +using Owlcat.Runtime.Visual.RenderPipeline; +using Owlcat.Runtime.Visual.RenderPipeline.RendererFeatures.OccludedObjectHighlighting; +using UnityEngine.EventSystems; +using Kingmaker.Controllers.Clicks; +using Kingmaker.UI.Models.SettingsUI.SettingAssets; +using Kingmaker.Code.UI.MVVM.VM.ServiceWindows; +using Kingmaker.Controllers.Rest; +using Kingmaker.Settings.Entities; +using Kingmaker.UI.Models; +using ModKit.Utility; +using TMPro; +using Kingmaker.PubSubSystem.Core.Interfaces; + +namespace ToyBox.BagOfPatches { + internal static class CameraPatches { + public static Settings Settings = Main.Settings; + public static Player player => Game.Instance.Player; + private static float CameraElevation = 0f; + + [HarmonyPatch(typeof(CameraController))] + private static class CameraControllerPatch { + [HarmonyPatch(nameof(CameraController.Tick))] + [HarmonyPrefix] + public static bool Tick(CameraController __instance) { + if (Game.Instance.CurrentlyLoadedArea.AreaStatGameMode == GameModeType.StarSystem || Game.Instance.CurrentlyLoadedArea.AreaStatGameMode == GameModeType.GlobalMap) { + return false; + } + var instance = CameraRig.Instance; + if (instance == null || (bool)instance.FixCamera) + return false; + if (Settings.toggleCameraPitch) { + instance.m_EnableOrbitCamera = true; + //Mod.Log($"{instance.MinSpaceCameraAngle} {instance.MaxSpaceCameraAngle}"); + //Mod.Log($"Euler angles: {instance.transform.eulerAngles}"); + instance.MinSpaceCameraAngle = -63; // -15 + instance.MaxSpaceCameraAngle = +100; // +20 + } else { + instance.MinSpaceCameraAngle = -20; + instance.MaxSpaceCameraAngle = +15; + + } + if (__instance.m_AllowScroll || Settings.toggleScrollOnAllMaps) { + __instance.Follower.TryFollow(); + instance.TickScroll(); + } + if (__instance.m_AllowRotate || Settings.toggleRotateOnAllMaps || Main.resetExtraCameraAngles) { + instance.TickRotate(); + } + if (__instance.m_AllowZoom || Settings.toggleZoomOnAllMaps) + instance.CameraZoom.TickZoom(); + instance.TickShake(); + return false; + } + } + + [HarmonyPatch(typeof(CameraZoom))] + private static class CameraZoomPatch { + private static float BaseFovMin => (Settings.toggleZoomOnAllMaps || Settings.toggleZoomableLocalMaps) ? 12 : 17.5f; + private static readonly float BaseFovMax = 30; + private static float FovMin => BaseFovMin / Settings.fovMultiplier; + private static float FovMax => BaseFovMax * Settings.AdjustedFovMultiplier; + + [HarmonyPatch(nameof(CameraZoom.TickZoom))] + [HarmonyPrefix] + private static bool TickZoom(CameraZoom __instance) { + if (Settings.fovMultiplier == 1 && !Settings.toggleZoomableLocalMaps) return true; + + if (__instance.m_ZoomRoutine != null || __instance.ZoomLock) + return false; + if (!__instance.IsScrollBusy + && Game.Instance.IsControllerMouse + && !__instance.IsOutOfScreen + && !PointerController.InGui) + __instance.m_PlayerScrollPosition += Input.GetAxis("Mouse ScrollWheel"); + __instance.m_ScrollPosition = __instance.m_PlayerScrollPosition + + __instance.m_GamepadScrollPosition; + __instance.m_GamepadScrollPosition = 0.0f; + __instance.m_ScrollPosition = Mathf.Clamp(__instance.m_ScrollPosition, 0.0f, __instance.ZoomLength); + __instance.m_SmoothScrollPosition = + Mathf.Lerp(__instance.m_SmoothScrollPosition, __instance.m_ScrollPosition, Time.unscaledDeltaTime * __instance.Smoothness); + __instance.m_Camera.fieldOfView = Mathf.Lerp(FovMax, FovMin, __instance.CurrentNormalizePosition); + if ((bool)(UnityEngine.Object)__instance.m_VirtualCamera) + __instance.m_VirtualCamera.m_Lens.FieldOfView = __instance.m_Camera.fieldOfView; + if (__instance.EnablePhysicalZoom) + __instance.m_Camera.transform.localPosition = new Vector3( + __instance.m_Camera.transform.localPosition.x, + __instance.m_Camera.transform.localPosition.y, + Mathf.Lerp(__instance.PhysicalZoomMin, + __instance.PhysicalZoomMax, + __instance.CurrentNormalizePosition + ) + ); + __instance.m_PlayerScrollPosition = __instance.m_ScrollPosition; + return false; + } + } + + [HarmonyPatch(typeof(CameraRig))] + public static class CameraRigPatch { + public static void OnAreaLoad() { + } + + + [HarmonyPatch(nameof(CameraRig.TickScroll))] + [HarmonyPrefix] + public static bool TickScroll(CameraRig __instance, ref Vector3 ___m_TargetPosition) { + if (!Settings.toggleCameraPitch + && !Settings.toggleCameraElevation + && !Main.resetExtraCameraAngles + && !Settings.toggleFreeCamera + ) + return true; + float num = Mathf.Min(Time.unscaledDeltaTime, 0.1f); + if (__instance.m_ScrollRoutine != null && Time.time > __instance.m_ScrollRoutineEndsOn) { + __instance.StopCoroutine(__instance.m_ScrollRoutine); + __instance.m_ScrollRoutine = null; + Vector3 vector = __instance.PlaceOnGround(__instance.m_ScrollRoutineEndPos); + __instance.transform.position = (__instance.m_TargetPosition = vector); + } + if (__instance.m_HandScrollLock) { + return false; + } + if (__instance.m_ScrollRoutine == null) { + Vector2 vector2 = __instance.m_ScrollOffset; + bool flag = __instance.m_FullScreenUIType > FullScreenUIType.Unknown; + if ((!Application.isEditor || CameraRig.DebugCameraScroll) && !Game.Instance.IsControllerGamepad && SettingsRoot.Controls.ScreenEdgeScrolling && !flag && Application.isFocused) { + vector2 += __instance.GetCameraScrollShiftByMouse(); + } + vector2 += __instance.m_ScrollBy2D; + __instance.m_ScrollBy2D.Set(0f, 0f); + if (vector2 == Vector2.zero) { + return false; + } + CameraController cameraController = Game.Instance.CameraController; + if (cameraController != null) { + CameraController.CameraUnitFollower follower = cameraController.Follower; + if (follower != null) { + follower.Release(); + } + } + float cameraScrollMultiplier = Game.Instance.CurrentlyLoadedArea.CameraScrollMultiplier; + vector2 *= __instance.ScrollSpeed * num * cameraScrollMultiplier * CameraRig.ConsoleScrollMod; + __instance.FigureOutScreenBasis(); + Vector3 prevPos = __instance.m_TargetPosition; + __instance.m_TargetPosition += vector2.x * __instance.Right + vector2.y * __instance.Up; + EventBus.RaiseEvent<ICameraMovementHandler>(delegate (ICameraMovementHandler h) { + h.HandleCameraTransformed(Vector3.Dot(prevPos, __instance.m_TargetPosition)); + }, true); + if (!Settings.toggleFreeCamera) { + if (!__instance.NoClamp && !__instance.m_SkipClampOneFrame) { + __instance.m_TargetPosition = __instance.ClampByLevelBounds(__instance.m_TargetPosition); + } + __instance.m_TargetPosition = __instance.PlaceOnGround(__instance.m_TargetPosition); + if (__instance.NewBehaviour) { + if (__instance.m_AttackPointPos == null) { + __instance.m_AttackPointPos = new Vector3?(__instance.Camera.transform.parent.localPosition); + } + __instance.m_TargetPosition = __instance.LowerGently(prevPos, __instance.m_TargetPosition, num); + } + } + } + __instance.m_ScrollOffset = Vector2.zero; + return false; + } + + [HarmonyPatch(nameof(CameraRig.TickRotate))] + [HarmonyPrefix] + public static bool TickRotate(CameraRig __instance, ref Vector3 ___m_TargetPosition) { + if (!Settings.toggleRotateOnAllMaps + && !Settings.toggleCameraPitch + && !Settings.toggleCameraElevation + && !Main.resetExtraCameraAngles + && !Settings.toggleInvertXAxis + && !Settings.toggleInvertKeyboardXAxis + ) + return true; + if (__instance.RotationByMouse || __instance.m_RotationByKeyboard) { + var eulerAngles = __instance.transform.rotation.eulerAngles; + var mouseMovement = Vector2.zero; + if (Main.resetExtraCameraAngles) { + eulerAngles.x = 0f; + CameraElevation = 60f; + __instance.m_TargetPosition = __instance.PlaceOnGround2(__instance.m_TargetPosition); + Main.resetExtraCameraAngles = false; + } else if (Input.GetKey(KeyCode.LeftControl) && Settings.toggleCameraElevation) { + var yRotationSign = Settings.toggleInvertYAxis ? 1f : -1f; + mouseMovement = __instance.CameraDragToRotate(); + ___m_TargetPosition.y += yRotationSign * mouseMovement.y / 10f; + CameraElevation = ___m_TargetPosition.y; + } + } + return true; + } + + [HarmonyPatch(nameof(CameraRig.ClampByLevelBounds))] + [HarmonyPostfix] + + public static void ClampByLevelBounds(Vector3 point, ref Vector3 __result) { + if (!Settings.toggleCameraElevation && !Settings.toggleFreeCamera) return; + __result = point; + } + [HarmonyPatch(nameof(CameraRig.PlaceOnGround))] + [HarmonyPostfix] + private static void PlaceOnGround(ref Vector3 __result) { + if (!Settings.toggleCameraElevation && !Settings.toggleFreeCamera) return; + __result.y = CameraElevation; + } + [HarmonyPatch(nameof(CameraRig.PlaceOnGround2)), HarmonyPriority(Priority.HigherThanNormal)] + [HarmonyPostfix] + private static void PlaceOnGround2(ref Vector3 __result) { + if (!Settings.toggleOffsetCameraHeight) return; + __result.y += Settings.CameraElevationOffset; + } + } + } +} diff --git a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Clipboard+Guids.cs b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/Clipboard+Guids.cs similarity index 83% rename from ToyBox/classes/MonkeyPatchin/BagOfPatches/Clipboard+Guids.cs rename to ToyBox/Classes/MonkeyPatchin/BagOfPatches/Clipboard+Guids.cs index ea68d3535..d09f142ab 100644 --- a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Clipboard+Guids.cs +++ b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/Clipboard+Guids.cs @@ -1,23 +1,30 @@ using HarmonyLib; using Kingmaker.PubSubSystem; -using Kingmaker.UI.MVVM._PCView.Slots; -using Kingmaker.UI.MVVM._VM.ActionBar; -using Kingmaker.UI.MVVM._VM.Tooltip.Bricks; -using Kingmaker.UI.MVVM._VM.Tooltip.Templates; -using Kingmaker.UI.UnitSettings; using Owlcat.Runtime.UI.Tooltips; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using ModKit; using UnityEngine; +using ModKit.Utility; +using Kingmaker.Code.UI.MVVM.VM.Tooltip.Templates; +using Kingmaker.Code.UI.MVVM.View.Slots; +using Kingmaker.Code.UI.MVVM.VM.Tooltip.Bricks; +using Kingmaker.UI.Models.UnitSettings; +using Kingmaker.Code.UI.MVVM.VM.ActionBar; namespace ToyBox.classes.MonkeyPatchin.BagOfPatches { + public static class GUIDTooltipStyes { + public static string GUIDTooltipStyle(this string guid) => RichText.SizePercent($"<color=grey>guid: {guid}</color>", 5); + } [HarmonyPatch] public class Clipboard_Guids { public static Settings settings = Main.Settings; + const TooltipTextType GUIDTooltipTextTypes = TooltipTextType.Simple | TooltipTextType.Italic; + public static void CopyToClipboard(string guid) { GUIUtility.systemCopyBuffer = guid; EventBus.RaiseEvent<IWarningNotificationUIHandler>(h => h.HandleWarning("Copied Guid to clipboard: " + guid, false)); @@ -32,7 +39,7 @@ public static void Tooltip1(TooltipTemplateAbility __instance, ref IEnumerable<I var list = __result.ToList(); var guid = __instance.BlueprintAbility?.AssetGuidThreadSafe; - list.Insert(0, new TooltipBrickText($"<color=grey>guid: {guid}</color>", TooltipTextType.Small | TooltipTextType.Italic)); + list.Insert(0, new TooltipBrickText(guid.GUIDTooltipStyle(), GUIDTooltipTextTypes)); __result = list; } @@ -47,7 +54,7 @@ public static void Tooltip2(TooltipTemplateActivatableAbility __instance, ref IE var guid = __instance.BlueprintActivatableAbility?.AssetGuidThreadSafe; var guid2 = __instance.BlueprintActivatableAbility?.m_Buff?.Guid.ToString(); - list.Insert(0, new TooltipBrickText($"<color=grey>guid: {guid}\nbuff: {guid2}</color>", TooltipTextType.Small | TooltipTextType.Italic)); + list.Insert(0, new TooltipBrickText($"{guid}\nbuff: {guid2}".GUIDTooltipStyle(), GUIDTooltipTextTypes)); __result = list; } @@ -61,7 +68,7 @@ public static void Tooltip3(TooltipTemplateItem __instance, ref IEnumerable<IToo var list = __result.ToList(); var guid = __instance.m_BlueprintItem?.AssetGuidThreadSafe ?? __instance.m_Item?.Blueprint?.AssetGuidThreadSafe; - list.Insert(0, new TooltipBrickText($"<color=grey>guid: {guid}</color>", TooltipTextType.Small | TooltipTextType.Italic)); + list.Insert(0, new TooltipBrickText(guid.GUIDTooltipStyle(), GUIDTooltipTextTypes)); __result = list; } @@ -75,7 +82,7 @@ public static void Tooltip4(TooltipTemplateBuff __instance, ref IEnumerable<IToo var list = __result.ToList(); var guid = __instance.Buff?.Blueprint?.AssetGuidThreadSafe; - list.Insert(0, new TooltipBrickText($"<color=grey>guid: {guid}</color>", TooltipTextType.Small | TooltipTextType.Italic)); + list.Insert(0, new TooltipBrickText(guid.GUIDTooltipStyle(), GUIDTooltipTextTypes)); __result = list; } diff --git a/ToyBox/Classes/MonkeyPatchin/BagOfPatches/Combat/ActionsRT.cs b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/Combat/ActionsRT.cs new file mode 100644 index 000000000..5b6804d95 --- /dev/null +++ b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/Combat/ActionsRT.cs @@ -0,0 +1,81 @@ +// borrowed shamelessly and enhanced from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/26, which is under the MIT License + +using HarmonyLib; +using Kingmaker; +using Kingmaker.Blueprints; +using Kingmaker.Controllers.Combat; +//using Kingmaker.Controllers.GlobalMap; +using Kingmaker.UnitLogic.Commands; + + + +//using Kingmaker.UI._ConsoleUI.Models; +//using Kingmaker.UI.RestCamp; +using Kingmaker.UnitLogic.Parts; +using System.Linq; + +//using Kingmaker.UI._ConsoleUI.GroupChanger; + +namespace ToyBox.BagOfPatches { + internal static class Actions { + public static Settings Settings = Main.Settings; + public static Player player => Game.Instance.Player; + + + [HarmonyPatch(typeof(PartUnitCombatState))] + public static class PartUnitCombatStatePatch { + [HarmonyPatch(nameof(PartUnitCombatState.SpendActionPoints))] + [HarmonyPrefix] + public static bool SpendActionPoints(PartUnitCombatState __instance, int? yellow = null, float? blue = null) { + if (!Settings.toggleUnlimitedActionsPerTurn) return true; + if (__instance.Owner.IsPartyOrPet()) { + return false; + } else { + return true; + } + } + [HarmonyPatch(nameof(PartUnitCombatState.SpendActionPointsAll))] + [HarmonyPrefix] + public static bool SpendActionPointsAll(PartUnitCombatState __instance) { + if (!(Settings.toggleUnlimitedActionsPerTurn && Settings.toggleReallyUnlimitedActionsPerTurn)) return true; + if (__instance.Owner.IsPartyOrPet()) { + return false; + } else { + return true; + } + } + } + + + public static class PartAbilityCooldownsPatch { + private static readonly string[] abilityGroupToDecooldownIds = new string[] { + "1cf206b13141425491c379bc75ef0699", //WeaponAttackAbilityGroup + "0a77ccc934d14b94b5171dc3faa531e4", //WeaponAttackAbilityGroup_PrimaryHand + "109c045a43c84bfaa46ca3d0aadfbf3c", //WeaponAttackAbilityGroup_SecondaryHand + "36fdf1bc96884a9e803dcbcc8e447785", //PsykerSpellsGroup + "73f152d564dc482289fc8a753ab3d571", //PsykerStaffPowers + "926c66e10782441bac49945d306697e1", //PsykerMinorPowers + "ebb0aef8634845069b938c90b9d114aa", //PsykerMajorPowers + + }; + + + [HarmonyPatch(typeof(UnitUseAbilityParams))] + public static class myPatch { + [HarmonyPatch(nameof(UnitUseAbilityParams.IgnoreCooldown), MethodType.Getter)] + [HarmonyPostfix] + public static void Result(ref bool __result, UnitUseAbilityParams __instance) { + + if (!__instance.Ability.Caster.IsInPlayerParty) + return; + if (Settings.toggleInfiniteAbilities || + (Settings.toggleNoAttackCooldowns && + __instance.Ability.AbilityGroups.Any(g => abilityGroupToDecooldownIds.Contains(g.AssetGuid))) + ) { + __result = true; + } + } + } + } + } +} \ No newline at end of file diff --git a/ToyBox/Classes/MonkeyPatchin/BagOfPatches/Combat/Difficulty.cs b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/Combat/Difficulty.cs new file mode 100644 index 000000000..bf4251903 --- /dev/null +++ b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/Combat/Difficulty.cs @@ -0,0 +1,39 @@ +using HarmonyLib; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.EntitySystem.Stats; +using System; +using System.Collections.Generic; + +namespace ToyBox.BagOfPatches { + [HarmonyPatch] + internal static class Difficulty { + internal static readonly HashSet<StatType> BadStats = [StatType.TemporaryHitPoints, StatType.Unknown, StatType.AttackOfOpportunityCount, + StatType.Crew, StatType.TurretRadius, StatType.TurretRating, StatType.MilitaryRating, + StatType.PsyRating, StatType.Evasion, StatType.MachineTrait, StatType.ArmourFore, + StatType.ArmourPort, StatType.ArmourStarboard, StatType.ArmourAft, StatType.Inertia, + StatType.Power, StatType.Aiming, StatType.RevealRadius, StatType.DetectionRadius, + StatType.ShieldsAmount, StatType.ShieldsRegeneration, StatType.Morale, StatType.Discipline, + StatType.DamageNonLethal]; + [HarmonyPatch(typeof(ModifiableValue), nameof(ModifiableValue.ModifiedValue), MethodType.Getter), HarmonyPostfix] + public static void get_ModifiableValue_ModifiedValue(ModifiableValue __instance, ref int __result) { + if (BadStats.Contains(__instance.OriginalType)) { + return; + } + if (__instance.Owner is BaseUnitEntity entity && entity is not StarshipEntity && entity.IsPlayerEnemy) { + var stat = __instance.OriginalType; + if (Main.Settings.toggleAddFlatEnemyMods) { + var flat = Main.Settings.flatEnemyMods[stat]; + if (flat != 0) { + __result += (int)flat; + } + } + if (Main.Settings.toggleAddMultiplierEnemyMods) { + var mult = Main.Settings.multiplierEnemyMods[stat]; + if (mult != 1) { + __result = (int)(mult * __result); + } + } + } + } + } +} diff --git a/ToyBox/Classes/MonkeyPatchin/BagOfPatches/DevelopmentRT.cs b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/DevelopmentRT.cs new file mode 100644 index 000000000..2787d7def --- /dev/null +++ b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/DevelopmentRT.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using HarmonyLib; +using Kingmaker; +using Kingmaker.Blueprints; +using Kingmaker.Blueprints.Items.Equipment; +using Kingmaker.EntitySystem; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.EntitySystem.Persistence; +using Kingmaker.EntitySystem.Persistence.JsonUtility; +using Kingmaker.EntitySystem.Persistence.Versioning; +using Kingmaker.Items; +using Kingmaker.UI.Models.Log.CombatLog_ThreadSystem; +using Kingmaker.Utility; +using Kingmaker.Utility.BuildModeUtils; +using ModKit; +using Newtonsoft.Json; +using Owlcat.Runtime.Core.Logging; +using UnityModManagerNet; + +namespace ToyBox.classes.MonkeyPatchin.BagOfPatches { + internal class Development { + public static Settings settings = Main.Settings; + + [HarmonyPatch(typeof(BuildModeUtility), nameof(BuildModeUtility.IsDevelopment), MethodType.Getter)] + private static class BuildModeUtility_IsDevelopment_Patch { + private static void Postfix(ref bool __result) { + if (settings.toggleDevopmentMode) __result = true; + } + } + + [HarmonyPatch(typeof(BuildModeUtility), nameof(BuildModeUtility.CheatsEnabled), MethodType.Getter)] + private static class BuildModeUtility_CheatsEnabled_Patch { + private static void Postfix(ref bool __result) { + if (settings.toggleDevopmentMode) __result = true; + } + } + + [HarmonyPatch(typeof(BlueprintConverter))] + private static class ForceSuccessfulLoad_Blueprints_Patch { + [HarmonyPatch(nameof(BlueprintConverter.ReadJson))] + [HarmonyPrefix] + public static bool ReadJson(ref object __result, JsonReader reader) { + if (!settings.enableLoadWithMissingBlueprints) return true; + var text = (string)reader.Value; + if (string.IsNullOrEmpty(text) || text == "null") { + //Mod.Warn($"ForceSuccessfulLoad_Blueprints_Patch - unable to find valid id - text: {text}"); + __result = null; // We still can't look up a blueprint without a valid id + return false; + } + SimpleBlueprint retrievedBlueprint; + try { + retrievedBlueprint = ResourcesLibrary.TryGetBlueprint(text); + } catch { + retrievedBlueprint = null; + } + if (retrievedBlueprint == null) { + Mod.Warn($"Failed to load blueprint by guid '{text}' but continued with null blueprint."); + OwlLogging.Log($"Failed to load blueprint by guid '{text}' but continued with null blueprint."); + } + __result = retrievedBlueprint; + + return false; + } + } + + [HarmonyPatch(typeof(EntityFact), nameof(EntityFact.AllComponentsCache), MethodType.Getter)] + private static class ForceSuccessfulLoad_OfFacts_Patch { + [HarmonyPrefix] + private static void Prefix(ref EntityFact __instance) { + if (__instance.Blueprint == null) Mod.Warn($"Fact type '{__instance}' failed to load. UniqueID: {__instance.UniqueId}"); + } + } + } +} \ No newline at end of file diff --git a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Dialog.cs b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/Dialog.cs similarity index 61% rename from ToyBox/classes/MonkeyPatchin/BagOfPatches/Dialog.cs rename to ToyBox/Classes/MonkeyPatchin/BagOfPatches/Dialog.cs index 448982d57..6aa4c50f6 100644 --- a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Dialog.cs +++ b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/Dialog.cs @@ -1,36 +1,43 @@  using HarmonyLib; +using JetBrains.Annotations; using Kingmaker; +using Kingmaker.Blueprints; +using Kingmaker.Code.UI.MVVM.VM.Dialog.Dialog; +using Kingmaker.Controllers; +using Kingmaker.Controllers.Dialog; +using Kingmaker.Controllers.Interfaces; using Kingmaker.Designers.EventConditionActionSystem.Conditions; -using Evalutors = Kingmaker.Designers.EventConditionActionSystem.Evaluators; +using Kingmaker.DialogSystem; using Kingmaker.DialogSystem.Blueprints; +using Kingmaker.DialogSystem.State; +using Kingmaker.ElementsSystem; +using Kingmaker.EntitySystem; using Kingmaker.EntitySystem.Entities; +using Kingmaker.Localization; +using Kingmaker.Mechanics.Entities; +using Kingmaker.PubSubSystem; +using Kingmaker.UnitLogic; using Kingmaker.UnitLogic.Parts; +using Kingmaker.Utility; using ModKit; -using System.Linq; using System; -using Kingmaker.DialogSystem; using System.Collections.Generic; -using Kingmaker.Utility; -using Kingmaker.UnitLogic; -using Kingmaker.Controllers.Dialog; -using Kingmaker.Blueprints; +using System.Linq; +using System.Reflection.Emit; using UnityEngine; -using Kingmaker.Controllers; -using Kingmaker.EntitySystem; +using Evalutors = Kingmaker.Designers.EventConditionActionSystem.Evaluators; using Random = System.Random; -using JetBrains.Annotations; -using Kingmaker.Localization; -using Kingmaker.ElementsSystem; -using Kingmaker.DialogSystem.State; namespace ToyBox.BagOfPatches { internal static class Dialog { public static Settings settings = Main.Settings; - public static Player player = Game.Instance.Player; + public static Player player => Game.Instance.Player; // These exclude certain secret companions internal static readonly HashSet<string> SecretCompanions = new() { + #region Wrath + /* { "0bb1c03b9f7bbcf42bb74478af2c6258" }, // Trever { "6b1f599497f5cfa42853d095bda6dafd" }, // Delamere for Lich { "d58b81fd7ec14784fa05bc29fb6c7ae0" }, // Galfrey for Lich @@ -38,10 +45,14 @@ internal static class Dialog { { "7ece3afabe2b6f343b17d1eaa409d273" }, // Ciar for Lich { "e551850403d61eb48bb2de010d12c894" }, // Kestoglyr for Lich { "0bcf3c125a28d164191e874e3c0c52de" } // Staunton for Lich + */ + #endregion }; // These exclude certain problematic cues internal static readonly HashSet<string> ProblemCues = new() { + #region Wrath + /* // Underground Army { "0b3974b21835707458e535fcb330a2a6" }, // SullLannIsNeutralized_dialogue - Cue 0014 @@ -52,43 +63,78 @@ internal static class Dialog { // Feud of the Faithful { "a2f183f2a53b6eb4ea4337b709eb2320" }, // HurlunRamien_Early_Dialogue - Cue 0098 { "5de6be5c80b5f25439151dec4a812714" }, // HurlunRamien_Early_Dialogue - Cue 0100 + */ + #endregion }; [HarmonyPatch(typeof(CompanionInParty), nameof(CompanionInParty.CheckCondition))] public static class CompanionInParty_CheckCondition_Patch { public static void Postfix(CompanionInParty __instance, ref bool __result) { + if (!settings.toggleRemoteCompanionDialog) return; if (__instance.Not) return; // We only want this patch to run for conditions requiring the character to be in the party so if it is for the inverse we bail. Example of this comes up with Lann and Wenduag in the final scene of the Prologue Labyrinth + // We don't want to match when the game only checks for Ex companions since this is basically a check for companions which left the party then + // Example is 6aeb6812dcc1464a9b087786556c9b18 which checks whether Pascal left as a companion. Really weird design from Owlcat right there. + if (__instance.MatchWhenEx && !__instance.MatchWhenActive && !__instance.MatchWhenDetached && !__instance.MatchWhenRemote) return; if (SecretCompanions.Contains(__instance.companion.AssetGuid.ToString())) return; if (ProblemCues.Contains(__instance.Owner.AssetGuid.ToString())) return; - if (settings.toggleRemoteCompanionDialog) { - if (__instance.Owner is BlueprintCue cueBP) { - Mod.Debug($"overiding {cueBP.name} Companion {__instance.companion.name} In Party to true"); - __result = true; - } - if (__instance.Owner is BlueprintCue etudeBP) { + UnitPartCompanion unitPartCompanion = null; + try { + unitPartCompanion = Game.Instance.Player.AllCharacters.FirstOrDefault(unit => unit.Blueprint == __instance.companion && !unit.IsDisposed)?.GetCompanionOptional(); + } catch (NullReferenceException ex) { + Mod.Trace(ex.ToString()); + } + if (unitPartCompanion != null) { + if (unitPartCompanion.State != CompanionState.None) { + if ((settings.toggleExCompanionDialog && unitPartCompanion.State == CompanionState.ExCompanion) || unitPartCompanion.State != CompanionState.ExCompanion) { + if (__instance.Owner is BlueprintCue cueBP) { + OwlLogging.Log($"overiding {cueBP.name} Companion {__instance.companion.name} In Party to true"); + __result = true; + } + if (__instance.Owner is BlueprintCue etudeBP) { + } + } } } } } + [HarmonyPatch] + public static class Evalualtors_CompanionInParty_Patch { + [HarmonyPatch(typeof(AbstractUnitEvaluator), nameof(AbstractUnitEvaluator.GetValueInternal))] + [HarmonyPrefix] + public static bool GetValueInternal(AbstractUnitEvaluator __instance, ref Entity __result) { + if (__instance is Evalutors.CompanionInParty ___instance) { + Mod.Debug($"Evalutors checking {___instance} guid:{___instance.AssetGuid} owner:{___instance.Owner.name} guid: {___instance.Owner.AssetGuid}) value: {__result}"); + if (!settings.toggleRemoteCompanionDialog) return true; + if (___instance.Owner is BlueprintCue cueBP) { + var unitEntityData = Game.Instance.Player.AllCharacters.FirstOrDefault(unit => ___instance.IsCompanion(unit.Blueprint)); + OwlLogging.Log($"Evalutors checking {___instance} guid:{___instance.AssetGuid} owner:{___instance.Owner.name} guid: {___instance.Owner.AssetGuid}) value: {__result}; Overriding to {unitEntityData}"); + __result = unitEntityData; + return false; + } + } + return true; + } - [HarmonyPatch(typeof(Evalutors.CompanionInParty), nameof(Evalutors.CompanionInParty.GetValueInternal))] - public static class Evalualtors_CompanionInParty_GetValueInternal_Patch { - public static bool Prefix(Kingmaker.Designers.EventConditionActionSystem.Evaluators.CompanionInParty __instance, ref UnitEntityData __result) { + [HarmonyPatch(typeof(Evalutors.CompanionInParty), nameof(Evalutors.CompanionInParty.GetAbstractUnitEntityInternal))] + [HarmonyPrefix] + public static bool GetAbstractUnitEntityInternal(Evalutors.CompanionInParty __instance, ref AbstractUnitEntity __result) { Mod.Debug($"Evalutors checking {__instance} guid:{__instance.AssetGuid} owner:{__instance.Owner.name} guid: {__instance.Owner.AssetGuid}) value: {__result}"); if (!settings.toggleRemoteCompanionDialog) return true; if (__instance.Owner is BlueprintCue cueBP) { - var unitEntityData = Game.Instance.Player.AllCrossSceneUnits.FirstOrDefault<UnitEntityData>((Func<UnitEntityData, bool>)(unit => __instance.IsCompanion(unit.Blueprint))); + var unitEntityData = Game.Instance.Player.AllCharacters.FirstOrDefault(unit => __instance.IsCompanion(unit.Blueprint)); + OwlLogging.Log($"Evalutors checking {__instance} guid:{__instance.AssetGuid} owner:{__instance.Owner.name} guid: {__instance.Owner.AssetGuid}) value: {__result}; Overriding to {unitEntityData}"); __result = unitEntityData; return false; } - return true; } } internal static readonly Dictionary<string, bool> DialogSpeaker_GetEntityOverrides = new() { + #region Wrath + /* // A Strike From The Sky { "804b8b87618f8c840a731383a5b448ed", true }, // GargoyleAttack_Start_Dialogue - Cue 0002 { "7c812f8f46bd3bb4ba8e6561888f6416", true }, // GargoyleAttack_Start_Dialogue - Cue 0008 @@ -186,75 +232,96 @@ public static bool Prefix(Kingmaker.Designers.EventConditionActionSystem.Evaluat // More Than Nothing { "cb4cc74067ccf074aad0974519a6013b", true }, // FoxMyself_dialogue - Cue 0001 - + */ + #endregion }; [HarmonyPatch(typeof(DialogSpeaker), nameof(DialogSpeaker.GetEntity))] public static class DialogSpeaker_GetEntity_Patch { - public static bool Prefix(DialogSpeaker __instance, BlueprintCueBase cue, ref UnitEntityData __result) { - if (!settings.toggleRemoteCompanionDialog) return true; + public static bool Prefix(DialogSpeaker __instance, BlueprintCueBase cue, ref BaseUnitEntity __result) { if (!settings.toggleRemoteCompanionDialog) return true; if (__instance.Blueprint == null) { __result = null; return false; } - Mod.Trace($"getting unit for speaker {__instance.Blueprint.name}"); - var dialogPosition = Game.Instance.DialogController.DialogPosition; - Mod.Trace($"dialogPos: {dialogPosition}"); - // Danger Danger! If you try to evaluate the following enumeration it can cause dialog to disappear so don't log it! - var second = Game.Instance.EntityCreator.CreationQueue.Select(ce => ce.Entity).OfType<UnitEntityData>(); + Vector3 dialogPosition = Game.Instance.DialogController.DialogPosition; + IEnumerable<BaseUnitEntity> enumerable = Game.Instance.EntitySpawner.CreationQueue.Select((EntitySpawnController.SpawnEntry ce) => ce.Entity).OfType<BaseUnitEntity>(); __instance.MakeEssentialCharactersConscious(); + __instance.ReplacedSpeakerWithErrorSpeaker = false; //Mod.Trace($"second: {second?.CollectionToString()} matching: {second.Select(__instance.SelectMatchingUnit).CollectionToString()}"); var overrides = DialogSpeaker_GetEntityOverrides; var GUID = cue?.AssetGuid.ToString(); - var hasOverride = GUID != null ? DialogSpeaker_GetEntityOverrides.ContainsKey(GUID) : false; - var overrideValue = hasOverride && DialogSpeaker_GetEntityOverrides[GUID]; - var unit = Game.Instance.State.Units.Concat(Game.Instance.Player.AllCrossSceneUnits) + bool overrideValue = false; + var hasOverride = GUID != null ? DialogSpeaker_GetEntityOverrides.TryGetValue(GUID, out overrideValue) : false; + bool IsExCompanion(BaseUnitEntity unit) { + UnitPartCompanion unitPartCompanion = unit.GetCompanionOptional(); + return unitPartCompanion != null && unitPartCompanion.State == CompanionState.ExCompanion; + } + bool IsMaybeCompanion(BaseUnitEntity unit) { + UnitPartCompanion unitPartCompanion = unit.GetCompanionOptional(); + return unitPartCompanion != null; + } + + var unit = Shodan.AllBaseUnits.Concat(Game.Instance.Player.Party).Concat(Game.Instance.Player.RemoteCompanions) //.Where(u => u.IsInGame && !u.Suppressed) .Where(u => u.IsInGame && !u.Suppressed - //|| (hasOverride ? overrideValue : settings.toggleExCompanionDialog || !u.IsExCompanion()) - ) - .Concat(second) - .Select(new Func<UnitEntityData, UnitEntityData>(__instance.SelectMatchingUnit)) + || (hasOverride ? overrideValue : settings.toggleExCompanionDialog || !IsExCompanion(u))) + .Concat(enumerable) + .Select(new Func<BaseUnitEntity, BaseUnitEntity>(__instance.SelectMatchingUnit)) + .NotNull() + .Distinct() + .Nearest(dialogPosition); + Mod.Debug($"found {unit?.CharacterName ?? "no one loaded".Cyan()} position: {unit?.Position.ToString() ?? "n/a"}"); + if (unit == null) { + unit = Game.Instance.Player.AllCharactersAndStarships.Where(IsMaybeCompanion) + .Where(u => !IsExCompanion(u) || settings.toggleExCompanionDialog) .NotNull() .Distinct() + .Where(u => u.Blueprint.AssetGuid == __instance.Blueprint.AssetGuid) .Nearest(dialogPosition); - Mod.Debug($"found {unit?.CharacterName ?? "no one".cyan()} position: {unit?.Position.ToString() ?? "n/a"}"); + Mod.Debug($"Did not find unit. Trying to get unit not in game: {unit?.CharacterName ?? (unit != null).ToString()} position: {unit?.Position.ToString() ?? "n/a"}"); + + } if (unit != null) { + /* if (unit.DistanceTo(dialogPosition) > 25) { - var mainChar = Game.Instance.Player.MainCharacter.Value; + var mainChar = Shodan.MainCharacter; var mainPos = mainChar.Position; var offset = 4f * UnityEngine.Random.insideUnitSphere; var mainDirection = mainChar.OrientationDirection; unit.Position = mainPos - 5 * mainDirection + offset; } + */ __result = unit; return false; } - DialogDebug.Add((BlueprintScriptableObject)cue, "speaker doesnt exist", Color.red); - __result = null; + string text = "ToyBox: speaker[" + __instance.Blueprint.name + "] doesnt exist. Skipping Cue"; + if (__instance.SpeakerPortrait != null || __instance.Blueprint.IsCompanion || __instance.DoNotReplaceSpeakerWithErrorSpeaker) { + DialogDebug.Add(cue, text); + __result = null; + return false; + } + DialogDebug.Add((BlueprintScriptableObject)cue, "ToyBox: speaker doesnt exist", Color.red); + __result = __instance.ErrorSpeaker; + __instance.ReplacedSpeakerWithErrorSpeaker = true; + text = "ToyBox: speaker[" + __instance.Blueprint.name + "] doesnt exist, replaced with defaultUnit"; + DialogDebug.Add(cue, text, Color.red); return false; } } - //[HarmonyPatch(typeof(BlueprintCue), nameof(BlueprintCue.CanShow))] - //public static class BlueprintCue_CanShow_Patch { - - // public static void Postfix(BlueprintCue __instance, ref bool __result) { - // Mod.Debug($"BlueprintCue_CanShow_Patch - {__instance?.Speaker?.Blueprint?.Name.orange() ?? ""} BP: {__instance} result: {__result}"); - // } - //} - - [HarmonyPatch(typeof(DialogController), nameof(DialogController.AddAnswers))] - public static class DialogController_AddAnswers_Patch { - public static bool Prefix(DialogController __instance, [NotNull] ref IEnumerable<BlueprintAnswerBase> answers, [CanBeNull] BlueprintCueBase continueCue) { + [HarmonyPatch(typeof(DialogController))] + public static class DialogControllerPatch { + [HarmonyPatch(nameof(DialogController.AddAnswers))] + [HarmonyPrefix] + public static bool AddAnswers(DialogController __instance, [NotNull] ref IEnumerable<BlueprintAnswerBase> answers, [CanBeNull] BlueprintCueBase continueCue) { if (!settings.toggleShowAnswersForEachConditionalResponse) return true; - List<BlueprintAnswerBase> expandedAnswers = new(); + var expandedAnswers = new List<BlueprintAnswerBase>(); foreach (var answerBase in answers) { if (answerBase is BlueprintAnswer answer) { if (answer.NextCue is CueSelection cueSelection) { - Mod.Debug($"checking: {answer.name} - {cueSelection.Cues.Count} {answer.Text}"); var cueCount = cueSelection.Cues.Count; + Mod.Debug($"checking: {answer.name} - cueCount:{cueCount} - {cueSelection.Cues.Count} {answer.Text}"); if (cueCount <= 1) expandedAnswers.Add(answer); else { @@ -266,27 +333,16 @@ public static bool Prefix(DialogController __instance, [NotNull] ref IEnumerable var dialogController = Game.Instance.DialogController; if (dialogController.LocalSelectedAnswers.Where(a => a.AssetGuid == answer.AssetGuid).Any()) continue; - if (dialog.SelectedAnswers.Where(a => a.AssetGuid == answer.AssetGuid).Any()) + if (dialog.SelectedAnswers.Contains(answer.AssetGuid)) continue; } - if (settings.toggleShowAllAnswersForEachConditionalResponse || cueBase.CanShow()) { - var multiAnswer = answer.ShallowClone(); - var cueSel = cueSelection.ShallowClone(); - cueSel.Cues = new List<BlueprintCueBaseReference>() { cueBase.ToReference<BlueprintCueBaseReference>() }; - cueSel.Strategy = Strategy.First; - multiAnswer.NextCue = cueSel; - expandedAnswers.Add(multiAnswer); - } } - } - else + } else expandedAnswers.Add(answer); } - } - else + } else expandedAnswers.Add(answer); - } - else + } else expandedAnswers.Add(answerBase); } answers = expandedAnswers; @@ -299,9 +355,8 @@ public static class AnswerSelected_CheckCondition_Patch { public static bool Prefix(AnswerSelected __instance, ref bool __result) { if (!settings.toggleShowAnswersForEachConditionalResponse) return true; if (!__instance.CurrentDialog) { - __result = Game.Instance.Player.Dialog.SelectedAnswers.Where(a => a.AssetGuid == __instance.Answer.AssetGuid).Any(); - } - else { + __result = Game.Instance.Player.Dialog.SelectedAnswers.Contains(__instance.Answer.AssetGuid); + } else { __result = Game.Instance.DialogController.LocalSelectedAnswers.Where(a => a.AssetGuid == __instance.Answer.AssetGuid).Any(); } @@ -309,54 +364,59 @@ public static bool Prefix(AnswerSelected __instance, ref bool __result) { } } -#if false - [HarmonyPatch(typeof(BlueprintAnswer), nameof(BlueprintAnswer.CanShow))] - public static class BlueprintAnswer_CanShow_Patch { - public static bool Prefix(BlueprintAnswer __instance, ref bool __result) { + [HarmonyPatch(typeof(AnswerVM), nameof(AnswerVM.IsAlreadySelected))] + public static class AnswerVM_IsAlreadySelected_Patch { + [HarmonyPrefix] + public static bool IsAlreadySelected(AnswerVM __instance, ref bool __result) { if (!settings.toggleShowAnswersForEachConditionalResponse) return true; - if (__instance.ShowOnce) { - var dialog = Game.Instance.Player.Dialog; - var dialogController = Game.Instance.DialogController; - var matchingSelectedAnswers = dialog.SelectedAnswers.Where(a => a.AssetGuid == __instance.AssetGuid); - Mod.Debug($"matchingAnswers = {string.Join(", ", matchingSelectedAnswers.Select(a => a.name))}"); - if (__instance.ShowOnceCurrentDialog) { - if (dialogController.LocalSelectedAnswers.Where(a => a.AssetGuid == __instance.AssetGuid).Any()) { - __result = true; - return false; - } - } - else if (matchingSelectedAnswers.Any()) { - __result = true; - return false; - } + __result = Game.Instance.Player.Dialog.SelectedAnswers.Contains(__instance.Answer.Value.AssetGuid); + return false; + } + } + [HarmonyPatch(typeof(HasFact), nameof(HasFact.CheckCondition))] + public static class HasFact_CheckCondition_Occupation_Patch { + [HarmonyPostfix] + public static void CheckCondition(HasFact __instance, ref bool __result) { + if (!settings.toggleOverrideOccupation || __instance.Unit.GetValue() != Game.Instance.Player.MainCharacterEntity) return; + if (settings.usedOccupations.Contains(__instance.Fact.AssetGuid)) { + __result = true; } - return true; } } -#endif -#if false - [HarmonyPatch(typeof(CueSelection), nameof(CueSelection.Select))] - public static class CueSelection_Select_Patch { - public static bool Prefix(CueSelection __instance, ref BlueprintCueBase __result) { - if (!settings.toggleRandomizeCueSelections) return true; - List<BlueprintCueBase> blueprintCueBaseList = null; - foreach (BlueprintCueBase blueprintCueBase in __instance.Cues.Dereference()) { - if (blueprintCueBase != null && blueprintCueBase.CanShow()) { - if (blueprintCueBaseList == null) - blueprintCueBaseList = new(); - blueprintCueBaseList.Add(blueprintCueBase); + /* Stop one single ending slide being faulty from skipping all others + [HarmonyPatch] + public static class BookPageStopDialogFix { + [HarmonyPatch(typeof(DialogController), nameof(DialogController.PlayBookPage)), HarmonyTranspiler] + public static IEnumerable<CodeInstruction> PreventStopDialog(IEnumerable<CodeInstruction> instructions) { + foreach (CodeInstruction instr in instructions) { + if (instr.Calls(AccessTools.Method(typeof(DialogController), nameof(DialogController.StopDialog)))) { + yield return new(OpCodes.Pop); + yield return new(OpCodes.Ldarg_0); + yield return new(OpCodes.Ldarg_1); + yield return CodeInstruction.Call((DialogController controller, BlueprintBookPage page) => NoCueHandler(controller, page)); + } else { + yield return instr; } } - if (blueprintCueBaseList == null) { - __result = null; - return false; + } + public static void NoCueHandler(DialogController controller, BlueprintBookPage page) { + string condition = $"Error trying to show BlueprintBookPage ({page}, {page.AssetGuid}, {controller.Dialog}, {controller.Dialog.AssetGuid}). Encountered invalid quest state.\nHere a list of possible cues and their needed quest states:\n"; + foreach (var cue in page.Cues.Dereference<BlueprintCueBase>()) { + condition += $"{cue.name}: "; + condition += PreviewUtilities.FormatConditions(cue.Conditions) + "\n"; + } + Mod.Log(condition); + // Ending slides - Epilogue_dialogue + if (controller.Dialog.AssetGuid == "44493af9e1ae4e378a6b5a413d4c69a1") { + controller.AddAnswers(page.Answers.Dereference<BlueprintAnswerBase>(), null); + EventBus.RaiseEvent<IBookPageHandler>(delegate (IBookPageHandler h) { + h.HandleOnBookPageShow(page, controller.m_BookPageCues, controller.m_Answers); + }, true); + } else { + controller.StopDialog(); } - int index = UnityEngine.Random.Range(0, blueprintCueBaseList.Count); - Mod.Debug($"CueSelection_Select_Patch - index: {index}"); - __result = blueprintCueBaseList[index]; - return false; } } -#endif - } + */ + } } \ No newline at end of file diff --git a/ToyBox/Classes/MonkeyPatchin/BagOfPatches/DiceRollsRT.cs b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/DiceRollsRT.cs new file mode 100644 index 000000000..0208108a9 --- /dev/null +++ b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/DiceRollsRT.cs @@ -0,0 +1,152 @@ +// borrowed shamelessly and enhanced from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/26, which is under the MIT License + +using HarmonyLib; +using Kingmaker; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.RuleSystem; +using Kingmaker.RuleSystem.Rules; +using Kingmaker.RuleSystem.Rules.Damage; +using Kingmaker.UI.Models.Log.Events; +using Kingmaker.Utility.Random; +using ModKit; +using ModKit.Utility; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; + +namespace ToyBox.BagOfPatches { + public static class DiceRollsRT { + private static bool changePolicy = true; + public static Settings settings = Main.Settings; + public static Player player => Game.Instance.Player; + [HarmonyPatch(typeof(RulePerformAttackRoll))] + private static class RulePerformAttackRollPatch { + private static bool forceHit; + private static bool forceCrit; + [HarmonyPatch(nameof(RulePerformAttackRoll.OnTrigger))] + [HarmonyPrefix] + private static void OnTriggerPrefix(RulebookEventContext context) { + if (context.Current.Initiator is BaseUnitEntity unit) { + forceCrit = BaseUnitDataUtils.CheckUnitEntityData(unit, settings.allHitsCritical); + forceHit = BaseUnitDataUtils.CheckUnitEntityData(unit, settings.allAttacksHit); + if (forceCrit || forceHit) { + changePolicy = true; + } + } + } + [HarmonyPatch(nameof(RulePerformAttackRoll.OnTrigger))] + [HarmonyPostfix] + private static void OnTriggerPostfix(ref RulePerformAttackRoll __instance) { + if (forceCrit) { + __instance.ResultIsRighteousFury = true; + __instance.Result = AttackResult.RighteousFury; + } + } + } + [HarmonyPatch(typeof(AttackHitPolicyContextData))] + private static class AttackHitPolicyPatch { + [HarmonyPatch(nameof(AttackHitPolicyContextData.Current), MethodType.Getter)] + [HarmonyPostfix] + private static void GetCurrent(ref AttackHitPolicyType __result) { + if (changePolicy) { + __result = AttackHitPolicyType.AutoHit; + changePolicy = false; + } + } + } + [HarmonyPatch(typeof(RuleRollDice))] + private static class RuleRollDicePatch { + [HarmonyPatch(nameof(RuleRollDice.Roll))] + [HarmonyPriority(Priority.LowerThanNormal)] + [HarmonyPostfix] + private static void Roll(RuleRollDice __instance) { + if (Rulebook.CurrentContext.Current is RuleRollChance chanceRoll) { + if (chanceRoll.RollTypeValue == RollType.Skill) { + if (BaseUnitDataUtils.CheckUnitEntityData(chanceRoll.InitiatorUnit, settings.skillsTake1)) { + __instance.m_Result = 1; + } else if (BaseUnitDataUtils.CheckUnitEntityData(chanceRoll.InitiatorUnit, settings.skillsTake25)) { + __instance.m_Result = 25; + } else if (BaseUnitDataUtils.CheckUnitEntityData(chanceRoll.InitiatorUnit, settings.skillsTake50)) { + __instance.m_Result = 50; + } + } + } + } + } + [HarmonyPatch(typeof(RuleRollDamage), nameof(RuleRollDamage.TryNullifyDamage))] + internal static class RuleRollDamagePatch { + [HarmonyPrefix] + private static void Pre(RuleRollDamage __instance) { + RuleRoleDicePatch.invertInitiator = true; + RuleRoleDicePatch.alternateUnit = __instance.TargetUnit; + } + [HarmonyPostfix] + private static void Post() { + RuleRoleDicePatch.invertInitiator = false; + RuleRoleDicePatch.alternateUnit = null; + } + + } + [HarmonyPatch(typeof(RuleRollDice))] + internal static class RuleRoleDicePatch { + // Nullify damage rolls are done with the attacker as the initiator; meaning Roll 1 for Party would make enemies invincible + internal static bool invertInitiator = false; + internal static BaseUnitEntity alternateUnit = null; + [HarmonyPatch(nameof(RuleRollDice.Roll))] + [HarmonyPostfix] + private static void Roll(RuleRollDice __instance) { + if (__instance.DiceFormula.Dice != DiceType.D100) return; + if (__instance.DiceFormula.Rolls > 1) return; + BaseUnitEntity initiator = invertInitiator ? alternateUnit : __instance.InitiatorUnit; + var result = __instance.m_Result; + if (initiator == null) return; + if (BaseUnitDataUtils.CheckUnitEntityData(initiator, settings.alwaysRoll1) + || (BaseUnitDataUtils.CheckUnitEntityData(initiator, settings.alwaysRoll1OutOfCombat) && !initiator.IsInCombat)) { + result = 1; + } else if (BaseUnitDataUtils.CheckUnitEntityData(initiator, settings.alwaysRoll50)) { + result = 50; + } else if (BaseUnitDataUtils.CheckUnitEntityData(initiator, settings.alwaysRoll100)) { + result = 100; + } else { + var min = 1; + var max = 101; + if (BaseUnitDataUtils.CheckUnitEntityData(initiator, settings.neverRoll1)) { + min = 2; + if (result == 1) { + result = PFStatefulRandom.RuleSystem.Range(min, max); + } + } + if (BaseUnitDataUtils.CheckUnitEntityData(initiator, settings.neverRoll100)) { + max = 100; + if (result == 100) { + result = UnityEngine.Random.Range(min, max); + } + } + if (BaseUnitDataUtils.CheckUnitEntityData(initiator, settings.rollWithAdvantage)) { + result = Math.Min(result, PFStatefulRandom.RuleSystem.Range(min, max)); + } else if (BaseUnitDataUtils.CheckUnitEntityData(initiator, settings.rollWithDisadvantage)) { + result = Math.Max(result, PFStatefulRandom.RuleSystem.Range(min, max)); + } + } + __instance.m_Result = result; + } + } + [HarmonyPatch(typeof(RulebookEvent.Dice))] + public static class RulebookEvent_Dice_Patch { + [HarmonyPatch(nameof(RulebookEvent.Dice.D10), MethodType.Getter)] + [HarmonyPostfix] + private static void GetDice(ref RuleRollD10 __result) { + if (Rulebook.CurrentContext.Current is RuleRollInitiative initiativeEvent) { + if (BaseUnitDataUtils.CheckUnitEntityData(initiativeEvent.InitiatorUnit, settings.roll1Initiative)) { + __result.m_Result = 1; + } else if (BaseUnitDataUtils.CheckUnitEntityData(initiativeEvent.InitiatorUnit, settings.roll5Initiative)) { + __result.m_Result = 5; + } else if (BaseUnitDataUtils.CheckUnitEntityData(initiativeEvent.InitiatorUnit, settings.roll10Initiative)) { + __result.m_Result = 10; + } + } + } + } + } +} \ No newline at end of file diff --git a/ToyBox/Classes/MonkeyPatchin/BagOfPatches/LevelUpPatchesRT.cs b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/LevelUpPatchesRT.cs new file mode 100644 index 000000000..722b9ecbc --- /dev/null +++ b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/LevelUpPatchesRT.cs @@ -0,0 +1,189 @@ +// borrowed shamelessly and enhanced from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/26, which is under the MIT License + +using Code.GameCore.ElementsSystem; +using HarmonyLib; +using JetBrains.Annotations; +using Kingmaker; +using Kingmaker.Blueprints; +using Kingmaker.Blueprints.Classes; +using Kingmaker.Blueprints.Root; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.EntitySystem.Stats; +using Kingmaker.UI.MVVM.VM.CharGen.Phases.BackgroundBase; +using Kingmaker.UI.MVVM.VM.ServiceWindows.CharacterInfo.Sections.Careers.CareerPath; +using Kingmaker.UnitLogic; +using Kingmaker.UnitLogic.Levelup; +using Kingmaker.UnitLogic.Progression; +using Kingmaker.UnitLogic.Progression.Paths; +using Kingmaker.UnitLogic.Progression.Prerequisites; +using ModKit; +using Owlcat.Runtime.UI.MVVM; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using ToyBox; +using ToyBox.classes.Infrastructure; +using UnityEngine; +//using ToyBox.Multiclass; + +namespace ToyBox.BagOfPatches { + internal static class LevelUp { + public static Settings Settings = Main.Settings; + public static Player player => Game.Instance.Player; + + [HarmonyPatch(typeof(PartUnitProgression))] + public static class UnitProgressionData_Patch { + public static int getMaybeZero(PartUnitProgression _instance) { + if (Settings.toggleSetDefaultRespecLevelZero) { + return int.MinValue; + } else if (Settings.toggleSetDefaultRespecLevelFifteen) { + return 15; + } else if (Settings.toggleSetDefaultRespecLevelThirtyfive) { + return 35; + } else { + var tmp = _instance.Owner.Blueprint.GetDefaultLevel(); + return tmp; + } + } + public static ValueTuple<BlueprintCareerPath, int> maybeGetNextCareer(PartUnitProgression _instance) { + ValueTuple<BlueprintCareerPath, int> ret; + try { + ret = _instance.AllCareerPaths.Last<ValueTuple<BlueprintCareerPath, int>>(); + } catch (Exception) { + ret = new(null, 1); + } + Mod.Debug($"Respec Career returned: {ret}"); + return ret; + + } + [HarmonyPatch(nameof(PartUnitProgression.Respec))] + [HarmonyTranspiler] + public static IEnumerable<CodeInstruction> Respec(IEnumerable<CodeInstruction> instructions) { + bool shouldSkipNextInstruction = false; + foreach (var instruction in instructions) { + if (shouldSkipNextInstruction) { + shouldSkipNextInstruction = false; + continue; + } + if (instruction.Calls(AccessTools.Method(typeof(BlueprintUnit), nameof(BlueprintUnit.GetDefaultLevel)))) { + yield return new CodeInstruction(OpCodes.Pop); + yield return new CodeInstruction(OpCodes.Ldarg_0); + instruction.opcode = OpCodes.Call; + instruction.operand = AccessTools.Method(typeof(UnitProgressionData_Patch), nameof(UnitProgressionData_Patch.getMaybeZero)); + } else if (instruction.Calls(AccessTools.PropertyGetter(typeof(PartUnitProgression), nameof(PartUnitProgression.AllCareerPaths)))) { + instruction.operand = AccessTools.Method(typeof(UnitProgressionData_Patch), nameof(UnitProgressionData_Patch.maybeGetNextCareer)); + shouldSkipNextInstruction = true; + } + yield return instruction; + } + + } + } + [HarmonyPatch(typeof(PrerequisiteLevel), nameof(PrerequisiteLevel.MeetsInternal))] + public static class PrerequisiteLevelPatch { + [HarmonyPostfix] + public static void MeetsInternal(PrerequisiteLevel __instance, IBaseUnitEntity unit, ref bool __result) { + if (!unit.IsPartyOrPetInterface()) return; // don't give extra feats to NPCs + if (!(__result ^ __instance.Not) && Settings.toggleIgnorePrerequisiteClassLevel) { + OwlLogging.Log($"PrerequisiteLevel.MeetsInternal - {unit.CharacterName} - {__instance.GetCaptionInternal()} -{__result} -> {true} "); + __result = !__instance.Not; + } + } + } + [HarmonyPatch(typeof(PrerequisiteFact), nameof(PrerequisiteFact.MeetsInternal))] + public static class PrerequisiteFactPatch { + [HarmonyPostfix] + public static void MeetsInternal(PrerequisiteFact __instance, IBaseUnitEntity unit, ref bool __result) { + if (!unit.IsPartyOrPetInterface()) return; // don't give extra feats to NPCs + if (!(__result ^ __instance.Not) && Settings.toggleFeaturesIgnorePrerequisites) { + if (!new StackTrace().ToString().Contains("Kingmaker.UI.MVVM.VM.CharGen")) { + OwlLogging.Log($"PrerequisiteFact.MeetsInternal - {unit.CharacterName} - {__instance.GetCaptionInternal()} - {__result} -> {true} (Not: {__instance.Not}"); + __result = !__instance.Not; + } + } + } + } + [HarmonyPatch(typeof(PrerequisiteStat), nameof(PrerequisiteStat.MeetsInternal))] + public static class PrerequisiteStatPatch { + [HarmonyPostfix] + public static void MeetsInternal(PrerequisiteStat __instance, IBaseUnitEntity unit, ref bool __result) { + if (!unit.IsPartyOrPetInterface()) return; // don't give extra feats to NPCs + if (!(__result ^ __instance.Not) && Settings.toggleIgnorePrerequisiteStatValue) { + OwlLogging.Log($"PrerequisiteStat.MeetsInternal - {unit.CharacterName} - {__instance.GetCaptionInternal()} -{__result} -> {true} "); + __result = !__instance.Not; + } + } + } + [HarmonyPatch(typeof(PartUnitProgression), nameof(PartUnitProgression.CanUpgradePath))] + public static class PartUnitProgression_CanUpgradePath_Patch { + [HarmonyPostfix] + public static void Meet(PartUnitProgression __instance, BlueprintPath path, ref bool __result) { + if (Settings.toggleIgnoreCareerPrerequisites) { + OwlLogging.Log($"PartUnitProgression_CanUpgradePath - {__instance.Owner.CharacterName} - {path.Name} - {__result} -> {true}"); + __result = __instance.GetPathRank(path) < path.Ranks; + } + + } + } + [HarmonyPatch(typeof(CareerPathsListVM), nameof(CareerPathsListVM.GetPrerequisitesCareers))] + public static class CareerPathsListVM_GetPrerequisitesCareers_Patch { + [HarmonyPrefix] + public static void GetPrerequisitesCareers(CareerPathsListVM __instance, ref List<BlueprintCareerPath> careerPaths, ref List<BlueprintFeature> features) { + if (Settings.toggleIgnoreCareerPrerequisites) { + OwlLogging.Log($"CareerPathsListVM_GetPrerequisitesCareers - {__instance} -> remove all prerequisites"); + careerPaths = []; + features = []; + } + + } + } + // This is needed because when leveling up, Owlcode creates a copy of the BlueprintUnit and applies all feats for the Preview. + // When we change stats the BaseValue is modified. That one is taken from the Blueprint in the Preview so our modifications aren't correctly saved there. + // This messes up stat previews. The internal stat changes still work, but coloring and +- would be off in a level up preview. This patch fixes that. + [HarmonyPatch(typeof(UnitHelper))] + public static class UnitHelper_Patch { + [HarmonyPatch(nameof(UnitHelper.CreatePreview))] + [HarmonyPostfix] + public static void UnitHelper_CreatePreview(BaseUnitEntity _this, bool createView, ref BaseUnitEntity __result) { + foreach (var obj in HumanFriendlyStats.StatTypes) { + try { + var modifiableValue = _this.Stats.GetStatOptional(obj); + if (modifiableValue != null) { + var modifiableValue2 = __result.Stats.GetStatOptional(obj); + if (modifiableValue2 == null) { + if (StatTypeHelper.IsSkill(obj)) { + modifiableValue2 = __result.Stats.Container.RegisterSkill(obj); + } else if (StatTypeHelper.IsAttribute(obj)) { + modifiableValue2 = __result.Stats.Container.RegisterAttribute(obj); + } else { + modifiableValue2 = __result.Stats.Container.Register(obj); + } + } + if (modifiableValue.BaseValue != modifiableValue2.BaseValue) { + modifiableValue2.BaseValue = modifiableValue.BaseValue; + } + } + } catch (NullReferenceException ex) { + Mod.Log($"ToyBox error while trying to copy base stats to preview unit:\nS{ex}"); + } + } + } + } + // In addition to the above; the preview unit would also have the feats that the unit should have. That prevents picking them on level up. This patch basically respecs the Preview to prevent that. + [HarmonyPatch(typeof(BlueprintUnit))] + public static class BlueprintUnit_Patch { + [HarmonyPatch(nameof(BlueprintUnit.CreateEntity))] + [HarmonyPostfix] + public static void CreateEntity(BaseUnitEntity __result) { + if (Settings.toggleSetDefaultRespecLevelZero || Settings.toggleSetDefaultRespecLevelFifteen || Settings.toggleSetDefaultRespecLevelThirtyfive) { + if (new StackTrace().ToString().Contains($"{typeof(LevelUpManager).FullName}.{nameof(LevelUpManager.CreatePreviewUnit)}")) { + __result.Progression.Respec(); + } + } + } + } + } +} diff --git a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Limits/Infinites.cs b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/Limits/Infinites.cs similarity index 64% rename from ToyBox/classes/MonkeyPatchin/BagOfPatches/Limits/Infinites.cs rename to ToyBox/Classes/MonkeyPatchin/BagOfPatches/Limits/Infinites.cs index 69eca21eb..c35723a8d 100644 --- a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Limits/Infinites.cs +++ b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/Limits/Infinites.cs @@ -14,8 +14,6 @@ using Kingmaker.UnitLogic.Abilities; using Kingmaker.UnitLogic.Abilities.Components; using Kingmaker.UnitLogic.ActivatableAbilities; -using Kingmaker.UnitLogic.Class.LevelUp; -using Kingmaker.UnitLogic.Class.LevelUp.Actions; using System; //using Kingmaker.UI._ConsoleUI.GroupChanger; using UnityModManager = UnityModManagerNet.UnityModManager; @@ -23,13 +21,28 @@ namespace ToyBox.BagOfPatches { internal static class Infinites { public static Settings settings = Main.Settings; - public static Player player = Game.Instance.Player; + public static Player player => Game.Instance.Player; + + [HarmonyPatch(typeof(ItemEntity), nameof(ItemEntity.SpendCharges), new Type[] { typeof(MechanicEntity) })] + public static class ItemEntity_SpendCharges_Patch { + public static bool Prefix(ref bool __result, MechanicEntity user, ItemEntity __instance) { + if (settings.toggleInfiniteItems && user.IsPartyOrPet()) { + var blueprintItemEquipment = __instance.Blueprint as BlueprintItemEquipment; + __result = blueprintItemEquipment && blueprintItemEquipment.GainAbility; // Don't skip the check about being a valid item and having an ability to use + return false; // We're skipping spend charges because even if someone else has logic to sometimes not spend charges, we don't care. We said "infinite" use. + } + return true; + } + } + [HarmonyPatch(typeof(AbilityResourceLogic), nameof(AbilityResourceLogic.Spend))] public static class AbilityResourceLogic_Spend_Patch { public static bool Prefix(AbilityData ability) { - var unit = ability.Caster.Unit; - if (unit?.Descriptor.IsPartyOrPet() == true && settings.toggleInfiniteAbilities) { + var unit = ability.Caster + ; + if (unit?.IsPartyOrPet() == true && settings.toggleInfiniteAbilities) { + return false; } @@ -42,35 +55,5 @@ public static class ActivatableAbilityResourceLogic_SpendResource_Patch { public static bool Prefix() => !settings.toggleInfiniteAbilities; } - [HarmonyPatch(typeof(AbilityData), nameof(AbilityData.SpellSlotCost), MethodType.Getter)] - public static class AbilityData_SpellSlotCost_Patch { - public static bool Prefix() => !settings.toggleInfiniteSpellCasts; - } - - [HarmonyPatch(typeof(SpendSkillPoint), nameof(SpendSkillPoint.Apply))] - public static class SpendSkillPoint_Apply_Patch { - public static bool Prefix(ref bool __state) { - __state = settings.toggleInfiniteSkillpoints; - return !__state; - } - - public static void Postfix(ref bool __state, LevelUpState state, UnitDescriptor unit, StatType ___Skill) { - if (__state) { - unit.Stats.GetStat(___Skill).BaseValue++; - } - } - } - - [HarmonyPatch(typeof(ItemEntity), nameof(ItemEntity.SpendCharges), new Type[] { typeof(UnitDescriptor) })] - public static class ItemEntity_SpendCharges_Patch { - public static bool Prefix(ref bool __result, UnitDescriptor user, ItemEntity __instance) { - if (settings.toggleInfiniteItems && user.IsPartyOrPet()) { - var blueprintItemEquipment = __instance.Blueprint as BlueprintItemEquipment; - __result = blueprintItemEquipment && blueprintItemEquipment.GainAbility; // Don't skip the check about being a valid item and having an ability to use - return false; // We're skipping spend charges because even if someone else has logic to sometimes not spend charges, we don't care. We said "infinite" use. - } - return true; - } - } } } \ No newline at end of file diff --git a/ToyBox/Classes/MonkeyPatchin/BagOfPatches/Limits/Unrestricted.cs b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/Limits/Unrestricted.cs new file mode 100644 index 000000000..28ac3a78e --- /dev/null +++ b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/Limits/Unrestricted.cs @@ -0,0 +1,105 @@ +// borrowed shamelessly and enhanced from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/26, which is under the MIT License +using HarmonyLib; +using Kingmaker; +using Kingmaker.Blueprints.Items.Components; +using Kingmaker.Blueprints.Items.Equipment; +using Kingmaker.DialogSystem.Blueprints; +using Kingmaker.Items; +using Kingmaker.Designers.EventConditionActionSystem.Conditions; +using ModKit; +using Kingmaker.ElementsSystem; +using Kingmaker.UnitLogic; +using System; +using System.Collections.Generic; +using UnityModManager = UnityModManagerNet.UnityModManager; +using Kingmaker.EntitySystem.Stats; +using static Kingmaker.EntitySystem.Stats.ModifiableValue; +using Kingmaker.EntitySystem.Entities; + +namespace ToyBox.BagOfPatches { + internal static class Unrestricted { + public static Settings settings = Main.Settings; + public static Player player => Game.Instance.Player; + [HarmonyPatch(typeof(EquipmentRestrictionClass), nameof(EquipmentRestrictionClass.CanBeEquippedBy))] + public static class EquipmentRestrictionClassNew_CanBeEquippedBy_Patch { + public static void Postfix(ref bool __result) { + if (settings.toggleEquipmentRestrictions) { + __result = true; + } + } + } + [HarmonyPatch(typeof(EquipmentRestrictionStat), nameof(EquipmentRestrictionStat.CanBeEquippedBy))] + public static class EquipmentRestrictionStat_CanBeEquippedBy_Patch { + public static void Postfix(ref bool __result) { + if (settings.toggleEquipmentRestrictions) { + __result = true; + } + } + } + [HarmonyPatch(typeof(ItemEntityArmor), nameof(ItemEntityArmor.CanBeEquippedInternal))] + public static class ItemEntityArmor_CanBeEquippedInternal_Patch { + public static void Postfix(ItemEntityArmor __instance, MechanicEntity owner, ref bool __result) { + if (settings.toggleEquipmentRestrictions) { + //Mod.Debug($"armor blueprint: {__instance?.Blueprint} - type:{__instance.Blueprint?.GetType().Name}"); + if (__instance.Blueprint is BlueprintItemEquipment blueprint) { + __result = blueprint.CanBeEquippedBy(owner); + } + } + } + } + /* DLC 2 Update removed a lot of stuff from ItemEntityShield (e.g. ArmourComponent) including this method + [HarmonyPatch(typeof(ItemEntityShield), nameof(ItemEntityShield.CanBeEquippedInternal))] + public static class ItemEntityShield_CanBeEquippedInternal_Patch { + public static void Postfix(ItemEntityShield __instance, MechanicEntity owner, ref bool __result) { + if (settings.toggleEquipmentRestrictions) { + if (__instance.Blueprint is BlueprintItemEquipment blueprint) { + __result = blueprint != null && blueprint.CanBeEquippedBy(owner); + } + } + } + } + */ +#if true + [HarmonyPatch(typeof(ItemEntity), nameof(ItemEntity.CanBeEquippedInternal))] + public static class ItemEntity_CanBeEquippedInternal_Patch { + [HarmonyPostfix] + public static void Postfix(ref bool __result) { + if (settings.toggleEquipmentRestrictions) { + __result = true; + } + } + } +#endif + + [HarmonyPatch(typeof(BlueprintAnswerBase))] + public static class BlueprintAnswerBasePatch { + [HarmonyPatch(nameof(BlueprintAnswerBase.IsSoulMarkRequirementSatisfied))] + [HarmonyPostfix] + public static void IsSoulMarkRequirementSatisfied(BlueprintAnswerBase __instance, ref bool __result) { + if (settings.toggleDialogRestrictions) { + __result = true; + } + } + + } + + [HarmonyPatch(typeof(BlueprintAnswer), nameof(BlueprintAnswer.CanSelect))] + public static class BlueprintAnswer_CanSelect_Patch { + public static void Postfix(ref bool __result) { + if (settings.toggleDialogRestrictionsEverything) { + __result = true; + } + } + } + + [HarmonyPatch(typeof(Modifier), nameof(Modifier.Stacks), MethodType.Getter)] + public static class ModifiableValue_UpdateValue_Patch { + public static bool Prefix(Modifier __instance) { + if (settings.toggleUnlimitedStatModifierStacking && __instance?.AppliedTo?.Owner is BaseUnitEntity entity && (entity?.IsPartyOrPet() ?? false)) { + __instance.StackMode = StackMode.ForceStack; + } + return true; + } + } + } +} \ No newline at end of file diff --git a/ToyBox/Classes/MonkeyPatchin/BagOfPatches/Misc.cs b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/Misc.cs new file mode 100644 index 000000000..1416fdfbf --- /dev/null +++ b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/Misc.cs @@ -0,0 +1,14 @@ +using HarmonyLib; +using Kingmaker.Achievements; + +namespace ToyBox.BagOfPatches { + internal static partial class Misc { + [HarmonyPatch(typeof(AchievementsManager), nameof(AchievementsManager.OnAchievementUnlocked))] + private static class AchievementsManager_OnAchievementsUnlocked_Patch { + private static void Postfix(AchievementEntity ach) { + AchievementsUnlocker.unlocked.Add(ach); + AchievementsUnlocker.AchievementBrowser.needsReloadData = true; + } + } + } +} \ No newline at end of file diff --git a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Misc.cs b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/MiscRT.cs similarity index 64% rename from ToyBox/classes/MonkeyPatchin/BagOfPatches/Misc.cs rename to ToyBox/Classes/MonkeyPatchin/BagOfPatches/MiscRT.cs index de6b4762d..712bb883f 100644 --- a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Misc.cs +++ b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/MiscRT.cs @@ -9,68 +9,417 @@ using Kingmaker.Blueprints.Items.Equipment; using Kingmaker.Blueprints.Items.Weapons; using Kingmaker.Blueprints.Root; -using Kingmaker.Cheats; +using Kingmaker.Code.UI.MVVM.View.ServiceWindows.Inventory; +using Kingmaker.Code.UI.MVVM.View.ServiceWindows.Inventory.PC; +using Kingmaker.Code.UI.MVVM.View.Slots; +using Kingmaker.Code.UI.MVVM.View.Vendor; +using Kingmaker.Code.UI.MVVM.VM.Common; +using Kingmaker.Code.UI.MVVM.VM.CounterWindow; using Kingmaker.Controllers; +using Kingmaker.Designers.WarhammerSurfaceCombatPrototype; +using Kingmaker.Designers.WarhammerSurfaceCombatPrototype.PsychicPowers; //using Kingmaker.Controllers.GlobalMap; using Kingmaker.EntitySystem; using Kingmaker.EntitySystem.Entities; using Kingmaker.EntitySystem.Persistence; using Kingmaker.Enums.Damage; +using Kingmaker.GameCommands; using Kingmaker.GameModes; using Kingmaker.Items; +using Kingmaker.Mechanics.Entities; +using Kingmaker.Modding; using Kingmaker.RuleSystem; +using Kingmaker.RuleSystem.Rules; using Kingmaker.Settings; -using Kingmaker.UI._ConsoleUI.CombatStartScreen; +using Kingmaker.Stores.DlcInterfaces; //using Kingmaker.UI._ConsoleUI.Models; using Kingmaker.UI.Common; using Kingmaker.UI.Models.Log.CombatLog_ThreadSystem; // using Steamworks; -using Kingmaker.UI.MVVM._PCView.ServiceWindows.Inventory; -using Kingmaker.UI.MVVM._PCView.Slots; -using Kingmaker.UI.MVVM._PCView.Vendor; -using Kingmaker.UI.MVVM._VM.Common; -using Kingmaker.UI.MVVM._VM.CounterWindow; -using Kingmaker.UI.TurnBasedMode; //using Kingmaker.UI.RestCamp; using Kingmaker.UnitLogic.Abilities.Blueprints; using Kingmaker.UnitLogic.Buffs; -using Kingmaker.UnitLogic.Class.Kineticist; using Kingmaker.Utility; using Kingmaker.View; using ModKit; using Owlcat.Runtime.UniRx; using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; using System.Linq; -using Kingmaker.Settings.Graphics; -using ToyBox.Multiclass; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.Serialization; +using UniRx; +//using ToyBox.Multiclass; //using Kingmaker.UI._ConsoleUI.GroupChanger; using UnityEngine; -using Utilities = Kingmaker.Cheats.Utilities; -using System.Collections.Generic; -using System.Collections; +using UnityModManagerNet; namespace ToyBox.BagOfPatches { - internal static class Misc { - public static Settings settings = Main.Settings; - public static Player player = Game.Instance.Player; + internal static partial class Misc { + public static Settings Settings = Main.Settings; + public static Player player => Game.Instance.Player; + private static BlueprintPsychicPhenomenaRoot.PsychicPhenomenaData[] _psychicPhenomenasFiltered = null; + private static BlueprintAbilityReference[] _minorPerilsFiltered = null; + private static BlueprintAbilityReference[] _majorPerilsFiltered = null; + public static void InvalidateFilteredWarpPhenomenaArrays() { + _psychicPhenomenasFiltered = null; + _minorPerilsFiltered = null; + _majorPerilsFiltered = null; + + } + [HarmonyPatch(typeof(UnityModManager.UI), nameof(UnityModManager.UI.DrawTab))] + private static class IMGUIColorFix { + private static Color cache; + private static Color cache2; + private static Color cache3; + private static Color cache4; + private static Color cache5; + private static Color cache6; + private static Color cache7; + [HarmonyPrefix] + private static void OnGUIPre() { + cache = GUI.skin.label.normal.textColor; + cache2 = GUI.skin.toggle.normal.textColor; + cache3 = GUI.skin.box.normal.textColor; + cache4 = GUI.skin.button.normal.textColor; + cache5 = GUI.skin.verticalSlider.normal.textColor; + cache6 = GUI.skin.textArea.normal.textColor; + cache7 = GUI.skin.textField.normal.textColor; + if (QualitySettings.activeColorSpace == ColorSpace.Linear) { + GUI.skin.label.normal.textColor = cache.gamma; + GUI.skin.toggle.normal.textColor = cache2.gamma; + GUI.skin.box.normal.textColor = cache3.gamma; + GUI.skin.button.normal.textColor = cache4.gamma; + GUI.skin.verticalSlider.normal.textColor = cache5.gamma; + GUI.skin.textArea.normal.textColor = cache6.gamma; + GUI.skin.textField.normal.textColor = cache7.gamma; + } + } + [HarmonyPostfix] + private static void OnGUIPost() { + if (QualitySettings.activeColorSpace == ColorSpace.Linear) { + GUI.skin.label.normal.textColor = cache; + GUI.skin.toggle.normal.textColor = cache2; + GUI.skin.box.normal.textColor = cache3; + GUI.skin.button.normal.textColor = cache4; + GUI.skin.verticalSlider.normal.textColor = cache5; + GUI.skin.textArea.normal.textColor = cache6; + GUI.skin.textField.normal.textColor = cache7; + } + } + } + [HarmonyPatch(typeof(RuleCalculatePsychicPhenomenaEffect))] + public static class RuleCalculatePsychicPhenomenaEffect_Patch { + [HarmonyPatch(nameof(RuleCalculatePsychicPhenomenaEffect.OnTrigger))] + [HarmonyPrefix] + public static bool OnTrigger() => !Settings.toggleNoPsychicPhenomena; + } + // Manually patched/unpatched when changing settings + public static IEnumerable<CodeInstruction> RuleCalculatePsychicPhenomenaEffect_OnTrigger_Transpiler(IEnumerable<CodeInstruction> instructions) { + foreach (var instruction in instructions) { + if (instruction.LoadsField(AccessTools.Field(typeof(BlueprintPsychicPhenomenaRoot), nameof(BlueprintPsychicPhenomenaRoot.PsychicPhenomena)))) { + yield return new CodeInstruction(OpCodes.Pop); + yield return CodeInstruction.Call(() => GetPsychicPhenomenaFiltered()); + continue; + } + if (instruction.LoadsField(AccessTools.Field(typeof(BlueprintPsychicPhenomenaRoot), nameof(BlueprintPsychicPhenomenaRoot.PerilsOfTheWarpMinor)))) { + yield return new CodeInstruction(OpCodes.Pop); + yield return CodeInstruction.Call(() => GetMinorPerilsFiltered()); + continue; + } + if (instruction.LoadsField(AccessTools.Field(typeof(BlueprintPsychicPhenomenaRoot), nameof(BlueprintPsychicPhenomenaRoot.PerilsOfTheWarpMajor)))) { + yield return new CodeInstruction(OpCodes.Pop); + yield return CodeInstruction.Call(() => GetMajorPerilsFiltered()); + continue; + } + yield return instruction; + } + } + public static BlueprintPsychicPhenomenaRoot.PsychicPhenomenaData[] GetPsychicPhenomenaFiltered() { + if (_psychicPhenomenasFiltered == null) { + BlueprintPsychicPhenomenaRoot.PsychicPhenomenaData[] newArr = null; + try { + newArr = BlueprintRoot.Instance.WarhammerRoot.PsychicPhenomenaRoot.PsychicPhenomena.Clone() as BlueprintPsychicPhenomenaRoot.PsychicPhenomenaData[]; + newArr.RemoveAll(i => Settings.excludedRandomPhenomena.Contains(i.Bark.Entries[0].Text.name)); + } catch (Exception ex) { + Mod.Error(ex); + } + _psychicPhenomenasFiltered = newArr ?? []; + } + return _psychicPhenomenasFiltered; + } + public static BlueprintAbilityReference[] GetMinorPerilsFiltered() { + if (_minorPerilsFiltered == null) { + BlueprintAbilityReference[] newArr = null; + try { + newArr = BlueprintRoot.Instance.WarhammerRoot.PsychicPhenomenaRoot.PerilsOfTheWarpMinor.Clone() as BlueprintAbilityReference[]; + newArr.RemoveAll(i => Settings.excludedPerilsMinor.Contains(i.guid)); + } catch (Exception ex) { + Mod.Error(ex); + } + _minorPerilsFiltered = newArr ?? []; + } + return _minorPerilsFiltered; + } + public static BlueprintAbilityReference[] GetMajorPerilsFiltered() { + if (_majorPerilsFiltered == null) { + BlueprintAbilityReference[] newArr = null; + try { + newArr = BlueprintRoot.Instance.WarhammerRoot.PsychicPhenomenaRoot.PerilsOfTheWarpMajor.Clone() as BlueprintAbilityReference[]; + newArr.RemoveAll(i => Settings.excludedPerilsMajor.Contains(i.guid)); + } catch (Exception ex) { + Mod.Error(ex); + } + _majorPerilsFiltered = newArr ?? []; + } + return _majorPerilsFiltered; + } + // Disables the lockout for reporting achievements + [HarmonyPatch(typeof(AchievementEntity), nameof(AchievementEntity.IsDisabled), MethodType.Getter)] + public static class AchievementEntity_IsDisabled_Patch { + private static void Postfix(ref bool __result, AchievementEntity __instance) { + if (!Settings.toggleAllowAchievementsDuringModdedGame) return; + if (!__instance.Data.OnlyMainCampaign && Game.Instance.Player.Campaign && !Game.Instance.Player.Campaign.IsMainGameContent) { + __result = true; + return; + } + BlueprintCampaignReference specificCampaign = __instance.Data.SpecificCampaign; + BlueprintCampaign blueprintCampaign = ((specificCampaign != null) ? specificCampaign.Get() : null); + __result = (!__instance.Data.OnlyMainCampaign && blueprintCampaign != null && Game.Instance.Player.Campaign != blueprintCampaign); + } + } + + // Removes the flag that taints the save file of a user who mods their game + [HarmonyPatch(typeof(Player), nameof(Player.ModsUser), MethodType.Getter)] + public static class Player_ModsUser_Patch { + public static void Postfix(ref bool __result) { + if (Settings.toggleAllowAchievementsDuringModdedGame) { + __result = false; + return; + } + } + } + [HarmonyPatch(typeof(VeilThicknessCounter))] + public static class VeilThicknessCounter_Patch { + [HarmonyPatch(nameof(VeilThicknessCounter.Value), MethodType.Setter)] + [HarmonyPrefix] + public static bool setValue() => !Settings.freezeVeilThickness; + } + [HarmonyPatch(typeof(Game))] + public static class Game_Patch { + [HarmonyPatch(nameof(Game.EndTurnBind))] + [HarmonyPrefix] + public static bool EndTurnBind() => !Settings.disableEndTurnHotkey; + } + /* The following is a new implementation of AutoEquipConsumables which didn't really work out + * (according to someone who later disappeared so I can't verify) so the old implementation stay even if it has issues + [HarmonyPatch(typeof(ItemSlot), nameof(ItemSlot.RemoveItem), [typeof(bool), typeof(bool), typeof(bool)])] + private static class ItemSlot_RemoveItem_Patch { + [HarmonyPrefix] + private static void Prefix(ItemSlot __instance, out object __state) { + if (Settings.togglAutoEquipConsumables && Game.Instance.CurrentMode == GameModeType.Default) { + if (__instance.Item?.Collection == null && __instance.Item.IsSpendCharges && __instance.Item.Charges < 1 && __instance.Item.RemoveFromSlotWhenNoCharges) { + if (__instance.Owner.IsPartyOrPet() && __instance is UsableSlot) { + __state = __instance.MaybeItem?.Blueprint; + return; + } + } + } + __state = null; + } + + private static void Postfix(ItemSlot __instance, object __state) { + if (__state != null) { + var replacement = Game.Instance.Player.Inventory.Items.FirstOrDefault(i => i.Blueprint == (__state as BlueprintItem) && i.HoldingSlot is not UsableSlot); + if (replacement != null) { + replacement = Game.Instance.Player.Inventory.Remove(replacement, 1); + __instance.MaybeOwnerInventory.Collection.Insert(replacement); + if (replacement.HoldingSlot != null) { + __instance.SwitchSlots(replacement.HoldingSlot); + } else { + __instance.InsertItem(replacement); + } + } + } + } + } + */ + [HarmonyPatch(typeof(Kingmaker.Items.Slots.ItemSlot), nameof(Kingmaker.Items.Slots.ItemSlot.RemoveItem), new Type[] { typeof(bool), typeof(bool), typeof(bool) })] + private static class ItemSlot_RemoveItem_Patch { + private static void Prefix(Kingmaker.Items.Slots.ItemSlot __instance, ref ItemEntity __state) { + if (Game.Instance.CurrentMode == GameModeType.Default && Settings.togglAutoEquipConsumables) { + __state = null; + if (__instance.Owner is BaseUnitEntity unit && unit.IsPartyOrPet()) { + var slot = unit.Body.QuickSlots.FindOrDefault(s => s.HasItem && s.Item == __instance.m_ItemRef); + if (slot != null) { + __state = __instance.m_ItemRef; + } + } + } + } + + private static IEnumerator FillItem(Kingmaker.Items.Slots.ItemSlot __instance, ItemEntity item) { + try { + Mod.Debug($"refill {item.Blueprint.Name.Cyan()}"); + __instance.InsertItem(item); + } catch (Exception e) { + Mod.Error($"{e}"); + } + yield return null; + } + + private static void Postfix(Kingmaker.Items.Slots.ItemSlot __instance, ref ItemEntity __state) { + if (Game.Instance.CurrentMode == GameModeType.Default && Settings.togglAutoEquipConsumables) { + if (__state != null && !(__state.Collection != null && __state.Collection != __instance.MaybeOwnerInventory?.Collection)) { + var blueprint = __state.Blueprint; + var item = Game.Instance.Player.Inventory.Items.FirstOrDefault(i => i.Blueprint.ItemType == ItemsItemType.Usable && i.Blueprint == blueprint); + if (item != null) { + + MainThreadDispatcher.StartCoroutine(FillItem(__instance, item)); + } + __state = null; + } + } + } + } + + + [HarmonyPatch(typeof(InventorySlotView), nameof(InventorySlotPCView.OnClick))] + public static class InventorySlotView_OnClick_Patch { + public static bool Prefix(InventorySlotView __instance) { + Mod.Debug("InventorySlotPCView.OnClick"); + if (Settings.toggleShiftClickToFastTransfer && KeyBindings.GetBinding("ClickToTransferModifier").IsModifierActive) { + __instance.OnDoubleClick(); + return false; + } + if (__instance.UsableSource != UsableSourceType.Inventory) return true; + if (!Settings.toggleShiftClickToUseInventorySlot) return true; + if (KeyBindings.GetBinding("InventoryUseModifier").IsModifierActive) { + var item = __instance.Item; + Mod.Debug($"InventorySlotPCView_OnClick_Patch - Using {item.Name}"); + try { + var user = item.GetBestAvailableUser(); + var target = RTExtensions.GetCurrentCharacter(); + Mod.Debug($"user: {user.CharacterName} - target:{target.CharacterName}"); + item.TryUseFromInventory(user, target); + } catch (Exception e) { + Mod.Error($"InventorySlotPCView_OnClick_Patch - {e}"); + } + return false; + } + return true; + } + } + + // Shift + Click Inventory Tweaks + [HarmonyPatch(typeof(CommonVM), + nameof(CommonVM.HandleOpen), + new Type[] { + typeof(CounterWindowType), typeof(ItemEntity), typeof(Action<int>) + })] + public static class CommonVM_HandleOpen_Patch { + public static bool Prefix(CounterWindowType type, ItemEntity item, Action<int> command) { + if (Settings.toggleShiftClickToFastTransfer && KeyBindings.GetBinding("ClickToTransferModifier").IsModifierActive) { + command.Invoke(item.Count); + return false; + } + return true; + } + } + // Those classes/methods are now generic. +#if false + [HarmonyPatch(typeof(ItemSlotPCView), nameof(ItemSlotPCView.OnClick))] + public static class ItemSlotPCView_OnClick_Patch { + public static bool Prefix(ItemSlotPCView __instance) { + if (Settings.toggleShiftClickToFastTransfer && KeyBindings.GetBinding("ClickToTransferModifier").IsModifierActive) { + __instance.OnDoubleClick(); + return false; + } + return true; + } + } + + + [HarmonyPatch(typeof(VendorSlotPCView), nameof(VendorSlotPCView.OnClick))] + public static class VendorSlotPCView_OnClick_Patch { + public static bool Prefix(VendorSlotPCView __instance) { + if (Settings.toggleShiftClickToFastTransfer && KeyBindings.GetBinding("ClickToTransferModifier").IsModifierActive) { + __instance.OnDoubleClick(); + return false; + } + return true; + } + } +#endif + + [HarmonyPatch(typeof(UnitEntityView), nameof(UnitEntityView.GetSizeScale))] + private static class UnitViewSizeMultiplaier_Patch { + [HarmonyPrefix] + private static void Calc(UnitEntityView __instance) { + if (__instance.EntityData == null) { + return; + } + if (Main.Settings.perSave.characterSizeModifier.TryGetValue(__instance.EntityData.HashKey(), out var size)) { + __instance.EntityData.Descriptor().State.m_Size = size; + } + } + [HarmonyPostfix] + private static void Calc(ref float __result, UnitEntityView __instance) { + if (__instance.EntityData == null) { + return; + } + if (Main.Settings.perSave.characterModelSizeMultiplier.TryGetValue(__instance.EntityData.HashKey(), out var scale)) { + // __instance.gameObject.transform.localScale = new Vector3(scale, scale, scale); + __result *= scale; + } + } + } [HarmonyPatch(typeof(Player), nameof(Player.OnAreaLoaded))] internal static class Player_OnAreaLoaded_Patch { private static void Postfix() { Mod.Debug("Player_OnAreaLoaded_Patch"); Settings.ClearCachedPerSave(); - MultipleClasses.SyncAllGestaltState(); + // if (Settings.experimentalLoadRecruitedCharactersFix) Game.Instance.Player.FixPartyAfterChange(); + PartyEditor.statEditorStorage.Clear(); + PartyEditor.skeletonReplacers.Clear(); + + foreach (var ID in Main.Settings.perSave.characterSkeletonReplacers.Keys) { + + foreach (BaseUnitEntity cha in Game.Instance.State.AllUnits.Where((u) => u.HashKey().Equals(ID))) { + + PartyEditor.skeletonReplacers[cha.HashKey()] = new SkeletonReplacer(cha); + PartyEditor.skeletonReplacers[cha.HashKey()].ApplyBonesModification(cha); + } + } + //MultipleClasses.SyncAllGestaltState(); } } - - [HarmonyPatch(typeof(SaveManager), nameof(SaveManager.SaveRoutine))] + [HarmonyPatch] internal static class SaveManager_SaveRoutine_Patch { - private static void Postfix() { - Mod.Log("SaveManager_SaveRoutine_Patch"); + [HarmonyPatch(typeof(SaveManager), nameof(SaveManager.SaveRoutine)), HarmonyPrefix] + private static void SaveRoutine() { Settings.SavePerSaveSettings(); } + [HarmonyPatch(typeof(SaveManager), nameof(SaveManager.LoadRoutine)), HarmonyPrefix] + private static void LoadRoutine() { + Settings.ClearCachedPerSave(); + } + [HarmonyPatch(typeof(ThreadedGameLoader), nameof(ThreadedGameLoader.DeserializeInGameSettings)), HarmonyPostfix] + private static void InGameSettings(ref System.Threading.Tasks.Task<InGameSettings> __result) { + __result = __result.ContinueWith(t => { + Settings.SetPerSaveSettings(t.Result); + return t.Result; + }); + } } + +#if false + public static BlueprintAbility ExtractSpell([NotNull] ItemEntity item) { var itemEntityUsable = item as ItemEntityUsable; if (itemEntityUsable?.Blueprint.Type != UsableItemType.Scroll) { @@ -117,46 +466,6 @@ private static void Postfix(Kingmaker.UI.ServiceWindow.ItemSlot __instance, ref } } - // Disables the lockout for reporting achievements - [HarmonyPatch(typeof(AchievementEntity), nameof(AchievementEntity.IsDisabled), MethodType.Getter)] - public static class AchievementEntity_IsDisabled_Patch { - private static void Postfix(ref bool __result, AchievementEntity __instance) { - //modLogger.Log("AchievementEntity.IsDisabled"); - if (settings.toggleAllowAchievementsDuringModdedGame) { - if (__instance.Data.ExcludedFromCurrentPlatform) { - __result = true; - return; - } - if (__instance.Data.OnlyMainCampaign && !Game.Instance.Player.Campaign.IsMainGameContent) { - __result = true; - return; - } - BlueprintCampaignReference specificCampaign = __instance.Data.SpecificCampaign; - BlueprintCampaign blueprintCampaign = ((specificCampaign != null) ? specificCampaign.Get() : null); - __result = (!__instance.Data.OnlyMainCampaign - && blueprintCampaign != null - && Game.Instance.Player.Campaign != blueprintCampaign) - || (__instance.Data.MinDifficulty != null - && Game.Instance.Player.MinDifficultyController.MinDifficulty.CompareTo(__instance.Data.MinDifficulty.Preset) < 0) - || __instance.Data.MinCrusadeDifficulty > SettingsRoot.Difficulty.KingdomDifficulty - || (__instance.Data.IronMan && !SettingsRoot.Difficulty.OnlyOneSave); - return; - } - } - } - - // Removes the flag that taints the save file of a user who mods their game - [HarmonyPatch(typeof(Player), nameof(Player.ModsUser), MethodType.Getter)] - public static class Player_ModsUser_Patch { - public static bool Prefix(ref bool __result) { - if (settings.toggleAllowAchievementsDuringModdedGame) { - __result = false; - return false; - } - return true; - } - } - // SPIDERS internal static class ModelReplacers { @@ -577,95 +886,13 @@ private static bool Prefix(TurnBasedModeUIController __instance) { } } - // Shift + Click Inventory Tweaks - [HarmonyPatch(typeof(CommonVM), - nameof(CommonVM.HandleOpen), - new Type[] { - typeof(CounterWindowType), typeof(ItemEntity), typeof(Action<int>) - })] - public static class CommonVM_HandleOpen_Patch { - public static bool Prefix(CounterWindowType type, ItemEntity item, Action<int> command) { - if (settings.toggleShiftClickToFastTransfer && UI.KeyBindings.GetBinding("ClickToTransferModifier").IsModifierActive) { - command.Invoke(item.Count); - return false; - } - return true; - } - } - [HarmonyPatch(typeof(ItemSlotPCView), nameof(ItemSlotPCView.OnClick))] - public static class ItemSlotPCView_OnClick_Patch { - public static bool Prefix(ItemSlotPCView __instance) { - if (settings.toggleShiftClickToFastTransfer && UI.KeyBindings.GetBinding("ClickToTransferModifier").IsModifierActive) { - __instance.OnDoubleClick(); - return false; - } - return true; - } - } - [HarmonyPatch(typeof(InventorySlotPCView), nameof(InventorySlotPCView.OnClick))] - public static class InventorySlotPCView_OnClick_Patch { - public static bool Prefix(InventorySlotPCView __instance) { - if (settings.toggleShiftClickToFastTransfer && UI.KeyBindings.GetBinding("ClickToTransferModifier").IsModifierActive) { - __instance.OnDoubleClick(); - return false; - } - if (__instance.UsableSource != UsableSourceType.Inventory) return true; - if (!settings.toggleShiftClickToUseInventorySlot) return true; - if (UI.KeyBindings.GetBinding("InventoryUseModifier").IsModifierActive) { - var item = __instance.Item; - Mod.Debug($"InventorySlotPCView_OnClick_Patch - Using {item.Name}"); - try { - item.TryUseFromInventory(item.GetBestAvailableUser(), (TargetWrapper)WrathExtensions.GetCurrentCharacter()); - } - catch (Exception e) { - Mod.Error($"InventorySlotPCView_OnClick_Patch - {e}"); - } - return false; - } - return true; - } - } - - [HarmonyPatch(typeof(VendorSlotPCView), nameof(VendorSlotPCView.OnClick))] - public static class VendorSlotPCView_OnClick_Patch { - public static bool Prefix(VendorSlotPCView __instance) { - if (settings.toggleShiftClickToFastTransfer && UI.KeyBindings.GetBinding("ClickToTransferModifier").IsModifierActive) { - __instance.OnDoubleClick(); - return false; - } - return true; - } - } - - [HarmonyPatch(typeof(LogThreadService), nameof(LogThreadService.OnGameLoaded))] - public static class LogThreadService_OnGameLoaded_Patch { - private static void Postfix() { - PartyEditor.lastScaleSize = new(); - foreach (var ID in Main.Settings.perSave.characterModelSizeMultiplier.Keys) { - foreach (UnitEntityData cha in Game.Instance.State.Units.Where((u) => u.CharacterName.Equals(ID))) { - float scale = Main.Settings.perSave.characterModelSizeMultiplier.GetValueOrDefault(ID, 1); - cha.View.gameObject.transform.localScale = new Vector3(scale, scale, scale); - PartyEditor.lastScaleSize[cha.HashKey()] = scale; - } - } - } - } - - [HarmonyPatch(typeof(Polymorph), nameof(Polymorph.TryReplaceView))] - private static class Polymorph_TryReplaceView_Patch { - private static void Postfix(Polymorph __instance) { - float scale = PartyEditor.lastScaleSize.GetValueOrDefault(__instance.Owner.HashKey(), 1); - __instance.Owner.View.transform.localScale = new Vector3(scale, scale, scale); - } - } - - [HarmonyPatch(typeof(Polymorph), nameof(Polymorph.RestoreView))] - private static class Polymorph_RestoreView_Patch { - private static void Postfix(Polymorph __instance) { - float scale = PartyEditor.lastScaleSize.GetValueOrDefault(__instance.Owner.HashKey(), 1); - __instance.Owner.View.transform.localScale = new Vector3(scale, scale, scale); + [HarmonyPatch(typeof(AchievementsManager), nameof(AchievementsManager.OnAchievementUnlocked))] + private static class AchievementsManager_OnAchievementsUnlocked_Patch { + private static void Postfix(AchievementEntity ach) { + AchievementsUnlocker.unlocked.Add(ach); + AchievementsUnlocker.AchievementBrowser.needsReloadData = true; } } #if false @@ -677,6 +904,7 @@ public static void SetResolution(int width, int height, FullScreenMode fullscree Mod.Log($"SetResolution - width: {width} height: {height} mode: {fullscreenMode}"); } } +#endif #endif } } diff --git a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Movement.cs b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/MovementRT.cs similarity index 65% rename from ToyBox/classes/MonkeyPatchin/BagOfPatches/Movement.cs rename to ToyBox/Classes/MonkeyPatchin/BagOfPatches/MovementRT.cs index cab0d1d5d..fd2ec1226 100644 --- a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Movement.cs +++ b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/MovementRT.cs @@ -2,40 +2,89 @@ using HarmonyLib; using Kingmaker; -using Kingmaker.Armies; -using Kingmaker.Armies.TacticalCombat.Parts; using Kingmaker.Assets.Controllers.GlobalMap; using Kingmaker.Cheats; using Kingmaker.Controllers.Clicks.Handlers; using Kingmaker.EntitySystem.Entities; -using Kingmaker.Globalmap.State; using Kingmaker.Globalmap.View; using Kingmaker.UnitLogic.Commands; using Kingmaker.UnitLogic.Commands.Base; +using Kingmaker.UnitLogic.Parts; using Kingmaker.Utility; using Kingmaker.Visual.Animation.Kingmaker.Actions; using ModKit; using System; using System.Linq; +using Kingmaker.Pathfinding; +using Kingmaker.UnitLogic; using UnityEngine; using UnityModManager = UnityModManagerNet.UnityModManager; +using Kingmaker.Mechanics.Entities; namespace ToyBox.BagOfPatches { internal static class Movement { - public static Settings settings = Main.Settings; - public static Player player = Game.Instance.Player; + public static Settings Settings = Main.Settings; + public static Player Player => Game.Instance.Player; - [HarmonyPatch(typeof(UnitEntityData), nameof(UnitEntityData.ModifiedSpeedMps), MethodType.Getter)] - public static class UnitEntityData_CalculateSpeedModifier_Patch { - private static void Postfix(UnitEntityData __instance, ref float __result) { - if (settings.partyMovementSpeedMultiplier == 1.0f || (__instance?.Descriptor?.IsPartyOrPet() != true)) - return; - var partTacticalCombat = __instance.Get<UnitPartTacticalCombat>(); - if (partTacticalCombat != null && partTacticalCombat.Faction != ArmyFaction.Crusaders) return; - __result *= settings.partyMovementSpeedMultiplier; + + [HarmonyPatch(typeof(PartMovable))] + public static class PartMoveablePatch { + [HarmonyPatch(nameof(PartMovable.ModifiedSpeedMps), MethodType.Getter)] + [HarmonyPostfix] + private static void ModifiedSpeedMps(PartMovable __instance, ref float __result) { + if (Settings.partyMovementSpeedMultiplier == 1.0f) return; + if (!(__instance.Owner is UnitEntity unit)) return; + if (!unit.CombatGroup.IsPlayerParty) return; + //Mod.Debug($"PartMovable.ModifiedSpeedMps - owner:{unit.CharacterName} old: {__result}"); + // TODO: deal with space movement + __result *= Settings.partyMovementSpeedMultiplier; } } + [HarmonyPatch(typeof(UnitHelper))] + public static class UnitHelperPatch { + [HarmonyPatch(nameof(UnitHelper.CreateMoveCommandUnit))] + [HarmonyPostfix] + public static void CreateMoveCommandUnit( + AbstractUnitEntity unit, + MoveCommandSettings settings, + float[] costPerEveryCell, + ForcedPath forcedPath, + ref UnitMoveToProperParams __result + + ) { + if (!(unit is BaseUnitEntity baseUnit)) return; + if (Settings.partyMovementSpeedMultiplier == 1.0f) return; + if (!baseUnit.CombatGroup.IsPlayerParty) return; + __result.OverrideSpeed = 5 * Settings.partyMovementSpeedMultiplier; + } + + [HarmonyPatch(nameof(UnitHelper.CreateMoveCommandShip))] + [HarmonyPostfix] + public static void CreateMoveCommandShip( + MoveCommandSettings settings, + int straightDistance, + int diagonalsCount, + int length, + ForcedPath forcedPath, + ref UnitMoveToProperParams __result + ) { + __result.OverrideSpeed = 5 * Settings.partyMovementSpeedMultiplier; + } + + [HarmonyPatch(nameof(UnitHelper.CreateMoveCommandParamsRT))] + [HarmonyPostfix] + public static void CreateMoveCommandParamsRT( + BaseUnitEntity unit, + MoveCommandSettings settings, + ref UnitMoveToParams __result + ) { + if (Settings.partyMovementSpeedMultiplier == 1.0f) return; + if (!unit.CombatGroup.IsPlayerParty) return; + __result.OverrideSpeed = 5 * Settings.partyMovementSpeedMultiplier; + } + } +#if false [HarmonyPatch(typeof(ClickGroundHandler), nameof(ClickGroundHandler.RunCommand))] public static class ClickGroundHandler_RunCommand_Patch { private static UnitMoveTo unitMoveTo = null; @@ -80,7 +129,7 @@ public static bool Prefix(UnitEntityData unit, ClickGroundHandler.CommandSetting [HarmonyPatch(typeof(GlobalMapMovementController), nameof(GlobalMapMovementController.GetRegionalModifier), new Type[] { })] public static class MovementSpeed_GetRegionalModifier_Patch1 { public static void Postfix(ref float __result) { - var speedMultiplier = Mathf.Clamp(settings.travelSpeedMultiplier, 0.1f, 100f); + var speedMultiplier = Mathf.Clamp(Settings.travelSpeedMultiplier, 0.1f, 100f); __result = speedMultiplier * __result; } } @@ -88,7 +137,7 @@ public static void Postfix(ref float __result) { [HarmonyPatch(typeof(GlobalMapMovementController), nameof(GlobalMapMovementController.GetRegionalModifier), new Type[] { typeof(Vector3) })] public static class MovementSpeed_GetRegionalModifier_Patch2 { public static void Postfix(ref float __result) { - var speedMultiplier = Mathf.Clamp(settings.travelSpeedMultiplier, 0.1f, 100f); + var speedMultiplier = Mathf.Clamp(Settings.travelSpeedMultiplier, 0.1f, 100f); __result = speedMultiplier * __result; } } @@ -110,7 +159,7 @@ public static void Prefix( ref float visualStepDistance) { // TODO - can we get rid of the other map movement multipliers and do them all here? if (traveler is GlobalMapArmyState armyState && armyState.Data.Faction == ArmyFaction.Crusaders) { - var speedMultiplier = Mathf.Clamp(settings.travelSpeedMultiplier, 0.1f, 100f); + var speedMultiplier = Mathf.Clamp(Settings.travelSpeedMultiplier, 0.1f, 100f); visualStepDistance = speedMultiplier * visualStepDistance; } } @@ -120,10 +169,11 @@ public static void Prefix( public static class GlobalMapArmyState_SpendMovementPoints_Patch { public static void Prefix(GlobalMapArmyState __instance, ref float points) { if (__instance.Data.Faction == ArmyFaction.Crusaders) { - var speedMultiplier = Mathf.Clamp(settings.travelSpeedMultiplier, 0.1f, 100f); + var speedMultiplier = Mathf.Clamp(Settings.travelSpeedMultiplier, 0.1f, 100f); points /= speedMultiplier; } } } +#endif } } diff --git a/ToyBox/Classes/MonkeyPatchin/BagOfPatches/Multipliers.cs b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/Multipliers.cs new file mode 100644 index 000000000..f348b9a72 --- /dev/null +++ b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/Multipliers.cs @@ -0,0 +1,117 @@ +using HarmonyLib; +using Kingmaker; +using Kingmaker.Blueprints.Classes.Experience; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.GameModes; +using Kingmaker.UnitLogic.Buffs; +using Kingmaker.UnitLogic.Buffs.Blueprints; +using Kingmaker.UnitLogic.Mechanics; +using ModKit; +using System; +using UnityEngine; + +namespace ToyBox.BagOfPatches { + internal static class Multipliers { + public static Settings settings = Main.Settings; + + [HarmonyPatch(typeof(BuffCollection))] + public static class BuffCollection_Patch { + private static bool isGoodBuff(BlueprintBuff blueprint) => !settings.buffsToIgnoreForDurationMultiplier.Contains(blueprint.AssetGuidThreadSafe); + [HarmonyPatch(nameof(BuffCollection.Add), new Type[] { typeof(BlueprintBuff), typeof(MechanicEntity), typeof(MechanicsContext), typeof(BuffDuration) })] + [HarmonyPostfix] + public static void Add(BlueprintBuff blueprint, MechanicEntity caster, MechanicsContext parentContext, ref BuffDuration duration) { + if (!duration.Rounds.HasValue) return; + if (caster == null) return; + try { + if (!caster.IsPlayerEnemy && isGoodBuff(blueprint)) { + if (!duration.IsPermanent) { + var newRounds = new Kingmaker.Utility.Rounds(Mathf.FloorToInt(duration.Rounds.Value.Value * settings.buffDurationMultiplierValue)); + duration = new(newRounds, duration.EndCondition); + } + } + } catch (Exception ex) { + Mod.Error(ex); + } + } + } + [HarmonyPatch(typeof(ExperienceHelper))] + public static class ExperienceHelper_Patch { + [HarmonyPatch(nameof(ExperienceHelper.GetCheckExp))] + [HarmonyPostfix] + public static void GetCheckExp(ref int __result) { + float mult = settings.experienceMultiplier; + if (settings.useSkillChecksExpSlider) { + mult = settings.experienceMultiplierSkillChecks; + } + if (mult != 1) { + __result = Mathf.RoundToInt(__result * mult); + } + } + [HarmonyPatch(nameof(ExperienceHelper.GetCheckExpByDifficulty))] + [HarmonyPostfix] + public static void GetCheckExpByDifficulty(ref int __result) { + float mult = settings.experienceMultiplier; + if (settings.useSkillChecksExpSlider) { + mult = settings.experienceMultiplierSkillChecks; + } + if (mult != 1) { + __result = Mathf.RoundToInt(__result * mult); + } + } + [HarmonyPatch(nameof(ExperienceHelper.GetMobExp))] + [HarmonyPostfix] + public static void GetMobExp(ref int __result) { + float mult = settings.experienceMultiplier; + if (settings.useCombatExpSlider) { + mult = settings.experienceMultiplierCombat; + } + if (mult != 1) { + __result = Mathf.RoundToInt(__result * mult); + } + } + [HarmonyPatch(nameof(ExperienceHelper.GetXp))] + [HarmonyPostfix] + public static void GetXp(ref int __result, EncounterType type) { + float mult = settings.experienceMultiplier; + if (Game.Instance.CurrentMode == GameModeType.SpaceCombat) { + if (settings.useSpaceExpSlider) { + mult = settings.experienceMultiplierSpace; + } + } else { + switch (type) { + case EncounterType.QuestNormal: + case EncounterType.QuestMain: { + if (settings.useQuestsExpSlider) { + mult = settings.experienceMultiplierQuests; + } + } + break; + case EncounterType.Mob: + case EncounterType.Boss: { + if (settings.useCombatExpSlider) { + mult = settings.experienceMultiplierCombat; + } + } + break; + case EncounterType.ChallengeMinor: + case EncounterType.ChallengeMajor: { + if (settings.useChallengesExpSlider) { + mult = settings.experienceMultiplierChallenges; + } + } + break; + case EncounterType.SkillCheck: { + if (settings.useSkillChecksExpSlider) { + mult = settings.experienceMultiplierSkillChecks; + } + } + break; + } + } + if (mult != 1) { + __result = Mathf.RoundToInt(__result * mult); + } + } + } + } +} \ No newline at end of file diff --git a/ToyBox/Classes/MonkeyPatchin/BagOfPatches/Romance.cs b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/Romance.cs new file mode 100644 index 000000000..50f114dce --- /dev/null +++ b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/Romance.cs @@ -0,0 +1,191 @@ +using HarmonyLib; +using Kingmaker; +using Kingmaker.Blueprints; +using Kingmaker.Designers.EventConditionActionSystem.Conditions; +using Kingmaker.ElementsSystem; +using Kingmaker.ElementsSystem.Interfaces; +using ModKit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TMPro; +using UnityEngine; + +namespace ToyBox.BagOfPatches { + internal static class Romance { + public static Settings settings = Main.Settings; + + // Any Gender Any Romance Overrides + // These modify the PcFemale/PcMale conditions for specific Owner blueprints + internal static readonly Dictionary<string, bool> PcFemaleOverrides = new() { + // World\Dialogs\Companions\Romances\Heinrix\StartingEvent\Answer_0004 + { "5457755c30ac417d9279fd740b90f549", true }, + // World\Dialogs\Companions\Romances\Heinrix\StartingEvent\Answer_0023 + { "8d6b7c53af134494a64a4de789759fb9", true }, + // World\Dialogs\Companions\Romances\Heinrix\StartingEvent\Answer_11 + { "4b4b769261f04a8cb5726e111c3f7081", true }, + // World\Dialogs\Companions\Romances\Heinrix\StartingEvent\Answer_2 + { "cf1d7205cf854709b038db477db48ac9", true }, + // World\Dialogs\Companions\Romances\Heinrix\StartingEvent\Check_0011 + { "d2c500fbc1b5450c8663d453a33b0eee", true }, + // World\Dialogs\Ch1\BridgeAndCabinet\Briefing\Answer_15; Override not necessary as it's unrelated to romance + // { "02e0bc30b5a146708dd62d68ac7490bd", true }, + // World\Dialogs\Companions\CompanionDialogues\Interrogator\Cue_10 + { "2df6bd21ad5a45a9b1c5142d51d647dc", true }, + // World\Dialogs\Companions\CompanionDialogues\Navigator\Cue_45; Override set to false as we want the male response from Cassia + { "0739ef639d774629a27d396cd733cfd4", false }, + // World\Dialogs\Companions\CompanionDialogues\Navigator\Cue_67; Override not necessary as both male and female responses are fitting + // { "ea42722c44c84835b7363db2fc09b23b", true }, + // World\Dialogs\Companions\CompanionDialogues\Ulfar\Cue_47M Override not necessary as it's unrelated to romance + // { "41897fd7a52249d3a53691fbcfcc9c19", true }, + // World\Dialogs\Companions\CompanionDialogues\Ulfar\Cue_89; Override is not necessary as Cue is unreferenced and empty + // { "c5efaa0ace544ca7a81d439e7cfc6ae5", true } + }; + internal static readonly Dictionary<string, bool> PcMaleOverrides = new() { + // World\Dialogs\Companions\Romances\Cassia\StartingEvent\Answer_0017 + { "85b651edb4f74381bbe762999273c6ec", true }, + // World\Dialogs\Companions\Romances\Cassia\StartingEvent\Answer_10 + { "56bbf1612e05489ba44bb4a52718e222", true }, + // World\Dialogs\Companions\Romances\Cassia\StartingEvent\Answer_5 + { "eb76f93740824d16b1e1f54b82de21e0", true }, + // World\Dialogs\Companions\Romances\Cassia\StartingEvent\Answer_8 + { "c292b399f4344a639ccb4df9ba66329e", true }, + // World\Dialogs\Companions\Romances\Cassia\StartingEvent\CassFirstTimeBlushing_a + { "95b0ba7d08e34f6c895b2fbeb53ea404", true }, + // Dialogs\Companions\CompanionQuests\Navigator\Navigator_Q1\CassiaSeriousTalk\Answer_8 + { "966f0cc2defa42bd836950aa1ebcde72", true }, + // World\Dialogs\Companions\CompanionDialogues\Navigator\Cue_24; Override not necessary as both male and female responses are fitting + // { "a903589840ba4ab683d6e6b9f985d458", true }, + // World\Dialogs\Ch3\Chasm\PitCassia\Answer_11 + { "c051d0c9f2ba4c23bff1d1e6f2cfe13d", true }, + // World\Dialogs\Ch3\Chasm\PitCassia\Answer_12 + { "3d24df76aacf4e2db047cf47ef3474d5", true }, + // World\Dialogs\Ch3\Chasm\PitCassia\Answer_19 + { "b3601cd9e84d43dbb4078bf77c89d728", true }, + // World\Dialogs\Ch3\Chasm\PitCassia\Answer_6 + { "17b34e1ae36443408805af3a3c2866f7", true }, + // World\Dialogs\Ch3\Chasm\PitCassia\Cue_29 + { "7f71e0b93dd9420d87151fc3e7114865", true }, + // World\Dialogs\Companions\CompanionDialogues\Navigator\Cue_47 + { "588a3c2e96c6403ca2c7104949b066e4", true }, + // World\Dialogs\Companions\CompanionQuests\Navigator\Navigator_Q2\Cassia_Q2_BE\Cue_0037 + { "bf7813b4ee3d49cdbc6305f454479db3", true }, + // World\Dialogs\Epilogues\Cue_378 + { "4d285cc256004e968d29730c5f9b2a5f", true } + }; + + // Path Romance Overrides + // These modify the EtudeStatus condition for specific Owner blueprints + internal static readonly Dictionary<(string, string), bool> ConditionCheckOverridesLoveIsFree = new() { + }; + internal static readonly Dictionary<string, bool> EtudeStatusOverridesLoveIsFree = new() { + }; + + // Multiple Romances overrides + // This modify the EtudeStatus condition for specific Owner blueprints + internal static readonly Dictionary<(string, string), bool> ConditionCheckOverrides = new() { + // Cue_43 (I assume Kibella) check for any active romance + { ("f4b42351c500429ba9cfbccf352ddd3b", "6a8601e5d98a450f8ed8b644c1cf1fea"), false }, + // Cue_43 (I assume Kibella) check jealousy dialog seen or active romances = 1 + { ("f4b42351c500429ba9cfbccf352ddd3b", "09f80fe401704413864b879f3bdfb970"), false } + }; + // Multiple Romances overrides + // This modifies all conditions of the blueprint with the specified id + internal static readonly Dictionary<string, bool> AllConditionCheckOverrides = new() { + // Kibellah romance after coming back (All) + { "d269a5417ca646759584a2ab7bddf319", false }, { "5ca382ed53964851bd19ce07efb7bb8c", false }, + // Kibellah romance after coming back (Cas) + { "683bf82b7663452a9fb92955b4b1d031", false }, { "aae351192ac24f84ab36e1839c1ab7c3", false }, + // Kibellah romance after coming back (Hein) + { "6b08fa9121c54f3c811536fef69f12c9", false }, { "ec45728761064a27bfafca1fbfa65355", false }, + // Kibellah romance after coming back (Jae) + { "25163b765a8442e2928f3423f080fa57", false }, { "83953390c5764c25a9d2d0b1f899f905", false }, + // Kibellah romance after coming back (Mar) + { "7d30413d5b7a426aa891b1e282792134", false }, { "2b989a9de0f04c2c848269294a0a4452", false }, + // Kibellah romance after coming back (Yrl) + { "c409b0626cce411ab6720916f310d9f2", false }, { "efc090e2c9794fac855be6c597637bda", false } + }; + internal static readonly Dictionary<string, bool> EtudeStatusOverrides = new() { + }; + internal static readonly Dictionary<string, bool> FlagInRangeOverrides = new() { + // RomanceCount Flag, as conditioned in Jealousy_event Blueprint, Activated by Jealousy_preparation + { "cbb219fcb46948fba48a8bed94663e5d", false } + }; + + + [HarmonyPatch(typeof(PcFemale), nameof(PcFemale.CheckCondition))] + public static class PcFemale_CheckCondition_Patch { + public static void Postfix(PcFemale __instance, ref bool __result) { + if (!settings.toggleAllowAnyGenderRomance || __instance?.Owner is null) return; + OwlLogging.Log($"checking {__instance} guid:{__instance.AssetGuid} owner:{__instance.Owner.name} guid: {__instance.Owner.AssetGuid}) value: {__result}"); + if (PcFemaleOverrides.TryGetValue(__instance.Owner.AssetGuid.ToString(), out var value)) { Mod.Debug($"overiding {__instance.Owner.name} to {value}"); __result = value; } + } + } + [HarmonyPatch(typeof(PcMale), nameof(PcMale.CheckCondition))] + public static class PcMale_CheckCondition_Patch { + public static void Postfix(PcMale __instance, ref bool __result) { + if (!settings.toggleAllowAnyGenderRomance || __instance?.Owner is null) return; + OwlLogging.Log($"checking {__instance} guid:{__instance.AssetGuid} owner:{__instance.Owner.name} guid: {__instance.Owner.AssetGuid}) value: {__result}"); + if (PcMaleOverrides.TryGetValue(__instance.Owner.AssetGuid.ToString(), out var value)) { Mod.Debug($"overiding {__instance.Owner.name} to {value}"); __result = value; } + } + } + + [HarmonyPatch(typeof(Condition), nameof(Condition.Check), [typeof(ConditionsChecker), typeof(IConditionDebugContext)])] + public static class Condition_Check_Patch { + public static void Postfix(Condition __instance, ref bool __result) { + if (__instance?.Owner is null) return; + + var key = (__instance.Owner.AssetGuid, __instance.AssetGuid); + if (settings.toggleAllowAnyGenderRomance) { + if (ConditionCheckOverridesLoveIsFree.TryGetValue(key, out var valueLoveIsFree)) { OwlLogging.Log($"overiding {(__instance.Owner.name, __instance.name)} to {valueLoveIsFree}"); __result = valueLoveIsFree; } + } + if (settings.toggleMultipleRomance) { + if (ConditionCheckOverrides.TryGetValue(key, out var value)) { + OwlLogging.Log($"overiding {(__instance.Owner.name, __instance.name)} to {value}"); + __result = value; + } + if (AllConditionCheckOverrides.TryGetValue(__instance.Owner.AssetGuid, out value)) { + OwlLogging.Log($"overiding {(__instance.Owner.name, __instance.name)} to {value}"); + __result = value; + } + } + } + } + + [HarmonyPatch(typeof(EtudeStatus), nameof(EtudeStatus.CheckCondition))] + public static class EtudeStatus_CheckCondition_Patch { + public static void Postfix(EtudeStatus __instance, ref bool __result) { + if (__instance?.Owner is null) return; + + var key = (__instance.Owner.AssetGuid.ToString()); + if (settings.toggleAllowAnyGenderRomance) { + if (EtudeStatusOverridesLoveIsFree.TryGetValue(key, out var valueLoveIsFree)) { OwlLogging.Log($"overiding {(__instance.Owner.name)} to {valueLoveIsFree}"); __result = valueLoveIsFree; } + } + if (settings.toggleMultipleRomance) { + if (EtudeStatusOverrides.TryGetValue(key, out var value)) { OwlLogging.Log($"overiding {__instance.Owner.name} to {value}"); __result = value; } + } + } + } + + [HarmonyPatch(typeof(FlagInRange), nameof(FlagInRange.CheckCondition))] + public static class FlagInRange_CheckCondition_Patch { + public static void Postfix(FlagInRange __instance, ref bool __result) { + if (__instance?.Owner is null) return; + if (settings.toggleMultipleRomance) { + if (FlagInRangeOverrides.TryGetValue(__instance.Owner.AssetGuid.ToString(), out var value)) { OwlLogging.Log($"overiding {__instance.Owner.name} to {value}"); __result = value; } + } + } + } + [HarmonyPatch(typeof(Kingmaker.Designers.EventConditionActionSystem.Conditions.RomanceLocked), nameof(Kingmaker.Designers.EventConditionActionSystem.Conditions.RomanceLocked.CheckCondition))] + public static class RomanceLocked_CheckCondition_Patch { + public static void Postfix(ref bool __result) { + if (settings.toggleMultipleRomance) { + if (__result) OwlLogging.Log("Overriding RomanceLocked.CheckCondition result to false"); + __result = false; + } + } + } + } +} diff --git a/ToyBox/Classes/MonkeyPatchin/BagOfPatches/Tweaks.cs b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/Tweaks.cs new file mode 100644 index 000000000..86d2cdd46 --- /dev/null +++ b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/Tweaks.cs @@ -0,0 +1,56 @@ +using HarmonyLib; +using Kingmaker.Blueprints.Root; +using Kingmaker.Controllers.Units; +using Kingmaker.UnitLogic; +using Kingmaker.View.MapObjects.Traps; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; + +namespace ToyBox.BagOfPatches { + [HarmonyPatch] + internal static partial class Tweaks { + [HarmonyPatch(typeof(TrapObjectData), nameof(TrapObjectData.TryTriggerTrap)), HarmonyPrefix] + private static bool TryTriggerTrap() { + if (Settings.disableTraps) { + return false; + } + return true; + } + [HarmonyPatch] + private static class Sprint_Walk_Range_Patches { + private static int GetMaxWalkDistance() { + return (int)(Settings.walkRangeMultiplier * BlueprintRoot.Instance.MaxWalkDistance); + } + private static int GetMinSprintDistance() { + return (int)(Settings.sprintRangeMultiplier * BlueprintRoot.Instance.MinSprintDistance); + } + [HarmonyTargetMethods] + public static IEnumerable<MethodInfo> GetMethods() { + yield return AccessTools.Method(typeof(UnitHelper), nameof(UnitHelper.WalkSpeedTypeByDistanceRT)); + yield return AccessTools.Method(typeof(UnitCommandsRunner), nameof(UnitCommandsRunner.TryApproachAndInteract)); + } + [HarmonyTranspiler] + private static IEnumerable<CodeInstruction> CreateMoveCommandParamsRT(IEnumerable<CodeInstruction> instructions) { + var fieldInfo = AccessTools.Field(typeof(BlueprintRoot), nameof(BlueprintRoot.MaxWalkDistance)); + var methodInfo = AccessTools.Method(typeof(Sprint_Walk_Range_Patches), nameof(GetMaxWalkDistance)); + + var fieldInfo2 = AccessTools.Field(typeof(BlueprintRoot), nameof(BlueprintRoot.MinSprintDistance)); + var methodInfo2 = AccessTools.Method(typeof(Sprint_Walk_Range_Patches), nameof(GetMinSprintDistance)); + foreach (var instruction in instructions) { + if (instruction.opcode == OpCodes.Ldfld && instruction.operand as FieldInfo == fieldInfo) { + yield return new CodeInstruction(OpCodes.Pop); + yield return new CodeInstruction(OpCodes.Call, methodInfo); + continue; + } + if (instruction.opcode == OpCodes.Ldfld && instruction.operand as FieldInfo == fieldInfo2) { + yield return new CodeInstruction(OpCodes.Pop); + yield return new CodeInstruction(OpCodes.Call, methodInfo2); + continue; + } + yield return instruction; + } + } + } + } +} \ No newline at end of file diff --git a/ToyBox/Classes/MonkeyPatchin/BagOfPatches/TweaksRT.cs b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/TweaksRT.cs new file mode 100644 index 000000000..9b4d98ef1 --- /dev/null +++ b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/TweaksRT.cs @@ -0,0 +1,369 @@ +// borrowed shamelessly and enhanced from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/26, which is under the MIT License +using DG.Tweening; +using HarmonyLib; +using Kingmaker; +using Kingmaker.Blueprints.Items.Equipment; +using Kingmaker.Blueprints.Root.Strings; +using Kingmaker.Cargo; +using Kingmaker.Cheats; +using Kingmaker.Code.UI.MVVM.View.LoadingScreen; +using Kingmaker.Code.UI.MVVM.View.MainMenu.PC; +using Kingmaker.Code.UI.MVVM.VM.MessageBox; +using Kingmaker.Code.UI.MVVM.VM.ServiceWindows.Inventory; +using Kingmaker.Code.UI.MVVM.VM.ShipCustomization; +using Kingmaker.Code.UI.MVVM.VM.Slots; +using Kingmaker.Code.UI.MVVM.VM.WarningNotification; +using Kingmaker.Controllers.Combat; +using Kingmaker.Controllers.MapObjects; +using Kingmaker.Controllers.TurnBased; +using Kingmaker.Designers; +using Kingmaker.DLC; +using Kingmaker.ElementsSystem.ContextData; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.GameCommands; +using Kingmaker.Items; +using Kingmaker.Items.Slots; +using Kingmaker.Networking; +using Kingmaker.Pathfinding; +using Kingmaker.PubSubSystem; +using Kingmaker.UI; +using Kingmaker.UI.Common; +using Kingmaker.UI.Legacy.MainMenuUI; +using Kingmaker.UI.Sound; +using Kingmaker.UnitLogic.Abilities; +using Kingmaker.Utility; +using Kingmaker.View.Covers; +using Kingmaker.Visual.Sound; +using ModKit; +using Owlcat.Runtime.Core.Utility; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using UniRx; +using UnityEngine; +using Warhammer.SpaceCombat.Blueprints; +using static Kingmaker.UnitLogic.Abilities.AbilityData; + +namespace ToyBox.BagOfPatches { + internal static partial class Tweaks { + public static Settings Settings = Main.Settings; + public static Player player => Game.Instance.Player; + + [HarmonyPatch(typeof(TurnController))] + private static class TurnController_Patch { + [HarmonyPatch(nameof(TurnController.IsPlayerTurn), MethodType.Getter)] + [HarmonyPostfix] + private static void IsPlayerTurn(TurnController __instance, ref bool __result) { + if (__instance.CurrentUnit == null) return; + if (Main.Settings.perSave.doOverrideEnableAiForCompanions.TryGetValue(__instance.CurrentUnit.HashKey(), out var maybeOverride)) { + if (maybeOverride.Item1) { + __result = !maybeOverride.Item2; + } + } + } + [HarmonyPatch(nameof(TurnController.IsAiTurn), MethodType.Getter)] + [HarmonyPostfix] + private static void IsAiTurn(TurnController __instance, ref bool __result) { + if (__instance.CurrentUnit == null) return; + if (Main.Settings.perSave.doOverrideEnableAiForCompanions.TryGetValue(__instance.CurrentUnit.HashKey(), out var maybeOverride)) { + if (maybeOverride.Item1) { + __result = maybeOverride.Item2; + } + } + } + } + /* + [HarmonyPatch(typeof(HandSlot))] + public static class A { + [HarmonyPatch(nameof(HandSlot.IsItemSupported)), HarmonyFinalizer] + private static Exception Wow(ref bool __result, Exception __exception) { + if (__exception != null) __result = false; + return null; + } + } + [HarmonyPatch(typeof(PartUnitBody))] + private static class B { + [HarmonyPatch(nameof(PartUnitBody.GetHandsEquipmentSet)), HarmonyPostfix] + private static void Wow(HandsEquipmentSet __result, PartUnitBody __instance, HandSlot slot) { + if (__result == null) { + for (int i = 0; i < __instance.m_HandsEquipmentSets.Length; i++) { + HandsEquipmentSet handsEquipmentSet = __instance.m_HandsEquipmentSets[i]; + if (handsEquipmentSet.PrimaryHand == slot) { + Mod.Log($"Primary == Slot but: {handsEquipmentSet.IsOverridePrimaryHand}"); + } + if (handsEquipmentSet.SecondaryHand == slot) { + Mod.Log($"Primary == Slot but: {handsEquipmentSet.IsOverrideSecondaryHand}"); + } + } + } + } + } + */ + [HarmonyPatch(typeof(PartUnitCombatState))] + private static class PartUnitCombatStatePatch { + public static void MaybeKill(PartUnitCombatState unitCombatState) { + if (Settings.togglekillOnEngage) { + List<BaseUnitEntity> partyUnits = Game.Instance.Player.m_PartyAndPets; + BaseUnitEntity unit = unitCombatState.Owner; + if (unit.CombatGroup.IsEnemy(GameHelper.GetPlayerCharacter()) + && !partyUnits.Contains(unit)) { + CheatsCombat.KillUnit(unit); + } + } + } + + [HarmonyPatch(nameof(PartUnitCombatState.JoinCombat))] + [HarmonyPostfix] + public static void JoinCombat(PartUnitCombatState __instance, bool surprised) { + MaybeKill(__instance); + } + } + + [HarmonyPatch(typeof(GameHistoryLog), nameof(GameHistoryLog.HandlePartyCombatStateChanged))] + private static class GameHistoryLogHandlePartyCombatStateChangedPatch { + private static void Postfix(ref bool inCombat) { + if (!inCombat && Settings.toggleRestoreSpellsAbilitiesAfterCombat) { + var partyMembers = Game.Instance.Player.PartyAndPets; + foreach (var u in partyMembers) { + foreach (var resource in u.AbilityResources) + u.AbilityResources.Restore(resource); + u.Brain.RestoreAvailableActions(); + } + } + if (!inCombat && Settings.toggleInstantRestAfterCombat) { + CheatsCombat.RestAll(); + } + } + } + [HarmonyPatch(typeof(AbilityData))] + private static class AbilityDataPatch { + //[HarmonyPatch(nameof(AbilityData.CanTargetFromNode), new Type[] {typeof(CustomGridNodeBase), typeof(CustomGridNodeBase), typeof(TargetWrapper), typeof(int), typeof(LosCalculations.CoverType), typeof(UnavailabilityReasonType?)})] + [HarmonyPatch(nameof(AbilityData.CanTargetFromNode), + new Type[] { + typeof(CustomGridNodeBase), + typeof(CustomGridNodeBase), + typeof(TargetWrapper), + typeof(int), + typeof(LosCalculations.CoverType), + typeof(UnavailabilityReasonType?), + typeof(int?) + }, + new ArgumentType[] { + ArgumentType.Normal, + ArgumentType.Normal, + ArgumentType.Normal, + ArgumentType.Out, + ArgumentType.Out, + ArgumentType.Out, + ArgumentType.Normal + })] + [HarmonyPostfix] + public static void CanTargetFromNode( + CustomGridNodeBase casterNode, + CustomGridNodeBase targetNodeHint, + TargetWrapper target, + ref int distance, + ref LosCalculations.CoverType los, + ref UnavailabilityReasonType? unavailabilityReason, + AbilityData __instance, + ref bool __result + ) { + if (!Settings.toggleIgnoreAbilityAnyRestriction) return; + if (!(__instance?.Caster?.IsPartyOrPet() ?? false)) return; + if (__result) return; + + if (unavailabilityReason is UnavailabilityReasonType reason) { + switch (reason) { + case UnavailabilityReasonType.AreaEffectsCannotOverlap: + if (Settings.toggleIgnoreAbilityAnyRestriction || Settings.toggleIgnoreAbilityAoeOverlap) + __result = true; + break; + case UnavailabilityReasonType.HasNoLosToTarget: + if (Settings.toggleIgnoreAbilityAnyRestriction || Settings.toggleIgnoreAbilityLineOfSight) + __result = true; + break; + case UnavailabilityReasonType.TargetTooFar: + if (Settings.toggleIgnoreAbilityAnyRestriction || Settings.toggleIgnoreAbilityTargetTooFar) + __result = true; + break; + case UnavailabilityReasonType.TargetTooClose: + if (Settings.toggleIgnoreAbilityAnyRestriction || Settings.toggleIgnoreAbilityTargetTooClose) + __result = true; + break; + default: + if (Settings.toggleIgnoreAbilityAnyRestriction) + __result = true; + break; + } + } else if (Settings.toggleIgnoreAbilityAnyRestriction) + __result = true; + } + } + [HarmonyPatch(typeof(MainMenuPCView))] + private static class MainMenuPCViewPatch { + [HarmonyPatch(nameof(MainMenuPCView.BindViewImplementation))] + [HarmonyPostfix] + private static void BindViewImplementation(MainMenuPCView __instance) { + if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) { + Main.freshlyLaunched = false; + Mod.Warn("Auto Load Save on Launch disabled"); + return; + } + if (Settings.toggleAutomaticallyLoadLastSave && Main.freshlyLaunched) { + Main.freshlyLaunched = false; + Game.Instance.SaveManager.UpdateSaveListIfNeeded(); + MainThreadDispatcher.StartCoroutine(UIUtilityCheckSaves.WaitForSaveUpdated(() => { __instance.ViewModel.LoadLastGame(); })); + } + Main.freshlyLaunched = false; + } + } + + [HarmonyPatch(typeof(LoadingScreenBaseView))] + public static class LoadingScreenBaseViewPatch { + [HarmonyPatch(nameof(LoadingScreenBaseView.ShowUserInputWaiting))] + [HarmonyPrefix] + private static bool ShowUserInputLayer(LoadingScreenBaseView __instance, bool state) { + if (!Settings.toggleSkipAnyKeyToContinueWhenLoadingSaves) return true; + if (!state) + return false; + __instance.m_ProgressBarContainer.DOFade(0.0f, 1f).OnComplete(() => __instance.StartPressAnyKeyLoopAnimation()).SetUpdate(true); + __instance.AddDisposable(MainThreadDispatcher.UpdateAsObservable() + .Subscribe(_ => { + UISounds.Instance.Sounds.Buttons.ButtonClick.Play(); + if (PhotonManager.Lobby.IsLoading) + PhotonManager.Instance.ContinueLoading(); + EventBus.RaiseEvent((Action<IContinueLoadingHandler>)(h => h.HandleContinueLoading())); + })); + return false; + } + } + public static class UnitEntityDataCanRollPerceptionExtension { + public static bool TriggerReroll = false; + public static bool CanRollPerception(BaseUnitEntity unit) { + if (TriggerReroll) { + return true; + } + + return unit.MovementAgent.Position.To2D() != unit.MovementAgent.m_PreviousPosition; + } + } + [HarmonyPatch] + public static class EquipDuringCombat_Transpiler_Patches { + private static string[] InventoryHelperTargetMethodNames = ["TryDrop", "TryEquip", "TryMoveSlotInInventory", "TryMoveToCargo", "TryUnequip", "CanChangeEquipment", "CanEquipItem"]; + [HarmonyTargetMethods] + public static IEnumerable<MethodBase> GetMethods() { + foreach (var method in AccessTools.GetDeclaredMethods(typeof(InventoryHelper))) { + if (InventoryHelperTargetMethodNames.Contains(method.Name)) { + yield return method; + } + } + yield return AccessTools.Method(typeof(InventoryDollVM), nameof(InventoryDollVM.ChooseSlotToItem)); + yield return typeof(InventoryDollVM).GetNestedTypes(BindingFlags.NonPublic | BindingFlags.Instance).SelectMany(t => t.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)).First(m => m.Name.Contains("TryInsertItem")); + yield return AccessTools.Method(typeof(ItemSlot), nameof(ItemSlot.IsPossibleInsertItems)); + yield return AccessTools.Method(typeof(ItemSlot), nameof(ItemSlot.IsPossibleRemoveItems)); + yield return AccessTools.Method(typeof(ArmorSlot), nameof(ArmorSlot.IsItemSupported)); + yield return AccessTools.Method(typeof(ArmorSlot), nameof(ArmorSlot.CanRemoveItem)); + } + [HarmonyTranspiler] + public static IEnumerable<CodeInstruction> ChooseSlotToItem(IEnumerable<CodeInstruction> instructions) { + bool skipNext = false; + foreach (var inst in instructions) { + if (skipNext) { + skipNext = false; + continue; + } + if (inst.Calls(AccessTools.Method(typeof(Game), "get_Player"))) { + skipNext = true; + yield return CodeInstruction.Call((Game game) => ShouldPreventInsertion(game)); + continue; + } + if (inst.Calls(AccessTools.Method(typeof(TurnController), "get_TurnBasedModeActive"))) { + yield return CodeInstruction.Call((TurnController controller) => ShouldPreventInsertion(controller)); + continue; + } + if (inst.Calls(AccessTools.Method(typeof(MechanicEntity), "get_IsInCombat"))) { + yield return CodeInstruction.Call((MechanicEntity entity) => ShouldPreventInsertion(entity)); + continue; + } + yield return inst; + } + } + public static bool ShouldPreventInsertion(MechanicEntity entity) { + if (Settings.toggleEquipItemsDuringCombat) return false; + else return entity.IsInCombat; + } + public static bool ShouldPreventInsertion(TurnController controller) { + if (Settings.toggleEquipItemsDuringCombat) return false; + else return controller.TurnBasedModeActive; + } + public static bool ShouldPreventInsertion(Game game) { + if (Settings.toggleEquipItemsDuringCombat) return false; + else return game.Player.IsInCombat; + } + } + [HarmonyPatch(typeof(ItemEntity), nameof(ItemEntity.IsUsableFromInventory), MethodType.Getter)] + public static class ItemEntityIsUsableFromInventoryPatch { + // Allow Item Use From Inventory During Combat + public static bool Prefix(ItemEntity __instance, ref bool __result) { + if (!Settings.toggleUseItemsDuringCombat) return true; + return __instance.Blueprint is not BlueprintItemEquipmentUsable; + } + } + + [HarmonyPatch(typeof(PartyAwarenessController))] + public static class PartyAwarenessControllerPatch { +#if false // TODO: why does this crash the game on load into area + public static MethodInfo HasMotionThisSimulationTick_Method = AccessTools.DeclaredMethod(typeof(PartMovable), "get_HasMotionThisSimulationTick"); + public static MethodInfo CanRollPerception_Method = AccessTools.DeclaredMethod(typeof(UnitEntityData_CanRollPerception_Extension), "CanRollPerception"); + + [HarmonyPatch(nameof(PartyAwarenessController.Tick))] + [HarmonyTranspiler] + private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { + foreach (var instr in instructions) { + if (instr.Calls(HasMotionThisSimulationTick_Method)) { + Mod.Trace("Found HasMotionThisSimulationTick and modded it"); + yield return new CodeInstruction(OpCodes.Call, CanRollPerception_Method); + } + else { + yield return instr; + } + } + } +#endif + [HarmonyPatch(nameof(PartyAwarenessController.Tick))] + [HarmonyPostfix] + private static void Tick() => UnitEntityDataCanRollPerceptionExtension.TriggerReroll = false; + } + [HarmonyPatch] + public static class SkipSplashScreen_Patch { + [HarmonyPrepare] + public static bool Prepare() => Settings.toggleSkipSplashScreen; + [HarmonyTargetMethods] + public static IEnumerable<MethodInfo> PatchTargets() { + yield return AccessTools.Method(typeof(SplashScreenController), nameof(SplashScreenController.ShowSplashScreen)); + yield return AccessTools.Method(typeof(MainMenuLoadingScreen), nameof(MainMenuLoadingScreen.OnStart)); + } + [HarmonyTranspiler] + public static IEnumerable<CodeInstruction> Start(IEnumerable<CodeInstruction> instructions) { + foreach (var inst in instructions) { + if (inst.Calls(AccessTools.Method(typeof(GameStarter), nameof(GameStarter.IsSkippingMainMenu)))) { + yield return new CodeInstruction(OpCodes.Ldc_I4_1).WithLabels(inst.labels); + } else if (inst.LoadsConstant("Logo Show Requested")) { + yield return new(OpCodes.Ldarg_0); + yield return CodeInstruction.Call((string _, MainMenuLoadingScreen screen) => Helper(_, screen)); + yield return new(OpCodes.Ret); + break; + } else { + yield return inst; + } + } + } + private static void Helper(string _, MainMenuLoadingScreen screen) { + screen.gameObject.SetActive(false); + GameStarter.Instance.StartGame(); + } + } + } +} diff --git a/ToyBox/Classes/MonkeyPatchin/BagOfPatches/VoiceOver.cs b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/VoiceOver.cs new file mode 100644 index 000000000..019921adf --- /dev/null +++ b/ToyBox/Classes/MonkeyPatchin/BagOfPatches/VoiceOver.cs @@ -0,0 +1,67 @@ +using HarmonyLib; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.EntitySystem; +using Kingmaker.Localization; +using Kingmaker.UI; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ModKit; +using Kingmaker.Code.UI.MVVM.VM.Bark; +using Kingmaker.Code.UI.MVVM.VM.Dialog.Dialog; +using Kingmaker.Controllers.Dialog; +using System.Diagnostics; +using Kingmaker.UnitLogic.Mechanics.Blueprints; +using Kingmaker; +using Kingmaker.Blueprints; +using UnityEngine; + +namespace ToyBox.BagOfPatches { + [HarmonyPatch] + internal static class VoiceOver { + internal static BlueprintUnit currentSpeaker = null; + [HarmonyPatch(typeof(BarkPlayer))] + internal static class BarkPlayer_Patches { + [HarmonyPatch(nameof(BarkPlayer.Bark), [typeof(Entity), typeof(LocalizedString), typeof(string), typeof(float), typeof(bool)])] + [HarmonyPrefix] + internal static void Bark1(Entity entity) { + if (entity is BaseUnitEntity entity2) { + currentSpeaker = entity2?.Blueprint; + } else { + currentSpeaker = null; + } + } + [HarmonyPatch(nameof(BarkPlayer.Bark), [typeof(Entity), typeof(LocalizedString), typeof(float), typeof(bool), typeof(BaseUnitEntity), typeof(bool), typeof(string), typeof(Color)])] + [HarmonyPrefix] + internal static void Bark2(Entity entity) { + if (entity is BaseUnitEntity entity2) { + currentSpeaker = entity2?.Blueprint; + } else { + currentSpeaker = null; + } + } + } + [HarmonyPatch(typeof(DialogVM), nameof(DialogVM.HandleOnCueShow))] + [HarmonyPrefix] + internal static void DialogVM_HandleOnCueShow(CueShowData data) { + currentSpeaker = data?.Cue?.Speaker?.Blueprint ?? Game.Instance.DialogController?.CurrentSpeaker?.Blueprint; + } + [HarmonyPatch(typeof(SpaceEventVM), nameof(SpaceEventVM.HandleOnCueShow))] + [HarmonyPrefix] + internal static void SpaceEventVM_HandleOnCueShow(CueShowData data) { + currentSpeaker = data?.Cue?.Speaker?.Blueprint ?? Game.Instance.DialogController?.CurrentSpeaker?.Blueprint; + } + [HarmonyPatch(typeof(LocalizedString), nameof(LocalizedString.GetVoiceOverSound))] + [HarmonyPrefix] + internal static bool GetVoiceOverSound(ref string __result) { + var cName = currentSpeaker?.CharacterName?.ToLower() ?? currentSpeaker?.AssetGuid?.ToString() ?? ""; + if (cName != "" && Main.Settings.namesToDisableVoiceOver.Contains(cName)) { + __result = ""; + return false; + } + return true; + } + } +} diff --git a/ToyBox/Classes/MonkeyPatchin/EnhancedUI/LocalMap.cs b/ToyBox/Classes/MonkeyPatchin/EnhancedUI/LocalMap.cs new file mode 100644 index 000000000..e004df94c --- /dev/null +++ b/ToyBox/Classes/MonkeyPatchin/EnhancedUI/LocalMap.cs @@ -0,0 +1,122 @@ +using HarmonyLib; +using Kingmaker; +using Kingmaker.Blueprints.Area; +using Kingmaker.Code.UI.MVVM.View.Overtips.Unit; +using Kingmaker.Code.UI.MVVM.View.ServiceWindows.LocalMap; +using Kingmaker.Code.UI.MVVM.View.ServiceWindows.LocalMap.Common.Markers; +using Kingmaker.Code.UI.MVVM.View.ServiceWindows.LocalMap.PC; +using Kingmaker.Code.UI.MVVM.VM.ServiceWindows.LocalMap; +using Kingmaker.Code.UI.MVVM.VM.ServiceWindows.LocalMap.Markers; +using Kingmaker.Code.UI.MVVM.VM.ServiceWindows.LocalMap.Utils; +using Kingmaker.Controllers; +using Kingmaker.Controllers.Clicks.Handlers; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.UnitLogic; +using ModKit; +using System; +using System.Collections.Generic; +using System.Linq; +using UniRx; +using UnityEngine; +using UnityEngine.UI; + +namespace ToyBox.BagOfPatches { + internal static class LocalMapPatches { + public static Settings Settings = Main.Settings; + public static Player player => Game.Instance.Player; + + public static float Zoom = 1.0f; + public static float Width = 0.0f; + public static Vector3 Position = Vector3.zero; + public static Vector3 FrameRotation = Vector3.zero; + [HarmonyPatch(typeof(LocalMapVM))] + internal static class LocalMapVMPatch { + [HarmonyPatch(nameof(LocalMapVM.SetMarkers))] + [HarmonyPrefix] + private static bool SetMarkers(LocalMapVM __instance) { + Mod.Debug($"LocalMapVM.SetMarkers"); + LocalMapModel.Markers.RemoveWhere(m => m.GetMarkerType() == LocalMapMarkType.Invalid); + foreach (var marker in LocalMapModel.Markers) + if (LocalMapModel.IsInCurrentArea(marker.GetPosition())) + __instance.MarkersVm.Add(new LocalMapCommonMarkerVM(marker)); + IEnumerable<BaseUnitEntity> first = Game.Instance.Player.PartyAndPets; + if (Game.Instance.Player.CapitalPartyMode) + first = first.Concat(Game.Instance.Player.RemoteCompanions.Where(u => !u.IsCustomCompanion())); + foreach (var unit in first) + if (unit.View != null + && unit.View.enabled + && !unit + .LifeState + .IsHiddenBecauseDead + && LocalMapModel.IsInCurrentArea(unit.Position) + ) { + __instance.MarkersVm.Add(new LocalMapCharacterMarkerVM(unit)); + __instance.MarkersVm.Add(new LocalMapDestinationMarkerVM(unit)); + } + + foreach (var units in Shodan.MainCharacter + .CombatGroup + .Memory.UnitsList) { + Mod.Debug($"Checking {units.Unit.CharacterName}"); + if (!units.Unit.IsPlayerFaction + && (units.Unit.IsVisibleForPlayer || units.Unit.InterestingnessCoefficent() > 0) + && !units.Unit.Descriptor() + .LifeState.IsDead + && LocalMapModel.IsInCurrentArea(units.Unit.Position) + ) { + __instance.MarkersVm.Add(new LocalMapUnitMarkerVM(units)); + } + } + return false; + } + } + + + + [HarmonyPatch(typeof(LocalMapMarkerPCView), nameof(LocalMapMarkerPCView.BindViewImplementation))] + private static class LocalMapMarkerPCView_BindViewImplementation_Patch { + [HarmonyPostfix] + public static void Postfix(LocalMapMarkerPCView __instance) { + if (__instance == null) + return; + //Mod.Debug($"LocalMapMarkerPCView.BindViewImplementation - {__instance.ViewModel.MarkerType} - {__instance.ViewModel.GetType().Name}"); + if (__instance.ViewModel.MarkerType == LocalMapMarkType.Loot) + __instance.AddDisposable(__instance.ViewModel.IsVisible.Subscribe(value => { + (__instance as LocalMapLootMarkerPCView)? + .gameObject.SetActive(value); + })); + } + + // Helper Function - Not a Patch + private static void UpdateMarker(LocalMapMarkerPCView markerView, BaseUnitEntity unit) { + var count = unit.InterestingnessCoefficent(); + //Mod.Debug($"{unit.CharacterName.orange()} -> unit interestingness: {count}"); + //var attentionMark = markerView.transform.Find("ToyBoxAttentionMark")?.gameObject; + //Mod.Debug($"attentionMark: {attentionMark}"); + var markImage = markerView.transform.Find("Mark").GetComponent<Image>(); + if (count >= 1) { + //Mod.Debug($"adding Mark to {unit.CharacterName.orange()}"); + var mark = markerView.transform; + markImage.color = new Color(1, 1f, 0); + } else { + // attentionMark?.SetActive(false); + markImage.color = new Color(1, 1, 1); + } + } + } + + [HarmonyPatch(typeof(OvertipUnitView))] + private static class OvertipUnitView_Patch { + [HarmonyPatch(nameof(OvertipUnitView.BindViewImplementation))] + [HarmonyPostfix] + public static void BindViewImplementation(OvertipUnitView __instance) { + if (!Settings.toggleShowInterestingNPCsOnLocalMap) return; + } + [HarmonyPatch(nameof(OvertipUnitView.UpdateVisibility))] + [HarmonyPostfix] + public static void UpdateInternal(OvertipUnitView __instance) { + if (!Settings.toggleShowInterestingNPCsOnLocalMap || __instance is null) return; + } + } + } +} \ No newline at end of file diff --git a/ToyBox/classes/MonkeyPatchin/EnhancedUI/Loot.cs b/ToyBox/Classes/MonkeyPatchin/EnhancedUI/LootRT.cs similarity index 66% rename from ToyBox/classes/MonkeyPatchin/EnhancedUI/Loot.cs rename to ToyBox/Classes/MonkeyPatchin/EnhancedUI/LootRT.cs index 832f204a9..4e2d517a9 100644 --- a/ToyBox/classes/MonkeyPatchin/EnhancedUI/Loot.cs +++ b/ToyBox/Classes/MonkeyPatchin/EnhancedUI/LootRT.cs @@ -1,63 +1,157 @@ using HarmonyLib; using Kingmaker; using Kingmaker.Blueprints; +using Kingmaker.Blueprints.Items; using Kingmaker.Blueprints.Items.Armors; using Kingmaker.Blueprints.Items.Components; using Kingmaker.Blueprints.Items.Weapons; -using Kingmaker.Items; -using Kingmaker.Items.Parts; -using Kingmaker.UI.MVVM._PCView.Loot; -using Kingmaker.UI.MVVM._PCView.ServiceWindows.Inventory; -using Kingmaker.UI.MVVM._PCView.ServiceWindows.LocalMap.Markers; -using Kingmaker.UI.MVVM._PCView.Slots; -using Kingmaker.UI.MVVM._PCView.Tooltip.Bricks; -using Kingmaker.UI.MVVM._PCView.Vendor; -using Kingmaker.UI.MVVM._VM.ServiceWindows.Inventory; -using Kingmaker.UI.MVVM._VM.ServiceWindows.LocalMap.Utils; -using static ToyBox.BlueprintExtensions; -using Kingmaker.UI.MVVM._VM.Slots; -using UniRx; -using Owlcat.Runtime.UI.MVVM; -using UnityEngine; -using UnityEngine.UI; -using System; -using System.Linq; -using System.Linq.Expressions; -using Kingmaker.Items.Slots; -using Kingmaker.UI.Common; -using System.Collections.Generic; -using ModKit; using Kingmaker.Blueprints.Root; -using Kingmaker.EntitySystem.Entities; -using ModKit.Utility; -using Kingmaker.UI.Tooltip; using Kingmaker.Blueprints.Root.Strings; +using Kingmaker.BundlesLoading; +using Kingmaker.EntitySystem; +using Kingmaker.EntitySystem.Entities; +using Kingmaker.EntitySystem.Persistence.Scenes; +using Kingmaker.Items; +using Kingmaker.Mechanics.Entities; +using Kingmaker.Modding; +using Kingmaker.PubSubSystem; +using Kingmaker.UI.Common; using Kingmaker.UnitLogic; -using System.Text; +using Kingmaker.UnitLogic.Parts; using Kingmaker.Utility; -using Kingmaker.UI.MVVM._VM.Party; using Kingmaker.View.MapObjects; -using Owlcat.Runtime.Core.Utils; -using Kingmaker.Blueprints.Items; -using Kingmaker.BundlesLoading; +using ModKit; +using ModKit.Utility; +using Owlcat.Runtime.Core.Utility; +using Owlcat.Runtime.UI.MVVM; +using System; +using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Linq.Expressions; +using System.Net.NetworkInformation; using System.Reflection; using System.Runtime.InteropServices.ComTypes; -using Kingmaker.EntitySystem.Persistence.Scenes; -using Kingmaker.EntitySystem; -using Kingmaker.Modding; +using System.Text; +using UniRx; +using UnityEngine; +using UnityEngine.UI; +using static ToyBox.BlueprintExtensions; +using ItemSlot = Kingmaker.Items.Slots.ItemSlot; namespace ToyBox.Inventory { internal static class Loot { public static Settings Settings = Main.Settings; - public static Player player = Game.Instance.Player; + public static Player player => Game.Instance.Player; +#if false + public static HashSet<EquipSlotType> SelectedLootSlotFilters = new(); + public static InventoryEquipSlotPCView selectedSlot = null; + public static bool ToggleSelectedLootFilter(EquipSlotType slotType, bool? forceState = null) { + var becomeActive = forceState ?? !SelectedLootSlotFilters.Contains(slotType); + if (becomeActive && !SelectedLootSlotFilters.Contains(slotType)) { + SelectedLootSlotFilters.Add(slotType); + } + else if (SelectedLootSlotFilters.Contains(slotType)) + SelectedLootSlotFilters.Remove(slotType); + return becomeActive; + } + public static void ShowLootFilterFeedback(this InventoryEquipSlotPCView equipSlotView, bool active) { + if (equipSlotView.gameObject?.transform is { } slotView + && slotView.Find("CanInsert") is { } canInsert + && slotView.Find("ChangeVisual") is { } changeVisual) { + canInsert.gameObject.SetActive(active); + changeVisual.gameObject.SetActive(active); + if (active) + selectedSlot = equipSlotView; + } + } + public static void SyncLootFilterFeedback(this InventoryEquipSlotView equipSlotView) { + if (equipSlotView is InventoryEquipSlotPCView slotPCView) { + var viewModel = equipSlotView.ViewModel; + var isActive = SelectedLootSlotFilters.Contains(viewModel.SlotType); + slotPCView.ShowLootFilterFeedback(isActive); + if (isActive) + selectedSlot = slotPCView; + } + } + public static void ClearSelectedLootSlotFilters() { + SelectedLootSlotFilters.Clear(); + selectedSlot = null; + SyncSelectedLootSlotFilters(); + } + public static void SyncSelectedLootSlotFilters() { + var inventoryView = UIHelpers.InventoryScreen; + if (inventoryView?.Find("Inventory/Doll")?.GetComponent<InventoryDollPCView>() is InventoryDollPCView dollView) { + dollView.m_Armor.SyncLootFilterFeedback(); + dollView.m_Belt.SyncLootFilterFeedback(); + dollView.m_Feet.SyncLootFilterFeedback(); + dollView.m_Glasses.SyncLootFilterFeedback(); + dollView.m_Gloves.SyncLootFilterFeedback(); + dollView.m_Head.SyncLootFilterFeedback(); + dollView.m_Neck.SyncLootFilterFeedback(); + dollView.m_Ring1.SyncLootFilterFeedback(); + dollView.m_Ring2.SyncLootFilterFeedback(); + dollView.m_Shirt.SyncLootFilterFeedback(); + dollView.m_Shoulders.SyncLootFilterFeedback(); + dollView.m_Wrist.SyncLootFilterFeedback(); + dollView.m_QuickSlots.ForEach(slot => slot.SyncLootFilterFeedback()); + } + } + internal static void SelectedCharacterDidChange() { + //SyncSelectedLootSlotFilters(); + } + [HarmonyPatch(typeof(InventoryEquipSlotPCView))] + private static class InventoryEquipSlotPCViewPatch { + // Modifies equipment slot background to work with rarity coloring + [HarmonyPatch(nameof(InventoryEquipSlotPCView.BindViewImplementation))] + [HarmonyPostfix] + public static void BindViewImplementation(InventoryEquipSlotPCView __instance) { + if (Settings.togglEquipSlotInventoryFiltering) + __instance.SyncLootFilterFeedback(); + if (!Settings.UsingLootRarity) return; + var backfill = __instance.gameObject? + .transform.Find("Backfill"); + var image = backfill?.GetComponent<UnityEngine.UI.Image>(); + if (image != null) { + image.color = ColoredEquipSlotBackgroundColor; + } + } + // Handles Clicks for Equipment Slot -> Inventory Filtering + [HarmonyPatch(nameof(InventoryEquipSlotPCView.OnClick))] + [HarmonyPostfix] + public static void OnClick(InventoryEquipSlotPCView __instance) { + if (!Settings.togglEquipSlotInventoryFiltering) return; + TogglEquipSlotInventoryFiltering(__instance); + } + [HarmonyPatch(nameof(InventoryEquipSlotPCView.OnDoubleClick))] + [HarmonyPostfix] + // This sounds weird but we want double click to reselect it so when you double click to remove something the slot filter stays selected + public static void OnDoubleClick(InventoryEquipSlotPCView __instance) { + if (!Settings.togglEquipSlotInventoryFiltering) return; + TogglEquipSlotInventoryFiltering(__instance); + } + internal static void TogglEquipSlotInventoryFiltering(InventoryEquipSlotPCView __instance) { + var equipSlotVM = __instance.ViewModel; + var slotType = equipSlotVM.SlotType; + var isSelected = SelectedLootSlotFilters.Contains(slotType); + SelectedLootSlotFilters.Clear(); + if (!isSelected) { + SelectedLootSlotFilters.Add(slotType); + } + SyncSelectedLootSlotFilters(); + EventBus.RaiseEvent((Action<IInventoryHandler>)(h => h.Refresh())); + //__instance.ShowLootFilterFeedback(show); + InventoryPCViewPatch.SavedInventoryVM?.StashVM.CollectionChanged(); + } + } // Highlight copyable scolls - [HarmonyPatch(typeof(LootSlotPCView), nameof(LootSlotPCView.BindViewImplementation))] - private static class ItemSlot_IsUsable_Patch { - public static void Postfix(ViewBase<ItemSlotVM> __instance) { + [HarmonyPatch(typeof(LootSlotPCView))] + private static class LootSlotPCViewPatch { + [HarmonyPatch(nameof(LootSlotPCView.BindViewImplementation))] + [HarmonyPostfix] + public static void BindViewImplementation(ViewBase<ItemSlotVM> __instance) { if (__instance is LootSlotPCView itemSlotPCView) { - // modLogger.Log($"checking {itemSlotPCView.ViewModel.Item}"); if (itemSlotPCView.ViewModel.HasItem && itemSlotPCView.ViewModel.IsScroll && Settings.toggleHighlightCopyableScrolls) { // modLogger.Log($"found {itemSlotPCView.ViewModel}"); itemSlotPCView.m_Icon.CrossFadeColor(new Color(0.5f, 1.0f, 0.5f, 1.0f), 0.2f, true, true); @@ -70,16 +164,40 @@ public static void Postfix(ViewBase<ItemSlotVM> __instance) { } // Adds Rarity color circles to items in inventory - [HarmonyPatch(typeof(ItemSlotView<EquipSlotVM>), nameof(ItemSlotView<EquipSlotVM>.RefreshItem))] - private static class ItemSlotView_RefreshItem_Patch { - public static void Postfix(InventoryEquipSlotView __instance) { - if (!__instance.SlotVM.HasItem || !__instance.SlotVM.IsScroll) { - __instance.m_Icon.color = Color.white; + [HarmonyPatch(typeof(ItemSlotView<EquipSlotVM>))] + private static class ItemSlotViewPatch { + [HarmonyPatch(nameof(ItemSlotView<EquipSlotVM>.RefreshItem))] + [HarmonyPostfix] + public static void RefreshItem(InventoryEquipSlotView __instance) { + if (__instance.ViewModel.HasItem && __instance.ViewModel.IsScroll && Settings.toggleHighlightCopyableScrolls) { + // modLogger.Log($"found {itemSlotPCView.ViewModel}"); + __instance.m_Icon.CrossFadeColor(new Color(0.5f, 1.0f, 0.5f, 1.0f), 0.2f, true, true); } - else if (__instance.SlotVM.IsScroll) { - __instance.m_Icon.color = new Color(0.5f, 1.0f, 0.5f, 1.0f); + else { + __instance.m_Icon.CrossFadeColor(Color.white, 0.2f, true, true); } var item = __instance.Item; + if (Settings.togglEquipSlotInventoryFiltering) { + try { + if (__instance.gameObject?.transform is { } inventorySlotView + && inventorySlotView.Find("Item/NeedCheckLayer") is { } conflictFeedback) { + var unit = SelectedCharacterObserver.Shared.SelectedUnit ?? WrathExtensions.GetCurrentCharacter(); + if (unit != null && item != null && SelectedLootSlotFilters.Any()) { + //Mod.Debug($"Unit: {unit.CharacterName}"); + //Mod.Debug($"Item: {item.Blueprint.GetDisplayName()}"); + var hasConflicts = unit.HasModifierConflicts(item); + conflictFeedback.gameObject.SetActive(hasConflicts); + var icon = conflictFeedback.GetComponent<Image>(); + icon.color = new Color(1.0f, 0.8f, 0.3f, 0.75f); + } + else + conflictFeedback.gameObject.SetActive(false); + } + } + catch (Exception e) { + Mod.Error(e); + } + } if (Settings.UsingLootRarity && item != null) { _ = item.Blueprint.GetComponent<AddItemShowInfoCallback>(); var cb = item.Get<ItemPartShowInfoCallback>(); @@ -146,9 +264,19 @@ public static void Postfix(ItemEntity __instance, ref string __result) { internal static Color ColoredEquipSlotBackgroundColor = new(1f, 1f, 1f, 0.45f); // Modifies inventory slot background to work with rarity coloring - [HarmonyPatch(typeof(InventoryPCView), nameof(InventoryPCView.BindViewImplementation))] - private static class InventoryPCView_BindViewImplementation_Patch { - public static void Postfix(InventoryPCView __instance) { + [HarmonyPatch(typeof(InventoryPCView))] + private static class InventoryPCViewPatch { + public static InventoryVM SavedInventoryVM = null; + [HarmonyPatch(nameof(InventoryPCView.BindViewImplementation))] + [HarmonyPostfix] + public static void BindViewImplementation(InventoryPCView __instance) { + SavedInventoryVM = __instance.ViewModel; + if (Settings.togglEquipSlotInventoryFiltering) { + ClearSelectedLootSlotFilters(); + SavedInventoryVM.StashVM.CollectionChanged(); + SelectedCharacterObserver.Shared.Notifiers -= SelectedCharacterDidChange; + SelectedCharacterObserver.Shared.Notifiers += SelectedCharacterDidChange; + } if (!Settings.UsingLootRarity) return; var decoration = __instance.gameObject? .transform.Find("Inventory/Stash/StashContainer/StashScrollView/decoration"); @@ -157,22 +285,14 @@ public static void Postfix(InventoryPCView __instance) { image.color = ColoredLootBackgroundColor; } } - } - - // Modifies equipment slot background to work with rarity coloring - [HarmonyPatch(typeof(InventoryEquipSlotPCView), nameof(InventoryEquipSlotPCView.BindViewImplementation))] - private static class InventoryEquipSlotPCView_BindViewImplementation_Patch { - public static void Postfix(InventoryEquipSlotPCView __instance) { - if (!Settings.UsingLootRarity) return; - var backfill = __instance.gameObject? - .transform.Find("Backfill"); - var image = backfill?.GetComponent<UnityEngine.UI.Image>(); - if (image != null) { - image.color = ColoredEquipSlotBackgroundColor; - } + [HarmonyPatch(nameof(InventoryPCView.HideWindow))] + [HarmonyPrefix] + public static void OnHide() { + Mod.Log("InventoryPCView.HideWindow"); + ClearSelectedLootSlotFilters(); + SelectedCharacterObserver.Shared.Notifiers -= SelectedCharacterDidChange; } } - // modifies weapon slot backgrounds to work with rarity coloring [HarmonyPatch(typeof(WeaponSetPCView), nameof(WeaponSetPCView.BindViewImplementation))] private static class WeaponSetPCView_BindViewImplementation_Patch { @@ -242,23 +362,22 @@ public static void Postfix(ref bool __result) { __result = true; } } - +#endif [HarmonyPatch(typeof(MassLootHelper), nameof(MassLootHelper.GetMassLootFromCurrentArea))] public static class PatchLootEverythingOnLeave_Patch { public static bool Prefix(ref IEnumerable<LootWrapper> __result) { if (!Settings.toggleMassLootEverything) return true; - IEnumerable<UnitEntityData> all_units = Game.Instance.State.Units.All; + IEnumerable<BaseUnitEntity> all_units = Shodan.AllBaseUnits.All; if (Settings.toggleLootAliveUnits) { - all_units = all_units.Where(unit => unit.IsInGame && unit.HasLoot); - } - else { + all_units = all_units.Where(unit => unit.IsInGame && (unit.IsDeadAndHasLoot || unit.Inventory.HasLoot)); + } else { all_units = all_units.Where(unit => unit.IsInGame && unit.IsDeadAndHasLoot); } var result_units = all_units.Select(unit => new LootWrapper { Unit = unit }); var all_entities = Game.Instance.State.Entities.All.Where(w => w.IsInGame); - var all_chests = all_entities.Select(s => s.Get<InteractionLootPart>()).Where(i => i?.Loot != Game.Instance.Player.SharedStash).NotNull(); + var all_chests = all_entities.SelectMany(s => s.GetAll<InteractionLootPart>()).Where(i => i?.Loot != Game.Instance.Player.SharedStash).NotNull(); var tmp = TempList.Get<InteractionLootPart>(); @@ -288,6 +407,7 @@ public static bool Prefix(ref IEnumerable<LootWrapper> __result) { return false; } } +#if false [HarmonyPatch(typeof(SceneLoader), nameof(SceneLoader.MatchStateWithScene))] static class SceneLoader_MatchStateWithScene_Patch { @@ -395,6 +515,7 @@ static void Postfix(ref AssetBundle __result) { } } } +#endif #endif } } diff --git a/ToyBox/Classes/MonkeyPatchin/HighlightObjectToggle.cs b/ToyBox/Classes/MonkeyPatchin/HighlightObjectToggle.cs new file mode 100644 index 000000000..0df5b2329 --- /dev/null +++ b/ToyBox/Classes/MonkeyPatchin/HighlightObjectToggle.cs @@ -0,0 +1,144 @@ +using HarmonyLib; +using Kingmaker; +using Kingmaker.Controllers.MapObjects; +using Kingmaker.GameModes; +using Kingmaker.PubSubSystem; +using Kingmaker.UI.InputSystems; +using Kingmaker.UI.Models.SettingsUI; +using Kingmaker.Utility.GameConst; +using Kingmaker.View.MapObjects; +using Kingmaker.View.MapObjects.InteractionComponentBase; +using ModKit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace ToyBox.classes.MonkeyPatchin { + public class HighlightObjectToggle : IGameModeHandler { + private static HashSet<GameModeType> TurnOffWhen = [GameModeType.Dialog, GameModeType.Cutscene, GameModeType.CutsceneGlobalMap]; + private static bool wasTurnedOffBefore = false; + internal static bool wasTurnedOff = false; + public void OnGameModeStart(GameModeType gameMode) { + if (!Main.Settings.highlightObjectsToggle) return; + if (Game.Instance.Player.IsInCombat) return; + if (TurnOffWhen.Contains(gameMode)) { + if (InteractionHighlightController.Instance?.IsHighlighting ?? false) { + wasTurnedOffBefore = true; + wasTurnedOff = true; + InteractionHighlightController.Instance?.HighlightOff(); + wasTurnedOff = false; + } + } else { + if (wasTurnedOffBefore && (!InteractionHighlightController.Instance?.IsHighlighting ?? false)) { + InteractionHighlightController.Instance?.HighlightOn(); + wasTurnedOffBefore = false; + } + } + } + public void OnGameModeStop(GameModeType gameMode) { + return; + } + + [HarmonyPatch(typeof(KeyboardAccess))] + private static class KeyboardAccess_Patch { + public static bool justChanged = false; + [HarmonyPatch(nameof(KeyboardAccess.OnCallbackByBinding))] + [HarmonyPrefix] + private static bool OnCallbackByBinding(KeyboardAccess.Binding binding) { + if (!Main.Settings.highlightObjectsToggle) return true; + if (Game.Instance?.Player?.IsInCombat ?? false) return true; + if (binding.Name.StartsWith(UISettingsRoot.Instance.UIKeybindGeneralSettings.HighlightObjects.name)) { + if (binding.Name.EndsWith(UIConsts.SuffixOn) && binding.InputMatched() && !justChanged) { + justChanged = true; + try { + InteractionHighlightController.Instance?.Highlight(!InteractionHighlightController.Instance?.IsHighlighting ?? false); + } catch { + justChanged = false; + return false; + } + Task.Run(() => { + Thread.Sleep(250); + justChanged = false; + }); + } + return false; + } + return true; + } + } + [HarmonyPatch(typeof(Player))] + private static class Player_Patch { + private static bool interestingTick = false; + private static bool wasOnBeforeFightIntern = false; + internal static bool wasOnBeforeFight = false; + [HarmonyPatch(nameof(Player.IsInCombat), MethodType.Setter)] + [HarmonyPrefix] + private static void setIsInCombatPre(bool value) { + if (!Main.Settings.highlightObjectsToggle) return; + interestingTick = value != Game.Instance.Player.IsInCombat; + if (!interestingTick) return; + if ((InteractionHighlightController.Instance?.IsHighlighting ?? false) && value) { + wasOnBeforeFightIntern = true; + wasOnBeforeFight = true; + try { + InteractionHighlightController.Instance.HighlightOff(); + } catch { } + wasOnBeforeFight = false; + } + } + [HarmonyPatch(nameof(Player.IsInCombat), MethodType.Setter)] + [HarmonyPostfix] + private static void SetIsInCombatPost(bool value) { + if (!Main.Settings.highlightObjectsToggle) return; + if (!interestingTick) return; + if (wasOnBeforeFightIntern && !value) { + wasOnBeforeFightIntern = false; + try { + InteractionHighlightController.Instance?.HighlightOn(); + } catch { } + } + interestingTick = false; + } + } + [HarmonyPatch(typeof(InteractionHighlightController))] + private static class InteractionHighlightController_Patch { + [HarmonyPatch(nameof(InteractionHighlightController.HighlightOff))] + [HarmonyPrefix] + private static bool HighlightOff() { + if (!Main.Settings.highlightObjectsToggle) return true; + if (Game.Instance.Player.IsInCombat) return true; + if (!Player_Patch.wasOnBeforeFight && !wasTurnedOff && !KeyboardAccess_Patch.justChanged) { + return false; + } + return true; + } + } + [HarmonyPatch(typeof(MapObjectView))] + private static class MapObjectView_Patch { + [HarmonyPatch(nameof(MapObjectView.ShouldBeHighlighted))] + [HarmonyPostfix] + private static void ShouldBeHighlighted(MapObjectView __instance, ref bool __result) { + if (__instance == null) return; + if (!Main.Settings.highlightHiddenObjects && !Main.Settings.highlightHiddenObjectsInFog) return; + bool flag = __instance.Highlighted || __instance.m_ForcedHighlightOnReveal || (__instance.GlobalHighlighting && (!__instance.Data.IsInFogOfWar || Main.Settings.highlightHiddenObjectsInFog)); + if (Game.Instance.TurnController.TurnBasedModeActive) { + if (__instance.Data.Parts.GetAll<InteractionPart>().Any((InteractionPart i) => i is InteractionLootPart)) { + flag = false; + } + } + bool HighlightOnHover = (__instance.Data.IsRevealed || Main.Settings.highlightHiddenObjects) && (__instance.CanBeAttackedDirectly || __instance.Data.Parts.GetAll<InteractionPart>().Any(i => { + InteractionType type = i.Type; + return (type == InteractionType.Approach || type == InteractionType.Direct) && i.Enabled && (!i.Settings.ShowOvertip || (i.Settings.ShowOvertip && i.Settings.ShowHighlight)); + })); + if (!flag || !HighlightOnHover || ((__instance.Data.IsRevealed || !__instance.Data.IsAwarenessCheckPassed) && !Main.Settings.highlightHiddenObjects)) { + __result = __instance.Data.Parts.GetAll<InteractionPart>().Any((InteractionPart i) => i.HasVisibleTrap()); + } else { + __result = true; + } + Mod.Debug($"Checking highlighting for {__instance.name}: Result:{__result}; flag:{flag}; Highlighted:{__instance.Highlighted} - ForcedHighlightOnReveal:{__instance.m_ForcedHighlightOnReveal} - GlobalHighlighting:{__instance.GlobalHighlighting} - IsInFogOfWar:{__instance.Data.IsInFogOfWar} - HighlightOnHover:{__instance.HighlightOnHover} - IsRevealed:{__instance.Data.IsRevealed} - AwarenessCheckPassed:{__instance.Data.IsAwarenessCheckPassed}"); + } + } + } +} \ No newline at end of file diff --git a/ToyBox/classes/MonkeyPatchin/ModUI.cs b/ToyBox/Classes/MonkeyPatchin/ModUI.cs similarity index 79% rename from ToyBox/classes/MonkeyPatchin/ModUI.cs rename to ToyBox/Classes/MonkeyPatchin/ModUI.cs index 22af694fc..a78022ea1 100644 --- a/ToyBox/classes/MonkeyPatchin/ModUI.cs +++ b/ToyBox/Classes/MonkeyPatchin/ModUI.cs @@ -12,10 +12,14 @@ namespace ToyBox.BagOfPatches { internal static class ModUI { - [HarmonyPatch(typeof(UnityModManager.UI), nameof(UnityModManager.UI.Update))] - internal static class UnityModManager_UI_Update_Patch { + [HarmonyPatch(typeof(UnityModManager.UI))] + internal static class UnityModManagerUIPatch { + public static UnityModManager.UI UnityModMangerUI = null; private static readonly Dictionary<int, float> scrollOffsets = new() { }; - private static void Postfix(UnityModManager.UI __instance, ref Rect ___mWindowRect, ref Vector2[] ___mScrollPosition, ref int ___tabId) { + [HarmonyPatch(nameof(UnityModManager.UI.Update))] + [HarmonyPostfix] + private static void Update(UnityModManager.UI __instance, ref Rect ___mWindowRect, ref Vector2[] ___mScrollPosition, ref int ___tabId) { + UnityModMangerUI = __instance; #if false // hack to fix mouse wheel which seems to gets de-magnified when the cursor is on the right side of the screen var scrollPosition = ___mScrollPosition[___tabId]; diff --git a/ToyBox/Classes/MonkeyPatchin/PreviewManagerRT.cs b/ToyBox/Classes/MonkeyPatchin/PreviewManagerRT.cs new file mode 100644 index 000000000..c5f8aff49 --- /dev/null +++ b/ToyBox/Classes/MonkeyPatchin/PreviewManagerRT.cs @@ -0,0 +1,394 @@ +using DG.Tweening; +using HarmonyLib; +using Kingmaker; +using Kingmaker.Blueprints; +using Kingmaker.Blueprints.Items; +using Kingmaker.Blueprints.Root; +using Kingmaker.Blueprints.Root.Strings; +using Kingmaker.Code.UI.MVVM.View.Dialog.Dialog; +using Kingmaker.Code.UI.MVVM.VM.Dialog.Dialog; +using Kingmaker.Code.Utility; +using Kingmaker.Controllers.Dialog; +using Kingmaker.Designers.EventConditionActionSystem.Actions; +using Kingmaker.Designers.EventConditionActionSystem.Conditions; +using Kingmaker.DialogSystem; +using Kingmaker.DialogSystem.Blueprints; +using Kingmaker.ElementsSystem; +using Kingmaker.Enums; +using Kingmaker.Localization; +using Kingmaker.PubSubSystem; +using Kingmaker.RandomEncounters; +using Kingmaker.Settings; +using Kingmaker.Settings.Entities; +using Kingmaker.TextTools; +using Kingmaker.UI.Common; +using Kingmaker.UI.MVVM.View.Dialog.Dialog; +using Kingmaker.UnitLogic.Alignments; +using Kingmaker.UnitLogic.Mechanics.Conditions; +using Kingmaker.Utility; +using ModKit; +using Owlcat.Runtime.Core.Utility; +using Owlcat.Runtime.UI.Tooltips; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Xml.Linq; +using TMPro; +using UnityEngine; +using UnityEngine.Events; +using UnityEngine.UI; + +namespace ToyBox { + public static class PreviewManager { + public static Settings Settings = Main.Settings; + public static Player player => Game.Instance.Player; + private static GameDialogsSettings DialogSettings => SettingsRoot.Game.Dialogs; + + private static List<Tuple<BlueprintCueBase, int, GameAction[], SoulMarkShift, SoulMarkShift>> CollateAnswerData(BlueprintAnswer answer, out bool isRecursive) { + var cueResults = new List<Tuple<BlueprintCueBase, int, GameAction[], SoulMarkShift, SoulMarkShift>>(); + var toCheck = new Queue<Tuple<BlueprintCueBase, int>>(); + isRecursive = false; + var visited = new HashSet<BlueprintAnswerBase> { }; + visited.Add(answer); + if (answer.NextCue.Cues.Count > 0) { + toCheck.Enqueue(new Tuple<BlueprintCueBase, int>(answer.NextCue.Cues[0], 1)); + } + cueResults.Add(new Tuple<BlueprintCueBase, int, GameAction[], SoulMarkShift, SoulMarkShift>( + null, + 0, + answer.OnSelect.Actions, + answer.SoulMarkShift, + answer.SoulMarkRequirement + + )); + while (toCheck.Count > 0) { + var item = toCheck.Dequeue(); + var cueBase = item.Item1; + var currentDepth = item.Item2; + if (currentDepth > 20) break; + if (cueBase is BlueprintCue cue) { + cueResults.Add(new Tuple<BlueprintCueBase, int, GameAction[], SoulMarkShift, SoulMarkShift>( + cue, + currentDepth, + cue.OnShow.Actions.Concat(cue.OnStop.Actions).ToArray(), + cue.SoulMarkShift, + cue.SoulMarkRequirement + )); + if (cue.Answers.Count > 0) { + var subAnswer = cue.Answers[0].Get(); + if (visited.Contains(subAnswer)) { + isRecursive = true; + break; + } + visited.Add(subAnswer); + } + if (cue.Continue.Cues.Count > 0) { + toCheck.Enqueue(new Tuple<BlueprintCueBase, int>(cue.Continue.Cues[0], currentDepth + 1)); + } + } else + if (cueBase is BlueprintBookPage page) { + cueResults.Add(new Tuple<BlueprintCueBase, int, GameAction[], SoulMarkShift, SoulMarkShift>( + page, + currentDepth, + page.OnShow.Actions, + null, + null + )); + if (page.Answers.Count > 0) { + var subAnswer = page.Answers[0].Get(); + if (visited.Contains(subAnswer)) { + isRecursive = true; + break; + } + visited.Add(subAnswer); + if (page.Answers[0].Get() is BlueprintAnswersList) break; + } + if (page.Cues.Count > 0) { + foreach (var c in page.Cues) { + bool canShow = false; + try { + canShow = c.Get().CanShow(); + } catch (Exception ex) { + Mod.Warn($"Answer Preview: Caught Exception\n{ex.ToString()}"); + } + if (canShow) { + toCheck.Enqueue(new Tuple<BlueprintCueBase, int>(c, currentDepth + 1)); + } + } + } + } else + if (cueBase is BlueprintCheck check) { + toCheck.Enqueue(new Tuple<BlueprintCueBase, int>(check.Success, currentDepth + 1)); + toCheck.Enqueue(new Tuple<BlueprintCueBase, int>(check.Fail, currentDepth + 1)); + } else + if (cueBase is BlueprintCueSequence sequence) { + foreach (var c in sequence.Cues) { + bool canShow = false; + try { + canShow = c.Get().CanShow(); + } catch (Exception ex) { + Mod.Warn($"Answer Preview: Caught Exception\n{ex.ToString()}"); + } + if (canShow) { + toCheck.Enqueue(new Tuple<BlueprintCueBase, int>(c, currentDepth + 1)); + } + } + if (sequence.Exit != null) { + var exit = sequence.Exit; + if (exit.Answers.Count > 0) { + var subAnswer = exit.Answers[0]; + if (visited.Contains(subAnswer)) { + isRecursive = true; + break; + } + visited.Add(subAnswer); + if (exit.Continue.Cues.Count > 0) { + toCheck.Enqueue(new Tuple<BlueprintCueBase, int>(exit.Continue.Cues[0], currentDepth + 1)); + } + } + } + } else { + break; + } + } + return cueResults; + } + + public static string GetFixedAnswerString(BlueprintAnswer answer, string bind, int index) { + var isBook = Game.Instance.DialogController.Dialog.Type == DialogType.Book; + string checkFormat = !isBook ? UIDialog.Instance.AnswerStringWithCheckFormat : UIDialog.Instance.AnswerStringWithCheckBeFormat; + var text = string.Empty; + if (DialogSettings.ShowSkillcheckDC) { + text = answer.SkillChecks.Aggregate( + "", + (current, skillCheck) + => current + + string.Format(checkFormat, + UIUtility.PackKeys(EntityLink.GetTag(EntityLink.Type.SkillcheckDC), skillCheck.Type), + LocalizedTexts.Instance.Stats.GetText(skillCheck.Type), + skillCheck.DC)); +#if FALSE + text = answer.SkillChecksDC.Aggregate( + string.Empty, + (string current, SkillCheckDC skillCheck) + => current + + string.Format(checkFormat, UIUtility.PackKeys(new object[] { + TooltipType.SkillcheckDC, + skillCheck.StatType + }), + LocalizedTexts.Instance.Stats.GetText(skillCheck.StatType), skillCheck.ValueDC)); +#endif + } + if (DialogSettings.ShowAlignmentRequirements && answer.SoulMarkRequirement.Empty) { + // TODO: recheck later versions of the game code to make sure this clause is still NOOP + // text = string.Format(UIDialog.Instance.AlignmentRequirementFormat, UIUtility.GetAlignmentRequirementText(answer.SoulMarkRequirement)) + text; + } + if ((bool)(SettingsEntity<bool>)SettingsRoot.Game.Dialogs.ShowAlignmentShiftsInAnswer && + answer.SoulMarkRequirement.Empty && answer.SoulMarkShift.Value != 0 && + (bool)(SettingsEntity<bool>)SettingsRoot.Game.Dialogs.ShowAlignmentShiftsInAnswer) + text = string.Format(UIDialog.Instance.AligmentShiftedFormat, + UIUtility.GetSoulMarkDirectionText(answer.SoulMarkShift.Direction).Text) + text; + + var stringByBinding = UIKeyboardTexts.Instance.GetStringByBinding(Game.Instance.Keyboard.GetBindingByName(bind)); + + return string.Format(UIDialog.Instance.AnswerDialogueFormat, + (!stringByBinding.Empty()) ? stringByBinding : index.ToString(), + text + ((!text.Empty()) ? " " : string.Empty) + answer.DisplayText); + } + public static string FormatShift(this SoulMarkShift shift, string format) { + if (shift != null && shift.Value != 0) { + if (shift.Description?.Text is string { Length: > 0 } description) { + return string.Format(format, $"{shift.Direction}, {shift.Value}, {shift.Description.Text}"); + } + return string.Format(format, $"{shift.Direction}, {shift.Value}"); + } + return null; + } + + public static string ResultsText(this BlueprintCue cue) { + if (cue == null) return ""; + var actions = cue.OnShow.Actions.Concat(cue.OnStop.Actions).ToArray(); + var alignment = cue.SoulMarkShift; + var text = ""; + if (actions.Length > 0) { + var result = PreviewUtilities.FormatActions(actions); + if (result == "") result = "EmptyAction"; + text += $" \n<size=75%>[{result}]</size>"; + } + if (alignment != null && alignment.Value > 0) { + text += $" \n<size=75%>[SoulMarkShift {alignment.Direction} by {alignment.Value} - {alignment.Description.Text}]"; + } + return text; + } + + public static string ResultsText(this BlueprintAnswer answer) { + var text = ""; + try { + var answerData = CollateAnswerData(answer, out var isRecursive); + if (isRecursive) { + text += $" <size=75%>[Repeats]</size>"; + } + var results = new List<string>(); + foreach (var data in answerData) { + var cue = data.Item1; + var depth = data.Item2; + var actions = data.Item3; + var alignment = data.Item4; + var alignmentRequirement = data.Item5; + var line = new List<string>(); + if (actions.Length > 0) { + line.AddRange(actions.SelectMany(action => PreviewUtilities.FormatActionAsList(action) + .Select(actionText => actionText == "" ? "EmptyAction" : actionText))); + } + if (alignmentRequirement.FormatShift("SoulMarkRequired({0})") is { } soulMarkRequiredText) { + line.Add(soulMarkRequiredText); + } + if (alignment.FormatShift("SoulMarkShift({0})") is { } soulMarkShiftText) { + line.Add(soulMarkShiftText); + } + if (cue is BlueprintCheck check) { + line.Add($"Check({check.Type}, DC {check.DC}, hidden {check.Hidden})"); + } + if (line.Count > 0) results.Add($"{depth}: {line.Join()}"); + } + if (results.Count > 0) text = $" \v<size=75%>[{results.Join()}]</size>"; + } catch (Exception ex) { + Mod.Warn($"Caught exception while trying to create answer preview:\n{ex.ToString()}"); + } + return text; + } + + [HarmonyPatch(typeof(UIConstsExtensions), nameof(UIConstsExtensions.GetAnswerFormattedString))] + private static class UIConsts_GetAnswerString_Patch { + private static void Postfix(ref string __result, BlueprintAnswer answer, string bind, int index) { + try { + if (!Main.Enabled) return; + if (Main.Settings.previewAlignmentRestrictedDialog && !answer.IsSoulMarkRequirementSatisfied()) { + __result = GetFixedAnswerString(answer, bind, index); + } + if (!Main.Settings.previewDialogResults) return; + var text = answer.ResultsText(); + if (Settings.previewDialogConditions) { + var conditions = PreviewUtilities.FormatConditionsAsList(answer); + var conditionsText = string.Join("\v", conditions); + if (!text.IsNullOrEmpty() && conditions.Any()) + __result += $"<size=75%>\v{conditionsText}\n{text}</size>"; + else if (!text.IsNullOrEmpty()) + __result += $"<size=75%>{text}</size>"; + else if (conditions.Any()) + __result += $"<size=75%>\v{conditionsText}</size>"; + } else if (!text.IsNullOrEmpty()) + __result += $"<size=75%>{text}</size>"; + } catch (Exception ex) { + Mod.Error(ex); + } + } + } + + [HarmonyPatch(typeof(CueVM), nameof(CueVM.GetCueText))] + private static class DialogCurrentPart_Fill_Patch { + [HarmonyPostfix] + public static void GetCueText(CueVM __instance, DialogColors dialogColors, ref string __result) { + try { + if (!Main.Enabled) return; + if (!Main.Settings.previewDialogResults) return; + var cue = Game.Instance.DialogController.CurrentCue; + var text = cue.ResultsText(); + __result += text; + } catch (Exception ex) { + Mod.Error(ex); + } + } + } + + [HarmonyPatch(typeof(DialogAnswerBaseView))] + private static class DialogAnswerViewPatch { + [HarmonyPatch(nameof(DialogAnswerBaseView.SetAnswer), [typeof(BlueprintAnswer)])] + [HarmonyPrefix] + private static bool SetAnswer(DialogAnswerBaseView __instance, BlueprintAnswer answer) { + if (!Settings.previewDialogResults && !Settings.toggleShowAnswersForEachConditionalResponse && !Settings.toggleMakePreviousAnswersMoreClear) return true; + DialogVotesBlockView dialogVotesBlockView = __instance.m_DialogVotesBlock.Or(null); + if (dialogVotesBlockView != null) { + dialogVotesBlockView.ShowHideHover(false); + } + var type = Game.Instance.DialogController.Dialog.Type; + var str = string.Format("DialogChoice{0}", (object)__instance.ViewModel.Index); + var text = UIConstsExtensions.GetAnswerFormattedString(answer, str, __instance.ViewModel.Index); + bool hasConditionsForTooltip = answer.HasConditionsForTooltip; + Color32 color = __instance.m_DialogColors.NormalAnswer; + switch (type) { + case DialogType.Common: + case DialogType.StarSystemEvent: { + color = (answer.CanSelect() ? ((__instance.ViewModel.IsAlreadySelected() && !__instance.ViewModel.IsSystem) ? __instance.m_DialogColors.SelectedAnswer : __instance.m_DialogColors.NormalAnswer) : __instance.m_DialogColors.DisabledAnswer); + if (hasConditionsForTooltip) { + string text2 = (answer.CanSelect() ? "UI_Dialog_ConditionSuccess" : "UI_Dialog_ConditionFail"); + string text3 = string.Format(UIConfig.Instance.UIDialogConditionsLinkFormat, answer.AssetGuid, text2); + string text4 = (answer.CanSelect() ? UIConstsExtensions.GetAnswerText(answer) : answer.DisplayText); + text = string.Format(UIDialog.Instance.AnswerDialogueFormat, __instance.ViewModel.Index, text3 + text4); + } + }; break; + case DialogType.Epilog: { + text = answer.DisplayText; + }; break; + } + var isAvail = answer.CanSelect(); + if (answer.NextCue.Cues.Count == 1) { + var cue = answer.NextCue.Cues.Dereference<BlueprintCueBase>().FirstOrDefault(); + var conditionText = PreviewUtilities.FormatConditions(cue.Conditions); + // var conditionText = $"{string.Join(", ", cue.Conditions.Conditions.Select(c => c.GetCaption()))}"; + // the following is a kludge for toggleShowAnswersForEachConditionalResponse to work around cases where there may be a next cue that doesn't get shown due it being already seen and the dialog being intended to fall through. We assume that any singleton conditional nextCue (CueSelection) was generated by this feature. We should look for edge cases to be sure. + isAvail = isAvail && (cue.CanShow() + || !Settings.toggleShowAnswersForEachConditionalResponse + || !answer.name.Contains("ToyBox") + || conditionText.Length == 0 + ); + Mod.Debug($"Fixing up available for ${answer.name} canShow: {cue.CanShow()} isAvail: {isAvail} - cue: {conditionText}"); + var color2 = isAvail ? "#005800><b>" : "#800000>"; + if (conditionText.Length > 0) + text += $"<size=75%><color={color2}[{conditionText.MergeSpaces(true)}]</color></size>"; + } + + __instance.SetAnswerText(text); + __instance.ViewModel.Enable.Value = answer.CanSelect() && isAvail; + + // TODO: this is new in RT so figure out whether we should do more preview stuff here + var color32 = isAvail ? __instance.m_DialogColors.NormalAnswer : __instance.m_DialogColors.DisabledAnswer; + + if (type == DialogType.Common || type == DialogType.StarSystemEvent) { + color32 = answer.CanSelect() + ? !__instance.ViewModel.IsAlreadySelected() || __instance.ViewModel.IsSystem + ? __instance.m_DialogColors.NormalAnswer + : __instance.m_DialogColors.SelectedAnswer + : __instance.m_DialogColors.DisabledAnswer; + if (answer.SelectConditions.HasConditions) { + var color2 = isAvail ? "#005800><b>" : "#800000>"; + text += "\n"; + foreach (var condition in answer.SelectConditions.Conditions) { + string conditionText = ""; + switch (condition) { + case ConditionHaveFullCargo _ when !condition.Not: + conditionText = string.Format(UIDialog.Instance.AnswerYouNeedFullCargo, + condition.GetCaption(), answer.DisplayText, __instance.ViewModel.Index); + break; + case ContextConditionHasItem _ when !condition.Not: + conditionText = string.Format(UIDialog.Instance.AnswerYouNeedItem, condition.GetCaption(), + answer.DisplayText, __instance.ViewModel.Index); + break; + default: + continue; + } + text += $"<size=75%><color={color2}[{conditionText.MergeSpaces(true)}]</color></size>"; + } + __instance.SetAnswerText(text); + } + } + + __instance.m_AnswerText.color = color32; + __instance.AddDisposable(Game.Instance.Keyboard.Bind(str, new Action(__instance.Confirm))); + if (__instance.ViewModel.IsSystem) __instance.AddDisposable(Game.Instance.Keyboard.Bind("NextOrEnd", new Action(__instance.Confirm))); ; + return false; + } + } + } +} diff --git a/ToyBox/classes/MonkeyPatchin/PreviewUtilities.cs b/ToyBox/Classes/MonkeyPatchin/PreviewUtilities.cs similarity index 69% rename from ToyBox/classes/MonkeyPatchin/PreviewUtilities.cs rename to ToyBox/Classes/MonkeyPatchin/PreviewUtilities.cs index c57805f10..26a8cf10d 100644 --- a/ToyBox/classes/MonkeyPatchin/PreviewUtilities.cs +++ b/ToyBox/Classes/MonkeyPatchin/PreviewUtilities.cs @@ -4,10 +4,11 @@ using UnityEngine; using Kingmaker.Designers.EventConditionActionSystem.Actions; using Kingmaker.ElementsSystem; -using Kingmaker.Kingdom.Blueprints; using Kingmaker.Blueprints; using System.Diagnostics; using System; +using Kingmaker.Designers.EventConditionActionSystem.Conditions; +using Kingmaker.DialogSystem.Blueprints; using ModKit; namespace ToyBox { @@ -58,11 +59,19 @@ public static List<string> ResolveConditional(Conditional conditional) { return result; } public static List<string> FormatActionAsList(GameAction action) { - if (action is Conditional) { - return ResolveConditional(action as Conditional); + if (action is Conditional conditional) { + return ResolveConditional(conditional); } var result = new List<string>(); - var caption = action?.GetCaption(); + var caption = ""; + if (action is RunActionHolder actionHolder) { + if (actionHolder.Holder.Get()?.Actions is { } subActions) { + var subActionList = FormatActions(subActions); + caption = $"Run Action Holder({string.Join(", ", subActionList)})"; + } + } else { + caption = action?.GetCaption(); + } caption = caption == "" || caption == null ? action?.GetType().Name ?? "" : caption; result.Add(caption); return result; @@ -73,17 +82,22 @@ public static string FormatActions(GameAction[] actions) => actions .Select(actionText => actionText == "" ? "EmptyAction" : actionText) .Join(); - public static string FormatConditions(Condition[] conditions) => conditions.Join(c => c.GetCaption()); + public static string FormatConditions(Condition[] conditions) => conditions.Join(c => { + if (c is CheckConditionsHolder holder) { + return "Conditions Holder".localize() + $"({FormatConditions(holder.ConditionsHolder.Get().Conditions)})"; + } else + return c.GetCaption(); + }); public static string FormatConditions(ConditionsChecker conditions) => FormatConditions(conditions.Conditions); - public static bool CausesGameOver(BlueprintKingdomEventBase blueprint) { - var results = blueprint.GetComponent<EventFinalResults>(); - if (results == null) return false; - foreach (var result in results.Results) { - foreach (var action in result.Actions.Actions) { - if (action is GameOver) return true; - } - } - return false; + public static List<string> FormatConditionsAsList(BlueprintAnswer answer) { + var list = new List<String>(); + if (answer.HasShowCheck) + list.Add("Show Check".localize() + $"({answer.ShowCheck.Type} " + "DC".localize() + $": {answer.ShowCheck.DC})"); + if (answer.ShowConditions.Conditions.Length > 0) + list.Add("Show Conditions".localize() + $"({FormatConditions(answer.ShowConditions)}"); + if (answer.SelectConditions is ConditionsChecker selectChecker && selectChecker.Conditions.Count() > 0) + list.Add("Select Conditions".localize() + $"({PreviewUtilities.FormatConditions(selectChecker)})"); ; + return list; } public class CodeTimer : IDisposable { private readonly Stopwatch m_Stopwatch; diff --git a/ToyBox/GlobalSuppressions.cs b/ToyBox/GlobalSuppressions.cs index d7e85b98c..baa84595c 100644 --- a/ToyBox/GlobalSuppressions.cs +++ b/ToyBox/GlobalSuppressions.cs @@ -5,3 +5,7 @@ using System.Diagnostics.CodeAnalysis; +[assembly: SuppressMessage( + "CodeQuality", + "IDE0051:Remove unused private members", + Justification = "Members are implicitly called via reflection and such")] \ No newline at end of file diff --git a/ToyBox/Info.json b/ToyBox/Info.json deleted file mode 100644 index b4c8c1463..000000000 --- a/ToyBox/Info.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "Id": "ToyBox", - "DisplayName": "ToyBox (a bag of tricks)", - "Author": "Narria", - "Version": "1.5.1i", - "ManagerVersion": "0.23.0", - "Requirements": [], - "AssemblyName": "ToyBox.dll", - "EntryMethod": "ToyBox.Main.Load", - "HomePage": "https://www.nexusmods.com/pathfinderwrathoftherighteous/mods/8?tab=posts&BH=0", - "Repository": "https://raw.githubusercontent.com/cabarius/ToyBox/master/ToyBox/Repository.json" -} \ No newline at end of file diff --git a/ToyBox/Localization/en.json b/ToyBox/Localization/en.json index ce510b540..bed5cc24b 100644 --- a/ToyBox/Localization/en.json +++ b/ToyBox/Localization/en.json @@ -1,326 +1,688 @@ { "LanguageCode": "en", - "Version": "1.5.1", + "Version": "1.5.13", "Contributors": "The ToyBox Team", "HomePage": "https://github.com/cabarius/ToyBox/", - "Strings": { - " Allow remote companions to make comments on dialog you are having.": " Allow remote companions to make comments on dialog you are having.", - " Only use when Friendship is Magic doesn't work, and then turn off immediately after. Can otherwise break your save": " Only use when Friendship is Magic doesn't work, and then turn off immediately after. Can otherwise break your save", - " your friends forgive even your most vile choices.": " your friends forgive even your most vile choices.", - "<b><color=orange>Blueprints</color></b> loading: ": "<b><color=orange>Blueprints</color></b> loading: ", - "<b><color=orange>Note:</color></b><color=#00ff00ff>This Unfair difficulty was bugged and applied the intended difficulty modifers twice. ToyBox allows you to keep playing at this Brutal difficulty level and beyond. Use the slider below to select your desired Brutality Level</color>": "<b><color=orange>Note:</color></b><color=#00ff00ff>This Unfair difficulty was bugged and applied the intended difficulty modifers twice. ToyBox allows you to keep playing at this Brutal difficulty level and beyond. Use the slider below to select your desired Brutality Level</color>", - "<b><color=yellow>\nNote:</color></b><color=orange> This applies only to the </color><b><color=yellow>current save.</color></b>": "<b><color=yellow>\nNote:</color></b><color=orange> This applies only to the </color><b><color=yellow>current save.</color></b>", - "<color=#00ff00ff>Allow </color><color=#C060F0FF>any gender</color> <color=#00ff00ff>for any </color><color=#FF4040FF>R</color><color=orange>o</color><color=yellow>m</color><color=#00ff00ff>a</color><color=cyan>n</color><color=#103080FF>c</color><color=#C060F0FF>e</color>": "<color=#00ff00ff>Allow </color><color=#C060F0FF>any gender</color> <color=#00ff00ff>for any </color><color=#FF4040FF>R</color><color=orange>o</color><color=yellow>m</color><color=#00ff00ff>a</color><color=cyan>n</color><color=#103080FF>c</color><color=#C060F0FF>e</color>", - "<color=#00ff00ff>Allow </color><color=#C060F0FF>multiple</color><color=#00ff00ff> romances at the same time</color>": "<color=#00ff00ff>Allow </color><color=#C060F0FF>multiple</color><color=#00ff00ff> romances at the same time</color>", - "<color=#00ff00ff>creation of </color>new characters<b><color=yellow>\nNote:</color></b><color=orange> This value applies to </color><b><color=yellow>all saves</color></b><color=orange> and in the main menu</color>": "<color=#00ff00ff>creation of </color>new characters<b><color=yellow>\nNote:</color></b><color=orange> This value applies to </color><b><color=yellow>all saves</color></b><color=orange> and in the main menu</color>", - "<color=#00ff00ff>the following options allow you to migrate previous settings that were stored in toybox to the new per setting save mechanism for </color><color=cyan>Multi-class selections, Gestalt Flags and Allow Levels Past 20 </color><color=orange>\nNote:</color><color=#00ff00ff>you may have configured this for a different save so use care in doing this migration</color>": "<color=#00ff00ff>the following options allow you to migrate previous settings that were stored in toybox to the new per setting save mechanism for </color><color=cyan>Multi-class selections, Gestalt Flags and Allow Levels Past 20 </color><color=orange>\nNote:</color><color=#00ff00ff>you may have configured this for a different save so use care in doing this migration</color>", - "<color=#00ff00ff>toggle this if you want show older ToyBox settings for </color><color=cyan>Multi-class selections, Gestalt Flags and Allow Levels Past 20 </color>": "<color=#00ff00ff>toggle this if you want show older ToyBox settings for </color><color=cyan>Multi-class selections, Gestalt Flags and Allow Levels Past 20 </color>", - "<color=#C04040E0>♥♥ </color><b>Love is Free</b><color=#C04040E0> ♥♥</color>": "<color=#C04040E0>♥♥ </color><b>Love is Free</b><color=#C04040E0> ♥♥</color>", - "<color=#D8D8D8A0>Unfair</color>": "<color=#D8D8D8A0>Unfair</color>", - "<color=cyan> + Click</color> To Transfer Entire Stack": "<color=cyan> + Click</color> To Transfer Entire Stack", - "<color=cyan> + Click</color> To Use Items In Inventory": "<color=cyan> + Click</color> To Use Items In Inventory", - "<color=cyan>Experimental</color><color=#00ff00ff>: in addition to regular leveling, this allows you to choose any mythic class each time you level up starting from mythic rank 1. This may have interesting and unexpected effects. Backup early and often...</color>": "<color=cyan>Experimental</color><color=#00ff00ff>: in addition to regular leveling, this allows you to choose any mythic class each time you level up starting from mythic rank 1. This may have interesting and unexpected effects. Backup early and often...</color>", - "<color=cyan>Experimental</color><color=#00ff00ff>: lets you select any feat ignoring prerequisites.</color>": "<color=cyan>Experimental</color><color=#00ff00ff>: lets you select any feat ignoring prerequisites.</color>", - "<color=orange>Experimental</color><color=#00ff00ff> This allows you to adjust pitch (Camera Tilt) by holding down Mouse3 (which previously just rotated).</color><color=orange> Note:</color><color=#00ff00ff> Holding alt while Mouse3 dragging lets you move the camera location.</color>": "<color=orange>Experimental</color><color=#00ff00ff> This allows you to adjust pitch (Camera Tilt) by holding down Mouse3 (which previously just rotated).</color><color=orange> Note:</color><color=#00ff00ff> Holding alt while Mouse3 dragging lets you move the camera location.</color>", - "<color=orange>Note:</color><color=#00ff00ff> For cutscenes and some situations the rotation keys are disabled so you have to hold down Mouse3 to drag in order to get rotation</color>": "<color=orange>Note:</color><color=#00ff00ff> For cutscenes and some situations the rotation keys are disabled so you have to hold down Mouse3 to drag in order to get rotation</color>", - "Ability Max": "Ability Max", - "Ability Min": "Ability Min", - "Add": "Add", - "Add Full Hit Die Value": "Add Full Hit Die Value", - "Adjusts costs of hiring mercenaries at the Pathfinder vendor": "Adjusts costs of hiring mercenaries at the Pathfinder vendor", - "Adjusts the movement speed of your party in area maps": "Adjusts the movement speed of your party in area maps", - "Adjusts the movement speed of your party on world maps": "Adjusts the movement speed of your party on world maps", - "Alignment": "Alignment", - "All Attacks Hit": "All Attacks Hit", - "All Experience": "All Experience", - "All Hits Critical": "All Hits Critical", - "All Mythic Paths": "All Mythic Paths", - "Allow ": "Allow ", - "Allow Achievements While Using Mods": "Allow Achievements While Using Mods", - "Allow Companions to Take Mythic Classes": "Allow Companions to Take Mythic Classes", - "Allow Item Use From Inventory During Combat": "Allow Item Use From Inventory During Combat", - "Allow Mouse3 Drag to adjust Camera Tilt": "Allow Mouse3 Drag to adjust Camera Tilt", - "Allow Multiple Archetypes When Selecting A New Class": "Allow Multiple Archetypes When Selecting A New Class", - "Allow Pets to Take Mythic Classes": "Allow Pets to Take Mythic Classes", - "Allow Simultaneous Activatable Abilities (Like Judgements)": "Allow Simultaneous Activatable Abilities (Like Judgements)", - "Alt + Mouse Wheel To Adjust Clip Plane": "Alt + Mouse Wheel To Adjust Clip Plane", - "Alternate Time Scale": "Alternate Time Scale", - "Always Able To Level Up": "Always Able To Level Up", - "Always Roll 1": "Always Roll 1", - "Always Roll 20": "Always Roll 20", - "Apply Feature Selection Multiplier to party members": "Apply Feature Selection Multiplier to party members", - "Arcanist: Spell Slot Multiplier": "Arcanist: Spell Slot Multiplier", - "Auto load Last Save on launch": "Auto load Last Save on launch", - "Barbarian: Auto Start Rage When Entering Combat": "Barbarian: Auto Start Rage When Entering Combat", - "Basic Attack Growth Pr": "Basic Attack Growth Pr", - "Be a <b><color=#C04040E0>Murder</color></b><color=orange> Hobo</color>": "Be a <b><color=#C04040E0>Murder</color></b><color=orange> Hobo</color>", - "Bindable hot key to swap between main and alternate time scale multipliers": "Bindable hot key to swap between main and alternate time scale multipliers", - "Brutality Level": "Brutality Level", - "Buff Duration": "Buff Duration", - "Buff Like A Goddess": "Buff Like A Goddess", - "buffs to add to list": "buffs to add to list", - "Build Points (Main)": "Build Points (Main)", - "Build Points (Mercenary)": "Build Points (Mercenary)", - "Camera": "Camera", - "Can Move Through": "Can Move Through", - "Change Party": "Change Party", - "Change the party without advancing time (good to bind)": "Change the party without advancing time (good to bind)", - "Change Weather": "Change Weather", - "Char Gen": "Char Gen", - "Character Creation": "Character Creation", - "Character Level": "Character Level", - "Cheats": "Cheats", - "Class Selection": "Class Selection", - "Class Specific": "Class Specific", - "Clear": "Clear", - "Clear Action Bar": "Clear Action Bar", - "Combat": "Combat", - "Common": "Common", - "Common Buffs": "Common Buffs", - "Companion Cost": "Companion Cost", - "Configure multiclass classes and gestalt flags to use during ": "Configure multiclass classes and gestalt flags to use during ", - "Corruption": "Corruption", - "Create & Level Up": "Create & Level Up", - "Ctrl + Mouse3 Drag To Adjust Camera Elevation": "Ctrl + Mouse3 Drag To Adjust Camera Elevation", - "current list": "current list", - "Decrees": "Decrees", - "Del": "Del", - "Demon: Auto Start Rage When Entering Combat": "Demon: Auto Start Rage When Entering Combat", - "Description": "Description", - "Dialog": "Dialog", - "Dialog Alignment": "Dialog Alignment", - "Dialog Results": "Dialog Results", - "Dice Rolls": "Dice Rolls", - "Disable Armor & Shield Arcane Spell Failure": "Disable Armor & Shield Arcane Spell Failure", - "Disable Armor & Shield Checks Penalty": "Disable Armor & Shield Checks Penalty", - "Disable Armor Max Dexterity": "Disable Armor Max Dexterity", - "Disable Armor Speed Reduction": "Disable Armor Speed Reduction", - "Disable Attacks of Opportunity": "Disable Attacks of Opportunity", - "Disable Attacks Of Opportunity": "Disable Attacks Of Opportunity", - "Disable Corruption": "Disable Corruption", - "Disable Dialog Restrictions (Alignment)": "Disable Dialog Restrictions (Alignment)", - "Disable Dialog Restrictions (Mythic Path)": "Disable Dialog Restrictions (Mythic Path)", - "Disable Equipment Restrictions": "Disable Equipment Restrictions", - "Disable Party Ability Damage": "Disable Party Ability Damage", - "Disable Party Negative Levels": "Disable Party Negative Levels", - "Disallow Companions Leaving Party": "Disallow Companions Leaving Party", - "Draws dialog choices that you have previously selected in smaller type": "Draws dialog choices that you have previously selected in smaller type", - "Duration Multiplier": "Duration Multiplier", - "Empowered": "Empowered", - "Enable Brutal Unfair Difficulty": "Enable Brutal Unfair Difficulty", - "Enable Loading with Blueprint Errors": "Enable Loading with Blueprint Errors", - "Enable Rotate on all maps and cutscenes": "Enable Rotate on all maps and cutscenes", - "Enable Teleport Keys": "Enable Teleport Keys", - "Enable Zoom on all maps and cutscenes": "Enable Zoom on all maps and cutscenes", - "Enemies": "Enemies", - "Enemy HP Multiplier": "Enemy HP Multiplier", - "Enhanced Map View": "Enhanced Map View", - "Equipment No Weight": "Equipment No Weight", - "Events": "Events", - "Everyone": "Everyone", - "Exceptions to Buff Duration Multiplier (Advanced; will cause blueprints to load)": "Exceptions to Buff Duration Multiplier (Advanced; will cause blueprints to load)", - "Expand Answers For Conditional Responses": "Expand Answers For Conditional Responses", - "Expand Dialog To Include Remote Companions": "Expand Dialog To Include Remote Companions", - "Experience": "Experience", - "Experience Multipliers": "Experience Multipliers", - "Experimental": "Experimental", - "Experimental ": "Experimental ", - "Experimental - With this enabled you can configure characters in the Party Editor to gain levels in additional classes whenever they level up. See the link for more information on this campaign variant.": "Experimental - With this enabled you can configure characters in the Party Editor to gain levels in additional classes whenever they level up. See the link for more information on this campaign variant.", - "Feature Selection Multiplier": "Feature Selection Multiplier", - "Field Of View": "Field Of View", - "Fix Alignment Shifts": "Fix Alignment Shifts", - "Fix Camera": "Fix Camera", - "Fix Incorrect Main Character": "Fix Incorrect Main Character", - "Fog of War Range": "Fog of War Range", - "FoV (Cut Scenes)": "FoV (Cut Scenes)", - "Free Camera": "Free Camera", - "Free Meta-Magic": "Free Meta-Magic", - "Friendly": "Friendly", - "Friendship is Magic": "Friendship is Magic", - "Gain ": "Gain ", - "Game Over Fix For <color=#FF6060FF>LEEEROOOOOOOYYY JEEEENKINS!!!</color> omg he just ran in!": "Game Over Fix For <color=#FF6060FF>LEEEROOOOOOOYYY JEEEENKINS!!!</color> omg he just ran in!", - "Game Time Scale": "Game Time Scale", - "gestalt": "gestalt", - "Gestalt Characters": "Gestalt Characters", - "Give All Items": "Give All Items", - "Go To Global Map": "Go To Global Map", - "Gold": "Gold", - "good for larger group or to reduce enemies": "good for larger group or to reduce enemies", - "good for party": "good for party", - "Hide": "Hide", - "Highlight Copyable Scrolls": "Highlight Copyable Scrolls", - "Highlight Hidden Objects": "Highlight Hidden Objects", - "Hit Point (Hit Die) Growth": "Hit Point (Hit Die) Growth", - "Hold down shift during launch to bypass": "Hold down shift during launch to bypass", - "Icky Stuff Begone!!!": "Icky Stuff Begone!!!", - "Identify All": "Identify All", - "If ticked, this will <b><color=#C04040E0>MURDER</color></b><color=#00ff00ff> all who dare to engage you!</color>": "If ticked, this will <b><color=#C04040E0>MURDER</color></b><color=#00ff00ff> all who dare to engage you!</color>", - "Ignore Alignment Requirements for Abilities": "Ignore Alignment Requirements for Abilities", - "Ignore Alignment When Choosing A Class": "Ignore Alignment When Choosing A Class", - "Ignore all Requirements for Abilities": "Ignore all Requirements for Abilities", - "Ignore Attribute Cap": "Ignore Attribute Cap", - "Ignore Caster Type And Spell Level Restrictions": "Ignore Caster Type And Spell Level Restrictions", - "Ignore Class Restrictions": "Ignore Class Restrictions", - "Ignore Event Solution Restrictions": "Ignore Event Solution Restrictions", - "Ignore Feat Restrictions": "Ignore Feat Restrictions", - "Ignore Forbidden Archetypes": "Ignore Forbidden Archetypes", - "Ignore Pet Sizes For Mounting": "Ignore Pet Sizes For Mounting", - "Ignore Prerequisite Features (like Race) when choosing Class": "Ignore Prerequisite Features (like Race) when choosing Class", - "Ignore Prerequisites When Choosing A Feat": "Ignore Prerequisites When Choosing A Feat", - "Ignore Remaining Attribute Points": "Ignore Remaining Attribute Points", - "Ignore Remaining Skill Points": "Ignore Remaining Skill Points", - "Ignore Required Class Levels": "Ignore Required Class Levels", - "Ignore Required Stat Values": "Ignore Required Stat Values", - "Ignore Skill Cap": "Ignore Skill Cap", - "Increase Carry Capacity": "Increase Carry Capacity", - "Increase Carry Capacity (Party Only)": "Increase Carry Capacity (Party Only)", - "increment": "increment", - "Infinite Abilities": "Infinite Abilities", - "Infinite Charges On Items": "Infinite Charges On Items", - "Infinite Spell Casts": "Infinite Spell Casts", - "In-Game Name": "In-Game Name", - "Initiative: Always Roll 1": "Initiative: Always Roll 1", - "Initiative: Always Roll 20": "Initiative: Always Roll 20", - "Instant change party members": "Instant change party members", - "Instant Cooldown": "Instant Cooldown", - "Instant Rest After Combat": "Instant Rest After Combat", - "Internal Name": "Internal Name", - "Invert X Axis": "Invert X Axis", - "Jealousy Begone!": "Jealousy Begone!", - "Keyboard:": "Keyboard:", - "Kill All Enemies": "Kill All Enemies", - "Kineticist: Allow Gather Power Without Hands": "Kineticist: Allow Gather Power Without Hands", - "Kineticist: Burn Reduction": "Kineticist: Burn Reduction", - "Level Increase/Decrease": "Level Increase/Decrease", - "Lobotomize Enemies": "Lobotomize Enemies", - "Lock Character Level": "Lock Character Level", - "Log ToyBox Keyboard Commands In Game": "Log ToyBox Keyboard Commands In Game", - "Lose ": "Lose ", - "Magus: Always Allow Spell Combat": "Magus: Always Allow Spell Combat", - "Main/Alt Timescale": "Main/Alt Timescale", - "Make All Feature Selections Optional": "Make All Feature Selections Optional", - "Make Controllable": "Make Controllable", - "Make game continue to play music on lost focus": "Make game continue to play music on lost focus", - "Make Puzzle Symbols More Clear": "Make Puzzle Symbols More Clear", - "Make Spell/Ability/Item Pop-Ups Wider ": "Make Spell/Ability/Item Pop-Ups Wider ", - "Make sure you have auto-fill turned off in settings or else this will just reset to default": "Make sure you have auto-fill turned off in settings or else this will just reset to default", - "Make tutorials not appear if disabled in settings": "Make tutorials not appear if disabled in settings", - "Makes alignment shifts towards pure good/evil/lawful/chaotic only shift on those axes": "Makes alignment shifts towards pure good/evil/lawful/chaotic only shift on those axes", - "Makes mouse zoom works for the local map (cities, dungeons, etc). Game restart required if you turn it off": "Makes mouse zoom works for the local map (cities, dungeons, etc). Game restart required if you turn it off", - "Mark Interesting NPCs": "Mark Interesting NPCs", - "Migrate": "Migrate", - "Modify Summons For": "Modify Summons For", - "Money Earned": "Money Earned", - "Mouse:": "Mouse:", - "Movement Speed": "Movement Speed", - "Multi-class settings": "Multi-class settings", - "Multiple Classes": "Multiple Classes", - "Multiple Classes On Level-Up": "Multiple Classes On Level-Up", - "Mythic": "Mythic", - "Mythic Paths": "Mythic Paths", - "Never Roll 1": "Never Roll 1", - "Never Roll 20": "Never Roll 20", - "No Fog Of War": "No Fog Of War", - "No Friendly Fire On AOEs": "No Friendly Fire On AOEs", - "No Material Components": "No Material Components", - "Non Combat: Take 10": "Non Combat: Take 10", - "Non Combat: Take 20": "Non Combat: Take 20", - "Object Highlight Toggle Mode": "Object Highlight Toggle Mode", - "Off": "Off", - "Only available at Mythic level 8 or higher": "Only available at Mythic level 8 or higher", - "Other Multipliers": "Other Multipliers", - "Override for Combat": "Override for Combat", - "Override for Quests": "Override for Quests", - "Override for Skill Checks": "Override for Skill Checks", - "Override for Traps": "Override for Traps", - "Page % of %": "Page % of %", - "Party": "Party", - "Party Level Cap 24 (exponential growth)": "Party Level Cap 24 (exponential growth)", - "Party Level Cap 40 (continuous growth after 20)": "Party Level Cap 40 (continuous growth after 20)", - "Please rest after adjusting to recalculate your spell slots.": "Please rest after adjusting to recalculate your spell slots.", - "Prepared Spellslots": "Prepared Spellslots", - "Prevent Alignment Changes": "Prevent Alignment Changes", - "Prevents dumb companions (that's you Greybor) from wiping the party by running running into the dragon room and dying...": "Prevents dumb companions (that's you Greybor) from wiping the party by running running into the dragon room and dying...", - "Prevents you from advancing all other Mythic Path": "Prevents you from advancing all other Mythic Path", - "Prevents you from advancing in Aeon or Azata": "Prevents you from advancing in Aeon or Azata", - "Preview": "Preview", - "Preview Results": "Preview Results", - "Previously Chosen Dialog Is Smaller ": "Previously Chosen Dialog Is Smaller ", - "Primary": "Primary", - "Quality of Life": "Quality of Life", - "Random Encounters": "Random Encounters", - "Refill consumables in belt slots if in inventory": "Refill consumables in belt slots if in inventory", - "Relic Info": "Relic Info", - "Remove": "Remove", - "Remove Buffs": "Remove Buffs", - "Remove Deaths Door": "Remove Deaths Door", - "Remove Level 20 Caster Level Cap": "Remove Level 20 Caster Level Cap", - "Reroll Perception": "Reroll Perception", - "Reset Interactables": "Reset Interactables", - "Resources": "Resources", - "Respec Refund Scrolls": "Respec Refund Scrolls", - "Rest All": "Rest All", - "Rest Selected": "Rest Selected", - "Restore Spells & Skills After Combat": "Restore Spells & Skills After Combat", - "Ride Any Unit As Your Mount": "Ride Any Unit As Your Mount", - "Roll With Avantage": "Roll With Avantage", - "Roll With Disavantage": "Roll With Disavantage", - "Rotation": "Rotation", - "Saving Throw Growth": "Saving Throw Growth", - "Secondary": "Secondary", - "See Party Editor for more fine grained alignment locking per character": "See Party Editor for more fine grained alignment locking per character", - "Set Perception to 40": "Set Perception to 40", - "Show": "Show", - "Show Acronyms in Spell/Ability/Item Pop-Ups": "Show Acronyms in Spell/Ability/Item Pop-Ups", - "Show Class Descriptions": "Show Class Descriptions", - "Show Migrations": "Show Migrations", - "Skill Checks: Take 10": "Skill Checks: Take 10", - "Skill Checks: Take 20": "Skill Checks: Take 20", - "Skill Point Growth": "Skill Point Growth", - "Skip Spell Selection": "Skip Spell Selection", - "Some players find spiders and other swarms icky. This replaces them with something more pleasant": "Some players find spiders and other swarms icky. This replaces them with something more pleasant", - "Some responses such as comments about your mythic powers will always choose the first one by default. This will show a copy of the answer and the condition for each possible response that an NPC might make to you based on": "Some responses such as comments about your mythic powers will always choose the first one by default. This will show a copy of the answer and the condition for each possible response that an NPC might make to you based on", - "Speeds up or slows down the entire game (movement, animation, everything)": "Speeds up or slows down the entire game (movement, animation, everything)", - "Spontaneous Caster Scroll Copy": "Spontaneous Caster Scroll Copy", - "Spontaneous Spells Per Day": "Spontaneous Spells Per Day", - "Summons": "Summons", - "Teleport": "Teleport", - "Teleport Party To You": "Teleport Party To You", - "The following skill check adjustments apply only out of combat": "The following skill check adjustments apply only out of combat", - "This <b>incredibly dangerous</b> setting overrides the default behavior of failing to load saves depending on missing blueprint mods. This desperate action can potentially enable you to recover your saved game, though you'll have to respec at minimum.": "This <b>incredibly dangerous</b> setting overrides the default behavior of failing to load saves depending on missing blueprint mods. This desperate action can potentially enable you to recover your saved game, though you'll have to respec at minimum.", - "This allows characters you control to move through the selected category of units during combat": "This allows characters you control to move through the selected category of units during combat", - "This allows you to play with the originally released Unfair difficulty. ": "This allows you to play with the originally released Unfair difficulty. ", - "This allows you to select a given feature more than once at level up": "This allows you to select a given feature more than once at level up", - "This allows you to select combinations of archetypes when selecting a class for the first time that contain distinct spellbooks": "This allows you to select combinations of archetypes when selecting a class for the first time that contain distinct spellbooks", - "this flag lets you not count this class in computing character level": "this flag lets you not count this class in computing character level", - "This is intended for you to be able to enjoy the game while using mods that enhance your quality of life. Please be mindful of the player community and avoid using this mod to trivialize earning prestige achievements like Sadistic Gamer. The author is in discussion with Owlcat about reducing the scope of achievement blocking to just these. Let's show them that we as players can mod and cheat responsibly.": "This is intended for you to be able to enjoy the game while using mods that enhance your quality of life. Please be mindful of the player community and avoid using this mod to trivialize earning prestige achievements like Sadistic Gamer. The author is in discussion with Owlcat about reducing the scope of achievement blocking to just these. Let's show them that we as players can mod and cheat responsibly.", - "This resets all the skill check rolls for all interactable objects in the area": "This resets all the skill check rolls for all interactable objects in the area", - "This will change the color of NPC names on the highlike makers and change the color map markers to indicate that they have interesting or conditional interactions": "This will change the color of NPC names on the highlike makers and change the color map markers to indicate that they have interesting or conditional interactions", - "this will remove your old multiclass settings from ToyBox settings but does not affect any other saves that have already migrated them": "this will remove your old multiclass settings from ToyBox settings but does not affect any other saves that have already migrated them", - "to select this class you must unselect at least one of your other existing classes": "to select this class you must unselect at least one of your other existing classes", - "ToyBox Archeologists can tag confusing puzzle pieces with green numbers in the game world and for inventory tool tips it will show text like this: <b><color=yellow>[PuzzlePiece Green3x1]</color></b><b><color=orange>\nNOTE: </color></b><color=orange>Needs game restart to take efect</color>": "ToyBox Archeologists can tag confusing puzzle pieces with green numbers in the game world and for inventory tool tips it will show text like this: <b><color=yellow>[PuzzlePiece Green3x1]</color></b><b><color=orange>\nNOTE: </color></b><color=orange>Needs game restart to take efect</color>", - "Travel Speed": "Travel Speed", - "Turn Based Combat Delay": "Turn Based Combat Delay", - "Unlimited Actions During Turn": "Unlimited Actions During Turn", - "Unlimited Stacking of Modifiers (Stat/AC/Hit/Damage/Etc)": "Unlimited Stacking of Modifiers (Stat/AC/Hit/Damage/Etc)", - "Unlock Aeon": "Unlock Aeon", - "Unlock Azata": "Unlock Azata", - "Unlock Devil": "Unlock Devil", - "Unlock Gold Dragon": "Unlock Gold Dragon", - "Unlock Legend": "Unlock Legend", - "Unlock Lich": "Unlock Lich", - "Unlock mythic paths besides Legend and Devil which block progression": "Unlock mythic paths besides Legend and Devil which block progression", - "Unlock Swarm": "Unlock Swarm", - "Unlock Trickster": "Unlock Trickster", - "Vendor Buy Price": "Vendor Buy Price", - "Vendor Sell Price": "Vendor Sell Price", - "Warning! Using these might break your game somehow. Recommend for experimental tinkering like trying out different builds, and not for actually playing the game.": "Warning! Using these might break your game somehow. Recommend for experimental tinkering like trying out different builds, and not for actually playing the game.", - "Warning: ": "Warning: ", - "when leveling up ": "when leveling up ", - "When ticked this shows ToyBox commands in the combat log which is helpful for you to know when you used the shortcut": "When ticked this shows ToyBox commands in the combat log which is helpful for you to know when you used the shortcut", - "Whole Team Moves Same Speed": "Whole Team Moves Same Speed", - "Witch/Shaman: Cackling/Shanting Extends Hexes By 10 Min (Out Of Combat)": "Witch/Shaman: Cackling/Shanting Extends Hexes By 10 Min (Out Of Combat)", - "You": "You", - "You can enable hot keys to teleport members of your party to your mouse cursor on Area or the Global Map": "You can enable hot keys to teleport members of your party to your mouse cursor on Area or the Global Map" - } + "Strings": { + " (This setting is per-save)": " (This setting is per-save)", + " Allow remote companions to make comments on dialog you are having.": " Allow remote companions to make comments on dialog you are having.", + " by the following amount:": " by the following amount:", + " meters": " meters", + "<": "<", + "<b><color=magenta>Note </color></b><b><color=orange>ToyBox was designed to offer the best user experience at widths of 1920 or higher. Please consider increasing your resolution up of at least 1920x1080 (ideally 4k) and go to Unity Mod Manager 'Settings' tab to change the mod window width to at least 1920. Increasing the UI scale is nice too when running at 4k</color></b>": "<b><color=magenta>Note </color></b><b><color=orange>ToyBox was designed to offer the best user experience at widths of 1920 or higher. Please consider increasing your resolution up of at least 1920x1080 (ideally 4k) and go to Unity Mod Manager 'Settings' tab to change the mod window width to at least 1920. Increasing the UI scale is nice too when running at 4k</color></b>", + "<b><color=magenta>Warning: </color></b><color=orange>This is an experimental preview of ToyBox (<color=cyan>Sh0dan</color>) for Rogue Trader Beta.</color><b><color=yellow> Save early and often.\r\n</color></b><b><color=magenta>Note:</color></b><color=orange> Not all features are functional at this time. The ToyBox team is working hard to get as much working as fast as possible</color>": "<b><color=magenta>Warning: </color></b><color=orange>This is an experimental preview of ToyBox (<color=cyan>Sh0dan</color>) for Rogue Trader Beta.</color><b><color=yellow> Save early and often.\r\n</color></b><b><color=magenta>Note:</color></b><color=orange> Not all features are functional at this time. The ToyBox team is working hard to get as much working as fast as possible</color>", + "<b><color=magenta>Warning: </color></b><color=orange>This is an experimental preview of ToyBox for Rogue Trader.</color><b><color=yellow> Save early and often.\r\n</color></b><b><color=magenta>Note:</color></b><color=orange> Not all features are functional at this time. If you notice a feature doesn't work please report that on GitHub or in the modding channels on the Owlcat Discord.</color>": "<b><color=magenta>Warning: </color></b><color=orange>This is an experimental preview of ToyBox for Rogue Trader.</color><b><color=yellow> Save early and often.\r\n</color></b><b><color=magenta>Note:</color></b><color=orange> Not all features are functional at this time. If you notice a feature doesn't work please report that on GitHub or in the modding channels on the Owlcat Discord.</color>", + "<b><color=orange>Achievements</color></b>": "<b><color=orange>Achievements</color></b>", + "<b><color=orange>Bag of Tricks</color></b>": "<b><color=orange>Bag of Tricks</color></b>", + "<b><color=orange>Blueprints</color></b> loading: ": "<b><color=orange>Blueprints</color></b> loading: ", + "<b><color=orange>character(s) can be </color></b><color=cyan>Recruited</color><color=#00ff00ff>. This allows you to add non party NPCs to your party as if they were mercenaries</color>": "<b><color=orange>character(s) can be </color></b><color=cyan>Recruited</color><color=#00ff00ff>. This allows you to add non party NPCs to your party as if they were mercenaries</color>", + "<b><color=orange>character(s) can be </color></b><color=cyan>Respecced</color><color=#00ff00ff>. Pressing Respec will close the mod window and take you to character level up</color>": "<b><color=orange>character(s) can be </color></b><color=cyan>Respecced</color><color=#00ff00ff>. Pressing Respec will close the mod window and take you to character level up</color>", + "<b><color=orange>Colonies</color></b>": "<b><color=orange>Colonies</color></b>", + "<b><color=orange>Dialog & NPCs</color></b>": "<b><color=orange>Dialog & NPCs</color></b>", + "<b><color=orange>Enchantment</color></b>": "<b><color=orange>Enchantment</color></b>", + "<b><color=orange>Enhanced UI</color></b>": "<b><color=orange>Enhanced UI</color></b>", + "<b><color=orange>Etudes</color></b>": "<b><color=orange>Etudes</color></b>", + "<b><color=orange>Level Up</color></b>": "<b><color=orange>Level Up</color></b>", + "<b><color=orange>Loot</color></b>": "<b><color=orange>Loot</color></b>", + "<b><color=orange>Note</color></b><color=#00ff00ff> this is a new and exciting feature that allows you to see for the first time the structure and some basic relationships of </color><b><color=cyan>Etudes</color></b><color=#00ff00ff> and other </color><b><color=cyan>Elements</color></b><color=#00ff00ff> that control the progression of your game story. Etudes are hierarchical in structure and additionally contain a set of </color><b><color=cyan>Elements</color></b><color=#00ff00ff> that can both conditions to check and actions to execute when the etude is started. As you browe you will notice there is a disclosure triangle next to the name which will show the children of the Etude. Etudes that have </color><b><color=cyan>Elements</color></b><color=#00ff00ff> will offer a second disclosure triangle next to the status that will show them to you.</color>": "<b><color=orange>Note</color></b><color=#00ff00ff> this is a new and exciting feature that allows you to see for the first time the structure and some basic relationships of </color><b><color=cyan>Etudes</color></b><color=#00ff00ff> and other </color><b><color=cyan>Elements</color></b><color=#00ff00ff> that control the progression of your game story. Etudes are hierarchical in structure and additionally contain a set of </color><b><color=cyan>Elements</color></b><color=#00ff00ff> that can both conditions to check and actions to execute when the etude is started. As you browe you will notice there is a disclosure triangle next to the name which will show the children of the Etude. Etudes that have </color><b><color=cyan>Elements</color></b><color=#00ff00ff> will offer a second disclosure triangle next to the status that will show them to you.</color>", + "<b><color=orange>Party</color></b>": "<b><color=orange>Party</color></b>", + "<b><color=orange>Quests</color></b>": "<b><color=orange>Quests</color></b>", + "<b><color=orange>Saves</color></b>": "<b><color=orange>Saves</color></b>", + "<b><color=orange>Search 'n Pick</color></b>": "<b><color=orange>Search 'n Pick</color></b>", + "<b><color=orange>Settings</color></b>": "<b><color=orange>Settings</color></b>", + "<b><color=yellow>BACK UP</color></b><color=orange> before playing with this feature.You will lose your mythic ranks but you can restore them in this Party Editor.</color>": "<b><color=yellow>BACK UP</color></b><color=orange> before playing with this feature.You will lose your mythic ranks but you can restore them in this Party Editor.</color>", + "<b><color=yellow>WARNING</color></b><color=orange> The Respec UI is </color><b><color=yellow>Non Interruptable</color></b><color=orange> please save before using</color>": "<b><color=yellow>WARNING</color></b><color=orange> The Respec UI is </color><b><color=yellow>Non Interruptable</color></b><color=orange> please save before using</color>", + "<b><color=yellow>WARNING</color></b><color=orange> these features are </color><b><color=yellow>EXPERIMENTAL</color></b><color=orange> and uses unreleased and likely buggy code.</color>": "<b><color=yellow>WARNING</color></b><color=orange> these features are </color><b><color=yellow>EXPERIMENTAL</color></b><color=orange> and uses unreleased and likely buggy code.</color>", + "<b><color=yellow>WARNING</color></b><color=orange> this tool can both miraculously fix your broken progression or it can break it even further. Save and back up your save before using.</color>": "<b><color=yellow>WARNING</color></b><color=orange> this tool can both miraculously fix your broken progression or it can break it even further. Save and back up your save before using.</color>", + "<color=#00A000FF>Uncommon</color>": "<color=#00A000FF>Uncommon</color>", + "<color=#00ff00ff>Allow </color><color=#C060F0FF>any gender</color> <color=#00ff00ff>for any </color><color=#FF4040FF>R</color><color=orange>o</color><color=yellow>m</color><color=#00ff00ff>a</color><color=cyan>n</color><color=#8080FFFF>c</color><color=#C060F0FF>e</color>": "<color=#00ff00ff>Allow </color><color=#C060F0FF>any gender</color> <color=#00ff00ff>for any </color><color=#FF4040FF>R</color><color=orange>o</color><color=yellow>m</color><color=#00ff00ff>a</color><color=cyan>n</color><color=#8080FFFF>c</color><color=#C060F0FF>e</color>", + "<color=#00ff00ff>Allow </color><color=#C060F0FF>multiple</color><color=#00ff00ff> romances at the same time</color>": "<color=#00ff00ff>Allow </color><color=#C060F0FF>multiple</color><color=#00ff00ff> romances at the same time</color>", + "<color=#00ff00ff>Some responses such as comments about your mythic powers will always choose the first one by default. This allows the game to mix things up a bit</color><b><color=yellow>\nWarning:</color></b><color=orange> this will introduce randomness to NPC responses to you in general and may lead to surprising or even wild outcomes</color>": "<color=#00ff00ff>Some responses such as comments about your mythic powers will always choose the first one by default. This allows the game to mix things up a bit</color><b><color=yellow>\nWarning:</color></b><color=orange> this will introduce randomness to NPC responses to you in general and may lead to surprising or even wild outcomes</color>", + "<color=#00ff00ff>Tells the game to reset the in game UI.</color><color=yellow> Warning</color><color=orange> Using this in dialog or the book will dismiss that dialog which may break progress so use with care</color>": "<color=#00ff00ff>Tells the game to reset the in game UI.</color><color=yellow> Warning</color><color=orange> Using this in dialog or the book will dismiss that dialog which may break progress so use with care</color>", + "<color=#00ff00ff>This allows rechosing the first arcehtype. Also makes Companion respec start from level 0.</color>": "<color=#00ff00ff>This allows rechosing the first arcehtype. Also makes Companion respec start from level 0.</color>", + "<color=#00ff00ff>This allows you to loot items that are locked such as items carried by certain NPCs and items locked on your characters</color><b><color=yellow>\nWARNING: </color></b><color=yellow>This may affect story progression (e.g. your purple knife)</color>": "<color=#00ff00ff>This allows you to loot items that are locked such as items carried by certain NPCs and items locked on your characters</color><b><color=yellow>\nWARNING: </color></b><color=yellow>This may affect story progression (e.g. your purple knife)</color>", + "<color=#00ff00ff>This makes loot function like Diablo or Borderlands. <color=orange>Note: turning this off requires you to save and reload for it to take effect.</color></color>": "<color=#00ff00ff>This makes loot function like Diablo or Borderlands. <color=orange>Note: turning this off requires you to save and reload for it to take effect.</color></color>", + "<color=#1030E0FF>Rare</color>": "<color=#1030E0FF>Rare</color>", + "<color=#6030F0FF>Epic</color>": "<color=#6030F0FF>Epic</color>", + "<color=#60FFFFFF>Primal</color>": "<color=#60FFFFFF>Primal</color>", + "<color=#808080FF>Trash</color>": "<color=#808080FF>Trash</color>", + "<color=#98761FFF>Notable</color>": "<color=#98761FFF>Notable</color>", + "<color=#A00000FF>Godly</color>": "<color=#A00000FF>Godly</color>", + "<color=#A000A0FF>Mythic</color>": "<color=#A000A0FF>Mythic</color>", + "<color=#C04040E0>♥♥ </color><b>Love is Free</b><color=#C04040E0> ♥♥</color>": "<color=#C04040E0>♥♥ </color><b>Love is Free</b><color=#C04040E0> ♥♥</color>", + "<color=#D0D0D0FF>None</color>": "<color=#D0D0D0FF>None</color>", + "<color=#D8D8D8A0>Common</color>": "<color=#D8D8D8A0>Common</color>", + "<color=#E67821E0>Legendary</color>": "<color=#E67821E0>Legendary</color>", + "<color=cyan> + Click</color> To Transfer Entire Stack": "<color=cyan> + Click</color> To Transfer Entire Stack", + "<color=cyan> + Click</color> To Use Items In Inventory": "<color=cyan> + Click</color> To Use Items In Inventory", + "<color=orange>Experimental</color> Allow remote companions to make comments on dialog you are having.": "<color=orange>Experimental</color> Allow remote companions to make comments on dialog you are having.", + "<color=orange>Note:</color><color=#00ff00ff> For cutscenes and some situations the rotation keys are disabled so you have to hold down Mouse3 to drag in order to get rotation</color>": "<color=orange>Note:</color><color=#00ff00ff> For cutscenes and some situations the rotation keys are disabled so you have to hold down Mouse3 to drag in order to get rotation</color>", + "<color=orange>Sandal says '</color><b><color=cyan>Enchantment'</color></b>": "<color=orange>Sandal says '</color><b><color=cyan>Enchantment'</color></b>", + ">": ">", + "Abilities": "Abilities", + "Ability Rsrc": "Ability Rsrc", + "Achievements": "Achievements", + "Achievements not available until you load a save.": "Achievements not available until you load a save.", + "Active": "Active", + "Add": "Add", + "Addendum: ": "Addendum: ", + "Adds a search field to Load/Save screen (in game only)": "Adds a search field to Load/Save screen (in game only)", + "Adjust ": "Adjust ", + "Adjust based on Level": "Adjust based on Level", + "Adjust Navigator Insight by the following amount:": "Adjust Navigator Insight by the following amount:", + "Adjust Profit Factor by the following amount:": "Adjust Profit Factor by the following amount:", + "Adjust Reputation by the following amount:": "Adjust Reputation by the following amount:", + "Adjust Resource Factor by the following amount:": "Adjust Resource Factor by the following amount:", + "Adjust Scrap by the following amount:": "Adjust Scrap by the following amount:", + "Adjusts costs of hiring mercenaries at the Pathfinder vendor": "Adjusts costs of hiring mercenaries at the Pathfinder vendor", + "Adjusts the movement speed of your party in area maps": "Adjusts the movement speed of your party in area maps", + "Adjusts the movement speed of your party on world maps": "Adjusts the movement speed of your party on world maps", + "All": "All", + "All Attacks Hit": "All Attacks Hit", + "All Experience": "All Experience", + "All Hits Critical": "All Hits Critical", + "All Units": "All Units", + "Allow ": "Allow ", + "Allow Achievements While Using Mods": "Allow Achievements While Using Mods", + "Allow Equipment Change During Combat": "Allow Equipment Change During Combat", + "Allow Item Use From Inventory During Combat": "Allow Item Use From Inventory During Combat", + "Allow Looting Of Locked Items": "Allow Looting Of Locked Items", + "Allow Mass Loot to steal from living NPCs": "Allow Mass Loot to steal from living NPCs", + "Alternate Time Scale": "Alternate Time Scale", + "Always Roll 1": "Always Roll 1", + "Always Roll 100": "Always Roll 100", + "Always Roll 50": "Always Roll 50", + "Answer": "Answer", + "Apply Bug Fixes": "Apply Bug Fixes", + "Archetypes": "Archetypes", + "Area Entry": "Area Entry", + "Areas": "Areas", + "Armor": "Armor", + "ArmourAft": "ArmourAft", + "ArmourFore": "ArmourFore", + "ArmourPort": "ArmourPort", + "ArmourStarboard": "ArmourStarboard", + "AttackOfOpportunityCount": "AttackOfOpportunityCount", + "Attributes": "Attributes", + "Auto Follow While Holding Camera Follow Key": "Auto Follow While Holding Camera Follow Key", + "Auto load Last Save on launch": "Auto load Last Save on launch", + "Bag of Tricks": "Bag of Tricks", + "Be a <b><color=#C04040E0>Murder</color></b><color=orange> Hobo</color>": "Be a <b><color=#C04040E0>Murder</color></b><color=orange> Hobo</color>", + "Belt": "Belt", + "Bind": "Bind", + "Bindable hot key to swap between main and alternate time scale multipliers": "Bindable hot key to swap between main and alternate time scale multipliers", + "Blueprint": "Blueprint", + "bp": "bp", + "Brains": "Brains", + "Buff Duration": "Buff Duration", + "Buff Like A Goddess": "Buff Like A Goddess", + "Buffs": "Buffs", + "buffs to add to list": "buffs to add to list", + "Camera": "Camera", + "Can Move Through": "Can Move Through", + "Can Start": "Can Start", + "Careers": "Careers", + "Categories": "Categories", + "Change Colony Stat": "Change Colony Stat", + "Change Party": "Change Party", + "Change Portrait": "Change Portrait", + "Change the party without advancing time (good to bind)": "Change the party without advancing time (good to bind)", + "Change Voice": "Change Voice", + "Change Weather": "Change Weather", + "Changing your gender may cause visual glitches": "Changing your gender may cause visual glitches", + "Character Level": "Character Level", + "Cheats": "Cheats", + "Check for Glyph Support": "Check for Glyph Support", + "CheckBluff": "CheckBluff", + "CheckDiplomacy": "CheckDiplomacy", + "CheckIntimidate": "CheckIntimidate", + "Classes": "Classes", + "Click On Equip Slots To Filter Inventory": "Click On Equip Slots To Filter Inventory", + "Collapse All": "Collapse All", + "Collating...": "Collating...", + "Colonies": "Colonies", + "Colonize": "Colonize", + "ColonyFoundation": "ColonyFoundation", + "Color Item Names": "Color Item Names", + "Colossal": "Colossal", + "Combat": "Combat", + "Common": "Common", + "Common Buffs": "Common Buffs", + "Common Tweaks": "Common Tweaks", + "Companion Cost": "Companion Cost", + "Complete": "Complete", + "Complete (Final)": "Complete (Final)", + "Components": "Components", + "condition: ": "condition: ", + "conflicts": "conflicts", + "Containers": "Containers", + "Contentment": "Contentment", + "Copying...": "Copying...", + "Corruption": "Corruption", + "Create & Level Up": "Create & Level Up", + "Crew": "Crew", + "Cruiser_2x4": "Cruiser_2x4", + "Ctrl + Mouse3 Drag To Adjust Camera Elevation": "Ctrl + Mouse3 Drag To Adjust Camera Elevation", + "Cues": "Cues", + "Current Amount": "Current Amount", + "Current Blueprint Portrait": "Current Blueprint Portrait", + "Current Culture": "Current Culture", + "Current Navigator Insight": "Current Navigator Insight", + "Current Profit Factor": "Current Profit Factor", + "Current Reputation": "Current Reputation", + "Current Scrap": "Current Scrap", + "Current Veil Thickness": "Current Veil Thickness", + "Custom": "Custom", + "custom exceptions": "custom exceptions", + "Cut Scenes": "Cut Scenes", + "Debug": "Debug", + "default exceptions": "default exceptions", + "Description": "Description", + "Dialog": "Dialog", + "Dialog & NPCs": "Dialog & NPCs", + "Dialog Alignment": "Dialog Alignment", + "Dialog Conditions": "Dialog Conditions", + "Dialog Results": "Dialog Results", + "Dice Rolls": "Dice Rolls", + "Diminutive": "Diminutive", + "Disable Attacks Of Opportunity": "Disable Attacks Of Opportunity", + "Disable Dialog Restrictions (Everything, Experimental)": "Disable Dialog Restrictions (Everything, Experimental)", + "Disable Dialog Restrictions (SoulMark)": "Disable Dialog Restrictions (SoulMark)", + "Disable end turn HotKey": "Disable end turn HotKey", + "Disable Random Encounters in Warp": "Disable Random Encounters in Warp", + "Disable Voice Over and Barks for this character": "Disable Voice Over and Barks for this character", + "Display guids in most tooltips, use shift + left click on items/abilities to copy guid to clipboard": "Display guids in most tooltips, use shift + left click on items/abilities to copy guid to clipboard", + "Display risky options": "Display risky options", + "Don't use AP (except abilities which consume all AP) During Turn": "Don't use AP (except abilities which consume all AP) During Turn", + "Don't wait for keypress when loading saves": "Don't wait for keypress when loading saves", + "Draws dialog choices that you have previously selected in smaller type": "Draws dialog choices that you have previously selected in smaller type", + "Drusians": "Drusians", + "Duration Multiplier": "Duration Multiplier", + "Edit Resources": "Edit Resources", + "Efficiency": "Efficiency", + "elements": "elements", + "Elements": "Elements", + "Empowered": "Empowered", + "Enable Game Development Mode": "Enable Game Development Mode", + "Enable Loading with Blueprint Errors": "Enable Loading with Blueprint Errors", + "Enable Mouse3 Dragging To Aim The Camera": "Enable Mouse3 Dragging To Aim The Camera", + "Enable Rotate on all maps and cutscenes": "Enable Rotate on all maps and cutscenes", + "Enable Search as you type for Browsers (needs restart)": "Enable Search as you type for Browsers (needs restart)", + "Enable Teleport Keys": "Enable Teleport Keys", + "Enable Zoom on all maps and cutscenes": "Enable Zoom on all maps and cutscenes", + "Ench. Type": "Ench. Type", + "Ench. Types": "Ench. Types", + "Enchantment": "Enchantment", + "Enemies": "Enemies", + "Enemy HP Multiplier": "Enemy HP Multiplier", + "EnemyShip": "EnemyShip", + "Enhanced Load/Save": "Enhanced Load/Save", + "Enhanced Map View": "Enhanced Map View", + "Enhanced UI": "Enhanced UI", + "Enterint a Trap Zone while having Traps disabled will prevent that Trap from triggering even if you deactivate this option in the future": "Enterint a Trap Zone while having Traps disabled will prevent that Trap from triggering even if you deactivate this option in the future", + "Equip (rarity)": "Equip (rarity)", + "Equipment": "Equipment", + "Error": "Error", + "Etude Status: ": "Etude Status: ", + "Etudes": "Etudes", + "Evasion": "Evasion", + "Everyone": "Everyone", + "Exceptions to Buff Duration Multiplier (Advanced; will cause blueprints to load)": "Exceptions to Buff Duration Multiplier (Advanced; will cause blueprints to load)", + "Expand All": "Expand All", + "Expand Answers For Conditional Responses": "Expand Answers For Conditional Responses", + "Expand Dialog To Include Remote Companions": "Expand Dialog To Include Remote Companions", + "Experience": "Experience", + "Experience Multipliers": "Experience Multipliers", + "Explorators": "Explorators", + "Export": "Export", + "Export current locale to file": "Export current locale to file", + "Faction Selector": "Faction Selector", + "Facts": "Facts", + "Failure to load saves that reference custom portraits": "Failure to load saves that reference custom portraits", + "Faith": "Faith", + "Features": "Features", + "Feet": "Feet", + "Female": "Female", + "Field Of View": "Field Of View", + "Fine": "Fine", + "Finish": "Finish", + "Fix Camera": "Fix Camera", + "Fix Incorrect Main Character": "Fix Incorrect Main Character", + "Flags": "Flags", + "Flags Only": "Flags Only", + "Fog of War Range": "Fog of War Range", + "FoV (Cut Scenes)": "FoV (Cut Scenes)", + "Free Camera": "Free Camera", + "Friendly": "Friendly", + "Frigate_1x2": "Frigate_1x2", + "Gain ": "Gain ", + "Game Time Scale": "Game Time Scale", + "Gargantuan": "Gargantuan", + "Gender": "Gender", + "Generate Comment Translation Table": "Generate Comment Translation Table", + "Give All Items": "Give All Items", + "Giving characters voices besides the default ones is untested.": "Giving characters voices besides the default ones is untested.", + "Glasses": "Glasses", + "Gloves": "Gloves", + "Go To Global Map": "Go To Global Map", + "Go to page: ": "Go to page: ", + "Go!": "Go!", + "Gold": "Gold", + "good for larger group or to reduce enemies": "good for larger group or to reduce enemies", + "good for party": "good for party", + "GrandCruiser_3x6": "GrandCruiser_3x6", + "Head": "Head", + "Hide": "Hide", + "Hide Completed": "Hide Completed", + "Highlight Copyable Scrolls": "Highlight Copyable Scrolls", + "Highlight Hidden Objects": "Highlight Hidden Objects", + "HitPoints": "HitPoints", + "Hold down shift during launch to bypass": "Hold down shift during launch to bypass", + "Hope": "Hope", + "Huge": "Huge", + "Identify All": "Identify All", + "If ticked, this will <b><color=#C04040E0>MURDER</color></b><color=#00ff00ff> all who dare to engage you!</color>": "If ticked, this will <b><color=#C04040E0>MURDER</color></b><color=#00ff00ff> all who dare to engage you!</color>", + "If you have a save that uses custom portraits and don't toggle this your game will crash when starting": "If you have a save that uses custom portraits and don't toggle this your game will crash when starting", + "If you tick this you can click on equipment slots to filter the inventory for items that fit in it.\nFor more <color=orange>Enhanced Inventory</color> and <color=orange>Spellbook</color> check out the <b><color=orange>Loot & Spellbook Tab</color></b>": "If you tick this you can click on equipment slots to filter the inventory for items that fit in it.\nFor more <color=orange>Enhanced Inventory</color> and <color=orange>Spellbook</color> check out the <b><color=orange>Loot & Spellbook Tab</color></b>", + "Ignore Ability Requirement - AOE Overlap": "Ignore Ability Requirement - AOE Overlap", + "Ignore Ability Requirement - Line of Sight": "Ignore Ability Requirement - Line of Sight", + "Ignore Ability Requirement - Max Range": "Ignore Ability Requirement - Max Range", + "Ignore Ability Requirement - Min Range": "Ignore Ability Requirement - Min Range", + "Ignore all Requirements for Abilities": "Ignore all Requirements for Abilities", + "Ignore Class Restrictions": "Ignore Class Restrictions", + "ignore Equipment Restrictions": "ignore Equipment Restrictions", + "Ignore Required Class Levels": "Ignore Required Class Levels", + "Ignore Required Stat Values": "Ignore Required Stat Values", + "Ignore Talent Prerequisites": "Ignore Talent Prerequisites", + "Import": "Import", + "Import/Export allows you to save and add a list of items to a file based on the type (e.g. Weapon.json). These files live in a new ToyBox folder that in the same folder that contains your saved games ": "Import/Export allows you to save and add a list of items to a file based on the type (e.g. Weapon.json). These files live in a new ToyBox folder that in the same folder that contains your saved games ", + "In Fog Of War ": "In Fog Of War ", + "Include Former Companions": "Include Former Companions", + "Increase Carry Capacity": "Increase Carry Capacity", + "Increase Carry Capacity (Party Only)": "Increase Carry Capacity (Party Only)", + "increment": "increment", + "Inertia": "Inertia", + "Infinite Abilities": "Infinite Abilities", + "Infinite Abilities (No cooldowns no cost)": "Infinite Abilities (No cooldowns no cost)", + "Infinite Charges On Items": "Infinite Charges On Items", + "Infinite Spell Casts": "Infinite Spell Casts", + "Info": "Info", + "In-Game Name": "In-Game Name", + "Initiative": "Initiative", + "Initiative: Always Roll 1": "Initiative: Always Roll 1", + "Initiative: Always Roll 10": "Initiative: Always Roll 10", + "Initiative: Always Roll 5": "Initiative: Always Roll 5", + "Inspect": "Inspect", + "Inspect Dialog Controller": "Inspect Dialog Controller", + "Inspect Party <color=orange>(for modders)</color>": "Inspect Party <color=orange>(for modders)</color>", + "Inspect Quests and Objectives": "Inspect Quests and Objectives", + "Inspecting: ": "Inspecting: ", + "Instant Rest After Combat": "Instant Rest After Combat", + "Interesting NPCs in the local area": "Interesting NPCs in the local area", + "Internal Name": "Internal Name", + "Invert X Axis": "Invert X Axis", + "Invert Y Axis": "Invert Y Axis", + "Item": "Item", + "Jealousy Begone!": "Jealousy Begone!", + "Kasballica": "Kasballica", + "Keyboard:": "Keyboard:", + "Kill": "Kill", + "Kill All Enemies": "Kill All Enemies", + "Large": "Large", + "Lets you open up the area's mass loot screen to grab goodies whenever you want. Normally shown only when you exit the area": "Lets you open up the area's mass loot screen to grab goodies whenever you want. Normally shown only when you exit the area", + "level": "level", + "Level": "Level", + "Level Increase/Decrease": "Level Increase/Decrease", + "Level Up": "Level Up", + "Limit": "Limit", + "Lobotomize Enemies": "Lobotomize Enemies", + "Localization": "Localization", + "Lock": "Lock", + "Log Level": "Log Level", + "Log ToyBox Keyboard Commands In Game": "Log ToyBox Keyboard Commands In Game", + "Loot": "Loot", + "Loot Checklist": "Loot Checklist", + "Loot Rarity Coloring": "Loot Rarity Coloring", + "Loot Rarity Filtering": "Loot Rarity Filtering", + "Lose ": "Lose ", + "Main Character": "Main Character", + "Main/Alt Timescale": "Main/Alt Timescale", + "Make Character AI Controlled": "Make Character AI Controlled", + "Make Controllable": "Make Controllable", + "Make game continue to play music on lost focus": "Make game continue to play music on lost focus", + "Make Puzzle Symbols More Clear": "Make Puzzle Symbols More Clear", + "Make Spell/Ability/Item Pop-Ups Wider ": "Make Spell/Ability/Item Pop-Ups Wider ", + "Make tutorials not appear if disabled in settings": "Make tutorials not appear if disabled in settings", + "Makes mouse zoom work for the local map (cities, dungeons, etc). Game restart required if you turn it off": "Makes mouse zoom work for the local map (cities, dungeons, etc). Game restart required if you turn it off", + "Male": "Male", + "Mark Interesting NPCs": "Mark Interesting NPCs", + "Mark Interesting NPCs on Map": "Mark Interesting NPCs on Map", + "Mass Loot": "Mass Loot", + "Matches: ": "Matches: ", + "max": "max", + "Maximize the ModManager window for best ToyBox user experience": "Maximize the ModManager window for best ToyBox user experience", + "Maximize Window": "Maximize Window", + "Maximum Rarity To Hide:": "Maximum Rarity To Hide:", + "Mechadendrite": "Mechadendrite", + "Medium": "Medium", + "MilitaryRating": "MilitaryRating", + "min": "min", + "Minimum Rarity For Loot Rarity Tags/Colors": "Minimum Rarity For Loot Rarity Tags/Colors", + "Modify Summons For": "Modify Summons For", + "Money Earned": "Money Earned", + "Mono Version": "Mono Version", + "Morale": "Morale", + "Mouse:": "Mouse:", + "Movement Speed": "Movement Speed", + "N/A": "N/A", + "Name of the new Blueprintportrait: ": "Name of the new Blueprintportrait: ", + "Name of the new Custom Portrait: ": "Name of the new Custom Portrait: ", + "Nearby": "Nearby", + "Nearby Distance": "Nearby Distance", + "Neck": "Neck", + "Never Roll 1": "Never Roll 1", + "Never Roll 100": "Never Roll 100", + "No Active Dialog": "No Active Dialog", + "No attack/spell cooldowns": "No attack/spell cooldowns", + "No Etudes": "No Etudes", + "No Fog Of War": "No Fog Of War", + "No Friendly Fire On AOEs": "No Friendly Fire On AOEs", + "No Items": "No Items", + "No Loot Available": "No Loot Available", + "Non Combat: Take 1": "Non Combat: Take 1", + "None": "None", + "NonUsable": "NonUsable", + "Object Highlight Toggle Mode": "Object Highlight Toggle Mode", + "Object Highlight Toggle Mode (Out of Combat!)": "Object Highlight Toggle Mode (Out of Combat!)", + "Off": "Off", + "Ongoing Events:": "Ongoing Events:", + "Ongoing Projects:": "Ongoing Projects:", + "Only show languages with existing localization files": "Only show languages with existing localization files", + "Open Mass Loot Window": "Open Mass Loot Window", + "Open the Localization Guide": "Open the Localization Guide", + "Other": "Other", + "Other Multipliers": "Other Multipliers", + "Override AI Control Behaviour": "Override AI Control Behaviour", + "Override for Challenges": "Override for Challenges", + "Override for Combat": "Override for Combat", + "Override for Quests": "Override for Quests", + "Override for Skill Checks": "Override for Skill Checks", + "Override for Space Combat": "Override for Space Combat", + "Override for Traps": "Override for Traps", + "Page % of %": "Page % of %", + "Page: ": "Page: ", + "Parameter": "Parameter", + "Party": "Party", + "Party & Pets": "Party & Pets", + "Party Level ": "Party Level ", + "Perm": "Perm", + "Pets": "Pets", + "Pick none to stop overwriting.": "Pick none to stop overwriting.", + "Pick size modifier to overwrite default.": "Pick size modifier to overwrite default.", + "Pirates": "Pirates", + "Planets": "Planets", + "Play": "Play", + "Play Example": "Play Example", + "Prepared Spellslots": "Prepared Spellslots", + "Prevent Psychic Phenomena": "Prevent Psychic Phenomena", + "Prevent Traps from triggering": "Prevent Traps from triggering", + "Prevent Veil Thickness from changing": "Prevent Veil Thickness from changing", + "Preview": "Preview", + "Preview Results": "Preview Results", + "Previously Chosen Dialog Is Smaller ": "Previously Chosen Dialog Is Smaller ", + "Primary": "Primary", + "Progression": "Progression", + "PsyRating": "PsyRating", + "Quality of Life": "Quality of Life", + "QuestObj": "QuestObj", + "Quests": "Quests", + "Races": "Races", + "Raider_1x1": "Raider_1x1", + "Random Encounters": "Random Encounters", + "Randomize NPC Responses To Dialog Choices": "Randomize NPC Responses To Dialog Choices", + "Rank": "Rank", + "Rarity: ": "Rarity: ", + "Rating": "Rating", + "rating: ": "rating: ", + "Ratings": "Ratings", + "Reason": "Reason", + "Recruit": "Recruit", + "Refill consumables in belt slots if in inventory": "Refill consumables in belt slots if in inventory", + "Refresh": "Refresh", + "Remote": "Remote", + "Remove": "Remove", + "Remove Buffs": "Remove Buffs", + "Remove Deaths Door": "Remove Deaths Door", + "Reroll Perception": "Reroll Perception", + "Reset": "Reset", + "Reset Interactables": "Reset Interactables", + "Reset UI": "Reset UI", + "Resolve": "Resolve", + "Resource": "Resource", + "ResourceMiner": "ResourceMiner", + "Resources": "Resources", + "Respec": "Respec", + "Respec from Level 0": "Respec from Level 0", + "Rest All": "Rest All", + "Rest Selected": "Rest Selected", + "Restart": "Restart", + "Restore Spells & Skills After Combat": "Restore Spells & Skills After Combat", + "Ring": "Ring", + "Roll With Advantage (take lower roll)": "Roll With Advantage (take lower roll)", + "Roll With Avantage": "Roll With Avantage", + "Roll With Disavantage": "Roll With Disavantage", + "Roll With Disavantage (take higher roll)": "Roll With Disavantage (take higher roll)", + "Rotation Options": "Rotation Options", + "RT Specific": "RT Specific", + "Save as png": "Save as png", + "Save ID: ": "Save ID: ", + "SaveFortitude": "SaveFortitude", + "SaveReflex": "SaveReflex", + "Saves": "Saves", + "SaveWill": "SaveWill", + "Search": "Search", + "Search Descriptions": "Search Descriptions", + "Search Limit": "Search Limit", + "Search 'n Pick": "Search 'n Pick", + "Secondary": "Secondary", + "Sector Map Points": "Sector Map Points", + "Security": "Security", + "Selected Chars": "Selected Chars", + "Set": "Set", + "Set Veil Thickness to the following amount:": "Set Veil Thickness to the following amount:", + "Settings": "Settings", + "Shield": "Shield", + "Ship": "Ship", + "ShipVendor": "ShipVendor", + "Shirt": "Shirt", + "Shoulders": "Shoulders", + "Show": "Show", + "Show a list of NPCs that may have quest objectives or other interesting features <color=yellow>(Warning: Spoilers)</color>": "Show a list of NPCs that may have quest objectives or other interesting features <color=yellow>(Warning: Spoilers)</color>", + "Show Acronyms in Spell/Ability/Item Pop-Ups": "Show Acronyms in Spell/Ability/Item Pop-Ups", + "Show All": "Show All", + "Show Blueprint Portrait Picker": "Show Blueprint Portrait Picker", + "Show Blueprint Voice Picker": "Show Blueprint Voice Picker", + "Show Character filter choices": "Show Character filter choices", + "Show Comments (some in Russian)": "Show Comments (some in Russian)", + "Show Custom Portrait Picker": "Show Custom Portrait Picker", + "Show Display & Internal Names": "Show Display & Internal Names", + "Show Everything When Leaving Map": "Show Everything When Leaving Map", + "Show Friendly": "Show Friendly", + "Show GameID": "Show GameID", + "Show GUIDs": "Show GUIDs", + "Show Internal Names": "Show Internal Names", + "Show Rarity Tags": "Show Rarity Tags", + "Show reasons you can not equip an item in tooltips": "Show reasons you can not equip an item in tooltips", + "Show Tree": "Show Tree", + "Show Unavailable Responses": "Show Unavailable Responses", + "Show Unrevealed Steps": "Show Unrevealed Steps", + "Size": "Size", + "Skill Checks: Take 1": "Skill Checks: Take 1", + "Skill Checks: Take 25": "Skill Checks: Take 25", + "Skill Checks: Take 50": "Skill Checks: Take 50", + "SkillAthletics": "SkillAthletics", + "SkillAwareness": "SkillAwareness", + "SkillCarouse": "SkillCarouse", + "SkillCoercion": "SkillCoercion", + "SkillCommerce": "SkillCommerce", + "SkillDemolition": "SkillDemolition", + "SkillLogic": "SkillLogic", + "SkillLoreImperium": "SkillLoreImperium", + "SkillLoreWarp": "SkillLoreWarp", + "SkillLoreXenos": "SkillLoreXenos", + "SkillMedicae": "SkillMedicae", + "SkillPersuasion": "SkillPersuasion", + "SkillTechUse": "SkillTechUse", + "Small": "Small", + "Some items might be invisible until looted": "Some items might be invisible until looted", + "Some responses such as comments about your mythic powers will always choose the first one by default. This will show a copy of the answer and the condition for each possible response that an NPC might make to you based on": "Some responses such as comments about your mythic powers will always choose the first one by default. This will show a copy of the answer and the condition for each possible response that an NPC might make to you based on", + "Sort By Count": "Sort By Count", + "Soul Marks": "Soul Marks", + "source: ": "source: ", + "Spawn": "Spawn", + "Speed": "Speed", + "Speeds up or slows down the entire game (movement, animation, everything)": "Speeds up or slows down the entire game (movement, animation, everything)", + "Spellbooks": "Spellbooks", + "Spells": "Spells", + "Spontaneous Spells Per Day": "Spontaneous Spells Per Day", + "StarshipAmmo": "StarshipAmmo", + "StarshipArmorPlating": "StarshipArmorPlating", + "StarshipAugerArray": "StarshipAugerArray", + "StarshipBridge": "StarshipBridge", + "StarshipGellerFieldDevice": "StarshipGellerFieldDevice", + "StarshipItem": "StarshipItem", + "StarshipLifeSustainer": "StarshipLifeSustainer", + "StarshipPlasmaDrives": "StarshipPlasmaDrives", + "Starships": "Starships", + "StarshipVoidShieldGenerator": "StarshipVoidShieldGenerator", + "StarshipWarpDrives": "StarshipWarpDrives", + "StarshipWeapon": "StarshipWeapon", + "Start": "Start", + "Stats": "Stats", + "status: ": "status: ", + "Steal from living NPCs": "Steal from living NPCs", + "Strip HTML (colors) from Logs Tab in Unity Mod Manager": "Strip HTML (colors) from Logs Tab in Unity Mod Manager", + "Strip HTML (colors) from Native Console": "Strip HTML (colors) from Native Console", + "Summons": "Summons", + "System Map": "System Map", + "Teleport": "Teleport", + "Teleport Party To You": "Teleport Party To You", + "The following skill check adjustments apply only out of combat": "The following skill check adjustments apply only out of combat", + "This <b>incredibly dangerous</b> setting overrides the default behavior of failing to load saves depending on missing blueprint mods. This desperate action can potentially enable you to recover your saved game, though you'll have to respec at minimum.": "This <b>incredibly dangerous</b> setting overrides the default behavior of failing to load saves depending on missing blueprint mods. This desperate action can potentially enable you to recover your saved game, though you'll have to respec at minimum.", + "This allows characters you control to move through the selected category of units during combat": "This allows characters you control to move through the selected category of units during combat", + "This allows you to adjust pitch (Camera Tilt) by holding down Mouse3 (which previously just rotated)": "This allows you to adjust pitch (Camera Tilt) by holding down Mouse3 (which previously just rotated)", + "This also includes companions who left the party such as Wenduag if you picked Lann": "This also includes companions who left the party such as Wenduag if you picked Lann", + "This directly changes your character level but will not change exp or adjust any features associated with your character. To do a normal level up use +1 Lvl above. This gets recalculated when you reload the game. ": "This directly changes your character level but will not change exp or adjust any features associated with your character. To do a normal level up use +1 Lvl above. This gets recalculated when you reload the game. ", + "This hides map pins of loot containers containing at most the selected rarity. <color=orange>Note: Changing settings requires reopening the map.</color>": "This hides map pins of loot containers containing at most the selected rarity. <color=orange>Note: Changing settings requires reopening the map.</color>", + "This is intended for you to be able to enjoy the game while using mods that enhance your quality of life. Please be mindful of the player community and avoid using this mod to trivialize earning prestige achievements like Sadistic Gamer. The author is in discussion with Owlcat about reducing the scope of achievement blocking to just these. Let's show them that we as players can mod and cheat responsibly.": "This is intended for you to be able to enjoy the game while using mods that enhance your quality of life. Please be mindful of the player community and avoid using this mod to trivialize earning prestige achievements like Sadistic Gamer. The author is in discussion with Owlcat about reducing the scope of achievement blocking to just these. Let's show them that we as players can mod and cheat responsibly.", + "This is the current voice!": "This is the current voice!", + "This makes loot function like Diablo or Borderlands. <color=orange>Note: turning this off requires you to save and reload for it to take effect.</color>": "This makes loot function like Diablo or Borderlands. <color=orange>Note: turning this off requires you to save and reload for it to take effect.</color>", + "This resets all the skill check rolls for all interactable objects in the area": "This resets all the skill check rolls for all interactable objects in the area", + "This sets your experience to match the current value of character level": "This sets your experience to match the current value of character level", + "This turns on the developer console which lets you access cheat commands, shows a FPS window (hide with F11), etc.\n<b><color=yellow>Warning: </color></b><color=orange>You may need to restart the game for this to fully take effect</color>": "This turns on the developer console which lets you access cheat commands, shows a FPS window (hide with F11), etc.\n<b><color=yellow>Warning: </color></b><color=orange>You may need to restart the game for this to fully take effect</color>", + "This will change the color of NPC names on the highlight makers and change the color map markers to indicate that they have interesting or conditional interactions": "This will change the color of NPC names on the highlight makers and change the color map markers to indicate that they have interesting or conditional interactions", + "This will change the color of NPC names on the highlike makers and change the color map markers to indicate that they have interesting or conditional interactions": "This will change the color of NPC names on the highlike makers and change the color map markers to indicate that they have interesting or conditional interactions", + "Tiny": "Tiny", + "To permanently remove these modded blueprint dependencies, load the damaged saved game, change areas, and then save the game. You can then respec any characters that were impacted.": "To permanently remove these modded blueprint dependencies, load the damaged saved game, change areas, and then save the game. You can then respec any characters that were impacted.", + "ToyBox Archeologists can tag confusing puzzle pieces with green numbers in the game world and for inventory tool tips it will show text like this: <b><color=yellow>[PuzzlePiece Green3x1]</color></b><b><color=orange>\nNOTE: </color></b><color=orange>Needs game restart to take efect</color>": "ToyBox Archeologists can tag confusing puzzle pieces with green numbers in the game world and for inventory tool tips it will show text like this: <b><color=yellow>[PuzzlePiece Green3x1]</color></b><b><color=orange>\nNOTE: </color></b><color=orange>Needs game restart to take efect</color>", + "ToyBox can patch some critical bugs in Rogue Trader Beta, including the following:": "ToyBox can patch some critical bugs in Rogue Trader Beta, including the following:", + "ToyBox has limited functionality from the main menu": "ToyBox has limited functionality from the main menu", + "Trace": "Trace", + "Travel Speed": "Travel Speed", + "TurretRadius": "TurretRadius", + "TurretRating": "TurretRating", + "Tweaks": "Tweaks", + "Units": "Units", + "Units CR": "Units CR", + "Unlimited Actions During Turn": "Unlimited Actions During Turn", + "Unlimited Stacking of Modifiers (Stat/AC/Hit/Damage/Etc)": "Unlimited Stacking of Modifiers (Stat/AC/Hit/Damage/Etc)", + "Unlock": "Unlock", + "Unstart": "Unstart", + "Usable": "Usable", + "Vendor Buy Price": "Vendor Buy Price", + "Vendor Sell Price": "Vendor Sell Price", + "Visual Character Size Multiplier": "Visual Character Size Multiplier", + "WarhammerAgility": "WarhammerAgility", + "WarhammerBallisticSkill": "WarhammerBallisticSkill", + "WarhammerFellowship": "WarhammerFellowship", + "WarhammerInitialAPBlue": "WarhammerInitialAPBlue", + "WarhammerInitialAPYellow": "WarhammerInitialAPYellow", + "WarhammerIntelligence": "WarhammerIntelligence", + "WarhammerPerception": "WarhammerPerception", + "WarhammerStrength": "WarhammerStrength", + "WarhammerToughness": "WarhammerToughness", + "WarhammerWeaponSkill": "WarhammerWeaponSkill", + "WarhammerWillpower": "WarhammerWillpower", + "Warning": "Warning", + "Weapon": "Weapon", + "Weapons": "Weapons", + "When enabled and you hold down the camera follow key (usually f) the camera will keep following the unit until you release it": "When enabled and you hold down the camera follow key (usually f) the camera will keep following the unit until you release it", + "When loading a game this will go right into the game without having to 'Press any key to continue'": "When loading a game this will go right into the game without having to 'Press any key to continue'", + "When ticked this shows ToyBox commands in the combat log which is helpful for you to know when you used the shortcut": "When ticked this shows ToyBox commands in the combat log which is helpful for you to know when you used the shortcut", + "Whole Party": "Whole Party", + "Whole Team Moves Same Speed": "Whole Team Moves Same Speed", + "will be used for editing ": "will be used for editing ", + "Wrist": "Wrist", + "You": "You", + "You can enable hot keys to teleport members of your party to your mouse cursor on Area or the Global Map": "You can enable hot keys to teleport members of your party to your mouse cursor on Area or the Global Map", + "Body parts global offsets": "Global offsets of body parts", + "Body parts global scales": "Global scales of body parts", + "Body parts local sizes": "Local sizes of body parts", + "Equipment elements offsets": "Offsets of equipment elements", + "Equipment elements sizes": "Sizes of equipment elements", + "OF_positionZ": "Character Z-axis offset", + "OF_shouldersX": "Shoulder X-axis offset", + "OF_shouldersZ": "Shoulder Z-axis offset", + "OF_upper_armsX": "Upper arm X-axis offset", + "OF_upper_legsX": "Upper leg X-axis offset", + "SC_pelvisX": "Pelvis bone scale X-axis", + "SC_pelvisY": "Pelvis bone scale Y-axis", + "SC_pelvisZ": "Pelvis bone scale Z-axis", + "SC_neck": "Neck scale", + "SC_shoulders": "Shoulder scale", + "SC_upper_arms": "Upper arm scale", + "SC_fore_arms": "Forearm scale", + "SC_upper_torso": "Upper torso scale", + "SC_middle_torso": "Middle torso scale", + "SC_lower_torso": "Lower torso scale", + "SC_stomach": "Stomach scale", + "SC_upper_legs": "Upper leg scale", + "SC_lower_legs": "Lower leg scale", + "SC_foots": "Foot scale", + "SC_toes": "Toe scale", + "SZ_head": "Head size", + "SZ_neck": "Neck size", + "SZ_shoulders": "Shoulder size", + "SZ_upper_arms": "Upper arm size", + "SZ_fore_arms": "Forearm size", + "SZ_hands": "Hand size", + "SZ_upper_torso": "Upper torso size", + "SZ_middle_torso": "Middle torso size", + "SZ_lower_torso": "Lower torso size", + "SZ_stomach": "Stomach size", + "SZ_pelvis": "Pelvis size", + "SZ_upper_legs": "Upper leg size", + "SZ_middle_legs": "Middle leg size", + "SZ_lower_legs": "Lower leg size", + "SZ_foots": "Foot size", + "SZ_toes": "Toe size", + "IO_cloakX": "Only cloak X-axis offset", + "IO_cloakY": "Only cloak Y-axis offset", + "IO_cloakZ": "Only cloak Z-axis offset", + "IO_backpackX": "Backpack and cloak X-axis offset", + "IO_backpackY": "Backpack and cloak Y-axis offset", + "IO_backpackZ": "Backpack and cloak Z-axis offset", + "IO_weapon_in_holstersRX": "Right holster X-axis offset", + "IO_weapon_in_holstersRY": "Right holster Y-axis offset", + "IO_weapon_in_holstersRZ": "Right holster Z-axis offset", + "IO_weapon_in_holstersLX": "Left holster X-axis offset", + "IO_weapon_in_holstersLY": "Left holster Y-axis offset", + "IO_weapon_in_holstersLZ": "Left holster Z-axis offset", + "IS_cloak": "Only cloak size", + "IS_backpack": "Backpack and cloak size", + "IS_weapon_in_hand": "Weapon size in hands", + "IS_weapon_in_holsters": "Weapon size in holsters", + "IS_back_weapon_R": "Back weapon size right", + "IS_back_weapon_L": "Back weapon size left" + } } \ No newline at end of file diff --git a/ToyBox/Localization/etude-comments.txt b/ToyBox/Localization/etude-comments.txt deleted file mode 100644 index 0fac51afb..000000000 --- a/ToyBox/Localization/etude-comments.txt +++ /dev/null @@ -1,1579 +0,0 @@ -`Грейбор не в гильдии `:`Grabor's not in the guild ` -` Анлок локации DryCrossroads `:` DryCrossroads Anchor ` -` если читали LEXICON_Part2_name `:` if you have read LEXICON_Part2_name ` -` отключает маунтов и петов `:` disables junta and pelt ` -` включается через диалог - Epilogues_afterlogues_dialogue - - `:` enabled through conversation-Epilogues_afterlogues_diague- - ` -` - `:` - ` -` 30_BattlefieldVictory = 30_HeroicVictory - `:` 30__dattlefieldVictory = 30_HeroicVictory - ` -` Ачивка включается через механику этюда, а условие коплитется в этюде IvorySanctum_MainEtude `:` The upholstery is included through the mechanic, and the condition is being smoked in '' IvorySanctum_MainEtude ''. ` -` выдается в диалоге Kiny Freed `:` appears in the Kiny Freed dialog ` -` Ачивка включается через механику этюда `:` Actnox is included through study mechanic ` -` Ачивка выдается через механику ачивки `:` The nod is issued through the [ [ Almashko Mechanic]] ` -` AreeluAndChild - `:` AreeluAndChild - ` -` ChildDeath `:` ChildDeath ` -` Ачивка включается через этюды компаньонов - `:` The beers are included through the [ [ etudes]] of the [ [ Companions]] - ` -` Проверяtт боссов на комплит - `:` Check the bosses on the computer. - ` -` Ачивка срабатывает в механике ачивки, после перевода этюда в play `:` The beatdown works in the mechanical engineer, after the translation of the sketch to play ` -` После Иза. Конец 5 главы в Дрезене `:` After From. End of Chapter 5 in Dresen ` -` Аневия и Ирабет будут помогать в Трешхолде `:` Anevia and Irabet will assist in the Treshold ` -` Аневия расстроилась и ушла из крузэйда и от нас `:` Anevia was upset and left the circle and away from us. ` -` Сменили Азатистый мифик на другой `:` 'The Azatistas' have been replaced by another ` -` Отбирание Айву для Локуста и Дракона `:` Aisu's Retain for Locust and the Dragon ` -` Отбираем дракона когда игрок стал Легендой (выполнил квест) `:` Take the dragon when the player becomes Legend ` -` Пофейлили Азатистый мифик `:` The '' 'Azatista' ' ` -` Апгрейд финнеана первый (3 глава) `:` Appgreed Finnane first (3 chapters) ` -` Апгрейд финнеана второй (5 глава) `:` Appgreed Finnane Second (5 Chapter) ` -` Апгрейд финнеана второй (5 глава) Нет диалога + личевский инчант `:` Appgreed Finnane Second (5 Chapter) No Dialogue + Lychevsky Inchant ` -` Разбили череп, отпустив Финнеана. -Финнеан превратиля в реликвию. `:` We broke the skull, letting Finnahan go. Finnane turned into a relic. ` -` Разбили череп, закараптив Финнеана. -Финнеан превратиля в личевскую реликвию. `:` The skull was broken, and Finnean's carp. Finnane was turned into a lycheville relic. ` -` Убедили Финнеана в том, что он классный. Он остается с нами и получает ЛевелАп. `:` Convinced by Finney that he's cool. He stays with us and gets Levelap. ` -` Отдали Финнеана Оружейнику. `:` They gave it to Finnian Organger. ` -` Откладывает коронацию на одно посещение тронного зала, чтобы дать возможность случитсья отсечке 5 главы `:` Postponing a coronation for one trip to the throne room so that you can give the chance to the 5-chapter compartment. ` -` Стонтон Вейн `:` Stoneton Vane ` -` специальная коллекция мусора, которую мы удалим ПЕРЕД релизом `:` a special collection of garbage that we remove from the release of the release ` -` Мержит вендор таблицы из 1 и 2 главы в 3 `:` Table 1 and 2 chapters in 3 hold ` -` Мержит вендор таблицы в 5 главу `:` Hold a table vendor in 5 ` -` Тут еще кладем доп лут в стартовый сундук `:` There's a place where they put the pudt in the launch trunk. ` -` выбран ангел в финале пролога `:` selected an angel in the final of the prologue ` -` Отказались от арбалета Ариилу `:` Disappeared from Ariel's crossbow ` -` Взяли арбалет Ариилу `:` They took Ariel's crossbow ` -` выбран демон в финале пролога `:` selected daemon in prolog final ` -` Скрыли Мифик Ангела от Вождя Сула. С нами останется Вендуаг, а Ланн уйдет. `:` The Mifik Angel from Leader of the Soul. Wenduag will stay with us, and Leanne will leave. ` -` Показали Мифик Ангела вождю Суллу. С нами останется Ланн, а Вендуаг уйдет. `:` Show the Myfik Angel to Chief Sulla. Lanne will stay with us, and Wendduag will leave. ` -` У нас есть Рэдианс `:` We have a Redance. ` -` пикали возмездие в проложном букивента `:` retributing in a false book ` -` пикали спасение в проложном букивента `:` rescue in a false bookie ` -` Финальная катсцена главы в таверне с Голври `:` The final stage of the chapter in the tavern with Golvry ` -` Когда заработает этот этюд, должен случиться SE про нападение на DH `:` When this sketch comes up, it must happen to SE about the attack on the DH. ` -` Таймер до нападения на DH `:` Timer before the attack on DH ` -` Если согласились спасать DH `:` If we agreed to save the DH ` -` SE в котором нас встречает гонец и сообщает что надо двигать в Таверну т.к. её скоро атакуют. `:` The SE in which the messenger meets us and says we have to move to the Tavern, because it will soon be attacked. ` -` Первое посещение таверны `:` First visit to the tavern ` -` Этюд, который стратует через 1 день, после пролога. Показывает барко сценку где крестоносцы травят Стонтона. `:` Eutid, who's suffering from one day after the prologue. It shows the baro scene where the Crusaders weed Stonton. ` -` Поспавнить Аранку в Кенабрес-бёрнинг после встречи с ней в особняке Даэрана. `:` The Aranka flood in Kensabres-Burning after meeting her at the Daaran mansion. ` -` Построили мост в Kenabres Burning и открыли проход во вторую половину города `:` The bridge was built in Kenabres Burning and opened in the second half of the city ` -` Если спасли DH `:` If you save the DH ` -` Если отказались спасать DH или Проиграли `:` If they refused to rescue DH or Proplay ` -` Этюд, для включения звуков резни `:` Eutid, to include the sound of the massacres ` -` Player made a decision about wardstone `:` Player made a decision about wardstone ` -` Напряжённая осадная музыка + дополнительные звуки боя в фоне `:` Tension music + additional sounds of battle in the background ` -` Играем тему Мифика с разрушения Вардстоуна и до финального диалога с Минаго `:` We are playing the theme of the Mifique from the destruction of Vardstone and to the final dialogue with Minago ` -` Маги пылающего копья следуют за нами `:` The magic of the burning spear follows us. ` -` Отключает мозги Стонтона для дуэлей игрока `:` Disables Stonton's brains for a duel player. ` -` Отключает мозги компаньонов и Ирабет с отрядом для дуэлей игрока `:` Disables the brains of companions and Irabet with a duel player. ` -` Если спрашивали Илекса о Летре `:` If you asked Ileks about Lettre. ` -` Игрок может видеть печати Зонтира `:` The player can see the print of the [ [ Zontir]] ` -` Жрец-заговорщик, пытавшийся прокрасться к Страж-каменю, убит `:` The priest, who tried to sneak up to the Guardian, was killed ` -` Йоран делает ножны для Сияния. Флаг чекается в Цитадели Дрезен в С2 `:` Yöran makes the sheath for Siyani. Flag in Citadel Dresen at C2 ` -` Если посетили кенабрис бернинг до атаки на Таверну. (для кондишенов в диалогах) `:` If you visited the kenbras before the attack on the Tavern. (for confectionery in dialogs) ` -` Если игрок общался с Телдоном в Teldon_dialogue в 1 главе `:` If the player communicated with Teldon_dialogue in 1 chapter ` -` Сказали Харлану на Кенабрис бернинге, что ЖрецАдепт1 убит. (чтобы не видеть тот же самый ансвер в таверне) `:` Said Harlan to the Cannabs berning that JretzAdelp1 was killed. (not to see the same tower in the tavern) ` -` Вендуаг: "Я запомню эти слова, тварь. И я верну их тебе" `:` Wendduag: " I'll remember those words, bitch. And I'll give it back to you. " ` -` "Словно раздавленный жук". {n}Вендуаг кривится.{/n} "Идем отсюда, госпо{mf`:` "Like a crushed bug." { n } Wendages curve. { /n } " Let' s get out of here, state { mf` -`дин`:`Dine` -`жа}. Это жалкое существо не стоит даже плевка". `:`Well, I do. This pitiless creature is not worth even a spit. " ` -` Печати Зонтира в EstrodTower `:` EstrodTower seals ` -` освободили Ангелов из Вардстоуна (Аеон всё портит) `:` Angelov was released from Vardstone. ` -` Возможно тут должен быт ьЧун `:` Maybe this is the home of the Chun. ` -` Для проверки на старте 3 главы. Нужен для проверки успешности крузейда `:` To test at the start of Chapter 3. Needed to verify the success of the cruise ` -` Родительский этюд варкэмпа `:` Warkamp's Parental Ethica ` -` второе перемещение варкэмпа + триггер на запуск Gargoyle attack `:` second mover warkamp + trigger to start Gargoyle attack ` -` первый триггер на перемещение лагеря и эвент с Региллом `:` First trigger to move the camp and away with Regell ` -` Когда пошли с хелнайтами `:` When you went with the hellothi. ` -` Поддержка звуком и музыкой сюжетного этюда `:` Sound and music support ` -` основной этюд Лост Чапель -завершается в конце Anevia_Congratulations_Dialogue `:` at the end of Anevia_Congratulations_Dialogue ` -` завершется в конце диалога Boss_End_Dialogue `:` stop at the end of the Boss_End_Dialogue dialog ` -` Когда пошли с паладинами `:` When we went with the palanies. ` -` Внешний этюд. Который говорит, что на варкемп нападают гаргульки. `:` Outside sketch. Who says the wardrobe is being attacked by the wardrobe. ` -` `:` ` -` События осады Дрезена, которые происходят во всех вариантах `:` The events of the siege of Dresen, which take place in all options ` -` Озвучка пока нет осады `:` The call hasn't been under siege yet. ` -` Озвучка пока идет осада `:` The impostor is under siege. ` -` Раскрыт способ проникнуть в тюрьму через дом - `:` A way to infiltrate the prison through the house. - ` -` Катсцена с объявлением 5-го Крестового Похода. Голфри и все-все-все. `:` Cattstage with the announcement of the 5th Crusade. Golfrey and everything-everything. ` -` Сценка во время экскурсии - Сиила бухает и шуткует с новобранцами из Неросиана. `:` The scene during the tour-Siila is buzzing and jesting with the new recruits from the Nerossian. ` -` Труппа неудачливого театра, которых мы спасем в Кенабресе и которые потом потащатся за нами в поход. Они ставят представление про командора и будут раз в главу приходит спрашивать совета. Их надо воткнуть в варкамп и желательно так, чтобы игрок мимо них не прошел. Если игрок пропустил этот диалог в главе 2, они с ним же появятся в Дрезене. После диалога труппа всегда сваливает и больше не стоит в кэмпе/Дрезене - до следующего момента, когда им придет пора появляться. `:` The troupe of the unsuccessful theater that we will save in Kenabrese and then follow us camping. They give an idea of the commander, and they will come to the head to ask for advice. They need to be put into the barkamp and preferably so that the player does not pass. If the player has missed this dialog in Chapter 2, they will appear in Dresen. After the dialogue, the troupe always dumps and no longer is in the Camp/Dresen-until the next moment when it is time for them to appear. ` -` Таймер перед стартом Q2 Сосиэля `:` The timer before the start of the Sosiel's Q2 ` -` Механика столицы `:` The Mechanic of the Capital ` -` Дефолтный мирный стейт варкэмпа `:` warkamp ` -` Общий аудио-этюд мирного варкэмпа `:` General Audi-sketch of the peace warkamp ` -` Дефолтовый мирный зональный этюд командного шатра `:` A Defolt Peaceful Area from the Command Centre ` -` Хеллнайтовый кусок лагеря БЕЗ хеллнайтов `:` Hell Bell Camp ` -` Зональный этюд мирного дефолтового варкэмпа `:` The regional sketch of the peaceful warkamp ` -` Если победили армии перед Дрезеном - `:` If the Army is defeated in front of Dresen - ` -` Когда Голфри с нами во второй главе `:` When Golfrey is with us in chapter two. ` -` Ирабет не сражается в Дрезене. Но вообще она с нами `:` Irabet doesn't fight in Dresen. But she's with us. ` -` Активируется, когда игрок убивает опционального босса демона в таверне. `:` Activated when the player kills an optional demon boss in the tavern. ` -` напали на Закариуса в третьей главе и убили его `:` attacked Zacarius in the third chapter and killed him ` -` Дейран в зале, бухает с народом. Танцы `:` Deeran is in the audience, buzing with the people. Dance ` -` Дейран в спальне, народ бухает в зале `:` It's in the bedroom, the people are in the room. ` -` Подожгли бухло и активировали водяных элементалей `:` Bury the booze and activated the water elemental ` -` В диалоге с Дейраном ParkDistract_DaeranQ2_dialog узнали про водяных элементалей. `:` In a conversation with Deiran ParkDistract_DaeranQ2_dialog, we learned about the water elemental. ` -` Стартовый стейт. Дейран толкает речь и спускается вниз к народу. `:` The starter button. Deiran pushes the speech and goes down to the people. ` -` Дейран стоит в парке с народом `:` Deiran is standing in the park with the people. ` -` Дейран ушёл подышать воздухом. `:` Deeran went to get some air. ` -` Ждем пока игрок выйдет с зоны `:` We're waiting for the player to get out of the zone. ` -` слепой в гибберинг сварм выжил `:` The blind in a boil survived. ` -` При убеждении Ирабет использовали реинфорсед Айона `:` Irabet was used by the Reinformford Ayon ` -` При убеждении Ирабет использовали реинфорсед Ангела `:` In the persuasion of Irabet, the reinformathered Angel was used ` -` При убеждении Ирабет использовали реинфорсед Ангела Возмездия `:` The Irabet used the Reigenor Angel of the Renaissance ` -` При убеждении Ирабет использовали реинфорсед Ангела Спасения `:` The Irathor Angel of the Salvation was used ` -` При убеждении Ирабет использовали реинфорсед Демона `:` The Reinformford Demon was used by Irabet ` -` При убеждении Ирабет использовали дипломатию `:` In the belief, Irabet used diplomacy ` -` Нура под прекрытием в лагере. Всё хорошо. `:` Noora is in the camp. It's okay. ` -` Мы раскрыли Нуру `:` We've solved Noura. ` -` зональный этюд убивающий Нюру `:` zonal ethudes killing Nyuru ` -` Этюд про события, когда мы решили пойти с Нюрой. Включается в совете перед Дрезеном `:` It's about the events when we decided to go with Nyuro. Included in the board before Dresen ` -` VendorTableMagic `:` VendorTableMagic ` -` VendorTableArmor `:` VendorTableArmor ` -` VendorTableExotic `:` VendorTableExotic ` -` VendorTableRanged `:` VendorTableRanged ` -` Интро в Дрезене на старте 3 главы `:` Intra in Dresen on the start of 3 chapters ` -` Сайдквест с рыцарями негаснущего света (преквест к событиям лича) `:` Sidekvest with Disnefus knights ` -` Если пришли по реинфорседу+ `:` If they came for the reinformform + ` -` Если этот этюд работает, то в Фейне есть лаборатория Зантира(ТЕПЕРЬ УЖЕ ВСЕГДА У ВСЕХ ЕСТЬ ЛАБА) `:` If this sketch works, there's a lab in the Face of the Fair (YOU ALREADY ALREADY ALL ALL IS LABAR) ` -` Если игрок зафелит айона, нужно комплитить этот этюд `:` If the player makes the ione, you need to be compliant with this. ` -` Если пришли не по реинфорседу `:` If we're not here for the reinformeda. ` -` TempleOfDelamereAfter и TempleOfDelamereAfterLichReinforced - взаимоисключающие, должны стартовать после Санктума в зависимости от того есть флаг DelamereLeftInTomb или нет `:` TempleOfDelamereAfter and TempleOfDelamereAfterReinforced-mutually exclusive, must start after Sanktum, depending on the flag DelamereLeftInTomb or not ` -` протухнет со всеми детьи после 3-ей главы `:` will die with all the children after the 3rd chapter ` -` не протухающий зональный этюд Арилу `:` not Airyu's sobering sketch ` -` от попадания в AL, до получения мифика (анлока выхода из лабы) `:` from a hit in AL, before the receipt of the mitfik (an end of the labia) ` -` активен пока компаньона не вернули `:` is active until the partner is returned ` -` теперь можно открывать "разрывы" `:` can now open "breaks" ` -` Если колокол из арушалай редабта хоть раз привозили. Заменяется визуал башни для колокла `:` If the bell from the arushalal rarest was brought in at least once. Replaced by the tower for a bell tower ` -` Когда по Столице ходят дефолтные человечки `:` When the Capital goes out, there's a defaulting little man. ` -` Когда колокол еще не привозили `:` When the bell wasn't brought in yet ` -` Когда вардстон еще не привозили в дрезен `:` When the wardstone was not brought to the dresden yet ` -` Когда привезли вардстон в Дрезен `:` When the wardstone was brought to Dresen ` -` Сайдквест с рыцарями Пылающего копья `:` Saidquest with the Knights of the Burning Spear ` -` Убрали обычную массовку, заменили на личёвую `:` The usual crowd was taken, replaced by the massier. ` -` управление скелетом `:` skeleton management ` -` "Эвент стартует сразу после саммита, при этом игрок не может выйти из Дрезена, не увидев этого эвента" `:` "Event will start immediately after the summit, and the player cannot leave Dresen without seeing the euent." ` -` Старт ивента в крусейде где игроку выдается письмо и квест обж `:` Start of ivent in a circle where the player is given a letter and a quest ` -` Капитан, выдающий наемников `:` The Captain of the Mercenaries ` -` Нотификатор для LichRankUp_3.9 `:` Notifier for LichRankUp_3.9 ` -` Нотификатор для LichRankUp_4 `:` Notifier for LichRankUp_4 ` -` Нотификатор для LichRankUp_5 - `:` Notifier for LichRankUp_5 - ` -` Нотификатор для LichRankUp_6 - `:` Notifier for LichRankUp_6 - ` -` таймер перед LichRankUp_3.9 `:` timer before LichRankUp_3.9 ` -` таймер перед LichRankUp_4 - `:` timer before LichRankUp_4 - ` -` таймер перед LichRankUp_5 - `:` timer before LichRankUp_5 - ` -` таймер перед LichRankUp_6 - - `:` timer before LichRankUp_6 - - ` -` этюд/сцена для всяких потенциально заменяемых NPC `:` to/stage for all potentially replaceable NPC ` -` этюд-флаг, используется в рангапах Дипломаси 6, 7, 8, в ансвере про Неросиан. Обеспечивает, чтобы игрок мог тыкнуть в этот ансвер только 1 раз `:` This is a flag that is used in the rangapps of the Diplomashy 6, 7, 8, in the Ansere on the Nerosian. Ensures that the player can pin in this hangover only once ` -` Дефолтная толпа для аутдора Дрезена `:` Dresen's Defolate Crowd ` -` В Дрезене нет зиккурата и района азаты `:` There are no zikkurata and azaty districts in Dresen ` -` Нотификейшн у этого ивента не нужен! (https://jira.owlcat.local/browse/PF-164295) `:` There is no need for this ivent! (https: //jira.owlcat.local/browse/PF-164295) ` -` Комплитить после КТС с рыцарями немеркнущего света `:` Replicate after the CTS with the Knights of the Intent Light ` -` Киар пришел и стоит в Дрезене `:` The Keir came and stood in Dresen ` -` Киар спровоцирован Личём `:` The Kire was provoked by the Lachem ` -` Это Грейбор, которого не брали в партию и он пока еще NPC `:` It's the Greybor, who wasn't in the party, and he's still a NPC. ` -` Морвег пришел и стоит в Дрезене `:` Morverg came and was in Dresen. ` -` Морвег пропал `:` Morweg is missing. ` -` Со старта 3 главы запрещаем выход из столицы пока не проиграет КТЦ Винтерсана `:` Since the start of the chapter, the exit from the capital will not be lost to the Wintersan KTZ. ` -` КТС приезда короля-дурака `:` The King's Fool's Return ` -` Зося зовет смотреть картины `:` Zosia wants to see pictures. ` -` Зося приходит рвать романс со злым мификом `:` Zosia has come to tear the romance with the evil world. ` -` дварфийка зовет смотреть представление `:` The dwarwick wants to see the show. ` -` [Draft] Аневия с сообщением о смерти Тревера `:` [ Draft] Anevia with a message about the death of Traver ` -` Миаммир мертва. (Главный этюд флаг) `:` Miamir is dead. (Main sketch of the flag) ` -` Зональный этюд, чтобы похайдить юнита и положитьтруп с лутом `:` Zonal etude to haunt the Yukon and put the body with a lute ` -` Таймер до смерти миаммир. (Сиабр высасывает из нее душу) `:` Timer to death, Miamir. (Siabre's sucking the soul out of her) ` -` Отправили Миаммир на лечение (Главный этюд флаг) `:` Send Miamir for treatment (Main sketch of the flag) ` -` Отправили Миаммир на суд (Главный этюд флаг) `:` Send Miamir to trial (Main sketch) ` -` таймер перед 3м КТЦ Аруши `:` Timer before the 3rd CTAC Arusha ` -` таймер перед первым романтическим эвентом `:` timer before first romantic enting ` -` таймер перед квестом на редут `:` this timer on the red thread ` -` Бук Эвент от Аруши во время сна `:` Buk Event from Arusha during sleep ` -` Таймеры для событий, не относящимся к мификами или компаньонам `:` Timers for non-mythical events or companions ` -` Не дает компаньонским КТЦ 3 главы вываливаться пачками `:` Doesn't give KTPS three chapters to fall out of packs. ` -` Отсечки, стартующие стартующие ивенты не по времени, а по пройденному игроком контенту `:` The splits, the older, old-timey verdant is not the time, but the content driven by the player ` -` Бук Эвент где Хепзамирах саммонит игрока во время сна `:` Event where Hepzamiamah sammones a player while sleeping ` -` Камелия предлагает выпить и сходить на балкон `:` Camellia offers to drink and go to the balcony ` -` Камелия убежала навсегда / WIP `:` Camellia ran away forever/WIP ` -` CamelliaVSAeon `:` CamelaVSAeon ` -` Эвент про убийство в Дрезене `:` The Dresen murder ` -` Камелия пробует "обычное свидание" (финал) `:` Camellia tries a "normal date" ` -` Камелия пробует "обычное свидание" `:` Camellia tries a "normal date." ` -` Камелия зовёт домой к Хоргусу `:` Camellia's calling home to Horgusu. ` -` финальный эвент романса с Камелией `:` final event of romance with Camellia ` -` финальный эвент романса с Камелией -outside lock control `:` final event of romance with Camellia outside lock control ` -` таймер перед бонусным убийством CamelliaQ1_NewMurder `:` CamelliaQ1_NewMurder Timer ` -` таймер перед разговором с Аневией в Camellia Q1 -Запускается после статрта королевского эвента WaitForCrime `:` The timer before talking to the Anevia in Camellia Q1 Runs after the royal euent of WaitForCrime ` -` таймер перед стартом финального квеста Камелии -если есть романс, то тикает только после NormalDate эвентов `:` The timer before the start of the final quest of Camellia, if there is a romance, then ticks only after NormalDate of the heents ` -` таймер перед CamelliaVSAeon -после успешного завершения Q1_NobleIntent (189129cc7a4afa6418ef0c162598e935) `:` CamelliaVSAeon timer after successful completion of Q1_Noble Intent (189129cc7a4afa6418ef0162598e935) ` -` таймер перед королевским эвентом WaitForCrime `:` timer before the royal euent of WaitForCrime ` -` таймер перед первым романтическим эвентом Камелии -после АрилуЛаб и старта романса Камелии (первый квест + диалог с ней) - `:` Timer before the first romantic Eent of Camellia after Arilubab and the start of the Romance of Camellia (first quest + dialogue with her) - ` -` таймер перед побегом Камелии `:` Camellia's timer ` -` 5-ая глава -таймер перед продолжением второго романтического эвента Камелии ( ImNotNormal ) - - `:` The 5th is the head of the timer before continuing the second romantic euent Camellia (ImNotNormal) - - ` -` 5-ая глава -таймер перед вторым романтическим эвентом Камелии - -(проставить кондишен на главу потом) `:` The 5th is the head of the timer before the second romantic enting of Camellia. ` -` 5-ая глава -таймер перед третьим романтическим эвентом Камелии - -(проставить кондишен на главу потом) `:` The 5th is the head of the timer in front of the third romantic Eent of Camellia. ` -` Таймер приезда Голфри `:` The Timer of the Fry ` -` Таймер до появления крестоносцев Негаснущего пламени (Каир и Морвег) `:` Timer before the advent of the Crusaders (Cairo and Morweg) ` -` Таймер до появления крестоносцев Пылающего копья (Клейм ИЛИ Нистра) `:` Taimer until the Crusaders of the Burning Spear (Klame OR Nstra) ` -` Таймер перед КТЦ, в котором Ирабет снова верит в себя `:` The timer before the TCC, in which the Irawet believes in himself again ` -` Таймер до автоматической пропажи Морвега `:` Tymer until Morvega's automatic disappearance ` -` Таймер перед КТЦ где аневия приходит рассказать о играющем в карты зосе `:` The timer in front of the ETC where the anevia comes to tell about playing cards ` -` таймер для KTC_Malessa -начало 5-ой главы - `:` KTC_Malessa timer starts 5th head - ` -` таймер для KTC_Markyll `:` timer for KTC_Maryll ` -` Таймер перед помощью из Кенабреса, привозом вардстона и подобного. `:` A timer before aid from Canabrassa, mooring bardstone and similar. ` -` Таймер перед КТЦ где Зося приходит разрывать отношения `:` Timer in front of the TSC where Zosya comes to break the relationship ` -` Таймер перед КТЦ где Зося приходит разрывать отношения со злым мификом `:` Timer in front of the TSC where Zosya comes to break the relationship with evil ` -` Таймер перед КТЦ где Зося зовет эскизировать `:` The timer of the TSC where Zosia wants to eatize ` -` Таймер перед КТЦ где театр зовет на представление `:` The timer before the ETC where the theatre is calling for ` -` Таймер перед КТЦ где театр приходит с диалогом про представление `:` Timer in front of the ETC where the theatre comes with dialogue about the presentation ` -` Таймер перед КТЦ гдеаневия сообщает что Тревер убит `:` Tremer before the ETC of Gudeanevia reports that Traver was killed ` -` Романс c3 `:` Romance c3 ` -` таймер перед проповедями Эмбер в столице `:` Amber timer in front of Amber's sermons. ` -` таймер перед похищением Эмбер, стартуется после того, как игрок увидел проповеди Эмбер `:` The timer in front of Amber's abduction starts after the player sees the sermon, Amber ` -` таймер перед LannAndDate (романс) -после Q3 Ланна `:` timer before LannAndDate (novel) after Q3 Lanna ` -` таймер перед KTC LannCantDo - `:` timer before KTC LannCantDo - ` -` таймер перед KTC с первым квестом Ланна - `:` timer before KTC with first quest of Lanna - ` -` таймер перед LannWantsTraining (романс) `:` timer before LannWantsTraining (Romance) ` -` таймер перед KTC MamaDrezen - `:` timer before KTC MamaDrezen - ` -` Таймер перед: Квест начинается в Дрезене, Aeon_KTC_Irabeth_Ravon_dialogue - Ирабет приходит и рассказывает, что семья ноблей из Неросиана прислала письмо `:` Before: Quest begins in Dresen, Aeon_KCT_Irabeth_Ravon_dialogue-Irabet arrives and tells us that the family of noblies from Nerosiana sent a letter ` -` Таймер перед: Квест начинается в Дрезене KTC_AeonWintersunLady - двое варваров, тех же, что в Q2 приходят и говорят что дрезенские солдаты убили приезжего из гальта ни за что ни про что, и его труп лежит возле деревни. `:` The Kvast begins in Dresden KTC_AeonWintersunLady-two barbarians, the same as Q2 comes and says that Dresden soldiers have killed a carriageway of a carriageway of no way, and his corpse lies near the village. ` -` Таймер перед: Квест начинается в Дрезене. КТС - в кабинет командора приходит Аверис, женщина-солдат. Аверис рассказывает, что её сестра Силлин погибла при взятии Дрезена, а недавно Аверис увидела у Рэмли, мужика, которого считают мародёром, амулет погибшей сестры. `:` Timer before: Quest begins in Dresen. The CU is coming to the commander's office by Averis, a female soldier. Averys says that her sister, Sellin, was killed at the capture of Dresen, and recently Averis saw at the Ram, the man who is considered a marauder, the amulet of the dead sister. ` -` Таймер перед: Квест начинается в Дрезене. KTC - офицер Леллан приводит на суд рядового Горво `:` Timer before: Quest begins in Dresen. KTC-officer Lellan brings Private Gorvo to trial ` -` Таймер до прилета геральда к ангелу `:` A timer until the arrival of herald to the angel ` -` KTC про первый прихд Элянки. Вызывается по времени в механике столицы `:` KTC about the first wharfade of Elanky. Called by time in the mechanics of the capital ` -` KTC про звонок от столпа черепов про атаку на Зиккурат - `:` KTC about a pillar of turtle attacks on Zigkurat - ` -` 5-ая глава -KTC про предупреждение жрецов `:` 5th Head of KTC about the Warnings ` -` KTC про первый приход Септимуса. Вызывается по времени в механике столицы `:` KTC about the first parish of Septimus. Called by time in the mechanics of the capital ` -` KTC про звонок бунт нежити `:` KTC about the mutiny. ` -` KTC про звонок от Захариуса. Вызывается по времени в механике столицы -( он же Spiritual Links ) `:` KTC about the call from Zacharius. Called by time in the mechanics of the capital (it is the Spiritual Links) ` -` KTC про звонок от Захариуса. Вызывается по времени в механике столицы -Выдает королевский эвент про постройку Зиккурата `:` KTC about the call from Zacharius. Called by time in the mechanics of the capital, [ [ Royal Evening]] about the construction of Zieckurat ` -` таймер перед приходом Elyanka, стартует после того как нас посылают в АрилуЛаб по квесту Spiritual Links `:` The timer, before the arrival of Eilanka, starts after we are sent to [ [ Arilubab]] at [ [ Spiritual Links]] ` -` таймер перед LichRankUp_1 `:` timer before LichRankUp_1 ` -` таймер перед LichRankUp_2 `:` timer before LichRankUp_2 ` -` таймер перед LichRankUp_3 `:` timer before LichRankUp_3 ` -` 5-ая глава -таймер перед звонком от предупреждением жрецов `:` The 5th is the head of the timer before the call from the priests ` -` таймер перед приходом Septimus `:` timer before Septimus arrives ` -` таймер перед KTC_SkullPillarAboutUndeadRiot -Стартует после того, как будут сыгран KTC_SeptimusComing + завершен квест SkullPillar `:` KTC_SSkullPellarAboutUndeadRiot timer is starting after the KTC_SeptimusComing + completed the SkullPillar quest ` -` таймер перед KTC_ZachariusCallToRitual (стартует из финала квеста AbodeOfDeath) `:` KTC_ZachariusCallToRitual timer (starts at the end of the AbodeOfDeath quests) ` -` таймер перед звонком от Захариуса `:` timer before calling from Zacharius ` -` Вторая отсечка `:` Second drop ` -` Первая отсечка `:` First compartment ` -` Третья отсейчка `:` Third Diet ` -` таймер перед KTC с первым квестом Вендуаг - `:` timer before KTC with Wenduag's first quest - ` -` таймер перед KTC с эвентом из романса Вендуаг - `:` timer before KTC with the romance of Wenduag - ` -` таймер перед KTC с первым большим эвентом из романса Вендуаг -начинается после комплита Q1 Венду (и активном романсе) `:` The timer before the KTC with the first big euent from the novel Wenduag begins after Q1 Wendy (and the active romance) ` -` таймер перед тем как Вендуаг начнёт тренировки по ночам - `:` Timer before Wenduag begins training at night - ` -` командир Everbright Crusaders, приводит игроку подкрепу из Мендева в начале 3 главы `:` Everbright Crusaders commander, leads the player to Mendev at the beginning of 3 ` -` Игрок получает подкрепление в Армию под руководством рыцаря Киара `:` The player receives reinforcements into the Army under the direction of Knight Kiara ` -` Стартуем квест в Диалоге Киара, если Морвег пропал или мы его нашли сами в Винтерсане `:` Stardite quests in the Dialogue of Ciara, if Morweg is missing, or we found him in Wintersan ` -` Технический этюд, чтобы не завершать событие, пока не договорили с Геральдом `:` Technical etude not to complete the event until agreed with the Herald ` -` Не помогли Герольду в первый раз `:` They didn' t help Herold for the first time. ` -` До первого рога `:` To the first horn ` -` Финальное столкновение с Дарразандом `:` Darrahand's Final Clash ` -` Не помогли Герольду во второй раз `:` They didn' t help Herold for the second time. ` -` После первого рога и до второго `:` After the first horn and the second ` -` После второго рога `:` After the second horn ` -` Иллюзии оставлены улучшены (Люди видят Демонов как людей, людей как людей) `:` The illusions are improved (People see Demons as people, people as humans) ` -` События решений по винтерсану `:` Wintersan Solution Events ` -` Резчица мертва `:` The mustard is dead. ` -` Если игрок демон и убрал в винтерсане Иллюзии. (перенести потом в нужное место) `:` If the devil is a demon and put it in the intersane of the Illusion. (move to the right place later) ` -` Если подслушали разговор мужиков в Таверне `:` If you overheard the men talking in the Tavern. ` -` Иллюзии сняты (Люди видят Демонов как демонов, людей как людей) `:` The illusions have been removed (People see Demons as demons, people like humans) ` -` Мархевок убит `:` Marchewok killed ` -` Мархевока забрала Жерибет в Санктум `:` Marhewok took Zeriblet to Sankum. ` -` Мархевок остался вождем `:` Marheok remained chief. ` -` Получили ключ от санктума в Винтерсане `:` Got a key from the Sanktum in Wintersan. ` -` Драка с мобами на первой арене. После драки открываются двери в первыой части данжа `:` The darkness with the moss in the first arena. After the fight, doors are opened in the first part of the danja ` -` Корневой этюд Кохх данжа `:` Kohch's Roots ` -` Драка на второй арене. Старт фхов во второй части данжа `:` The stage of the second arenas. Start of fchs in the second part of the danj ` -` Игрок получил отмычку от Шва `:` The player received a jumper from Shwe ` -` Добавление диалога с побитым киборгов мед лабе `:` To add a dialogue with the beaten cyborgs of the honey ` -` Имморталити на нейтрального моба в хим лабе `:` Immortalichi on a neutral bowl in his hand ` -` Драка с вторым боссом данжа. Иммпорталити и старт диалога после его смерти `:` Drac with the second boss. Importalitti and launch of dialogue after his death ` -` Ciar - враг - `:` Ciar-Enemy - ` -` Ciar убит в зиккурате `:` Ciar killed in zikkurat ` -` Ciar победили в зиккурате и отпустили `:` Ciar win in zikkate and released ` -` Ciar сделали undead в зиккурате `:` Ciar made undead in a zigkurate ` -` отпустили Деламер (до пятой главы) -Если мы отпустили ее до 5 главы, то она нападет в 5 главе -Если мы отпустили ее после 5 главы, про нее будет эпилог `:` dismissed Delamer (up to the fifth chapter) -If we let her go before 5 chapters, she will attack 5. If we let her go after 5 chapters, she'll have an epilogue. ` -` отпустили Деламер (в пятой главе) -Если мы отпустили ее до 5 главы, то она нападет в 5 главе -Если мы отпустили ее после 5 главы, про нее будет эпилог `:` Delamer's release (in chapter five) -If we let her go before 5 chapters, she will attack 5. If we let her go after 5 chapters, she'll have an epilogue. ` -` Отпущенная Деламер стоит в Пулуре `:` Delamer's downside is standing in Pullouus. ` -` Деламер живет в зиккурате `:` Delamer lives in a zigkurat ` -` Деламер нас уважает `:` Delamer respects us. ` -` Опустить форс филд когда отключены 4 генератора `:` Omit field forfield when 4 generators are turned off ` -` Битва с ласт боссом `:` Battle of the Boss ` -` Поднимает форсфилд, через который нельзя пройти к боссу `:` Res a forsfield through which you can't go to the boss. ` -` Драка с мобами в кузне `:` The Doorak with the Mob in the Teach ` -` Драка с мобами в плавильне `:` The Drac with the Mob in the Plaid ` -` Трек отключения генератора `:` Gen Disablement ` -` https://confluence.owlcat.local/display/PF2/Hall+of+Fame `:` https://confluence.owlcat.local/display/PF2/Hall+of+Fame ` -` Флаг если получили код от столикого в финальном диалоге `:` Flag if received code from table in final dialogue ` -` Кестоглир живет в зиккурате `:` He lives in a zigkurat. ` -` Если попали в данж через персепшен, а не потому что локация ревильнулась через КТЦ `:` If they hit the dang through the peppers, not because the location was relocated through the TCC. ` -` Kyado может стать торговцем в Дрезене `:` Kyado may become a trader in Dresen ` -` Отключение ловушек (если не выключили руками) и стоп боя `:` Disabling traps (if not turned off) and stop fighting ` -` Этюд для проекта с киборгами `:` A Project with a Cyborg Project ` -` Нейтральные киборги `:` Neutral cyborgs ` -` Если Голфри оставила игрока в должности Коммандора после Фейна `:` If Golfrey left the player in the position of Commandor after Fain ` -` Уговорили Алфри называть щупальца - щупальцами `:` I told Alfrey to call tentacles. ` -` Staunton живет в зиккурате `:` Staunton lives in a zigkurat ` -` Флаг если сломали финальную машину `:` Flag if the final machine is broken ` -` Стоп катсцен для отключенных киборгов в подсобке `:` Stop trouscenes for disabled cyborgs in the back room ` -` вское дополнительное для AreeluLabReturn (5-ая глава) на основной локации (AreeluLab) `:` AreluLabReturn (5th chapter) in the main location (AreeluLab) ` -` Барк на Дверь и ее открытие `:` The door to the door and its opening ` -` Бэйкерский квест с яйцом дракона в 3 главе и продолжение его в 5 главе по бейкерсокму или квесту мифик дракона `:` Baker quest with dragon's egg in 3 chapter and continuation of it in the 5 chapter on beaker or quests of the dragon ` -` Драконий квест в 5 главе `:` Draconian quests in 5 ` -` Квест в 3 главе `:` Quest in 3 ` -` Открыть проход в драконью пещеру по квесту в 3 главе `:` Open a cave entrance to the dragon's cave in 3 chapter ` -` Фоллоу НПЦ Латимас `:` Latimas NFP Follow ` -` Выдать дракончика `:` Issue Draconian ` -` Файт с драконом. Отслеживание смерти одного из них `:` A dragon with a dragon. Monitoring the death of one of them ` -` Бафф на хокугола `:` Buff to the Khokola ` -` Старт катсцены - Латимас атакует колокол. Играется если сражаемся с Хокуголом `:` The scene is Latimas attacking the bell. Playing if we fight the Hokola ` -` Если убили медведя `:` If the Bear was killed ` -` Взяли письмо дочери шаманки `:` They took a letter from the shaman's daughter. ` -` Убили Гесмерху `:` Killed by Hesmerhu ` -` Гесмерха Жива. В лесу Новый защитник `:` Hesmerch Alive. In the Forest New Protector ` -` Гесмерха Жива. В лесу Старый защитник `:` Hesmerch Alive. In the woods, the Old Protector ` -` Если просрали армии во 2 главе `:` If there was an army in the 2nd. ` -` Если просрали армии в 3 главе `:` If there was an army in three, ` -` Если мы в Фейне спасли минотавра и он выжил. `:` If we were in Feynot to save the minotaur and he survived. ` -` Когда нас отправили в молтен скар, но мы его еще не нашли `:` When we were sent to the car, a skar, but we haven't found it yet. ` -` Убили Дракона в WW `:` The Dragon was killed in WW ` -` активно нападение Ciar на зиккурат `:` Actively attacked by the jiar on the zikkurat ` -` Зиккурат построен `:` Zieckurath buildings ` -` общий сэйт последнего нападения на Зиккурат `:` general seite of the latest attack on Zigkurat ` -` Труп романса в Зиккурате `:` The Trup of the Romance in Zigurat ` -` Труп романса в Зиккурате (таймер) `:` The Pieces of the Romance in Zigkurat (timer) ` -` работает только если мы перестали быть Личом `:` It only works if we stop being Leech. ` -` бунт нежити `:` mutinies of the Jews ` -` в Зиккурате кровавые скелеты `:` in the Sikkurat Blood skeletons ` -` в Зиккурате обычные скелеты `:` in Zigurate, conventional skeletons ` -` в Зиккурате скелеты Ургатоа `:` Zikkurat skeletons of Urgacoa ` -` 2-ая атака на Зиггурат `:` 2nd attack on Ziggurat ` -` Зиккурат проапгрейдили `:` Zikkurat propgradjaly ` -` выставляет Закариуса в Зиккурат `:` will present Zacarius in Zigkurat ` -` В Дрезене появляется зиккурат `:` Zikkurat appears in Dresen ` -` поапгрейдили столб `:` ramp of the pole ` -` построили столб черепов в Зиккурате `:` Buila skull stake in Zigurat ` -` Шов в шахтах и Арилу знает где он `:` The shafts in the mines and Arile know where he is. ` -` Вошли в бордель через портал верхнего города `:` It was in the brothel through the portal of the upper city ` -` Летит на корабле из дворца Нокты `:` He is flying a ship from the palace of Nokta ` -` Минаго и Чиварро живы и всё у них хорошос `:` Minago and Chivarro are alive and they're all good. ` -` Этюд, который выдает обжективы квеста ConquestOfalushinirra по набору определенного значения Renown - `:` [ [ ConquestOfalushirra]]'s quest for a set of '' Renown '' - ` -` Поговорили с Сарзаксисом `:` Taltalked to Sarzaxis. ` -` Миэлара 150k `:` Millar 150k ` -` Керз -110k `:` Kerz -110k ` -` Миэлара 100k `:` Millar 100k ` -` Керз - 80k `:` Kerz-80k ` -` Герольд Йомедай `:` Herold Yomeday ` -` ` `:` ` ` -` ProfaneGift выданный повторно ` `:` ProfaneGift from reissued ` ` -` Отображает какую концовку игрок получил в 3 кв Зосиеля `:` The player won 3 square meters in [ [ Zysiel]] ` -` Арена пройдена, Зеклекс стал за главного `:` Arena passed, Zekleks was in charge. ` -` Лифт стоит внизу `:` Elevator's downstairs. ` -` Лифт стоит наверху `:` The elevator's upstairs. ` -` Знаем о иллюзии в драконьем логове `:` We know about the illusion in the dragon's lair. ` -` Хепзамира мертва `:` Hepzamira's dead. ` -` Отпустили калавакуса-повара `:` A catapulacu-chefs have been released ` -` Убили дракона `:` Killed the Dragon ` -` Спасли-освободили рабов `:` They freed the slaves. ` -` суккубы в Арене `:` succubus in Arena ` -` мы сделали операцию демонграфта `:` We made the dismantling operation. ` -` гладиаторы в Арене `:` Gladiators in Arena ` -` гладиаторы в Нексусе `:` Gladiators in Nexus ` -` голарионские рабы в Арене `:` Goranos in Arena ` -` голарионские рабы в Нексусе `:` Gholarion's slaves in the Nexus ` -` Анхпйдит и спавнит мобов на арене `:` Anxiet and the Fauns in the Arena ` -` Unhide clean map objects `:` Unhide clean map objects ` -` затыкает Геральда/убирает все его интеррапты `:` Herales/cleans all his interferes. ` -` завершается стартом обжектива Obj1_ReturnToNexus `:` end with Obj1_ReturnToNexus ` -` стартует вместе с обжективом Obj1_ReturnToNexus `:` starts with Obj1_ReturnToNexus ` -` Разрушаем портальную арку после Колифира `:` Destroy the Arch after Colifir ` -` начинается в Нексусе, актуально до 3-его эвента `:` begins in Nexus, actually up to 3 ` -` стэйты романса -завершение этого этюда == fail романса `:` the romance of the completion of this sketch == ` -` KTC Вендуаг Q2 `:` KTC Wenduag Q2 ` -` Lann Q2 `:` Lann Q2 ` -` заменить амулет здесь и в Answer_0235 (8a39f16f1e6f6b947b14836bfd0362bd) `:` replace the amulet here and in Answer_0235 (8a39f161fo e6f6b947b14836bfd0362bd) ` -` стартуется любым из - -BattleBlissAS_Gladiators_Nexus, BattleBlissAS_GolarionSlaves_Nexus, Nexus_Aasimars, Nexus_AzataSlaves, Nexus_PleasureSlaves - `:` starts with any of the BattleBlissAS_Gladiators_Nexus, BattlelBlisAS_GolarionSlaves_Nexus, Nexus_Aasimars, Nexus_Azatavlaves, Nexus_PleasureSlaves - ` -` Геральд убежал и его поймали враги `:` The Herald ran away and was caught by the enemy ` -` портал в Алушинирру открыт `:` Portal at Alushinirra open ` -` Цепочка ангельских квестов и механик, которая не работает, если игрок ушёл с пути Ангела `:` A chain of angel quests and a mechanic who does not work if the player leaves the path of Angel ` -` Игрок стал Ангелом `:` The Player became Angel ` -` Выключает портал к кораблю Миэлары `:` Disables the portal to the ship, Milary. ` -` выдача Add4B_NoGladiators_TalkWithZeklex `:` issuing Add4B_NoGladiators_TalkWithZeklex ` -` Рваный `:` The Ransy ` -` наверное лишний этюд `:` Must be the extra one. ` -` Бафомета убила Ноктикула - `:` Baphomet killed Noctykul - ` -` Бафомета убил игрок `:` Baphomet killed the player ` -` Гладиаторы из Нексуса в Дрезене `:` Gladiators from Nexus in Dresen ` -` Рабы из Нексуса в Дрезене `:` Slazos from the Nexus in Dresen ` -` Нексус был в победном стэйте `:` Nexus was a poor man. ` -` Прокинули волю против profane gift'а в диалоге с Бафометом на Колифире `:` The will against the profane gift'in dialogue with Bafumet on Colifiire ` -` Игрок отдал лексикон Нокте `:` The player gave the Lexicon to Nota ` -` Группа этюдов, которые показывают, что мифик изменил свой визуал `:` A group of sketches that show that the world changed his visuals ` -` Добавить Аминасу трейд таблицу `:` Add Aminous Table ` -` Начальный стэйт `:` Initial article ` -` Корневой этюд запускает две механики `:` The root sketch starts two mechanics ` -` Призрачный стэйт `:` Ghost Stein ` -` Дефолт этюд Кенабрес Ребилдед `:` Dette Ethyud of Canabes Rebilia ` -` Убийство босса демона Глабрезу (2 фаза) `:` The assassination of the boss of the Glabyrese daemon (phase 2) ` -` Дефолтный стэйт `:` The Defolite state ` -` На коронации решил/обещал остаться, когда все закончится `:` On the coronation he decided to stay when it was over. ` -` На коронации решил/обещал уйти, когда все закончится `:` The coronation decided to leave when it was over. ` -` Срабатывает только после помощи и жениху, и невесте. `:` Only works after the bride and groom. ` -` Игрок принял предложение Арешкагаль стать ее Аватаром и получил MaskOfNothing `:` The player accepted the offer to become [ [ Avatar]] and received [ [ MaskOfNothing]] ` -` зональный AreeluLabReturn (5-ая глава) `:` area of AreeluLabReturn (5th chapter) ` -` Кнопки 1-4 символа `:` Buttons 1-4 characters ` -` Кнопки 5-8 символа `:` 5-8 character buttons ` -` зональный DemonicCommando `:` zone DemonicCommando ` -` Зональный этюд SE_MeetAnemora (Mythic Locust) `:` Zonal sketch SE_MeetAnemora (Mythic Locust) ` -` мы заходим в Трешхолд и 5-ая глава заканчивается `:` We go to the Treshold and the 5th chapter is running out. ` -` Мехника ConnectingElements `:` Mehnica ConnectingElements ` -` Заменяем диалоги/барки у отряда Голфри в случае успешной победы над монстром `:` Replaces the diology/barca of Golfray's squad in the event of a successful victory over the monster ` -` Лагерь разгромили, знамя унесли `:` The camp was trashed and the banner was taken away ` -` Чун в лагере у знамени `:` The Chung in the Camp by the Banner ` -` Дескари побежден любым из способов `:` Descary vanquished in any way ` -` Стейт "после" - вылез Дескари `:` State "after"-Deskari's coming out. ` -` дескари не сможет сбежать из боя пока не умрет `:` The paratroopers won't be able to escape the fight until they die. ` -` механика для убивания всех к кому игрок не пришёл и они умерли за кадром `:` the mechanics to kill everyone to whom the player didn' t come and they died by the frame. ` -` Ивент в одной из точек прошел, готов к переходу зоны в стейт "после" `:` Ivent in one of the points has passed, is ready to move of the zone to the "after" ` -` Голфри дралась сама без помощи игрока `:` Galfrey was in a fight without the help of the player ` -` Голфри пошла в храм манускриптов `:` Golfrey went to the temple of the manuscripts ` -` Ирабет пошла к логову Теренделев/Монстра `:` Irabet has gone to the den of Terendelev/Monster ` -` Халран в лагере у знамени `:` Halran at Camp by the Banner ` -` Anemora killed `:` Anemora killed ` -` Monster (Terendelev or Dragon) killed `:` Monster (Terendelv or Dragon) killed ` -` Запускаем диалог Иры/Голфри после смерти монстра `:` Launching the Ira/Golfrey dialogue after the death of the monster ` -` Голфри билась с помощью игрока `:` Golfrey fought with the help of the player ` -` Демоны забрали клинок доблести `:` Demons picked up the sword of valor. ` -` Дескари убит в Изе `:` The Deskari was killed in the From ` -` Ирабет пожертвовала собой спасая Голфри `:` The Gottfried has sacrificed himself to save Golfrey ` -` Ирабет умерла `:` Iran has died. ` -` Патруль из Иза ушел и вынес лейр марилитов `:` The patrol left and delivered the [ [ maryliolite]] leer ` -` Пройден букивент Verbovezzor `:` Proyeden Verbosezzor ` -` между насильственной смертью Арилу и закрытием МЯ ещё трепались в диалоге `:` between the violent death of Ariel and the closure of the MJ are still in the dialogue ` -` Арилу пожертвовали для открытия новых разрывов `:` Arila donated to open up new gaps ` -` он же DemonMythDesicion `:` It is a DemonMythDeicion ` -` не стал закрывать язву (без дополнительных решений) `:` did not close the ulcer (without additional solutions) ` -` закрыли МЯ пожетвовав PC `:` closed CMI by batting PC ` -` нищебродский финал с Нирваной `:` Nirvana Finals ` -` запускает Ending_Trickster_AllPlanes или Ending_Trickster_AllPlanesAndFW `:` runs Ending_Trickster_AllPlanes or Ending_Tickster_AllPlanesAndFW ` -` финал - "все планы" `:` Finals-All Plans ` -` финал - "все планы и Первый мир" `:` Finals-All Plans and First World ` -` чтобы меньше тормозило, вынес в ThresholdCamp_Scripts01..03 всякие огромные массивы с экшенами `:` to make it less slow, put it in ThresholdCamp_Scripts01.03, all huge amounts of ecstasy ` -` должен стартовать при выполнении проекта NorthernLights_Pulura `:` must start while running the Northern Lights_Pulura project ` -` всякие ноунэймы в лагере -(и баннер наверное сюда же) `:` all noonomes in the camp (and the banner is probably the same) ` -` нэймеды `:` Nedamed ` -` Mythic-specific NPC `:` Mythic-specific NPC ` -` механика финальной зоны `:` mechanics of the final zone ` -` Комплит значит фэйл романса `:` Comslab means the romance novel. ` -` Флаг для рангапов `:` Flag for ranking ` -` сон в 4-ой главе `:` fourth chapter ` -` ждём 2 дня до эрранда `:` Waiting for 2 days to errand ` -` Аневия говорит что Арушалай самоизолировалась в казематах (романс) `:` Anevia says that Arushalai is self-contained in casemates ` -` KTC, в котором Аруша просит нас посетить ее сны. `:` KTC, in which Arusha asks us to visit her dreams. ` -` Романс начат и не завершен или не зафейлен. - `:` Romance has been started and has not been completed or failed. - ` -` Мы успокоили Арушу после Арилабы `:` We calmed Arusha after Arilaba. ` -` после финала романса / в тронном зале (спальне) -т.к. юзается в Злой Аруше - живёт в стэйтах `:` after the final of the Romance/in the [ [ Throne Hall]] (the bedroom), as he lives in the [ [ Zilla]] ` -` Мы подсказали Аруше сделать в Алушинирре доброе дело `:` We have told Arusha to do a good job in Alushinirra. ` -` Мы защитили Арушу от знакомцев в борделе `:` We protected Arusha from the strangers in the brothel. ` -` Игрок был демоном, но заредимился `:` The player was a demon, but he was not ` -` романс в одном шаге от фэйла -(но ещё не зафэйлен) `:` Romance in one step from the Fale (but not yet sealed) ` -` Романс закончен `:` Romance finished. ` -` Романс закончен тру эндингом `:` Romance finished with three endings. ` -` Мы успокоили Арушу после Винтерсана `:` We have reassured Arusha after Wintersana. ` -` EX + вне активной партии и с отключённым мозгом (Nexus) `:` EX + outside the active lot and with the disconnected brain (Nexus) ` -` Это касается только событий второй главы, в третьей главе за рекрут отвечает другой этюд `:` This applies only to the events of the second chapter, and the third chapter is the responsibility of the other ` -` Если аруша померла посреди локации и улетела в дрезен `:` If the aroush died in the middle of the location and flew away to the drench ` -` Аневия ловит нас и заставляет расследовать всякое `:` Anavius catches us and forces us to investigate. ` -` Спавн Камелии и ее механики при атаке Гаргулий во 2 главе `:` Camellia and her mechanics at the Gargouli attack in Chapter 2 ` -` после финала романса / в тронном зале (спальне) `:` after the final of the Romance/Throne Room (Bedroom) ` -` Игрок трахнул Камелию во время NormalDate `:` The gambler fucked Camellia during NormalDate ` -` Игрок трахнул Камелию во время ее Ку1 `:` The player fucked Camellia during her Ku1. ` -` Не сошлись во взглядах, Камелия считает, что игрок ее не понимает `:` They did not agree, Camellia believes that the player does not understand ` -` Придумали тупую идею про эвент 1 `:` You thought a stupid idea about eent1. ` -` Камелия закончила романс в дефолтном руте `:` Camellia finished the romance in the default rut. ` -` Пытались заромансить в 1 главе `:` Tried to romancy in chapter 1. ` -` Поддержали ее мнение об Алушинирре, показали, что мы понимаем, что ей движет `:` Support her opinion of Alushinirre, showing that we understand that she is moving ` -` Если усердно трахаем ее второй раз на трупе `:` If she's banging her for a second time on a corpse. ` -` Камелия полностью уверена, что игрок хорошо ее понимает `:` Camellia fully believes that the player understands her well ` -` Игрок просил Камелию постараться быть адекватной, признавшись в любви `:` The player asked Kameli to try to be adequate by recognizing her love ` -` Придумали клевую идею где провести эвент 1 `:` You think about the cool idea where you could spend your time on eent1. ` -` Поговорили с ней про любовь в межэвентии 1-2 `:` We talked to her about love in the interventil 1-2. ` -` Правильно поговорили про наше общее будущее `:` You were right to talk about our common future. ` -` Камелия согласилась постараться вести себя хорошо `:` Camellia has agreed to try to be good. ` -` пререквизит для старта романса CamelliaRomance_Start `:` interrupt to start Romance's CamelliaRomance_Start ` -` старт романса `:` start of romance ` -` стэйт Камелии `:` Camelie's wall ` -` для трупа в подвале `:` For a dead body in a basement. ` -` EX + вне активной партии и с отключённым мозгом (LostChapel) `:` EX + outside the active party and with the disconnected brain (LostChapel) ` -` Камелия получила завещание Хоргуса `:` The Camellia received the will of the Horus ` -` стэйт Хоргуса `:` Khorus's stair ` -` Романс начат и не завершен или не зафейлен. -Комплит значит зафейлен `:` Romance has been started and has not been completed or failed. -It's a fae. ` -` Трупы лемонов в комнате командора `:` The Corpses of the lemons in the Room of the Commander ` -` Какой-то Серёгин этюд :) `:` Some Serogin sketch:) ` -` Командор чуть не спалил Барабашку `:` The Commander almost burned Barabashka. ` -` Дейран был в партии когда командора украли в Бэтлблисе. Используется для триггера диалога после Бэтлблисса `:` Deeran was in the party when the commander was stolen in Batlblis. Used for dialog trigger after BEthnic ` -` Видели CueSequence_0095 (8add2c2c1c2600a4aac4d525226902b7) `:` SeeSequence_0095 (8add2c2c1c2600a4aac4d525226902b7) ` -` Реакция игрока на Дейрана ангстящего из-за отправки в крестовый поход `:` The reaction of the player to Deeran is angstowing by sending to the Crusade ` -` Мы хотим жениться `:` We want to get married. ` -` Романс закончен см флагом Bitch `:` Romance is over with the Bitch flag ` -` Романс закончен см флагом Nice `:` Romance is over with the Nice flag ` -` В Дрезену дали Дейранк понять что рады его обществу и тому что он в кои-то веки не язвит `:` In Dresen, he was given a sense of the pleasure of his society and the fact that he had never been in ulcers for ever ` -` Дейран и купальни `:` Deiran and the baths ` -` Удачный ивент2 - "суммирует" предыдущие флаги `:` Removed ivent2-"sums up" previous flags ` -` Дейран волновался за командора `:` Deeran was worried about the commander. ` -` Командор носит амулет - подарок Дейрана `:` Commander carries a gift from Deeran ` -` Дейран намекает что может полюбить командора `:` Deeran implied that he could love the commodore. ` -` Командор нашел тайник с запиской `:` Commander found a stash of the note. ` -` Командор считает что у них есть будущее `:` The Commander thinks they have a future. ` -` Встретились с Голфри в Сердце Защитника `:` Met with Golfrey in the Protector's heart. ` -` Решили добивать Чужого `:` Decided to get Chaugo. ` -` Решили отпустить Чужого `:` They decided to let go of the Jojo. ` -` Простой бой с Чужим `:` A simple battle with the Zuzim ` -` Дейран сам убил инквизитора, не послушав игрока. `:` Deeran killed the Inquisitor himself without listening to the player. ` -` Убили Дейрана любым способом. `:` Killed Deeran in any way. ` -` Мы убили инквизитора Летра. У Дейрана все хорошо - он не лишен титула и вообще. `:` We killed the Inquisitor, Létre. Deeran's all right, he's not stripped of his title or anything. ` -` Отдали Дейрана на суд инквизиции `:` To the Court of Inquisition ` -` Сложный бой с Чужим `:` Hard fight with Zhuzm ` -` Спросили Ирабет про Дейрана `:` We asked for Irabet about the Dalan. ` -` Эмбер жива и с игроком. Является нашим полноценным компаньоном `:` Amber's alive and with a player. He is our full partner ` -` Эмбер жива но ушла от игрока по сюжету. `:` Amber's alive, but she left the story player. ` -` Эмбер мертва `:` Amber's dead. ` -` Эмбер жива, но игрок ее выгнал `:` Amber's alive, but the player kicked her out. ` -` культисты EmberQ1 сидят в тюрьме `:` [ [ EmberQ1]] ` -` культисты EmberQ1 сидят в тюрьме, Эмбер читает им проповедь -работает по выходным, днём и вечером `:` [ [ EmberQ1]] is in prison, and Amber reads his sermon on weekends, day and night ` -` культистка из КТЦ EmberQ1 на свободе `:` CST EmberQ1 ` -` культистка из КТЦ EmberQ1 сидит в тюрьме `:` CST EmberQ1 is in prison ` -` эвент в Алушинирре `:` Evening at Alushinirre ` -` зона Алтарь Бафомета, в пятой главе `:` Altar Baphomet, fifth chapter ` -` SE в пятой главе `:` SE in the fifth chapter ` -` активен Q1 стартуется по таймеру в столице -(зональный этюд алтаря - EmberWaywardBaphomentsAltar) `:` The Q1 starts on a timer in the capital (Zonal etudes-EmberWaywardBaphomentsAltar) ` -` KTC про похищение Эмбер. Вызывается по времени в механике столицы `:` KTC about Amber's abduction. Called by time in the mechanics of the capital ` -` активен Q1 / в столице `:` active Q1/in the capital ` -` плохая концовка -нельзя использовать в процессе выполнения квеста `:` Bad ending cannot be used during quests ` -` хорошая концовка -нельзя использовать в процессе выполнения квеста `:` Good ending cannot be used in the quest process ` -` нейтральная концовка -нельзя использовать в процессе выполнения квеста `:` Neutral endings may not be used in the quest process ` -` EX + вне активной партии и с отключённым мозгом (Alushinirra Lower City) `:` EX + outside the active party and with the Brain (Alushinirra Lower City) ` -` Стоп катсцен умирающего йозза `:` Stop the dying Yosse scene ` -` Грейбор убит игроком в 3 главе `:` Greybor was killed by a player in the third chapter ` -` Этот этюд стартует если мы убиваем грейбора и лутаем с него ключ от его комнаты, где лежит голда за нашу голову. `:` This sketch is going to starve if we kill the grapplet and get the key to his room from his room, where the head lies behind our head. ` -` Дракон улетел в ИвориСанктум `:` The Dragon has gone to Ivoriskum. ` -` Когда убили дракона в IvorySanctum `:` When the dragon was killed in the IvorySanctum ` -` Наняли Грейбора на постоянку (Дракон убит) `:` Hired Grayboron to the Apostle (Dragon Killed) ` -` Наняли Грейбора для поимки дракона `:` Hire Grabor to capture the dragon. ` -` Наняли Грейбора на постоянку с доплатой (Дракон убит) `:` Hired Graybora on a postcard with a dockboard (Dragon killed) ` -` Наняли Грейбора на постоянку (Дракон НЕ убит) `:` Nanning the Greybor on a Apostolic (Dragon has NOT been killed) ` -` Деньги грейбора за голову игрока `:` The Money of the Grow over the Player's Head ` -` Наняли Грейбора на постоянку с доплатой (Дракон НЕ убит) `:` Nanjali Greyboru on a dockboard with a dockboard (Dragon has NOT been killed) ` -` Когда у Грейбора урезанный компаньонский диалог на время охоты на дракона -Этот этюд надо комплитить в местах, где мы нанимаем Грейбора на постоянку `:` When Greibor has a reduced companion dialogue for the time of the dragon's hunt, This sketch needs to be in compliance with the places where we hire Greyboru. ` -` Когда у Грейбора нормальный компаньонский диалог `:` When Grábor is a normal companion ` -` События с засадой на дракона `:` Dragon-ambush events ` -` Когда Грейбор стоит в таверне как NPC - `:` When Greibor is standing in a tavern as a NPC - ` -` Удалить `:` Delete ` -` Выдать 4 обжектив (выследить дракона) закрываем этюд с SE Нападениями дракона `:` Issue 4: (track the dragon) close the sketch with the SE Attack of the Dragon ` -` КТЦ нападения ассасина в спальне игрока `:` Hassassin's Assassin attack in the player's bedroom ` -` Грейбор в гильдии и лоялен `:` Grabor in the Guild and Loyalist ` -` Таймер боя на 4 раунда. Работет через катчцену FightTimer, где этюд и комплитится `:` The battle timer for four rounds. Works through the FightTimer, where the sketch is ` -` Грейбор не в гильдии и предатель `:` Grabor is not in the guild and the traitor. ` -` рыцари из МолтенСкара у Ланна ИЛИ Вендуаг (только в диалоге) `:` Knights of MaltenScar at Lanne OR Wenduag (dialogue only) ` -` Ланн перед входом в Bad Luck `:` Lanne before entering Bad Luck ` -` Ланн пропал из Нексуса `:` Lannes is missing from the Nexus. ` -` Ланн essential `:` Lanne essential ` -` комплит этого этюда выключает всех его детей и этим фэйлит романс `:` a companion of this sketch turns off all his children and that flyful of romance ` -` KTC LannWantsTraining (романс) `:` KTC LannWantsTraining (Romance) ` -` Ланн новый вождь монгрельмэнов `:` The New Chief of the Montralmen ` -` Монгрелы в Дрезене -зональный `:` Mongrel in Dresden zonal ` -` Монгрелы в Дрезене после последнего квеста Ланна/Вендуаг `:` Mongrel in Dresen after last quest of Lanna/Wenduag ` -` убили Сулла `:` killed Sulla ` -` KTC LannAndDate (романс) `:` KTC LannAndDate (Romance) ` -` KTC_LannCanDo `:` KTC_LannCanDo ` -` KTC_LannComeNeathholm - `:` KTC_LannComeNeathholm - ` -` KTC_MamaDrezen `:` KTC_MamaDrezen ` -` Ланн приходит к Маме -вероятно не нужен и полностью дублируется LannMamaAfter (ef79fe4615b617c4fb166d271095b817) `:` Lannes probably don't need to see LannamaAfter (ef79fe4615b617c4fb166d271095b817) ` -` Романтический визит Ланна в Варкэмпе `:` Lanna's romantic visit to Warkamp ` -` стартует вместе к КТС этюдом `:` starts working together for the CTS study ` -` используется в квестах Ланна и Вендуаг `:` Used in the quests of Lanna and Wenduag. ` -` DateWithLann (романс) `:` DateWithLann ` -` Романс начат/активен `:` Romance Start/Active ` -` Если не был поднят LannShy или позволили Ланну открыть бутылку. `:` If you didn' t raise the LannShy or let Leanne open the bottle. ` -` Если не был поднят LannShy или попробовали пирожки Ланна. `:` If you didn' t pick up the Lannshy or tried Lanna's pie. ` -` Задоминировали Ланна в спарринге или Позвали Ланна драться в кабак - -(Ланн немного стесняется командора) `:` Lanna was dominated in the sparring, or called Lanna to fight in the cabak (Lann is a little bit shy of the commander) ` -` Ланн почувствовал себя уютно рядом с командором. `:` Lananne felt comfortable beside the commander. ` -` Сказал Ланну, что тот будет хорошим отцом. `:` Said to Lannu that he would be a good father. ` -` -Назвалась девушкой Ланна в письме матери -И/ИЛИ - -Ланн дарит свои чётки `:` -Lanna's girlfriend was named in her mother's letter, AND/OR Lannes gave her rosy ` -` Смерть ненио геймовер `:` The Death of the Nio Geimover ` -` EX + вне активной партии и с отключённым мозгом (Во время боя с арешкой если она ее контролирует) `:` EX + outside the active lot and with the disconnected brain (during the battle with the areal if it controls it) ` -` Прошли данж Арешки `:` It's been a moment. ` -` Этюд, который скрывает что Ненио лиса. Комплитится в Безымянных руинах, открывая фичи лисы и прочее. - `:` Eutid, who's hiding that Neenio is the fox. He was in the Unnamed ruins, opening the foxes of foxes and so on. - ` -` Убита игроком при первой встрече. (как NPC) `:` Killed by the player in the first meeting. (as NPC) ` -` Игрок был в руинах без лисы `:` The player was in the rubies without the fox ` -` Флаг в компаньонском диалоге `:` Flag of the Company Dialogue ` -` Поспавнить лису рядом с Арешкой `:` Visit the fox next to Areshkou ` -` Игрок не стал брать в партию Ненио при первой встрече и она ушла. (как NPC) `:` The player did not join the Nenio party at the first meeting and she left. (as NPC) ` -` Первый эксперемент Нению про экзамен культистам Бафомета `:` The first blunder of the Baphomet cultists ` -` Хеллнайты в лагере `:` Hellnats in Camp ` -` Добавляет Аминаса с диалогом на выходе из столицы в случае если Регилл убит на момент Ку2 `:` Add Aminas with dialogue at the exit of the capital in case Regill is killed at Ku2 ` -` Не взяли Регилла в партию и убили его в добавок `:` They didn' t pick up the record, and they killed him in addition to killing him. ` -` Первый визит Якера в варкэмп. -Стартует на данный момент экшном из GlobalmapBeforeSwarm `:` Yakera's first visit to the warkamp. -Starting at this moment in the GlobalmapBeforeSarm ` -` Взяли Регилла в партию на карте HellknightsUnderAttack (не в Лост Чапели!!!) `:` They took a Vellet into a game on the HellknowtsUnderAttack map (not in Leosta Chapley!!) ` -` Не взяли Регилла в партию на карте HellknightsUnderAttack (не в Лост Чапели!!!) `:` Do not take the Case in a party on the HellknistsUnderAttack card (not in Leost Chapley!!) ` -` Мы казнили Якера в KTC2_HellknightsYakerInWarCamp `:` We executed Yaker in KTC2_HellknersYakerInWarCamp. ` -` После старта 5 главы и до момента старта Q3 `:` After the start of the fifth chapter and before the start of Q3 ` -` Спавн Сиилы и ее механики при атаке Гаргулий во 2 главе `:` The Sailor and Mechanics of the Gargoyle attack in Chapter 2 ` -` Встретили друзей Сиилы на Кенабрис Бернинг и они уехали в таверну `:` Met Siila's friends at the Cannes Burning, and they went to the tavern. ` -` Узнать про тревера от Аминаса `:` Learn about Aminas trusts ` -` Узнать про тревера от Регилла `:` Find out about the trust from Regell. ` -` Рисующий Сосиэль в Варкэмпе 2 главы `:` Drawing Sosiel in Warkamp 2 Chapter ` -` Рисующий Сосиэль в Дрезене 3 главы `:` Drawing Sosiel in Dresen 3 chapters ` -` Таймер на КТЦ в Нексусе которое играется если игрок долго тупит и не начинает делать квест `:` The tycoon at the Nexus in the Nexus that plays if the player takes a long time to be stupid and does not start quests ` -` Сосиэль рисует жрецов `:` The Sosiel draws the priests ` -` Сосиэль рисует мертвецов `:` Sausel paints the dead ` -` Сосиэль рисует Арона Кира `:` Sosiel draws Aron Kira ` -` Сосиэль рисует жрецов после разговора на протяжении 1 дня `:` Sosiel draws the priests after a conversation for 1 day ` -` Сосиэль стоит рядом с мальбертом после разговора на протяжении 1 дня `:` Sosiel is standing next to the malbert after the conversation for 1 day ` -` Сосиэль стоит в госпитале 1 день `:` Sosiel is in hospital 1 day ` -` Сосиэль стоит на своём обычном месте 1 день `:` Sosiel is standing in his usual place 1 day ` -` Сосиэль катсценой идёт в госпиталь `:` Sosiel goes to the hospital ` -` Сосиэль рисует Аневию `:` Sosiel draws an Anevia ` -` Как выяснилось, по тексту она всё-таки не в платье. А жаль. `:` It turns out she's not wearing a dress. That's a shame. ` -` Сосиэль рисует Лост Чапель `:` Sostiel paints a Loost Chapel ` -` Сосиэль рисует Ирабет `:` Saosiel painting the Irabet ` -` Сосиэль рисует Лост Чапель после разговора на протяжении 1 дня `:` Sosiel draws the [ [ Loast Cup]] after a conversation for 1 day ` -` Сосиэль рисует Аневию после разговора на протяжении 1 дня `:` Sosiel draws Anavius after 1 day of conversation ` -` Сосиэль рисует Ирабет после разговора на протяжении 1 дня `:` Sosiel draws Iranbet after conversation for 1 day ` -` Висит пока мы не поговорим с Самозванцем первый раз. Оверрайдит диалог Сосиэля. `:` It's until we talk to the impostor for the first time. Overridet Sosiel's dialogue. ` -` КТЦ где игроку стартует диалог Dialogue_Sosiel_Q3_KTC и выдаёт Obj_00a_TalkBlackmask, поговорить с чёрной маской. `:` The ETC where the player starts the Dialoe_Sosiel_Q3_KTC dialog and gives Obj_00a_TalkBlackmask to talk to the black mask. ` -` Концовка квеста: Выход с Арене и запуск последнего диалога `:` Bride end: Exit Arena and launch of final dialogue ` -` Отслеживание смерти Зосиеля или Тревера в варианте Victim. `:` Track the death of Zoshyel or Traver in the Victim version. ` -` Старт катсцены если игрок выбрал вариант Betrayer в диалоге MeetTrever `:` Start of the stage if the player chose Betrayer in the MeetTrever dialog ` -` Драка с Тревером, если игрок выбрал напасть в диалоге MeetTrever `:` The battle with Traver, if the player chose to attack MeetTrever ` -` Встреча Зосиеля и Тревера на арене `:` Meet Zoshilya and Traver in the Arena ` -` Старт катсцены если игрок выбрал вариант Hero в диалоге MeetTrever `:` Start stage if player selects Hero in MeetTrever's dialog ` -` Старт катсцены если игрок выбрал вариант Victim в диалоге MeetTrever `:` Start stage if player chose Victim in MeetTrever's dialog ` -` Монгрелы Вендуаг в Дрезене -зональный `:` Wendduag Mongrel in Dresende zonal ` -` посрались с Венудаг во время ночной тренировки `:` I had a fight with Wenduck during the night training. ` -` после боя или после обвинения Вендуаг в убийстве / зональный `:` After the battle or after the Wanduan accusation of murder/zonal ` -` монгрелы ушли из столицы и крузэйда -also см. WenduagDrillFightAfter `:` the mongrel left the capital and the circle of also, see. WenduagallFightAfter ` -` Венду essential `:` Wendy essential ` -` отпустили / блочит романс с ней `:` let go/bleach the romance with her. ` -` корневой этюд романса - комплит фэйлит романс `:` The root sketch of the romance is the Romance of the Romance ` -` взяли в группу / блочит романс с ней -также, запускает WenduagCapitalTraitorPosition `:` in a group/bleach romance with it, launches WenduagCapitalTraitorPosition ` -` самоубийство Вендуаг в Return2Drezen `:` Wendouag suicide in Return2Drezen ` -` waiting 4 rest -Венду уходит, ждёт Waiting_WenduagInterlude03 `:` waiting 4 rest of Venda leaves, waiting for Waiting_WenduagInterlude03 ` -` waiting 4 rest -Венду возвращается `:` waiting 4 rest Wendy returns ` -` KTC_WenduagComeNeathholm `:` KTC_WenduagComeNeathholm ` -` WenduagRomance_CantUnderstand `:` WenduagRomance_CantUnderstand ` -` WenduagRomance_TroublesInTheTavern `:` WenduagRomance_Troubles ` -` стартует вместе к КТС этюдом (живёт в States т.к. по ходу квеста Вендуаг может временно отваливатся от нас) `:` is starting with the CTS Ethyude (living in States because of the quest of Wenduag may be temporarily detached from us) ` -` финальный романтический эвент `:` final romantic euent ` -` лочит флаг `:` bleach flag ` -` HavocInTheTavern (романс) `:` HavocInTheTavern ` -` второй романтический эвент, в Нексусе `:` The second romantic euenta, in the Nexus ` -` WenduRom_PersonalPunishment (романс) `:` WenduRom_PersonalPunishment (Romance) ` -` Романс закончен -(он же WenduRom_Default) `:` Romance is finished (it's WenduRom_Default) ` -` Романс закончен тру эндингом -(он же WenduRom_ItIsTrue) `:` The Romance has finished with the three endings (he is WenduRom_ItIsTrue) ` -` склонил Венду к попытке исправиться (в будущем) `:` inclined Wendy to try to change (in the future) ` -` утешил Вендуаг, когда она комплексовала, что ничего не понимает в жизни на поверхности `:` to console Wendduag, when she integrated her, that she didn' t understand anything in life on the surface ` -` дал Вендуаг помучать Кьядо `:` I gave Wenduag to Kyado. ` -` убедительно поговорил с Вендуаг во время пьянки в таверне в Алушинирре `:` I spoke to Wendduag during a drinking party at the Alushinirre. ` -` успокоил ревность Вендуаг касательно Веллексии `:` I calmed Wendduag's jealousy over Vallexia. ` -` вдохновил Вендуаг быть лидером племени `:` inspired Wenduag to be the leader of the tribe ` -` не лез Вендуаг под кожу с разговорами об отце, дал созреть и рассказать все самой `:` It wasn't the way Wendduag went into the skin with talking about his father, and he was ready to talk about it all by himself. ` -` утащил к себе Вендуаг для "личного наказания" после убийства в таверне и имел более откровенный разговор `:` He drags Wenduag for "personal punishment" after the murder in the tavern and had a more open conversation ` -` откровенно поговорил с Вендуаг после драки в Нексусе `:` I had a frank conversation with Wendduag after the fight in the Nexus. ` -` откровенно поговорил с Вендуаг после причащения ядом Савамелеха `:` I had a frank conversation with Wendduag after the tea-house of Sawameleh. ` -` Флаг из рангапов `:` Flag from rank ` -` Банда плутифлингов убита `:` The Utilla Banda killed ` -` Жрица Абадара `:` Priestess of Abadar ` -` Столица, когда Chun ушел в Из и еще не вернулся `:` The capital, when Chun left and did not return ` -` Столица, когда Циар ушел в Из и еще не вернулся `:` The capital, when the Cyar left and has not yet returned ` -` Актеры из театра, первый раз встречаем в 1 главе (Горящий кенабрис) `:` Actors from the theatre, the first time we meet in 1 chapter ` -` Полуэльф Культист писарь. Первый раз встречаем в 1 главе (Estrod Tower) `:` Semi-Elf Kultiste Episoar. First time we meet in 1 chapter (Estrod Tower) ` -` Эхо Дескари `:` Echo Dessari ` -` Герольда украл Бафомет и теперь его нет в Дрезене `:` The herald stole Baphomet and now it is not in Dresen ` -` Прелат Кенабриса, встречаем в первой главе на зоне Горящий Кенабрис `:` 'The first chapter in the Burning Kenbris' ` -` Столица, когда Харлан ушел в Из и еще не вернулся `:` The capital, when Harlan left and has not yet returned ` -` Когда ирабет пришлось уйти в Голфри из-за того, что игрок не командир `:` When the irabet had to go to Golfrey because the player is not a commander ` -` зональный `:` zonal ` -` Юный крестоносец из ордена Пылающего копья (крестоносцы маги, возглавляет их Миамир). -Первый раз втречаем в 1 главе (BlackWingRuins) и спасаем их от сожжения. `:` A young crusader from the Order of the Flaming Spear (the Mage Crusaders leads them). ` -` Хороший Зам Харалана, если не выбрали никого между Харланои и Ремьеном `:` Good Zam Haralan, if there is no one between Harlano and Remen ` -` Тифлинг-предатель из квеста Вольжифа `:` Tiffling is a traitor from the quest of Wolzhyf. ` -` Ноктикула мертва `:` The Nocticulus is dead. ` -` крестоносец из ордена Flamelance. Она - один из двух претендентов на командование орденом ввиду пропажи их предводителя Миамир `:` A crusader from the Order of Flamelance. She is one of the two candidates for command of the Order in view of the disappearance of their leader Miamir ` -` Аазимар, жрец Дезны, встречаем в первой главе на зоне Горящий Кенабрис `:` Aazimar, Priest of Desna, meet in the first chapter in the area of the burning kenbris ` -` Если убили Соану `:` If Soanu was killed, ` -` Сказитель вернулся в столицу в 3 главе. Через КТЦ или квест Грейбора `:` The mischief returned to the capital in 3. Through the TCC or the quest of Greybora ` -` Ангел, заточенный в Арилу лабе `:` An Angel Wrecked in Ariel Laba ` -` Чун мёртв `:` Chun is dead ` -` Чун - прелат Дрезена `:` Chung-prelat Dresen ` -` Циар мертв. Не важно по какой причине `:` The circus is dead. No matter why. ` -` Циар ушел и не вернётся. Не важно по какой причине `:` The circus is gone and he's not coming back. No matter why. ` -` "Вот видите, это никакой не демон! Здравствуйте, {mf`:` " You see, it's not a demon! Hello, { mf` -`добрый друг`:`good friend` -`добрая подруга} и спаситель{mf`:`a good friend } and the savior { mf` -``:`` -`ница}! Я — бабуля Гретлин, режиссер, а это — члены моей труппы". `:`nica }! I'm Granny Gretlin, the director, and these are the members of my troupe. " ` -` Когда Голфри приехала в Дрезен до посещения фейна `:` When Golfrey came to Dresen before she went to the Feyne ` -` Голфри мертва не важно каким способом. Если зомби, то тоже мертва `:` The Golfray is dead no matter what. If the zombie is dead, too. ` -` Galfrey освободит ангелов из Вардстоунов, после конца игры `:` Galfree will free the Angels from the Wardstones, after the end of the game ` -` механика в катсцене PlayerComesToGalfrey `:` mechanics in PlayerComesToGalfree ` -` Galfrey живет в зиккурате `:` Galfree lives in a zigkurat ` -` Голфри узнала про лексикон `:` Golfrey found out about the lexicon. ` -` Galfrey знает что в Страж-камне заключены души ангелов `:` Galfrey knows that the souls of angels are surrounded by the Guardians ` -` Irabeth_C3_Intro_Dialogue Cue_0028, Cue_0037- нужно пометить флагом что мы видели этот куй, проверяется в другом диалоге -GalfreyArrives_Drezen_c3_dialog Cue_0052 - нужно подключить! `:` Irabeth_C3_Intro_Dialogue Cue_0028, Cue_0037-you need to flag the flag that we have seen this cookie is checked in another GalfreyArrives_Drezen_c3_dialog Cue_0052-connect! ` -` Игрок сделал Голфри бабулей `:` The player made the Golfry Maw Maw ` -` Когда Голфри еще не приехала перед походом в фейн `:` When the Golfrey hasn't arrived yet before going to the Feyne ` -` Этюд, который закомплитится при выходе из локации башни сказителя `:` Eutid, who is in the process of coming out of the location of the narrator ` -` Хал дракон стоит в столице `:` Hal of the Dragon is in the capital ` -` Флаг - убили Хэла в 3 главе `:` Flag was killed by Hal in 3 ` -` Хал ушел в кенабрес по квесту. Хайд в столице `:` Halo went to the quests on the questa. Hyde in the Capital ` -` Когда прошли санктум и герольд улетел в Дрезен `:` When the Sanskum passed and the herald flew to Dresen ` -` Герольд не верит игроку `:` Herold does not trust the player ` -` Герольд удалился в изгнание `:` The Herold went into exile ` -` Убили Герольда в Лабиринте `:` Gerold was killed in the Labyrinth ` -` Герольд вернулся на небеса `:` Herold has returned to heaven ` -` Герольд верит игроку `:` Herald believes the player ` -` Таргона в городе может стоять только у Ангела `:` Targon in the city can only be in Angel's face. ` -` Халран - прелат Дрезена `:` Halran-Prelat Dresen ` -` Зафейлились в подбадривании Ирабет на старте 3 главы `:` It's been faked in Irabet's subrosion at the start of the chapter three. ` -` Ирабет ушла от нас `:` Irabet has left us. ` -` Игрок лично убил Ирабет `:` The player personally killed Irabet ` -` "На кого ты охотишься здесь, в Мендеве?" `:` "Who are you hunting here in Mendev?" ` -` "Я знаю, кто ты. Ты — Кайлесса, культистка Дескари". `:` " I know who you are. You're a kayless, a cult of Deskari. " ` -` "Что тебе от меня нужно, солдат?!" `:` "What do you want from me, soldier?" ` -` Мы отпустили Минаго по реинфорседу Азаты (MinaghoHappyEnd) `:` We released Minago from the Reine to Azat (MinaghoHappyEnd) ` -` Просто отпустили в C4 `:` Just let go to C4. ` -` Рамьен - прелат Дрезена -если закомпличен - то Рамьен был прелатом, но теперь уже нет. Дрезен без правителя. `:` Ramien-prelate Dresenis, if the need to be compliant, Ramien was a prelate, but now it's gone. Dresen without governor. ` -` Игрок знает что Арилу чувствует раскаяние из-за Шва `:` The player knows that Ariel feels remorse because of Shwe ` -` Шов отправлен в шахты `:` Shiv's sent to the mines. ` -` Шов освобожден из рабства `:` Shov was released from slavery ` -` Шов умер `:` Show died ` -` Шов стал мификом (и любит нас) `:` The Shah became a [ [ mythical]] (and loves us) ` -` Шов НЕ стал мификом и обижен на нас `:` Shaow has NOT become a mifique and resented at us ` -` Нашли страничку лексикона в башне дракона. (3 глава) `:` We found a lexicon page in the dragon tower. (3 chapter) ` -` рассказали Сказителю про Kiny / или наоборот `:` told the Kiny/or vice versa ` -` в диалоге уговорили его "принять предложение Фаразмы" `:` in the dialogue, persuaded him to "accept the Phasma proposal" ` -` в диалоге уговорили его "принять предложение Фаразмы", но умрёт он через несколько лет `:` in the dialogue, persuaded him to "accept the Phasma proposal," but he will die in a few years ` -` в диалоге уговорили его "отказаться от предложения Фаразмы" `:` In the dialogue, they persuaded him to "give up the Phasma proposal" ` -` Когда мы осудили Таргону Аэоном в Лабе и сожгли ей крылос `:` When we condemned Targon Aéon in Laba and burned down her wing ` -` Таргона с концами умерла в Mutasafen Lair `:` The Targon has died at the Mutaafen Lair ` -` У Таргоны нормальные ангельские крылья `:` The Targon has normal angel wings ` -` Когда освободили Таргону `:` When the Targon was released ` -` Когда мы напали на Таргону в Арилу лабе `:` When we attacked Targon in Ariel's lab ` -` В лабе Мутасафена дали Таргоне убиться `:` Muthasafen's lab has killed Targon ` -` В лабе Мутасафена не дали Таргоне убиться `:` Mutasafen's lab has not given the Targon to the murder. ` -` Теренделев была воскрешена и на находится в дрезене `:` Terendel was resurrected and was in a Dresen ` -` Спасли Яниэль в Фейне `:` Saved by Yaniel in Fein ` -` Убили Яниэль в Фейне `:` Killed Yaniel in Fein ` -` Выдает аддендум про разговор с зеркалом после трех судов `:` Issue a Addenum conversation with a mirror after three ships ` -` Тут все суды 5 главы `:` There are five chapters in the courts. ` -` Когда зафейлили путь айона -https://confluence.owlcat.local/pages/viewpage.action?spaceKey=PF2&title=Broken+Mythics `:` When the path of the ayon https://confluence.owlcat.local/pages/viewpage.action?spaceKey=PF2&title=Broken+Mythics ` -` Игрок сменил мифика на другого в 5 главе -https://confluence.owlcat.local/pages/viewpage.action?spaceKey=PF2&title=Broken+Mythics `:` The player has changed the mythic to another in the 5-chapter https://confluence.owlcat.local/pages/viewpage.action?spaceKey=PF2&title=Broken+Mythics ` -` Ноктикула осудила Зелекса на суде `:` Noctikus condemned Zelèks at trial. ` -` "Я не помню". `:` "I don't remember." ` -` Что отвечал игрок в первом диалоге - `:` What the player responded in the first dialogue - ` -` "Кто я — не твое дело". `:` "Who I am none of your business." ` -` "Спасибо за помощь". `:` "Thanks for your help." ` -` "Кто ты?" `:` "Who are you?" ` -` "Что со мной случилось?" `:` "What happened to me?" ` -` "Крестоносец. Я приш{mf`:` " The Crusaders. I am in { mf` -`ел`:`el` -`ла} воевать с демонами". `:`I will fight with demons. " ` -` "Странни{mf`:` " Stranno { mf` -`к`:`to` -`ца}. Я просто проходил{mf`:`c }. I was just passing { mf.` -``:`` -`а} мимо". `:`a } by ". ` -` "Я прибыл{mf`:` " I arrived { mf` -``:`` -`а} в город по делам". `:`a } to the city of business. " ` -` "Я давно живу в этом городе". `:` "I've lived in this town for a long time." ` -` "Я {name}". `:` "I { name }". ` -` "Ты действительно дракон?" `:` "Are you really a dragon?" ` -` Айон прогнал взяточников `:` He ran the bribe-takers. ` -` Когда чувак с неизвестным преступлением уехал после суда `:` When a dude with an unknown crime left after the trial. ` -` Айон прогнал лжецов `:` Aion ran the liars. ` -` Айон пронал мародеров `:` Aion of the Marauders ` -` Айон прогнал клятвопреступников `:` Aion sent the perps. ` -` Айон прогнал всех воров `:` Aion sent all the thieves out. ` -` Если в ГГ профукали айона `:` If the GG is a quayton. ` -` Если этот этюд работает, то в Фейне есть лаборатория Зантира `:` If this sketch works, there's a Zantira lab in Fae. ` -` Цена зданий уменьшена на 20% `:` The price of buildings has been reduced by 20% ` -` Первое здание дает в три раза больше юнитов в прирост `:` The first building is three times more units ` -` Армии имеют на 10 больше мув пойнтов, генералы имеют на 1 больше атаку, защиту и силу магии. `:` The army has ten more points of the point, the generals have 1 more attack, protection and magic. ` -` Солдаты получают иммун к страху, компулшену, конфужону. -Проект по призыву мифических существ: Водные элементали `:` The soldiers get an immun for fear, compumulsion, confusson. -A project on the conscription of mythical creatures: Water elenements ` -` Солдаты получают иммун к ядам, стуну и параличу. -Проект по призыву мифических существ: Огненные элементали `:` The soldiers receive the immun to the poisons, the snitch and the paralysis. -Project on the conscription of mythical creatures: Fire elements ` -` Каждый юнит в армии получает +1 АС за каждого союзного юнита рядом (в соседних клетках по вертикали и горизонтали). `:` Each young man in the army gets + 1 AU for each union unit adjacent (vertical and horizontal in the neighboring cells). ` -` Увеличивает приток всех ресурсов на 25% `:` Increases the flow of all resources by 25% ` -` Генерал в любой момент боя может дать всей армии джаджмент `:` The general can give the entire army a jezement at any time ` -` анлок найма водных и воздушных элементалей `:` Recruitment of water and air elemental ` -` анлок найма огненных и земляных элементалей `:` The recruitment of firemen and earth elements ` -` анлок найма кинетиков -какой-то баф, связанный с радиацией Язвы `:` kinetics of some kind of Yazza radiation ` -` Армии игрока становятся bane of demons `:` The player's army becomes bane of demars ` -` Армии игрока становятся bane of magic `:` The players ' armies become bane of magic ` -` дает эонов в найм `:` gives the eons an employment ` -` дает иневитаблов в найм `:` gives the inforvitable to the hire ` -` Этот этюд анлокает айона в механике `:` This is an ione anon in mechanics. ` -` Что мы сказали Стонтону в прошлом `:` What we said to Stonton in the past. ` -` Дьявол прибыл к айону `:` The devil has come to the ayon ` -` Если помиловали влюбленных `:` If the lovers have pardoned ` -` Если неправильно назвали Рэмли список из стэша `:` If they mislabeled Ram's list from the wall ` -` Осудили жрицу и ее больше нет в городе `:` Condemed the priestess, and she is no longer in the city ` -` Когда учеником Ариилу стал Мустафа и в фэйне его лаборатория `:` When Ariel was a student, Mustafa and his laboratory were in the [ [ Faine]] ` -` Игрок отменил смерть Теренделев `:` The player canceled the death of Terendell ` -` Когда сходили в прошлое и разделили Зантира. Потом сказали ему ихучать кристаллы и чтобы нас нашел в будущем `:` When I went to the past and divided the Zantyr. Then they told him to learn the crystals and to find us in the future ` -` Фай в таверне начал светиться `:` The fay in the tavern began to glow ` -` "Я твой друг, и я приш{mf`:` " I am your friend, and I am a { mf` -`ел`:`el` -`ла}, чтобы не дать тебе совершить ужасную ошибку". `:`l } to keep you from making a terrible mistake. " ` -` "Я т{mf`:` " I t { mf` -`от`:`from` -`а}, кому придется убить тебя, если ты не остановишься". `:`"Who will have to kill you if you don't stop." ` -` "Я — т{mf`:` " I am { mf` -`от`:`from` -`а}, кто возглавит Пятый крестовый поход после того, как сейчас по твоей вине провалится Второй". `:`and } who will lead the Fifth Crusade after you fail the Second. " ` -` "Я — эон. Голос разума. Я здесь, чтобы остановить это безумие". `:` " I'm an Eon. The voice of reason. I'm here to stop this madness. " ` -` Спрашивал ли игрок про Арушу при возвращении из прошлого `:` Whether or not the player asked about Arusha on his return from the past ` -` Спрашивал ли игрок про Грейбора при возвращении из прошлого `:` Whether or not the player asked about the Greybor back from the past ` -` Второй стейт столицы мифика -https://confluence.owlcat.local/pages/viewpage.action?pageId=8921190 `:` https://confluence.owlcat.local/pages/viewpage.action?pageId=8921190 ` -` Когда проанлочили Ариилу лаб и там появился рифт `:` When Ariel's anus was broken and there was a rift ` -` В 3 главе когда светится зеркало 2 первых суда `:` In the third chapter, when the mirror of two first vessels mirrors ` -` После двух первых судов `:` After the first two vessels ` -` С3 Аэон - Q10Орвенн Далмора против Дрезена `:` P-3 Aéon-Q10Orlando Dalmor v. Dresen ` -` С3 Аэон - Q11Дрезен против убийцы гальтского гостя `:` C3 Aéon-Q11Dresen v. the murderer of a galvanic guest ` -` С3 Аэон - Q3Дрезен против заговора молчания `:` C3 Aéon-Q3Dresen v. conspiracy of silence ` -` С3 Аэон - Q5Рядовой Аверис против рядового Рэмли `:` C3 Aéon-Q5Private Averis v. Private Ramley ` -` С3 Аэон - Q7Дрезен против поющей жрицы `:` C3 Aeon-Q7Dresen v. Singing ` -` С3 Аэон - Q9Рядовой Горво против армии Дрезена `:` P-3 Aéon-Q9, Gorvo v. the Dresen army ` -` Скриптзона перед зеркалом `:` CryptZone in front of the mirror ` -` Выключалка свечения зеркала `:` The mirror illumination of the mirror ` -` Диалог с зеркалом, если игрок еще не сделал реинфорс айона `:` Dialogue with the mirror if the player has not yet made a redevelopment of the ione ` -` Когда Фай решил торговать наркотой и попал в тюрьму `:` When Fai decided to sell drugs and go to prison ` -` Рифт в процессе `:` Rift in process ` -` Когда надо поговорить с зеркалом после двух судов второй волны `:` When we need to talk to the mirror after two vessels of the second wave ` -` Компаньонский диалог с зеркалом `:` The Dialogue with the Mirror ` -` Суд Шаксы `:` Shaksa's Court ` -` Суд Миелары `:` Court of Myelara ` -` Суд Зеклекса `:` The Zekleks Court ` -` Случился ли первый суд `:` Whether the first trial has happened ` -` На коронации выбрали "действия возмездия" `:` The coronation has chosen "action of retribution" ` -` На коронации выбрали "действие спасения" `:` The coronation has chosen a "rescue action" ` -` Сменили Ангельский мифик на другой `:` I've changed the Angel to another. ` -` Зафейлили ангельский мифик - `:` The World's Faith - ` -` Позволяет делать реинфорседы когда игрок может стать ангелом `:` Allows you to make reinformers when the player can become an angel ` -` Спрашивали Герольда про мутацию Таргоны `:` Asking Herold about the mutation of the Targon ` -` Взяли паларский проект `:` They've got the tent project. ` -` INACTIVE `:` INACTIVE ` -` Этот этюд фейлит мифик Ангела `:` This is a fake Angel's sketch. ` -` Если Эхо нападало на Ангела в SE Алушенирры `:` If Echo was attacking Angel in SE Alushenirra. ` -` Эхо Дескари похитил Элиандру `:` Echo Deskari kidnapped Elianry ` -` Эхо Дескари похитил Катайра `:` Echo Deskari abducted the Cataira ` -` Скупили весь общак `:` I bought the whole dorm. ` -` Сдавали лут в общак `:` Led in the dorm. ` -` Не сдавали лут в общак `:` They didn' t give up in the dorm. ` -` Выбрали "правильные" реплики во втором диалоге `:` Select "correct" replicas in the second dialog ` -` 02_StopMutasafen (2fe2f0d628cfb07458ee8e21b4f50b2c) - completed `:` 02_StopMutafen (2fe2f0d628cfb07458e8e21b4f50b2c)-completed ` -` Прилет Геральда к ангелу. Стартуется по таймеру `:` The Herald will come to the angel. Starting on a timer. ` -` Яниэль будет нам помогать с Минаго `:` Yaniel will help us with Minago. ` -` Позитивная мораль имеет х1.5 эффект на прирост рекрутов. `:` Positive morale has x1.5 effects on the growth of recruits. ` -` Позитивная мораль имеет двойной эффект на прирост ресурсов. `:` Positive morality has a double effect on resource growth. ` -` Позитивная мораль имеет двойной эффект на прирост финансов. `:` Positive morality has a double effect on the growth of finance. ` -` Генерал получает пассивную абилку дающую бонус +1 к АС и дополнительно +1 к АС за каждых союзников на поле боя (максимум +3) всем юнитам -Проект по призыву мифических существ: Мованик Дева `:` The general receives a passive abik giving bonus + 1 to the AU and an additional + 1 to the AU for every ally on the battlefield (maximum + 3) all units of the project on the conscription of mythical creatures: ` -` Позитивная мораль имеет двойной эффект на шанс прока второго хода в бою. -Проект по призыву мифических существ: Мованик Дева - `:` Positive morality has a double effect on the chance of a second move in battle. -Project on the conscription of mythical creatures: Movanik Deva - ` -` Появляется ритуал "Призвать небесное воинство", вызывающий ангелов на глобал мап. Появляется ритуал призыва ангелов в бою. `:` The ritual of "Call the Heavenly Host", which calls the angels on the global mop, appears. The call of angels in battle is a ritual. ` -` Появляется глобальный ритуал Storm of Justice, наносящий каждому отряду целевой армии (d6 damage formula) если цель обычная, d8 если злая, d10 если демон или нежить, d12 если высший демон, злой дракон или супернежить (дракон скелет или лич). -Появляется боевой ритуал Bolt of Justice, наносящий такой же урон, но в одну цель. - `:` The Storm of Justice is a global ritual that causes every target army (d6 damage formula) if the target is ordinary, d8 if it is a mean, d10 if the demon or not to live, d12 if the supreme demon, the evil dragon, or the supernatural (dragon skeleton or lime). - ` -` Дает боевые ритуалы Blade of the Sun (наносит холи d8 damage formula урон по линии через все поле боя) -Все инфантри и кавалерия имеют Smite Evil применемый один раз за бой. `:` Provides combat rituals of the Blade of the Sun (deals the d8 damage formula damage through the entire battlefield) -All the infantrs and cavalry have a Smite Evil in a fight. ` -` +15% к размеру госпиталя `:` + 15 per cent of the hospital ` -` Дает боевые ритуалы Ward from Weakness (дает цели иммунитет к негативным воздействиям) -Все стрелки имеют Smite Evil применемый один раз за бой. `:` gives combat rituals Ward from Weakness -All the arrows have a Smit Evil, once for a fight. ` -` Сурово карать. Они - враги - -Появляется глобальный ритуал Bestow Wrath. Выбранная союзная армия получает бафф, в этот день в начале боя враги получат заряд урона 4d6 мифик лвл `:` A harsh punishment. They are enemies of the global ritual of Bestow Wrath. The [ [ allied army]] is receiving the [ [ Baff]], the day at the beginning of the battle, the enemy will be charged with a 4d6 [ [ blubl]] loss ` -` Спасать и прощать. Мы бьемся за жизни смертных - -Появляется глобальный ритуал Bestow Protection. Бафф, дающий каждому юниту в армии буфер на (d10 *мифик лвл) хп на следующий бой сегодня. `:` To save and to forgive. We are fighting for the life of the mortal Pois is the global ritual of Bestow Protection. Buff, which gives every juniper in the army a buffer on (d10 * world) hp to the next fight tonight. ` -` Всех казнить без разбору `:` All executions without distinction ` -` Монадик девы - милишники с огенными мечами и баффами клериков. `:` Monks of the Mir-Minsters with swords and swords of clerics. ` -` Астрал девы - средние милишники (но очень крутые против нежити - молот убивает часть нежити с прока), имеют хил и блейд барьер в заклинаниях. `:` The Astral of the Minster-the average mylichans (but very steep against the nolita-the hammer kills part of the gap), has a chil and a blade barrier in spells. ` -` Только пришли, инициация зоны `:` Only the Zone Initiation arrived. ` -` Сайдовые квесты `:` Cod quests ` -` Мейн квест `:` Mayne Bride ` -` Финал мейн квеста `:` Family Quest Final ` -` Ритуал `:` Ritual ` -` Тревога после ритуала `:` Alarm after the Ritual ` -` Идем бить культистов `:` Let' s hit the cult. ` -` Надо поговорить с культистом `:` We need to talk to the cult. ` -` Лидер культистов творит фигню `:` The leader of the cult artists does the thing. ` -` Этот этюд анлокает азату в механике `:` This sketch is for the asatu in mechanics. ` -` Азата принемает контракт Мефисто и убивает всех своих Вольных крестоносцев `:` Azata is forced to accept Mephisto's contract and kill all his Crusaders ` -` Азата убивает всех своих вольных крестоносцев, потому что Мефисто его обманул `:` Azata is killing all his crusaders because Mephisto deceived him ` -` Азата дерется с Мефисто, все счастливы `:` Azat fights with Mephisto, everyone is happy. ` -` Игрок разоблачил наставника в квесте с4 азаты `:` The player unmasked a mentor in the quests of the four azaths ` -` Мы хорошая правильная азата `:` We're a good right azut. ` -` "Хаос победил" `:` "Chaos has won." ` -` выключается после начала осады Дрезена `:` shuts down after the onset of the siege of Dresen ` -` Игрок отказался менять Алаймент на нужный. Фейл всей цепочки. `:` The player refused to change the Alinement to the right one. The entire chain. ` -` "Дьявол победил" `:` The Devil Wins. ` -` Тихоня погиб в Фейне `:` Tikhon died in Fein ` -` Тестовый Этюд для азаты С5 `:` Test Eutid for the P5 ` -` В Дрезене появляется район Азаты `:` Azaty's area appears in Drezene ` -` В Midnight Fane нам помагают бандиты. -Стартует в RobinHoodArrives_azata_c3_dialog - в ансверах 9 и 10 `:` In the Midnight Fane, we're a gangster. -Starituth in RobinHoodArrives_azata_c3_dialog-in ancores 9 and 10 ` -` В Midnight Fane нам помогают дети. -Выдается в ChildCrusadersArrive_azata_c3_dialog - в ансвере 5 `:` We have kids in Midnight Fane. -Issuing in the ChildCrusadersArrive_azata_c3_dialog-in ansere 5 ` -` Уговорили вольных крестоносцев отступить `:` The freestall crusaders retreated. ` -` В Midnight Fane нам помагают мимики. -Выдается в MimicsArrive_azata_c3_dialog - в ансвенре 5 `:` In the Midnight Fane, we're a little gay. -Embodied in MimicsArrive_azata_c3_dialog-in ansire 5 ` -` Вольные крестоносцы ломанулись вперед `:` The crusaders loited forward ` -` В Midnight Fane нам помагают треанты. -Выдается в TrentsArrive_azata_c3_dialog - арсверы 11, 12, 25 `:` In the Midnight Fane, we're all in awe. -Issuers to TrentsArrive_azata_c3_dialog-arsverts 11, 12, 25 ` -` Принять Робин Гуда и разбойников в крестоносцы `:` Take Robin Hood and the Robbers to the Crusaders ` -` Принять детей в полноценные крестоносцы `:` To take children into full crusaders ` -` Сказать трентам отпустить быстродрева на волю `:` Tell the trents to let go of the fast. ` -` Сдать Робин Гуда хеллнайтам `:` Robin Hood of hellnatus ` -` Отослать Робин Гуда `:` [ [ Robin Hood]] ` -` Отослать детей `:` Send children ` -` Отругать валхалфлингов `:` Scuss of the Walkhalflings ` -` Выгнать мимиков `:` Eject Mimics ` -` Взять в заложники рощи трентов `:` Take the groves of the trenes hostage. ` -` Выгнать трентов `:` Eject the Trents ` -` Тераформинг Молтенскара `:` Moltenscara bombing ` -` Включенный этюд запускает функционал острова летающего на ГМ. `:` The included sketch launches the functionality of the island flying to the GM. ` -` Новая абилка для генерала - Inspired Advance (дает всем юнитам +2 movement speed и абилку увеличивающую урон за каждую пройденную клетку) `:` The General's new [ [ ibilka]] is an Advance Advance (which gives all the units + 2 movement speed and a [ [ billboard]]) ` -` Дает песню - Song of Seasons (каждый юнит лечит своих и дамажит врагов в конце хода вокруг себя). `:` Song of Seasons (every young man cures his or her enemies at the end of the day). ` -` Новая абилка для генерала - Song of the People (все юниты получают фланкинг бонус +4 к атаке и урону) `:` The new general is the "Song of the People" (all units receive a flanking bonus + 4 to attack and damage) ` -` Искать союзников - -Теперь можно нанимать как наемников Халфлингов из беллфлауера, бывших рабов. Они роги со слингстаффами.. `:` The search for Allies can now be hired as Halfling mercenaries from the Bellevauer, former slaves. They're antlers with shingles. ` -` Проводить разведку - -Армии теперь ходят в полтора раза дальше, а юниты на 1 клетку дальше. `:` Intelligence of the Army now goes one and a half times further, and the units are 1 cell further. ` -` Воевать - -Теперь можно нанимать как наемников жрецов Десны. Они клерики, которые умеют очень хорошо диспеллить негативные эффекты. `:` The war can now be hired as a mercenary of the gums of Desna. They're clerics who can very well dispel negative effects. ` -` Каких звать Ореадов - -Ореадов-монахов - средней мощности, с ентанглами и throrn body. - `:` What are the names of Oreadov's monks-monks-average strength, with the entrants and the thorn body. - ` -` Каких звать Ореадов - -Ореадов-воинов - очень мощные, с очень болезненной атакой. - `:` The names of Oreadov's warriors are very powerful, with a very painful attack. - ` -` Каких звать Ореадов - -Ореадов-жрецов - средней мощности, с баффами и лечением. `:` What are the names of Oreadov's average power, the baffees, and the treatment? ` -` Кого позвать из Элизиума на помощь? - -Лилиенд - барды, они умеют петь Inspire Courage, лечить, парализовать врагов, кастить Sound Burst. `:` Who is going to get help from Elysium? - -They can sing Inspire Courage, treat, paralyze enemies, sown Sound Burst. ` -` Кого позвать из Элизиума на помощь? - -Бралани азаты - крутые стрелки которые еще могут баффать и кастить Lightning Bolt `:` Who is going to get help from Elysium? - -Lightning Bolts can also use the Lightning Bolt Brough ` -` Сон о новой жизни - -Добавляется еще одна песня - Song of the Second Breath- Дает выбранному юниту временные 3*power max hp. После применения этой песни больше песни применять нельзя. `:` Dream of the Second Breath song-Song of the Second Breath song is added to the chosen iunites of temporary 3 *power max hp. Once this song has been used, no more songs can be used. ` -` Сон о победе - -Добавляется еще одна песня - Song of the the Last Push - выбранный юнит в этом ходу походит еще два раза. После применения этой песни больше песни применять нельзя. `:` Song of the the Last Push is a song about the winning of the song two more times. Once this song has been used, no more songs can be used. ` -` Сон о доме - -Добавляется еще одна песня - Song of the Last Step - Song of the Last Step - все юниты получают телепортацию, штраф к инициативе -5. После применения этой песни больше песни применять нельзя. `:` Song of the Last Step-Song of the Last Step. Once this song has been used, no more songs can be used. ` -` Веселимся тихо - -Azata Veranalia (делаем ларджовую женщину эльфийку в робе и накрываем ее ФХом из PF:KM autumn treant creature) - сверхмощный кастер с масс хилом, destruction'ом, чейн лайтнингами. `:` We have fun quietly, Azata Veranalia (make a largeness flake in the robe and cover her with FX from PF:KM autumn train)-a superpower kaster with a lot of chil, destruction', honkies. ` -` Веселимся буйно - -Хавок драконов - топового юнита с очень крутыми статами. `:` The fun of the dragon's hoof-top dragons with very cool articles. ` -` в "After release" - надо просто линкануть к родителю. -+в MoveAzataIslandToLocation надо будет указать глобал мапу `:` in "After release"-you just have to link to parent. + MoveAzatIslandToLocation will need to specify the global Mapu ` -` 3 дня до финального обжектива (запускается, если 3 обжектива-задания выполнены) `:` 3 days ahead of the final obsession (run if 3 of the jobs are completed) ` -` Дефолтное состояние всех NPC + обжи `:` Defolate status of all NPC + s ` -` 1 день на установку лагеря `:` 1 day for camp installation ` -` Стейт который говорит, что нет боевки `:` Stite, who says there's no clot. ` -` Если этюд закомпличен, значит игрок терраформировал остров `:` If the sketch is compliant, then the player terraforming the island ` -` Запускает эмбиенс на первый визит(пустыня) и эмиттер на корапшене -Стартует в ЭТЮДЕ AzataIsland_FirstVisit -Завершается в КАТСЦЕНЕ AzataIsland_FirstVisit `:` Embiens starts the first visit (desert) and the emitter on the corapeni startuts at AzzataIsland_FirstVisit Ends at the Azatasad_FirstVisit CENSUS. ` -` Запускает эмбиенс, эмиттеры для терраформированного острова. -Стартует в КАТСЦЕНЕ AzataIsland_FirstVisit -Завершется в ЭТЮДЕ IslandFly `:` Runs the embens, the emitters for the terraformed island. -Startoth at Azataslyand_FirstVisit will end in ETYDE IslandFly. ` -` Включает эмбиенс и эмиттеры на взлетевший остров. -Стартует в ЭТЮДЕ IslandFly `:` Enables the embiens and the emitters to take off the island. -Startute at ETIE IslandFly ` -` Выдача аддендума - возвращайся на остров `:` return to the island ` -` 14 дня для продолжения квеста азаты и старт таймер до прихода КТС Трентов `:` 14 days to continue the quests of azaty and start timer until the arrival of the CTS Trent ` -` Третий обжектив основого квеста - поле чудес `:` The third pillar of the foundation of the quest is the field of miracles ` -` Таймер до 5 обжектива. Стартует если 3 и 4 выполнены или колокол привезли в дрезен `:` A timer of up to five. Stargates if 3 and 4 are executed or the bell is delivered to the dresen ` -` Стартует 5 обжектив с Колоколом и Арфой `:` Staritet 5s with the Bell and the Arfa ` -` Таймер до постройки Источника или Поляны трентов `:` Timer pending the construction of the Source or the Trent ` -` Запускает эмбиенс для состояния локации перед полетом острова `:` Runs the embens for the location of the location before the island's flight ` -` Загрузка банка аудио для катсцены взлета `:` Loading the audio bank for the takeoff stage of takeoff ` -` Валхахфлинги и мимики в Ивори санктум `:` The Walkhfelling and the Mimics in Ivori Sachkta ` -` Вольыне крестоносцы в Молтен скаре `:` Volyn Volyn of the Crusaders in the Molten Scouts ` -` Этюд, который говорит, что что ментор Азаты стоит в зале собраний КТС `:` Eutid, who says that Azaty's mentor stands in the meeting room of the [ [ TC]] ` -` Этюд позволяющий делать реинфорседы, когда игрок может стать демоном `:` A [ [ Eutie]] allowing the player to become a reinformer when the player can become a demon ` -` Звуки толпы для демона `:` The sound of crowds for the demon. ` -` Катсцены боя для массовки `:` Cutscenes of the fight for the fight. ` -` Чилексы выгнаны из города `:` The Chiles are out of town. ` -` был раньше Аеоном `:` He used to be Aeon. ` -` был раньше Азатой `:` Azatoy's earlier ` -` Механически можем стать дьяволом `:` Mechanically able to become the devil ` -` Отказался стать генерал-губернатором `:` Defer to becoming a Governor-General ` -` При становлении дьяволом игрок теряет все бонусы ранг апов от азаты, - -Ему так же дают нанимать дьяволов Ериний - крутых, но немногочисленных лучниц. `:` When the devil is established, the player loses all bonuses to the rank of apps, - -He is also allowed to hire the devils of Jerusalem-the cool but not numerous archers. ` -` Анлокается здание infernal forge, увеличивающее приток финансов на 100 и материалов на 3. `:` The infernal forge building is growing, increasing the flow of finance into 100 and materials by 3. ` -` Игроку дают 50000 золота. `:` The player is given 50,000 gold. ` -` Выбранный игроком апгрейд пехоты в рангапе Military 1 меняется на Devil Infantry - тяжелую пехоту с очень крутыми статами - хаендовый юнит с высоким АС. `:` The infantry chosen by the player in the Military 1 rank changes to Devil Infantry, a heavy infantry with very cool articles-hustle young with a high AU. ` -` Выбранный игроком апгрейд кавалерии в рангапе Military 3 меняется на Devil Cavalry - тяжелую кавалерию с очень крутыми статами - хаендовый юнит с высоким дамагом и смайт хаос. `:` The chosen player of the [ [ cavalry]] of the [ [ Military Hotel]] in the '' Military '' [ [ Military]], the [ [ Military Cavalry]] is changed to Devil Cavalry-a heavy cavalry with a very cool statue-a chainte young with a high damage, and savor the chaos. ` -` Выбранный игроком апгрейд лучников в рангапе Military 2 меняется на Devil Archers - лучников с очень крутыми статами - хаендовый юнит с высоким дамагом. `:` The player selected by the gamer of archers in the rank of Military 2 is changed to Devil Archers-archers with a very cool statue-chaenoon young with a high damage. ` -` Игроку предлагают организовать собственный орден подобный хеллайтам - -Справимся и так. - -Мораль и макс мораль увеличиваются на 30. `:` The player is offered to organize his own order similar to hellites in the right and so on. - -Morals and Morals increase by 30. ` -` Становятся доступны наемники Slaves - -Это пехота с DRом и высоким уроном, но не очень быстрая и с плохой моралью. `:` Slaves mercenaries is available with DR and high damage, but not very fast and with bad morals. ` -` Infernal forge помимо своих бонусов начинает давать +1 к атаке, АС, сейвам и инициативе всем тренируемым юнитам (стекается) `:` Infernal forge, in addition to its bonuses, starts to give + 1 to attack, AU, Saewis and the initiative to all the units trained ` -` Игроку дают 200000 золота. `:` The player is given 200,000 gold. ` -` Что делать с реликвиями Саркориса? - -Построить реликварий. - -Здание, дающее +3 к атаке, АС, сейвам и инициативе всем мифическим юнитам (стекается) `:` What do you do with the Sarcoris relic? - -Build a relic. - -The building that gives + 3 to the attack, the AU, the Seycheis and the initiative all the mythical units ` -` Игрок получает универсально крутой предмет. `:` The player gets a versatile object. ` -` Нужны ли игроку в армию клерики Асмодея? - -Да, давайте клериков. - -Игроку даются клерики Асмодея - хаендовые дивайн кастеры с высокой защитой и которые могут кастить доминейт в добавок к обычным дивайновым спеллам. `:` Do you need a player in the armies of Asmodeus? - -Yeah, let' s do the clerics. - -The player is given the Asmodeya-Haender marvellings with high protection and who can muse a blow job in addition to ordinary special spellings. ` -` Нужны ли игроку в армию клерики Асмодея? - -Дайте лучше дьяволов. - -Игрок теперь может нанимать Executioner Devil - очень мощных милишниц с бонусом на атаки если они не двигаются. `:` Do you need a player in the armies of Asmodeus? - -Let the devils be better. - -The player can now hire the Executioner Devil-very powerful mills with an attack bonus if they do not move. ` -` Корневой этюд для квеста дракона в 3 главе `:` Root sketch for the dragon's quest in the third chapter ` -` Флаг - убили культиста в квесте дракона 3 главы `:` The flag was killed by a 3-chapter cult in the dragon's quest ` -` Механически разблокирует дракона `:` Mechanically unlocks a dragon ` -` Чек флага - пощадили ли мы культиста в квесте дракона 3 главы `:` Do we have a chek of a flag in the dragon's quest? ` -` пытался поредимить в ALR `:` tried in ALR ` -` Кенабрес ребилдед: Квесты драконов в 5 главе `:` Kenabres Rabilgrandfather: The Dragons of Dragons in Chapter 5 ` -` Полиморф драконов в людей и смена их имен. По завершению полиморф в драконов `:` Polymorpheme of dragons in humans and change of their names. By the end of the polymorphus in the dragons ` -` Хал стоит в отстроенном кенабресе `:` Hal is standing in the rebuilt kenabrese. ` -` Анлок Sevalros Lair по квесту дракона `:` Anlock Sevelros Lore by dragon's quests ` -` Ревил локации Terendelev Lair для квеста дракона в 3 главе `:` Relaved Terendelevv Lair's location for dragon's quest in 3 chapter ` -` Квест дракона Тивандис на цветы в лэер. Открыть AngelFlowersLair `:` The dragon's quest for the flowers in the laèer. Open AngelFlowersLair ` -` Квест от дракона Оргомандиаса на книгу в айвори лабиринт `:` Quest from the Orgomandias dragon to a book in an aiven maze ` -` Флаг на правильные ответ в диалоге с Нидалинн `:` Flag for correct answers in dialog with NidTallinn ` -` Флаг на правильный ответ в диалоге с Оргомандиасом `:` Flag for correct answer in dialogue with Orgomandiaz ` -` Выдача обжа на диалог с Хэлом когда выполнил или не выполнил квесты драконов `:` The issuance of a dialogue with Hal when he performed or did not perform the dragons ' quests ` -` Флаг на правильный ответ в диалоге с Тивандис `:` Flag for correct answer in dialogue with Tiwanis ` -` Квест от Нидалинн на сыр `:` Kveston from Nidylinna to cheese ` -` Драка с Севалросом `:` Coval with Cevalros ` -` Флаг - Севалрос остался коррапченым и улетел `:` The flag-Sevalros remained corruped and flew away ` -` Флаг - Севалрос умирает, но стал серебряным драконом `:` The flag of Cevalros is dying, but has become a silver dragon ` -` Флаг - Севалрос стал серебряным драконом и улетел `:` Sevalros became a silver dragon and flew away ` -` Флаг - Севалрос умер коррпаченым драконом `:` The flag of Sevalros died of a dragon. ` -` Старт боя с культистами (начинается через скрипт зону в механике) -Вешаем бафф бессмертия на 1 культиста, т.к. он должен выжить и его судьба решается в диалоге -приходи Хэла при смерти всех культистов `:` Start of the battle with the cultists (starts through the script zone in mechanics) -The [ [ batff]] of the [ [ immortality]] on the 1 [ [ cult]], because he must survive and his fate is decided in the [ [ Hal's Parish]] in the death of all cultists ` -` Добавление механики с кулльтистами в логово теренделев `:` Adding mechanics with coultists in the lair of the terendes ` -` Атакуем дракона Хэла, бой `:` Hal's dragon, fight. ` -` Убиваем оставшегося культиста `:` Kill the rest of the cult. ` -` Этюд который стартуется если мы отпускаем последнего выжившего культиста `:` That's the one that's gonna start if we release the last survivor. ` -` Механически разблокирует выбор легенды для игрока `:` Mechanically unlock the player's legend ` -` Отказались брать Лексикон `:` You were supposed to take the Lexicon. ` -` выдаёт эрранд если полутали череп Альдерпаша `:` issues an errand if the skull is crawling with Alderpaşa's skull ` -` Этот этюд анлокает лича в механике -Может быть залочен в том же диалоге где его выдали `:` This sketch of the lić in the mechanic may be locked in the same dialogue where he was issued ` -` не стал настоящим Личём (отказался от превращения в андеда) `:` did not become a real Lachchem (renounced turning into an andeda) ` -` нам сломали филактерию `:` we broke the philter. ` -` Влияние мифика на столицу `:` Influence of the Mifiq on the Capital ` -` зональный (столица) `:` zonal (capital) ` -` Элянка пришла и стала нашим советником `:` The elank came and became our advisor. ` -` мы поссорились с Elyanka -она придёт воевать с нами после Личефикации `:` We quarreled with the Elyanka she would come to fight with us after Leitacia ` -` управляет эвентами в UnloyalCounselor `:` manages eents in UnloyalCounselor ` -` по тем или иным причинам эвенты Лича закончились (например из-за неправильного алаймента) `:` For some reason, the Litch's heals ran out (for example, due to wrong alayment) ` -` Мы подружились с Элянкой `:` We made friends with Elanka. ` -` Мы подружились с Септимусом `:` We made friends with Septimus. ` -` Прогнать - -Все генералы получают бонус к силе магии и мане - -Все генералы получают Bone Explosion, Siphon Time, Bone Spear `:` All generals get a bonus to magic and all generals get Bone Explosion, Siphon Time, Bone Spear ` -` Всех принять - -Некромансия воскрешает на 40% больше нежити. - -Все генералы получают Bone Shield и False Grace `:` The Necromancy is resurrecting 40% more. - -All generals get Bone Shield and False Grace ` -` Скелеты лучники. - -Часть нежити ресается скелетами лучниками - они не очень сильные, но лучники. `:` Skeletas are the archers. - -A part of the unliving is a skeleton of archers-they are not very strong, but the archers. ` -` Скелеты чемпионы - -Часть нежити (10%) ресается скелетал чемпионами - достаточно мощной нежитью с высокой защитой. `:` Skeletas champions part of the undead (10%) was skims of the champions-quite a powerful non-jitus with high protection. ` -` Вампиры. - -Часть нежити ресается вампирами - они имеют спец атаки дебаффающие врагов и хилящие их. `:` The vampires, part of the unliving are vampires, they have a special attack of debaffling enemies and preying them. ` -` Хоронить героев - -Плюс 10 морали, снижает негативную мораль от нежити в армии в два раза. `:` To bury the heroes plus 10 morals, reduces the negative morale of the army twice. ` -` В самом начале 5ой главы лич получает армию скелетов, зомби и выбранных юнитов во втором рангапе стоимостью в 25000 финансов `:` At the beginning of the 5th chapter, Lich receives an army of skeletons, zombie and selected units in the second rangep cost of 25,000 ` -` Вскрыть могильники Саркориса - -Дает пассивный прирост скелетов `:` Warm up the Sarcoris mists give us passive skeletons. ` -` Жатва - -Дает пассивный прирост зомби `:` Zatova's a passive zombie growth. ` -` Законы Мертвых - -Дает пассивный прирост нежити из рангапа 2 `:` The Dead's laws give us a passive growth rate of 2. ` -` Скормить жрецов нежити - -Дает всей нежити в армиях абилку с 33% вероятностью позволяющую сделать дополнительный ход, как при положительной морали `:` To grimace the priests in the army, the Abilka, with a 33% chance of making an extra move, as with positive morals, is the most likely ` -` Коррумировать церковь - -Дает новый ритуал Animate Dead `:` Corruize the church with the new ritual of Animate Dead ` -` Выгнать и обокрасть - -Дает артефакт `:` Remove and remove the Artifact ` -` Тайное общество магов - -Часть нежити (2%) восрешается в виде личей - мощных кастеров, которые так же могут бесконечно стрелять лучами негативной энергии. - -За каждые 40000 опыта - один лич (оценивается в 200 финансов) `:` The secret society of the mages is part of the non-housing (2%) is solved in the form of lich-powerful stasters, which can also endlessly shoot negative energy beams. - -For every 40,000 experience-one Lic (estimated in 200) ` -` Орден грейвнайтов - -Часть нежити (2%) воскрешается в виде Grave Knight'ов - очень сильной пехоты, которая усиливает всю нежить в армии. - -За каждые 40000 опыта - один grave knight (оценивается в 200 финансов) `:` The Order of the Greywaines (2%) is resurrect as the Grave Knightly-a very strong infantry that reinforces the entire army. - -For every 40,000 experience-one grave knowledge (estimated in 200 finance) ` -` Культ фараона - -Часть нежити (2%) воскрешается в виде Мумий-клериков - -За каждые 40000 опыта - одна мумия (оценивается в 200 финансов) `:` The cult of the pharaoh Part of the nezhychi (2%) is revived in the form of [ [ Mummies]] for every 40,000 experience-one mummy (estimated in 200 finance) ` -` Дает найм равенеров `:` -The Rabeners are hired. ` -` Дает найм чумных драконов `:` She's hiring some lumber dragons. ` -` Дает найм Nightshade, Nightwalker (визуально - балор с ФХом от спектра). `:` I'm hiring Nightshade, Nightwalker (a visually-pamper with FX from the spectrum). ` -` "Кровавая свадьба" `:` "Blood Wedding" ` -` баркосцены между Элянкой и Септимусом `:` Barcosus between Elyanka and Septimus ` -` Септимус пришёл и стал нашим советником `:` Septimus has come and became our advisor. ` -` мы поссорились с Septimus -он придёт воевать с нами после Личефикации `:` We had a fight with Septimus, he would come to fight with us after the Licification. ` -` НекроДрезен `:` Necrodresen ` -` мы стали нежитью и получили 9 мифик у Захара `:` We became undead and received nine Mifiq from Zahara ` -` труп Стонтона на площади Дрезена `:` Stonton's corpse in Dresen Square ` -` труп Стонтона лежит в тюрьме Дрезена `:` Stonton's corpse is in Dresen prison ` -` призраки в AL `:` phantoms in AL ` -` Призраки на разных зонах `:` Ghosts in different zones ` -` призраки в AR `:` ghosts in the AR ` -` призраки в DC `:` ghosts in DC ` -` призраки в GS `:` GGS ghosts ` -` призраки в Iz `:` Ghosts in Iz ` -` для подвала Лича до того как ты стал Личем `:` Lich's basement before you became a Lachem. ` -` призраки в LC `:` ghosts in LC ` -` Отключаем слизь через день после убийства королевы `:` Disengaging the slime the day after the queen's murder. ` -` Выдаем проект про эксперименты Зантира, если полутали его заметки в Костяной Ложе `:` We're running an Xanter project if he's a little stickup in Kostya Lie. ` -` Оставлю культистов в своем войске живыми. - -Культисты это такие походные рационы для армии сварма - наехав на них свармы дамажат их, баффаются и лечатся. Статы культистов аналогичны культистам демона - они умеют делать ченнел негатив и кастовать клерические баффы. `:` I'll leave the cultists in my army alive. - -Cultists are such meals for the army of the welt-they give them the welms of damagthem, the baffalo and cures. The art of cultists is the same as the Demon's cultists-they know how to make the negativity and to knock out the Clerical Baffes. ` -` Я скормлю их рою - паукам. - -Пауков становится больше на 500. Свармы пауков могут кастовать клерические спеллы (до 3 уровня) и делать ченнел негатив. Убитые ченнел негатив враги считаются съеденными этим свармом. `:` I feed them to the spiders. - -Spiders are getting bigger by 500. Spiders can be scled in clerical specials (up to 3 levels) and made a negative. The dead negativity of the enemy are considered to be eaten by this welt. ` -` Я скормлю их рою - саранче. - -Саранчи становится больше на 500. Свармы саранчи могут кастовать клерические спеллы (до 3 уровня) и делать ченнел негатив. Убитые ченнел негатив враги считаются съеденными этим свармом. `:` I feed them to the saunas. - -The ranches are bigger by 500. The locusts can be scleric hasty (up to 3 levels) and made a negative. The dead negativity of the enemy are considered to be eaten by this welt. ` -` Я скормлю их рою - вескаворам. - -Вескаворов становится больше на 500. Свармы вескаворов могут кастовать клерические спеллы (до 3 уровня) и делать ченнел негатив. Убитые ченнел негатив враги считаются съеденными этим свармом. `:` I feed them to their faces. - -The vespacers are getting bigger by 500. The swarms of gaily thieves may be cysterical (up to 3 levels) and made to the negativity. The dead negativity of the enemy are considered to be eaten by this welt. ` -` Оставлю рейнджеров в своем войске живыми. - -Эти культисты про быстрое убийство конкретных врагов и большой урон с первой атаки (у свармов с их АоЕ уроном могут быть проблемы с этим). `:` I'll leave the Rangers in my army alive. - -These cultists for the swift killing of specific enemies and the great loss of the first attack (with their AoE damage could be a problem with that). ` -` Я скормлю их рою - паукам. - -Пауков становится больше на 500. Свармы пауков теперь могут один раз в бой переместиться в конкретного врага и заодно нанести ему много урона и подебаффить его. `:` I feed them to the spiders. - -Spiders are getting bigger by 500. Spidings can now be moved to a specific enemy once in the battle, and to do a lot of damage to it and debit it. ` -` Я скормлю их рою - саранче. - -Саранчи становится больше на 500. Свармы саранчи теперь могут один раз в бой переместиться в конкретного врага и заодно нанести ему и всему на линии до него много урона. `:` I feed them to the saunas. - -The ranches are bigger by 500. The swarms of locusts can now be shot once in the fight to a particular enemy and also inflict a lot of damage on him and the rest of the line. ` -` Я скормлю их рою - вескаворам. - -Вескаворов становится больше на 500. Свармы вескаворов теперь могут один раз в бой переместиться в конкретного врага и заодно нанести ему много урона. `:` I feed them to their faces. - -The vespacers are getting bigger by 500. The swarms of the oars can now be moved once into the fight to a particular enemy and to inflict a lot of damage. ` -` Я съем вас сам. - -Игрок получает бонус на первую атаку в бою (автокрит), абилки ассасина 7 уровня. `:` I'll eat you myself. - -The player receives a bonus for the first attack in the battle (Autocrit), the Hassassin's level' s 7. ` -` Оставлю их в своем войске живыми. - -Это мощные кастеры, которых очень тяжело убить, но которых мало. `:` I'll keep them alive. - -These are powerful posters, which are very hard to kill, but they are not enough. ` -` Я скормлю их рою - паукам. - -Пауков становится больше на 1000. Свармы пауков теперь могут кастовать арканические заклинания. Заклинания слабее чем у worm-that-walks, но усиливаются от количества свармов. `:` I feed them to the spiders. - -Spiders are becoming more than 1,000. The spiders can now cover the arkanical spells. The spells are weaker than the storm-that-walks, but are amplified by the number of welds. ` -` Я скормлю их рою - саранче. - -Саранчи становится больше на 1000. Свармы саранчи теперь могут кастовать арканические заклинания. Заклинания слабее чем у worm-that-walks, но усиливаются от количества свармов. `:` I feed them to the saunas. - -The ranches are bigger by 1000. The locusts are now able to cover the arkanical spells. The spells are weaker than the storm-that-walks, but are amplified by the number of welds. ` -` Я скормлю их рою - вескаворам. - -Вескаворов становится больше на 1000. Свармы вескаворов теперь могут кастовать арканические заклинания. Заклинания слабее чем у worm-that-walks, но усиливаются от количества свармов. `:` I feed them to their faces. - -The vespacers are becoming more than 1,000. The savory welding can now cover the arkanical spells. The spells are weaker than the storm-that-walks, but are amplified by the number of welds. ` -` Я съем вас сам. - -Игрок получает арканные заклинания как 17 уровня визард. `:` I'll eat you myself. - -The player receives arcanna spells like 17 levels of virgard. ` -` Преклониться. - -Теперь все свармы армии игрока разносят чуму - все враги с ней получают урон и разлагаются, дебаффясь все сильнее каждый ход. Однако убитые чумой враги не прибавляются к рою. `:` Now all the warms of the player's army spread the plague-all the enemies with it get the damage and decompose, the debaffe is growing more and more every move. But the plague killed by the enemy is not added to the face. ` -` Подчинить культистов Глаундера - -дает элитных культистов одноразово и много `:` Gladder's cultists give elite cultists a one-time and a lot of ` -` Я скормлю их рою - паукам. - -Пауков становится больше на 1000. Свармы пауков могут кастовать клерические спеллы (до 5 уровня) и делать ченнел негатив. Убитые ченнел негатив враги считаются съеденными этим свармом. Если в первом ранг апе игрок уже кормил пауков культистами - ранг доступных спеллов повышается до 7-ого. `:` I feed them to the spiders. - -Spiders are becoming more than 1,000. Spiders can be scled in clerical specials (up to 5 levels) and can be done with negativity. The dead negativity of the enemy are considered to be eaten by this welt. If in the first rank the player has already fed the spiders with cultists-the rank of the available spells rises to 7th. ` -` Я скормлю их рою - саранче. - -Саранчи становится больше на 1000. Свармы саранчи могут кастовать клерические клерические спеллы (до 5 уровня) и делать ченнел негатив. Убитые ченнел негатив враги считаются съеденными этим свармом. Если в первом ранг апе игрок уже кормил саранчу культистами - ранг доступных спеллов повышается до 7-ого. `:` I feed them to the saunas. - -The ranches are bigger by 1000. The locusts can be sclerical clerical clerical specials (up to 5 levels) and made a negative. The dead negativity of the enemy are considered to be eaten by this welt. If in the first grade the player has already fed locusts cultists-the rank of the available ripe sappers is raised to 7th. ` -` Я скормлю их рою - вескаворам. - -Вескаворов становится больше на 1000. Свармы вескаворов могут кастовать клерические клерические спеллы (до 5 уровня) и делать ченнел негатив. Убитые ченнел негатив враги считаются съеденными этим свармом. Если в первом ранг апе игрок уже кормил вескаворов культистами - ранг доступных спеллов повышается до 7-ого. `:` I feed them to their faces. - -The vespacers are becoming more than 1,000. The swarms of gaily thieves may be stoveped the clerical clerical hurrs (up to 5 levels) and the negativity was done. The dead negativity of the enemy are considered to be eaten by this welt. If in the first place the player has already fed the johs, the ranks of the cultists-the ranks of the available spla-can be raised to 7th. ` -` Я съем вас сам. - -Игрок получает диванные заклинания как 17 уровня клерик. `:` I'll eat you myself. - -The player gets a couch spell as 17-level cleric. ` -` Приму их дары и их службу. - -Игрок получает большую армию рыцарей. Рыцари достаточно сильные кавалеристские юниты и их очень много (4000) - Demonic Knights. `:` I'll take their gifts and their service. - -The player gets a large army of knights. The knights are strong enough to be strong, and there are a lot of them (4000)-Demonic Knights. ` -` Я скормлю их рою - паукам. - -Пауков становится больше на 4000. Свармы пауков теперь движуться быстрее на 2. `:` I feed them to the spiders. - -Spiders become more than 4000. Spiders now move faster by 2. ` -` Я скормлю их рою - саранче. - -Саранчи становится больше на 4000. Свармы саранчи теперь движуться быстрее на 2. `:` I feed them to the saunas. - -The ranches are bigger on 4000. The locusts are now moving faster by two. ` -` Я скормлю их рою - вескаворам. - -Вескаворов становится больше на 4000. Свармы вескаворов теперь движуться быстрее на 2. `:` I feed them to their faces. - -Vespacore gets bigger by 4,000. The savory welding is now moving faster to 2. ` -` Я съем вас сам. - -Игрок получает 200 дополнительных ХП. `:` I'll eat you myself. - -The player receives 200 additional CPs. ` -` заткнули голос Дескари у нас в голове `:` will shut the Descari's voice in our heads. ` -` Когда решили использовать гадость вескаворов в Дрезене `:` When we decided to use the gavel in Dresen ` -` Использовали ферамоны в Дрезене `:` Use of the Drezene Dramones ` -` На момент перерождения командора в отряде были Сиила или Регилл. Анлочит ивент SE_LocustMythic_Reckoning (69f26a560623b174586cdc74ec81b331) `:` At the time of the rebirth, the commander in the detachment was Siila or Regell. Anloys SE_LocustMythic_Reckoning (69f26a560623b174586cdc74ec81b331) ` -` Локуст механически доступен для выбора `:` Locust mechanic is available for selection ` -` Когда решили сохранить гадость вескаворов `:` When we decided to keep the joys of the gavel. ` -` Зантир уходит, чтобы самостоятельно превратиться в сварма и прийти помогать нам в Трешолде. `:` Zandir is leaving to turn himself into a weld and come to help us in Tresold. ` -` Выполнили проект после получени записок Зантира в Костяной Ложе `:` The project was completed after the [ [ Costya Lie]] notes were written ` -` Игрок знает что Сокот - брат и любовник Ноктикулы `:` The player knows that Sokot is a brother and lover of Nocticula ` -` Вызвал короля-дурака бить демонов в начале 5й главы `:` Called the king-fool to beat the demons at the beginning of the 5th chapter ` -` Солдаты начинают приносить доход. Каждый из них приносит свой тир в неделю (т.е. пехота и лучники приносят по 1 в неделю, а паладины 2 в неделю). `:` The soldiers are beginning to generate income. Each of them brings its own shooting range in a week (that is, infantry and archers bring 1 per week, and paladins 2 per week). ` -` Офицеры получают случайное число опыта каждый день (Левел d100). `:` Officers get random experience every day (Lewell d100). ` -` Пивоварня - жрецы `:` Priests and priests ` -` Пивоварня - маги `:` brewery-Mages ` -` Пивоварня - солдаты - `:` brewery-soldiers - ` -` В каждом бою есть один из случайных модификаторов. - -Порядок убийств: Один из врагов отмечается специальной меткой. Он должен умереть первым - после его убийства всю армию баффает 20% шансом нанести двойной урон. Если убить другого юнита перед ним - бонус пропадет и у всей армии упадет мораль на 50. - -Интересное место: На карте отмечается случайная клетка. Встав в нее юнит до конца боя получает 20% шанс нанести двойной урон. - -Полиморф: У юнитов игрока появляется абилка сделать полиморф - она превращает юнитов до конца боя в случайное существо из списка (волк, медведь, смилодон, дракон, пони). - -Клад: На стороне карты противника случайная точка отмечается как клад. Если в нее зайти - за победу дадут доп ресурсы (случайные). - -Ферзь: Пехота дошедшая до вражеского края карты превращается в драконов до конца боя. - -Неожиданный эффект: Лучники теряют возможность стрелять, наносят четверной урон в мили. - -Сумасшествие: Все свои атакуют случайных врагов. Враги атакуют просто случайные цели. Никто не управляет юнитами. `:` Every battle has one of the random modifiers. - -Murder Order: One of the enemies is marked by a special mark. He must die first-after he is killed, the entire army is baffled by a 20% chance of causing a double damage. If you kill another young man in front of him, the bonus will be lost, and the entire army will have a moral fall of 50. - -An interesting place: A random cell is marked on the map. The boy gets a 20% chance of double damage before the end of the fight. - -Polymorph: The youth of the player will have a polymorphe-it turns the units into a random creature from the list (the wolf, the bear, the dragon, the dragon, the ponies). If she gets into her, they will get the resources (random) for victory. Fearings: The Pechota that reached the enemy edge of the map turns into dragons until the end of the battle. - -The unexpected effect: Archers lose the ability to shoot, do four-mile damage. - -It's crazy: all of them are attacking the random enemy. Enemige attacks are just random targets. Nobody controls the units. ` -` На крусейд каждую неделю вешается один из случайных баффов. - -Распродажа: Юниты определенного типа (кавалерия, пехота, лучники и т.д.) стоят на 90% дешевле. - -Архитектурное вдохновление: Конкретное здание из доступных стоит на 90% дешевле. - -Люди хотят: Если игрок за неделю построит определенное здание - ему дадут 7 доходов в деньгах и ресурсах, повысят мораль на 20. - -Тур: На карте отмечается точка, если туда зайти генералом - ему дадут половину опыта до следующего уровня. `:` One of the random Buffoffs hangs out every week. - -Sale: Unites of a certain type (cavalry, infantry, archers, etc.) are 90% cheaper. - -Architectural inspiration: A concrete building from affordable costs is 90% cheaper. - -People want: If a player builds a certain building in a week-he will be given 7 income in money and resources-it will increase morale by 20. - -Thor: On the map there is a point, if a general-he is given half the experience to the next level. ` -` Включаются бонусы от обоих предыдущих вариантов, однако всегда поднят красный флаг на -1 морали в день "Все в крусейде работает как попало". `:` The bonus includes bonuses from both previous versions, but the red flag is always raised on the first day "Everyone in the circle works as if". ` -` Foot Heavy Scout Bardruids Dragonriders - пешие друиды в тяжелой броне (БЕЗ ДРАКОНОВ). Имеет друидские и бардовские баффы, вообще отлично баффает и дерется. `:` Foot Heavy Scot Bardruids Dragonriders-foot druids in heavy armour (BES DRACONOV). It has Druid and Bardovska baffles, and it's a very good fight. ` -` Warlocks Warwarpriests - воины в тяжелой броне, с башенными щитами, скоростью 1 и кучей баффов которые можно кастить только на себя. Очень мощные (если пробаффаются), очень медленные. `:` Warlocks Warwarpriessts are warriors in heavy armor, with bated shields, speed 1, and a bunch of beaks that can only be fused for themselves. Very powerful (if perforated), very slow. ` -` Vampiric Ninja Pirates `:` Vampire Ninja Pirates ` -` Сворд сейнт с фаухардом! `:` Sold is with the Fauhard! ` -` Иллюзионисты убивающие все Phantasmal Killer'ом. `:` The Illusionists kill all Phantasmal Killer' s. ` -` Скейлед фист, который еще и dragon disciple, и eldritch knight, и паладин `:` Skeleda is a fixer who is also the dragon disciple, and the eldritch knight and the paladin ` -` Паладины с дипом в thug'а. `:` The paladins with the dip in the thug' s. ` -` Дурацкая экипировка - -Солдаты получают себе рандомные шлемы и светящееся оружие. Шлемы рандомятся из всего списка шлемов игры, енчанты на оружие из всего списка енчантов оружия. `:` The Soldier's outfits get their own helmets and light weapons. Helmets are from the entire list of helmets of the game, the enchants on the arms from the entire list of the arms of the weapon. ` -` Петы - -Каждый солдат может позвать себе (за стандарт) пета на поле боя. 1 тир вызывает ???, 2 тир вызывает ???, 3 и 4 тир вызывает мамонтов. - -Звери 1 и 2 тиров - олени, сороконожки `:` Pets each soldier can call himself (for a standard) pausing on the battlefield. 1 dash causes? ??, 2 dashes are called? ??, 3 and 4 is calling mammoths. - -Beast 1 and 2 dashes, centipede ` -` Артефакт `:` Artifact ` -` КТС первого визита Сокотбенота, которыйначинает линейку совета `:` Sokoto's first visit to the board of Sokótbenot ` -` КТС Сокота в пятой главе `:` Co-Cattle CV in the Fifth Chapter ` -` КТС Короля дурака про поиск арилу лабы `:` The King of the Fool's quest for the Aril of the Laba ` -` КТС Короля дурака про поиск арилу лабы если его прогнал игрок, а не посадил на трон `:` The CU of the King is a fool to search for the Aril of the lab if the player did not put him on the throne ` -` Пошучено `:` Received ` -` Бой против совета `:` Fight Against the Board ` -` Бой против совета, Нокта за нас `:` Fight against the council, Nota for us. ` -` Бой против нокты, совет за нас `:` The fight against the legs, the council for us. ` -` Выдается в Blackwing Ruins - добавляет диверсию Калеба в Серый Гарнизон `:` Disbursing in the Blackwing Ruins-adds Caleb's diversion to the Grey Garrison ` -` Пошучено над Ноктой в 5й главе `:` The Head of the Nota in the 5th chapter ` -` Нокта явится на совет в 5й главе `:` The night will be on the council in the 5th chapter. ` -` Сокот покинул совет `:` The cocoon left the council ` -` Трикстер создал свое знамя из носка `:` The Trickster made his banner out of his sock. ` -` Этюд, который фейлит трикстера `:` The Hail of the Tricster ` -` Этот этюд анлокает трикстера в механике `:` This sketch is an anlocus of the trixster in mechanics. ` -` получил TE `:` received the TE ` -` Военный совет вперед Дрезеном `:` Military Council Forward by Dresen ` -` Сейчас идёт какой-то кингдом-турн`:` It's going to be some kind of a movie tour.` diff --git a/ToyBox/Localization/ru-RU.json b/ToyBox/Localization/ru-RU.json new file mode 100644 index 000000000..59760cae7 --- /dev/null +++ b/ToyBox/Localization/ru-RU.json @@ -0,0 +1,688 @@ + { + "LanguageCode": "ru-RU", + "Version": "1.5.13", + "Contributors": "Claire de Volta", + "HomePage": "https://github.com/cabarius/ToyBox/", + "Strings": { + " (This setting is per-save)": " (Эта настройка доступна для каждого сохранения)", + " Allow remote companions to make comments on dialog you are having.": " Разрешите собеседникам не в партии комментировать ваш диалог.", + " by the following amount:": " на следующую сумму:", + " meters": " метры", + "<": "<", + "<b><color=magenta>Note </color></b><b><color=orange>ToyBox was designed to offer the best user experience at widths of 1920 or higher. Please consider increasing your resolution up of at least 1920x1080 (ideally 4k) and go to Unity Mod Manager 'Settings' tab to change the mod window width to at least 1920. Increasing the UI scale is nice too when running at 4k</color></b>": "<b><color=magenta>Note </color></b><b><color=orange>ToyBox был разработан таким образом, чтобы обеспечить наилучший пользовательский опыт при ширине 1920 или выше. Пожалуйста, подумайте об увеличении вашего разрешения как минимум до 1920x1080 (идеально для 4k) и перейдите к Unity Mod Manager 'Settings', во вкладке изменения ширины окна выставьте как минимум на 1920. Увеличение масштаба ПИ тоже хорошо при работе на 4k</color></b>", + "<b><color=magenta>Warning: </color></b><color=orange>This is an experimental preview of ToyBox (<color=cyan>Sh0dan</color>) for Rogue Trader Beta.</color><b><color=yellow> Save early and often.\r\n</color></b><b><color=magenta>Note:</color></b><color=orange> Not all features are functional at this time. The ToyBox team is working hard to get as much working as fast as possible</color>": "<b><color=magenta>Внимание: </color></b><color=orange>Это экспериментальный предварительный просмотр ToyBox (<color=cyan>Sh0dan</color>) для бета-версии Rogue Trader.</color><b><color=yellow> Сохраняйтесь почаще.\r\n</color></b><b><color=magenta>Примечание:</color></b><color=orange> В настоящее время не все функции работают. Команда ToyBox прилагает все усилия, чтобы как можно быстрее выполнить как можно больше работ</color>", + "<b><color=magenta>Warning: </color></b><color=orange>This is an experimental preview of ToyBox for Rogue Trader.</color><b><color=yellow> Save early and often.\r\n</color></b><b><color=magenta>Note:</color></b><color=orange> Not all features are functional at this time. If you notice a feature doesn't work please report that on GitHub or in the modding channels on the Owlcat Discord.</color>": "<b><color=magenta>Внимание: </color></b><color=orange>Это экспериментальная предварительная версия ToyBox для Rogue Trader.</color><b><color=yellow> Сохраняйтесь чаще .\r\n</color></b><b><color=magenta>Примечание.</color></b><color=orange> В настоящее время не все функции работают. Если вы заметили, что какая-то функция не работает, сообщите об этом на GitHub или в каналах моддинга на Owlcat Discord.</color>", + "<b><color=orange>Achievements</color></b>": "<b><color=orange>Достижения</color></b>", + "<b><color=orange>Bag of Tricks</color></b>": "<b><color=orange>Коробка с трюками</color></b>", + "<b><color=orange>Blueprints</color></b> loading: ": "<b><color=orange>Чертежи</color></b> загружается: ", + "<b><color=orange>character(s) can be </color></b><color=cyan>Recruited</color><color=#00ff00ff>. This allows you to add non party NPCs to your party as if they were mercenaries</color>": "<b><color=orange>character(s) can be </color></b><color=cyan>Завербовать</color><color=#00ff00ff>. Это позволяет вам добавлять в свою Партию непартийных НПС, как если бы они были наемниками</color>", + "<b><color=orange>character(s) can be </color></b><color=cyan>Respecced</color><color=#00ff00ff>. Pressing Respec will close the mod window and take you to character level up</color>": "<b><color=orange>Персонаж(и) может быть </color></b><color=cyan>СБРОШЕН</color><color=#00ff00ff>. Нажатие кнопки Перераспределение(сброса) закроет окно программы и приведет вас к повышению уровня персонажа. </color>", + "<b><color=orange>Colonies</color></b>": "<b><color=orange>Колонии</color></b>", + "<b><color=orange>Dialog & NPCs</color></b>": "<b><color=orange>Диалоги и НПС</color></b>", + "<b><color=orange>Enchantment</color></b>": "<b><color=orange>Зачарование</color></b>", + "<b><color=orange>Enhanced UI</color></b>": "<b><color=orange>Улучшение ГПИ</color></b>", + "<b><color=orange>Etudes</color></b>": "<b><color=orange>Этюды</color></b>", + "<b><color=orange>Level Up</color></b>": "<b><color=orange>Поднятие уровня</color></b>", + "<b><color=orange>Loot</color></b>": "<b><color=orange>Лут</color></b>", + "<b><color=orange>Note</color></b><color=#00ff00ff> this is a new and exciting feature that allows you to see for the first time the structure and some basic relationships of </color><b><color=cyan>Etudes</color></b><color=#00ff00ff> and other </color><b><color=cyan>Elements</color></b><color=#00ff00ff> that control the progression of your game story. Etudes are hierarchical in structure and additionally contain a set of </color><b><color=cyan>Elements</color></b><color=#00ff00ff> that can both conditions to check and actions to execute when the etude is started. As you browe you will notice there is a disclosure triangle next to the name which will show the children of the Etude. Etudes that have </color><b><color=cyan>Elements</color></b><color=#00ff00ff> will offer a second disclosure triangle next to the status that will show them to you.</color>": "<b><color=orange>Примечание</color></b><color=#00ff00ff> это новая и захватывающая функция, которая позволит вам впервые увидеть структуру и некоторые основные взаимосвязи </color><b><color=cyan>Этюдов</color></b><color=#00ff00ff> и других </color><b><color=cyan>Элементов</color></b><color=#00ff00ff> которые управляют развитием вашей игровой истории. Этюды имеют иерархическую структуру и дополнительно содержат еще наборы </color><b><color=cyan>Элементов</color></b><color=#00ff00ff> и это может как дать вам проверять условия, так и выполнять различные действия при запуске этюда. Просматривая, вы заметите, что рядом с названием есть треугольник, в котором будут показаны дочерние элементы этюда. Этюды, имеют </color><b><color=cyan>Элементы</color></b><color=#00ff00ff> и предложат вам второй треугольник, рядом со статусом, который раскроет дополнительную информацию.</color>", + "<b><color=orange>Party</color></b>": "<b><color=orange>Партия</color></b>", + "<b><color=orange>Quests</color></b>": "<b><color=orange>Квесты</color></b>", + "<b><color=orange>Saves</color></b>": "<b><color=orange>Сохранения</color></b>", + "<b><color=orange>Search 'n Pick</color></b>": "<b><color=orange>Ищи и Выбирай</color></b>", + "<b><color=orange>Settings</color></b>": "<b><color=orange>Настройки</color></b>", + "<b><color=yellow>BACK UP</color></b><color=orange> before playing with this feature.You will lose your mythic ranks but you can restore them in this Party Editor.</color>": "<b><color=yellow>ХОРОШО ПОДУМАЙТЕ</color></b><color=orange> прежде чем играть с этой функцией.Вы потеряете свои Мифические ранги, но вы можете восстановить их в редакторе Партии.</color>", + "<b><color=yellow>WARNING</color></b><color=orange> The Respec UI is </color><b><color=yellow>Non Interruptable</color></b><color=orange> please save before using</color>": "<b><color=yellow>ВНИМАНИЕ</color></b><color=orange> Это ГПИ Перераспределения (сброса). </color><b><color=yellow>Не прерываемый.</color></b><color=orange> Пожалуйста, сохранитесь перед использованием</color>", + "<b><color=yellow>WARNING</color></b><color=orange> these features are </color><b><color=yellow>EXPERIMENTAL</color></b><color=orange> and uses unreleased and likely buggy code.</color>": "<b><color=yellow>ВНИМАНИЕ</color></b><color=orange> Эти функции являются </color><b><color=yellow>ЭКСПЕРИМЕНТАЛЬНЫМИ</color></b><color=orange> и используют неизданный и, вероятно, глючный код.</color>", + "<b><color=yellow>WARNING</color></b><color=orange> this tool can both miraculously fix your broken progression or it can break it even further. Save and back up your save before using.</color>": "<b><color=yellow>ВНИМАНИЕ</color></b><color=orange> Этот инструмент может как, чудесным образом, исправить вашу нарушенную прогрессию, так и нарушить ее еще больше. Сохранитесь и создайте резервную копию вашего сохранения перед использованием.</color>", + "<color=#00A000FF>Uncommon</color>": "<color=#00A000FF>Необыч</color>", + "<color=#00ff00ff>Allow </color><color=#C060F0FF>any gender</color> <color=#00ff00ff>for any </color><color=#FF4040FF>R</color><color=orange>o</color><color=yellow>m</color><color=#00ff00ff>a</color><color=cyan>n</color><color=#8080FFFF>c</color><color=#C060F0FF>e</color>": "<color=#00ff00ff>Позволяет </color><color=#C060F0FF>любому полу</color> <color=#00ff00ff>любого </color><color=#FF4040FF>Ро</color><color=orange>м</color><color=yellow>а</color><color=#00ff00ff>н</color><color=cyan>си</color><color=#8080FFFF>т</color><color=#C060F0FF>ь</color>", + "<color=#00ff00ff>Allow </color><color=#C060F0FF>multiple</color><color=#00ff00ff> romances at the same time</color>": "<color=#00ff00ff>Позволяет вести </color><color=#C060F0FF>множество</color><color=#00ff00ff> романов в одно и то же время</color>", + "<color=#00ff00ff>Some responses such as comments about your mythic powers will always choose the first one by default. This allows the game to mix things up a bit</color><b><color=yellow>\nWarning:</color></b><color=orange> this will introduce randomness to NPC responses to you in general and may lead to surprising or even wild outcomes</color>": "<color=#00ff00ff>В некоторых ответах, таких как комментарии о ваших Мифических способностях, по умолчанию всегда будет выбран первый вариант. Это позволит игре немного запутать ход событий</color><b><color=yellow>\nWarning:</color></b><color=orange> это привнесет случайность в ответы НПС на ваши запросы и может привести к неожиданным или даже диким результатам</color>", + "<color=#00ff00ff>Tells the game to reset the in game UI.</color><color=yellow> Warning</color><color=orange> Using this in dialog or the book will dismiss that dialog which may break progress so use with care</color>": "<color=#00ff00ff>Команда для сброса настроек ГПИ.</color><color=yellow> Внимание</color><color=orange> Использование этого параметра в диалоге или книге приведет к закрытию этого диалога, что может нарушить ход игрового прогресса, поэтому используйте его с осторожностью</color>", + "<color=#00ff00ff>This allows rechosing the first arcehtype. Also makes Companion respec start from level 0.</color>": "<color=#00ff00ff>Это позволяет повторно выбрать первый архетип. Также настройка Компаньонов начинается с уровня 0.</color>", + "<color=#00ff00ff>This allows you to loot items that are locked such as items carried by certain NPCs and items locked on your characters</color><b><color=yellow>\nWARNING: </color></b><color=yellow>This may affect story progression (e.g. your purple knife)</color>": "<color=#00ff00ff>Это позволит вам получить заблокированные предметы, например, те, что носят важные НПС, и предметы, заблокированные на ваших персонажах</color><b><color=yellow>\nВНИМАНИЕ: </color></b><color=yellow>Это может повлиять на развитие сюжета (например, ваш фиолетовый нож).</color>", + "<color=#00ff00ff>This makes loot function like Diablo or Borderlands. <color=orange>Note: turning this off requires you to save and reload for it to take effect.</color></color>": "<color=#00ff00ff>Это позволит луту работать как в Diablo или Borderlands. <color=orange>Примечание: чтобы отключить, вам необходимо сохраниться и перезагрузиться, чтобы это вступило в силу.</color></color>", + "<color=#1030E0FF>Rare</color>": "<color=#1030E0FF>Редк</color>", + "<color=#6030F0FF>Epic</color>": "<color=#6030F0FF>Эпик</color>", + "<color=#60FFFFFF>Primal</color>": "<color=#60FFFFFF>Изнач</color>", + "<color=#808080FF>Trash</color>": "<color=#808080FF>Мусор</color>", + "<color=#98761FFF>Notable</color>": "<color=#98761FFF>Особый</color>", + "<color=#A00000FF>Godly</color>": "<color=#A00000FF>Божест</color>", + "<color=#A000A0FF>Mythic</color>": "<color=#A000A0FF>Мифик</color>", + "<color=#C04040E0>♥♥ </color><b>Love is Free</b><color=#C04040E0> ♥♥</color>": "<color=#C04040E0>♥♥ </color><b>Любовь для всех</b><color=#C04040E0> ♥♥</color>", + "<color=#D0D0D0FF>None</color>": "<color=#D0D0D0FF>Ничего</color>", + "<color=#D8D8D8A0>Common</color>": "<color=#D8D8D8A0>Обычн</color>", + "<color=#E67821E0>Legendary</color>": "<color=#E67821E0>Легенд</color>", + "<color=cyan> + Click</color> To Transfer Entire Stack": "<color=cyan> + Click</color> Для передачи всего стека", + "<color=cyan> + Click</color> To Use Items In Inventory": "<color=cyan> + Click</color> Чтобы использовать товары из инвентаря", + "<color=orange>Experimental</color> Allow remote companions to make comments on dialog you are having.": "<color=orange>Экспериментально</color> Разрешить компаньонам не в партии комментировать диалог, который вы ведете.", + "<color=orange>Note:</color><color=#00ff00ff> For cutscenes and some situations the rotation keys are disabled so you have to hold down Mouse3 to drag in order to get rotation</color>": "<color=orange>Примечание:</color><color=#00ff00ff> Для роликов и некоторых ситуаций клавиши поворота отключены, поэтому вам придется удерживать нажатой Mouse3 чтобы вращать</color>", + "<color=orange>Sandal says '</color><b><color=cyan>Enchantment'</color></b>": "<color=orange>Сэндал сказал '</color><b><color=cyan>Enchantment'</color></b>", + ">": ">", + "Abilities": "Способности", + "Ability Rsrc": "Способность Rsrc", + "Achievements": "Достижения", + "Achievements not available until you load a save.": "Достижения недоступны до тех пор, пока вы не загрузите сохранение.", + "Active": "Активные", + "Add": "Дать", + "Addendum: ": "Приложение: ", + "Adds a search field to Load/Save screen (in game only)": "Добавить поле поиска на экран загрузки/сохранения(только в игре)", + "Adjust ": "Регулировать ", + "Adjust based on Level": "Регулирует зависимость от уровня", + "Adjust Navigator Insight by the following amount:": "Регулирует Интуицию Навигатора на следующую величину:", + "Adjust Profit Factor by the following amount:": "Регулирует Фактор Прибыли на следующую величину:", + "Adjust Reputation by the following amount:": "Регулирует репутацию на следующую величину:", + "Adjust Resource Factor by the following amount:": "Регулирует коэффициент ресурса на следующую величину: ", + "Adjust Scrap by the following amount:": "Регулирует количество металлолома на следующее количество:", + "Adjusts costs of hiring mercenaries at the Pathfinder vendor": "Регулирует затраты на наем наемников у продавца", + "Adjusts the movement speed of your party in area maps": "Регулирует скорость передвижения вашей Партии на картах локации", + "Adjusts the movement speed of your party on world maps": "Регулирует скорость передвижения вашей Партии на картах мира", + "All": "Все", + "All Attacks Hit": "Все атаки без промаха", + "All Experience": "Весь опыт", + "All Hits Critical": "Все попадания критические", + "All Units": "Все юниты", + "Allow ": "Решимость", + "Allow Achievements While Using Mods": "Разрешить достижения при использовании модов", + "Allow Equipment Change During Combat": "Разрешить смену экипировки во время боя", + "Allow Item Use From Inventory During Combat": "Разрешить испол. предметов инвентаря во время боя", + "Allow Looting Of Locked Items": "Разрешить разграбление заблок. предметов", + "Allow Mass Loot to steal from living NPCs": "Разрешить массово красть лут у живых НПС", + "Alternate Time Scale": "Альтерн. шкала времени", + "Always Roll 1": "Всегда бросайте 1", + "Always Roll 100": "Всегда бросайте 100", + "Always Roll 50": "Всегда бросайте 50", + "Answer": "Решение", + "Apply Bug Fixes": "Применить исправление ошибок", + "Archetypes": "Архетипы", + "Area Entry": "Выход из локации", + "Areas": "Локации", + "Armor": "Броня", + "ArmourAft": "Броня-кормовая часть", + "ArmourFore": "Броня-носовая часть", + "ArmourPort": "Броня-левый борт", + "ArmourStarboard": "Броня-правый борт", + "AttackOfOpportunityCount": "Атака по возможности учитывается", + "Attributes": "Атрибуты", + "Auto Follow While Holding Camera Follow Key": "Автом. слежение при удерживании клавиши слежения за камерой", + "Auto load Last Save on launch": "Автоматическая загрузка последнего сохранения при запуске", + "Bag of Tricks": "Коробка с трюками", + "Be a <b><color=#C04040E0>Murder</color></b><color=orange> Hobo</color>": "Мгновенное <b><color=#C04040E0>Убийство</color></b>", + "Belt": "Пояс", + "Bind": "Привязать", + "Bindable hot key to swap between main and alternate time scale multipliers": "Привязываемая горячая клавиша для переключения между основным и альтернативным множителями шкалы времени", + "Blueprint": "Схема", + "bp": "Боевые/бонус очки", + "Brains": "Голова", + "Buff Duration": "Продолжительность бафа", + "Buff Like A Goddess": "Бафы как у Божества", + "Buffs": "Бафы", + "buffs to add to list": "Бафы для добавления в список", + "Camera": "Камера", + "Can Move Through": "Можно переместиться", + "Can Start": "Можно начать", + "Careers": "Карьера", + "Categories": "Категории", + "Change Colony Stat": "Изменить статистику Колонии", + "Change Party": "Изменить Партию", + "Change Portrait": "Изменить портрет", + "Change the party without advancing time (good to bind)": "Изменить Партию без прокруки времени (для привязки)", + "Change Voice": "Изменить голос", + "Change Weather": "Изменение погоды", + "Changing your gender may cause visual glitches": "Изменение вашего пола может вызвать визуальные сбои", + "Character Level": "Уровень персонажа", + "Cheats": "Читы", + "Check for Glyph Support": "Проверить поддержку глифов", + "CheckBluff": "Проверка Блефа", + "CheckDiplomacy": "Проверка Дипломатии", + "CheckIntimidate": "Проверка Напора", + "Classes": "Классы", + "Click On Equip Slots To Filter Inventory": "Нажмите на Слоты Экипировки, чтобы отфильтровать инвентарь", + "Collapse All": "Свернуть все", + "Collating...": "Сравнение...", + "Colonies": "Колонии", + "Colonize": "Колония", + "ColonyFoundation": "Основа Колонии", + "Color Item Names": "Названия цветных элементов", + "Colossal": "Колоссальный", + "Combat": "Бой", + "Common": "Получить", + "Common Buffs": "Получить баф", + "Common Tweaks": "Получить хитрость", + "Companion Cost": "Стоимость компаньона", + "Complete": "Завершить", + "Complete (Final)": "Завершить (Финальный)", + "Components": "Компоненты", + "condition: ": "состояние: ", + "conflicts": "конфликты", + "Containers": "Контейнеры", + "Contentment": "Довольство", + "Copying...": "Копирование...", + "Corruption": "Еретик", + "Create & Level Up": "Создание & Повышение уровня", + "Crew": "Экипаж", + "Cruiser_2x4": "Крейсер_2x4", + "Ctrl + Mouse3 Drag To Adjust Camera Elevation": "Ctrl + Mouse3, чтобы отрегулировать угол наклона Камеры", + "Cues": "Подсказки", + "Current Amount": "Текущая сумма", + "Current Blueprint Portrait": "Текущий мини.портрет", + "Current Culture": "Текущий язык", + "Current Navigator Insight": "Текущая интуиция Навигатора", + "Current Profit Factor": "Текущий Фактор Прибыли", + "Current Reputation": "Текущая Репутация", + "Current Scrap": "Текущий металлолом", + "Current Veil Thickness": "Текущая толщина Завесы", + "Custom": "Пользоват.", + "custom exceptions": "пользовательские исключения", + "Cut Scenes": "Вырезанные сцены", + "Debug": "Отладка", + "default exceptions": "исключения по умолчанию", + "Description": "Описание", + "Dialog": "Диалог", + "Dialog & NPCs": "Диалоги & НПС", + "Dialog Alignment": "Диалоги на Мировоззрения", + "Dialog Conditions": "Условия диалога", + "Dialog Results": "Результаты диалога", + "Dice Rolls": "Броски кубиков", + "Diminutive": "Миниатюрный", + "Disable Attacks Of Opportunity": "Отключить атаки по возможности", + "Disable Dialog Restrictions (Everything, Experimental)": "Отключить ограничения на диалоги (Все, Экспериментально)", + "Disable Dialog Restrictions (SoulMark)": "Отключить ограничения на диалоги (Метка души)", + "Disable end turn HotKey": "Отключить горячую клавишу завершения поворота", + "Disable Random Encounters in Warp": "Отключить случайные столкновения в Варпе", + "Disable Voice Over and Barks for this character": "Отключить закадровый голос и лай для текущего персонажа", + "Display guids in most tooltips, use shift + left click on items/abilities to copy guid to clipboard": "Отображать идентификатор в большинстве всплывающих подсказок, используйте shift + щелчек левой кнопкой мыши по элементам/возможностям, чтобы скопировать идентификатор в буфер обмена", + "Display risky options": "Отображать рискованные варианты", + "Don't use AP (except abilities which consume all AP) During Turn": "Не затрачивать ОД (кроме способностей, которые потребляют все ОД) во время хода", + "Don't wait for keypress when loading saves": "Никакого нажатия клавиш при загрузке сохранений", + "Draws dialog choices that you have previously selected in smaller type": "Отображает варианты диалога, которые вы ранее выбрали, более мелким шрифтом", + "Drusians": "Друзианцы", + "Duration Multiplier": "Множитель продолжительности", + "Edit Resources": "Редактировать ресурсы", + "Efficiency": "Эффективность", + "elements": "элементы", + "Elements": "Элементы", + "Empowered": "Наделить", + "Enable Game Development Mode": "Включить режим Разработчика игры", + "Enable Loading with Blueprint Errors": "Включить загрузку с ошибками чертежей", + "Enable Mouse3 Dragging To Aim The Camera": "Включить Mouse3 для наведения камеры", + "Enable Rotate on all maps and cutscenes": "Включить поворот на всех картах и кат-сценах", + "Enable Search as you type for Browsers (needs restart)": "Включите поиск по мере ввода текста в графе поиска (требуется перезагрузка)", + "Enable Teleport Keys": "Включить ключи телепортации", + "Enable Zoom on all maps and cutscenes": "Включить масштабирование на всех картах и кат-сценах", + "Ench. Type": "Зачар. Тип", + "Ench. Types": "Зачар. Типы", + "Enchantment": "Зачарование", + "Enemies": "Враги", + "Enemy HP Multiplier": "Множитель ХП противника", + "EnemyShip": "Вражеский корабль", + "Enhanced Load/Save": "Улучшенная загрузка/сохранение", + "Enhanced Map View": "Улучшенный вид карты", + "Enhanced UI": "Улучшение ГПИ", + "Enterint a Trap Zone while having Traps disabled will prevent that Trap from triggering even if you deactivate this option in the future": "Предотвратит срабатывание ловушки, в то время как ловушка отключена, даже если вы отключите эту опцию в будущем", + "Equip (rarity)": "Экипировка (редкое)", + "Equipment": "Экипировка", + "Error": "Ошибка", + "Etude Status: ": "Статус Этюда: ", + "Etudes": "Этюды", + "Evasion": "Уклонение", + "Everyone": "Все", + "Exceptions to Buff Duration Multiplier (Advanced; will cause blueprints to load)": "Убрать множитель продолжительности действия бафа(дополнительно; приведет к перезагрузке схемы)", + "Expand All": "Развернуть все", + "Expand Answers For Conditional Responses": "Раскрыть последствия для условных ответов", + "Expand Dialog To Include Remote Companions": "Открыть диалог включения оставленных компаньонов", + "Experience": "Опыт", + "Experience Multipliers": "Множители опыта", + "Explorators": "Эксплораторы", + "Export": "Экспорт", + "Export current locale to file": "Экспорт текущего языкового стандарта в файл", + "Faction Selector": "Выбор фактов", + "Facts": "Факты", + "Failure to load saves that reference custom portraits": "Сбой при загрузке сохраняет ссылки на пользов. портреты", + "Faith": "Догматик", + "Features": "Способности", + "Feet": "Ноги", + "Female": "Женщина", + "Field Of View": "Поле зрения", + "Fine": "Отлично", + "Finish": "Завершить", + "Fix Camera": "Починить камеру", + "Fix Incorrect Main Character": "Исправление неполадок с главным персонажем", + "Flags": "Флаги", + "Flags Only": "Только флаги", + "Fog of War Range": "Диапазон Тумана Войны", + "FoV (Cut Scenes)": "Угол обзора (Вырезанные сцены)", + "Free Camera": "Свободная камера", + "Friendly": "Друзья", + "Frigate_1x2": "Фрегат_1x2", + "Gain ": "Начислить ", + "Game Time Scale": "Шкала игрового времени", + "Gargantuan": "Гигантский", + "Gender": "Гендер", + "Generate Comment Translation Table": "Сгенерировать таблицу перевода комментариев", + "Give All Items": "Дать все предметы", + "Giving characters voices besides the default ones is untested.": "Предоставление персонажам голосов, помимо голосов по умолчанию, не тестировалось.", + "Glasses": "Очки", + "Gloves": "Перчатки", + "Go To Global Map": "Перейти к Глобальной карте", + "Go to page: ": "Перейти на стр: ", + "Go!": "Перейти!", + "Gold": "Золото", + "good for larger group or to reduce enemies": "Подходит для большой Партии или для уменьшения количества Врагов", + "good for party": "Подходит для Партии", + "GrandCruiser_3x6": "Гранд Крузер_3x6", + "Head": "Голова", + "Hide": "Скрыть", + "Hide Completed": "Скрыть завершенные", + "Highlight Copyable Scrolls": "Выделить копируемые свитки", + "Highlight Hidden Objects": "Выделить скрытые объекты", + "HitPoints": "ХП", + "Hold down shift during launch to bypass": "Удерживайте нажатой клавишу Shift во время запуска, чтобы обойти", + "Hope": "Схизматик", + "Huge": "Огромный", + "Identify All": "Идентифицировать все", + "If ticked, this will <b><color=#C04040E0>MURDER</color></b><color=#00ff00ff> all who dare to engage you!</color>": "Если отмечен галочкой, то позволит <b><color=#C04040E0>Убить</color></b><color=#00ff00ff> всех, кто вступит с вами в бой</color>", + "If you have a save that uses custom portraits and don't toggle this your game will crash when starting": "Если у вас есть сохранение, в котором используются пользовательские портреты, и вы не переключили это - ваша игра вылетит при запуске", + "If you tick this you can click on equipment slots to filter the inventory for items that fit in it.\nFor more <color=orange>Enhanced Inventory</color> and <color=orange>Spellbook</color> check out the <b><color=orange>Loot & Spellbook Tab</color></b>": "Если вы установите этот флажок, вы можете нажать на слоты для снаряжения, чтобы отфильтровать инвентарь на предмет экипировки, которую в него можно поместить.\nПрежде чем использовать <color=orange>Расширенный инвентарь</color> и <color=orange>Книга заклинаний</color> ознакомьтесь с <b><color=orange>Лут & Книга заклинаний</color></b>", + "Ignore Ability Requirement - AOE Overlap": "Игнорировать требования к способностям - Перекрытие AOE", + "Ignore Ability Requirement - Line of Sight": "Игнорировать требования к способностям - Прямая видимость", + "Ignore Ability Requirement - Max Range": "Игнорировать требования к способностям - Максимальный диапазон", + "Ignore Ability Requirement - Min Range": "Игнорировать требования к способностям - Минимальный диапазон", + "Ignore all Requirements for Abilities": "Игнорировать требования к способностям Мировоззрения", + "Ignore Class Restrictions": "Игнорировать ограничения класса", + "ignore Equipment Restrictions": "Игнорировать ограничения на экипировку", + "Ignore Required Class Levels": "Игнорировать требуемые уровни класса", + "Ignore Required Stat Values": "Игнорировать требуемые значения характеристик", + "Ignore Talent Prerequisites": "Игнорировать требования для таланта", + "Import": "Импорт", + "Import/Export allows you to save and add a list of items to a file based on the type (e.g. Weapon.json). These files live in a new ToyBox folder that in the same folder that contains your saved games ": "Импорт/экспорт позволяет сохранять и добавлять список элементов в файл в зависимости от типа (например, Weapon.json). Эти файлы хранятся в новой папке ToyBox, которая находится в той же папке, что и ваши сохраненные игры ", + "In Fog Of War ": "В Тумане Войны ", + "Include Former Companions": "Включая бывших компаньонов", + "Increase Carry Capacity": "Увеличить грузоподъемность", + "Increase Carry Capacity (Party Only)": "Увеличить грузоподъемность (только Партии)", + "increment": "Увеличить на:", + "Inertia": "Инерция", + "Infinite Abilities": "Бесконечные способности", + "Infinite Abilities (No cooldowns no cost)": "Бесконечные способности(без перезарядки,без затрат)", + "Infinite Charges On Items": "Бесконечный заряд на предметах", + "Infinite Spell Casts": "Бесконечные заклинания", + "Info": "Инфо", + "In-Game Name": "Игровое имя", + "Initiative": "Инициатива", + "Initiative: Always Roll 1": "Инициатива: Всегда выпадает 1", + "Initiative: Always Roll 10": "Инициатива: Всегда выпадает 10", + "Initiative: Always Roll 5": "Инициатива: Всегда выпадает 5", + "Inspect": "Осмотр", + "Inspect Dialog Controller": "Просмотр управление диалога", + "Inspect Party <color=orange>(for modders)</color>": "Простотр Партии <color=orange>(для моддеров)</color>", + "Inspect Quests and Objectives": "Простотр квестов и заданий", + "Inspecting: ": "Осмотр: ", + "Instant Rest After Combat": "Мгновенный отдых после боя", + "Interesting NPCs in the local area": "Отметить интересных НПС в локации", + "Internal Name": "Внутреннее имя", + "Invert X Axis": "Инвертировать ось X", + "Invert Y Axis": "Инвертировать ось Y", + "Item": "Предмет", + "Jealousy Begone!": "Прочь ревность!", + "Kasballica": "Касбаллика", + "Keyboard:": "Клавиатура:", + "Kill": "Убить", + "Kill All Enemies": "Убить всех врагов", + "Large": "Большой", + "Lets you open up the area's mass loot screen to grab goodies whenever you want. Normally shown only when you exit the area": "Позволяет вам открыть экран массового лута в этом районе, чтобы выхватить лакомства в любое удобное для вас время. Обычно отображается только при выходе из зоны", + "level": "уровень", + "Level": "Уровень", + "Level Increase/Decrease": "Повышение/Понижение уровня", + "Level Up": "Повышение уровня", + "Limit": "Лимит", + "Lobotomize Enemies": "Лоботомированные враги", + "Localization": "Локализация", + "Lock": "Блокировать", + "Log Level": "Регистрация уровня", + "Log ToyBox Keyboard Commands In Game": "Регистация команды клавиатуры ToyBox в игре", + "Loot": "Лут", + "Loot Checklist": "Контрольный список лута", + "Loot Rarity Coloring": "Окраска редкости лута", + "Loot Rarity Filtering": "Фильтр редкости лута", + "Lose ": "Убрать ", + "Main Character": "Основной персонаж", + "Main/Alt Timescale": "Основная/альтернативная шкала времени", + "Make Character AI Controlled": "Сделать персонажа управляемым ИИ", + "Make Controllable": "Сделать управляемым", + "Make game continue to play music on lost focus": "Заставить игру продолжать воспроизводить музыку при потере фокуса", + "Make Puzzle Symbols More Clear": "Сделать символы головоломки более понятными", + "Make Spell/Ability/Item Pop-Ups Wider ": "Расширить всплывающие окна заклинаний/способностей/предметов ", + "Make tutorials not appear if disabled in settings": "Сделайте так, чтобы подсказки не отображались, если они отключены в настройках", + "Makes mouse zoom work for the local map (cities, dungeons, etc). Game restart required if you turn it off": "Позволяет увеличить масштаб мыши для локальной карты (города,подземелья и т.д.). Требуется перезапуск игры, если вы это выключили", + "Male": "Мужчина", + "Mark Interesting NPCs": "Отметить интересных НПС", + "Mark Interesting NPCs on Map": "Отметить интересных НПС на карте", + "Mass Loot": "Массовый лут", + "Matches: ": "Совпадение: ", + "max": "макс", + "Maximize the ModManager window for best ToyBox user experience": "Максимально разверните окно менеджера модов для удобства пользования ToyBox", + "Maximize Window": "Развернуть окно", + "Maximum Rarity To Hide:": "Макс. редкость, которую нужно скрыть:", + "Mechadendrite": "Механодендрит", + "Medium": "Cредний", + "MilitaryRating": "Военный Рейтинг", + "min": "мин", + "Minimum Rarity For Loot Rarity Tags/Colors": "Минимальная редкость для тегов/цветов редкости лута", + "Modify Summons For": "Модификатор призывов", + "Money Earned": "Заработанные деньги", + "Mono Version": "Моно версия", + "Morale": "Моральный дух", + "Mouse:": "Мышь:", + "Movement Speed": "Скорость перемещения", + "N/A": "N/A", + "Name of the new Blueprintportrait: ": "Название нового мини.портрета: ", + "Name of the new Custom Portrait: ": "Название нового пользов.портрета: ", + "Nearby": "Окружение", + "Nearby Distance": "Рядом", + "Neck": "Шея", + "Never Roll 1": "Никогда не выпадает 1", + "Never Roll 100": "Никогда не выпадает 100", + "No Active Dialog": "Нет активного диалога", + "No attack/spell cooldowns": "Блокирует атаки/восстановление заклинаний", + "No Etudes": "Нет Этюдов", + "No Fog Of War": "Нет Тумана Войны", + "No Friendly Fire On AOEs": "Нет Friendly Fire при AOE", + "No Items": "Нет предметов", + "No Loot Available": "Нет лута", + "Non Combat: Take 1": "Вне боя: выпадает 1", + "None": "Ничего", + "NonUsable": "Неиспользуемые", + "Object Highlight Toggle Mode": "Режим переключения выделения объекта", + "Object Highlight Toggle Mode (Out of Combat!)": "Режим переключения выделения объекта (вне боя!)", + "Off": "Выкл", + "Ongoing Events:": "Текущие события:", + "Ongoing Projects:": "Текущие проекты:", + "Only show languages with existing localization files": "Показывать только языки с существующими файлами локализации", + "Open Mass Loot Window": "Открыть окно массового лута", + "Open the Localization Guide": "Открыть руководство по локализации", + "Other": "Другое", + "Other Multipliers": "Другие множители", + "Override AI Control Behaviour": "Переопределить поведение управления ИИ", + "Override for Challenges": "Назначить для испытаний", + "Override for Combat": "Назначить для боя", + "Override for Quests": "Назначить для квестов", + "Override for Skill Checks": "Назначить для проверки навыков", + "Override for Space Combat": "Назначить для Космического боя", + "Override for Traps": "Назначить для ловушек", + "Page % of %": "Страница % от %", + "Page: ": "Стр: ", + "Parameter": "Параметр", + "Party": "Партия", + "Party & Pets": "Партия & Питомцы", + "Party Level ": "Уровень Партии ", + "Perm": "Завивка", + "Pets": "Питомцы", + "Pick none to stop overwriting.": "Выберите «Нет», чтобы прекратить перезапись.", + "Pick size modifier to overwrite default.": "Выберите модификатор размера, чтобы перезаписать значение по умолчанию.", + "Pirates": "Пустотное братство", + "Planets": "Планеты", + "Play": "Игрок", + "Play Example": "Пример игры", + "Prepared Spellslots": "Подготовленные слоты заклинаний", + "Prevent Psychic Phenomena": "Предотвратить психическое явление", + "Prevent Traps from triggering": "Предотвратить срабатывание ловушек", + "Prevent Veil Thickness from changing": "Предотвратить изменение Завесы", + "Preview": "Предв. просмотр", + "Preview Results": "Предв. просмотр результатов", + "Previously Chosen Dialog Is Smaller ": "Ранее выбранный диалог стал меньше ", + "Primary": "Первичный", + "Progression": "Прогресс", + "PsyRating": "Пси-рейтинг", + "Quality of Life": "Качество жизни", + "QuestObj": "Квестобъекты", + "Quests": "Квесты", + "Races": "Расы", + "Raider_1x1": "Рейдер_1x1", + "Random Encounters": "Случайные встречи", + "Randomize NPC Responses To Dialog Choices": "Рандомизируйте ответы НПС в диалоге", + "Rank": "Ранг", + "Rarity: ": "Редкость: ", + "Rating": "Рейтинг", + "rating: ": "рейтинг", + "Ratings": "Рейтинги", + "Reason": "Причина", + "Recruit": "Рекрут", + "Refill consumables in belt slots if in inventory": "Вставлять расходные материалы в ячейки ремня, если они есть в инвентаре", + "Refresh": "Обновление", + "Remote": "Удаленные", + "Remove": "Удалить", + "Remove Buffs": "Удалить бафы", + "Remove Deaths Door": "Удалить Дверь Смерти", + "Reroll Perception": "Повторный бросок на Внимание", + "Reset": "Сброс", + "Reset Interactables": "Сброс взаимодействий", + "Reset UI": "Сброс ГПИ", + "Resolve": "Разрешить", + "Resource": "Ресурс", + "ResourceMiner": "Добытчик ресурсов", + "Resources": "Ресурсы", + "Respec": "Перераспределение", + "Respec from Level 0": "Перераспределение с уровня 0", + "Rest All": "Отдых всех", + "Rest Selected": "Отдых выбраных", + "Restart": "Перезапуск", + "Restore Spells & Skills After Combat": "Восстанавливать заклинания и навыки после боя", + "Ring": "Кольцо", + "Roll With Advantage (take lower roll)": "Бросок с преимуществом (взять меньший результат)", + "Roll With Avantage": "Бросок с преимуществом", + "Roll With Disavantage": "Бросок с недостатком", + "Roll With Disavantage (take higher roll)": "Бросок с недостатком (взять больший результат)", + "Rotation Options": "Варианты вращения", + "RT Specific": "Спец.РТ", + "Save as png": "Сохранить как png", + "Save ID: ": "ID сохранения: ", + "SaveFortitude": "Сохранить стойкость", + "SaveReflex": "Сохранить рефлекс", + "Saves": "Сохранения", + "SaveWill": "Сохранить волю", + "Search": "Поиск", + "Search Descriptions": "Описания поиска", + "Search Limit": "Ограничение поиска", + "Search 'n Pick": "Ищи и Выбирай", + "Secondary": "Вторичный", + "Sector Map Points": "Точки на карте", + "Security": "Безопасность", + "Selected Chars": "Выбранные символы", + "Set": "Сет", + "Set Veil Thickness to the following amount:": "Установите для толщины Завесы следующее значение:", + "Settings": "Настройки", + "Shield": "Щит", + "Ship": "Корабль", + "ShipVendor": "Имперский флот", + "Shirt": "Рубашки", + "Shoulders": "Плечи", + "Show": "Показать", + "Show a list of NPCs that may have quest objectives or other interesting features <color=yellow>(Warning: Spoilers)</color>": "Показать список НПС, у которых могут быть цели квеста или другие интересные функции <color=yellow>(Внимание: Спойлеры)</color>", + "Show Acronyms in Spell/Ability/Item Pop-Ups": "Показывать сокращения в окнах заклинаний/способностей/предметов", + "Show All": "Показать все", + "Show Blueprint Portrait Picker": "Показать мини.портреты", + "Show Blueprint Voice Picker": "Показать план выбора голоса", + "Show Character filter choices": "Показывать фильтры выбора персонажа", + "Show Comments (some in Russian)": "Показать комментарии (некоторые на русском языке)", + "Show Custom Portrait Picker": "Показать окно выбора пользов.портрета", + "Show Display & Internal Names": "Показать отображаемые и внутренние имена", + "Show Everything When Leaving Map": "Показать все при выходе с карты", + "Show Friendly": "Показать у друзей", + "Show GameID": "Показать GameID", + "Show GUIDs": "Показать GUIDs", + "Show Internal Names": "Показать внутренние имена", + "Show Rarity Tags": "Показать теги редкости", + "Show reasons you can not equip an item in tooltips": "Показать причины, по которым вы не можете надеть экипировку, во всплывающих подсказках", + "Show Tree": "Показать дерево", + "Show Unavailable Responses": "Показать недоступные ответы", + "Show Unrevealed Steps": "Показать невыбранные шаги", + "Size": "Размер", + "Skill Checks: Take 1": "Проверка навыков: Выбросить 1", + "Skill Checks: Take 25": "Проверка навыков: Выбросить 25", + "Skill Checks: Take 50": "Проверка навыков: Выбросить 50", + "SkillAthletics": "Навык Атлетика", + "SkillAwareness": "Навык Наблюдательность", + "SkillCarouse": "Навык Устойчивость", + "SkillCoercion": "Навык Напор", + "SkillCommerce": "Навык Коммерция", + "SkillDemolition": "Навык Взрывотехника", + "SkillLogic": "Навык Логика", + "SkillLoreImperium": "Навык Знание Империума", + "SkillLoreWarp": "Навык Знание Варпа", + "SkillLoreXenos": "Навык Знание Ксеносов", + "SkillMedicae": "Навык Медицина", + "SkillPersuasion": "Навык Убеждение", + "SkillTechUse": "Навык Техника", + "Small": "Маленький", + "Some items might be invisible until looted": "Некоторые предметы могут быть невидимы до тех пор, пока их не разграбят", + "Some responses such as comments about your mythic powers will always choose the first one by default. This will show a copy of the answer and the condition for each possible response that an NPC might make to you based on": "В некоторых ответах, таких как комментарии о ваших Мифических способностях, по умолчанию всегда будет выбран первый вариант. Здесь будет показана копия ответа и условие для каждого возможного ответа, который НПС может дать вам на этой основе", + "Sort By Count": "Сортировать по количеству", + "Soul Marks": "Метки души", + "source: ": "исток", + "Spawn": "Спавн", + "Speed": "Скорость", + "Speeds up or slows down the entire game (movement, animation, everything)": "Ускоряет или замедляет всю игру (движение,анимацию,все остальное)", + "Spellbooks": "Книги заклинаний", + "Spells": "Заклинания", + "Spontaneous Spells Per Day": "Спонтанные заклинания в день", + "StarshipAmmo": "Боеприпасы звездолета", + "StarshipArmorPlating": "Броня звездолета", + "StarshipAugerArray": "Оже-массив звездолета", + "StarshipBridge": "Ликвидировать звездолет", + "StarshipGellerFieldDevice": "Полевое устройство звездолета Геллера", + "StarshipItem": "Предметы звездолета", + "StarshipLifeSustainer": "Система жизнеобеспечения звездолета", + "StarshipPlasmaDrives": "Плазменные двигатели звездолета", + "Starships": "Звездолеты", + "StarshipVoidShieldGenerator": "Генератор пустотного щита звездолета", + "StarshipWarpDrives": "Варп-двигатели звездолета", + "StarshipWeapon": "Оружие звездолета", + "Start": "Начать", + "Stats": "Статистика", + "status: ": "статус: ", + "Steal from living NPCs": "Красть у живых НПС", + "Strip HTML (colors) from Logs Tab in Unity Mod Manager": "Удалить HTML (цвета) из вкладки Logs в Unity Mod Manager", + "Strip HTML (colors) from Native Console": "Удалить HTML (цвета) из нативной консоли", + "Summons": "Призывы", + "System Map": "Системная карта", + "Teleport": "Телепорт", + "Teleport Party To You": "Телепортирует Партию к вам", + "The following skill check adjustments apply only out of combat": "Следующие настройки проверки навыков применяются только вне боя", + "This <b>incredibly dangerous</b> setting overrides the default behavior of failing to load saves depending on missing blueprint mods. This desperate action can potentially enable you to recover your saved game, though you'll have to respec at minimum.": "", + "This allows characters you control to move through the selected category of units during combat": "Это позволит персонажам, которыми вы управляете, перемещаться мимо выбранной категории юнитов во время боя", + "This allows you to adjust pitch (Camera Tilt) by holding down Mouse3 (which previously just rotated)": "Это позволит вам регулировать высоту тона (наклон камеры), удерживая нажатой Mouse3 (который ранее просто вращался)", + "This also includes companions who left the party such as Wenduag if you picked Lann": "Сюда также входят компаньоны, которые покинули Партию, такие как Вендуаг, если вы выбрали Ланна", + "This directly changes your character level but will not change exp or adjust any features associated with your character. To do a normal level up use +1 Lvl above. This gets recalculated when you reload the game. ": "Это напрямую изменит уровень вашего персонажа, но не изменит опыт или не скорректирует какие-либо особенности, связанные с вашим персонажем. Чтобы повысить обычный уровень, используйте +1 уровень выше. Это значение пересчитывается при перезагрузке игры. ", + "This hides map pins of loot containers containing at most the selected rarity. <color=orange>Note: Changing settings requires reopening the map.</color>": "Это скроет значки контейнеров лута с карты, не содержащих выбранного раритета. <color=orange>Примечание: Для изменения настроек требуется повторное открытие карты.</color>", + "This is intended for you to be able to enjoy the game while using mods that enhance your quality of life. Please be mindful of the player community and avoid using this mod to trivialize earning prestige achievements like Sadistic Gamer. The author is in discussion with Owlcat about reducing the scope of achievement blocking to just these. Let's show them that we as players can mod and cheat responsibly.": "Это предназначено для того, чтобы вы могли наслаждаться игрой, используя моды, которые улучшают качество вашей жизни. Пожалуйста, будьте внимательны к сообществу игроков и избегайте использования этого мода для упрощения получения престижных достижений, словно геймер-садист. Автор договорился с Owlcat о сокращении масштабов блокировки достижений только до этого уровня. Давайте покажем им, что мы, игроки, можем ответственно модифицировать и жульничать.", + "This is the current voice!": "Это нынешний голос!", + "This makes loot function like Diablo or Borderlands. <color=orange>Note: turning this off requires you to save and reload for it to take effect.</color>": "Это позволит луту функционировать подобно Diablo или Borderlands. <color=orange>Примечание: чтобы отключить это, вам необходимо сохраниться и перезагрузить игру, чтобы настройка вступила в силу.</color>", + "This resets all the skill check rolls for all interactable objects in the area": "Это сбросит все броски проверки навыков для всех объектов взаимодействия в этой локации", + "This sets your experience to match the current value of character level": "Это устанавливает ваш опыт в соответствии с текущим значением уровня персонажа.", + "This turns on the developer console which lets you access cheat commands, shows a FPS window (hide with F11), etc.\n<b><color=yellow>Warning: </color></b><color=orange>You may need to restart the game for this to fully take effect</color>": "Это включит консоль Разработчика, которая позволит вам получить доступ к командам читов, покажет окно FPS (скрыть с помощью F11), и т.д..\n<b><color=yellow>Предупреждение: </color></b><color=orange>Возможно, вам потребуется перезапустить игру, чтобы это полностью вступило в силу</color>", + "This will change the color of NPC names on the highlight makers and change the color map markers to indicate that they have interesting or conditional interactions": "", + "This will change the color of NPC names on the highlike makers and change the color map markers to indicate that they have interesting or conditional interactions": "Это изменит цвет имен НПС и изменит цвет маркеров на карте, чтобы указать, что у них есть интересные или условные взаимодействия", + "Tiny": "Крошечный", + "To permanently remove these modded blueprint dependencies, load the damaged saved game, change areas, and then save the game. You can then respec any characters that were impacted.": "", + "ToyBox Archeologists can tag confusing puzzle pieces with green numbers in the game world and for inventory tool tips it will show text like this: <b><color=yellow>[PuzzlePiece Green3x1]</color></b><b><color=orange>\nNOTE: </color></b><color=orange>Needs game restart to take efect</color>": "Археологи ToyBox могут помечать запутанные кусочки головоломки зелеными цифрами в игровом мире, а во всплывающих подсказках инвентаря будет отображаться текст, подобный этому: <b><color=yellow>[Кусочек головоломки зеленого цвета x 1]</color></b><b><color=orange>\nПримечание: </color></b><color=orange>Для достижения эффекта требуется перезапуск игры</color>", + "ToyBox can patch some critical bugs in Rogue Trader Beta, including the following:": "ToyBox может исправить некоторые критические ошибки в бета-версии Rogue Trader, включая следующие:", + "ToyBox has limited functionality from the main menu": "ToyBox имеет ограниченную функциональность в главном меню", + "Trace": "Отслеживание", + "Travel Speed": "Скорость перемещения на карте", + "TurretRadius": "Радиус турели", + "TurretRating": "Рейтинг турели", + "Tweaks": "Хитрости", + "Units": "Юниты", + "Units CR": "Рейтинг опасности юнитов", + "Unlimited Actions During Turn": "Неограниченное количество действий во время хода", + "Unlimited Stacking of Modifiers (Stat/AC/Hit/Damage/Etc)": "Неограниченное количество модификаторов (Стат/КБ/Попадание/Урон/и т.д.)", + "Unlock": "Разблокировать", + "Unstart": "Отменить запуск", + "Usable": "Используемые", + "Vendor Buy Price": "Цена покупки у продавца", + "Vendor Sell Price": "Цена продажи продавцом", + "Visual Character Size Multiplier": "Множитель визуального размера", + "WarhammerAgility": "Ловкость", + "WarhammerBallisticSkill": "Дальний бой", + "WarhammerFellowship": "Общительность", + "WarhammerInitialAPBlue": "Начальный APBlue", + "WarhammerInitialAPYellow": "APЖелтый", + "WarhammerIntelligence": "Интеллект", + "WarhammerPerception": "Восприятие", + "WarhammerStrength": "Сила", + "WarhammerToughness": "Выносливость", + "WarhammerWeaponSkill": "Ближний бой", + "WarhammerWillpower": "Стойкость", + "Warning": "Внимание:", + "Weapon": "Оружие", + "Weapons": "Оружия", + "When enabled and you hold down the camera follow key (usually f) the camera will keep following the unit until you release it": "При включении и удерживании нажатой клавиши слежения за камерой (обычно f) камера будет продолжать следовать за юнитом до тех пор, пока вы не отпустите ее", + "When loading a game this will go right into the game without having to 'Press any key to continue'": "При загрузке игры это уберет 'Нажмите любую кнопку для продолжения", + "When ticked this shows ToyBox commands in the combat log which is helpful for you to know when you used the shortcut": "Если этот флажок установлен, в журнале боевых действий отображаются команды ToyBox, чтобы знать, когда вы использовали флажки", + "Whole Party": "Вся Партия", + "Whole Team Moves Same Speed": "Вся Партия движется с одинаковой скоростью", + "will be used for editing ": "будет использоваться для редактирования ", + "Wrist": "Запястье", + "You": "Вы", + "You can enable hot keys to teleport members of your party to your mouse cursor on Area or the Global Map": "Вы можете включить горячие клавиши для телепортации членов вашей Партии к указателю мыши на Локации или Глобальной карте", + "Body parts global offsets": "Глобальное смещение частей тела", + "Body parts global scales": "Глобальные масштабы частей тела", + "Body parts local sizes": "Локальные размеры частей тела", + "Equipment elements offsets": "Смещение элементов экипировки", + "Equipment elements sizes": "Размеры элементов экипировки", + "OF_positionZ": "Смещение персонажа по оси Z", + "OF_shouldersX": "Смещение плеч по оси X", + "OF_shouldersZ": "Смещение плеч по оси Z", + "OF_upper_armsX": "Смещение верха рук по оси X", + "OF_upper_legsX": "Смещение верха ног по оси X", + "SC_pelvisX": "Масштаб первой кости тела по X", + "SC_pelvisY": "Масштаб первой кости тела по Y", + "SC_pelvisZ": "Масштаб первой кости тела по Z", + "SC_neck": "Масштаб шеи", + "SC_shoulders": "Масштаб плеч", + "SC_upper_arms": "Масштаб верхних рук", + "SC_fore_arms": "Масштаб предплечий", + "SC_upper_torso": "Масштаб верхней части торса", + "SC_middle_torso": "Масштаб средней части торса", + "SC_lower_torso": "Масштаб нижней части торса", + "SC_stomach": "Масштаб живота", + "SC_upper_legs": "Масштаб верхних ног", + "SC_lower_legs": "Масштаб нижних ног", + "SC_foots": "Масштаб стоп", + "SC_toes": "Масштаб пальцев ног", + "SZ_head": "Размер головы", + "SZ_neck": "Размер шеи", + "SZ_shoulders": "Размер плеч", + "SZ_upper_arms": "Размер верхних рук", + "SZ_fore_arms": "Размер предплечий", + "SZ_hands": "Размер кистей", + "SZ_upper_torso": "Размер верхней части торса", + "SZ_middle_torso": "Размер средней части торса", + "SZ_lower_torso": "Размер нижней части торса", + "SZ_stomach": "Размер живота", + "SZ_pelvis": "Размер таза", + "SZ_upper_legs": "Размер верхних ног", + "SZ_middle_legs": "Размер средних ног", + "SZ_lower_legs": "Размер нижних ног", + "SZ_foots": "Размер стоп", + "SZ_toes": "Размер пальцев ног", + "IO_cloakX": "Смещение только плаща по оси X", + "IO_cloakY": "Смещение только плаща по оси Y", + "IO_cloakZ": "Смещение только плаща по оси Z", + "IO_backpackX": "Смещение рюкзака и плаща по оси Y", + "IO_backpackY": "Смещение рюкзака и плаща по оси Y", + "IO_backpackZ": "Смещение рюкзака и плаща по оси Z", + "IO_weapon_in_holstersRX": "Смещение правой кобуры по оси X", + "IO_weapon_in_holstersRY": "Смещение правой кобуры по оси Y", + "IO_weapon_in_holstersRZ": "Смещение правой кобуры по оси Z", + "IO_weapon_in_holstersLX": "Смещение левой кобуры по оси X", + "IO_weapon_in_holstersLY": "Смещение левой кобуры по оси Y", + "IO_weapon_in_holstersLZ": "Смещение левой кобуры по оси Z", + "IS_cloak": "Размер только плаща", + "IS_backpack": "Размер рюкзака и плаща", + "IS_weapon_in_hand": "Размер оружия в руках", + "IS_weapon_in_holsters": "Размер оружия в кобурах", + "IS_back_weapon_R": "Размер оружия за спиной справа", + "IS_back_weapon_L": "Размер оружия за спиной слева" + } +} \ No newline at end of file diff --git a/ToyBox/Localization/tr.json b/ToyBox/Localization/tr.json new file mode 100644 index 000000000..70c37fb1f --- /dev/null +++ b/ToyBox/Localization/tr.json @@ -0,0 +1,688 @@ +{ + "LanguageCode": "tr", + "Version": "1.5.13", + "Contributors": "cankutahya.com.tr?", + "HomePage": "https://github.com/cabarius/ToyBox/", + "Strings": { + " (This setting is per-save)": " (Bu ayar her kayıt dosyası için ayrıdır)", + " Allow remote companions to make comments on dialog you are having.": " Uzaktaki yoldaşların, yaptığınız diyaloglar hakkında yorum yapmasına izin ver.", + " by the following amount:": " şu miktarda:", + " meters": " metre", + "<": "<", + "<b><color=magenta>Note </color></b><b><color=orange>ToyBox was designed to offer the best user experience at widths of 1920 or higher. Please consider increasing your resolution up of at least 1920x1080 (ideally 4k) and go to Unity Mod Manager 'Settings' tab to change the mod window width to at least 1920. Increasing the UI scale is nice too when running at 4k</color></b>": "<b><color=magenta>Not </color></b><b><color=orange>ToyBox, en iyi kullanıcı deneyimini 1920 veya daha yüksek genişliklerde sunmak üzere tasarlanmıştır. Lütfen çözünürlüğünüzü en az 1920x1080'e (tercihen 4k) yükseltmeyi ve Unity Mod Manager 'Ayarlar' sekmesine giderek mod penceresi genişliğini en az 1920 olarak değiştirmeyi düşünün. 4k'da çalışırken arayüz ölçeğini artırmak da güzeldir.</color></b>", + "<b><color=magenta>Warning: </color></b><color=orange>This is an experimental preview of ToyBox (<color=cyan>Sh0dan</color>) for Rogue Trader Beta.</color><b><color=yellow> Save early and often.\r\n</color></b><b><color=magenta>Note:</color></b><color=orange> Not all features are functional at this time. The ToyBox team is working hard to get as much working as fast as possible</color>": "<b><color=magenta>Uyarı: </color></b><color=orange>Bu, Rogue Trader Beta için ToyBox'ın (<color=cyan>Sh0dan</color>) deneysel bir önizlemesidir.</color><b><color=yellow> Sık sık ve önceden kaydedin.\r\n</color></b><b><color=magenta>Not:</color></b><color=orange> Şu anda tüm özellikler işlevsel değildir. ToyBox ekibi, mümkün olduğunca çok özelliği en hızlı şekilde çalıştırmak için çok çalışıyor.</color>", + "<b><color=magenta>Warning: </color></b><color=orange>This is an experimental preview of ToyBox for Rogue Trader.</color><b><color=yellow> Save early and often.\r\n</color></b><b><color=magenta>Note:</color></b><color=orange> Not all features are functional at this time. If you notice a feature doesn't work please report that on GitHub or in the modding channels on the Owlcat Discord.</color>": "<b><color=magenta>Uyarı: </color></b><color=orange>Bu, Rogue Trader için ToyBox'ın deneysel bir önizlemesidir.</color><b><color=yellow> Sık sık ve önceden kaydedin.\r\n</color></b><b><color=magenta>Not:</color></b><color=orange> Şu anda tüm özellikler işlevsel değildir. Bir özelliğin çalışmadığını fark ederseniz, lütfen bunu GitHub'da veya Owlcat Discord'daki modlama kanallarında bildirin.</color>", + "<b><color=orange>Achievements</color></b>": "<b><color=orange>Başarımlar</color></b>", + "<b><color=orange>Bag of Tricks</color></b>": "<b><color=orange>Hile Çantası</color></b>", + "<b><color=orange>Blueprints</color></b> loading: ": "<b><color=orange>Taslaklar</color></b> yükleniyor: ", + "<b><color=orange>character(s) can be </color></b><color=cyan>Recruited</color><color=#00ff00ff>. This allows you to add non party NPCs to your party as if they were mercenaries</color>": "<b><color=orange>karakter(ler) </color></b><color=cyan>İşe Alınabilir</color><color=#00ff00ff>. Bu, parti dışı NPC'leri paralı askerlermiş gibi partinize eklemenizi sağlar</color>", + "<b><color=orange>character(s) can be </color></b><color=cyan>Respecced</color><color=#00ff00ff>. Pressing Respec will close the mod window and take you to character level up</color>": "<b><color=orange>karakter(ler) </color></b><color=cyan>Yeniden Yeteneklendirilebilir</color><color=#00ff00ff>. Yeniden Yeteneklendir'e basmak mod penceresini kapatacak ve sizi karakter seviye atlama ekranına götürecektir</color>", + "<b><color=orange>Colonies</color></b>": "<b><color=orange>Koloniler</color></b>", + "<b><color=orange>Dialog & NPCs</color></b>": "<b><color=orange>Diyalog & NPC'ler</color></b>", + "<b><color=orange>Enchantment</color></b>": "<b><color=orange>Efsunlama</color></b>", + "<b><color=orange>Enhanced UI</color></b>": "<b><color=orange>Gelişmiş Arayüz</color></b>", + "<b><color=orange>Etudes</color></b>": "<b><color=orange>Etütler</color></b>", + "<b><color=orange>Level Up</color></b>": "<b><color=orange>Seviye Atlama</color></b>", + "<b><color=orange>Loot</color></b>": "<b><color=orange>Ganimet</color></b>", + "<b><color=orange>Note</color></b><color=#00ff00ff> this is a new and exciting feature that allows you to see for the first time the structure and some basic relationships of </color><b><color=cyan>Etudes</color></b><color=#00ff00ff> and other </color><b><color=cyan>Elements</color></b><color=#00ff00ff> that control the progression of your game story. Etudes are hierarchical in structure and additionally contain a set of </color><b><color=cyan>Elements</color></b><color=#00ff00ff> that can both conditions to check and actions to execute when the etude is started. As you browe you will notice there is a disclosure triangle next to the name which will show the children of the Etude. Etudes that have </color><b><color=cyan>Elements</color></b><color=#00ff00ff> will offer a second disclosure triangle next to the status that will show them to you.</color>": "<b><color=orange>Not</color></b><color=#00ff00ff> bu, oyun hikayenizin ilerlemesini kontrol eden </color><b><color=cyan>Etütlerin</color></b><color=#00ff00ff> ve diğer </color><b><color=cyan>Elementlerin</color></b><color=#00ff00ff> yapısını ve bazı temel ilişkilerini ilk kez görmenizi sağlayan yeni ve heyecan verici bir özelliktir. Etütler hiyerarşik bir yapıdadır ve ek olarak, etüt başladığında hem kontrol edilecek koşulları hem de yürütülecek eylemleri içerebilen bir dizi </color><b><color=cyan>Element</color></b><color=#00ff00ff> içerir. Göz atarken, ismin yanında Etüdün alt öğelerini gösterecek bir açılır üçgen olduğunu fark edeceksiniz. </color><b><color=cyan>Elementlere</color></b><color=#00ff00ff> sahip olan Etütler, durumun yanında size bunları gösterecek ikinci bir açılır üçgen sunacaktır.</color>", + "<b><color=orange>Party</color></b>": "<b><color=orange>Parti</color></b>", + "<b><color=orange>Quests</color></b>": "<b><color=orange>Görevler</color></b>", + "<b><color=orange>Saves</color></b>": "<b><color=orange>Kayıtlar</color></b>", + "<b><color=orange>Search 'n Pick</color></b>": "<b><color=orange>Ara ve Seç</color></b>", + "<b><color=orange>Settings</color></b>": "<b><color=orange>Ayarlar</color></b>", + "<b><color=yellow>BACK UP</color></b><color=orange> before playing with this feature.You will lose your mythic ranks but you can restore them in this Party Editor.</color>": "<b><color=yellow>YEDEK ALIN</color></b><color=orange> bu özellikle oynamadan önce. Mitik rütbelerinizi kaybedeceksiniz ancak bunları bu Parti Düzenleyicisinde geri yükleyebilirsiniz.</color>", + "<b><color=yellow>WARNING</color></b><color=orange> The Respec UI is </color><b><color=yellow>Non Interruptable</color></b><color=orange> please save before using</color>": "<b><color=yellow>UYARI</color></b><color=orange> Yeniden Yeteneklendirme Arayüzü </color><b><color=yellow>Kesintiye Uğratılamaz</color></b><color=orange> lütfen kullanmadan önce kaydedin</color>", + "<b><color=yellow>WARNING</color></b><color=orange> these features are </color><b><color=yellow>EXPERIMENTAL</color></b><color=orange> and uses unreleased and likely buggy code.</color>": "<b><color=yellow>UYARI</color></b><color=orange> bu özellikler </color><b><color=yellow>DENEYSELDİR</color></b><color=orange> ve yayınlanmamış ve muhtemelen hatalı kod kullanır.</color>", + "<b><color=yellow>WARNING</color></b><color=orange> this tool can both miraculously fix your broken progression or it can break it even further. Save and back up your save before using.</color>": "<b><color=yellow>UYARI</color></b><color=orange> bu araç bozuk ilerlemenizi mucizevi bir şekilde düzeltebileceği gibi, daha da bozabilir. Kullanmadan önce kaydedin ve yedeğini alın.</color>", + "<color=#00A000FF>Uncommon</color>": "<color=#00A000FF>Sıradışı</color>", + "<color=#00ff00ff>Allow </color><color=#C060F0FF>any gender</color> <color=#00ff00ff>for any </color><color=#FF4040FF>R</color><color=orange>o</color><color=yellow>m</color><color=#00ff00ff>a</color><color=cyan>n</color><color=#8080FFFF>c</color><color=#C060F0FF>e</color>": "<color=#00ff00ff>Herhangi bir </color><color=#C060F0FF>cinsiyete</color> <color=#00ff00ff>herhangi bir </color><color=#FF4040FF>R</color><color=orange>o</color><color=yellow>m</color><color=#00ff00ff>a</color><color=cyan>n</color><color=#8080FFFF>t</color><color=#C060F0FF>i</color><color=#00ff00ff>zm için</color> <color=#00ff00ff>izin ver</color>", + "<color=#00ff00ff>Allow </color><color=#C060F0FF>multiple</color><color=#00ff00ff> romances at the same time</color>": "<color=#00ff00ff>Aynı anda </color><color=#C060F0FF>birden fazla</color><color=#00ff00ff> romantizme izin ver</color>", + "<color=#00ff00ff>Some responses such as comments about your mythic powers will always choose the first one by default. This allows the game to mix things up a bit</color><b><color=yellow>\nWarning:</color></b><color=orange> this will introduce randomness to NPC responses to you in general and may lead to surprising or even wild outcomes</color>": "<color=#00ff00ff>Mitik güçleriniz hakkındaki yorumlar gibi bazı yanıtlar varsayılan olarak her zaman ilkini seçecektir. Bu, oyunun işleri biraz karıştırmasına olanak tanır</color><b><color=yellow>\nUyarı:</color></b><color=orange> bu, genel olarak NPC'lerin size verdiği yanıtlara rastgelelik katacak ve şaşırtıcı ve hatta çılgın sonuçlara yol açabilecektir</color>", + "<color=#00ff00ff>Tells the game to reset the in game UI.</color><color=yellow> Warning</color><color=orange> Using this in dialog or the book will dismiss that dialog which may break progress so use with care</color>": "<color=#00ff00ff>Oyuna oyun içi arayüzü sıfırlamasını söyler.</color><color=yellow> Uyarı</color><color=orange> Bunu diyalogda veya kitapta kullanmak o diyaloğu kapatır, bu da ilerlemeyi bozabilir, bu yüzden dikkatli kullanın</color>", + "<color=#00ff00ff>This allows rechosing the first arcehtype. Also makes Companion respec start from level 0.</color>": "<color=#00ff00ff>Bu, ilk arketipin yeniden seçilmesine olanak tanır. Ayrıca Yoldaş yeniden yeteneklendirmesinin 0. seviyeden başlamasını sağlar.</color>", + "<color=#00ff00ff>This allows you to loot items that are locked such as items carried by certain NPCs and items locked on your characters</color><b><color=yellow>\nWARNING: </color></b><color=yellow>This may affect story progression (e.g. your purple knife)</color>": "<color=#00ff00ff>Bu, belirli NPC'ler tarafından taşınan eşyalar ve karakterlerinizde kilitli olan eşyalar gibi kilitli eşyaları yağmalamanızı sağlar</color><b><color=yellow>\nUYARI: </color></b><color=yellow>Bu, hikaye ilerlemesini etkileyebilir (ör. mor bıçağınız)</color>", + "<color=#00ff00ff>This makes loot function like Diablo or Borderlands. <color=orange>Note: turning this off requires you to save and reload for it to take effect.</color></color>": "<color=#00ff00ff>Bu, ganimetin Diablo veya Borderlands gibi çalışmasını sağlar. <color=orange>Not: bunu kapatmak, etkili olması için kaydetmenizi ve yeniden yüklemenizi gerektirir.</color></color>", + "<color=#1030E0FF>Rare</color>": "<color=#1030E0FF>Nadir</color>", + "<color=#6030F0FF>Epic</color>": "<color=#6030F0FF>Epik</color>", + "<color=#60FFFFFF>Primal</color>": "<color=#60FFFFFF>İlkel</color>", + "<color=#808080FF>Trash</color>": "<color=#808080FF>Çöp</color>", + "<color=#98761FFF>Notable</color>": "<color=#98761FFF>Kayda Değer</color>", + "<color=#A00000FF>Godly</color>": "<color=#A00000FF>Tanrısal</color>", + "<color=#A000A0FF>Mythic</color>": "<color=#A000A0FF>Mitik</color>", + "<color=#C04040E0>♥♥ </color><b>Love is Free</b><color=#C04040E0> ♥♥</color>": "<color=#C04040E0>♥♥ </color><b>Aşk Bedava</b><color=#C04040E0> ♥♥</color>", + "<color=#D0D0D0FF>None</color>": "<color=#D0D0D0FF>Yok</color>", + "<color=#D8D8D8A0>Common</color>": "<color=#D8D8D8A0>Yaygın</color>", + "<color=#E67821E0>Legendary</color>": "<color=#E67821E0>Efsanevi</color>", + "<color=cyan> + Click</color> To Transfer Entire Stack": "<color=cyan> + Tıkla</color> Tüm Yığını Aktarmak İçin", + "<color=cyan> + Click</color> To Use Items In Inventory": "<color=cyan> + Tıkla</color> Envanterdeki Eşyaları Kullanmak İçin", + "<color=orange>Experimental</color> Allow remote companions to make comments on dialog you are having.": "<color=orange>Deneysel</color> Uzaktaki yoldaşların, yaptığınız diyaloglar hakkında yorum yapmasına izin ver.", + "<color=orange>Note:</color><color=#00ff00ff> For cutscenes and some situations the rotation keys are disabled so you have to hold down Mouse3 to drag in order to get rotation</color>": "<color=orange>Not:</color><color=#00ff00ff> Ara sahneler ve bazı durumlar için döndürme tuşları devre dışı bırakılmıştır, bu nedenle döndürmek için Fare3'e basılı tutarak sürüklemeniz gerekir</color>", + "<color=orange>Sandal says '</color><b><color=cyan>Enchantment'</color></b>": "<color=orange>Sandal '</color><b><color=cyan>Efsunlama'</color></b><color=orange> diyor</color>", + ">": ">", + "Abilities": "Yetenekler", + "Ability Rsrc": "Yetenek Kyn.", + "Achievements": "Başarımlar", + "Achievements not available until you load a save.": "Başarımlar, bir kayıt yükleyene kadar mevcut değil.", + "Active": "Aktif", + "Add": "Ekle", + "Addendum: ": "Ek: ", + "Adds a search field to Load/Save screen (in game only)": "Yükleme/Kaydetme ekranına bir arama alanı ekler (sadece oyun içinde)", + "Adjust ": "Ayarla ", + "Adjust based on Level": "Seviyeye göre ayarla", + "Adjust Navigator Insight by the following amount:": "Seyyah İçgörüsünü şu miktarda ayarla:", + "Adjust Profit Factor by the following amount:": "Kâr Faktörünü şu miktarda ayarla:", + "Adjust Reputation by the following amount:": "İtibar Puanını şu miktarda ayarla:", + "Adjust Resource Factor by the following amount:": "Kaynak Faktörünü şu miktarda ayarla:", + "Adjust Scrap by the following amount:": "Hurda miktarını şu miktarda ayarla:", + "Adjusts costs of hiring mercenaries at the Pathfinder vendor": "Pathfinder satıcısında paralı asker kiralamanın maliyetlerini ayarlar", + "Adjusts the movement speed of your party in area maps": "Partinizin alan haritalarındaki hareket hızını ayarlar", + "Adjusts the movement speed of your party on world maps": "Partinizin dünya haritalarındaki hareket hızını ayarlar", + "All": "Tümü", + "All Attacks Hit": "Tüm Saldırılar İsabet Eder", + "All Experience": "Tüm Deneyim", + "All Hits Critical": "Tüm Vuruşlar Kritik", + "All Units": "Tüm Birimler", + "Allow ": "İzin Ver ", + "Allow Achievements While Using Mods": "Mod Kullanırken Başarımlara İzin Ver", + "Allow Equipment Change During Combat": "Savaş Sırasında Ekipman Değişimine İzin Ver", + "Allow Item Use From Inventory During Combat": "Savaş Sırasında Envanterden Eşya Kullanımına İzin Ver", + "Allow Looting Of Locked Items": "Kilitli Eşyaları Yağmalamaya İzin Ver", + "Allow Mass Loot to steal from living NPCs": "Toplu Yağmanın yaşayan NPC'lerden çalmasına izin ver", + "Alternate Time Scale": "Alternatif Zaman Ölçeği", + "Always Roll 1": "Her Zaman 1 At", + "Always Roll 100": "Her Zaman 100 At", + "Always Roll 50": "Her Zaman 50 At", + "Answer": "Cevap", + "Apply Bug Fixes": "Hata Düzeltmelerini Uygula", + "Archetypes": "Arketipler", + "Area Entry": "Alan Girişi", + "Areas": "Alanlar", + "Armor": "Zırh", + "ArmourAft": "ZırhKıç", + "ArmourFore": "ZırhPruva", + "ArmourPort": "Zırhİskele", + "ArmourStarboard": "ZırhSancak", + "AttackOfOpportunityCount": "FırsatSaldırısıSayısı", + "Attributes": "Nitelikler", + "Auto Follow While Holding Camera Follow Key": "Kamera Takip Tuşuna Basılıyken Otomatik Takip Et", + "Auto load Last Save on launch": "Başlatıldığında Son Kaydı Otomatik Yükle", + "Bag of Tricks": "Hile Çantası", + "Be a <b><color=#C04040E0>Murder</color></b><color=orange> Hobo</color>": "Bir <b><color=#C04040E0>Cinayet</color></b><color=orange> Hobosu</color> Ol", + "Belt": "Kemer", + "Bind": "Ata", + "Bindable hot key to swap between main and alternate time scale multipliers": "Ana ve alternatif zaman ölçeği çarpanları arasında geçiş yapmak için atanabilir kısayol tuşu", + "Blueprint": "Taslak", + "bp": "tslk", + "Brains": "Beyinler", + "Buff Duration": "Güçlendirme Süresi", + "Buff Like A Goddess": "Bir Tanrıça Gibi Güçlendir", + "Buffs": "Güçlendirmeler", + "buffs to add to list": "listeye eklenecek güçlendirmeler", + "Camera": "Kamera", + "Can Move Through": "İçinden Geçebilir", + "Can Start": "Başlayabilir", + "Careers": "Kariyerler", + "Categories": "Kategoriler", + "Change Colony Stat": "Koloni İstatistiğini Değiştir", + "Change Party": "Partiyi Değiştir", + "Change Portrait": "Portreyi Değiştir", + "Change the party without advancing time (good to bind)": "Zamanı ilerletmeden partiyi değiştir (atamak için iyi)", + "Change Voice": "Sesi Değiştir", + "Change Weather": "Havayı Değiştir", + "Changing your gender may cause visual glitches": "Cinsiyetinizi değiştirmek görsel hatalara neden olabilir", + "Character Level": "Karakter Seviyesi", + "Cheats": "Hileler", + "Check for Glyph Support": "Glif Desteğini Kontrol Et", + "CheckBluff": "BlöfTesti", + "CheckDiplomacy": "DiplomasiTesti", + "CheckIntimidate": "GözdağıTesti", + "Classes": "Sınıflar", + "Click On Equip Slots To Filter Inventory": "Envanteri Filtrelemek İçin Ekipman Yuvalarına Tıklayın", + "Collapse All": "Tümünü Daralt", + "Collating...": "Harmanlanıyor...", + "Colonies": "Koloniler", + "Colonize": "Kolonileştir", + "ColonyFoundation": "KoloniKuruluşu", + "Color Item Names": "Eşya İsimlerini Renklendir", + "Colossal": "Devasa", + "Combat": "Savaş", + "Common": "Yaygın", + "Common Buffs": "Yaygın Güçlendirmeler", + "Common Tweaks": "Yaygın İnce Ayarlar", + "Companion Cost": "Yoldaş Maliyeti", + "Complete": "Tamamla", + "Complete (Final)": "Tamamla (Nihai)", + "Components": "Bileşenler", + "condition: ": "koşul: ", + "conflicts": "çakışmalar", + "Containers": "Konteynerler", + "Contentment": "Memnuniyet", + "Copying...": "Kopyalanıyor...", + "Corruption": "Yozlaşma", + "Create & Level Up": "Oluştur & Seviye Atlat", + "Crew": "Mürettebat", + "Cruiser_2x4": "Kruvazör_2x4", + "Ctrl + Mouse3 Drag To Adjust Camera Elevation": "Kamera Yüksekliğini Ayarlamak için Ctrl + Fare3 Sürükle", + "Cues": "İpuçları", + "Current Amount": "Mevcut Miktar", + "Current Blueprint Portrait": "Mevcut Taslak Portresi", + "Current Culture": "Mevcut Kültür", + "Current Navigator Insight": "Mevcut Seyyah İçgörüsü", + "Current Profit Factor": "Mevcut Kâr Faktörü", + "Current Reputation": "Mevcut İtibar", + "Current Scrap": "Mevcut Hurda", + "Current Veil Thickness": "Mevcut Peçe Kalınlığı", + "Custom": "Özel", + "custom exceptions": "özel istisnalar", + "Cut Scenes": "Ara Sahneler", + "Debug": "Hata Ayıklama", + "default exceptions": "varsayılan istisnalar", + "Description": "Açıklama", + "Dialog": "Diyalog", + "Dialog & NPCs": "Diyalog & NPC'ler", + "Dialog Alignment": "Diyalog Hizalaması", + "Dialog Conditions": "Diyalog Koşulları", + "Dialog Results": "Diyalog Sonuçları", + "Dice Rolls": "Zar Atışları", + "Diminutive": "Küçücük", + "Disable Attacks Of Opportunity": "Fırsat Saldırılarını Devre Dışı Bırak", + "Disable Dialog Restrictions (Everything, Experimental)": "Diyalog Kısıtlamalarını Devre Dışı Bırak (Her Şey, Deneysel)", + "Disable Dialog Restrictions (SoulMark)": "Diyalog Kısıtlamalarını Devre Dışı Bırak (Ruhİzi)", + "Disable end turn HotKey": "Tur sonu Kısayol Tuşunu devre dışı bırak", + "Disable Random Encounters in Warp": "Warp'ta Rastgele Karşılaşmaları Devre Dışı Bırak", + "Disable Voice Over and Barks for this character": "Bu karakter için Seslendirmeyi ve Seslenmeleri Devre Dışı Bırak", + "Display guids in most tooltips, use shift + left click on items/abilities to copy guid to clipboard": "Çoğu ipucunda guid'leri göster, guid'yi panoya kopyalamak için eşyalara/yeteneklere shift + sol tıklama kullanın", + "Display risky options": "Riskli seçenekleri göster", + "Don't use AP (except abilities which consume all AP) During Turn": "Tur Sırasında AP kullanma (tüm AP'yi tüketen yetenekler hariç)", + "Don't wait for keypress when loading saves": "Kayıtları yüklerken tuşa basmayı bekleme", + "Draws dialog choices that you have previously selected in smaller type": "Daha önce seçtiğiniz diyalog seçeneklerini daha küçük harflerle çizer", + "Drusians": "Drusianlar", + "Duration Multiplier": "Süre Çarpanı", + "Edit Resources": "Kaynakları Düzenle", + "Efficiency": "Verimlilik", + "elements": "elementler", + "Elements": "Elementler", + "Empowered": "Güçlendirilmiş", + "Enable Game Development Mode": "Oyun Geliştirme Modunu Etkinleştir", + "Enable Loading with Blueprint Errors": "Taslak Hatalarıyla Yüklemeyi Etkinleştir", + "Enable Mouse3 Dragging To Aim The Camera": "Kamerayı Hedeflemek İçin Fare3 Sürüklemeyi Etkinleştir", + "Enable Rotate on all maps and cutscenes": "Tüm haritalarda ve ara sahnelerde döndürmeyi etkinleştir", + "Enable Search as you type for Browsers (needs restart)": "Tarayıcılar için yazdıkça aramayı etkinleştir (yeniden başlatma gerekir)", + "Enable Teleport Keys": "Işınlanma Tuşlarını Etkinleştir", + "Enable Zoom on all maps and cutscenes": "Tüm haritalarda ve ara sahnelerde yakınlaştırmayı etkinleştir", + "Ench. Type": "Efsun Tipi", + "Ench. Types": "Efsun Tipleri", + "Enchantment": "Efsunlama", + "Enemies": "Düşmanlar", + "Enemy HP Multiplier": "Düşman Can Çarpanı", + "EnemyShip": "DüşmanGemisi", + "Enhanced Load/Save": "Gelişmiş Yükle/Kaydet", + "Enhanced Map View": "Gelişmiş Harita Görünümü", + "Enhanced UI": "Gelişmiş Arayüz", + "Enterint a Trap Zone while having Traps disabled will prevent that Trap from triggering even if you deactivate this option in the future": "Tuzaklar devre dışıyken bir Tuzak Bölgesine girmek, gelecekte bu seçeneği devre dışı bıraksanız bile o Tuzağın tetiklenmesini önleyecektir", + "Equip (rarity)": "Kuşan (nadir)", + "Equipment": "Ekipman", + "Error": "Hata", + "Etude Status: ": "Etüt Durumu: ", + "Etudes": "Etütler", + "Evasion": "Kaçınma", + "Everyone": "Herkes", + "Exceptions to Buff Duration Multiplier (Advanced; will cause blueprints to load)": "Güçlendirme Süresi Çarpanı İstisnaları (Gelişmiş; taslakların yüklenmesine neden olur)", + "Expand All": "Tümünü Genişlet", + "Expand Answers For Conditional Responses": "Koşullu Yanıtlar İçin Cevapları Genişlet", + "Expand Dialog To Include Remote Companions": "Diyaloğu Uzak Yoldaşları İçerecek Şekilde Genişlet", + "Experience": "Deneyim", + "Experience Multipliers": "Deneyim Çarpanları", + "Explorators": "Kaşifler", + "Export": "Dışa Aktar", + "Export current locale to file": "Mevcut yereli dosyaya aktar", + "Faction Selector": "Fraksiyon Seçici", + "Facts": "Gerçekler", + "Failure to load saves that reference custom portraits": "Özel portrelere referans veren kayıtların yüklenememesi", + "Faith": "İnanç", + "Features": "Özellikler", + "Feet": "Ayaklar", + "Female": "Kadın", + "Field Of View": "Görüş Alanı", + "Fine": "İyi", + "Finish": "Bitir", + "Fix Camera": "Kamerayı Düzelt", + "Fix Incorrect Main Character": "Yanlış Ana Karakteri Düzelt", + "Flags": "Bayraklar", + "Flags Only": "Sadece Bayraklar", + "Fog of War Range": "Savaş Sisi Menzili", + "FoV (Cut Scenes)": "GA (Ara Sahneler)", + "Free Camera": "Serbest Kamera", + "Friendly": "Dost", + "Frigate_1x2": "Fırkateyn_1x2", + "Gain ": "Kazan ", + "Game Time Scale": "Oyun Zaman Ölçeği", + "Gargantuan": "Kocaman", + "Gender": "Cinsiyet", + "Generate Comment Translation Table": "Yorum Çeviri Tablosu Oluştur", + "Give All Items": "Tüm Eşyaları Ver", + "Giving characters voices besides the default ones is untested.": "Karakterlere varsayılanların dışında sesler vermek test edilmemiştir.", + "Glasses": "Gözlük", + "Gloves": "Eldiven", + "Go To Global Map": "Küresel Haritaya Git", + "Go to page: ": "Sayfaya git: ", + "Go!": "Git!", + "Gold": "Altın", + "good for larger group or to reduce enemies": "daha büyük gruplar için veya düşmanları azaltmak için iyi", + "good for party": "parti için iyi", + "GrandCruiser_3x6": "BüyükKruvazör_3x6", + "Head": "Kafa", + "Hide": "Gizle", + "Hide Completed": "Tamamlananları Gizle", + "Highlight Copyable Scrolls": "Kopyalanabilir Parşömenleri Vurgula", + "Highlight Hidden Objects": "Gizli Nesneleri Vurgula", + "HitPoints": "CanPuanı", + "Hold down shift during launch to bypass": "Atlamak için başlatma sırasında shift tuşunu basılı tutun", + "Hope": "Umut", + "Huge": "Dev", + "Identify All": "Tümünü Tanımla", + "If ticked, this will <b><color=#C04040E0>MURDER</color></b><color=#00ff00ff> all who dare to engage you!</color>": "İşaretlenirse, bu sizinle çatışmaya cüret eden herkesi <b><color=#C04040E0>KATLEDECEKTİR</color></b><color=#00ff00ff>!</color>", + "If you have a save that uses custom portraits and don't toggle this your game will crash when starting": "Özel portreler kullanan bir kaydınız varsa ve bunu açmazsanız oyununuz başlarken çökecektir", + "If you tick this you can click on equipment slots to filter the inventory for items that fit in it.\nFor more <color=orange>Enhanced Inventory</color> and <color=orange>Spellbook</color> check out the <b><color=orange>Loot & Spellbook Tab</color></b>": "Bunu işaretlerseniz, envanteri ona uyan eşyalar için filtrelemek üzere ekipman yuvalarına tıklayabilirsiniz.\nDaha fazla <color=orange>Gelişmiş Envanter</color> ve <color=orange>Büyü Kitabı</color> için <b><color=orange>Ganimet & Büyü Kitabı Sekmesine</color></b> göz atın", + "Ignore Ability Requirement - AOE Overlap": "Yetenek Gereksinimini Yoksay - Alan Etkisi Örtüşmesi", + "Ignore Ability Requirement - Line of Sight": "Yetenek Gereksinimini Yoksay - Görüş Hattı", + "Ignore Ability Requirement - Max Range": "Yetenek Gereksinimini Yoksay - Maksimum Menzil", + "Ignore Ability Requirement - Min Range": "Yetenek Gereksinimini Yoksay - Minimum Menzil", + "Ignore all Requirements for Abilities": "Yetenekler için tüm Gereksinimleri Yoksay", + "Ignore Class Restrictions": "Sınıf Kısıtlamalarını Yoksay", + "ignore Equipment Restrictions": "Ekipman Kısıtlamalarını yoksay", + "Ignore Required Class Levels": "Gerekli Sınıf Seviyelerini Yoksay", + "Ignore Required Stat Values": "Gerekli İstatistik Değerlerini Yoksay", + "Ignore Talent Prerequisites": "Yetenek Önkoşullarını Yoksay", + "Import": "İçe Aktar", + "Import/Export allows you to save and add a list of items to a file based on the type (e.g. Weapon.json). These files live in a new ToyBox folder that in the same folder that contains your saved games ": "İçe/Dışa Aktarma, türe göre bir öğe listesini bir dosyaya kaydetmenize ve eklemenize olanak tanır (ör. Weapon.json). Bu dosyalar, kayıtlı oyunlarınızı içeren aynı klasörde yeni bir ToyBox klasöründe bulunur ", + "In Fog Of War ": "Savaş Sisinde ", + "Include Former Companions": "Eski Yoldaşları Dahil Et", + "Increase Carry Capacity": "Taşıma Kapasitesini Artır", + "Increase Carry Capacity (Party Only)": "Taşıma Kapasitesini Artır (Sadece Parti)", + "increment": "artış", + "Inertia": "Atalet", + "Infinite Abilities": "Sonsuz Yetenekler", + "Infinite Abilities (No cooldowns no cost)": "Sonsuz Yetenekler (Bekleme süresi yok, maliyet yok)", + "Infinite Charges On Items": "Eşyalarda Sonsuz Yük", + "Infinite Spell Casts": "Sonsuz Büyü Kullanımı", + "Info": "Bilgi", + "In-Game Name": "Oyun İçi İsim", + "Initiative": "İnisiyatif", + "Initiative: Always Roll 1": "İnisiyatif: Her Zaman 1 At", + "Initiative: Always Roll 10": "İnisiyatif: Her Zaman 10 At", + "Initiative: Always Roll 5": "İnisiyatif: Her Zaman 5 At", + "Inspect": "İncele", + "Inspect Dialog Controller": "Diyalog Kontrolcüsünü İncele", + "Inspect Party <color=orange>(for modders)</color>": "Partiyi İncele <color=orange>(modcular için)</color>", + "Inspect Quests and Objectives": "Görevleri ve Hedefleri İncele", + "Inspecting: ": "İnceleniyor: ", + "Instant Rest After Combat": "Savaştan Sonra Anında Dinlen", + "Interesting NPCs in the local area": "Yerel alandaki ilginç NPC'ler", + "Internal Name": "Dahili İsim", + "Invert X Axis": "X Eksenini Ters Çevir", + "Invert Y Axis": "Y Eksenini Ters Çevir", + "Item": "Eşya", + "Jealousy Begone!": "Kıskançlık Defol!", + "Kasballica": "Kasballica", + "Keyboard:": "Klavye:", + "Kill": "Öldür", + "Kill All Enemies": "Tüm Düşmanları Öldür", + "Large": "Büyük", + "Lets you open up the area's mass loot screen to grab goodies whenever you want. Normally shown only when you exit the area": "İstediğiniz zaman güzellikleri kapmak için alanın toplu yağma ekranını açmanızı sağlar. Normalde sadece alandan çıktığınızda gösterilir", + "level": "seviye", + "Level": "Seviye", + "Level Increase/Decrease": "Seviye Artırma/Azaltma", + "Level Up": "Seviye Atla", + "Limit": "Limit", + "Lobotomize Enemies": "Düşmanları Lobotomize Et", + "Localization": "Yerelleştirme", + "Lock": "Kilitle", + "Log Level": "Kayıt Seviyesi", + "Log ToyBox Keyboard Commands In Game": "ToyBox Klavye Komutlarını Oyunda Kaydet", + "Loot": "Ganimet", + "Loot Checklist": "Ganimet Kontrol Listesi", + "Loot Rarity Coloring": "Ganimet Nadirliği Renklendirmesi", + "Loot Rarity Filtering": "Ganimet Nadirliği Filtrelemesi", + "Lose ": "Kaybet ", + "Main Character": "Ana Karakter", + "Main/Alt Timescale": "Ana/Alt Zaman Ölçeği", + "Make Character AI Controlled": "Karakteri Yapay Zeka Kontrollü Yap", + "Make Controllable": "Kontrol Edilebilir Yap", + "Make game continue to play music on lost focus": "Odak kaybedildiğinde oyunun müzik çalmaya devam etmesini sağla", + "Make Puzzle Symbols More Clear": "Bulmaca Sembollerini Daha Net Yap", + "Make Spell/Ability/Item Pop-Ups Wider ": "Büyü/Yetenek/Eşya Açılır Pencerelerini Genişlet ", + "Make tutorials not appear if disabled in settings": "Ayarlarda devre dışı bırakılmışsa eğitimlerin görünmemesini sağla", + "Makes mouse zoom work for the local map (cities, dungeons, etc). Game restart required if you turn it off": "Fare yakınlaştırmasının yerel harita (şehirler, zindanlar vb.) için çalışmasını sağlar. Kapatırsanız oyunun yeniden başlatılması gerekir", + "Male": "Erkek", + "Mark Interesting NPCs": "İlginç NPC'leri İşaretle", + "Mark Interesting NPCs on Map": "Haritada İlginç NPC'leri İşaretle", + "Mass Loot": "Toplu Ganimet", + "Matches: ": "Eşleşenler: ", + "max": "maks", + "Maximize the ModManager window for best ToyBox user experience": "En iyi ToyBox kullanıcı deneyimi için Mod Yöneticisi penceresini en üst düzeye çıkarın", + "Maximize Window": "Pencereyi Büyüt", + "Maximum Rarity To Hide:": "Gizlenecek Maksimum Nadirlik:", + "Mechadendrite": "Mekanikdal", + "Medium": "Orta", + "MilitaryRating": "AskeriDeğer", + "min": "min", + "Minimum Rarity For Loot Rarity Tags/Colors": "Ganimet Nadirliği Etiketleri/Renkleri için Minimum Nadirlik", + "Modify Summons For": "Çağırılanları Şunun İçin Değiştir", + "Money Earned": "Kazanılan Para", + "Mono Version": "Mono Sürümü", + "Morale": "Moral", + "Mouse:": "Fare:", + "Movement Speed": "Hareket Hızı", + "N/A": "N/A", + "Name of the new Blueprintportrait: ": "Yeni Taslakportresinin adı: ", + "Name of the new Custom Portrait: ": "Yeni Özel Portrenin adı: ", + "Nearby": "Yakındaki", + "Nearby Distance": "Yakındaki Mesafe", + "Neck": "Boyun", + "Never Roll 1": "Asla 1 Atma", + "Never Roll 100": "Asla 100 Atma", + "No Active Dialog": "Aktif Diyalog Yok", + "No attack/spell cooldowns": "Saldırı/büyü bekleme süresi yok", + "No Etudes": "Etüt Yok", + "No Fog Of War": "Savaş Sisi Yok", + "No Friendly Fire On AOEs": "Alan Etkilerinde Dost Ateşi Yok", + "No Items": "Eşya Yok", + "No Loot Available": "Mevcut Ganimet Yok", + "Non Combat: Take 1": "Savaş Dışı: 1 Al", + "None": "Yok", + "NonUsable": "Kullanılamaz", + "Object Highlight Toggle Mode": "Nesne Vurgulama Değiştirme Modu", + "Object Highlight Toggle Mode (Out of Combat!)": "Nesne Vurgulama Değiştirme Modu (Savaş Dışı!)", + "Off": "Kapalı", + "Ongoing Events:": "Devam Eden Etkinlikler:", + "Ongoing Projects:": "Devam Eden Projeler:", + "Only show languages with existing localization files": "Yalnızca mevcut yerelleştirme dosyaları olan dilleri göster", + "Open Mass Loot Window": "Toplu Ganimet Penceresini Aç", + "Open the Localization Guide": "Yerelleştirme Kılavuzunu Aç", + "Other": "Diğer", + "Other Multipliers": "Diğer Çarpanlar", + "Override AI Control Behaviour": "Yapay Zeka Kontrol Davranışını Geçersiz Kıl", + "Override for Challenges": "Meydan Okumalar için Geçersiz Kıl", + "Override for Combat": "Savaş için Geçersiz Kıl", + "Override for Quests": "Görevler için Geçersiz Kıl", + "Override for Skill Checks": "Beceri Testleri için Geçersiz Kıl", + "Override for Space Combat": "Uzay Savaşı için Geçersiz Kıl", + "Override for Traps": "Tuzaklar için Geçersiz Kıl", + "Page % of %": "Sayfa % / %", + "Page: ": "Sayfa: ", + "Parameter": "Parametre", + "Party": "Parti", + "Party & Pets": "Parti & Evcil Hayvanlar", + "Party Level ": "Parti Seviyesi ", + "Perm": "Kalıcı", + "Pets": "Evcil Hayvanlar", + "Pick none to stop overwriting.": "Üzerine yazmayı durdurmak için hiçbirini seçme.", + "Pick size modifier to overwrite default.": "Varsayılanın üzerine yazmak için boyut değiştirici seç.", + "Pirates": "Korsanlar", + "Planets": "Gezegenler", + "Play": "Oynat", + "Play Example": "Örneği Oynat", + "Prepared Spellslots": "Hazırlanmış Büyü Yuvaları", + "Prevent Psychic Phenomena": "Psişik Fenomenleri Önle", + "Prevent Traps from triggering": "Tuzakların tetiklenmesini önle", + "Prevent Veil Thickness from changing": "Peçe Kalınlığının değişmesini önle", + "Preview": "Önizleme", + "Preview Results": "Sonuçları Önizle", + "Previously Chosen Dialog Is Smaller ": "Daha Önce Seçilen Diyalog Daha Küçük ", + "Primary": "Birincil", + "Progression": "İlerleme", + "PsyRating": "PsiDeğeri", + "Quality of Life": "Yaşam Kalitesi", + "QuestObj": "GörevHedefi", + "Quests": "Görevler", + "Races": "Irklar", + "Raider_1x1": "Akıncı_1x1", + "Random Encounters": "Rastgele Karşılaşmalar", + "Randomize NPC Responses To Dialog Choices": "Diyalog Seçeneklerine NPC Yanıtlarını Rastgeleleştir", + "Rank": "Rütbe", + "Rarity: ": "Nadirliği: ", + "Rating": "Değerlendirme", + "rating: ": "değerlendirme: ", + "Ratings": "Değerlendirmeler", + "Reason": "Sebep", + "Recruit": "İşe Al", + "Refill consumables in belt slots if in inventory": "Envanterdeyse kemer yuvalarındaki tüketilebilirleri yeniden doldur", + "Refresh": "Yenile", + "Remote": "Uzak", + "Remove": "Kaldır", + "Remove Buffs": "Güçlendirmeleri Kaldır", + "Remove Deaths Door": "Ölümün Kapısını Kaldır", + "Reroll Perception": "Algıyı Yeniden At", + "Reset": "Sıfırla", + "Reset Interactables": "Etkileşimlileri Sıfırla", + "Reset UI": "Arayüzü Sıfırla", + "Resolve": "Kararlılık", + "Resource": "Kaynak", + "ResourceMiner": "KaynakMadencisi", + "Resources": "Kaynaklar", + "Respec": "Yeniden Yeteneklendir", + "Respec from Level 0": "Seviye 0'dan Yeniden Yeteneklendir", + "Rest All": "Tümünü Dinlendir", + "Rest Selected": "Seçileni Dinlendir", + "Restart": "Yeniden Başlat", + "Restore Spells & Skills After Combat": "Savaştan Sonra Büyüleri ve Becerileri Geri Yükle", + "Ring": "Yüzük", + "Roll With Advantage (take lower roll)": "Avantajla At (daha düşük zarı al)", + "Roll With Avantage": "Avantajla At", + "Roll With Disavantage": "Dezavantajla At", + "Roll With Disavantage (take higher roll)": "Dezavantajla At (daha yüksek zarı al)", + "Rotation Options": "Döndürme Seçenekleri", + "RT Specific": "RT'ye Özel", + "Save as png": "png olarak kaydet", + "Save ID: ": "Kayıt ID: ", + "SaveFortitude": "KurtulmaMetanet", + "SaveReflex": "KurtulmaRefleks", + "Saves": "Kayıtlar", + "SaveWill": "Kurtulmaİrade", + "Search": "Ara", + "Search Descriptions": "Açıklamalarda Ara", + "Search Limit": "Arama Limiti", + "Search 'n Pick": "Ara ve Seç", + "Secondary": "İkincil", + "Sector Map Points": "Sektör Harita Noktaları", + "Security": "Güvenlik", + "Selected Chars": "Seçili Karakterler", + "Set": "Ayarla", + "Set Veil Thickness to the following amount:": "Peçe Kalınlığını şu miktara ayarla:", + "Settings": "Ayarlar", + "Shield": "Kalkan", + "Ship": "Gemi", + "ShipVendor": "GemiSatıcısı", + "Shirt": "Gömlek", + "Shoulders": "Omuzlar", + "Show": "Göster", + "Show a list of NPCs that may have quest objectives or other interesting features <color=yellow>(Warning: Spoilers)</color>": "Görev hedefleri veya diğer ilginç özellikleri olabilecek NPC'lerin bir listesini göster <color=yellow>(Uyarı: Spoiler)</color>", + "Show Acronyms in Spell/Ability/Item Pop-Ups": "Büyü/Yetenek/Eşya Açılır Pencerelerinde Kısaltmaları Göster", + "Show All": "Tümünü Göster", + "Show Blueprint Portrait Picker": "Taslak Portre Seçicisini Göster", + "Show Blueprint Voice Picker": "Taslak Ses Seçicisini Göster", + "Show Character filter choices": "Karakter filtresi seçeneklerini göster", + "Show Comments (some in Russian)": "Yorumları Göster (bazıları Rusça)", + "Show Custom Portrait Picker": "Özel Portre Seçicisini Göster", + "Show Display & Internal Names": "Görünen ve Dahili İsimleri Göster", + "Show Everything When Leaving Map": "Haritadan Ayrılırken Her Şeyi Göster", + "Show Friendly": "Dostları Göster", + "Show GameID": "OyunID'sini Göster", + "Show GUIDs": "GUID'leri Göster", + "Show Internal Names": "Dahili İsimleri Göster", + "Show Rarity Tags": "Nadirliği Etiketlerini Göster", + "Show reasons you can not equip an item in tooltips": "İpuçlarında bir eşyayı neden kuşanamadığınızın nedenlerini göster", + "Show Tree": "Ağacı Göster", + "Show Unavailable Responses": "Mevcut Olmayan Yanıtları Göster", + "Show Unrevealed Steps": "Açıklanmamış Adımları Göster", + "Size": "Boyut", + "Skill Checks: Take 1": "Beceri Testleri: 1 Al", + "Skill Checks: Take 25": "Beceri Testleri: 25 Al", + "Skill Checks: Take 50": "Beceri Testleri: 50 Al", + "SkillAthletics": "BeceriAtletizm", + "SkillAwareness": "BeceriFarkındalık", + "SkillCarouse": "BeceriAlemcilik", + "SkillCoercion": "BeceriZorlama", + "SkillCommerce": "BeceriTicaret", + "SkillDemolition": "BeceriYıkım", + "SkillLogic": "BeceriMantık", + "SkillLoreImperium": "Beceriİmparatorlukİrfanı", + "SkillLoreWarp": "BeceriWarpİrfanı", + "SkillLoreXenos": "BeceriYabancılİrfanı", + "SkillMedicae": "BeceriTıbbiye", + "SkillPersuasion": "Beceriİkna", + "SkillTechUse": "BeceriTeknolojiKullanımı", + "Small": "Küçük", + "Some items might be invisible until looted": "Bazı eşyalar yağmalanana kadar görünmez olabilir", + "Some responses such as comments about your mythic powers will always choose the first one by default. This will show a copy of the answer and the condition for each possible response that an NPC might make to you based on": "Mitik güçleriniz hakkındaki yorumlar gibi bazı yanıtlar varsayılan olarak her zaman ilkini seçecektir. Bu, bir NPC'nin size verebileceği her olası yanıt için cevabın bir kopyasını ve koşulunu gösterecektir", + "Sort By Count": "Sayıya Göre Sırala", + "Soul Marks": "Ruh İzleri", + "source: ": "kaynak: ", + "Spawn": "Yarat", + "Speed": "Hız", + "Speeds up or slows down the entire game (movement, animation, everything)": "Tüm oyunu hızlandırır veya yavaşlatır (hareket, animasyon, her şey)", + "Spellbooks": "Büyü Kitapları", + "Spells": "Büyüler", + "Spontaneous Spells Per Day": "Günlük Spontane Büyüler", + "StarshipAmmo": "YıldızGemisiMühimmat", + "StarshipArmorPlating": "YıldızGemisiZırhKaplama", + "StarshipAugerArray": "YıldızGemisiSondajDizisi", + "StarshipBridge": "YıldızGemisiKöprü", + "StarshipGellerFieldDevice": "YıldızGemisiGellerAlanıCihazı", + "StarshipItem": "YıldızGemisiEşyası", + "StarshipLifeSustainer": "YıldızGemisiYaşamDestek", + "StarshipPlasmaDrives": "YıldızGemisiPlazmaMotorları", + "Starships": "Yıldız Gemileri", + "StarshipVoidShieldGenerator": "YıldızGemisiBoşlukKalkanıJeneratörü", + "StarshipWarpDrives": "YıldızGemisiWarpMotorları", + "StarshipWeapon": "YıldızGemisiSilah", + "Start": "Başlat", + "Stats": "İstatistikler", + "status: ": "durum: ", + "Steal from living NPCs": "Yaşayan NPC'lerden çal", + "Strip HTML (colors) from Logs Tab in Unity Mod Manager": "Unity Mod Manager'daki Kayıtlar Sekmesinden HTML'yi (renkleri) kaldır", + "Strip HTML (colors) from Native Console": "Yerel Konsoldan HTML'yi (renkleri) kaldır", + "Summons": "Çağırılanlar", + "System Map": "Sistem Haritası", + "Teleport": "Işınlan", + "Teleport Party To You": "Partiyi Yanına Işınla", + "The following skill check adjustments apply only out of combat": "Aşağıdaki beceri testi ayarlamaları yalnızca savaş dışında geçerlidir", + "This <b>incredibly dangerous</b> setting overrides the default behavior of failing to load saves depending on missing blueprint mods. This desperate action can potentially enable you to recover your saved game, though you'll have to respec at minimum.": "Bu <b>inanılmaz derecede tehlikeli</b> ayar, eksik taslak modlarına bağlı olarak kayıtları yükleyememenin varsayılan davranışını geçersiz kılar. Bu umutsuz eylem, potansiyel olarak kayıtlı oyununuzu kurtarmanıza olanak tanıyabilir, ancak en azından yeniden yeteneklendirmeniz gerekecektir.", + "This allows characters you control to move through the selected category of units during combat": "Bu, kontrol ettiğiniz karakterlerin savaş sırasında seçilen birim kategorisinden geçmesine izin verir", + "This allows you to adjust pitch (Camera Tilt) by holding down Mouse3 (which previously just rotated)": "Bu, Fare3'ü basılı tutarak eğimi (Kamera Eğimi) ayarlamanıza olanak tanır (daha önce sadece döndürürdü)", + "This also includes companions who left the party such as Wenduag if you picked Lann": "Bu, Lann'ı seçtiyseniz Wenduag gibi partiden ayrılan yoldaşları da içerir", + "This directly changes your character level but will not change exp or adjust any features associated with your character. To do a normal level up use +1 Lvl above. This gets recalculated when you reload the game. ": "Bu, karakter seviyenizi doğrudan değiştirir ancak deneyimi değiştirmez veya karakterinizle ilişkili herhangi bir özelliği ayarlamaz. Normal bir seviye atlamak için yukarıdaki +1 Seviye'yi kullanın. Bu, oyunu yeniden yüklediğinizde yeniden hesaplanır. ", + "This hides map pins of loot containers containing at most the selected rarity. <color=orange>Note: Changing settings requires reopening the map.</color>": "Bu, en fazla seçilen nadirlikteki ganimet kaplarının harita iğnelerini gizler. <color=orange>Not: Ayarları değiştirmek haritayı yeniden açmayı gerektirir.</color>", + "This is intended for you to be able to enjoy the game while using mods that enhance your quality of life. Please be mindful of the player community and avoid using this mod to trivialize earning prestige achievements like Sadistic Gamer. The author is in discussion with Owlcat about reducing the scope of achievement blocking to just these. Let's show them that we as players can mod and cheat responsibly.": "Bu, yaşam kalitenizi artıran modları kullanırken oyundan zevk alabilmeniz için tasarlanmıştır. Lütfen oyuncu topluluğuna dikkat edin ve Sadistic Gamer gibi prestijli başarıları kazanmayı basitleştirmek için bu modu kullanmaktan kaçının. Yazar, Owlcat ile başarı engellemenin kapsamını sadece bunlarla sınırlamak için görüşüyor. Onlara oyuncular olarak sorumlu bir şekilde mod yapabileceğimizi ve hile yapabileceğimizi gösterelim.", + "This is the current voice!": "Bu mevcut ses!", + "This makes loot function like Diablo or Borderlands. <color=orange>Note: turning this off requires you to save and reload for it to take effect.</color>": "Bu, ganimetin Diablo veya Borderlands gibi çalışmasını sağlar. <color=orange>Not: bunu kapatmak, etkili olması için kaydetmenizi ve yeniden yüklemenizi gerektirir.</color>", + "This resets all the skill check rolls for all interactable objects in the area": "Bu, alandaki tüm etkileşimli nesneler için tüm beceri testi zarlarını sıfırlar", + "This sets your experience to match the current value of character level": "Bu, deneyiminizi karakter seviyesinin mevcut değeriyle eşleşecek şekilde ayarlar", + "This turns on the developer console which lets you access cheat commands, shows a FPS window (hide with F11), etc.\n<b><color=yellow>Warning: </color></b><color=orange>You may need to restart the game for this to fully take effect</color>": "Bu, hile komutlarına erişmenizi sağlayan geliştirici konsolunu açar, bir FPS penceresi gösterir (F11 ile gizle) vb.\n<b><color=yellow>Uyarı: </color></b><color=orange>Bunun tam olarak etkili olması için oyunu yeniden başlatmanız gerekebilir</color>", + "This will change the color of NPC names on the highlight makers and change the color map markers to indicate that they have interesting or conditional interactions": "Bu, vurgulama işaretleyicilerindeki NPC adlarının rengini değiştirir ve ilginç veya koşullu etkileşimleri olduğunu belirtmek için renkli harita işaretleyicilerini değiştirir", + "This will change the color of NPC names on the highlike makers and change the color map markers to indicate that they have interesting or conditional interactions": "Bu, benzer vurgu işaretleyicilerindeki NPC adlarının rengini değiştirir ve ilginç veya koşullu etkileşimleri olduğunu belirtmek için renkli harita işaretleyicilerini değiştirir", + "Tiny": "Ufacık", + "To permanently remove these modded blueprint dependencies, load the damaged saved game, change areas, and then save the game. You can then respec any characters that were impacted.": "Bu modlanmış taslak bağımlılıklarını kalıcı olarak kaldırmak için, hasarlı kayıtlı oyunu yükleyin, alanları değiştirin ve ardından oyunu kaydedin. Daha sonra etkilenen tüm karakterleri yeniden yeteneklendirebilirsiniz.", + "ToyBox Archeologists can tag confusing puzzle pieces with green numbers in the game world and for inventory tool tips it will show text like this: <b><color=yellow>[PuzzlePiece Green3x1]</color></b><b><color=orange>\nNOTE: </color></b><color=orange>Needs game restart to take efect</color>": "ToyBox Arkeologları, oyun dünyasındaki kafa karıştırıcı bulmaca parçalarını yeşil sayılarla etiketleyebilir ve envanter ipuçları için şöyle bir metin gösterir: <b><color=yellow>[BulmacaParçası Yeşil3x1]</color></b><b><color=orange>\nNOT: </color></b><color=orange>Etkili olması için oyunun yeniden başlatılması gerekir</color>", + "ToyBox can patch some critical bugs in Rogue Trader Beta, including the following:": "ToyBox, Rogue Trader Beta'daki bazı kritik hataları yamalayabilir, bunlar dahil:", + "ToyBox has limited functionality from the main menu": "ToyBox'ın ana menüden sınırlı işlevselliği vardır", + "Trace": "İzle", + "Travel Speed": "Seyahat Hızı", + "TurretRadius": "TaretYarıçapı", + "TurretRating": "TaretDeğeri", + "Tweaks": "İnce Ayarlar", + "Units": "Birimler", + "Units CR": "Birimler ZG", + "Unlimited Actions During Turn": "Tur Sırasında Sınırsız Eylem", + "Unlimited Stacking of Modifiers (Stat/AC/Hit/Damage/Etc)": "Değiştiricilerin Sınırsız İstiflenmesi (İstatistik/ZK/Vuruş/Hasar/Vb)", + "Unlock": "Kilidi Aç", + "Unstart": "Başlatma", + "Usable": "Kullanılabilir", + "Vendor Buy Price": "Satıcı Alış Fiyatı", + "Vendor Sell Price": "Satıcı Satış Fiyatı", + "Visual Character Size Multiplier": "Görsel Karakter Boyut Çarpanı", + "WarhammerAgility": "WarhammerÇeviklik", + "WarhammerBallisticSkill": "WarhammerBalistikBeceri", + "WarhammerFellowship": "WarhammerSempati", + "WarhammerInitialAPBlue": "WarhammerBaşlangıçAPMavi", + "WarhammerInitialAPYellow": "WarhammerBaşlangıçAPSarı", + "WarhammerIntelligence": "WarhammerZeka", + "WarhammerPerception": "WarhammerAlgı", + "WarhammerStrength": "WarhammerGüç", + "WarhammerToughness": "WarhammerDayanıklılık", + "WarhammerWeaponSkill": "WarhammerSilahBeceri", + "WarhammerWillpower": "WarhammerİradeGücü", + "Warning": "Uyarı", + "Weapon": "Silah", + "Weapons": "Silahlar", + "When enabled and you hold down the camera follow key (usually f) the camera will keep following the unit until you release it": "Etkinleştirildiğinde ve kamera takip tuşunu (genellikle f) basılı tuttuğunuzda, kamera siz bırakana kadar birimi takip etmeye devam eder", + "When loading a game this will go right into the game without having to 'Press any key to continue'": "Bir oyunu yüklerken, 'Devam etmek için herhangi bir tuşa basın' demek zorunda kalmadan doğrudan oyuna girecektir", + "When ticked this shows ToyBox commands in the combat log which is helpful for you to know when you used the shortcut": "İşaretlendiğinde bu, savaş günlüğünde ToyBox komutlarını gösterir, bu da kısayolu ne zaman kullandığınızı bilmeniz için yararlıdır", + "Whole Party": "Tüm Parti", + "Whole Team Moves Same Speed": "Tüm Takım Aynı Hızda Hareket Eder", + "will be used for editing ": "düzenleme için kullanılacak ", + "Wrist": "Bilek", + "You": "Sen", + "You can enable hot keys to teleport members of your party to your mouse cursor on Area or the Global Map": "Partinizin üyelerini Alan veya Küresel Harita'daki fare imlecinize ışınlamak için kısayol tuşlarını etkinleştirebilirsiniz", + "Body parts global offsets": "Vücut parçaları genel ofsetleri", + "Body parts global scales": "Vücut parçaları genel ölçekleri", + "Body parts local sizes": "Vücut parçaları yerel boyutları", + "Equipment elements offsets": "Ekipman elemanları ofsetleri", + "Equipment elements sizes": "Ekipman elemanları boyutları", + "OF_positionZ": "Karakter Z ekseni ofseti", + "OF_shouldersX": "Omuz X ekseni ofseti", + "OF_shouldersZ": "Omuz Z ekseni ofseti", + "OF_upper_armsX": "Üst kol X ekseni ofseti", + "OF_upper_legsX": "Üst bacak X ekseni ofseti", + "SC_pelvisX": "Pelvis kemiği ölçeği X ekseni", + "SC_pelvisY": "Pelvis kemiği ölçeği Y ekseni", + "SC_pelvisZ": "Pelvis kemiği ölçeği Z ekseni", + "SC_neck": "Boyun ölçeği", + "SC_shoulders": "Omuz ölçeği", + "SC_upper_arms": "Üst kol ölçeği", + "SC_fore_arms": "Önkol ölçeği", + "SC_upper_torso": "Üst gövde ölçeği", + "SC_middle_torso": "Orta gövde ölçeği", + "SC_lower_torso": "Alt gövde ölçeği", + "SC_stomach": "Karın ölçeği", + "SC_upper_legs": "Üst bacak ölçeği", + "SC_lower_legs": "Alt bacak ölçeği", + "SC_foots": "Ayak ölçeği", + "SC_toes": "Ayak parmağı ölçeği", + "SZ_head": "Kafa boyutu", + "SZ_neck": "Boyun boyutu", + "SZ_shoulders": "Omuz boyutu", + "SZ_upper_arms": "Üst kol boyutu", + "SZ_fore_arms": "Önkol boyutu", + "SZ_hands": "El boyutu", + "SZ_upper_torso": "Üst gövde boyutu", + "SZ_middle_torso": "Orta gövde boyutu", + "SZ_lower_torso": "Alt gövde boyutu", + "SZ_stomach": "Karın boyutu", + "SZ_pelvis": "Pelvis boyutu", + "SZ_upper_legs": "Üst bacak boyutu", + "SZ_middle_legs": "Orta bacak boyutu", + "SZ_lower_legs": "Alt bacak boyutu", + "SZ_foots": "Ayak boyutu", + "SZ_toes": "Ayak parmağı boyutu", + "IO_cloakX": "Sadece pelerin X ekseni ofseti", + "IO_cloakY": "Sadece pelerin Y ekseni ofseti", + "IO_cloakZ": "Sadece pelerin Z ekseni ofseti", + "IO_backpackX": "Sırt çantası ve pelerin X ekseni ofseti", + "IO_backpackY": "Sırt çantası ve pelerin Y ekseni ofseti", + "IO_backpackZ": "Sırt çantası ve pelerin Z ekseni ofseti", + "IO_weapon_in_holstersRX": "Sağ kılıf X ekseni ofseti", + "IO_weapon_in_holstersRY": "Sağ kılıf Y ekseni ofseti", + "IO_weapon_in_holstersRZ": "Sağ kılıf Z ekseni ofseti", + "IO_weapon_in_holstersLX": "Sol kılıf X ekseni ofseti", + "IO_weapon_in_holstersLY": "Sol kılıf Y ekseni ofseti", + "IO_weapon_in_holstersLZ": "Sol kılıf Z ekseni ofseti", + "IS_cloak": "Sadece pelerin boyutu", + "IS_backpack": "Sırt çantası ve pelerin boyutu", + "IS_weapon_in_hand": "Ellerdeki silah boyutu", + "IS_weapon_in_holsters": "Kılıflardaki silah boyutu", + "IS_back_weapon_R": "Sırt silahı boyutu sağ", + "IS_back_weapon_L": "Sırt silahı boyutu sol" + } +} diff --git a/ToyBox/Localization/zh-CN.json b/ToyBox/Localization/zh-CN.json new file mode 100644 index 000000000..d998738bf --- /dev/null +++ b/ToyBox/Localization/zh-CN.json @@ -0,0 +1,629 @@ +{ + "LanguageCode": "zh-CN", + "Version": "1.5.13", + "Contributors": "ohZbs, Kirie, lhr7324925", + "HomePage": "https://github.com/cabarius/ToyBox/", + "Strings": { + " (This setting is per-save)": "(此设置为单独存档生效)", + " Allow remote companions to make comments on dialog you are having.": " 允许远程同伴对你正在进行的对话发表评论。", + " by the following amount:": "按以下量级:", + " meters": " 米", + "<": "<", + "<b><color=magenta>Note </color></b><b><color=orange>ToyBox was designed to offer the best user experience at widths of 1920 or higher. Please consider increasing your resolution up of at least 1920x1080 (ideally 4k) and go to Unity Mod Manager 'Settings' tab to change the mod window width to at least 1920. Increasing the UI scale is nice too when running at 4k</color></b>": "<b><color=magenta>注意</color></b><b><color=orange>ToyBox被设计在1920或更高的宽度下提供最佳的用户体验。请考虑将分辨率提高到至少1920x1080(理想情况下是4k),然后转到Unity Mod Manager-设置-选项卡将Mod窗口宽度更改为至少1920。当以4k运行时,增加UI比例也很好</color></b>", + "<b><color=magenta>Warning: </color></b><color=orange>This is an experimental preview of ToyBox (<color=cyan>Sh0dan</color>) for Rogue Trader Beta.</color><b><color=yellow> Save early and often.\r\n</color></b><b><color=magenta>Note:</color></b><color=orange> Not all features are functional at this time. The ToyBox team is working hard to get as much working as fast as possible</color>": "<b><color=magenta>警告:</color></b><color=orange>这是一个为行商浪人所作的先行版Toy Box(<color=cyan>Sh0dan</color>)。</color><b><color=yellow>请经常存档\r\n</color></b><b><color=magenta>注意:</color></b><color=orange>功能尚未完善。Toy Box团队正在为此尽可能努力制作中</color>", + "<b><color=magenta>Warning: </color></b><color=orange>This is an experimental preview of ToyBox for Rogue Trader.</color><b><color=yellow> Save early and often.\r\n</color></b><b><color=magenta>Note:</color></b><color=orange> Not all features are functional at this time. If you notice a feature doesn't work please report that on GitHub or in the modding channels on the Owlcat Discord.</color>": "<b><color=magenta>警告:</color></b><color=orange>这是ToyBox for Rogue Trader的实验性预览版。</color><b><color=yellow> 请经常存档。\r\n</color></b><b><color=magenta>注意:</color></b><color=orange> 目前并非所有功能都正常运行。如果你发现某个功能不工作,请在GitHub或Owlcat Discord的模组频道上报告。</color>", + "<b><color=orange>Achievements</color></b>": "<b><color=orange>成就</color></b>", + "<b><color=orange>Bag of Tricks</color></b>": "<b><color=orange>杂项包</color></b>", + "<b><color=orange>Blueprints</color></b> loading: ": "<b><color=orange>蓝图</color></b> 加载:", + "<b><color=orange>character(s) can be </color></b><color=cyan>Recruited</color><color=#00ff00ff>. This allows you to add non party NPCs to your party as if they were mercenaries</color>": "<b><color=orange>角色可被</color></b><color=cyan>招募</color><color=#00ff00ff>。这允许你将非队伍NPC添加到队伍中,就像雇佣兵一样</color>", + "<b><color=orange>character(s) can be </color></b><color=cyan>Respecced</color><color=#00ff00ff>. Pressing Respec will close the mod window and take you to character level up</color>": "<b><color=orange>角色可被</color></b><color=cyan>重新分配</color><color=#00ff00ff>。按下重新分配将关闭mod窗口并带你到角色升级界面</color>", + "<b><color=orange>Colonies</color></b>": "<b><color=orange>殖民地</color></b>", + "<b><color=orange>Dialog & NPCs</color></b>": "<b><color=orange>对话与NPC</color></b>", + "<b><color=orange>Enchantment</color></b>": "<b><color=orange>附魔</color></b>", + "<b><color=orange>Enhanced UI</color></b>": "<b><color=orange>增强UI</color></b>", + "<b><color=orange>Etudes</color></b>": "<b><color=orange>研究</color></b>", + "<b><color=orange>Level Up</color></b>": "<b><color=orange>升级</color></b>", + "<b><color=orange>Loot</color></b>": "<b><color=orange>战利品</color></b>", + "<b><color=orange>Note</color></b><color=#00ff00ff> this is a new and exciting feature that allows you to see for the first time the structure and some basic relationships of </color><b><color=cyan>Etudes</color></b><color=#00ff00ff> and other </color><b><color=cyan>Elements</color></b><color=#00ff00ff> that control the progression of your game story. Etudes are hierarchical in structure and additionally contain a set of </color><b><color=cyan>Elements</color></b><color=#00ff00ff> that can both conditions to check and actions to execute when the etude is started. As you browe you will notice there is a disclosure triangle next to the name which will show the children of the Etude. Etudes that have </color><b><color=cyan>Elements</color></b><color=#00ff00ff> will offer a second disclosure triangle next to the status that will show them to you.</color>": "<b><color=orange>注意</color></b><color=#00ff00ff> 这是一个新颖激动人心的功能,让你首次能够看到控制游戏故事进程的</color><b><color=cyan>研究</color></b><color=#00ff00ff>和其他</color><b><color=cyan>元素</color></b><color=#00ff00ff>的结构和一些基本关系。研究具有分层结构,并且额外包含一组</color><b><color=cyan>元素</color></b><color=#00ff00ff>,这些元素既可以检查条件,也可以在研究启动时执行动作。浏览时你会注意到名称旁边有一个展开三角形,它将显示研究的子项。拥有</color><b><color=cyan>元素</color></b><color=#00ff00ff>的研究会在状态旁提供第二个展开三角形来显示它们。</color>", + "<b><color=orange>Party</color></b>": "<b><color=orange>队伍</color></b>", + "<b><color=orange>Quests</color></b>": "<b><color=orange>任务</color></b>", + "<b><color=orange>Saves</color></b>": "<b><color=orange>存档</color></b>", + "<b><color=orange>Search 'n Pick</color></b>": "<b><color=orange>搜索添加</color></b>", + "<b><color=orange>Settings</color></b>": "<b><color=orange>设置</color></b>", + "<b><color=yellow>BACK UP</color></b><color=orange> before playing with this feature.You will lose your mythic ranks but you can restore them in this Party Editor.</color>": "<b><color=yellow>请备份存档</color></b><color=orange>再使用此功能。你会失去你的神话阶层,但你可以在这个队伍编辑器中恢复它们。</color>", + "<b><color=yellow>WARNING</color></b><color=orange> The Respec UI is </color><b><color=yellow>Non Interruptable</color></b><color=orange> please save before using</color>": "<b><color=yellow>警告:</color></b><color=orange>重新分配界面是</color><b><color=yellow>不可中断</color></b><color=orange>的,请在启用前保存。</color>", + "<b><color=yellow>WARNING</color></b><color=orange> these features are </color><b><color=yellow>EXPERIMENTAL</color></b><color=orange> and uses unreleased and likely buggy code.</color>": "<b><color=yellow>警告:</color></b><color=orange>此功能为</color><b><color=yellow>实验性</color></b><color=orange> 并使用未发布和可能有bug的代码。</color>", + "<b><color=yellow>WARNING</color></b><color=orange> this tool can both miraculously fix your broken progression or it can break it even further. Save and back up your save before using.</color>": "<b><color=yellow>警告:</color></b><color=orange>此工具既可以神奇地修复你的进度,也可以使其变得更糟。请在启用前备份存档。</color>", + "<color=#00A000FF>Uncommon</color>": "<color=#00A000FF>精良</color>", + "<color=#00ff00ff>Allow </color><color=#C060F0FF>any gender</color> <color=#00ff00ff>for any </color><color=#FF4040FF>R</color><color=orange>o</color><color=yellow>m</color><color=#00ff00ff>a</color><color=cyan>n</color><color=#8080FFFF>c</color><color=#C060F0FF>e</color>": "<color=#00ff00ff>允许</color><color=#C060F0FF>任何性别</color> <color=#00ff00ff>为</color><color=#FF4040FF>R</color><color=orange>o</color><color=yellow>m</color><color=#00ff00ff>a</color><color=cyan>n</color><color=#8080FFFF>c</color><color=#C060F0FF>e</color>", + "<color=#00ff00ff>Allow </color><color=#C060F0FF>multiple</color><color=#00ff00ff> romances at the same time</color>": "<color=#00ff00ff>允许</color><color=#C060F0FF>同时</color><color=#00ff00ff>进行多个</color><color=#C060F0FF>浪漫关系</color>", + "<color=#00ff00ff>Some responses such as comments about your mythic powers will always choose the first one by default. This allows the game to mix things up a bit</color><b><color=yellow>\nWarning:</color></b><color=orange> this will introduce randomness to NPC responses to you in general and may lead to surprising or even wild outcomes</color>": "<color=#00ff00ff>某些回应(如关于你神话力量的评论)默认始终选择第一个。这将允许游戏将一些东西混合起来</color><b><color=yellow>\n警告:</color></b><color=orange>这将给NPC的反应引入随机性,并可能导致令人惊讶甚至狂野的结果</color>", + "<color=#00ff00ff>Tells the game to reset the in game UI.</color><color=yellow> Warning</color><color=orange> Using this in dialog or the book will dismiss that dialog which may break progress so use with care</color>": "<color=#00ff00ff>这告诉游戏重置游戏内UI。</color><color=yellow> 警告</color><color=orange> 在对话或书籍中使用此功能可能会导致进度中断,请谨慎使用。</color>", + "<color=#00ff00ff>This allows rechosing the first arcehtype. Also makes Companion respec start from level 0.</color>": "<color=#00ff00ff>这允许你重新选择第一个变体。同时使同伴重新分配从等级0开始。</color>", + "<color=#00ff00ff>This allows you to loot items that are locked such as items carried by certain NPCs and items locked on your characters</color><b><color=yellow>\nWARNING: </color></b><color=yellow>This may affect story progression (e.g. your purple knife)</color>": "<color=#00ff00ff>这允许你掠夺被锁定的物品,如某些NPC携带的物品和锁定在你的角色身上的物品</color><b><color=yellow>\n警告:</color></b><color=yellow>这可能影响游戏进程(如锁定在你身上的紫色石刀)</color>", + "<color=#00ff00ff>This makes loot function like Diablo or Borderlands. <color=orange>Note: turning this off requires you to save and reload for it to take effect.</color></color>": "<color=#00ff00ff>这使得战利品功能类似暗黑破坏神或无主之地。<color=orange>注意:关闭此项需要保存并重新加载才能生效。</color></color>", + "<color=#1030E0FF>Rare</color>": "<color=#1030E0FF>稀有</color>", + "<color=#6030F0FF>Epic</color>": "<color=#6030F0FF>史诗</color>", + "<color=#60FFFFFF>Primal</color>": "<color=#60FFFFFF>基础</color>", + "<color=#808080FF>Trash</color>": "<color=#808080FF>垃圾</color>", + "<color=#98761FFF>Notable</color>": "<color=#98761FFF>值得注意</color>", + "<color=#A00000FF>Godly</color>": "<color=#A00000FF>神圣</color>", + "<color=#A000A0FF>Mythic</color>": "<color=#A000A0FF>神话</color>", + "<color=#C04040E0>♥♥ </color><b>Love is Free</b><color=#C04040E0> ♥♥</color>": "<color=#C04040E0>♥♥ </color><b>爱是自由的</b><color=#C04040E0> ♥♥</color>", + "<color=#D0D0D0FF>None</color>": "<color=#D0D0D0FF>无</color>", + "<color=#D8D8D8A0>Common</color>": "<color=#D8D8D8A0>普通</color>", + "<color=#E67821E0>Legendary</color>": "<color=#E67821E0>传奇</color>", + "<color=cyan> + Click</color> To Transfer Entire Stack": "<color=cyan> + 点击</color> 转移整堆物品", + "<color=cyan> + Click</color> To Use Items In Inventory": "<color=cyan> + 点击</color> 使用背包中的物品", + "<color=orange>Experimental</color> Allow remote companions to make comments on dialog you are having.": "<color=orange>实验性</color> 允许远程同伴对你正在进行的对话发表评论。", + "<color=orange>Note:</color><color=#00ff00ff> For cutscenes and some situations the rotation keys are disabled so you have to hold down Mouse3 to drag in order to get rotation</color>": "<color=orange>注意:</color><color=#00ff00ff> 对于过场和某些情况下,旋转键被禁用,所以你必须按住鼠标中键拖动才能旋转</color>", + "<color=orange>Sandal says '</color><b><color=cyan>Enchantment'</color></b>": "<color=orange>Sandal说</color><b><color=cyan>“增强!”</color></b>", + ">": ">", + "Abilities": "能力", + "Ability Rsrc": "能力资源", + "Achievements": "成就", + "Achievements not available until you load a save.": "成就不可用直到你加载一次存档", + "Active": "活跃", + "Add": "添加", + "Addendum: ": "附录:", + "Adds a search field to Load/Save screen (in game only)": "在加载/保存界面添加一个搜索框(仅在游戏中)", + "Adjust ": "调整", + "Adjust based on Level": "基于当前等级调整", + "Adjust Navigator Insight by the following amount:": "据以下数目调整导航员洞察力", + "Adjust Profit Factor by the following amount:": "据以下数目调整利润因子", + "Adjust Reputation by the following amount:": "据以下数目调整威望", + "Adjust Resource Factor by the following amount:": "据以下数目调整资源因数", + "Adjust Scrap by the following amount:": "据以下数目调整废料", + "Adjusts costs of hiring mercenaries at the Pathfinder vendor": "调整在开拓者商人处雇佣佣兵的费用", + "Adjusts the movement speed of your party in area maps": "调整你的队伍在区域地图中的移动速度", + "Adjusts the movement speed of your party on world maps": "调整你的队伍在世界地图上的移动速度", + "All": "所有", + "All Attacks Hit": "所有攻击命中", + "All Experience": "所有经验", + "All Hits Critical": "所有命中暴击", + "All Units": "所有单位", + "Allow ": "允许", + "Allow Achievements While Using Mods": "使用Mod时允许成就", + "Allow Equipment Change During Combat": "允许战斗中切换装备", + "Allow Item Use From Inventory During Combat": "允许战斗中从背包使用物品", + "Allow Looting Of Locked Items": "允许获得锁定物品战利品", + "Allow Mass Loot to steal from living NPCs": "允许战利品窗口显示活人NPC身上的物品", + "Alternate Time Scale": "备选时间流速", + "Always Roll 1": "永远掷1", + "Always Roll 100": "永远掷100", + "Always Roll 50": "永远掷50", + "Answer": "回答", + "Apply Bug Fixes": "应用Bug修复", + "Archetypes": "变体", + "Area Entry": "区域入口", + "Areas": "区域", + "Armor": "护甲", + "ArmourAft": "后甲", + "ArmourFore": "前甲", + "ArmourPort": "左舷甲", + "ArmourStarboard": "右舷甲", + "AttackOfOpportunityCount": "借机次数", + "Attributes": "属性", + "Auto Follow While Holding Camera Follow Key": "按住相机跟随键时自动跟随", + "Auto load Last Save on launch": "启动时自动加载最新存档", + "Bag of Tricks": "杂项", + "Be a <b><color=#C04040E0>Murder</color></b><color=orange> Hobo</color>": "成为<b><color=#C04040E0>杀人</color></b><color=orange>狂</color>", + "Belt": "腰带", + "Bind": "绑定", + "Bindable hot key to swap between main and alternate time scale multipliers": "用于在主要和备选时间流速之间切换的可绑定热键", + "Blueprint": "蓝图", + "bp": "基础", + "Brains": "AI", + "Buff Duration": "增益持续时间", + "Buff Like A Goddess": "女神级增益", + "Buffs": "增益", + "buffs to add to list": "要添加到列表中的增益", + "Camera": "相机", + "Can Move Through": "无视模型阻挡(不是无视地形)", + "Can Start": "可以开始", + "Careers": "职业生涯", + "Categories": "类别", + "Change Colony Stat": "更改殖民地数据", + "Change Party": "更换队友", + "Change Portrait": "更换头像", + "Change the party without advancing time (good to bind)": "更换队友而不推进时间(可绑定热键)", + "Change Voice": "更换声音", + "Change Weather": "改变天气", + "Changing your gender may cause visual glitches": "改变性别可能会导致瞎眼建模", + "Character Level": "角色等级", + "Cheats": "作弊", + "Check for Glyph Support": "检查字形支持", + "CheckBluff": "唬骗", + "CheckDiplomacy": "交涉", + "CheckIntimidate": "威吓", + "Classes": "职业", + "Click On Equip Slots To Filter Inventory": "点击装备栏过滤背包物品", + "Collapse All": "折叠所有", + "Collating...": "整理中…", + "Colonies": "殖民地", + "Colonize": "殖民", + "ColonyFoundation": "殖民地建设", + "Color Item Names": "物品名称颜色", + "Colossal": "巨型", + "Combat": "战斗", + "Common": "常见", + "Common Buffs": "普通增益", + "Common Tweaks": "通常调整", + "Companion Cost": "佣兵费用", + "Complete": "完成", + "Complete (Final)": "完成(最终)", + "Components": "组件", + "condition: ": "条件:", + "conflicts": "冲突", + "Containers": "容器", + "Contentment": "满足", + "Copying...": "抄写中…", + "Corruption": "深渊腐化", + "Create & Level Up": "创建和升级", + "Crew": "船员", + "Cruiser_2x4": "巡洋舰_2x4", + "Ctrl + Mouse3 Drag To Adjust Camera Elevation": "Ctrl + 鼠标中键拖动以调整相机高度", + "Cues": "旁白", + "Current Amount": "当前数量", + "Current Blueprint Portrait": "当前蓝图头像", + "Current Culture": "当前文化", + "Current Navigator Insight": "当前导航员洞察力", + "Current Profit Factor": "当前利润因子", + "Current Reputation": "当前声望", + "Current Scrap": "当前废料", + "Current Veil Thickness": "当前虚空帷幕厚度", + "Custom": "附加", + "custom exceptions": "已添加例外", + "Cut Scenes": "过场", + "Debug": "调试", + "default exceptions": "默认例外", + "Description": "描述", + "Dialog": "对话", + "Dialog & NPCs": "对话与NPC", + "Dialog Alignment": "阵营对话", + "Dialog Conditions": "对话条件", + "Dialog Results": "对话结果", + "Dice Rolls": "掷骰", + "Diminutive": "微型", + "Disable Attacks Of Opportunity": "禁用借机攻击", + "Disable Dialog Restrictions (Everything, Experimental)": "禁用对话限制(所有,实验性)", + "Disable Dialog Restrictions (SoulMark)": "禁用对话限制(灵魂伴侣)", + "Disable end turn HotKey": "禁用结束回合热键", + "Disable Random Encounters in Warp": "禁用随机遭遇战", + "Disable Voice Over and Barks for this character": "禁用此角色的语音和独白", + "Display guids in most tooltips, use shift + left click on items/abilities to copy guid to clipboard": "在大多数提示中显示GUID,使用Shift+左键点击物品/能力将GUID复制到剪贴板", + "Display risky options": "显示危险选项", + "Don't use AP (except abilities which consume all AP) During Turn": "回合中不使用行动点(消耗所有行动点的能力除外)", + "Don't wait for keypress when loading saves": "读取存档时不要等待按键", + "Draws dialog choices that you have previously selected in smaller type": "将已选择的对话选项文字缩小", + "Drusians": "德鲁西亚人", + "Duration Multiplier": "持续时间倍数", + "Edit Resources": "编辑资源", + "Efficiency": "效率", + "elements": "元素", + "Elements": "元素", + "Empowered": "强化增益", + "Enable Game Development Mode": "启用游戏开发模式", + "Enable Loading with Blueprint Errors": "启用蓝图错误时的加载", + "Enable Mouse3 Dragging To Aim The Camera": "允许鼠标中键拖动来调整相机", + "Enable Rotate on all maps and cutscenes": "在所有地图和过场中启用视角旋转", + "Enable Search as you type for Browsers (needs restart)": "为浏览器启用输入即搜索(需要重启)", + "Enable Teleport Keys": "启用传送热键", + "Enable Zoom on all maps and cutscenes": "在所有地图和过场中启用视角缩放", + "Ench. Type": "强化类型", + "Ench. Types": "强化类型", + "Enchantment": "强化", + "Enemies": "敌人", + "Enemy HP Multiplier": "敌人生命值倍数", + "EnemyShip": "敌舰", + "Enhanced Load/Save": "增强的加载/保存", + "Enhanced Map View": "增强的地图视图", + "Enhanced UI": "增强UI", + "Enterint a Trap Zone while having Traps disabled will prevent that Trap from triggering even if you deactivate this option in the future": "勾选该选项进入陷阱区域将禁止此陷阱触发,即使取消勾选该选项,此陷阱也不会再次触发", + "Equip (rarity)": "装备(稀有度)", + "Equipment": "装备", + "Error": "错误", + "Etude Status: ": "研究状态:", + "Etudes": "研究", + "Evasion": "闪避", + "Everyone": "所有人", + "Exceptions to Buff Duration Multiplier (Advanced; will cause blueprints to load)": "增益持续时间修改的例外(高级;将导致蓝图加载)", + "Expand All": "展开所有", + "Expand Answers For Conditional Responses": "扩展有条件的回答", + "Expand Dialog To Include Remote Companions": "扩展对话以包括远程同伴", + "Experience": "经验", + "Experience Multipliers": "经验倍数", + "Explorators": "探险家", + "Export": "导出", + "Export current locale to file": "将当前语言导出到文件", + "Faction Selector": "派系选择", + "Facts": "实物", + "Failure to load saves that reference custom portraits": "存档自定义肖像加载失败", + "Faith": "信仰", + "Features": "特性", + "Feet": "鞋子", + "Female": "女性", + "Field Of View": "视野范围", + "Fine": "良好", + "Finish": "完成", + "Fix Camera": "修正相机视角", + "Fix Incorrect Main Character": "修正不正确的主角", + "Flags": "标志", + "Flags Only": "仅标志", + "Fog of War Range": "战争迷雾范围", + "FoV (Cut Scenes)": "视野范围(过场)", + "Free Camera": "自由相机视角", + "Friendly": "友军", + "Frigate_1x2": "护卫舰_1x2", + "Gain ": "获得 ", + "Game Time Scale": "游戏时间流速", + "Gargantuan": "超巨型", + "Gender": "性别", + "Generate Comment Translation Table": "生成评论翻译表", + "Give All Items": "给予所有物品", + "Giving characters voices besides the default ones is untested.": "为角色添加默认以外的声音是未经测试的。", + "Glasses": "眼镜", + "Gloves": "手套", + "Go To Global Map": "前往世界地图", + "Go to page: ": "前往页面", + "Go!": "跳转", + "Gold": "金币", + "good for larger group or to reduce enemies": "用于更大范围或弱化敌方", + "good for party": "用于队伍", + "GrandCruiser_3x6": "大巡洋舰_3x6", + "Head": "头部", + "Hide": "隐藏", + "Hide Completed": "隐藏已完成", + "Highlight Copyable Scrolls": "高亮可学习卷轴", + "Highlight Hidden Objects": "高亮隐藏物体", + "HitPoints": "生命值", + "Hold down shift during launch to bypass": "启动时按住Shift键以跳过", + "Hope": "希望", + "Huge": "巨大", + "Identify All": "鉴定一切", + "If ticked, this will <b><color=#C04040E0>MURDER</color></b><color=#00ff00ff> all who dare to engage you!</color>": "谁敢<b><color=#C04040E0>向我挑衅,</color></b><color=#00ff00ff>我将终结他的生命!</color>——格拉利昂百宝袋流指挥官符文已配置", + "If you have a save that uses custom portraits and don't toggle this your game will crash when starting": "如果你有一个使用自定义肖像的存档,而不勾选这个选项,你的游戏将在开始时崩溃", + "If you tick this you can click on equipment slots to filter the inventory for items that fit in it.\nFor more <color=orange>Enhanced Inventory</color> and <color=orange>Spellbook</color> check out the <b><color=orange>Loot & Spellbook Tab</color></b>": "如果你勾选了这个选项,你就可以点击装备栏来过滤背包中适合它的物品。\n如需更多<color=orange>增强的背包</color>和<color=orange>法术书</color>参见<b><color=orange>战利品和法术书页面</color></b>", + "Ignore Ability Requirement - AOE Overlap": "忽略能力要求-AOE覆盖", + "Ignore Ability Requirement - Line of Sight": "忽略能力要求-视线内", + "Ignore Ability Requirement - Max Range": "忽略能力要求-最大范围", + "Ignore Ability Requirement - Min Range": "忽略能力要求-最小范围", + "Ignore all Requirements for Abilities": "忽略能力的所有要求", + "Ignore Class Restrictions": "忽略职业限制", + "ignore Equipment Restrictions": "忽略装备限制", + "Ignore Required Class Levels": "忽略所需的职业等级", + "Ignore Required Stat Values": "忽略所需的属性值", + "Ignore Talent Prerequisites": "忽略天赋先决条件", + "Import": "导入", + "Import/Export allows you to save and add a list of items to a file based on the type (e.g. Weapon.json). These files live in a new ToyBox folder that in the same folder that contains your saved games ": "导入/导出允许您根据类型存档并将物品列表添加到一个文件中(例如Weapon.json)。这些文件位于一个新的ToyBox文件夹中,该文件夹包含您保存的游戏存档", + "In Fog Of War ": "穿透战争迷雾", + "Include Former Companions": "包括过去的队友", + "Increase Carry Capacity": "增加负重", + "Increase Carry Capacity (Party Only)": "增加负重(仅队伍)", + "increment": "增量", + "Inertia": "惯性", + "Infinite Abilities": "无限能力", + "Infinite Abilities (No cooldowns no cost)": "无限能力(无冷却无消耗)", + "Infinite Charges On Items": "物品无限充能", + "Infinite Spell Casts": "无限施法次数", + "Info": "信息", + "In-Game Name": "游戏内名称", + "Initiative": "先攻", + "Initiative: Always Roll 1": "先攻:永远掷1", + "Initiative: Always Roll 10": "先攻:永远掷10", + "Initiative: Always Roll 5": "先攻:永远掷5", + "Inspect": "检验", + "Inspect Dialog Controller": "检验对话控制器", + "Inspect Party <color=orange>(for modders)</color>": "检验全队<color=orange>(mod制作者用)</color>", + "Inspect Quests and Objectives": "检验任务和目标", + "Inspecting: ": "检验中:", + "Instant Rest After Combat": "战斗后立即休息", + "Interesting NPCs in the local area": "当地有趣的NPC", + "Internal Name": "内部名称", + "Invert X Axis": "反转X轴", + "Invert Y Axis": "反转Y轴", + "Item": "物品", + "Jealousy Begone!": "嫉妒走开!", + "Kasballica": "卡斯巴利卡", + "Keyboard:": "键盘:", + "Kill": "杀死", + "Kill All Enemies": "杀死所有敌人", + "Large": "大型", + "Lets you open up the area's mass loot screen to grab goodies whenever you want. Normally shown only when you exit the area": "让你打开当前区域的大型战利品窗口,随时随地刮地三尺。通常只有在你离开当前区域时才会显示", + "level": "等级", + "Level": "等级", + "Level Increase/Decrease": "等级增减", + "Level Up": "升级", + "Limit": "限制", + "Lobotomize Enemies": "敌人变白痴", + "Localization": "本地化", + "Lock": "锁定", + "Log Level": "日志级别", + "Log ToyBox Keyboard Commands In Game": "在游戏中记录ToyBox指令", + "Loot": "战利品", + "Loot Checklist": "战利品确认列表", + "Loot Rarity Coloring": "战利品稀有度颜色", + "Loot Rarity Filtering": "战利品稀有度过滤", + "Lose ": "失去 ", + "Main Character": "主角", + "Main/Alt Timescale": "主要/备选时间流速", + "Make Character AI Controlled": "使角色AI控制", + "Make Controllable": "使召唤物可控制", + "Make game continue to play music on lost focus": "使游戏在后台时继续播放音乐", + "Make Puzzle Symbols More Clear": "使拼图碎片更清楚", + "Make Spell/Ability/Item Pop-Ups Wider ": "使法术/能力/物品弹出窗口更宽", + "Make tutorials not appear if disabled in settings": "如果已在设置中禁用则不再显示教程", + "Makes mouse zoom work for the local map (cities, dungeons, etc). Game restart required if you turn it off": "使鼠标缩放适用于区域地图(城市、地牢等)。如果关闭,则需要重新启动游戏", + "Male": "男性", + "Mark Interesting NPCs": "标记有趣的NPC", + "Mark Interesting NPCs on Map": "在地图上标记有趣的NPC", + "Mass Loot": "大型战利品", + "Matches: ": "符合要求:", + "max": "最大", + "Maximize the ModManager window for best ToyBox user experience": "最大化ModManager窗口以提供最佳ToyBox用户体验", + "Maximize Window": "最大化窗口", + "Maximum Rarity To Hide:": "不显示的最高稀有度", + "Mechadendrite": "机械枝", + "Medium": "中型", + "MilitaryRating": "军事评级", + "min": "最小", + "Minimum Rarity For Loot Rarity Tags/Colors": "战利品稀有标签/颜色的最低稀有度", + "Modify Summons For": "修改召唤物", + "Money Earned": "赚取的金钱", + "Mono Version": "Mono版本", + "Morale": "士气", + "Mouse:": "鼠标:", + "Movement Speed": "移动速度", + "N/A": "N/A", + "Name of the new Blueprintportrait: ": "新蓝图头像的名称:", + "Name of the new Custom Portrait: ": "新自定义头像的名称:", + "Nearby": "附近", + "Nearby Distance": "附近距离", + "Neck": "颈部", + "Never Roll 1": "永远不会掷1", + "Never Roll 100": "永远不会掷100", + "No Active Dialog": "无活动对话", + "No attack/spell cooldowns": "无攻击/法术冷却", + "No Etudes": "无研究", + "No Fog Of War": "无战争迷雾", + "No Friendly Fire On AOEs": "AOE无友军伤害", + "No Items": "无物品", + "No Loot Available": "无战利品", + "Non Combat: Take 1": "战斗外:掷1", + "None": "无", + "NonUsable": "不可用物品", + "Object Highlight Toggle Mode": "目标高亮切换模式", + "Object Highlight Toggle Mode (Out of Combat!)": "物体高亮切换模式(仅战斗外!)", + "Off": "关闭", + "Ongoing Events:": "进行中事件", + "Ongoing Projects:": "进行中项目", + "Only show languages with existing localization files": "仅显示已有本地化文件的语言", + "Open Mass Loot Window": "打开战利品窗口", + "Open the Localization Guide": "打开本地化指南", + "Other": "其他", + "Other Multipliers": "其他倍数", + "Override AI Control Behaviour": "覆盖AI控制行为", + "Override for Challenges": "覆盖挑战", + "Override for Combat": "仅战斗", + "Override for Quests": "仅任务", + "Override for Skill Checks": "仅技能检定", + "Override for Space Combat": "覆盖太空战斗", + "Override for Traps": "仅陷阱", + "Page % of %": "页面 % / %", + "Page: ": "页面", + "Parameter": "参数", + "Party": "队伍", + "Party & Pets": "队伍/宠物", + "Party Level ": "队伍等级", + "Perm": "永久", + "Pets": "宠物", + "Pick none to stop overwriting.": "选择无以停止覆盖。", + "Pick size modifier to overwrite default.": "选择尺寸修正来覆盖默认值。", + "Pirates": "海盗", + "Planets": "行星", + "Play": "播放", + "Play Example": "播放示例", + "Prepared Spellslots": "每日准备法术位", + "Prevent Psychic Phenomena": "防止灵能现象", + "Prevent Traps from triggering": "禁止陷阱触发", + "Prevent Veil Thickness from changing": "防止虚空帷幕厚度改变", + "Preview": "预览", + "Preview Results": "预览结果", + "Previously Chosen Dialog Is Smaller ": "较小的已选择对话", + "Primary": "主要", + "Progression": "升级", + "PsyRating": "灵能等级", + "Quality of Life": "生活质量", + "QuestObj": "任务物体", + "Quests": "任务", + "Races": "种族", + "Raider_1x1": "掠夺者_1x1", + "Random Encounters": "随机遭遇", + "Randomize NPC Responses To Dialog Choices": "随机化NPC对对话选项的反应", + "Rank": "等级", + "Rarity: ": "稀有度", + "Rating": "评分", + "rating: ": "评分", + "Ratings": "评分", + "Reason": "原因", + "Recruit": "招募", + "Refill consumables in belt slots if in inventory": "自动从背包填充物品栏中的消耗品", + "Refresh": "刷新", + "Remote": "远程", + "Remove": "移除", + "Remove Buffs": "移除增益", + "Remove Deaths Door": "移除死亡", + "Reroll Perception": "重掷察觉检定", + "Reset": "重置", + "Reset Interactables": "重置可交互物体", + "Reset UI": "重置UI", + "Resolve": "决心", + "Resource": "资源", + "ResourceMiner": "资源采集器", + "Resources": "资源", + "Respec": "重新分配", + "Respec from Level 0": "从0级重新分配", + "Rest All": "全体休息", + "Rest Selected": "选中休息", + "Restart": "重新开始", + "Restore Spells & Skills After Combat": "战斗后恢复法术位和技能次数", + "Ring": "戒指", + "Roll With Advantage (take lower roll)": "优势掷骰(取较低结果)", + "Roll With Avantage": "优势掷骰", + "Roll With Disavantage": "劣势掷骰", + "Roll With Disavantage (take higher roll)": "劣势掷骰(取较高结果)", + "Rotation Options": "旋转选项", + "RT Specific": "即时制专属", + "Save as png": "保存为png", + "Save ID: ": "存档ID", + "SaveFortitude": "强韧豁免", + "SaveReflex": "反射豁免", + "Saves": "存档", + "SaveWill": "意志豁免", + "Search": "搜索", + "Search Descriptions": "搜索描述", + "Search Limit": "搜索限制", + "Search 'n Pick": "搜索/添加", + "Secondary": "次要", + "Sector Map Points": "区域地图标记", + "Security": "安全性", + "Selected Chars": "选中角色", + "Set": "设置", + "Set Veil Thickness to the following amount:": "将视界厚度设置为以下数量:", + "Settings": "设置", + "Shield": "盾牌", + "Ship": "星舰", + "ShipVendor": "船只商人", + "Shirt": "衣服", + "Shoulders": "肩部", + "Show": "显示", + "Show a list of NPCs that may have quest objectives or other interesting features <color=yellow>(Warning: Spoilers)</color>": "显示可能具有任务目标或其他有趣特性的NPC列表 <color=yellow>(警告:剧透)</color>", + "Show Acronyms in Spell/Ability/Item Pop-Ups": "在法术/能力/物品弹出窗口中显示首字母缩写", + "Show All": "显示所有", + "Show Blueprint Portrait Picker": "显示蓝图头像选择器", + "Show Blueprint Voice Picker": "显示蓝图声音选择器", + "Show Character filter choices": "显示角色过滤选项", + "Show Comments (some in Russian)": "显示评论(部分为俄语)", + "Show Custom Portrait Picker": "显示自定义头像选择器", + "Show Display & Internal Names": "显示名称和内部名称", + "Show Everything When Leaving Map": "离开地图时展示所有物品", + "Show Friendly": "显示友军", + "Show GameID": "显示GameID", + "Show GUIDs": "显示GUIDs", + "Show Internal Names": "显示内部名称", + "Show Rarity Tags": "显示稀有度标签", + "Show reasons you can not equip an item in tooltips": "在提示中显示无法装备物品的原因", + "Show Tree": "显示分支树", + "Show Unavailable Responses": "显示非可用回答", + "Show Unrevealed Steps": "显示未显示的步骤", + "Size": "尺寸", + "Skill Checks: Take 1": "技能检定:掷1", + "Skill Checks: Take 25": "技能检定:掷25", + "Skill Checks: Take 50": "技能检定:掷50", + "SkillAthletics": "运动", + "SkillAwareness": "觉察", + "SkillCarouse": "狂欢", + "SkillCoercion": "强迫", + "SkillCommerce": "商业", + "SkillDemolition": "爆破", + "SkillLogic": "逻辑", + "SkillLoreImperium": "帝国学识", + "SkillLoreWarp": "亚空间学识", + "SkillLoreXenos": "异形学识", + "SkillMedicae": "医疗", + "SkillPersuasion": "沟通", + "SkillTechUse": "技术使用", + "Small": "小型", + "Some items might be invisible until looted": "有些物品在掉落之前可能是不可见的", + "Some responses such as comments about your mythic powers will always choose the first one by default. This will show a copy of the answer and the condition for each possible response that an NPC might make to you based on": "某些回应(如关于你神话力量的评论)默认始终选择第一个。这将显示每种可能的回复,以及NPC可能做出的每种可能回复的条件", + "Sort By Count": "按数量排序", + "Soul Marks": "灵魂印记", + "source: ": "来源:", + "Spawn": "生成", + "Speed": "移动速度", + "Speeds up or slows down the entire game (movement, animation, everything)": "加速或减速整个游戏(移动、动画、一切)", + "Spellbooks": "法术书", + "Spells": "法术", + "Spontaneous Spells Per Day": "每日自发法术位", + "StarshipAmmo": "星舰弹药", + "StarshipArmorPlating": "星舰装甲板", + "StarshipAugerArray": "星舰占卜仪阵列", + "StarshipBridge": "星舰舰桥", + "StarshipGellerFieldDevice": "星舰盖勒力场装置", + "StarshipItem": "星舰物品", + "StarshipLifeSustainer": "星舰生命维持器", + "StarshipPlasmaDrives": "星舰等离子引擎", + "Starships": "星舰", + "StarshipVoidShieldGenerator": "星舰虚空护盾发生器", + "StarshipWarpDrives": "星舰曲速引擎", + "StarshipWeapon": "星舰武器", + "Start": "开始", + "Stats": "属性", + "status: ": "状态:", + "Steal from living NPCs": "从存活剧情人物身上偷窃", + "Strip HTML (colors) from Logs Tab in Unity Mod Manager": "从Unity Mod Manager日志选项卡中移除HTML(颜色)", + "Strip HTML (colors) from Native Console": "从原生控制台中移除HTML(颜色)", + "Summons": "召唤物", + "System Map": "系统地图", + "Teleport": "传送", + "Teleport Party To You": "将队伍传送到你身边", + "The following skill check adjustments apply only out of combat": "以下技能检定调整仅适用于战斗外", + "This <b>incredibly dangerous</b> setting overrides the default behavior of failing to load saves depending on missing blueprint mods. This desperate action can potentially enable you to recover your saved game, though you'll have to respec at minimum.": "这个<b>极其危险</b>的设置会覆盖默认的存档加载失败行为,这种绝望的行动可能会让你恢复你的存档,尽管你需要重新分配。", + "This allows characters you control to move through the selected category of units during combat": "这允许你控制的角色在战斗中的移动可以穿过所选类别的单位", + "This allows you to adjust pitch (Camera Tilt) by holding down Mouse3 (which previously just rotated)": "这允许你通过按住鼠标中键(之前只是旋转)来调整俯仰(相机倾斜)", + "This also includes companions who left the party such as Wenduag if you picked Lann": "这也包括那些离开队伍的同伴,比如如果你选择了兰恩,就会有雯朵格", + "This directly changes your character level but will not change exp or adjust any features associated with your character. To do a normal level up use +1 Lvl above. This gets recalculated when you reload the game. ": "这将直接改变你的角色等级,但不会改变经验或调整任何与你的角色相关的特性。如果是想进行一次正常的升级,请使用上方的lv右侧的+1。当你重新加载游戏时,这将被重新计算。", + "This hides map pins of loot containers containing at most the selected rarity. <color=orange>Note: Changing settings requires reopening the map.</color>": "这隐藏了战利品界面的地图标记,最多只包含选定稀有度的物品。<color=orange>注意:更改设置需要重新打开地图。</color>", + "This is intended for you to be able to enjoy the game while using mods that enhance your quality of life. Please be mindful of the player community and avoid using this mod to trivialize earning prestige achievements like Sadistic Gamer. The author is in discussion with Owlcat about reducing the scope of achievement blocking to just these. Let's show them that we as players can mod and cheat responsibly.": "这是为了让你能够享受游戏,同时使用mod,提高你的生活质量。请注意玩家社区,避免使用此mod来简化获得像受虐历程这样的威望成就。作者正在与Owlcat讨论将成就禁止范围减少到仅这些情况。让我们向他们展示,作为玩家,我们可以负责任地使用mod和作弊", + "This is the current voice!": "这是当前声音!", + "This makes loot function like Diablo or Borderlands. <color=orange>Note: turning this off requires you to save and reload for it to take effect.</color>": "这使得战利品功能类似暗黑破坏神或无主之地。<color=orange>注意:关闭此项需要保存并重新加载才能生效。</color>", + "This resets all the skill check rolls for all interactable objects in the area": "这会重置区域内所有可交互物体的所有技能检定结果", + "This sets your experience to match the current value of character level": "这将使你的经验值与角色当前等级的经验值相匹配", + "This turns on the developer console which lets you access cheat commands, shows a FPS window (hide with F11), etc.\n<b><color=yellow>Warning: </color></b><color=orange>You may need to restart the game for this to fully take effect</color>": "这会打开开发者控制台,让你可以访问作弊命令,显示FPS窗口(F11隐藏)等。\n<b><color=yellow>警告:</color></b><color=orange>你可能需要重新启动游戏才能完全生效</color>", + "This will change the color of NPC names on the highlight makers and change the color map markers to indicate that they have interesting or conditional interactions": "这将改变高亮的NPC名称颜色,并更改地图标记颜色,以表示他们拥有有趣或有条件的交互", + "This will change the color of NPC names on the highlike makers and change the color map markers to indicate that they have interesting or conditional interactions": "这将改变高亮的NPC名称颜色,并更改地图标记颜色,以表示他们拥有有趣或有条件的交互", + "Tiny": "微小", + "To permanently remove these modded blueprint dependencies, load the damaged saved game, change areas, and then save the game. You can then respec any characters that were impacted.": "要永久移除此mod的蓝图依赖,请加载损坏的存档,更换区域,然后保存游戏。然后你可以重新分配那些受到影响的角色。", + "ToyBox Archeologists can tag confusing puzzle pieces with green numbers in the game world and for inventory tool tips it will show text like this: <b><color=yellow>[PuzzlePiece Green3x1]</color></b><b><color=orange>\nNOTE: </color></b><color=orange>Needs game restart to take efect</color>": "ToyBox考古学家可以在游戏世界中用绿色数字标记令人困惑的拼图碎片,对于背包工具提示,它将显示这样的文本:<b><color=yellow>[拼图碎片 绿3x1]</color></b><b><color=orange>\n注意:</color></b><color=orange>需要重新启动游戏才能生效</color>", + "ToyBox can patch some critical bugs in Rogue Trader Beta, including the following:": "ToyBox可以修复《正义之怒Beta》中的一些关键错误,包括以下内容:", + "ToyBox has limited functionality from the main menu": "ToyBox在游戏主菜单下功能有限", + "Trace": "追踪", + "Travel Speed": "旅行速度", + "TurretRadius": "炮塔半径", + "TurretRating": "炮塔评级", + "Tweaks": "调整", + "Units": "单位", + "Units CR": "单位等级", + "Unlimited Actions During Turn": "回合内无限行动", + "Unlimited Stacking of Modifiers (Stat/AC/Hit/Damage/Etc)": "调整值无限叠加(属性/AC/攻击/伤害/等)", + "Unlock": "解锁", + "Unstart": "取消开始", + "Usable": "可用物品", + "Vendor Buy Price": "商人购买价格", + "Vendor Sell Price": "商人出售价格", + "Visual Character Size Multiplier": "角色视觉尺寸倍数", + "WarhammerAgility": "敏捷", + "WarhammerBallisticSkill": "射击技能", + "WarhammerFellowship": "魅力", + "WarhammerInitialAPBlue": "初始蓝色行动点", + "WarhammerInitialAPYellow": "初始黄色行动点", + "WarhammerIntelligence": "智力", + "WarhammerPerception": "感知", + "WarhammerStrength": "力量", + "WarhammerToughness": "坚韧", + "WarhammerWeaponSkill": "武器技能", + "WarhammerWillpower": "意志力", + "Warning": "警告", + "Weapon": "武器", + "Weapons": "武器", + "When enabled and you hold down the camera follow key (usually f) the camera will keep following the unit until you release it": "启用后,按住相机跟随键(通常是F),相机会一直跟随单位,直到你松开它", + "When loading a game this will go right into the game without having to 'Press any key to continue'": "加载游戏时将直接进入游戏,而无需“按任何键继续”。", + "When ticked this shows ToyBox commands in the combat log which is helpful for you to know when you used the shortcut": "勾选后,战斗日志中会显示ToyBox指令,这有助于您了解何时使用了快捷键", + "Whole Party": "全队", + "Whole Team Moves Same Speed": "全队移动同速", + "will be used for editing ": "将用于编辑", + "Wrist": "腕部", + "You": "你", + "You can enable hot keys to teleport members of your party to your mouse cursor on Area or the Global Map": "你可以启用热键将队友传送到区域或大地图的鼠标光标位置" + } +} \ No newline at end of file diff --git a/ToyBox/ModDetails/0ToyBox0.jpg b/ToyBox/ModDetails/0ToyBox0.jpg new file mode 100644 index 000000000..f2f17eebc Binary files /dev/null and b/ToyBox/ModDetails/0ToyBox0.jpg differ diff --git a/ToyBox/ModDetails/Incompatibilities.json b/ToyBox/ModDetails/Incompatibilities.json new file mode 100644 index 000000000..a2c80a884 --- /dev/null +++ b/ToyBox/ModDetails/Incompatibilities.json @@ -0,0 +1,8 @@ +[ + [ "1.7.27", "1.5.0.0" ], + [ "1.7.22", "1.5.0.0" ], + [ "1.7.19", "1.4.1.0" ], + [ "1.7.15", "1.4.0.0" ], + [ "1.6.8", "1.2.1.7" ], + [ "1.5.14", "1.2.0.23" ] +] diff --git a/ToyBox/ModDetails/Info.json b/ToyBox/ModDetails/Info.json new file mode 100644 index 000000000..8f08eafa8 --- /dev/null +++ b/ToyBox/ModDetails/Info.json @@ -0,0 +1,12 @@ +{ + "Id": "0ToyBox0", + "DisplayName": "ToyBox for RogueTrader", + "Author": "Narria, ADDB", + "Version": "1.7.31", + "ManagerVersion": "0.23.0", + "Requirements": [], + "AssemblyName": "ToyBox.dll", + "EntryMethod": "ToyBox.Main.Load", + "HomePage": "https://github.com/xADDBx/ToyBox-RogueTrader", + "Repository": "https://raw.githubusercontent.com/xADDBx/ToyBox-RogueTrader/main/ToyBox/ModDetails/Repository.json" +} \ No newline at end of file diff --git a/ToyBox/ModDetails/OwlcatModificationManifest.json b/ToyBox/ModDetails/OwlcatModificationManifest.json new file mode 100644 index 000000000..795b394f9 --- /dev/null +++ b/ToyBox/ModDetails/OwlcatModificationManifest.json @@ -0,0 +1,12 @@ +{ + "UniqueName": "0ToyBox0", + "Version": "1.7.31", + "DisplayName": "ToyBox for RogueTrader", + "Description": "Use Workshop-description.txt", + "Author": "Narria, ADDB", + "ImageName": "0ToyBox0.jpg", + "WorkshopId": "3107755014", + "Repository": "https://github.com/xADDBx/ToyBox/tree/RT", + "HomePage": "https://github.com/xADDBx/ToyBox/releases", + "Dependencies": [] +} \ No newline at end of file diff --git a/ToyBox/ModDetails/Repository.json b/ToyBox/ModDetails/Repository.json new file mode 100644 index 000000000..87ea6e0ca --- /dev/null +++ b/ToyBox/ModDetails/Repository.json @@ -0,0 +1,8 @@ +{ + "Releases": [ + { + "Id": "0ToyBox0", + "Version": "1.7.31" + } + ] +} \ No newline at end of file diff --git a/ToyBox/ModDetails/Workshop-description.txt b/ToyBox/ModDetails/Workshop-description.txt new file mode 100644 index 000000000..1125bbf0c --- /dev/null +++ b/ToyBox/ModDetails/Workshop-description.txt @@ -0,0 +1,36 @@ +[h2]Information[/h2] +Mod with Cheats, Tweaks and Quality of Life Improvements +[list] +[*]Bag of Tricks: +[list] +[*]Allow both male and female RTs to romance Heinrix and Cassia +[*]Allow Remote Companions to join into dialog +[*]Modify Faction Reputation, Navigator's Insight, Scrap and more +[*]Experience Multiplier +[*]Dice Roll Cheats/Options +[*]Enable Achievements with Mods installed +[/list] +[*]Level Up: Make Respec start from Level 0 for you and Companions +[*]Party Editor: Modify your own conviction and/or stats or features or portraits or voices of party members +[*]Search 'n Pick: Search for ingame items/stuff and spawn/teleport/give yourself things! +[*]Etudes: Fix your progression by changing states! +[*]Colonies: Modify different Colony Resources +[/list] + +Warning: ToyBox for Warhammer 40,000: Rogue Trader is a complex mod. Save early and often. +Note: If you find non-functioning features, please report them. +Note: Using Mods will disable Achievements. There is an option to re-enable achievements in the Bag of Tricks category of this mod. + +[h2]Installation[/h2] +Should be automatic for Steam Players as of Game Version 1.1.52. +Open the mod menu using CTRL+F10 once in-game. (Don't use Shift+F10; that's another Mod Menu which ToyBox does not use). + +[url=https://www.reddit.com/r/RogueTraderCRPG/comments/18cxbyo/modding_in_rogue_trader/]Information on Modding in Rogue Trader[/url] +[url=https://github.com/xADDBx/ToyBox/blob/RT/ToyBox/ReadMe.md]Release Notes[/url] + +Credits to: +[list] +[*] Narria, the original creator of the mod who is currently busy which is why I'm maintaining the mod for now +[*] CascadingDragon, the writer of the installation instructions +[*] Other people from the Owlcat Discord who helped me with modding. +[/list] \ No newline at end of file diff --git a/ToyBox/Properties/AssemblyInfo.cs b/ToyBox/Properties/AssemblyInfo.cs deleted file mode 100644 index f49e8733e..000000000 --- a/ToyBox/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ToyBox")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ToyBox")] -[assembly: AssemblyCopyright("Copyright © 2021")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("22dcb4e1-d979-4ea9-913a-4ee1634b4ded")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.*")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ToyBox/ReadMe.md b/ToyBox/ReadMe.md index 47e4d36f5..9f7613091 100644 --- a/ToyBox/ReadMe.md +++ b/ToyBox/ReadMe.md @@ -1,114 +1,668 @@ # ToyBox -### Now with 400+ Cheats, Tweaks and Quality of Life Improvements -* **Bag of Tricks**: 142 (or 173 depending on how you count) -* **Level Up & Multiclass**: 57 -* **Party Editor**: 67 -* **Loot Checklist**: 4 -* **Enchantment**: 20 ways to view add, remove enchantments from your favorite items -* **Search 'n Pick**: 75 ways to view, add, remove blueprints plus a fun global teleportation feature -* **Crusade**: 38 -* **Armies**: 22 -* **Events/Decrees**: 9 -* **Etudes**: 6225 x 3 (start, unstart, complete) -* **Quest Resolution**: 4 - -**Please backup early and backup often.** - -### Install & Setup -1. Install the Unity Mod Manager. -1. Install the mod using the Unity Mod Manager or extract the archive to your game's mod folder (e.g. '\Steam\steamapps\common\Pathfinder Second Adventure\Mods'). -1. Start the game and load a save or start a new save (the mod's functions can't accessed from the main menu). -1. Open the Unity Mod Manager by pressing CTRL + F10. -1. Adjust the settings in the mod's menu -1. Important this mod is designed to be run at 1920x1080 or higher. -1. Please set your resolution to at least 1920x1080 -1. Go to Settings tab on Unity Mod Manager to set your screen width to at least 1920 wide - -### Usage -* **Bag of Tricks**: this is a collection of quality of life, quick cheats, settings, multipliers, etc from the awesome Kingmaker mod of the same name plus a bag or two of new tricks ^_^ -* **Level Up & Multiclass**: a variety of character creation, level up, unlock mythic paths plus support for multiple classes per level up and gestalt gameplay -* **Party Editor**: lets you edit almost any aspect of your character. Make sure you explore all the different disclosure toggles. You can edit classes, stats, facts (feats and more), buffs, abilities, spells and spellbooks as well as the composition of your party -* **Loot Coloring & Checklist**: this lets you enable a loot grading and coloring system similar to Borderlands or Diablo. It also gives you a screen where view all the items in an area that you have not looted yet. -Enchantment: allows you to add or remove enchantments from the items in your inventory -* **Search 'n Pick**: this lets you search through all the available resources (items, feats, abilities, spells and many more) and manipulate your game state in an almost limitless set of ways. You can add/remove items, feats, abilities, etc. You can spawn any unit. You can start/unstart/complete etudes, quests, etc. You can teleport to any area in the game. It is almost unimaginable how much you can do in here so keep digging! -* **Crusade**: this allows you to edit various aspects of your crusade state. -* **Armies**: this allows you to edit the composition and stats of your armies -* **Etudes**: this is a new and exciting feature that allows you to see for the first time the structure and some basic relationships of Etudes and other Elements that control the progression of your game story. Etudes are hierarchical in structure and additionally contain a set of Elements that can both conditions to check and actions to execute when the etude is started. As you browe you will notice there is a disclosure triangle next to the name which will show the children of the Etude. Etudes that have Elements will offer a second disclosure triangle next to the status that will show them to you. -WARNING: this tool can both miraculously fix your broken progression or it can break it even further. Save and back up your save before using. Remember that "with great power comes great responsibility" -* **Quest Resolution**: this allows you to view your active quests and advance them as needed to work around bugs or skip quests you don't want to do. Be warned this may break your game progression if used carelessly. -### Ver 1.5.2 (Comming Soon) -**ToyBox 1.5.2** ***Preview*** **May 12, 2023i** - * (***Narria***) Improved Intersting NPC calaculations - * (***Narria***) Improved the UI for showing data about interesting NPCS so now the highlight will disappear when conditions are not met (this is not perfect but will improve over time) - * Entries for the same condition are merged when they occur for multiple sources - * Fixed issue for Intersting NPCs show all where it was showing other copies of the NPCs that were not really in the area at the current time. - * Added toggle under Show All to show the other versions of NPCs - * (***Narria***) Overtips showing interesting NPCs now update immediately when their state changes (like after giving all your elven pages to the storyteller) + +### Install & Setup (Rogue) + +1. Download the ToyBox mod file and unzip +1. If the folder is not already named 0ToyBox0 please rename it to that +1. Launch the game at least once. +1. **Please note that the game comes with its own built in Unity Mod Manager so you do not need to install another one** +1. Navigate to %userprofile%\AppData\LocalLow\Owlcat Games\Warhammer 40000 Rogue Trader\UnityModManager\ +1. An example path is C:\Users\PC\AppData\LocalLow\Owlcat Games\Warhammer 40000 Rogue Trader\UnityModManager\ +1. Copy 0ToyBox0 into the UnityModManagerFolder +1. Launch Rogue Trader and you may need to hit ctrl+F10 to see the mod manager window +1. Load a save or start a new game to get the most out of of the mod + +* Warning: ToyBox for Warhammer 40,000: Rogue Trader is a complex mod. Save early and often. +* Note: If you find non-functioning features, please report them. + +### Usage +Here is a summarized list of features. This list only includes a part of the features contained in the mod. + +- **Bag of Tricks**: + * Allow both male and female RTs to romance Heinrix and Cassia + * Allow Remote Companions to join into dialog + * Modify Faction Reputation, Navigator's Insight, Scrap and more + * Experience Multiplier + * Dice Roll Cheats/Options + * Enable Achievements with Mods installed +- **Level Up**: Make Respec start from Level 0 for you and Companions +- **Party Editor**: Modify your own conviction and/or stats or features or portraits or voices of party members or Respec Characters for free +- **Colonies**: Modify different Colony Resources +- **Search 'n Pick**: this lets you search through various available things (items, feats, abilities, unity and locations) and spawn/give/summon them + and manipulate your game state in an almost limitless set of ways. You can add/remove items, feats, abilities, + etc. You can spawn any unit. You can start/unstart/complete etudes, quests, etc. You can teleport to any area in the + game. It is almost unimaginable how much you can do in here so keep digging! +- **Etudes**: this is a new and exciting feature that allows you to see for the first time the structure and some basic + relationships of Etudes and other Elements that control the progression of your game story. Etudes are hierarchical in + structure and additionally contain a set of Elements that can both conditions to check and actions to execute when the + etude is started. As you browe you will notice there is a disclosure triangle next to the name which will show the + children of the Etude. Etudes that have Elements will offer a second disclosure triangle next to the status that will + show them to you. + WARNING: this tool can both miraculously fix your broken progression or it can break it even further. Save and back up + your save before using. Remember that "with great power comes great responsibility" +- **Quest Resolution**: this allows you to view your active quests and advance them as needed to work around bugs or + skip quests you don't want to do. Be warned this may break your game progression if used carelessly. + +### Toybox Rogue - Ver 1.7.31 (built for 1.5.0.300) +* (***ADDB***) Minor adjustments to love is free: Improved PcMale and PcFemale overrides; overriding only when necessary to get the romancy response. + +### Toybox Rogue - Ver 1.7.30 (built for 1.5.0.293) +* (***ADDB***) Fix per-save settings loading too late, thereby breaking some features (e.g. Visual Size Multiplier) + +### Toybox Rogue - Ver 1.7.29 (built for 1.5.0.293) +* (***ADDB***) Fix preview units not counting cheated base values for some special stats (e.g. Psy Rating) + +### Toybox Rogue - Ver 1.7.28 (built for 1.5.0.293) +* (***ADDB***) Fix somewhat serious conflict when MicroPatches, ToyBox and other Owlmods that had BlueprintPatches were installed. Details: + * When all three mentioned components where installed, ToyBox threaded Blueprint Loading would have errors; causing significant issues down the line + * When BPIdCache is enabled; the first start after an update will always do a threaded blueprint load, causing ***people to be stuck at 42% or so in game startup*** for that first start (subsequent starts previously worked normally as long as nothing that loaded blueprints was used in ToyBox). + +### Toybox Rogue - Ver 1.7.27 (built for 1.5.0.293) +* (***ADDB***) 1.5 Compat: + * The color fixup in 1.7.23 caused various issues in threads; those should be fixed (notably by Search 'n Pick working again) + * Fixed DisclosureToggle vertical text offset + * Fixed Party UI spacing issue on different ui scale +* (***ADDB***) Prevent BPLoader from running too early if GUI is opened directly + +### Toybox Rogue - Ver 1.7.25 (built for 1.5.0.261) +* (***ADDB***) Re-implement size change features under Party => Stats: + * View multiplier and size override can now both be applied at the same time (previously any size override would render view multiplier useless). + * Polymorphs (pretty much exclusively tied to cutscenes in this game) should no longer break/reset the size. + +### Toybox Rogue - Ver 1.7.24 (built for 1.5.0.261) +* (***Ethyleye***) Updated Chinese localization +* (***ADDB***) Fix oversized guid tooltip on items + +### Toybox Rogue - Ver 1.7.23 (built for 1.5.0.261) +* (***ADDB***) 1.5 Compat: + * A new way to calculate Skillcheck Experience was added; that's now covered under the existing multipliers again + * Remove "Make DLC1 theme play again in main menu" as there is a base game setting by now + * Fix TB AssetLoader (UnityEngine.ImageConversionModule targetting netstandard2.1 and using System.ReadOnlySpan`1 thereby causing a compile error) + * Fix sudden line wrapping issues caused by engine update + * Kinda fix color issues caused by engine update +* (***ADDB***) Readd old Refill consumable feature since people complained about it being removed. I had new implementation; but the person who helped test it said there's an issue with it and then disappeared. + +### Toybox Rogue - Ver 1.7.22 (built for 1.4.1.231) +* (***ADDB***) Prevent early cctor calls because of static Player fields by turning those into property getters + +### Toybox Rogue - Ver 1.7.21 (built for 1.4.1.231) +* (***ADDB***) Change how Enemy Sliders were implemented: They should now take affect *after* existing modifiers are applied, meaning a x2 for hp should double hp. Thanks @Meagermantis1 for helping with testing. +* (***ADDB***) Remove some unnecessary stats from enemy difficulty sliders +* (***ADDB***) Fix Experience multiplier for some mobs and skill checks. +* (***ADDB***) Proper Buff Rank handling +* (***ADDB***) Remove "Refill consumables in belt slots if in inventory" as it is buggy. If anyone is actually using this reach out and we'll attempt to fix it. + +### Toybox Rogue - Ver 1.7.20 (built for 1.4.1.217) +* (***ADDB***) Fix ToyBox breaking Owlmods in weird/random ways when it loads blueprints. (This is considered a major issue so previous versions will be marked as incompatible) + +### Toybox Rogue - Ver 1.7.19 (built for 1.4.1.208) +* (***ADDB***) Fix issue with threaded loader +* (***ADDB***) PatchTool: Allow dumping sprites +* (***ADDB***) Catch more exceptions for BP GetTitle + +### Toybox Rogue - Ver 1.7.18 (built for 1.4.1.206) +* (***ADDB / Microsoftenator***) Party => Mechadendrites Editor that allows adding/removing Mechadendrites? +* (***ADDB***) Fix respec for units with pets +* (***ADDB***) Refill Belt Consumables no longer works for enemies (???) +* (***ADDB***) Enemy stat adjustments now allow fractional multipliers +* (***ADDB***) Fix Version Checker +* (***ADDB***) Fail more gracefully +* (***ADDB***) "Don't use any AP during your turn" child feature is now automatically disabled when its parent is disabled. +* (***bymck***) Partial Turkish Localization? + +### Toybox Rogue - Ver 1.7.17 (built for 1.4.0.185) +* (***Deltharis***) Add new Arbitrator archetype to Bag of Tricks => Override Story Occupation +* (***ADDB***) Fix Search 'n Pick issue where certain modded blueprints would have Element.GetCaption throw an uncaught exception. +* (***ADDB***) PatchTool: Special case certain Unity Value types (e.g. Vector2) and allow instantiating them. + +### Toybox Rogue - Ver 1.7.16 (built for 1.4.0.185) +* (***ADDB***) Probably fixed issue where ToyBox could cause a "Unable to change equipment in combat" warning. + +### Toybox Rogue - Ver 1.7.15 (built for 1.4.0.184) +* (***ADDB***) Added Bag of Tricks => Quality of Life feature to play DLC1 main menu theme again. +* (***ADDB***) DLC2 Fixes: Stuff related to the following things changed: some dialog features; stuff related to shields; Party Editor for pets +* (***ADDB***) Allow adding flat and percent boosts to enemy stats (at the bottom of Bag of Tricks); e.g. Enemy Health Multiplier +* (***ADDB***) Reset level now works in Party Tab +* (***ADDB***) Fix Skip Splash Screen +* (***ADDB***) Prevent log spam +* (***ADDB***) PatchTool: Fix rare UI crash +* (***ADDB***) PatchTool: Auto-Generate Components and Elements. ***Those need (unique) names or it could cause problems*** (e.g. in rare cases it can prevent saving). I still suggest creating your own name! This is merely a fix to stop some accidental issues +* (***ADDB***) PatchTool: Now ignore whether the element type of an array or generic constraint is abstract or an interface; e.g. properly allowing instantiation of Condition[]. +* (***ADDB***) PatchTool: Now restrict new list elements to the type of the actual list object, not the field type. This could previously cause issues where the patch failed. +* (***ADDB***) PatchTool: Introduced Dangerous Patches +* (***ADDB***) PatchTool: Fixed some bugs related to Unity Objects +* (***ADDB***) PatchTool: Apply Patches later so that it runs after most content mods. + +### ToyBox Rogue - Ver 1.7.13 built for Rogue Trader 1.3.1.11 +* (***ADDB***) PatchTool: List elements now properly show all their fields instead of only the fields of the list type +* (***ADDB***) PatchTool: Properly track Element-types created via field Instantiation +* (***ADDB***) PatchTool: Fix collections filled with null elements breaking ui +* (***ADDB***) PatchTool: Catch error when target blueprint is missing (e.g. due to mod changes) +* (***ADDB***) PatchTool: Support sbyte +* (***ADDB***) Apply performance Transpilers only once. Whoopsie. + +### ToyBox Rogue - Ver 1.7.12 built for Rogue Trader 1.3.1.11 +* (***ADDB***) PatchTool: Fix regression which broke enum patches +* (***ADDB***) PatchTool: Properly deserialize nested operations with the custom converter + +### ToyBox Rogue - Ver 1.7.11 built for Rogue Trader 1.3.1.11 +* (***ADDB***) PatchTool: Finish implementing patch versioning (I forgot) + +### ToyBox Rogue - Ver 1.7.10 built for Rogue Trader 1.3.1.11 +* (***ADDB***) PatchTool: Make patch .json files better readable by serializing enums as strings and adding a No-Op when enums are unused. +* (***ADDB***) PatchTool: Show failed patches in the list as actually failed. +* (***ADDB***) PatchTool: Various Patcher optimizations, which very vastly improved initial load time for patches. +* (***ADDB***) PatchTool: Make it possible to indirectly modify localization of things by allowing changing of keys under LocalizedString => Shared => LocalizedString +* (***ADDB***) PatchTool: Prevent broken Blueprint references from crashing the UI. +* (***ADDB***) PatchTool: Prevent internal ToString exception from crashing the UI. +* (***ADDB***) Prevent CompanionInParty.CheckCondition patch from causing exceptions on game load by no longer getting companion state of disposed units + +### ToyBox Rogue - Ver 1.7.9 built for Rogue Trader 1.3.1.6 +* (***ADDB***) Fix Dice Rolls Cheats (e.g. "Always Roll 1") using the wrong unit as initiator for the damage negation roll from the "The Emperor Protects" feature. E.g. this could result in enemies being invincible if "Always Roll 1" is activated for the Party. +* (***ADDB***) Fix "Ignore Talent Prerequisites", "Ignore Required Stat Values" and "Ignore Required Class Levels" not working for inverted checks. *Note: Unconfirmed since nobody was available for testing* +* (***ADDB***) Fix localization removing Add/Remove buttons from Party Editor Browsers. +* (***ADDB***) Patch Tool: Remove NameSpace from generic types, making list types a lot more readable +* (***ADDB***) Patch Tool: Implement being able to null/delete field values (hidden by default) +* (***ADDB***) Patch Tool: Implement being able to create field values (hidden by default) +* (***ADDB***) Patch Tool: Add toggle to keep fields open after changing a value +* (***ADDB***) Patch Tool: Rewrite UI to be path based --> Allow patching struct types +* (***ADDB***) Patch Tool: Fix patching some Blueprints causing CTDs +* (***ADDB***) Patch Tool: Fix patching some other Blueprints causing CTDs +* (***ADDB***) Patch Tool: Fix adding new elements to Collections of references breaking things +* (***ADDB***) Add Slider to Enhanced Camera to (persistently) offset the Camera Elevation by a specified value + +### ToyBox Rogue - Ver 1.7.8 built for Rogue Trader 1.3.0.57 +* (***ADDB***) Fix somewhat critical error in PatchTool which would DeepCopy Blueprints, messing up cached references (noticeable e.g. when Proficiency Requirements aren't satisfied after patching something until the game is restarted once). +* (***ADDB***) No longer ignore delegates when Deep Copying => Fix e.g. Abilities disappearing from Weapons after patching them until restarting the game. +* (***ADDB***) Prevent possible issue with Patch Tool?. +* (***ADDB***) Fixed Patch Tool not being able to manually remove the last patch operation in the Patch Manager. +* (***ADDB***) BPId Cache now Caches Items, Weapons and Armors (mostly noticeable in that the BlueprintPicker in PatchTool now has those as categories). + +### ToyBox Rogue - Ver 1.7.7 built for Rogue Trader 1.3.0.57 +* (***Di-Crash***) Add "Skeleton replacer" to Party => Stats + * This feature allows tweaking the characters experience in a way. + * Using different sliders, you can e.g. modify your characters height and/or proportions, offsets etc. + * It's a little more involved, but offers quite a bit of freedom. +* (***jonHinkerton***) Add Button (allows binding to hotkey) to open faction trade vendor window from anywhere. +* (***ADDB***) Add "Show hidden loot in Checklist" to the Loot Tab. The checklist doesn't show loot which wasn't seen yet. +* (***Di-Crash***) Minor UI adjustments in Party => Stats. +* (***ADDB***) Patch Tool fixes: + * Fix Patch Tool for types implementing IList<T> + * Fix Patch Tool possible stack overflow in DeepCopy + +### ToyBox Rogue - Ver 1.7.6 built for Rogue Trader 1.2.1.26 +* (***ADDB***) Fix Patch Tool not being able to modify top level primitives. +* (***ADDB***) Improve Patch Tool UI (now highlighting currently selected row). + +### ToyBox Rogue - Ver 1.7.5 built for Rogue Trader 1.2.1.26 +* (***ADDB***) Fixed major issue in the Patch Tool system (fields in a collection could cause the patch to fail with a System.ArgumentException). +* (***ADDB***) List elements now have their exact type shown. + +### ToyBox Rogue - Ver 1.7.4 built for Rogue Trader 1.2.1.26 +* (***ADDB***) Fix NRE in Patch Tool (Fix Deep Copy throwing when Object.GetHashCode throws). + +### ToyBox Rogue - Ver 1.7.3 built for Rogue Trader 1.2.1.26 +* (***ADDB***) Add LoadPreset BP Action for BlueprintAreaPresets. +* (***ADDB***) Patch Tool: + * Creating a new Element or Component now automatically assigns the Owner Blueprint. + * Fix null strings crashing UI. + * Fix UI Clipboard Label inverted Logic. +* (***ADDB***) Prevent certain exceptions in Search 'n Pick with modded BPs + +### ToyBox Rogue - Ver 1.7.1 built for Rogue Trader 1.2.1.26 +* (***ADDB***) Various Patch Tool UI fixes + +### ToyBox Rogue - Ver 1.7.0 built for Rogue Trader 1.2.1.26 +* (***ADDB***) Finished Patch Tool: + * Patch tool blueprint picker and blueprint references support. + * Reworked UI some more + * Allow filtering shown fields + * Allow removing the last PatchOperation in a patch (to undo a change) +* (***ADDB***) Reimplemented Allow Equipment Change During Combat. That one was more complicated to fix than I expected... +* (***ADDB***) Hide Loot Coloring Options since we don't actually have a heuristic to determine how valuable an object is. + +### ToyBox Rogue - Ver 1.6.12 built for Rogue Trader 1.2.1.19 +* (***ADDB***) Rewrite the Psychic Phenomena customizer since it seems like it might've caused crashes for some people. +* (***ADDB***) Patch tool stuff: + * UI improvement (spaces should now be more consistent) + * Collection support (it is now possible to add and remove items to collections) + * Flag enum support + * Multiple tab support + * UI to manage existing patches + * Fix some patch tool bugs + +### ToyBox Rogue - Ver 1.6.11 (DEBUG) built for Rogue Trader 1.2.1.19 +* (***ADDB***) Initial Blueprint Patcher Impl. +* (***ADDB***) Reinstall on checksum failure now reinstalls current version. +* (***ADDB***) Fix very rare issue with Browser. + +### ToyBox Rogue - Ver 1.6.10 built for Rogue Trader 1.2.1.19 +* (***ADDB***) Added check for corrupted mod files. +* (***ADDB***) Added notice gui when the mod is opened for the first time. +* (***ADDB***) Changed auto-update to make it less error-prone. + +### ToyBox Rogue - Ver 1.6.9 built for Rogue Trader 1.2.1.19 +* (***ADDB***) Fixed an issue where game files included faulty elements, which could potentially affect some answers not being shown is Dialog Preview was enabled. (Nobody confirmed any missing answers but I'll rate this as critical anyways). + +### ToyBox Rogue - Ver 1.6.8 built for Rogue Trader 1.2.1.17 +* (***ADDB***) After getting another report about "A Card Inverted", it seems like Remote Companion Dialog had an issue about units who had yet to spawn, sometimes preventing dialog options to appear. This should be fixed now. +* (***ADDB***) Implement auto-update for incompatible outdated versions (enabled by default). +* (***ADDB***) Implement always auto-update (tries to keep you on the latest version) (disabled by default). + +### ToyBox Rogue - Ver 1.6.7 built for Rogue Trader 1.2.1.17 +* (***ADDB***) Recompile for new version. +* (***ADDB***) Change the mod display name if the mod is forcibly turned off. + +### ToyBox Rogue - Ver 1.6.6 built for Rogue Trader 1.2.1.12 +* (***ADDB***) Fixed dialog preview for ConditionHaveFullCargo and ContextConditionHasItem. + +### ToyBox Rogue - Ver 1.6.5 built for Rogue Trader 1.2.1.12 +* (***ADDB***) It seems that previous versions of ToyBox broke the "A Card Inverted" quest. It took me a while to figure out because nobody really reported the issue directly, but this should be fixed now. + +### ToyBox Rogue - Ver 1.6.4 built for Rogue Trader 1.2.1.12 +* (***ADDB***) Thanks to someone sending me a save; Actually fix the No Jealousy option for Kibella. + +### ToyBox Rogue - Ver 1.6.3 built for Rogue Trader 1.2.1.12 +* (***ADDB***) Tried to fix: No Jealousy option doesn't work for Kibella when *exactly* one other character is romanced. + +### ToyBox Rogue - Ver 1.6.2 built for Rogue Trader 1.2.1.7 +* (***ADDB***) Fix BPs not loading in various menus. + +### ToyBox Rogue - Ver 1.6.1 built for Rogue Trader 1.2.1.7 +* (***ADDB***) Fix max walk distance/Min sprint distance multipliers. + +### ToyBox Rogue - Ver 1.6.0 built for Rogue Trader 1.2.1.7 +* (***ADDB***) Fix for new version. +* (***ADDB***) Implement Ignore Career Prerequisites better. +* (***ADDB***) It is now possible to override origin for dialog purposes, making it possible to role play as a different (or multiple different) origins compared to what was chosen at character creation (Bag of Tricks => Dialog). +* (***ADDB***) Something something static Harmony Patch Analyzer => Fixes; Initiative Rolls Cheats never worked??? +* (***ADDB***) Skip "Copying..." phase when no search query is used. +* (***ADDB***) Fix the issue that Party tab browsers were flickering with Show All activated. +* (***ADDB***) Something something Browser more resistent to malformed Blueprints? +* (***ADDB***) Optimize "Collating..." phase. A lot. Like, really a lot. +* (***ADDB***) Even more BP Loader optimizations. Ugh. (In relation to that added ToyBox => Settings => "Blueprint Loader Amount of Shards"). +* (***ADDB***) Fixed blueprints sometimes loading twice in Search 'n Pick +* (***ADDB / Microsoftenator***) Optimized Blueprint Loading times even more by improving Owlcat ReflectionBasedSerializer. (Probably ~25% to 50% on top of the previous reduction). +* (***ADDB / Microsoftenator***) Fixed issue where the new BlueprintLoader could randomly blow up. +* (***ADDB***) Implemented partial loading. This means, if you aren't looking at Search 'n Pick, other tabs don't need to load every Blueprint. This massively reduces load times after the game was loaded the first time. (Certain tabs load more than 10 times faster). +* (***ADDB***) Threaded Blueprint Loading. When ToyBox needs to load blueprints it should now work faster. (Reduction of all Blueprint loading times of around ~25% to 50%). +* (***ADDB***) Added options to use BPIdCache and Automatically build said cache (by forcing a preload) in ToyBox => Settings. +* (***ADDB***) Added option to enable blueprint preloading in Settings. +* (***ADDB***) Added options to change Blueprint Loading thread amount and chunk size in ToyBox => Settings. + +### ToyBox Rogue - Ver 1.5.21 built for Rogue Trader 1.2.0.30 +* (***ADDB***) Fix Preview Dialog option disabling dialog hotkeys. (I did some refactoring for that method; if it somehow doesn't work anymore please report). +* (***ADDB***) Added option to customize Perils of the Warp under Bag of Tricks => Rogue Cheats => "Tweaks" => "Customize Psychic Phenomena/Perils of the Warp" (pretty buch at the bottom of Rogue Cheats) (***Experimental***; it should'nt break anything but I don't know if it will work as expected; Feedback would be appreciated). + +### ToyBox Rogue - Ver 1.5.20 built for Rogue Trader 1.2.0.30 +* (***ADDB***) Added Bag of Tricks => QoL => Skip Splash Screen (this is basically the mod I released a while ago built into ToyBox). +* (***ADDB***) Dice Rolls Changes (Advantage/Disadvantage now applies after never/atleast changes). +* (***ADDB***) When NeverRoll1 and 100 are both active, rolling a 20 and then a reroll in the 1 (effectively bypassing NeverRoll1) is now no longer possible. + +### ToyBox Rogue - Ver 1.5.18 built for Rogue Trader 1.2.0.28 +* (***ADDB***) Added options to start respec from before second archetype and before third archetype (Level up from 15, level up from 35). + +### ToyBox Rogue - Ver 1.5.17 built for Rogue Trader 1.2.0.28 +* (***ADDB***) Unlimited Stacking of Modifiers no longer applies to enemies. +* (***ADDB***) Added Level Up => Ignore Archetypes Prerequisites. It's buggy. It allows picking e.g. Warrior => Master Tactician. This does not allow picking 2 archetypes from the same level or skipping one. +* (***ADDB***) Renamed "Walk (no run) Range" to "Max walk distance". Also changed the implementation; it should now also work when trying to interact with something. +* (***ADDB***) Added Bag of Ticks => Other Multipliers => Min sprint distance. + +### ToyBox Rogue - Ver 1.5.16 built for Rogue Trader 1.2.0.25 +* (***ADDB***) Added Bag of Tricks => Other Multipliers => Walk (no run) Range; Adjusts how far of your character you can click and still cause your character to walk instead of run. + +### ToyBox Rogue - Ver 1.5.15 built for Rogue Trader 1.2.0.25 +* (***ADDB***) Release for new version. +* (***ADDB***) Add incompatibility checker! From now on ToyBox will send a web request to check whether the current mod version has known incompatibilities with the current game version. This is done for cases where the mod still loads after updates, but causes issues in the background (i.e. enemies only having 1 HP). If it detects incompatibilities it will stop loading. + +### ToyBox Rogue - Ver 1.5.14 built for Rogue Trader 1.1.67 +* (***ClaireDeVolta***) Add Russian Localization. + +### ToyBox Rogue - Ver 1.5.13a built for Rogue Trader 1.1.67 +* (***ADDB***) Added an option to disable Voice Over via Party => Stats. +* (***ADDB***) Improve Party GUI tabs sometimes not clickeable (especially in Nearby configuration). +* (***ADDB***) Support adding new Blueprints even after ToyBox finished loading. + +### ToyBox Rogue - Ver 1.5.12 built for Rogue Trader 1.1.67 +* (***ADDB***) Readded ArcaneTrixter's loading with Blueprint Errors (Why was it ever removed? Who knows.). +* (***ADDB***) Fixed mod for new game version. + +### ToyBox Rogue - Ver 1.5.11 built for Rogue Trader 1.1.52 +* (***ADDB***) It's now possible to change the Ranks of Stat Advancements; meaning you can now modify your character creation Stats by using Party => Features. + +### ToyBox Rogue - Ver 1.5.10d built for Rogue Trader 1.1.28 +* (***ADDB***) Added Size Change to Party => Stats (this allows changing grid size of companions; < Large = 1x1; Large = 2x2; Huge = 3x3; Gargantum = 4x4). After saving + reloading the Character Model size will change automatically depending on the new size. +* (***ADDB***) Fixes for new version (mostly fixing respec). +* (***ADDB***) Added improved glyph support detection. If you're symbols used to be nice but are now weird then please report that! +* (***ADDB***) Reworked Ascii Glyphs (for systems where default glyphs aren't supported). +* (***ADDB***) Ported to SDK style project; Code Cleanup; etc. All in all this should not influence usage experience but should make developing and building for other people easier. + +### ToyBox Rogue - Ver 1.5.10a built for Rogue Trader 1.0.102 +* (***ADDB***) Removed experimental invisibility fix because it didn't work? +* (***ADDB***) Removed some stuff related to Outfits which wasn't really finished. +* (***ADDB***) Search 'n Pick now allows colonizing planets (this is pretty experimental; feel free to provide feedback). **You need to be in the respective Star System on the Star System Map to Colonize a planet**. + +### ToyBox Rogue - Ver 1.5.10 built for Rogue Trader 1.0.98 +* (***ADDB***) One of my previous changes broke Visual size multiplier; fixed that. +* (***ADDB***) (Maybe) readded changing Ranks of features which support that. +* (***Deltharis***) Reversing Advantage/Disadvantage for RT. +* (***Deltharis***) Added ship options for roll cheats. +* (***Deltharis***) Modified the infinite action stuff. An option to allow multiple attacks should now exist. +* (***ADDB***) Fix Achievements tab? +* (***ADDB***) Addressed a caching issue which made Achievements tab behave weirdly when loading another save. +* (***ADDB***) Fix BlueprintVoiceEditor crashing mod when editing Unit without an existing voice. +* (***ADDB***) Fixed Remote Companion Dialog somehow causing the game to assume that a companion was turned into an ExCompanion. +* (***ADDB***) Maybe fix units teleporting during dialog? +* (***ADDB***) Fix Show Interestingness Coefficient feature sometimes crashing the mod. +* (***ADDB***) It is now possible to use the Party => Stats window to let AI take control of Companions during fights. +* (***ADDB***) To prevent collisions this update introduced some changes which will reset existing VisualSizeMultiplier options. Until now those have been based on Character Name. They are now based on Character Unique Id. +* (***ADDB***) Added experimental fix for recruited characters being invisible when changing Areas. **You need to manually activate this with the toggle located in the Party Tab.** +* (***ADDB***) Development mode now allows cheat-only commands. +* (***ADDB***) Fixed some recruited units being controlled by AI instead of the player. +* (***ADDB***) Probably finally fixed Highlight Hidden Objects... +* (***ADDB***) Fix (keep) highlighting wrong objects. +* (***ADDB***) Maybe fix Remote Companion Dialog including Companions before recruiting them. +* (***ADDB***) Readd unstart Etude. +* (***ADDB***) Made Achievement Enabler opt-out by default. (If you are updating the mod then this changes nothing for you). +* (***ADDB***) Remove more log spam. +* (***ADDB***) Fixed broken Custom Portrait Editor. +* (***ADDB***) Fixed some stuff staying highlighted when activating highlight hidden objects. +* (***ADDB***) Infinite Actions now no longer works for enemies. +* (***ADDB***) Added new option to really don't use AP (even for attacks and stuff). +* (***ADDB***) Fixed Search 'n Pick gets stuck on collation in Progression Category. +* (***ADDB***) Added option to pick character filter in Search 'n Pick window. +* (***ADDB***) Did some work at Camera Tweaks in Enhanced UI; soem stuff works now; some not. +* (***ADDB***) Added option to change/freeze Veil Thickness. +* (***ADDB***) Maybe Removed Buff Multiplier error. + +### ToyBox Rogue - Ver 1.5.9 built for Rogue Trader 1.0.68 +* (***ADDB***) Fixed Remote Companion Dialog not showing dialog for... remote companions. +* (***ADDB***) Fixed respecced from 0 for Cassia. +* (***ADDB***) Added cheat to disable psychic phenomena. +* (***ADDB***) Added option to dump/extract built-in portraits. +* (***ADDB***) Fix Jealousy Begone (someone reached the part of the game where the jealous dialog can be triggered and noticed it still triggered; with that save I was able to fix it). +* (***ADDB***) Fix Highlight Objects not revealing everything when the corresponding options are enaabled. +* (***ADDB***) Add Voice to StatEditor. +* (***ADDB***) Leveling up respecced from 0 companions now displays the picked/unpicked feats correctly. +* (***ADDB***) Maybe fix some broken dice tweaks that broke while trying to fix some broken dice tweaks? +* (***ADDB***) I accidentally broke Search 'n Pick in one of the last patches I think. +* (***ADDB***) Add QoL option to disable end turn keybind. +* (***ADDB***) KillAll could fail in some fights which crashed the mod (Invalid Cast Exception); that's fixed. +* (***ADDB***) Since someonee requested it; changed PCMale and PCFemale overrides (Love is Free) to now also work for stuff that isn't explicitly a romance dialog (e.g. during private companion dialogs). +* (***ADDB***) Disabled Expand Answers For Conditional Responses because it was actually broken and I don't have time to look at that whole system. +* (***ADDB***) Added workaround for people who use Ignore Talent Perquisite in Character Creation. +* (***ADDB***) Maybe fix some broken dice tweaks? +* (***ADDB***) Added Show Risky Toggles option which hides a Show All Dialog Answers (Ignore Everything) options. +* (***ADDB***) Removed log spam because of missing Etude Comment Translation +* (***ADDB***) The Level Up preview no longer displays wrong numbers when doing a level up. +* (***ADDB***) Rewrote object highlight system because people don't turn it off before combat. +* (***ADDB***) Experimental Fix for issue where leaving a unit from another region selected in Search 'n Pick would crash the mod because that unit was already disposed. +* (***ADDB***) Fix Stats Editor for Abelard (and any other companion with missing base soul marks). +### ToyBox Rogue - Ver 1.5.8a built for Rogue Trader 1.0.62 +* (***ADDB***) Allow Respec From Zero for Companions and RT. See Level Up Category. +* (***ADDB***) Fixed weird behaviour when changing stats using the Textfield. +* (***ADDB***) Fixed Search 'n Pick - missing key update if collation happens too fast +* (***ADDB***) Remove ~250 unimplemented settings (and UI Labels/Toggles in relation to them) +* (***ADDB***) Maybe fix experience multiplier +* (***ADDB***) Maybe fix BuffDuration multiplier +* (***ADDB***) Remove Continue Audio on Lost Focus since the game natively supports this +* (***ADDB***) Fix Soul Mark Editor +* (***ADDB***) Fix some other stuff like Navigator Resources and Respec +* (***ADDB***) Maybe Re-Add Remote Dialog options (buggy; report if you encounter anything!) +* (***ADDB***) Maybe Re-Add Bi + Poly Romance options (buddy; report if you encounter anything!) +### ToyBox Wrath - Ver 1.5.8b (built for 2.2.0as) +* (***ADDB***) Ride everything now really allows riding everything. Looks ridiculous but still. +* (***ADDB***) Fixed weird behaviour when changing stats using the Textfield. +* (***ADDB***) Prevent mod from being unable to load when localization files are missing. +* (***ADDB***) Improved detection and added auto-deletion of concurrently installed outdated ToyBox version. +* (***ifarmpandas***) Added ActivatableAbilities to "Abilities" button in party editor. +* (***ADDB***) Recompile for new version. +* (***ADDB***) Fixed some whoopsies (missing Localization files in Release; Wrong Repository Branch in Info file etc.). +### ToyBox Rogue - Ver 1.5.7 (built for 0.2.1ah) +* (***ADDB***) Added all localization keys to allow full localization. +### ToyBox Wrath - Ver 1.5.7 (built for 2.1.5r) +* (***ADDB***) Added all localization keys to allow full localization. +* (***Hinkiii***) Made descriptor size change persist by adding per-save-setting for it. +* (***ADDB***) Armies -> Recruitment Editor now changes Growth instead of available units. +* (***ifarmpandas***) Add toggle for instant cooldown on global crusade spells. +### ToyBox Rogue - Ver 1.5.6 (built for 0.2.1ah) +* (***ADDB***) Added Kill Button to Party Editor. +* (***ADDB***) It is now possible to choose Build-In Portraits with the Portrait Picker. +* (***ADDB***) Portrait Picker is now implemented as a Browser to save RAM; portraits are now unloaded when changing away from the Stats tab. +* (***ADDB***) Fixed Search 'n Pick Category Switching not doing anything when a search query is already entered. +* (***ADDB***) Search 'n Pick Collation Categories are now sorted by the number of entries (again). +* (***ADDB***) Fixed changing stats not persisting through save+load. +* (***ADDB***) Collation Category Search now works. +* (***ADDB***) Added a toggle to either sort collation categories by name or by amount of entries. +### ToyBox Wrath - Ver 1.5.6 (built for 2.1.4w) +* (***ADDB***) Search 'n Pick changes related to Parameterized Features and Feature Selections (ported Party Editor GUI to Search 'n Pick). +* (***ADDB***) Override Ability Points can now finally correctly decide between Mercenary and Player character. +* (***ADDB***) Added Kill Button to Party Editor. +* (***ADDB***) It is now possible to choose Build-In Portraits with the Portrait Picker. +* (***ADDB***) Portrait Picker is now implemented as a Browser to save RAM; portraits are now unloaded when changing away from the Stats tab. +* (***ADDB***) Fixed Search 'n Pick Category Switching not doing anything when a search query is already entered. +* (***ADDB***) Adjusted Search 'n Pick Parameterized Blueprints Layout. +* (***ADDB***) Search 'n Pick Collation Categories are now sorted by the number of entries (again). +* (***ADDB***) Search 'n Pick is now no longer stuck at Collating... when using Path of Rage. +* (***ADDB***) Search 'n Pick is now no longer stuck at Collating... in some cases. If you still encounter this bug please report it on Discord. +* (***ADDB***) Fixed enhanced save/load NullError in MainMenu. +* (***ADDB***) Collation Category Search now works. +* (***ADDB***) Added a toggle to either sort collation categories by name or by amount of entries. +### ToyBox Rogue - Ver 1.5.5a +* (***ADDB***) Made more Buffs visible in the exclusion editor. +* (***ADDB***) Made Spaceships renamable. +* (***ADDB***) Fixed Stats Editor (stats not shown correctly and modifying was weird). +### ToyBox Wrath - Ver 1.5.5a +* (***ADDB***) updated game version 2.1.4w +* (***ADDB***) Achievements unlocking stuff +* (***ADDB***) Made more Buffs visible in the exclusion editor. +* (***ADDB***) (Hopefuly) removed NullReferenceException in log for levelup stuff. +* (***ADDB***) Equipment No Weight will now be applied when loading a save +* (***ADDB***) Fix Equipment load being multiplied when entering a new area +* (***ADDB***) Maybe fix stuck at collation... (probably not) + ## ToyBox Rogue - Ver 1.5.4c + * Port for prelease versions of War Hammer 40000: Rogue Trader + * This is a work in progress. + * Please enjoy the parts of the mod that do work (which is more than one might expect) + * Please be patient as I bring up more of the mod over time + * (***Narria***) Fixed crasher that made saves disappear if you use custom portraits + * (***Narria***) Added a toggle at the top of Bag of Tricks to enable ToyBox bug fixes like the one above + * (***Narria***) Got character rename working in the party editor + * (***Narria***) Add/Remove/Recruit/Uncrecruit now work + * (***Narria***) You can now edit abilities in the party editor + * (***Narria***) You can now edit your Starship in the party editor + * (***Narria***) Got some basic level up optiosn working for ignoring various Talent restructions (level, stat, other talent) + * (***Narria***) **Got this up on Beta 0.2.1y including fixing the dialog bug** + * (***Narria***) show a little more info in dialog preview + * (***Narria***) basic dialog browser that lets you look through the dialog tree + * (***Narria***) dialog preview now shows conditions for answers too + * (***Narria***) Made a GUI for the portrait picker + * (***ADDB***) Dice cheats port + * (***ADDB***) Added Navigator Resource Editor + * (***ADDB***) Added Scrap Resource Editor + * (***ADDB***) Added InGame Portrait Changer + * (***ADDB***) Added Faction Reputation Editor + * (***ADDB***) Fixed QuestEditor Out of Bounds error + * (***ADDB***) Added Toggle to disable random warp encounter + * (***ADDB***) Reworked Search and Pick + * (***ADDB***) Fixed EnchantmentEditor paging selection add stuff + * (***ADDB***) Fixed EnchantmentEditor adding/removing not working + * (***ADDB***) Profit Factor Editor + * (***ADDB***) WIP Added Colony Tab + * Add/Remove a specified amount from Colony Resources (like Adamantium) + * For each colony: + * Finish ongoing Projects (untested) + * Modify Colony Stats (like Security) + * Add/Remove traits to/from a colony +### ToyBox Wrath - Ver 1.5.4c +* (***Narria***) Some Etude changes +* (***Narria***) beginnings of a dialog browser +* (***Narria***) dialog preview now shows conditions for answers too +* (***Narria***) Made a GUI for the portrait picker +* (***BuckAMayzing***) Gestalt Companion fix +* (***BuckAMayzing***) Mercenary Gestalt fix +* (***ADDB***) Dice cheats small changes +* (***ADDB***) Added replenish recruits button +* (***ADDB***) Fixed finish Settlement Building Button +* (***ADDB***) Added Sliders to modify the buff length needed for enduring spells to take effect +* (***ADDB***) Adjusted the logic of Recruit Multiplier +* (***ADDB***) Add new Bindable Action Buttons for Loot Revealers +* (***ADDB***) Prevent Mod from overriding Build Points when not changed in the settings +* (***ADDB***) Added toggle to disable traps +* (***ADDB***) Small ToyBox Swarm GUI fix +* (***ADDB***) Enchantment Tab Improvement + * Added paging to Inventory search for Improved Performance + * Improved Inventory Search Keys + * Exchanged old Enchant Browser for new Browser +* (***ADDB*** and ***BuckAMayzing***) Changes to BuffExclusion behaviour + * Added ~100 Buffs to the default exclusions (every hidden buff that has Cooldown in its name) + * Separated buffs excluded by default and buffs excluded by the user into different categories +* (***ADDB***) Added Ingame Portrait Changer +* (***ADDB***) Reworked Search and Pick +* (***ADDB***) Fixed EnchantmentEditor paging selection add stuff +### Ver 1.5.3 Wrath - Sh0dan Unified Edition +* (***Narria***) Massive internal rework to accomodate both Wrath and Rogue Trader (codename: Shodan) + * Changed mod ID and title name in Mod Manager to make this clear. + * Mod will appear a new folder, please disable old ToyBox once you are happy with this version + * **Your old ToyBox settings should migrate automatically** + * Please report any issues you encounter +* (***Narria***) **Fixed nastry regression that broke gestalt level ups. Things should be back to ToyBox 1.5.1 goodness + ** +* (***Narria***) Fixed issue of game double calling SelectClass.Apply leading to multiclass levels being added more than + once during character creation **Please test out and report any issues you find in * (***Narria***) **Enhanced UI** + Added buttons to maximize mod window size and also show it when you get get the UI warning for small mod windows +* (***Narria***) Fixed crasher bug with kill all button (most apparent when you use it from ToyBox UI) +* (***Narria***) Fixed issue where adding spells from all spellbooks and other cases wasn't working +* (***Narria***) Added Saves tab where you can currently just view saves. Look for more in the future +* (***Narria***) Added Crusade Events to Search 'n Pick +* (***Narria***) **Fixed bug where loot slot filtering was being applied to things outside of inventory** +* (***Narria***) Fixed crasher when you ignore equipment restrictions +* (***Narria***) Fixed bug in Party Editor where Adjust Level Based On Experience modified your mythic experience and + not your character experience +* (***ADDB***) Added Achievements Unlocker. +* (***ADDB***) Made GameID editable. +* (***BuckAMayzing***) Fixed issue with Bulk Sell unintentionally selling items that were still equipped on characters. +* (***BuckAMayzing***) Fixed infiinte recursion bug that broke loading from saves and movement in certain cases +* (***icls1337***) Localization for Mandarin + +### Ver 1.5.2 + +* (***Narria***) **UI Reorg** Moved UI Enhancements into new top level **Enhanced UI** tab and the Loot tab is now just + for loot related stuff +* (***Narria***) Quality of Life: **Click On Equip Slots To Filter Inventory** this lets you filter the inventory to + just the items that can be equipped by the slot you click on. + * **Click On Equip Slots** will mark items that have modifier conflicts with a yellow background +* (***Narria***) **Ignore Forbidden Archetypes now also lets you choose ones such as Griffonheart in the creation and + level up screen** +* (***Narria***) **Fixed crasher when multiclass is turned in and doing class selection during char creation** +* (***Narria***) Improved Interesting NPC calaculations +* (***Narria***) Added Dialog Inspection to InterestingNPCs list so now you can inspect both the Unit and its Dialog +* (***Narria***) Improved the UI for showing data about interesting NPCS so now the highlight will disappear when + conditions are not met (this is not perfect but will improve over time) + * Entries for the same condition are merged when they occur for multiple sources + * Fixed issue for Interesting NPCs show all where it was showing other copies of the NPCs that were not really in + the area at the current time. + * Added toggle under Show All to show the other versions of NPCs +* (***Narria***) **Added Enhanced Load/Save Window that lets you search for your saves in the main menu and in game** +* (***Narria***) Overtips showing interesting NPCs now update immediately when their state changes (like after giving + all your elven pages to the storyteller) * (***Narria***) **Murder Hobo** and **Kill All** now work in Tactical Combat cuz why not? -* (***Narria***) Changed logic for creation points to enforce game point minimums and added a toggle to let you ignore this +* (***BuckAMayzing***) **More Bulk Sell** - integrated the More Bulk Sell mod into ToyBox (and fixed outstanding + issues), as it appears to have been abandoned. Credit to zesen for the original version of the mod. +* (***Narria***) Changed logic for creation points to enforce game point minimums and added a toggle to let you ignore + this * (***Narria***) Fixed bug where mass loot would not come up in situations with no ground loot -* (***Narria***) Fixed bug where NPC portraits were missing if you had **Expand Dialog To Include Remote Companions ** but not **Include Former Companions** +* (***Narria***) Fixed bug where NPC portraits were missing if you had **Expand Dialog To Include Remote Companions ** + but not **Include Former Companions** * (***Narria***) Fixed bug with missing NPC dialog +* (***Narria***) **Fixed bug with Expanded Dialog where it didn't allow you to finish some dialogs** * (***Narria***) Fixed crasher with add all Mercenaries in Mercenaries editor * (***Narria***) Cleaned up UI of Mercenaries Editor - * (***ADDB***) Initial support for localization - * (***BuckAMayzing***) Patched an issue that could cause a crash if a shared string was referenced incorrectly in other mods. +* (***Narria***) Fixed bug where Highlight Learnable scrolls highlight was not showing when you scroll the inventory + list +* (***Narria***) When you use **Expanded Answers For Conditional Responses** it will properly check to see if the answer + has been seen before +* (***Narria***) **Level Up & Multiclass** + * Reorganzed tab to move the settings closer to the top + * Added shortcut button to **Maximize Mythic Flexibility** +* (***Narria***) **Option to Keep Following with the camera when you hold down the Camera Follow Key** +* (***ADDB***) Initial support for localization +* (***BuckAMayzing***) Patched an issue that could cause a crash if a shared string was referenced incorrectly in other + mods. + ### Ver 1.5.1 -* (***Narria***) **Quality of Life: Enhanced Map**: - * You can enable zooming of the Local Map that you see in Cities, Dungeons and other Encounter spots. - * Zooming will resize various points of interest so you can separate ones that are stacked as you zoom more - * Made picture frame get thinner so it doesn't block your view - * Clicking and right clicking on the map also repositions the frame or places the movement marker correctly - * Using direction keys on the map will move in map coordinates not world coordinates, which is what one would expect - * Added map scroll multiplier when zoomable map is enabled + +* (***Narria***) **Quality of Life: Enhanced Map**: + * You can enable zooming of the Local Map that you see in Cities, Dungeons and other Encounter spots. + * Zooming will resize various points of interest so you can separate ones that are stacked as you zoom more + * Made picture frame get thinner so it doesn't block your view + * Clicking and right clicking on the map also repositions the frame or places the movement marker correctly + * Using direction keys on the map will move in map coordinates not world coordinates, which is what one would expect + * Added map scroll multiplier when zoomable map is enabled * (***Narria***) Quality of Life: Option to mark NPCs with interesting dialog/quests on the map - * Also marks interesting NPCs on the main screen by coloring their name + * Also marks interesting NPCs on the main screen by coloring their name * (***Narria***) Added Interesting NPC list to the Quest Editor tab -* (***Narria***) **Quality of Life**: ToyBox Archeology Corps has labeled your puzzle pieces for you to make identification easier. - * Added puzzle piece tags to the puzzle piece to identify the puzzle and the 2 symbols as a pair of numbers formated like this **[Puzzle Piece: Purple2x3]**. This should make it easier to identify and track the pieces in your inventory. +* (***Narria***) **Quality of Life**: ToyBox Archeology Corps has labeled your puzzle pieces for you to make + identification easier. + * Added puzzle piece tags to the puzzle piece to identify the puzzle and the 2 symbols as a pair of numbers formated + like this **[Puzzle Piece: Purple2x3]**. This should make it easier to identify and track the pieces in your + inventory. * (***Narria***) Fixed regression that made it impossible to use the spellbook merge feature * (***Narria***) Fixed regression in character name editing * (***Narria***) Made Selection Level a little more clear in FeatureSelection detail lists * (***Narria***) Further performance improvements to Party Editor search while you type * (***Narria***) You can now inspect the selected character list in Party Editor and the Quest list in Quest Editor * (***Narria***) Improved Inspector searching by using a background thread and made it a little prettier -* (***Narria***) Inspecter searches now works with terms (across parents) so if you specify 'Foo Bar' it will find nodes that have foo and bar somewhere across the node and its parents in the outline. This is helpful for to pull out parts of nodes at different levels for example in Quest Editor if you inspect all quests and search for 'Cloud Objective' it will show the objective property for all quests that contain the name Cloud -* (***Narria***) Implemented workaround for Pet Naming bug that comes up if you set default gestalt options. This will prevent you from being able to gestalt the pet when you choose its name and first level. You can however take one additional level in the secondary class and then afterwards mark that class as gestalt and set the multiclass flag and from thereafter it will function just as if you created it as gestalt. +* (***Narria***) Inspecter searches now works with terms (across parents) so if you specify 'Foo Bar' it will find nodes + that have foo and bar somewhere across the node and its parents in the outline. This is helpful for to pull out parts + of nodes at different levels for example in Quest Editor if you inspect all quests and search for 'Cloud Objective' it + will show the objective property for all quests that contain the name Cloud +* (***Narria***) Implemented workaround for Pet Naming bug that comes up if you set default gestalt options. This will + prevent you from being able to gestalt the pet when you choose its name and first level. You can however take one + additional level in the secondary class and then afterwards mark that class as gestalt and set the multiclass flag and + from thereafter it will function just as if you created it as gestalt. * (***Narria***) Inspector: brought back the Refresh Button * (***ADDB***) Added Button to remove all existing Swarm That Walks clones. * (***ADDB***) Fixed Bug introduced in a Preview that made every loot marker appear automatically. * (***BuckAMayzing***) Added an "Unlearned Scrolls" category in Enhanced Inventory. -### Ver 1.5.0 + +### Ver 1.5.0 + * (***Narria***) Inventory Enhancements - * Brought in some bits of ***Xenofell's*** lovely **Enhanced Inventory** to give a unified experience with Loot Rarity and to revive the mod which needed some love - * Added the ability to sort items in inventory and vendors by rarity - * Ability to choose which sort categories appear in the dropdown - * Much more useful **SearchFilter Categories** including ones from Enhanced Inventory - * **Enhanced Spellbook** - gives you search, filtering and more - * Fixed compatibility issue with BubbleBuffs - * Fixed issue with broken sorters when Enhanced Inventory is turned off - * Added item counts it the inventory screen and spell counts to the spellbook when enhanced versions are turned on - * Inventory - added toggle to let you keep the search active when you dismiss the search bar - * Fixed long standing bug where Showing the Mass Loot Window with ToyBox triggered a bug in the game that would cause item loss when interacting with the Player Chest - * Added bindable button to show the Shared Party Chest anywhere you want + * Brought in some bits of ***Xenofell's*** lovely **Enhanced Inventory** to give a unified experience with Loot + Rarity and to revive the mod which needed some love + * Added the ability to sort items in inventory and vendors by rarity + * Ability to choose which sort categories appear in the dropdown + * Much more useful **SearchFilter Categories** including ones from Enhanced Inventory + * **Enhanced Spellbook** - gives you search, filtering and more + * Fixed compatibility issue with BubbleBuffs + * Fixed issue with broken sorters when Enhanced Inventory is turned off + * Added item counts it the inventory screen and spell counts to the spellbook when enhanced versions are turned on + * Inventory - added toggle to let you keep the search active when you dismiss the search bar + * Fixed long standing bug where Showing the Mass Loot Window with ToyBox triggered a bug in the game that would + cause item loss when interacting with the Player Chest + * Added bindable button to show the Shared Party Chest anywhere you want * (***Narria***) Added annoation in bio for ToyBox generated alignment shifts -* (***Narria***) Fixed various crashes that would occur if you brought up the mod in different tabs while launching the game and loading into a save +* (***Narria***) Fixed various crashes that would occur if you brought up the mod in different tabs while launching the + game and loading into a save * (***Narria***) DataViewer views now filter out properties with empty collections * (***Narria***) Quest Editor option to inspect quests and quest objectives with DataViewer -* (***Narria***) Added Dice Roll overrides for Skill checks only (thanks AlterAsc) and cleaned up the options a bit to make them more clear +* (***Narria***) Added Dice Roll overrides for Skill checks only (thanks AlterAsc) and cleaned up the options a bit to + make them more clear * (***Narria***) Search 'n Pick now displays localized names (if available) and allows you to search them * (***Narria***) Search 'n Pick: Added counts to the collation categories * (***Narria***) Search 'n Pick: Fixed various duplicate value issues when using sub-categories * (***Narria***) Search 'n Pick: You can now add and remove Crusade Global Magic Spells * (***Narria***) Added tweak in Loot Tab to allow you to loot locked items * (***Narria***) Added search to inspectors (Data Viewer) -* (***Narria***) Implemented FeatureSelection and ParameterizedFeature for the new Browser, which means you can now add and remove a lot more things like Racial Herritage, Weapon Focus and Oracle Mystery Selections. +* (***Narria***) Implemented FeatureSelection and ParameterizedFeature for the new Browser, which means you can now add + and remove a lot more things like Racial Herritage, Weapon Focus and Oracle Mystery Selections. * (***Narria***) New spiffy UI for editing parameterized feats and feature selections * (***Narria***) Fixed various issues with feature selection and parameterized features as well * (***Narria***) Fixed some Helper functions. * (***Narria***) Added ability to Inspect units in Party Editor (Data Viewer style). * (***Narria***) Cleaned up layout of Party Editor for narrow and medium widths. -* (***Narria***) Improved UI for Keybindings by allowing you to Clear a Key Binding and Remove Conflicts +* (***Narria***) Improved UI for Keybindings by allowing you to Clear a Key Binding and Remove Conflicts * (***Narria***) Added marking of search results in Party Editor blueprint views * (***Narria***) Added more explainer text to dialog options * (***Narria***) **Fixed bug where certain buff durations like Smite Evil were getting set to zero duration** @@ -119,68 +673,89 @@ WARNING: this tool can both miraculously fix your broken progression or it can b * (***ADDB***) Changed Browser to improve performance. * (***ADDB***) Fixed belt consumable feature. * (***ADDB***) Fixed visual size scaling breaking when using any sort of polymorph. -* (***ADDB***) Allow Achievements now no longer wrongly awards achievements (achievements locked behind difficulty or main campaign or platform). +* (***ADDB***) Allow Achievements now no longer wrongly awards achievements (achievements locked behind difficulty or + main campaign or platform). * (***ADDB***) Reroll Perception button now also rerolls perception on the global map. * (***ADDB***) Fixed FogOfWar Multiplier resetting when switching maps or loading. * (***ADDB***) Added option to increase Swarm Power in Party Editor -> Stats. * (***ADDB***) Fix Murder Hobo for Leaper's Smile swarms. * (***ADDB*** & ***Narria***) Added a new feature to affect Crusade Mercenaries and Kingdom Recruits. - * Added Button to add all units in your current armies to your Mercenary pool if they are not recruitable. - * Added Bindable Button to reroll all Mercenary units (ignoring leftover rerolls and locked slots). - * Added a ValueAdjustor where you can change the amount of Mercenary Slots. - * (Experimental) Added a toggle to automatically add new Units in friendly armies to the Mercenary pool if not recruitable. - * Added a list showing all ArmyUnits, a toggle to add/remove them from the Mercenary and Recruitment Pools, a label indicating which pool(s) they are in and a slider to change their Pool Weight (affecting reroll chances for Mercenary). + * Added Button to add all units in your current armies to your Mercenary pool if they are not recruitable. + * Added Bindable Button to reroll all Mercenary units (ignoring leftover rerolls and locked slots). + * Added a ValueAdjustor where you can change the amount of Mercenary Slots. + * (Experimental) Added a toggle to automatically add new Units in friendly armies to the Mercenary pool if not + recruitable. + * Added a list showing all ArmyUnits, a toggle to add/remove them from the Mercenary and Recruitment Pools, a label + indicating which pool(s) they are in and a slider to change their Pool Weight (affecting reroll chances for + Mercenary). + ### Ver 1.4.25 + * (***CascadingDragon***) and (***ADDB***) Update for game version 2.1.2d. - * Fixed? optional trait selection skip and removed the one where no traits left (since official game introduced that feature). - * Temporarily removed the refill belt consumable feature since it's weird. + * Fixed? optional trait selection skip and removed the one where no traits left (since official game introduced that + feature). + * Temporarily removed the refill belt consumable feature since it's weird. * (***ADDB***) Fixed Search 'n Pick crashing when using search in Main Menu. * (***ADDB***) Added Visual Character Size Multiplier. + ### Ver 1.4.24 + * (***Narria***) Loot coloring improvents - * Added rarity tags when color loot items is active - * Added new alternative to just color the rarity tags and show titles in black text - * Fixed rating for necklaces, rings and cloaks - * This uncovered a need for an additional loot level so I added a new level called Primal which is equal to the old Godly and then increased the item rating required for godly to represent truely insane items like the +8 belt of physical perfection which gives +8 to 3 stats which calculates out to a rating of 240 - * Recolored loot tiers and made them more readable - * Here are the item rating to rarity tiers and new color asignments. - * 200+ Godly - Red - * 115+ Primal - Cyan - * 80+ Mythic - Pink - * 50+ Legendary - Orange - * 30+ Epic - Purple - * 20+ Rare - Blue - * 10+ Uncommon - Green - * You can think of a normal +1 weapon or armor being worth 10 rating points - * Note: You can now see item ratings in the Enchantment tab. + * Added rarity tags when color loot items is active + * Added new alternative to just color the rarity tags and show titles in black text + * Fixed rating for necklaces, rings and cloaks + * This uncovered a need for an additional loot level so I added a new level called Primal which is equal to the + old Godly and then increased the item rating required for godly to represent truely insane items like the +8 + belt of physical perfection which gives +8 to 3 stats which calculates out to a rating of 240 + * Recolored loot tiers and made them more readable + * Here are the item rating to rarity tiers and new color asignments. + * 200+ Godly - Red + * 115+ Primal - Cyan + * 80+ Mythic - Pink + * 50+ Legendary - Orange + * 30+ Epic - Purple + * 20+ Rare - Blue + * 10+ Uncommon - Green + * You can think of a normal +1 weapon or armor being worth 10 rating points + * Note: You can now see item ratings in the Enchantment tab. * (***Narria***) Search 'n Pick Improvements - * DataViewer - you can now peek into the details of blueprints with a disclosure triangle by the blueprint class - * Added subcategories for Mythic, Arcane, Divine, etc for the Classes category - * Added subcategories for conditions for quest objectives, dialogs, cues, answers - * Added option to display elements (including conditions) + * DataViewer - you can now peek into the details of blueprints with a disclosure triangle by the blueprint class + * Added subcategories for Mythic, Arcane, Divine, etc for the Classes category + * Added subcategories for conditions for quest objectives, dialogs, cues, answers + * Added option to display elements (including conditions) * (***Narria***) Enchantment Editor improvements - * Added **All** option to selecting item types - * Now show item rating (blueprint rating) and rating of enchantments - * Sort items by rating - * Added toggle to show ratings in list of items - * Improved grading of enchantments so that ones that have 0 cost (which I think is a bug) count as common (rating 5) - * Fixed bug where weapon type selection would be lost when you selected a weapon other than the first one in the list and then closed and reopened the mod window -* (***Narria***) Added temporary AI category to Party view. Right now you can see what AI actions/considerations are active on a units brain. This is the beginning of an improved AI/Gambits feature which will be in its own top level tab + * Added **All** option to selecting item types + * Now show item rating (blueprint rating) and rating of enchantments + * Sort items by rating + * Added toggle to show ratings in list of items + * Improved grading of enchantments so that ones that have 0 cost (which I think is a bug) count as common (rating 5) + * Fixed bug where weapon type selection would be lost when you selected a weapon other than the first one in the + list and then closed and reopened the mod window +* (***Narria***) Added temporary AI category to Party view. Right now you can see what AI actions/considerations are + active on a units brain. This is the beginning of an improved AI/Gambits feature which will be in its own top level + tab * (***Narria***) Added Brains, AIActions and Considerations to Search 'n Pick * (***Narria***) Fixed crasher during character creation on the loot screen -* (***Narria***) Fixed issue where the arrow key Y scrolling becomes inverted when tilt the camera higher than like 45 or 60 degrees -* (***ADDB***) (Experimental) Added a feature that allows hiding map loot pins on the map if the contained loot doesn't reach at least a selected rarity. +* (***Narria***) Fixed issue where the arrow key Y scrolling becomes inverted when tilt the camera higher than like 45 + or 60 degrees +* (***ADDB***) (Experimental) Added a feature that allows hiding map loot pins on the map if the contained loot doesn't + reach at least a selected rarity. * (***ADDB***) Added Kill on Engage toggle (Which kills a unit as soon as it joins a fight against Character). * (***ADDB***) Added Fog of War Radius multiplier. * (***ADDB***) Added toggle to continue playing music on lost focus. * (***ADDB***) Changed ValueAdjuster UI so that its elements don't clip into each other anymore. -* (***BuckAMayzing***) Prevented resurrected companions and summoned creatures from being unable to act when buff duration multiplier was set to a very high number +* (***BuckAMayzing***) Prevented resurrected companions and summoned creatures from being unable to act when buff + duration multiplier was set to a very high number * (***CascadingDragon***) Moved "Disallow Companions" back to Dialog section * (***CascadingDragon***) Added options to shift alignment numerically + ### Ver 1.4.23 -* (***Narria***) Party Editor is faster for browsing existing features/spells/etc - Blueprints don't load until you select Show All + +* (***Narria***) Party Editor is faster for browsing existing features/spells/etc - Blueprints don't load until you + select Show All * (***Narria***) Deferred Blueprint loading for Armies as well -* (***Narria***) Fixed issue where **Expand Answers For Conditional Responses** did not work for cases where there was a default outcome. Now when you meet Cial in ch3, Liches can choose the non Lichy response. +* (***Narria***) Fixed issue where **Expand Answers For Conditional Responses** did not work for cases where there was a + default outcome. Now when you meet Cial in ch3, Liches can choose the non Lichy response. * (***Narria***) If you gestalt a mythic class it will no longer appear as your main mythic path * (***Narria***) Army Editor now lets you edit current mana for a leader * (***Narria***) Party Editor: Fixed crasher in changing characters after selecting other spellbook @@ -193,465 +768,601 @@ WARNING: this tool can both miraculously fix your broken progression or it can b * (***ArcaneTrixter***) Fix buff duration overflow issue. * (***ArcaneTrixter***) Fix bugs reported for merged spellbooks. * (***ArcaneTrixter***) Fix Ignore Attribute Cap. -* (***ArcaneTrixter***) Enhancement for Ignore Alignment Requirements to enable divine casters to ignore deity alignments. Looking at you, Camellia! +* (***ArcaneTrixter***) Enhancement for Ignore Alignment Requirements to enable divine casters to ignore deity + alignments. Looking at you, Camellia! * (***ArcaneTrixter***) Adding cheating option to ignore anything at all that would prevent you from using an ability. * (***gespenstgaming***) Increased the creation ability point cap to 600. * (***ADDB***) Added field for army general experience. -* (***ADDB***) Fixed Preview Event Results and made Ignore Event Solution Restrictions independent from Preview Flags. Added toggle to disable showing restrictions. +* (***ADDB***) Fixed Preview Event Results and made Ignore Event Solution Restrictions independent from Preview Flags. + Added toggle to disable showing restrictions. * (***ADDB***) Added experience multipliers for different experience sources. * (***ADDB***) Made Individual Class and Mythic Experience editable in Party -> Classes tab. -* (***ADDB***) Fixed a bug where activation Decree Preview would still show when opening an Event Window after opening a Decree. +* (***ADDB***) Fixed a bug where activation Decree Preview would still show when opening an Event Window after opening a + Decree. + ### Ver 1.4.22 + * Updated for Last Sarkorians DLC - Game Version 2.1.0w+ * (***ArcaneTrixter***) Fixed actions bars patches being broken by game update. * (***BuckAMayzing***) Made buff exclusion search case insensitive + ### Ver 1.4.21 + * (***mcb***) spell selection on levelup no longer prompts you to choose a spell if all spells of that level are known -* (***mcb***) Free Metamagic now no longer increases casting time of spontaneous casters when casting spells with metamagics applied to them -* (***ArcaneTrixter***) Fixed etude null ref for Woljif Romance and other mods that introduce etudes without all expected fields. +* (***mcb***) Free Metamagic now no longer increases casting time of spontaneous casters when casting spells with + metamagics applied to them +* (***ArcaneTrixter***) Fixed etude null ref for Woljif Romance and other mods that introduce etudes without all + expected fields. * (***ArcaneTrixter***) Cleaned up logic for infinite item use, should stop belt items from being eaten. -* (***ArcaneTrixter***) Added option to "Enable Loading with Blueprint Errors" to enable loading modded saves and stripping added BPs back out of saves. This comes with many warnings, but could be a better alternative than restarting a game. +* (***ArcaneTrixter***) Added option to "Enable Loading with Blueprint Errors" to enable loading modded saves and + stripping added BPs back out of saves. This comes with many warnings, but could be a better alternative than + restarting a game. * (***ArcaneTrixter***) Fixes for RemoveBuffs to not remove things it shouldn't -* (***ArcaneTrixter***) New option for Mass Loot to not steal from living NPCs, and a fix for crashing toybox when there was no loot for the area loot button. +* (***ArcaneTrixter***) New option for Mass Loot to not steal from living NPCs, and a fix for crashing toybox when there + was no loot for the area loot button. * (***ArcaneTrixter***) New option for loot coloring to set minimum rarity for loot coloration/highlighting. * (***ArcaneTrixter***) Fix for error in speed patch throwing exception on missing unit data in guard clause. + ### Ver 1.4.20 -* (***AeonBlack***) Fix Multiclass/Gestalt on DLC Player/Mercenary Respec. (Note: Still broken on Mercenary Recruit, Workaround: Hire and then Respec through Party Editor.) -* (***ArcaneTrixter***) Updated default value of kineticist burn reduction. It's applying 0 unless modified, but it should reset to no-impact as default behavior. + +* (***AeonBlack***) Fix Multiclass/Gestalt on DLC Player/Mercenary Respec. (Note: Still broken on Mercenary Recruit, + Workaround: Hire and then Respec through Party Editor.) +* (***ArcaneTrixter***) Updated default value of kineticist burn reduction. It's applying 0 unless modified, but it + should reset to no-impact as default behavior. * (***ArcaneTrixter***) Split spellslot multipliers for prepared vs spontaneous casters. * (***ArcaneTrixter***) Fix uncapped caster levels mythic levels. * (***ArcaneTrixter***) Fixed "Open Area Exit Loot Window" button. * (***ArcaneTrixter***) Fixes for game version 2.0.6 - fix Search & Pick, Rest Selected, Make All Features Optional + ### Ver 1.4.19 + ***Important**: *Make sure you are on the latest version of the game 2.0.4j or newer* -* (***ArcaneTrixter***) Ignore Class Restrictions now works for mythic classes as well, regardless of quest and etude states. + +* (***ArcaneTrixter***) Ignore Class Restrictions now works for mythic classes as well, regardless of quest and etude + states. * (***ArcaneTrixter***) Adding spellbooks should now add the correct spellbook type rather than the base class. * (***ArcaneTrixter***) Fix half casters having incorrect caster level with Remove Level 20 Caster Level Cap * (***Azdie***) Fixed the spacing on dialog previews, newline replaces with vertical tab. * (***Azdie***) Fixed dialog preview option also disabling answers' restrictions. + ### Ver 1.4.18 -* (***BuckAMayzing***) Fixed a bug where a couple of army debuffs (Nauseated, Stinking Cloud) were incorrectly being multiplied by the buff duration multiplier. + +* (***BuckAMayzing***) Fixed a bug where a couple of army debuffs (Nauseated, Stinking Cloud) were incorrectly being + multiplied by the buff duration multiplier. * (***BuckAMayzing***) Fixed a bug which caused Vertical regions to crash the UI. * (***BuckAMayzing***) Added configurability for buffs to exclude from buff duration multiplier in Bag of Tricks. + ### Ver 1.4.17 (DLC 3 release 1.4.2a) + * Support for Beta 1.4.2a + ### Ver 1.4.16 (DLC 2 release 1.3.0k) + * DLC 2 Compatibility - * (***Vermie***) Added button to reveal DLC portal loot + * (***Vermie***) Added button to reveal DLC portal loot + ### Ver 1.4.15 (DLC release 1.2.0.aa) + * DLC Compatibility fixes - * Fixed crash in achivement blocker disable that probably borked a lot of things + * Fixed crash in achivement blocker disable that probably borked a lot of things + ### Ver 1.4.14 (beta 1.2.0.h) + * **Beta Compatibility** now works with Beta 1.2.0h * **Bag of Tricks** - * Added ability to show, clear and disable corruption - * (***KnightOfSeiros***) Added a deraknis-begone option to replace all derakni models, so as deskari-begone - * **Dialog** - * Fixed issue that caused you to be unable to finish dialog with Nenio in chapter 2 and 5 + * Added ability to show, clear and disable corruption + * (***KnightOfSeiros***) Added a deraknis-begone option to replace all derakni models, so as deskari-begone + * **Dialog** + * Fixed issue that caused you to be unable to finish dialog with Nenio in chapter 2 and 5 * **LevelUp** - * **Split Class and Feat Prequisite Ignore (Nihilistzsche)** - * Split the class and feat prerequisite ignore into two separate settings. + * **Split Class and Feat Prequisite Ignore (Nihilistzsche)** + * Split the class and feat prerequisite ignore into two separate settings. * **Armies Editor** - * (***KnightOfSeiros***) Add squads in Army Editor + * (***KnightOfSeiros***) Add squads in Army Editor + ### Ver 1.4.13 (current for non beta) + * **Bag of Tricks** - * **Quality of Life** - * **Game Over** - Added toggle to block Game Over when certain companions die in combat, so if ***Leerooooy (Greybor) Jenkins*** runs in and your leader runs in to save him and dies the game does not end unless the whole party wipes. - * **Dialog** - * Toy to make previously chosen dialog answers show up in smaller dimmer text - * **Expand Answers For Conditional Responses** - * Dialog dependent on previous dialog checks now works correctly with the feature enabled - * Fixed issue that generated bogus extra answers for non conditional response by ensuring that the feature only gets activated by a set of continuation cues that all have conditions - * **Remote Companion Dialog** - * (***Ronin***) Convinced Daeran to stop repeating himself in the Grey Garrison - * **Camera** - * Mouse3 Camera + forward/back keys now lets you fly freely when pitch is enabled - * Additional setting for completely free camera - * Added Toy to let you adjust camera height with Ctrl+Mouse3 Camera Drag (very experimental) - * Added Toy to let you adjust the clip plane with Alt+MouseWheel - * Preview Relic Info (*thanks to rathtr*) + * **Quality of Life** + * **Game Over** - Added toggle to block Game Over when certain companions die in combat, so if + ***Leerooooy (Greybor) Jenkins*** runs in and your leader runs in to save him and dies the game does not end + unless the whole party wipes. + * **Dialog** + * Toy to make previously chosen dialog answers show up in smaller dimmer text + * **Expand Answers For Conditional Responses** + * Dialog dependent on previous dialog checks now works correctly with the feature enabled + * Fixed issue that generated bogus extra answers for non conditional response by ensuring that the feature + only gets activated by a set of continuation cues that all have conditions + * **Remote Companion Dialog** + * (***Ronin***) Convinced Daeran to stop repeating himself in the Grey Garrison + * **Camera** + * Mouse3 Camera + forward/back keys now lets you fly freely when pitch is enabled + * Additional setting for completely free camera + * Added Toy to let you adjust camera height with Ctrl+Mouse3 Camera Drag (very experimental) + * Added Toy to let you adjust the clip plane with Alt+MouseWheel + * Preview Relic Info (*thanks to rathtr*) * **Level Up & Multiclass** - * **ToyBox now supports multiple archetypes when you select a new class following tabletop rules** - * Thank you **Vek** for making this and sharing it with ToyBox users (please check out the excellent Table Top Tweaks mod as well. https://github.com/Vek17/WrathMods-TabletopTweaks) + * **ToyBox now supports multiple archetypes when you select a new class following tabletop rules** + * Thank you **Vek** for making this and sharing it with ToyBox users (please check out the excellent Table Top + Tweaks mod as well. https://github.com/Vek17/WrathMods-TabletopTweaks) * **Search 'n Pick** - * Option to show display name (localized name) for things that have it - * Search now looks at both display name (localized name) and internal name. Previously it only looked at internal name + * Option to show display name (localized name) for things that have it + * Search now looks at both display name (localized name) and internal name. Previously it only looked at internal + name * **Quest Editor** - * Added descriptions and improved UI in quest editor - * Quest steps now number properly - * Steps with missing titles show blueprint name - * quest steps are now colored based on whether completed, current or not seen yet. - * Failure steps are colored red + * Added descriptions and improved UI in quest editor + * Quest steps now number properly + * Steps with missing titles show blueprint name + * quest steps are now colored based on whether completed, current or not seen yet. + * Failure steps are colored red * **Loot** * (***Hambeard***) Added hotkey bindable button to allow for area exit loot window to be opened mid dungeon. * (***Hambeard***) Added button to show all ground loot on the map. * (***Hambeard***) Added perception dc's to loot check list to indicate chest is hidden * (***Hambeard***) Added trickery dc's to loot check list to indicate chest is locked * (***Hambeard***) Added button to include hidden chest when revealing them to the map + ### Ver 1.4.12 + * **Bag of Tricks** - * Cheat to let you enable Unlimited Stacking of Modifiers (Stat/AC/Hit/Damage/Etc) - * Fixed backwards default X rotation when camera mods are enabled - * Options to Invert Mouse X and Y Axis in camera settings - * Moved dialog options into their own section in Bag of Tricks - * Mythic Dialog Restrictions are beginning to apply to crusade events. - * **Expand Answers For Conditional Responses** - Some responses such as comments about your mythic powers will always choose the first one by default. This will show a copy of the answer and the condition for each possible response that an NPC might make to you based on. - * Option to see unavailable answers so you can see the conditions you are not meeting - * **Friendship is Magic** - * (***Ronin***) Now for goodie-two-shoes as well! - * (***Ronin***) Added support for Ciar and the Hand - * **Alignment Unrestrcted** - * (***Ronin***) Included support for dialogs from NPC, not just dialog from the MC - * (***Ronin***) Fixes for Lann/Wendu's questline + * Cheat to let you enable Unlimited Stacking of Modifiers (Stat/AC/Hit/Damage/Etc) + * Fixed backwards default X rotation when camera mods are enabled + * Options to Invert Mouse X and Y Axis in camera settings + * Moved dialog options into their own section in Bag of Tricks + * Mythic Dialog Restrictions are beginning to apply to crusade events. + * **Expand Answers For Conditional Responses** - Some responses such as comments about your mythic powers will + always choose the first one by default. This will show a copy of the answer and the condition for each possible + response that an NPC might make to you based on. + * Option to see unavailable answers so you can see the conditions you are not meeting + * **Friendship is Magic** + * (***Ronin***) Now for goodie-two-shoes as well! + * (***Ronin***) Added support for Ciar and the Hand + * **Alignment Unrestrcted** + * (***Ronin***) Included support for dialogs from NPC, not just dialog from the MC + * (***Ronin***) Fixes for Lann/Wendu's questline * **Remote Companion Dialog** - * (***Ronin***) Fixed errors with Wendu and Lann not properly counting as "former" companions + * (***Ronin***) Fixed errors with Wendu and Lann not properly counting as "former" companions * (***Ronin***) Fixed errors with Nenio's final quest * **Loot** - * Added a friendly unit filter toggle to the loot checklist + * Added a friendly unit filter toggle to the loot checklist * **Crusade Editor** - * Now shows conditions and preview for crusade event solutions - * Option to ignore crusade event solution restrictions - * Cleaned up UI layout and description coloring to match the rest of the mod - * (***KnightOfSeiros***) Toggle to choose no decree costs - * (***KnightOfSeiros***) Now shows preview for decree and event results + * Now shows conditions and preview for crusade event solutions + * Option to ignore crusade event solution restrictions + * Cleaned up UI layout and description coloring to match the rest of the mod + * (***KnightOfSeiros***) Toggle to choose no decree costs + * (***KnightOfSeiros***) Now shows preview for decree and event results * **Army Editor** - * Moved army cheats to armies editor + * Moved army cheats to armies editor + ### Ver 1.4.11 + * **Bag of Tricks** - * **Friendship is Magic** - * (***Ronin***) Allow friendships to survive after commiting the most vile actions - * **Remote Companion Dialog** - * (***Ronin***) Companions no longer mute during Act 5 companion quests, Bad Blood, or Underground Army - * Toggle to let a rider mount pets without size restriction - * Toggle to let a rider mount anything (experimental) - * Tweak to clear the action bar of the selected char - * **Camera** - * Fixed bug where FOV multilier stopped working when using enhanced camera settings - * Made camera zoom smoother on the mouse wheel, especially at higher FOV settings + * **Friendship is Magic** + * (***Ronin***) Allow friendships to survive after commiting the most vile actions + * **Remote Companion Dialog** + * (***Ronin***) Companions no longer mute during Act 5 companion quests, Bad Blood, or Underground Army + * Toggle to let a rider mount pets without size restriction + * Toggle to let a rider mount anything (experimental) + * Tweak to clear the action bar of the selected char + * **Camera** + * Fixed bug where FOV multilier stopped working when using enhanced camera settings + * Made camera zoom smoother on the mouse wheel, especially at higher FOV settings * **Level Up** - * Toggle to let pets take mythic classes - * Fixed mythic level up to show the correct mythic level on the top left of the level up screen. **Note**: the progression UI still puts stuff in the wrong place but at least the calculations should be better - * Toggle for ignoring racial feature prerequisites works once again + * Toggle to let pets take mythic classes + * Fixed mythic level up to show the correct mythic level on the top left of the level up screen. **Note**: the + progression UI still puts stuff in the wrong place but at least the calculations should be better + * Toggle for ignoring racial feature prerequisites works once again * **Party Editor** - * Improved UI for mythic spellbook merging. You can also merge more than one mythic spellbook into the same normal spellbook. **Note**: only the first merge increasess the mythic level (caster level) of the spellbook but you do get the extra spells. - * Added Mythic display of Mythic Experience and a button to set mythic experience so you can delevel your mythic level like you can for normal character level - * You can no longer select all mythic classes as gestalt - * Feature Tree view now shows levels for features acquired during level up + * Improved UI for mythic spellbook merging. You can also merge more than one mythic spellbook into the same normal + spellbook. **Note**: only the first merge increasess the mythic level (caster level) of the spellbook but you do + get the extra spells. + * Added Mythic display of Mythic Experience and a button to set mythic experience so you can delevel your mythic + level like you can for normal character level + * You can no longer select all mythic classes as gestalt + * Feature Tree view now shows levels for features acquired during level up * **Crusade Editor** - * Added Settlement Editor - * (***KnightOfSeiros***) Added immediately build buildings - * (***KnightOfSeiros***) Toggle to ignore all building restrictions - * (***KnightOfSeiros***) Added immediately finish decrees - * (***KnightOfSeiros***) Toggle to ignore start decree restrictions + * Added Settlement Editor + * (***KnightOfSeiros***) Added immediately build buildings + * (***KnightOfSeiros***) Toggle to ignore all building restrictions + * (***KnightOfSeiros***) Added immediately finish decrees + * (***KnightOfSeiros***) Toggle to ignore start decree restrictions + ### Ver 1.4.10 + * **Bag of Tricks** - * **Remote Companion Dialog** - * added infrastructure for handling situations with ex companions where an Etude may expect them to not be on the team. We have resolved the cases in *A Strike from the Sky*. Please report other situations where you see odd behavior and we will resolve it ASAP. - * (***Ronin***) Secret companions no longer appear early to spoil the surprise - * (***Ronin + Narria***) Companions no longer mute during *A Strike From The Sky* - * Improved camera zoom behavior when camera tilt/zoom all scenes is active - * Fixed some crashers in dialog preview and control summons - * (***Flat***) Add Rest Selected Unit - * (***Flat***) Add toggle to disable Attack of Opportunities - * (***DeadMoroz***) Fixed crasher that led to broken character switch when Loot Coloring is enabled and magical item was rewarded after tactical battle - * (***DeadMoroz***) Enable action bar for controllable summons. Prefill action bar with spell abilities and charge. - * (***Pheonix99***) Added Headers to Add Enchant search table, clairified Search Limit + * **Remote Companion Dialog** + * added infrastructure for handling situations with ex companions where an Etude may expect them to not be on + the team. We have resolved the cases in *A Strike from the Sky*. Please report other situations where you see + odd behavior and we will resolve it ASAP. + * (***Ronin***) Secret companions no longer appear early to spoil the surprise + * (***Ronin + Narria***) Companions no longer mute during *A Strike From The Sky* + * Improved camera zoom behavior when camera tilt/zoom all scenes is active + * Fixed some crashers in dialog preview and control summons + * (***Flat***) Add Rest Selected Unit + * (***Flat***) Add toggle to disable Attack of Opportunities + * (***DeadMoroz***) Fixed crasher that led to broken character switch when Loot Coloring is enabled and magical item + was rewarded after tactical battle + * (***DeadMoroz***) Enable action bar for controllable summons. Prefill action bar with spell abilities and charge. + * (***Pheonix99***) Added Headers to Add Enchant search table, clairified Search Limit + ### Ver 1.4.9.1 + * **Bag of Tricks** - * **Remote Companion Dialog** - * Fixed issue with Ex Companion dialog that would break some scenes involving Lann and Wenduag like at the end of the Prologue Labyrinth - * Cleaned up layout of checkboxes for remote dialog - * **Love is Free** - * (***Ronin***) Vellexia romance opened to all paths + * **Remote Companion Dialog** + * Fixed issue with Ex Companion dialog that would break some scenes involving Lann and Wenduag like at the end + of the Prologue Labyrinth + * Cleaned up layout of checkboxes for remote dialog + * **Love is Free** + * (***Ronin***) Vellexia romance opened to all paths + ### Ver 1.4.9 + * **Bag of Tricks** - * **Quality of Life** - * Remote Companion Dialog - Allow remote companions (even former party members) to make comments on dialog you are having - * Include Former Companions - Let's you also listen to remarks from your former companions. - * This is experimental so it may break dialogs. If you find an issue please come find me (Narria) on the WoTR discord (https://discord.gg/CRsCZNw8Rf) and give me a copy of the save. - * **Camera** - * **Massive improvements** - * Camera settings moved to new section - * Toggles to allow zooming and rotation on any map or cutscene (May need to use Mouse3 Drag to rotate in some situations) - * Toggle to allow changing the camera tilt to get an almost 3rd person view. (experimental preview) - * **Love is Free** - * (***Ronin***) Lich path no longer blocks romance + * **Quality of Life** + * Remote Companion Dialog - Allow remote companions (even former party members) to make comments on dialog you + are having + * Include Former Companions - Let's you also listen to remarks from your former companions. + * This is experimental so it may break dialogs. If you find an issue please come find me (Narria) on the WoTR + discord (https://discord.gg/CRsCZNw8Rf) and give me a copy of the save. + * **Camera** + * **Massive improvements** + * Camera settings moved to new section + * Toggles to allow zooming and rotation on any map or cutscene (May need to use Mouse3 Drag to rotate in some + situations) + * Toggle to allow changing the camera tilt to get an almost 3rd person view. (experimental preview) + * **Love is Free** + * (***Ronin***) Lich path no longer blocks romance * **Level Up** - * (***Truinto***) Unlock Party Level Cap (continuous or exponential) + * (***Truinto***) Unlock Party Level Cap (continuous or exponential) * **Enchantment** - * Added collation for enchantment types - * Show enchantment rating - * Improved sorting of enchantments - * Show character name equiping an item if is equiped - * Fixed crash on null comment/prefix/suffix in BlueprintItemEnchantment (pheonix99) + * Added collation for enchantment types + * Show enchantment rating + * Improved sorting of enchantments + * Show character name equiping an item if is equiped + * Fixed crash on null comment/prefix/suffix in BlueprintItemEnchantment (pheonix99) * **Loot** - * Item rarity takes into account enchantment rarity + * Item rarity takes into account enchantment rarity * **Armies Editor** - * Added the ability to spawn an army as either friendly or hostile. - * Add buttons for destroying an army and for restoring movement points + * Added the ability to spawn an army as either friendly or hostile. + * Add buttons for destroying an army and for restoring movement points * **Misc** - * Fixed crasher that led to interference with World Crawl and possibly other mods - * Fixed a crasher that impacted some text fields - * Reworked code around scroll coloring to improve stability + * Fixed crasher that led to interference with World Crawl and possibly other mods + * Fixed a crasher that impacted some text fields + * Reworked code around scroll coloring to improve stability + ### ver 1.4.8.1 + * Fixed some edge cases for **Jealousy Begone!** + ### Ver 1.4.8 + * **Bag of Tricks** - * **Love is Free** - * Mythic path no longer blocks romance with the queen - * Toggle to allow spells/abilities/items toolbar pop-ups grow wider - * **Group Picker** - * Changing party in a map does not break characters anymore - * No longer brings up the mod when activating it via hot key - * Now works on the global map too + * **Love is Free** + * Mythic path no longer blocks romance with the queen + * Toggle to allow spells/abilities/items toolbar pop-ups grow wider + * **Group Picker** + * Changing party in a map does not break characters anymore + * No longer brings up the mod when activating it via hot key + * Now works on the global map too * **Enchantment** - * (***Pheonix99***) Fix for stack merging eating enchants - EX: If you add mighty fists to an amulet of natural armor, then stick it in inventory where you have another natural armor amulet of the same type, they're merged into a stack and the mighty fists enchant vanishes + * (***Pheonix99***) Fix for stack merging eating enchants - EX: If you add mighty fists to an amulet of natural + armor, then stick it in inventory where you have another natural armor amulet of the same type, they're merged + into a stack and the mighty fists enchant vanishes * **Search 'n Pick** - * Fixed issue where if you select items and then weapons that the subcategories would not appear - * Fixed some misc crashes caused by bad blueprints + * Fixed issue where if you select items and then weapons that the subcategories would not appear + * Fixed some misc crashes caused by bad blueprints * **Crusade Editor** - * Fixed issue where Build Time slider wouldn't slide + * Fixed issue where Build Time slider wouldn't slide * **Misc** - * Fixed some crashers in inventory UI + * Fixed some crashers in inventory UI + ### Ver 1.4.7 + * **Bag of Tricks** - * ♥♥ Love is Free ♥♥ - Allow any gender for any romance - * Jelousy Begone! - Allow multiple romances at the same time - * Big thanks to ***Paladingineer*** and ***Ronin*** for their contributions to making this possible + * ♥♥ Love is Free ♥♥ - Allow any gender for any romance + * Jelousy Begone! - Allow multiple romances at the same time + * Big thanks to ***Paladingineer*** and ***Ronin*** for their contributions to making this possible * **Party Editor** - * (***Truinto***) added clear buttons for opposition schools - * (***Truinto***) allow CompletelyNormal Metamagic to work while using free metamagic cheat + * (***Truinto***) added clear buttons for opposition schools + * (***Truinto***) allow CompletelyNormal Metamagic to work while using free metamagic cheat * **Search 'n Pick** - * (***BarleyFlour***) Added the ability to spawn an army as either friendly or hostile + * (***BarleyFlour***) Added the ability to spawn an army as either friendly or hostile * **Armies Editor** - * can edit leader stats - * can edit leader skills -* **Etude Editor** - * Ability to show GUID and comments + * can edit leader stats + * can edit leader skills +* **Etude Editor** + * Ability to show GUID and comments + ### Ver 1.4.6 (mainline 1.1.0i) + * **Updated to WoTR 1.1.0.i (beta is done and released)** * **Bag of Tricks** - * UI improvements for use and transfer - * ***(Flat)*** click modifiers for use and transfer are now configurable + * UI improvements for use and transfer + * ***(Flat)*** click modifiers for use and transfer are now configurable * **Search 'n Pick** - * Improved Performance of parameterized feature and feature selection filters -* **Etude Editor** - * Expanding children of etudes inside elements now works - * Now shows conflicting Etudes for AnotherEtudeOfGroupIsPlaying - * Fixed nasty crash when you opened elements on some of Camellia's romance items like the one with the necklace - * Made things even wider so stuff formats well - * ***(Flat)*** Editor will not crash if localization files are not found + * Improved Performance of parameterized feature and feature selection filters +* **Etude Editor** + * Expanding children of etudes inside elements now works + * Now shows conflicting Etudes for AnotherEtudeOfGroupIsPlaying + * Fixed nasty crash when you opened elements on some of Camellia's romance items like the one with the necklace + * Made things even wider so stuff formats well + * ***(Flat)*** Editor will not crash if localization files are not found * **Settings** - * ***(Flat)*** Fixed a typo for developer console + * ***(Flat)*** Fixed a typo for developer console * **Post-Build (Developer QoL)** - * ***(Flat)*** Post-Build script now copies over all packaged files to WrathPath + * ***(Flat)*** Post-Build script now copies over all packaged files to WrathPath + ### Ver 1.4.5 (Universal -- works on mainline and beta) + * **General** - * 1.4.5 is now cross compatible between beta and mainline + * 1.4.5 is now cross compatible between beta and mainline * **Bag of Tricks** - * Teleport keys now work on the local map + * Teleport keys now work on the local map * **Level Up & Multiclass** - * If multiclass or ignore class and feat restrictions is set you can see a full range of class choices for pets - * **Warning** *this is experimental so save early and often* - * ***(Aephiex)*** Added new toggle 'Ignore Required Class Levels' + * If multiclass or ignore class and feat restrictions is set you can see a full range of class choices for pets + * **Warning** *this is experimental so save early and often* + * ***(Aephiex)*** Added new toggle 'Ignore Required Class Levels' * **Search 'n Pick** - * You can now add feature selections such as Deity. - * **Warning** *this is experimental so save early and often* + * You can now add feature selections such as Deity. + * **Warning** *this is experimental so save early and often* * **Etude Editor** - * You now have actions inside the Etude Elements which may be useful for quite a few things - * Shows the state of conditional elements -* **Quest Editor** - * you can now teleport to stages of quest progression when available + * You now have actions inside the Etude Elements which may be useful for quite a few things + * Shows the state of conditional elements +* **Quest Editor** + * you can now teleport to stages of quest progression when available * **Teleportation** - * Teleporting to global map points now work across the global map and kenabres map + * Teleporting to global map points now work across the global map and kenabres map + ### Ver 1.4.4 (for beta) + * **Ported to Beta** (Game Version 1.1.0d) * **Bag of Tricks** Made spell acronyms default to off as intended (sorry about that) + ### Ver 1.4.3.1 (use this for current non beta until 1.1.x releases) - * Autoload of saves on launch can be disabled by holding down shift -### Ver 1.4.3 + +* Autoload of saves on launch can be disabled by holding down shift + +### Ver 1.4.3 + * **Level Up & Multiclass** - * ***Fixed Major Bugs in Multiclass*** - * Fixed bug where class powers, curses, etc were not being applied in some cases - * Fixed bugs where multiclass classes were being applied more than once on companions - * In Game Multiclass UI - * Now works during character creation - * Various stability improvements - * You can now unlock Lich Mythic path (was accidently removed) -* **Bag of Tricks** - * **Spell/Ability Pickers** option to show acronyms in the spell slots in order to be able to identify and choose them faster - * (***Aephiex***) Added new Quality of Life toggle 'Respec Refund Scrolls' + * ***Fixed Major Bugs in Multiclass*** + * Fixed bug where class powers, curses, etc were not being applied in some cases + * Fixed bugs where multiclass classes were being applied more than once on companions + * In Game Multiclass UI + * Now works during character creation + * Various stability improvements + * You can now unlock Lich Mythic path (was accidently removed) +* **Bag of Tricks** + * **Spell/Ability Pickers** option to show acronyms in the spell slots in order to be able to identify and choose + them faster + * (***Aephiex***) Added new Quality of Life toggle 'Respec Refund Scrolls' * **Party Editor** Fixed bug where Recruitment was not using the new code path * **Search 'n Pick** - * Items now display flavor text if it is available and it can be searched whenever you search descriptions - * Fixed crasher when viewing Unlockable Flags -* **Etude Editor** - * Etude Elements are now more obvious that you can explore them - * The editor can now expand width as wide as needed which should improve layout. This means you will need to use the horizontal scroll bars for deep exploration + * Items now display flavor text if it is available and it can be searched whenever you search descriptions + * Fixed crasher when viewing Unlockable Flags +* **Etude Editor** + * Etude Elements are now more obvious that you can explore them + * The editor can now expand width as wide as needed which should improve layout. This means you will need to use the + horizontal scroll bars for deep exploration + ### Ver 1.4.2.2 + * **Level Up & Multiclass** fixed crasher in save due to interaction with in game level up UI + ### Ver 1.4.2.1 + * **Level Up & Multiclass** fixed crasher in the main menu when viewing level up options + ### Ver 1.4.2 -* **Level Up & Multiclass** - * You can now configure multiclass options directly in the level up screen! - * You are now prevented from making choices that break level up like selecting all of your existing classes as multiclass. Instead choose your desired new class as a multiclass and level up one of your existing - * Fixed issue where char gen class choices would get applied to companions during some level ups. Please give feedback if you see issues respecing your companions - * removed unimplemented flags in the multi-class config to avoid confusion. Please file feature requests if there are any that you really wanted. -* **Bag of Tricks** - * moved teleport keys to go with teleport party to me and gave it its own section with explainer text - * Added common cheat to reset interaction point skill checks in an area - * (***Aephiex***) Renamed 'Disable Arcane Spell Failure' into 'Disable Armor & Shield Arcane Spell Failure' to make it more clear - * (***Aephiex***) Fixed issue where 'Disable Armor Max Dexterity' permanently changes armor max dexterity into 99 even after disabling this option - * (***Aephiex***) Added new option 'Disable Armor Speed Reduction' which disables the -10 speed while wearing medium and heavy armor - * (***Aephiex***) Added new option 'Disable Armor & Shield Checks Penalty' which disables the checks penalty applied while wearing armor and holding shield - * (***Aephiex***) Added new multiplier slider 'Increase Carry Capacity (Party Only)' which multiplies party carry capacity without changing the carry capacity of individual characters; the original multiplier still affects both + +* **Level Up & Multiclass** + * You can now configure multiclass options directly in the level up screen! + * You are now prevented from making choices that break level up like selecting all of your existing classes as + multiclass. Instead choose your desired new class as a multiclass and level up one of your existing + * Fixed issue where char gen class choices would get applied to companions during some level ups. Please give + feedback if you see issues respecing your companions + * removed unimplemented flags in the multi-class config to avoid confusion. Please file feature requests if there + are any that you really wanted. +* **Bag of Tricks** + * moved teleport keys to go with teleport party to me and gave it its own section with explainer text + * Added common cheat to reset interaction point skill checks in an area + * (***Aephiex***) Renamed 'Disable Arcane Spell Failure' into 'Disable Armor & Shield Arcane Spell Failure' to make + it more clear + * (***Aephiex***) Fixed issue where 'Disable Armor Max Dexterity' permanently changes armor max dexterity into 99 + even after disabling this option + * (***Aephiex***) Added new option 'Disable Armor Speed Reduction' which disables the -10 speed while wearing medium + and heavy armor + * (***Aephiex***) Added new option 'Disable Armor & Shield Checks Penalty' which disables the checks penalty applied + while wearing armor and holding shield + * (***Aephiex***) Added new multiplier slider 'Increase Carry Capacity (Party Only)' which multiplies party carry + capacity without changing the carry capacity of individual characters; the original multiplier still affects both * **Party Editor** renamed 'Facts' to the more correct name 'Features' * **Search 'n Pick** - * made categories for 'Features' much more useful. Choose Search 'n Pick > Features and see all the interesting categories to explore ^_^ - * you can now hover over the type field for a blueprint to get a button that lets you show other items in the same category + * made categories for 'Features' much more useful. Choose Search 'n Pick > Features and see all the interesting + categories to explore ^_^ + * you can now hover over the type field for a blueprint to get a button that lets you show other items in the same + category * **Enchantment** - * Sandal has discovered the mythic path of Trickster and can reveal hidden secrets in your items. Look for some fun new buttons at the bottom of the target info on the enchantment tab. + * Sandal has discovered the mythic path of Trickster and can reveal hidden secrets in your items. Look for some fun + new buttons at the bottom of the target info on the enchantment tab. * **Quest Editor** * shows some area and location info in anticipating of adding teleport to quest locations soon + ### Ver 1.4.1 + * **Etude Editor** fixed crasher + ### Ver 1.4.1 -* **Important** This release changes where settings for ***gestalt, multiclass, allow level past 20*** state is saved from ToyBox settings to your game save. This will **RESET all these settings** to their **DEFAULT** state. We provide a way to migrate your settings from legacy ToyBox settings to your current save file. Look for it under **Level Up** in the multiclass config area. **Note** you will need to do this the first time you load any save that you wish to continue using these features and make sure you remember to save after migration. -* **Multiclasss & Gestalt** - * Moved save state from settings into Owlcats save file extensions for save files so now your gestalt state will live in your save and never get out of sync due to toybox settings changes - * Added migration buttons if ToyBox detects that you have old save state in your settings and nothing in the current save. Please use these with care. - * You also have the option to remove the migration data - * Added descriptive string for gestalt flag in Level Up and Party Editor - * Improved new multiclass selection UI to allow you to select other archetypes for a class that you have an existing archetype. This will only apply during respect. - * Added orange warning message to inform user of this behavior - * Now shows overall character level and level of each class or archetype that you have - * Fixed bug where we were not syncing gestalt state at some key times like after loading and during migration - * ***Note*** this should make ToyBox Multiclass and Gestalt play better with respecing + +* **Important** This release changes where settings for ***gestalt, multiclass, allow level past 20*** state is saved + from ToyBox settings to your game save. This will **RESET all these settings** to their **DEFAULT** state. We provide + a way to migrate your settings from legacy ToyBox settings to your current save file. Look for it under **Level Up** + in the multiclass config area. **Note** you will need to do this the first time you load any save that you wish to + continue using these features and make sure you remember to save after migration. +* **Multiclasss & Gestalt** + * Moved save state from settings into Owlcats save file extensions for save files so now your gestalt state will + live in your save and never get out of sync due to toybox settings changes + * Added migration buttons if ToyBox detects that you have old save state in your settings and nothing in the current + save. Please use these with care. + * You also have the option to remove the migration data + * Added descriptive string for gestalt flag in Level Up and Party Editor + * Improved new multiclass selection UI to allow you to select other archetypes for a class that you have an existing + archetype. This will only apply during respect. + * Added orange warning message to inform user of this behavior + * Now shows overall character level and level of each class or archetype that you have + * Fixed bug where we were not syncing gestalt state at some key times like after loading and during migration + * ***Note*** this should make ToyBox Multiclass and Gestalt play better with respecing * **Search 'n Pick** made collation keys searchable * **Army Editor** now show leader info and lets you view their skills (editing is coming in 1.4.2) * **Etude Editor** - * Show translated comments (not 100% but much better than nothing) -### Ver 1.4.0 + * Show translated comments (not 100% but much better than nothing) + +### Ver 1.4.0 + * **New Etude Editor** is a powerful way to view and edit Etude progression - * Tree view - * etudes and their children - * can expand immediate children or go all the way down - * can reveal the various conditions involved with the etude which is very powerful for understanding their mechanics. This will be improved to let you manipulate the conditions as much as possible - * Actions to start, unstart and complete etudes are available - * Much more coming which I will talk about soon + * Tree view + * etudes and their children + * can expand immediate children or go all the way down + * can reveal the various conditions involved with the etude which is very powerful for understanding their + mechanics. This will be improved to let you manipulate the conditions as much as possible + * Actions to start, unstart and complete etudes are available + * Much more coming which I will talk about soon * **Bag of tricks** - * (***ShadowRanger***) Added a fix that should resolve the zoomed out camera bug if you had both ToyBox and Free Camera installed. You may need to reset Toybox's fovMultiplier to 1 for it to take effect. - * (***ShadowRanger***) Added a new sub-menu for all 'begone' versions called ***Icky Stuff Begone!!!*** and added green explainer text - * (***ShadowRanger***) Added retrievers begone - * (***Mafemergency***) Tweak to reset interaction skill checks -* **Multiclass & Gestalt** - * Multiclass config list marks the classes you have in orange and shows the state of the gestalt flag - * Note these remain separate for now but I hope showing them in a unified UI will make this more clear -* **Party Editor** - * fixed crasher when you lowered caster level multiple times - * ***Add All*** spells no longer adds extra copies of spells you already learned - * Fixed layout issue that would push ***Add All*** to the right side of the screen - * ***Select From All Spellbooks*** now shows the spells from all spellbooks -* **Loot Checklist** - * Checklist is now searchable - * now shows the private name of the area you are in so you can find it in Search 'n Pick - * Items within a container or unit sort by rarity + * (***ShadowRanger***) Added a fix that should resolve the zoomed out camera bug if you had both ToyBox and Free + Camera installed. You may need to reset Toybox's fovMultiplier to 1 for it to take effect. + * (***ShadowRanger***) Added a new sub-menu for all 'begone' versions called ***Icky Stuff Begone!!!*** and added + green explainer text + * (***ShadowRanger***) Added retrievers begone + * (***Mafemergency***) Tweak to reset interaction skill checks +* **Multiclass & Gestalt** + * Multiclass config list marks the classes you have in orange and shows the state of the gestalt flag + * Note these remain separate for now but I hope showing them in a unified UI will make this more clear +* **Party Editor** + * fixed crasher when you lowered caster level multiple times + * ***Add All*** spells no longer adds extra copies of spells you already learned + * Fixed layout issue that would push ***Add All*** to the right side of the screen + * ***Select From All Spellbooks*** now shows the spells from all spellbooks +* **Loot Checklist** + * Checklist is now searchable + * now shows the private name of the area you are in so you can find it in Search 'n Pick + * Items within a container or unit sort by rarity * **Search 'n Pick** cleaned up some layout behavior that would make the character picker get wider than necessary * **Kingdom Editor** now has a bunch of new adjustments including: - * Current Day - * Current Turn - * Claim Cost Modifier - * Claim Time Modifier - * Rankup Time Modifer - * Build Time Modifier - * Random Encounter Chance Modifier (Unclaimed, Claimed, Upgraded) - * Confidence (Royal Court) - * Confidence (Nobles) - * Victories This Week - * Unrest - * Kingdom Alignment + * Current Day + * Current Turn + * Claim Cost Modifier + * Claim Time Modifier + * Rankup Time Modifer + * Build Time Modifier + * Random Encounter Chance Modifier (Unclaimed, Claimed, Upgraded) + * Confidence (Royal Court) + * Confidence (Nobles) + * Victories This Week + * Unrest + * Kingdom Alignment * **Misc** - * Fixed crasher when editing slider values via keyboard + * Fixed crasher when editing slider values via keyboard ### Ver 1.3.19 -* **Bag of Tricks** - * You can now enable Developer Mode which enables the developer console which you can access by hitting tilde ` - * (***ShadowRanger***) Bugfix for taking 10 out of combat: corrected missing IsInCombat flag - * (***ShadowRanger***) Added a vescavors-begone option, similar to spiders-begone, to replace all vescavor models - * (***Mafemergency***)* Tweak to highlight hidden interactable objects - * (***ArcaneTrixter***)* Added button to correctly reassign main character back to original main character when it's been broken by gestalt or other issues. -* **Multiple Classes Per Level Up** - added safey check where if all classes are gestalt (can happen if you set your original class gestalt and the load a save from before you gained your new main class) then we treat the first one as non gestalt + +* **Bag of Tricks** + * You can now enable Developer Mode which enables the developer console which you can access by hitting tilde ` + * (***ShadowRanger***) Bugfix for taking 10 out of combat: corrected missing IsInCombat flag + * (***ShadowRanger***) Added a vescavors-begone option, similar to spiders-begone, to replace all vescavor models + * (***Mafemergency***)* Tweak to highlight hidden interactable objects + * (***ArcaneTrixter***)* Added button to correctly reassign main character back to original main character when it's + been broken by gestalt or other issues. +* **Multiple Classes Per Level Up** - added safey check where if all classes are gestalt (can happen if you set your + original class gestalt and the load a save from before you gained your new main class) then we treat the first one as + non gestalt * **Enchantment** now displays item attributes (magic, natural, etc) for the weapon you are editing * **Loot Coloring** - Improved legibility of colored loot in various loot views * **Loot Checklist** - Containers and Units now sort with higest rarity loot at the top * **Party Editor** - * Changed Stat < and > arrows to have a box background instead of the ugly buttons - * Fixed long standing crasher when editing stats in the text field and you hit enter - * (***ArcaneTrixter***) Added toggle to allow companions to take Mythic Classes + * Changed Stat < and > arrows to have a box background instead of the ugly buttons + * Fixed long standing crasher when editing stats in the text field and you hit enter + * (***ArcaneTrixter***) Added toggle to allow companions to take Mythic Classes * **Search 'n Pick** Massive improvements to sub-categories with a lot more things to filter by - * This is a first step towards being able to filter on more than one value in the subcategory at the same time - * Shows more data mining information including various attributes associated with blueprints such as ***IsMythic***, ***IsMagic***, and many more - * Sub-categories with numbers in them now sort properly - * You can see item costs binned into ranges with units of '⊙' + * This is a first step towards being able to filter on more than one value in the subcategory at the same time + * Shows more data mining information including various attributes associated with blueprints such as ***IsMythic***, + ***IsMagic***, and many more + * Sub-categories with numbers in them now sort properly + * You can see item costs binned into ranges with units of '⊙' * **Army Editor** - * Moved Armies into its own Tab - * Fixed bug where squads disclosure would never close - * You can now open one squad in your army and another on the demon armies - * Reworked the layout to resemble other similar editors - * Now displays the *Locaton* of the army - * You can ***Teleport*** to any army from anywhere in the game - * You can now ***Summon*** any army to you if you are on the global map + * Moved Armies into its own Tab + * Fixed bug where squads disclosure would never close + * You can now open one squad in your army and another on the demon armies + * Reworked the layout to resemble other similar editors + * Now displays the *Locaton* of the army + * You can ***Teleport*** to any army from anywhere in the game + * You can now ***Summon*** any army to you if you are on the global map * **Crusade Editor** - * Reworked UI to match design of other parts of the mod - * Fixed bug where it was displaying the Exp required for the current rank rather than the next rank - * (***Delth***) Added Crusade Cards (Events, Decrees etc) time multiplier (feature ported from KingdomResolution - thanks Spacehamster!) + * Reworked UI to match design of other parts of the mod + * Fixed bug where it was displaying the Exp required for the current rank rather than the next rank + * (***Delth***) Added Crusade Cards (Events, Decrees etc) time multiplier (feature ported from KingdomResolution - + thanks Spacehamster!) * **General Fixes** - * (***ArcaneTrixter***) Made spell learning for spontaneous casters less buggy. Is currently tied to game load and level up, so if you're having issues with spells known try reloading. - * (***ArcaneTrixter***) Made it so you shouldn't be able to gestalt all non-mythic classes and become a level 0 character. - * (***ArcaneTrixter***) Fixed forced progressions such as Azata Life-Bonding friendship in cases where you aren't using the feat multiplier. + * (***ArcaneTrixter***) Made spell learning for spontaneous casters less buggy. Is currently tied to game load and + level up, so if you're having issues with spells known try reloading. + * (***ArcaneTrixter***) Made it so you shouldn't be able to gestalt all non-mythic classes and become a level 0 + character. + * (***ArcaneTrixter***) Fixed forced progressions such as Azata Life-Bonding friendship in cases where you aren't + using the feat multiplier. ### Ver 1.3.18 + * **Key Bindings** - now shows conflicting keyBinds * **Bag of Tricks** - * ***Equipment No Weight*** tweek is back. It was stolen by quasits who work for the merchant guild... - * Added a ***Buff Like A Godess*** which makes you practically invulnerable - * Renamed ***Full Buffs Please*** to ***Common Buffs*** because it gives you Bless/Haste/Displacement/Heroism - * (***ShadowRanger***) Added in two ways of taking 10 out of combat. Always - rolls a 10 everytime. Minimum - rolls at least a 10 everytime. - * (***Mafemergency***) Added an action to lobotomize enemies bindable key that will render them unable to move or attack in combat. This is useful for when you need punching bags that don't fight back - * (***Mafemergency***) Fixed perception check rerolling - * (***Mafemergency & Narria***) Added a alternate game timescale multiplier option and bindable key to toggle between them. Useful for when you want to fast forward through things + * ***Equipment No Weight*** tweek is back. It was stolen by quasits who work for the merchant guild... + * Added a ***Buff Like A Godess*** which makes you practically invulnerable + * Renamed ***Full Buffs Please*** to ***Common Buffs*** because it gives you Bless/Haste/Displacement/Heroism + * (***ShadowRanger***) Added in two ways of taking 10 out of combat. Always - rolls a 10 everytime. Minimum - rolls + at least a 10 everytime. + * (***Mafemergency***) Added an action to lobotomize enemies bindable key that will render them unable to move or + attack in combat. This is useful for when you need punching bags that don't fight back + * (***Mafemergency***) Fixed perception check rerolling + * (***Mafemergency & Narria***) Added a alternate game timescale multiplier option and bindable key to toggle + between them. Useful for when you want to fast forward through things * **Party Editor** - * (***ArcaneTrixter***) Fix various issues with learning spells related to merging spellbooks and gestalt spellcasters. On leveling up, you should be prompted to pick spells as a caster of the appropriate caster level. - * (***ArcaneTrixter***) Fixed small bug related to paladin/ranger caster level. Feel free to use the caster level cheats if you wish to continue playing with that! + * (***ArcaneTrixter***) Fix various issues with learning spells related to merging spellbooks and gestalt + spellcasters. On leveling up, you should be prompted to pick spells as a caster of the appropriate caster level. + * (***ArcaneTrixter***) Fixed small bug related to paladin/ranger caster level. Feel free to use the caster level + cheats if you wish to continue playing with that! * **Loot** - Made scribable scrolls show up as uncommon * **Enchantment** - * Sandal knows you like to hoard loot so he will help you find items with a handy new Search field in the inventory column - * Added very basic export and inport commands that allows you to save and add a list of items to a file based on the type (e.g. Weapon.json). These files live in a new ToyBox folder that in the same folder that contains your saved games - * Added dividers in the target item box + * Sandal knows you like to hoard loot so he will help you find items with a handy new Search field in the inventory + column + * Added very basic export and inport commands that allows you to save and add a list of items to a file based on the + type (e.g. Weapon.json). These files live in a new ToyBox folder that in the same folder that contains your saved + games + * Added dividers in the target item box * *Search 'n Pick* *Improved scrolling performance after searching for Paramterized Feats ### Ver 1.3.17 - * Brutal Unfair Difficulty & Brutal Level Slider - * Improved explainer text for Brutal Unfair Difficulty to describe how Unfair was applying Unfair Modifiers twice and that Owlcat fixed the bug to only apply them once. + +* Brutal Unfair Difficulty & Brutal Level Slider + * Improved explainer text for Brutal Unfair Difficulty to describe how Unfair was applying Unfair Modifiers twice + and that Owlcat fixed the bug to only apply them once. * Changed slider values as follows - * 1 - Unfair (Current Game) - * 2 - Brutal (As released with double Unfair bug) - * 3+ - Applies the Unfair modifiers 3 or more times + * 1 - Unfair (Current Game) + * 2 - Brutal (As released with double Unfair bug) + * 3+ - Applies the Unfair modifiers 3 or more times * Provide a clear title for the Brutality Levels (Unfair, Brutal, Uncommon, Rare, Epic, etc) * Loot Coloring - * Made sure non magic items had rarity capped at Common - * Titles and loot coloring should match now - * This means rarity for weapons and armor now show up correctly in Enchantment. Sandal apologizes for misrepresenting your items... + * Made sure non magic items had rarity capped at Common + * Titles and loot coloring should match now + * This means rarity for weapons and armor now show up correctly in Enchantment. Sandal apologizes for + misrepresenting your items... * Cleaned up UI around Spellbook Edit toggle * Improved the look and layout of all Sliders * Enchantment Editor - * Search now updates as you type - * Added Search Button and Match Count - * Made search text persistent in settings - * Misc UI cleanup - * (Truinto) Added support for enchanting shields - * (Truinto) Added support for enchanting double ended weapons -* (ArcaneTrixter) Handled additional edge cases around factions for Crusade Power Multiplier; should properly make anything that isn't explicitly player faction get enemy multiplier. + * Search now updates as you type + * Added Search Button and Match Count + * Made search text persistent in settings + * Misc UI cleanup + * (Truinto) Added support for enchanting shields + * (Truinto) Added support for enchanting double ended weapons +* (ArcaneTrixter) Handled additional edge cases around factions for Crusade Power Multiplier; should properly make + anything that isn't explicitly player faction get enemy multiplier. ### Ver 1.3.16 @@ -662,62 +1373,67 @@ WARNING: this tool can both miraculously fix your broken progression or it can b ### Ver 1.3.15 -* **Note**: Patch 1.0.7c-e patch of the game introduced a few issues impacting ToyBox. Me and my team are working on resolving these as quickly as we can.  Please help out by filing issues here: https://github.com/cabarius/ToyBox/issues +* **Note**: Patch 1.0.7c-e patch of the game introduced a few issues impacting ToyBox. Me and my team are working on + resolving these as quickly as we can. Please help out by filing issues here: https://github.com/cabarius/ToyBox/issues * Bag of Tricks - * Allow Shift Click To Transfer Entire Stack - * Holding down shift lets you transfer whole stacks of items to the vendor and other similar things - * Moved Game Time Scale Slider into Quality of Life - * Fixed an issue with "Refill consumables in belt slots" that would cause items to disappear from inventory if you shifted an item and then immediately triggered a battle through dialog + * Allow Shift Click To Transfer Entire Stack + * Holding down shift lets you transfer whole stacks of items to the vendor and other similar things + * Moved Game Time Scale Slider into Quality of Life + * Fixed an issue with "Refill consumables in belt slots" that would cause items to disappear from inventory if you + shifted an item and then immediately triggered a battle through dialog * Loot Coloring - * Fixed issue that caused notable items to no longer show their special highlight - * Darkened trash color + * Fixed issue that caused notable items to no longer show their special highlight + * Darkened trash color * Level Up - * You can now unlock individual mythic paths - * Made mythic path unlock more robust and refresh the UI to reflect the new unlocks without reloading - * Changed "Make All Feat Selections Optionsl" to "Make All Feature Selections Optional" - * Fixed bug that prevented mythic level up from happening with multiclass enabled + * You can now unlock individual mythic paths + * Made mythic path unlock more robust and refresh the UI to reflect the new unlocks without reloading + * Changed "Make All Feat Selections Optionsl" to "Make All Feature Selections Optional" + * Fixed bug that prevented mythic level up from happening with multiclass enabled * Party Editor - * You can now see and change your gender in Stats. This may help you do same gender romances - * Changing a character's name immediately refreshes the tool tips and other UI - * You can now hit +1 to level up multiple times before going to level up screen - * Reworked Level Up UI to give clear feedback about how many levels you have given yourself - * Renamed "Levels Like a Legendary Hero" to "Allow Levels Past 20" and added some green explainer text - * Moved it to just under Multiple Classes Per Level Up - -* Allow Achievements While Using Mods now also marks your save file as unmodded so you can continue to earn achievements after disabling ToyBox (as long as you disable all other mods) + * You can now see and change your gender in Stats. This may help you do same gender romances + * Changing a character's name immediately refreshes the tool tips and other UI + * You can now hit +1 to level up multiple times before going to level up screen + * Reworked Level Up UI to give clear feedback about how many levels you have given yourself + * Renamed "Levels Like a Legendary Hero" to "Allow Levels Past 20" and added some green explainer text + * Moved it to just under Multiple Classes Per Level Up + +* Allow Achievements While Using Mods now also marks your save file as unmodded so you can continue to earn achievements + after disabling ToyBox (as long as you disable all other mods) * (Vek17) Added Turn Based Combat Delay slider to Bag of Tricks > Quality of Life ### Ver 1.3.14 * Party Editor > Adding characters to the party - * Last change to add npcs to party broke when adding characters you have already recruited. - * You can now recruit NPCs that are not in your wider group - * Restored the previous Add functionality to add characters that you have aleady recruited - * Any char in your party with either have Add or Recruit but not both - * Colored both Recruit and Respec to cyan and made the warnings  -* Added some more extensive debug logging for feature selection multiplier.  (working on a proper fix) + * Last change to add npcs to party broke when adding characters you have already recruited. + * You can now recruit NPCs that are not in your wider group + * Restored the previous Add functionality to add characters that you have aleady recruited + * Any char in your party with either have Add or Recruit but not both + * Colored both Recruit and Respec to cyan and made the warnings* Added some more extensive debug logging for feature + selection multiplier.(working on a proper fix) * (ArcaneTrixter) Fixed Legend exp on level -* (Delth) Added ability to add/remove skills from generals (works on currently selected army) - new selection type in Search'n'Pick +* (Delth) Added ability to add/remove skills from generals (works on currently selected army) - new selection type in + Search'n'Pick ### Ver 1.3.13.1 * Fixed Crash in Echantments when you have no inventory in a new game * Enchantments - show descriptions of existing enchantments of the selected weapon -* Identify All now identifies items that are equiped. Useful if you recruit an NPC to your party because you see cool stuff on them in Loot Checklist +* Identify All now identifies items that are equiped. Useful if you recruit an NPC to your party because you see cool + stuff on them in Loot Checklist ### Ver 1.3.13 -* Sandal says `Enchantment`! -  * Sandal comes through an interplaner portal from Thedas (Dragon Age) and has blessed ye brave crusaders with the power of item Enchantment - * This is a powerful yet easy to use enchantment editor available in a new top level tab called 'Enchantment' - * Special thanks to Truinto for delivering awesome base code and Narria for polishing the UI +* Sandal says `Enchantment`!* Sandal comes through an interplaner portal from Thedas (Dragon Age) and has blessed ye + brave crusaders with the power of item Enchantment + * This is a powerful yet easy to use enchantment editor available in a new top level tab called 'Enchantment' + * Special thanks to Truinto for delivering awesome base code and Narria for polishing the UI * Updated item rarity to account for added enchantments either by Sandal or other sources * Party Editor Improvements - * You can now rename character, pets and others under your control - * Units you add to your party don't run away when you leave the area + * You can now rename character, pets and others under your control + * Units you add to your party don't run away when you leave the area * Added pagination to Search 'n Pick so now you can go page by page or slide through more than 1000 search results * Added experimental Show Tree in Party Editor > Char > Facts @@ -731,58 +1447,71 @@ WARNING: this tool can both miraculously fix your broken progression or it can b ### Ver 1.3.11 * Found a grey that works for trash loot. The previous brown still looked too much like a meaningful loot color -* (ArcaneTrixter) Added a 'Brutal Unfair Difficulty' to Quality of Life. Toggle on if you miss the previous challenge of Unfair or if you thought it too easy there's a slider now. +* (ArcaneTrixter) Added a 'Brutal Unfair Difficulty' to Quality of Life. Toggle on if you miss the previous challenge of + Unfair or if you thought it too easy there's a slider now. * (ArcaneTrixter) Modified 'Disable Arcane Spell Failure' to set spell failure to 0 so you can Blink freely. * (ArcaneTrixter) No longer squaring the multiplier from 'Spells Per Day' when applying to spellbooks. * (Vek17) Fixed issues around the Feat Multiplier including - * All story companions feats/backgrounds/etc. - * most notably a certain wizard who unlearns how to cast spells if your multiplier is at least 8 - * This is retroactive if you ever level up in the future with the multiplier on. - * Messed up All mythic 'fake' companions like Skeleton Minion for lich or Azata summon. - * Caused certain gestalt combinations to give sudden ridiculous level-ups of companions or sneak attack or kinetic blast. + * All story companions feats/backgrounds/etc. + * most notably a certain wizard who unlearns how to cast spells if your multiplier is at least 8 + * This is retroactive if you ever level up in the future with the multiplier on. + * Messed up All mythic 'fake' companions like Skeleton Minion for lich or Azata summon. + * Caused certain gestalt combinations to give sudden ridiculous level-ups of companions or sneak attack or kinetic + blast. ### Ver 1.3.10 * Added new top level tab: "Loot * Moved Loot Coloring Settings to Loot Tab -* With the kind permission and help from the legend himself, @Hambeard, ToyBox now supports a new version of TheLootCheckList    Loot color tweaks: notable is now yellow and trash is now an appropriate sh_t brown color +* With the kind permission and help from the legend himself, @Hambeard, ToyBox now supports a new version of + TheLootCheckList Loot color tweaks: notable is now yellow and trash is now an appropriate sh_t brown color * Hot Key Binding control now scales properly to Unity Mod Manager UI scale setting. * (ArcaneTrixter) Added 'Ignore Prerequisite Features (like Race) when choosing Class' to levelup. * (ArcaneTrixter) Bumped max burn for kineticist burn cheat to 30 so you never have to be burned again. * (ArcaneTrixter) Reorganized stat section of party editor to not just be the random enum order. * (ArcaneTrixter) Allowed multiple judgements to be active at once. * (ArcaneTrixter) Added toggle to remove Level 20 Caster Level cap. -* (ArcaneTrixter) Added ability to merge standalone mythics into any spellbook. Spell slots and spells per day are still based on the original type, e.g. Magus gets 6th level spells and below. -* (ArcaneTrixter) Added Add All button to spellbooks when browsing new spells. It works with Search All Spellbooks and respects current search results +* (ArcaneTrixter) Added ability to merge standalone mythics into any spellbook. Spell slots and spells per day are still + based on the original type, e.g. Magus gets 6th level spells and below. +* (ArcaneTrixter) Added Add All button to spellbooks when browsing new spells. It works with Search All Spellbooks and + respects current search results * (Delth) hopefully fixed gestalt skillpoint calculations ### Ver 1.3.9 * Made Alignment section in Bag of Tricks - * Fixing alignment shifts for neutral good and similar alignments - * Toggle allowing you to Prevent alignment shifts + * Fixing alignment shifts for neutral good and similar alignments + * Toggle allowing you to Prevent alignment shifts * Added per character alignment locking in Party Editor > Char > Stats * Books and other items that have info triggers are flagged as notable and show a circle around it * Changed coloring for notable items to be a distinct lime green * (ArcaneTrixter) Added mythic spellbooks to the new spellbook management section of Party Editor. -* (ArcaneTrixter) Added 'Ignore Alignment Requirement for Abilities' to Cheats menu for those struggling paladins and others. -* Note:  Freemantr80  mentioned in the nexus forums - Regarding romance, I've played twice now with 3 romances to the end. All you need to do is change the FLAG romance count value to 1 before coming back from abyss, and you won't get harassed by your lovers over having multiple partners.)  We are planning on better romance support including proper polyamory and same sex but are working on some needed infrastructure improvements to provide this and more +* (ArcaneTrixter) Added 'Ignore Alignment Requirement for Abilities' to Cheats menu for those struggling paladins and + others. +* Note:Freemantr80 mentioned in the nexus forums - Regarding romance, I've played twice now with 3 romances to the end. + All you need to do is change the FLAG romance count value to 1 before coming back from abyss, and you won't get + harassed by your lovers over having multiple partners.)We are planning on better romance support including proper + polyamory and same sex but are working on some needed infrastructure improvements to provide this and more ### Ver 1.3.8 * Loot Colors and Filters - * ToyBox gives WoTR a Diablo 2/Borderlands style loot rarity and coloring system including - * Checkbox to have the game color your loot according to rarity in inventory, containers, etc. + * ToyBox gives WoTR a Diablo 2/Borderlands style loot rarity and coloring system including + * Checkbox to have the game color your loot according to rarity in inventory, containers, etc. * "Identify All" added to Bag of Tricks > Common * Added quality of life tweak to let you shift click to use items in your inventory * Split Tweaks into Quality of Life and Cheats to make it less huge * New categories for collating items by rarity, cost, enchant value * Search 'n Pick now defaults to searching descriptions. Untick the checkbox to disable this -* Renamed the tweak 'Enable multiple romance (experimental)' to 'Disable Romance IsLocked Flag (experimental)' which is more accurate because we don't know what this does but it doesn't unlock multiple romances. That is under investigation. +* Renamed the tweak 'Enable multiple romance (experimental)' to 'Disable Romance IsLocked Flag (experimental)' which is + more accurate because we don't know what this does but it doesn't unlock multiple romances. That is under + investigation. * (ArcaneTrixter) Added support for Legendary Heroes throughout Toybox, made fake legendary flag per character. * (ArcaneTrixter) Added toggles for immunity to negative levels and ability drain. -* (ArcaneTrixter) Revised + added functionality to spellbook tab in party editor. Can now add/remove from specific spellbooks, and can select spells from any spellbook based on toggle. -* (Delth) Explicitly removed certain negative buffs (Prone, Fatigued) that the game does not mark as harmful from buff duration multiplier +* (ArcaneTrixter) Revised + added functionality to spellbook tab in party editor. Can now add/remove from specific + spellbooks, and can select spells from any spellbook based on toggle. +* (Delth) Explicitly removed certain negative buffs (Prone, Fatigued) that the game does not mark as harmful from buff + duration multiplier * (Truinto) Added tweak: Auto Start Rage When Entering Combat * (Truinto) Added tweak: Mass Loot Shows Everything When Leaving Map * (Truinto) Added tweak: Equipment No Weight @@ -792,14 +1521,17 @@ WARNING: this tool can both miraculously fix your broken progression or it can b ### Ver 1.3.7 -* You can now add key binds to cheat buttons like "Rest All", "Full Bufs", "Reroll Perception", etc  -* HotKeys now recognize shift, alt, ctrl, alt and command key combos +* You can now add key binds to cheat buttons like "Rest All", "Full Bufs", "Reroll Perception", etc* HotKeys now + recognize shift, + alt, ctrl, alt and + command key combos * Teleport Party Key now works on Global or City Map * Search 'n Pick - You can now Unstart Etudes which can help fix broken progression * Added search 'n pick categories for Dialog, Cues and Answers * Movement speed multiplier now affects when you interact with a map object or npc * (ArcaneTrixter) Added option to use legendary hero leveling as non-legends. -* (ArcaneTrixter) Added a number of things around caster levels and spellbooks, including a +1/-1 caster level for any spellbook - Experimental +* (ArcaneTrixter) Added a number of things around caster levels and spellbooks, including a +1/-1 caster level for any + spellbook - Experimental ### Ver 1.3.6 @@ -810,7 +1542,8 @@ WARNING: this tool can both miraculously fix your broken progression or it can b * Toggling search descriptions now updates search results * No friendly fire toggle should work better with pets -* (ArcaneTrixter, Narria) Allies should no longer get piles of (sometimes contradictory) starting feats when initially recruited (Looking at you, Nenio!). +* (ArcaneTrixter, Narria) Allies should no longer get piles of (sometimes contradictory) starting feats when initially + recruited (Looking at you, Nenio!). * (ArcaneTrixter, Narria) Party and pets should once again be affected by feat multiplier in town. * (ArcaneTrixter) Added requested flag to allow Magus Spell Combat even in situations where their hands are full. @@ -829,51 +1562,65 @@ WARNING: this tool can both miraculously fix your broken progression or it can b * Increased MaxFov to 5 * Toggle to not charge campaign time when changing characters * (ArcaneTrixter & Narria) Added button to show the group editor anywhere -* (ArcaneTrixter) Added an experimental "Large Player Armies" toggle to the Crusade tab, which will enable players to have up to 14 units in their army. This might have unintended side effects if reloading a game with large armies without this setting enabled. +* (ArcaneTrixter) Added an experimental "Large Player Armies" toggle to the Crusade tab, which will enable players to + have up to 14 units in their army. This might have unintended side effects if reloading a game with large armies + without this setting enabled. * (ArcaneTrixter) Added Kineticist class cheats to gather power with hands full and to allow additional burn reduction. * (ArcaneTrixter) Enabled initial parameterized feat support. * (ArcaneTrixter) Added a toggle for teleport keys being active in the Tweaks section of Bag of Tricks. * (Truinto) Witch/Shaman: Cackling/Shanting Extends Hexes By 10 min (out of combat) -* (Truinto) Allow Simultaneous Activatable Abilities  (like judgements) +* (Truinto) Allow Simultaneous Activatable Abilities(like judgements) ### Ver 1.3.2 + * Enemy armies no longer speed up with movement speed multiplier. Only your armies become speedsters ^_^ -* Initial teleport to cursor for party and main character support.  Use comma ',' to teleport the party and '.' to teleport the main char +* Initial teleport to cursor for party and main character support. Use comma ',' to teleport the party and '.' to + teleport the main char * Move party together is now disabled for turn based combat (it broke it before) * (ArcaneTrixter)Moved Crusade stuff to a new Crusade tab (more like army editing coming soon) ### Ver 1.3.1.2 -* Fixed crash on loading into a game that can sometimes happen due to multiclass config being set to a party member higher than the number available in the game you are loading. +* Fixed crash on loading into a game that can sometimes happen due to multiclass config being set to a party member + higher than the number available in the game you are loading. ### Ver 1.3.1.1 -* Fixed slow movement speed when movement modifiers are off or when move as one is turned on with no change to speed multiplier +* Fixed slow movement speed when movement modifiers are off or when move as one is turned on with no change to speed + multiplier ### Ver 1.3.1 -* You can now view, lock, unlock, increment and decrement UnlockableFlags in Search 'n Pick - be careful as there are 1400+ of these and we don't know what most of them do. +* You can now view, lock, unlock, increment and decrement UnlockableFlags in Search 'n Pick - be careful as there are + 1400+ of these and we don't know what most of them do. * (ArcaneTrixter) Added sliders for increased recruitment amounts and modifiable recruitment costs * (ArcaneTrixter) Added max dex and arcane spell failure toggles requested in issue #106 -* (ArcaneTrixter) Added a multiplier ('After Army Battle Raise Multiplier') for the number of units raised by Lich's Necromancy and similar effects. This code only gets triggered when players win, so shouldn't have any additional side effects. -* (ArcaneTrixter) This increases the speed at which generals level up and the amount of units spawned by features like Necromancy after battles. I pegged it to the existing experience multiplier to be consistent, but it could be its own multiplier if others prefer. +* (ArcaneTrixter) Added a multiplier ('After Army Battle Raise Multiplier') for the number of units raised by Lich's + Necromancy and similar effects. This code only gets triggered when players win, so shouldn't have any additional side + effects. +* (ArcaneTrixter) This increases the speed at which generals level up and the amount of units spawned by features like + Necromancy after battles. I pegged it to the existing experience multiplier to be consistent, but it could be its own + multiplier if others prefer. ### Ver 1.3.0 + * Multiple Classes On Level Up (Gestalting) is live. * This is a complex new feature so it is still marked experimental - * Note that configuration such as which classes you will multiclass on level up has been reset.  - * Any past results of multiclassing will remain intact however. + * Note that configuration such as which classes you will multiclass on level up has been reset.* Any past results of + multiclassing will + remain intact + however. * Cleaned up Multiclass UI for release * Archetypes now work in char gen and fixed other bugs with multiclass -* New tweak in Level up to let you hit next on any feature selector that runs out of feats.  - * This is activated automatically if feat selection multiplier is greater than 1 +* New tweak in Level up to let you hit next on any feature selector that runs out of feats. + * This is activated automatically if feat selection multiplier is greater than 1 * Additional tweak to let you skip any feat selection you wish * Improved run speed multiplier to work when moving and now when looting chests * Fixed 'Whole Team Moves At Same Speed' to only happen when it is ticked. * Moved 'Whole Team Moves At Same Speed' tick box to be next to the run speed multiplier slider * Added Time Scale Multiplier which affects the whole game -* Characters you add to the party via party editor now are fully controllable and viewable (can't add non companions permanently to party yet but working on it)  -* Made adding characters to party cease to be fighting and aggressive +* Characters you add to the party via party editor now are fully controllable and viewable (can't add non companions + permanently to party yet but working on it)* Made adding characters to party cease to be fighting and aggressive * Blueprints now load on the mac so you can now fully use the mod ### Ver 1.2.13 @@ -881,7 +1628,8 @@ WARNING: this tool can both miraculously fix your broken progression or it can b * Added sliders for Crusade Morale, Max Morale and Min Morale (more improvements to crusade are on their way) * Tweak to refill belt consumables from your inventory * Toggle in Search 'n Pick to let you search descriptions -* Reduced log spam which can fill up Player.log which can grow huge so you may want to quit and delete the file from time to time.  It lives in $User\AppData\LocalLow\Owlcat Games\Pathfinder Wrath of the Righteous\ +* Reduced log spam which can fill up Player.log which can grow huge so you may want to quit and delete the file from + time to time. It lives in $User\AppData\LocalLow\Owlcat Games\Pathfinder Wrath of the Righteous\ ### Ver 1.2.12 @@ -890,13 +1638,15 @@ WARNING: this tool can both miraculously fix your broken progression or it can b * Pets can now multiclass if you turn on ignore class and feat restrictions so make that bully thug or barbarian wardog! * (Fire) Tweak to block tutorials if you have them turned off in settings * (Delth) Added flag to possibly enable multiple romances (experimental and untested, volunteers welcome) -* (Delth) Spiders Begone ported from Bag of Tricks! Spider looks changed to wolves, spider swarm looks to rat swarms.  +* (Delth) Spiders Begone ported from Bag of Tricks! Spider looks changed to wolves, spider swarm looks to rat swarms. ### Ver 1.2.11 -* Unified gestalt flag in party editor with gestalt config so if you level up with a chosen set of multiclasses it will mark them as gestalt.  If you mark a class as gestalt it will get added to the multiclass set. +* Unified gestalt flag in party editor with gestalt config so if you level up with a chosen set of multiclasses it will + mark them as gestalt. If you mark a class as gestalt it will get added to the multiclass set. * Companions can now multiclass -* Fixed calculation error in multiclass skill points - note if you choose a gestalt class to level up as primary then it treats it as if you did not gain a character level +* Fixed calculation error in multiclass skill points - note if you choose a gestalt class to level up as primary then it + treats it as if you did not gain a character level * Your dear pets can once again get more feats with feat selection multiplier * Fixed gestalt level up rules for hp/bab/saves/skills and crasher affecting respec mods * Added a URL describing gestalt classes in Level Up & Multiclass section @@ -905,23 +1655,25 @@ WARNING: this tool can both miraculously fix your broken progression or it can b * (Delth) "All hits critical" was in fact "All attacks hit and are critical" - split into two separate options ### Ver 1.2.10 + * Added feature to exclude classes in character level calculation, which helps gestalt class tinkering * Search 'n Pick - Update match count when you change collation category - * Fixed Performance issue caused by showing combat challenge rating in party editor so removed it for now - * Update match count when you change collation category + * Fixed Performance issue caused by showing combat challenge rating in party editor so removed it for now + * Update match count when you change collation category * Raised max caster level to 20 for spellbook calculations (thanks Delth) -* Made buff length multiplier not apply to harmful buffs (thanks Delth) +* Made buff length multiplier not apply to harmful buffs (thanks Delth) ### Ver 1.2.9 -* Renamed 'Cheap Tricks' Tab to 'Bag of Tricks' in honor of m0nster and the great mod that helped make ToyBox what it is today +* Renamed 'Cheap Tricks' Tab to 'Bag of Tricks' in honor of m0nster and the great mod that helped make ToyBox what it is + today * Fixed bug affecting turn based commands like Studied Target or Gather Power ### Ver 1.2.8 * Fixed bug that would spawn enemies more powerful than they should -* Tweak to allow you to control summons,  -* Adjust summon duration and modify level for 2 different unit factions such as your party and enemies +* Tweak to allow you to control summons,* Adjust summon duration and modify level for 2 different unit factions such as + your party and enemies * Added ability to view (not modify yet) Archetypes and Parametrized Features to Search 'n Pick * Added party stats and encounterCR (when in a fight) to party editor @@ -932,7 +1684,8 @@ WARNING: this tool can both miraculously fix your broken progression or it can b ### Ver 1.2.6 -* Added ability to see and set your experience to match your current character level (See Party Editor > Character > Classes) +* Added ability to see and set your experience to match your current character level (See Party Editor > Character > + Classes) * Added support for Roll Only 20s out of combat * Made scribable scrolls show up in containers or corpses in addition to inventory and vendors * Got rid of html tags from descriptions @@ -941,9 +1694,11 @@ WARNING: this tool can both miraculously fix your broken progression or it can b * Added tweak to highlight scrolls that can be copied into your spell book (or recipes) * Improved stability of fov (zoom multiplier) when you disable and renable the mod multiple times -* Added experimental slider to increase max FoV multiplier.  This can cause perf issues in some maps +* Added experimental slider to increase max FoV multiplier. This can cause perf issues in some maps * Fixed out of range bug in dialog preview -* Released experimental version of 1.2.5 with multiple classes per level turned on (please download the experimental file to try this out and file bugs here: [https://github.com/cabarius/ToyBox/issues](https://github.com/cabarius/ToyBox/issues)) +* Released experimental version of 1.2.5 with multiple classes per level turned on (please download the experimental + file to try this out and file bugs + here: [https://github.com/cabarius/ToyBox/issues](https://github.com/cabarius/ToyBox/issues)) ### Ver 1.2.4 @@ -965,6 +1720,7 @@ WARNING: this tool can both miraculously fix your broken progression or it can b * Fixed disappearing doll in inventory screen and map refresh bugs due to NoFogOfWar flag ### Ver 1.2.0 + * Ported to Beta 2 * UI improvements * More beautiful check-boxes and disclosure controls @@ -984,6 +1740,7 @@ WARNING: this tool can both miraculously fix your broken progression or it can b * Start and complete quests, quest objectives (Experimental) ### Ver 1.1.11 + * Getting ready for 1.2.0 but doing a pre-release as 1.1.11 * Significant Improvements to scrolling speed and other aspects of performance in Search 'n Pick and Party Editor * Moved Level Up related tweaks to a new tab called 'Level Up' @@ -996,12 +1753,13 @@ WARNING: this tool can both miraculously fix your broken progression or it can b * You can now add BlueprintActivatableAbility * Added category for browsing Wrath's in memory cache * Experimental Multiclass on level up preview (experimental binary only) - * This is very limited right now as it only gives you the classes - * It does not let you choose feats, abilities, spells - * If you want to check it out please download the side experimental version from nexus + * This is very limited right now as it only gives you the classes + * It does not let you choose feats, abilities, spells + * If you want to check it out please download the side experimental version from nexus * Various other small improvements ### Ver 1.1.10 + * Tweak for autoloading last save on app launch * Improved layout for mod window sizes < 2000 and < 1600 * Reverted mouse wheel patch that broke horizontal scrolling @@ -1019,11 +1777,13 @@ WARNING: this tool can both miraculously fix your broken progression or it can b * Mouse Wheel scrolling should work reliably now ### Ver 1.1.8 + * Added Dice Roll Tweaks to Cheap Tricks * Added friendly (Non Hostile) as a category to which you can apply tweaks * Fixed bug where mercenaries where getting main character build points instead of mercenary ### Ver 1.1.7 + * Added Attack of Opportunity Disable to Cheap Tricks * Can now choose Alignment in Party Editor > Stats * Can now choose Size in Party Editor > Stats @@ -1033,17 +1793,23 @@ WARNING: this tool can both miraculously fix your broken progression or it can b * Fixed bug with movement speed multiplier where it made the main character go slow with moveAsOne off ### Ver 1.1.6 -* Ignore Class And Feat Restrictions now allows you to choose any mythic class each time you level up starting from level 1 + +* Ignore Class And Feat Restrictions now allows you to choose any mythic class each time you level up starting from + level 1 ### Ver 1.1.5 -* Fixed bug with meta magic where if Free Meta Magic is on it doubles the meta-magic and if off it hides it (I fixed this before but it was lost somewhere along the way) +* Fixed bug with meta magic where if Free Meta Magic is on it doubles the meta-magic and if off it hides it(I fixed this + before but it was lost somewhere along the way) ### Ver 1.1.4 -* ` (was Ver 1.1.3 but had the wrong version in Info.json so 1.1.3 is now a lost version)` + +* ` (was Ver 1.1.3 but had the wrong version in Info.json so 1.1.3 is now a lost version)` * Party Editor > Classes - * Can now adjust character level, mythic level, and induvial class level without triggering level up  - * Reset character level to current exp + * Can now adjust character level, mythic level, and induvial class level without triggering level up* Reset + character + level to + current exp * Added text field for direct stat editing * Cheap Tricks * Gold and party experience increase @@ -1053,17 +1819,30 @@ WARNING: this tool can both miraculously fix your broken progression or it can b * Added a fun teleport feature to blueprint browser (Entry Points) * Added more filter categories * Misc - * Added divider lines (optional) to make it easier to look through lists of blueprints, features, abilities, spells, etc + * Added divider lines (optional) to make it easier to look through lists of blueprints, features, abilities, spells, + etc * Improved look of checkboxes (got rid of distracting red x and made them grey) * Improved formatting of spellbook names and avoid blank names where possible * Bug Fixes - * Fixed issue during level up where the game might demand you pick a feature (feat, boon, etc) from a list where nothing was available.  This makes feat multiplier work in many cases where it didn't even in bag of tricks.   - * Fixed issue where movement speed multiplier was not being applied correctly + * Fixed issue during level up where the game might demand you pick a feature (feat, boon, etc)from a list where + nothing was available. This makes feat multiplier work in many cases where it didn't even in bag of tricks.* Fixed + issue + where + movement + speed + multiplier + was + not + being + applied + correctly * Fixed bug that was preventing bard/azata build from spending resource points * Fixed issue that prevented adding 9th level spells ### Ver 1.1.2 -* Fixed bug where you could only toggle to show stats/facts/abilities/etc for main character even though you selected a toggle on another + +* Fixed bug where you could only toggle to show stats/facts/abilities/etc for main character even though you selected a + toggle on another * Fixed bug that broke abilities and cooldowns during combat * Added bonus feature 'Unlimited Actions During Turn' for Turn Based Combat * Tweaks to party editor UI and labels @@ -1074,6 +1853,7 @@ WARNING: this tool can both miraculously fix your broken progression or it can b * Added Nearby Units and show distance to other units ### Ver 1.1.0 + * Cheap Tricks * Ported ~3 dozen flags, multipliers, etc. patches from Bag of Tricks (see below for full list) * Search 'n Pick @@ -1085,64 +1865,67 @@ WARNING: this tool can both miraculously fix your broken progression or it can b * Toggles to show classes, buffs, abilities and spellbook and can edit all but classes * Toggles in party editor now close other toggles * Show counts of some toggles like classes and spells - ** Can see Friendlies, Enemies and All Units! + ** Can see Friendlies, Enemies and All Units! * Performance - Switched blueprint loading to async (no more freezing of app when you do first search) - Loading indicator on startup - Massively improved performance on Party Editor and Search 'n Pick lists + Switched blueprint loading to async (no more freezing of app when you do first search) + Loading indicator on startup + Massively improved performance on Party Editor and Search 'n Pick lists * Cleaned up UI and bug fixes * Ported BoT Features - * This is experimental so please report bugs [https://github.com/cabarius/ToyBox/issues](https://github.com/cabarius/ToyBox/issues) + * This is experimental so please report + bugs[https://github.com/cabarius/ToyBox/issues](https://github.com/cabarius/ToyBox/issues) * Flags - * Whole Team Moves Same Speed - * Instant Cooldown - * Spontaneous Caster Scroll Copy - * Disable Equipment Restrictions - * Disable Dialog Restrictions - * Infinite Charges On Items - * No Friendly Fire On AOEs - * Free Meta-Magic - * No Material Components - * Instant Rest After Combat + * Whole Team Moves Same Speed + * Instant Cooldown + * Spontaneous Caster Scroll Copy + * Disable Equipment Restrictions + * Disable Dialog Restrictions + * Infinite Charges On Items + * No Friendly Fire On AOEs + * Free Meta-Magic + * No Material Components + * Instant Rest After Combat * Multipliers - * Experience - * Money Earned - * Sell Price - * Encumbrance - * Spells Per Day - * Movement Speed - * Travel Speed - * Companion Cost - * Enemy HP Multiplier - * Buff Duration + * Experience + * Money Earned + * Sell Price + * Encumbrance + * Spells Per Day + * Movement Speed + * Travel Speed + * Companion Cost + * Enemy HP Multiplier + * Buff Duration * Level Up - * Feats Multiplier - * Always Able To Level Up - * Add Full Hit Die Value - * Ignore Class And Feat Restrictions - * Ignore Prerequisites When Choosing A Feat - * Ignore Caster Type And Spell Level Restrictions - * Ignore Forbidden Archetypes - * Ignore Required Stat Values - * Ignore Alignment When Choosing A Class - * Skip Spell Selection + * Feats Multiplier + * Always Able To Level Up + * Add Full Hit Die Value + * Ignore Class And Feat Restrictions + * Ignore Prerequisites When Choosing A Feat + * Ignore Caster Type And Spell Level Restrictions + * Ignore Forbidden Archetypes + * Ignore Required Stat Values + * Ignore Alignment When Choosing A Class + * Skip Spell Selection ### Ver 1.0.6 -* Major Overhaul of the UI.  Each major area of features is in a separate tab (Cheap Tricks, Party Editor, Blueprint Search, Quest Resolution) +* Major Overhaul of the UI. Each major area of features is in a separate tab (Cheap Tricks, Party Editor, Blueprint + Search, Quest Resolution) * Added experimental Respec Feature * fixed bug where the arrows were backwards in add/remove rank and also made sure it couldn't take you below 1 -* various stability improvements  +* various stability improvements ### Ver 1.0.5 -* Ported Dialog Preview from Kingdom Resolution Mod. Now you can get a preview of results from Dialog, Alignment Restricted Dialog, Events and Random Encounters +* Ported Dialog Preview from Kingdom Resolution Mod. Now you can get a preview of results from Dialog, Alignment + Restricted Dialog, Events and Random Encounters ### Ver 1.0.4 -* Search Character Picker - Can now add features to a specifically chosen party member +* Search Character Picker - Can now add features to a specifically chosen party member * Quest Resolution - browse and modify progress in your quests (great for dealing with bugged quests) * Improved layout at lower resolutions (not perfect yet) * Improved search performance @@ -1150,57 +1933,70 @@ WARNING: this tool can both miraculously fix your broken progression or it can b ### Ver 1.0.3 -* party picker now lets you browse Party, Party & Pets, All Characters, Active Companions. Remote Companions, Mercs, Pets +* party picker now lets you browse Party, Party & Pets, All Characters, Active Companions. Remote Companions, Mercs, + Pets * Add/Remove party members * Teleport Party To You * Run Perception Check -* ToggleTabHighlightsMode is ported from Spacehamster's awesome Kingdom Resolution Mod for Kingmaker https://www.nexusmods.com/pathfinderkingmaker/mods/36 based on code originally by fireundubh +* ToggleTabHighlightsMode is ported from Spacehamster's awesome Kingdom Resolution Mod for + Kingmaker https://www.nexusmods.com/pathfinderkingmaker/mods/36 based on code originally by fireundubh + ### Ver 1.0.0 + * Search and entire blueprint catalog for feats, features, items and more * Browse party members, level up, mythic level up, modify stats -* Browse and  remove features by party member (back up before using) +* Browse and remove features by party member (back up before using) * Various cheats based on console commands -To use search type a string into the search field at the bottom and hit enter or click the search button.  At first you have to wait 10 seconds or so for the blueprints to load but after that, it is fast.  You choose a category with the provided toolbar. +To use search type a string into the search field at the bottom and hit enter or click the search button. At first you +have to wait 10 seconds or so for the blueprints to load but after that, it is fast. You choose a category with the +provided toolbar. Search results will offer you actions such as adding a feature or item. ### Install & Use 1. Install the [Unity Mod Manager](https://www.nexusmods.com/site/mods/21/?tab=files). -1. Install the mod using the Unity Mod Manager or extract the archive to your game's mod folder (e.g. '\Steam\steamapps\common\Pathfinder Second Adventure\Mods'). +1. Install the mod using the Unity Mod Manager or extract the archive to your game's mod folder (e.g. ' + \Steam\steamapps\common\Pathfinder Second Adventure\Mods'). 1. Start the game and load a save or start a new save (the mod's functions can't accessed from the main menu). 1. Open the Unity Mod Manager by pressing CTRL + F10. 1. Adjust the settings in the mod's menu - -Please feel free to make other enhancement requests [here](https://github.com/cabarius/ToyBox/issues).  -Please tap new issue and mark it as an `enhancement`. -Please also file any bugs you find [here](https://github.com/cabarius/ToyBox/issues).  + +Please feel free to make other enhancement requests[here](https://github.com/cabarius/ToyBox/issues). Please tap new +issue and mark it as an `enhancement`. +Please also file any bugs you find[here](https://github.com/cabarius/ToyBox/issues). ### Acknowledgments -* **ArcaneTrixter** for many awesome improvements and bug fixes -* **fire** & **m0nster** for lots of awesome code from bag of tricks -* [Truinto](https://github.com/cabarius/ToyBox/issues?q=is%3Apr+author%3ATruinto), **Delth, Aphelion, fire** for great contributions to the ToyBox project +* **ArcaneTrixter**for many awesome improvements and bug fixes +* **fire**& **m0nster**for lots of awesome code from bag of tricks +* [Truinto](https://github.com/cabarius/ToyBox/issues?q=is%3Apr+author%3ATruinto),**Delth, Aphelion, fire** for great + contributions to the ToyBox project * **Owlcat Games** - for making fun and amazing games * **Paizo** - for carrying the D20 3.5 torch * Pathfinder Wrath of The Righteous Discord channel members - * **@Spacehamster** - awesome tutorials and taking time to teach me modding WoTR, and letting me port stuff from Kingdom Resolution Mod - * **@m0nster** - for giving me permission to port stuff from Back of Tricks - * **@Vek17, @Bubbles, @Balkoth, @swizzlewizzle** and the rest of our great Discord modding community - help, moral support and just general awesomeness - * **@m0nster, @Hsinyu, @fireundubh** for Bag of Tricks which inspired me to get into modding WoTR because I missed this mod so much -* PS: Learn to mod Kingmaker Games at Spacehamster's [Modding Wiki](https://github.com/spacehamster/OwlcatModdingWiki/wiki/Beginner-Guide ) + * **@Spacehamster** - awesome tutorials and taking time to teach me modding WoTR, and letting me port stuff from + Kingdom Resolution Mod + * **@m0nster** - for giving me permission to port stuff from Back of Tricks + * **@Vek17, @Bubbles, @Balkoth, @swizzlewizzle** and the rest of our great Discord modding community - help, moral + support and just general awesomeness + * **@m0nster, @Hsinyu, @fireundubh** for Bag of Tricks which inspired me to get into modding WoTR because I missed + this mod so much +* PS: Learn to mod Kingmaker Games at + Spacehamster's [Modding Wiki](https://github.com/spacehamster/OwlcatModdingWiki/wiki/Beginner-Guide ) * Come visit the authors Narria et al on the [WoTR Discord](https://discord.gg/wotr) ### Source Code**: [https://github.com/cabarius/ToyBox](https://github.com/cabarius/ToyBox) ### Development Setup + 1. Install ToyBox mod into your game via Unity Mod Manager 2. Clone the git repo 3. Locate the install folder of Pathfinder Wrath of the Righteous 4. Go to System Properties > Environment Variables and add `WrathPath` with a value that looks like this: - `WrathPath C:\Program Files (x86)\Steam\steamapps\common\Pathfinder Second Adventure` + `WrathPath C:\Program Files (x86)\Steam\steamapps\common\Pathfinder Second Adventure` 5. First time and when the game updates make sure you clean the solution to trigger the publicize step 6. build the solution debug and it will automatically build and install into the mod folder in the game @@ -1210,8 +2006,15 @@ Please also file any bugs you find [here](https://github.com/cabarius/ToyBox/is Copyright <2021> Narria (github user Cabarius) -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/ToyBox/Repository.json b/ToyBox/Repository.json deleted file mode 100644 index c0889b5bb..000000000 --- a/ToyBox/Repository.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Releases": [ - { - "Id": "ToyBox", - "Version": "1.5.1" - } - ] -} \ No newline at end of file diff --git a/ToyBox/ToyBox.csproj b/ToyBox/ToyBox.csproj deleted file mode 100644 index f09807a1a..000000000 --- a/ToyBox/ToyBox.csproj +++ /dev/null @@ -1,324 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> - <PropertyGroup> - <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> - <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> - <ProjectGuid>{22DCB4E1-D979-4EA9-913A-4EE1634B4DED}</ProjectGuid> - <OutputType>Library</OutputType> - <AppDesignerFolder>Properties</AppDesignerFolder> - <RootNamespace>ToyBox</RootNamespace> - <AssemblyName>ToyBox</AssemblyName> - <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> - <FileAlignment>512</FileAlignment> - <Deterministic>false</Deterministic> - <LangVersion>9</LangVersion> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> - <DebugSymbols>true</DebugSymbols> - <DebugType>portable</DebugType> - <Optimize>false</Optimize> - <OutputPath>bin\Debug\0ToyBox0\</OutputPath> - <DefineConstants>DEBUG;TRACE</DefineConstants> - <ErrorReport>none</ErrorReport> - <WarningLevel>4</WarningLevel> - <AllowUnsafeBlocks>true</AllowUnsafeBlocks> - <PlatformTarget>x86</PlatformTarget> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <DebugType>pdbonly</DebugType> - <Optimize>true</Optimize> - <OutputPath>bin\Release\0ToyBox0\</OutputPath> - <DefineConstants>TRACE</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - <AllowUnsafeBlocks>true</AllowUnsafeBlocks> - </PropertyGroup> - <ItemGroup> - <PubliciseInputAssemblies Include="$(WrathPath)\Wrath_Data\Managed\Assembly-CSharp.dll" /> - <PubliciseInputAssemblies Include="$(WrathPath)\Wrath_Data\Managed\Owlcat.Runtime.UI.dll" /> - <PubliciseInputAssemblies Include="$(WrathPath)\Wrath_Data\Managed\UnityModManager\UnityModManager.dll" /> - </ItemGroup> - <Target Name="Publicise" AfterTargets="BeforeBuild;Clean" Inputs="@(PubliciseInputAssemblies)" Outputs="$(SolutionDir)lib\%(PubliciseInputAssemblies.FileName)_public.dll;$(SolutionDir)lib\%(PubliciseInputAssemblies.FileName)_public.hash"> - <Publicise InputAssemblies="@(PubliciseInputAssemblies)" OutputPath="$(SolutionDir)lib/" PubliciseCompilerGenerated="true" /> - </Target> - <ItemGroup> - <PackageReference Include="Aze.Publicise.MSBuild.Task" Version="1.0.0" /> - <Reference Include="0Harmony"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\UnityModManager\0Harmony.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="Assembly-CSharp"> - <HintPath>$(SolutionDir)lib\Assembly-CSharp_public.dll</HintPath> - <Private>False</Private> - </Reference> <Reference Include="Assembly-CSharp-firstpass"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="DOTween"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\DOTween.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="Microsoft.Build.Framework" /> - <Reference Include="Microsoft.Build.Utilities.v4.0" /> - <Reference Include="Newtonsoft.Json"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\Newtonsoft.Json.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="Owlcat.Runtime.Core"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\Owlcat.Runtime.Core.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="Owlcat.Runtime.UniRx"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\Owlcat.Runtime.UniRx.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="Owlcat.Runtime.UI_public"> - <HintPath>$(SolutionDir)lib\Owlcat.Runtime.UI_public.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="Owlcat.Runtime.Validation"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\Owlcat.Runtime.Validation.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="Owlcat.Runtime.Visual"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\Owlcat.Runtime.Visual.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="System" /> - <Reference Include="System.Core" /> - <Reference Include="System.Web"> - <Private>False</Private> - </Reference> - <Reference Include="System.Web.Services"> - <Private>False</Private> - </Reference> - <Reference Include="System.Xml.Linq" /> - <Reference Include="System.Data.DataSetExtensions" /> - <Reference Include="Microsoft.CSharp"> - <Private>False</Private> - </Reference> - <Reference Include="System.Data" /> - <Reference Include="System.Net.Http" /> - <Reference Include="System.Xml" /> - <Reference Include="UniRx"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\UniRx.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="Unity.TextMeshPro"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\Unity.TextMeshPro.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="UnityEngine"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\UnityEngine.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="UnityEngine.AssetBundleModule"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\UnityEngine.AssetBundleModule.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="UnityEngine.CoreModule"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\UnityEngine.CoreModule.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="UnityEngine.ImageConversionModule"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\UnityEngine.ImageConversionModule.dll</HintPath> - </Reference> - <Reference Include="UnityEngine.IMGUIModule"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\UnityEngine.IMGUIModule.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="UnityEngine.InputLegacyModule"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\UnityEngine.InputLegacyModule.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="UnityEngine.InputModule"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\UnityEngine.InputModule.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="UnityEngine.Physics2DModule"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\UnityEngine.Physics2DModule.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="UnityEngine.PhysicsModule"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\UnityEngine.PhysicsModule.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="UnityEngine.TextRenderingModule"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\UnityEngine.TextRenderingModule.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="UnityEngine.UI"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\UnityEngine.UI.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="UnityEngine.UIModule"> - <HintPath>$(WrathPath)\Wrath_Data\Managed\UnityEngine.UIModule.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="UnityModManager_public"> - <HintPath>..\lib\UnityModManager_public.dll</HintPath> - <Private>False</Private> - </Reference> - </ItemGroup> - <ItemGroup> - <Compile Include="classes\CustomEnchantmentStackingFix.cs" /> - <Compile Include="classes\Infrastructure\AssetLoader.cs" /> - <Compile Include="classes\Infrastructure\Blueprints\BlueprintExtensionsQuest.cs" /> - <Compile Include="classes\Infrastructure\Borrowed\SmartConsole.cs" /> - <Compile Include="classes\Infrastructure\Blueprints\BlueprintDataSource.cs" /> - <Compile Include="classes\MainUI\EnhancedUI\BulkSell.cs" /> - <Compile Include="classes\MainUI\PartyEditor\HumanFriendlyStats.cs" /> - <Compile Include="classes\Infrastructure\Borrowed\StateReplacer.cs" /> - <Compile Include="classes\Infrastructure\CasterHelpers.cs" /> - <Compile Include="classes\MainUI\EnhancedUI\ItemRarity.cs" /> - <Compile Include="classes\MainUI\EnhancedUI\EnhancedInventory.cs" /> - <Compile Include="classes\MainUI\EnhancedUI\LootHelper.cs" /> - <Compile Include="classes\Infrastructure\SettingsDefaults.cs" /> - <Compile Include="classes\Infrastructure\Teleport.cs" /> - <Compile Include="classes\Infrastructure\UIHelpers.cs" /> - <Compile Include="classes\Infrastructure\Utils.cs" /> - <Compile Include="classes\Infrastructure\WrathExtensions.cs" /> - <Compile Include="classes\Infrastructure\Blueprints\BlueprintExtensions.cs" /> - <Compile Include="classes\MainUI\BraaainzEditor.cs" /> - <Compile Include="classes\MainUI\Browser\BuffExclusionEditor.cs" /> - <Compile Include="classes\MainUI\Browser\Editor.cs" /> - <Compile Include="classes\MainUI\Crusade\ArmiesEditor.cs" /> - <Compile Include="classes\MainUI\Crusade\CrusadeEditor.cs" /> - <Compile Include="classes\MainUI\Crusade\EventEditor.cs" /> - <Compile Include="classes\MainUI\Crusade\SettlementsEditor.cs" /> - <Compile Include="classes\MainUI\EnchantmentEditor.cs" /> - <Compile Include="classes\MainUI\Etudes\EtudeChildrenDrawer.cs" /> - <Compile Include="classes\MainUI\Etudes\EtudeInfo.cs" /> - <Compile Include="classes\MainUI\Etudes\EtudesEditor.cs" /> - <Compile Include="classes\MainUI\Etudes\EtudeTreeModel.cs" /> - <Compile Include="classes\MainUI\Etudes\ReferenceGraph.cs" /> - <Compile Include="classes\MainUI\EnhancedUI\EnhancedInventoryController.cs" /> - <Compile Include="classes\MainUI\EnhancedUI\SelectedCharacterObserver.cs" /> - <Compile Include="classes\MainUI\EnhancedUI\OnAreaLoad.cs" /> - <Compile Include="classes\MainUI\EnhancedUI\RemappableInt.cs" /> - <Compile Include="classes\MainUI\EnhancedUI\SearchBar.cs" /> - <Compile Include="classes\MainUI\EnhancedUI\EnhancedSpellbookController.cs" /> - <Compile Include="classes\Infrastructure\UIWidgetHelpers.cs" /> - <Compile Include="classes\MainUI\PartyEditor\ClassesEditor.cs" /> - <Compile Include="classes\MainUI\PartyEditor\FeaturesTreeEditor.cs" /> - <Compile Include="classes\MainUI\PartyEditor\SpellsEditor.cs" /> - <Compile Include="classes\MainUI\PartyEditor\StatsEditor.cs" /> - <Compile Include="classes\MainUI\EnhancedUI\PhatLoot.cs" /> - <Compile Include="classes\MainUI\MulticlassPicker.cs" /> - <Compile Include="classes\MainUI\LevelUp.cs" /> - <Compile Include="classes\MainUI\Browser\BlueprintAction.cs" /> - <Compile Include="classes\Infrastructure\Borrowed\Accessors.cs" /> - <Compile Include="classes\Infrastructure\Borrowed\PartyUtils.cs" /> - <Compile Include="classes\Infrastructure\UnitEntityDataUtils.cs" /> - <Compile Include="classes\MainUI\Actions.cs" /> - <Compile Include="classes\MainUI\Browser\BlueprintListUI.cs" /> - <Compile Include="classes\Infrastructure\Blueprints\BlueprintLoader.cs" /> - <Compile Include="classes\MainUI\ActionButtons.cs" /> - <Compile Include="classes\MainUI\Browser\FactsEditor.cs" /> - <Compile Include="classes\MainUI\Main.cs" /> - <Compile Include="classes\MainUI\Browser\SearchAndPick.cs" /> - <Compile Include="classes\MainUI\BagOfTricks.cs" /> - <Compile Include="classes\Infrastructure\CharacterPicker.cs" /> - <Compile Include="classes\MainUI\QuestEditor.cs" /> - <Compile Include="classes\MainUI\PartyEditor\PartyEditor.cs" /> - <Compile Include="classes\Infrastructure\RespecHelper.cs" /> - <Compile Include="classes\Models\Settings+BulkSell.cs" /> - <Compile Include="classes\Models\Settings+Multiclass.cs" /> - <Compile Include="classes\Models\Settings+UI.cs" /> - <Compile Include="classes\Models\Settings.cs" /> - <Compile Include="classes\MonkeyPatchin\BagOfPatches\ActionBar.cs" /> - <Compile Include="classes\MonkeyPatchin\BagOfPatches\Alignment.cs" /> - <Compile Include="classes\MonkeyPatchin\BagOfPatches\Camera.cs" /> - <Compile Include="classes\MonkeyPatchin\BagOfPatches\Clipboard+Guids.cs" /> - <Compile Include="classes\MonkeyPatchin\BagOfPatches\Combat\Actions.cs" /> - <Compile Include="classes\MonkeyPatchin\BagOfPatches\Combat\NoFriendlyFire.cs" /> - <Compile Include="classes\MonkeyPatchin\BagOfPatches\Crusade.cs" /> - <Compile Include="classes\MonkeyPatchin\BagOfPatches\Development.cs" /> - <Compile Include="classes\MonkeyPatchin\BagOfPatches\Dialog.cs" /> - <Compile Include="classes\MonkeyPatchin\BagOfPatches\DiceRolls.cs" /> - <Compile Include="classes\MonkeyPatchin\BagOfPatches\LevelUpPatches.cs" /> - <Compile Include="classes\MonkeyPatchin\BagOfPatches\Limits\Infinites.cs" /> - <Compile Include="classes\MonkeyPatchin\BagOfPatches\Limits\Metamagic.cs" /> - <Compile Include="classes\MonkeyPatchin\BagOfPatches\Limits\Unrestricted.cs" /> - <Compile Include="classes\MonkeyPatchin\BagOfPatches\Localization.cs" /> - <Compile Include="classes\MonkeyPatchin\BagOfPatches\Misc.cs" /> - <Compile Include="classes\MonkeyPatchin\BagOfPatches\Movement.cs" /> - <Compile Include="classes\MonkeyPatchin\BagOfPatches\Multipliers.cs" /> - <Compile Include="classes\MonkeyPatchin\BagOfPatches\NewChar.cs" /> - <Compile Include="classes\MonkeyPatchin\BagOfPatches\Pets.cs" /> - <Compile Include="classes\MonkeyPatchin\BagOfPatches\Romance.cs" /> - <Compile Include="classes\MonkeyPatchin\BagOfPatches\Selectors.cs" /> - <Compile Include="classes\MonkeyPatchin\BagOfPatches\Settlement.cs" /> - <Compile Include="classes\MonkeyPatchin\BagOfPatches\Spellbooks.cs" /> - <Compile Include="classes\MonkeyPatchin\BagOfPatches\Summons.cs" /> - <Compile Include="classes\MonkeyPatchin\BagOfPatches\Tweaks.cs" /> - <Compile Include="classes\MonkeyPatchin\BagOfPatches\zToDo.cs" /> - <Compile Include="classes\MonkeyPatchin\Braaainz.cs" /> - <Compile Include="classes\MonkeyPatchin\HighlightObjectToggle.cs" /> - <Compile Include="classes\MonkeyPatchin\EnhancedUI\InventoryStashVM.cs" /> - <Compile Include="classes\MonkeyPatchin\EnhancedUI\ItemsFilter.cs" /> - <Compile Include="classes\MonkeyPatchin\EnhancedUI\ItemsFilterPCView.cs" /> - <Compile Include="classes\MonkeyPatchin\EnhancedUI\ItemsFilterSearchPCView.cs" /> - <Compile Include="classes\MonkeyPatchin\EnhancedUI\LocalMap.cs" /> - <Compile Include="classes\MonkeyPatchin\EnhancedUI\Loot.cs" /> - <Compile Include="classes\MonkeyPatchin\ModUI.cs" /> - <Compile Include="classes\MonkeyPatchin\MoveThroughOthers.cs" /> - <Compile Include="classes\MonkeyPatchin\Multiclass\Archetypes.cs" /> - <Compile Include="classes\MonkeyPatchin\Multiclass\LevelUP+Multiclass.cs" /> - <Compile Include="classes\MonkeyPatchin\Multiclass\MulticlassMod.cs" /> - <Compile Include="classes\MonkeyPatchin\Multiclass\MultipleClasses.cs" /> - <Compile Include="classes\MonkeyPatchin\Multiclass\StatProgression\HP.cs" /> - <Compile Include="classes\MonkeyPatchin\Multiclass\StatProgression\SavesBAB.cs" /> - <Compile Include="classes\MonkeyPatchin\Multiclass\StatProgression\SkillPoint.cs" /> - <Compile Include="classes\MonkeyPatchin\Multiclass\WrathExtensionsMulticlass.cs" /> - <Compile Include="classes\MonkeyPatchin\PartyView.cs" /> - <Compile Include="classes\MonkeyPatchin\PreviewManager.cs" /> - <Compile Include="classes\MonkeyPatchin\PreviewUtilities.cs" /> - <Compile Include="classes\MonkeyPatchin\Vendor\BulkSellLogic.cs" /> - <Compile Include="GlobalSuppressions.cs" /> - <Compile Include="Properties\AssemblyInfo.cs" /> - </ItemGroup> - <ItemGroup> - <EmbeddedResource Include="Config\bindings.json" /> - <None Include="Repository.json"> - <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> - </None> - <None Include="Info.json"> - <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> - </None> - </ItemGroup> - <ItemGroup> - <Compile Include="classes\MainUI\Playground.cs" /> - </ItemGroup> - <ItemGroup> - <None Include="ReadMe.md" /> - <Content Include="Localization\*"> - <CopyToOutputDirectory>Always</CopyToOutputDirectory> - </Content> - <Content Include="zmisc\api.txt" /> - <None Include="zip-hash-sign.ps1" /> - </ItemGroup> - <ItemGroup> - <EmbeddedResource Include="Art\**\*.png" /> - </ItemGroup> - <ItemGroup /> - <Import Project="$(SolutionDir)ModKit\ModKitSrc.projitems" Label="Shared" /> - <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> - <PropertyGroup> - <PostBuildEvent>echo "$(TargetPath)" ">$(WrathPath)\Mods\0$(ProjectName)0\$(ProjectName).dll*" - xcopy /Y "$(TargetPath)" "$(WrathPath)\Mods\0$(ProjectName)0\$(ProjectName).dll*" - xcopy /Y "$(TargetDir)$(TargetName).pdb" "$(WrathPath)\Mods\0$(ProjectName)0\$(ProjectName).pdb*" - xcopy /Y "$(ProjectDir)\Info.json" "$(WrathPath)\Mods\0$(ProjectName)0\Info.json*" - xcopy /Y "$(ProjectDir)\Repository.json" "$(WrathPath)\Mods\0$(ProjectName)0\Repository.json*" - xcopy /E /Y "$(ProjectDir)Localization\" "$(WrathPath)\Mods\0$(ProjectName)0\Localization\" - cd "$(TargetDir)" - powershell.exe -ExecutionPolicy Unrestricted -f "$(ProjectDir)zip-hash-sign.ps1" -</PostBuildEvent> - </PropertyGroup> - <ProjectExtensions> - <VisualStudio> - <UserProperties info_1json__JsonSchema="https://json.schemastore.org/global.json" config_4bindings_1json__JsonSchema="https://json.schemastore.org/global.json" /> - </VisualStudio> - </ProjectExtensions> -</Project> \ No newline at end of file diff --git a/ToyBox/ToyBoxRT.csproj b/ToyBox/ToyBoxRT.csproj new file mode 100644 index 000000000..74a8c827a --- /dev/null +++ b/ToyBox/ToyBoxRT.csproj @@ -0,0 +1,97 @@ +<Project Sdk="Microsoft.NET.Sdk"> + <Import Project="$(SolutionDir)GamePath.props" Condition="Exists('$(SolutionDir)GamePath.props')" /> + <PropertyGroup> + <TargetFramework>net481</TargetFramework> + <AssemblyName>ToyBox</AssemblyName> + <Description>ToyBox</Description> + <AppDesignerFolder>Properties</AppDesignerFolder> + <OutputType>Library</OutputType> + <Version>1.7.31</Version> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + <LangVersion>latest</LangVersion> + <Nullable>annotations</Nullable> + <RootNamespace>ToyBox</RootNamespace> + <AnalysisLevel>preview</AnalysisLevel> + <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> + <RestoreAdditionalProjectSources>https://api.nuget.org/v3/index.json</RestoreAdditionalProjectSources> + <RogueTraderData>$(LocalAppData)Low\Owlcat Games\Warhammer 40000 Rogue Trader</RogueTraderData> + </PropertyGroup> + <ItemGroup> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\netstandard*.dll" Private="false" /> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\Unity*.dll" Private="false" /> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\UnityEngine.IMGUIModule.dll*" Publicize="true" Private="false" /> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\Kingmaker*.dll" Private="false" /> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\Utility*.dll" Private="false" /> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\Core*.dll" Private="false" /> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\Owlcat*.dll" Publicize="true" Private="false" /> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\RogueTrader*.dll" Publicize="true" Private="false" /> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\Code*.dll" Publicize="true" Private="false" /> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\LocalizationShared.dll*" Publicize="true" Private="false" /> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\UnityEngine.CoreModule.dll*" Publicize="true" Private="false" /> + <Reference Include="$(RogueTraderData)\UnityModManager\UnityModManager.dll*" Publicize="true" Private="false" /> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\0Harmony.dll*" Private="false" /> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\Newtonsoft.Json.dll*" Private="false" /> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\AK.Wwise.Unity.*" Private="false" /> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\AstarPathfindingProject.dll*" Private="false" /> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\BuildMode.dll*" Private="false" /> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\BundlesBaseTypes.dll*" Private="false" /> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\Cinemachine.dll*" Private="false" /> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\Assembly-CSharp-firstpass.dll*" Private="false" /> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\ContextData.dll*" Private="false" /> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\CountingGuard.dll*" Private="false" /> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\DOTween*" Private="false" /> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\StateHasher.dll*" Private="false" /> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\MemoryPack.dll*" Private="false" /> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\PFlog.dll*" Private="false" /> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\StatefulRandom.dll*" Private="false" /> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\UberLogger.dll*" Private="false" /> + <Reference Include="$(RogueTraderInstallDir)\WH40KRT_Data\Managed\UniRx.dll*" Private="false" /> + <Reference Include="System.IO.Compression" Private="false" /> + </ItemGroup> + <ItemGroup> + <None Include="ReadMe.md" /> + <Content Include="Localization\*.*"> + <Link>Localization\%(Filename)%(Extension)</Link> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </Content> + <None Include="ModDetails\Info.json" CopyToOutputDirectory="PreserveNewest" Link="%(Filename)%(Extension)" /> + <None Include="ModDetails\OwlcatModificationManifest.json" CopyToOutputDirectory="PreserveNewest" Link="%(Filename)%(Extension)" /> + </ItemGroup> + <Target Name="Deploy" AfterTargets="Build"> + <ItemGroup> + <FilesToHash Include="$(TargetDir)$(AssemblyName).dll" /> + </ItemGroup> + <GetFileHash Files="@(FilesToHash)" Algorithm="SHA256"> + <Output TaskParameter="Hash" PropertyName="DllHash" /> + </GetFileHash> + <WriteLinesToFile File="$(TargetDir)checksum" Lines="$(DllHash)" Overwrite="true" /> + <Message Text="DLL Hash (SHA256): $(DllHash)" Importance="High" /> + <ItemGroup> + <Files Include="$(TargetDir)\**\*.*" /> + </ItemGroup> + <Copy SourceFiles="@(Files)" DestinationFiles="@(Files->'$(RogueTraderData)\UnityModManager\0$(AssemblyName)0\%(RecursiveDir)%(Filename)%(Extension)')" /> + <ZipDirectory SourceDirectory="$(MSBuildProjectDirectory)\$(OutputPath)" DestinationFile="$(MSBuildProjectDirectory)\$(OutputPath)\..\$(AssemblyName)-$(Version).zip" Overwrite="true" /> + </Target> + <ItemGroup> + <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="all" /> + <PackageReference Include="BepInEx.AssemblyPublicizer.MSBuild" IncludeAssets="build; contentfiles" Version="0.4.2" PrivateAssets="all" /> + <PackageReference Include="PublishToWorkshop" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" Version="1.0.10" PrivateAssets="all" /> + <PackageReference Include="MicroUtils.HarmonyAnalyzers" IncludeAssets="runtime; build; native; contentfiles; analyzers" Version="*-*" PrivateAssets="all" /> + </ItemGroup> + <Target Name="PublishToSteamWorkshop" AfterTargets="Publish"> + <PublishToWorkshop PathToManifest="$(MSBuildThisFileDirectory)\ModDetails\OwlcatModificationManifest.json" ImageDir="$(MSBuildThisFileDirectory)\ModDetails\" BuildDir="$(MSBuildProjectDirectory)\$(OutputPath)" PathToDescription="$(MSBuildThisFileDirectory)\ModDetails\Workshop-description.txt" GameAppId="2186680" /> + </Target> + <Target Name="GenerateCustomPropsFile" BeforeTargets="BeforeBuild" Condition="$(RogueTraderInstallDir) == ''"> + <Exec Command="findstr /C:"Mono path[0]" "$(RogueTraderData)\Player.log"" IgnoreExitCode="true" ConsoleToMSBuild="true"> + <Output TaskParameter="ExitCode" PropertyName="ExitCode" /> + <Output TaskParameter="ConsoleOutput" PropertyName="MonoPathLine" /> + </Exec> + <PropertyGroup> + <MonoPathRegex>^Mono path\[0\] = '(.*?)/WH40KRT_Data/Managed'$</MonoPathRegex> + </PropertyGroup> + <PropertyGroup> + <RogueTraderInstallDir>$([System.Text.RegularExpressions.Regex]::Match($(MonoPathLine), $(MonoPathRegex)).Groups[1].Value)</RogueTraderInstallDir> + </PropertyGroup> + <WriteLinesToFile File="$(SolutionDir)GamePath.props" Lines="<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> <PropertyGroup> <RogueTraderInstallDir>$(RogueTraderInstallDir)</RogueTraderInstallDir> </PropertyGroup> </Project>" Overwrite="true" Encoding="utf-8" /> + </Target> +</Project> diff --git a/ToyBox/classes/CustomEnchantmentStackingFix.cs b/ToyBox/classes/CustomEnchantmentStackingFix.cs deleted file mode 100644 index ed3dda79f..000000000 --- a/ToyBox/classes/CustomEnchantmentStackingFix.cs +++ /dev/null @@ -1,35 +0,0 @@ -using HarmonyLib; -using Kingmaker.Blueprints; -using Kingmaker.Items; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace ToyBox { - //Fix for enchants added with toybox getting erased by stack merges - EX: If you add mighty fists to an amulet of natural armor, then stick it in inventory where you have another natural armor amulet of the same type, they're merged into a stack and the mighty fists enchant vanishes - class CustomEnchantmentStackingFix { - [HarmonyPatch(typeof(ItemEntity), nameof(ItemEntity.CanBeMerged), new Type[] { typeof(ItemEntity) })] - static class Mergeupgrade { - public static void Postfix(ref bool __result, ItemEntity __instance, ItemEntity other) { - if (__result) { - if (!(__instance is ItemEntityUsable) && !(other is ItemEntityUsable)) { - - if (__instance.Enchantments.Count != other.Enchantments.Count)//This catches every case but same number of new enchants added - { - - __result = false; - return; - } - else if (__instance.Enchantments.Select(x => x.Blueprint.ToReference<BlueprintItemEnchantmentReference>()).Except(other.Enchantments.Select(x => x.Blueprint.ToReference<BlueprintItemEnchantmentReference>())).Any()) //And this catches the rest - { - - return; - } - } - } - } - } - } -} diff --git a/ToyBox/classes/Infrastructure/Blueprints/BlueprintDataSource.cs b/ToyBox/classes/Infrastructure/Blueprints/BlueprintDataSource.cs deleted file mode 100644 index 82d66737f..000000000 --- a/ToyBox/classes/Infrastructure/Blueprints/BlueprintDataSource.cs +++ /dev/null @@ -1,53 +0,0 @@ -using UnityEngine; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Kingmaker.Blueprints; -using Kingmaker.BundlesLoading; -using ModKit; -using Kingmaker.Blueprints.Facts; -using System.Web.Caching; - -namespace ToyBox { - internal class BlueprintDataSource : DataSource<SimpleBlueprint> { - private HashSet<BlueprintGuid> _allGUIDS; - internal readonly HashSet<string> badBlueprints = new() { "ce0842546b73aa34b8fcf40a970ede68", "2e3280bf21ec832418f51bee5136ec7a", "b60252a8ae028ba498340199f48ead67", "fb379e61500421143b52c739823b4082" }; - protected override void LoadData() { - var toc = ResourcesLibrary.BlueprintsCache.m_LoadedBlueprints; - if (toc == null) { - Stop(); - return; - } - var bpCache = ResourcesLibrary.BlueprintsCache; - var blueprintsInProcess = new List<Entry> { }; - var watch = System.Diagnostics.Stopwatch.StartNew(); - var loaded = 0; - var total = 1; - var allGUIDs = toc.AsEnumerable().OrderBy(e => e.Value.Offset); - total = allGUIDs.Count(); - Mod.Log($"Loading {total} Blueprints"); - UpdateProgress(blueprintsInProcess, total); - foreach (var entry in allGUIDs) { - if (badBlueprints.Contains(entry.Key.ToString())) continue; - SimpleBlueprint bp; - try { - bp = bpCache.Load(entry.Key); - } - catch { - Mod.Warn($"cannot load GUID: {entry.Key}"); - continue; - } - blueprintsInProcess.Add(Transformer(bp)); - loaded += 1; - if (loaded % 1000 == 0) { - UpdateProgress(blueprintsInProcess, total); - blueprintsInProcess.Clear(); - } - } - UpdateProgress(blueprintsInProcess, total, true); - blueprintsInProcess.Clear(); - } - } -} diff --git a/ToyBox/classes/Infrastructure/Blueprints/BlueprintExtensions.cs b/ToyBox/classes/Infrastructure/Blueprints/BlueprintExtensions.cs deleted file mode 100644 index fce9a2d6b..000000000 --- a/ToyBox/classes/Infrastructure/Blueprints/BlueprintExtensions.cs +++ /dev/null @@ -1,322 +0,0 @@ -// Copyright < 2021 > Narria (github user Cabarius) - License: MIT -using HarmonyLib; -using Kingmaker.AreaLogic.Etudes; -using Kingmaker.Blueprints; -using Kingmaker.Blueprints.Area; -using Kingmaker.Blueprints.Classes; -using Kingmaker.Blueprints.Classes.Selection; -using Kingmaker.Blueprints.Classes.Spells; -using Kingmaker.Blueprints.Facts; -using Kingmaker.Blueprints.Items; -using Kingmaker.Blueprints.Items.Ecnchantments; -using Kingmaker.Craft; -using Kingmaker.ElementsSystem; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.UI; -using Kingmaker.UnitLogic; -using Kingmaker.UnitLogic.Buffs.Blueprints; -using Kingmaker.UnitLogic.Mechanics; -using Kingmaker.Utility; -using ModKit; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; - -namespace ToyBox { - - public static partial class BlueprintExtensions { - public static Settings Settings => Main.Settings; - - private static ConditionalWeakTable<object, List<string>> _cachedCollationNames = new() { }; - private static readonly HashSet<BlueprintGuid> BadList = new(); - public static void ResetCollationCache() => _cachedCollationNames = new ConditionalWeakTable<object, List<string>> { }; - private static void AddOrUpdateCachedNames(SimpleBlueprint bp, List<string> names) { - names = names.Distinct().ToList(); - if (_cachedCollationNames.TryGetValue(bp, out _)) { - _cachedCollationNames.Remove(bp); - //Mod.Log($"removing: {bp.NameSafe()}"); - } - _cachedCollationNames.Add(bp, names); - //Mod.Log($"adding: {bp.NameSafe()} - {names.Count} - {String.Join(", ", names)}"); - } - - public static string GetDisplayName(this SimpleBlueprint bp) => bp switch { - BlueprintAbilityResource abilityResource => abilityResource.Name, - BlueprintArchetype archetype => archetype.Name, - BlueprintCharacterClass charClass => charClass.Name, - BlueprintItem item => item.Name, - BlueprintItemEnchantment enchant => enchant.Name, - BlueprintUnitFact fact => fact.NameSafe(), - SimpleBlueprint blueprint => blueprint.name, - _ => "n/a" - }; - public static string GetDisplayName(this BlueprintSpellbook bp) { - var name = bp.DisplayName; - if (string.IsNullOrEmpty(name)) name = bp.name.Replace("Spellbook", ""); - return name; - } - public static string GetTitle(SimpleBlueprint blueprint) { - if (blueprint is IUIDataProvider uiDataProvider) { - string name; - var isEmpty = uiDataProvider.Name.IsNullOrEmpty(); - if (isEmpty) { - name = blueprint.name; - } - else { - if (blueprint is BlueprintSpellbook spellbook) - return $"{spellbook.Name} - {spellbook.name}"; - name = uiDataProvider.Name; - if (name == "<null>" || name.StartsWith("[unknown key: ")) { - name = blueprint.name; - } - else if (Settings.showDisplayAndInternalNames) { - name += $" : {blueprint.name.color(RGBA.darkgrey)}"; - } - } - return name; - } - return blueprint.name; - } - public static string GetSearchKey<Definition>(Definition feature) where Definition : SimpleBlueprint, IUIDataProvider { - string name; - var isEmpty = feature.Name.IsNullOrEmpty(); - if (isEmpty) { - name = feature.name; - } - else { - if (feature is BlueprintSpellbook spellbook) - return $"{spellbook.Name} {spellbook.name}"; - name = feature.Name; - if (name == "<null>" || name.StartsWith("[unknown key: ")) { - name = feature.name; - } - else if (Settings.showDisplayAndInternalNames) { - name += $" : {feature.name}"; - } - } - return name.StripHTML(); // can we get rid of this? - } - public static string GetSortKey(SimpleBlueprint blueprint) { - if (blueprint is IUIDataProvider uiDataProvider) { - string name; - var isEmpty = uiDataProvider.Name.IsNullOrEmpty(); - if (isEmpty) { - name = blueprint.name; - } - else { - if (blueprint is BlueprintSpellbook spellbook) - return $"{spellbook.Name} - {spellbook.name}"; - name = uiDataProvider.Name; - if (name == "<null>" || name.StartsWith("[unknown key: ")) { - name = blueprint.name; - } - else if (Settings.showDisplayAndInternalNames) { - name += blueprint.name; - } - } - return name; - } - return blueprint.name; - } - public static IEnumerable<string> Attributes(this SimpleBlueprint bp) { - List<string> modifiers = new(); - if (BadList.Contains(bp.AssetGuid)) return modifiers; - var traverse = Traverse.Create(bp); - foreach (var property in Traverse.Create(bp).Properties().Where(property => property.StartsWith("Is"))) { - try { - var value = traverse.Property<bool>(property)?.Value; - if (value.HasValue && value.GetValueOrDefault()) { - modifiers.Add(property); //.Substring(2)); - } - } - catch (Exception e) { - Mod.Warn($"${bp.name}.{property} thew an exception: {e.Message}"); - BadList.Add(bp.AssetGuid); - break; - } - } - return modifiers; - } - private static List<string> DefaultCollationNames(this SimpleBlueprint bp, string[] extras) { - _cachedCollationNames.TryGetValue(bp, out var names); - if (names == null) { - names = new List<string> { }; - var typeName = bp.GetType().Name.Replace("Blueprint", ""); - //var stripIndex = typeName.LastIndexOf("Blueprint"); - //if (stripIndex > 0) typeName = typeName.Substring(stripIndex + "Blueprint".Length); - names.Add(typeName); - foreach (var attribute in bp.Attributes()) - names.Add(attribute.orange()); - _cachedCollationNames.Add(bp, names.Distinct().ToList()); - } - - if (extras != null) names = names.Concat(extras).ToList(); - return names; - } - public static List<string> CollationNames(this SimpleBlueprint bp, params string[] extras) => DefaultCollationNames(bp, extras); - public static List<string> CollationNames(this BlueprintCharacterClass bp, params string[] extras) { - var names = DefaultCollationNames(bp, extras); - if (bp.IsArcaneCaster) names.Add("Arcane"); - if (bp.IsDivineCaster) names.Add("Divine"); - if (bp.IsMythic) names.Add("Mythic"); - return names; - } - public static List<string> CollationNames(this BlueprintSpellbook bp, params string[] extras) { - var names = DefaultCollationNames(bp, extras); - if (bp.CharacterClass.IsDivineCaster) names.Add("Divine"); - AddOrUpdateCachedNames(bp, names); - return names; - } - public static List<string> CollationNames(this BlueprintBuff bp, params string[] extras) { - var names = DefaultCollationNames(bp, extras); - if (bp.Harmful) names.Add("Harmful"); - if (bp.RemoveOnRest) names.Add("Rest Removes"); - if (bp.RemoveOnResurrect) names.Add("Res Removes"); - if (bp.Ranks > 0) names.Add($"{bp.Ranks} Ranks"); - - AddOrUpdateCachedNames(bp, names); - return names; - } - public static List<string> CollationNames(this BlueprintIngredient bp, params string[] extras) { - var names = DefaultCollationNames(bp, extras); - if (bp.Destructible) names.Add("Destructible"); - if (bp.FlavorText != null) names.Add(bp.FlavorText); - AddOrUpdateCachedNames(bp, names); - return names; - } - public static List<string> CollationNames(this BlueprintArea bp, params string[] extras) { - var names = DefaultCollationNames(bp, extras); - var typeName = bp.GetType().Name.Replace("Blueprint", ""); - if (typeName == "Area") names.Add($"Area CR{bp.CR}"); - AddOrUpdateCachedNames(bp, names); - return names; - } - public static List<string> CollationNames(this BlueprintEtude bp, params string[] extras) { - var names = DefaultCollationNames(bp, extras); - //foreach (var item in bp.ActivationCondition) { - // names.Add(item.name.yellow()); - //} - //names.Add(bp.ValidationStatus.ToString().yellow()); - //if (bp.HasParent) names.Add($"P:".yellow() + bp.Parent.NameSafe()); - //foreach (var sibling in bp.StartsWith) { - // names.Add($"W:".yellow() + bp.Parent.NameSafe()); - //} - //if (bp.HasLinkedAreaPart) names.Add($"area {bp.LinkedAreaPart.name}".yellow()); - //foreach (var condition in bp.ActivationCondition?.Conditions) - // names.Add(condition.GetCaption().yellow()); - AddOrUpdateCachedNames(bp, names); - return names; - } - public static string[] CaptionNames(this SimpleBlueprint bp) => bp.m_AllElements?.OfType<Condition>()?.Select(e => e.GetCaption() ?? "")?.ToArray() ?? new string[] { }; - public static List<String> CaptionCollationNames(this SimpleBlueprint bp) => bp.CollationNames(bp.CaptionNames()); - // Custom Attributes that Owlcat uses - public static IEnumerable<InfoBoxAttribute> GetInfoBoxes(this SimpleBlueprint bp) => bp.GetAttributes<InfoBoxAttribute>(); - - public static string GetInfoBoxDescription(this SimpleBlueprint bp) => string.Join("\n", bp.GetInfoBoxes().Select(attr => attr.Text)); - - private static readonly Dictionary<Type, IEnumerable<SimpleBlueprint>> blueprintsByType = new(); - public static IEnumerable<SimpleBlueprint> BlueprintsOfType(Type type) { - if (blueprintsByType.TryGetValue(type, out var ofType)) return ofType; - var blueprints = BlueprintLoader.Shared.GetBlueprints(); - if (blueprints == null) return new List<SimpleBlueprint>(); - var filtered = blueprints.Where((bp) => bp?.GetType().IsKindOf(type) == true).ToList(); - // FIXME - why do we get inconsistent partial results if we cache here - //if (filtered.Count > 0) - // blueprintsByType[type] = filtered; - return filtered; - } - - public static IEnumerable<BPType> BlueprintsOfType<BPType>() where BPType : SimpleBlueprint { - var type = typeof(BPType); - if (blueprintsByType.TryGetValue(type, out var value)) return value.OfType<BPType>(); - var blueprints = BlueprintLoader.Shared.GetBlueprints<BPType>(); - if (blueprints == null) return new List<BPType>(); - var filtered = blueprints.Where((bp) => bp != null).ToList(); - // FIXME - why do we get inconsistent partial results if we cache here - //if (filtered.Count > 0) - // blueprintsByType[type] = filtered; - return filtered; - } - - public static IEnumerable<T> GetBlueprints<T>() where T : SimpleBlueprint => BlueprintsOfType<T>(); - public static int GetSelectableFeaturesCount(this BlueprintFeatureSelection selection, UnitDescriptor unit) { - var count = 0; - var component = selection.GetComponent<NoSelectionIfAlreadyHasFeature>(); - if (component == null) - return count; - if (component.AnyFeatureFromSelection) { - count += selection.AllFeatures.Count(allFeature => !unit.Progression.Features.HasFact((BlueprintFact)allFeature)); - } - count += component.Features.Count(feature => !unit.Progression.Features.HasFact((BlueprintFact)feature)); - return count; - } - - // BlueprintFeatureSelection Helpers - public class FeatureSelectionEntry { - public BlueprintFeature feature = null; - public int level = 0; - public FeatureSelectionData data; - } - public static bool HasFeatureSelection(this UnitEntityData ch, BlueprintFeatureSelection bp, BlueprintFeature feature) { - var progression = ch?.Descriptor?.Progression; - if (progression == null) return false; - if (!progression.Features.HasFact(bp)) return false; - return progression.Selections.TryGetValue(bp, out var selection) - && selection.SelectionsByLevel.Values.Any(l => l.Any(f => f == feature)); - } - public static List<BlueprintFeature> FeatureSelectionValues(this UnitEntityData ch, BlueprintFeatureSelection bp) => bp.AllFeatures.Where(f => ch.HasFeatureSelection(bp, f)).ToList(); - public static List<FeatureSelectionEntry> FeatureSelectionEntries(this UnitEntityData ch, BlueprintFeatureSelection bp) - => (from pair in ch.Descriptor.Progression.Selections - where pair.Key == bp - from byLevelPair in pair.Value.SelectionsByLevel - from feature in byLevelPair.Value - select new FeatureSelectionEntry { feature = feature, level = byLevelPair.Key, data = pair.Value }).ToList(); - public static void AddFeatureSelection(this UnitEntityData ch, BlueprintFeatureSelection bp, BlueprintFeature feature, int level = 1) { - var source = new FeatureSource(); - ch?.Progression?.AddSelection(bp, source, level, feature); - var featureCollection = ch?.Progression?.Features; - if (featureCollection == null) return; - var fact = new Feature(feature, featureCollection.Owner, null); - fact = featureCollection.Manager.Add<Feature>(fact); - fact?.SetSource(source, level); - // ch?.Progression?.Features.AddFeature(bp).SetSource(source, 1); - } - public static void RemoveFeatureSelection(this UnitEntityData ch, BlueprintFeatureSelection bp, FeatureSelectionData data, BlueprintFeature feature) { - var progression = ch?.Descriptor?.Progression; - if (progression == null) return; - var fact = progression.Features.GetFact(feature); - BlueprintFeatureSelection featureSelection = null; - FeatureSelectionData featureSelectionData = null; - var level = -1; - foreach (var selection in progression.Selections) { - foreach (var keyValuePair in selection.Value.SelectionsByLevel.Where(keyValuePair => keyValuePair.Value.HasItem<BlueprintFeature>(feature))) { - featureSelection = selection.Key; - featureSelectionData = selection.Value; - level = keyValuePair.Key; - break; - } - if (level >= 0) - break; - } - if (featureSelection != null) { - featureSelectionData.RemoveSelection(level, feature); - } - if (fact == null) return; - progression.Features.RemoveFact(fact); - } - - // BlueprintParametrizedFeature Helpers - public static bool HasParameterizedFeatureItem(this UnitEntityData ch, BlueprintParametrizedFeature bp, IFeatureSelectionItem item) { - if (!bp.Items.Any()) return false; - var existing = ch?.Descriptor?.Unit?.Facts?.Get<Feature>(i => i.Blueprint == bp && i.Param == item.Param); - return existing != null; - } - public static List<IFeatureSelectionItem> ParameterizedFeatureItems(this UnitEntityData ch, BlueprintParametrizedFeature bp) => bp.Items.Where(f => ch.HasParameterizedFeatureItem(bp, f)).ToList(); - public static void AddParameterizedFeatureItem(this UnitEntityData ch, BlueprintParametrizedFeature bp, IFeatureSelectionItem item) => ch?.Descriptor?.AddFact<UnitFact>(bp, null, item.Param); - public static void RemoveParameterizedFeatureItem(this UnitEntityData ch, BlueprintParametrizedFeature bp, IFeatureSelectionItem item) { - var fact = ch.Descriptor?.Unit?.Facts?.Get<Feature>(i => i.Blueprint == bp && i.Param == item.Param); - ch?.Progression?.Features?.RemoveFact(fact); - } - } -} \ No newline at end of file diff --git a/ToyBox/classes/Infrastructure/Blueprints/BlueprintLoader.cs b/ToyBox/classes/Infrastructure/Blueprints/BlueprintLoader.cs deleted file mode 100644 index 25906aebe..000000000 --- a/ToyBox/classes/Infrastructure/Blueprints/BlueprintLoader.cs +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright < 2021 > Narria(github user Cabarius) - License: MIT -using UnityEngine; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using Kingmaker.Blueprints; -using Kingmaker.BundlesLoading; -using ModKit; -using System; -using Kingmaker.Blueprints.Facts; - -namespace ToyBox { - - public class BlueprintLoader : MonoBehaviour { - public delegate void LoadBlueprintsCallback(IEnumerable<SimpleBlueprint> blueprints); - - private LoadBlueprintsCallback callback; - private List<SimpleBlueprint> _blueprintsInProcess; - private List<SimpleBlueprint> blueprints; - //private List<SimpleBlueprint> blueprints; - public float progress = 0; - private static BlueprintLoader _shared; - public static BlueprintLoader Shared { - get { - if (_shared == null) { - _shared = new GameObject().AddComponent<BlueprintLoader>(); - DontDestroyOnLoad(_shared.gameObject); - } - return _shared; - } - } - private IEnumerator coroutine; - private void UpdateProgress(int loaded, int total) { - if (total <= 0) { - progress = 0.0f; - return; - } - progress = (float)loaded / (float)total; - } - - internal readonly HashSet<string> badBlueprints = new() { "ce0842546b73aa34b8fcf40a970ede68", "2e3280bf21ec832418f51bee5136ec7a", "b60252a8ae028ba498340199f48ead67", "fb379e61500421143b52c739823b4082" }; - - private IEnumerator LoadBlueprints() { - yield return null; - var bpCache = ResourcesLibrary.BlueprintsCache; - while (bpCache == null) { - yield return null; - bpCache = ResourcesLibrary.BlueprintsCache; - } - _blueprintsInProcess = new List<SimpleBlueprint> { }; - var toc = ResourcesLibrary.BlueprintsCache.m_LoadedBlueprints; - while (toc == null) { - yield return null; - toc = ResourcesLibrary.BlueprintsCache.m_LoadedBlueprints; - } - - var watch = System.Diagnostics.Stopwatch.StartNew(); -#if true // TODO - Truinto for evaluation; my result improved from 2689 to 17 milliseconds - var loaded = 0; - var total = 1; - var allGUIDs = toc.AsEnumerable().OrderBy(e => e.Value.Offset); - total = allGUIDs.Count(); - Mod.Log($"Loading {total} Blueprints"); - UpdateProgress(loaded, total); - foreach (var entry in allGUIDs) { - if (badBlueprints.Contains(entry.Key.ToString())) continue; - SimpleBlueprint bp; - try { - bp = bpCache.Load(entry.Key); - } - catch { - Mod.Warn($"cannot load GUID: {entry.Key}"); - continue; - } - _blueprintsInProcess.Add(bp); - loaded += 1; - UpdateProgress(loaded, total); - if (loaded % 1000 == 0) { - yield return null; - } - } -#else - blueprints = ResourcesLibrary.BlueprintsCache.m_LoadedBlueprints.Values.Select(s => s.Blueprint).ToList(); -#endif - watch.Stop(); - Mod.Log($"loaded {_blueprintsInProcess.Count} blueprints in {watch.ElapsedMilliseconds} milliseconds"); - callback(_blueprintsInProcess); - yield return null; - StopCoroutine(coroutine); - coroutine = null; - } - private void Load(LoadBlueprintsCallback callback) { - if (coroutine != null) { - StopCoroutine(coroutine); - coroutine = null; - } - this.callback = callback; - coroutine = LoadBlueprints(); - StartCoroutine(coroutine); - } - public bool IsLoading { - get { - if (coroutine != null) { - return true; - } - return false; - } - } - - public List<SimpleBlueprint> GetBlueprints() { - if (blueprints == null) { - if (Shared.IsLoading) { return null; } - else { - Mod.Debug($"calling BlueprintLoader.Load"); - Shared.Load((bps) => { - _blueprintsInProcess = bps.ToList(); - blueprints = _blueprintsInProcess; - Mod.Debug($"success got {bps.Count()} bluerints"); - }); - return null; - } - } - return blueprints; - } - public List<BPType> GetBlueprints<BPType>() { - var bps = GetBlueprints(); - return bps?.OfType<BPType>().ToList() ?? null; - } - - private IEnumerable<BPType> GetBlueprintsByGuids<BPType>(IEnumerable<BlueprintGuid> guids) where BPType: BlueprintFact { - var bps = GetBlueprints<BPType>(); - return bps?.Where(bp => guids.Contains(bp.AssetGuid)); - } - - public IEnumerable<BPType> GetBlueprintsByGuids<BPType>(IEnumerable<string> guids) where BPType: BlueprintFact => GetBlueprintsByGuids<BPType>(guids.Select(g => BlueprintGuid.Parse(g))); - } - - public static class BlueprintLoader<BPType> { - public static IEnumerable<BPType> blueprints = null; - } - - public static class BlueprintLoaderOld { - public delegate void LoadBlueprintsCallback(IEnumerable<SimpleBlueprint> blueprints); - - private static AssetBundleRequest LoadRequest; - public static float progress = 0; - public static void Load(LoadBlueprintsCallback callback) { -#if false - var bundle = (AssetBundle)AccessTools.Field(typeof(ResourcesLibrary), "s_BlueprintsBundle").GetValue(null); - Main.Log($"got bundle {bundle}"); - LoadRequest = bundle.LoadAllAssetsAsync<BlueprintScriptableObject>(); -#endif - var bundle = BundlesLoadService.Instance.RequestBundle(AssetBundleNames.BlueprintAssets); - BundlesLoadService.Instance.LoadDependencies(AssetBundleNames.BlueprintAssets); - LoadRequest = bundle.LoadAllAssetsAsync<object>(); - Mod.Trace($"created request {LoadRequest}"); - LoadRequest.completed += (asyncOperation) => { - Mod.Trace($"completed request and calling completion - {LoadRequest.allAssets.Length} Assets "); - callback(LoadRequest.allAssets.Cast<SimpleBlueprint>()); - LoadRequest = null; - }; - } - public static bool LoadInProgress() { - if (LoadRequest != null) { - progress = LoadRequest.progress; - return true; - } - return false; - } - } -} diff --git a/ToyBox/classes/Infrastructure/Borrowed/SmartConsole.cs b/ToyBox/classes/Infrastructure/Borrowed/SmartConsole.cs deleted file mode 100644 index a4cb852a1..000000000 --- a/ToyBox/classes/Infrastructure/Borrowed/SmartConsole.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Media; -using System.Text; -using System.Threading.Tasks; -using UnityModManagerNet; -using ModKit; -using System.IO; - -namespace ToyBox { - public static class SmartConsoleCommands { - public static void Register() { - SmartConsole.RegisterCommand("beep", "", "Plays the 'beep' system sound.", new SmartConsole.ConsoleCommandFunction(Beep)); - SmartConsole.RegisterCommand("bat", "bat fileName", "Executes commands from a file in the Bag of Tricks folder.", new SmartConsole.ConsoleCommandFunction(CommandBatch)); - - } - - public static void Beep(string parameters) => SystemSounds.Beep.Play(); - - public static void CommandBatch(string parameters) { - parameters = parameters.Remove(0, 4); - if (File.Exists(Mod.modEntryPath + parameters)) { - try { - var i = 0; - var commands = File.ReadAllLines(Mod.modEntryPath + parameters); - foreach (var s in commands) { - SmartConsole.WriteLine($"[{i}]: {s}"); - SmartConsole.ExecuteLine(s); - i++; - } - } - catch (Exception e) { - Mod.Error(e); - } - } - else { - SmartConsole.WriteLine($"'{parameters}' Not Found"); - } - } - } -} diff --git a/ToyBox/classes/Infrastructure/Borrowed/StateReplacer.cs b/ToyBox/classes/Infrastructure/Borrowed/StateReplacer.cs deleted file mode 100644 index ac4c96e6a..000000000 --- a/ToyBox/classes/Infrastructure/Borrowed/StateReplacer.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Kingmaker.Blueprints.Classes; -using Kingmaker.UnitLogic.Class.LevelUp; - - -namespace ToyBox { - // https://github.com/paxchristos/pk_multiple_classes_per_level_fork - public class StateReplacer { - public readonly LevelUpState State; - public readonly BlueprintCharacterClass SelectedClass; - public readonly int NextClassLevel; - - private StateReplacer() { } - - public StateReplacer(LevelUpState state) { - State = state; - SelectedClass = state.SelectedClass; - NextClassLevel = state.NextClassLevel; - } - - public void Replace(BlueprintCharacterClass selectedClass) => State.SelectedClass = selectedClass; - - public void Replace(BlueprintCharacterClass selectedClass, int nextClassLevel) { - State.SelectedClass = selectedClass; - State.NextClassLevel = nextClassLevel; - } - - public void Restore() { - State.SelectedClass = SelectedClass; - State.NextClassLevel = NextClassLevel; - } - } -} diff --git a/ToyBox/classes/Infrastructure/CasterHelpers.cs b/ToyBox/classes/Infrastructure/CasterHelpers.cs deleted file mode 100644 index 2a688f2c5..000000000 --- a/ToyBox/classes/Infrastructure/CasterHelpers.cs +++ /dev/null @@ -1,255 +0,0 @@ -using Kingmaker.Blueprints; -using Kingmaker.Blueprints.Classes; -using Kingmaker.Blueprints.Classes.Selection; -using Kingmaker.Blueprints.Classes.Spells; -using Kingmaker.Blueprints.Root; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.UnitLogic; -using Kingmaker.UnitLogic.Abilities; -using Kingmaker.UnitLogic.Abilities.Blueprints; -using Kingmaker.UnitLogic.FactLogic; -using ModKit; -using System.Collections.Generic; -using System.Linq; - -namespace ToyBox.classes.Infrastructure { - public static class CasterHelpers { - private static readonly Dictionary<string, List<int>> UnitSpellsKnown = new(); - // This is to figure out how many caster levels you actually have real levels in - public static Dictionary<BlueprintSpellbook, int> GetOriginalCasterLevel(UnitDescriptor unit) { - var mythicLevel = 0; - BlueprintSpellbook mythicSpellbook = null; - Dictionary<BlueprintSpellbook, int> casterLevelDictionary = new(); - foreach (var classInfo in unit.Progression.Classes) { - if (classInfo.CharacterClass == BlueprintRoot.Instance.Progression.MythicStartingClass || - classInfo.CharacterClass == BlueprintRoot.Instance.Progression.MythicCompanionClass) { - if (mythicSpellbook == null) { - mythicLevel += classInfo.Level; - } - else { - casterLevelDictionary[mythicSpellbook] += classInfo.Level; - } - } - - if (classInfo.Spellbook == null) { - continue; - } - - var casterLevel = classInfo.Level + classInfo.Spellbook.CasterLevelModifier; - if (classInfo.CharacterClass.IsMythic) { - casterLevel += mythicLevel; - mythicSpellbook = classInfo.Spellbook; - } - - var skipLevels = classInfo.CharacterClass.GetComponent<SkipLevelsForSpellProgression>(); - - if (skipLevels?.Levels?.Length > 0) { - foreach (var skipLevelsLevel in skipLevels.Levels) { - if (classInfo.Level >= skipLevelsLevel) { --casterLevel; } - } - } - - var levelToStartCountingFrom = classInfo.CharacterClass.PrestigeClass ? GetPrestigeCasterLevelStart(classInfo.CharacterClass.Progression) : 1; - casterLevel += 1 - levelToStartCountingFrom; - - if (classInfo.Spellbook.IsMythic) { - casterLevel *= 2; - } - - if (casterLevelDictionary.ContainsKey(classInfo.Spellbook)) { - casterLevelDictionary[classInfo.Spellbook] += casterLevel; - } - else { - casterLevelDictionary[classInfo.Spellbook] = casterLevel; - } - } -#if DEBUG - foreach (var spellbook in casterLevelDictionary) { - Mod.Trace($"spellbook - {spellbook.Key.Name}: {spellbook.Value}"); - } -#endif - return casterLevelDictionary; - } - - public static int GetRealCasterLevel(UnitDescriptor unit, BlueprintSpellbook spellbook) { - var hasCasterLevel = GetOriginalCasterLevel(unit).TryGetValue(spellbook, out var level); - return hasCasterLevel ? level : 0; - } - - - private static int GetPrestigeCasterLevelStart(BlueprintProgression progression) { - foreach (var level in progression.LevelEntries) { - if (level.Features.OfType<BlueprintFeatureSelection>().SelectMany(feature => feature.AllFeatures.OfType<BlueprintFeatureReplaceSpellbook>()).Any()) { - return level.Level; - } - } - return 1; - } - - public static void RemoveSpellsOfLevel(this Spellbook spellbook, int level) { - var spells = new List<AbilityData>(spellbook.GetKnownSpells(level)); - // copy constructor is needed to avoid self mutation here - spells.ForEach(x => spellbook.RemoveSpell(x.Blueprint)); - } - - public static void LowerCasterLevel(Spellbook spellbook) { - var oldMaxSpellLevel = spellbook.MaxSpellLevel; - spellbook.m_BaseLevelInternal--; - var newMaxSpellLevel = spellbook.MaxSpellLevel; - if (newMaxSpellLevel < oldMaxSpellLevel) { - RemoveSpellsOfLevel(spellbook, oldMaxSpellLevel); - } - } - - public static void AddCasterLevel(Spellbook spellbook) { - var oldMaxSpellLevel = spellbook.MaxSpellLevel; - spellbook.m_BaseLevelInternal++; - var newMaxSpellLevel = spellbook.MaxSpellLevel; - if (newMaxSpellLevel > oldMaxSpellLevel) { - spellbook.LearnSpellsOnRaiseLevel(oldMaxSpellLevel, newMaxSpellLevel, false); - } - } - public static void AddIfUnknown(this Spellbook spellbook, int level, BlueprintAbility ability) { - if (!spellbook.IsKnown(ability)) spellbook.AddKnown(level, ability); - } - - public static void AddAllSpellsOfSelectedLevel(Spellbook spellbook, int level) { - List<BlueprintAbility> toLearn; - if (Main.Settings.showFromAllSpellbooks) { - var normal = BlueprintExtensions.GetBlueprints<BlueprintSpellbook>() - .Where(x => ((BlueprintSpellbook)x).SpellList != null) - .SelectMany(x => ((BlueprintSpellbook)x).SpellList.GetSpells(level)); - var mythic = BlueprintExtensions.GetBlueprints<BlueprintSpellbook>() - .Where(x => ((BlueprintSpellbook)x).MythicSpellList != null) - .SelectMany(x => ((BlueprintSpellbook)x).MythicSpellList.GetSpells(level)); - toLearn = normal.Concat(mythic).Distinct().ToList(); - } - else { - toLearn = spellbook.Blueprint.SpellList.GetSpells(level); - } - - toLearn.ForEach(x => spellbook.AddIfUnknown(level, x)); - } - - public static IEnumerable<BlueprintAbility> GetSpellsLearnableOfLevel(this Spellbook sb, int lvl) { - foreach (var s in sb.Blueprint.SpellList.GetSpells(lvl)) - if (!sb.IsKnown(s)) - yield return s; - } - - public static void HandleAddAllSpellsOnPartyEditor(UnitDescriptor unit, List<BlueprintAbility> abilities) { - if (!PartyEditor.SelectedSpellbook.TryGetValue(unit.HashKey(), out var selectedSpellbook)) { - return; - } - var level = PartyEditor.selectedSpellbookLevel; - if (level == selectedSpellbook.Blueprint.MaxSpellLevel + 1) - level = PartyEditor.newSpellLvl; - if (abilities != null) { - abilities.ForEach(x => selectedSpellbook.AddIfUnknown(level, x)); - } - else { - AddAllSpellsOfSelectedLevel(selectedSpellbook, level); - } - } - - public static void HandleAddAllSpellsOnPartyEditor(UnitDescriptor unit) { - if (!PartyEditor.SelectedSpellbook.TryGetValue(unit.HashKey(), out var selectedSpellbook)) { - return; - } - var level = PartyEditor.selectedSpellbookLevel; - if (level == selectedSpellbook.Blueprint.MaxSpellLevel + 1) - level = PartyEditor.newSpellLvl; - selectedSpellbook.RemoveSpellsOfLevel(level); - } - - public static int GetActualSpellsLearnedForClass(UnitDescriptor unit, Spellbook spellbook, int level) { - // Get all +spells known facts for this spellbook's class so we can ignore them when getting spell counts - var spellsToIgnore = unit.Facts.List.SelectMany(x => - x.BlueprintComponents.Where(y => y is AddKnownSpell)).Select(z => z as AddKnownSpell) - .Where(x => x.CharacterClass == spellbook.Blueprint.CharacterClass && (x.Archetype == null || unit.Progression.IsArchetype(x.Archetype))).Select(y => y.Spell) - .ToList(); - - return GetActualSpellsLearned(spellbook, level, spellsToIgnore); - } - - public static int GetActualSpellsLearned(Spellbook spellbook, int level, List<BlueprintAbility> spellsToIgnore) { - var known = spellbook.SureKnownSpells(level) - .Where(x => !x.IsTemporary - && !x.CopiedFromScroll - && !x.IsFromMythicSpellList - && x.SourceItem == null - && x.SourceItemEquipmentBlueprint == null - && x.SourceItemUsableBlueprint == null - && !x.IsMysticTheurgeCombinedSpell - && !spellsToIgnore.Contains(x.Blueprint)) - .Distinct() - .ToList(); - - return known.Count; - } - - public static IEnumerable<ClassData> MergableClasses(this UnitEntityData unit) { - var spellbookCandidates = unit.Spellbooks - .Where(sb => sb.IsStandaloneMythic && sb.Blueprint.CharacterClass != null) - .Select(sb => sb.Blueprint).ToHashSet(); - //Mod.Log($"{unit.CharacterName} - spellbookCandidates: {string.Join(", ", spellbookCandidates.Select(sb => sb.DisplayName))}"); - var classCandidates = unit.Progression.Classes - .Where(cl => cl.Spellbook != null && spellbookCandidates.Contains(cl.Spellbook)); - //Mod.Log($"{unit.CharacterName} - classCandidates: {string.Join(", ", classCandidates.Select(cl => cl.CharacterClass.Name))}"); - return classCandidates; - } - public static void MergeMythicSpellbook(this Spellbook targetSpellbook, ClassData fromClass) { - var unit = targetSpellbook.Owner; - var oldMythicSpellbookBp = fromClass?.Spellbook; - if (fromClass == null || oldMythicSpellbookBp == null || !oldMythicSpellbookBp.IsMythic) { - Mod.Warn("Can't merge because you don't have a mythic class / mythic spellbook!"); - return; - } - - fromClass.Spellbook = targetSpellbook.Blueprint; - targetSpellbook.m_Type = SpellbookType.Mythic; - targetSpellbook.AddSpecialList(oldMythicSpellbookBp.MythicSpellList); - for (var i = targetSpellbook.MythicLevel; i < unit.Progression.MythicLevel; i++) { - targetSpellbook.AddMythicLevel(); - } - unit.DeleteSpellbook(oldMythicSpellbookBp); - } - private static readonly Dictionary<int, List<BlueprintAbility>> AllSpellsCache = new(); - public static List<BlueprintAbility> GetAllSpells(int level) { - if (AllSpellsCache.TryGetValue(level, out var spells)) { - return spells; - } - else { - if (level == -1) { - var abilities = BlueprintExtensions.GetBlueprints<BlueprintAbility>(); - spells = new List<BlueprintAbility>(); - foreach (var ability in abilities) { - if (ability.IsSpell) { - spells.Add(ability); - } - } - } - else { - var spellbooks = BlueprintExtensions.GetBlueprints<BlueprintSpellbook>(); - if (spellbooks == null) return null; - Mod.Log($"spellbooks: {spellbooks.Count()}"); - - var normal = from spellbook in spellbooks - where spellbook.SpellList != null - from spell in spellbook.SpellList.GetSpells(level) - select spell; - Mod.Log($"normal: {normal.Count()}"); - var mythic = from spellbook in spellbooks - where spellbook.MythicSpellList != null - from spell in spellbook.MythicSpellList.GetSpells(level) - select spell; - Mod.Log($"mythic: {mythic.Count()}"); - spells = normal.Concat(mythic).Distinct().ToList(); - } - if (spells.Count() > 0) - AllSpellsCache[level] = spells; - return spells; - } - } - } -} diff --git a/ToyBox/classes/Infrastructure/CharacterPicker.cs b/ToyBox/classes/Infrastructure/CharacterPicker.cs deleted file mode 100644 index 88caecc2e..000000000 --- a/ToyBox/classes/Infrastructure/CharacterPicker.cs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright < 2021 > Narria (github user Cabarius) - License: MIT -using System.Collections.Generic; -using System.Linq; -using Kingmaker; -using Kingmaker.Designers; -using Kingmaker.EntitySystem.Entities; -using ModKit; -using static ModKit.UI; - -namespace ToyBox { - public class CharacterPicker { - public static NamedFunc<List<UnitEntityData>>[] PartyFilterChoices = null; - private static readonly Player partyFilterPlayer = null; - public static float nearbyRange = 25; - - public static NamedFunc<List<UnitEntityData>>[] GetPartyFilterChoices() { - if (partyFilterPlayer != Game.Instance.Player) PartyFilterChoices = null; - if (Game.Instance.Player != null && PartyFilterChoices == null) { - PartyFilterChoices = new NamedFunc<List<UnitEntityData>>[] { - new NamedFunc<List<UnitEntityData>>("Party", () => Game.Instance.Player.Party), - new NamedFunc<List<UnitEntityData>>("Party & Pets", () => Game.Instance.Player.m_PartyAndPets), - new NamedFunc<List<UnitEntityData>>("All", () => Game.Instance.Player.AllCharacters), - new NamedFunc<List<UnitEntityData>>("Active", () => Game.Instance.Player.ActiveCompanions), - new NamedFunc<List<UnitEntityData>>("Remote", () => Game.Instance.Player.m_RemoteCompanions), - new NamedFunc<List<UnitEntityData>>("Custom", PartyUtils.GetCustomCompanions), - new NamedFunc<List<UnitEntityData>>("Pets", PartyUtils.GetPets), - //new NamedFunc<List<UnitEntityData>>("Familiars", Game.Instance.Player.Party.SelectMany(ch => ch.Familiars), - new NamedFunc<List<UnitEntityData>>("Nearby", () => { - var player = GameHelper.GetPlayerCharacter(); - return player == null - ? new List<UnitEntityData> () - : GameHelper.GetTargetsAround(GameHelper.GetPlayerCharacter().Position, nearbyRange , false, false).ToList(); - }), - new NamedFunc<List<UnitEntityData>>("Friendly", () => Game.Instance.State.Units.Where((u) => u != null && !u.IsEnemy(GameHelper.GetPlayerCharacter())).ToList()), - new NamedFunc<List<UnitEntityData>>("Enemies", () => Game.Instance.State.Units.Where((u) => u != null && u.IsEnemy(GameHelper.GetPlayerCharacter())).ToList()), - new NamedFunc<List<UnitEntityData>>("All Units", () => Game.Instance.State.Units.ToList()), - }; - } - return PartyFilterChoices; - } - public static List<UnitEntityData> GetCharacterList() { - var partyFilterChoices = GetPartyFilterChoices(); - return partyFilterChoices?[Main.Settings.selectedPartyFilter].func(); - } - - private static int _selectedIndex = 0; - public static UnitEntityData GetSelectedCharacter() { - var characters = GetCharacterList(); - if (characters == null || characters.Count == 0) { - return Game.Instance.Player.MainCharacter; - } - if (_selectedIndex >= characters.Count) _selectedIndex = 0; - return characters[_selectedIndex]; - } - public static void ResetGUI() => _selectedIndex = 0; - - public static NamedFunc<List<UnitEntityData>> OnFilterPickerGUI() { - var filterChoices = GetPartyFilterChoices(); - if (filterChoices == null) { return null; } - - var characterListFunc = TypePicker( - null, - ref Main.Settings.selectedPartyFilter, - filterChoices - ); - return characterListFunc; - } - public static void OnCharacterPickerGUI(float indent = 0) { - - var characters = GetCharacterList(); - if (characters == null) { return; } - using (HorizontalScope(AutoWidth())) { - Space(indent); - ActionSelectionGrid(ref _selectedIndex, - characters.Select((ch) => ch.CharacterName).ToArray(), - 8, - (index) => { SearchAndPick.UpdateSearchResults(); }, - AutoWidth()); - } - var selectedCharacter = GetSelectedCharacter(); - if (selectedCharacter != null) { - using (HorizontalScope(AutoWidth())) { - Space(indent); - Label($"{GetSelectedCharacter().CharacterName}".orange().bold(), AutoWidth()); - Space(5); - Label("will be used for editing ".green()); - } - } - } - } -} \ No newline at end of file diff --git a/ToyBox/classes/Infrastructure/RespecHelper.cs b/ToyBox/classes/Infrastructure/RespecHelper.cs deleted file mode 100644 index 9b849d62d..000000000 --- a/ToyBox/classes/Infrastructure/RespecHelper.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Kingmaker; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.Designers.EventConditionActionSystem.Actions; -using Kingmaker.PubSubSystem; -using Kingmaker.UnitLogic.Parts; -using System; -using System.Collections.Generic; -using System.Linq; -using ModKit; - -namespace ToyBox { - internal class RespecHelper { - - public static List<UnitEntityData> GetRespecableUnits() { - var player = Game.Instance.Player; - var enumerable = player.AllCrossSceneUnits.Where(delegate (UnitEntityData u) { - var unitPartCompanion = u.Get<UnitPartCompanion>(); - if (unitPartCompanion == null || unitPartCompanion.State != CompanionState.InParty) { - var unitPartCompanion2 = u.Get<UnitPartCompanion>(); - if (unitPartCompanion2 == null) { - return false; - } - - return unitPartCompanion2.State == CompanionState.Remote; - } - - return true; - }); - var respecUnits = (from ch in enumerable - where RespecCompanion.CanRespec(ch) - select ch).ToList(); - return respecUnits; - } - - public static void Respec(UnitEntityData unit) { - Mod.Debug("Initiating Respec"); - EventBus.RaiseEvent(delegate (IRespecInitiateUIHandler h) { - h.HandleRespecInitiate(unit, FinishRespec); - }); - - } - - private static void FinishRespec() => Mod.Debug("Finishing Respec");// Maybe Apply Rest Without Advancing Time ? - } -} diff --git a/ToyBox/classes/Infrastructure/SettingsDefaults.cs b/ToyBox/classes/Infrastructure/SettingsDefaults.cs deleted file mode 100644 index f84282481..000000000 --- a/ToyBox/classes/Infrastructure/SettingsDefaults.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Kingmaker.Enums.Damage; -using System; -using System.Collections.Generic; - -namespace ToyBox { - internal static class SettingsDefaults { - public static readonly HashSet<string> DefaultBuffsToIgnoreForDurationMultiplier = new() { - "24cf3deb078d3df4d92ba24b176bda97", //Prone - "e6f2fc5d73d88064583cb828801212f4", //Fatigued - "bb1b849f30e6464284c1efd0e812d626", //Army Nauseated - "f59aa0658cda4c7b82bf73c632a39650", //Army Stinking Cloud - "6179bbe7a7b4b674c813dedbca121799", //Summoned Unit Appear Buff (causes inaction for summoned units) - "12f2f2cf326dfd743b2cce5b14e99b3c", //Resurrection Buff - }; - - public static void InitializeDefaultDamageTypes() { - if (Main.Settings.bulkSellSettings.damageReality.Count == 0) { - foreach (DamageRealityType type in Enum.GetValues(typeof(DamageRealityType))) - Main.Settings.bulkSellSettings.damageReality.Add(type, true); - foreach (DamageAlignment type in Enum.GetValues(typeof(DamageAlignment))) - Main.Settings.bulkSellSettings.damageAlignment.Add(type, true); - foreach (PhysicalDamageMaterial type in Enum.GetValues(typeof(PhysicalDamageMaterial))) - Main.Settings.bulkSellSettings.damageMaterial.Add(type, true); - foreach (DamageEnergyType type in Enum.GetValues(typeof(DamageEnergyType))) - Main.Settings.bulkSellSettings.damageEnergy.Add(type, true); - } - } - } - -} diff --git a/ToyBox/classes/Infrastructure/Teleport.cs b/ToyBox/classes/Infrastructure/Teleport.cs deleted file mode 100644 index 756fd4224..000000000 --- a/ToyBox/classes/Infrastructure/Teleport.cs +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright < 2021 > Narria (github user Cabarius) - License: MIT -// based on code by hambeard (thank you ^_^) -using Kingmaker; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.GameModes; -using Kingmaker.PubSubSystem; -using Kingmaker.View; -using System; -using UnityEngine; -using JetBrains.Annotations; -using Kingmaker.Globalmap.Blueprints; -using Kingmaker.Globalmap.State; -using Kingmaker.Globalmap.View; -using Kingmaker.Globalmap; -using Kingmaker.Utility; -using Kingmaker.EntitySystem.Persistence; -using ModKit; -using UnityModManagerNet; -using Kingmaker.UI.MVVM._PCView.ServiceWindows.LocalMap; -using Kingmaker.Visual.LocalMap; -using Kingmaker.UI.MVVM._VM.ServiceWindows.LocalMap.Utils; -using Kingmaker.Blueprints.Area; -using Kingmaker.Designers; -using System.Linq; - -namespace ToyBox { - public static class Teleport { - public static Settings Settings => Main.Settings; - //private static readonly HoverHandler _hover = new(); - - public static void TeleportUnit(UnitEntityData unit, Vector3 position) { - var view = unit.View; - var localMap = Game.Instance?.UI.Canvas?.transform?.Find("ServiceWindowsPCView/LocalMapPCView"); - if (localMap?.gameObject.activeSelf ?? false) { - var localMapView = localMap.GetComponent<LocalMapPCView>(); - var viewModel = localMapView.ViewModel; - RectTransformUtility.ScreenPointToLocalPointInRectangle(localMapView.m_Image.rectTransform, (Vector2)Input.mousePosition, Game.Instance.UI.UICamera, out var localPoint); - var localPos = localPoint + Vector2.Scale(localMapView.m_Image.rectTransform.sizeDelta, localMapView.m_Image.rectTransform.pivot); - var vector3 = LocalMapRenderer.Instance.ViewportToWorldPoint((Vector3)new Vector2(localPos.x / (float)viewModel.DrawResult.Value.ColorRT.width, localPos.y / (float)viewModel.DrawResult.Value.ColorRT.height)); - if (!LocalMapModel.IsInCurrentArea(vector3)) - vector3 = AreaService.Instance.CurrentAreaPart.Bounds.LocalMapBounds.ClosestPoint(vector3); - Mod.Debug($"PointerPosition - adjusting result {position} to {vector3}"); - Game.Instance.UI.GetCameraRig().ScrollTo(vector3); - position = vector3; - } - - if (view != null) view.StopMoving(); - - unit.Stop(); - - unit.Position = position; - - foreach (var fam in unit.Familiars) { - if (fam) - fam.TeleportToMaster(false); - } - } - - public static void TeleportSelected() { - foreach (var unit in Game.Instance.UI.SelectionManager.SelectedUnits) { - TeleportUnit(unit, Utils.PointerPosition()); - } - } - - public static void TeleportParty() { - foreach (var unit in Game.Instance.Player.m_PartyAndPets) { - TeleportUnit(unit, Utils.PointerPosition()); - } - } - public static void TeleportPartyToPlayer() { - var currentMode = Game.Instance.CurrentMode; - var partyMembers = Game.Instance.Player.m_PartyAndPets; - if (currentMode == GameModeType.Default || currentMode == GameModeType.Pause) { - foreach (var unit in partyMembers) { - if (unit != Game.Instance.Player.MainCharacter.Value) { - unit.Commands.InterruptMove(); - unit.Commands.InterruptMove(); - unit.Position = Game.Instance.Player.MainCharacter.Value.Position; - } - } - } - } - public static void TeleportEveryoneToPlayer() { - var currentMode = Game.Instance.CurrentMode; - if (currentMode == GameModeType.Default || currentMode == GameModeType.Pause) { - foreach (var unit in Game.Instance.State.Units) { - if (unit != Game.Instance.Player.MainCharacter.Value) { - unit.Commands.InterruptMove(); - unit.Commands.InterruptMove(); - unit.Position = Game.Instance.Player.MainCharacter.Value.Position; - } - } - } - } - public static void TeleportPartyOnGlobalMap() { - _ = GlobalMapView.Instance; - var pointerPos = Utils.PointerPosition(); - var pointerTransform = new GameObject().transform; - pointerTransform.position = pointerPos; - var locationToObject = GlobalMapView.Instance.GetNearestLocationToObject(pointerTransform); - locationToObject.Blueprint.TeleportToGlobalMapPoint(); - } - public static void TeleportToGlobalMap(Action callback = null) { - var globalMap = Game.Instance.BlueprintRoot.GlobalMap; - var areaEnterPoint = globalMap.All.FindOrDefault(i => i.Get().GlobalMapEnterPoint != null)?.Get().GlobalMapEnterPoint; - Game.Instance.LoadArea(areaEnterPoint.Area, areaEnterPoint, AutoSaveMode.None, callback: callback ?? (() => { })); - } - public static bool TeleportToGlobalMapPoint(this BlueprintGlobalMapPoint destination) { - if (GlobalMapView.Instance != null) { - var globalMapController = Game.Instance.GlobalMapController; - var globalMapUI = Game.Instance.UI.GlobalMapUI; - var globalMapView = GlobalMapView.Instance; - var globalMapState = Game.Instance.Player.GetGlobalMap(destination.GlobalMap); - - var pointState = Game.Instance.Player.GetGlobalMap(destination.GlobalMap).GetPointState(destination); - pointState.EdgesOpened = true; - pointState.Reveal(); - var pointView = globalMapView.GetPointView(destination); - if ((bool)(UnityEngine.Object)globalMapView) { - if ((bool)(UnityEngine.Object)pointView) - globalMapView.RevealLocation(pointView); - } - foreach (var edge in pointState.Edges) { - edge.UpdateExplored(1f, 1); - globalMapView.GetEdgeView(edge.Blueprint)?.UpdateRenderers(); - - } - globalMapController.StartTravels(); - EventBus.RaiseEvent<IGlobalMapPlayerTravelHandler>(h => h.HandleGlobalMapPlayerTravelStarted(globalMapView.State.Player, false)); - globalMapView.State.Player.SetCurrentPosition(new GlobalMapPosition(destination)); - globalMapView.GetPointView(destination)?.OpenOutgoingEdges((GlobalMapPointView)null); - globalMapView.UpdatePawnPosition(); - globalMapController.Stop(); - EventBus.RaiseEvent<IGlobalMapPlayerTravelHandler>((Action<IGlobalMapPlayerTravelHandler>)(h => h.HandleGlobalMapPlayerTravelStopped((IGlobalMapTraveler)globalMapView.State.Player))); - globalMapView.PlayerPawn?.m_Compass?.TryClear(); - globalMapView.PlayerPawn?.m_Compass?.TrySet(); - return true; - } - return false; - } - - public static void To(this BlueprintAreaEnterPoint enterPoint) => GameHelper.EnterToArea(enterPoint, AutoSaveMode.None); - public static void To(this BlueprintGlobalMap globalMap) => GameHelper.EnterToArea(globalMap.GlobalMapEnterPoint, AutoSaveMode.None); - public static void To(this BlueprintArea area) { - var areaEnterPoints = BlueprintExtensions.BlueprintsOfType<BlueprintAreaEnterPoint>(); - var blueprint = areaEnterPoints.FirstOrDefault(bp => bp is BlueprintAreaEnterPoint ep && ep.Area == area); - if (blueprint is BlueprintAreaEnterPoint enterPoint) { - GameHelper.EnterToArea(enterPoint, AutoSaveMode.None); - } - } - public static void To(this BlueprintGlobalMapPoint globalMapPoint) { - Game.Instance.LoadArea(globalMapPoint.GlobalMap.GlobalMapEnterPoint, AutoSaveMode.None, () => { - TeleportToGlobalMapPoint(globalMapPoint); - }); - //if (!Teleport.TeleportToGlobalMapPoint(globalMapPoint)) { - // Teleport.TeleportToGlobalMap(() => Teleport.TeleportToGlobalMapPoint(globalMapPoint)); - //} - } - - internal class HoverHandler : IUnitDirectHoverUIHandler, IDisposable { - public UnitEntityData Unit { get; private set; } - private UnitEntityData _currentUnit; - - public HoverHandler() { - EventBus.Subscribe(this); - } - public void Dispose() => throw new NotImplementedException(); - - public void HandleHoverChange([NotNull] UnitEntityView unitEntityView, bool isHover) { - if (isHover) _currentUnit = unitEntityView.Data; - } - - public void LockUnit() { - if (_currentUnit != null) - this.Unit = _currentUnit; - } - } - } -} \ No newline at end of file diff --git a/ToyBox/classes/Infrastructure/UIWidgetHelpers.cs b/ToyBox/classes/Infrastructure/UIWidgetHelpers.cs deleted file mode 100644 index 1d77c8946..000000000 --- a/ToyBox/classes/Infrastructure/UIWidgetHelpers.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using TMPro; -using UnityEngine; -using UnityEngine.SceneManagement; -using UnityEngine.UI; -using UnityModManagerNet; -using Kingmaker; -using Kingmaker.GameModes; -using Kingmaker.Localization; -using Kingmaker.Utility; -using Kingmaker.UI; -using Kingmaker.UI.Common; -using Kingmaker.UI.GlobalMap; -using Kingmaker.Globalmap; -using static ToyBox.UIHelpers; - -namespace ToyBox { - public static partial class UIHelpers { - public static WidgetPaths_1_0 WidgetPaths; - public static Transform Settings => SceneManager.GetSceneByName("UI_LoadingScreen_Scene").GetRootGameObjects().First(x => x.name.StartsWith("CommonPCView")).ChildTransform("Canvas/SettingsView"); - public static Transform UIRoot => UIUtility.IsGlobalMap() ? GlobalMapUI.Instance.transform : StaticCanvas.Instance.transform; - public static Transform ServiceWindow => UIUtility.IsGlobalMap() ? UIRoot.Find("ServiceWindowsConfig").transform : UIRoot.Find("ServiceWindowsPCView"); - // We deal with two different cases for finding our UI bits (thanks Owlcat!) - // InGamePCView(Clone)/InGameStaticPartPCView/StaticCanvas/ServiceWindowsPCView - // GlobalMapPCView(Clone)/StaticCanvas/ServiceWindowsConfig - - public static Transform SpellbookScreen => ServiceWindow.Find(WidgetPaths.SpellScreen); - public static Transform MythicInfoView => ServiceWindow.Find(WidgetPaths.MythicView); - public static Transform EncyclopediaView => ServiceWindow.Find(WidgetPaths.EncyclopediaView); - - public static Transform CharacterScreen => ServiceWindow.Find(WidgetPaths.CharacterScreen); - - public static Transform InventoryScreen => ServiceWindow.Find(WidgetPaths.InventoryScreen); - public static Transform LocalMapScreen => ServiceWindow.Find(WidgetPaths.LocalMapScreen); - - public class WidgetPaths_1_0 { - public virtual string SpellScreen => "SpellbookView/SpellbookScreen"; - public virtual string MythicView => "MythicInfoView"; - public virtual string EncyclopediaView => "EncyclopediaView"; - - public virtual string CharacterScreen => "CharacterInfoView/CharacterScreen"; - public virtual string InventoryScreen => throw new NotImplementedException(); // If we ever need to support old stuff then put something here - public virtual string LocalMapScreen => throw new NotImplementedException(); // If we ever need to support old stuff then put something here - } - - class WidgetPaths_1_1 : WidgetPaths_1_0 { - public override string SpellScreen => "SpellbookPCView/SpellbookScreen"; - public override string MythicView => "MythicInfoPCView"; - public override string EncyclopediaView => "EncyclopediaPCView"; - public override string CharacterScreen => "CharacterInfoPCView/CharacterScreen"; - } - - class WidgetPaths_1_2 : WidgetPaths_1_1 { } - - class WidgetPaths_1_4 : WidgetPaths_1_2 { - public override string SpellScreen => "Background/Windows/SpellbookPCView/SpellbookScreen"; - public override string MythicView => "Background/Windows/MythicInfoPCView"; - public override string EncyclopediaView => "Background/Windows/EncyclopediaPCView"; - public override string CharacterScreen => "Background/Windows/CharacterInfoPCView/CharacterScreen"; - } - - class WidgetPaths_2_0 : WidgetPaths_1_4 { - public override string InventoryScreen => "Background/Windows/InventoryPCView"; - public override string LocalMapScreen => "Background/Windows/LocalMapPCView"; - } - - public static void OnLoad() { - if (UnityModManager.gameVersion.Major == 2) { - UIHelpers.WidgetPaths = new WidgetPaths_2_0(); - } - else if (UnityModManager.gameVersion.Major == 1) { - - if (UnityModManager.gameVersion.Minor == 4) - UIHelpers.WidgetPaths = new WidgetPaths_1_4(); - else if (UnityModManager.gameVersion.Minor == 3) - UIHelpers.WidgetPaths = new WidgetPaths_1_2(); - else if (UnityModManager.gameVersion.Minor == 2) - UIHelpers.WidgetPaths = new WidgetPaths_1_2(); - else if (UnityModManager.gameVersion.Minor == 1) - UIHelpers.WidgetPaths = new WidgetPaths_1_1(); - else - UIHelpers.WidgetPaths = new WidgetPaths_1_0(); - } - } - } -} \ No newline at end of file diff --git a/ToyBox/classes/Infrastructure/WrathExtensions.cs b/ToyBox/classes/Infrastructure/WrathExtensions.cs deleted file mode 100644 index 57cbf8f2f..000000000 --- a/ToyBox/classes/Infrastructure/WrathExtensions.cs +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright < 2021 > Narria (github user Cabarius) - License: MIT -using System; -using UnityEngine; -using Kingmaker.Blueprints; -using Kingmaker.Blueprints.Classes; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.UI; -using Kingmaker.UI.Common; -//using Kingmaker.UI.LevelUp.Phase; -using Kingmaker.UnitLogic; -using Alignment = Kingmaker.Enums.Alignment; -using ModKit; -using Kingmaker.UnitLogic.Alignments; -using System.Linq; -using Kingmaker; -using Kingmaker.UI.FullScreenUITypes; -using Kingmaker.Utility; -using Kingmaker.UnitLogic.Mechanics; -using Kingmaker.Assets.UI; - -namespace ModKit { - public partial class UI { - public static string Name(this Alignment a) => UIUtility.GetAlignmentName(a); - public static string Acronym(this Alignment a) => UIUtility.GetAlignmentAcronym(a); - - - public static Alignment[] Alignments = new Alignment[] { - Alignment.LawfulGood, Alignment.NeutralGood, Alignment.ChaoticGood, - Alignment.LawfulNeutral, Alignment.TrueNeutral, Alignment.ChaoticNeutral, - Alignment.LawfulEvil, Alignment.NeutralEvil, Alignment.ChaoticEvil - }; - public static RGBA Color(this Alignment a) => a switch { - Alignment.LawfulGood => RGBA.aqua, - Alignment.NeutralGood => RGBA.lime, - Alignment.ChaoticGood => RGBA.yellow, - Alignment.LawfulNeutral => RGBA.blue, - Alignment.TrueNeutral => RGBA.white, - Alignment.ChaoticNeutral => RGBA.orange, - Alignment.LawfulEvil => RGBA.purple, - Alignment.NeutralEvil => RGBA.fuchsia, - Alignment.ChaoticEvil => RGBA.red, - _ => RGBA.grey, - }; - public static AlignmentMaskType[] AlignmentMasks = new AlignmentMaskType[] { - AlignmentMaskType.None, AlignmentMaskType.Good, AlignmentMaskType.Evil, - AlignmentMaskType.Any, AlignmentMaskType.Lawful, AlignmentMaskType.Chaotic, - AlignmentMaskType.LawfulGood, AlignmentMaskType.NeutralGood, AlignmentMaskType.ChaoticGood, - AlignmentMaskType.LawfulNeutral, AlignmentMaskType.TrueNeutral, AlignmentMaskType.ChaoticNeutral, - AlignmentMaskType.LawfulEvil, AlignmentMaskType.NeutralEvil, AlignmentMaskType.ChaoticEvil, - }; - public static RGBA Color(this AlignmentMaskType a) { - switch (a) { - case AlignmentMaskType.None: return RGBA.grey; - case AlignmentMaskType.Good: return RGBA.lime; - case AlignmentMaskType.Evil: return RGBA.fuchsia; - case AlignmentMaskType.Any: return RGBA.grey; - case AlignmentMaskType.Lawful: return RGBA.blue; ; - case AlignmentMaskType.Chaotic: return RGBA.orange; - case AlignmentMaskType.LawfulGood: return RGBA.aqua; - case AlignmentMaskType.NeutralGood: return RGBA.lime; - case AlignmentMaskType.ChaoticGood: return RGBA.yellow; - case AlignmentMaskType.LawfulNeutral: return RGBA.blue; - case AlignmentMaskType.TrueNeutral: return RGBA.white; - case AlignmentMaskType.ChaoticNeutral: return RGBA.orange; - case AlignmentMaskType.LawfulEvil: return RGBA.purple; - case AlignmentMaskType.NeutralEvil: return RGBA.fuchsia; - case AlignmentMaskType.ChaoticEvil: return RGBA.red; - } - return RGBA.grey; - } - public static AlignmentShiftDirection[] AlignmentShiftDirections = new AlignmentShiftDirection[] { - AlignmentShiftDirection.LawfulGood, - AlignmentShiftDirection.Good, - // AlignmentShiftDirection.NeutralGood, // skip this for clearer UI and avoiding wierd oscilations - AlignmentShiftDirection.ChaoticGood, - AlignmentShiftDirection.Lawful, - //AlignmentShiftDirection.LawfulNeutral, - AlignmentShiftDirection.TrueNeutral, - AlignmentShiftDirection.Chaotic, - //AlignmentShiftDirection.ChaoticNeutral, - AlignmentShiftDirection.LawfulEvil, - AlignmentShiftDirection.Evil, - //AlignmentShiftDirection.NeutralEvil, - AlignmentShiftDirection.ChaoticEvil - }; - public static RGBA Color(this AlignmentShiftDirection a) { - switch (a) { - case AlignmentShiftDirection.Good: return RGBA.lime; - case AlignmentShiftDirection.Evil: return RGBA.fuchsia; - case AlignmentShiftDirection.Lawful: return RGBA.blue; ; - case AlignmentShiftDirection.Chaotic: return RGBA.orange; - case AlignmentShiftDirection.LawfulGood: return RGBA.aqua; - case AlignmentShiftDirection.NeutralGood: return RGBA.lime; - case AlignmentShiftDirection.ChaoticGood: return RGBA.yellow; - case AlignmentShiftDirection.LawfulNeutral: return RGBA.blue; - case AlignmentShiftDirection.TrueNeutral: return RGBA.white; - case AlignmentShiftDirection.ChaoticNeutral: return RGBA.orange; - case AlignmentShiftDirection.LawfulEvil: return RGBA.purple; - case AlignmentShiftDirection.NeutralEvil: return RGBA.fuchsia; - case AlignmentShiftDirection.ChaoticEvil: return RGBA.red; - } - return RGBA.grey; - } - public static void AlignmentGrid(string title, Alignment alignment, Action<Alignment> action, params GUILayoutOption[] options) { - using (HorizontalScope()) { - if (title?.Length > 0) { - Label(title.cyan(), options); - } - var alignmentIndex = Array.IndexOf(Alignments, alignment); - var titles = Alignments.Select( - a => a.Acronym().color(a.Color()).bold()).ToArray(); - if (SelectionGrid(ref alignmentIndex, titles, 3, Width(250f))) { - action(Alignments[alignmentIndex]); - } - } - } - public static void AlignmentGrid(Alignment alignment, Action<Alignment> action, params GUILayoutOption[] options) - => AlignmentGrid(null, alignment, action, options); - } -} - -namespace ToyBox { - public static class WrathExtensions { - public static string HashKey(this UnitEntityData ch) => ch.CharacterName; // + ch.UniqueId; } - public static string HashKey(this UnitDescriptor ch) => ch.CharacterName; - public static string HashKey(this BlueprintCharacterClass cl) => cl.NameSafe(); - public static string HashKey(this BlueprintArchetype arch) => arch.NameSafe(); - - public static string GetDescription(this SimpleBlueprint bp) - // borrowed shamelessly and enhanced from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/26, which is under the MIT License - { - try { - // avoid exceptions on known broken items - var guid = bp.AssetGuid; - if (guid == "b60252a8ae028ba498340199f48ead67" || guid == "fb379e61500421143b52c739823b4082") return null; - var associatedBlueprint = bp as IUIDataProvider; - return associatedBlueprint?.Description?.StripHTML(); - // Why did BoT do this instead of the above which is what MechanicsContext.SelectUIData() does for description -#if false - var description = associatedBlueprint.Description; - UnitReference mainChar = Game.Instance.Player.MainCharacter; - if (mainChar == null) { return ""; } - MechanicsContext context = new MechanicsContext((UnitEntityData)null, mainChar.Value.Descriptor, bp, (MechanicsContext)null, (TargetWrapper)null); - return context?.SelectUIData(UIDataType.Description)?.Description ?? ""; -#endif - } - catch (Exception e) { - Console.Write($"{e}"); -#if DEBUG - return "ERROR".red().bold() + $": caught exception {e}"; -#else - return ""; -#endif - } - } - public static UnitEntityData GetCurrentCharacter() { - var firstSelectedUnit = Game.Instance.SelectionCharacter.FirstSelectedUnit; - return (object)firstSelectedUnit != null ? firstSelectedUnit : (UnitEntityData)Game.Instance.Player.MainCharacter; - } - } -} diff --git a/ToyBox/classes/MainUI/BagOfTricks.cs b/ToyBox/classes/MainUI/BagOfTricks.cs deleted file mode 100644 index e710a01f3..000000000 --- a/ToyBox/classes/MainUI/BagOfTricks.cs +++ /dev/null @@ -1,740 +0,0 @@ -// Copyright < 2021 > Narria (github user Cabarius) - License: MIT - -using Kingmaker; -using Kingmaker.Cheats; -using Kingmaker.Controllers; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.Kingdom; -using Kingmaker.PubSubSystem; -using Kingmaker.UnitLogic; -using Kingmaker.View; -using ModKit; -using System; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; -using UnityModManagerNet; -using static ModKit.UI; - -namespace ToyBox { - public static class BagOfTricks { - public static Settings settings => Main.Settings; - - // cheats combat - private const string RestAll = "Rest All"; - private const string RestSelected = "Rest Selected"; - private const string Empowered = "Empowered"; - private const string FullBuffPlease = "Common Buffs"; - private const string GoddesBuffs = "Buff Like A Goddess"; - private const string RemoveBuffs = "Remove Buffs"; - private const string RemoveDeathsDoor = "Remove Deaths Door"; - private const string KillAllEnemies = "Kill All Enemies"; - //private const string SummonZoo = "Summon Zoo" - private const string LobotomizeAllEnemies = "Lobotomize Enemies"; - private const string ToggleMurderHobo = "Toggle Murder Hobo"; - - // cheats common - private const string TeleportPartyToYou = "Teleport Party To You"; - private const string GoToGlobalMap = "Go To Global Map"; - private const string RerollPerception = "Reroll Perception"; - private const string RerollInteractionSkillChecks = "Reset Interactables"; - private const string ChangeParty = "Change Party"; - private const string ChangWeather = "Change Weather"; - - // other - private const string TimeScaleMultToggle = "Main/Alt Timescale"; - private const string PreviewDialogResults = "Preview Results"; - private const string ResetAdditionalCameraAngles = "Fix Camera"; - - //For buffs exceptions - private static bool showBuffDurationExceptions = false; - - public static void OnLoad() { - // Combat - KeyBindings.RegisterAction(RestAll, () => CheatsCombat.RestAll()); - KeyBindings.RegisterAction(RestSelected, () => Actions.RestSelected()); - KeyBindings.RegisterAction(Empowered, () => CheatsCombat.Empowered("")); - KeyBindings.RegisterAction(FullBuffPlease, () => CheatsCombat.FullBuffPlease("")); - KeyBindings.RegisterAction(GoddesBuffs, () => CheatsCombat.Iddqd("")); - KeyBindings.RegisterAction(RemoveBuffs, () => Actions.RemoveAllBuffs()); - KeyBindings.RegisterAction(RemoveDeathsDoor, () => CheatsCombat.DetachDebuff()); - KeyBindings.RegisterAction(KillAllEnemies, () => Actions.KillAll()); - //KeyBindings.RegisterAction(SummonZoo, () => CheatsCombat.SpawnInspectedEnemiesUnderCursor("")); - KeyBindings.RegisterAction(LobotomizeAllEnemies, () => Actions.LobotomizeAllEnemies()); - // Common - KeyBindings.RegisterAction(TeleportPartyToYou, () => Teleport.TeleportPartyToPlayer()); - KeyBindings.RegisterAction(GoToGlobalMap, () => Teleport.TeleportToGlobalMap()); - KeyBindings.RegisterAction(RerollPerception, () => Actions.RunPerceptionTriggers()); - KeyBindings.RegisterAction(RerollInteractionSkillChecks, () => Actions.RerollInteractionSkillChecks()); - KeyBindings.RegisterAction(ChangeParty, () => { Actions.ChangeParty(); }); - KeyBindings.RegisterAction(ChangWeather, () => CheatsCommon.ChangeWeather("")); - // Other - KeyBindings.RegisterAction(TimeScaleMultToggle, - () => { - settings.useAlternateTimeScaleMultiplier = !settings.useAlternateTimeScaleMultiplier; - Actions.ApplyTimeScale(); - }, - title => ToggleTranscriptForState(title, settings.useAlternateTimeScaleMultiplier) - ); - KeyBindings.RegisterAction(PreviewDialogResults, () => { - settings.previewDialogResults = !settings.previewDialogResults; - var dialogController = Game.Instance.DialogController; - }); - KeyBindings.RegisterAction(ResetAdditionalCameraAngles, () => { - Main.resetExtraCameraAngles = true; - }); - KeyBindings.RegisterAction(ToggleMurderHobo, - () => settings.togglekillOnEngage = !settings.togglekillOnEngage, - title => ToggleTranscriptForState(title, settings.togglekillOnEngage) - ); - } - public static void ResetGUI() { } - public static void OnGUI() { -#if BUILD_CRUI - ActionButton("Demo crUI", () => ModKit.crUI.Demo()); -#endif - if (Main.IsInGame) { - BeginHorizontal(); - Space(25); - Label("increment".localize().cyan(), AutoWidth()); - var increment = IntTextField(ref settings.increment, null, Width(150)); - EndHorizontal(); - var mainChar = Game.Instance.Player.MainCharacter.Value; - var kingdom = KingdomState.Instance; - HStack("Resources".localize(), 1, - () => { - var money = Game.Instance.Player.Money; - Label("Gold".localize().cyan(), Width(150)); - Label(money.ToString().orange().bold(), Width(200)); - ActionButton("Gain ".localize() + $"{increment}", () => Game.Instance.Player.GainMoney(increment), AutoWidth()); - ActionButton("Lose ".localize() + $"{increment}", () => { - var loss = Math.Min(money, increment); - Game.Instance.Player.GainMoney(-loss); - }, AutoWidth()); - }, - () => { - var exp = mainChar.Progression.Experience; - Label("Experience".localize().cyan(), Width(150)); - Label(exp.ToString().orange().bold(), Width(200)); - ActionButton("Gain ".localize() + $"{increment}", () => { - Game.Instance.Player.GainPartyExperience(increment); - }, AutoWidth()); - }, - () => { - var corruption = Game.Instance.Player.Corruption; - Label("Corruption".localize().cyan(), Width(150)); - Label(corruption.CurrentValue.ToString().orange().bold(), Width(200)); - ActionButton($"Clear".localize(), () => corruption.Clear(), AutoWidth()); - 25.space(); - Toggle("Disable Corruption".localize(), ref settings.toggleDisableCorruption); - }, - () => { } - ); - } - Div(0, 25); - HStack("Combat".localize(), 2, - () => BindableActionButton(RestAll, true), - () => BindableActionButton(RestSelected, true), - () => BindableActionButton(FullBuffPlease, true), - () => BindableActionButton(Empowered, true), - () => BindableActionButton(GoddesBuffs, true), - () => BindableActionButton(RemoveBuffs, true), - () => BindableActionButton(RemoveDeathsDoor, true), - () => BindableActionButton(KillAllEnemies, true), - //() => UI.BindableActionButton(SummonZoo), - () => BindableActionButton(LobotomizeAllEnemies, true), - () => { }, - () => { - using (VerticalScope()) { - using (HorizontalScope()) { - using (VerticalScope(220.width())) { - using (HorizontalScope()) { - Toggle(("Be a " + "Murder".red().bold() + " Hobo".orange()).localize(), ref settings.togglekillOnEngage, 222.width()); - KeyBindPicker(ToggleMurderHobo, "", 50); - } - } - 158.space(); - Label(("If ticked, this will " + "MURDER".red().bold() + " all who dare to engage you!".green()).localize(), AutoWidth()); - } - using (HorizontalScope()) { - if (Toggle("Log ToyBox Keyboard Commands In Game".localize(), ref Mod.ModKitSettings.toggleKeyBindingsOutputToTranscript, 450.width())) - ModKitSettings.Save(); - 50.space(); - HelpLabel("When ticked this shows ToyBox commands in the combat log which is helpful for you to know when you used the shortcut".localize()); - } - } - } - ); - Div(0, 25); - HStack("Teleport".localize(), 2, - () => BindableActionButton(TeleportPartyToYou, true), - () => { - Toggle("Enable Teleport Keys".localize(), ref settings.toggleTeleportKeysEnabled); - Space(100); - if (settings.toggleTeleportKeysEnabled) { - using (VerticalScope()) { - KeyBindPicker("TeleportMain", "Main Character".localize(), 0, 200); - KeyBindPicker("TeleportSelected", "Selected Chars".localize(), 0, 200); - KeyBindPicker("TeleportParty", "Whole Party".localize(), 0, 200); - } - } - Space(25); - Label("You can enable hot keys to teleport members of your party to your mouse cursor on Area or the Global Map".localize().green()); - }); - Div(0, 25); - HStack("Common".localize(), 2, - () => BindableActionButton(GoToGlobalMap, true), - () => { - BindableActionButton(ChangeParty, true); - Space(-75); - HelpLabel("Change the party without advancing time (good to bind)".localize()); - }, - () => BindableActionButton(RerollPerception, true), - () => { - BindableActionButton(RerollInteractionSkillChecks, true); - Space(-75); - Label("This resets all the skill check rolls for all interactable objects in the area".localize().green()); - }, - () => { - NonBindableActionButton("Set Perception to 40".localize(), () => { - CheatsCommon.StatPerception(); - Actions.RunPerceptionTriggers(); - }); - }, - () => BindableActionButton(ChangWeather, true), - () => NonBindableActionButton("Give All Items".localize(), () => CheatsUnlock.CreateAllItems("")), - () => NonBindableActionButton("Identify All".localize(), () => Actions.IdentifyAll()), - () => { } - ); - Div(0, 25); - HStack("Preview".localize(), 0, () => { - Toggle("Dialog Results".localize(), ref settings.previewDialogResults); - Space(25); - Toggle("Dialog Alignment".localize(), ref settings.previewAlignmentRestrictedDialog); - Space(25); - Toggle("Random Encounters".localize(), ref settings.previewRandomEncounters); - Space(25); - Toggle("Events".localize(), ref settings.previewEventResults); - Space(25); - Toggle("Decrees".localize(), ref settings.previewDecreeResults); - Space(25); - Toggle("Relic Info".localize(), ref settings.previewRelicResults); - Space(25); - BindableActionButton(PreviewDialogResults, true); - }); - Div(0, 25); - HStack("Dialog".localize(), 1, - () => { - Toggle(("♥♥ ".red() + "Love is Free".bold() + " ♥♥".red()).localize(), ref settings.toggleAllowAnyGenderRomance, 300.width()); - 25.space(); - Label(("Allow ".green() + "any gender".color(RGBA.purple) + " " + "for any ".green() + "R".color(RGBA.red) + "o".orange() + "m".yellow() + "a".green() + "n".cyan() + "c".color(RGBA.rare) + "e".color(RGBA.purple)).localize()); - }, - () => { - Toggle("Jealousy Begone!".localize().bold(), ref settings.toggleMultipleRomance, 300.width()); - 25.space(); - Label(("Allow ".green() + "multiple".color(RGBA.purple) + " romances at the same time".green()).localize()); - }, - () => { - Toggle("Friendship is Magic".localize().bold(), ref settings.toggleFriendshipIsMagic, 300.width()); - 25.space(); - Label("Experimental ".localize().orange() + " your friends forgive even your most vile choices.".localize().green()); - }, - () => { - Toggle("Disallow Companions Leaving Party".localize(), ref settings.toggleBlockUnrecruit, 300.width()); - 200.space(); - Label("Warning: ".localize().color(RGBA.red) + " Only use when Friendship is Magic doesn't work, and then turn off immediately after. Can otherwise break your save".localize().orange()); - }, - () => { - Toggle("Previously Chosen Dialog Is Smaller ".localize(), ref settings.toggleMakePreviousAnswersMoreClear, 300.width()); - 200.space(); - Label("Draws dialog choices that you have previously selected in smaller type".localize().green()); - }, - () => { - Toggle("Expand Dialog To Include Remote Companions".localize().bold(), ref settings.toggleRemoteCompanionDialog, 300.width()); - 200.space(); - Label("Experimental".localize().orange() + " Allow remote companions to make comments on dialog you are having.".localize().green()); - }, - () => { - if (settings.toggleRemoteCompanionDialog) { - 50.space(); - Toggle("Include Former Companions".localize(), ref settings.toggleExCompanionDialog); - 175.space(); - Label("This also includes companions who left the party such as Wenduag if you picked Lann".localize().green()); - } - }, - () => { - using (VerticalScope(300.width())) { - Toggle("Expand Answers For Conditional Responses".localize(), ref settings.toggleShowAnswersForEachConditionalResponse); - if (settings.toggleShowAnswersForEachConditionalResponse) { - using (HorizontalScope()) { - 50.space(); - Toggle("Show Unavailable Responses".localize(), ref settings.toggleShowAllAnswersForEachConditionalResponse); - } - } - } - 75.space(); - Label("Some responses such as comments about your mythic powers will always choose the first one by default. This will show a copy of the answer and the condition for each possible response that an NPC might make to you based on".localize().green()); - }, -#if DEBUG - () => { - Toggle("Randomize NPC Responses To Dialog Choices", ref settings.toggleRandomizeCueSelections, 300.width()); - 200.space(); - Label("Some responses such as comments about your mythic powers will always choose the first one by default. This allows the game to mix things up a bit".green() + "\nWarning:".yellow().bold() + " this will introduce randomness to NPC responses to you in general and may lead to surprising or even wild outcomes".orange()); - }, -#endif - () => Toggle("Disable Dialog Restrictions (Alignment)".localize(), ref settings.toggleDialogRestrictions), - () => Toggle("Disable Dialog Restrictions (Mythic Path)".localize(), ref settings.toggleDialogRestrictionsMythic), - () => Toggle("Ignore Event Solution Restrictions".localize(), ref settings.toggleIgnoreEventSolutionRestrictions), -#if DEBUG - () => Toggle("Disable Dialog Restrictions (Everything, Experimental)", ref settings.toggleDialogRestrictionsEverything), -#endif - () => { } - ); - Div(0, 25); - HStack("Quality of Life".localize(), 1, - () => { - Toggle("Allow Achievements While Using Mods".localize(), ref settings.toggleAllowAchievementsDuringModdedGame, 500.width()); - Label("This is intended for you to be able to enjoy the game while using mods that enhance your quality of life. Please be mindful of the player community and avoid using this mod to trivialize earning prestige achievements like Sadistic Gamer. The author is in discussion with Owlcat about reducing the scope of achievement blocking to just these. Let's show them that we as players can mod and cheat responsibly.".localize().orange()); - }, - // () => { if (Toggle("Expanded Party View", ref settings.toggleExpandedPartyView)) PartyVM_Patches.Repatch(), - () => { - Toggle("Enhanced Map View".localize(), ref settings.toggleZoomableLocalMaps, 500.width()); - HelpLabel("Makes mouse zoom works for the local map (cities, dungeons, etc). Game restart required if you turn it off".localize()); - }, - () => Toggle("Object Highlight Toggle Mode".localize(), ref settings.highlightObjectsToggle), - () => { - Toggle("Mark Interesting NPCs".localize(), ref settings.toggleShowInterestingNPCsOnLocalMap, 500.width()); - HelpLabel("This will change the color of NPC names on the highlike makers and change the color map markers to indicate that they have interesting or conditional interactions".localize()); - }, - () => Toggle("Make game continue to play music on lost focus".localize(), ref settings.toggleContinueAudioOnLostFocus), - () => Toggle("Highlight Copyable Scrolls".localize(), ref settings.toggleHighlightCopyableScrolls), - () => { - Toggle("Auto load Last Save on launch".localize(), ref settings.toggleAutomaticallyLoadLastSave, 500.width()); - HelpLabel("Hold down shift during launch to bypass".localize()); - }, - () => Toggle(("Game Over Fix For " + "LEEEROOOOOOOYYY JEEEENKINS!!!".color(RGBA.maroon) + " omg he just ran in!").localize(), ref settings.toggleGameOverFixLeeerrroooooyJenkins), - () => { - 503.space(); - HelpLabel("Prevents dumb companions (that's you Greybor) from wiping the party by running running into the dragon room and dying...".localize()); - }, - () => Toggle("Make Spell/Ability/Item Pop-Ups Wider ".localize(), ref settings.toggleWidenActionBarGroups), - () => { - if (Toggle("Show Acronyms in Spell/Ability/Item Pop-Ups".localize(), ref settings.toggleShowAcronymsInSpellAndActionSlots)) { - Main.SetNeedsResetGameUI(); - } - }, - () => { - Toggle("Icky Stuff Begone!!!".localize(), ref settings.toggleReplaceModelMenu, (settings.toggleReplaceModelMenu ? 248 : 499).width()); - if (settings.toggleReplaceModelMenu) { - using (VerticalScope(Width(247))) { - Toggle("Spiders Begone!".localize(), ref settings.toggleSpiderBegone); - Toggle("Vescavors Begone!".localize(), ref settings.toggleVescavorsBegone); - Toggle("Retrievers Begone!".localize(), ref settings.toggleRetrieversBegone); - Toggle("Deraknis Begone!".localize(), ref settings.toggleDeraknisBegone); - Toggle("Deskari Begone!".localize(), ref settings.toggleDeskariBegone); - } - } - Label("Some players find spiders and other swarms icky. This replaces them with something more pleasant".localize().green()); - }, - () => Toggle("Make tutorials not appear if disabled in settings".localize(), ref settings.toggleForceTutorialsToHonorSettings), - () => Toggle("Refill consumables in belt slots if in inventory".localize(), ref settings.togglAutoEquipConsumables), - () => { - var modifier = KeyBindings.GetBinding("InventoryUseModifier"); - var modifierText = modifier.Key == KeyCode.None ? "Modifer" : modifier.ToString(); - Toggle("Allow ".localize() + $"{modifierText}".cyan() + (" + Click".cyan() + " To Use Items In Inventory").localize(), ref settings.toggleShiftClickToUseInventorySlot, 470.width()); - if (settings.toggleShiftClickToUseInventorySlot) { - ModifierPicker("InventoryUseModifier", "", 0); - } - }, - () => { - var modifier = KeyBindings.GetBinding("ClickToTransferModifier"); - var modifierText = modifier.Key == KeyCode.None ? "Modifer" : modifier.ToString(); - Toggle("Allow ".localize() + $"{modifierText}".cyan() + (" + Click".cyan() + " To Transfer Entire Stack").localize(), ref settings.toggleShiftClickToFastTransfer, 470.width()); - if (settings.toggleShiftClickToFastTransfer) { - ModifierPicker("ClickToTransferModifier", "", 0); - } - }, - () => Toggle("Respec Refund Scrolls".localize(), ref settings.toggleRespecRefundScrolls), - () => { - Toggle("Make Puzzle Symbols More Clear".localize(), ref settings.togglePuzzleRelief); - 25.space(); - HelpLabel(("ToyBox Archeologists can tag confusing puzzle pieces with green numbers in the game world and for inventory tool tips it will show text like this: " + "[PuzzlePiece Green3x1]".yellow().bold() + "\nNOTE: ".orange().bold() + "Needs game restart to take efect".orange()).localize()); - }, - () => { - ActionButton("Clear Action Bar".localize(), () => Actions.ClearActionBar()); - 50.space(); - Label("Make sure you have auto-fill turned off in settings or else this will just reset to default".localize().green()); - }, - () => ActionButton("Fix Incorrect Main Character".localize(), () => { - var probablyPlayer = Game.Instance.Player?.Party? - .Where(x => !x.IsCustomCompanion()) - .Where(x => !x.IsStoryCompanion()).ToList(); - if (probablyPlayer is { Count: 1 }) { - var newMainCharacter = probablyPlayer.First(); - var text = "Promoting % to main character!".localize().Split('%'); - Mod.Warn($"{text[0]}{newMainCharacter.CharacterName}{text[1]}"); - if (Game.Instance != null) Game.Instance.Player.MainCharacter = newMainCharacter; - } - }, AutoWidth()), - () => { Toggle("Enable Loading with Blueprint Errors".localize().color(RGBA.maroon), ref settings.enableLoadWithMissingBlueprints); 25.space(); Label($"This {"incredibly dangerous".bold()} setting overrides the default behavior of failing to load saves depending on missing blueprint mods. This desperate action can potentially enable you to recover your saved game, though you'll have to respec at minimum.".localize().orange()); }, - () => { - if (settings.enableLoadWithMissingBlueprints) { - Label("To permanently remove these modded blueprint dependencies, load the damaged saved game, change areas, and then save the game. You can then respec any characters that were impacted.".localize().orange()); - } - }, - () => { - using (VerticalScope()) { - Div(0, 25, 1280); - var useAlt = settings.useAlternateTimeScaleMultiplier; - var mainTimeScaleTitle = "Game Time Scale".localize(); - if (useAlt) mainTimeScaleTitle = mainTimeScaleTitle.grey(); - var altTimeScaleTitle = "Alternate Time Scale".localize(); - if (!useAlt) altTimeScaleTitle = altTimeScaleTitle.grey(); - using (HorizontalScope()) { - LogSlider(mainTimeScaleTitle, ref settings.timeScaleMultiplier, 0f, 20, 1, 1, "", Width(450)); - Space(25); - Label("Speeds up or slows down the entire game (movement, animation, everything)".localize().green()); - } - using (HorizontalScope()) { - LogSlider(altTimeScaleTitle, ref settings.alternateTimeScaleMultiplier, 0f, 20, 5, 1, "", Width(450)); - } - using (HorizontalScope()) { - BindableActionButton(TimeScaleMultToggle, true); - Space(-95); - Label("Bindable hot key to swap between main and alternate time scale multipliers".localize().green()); - } - Div(0, 25, 1280); - } - }, - () => Slider("Turn Based Combat Delay".localize(), ref settings.turnBasedCombatStartDelay, 0f, 4f, 4f, 1, "", Width(450)), - () => { - using (VerticalScope()) { - - using (HorizontalScope()) { - using (VerticalScope()) { - Div(0, 25, 1280); - if (Toggle("Enable Brutal Unfair Difficulty".localize(), ref settings.toggleBrutalUnfair)) { - EventBus.RaiseEvent<IDifficultyChangedClassHandler>((Action<IDifficultyChangedClassHandler>)(h => { - h.HandleDifficultyChanged(); - Main.SetNeedsResetGameUI(); - })); - } - Space(15); - Label("This allows you to play with the originally released Unfair difficulty. ".localize().green() + ("Note:".orange().bold() + "This Unfair difficulty was bugged and applied the intended difficulty modifers twice. ToyBox allows you to keep playing at this Brutal difficulty level and beyond. Use the slider below to select your desired Brutality Level".green()).localize(), Width(1200)); - Space(15); - using (HorizontalScope()) { - if (Slider("Brutality Level".localize(), ref settings.brutalDifficultyMultiplier, 1f, 8f, 2f, 1, "", Width(450))) { - EventBus.RaiseEvent<IDifficultyChangedClassHandler>((Action<IDifficultyChangedClassHandler>)(h => { - h.HandleDifficultyChanged(); - Main.SetNeedsResetGameUI(); - })); - } - Space(25); - var brutaltiy = settings.brutalDifficultyMultiplier; - string label; - var suffix = Math.Abs(brutaltiy - Math.Floor(brutaltiy)) <= float.Epsilon ? "" : "+"; - switch (brutaltiy) { - case float level when level < 2.0: - label = $"Unfair{suffix}".Rarity(RarityType.Common); - break; - case float level when level < 3.0: - label = $"Brutal{suffix}"; - break; - default: - var rarity = (RarityType)brutaltiy; - label = $"{rarity}{suffix}".Rarity(rarity); - break; - } - using (VerticalScope(AutoWidth())) { - Space(UnityModManager.UI.Scale(3)); - Label(label.localize().bold(), largeStyle, AutoWidth()); - } - } - Space(-10); - } - } - } - }, - () => { } - ); - Div(0, 25); - HStack("Camera".localize(), - 1, - () => Toggle("Enable Zoom on all maps and cutscenes".localize(), ref settings.toggleZoomOnAllMaps), - () => { - Toggle("Enable Rotate on all maps and cutscenes".localize(), ref settings.toggleRotateOnAllMaps); - 153.space(); - Label(("Note:".orange() + " For cutscenes and some situations the rotation keys are disabled so you have to hold down Mouse3 to drag in order to get rotation".green()).localize()); - }, - () => { - Toggle("Alt + Mouse Wheel To Adjust Clip Plane".localize(), ref settings.toggleUseAltMouseWheelToAdjustClipPlane); - }, - () => { - Toggle("Ctrl + Mouse3 Drag To Adjust Camera Elevation".localize(), ref settings.toggleCameraElevation); - 25.space(); - Toggle("Free Camera".localize(), ref settings.toggleFreeCamera); - }, - () => Label("Rotation".localize().cyan()), - () => { - 50.space(); - if (Toggle("Allow Mouse3 Drag to adjust Camera Tilt".localize(), ref settings.toggleCameraPitch)) { - Main.resetExtraCameraAngles = true; - } - 100.space(); - Label(("Experimental".orange() + " This allows you to adjust pitch (Camera Tilt) by holding down Mouse3 (which previously just rotated).".green() + " Note:".orange() + " Holding alt while Mouse3 dragging lets you move the camera location.".green()).localize()); - }, - () => { - 50.space(); - Label("Mouse:".localize().cyan(), 125.width()); - 25.space(); - Toggle("Invert X Axis".localize(), ref settings.toggleInvertXAxis); - if (settings.toggleCameraPitch) { - 25.space(); - Toggle("Invert Y Axis".localize(), ref settings.toggleInvertYAxis); - } - }, - () => { - 50.space(); - Label("Keyboard:".localize().cyan(), 125.width()); - 25.space(); - Toggle("Invert X Axis".localize(), ref settings.toggleInvertKeyboardXAxis); - }, - () => { - 50.space(); - BindableActionButton(ResetAdditionalCameraAngles, true); - }, - () => LogSlider("Field Of View".localize(), ref settings.fovMultiplier, 0.4f, 5.0f, 1, 2, "", AutoWidth()), - () => LogSlider("FoV (Cut Scenes)".localize(), ref settings.fovMultiplierCutScenes, 0.4f, 5.0f, 1, 2, "", AutoWidth()), - () => { } - ); - Div(0, 25); - HStack("Alignment".localize(), 1, - () => { Toggle("Fix Alignment Shifts".localize(), ref settings.toggleAlignmentFix); Space(119); Label("Makes alignment shifts towards pure good/evil/lawful/chaotic only shift on those axes".localize().green()); }, - () => { Toggle("Prevent Alignment Changes".localize(), ref settings.togglePreventAlignmentChanges); Space(25); Label("See Party Editor for more fine grained alignment locking per character".localize().green()); }, - () => { } - ); - Div(0, 25); - HStack("Cheats".localize(), 1, - () => Toggle("Unlimited Stacking of Modifiers (Stat/AC/Hit/Damage/Etc)".localize(), ref settings.toggleUnlimitedStatModifierStacking), - () => { - using (HorizontalScope()) { - ToggleCallback("Highlight Hidden Objects".localize(), ref settings.highlightHiddenObjects, Actions.UpdateHighlights); - if (settings.highlightHiddenObjects) { - Space(100); - ToggleCallback("In Fog Of War ".localize(), ref settings.highlightHiddenObjectsInFog, Actions.UpdateHighlights); - } - } - }, - () => Toggle("Infinite Abilities".localize(), ref settings.toggleInfiniteAbilities), - () => Toggle("Infinite Spell Casts".localize(), ref settings.toggleInfiniteSpellCasts), - () => Toggle("No Material Components".localize(), ref settings.toggleMaterialComponent), - () => Toggle("Disable Party Negative Levels".localize(), ref settings.togglePartyNegativeLevelImmunity), - () => Toggle("Disable Party Ability Damage".localize(), ref settings.togglePartyAbilityDamageImmunity), - () => Toggle("Disable Attacks of Opportunity".localize(), ref settings.toggleAttacksofOpportunity), - () => Toggle("Unlimited Actions During Turn".localize(), ref settings.toggleUnlimitedActionsPerTurn), - () => Toggle("Infinite Charges On Items".localize(), ref settings.toggleInfiniteItems), - - () => Toggle("Instant Cooldown".localize(), ref settings.toggleInstantCooldown), - - () => Toggle("Spontaneous Caster Scroll Copy".localize(), ref settings.toggleSpontaneousCopyScrolls), - - () => Toggle("Disable Equipment Restrictions".localize(), ref settings.toggleEquipmentRestrictions), - () => Toggle("Disable Armor Max Dexterity".localize(), ref settings.toggleIgnoreMaxDexterity), - () => Toggle("Disable Armor Speed Reduction".localize(), ref settings.toggleIgnoreSpeedReduction), - () => Toggle("Disable Armor & Shield Arcane Spell Failure".localize(), ref settings.toggleIgnoreSpellFailure), - () => Toggle("Disable Armor & Shield Checks Penalty".localize(), ref settings.toggleIgnoreArmorChecksPenalty), - - () => Toggle("No Friendly Fire On AOEs".localize(), ref settings.toggleNoFriendlyFireForAOE), - () => Toggle("Free Meta-Magic".localize(), ref settings.toggleMetamagicIsFree), - - () => Toggle("No Fog Of War".localize(), ref settings.toggleNoFogOfWar), - () => Toggle("Restore Spells & Skills After Combat".localize(), ref settings.toggleRestoreSpellsAbilitiesAfterCombat), - //() => UI.Toggle("Recharge Items After Combat", ref settings.toggleRechargeItemsAfterCombat), - //() => UI.Toggle("Access Remote Characters", ref settings.toggleAccessRemoteCharacters,0), - //() => UI.Toggle("Show Pet Portraits", ref settings.toggleShowAllPartyPortraits,0), - () => Toggle("Instant Rest After Combat".localize(), ref settings.toggleInstantRestAfterCombat), - () => Toggle("Instant change party members".localize(), ref settings.toggleInstantChangeParty), - () => ToggleCallback("Equipment No Weight".localize(), ref settings.toggleEquipmentNoWeight, BagOfPatches.Tweaks.NoWeight_Patch1.Refresh), - () => Toggle("Allow Item Use From Inventory During Combat".localize(), ref settings.toggleUseItemsDuringCombat), - () => Toggle("Ignore Alignment Requirements for Abilities".localize(), ref settings.toggleIgnoreAbilityAlignmentRestriction), - () => Toggle("Ignore all Requirements for Abilities".localize(), ref settings.toggleIgnoreAbilityAnyRestriction), - () => Toggle("Ignore Pet Sizes For Mounting".localize(), ref settings.toggleMakePetsRidable), - () => Toggle("Ride Any Unit As Your Mount".localize(), ref settings.toggleRideAnything), - () => { } - ); - Div(153, 25); - HStack("", 1, - () => EnumGrid("Disable Attacks Of Opportunity".localize(), ref settings.noAttacksOfOpportunitySelection, true, AutoWidth()), - () => EnumGrid("Can Move Through".localize(), ref settings.allowMovementThroughSelection, true, AutoWidth()), - () => { - Space(328); Label("This allows characters you control to move through the selected category of units during combat".localize().green(), AutoWidth()); - } -#if false - () => { UI.Slider("Collision Radius Multiplier", ref settings.collisionRadiusMultiplier, 0f, 2f, 1f, 1, "", UI.AutoWidth()); }, -#endif - ); - Div(0, 25); - HStack("Class Specific".localize(), 1, - () => Slider("Kineticist: Burn Reduction".localize(), ref settings.kineticistBurnReduction, 0, 30, 0, "", AutoWidth()), - () => Slider("Arcanist: Spell Slot Multiplier".localize(), ref settings.arcanistSpellslotMultiplier, 0.5f, 10f, - 1f, 1, "", AutoWidth()), - () => { - Space(25); - Label("Please rest after adjusting to recalculate your spell slots.".localize().green()); - }, - () => Toggle("Witch/Shaman: Cackling/Shanting Extends Hexes By 10 Min (Out Of Combat)".localize(), ref settings.toggleExtendHexes), - () => Toggle("Allow Simultaneous Activatable Abilities (Like Judgements)".localize(), ref settings.toggleAllowAllActivatable), - () => Toggle("Kineticist: Allow Gather Power Without Hands".localize(), ref settings.toggleKineticistGatherPower), - () => Toggle("Barbarian: Auto Start Rage When Entering Combat".localize(), ref settings.toggleEnterCombatAutoRage), - () => Toggle("Demon: Auto Start Rage When Entering Combat".localize(), ref settings.toggleEnterCombatAutoRageDemon), - () => Toggle("Magus: Always Allow Spell Combat".localize(), ref settings.toggleAlwaysAllowSpellCombat), - () => { } - ); - Div(0, 25); - HStack("Experience Multipliers".localize(), 1, - () => LogSlider("All Experience".localize(), ref settings.experienceMultiplier, 0f, 100f, 1, 1, "", AutoWidth()), - () => { - using (HorizontalScope()) { - Toggle("Override for Combat".localize(), ref settings.useCombatExpSlider, Width(275)); - if (settings.useCombatExpSlider) { - Space(10); - LogSliderCustomLabelWidth("", ref settings.experienceMultiplierCombat, 0f, 100f, 1, 1, "", 12, AutoWidth()); - } - } - }, - () => { - using (HorizontalScope()) { - Toggle("Override for Quests".localize(), ref settings.useQuestsExpSlider, Width(275)); - if (settings.useQuestsExpSlider) { - Space(10); - LogSliderCustomLabelWidth("", ref settings.experienceMultiplierQuests, 0f, 100f, 1, 1, "", 12, AutoWidth()); - } - } - }, - () => { - using (HorizontalScope()) { - Toggle("Override for Skill Checks".localize(), ref settings.useSkillChecksExpSlider, Width(275)); - if (settings.useSkillChecksExpSlider) { - Space(10); - LogSliderCustomLabelWidth("", ref settings.experienceMultiplierSkillChecks, 0f, 100f, 1, 1, "", 12, AutoWidth()); - } - } - }, - () => { - using (HorizontalScope()) { - Toggle("Override for Traps".localize(), ref settings.useTrapsExpSlider, Width(275)); - if (settings.useTrapsExpSlider) { - Space(10); - LogSliderCustomLabelWidth("", ref settings.experienceMultiplierTraps, 0f, 100f, 1, 1, "", 12, AutoWidth()); - } - } - } - ); - Div(0, 25); - HStack("Other Multipliers".localize(), 1, - () => { - LogSlider("Fog of War Range".localize(), ref settings.fowMultiplier, 0f, 100f, 1, 1, "", AutoWidth()); - List<UnitEntityData> units = Game.Instance?.Player?.m_PartyAndPets; - if (units != null) { - foreach (var unit in units) { - FogOfWarController.VisionRadiusMultiplier = settings.fowMultiplier; - FogOfWarRevealerSettings revealer = unit.View?.FogOfWarRevealer; - if (revealer != null) { - if (settings.fowMultiplier == 1) { - revealer.DefaultRadius = true; - revealer.UseDefaultFowBorder = true; - revealer.Radius = 1.0f; - } - else { - revealer.DefaultRadius = false; - revealer.UseDefaultFowBorder = false; - revealer.Radius = FogOfWarController.VisionRadius * settings.fowMultiplier; - } - } - } - } - }, - () => LogSlider("Money Earned".localize(), ref settings.moneyMultiplier, 0f, 20, 1, 1, "", AutoWidth()), - () => LogSlider("Vendor Sell Price".localize(), ref settings.vendorSellPriceMultiplier, 0f, 20, 1, 1, "", AutoWidth()), - () => LogSlider("Vendor Buy Price".localize(), ref settings.vendorBuyPriceMultiplier, 0f, 20, 1, 1, "", AutoWidth()), - () => Slider("Increase Carry Capacity".localize(), ref settings.encumberanceMultiplier, 1, 100, 1, "", AutoWidth()), - () => Slider("Increase Carry Capacity (Party Only)".localize(), ref settings.encumberanceMultiplierPartyOnly, 1, 100, 1, "", AutoWidth()), - () => LogSlider("Spontaneous Spells Per Day".localize(), ref settings.spellsPerDayMultiplier, 0f, 20, 1, 1, "", AutoWidth()), - () => LogSlider("Prepared Spellslots".localize(), ref settings.memorizedSpellsMultiplier, 0f, 20, 1, 1, "", AutoWidth()), - () => { - LogSlider("Movement Speed".localize(), ref settings.partyMovementSpeedMultiplier, 0f, 20, 1, 1, "", Width(600)); - Space(25); - Toggle("Whole Team Moves Same Speed".localize(), ref settings.toggleMoveSpeedAsOne); - Space(25); - Label("Adjusts the movement speed of your party in area maps".localize().green()); - }, - () => { - LogSlider("Travel Speed".localize(), ref settings.travelSpeedMultiplier, 0f, 20, 1, 1, "", Width(600)); - Space(25); - Label("Adjusts the movement speed of your party on world maps".localize().green()); - }, - () => { - LogSlider("Companion Cost".localize(), ref settings.companionCostMultiplier, 0, 20, 1, 1, "", Width(600)); - Space(25); - Label("Adjusts costs of hiring mercenaries at the Pathfinder vendor".localize().green()); - - }, - () => LogSlider("Enemy HP Multiplier".localize(), ref settings.enemyBaseHitPointsMultiplier, 0.1f, 20, 1, 1, "", AutoWidth()), - () => LogSlider("Buff Duration".localize(), ref settings.buffDurationMultiplierValue, 0f, 9999, 1, 1, "", AutoWidth()), - () => DisclosureToggle("Exceptions to Buff Duration Multiplier (Advanced; will cause blueprints to load)".localize(), ref showBuffDurationExceptions), - () => { - if (!showBuffDurationExceptions) return; - - BuffExclusionEditor.OnGUI(); - }, - () => { } - ); - Actions.ApplyTimeScale(); - Div(0, 25); - HStack("Dice Rolls".localize(), 1, - () => EnumGrid("All Attacks Hit".localize(), ref settings.allAttacksHit, true, AutoWidth()), - () => EnumGrid("All Hits Critical".localize(), ref settings.allHitsCritical, true, AutoWidth()), - () => EnumGrid("Roll With Avantage".localize(), ref settings.rollWithAdvantage, true, AutoWidth()), - () => EnumGrid("Roll With Disavantage".localize(), ref settings.rollWithDisadvantage, true, AutoWidth()), - () => EnumGrid("Always Roll 20".localize(), ref settings.alwaysRoll20, true, AutoWidth()), - () => EnumGrid("Always Roll 1".localize(), ref settings.alwaysRoll1, true, AutoWidth()), - () => EnumGrid("Never Roll 20".localize(), ref settings.neverRoll20, true, AutoWidth()), - () => EnumGrid("Never Roll 1".localize(), ref settings.neverRoll1, true, AutoWidth()), - () => EnumGrid("Initiative: Always Roll 20".localize(), ref settings.roll20Initiative, true, AutoWidth()), - () => EnumGrid("Initiative: Always Roll 1".localize(), ref settings.roll1Initiative, true, AutoWidth()), - () => EnumGrid("Non Combat: Take 10".localize(), ref settings.take10always, true, AutoWidth()), - // () => EnumGrid("Non Combat: Take 10 (Min)", ref settings.take10minimum, AutoWidth()), - () => EnumGrid("Non Combat: Take 20".localize(), ref settings.alwaysRoll20OutOfCombat, true, AutoWidth()), - () => { 330.space(); Label("The following skill check adjustments apply only out of combat".localize().green()); }, - () => EnumGrid("Skill Checks: Take 10".localize(), ref settings.skillsTake10, true, AutoWidth()), - () => EnumGrid("Skill Checks: Take 20".localize(), ref settings.skillsTake20, true, AutoWidth()), - () => { } - ); - Div(0, 25); - HStack("Summons".localize(), 1, - () => Toggle("Make Controllable".localize(), ref settings.toggleMakeSummmonsControllable), - () => { - using (VerticalScope()) { - Div(0, 25); - using (HorizontalScope()) { - Label("Primary".localize().orange(), AutoWidth()); Space(215); Label("good for party".localize().green()); - } - Space(25); - EnumGrid("Modify Summons For".localize(), ref settings.summonTweakTarget1, true, AutoWidth()); - LogSlider("Duration Multiplier".localize(), ref settings.summonDurationMultiplier1, 0f, 20, 1, 2, "", AutoWidth()); - Slider("Level Increase/Decrease".localize(), ref settings.summonLevelModifier1, -20f, +20f, 0f, 0, "", AutoWidth()); - Div(0, 25); - using (HorizontalScope()) { - Label("Secondary".localize().orange(), AutoWidth()); Space(215); Label("good for larger group or to reduce enemies".localize().green()); - } - Space(25); - EnumGrid("Modify Summons For".localize(), ref settings.summonTweakTarget2, true, AutoWidth()); - LogSlider("Duration Multiplier".localize(), ref settings.summonDurationMultiplier2, 0f, 20, 1, 2, "", AutoWidth()); - Slider("Level Increase/Decrease".localize(), ref settings.summonLevelModifier2, -20f, +20f, 0f, 0, "", AutoWidth()); - } - }, - () => { } - ); - } - } -} diff --git a/ToyBox/classes/MainUI/BraaainzEditor.cs b/ToyBox/classes/MainUI/BraaainzEditor.cs deleted file mode 100644 index e46414390..000000000 --- a/ToyBox/classes/MainUI/BraaainzEditor.cs +++ /dev/null @@ -1,146 +0,0 @@ -using Kingmaker.AI; -using Kingmaker.AI.Blueprints; -using Kingmaker.AI.Blueprints.Considerations; -using Kingmaker.EntitySystem.Entities; -using ModKit; -using System; -using System.Collections.Generic; -using System.Linq; -using ModKit.DataViewer; -using static ModKit.UI; - -namespace ToyBox.classes.MainUI { - public static class BraaainzEditor { - public static Settings Settings => Main.Settings; - - private static List<BlueprintBrain> _allBraaainz = null; - private static List<BlueprintBrain> AllBraaainz { - get { - if (_allBraaainz != null) return _allBraaainz; - _allBraaainz = BlueprintLoader.Shared.GetBlueprints<BlueprintBrain>()?.OrderBy(bp => bp.GetDisplayName())?.ToList(); - return _allBraaainz; - } - } - - private static bool _pickBrain = false; - private static bool _customBrain = false; - private static string _brainSearchText = ""; - - public static Browser<BlueprintAiAction, AiAction> ActionBrowser = new(true, true); - public static Browser<Consideration, Consideration> ConsiderationBrowser = new(true, true); - public static Dictionary<BlueprintAiAction, Browser<Consideration, Consideration>> TargetConsiderationBrowser = new(); - - public static void OnGUI() { - Label("Group".orange().bold()); - CharacterPicker.OnFilterPickerGUI(); - Div(); - using (HorizontalScope()) { - 50.space(); - Label("Character".orange().bold()); - } - 5.space(); - CharacterPicker.OnCharacterPickerGUI(50); - 5.space(); - Div(50); - OnBrainGUI(CharacterPicker.GetSelectedCharacter()); - } - public static void OnBrainGUI(UnitEntityData ch) { - if (ch == null) return; - Label("This allows you to edit the AI for your characters much like Gambits in Final Fantasy 12, Dragon Age, or Pillars of Eternity. You can either choose one of the available default brains or build a custom one by choosing from a list of AI actions".green()); - using (HorizontalScope()) { - Label("Current Brain: ".cyan() + ch.Brain.Blueprint.GetDisplayName()); - 10.space(); - if (Toggle("Customize", ref _customBrain)) if (_customBrain) _pickBrain = false; - if (!_customBrain) { - 10.space(); - DisclosureToggle("Pick Existing", ref _pickBrain); - } - } - if (_pickBrain) { - var braaainz = AllBraaainz; - if (braaainz != null) { - var selectedBrain = ch.Brain.Blueprint; - if (GridPicker<BlueprintBrain>("Braaainzzz!", ref selectedBrain, AllBraaainz, null, br => br.GetDisplayName(), ref _brainSearchText, 1, 500.width())) { - ch.Brain.SetBrain(selectedBrain); - ch.Brain.RestoreAvailableActions(); - ActionBrowser.ResetSearch(); - } - } - else - Label("Blueprints".orange().bold() + " loading: " + BlueprintLoader.Shared.progress.ToString("P2").cyan().bold()); - } - - - ActionBrowser.OnGUI( - ch.Brain.Actions, - BlueprintExtensions.GetBlueprints<BlueprintAiAction>, - a => (BlueprintAiAction)a.Blueprint, - bp => bp.GetDisplayName(), - bp => $"{bp.GetDisplayName()} {bp.GetDescription()}", - null, - (bp, action) => { - Browser.DetailToggle(bp.GetDisplayName(), bp, bp); - ReflectionTreeView.DetailToggle("Inspect", bp, action != null ? action : bp); - var attributes = bp.GetCustomAttributes(); - var text = String.Join("\n", attributes.Select((name, value) => $"{name}: {value}")); - Label($"{text.green()}", AutoWidth()); - }, - (bp, action) => { - ReflectionTreeView.OnDetailGUI((bp)); - Browser.OnDetailGUI(bp, _ => { - if (action?.ActorConsiderations.Count > 0) { - using (HorizontalScope()) { - 150.space(); - Label($"{"Actor Considerations".orange().bold()} - {ch.CharacterName.cyan()}"); - } - ConsiderationBrowser.OnGUI( - action.ActorConsiderations, - BlueprintExtensions.GetBlueprints<Consideration>, - c => c, - c => c.GetDisplayName(), - c => c.GetDisplayName(), - null, - (bp, c) => { - Label(c.GetDisplayName()); - ReflectionTreeView.DetailToggle("", bp, c ?? bp); - - var attributes = bp.GetCustomAttributes(); - var text = string.Join("\n", attributes.Select((name, value) => $"{name} : {value}")); - Label(text.green(), AutoWidth()); - }, - (bp, _) => ReflectionTreeView.OnDetailGUI(bp, 150), - 150, true, false - ); - } - using (HorizontalScope()) { - 150.space(); - Label($"Target Consideration".orange()); - } - var targetConsiderationsBrowser = TargetConsiderationBrowser.GetValueOrDefault(bp, null); - if (targetConsiderationsBrowser == null) { - targetConsiderationsBrowser = new Browser<Consideration, Consideration>(); - TargetConsiderationBrowser[bp] = targetConsiderationsBrowser; - } - targetConsiderationsBrowser.OnGUI( - action?.TargetConsiderations, - BlueprintExtensions.GetBlueprints<Consideration>, - c => c, - c => c.GetDisplayName(), - c => c.GetDisplayName(), - null, - (bp, c) => { - Label(c.GetDisplayName()); - ReflectionTreeView.DetailToggle("", bp); - var attributes = bp.GetCustomAttributes(); - var text = string.Join("\n", attributes.Select((name, value) => $"{name} : {value}")); - Label(text.green(), AutoWidth()); - }, - (bp, _) => ReflectionTreeView.OnDetailGUI(bp, 150), - 150, true, false - ); - }); - } - ); - } - } -} \ No newline at end of file diff --git a/ToyBox/classes/MainUI/Browser/BlueprintAction.cs b/ToyBox/classes/MainUI/Browser/BlueprintAction.cs deleted file mode 100644 index 4fc3eabb4..000000000 --- a/ToyBox/classes/MainUI/Browser/BlueprintAction.cs +++ /dev/null @@ -1,444 +0,0 @@ -// Copyright < 2021 > Narria (github user Cabarius) - License: MIT - -using Kingmaker; -using Kingmaker.AI.Blueprints.Considerations; -using Kingmaker.AreaLogic.Cutscenes; -using Kingmaker.AreaLogic.Etudes; -using Kingmaker.AreaLogic.QuestSystem; -using Kingmaker.Armies.Blueprints; -using Kingmaker.Blueprints; -using Kingmaker.Blueprints.Area; -using Kingmaker.Blueprints.Classes; -using Kingmaker.Blueprints.Classes.Selection; -using Kingmaker.Blueprints.Classes.Spells; -using Kingmaker.Blueprints.Facts; -using Kingmaker.Blueprints.Items; -using Kingmaker.Blueprints.Quests; -using Kingmaker.Designers; -using Kingmaker.Designers.EventConditionActionSystem.ContextData; -using Kingmaker.ElementsSystem; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.EntitySystem.Persistence; -using Kingmaker.Globalmap.Blueprints; -using Kingmaker.Kingdom; -using Kingmaker.Kingdom.Blueprints; -using Kingmaker.UnitLogic; -using Kingmaker.UnitLogic.Abilities.Blueprints; -using Kingmaker.UnitLogic.ActivatableAbilities; -using Kingmaker.UnitLogic.Buffs.Blueprints; -using Kingmaker.Utility; -using ModKit; -using System; -using System.Collections.Generic; -using System.Linq; -using Kingmaker.Crusade.GlobalMagic; - -namespace ToyBox { - public abstract class BlueprintAction { - public delegate void Perform(SimpleBlueprint bp, UnitEntityData ch = null, int count = 1, int listValue = 0); - - public delegate bool CanPerform(SimpleBlueprint bp, UnitEntityData ch = null, int listValue = 0); - - private static Dictionary<Type, BlueprintAction[]> actionsForType; - - public static BlueprintAction[] ActionsForType(Type type) { - if (actionsForType == null) { - actionsForType = new Dictionary<Type, BlueprintAction[]>(); - BlueprintActions.InitializeActions(); - } - - actionsForType.TryGetValue(type, out var result); - - if (result == null) { - var baseType = type.BaseType; - - if (baseType != null) { - result = ActionsForType(baseType); - } - - result ??= new BlueprintAction[] { }; - - actionsForType[type] = result; - } - - return result; - } - - public static IEnumerable<BlueprintAction> ActionsForBlueprint(SimpleBlueprint bp) => ActionsForType(bp.GetType()); - public static void Register<T>(string name, BlueprintAction<T>.Perform perform, BlueprintAction<T>.CanPerform canPerform = null, bool isRepeatable = false) where T : SimpleBlueprint { - var action = new BlueprintAction<T>(name, perform, canPerform, isRepeatable); - var type = action.BlueprintType; - actionsForType.TryGetValue(type, out var existing); - existing ??= new BlueprintAction[] { }; - var list = existing.ToList(); - list.Add(action); - actionsForType[type] = list.ToArray(); - } - - public string name { get; protected set; } - - public Perform action; - - public CanPerform canPerform; - - protected BlueprintAction(string name, bool isRepeatable) { - this.name = name; - this.isRepeatable = isRepeatable; - } - - public bool isRepeatable; - - public abstract Type BlueprintType { get; } - } - - public class BlueprintAction<BPType> : BlueprintAction where BPType : SimpleBlueprint { - public new delegate void Perform(BPType bp, UnitEntityData ch, int count = 1, int listValue = 0); - - public new delegate bool CanPerform(BPType bp, UnitEntityData ch, int listValue = 0); - - public BlueprintAction(string name, Perform action, CanPerform canPerform = null, bool isRepeatable = false) : base(name, isRepeatable) { - this.action = (bp, ch, n, index) => action((BPType)bp, ch, n, index); - this.canPerform = (bp, ch, index) => Main.IsInGame && bp is BPType bpt && (canPerform?.Invoke(bpt, ch, index) ?? true); - } - - public override Type BlueprintType => typeof(BPType); - } - - public static class BlueprintActions { - public static IEnumerable<BlueprintAction> GetActions(this SimpleBlueprint bp) => BlueprintAction.ActionsForBlueprint(bp); - private static Dictionary<BlueprintParametrizedFeature, IFeatureSelectionItem[]> parametrizedSelectionItems = new(); - public static IFeatureSelectionItem ParametrizedSelectionItems(this BlueprintParametrizedFeature feature, int index) { - if (parametrizedSelectionItems.TryGetValue(feature, out var value)) return index < value.Length ? value[index] : null ; - value = feature.Items.OrderBy(x => x.Name).ToArray(); - if (value == null) return null; - parametrizedSelectionItems[feature] = value; - return index < value.Length ? value[index] : null; - } - private static Dictionary<BlueprintFeatureSelection, BlueprintFeature[]> featureSelectionItems = new(); - public static BlueprintFeature FeatureSelectionItems(this BlueprintFeatureSelection feature, int index) { - if (featureSelectionItems.TryGetValue(feature, out var value)) return index < value.Length ? value[index] : null; - value = feature.AllFeatures.OrderBy(x => x.NameSafe()).ToArray(); - if (value == null) return null; - featureSelectionItems[feature] = value; - return index < value.Length ? value[index] : null; - } - public static void InitializeActions() { - var flags = Game.Instance.Player.UnlockableFlags; - BlueprintAction.Register<BlueprintItem>("Add", - (bp, ch, n, index) => Game.Instance.Player.Inventory.Add(bp, n), isRepeatable: true); - - BlueprintAction.Register<BlueprintItem>("Remove", - (bp, ch, n, index) => Game.Instance.Player.Inventory.Remove(bp, n), - (bp, ch, index) => Game.Instance.Player.Inventory.Contains(bp), true); - - BlueprintAction.Register<BlueprintUnit>("Spawn", - (bp, ch, n, index) => Actions.SpawnUnit(bp, n), isRepeatable: true); - - // Features - BlueprintAction.Register<BlueprintFeature>("Add", - (bp, ch, n, index) => ch.Progression.Features.AddFeature(bp), - (bp, ch, index) => !ch.Progression.Features.HasFact(bp)); - - BlueprintAction.Register<BlueprintFeature>("Remove", - (bp, ch, n, index) => ch.Progression.Features.RemoveFact(bp), - (bp, ch, index) => ch.Progression.Features.HasFact(bp)); - BlueprintAction.Register<BlueprintFeature>("<", - (bp, ch, n, index) => ch.Progression.Features.GetFact(bp)?.RemoveRank(), - (bp, ch, index) => { - var feature = ch.Progression.Features.GetFact(bp); - return feature?.GetRank() > 1; - }); - - BlueprintAction.Register<BlueprintFeature>(">", - (bp, ch, n, index) => ch.Progression.Features.GetFact(bp)?.AddRank(), - (bp, ch, index) => { - var feature = ch.Progression.Features.GetFact(bp); - return feature != null && feature.GetRank() < feature.Blueprint.Ranks; - }); - // Paramaterized Feature - BlueprintAction.Register<BlueprintParametrizedFeature>("Add", - (bp, ch, n, index) => { - var value = bp.ParametrizedSelectionItems(BlueprintListUI.ParamSelected[index])?.Param; - ch?.Descriptor?.AddFact<UnitFact>(bp, null, value); - }, - (bp, ch, index) => { - var value = bp.ParametrizedSelectionItems(BlueprintListUI.ParamSelected[index])?.Param; - var existing = ch?.Descriptor?.Unit?.Facts?.Get<Feature>(i => i.Blueprint == bp && i.Param == value); - return existing == null; - }); - BlueprintAction.Register<BlueprintParametrizedFeature>("Remove", - (bp, ch, n, index) => { - var value = bp.ParametrizedSelectionItems(BlueprintListUI.ParamSelected[index])?.Param; - var fact = ch.Descriptor?.Unit?.Facts?.Get<Feature>(i => i.Blueprint == bp && i.Param == value); - ch?.Progression?.Features?.RemoveFact(fact); - }, - (bp, ch, index) => { - if (bp.Items.Count() == 0) return false; - var value = bp.ParametrizedSelectionItems(BlueprintListUI.ParamSelected[index])?.Param; - var existing = ch?.Descriptor?.Unit?.Facts?.Get<Feature>(i => i.Blueprint == bp && i.Param == value); - return existing != null; - }); - // Feature Selection - BlueprintAction.Register<BlueprintFeatureSelection>("Add", - (bp, ch, n, index) => { - var value = bp.FeatureSelectionItems(BlueprintListUI.ParamSelected[index]); - var source = new FeatureSource(); - ch?.Descriptor?.Progression.Features.AddFeature(bp).SetSource(source, 1); - ch?.Progression?.AddSelection(bp, source, 0, value); - - }, - (bp, ch, index) => { - var progression = ch?.Descriptor?.Progression; - if (progression == null) return false; - if (!progression.Features.HasFact(bp)) return true; - var value = bp.FeatureSelectionItems(BlueprintListUI.ParamSelected[index]); - if (progression.Selections.TryGetValue(bp, out var selection)) { - if (selection.SelectionsByLevel.Values.Any(l => l.Any(f => f == value))) return false; - } - return true; - }); - BlueprintAction.Register<BlueprintFeatureSelection>("Rem. All", - (bp, ch, n, index) => { - var progression = ch?.Descriptor?.Progression; - var value = bp.FeatureSelectionItems(BlueprintListUI.ParamSelected[index]); - //Feature fact = progression.Features.GetFact(bp); - var fact = ch.Descriptor?.Unit?.Facts?.Get<Feature>(i => i.Blueprint == bp && i.Param == value); - var selections = ch?.Descriptor?.Progression.Selections; - BlueprintFeatureSelection featureSelection = null; - FeatureSelectionData featureSelectionData = null; - var level = -1; - foreach (var selection in selections) { - foreach (var keyValuePair in selection.Value.SelectionsByLevel) { - if (keyValuePair.Value.HasItem<BlueprintFeature>(bp)) { - featureSelection = selection.Key; - featureSelectionData = selection.Value; - level = keyValuePair.Key; - break; - } - } - if (level >= 0) - break; - } - featureSelectionData?.RemoveSelection(level, value); - progression.Features.RemoveFact(bp); - }, - (bp, ch, index) => { - var progression = ch?.Descriptor?.Progression; - if (progression == null) return false; - if (!progression.Features.HasFact(bp)) return false; - var value = bp.FeatureSelectionItems(BlueprintListUI.ParamSelected[index]); - if (progression.Selections.TryGetValue(bp, out var selection)) { - if (selection.SelectionsByLevel.Values.Any(l => l.Any(f => f == value))) return true; - } - return false; - }); - - // Facts - BlueprintAction.Register<BlueprintUnitFact>("Add", - (bp, ch, n, index) => ch.AddFact(bp), - (bp, ch, index) => !ch.Facts.List.Select(f => f.Blueprint).Contains(bp)); - - BlueprintAction.Register<BlueprintUnitFact>("Remove", - (bp, ch, n, index) => ch.RemoveFact(bp), - (bp, ch, index) => ch.Facts.List.Select(f => f.Blueprint).Contains(bp)); - - //BlueprintAction.Register<BlueprintArchetype>( - // "Add", - // (bp, ch, n, index) => ch.Progression.AddArchetype(ch.Progression.Classes.First().CharacterClass, bp), - // (bp, ch, index) => ch.Progression.CanAddArchetype(ch.Progression.Classes.First().CharacterClass, bp) - // ); - //BlueprintAction.Register<BlueprintArchetype>("Remove", - // (bp, ch, n, index) => ch.Progression.AddArchetype(ch.Progression.Classes.First().CharacterClass, bp), - // (bp, ch, index) => ch.Progression.Classes.First().Archetypes.Contains(bp) - // ); - - // Spellbooks - BlueprintAction.Register<BlueprintSpellbook>("Add", - (bp, ch, n, index) => ch.Descriptor.DemandSpellbook(bp), - (bp, ch, index) => ch.Descriptor.Spellbooks.All(sb => sb.Blueprint != bp)); - - BlueprintAction.Register<BlueprintSpellbook>("Remove", - (bp, ch, n, index) => ch.Descriptor.DeleteSpellbook(bp), - (bp, ch, index) => ch.Descriptor.Spellbooks.Any(sb => sb.Blueprint == bp)); - - BlueprintAction.Register<BlueprintSpellbook>(">", - (bp, ch, n, index) => { - try { - var spellbook = ch.Descriptor.Spellbooks.First(sb => sb.Blueprint == bp); - - if (spellbook.IsMythic) { - spellbook.AddMythicLevel(); - } - else { - spellbook.AddBaseLevel(); - } - } - catch (Exception e) { Mod.Error(e); } - }, - (bp, ch, index) => ch.Descriptor.Spellbooks.Any(sb => sb.Blueprint == bp && sb.CasterLevel < bp.MaxSpellLevel)); - - // Buffs - BlueprintAction.Register<BlueprintBuff>("Add", - (bp, ch, n, index) => GameHelper.ApplyBuff(ch, bp), - (bp, ch, index) => !ch.Descriptor.Buffs.HasFact(bp)); - - BlueprintAction.Register<BlueprintBuff>("Remove", - (bp, ch, n, index) => ch.Descriptor.RemoveFact(bp), - (bp, ch, index) => ch.Descriptor.Buffs.HasFact(bp)); - - BlueprintAction.Register<BlueprintBuff>("<", - (bp, ch, n, index) => ch.Descriptor.Buffs.GetFact(bp)?.RemoveRank(), - (bp, ch, index) => { - var buff = ch.Descriptor.Buffs.GetFact(bp); - - return buff?.GetRank() > 1; - }); - - BlueprintAction.Register<BlueprintBuff>(">", - (bp, ch, n, index) => ch.Descriptor.Buffs.GetFact(bp)?.AddRank(), - (bp, ch, index) => { - var buff = ch.Descriptor.Buffs.GetFact(bp); - - return buff != null && buff.GetRank() < buff.Blueprint.Ranks - 1; - }); - // Kingdom Bufs - BlueprintAction.Register<BlueprintKingdomBuff>("Add", - (bp, ch, n, index) => KingdomState.Instance?.AddBuff(bp, null, null, 0), - (bp, ch, index) => (KingdomState.Instance != null) && !KingdomState.Instance.ActiveBuffs.HasFact(bp)); - - BlueprintAction.Register<BlueprintKingdomBuff>("Remove", - (bp, ch, n, index) => KingdomState.Instance?.ActiveBuffs.RemoveFact(bp), - (bp, ch, index) => (KingdomState.Instance != null) && KingdomState.Instance.ActiveBuffs.HasFact(bp)); - - - // Abilities - BlueprintAction.Register<BlueprintAbility>("Add", - (bp, ch, n, index) => ch.AddAbility(bp), - (bp, ch, index) => ch.CanAddAbility(bp)); - - BlueprintAction.Register<BlueprintAbility>("At Will", - (bp, ch, n, index) => ch.AddSpellAsAbility(bp), - (bp, ch, index) => ch.CanAddSpellAsAbility(bp)); - - BlueprintAction.Register<BlueprintAbility>("Remove", - (bp, ch, n, index) => ch.RemoveAbility(bp), - (bp, ch, index) => ch.HasAbility(bp)); - // GlobalSpells - BlueprintAction.Register<BlueprintGlobalMagicSpell>("Add", - (bp, ch, n, index) => Game.Instance.Player.GlobalMapSpellsManager.AddSpell(bp), - (bp, ch, index) => !Game.Instance.Player.GlobalMapSpellsManager.m_SpellBook.HasItem(x => x.BlueprintGuid == bp.AssetGuid)); - - BlueprintAction.Register<BlueprintGlobalMagicSpell>("Remove", - (bp, ch, n, index) => Game.Instance.Player.GlobalMapSpellsManager.RemoveSpell(bp), - (bp, ch, index) => Game.Instance.Player.GlobalMapSpellsManager.m_SpellBook.HasItem(x => x.BlueprintGuid == bp.AssetGuid)); - // Ability Resources - - BlueprintAction.Register<BlueprintAbilityResource>("Add", - (bp, ch, n, index) => ch.Resources.Add(bp, true), - (bp, ch, index) => !ch.Resources.ContainsResource(bp)); - - BlueprintAction.Register<BlueprintAbilityResource>("Remove", - (bp, ch, n, index) => ch.Resources.Remove(bp), - (bp, ch, index) => ch.Resources.ContainsResource(bp)); - - // Spellbooks - - - // BlueprintActivatableAbility - BlueprintAction.Register<BlueprintActivatableAbility>("Add", - (bp, ch, n, index) => ch.Descriptor.AddFact(bp), - (bp, ch, index) => !ch.Descriptor.HasFact(bp)); - - BlueprintAction.Register<BlueprintActivatableAbility>("Remove", - (bp, ch, n, index) => ch.Descriptor.RemoveFact(bp), - (bp, ch, index) => ch.Descriptor.HasFact(bp)); - - // Quests - BlueprintAction.Register<BlueprintQuest>("Start", - (bp, ch, n, index) => Game.Instance.Player.QuestBook.GiveObjective(bp.Objectives.First()), - (bp, ch, index) => Game.Instance.Player.QuestBook.GetQuest(bp) == null); - - BlueprintAction.Register<BlueprintQuest>("Complete", - (bp, ch, n, index) => { - foreach (var objective in bp.Objectives) { - Game.Instance.Player.QuestBook.CompleteObjective(objective); - } - }, (bp, ch, index) => Game.Instance.Player.QuestBook.GetQuest(bp)?.State == QuestState.Started); - - // Quests Objectives - BlueprintAction.Register<BlueprintQuestObjective>("Start", - (bp, ch, n, index) => Game.Instance.Player.QuestBook.GiveObjective(bp), - (bp, ch, index) => Game.Instance.Player.QuestBook.GetQuest(bp.Quest) == null); - - BlueprintAction.Register<BlueprintQuestObjective>("Complete", - (bp, ch, n, index) => Game.Instance.Player.QuestBook.CompleteObjective(bp), - (bp, ch, index) => Game.Instance.Player.QuestBook.GetQuest(bp.Quest)?.State == QuestState.Started); - - // Etudes - BlueprintAction.Register<BlueprintEtude>("Start", - (bp, ch, n, index) => Game.Instance.Player.EtudesSystem.StartEtude(bp), - (bp, ch, index) => Game.Instance.Player.EtudesSystem.EtudeIsNotStarted(bp)); - BlueprintAction.Register<BlueprintEtude>("Unstart", - (bp, ch, n, index) => Game.Instance.Player.EtudesSystem.UnstartEtude(bp), - (bp, ch, index) => !Game.Instance.Player.EtudesSystem.EtudeIsNotStarted(bp)); - BlueprintAction.Register<BlueprintEtude>("Complete", - (bp, ch, n, index) => Game.Instance.Player.EtudesSystem.MarkEtudeCompleted(bp), - (bp, ch, index) => !Game.Instance.Player.EtudesSystem.EtudeIsNotStarted(bp) && - !Game.Instance.Player.EtudesSystem.EtudeIsCompleted(bp)); - // Flags - BlueprintAction.Register<BlueprintUnlockableFlag>("Unlock", - (bp, ch, n, index) => flags.Unlock(bp), - (bp, ch, index) => !flags.IsUnlocked(bp)); - - BlueprintAction.Register<BlueprintUnlockableFlag>("Lock", - (bp, ch, n, index) => flags.Lock(bp), - (bp, ch, index) => flags.IsUnlocked(bp)); - - BlueprintAction.Register<BlueprintUnlockableFlag>(">", - (bp, ch, n, index) => flags.SetFlagValue(bp, flags.GetFlagValue(bp) + n), - (bp, ch, index) => flags.IsUnlocked(bp)); - - BlueprintAction.Register<BlueprintUnlockableFlag>("<", - (bp, ch, n, index) => flags.SetFlagValue(bp, flags.GetFlagValue(bp) - n), - (bp, ch, index) => flags.IsUnlocked(bp)); - // Cutscenes - BlueprintAction.Register<Cutscene>("Play", (bp, ch, n, index) => { - Actions.ToggleModWindow(); - var cutscenePlayerData = CutscenePlayerData.Queue.FirstOrDefault(c => c.PlayActionId == bp.name); - - if (cutscenePlayerData != null) { - cutscenePlayerData.PreventDestruction = true; - cutscenePlayerData.Stop(); - cutscenePlayerData.PreventDestruction = false; - } - - var state = ContextData<SpawnedUnitData>.Current?.State; - CutscenePlayerView.Play(bp, null, true, state).PlayerData.PlayActionId = bp.name; - }); - - // Teleport - BlueprintAction.Register<BlueprintAreaEnterPoint>("Teleport", (enterPoint, ch, n, index) => Teleport.To(enterPoint)); - BlueprintAction.Register<BlueprintGlobalMap>("Teleport", (map, ch, n, index) => Teleport.To(map)); - BlueprintAction.Register<BlueprintArea>("Teleport", (area, ch, n, index) => Teleport.To(area)); - BlueprintAction.Register<BlueprintGlobalMapPoint>("Teleport", (globalMapPoint, ch, n, index) => Teleport.To(globalMapPoint)); - - //Army - BlueprintAction.Register<BlueprintArmyPreset>("Add Friendly", (bp, ch, n, l) => { - Actions.CreateArmy(bp,true); - }); - BlueprintAction.Register<BlueprintArmyPreset>("Add Hostile", (bp, ch, n, l) => { - Actions.CreateArmy(bp,false); - }); - - //ArmyGeneral - BlueprintAction.Register<BlueprintLeaderSkill>("Add", - (bp, ch, n, l) => Actions.AddSkillToLeader(bp), - (bp, ch, index) => Actions.LeaderSelected(bp) && !Actions.LeaderHasSkill(bp)); - - //ArmyGeneral - BlueprintAction.Register<BlueprintLeaderSkill>("Remove", - (bp, ch, n, l) => Actions.RemoveSkillFromLeader(bp), - (bp, ch, index) => Actions.LeaderSelected(bp) && Actions.LeaderHasSkill(bp)); - } - } -} \ No newline at end of file diff --git a/ToyBox/classes/MainUI/Browser/FactsEditor.cs b/ToyBox/classes/MainUI/Browser/FactsEditor.cs deleted file mode 100644 index a22262b2c..000000000 --- a/ToyBox/classes/MainUI/Browser/FactsEditor.cs +++ /dev/null @@ -1,308 +0,0 @@ -// Copyright < 2021 > Narria (github user Cabarius) - License: MIT -using Kingmaker.Blueprints; -using Kingmaker.Blueprints.Classes; -using Kingmaker.Blueprints.Classes.Selection; -using Kingmaker.Blueprints.Classes.Spells; -using Kingmaker.Blueprints.Facts; -using Kingmaker.EntitySystem; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.PubSubSystem; -using Kingmaker.UI; -using Kingmaker.UnitLogic; -using Kingmaker.UnitLogic.Abilities; -using Kingmaker.UnitLogic.Abilities.Blueprints; -using Kingmaker.UnitLogic.Buffs; -using Kingmaker.UnitLogic.Buffs.Blueprints; -using Kingmaker.Utility; -using ModKit; -using ModKit.DataViewer; -using ModKit.Utility; -using System; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; -using static ModKit.UI; -using static ToyBox.BlueprintExtensions; - -namespace ToyBox { - public class FactsEditor { - public class CollectionChangedSubscriber : IFactCollectionUpdatedHandler { - public CollectionChangedSubscriber() { - EventBus.Subscribe(this); - } - public void HandleFactCollectionUpdated(EntityFactsProcessor collection) { - foreach (var b in BuffBrowserDict.Values) { - b.needsReloadData = true; - } - foreach (var b in FeatureBrowserDict.Values) { - b.needsReloadData = true; - } - foreach (var b in AbilityBrowserDict.Values) { - b.needsReloadData = true; - } - } - } - private static Settings Settings => Main.Settings; - private static bool _showTree = false; - private static readonly int repeatCount = 1; - private static readonly FeaturesTreeEditor treeEditor = new(); - private static readonly CollectionChangedSubscriber collectionChangedSubscriber = new(); - - private static readonly Dictionary<UnitEntityData, Browser<BlueprintFeature, Feature>> FeatureBrowserDict = new(); - private static readonly Dictionary<UnitEntityData, Browser<BlueprintBuff, Buff>> BuffBrowserDict = new(); - private static readonly Dictionary<UnitEntityData, Browser<BlueprintAbility, Ability>> AbilityBrowserDict = new(); - private static readonly Browser<BlueprintFeature, FeatureSelectionEntry> FeatureSelectionBrowser = new() { IsDetailBrowser = true }; - private static readonly Browser<IFeatureSelectionItem, IFeatureSelectionItem> ParameterizedFeatureBrowser = new() { IsDetailBrowser = true }; - - public static void BlueprintRowGUI<Item, Definition>(Browser<Definition, Item> browser, - Item feature, - Definition blueprint, - UnitEntityData ch, - List<Action> todo - ) where Definition : BlueprintScriptableObject, IUIDataProvider { - var remainingWidth = ummWidth; - // Indent - remainingWidth -= 50; - var titleWidth = (remainingWidth / (IsWide ? 3.5f : 4.0f)) - 100; - remainingWidth -= titleWidth; - - var text = GetTitle(blueprint).MarkedSubstring(browser.SearchText); - var titleKey = $"{blueprint.AssetGuid}"; - if (feature != null) { - text = text.Cyan().Bold(); - } - if (blueprint is BlueprintFeatureSelection featureSelection - || blueprint is BlueprintParametrizedFeature parametrizedFeature - ) { - if (Browser.DetailToggle(text, blueprint, feature != null ? feature : blueprint, (int)titleWidth)) - browser.ReloadData(); - } - else - Label(text, Width((int)titleWidth)); - - var lastRect = GUILayoutUtility.GetLastRect(); - - var mutatorLookup = BlueprintAction.ActionsForType(typeof(Definition)).Distinct().ToDictionary(a => a.name, a => a); - var add = mutatorLookup.GetValueOrDefault("Add", null); - var remove = mutatorLookup.GetValueOrDefault("Remove", null); - var decrease = mutatorLookup.GetValueOrDefault("<", null); - var increase = mutatorLookup.GetValueOrDefault(">", null); - - mutatorLookup.Remove("Add"); - mutatorLookup.Remove("Remove"); - mutatorLookup.Remove("<"); - mutatorLookup.Remove(">"); - if (feature != null) { - bool canDecrease = decrease?.canPerform(blueprint, ch) ?? false; - bool canIncrease = increase?.canPerform(blueprint, ch) ?? false; - if ((canDecrease || canIncrease) && feature is UnitFact rankFeature) { - var v = rankFeature.GetRank(); - decrease.BlueprintActionButton(ch, blueprint, () => todo.Add(() => decrease!.action(blueprint, ch, repeatCount)), 60); - Space(10f); - Label($"{v}".orange().bold(), Width(30)); - increase.BlueprintActionButton(ch, blueprint, () => todo.Add(() => increase!.action(blueprint, ch, repeatCount)), 60); - Space(17); - remainingWidth -= 190; - } else { - Space(190); - remainingWidth -= 190; - } - } else { - Space(190); - remainingWidth -= 190; - } - var canAdd = add?.canPerform(blueprint, ch) ?? false; - var canRemove = remove?.canPerform(blueprint, ch) ?? false; - if (canRemove) { - remove.BlueprintActionButton(ch, blueprint, () => todo.Add(() => { browser.needsReloadData = true; remove.action(blueprint, ch, repeatCount); }), 150); - } - if (canAdd) { - add.BlueprintActionButton(ch, blueprint, () => todo.Add(() => { browser.needsReloadData = true; add.action(blueprint, ch, repeatCount); }), 150); - } - remainingWidth -= 178; - Space(20); remainingWidth -= 20; - ReflectionTreeView.DetailToggle("", blueprint, feature != null ? feature : blueprint, 0); - using (VerticalScope(Width(remainingWidth - 100))) { - try { - if (Settings.showAssetIDs) - ClipboardLabel(blueprint.AssetGuid.ToString(), AutoWidth()); - Label(blueprint.Description.StripHTML().MarkedSubstring(browser.SearchText).green(), Width(remainingWidth - 100)); - } - catch (Exception e) { - Mod.Warn($"Error in blueprint: {blueprint.AssetGuid}"); - Mod.Warn($" name: {blueprint.name}"); - Mod.Error(e); - } - } - } - - public static List<Action> OnGUI<Item, Definition>(UnitEntityData ch, Browser<Definition, Item> browser, List<Item> fact, string name) - where Item : UnitFact - where Definition : BlueprintUnitFact { - bool updateTree = false; - List<Action> todo = new(); - if (_showTree) { - using (HorizontalScope()) { - Space(670); - Toggle("Show Tree", ref _showTree, Width(250)); - } - treeEditor.OnGUI(ch, updateTree); - } else { - browser.OnGUI( - fact, - GetBlueprints<Definition>, - (feature) => (Definition)feature.Blueprint, - (blueprint) => $"{GetSearchKey(blueprint)}" + (Settings.searchDescriptions ? $"{blueprint.Description}" : ""), - GetSortKey, - () => { - using (HorizontalScope()) { - var reloadData = false; - Toggle("Show GUIDs", ref Main.Settings.showAssetIDs); - 20.space(); - reloadData |= Toggle("Show Internal Names", ref Settings.showDisplayAndInternalNames); - 20.space(); - updateTree |= Toggle("Show Tree", ref _showTree); - 20.space(); - //Toggle("Show Inspector", ref Settings.factEditorShowInspector); - //20.space(); - reloadData |= Toggle("Search Descriptions", ref Settings.searchDescriptions); - if (reloadData) { - browser.ResetSearch(); - FeatureSelectionBrowser.ResetSearch(); - ParameterizedFeatureBrowser.ResetSearch(); - } - } - }, - (blueprint, feature) => BlueprintRowGUI(browser,feature, blueprint, ch, todo), - (blueprint, feature) => { - ReflectionTreeView.OnDetailGUI(blueprint); - switch (blueprint) { - case BlueprintFeatureSelection featureSelection: - Browser.OnDetailGUI(blueprint, bp => { - FeatureSelectionBrowser.needsReloadData |= browser.needsReloadData; - FeatureSelectionBrowser.OnGUI( - ch.FeatureSelectionEntries(featureSelection), - () => - featureSelection.AllFeatures.OrderBy(f => f.Name), - e => e.feature, - f => $"{GetSearchKey(f)} " + (Settings.searchDescriptions ? f.Description : ""), - GetTitle, - null, - (f, selectionEntry) => { - var title = GetTitle(f).MarkedSubstring(FeatureSelectionBrowser.SearchText); - if (selectionEntry != null) title = title.Cyan().Bold(); - var titleWidth = (ummWidth / (IsWide ? 3.5f : 4.0f)) - 200; - Label(title, Width(titleWidth)); - 78.space(); - if (selectionEntry != null) { - var level = selectionEntry.level; - Space(-25); - using (VerticalScope(125)) { - using (HorizontalScope(125)) { - Label("sel lvl", 50.width()); - if (ValueAdjuster(ref level, 1, 0, 20, false)) { - ch.RemoveFeatureSelection(featureSelection, - selectionEntry.data, - f); - ch.AddFeatureSelection(featureSelection, f, level); - FeatureSelectionBrowser.ReloadData(); - browser.ReloadData(); - } - } - } - 20.space(); - Label($"{selectionEntry.data.Source.Blueprint.GetDisplayName()}", - 250.width()); - } - else - 354.space(); - if (ch.HasFeatureSelection(featureSelection, f)) - ActionButton("Remove", - () => { - if (selectionEntry == null) return; - ch.RemoveFeatureSelection(featureSelection, selectionEntry.data, f); - FeatureSelectionBrowser.needsReloadData = true; - browser.needsReloadData = true; - }, - 150.width()); - else - ActionButton("Add", - () => { - ch.AddFeatureSelection(featureSelection, f); - FeatureSelectionBrowser.needsReloadData = true; - browser.needsReloadData = true; - }, - 150.width()); - 15.space(); - Label(f.GetDescription().StripHTML().MarkedSubstring(FeatureSelectionBrowser.SearchText).green()); - }, - null, - 100); - }); - break; - case BlueprintParametrizedFeature parametrizedFeature: - Browser.OnDetailGUI(blueprint, bp => { - ParameterizedFeatureBrowser.needsReloadData |= browser.needsReloadData; - ParameterizedFeatureBrowser.OnGUI( - ch.ParameterizedFeatureItems(parametrizedFeature), - () => parametrizedFeature.Items.OrderBy(i => i.Name), - i => i, - i => $"{i.Name} " + (Settings.searchDescriptions ? i.Param?.Blueprint?.GetDescription() : ""), - i => i.Name, - null, - (def , item) => { - var title = def.Name.MarkedSubstring(ParameterizedFeatureBrowser.SearchText); - // make the title cyan if we have the item - if (item != null) title = title.Cyan().Bold(); - - var titleWidth = (ummWidth / (IsWide ? 3.5f : 4.0f)); - Label(title, Width(titleWidth)); - 25.space(); - if (ch.HasParameterizedFeatureItem(parametrizedFeature, def)) - ActionButton("Remove", () => { - ch.RemoveParameterizedFeatureItem(parametrizedFeature, def); - ParameterizedFeatureBrowser.needsReloadData = true; - browser.needsReloadData = true; - }, 150.width()); - else - ActionButton("Add", () => { - ch.AddParameterizedFeatureItem(parametrizedFeature, def); - ParameterizedFeatureBrowser.needsReloadData = true; - browser.needsReloadData = true; - }, 150.width()); - 15.space(); - Label(def.Param?.Blueprint?.GetDescription().StripHTML().MarkedSubstring(ParameterizedFeatureBrowser.SearchText).green()); - }, null, 100); - }); - break; - } - }, 50, false, true, 100, 300, "", true); - } - return todo; - } - public static List<Action> OnGUI(UnitEntityData ch, List<Feature> feature) { - var featureBrowser = FeatureBrowserDict.GetValueOrDefault(ch, null); - if (featureBrowser == null) { - featureBrowser = new Browser<BlueprintFeature, Feature>(true, true) {}; - FeatureBrowserDict[ch] = featureBrowser; - } - return OnGUI(ch, featureBrowser, feature, "Features"); - } - public static List<Action> OnGUI(UnitEntityData ch, List<Buff> buff) { - var buffBrowser = BuffBrowserDict.GetValueOrDefault(ch, null); - if (buffBrowser == null) { - buffBrowser = new Browser<BlueprintBuff, Buff>(true, true); - BuffBrowserDict[ch] = buffBrowser; - } - return OnGUI(ch, buffBrowser, buff, "Buffs"); - } - public static List<Action> OnGUI(UnitEntityData ch, List<Ability> ability) { - var abilityBrowser = AbilityBrowserDict.GetValueOrDefault(ch, null); - if (abilityBrowser == null) { - abilityBrowser = new Browser<BlueprintAbility, Ability>(true, true); - AbilityBrowserDict[ch] = abilityBrowser; - } - return OnGUI(ch, abilityBrowser, ability, "Abilities"); - } - } -} diff --git a/ToyBox/classes/MainUI/Browser/SearchAndPick.cs b/ToyBox/classes/MainUI/Browser/SearchAndPick.cs deleted file mode 100644 index f5e24a0b4..000000000 --- a/ToyBox/classes/MainUI/Browser/SearchAndPick.cs +++ /dev/null @@ -1,421 +0,0 @@ -// Copyright < 2021 > Narria (github user Cabarius) - License: MIT -using UnityEngine; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using Kingmaker.Armies.Blueprints; -using Kingmaker.Blueprints; -using Kingmaker.Blueprints.Area; -using Kingmaker.Blueprints.Classes; -using Kingmaker.Blueprints.Classes.Selection; -using Kingmaker.Blueprints.Classes.Spells; -using Kingmaker.Blueprints.Facts; -using Kingmaker.Blueprints.Items; -using Kingmaker.Blueprints.Items.Equipment; -using Kingmaker.Blueprints.Items.Weapons; -using Kingmaker.Blueprints.Quests; -using Kingmaker.Craft; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.Globalmap.Blueprints; -using Kingmaker.UnitLogic.Abilities.Blueprints; -using Kingmaker.UnitLogic.Buffs.Blueprints; -using Kingmaker.Utility; -using Kingmaker.AreaLogic.Etudes; -using Kingmaker.AreaLogic.Cutscenes; -using ModKit; -using static ModKit.UI; -using ModKit.Utility; -using Kingmaker.DialogSystem.Blueprints; -using Kingmaker.Blueprints.Items.Armors; -using Kingmaker.Designers.EventConditionActionSystem.Actions; -using Kingmaker.ElementsSystem; -using Kingmaker.Kingdom.Blueprints; -using Kingmaker.AI.Blueprints.Considerations; -using Kingmaker.AI.Blueprints; -using ModKit.DataViewer; -using Kingmaker.AreaLogic.QuestSystem; -using Kingmaker.Crusade.GlobalMagic; -using static ToyBox.BlueprintExtensions; - -namespace ToyBox { - public static class SearchAndPick { - public static Settings Settings => Main.Settings; - - public static IEnumerable<SimpleBlueprint> unpagedBPs = null; - public static IEnumerable<SimpleBlueprint> filteredBPs = null; - public static Dictionary<string, List<SimpleBlueprint>> collatedBPs = null; - public static IEnumerable<SimpleBlueprint> selectedCollatedBPs = null; - public static List<string> collationKeys = null; - public static List<string> collationTitles = null; - public static int selectedCollationIndex = 0; - private static bool firstSearch = true; - public static string[] filteredBPNames = null; - public static int uncolatedMatchCount = 0; - public static int matchCount = 0; - public static int pageCount = 0; - public static int currentPage = 0; - public static string collationSearchText = ""; - public static string parameter = ""; - private static readonly char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; - - private static readonly NamedTypeFilter[] blueprintTypeFilters = new NamedTypeFilter[] { - new NamedTypeFilter<SimpleBlueprint>("All", null, bp => bp.CollationNames( -#if DEBUG - bp.m_AllElements?.OfType<Condition>()?.Select(e => e.GetCaption() ?? "")?.ToArray() ?? new string[] {} -#endif - )), - //new NamedTypeFilter<SimpleBlueprint>("All", null, bp => bp.Collat`ionNames(bp.m_AllElements?.Select(e => e.GetType().Name).ToArray() ?? new string[] {})), - //new NamedTypeFilter<SimpleBlueprint>("All", null, bp => bp.CollationNames(bp.m_AllElements?.Select(e => e.ToString().TrimEnd(digits)).ToArray() ?? new string[] {})), - //new NamedTypeFilter<SimpleBlueprint>("All", null, bp => bp.CollationNames(bp.m_AllElements?.Select(e => e.name.Split('$')[1].TrimEnd(digits)).ToArray() ?? new string[] {})), - new NamedTypeFilter<BlueprintFact>("Facts", null, bp => bp.CollationNames()), - new NamedTypeFilter<BlueprintFeature>("Features", null, bp => bp.CollationNames( bp.Groups.Select(g => g.ToString()).ToArray())), - new NamedTypeFilter<BlueprintParametrizedFeature>("ParamFeatures", null, bp => new List<string> {bp.ParameterType.ToString() }), - new NamedTypeFilter<BlueprintFeatureSelection>("Feature Selection", null, bp => bp.CollationNames(bp.Group.ToString(), bp.Group2.ToString())), - new NamedTypeFilter<BlueprintCharacterClass>("Classes", null, bp => bp.CollationNames()), - new NamedTypeFilter<BlueprintProgression>("Progression", null, bp => bp.Classes.Select(cl => cl.Name).ToList()), - new NamedTypeFilter<BlueprintArchetype>("Archetypes", null, bp => bp.CollationNames()), - new NamedTypeFilter<BlueprintAbility>("Abilities", null, bp => bp.CollationNames()), - new NamedTypeFilter<BlueprintAbility>("Spells", bp => bp.IsSpell, bp => bp.CollationNames(bp.School.ToString())), - new NamedTypeFilter<BlueprintGlobalMagicSpell>("Global Spells", null, bp => bp.CollationNames()), - new NamedTypeFilter<BlueprintBrain>("Brains", null, bp => bp.CollationNames()), - new NamedTypeFilter<BlueprintAiAction>("AIActions", null, bp => bp.CollationNames()), - new NamedTypeFilter<Consideration>("Considerations", null, bp => bp.CollationNames()), - new NamedTypeFilter<BlueprintAbilityResource>("Ability Rsrc", null, bp => bp.CollationNames()), - new NamedTypeFilter<BlueprintSpellbook>("Spellbooks", null, bp => bp.CollationNames(bp.CharacterClass.Name.ToString())), - new NamedTypeFilter<BlueprintBuff>("Buffs", null, bp => bp.CollationNames()), - new NamedTypeFilter<BlueprintKingdomBuff>("Kingdom Buffs", null, bp => bp.CollationNames()), - - new NamedTypeFilter<BlueprintItem>("Item", null, (bp) => { - if (bp.m_NonIdentifiedNameText?.ToString().Length > 0) return bp.CollationNames(bp.m_NonIdentifiedNameText); - return bp.CollationNames(bp.ItemType.ToString()); - }), - new NamedTypeFilter<BlueprintItemEquipment>("Equipment", null, (bp) => bp.CollationNames(bp.ItemType.ToString(), $"{bp.Cost.ToBinString("⊙".yellow())}")), - new NamedTypeFilter<BlueprintItemEquipment>("Equip (rarity)", null, (bp) => new List<string> {bp.Rarity().GetString() }), - new NamedTypeFilter<BlueprintItemWeapon>("Weapons", null, (bp) => { - var type = bp.Type; - var category = type?.Category; - if (category != null) return bp.CollationNames(category.ToString(), $"{bp.Cost.ToBinString("⊙".yellow())}"); - if (type != null) return bp.CollationNames(type.NameSafe(), $"{bp.Cost.ToBinString("⊙".yellow())}"); - return bp.CollationNames("?", $"{bp.Cost.ToBinString("⊙".yellow())}"); - }), - new NamedTypeFilter<BlueprintItemArmor>("Armor", null, (bp) => { - var type = bp.Type; - if (type != null) return bp.CollationNames(type.DefaultName, $"{bp.Cost.ToBinString("⊙".yellow())}"); - return bp.CollationNames("?", $"{bp.Cost.ToBinString("⊙".yellow())}"); - }), - new NamedTypeFilter<BlueprintItemEquipmentUsable>("Usable", null, bp => bp.CollationNames(bp.SubtypeName, $"{bp.Cost.ToBinString("⊙".yellow())}")), - new NamedTypeFilter<BlueprintIngredient>("Ingredient", null, bp => bp.CollationNames()), - new NamedTypeFilter<BlueprintUnit>("Units", null, bp => bp.CollationNames(bp.Type?.Name ?? bp.Race?.Name ?? "?", $"CR{bp.CR}")), - new NamedTypeFilter<BlueprintUnit>("Units CR", null, bp => bp.CollationNames($"CR {bp.CR}")), - new NamedTypeFilter<BlueprintRace>("Races", null, bp => bp.CollationNames()), - new NamedTypeFilter<BlueprintArea>("Areas", null, bp => bp.CollationNames()), - //new NamedTypeFilter<BlueprintAreaPart>("Area Parts", null, bp => bp.CollationName()), - new NamedTypeFilter<BlueprintAreaEnterPoint>("Area Entry", null, bp =>bp.CollationNames(bp.m_Area.NameSafe())), - //new NamedTypeFilter<BlueprintAreaEnterPoint>("AreaEntry ", null, bp => bp.m_Tooltip.ToString()), - new NamedTypeFilter<BlueprintGlobalMapPoint>("Map Points", null, bp => bp.CollationNames(bp.GlobalMapZone.ToString())), - new NamedTypeFilter<BlueprintGlobalMap>("Global Map"), - new NamedTypeFilter<Cutscene>("Cut Scenes", null, bp => bp.CollationNames(bp.Priority.ToString())), - //new NamedTypeFilter<BlueprintMythicInfo>("Mythic Info"), - new NamedTypeFilter<BlueprintQuest>("Quests", null, bp => bp.CollationNames(bp.m_Type.ToString())), - new NamedTypeFilter<BlueprintQuestObjective>("QuestObj", null, bp => bp.CaptionCollationNames()), - new NamedTypeFilter<BlueprintEtude>("Etudes", null, bp =>bp.CollationNames(bp.Parent?.GetBlueprint().NameSafe() ?? "" )), - new NamedTypeFilter<BlueprintUnlockableFlag>("Flags", null, bp => bp.CaptionCollationNames()), - new NamedTypeFilter<BlueprintDialog>("Dialog",null, bp => bp.CaptionCollationNames()), - new NamedTypeFilter<BlueprintCue>("Cues", null, bp => { - if (bp.Conditions.HasConditions) { - return bp.CollationNames(bp.Conditions.Conditions.First().NameSafe().SubstringBetweenCharacters('$', '$')); - } - return new List<string> { "-" }; - }), - new NamedTypeFilter<BlueprintAnswer>("Answer", null, bp => bp.CaptionCollationNames()), - new NamedTypeFilter<BlueprintArmyPreset>("Armies", null, bp => bp.CollationNames()), - new NamedTypeFilter<BlueprintLeaderSkill>("ArmyGeneralSkill", null, bp => bp.CollationNames()), -#if false - new NamedTypeFilter<BlueprintItemEquipment>("Equip (ench)", null, (bp) => { - try { - var enchants = bp.CollectEnchantments(); - var value = enchants.Sum((e) => e.EnchantmentCost); - return new List<string> { value.ToString() }; - } - catch { - return new List<string> { "0" }; - } - }), - new NamedTypeFilter<BlueprintItemEquipment>("Equip (cost)", null, (bp) => new List<string> {bp.Cost.ToBinString() }), -#endif - //new NamedTypeFilter<SimpleBlueprint>("In Memory", null, bp => bp.CollationName(), () => ResourcesLibrary.s_LoadedBlueprints.Values.Where(bp => bp != null)), - - }; - - public static NamedTypeFilter selectedTypeFilter = null; - - public static IEnumerable<SimpleBlueprint> blueprints = null; - - public static void ResetSearch() { - filteredBPs = null; - filteredBPNames = null; - collatedBPs = null; - ReflectionTreeView.ClearExpanded(); - BlueprintListUI.needsLayout = true; - } - public static void ResetGUI() { - ResetSearch(); - Settings.selectedBPTypeFilter = 1; - } - public static void UpdatePageCount() { - if (Settings.searchLimit > 0) { - pageCount = matchCount / Settings.searchLimit; - currentPage = Math.Min(currentPage, pageCount); - } - else { - pageCount = 1; - currentPage = 1; - } - } - public static void UpdatePaginatedResults() { - var limit = Settings.searchLimit; - var count = unpagedBPs.Count(); - var offset = Math.Min(count, currentPage * limit); - limit = Math.Min(limit, Math.Max(count, count - limit)); - Mod.Trace($"{currentPage} / {pageCount} count: {count} => offset: {offset} limit: {limit} "); - filteredBPs = unpagedBPs.Skip(offset).Take(limit).ToArray(); - filteredBPNames = filteredBPs.Select(b => b.NameSafe()).ToArray(); - - } - public static void UpdateSearchResults() { - if (blueprints == null) return; - selectedCollationIndex = 0; - selectedCollatedBPs = null; - BlueprintListUI.needsLayout = true; - if (Settings.searchText.Trim().Length == 0) { - ResetSearch(); - } - var searchText = Settings.searchText; - var terms = searchText.Split(' ').Select(s => s.ToLower()).ToHashSet(); - selectedTypeFilter = blueprintTypeFilters[Settings.selectedBPTypeFilter]; - var selectedType = selectedTypeFilter.type; - IEnumerable<SimpleBlueprint> bps = null; - if (selectedTypeFilter.blueprintSource != null) bps = selectedTypeFilter.blueprintSource(); - else bps = from bp in BlueprintExtensions.BlueprintsOfType(selectedType) - where selectedTypeFilter.filter(bp) - select bp; - var filtered = new List<SimpleBlueprint>(); - foreach (var blueprint in bps) { - if (blueprint.AssetGuid.ToString().Contains(searchText) - || blueprint.GetType().ToString().Contains(searchText)) { - filtered.Add(blueprint); - } - else { - var name = GetTitle(blueprint); - var displayName = blueprint.GetDisplayName(); - var description = blueprint.GetDescription() ?? ""; - if (terms.All(term => name.Matches(term)) - || terms.All(term => displayName.Matches(term)) - || Settings.searchDescriptions && - ( terms.All(term => description.Matches(term)) - || blueprint is BlueprintItem itemBP - && terms.All(term => { - try { - return itemBP.FlavorText.Matches(term); - } catch (NullReferenceException e) { - return false; - } - }) - ) - ) { - filtered.Add(blueprint); - } - } - } - filteredBPs = filtered.OrderBy(bp => bp.NameSafe()); - matchCount = filtered.Count(); - UpdatePageCount(); - for (var i = 0; i < BlueprintListUI.ParamSelected.Length; i++) { - BlueprintListUI.ParamSelected[i] = 0; - } - uncolatedMatchCount = matchCount; - if (selectedTypeFilter.collator != null) { - collatedBPs = (from bp in filtered - from key in selectedTypeFilter.collator(bp) - //where selectedTypeFilter.collator(bp).Contains(key) // this line causes a mutation error - group bp by key into g - orderby g.Key.LongSortKey(), g.Key - select g).ToDictionary(g => g.Key, g => g.ToList().Distinct().ToList()); - _ = collatedBPs.Count(); - var keys = collatedBPs.ToList().Select(cbp => cbp.Key).ToList(); - collationKeys = new List<string> { "All" }; - collationKeys.AddRange(keys); - var titles = collatedBPs.ToList().Select(cbp => $"{cbp.Key} ({cbp.Value.Count()})").ToList(); - collationTitles = new List<string> { $"All ({filtered.Count()})" }; - collationTitles.AddRange(titles); - } - else { - collationKeys = null; - collationTitles = null; - } - - unpagedBPs = filteredBPs; - UpdatePaginatedResults(); - firstSearch = false; - UpdateCollation(); - } - public static void UpdateCollation() { - if (collationKeys == null || collatedBPs == null) return; - var selectedKey = collationKeys.ElementAt(selectedCollationIndex); - foreach (var pair in collatedBPs) { - if (pair.Key == selectedKey) { - matchCount = pair.Value.Count(); - selectedCollatedBPs = pair.Value.Take(Settings.searchLimit).Distinct().ToArray(); - UpdatePageCount(); - } - } - BlueprintListUI.needsLayout = true; - } - public static void OnGUI() { - if (blueprints == null) { - blueprints = BlueprintLoader.Shared.GetBlueprints(); - if (blueprints != null) UpdateSearchResults(); - } - // Stackable browser - using (HorizontalScope(Width(350))) { - var remainingWidth = ummWidth; - // First column - Type Selection Grid - using (VerticalScope(GUI.skin.box)) { - ActionSelectionGrid(ref Settings.selectedBPTypeFilter, - blueprintTypeFilters.Select(tf => tf.name).ToArray(), - 1, - (selected) => { UpdateSearchResults(); }, - buttonStyle, - Width(200)); - } - remainingWidth -= 350; - var collationChanged = false; - if (collatedBPs != null && collationTitles != null) { - using (VerticalScope(GUI.skin.box)) { - var selectedKey = collationTitles.ElementAt(selectedCollationIndex); - if (VPicker("Categories", ref selectedKey, collationTitles, null, s => s, ref collationSearchText, Width(300))) { - collationChanged = true; BlueprintListUI.needsLayout = true; - } - if (selectedKey != null) - selectedCollationIndex = collationTitles.IndexOf(selectedKey); - -#if false - UI.ActionSelectionGrid(ref selectedCollationIndex, collationKeys.ToArray(), - 1, - (selected) => { collationChanged = true; BlueprintListUI.needsLayout = true; }, - UI.buttonStyle, - UI.Width(200)); -#endif - } - remainingWidth -= 450; - } - - // Section Column - Main Area - using (VerticalScope(MinWidth(remainingWidth))) { - // Search Field and modifiers - using (HorizontalScope()) { - ActionTextField( - ref Settings.searchText, - "searchText", - (text) => { }, - () => UpdateSearchResults(), - Width(400)); - 50.space(); - Label("Limit", AutoWidth()); - 15.space(); - ActionIntTextField( - ref Settings.searchLimit, - "searchLimit", - (limit) => { }, - () => UpdateSearchResults(), - Width(75)); - if (Settings.searchLimit > 1000) { Settings.searchLimit = 1000; } - 25.space(); - if (Toggle("Search Descriptions", ref Settings.searchDescriptions, AutoWidth())) UpdateSearchResults(); - 25.space(); - if (Toggle("Attributes", ref Settings.showAttributes, AutoWidth())) UpdateSearchResults(); - 25.space(); - Toggle("Show GUIDs", ref Settings.showAssetIDs, AutoWidth()); - 25.space(); - Toggle("Components", ref Settings.showComponents, AutoWidth()); - 25.space(); - Toggle("Elements", ref Settings.showElements, AutoWidth()); - 25.space(); - Toggle("Show Display & Internal Names", ref Settings.showDisplayAndInternalNames, AutoWidth()); - } - // Search Button and Results Summary - using (HorizontalScope()) { - ActionButton("Search", () => { - UpdateSearchResults(); - }, AutoWidth()); - Space(25); - if (firstSearch) { - Label("please note the first search may take a few seconds.".green(), AutoWidth()); - } - else if (matchCount > 0) { - var title = "Matches: ".green().bold() + $"{matchCount}".orange().bold(); - if (matchCount > Settings.searchLimit) { title += " => ".cyan() + $"{Settings.searchLimit}".cyan().bold(); } - Label(title, ExpandWidth(false)); - } - Space(130); - Label($"Page: ".green() + $"{Math.Min(currentPage + 1, pageCount + 1)}".orange() + " / " + $"{pageCount + 1}".cyan(), AutoWidth()); - ActionButton("-", () => { - currentPage = Math.Max(currentPage -= 1, 0); - UpdatePaginatedResults(); - }, AutoWidth()); - ActionButton("+", () => { - currentPage = Math.Min(currentPage += 1, pageCount); - UpdatePaginatedResults(); - }, AutoWidth()); - Space(25); - var pageNum = currentPage + 1; - if (Slider(ref pageNum, 1, pageCount + 1, 1)) UpdatePaginatedResults(); - currentPage = pageNum - 1; - } - Space(10); - - if (filteredBPs != null) { - CharacterPicker.OnCharacterPickerGUI(); - UnitReference selected = CharacterPicker.GetSelectedCharacter(); - var bps = filteredBPs; - if (selectedCollationIndex == 0) { - selectedCollatedBPs = null; - matchCount = uncolatedMatchCount; - UpdatePageCount(); - } - if (selectedCollationIndex > 0) { - if (collationChanged) { - UpdateCollation(); - } - bps = selectedCollatedBPs; - } - BlueprintListUI.OnGUI(selected, bps, 0, remainingWidth, null, selectedTypeFilter, (keys) => { - if (keys.Length > 0) { - var changed = false; - //var bpTypeName = keys[0]; - //var newTypeFilterIndex = blueprintTypeFilters.FindIndex(f => f.type.Name == bpTypeName); - //if (newTypeFilterIndex >= 0) { - // settings.selectedBPTypeFilter = newTypeFilterIndex; - // changed = true; - //} - if (keys.Length > 1) { - var collationKey = keys[1]; - var newCollationIndex = collationKeys.FindIndex(ck => ck == collationKey); - if (newCollationIndex >= 0) { - selectedCollationIndex = newCollationIndex; - UpdateCollation(); - } - } - if (changed) { - UpdateSearchResults(); - } - } - }).ForEach(action => action()); - } - Space(25); - } - } - } - } -} \ No newline at end of file diff --git a/ToyBox/classes/MainUI/Crusade/ArmiesEditor.cs b/ToyBox/classes/MainUI/Crusade/ArmiesEditor.cs deleted file mode 100644 index 75f927dda..000000000 --- a/ToyBox/classes/MainUI/Crusade/ArmiesEditor.cs +++ /dev/null @@ -1,628 +0,0 @@ -using Kingmaker; -using Kingmaker.Armies; -using Kingmaker.Armies.Blueprints; -using Kingmaker.Armies.State; -using Kingmaker.Blueprints; -using Kingmaker.Blueprints.Root; -using Kingmaker.Globalmap.State; -using Kingmaker.Globalmap.View; -using Kingmaker.Kingdom; -using Kingmaker.PubSubSystem; -using Kingmaker.UnitLogic.Abilities.Blueprints; -using ModKit; -using ModKit.Utility; -using System.Collections.Generic; -using System.Linq; -using UnityModManagerNet; -using static ModKit.UI; - -namespace ToyBox.classes.MainUI { - public static class ArmiesEditor { - public static Settings settings => Main.Settings; - - public static IEnumerable<(GlobalMapArmyState, float)> armies; - public static IEnumerable<(GlobalMapArmyState, float)> playerArmies; - public static IEnumerable<(GlobalMapArmyState, float)> demonArmies; - public static string skillsSearchText = ""; - public static Browser<BlueprintUnit, BlueprintUnit> mercenaryBrowser = new(true, true); - - public static void OnShowGUI() => UpdateArmies(); - public static void UpdateArmies() { - armies = ArmiesByDistanceFromPlayer()?.ToList(); - if (armies != null) { - playerArmies = from army in armies - where army.Item1.Data.Faction == ArmyFaction.Crusaders - select army; - demonArmies = from army in armies - where army.Item1.Data.Faction == ArmyFaction.Demons - select army; - } - } - - private static readonly Dictionary<string, GlobalMapArmyState> armySelection = new(); - private static Dictionary<object, bool> toggleStates = new(); - private static Dictionary<object, bool> toggleShowSquadStates = new(); - public static IEnumerable<BlueprintLeaderSkill> allLeaderSkills; - public static IEnumerable<BlueprintLeaderSkill> GetAllLeaderSkills() { - if (allLeaderSkills != null) return allLeaderSkills; - else { - allLeaderSkills = BlueprintLoader.Shared.GetBlueprints<BlueprintLeaderSkill>(); - return allLeaderSkills; - } - } - public static IEnumerable<BlueprintAbility> allLeaderAbilities; - public static IEnumerable<BlueprintAbility> GetAllLeaderAbilities() { - if (allLeaderAbilities != null) return allLeaderAbilities; - else { - allLeaderAbilities = BlueprintLoader.Shared.GetBlueprints<BlueprintAbility>(); - return allLeaderAbilities; - } - } - - public static readonly object locker = new(); - public static Dictionary<int, bool> IsInMercenaryPool = new(); - public static Dictionary<int, bool> IsInRecruitPool = new(); - public static IEnumerable<BlueprintUnit> armyBlueprints; - public static IEnumerable<BlueprintUnit> recruitPool; - public static bool poolChanged = true; - public static List<BlueprintUnit> mercenaryUnits; - - public static void LoadMercenaryData() { - armyBlueprints = BlueprintExtensions.GetBlueprints<BlueprintUnit>().Where((u) => u.NameSafe().StartsWith("Army")); - IEnumerable<BlueprintUnit> recruitPool = KingdomState.Instance.RecruitsManager.Pool.Select((r) => r.Unit); - foreach (var entry in armyBlueprints) { - IsInRecruitPool[entry.GetHashCode()] = recruitPool.Contains(entry); - IsInMercenaryPool[entry.GetHashCode()] = KingdomState.Instance.MercenariesManager.HasUnitInPool(entry); - } - mercenaryBrowser?.ReloadData(); - } - public static void AddAllCurrentUnits() { - var playerArmies = from army in ArmiesByDistanceFromPlayer() - where army.Item1.Data.Faction == ArmyFaction.Crusaders - select army; - foreach (var army in playerArmies) { - foreach (var squad in army.Item1.Data.Squads) { - var unit = squad.Unit.GetHashCode(); - bool hasMercenary = IsInMercenaryPool.ContainsKey(unit) && IsInMercenaryPool[unit]; - bool hasRecruit = IsInRecruitPool.ContainsKey(unit) && IsInRecruitPool[unit]; - if (!hasMercenary && !hasRecruit) { - KingdomState.Instance.MercenariesManager.AddMercenary(squad.Unit, 1); - } - } - LoadMercenaryData(); - } - } - public static bool discloseMercenaryUnits = false; - private const string RerollAll = "Reroll Mercs"; - - public static void OnLoad() { - KeyBindings.RegisterAction(RerollAll, () => { - var mercenaryManager = KingdomState.Instance.MercenariesManager; - mercenaryManager.CurrentSlots.RemoveAll((v) => true); - mercenaryManager.RollSlots(mercenaryManager.MaxAllowedSlots - mercenaryManager.CurrentSlots.Count); - EventBus.RaiseEvent<IArmyMercenarySlotsHandler>(delegate (IArmyMercenarySlotsHandler h) { - h.HandleSlotsRerolled(); - }); - }); - } - public static void OnGUI() { - if (Game.Instance?.Player == null) return; - var kingdom = KingdomState.Instance; - if (kingdom == null) { - Label("You must unlock the crusade before you can access these toys.".yellow().bold()); - return; - } - HStack("Tweaks", 1, - () => Toggle("Infinite Mercenary Rerolls", ref settings.toggleInfiniteArmyRerolls), - () => { - Toggle("Experimental - Enable Large Player Armies", ref settings.toggleLargeArmies); - if (settings.toggleLargeArmies) { - BlueprintRoot.Instance.Kingdom.StartArmySquadsCount = 14; - BlueprintRoot.Instance.Kingdom.MaxArmySquadsCount = 14; - } - else { - BlueprintRoot.Instance.Kingdom.StartArmySquadsCount = 4; - BlueprintRoot.Instance.Kingdom.MaxArmySquadsCount = 7; - } - }, - () => Slider("Recruitment Cost", ref settings.recruitmentCost, 0f, 1f, 1f, 2, "", AutoWidth()), - () => LogSlider("Number of Recruits", ref settings.recruitmentMultiplier, 0f, 100, 1, 1, "", - AutoWidth()), - () => LogSlider("Army Experience Multiplier", ref settings.armyExperienceMultiplier, 0f, 100, 1, 1, "", - AutoWidth()), - () => LogSlider("After Army Battle Raise Multiplier", ref settings.postBattleSummonMultiplier, 0f, 100, - 1, 1, "", AutoWidth()), - () => Slider("Player Leader Ability Strength", ref settings.playerLeaderPowerMultiplier, 0f, 10f, 1f, 2, "", AutoWidth()), - () => Slider("Enemy Leader Ability Strength", ref settings.enemyLeaderPowerMultiplier, 0f, 5f, 1f, 2, "", AutoWidth()) - ); - Div(0, 25); - var mercenaryManager = KingdomState.Instance.MercenariesManager; - var recruitsManager = KingdomState.Instance.RecruitsManager; - if (poolChanged) { - mercenaryUnits = mercenaryManager.Pool.Select((u) => u.Unit).ToList(); - foreach (var unit in mercenaryUnits) { - IsInMercenaryPool[unit.GetHashCode()] = true; - } - recruitPool = KingdomState.Instance.RecruitsManager.Pool.Select((r) => r.Unit); - mercenaryUnits.AddRange(recruitPool); - } - HStack("Mercenaries", 1, - () => { - ActionButton("Add All Units", () => AddAllCurrentUnits(), 200.width()); - 110.space(); - Label("Adds all currently active friendly units that are neither recruitable nor Mercanries to Mercenary units.".green()); - }, - () => { - BindableActionButton(RerollAll, 200.width()); - Space(-93); - Label("Rerolls Mercenary Units for free.".green()); - }, - () => { - ValueAdjustorEditable("Mercenary Slots", () => mercenaryManager.MaxAllowedSlots, - v => mercenaryManager.AddSlotsCount(v - mercenaryManager.MaxAllowedSlots), 1, 0, 200); - }, - () => { - using (VerticalScope()) { - Toggle("Add new units in friendly armies to Mercenary Pool if not Recruitable.".cyan(), ref settings.toggleAddNewUnitsAsMercenaries, AutoWidth()); - 10.space(); - Div(); - 15.space(); - } - }, - () => DisclosureToggle("Show Recruitment Pools".Orange(), ref discloseMercenaryUnits), - () => { - if (discloseMercenaryUnits) { - using (VerticalScope()) { - mercenaryBrowser.OnGUI( - mercenaryUnits, - () => { - if (armyBlueprints == null || armyBlueprints?.Count() == 0) { - LoadMercenaryData(); - } - return armyBlueprints; - }, - (unit) => unit, - (unit) => IsInRecruitPool.GetValueOrDefault(unit.GetHashCode(), false) ? unit.GetDisplayName().orange().bold() : unit.GetDisplayName(), - (unit) => $"{unit.NameSafe()} {unit.GetDisplayName()} {unit.Description}", - () => { - var bluh = ummWidth - 50; - var titleWidth = (bluh / (IsWide ? 3.0f : 4.0f)) - 100; - TitleLabel("Unit", Width((int)titleWidth)); - 125.space(); - TitleLabel("Action", Width(210)); - 20.space(); - TitleLabel("Pool", Width(200)); - 20.space(); - TitleLabel("Recruitment Weight (Mercenary only)", AutoWidth()); - }, - (unit, _) => { - var bluh = ummWidth - 50; - var titleWidth = (bluh / (IsWide ? 3.0f : 4.0f)) - 100; - bool isInMercPool = IsInMercenaryPool.GetValueOrDefault(unit.GetHashCode(), false); - bool isInKingdomPool = IsInRecruitPool.GetValueOrDefault(unit.GetHashCode(), recruitPool.Contains(unit)); - var title = unit.GetDisplayName(); - if (isInKingdomPool) - title = title.orange().bold(); - else if (isInMercPool) - title = title.cyan().bold(); - Label(title, Width((int)titleWidth)); - ActionButton(isInMercPool ? "Rem Merc" : "Add Merc", - () => { - mercenaryBrowser.needsReloadData = true; - if (isInMercPool) { - mercenaryManager.RemoveMercenary(unit); - isInMercPool = false; - } - else { - mercenaryManager.AddMercenary(unit, 1); - isInMercPool = true; - } - IsInMercenaryPool[unit.GetHashCode()] = isInMercPool; - }, 150.width()); - 10.space(); - ActionButton(isInKingdomPool ? "Rem Recruit" : "Add Recruit", () => { - mercenaryBrowser.needsReloadData = true; - if (isInKingdomPool) { - var count = recruitsManager.GetCountInPool(unit); - recruitsManager.DecreasePool(unit, count); - isInKingdomPool = false; - } - else { - var pool = recruitsManager.Pool; - var count = pool.Sum(r => r.Count) / pool.Count; - recruitsManager.IncreasePool(unit, count); - isInKingdomPool = true; - } - IsInRecruitPool[unit.GetHashCode()] = isInKingdomPool; - }, 150.width()); - var poolText = $"{(isInMercPool ? $"Merc".cyan() : "")} {(isInKingdomPool ? $"Recruit ({recruitsManager.GetCountInPool(unit)})".orange() : "")}".Trim(); - 50.space(); - Label(poolText, Width(200)); - 25.space(); - if (isInMercPool) { - var poolInfo = mercenaryManager.Pool.FirstOrDefault(pi => pi.Unit == unit); - if (poolInfo != null) { - var weight = poolInfo.Weight; - if (LogSliderCustomLabelWidth("Weight", ref weight, 0.01f, 1000, 1, 2, "", 70, AutoWidth())) { - poolInfo.UpdateWeight(weight); - } - } - else { - Label("Weird", AutoWidth()); - } - } - }); - } - } - }); - - Div(0, 25); - - if (armies == null) - UpdateArmies(); - if (playerArmies != null) - ArmiesGUI("Player Armies", playerArmies); - if (playerArmies != null && demonArmies != null) - Div(0, 25, 0); - if (demonArmies != null) - ArmiesGUI("Demon Armies", demonArmies); - } - public static void ArmiesGUI(string title, IEnumerable<(GlobalMapArmyState, float)> armies) { - if (armies.Count() == 0) return; - var selectedArmy = armySelection.GetValueOrDefault(title, null); - using (VerticalScope()) { - HStack(title, 1, - () => { - Label("Name", MinWidth(100), MaxWidth(250)); - Label("Type", MinWidth(100), MaxWidth(250)); - Label("Leader", Width(350)); - Label("Squad Count", Width(150)); - Space(55); - Label("Location", Width(400)); - Space(25); - Label("Dist"); - }, - () => { - using (VerticalScope()) { - var last = armies.Last().Item1; - foreach (var armyEntry in armies) { - var showLeader = false; - var showSquads = false; - var showAllLeaderSkills = false; - var showAllRituals = false; - var army = armyEntry.Item1; - var leader = army.Data.Leader; - var distance = armyEntry.Item2; - var showAddSquad = false; - BlueprintUnit squadToAdd = null; - - using (HorizontalScope()) { - Label(army.Data.ArmyName.ToString().orange().bold(), MinWidth(100), MaxWidth(250)); - Label(army.ArmyType.ToString().cyan(), MinWidth(100), MaxWidth(250)); - if (leader != null) { - showLeader = toggleStates.GetValueOrDefault(leader, false); - if (DisclosureToggle(leader.LocalizedName, ref showLeader, 350)) { - selectedArmy = army == selectedArmy ? null : army; - toggleStates[leader] = showLeader; - } - } - else Space(353); - var squads = army.Data.Squads; - Label(squads.Count.ToString().cyan(), Width(35)); - showSquads = toggleStates.GetValueOrDefault(squads, false); - if (DisclosureToggle("Squads", ref showSquads, 125)) { - selectedArmy = army == selectedArmy ? null : army; - toggleStates[squads] = showSquads; - - } - Space(50); - var displayName = army.Location?.GetDisplayName() ?? "traveling on a path"; - Label(displayName.yellow(), Width(400)); - Space(25); - var distStr = distance >= 0 ? $"{distance:0.#}" : "-"; - Label(distStr, Width(50)); - Space(50); - ActionButton("Teleport", () => TeleportToArmy(army), Width(150)); - Space(25); - if (GlobalMapView.Instance != null) { - ActionButton("Summon", () => SummonArmy(army), Width(150)); - } - Space(25); - if (army.Data.Faction == ArmyFaction.Crusaders) { - ActionButton("Full MP", () => { - var additionalMP = army.Data.GetArmyBonusSkills().Select(a => a.DailyMovementPoints); - army.RestoreMovementPoints(40 + additionalMP.Sum()); - }, Width(150)); - } - Space(25); - ActionButton("Destroy", () => { - // army.Data.RemoveAllSquads(); - Game.Instance.Player.GlobalMap.LastActivated.DestroyArmy(army); - UpdateArmies(); - }, Width(150)); - } - if (showLeader) { - Div(0, 10); - showAllLeaderSkills = toggleStates.GetValueOrDefault(leader.Skills, false); - showAllRituals = toggleStates.GetValueOrDefault(leader.m_RitualSlots, false); - using (VerticalScope()) { - using (HorizontalScope()) { - Space(100); - using (VerticalScope()) { - Label("Stats".yellow()); - ValueAdjuster("Level".cyan(), () => leader.Level, (l) => leader.m_Level = l, 1, 0, 20, 375.width()); - ValueAdjustorEditable("Experience".cyan(), () => leader.Experience, (e) => leader.m_Experience = e, 100, 0, int.MaxValue, 375.width()); - var stats = leader.Stats; - ValueAdjuster("Attack Bonus".cyan(), - () => stats.AttackBonus.BaseValue, - (v) => stats.AttackBonus.BaseValue = v, 1, - stats.AttackBonus.MinValue, - stats.AttackBonus.MaxValue, - Width(375)); - ValueAdjuster("Defense Bonus".cyan(), - () => stats.DefenseBonus.BaseValue, - (v) => stats.DefenseBonus.BaseValue = v, 1, - stats.DefenseBonus.MinValue, - stats.DefenseBonus.MaxValue, - Width(375)); - ValueAdjuster("Infirmary Size".cyan(), - () => stats.InfirmarySize.BaseValue, - (v) => stats.InfirmarySize.BaseValue = v, 25, - stats.InfirmarySize.MinValue, - stats.InfirmarySize.MaxValue, Width(375)); - ValueAdjuster("Mana".cyan(), - () => stats.CurrentMana, - (v) => stats.CurrentMana = v, 5, - stats.MaxMana.MinValue, - stats.MaxMana.MaxValue, - Width(375)); - ValueAdjuster("Max Mana".cyan(), - () => stats.MaxMana.BaseValue, - (v) => stats.MaxMana.BaseValue = v, 5, - stats.MaxMana.MinValue, - stats.MaxMana.MaxValue, - Width(375)); - ValueAdjuster("Mana Regen".cyan(), - () => stats.ManaRegeneration.BaseValue, - (v) => stats.ManaRegeneration.BaseValue = v, 1, - stats.ManaRegeneration.MinValue, - stats.ManaRegeneration.MaxValue, - Width(375)); - ValueAdjuster("Spell Strength".cyan(), - () => stats.SpellStrength.BaseValue, - (v) => stats.SpellStrength.BaseValue = v, 1, - stats.SpellStrength.MinValue, - stats.SpellStrength.MaxValue, Width(375)); - } - } - using (HorizontalScope()) { - Space(100); - Label("Skills".yellow(), Width(85)); - if (DisclosureToggle("Show All".orange().bold(), ref showAllLeaderSkills, 125)) { - toggleStates[leader.Skills] = showAllLeaderSkills; - } - //UI.Space(285); - //UI.Label("Action".yellow(), UI.Width(150)); - } - using (HorizontalScope()) { - Space(100); - ActionTextField(ref skillsSearchText, "Search", (s) => { }, () => { }, 235.width()); - } - var skills = showAllLeaderSkills ? GetAllLeaderSkills() : leader.Skills; - if (skillsSearchText.Length > 0) { - var searchText = skillsSearchText.ToLower(); - skills = skills.Where( - skill => skill.LocalizedName.ToString().ToLower().Contains(searchText) - || skill.LocalizedDescription.ToString().ToLower().Contains(searchText)); - } - BlueprintLeaderSkill skillToAdd = null; - BlueprintLeaderSkill skillToRemove = null; - if (skills != null) - foreach (var skill in skills) { - var leaderHasSkill = leader.Skills.Contains(skill); - using (HorizontalScope()) { - Space(100); - var skillName = (string)skill.LocalizedName; - if (leaderHasSkill) skillName = skillName.cyan(); - Label(skillName, Width(375)); - Space(25); - if (leaderHasSkill) - ActionButton("Remove", () => { skillToRemove = skill; }, Width(150)); - else - ActionButton("Add", () => { skillToAdd = skill; }, Width(150)); - Space(100); - var description = (string)skill.LocalizedDescription; - Label(description.StripHTML().green()); - } - } - if (skillToAdd != null) leader.AddSkill(skillToAdd, true); - if (skillToRemove != null) leader.RemoveSkill(skillToRemove); -#if false - using (HorizontalScope()) { - Space(100); - Label("Rituals".yellow(), Width(85)); - if (DisclosureToggle("Show All".orange().bold(), ref showAllRituals, 125)) { - toggleStates[leader.m_RitualSlots] = showAllRituals; - } - //UI.Space(285); - //UI.Label("Action".yellow(), UI.Width(150)); - } - - var leaderAbilities = leader.m_RitualSlots.Select(s => (BlueprintAbility)s).Where(s => s != null); - var abilities = showAllRituals ? GetAllLeaderAbilities() : leaderAbilities; - if (abilities != null) { - var canAdd = leaderAbilities.Count() < 14; - foreach (var ability in abilities) { - var leaderHasAbility = leaderAbilities.Contains(ability); - using (HorizontalScope()) { - Space(100); - var name = (string)ability.name; - if (leaderHasAbility) name = name.cyan(); - Label(name, Width(375)); - Space(25); - if (leaderHasAbility) - ActionButton("Remove", () => { }, Width(150)); - else if (canAdd) - ActionButton("Add", () => { }, Width(150)); - else Space(153); - Space(100); - var description = (string)ability.GetDescription(); - Label(description.StripHTML().green()); - } - } - } -#endif - if (!showSquads) - Div(0, 10); - } - } - if (showSquads) { - Div(0, 10); - using (VerticalScope()) { - using (HorizontalScope()) { - Label("Squad Name".yellow(), Width(475)); - Space(25); - Label("Unit Count".yellow(), Width(250)); - } - } - using (VerticalScope()) { - var squads = army.Data.m_Squads; - SquadState squadToRemove = null; - showAddSquad = toggleShowSquadStates.GetValueOrDefault(squads, false); - foreach (var squad in squads) { - using (HorizontalScope()) { - Label(squad.Unit.NameSafe(), Width(475)); - Space(25); - var count = squad.Count; - ActionIntTextField(ref count, - (value) => { - squad.SetCount(value); - }, Width(225) - ); - Space(25); - ActionButton("Remove", () => { - squadToRemove = squad; - }, Width(150)); - } - } - if (title == "Player Armies") { - if (DisclosureToggle("Add Squads".Yellow(), ref showAddSquad, 125)) { - toggleShowSquadStates[squads] = showAddSquad; - } - } - if (showAddSquad) { - Div(0, 10); - var count = 0; - var kingdom = KingdomState.Instance; - var mercenariesManager = kingdom.MercenariesManager; - var mercenariesPool = mercenariesManager.Pool; - var recruitManager = kingdom.RecruitsManager; - var growthPool = recruitManager.Growth; - using (VerticalScope()) { - using (HorizontalScope()) { - Label("Unit Count".cyan(), AutoWidth()); - count = IntTextField(ref settings.unitCount, null, Width(150)); - } - - foreach (var poolInfo in mercenariesPool) { - var unit = poolInfo.Unit; - using (HorizontalScope()) { - Label(unit.NameSafe(), Width(520)); - ActionButton("Add", - () => { - squadToAdd = unit; - }, Width(150)); - } - } - foreach (var poolInfo in growthPool) { - var unit = poolInfo.Unit; - using (HorizontalScope()) { - Label(unit.NameSafe(), Width(520)); - ActionButton("Add", - () => { - squadToAdd = unit; - }, Width(150)); - } - } - } - if (squadToAdd != null) { - var merge = false; - foreach (var squad in army.Data.Squads) { - if (squad.Unit.NameSafe() == squadToAdd.NameSafe()) { - merge = true; - break; - } - } - army.Data.Add(squadToAdd, count, merge, null); - } - } - - if (squadToRemove != null) { - squadToRemove.Army.RemoveSquad(squadToRemove); - } - } - if (army != last) - Div(); - } - } - } - } - ); - if (selectedArmy != null) { - armySelection[title] = selectedArmy; - } - else { - armySelection.Remove(title); - } - } - } - - - - public static GlobalMapState MainGlobalMapState() { - var player = Game.Instance?.Player ?? null; - var globalMapStates = player?.AllGlobalMaps; - var armyMapStates = from mapState in globalMapStates - where mapState.Armies.Count > 0 - select mapState; - if (armyMapStates.Count() == 0) return null; - // The current game only has one map that has armies so we will assume there is one. If DLC changes this when we need to do the calculations below on each of the maps and the player position in them separately - var mainMapState = armyMapStates.First(); - return mainMapState; - } - public static void TeleportToArmy(GlobalMapArmyState army) { - var mapPoint = army.Position.Location; - UnityModManager.UI.Instance.ToggleWindow(); - if (!Teleport.TeleportToGlobalMapPoint(mapPoint)) { - Teleport.TeleportToGlobalMap(() => Teleport.TeleportToGlobalMapPoint(mapPoint)); - } - } - public static void SummonArmy(GlobalMapArmyState army) { - var mainMapState = MainGlobalMapState(); - var position = mainMapState.PlayerPosition; - army.SetCurrentPosition(position); - } - public static float ArmyDistance(this GlobalMapState mapState, GlobalMapArmyState army, GlobalMapPosition position) { - var dist = -1.0f; - try { - var location = position.Location; - var travelData = mapState?.PathManager?.CalculateArmyPathToPosition(army, position); - var length = travelData?.GetLength(false); - dist = length.HasValue ? length.GetValueOrDefault() : -1.0f; - } - catch { } - return dist; - } - public static IEnumerable<(GlobalMapArmyState, float)> ArmiesByDistanceFromPlayer() { - if (!Main.IsInGame || GlobalMapView.Instance is null) return null; - var mainMapState = MainGlobalMapState(); - if (mainMapState == null) return null; - var armies = mainMapState.Armies; - var position = mainMapState.PlayerPosition; - var results = from army in armies select (army, mainMapState.ArmyDistance(army, position)); - results = from item in results where item.Item2 >= 0 || item.army.IsRevealed select item; - results = results.OrderBy(r => r.Item2).ThenBy(r => r.army.Location?.GetDisplayName() ?? "traveling on a path"); - return results; - } - } -} diff --git a/ToyBox/classes/MainUI/Crusade/CrusadeEditor.cs b/ToyBox/classes/MainUI/Crusade/CrusadeEditor.cs deleted file mode 100644 index ec6ba9285..000000000 --- a/ToyBox/classes/MainUI/Crusade/CrusadeEditor.cs +++ /dev/null @@ -1,191 +0,0 @@ -using Kingmaker; -using Kingmaker.Armies; -using Kingmaker.Armies.State; -using Kingmaker.Blueprints; -using Kingmaker.Blueprints.Root; -using Kingmaker.Globalmap.State; -using Kingmaker.Kingdom; -using Kingmaker.Kingdom.Blueprints; -using ModKit; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; - -namespace ToyBox.classes.MainUI { - public static class CrusadeEditor { - public static void ResetGUI() { } - public static Settings settings => Main.Settings; - - public static void OnGUI() { - if (Game.Instance?.Player == null) return; - var ks = KingdomState.Instance; - if (ks == null) { - UI.Label("You must unlock the crusade before you can access these toys.".yellow().bold()); - return; - } - var moraleState = ks.MoraleState; - UI.HStack("Morale", 1, - () => { - UI.Toggle("Flags always green", ref settings.toggleCrusadeFlagsStayGreen); - KingdomCheats.AddMorale(); - }, - () => { - var value = moraleState.CurrentValue; - UI.Slider("Morale", ref value, moraleState.MinValue, moraleState.MaxValue, 1, "", UI.AutoWidth()); - moraleState.CurrentValue = value; - }, - () => { - var value = moraleState.MaxValue; - UI.Slider("Max Morale", ref value, -200, 200, 20, "", UI.AutoWidth()); - moraleState.MaxValue = value; - }, - () => { - var value = moraleState.MinValue; - UI.Slider("Min Morale", ref value, -200, 200, -100, "", UI.AutoWidth()); - moraleState.MinValue = value; - }, - () => { } - ); - UI.Div(0, 25); - UI.HStack("Kingdom", 1, - () => { - UI.Label("increment".cyan(), UI.Width(325)); - var increment = UI.IntTextField(ref settings.increment, null, UI.Width(150)); - UI.Space(25); - UI.Label("Experimental".orange().bold()); - }, - () => { - using (UI.VerticalScope()) { - UI.Space(15); - UI.Div(0, 0, 800); - using (UI.HorizontalScope()) { - UI.Label("Kingdom Stat", UI.Width(325)); - UI.Label("Rank", UI.Width(150)); - UI.Label("Experience", UI.Width(150)); - UI.Label("Next Rank", UI.Width(150)); - } - foreach (var kingdomStat in ks.Stats) { - var conditions = KingdomRoot.Instance.RankUps.Conditions[kingdomStat.Type]; - using (UI.HorizontalScope()) { - var rank = kingdomStat.Rank; - var exp = kingdomStat.Value.ToString().orange(); - var required = conditions.GetRequiredStatValue(kingdomStat.Rank + 1).ToString().cyan(); - UI.ValueAdjuster(kingdomStat.Type.ToString(), () => kingdomStat.Rank, v => kingdomStat.Rank = v, 1, 0, 8); - UI.Space(42); - UI.Label(exp, UI.Width(150)); - UI.Label(required, UI.Width(150)); - UI.Space(10); - UI.ActionButton($"Gain {settings.increment}", () => { - kingdomStat.Value += settings.increment; - }, UI.AutoWidth()); - UI.ActionButton($"Lose {settings.increment}", () => { - kingdomStat.Value -= settings.increment; - }, UI.AutoWidth()); - } - } - UI.Div(0, 0, 800); - UI.DescriptiveLabel("Cost Modifiers", "The following modifiers all work on ".green() + "cost = cost (1 + modifier) ".yellow() + "so a value of ".green() + "-1".yellow() + " means the cost is free, ".green() + "0".yellow() + " is normal cost and ".green() + "2".yellow() + " increases it 3x".green()); - UI.Slider("Claim Cost Modifier", () => ks.ClaimCostModifier, v => ks.ClaimCostModifier = v, -1, 2, 0, 1); - UI.Slider("Claim Time Modifier", () => ks.ClaimTimeModifier, v => ks.ClaimTimeModifier = v, -1, 2, 0, 1); - UI.Slider("Rankup Time Modifer", () => ks.RankupTimeModifier, v => ks.RankupTimeModifier = v, -1, 2, 0, 1); - UI.Slider("Build Time Modifier", ref settings.kingdomBuildingTimeModifier, -1, 2, 0, 1); - UI.Div(0, 0, 800); - UI.DescriptiveLabel("Random Encounters", "The following modifiers all work on ".green() + "chance = chance (1 + modifier) ".yellow() + "so a value of ".green() + "-1".yellow() + " means the chance is 0, ".green() + "0".yellow() + " is chance cost and ".green() + "2".yellow() + " increases it 3x".green()); - UI.Slider("% Chance (Unclaimed)", () => ks.REModifierUnclaimed, v => ks.REModifierUnclaimed = v, -1f, 2f, 0f, 1); - UI.Slider("% Chance (Claimed)", () => ks.REModifierClaimed, v => ks.REModifierClaimed = v, -1, 2, -0.5f, 1); - UI.Slider("% Chance (Upgraded)", () => ks.REModifierUpgraded, v => ks.REModifierUnclaimed = v, -1f, 2f, -1f, 1); - UI.Div(0, 0, 800); - UI.ValueAdjuster("Confidence (Royal Court)", () => ks.RoyalCourtConfidence, v => ks.RoyalCourtConfidence = v, 1, 0, int.MaxValue); - UI.ValueAdjuster("Confidence (Nobles)", () => ks.NobilityConfidence, v => ks.NobilityConfidence = v, 1, 0, int.MaxValue); - UI.ValueAdjuster("Victories This Week", () => ks.VictoriesThisWeek, v => ks.VictoriesThisWeek = v, 1, 0, int.MaxValue); - UI.EnumGrid("Unrest", () => ks.Unrest, (u) => ks.Unrest = u); - UI.AlignmentGrid("Alignment", ks.Alignment, (a) => ks.Alignment = a, UI.Width(325)); - } - }, - () => { - using (UI.VerticalScope()) { - UI.Space(15); - UI.Div(0, 0, 800); - UI.Label("Kingdom Finances"); - } - }, - () => { - UI.Label("Finances".cyan(), UI.Width(325)); - UI.Label(ks.Resources.Finances.ToString().orange().bold(), UI.Width(100)); - UI.ActionButton($"Gain {settings.increment}", () => { - ks.Resources += KingdomResourcesAmount.FromFinances(settings.increment); - }, UI.AutoWidth()); - UI.ActionButton($"Lose {settings.increment}", () => { - ks.Resources -= KingdomResourcesAmount.FromFinances(settings.increment); - }, UI.AutoWidth()); - }, - () => { - UI.Label("Materials".cyan(), UI.Width(325)); - UI.Label(ks.Resources.Materials.ToString().orange().bold(), UI.Width(100)); - UI.ActionButton($"Gain {settings.increment}", () => { - ks.Resources += KingdomResourcesAmount.FromMaterials(settings.increment); - }, UI.AutoWidth()); - UI.ActionButton($"Lose {settings.increment}", () => { - ks.Resources -= KingdomResourcesAmount.FromMaterials(settings.increment); - }, UI.AutoWidth()); - }, - () => { - UI.Label("Favors".cyan(), UI.Width(325)); - UI.Label(ks.Resources.Favors.ToString().orange().bold(), UI.Width(100)); - UI.ActionButton($"Gain {settings.increment}", () => { - ks.Resources += KingdomResourcesAmount.FromFavors(settings.increment); - }, UI.AutoWidth()); - UI.ActionButton($"Lose {settings.increment}", () => { - ks.Resources -= KingdomResourcesAmount.FromFavors(settings.increment); - }, UI.AutoWidth()); - }, - () => { - using (UI.VerticalScope()) { - UI.Space(15); - UI.Div(0, 0, 800); - } - }, - () => UI.Toggle("Instant Events", ref settings.toggleInstantEvent), - () => UI.Toggle("Ignore Event Solution Restrictions", ref settings.toggleIgnoreEventSolutionRestrictions), - () => { - - UI.Slider("Crusade card resolution time multiplier", ref settings.kingdomTaskResolutionLengthMultiplier, -1, 2, 0, 2, "", UI.Width(400)); - UI.Space(25); - UI.Label("Multiplies crusade card resolution time by (1 + modifier). -1 will make things as fast as possible (minimum 1 day to avoid possible bugs)".green()); - }, - () => { - UI.Slider("Build Time Modifier", ref settings.kingdomBuildingTimeModifier, -1, 2, 0, 2, "", UI.Width(400)); - var instance = KingdomState.Instance; - if (instance != null) { - instance.BuildingTimeModifier = settings.kingdomBuildingTimeModifier; - } - UI.Space(25); - UI.Label("Multiplies build time by (1 + modifier). -1 will make new buildings instant.".green()); - }, - () => { - var startDate = Game.Instance.BlueprintRoot.Calendar.GetStartDate(); - var currentDate = KingdomState.Instance.Date; - var dateText = Game.Instance.BlueprintRoot.Calendar.GetDateText(currentDate - startDate, GameDateFormat.Full, true); - using (UI.VerticalScope()) { - UI.Space(15); - UI.Div(0, 0, 800); - using (UI.HorizontalScope()) { - UI.Label("Date".cyan(), UI.Width(325)); - UI.Label(dateText.orange().bold(), UI.AutoWidth()); - UI.ActionButton($"+1 Day", () => { Actions.KingdomTimelineAdvanceDays(1); }, UI.Width(150)); - UI.ActionButton($"+1 Month", () => { - Actions.KingdomTimelineAdvanceDays(KingdomState.Instance.DaysTillNextMonth); - }, UI.Width(150)); - } - UI.ValueAdjuster("Current Day", () => ks.CurrentDay, v => ks.CurrentDay = v, 1, 0, int.MaxValue); - UI.ValueAdjuster("Current Turn", () => ks.CurrentTurn, v => ks.CurrentTurn = v, 1, 0, int.MaxValue); - } - }, - () => { } - ); - 25.space(); - UI.Div(); - SettlementsEditor.OnGUI(); - } - } -} diff --git a/ToyBox/classes/MainUI/Crusade/EventEditor.cs b/ToyBox/classes/MainUI/Crusade/EventEditor.cs deleted file mode 100644 index 2af39fdec..000000000 --- a/ToyBox/classes/MainUI/Crusade/EventEditor.cs +++ /dev/null @@ -1,116 +0,0 @@ -using Kingmaker; -using ModKit; -using static ModKit.UI; -using Kingmaker.Kingdom; -using Kingmaker.Kingdom.Tasks; -using Kingmaker.Kingdom.Blueprints; -using Kingmaker.Blueprints; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; - -namespace ToyBox.classes.MainUI { - public static class EventEditor { - public static Settings settings => Main.Settings; - - public static void OnGUI() { - if (Game.Instance?.Player == null) return; - var ks = KingdomState.Instance; - if (ks == null) { - Label("You must unlock the crusade before you can access these toys.".yellow().bold()); - return; - } - Div(0, 25); - HStack("Events", 1, - () => Toggle("Preview Events", ref settings.previewEventResults), - () => Toggle("Instant Events", ref settings.toggleInstantEvent), - () => { - using (VerticalScope()) { - Toggle("Ignore Event Solution Restrictions", ref settings.toggleIgnoreEventSolutionRestrictions); - if (settings.toggleIgnoreEventSolutionRestrictions) { - using (HorizontalScope()) { - 50.space(); - Toggle("Hide Even Solution Restrictions Preview", ref settings.toggleHideEventSolutionRestrictionsPreview); - } - } - } - }, - () => { - using (VerticalScope()) { - if (ks.ActiveEvents.Count == 0) - Label("No active events".orange().bold()); - foreach (var activeEvent in ks.ActiveEvents) { - /* If it's an event not a decree - * Events are associated with Tasks by EventTask - * EventTask is a child of Task - * Task(decree) must also have a corresponding event - * Event(AKA the "Event" in the game) does not have an associated task(EventTask) - */ - if (activeEvent.AssociatedTask == null) { - Div(0, 25); - using (HorizontalScope()) { - Label(activeEvent.FullName.cyan(), 350.width()); - 25.space(); - Label(activeEvent.EventBlueprint.InitialDescription.StripHTML().green()); - } - } - } - } - } - ); - - Div(0, 25); - HStack("Decrees", 1, - () => Toggle("Preview Decrees", ref settings.previewDecreeResults), - () => Toggle("Ignore Start Restrictions", ref settings.toggleIgnoreStartTaskRestrictions, AutoWidth()), - //TODO: toggle to ignore specific restrictions - () => Toggle("No Decree Resource Costs", ref settings.toggleTaskNoResourcesCost), - () => { - using (VerticalScope()) { - - if (ks.ActiveTasks.Count() == 0) - Label("No active decrees".orange().bold()); - foreach (var activeTask in ks.ActiveEvents) { - if (activeTask.AssociatedTask != null) { - Div(0, 25); - var task = activeTask.AssociatedTask; - using (HorizontalScope()) { - Label(task.Name.cyan(), 350.width()); - 25.space(); - if (task.IsInProgress) - Label($"Ends in {task.EndsOn - ks.CurrentDay} days", 200.width()); - else { - ActionButton("Start", () => { - task.Start(); - }, 200.width()); - } - 25.space(); - - if (task.IsInProgress) { - ActionButton("Finish", () => { - task.m_BonusDays = task.Duration; - }, 120.width()); - if (task.CanCancelStarted) { - ActionButton("Cancel", () => { - task.Cancel(); - }, 120.width()); - } - else - 123.space(); - } - else - 249.space(); - 25.space(); - var taskBlueprint = task.Event.EventBlueprint as BlueprintKingdomProject; - Label(task.Description.StripHTML().orange() + "\n" + taskBlueprint.MechanicalDescription.ToString().StripHTML().green()); - } - } - } - ks.TimelineManager.UpdateEvents(); - } - } - ); - - } - } -} diff --git a/ToyBox/classes/MainUI/Crusade/SettlementsEditor.cs b/ToyBox/classes/MainUI/Crusade/SettlementsEditor.cs deleted file mode 100644 index 54526637f..000000000 --- a/ToyBox/classes/MainUI/Crusade/SettlementsEditor.cs +++ /dev/null @@ -1,71 +0,0 @@ - -using ModKit; -using static ModKit.UI; -using ModKit.Utility; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; -using UnityModManagerNet; -using Kingmaker; -using Kingmaker.Kingdom; - -namespace ToyBox.classes.MainUI { - public static class SettlementsEditor { - public static Settings Settings => Main.Settings; - private static Dictionary<object, bool> toggleStates = new(); - - public static void OnGUI() { - if (Game.Instance?.Player == null) return; - var kingdom = KingdomState.Instance; - if (kingdom == null) { - Label("You must unlock the crusade before you can access these toys.".yellow().bold()); - return; - } - HStack("Settlements", 1, - () => { - using (VerticalScope()) { - if (kingdom.SettlementsManager.Settlements.Count == 0) - Label("None".orange().bold() + " - please progress further into the game".green()); - Toggle("Ignore building restrions", ref Settings.toggleIgnoreSettlementRestrictions, AutoWidth()); - /* - if (Settings.toggleIgnoreSettlementRestrictions) { - UI.Toggle("Ignore player class restrictions", ref Settings.toggleIgnoreBuildingClassRestrictions); - UI.Toggle("Ignore building adjacency restrictions", ref Settings.toggleIgnoreBuildingAdjanceyRestrictions); - } - */ - foreach (var settlement in kingdom.SettlementsManager.Settlements) { - var showBuildings = false; - var buildings = settlement.Buildings; - using (HorizontalScope()) { - Label(settlement.Name.orange().bold(), 350.width()); - 25.space(); - if (EnumGrid(ref settlement.m_Level)) { - - } - 25.space(); - showBuildings = toggleStates.GetValueOrDefault(buildings, false); - if (DisclosureToggle($"Buildings: {buildings.Count()}", ref showBuildings, 150)) { - toggleStates[buildings] = showBuildings; - } - } - if (showBuildings) { - foreach (var building in buildings) { - using (HorizontalScope()) { - 100.space(); - Label(building.Blueprint.name.cyan(), 350.width()); - ActionButton("Finish", () => { - building.IsFinished = true; - }, AutoWidth()); - 25.space(); - Label(building.IsFinished.ToString(), 200.width()); - 25.space(); - Label(building.Blueprint.MechanicalDescription.ToString().StripHTML().orange() + "\n" + building.Blueprint.Description.ToString().StripHTML().green()); - } - } - } - } - } - }); - } - } -} \ No newline at end of file diff --git a/ToyBox/classes/MainUI/EnchantmentEditor.cs b/ToyBox/classes/MainUI/EnchantmentEditor.cs deleted file mode 100644 index b022d944d..000000000 --- a/ToyBox/classes/MainUI/EnchantmentEditor.cs +++ /dev/null @@ -1,691 +0,0 @@ -using Kingmaker; -using Kingmaker.Blueprints; -using Kingmaker.Blueprints.Classes; -using Kingmaker.Blueprints.Items.Armors; -using Kingmaker.Blueprints.Items.Ecnchantments; -using Kingmaker.Blueprints.Items.Weapons; -using Kingmaker.Blueprints.Items.Shields; -using Kingmaker.Designers.Mechanics.Facts; -using Kingmaker.EntitySystem.Persistence.JsonUtility; -using Kingmaker.Items; -using Kingmaker.UI.Common; -using Kingmaker.UnitLogic.Mechanics; -using Kingmaker.Utility; -using ModKit; -using ModKit.Utility; -using static ModKit.UI; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using UnityEngine; - -namespace ToyBox.classes.MainUI { - public static class EnchantmentEditor { - public static Settings settings => Main.Settings; - - #region GUI - public static BlueprintItemWeapon basicSpikeShield = ResourcesLibrary.TryGetBlueprint<BlueprintItemWeapon>("62c90581f9892e9468f0d8229c7321c4"); //StandardWeaponLightShield - - public static int selectedItemType; - public static int selectedItemIndex; - public static int selectedEnchantIndex; - public static (string, string) renameState = (null, null); - public static ItemEntity selectedItem = null; - public static ItemEntity editedItem = null; - public static string itemSearchText = ""; - public static string[] ItemTypeNames = null; - private static List<ItemEntity> inventory; - private static List<BlueprintItemEnchantment> enchantments; - private static List<BlueprintItemEnchantment> filteredEnchantments = new(); - public static IEnumerable<IGrouping<string, BlueprintItemEnchantment>> collatedBPs = null; - private static List<BlueprintItemEnchantment> selectedCollatedEnchantments; - private static List<string> collationKeys = new(); - private static string collationKey; - private static string collationSearchText; - public static int matchCount = 0; - - public static void ResetGUI() { } - - public static void OnShowGUI() => UpdateItems(); - public static void OnGUI() { - if (!Main.IsInGame) return; - if (ItemTypeNames == null) - ItemTypeNames = Enum.GetNames(typeof(ItemsFilter.ItemType)).ToList().Prepend("All").ToArray(); - Label("Sandal says '".orange() + "Enchantment'".cyan().bold()); - // load blueprints - if (enchantments == null) { - var blueprints = BlueprintLoader.Shared.GetBlueprints(); - if (blueprints == null) return; - - enchantments = new List<BlueprintItemEnchantment>(); - foreach (var bp in blueprints) { - if (bp is BlueprintItemEnchantment enchantBP) { - enchantments.Add(enchantBP); - } - } - enchantments.Sort((l, r) => { - return r.Rarity().CompareTo(l.Rarity()); - //if (l.Description != null && r.Description != null || l.Description == null && r.Description == null) { - // return r.Rarity().CompareTo(r.Rarity()); - //} else if (l.Description != null) return 1; - //return -1; - }); - enchantments.TrimExcess(); - UpdateItems(); - UpdateSearchResults(); - } - - // Stackable browser - using (HorizontalScope(Width(350))) { - var remainingWidth = ummWidth; - - // First column - Type Selection Grid - using (VerticalScope()) { - ActionSelectionGrid( - ref selectedItemType, - ItemTypeNames, - 1, - index => { - selectedItemIndex = index; - UpdateItems(); - }, - buttonStyle, - Width(175)); - Space(25); - if (VPicker("Ench. Types".cyan(), ref collationKey, collationKeys, "All", (s) => s, ref collationSearchText, Width(175))) { - Mod.Debug($"collationKey: {collationKey}"); - UpdateCollation(); - } - } - var itemTypeName = ItemTypeNames[selectedItemType]; - remainingWidth -= 250; - Space(10); - // Second column - Item Selection Grid - using (VerticalScope(GUI.skin.box)) { - ActionTextField( - ref itemSearchText, - "itemSearchText", - (text) => UpdateItems(), - () => UpdateItems(), - Width(375)); - using (HorizontalScope()) { - 10.space(); - Toggle("Ratings", ref settings.showRatingForEnchantmentInventoryItems, 147.width()); - 10.space(); - ActionButton("Export", () => inventory.Export(itemTypeName + ".json"), 100.width()); - ActionButton("Import", () => { - Game.Instance.Player.Inventory.Import(itemTypeName + ".json"); - UpdateItems(); - }, 100.width()); - } - if (inventory.Count > 0) { - ActionSelectionGrid( - ref selectedItemIndex, - inventory.Select(item => item.NameAndOwner(true)).ToArray(), - 1, - index => selectedItem = inventory[selectedItemIndex], - rarityButtonStyle, - Width(375)); - } - else { - Label("No Items".grey(), Width(375)); - } - } - remainingWidth -= 400; - Space(10); - // Section Column - Main Area - using (VerticalScope(MinWidth(remainingWidth))) { - Label("Import/Export allows you to save and add a list of items to a file based on the type (e.g. Weapon.json). These files live in a new ToyBox folder that in the same folder that contains your saved games ".green()); - if (selectedItem != null) { - var item = selectedItem; - //UI.Label("Target".cyan()); - Div(); - using (HorizontalScope(GUI.skin.box, MinHeight(125))) { - var rarity = item.Rarity(); - //Main.Log($"item.Name - {item.Name.ToString().Rarity(rarity)} rating: {item.Blueprint.Rating(item)}"); - Space(25); - using (VerticalScope(Width(400))) { - Label(item.NameAndOwner(false).bold(), Width(400)); - 25.space(); - var bp = item.Blueprint; - Label($"rating: {item.Rating().ToString().orange().bold()} (bp:{item.Blueprint.Rating().ToString().orange().bold()})".cyan()); - using (HorizontalScope()) { - var modifers = bp.Attributes(); - if (item.IsEpic) modifers = modifers.Prepend("epic "); - Label(string.Join(" ", modifers).cyan(), AutoWidth()); - //if (bp is BlueprintItemWeapon bpW) { - // if (bpW.IsMagic) UI.Label("magic ".cyan(), UI.AutoWidth()); - // if (bpW.IsNotable) UI.Label("notable ".Rarity(RarityType.Notable), UI.AutoWidth()); - // if (bpW.IsNatural) UI.Label("natural ".grey(), UI.AutoWidth()); - // if (bpW.IsMelee) UI.Label("melee ".grey(), UI.AutoWidth()); - // if (bpW.IsRanged) UI.Label("ranged ".grey(), UI.AutoWidth()); - //} - //if (bp is BlueprintItemArmor bpA) { - // if (bpA.IsMagic) UI.Label("magic ".cyan(), UI.AutoWidth()); - // if (bpA.IsNotable) UI.Label("notable ".Rarity(RarityType.Notable), UI.AutoWidth()); - // if (bpA.IsShield) UI.Label("shield ".grey(), UI.AutoWidth()); - //} - } - } - Space(25); - if (item is ItemEntityShield shield) { - using (VerticalScope()) { - using (HorizontalScope()) { - Label("Shield".orange(), Width(100)); - TargetItemGUI(shield.ArmorComponent); - } - Div(); - if (shield.WeaponComponent != null) { - using (HorizontalScope()) { - Label("Spikes".orange(), Width(100)); - TargetItemGUI(shield.WeaponComponent); - } - ActionButton("Remove ", () => shield.WeaponComponent = null, AutoWidth()); - } - else { - var compTitle = shield.Blueprint.WeaponComponent?.name; - compTitle = compTitle != null ? " from " + compTitle.yellow() : ""; - ActionButton("Add " + "Spikes".orange() + compTitle, () => shield.WeaponComponent = new ItemEntityWeapon(shield.Blueprint.WeaponComponent ?? basicSpikeShield, shield), AutoWidth()); - } - } - } - else if (item is ItemEntityWeapon weapon && weapon.Second != null) { - using (VerticalScope()) { - using (HorizontalScope()) { - Label("Main".orange(), Width(100)); - TargetItemGUI(weapon); - } - Div(); - using (HorizontalScope()) { - Label("2nd".orange(), Width(100)); - TargetItemGUI(weapon.Second); - } - } - } - else { - TargetItemGUI(item); - } - } - using (HorizontalScope()) { - ActionButton("Sandal".cyan() + ", yer a Trickster!", () => { - AddTricksterEnchantmentsTier1(item); - }, AutoWidth()); - ActionButton("Gimmie More!".DarkModeRarity(RarityType.Epic), () => { - AddTricksterEnchantmentsTier2or3(item, false); - }, rarityButtonStyle, AutoWidth()); - ActionButton("En-chaannt-ment".DarkModeRarity(RarityType.Legendary), () => { - AddTricksterEnchantmentsTier2or3(item, true); - }, rarityButtonStyle, AutoWidth()); - Label("Sandal".cyan() + " has discovered the mythic path of Trickster and can reveal hidden secrets in your items".green()); - } - Div(); - } - // Search Field and modifiers - Space(10); - using (HorizontalScope()) { - ActionTextField( - ref settings.searchTextEnchantments, - "searchText", - (text) => { UpdateSearchResults(); }, - () => { UpdateSearchResults(); }, - MinWidth(100), MaxWidth(450)); - Space(25); - Label("Search Limit", AutoWidth()); - ActionIntTextField( - ref settings.searchLimit, - "searchLimit", - (limit) => { }, - () => { UpdateSearchResults(); }, - MinWidth(75), MaxWidth(175)); - if (settings.searchLimit > 1000) { settings.searchLimit = 1000; } - Space(25); - if (Toggle("Search Descriptions", ref settings.searchDescriptions)) UpdateSearchResults(); - Space(25); - Toggle("Show GUIDs", ref settings.showAssetIDs); - Space(25); - Toggle("Components", ref settings.showComponents); - //UI.Space(25); - //UI.Toggle("Elements", ref settings.showElements); - } - Space(10); - using (HorizontalScope()) { - ActionButton("Search", () => UpdateSearchResults(), AutoWidth()); - Space(25); - if (matchCount > 0 && settings.searchTextEnchantments.Length > 0) { - var matchesText = "Matches: ".green().bold() + $"{matchCount}".orange().bold(); - if (matchCount > settings.searchLimit) { matchesText += "Displaying: ".cyan() + $"{settings.searchLimit}".cyan().bold(); } - Label(matchesText, ExpandWidth(false)); - } - } - using (HorizontalScope()) { - Space(5); - Label("Enchantment".blue(), Width(400)); - Space(314); - Label("Rating".blue(), Width(75)); - Space(310); - Label("Description".blue()); - - } - Space(10); - Div(); - // List of enchantments with buttons to add to item - EnchantmentsListGUI(); - } - } - } - - public static void TargetItemGUI(ItemEntity item) { - var enchantements = GetEnchantments(item); - if (enchantements.Count > 0) { - using (VerticalScope()) { - var index = 0; - foreach (var entry in enchantements) { - if (index++ > 0) Div(); - var enchant = entry.Key; - var enchantBP = enchant.Blueprint; - var name = enchantBP.name; - if (name != null && name.Length > 0) { - name = name.DarkModeRarity(enchantBP.Rarity()); - using (HorizontalScope()) { - Label(name, Width(450)); - Space(25); - Label($"{(enchantBP.Rating()).ToString().orange().bold()}".cyan(), 50.width()); - Space(25); - Label(entry.Value ? "Custom".yellow() : "Perm".orange(), Width(100)); - Space(25); - ActionButton("Remove", () => RemoveEnchantment(item, enchant), AutoWidth()); - var description = enchantBP.Description; - if (description != null) { - Space(25); - Label(description.StripHTML().green()); - } - } - } - } - } - } - else { - Label("No Enchantments".orange()); - } - } - public static void EnchantmentsListGUI() { - Div(5); - var enchantements = selectedCollatedEnchantments ?? filteredEnchantments; - - for (var i = 0; i < enchantements.Count; i++) { - var enchant = enchantements[i]; - var title = enchant.name.DarkModeRarity(enchant.Rarity()); - - using (HorizontalScope()) { - Space(5); - Label(title, Width(400)); - if (selectedItem is ItemEntityShield shield) { - ActionButton("+ " + "Armor".orange(), () => AddClicked(i), Width(150)); - if (shield.ArmorComponent.Enchantments.Any(e => e.Blueprint == enchant)) - ActionButton("- " + "Armor".orange(), () => RemoveClicked(i), Width(150)); - else - Space(154); - if (shield.WeaponComponent != null) { - ActionButton("+ " + "Spikes".orange(), () => AddClicked(i, true), Width(150)); - if (shield.WeaponComponent.Enchantments.Any(e => e.Blueprint == enchant)) - ActionButton("- " + "Spikes".orange(), () => RemoveClicked(i, true), Width(150)); - else - Space(154); - } - } - else if (selectedItem is ItemEntityWeapon weapon && weapon?.Second != null) { - ActionButton("+ " + "Main".orange(), () => AddClicked(i), Width(150)); - if (weapon.Enchantments.Any(e => e.Blueprint == enchant)) - ActionButton("- " + "Main".orange(), () => RemoveClicked(i), Width(150)); - else - Space(154); - ActionButton("+ " + "2nd".orange(), () => AddClicked(i, true), Width(150)); - if (weapon.Second.Enchantments.Any(e => e.Blueprint == enchant)) - ActionButton("- " + "2nd".orange(), () => RemoveClicked(i, true), Width(150)); - else - Space(154); - } - else { - ActionButton("Add", () => AddClicked(i), Width(150)); - if (selectedItem?.Enchantments.Any(e => e.Blueprint == enchant) ?? false) - ActionButton("Remove", () => RemoveClicked(i), Width(150)); - else - Space(154); - } - - Space(10); - Label($"{enchant.Rating()}".yellow(), 75.width()); // ⊙ - Space(10); - var description = enchant.Description.StripHTML().green(); - if (enchant.Comment?.Length > 0) description = enchant.Comment.orange() + " " + description; - if (enchant.Prefix?.Length > 0) description = enchant.Prefix.yellow() + " " + description; - if (enchant.Suffix?.Length > 0) description = enchant.Suffix.yellow() + " " + description; - if (settings.showAssetIDs) { - using (VerticalScope()) { - using (HorizontalScope()) { - Label(enchant.CollationNames().First().cyan(), Width(300)); - ClipboardLabel(enchant.AssetGuid.ToString(), AutoWidth()); - } - Label(description); - - } - } - else { - Label(enchant.CollationNames().First().cyan(), Width(300)); - Label(description); - } - } - Div(); - } - } - public static void UpdateItems() { - var selectedItemTypeEnumIndex = selectedItemType - 1; - var searchText = itemSearchText.ToLower(); - if (Game.Instance?.Player?.Inventory == null) return; - inventory = (from item in Game.Instance.Player.Inventory - where item.Name.ToLower().Contains(searchText) - && (selectedItemType == 0 - || (int)item.Blueprint.ItemType == selectedItemTypeEnumIndex - ) - orderby item.Rating() descending, item.Name - select item - ).ToList(); - if (editedItem != null) { - selectedItemIndex = inventory.IndexOf(editedItem); - editedItem = null; - } - if (selectedItemIndex >= inventory.Count || selectedItemIndex < 0) { - selectedItemIndex = 0; - } - selectedItem = selectedItemIndex < inventory.Count ? inventory.ElementAt(selectedItemIndex) : null; - } - public static void UpdateSearchResults() { - filteredEnchantments.Clear(); - editedItem = null; - var terms = settings.searchTextEnchantments.Split(' ').Select(s => s.ToLower()).ToHashSet(); - - for (var i = 0; i < enchantments.Count; i++) { - var enchant = enchantments[i]; - if (enchant.AssetGuid.ToString().Contains(settings.searchTextEnchantments) - || enchant.GetType().ToString().Contains(settings.searchTextEnchantments) - ) { - filteredEnchantments.Add(enchant); - } - else { - var name = enchant.name; - var displayName = enchant.GetDisplayName(); - var description = enchant.Description ?? ""; - description = description.StripHTML(); - if (terms.All(term => name.Matches( term)) - || terms.All(term => displayName.Matches(term)) - || settings.searchDescriptions && terms.All(term => description.Matches(term)) - ) { - filteredEnchantments.Add(enchant); - } - } - } - matchCount = filteredEnchantments.Count(); - var filtered = from bp in filteredEnchantments - orderby bp.Rating() descending, bp.name - select bp; - //.ThenByDescending(bp => bp.IdentifyDC) - collatedBPs = from bp in filtered - from key in bp.CollationNames().Select(n => n.Replace("Enchantment", "")) - group bp by key into g - orderby g.Key.LongSortKey(), g.Key - select g; - _ = collatedBPs.Count(); - var keys = collatedBPs.ToList().Select(cbp => cbp.Key).ToList(); - collationKeys = new List<string> { }; - collationKeys.AddRange(keys); - filteredEnchantments = filtered.Take(settings.searchLimit).ToList(); - UpdateCollation(); - } - public static void UpdateCollation() { - if (collationKey == null) - selectedCollatedEnchantments = null; - else - foreach (var group in collatedBPs) { - Mod.Debug($"group: {group.Key}"); - if (group.Key == collationKey) { - matchCount = group.Count(); - selectedCollatedEnchantments = group.ToList(); - } - } - } - public static void AddClicked(int index, bool second = false) { - if (selectedItemIndex < 0 || selectedItemIndex >= inventory.Count) return; - if (index < 0 || index >= filteredEnchantments.Count) return; - var enchantements = selectedCollatedEnchantments ?? filteredEnchantments; - var selected = inventory.ElementAt(selectedItemIndex); - if (selected is ItemEntityShield shield) { - if (!second) - AddEnchantment(shield.ArmorComponent, enchantements[index]); - else - AddEnchantment(shield.WeaponComponent, enchantements[index]); - editedItem = shield; - } - else if (second && selected is ItemEntityWeapon weapon) { - AddEnchantment(weapon.Second, enchantements[index]); - editedItem = weapon; - } - else { - AddEnchantment(selected, enchantements[index]); - editedItem = selected; - } - } - public static void RemoveClicked(int index, bool second = false) { - if (selectedItemIndex < 0 || selectedItemIndex >= inventory.Count) return; - if (index < 0 || index >= filteredEnchantments.Count) return; - var enchantements = selectedCollatedEnchantments ?? filteredEnchantments; - var selected = inventory.ElementAt(selectedItemIndex); - if (selected is ItemEntityShield shield) { - if (!second) - RemoveEnchantment(shield.ArmorComponent, enchantements[index]); - else - RemoveEnchantment(shield.WeaponComponent, enchantements[index]); - editedItem = shield; - } - if (second && selected is ItemEntityWeapon weapon) { - RemoveEnchantment(weapon.Second, enchantements[index]); - editedItem = weapon; - } - else { - RemoveEnchantment(selected, enchantements[index]); - editedItem = selected; - } - } - - #endregion - - #region Code - public static void AddEnchantment(ItemEntity item, BlueprintItemEnchantment enchantment, Rounds? duration = null) { - if (item?.m_Enchantments == null) - Mod.Trace("item.m_Enchantments is null"); - - var fake_context = new MechanicsContext(default); // if context is null, items may stack which could cause bugs - - //var fi = AccessTools.Field(typeof(MechanicsContext), nameof(MechanicsContext.AssociatedBlueprint)); - //fi.SetValue(fake_context, enchantment); // check if AssociatedBlueprint must be set; I think not - - item.AddEnchantment(enchantment, fake_context, duration); - } - - public static void RemoveEnchantment(ItemEntity item, BlueprintItemEnchantment enchantment) { - if (item == null) return; - item.RemoveEnchantment(item.GetEnchantment(enchantment)); - } - - public static void RemoveEnchantment(ItemEntity item, ItemEnchantment enchantment) { - if (item == null) return; - item.RemoveEnchantment(enchantment); - } - - - - public static void AddTricksterEnchantmentsTier1(ItemEntity item) { - var tricksterKnowledgeArcanaTier1 = ResourcesLibrary.TryGetBlueprint<BlueprintFeature>("c7bb946de7454df4380c489a8350ba38"); - var tricksterTier1Toy = tricksterKnowledgeArcanaTier1.GetComponent<TricksterArcanaBetterEnhancements>(); - var fake_context = new MechanicsContext(default); // if context is null, items may stack which could cause bugs - - var itemEnchantmentList = new List<ItemEnchantment>(); - foreach (var enchantment in item.Enchantments) - itemEnchantmentList.Add(enchantment); - if (!item.Enchantments.Any<ItemEnchantment>((Func<ItemEnchantment, bool>)(p => ((IList<BlueprintItemEnchantmentReference>)tricksterTier1Toy.EnhancementEnchantments).Any<BlueprintItemEnchantmentReference>((Func<BlueprintItemEnchantmentReference, bool>)(param => param.Get() == p.Blueprint))))) - return; - foreach (var itemEnchantment in itemEnchantmentList) { - var enchantment = itemEnchantment; - if (!tricksterTier1Toy.BestEnchantments.Any<BlueprintItemEnchantmentReference>((Func<BlueprintItemEnchantmentReference, bool>)(p => p.Get() == enchantment.Blueprint)) && ((IList<BlueprintItemEnchantmentReference>)tricksterTier1Toy.EnhancementEnchantments).Any<BlueprintItemEnchantmentReference>((Func<BlueprintItemEnchantmentReference, bool>)(p => p.Get() == enchantment.Blueprint))) { - var index = ((IEnumerable<BlueprintItemEnchantmentReference>)tricksterTier1Toy.EnhancementEnchantments).FindIndex<BlueprintItemEnchantmentReference>((Func<BlueprintItemEnchantmentReference, bool>)(p => p.Get() == enchantment.Blueprint)); - if (tricksterTier1Toy.EnhancementEnchantments.Length > index + 1) { - item.RemoveEnchantment(enchantment); - item.AddEnchantment(tricksterTier1Toy.EnhancementEnchantments[index + 1].Get(), fake_context); - } - } - } - } - public static void AddTricksterEnchantmentsTier2or3(ItemEntity item, bool isTier3) { - var tricksterKnowledgeArcanaBP = ResourcesLibrary.TryGetBlueprint<BlueprintFeature>(isTier3 ? "5e26c673173e423881e318d2f0ae84f0" : "7bbd9f681440a294382b527a554e419d"); - var tricksterToy = tricksterKnowledgeArcanaBP.GetComponent<TricksterArcanaAdditionalEnchantments>(); - var fake_context = new MechanicsContext(default); // if context is null, items may stack which could cause bugs - - var source = new List<BlueprintItemEnchantment>(); - foreach (var commonEnchantment in tricksterToy.CommonEnchantments) - source.Add((BlueprintItemEnchantment)(BlueprintReference<BlueprintItemEnchantment>)commonEnchantment); - if (item is ItemEntityWeapon || item is ItemEntityShield) { - foreach (var weaponEnchantment in tricksterToy.WeaponEnchantments) - source.Add((BlueprintItemEnchantment)(BlueprintWeaponEnchantment)(BlueprintReference<BlueprintWeaponEnchantment>)weaponEnchantment); - } - if (item is ItemEntityArmor || item is ItemEntityShield) { - foreach (var armorEnchantment in tricksterToy.ArmorEnchantments) - source.Add((BlueprintItemEnchantment)(BlueprintArmorEnchantment)(BlueprintReference<BlueprintArmorEnchantment>)armorEnchantment); - } - foreach (var enchantment in item.Enchantments) - source.Remove(enchantment.Blueprint); - if (source.Empty<BlueprintItemEnchantment>()) - return; - var blueprint = source.ToList<BlueprintItemEnchantment>().Random<BlueprintItemEnchantment>(); - var itemEntityShield = item as ItemEntityShield; - switch (blueprint) { - case BlueprintWeaponEnchantment _ when itemEntityShield != null: - var weaponComponent = itemEntityShield.WeaponComponent; - if (weaponComponent == null) - break; - weaponComponent.AddEnchantment(blueprint, fake_context); - break; - case BlueprintArmorEnchantment _ when itemEntityShield != null: - itemEntityShield.ArmorComponent.AddEnchantment(blueprint, fake_context); - break; - default: - item.AddEnchantment(blueprint, fake_context); - break; - } - } - /// <summary>definitely not useless</summary> - /// <returns>Key is ItemEnchantments of given item. Value is true, if it is a temporary enchantment.</returns> - public static Dictionary<ItemEnchantment, bool> GetEnchantments(ItemEntity item) { - Dictionary<ItemEnchantment, bool> enchantments = new(); - if (item == null) return enchantments; - var base_enchantments = item.Blueprint.Enchantments; - foreach (var enchantment in item.Enchantments) { - enchantments.Add(enchantment, !base_enchantments.Contains(enchantment.Blueprint)); - } - return enchantments; - } - - // currently nonfunctional until Nordic gets his patch working - - //public static int CalcCost(ItemEntity item, BlueprintItemEnchantment enchantment) { - // if (item.Blueprint is BlueprintItemWeapon || item.Blueprint is BlueprintItemArmor || item.Blueprint is BlueprintItemShield) { - // int currentBonus = GetEffectiveBonus(item); - - // if (currentBonus + enchantment.EnchantmentCost > 10) { - // return -1; - // } - - // long currentPrice = item.Blueprint.SellPrice; - // long basePrice; - // if (item.Blueprint is BlueprintItemArmor) { - // basePrice = currentPrice - (1000 * currentBonus * currentBonus); // get the price of the item without its bonuses - // } else if (item.Blueprint is BlueprintItemWeapon) { - - // } - // } - - // return -1; - //} - - /// <summary> - /// Makes getting the effective bonus of an item more readable - /// </summary> - /// <returns>Total effective bonus of all permanent enchantments on the item; 0 if none</returns> - public static int GetEffectiveBonus(ItemEntity item) { - if (item == null) return 0; - - return item.Enchantments.Sum((enchantment) => enchantment.Blueprint.EnchantmentCost); - } - - /// <summary> - /// Gives the current enhancement bonus of the item - /// </summary> - /// <returns></returns> - public static int CurrentEnhancement(ItemEntity item) { - if (item == null) return 0; - - var enhanceCheck = new Regex(@"Enhancement\d$"); - var enhancements = new int[20]; - - foreach (var enchant in item.Blueprint.Enchantments) { - if (enhanceCheck.IsMatch(enchant.Name)) { - try { - enhancements.Append(int.Parse(enchant.name.Substring(11))); - } - catch { // catches any edge cases where the name is something like "Enhancement3hop" and just ignores those - continue; - } - } - } - - if (!enhancements.Empty()) { - return enhancements.Max(); - } - - return 0; - } - - /// <summary>maybe useful to render button texts/colors</summary> - /// <returns>Source of Enchantment</returns> - public static Source GetSource(ItemEntity item, BlueprintItemEnchantment enchantment) { - - var enc = item.GetEnchantment(enchantment); - if (enc == null) { - if (item.Blueprint.Enchantments.Contains(enchantment)) { - return Source.Removed; - } - return Source.Not; - } - - if (enc.EndTime != null) { - return Source.Timed; - } - - if (enc.ParentContext == null) { //item.Blueprint.Enchantments.Contains(enchantment) - return Source.Blueprint; - } - - return Source.Added; - } - #endregion - - #region Classes - public enum Source { - Not, // enchantment is not on the item - Removed, // enchantment is removed by toybox; removing enchantments which should be on an item, might cause stacking bugs - Timed, // enchantment is temporarily added by ability/spell (like Magic Weapon) - Added, // enchantment is added by toybox - Blueprint, // enchantment is part of item's blueprint - } - #endregion - } -} diff --git a/ToyBox/classes/MainUI/EnhancedUI/BulkSell.cs b/ToyBox/classes/MainUI/EnhancedUI/BulkSell.cs deleted file mode 100644 index 3a6ee0862..000000000 --- a/ToyBox/classes/MainUI/EnhancedUI/BulkSell.cs +++ /dev/null @@ -1,121 +0,0 @@ -using ModKit.Utility; -using System; -using System.Linq; -using ModKit; -using ToyBox.classes.Models; -using UnityEngine; -using static ModKit.UI; - -namespace ToyBox { - internal static class BulkSell { - private static readonly BulkSellSettings _settings = Main.Settings.bulkSellSettings; - - public static void OnGUI() { - void BonusItemOptions(string itemTypeName, string bonusType, ref int enchantLevel, ref int stackSize, Action accessory = null) { - using (HorizontalScope()) { - Label($"{itemTypeName.Cyan()}:", 180.width()); - Label($"{bonusType.orange()}", 150.width()); - Slider(ref enchantLevel, 0, 20, 0, "", 300.width()); - Slider(ref stackSize, 0, 20, 1, "", 300.width()); - accessory?.Invoke(); - } - } - - void ConsumableOptions(string itemTypeName, ref bool sellToggle, ref int stackSize) { - using (HorizontalScope()) { - Toggle($"Sell {itemTypeName}", ref sellToggle, 150.width()); - Space(25); - Label("Amount To Keep".Cyan(), 150.width()); - Slider(ref stackSize, 0, 200, 1, "", AutoWidth()); - } - } - - void DamageTypeOptions<T>(string title, SerializableDictionary<T, bool> settings) where T : Enum { - using (HorizontalScope()) { - Label(title.orange(), 220.width()); - using (VerticalScope()) { - settings.Keys - .Select((type, index) => new { type, index }) - .GroupBy(g => g.index / 5) - .ToList() - .ForEach(group => { - using (HorizontalScope()) { - group.ToList().ForEach(type => { - ActionToggle(type.type.ToString(), () => settings[type.type], b => settings[type.type] = b, 150); - Space(10); - }); - } - }); - } - } - } - - using (VerticalScope()) { - // create GUI sections - using (HorizontalScope()) { - TitleLabel("Category".Cyan(),178.width()); - TitleLabel("Type".Cyan(), 150.width()); - 5.space(); - TitleLabel("Max Modifier".Cyan(), 300.width()); - 190.space(); - TitleLabel("Amount To Keep".Cyan(), 300.width()); - } - BonusItemOptions("Armors", - "enchantment", - ref _settings.armorEnchantLevel, - ref _settings.armorStackSize, - () => Toggle("Sell unique armors", ref _settings.sellUniqueArmors) - ); - BonusItemOptions("Shields", - "enchantment", - ref - _settings.shieldEnchantLevel, - ref _settings.shieldStackSize, - () => Toggle("Sell unique shields", ref _settings.sellUniqueShields)); - BonusItemOptions("Belts", "attribute", ref _settings.maxAttributeBonusForBelt, ref _settings.beltStackSize); - BonusItemOptions("Head items", "attribute", ref _settings.maxAttributeBonusForHead, ref _settings.headStackSize); - BonusItemOptions("Cloaks", "save", ref _settings.maxSaveBonusForCloaks, ref _settings.cloakStackSize); - BonusItemOptions("Bracers", "AC", ref _settings.maxACBonusForBracers, ref _settings.bracerStackSize); - BonusItemOptions("Amulets", "AC", ref _settings.maxACBonusForNeck, ref _settings.neckStackSize); - BonusItemOptions("Rings", "AC", ref _settings.maxACBonusForRings, ref _settings.ringStackSize); - BonusItemOptions("Weapons", - "enchantment", - ref _settings.weaponEnchantLevel, - ref _settings.weaponStackSize, - () => Toggle("Sell unique weapons", ref _settings.sellUniqueWeapons) - ); - using (HorizontalScope()) { - 180.space(); - DisclosureToggle("Damage Types".Cyan(), ref _settings.showWeaponEnergyTypes, 240f); - if (_settings.showWeaponEnergyTypes) { - using (VerticalScope()) { - DamageTypeOptions("Elemental:", _settings.damageEnergy); - DamageTypeOptions("Alignment:", _settings.damageAlignment); - DamageTypeOptions("Materials:", _settings.damageMaterial); - DamageTypeOptions("Other:", _settings.damageReality); - } - } - } - Label("Consumables", AutoWidth()); - DivLast(); - ConsumableOptions("Potions", ref _settings.sellPotions, ref _settings.potionStackSize); - ConsumableOptions("Scrolls", ref _settings.sellScrolls, ref _settings.scrollStackSize); - ConsumableOptions("Ingredients", ref _settings.sellIngredients, ref _settings.ingredientStackSize); - - Div(0, 25); - Slider("Change all enhancement modifiers", ref _settings.globalModifier, 0, 10, 0, "", AutoWidth()); - ActionButton("Apply", () => { - _settings.maxAttributeBonusForBelt = _settings.globalModifier; - _settings.maxAttributeBonusForHead = _settings.globalModifier; - _settings.maxSaveBonusForCloaks = _settings.globalModifier; - _settings.maxACBonusForRings = _settings.globalModifier; - _settings.maxACBonusForBracers = _settings.globalModifier; - _settings.maxACBonusForNeck = _settings.globalModifier; - _settings.weaponEnchantLevel = _settings.globalModifier; - _settings.armorEnchantLevel = _settings.globalModifier; - _settings.shieldEnchantLevel = _settings.globalModifier; - }, AutoWidth()); - } - } - } -} diff --git a/ToyBox/classes/MainUI/EnhancedUI/EnhancedInventoryController.cs b/ToyBox/classes/MainUI/EnhancedUI/EnhancedInventoryController.cs deleted file mode 100644 index e052471f0..000000000 --- a/ToyBox/classes/MainUI/EnhancedUI/EnhancedInventoryController.cs +++ /dev/null @@ -1,278 +0,0 @@ -using Kingmaker; -using Kingmaker.Blueprints.Root; -using Kingmaker.UI; -using Kingmaker.UI.Common; -using Kingmaker.UI.MVVM._PCView.Loot; -using Kingmaker.UI.MVVM._PCView.ServiceWindows.Inventory; -using Kingmaker.UI.MVVM._PCView.Slots; -using Kingmaker.UI.MVVM._PCView.Vendor; -using System; -using System.Collections.Generic; -using ModKit; -using UniRx; -using UnityEngine; -using UnityEngine.UI; -using Owlcat.Runtime.UI.MVVM; - -namespace ToyBox { - public enum InventoryType { - InventoryStash, - Vendor, - LootCollector, - LootInventoryStash - } - - public class EnhancedInventoryController : MonoBehaviour { - public InventoryType Type; - - private Transform m_filter_block; - private SearchBar m_search_bar; - private Image[] m_search_icons; - private ReactiveProperty<ItemsFilter.FilterType> m_active_filter; - private IDisposable m_char_selection_changed_cb; - - private bool m_apply_handlers = true; - private bool m_deferred_update = false; -#if true - public void Awake() { - m_filter_block = transform.Find(PathToFilterBlock(Type)); - m_search_bar = new SearchBar(m_filter_block, "Enter item name..."); - - m_search_bar.Dropdown.onValueChanged.AddListener(delegate { - UpdateDropdownIcon(); - m_deferred_update = true; - }); - - m_search_bar.InputField.onValueChanged.AddListener(delegate { m_deferred_update = true; }); -#if false - if (Main.Settings.InventorySearchBarScrollResetOnSubmit) - { - m_search_bar.InputField.onSubmit.AddListener(delegate - { - transform.Find(PathToStashScroll(Type)).GetComponent<Scrollbar>().value = 0.0f; - }); - } -#endif - - m_char_selection_changed_cb = Game.Instance.SelectionCharacter.SelectedUnit.Subscribe(delegate { m_deferred_update = true; }); -#if true - // Add options to the dropdown... - - List<string> options = new List<string>(); - - foreach (FilterCategories flag in EnumHelper.ValidFilterCategories) - { - if (Main.Settings.SearchFilterCategories.HasFlag(flag)) - { - (int idx, string text) = EnhancedInventory.FilterCategoryMap[flag]; - - if (text == null) - { - ItemsFilter.FilterType localization_enum = (ItemsFilter.FilterType)idx; - - // For whatever reason, the localization DB has the wrong info for some of these options... I suspect someone changed the enum order - // around and these particular strings are not used anywhere. - - switch (idx) - { - case (int)ItemsFilter.FilterType.Ingredients: localization_enum = ItemsFilter.FilterType.NonUsable; break; - case (int)ItemsFilter.FilterType.Usable: localization_enum = ItemsFilter.FilterType.Ingredients; break; - case (int)ItemsFilter.FilterType.NonUsable: localization_enum = ItemsFilter.FilterType.Usable; break; - } - - text = LocalizedTexts.Instance.ItemsFilter.GetText(localization_enum); - EnhancedInventory.FilterCategoryMap[flag] = (idx, text); - } - - options.Add(text); - } - } - - m_search_bar.Dropdown.AddOptions(options); - m_search_bar.UpdatePlaceholder(); - - // Gather images for the dropdown... - - List<Image> images = new List<Image>(); - GameObject switch_bar = m_filter_block.Find("SwitchBar").gameObject; - - foreach (Transform child in switch_bar.transform) - { - images.Add(child.Find("Icon")?.GetComponent<Image>()); - } - - while (images.Count < options.Count) - { - images.Add(null); - } - - m_search_icons = images.ToArray(); - - UpdateDropdownIcon(); - - // Tweak positioning depending on user config... - - RectTransform search_transform = m_search_bar.GameObject.GetComponent<RectTransform>(); - - if (Main.Settings.toggleEnhancedInventory) - { - search_transform.localScale = new Vector3(0.6f, 0.6f, 1.0f); - search_transform.localPosition = new Vector3(0.0f, -8.0f, 0.0f); - - RectTransform sb_transform = switch_bar.GetComponent<RectTransform>(); - sb_transform.localPosition = new Vector3( - sb_transform.localPosition.x, - sb_transform.localPosition.y + 23.0f, - sb_transform.localPosition.z); - sb_transform.localScale = new Vector3(0.6f, 0.6f, 1.0f); - - // Select the appropriate handler, if possible, in the switch bar, when selected using the dropdown. - m_search_bar.Dropdown.onValueChanged.AddListener(delegate (int idx) - { - if (idx <= (int)ItemsFilter.FilterType.NonUsable) - { - switch_bar.transform.GetChild(idx).GetComponent<ItemsFilterEntityPCView>().ViewModel.IsSelected.Value = true; - } - }); - - // destroy the top and bottom gfx as they cause a lot of noise - Destroy(m_search_bar.GameObject.transform.Find("Background/Decoration/TopLineImage").gameObject); - Destroy(m_search_bar.GameObject.transform.Find("Background/Decoration/BottomLineImage").gameObject); - } - else - { - search_transform.localScale = new Vector3(0.85f, 0.85f, 1.0f); - search_transform.localPosition = new Vector3(0.0f, 2.0f, 0.0f); - Destroy(switch_bar); - } -#endif - } - - private void OnEnable() { - m_apply_handlers = true; - } - - private void OnDestroy() { - m_char_selection_changed_cb.Dispose(); - } - - private void Update() { - if (m_apply_handlers) { - switch (Type) { - case InventoryType.InventoryStash: { - InventoryStashPCView stash_pc_view = GetComponentInParent<InventoryStashPCView>(); - m_active_filter = stash_pc_view.ViewModel.ItemsFilter.CurrentFilter; - stash_pc_view.ViewModel.ItemSlotsGroup.CollectionChangedCommand.Subscribe(delegate { m_deferred_update = true; }); - stash_pc_view.ViewModel.ItemsFilter.CurrentSorter.Subscribe(delegate { m_deferred_update = true; }); - break; - } - case InventoryType.Vendor: { - VendorPCView vendor_pc_view = GetComponentInParent<VendorPCView>(); - m_active_filter = vendor_pc_view.ViewModel.VendorItemsFilter.CurrentFilter; - vendor_pc_view.ViewModel.VendorSlotsGroup.CollectionChangedCommand.Subscribe(delegate { m_deferred_update = true; }); - vendor_pc_view.ViewModel.VendorItemsFilter.CurrentSorter.Subscribe(delegate { m_deferred_update = true; }); - break; - } - case InventoryType.LootCollector: { - LootCollectorPCView collector_pc_view = GetComponent<LootCollectorPCView>(); - m_active_filter = collector_pc_view.ViewModel.ItemsFilter?.CurrentFilter; - collector_pc_view.ViewModel.CollectionChangedCommand.Subscribe(delegate { m_deferred_update = true; }); - - if (m_active_filter != null) // can be null if not on stash view - { - collector_pc_view.ViewModel.ItemsFilter.CurrentSorter.Subscribe(delegate { m_deferred_update = true; }); - } - break; - } - case InventoryType.LootInventoryStash: { - LootInventoryStashPCView inventory_pc_view = GetComponentInParent<LootInventoryStashPCView>(); - m_active_filter = inventory_pc_view.ViewModel.ItemsFilter.CurrentFilter; - inventory_pc_view.ViewModel.ItemSlotsGroup.CollectionChangedCommand.Subscribe(delegate { m_deferred_update = true; }); - inventory_pc_view.ViewModel.ItemsFilter.CurrentSorter.Subscribe(delegate { m_deferred_update = true; }); - break; - } - } -#if false - Transform switch_bar = m_filter_block.Find("SwitchBar"); - - if (switch_bar != null && Type != InventoryType.LootCollector) - { - // Add listeners to each button; if the button changes, we change the dropdown to match. - foreach (ItemsFilter.FilterType filter in Enum.GetValues(typeof(ItemsFilter.FilterType))) - { - int idx = (int)filter; - int mapped_idx = Main.FilterMapper.From(idx); - - if (mapped_idx == -1) - { - switch_bar.transform.GetChild(idx).gameObject.SetActive(false); - } - else - { - ItemsFilterEntityPCView toggle = switch_bar.transform.GetChild(idx).GetComponent<ItemsFilterEntityPCView>(); - toggle.ViewModel.IsSelected.Subscribe(delegate (bool on) { if (on) m_search_bar.Dropdown.value = mapped_idx; } ); - } - } - } - - if (Type == InventoryType.InventoryStash || Type == InventoryType.LootInventoryStash) - { - if (Main.Settings.InventorySearchBarResetFilterWhenOpening) - { - m_search_bar.Dropdown.value = Main.FilterMapper.From((int)ItemsFilter.FilterType.NoFilter); - } - - if (Main.Settings.InventorySearchBarFocusWhenOpening) - { - m_search_bar.FocusSearchBar(); - } - } -#endif - m_apply_handlers = false; - } - - if (m_deferred_update) { - Mod.Log("hi deferred_update"); - if (m_active_filter != null) { -#if false - Hooks.ItemsFilter_ShouldShowItem_Blueprint.SearchContents = m_search_bar.InputField.text; - m_active_filter.SetValueAndForceNotify((ItemsFilter.FilterType)Main.FilterMapper.To(m_search_bar.Dropdown.value)); - Hooks.ItemsFilter_ShouldShowItem_Blueprint.SearchContents = null; -#endif - } - - m_deferred_update = false; - } - } - - private void UpdateDropdownIcon() { - m_search_bar.DropdownIconObject.GetComponent<Image>().sprite = m_search_icons[m_search_bar.Dropdown.value]?.sprite; - m_search_bar.DropdownIconObject.gameObject.SetActive(m_search_bar.DropdownIconObject.GetComponent<Image>().sprite != null); - } -#endif - public static string PathToFilterBlock(InventoryType type) { - switch (type) { - case InventoryType.LootCollector: return "Filters/PC_FilterBlock (1)/FilterPCView"; - case InventoryType.LootInventoryStash: return "Filters/PC_FilterBlock/FilterPCView"; - } - - return "PC_FilterBlock/FilterPCView"; - } - - public static string PathToSorter(InventoryType type) { - string filter = PathToFilterBlock(type); - return filter.Substring(0, filter.LastIndexOf('/')); - } - - public static string PathToStashScroll(InventoryType type) { - switch (type) { - case InventoryType.InventoryStash: return "StashScrollView/Scrollbar Vertical"; - case InventoryType.Vendor: return "VendorStashScrollView/Scrollbar Vertical"; - case InventoryType.LootCollector: return "Collector/StashScrollView/Scrollbar Vertical"; - case InventoryType.LootInventoryStash: return "Stash/StashScrollView/Scrollbar Vertical"; - } - - return null; - } - } -} \ No newline at end of file diff --git a/ToyBox/classes/MainUI/EnhancedUI/EnhancedSpellbookController.cs b/ToyBox/classes/MainUI/EnhancedUI/EnhancedSpellbookController.cs deleted file mode 100644 index 5265efdcd..000000000 --- a/ToyBox/classes/MainUI/EnhancedUI/EnhancedSpellbookController.cs +++ /dev/null @@ -1,432 +0,0 @@ -using Kingmaker.UI.MVVM._PCView.ServiceWindows.Spellbook; -using Kingmaker.UI.MVVM._PCView.ServiceWindows.Spellbook.KnownSpells; -using Owlcat.Runtime.UniRx; -using System; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; -using UniRx; -using Kingmaker.UnitLogic.Abilities; -using Kingmaker.UI.Common; -using Kingmaker.UI.MVVM._VM.ServiceWindows.Spellbook.KnownSpells; -using Kingmaker.UnitLogic; -using Owlcat.Runtime.UI.Controls.Other; -using Owlcat.Runtime.UI.Controls.Button; -using Kingmaker.UI.MVVM._VM.ServiceWindows.Spellbook.Metamagic; -using HarmonyLib; -using Kingmaker.UI; -using TMPro; -using UnityEngine.UI; -using Kingmaker.UI.MVVM._VM.ServiceWindows.Spellbook.Switchers; -using Kingmaker.UnitLogic.Abilities.Blueprints; -using Kingmaker.UI.MVVM._PCView.ServiceWindows.CharacterInfo.Menu; -using Kingmaker.Items; -using Kingmaker.Blueprints.Items.Components; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.Blueprints; -using Kingmaker.Blueprints.Root; -using Kingmaker.EntitySystem.Stats; -using ModKit; -using Kingmaker.PubSubSystem; -using Kingmaker.UI._ConsoleUI.InputLayers.InGameLayer; -using Kingmaker; - -namespace ToyBox { - public class DummyKnownSpellsView : SpellbookKnownSpellsPCView { - public override void BindViewImplementation() { } - public override void DestroyViewImplementation() { } - } - - public class EnhancedSpellbookController : MonoBehaviour, - IGlobalSubscriber, - ISubscriber { - private SearchBar m_search_bar; - - private IReactiveProperty<Spellbook> m_spellbook; - private IReactiveProperty<SpellbookLevelVM> m_spellbook_level; - private IReactiveProperty<AbilityDataVM> m_selected_spell; - - private ScrollRectExtended m_scroll_bar; - private TextMeshProUGUI m_title_label; - private SpellbookKnownSpellPCView m_known_spell_prefab; - private SpellbookSpellPCView m_possible_spell_prefab; - - private ToggleWorkaround m_all_spells_checkbox; - private ToggleWorkaround m_possible_spells_checkbox; - private ToggleWorkaround m_metamagic_checkbox; - private Button m_learn_scrolls_button; - - private string m_localized_fort; - private string m_localized_reflex; - private string m_localized_will; - - private List<IDisposable> m_handlers = new List<IDisposable>(); - private bool m_deferred_update = true; - - private int m_last_spell_level = -1; - private IDisposable m_SelectedUnitUpdate; - private UnitEntityData m_selected_unit = null; - - public void OnDestroy() { - if (m_SelectedUnitUpdate != null) { - m_SelectedUnitUpdate.Dispose(); - } - } - public void Awake() { - EventBus.Subscribe((object)this); - m_SelectedUnitUpdate = Game.Instance.SelectionCharacter.SelectedUnit.Subscribe(delegate (UnitReference u) { - m_selected_unit = u.Value; - //Mod.Log($"EnhancedSpellbookController - selected character changed to {m_selected_unit?.CharacterName.orange() ?? "null"}"); - m_deferred_update = true; - }); - var mainContainer = transform.Find("MainContainer"); - Mod.Debug($"EnhancedSpellbookController - Awake - {mainContainer}"); - m_search_bar = new SearchBar(mainContainer, "Enter spell name..."); - m_search_bar.GameObject.transform.localScale = new Vector3(0.85f, 0.85f, 1.0f); - m_search_bar.GameObject.transform.localPosition = new Vector2(-61.0f, 386.0f); - m_search_bar.DropdownIconObject.SetActive(false); - m_search_bar.Dropdown.onValueChanged.AddListener(delegate { - m_deferred_update = true; - m_scroll_bar.ScrollToTop(); - }); - m_search_bar.InputField.onValueChanged.AddListener(delegate { - m_deferred_update = true; - m_scroll_bar.ScrollToTop(); - }); - - // Setup string options... - - m_localized_fort = LocalizedTexts.Instance.Stats.Entries.First(i => i.Stat == StatType.SaveFortitude).Text; - m_localized_reflex = LocalizedTexts.Instance.Stats.Entries.First(i => i.Stat == StatType.SaveReflex).Text; - m_localized_will = LocalizedTexts.Instance.Stats.Entries.First(i => i.Stat == StatType.SaveWill).Text; - - List<string> options = Enum.GetValues(typeof(SpellbookFilter)).Cast<SpellbookFilter>().Select(i => i.ToString()).ToList(); - options[(int)SpellbookFilter.NoFilter] = "No Filter"; - options[(int)SpellbookFilter.TargetsFortitude] = string.Format("Spell Targets {0}", m_localized_fort); - options[(int)SpellbookFilter.TargetsReflex] = string.Format("Spell Targets {0}", m_localized_reflex); - options[(int)SpellbookFilter.TargetsWill] = string.Format("Spell Targets {0}", m_localized_will); - options[(int)SpellbookFilter.SupportsMetamagic] = string.Format("Supports Metamagic"); - - m_search_bar.Dropdown.AddOptions(options); - m_search_bar.UpdatePlaceholder(); - - // Grab the title label so we can stick spell counts in it - m_title_label = transform.Find("MainContainer/Information/MainTitle/TitleLabel").GetComponent<TextMeshProUGUI>(); - - // The scroll bar is used for resetting the scroll. - m_scroll_bar = transform.Find("MainContainer/KnownSpells/StandardScrollView").GetComponent<ScrollRectExtended>(); - - Transform known_spells_transform = transform.Find("MainContainer/KnownSpells"); - - // Grab what we need from the old view then destroy it. - SpellbookKnownSpellsPCView old_view = known_spells_transform.GetComponent<SpellbookKnownSpellsPCView>(); - m_known_spell_prefab = old_view.m_KnownSpellView; - m_possible_spell_prefab = old_view.m_PossibleSpellView; - Destroy(old_view); - - // Make a dummy view that does nothing - we handle the logic in here. - DummyKnownSpellsView dummy = known_spells_transform.gameObject.AddComponent<DummyKnownSpellsView>(); - dummy.m_KnownSpellView = m_known_spell_prefab; - dummy.m_PossibleSpellView = m_possible_spell_prefab; - var spellbookPCView = GetComponentInParent<SpellbookPCView>(); - if (spellbookPCView) - spellbookPCView.m_KnownSpellsView = dummy; - - // Disable the current spell level indicator, it isn't used any more. - var spellLevelIndicator = transform.Find("MainContainer/Information/CurrentLevel")?.gameObject; - if (spellLevelIndicator != null) Destroy(spellLevelIndicator); - - // Create button to toggle metamagic. - GameObject all_spells_button = Instantiate(transform.Find("MainContainer/KnownSpells/Toggle").gameObject, transform.Find("MainContainer/KnownSpells")); - all_spells_button.name = "ToggleAllSpells"; - all_spells_button.transform.localPosition = new Vector2(501.0f, -405.0f); - all_spells_button.transform.Find("Label").GetComponent<TextMeshProUGUI>().text = "Show All Spell Levels"; - m_all_spells_checkbox = all_spells_button.GetComponent<ToggleWorkaround>(); - m_all_spells_checkbox.onValueChanged.AddListener(delegate { - m_deferred_update = true; - m_scroll_bar.ScrollToTop(); - }); - m_all_spells_checkbox.isOn = Main.Settings.toggleSpellbookShowAllSpellsByDefault; -#if true // FIXME - remove this once Bubblebuffs updates. If I need to add a new button use this path name - GameObject metamagic_button = Instantiate(transform.Find("MainContainer/KnownSpells/Toggle").gameObject, transform.Find("MainContainer/KnownSpells")); - metamagic_button.name = "ToggleMetamagic"; - metamagic_button.transform.localPosition = new Vector2(501.0f, -480.0f); - metamagic_button.transform.Find("Label").GetComponent<TextMeshProUGUI>().text = "Show Metamagic"; - m_metamagic_checkbox = metamagic_button.GetComponent<ToggleWorkaround>(); - m_metamagic_checkbox.onValueChanged.AddListener(delegate { - m_deferred_update = true; - m_scroll_bar.ScrollToTop(); - }); - m_metamagic_checkbox.isOn = Main.Settings.toggleSpellbookShowMetamagicByDefault; - m_metamagic_checkbox.gameObject.SetActive((false)); -#endif - GameObject possible_spells_button = Instantiate(transform.Find("MainContainer/KnownSpells/Toggle").gameObject, transform.Find("MainContainer/KnownSpells")); - possible_spells_button.name = "TogglePossibleSpells"; - possible_spells_button.transform.localPosition = new Vector2(501.0f, -443.0f); - possible_spells_button.transform.Find("Label").GetComponent<TextMeshProUGUI>().text = "Show Unlearned Spells"; - m_possible_spells_checkbox = possible_spells_button.GetComponent<ToggleWorkaround>(); - m_possible_spells_checkbox.onValueChanged.AddListener(delegate { - m_deferred_update = true; - m_scroll_bar.ScrollToTop(); - }); - - // Hide original; keep it around for mod interop. - transform.Find("MainContainer/KnownSpells/Toggle").gameObject.SetActive(false); - - // Move the levels display (which is still used for displaying memorized spells). - Transform levels = transform.Find("MainContainer/Levels"); - levels.GetComponent<HorizontalLayoutGroupWorkaround>().childAlignment = TextAnchor.MiddleLeft; - levels.localPosition = new Vector2(739.0f, 385.0f); - - // Shamelessly steal a button from the inventory and repurpose it for our nefarious deeds. - if (m_learn_scrolls_button == null) { - GameObject learn_spells_object = Instantiate(transform.parent.parent.Find("CharacterInfoPCView/CharacterScreen/Menu/Button").gameObject, transform.Find("MainContainer")); - learn_spells_object.name = "LearnAllSpells"; - learn_spells_object.transform.localPosition = new Vector2(800.0f, -430.0f); - - Transform existing_bg = learn_spells_object.transform.Find("ButtonBackground"); - learn_spells_object.AddComponent<Image>().sprite = existing_bg.GetComponent<Image>().sprite; - - RectTransform rect = learn_spells_object.GetComponent<RectTransform>(); - rect.sizeDelta = new Vector2(150.0f, 60.0f); - - Destroy(existing_bg.gameObject); - Destroy(learn_spells_object.transform.Find("Selected").gameObject); - Destroy(learn_spells_object.GetComponent<CharInfoMenuPCView>()); - Destroy(learn_spells_object.GetComponent<OwlcatMultiButton>()); - - m_learn_scrolls_button = learn_spells_object.AddComponent<Button>(); - m_learn_scrolls_button.onClick.AddListener(delegate { - m_deferred_update = true; - - UnitEntityData unit = m_selected_unit; - foreach (ItemEntity item in GetLearnableScrolls()) { - CopyScroll copy = item.Blueprint.GetComponent<CopyScroll>(); - copy.Copy(item, unit); - } - }); - } - UpdateLearnScrollButton(); - } - - private void OnEnable() { - m_spellbook = null; - } - - private void Update() { - //Mod.Log($"EnhancedSpellbookController - Update"); - - m_deferred_update |= m_spellbook == null; - - if (m_spellbook == null) { - Setup(); - } - - if (m_deferred_update) { - foreach (IDisposable handler in m_handlers) { - handler.Dispose(); - } - - m_handlers.Clear(); - - UpdateLearnScrollButton(); - - WidgetListMVVM widgets = transform.Find("MainContainer/KnownSpells").GetComponent<WidgetListMVVM>(); - widgets.Clear(); - - if (m_spellbook.Value != null && m_spellbook_level.Value != null) { - var count = 0; - count += DrawKnownSpells(widgets); - - if (m_possible_spells_checkbox.isOn) { - count += DrawPossibleSpells(widgets); - } - UpdateSpellCount(count); - foreach (SpellbookKnownSpellPCView spell in transform - .Find("MainContainer/KnownSpells/StandardScrollView/Viewport/Content") - .GetComponentsInChildren<SpellbookKnownSpellPCView>()) { - if (m_all_spells_checkbox.isOn) { - // Event per slot in the prefab to change the selected option. - m_handlers.Add(spell.m_Button.OnLeftClickAsObservable().Subscribe(delegate(Unit _) { SelectMemorisationLevel(spell.ViewModel.SpellLevel); })); - - // Draw the level... - if (Main.Settings.toggleSpellbookShowLevelWhenViewingAllSpells) { - spell.m_SpellLevelContainer.SetActive(true); - } - } - - // If we've chosen to disable metamagic circles, axe them. - if (!Main.Settings.toggleSpellbookShowEmptyMetamagicCircles) { - for (int i = 0; i < spell.ViewModel.SpellMetamagicFeatures.Count; ++i) { - if (!spell.ViewModel.AppliedMetamagicFeatures.Contains(spell.ViewModel.SpellMetamagicFeatures[i])) { - spell.m_MetamagicIcons[i].gameObject.SetActive(false); - } - } - } - } - } - m_deferred_update = false; - } - } - - void UpdateSpellCount(int count) { - m_title_label?.AddSuffix($" ({count} spells)".size(25), '('); - } - void RemoveSpellCount() { - m_title_label?.AddSuffix(null, '('); - } - - private bool ShouldShowSpell(BlueprintAbility spell, SpellbookFilter filter) { - string save = spell.LocalizedSavingThrow; - - if (filter == SpellbookFilter.TargetsFortitude - || filter == SpellbookFilter.TargetsReflex - || filter == SpellbookFilter.TargetsWill - || filter == SpellbookFilter.SupportsMetamagic - ) { - if (string.IsNullOrWhiteSpace(save)) return false; - else if (filter == SpellbookFilter.TargetsFortitude && save.IndexOf(m_localized_fort, StringComparison.OrdinalIgnoreCase) == -1) return false; - else if (filter == SpellbookFilter.TargetsReflex && save.IndexOf(m_localized_reflex, StringComparison.OrdinalIgnoreCase) == -1) return false; - else if (filter == SpellbookFilter.TargetsWill && save.IndexOf(m_localized_will, StringComparison.OrdinalIgnoreCase) == -1) return false; - else if (filter == SpellbookFilter.SupportsMetamagic && spell.AvailableMetamagic == 0) - return false; - } - - string text = m_search_bar.InputField.text; - - if (string.IsNullOrWhiteSpace(text)) { - return true; - } - else if (Main.Settings.SpellbookSearchCriteria.HasFlag(SpellbookSearchCriteria.SpellName) && spell.Name.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0) { - return true; - } - else if (Main.Settings.SpellbookSearchCriteria.HasFlag(SpellbookSearchCriteria.SpellDescription) && spell.Description.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0) { - return true; - } - else if (Main.Settings.SpellbookSearchCriteria.HasFlag(SpellbookSearchCriteria.SpellSaves) && save.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0) { - return true; - } - else if (Main.Settings.SpellbookSearchCriteria.HasFlag(SpellbookSearchCriteria.SpellSchool) && spell.School.ToString().IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0) { - return true; - } - - return false; - } - - private void Setup() { - // Grab the various state we need... - SpellbookPCView spellbook_pc_view = GetComponentInParent<SpellbookPCView>(); - m_spellbook = spellbook_pc_view.ViewModel.CurrentSpellbook; - m_spellbook_level = spellbook_pc_view.ViewModel.CurrentSpellbookLevel; - m_selected_spell = spellbook_pc_view.ViewModel.CurrentSelectedSpell; - - m_spellbook_level.Subscribe(delegate(SpellbookLevelVM level) { - if (level == null) return; - - // Changing the selected level nothing for our view unless we're viewing all spells. - if (!m_all_spells_checkbox.isOn || level.Level == 11 || m_last_spell_level == 11) { - m_deferred_update = true; - m_scroll_bar.ScrollToTop(); - } - - m_last_spell_level = level.Level; - }); - - // This event is fired when the metamagic builder is opened or shut. - spellbook_pc_view.ViewModel.MetamagicBuilderMode.Subscribe(delegate(bool state) { - if (!state) return; - - // If we've been opened, we need to register for the callback every time a new spell is created. - Action old_action = spellbook_pc_view.ViewModel.SpellbookMetamagicMixerVM.m_OnComplete; - AccessTools.FieldRef<SpellbookMetamagicMixerVM, Action> field = AccessTools.FieldRefAccess<SpellbookMetamagicMixerVM, Action>(nameof(SpellbookMetamagicMixerVM.m_OnComplete)); - field.Invoke(spellbook_pc_view.ViewModel.SpellbookMetamagicMixerVM) = delegate { - m_deferred_update = true; - - if (Main.Settings.toggleSpellbookAutoSwitchToMetamagicTab) { - old_action(); - } - }; - }); - - // This event is fired when changing spellbook or updating the spells inside the spellbook. - spellbook_pc_view.m_CharacteristicsView.ViewModel.RefreshCommand.ObserveLastValueOnLateUpdate().Subscribe(delegate(Unit _) { - m_deferred_update = true; - m_scroll_bar.ScrollToTop(); - }); - - if (Main.Settings.toggleSpellbookSearchBarFocusWhenOpening) { - m_search_bar.FocusSearchBar(); - } - } - - private int DrawKnownSpells(WidgetListMVVM widgets) { - List<AbilityDataVM> known_spells = new List<AbilityDataVM>(); - - int spellbook_level = m_spellbook_level.Value.Level; - var count = 0; - for (int level = 0; level <= 10; ++level) { - if (!m_all_spells_checkbox.isOn && spellbook_level != 11 && level != spellbook_level) continue; - - foreach (AbilityData spell in UIUtilityUnit.GetKnownSpellsForLevel(level, m_spellbook.Value)) { -// if (!m_metamagic_checkbox.isOn && spell.MetamagicData != null) continue; - if (spellbook_level == 11 && spell.MetamagicData == null) continue; - - if (ShouldShowSpell(spell.Blueprint, (SpellbookFilter)m_search_bar.Dropdown.value)) { - known_spells.Add(new AbilityDataVM(spell, m_spellbook.Value, m_selected_spell)); - count++; - } - } - } - widgets.DrawEntries(known_spells.OrderBy(i => i.SpellLevel).ThenBy(i => i.DisplayName), m_known_spell_prefab); - return count; - } - - private int DrawPossibleSpells(WidgetListMVVM widgets) { - List<BlueprintAbilityVM> possible_spells = new List<BlueprintAbilityVM>(); - - int spellbook_level = m_spellbook_level.Value.Level; - var count = 0; - if (spellbook_level != 11) { - for (int level = 0; level <= 9; ++level) { - if (!m_all_spells_checkbox.isOn && level != spellbook_level) continue; - - foreach (BlueprintAbility spell in UIUtilityUnit.GetAllPossibleSpellsForLevel(level, m_spellbook.Value)) { - if (ShouldShowSpell(spell, (SpellbookFilter)m_search_bar.Dropdown.value)) { - possible_spells.Add(new BlueprintAbilityVM(spell, m_spellbook.Value, spellbook_level)); - count++; - } - } - } - } - widgets.DrawEntries(possible_spells.OrderBy(i => i.m_SpellLevel).ThenBy(i => i.DisplayName), m_possible_spell_prefab); - return count; - } - - private void SelectMemorisationLevel(int level) { - Transform levels = transform.Find("MainContainer/Levels"); - levels.GetChild(level).GetComponent<OwlcatMultiButton>().OnLeftClick.Invoke(); - } - - private List<ItemEntity> GetLearnableScrolls() { - List<ItemEntity> result = new List<ItemEntity>(); - - UnitEntityData unit = m_selected_unit; - if (unit == null) return result; - Mod.Log($"GetLearnableScrolls for {unit.CharacterName.orange()}"); - foreach (ItemEntity item in UIUtility.GetStashItems()) { - CopyScroll scroll = item.Blueprint.GetComponent<CopyScroll>(); - if (scroll != null && scroll.CanCopy(item, unit)) { - result.Add(item); - } - } - return result; - } - - private void UpdateLearnScrollButton() { - List<ItemEntity> learnable_scrolls = GetLearnableScrolls(); - m_learn_scrolls_button.interactable = learnable_scrolls.Count > 0; - TextMeshProUGUI title_text = m_learn_scrolls_button.transform.Find("MenuTitle").GetComponent<TextMeshProUGUI>(); - title_text.text = string.Format("Learn {0} scrolls", learnable_scrolls.Count); - } - } -} \ No newline at end of file diff --git a/ToyBox/classes/MainUI/EnhancedUI/LootHelper.cs b/ToyBox/classes/MainUI/EnhancedUI/LootHelper.cs deleted file mode 100644 index d2cc4c774..000000000 --- a/ToyBox/classes/MainUI/EnhancedUI/LootHelper.cs +++ /dev/null @@ -1,152 +0,0 @@ -using Kingmaker; -using Kingmaker.Blueprints.Loot; -using Kingmaker.Designers.EventConditionActionSystem.Actions; -using Kingmaker.ElementsSystem; -using Kingmaker.EntitySystem; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.EntitySystem.Stats; -using Kingmaker.Items; -using Kingmaker.PubSubSystem; -using Kingmaker.UI.MVVM; -using Kingmaker.UI.MVVM._PCView.Loot; -using Kingmaker.UI.MVVM._VM.Loot; -using Kingmaker.UnitLogic; -using Kingmaker.Utility; -using Kingmaker.View; -using Kingmaker.View.MapObjects; -using ModKit; -using Newtonsoft.Json; -using Owlcat.Runtime.Core.Utils; -using Owlcat.Runtime.UI.Controls.Button; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.Serialization.Formatters.Binary; -using UnityEngine; - -namespace ToyBox { - public static class LootHelper { - public static string NameAndOwner(this ItemEntity u, bool showRating, bool darkmode = false) => - (showRating ? $"{u.Rating()} ".orange().bold() : "") - + (u.Owner != null ? $"({u.Owner.CharacterName}) ".orange() : "") - + (darkmode ? u.Name.StripHTML().DarkModeRarity(u.Rarity()) : u.Name); - public static string NameAndOwner(this ItemEntity u, bool darkmode = false) => u.NameAndOwner(Main.Settings.showRatingForEnchantmentInventoryItems, darkmode); - public static bool IsLootable(this ItemEntity item, RarityType filter = RarityType.None) { - var rarity = item.Rarity(); - if ((int)rarity < (int)filter) return false; - return item.IsLootable; - } - public static List<ItemEntity> Lootable(this List<ItemEntity> loots, RarityType filter = RarityType.None) => loots.Where(l => l.IsLootable(filter)).ToList(); - public static string GetName(this LootWrapper present) { - if (present.InteractionLoot != null) { - // var name = present.InteractionLoot.Owner.View.name; - var name = present.InteractionLoot.Source.name; - if (name == null || name.Length == 0) name = "Ground"; - return name; - } - if (present.Unit != null) return present.Unit.CharacterName; - return null; - } - - public static List<ItemEntity> GetInteraction(this LootWrapper present) { - if (present.InteractionLoot != null) return present.InteractionLoot.Loot.Items; - if (present.Unit != null) return present.Unit.Inventory.Items; - return null; - } - public static IEnumerable<ItemEntity> Search(this IEnumerable<ItemEntity> items, string searchText) => items.Where(i => searchText.Length > 0 ? i.Name.ToLower().Contains(searchText.ToLower()) : true); - public static List<ItemEntity> GetLewtz(this LootWrapper present, string searchText = "") { - if (present.InteractionLoot != null) return present.InteractionLoot.Loot.Items.Search(searchText).ToList(); - if (present.Unit != null) return present.Unit.Inventory.Items.Search(searchText).ToList(); - return null; - } - public static IEnumerable<LootWrapper> GetMassLootFromCurrentArea() { - List<LootWrapper> lootWrapperList = new(); - var units = Game.Instance.State.Units.All - .Where<UnitEntityData>((Func<UnitEntityData, bool>)(u => u.IsInGame && !u.Descriptor.IsPartyOrPet())); - //.Where<UnitEntityData>((Func<UnitEntityData, bool>)(u => u.IsRevealed && u.IsDeadAndHasLoot)); - foreach (var unitEntityData in units) - lootWrapperList.Add(new LootWrapper() { - Unit = unitEntityData - }); - var interactionLootParts = Game.Instance.State.MapObjects.All - .Where<EntityDataBase>(e => e.IsInGame) - .Select<EntityDataBase, InteractionLootPart>(i => i.Get<InteractionLootPart>()) - .Where<InteractionLootPart>(i => i?.Loot != Game.Instance.Player.SharedStash) - .NotNull<InteractionLootPart>(); - var source = TempList.Get<InteractionLootPart>(); - foreach (var interactionLootPart in interactionLootParts) { - if (// interactionLootPart.Owner.IsRevealed && - interactionLootPart.Loot.HasLoot - //&& ( - // interactionLootPart.LootViewed || interactionLootPart.View is DroppedLoot && !(bool)(EntityPart)interactionLootPart.Owner.Get<DroppedLoot.EntityPartBreathOfMoney>() || (bool)(UnityEngine.Object)interactionLootPart.View.GetComponent<SkinnedMeshRenderer>() - // ) - ) - source.Add(interactionLootPart); - } - var collection = source.Distinct<InteractionLootPart>((IEqualityComparer<InteractionLootPart>)new MassLootHelper.LootDuplicateCheck()).Select<InteractionLootPart, LootWrapper>((Func<InteractionLootPart, LootWrapper>)(i => new LootWrapper() { - InteractionLoot = i - })); - lootWrapperList.AddRange(collection); - return (IEnumerable<LootWrapper>)lootWrapperList; - } - public static void ShowAllChestsOnMap(bool hidden = false) { - var interactionLootParts = Game.Instance.State.MapObjects.All - .Where<EntityDataBase>(e => e.IsInGame) - .Select<EntityDataBase, InteractionLootPart>(i => i.Get<InteractionLootPart>()) - .Where<InteractionLootPart>(i => i?.Loot != Game.Instance.Player.SharedStash) - .NotNull<InteractionLootPart>(); - foreach (var interactionLootPart in interactionLootParts) { - if (hidden) interactionLootPart.Owner.IsPerceptionCheckPassed = true; - interactionLootPart.Owner.SetIsRevealedSilent(true); - } - } - - public static void ShowAllInevitablePortalLoot() { - var interactionLootRevealers = Game.Instance.State.MapObjects.All.OfType<MapObjectEntityData>() - .Where(e => e.IsInGame) - .SelectMany(e => e.Interactions).OfType<InteractionSkillCheckPart>().NotNull() - .Where(i => i.Settings?.DC == 0 && i.Settings.Skill == StatType.Unknown) - .SelectMany(i => i.Settings.CheckPassedActions?.Get()?.Actions?.Actions ?? new GameAction[0]).OfType<HideMapObject>() - .Where(a => a.Unhide) - .Where(a => a.MapObject.GetValue()?.Get<InteractionLootPart>() is not null); - foreach (var revealer in interactionLootRevealers) { - revealer.RunAction(); - } - } - public static void OpenMassLoot() { - var loot = MassLootHelper.GetMassLootFromCurrentArea(); - if (loot == null) return; - var count = loot.Count(); - var count2 = loot.Count(present => present.InteractionLoot != null); - Mod.Debug($"MassLoot: Count = {loot.Count()}"); - Mod.Debug($"MassLoot: Count2 = {count}"); - if (count == 0) return; - // Access to LootContextVM - var contextVM = RootUIContext.Instance.InGameVM?.StaticPartVM?.LootContextVM; - if (contextVM == null) return; - // Add new loot... - var lootVM = new LootVM(LootContextVM.LootWindowMode.ZoneExit, loot, null, () => contextVM.DisposeAndRemove(contextVM.LootVM)); - - // Open window add lootVM int contextVM - contextVM.LootVM.Value = lootVM; - - //EventBus.RaiseEvent((Action<ILootInterractionHandler>)(e => e.HandleZoneLootInterraction(null))); - } - public static void OpenPlayerChest() { - // Access to LootContextVM - var contextVM = RootUIContext.Instance.InGameVM?.StaticPartVM?.LootContextVM; - if (contextVM == null) return; - // Add new loot... - var objects = new EntityViewBase[] { }; - var lootVM = new LootVM(LootContextVM.LootWindowMode.PlayerChest, objects , () => contextVM.DisposeAndRemove(contextVM.LootVM)); - var sharedStash = Game.Instance.Player.SharedStash; - var lootObjectVM = new LootObjectVM("Player Chest", "", sharedStash, LootContextVM.LootWindowMode.PlayerChest, 1); - lootVM.ContextLoot.Add(lootObjectVM); - lootVM.AddDisposable(lootObjectVM); - // Open window add lootVM int contextVM - contextVM.LootVM.Value = lootVM; - } - } -} \ No newline at end of file diff --git a/ToyBox/classes/MainUI/EnhancedUI/OnAreaLoad.cs b/ToyBox/classes/MainUI/EnhancedUI/OnAreaLoad.cs deleted file mode 100644 index 386291525..000000000 --- a/ToyBox/classes/MainUI/EnhancedUI/OnAreaLoad.cs +++ /dev/null @@ -1,134 +0,0 @@ -using Kingmaker; -using Kingmaker.PubSubSystem; -using Kingmaker.UI.MVVM._VM.ServiceWindows.Inventory; -using Kingmaker.UI.ServiceWindow; -using ModKit; -using TMPro; -using ToyBox.classes.MainUI.Inventory; -using UnityEngine; -using UnityEngine.UI; - -namespace ToyBox { - public class OnAreaLoad : IAreaHandler { - public Settings Settings => Main.Settings; - public void SelectedCharacterDidChange() { - - } - public void OnAreaDidLoad() { - SelectedCharacterObserver.Shared.Notifiers -= SelectedCharacterDidChange; - SelectedCharacterObserver.Shared.Notifiers += SelectedCharacterDidChange; - Mod.Log("OnAreaDidLoad"); - EnhancedInventory.RefreshRemappers(); - if (Settings.toggleEnhancedSpellbook) { - LoadSpellbookSearchBar(); - } - -#if false - if (Settings.EnableInventorySearchBar) - { - LoadInventorySearchBar(); - } - - - if (Main.Settings.EnableHighlightableLoot) - { - LoadHighlightLoot(); - } - - if (Main.Settings.EnableVisualOverhaulSorting) - { - SetupSortingStyle(); - } -#endif - } - - public void OnAreaBeginUnloading() { } - - private readonly (string, InventoryType)[] m_inventory_paths = new (string, InventoryType)[] { - // Regular, in-game inventory. - ("ServiceWindowsPCView/InventoryPCView/Inventory/Stash/StashContainer", InventoryType.InventoryStash), - - // World map inventory. - ("ServiceWindowsConfig/InventoryPCView/Inventory/Stash/StashContainer", InventoryType.InventoryStash), - - // Vendor screen: PC inventory view. - ("VendorPCView/MainContent/PlayerStash", InventoryType.InventoryStash), - - // Vendor screen: Vendor goods view. - ("VendorPCView/MainContent/VendorBlock", InventoryType.Vendor), - - // Shared stash: PC inventory view. - ("LootPCView/Window/Inventory", InventoryType.LootInventoryStash), - - // Shared stash: Stash items view. - ("LootPCView/Window/Collector", InventoryType.LootCollector), - }; - - private void LoadInventorySearchBar() { - foreach ((string path, InventoryType type) in m_inventory_paths) { - Transform filters_block_transform = Game.Instance.UI.MainCanvas.transform.Find(path); - if (filters_block_transform != null) { - filters_block_transform.gameObject.AddComponent<EnhancedInventoryController>().Type = type; - } - } - } - - // InGamePCView(Clone)/InGameStaticPartPCView/StaticCanvas/ServiceWindowsPCView/Background/Windows/SpellbookPCView/SpellbookScreen/MainContainer/Information/MainTitle/ - // GlobalMapPCView(Clone)/StaticCanvas/ServiceWindowsConfig/Background/Windows/SpellbookPCView/SpellbookScreen/MainContainer/Information/MainTitle/ - private void LoadSpellbookSearchBar() { - string[] paths = new string[] { - "ServiceWindowsPCView/Background/Windows/SpellbookPCView/SpellbookScreen", // game - "ServiceWindowsConfig/Background/Windows/SpellbookPCView/SpellbookScreen" // world map - }; - - foreach (string path in paths) { - Transform spellbook = Game.Instance.UI.MainCanvas.transform.Find(path); - if (spellbook != null) { - var controller = spellbook.gameObject.AddComponent<EnhancedSpellbookController>(); -// controller.Awake(); // FIXME - why do I have to call this? What is the proper way to get this controller installed and get awake called by the framework and not by Marria - } - } - } - - private void SetupSortingStyle() { - foreach ((string path, InventoryType type) in m_inventory_paths) { - string viewport_path = $"{path}/{EnhancedInventoryController.PathToSorter(type)}/Sorting/Dropdown/Template/Viewport"; - Transform viewport = Game.Instance.UI.MainCanvas.transform.Find(viewport_path); - - // This happens if we're on a screen that we don't have access to or screens that have different formatting. - if (viewport == null) continue; - - Transform content = viewport.Find("Content"); - Transform item = content.Find("Item"); - - VerticalLayoutGroup group = content.GetComponent<VerticalLayoutGroup>(); - TextMeshProUGUI item_label = item.Find("Item Label").GetComponent<TextMeshProUGUI>(); - RectTransform item_background = item.Find("Item Background").GetComponent<RectTransform>(); - RectTransform item_checkmark = item.Find("Item Checkmark").GetComponent<RectTransform>(); - RectTransform item_bottom_border = item.Find("BottomBorderImage").GetComponent<RectTransform>(); - - group.spacing = 4; - group.padding.top = 0; - group.padding.bottom = 0; - - item_label.fontSize = 16.0f; - item_label.horizontalAlignment = HorizontalAlignmentOptions.Center; - - item_background.anchorMin = new Vector2(0.0f, 0.0f); - item_background.anchorMax = new Vector2(1.0f, 1.0f); - item_background.offsetMin = new Vector2(0.0f, 0.0f); - item_background.offsetMax = new Vector2(0.0f, 0.0f); - - item_checkmark.anchorMin = new Vector2(0.0f, 0.0f); - item_checkmark.anchorMax = new Vector2(1.0f, 1.0f); - item_checkmark.offsetMin = new Vector2(0.0f, 0.0f); - item_checkmark.offsetMax = new Vector2(0.0f, 0.0f); - - item_bottom_border.anchorMin = new Vector2(0.0f, 0.0f); - item_bottom_border.anchorMax = new Vector2(1.0f, 0.0f); - item_bottom_border.offsetMin = new Vector2(0.0f, -2.0f); - item_bottom_border.offsetMax = new Vector2(0.0f, 0.0f); - } - } - } -} \ No newline at end of file diff --git a/ToyBox/classes/MainUI/EnhancedUI/PhatLoot.cs b/ToyBox/classes/MainUI/EnhancedUI/PhatLoot.cs deleted file mode 100644 index 35d6c4322..000000000 --- a/ToyBox/classes/MainUI/EnhancedUI/PhatLoot.cs +++ /dev/null @@ -1,395 +0,0 @@ -using System; -using System.Linq; -using UnityEngine; -using Kingmaker; -using Kingmaker.Designers.EventConditionActionSystem.Evaluators; -using Kingmaker.EntitySystem.Entities; -using ModKit; -using ToyBox.Multiclass; -using static ModKit.UI; -using Kingmaker.View.MapObjects; -using Kingmaker.View.MapObjects.InteractionRestrictions; -using ModKit.Utility; - -namespace ToyBox { - public class PhatLoot { - public static Settings Settings => Main.Settings; - public static string searchText = ""; - - // - private const string MassLootBox = "Open Mass Loot Window"; - private const string OpenPlayerChest = "Open Player Chest"; - public static void ResetGUI() { } - - public static void OnLoad() { - KeyBindings.RegisterAction(MassLootBox, LootHelper.OpenMassLoot); - KeyBindings.RegisterAction(OpenPlayerChest, LootHelper.OpenPlayerChest); - } - - public static void OnGUI() { - if (Game.Instance?.Player?.Inventory == null) return; -#if false - Div(0, 25); - var inventory = Game.Instance.Player.Inventory; - var items = inventory.ToList(); - HStack("Inventory", 1, - () => { - ActionButton("Export", () => items.Export("inventory.json"), Width(150)); - Space(25); - ActionButton("Import", () => inventory.Import("inventory.json"), Width(150)); - Space(25); - ActionButton("Replace", () => inventory.Import("inventory.json", true), Width(150)); - }, - () => { } - ); -#endif - Div(0, 25); - HStack("Loot", 1, - () => { - BindableActionButton(MassLootBox, Width(400)); - Space(95 - 150); - Label("Lets you open up the area's mass loot screen to grab goodies whenever you want. Normally shown only when you exit the area".green()); - }, - () => { - BindableActionButton(OpenPlayerChest, Width(400)); - Space(95 - 150); - Label("Lets you open up your player storage chest that you find near your bed at the Inn and other places".green()); - }, - () => { - ActionButton("Reveal Ground Loot", () => LootHelper.ShowAllChestsOnMap(), Width(400)); - Space(150); - Label("Shows all chests/bags/etc on the map excluding hidden".green()); - }, - () => { - ActionButton("Reveal Hidden Ground Loot", () => LootHelper.ShowAllChestsOnMap(true), Width(400)); - Space(150); - Label("Shows all chests/bags/etc on the map including hidden".green()); - }, - () => { - ActionButton("Reveal Inevitable Loot", LootHelper.ShowAllInevitablePortalLoot, Width(400)); - Space(150); - Label("Shows unlocked Inevitable Excess DLC rewards on the map".green()); - }, -#if DEBUG - () => Toggle("Show reasons you can not equip an item in tooltips", ref Settings.toggleShowCantEquipReasons), -#endif - () => { } - ); - Div(0, 25); - HStack(("Mass Loot"), 1, - () => { - Toggle("Show Everything When Leaving Map", ref Settings.toggleMassLootEverything, 400.width()); - 150.space(); - Label("Some items might be invisible until looted".green()); - }, - () => { - Toggle("Steal from living NPCs", ref Settings.toggleLootAliveUnits, 400.width()); - 150.space(); - Label("Allow Mass Loot to steal from living NPCs".green()); - }, - () => { - Toggle("Allow Looting Of Locked Items", ref Settings.toggleOverrideLockedItems, 400.width()); - 150.space(); - Label("This allows you to loot items that are locked such as items carried by certain NPCs and items locked on your characters" - .green() - + "\nWARNING: ".yellow().bold() - + "This may affect story progression (e.g. your purple knife)".yellow()); - }, - () => { } - ); - Div(0, 25); - HStack("Loot Rarity Coloring", 1, - () => { - using (VerticalScope(300.width())) { - Toggle("Show Rarity Tags", ref Settings.toggleShowRarityTags); - Toggle("Color Item Names", ref Settings.toggleColorLootByRarity); - } - using (VerticalScope()) { - Label($"This makes loot function like Diablo or Borderlands. {"Note: turning this off requires you to save and reload for it to take effect.".orange()}" - .green()); - } - }, - () => { - using (VerticalScope(400.width())) { - Label("Minimum Rarity For Loot Rarity Tags/Colors".cyan(), AutoWidth()); - RarityGrid(ref Settings.minRarityToColor, 4, AutoWidth()); - } - }); - Div(0, 25); - HStack("Loot Rarity Filtering", 1, - () => { - using (VerticalScope()) { - Label("Warning: ".orange().bold() + "The following is experimental and might behave unexpectedly.".green()); - using (HorizontalScope()) { - using (VerticalScope()) { - Label($"This hides map pins of loot containers containing at most the selected rarity. {"Note: Changing settings requires reopening the map.".orange()}".green()); - Label("Maximum Rarity To Hide:".cyan(), AutoWidth()); - RarityGrid(ref Settings.maxRarityToHide, 4, AutoWidth()); - } - } - } - }, - // The following options let you configure loot filtering and auto sell levels:".green()); - () => { } - ); - Div(0, 25); - HStack("Enhanced Inventory", - 1, - () => { - using (VerticalScope()) { - using (HorizontalScope()) { - if (Toggle("Enable Enhanced Inventory", ref Settings.toggleEnhancedInventory, 300.width())) - EnhancedInventory.RefreshRemappers(); - 25.space(); - Label("Selected features revived from Xenofell's excellent mod".green()); - } - - using (HorizontalScope()) { - Toggle("Always Keep Search Filter Active", ref Settings.toggleDontClearSearchWhenLoseFocus, 300.width()); - 25.space(); - HelpLabel("When ticked, this keeps your search active when you click to dismiss the Search Bar. This allows you to apply the search to different item categories.\n" + "Untick this if you wish for the standard game behavior where it clears your search".orange()); - } - 15.space(); - } - }, - () => { - if (!Settings.toggleEnhancedInventory) return; - using (VerticalScope()) { - Rect divRect; - using (HorizontalScope()) { - Label("Enabled Sort Categories".Cyan(), 300.width()); - 25.space(); - HelpLabel("Here you can choose which Sort Options appear in the popup menu"); - divRect = DivLastRect(); - } - var hscopeRect = DivLastRect(); - Div(hscopeRect.x, 0, divRect.x + divRect.width - hscopeRect.x); - ItemSortCategories new_options = ItemSortCategories.NotSorted; - var selectableCategories = EnumHelper.ValidSorterCategories.Where(i => i != ItemSortCategories.NotSorted).ToList(); - var changed = false; - Table(selectableCategories, - (flag) => { - //Mod.Log($" {flag.ToString()}"); - if (flag == ItemSortCategories.NotSorted || flag == ItemSortCategories.Default) - return; - bool isSet = Settings.InventoryItemSorterOptions.HasFlag(flag); - using (HorizontalScope(250)) { - 30.space(); - if (Toggle($"{EnhancedInventory.SorterCategoryMap[flag].Item2 ?? flag.ToString()}", ref isSet)) changed = true; - } - if (isSet) { - new_options |= flag; - } - }, - 2, - null, - 375.width()); - 65.space(() => ActionButton("Use Default", () => new_options = ItemSortCategories.Default)); - Settings.InventoryItemSorterOptions = new_options; - if (changed) EnhancedInventory.RefreshRemappers(); - } - }, - () => { - if (!Settings.toggleEnhancedInventory) return; - using (VerticalScope()) { - Rect divRect; - using (HorizontalScope()) { - Label("Enabled Search Filters".Cyan(), 300.width()); - 25.space(); - HelpLabel("Here you can choose which Search filters appear in the popup menu"); - divRect = DivLastRect(); - } - var hscopeRect = DivLastRect(); - Div(hscopeRect.x, 0, divRect.x + divRect.width - hscopeRect.x); - FilterCategories new_options = default; - var selectableFilters = EnumHelper.ValidFilterCategories.Where(i => i != FilterCategories.NoFilter).ToList(); - var changed = false; - Table(selectableFilters, - (flag) => { - //Mod.Log($" {flag.ToString()}"); - bool isSet = Settings.SearchFilterCategories.HasFlag(flag); - using (HorizontalScope(250)) { - 30.space(); - if (Toggle($"{EnhancedInventory.FilterCategoryMap[flag].Item2 ?? flag.ToString()}", ref isSet)) changed = true; - } - if (isSet) { - new_options |= flag; - } - }, - 2, - null, - 375.width()); - 65.space(() => ActionButton("Use Default", () => new_options = FilterCategories.Default)); - Settings.SearchFilterCategories = new_options; - if (changed) EnhancedInventory.RefreshRemappers(); - } - }); - Div(0, 25); - HStack("Spellbook", - 1, - () => { - if (Toggle("Enable Enhanced Spellbook", ref Settings.toggleEnhancedSpellbook, 300.width())) - EnhancedInventory.RefreshRemappers(); - 25.space(); - Label("Various spellbook enhancements revived from Xenofell's excellent mod".green()); - }, - () => { - if (Settings.toggleEnhancedSpellbook) { - using (VerticalScope()) { - Toggle("Give the search bar focus when opening the spellbook screen", ref Settings.toggleSpellbookSearchBarFocusWhenOpening); - Toggle("Show all spell levels by default", ref Settings.toggleSpellbookShowAllSpellsByDefault); - //Toggle("Show metamagic by default", ref Settings.toggleSpellbookShowMetamagicByDefault); - Toggle("Show the empty grey metamagic circles above spells", ref Settings.toggleSpellbookShowEmptyMetamagicCircles); - Toggle("Show level of the spell when the spellbook is showing all spell levels", ref Settings.toggleSpellbookShowLevelWhenViewingAllSpells); - Toggle("After creating a metamagic spell, switch to the metamagic tab", ref Settings.toggleSpellbookAutoSwitchToMetamagicTab); - 15.space(); - Rect divRect; - using (HorizontalScope()) { - Label("Spellbook Search Criteria".Cyan(), 300.width()); - 25.space(); - HelpLabel("Here you can choose which Search filters appear in the spellbook search popup menu"); - divRect = DivLastRect(); - } - var hscopeRect = DivLastRect(); - Div(hscopeRect.x, 0, divRect.x + divRect.width - hscopeRect.x); - SpellbookSearchCriteria new_options = default; - var changed = false; - var spellbookFilterCategories = EnumHelper.ValidSpellbookSearchCriteria.ToList(); - Table(spellbookFilterCategories, - (flag) => { - //Mod.Log($" {flag.ToString()}"); - bool isSet = Settings.SpellbookSearchCriteria.HasFlag(flag); - using (HorizontalScope(250)) { - 30.space(); - if (Toggle($"{flag.ToString()}", ref isSet)) changed = true; - } - if (isSet) { - new_options |= flag; - } - }, - 2, - null, - 375.width()); - 65.space(() => ActionButton("Use Default", () => new_options = SpellbookSearchCriteria.Default)); - Settings.SpellbookSearchCriteria = new_options; - if (changed) EnhancedInventory.RefreshRemappers(); - } - } - }, - () => { }); - Div(0, 25); - HStack("Bulk Sell", 1, - () => { - Toggle("Enable custom bulk selling settings", ref Settings.toggleCustomBulkSell, 400.width()); - }, - () => { - if (!Settings.toggleCustomBulkSell) return; - using (VerticalScope()) { - BulkSell.OnGUI(); - } - - }); - Div(0, 25); - if (Game.Instance.CurrentlyLoadedArea == null) return; - var isEmpty = true; - HStack("Loot Checklist", 1, - () => { - var areaName = ""; - if (Main.IsInGame) { - try { - areaName = Game.Instance.CurrentlyLoadedArea.AreaDisplayName; - } - catch { } - var areaPrivateName = Game.Instance.CurrentlyLoadedArea.name; - if (areaPrivateName != areaName) areaName += $"\n({areaPrivateName})".yellow(); - } - Label(areaName.orange().bold(), Width(300)); - Label("Rarity: ".cyan(), AutoWidth()); - RarityGrid(ref Settings.lootChecklistFilterRarity, 4, AutoWidth()); - }, - () => { - ActionTextField( - ref searchText, - "itemSearchText", - (text) => { }, - () => { }, - Width(300)); - Space(25); Toggle("Show Friendly", ref Settings.toggleLootChecklistFilterFriendlies); - Space(25); Toggle("Blueprint", ref Settings.toggleLootChecklistFilterBlueprint, AutoWidth()); - Space(25); Toggle("Description", ref Settings.toggleLootChecklistFilterDescription, AutoWidth()); - }, - () => { - if (!Main.IsInGame) { Label("Not available in the Main Menu".orange()); return; } - var presentGroups = LootHelper.GetMassLootFromCurrentArea().GroupBy(p => p.InteractionLoot != null ? "Containers" : "Units"); - var indent = 3; - using (VerticalScope()) { - foreach (var group in presentGroups.Reverse()) { - var presents = group.AsEnumerable().OrderByDescending(p => { - var loot = p.GetLewtz(searchText); - if (loot.Count == 0) return 0; - else return (int)loot.Max(l => l.Rarity()); - }).ToList(); - var rarity = Settings.lootChecklistFilterRarity; - var count = presents.Where(p => p.Unit == null || (Settings.toggleLootChecklistFilterFriendlies && !p.Unit.IsPlayersEnemy || p.Unit.IsPlayersEnemy) || (!Settings.toggleLootChecklistFilterFriendlies && p.Unit.IsPlayersEnemy)).Count(p => p.GetLewtz(searchText).Lootable(rarity).Count() > 0); - Label($"{group.Key.cyan()}: {count}"); - Div(indent); - foreach (var present in presents) { - var phatLewtz = present.GetLewtz(searchText).Lootable(rarity).OrderByDescending(l => l.Rarity()).ToList(); - var unit = present.Unit; - if (phatLewtz.Any() - && (unit == null - || (Settings.toggleLootChecklistFilterFriendlies && !unit.IsPlayersEnemy || unit.IsPlayersEnemy) - || (!Settings.toggleLootChecklistFilterFriendlies && unit.IsPlayersEnemy) - ) - ) { - isEmpty = false; - Div(); - using (HorizontalScope()) { - Space(indent); - Label($"{present.GetName()}".orange().bold(), Width(325)); - if (present.InteractionLoot != null) { - if (present.InteractionLoot?.Owner?.PerceptionCheckDC > 0) - Label($" Perception DC: {present.InteractionLoot?.Owner?.PerceptionCheckDC}".green().bold(), Width(125)); - else - Label($" Perception DC: NA".orange().bold(), Width(125)); - int? trickDc = present.InteractionLoot?.Owner?.Get<DisableDeviceRestrictionPart>()?.DC; - if (trickDc > 0) - Label($" Trickery DC: {trickDc}".green().bold(), Width(125)); - else - Label($" Trickery DC: NA".orange().bold(), Width(125)); - } - Space(25); - using (VerticalScope()) { - foreach (var lewt in phatLewtz) { - var description = lewt.Blueprint.Description; - var showBP = Settings.toggleLootChecklistFilterBlueprint; - var showDesc = Settings.toggleLootChecklistFilterDescription && description != null && description.Length > 0; - using (HorizontalScope()) { - //Main.Log($"rarity: {lewt.Blueprint.Rarity()} - color: {lewt.Blueprint.Rarity().color()}"); - Label(lewt.Name.StripHTML().Rarity(lewt.Blueprint.Rarity()), showDesc || showBP ? Width(350) : AutoWidth()); - if (showBP) { - Space(100); Label(lewt.Blueprint.GetDisplayName().grey(), showDesc ? Width(350) : AutoWidth()); - } - if (!showDesc) continue; - Space(100); Label(description.StripHTML().green()); - } - } - } - } - Space(25); - } - } - Space(25); - } - } - }, - () => { - if (!isEmpty) return; - using (HorizontalScope()) { - Label("No Loot Available".orange(), AutoWidth()); - } - } - ); - } - } -} diff --git a/ToyBox/classes/MainUI/EnhancedUI/SearchBar.cs b/ToyBox/classes/MainUI/EnhancedUI/SearchBar.cs deleted file mode 100644 index 839970fac..000000000 --- a/ToyBox/classes/MainUI/EnhancedUI/SearchBar.cs +++ /dev/null @@ -1,105 +0,0 @@ -using Kingmaker; -using Kingmaker.UI.MVVM._PCView.CharGen.Phases.FeatureSelector; -using Owlcat.Runtime.UI.Controls.Button; -using TMPro; -using UnityEngine; -using UnityEngine.EventSystems; -using ModKit; - -namespace ToyBox -{ - public class SearchBar - { - public GameObject GameObject; - public TMP_InputField InputField; - public OwlcatButton InputButton; - public TMP_Dropdown Dropdown; - public OwlcatButton DropdownButton; - public GameObject DropdownIconObject; - public TextMeshProUGUI PlaceholderText; - - public SearchBar(Transform parent, string placeholder, string name = "EnhancedInventory_SearchBar") - { - Transform prefab_transform = Game.Instance.UI.MainCanvas.transform.Find("ChargenPCView/ContentWrapper/DetailedViewZone/ChargenFeaturesDetailedPCView/FeatureSelectorPlace/FeatureSelectorView/FeatureSearchView"); - - if (prefab_transform == null) - { - string err = "Error: Unable to locate search bar prefab, it's likely a patch has changed the UI setup, or you are in an unexpected situation. Please report this bug!"; - Mod.Error(err); - throw new UnityException(err); - } - - GameObject = GameObject.Instantiate(prefab_transform, parent, false).gameObject; - GameObject.name = name; - - InputButton = GameObject.transform.Find("FieldPlace/SearchField/SearchBackImage/Placeholder").GetComponent<OwlcatButton>(); - Dropdown = GameObject.transform.Find("FieldPlace/SearchField/SearchBackImage/Dropdown").GetComponent<TMP_Dropdown>(); - DropdownButton = GameObject.transform.Find("FieldPlace/SearchField/SearchBackImage/Dropdown/GenerateButtonPlace").GetComponent<OwlcatButton>(); - DropdownIconObject = GameObject.transform.Find("FieldPlace/SearchField/SearchBackImage/Dropdown/GenerateButtonPlace/GenerateButton/Icon").gameObject; - PlaceholderText = GameObject.transform.Find("FieldPlace/SearchField/SearchBackImage/Placeholder/Label").GetComponent<TextMeshProUGUI>(); - InputField = GameObject.transform.Find("FieldPlace/SearchField/SearchBackImage/InputField").GetComponent<TMP_InputField>(); - - InputField.onValueChanged.AddListener(delegate (string _) { OnInputFieldEdit(); }); - InputField.onEndEdit.AddListener(delegate (string _) { OnInputFieldEditEnd(); }); - InputButton.OnLeftClick.AddListener(delegate { OnInputClick(); }); - Dropdown.onValueChanged.AddListener(delegate (int _) { OnDropdownSelected(); }); - DropdownButton.OnLeftClick.AddListener(delegate { OnDropdownButton(); }); - - GameObject.Destroy(GameObject.GetComponent<CharGenFeatureSearchPCView>()); // controller from where we stole the search bar - InputField.transform.Find("Text Area/Placeholder").GetComponent<TextMeshProUGUI>().SetText(placeholder); - Dropdown.ClearOptions(); - - GameObject.Destroy(Dropdown.template.Find("Viewport/TopBorderImage").gameObject); - Transform border = Dropdown.template.Find("Viewport/Content/Item/BottomBorderImage"); - RectTransform rect = border.GetComponent<RectTransform>(); - rect.anchorMin = new Vector2(0.0f, 0.0f); - rect.anchorMax = new Vector2(1.0f, 0.0f); - rect.offsetMin = new Vector2(0.0f, 0.0f); - rect.offsetMax = new Vector2(0.0f, 2.0f); - } - - public void FocusSearchBar() - { - OnInputClick(); - } - - public void UpdatePlaceholder() - { - PlaceholderText.text = string.IsNullOrEmpty(InputField.text) ? Dropdown.options[Dropdown.value].text : InputField.text; - } - - private void OnDropdownButton() - { - Dropdown.Show(); - } - - private void OnDropdownSelected() - { - UpdatePlaceholder(); - } - - private void OnInputClick() - { - InputButton.gameObject.SetActive(false); - InputField.gameObject.SetActive(true); - InputField.Select(); - InputField.ActivateInputField(); - } - - private void OnInputFieldEdit() - { - UpdatePlaceholder(); - } - - private void OnInputFieldEditEnd() - { - InputField.gameObject.SetActive(false); - InputButton.gameObject.SetActive(true); - - if (!EventSystem.current.alreadySelecting) // could be, in same click, ending edit and starting dropdown - { - EventSystem.current.SetSelectedGameObject(GameObject); // return focus to regular UI - } - } - } -} diff --git a/ToyBox/classes/MainUI/EnhancedUI/SelectedCharacterObserver.cs b/ToyBox/classes/MainUI/EnhancedUI/SelectedCharacterObserver.cs deleted file mode 100644 index 3b9df893d..000000000 --- a/ToyBox/classes/MainUI/EnhancedUI/SelectedCharacterObserver.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Kingmaker; -using Kingmaker.EntitySystem; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.PubSubSystem; -using UniRx; -using ModKit; - -namespace ToyBox.classes.MainUI.Inventory { - internal class SelectedCharacterObserver : IGlobalSubscriber, - ISubscriber { - public static SelectedCharacterObserver Shared { get; private set; } = new(); - private IDisposable m_SelectedUnitUpdate; - public UnitEntityData SelectedUnit = null; - public delegate void NotifyDelegate(); - public NotifyDelegate Notifiers; - - public SelectedCharacterObserver() { - EventBus.Subscribe((object)this); - m_SelectedUnitUpdate = Game.Instance.SelectionCharacter.SelectedUnit.Subscribe(delegate(UnitReference u) { - SelectedUnit = u.Value; - Mod.Log($"SelectedCharacterObserver - selected character changed to {SelectedUnit?.CharacterName.orange() ?? "null"}"); - Notifiers?.Invoke(); - }); - } - } -} diff --git a/ToyBox/classes/MainUI/Etudes/EtudeChildrenDrawer.cs b/ToyBox/classes/MainUI/Etudes/EtudeChildrenDrawer.cs deleted file mode 100644 index 2e53e0ce2..000000000 --- a/ToyBox/classes/MainUI/Etudes/EtudeChildrenDrawer.cs +++ /dev/null @@ -1,122 +0,0 @@ -using Kingmaker.AreaLogic.Etudes; -using Kingmaker.Blueprints; -using ModKit; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace ToyBox { - - public class EtudeChildrenDrawer { - private Dictionary<BlueprintGuid, EtudeInfo> loadedEtudes = new Dictionary<BlueprintGuid, EtudeInfo>(); - private Dictionary<BlueprintGuid, EtudeDrawerData> etudeDrawerData = new Dictionary<BlueprintGuid, EtudeDrawerData>(); - private BlueprintGuid parentEtude; - //private float chainedShift = 40; - //private float linkedShift = 20; - //private float verticalShift = 20; - //private float lastRectMaxY; - //private Rect workspaceRect; - private int maxDepthToDefaultShow = 0; - private bool FirstLayoutProcess; - public float DefaultExpandedNodeWidth = 600; - - public ReferenceGraph ReferenceGraph; - private ReferenceGraph.Entry selectedEntry; - private List<ReferenceGraph.Ref> startReferences = new List<ReferenceGraph.Ref>(); - private List<ReferenceGraph.Ref> completeReferences = new List<ReferenceGraph.Ref>(); - private List<ReferenceGraph.Ref> checkReferences = new List<ReferenceGraph.Ref>(); - private List<ReferenceGraph.Ref> synchronizedReferences = new List<ReferenceGraph.Ref>(); - private List<ReferenceGraph.Ref> otherReferences = new List<ReferenceGraph.Ref>(); - private List<BlueprintGuid> conflictingGroupReferences = new List<BlueprintGuid>(); - private bool startFoldout = false; - private bool completeFoldout = false; - private bool checkFoldout = false; - private bool synchronizedFoldout = false; - private bool otherFoldout = false; - private bool conflictingGroupFoldout = false; - - private string oldFind = ""; - private Dictionary<BlueprintGuid, EtudeInfo> foundedEtudes = new Dictionary<BlueprintGuid, EtudeInfo>(); - - public static bool newParentFromContestComand = false; - public static BlueprintGuid newParentID; - - private BlueprintGuid SelectedId; - private string SelectedName = ""; - - private EtudeChildrenDrawer() { - } - - public EtudeChildrenDrawer(Dictionary<BlueprintGuid, EtudeInfo> etudes) { - loadedEtudes = etudes; - } - - public void SetParent(BlueprintGuid parent) { - parentEtude = parent; - etudeDrawerData = new Dictionary<BlueprintGuid, EtudeDrawerData>(); - FirstLayoutProcess = true; - } - - public static void TryToSetParent(BlueprintGuid parent) { - newParentFromContestComand = true; - newParentID = parent; - } - - - public void Update() { - var oldSelectedEtude = (BlueprintEtude)ResourcesLibrary.TryGetBlueprint(SelectedId); - if (oldSelectedEtude == null) { - EtudesTreeModel.Instance.RemoveEtudeData(SelectedId); - return; - } - - if (oldSelectedEtude.name != SelectedName) - EtudesTreeModel.Instance.UpdateEtude(oldSelectedEtude); - } - public void OnGUI() { - if (parentEtude == BlueprintGuid.Empty) - return; - - //HandleEvents(); - - UI.Label($"Child Etudes: {loadedEtudes[parentEtude].Name}", UI.AutoWidth()); - - //GUI.DrawTextureWithTexCoords(workspaceRect, etudeViewer.grid, - // new Rect(_zoomCoordsOrigin.x / 30, -_zoomCoordsOrigin.y / 30, workspaceRect.width / (30 * _zoom), - // workspaceRect.height / (30 * _zoom))); - -#if false - PrepareLayout(); - DrawSelection(); - DrawLines(); - DrawEtudes(); - DrawReferences(); - DrawFind(); - GUILayout.EndArea(); - EditorZoomArea.End(); - - if (newParentFromContestComand) { - if (loadedEtudes.ContainsKey(newParentID)) { - BlueprintEtude clickedEtude = (BlueprintEtude)ResourcesLibrary.TryGetBlueprint(newParentID); - Selection.activeObject = BlueprintEditorWrapper.Wrap(clickedEtude); - - if (clickedEtude.Parent.IsEmpty()) { - parentEtude = clickedEtude.AssetGuid; - } - else { - parentEtude = clickedEtude.Parent.GetBlueprint().AssetGuid; - } - - etudeDrawerData = new Dictionary<BlueprintGuid, EtudeDrawerData>(); - _zoomCoordsOrigin = Vector2.zero; - FirstLayoutProcess = true; - } - - newParentFromContestComand = false; - } -#endif - } - } -} diff --git a/ToyBox/classes/MainUI/LevelUp.cs b/ToyBox/classes/MainUI/LevelUp.cs deleted file mode 100644 index b629c241b..000000000 --- a/ToyBox/classes/MainUI/LevelUp.cs +++ /dev/null @@ -1,186 +0,0 @@ -using Kingmaker; -using Kingmaker.EntitySystem.Entities; -using ModKit; -using System; -using System.Linq; -using static ModKit.UI; - -namespace ToyBox { - public class LevelUp { - public static Settings settings => Main.Settings; - public static void ResetGUI() { } - public static void OnGUI() { - HStack("Character Creation".localize(), 1, - () => { - using (VerticalScope()) { - using (HorizontalScope()) { - Slider("Build Points (Main)".localize(), ref settings.characterCreationAbilityPointsPlayer, 1, 600, 25, "", 300.width()); - 25.space(); - Toggle("Ignore Game Minimum".localize(), ref settings.characterCreationAbilityPointsOverrideGameMinimums); - 25.space(); - HelpLabel("Tick this if you want these sliders to let you go below game specified minimum point value".localize()); - Space(); - } - using (HorizontalScope()) { - Slider("Build Points (Mercenary)".localize(), ref settings.characterCreationAbilityPointsMerc, 1, 600, 25, "", AutoWidth()); - } - } - }, - () => Slider("Ability Max".localize(), ref settings.characterCreationAbilityPointsMax, 0, 50, 18, "", AutoWidth()), - () => Slider("Ability Min".localize(), ref settings.characterCreationAbilityPointsMin, 0, 50, 7, "", AutoWidth()), - //() => { - // UI.Toggle("All Appearance Options", ref settings.toggleAllRaceCustomizations); - // UI.Space(25); - // UI.Label("Allows you to choose all appearance options from any race".green()); - //}, - () => { } - ); - Div(0, 25); - HStack("Mythic Paths".localize(), 1, - () => Label("Warning! Using these might break your game somehow. Recommend for experimental tinkering like trying out different builds, and not for actually playing the game.".localize().green()), - () => ActionButton("Unlock Aeon".localize(), Actions.UnlockAeon, Width(300)), - () => ActionButton("Unlock Azata".localize(), Actions.UnlockAzata, Width(300)), - () => ActionButton("Unlock Trickster".localize(), Actions.UnlockTrickster, Width(300)), - () => ActionButton("Unlock Lich".localize(), Actions.UnlockLich, Width(300)), - () => { ActionButton("Unlock Swarm".localize(), Actions.UnlockSwarm, Width(300)); Space(25); Label("Only available at Mythic level 8 or higher".localize().green()); }, - () => { ActionButton("Unlock Gold Dragon".localize(), Actions.UnlockGoldDragon, Width(300)); Space(25); Label("Only available at Mythic level 8 or higher".localize().green()); }, - () => { - ActionButton("All Mythic Paths".localize().orange(), Actions.UnlockAllBasicMythicPaths, Width(300)); - Space(25); - Label("Unlock mythic paths besides Legend and Devil which block progression".localize().green()); - }, - () => Label("", Height(10)), - () => { ActionButton("Unlock Devil".localize(), Actions.UnlockDevil, Width(300)); Space(25); Label("Prevents you from advancing in Aeon or Azata".localize().green()); }, - () => { ActionButton("Unlock Legend".localize(), Actions.UnlockLegend, Width(300)); Space(25); Label("Prevents you from advancing all other Mythic Path".localize().green()); }, - () => { } - ); - Div(0, 25); - HStack("Create & Level Up".localize(), 1, - () => { - Slider("Feature Selection Multiplier".localize(), ref settings.featsMultiplier, 0, 10, 1, "", Width(600)); - Space(25); - Label("This allows you to select a given feature more than once at level up".localize().green()); - }, - () => Toggle("Apply Feature Selection Multiplier to party members".localize(), ref settings.toggleFeatureMultiplierCompanions), - () => { - Toggle("Allow Multiple Archetypes When Selecting A New Class".localize(), ref settings.toggleMultiArchetype); - 25.space(); - Label("This allows you to select combinations of archetypes when selecting a class for the first time that contain distinct spellbooks".localize().green()); - }, - () => Toggle("Make All Feature Selections Optional".localize(), ref settings.toggleOptionalFeatSelection), - () => { - Toggle("Ignore Attribute Cap".localize(), ref settings.toggleIgnoreAttributeCap); - Space(25); - Toggle("Ignore Remaining Attribute Points".localize(), ref settings.toggleIgnoreAttributePointsRemaining); - }, - () => { - Toggle("Ignore Skill Cap".localize(), ref settings.toggleIgnoreSkillCap); - Space(73); - Toggle("Ignore Remaining Skill Points".localize(), ref settings.toggleIgnoreSkillPointsRemaining); - }, - () => Toggle("Always Able To Level Up".localize(), ref settings.toggleNoLevelUpRestrictions), - () => Toggle("Add Full Hit Die Value".localize(), ref settings.toggleFullHitdiceEachLevel), - () => { - Toggle("Ignore Class Restrictions".localize(), ref settings.toggleIgnoreClassRestrictions); - Space(25); - Label(("Experimental".cyan() + ": in addition to regular leveling, this allows you to choose any mythic class each time you level up starting from mythic rank 1. This may have interesting and unexpected effects. Backup early and often...".green()).localize()); - }, - () => { - Toggle("Ignore Feat Restrictions".localize(), ref settings.toggleIgnoreFeatRestrictions); - Space(25); - Label(("Experimental".cyan() + ": lets you select any feat ignoring prerequisites.".green()).localize()); - }, - () => Toggle("Allow Companions to Take Mythic Classes".localize(), ref settings.toggleAllowCompanionsToBecomeMythic), - () => Toggle("Allow Pets to Take Mythic Classes".localize(), ref settings.toggleAllowMythicPets), - () => Toggle("Ignore Prerequisites When Choosing A Feat".localize(), ref settings.toggleFeaturesIgnorePrerequisites), - () => Toggle("Ignore Caster Type And Spell Level Restrictions".localize(), ref settings.toggleIgnoreCasterTypeSpellLevel), - () => Toggle("Ignore Forbidden Archetypes".localize(), ref settings.toggleIgnoreForbiddenArchetype), - () => Toggle("Ignore Required Stat Values".localize(), ref settings.toggleIgnorePrerequisiteStatValue), - () => Toggle("Ignore Required Class Levels".localize(), ref settings.toggleIgnorePrerequisiteClassLevel), - () => Toggle("Ignore Alignment When Choosing A Class".localize(), ref settings.toggleIgnoreAlignmentWhenChoosingClass), - () => Toggle("Ignore Prerequisite Features (like Race) when choosing Class".localize(), ref settings.toggleIgnoreFeaturePrerequisitesWhenChoosingClass), -#if false // This is incredibly optimistic and requires resolving a bunch of conflicts with the existing gestalt and scroll copy logic - () => UI.Toggle("Ignore Spellbook Restrictions When Choosing Spells", ref settings.toggleUniversalSpellbookd), -#endif - - () => Toggle("Skip Spell Selection".localize(), ref settings.toggleSkipSpellSelection), -#if DEBUG - () => Toggle("Lock Character Level".localize(), ref settings.toggleLockCharacterLevel), - // () => UI.Toggle("Ignore Alignment Restrictions", ref settings.toggleIgnoreAlignmentRestriction), -#endif -#if false - // Do we need these or is it covered by toggleFeaturesIgnorePrerequisites - () => { UI.Toggle("Ignore Feat Prerequisites When Choosing A Class", ref settings.toggleIgnoreFeaturePrerequisitesWhenChoosingClass); }, - () => { UI.Toggle("Ignore Feat Prerequisits (List) When Choosing A Class", ref settings.toggle); }, -#endif - () => Toggle("Remove Level 20 Caster Level Cap".localize(), ref settings.toggleUncappedCasterLevel), - () => Toggle("Party Level Cap 40 (continuous growth after 20)".localize(), ref settings.toggleContinousLevelCap), - () => Toggle("Party Level Cap 24 (exponential growth)".localize(), ref settings.toggleExponentialLevelCap), - - () => { } - ); -#if true - Div(0, 25); - HStack("Multiple Classes".localize(), 1, - //() => UI.Label("Experimental Preview".magenta(), UI.AutoWidth()), - () => { - Toggle("Multiple Classes On Level-Up".localize(), ref settings.toggleMulticlass); - Space(25); - using (VerticalScope()) { - Label("Experimental - With this enabled you can configure characters in the Party Editor to gain levels in additional classes whenever they level up. See the link for more information on this campaign variant.".localize().green()); - LinkButton("Gestalt Characters".localize(), "https://www.d20srd.org/srd/variant/classes/gestaltCharacters.htm"); - Space(15); - } - }, - () => { - EnumGrid("Hit Point (Hit Die) Growth".localize(), ref settings.multiclassHitPointPolicy, 0, AutoWidth()); - }, - () => { - EnumGrid("Basic Attack Growth Pr".localize(), ref settings.multiclassBABPolicy, 0, AutoWidth()); - }, - () => { - EnumGrid("Saving Throw Growth".localize(), ref settings.multiclassSavingThrowPolicy, 0, AutoWidth()); - }, - () => { - EnumGrid("Skill Point Growth".localize(), ref settings.multiclassSkillPointPolicy, 0, AutoWidth()); - }, -#if false - () => UI.Toggle("Use Recalculate Caster Levels", ref settings.toggleRecalculateCasterLevelOnLevelingUp), - () => UI.Toggle("Restrict Caster Level To Current", ref settings.toggleRestrictCasterLevelToCharacterLevel), - //() => { UI.Toggle("Restrict CL to Current (temp) ", ref settings.toggleRestrictCasterLevelToCharacterLevelTemporary), - () => UI.Toggle("Restrict Class Level for Prerequisites to Caster Level", ref settings.toggleRestrictClassLevelForPrerequisitesToCharacterLevel), - () => UI.Toggle("Fix Favored Class HP", ref settings.toggleFixFavoredClassHP), - () => UI.Toggle("Always Receive Favored Class HP", ref settings.toggleAlwaysReceiveFavoredClassHP), - () => UI.Toggle("Always Receive Favored Class HP Except Prestige", ref settings.toggleAlwaysReceiveFavoredClassHPExceptPrestige), -#endif - () => { } - ); - - if (settings.toggleMulticlass) { - UnitEntityData selectedChar = null; - Div(0, 25); - HStack("Class Selection".localize(), 1, - () => { - if (Main.IsInGame) { - var characters = Game.Instance.Player.m_PartyAndPets; - if (characters == null) { return; } - settings.selectedClassToConfigMulticlass = Math.Min(characters.Count, settings.selectedClassToConfigMulticlass); - ActionSelectionGrid(ref settings.selectedClassToConfigMulticlass, - characters.Select((ch) => ch.CharacterName).Prepend("Char Gen".localize()).ToArray(), - 6, - (index) => { }, - AutoWidth() - ); - if (settings.selectedClassToConfigMulticlass <= 0) selectedChar = null; - else selectedChar = characters[settings.selectedClassToConfigMulticlass - 1]; - } - }, - () => { } - ); - - MulticlassPicker.OnGUI(selectedChar, 150); - } -#endif - } - } -} diff --git a/ToyBox/classes/MainUI/Main.cs b/ToyBox/classes/MainUI/Main.cs deleted file mode 100644 index 8e1d46281..000000000 --- a/ToyBox/classes/MainUI/Main.cs +++ /dev/null @@ -1,275 +0,0 @@ -// Copyright < 2021 > Narria (github user Cabarius) - License: MIT -// Special thanks to @SpaceHampster and @Velk17 from Pathfinder: Wrath of the Rightous Discord server for teaching me how to mod Unity games -using HarmonyLib; -using Kingmaker; -using Kingmaker.GameModes; -using Kingmaker.UI.Common; -using Kingmaker.UI.Models.Log.CombatLog_ThreadSystem; -using Kingmaker.UI.Models.Log.CombatLog_ThreadSystem.LogThreads.Common; -using Kingmaker.Utility; -using ModKit; -using ModKit.DataViewer; -using Owlcat.Runtime.Core.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using ToyBox.classes.Infrastructure; -using ToyBox.classes.MainUI; -using ToyBox.Multiclass; -using UnityEngine; -using UnityModManagerNet; -using static ModKit.UI; - -namespace ToyBox { -#if DEBUG - [EnableReloading] -#endif - internal static class Main { - internal static Harmony HarmonyInstance; - public static readonly LogChannel logger = LogChannelFactory.GetOrCreate("Respec"); - private static string _modId; - public static Settings Settings; - public static MulticlassMod multiclassMod; - public static bool Enabled; - public static bool IsModGUIShown = false; - public static bool freshlyLaunched = true; - public static bool NeedsActionInit = true; - private static bool _needsResetGameUI = false; - private static bool _resetRequested = false; - private static DateTime _resetRequestTime = DateTime.Now; - public static bool resetExtraCameraAngles = false; - public static void SetNeedsResetGameUI() { - _resetRequested = true; - _resetRequestTime = DateTime.Now; - Mod.Debug($"resetRequested - {_resetRequestTime}"); - } - public static bool IsInGame => Game.Instance.Player?.Party.Any() ?? false; - - private static Exception _caughtException = null; - - public static List<GameObject> Objects; - - private static bool Load(UnityModManager.ModEntry modEntry) { - try { -#if DEBUG - modEntry.OnUnload = Unload; -#endif - _modId = modEntry.Info.Id; - - Mod.OnLoad(modEntry); - UIHelpers.OnLoad(); - Settings = UnityModManager.ModSettings.Load<Settings>(modEntry); - SettingsDefaults.InitializeDefaultDamageTypes(); - - - HarmonyInstance = new Harmony(modEntry.Info.Id); - HarmonyInstance.PatchAll(Assembly.GetExecutingAssembly()); - - LocalizationManager.Enable(); - - modEntry.OnToggle = OnToggle; - modEntry.OnShowGUI = OnShowGUI; - modEntry.OnHideGUI = OnHideGUI; - modEntry.OnGUI = OnGUI; - modEntry.OnUpdate = OnUpdate; - modEntry.OnSaveGUI = OnSaveGUI; - Objects = new List<GameObject>(); - KeyBindings.OnLoad(modEntry); - multiclassMod = new Multiclass.MulticlassMod(); - HumanFriendlyStats.EnsureFriendlyTypesContainAll(); - Mod.logLevel = Settings.loggingLevel; - Mod.InGameTranscriptLogger = text => { - Mod.Log("CombatLog - " + text); - var message = new CombatLogMessage("ToyBox".blue() + " - " + text, Color.black, PrefixIcon.RightArrow); - - var messageLog = LogThreadService.Instance.m_Logs[LogChannelType.Common].First(x => x is MessageLogThread); - var tacticalCombatLog = LogThreadService.Instance.m_Logs[LogChannelType.TacticalCombat].First(x => x is MessageLogThread); - messageLog.AddMessage(message); - tacticalCombatLog?.AddMessage(message); - }; - } - catch (Exception e) { - Mod.Error(e); - throw e; - } - return true; - } -#if DEBUG - private static bool Unload(UnityModManager.ModEntry modEntry) { - foreach (var obj in Objects) { - UnityEngine.Object.DestroyImmediate(obj); - } - BlueprintExtensions.ResetCollationCache(); - HarmonyInstance.UnpatchAll(_modId); - EnhancedInventory.OnUnLoad(); - NeedsActionInit = true; - return true; - } -#endif - private static bool OnToggle(UnityModManager.ModEntry modEntry, bool value) { - Enabled = value; - return true; - } - - private static void ResetGUI(UnityModManager.ModEntry modEntry) { - Settings = UnityModManager.ModSettings.Load<Settings>(modEntry); - Settings.searchText = ""; - Settings.searchLimit = 100; - Mod.ModKitSettings.browserSearchLimit = 25; - ModKitSettings.Save(); - BagOfTricks.ResetGUI(); - LevelUp.ResetGUI(); - PartyEditor.ResetGUI(); - CrusadeEditor.ResetGUI(); - CharacterPicker.ResetGUI(); - SearchAndPick.ResetGUI(); - QuestEditor.ResetGUI(); - BlueprintExtensions.ResetCollationCache(); - _caughtException = null; - } - - private static void OnGUI(UnityModManager.ModEntry modEntry) { - if (!Enabled) return; - IsModGUIShown = true; - if (!IsInGame) { - Label("ToyBox has limited functionality from the main menu".yellow().bold()); - } - if (!IsWide) { - Label("Note ".magenta().bold() + "ToyBox was designed to offer the best user experience at widths of 1920 or higher. Please consider increasing your resolution up of at least 1920x1080 (ideally 4k) and go to Unity Mod Manager 'Settings' tab to change the mod window width to at least 1920. Increasing the UI scale is nice too when running at 4k".orange().bold()); - } - try { - var e = Event.current; - userHasHitReturn = e.keyCode == KeyCode.Return; - focusedControlName = GUI.GetNameOfFocusedControl(); - if (_caughtException != null) { - Label("ERROR".red().bold() + $": caught exception {_caughtException}"); - ActionButton("Reset".orange().bold(), () => { ResetGUI(modEntry); }, AutoWidth()); - return; - } -#if false - using (UI.HorizontalScope()) { - UI.Label("Suggestions or issues click ".green(), UI.AutoWidth()); - UI.LinkButton("here", "https://github.com/cabarius/ToyBox/issues"); - UI.Space(50); - UI.Label("Chat with the Authors, Narria et all on the ".green(), UI.AutoWidth()); - UI.LinkButton("WoTR Discord", "https://discord.gg/wotr"); - } -#endif - TabBar(ref Settings.selectedTab, - () => { - if (BlueprintLoader.Shared.IsLoading) { - Label("Blueprints".orange().bold() + " loading: " + BlueprintLoader.Shared.progress.ToString("P2").cyan().bold()); - } - else Space(25); - }, - new NamedAction("Bag of Tricks", BagOfTricks.OnGUI), - new NamedAction("Level Up", LevelUp.OnGUI), - new NamedAction("Party", PartyEditor.OnGUI), - new NamedAction("Loot & Spellbook", PhatLoot.OnGUI), - new NamedAction("Enchantment", EnchantmentEditor.OnGUI), -#if false - new NamedAction("Playground", () => Playground.OnGUI()), -#endif - new NamedAction("Search 'n Pick", SearchAndPick.OnGUI), - new NamedAction("Crusade", CrusadeEditor.OnGUI), - new NamedAction("Armies", ArmiesEditor.OnGUI), - new NamedAction("Events/Decrees", EventEditor.OnGUI), -#if DEBUG - new NamedAction("Gambits (AI)", BraaainzEditor.OnGUI), -#endif - new NamedAction("Etudes", EtudesEditor.OnGUI), - new NamedAction("Quests", QuestEditor.OnGUI), - new NamedAction("Settings", SettingsUI.OnGUI) - ); - } - catch (Exception e) { - Console.Write($"{e}"); - _caughtException = e; - ReflectionSearch.Shared.Stop(); - } - } - - private static void OnSaveGUI(UnityModManager.ModEntry modEntry) => Settings.Save(modEntry); - private static void OnShowGUI(UnityModManager.ModEntry modEntry) { - IsModGUIShown = true; - EnchantmentEditor.OnShowGUI(); - ArmiesEditor.OnShowGUI(); - EtudesEditor.OnShowGUI(); - Mod.OnShowGUI(); - } - - private static void OnHideGUI(UnityModManager.ModEntry modEntry) => IsModGUIShown = false; - - private static void OnUpdate(UnityModManager.ModEntry modEntry, float z) { - if (Game.Instance?.Player != null) { - var corruption = Game.Instance.Player.Corruption; - var corruptionDisabled = (bool)corruption.Disabled; - if (corruptionDisabled != Settings.toggleDisableCorruption) { - if (Settings.toggleDisableCorruption) - corruption.Disabled.Retain(); - else - corruption.Disabled.ReleaseAll(); - } - } - Mod.logLevel = Settings.loggingLevel; - if (NeedsActionInit) { - BagOfTricks.OnLoad(); - PhatLoot.OnLoad(); - ArmiesEditor.OnLoad(); - EnhancedInventory.OnLoad(); - NeedsActionInit = false; - } - //if (resetExtraCameraAngles) { - // Game.Instance.UI.GetCameraRig().TickRotate(); // Kludge - TODO: do something better... - //} - if (_resetRequested) { - var timeSinceRequest = DateTime.Now.Subtract(_resetRequestTime).TotalMilliseconds; - //Main.Log($"timeSinceRequest - {timeSinceRequest}"); - if (timeSinceRequest > 1000) { - Mod.Debug($"resetExecuted - {timeSinceRequest}".cyan()); - _needsResetGameUI = true; - _resetRequested = false; - } - } - if (_needsResetGameUI) { - Game.Instance.ScheduleAction(() => { - _needsResetGameUI = false; - Game.ResetUI(); - - // TODO - Find out why the intiative tracker comes up when I do Game.ResetUI. The following kludge makes it go away - - var canvas = Game.Instance?.UI?.Canvas?.transform; - //Main.Log($"canvas: {canvas}"); - var hudLayout = canvas?.transform.Find("HUDLayout"); - //Main.Log($"hudLayout: {hudLayout}"); - var initiaveTracker = hudLayout.transform.Find("Console_InitiativeTrackerHorizontalPC"); - //Main.Log($" initiaveTracker: {initiaveTracker}"); - initiaveTracker?.gameObject?.SetActive(false); - - }); - } - var currentMode = Game.Instance.CurrentMode; - if (IsModGUIShown || Event.current == null || !Event.current.isKey) return; - KeyBindings.OnUpdate(); - if (IsInGame - && Settings.toggleTeleportKeysEnabled - && (currentMode == GameModeType.Default - || currentMode == GameModeType.Pause - || currentMode == GameModeType.GlobalMap - ) - ) { - if (UIUtility.IsGlobalMap()) { - if (KeyBindings.IsActive("TeleportParty")) - Teleport.TeleportPartyOnGlobalMap(); - } - if (KeyBindings.IsActive("TeleportMain")) - Teleport.TeleportUnit(Game.Instance.Player.MainCharacter.Value, Utils.PointerPosition()); - if (KeyBindings.IsActive("TeleportSelected")) - Teleport.TeleportSelected(); - if (KeyBindings.IsActive("TeleportParty")) - Teleport.TeleportParty(); - } - } - } -} \ No newline at end of file diff --git a/ToyBox/classes/MainUI/MulticlassPicker.cs b/ToyBox/classes/MainUI/MulticlassPicker.cs deleted file mode 100644 index 8933caa7e..000000000 --- a/ToyBox/classes/MainUI/MulticlassPicker.cs +++ /dev/null @@ -1,267 +0,0 @@ -// Copyright < 2021 > Narria (github user Cabarius) - License: MIT -using Kingmaker; -using Kingmaker.Blueprints.Classes; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.Utility; -using ModKit; -using System.Linq; -using ToyBox.Multiclass; - -namespace ToyBox { - public class MulticlassPicker { - public static Settings settings => Main.Settings; - - public static void OnGUI(UnitEntityData ch, float indent = 100) { - var targetString = ch == null - ? ("creation of ".green() + "new characters" + "\nNote:".yellow().bold() - + " This value applies to ".orange() + "all saves".yellow().bold() + " and in the main menu".orange()).localize() - : "when leveling up ".localize().green() + ch.CharacterName.orange().bold() + ("\nNote:".yellow().bold() - + " This applies only to the ".orange() + "current save.".yellow().bold()).localize(); - using (UI.HorizontalScope()) { - UI.Space(indent); - UI.Label("Configure multiclass classes and gestalt flags to use during ".localize() + $"{targetString}".green()); - UI.Space(25); - UI.Toggle("Show Class Descriptions".localize(), ref settings.toggleMulticlassShowClassDescriptions); - } - UI.Space(15); - MigrationOptions(indent); - if (Game.Instance?.BlueprintRoot?.Progression == null) return; - var options = MulticlassOptions.Get(ch); - var classes = Game.Instance.BlueprintRoot.Progression.CharacterClasses; - var mythicClasses = Game.Instance.BlueprintRoot.Progression.CharacterMythics; - var showDesc = settings.toggleMulticlassShowClassDescriptions; - if (ch != null) { - using (UI.HorizontalScope()) { - UI.Space(indent); - UI.Label($"Character Level".localize().cyan().bold(), UI.Width(300)); - UI.Space(25); - UI.Label(ch.Progression.CharacterLevel.ToString().orange().bold()); - } - UI.Space(25); - } - foreach (var cl in classes) { - if (PickerRow(ch, cl, options, indent)) { - MulticlassOptions.Set(ch, options); - Mod.Trace("MulticlassOptions.Set"); - } - } - UI.Space(10); - UI.Div(indent); - UI.Space(-3); - if (showDesc) { - using (UI.HorizontalScope()) { - UI.Space(indent); UI.Label("Mythic".localize().cyan()); - } - } - foreach (var mycl in mythicClasses) { - if (PickerRow(ch, mycl, options, indent)) { - MulticlassOptions.Set(ch, options); - Mod.Trace("MulticlassOptions.Set"); - } - } - } - - public static bool PickerRow(UnitEntityData ch, BlueprintCharacterClass cl, MulticlassOptions options, float indent = 100) { - var changed = false; - var showDesc = settings.toggleMulticlassShowClassDescriptions; - if (showDesc) UI.Div(indent, 15); - var cd = ch?.Progression.GetClassData(cl); - var chArchetype = cd?.Archetypes.FirstOrDefault<BlueprintArchetype>(); - var archetypeOptions = options.ArchetypeOptions(cl); - var showGestaltToggle = false; - if (ch != null && cd != null) { - var classes = ch?.Progression.Classes; - var classCount = classes?.Count(x => !x.CharacterClass.IsMythic); - var gestaltCount = classes?.Count(cd => !cd.CharacterClass.IsMythic && ch.IsClassGestalt(cd.CharacterClass)); - var mythicCount = classes.Count(x => x.CharacterClass.IsMythic); - var mythicGestaltCount = classes.Count(cd => cd.CharacterClass.IsMythic && ch.IsClassGestalt(cd.CharacterClass)); - - showGestaltToggle = ch.IsClassGestalt(cd.CharacterClass) - || !cd.CharacterClass.IsMythic && classCount - gestaltCount > 1 - || cd.CharacterClass.IsMythic && mythicCount - mythicGestaltCount > 1; - } - var charHasClass = cd != null && chArchetype == null; - // Class Toggle - var canSelectClass = MulticlassOptions.CanSelectClassAsMulticlass(ch, cl); - using (UI.HorizontalScope()) { - UI.Space(indent); - var optionsHasClass = options.Contains(cl); - UI.ActionToggle( - charHasClass ? cl.Name.orange() + $" ({cd.Level})".orange() : cl.Name, - () => optionsHasClass, - (v) => { - if (v) { - archetypeOptions = options.Add(cl); - if (chArchetype != null) { - archetypeOptions.Add(chArchetype); - options.SetArchetypeOptions(cl, archetypeOptions); - } - } - else options.Remove(cl); - var action = v ? "Add".localize().green() : "Del".localize().yellow(); - Mod.Trace($"PickerRow - {action} class: {cl.HashKey()} - {options} -> {options.Contains(cl)}"); - changed = true; - }, - () => !canSelectClass, - 350); - UI.Space(247); - using (UI.VerticalScope()) { - if (!canSelectClass) - UI.Label("to select this class you must unselect at least one of your other existing classes".localize().orange()); - if (optionsHasClass && chArchetype != null && archetypeOptions.Empty()) { - UI.Label("due to existing archetype, ".localize() + $"{chArchetype.Name.yellow()}" + ", this multiclass option will only be applied during respec.".localize().orange()); - } - if (showGestaltToggle && chArchetype == null) { - using (UI.HorizontalScope()) { - UI.Space(-150); - UI.ActionToggle("gestalt".localize().grey(), () => ch.IsClassGestalt(cd.CharacterClass), - (v) => { - ch.SetClassIsGestalt(cd.CharacterClass, v); - ch.Progression.UpdateLevelsForGestalt(); - changed = true; - }, 125); - UI.Space(25); - UI.Label("this flag lets you not count this class in computing character level".localize().green()); - } - } - if (showDesc) { - using (UI.HorizontalScope()) { - UI.Label(cl.Description.StripHTML().green()); - } - } - } - } - // Archetypes - using (UI.HorizontalScope()) { - var showedGestalt = false; - UI.Space(indent); - var archetypes = cl.Archetypes; - if (options.Contains(cl) && archetypes.Any() || chArchetype != null || charHasClass) { - UI.Space(50); - using (UI.VerticalScope()) { - foreach (var archetype in cl.Archetypes) { - if (showDesc) UI.Div(); - using (UI.HorizontalScope()) { - var hasArch = archetypeOptions.Contains(archetype); - UI.ActionToggle( - archetype == chArchetype ? cd.ArchetypesName().orange() + $" ({cd.Level})".orange() : archetype.Name, - () => hasArch, - (v) => { - if (v) archetypeOptions.AddExclusive(archetype); - else archetypeOptions.Remove(archetype); - options.SetArchetypeOptions(cl, archetypeOptions); - var action = v ? "Add".localize().green() : "Del".localize().yellow(); - Mod.Trace($"PickerRow - {action} - arch: {archetype.HashKey()} - {archetypeOptions}"); - changed = true; - }, - () => !canSelectClass, - 300); - UI.Space(250); - using (UI.VerticalScope()) { - - if (hasArch && archetype != chArchetype && (chArchetype != null || charHasClass)) { - if (chArchetype != null) - UI.Label($"due to existing archetype, ".localize() + $"{chArchetype.Name.yellow()}" + ", this multiclass archetype will only be applied during respec.".localize().orange()); - else - UI.Label($"due to existing class, ".localize() + $"{cd.CharacterClass.Name.yellow()}" + ", this multiclass archetype will only be applied during respec.".localize().orange()); - } - else if (showGestaltToggle && archetype == chArchetype) { - using (UI.HorizontalScope()) { - UI.Space(-155); - UI.ActionToggle("gestalt".localize().grey(), () => ch.IsClassGestalt(cd.CharacterClass), - (v) => { - ch.SetClassIsGestalt(cd.CharacterClass, v); - ch.Progression.UpdateLevelsForGestalt(); - changed = true; - }, 125); - UI.Space(25); - UI.Label("this flag lets you not count this class in computing character level".localize().green()); - showedGestalt = true; - } - } - if (showDesc) { - using (UI.VerticalScope()) { - if (showedGestalt) { - UI.Label("this flag lets you not count this class in computing character level".localize().green()); - UI.DivLast(); - } - UI.Label(archetype.Description.StripHTML().green()); - } - } - } - } - } - } - } - } - return changed; - } - public static bool areYouSure1 = false; - public static bool areYouSure2 = false; - public static bool areYouSure3 = false; - public static void MigrationOptions(float indent) { - if (!Main.IsInGame) return; - var hasMulticlassMigration = settings.multiclassSettings.Count > 0 - && (settings.toggleAlwaysShowMigration || settings.perSave.multiclassSettings.Count == 0); - var hasGestaltMigration = settings.excludeClassesFromCharLevelSets.Count > 0 - && (settings.toggleAlwaysShowMigration || settings.perSave.excludeClassesFromCharLevelSets.Count == 0); - var hasLevelAsLegendMigration = settings.perSave.charIsLegendaryHero.Count > 0 - && (settings.toggleAlwaysShowMigration || settings.perSave.charIsLegendaryHero.Count == 0); - var hasAvailableMigrations = hasMulticlassMigration || hasGestaltMigration || hasLevelAsLegendMigration; - var migrationCount = settings.multiclassSettings.Count + settings.excludeClassesFromCharLevelSets.Count + settings.charIsLegendaryHero.Count; - if (migrationCount > 0) { - using (UI.HorizontalScope()) { - UI.Space(indent); - UI.Toggle("Show Migrations".localize(), ref settings.toggleAlwaysShowMigration); - UI.Space(25); - UI.Label(("toggle this if you want show older ToyBox settings for ".green() + "Multi-class selections, Gestalt Flags and Allow Levels Past 20 ".cyan()).localize()); - } - } - if (migrationCount > 0) { - UI.Div(indent); - if (hasAvailableMigrations) { - using (UI.HorizontalScope()) { - UI.Space(indent); - using (UI.VerticalScope()) { - UI.Label(("the following options allow you to migrate previous settings that were stored in toybox to the new per setting save mechanism for ".green() + "Multi-class selections, Gestalt Flags and Allow Levels Past 20 ".cyan() + "\nNote:".orange() + "you may have configured this for a different save so use care in doing this migration".green()).localize()); - if (hasMulticlassMigration) - using (UI.HorizontalScope()) { - UI.Label("Multi-class settings".localize(), UI.Width(300)); - UI.Space(25); - UI.Label($"{settings.multiclassSettings.Count}".cyan()); - UI.Space(25); - UI.ActionButton("Migrate".localize(), () => { settings.perSave.multiclassSettings = settings.multiclassSettings; Settings.SavePerSaveSettings(); }); - UI.Space(25); - UI.DangerousActionButton("Remove".localize(), "this will remove your old multiclass settings from ToyBox settings but does not affect any other saves that have already migrated them".localize(), ref areYouSure1, () => settings.multiclassSettings.Clear()); - } - if (hasGestaltMigration) - using (UI.HorizontalScope()) { - UI.Label("Gestalt Flags".localize(), UI.Width(300)); - UI.Space(25); - UI.Label($"{settings.excludeClassesFromCharLevelSets.Count}".cyan()); - UI.Space(25); - UI.ActionButton("Migrate".localize(), () => { - settings.perSave.excludeClassesFromCharLevelSets = settings.excludeClassesFromCharLevelSets; Settings.SavePerSaveSettings(); - MultipleClasses.SyncAllGestaltState(); - }); - UI.Space(25); - UI.DangerousActionButton("Remove".localize(), "this will remove your old gestalt flags from ToyBox settings but does not affect any other saves that have already migrated them".localize(), ref areYouSure2, () => settings.excludeClassesFromCharLevelSets.Clear()); - } - if (hasLevelAsLegendMigration) - using (UI.HorizontalScope()) { - UI.Label("Chars Able To Exceed Level 20".localize(), UI.Width(300)); - UI.Space(25); - UI.Label($"{settings.charIsLegendaryHero.Count}".cyan()); - UI.Space(25); - UI.ActionButton("Migrate".localize(), () => { settings.perSave.charIsLegendaryHero = settings.charIsLegendaryHero; Settings.SavePerSaveSettings(); }); - UI.Space(25); - UI.DangerousActionButton("Remove".localize(), "this will remove your old Allow Level Past 20 flags from ToyBox settings but does not affect any other saves that have already migrated them".localize(), ref areYouSure3, () => settings.charIsLegendaryHero.Clear()); - } - } - } - UI.Div(indent); - } - } - } - } -} \ No newline at end of file diff --git a/ToyBox/classes/MainUI/PartyEditor/ClassesEditor.cs b/ToyBox/classes/MainUI/PartyEditor/ClassesEditor.cs deleted file mode 100644 index 26591cdd3..000000000 --- a/ToyBox/classes/MainUI/PartyEditor/ClassesEditor.cs +++ /dev/null @@ -1,203 +0,0 @@ -using UnityEngine; -using System; -using System.Collections.Generic; -using System.Linq; -using Kingmaker; -using Kingmaker.Blueprints.Classes; -using Kingmaker.Blueprints.Classes.Spells; -using Kingmaker.Designers; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.EntitySystem.Stats; -using Kingmaker.UnitLogic; -using ToyBox.Multiclass; -using Alignment = Kingmaker.Enums.Alignment; -using ModKit; -using static ModKit.UI; -using ModKit.Utility; -using ToyBox.classes.Infrastructure; -using Kingmaker.PubSubSystem; -using Kingmaker.Blueprints; -using Kingmaker.UnitLogic.FactLogic; -using Kingmaker.UnitLogic.Parts; -using static Kingmaker.Utility.UnitDescription.UnitDescription; - - -namespace ToyBox { - public partial class PartyEditor { - public static void OnClassesGUI(UnitEntityData ch, List<Kingmaker.UnitLogic.ClassData> classData, UnitEntityData selectedCharacter) { - Div(100, 20); - using (HorizontalScope()) { - Space(100); - Toggle("Multiple Classes On Level-Up", ref Settings.toggleMulticlass); - if (Settings.toggleMulticlass) { - Space(40); - if (DisclosureToggle("Config".orange().bold(), ref editMultiClass)) { - multiclassEditCharacter = selectedCharacter; - } - Space(53); - Label("Experimental - See 'Level Up + Multiclass' for more options and info".green()); - } - } - using (HorizontalScope()) { - Space(100); - ActionToggle("Allow Levels Past 20", - () => { - var hasValue = Settings.perSave.charIsLegendaryHero.TryGetValue(ch.HashKey(), out var isLegendaryHero); - return hasValue && isLegendaryHero; - }, - (val) => { - if (Settings.perSave.charIsLegendaryHero.ContainsKey(ch.HashKey())) { - Settings.perSave.charIsLegendaryHero[ch.HashKey()] = val; - Settings.SavePerSaveSettings(); - } - else { - Settings.perSave.charIsLegendaryHero.Add(ch.HashKey(), val); - Settings.SavePerSaveSettings(); - } - }, - 0f, - AutoWidth()); - Space(380); - Label("Tick this to let your character exceed the level 20 level cap like the Legend mythic path".green()); - } - Div(100, 20); - if (editMultiClass) { - MulticlassPicker.OnGUI(ch); - } - else { - var prog = ch.Descriptor.Progression; - using (HorizontalScope()) { - using (HorizontalScope(Width(600))) { - Space(100); - Label("Character Level".cyan(), Width(250)); - ActionButton("<", () => prog.CharacterLevel = Math.Max(0, prog.CharacterLevel - 1), AutoWidth()); - Space(25); - Label("level".green() + $": {prog.CharacterLevel}", Width(100f)); - ActionButton(">", () => prog.CharacterLevel = Math.Min(prog.MaxCharacterLevel, prog.CharacterLevel + 1), AutoWidth()); - } - ActionButton("Reset", () => ch.resetClassLevel(), Width(150)); - Space(23); - using (VerticalScope()) { - Label("This directly changes your character level but will not change exp or adjust any features associated with your character. To do a normal level up use +1 Lvl above. This gets recalculated when you reload the game. ".green()); - Label("If you want to alter default character level mark classes you want to exclude from the calculation with ".orange() + "gestalt".orange().bold() + " which means those levels were added for multi-classing. See the link for more information on this campaign variant.".orange()); - LinkButton("Gestalt Characters", "https://www.d20srd.org/srd/variant/classes/gestaltCharacters.htm"); - } - } - using (HorizontalScope()) { - using (HorizontalScope(Width(600))) { - Space(100); - Label("Experience".cyan(), Width(250)); - Space(25); - int tmpExp = prog.Experience; - IntTextField(ref tmpExp, null, Width(150f)); - prog.Experience = tmpExp; - } - } - using (HorizontalScope()) { - using (HorizontalScope(Width(781))) { - Space(100); - ActionButton("Adjust based on Level", () => { - prog.MythicExperience = prog.MythicLevel; - }, AutoWidth()); - Space(27); - } - Label("This sets your experience to match the current value of character level".green()); - } - Div(100, 25); - using (HorizontalScope()) { - using (HorizontalScope(Width(600))) { - Space(100); - Label("Mythic Level".cyan(), Width(250)); - ActionButton("<", () => prog.MythicLevel = Math.Max(0, prog.MythicLevel - 1), AutoWidth()); - Space(25); - Label("my lvl".green() + $": {prog.MythicLevel}", Width(100f)); - ActionButton(">", () => prog.MythicLevel = Math.Min(10, prog.MythicLevel + 1), AutoWidth()); - } - Space(181); - Label("This directly changes your mythic level but will not adjust any features associated with your character. To do a normal mythic level up use +1 my above".green()); - } - using (HorizontalScope()) { - using (HorizontalScope(Width(600))) { - Space(100); - Label("Experience".cyan(), Width(250)); - Space(25); - int tmpMythicExp = prog.MythicExperience; - IntTextField(ref tmpMythicExp, null, Width(150f)); - if (0 <= tmpMythicExp && tmpMythicExp <= 10) { - prog.MythicExperience = tmpMythicExp; - } // If Mythic experience is 0, entering any number besides 1 is > 10, meaning the number would be overwritten with; this is to prevent that - else if (tmpMythicExp % 10 == 0) { - prog.MythicExperience = tmpMythicExp / 10; - } - } - } - using (HorizontalScope()) { - using (HorizontalScope(Width(781))) { - Space(100); - ActionButton("Adjust based on Level", () => { - prog.MythicExperience = prog.MythicLevel; - }, AutoWidth()); - Space(27); - } - Label("This sets your mythic experience to match the current value of mythic level. Note that mythic experience is 1 point per level".green()); - } - var classCount = classData.Count(x => !x.CharacterClass.IsMythic); - var gestaltCount = classData.Count(cd => !cd.CharacterClass.IsMythic && ch.IsClassGestalt(cd.CharacterClass)); - var mythicCount = classData.Count(x => x.CharacterClass.IsMythic); - var mythicGestaltCount = classData.Count(cd => cd.CharacterClass.IsMythic && ch.IsClassGestalt(cd.CharacterClass)); - foreach (var cd in classData) { - var showedGestalt = false; - Div(100, 20); - using (HorizontalScope()) { - Space(100); - using (VerticalScope(Width(250))) { - var className = cd.CharacterClass.Name; - var archetype = cd.Archetypes.FirstOrDefault<BlueprintArchetype>(); - if (archetype != null) { - var archName = archetype.Name; - Label(archName.orange(), Width(250)); - if (!archName.Contains(className)) - Label(className.yellow(), Width(250)); - } - else { - Label(className.orange(), Width(250)); - } - } - ActionButton("<", () => cd.Level = Math.Max(0, cd.Level - 1), AutoWidth()); - Space(25); - Label("level".green() + $": {cd.Level}", Width(100f)); - var maxLevel = cd.CharacterClass.Progression.IsMythic ? 10 : 20; - ActionButton(">", () => cd.Level = Math.Min(maxLevel, cd.Level + 1), AutoWidth()); - Space(23); - if (ch.IsClassGestalt(cd.CharacterClass) - || !cd.CharacterClass.IsMythic && classCount - gestaltCount > 1 - || cd.CharacterClass.IsMythic && mythicCount - mythicGestaltCount > 1 - ) { - ActionToggle( - "gestalt".grey(), - () => ch.IsClassGestalt(cd.CharacterClass), - (v) => { - ch.SetClassIsGestalt(cd.CharacterClass, v); - ch.Progression.UpdateLevelsForGestalt(); - }, - 125 - ); - showedGestalt = true; - } - else Space(125); - Space(27); - using (VerticalScope()) { - if (showedGestalt) { - if (showedGestalt) { - Label("this flag lets you not count this class in computing character level".green()); - DivLast(); - } - } - Label(cd.CharacterClass.Description.StripHTML().green(), AutoWidth()); - } - } - } - } - } - } -} \ No newline at end of file diff --git a/ToyBox/classes/MainUI/PartyEditor/HumanFriendlyStats.cs b/ToyBox/classes/MainUI/PartyEditor/HumanFriendlyStats.cs deleted file mode 100644 index 41e6dacda..000000000 --- a/ToyBox/classes/MainUI/PartyEditor/HumanFriendlyStats.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Kingmaker.EntitySystem.Stats; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace ToyBox.classes.Infrastructure { - public static class HumanFriendlyStats { - public static void EnsureFriendlyTypesContainAll() { - if (Enum.GetValues(typeof(StatType)).Length != StatTypes.Count) { - HashSet<int> friendlyTypes = new(StatTypes.Cast<int>().ToList()); - var missingTypes = Enum.GetValues(typeof(StatType)).Cast<int>().ToList() - .Where(orig => friendlyTypes.Contains(orig) == false) - .Select(x => (StatType)x); - StatTypes.AddRange(missingTypes); - } - } - - public static List<StatType> StatTypes = new() { - StatType.Unknown, - StatType.Strength, - StatType.Dexterity, - StatType.Constitution, - StatType.Intelligence, - StatType.Wisdom, - StatType.Charisma, - - StatType.BaseAttackBonus, - StatType.AdditionalAttackBonus, - StatType.AdditionalDamage, - StatType.AttackOfOpportunityCount, - StatType.Reach, - StatType.SneakAttack, - - StatType.HitPoints, - StatType.TemporaryHitPoints, - StatType.DamageNonLethal, - StatType.AC, - StatType.AdditionalCMB, - StatType.AdditionalCMD, - StatType.SaveFortitude, - StatType.SaveWill, - StatType.SaveReflex, - StatType.Initiative, - StatType.Speed, - - StatType.SkillAthletics, - StatType.SkillKnowledgeArcana, - StatType.SkillKnowledgeWorld, - StatType.SkillLoreNature, - StatType.SkillLoreReligion, - StatType.SkillMobility, - StatType.SkillPerception, - StatType.SkillPersuasion, - StatType.SkillStealth, - StatType.SkillThievery, - StatType.SkillUseMagicDevice, - StatType.CheckBluff, - StatType.CheckDiplomacy, - StatType.CheckIntimidate - }; - } -} diff --git a/ToyBox/classes/MainUI/PartyEditor/PartyEditor.cs b/ToyBox/classes/MainUI/PartyEditor/PartyEditor.cs deleted file mode 100644 index 33984653d..000000000 --- a/ToyBox/classes/MainUI/PartyEditor/PartyEditor.cs +++ /dev/null @@ -1,318 +0,0 @@ -// Copyright < 2021 > Narria (github user Cabarius) - License: MIT -using Kingmaker; -using Kingmaker.Designers; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.UnitLogic; -using Kingmaker.UnitLogic.Parts; -using ModKit; -using ModKit.DataViewer; -using System; -using System.Collections.Generic; -using System.Linq; -using static ModKit.UI; - -namespace ToyBox { - public partial class PartyEditor { - public static Settings Settings => Main.Settings; - - private enum ToggleChoice { - Classes, - Stats, - Facts, - Features, - Buffs, - Abilities, - Spells, - AI, - None, - }; - private const int NarrowIndent = 413; - - private static ToggleChoice selectedToggle = ToggleChoice.None; - private static int editingCharacterIndex = 0; - private static UnitEntityData charToAdd = null; - private static UnitEntityData charToRecruit = null; - private static UnitEntityData charToRemove = null; - private static UnitEntityData charToUnrecruit = null; - private static bool editMultiClass = false; - private static UnitEntityData multiclassEditCharacter = null; - private static int respecableCount = 0; - private static int recruitableCount = 0; - private static int selectedSpellbook = 0; - private static (string, string) nameEditState = (null, null); - public static int selectedSpellbookLevel = 0; - private static bool editSpellbooks = false; - private static UnitEntityData spellbookEditCharacter = null; - private static readonly Dictionary<string, int> statEditorStorage = new(); - public static Dictionary<string, Spellbook> SelectedSpellbook = new(); - private static UnitEntityData GetEditCharacter() { - var characterList = CharacterPicker.GetCharacterList(); - if (characterList == null || characterList.Count == 0) return null; - if (editingCharacterIndex >= characterList.Count) editingCharacterIndex = 0; - return characterList[editingCharacterIndex]; - } - - public static void ResetGUI() { - editingCharacterIndex = 0; - selectedSpellbook = 0; - selectedSpellbookLevel = 0; - CharacterPicker.PartyFilterChoices = null; - Main.Settings.selectedPartyFilter = 0; - } - - // This bit of kludge is added in order to tell whether our generic actions are being accessed from this screen or the Search n' Pick - public static bool IsOnPartyEditor() => Main.Settings.selectedTab == 2; - - public static void ActionsGUI(UnitEntityData ch) { - var player = Game.Instance.Player; - Space(25); - if (!player.PartyAndPets.Contains(ch) && player.AllCharacters.Contains(ch)) { - ActionButton("Add", () => { charToAdd = ch; }, Width(150)); - Space(25); - } - else if (player.ActiveCompanions.Contains(ch)) { - ActionButton("Remove", () => { charToRemove = ch; }, Width(150)); - Space(25); - } - else if (!player.AllCharacters.Contains(ch)) { - recruitableCount++; - ActionButton("Recruit".cyan(), () => { charToRecruit = ch; }, Width(150)); - Space(25); - } - if (player.AllCharacters.Contains(ch) && !ch.IsStoryCompanion()) { - ActionButton("Unrecruit".cyan(), () => { charToUnrecruit = ch; charToRemove = ch; }, Width(150)); - Space(25); - - } - // else - // Space(178); - if (RespecHelper.GetRespecableUnits().Contains(ch)) { - respecableCount++; - ActionButton("Respec".cyan(), () => { Actions.ToggleModWindow(); RespecHelper.Respec(ch); }, Width(150)); - } - else { - Space(153); - } -#if false - Space(25); - ActionButton("Log Caster Info", () => CasterHelpers.GetOriginalCasterLevel(ch.Descriptor), - AutoWidth()); -#endif - Label("", AutoWidth()); - } - public static void OnGUI() { - var player = Game.Instance.Player; - if (player == null) return; - charToAdd = null; - charToRecruit = null; - charToRemove = null; - charToUnrecruit = null; - var characterListFunc = CharacterPicker.OnFilterPickerGUI(); - var characterList = characterListFunc.func(); - var mainChar = GameHelper.GetPlayerCharacter(); - if (characterListFunc.name == "Nearby") { - Slider("Nearby Distance", ref CharacterPicker.nearbyRange, 1f, 200, 25, 0, " meters", Width(250)); - characterList = characterList.OrderBy((ch) => ch.DistanceTo(mainChar)).ToList(); - } - Space(20); - var chIndex = 0; - recruitableCount = 0; - respecableCount = 0; - var selectedCharacter = GetEditCharacter(); - var isWide = IsWide; - if (Main.IsInGame) { - using (HorizontalScope()) { - Label($"Party Level ".cyan() + $"{Game.Instance.Player.PartyLevel}".orange().bold(), AutoWidth()); - Space(110); - ReflectionTreeView.DetailToggle("Inspect Party "+ "(for modders)".orange(), "All" , characterList, 0); -#if false // disabled until we fix performance - var encounterCR = CheatsCombat.GetEncounterCr(); - if (encounterCR > 0) { - UI.Label($"Encounter CR ".cyan() + $"{encounterCR}".orange().bold(), UI.AutoWidth()); - } -#endif - } - } - ReflectionTreeView.OnDetailGUI("All"); - List<Action> todo = new(); - foreach (var ch in characterList) { - var classData = ch.Progression.Classes; - // TODO - understand the difference between ch.Progression and ch.Descriptor.Progression - var progression = ch.Descriptor.Progression; - var xpTable = progression.ExperienceTable; - var level = progression.CharacterLevel; - var mythicLevel = progression.MythicLevel; - var spellbooks = ch.Spellbooks.ToList(); - var spellCount = spellbooks.Sum((sb) => sb.GetAllKnownSpells().Count()); - var isOnTeam = player.AllCharacters.Contains(ch); - using (HorizontalScope()) { - var name = ch.CharacterName; - if (Game.Instance.Player.AllCharacters.Contains(ch)) { - var oldEditState = nameEditState; - if (isWide) { - if (EditableLabel(ref name, ref nameEditState, 200, n => n.orange().bold(), MinWidth(100), MaxWidth(400))) { - ch.Descriptor.CustomName = name; - Main.SetNeedsResetGameUI(); - } - } - else - if (EditableLabel(ref name, ref nameEditState, 200, n => n.orange().bold(), Width(230))) { - ch.Descriptor.CustomName = name; - Main.SetNeedsResetGameUI(); - } - if (nameEditState != oldEditState) { - Mod.Log($"EditState changed: {oldEditState} -> {nameEditState}"); - } - } - else { - if (isWide) - Label(ch.CharacterName.orange().bold(), MinWidth(100), MaxWidth(400)); - else - Label(ch.CharacterName.orange().bold(), Width(230)); - } - Space(5); - var distance = mainChar.DistanceTo(ch); ; - Label(distance < 1 ? "" : distance.ToString("0") + "m", Width(75)); - Space(5); - int nextLevel; - for (nextLevel = level; xpTable.HasBonusForLevel(nextLevel + 1) && progression.Experience >= xpTable.GetBonus(nextLevel + 1); nextLevel++) { } - if (nextLevel <= level || !isOnTeam) - Label((level < 10 ? " lvl" : " lv").green() + $" {level}", Width(90)); - else - Label((level < 10 ? " " : "") + $"{level} > " + $"{nextLevel}".cyan(), Width(90)); - // Level up code adapted from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/2 - if (player.AllCharacters.Contains(ch)) { - if (xpTable.HasBonusForLevel(nextLevel + 1)) { - ActionButton("+1", () => { - progression.AdvanceExperienceTo(xpTable.GetBonus(nextLevel + 1), true); - }, Width(63)); - } - else { Label("max", Width(63)); } - } - else { Space(66); } - Space(10); - var nextML = progression.MythicExperience; - if (nextML <= mythicLevel || !isOnTeam) - Label((mythicLevel < 10 ? " my" : " my").green() + $" {mythicLevel}", Width(90)); - else - Label((level < 10 ? " " : "") + $"{mythicLevel} > " + $"{nextML}".cyan(), Width(90)); - if (player.AllCharacters.Contains(ch)) { - if (progression.MythicExperience < 10) { - ActionButton("+1", () => { - progression.AdvanceMythicExperience(progression.MythicExperience + 1, true); - }, Width(63)); - } - else { Label("max", Width(63)); } - } - else { Space(66); } - Space(30); - Wrap(IsNarrow, NarrowIndent, 0); - var prevSelectedChar = selectedCharacter; - var showClasses = ch == selectedCharacter && selectedToggle == ToggleChoice.Classes; - if (DisclosureToggle($"{classData.Count} Classes", ref showClasses, 140)) { - if (showClasses) { - selectedCharacter = ch; selectedToggle = ToggleChoice.Classes; Mod.Trace($"selected {ch.CharacterName}"); - } - else { selectedToggle = ToggleChoice.None; } - } - var showStats = ch == selectedCharacter && selectedToggle == ToggleChoice.Stats; - if (DisclosureToggle("Stats", ref showStats, 95)) { - if (showStats) { selectedCharacter = ch; selectedToggle = ToggleChoice.Stats; } - else { selectedToggle = ToggleChoice.None; } - } - //var showFacts = ch == selectedCharacter && selectedToggle == ToggleChoice.Facts; - //if (UI.DisclosureToggle("Facts", ref showFacts, 125)) { - // if (showFacts) { selectedCharacter = ch; selectedToggle = ToggleChoice.Facts; } - // else { selectedToggle = ToggleChoice.None; } - //} - var showFeatures = ch == selectedCharacter && selectedToggle == ToggleChoice.Features; - if (DisclosureToggle("Features", ref showFeatures, 125)) { - if (showFeatures) { selectedCharacter = ch; selectedToggle = ToggleChoice.Features; } - else { selectedToggle = ToggleChoice.None; } - } - Wrap(!IsWide, NarrowIndent, 0); - var showBuffs = ch == selectedCharacter && selectedToggle == ToggleChoice.Buffs; - if (DisclosureToggle("Buffs", ref showBuffs, 90)) { - if (showBuffs) { selectedCharacter = ch; selectedToggle = ToggleChoice.Buffs; } - else { selectedToggle = ToggleChoice.None; } - } - var showAbilities = ch == selectedCharacter && selectedToggle == ToggleChoice.Abilities; - if (DisclosureToggle("Abilities", ref showAbilities, 125)) { - if (showAbilities) { selectedCharacter = ch; selectedToggle = ToggleChoice.Abilities; } - else { selectedToggle = ToggleChoice.None; } - } - var showSpells = ch == selectedCharacter && selectedToggle == ToggleChoice.Spells; - if (DisclosureToggle($"{spellCount} Spells", ref showSpells, 150)) { - if (showSpells) { selectedCharacter = ch; selectedToggle = ToggleChoice.Spells; } - else { selectedToggle = ToggleChoice.None; } - } - var showAI = ch == selectedCharacter && selectedToggle == ToggleChoice.AI; - ReflectionTreeView.DetailToggle("Ins", ch, ch, 75); - Wrap(!isWide, NarrowIndent - 20); - ActionsGUI(ch); - if (prevSelectedChar != selectedCharacter) { - selectedSpellbook = 0; - } - } - if (!isWide) Div(00, 10); - 5.space(); - ReflectionTreeView.OnDetailGUI(ch); - //if (!UI.IsWide && (selectedToggle != ToggleChoice.Stats || ch != selectedCharacter)) { - // UI.Div(20, 20); - //} - if (selectedCharacter != spellbookEditCharacter) { - editSpellbooks = false; - spellbookEditCharacter = null; - } - if (selectedCharacter != multiclassEditCharacter) { - editMultiClass = false; - multiclassEditCharacter = null; - } - if (ch == selectedCharacter && selectedToggle == ToggleChoice.Classes) { - OnClassesGUI(ch, classData, selectedCharacter); - } - if (ch == selectedCharacter && selectedToggle == ToggleChoice.Stats) { - OnStatsGUI(ch); - } - //if (ch == selectedCharacter && selectedToggle == ToggleChoice.Facts) { - // todo = FactsEditor.OnGUI(ch, ch.Facts.m_Facts); - //} - if (ch == selectedCharacter && selectedToggle == ToggleChoice.Features) { - todo = FactsEditor.OnGUI(ch, ch.Progression.Features.Enumerable.ToList()); - } - if (ch == selectedCharacter && selectedToggle == ToggleChoice.Buffs) { - todo = FactsEditor.OnGUI(ch, ch.Descriptor.Buffs.Enumerable.ToList()); - } - if (ch == selectedCharacter && selectedToggle == ToggleChoice.Abilities) { - todo = FactsEditor.OnGUI(ch, ch.Descriptor.Abilities.Enumerable.ToList()); - } - if (ch == selectedCharacter && selectedToggle == ToggleChoice.Spells) { - todo = OnSpellsGUI(ch, spellbooks); - } - if (selectedCharacter != GetEditCharacter()) { - editingCharacterIndex = characterList.IndexOf(selectedCharacter); - } - chIndex += 1; - } - Space(25); - if (recruitableCount > 0) { - Label($"{recruitableCount} character(s) can be ".orange().bold() + " Recruited".cyan() + ". This allows you to add non party NPCs to your party as if they were mercenaries".green()); - } - if (respecableCount > 0) { - Label($"{respecableCount} character(s) can be ".orange().bold() + "Respecced".cyan() + ". Pressing Respec will close the mod window and take you to character level up".green()); - Label("WARNING".yellow().bold() + " The Respec UI is ".orange() + "Non Interruptable".yellow().bold() + " please save before using".orange()); - } - if (recruitableCount > 0 || respecableCount > 0) { - Label("WARNING".yellow().bold() + " these features are ".orange() + "EXPERIMENTAL".yellow().bold() + " and uses unreleased and likely buggy code.".orange()); - Label("BACK UP".yellow().bold() + " before playing with this feature.You will lose your mythic ranks but you can restore them in this Party Editor.".orange()); - } - Space(25); - foreach (var action in todo) - action(); - if (charToAdd != null) { UnitEntityDataUtils.AddCompanion(charToAdd); } - if (charToRecruit != null) { UnitEntityDataUtils.RecruitCompanion(charToRecruit); } - if (charToRemove != null) { UnitEntityDataUtils.RemoveCompanion(charToRemove); } - if (charToUnrecruit != null) { charToUnrecruit.Ensure<UnitPartCompanion>().SetState(CompanionState.None); charToUnrecruit.Remove<UnitPartCompanion>(); } - } - } -} \ No newline at end of file diff --git a/ToyBox/classes/MainUI/PartyEditor/SpellsEditor.cs b/ToyBox/classes/MainUI/PartyEditor/SpellsEditor.cs deleted file mode 100644 index a6efd54c6..000000000 --- a/ToyBox/classes/MainUI/PartyEditor/SpellsEditor.cs +++ /dev/null @@ -1,248 +0,0 @@ -using Kingmaker.Blueprints; -using Kingmaker.Blueprints.Classes.Spells; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.UnitLogic; -using Kingmaker.UnitLogic.Abilities; -using Kingmaker.UnitLogic.Abilities.Blueprints; -using Kingmaker.UnitLogic.FactLogic; -using ModKit; -using ModKit.DataViewer; -using ModKit.Utility; -using System; -using System.Collections.Generic; -using System.Linq; -using ToyBox.classes.Infrastructure; -using UnityEngine; -using static ModKit.UI; -using static ToyBox.BlueprintExtensions; - - -namespace ToyBox { - public partial class PartyEditor { - public static Dictionary<UnitEntityData, Browser<BlueprintSpellbook, Spellbook>> SpellbookBrowserDict = new(); - public static Dictionary<UnitEntityData, Browser<BlueprintAbility, AbilityData>> SpellBrowserDict = new(); - private static bool _startedLoading = false; - public static int newSpellLvl = 0; - public static List<Action> OnSpellsGUI(UnitEntityData ch, List<Spellbook> spellbooks) { - List<Action> todo = new(); - Space(20); - var names = spellbooks.Select((sb) => sb.Blueprint.GetDisplayName()).ToArray(); - var titles = names.Select((name, i) => $"{name} ({spellbooks.ElementAt(i).CasterLevel})").ToArray(); - if (spellbooks.Any()) { - if (selectedSpellbook >= spellbooks.Count) - selectedSpellbook = 0; - var spellbook = spellbooks.ElementAt(selectedSpellbook); - bool selectedSpellBookChanged = false; - using (HorizontalScope()) { - selectedSpellBookChanged = SelectionGrid(ref selectedSpellbook, titles, Math.Min(titles.Length, 7), AutoWidth()); - if (selectedSpellbook >= names.Length) selectedSpellbook = 0; - DisclosureToggle("Edit".orange().bold(), ref editSpellbooks); - Space(-50); - var mergeableClasses = ch.MergableClasses().ToList(); - if (spellbook.IsStandaloneMythic || mergeableClasses.Count() == 0) { - Label($"Mythic Merging".cyan(), AutoWidth()); - 25.space(); - Label("When you get standalone mythic spellbooks you can merge them here by select.".green()); - } - else { - Label($"Merge Mythic:".cyan(), 175.width()); - 25.space(); - foreach (var cl in mergeableClasses) { - var sb = spellbook; - ActionButton(cl.CharacterClass.LocalizedName.ToString(), () => sb.MergeMythicSpellbook(cl)); - 15.space(); - } - 25.space(); - using (VerticalScope()) { - Label("Merging your mythic spellbook will cause you to transfer all mythic spells to your normal spellbook and gain caster levels equal to your mythic level. You will then be able to re-select spells on next level up or mythic level up. Merging a second mythic spellbook will transfer the spells but not increase your caster level further. If you want more CL then increase it below.".green()); - Label("Warning: This is irreversible. Please save before continuing!".Orange()); - } - } - } - spellbook = spellbooks.ElementAt(selectedSpellbook); - if (editSpellbooks) { - spellbookEditCharacter = ch; - SpellBookBrowserOnGUI(ch, spellbooks, todo); - } - else { - var spellBrowser = SpellBrowserDict.GetValueOrDefault(ch, null); - if (spellBrowser == null) { - spellBrowser = new Browser<BlueprintAbility, AbilityData>(); - SpellBrowserDict[ch] = spellBrowser; - } - var maxLevel = spellbook.Blueprint.MaxSpellLevel; - var casterLevel = spellbook.CasterLevel; - using (HorizontalScope()) { - var tempSelected = selectedSpellbookLevel; - EnumerablePicker( - "Spells known", - ref selectedSpellbookLevel, - Enumerable.Range(0, spellbook.Blueprint.MaxSpellLevel + 2), - 0, - (lvl) => { - if (lvl < spellbook.Blueprint.MaxSpellLevel + 1) { - var levelText = spellbook.Blueprint?.SpellsPerDay?.GetCount(casterLevel, lvl) != null ? $"L{lvl}".bold() : $"L{lvl}".grey(); - var knownCount = spellbook.GetKnownSpells(lvl).Count; - var countText = knownCount > 0 ? $" ({knownCount})".white() : ""; - return levelText + countText; - } - else { - return "All Spells"; - } - }, - AutoWidth() - ); - if (tempSelected != selectedSpellbookLevel || selectedSpellBookChanged) { - spellBrowser.ResetSearch(); - spellBrowser.startedLoadingAvailable = true; - } - Space(20); - if (casterLevel > 0) { - ActionButton("-1 CL", () => CasterHelpers.LowerCasterLevel(spellbook), AutoWidth()); - } - if (casterLevel < 40) { - ActionButton("+1 CL", () => CasterHelpers.AddCasterLevel(spellbook), AutoWidth()); - } - // removes opposition schools; these are not cleared when removing facts; to add new opposition schools, simply add the corresponding fact again - if (spellbook.OppositionSchools.Any()) { - ActionButton("Clear Opposition Schools", () => { - spellbook.OppositionSchools.Clear(); - spellbook.ExOppositionSchools.Clear(); - ch.Facts.RemoveAll<UnitFact>(r => r.Blueprint.GetComponent<AddOppositionSchool>(), true); - }, AutoWidth()); - } - if (spellbook.OppositionDescriptors != 0) { - ActionButton("Clear Opposition Descriptors", () => { - spellbook.OppositionDescriptors = 0; - ch.Facts.RemoveAll<UnitFact>(r => r.Blueprint.GetComponent<AddOppositionDescriptor>(), true); - }, AutoWidth()); - } - } - var unorderedSpells = selectedSpellbookLevel <= spellbook.Blueprint.MaxSpellLevel ? spellbook.GetKnownSpells(selectedSpellbookLevel) : spellbook.GetAllKnownSpells(); - var spells = unorderedSpells.OrderBy(d => d.Name).ToList(); - SelectedSpellbook[ch.HashKey()] = spellbook; - spellBrowser.OnGUI( - spells, - () => { - List<BlueprintAbility> availableSpells; - if (Settings.showFromAllSpellbooks || (spellbook.Blueprint.MaxSpellLevel + 1) == selectedSpellbookLevel) { - if ((spellbook.Blueprint.MaxSpellLevel + 1) == selectedSpellbookLevel) { - availableSpells = new List<BlueprintAbility>(CasterHelpers.GetAllSpells(-1)); - } - else { - availableSpells = new List<BlueprintAbility>(CasterHelpers.GetAllSpells(selectedSpellbookLevel)); - } - } - else { - availableSpells = new List<BlueprintAbility>(spellbook.Blueprint.SpellList.GetSpells(selectedSpellbookLevel)); - } - if (!((spellbook.Blueprint.MaxSpellLevel + 1) == selectedSpellbookLevel)) { - spells.ForEach((s) => availableSpells.Add(s.Blueprint)); - } - return availableSpells.ToHashSet().ToList(); // todo: why are there duplicates here? - }, - feature => feature.Blueprint, - blueprint => $"{GetTitle(blueprint)}" + (Settings.searchDescriptions ? $" {blueprint.GetDescription()}" : ""), - GetTitle, - () => { - using (HorizontalScope()) { - bool needsReload = false; - Toggle("Show GUIDs", ref Main.Settings.showAssetIDs); - 20.space(); - needsReload |= Toggle("Show Internal Names", ref Settings.showDisplayAndInternalNames); - 20.space(); - // Toggle("Show Inspector", ref Settings.factEditorShowInspector); - // 20.space(); - needsReload |= Toggle("Search Descriptions", ref Settings.searchDescriptions); - 20.space(); - if (Toggle("Search All Spellbooks", ref Settings.showFromAllSpellbooks)) { - spellBrowser.ResetSearch(); - _startedLoading = true; - } - if (needsReload) spellBrowser.ResetSearch(); - GUI.enabled = !spellBrowser.isSearching; - Space(20); - ActionButton("Add All", () => CasterHelpers.HandleAddAllSpellsOnPartyEditor(ch.Descriptor, spellBrowser.filteredDefinitions.Cast<BlueprintAbility>().ToList()), AutoWidth()); - Space(20); - ActionButton("Remove All", () => CasterHelpers.HandleAddAllSpellsOnPartyEditor(ch.Descriptor), AutoWidth()); - GUI.enabled = true; - if ((spellbook.Blueprint.MaxSpellLevel + 1) == selectedSpellbookLevel) { - 10.space(); - Label("Spells are added at Level: ".green() + newSpellLvl.ToString().orange(), AutoWidth()); - 10.space(); - ActionButton("-", () => { - if (newSpellLvl >= 0) { - if (newSpellLvl == 0) { - newSpellLvl = spellbook.Blueprint.MaxSpellLevel; - } - else { - newSpellLvl -= 1; - } - } - }, AutoWidth()); - ActionButton("+", () => { - if (newSpellLvl == spellbook.MaxSpellLevel) { - newSpellLvl = 1; - } - else { - newSpellLvl += 1; - } - }, AutoWidth()); - } - } - }, - (blueprint, feature) => FactsEditor.BlueprintRowGUI(spellBrowser, feature, - blueprint, ch, todo), - (blueprint, feature) => { - ReflectionTreeView.OnDetailGUI(blueprint); - }, 50, false, true, 100, 300, "", true); - } - } - else { - SpellBookBrowserOnGUI(ch, spellbooks, todo, true); - } - return todo; - } - private static void SpellBookBrowserOnGUI(UnitEntityData ch, IEnumerable<Spellbook> spellbooks, List<Action> todo, bool forceShowAll = false) { - var spellbookBrowser = SpellbookBrowserDict.GetValueOrDefault(ch, null); - if (spellbookBrowser == null) { - spellbookBrowser = new Browser<BlueprintSpellbook, Spellbook>(); - SpellbookBrowserDict[ch] = spellbookBrowser; - } - if (forceShowAll) { - spellbookBrowser.ShowAll = true; - spellbookBrowser.needsReloadData = true; - } - spellbookBrowser.OnGUI( - spellbooks, - BlueprintExtensions.GetBlueprints<BlueprintSpellbook>, - (feature) => feature.Blueprint, - blueprint => $"{GetTitle(blueprint)}" + (Settings.searchDescriptions ? $" {blueprint.GetDescription()}" : ""), - GetTitle, - () => { - using (HorizontalScope()) { - Toggle("Show GUIDs", ref Main.Settings.showAssetIDs, 150.width()); - 20.space(); - if (Toggle("Show Internal Names", ref Settings.showDisplayAndInternalNames, 200.width())) - spellbookBrowser.ResetSearch(); - 20.space(); - // Toggle("Show Inspector", ref Settings.factEditorShowInspector, 150.width()); - // 20.space(); - - if (Toggle("Search Descriptions", ref Settings.searchDescriptions, 250.width())) { - spellbookBrowser.ResetSearch(); - } - } - }, - (blueprint, feature) => FactsEditor.BlueprintRowGUI(spellbookBrowser, feature, blueprint, ch, todo), - (blueprint, feature) => { ReflectionTreeView.OnDetailGUI(blueprint); }, - 50, - false, - true, - 100, - 300, - "", - true); - } - } -} diff --git a/ToyBox/classes/MainUI/PartyEditor/StatsEditor.cs b/ToyBox/classes/MainUI/PartyEditor/StatsEditor.cs deleted file mode 100644 index d7514047a..000000000 --- a/ToyBox/classes/MainUI/PartyEditor/StatsEditor.cs +++ /dev/null @@ -1,226 +0,0 @@ -using Kingmaker; -using Kingmaker.Blueprints; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.EntitySystem.Stats; -using Kingmaker.Enums; -using Kingmaker.UnitLogic.Alignments; -using Kingmaker.UnitLogic.Parts; -using ModKit; -using ModKit.Utility; -using System; -using System.Collections.Generic; -using System.Linq; -using ToyBox.classes.Infrastructure; -using UnityEngine; -using static ModKit.UI; - -namespace ToyBox { - public partial class PartyEditor { - public class ToyBoxAlignmentProvider : IAlignmentShiftProvider { - AlignmentShift IAlignmentShiftProvider.AlignmentShift => new() { Description = "ToyBox Party Editor".LocalizedStringInGame() }; - } - public static IAlignmentShiftProvider ToyboxAlignmentProvider => new ToyBoxAlignmentProvider(); - - public static Dictionary<string, float> lastScaleSize = new(); - private static int _increase = 1; - public static void OnStatsGUI(UnitEntityData ch) { - Div(100, 20, 755); - var alignment = ch.Descriptor.Alignment.ValueRaw; - using (HorizontalScope()) { - 100.space(); - Label("Alignment", Width(425)); - Label($"{alignment.Name()}".color(alignment.Color()).bold(), Width(1250f)); - } - using (HorizontalScope()) { - 528.space(); - AlignmentGrid(alignment, (a) => ch.Descriptor.Alignment.Set(a)); - } - Div(100, 20, 755); - using (HorizontalScope()) { - var charAlignment = ch.Descriptor.Alignment; - 100.space(); - Label($"Shift Alignment {alignment.Acronym().color(alignment.Color()).bold()} {(charAlignment.VectorRaw * 50).ToString().Cyan()} by", 340.width()); - 5.space(); - var increment = IntTextField(ref Settings.alignmentIncrement, null, 55.width()); - var maskIndex = -1; - 20.space(); - var titles = AlignmentShiftDirections.Select( - a => $"{increment.ToString("+0;-#").orange()} {a.ToString().color(a.Color()).bold()}").ToArray(); - if (SelectionGrid(ref maskIndex, titles, 3, 650.width())) { - charAlignment.Shift(AlignmentShiftDirections[maskIndex], increment, ToyboxAlignmentProvider); - } - } - Div(100, 20, 755); - var alignmentMask = ch.Descriptor.Alignment.m_LockedAlignmentMask; - using (HorizontalScope()) { - 100.space(); - Label("Alignment Lock", 425.width()); - //UI.Label($"{alignmentMask.ToString()}".color(alignmentMask.Color()).bold(), UI.Width(325)); - Label($"Experimental - this sets a mask on your alignment shifts. {"Warning".bold().orange()}{": Using this may change your alignment.".orange()}".green()); - } - using (HorizontalScope()) { - 528.space(); - var maskIndex = Array.IndexOf(AlignmentMasks, alignmentMask); - var titles = AlignmentMasks.Select( - a => a.ToString().color(a.Color()).bold()).ToArray(); - if (SelectionGrid(ref maskIndex, titles, 3, 650.width())) { - ch.Descriptor.Alignment.LockAlignment(AlignmentMasks[maskIndex], new Alignment?()); - } - } - Div(100, 20, 755); - using (HorizontalScope()) { - Space(100); - Label("Size", Width(425)); - var size = ch.Descriptor.State.Size; - Label($"{size}".orange().bold(), Width(175)); - } - using (HorizontalScope()) { - Space(528); - EnumGrid( - () => ch.Descriptor.State.Size, - (s) => ch.Descriptor.State.Size = s, - 3, Width(600)); - } - using (HorizontalScope()) { - Space(528); - ActionButton("Reset", () => { ch.Descriptor.State.Size = ch.Descriptor.OriginalSize; }, Width(197)); - } - using (HorizontalScope()) { - if (ch != null && ch.HashKey() != null) { - Space(100); - var scaleMult = ch.View.gameObject.transform.localScale[0]; - var lastScale = lastScaleSize.GetValueOrDefault(ch.HashKey(), 1); - if (lastScale != scaleMult) { - ch.View.gameObject.transform.localScale = new Vector3(lastScale, lastScale, lastScale); - } - if (LogSliderCustomLabelWidth("Visual Character Size Multiplier".color(RGBA.none) + " (This setting is per-save)", ref lastScale, 0.01f, 40f, 1, 2, "", 400, AutoWidth())) { - Main.Settings.perSave.characterModelSizeMultiplier[ch.HashKey()] = lastScale; - ch.View.gameObject.transform.localScale = new Vector3(lastScale, lastScale, lastScale); - lastScaleSize[ch.HashKey()] = lastScale; - Settings.SavePerSaveSettings(); - } - } - } - if (ch.Descriptor.Progression.GetCurrentMythicClass()?.CharacterClass.Name == "Swarm That Walks") { - UnitPartLocustSwarm SwarmPart = null; - UnitPartLocustClonePets SwarmClones = null; - bool found = false; - foreach (var part in ch.Parts.Parts) { - var tmpPart = part as UnitPartLocustSwarm; - var tmpClone = part as UnitPartLocustClonePets; - if (tmpPart != null) { - found = true; - SwarmPart = tmpPart; - } - if (tmpClone != null) { - SwarmClones = tmpClone; - } - } - if (found) { - Div(100, 20, 755); - if (SwarmPart != null) { - using (HorizontalScope()) { - Space(100); - Label("Swarm Power", Width(150)); - Label($"Currently: {SwarmPart.CurrentStrength}/{SwarmPart.CurrentScale}".green()); - } - using (HorizontalScope()) { - Space(100); - Label("Warning:".red().bold(), Width(150)); - Label("This is not reversible.".orange().bold(), Width(250)); - Space(25); - ActionButton("Increase Swarm Power", () => SwarmPart.AddStrength(_increase)); - Space(10); - IntTextField(ref _increase, "", MinWidth(50), AutoWidth()); - Space(25); - Label("This increases your Swarm Power by the provided value.".green()); - } - } - if (SwarmClones != null) { - using (HorizontalScope()) { - Space(100); - Label("Swarm Clones", Width(150)); - Label($"Currently: {SwarmClones?.m_SpawnedPetRefs?.Count}".green()); - } - using (HorizontalScope()) { - Space(100); - Label("Warning:".red().bold(), Width(150)); - Label("This is not reversible.".orange().bold(), Width(250)); - Space(25); - ActionButton("Remove all Clones", () => { - var toRemove = SwarmClones.m_SpawnedPetRefs.ToList(); - SwarmClones.RemoveClones(); - foreach (var clone in toRemove) { - Game.Instance.Player.RemoveCompanion(clone.Value); - Game.Instance.Player.DismissCompanion(clone.Value); - Game.Instance.Player.DetachPartyMember(clone.Value); - Game.Instance.Player.CrossSceneState.RemoveEntityData(clone.Value); - - } - - foreach (var buff in ch.Buffs.Enumerable.ToList()) { - if (BlueprintExtensions.GetTitle(buff.Blueprint).ToLower().Contains("locustclone")) { - ch.Buffs.RemoveFact(buff); - } - } - }); - } - } - } - } - Div(100, 20, 755); - using (HorizontalScope()) { - Space(100); - Label("Gender", Width(400)); - Space(25); - var gender = ch.Descriptor.CustomGender ?? ch.Descriptor.Gender; - var isFemale = gender == Gender.Female; - using (HorizontalScope(Width(200))) { - if (Toggle(isFemale ? "Female" : "Male", ref isFemale, - "♀".color(RGBA.magenta).bold(), - "♂".color(RGBA.aqua).bold(), - 0, largeStyle, GUI.skin.box, Width(300), Height(20))) { - ch.Descriptor.CustomGender = isFemale ? Gender.Female : Gender.Male; - } - } - Label("Changing your gender may cause visual glitches".green()); - } - Space(10); - Div(100, 20, 755); - foreach (var obj in HumanFriendlyStats.StatTypes) { - var statType = (StatType)obj; - var modifiableValue = ch.Stats.GetStat(statType); - if (modifiableValue == null) { - continue; - } - - var key = $"{ch.CharacterName}-{statType}"; - var storedValue = statEditorStorage.ContainsKey(key) ? statEditorStorage[key] : modifiableValue.BaseValue; - var statName = statType.ToString(); - if (statName == "BaseAttackBonus" || statName == "SkillAthletics" || statName == "HitPoints") { - Div(100, 20, 755); - } - using (HorizontalScope()) { - Space(100); - Label(statName, Width(400f)); - Space(25); - ActionButton(" < ", () => { - modifiableValue.BaseValue -= 1; - storedValue = modifiableValue.BaseValue; - }, GUI.skin.box, AutoWidth()); - Space(20); - Label($"{modifiableValue.BaseValue}".orange().bold(), Width(50f)); - ActionButton(" > ", () => { - modifiableValue.BaseValue += 1; - storedValue = modifiableValue.BaseValue; - }, GUI.skin.box, AutoWidth()); - Space(25); - ActionIntTextField(ref storedValue, (v) => { - modifiableValue.BaseValue = v; - }, Width(75)); - statEditorStorage[key] = storedValue; - } - } - } - } -} \ No newline at end of file diff --git a/ToyBox/classes/MainUI/Playground.cs b/ToyBox/classes/MainUI/Playground.cs deleted file mode 100644 index 8f96000f2..000000000 --- a/ToyBox/classes/MainUI/Playground.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using UnityEngine; -using ModKit; -using System.Collections; -using System.Collections.Generic; -using Kingmaker.Blueprints; - -namespace ModKit { - public static partial class ui { - public static class Size { - public const float Automatic = -1; - public const float Expands = float.PositiveInfinity; - - } - public class View : IDisposable { - public View background { get; set; } = null; - public Color color { get; set; } = Color.clear; - public float height { get; set; } = Size.Automatic; - public float width { get; set; } = Size.Automatic; - public RectOffset padding { get; set; } = new RectOffset(); - public void Dispose() => throw new NotImplementedException(); - public static explicit operator View(Color c) => new() { color = c }; - } - public class Stack : View { - public float spacing { get; set; } = 25; - } - - public class HStack : Stack { - - } - public class VStack : Stack { - - } - public class List<ItemType> : Stack { - public IEnumerable<ItemType> items; - public void ForEach(Action<ItemType> action) { } - public void ForEach(Action<ItemType, int> action) { } - } - - public class Label : View { - public string text { get; set; } = ""; - public Label(string t) { this.text = t; } - } - } -} - -namespace ToyBox { - // A place to play... - public static class Playground { - public static void OnGUI() { - var blueprints = SearchAndPick.filteredBPs; - using var list = new ui.List<SimpleBlueprint> { items = blueprints, spacing = 5 }; using (new ui.VStack()) { - UI.Label("Blueprints"); - list.ForEach((bp) => { - using (new ui.HStack { spacing = 25 }) { - new ui.Label(bp.name); - new ui.Label(bp.GetDescription()); - } - }); - } - } - } -} - - -// using (var stack = new UI.HStack { spacing = 25, color = RGBA.darkgrey.color() }) { diff --git a/ToyBox/classes/MainUI/QuestEditor.cs b/ToyBox/classes/MainUI/QuestEditor.cs deleted file mode 100644 index 6cec8c82c..000000000 --- a/ToyBox/classes/MainUI/QuestEditor.cs +++ /dev/null @@ -1,450 +0,0 @@ -// borrowed shamelessly and enhanced from Kingdom Resolution Mod -// "Author": "spacehamster", -// "HomePage": "https://www.nexusmods.com/pathfinderkingmaker/mods/36", -// "Repository": "https://raw.githubusercontent.com/spacehamster/KingmakerKingdomResolutionMod/master/KingdomResolution/Repository.json" -// Copyright < 2018 > Spacehamster -// Copyright < 2021 > Ported version - Narria (github user Cabarius) - License: MIT -using UnityEngine; -using HarmonyLib; -using System; -using System.Linq; -using Kingmaker; -using Kingmaker.AreaLogic.QuestSystem; -using Kingmaker.EntitySystem.Entities; -using ModKit; -using static ModKit.UI; -using ModKit.DataViewer; -using System.Collections.Generic; -using Kingmaker.Blueprints.Quests; -using Kingmaker.Designers.EventConditionActionSystem.Conditions; -using Kingmaker.UnitLogic.Parts; -using ModKit.Utility; -using static Kingmaker.UnitLogic.Interaction.SpawnerInteractionPart; -using static ToyBox.BlueprintExtensions; -using Kingmaker.Designers; -using Kingmaker.Designers.EventConditionActionSystem.Actions; -using Kingmaker.ElementsSystem; -using System.Security.AccessControl; - -namespace ToyBox { - public static class QuestExensions { - private static readonly RGBA[] titleColors = new RGBA[] { - RGBA.brown, - RGBA.cyan, - RGBA.darkgrey, - RGBA.red - }; - private static readonly string[] questColors = new string[] { - "gray", - "cyan", - "white", - "red" - }; - - public static bool IsRevealed(this QuestObjective objective) => objective.State == QuestObjectiveState.Started || objective.State == QuestObjectiveState.Completed; - public static string stateColored(this string text, Quest quest) => RichText.color(text, questColors[(int)quest.State]); - public static string stateColored(this string text, QuestObjective objective) => RichText.color(text, questColors[(int)objective.State]); - public static string titleColored(this Quest quest) => quest.Blueprint.Title.ToString().color(titleColors[(int)quest.State]); - public static string titleColored(this QuestObjective objective, BlueprintQuestObjective bp = null) { - var blueprint = objective?.Blueprint ?? bp; - var state = objective?.State ?? QuestObjectiveState.None; - var title = blueprint.Title.ToString(); - if (title.Length == 0) title = blueprint.ToString(); - if (blueprint.IsAddendum) - title = "Addendum: ".color(RGBA.white) + title; - if (blueprint.name.Contains("_Fail")) - return title.red(); - else - return title.color(titleColors[(int)state]); - } - public static string titleColored(this string title, QuestObjectiveState state) => title.color(titleColors[(int)state]); - public static string stateString(this Quest quest) => quest.State == QuestState.None ? "" : $"{quest.State}".stateColored(quest).bold(); - public static string stateString(this QuestObjective objective) => objective.State == QuestObjectiveState.None ? "" : $"{objective.State}".stateColored(objective).bold(); - } - - public class QuestEditor { - public static Settings Settings => Main.Settings; - public static bool ShowInactive => Settings.toggleIntrestingNPCsShowFalseConditions; - public static Player player => Game.Instance.Player; - private static bool[] selectedQuests = new bool[0]; - private static readonly Browser<UnitEntityData, UnitEntityData> ConditionsBrowser = new(); - - public static void ResetGUI() { } - - public static void OnGUI() { - if (!Main.IsInGame) return; - var quests = Game.Instance?.Player?.QuestBook.Quests.ToArray(); - if (quests == null) return; - selectedQuests = (selectedQuests.Length != quests.Length) ? new bool[quests.Length] : selectedQuests; - var index = 0; - var contentColor = GUI.contentColor; - Div(); - 10.space(); - using (HorizontalScope()) { - Toggle("Mark Interesting NPCs on Map", ref Settings.toggleShowInterestingNPCsOnLocalMap, 375.width()); - HelpLabel("This will change the color of NPC names on the highlight makers and change the color map markers to indicate that they have interesting or conditional interactions"); - } - using (HorizontalScope()) { - DisclosureToggle("Interesting NPCs in the local area".cyan(), ref Settings.toogleShowInterestingNPCsOnQuestTab); - 200.space(); - HelpLabel("Show a list of NPCs that may have quest objectives or other interesting features " + "(Warning: Spoilers)".yellow()); - } - if (Settings.toogleShowInterestingNPCsOnQuestTab) { - using (HorizontalScope()) { - 50.space(); - using (VerticalScope(GUI.skin.box)) { - //if (Game.Instance?.State?.Units.All is { } units) { - if (Game.Instance?.State?.Units is { } unitsPool) { - var units = Settings.toggleInterestingNPCsShowHidden ? unitsPool.All : unitsPool.ToList(); - ConditionsBrowser.OnGUI( - units.Where(u => u.InterestingnessCoefficent() >= 1), - () => units, - i => i, - u => u.CharacterName, - u => u.CharacterName, - () => { - Toggle("Show Inactive Conditions", ref Settings.toggleIntrestingNPCsShowFalseConditions); - if (ConditionsBrowser.ShowAll) { - 25.space(); - if (Toggle("Show other versions of NPCs", ref Settings.toggleInterestingNPCsShowHidden)) - ConditionsBrowser.ReloadData(); - } -#if DEBUG - 25.space(); - ActionButton("Reveal All On Map", RevealInterestingNPCs); -#endif - }, - (u, _) => { - var name = u.CharacterName; - var coefficient = u.InterestingnessCoefficent(); - if (coefficient > 0) - name = name.orange(); - else - name = name.grey(); - Label(name, 600.width()); - 175.space(); - Label($"Interestingness Coefficient: ".grey() + RichTextExtensions.Cyan(coefficient.ToString())); - 50.space(); - ReflectionTreeView.DetailToggle("", u.Parts.Parts); - }, - (u, _) => { - ReflectionTreeView.OnDetailGUI(u.Parts.Parts); - var entries = u.GetUnitInteractionConditions(); - var checkerEntries = entries.Where(e => e.HasConditins && (ShowInactive || e.IsActive())); - var conditions = - from entry in checkerEntries - from condition in entry.checker.Conditions - group (condition, entry) by condition.GetCaption() - into g - select g.Select(p => (p.condition, new object[] { p.entry.source } as IEnumerable<object>)) - .Aggregate((p, q) - => (p.condition, p.Item2.Concat(q.Item2)) - ); - var elementEntries = entries.Where(e => e.HasElements && (ShowInactive || e.IsActive())); - if (conditions.Any()) { - using (HorizontalScope()) { - 115.space(); - Label("Conditions".yellow()); - } - } - foreach (var entry in conditions) { - OnGUI(entry.condition, - string.Join(", ", entry.Item2.Select(source => source.ToString())), - 150 - ); - } - if (elementEntries.Any()) { - using (HorizontalScope()) { - 115.space(); - Label("Elements".yellow()); - } - } - foreach (var entry in elementEntries) { - foreach (var element in entry.elements.OrderBy(e => e.GetType().Name)) { - OnGUI(element, entry.source); - } - } - }, - 50, - false, - true, - 100, - 300, - "", - true); - } - } - } - } - Div(0,25); - using (HorizontalScope()) { - Label("Quests".cyan()); - } - var split = quests.GroupBy(q => q.State == QuestState.Completed).OrderBy(g => g.Key); - using (HorizontalScope()) { - Toggle("Hide Completed", ref Settings.toggleQuestHideCompleted); - 25.space(); - Toggle("Show Unrevealed Steps", ref Settings.toggleQuestsShowUnrevealedObjectives); - 25.space(); - Toggle("Inspect Quests and Objectives", ref Settings.toggleQuestInspector); - if (Settings.toggleQuestInspector) { - 25.space(); - ReflectionTreeView.DetailToggle("Inspect", selectedQuests, split, 0); - } - } - if (Settings.toggleQuestInspector) { - ReflectionTreeView.OnDetailGUI(selectedQuests); - } - foreach (var group in split) { - foreach (var quest in group.ToList()) { - if (Settings.toggleQuestHideCompleted && quest.State == QuestState.Completed && selectedQuests[index]) { - selectedQuests[index] = false; - } - if (!Settings.toggleQuestHideCompleted || quest.State != QuestState.Completed || selectedQuests[index]) { - using (HorizontalScope()) { - 50.space(); - Label(quest.Blueprint.Title.ToString().orange().bold(), Width(600)); - 50.space(); - DisclosureToggle(quest.stateString(), ref selectedQuests[index]); - if (Settings.toggleQuestInspector) - ReflectionTreeView.DetailToggle("Inspect", quest, quest, 0); - 50.space(); - Label(quest.Blueprint.Description.ToString().StripHTML().green()); - } - if (Settings.toggleQuestInspector) { - ReflectionTreeView.OnDetailGUI(quest); - } - if (selectedQuests[index]) { - var objectiveIndex = 0; - foreach (var questObjective in quest.Objectives) { - if (Settings.toggleQuestsShowUnrevealedObjectives || questObjective.IsRevealed()) { - if (questObjective.ParentObjective == null) { - Div(100, 25); - using (HorizontalScope(AutoWidth())) { - Space(50); - objectiveIndex = questObjective.Order == 0 ? objectiveIndex + 1 : questObjective.Order; - Label($"{objectiveIndex}", Width(50)); - Label(questObjective.titleColored(), Width(600)); - 25.space(); - Label(questObjective.stateString(), Width(150)); - if (Settings.toggleQuestInspector) - ReflectionTreeView.DetailToggle("Inspect", questObjective, questObjective, 0); - Space(25); - using (HorizontalScope(300)) { - Space(0); - if (questObjective.State == QuestObjectiveState.None && quest.State == QuestState.Started) { - ActionButton("Start", () => { questObjective.Start(); }, Width(150)); - } - else if (questObjective.State == QuestObjectiveState.Started) { - ActionButton(questObjective.Blueprint.IsFinishParent ? "Finish" : "Complete", () => { - questObjective.Complete(); - }, Width(150)); - if (questObjective.Blueprint.AutoFailDays > 0) { - ActionButton("Reset Time", () => { - Traverse.Create(questObjective).Field("m_ObjectiveStartTime").SetValue(Game.Instance.Player.GameTime); - }, Width(150)); - } - } - else if (questObjective.State == QuestObjectiveState.Failed && (questObjective.Blueprint.IsFinishParent || quest.State == QuestState.Started)) { - ActionButton("Restart", () => { - if (quest.State == QuestState.Completed || quest.State == QuestState.Failed) { - Traverse.Create(quest).Field("m_State").SetValue(QuestState.Started); - } - questObjective.Reset(); - questObjective.Start(); - }, Width(50)); - } - } - DrawTeleports(questObjective); - Label(questObjective.Blueprint.Description.ToString().StripHTML().green(), 1000.width()); - Label("", AutoWidth()); - } - if (Settings.toggleQuestInspector) { - ReflectionTreeView.OnDetailGUI(questObjective); - } - if (questObjective.State == QuestObjectiveState.Started) { - var childIndex = 0; - foreach (var childObjective in quest.Objectives) { - if (Settings.toggleQuestsShowUnrevealedObjectives || childObjective.IsRevealed()) { - if (childObjective.ParentObjective == questObjective) { - Div(100, 25); - using (HorizontalScope(AutoWidth())) { - Space(100); - childIndex = childObjective.Order == 0 ? childIndex + 1 : questObjective.Order; - Label($"{childIndex}", Width(50)); - Space(10); - Label(childObjective.titleColored(), Width(600)); - 25.space(); - Label(childObjective.stateString(), Width(150)); - if (Settings.toggleQuestInspector) - ReflectionTreeView.DetailToggle("Inspect", questObjective, questObjective, 0); - Space(25); - using (HorizontalScope(300)) { - if (childObjective.State == QuestObjectiveState.None) { - ActionButton("Start", () => { childObjective.Start(); }, Width(150)); - } - else if (childObjective.State == QuestObjectiveState.Started) { - ActionButton(childObjective.Blueprint.IsFinishParent ? "Complete (Final)" : "Complete", () => { - childObjective.Complete(); - }, Width(150)); - } - else 153.space(); - } - DrawTeleports(childObjective); - Label(childObjective.Blueprint.Description.ToString().StripHTML().green(), 1000.width()); - Label("", AutoWidth()); - } - if (Settings.toggleQuestInspector) { - ReflectionTreeView.OnDetailGUI(childObjective); - } - } - } - } - } - } - } - } - } - Div(50, 25); - } - index++; - } - } - Space(25); - } - public static void DrawTeleports(QuestObjective objective) { - using (HorizontalScope(MaxWidth(850))) { - var areas = objective.Blueprint.Areas; - var locations = objective.Blueprint.Locations; - if (areas.Count > 0 || locations.Count > 0) { - if (locations.Count > 0) { - Label("TP"); - 25.space(); - using (VerticalScope(MaxWidth(600))) { - foreach (var location in locations) { - var bp = location.GetBlueprint(); - if (bp != null) - ActionButton(bp.name.yellow(), () => Teleport.To(location)); - } - } - } -#if false - if (areas.Count > 0) { - Label(" Area"); - Space(25); - foreach (var area in areas) { - ActionButton(area.name.yellow(), () => Teleport.To(area)); - } - } -#endif - } - } - } - public static void OnGUI(Element element, object source, int indent = 150, bool forceShow = false) { - if (!element.IsActive() - && source is not ActionsHolder // kludge again for Actions holder for Lathimas - && !Settings.toggleIntrestingNPCsShowFalseConditions - && !forceShow - ) return; - using (HorizontalScope()) { - Space(indent); - switch (element) { - case ObjectiveStatus objectiveStatus: - OnGUI(objectiveStatus, source); - break; - case QuestStatus questStatus: - OnGUI(questStatus, source); - break; - case EtudeStatus etudeStatus: - OnGUI(etudeStatus, source); - break; - case Conditional conditional: - OnGUI(conditional, source); - break; - case Condition condition: - OnGUI(condition, source); - break; - default: - OnOtherElementGUI(element, source); - break; - } - } - } - public static void OnGUI(ConditionsChecker checker, object source, int indent = 150, bool forceShow = false) { - foreach (var condition in checker.Conditions.OrderBy(c => c.GetType().Name)) { - OnGUI(condition, source, indent, forceShow); - } - } - public static void OnGUI(Conditional conditional, object source) { - if (conditional.ConditionsChecker.Conditions.Any()) { - Label("Conditional:".cyan(), 150.width()); - //Label(string.Join(", ", conditional.ConditionsChecker.Conditions.Select(c => c.GetCaption()))); - Label(conditional.Comment, 375.width()); - using (VerticalScope()) { - OnGUI(conditional.ConditionsChecker, source, 0, true); - } - } - } - public static void OnGUI(QuestStatus questStatus, object source) { - Label("Quest Status: ".cyan(), 150.width()); - var quest = questStatus.Quest; - var state = GameHelper.Quests.GetQuestState(quest); - var title = $"{quest.Title.ToString().orange().bold()}"; - Label(title, 500.width()); - 22.space(); - using (VerticalScope()) { - HelpLabel(quest.Description); - Label($"status: ".cyan() + state.ToString()); - Label("condition: ".cyan() + questStatus.CaptionString()); - Label("source: ".cyan() + source.ToString().yellow()); - } - } - public static void OnGUI(ObjectiveStatus objectiveStatus, object source) { - Label("Objective Status: ".cyan(), 150.width()); - - var objectiveBP = objectiveStatus.QuestObjective; - var objective = Game.Instance.Player.QuestBook.GetObjective(objectiveBP); - var quest = objectiveBP.Quest; - var state = objective?.State ?? QuestObjectiveState.None; - var title = $"{quest.Title.ToString().orange().bold()} : {objective.titleColored(objectiveBP)}"; - Label(title, 500.width()); - 22.space(); - using (VerticalScope()) { - HelpLabel(objectiveBP.Description); - Label($"status: ".cyan() + state.ToString().titleColored(state)); - Label("condition: ".cyan() + objectiveStatus.CaptionString()); - Label("source: ".cyan() + source.ToString().yellow()); - } - } - public static void OnGUI(EtudeStatus etudeStatus, object source) { - Label("Etude Status: ".cyan(), 150.width()); - var etudeBP = etudeStatus.Etude; - Label(etudeBP.name.orange(), 500.width()); - var etudeState = Game.Instance.Player.EtudesSystem.GetSavedState(etudeBP); - var debugInfo = Game.Instance.Player.EtudesSystem.GetDebugInfo(etudeBP); - 22.space(); - using (VerticalScope()) { - HelpLabel(debugInfo); - Label($"status: ".cyan() + etudeState.ToString()); - Label("condition: ".cyan() + etudeStatus.CaptionString()); - Label("source: ".cyan() + source.ToString().yellow()); - } - } - public static void OnGUI(Condition condition, object source) { - Label($"{condition.GetType().Name}:".cyan(), 150.width()); - Label(source.ToString().yellow(), 500.width()); - 22.space(); - using (VerticalScope()) { - Label("condition: ".cyan() + condition.CaptionString()); - } - } - public static void OnOtherElementGUI(Element element, object source) { - Label($"{element.GetType().Name}:".cyan(), 150.width()); - Label(source.ToString().yellow(), 500.width()); - 22.space(); - using (VerticalScope()) { - Label("caption: ".cyan() + element.CaptionString()); - } - } - } -} diff --git a/ToyBox/classes/Models/Settings+BulkSell.cs b/ToyBox/classes/Models/Settings+BulkSell.cs deleted file mode 100644 index 66db63de5..000000000 --- a/ToyBox/classes/Models/Settings+BulkSell.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Kingmaker.Enums.Damage; -using ModKit.Utility; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace ToyBox.classes.Models { - [Serializable] - public class BulkSellSettings { - public int weaponEnchantLevel = 1; - public int weaponStackSize = 0; - public int armorEnchantLevel = 1; - public int armorStackSize = 0; - public int shieldEnchantLevel = 1; - public int shieldStackSize = 0; - public int beltStackSize = 0; - public int cloakStackSize = 0; - public int ringStackSize = 0; - public int headStackSize = 0; - public int bracerStackSize = 0; - public int neckStackSize = 0; - public int potionStackSize = 50; - public int scrollStackSize = 50; - public int ingredientStackSize = 50; - - public SerializableDictionary<DamageRealityType, bool> damageReality = new(); - public SerializableDictionary<DamageAlignment, bool> damageAlignment = new(); - public SerializableDictionary<PhysicalDamageMaterial, bool> damageMaterial = new(); - public SerializableDictionary<DamageEnergyType, bool> damageEnergy = new(); - - public bool showWeaponEnergyTypes = false; - - public bool sellIngredients = true; - public bool sellPotions = true; - public bool sellScrolls = true; - - public bool sellUniqueWeapons = false; - public bool sellUniqueArmors = false; - public bool sellUniqueShields = false; - - public int maxAttributeBonusForBelt = 2; - public int maxAttributeBonusForHead = 2; - public int maxSaveBonusForCloaks = 2; - public int maxACBonusForRings = 2; - public int maxACBonusForBracers = 2; - public int maxACBonusForNeck = 2; - - public int globalModifier = 1; - - } -} diff --git a/ToyBox/classes/Models/Settings+Multiclass.cs b/ToyBox/classes/Models/Settings+Multiclass.cs deleted file mode 100644 index 689398af9..000000000 --- a/ToyBox/classes/Models/Settings+Multiclass.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright < 2021 > Narria (github user Cabarius) - License: MIT -using ModKit.Utility; -using System.Collections.Generic; -using Kingmaker.UnitLogic; -using ModKit; -using Kingmaker.Blueprints.Classes; -using Kingmaker.EntitySystem; -using Newtonsoft.Json; - -namespace ToyBox { - public class ArchetypeOptions : HashSet<string> { - public const string NoArchetype = "$NoArchetype"; - - public bool Contains(BlueprintArchetype arch) => Contains(arch == null ? NoArchetype : arch.HashKey()); - - public void Add(BlueprintArchetype arch) => Add(arch == null ? NoArchetype : arch.HashKey()); - public void AddExclusive(BlueprintArchetype arch) { Clear(); Add(arch); } - public void Remove(BlueprintArchetype arch) => Remove(arch == null ? NoArchetype : arch.HashKey()); - public override string ToString() { - var result = "{"; - foreach (var arch in this) result += " " + arch + ","; - result += "}"; - return result; - } - } - public class MulticlassOptions : SerializableDictionary<string, ArchetypeOptions> { - public const string CharGenKey = @"$CharacterGeneration"; - public static MulticlassOptions Get(UnitDescriptor ch) { - //Main.Log($"stack: {System.Environment.StackTrace}"); - MulticlassOptions options; - if (ch == null || ch.CharacterName == "Knight Commander" || ch.CharacterName == "Player Character") { - options = Main.Settings.multiclassSettings.GetValueOrDefault(CharGenKey, new MulticlassOptions()); - //Mod.Debug($"MulticlassOptions.Get - chargen - options: {options}"); - } - else { - if (ch.HashKey() == null) return null; - options = Main.Settings.perSave.multiclassSettings.GetValueOrDefault(ch.HashKey(), new MulticlassOptions()); - //Mod.Debug($"MulticlassOptions.Get - {ch.CharacterName} - set: {options}"); - } - return options; - } - public static bool CanSelectClassAsMulticlass(UnitDescriptor ch, BlueprintCharacterClass cl) { - if (!Main.IsInGame) return true; - if (ch == null) return true; - if (cl == null) return false; - var options = Get(ch); - var checkMythic = cl.IsMythic; - var foundIt = false; - var classCount = 0; - var selectedCount = 0; - foreach (var cd in ch.Progression.Classes) { - var charClass = cd.CharacterClass; - if (cd.CharacterClass.IsMythic == checkMythic) { - classCount += 1; - var contains = options.Contains(charClass); - if (contains) { - selectedCount += 1; - } - if (charClass == cl && !contains) - foundIt = true; - } - } - var result = !foundIt || (classCount - selectedCount > 1); - //Mod.Trace($"canSelect {cl.Name} - foundIt : {foundIt} count: {classCount} selected: {selectedCount} => {result}"); - return result; - } - public static void Set(UnitDescriptor ch, MulticlassOptions options) { - //modLogger.Log($"stack: {System.Environment.StackTrace}"); - if (ch == null || ch.CharacterName == "Knight Commander" || ch.CharacterName == "Player Character") - Main.Settings.multiclassSettings[CharGenKey] = options; - else { - if (ch.HashKey() == null) return; - Mod.Debug($"options: {options}"); - Main.Settings.perSave.multiclassSettings[ch.HashKey()] = options; - Mod.Trace($"multiclass options: {string.Join(" ", Main.Settings.perSave.multiclassSettings)}"); - Settings.SavePerSaveSettings(); - } - } - - public ArchetypeOptions ArchetypeOptions(BlueprintCharacterClass cl) => this.GetValueOrDefault(cl.HashKey(), new ArchetypeOptions()); - public void SetArchetypeOptions(BlueprintCharacterClass cl, ArchetypeOptions archOptions) => this[cl.HashKey()] = archOptions; - public bool Contains(BlueprintCharacterClass cl) => ContainsKey(cl.HashKey()); - public ArchetypeOptions Add(BlueprintCharacterClass cl) => this[cl.HashKey()] = new ArchetypeOptions(); - public void Remove(BlueprintCharacterClass cl) => Remove(cl.HashKey()); - public override string ToString() { - var result = base.ToString() + " {\n"; - foreach (var classEntry in this) { - result += $" {classEntry.Key} : {classEntry.Value}\n"; - } - result += "}"; - return result; - } - } -} diff --git a/ToyBox/classes/Models/Settings+UI.cs b/ToyBox/classes/Models/Settings+UI.cs deleted file mode 100644 index 7d7b1a13d..000000000 --- a/ToyBox/classes/Models/Settings+UI.cs +++ /dev/null @@ -1,70 +0,0 @@ -using ModKit; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using UnityEngine; - -namespace ToyBox { - public partial class SettingsUI { - public static string cultureSearchText = ""; - public static CultureInfo uiCulture; - public static List<CultureInfo> cultures = new(); - public static void OnGUI() { - UI.HStack("Settings", 1, - () => { - UI.ActionButton("Reset UI", () => Main.SetNeedsResetGameUI()); - 25.space(); - UI.Label("Tells the game to reset the in game UI.".green() + " Warning".yellow() + " Using this in dialog or the book will dismiss that dialog which may break progress so use with care".orange()); - }, - () => { - UI.Toggle("Enable Game Development Mode", ref Main.Settings.toggleDevopmentMode); - UI.Space(25); - UI.Label("This turns on the developer console which lets you access cheat commands, shows a FPS window (hide with F11), etc".green()); - }, - () => UI.Label(""), - () => UI.EnumGrid("Log Level", ref Main.Settings.loggingLevel, UI.AutoWidth()), - () => UI.Label(""), - () => UI.Toggle("Strip HTML (colors) from Native Console", ref Main.Settings.stripHtmlTagsFromNativeConsole), -#if DEBUG - () => UI.Toggle("Strip HTML (colors) from Logs Tab in Unity Mod Manager", ref Main.Settings.stripHtmlTagsFromUMMLogsTab), -#endif - () => UI.Toggle("Display guids in most tooltips, use shift + left click on items/abilities to copy guid to clipboard", ref Main.Settings.toggleGuidsClipboard), - () => { } - ); -#if DEBUG - UI.Div(0, 25); - UI.HStack("Localizaton", 1, - () => { - if (Event.current.type != EventType.Repaint) { - uiCulture = CultureInfo.GetCultureInfo(Mod.ModKitSettings.uiCultureCode); - if (Main.Settings.onlyShowLanguagesWithFiles) { - cultures = LocalizationManager.getLanguagesWithFile().Select((code, index) => CultureInfo.GetCultureInfo(code)).OrderBy(ci => ci.DisplayName).ToList(); - } - else { - cultures = CultureInfo.GetCultures(CultureTypes.AllCultures).OrderBy(ci => ci.DisplayName).ToList(); - } - } - using (UI.VerticalScope()) { - using (UI.HorizontalScope()) { - UI.Label("Current Cultrue".cyan(), UI.Width(275)); - UI.Space(25); - UI.Label($"{uiCulture.DisplayName}({uiCulture.Name})".orange()); - UI.Space(25); - UI.ActionButton("Export current locale to file".cyan(), () => LocalizationManager.Export()); - UI.Space(25); - UI.Toggle("Only show languages with existing localization files", ref Main.Settings.onlyShowLanguagesWithFiles); - UI.Space(25); - UI.LinkButton("Open the Localization Guide", "https://github.com/cabarius/ToyBox/wiki/Localization-Guide"); - } - if (UI.GridPicker<CultureInfo>("Culture", ref uiCulture, cultures, null, ci => ci.DisplayName, ref cultureSearchText, 8, UI.rarityButtonStyle, UI.Width(UI.ummWidth - 350))) { - Mod.ModKitSettings.uiCultureCode = uiCulture.Name; - LocalizationManager.Update(); - } - } - }, - () => { } - ); -#endif - } - } -} diff --git a/ToyBox/classes/Models/Settings.cs b/ToyBox/classes/Models/Settings.cs deleted file mode 100644 index f51751cdd..000000000 --- a/ToyBox/classes/Models/Settings.cs +++ /dev/null @@ -1,505 +0,0 @@ -// Copyright < 2021 > Narria (github user Cabarius) - License: MIT -using Kingmaker; -using Kingmaker.EntitySystem; -using Kingmaker.EntitySystem.Persistence; -using ModKit; -using ModKit.Utility; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using ToyBox.classes.Models; -using UnityEngine; -using UnityModManagerNet; - -namespace ToyBox { - public class PerSaveSettings : EntityPart { - public const string ID = "ToyBox.PerSaveSettings"; - public delegate void Changed(PerSaveSettings perSave); - [JsonIgnore] - public static Changed observers; - - // schema for storing multiclass settings - // Dictionary<CharacterName, - // Dictionary<ClassID, HashSet<ArchetypeIDs> - // For character gen config we use the following special key: - [JsonProperty] - public Dictionary<string, MulticlassOptions> multiclassSettings = new(); - - // This is the set of classes that each char has leveled up under multi-class. They will be excluded from char level calculations - [JsonProperty] - public Dictionary<string, HashSet<string>> excludeClassesFromCharLevelSets = new(); - - // This is the scaling modifier which is applied to the visual model of each character - [JsonProperty] - public Dictionary<string, float> characterModelSizeMultiplier = new(); - - // Dictionary of Name/IsLegendaryHero for configuration per party member - [JsonProperty] - public Dictionary<string, bool> charIsLegendaryHero = new(); - } - - public class Settings : UnityModManager.ModSettings { - private static PerSaveSettings cachedPerSave = null; - public const string PerSaveKey = "ToyBox"; - public static void ClearCachedPerSave() => cachedPerSave = null; - public static void ReloadPerSaveSettings() { - var player = Game.Instance?.Player; - if (player == null || Game.Instance.SaveManager.CurrentState == SaveManager.State.Loading) return; - Mod.Debug($"reloading per save settings from Player.SettingsList[{PerSaveKey}]"); - if (player.SettingsList.TryGetValue(PerSaveKey, out var obj) && obj is string json) { - try { - cachedPerSave = JsonConvert.DeserializeObject<PerSaveSettings>(json); - Mod.Debug($"read successfully from Player.SettingsList[{PerSaveKey}]"); - } - catch (Exception e) { - Mod.Error($"failed to read from Player.SettingsList[{PerSaveKey}]"); - Mod.Error(e); - } - } - if (cachedPerSave == null) { - Mod.Warn("per save settings not found, creating new..."); - cachedPerSave = new PerSaveSettings { - }; - SavePerSaveSettings(); - } - } - public static void SavePerSaveSettings() { - var player = Game.Instance?.Player; - if (player == null) return; - if (cachedPerSave == null) - ReloadPerSaveSettings(); - var json = JsonConvert.SerializeObject(cachedPerSave); - player.SettingsList[PerSaveKey] = json; - try { - Mod.Debug($"saved to Player.SettingsList[{PerSaveKey}]"); - Mod.Trace($"multiclass options: {string.Join(" ", cachedPerSave.multiclassSettings)}"); - if (PerSaveSettings.observers is MulticastDelegate mcdel) { - var doomed = new List<PerSaveSettings.Changed>(); - foreach (var inv in mcdel.GetInvocationList()) { - if (inv.Target == null && inv is PerSaveSettings.Changed changed) - doomed.Add(changed); - } - foreach (var del in doomed) { - Mod.Debug("removing observer: {del} from PerSaveSettings"); - PerSaveSettings.observers -= del; - } - } - if (cachedPerSave) - PerSaveSettings.observers?.Invoke(cachedPerSave); - } - catch (Exception e) { - Mod.Error(e); - } - } - public PerSaveSettings perSave { - get { - if (cachedPerSave != null) return cachedPerSave; - ReloadPerSaveSettings(); - return cachedPerSave; - } - } - - // Main - public int selectedTab = 0; - public int increment = 10000; - public int alignmentIncrement = 5; - - // Quality of Life - public bool toggleContinueAudioOnLostFocus = false; - public bool highlightObjectsToggle = false; - public bool toggleHighlightCopyableScrolls = false; - public bool toggleShiftClickToUseInventorySlot = false; - public bool toggleShiftClickToFastTransfer = false; - public bool toggleShowAcronymsInSpellAndActionSlots = false; - public bool toggleWidenActionBarGroups = false; - public bool toggleGameOverFixLeeerrroooooyJenkins = false; - public bool enableLoadWithMissingBlueprints = false; - public bool toggleExpandedPartyView = false; - public bool togglePuzzleRelief = false; - public bool toggleZoomableLocalMaps = false; - public float zoomableLocalMapScrollSpeedMultiplier = 4; - public bool toogleShowInterestingNPCsOnQuestTab = false; - public bool toggleShowInterestingNPCsOnLocalMap = false; - public bool toggleShowQuestMarkersOnLocalMap = false; - - // Camera - public bool toggleZoomOnAllMaps = false; - public bool toggleRotateOnAllMaps = false; - public bool toggleScrollOnAllMaps = false; - public bool toggleCameraPitch = false; - public bool toggleCameraElevation = false; - public bool toggleFreeCamera = false; - public bool toggleInvertXAxis = false; - public bool toggleInvertKeyboardXAxis = false; - public bool toggleInvertYAxis = false; - public bool toggleUseAltMouseWheelToAdjustClipPlane = false; - public float fovMultiplier = 1; - public float AdjustedFovMultiplier => Math.Max(fovMultiplier, toggleZoomableLocalMaps ? 1.25f : 0.4f); - public float fovMultiplierCutScenes = 1; - public float fovMultiplierMax = 1.25f; - - // Tweaks - public int kineticistBurnReduction = 0; - public bool toggleSpontaneousCopyScrolls = false; - public bool toggleInstantEvent = false; - public bool toggleInfiniteAbilities = false; - public bool toggleInfiniteSpellCasts = false; - public bool toggleInfiniteSkillpoints = false; - public bool toggleInstantCooldown = false; - public bool toggleUnlimitedActionsPerTurn = false; - public bool toggleEquipmentRestrictions = false; - public bool toggleIgnoreMaxDexterity = false; - public bool toggleIgnoreArmorChecksPenalty = false; - public bool toggleIgnoreSpeedReduction = false; - public bool toggleIgnoreSpellFailure = false; - public bool togglePartyNegativeLevelImmunity = false; - public bool togglePartyAbilityDamageImmunity = false; - public bool toggleDialogRestrictions = false; - public bool toggleDialogRestrictionsMythic = false; - public bool toggleDialogRestrictionsEverything = false; - public bool toggleNoFriendlyFireForAOE = false; - public bool toggleIgnoreSettlementRestrictions = false; - public bool toggleMoveSpeedAsOne = false; - public bool toggleNoFogOfWar = false; - public bool toggleRestoreSpellsAbilitiesAfterCombat = false; - public bool toggleRechargeItemsAfterCombat = false; - public bool toggleInstantRestAfterCombat = false; - public bool toggleShowAllPartyPortraits = false; // TODO - port this - public bool toggleAccessRemoteCharacters = false; - public bool toggleInfiniteItems = false; - public bool toggleMetamagicIsFree = false; - public bool toggleMaterialComponent = false; - public bool toggleAutomaticallyLoadLastSave = false; - public bool toggleBlockUnrecruit = false; - public bool toggleAllowAchievementsDuringModdedGame = false; - public bool toggleForceTutorialsToHonorSettings = false; - public bool toggleAllowAnyGenderRomance = false; - public bool toggleMultipleRomance = false; - public bool toggleFriendshipIsMagic = false; - public bool toggleReplaceModelMenu = false; - public bool toggleSpiderBegone = false; - public bool toggleVescavorsBegone = false; - public bool toggleRetrieversBegone = false; - public bool toggleDeraknisBegone = false; - public bool toggleDeskariBegone = false; - public bool togglAutoEquipConsumables = false; - public bool toggleInstantChangeParty = false; - public bool toggleExtendHexes = false; - public bool toggleAllowAllActivatable = false; - public bool toggleKineticistGatherPower = false; - public bool toggleAlwaysAllowSpellCombat = false; - public bool toggleInstantPartyChange = false; - public bool toggleEnterCombatAutoRage = false; - public bool toggleEnterCombatAutoRageDemon = false; - public bool toggleEquipmentNoWeight = false; - public bool toggleUseItemsDuringCombat = false; - public bool toggleTeleportKeysEnabled = false; - public bool toggleRespecRefundScrolls = false; - public bool toggleAlignmentFix = false; - public bool togglePreventAlignmentChanges = false; - public bool toggleBrutalUnfair = false; - public bool highlightHiddenObjects = false; - public bool highlightHiddenObjectsInFog = false; - public bool toggleAttacksofOpportunity = false; - public bool toggleMakePetsRidable = false; - public bool toggleRideAnything = false; - public bool toggleUnlimitedStatModifierStacking = false; - public bool togglekillOnEngage = false; - public bool toggleDisableCorruption = false; - - // Loot - public bool toggleColorLootByRarity = false; - public bool toggleShowRarityTags = false; - public bool toggleSortByRarirtyFirst = true; - public bool UsingLootRarity => toggleColorLootByRarity || toggleShowRarityTags; - public RarityType minRarityToColor = 0; - public RarityType lootFilterIgnore = RarityType.None; - public RarityType lootFilterAutoSell = RarityType.None; - public bool toggleMassLootEverything = false; - public bool toggleLootAliveUnits = false; - public bool toggleOverrideLockedItems = false; - public bool toggleLootChecklistFilterFriendlies = false; - public bool toggleLootChecklistFilterBlueprint = false; - public bool toggleLootChecklistFilterDescription = false; - public bool toggleShowCantEquipReasons = false; - public RarityType lootChecklistFilterRarity = RarityType.None; - public RarityType maxRarityToHide = RarityType.None; - public bool toggleCustomBulkSell = false; - public BulkSellSettings bulkSellSettings = new(); - - // Enhanced Inventory - public bool toggleEnhancedInventory = false; - public bool toggleDontClearSearchWhenLoseFocus = true; - public bool toggleEnhancedSpellbook = false; - public bool toggleSpellbookShowAllSpellsByDefault = false; - public bool toggleSpellbookShowMetamagicByDefault = true; - public bool toggleSpellbookShowLevelWhenViewingAllSpells = true; - public bool toggleSpellbookShowEmptyMetamagicCircles = true; - public bool toggleSpellbookAutoSwitchToMetamagicTab = false; - public bool toggleSpellbookSearchBarFocusWhenOpening = false; - public FilterCategories SearchFilterCategories = FilterCategories.Default; - public ItemSortCategories InventoryItemSorterOptions = ItemSortCategories.Default; - public InventorySearchCriteria InventorySearchCriteria = InventorySearchCriteria.Default; - public SpellbookSearchCriteria SpellbookSearchCriteria = SpellbookSearchCriteria.Default; - - // Crusade - public bool toggleInfiniteArmyRerolls = false; - public bool toggleLargeArmies = false; - public bool toggleCrusadeFlagsStayGreen = false; - public bool toggleIgnoreStartTaskRestrictions = false; - public bool toggleTaskNoResourcesCost = false; - public bool toggleAddNewUnitsAsMercenaries = false; - public float postBattleSummonMultiplier = 1; - public float recruitmentCost = 1; - public float recruitmentMultiplier = 1; - public float armyExperienceMultiplier = 1; - public float playerLeaderPowerMultiplier = 1; - public float enemyLeaderPowerMultiplier = 1; - public float kingdomTaskResolutionLengthMultiplier = 0; - public bool toggleIgnoreEventSolutionRestrictions = false; - public bool toggleHideEventSolutionRestrictionsPreview = false; - public int unitCount = 100; - - // Settlement - public bool toggleIgnoreBuildingClassRestrictions = false; - public bool toggleIgnoreBuildingAdjanceyRestrictions = false; - - // selectors - public UnitSelectType noAttacksOfOpportunitySelection = UnitSelectType.Off; - public UnitSelectType allowMovementThroughSelection = UnitSelectType.Off; - public float collisionRadiusMultiplier = 1; - - // char creation - public bool toggleAllRaceCustomizations = false; - public bool toggleIgnoreGenderRestrictions = false; - - // level up - public bool toggleNoLevelUpRestrictions = false; - public bool toggleFullHitdiceEachLevel = false; - public bool toggleIgnoreClassRestrictions = false; - public bool toggleIgnoreFeatRestrictions = false; - public bool toggleAllowCompanionsToBecomeMythic = false; - public bool toggleAllowMythicPets = false; - public bool toggleIgnorePrerequisites = false; - public bool toggleIgnoreCasterTypeSpellLevel = false; - public bool toggleIgnoreForbiddenArchetype = false; - public bool toggleIgnorePrerequisiteStatValue = false; - public bool toggleIgnorePrerequisiteClassLevel = false; - public bool toggleIgnoreAlignmentWhenChoosingClass = false; - public bool toggleIgnoreFeaturePrerequisitesWhenChoosingClass = false; // TODO - implement - public bool toggleIgnoreForbiddenFeatures = false; - public bool toggleIgnoreFeaturePrerequisites = false; - public bool toggleIgnoreFeatureListPrerequisites = false; - public bool toggleFeaturesIgnorePrerequisites = false; - public bool toggleSkipSpellSelection = false; - public bool toggleOptionalFeatSelection = false; - public bool toggleUniversalSpellbookd = false; - public bool toggleUncappedCasterLevel = false; - public bool toggleContinousLevelCap = false; - public bool toggleExponentialLevelCap = false; - public bool toggleFeatureMultiplierCompanions = false; - - // Multipliers - public int featsMultiplier = 1; - public int encumberanceMultiplier = 1; - public int encumberanceMultiplierPartyOnly = 1; - public float experienceMultiplier = 1; - public float experienceMultiplierCombat = 1; - public float experienceMultiplierQuests = 1; - public float experienceMultiplierSkillChecks = 1; - public float experienceMultiplierTraps = 1; - public bool useCombatExpSlider = false; - public bool useQuestsExpSlider = false; - public bool useSkillChecksExpSlider = false; - public bool useTrapsExpSlider = false; - public float fowMultiplier = 1; - public float moneyMultiplier = 1; - public float vendorSellPriceMultiplier = 1; - public float vendorBuyPriceMultiplier = 1; - public float fatigueHoursModifierMultiplier = 1; - public float spellsPerDayMultiplier = 1; - public float memorizedSpellsMultiplier = 1; - public float partyMovementSpeedMultiplier = 1; - public float travelSpeedMultiplier = 1; - public int characterCreationAbilityPointsMax = 18; - public int characterCreationAbilityPointsMin = 7; - public int characterCreationAbilityPointsPlayer = 25; - public int characterCreationAbilityPointsMerc = 25; - public bool characterCreationAbilityPointsOverrideGameMinimums = false; - public float companionCostMultiplier = 1; - public float kingdomBuildingTimeModifier = 0; - public float enemyBaseHitPointsMultiplier = 1; - public float buffDurationMultiplierValue = 1; - public float timeScaleMultiplier = 1; - public float alternateTimeScaleMultiplier = 3; - public bool useAlternateTimeScaleMultiplier = false; - public float arcanistSpellslotMultiplier = 1; - public float brutalDifficultyMultiplier = 1; - public float turnBasedCombatStartDelay = 4; - - // Dice Rolls - public UnitSelectType allAttacksHit = UnitSelectType.Off; - public UnitSelectType allHitsCritical = UnitSelectType.Off; - public UnitSelectType rollWithAdvantage = UnitSelectType.Off; - public UnitSelectType rollWithDisadvantage = UnitSelectType.Off; - public UnitSelectType alwaysRoll20 = UnitSelectType.Off; - public UnitSelectType alwaysRoll20OutOfCombat = UnitSelectType.Off; - public UnitSelectType neverRoll20 = UnitSelectType.Off; - public UnitSelectType alwaysRoll1 = UnitSelectType.Off; - public UnitSelectType neverRoll1 = UnitSelectType.Off; - public UnitSelectType roll20Initiative = UnitSelectType.Off; - public UnitSelectType roll1Initiative = UnitSelectType.Off; - public UnitSelectType take10always = UnitSelectType.Off; - public UnitSelectType take10minimum = UnitSelectType.Off; - public UnitSelectType skillsTake10 = UnitSelectType.Off; - public UnitSelectType skillsTake20 = UnitSelectType.Off; - - // Summons - public bool toggleMakeSummmonsControllable = false; - public UnitSelectType summonTweakTarget1 = UnitSelectType.Off; - public float summonDurationMultiplier1 = 1; - public float summonLevelModifier1 = 0; - public UnitSelectType summonTweakTarget2 = UnitSelectType.Off; - public float summonDurationMultiplier2 = 1; - public float summonLevelModifier2 = 0; - - // Key Bindings - //public SerializableDictionary<string, KeyCode> - - public KeyCode teleportMainHotKey = KeyCode.Comma; - public KeyCode teleportSelectedHotKey = KeyCode.Period; - public KeyCode teleportPartyHotKey = KeyCode.Slash; - - // Party Editor - public int selectedPartyFilter = 0; - - // Blueprint Browser - public int searchLimit = 100; - public int selectedBPTypeFilter = 1; - public string searchText = ""; - public bool searchDescriptions = true; - public bool showAttributes = false; - public bool showAssetIDs = false; - public bool showComponents = false; - public bool showElements = false; - public bool showDivisions = true; - public bool showFromAllSpellbooks = false; - public bool showDisplayAndInternalNames = false; - public bool factEditorShowInspector = true; - - // Enchantment (Sandal) - public string searchTextEnchantments = ""; - public bool showRatingForEnchantmentInventoryItems = true; - - // Dialog & Previews (Dialogs, Events ,etc) - public bool previewDialogResults = false; - public bool previewAlignmentRestrictedDialog = false; - public bool previewRandomEncounters = false; - public bool previewDecreeResults = false; - public bool previewEventResults = false; - public bool previewRelicResults = false; - public bool toggleRemoteCompanionDialog = false; - public bool toggleExCompanionDialog = false; - public bool toggleRandomizeCueSelections = false; - public bool toggleShowAnswersForEachConditionalResponse = false; - public bool toggleShowAllAnswersForEachConditionalResponse = false; - public bool toggleShowDialogConditions = false; - public bool toggleMakePreviousAnswersMoreClear = false; - - // Etudes - public bool showEtudeComments = true; - - // Quests - public bool toggleQuestHideCompleted = true; - public bool toggleQuestsShowUnrevealedObjectives = false; - public bool toggleQuestInspector = false; - public bool toggleIntrestingNPCsShowFalseConditions = false; - public bool toggleInterestingNPCsShowHidden = false; - - // Multi-Class - public bool toggleMulticlass = false; // big switch - TODO - do we need this? - public bool toggleMultiArchetype = false; - public bool toggleMulticlassInGameUI = false; - public bool toggleMulticlassShowClassDescriptions = false; - public bool toggleAlwaysShowMigration = false; - public int selectedClassToConfigMulticlass = 0; - - // schema for storing multiclass settings - // Dictionary<CharacterName, - // Dictionary<ClassID, HashSet<ArchetypeIDs> - // For character gen config we use the following special key: - public SerializableDictionary<string, MulticlassOptions> multiclassSettings = new(); - - // This is the set of classes that each char has leveled up under multi-class. They will be excluded from char level calculations - public SerializableDictionary<string, HashSet<string>> excludeClassesFromCharLevelSets = new(); - - // Dictionary of Name/IsLegendaryHero for configuration per party member - public SerializableDictionary<string, bool> charIsLegendaryHero = new(); - - public Multiclass.ProgressionPolicy multiclassHitPointPolicy = 0; - public Multiclass.ProgressionPolicy multiclassSavingThrowPolicy = 0; - public Multiclass.ProgressionPolicy multiclassBABPolicy = 0; - public Multiclass.ProgressionPolicy multiclassSkillPointPolicy = 0; - - - public bool toggleTakeHighestHitDie = true; - public bool toggleTakeHighestSkillPoints = true; - public bool toggleTakeHighestBAB = true; - public bool toggleTakeHighestSaveByRecalculation = true; - public bool toggleTakeHighestSaveByAlways = false; - public bool toggleRecalculateCasterLevelOnLevelingUp = true; - public bool toggleRestrictCasterLevelToCharacterLevel = true; - public bool toggleRestrictCasterLevelToCharacterLevelTemporary = false; - public bool toggleRestrictClassLevelForPrerequisitesToCharacterLevel = true; - public bool toggleFixFavoredClassHP = false; - public bool toggleAlwaysReceiveFavoredClassHPExceptPrestige = true; - public bool toggleAlwaysReceiveFavoredClassHP = false; - - // these are useful and belong in Char Generation - public bool toggleIgnoreAlignmentRestrictionCharGen = false; // was called toggleIgnoreAlignmentRestriction in Multi-Class mod - public bool toggleIgnoreAttributeCap = false; - public bool toggleIgnoreAttributePointsRemainingChargen = false; - - // these are useful and belong in LevelUp - public bool toggleLockCharacterLevel = false; - public bool toggleIgnoreAttributePointsRemaining = false; - public bool toggleIgnoreSkillCap = false; - public bool toggleIgnoreSkillPointsRemaining = false; - - // Some of these look redundant. It might be nice to add the fine grain configuration but part of the philosphy of ToyBox is to avoid too much kitchen sink options. I would like to focus and simplify this. Maybe see if there is a way to unify these into some broader groupings like I did in Cheap Tricks for patches that adopted CheckUnitEntityData (Off, You, Party, Enemies, etc) - // public bool toggleIgnoreClassAndFeatRestrictions = false; - public HashSet<string> ignoredPrerequisiteSet = new(); // adding this granularity might be nice - - public bool toggleIgnoreAbilityAlignmentRestriction = false; - public bool toggleIgnoreAbilityAnyRestriction = false; - public bool toggleIgnoreAolityCasterCheckers = false; - public HashSet<string> ignoredAbilityCasterCheckerSet = new(); - public bool toggleIgnoreActivatableAbilityRestrictions = false; - public HashSet<string> ignoredActivatableAbilityRestrictionSet = new(); - public bool toggleIgnoreEquipmentRestrictions = false; - public HashSet<string> ignoredEquipmentRestrictionSet = new(); - public bool toggleIgnoreBuildingRestrictions = false; - public HashSet<string> ignoredBuildingRestrictionSet = new(); - public HashSet<string> buffsToIgnoreForDurationMultiplier = new(SettingsDefaults.DefaultBuffsToIgnoreForDurationMultiplier); - - // Development - public LogLevel loggingLevel = LogLevel.Info; - public bool stripHtmlTagsFromUMMLogsTab = false; - public bool stripHtmlTagsFromNativeConsole = true; - public bool toggleShowDebugInfo = true; - public bool toggleDevopmentMode = false; - public bool toggleUberLoggerForwardPrefix = false; - public bool toggleGuidsClipboard = true; - public bool onlyShowLanguagesWithFiles = true; - - // Deprecated - private bool toggleNoLevelUpRestirctions = false; // deprecated - internal bool toggleSpellbookAbilityAlignmentChecks = false; - private bool hideCompleted = true; - - // Save - public override void Save(UnityModManager.ModEntry modEntry) => Save(this, modEntry); - - } -} - diff --git a/ToyBox/classes/MonkeyPatchin/BagOfPatches/ActionBar.cs b/ToyBox/classes/MonkeyPatchin/BagOfPatches/ActionBar.cs deleted file mode 100644 index a784f946f..000000000 --- a/ToyBox/classes/MonkeyPatchin/BagOfPatches/ActionBar.cs +++ /dev/null @@ -1,107 +0,0 @@ -using HarmonyLib; -using Kingmaker; -using Kingmaker.UI.MVVM._PCView.ActionBar; -using Kingmaker.UI.MVVM._VM.Tooltip.Templates; -using Kingmaker.UI.UnitSettings; -using ModKit; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using TMPro; -using UnityEngine; -using UnityEngine.UI.Extensions; - -namespace ToyBox.BagOfPatches { - internal static class ActionBar { - public static Settings settings = Main.Settings; - public static Player player = Game.Instance.Player; - - - [HarmonyPatch(typeof(ActionBarGroupPCView), nameof(ActionBarGroupPCView.SetStatePosition))] - public static class ActionBarGroupPCViewSetStatePosition_Patch { - public static void Prefix(ActionBarGroupPCView __instance) { - if (!settings.toggleWidenActionBarGroups) return; - var isSpellGroup = __instance is ActionBarSpellGroupPCView; - var rectTransform = __instance.m_RectTransform; - var itemCount = __instance.m_SlotsList.Count(s => !s.ViewModel?.IsEmpty.Value ?? false); - var columnCount = Math.Max(5, (int)(0.85 * Math.Sqrt(itemCount))); - var rowCount = (int)(Math.Ceiling((float)itemCount / columnCount)); - if (isSpellGroup) { - //rowCount += 1; // nudge for spell group ??? TODO - why? - rowCount = Math.Max(rowCount, (int)Math.Ceiling((((__instance as ActionBarSpellGroupPCView).m_Levels.Count - 1) * 26) / 53f)) + 1; - } - var width = columnCount * 53f; - var xoffset = (columnCount - 5) * 53f; - var height = rowCount * 53f + 40 + (isSpellGroup ? 5 : 0); - var oldOffset = rectTransform.offsetMax; - var oldSize = rectTransform.sizeDelta; - //Mod.Debug($"ActionBarGroupPCViewSetStatePosition_Patch - itemCount:{itemCount} width:{oldSize.x} - > {width}"); - rectTransform.offsetMax = new Vector2(width, height); - rectTransform.sizeDelta = new Vector2(width, height); - } - } - - - [HarmonyPatch(typeof(ActionBarBaseSlotPCView), nameof(ActionBarBaseSlotPCView.BindViewImplementation))] - public static class ActionBarBaseSlotPCView_BindViewImplementation_Patch { - public static void Postfix(ActionBarBaseSlotPCView __instance) { - if (!settings.toggleShowAcronymsInSpellAndActionSlots) return; - var viewModel = __instance.ViewModel; - var icon = __instance.transform.Find("BackgroundIcon"); - var mechanicSlot = viewModel.MechanicActionBarSlot; - var name = mechanicSlot.GetTitle(); - switch (mechanicSlot) { - case MechanicActionBarSlotAbility abilitySlot: name = abilitySlot.GetTitle(); break; - case MechanicActionBarSlotActivableAbility activatableSlot: name = activatableSlot.GetTitle(); break; - case MechanicActionBarSlotGlobalMagicSpell globalMagicSpellSlot: name = globalMagicSpellSlot.GetTitle(); break; - case MechanicActionBarSlotItem itemSlot: name = itemSlot.GetTitle(); break; - case MechanicActionBarSlotSpell spellSlot: name = spellSlot.GetTitle(); break; - case MechanicActionBarSlotSpontaneusConvertedSpell convSpellSlot: name = convSpellSlot.GetTitle(); break; - } - name = name.StripHTML(); - if (name?.Length <= 0) return; - var title = string.Join("", name.Split(' ').Select(s => s[0]).Where(c => Char.IsLetter(c)).Take(4)); - //Mod.Debug($"mechanicSlot: {mechanicSlot} : {mechanicSlot.GetType()} - {name} => {title}"); - var acronym = __instance.transform.Find("BackgroundIcon/ActionBarAcronym-ToyBox"); - if (acronym == null) { - var count = __instance.transform.Find("BackgroundIcon/Count"); - acronym = UnityEngine.Object.Instantiate(count, icon.transform); - acronym.transform.SetSiblingIndex(4); - acronym.name = "ActionBarAcronym-ToyBox"; - } - var rectTransform = acronym.transform as RectTransform; - var len = title.Length; - rectTransform.anchorMin = new Vector2(.95f - 0.09f * Math.Max(0, 4 - len), 0.15f); // - 0.35f); - rectTransform.anchorMax = new Vector2(1.0f - 0.09f * Math.Max(0, 4 - len), 0.35f); // - 0.35f); - var percent = len <= 3 ? 100 : len < 4 ? 100 : len < 5 ? 83 : 75; - acronym.GetComponentInChildren<TextMeshProUGUI>().text = $"<size={percent}%>{title}</size>"; - acronym.gameObject.SetActive(true); - if (acronym.gameObject.GetComponent<UICornerCut>() == null) { - var cornerCut = acronym.gameObject.AddComponent<UICornerCut>(); - cornerCut?.gameObject?.SetActive(true); - } - } - } - } -} -#if false - rectTransform.anchorMin = new Vector2(.95f, .15f); - rectTransform.anchorMax = new Vector2(1.0f, .35f); - var prototypeText = __instance.gameObject.transform.parent.transform.Find("Background/Header/HeaderText"); - var text = GameObject.Instantiate(prototypeText, __instance.transform); - var mesh = text.GetComponent<TextMeshProUGUI>(); - - var mechanicSlot = viewModel.MechanicActionBarSlot; - var name = ""; - switch (mechanicSlot) { - case MechanicActionBarSlotAbility abilitySlot: name = abilitySlot.Ability.NameForAcronym; break; - case MechanicActionBarSlotActivableAbility activatableSlot: name = activatableSlot.ActivatableAbility.NameForAcronym; break; - case MechanicActionBarSlotGlobalMagicSpell globalMagicSpellSlot: name = globalMagicSpellSlot.SpellState.Blueprint.name; break; - case MechanicActionBarSlotItem itemSlot: name = itemSlot.Item.NameForAcronym; break; - case MechanicActionBarSlotSpell spellSlot: name = spellSlot.Spell.NameForAcronym; break; - case MechanicActionBarSlotSpontaneusConvertedSpell convSpellSlot: name = convSpellSlot.Spell.NameForAcronym; break; - } - string.Concat(name.Where(c => c >= 'A' && c <= 'Z')); -#endif diff --git a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Alignment.cs b/ToyBox/classes/MonkeyPatchin/BagOfPatches/Alignment.cs deleted file mode 100644 index 4d729f4e1..000000000 --- a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Alignment.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using HarmonyLib; -using UnityModManagerNet; -using Kingmaker; -using Kingmaker.Enums; -using Kingmaker.UnitLogic.Alignments; -using UnityEngine; -using Kingmaker.UnitLogic.FactLogic; -using Kingmaker.UnitLogic.Abilities.Components.CasterCheckers; -using ModKit; - -namespace ToyBox { - public static class AlignmentPatches { - public static Settings settings = Main.Settings; - public static Player player = Game.Instance.Player; - - [HarmonyPatch(typeof(UnitAlignment), nameof(UnitAlignment.GetDirection))] - private static class UnitAlignment_GetDirection_Patch { - private static void Postfix(UnitAlignment __instance, ref Vector2 __result, AlignmentShiftDirection direction) { - if (settings.toggleAlignmentFix) { - if (direction == AlignmentShiftDirection.NeutralGood) __result = new Vector2(0, 1); - if (direction == AlignmentShiftDirection.NeutralEvil) __result = new Vector2(0, -1); - if (direction == AlignmentShiftDirection.LawfulNeutral) __result = new Vector2(-1, 0); - if (direction == AlignmentShiftDirection.ChaoticNeutral) __result = new Vector2(1, 0); - } - } - } - [HarmonyPatch(typeof(UnitAlignment), nameof(UnitAlignment.Set), new Type[] { typeof(Alignment), typeof(bool) })] - private static class UnitAlignment_Set_Patch { - private static void Prefix(UnitAlignment __instance, ref Kingmaker.Enums.Alignment alignment) { - if (settings.togglePreventAlignmentChanges) { - if (__instance.m_Value != null) - alignment = (Kingmaker.Enums.Alignment)__instance.m_Value; - } - } - } - [HarmonyPatch(typeof(UnitAlignment), nameof(UnitAlignment.Shift), new Type[] { typeof(AlignmentShiftDirection), typeof(int), typeof(IAlignmentShiftProvider) })] - private static class UnitAlignment_Shift_Patch { - private static bool Prefix(UnitAlignment __instance, AlignmentShiftDirection direction, ref int value, IAlignmentShiftProvider provider) { - try { - if (settings.togglePreventAlignmentChanges) { - value = 0; - } - - if (settings.toggleAlignmentFix) { - if (value == 0) { - return false; - } - var vector = __instance.m_Vector; - var num = (float)value / __instance.Radius; - var directionVector = __instance.GetDirection(direction); - var newAlignment = __instance.m_Vector + directionVector * num; - if (newAlignment.magnitude > 1f) { - //Instead of normalizing towards true neutral, normalize opposite to the alignment vector - //to prevent sliding towards neutral - newAlignment -= (newAlignment.magnitude - newAlignment.normalized.magnitude) * directionVector; - } - if (direction == AlignmentShiftDirection.TrueNeutral && (Vector2.zero - __instance.m_Vector).magnitude < num) { - newAlignment = Vector2.zero; - } - __instance.SetVector(newAlignment); - __instance.UpdateValue(); - __instance.OnChanged(direction, vector, provider, true); - return false; - } - } - catch (Exception e) { - Mod.Error(e); - } - return true; - } - } - - [HarmonyPatch(typeof(ForbidSpellbookOnAlignmentDeviation), nameof(ForbidSpellbookOnAlignmentDeviation.CheckAlignment))] - private static class ForbidSpellbookOnAlignmentDeviation_CheckAlignment_Patch { - private static bool Prefix(ForbidSpellbookOnAlignmentDeviation __instance) { - if (settings.toggleSpellbookAbilityAlignmentChecks) { - __instance.Alignment = __instance.Owner.Alignment.ValueRaw.ToMask(); - } - return true; - } - } - [HarmonyPatch(typeof(AbilityCasterAlignment), nameof(AbilityCasterAlignment.IsCasterRestrictionPassed))] - private static class AbilityCasterAlignment_CheckAlignment_Patch { - private static void Postfix(ref bool __result) { - if (settings.toggleSpellbookAbilityAlignmentChecks) { - __result = true; - } - } - } - } -} \ No newline at end of file diff --git a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Camera.cs b/ToyBox/classes/MonkeyPatchin/BagOfPatches/Camera.cs deleted file mode 100644 index cf036e53b..000000000 --- a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Camera.cs +++ /dev/null @@ -1,289 +0,0 @@ -// borrowed shamelessly and enhanced from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/26, which is under the MIT License - -using HarmonyLib; -using JetBrains.Annotations; -using Kingmaker; -using Kingmaker.Blueprints.Items; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.GameModes; -using Kingmaker.Items; -using Kingmaker.UnitLogic; -using Kingmaker.UnitLogic.Abilities; -using Kingmaker.UnitLogic.Buffs; -using Kingmaker.UnitLogic.Buffs.Blueprints; -using Kingmaker.UnitLogic.Mechanics; -using Kingmaker.UnitLogic.Parts; -using Kingmaker.View; -using System; -using System.Linq; -using UnityEngine; -using UnityModManager = UnityModManagerNet.UnityModManager; -using Kingmaker.Settings; -using Kingmaker.Settings.Difficulty; -using ModKit; -using Kingmaker.Blueprints.Items.Ecnchantments; -using Kingmaker.Utility; -using System.Collections.Generic; -using CameraMode = Kingmaker.View.CameraMode; -using DG.Tweening; -using Kingmaker.Blueprints; -using Kingmaker.Blueprints.Root; -using Owlcat.Runtime.Visual.RenderPipeline; -using Owlcat.Runtime.Visual.RenderPipeline.RendererFeatures.OccludedObjectHighlighting; -using Kingmaker.Blueprints.Area; -using Kingmaker.UI.MVVM._PCView.ServiceWindows.LocalMap; -using Kingmaker.Visual.LocalMap; -using Kingmaker.UI.MVVM._VM.ServiceWindows.LocalMap; -using Kingmaker.UI; -using Kingmaker.Visual.Particles.ForcedCulling; -using Kingmaker.Visual.LocalMap; -using Kingmaker.UI.MVVM._VM.ServiceWindows.LocalMap.Utils; -using static Kingmaker.Visual.LocalMap.LocalMapRenderer; -using Kingmaker.UI.MVVM._VM.ServiceWindows.LocalMap.Markers; -using Kingmaker.UI.MVVM._PCView.ServiceWindows.LocalMap.Markers; -using Kingmaker.UnitLogic.Class.LevelUp.Actions; -using UnityEngine.EventSystems; -using Kingmaker.Designers.EventConditionActionSystem.Evaluators; - -using Kingmaker.Controllers.Clicks.Handlers; -using static ModKit.UI; -using Kingmaker.UI.MVVM._PCView.ServiceWindows.LocalMap.Markers; -using Kingmaker.UnitLogic.FactLogic; -using Kingmaker.UnitLogic.Interaction; -using static Kingmaker.UnitLogic.FactLogic.AddLocalMapMarker; -using static Kingmaker.UnitLogic.Interaction.SpawnerInteractionPart; -using Kingmaker.Designers.EventConditionActionSystem.Conditions; -using Kingmaker.Designers; -using Kingmaker.UI.MVVM._VM.ServiceWindows; -using UniRx; - -namespace ToyBox.BagOfPatches { - internal static class CameraPatches { - public static Settings settings = Main.Settings; - public static Player player = Game.Instance.Player; - private static float CameraElevation = 0f; - - [HarmonyPatch(typeof(CameraRig), nameof(CameraRig.Update))] - static class CameraRig_Update_Patch { - static void Postfix(CameraRig __instance, ref Vector3 ___m_TargetPosition) { - if (settings.toggleRotateOnAllMaps || Main.resetExtraCameraAngles) - __instance.TickRotate(); - if (settings.toggleZoomOnAllMaps) - __instance.CameraZoom.TickZoom(); - if (settings.toggleScrollOnAllMaps) { - __instance.TickScroll(); - //__instance.TickCameraDrag(); - //__instance.CameraDragToMove(); - } - } - } - - [HarmonyPatch(typeof(CameraZoom), nameof(CameraZoom.TickZoom))] - private static class CameraZoom_TickZoom_Patch { - private static bool firstCall = true; - private static float BaseFovMin => (settings.toggleZoomOnAllMaps || settings.toggleZoomableLocalMaps) ? 12 : 17.5f; - private static readonly float BaseFovMax = 30; - private static float FovMin => BaseFovMin / settings.fovMultiplier; - private static float FovMax => BaseFovMax * settings.AdjustedFovMultiplier; - private static bool Prefix(CameraZoom __instance, - Coroutine ___m_ZoomRoutine, - float ___m_Smooth, - ref float ___m_PlayerScrollPosition, - ref float ___m_ScrollPosition, - ref float ___m_SmoothScrollPosition, - Camera ___m_Camera, - float ___m_ZoomLenght) { - if (settings.fovMultiplier == 1 && !settings.toggleZoomableLocalMaps) return true; - if (!__instance.IsScrollBusy && Game.Instance.IsControllerMouse && (double)Input.GetAxis("Mouse ScrollWheel") != 0.0 && ((double)___m_Camera.fieldOfView > (double)FovMin || (double)Input.GetAxis("Mouse ScrollWheel") < 0.0)) { - if (settings.toggleUseAltMouseWheelToAdjustClipPlane && (Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.LeftShift))) { - var cameraRig = Game.Instance.UI.GetCameraRig(); - var highlightingFeature = OwlcatRenderPipeline.Asset.ScriptableRendererData.rendererFeatures.OfType<OccludedObjectHighlightingFeature>().Single<OccludedObjectHighlightingFeature>(); - if (Input.GetKey(KeyCode.LeftAlt)) { - highlightingFeature.DepthClip.NearCameraClipDistance += Input.GetAxis("Mouse ScrollWheel") * 5; - Mod.Debug($"highlightingFeature.DepthClip.NearCameraClipDistance: {highlightingFeature.DepthClip.NearCameraClipDistance}"); - } - if (Input.GetKey(KeyCode.LeftShift)) { - highlightingFeature.DepthClip.ClipTreshold += Input.GetAxis("Mouse ScrollWheel") / 5; - Mod.Debug($"highlightingFeature.DepthClip.ClipTreshold: {highlightingFeature.DepthClip.ClipTreshold}"); - //highlightingFeature.DepthClip.AlphaScale += Input.GetAxis("Mouse ScrollWheel"); - //highlightingFeature.DepthClip.NoiseTiling += Input.GetAxis("Mouse ScrollWheel"); - - } - return false; - } - ___m_PlayerScrollPosition += __instance.IsOutOfScreen ? 0.0f : Input.GetAxis("Mouse ScrollWheel"); - if ((double)___m_PlayerScrollPosition <= 0.0) - ___m_PlayerScrollPosition = 0.01f; - } - if ((double)___m_PlayerScrollPosition <= 0.0) { - ___m_PlayerScrollPosition = (float)(((double)FovMax - (double)FovMin) / 18.0); - } - ___m_ScrollPosition = ___m_PlayerScrollPosition; - ___m_SmoothScrollPosition = Mathf.Lerp(___m_SmoothScrollPosition, ___m_PlayerScrollPosition, Time.unscaledDeltaTime * ___m_Smooth); - ___m_Camera.fieldOfView = Mathf.Lerp(FovMax, FovMin, (float)((double)__instance.CurrentNormalizePosition * ((double)__instance.FovMax - (double)__instance.FovMin) / ((double)FovMax - (double)FovMin))); - ___m_PlayerScrollPosition = ___m_ScrollPosition; - return false; - } - } - - [HarmonyPatch(typeof(CameraRig), nameof(CameraRig.TickScroll))] - private static class CameraRig_TickScroll_Patch { - public static bool Prefix(CameraRig __instance, ref Vector3 ___m_TargetPosition) { - if (!settings.toggleCameraPitch && !settings.toggleCameraElevation && !Main.resetExtraCameraAngles && !settings.toggleFreeCamera) return true; - var isInlocalMap = Game.Instance?.RootUiContext.CurrentServiceWindow == ServiceWindowsType.LocalMap; - var dt = Mathf.Min(Time.unscaledDeltaTime, 0.1f); - if (__instance.m_ScrollRoutine != null && (double)Time.time > (double)__instance.m_ScrollRoutineEndsOn) { - __instance.StopCoroutine(__instance.m_ScrollRoutine); - __instance.m_ScrollRoutine = (Coroutine)null; - } - if (__instance.m_ScrollRoutine == null) { - var eulerAngles = __instance.transform.rotation.eulerAngles; - var scrollOffset = __instance.m_ScrollOffset; - if (eulerAngles.x > 180) - scrollOffset.y = -scrollOffset.y; - if (settings.toggleZoomableLocalMaps && isInlocalMap && scrollOffset.magnitude > 0) { - var frameRotation = LocalMapPatches.FrameRotation; - var zoom = LocalMapPatches.Zoom; - var newScrollOffset = Quaternion.AngleAxis(-frameRotation.z, Vector3.forward) * scrollOffset; - //Mod.Debug($"inMap: {scrollOffset} -> {newScrollOffset} angle: {frameRotation}"); - scrollOffset = newScrollOffset * settings.zoomableLocalMapScrollSpeedMultiplier / zoom; - } - if ((bool)(SimpleBlueprint)BlueprintRoot.Instance && !Game.Instance.IsControllerGamepad && (bool)(SettingsEntity<bool>)SettingsRoot.Controls.ScreenEdgeScrolling && (Cursor.visible || (bool)(SettingsEntity<bool>)SettingsRoot.Controls.CameraScrollOutOfScreenEnabled) && Game.Instance.CurrentMode != GameModeType.FullScreenUi) - scrollOffset += __instance.GetCameraScrollShiftByMouse(); - var scrollVector2 = scrollOffset + __instance.CameraDragToMove() + __instance.m_ScrollBy2D; - __instance.m_ScrollBy2D.Set(0.0f, 0.0f); - __instance.TickCameraDrag(); - Game.Instance.CameraController?.Follower?.Release(); - var currentlyLoadedArea = Game.Instance.CurrentlyLoadedArea; - var num = currentlyLoadedArea != null ? currentlyLoadedArea.CameraScrollMultiplier : 1f; - var scaledScrollVector = scrollVector2 * (__instance.m_ScrollSpeed * dt * num * CameraRig.ConsoleScrollMod); - var scrollMagnatude = (float)Math.Sqrt(Vector2.Dot(scaledScrollVector, scaledScrollVector)); - - //var scaledScrollVector3 = scrollVector3 * (__instance.m_ScrollSpeed * dt * num * CameraRig.ConsoleScrollMod); - var worldPoint1 = __instance.Camera.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, 1f)); - var worldPoint2 = __instance.Camera.ViewportToWorldPoint(new Vector3(1f, 0.5f, 1f)); - var worldPoint3 = __instance.Camera.ViewportToWorldPoint(new Vector3(0.5f, 1f, 1f)); - //Mod.Debug($"worldPoint xyZ: {worldPoint1.normalized} XyZ: {worldPoint2.normalized} zYZ: {worldPoint3.normalized} "); - var pitch = __instance.transform.eulerAngles.x - 42.75; - var pitchRadians = Math.PI * pitch / 180f; - worldPoint2.y = worldPoint1.y; - worldPoint3.y = worldPoint1.y; - var vector3 = worldPoint2 - worldPoint1; - __instance.Right = vector3.normalized; - vector3 = worldPoint3 - worldPoint1; - __instance.Up = vector3.normalized; - if (pitch >= 0) __instance.Up = -__instance.Up; - var targetPosition = __instance.m_TargetPosition; - var yAxis = new Vector3(0, 2, 0); - var dPos = scaledScrollVector.x * __instance.Right + scaledScrollVector.y * __instance.Up; - if (__instance.m_RotationByMouse) - dPos += scaledScrollVector.y * (float)Math.Sin(pitchRadians) * yAxis; - //Mod.Debug($"dPos: {dPos} pitch: {pitch:##.000} sine: {Math.Sin(pitchRadians):#.000}"); - __instance.m_TargetPosition += dPos; - if (!settings.toggleFreeCamera) { - if (!__instance.NoClamp && !__instance.m_SkipClampOneFrame) - __instance.m_TargetPosition = __instance.ClampByLevelBounds(__instance.m_TargetPosition); - __instance.m_TargetPosition = __instance.PlaceOnGround(__instance.m_TargetPosition); - if (__instance.NewBehaviour) { - if (!__instance.m_AttachPointPos.HasValue) - __instance.m_AttachPointPos = new Vector3?(__instance.Camera.transform.parent.localPosition); - __instance.m_TargetPosition = __instance.LowerGently(targetPosition, __instance.m_TargetPosition, dt); - } - } - } - __instance.m_ScrollOffset = Vector2.zero; - return false; - } - } - - private static Vector2 CameraDragToRotate2D(this CameraRig __instance) { - if (!__instance.m_BaseMousePoint.HasValue) - return new Vector2(0.0f, 0.0f); - var basePoint = __instance.m_BaseMousePoint.Value; - var dx = basePoint.x - __instance.GetLocalPointerPosition().x; - var dy = basePoint.y - __instance.GetLocalPointerPosition().y; - var dist = Math.Sqrt(dx * dx + dy * dy); - var rotateDistance = ((double)__instance.m_RotateDistance - (double)dist) * ((bool)(SimpleBlueprint)BlueprintRoot.Instance ? (double)SettingsRoot.Controls.CameraRotationSpeedEdge.GetValue() : 2.0); - __instance.m_RotateDistance = (float)rotateDistance; - basePoint.x -= 0.25f * dx; - basePoint.y -= 0.25f * dy; - __instance.m_BaseMousePoint = basePoint; - return new Vector2(dx, dy); - } - - [HarmonyPatch(typeof(CameraRig), nameof(CameraRig.TickRotate))] - private static class CameraRig_TickRotate_Patch { - - public static bool Prefix(CameraRig __instance, ref Vector3 ___m_TargetPosition) { - if (!settings.toggleRotateOnAllMaps && !settings.toggleCameraPitch && !settings.toggleCameraElevation && !Main.resetExtraCameraAngles && !settings.toggleInvertXAxis && !settings.toggleInvertKeyboardXAxis) return true; - var usePitch = settings.toggleCameraPitch; - if (__instance.m_RotateRoutine != null && (double)Time.time > (double)__instance.m_RotateRoutineEndsOn) { - __instance.StopCoroutine(__instance.m_RotateRoutine); - __instance.m_RotateRoutine = (Coroutine)null; - } - - if (__instance.m_ScrollRoutine != null || __instance.m_RotateRoutine != null)// || __instance.m_HandRotationLock) - return false; - __instance.RotateByMiddleButton(); - var mouseMovement = new Vector2(0, 0); - var xRotationSign = 1f; - var yRotationSign = settings.toggleInvertYAxis ? 1f : -1f; - if (__instance.m_RotationByMouse) { - if (!settings.toggleInvertXAxis) xRotationSign = -1; - mouseMovement = __instance.CameraDragToRotate2D(); - } - else if (__instance.m_RotationByKeyboard) { - mouseMovement.x = __instance.m_RotateOffset; - if (settings.toggleInvertKeyboardXAxis) xRotationSign = -1; - } - if (__instance.m_RotationByMouse || __instance.m_RotationByKeyboard || Main.resetExtraCameraAngles) { - var eulerAngles = __instance.transform.rotation.eulerAngles; - eulerAngles.y += xRotationSign * mouseMovement.x * __instance.m_RotationSpeed * CameraRig.ConsoleRotationMod; - if (Main.resetExtraCameraAngles) { - eulerAngles.x = 0f; - CameraElevation = 60f; - __instance.m_TargetPosition = __instance.PlaceOnGround2(__instance.m_TargetPosition); - var cameraRig = Game.Instance.UI.GetCameraRig(); - var highlightingFeature = OwlcatRenderPipeline.Asset.ScriptableRendererData.rendererFeatures.OfType<OccludedObjectHighlightingFeature>().Single<OccludedObjectHighlightingFeature>(); - highlightingFeature.DepthClip.NearCameraClipDistance = 10; - highlightingFeature.DepthClip.ClipTreshold = 0; - Main.resetExtraCameraAngles = false; - } - else { - if (Input.GetKey(KeyCode.LeftControl)) { - ___m_TargetPosition.y += yRotationSign * mouseMovement.y / 10f; - CameraElevation = ___m_TargetPosition.y; - } - else if (usePitch) { - eulerAngles.x += yRotationSign * mouseMovement.y * __instance.m_RotationSpeed * CameraRig.ConsoleRotationMod; - //Mod.Debug($"eulerX: {eulerAngles.x} Y: {eulerAngles.y} Z: {eulerAngles.z}"); - } - } - __instance.transform.DOKill(); - __instance.transform.DOLocalRotate(eulerAngles, __instance.m_RotationTime).SetUpdate<Tweener>(true); - } - - __instance.m_RotationByKeyboard = false; - __instance.m_RotateOffset = 0.0f; - return false; - } - } - [HarmonyPatch(typeof(CameraRig), nameof(CameraRig.PlaceOnGround))] - private static class CameraRig_PlaceOnGround_Patch { - private static void Postfix(ref Vector3 __result) { - if (!settings.toggleCameraElevation) return; - __result.y = CameraElevation; - } - } - - [HarmonyPatch(typeof(CameraRig), nameof(CameraRig.SetMode))] - private static class CameraRig_SetMode_Apply { - public static void Postfix(CameraRig __instance, CameraMode mode) { - if (settings.fovMultiplierCutScenes == 1 && settings.fovMultiplier == 1) return; - if (mode == CameraMode.Default && Game.Instance.CurrentMode == GameModeType.Cutscene) { - __instance.Camera.fieldOfView = __instance.CameraZoom.FovMax * settings.fovMultiplierCutScenes / settings.fovMultiplier; - } - } - } - } -} diff --git a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Combat/Actions.cs b/ToyBox/classes/MonkeyPatchin/BagOfPatches/Combat/Actions.cs deleted file mode 100644 index 54d17e65f..000000000 --- a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Combat/Actions.cs +++ /dev/null @@ -1,82 +0,0 @@ -// borrowed shamelessly and enhanced from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/26, which is under the MIT License - -using HarmonyLib; -using Kingmaker; -using Kingmaker.Controllers.Combat; -//using Kingmaker.Controllers.GlobalMap; -using Kingmaker.EntitySystem.Entities; -//using Kingmaker.UI._ConsoleUI.Models; -//using Kingmaker.UI.RestCamp; -using Kingmaker.UnitLogic.Commands.Base; -using System; -//using Kingmaker.UI._ConsoleUI.GroupChanger; -using TurnBased.Controllers; -using UnityModManager = UnityModManagerNet.UnityModManager; - -namespace ToyBox.BagOfPatches { - internal static class ACtions { - public static Settings settings = Main.Settings; - public static Player player = Game.Instance.Player; - - - [HarmonyPatch(typeof(UnitCombatState), nameof(UnitCombatState.HasCooldownForCommand))] - [HarmonyPatch(new Type[] { typeof(UnitCommand) })] - public static class UnitCombatState_HasCooldownForCommand_Patch1 { - public static void Postfix(ref bool __result, UnitCombatState __instance) { - if (settings.toggleInstantCooldown && __instance.Unit.IsDirectlyControllable) { - __result = false; - } - if (CombatController.IsInTurnBasedCombat() && settings.toggleUnlimitedActionsPerTurn) { - __result = false; - } - } - } - - [HarmonyPatch(typeof(UnitCombatState), nameof(UnitCombatState.HasCooldownForCommand))] - [HarmonyPatch(new Type[] { typeof(UnitCommand.CommandType) })] - public static class UnitCombatState_HasCooldownForCommand_Patch2 { - public static void Postfix(ref bool __result, UnitCombatState __instance) { - if (settings.toggleInstantCooldown && __instance.Unit.IsDirectlyControllable) { - __result = false; - } - if (CombatController.IsInTurnBasedCombat() && settings.toggleUnlimitedActionsPerTurn) { - __result = false; - } - } - } - - [HarmonyPatch(typeof(UnitCombatState), nameof(UnitCombatState.OnNewRound))] - public static class UnitCombatState_OnNewRound_Patch { - public static bool Prefix(UnitCombatState __instance) { - if (__instance.Unit.IsDirectlyControllable && settings.toggleInstantCooldown) { - __instance.Cooldown.Initiative = 0f; - __instance.Cooldown.StandardAction = 0f; - __instance.Cooldown.MoveAction = 0f; - __instance.Cooldown.SwiftAction = 0f; - __instance.Cooldown.AttackOfOpportunity = 0f; - } - if (CombatController.IsInTurnBasedCombat() && settings.toggleUnlimitedActionsPerTurn) { - __instance.Cooldown.Initiative = 0f; - __instance.Cooldown.StandardAction = 0f; - __instance.Cooldown.MoveAction = 0f; - __instance.Cooldown.SwiftAction = 0f; - __instance.Cooldown.AttackOfOpportunity = 0f; - } - return true; - } - } - - [HarmonyPatch(typeof(UnitEntityData), nameof(UnitEntityData.SpendAction))] - public static class UnitEntityData_SpendAction_Patch { - - public static bool Prefix(UnitCommand.CommandType type, bool isFullRound, float timeSinceCommandStart, UnitEntityData __instance) { - if (!__instance.IsInCombat) return true; - if (!settings.toggleUnlimitedActionsPerTurn) return true; - else if (CombatController.IsInTurnBasedCombat()) { - return false; - } - return true; - } - } - } -} \ No newline at end of file diff --git a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Combat/NoFriendlyFire.cs b/ToyBox/classes/MonkeyPatchin/BagOfPatches/Combat/NoFriendlyFire.cs deleted file mode 100644 index a52b09b9e..000000000 --- a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Combat/NoFriendlyFire.cs +++ /dev/null @@ -1,173 +0,0 @@ -// borrowed shamelessly and enhanced from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/26, which is under the MIT License - -using HarmonyLib; -using Kingmaker; -using Kingmaker.Blueprints; -//using Kingmaker.Controllers.GlobalMap; -using Kingmaker.Designers; -using Kingmaker.ElementsSystem; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.RuleSystem.Rules; -using Kingmaker.RuleSystem.Rules.Damage; -//using Kingmaker.UI._ConsoleUI.Models; -//using Kingmaker.UI.RestCamp; -using Kingmaker.UnitLogic.Abilities; -using Kingmaker.UnitLogic.Abilities.Blueprints; -using Kingmaker.UnitLogic.Abilities.Components; -using Kingmaker.UnitLogic.Buffs.Blueprints; -using Kingmaker.Utility; -using System; -using System.Collections.Generic; -using System.Linq; -//using Kingmaker.UI._ConsoleUI.GroupChanger; -using UnityModManager = UnityModManagerNet.UnityModManager; - -namespace ToyBox.BagOfPatches { - internal static class NoFriendlyFire { - public static Settings settings = Main.Settings; - public static Player player = Game.Instance.Player; - - [HarmonyPatch(typeof(AbilityTargetsAround), nameof(AbilityTargetsAround.Select))] - public static class AbilityTargetsAround_Select_Patch { - public static void Postfix(ref IEnumerable<TargetWrapper> __result, AbilityTargetsAround __instance, ConditionsChecker ___m_Condition, AbilityExecutionContext context, TargetWrapper anchor) { - if (settings.toggleNoFriendlyFireForAOE) { - var caster = context.MaybeCaster; - var targets = GameHelper.GetTargetsAround(anchor.Point, __instance.AoERadius); - if (caster == null) { - __result = Enumerable.Empty<TargetWrapper>(); - return; - } - switch (__instance.m_TargetType) { - case TargetType.Enemy: - targets = targets.Where(caster.IsEnemy); - break; - case TargetType.Ally: - targets = targets.Where(caster.IsAlly); - break; - default: - throw new ArgumentOutOfRangeException(); - case TargetType.Any: - break; - } - if (___m_Condition.HasConditions) { - targets = targets.Where(u => { using (context.GetDataScope(u)) { return ___m_Condition.Check(); } }).ToList(); - } - if (caster.Descriptor.IsPartyOrPet() && ((context.AbilityBlueprint.EffectOnAlly == AbilityEffectOnUnit.Harmful) || (context.AbilityBlueprint.EffectOnEnemy == AbilityEffectOnUnit.Harmful))) { - if (context.AbilityBlueprint.HasLogic<AbilityUseOnRest>()) { - var componentType = context.AbilityBlueprint.GetComponent<AbilityUseOnRest>().Type; - //bool healDamage = componentType == AbilityUseOnRestType.HealDamage || componentType == AbilityUseOnRestType.HealDamage; - var healDamage = componentType == AbilityUseOnRestType.HealDamage; - targets = targets.Where(target => { - if (target.Descriptor.IsPartyOrPet() && !healDamage) { - var forUndead = componentType == AbilityUseOnRestType.HealMassUndead || componentType == AbilityUseOnRestType.HealSelfUndead || componentType == AbilityUseOnRestType.HealUndead; - return forUndead == target.Descriptor.IsUndead; - } - return true; - }); - } - else { - targets = targets.Where(target => !target.Descriptor.IsPartyOrPet()); - } - } - __result = targets.Select(target => new TargetWrapper(target)); - } - } - } - - [HarmonyPatch(typeof(RuleDealDamage), nameof(RuleDealDamage.ApplyDifficultyModifiers))] - public static class RuleDealDamage_ApplyDifficultyModifiers_Patch { - public static void Postfix(ref int __result, RuleDealDamage __instance, int damage) { - if (settings.toggleNoFriendlyFireForAOE) { - SimpleBlueprint blueprint = __instance.Reason.Context?.AssociatedBlueprint; - if (!(blueprint is BlueprintBuff)) { - var blueprintAbility = __instance.Reason.Context?.SourceAbility; - if (blueprintAbility != null && - __instance.Initiator.Descriptor.IsPartyOrPet() && - __instance.Target.Descriptor.IsPartyOrPet() && - ((blueprintAbility.EffectOnAlly == AbilityEffectOnUnit.Harmful) || (blueprintAbility.EffectOnEnemy == AbilityEffectOnUnit.Harmful))) { - __result = 0; - } - } - } - } - } - - // public bool IsSuccessRoll(int d20, int successBonus = 0) => d20 + this.TotalBonus + successBonus >= this.DC; - [HarmonyPatch(typeof(RuleSkillCheck), nameof(RuleSkillCheck.IsSuccessRoll))] - [HarmonyPatch(new Type[] { typeof(int), typeof(int) })] - - public static class RuleSkillCheck_IsSuccessRoll_Patch { - private static void Postfix(ref bool __result, RuleSkillCheck __instance) { - if (settings.toggleNoFriendlyFireForAOE) { - if (__instance.Reason != null) { - if (__instance.Reason.Ability != null) { - if (__instance.Reason.Caster != null && __instance.Reason.Caster.Descriptor.IsPartyOrPet() && __instance.Initiator.Descriptor.IsPartyOrPet() && __instance.Reason.Ability.Blueprint != null && ((__instance.Reason.Ability.Blueprint.EffectOnAlly == AbilityEffectOnUnit.Harmful) || (__instance.Reason.Ability.Blueprint.EffectOnEnemy == AbilityEffectOnUnit.Harmful))) { - __result = true; - } - } - } - } - } - } - - [HarmonyPatch(typeof(RulePartyStatCheck), nameof(RulePartyStatCheck.Success), MethodType.Getter)] - public static class RulePartyStatCheck_IsPassed_Patch { - private static void Postfix(ref bool __result, RulePartyStatCheck __instance) { - if (settings.toggleNoFriendlyFireForAOE) { - if (__instance.Reason != null) { - if (__instance.Reason.Ability != null) { - if (__instance.Reason.Caster != null && __instance.Reason.Caster.Descriptor.IsPartyOrPet() && __instance.Initiator.Descriptor.IsPartyOrPet() && __instance.Reason.Ability.Blueprint != null && ((__instance.Reason.Ability.Blueprint.EffectOnAlly == AbilityEffectOnUnit.Harmful) || (__instance.Reason.Ability.Blueprint.EffectOnEnemy == AbilityEffectOnUnit.Harmful))) { - __result = true; - } - } - } - } -#if false - if (StringUtils.ToToggleBool(settings.togglePassSavingThrowIndividual)) { - for (int i = 0; i < settings.togglePassSavingThrowIndividualArray.Count(); i++) { - if (StringUtils.ToToggleBool(settings.togglePassSavingThrowIndividualArray[i]) && Storage.statsSavesDict[Storage.individualSavesArray[i]] == __instance.StatType) { - if (UnitEntityDataUtils.CheckUnitEntityData(__instance.Initiator, (UnitSelectType)settings.indexPassSavingThrowIndividuall)) { - __result = true; - } - } - } - } - if (StringUtils.ToToggleBool(settings.togglePassSkillChecksIndividual)) { - for (int i = 0; i < settings.togglePassSkillChecksIndividualArray.Count(); i++) { - if (StringUtils.ToToggleBool(settings.togglePassSkillChecksIndividualArray[i]) && Storage.statsSkillsDict.Union(Storage.statsSocialSkillsDict).ToDictionary(d => d.Key, d => d.Value)[Storage.individualSkillsArray[i]] == __instance.StatType) { - if (UnitEntityDataUtils.CheckUnitEntityData(__instance.Initiator, (UnitSelectType)settings.indexPassSkillChecksIndividual)) { - __result = true; - } - } - } - } -#endif - } - } - [HarmonyPatch(typeof(RuleSavingThrow), "IsPassed", MethodType.Getter)] - public static class RuleSavingThrow_IsPassed_Patch { - internal static void Postfix(ref bool __result, RuleSavingThrow __instance) { - if (settings.toggleNoFriendlyFireForAOE) { - if (__instance.Reason != null) { - if (__instance.Reason.Ability != null) { - if (__instance.Reason.Caster != null && __instance.Reason.Caster.Descriptor.IsPartyOrPet() && __instance.Initiator.Descriptor.IsPartyOrPet() && __instance.Reason.Ability.Blueprint != null && ((__instance.Reason.Ability.Blueprint.EffectOnAlly == AbilityEffectOnUnit.Harmful) || (__instance.Reason.Ability.Blueprint.EffectOnEnemy == AbilityEffectOnUnit.Harmful))) { - __result = true; - } - } - } - } -#if false - if (StringUtils.ToToggleBool(settings.togglePassSavingThrowIndividual)) { - for (int i = 0; i < settings.togglePassSavingThrowIndividualArray.Count(); i++) { - if (StringUtils.ToToggleBool(settings.togglePassSavingThrowIndividualArray[i]) && Storage.statsSavesDict[Storage.individualSavesArray[i]] == __instance.StatType) { - if (UnitEntityDataUtils.CheckUnitEntityData(__instance.Initiator, (UnitSelectType)settings.indexPassSavingThrowIndividuall)) { - __result = true; - } - } - } - } -#endif - } - } - } -} diff --git a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Crusade.cs b/ToyBox/classes/MonkeyPatchin/BagOfPatches/Crusade.cs deleted file mode 100644 index 6230f6fc7..000000000 --- a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Crusade.cs +++ /dev/null @@ -1,209 +0,0 @@ -using HarmonyLib; -using Kingmaker.Armies; -using Kingmaker.Armies.Blueprints; -using Kingmaker.Armies.State; -using Kingmaker.Armies.TacticalCombat; -using Kingmaker.Armies.TacticalCombat.Parts; -using Kingmaker.Blueprints; -using Kingmaker.Blueprints.Root; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.Globalmap.State; -using Kingmaker.Kingdom; -using Kingmaker.Kingdom.Armies; -using Kingmaker.Kingdom.Blueprints; -using Kingmaker.Kingdom.Buffs; -using Kingmaker.Kingdom.Flags; -using Kingmaker.Kingdom.Rules; -using Kingmaker.Kingdom.Tasks; -using System; -using System.Collections.Generic; -using System.Linq; -using ToyBox.classes.MainUI; -using UnityEngine; -namespace ToyBox.classes.MonkeyPatchin.BagOfPatches { - public static class Crusade { - public static Settings Settings = Main.Settings; - - [HarmonyPatch(typeof(ArmyMercenariesManager), nameof(ArmyMercenariesManager.Reroll))] - public static class ArmyMercenariesManager_Reroll_Patch { - public static void Prefix(ref ArmyMercenariesManager __instance, ref int __state) { - if (Settings.toggleInfiniteArmyRerolls) { - __state = __instance.FreeRerollsLeftCount; - __instance.FreeRerollsLeftCount = 99; - } - } - - public static void Postfix(ref ArmyMercenariesManager __instance, int __state) { - if (Settings.toggleInfiniteArmyRerolls) { - __instance.FreeRerollsLeftCount = __state; - } - } - } - - [HarmonyPatch(typeof(TacticalCombatHelper), nameof(TacticalCombatHelper.GetSpellPower))] - public static class TacticalCombatHelper_GetSpellPower_Patch { - public static void Postfix(ref int __result, [HarmonyArgument(0)] UnitEntityData unit) { - var leaderPowerMultiplier = unit.Get<UnitPartLeaderTacticalCombat>()?.LeaderData.Faction != ArmyFaction.Crusaders - ? Settings.enemyLeaderPowerMultiplier - : Settings.playerLeaderPowerMultiplier; - - __result = Mathf.RoundToInt(__result * leaderPowerMultiplier); - } - } - - [HarmonyPatch(typeof(ArmyData), nameof(ArmyData.MaxSquadsCount), MethodType.Getter)] - public static class ArmyData_MaxSquadsCount_Patch { - public static void Postfix(ref ArmyModifiableValue __result, ArmyData __instance) { - if (Settings.toggleLargeArmies && __result != null && __result.ModifiedValue != 0 && __instance.Faction == ArmyFaction.Crusaders) { - __result.MaxValue = ArmyData.PositionsCount; - __result.MinValue = ArmyData.PositionsCount; - __result.m_BaseValue = __result.MaxValue; - __result.ModifiedValue = __result.MaxValue; - BlueprintRoot.Instance.Kingdom.StartArmySquadsCount = ArmyData.PositionsCount; - BlueprintRoot.Instance.Kingdom.MaxArmySquadsCount = ArmyData.PositionsCount; - } - } - } - - [HarmonyPatch(typeof(ArmyData))] - public static class ArmyData_CalculateExperience_Patch { - [HarmonyPatch(nameof(ArmyData.CalculateExperience))] - [HarmonyPostfix] - public static void Postfix(ref int __result) => __result = Mathf.RoundToInt(__result * (float)Math.Round(Settings.armyExperienceMultiplier, 1)); - - [HarmonyPrefix] - [HarmonyPatch(nameof(ArmyData.CalculateDangerRating))] - public static bool PrefixCalculateDangerRating(ArmyData __instance, ref int __result) { - var armyRoot = BlueprintRoot.Instance.ArmyRoot; - var num1 = Mathf.FloorToInt((float)(__instance.CalculateExperience() / Math.Round(Settings.experienceMultiplier, 1) + armyRoot.ArmyDangerBonus) * armyRoot.ArmyDangerMultiplier); - if (num1 < 0) { - __result = 1; - return false; - } - - var num2 = 0; - var bonuses = BlueprintRoot.Instance.LeadersRoot.ExpTable.Bonuses; - for (var index = 0; index < bonuses.Length && bonuses[index] <= num1; ++index) - ++num2; - __result = num2; - return false; - } - } - - [HarmonyPatch(typeof(SummonUnitsAfterArmyBattle), nameof(SummonUnitsAfterArmyBattle.HandleArmiesBattleResultsApplied))] - public static class SummonUnitsAfterArmyBattle_Patch { - public static void Prefix(ref TacticalCombatResults results) => results = new TacticalCombatResults(results.Attacker, results.Defender, Mathf.RoundToInt(results.BattleExp * Settings.postBattleSummonMultiplier), - results.CrusadeStatsBonus, results.Winner, results.ToResurrect, results.Units, results.Retreat); - } - - [HarmonyPatch(typeof(MercenarySlot), nameof(MercenarySlot.Price), MethodType.Getter)] - private static class MercenarySlot_Price_Patch { - private static void Postfix(ref KingdomResourcesAmount __result) { - __result *= Settings.recruitmentCost; - if (!__result.IsPositive) { - __result = KingdomResourcesAmount.Zero; - } - } - } - - [HarmonyPatch(typeof(RuleCalculateUnitRecruitingCost), nameof(RuleCalculateUnitRecruitingCost.ResultCost), MethodType.Getter)] - private static class RuleCalculateUnitRecruitingCost_ResultCost_Patch { - private static void Postfix(ref KingdomResourcesAmount __result) { - var finances = __result.m_Finances > 0 ? Mathf.RoundToInt(Math.Max(1, Settings.recruitmentCost * __result.m_Finances)) : 0; - var materials = __result.m_Materials > 0 ? Mathf.RoundToInt(Math.Max(1, Settings.recruitmentCost * __result.m_Materials)) : 0; - var favors = __result.m_Favors > 0 ? Mathf.RoundToInt(Math.Max(1, Settings.recruitmentCost * __result.m_Favors)) : 0; - - __result = new KingdomResourcesAmount { m_Favors = favors, m_Finances = finances, m_Materials = materials }; - } - } - - [HarmonyPatch(typeof(ArmyRecruitsManager), nameof(ArmyRecruitsManager.Increase))] - private static class ArmyRecruitsManager_Patch { - private static void Prefix(ref int count) => count = Mathf.RoundToInt(count * Settings.recruitmentMultiplier); - } - - [HarmonyPatch(typeof(ArmyMercenariesManager), nameof(ArmyMercenariesManager.Recruit))] - private static class ArmyMercenariesManager_Recruit_Patch { - private static void Prefix(ref MercenarySlot slot) => slot.Recruits.Count = Mathf.RoundToInt(slot.Recruits.Count * Settings.recruitmentMultiplier); - } - - [HarmonyPatch(typeof(KingdomMoraleFlag), nameof(KingdomMoraleFlag.ChangeDaysLeft))] - private static class KingdomMoraleFlag_Patch { - private static void Prefix(ref int daysDelta) { - if (Settings.toggleCrusadeFlagsStayGreen && daysDelta < 0) { - daysDelta = 0; - } - } - } - - [HarmonyPatch(typeof(KingdomEvent), nameof(KingdomEvent.CalculateResolutionTime))] - private static class KingdomEvent_CalculateResolutionTime_Patch { - private static void Postfix(KingdomEvent __instance, ref int __result) { - if (Settings.kingdomTaskResolutionLengthMultiplier == 0) return; - if (__instance.EventBlueprint.IsResolveByBaron) return; //this is a guard from KingdomResolution, not sure why it's there or if we still need it - //KingdomResolution split this into multiple settings, but this should be good enough until someone who cares checks what blueprint types we have - __result = Mathf.RoundToInt(__result * (Settings.kingdomTaskResolutionLengthMultiplier + 1)); - __result = __result < 1 ? 1 : __result; - } - } - - [HarmonyPatch(typeof(KingdomTaskEvent), nameof(KingdomTaskEvent.CanBeStarted))] - public static class KingdomTaskEvent_CanBeStarted_Patch { - public static void Postfix(ref bool __result) { - if (Settings.toggleIgnoreStartTaskRestrictions) - __result = true; - } - } - - [HarmonyPatch(typeof(BlueprintKingdomEventBase), nameof(BlueprintKingdomEventBase.GetAvailableLeader))] - public static class BlueprintKingdomEventBase_GetAvailableLeader_Patch { - public static void Postfix(BlueprintKingdomEventBase __instance, ref LeaderState __result) { - if (Settings.toggleIgnoreStartTaskRestrictions) - __result = new LeaderState(__instance.GetDefaultResolutionType()); - } - } - - [HarmonyPatch(typeof(KingdomTaskEvent), nameof(KingdomTaskEvent.OneTimeCost), MethodType.Getter)] - public static class KingdomTaskEvent_OneTimeCost_Patch { - public static void Postfix(ref KingdomResourcesAmount __result) { - if (Settings.toggleTaskNoResourcesCost) - __result = new KingdomResourcesAmount(); - } - } - - [HarmonyPatch(typeof(ArmyData), nameof(ArmyData.Add))] - public static class ArmyData_Add_Patch { - public static void Postfix(ArmyData __instance, BlueprintUnit unit) { - if (__instance.Faction == ArmyFaction.Crusaders) { - if (Settings.toggleAddNewUnitsAsMercenaries) { - if (ArmiesEditor.armyBlueprints != null && ArmiesEditor.armyBlueprints?.Count() != 0) { - if (!ArmiesEditor.IsInMercenaryPool[unit.GetHashCode()] && !ArmiesEditor.IsInRecruitPool[unit.GetHashCode()]) { - ArmiesEditor.IsInMercenaryPool[unit.GetHashCode()] = true; - KingdomState.Instance.MercenariesManager.AddMercenary(unit, 1); - } - else { - IEnumerable<BlueprintUnit> recruitPool = from recruitable in KingdomState.Instance.RecruitsManager.Pool - select recruitable.Unit; - if (!recruitPool.Contains(unit) && !KingdomState.Instance.MercenariesManager.HasUnitInPool(unit)) { - KingdomState.Instance.MercenariesManager.AddMercenary(unit, 1); - } - } - } - } - } - } - } - [HarmonyPatch(typeof(ArmyMercenariesManager), nameof(ArmyMercenariesManager.AddMercenary))] - public static class ArmyMercenariesManager_AddMercenary_Patch { - public static void Postfix() { - ArmiesEditor.poolChanged = true; - } - } - [HarmonyPatch(typeof(ArmyMercenariesManager), nameof(ArmyMercenariesManager.RemoveMercenary))] - public static class ArmyMercenariesManager_RemoveMercenary_Patch { - public static void Postfix() { - ArmiesEditor.poolChanged = true; - } - } - } -} diff --git a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Development.cs b/ToyBox/classes/MonkeyPatchin/BagOfPatches/Development.cs deleted file mode 100644 index 8850d7e14..000000000 --- a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Development.cs +++ /dev/null @@ -1,171 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using HarmonyLib; -using Kingmaker; -using Kingmaker.Blueprints; -using Kingmaker.Blueprints.Items.Equipment; -using Kingmaker.EntitySystem; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.EntitySystem.Persistence; -using Kingmaker.EntitySystem.Persistence.JsonUtility; -using Kingmaker.EntitySystem.Persistence.Versioning; -using Kingmaker.Items; -using Kingmaker.UI.MVVM._VM.CharGen; -using Kingmaker.Utility; -using ModKit; -using Newtonsoft.Json; -using Owlcat.Runtime.Core.Logging; -using UnityModManagerNet; -using static Kingmaker.EntitySystem.Persistence.Versioning.JsonUpgradeSystem; - -namespace ToyBox.classes.MonkeyPatchin.BagOfPatches { - internal class Development { - public static Settings settings = Main.Settings; - - [HarmonyPatch(typeof(BuildModeUtility), nameof(BuildModeUtility.IsDevelopment), MethodType.Getter)] - private static class BuildModeUtility_IsDevelopment_Patch { - private static void Postfix(ref bool __result) { - if (settings.toggleDevopmentMode) __result = true; - } - } - - [HarmonyPatch(typeof(SmartConsole), nameof(SmartConsole.WriteLine))] - private static class SmartConsole_WriteLine_Patch { - private static void Postfix(string message) { - if (settings.toggleDevopmentMode) { - Mod.Log(message); - var timestamp = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; - UberLoggerAppWindow.Instance.Log(new LogInfo(null, LogChannel.Default, timestamp, LogSeverity.Message, new List<LogStackFrame>(), false, message, Array.Empty<object>()) - ); - } - } - } -#if false - [HarmonyPatch(typeof(SmartConsole), nameof(SmartConsole.Initialise))] - private static class SmartConsole_Initialise_Patch { - private static void Postfix() { - if (settings.toggleDevopmentMode) { - SmartConsoleCommands.Register(); - } - } - } - - [HarmonyPatch(typeof(Owlcat.Runtime.Core.Logging.Logger), nameof(Owlcat.Runtime.Core.Logging.Logger.ForwardToUnity))] - private static class UberLoggerLogger_ForwardToUnity_Patch { - private static void Prefix(ref object message) { - if (settings.toggleUberLoggerForwardPrefix) { - var message1 = "[UberLogger] " + message as string; - message = message1 as object; - } - } - } -#endif - // This patch if for you @ArcaneTrixter and @Vek17 - [HarmonyPatch(typeof(UnityModManager.Logger), nameof(UnityModManager.Logger.Write))] - private static class Logger_Logger_Patch { - private static bool Prefix(string str, bool onlyNative = false) { - if (str == null) - return false; - var stripHTMLNative = settings.stripHtmlTagsFromNativeConsole; - var sriptHTMLHistory = settings.stripHtmlTagsFromUMMLogsTab; - Console.WriteLine(stripHTMLNative ? str.StripHTML() : str); - - if (onlyNative) - return false; - if (sriptHTMLHistory) str = str.StripHTML(); - UnityModManager.Logger.buffer.Add(str); - UnityModManager.Logger.history.Add(str); - - if (UnityModManager.Logger.history.Count >= UnityModManager.Logger.historyCapacity * 2) { - var result = UnityModManager.Logger.history.Skip(UnityModManager.Logger.historyCapacity); - UnityModManager.Logger.history.Clear(); - UnityModManager.Logger.history.AddRange(result); - } - return false; - } - } - - [HarmonyPatch(typeof(CharGenContextVM), nameof(CharGenContextVM.HandleRespecInitiate))] - private static class CharGenContextVM_HandleRespecInitiate_Patch { - private static void Prefix(ref CharGenContextVM __instance, ref UnitEntityData character, ref Action successAction) { - if (settings.toggleRespecRefundScrolls) { - var scrolls = new List<BlueprintItemEquipmentUsable>(); - - var loadedscrolls = Game.Instance.BlueprintRoot.CraftRoot.m_ScrollsItems.Select(a => ResourcesLibrary.TryGetBlueprint<BlueprintItemEquipmentUsable>(a.Guid)); - foreach (var spellbook in character.Spellbooks) { - foreach (var scrollspell in spellbook.GetAllKnownSpells()) - if (scrollspell.CopiedFromScroll) - if (loadedscrolls.TryFind(a => a.Ability.NameForAcronym == scrollspell.Blueprint.NameForAcronym, out var item)) - scrolls.Add(item); - } - - successAction = PatchedSuccessAction(successAction, scrolls); - } - } - - private static Action PatchedSuccessAction(Action successAction, List<BlueprintItemEquipmentUsable> scrolls) => - () => { - foreach (var scroll in scrolls) Game.Instance.Player.Inventory.Add(new ItemEntityUsable(scroll)); - successAction.Invoke(); - }; - } - - [HarmonyPatch(typeof(BlueprintConverter), nameof(BlueprintConverter.ReadJson))] - private static class ForceSuccessfulLoad_Blueprints_Patch { - private static bool Prefix(ref object __result, JsonReader reader) { - if (!settings.enableLoadWithMissingBlueprints) return true; - var text = (string)reader.Value; - if (string.IsNullOrEmpty(text) || text == "null") { - //Mod.Warn($"ForceSuccessfulLoad_Blueprints_Patch - unable to find valid id - text: {text}"); - __result = null; // We still can't look up a blueprint without a valid id - return false; - } - SimpleBlueprint retrievedBlueprint; - try { - retrievedBlueprint = ResourcesLibrary.TryGetBlueprint(BlueprintGuid.Parse(text)); - } - catch { - retrievedBlueprint = null; - } - if (retrievedBlueprint == null) Mod.Warn($"Failed to load blueprint by guid '{text}' but continued with null blueprint."); - __result = retrievedBlueprint; - - return false; - } - } - - [HarmonyPatch(typeof(EntityFact), nameof(EntityFact.ComponentsDictionary), MethodType.Setter)] - private static class ForceSuccessfulLoad_OfFacts_Patch { - private static void Prefix(ref EntityFact __instance) { - if (__instance.Blueprint == null) Mod.Warn($"Fact type '{__instance}' failed to load. UniqueID: {__instance.UniqueId}"); - } - } - #if false - [HarmonyPatch(typeof(JsonUpgradeSystem))] - public static class JsonUpgradeSystemPatch { - [HarmonyPatch(nameof(JsonUpgradeSystem.GetUpgraders), typeof(SaveInfo))] - [HarmonyPrefix] - private static bool GetUpgraders(SaveInfo saveInfo, IEnumerable<UpgraderEntry> __result) { - return false; - if (!settings.enableLoadWithMissingBlueprints) return true; - var saveVersionsSet = new HashSet<int>(saveInfo.Versions); - var availableList = s_Updaters.Select(u => u.Version).ToList(); - var availableSet = new HashSet<int>(availableList); - var saveVersions = string.Join(", ", saveInfo.Versions.Select(i => i.ToString()).ToArray()); - var availVersions = string.Join(", ", availableList.Select(i => i.ToString()).ToArray()); - Mod.Warn($"save versions: {saveVersions}"); - Mod.Warn($"available versions: {availVersions}"); - foreach (var version in saveInfo.Versions) { - if (!availableSet.Contains(version)) { - Mod.Warn(string.Format("Unknown version in save info: {0}", version) + string.Format("\nSave versions: {0}", saveInfo.Versions) + string.Format("\nKnown versions: {0}", availableList)); -// throw new JsonUpgradeException(string.Format("Unknown version in save info: {0}", version) + string.Format("\nSave versions: {0}", saveInfo.Versions) + string.Format("\nKnown versions: {0}", availableList)); - } - } - __result = s_Updaters.Where(u => !saveVersionsSet.Contains(u.Version)); - return false; - } - } -#endif - } -} \ No newline at end of file diff --git a/ToyBox/classes/MonkeyPatchin/BagOfPatches/DiceRolls.cs b/ToyBox/classes/MonkeyPatchin/BagOfPatches/DiceRolls.cs deleted file mode 100644 index 8f3d1853b..000000000 --- a/ToyBox/classes/MonkeyPatchin/BagOfPatches/DiceRolls.cs +++ /dev/null @@ -1,137 +0,0 @@ -// borrowed shamelessly and enhanced from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/26, which is under the MIT License - -using HarmonyLib; -using Kingmaker; -//using Kingmaker.Controllers.GlobalMap; -using Kingmaker.RuleSystem; -using Kingmaker.RuleSystem.Rules; -using ModKit; -//using Kingmaker.UI._ConsoleUI.Models; -//using Kingmaker.UI.RestCamp; -using System; -//using Kingmaker.UI._ConsoleUI.GroupChanger; -using UnityModManager = UnityModManagerNet.UnityModManager; - -namespace ToyBox.BagOfPatches { - internal static class DiceRolls { - public static Settings settings = Main.Settings; - public static Player player = Game.Instance.Player; - - [HarmonyPatch(typeof(RuleAttackRoll), nameof(RuleAttackRoll.IsCriticalConfirmed), MethodType.Getter)] - private static class HitPlayer_OnTriggerl_Patch { - private static void Postfix(ref bool __result, RuleAttackRoll __instance) { - if (__instance.IsHit && UnitEntityDataUtils.CheckUnitEntityData(__instance.Initiator, settings.allHitsCritical)) { - __result = true; - } - } - } - [HarmonyPatch(typeof(RuleAttackRoll), nameof(RuleAttackRoll.IsHit), MethodType.Getter)] - private static class HitPlayer_OnTrigger2_Patch { - private static void Postfix(ref bool __result, RuleAttackRoll __instance) { - if (UnitEntityDataUtils.CheckUnitEntityData(__instance.Initiator, settings.allAttacksHit)) { - __result = true; - } - } - } - -#if false - [HarmonyPatch(typeof(RuleCastSpell), nameof(RuleCastSpell.IsArcaneSpellFailed), MethodType.Getter)] - public static class RuleCastSpell_IsArcaneSpellFailed_Patch { - static void Postfix(RuleCastSpell __instance, ref bool __result) { - if ((__instance.Spell.Caster?.Unit?.IsPartyMemberOrPet() ?? false) && (StringUtils.ToToggleBool(settings.toggleArcaneSpellFailureRoll))) { - if (!StringUtils.ToToggleBool(settings.toggleArcaneSpellFailureRollOutOfCombatOnly)) { - __result = false; - } - else if (StringUtils.ToToggleBool(settings.toggleArcaneSpellFailureRollOutOfCombatOnly) && !__instance.Initiator.IsInCombat) { - __result = false; - } - - } - } - } -#endif - - [HarmonyPatch(typeof(RuleRollDice), nameof(RuleRollDice.Roll))] - public static class RuleRollDice_Roll_Patch { - private static void Postfix(RuleRollDice __instance) { - if (__instance.DiceFormula.Dice != DiceType.D20) return; - var initiator = __instance.Initiator; - var result = __instance.m_Result; - //modLogger.Log($"initiator: {initiator.CharacterName} isInCombat: {initiator.IsInCombat} alwaysRole20OutOfCombat: {settings.alwaysRoll20OutOfCombat}"); - //Mod.Debug($"initiator: {initiator.CharacterName} Initial D20Roll: " + result); - if (UnitEntityDataUtils.CheckUnitEntityData(initiator, settings.alwaysRoll20) - || (UnitEntityDataUtils.CheckUnitEntityData(initiator, settings.alwaysRoll20OutOfCombat) - && !initiator.IsInCombat - ) - ) { - result = 20; - } - else if (UnitEntityDataUtils.CheckUnitEntityData(initiator, settings.alwaysRoll1)) { - result = 1; - } - else { - if (UnitEntityDataUtils.CheckUnitEntityData(initiator, settings.rollWithAdvantage)) { - result = Math.Max(result, UnityEngine.Random.Range(1, 21)); - } - else if (UnitEntityDataUtils.CheckUnitEntityData(initiator, settings.rollWithDisadvantage)) { - result = Math.Min(result, UnityEngine.Random.Range(1, 21)); - } - var min = 1; - if (UnitEntityDataUtils.CheckUnitEntityData(initiator, settings.neverRoll1) && result == 1) { - result = UnityEngine.Random.Range(2, 21); - min = 2; - } - if (UnitEntityDataUtils.CheckUnitEntityData(initiator, settings.take10always) && result < 10 && !initiator.IsInCombat) { - result = 10; - min = 10; - } -#if false - if (UnitEntityDataUtils.CheckUnitEntityData(initiator, settings.take10minimum) && result < 10 && !initiator.IsInCombat) { - result = UnityEngine.Random.Range(10, 21); - min = 10; - } -#endif - if (UnitEntityDataUtils.CheckUnitEntityData(initiator, settings.neverRoll20) && result == 20) { - result = UnityEngine.Random.Range(min, 20); - } - } - //Mod.Debug("Modified D20Roll: " + result); - __instance.m_Result = result; - } - } - - [HarmonyPatch(typeof(RuleInitiativeRoll), nameof(RuleInitiativeRoll.Result), MethodType.Getter)] - public static class RuleInitiativeRoll_OnTrigger_Patch { - private static void Postfix(RuleInitiativeRoll __instance, ref int __result) { - if (UnitEntityDataUtils.CheckUnitEntityData(__instance.Initiator, settings.roll1Initiative)) { - __result = 1 + __instance.Modifier; - Mod.Trace("Modified InitiativeRoll: " + __result); - } - else if (UnitEntityDataUtils.CheckUnitEntityData(__instance.Initiator, settings.roll20Initiative)) { - __result = 20 + __instance.Modifier; - Mod.Trace("Modified InitiativeRoll: " + __result); - } - } - } - - // Thanks AlterAsc - https://github.com/alterasc/CombatRelief/blob/main/CombatRelief/SkillRolls.cs - [HarmonyPatch(typeof(RuleSkillCheck), nameof(RuleSkillCheck.RollD20))] - public static class RuleSkillCheck_RollD20_Patch { - [HarmonyPrefix] - private static bool Prefix(ref RuleRollD20 __result, RuleSkillCheck __instance) { - if (__instance.Initiator.IsInCombat) { - return true; - } - if (UnitEntityDataUtils.CheckUnitEntityData(__instance.Initiator, settings.skillsTake20)) { - __result = RuleRollD20.FromInt(__instance.Initiator, 20); - return false; - } - if (UnitEntityDataUtils.CheckUnitEntityData(__instance.Initiator, settings.skillsTake10)) { - __result = RuleRollD20.FromInt(__instance.Initiator, 10); - return false; - } - return true; - } - } - } -} diff --git a/ToyBox/classes/MonkeyPatchin/BagOfPatches/LevelUpPatches.cs b/ToyBox/classes/MonkeyPatchin/BagOfPatches/LevelUpPatches.cs deleted file mode 100644 index c52a8f1d4..000000000 --- a/ToyBox/classes/MonkeyPatchin/BagOfPatches/LevelUpPatches.cs +++ /dev/null @@ -1,683 +0,0 @@ -// borrowed shamelessly and enhanced from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/26, which is under the MIT License - -using HarmonyLib; -using JetBrains.Annotations; -using Kingmaker; -using Kingmaker.Blueprints; -using Kingmaker.Blueprints.Classes; -using Kingmaker.Blueprints.Classes.Prerequisites; -using Kingmaker.Blueprints.Classes.Selection; -using Kingmaker.Blueprints.Root; -using Kingmaker.EntitySystem.Stats; -using Kingmaker.UI.MVVM._VM.CharGen.Phases.Class; -using Kingmaker.UnitLogic; -using Kingmaker.UnitLogic.Class.LevelUp; -using Kingmaker.UnitLogic.Class.LevelUp.Actions; -using System; -using System.Collections.Generic; -using System.Linq; -using Kingmaker.UI.MVVM._VM.CharGen.Phases.Skills; -using Kingmaker.UI.MVVM._VM.CharGen.Phases.FeatureSelector; -using Kingmaker.UI.MVVM._VM.ServiceWindows.CharacterInfo.Sections.LevelClassScores.Experience; -using Kingmaker.UI.ServiceWindow; -using UnityEngine; -using ModKit; -using System.Reflection; -using System.Reflection.Emit; -using Kingmaker.UI.MVVM._VM.CharGen.Phases.Name; -using ToyBox; -using ToyBox.Multiclass; - -namespace ToyBox.BagOfPatches { - internal static class LevelUp { - public static Settings settings = Main.Settings; - public static Player player = Game.Instance.Player; - - [HarmonyPatch(typeof(LevelUpController), nameof(LevelUpController.CanLevelUp))] - private static class LevelUpController_CanLevelUp_Patch { - private static void Postfix(ref bool __result) { - if (settings.toggleNoLevelUpRestrictions) { - __result = true; - } - } - } - - [HarmonyPatch(typeof(UnitProgressionData))] - private static class UnitProgressionData_LegendaryHero_Patch { - // note: no need to set AssetGuid or anything, 'Bonuses' is the only field accessed - private static BlueprintStatProgression XPcontinuous = new() { - Bonuses = new int[] { - 0,0,2000,5000,9000,15000,23000,35000,51000,75000,105000,155000,220000,315000,445000,635000,890000,1300000,1800000,2550000, - 3600000,4650000,5700000,6750000,7800000,8850000,9900000,10950000,12000000,13050000,14100000,15150000,16200000,17250000, - 18300000,19350000,20400000,21450000,22500000,23550000,24600000 } - }; - - private static BlueprintStatProgression XPexponential = new() { - Bonuses = new int[] { - 0,0,2000,5000,9000,15000,23000,35000,51000,75000,105000,155000,220000,315000,445000,635000,890000,1300000,1800000,2550000, - 3600000,5700000,9900000,18300000,35100000 } - }; - - [HarmonyPatch(nameof(UnitProgressionData.ExperienceTable), MethodType.Getter)] - private static bool Prefix(ref BlueprintStatProgression __result, UnitProgressionData __instance) { - var hashKey = __instance.Owner.HashKey(); - var perSave = settings.perSave; - if (perSave is null) return true; - perSave.charIsLegendaryHero.TryGetValue(hashKey, out var isFakeLegendaryHero); - //Mod.Trace($"UnitProgressionData_ExperienceTable - {__instance.Owner.CharacterName.orange()} isFakeLegoHero:{isFakeLegendaryHero}"); - - if (__instance.Owner.State.Features.LegendaryHero || isFakeLegendaryHero) - __result = Game.Instance.BlueprintRoot.Progression.LegendXPTable; - else if (settings.toggleContinousLevelCap) - __result = XPcontinuous; - else if (settings.toggleExponentialLevelCap) - __result = XPexponential; - else - return true; - - return false; - } - - [HarmonyPatch(nameof(UnitProgressionData.MaxCharacterLevel), MethodType.Getter)] - private static bool Prefix(ref int __result, UnitProgressionData __instance) { - var hashKey = __instance.Owner.HashKey(); - var perSave = settings.perSave; - if (perSave is null) return true; - perSave.charIsLegendaryHero.TryGetValue(hashKey, out var isFakeLegendaryHero); - //Mod.Trace ($"UnitProgressionData_MaxCharacterLevel - {__instance.Owner.CharacterName.orange()} isFakeLegoHero:{isFakeLegendaryHero}"); - - if (__instance.Owner.State.Features.LegendaryHero || isFakeLegendaryHero) - __result = 40; - else if (settings.toggleContinousLevelCap) - __result = 40; - else if (settings.toggleExponentialLevelCap) - __result = 24; - else - return true; - - return false; - } - } -#if false - [HarmonyPatch(typeof(UnitProgressionData), nameof(UnitProgressionData.AddClassLevel))] - public static class UnitProgressionData_AddClassLevel_Patch { - private static readonly MethodInfo UnitProgressionData_GetExperienceTable = - AccessTools.PropertyGetter(typeof(UnitProgressionData), "ExperienceTable"); - - private static readonly FieldInfo BlueprintStatProgression_GetBonuses = - AccessTools.Field(typeof(BlueprintStatProgression), "Bonuses"); - - private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { - var codes = new List<CodeInstruction>(instructions); - var target = FindInsertionPoint(codes); - if (target < 0) { - Mod.Error("UnitProgressionData_AddClassLevel_Patch Transpiler unable to find target!"); - return codes; - } - - codes[target] = new CodeInstruction(OpCodes.Nop); - codes[target + 1] = new CodeInstruction(OpCodes.Ldarg_0); - codes[target + 2] = - new CodeInstruction(new CodeInstruction(OpCodes.Callvirt, UnitProgressionData_GetExperienceTable)); - codes[target + 3] = new CodeInstruction(OpCodes.Ldfld, BlueprintStatProgression_GetBonuses); - - return codes; - } - private static int FindInsertionPoint(List<CodeInstruction> codes) { - for (var i = 0; i < codes.Count; i++) { - if (codes[i].opcode == OpCodes.Ldfld && codes[i].LoadsField(BlueprintStatProgression_GetBonuses)) { - return i - 3; - } - } - - Mod.Error("UnitProgressionData_AddClassLevel_Patch: COULD NOT FIND TARGET"); - return -1; - } - } -#endif - - [HarmonyPatch(typeof(CharSheetCommonLevel), nameof(CharSheetCommonLevel.Initialize))] - private static class CharSheetCommonLevel_FixExperienceBar_Patch { - public static void Postfix(UnitProgressionData data, ref CharSheetCommonLevel __instance) { - __instance.Level.text = "Level " + data.CharacterLevel; - var nextLevel = data.ExperienceTable.Bonuses[data.CharacterLevel + 1]; - var currentLevel = data.ExperienceTable.Bonuses[data.CharacterLevel]; - var experience = data.Experience; - __instance.Exp.text = $"{experience as object}/{nextLevel as object}"; - __instance.Bar.value = (float)(experience - currentLevel) / (float)(nextLevel - currentLevel); - } - } - - [HarmonyPatch(typeof(CharInfoExperienceVM), nameof(CharInfoExperienceVM.RefreshData))] - private static class CharInfoExperienceVM_FixExperienceBar_Patch { - public static void Postfix(ref CharInfoExperienceVM __instance) { - var unit = __instance.Unit.Value; - __instance.NextLevelExp = unit.Progression.ExperienceTable.Bonuses[Mathf.Min(unit.Progression.CharacterLevel + 1, unit.Progression.ExperienceTable.Bonuses.Length - 1)]; - __instance.CurrentLevelExp = unit.Progression.ExperienceTable.Bonuses.ElementAtOrDefault(unit.Progression.CharacterLevel); - } - } - - // ignoreAttributesPointsRemainng - [HarmonyPatch(typeof(StatsDistribution), nameof(StatsDistribution.IsComplete))] - private static class StatsDistribution_IsComplete_Patch { - private static void Postfix(ref bool __result) { - if (settings.toggleIgnoreAttributePointsRemaining) { - __result = true; - } - } - } - - [HarmonyPatch(typeof(SpendAttributePoint), nameof(SpendAttributePoint.Check))] - private static class SpendAttributePoint_Check_Patch { - private static void Postfix(ref bool __result) { - if (settings.toggleIgnoreAttributePointsRemaining) { - __result = true; - } - } - } - - // ignoreSkillPointsRemaining - [HarmonyPatch(typeof(CharGenSkillsPhaseVM), nameof(CharGenSkillsPhaseVM.SelectionStateIsCompleted))] - private static class CharGenSkillsPhaseVM_SelectionStateIsCompleted_Patch { - private static void Postfix(ref bool __result) { - if (settings.toggleIgnoreSkillPointsRemaining) { - __result = true; - } - } - } - // ignoreSkillPointsRemaing, ignoreSkillCap - [HarmonyPatch(typeof(SpendSkillPoint), nameof(SpendSkillPoint.Check), new Type[] { typeof(LevelUpState), typeof(UnitDescriptor) })] - private static class SpendSkillPoint_Check_Patch { - public static bool Prefix(SpendSkillPoint __instance) => !(settings.toggleIgnoreSkillCap || settings.toggleIgnoreSkillPointsRemaining); - private static void Postfix(ref bool __result, SpendSkillPoint __instance, LevelUpState state, UnitDescriptor unit) => __result = StatTypeHelper.Skills.Contains<StatType>(__instance.Skill) - && (settings.toggleIgnoreSkillCap || unit.Stats.GetStat(__instance.Skill).BaseValue < state.NextCharacterLevel) - && (settings.toggleIgnoreSkillPointsRemaining || state.SkillPointsRemaining > 0); - } - // ignoreSkillCap - [HarmonyPatch(typeof(CharGenSkillAllocatorVM), nameof(CharGenSkillAllocatorVM.UpdateSkillAllocator))] - private static class CharGenSkillAllocatorVM_UpdateSkillAllocator_Patch { - public static bool Prefix(CharGenSkillAllocatorVM __instance) { - if (settings.toggleIgnoreSkillCap) { - __instance.IsClassSkill.Value = (bool)__instance.Skill?.ClassSkill; - var stat1 = __instance.m_LevelUpController.Unit.Stats.GetStat(__instance.StatType); - var stat2 = __instance.m_LevelUpController.Preview.Stats.GetStat(__instance.StatType); - __instance.CanAdd.Value = !__instance.m_LevelUpController.State.IsSkillPointsComplete() && __instance.m_LevelUpController.State.SkillPointsRemaining > 0; - __instance.CanRemove.Value = stat2.BaseValue > stat1.BaseValue; - return false; - } - return true; - } - } - - // full HD - [HarmonyPatch(typeof(ApplyClassMechanics), nameof(ApplyClassMechanics.ApplyHitPoints), new Type[] { typeof(LevelUpState), typeof(ClassData), typeof(UnitDescriptor) })] - private static class ApplyClassMechanics_ApplyHitPoints_Patch { - private static void Postfix(LevelUpState state, ClassData classData, ref UnitDescriptor unit) { - if (settings.toggleFullHitdiceEachLevel && unit.IsPartyOrPet() && state.NextClassLevel > 1) { - var newHitDie = ((int)classData.CharacterClass.HitDie / 2) - 1; - unit.Stats.HitPoints.BaseValue += newHitDie; - } -#if false - else if (StringUtils.ToToggleBool(settings.toggleRollHitDiceEachLevel) && unit.IsPartyMemberOrPet() && state.NextLevel > 1) { - int oldHitDie = ((int)classData.CharacterClass.HitDie / 2) + 1; - DiceFormula diceFormula = new DiceFormula(1, classData.CharacterClass.HitDie); - int roll = RuleRollDice.Dice.D(diceFormula); - - unit.Stats.HitPoints.BaseValue -= oldHitDie; - unit.Stats.HitPoints.BaseValue += roll; - } -#endif - } - } - [HarmonyPatch(typeof(PrerequisiteFeature), nameof(PrerequisiteFeature.CheckInternal))] - private static class PrerequisiteFeature_CanLevelUp_Patch { - private static void Postfix(ref bool __result) { - if (settings.toggleIgnoreFeaturePrerequisites) { - __result = true; - } - } - } - [HarmonyPatch(typeof(PrerequisiteFeaturesFromList), nameof(PrerequisiteFeaturesFromList.CheckInternal))] - private static class PrerequisiteFeaturesFromList_CanLevelUp_Patch { - private static void Postfix(ref bool __result) { - if (settings.toggleIgnoreFeatureListPrerequisites) { - __result = true; - } - } - } - - [HarmonyPatch(typeof(FeatureSelectionState), nameof(FeatureSelectionState.IgnorePrerequisites), MethodType.Getter)] - private static class FeatureSelectionState_IgnorePrerequisites_Patch { - private static void Postfix(ref bool __result) { - if (settings.toggleFeaturesIgnorePrerequisites) { - __result = true; - } - } - } - [HarmonyPatch(typeof(IgnorePrerequisites), nameof(IgnorePrerequisites.Ignore), MethodType.Getter)] - private static class IgnorePrerequisites_Ignore_Patch { - private static void Postfix(ref bool __result) { - if (Game.Instance.LevelUpController == null) return; - var state = Game.Instance.LevelUpController.State; - - if (!state.IsClassSelected) { - if (settings.toggleIgnoreClassRestrictions) { - __result = true; - } - } - else { - if (settings.toggleIgnoreFeatRestrictions) { - __result = true; - } - } - } - } - - [HarmonyPatch(typeof(Kingmaker.DialogSystem.Blueprints.BlueprintMythicsSettings), nameof(Kingmaker.DialogSystem.Blueprints.BlueprintMythicsSettings.IsMythicClassUnlocked))] - public static class BlueprintMythicsSettings_IsMythicClassUnlocked_Patch { - private static void Postfix(ref bool __result) { - if (settings.toggleIgnoreClassRestrictions) { - __result = true; - } - } - } - - [HarmonyPatch(typeof(LevelUpController), nameof(LevelUpController.IsPossibleMythicSelection), MethodType.Getter)] - private static class LevelUpControllerIsPossibleMythicSelection_Patch { - private static void Postfix(ref bool __result, LevelUpController __instance) { - if (settings.toggleIgnoreClassRestrictions || settings.toggleAllowCompanionsToBecomeMythic && !__instance.Unit.IsMainCharacter) { - __result = true; - } - } - } - - [HarmonyPatch(typeof(BlueprintCharacterClass), nameof(BlueprintCharacterClass.MeetsPrerequisites))] - private static class BlueprintCharacterClass_MeetsPrerequisites_Patch { - private static void Postfix(ref bool __result, BlueprintCharacterClass __instance, [NotNull] UnitDescriptor unit, [NotNull] LevelUpState state) { - if (!settings.toggleAllowCompanionsToBecomeMythic || unit.IsMainCharacter || !__instance.IsMythic) return; - - if (__instance == BlueprintRoot.Instance.Progression.MythicCompanionClass && - unit.Progression.LastMythicClass != __instance && unit.Progression.LastMythicClass != null) { - __result = false; - return; - } - - if (state.NextMythicLevel == 8 && __instance.m_IsHigherMythic) { - __result = true; - return; - } - - if (unit.Progression.LastMythicClass == __instance) { - __result = true; - return; - } - - if (state.NextMythicLevel == 3 && !__instance.m_IsHigherMythic) { - __result = true; - } - } - } - - [HarmonyPatch(typeof(PrerequisiteCasterTypeSpellLevel), nameof(PrerequisiteCasterTypeSpellLevel.CheckInternal))] - public static class PrerequisiteCasterTypeSpellLevel_Check_Patch { - public static void Postfix( - [CanBeNull] FeatureSelectionState selectionState, - [NotNull] UnitDescriptor unit, - [CanBeNull] LevelUpState state, - ref bool __result) { - if (!unit.IsPartyOrPet()) return; // don't give extra feats to NPCs - if (settings.toggleIgnoreCasterTypeSpellLevel) { - __result = true; - } - } - } - [HarmonyPatch(typeof(PrerequisiteNoArchetype), nameof(PrerequisiteNoArchetype.CheckInternal))] - public static class PrerequisiteNoArchetype_Check_Patch { - public static void Postfix( - [CanBeNull] FeatureSelectionState selectionState, - [NotNull] UnitDescriptor unit, - [CanBeNull] LevelUpState state, - ref bool __result) { - if (!unit.IsPartyOrPet()) return; // don't give extra feats to NPCs - if (settings.toggleIgnoreForbiddenArchetype) { - __result = true; - } - } - } - - [HarmonyPatch(typeof(PrerequisiteStatValue), nameof(PrerequisiteStatValue.CheckInternal))] - public static class PrerequisiteStatValue_Check_Patch { - public static void Postfix( - [CanBeNull] FeatureSelectionState selectionState, - [NotNull] UnitDescriptor unit, - [CanBeNull] LevelUpState state, - ref bool __result) { - if (!unit.IsPartyOrPet()) return; // don't give extra feats to NPCs - if (settings.toggleIgnorePrerequisiteStatValue) { - __result = true; - } - } - } - - [HarmonyPatch(typeof(PrerequisiteClassLevel), nameof(PrerequisiteClassLevel.CheckInternal))] - public static class PrerequisiteClassLevel_Check_Patch { - public static void Postfix( - [CanBeNull] FeatureSelectionState selectionState, - [NotNull] UnitDescriptor unit, - [CanBeNull] LevelUpState state, - PrerequisiteClassLevel __instance, - ref bool __result) { - if (!unit.IsPartyOrPet()) return; // don't give extra feats to NPCs - if (!__result && settings.toggleIgnorePrerequisiteClassLevel && !__instance.HideInUI) { - var characterClass = (BlueprintCharacterClass)(__instance.m_CharacterClass).GetBlueprint(); - if (!characterClass.HideIfRestricted && !characterClass.IsMythic) - __result = true; - } - } - } - - [HarmonyPatch(typeof(BlueprintRace), nameof(BlueprintRace.GetRestrictedByArchetype))] - private static class BlueprintRace_GetRestrictedByArchetype_Patch { - public static bool Prefix(BlueprintRace __instance, UnitDescriptor unit, ref BlueprintArchetype __result) { - if (!settings.toggleIgnoreFeaturePrerequisitesWhenChoosingClass) return true; - __result = null; - return false; - } - } - - [HarmonyPatch(typeof(CharGenClassSelectorItemVM), nameof(CharGenClassSelectorItemVM.SpecialRequiredRace), MethodType.Getter)] - private static class CharGenClassSelectorItemVM_SpecialRequiredRace_Patch { - public static bool Prefix(CharGenClassSelectorItemVM __instance, ref BlueprintRace __result) { - if (!settings.toggleIgnoreFeaturePrerequisitesWhenChoosingClass) return true; - __result = null; - return false; - } - } - - - [HarmonyPatch(typeof(PrerequisiteFeature))] - public static class PrerequisiteFeature_CheckInternal_Patch { - [HarmonyPostfix] - [HarmonyPatch(nameof(PrerequisiteFeature.CheckInternal))] - public static void PostfixCheckInternal([NotNull] UnitDescriptor unit, ref bool __result) { - if (!unit.IsPartyOrPet()) { - return; - } - - if (settings.toggleIgnoreFeaturePrerequisitesWhenChoosingClass) { - __result = true; - } - } - - [HarmonyPostfix] - [HarmonyPatch(nameof(PrerequisiteFeature.ConsiderFulfilled))] - public static void PostfixConsiderFulfilled([NotNull] UnitDescriptor unit, ref bool __result) { - if (!unit.IsPartyOrPet()) { - return; - } - - if (settings.toggleIgnoreFeaturePrerequisitesWhenChoosingClass) { - __result = true; - } - } - } - - [HarmonyPatch(typeof(PrerequisiteAlignment), nameof(PrerequisiteAlignment.CheckInternal))] - public static class PrerequisiteAlignment_Check_Patch { - public static void Postfix( - [CanBeNull] FeatureSelectionState selectionState, - [NotNull] UnitDescriptor unit, - [CanBeNull] LevelUpState state, - ref bool __result) { - if (!unit.IsPartyOrPet()) return; // don't give extra feats to NPCs - if (settings.toggleIgnoreAlignmentWhenChoosingClass) { - __result = true; - } - } - } - - [HarmonyPatch(typeof(PrerequisiteNoFeature), nameof(PrerequisiteNoFeature.CheckInternal))] - public static class PrerequisiteNoFeature_Check_Patch { - public static void Postfix( - [CanBeNull] FeatureSelectionState selectionState, - [NotNull] UnitDescriptor unit, - [CanBeNull] LevelUpState state, - ref bool __result) { - if (!unit.IsPartyOrPet()) return; // don't give extra feats to NPCs - if (settings.toggleIgnoreForbiddenFeatures) { - __result = true; - } - } - } -#if false - [HarmonyPatch(typeof(Spellbook), nameof(Spellbook.AddCasterLevel))] - public static class Spellbook_AddCasterLevel_Patch { - public static bool Prefix() { - return false; - } - - public static void Postfix(ref Spellbook __instance, ref int ___m_CasterLevelInternal, List<BlueprintSpellList> ___m_SpecialLists) { - int maxSpellLevel = __instance.MaxSpellLevel; - ___m_CasterLevelInternal += settings.addCasterLevel; - int maxSpellLevel2 = __instance.MaxSpellLevel; - if (__instance.Blueprint.AllSpellsKnown) { - Traverse addSpecialMethod = Traverse.Create(__instance).Method("AddSpecial", new Type[] { typeof(int), typeof(BlueprintAbility) }); - for (int i = maxSpellLevel + 1; i <= maxSpellLevel2; i++) { - foreach (BlueprintAbility spell in __instance.Blueprint.SpellList.GetSpells(i)) { - __instance.AddKnown(i, spell); - } - foreach (BlueprintSpellList specialList in ___m_SpecialLists) { - foreach (BlueprintAbility spell2 in specialList.GetSpells(i)) { - addSpecialMethod.GetValue(i, spell2); - } - } - } - } - } - } -#endif - [HarmonyPatch(typeof(SpellSelectionData), nameof(SpellSelectionData.CanSelectAnything), new Type[] { typeof(UnitDescriptor) })] - public static class SpellSelectionData_CanSelectAnything_Patch { - public static void Postfix(UnitDescriptor unit, ref bool __result) { - if (!unit.IsPartyOrPet()) return; // don't give extra feats to NPCs - if (settings.toggleSkipSpellSelection) { - __result = false; - } - } - } - - // Let user advance if no options left for feat selection - [HarmonyPatch(typeof(CharGenFeatureSelectorPhaseVM))] - private static class CharGenFeatureSelectorPhaseVM_HandleOptionalFeatSelection_Patch { - [HarmonyPatch(nameof(CharGenFeatureSelectorPhaseVM.CheckIsCompleted))] - [HarmonyPostfix] - private static void Postfix_CharGenFeatureSelectorPhaseVM_CheckIsCompleted(CharGenFeatureSelectorPhaseVM __instance, ref bool __result) { - - if (settings.toggleOptionalFeatSelection) { - __result = true; - } - else if (settings.featsMultiplier != 1) { - var featureSelectorStateVM = __instance.FeatureSelectorStateVM; - var selectionState = featureSelectorStateVM.SelectionState; - var selectionVM = __instance.FeatureSelectorStateVM; - var state = Game.Instance.LevelUpController.State; - IFeatureSelection selection = selection = selectionVM.Feature as IFeatureSelection; - var availableItems = selection?.Items - .Where((IFeatureSelectionItem item) => selection.CanSelect(state.Unit, state, selectionState, item)); - //Main.Log($"CharGenFeatureSelectorPhaseVM_CheckIsCompleted_Patch - availableCount: {availableItems.Count()}"); - if (availableItems.Count() == 0) - __result = true; - } - } - - [HarmonyPatch(nameof(CharGenFeatureSelectorPhaseVM.OnBeginDetailedView))] - [HarmonyPostfix] - private static void Postfix_CharGenFeatureSelectorPhaseVM_OnPostBeginDetailedView(CharGenFeatureSelectorPhaseVM __instance) { - if (settings.toggleOptionalFeatSelection) { - __instance.IsCompleted.Value = true; - } - } - } - #if false - [HarmonyPatch(typeof(CharGenNamePhaseVM))] - private static class CharGenNamePhaseVMPatch { - [HarmonyPatch(nameof(CharGenFeatureSelectorPhaseVM.CheckIsCompleted))] - [HarmonyPostfix] - // This is a hack work around for https://github.com/cabarius/ToyBox/issues/868 - // We basically check to see if the character has gestalt which when you set the name adds more options to select which confuses this VM leaving it thinking this is still true, IsInDetailedView, when it really is not. In this case we see if the character has gestalt options and if it does we just say we are done. - public static void CheckIsCompleted(CharGenNamePhaseVM __instance, ref bool __result) { - if (!settings.toggleMulticlass) return; - Mod.Debug("multiclass is on"); - if (string.IsNullOrEmpty(__instance.InputText)) return; - Mod.Debug($"Has InputText: {__instance.InputText}"); - var unit = __instance.LevelUpController.Unit; - if (!unit.Descriptor?.IsPartyOrPet() ?? false) return; - Mod.Debug($"unit: {unit.CharacterName}"); - var state = __instance.LevelUpController.State; - var useDefaultMulticlassOptions = state.IsCharGen(); - var options = MulticlassOptions.Get(useDefaultMulticlassOptions ? null : unit); - Mod.Debug($"state: {state} - charGen: {useDefaultMulticlassOptions} - {options}"); - if (options == null || options.Count == 0) return; - __instance.m_IsInDetailedView.Value = false; - __result = true; - } - } - #endif - -#if false - [HarmonyPatch(typeof(ProgressionData), nameof(ProgressionData.CalculateLevelEntries))] - public static class ProgressionData_CalculateLevelEntries_Patch { - public static bool Prefix(ProgressionData __instance, ref LevelEntry[] __result) { - var featMultiplier = settings.featsMultiplier; - if (featMultiplier < 2) return true; - List<LevelEntry> levelEntryList = new List<LevelEntry>(); - foreach (LevelEntry levelEntry in __instance.Blueprint.LevelEntries) { - int level = levelEntry.Level; - Main.Log($"levelEntry {level} - {string.Join(", ", levelEntry.Features.Select(f => f.name.cyan()))}"); - var blueprintFeatureBaseList = new List<BlueprintFeatureBase>(levelEntry.Features); - foreach (BlueprintArchetype archetype in __instance.Archetypes) { - foreach (BlueprintFeatureBase feature in (IEnumerable<BlueprintFeatureBase>)archetype.GetRemoveEntry(level).Features) - blueprintFeatureBaseList.Remove(feature); - Main.Log($"adding archetype: {archetype.name.cyan()} - {levelEntry.Level}"); - blueprintFeatureBaseList.AddRange((IEnumerable<BlueprintFeatureBase>)archetype.GetAddEntry(level).Features); - } - if (blueprintFeatureBaseList.Count > 0) { - LevelEntry levelEntry2 = new LevelEntry() { - Level = level - }; - var features = new List<BlueprintFeatureBase>(); - for (int ii = 0; ii < featMultiplier; ii++) { - features = features.Concat(blueprintFeatureBaseList).ToList(); - } - levelEntry2.SetFeatures(features); - levelEntryList.Add(levelEntry2); - } - } - levelEntryList.Sort((e1, e2) => e1.Level.CompareTo(e2.Level)); - __result = levelEntryList.ToArray(); - return false; - } - } - //if (__instance.Archetypes.Count <= 0) { - // List<LevelEntry> levelEntries = new List<LevelEntry>(); - // foreach (LevelEntry levelEntry in __instance.Blueprint.LevelEntries) { - // Main.Log($"adding level entry - {levelEntry.Level}}"); - // for (int ii = 0; ii < featMultiplier; ii++) { - // levelEntries.Add(levelEntry); - // } - // } - // levelEntries.Sort((e1, e2) => e1.Level.CompareTo(e2.Level)); - // __result = levelEntries.ToArray(); - // return false; - //} - -#else - /** - * The feat multiplier is the source of several hard to track down bugs. To quote ArcaneTrixter: - * All story companions feats/backgrounds/etc. most notably a certain wizard who unlearns how to cast spells if your multiplier is at least 8. Also this is retroactive if you ever level up in the future with the multiplier on. - * All mythic 'fake' companions like Skeleton Minion for lich or Azata summon. - * Required adding in "skip feat selection" because it broke level ups. - * Causes certain gestalt combinations to give sudden ridiculous level-ups of companions or sneak attack or kinetic blast. - */ - [HarmonyPatch(typeof(LevelUpHelper), nameof(LevelUpHelper.AddFeaturesFromProgression))] - public static class MultiplyFeatPoints_LevelUpHelper_AddFeatures_Patch { - public static bool Prefix( - [NotNull] LevelUpState state, - [NotNull] UnitDescriptor unit, - [NotNull] IList<BlueprintFeatureBase> features, - FeatureSource source, - int level) { - if (settings.featsMultiplier < 2 || (!settings.toggleFeatureMultiplierCompanions && !unit.IsMainCharacter)) return true; - //Main.Log($"name: {unit.CharacterName} isMemberOrPet:{unit.IsPartyMemberOrPet()}".cyan().bold()); - if (!unit.IsPartyOrPet()) return true; - Mod.Trace($"Log adding {settings.featsMultiplier}x features from {source.Blueprint.name.orange()} : {source.Blueprint.GetType().Name.yellow()} for {unit.CharacterName.green()} {string.Join(", ", state.Selections.Select(s => $"{s.Selection}")).cyan()}"); - foreach (var featureBP in features.OfType<BlueprintFeature>()) { - Mod.Trace($" checking {featureBP.NameSafe().cyan()} : {featureBP.GetType().Name.yellow()}"); - var multiplier = settings.featsMultiplier; - for (var i = 0; i < multiplier; ++i) { - if (featureBP.MeetsPrerequisites(null, unit, state, true)) { - if (featureBP is IFeatureSelection selection && (!selection.IsSelectionProhibited(unit) || selection.IsObligatory())) { - Mod.Trace($" adding: {featureBP.NameSafe().cyan()}".orange()); - state.AddSelection(null, source, selection, level); - } - } - } - var feature = (Kingmaker.UnitLogic.Feature)unit.AddFact(featureBP); - var source1 = source; - var level1 = level; - feature.SetSource(source1, level1); - if (featureBP is BlueprintProgression progression) { - Mod.Trace($" updating unit: {unit.CharacterName.orange()} {progression} bp: {featureBP.NameSafe()}".cyan()); - LevelUpHelper.UpdateProgression(state, unit, progression); - } - } - return false; - } - } - /** - * This alternative re-targets the multiplier into a Postfix instead of a Prefix to reduce the patch foot print, as well as adds progression white listing to make feature multiplication opt in by the developer instead of just multiplying everything always. As setup in this request only the base feat selections that all characters get will be multiplied, which to my mind best suits the name and description of what this setting does. This should also significantly reduce or resolve several associated bugs due to the reduction of scope on this feature - */ - -#endif -#if false - [HarmonyPatch(typeof(LevelUpHelper), nameof(LevelUpHelper.AddFeaturesFromProgression))] - public static class MultiplyFeatPoints_LevelUpHelper_AddFeatures_Patch { - //Defines which progressions are allowed to be multiplied to prevent unexpected behavior - private static readonly BlueprintGuid[] AllowedProgressions = new BlueprintGuid[] { - BlueprintGuid.Parse("5b72dd2ca2cb73b49903806ee8986325") //BasicFeatsProgression - }; - public static void Postfix( - [NotNull] LevelUpState state, - [NotNull] UnitDescriptor unit, - [NotNull] IList<BlueprintFeatureBase> features, - FeatureSource source, - int level) { - - if (settings.featsMultiplier < 2) { return; } - if (!unit.IsPartyOrPet()) { return; } - if (!AllowedProgressions.Any(allowed => source.Blueprint.AssetGuid.Equals(allowed))) { return; } - - Main.Log($"Log adding {settings.featsMultiplier}x feats for {unit.CharacterName}"); - int multiplier = settings.featsMultiplier - 1; - //We filter to only include feat selections of the feat group to prevent things like deities being multiplied - var featSelections = features - .OfType<BlueprintFeatureSelection>() - .Where(s => s.GetGroup() == FeatureGroup.Feat); - foreach (var selection in featSelections) { - if (selection.MeetsPrerequisites(null, unit, state, true) - && (!selection.IsSelectionProhibited(unit) || selection.IsObligatory())) { - - ExecuteByMultiplier(multiplier, () => state.AddSelection(null, source, selection, level)); - } - } - return; - } - private static void ExecuteByMultiplier(int multiplier, Action run = null) { - for (int i = 0;i < multiplier;++i) { - run.Invoke(); - } - } - } -#endif - } -} diff --git a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Limits/Metamagic.cs b/ToyBox/classes/MonkeyPatchin/BagOfPatches/Limits/Metamagic.cs deleted file mode 100644 index 3df4dffce..000000000 --- a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Limits/Metamagic.cs +++ /dev/null @@ -1,76 +0,0 @@ -// borrowed shamelessly and enhanced from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/26, which is under the MIT License -using HarmonyLib; -using Kingmaker; -//using Kingmaker.Controllers.GlobalMap; -using Kingmaker.RuleSystem.Rules; -//using Kingmaker.UI._ConsoleUI.Models; -using Kingmaker.UI.MainMenuUI; -//using Kingmaker.UI.RestCamp; -using Kingmaker.UnitLogic; -using Kingmaker.UnitLogic.Abilities; -using Kingmaker.UnitLogic.FactLogic; -using ModKit; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Reflection; -using System.Reflection.Emit; -//using Kingmaker.UI._ConsoleUI.GroupChanger; -using UnityModManager = UnityModManagerNet.UnityModManager; - -namespace ToyBox.BagOfPatches { - internal static class MetamagicPatches { - public static Settings settings = Main.Settings; - public static Player player = Game.Instance.Player; - - [HarmonyPatch(typeof(MetamagicHelper), nameof(MetamagicHelper.DefaultCost))] - public static class MetamagicHelper_DefaultCost_Patch { - public static void Postfix(ref int __result) { - if (settings.toggleMetamagicIsFree) { - //Mod.Debug($"MetamagicHelper_DefaultCost_Patch - {__result} -> 0"); - __result = Math.Min(0, __result); - } - } - } - - [HarmonyPatch(typeof(RuleCollectMetamagic), nameof(RuleCollectMetamagic.AddMetamagic))] - public static class RuleCollectMetamagic_AddMetamagic_Patch { - public static bool Prefix() => !settings.toggleMetamagicIsFree; - public static void Postfix(ref RuleCollectMetamagic __instance, int ___m_SpellLevel, Feature metamagicFeature) { - if (settings.toggleMetamagicIsFree) { - var component = metamagicFeature.GetComponent<AddMetamagicFeat>(); - if (component == null) { - Mod.Trace(string.Format("Trying to add metamagic feature without metamagic component: {0}", (object)metamagicFeature)); - } - else { - __instance.KnownMetamagics.Add(metamagicFeature); - var metamagic = component.Metamagic; - if (___m_SpellLevel < 0 || ___m_SpellLevel >= 10 || ___m_SpellLevel + component.Metamagic.DefaultCost() > 10 || __instance.SpellMetamagics.Contains(metamagicFeature) || (__instance.Spell.AvailableMetamagic & metamagic) != metamagic) - return; - __instance.SpellMetamagics.Add(metamagicFeature); - } - } - } - } - - [HarmonyPatch(typeof(AbilityData), nameof(AbilityData.RequireFullRoundAction), MethodType.Getter)] - public static class AbilityData_RequireFullRoundAction { - private static bool IsSpontanReplacement(AbilityData abilityData) { - if (settings.toggleMetamagicIsFree) - return false; - return abilityData.IsSpontaneous; - } - - public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { - MethodInfo torep = AccessTools.PropertyGetter(typeof(AbilityData), nameof(AbilityData.IsSpontaneous)); - foreach (CodeInstruction c in instructions) { - if (c.opcode == OpCodes.Call && (c.operand as MethodInfo) == torep) { - Mod.Trace("AbilityData_RequireFullRoundAction found and replaced"); - c.operand = AccessTools.DeclaredMethod(typeof(AbilityData_RequireFullRoundAction), nameof(IsSpontanReplacement)); - } - yield return c; - } - } - } - } -} \ No newline at end of file diff --git a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Limits/Unrestricted.cs b/ToyBox/classes/MonkeyPatchin/BagOfPatches/Limits/Unrestricted.cs deleted file mode 100644 index 78bc1c645..000000000 --- a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Limits/Unrestricted.cs +++ /dev/null @@ -1,136 +0,0 @@ -// borrowed shamelessly and enhanced from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/26, which is under the MIT License -using HarmonyLib; -using Kingmaker; -using Kingmaker.Blueprints.Items.Components; -using Kingmaker.Blueprints.Items.Equipment; -using Kingmaker.DialogSystem.Blueprints; -using Kingmaker.Items; -using Kingmaker.Designers.EventConditionActionSystem.Conditions; -using ModKit; -using Kingmaker.ElementsSystem; -using Kingmaker.Kingdom.Settlements; -using Kingmaker.UnitLogic; -using System; -using System.Collections.Generic; -using UnityModManager = UnityModManagerNet.UnityModManager; -using Kingmaker.EntitySystem.Stats; -using static Kingmaker.EntitySystem.Stats.ModifiableValue; - -namespace ToyBox.BagOfPatches { - internal static class Unrestricted { - public static Settings settings = Main.Settings; - public static Player player = Game.Instance.Player; - - [HarmonyPatch(typeof(EquipmentRestrictionAlignment), nameof(EquipmentRestrictionAlignment.CanBeEquippedBy))] - public static class EquipmentRestrictionAlignment_CanBeEquippedBy_Patch { - public static void Postfix(ref bool __result) { - if (settings.toggleEquipmentRestrictions) { - __result = true; - } - } - } - [HarmonyPatch(typeof(EquipmentRestrictionClass), nameof(EquipmentRestrictionClass.CanBeEquippedBy))] - public static class EquipmentRestrictionClassNew_CanBeEquippedBy_Patch { - public static void Postfix(ref bool __result) { - if (settings.toggleEquipmentRestrictions) { - __result = true; - } - } - } - [HarmonyPatch(typeof(EquipmentRestrictionStat), nameof(EquipmentRestrictionStat.CanBeEquippedBy))] - public static class EquipmentRestrictionStat_CanBeEquippedBy_Patch { - public static void Postfix(ref bool __result) { - if (settings.toggleEquipmentRestrictions) { - __result = true; - } - } - } - [HarmonyPatch(typeof(ItemEntityArmor), nameof(ItemEntityArmor.CanBeEquippedInternal))] - public static class ItemEntityArmor_CanBeEquippedInternal_Patch { - public static void Postfix(ItemEntityArmor __instance, UnitDescriptor owner, ref bool __result) { - if (settings.toggleEquipmentRestrictions) { - var blueprint = __instance.Blueprint as BlueprintItemEquipment; - __result = blueprint != null && blueprint.CanBeEquippedBy(owner); - } - } - } - [HarmonyPatch(typeof(ItemEntityShield), nameof(ItemEntityShield.CanBeEquippedInternal))] - public static class ItemEntityShield_CanBeEquippedInternal_Patch { - public static void Postfix(ItemEntityShield __instance, UnitDescriptor owner, ref bool __result) { - if (settings.toggleEquipmentRestrictions) { - var blueprint = __instance.Blueprint as BlueprintItemEquipment; - __result = blueprint != null && blueprint.CanBeEquippedBy(owner); - } - } - } - [HarmonyPatch(typeof(ItemEntityWeapon), nameof(ItemEntityWeapon.CanBeEquippedInternal))] - public static class ItemEntityWeapon_CanBeEquippedInternal_Patch { - public static void Postfix(ItemEntityWeapon __instance, UnitDescriptor owner, ref bool __result) { - if (settings.toggleEquipmentRestrictions) { - var blueprint = __instance.Blueprint as BlueprintItemEquipment; - __result = blueprint != null && blueprint.CanBeEquippedBy(owner); - } - } - } - - internal static readonly Dictionary<string, bool> PlayerAlignmentIsOverrides = new() { - { "fdc9eb3b03cf8ef4ca6132a04970fb41", false }, // DracoshaIntro_MythicAzata_dialog - Cue_0031 - }; - - [HarmonyPatch(typeof(PlayerAlignmentIs), nameof(PlayerAlignmentIs.CheckCondition))] - public static class PlayerAlignmentIs_CheckCondition_Patch { - public static void Postfix(PlayerAlignmentIs __instance, ref bool __result) { - if (!settings.toggleDialogRestrictions || __instance?.Owner is null) return; - Mod.Debug($"checking {__instance} guid:{__instance.AssetGuid} owner:{__instance.Owner.name} guid: {__instance.Owner.AssetGuid}) value: {__result}"); - if (PlayerAlignmentIsOverrides.TryGetValue(__instance.Owner.AssetGuid.ToString(), out var value)) { Mod.Debug($"overiding {__instance.Owner.name} to {value}"); __result = value; } - else __result = true; - } - } - - [HarmonyPatch(typeof(BlueprintAnswerBase), nameof(BlueprintAnswerBase.IsAlignmentRequirementSatisfied), MethodType.Getter)] - public static class BlueprintAnswerBase_IsAlignmentRequirementSatisfied_Patch { - public static void Postfix(BlueprintAnswerBase __instance, ref bool __result) { - if (settings.toggleDialogRestrictions) { - __result = true; - } - } - } - - [HarmonyPatch(typeof(BlueprintAnswerBase), nameof(BlueprintAnswerBase.IsMythicRequirementSatisfied), MethodType.Getter)] - public static class BlueprintAnswerBase_IsMythicRequirementSatisfied_Patch { - public static void Postfix(ref bool __result) { - if (settings.toggleDialogRestrictionsMythic) { - __result = true; - } - } - } - - [HarmonyPatch(typeof(BlueprintAnswer), nameof(BlueprintAnswer.CanSelect))] - public static class BlueprintAnswer_CanSelect_Patch { - public static void Postfix(ref bool __result) { - if (settings.toggleDialogRestrictionsEverything) { - __result = true; - } - } - } - - [HarmonyPatch(typeof(Spellbook), nameof(Spellbook.CasterLevel), MethodType.Getter)] - public static class Spellbook_CasterLevel_Patch { - public static void Postfix(ref int __result, Spellbook __instance) { - if (settings.toggleUncappedCasterLevel) { - __result = Math.Max(0, __instance.m_BaseLevelInternal + __instance.Blueprint.CasterLevelModifier) + __instance.m_MythicLevelInternal; - } - } - } - - [HarmonyPatch(typeof(Modifier), nameof(Modifier.Stacks), MethodType.Getter)] - public static class ModifiableValue_UpdateValue_Patch { - public static bool Prefix(Modifier __instance) { - if (settings.toggleUnlimitedStatModifierStacking) { - __instance.StackMode = StackMode.ForceStack; - } - return true; - } - } - } -} \ No newline at end of file diff --git a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Localization.cs b/ToyBox/classes/MonkeyPatchin/BagOfPatches/Localization.cs deleted file mode 100644 index 082cb4f7b..000000000 --- a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Localization.cs +++ /dev/null @@ -1,39 +0,0 @@ -using HarmonyLib; -using Kingmaker.Localization; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace ToyBox.BagOfPatches { - internal static class Localization { - - [HarmonyPatch(typeof(LocalizationManager), nameof(LocalizationManager.OnLocaleChanged))] - public class Patch_LocalizationChanged { - [HarmonyPatch] - [HarmonyPrefix] - public static void Prefix() { - "ToyBox Party Editor".AddLocalizedString(); - // how to overwrite existing string - //var unit = ResourcesLibrary.TryGetBlueprint<BlueprintUnit>("afa0eb762a9c4093b8ab29db4d905a13"); - //var localizedString = unit.LocalizedName.String; - //LocalizationManager.CurrentPack.PutString(localizedString.m_Key, "Some Fancy New Name"); - - // how to create a new string and assign it to something - //var localizedString2 = new LocalizedString() { m_Key = "this can be any string. just needs to be unique" }; - //LocalizationManager.CurrentPack.PutString(localizedString2.m_Key, "Some Fancy New Name"); - //var unit2 = ResourcesLibrary.TryGetBlueprint<BlueprintUnit>("afa0eb762a9c4093b8ab29db4d905a13"); - //unit2.LocalizedName.String = localizedString; - } - } - - [HarmonyPatch(typeof(LocalizedString), nameof(LocalizedString.GetActualKey))] - public class Patch_ActualKeyBug - { - [HarmonyPatch] - [HarmonyPostfix] - public static string Postfix(string original) => original ?? ""; - } - } -} diff --git a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Multipliers.cs b/ToyBox/classes/MonkeyPatchin/BagOfPatches/Multipliers.cs deleted file mode 100644 index 1b7b83ad1..000000000 --- a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Multipliers.cs +++ /dev/null @@ -1,306 +0,0 @@ -// borrowed shamelessly and enhanced from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/26, which is under the MIT License - -using HarmonyLib; -using JetBrains.Annotations; -using Kingmaker; -using Kingmaker.Blueprints.Items; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.GameModes; -using Kingmaker.Items; -using Kingmaker.UnitLogic; -using Kingmaker.UnitLogic.Abilities; -using Kingmaker.UnitLogic.Buffs; -using Kingmaker.UnitLogic.Buffs.Blueprints; -using Kingmaker.UnitLogic.Mechanics; -using Kingmaker.UnitLogic.Parts; -using Kingmaker.View; -using System; -using System.Linq; -using UnityEngine; -using UnityModManager = UnityModManagerNet.UnityModManager; -using Kingmaker.Settings; -using Kingmaker.Settings.Difficulty; -using ModKit; -using Kingmaker.Blueprints.Items.Ecnchantments; -using Kingmaker.Utility; -using System.Collections.Generic; -using CameraMode = Kingmaker.View.CameraMode; -using DG.Tweening; -using Kingmaker.Blueprints; -using Kingmaker.Blueprints.Root; -using Kingmaker.QA.Statistics; - -namespace ToyBox.BagOfPatches { - internal static class Multipliers { - public static Settings settings = Main.Settings; - public static Player player = Game.Instance.Player; - - [HarmonyPatch(typeof(EncumbranceHelper), nameof(EncumbranceHelper.GetHeavy))] - private static class EncumbranceHelper_GetHeavy_Patch { - private static void Postfix(ref int __result) => __result = Mathf.RoundToInt(__result * settings.encumberanceMultiplier); - } - - [HarmonyPatch(typeof(EncumbranceHelper), nameof(EncumbranceHelper.GetPartyCarryingCapacity), new Type[] { })] - private static class EncumbranceHelper_GetPartyCarryingCapacity_Patch_1 { - private static void Postfix(ref EncumbranceHelper.CarryingCapacity __result) { - __result.Light = Mathf.RoundToInt(__result.Light * settings.encumberanceMultiplierPartyOnly); - __result.Medium = Mathf.RoundToInt(__result.Medium * settings.encumberanceMultiplierPartyOnly); - __result.Heavy = Mathf.RoundToInt(__result.Heavy * settings.encumberanceMultiplierPartyOnly); - } - } - - [HarmonyPatch(typeof(EncumbranceHelper), nameof(EncumbranceHelper.GetPartyCarryingCapacity), new Type[] { typeof(IEnumerable<UnitReference>) })] - private static class EncumbranceHelper_GetPartyCarryingCapacity_Patch_2 { - private static void Postfix(ref EncumbranceHelper.CarryingCapacity __result) { - __result.Light = Mathf.RoundToInt(__result.Light * settings.encumberanceMultiplierPartyOnly); - __result.Medium = Mathf.RoundToInt(__result.Medium * settings.encumberanceMultiplierPartyOnly); - __result.Heavy = Mathf.RoundToInt(__result.Heavy * settings.encumberanceMultiplierPartyOnly); - } - } - - [HarmonyPatch(typeof(UnitPartWeariness), nameof(UnitPartWeariness.GetFatigueHoursModifier))] - private static class EncumbranceHelper_GetFatigueHoursModifier_Patch { - private static void Postfix(ref float __result) => __result *= (float)Math.Round(settings.fatigueHoursModifierMultiplier, 1); - } - - [HarmonyPatch(typeof(Player), nameof(Player.GainPartyExperience))] - public static class Player_GainPartyExperience_Patch { - [HarmonyPrefix] - public static bool Prefix(Player __instance, ref int gained, ExperienceGainStatistic.GainType statType) { - bool useNormal = true; - switch (statType) { - case ExperienceGainStatistic.GainType.Mob: { - if (settings.useCombatExpSlider) { - gained = Mathf.RoundToInt(gained * (float)Math.Round(settings.experienceMultiplierCombat, 1)); - useNormal = false; - } - }; break; - case ExperienceGainStatistic.GainType.Check: { - if (settings.useSkillChecksExpSlider) { - gained = Mathf.RoundToInt(gained * (float)Math.Round(settings.experienceMultiplierSkillChecks, 1)); - useNormal = false; - } - }; break; - case ExperienceGainStatistic.GainType.Quest: { - if (settings.useQuestsExpSlider) { - gained = Mathf.RoundToInt(gained * (float)Math.Round(settings.experienceMultiplierQuests, 1)); - useNormal = false; - } - }; break; - case ExperienceGainStatistic.GainType.Trap: { - if (settings.useTrapsExpSlider) { - gained = Mathf.RoundToInt(gained * (float)Math.Round(settings.experienceMultiplierTraps, 1)); - useNormal = false; - } - }; break; - } - if (useNormal) { - gained = Mathf.RoundToInt(gained * (float)Math.Round(settings.experienceMultiplier, 1)); - } - return true; - } - } - - [HarmonyPatch(typeof(Player), nameof(Player.GainMoney))] - public static class Player_GainMoney_Patch { - [HarmonyPrefix] - public static bool Prefix(Player __instance, ref long amount) { - amount = Mathf.RoundToInt(amount * (float)Math.Round(settings.moneyMultiplier, 1)); - return true; - } - } - - [HarmonyPatch(typeof(Spellbook), nameof(Spellbook.GetSpellSlotsCount))] - public static class BlueprintSpellsTable_GetCount_Patch { - private static void Postfix(ref int __result, Spellbook __instance, int spellLevel) { - if (__result > 0 && __instance.Blueprint.IsArcanist) { - var spellsKnown = __instance.m_KnownSpells[spellLevel].Count; - __result = Math.Min(Mathf.RoundToInt(__result * settings.arcanistSpellslotMultiplier), spellsKnown); - } - } - } - - [HarmonyPatch(typeof(Spellbook), nameof(Spellbook.GetSpellsPerDay))] - private static class Spellbook_GetSpellsPerDay_Patch { - private static void Postfix(ref int __result, Spellbook __instance) { - if (__instance.Blueprint.MemorizeSpells && !__instance.Blueprint.IsArcanist) { // prepapred spellcaster slots multiplier - __result = Mathf.RoundToInt(__result * (float)Math.Round(settings.memorizedSpellsMultiplier, 1)); - return; - } - - // Spontaneous multiplier - __result = Mathf.RoundToInt(__result * (float)Math.Round(settings.spellsPerDayMultiplier, 1)); - } - } - - [HarmonyPatch(typeof(Player), nameof(Player.GetCustomCompanionCost))] - public static class Player_GetCustomCompanionCost_Patch { - public static bool Prefix(ref bool __state) => !__state; // FIXME - why did Bag of Tricks do this? - - public static void Postfix(ref int __result) => __result = Mathf.RoundToInt(__result * settings.companionCostMultiplier); - } - - /** - public Buff AddBuff( - BlueprintBuff blueprint, - UnitEntityData caster, - TimeSpan? duration, - [CanBeNull] AbilityParams abilityParams = null) { - MechanicsContext context = new MechanicsContext(caster, this.Owner, (SimpleBlueprint)blueprint); - if (abilityParams != null) - context.SetParams(abilityParams); - return this.Manager.Add<Buff>(new Buff(blueprint, context, duration)); - } - */ -#if false - [HarmonyPatch(typeof(Buff), nameof(Buff.AddBuff))] - [HarmonyPatch(new Type[] { typeof(BlueprintBuff), typeof(UnitEntityData), typeof(TimeSpan?), typeof(AbilityParams) })] - public static class Buff_AddBuff_patch { - public static void Prefix(BlueprintBuff blueprint, UnitEntityData caster, ref TimeSpan? duration, [CanBeNull] AbilityParams abilityParams = null) { - try { - if (!caster.IsPlayersEnemy) { - if (duration != null) { - duration = TimeSpan.FromTicks(Convert.ToInt64(duration.Value.Ticks * settings.buffDurationMultiplierValue)); - } - } - } - catch (Exception e) { - Mod.Error(e); - } - - Mod.Debug("Initiator: " + caster.CharacterName + "\nBlueprintBuff: " + blueprint.Name + "\nDuration: " + duration.ToString()); - } - } -#endif - - - private static readonly HashSet<string> badBuffs = settings.buffsToIgnoreForDurationMultiplier; - - private static bool isGoodBuff(BlueprintBuff blueprint) => !blueprint.Harmful && !badBuffs.Contains(blueprint.AssetGuidThreadSafe); - - [HarmonyPatch(typeof(BuffCollection), nameof(BuffCollection.AddBuff), new Type[] { - typeof(BlueprintBuff), - typeof(UnitEntityData), - typeof(TimeSpan?), - typeof(AbilityParams) - })] - public static class BuffCollection_AddBuff_patch { - public static void Prefix(BlueprintBuff blueprint, UnitEntityData caster, ref TimeSpan? duration, [CanBeNull] AbilityParams abilityParams = null) { - try { - if (!caster.IsPlayersEnemy && isGoodBuff(blueprint)) { - if (duration != null) { - duration = GetNewBuffDuration((TimeSpan)duration); - } - } - } - catch (Exception e) { - Mod.Error(e); - } - - //Mod.Debug("Initiator: " + caster.CharacterName + "\nBlueprintBuff: " + blueprint.Name + "\nDuration: " + duration.ToString()); - } - } - - [HarmonyPatch(typeof(BuffCollection), nameof(BuffCollection.AddBuff), new Type[] { - typeof(BlueprintBuff), - typeof(MechanicsContext), - typeof(TimeSpan?) - })] - public static class BuffCollection_AddBuff2_patch { - public static void Prefix(BlueprintBuff blueprint, MechanicsContext parentContext, ref TimeSpan? duration) { - float adjusted = 0; - try { - if (!parentContext.MaybeCaster.IsPlayersEnemy && isGoodBuff(blueprint)) { - if (duration != null) { - var oldDuration = duration; - duration = GetNewBuffDuration((TimeSpan)duration); - //Mod.Warn($"BuffCollection_AddBuff2_patch - buff: {blueprint.name} duration: {oldDuration} => {duration} - ticks: {duration.Value.Ticks} * {settings.buffDurationMultiplierValue}"); - } - } - } - catch (Exception e) { - //Mod.Error($"BuffCollection_AddBuff2_patch - duration: {duration} - ticks: {duration.Value.Ticks} * {settings.buffDurationMultiplierValue} => {adjusted}"); - Mod.Error(e); - } - - //Mod.Debug("Initiator: " + parentContext.MaybeCaster.CharacterName + "\nBlueprintBuff: " + blueprint.Name + "\nDuration: " + duration.ToString()); - } - } - - private static TimeSpan GetNewBuffDuration(TimeSpan originalDuration) { - // deal with large value edge cases, assume that any value over half of Max Ticks divided by our multiplier should be left alone - if (originalDuration == TimeSpan.MaxValue - || (double)originalDuration.Ticks > (((double)TimeSpan.MaxValue.Ticks) / (2.0f * (double)settings.buffDurationMultiplierValue)) - ) - return originalDuration; - // Ok we have a duration we can actually modify without overflow - var ticks = originalDuration.Ticks; - var adjusted = (long)(ticks * settings.buffDurationMultiplierValue); - Mod.Log($"originalDur: {originalDuration} ticks: {ticks} adjusted:{adjusted}"); - adjusted = Math.Max(0, adjusted); - return TimeSpan.FromTicks(Convert.ToInt64(adjusted)); - } - - [HarmonyPatch(typeof(ItemEntity), nameof(ItemEntity.AddEnchantment), new Type[] { - typeof(BlueprintItemEnchantment), - typeof(MechanicsContext), - typeof(Rounds?) - })] - public static class ItemEntity_AddEnchantment_Patch { - public static void Prefix(BlueprintBuff blueprint, MechanicsContext parentContext, ref Rounds? duration) { - try { - if (!parentContext?.MaybeCaster?.IsPlayersEnemy ?? false && isGoodBuff(blueprint)) { - if (duration != null) { - duration = new Rounds((int)(duration.Value.Value * settings.buffDurationMultiplierValue)); - } - } - } - catch (Exception e) { - Mod.Error(e); - } - } - } - - [HarmonyPatch(typeof(DifficultyPresetsList), nameof(DifficultyPresetsList.GetAdjustmentPreset))] - public static class DifficultyPresetList_EnemyHpMultiplier_Patch { - public static void Postfix(ref DifficultyPresetsList.StatsAdjustmentPreset __result, StatsAdjustmentsType preset) { - var hp = preset switch { - StatsAdjustmentsType.ExtraDecline => 0.4f, - StatsAdjustmentsType.StrongDecline => 0.6f, - StatsAdjustmentsType.Decline => 0.8f, - _ => 1f - }; - - __result.HPMultiplier = hp * settings.enemyBaseHitPointsMultiplier; - - if (settings.toggleBrutalUnfair) { - __result.BasicStatBonusMultiplier = Mathf.RoundToInt(2 * (1 + settings.brutalDifficultyMultiplier)); - __result.DerivativeStatBonusMultiplier = Mathf.RoundToInt(2 * (1 + settings.brutalDifficultyMultiplier)); - //__result.HPMultiplier = Mathf.RoundToInt(__result.HPMultiplier * settings.brutalDifficultyMultiplier); - __result.AbilityDCBonus = Mathf.RoundToInt(2 * (1 + settings.brutalDifficultyMultiplier)); - __result.SkillCheckDCBonus = Mathf.RoundToInt(2 * (1 + settings.brutalDifficultyMultiplier)); - } - } - } - - [HarmonyPatch(typeof(VendorLogic), nameof(VendorLogic.GetItemSellPrice), new Type[] { typeof(ItemEntity) })] - private static class VendorLogic_GetItemSellPrice_Patch { - private static void Postfix(ref long __result) => __result = (long)(__result * settings.vendorSellPriceMultiplier); - } - - [HarmonyPatch(typeof(VendorLogic), nameof(VendorLogic.GetItemSellPrice), new Type[] { typeof(BlueprintItem) })] - private static class VendorLogic_GetItemSellPrice_Patch2 { - private static void Postfix(ref long __result) => __result = (long)(__result * settings.vendorSellPriceMultiplier); - } - - [HarmonyPatch(typeof(VendorLogic), nameof(VendorLogic.GetItemBuyPrice), new Type[] { typeof(ItemEntity) })] - private static class VendorLogic_GetItemBuyPrice_Patch { - private static void Postfix(ref long __result) => __result = (long)(__result * settings.vendorBuyPriceMultiplier); - } - - [HarmonyPatch(typeof(VendorLogic), nameof(VendorLogic.GetItemBuyPrice), new Type[] { typeof(BlueprintItem) })] - private static class VendorLogic_GetItemBuyPrice_Patc2h { - private static void Postfix(ref long __result) => __result = (long)(__result * settings.vendorBuyPriceMultiplier); - } - } -} diff --git a/ToyBox/classes/MonkeyPatchin/BagOfPatches/NewChar.cs b/ToyBox/classes/MonkeyPatchin/BagOfPatches/NewChar.cs deleted file mode 100644 index de3553586..000000000 --- a/ToyBox/classes/MonkeyPatchin/BagOfPatches/NewChar.cs +++ /dev/null @@ -1,110 +0,0 @@ -// borrowed shamelessly and enhanced from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/26, which is under the MIT License - -using HarmonyLib; -using Kingmaker; -//using Kingmaker.Controllers.GlobalMap; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.EntitySystem.Stats; -//using Kingmaker.UI._ConsoleUI.Models; -//using Kingmaker.UI.RestCamp; -using Kingmaker.UnitLogic; -using Kingmaker.UnitLogic.Class.LevelUp; -using System; -using Kingmaker.Blueprints; -using Kingmaker.Blueprints.CharGen; -//using Kingmaker.UI._ConsoleUI.GroupChanger; -using UnityModManager = UnityModManagerNet.UnityModManager; - -namespace ToyBox.BagOfPatches { - internal static class NewChar { - public static Settings settings = Main.Settings; - public static Player player = Game.Instance.Player; - - // public LevelUpState([NotNull] UnitEntityData unit, LevelUpState.CharBuildMode mode, bool isPregen) - [HarmonyPatch(typeof(LevelUpState), MethodType.Constructor)] - [HarmonyPatch(new Type[] { typeof(UnitEntityData), typeof(LevelUpState.CharBuildMode), typeof(bool) })] - public static class LevelUpState_Patch { - [HarmonyPriority(Priority.Low)] - public static void Postfix(UnitDescriptor unit, LevelUpState.CharBuildMode mode, ref LevelUpState __instance, bool isPregen) { - if (__instance.IsFirstCharacterLevel) { - if (!__instance.IsPregen) { - var component = unit.Blueprint.GetComponent<StartingStatPointsComponent>(); - var minimumPoints = 0; - if (!settings.characterCreationAbilityPointsOverrideGameMinimums) - minimumPoints = component != null ? component.StartingStatPoints : 25; - // Kludge - there is some weirdness where the unit in the character generator does not return IsCustomCharacter() as true during character creation so I have to check the blueprint. The thing is if I actually try to get the blueprint name the game crashes so I do this kludge calling unit.Blueprint.ToString() - var isCustom = unit.Blueprint.ToString() == "CustomCompanion"; - //Logger.Log($"unit.Blueprint: {unit.Blueprint.ToString()}"); - //Logger.Log($"not pregen - isCust: {isCustom}"); - var pointCount = Math.Max(minimumPoints, __instance.m_Unit.IsCustomCompanion() ? settings.characterCreationAbilityPointsMerc : settings.characterCreationAbilityPointsPlayer); - - //Logger.Log($"points: {pointCount}"); - - __instance.StatsDistribution.Start(pointCount); - } - } - } - } - - [HarmonyPatch(typeof(StatsDistribution), nameof(StatsDistribution.CanRemove))] - public static class StatsDistribution_CanRemove_Patch { - public static void Postfix(ref bool __result, StatType attribute, StatsDistribution __instance) { - if (settings.characterCreationAbilityPointsMin != 7) { - __result = __instance.Available && __instance.StatValues[attribute] > settings.characterCreationAbilityPointsMin; - } - } - } - - [HarmonyPatch(typeof(StatsDistribution), nameof(StatsDistribution.CanAdd))] - public static class StatsDistribution_CanAdd_Patch { - public static void Postfix(ref bool __result, StatType attribute, StatsDistribution __instance) { - var attributeMax = settings.characterCreationAbilityPointsMax; - if (!__instance.Available) { - __result = false; - } - else if (settings.toggleIgnoreAttributeCap) { - __result = true; - } - else { - if (attributeMax <= 18) { - attributeMax = 18; - } - var attributeValue = __instance.StatValues[attribute]; - __result = attributeValue < attributeMax && __instance.GetAddCost(attribute) <= __instance.Points; - } - } - } - [HarmonyPatch(typeof(StatsDistribution), nameof(StatsDistribution.GetAddCost))] - public static class StatsDistribution_GetAddCost_Patch { - public static bool Prefix(StatsDistribution __instance, StatType attribute) { - var attributeValue = __instance.StatValues[attribute]; - return attributeValue > 7 && attributeValue < 17; - } - public static void Postfix(StatsDistribution __instance, ref int __result, StatType attribute) { - var attributeValue = __instance.StatValues[attribute]; - if (attributeValue <= 7) { - __result = 2; - } - if (attributeValue >= 17) { - __result = 4; - } - } - } - [HarmonyPatch(typeof(StatsDistribution), nameof(StatsDistribution.GetRemoveCost))] - public static class StatsDistribution_GetRemoveCost_Patch { - public static bool Prefix(StatsDistribution __instance, StatType attribute) { - var attributeValue = __instance.StatValues[attribute]; - return attributeValue > 7 && attributeValue < 17; - } - public static void Postfix(StatsDistribution __instance, ref int __result, StatType attribute) { - var attributeValue = __instance.StatValues[attribute]; - if (attributeValue <= 7) { - __result = -2; - } - else if (attributeValue >= 17) { - __result = -4; - } - } - } - } -} diff --git a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Pets.cs b/ToyBox/classes/MonkeyPatchin/BagOfPatches/Pets.cs deleted file mode 100644 index 4574ee02d..000000000 --- a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Pets.cs +++ /dev/null @@ -1,53 +0,0 @@ -using HarmonyLib; -using Kingmaker; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.Enums; -using Kingmaker.PubSubSystem; -using Kingmaker.UnitLogic; -using Kingmaker.UnitLogic.Abilities.Components.TargetCheckers; -using Kingmaker.UnitLogic.Parts; -using System; - -namespace ToyBox.BagOfPatches { - internal static class Pets { - public static Settings settings = Main.Settings; - - - [HarmonyPatch(typeof(AbilityTargetIsSuitableMountSize), nameof(AbilityTargetIsSuitableMountSize.CanMount))] - private static class AbilityTargetIsSuitableMountSize_CanMount_Patch { - private static bool Prefix(UnitEntityData master, UnitEntityData pet, ref bool __result) { - if (!settings.toggleMakePetsRidable) return true; - __result = true; - return false; - } - } - [HarmonyPatch(typeof(AbilityTargetIsSuitableMount), nameof(AbilityTargetIsSuitableMount.CanMount))] - private static class AbilityTargetIsSuitableMount_CanMount_Patch { - private static bool Prefix(UnitEntityData master, UnitEntityData pet, ref bool __result) { - if (!settings.toggleRideAnything) return true; - __result = true; - return false; - } - } - [HarmonyPatch(typeof(UnitProgressionData), nameof(UnitProgressionData.GainMythicExperience))] - private static class UnitProgressionData_GainMythicExperience_Patch { - - private static bool Prefix(UnitProgressionData __instance, int experience) { - if (!settings.toggleAllowMythicPets) return true; - if (experience < 1) { - PFLog.Default.Error(string.Format("Current mythic level of {0} is {1}, trying to raise to {2}! Aborting", (object)__instance.Owner, (object)__instance.MythicLevel, (object)(__instance.MythicExperience + experience))); - } - else { - __instance.MythicExperience += experience; - if (__instance.MythicExperience > 10) - PFLog.Default.Error(string.Format("Current mythic level of {0} is {1}, trying to raise to {2}! Can't do this", (object)__instance.Owner, (object)__instance.MythicLevel, (object)(__instance.MythicExperience + experience))); - var pair = UnitPartDualCompanion.GetPair(__instance.Owner.Unit); - if (pair != (UnitDescriptor)null) - pair.Descriptor.Progression.MythicExperience = __instance.MythicExperience; - EventBus.RaiseEvent<IUnitGainMythicExperienceHandler>((Action<IUnitGainMythicExperienceHandler>)(h => h.HandleUnitGainMythicExperience(__instance.Owner, experience))); - } - return false; - } - } - } -} \ No newline at end of file diff --git a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Romance.cs b/ToyBox/classes/MonkeyPatchin/BagOfPatches/Romance.cs deleted file mode 100644 index 0f7f52b05..000000000 --- a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Romance.cs +++ /dev/null @@ -1,206 +0,0 @@ -using HarmonyLib; -using Kingmaker; -using Kingmaker.Blueprints; -using Kingmaker.Designers.EventConditionActionSystem.Conditions; -using Kingmaker.ElementsSystem; -using Kingmaker.UI.MVVM._PCView.ActionBar; -using Kingmaker.UI.MVVM._VM.Tooltip.Templates; -using Kingmaker.UI.UnitSettings; -using ModKit; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using TMPro; -using UnityEngine; - -namespace ToyBox.BagOfPatches { - internal static class Romance { - public static Settings settings = Main.Settings; - public static Player player = Game.Instance.Player; - - // Any Gender Any Romance Overrides - // These modify the PcFemale/PcMale conditions for specific Owner blueprints - internal static readonly Dictionary<string, bool> PcFemaleOverrides = new() { - // Lann - { "2e7f66de32ccad14bbb17855b0d125fb", true }, // Etude LannKTC_WarCamp_LannAndFeelings - { "d9baf40d38ceaf248bd5306f0e344bdb", true }, // Etude LannRomance - { "15fbf08c47b9cf34d8d535765e9a143a", true }, // Answer Answer_0052 - { "2942d51c334b42748822ea2c48093a72", true }, // Answer AnswersList_0201 - { "c68fad6a2d296f54f825eb1557153923", true }, // Dialog WorldwoundEdge_GMBE - // Sosiel - { "1071445514a15ec42a057a987886a0b5", false }, // Cue Cue_0019 - { "8abf3aa7d2244f048abdcfbc48721eff", false }, // Cue Cue_0030 - { "54fea9d1c9e0b69429bec08fb49a40d2", false }, // Cue Cue_0235 - // Camellia - { "0144dcae4dc708744850d81254f28ec4", false }, // Cue Cue_0067_NoGiefSex - { "7fec7b3b23df5f9498083f096b09f055", false } // Answer Answer_0057 - }; - internal static readonly Dictionary<string, bool> PcMaleOverrides = new() { - // Sosiel - { "5170dd15fdfd0094aa561e4f331c269f", true }, // Cue Cue_0018 - { "7364becdf5cc4b94dba30a9fe7c3b790", true }, // Cue Cue_0234 - { "e166872fc2989f548af1b3e2ba8f7156", true }, // Cue Cue_0029 - // Camellia - { "55c0fe80d141ecf40b49c7ad12746afb", true }, // Cue Cue_0016 - { "789ffa9876fd92f439d4b975b16be283", true }, // Cue Cue_0066_GiefSex - { "f263d6ed04831f240bf2a8dce2b5ce33", true }, // Answer Answer_0052 - { "a96fc116bb7af94488b6da41161a47c7", true }, // Answer Answer_0060 - }; - - // Path Romance Overrides - // These modify the EtudeStatus condition for specific Owner blueprints - internal static readonly Dictionary<(string, string), bool> ConditionCheckOverridesLoveIsFree = new() - { - // Lich Path - { ("7f532b681d64f3741a7aa0aebba7c4db", "977f3380-2938-4cc8-a26a-448edc6f9259"), false }, // Etude CamelliaRomance_Start status is: Not Playing; - { ("7f532b681d64f3741a7aa0aebba7c4db", "52632774-cbb4-4eea-ada6-37ec2708e07d"), false }, // Etude WenduagRomance_Active status is: Not Playing; - { ("7f532b681d64f3741a7aa0aebba7c4db", "12928be5-97e8-4e7c-ac5c-02d704289e7f"), false }, // Etude LannRomance_Active status is: Not Playing; - { ("7f532b681d64f3741a7aa0aebba7c4db", "2bbad7a7-5918-4c14-b909-f1a7bbce9248"), false }, // Etude ArueshalaeRomance_Active status is: Not Playing; - { ("7f532b681d64f3741a7aa0aebba7c4db", "55fd2fa2-9644-462a-a02e-23987e05fd62"), false }, // Etude DaeranRomance_Active status is: Not Playing; - { ("7f532b681d64f3741a7aa0aebba7c4db", "b0f684c1-0cc9-4ec7-a4a3-fe47e3d9847c"), false }, // Etude SosielRomance_Active status is: Not Playing; - }; - internal static readonly Dictionary<string, bool> EtudeStatusOverridesLoveIsFree = new() - { - // Lich Path - { "2ebd861e55143014c8067c6832cdf21c", false }, // Cue_0048 - // Vellexia - { "81b15ede1bb2a3e4e926d5ca4be3e193", true }, // Cue_0066 - { "3aa48f68198afe14cb6de752ce80cc8f", true }, // Cue_0078 - // Queen - { "7a160960668f2ef4180cb56edb8388e9", true }, // Cue_0044 - }; - - // Multiple Romances overrides - // This modify the EtudeStatus condition for specific Owner blueprints - internal static readonly Dictionary<(string, string), bool> ConditionCheckOverrides = new() { - { ("39bd3b1e9fb6fef4c8e92674c02295df", "6a5a5f14-0531-421e-8225-f777fd22fa52"), true }, // Not Etude CamelliaRomance_Start status is: Playing; - { ("39bd3b1e9fb6fef4c8e92674c02295df", "0dadef83-142b-4126-9ef3-d2b3d6ac3c00"), true }, // Not Etude WenduagRomance_Active status is: Playing; - { ("39bd3b1e9fb6fef4c8e92674c02295df", "6af9fc46-b172-45f6-991b-95864d7535dd"), true }, // Not Etude LannRomance_Active status is: Playing; - { ("39bd3b1e9fb6fef4c8e92674c02295df", "08562d17-c875-477d-916f-484c86d6d56b"), true }, // Not Etude ArueshalaeRomance_Active status is: Playing; - { ("39bd3b1e9fb6fef4c8e92674c02295df", "eed10502-68fe-4f06-93f6-9a5a344194e1"), true }, // Not Etude DaeranRomance_Active status is: Playing; - { ("39bd3b1e9fb6fef4c8e92674c02295df", "79f99282-5d3a-4b2b-8d2e-61a0dc8f033f"), true }, // Not Etude SosielRomance_Active status is: Playing; - }; - internal static readonly Dictionary<string, bool> EtudeStatusOverrides = new() { - { "f4acc1a428ffbee42965a6f13fe270ac", false }, // Cue_0058 - }; - internal static readonly Dictionary<string, bool> FlagInRangeOverrides = new() { - { "4799a25da39295b43a6eefcd2cb2b4a7", false }, // Etude KTC_Jealousy - }; - - internal static readonly Dictionary<string, bool> EtudeStatusOverridesFriendshipIsMagic = new() { - // Crusaders at large + Queen - { "60ad7865e557c50489244e3f3f7fc6bc", false }, // GalfreyOnTheEdge_Iz_ch5_Dialog - Cue_0015 - { "70f2c9f4876257a4f8c47b5409752aef", true }, // GalfreyOnTheEdge_Iz_ch5_Dialog - Cue_0062 - { "136b18c04f144794b9c5b5d39e0e296e", false }, // GalfreyAfter_Iz_ch5_Dialog - Cue_0017 - { "d9d009964b3f0e945a0d509dc56db853", false }, // GalfreyAfter_Iz_ch5_Dialog - Cue_0063 - { "070e849ca16487340ae9641fcfabab53", true }, // Coronation_Dialogue - Cue_0211 - { "977012d051210674c91dc2c31a3fddbe", true }, // Coronation_Dialogue - Cue_0053 - - { "365405cc55044874893d26759532ea07", false }, // DrezenSiege_Council_Dialogue - Cue_0107 - - // Ciar (commented out due to Lich issue) - // { "5e51a15b014a1bb4181f5feaa4f71e32", false }, // KTC_Ciar_FirstVisit - Cue_0014 - - // Hand of the Inheritor - { "4a056226dce658f4da30d99d851537ec", false }, // Herald_IvoryLabyrinth_dialog - Cue_0011 - { "0a5a3d097018c1041b1eedb6fb26c5c3", false }, // Herald_IvoryLabyrinth_dialog - Cue_0018 - { "223ea6c047133024a96a18049ffe7679", true }, // Herraxa_dialogue - Cue_0268 - - }; - internal static readonly Dictionary<string, bool> FlagInRangeOverridesFriendshipIsMagic = new() { - // Cam - { "8c8b7f25df243dd4799da10e5683ff64", true }, // AfterHorgus_Dialogue - Cue_0024 - - // Hand of the Inheritor - { "308d6b0d4d0c1944dadccf4a4942d085", true }, // Audience_Areelu_c4_dialog - Cue_0067 - { "e3b2f7241dae25548a35c65729c6f50e", true }, // Audience_Areelu_c4_dialog - Cue_0086 - - }; - internal static readonly Dictionary<string, bool> EtudeStatusOverridesAnyMythic = new() { - // Crusade Events - { "d9fd5839ef1a44fe81473fc2bac2078b", true }, // CrusadeEvent05 - }; - - - [HarmonyPatch(typeof(PcFemale), nameof(PcFemale.CheckCondition))] - public static class PcFemale_CheckCondition_Patch { - public static void Postfix(PcFemale __instance, ref bool __result) { - if (!settings.toggleAllowAnyGenderRomance || __instance?.Owner is null) return; - Mod.Debug($"checking {__instance} guid:{__instance.AssetGuid} owner:{__instance.Owner.name} guid: {__instance.Owner.AssetGuid}) value: {__result}"); - if (PcFemaleOverrides.TryGetValue(__instance.Owner.AssetGuid.ToString(), out var value)) { Mod.Debug($"overiding {__instance.Owner.name} to {value}"); __result = value; } - } - } - [HarmonyPatch(typeof(PcMale), nameof(PcMale.CheckCondition))] - public static class PcMale_CheckCondition_Patch { - public static void Postfix(PcMale __instance, ref bool __result) { - if (!settings.toggleAllowAnyGenderRomance || __instance?.Owner is null) return; - Mod.Debug($"checking {__instance} guid:{__instance.AssetGuid} owner:{__instance.Owner.name} guid: {__instance.Owner.AssetGuid}) value: {__result}"); - if (PcMaleOverrides.TryGetValue(__instance.Owner.AssetGuid.ToString(), out var value)) { Mod.Debug($"overiding {__instance.Owner.name} to {value}"); __result = value; } - } - } - - // This is in testing and everything but debug output should be *disabled* for users - [HarmonyPatch(typeof(ItemsEnough), nameof(ItemsEnough.CheckCondition))] - public static class ItemsEnough_CheckCondition_Patch { - public static void Postfix(ItemsEnough __instance, ref bool __result) { - if (!settings.toggleAllowAnyGenderRomance || __instance?.Owner is null) return; - Mod.Debug($"checking {__instance} guid:{__instance.AssetGuid} owner:{__instance.Owner.name} guid: {__instance.Owner.AssetGuid}) value: {__result}"); - // if (PcMaleOverrides.TryGetValue(__instance.Owner.AssetGuid.ToString(), out var value)) { Mod.Debug($"overiding {__instance.Owner.name} to {value}"); __result = value; } - } - } - - [HarmonyPatch(typeof(Condition), nameof(Condition.Check))] - public static class Condition_Check_Patch { - public static void Postfix(Condition __instance, ref bool __result) { - if (__instance?.Owner is null) return; - - var key = (__instance.Owner.AssetGuid.ToString(), __instance.AssetGuid); - if (settings.toggleAllowAnyGenderRomance) { - if (ConditionCheckOverridesLoveIsFree.TryGetValue(key, out var valueLoveIsFree)) { Mod.Debug($"overiding {(__instance.Owner.name, __instance.name)} to {valueLoveIsFree}"); __result = valueLoveIsFree; } - } - if (settings.toggleMultipleRomance) { - if (ConditionCheckOverrides.TryGetValue(key, out var value)) { - Mod.Debug($"overiding {(__instance.Owner.name, __instance.name)} to {value}"); - __result = value; - } - } - } - } - - [HarmonyPatch(typeof(EtudeStatus), nameof(EtudeStatus.CheckCondition))] - public static class EtudeStatus_CheckCondition_Patch { - public static void Postfix(EtudeStatus __instance, ref bool __result) { - if (__instance?.Owner is null) return; - - var key = (__instance.Owner.AssetGuid.ToString()); - if (settings.toggleAllowAnyGenderRomance) { - if (EtudeStatusOverridesLoveIsFree.TryGetValue(key, out var valueLoveIsFree)) { Mod.Debug($"overiding {(__instance.Owner.name)} to {valueLoveIsFree}"); __result = valueLoveIsFree; } - } - if (settings.toggleMultipleRomance) { - if (EtudeStatusOverrides.TryGetValue(key, out var value)) { Mod.Debug($"overiding {__instance.Owner.name} to {value}"); __result = value; } - } - if (settings.toggleFriendshipIsMagic) { - if (EtudeStatusOverridesFriendshipIsMagic.TryGetValue(key, out var valueFriendshipIsMagic)) { Mod.Debug($"overiding {(__instance.Owner.name)} to {valueFriendshipIsMagic}"); __result = valueFriendshipIsMagic; } - } - if (settings.toggleDialogRestrictionsMythic) { - if (EtudeStatusOverridesAnyMythic.TryGetValue(key, out var valueDialogRestrictionsMythic)) { Mod.Debug($"overiding {(__instance.Owner.name)} to {valueDialogRestrictionsMythic}"); __result = valueDialogRestrictionsMythic; } - } - } - } - - [HarmonyPatch(typeof(FlagInRange), nameof(FlagInRange.CheckCondition))] - public static class FlagInRange_CheckCondition_Patch { - public static void Postfix(FlagInRange __instance, ref bool __result) { - if (__instance?.Owner is null) return; - if (settings.toggleMultipleRomance) { - if (FlagInRangeOverrides.TryGetValue(__instance.Owner.AssetGuid.ToString(), out var value)) { Mod.Debug($"overiding {__instance.Owner.name} to {value}"); __result = value; } - } - if (settings.toggleFriendshipIsMagic) { - if (FlagInRangeOverridesFriendshipIsMagic.TryGetValue(__instance.Owner.AssetGuid.ToString(), out var valueFriendshipIsMagic)) { Mod.Debug($"overiding {__instance.Owner.name} to {valueFriendshipIsMagic}"); __result = valueFriendshipIsMagic; } - } - } - } - } -} diff --git a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Selectors.cs b/ToyBox/classes/MonkeyPatchin/BagOfPatches/Selectors.cs deleted file mode 100644 index 9d68ea892..000000000 --- a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Selectors.cs +++ /dev/null @@ -1,30 +0,0 @@ -// borrowed shamelessly and enhanced from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/26, which is under the MIT License - -using HarmonyLib; -using Kingmaker; -using Kingmaker.Controllers.Combat; -//using Kingmaker.Controllers.GlobalMap; -using Kingmaker.EntitySystem.Entities; -//using Kingmaker.UI._ConsoleUI.Models; -//using Kingmaker.UI.RestCamp; -//using Kingmaker.UI._ConsoleUI.GroupChanger; -using UnityModManager = UnityModManagerNet.UnityModManager; - -namespace ToyBox.BagOfPatches { - internal static class Selectors { - public static Settings settings = Main.Settings; - public static Player player = Game.Instance.Player; - [HarmonyPatch(typeof(UnitCombatState), nameof(UnitCombatState.AttackOfOpportunity))] - private static class UnitCombatState_AttackOfOpportunity_Patch { - private static bool Prefix(UnitEntityData target) { - if (settings.toggleAttacksofOpportunity && target.IsPlayerFaction) { - return false; - } - if (UnitEntityDataUtils.CheckUnitEntityData(target, settings.noAttacksOfOpportunitySelection)) { - return false; - } - return true; - } - } - } -} diff --git a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Settlement.cs b/ToyBox/classes/MonkeyPatchin/BagOfPatches/Settlement.cs deleted file mode 100644 index ddf65dc0f..000000000 --- a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Settlement.cs +++ /dev/null @@ -1,53 +0,0 @@ -using HarmonyLib; -using Kingmaker.Kingdom.Settlements; -using System; - -namespace ToyBox.BagOfPatches { - public static class Settlement { - public static Settings settings = Main.Settings; - - [HarmonyPatch(typeof(BlueprintSettlementBuilding), nameof(BlueprintSettlementBuilding.CheckRestrictions), new Type[] { typeof(SettlementState) })] - public static class BlueprintSettlementBuilding_CheckRestrictions_Patch1 { - public static void Postfix(ref bool __result) { - if (settings.toggleIgnoreSettlementRestrictions) { - __result = true; - } - } - } - - [HarmonyPatch(typeof(BlueprintSettlementBuilding), nameof(BlueprintSettlementBuilding.CheckRestrictions), new Type[] { typeof(SettlementState), typeof(SettlementGridTopology.Slot) })] - public static class BlueprintSettlementBuilding_CheckRestrictions_Patch2 { - public static void Postfix(ref bool __result) { - if (settings.toggleIgnoreSettlementRestrictions) { - __result = true; - } - } - } - - [HarmonyPatch(typeof(SettlementState), nameof(SettlementState.CanBuildUprgade), new Type[] { typeof(BlueprintSettlementBuilding) })] - public static class SettlementState_CanBuildUprgade_Patch { - public static void Postfix(ref bool __result) { - if (settings.toggleIgnoreSettlementRestrictions) { - __result = true; - } - } - } - - [HarmonyPatch(typeof(SettlementState), nameof(SettlementState.CanBuildByLevel), new Type[] { typeof(BlueprintSettlementBuilding) })] - public static class SettlementState_CanBuildByLevel_Patch { - private static void Postfix(ref bool __result) { - if (settings.toggleIgnoreSettlementRestrictions) { - __result = true; - } - } - } - - [HarmonyPatch(typeof(SettlementState), nameof(SettlementState.CanBuild), new Type[] { typeof(BlueprintSettlementBuilding) })] - public static class SettlementState_CanBuild_Patch { - public static void Postfix(ref bool __result) { - if (settings.toggleIgnoreSettlementRestrictions) - __result = true; - } - } - } -} diff --git a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Spellbooks.cs b/ToyBox/classes/MonkeyPatchin/BagOfPatches/Spellbooks.cs deleted file mode 100644 index 8a6426ca4..000000000 --- a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Spellbooks.cs +++ /dev/null @@ -1,23 +0,0 @@ -using HarmonyLib; -using Kingmaker.UnitLogic; - -namespace ToyBox.BagOfPatches { - internal static class Spellbooks { - public static Settings settings = Main.Settings; - -#if false - [HarmonyPatch(typeof(Spellbook), nameof(Spellbook.GetMaxSpellLevel))] - public static class Spellbook_GetMaxSpellLevel_Patch { - public static bool Prefix(Spellbook __instance, ref int __result) { - int num = -1; - for (var spellLevel = 0; spellLevel <= 10; ++spellLevel) { - if (__instance.Blueprint.SpellsPerDay.GetCount(__instance.CasterLevel, spellLevel).HasValue) - num = spellLevel; - } - __result = num != 9 || !(bool)__instance.Owner.State.Features.EnableSpellLevel10 ? num : 10; - return true; - } - } -#endif - } -} \ No newline at end of file diff --git a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Summons.cs b/ToyBox/classes/MonkeyPatchin/BagOfPatches/Summons.cs deleted file mode 100644 index 0207056a6..000000000 --- a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Summons.cs +++ /dev/null @@ -1,175 +0,0 @@ -// borrowed shamelessly and enhanced from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/26, which is under the MIT License - -using HarmonyLib; -using Kingmaker; -using Kingmaker.AreaLogic.SummonPool; -using Kingmaker.Blueprints; -//using Kingmaker.Controllers.GlobalMap; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.RuleSystem.Rules; -//using Kingmaker.UI._ConsoleUI.Models; -//using Kingmaker.UI.RestCamp; -using Kingmaker.UnitLogic.Parts; -using Kingmaker.Utility; -using System; -//using Kingmaker.UI._ConsoleUI.GroupChanger; -using Kingmaker.UI.ActionBar; -using TurnBased.Controllers; -using UnityEngine; -using UnityModManager = UnityModManagerNet.UnityModManager; -using ModKit; -using Kingmaker.View; -using Kingmaker.UI.Common; -using System.Collections.Generic; -using System.Linq; -using Kingmaker.UI.MVVM._VM.ActionBar; -using Kingmaker.UI.UnitSettings; -using Kingmaker.UnitLogic.Abilities; -using Kingmaker.EntitySystem.Persistence; -using static Kingmaker.Visual.Animation.Kingmaker.Actions.UnitAnimationActionHandEquip; -using Kingmaker.UnitLogic.Abilities.Blueprints; - -namespace ToyBox.BagOfPatches { - internal static class Summons { - public static Settings settings = Main.Settings; - public static Player player = Game.Instance.Player; - private static bool SummonedByPlayerFaction = false; - - // prefill actionbar slots for controlled summon with spell-like abilities and charge ability - [HarmonyPatch(typeof(ActionBarVM), nameof(ActionBarVM.SetMechanicSlots))] - private static class ActionBarVM_SetMechanicSlots_Patch { - private static bool Prefix(ActionBarVM __instance, UnitEntityData unit) { - if (settings.toggleMakeSummmonsControllable && !LoadingProcess.Instance.IsLoadingInProcess && unit != null && unit.IsSummoned()) { - if (unit.UISettings.GetSlot(0, unit) is MechanicActionBarSlotEmpty) { - var index = 1; - foreach (var ability in unit.Abilities) { - if (ability.Blueprint.AssetGuidThreadSafe == "c78506dd0e14f7c45a599990e4e65038") { //Setting charge ability to first slot - unit.UISettings.SetSlot(unit, ability, 0); - } - else if (index < __instance.Slots.Count && ability.Blueprint.Type != AbilityType.CombatManeuver && ability.Blueprint.Type != AbilityType.Physical) { - unit.UISettings.SetSlot(unit, ability, index++); - } - } - } - } - return true; - } - } - - [HarmonyPatch(typeof(UIUtility), nameof(UIUtility.GetGroup))] - private static class UIUtility_GetGroup_Patch { - private static void Postfix(ref List<UnitEntityData> __result) { - if (settings.toggleMakeSummmonsControllable) { - try { - __result.AddRange(Game.Instance.Player.Group.Select(u => u).Where(u => u.IsSummoned())); - } - catch {} - } - } - } - - [HarmonyPatch(typeof(Player), nameof(Player.MoveCharacters))] - private static class Player_MoveCharacters_Patch { - private static void Postfix() { - if (settings.toggleMakeSummmonsControllable) { - foreach (var unit in Game.Instance.Player.Group) { - if (unit.IsSummoned()) { - var view = unit.View; - if (view != null) { - view.StopMoving(); - } - unit.Position = Game.Instance.Player.MainCharacter.Value.Position; - unit.DesiredOrientation = Game.Instance.Player.MainCharacter.Value.Orientation; - } - } - } - } - } - - [HarmonyPatch(typeof(SummonPool), nameof(SummonPool.Register))] - private static class SummonPool_Register_Patch { - private static void Postfix(ref UnitEntityData unit) { - //if (settings.toggleSetSpeedOnSummon) { - // unit.Descriptor.Stats.GetStat(StatType.Speed).BaseValue = settings.setSpeedOnSummonValue; - //} - - if (settings.toggleMakeSummmonsControllable && SummonedByPlayerFaction) { - // Main.Log($"SummonPool.Register: Unit [{unit.CharacterName}] [{unit.UniqueId}]"); - UnitEntityDataUtils.Charm(unit); - //unit.Ensure<UnitPartFollowUnit>().Init(Game.Instance.Player.MainCharacter.Value, true, false); -#if false - if (unit.Blueprint.AssetGuid == "6fdf7a3f850a1eb48bfbf44d9d0f45dd" && StringUtils.ToToggleBool(settings.toggleDisableWarpaintedSkullAbilityForSummonedBarbarians)) // WarpaintedSkullSummonedBarbarians - { - if (unit.Body.Head.HasItem && unit.Body.Head.Item?.Blueprint?.AssetGuid == "5d343648bb8887d42b24cbadfeb36991") // WarpaintedSkullItem - { - unit.Body.Head.Item.Ability.Deactivate(); - Common.ModLoggerDebug(unit.Body.Head.Item.Name + "'s ability active: " + unit.Body.Head.Item.Ability.Active); - } - } -#endif - SummonedByPlayerFaction = false; - } - -#if false - if (StringUtils.ToToggleBool(settings.toggleRemoveSummonsGlow)) { - unit.Buffs.RemoveFact(Utilities.GetBlueprintByGuid<BlueprintFact>("706c182e86d9be848b59ddccca73d13e")); // SummonedCreatureVisual - unit.Buffs.RemoveFact(Utilities.GetBlueprintByGuid<BlueprintFact>("e4b996b5168fe284ab3141a91895d7ea")); // NaturalAllyCreatureVisual - } -#endif - } - } - - [HarmonyPatch(typeof(RuleSummonUnit), MethodType.Constructor, new Type[] { - typeof(UnitEntityData), - typeof(BlueprintUnit), - typeof(Vector3), - typeof(Rounds), - typeof(int) } - )] - public static class RuleSummonUnit_Constructor_Patch { - public static void Prefix(UnitEntityData initiator, BlueprintUnit blueprint, Vector3 position, ref Rounds duration, ref int level, RuleSummonUnit __instance) { - Mod.Debug($"old duration: {duration} level: {level} \n mult: {settings.summonDurationMultiplier1} levelInc: {settings.summonLevelModifier1}\n initiatior: {initiator} tweakTarget: {settings.summonTweakTarget1} shouldTweak: {UnitEntityDataUtils.CheckUnitEntityData(initiator, settings.summonTweakTarget1)}"); - if (UnitEntityDataUtils.CheckUnitEntityData(initiator, settings.summonTweakTarget1)) { - if (settings.summonDurationMultiplier1 != 1) { - duration = new Rounds(Convert.ToInt32(duration.Value * settings.summonDurationMultiplier1)); - } - if (settings.summonLevelModifier1 != 0) { - level = Math.Max(0, Math.Min(level + (int)settings.summonLevelModifier1, 20)); - } - } - else if (UnitEntityDataUtils.CheckUnitEntityData(initiator, settings.summonTweakTarget2)) { - if (settings.summonDurationMultiplier2 != 1) { - duration = new Rounds(Convert.ToInt32(duration.Value * settings.summonDurationMultiplier2)); - } - if (settings.summonLevelModifier2 >= 0) { - level = Math.Max(0, Math.Min(level + (int)settings.summonLevelModifier1, 20)); - } - } - Mod.Debug($"new duration: {duration} level: {level}"); - - if (settings.toggleMakeSummmonsControllable) { - SummonedByPlayerFaction = initiator.IsPlayerFaction; - } - Mod.Debug("Initiator: " + initiator.CharacterName + $"(PlayerFaction : {initiator.IsPlayerFaction})" + "\nBlueprint: " + blueprint.CharacterName + "\nDuration: " + duration.Value); - } - } - - [HarmonyPatch(typeof(ActionBarManager), nameof(ActionBarManager.CheckTurnPanelView))] - internal static class ActionBarManager_CheckTurnPanelView_Patch { - private static void Postfix(ActionBarManager __instance) { - if (settings.toggleMakeSummmonsControllable && CombatController.IsInTurnBasedCombat()) { - Traverse.Create((object)__instance).Method("ShowTurnPanel", Array.Empty<object>()).GetValue(); - } - } - } - - [HarmonyPatch(typeof(UnitEntityData), nameof(UnitEntityData.IsDirectlyControllable), MethodType.Getter)] - public static class UnitEntityData_IsDirectlyControllable_Patch { - public static void Postfix(UnitEntityData __instance, ref bool __result) { - if (settings.toggleMakeSummmonsControllable && __instance.Descriptor.IsPartyOrPet() && !__result && __instance.Get<UnitPartSummonedMonster>() != null && !__instance.Descriptor.State.IsFinallyDead && !__instance.Descriptor.State.IsPanicked && !__instance.IsDetached && !__instance.PreventDirectControl) { - __result = true; - } - } - } - } -} diff --git a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Tweaks.cs b/ToyBox/classes/MonkeyPatchin/BagOfPatches/Tweaks.cs deleted file mode 100644 index 0a2b36669..000000000 --- a/ToyBox/classes/MonkeyPatchin/BagOfPatches/Tweaks.cs +++ /dev/null @@ -1,691 +0,0 @@ -// borrowed shamelessly and enhanced from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/26, which is under the MIT License -using HarmonyLib; -using JetBrains.Annotations; -using Kingmaker; -using Kingmaker.Blueprints; -using Kingmaker.Blueprints.Items.Armors; -using Kingmaker.Blueprints.Items.Components; -using Kingmaker.Blueprints.Items.Equipment; -using Kingmaker.Cheats; -using Kingmaker.Controllers; -using Kingmaker.Controllers.Combat; -using Kingmaker.Controllers.MapObjects; -using Kingmaker.Controllers.Rest; -using Kingmaker.Designers.EventConditionActionSystem.Actions; -using Kingmaker.EntitySystem; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.EntitySystem.Stats; -using Kingmaker.Enums; -using Kingmaker.Globalmap; -using Kingmaker.Items; -using Kingmaker.Kingdom; -using Kingmaker.Kingdom.Tasks; -using Kingmaker.Kingdom.UI; -using Kingmaker.PubSubSystem; -using Kingmaker.RuleSystem.Rules; -using Kingmaker.RuleSystem.Rules.Abilities; -using Kingmaker.Tutorial; -using Kingmaker.UI.FullScreenUITypes; -using Kingmaker.UI.Group; -using Kingmaker.UI.Kingdom; -using Kingmaker.UI.MainMenuUI; -using Kingmaker.UnitLogic; -using Kingmaker.UnitLogic.Abilities; -using Kingmaker.UnitLogic.Abilities.Blueprints; -using Kingmaker.UnitLogic.Abilities.Components.CasterCheckers; -using Kingmaker.UnitLogic.Abilities.Components.TargetCheckers; -using Kingmaker.UnitLogic.ActivatableAbilities; -using Kingmaker.UnitLogic.Class.Kineticist; -using Kingmaker.UnitLogic.Class.Kineticist.ActivatableAbility; -using Kingmaker.UnitLogic.Mechanics.Actions; -using Kingmaker.UnitLogic.Parts; -using Kingmaker.Utility; -using Kingmaker.View.MapObjects; -using Kingmaker.Visual.Sound; -using ModKit; -using Owlcat.Runtime.Core.Utils; -using Owlcat.Runtime.Visual.RenderPipeline.RendererFeatures.FogOfWar; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using Kingmaker.Armies.TacticalCombat.Controllers; -using UnityEngine; -using static Kingmaker.Utility.MassLootHelper; -using Object = UnityEngine.Object; -using Kingmaker.Blueprints.Area; -using Kingmaker.Globalmap.State; -using ToyBox; - -namespace ToyBox.BagOfPatches { - internal static class Tweaks { - public static Settings settings = Main.Settings; - public static Player player = Game.Instance.Player; - private static readonly BlueprintGuid rage_barbarian = BlueprintGuid.Parse("df6a2cce8e3a9bd4592fb1968b83f730"); - private static readonly BlueprintGuid rage_blood = BlueprintGuid.Parse("e3a0056eedac7754ca9a50603ba05177"); - private static readonly BlueprintGuid rage_focused = BlueprintGuid.Parse("eccb3f963b3f425dac1f5f384927c3cc"); - private static readonly BlueprintGuid rage_demon = BlueprintGuid.Parse("260daa5144194a8ab5117ff568b680f5"); - - // private static bool CanCopySpell([NotNull] BlueprintAbility spell, [NotNull] Spellbook spellbook) => spellbook.Blueprint.CanCopyScrolls && !spellbook.IsKnown(spell) && spellbook.Blueprint.SpellList.Contains(spell); - - [HarmonyPatch(typeof(CopyScroll), nameof(CopyScroll.CanCopySpell))] - [HarmonyPatch(new Type[] { typeof(BlueprintAbility), typeof(Spellbook) })] - public static class CopyScroll_CanCopySpell_Patch { - private static bool Prefix() => false; - - private static void Postfix([NotNull] BlueprintAbility spell, [NotNull] Spellbook spellbook, ref bool __result) { - if (spellbook.IsKnown(spell)) { - __result = false; - return; - } - var spellListContainsSpell = spellbook.Blueprint.SpellList.Contains(spell); - - if (settings.toggleSpontaneousCopyScrolls && spellbook.Blueprint.Spontaneous && spellListContainsSpell) { - __result = true; - return; - } - - __result = spellbook.Blueprint.CanCopyScrolls && spellListContainsSpell; - } - } - - [HarmonyPatch(typeof(KingdomUIEventWindow), nameof(KingdomUIEventWindow.OnClose))] - public static class KingdomUIEventWindow_OnClose_Patch { - public static bool Prefix(ref bool __state) { - __state = settings.toggleInstantEvent; - return !__state; - } - - public static void Postfix(bool __state, KingdomEventUIView ___m_KingdomEventView, KingdomEventHandCartController ___m_Cart) { - if (__state) { - if (___m_KingdomEventView != null) { - EventBus.RaiseEvent((IEventSceneHandler h) => h.OnEventSelected(null, ___m_Cart)); - - if (___m_KingdomEventView.IsFinished || ___m_KingdomEventView.m_Event.AssociatedTask?.AssignedLeader == null || ___m_KingdomEventView.Blueprint.NeedToVisitTheThroneRoom) { - return; - } - - var inProgress = ___m_KingdomEventView.IsInProgress; - var leader = ___m_KingdomEventView.m_Event.AssociatedTask?.AssignedLeader; - - if (!inProgress || leader == null) { - return; - } - - ___m_KingdomEventView.Event.Resolve(___m_KingdomEventView.Task); - - if (___m_KingdomEventView.RulerTimeRequired <= 0) { - return; - } - - foreach (var unitEntityData in player.AllCharacters) { - RestController.ApplyRest(unitEntityData.Descriptor); - } - - new KingdomTimelineManager().MaybeUpdateTimeline(); - } - } - } - } - [HarmonyPatch(typeof(KingdomTaskEvent), nameof(KingdomTaskEvent.SkipPlayerTime), MethodType.Getter)] - public static class KingdomTaskEvent_SkipPlayerTime_Patch { - public static void Postfix(ref int __result) { - if (settings.toggleInstantEvent) { - __result = 0; - } - } - } - - [HarmonyPatch(typeof(KingdomUIEventWindowFooter), nameof(KingdomUIEventWindowFooter.OnStart))] - public static class KingdomUIEventWindowFooter_OnStart_Patch { - public static bool Prefix(ref bool __state) { - __state = settings.toggleInstantEvent; - return !__state; - } - - public static void Postfix(KingdomEventUIView ___m_KingdomEventView, bool __state) { - if (__state) { - EventBus.RaiseEvent((IKingdomUIStartSpendTimeEvent h) => h.OnStartSpendTimeEvent(___m_KingdomEventView.Blueprint)); - var kingdomTaskEvent = ___m_KingdomEventView?.Task; - EventBus.RaiseEvent((IKingdomUICloseEventWindow h) => h.OnClose()); - kingdomTaskEvent?.Start(false); - - if (kingdomTaskEvent == null) { - return; - } - - if (kingdomTaskEvent.IsFinished || kingdomTaskEvent.AssignedLeader == null || ___m_KingdomEventView.Blueprint.NeedToVisitTheThroneRoom) { - return; - } - - kingdomTaskEvent.Event.Resolve(kingdomTaskEvent); - - if (___m_KingdomEventView.RulerTimeRequired <= 0) { - return; - } - foreach (var unitEntityData in player.AllCharacters) { - RestController.ApplyRest(unitEntityData.Descriptor); - } - new KingdomTimelineManager().MaybeUpdateTimeline(); - } - } - } - - [HarmonyPatch(typeof(FogOfWarArea), nameof(FogOfWarArea.RevealOnStart), MethodType.Getter)] - public static class FogOfWarArea_Active_Patch { - private static bool Prefix(ref bool __result) { - if (!settings.toggleNoFogOfWar) return true; - __result = true; - return false; - // // We need this to avoid hanging the game on launch - // if (Main.Enabled && Main.IsInGame && __result != null && settings != null) { - // __result.enabled = !settings.toggleNoFogOfWar; - // } - } - } - - [HarmonyPatch(typeof(GameHistoryLog), nameof(GameHistoryLog.HandlePartyCombatStateChanged))] - private static class GameHistoryLog_HandlePartyCombatStateChanged_Patch { - private static void Postfix(ref bool inCombat) { - if (!inCombat && settings.toggleRestoreSpellsAbilitiesAfterCombat) { - var partyMembers = Game.Instance.Player.PartyAndPets; - foreach (var u in partyMembers) { - foreach (var resource in u.Descriptor.Resources) - u.Descriptor.Resources.Restore(resource); - foreach (var spellbook in u.Descriptor.Spellbooks) - spellbook.Rest(); - u.Brain.RestoreAvailableActions(); - } - } - if (!inCombat && settings.toggleRechargeItemsAfterCombat) { - - } - if (!inCombat && settings.toggleInstantRestAfterCombat) { - CheatsCombat.RestAll(); - } - if (inCombat && (settings.toggleEnterCombatAutoRage || settings.toggleEnterCombatAutoRage)) { - foreach (var unit in Game.Instance.Player.Party) { - var flag = true; - if (settings.toggleEnterCombatAutoRageDemon) { // we prefer demon rage, as it's more powerful - foreach (var ability in unit.Abilities) { - if (ability.Blueprint.AssetGuid == rage_demon && ability.Data.IsAvailableForCast) { - Kingmaker.RuleSystem.Rulebook.Trigger(new RuleCastSpell(ability.Data, unit)); - ability.Data.Spend(); - flag = false; // if demon rage is active, we skip the normal rage checks - break; - } - } - } - if (flag && settings.toggleEnterCombatAutoRage) { - foreach (var activatable in unit.ActivatableAbilities) { - if (activatable.Blueprint.AssetGuid == rage_barbarian - || activatable.Blueprint.AssetGuid == rage_blood - || activatable.Blueprint.AssetGuid == rage_focused) { - activatable.IsOn = true; - break; - } - } - } - } - } -#if false - if (!inCombat && settings.toggleRestoreItemChargesAfterCombat) { - Cheats.RestoreAllItemCharges(); - } - - if (inCombat && StringUtils.ToToggleBool(settings.toggleArmourChecksPenalty0) && StringUtils.ToToggleBool(settings.toggleArmourChecksPenalty0OutOfCombatOnly)) { - foreach (UnitEntityData unitEntityData in Game.Instance.Player.Party) { - Common.RecalculateArmourItemStats(unitEntityData); - } - } - if (!inCombat && StringUtils.ToToggleBool(settings.toggleArmourChecksPenalty0) && StringUtils.ToToggleBool(settings.toggleArmourChecksPenalty0OutOfCombatOnly)) { - foreach (UnitEntityData unitEntityData in Game.Instance.Player.Party) { - Common.RecalculateArmourItemStats(unitEntityData); - } - } -#endif - } - } - - [HarmonyPatch(typeof(GroupController), nameof(GroupController.WithRemote), MethodType.Getter)] - private static class GroupController_WithRemote_Patch { - private static void Postfix(GroupController __instance, ref bool __result) { - if (settings.toggleAccessRemoteCharacters) { - if (__instance.FullScreenEnabled) { - switch (Traverse.Create(__instance).Field("m_FullScreenUIType").GetValue()) { - case FullScreenUIType.Inventory: - case FullScreenUIType.CharacterScreen: - case FullScreenUIType.SpellBook: - case FullScreenUIType.Vendor: - __result = true; - break; - } - } - } - } - } - - [HarmonyPatch(typeof(AbilityData), nameof(AbilityData.RequireMaterialComponent), MethodType.Getter)] - public static class AbilityData_RequireMaterialComponent_Patch { - public static void Postfix(ref bool __result) { - if (settings.toggleMaterialComponent) { - __result = false; - } - } - } - - [HarmonyPatch(typeof(BlueprintArmorType), nameof(BlueprintArmorType.HasDexterityBonusLimit), MethodType.Getter)] - public static class BlueprintArmorType_HasDexterityBonusLimit_Patch { - public static bool Prefix(ref bool __result) { - if (settings.toggleIgnoreMaxDexterity) { - __result = false; - return false; - } - return true; - } - } - - [HarmonyPatch(typeof(BlueprintArmorType), nameof(BlueprintArmorType.ArmorChecksPenalty), MethodType.Getter)] - public static class BlueprintArmorType_ArmorChecksPenalty_Patch { - public static bool Prefix(ref int __result) { - if (settings.toggleIgnoreArmorChecksPenalty) { - __result = 0; - return false; - } - return true; - } - } - - [HarmonyPatch(typeof(ItemEntityArmor), nameof(ItemEntityArmor.RecalculateStats))] - public static class ItemEntityArmor_RecalculateStats_Patch { - public static void Postfix(ItemEntityArmor __instance) { - if (settings.toggleIgnoreSpeedReduction) { - if (__instance.m_Modifiers != null) { - __instance.m_Modifiers.ForEach(delegate (ModifiableValue.Modifier m) { - var appliedTo = m.AppliedTo; - var desc = m.ModDescriptor; - if (appliedTo == __instance.Wielder.Stats.Speed && (desc == ModifierDescriptor.Shield || desc == ModifierDescriptor.Armor)) { - m.Remove(); - } - }); - } - } - } - } - - [HarmonyPatch(typeof(BlueprintArmorType), nameof(BlueprintArmorType.ArcaneSpellFailureChance), MethodType.Getter)] - public static class BlueprintArmorType_ArcaneSpellFailureChance_Patch { - public static bool Prefix(ref int __result) { - if (settings.toggleIgnoreSpellFailure) { - __result = 0; - return false; - } - return true; - } - } - - [HarmonyPatch(typeof(RuleCastSpell))] - public static class RuleCastSpell_SpellFailureChance_Patch { - [HarmonyPatch(nameof(RuleCastSpell.SpellFailureChance), MethodType.Getter)] - [HarmonyPrefix] - public static bool PrefixSpellFailureChance(ref int __result) { - if (settings.toggleIgnoreSpellFailure) { - __result = 0; - return false; - } - return true; - } - - [HarmonyPatch(nameof(RuleCastSpell.ArcaneSpellFailureChance), MethodType.Getter)] - [HarmonyPrefix] - public static bool PrefixArcaneSpellFailureChance(ref int __result) { - if (settings.toggleIgnoreSpellFailure) { - __result = 0; - return false; - } - return true; - } - } - - [HarmonyPatch(typeof(RuleDrainEnergy), nameof(RuleDrainEnergy.TargetIsImmune), MethodType.Getter)] - private static class RuleDrainEnergy_Immune_Patch { - public static void Postfix(RuleDrainEnergy __instance, ref bool __result) { - if (__instance.Target.Descriptor.IsPartyOrPet() && settings.togglePartyNegativeLevelImmunity) { - __result = true; - } - } - } - - [HarmonyPatch(typeof(RuleDealStatDamage), nameof(RuleDealStatDamage.Immune), MethodType.Getter)] - private static class RuleDealStatDamage_Immune_Patch { - public static void Postfix(RuleDrainEnergy __instance, ref bool __result) { - if (__instance.Target.Descriptor.IsPartyOrPet() && settings.togglePartyAbilityDamageImmunity) { - __result = true; - } - } - } - - [HarmonyPatch] - private static class AbilityAlignment_IsRestrictionPassed_Patch { - [HarmonyPatch(typeof(AbilityCasterAlignment), nameof(AbilityCasterAlignment.IsCasterRestrictionPassed))] - [HarmonyPostfix] - public static void PostfixCasterRestriction(ref bool __result) { - if (settings.toggleIgnoreAbilityAlignmentRestriction) { - __result = true; - } - } - - [HarmonyPatch(typeof(UnitPartForbiddenSpellbooks), nameof(UnitPartForbiddenSpellbooks.IsForbidden))] - [HarmonyPostfix] - public static void PostfixForbiddenSpellbookRestriction(ref bool __result) { - if (settings.toggleIgnoreAbilityAlignmentRestriction) { - __result = false; - } - } - - [HarmonyPatch(typeof(UnitPartForbiddenSpellbooks), nameof(UnitPartForbiddenSpellbooks.Add))] - [HarmonyPrefix] - public static bool PrefixForbidSpellbook(ForbidSpellbookReason reason) { - if (settings.toggleIgnoreAbilityAlignmentRestriction && reason == ForbidSpellbookReason.Alignment) { // Don't add to forbidden list - return false; - } - return true; - } - - [HarmonyPatch(typeof(AbilityTargetAlignment), nameof(AbilityTargetAlignment.IsTargetRestrictionPassed))] - [HarmonyPostfix] - public static void PostfixTargetRestriction(ref bool __result) { - if (settings.toggleIgnoreAbilityAlignmentRestriction) { - __result = true; - } - } - } - - [HarmonyPatch] - private static class AbilityData_CanBeCastByCaster_Patch { - [HarmonyPatch(typeof(AbilityData), nameof(AbilityData.CanBeCastByCaster), MethodType.Getter)] - [HarmonyPrefix] - public static bool PostfixCasterRestriction(ref bool __result, AbilityData __instance) { - if (settings.toggleIgnoreAbilityAnyRestriction && __instance?.Caster?.Unit?.Descriptor?.IsPartyOrPet() == true) { - __result = true; - return false; - } - return true; - } - } - - [HarmonyPatch(typeof(MainMenuBoard), nameof(MainMenuBoard.Update))] - private static class MainMenuButtons_UpdatePatch { - private static void Postfix() { - if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) { - Main.freshlyLaunched = false; - Mod.Warn("Auto Load Save on Launch disabled"); - return; - } - if (settings.toggleAutomaticallyLoadLastSave && Main.freshlyLaunched) { - Main.freshlyLaunched = false; - - var mainMenu = UnityEngine.Object.FindObjectOfType<MainMenu>(); - mainMenu.EnterGame(new Action(() => { - if (Game.Instance.SaveManager.GetLatestSave() == null) - return; - Game.Instance.LoadGame(Game.Instance.SaveManager.GetLatestSave()); - })); - } - Main.freshlyLaunched = false; - } - } - - [HarmonyPatch(typeof(Player), nameof(Player.GameOver))] - private static class Player_GameOverReason_Patch { - private static bool Prefix(Player __instance, Player.GameOverReasonType reason) { - if (!settings.toggleGameOverFixLeeerrroooooyJenkins || reason != Player.GameOverReasonType.EssentialUnitIsDead) return true; - return false; - } - } - - [HarmonyPatch(typeof(Tutorial), nameof(Tutorial.IsBanned))] - private static class Tutorial_IsBanned_Patch { - private static bool Prefix(ref Tutorial __instance, ref bool __result) { - if (settings.toggleForceTutorialsToHonorSettings) { - // __result = !__instance.HasTrigger ? __instance.Owner.IsTagBanned(__instance.Blueprint.Tag) : __instance.Banned; - __result = __instance.Owner.IsTagBanned(__instance.Blueprint.Tag) || __instance.Banned; - //modLogger.Log($"hasTrigger: {__instance.HasTrigger} tag: {__instance.Blueprint.Tag} isTagBanned:{__instance.Owner.IsTagBanned(__instance.Blueprint.Tag)} this.Banned: {__instance.Banned} ==> {__result}"); - return false; - } - return true; - } - } - - [HarmonyPatch(typeof(ItemsCollection), nameof(ItemsCollection.DeltaWeight))] - public static class NoWeight_Patch1 { - public static void Refresh(bool value) { - if (value) - Game.Instance.Player.Inventory.Weight = 0f; - else - Game.Instance.Player.Inventory.UpdateWeight(); - } - - public static bool Prefix(ItemsCollection __instance) { - if (!settings.toggleEquipmentNoWeight) return true; - - if (__instance.IsPlayerInventory) { - __instance.Weight = 0f; - } - return false; - } - } - - [HarmonyPatch(typeof(UnitBody), nameof(UnitBody.EquipmentWeight), MethodType.Getter)] - public static class NoWeight_Patch2 { - public static bool Prefix(ref float __result) { - if (!settings.toggleEquipmentNoWeight) return true; - - __result = 0f; - return false; - } - } - - [HarmonyPatch(typeof(ItemEntity), nameof(ItemEntity.IsUsableFromInventory), MethodType.Getter)] - public static class ItemEntity_IsUsableFromInventory_Patch { - // Allow Item Use From Inventory During Combat - public static bool Prefix(ItemEntity __instance, ref bool __result) { - if (!settings.toggleUseItemsDuringCombat) return true; - - var item = __instance.Blueprint as BlueprintItemEquipment; - __result = item?.Ability != null; - return false; - } - } - - [HarmonyPatch(typeof(Unrecruit), nameof(Unrecruit.RunAction))] - public class Unrecruit_RunAction_Patch { - public static bool Prefix() => !settings.toggleBlockUnrecruit; - } - - [HarmonyPatch(typeof(Kingmaker.Designers.EventConditionActionSystem.Conditions.RomanceLocked), nameof(Kingmaker.Designers.EventConditionActionSystem.Conditions.RomanceLocked.CheckCondition))] - public static class RomanceLocked_CheckCondition_Patch { - public static void Postfix(ref bool __result) { - if (settings.toggleMultipleRomance) { - __result = false; - } - } - } - - [HarmonyPatch(typeof(ContextActionReduceBuffDuration), nameof(ContextActionReduceBuffDuration.RunAction))] - public static class ContextActionReduceBuffDuration_RunAction_Patch { - public static bool Prefix(ContextActionReduceBuffDuration __instance) { - if (settings.toggleExtendHexes && !Game.Instance.Player.IsInCombat - && (__instance.TargetBuff.name.StartsWith("WitchHex") || __instance.TargetBuff.name.StartsWith("ShamanHex"))) { - __instance.Target.Unit.Buffs.GetBuff(__instance.TargetBuff).IncreaseDuration(new TimeSpan(0, 10, 0)); - return false; - } - return true; - } - } - - [HarmonyPatch(typeof(UnitPartActivatableAbility), nameof(UnitPartActivatableAbility.GetGroupSize))] - public static class UnitPartActivatableAbility_GetGroupSize_Patch { - public static List<ActivatableAbilityGroup> groups = Enum.GetValues(typeof(ActivatableAbilityGroup)).Cast<ActivatableAbilityGroup>().ToList(); - public static bool Prefix(ActivatableAbilityGroup group, ref int __result) { - if (settings.toggleAllowAllActivatable && groups.Any(group)) { - __result = 99; - return false; - } - return true; - } - } - - [HarmonyPatch(typeof(ActivatableAbility), nameof(ActivatableAbility.SetIsOn))] - public static class ActivatableAbility_SetIsOn_Patch { - public static void Prefix(ref bool value, ActivatableAbility __instance) { - if (settings.toggleAllowAllActivatable && __instance.Blueprint.Group == ActivatableAbilityGroup.Judgment) { - value = true; - } - } - } - - [HarmonyPatch(typeof(RestrictionCanGatherPower), nameof(RestrictionCanGatherPower.IsAvailable))] - public static class RestrictionCanGatherPower_IsAvailable_Patch { - public static bool Prefix(ref bool __result) { - if (!settings.toggleKineticistGatherPower) { - return true; - } - - __result = true; - return false; - } - } - - [HarmonyPatch(typeof(KineticistAbilityBurnCost), nameof(KineticistAbilityBurnCost.GetTotal))] - public static class KineticistAbilityBurnCost_GetTotal_Patch { - public static void Postfix(ref int __result) => __result = Math.Max(0, __result - settings.kineticistBurnReduction); - } - - [HarmonyPatch(typeof(UnitPartMagus), nameof(UnitPartMagus.IsSpellCombatThisRoundAllowed))] - public static class UnitPartMagus_IsSpellCombatThisRoundAllowed_Patch { - public static void Postfix(ref bool __result, UnitPartMagus __instance) { - if (settings.toggleAlwaysAllowSpellCombat && __instance.Owner != null && __instance.Owner.IsPartyOrPet()) { - __result = true; - } - } - } - - [HarmonyPatch(typeof(GlobalMapPathManager), nameof(GlobalMapPathManager.GetTimeToCapital))] - public static class GlobalMapPathManager_GetTimeToCapital_Patch { - public static void Postfix(bool andBack, ref TimeSpan? __result) { - if (settings.toggleInstantChangeParty && andBack && __result != null) { - __result = TimeSpan.Zero; - } - } - } - -#if false - [HarmonyPatch(typeof(IngameMenuManager), nameof(IngameMenuManager.OpenGroupManager))] - private static class IngameMenuManager_OpenGroupManager_Patch { - private static bool Prefix(IngameMenuManager __instance) { - if (settings.toggleInstantPartyChange) { - var startChangedPartyOnGlobalMap = __instance.GetType().GetMethod("StartChangedPartyOnGlobalMap", BindingFlags.NonPublic | BindingFlags.Instance); - startChangedPartyOnGlobalMap.Invoke(__instance, new object[] { }); - return false; - } - return true; - } - } -#endif - public static class UnitEntityData_CanRollPerception_Extension { - public static bool TriggerReroll = false; - public static bool CanRollPerception(UnitEntityData unit) { - if (TriggerReroll) { - return true; - } - - return unit.HasMotionThisTick; - } - } - - [HarmonyPatch(typeof(PartyPerceptionController), nameof(PartyPerceptionController.Tick))] - public static class PartyPerceptionController_Tick_Patch { - public static MethodInfo HasMotionThisTick_Method = AccessTools.DeclaredMethod(typeof(UnitEntityData), "get_HasMotionThisTick"); - public static MethodInfo CanRollPerception_Method = AccessTools.DeclaredMethod(typeof(UnitEntityData_CanRollPerception_Extension), "CanRollPerception"); - - private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { - foreach (var instr in instructions) { - if (instr.Calls(HasMotionThisTick_Method)) { - yield return new CodeInstruction(OpCodes.Call, CanRollPerception_Method); - } - else { - yield return instr; - } - } - } - - private static void Postfix() => UnitEntityData_CanRollPerception_Extension.TriggerReroll = false; - } - - [HarmonyPatch(typeof(UnitCombatState), nameof(UnitCombatState.Engage))] - public static class UnitCombatState_Engage_Patch { - private static void Postfix(UnitCombatState __instance) { - UnitEntityDataUtils.maybeKill(__instance); - } - } - - [HarmonyPatch(typeof(UnitCombatState), nameof(UnitCombatState.JoinCombat))] - public static class UnitCombatState_JoinCombat_Patch { - private static void Postfix(UnitCombatState __instance) { - UnitEntityDataUtils.maybeKill(__instance); - } - } - - [HarmonyPatch(typeof(TacticalCombatUnitEngagementController))] - public static class TacticalCombatUnitEngagementControllerPatch { - [HarmonyPatch(nameof(TacticalCombatUnitEngagementController.Tick))] - [HarmonyPostfix] - public static void Tick(TacticalCombatUnitEngagementController __instance) { - if (settings.togglekillOnEngage) { - Actions.KillAllTacticalUnits(); - } - } - - } - - [HarmonyPatch(typeof(AkSoundEngineController), nameof(AkSoundEngineController.OnApplicationFocus))] - public static class AkSoundEngineController_OnApplicationFocus_Patch { - private static bool Prefix(AkSoundEngineController __instance) { - if (settings.toggleContinueAudioOnLostFocus) { - return false; - } - else { - return true; - } - } - } - - [HarmonyPatch(typeof(SoundState), nameof(SoundState.OnApplicationFocusChanged))] - public static class SoundState_OnApplicationFocusChanged_Patch { - private static bool Prefix(SoundState __instance) { - if (settings.toggleContinueAudioOnLostFocus) { - return false; - } - return true; - } - } - [HarmonyPatch(typeof(FogOfWarController), "<CollectRevealers>g__CollectUnit|15_0")] - public static class FogOfWarController_CollectRevealers_CompilerMethod_Patch { - public static void Prefix(UnitEntityData unit) { - var revealer = unit.View.SureFogOfWarRevealer(); - if (settings.fowMultiplier != 1) { - revealer.DefaultRadius = false; - revealer.UseDefaultFowBorder = false; - revealer.Radius = FogOfWarController.VisionRadius * settings.fowMultiplier; - } - else { - revealer.DefaultRadius = true; - revealer.UseDefaultFowBorder = true; - revealer.Radius = 1.0f; - } - } - } - } -} diff --git a/ToyBox/classes/MonkeyPatchin/BagOfPatches/zToDo.cs b/ToyBox/classes/MonkeyPatchin/BagOfPatches/zToDo.cs deleted file mode 100644 index ea6cf2b2c..000000000 --- a/ToyBox/classes/MonkeyPatchin/BagOfPatches/zToDo.cs +++ /dev/null @@ -1,1999 +0,0 @@ -// borrowed shamelessly and enchanced from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/26, which is under the MIT License -#if false -using HarmonyLib; -using JetBrains.Annotations; -using Kingmaker; -using Kingmaker.AreaLogic.QuestSystem; -using Kingmaker.AreaLogic.SummonPool; -using Kingmaker.Assets.Controllers.GlobalMap; -using Kingmaker.Blueprints; -using Kingmaker.Blueprints.Area; -using Kingmaker.Blueprints.Classes; -using Kingmaker.Blueprints.Classes.Prerequisites; -using Kingmaker.Blueprints.Classes.Selection; -using Kingmaker.Blueprints.Classes.Spells; -using Kingmaker.Blueprints.Facts; -using Kingmaker.Blueprints.Items; -using Kingmaker.Blueprints.Items.Armors; -using Kingmaker.Blueprints.Items.Components; -using Kingmaker.Blueprints.Items.Equipment; -using Kingmaker.Blueprints.Items.Shields; -using Kingmaker.Blueprints.Items.Weapons; -using Kingmaker.Blueprints.Root; -using Kingmaker.Cheats; -using Kingmaker.Controllers; -using Kingmaker.Controllers.Clicks.Handlers; -using Kingmaker.Controllers.Combat; -//using Kingmaker.Controllers.GlobalMap; -using Kingmaker.Controllers.Rest; -using Kingmaker.Controllers.Rest.Cooking; -using Kingmaker.Controllers.Units; -using Kingmaker.Designers; -using Kingmaker.Designers.EventConditionActionSystem.Actions; -using Kingmaker.Designers.EventConditionActionSystem.Evaluators; -using Kingmaker.DialogSystem.Blueprints; -using Kingmaker.Dungeon; -using Kingmaker.Dungeon.Blueprints; -using Kingmaker.Dungeon.Units.Debug; -using Kingmaker.ElementsSystem; -using Kingmaker.EntitySystem; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.EntitySystem.Persistence; -using Kingmaker.EntitySystem.Persistence.JsonUtility; -using Kingmaker.EntitySystem.Stats; -using Kingmaker.Enums; -using Kingmaker.Enums.Damage; -using Kingmaker.Formations; -using DG.Tweening; -using Kingmaker.GameModes; -using Kingmaker.Globalmap; -using Kingmaker.Items; -using Kingmaker.Kingdom; -using Kingmaker.Kingdom.Blueprints; -using Kingmaker.Kingdom.Settlements; -using Kingmaker.Kingdom.Tasks; -using Kingmaker.Kingdom.UI; -using Kingmaker.PubSubSystem; -using Kingmaker.RandomEncounters; -using Kingmaker.RuleSystem; -using Kingmaker.RuleSystem.Rules; -using Kingmaker.RuleSystem.Rules.Abilities; -using Kingmaker.RuleSystem.Rules.Damage; -using Kingmaker.TextTools; -using Kingmaker.UI; -//using Kingmaker.UI._ConsoleUI.Models; -using Kingmaker.UI.Common; -using Kingmaker.UI.FullScreenUITypes; -using Kingmaker.UI.Group; -using Kingmaker.UI.IngameMenu; -using Kingmaker.UI.Kingdom; -using Kingmaker.UI.Log; -using Kingmaker.UI.MainMenuUI; -using Kingmaker.UI.MVVM; -using Kingmaker.UI.MVVM.CharGen; -using Kingmaker.UI.MVVM.CharGen.Phases; -using Kingmaker.UI.MVVM.CharGen.Phases.Mythic; -using Kingmaker.UI.RestCamp; -using Kingmaker.UI.ServiceWindow; -using Kingmaker.UI.ServiceWindow.LocalMap; -using Kingmaker.UnitLogic; -using Kingmaker.UnitLogic.Abilities; -using Kingmaker.UnitLogic.Abilities.Blueprints; -using Kingmaker.UnitLogic.Abilities.Components; -using Kingmaker.UnitLogic.Abilities.Components.CasterCheckers; -using Kingmaker.UnitLogic.ActivatableAbilities; -using Kingmaker.UnitLogic.Alignments; -using Kingmaker.UnitLogic.Buffs; -using Kingmaker.UnitLogic.Buffs.Blueprints; -using Kingmaker.UnitLogic.Class.Kineticist; -using Kingmaker.UnitLogic.Class.LevelUp; -using Kingmaker.UnitLogic.Class.LevelUp.Actions; -using Kingmaker.UnitLogic.Commands; -using Kingmaker.UnitLogic.Commands.Base; -using Kingmaker.UnitLogic.FactLogic; -using Kingmaker.UnitLogic.Mechanics; -using Kingmaker.UnitLogic.Mechanics.Conditions; -using Kingmaker.UnitLogic.Parts; -using Kingmaker.Utility; -using Kingmaker.View; -using Kingmaker.View.MapObjects; -using Kingmaker.View.MapObjects.InteractionRestrictions; -using Kingmaker.View.Spawners; -using Kingmaker.Visual; -using Kingmaker.Visual.Animation.Kingmaker.Actions; -using Kingmaker.Visual.HitSystem; -using Kingmaker.Visual.LocalMap; -using Kingmaker.Visual.Sound; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -//using Kingmaker.UI._ConsoleUI.GroupChanger; -using Kingmaker.UI.ActionBar; -using Owlcat.Runtime.Visual.RenderPipeline.RendererFeatures.FogOfWar; -using TMPro; -using TurnBased.Controllers; -using UnityEngine; -using UnityEngine.Events; -using UnityEngine.EventSystems; -using UnityEngine.SceneManagement; -using UnityEngine.UI; -using static Kingmaker.UnitLogic.Class.LevelUp.LevelUpState; -using UnityModManager = UnityModManagerNet.UnityModManager; - -namespace ToyBox { -#if false - static class HarmonyPatches_ToDo { - public static Settings settings = Main.settings; - public static UnityModManager.ModEntry.ModLogger modLogger = Logger.modLogger; - public static Player player = Game.Instance.Player; - - [HarmonyPatch(typeof(UnitEntityData), "CreateView")] - public static class UnitEntityData_CreateView_Patch { - public static void Prefix(ref UnitEntityData __instance) { - if (StringUtils.ToToggleBool(settings.toggleSpiderBegone)) { - SpidersBegone.CheckAndReplace(ref __instance); - } - } - } - - [HarmonyPatch(typeof(BlueprintUnit), "PreloadResources")] - public static class BlueprintUnit_PreloadResources_Patch { - public static void Prefix(ref BlueprintUnit __instance) { - if (StringUtils.ToToggleBool(settings.toggleSpiderBegone)) { - SpidersBegone.CheckAndReplace(ref __instance); - } - } - } - - [HarmonyPatch(typeof(EntityCreationController), "SpawnUnit")] - [HarmonyPatch(new Type[] { typeof(BlueprintUnit), typeof(Vector3), typeof(Quaternion), typeof(SceneEntitiesState) })] - public static class EntityCreationControllert_SpawnUnit_Patch1 { - public static void Prefix(ref BlueprintUnit unit) { - if (StringUtils.ToToggleBool(settings.toggleSpiderBegone)) { - SpidersBegone.CheckAndReplace(ref unit); - } - } - } - - [HarmonyPatch(typeof(EntityCreationController), "SpawnUnit")] - [HarmonyPatch(new Type[] { typeof(BlueprintUnit), typeof(UnitEntityView), typeof(Vector3), typeof(Quaternion), typeof(SceneEntitiesState) })] - public static class EntityCreationControllert_SpawnUnit_Patch2 { - public static void Prefix(ref BlueprintUnit unit) { - if (StringUtils.ToToggleBool(settings.toggleSpiderBegone)) { - SpidersBegone.CheckAndReplace(ref unit); - } - } - - } - - [HarmonyPatch(typeof(ContextConditionAlignment), "CheckCondition")] - public static class ContextConditionAlignment_CheckCondition_Patch { - public static void Postfix(ref bool __result, ContextConditionAlignment __instance) { - if (StringUtils.ToToggleBool(settings.toggleReverseCasterAlignmentChecks)) { - if (__instance.CheckCaster) { - __result = !__result; - } - } - } - } - - [HarmonyPatch(typeof(RuleSummonUnit), MethodType.Constructor)] - [HarmonyPatch(new Type[] { typeof(UnitEntityData), typeof(BlueprintUnit), typeof(Vector3), typeof(Rounds), typeof(int) })] - public static class RuleSummonUnit_Constructor_Patch { - public static void Prefix(UnitEntityData initiator, BlueprintUnit blueprint, Vector3 position, ref Rounds duration, ref int level, RuleSummonUnit __instance) { - if (StringUtils.ToToggleBool(settings.toggleSummonDurationMultiplier) && UnitEntityDataUtils.CheckUnitEntityData(initiator, (UnitSelectType)settings.indexSummonDurationMultiplier)) { - duration = new Rounds(Convert.ToInt32(duration.Value * settings.finalSummonDurationMultiplierValue)); ; - } - - if (StringUtils.ToToggleBool(settings.toggleSetSummonLevelTo20) && UnitEntityDataUtils.CheckUnitEntityData(initiator, (UnitSelectType)settings.indexSetSummonLevelTo20)) { - level = 20; - } - - if (StringUtils.ToToggleBool(settings.toggleMakeSummmonsControllable)) { - Storage.SummonedByPlayerFaction = initiator.IsPlayerFaction; - } - - Logger.ModLoggerDebug("Initiator: " + initiator.CharacterName + $"(PlayerFaction : {initiator.IsPlayerFaction})" + "\nBlueprint: " + blueprint.CharacterName + "\nPosition: " + position.ToString() + "\nDuration: " + duration.Value + "\nLevel: " + level); - } - } - - [HarmonyPatch(typeof(ActionBarManager), "CheckTurnPanelView")] - internal static class ActionBarManager_CheckTurnPanelView_Patch { - private static void Postfix(ActionBarManager __instance) { - if (StringUtils.ToToggleBool(settings.toggleMakeSummmonsControllable) && CombatController.IsInTurnBasedCombat()) { - Traverse.Create((object)__instance).Method("ShowTurnPanel", Array.Empty<object>()).GetValue(); - } - } - } - - [HarmonyPatch(typeof(UberLogger.Logger), "ForwardToUnity")] - static class UberLoggerLogger_ForwardToUnity_Patch { - static void Prefix(ref object message) { - if (StringUtils.ToToggleBool(settings.toggleUberLoggerForwardPrefix)) { - string message1 = "[UberLogger] " + message as string; - message = message1 as object; - } - } - } - - - [HarmonyPatch(typeof(DungeonStageInitializer), "Initialize")] - static class DungeonStageInitializer_Initialize_Patch { - static void Prefix(BlueprintDungeonArea area) { - Logger.ModLoggerDebug("Game.Instance.Player.DungeonState.Stage: " + Game.Instance.Player.DungeonState.Stage); - } - } - [HarmonyPatch(typeof(DungeonDebug), "SaveStage")] - static class DungeonDebug_SaveStage_Patch_Pre { - static void Prefix(string filename) { - Logger.ModLoggerDebug("DungeonDebug.SaveStage filename: " + filename); - Logger.ModLoggerDebug("DungeonDebug.SaveStage Path: " + Path.Combine(Application.persistentDataPath, "DungeonStages")); - } - } - [HarmonyPatch(typeof(DungeonDebug), "SaveStage")] - static class DungeonDebug_SaveStage_Patch_Post { - static void Postfix(string filename) { - if (settings.settingShowDebugInfo) { - try { - string str = File.ReadAllText(Path.Combine(Application.persistentDataPath, $"DungeonStages\\{filename}")); - modLogger.Log($"START---{filename}---START\n" + str + $"\nEND---{filename}---END"); - } - catch (Exception e) { - modLogger.Log(e.ToString()); - } - } - } - } - - [HarmonyPatch(typeof(RuleCalculateArmorCheckPenalty), "OnTrigger")] - public static class RuleCalculateArmorCheckPenalty_OnTrigger_Patch { - private static bool Prefix(RuleCalculateArmorCheckPenalty __instance) { - if (StringUtils.ToToggleBool(settings.toggleArmourChecksPenalty0)) { - if (!StringUtils.ToToggleBool(settings.toggleArmourChecksPenalty0OutOfCombatOnly)) { - Traverse.Create(__instance).Property("Penalty").SetValue(0); - return false; - } - else if (StringUtils.ToToggleBool(settings.toggleArmourChecksPenalty0OutOfCombatOnly) && !__instance.Armor.Wielder.Unit.IsInCombat) { - Traverse.Create(__instance).Property("Penalty").SetValue(0); - return false; - } - - } - return true; - } - } - [HarmonyPatch(typeof(UIUtilityItem), "GetArmorData")] - public static class UIUtilityItem_GetArmorData_Patch { - private static void Postfix(ref UIUtilityItem.ArmorData __result, ref ItemEntityArmor armor) { - if (StringUtils.ToToggleBool(settings.toggleArmourChecksPenalty0)) { - if (!StringUtils.ToToggleBool(settings.toggleArmourChecksPenalty0OutOfCombatOnly)) { - UIUtilityItem.ArmorData armorData = __result; - armorData.ArmorCheckPenalty = 0; - __result = armorData; - } - else if (StringUtils.ToToggleBool(settings.toggleArmourChecksPenalty0OutOfCombatOnly)) { - Logger.ModLoggerDebug(armor.Name); - if (armor.Wielder != null) { - Logger.ModLoggerDebug(armor.Name + ": " + armor.Wielder.CharacterName); - if (!armor.Wielder.Unit.IsInCombat) { - Logger.ModLoggerDebug(armor.Name + ": " + armor.Wielder.CharacterName + " - " + armor.Wielder.Unit.IsInCombat); - UIUtilityItem.ArmorData armorData = __result; - armorData.ArmorCheckPenalty = 0; - __result = armorData; - } - } - else { - Logger.ModLoggerDebug("!" + armor.Name); - UIUtilityItem.ArmorData armorData = __result; - armorData.ArmorCheckPenalty = 0; - __result = armorData; - } - - } - } - } - } - - - [HarmonyPatch(typeof(BlueprintAbility), "GetRange")] - static class BlueprintAbility_GetRange_Patch_Pre { - - private static Feet defaultClose = 30.Feet(); - private static Feet defaultMedium = 40.Feet(); - private static Feet defaultlong = 50.Feet(); - private static Feet cotwMedium = 60.Feet(); - private static Feet cotwLong = 100.Feet(); - [HarmonyPriority(Priority.Low)] - static void Postfix(ref Feet __result) { - if (StringUtils.ToToggleBool(settings.toggleTabletopSpellAbilityRange)) { - if (Main.callOfTheWild.ModIsActive()) { - if (__result == defaultClose) { - __result = 25.Feet(); - } - else if (__result == cotwMedium) { - __result = 100.Feet(); - } - else if (__result == cotwLong) { - __result = 400.Feet(); - } - } - else { - if (__result == defaultClose) { - __result = 25.Feet(); - } - else if (__result == defaultMedium) { - __result = 100.Feet(); - } - else if (__result == defaultlong) { - __result = 400.Feet(); - } - } - } - if (StringUtils.ToToggleBool(settings.toggleCustomSpellAbilityRange)) { - if (Main.callOfTheWild.ModIsActive()) { - if (__result == defaultClose) { - __result = settings.customSpellAbilityRangeClose.Feet(); - } - else if (__result == cotwMedium) { - __result = settings.customSpellAbilityRangeMedium.Feet(); - } - else if (__result == cotwLong) { - __result = settings.customSpellAbilityRangeLong.Feet(); - } - } - else { - if (__result == defaultClose) { - __result = settings.customSpellAbilityRangeClose.Feet(); - } - else if (__result == defaultMedium) { - __result = settings.customSpellAbilityRangeMedium.Feet(); - } - else if (__result == defaultlong) { - __result = settings.customSpellAbilityRangeLong.Feet(); - } - } - - } - - - - if (StringUtils.ToToggleBool(settings.toggleSpellAbilityRangeMultiplier)) { - if (settings.useCustomSpellAbilityRangeMultiplier) { - __result = __result * settings.customSpellAbilityRangeMultiplier; - } - else { - __result = __result * settings.spellAbilityRangeMultiplier; - } - } - } - } - - [HarmonyPatch(typeof(UnitEntityData), "IsDirectlyControllable", MethodType.Getter)] - public static class UnitEntityData_IsDirectlyControllable_Patch { - public static void Postfix(UnitEntityData __instance, ref bool __result) { - if (StringUtils.ToToggleBool(settings.toggleMakeSummmonsControllable) && __instance.IsPlayerFaction && !__result && __instance.Get<UnitPartSummonedMonster>() != null && !__instance.Descriptor.State.IsFinallyDead && !__instance.Descriptor.State.IsPanicked && !__instance.IsDetached && !__instance.PreventDirectControl) { - __result = true; - } - } - } - // (KingmakerUIExtensionsMod\UIExtensions\Features\AlwaysDisplayPetsPortrait.cs) force the pets to be displayed - [HarmonyPatch(typeof(GroupController), "WithPet", MethodType.Getter)] - static class GroupController_get_WithPet_Patch { - [HarmonyPostfix] - static void Postfix(ref bool __result) { - __result = settings.toggleShowAllPartyPortraits; - } - } - - // (KingmakerUIExtensionsMod\UIExtensions\Features\AlwaysDisplayPetsPortrait.cs) fix the navigation block isn't refreshed after loading a save - [HarmonyPatch(typeof(GroupController), "Kingmaker.UI.IUIElement.Initialize")] - static class GroupController_Initialize_Patch { - [HarmonyPostfix] - static void Postfix() { - if (settings.toggleShowAllPartyPortraits) { - GroupControllerUtils.NaviBlockShowAllPartyMembers(); - } - } - } - - // (KingmakerUIExtensionsMod\UIExtensions\Features\AlwaysDisplayPetsPortrait.cs) fix the game could duplicately bind a hotkey to character slots multiple times - [HarmonyPatch(typeof(GroupCharacter), nameof(GroupCharacter.Initialize), typeof(UnitEntityData), typeof(int))] - static class GroupCharacter_Initialize_Patch { - [HarmonyPrefix] - static void Prefix(GroupCharacter __instance) { - __instance.Bind(false); - } - } - - [HarmonyPatch(typeof(GroupController), nameof(GroupController.OnScroll), typeof(PointerEventData))] - static class GroupController_OnScroll_Patch { - [HarmonyPrefix] - static bool Prefix(GroupController __instance, PointerEventData eventData) { - if (StringUtils.ToToggleBool(settings.toggleShowAllPartyPortraits)) { - float y = eventData.scrollDelta.y; - if ((double)Math.Abs(y) < (double)Mathf.Epsilon || !__instance.NavigateCharacters((double)y < 0.0)) { - return false; - } - Game.Instance.UI.Common.UISound.Play(UISoundType.ButtonClick); - return false; - } - return true; - } - } - - // (KingmakerUIExtensionsMod\UIExtensions\Features\AlwaysDisplayPetsPortrait.cs) update after gain a pet (after leveling up) - [HarmonyPatch(typeof(SceneEntitiesState), nameof(SceneEntitiesState.AddEntityData), typeof(EntityDataBase))] - static class SceneEntitiesState_AddEntityData_Patch { - [HarmonyPostfix] - static void Postfix(EntityDataBase data) { - if (StringUtils.ToToggleBool(settings.toggleShowAllPartyPortraits)) { - if (data is UnitEntityData unit && unit.IsPlayerFaction && unit.Descriptor.IsPet) { - GroupControllerUtils.NaviBlockShowAllPartyMembers(); - } - } - } - } - - // (KingmakerUIExtensionsMod\UIExtensions\Features\AlwaysDisplayPetsPortrait.cs) fixed bugs on dragging portraits - [HarmonyPatch(typeof(GroupController), nameof(GroupController.DragCharacter), typeof(GroupCharacter))] - static class GroupController_DragCharacter_Patch { - [HarmonyPrefix] - static bool Prefix(GroupController __instance, GroupCharacter groupCharacter) { - if (StringUtils.ToToggleBool(settings.toggleShowAllPartyPortraits)) { - List<GroupCharacter> characters = GroupControllerUtils.GetCharacters(); - UnitEntityData targetUnit = GetTargetToSwap(groupCharacter, characters); - if (targetUnit != null) { - SwapUnitsIndex(groupCharacter.Unit, targetUnit); - SortCharacters(groupCharacter, characters); - EventBus.RaiseEvent<IPartyChangedUIHandler>(h => h.HandlePartyChanged()); - } - return false; - } - return true; - } - - static UnitEntityData GetTargetToSwap(GroupCharacter source, List<GroupCharacter> characters) { - float halfWidth = source.TargetPanel.sizeDelta.x / 2f; - Vector3 sourcePosition = source.TargetPanel.localPosition; - bool sourceIsPet = source.Unit.Descriptor.IsPet; - bool IsValidTarget(GroupCharacter targetCharacter) { - UnitEntityData target = targetCharacter.Unit; - return target != null && (sourceIsPet ? target.Descriptor.Pet == null : !target.Descriptor.IsPet); - } - - // check the next unit - if (source.Index < 5) { - GroupCharacter targetCharacter = characters.Skip(source.Index + 1).FirstOrDefault(IsValidTarget); - if (targetCharacter != null && sourcePosition.x > targetCharacter.BasePosition.x - halfWidth) { - return targetCharacter.Unit; - } - } - - // check the prior unit - if (source.Index > 0) { - GroupCharacter targetCharacter = characters.Take(source.Index).LastOrDefault(IsValidTarget); - if (targetCharacter != null && sourcePosition.x < targetCharacter.BasePosition.x + halfWidth) { - return targetCharacter.Unit; - } - } - - return null; - } - - static void SwapUnitsIndex(UnitEntityData source, UnitEntityData target) { - void EnsureNotPet(ref UnitEntityData unit) - => unit = unit.Descriptor.IsPet ? unit.Descriptor.Master.Value : unit; - EnsureNotPet(ref source); - EnsureNotPet(ref target); - - // get index - List<UnitReference> characters = Game.Instance.Player.PartyCharacters; - int sourceIndex = characters.FindIndex((UnitReference pc) => pc.Value == source); - int targetIndex = characters.FindIndex((UnitReference pc) => pc.Value == target); - - // swap - UnitReference sourceUnit = characters[sourceIndex]; - characters[sourceIndex] = characters[targetIndex]; - characters[targetIndex] = sourceUnit; - Game.Instance.Player.InvalidateCharacterLists(); - } - - static void SortCharacters(GroupCharacter source, List<GroupCharacter> characters) { - List<UnitEntityData> sortedUnits = - UIUtility.GetGroup(GroupControllerUtils.GetWithRemote(), GroupControllerUtils.GetWithPet()) - .Skip(GroupControllerUtils.GetStartIndex()).Take(6).ToList(); - List<Vector3> positions = characters.Select(item => item.BasePosition).ToList(); - - for (int i = 0; i < 6 && i < sortedUnits.Count; i++) { - UnitEntityData unit = sortedUnits[i]; - GroupCharacter character = characters.Find(c => c.Unit == unit); - if (character == null) { - character = characters.Find(c => !sortedUnits.Contains(c.Unit)); - GroupControllerUtils.SetCharacter(unit, character.Index); - } - if (character.Index != i) { - DoMoveCharacter(character, i, positions[i], character != source); - } - } - - characters.Sort((x, y) => x.Index - y.Index); - } - - static void DoMoveCharacter(GroupCharacter character, int newIndex, Vector3 newPosition, bool doLocalMove) { - character.Bind(false); - character.Index = newIndex; - character.Bind(true); - character.BasePosition = newPosition; - if (doLocalMove) { - character.transform.DOLocalMove(newPosition, 0.2f, false).SetUpdate(true); - } - } - } - [HarmonyPatch(typeof(GlobalMapLocation), "HandleHoverChange", new Type[] { typeof(bool) })] - internal static class GlobalMapLocation_HandleHoverChange_Patch { - private static void Postfix(GlobalMapLocation __instance, ref bool isHover) { - if (Main.enabled && isHover) - Storage.lastHoveredLocation = __instance; - else - Storage.lastHoveredLocation = null; - } - } - - // TODO - This may contain fix for mouse wheel getting lost... - [HarmonyPatch(typeof(UnityModManager.UI), "Update")] - internal static class UnityModManager_UI_Update_Patch { - private static void Postfix(UnityModManager.UI __instance, ref Rect ___mWindowRect, ref Vector2[] ___mScrollPosition, ref int ___tabId) { - Storage.ummRect = ___mWindowRect; - //Storage.ummWidth = ___mWindowWidth; float ___mWindowWidth - Storage.ummScrollPosition = ___mScrollPosition; - Storage.ummTabId = ___tabId; - - if (Main.enabled) { - GameModeType currentGameMode = Game.Instance.CurrentMode; - - if ((currentGameMode == GameModeType.Default || currentGameMode == GameModeType.EscMode) && StringUtils.ToToggleBool(settings.toggleShowAreaName) && !Game.Instance.IsPaused) { - Main.sceneAreaInfo.On(); - Main.sceneAreaInfo.Text("<b>" + Game.Instance.CurrentlyLoadedArea.AreaName.ToString() + "</b>"); - } - else { - Main.sceneAreaInfo.Off(); - } - - if (currentGameMode != GameModeType.None && StringUtils.ToToggleBool(settings.toggleDisplayObjectInfo)) { - if (Main.sceneAreaInfo.baseGameObject.activeSelf) { - Vector3 temp = Main.sceneAreaInfo.baseGameObject.GetComponent<RectTransform>().position; - temp.y = Main.sceneAreaInfo.baseGameObject.GetComponent<RectTransform>().position.y - Main.sceneAreaInfo.baseGameObject.GetComponent<RectTransform>().sizeDelta.y; - Main.objectInfo.baseGameObject.GetComponent<RectTransform>().position = temp; - } - else { - Main.objectInfo.baseGameObject.GetComponent<RectTransform>().position = new Vector3(Screen.width / 2f, Screen.height * 0.95f, 0); - } - Main.objectInfo.On(); - BlueprintScriptableObject blueprint; - UnitEntityData unitUnderMouse = Common.GetUnitUnderMouse(); - BlueprintScriptableObject[] scriptableObjectArray = ActionKey.Tooltip(); - if (unitUnderMouse != null) { - blueprint = (BlueprintScriptableObject)unitUnderMouse.Blueprint; - } - else { - if (scriptableObjectArray == null) { - goto DisplayObjectInfoEnd; - } - blueprint = scriptableObjectArray[0]; - } - Main.objectInfo.Text($"<b>{blueprint.AssetGuid}\n{Utilities.GetBlueprintName(blueprint)}</b>"); - } - else { - Main.objectInfo.Off(); - } - DisplayObjectInfoEnd: - - if (settings.toggleEnableFocusCamera && Game.Instance?.UI?.GetCameraRig() != null && (currentGameMode == GameModeType.Default || currentGameMode == GameModeType.Pause)) { - List<UnitEntityData> partyMembers = Game.Instance.Player?.ControllableCharacters; - - if (partyMembers != Storage.partyMembersFocusUnits) { - Storage.partyMembersFocusUnits = partyMembers; - } - - if (settings.partyMembersFocusPositionCounter >= Storage.partyMembersFocusUnits.Count) { - settings.partyMembersFocusPositionCounter = Storage.partyMembersFocusUnits.Count - 1; - } - - if (Input.GetKeyDown(settings.focusCameraKey)) { - if (settings.focusCameraToggle) { - settings.focusCameraToggle = false; - } - else { - settings.focusCameraToggle = true; - } - } - - if (settings.focusCameraToggle && !Input.GetKey(KeyCode.Mouse2)) { - Game.Instance.UI.GetCameraRig().ScrollTo(Storage.partyMembersFocusUnits[settings.partyMembersFocusPositionCounter].Position); - } - - if (Input.GetKeyDown(settings.focusCylceKey)) { - - if (settings.partyMembersFocusPositionCounter < Storage.partyMembersFocusUnits.Count) { - if (settings.partyMembersFocusPositionCounter == Storage.partyMembersFocusUnits.Count - 1) { - settings.partyMembersFocusPositionCounter = 0; - } - else { - settings.partyMembersFocusPositionCounter++; - } - } - else { - settings.partyMembersFocusPositionCounter = 0; - - } - } - } - - if (Input.GetKeyDown(settings.togglePartyAlwaysRoll20Key) && settings.toggleEnablePartyAlwaysRoll20Hotkey) { - if (settings.togglePartyAlwaysRoll20) { - settings.togglePartyAlwaysRoll20 = Storage.isTrueString; - Common.AddLogEntry(Strings.GetText("buttonToggle_PartyAlwaysRolls20") + ": " + Strings.GetText("logMessage_Enabled"), Color.black); - - } - - else if (settings.togglePartyAlwaysRoll20) { - settings.togglePartyAlwaysRoll20 = Storage.isFalseString; - Common.AddLogEntry(Strings.GetText("buttonToggle_PartyAlwaysRolls20") + ": " + Strings.GetText("logMessage_Disabled"), Color.black); - } - } - - if (Input.GetKeyDown(settings.resetCutsceneLockKey) && settings.toggleEnableResetCutsceneLockHotkey) { - Game.Instance.CheatResetCutsceneLock(); - Common.AddLogEntry(Strings.GetText("button_ResetCutsceneLock") + ": " + Strings.GetText("logMessage_Enabled"), Color.black); - } - - - if (Input.GetKeyDown(settings.actionKey) && settings.toggleEnableActionKey && settings.actionKeyIndex == 6 && ActionKey.teleportUnit != null && ActionKey.teleportUnit.IsInGame) { - GameModeType currentMode = Game.Instance.CurrentMode; - if (currentMode == GameModeType.Default || currentMode == GameModeType.Pause) { - Vector3 mousePosition = Common.MousePositionLocalMap(); - Common.TeleportUnit(ActionKey.teleportUnit, mousePosition); - } - ActionKey.teleportUnit = null; - } - - if (Input.GetKeyDown(settings.actionKey) && settings.toggleEnableActionKey && settings.actionKeyIndex == 8 && ActionKey.rotateUnit != null && ActionKey.rotateUnit.IsInGame) { - GameModeType currentMode = Game.Instance.CurrentMode; - if (currentMode == GameModeType.Default || currentMode == GameModeType.Pause) { - Vector3 mousePosition = Common.MousePositionLocalMap(); - Common.RotateUnit(ActionKey.rotateUnit, mousePosition); - } - ActionKey.rotateUnit = null; - } - - if (Input.GetKeyDown(settings.actionKey) && StringUtils.ToToggleBool(settings.toggleEnableActionKey) && settings.actionKeyIndex != 0) { - ActionKey.Functions(settings.actionKeyIndex); - } - - if (StringUtils.ToToggleBool(settings.toggleHUDToggle) && Game.Instance.Player.ControllableCharacters.Any() && Input.GetKeyDown(settings.hudToggleKey) && Game.Instance.CurrentMode != GameModeType.None) { - Common.ToggleHUD(); - } - - if (Input.GetKey(settings.teleportKey) && settings.toggleEnableTeleport) { - GameModeType currentMode = Game.Instance.CurrentMode; - if (currentMode == GameModeType.Default || currentMode == GameModeType.Pause) { - List<UnitEntityData> selectedUnits = Game.Instance.UI.SelectionManagerPC.GetSelectedUnits(); - Vector3 mousePosition = Common.MousePositionLocalMap(); - foreach (UnitEntityData unit in selectedUnits) { - Common.TeleportUnit(unit, mousePosition); - } - } - else if (currentMode == GameModeType.GlobalMap && Storage.lastHoveredLocation != null) { - GlobalMapRules.Instance.TeleportParty(Storage.lastHoveredLocation.Blueprint); - Storage.lastHoveredLocation = null; - } - } - } - } - } - - [HarmonyPatch(typeof(RandomEncountersController), "GetAvoidanceCheckResult")] - internal static class RandomEncountersControllern_GetAvoidanceCheckResult_Patch { - private static void Postfix(ref RandomEncounterAvoidanceCheckResult __result) { - if (StringUtils.ToToggleBool(settings.toggleEnableAvoidanceSuccess) && StringUtils.ToToggleBool(settings.toggleEnableRandomEncounterSettings)) { - __result = RandomEncounterAvoidanceCheckResult.Success; - } - } - } - - [HarmonyPatch(typeof(GroupCharacterPortraitController), "OnUnitSelectionAdd")] - internal static class GroupCharacterPortraitController_OnUnitSelectionAdd_Patch { - private static void Postfix(UnitEntityData selected) { - if (StringUtils.ToToggleBool(settings.toggleEnableFocusCamera) && StringUtils.ToToggleBool(settings.toggleEnableFocusCameraSelectedUnit)) { - settings.partyMembersFocusPositionCounter = Storage.partyMembersFocusUnits.FindIndex(a => a == selected); - } - } - } - - [HarmonyPatch(typeof(UnityModManager), "SaveSettingsAndParams")] - internal static class UnityModManager_SaveSettingsAndParams_Patch { - private static void Postfix(UnityModManager __instance) { - FavouritesFactory.SerializeFavourites(); - if (settings.toggleEnableTaxCollector) { - TaxCollector.Serialize(Main.taxSettings, Storage.modEntryPath + Storage.taxCollectorFolder + "\\" + Storage.taxCollectorFile); - } - } - } - - [HarmonyPatch(typeof(CameraRig), "SetRotation")] - static class CameraRig_SetRotation_Patch { - static bool Prefix(ref float cameraRotation) { - if (settings.toggleEnableCameraRotation) { - if (cameraRotation != settings.defaultRotation) { - // If we enter a new area with a different default camera angle, reset the rotation to 0 - settings.defaultRotation = cameraRotation; - settings.cameraRotation = 0; - } - cameraRotation += settings.cameraRotation; - Main.rotationChanged = true; - if (Main.localMap) { - // If the local map is open, call the Set method to redraw things - Traverse.Create(Main.localMap).Method("Set").GetValue(); - } - return true; - } - return true; - } - } - - - - [HarmonyPatch(typeof(CameraRig), "TickScroll")] - static class CameraKey_Patch { - static bool Prefix(ref CameraRig __instance) { - - if (StringUtils.ToToggleBool(settings.toggleEnableCameraScrollSpeed)) { - Traverse.Create(__instance).Field("m_ScrollSpeed").SetValue(settings.cameraScrollSpeed); - } - - if (settings.toggleEnableCameraRotation) { - if (Input.GetKey(settings.cameraTurnLeft)) { - settings.cameraRotation -= settings.cameraTurnRate; - if (settings.cameraRotation < -180) { - settings.cameraRotation += 360; - } - } - else if (Input.GetKey(settings.cameraTurnRight)) { - settings.cameraRotation += settings.cameraTurnRate; - if (settings.cameraRotation >= 180) { - settings.cameraRotation -= 360; - } - } - else if (Input.GetKey(settings.cameraReset)) { - settings.cameraRotation = 0; - } - else { - return true; - } - HarmonyInstance.Create("kingmaker.camerarotation").Patch(AccessTools.Method(typeof(CameraRig), "SetRotation"), new HarmonyMethod(typeof(BagOfTricks.Main).GetMethod("CameraRig_SetRotation_Patch")), null); - Game.Instance.UI.GetCameraRig().SetRotation(settings.defaultRotation); - return true; - } - return true; - } - } - [HarmonyPatch(typeof(LocalMap), "OnShow")] - static class LocalMap_OnShow_Patch { - static void Prefix(LocalMap __instance) { - Main.localMap = __instance; - } - - } - - [HarmonyPatch(typeof(LocalMap), "OnHide")] - static class LocalMap_OnHide_Patch { - static void Postfix() { - Main.localMap = null; - } - } - - [HarmonyPatch(typeof(LocalMapRenderer), "IsDirty")] - static class LocalMapRenderer_IsDirty_Patch { - static void Postfix(ref bool __result) { - if (Main.rotationChanged) { - // If rotation has changed since drawing the map image, return that it's dirty. - __result = true; - Main.rotationChanged = false; - } - } - } - [HarmonyPatch(typeof(CameraZoom))] - [HarmonyPatch("TickSmoothZoomToTargetValue")] - public static class CameraZoom_TickSmoothZoomToTargetValue_Patch { - static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { - var codes = new List<CodeInstruction>(instructions); - if (settings.toggleEnableCameraZoom) { - - int foundFovMin = -1; - int foundFovMax = -1; - - for (int i = 0; i < codes.Count; i++) { - if (codes[i].opcode == OpCodes.Callvirt && codes[i - 1].opcode == OpCodes.Call && codes[i - 2].opcode == OpCodes.Call) { - foundFovMin = i - 4; - foundFovMax = i - 6; - break; - } - } - - codes[foundFovMin - 1].opcode = OpCodes.Nop; - codes[foundFovMin].opcode = OpCodes.Ldc_R4; - codes[foundFovMin].operand = float.Parse(settings.savedFovMin); - - codes[foundFovMax - 1].opcode = OpCodes.Nop; - codes[foundFovMax].opcode = OpCodes.Ldc_R4; - codes[foundFovMax].operand = float.Parse(settings.savedFovMax); - - return codes.AsEnumerable(); - } - return codes.AsEnumerable(); - } - } - - [HarmonyPatch(typeof(CameraRig))] - [HarmonyPatch("SetMapMode")] - public static class CameraRig_SetMapMode_Patch { - static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { - var codes = new List<CodeInstruction>(instructions); - if (settings.toggleEnableCameraZoom) { - int foundFovGlobalMap = -1; - - for (int i = 0; i < codes.Count; i++) { - if (codes[i].opcode == OpCodes.Br && codes[i - 1].opcode == OpCodes.Ldfld && codes[i - 2].opcode == OpCodes.Call && codes[i - 3].opcode == OpCodes.Ldarg_0) { - foundFovGlobalMap = i - 1; - break; - } - } - - codes[foundFovGlobalMap - 2].opcode = OpCodes.Nop; - codes[foundFovGlobalMap - 1].opcode = OpCodes.Nop; - codes[foundFovGlobalMap].opcode = OpCodes.Ldc_R4; - codes[foundFovGlobalMap].operand = float.Parse(settings.savedFovGlobalMap, Storage.cultureEN); - - return codes.AsEnumerable(); - } - return codes.AsEnumerable(); - } - } - - //UI start - [HarmonyPatch(typeof(EscMenuWindow), "Initialize")] - internal static class EscMenuWindow_Initialize_Patch { - private static void Postfix(EscMenuWindow __instance) { - if (StringUtils.ToToggleBool(settings.toggleUnityModManagerButton) && Traverse.Create((object)__instance).Field("UnityModManager_Button").GetValue<Button>() == null) { - try { - Button saveButton = Traverse.Create((object)__instance).Field("ButtonSave").GetValue<Button>(); - Transform saveButtonParent = saveButton.transform.parent; - Button ummButton = UnityEngine.Object.Instantiate<Button>(saveButton); - ummButton.name = "UnityModManager_Button"; - ummButton.transform.SetParent(saveButtonParent, false); - ummButton.onClick = new Button.ButtonClickedEvent(); - ummButton.onClick.AddListener((UnityAction)(() => OnClick())); - ummButton.GetComponentInChildren<TextMeshProUGUI>().text = Strings.GetText("misc_UnityModManager"); - ummButton.transform.SetSiblingIndex(ummButton.transform.GetSiblingIndex() - Mathf.RoundToInt(settings.unityModManagerButtonIndex)); - } - catch (Exception exception) { - modLogger.Log(exception.ToString()); - } - } - - } - public static void OnClick() { - try { - UnityModManager.UI.Instance.ToggleWindow(true); - } - catch (Exception e) { - - modLogger.Log(e.ToString()); ; - } - } - } - - [HarmonyPatch(typeof(Inventory), "UpdatePlayerMoneyAndInventoryWeight")] - internal static class Inventory_UpdatePlayerMoneyAndInventoryWeight_Patch { - private static void Postfix(Inventory __instance, TextMeshProUGUI ___PlayerMoneyNow) { - if (StringUtils.ToToggleBool(settings.toggleScaleInventoryMoney)) { - try { - int digits = Math.Abs((int)Math.Floor(Math.Log10(player.Money)) + 1); - String s = player.Money.ToString(); - switch (digits) { - case 8: - ___PlayerMoneyNow.text = RichTextUtils.SizePercent(s, 80); - break; - case 9: - ___PlayerMoneyNow.text = RichTextUtils.SizePercent(s, 75); - break; - case 10: - ___PlayerMoneyNow.text = RichTextUtils.SizePercent(s, 70); - break; - case 11: - ___PlayerMoneyNow.text = RichTextUtils.SizePercent(s, 65); - break; - case 12: - ___PlayerMoneyNow.text = RichTextUtils.SizePercent(s, 60); - break; - } - } - catch (Exception exception) { - modLogger.Log(exception.ToString()); - } - } - - } - } - //UI end - - [HarmonyPatch(typeof(Player), "OnAreaLoaded")] - internal static class Player_OnAreaLoaded_Patch { - private static void Postfix() { - Main.ReloadPartyState(); - ActionKey.teleportUnit = null; - ActionKey.rotateUnit = null; - } - } - [HarmonyPatch(typeof(Player), "AttachPartyMember")] - internal static class Player_AttachPartyMember_Patch { - private static void Postfix() { - Main.ReloadPartyState(); - } - } - - [HarmonyPatch(typeof(Player), "AddCompanion")] - internal static class Player_AddCompanion_Patch { - private static void Postfix() { - Main.ReloadPartyState(); - if (StringUtils.ToToggleBool(settings.toggleShowAllPartyPortraits)) { - GroupControllerUtils.NaviBlockShowAllPartyMembers(); - } - - } - } - [HarmonyPatch(typeof(Player), "RemoveCompanion")] - internal static class Player_RemoveCompanion_Patch { - private static void Postfix() { - Main.ReloadPartyState(); - } - } - [HarmonyPatch(typeof(Player), "DismissCompanion")] - internal static class Player_DismissCompanion_Patch { - private static void Postfix() { - Main.ReloadPartyState(); - } - } - [HarmonyPatch(typeof(Player), "SwapAttachedAndDetachedPartyMembers")] - internal static class Player_SwapAttachedAndDetachedPartyMembers_Patch { - private static void Postfix() { - Main.ReloadPartyState(); - } - } - - [HarmonyPatch(typeof(EscMenuWindow), "OnHotKeyEscPressed")] - internal static class EscMenuWindow_OnHotKeyEscPressed_Patch { - private static void Postfix() { - if (settings.toggleEnableTaxCollector) { - try { - TaxCollector.Serialize(Main.taxSettings, Storage.modEntryPath + Storage.taxCollectorFolder + "\\" + Storage.taxCollectorFile); - - } - catch (Exception e) { - - modLogger.Log(e.ToString()); - } - } - } - } - - [HarmonyPatch(typeof(Player), "Initialize")] - internal static class Player_Initialize_Patch { - private static void Postfix() { - //settings.defaultVendorSellPriceMultiplier = (float)Game.Instance.BlueprintRoot.Vendors.SellModifier; - Main.CheckRandomEncounterSettings(); - if (settings.artisanMasterpieceChance != Defaults.artisanMasterpieceChance && KingdomRoot.Instance != null) { - KingdomRoot.Instance.ArtisanMasterpieceChance = settings.artisanMasterpieceChance; - } - if (StringUtils.ToToggleBool(settings.toggleNoResourcesClaimCost) && KingdomRoot.Instance != null) { - KingdomRoot.Instance.DefaultMapResourceCost = 0; - } - //Game.Instance.BlueprintRoot.Vendors.SellModifier = settings.vendorSellPriceMultiplier; - } - } - - [HarmonyPatch(typeof(Game), "LoadGame")] - public static class Game_LoadGame_Patch { - private static void Postfix(SaveInfo saveInfo) { - Game.Instance.BlueprintRoot.Vendors.SellModifier = settings.vendorSellPriceMultiplier; - } - } - - [HarmonyPatch(typeof(TimeController), "Tick")] - static class TimeController_Tick_Patch { - public static bool Prefix() { - if (settings.debugTimeMultiplier != Defaults.debugTimeScale && settings.useCustomDebugTimeMultiplier == false && Game.Instance.TimeController.DebugTimeScale != settings.debugTimeMultiplier) { - Game.Instance.TimeController.DebugTimeScale = settings.debugTimeMultiplier; - } - - if (settings.finalCustomDebugTimeMultiplier != Defaults.debugTimeScale && settings.useCustomDebugTimeMultiplier == true && Game.Instance.TimeController.DebugTimeScale != settings.finalCustomDebugTimeMultiplier) { - Game.Instance.TimeController.DebugTimeScale = settings.finalCustomDebugTimeMultiplier; - } - return true; - } - } - - [HarmonyPatch(typeof(TimeController), "Tick")] - public static class TimeController_Tick_Patch2 { - static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { - var codes = new List<CodeInstruction>(instructions); - int found = -1; - - for (int i = 0; i < codes.Count; i++) { - if (codes[i].opcode == OpCodes.Stfld && codes[i - 1].opcode == OpCodes.Call && codes[i - 2].opcode == OpCodes.Call && codes[i - 3].opcode == OpCodes.Conv_R8 && codes[i - 4].opcode == OpCodes.Call && codes[i - 5].opcode == OpCodes.Ldarg_0 && codes[i - 6].opcode == OpCodes.Ldfld && codes[i - 7].opcode == OpCodes.Dup) { - found = i; - break; - } - } - - - if (found != -1 && StringUtils.ToToggleBool(settings.toggleStopGameTime)) { - codes[found - 5].opcode = OpCodes.Ldc_I4_0; - codes[found - 4].opcode = OpCodes.Nop; - return codes.AsEnumerable(); - } - else { - return codes.AsEnumerable(); - } - - - } - } - [HarmonyPatch(typeof(GlobalMapTeleport), "RunAction")] - public static class GlobalMapTeleport_RunAction_Patch { - public static bool Prefix(ref float ___SkipHours) { - if (StringUtils.ToToggleBool(settings.toggleStopGameTime)) { - ___SkipHours = 0f; - - } - return true; - } - } - [HarmonyPatch(typeof(GlobalMapTeleportLast), "RunAction")] - public static class GlobalMapTeleportLast_RunAction_Patch { - public static bool Prefix(ref float ___SkipHours) { - if (StringUtils.ToToggleBool(settings.toggleStopGameTime)) { - ___SkipHours = 0f; - - } - return true; - } - } - [HarmonyPatch(typeof(Game), "AdvanceGameTime")] - public static class Game_AdvanceGameTime_Patch { - public static bool Prefix(ref TimeSpan delta) { - if (StringUtils.ToToggleBool(settings.toggleStopGameTime)) { - delta = TimeSpan.Zero; - } - return true; - } - } - - [HarmonyPatch(typeof(RuleDealDamage), "ApplyDifficultyModifiers")] - public static class Game_RollDamage_PrePatch { - public static bool Prefix(RuleDealDamage __instance, ref int damage) { - if (StringUtils.ToToggleBool(settings.toggleNoDamageFromEnemies) && __instance.Initiator.IsPlayersEnemy) { - damage = 0; - } - if (StringUtils.ToToggleBool(settings.togglePartyOneHitKills) && __instance.Initiator.IsPlayerFaction) { - UnitEntityData unit = __instance.Target; - damage = unit.Descriptor.Stats.HitPoints.ModifiedValue + unit.Descriptor.Stats.TemporaryHitPoints.ModifiedValue + 1; - } - return true; - } - } - [HarmonyPatch(typeof(RuleDealDamage), "ApplyDifficultyModifiers")] - static class RuleDealDamage_ApplyDifficultyModifiers_PostPatch { - private static void Postfix(RuleDealDamage __instance, ref int __result) { - if (StringUtils.ToToggleBool(settings.toggleDamageDealtMultipliers)) { - if (StringUtils.ToToggleBool(settings.toggleEnemiesDamageDealtMultiplier) && __instance.Initiator.IsPlayersEnemy) { - __result = Mathf.RoundToInt(__result * settings.enemiesDamageDealtMultiplier); - } - if (StringUtils.ToToggleBool(settings.togglePartyDamageDealtMultiplier) && __instance.Initiator.IsPlayerFaction) { - __result = Mathf.RoundToInt(__result * settings.partyDamageDealtMultiplier); - } - } - } - } - - [HarmonyPatch(typeof(DisableDeviceRestriction), "CheckRestriction")] - public static class DisableDeviceRestriction_CheckRestriction_Patch { - public static bool Prefix(DisableDeviceRestriction __instance, ref UnitEntityData user) { - if (StringUtils.ToToggleBool(settings.toggleAllDoorContainersUnlocked)) { - DisableDeviceRestriction.DisableDeviceRestrictionData data = (DisableDeviceRestriction.DisableDeviceRestrictionData)__instance.Data; - data.Unlocked = true; - __instance.Data = data; - } - if (StringUtils.ToToggleBool(settings.toggleRepeatableLockPicking)) { - DisableDeviceRestriction.DisableDeviceRestrictionData data = (DisableDeviceRestriction.DisableDeviceRestrictionData)__instance.Data; - data.LastSkillRank.Clear(); - __instance.Data = data; - if (StringUtils.ToToggleBool(settings.toggleRepeatableLockPickingWeariness)) { - user.Ensure<UnitPartWeariness>().AddWearinessHours(settings.finalRepeatableLockPickingWeariness); - } - if (StringUtils.ToToggleBool(settings.toggleRepeatableLockPickingLockPicks)) { - Storage.unitLockPick = user; - Storage.checkLockPick = true; - } - } - return true; - } - } - - [HarmonyPatch(typeof(RuleSkillCheck), MethodType.Constructor)] - [HarmonyPatch(new Type[] { typeof(UnitEntityData), typeof(StatType), typeof(int) })] - public static class RuleSkillCheck_RollResult_Patch { - private static void Prefix([NotNull] UnitEntityData unit, StatType statType, ref int dc) { - if (StringUtils.ToToggleBool(settings.togglePassSkillChecksIndividual) && StringUtils.ToToggleBool(settings.togglePassSkillChecksIndividualDC99)) { - for (int i = 0; i < settings.togglePassSkillChecksIndividualArray.Count(); i++) { - if (StringUtils.ToToggleBool(settings.togglePassSkillChecksIndividualArray[i]) && Storage.statsSkillsDict.Union(Storage.statsSocialSkillsDict).ToDictionary(d => d.Key, d => d.Value)[Storage.individualSkillsArray[i]] == statType) { - if (UnitEntityDataUtils.CheckUnitEntityData(unit, (UnitSelectType)settings.indexPassSkillChecksIndividual)) { - dc = -99; - } - } - } - } - } - } - [HarmonyPatch(typeof(RulePartySkillCheck), MethodType.Constructor)] - [HarmonyPatch(new Type[] { typeof(StatType), typeof(int) })] - public static class RulePartySkillCheckk_RollResult_Patch { - private static void Prefix(StatType statType, ref int difficultyClass) { - if (StringUtils.ToToggleBool(settings.togglePassSkillChecksIndividual) && StringUtils.ToToggleBool(settings.togglePassSkillChecksIndividualDC99)) { - for (int i = 0; i < settings.togglePassSkillChecksIndividualArray.Count(); i++) { - if (StringUtils.ToToggleBool(settings.togglePassSkillChecksIndividualArray[i]) && Storage.statsSkillsDict.Union(Storage.statsSocialSkillsDict).ToDictionary(d => d.Key, d => d.Value)[Storage.individualSkillsArray[i]] == statType) { - difficultyClass = -99; - } - } - } - } - } - [HarmonyPatch(typeof(StaticEntityData), "IsPerceptionCheckPassed", MethodType.Getter)] - public static class StaticEntityData_IsPerceptionCheckPassed_Patch { - private static void Postfix(ref bool __result, StaticEntityData __instance) { - if (StringUtils.ToToggleBool(settings.togglePassSkillChecksIndividual)) { - if (StringUtils.ToToggleBool(settings.togglePassSkillChecksIndividualArray[6])) { - __result = true; - } - } - } - } - - [HarmonyPatch(typeof(RestController), "PerformCampingChecks")] - public static class RestController_PerformCampingChecks__Patch { - private static void Postfix(RestController __instance) { - if (StringUtils.ToToggleBool(settings.toggleRepeatableLockPickingLockPicks)) { - int newLockPicks = Storage.lockPicks; - int limit = 0; - List<UnitEntityData> partyMembers = Game.Instance.Player.Party; - foreach (UnitEntityData controllableCharacter in partyMembers) { - int baseValue = controllableCharacter.Stats.SkillThievery.BaseValue; - if (baseValue > 0) { - RuleSkillCheck skillCheck = new RuleSkillCheck(controllableCharacter, StatType.SkillThievery, Storage.lockPicksCreationDC); - int result = skillCheck.BaseRollResult; - if (result < 5) { - newLockPicks += 1; - } - else if (result > 5 && result < 10) { - newLockPicks += 2; - } - else if (result > 10 && result < 15) { - newLockPicks += 3; - } - else if (result > 15 && result < 20) { - newLockPicks += 4; - } - else if (result > 20) { - newLockPicks += 5; - } - limit += 5; - } - } - if (newLockPicks > 0) { - Storage.lockPicks = Mathf.Clamp(newLockPicks, 1, limit); - } - } - } - } - - - [HarmonyPatch(typeof(BlueprintCookingRecipe), "CheckIngredients")] - public static class BlueprintCookingRecipe_CheckIngredients_Patch { - private static void Postfix(ref bool __result) { - if (StringUtils.ToToggleBool(settings.toggleNoIngredientsRequired)) { - __result = true; - } - } - } - [HarmonyPatch(typeof(BlueprintCookingRecipe), "SpendIngredients")] - public static class BlueprintCookingRecipe_SpendIngredients_Patch { - public static bool Prefix(BlueprintCookingRecipe __instance) { - if (StringUtils.ToToggleBool(settings.toggleNoIngredientsRequired)) { - __instance.Ingredients = new BlueprintCookingRecipe.ItemEntry[0]; - } - return true; - } - } - - [HarmonyPatch(typeof(KineticistAbilityBurnCost), "GetTotal")] - public static class KineticistAbilityBurnCost_GetTotal_Patch { - private static void Postfix(ref int __result) { - if (StringUtils.ToToggleBool(settings.toggleNoBurnKineticist)) { - __result = 0; - - } - } - } - [HarmonyPatch(typeof(AbilityAcceptBurnOnCast), "OnCast")] - public static class UnitPartKineticistt_AcceptBurn_Patch { - public static bool Prefix(ref int ___BurnValue) { - if (StringUtils.ToToggleBool(settings.toggleNoBurnKineticist)) { - ___BurnValue = 0; - } - return true; - - } - } - - [HarmonyPatch(typeof(SaveManager), "PrepareSave")] - public static class SaveManager_PrepareSave_Patch { - private static void Postfix(SaveInfo save) { - if (StringUtils.ToToggleBool(settings.toggleRepeatableLockPickingLockPicks)) { - SaveTools.SaveFile(save); - } - } - } - [HarmonyPatch(typeof(Game), "LoadGame")] - public static class Game_LoadGame_Patch { - private static void Postfix(SaveInfo saveInfo) { - Game.Instance.BlueprintRoot.Vendors.SellModifier = settings.vendorSellPriceMultiplier; - } - } - } - [HarmonyPatch(typeof(SteamSavesReplicator), "DeleteSave")] - public static class SteamSavesReplicator_DeleteSave_Patch { - private static void Postfix(SaveInfo saveInfo) { - if (StringUtils.ToToggleBool(settings.toggleRepeatableLockPickingLockPicks)) { - SaveTools.DeleteFile(saveInfo); - } - } - } - - [HarmonyPatch(typeof(UnitPartKineticist), "HealBurn")] - public static class UnitPartKineticist_HealBurn_Patch { - private static void Postfix(UnitPartKineticist __instance) { - if (StringUtils.ToToggleBool(settings.toggleMaximiseAcceptedBurn)) { - for (int i = __instance.AcceptedBurn; i < __instance.MaxBurn; i++) { - AbilityData abilityData = new AbilityData(Utilities.GetBlueprintByGuid<BlueprintAbility>("a5631955254ae5c4d9cc2d16870448a2"), __instance.Owner); - __instance.AcceptBurn(__instance.MaxBurn, abilityData); - } - - - } - } - } - [HarmonyPatch(typeof(UnitPartKineticist), "ClearAcceptedBurn")] - public static class UnitPartKineticist_ClearAcceptedBurn_Patch { - private static void Postfix(UnitPartKineticist __instance) { - if (StringUtils.ToToggleBool(settings.toggleMaximiseAcceptedBurn)) { - for (int i = __instance.AcceptedBurn; i < __instance.MaxBurn; i++) { - AbilityData abilityData = new AbilityData(Utilities.GetBlueprintByGuid<BlueprintAbility>("a5631955254ae5c4d9cc2d16870448a2"), __instance.Owner); - __instance.AcceptBurn(__instance.MaxBurn, abilityData); - } - } - } - } - [HarmonyPatch(typeof(Inventory), "SetCharacter")] - public static class Inventory_SetCharacter_Patch { - private static void Postfix(Inventory __instance) { - if (StringUtils.ToToggleBool(settings.toggleShowPetInventory)) { - if (GroupController.Instance.GetCurrentCharacter().Descriptor.IsPet) { - __instance.Placeholder.gameObject.SetActive(false); - } - } - } - } - - [HarmonyPatch(typeof(BlueprintArmorType), "HasDexterityBonusLimit", MethodType.Getter)] - public static class BlueprintArmorType_HasDexterityBonusLimit_Patch { - private static void Postfix(ref bool __result) { - if (StringUtils.ToToggleBool(settings.toggleDexBonusLimit99)) { - __result = false; - } - } - } - [HarmonyPatch(typeof(BlueprintArmorType), "MaxDexterityBonus", MethodType.Getter)] - public static class BlueprintArmorType_MaxDexterityBonus_Patch { - private static void Postfix(ref int __result) { - if (StringUtils.ToToggleBool(settings.toggleDexBonusLimit99)) { - __result = 99; - } - } - } - - [HarmonyPatch(typeof(BlueprintItem), "Weight", MethodType.Getter)] - public static class BlueprintItem_Weight_Patch { - private static void Postfix(ref float __result) { - if (StringUtils.ToToggleBool(settings.toggleItemsWeighZero)) { - __result = 0f; - } - } - } - [HarmonyPatch(typeof(BlueprintArmorType), "Weight", MethodType.Getter)] - public static class BlueprintArmorType_Weight_Patch { - private static void Postfix(ref float __result) { - if (StringUtils.ToToggleBool(settings.toggleItemsWeighZero)) { - __result = 0f; - } - } - } - [HarmonyPatch(typeof(BlueprintItemArmor), "Weight", MethodType.Getter)] - public static class BlueprintItemArmor_Weight_Patch { - private static void Postfix(ref float __result) { - if (StringUtils.ToToggleBool(settings.toggleItemsWeighZero)) { - __result = 0f; - } - } - } - [HarmonyPatch(typeof(BlueprintItemShield), "Weight", MethodType.Getter)] - public static class BlueprintItemShield_Weight_Patch { - private static void Postfix(ref float __result) { - if (StringUtils.ToToggleBool(settings.toggleItemsWeighZero)) { - __result = 0f; - } - } - } - [HarmonyPatch(typeof(BlueprintItemWeapon), "Weight", MethodType.Getter)] - public static class BlueprintItemWeapon_Weight_Patch { - private static void Postfix(ref float __result) { - if (StringUtils.ToToggleBool(settings.toggleItemsWeighZero)) { - __result = 0f; - } - } - } - [HarmonyPatch(typeof(BlueprintWeaponType), "Weight", MethodType.Getter)] - public static class BlueprintWeaponType_Weight_Patch { - private static void Postfix(ref float __result) { - if (StringUtils.ToToggleBool(settings.toggleItemsWeighZero)) { - __result = 0f; - } - } - } - - [HarmonyPatch(typeof(RuleCalculateAttacksCount), "OnTrigger")] - public static class RuleCalculateAttacksCount_OnTrigger_Patch { - private static void Prefix(ref RuleCalculateAttacksCount.AttacksCount ___PrimaryHand, ref RuleCalculateAttacksCount.AttacksCount ___SecondaryHand, RuleCalculateAttacksCount __instance) { - if (__instance.Initiator.IsPlayerFaction && StringUtils.ToToggleBool(settings.toggleExtraAttacksParty)) { - ___PrimaryHand.AdditionalAttacks += settings.extraAttacksPartyPrimaryHand; - if (!__instance.Initiator.Body.SecondaryHand.HasShield) { - ___SecondaryHand.AdditionalAttacks += settings.extraAttacksPartySecondaryHand; - } - } - } - } - - [HarmonyPatch(typeof(UnitAlignment), "GetDirection")] - static class UnitAlignment_GetDirection_Patch { - static void Postfix(UnitAlignment __instance, ref Vector2 __result, AlignmentShiftDirection direction) { - if (!Main.enabled) return; - if (StringUtils.ToToggleBool(settings.toggleAlignmentFix)) { - if (direction == AlignmentShiftDirection.NeutralGood) __result = new Vector2(0, 1); - if (direction == AlignmentShiftDirection.NeutralEvil) __result = new Vector2(0, -1); - if (direction == AlignmentShiftDirection.LawfulNeutral) __result = new Vector2(-1, 0); - if (direction == AlignmentShiftDirection.ChaoticNeutral) __result = new Vector2(1, 0); - } - } - } - [HarmonyPatch(typeof(UnitAlignment), "Set", new Type[] { typeof(Alignment), typeof(bool) })] - static class UnitAlignment_Set_Patch { - static void Prefix(UnitAlignment __instance, ref Alignment alignment) { - if (StringUtils.ToToggleBool(settings.togglePreventAlignmentChanges)) { - alignment = __instance.Value; - } - } - } - [HarmonyPatch(typeof(UnitAlignment), "Shift", new Type[] { typeof(AlignmentShiftDirection), typeof(int), typeof(IAlignmentShiftProvider) })] - static class UnitAlignment_Shift_Patch { - static bool Prefix(UnitAlignment __instance, AlignmentShiftDirection direction, ref int value, IAlignmentShiftProvider provider) { - try { - if (!Main.enabled) return true; - - if (StringUtils.ToToggleBool(settings.togglePreventAlignmentChanges)) { - value = 0; - } - - if (StringUtils.ToToggleBool(settings.toggleAlignmentFix)) { - if (value == 0) { - return false; - } - Vector2 vector = __instance.Vector; - float num = (float)value / 50f; - var directionVector = Traverse.Create(__instance).Method("GetDirection", new object[] { direction }).GetValue<Vector2>(); - Vector2 newAlignment = __instance.Vector + directionVector * num; - if (newAlignment.magnitude > 1f) { - //Instead of normalizing towards true neutral, normalize opposite to the alignment vector - //to prevent sliding towards neutral - newAlignment -= (newAlignment.magnitude - newAlignment.normalized.magnitude) * directionVector; - } - if (direction == AlignmentShiftDirection.TrueNeutral && (Vector2.zero - __instance.Vector).magnitude < num) { - newAlignment = Vector2.zero; - } - Traverse.Create(__instance).Property<Vector2>("Vector").Value = newAlignment; - Traverse.Create(__instance).Method("UpdateValue").GetValue(); - //Traverse requires the parameter types to find interface parameters - Traverse.Create(__instance).Method("OnChanged", - new Type[] { typeof(AlignmentShiftDirection), typeof(Vector2), typeof(IAlignmentShiftProvider), typeof(bool) }, - new object[] { direction, vector, provider, true }).GetValue(); - return false; - } - } - catch (Exception e) { - modLogger.Log(e.ToString()); - } - return true; - } - } - - [HarmonyPatch(typeof(ForbidSpellbookOnAlignmentDeviation), "CheckAlignment")] - static class ForbidSpellbookOnAlignmentDeviation_CheckAlignment_Patch { - static bool Prefix(ForbidSpellbookOnAlignmentDeviation __instance) { - if (StringUtils.ToToggleBool(settings.toggleSpellbookAbilityAlignmentChecks)) { - __instance.Alignment = __instance.Owner.Alignment.Value.ToMask(); - } - return true; - } - } - [HarmonyPatch(typeof(AbilityCasterAlignment), "CorrectCaster")] - static class AbilityCasterAlignment_CheckAlignment_Patch { - static void Postfix(ref bool __result) { - if (StringUtils.ToToggleBool(settings.toggleSpellbookAbilityAlignmentChecks)) { - __result = true; - } - } - } - - [HarmonyPatch(typeof(SummonPool), "Register")] - static class SummonPool_Register_Patch { - static void Postfix(ref UnitEntityData unit) { - if (StringUtils.ToToggleBool(settings.toggleSetSpeedOnSummon)) { - unit.Descriptor.Stats.GetStat(StatType.Speed).BaseValue = settings.setSpeedOnSummonValue; - } - - if (StringUtils.ToToggleBool(settings.toggleMakeSummmonsControllable) && Storage.SummonedByPlayerFaction) { - Logger.ModLoggerDebug($"SummonPool.Register: Unit [{unit.CharacterName}] [{unit.UniqueId}]"); - UnitEntityDataUtils.Charm(unit); - - if (unit.Blueprint.AssetGuid == "6fdf7a3f850a1eb48bfbf44d9d0f45dd" && StringUtils.ToToggleBool(settings.toggleDisableWarpaintedSkullAbilityForSummonedBarbarians)) // WarpaintedSkullSummonedBarbarians - { - if (unit.Body.Head.HasItem && unit.Body.Head.Item?.Blueprint?.AssetGuid == "5d343648bb8887d42b24cbadfeb36991") // WarpaintedSkullItem - { - unit.Body.Head.Item.Ability.Deactivate(); - Logger.ModLoggerDebug(unit.Body.Head.Item.Name + "'s ability active: " + unit.Body.Head.Item.Ability.Active); - } - } - Storage.SummonedByPlayerFaction = false; - } - - if (StringUtils.ToToggleBool(settings.toggleRemoveSummonsGlow)) { - unit.Buffs.RemoveFact(Utilities.GetBlueprintByGuid<BlueprintFact>("706c182e86d9be848b59ddccca73d13e")); // SummonedCreatureVisual - unit.Buffs.RemoveFact(Utilities.GetBlueprintByGuid<BlueprintFact>("e4b996b5168fe284ab3141a91895d7ea")); // NaturalAllyCreatureVisual - } - } - } - - [HarmonyPatch(typeof(Quest), "TimeToFail", MethodType.Getter)] - static class Quest_HandleTimePassed_Patch { - static void Postfix(ref TimeSpan? __result) { - if (__result != null && StringUtils.ToToggleBool(settings.toggleFreezeTimedQuestAt90Days)) { - __result = TimeSpan.FromDays(90); - } - } - } - [HarmonyPatch(typeof(QuestObjective), "TimeToFail", MethodType.Getter)] - static class QuestObjective_HandleTimePassed_Patch { - static void Postfix(ref TimeSpan? __result) { - if (__result != null && StringUtils.ToToggleBool(settings.toggleFreezeTimedQuestAt90Days)) { - __result = TimeSpan.FromDays(90); - } - } - } - - [HarmonyPatch(typeof(QuestObjective), "Fail")] - static class QuestObjective_Fail_Patch { - static bool Prefix() { - if (StringUtils.ToToggleBool(settings.togglePreventQuestFailure)) { - return false; - } - return true; - } - } - - [HarmonyPatch(typeof(RuleCheckCastingDefensively), "Success", MethodType.Getter)] - static class RuleCheckCastingDefensively_Success_Patch { - static void Postfix(ref bool __result, RuleCheckCastingDefensively __instance) { - if (StringUtils.ToToggleBool(settings.toggleAlwaysSucceedCastingDefensively) && __instance.Initiator.IsPlayerFaction) { - __result = true; - } - } - } - - [HarmonyPatch(typeof(RuleCheckConcentration), "Success", MethodType.Getter)] - static class RuleCheckConcentration_Success_Patch { - static void Postfix(ref bool __result, RuleCheckConcentration __instance) { - if (StringUtils.ToToggleBool(settings.toggleAlwaysSucceedConcentration) && __instance.Initiator.IsPlayerFaction) { - __result = true; - } - } - } - - [HarmonyPatch(typeof(SpellBookToggle), "SetUp")] - static class SpellBookToggle_SetUp_Patch { - static bool Prefix(UnitEntityData unit, SpellBookToggle __instance, List<SpellbookClassTab> ___m_Tabs) { - if (StringUtils.ToToggleBool(settings.toggleSortSpellbooksAlphabetically)) { - __instance.Initialize(); - foreach (SpellbookClassTab tab in ___m_Tabs) - tab.SetActive(false); - __instance.Spellbooks = new List<Spellbook>(); - int i = 0; - __instance.Spellbooks = unit.Descriptor.Spellbooks.OrderBy(d => d.Blueprint.DisplayName).ToList(); - foreach (Spellbook spellbook in __instance.Spellbooks) { - spellbook.UpdateAllSlotsSize(false); - if (i < ___m_Tabs.Count) { - ___m_Tabs[i].SetName(spellbook.Blueprint.DisplayName); - ___m_Tabs[i].SetIndex(i); - ___m_Tabs[i].Toggle.onValueChanged.AddListener(new UnityAction<bool>(__instance.OnToggle)); - ___m_Tabs[i].SetLevel(spellbook.CasterLevel); - ___m_Tabs[i].SetActive(true); - ++i; - } - else - break; - } - if (__instance.Spellbooks.Count > 0) - __instance.SelectSpellbookByIndex(0); - __instance.gameObject.SetActive(__instance.Spellbooks.Count > 1); - return false; - } - return true; - } - } - - [HarmonyPatch(typeof(SpellBookView), "GetSpellsForLevel")] - static class SpellBookView_GetSpellsForLevel_Patch { - static void Postfix(ref List<AbilityData> __result) { - if (StringUtils.ToToggleBool(settings.toggleSortSpellsAlphabetically)) { - __result = __result.OrderBy(d => d.Name).ToList(); - } - } - } - - [HarmonyPatch(typeof(HitPlayer), "CalcHitLevel")] - static class HitPlayer_CalcHitLevel_Patch { - static void Postfix(ref HitLevel __result) { - if (StringUtils.ToToggleBool(settings.toggleSortSpellsAlphabetically)) { - __result = HitLevel.Crit; - } - } - } - - } - [HarmonyPatch(typeof(CampingSettings), "IsDungeon", MethodType.Getter)] - static class CampingSettings_IsDungeon_Patch { - static void Postfix(ref bool __result) { - if (!Main.Enabled) { - return; - } - if (StringUtils.ToToggleBool(settings.toggleCookingAndHuntingInDungeons)) { - __result = false; - } - } - } - - [HarmonyPatch(typeof(SoundState), "OnAreaLoadingComplete")] - public static class SoundState_OnAreaLoadingComplete_Patch { - private static void Postfix() { - Logger.ModLoggerDebug(Game.Instance.CurrentlyLoadedArea.AreaName.ToString()); - Logger.ModLoggerDebug(Game.Instance.CurrentlyLoadedArea.AssetGuid); - Logger.ModLoggerDebug(SceneManager.GetActiveScene().name); - - if (StringUtils.ToToggleBool(settings.toggleUnlimitedCasting) && SceneManager.GetActiveScene().name == "HouseAtTheEdgeOfTime_Courtyard_Light") { - UIUtility.ShowMessageBox(Strings.GetText("warning_UnlimitedCasting"), DialogMessageBoxBase.BoxType.Message, new Action<DialogMessageBoxBase.BoxButton>(Common.CloseMessageBox)); - } - if (StringUtils.ToToggleBool(settings.toggleNoDamageFromEnemies) && Game.Instance.CurrentlyLoadedArea.AssetGuid == "0ba5b24abcd5523459e54cd5877cb837") { - UIUtility.ShowMessageBox(Strings.GetText("warning_NoDamageFromEnemies"), DialogMessageBoxBase.BoxType.Message, new Action<DialogMessageBoxBase.BoxButton>(Common.CloseMessageBox)); - } - - } - } - - [HarmonyPatch(typeof(EncumbranceHelper.CarryingCapacity), "GetEncumbrance")] - [HarmonyPatch(new Type[] { typeof(float) })] - static class EncumbranceHelperCarryingCapacity_GetEncumbrance_Patch { - static void Postfix(ref Encumbrance __result) { - if (StringUtils.ToToggleBool(settings.toggleSetEncumbrance)) { - __result = Common.IntToEncumbrance(settings.setEncumbrancIndex); - } - } - } - - [HarmonyPatch(typeof(PartyEncumbranceController), "UpdateUnitEncumbrance")] - static class PartyEncumbranceController_UpdateUnitEncumbrance_Patch { - static void Postfix(UnitDescriptor unit) { - if (StringUtils.ToToggleBool(settings.toggleSetEncumbrance)) { - unit.Encumbrance = Common.IntToEncumbrance(settings.setEncumbrancIndex); - unit.Remove<UnitPartEncumbrance>(); - } - } - } - [HarmonyPatch(typeof(PartyEncumbranceController), "UpdatePartyEncumbrance")] - static class PartyEncumbranceController_UpdatePartyEncumbrance_Patch { - static bool Prefix() { - if (StringUtils.ToToggleBool(settings.toggleSetEncumbrance)) { - player.Encumbrance = Common.IntToEncumbrance(settings.setEncumbrancIndex); - return false; - } - return true; - } - } - [HarmonyPatch(typeof(GlobalMapRules), "ChangePartyOnMap")] - static class GlobalMapRules_ChangePartyOnMap_Patch { - static bool Prefix() { - if (StringUtils.ToToggleBool(settings.toggleInstantPartyChange)) { - return false; - } - return true; - } - } - [HarmonyPatch(typeof(IngameMenuManager), "OpenGroupManager")] - static class IngameMenuManager_OpenGroupManager_Patch { - static bool Prefix(IngameMenuManager __instance) { - if (StringUtils.ToToggleBool(settings.toggleInstantPartyChange)) { - MethodInfo startChangedPartyOnGlobalMap = __instance.GetType().GetMethod("StartChangedPartyOnGlobalMap", BindingFlags.NonPublic | BindingFlags.Instance); - startChangedPartyOnGlobalMap.Invoke(__instance, new object[] { }); - return false; - } - return true; - } - } - - [HarmonyPatch(typeof(BuildModeUtility), "IsDevelopment", MethodType.Getter)] - static class BuildModeUtility_IsDevelopment_Patch { - static void Postfix(ref bool __result) { - if (StringUtils.ToToggleBool(settings.toggleDevTools)) { - __result = true; - } - } - } - [HarmonyPatch(typeof(SmartConsole), "WriteLine")] - static class SmartConsole_WriteLine_Patch { - static void Postfix(string message) { - if (StringUtils.ToToggleBool(settings.toggleDevTools)) { - modLogger.Log(message); - UberLoggerAppWindow.Instance.Log(new UberLogger.LogInfo((UnityEngine.Object)null, nameof(SmartConsole), UberLogger.LogSeverity.Message, new List<UberLogger.LogStackFrame>(), (object)message, (object[])Array.Empty<object>())); - - } - } - } - [HarmonyPatch(typeof(SmartConsole), "Initialise")] - static class SmartConsole_Initialise_Patch { - static void Postfix() { - if (StringUtils.ToToggleBool(settings.toggleDevTools)) { - SmartConsoleCommands.Register(); - } - } - } - [HarmonyPatch(typeof(Kingmaker.MainMenu), "Start")] - static class MainMenu_Start_Patch { - static void Postfix() { - ModifiedBlueprintTools.Patch(); - - if (StringUtils.ToToggleBool(settings.toggleNoTempHPKineticist)) { - Cheats.PatchBurnEffectBuff(0); - } - } - } - [HarmonyPatch(typeof(MainMenuButtons), "Update")] - static class MainMenuButtons_Update_Patch { - static void Postfix() { - if (StringUtils.ToToggleBool(settings.toggleAutomaticallyLoadLastSave) && Storage.firstStart) { - Storage.firstStart = false; - EventBus.RaiseEvent<IUIMainMenu>((Action<IUIMainMenu>)(h => h.LoadLastGame())); - } - Storage.firstStart = false; - } - } - - - [HarmonyPatch(typeof(UnitPartNegativeLevels), "Add")] - static class UnitPartNegativeLevels_Add_Patch { - static bool Prefix(UnitPartNegativeLevels __instance) { - if (StringUtils.ToToggleBool(settings.toggleNoNegativeLevels) && __instance.Owner.IsPlayerFaction) { - return false; - } - return true; - } - } - - [HarmonyPatch(typeof(Kingmaker.Items.Slots.ItemSlot), "RemoveItem")] - [HarmonyPatch(new Type[] { typeof(bool) })] - static class ItemSlot_RemoveItem_Patch { - static void Prefix(Kingmaker.Items.Slots.ItemSlot __instance, ItemEntity ___m_Item, UnitDescriptor ___Owner, ref ItemEntity __state) { - if (Game.Instance.CurrentMode == GameModeType.Default && StringUtils.ToToggleBool(settings.togglAutoEquipConsumables)) { - __state = null; - if (___Owner.Body.QuickSlots.Any(x => x.HasItem && x.Item == ___m_Item)) { - __state = ___m_Item; - } - } - } - static void Postfix(Kingmaker.Items.Slots.ItemSlot __instance, ItemEntity ___m_Item, UnitDescriptor ___Owner, ItemEntity __state) { - if (Game.Instance.CurrentMode == GameModeType.Default && StringUtils.ToToggleBool(settings.togglAutoEquipConsumables)) { - if (__state != null) { - BlueprintItem blueprint = __state.Blueprint; - foreach (ItemEntity item in Game.Instance.Player.Inventory.Items) { - if (item.Blueprint.ItemType == ItemsFilter.ItemType.Usable && item.Blueprint == blueprint) { - __instance.InsertItem(item); - break; - } - } - __state = null; - } - } - } - } - - [HarmonyPatch(typeof(UnitEntityData), "CreateView")] - public static class UnitEntityData_CreateView_Patch { - public static void Prefix(ref UnitEntityData __instance) { - if (StringUtils.ToToggleBool(settings.toggleSpiderBegone)) { - SpidersBegone.CheckAndReplace(ref __instance); - } - } - } - - [HarmonyPatch(typeof(BlueprintUnit), "PreloadResources")] - public static class BlueprintUnit_PreloadResources_Patch { - public static void Prefix(ref BlueprintUnit __instance) { - if (StringUtils.ToToggleBool(settings.toggleSpiderBegone)) { - SpidersBegone.CheckAndReplace(ref __instance); - } - } - } - - [HarmonyPatch(typeof(EntityCreationController), "SpawnUnit")] - [HarmonyPatch(new Type[] { typeof(BlueprintUnit), typeof(Vector3), typeof(Quaternion), typeof(SceneEntitiesState) })] - public static class EntityCreationControllert_SpawnUnit_Patch1 { - public static void Prefix(ref BlueprintUnit unit) { - if (StringUtils.ToToggleBool(settings.toggleSpiderBegone)) { - SpidersBegone.CheckAndReplace(ref unit); - } - } - } - - [HarmonyPatch(typeof(EntityCreationController), "SpawnUnit")] - [HarmonyPatch(new Type[] { typeof(BlueprintUnit), typeof(UnitEntityView), typeof(Vector3), typeof(Quaternion), typeof(SceneEntitiesState) })] - public static class EntityCreationControllert_SpawnUnit_Patch2 { - public static void Prefix(ref BlueprintUnit unit) { - if (StringUtils.ToToggleBool(settings.toggleSpiderBegone)) { - SpidersBegone.CheckAndReplace(ref unit); - } - } - - } - - [HarmonyPatch(typeof(ContextConditionAlignment), "CheckCondition")] - public static class ContextConditionAlignment_CheckCondition_Patch { - public static void Postfix(ref bool __result, ContextConditionAlignment __instance) { - if (StringUtils.ToToggleBool(settings.toggleReverseCasterAlignmentChecks)) { - if (__instance.CheckCaster) { - __result = !__result; - } - } - } - } - - [HarmonyPatch(typeof(RuleSummonUnit), MethodType.Constructor)] - [HarmonyPatch(new Type[] { typeof(UnitEntityData), typeof(BlueprintUnit), typeof(Vector3), typeof(Rounds), typeof(int) })] - public static class RuleSummonUnit_Constructor_Patch { - public static void Prefix(UnitEntityData initiator, BlueprintUnit blueprint, Vector3 position, ref Rounds duration, ref int level, RuleSummonUnit __instance) { - if (StringUtils.ToToggleBool(settings.toggleSummonDurationMultiplier) && UnitEntityDataUtils.CheckUnitEntityData(initiator, (UnitSelectType)settings.indexSummonDurationMultiplier)) { - duration = new Rounds(Convert.ToInt32(duration.Value * settings.finalSummonDurationMultiplierValue)); ; - } - - if (StringUtils.ToToggleBool(settings.toggleSetSummonLevelTo20) && UnitEntityDataUtils.CheckUnitEntityData(initiator, (UnitSelectType)settings.indexSetSummonLevelTo20)) { - level = 20; - } - - if (StringUtils.ToToggleBool(settings.toggleMakeSummmonsControllable)) { - Storage.SummonedByPlayerFaction = initiator.IsPlayerFaction; - } - - Logger.ModLoggerDebug("Initiator: " + initiator.CharacterName + $"(PlayerFaction : {initiator.IsPlayerFaction})" + "\nBlueprint: " + blueprint.CharacterName + "\nPosition: " + position.ToString() + "\nDuration: " + duration.Value + "\nLevel: " + level); - } - } - - [HarmonyPatch(typeof(ActionBarManager), "CheckTurnPanelView")] - internal static class ActionBarManager_CheckTurnPanelView_Patch { - private static void Postfix(ActionBarManager __instance) { - if (StringUtils.ToToggleBool(settings.toggleMakeSummmonsControllable) && CombatController.IsInTurnBasedCombat()) { - Traverse.Create((object)__instance).Method("ShowTurnPanel", Array.Empty<object>()).GetValue(); - } - } - } - - [HarmonyPatch(typeof(UberLogger.Logger), "ForwardToUnity")] - static class UberLoggerLogger_ForwardToUnity_Patch { - static void Prefix(ref object message) { - if (StringUtils.ToToggleBool(settings.toggleUberLoggerForwardPrefix)) { - string message1 = "[UberLogger] " + message as string; - message = message1 as object; - } - } - } - - - [HarmonyPatch(typeof(DungeonStageInitializer), "Initialize")] - static class DungeonStageInitializer_Initialize_Patch { - static void Prefix(BlueprintDungeonArea area) { - Logger.ModLoggerDebug("Game.Instance.Player.DungeonState.Stage: " + Game.Instance.Player.DungeonState.Stage); - } - } - [HarmonyPatch(typeof(DungeonDebug), "SaveStage")] - static class DungeonDebug_SaveStage_Patch_Pre { - static void Prefix(string filename) { - Logger.ModLoggerDebug("DungeonDebug.SaveStage filename: " + filename); - Logger.ModLoggerDebug("DungeonDebug.SaveStage Path: " + Path.Combine(Application.persistentDataPath, "DungeonStages")); - } - } - [HarmonyPatch(typeof(DungeonDebug), "SaveStage")] - static class DungeonDebug_SaveStage_Patch_Post { - static void Postfix(string filename) { - if (settings.settingShowDebugInfo) { - try { - string str = File.ReadAllText(Path.Combine(Application.persistentDataPath, $"DungeonStages\\{filename}")); - modLogger.Log($"START---{filename}---START\n" + str + $"\nEND---{filename}---END"); - } - catch (Exception e) { - modLogger.Log(e.ToString()); - } - } - } - } - - [HarmonyPatch(typeof(RuleCalculateArmorCheckPenalty), "OnTrigger")] - public static class RuleCalculateArmorCheckPenalty_OnTrigger_Patch { - private static bool Prefix(RuleCalculateArmorCheckPenalty __instance) { - if (StringUtils.ToToggleBool(settings.toggleArmourChecksPenalty0)) { - if (!StringUtils.ToToggleBool(settings.toggleArmourChecksPenalty0OutOfCombatOnly)) { - Traverse.Create(__instance).Property("Penalty").SetValue(0); - return false; - } - else if (StringUtils.ToToggleBool(settings.toggleArmourChecksPenalty0OutOfCombatOnly) && !__instance.Armor.Wielder.Unit.IsInCombat) { - Traverse.Create(__instance).Property("Penalty").SetValue(0); - return false; - } - - } - return true; - } - } - [HarmonyPatch(typeof(UIUtilityItem), "GetArmorData")] - public static class UIUtilityItem_GetArmorData_Patch { - private static void Postfix(ref UIUtilityItem.ArmorData __result, ref ItemEntityArmor armor) { - if (StringUtils.ToToggleBool(settings.toggleArmourChecksPenalty0)) { - if (!StringUtils.ToToggleBool(settings.toggleArmourChecksPenalty0OutOfCombatOnly)) { - UIUtilityItem.ArmorData armorData = __result; - armorData.ArmorCheckPenalty = 0; - __result = armorData; - } - else if (StringUtils.ToToggleBool(settings.toggleArmourChecksPenalty0OutOfCombatOnly)) { - Logger.ModLoggerDebug(armor.Name); - if (armor.Wielder != null) { - Logger.ModLoggerDebug(armor.Name + ": " + armor.Wielder.CharacterName); - if (!armor.Wielder.Unit.IsInCombat) { - Logger.ModLoggerDebug(armor.Name + ": " + armor.Wielder.CharacterName + " - " + armor.Wielder.Unit.IsInCombat); - UIUtilityItem.ArmorData armorData = __result; - armorData.ArmorCheckPenalty = 0; - __result = armorData; - } - } - else { - Logger.ModLoggerDebug("!" + armor.Name); - UIUtilityItem.ArmorData armorData = __result; - armorData.ArmorCheckPenalty = 0; - __result = armorData; - } - - } - } - } - } - - - [HarmonyPatch(typeof(BlueprintAbility), "GetRange")] - static class BlueprintAbility_GetRange_Patch_Pre { - - private static Feet defaultClose = 30.Feet(); - private static Feet defaultMedium = 40.Feet(); - private static Feet defaultlong = 50.Feet(); - private static Feet cotwMedium = 60.Feet(); - private static Feet cotwLong = 100.Feet(); - [HarmonyPriority(Priority.Low)] - static void Postfix(ref Feet __result) { - if (StringUtils.ToToggleBool(settings.toggleTabletopSpellAbilityRange)) { - if (Main.callOfTheWild.ModIsActive()) { - if (__result == defaultClose) { - __result = 25.Feet(); - } - else if (__result == cotwMedium) { - __result = 100.Feet(); - } - else if (__result == cotwLong) { - __result = 400.Feet(); - } - } - else { - if (__result == defaultClose) { - __result = 25.Feet(); - } - else if (__result == defaultMedium) { - __result = 100.Feet(); - } - else if (__result == defaultlong) { - __result = 400.Feet(); - } - } - } - if (StringUtils.ToToggleBool(settings.toggleCustomSpellAbilityRange)) { - if (Main.callOfTheWild.ModIsActive()) { - if (__result == defaultClose) { - __result = settings.customSpellAbilityRangeClose.Feet(); - } - else if (__result == cotwMedium) { - __result = settings.customSpellAbilityRangeMedium.Feet(); - } - else if (__result == cotwLong) { - __result = settings.customSpellAbilityRangeLong.Feet(); - } - } - else { - if (__result == defaultClose) { - __result = settings.customSpellAbilityRangeClose.Feet(); - } - else if (__result == defaultMedium) { - __result = settings.customSpellAbilityRangeMedium.Feet(); - } - else if (__result == defaultlong) { - __result = settings.customSpellAbilityRangeLong.Feet(); - } - } - - } - - - - if (StringUtils.ToToggleBool(settings.toggleSpellAbilityRangeMultiplier)) { - if (settings.useCustomSpellAbilityRangeMultiplier) { - __result = __result * settings.customSpellAbilityRangeMultiplier; - } - else { - __result = __result * settings.spellAbilityRangeMultiplier; - } - } - } - } -#endif -} -#endif \ No newline at end of file diff --git a/ToyBox/classes/MonkeyPatchin/Braaainz.cs b/ToyBox/classes/MonkeyPatchin/Braaainz.cs deleted file mode 100644 index 2c6c837c0..000000000 --- a/ToyBox/classes/MonkeyPatchin/Braaainz.cs +++ /dev/null @@ -1,290 +0,0 @@ -using HarmonyLib; -using Kingmaker; -using Kingmaker.Blueprints; -using Kingmaker.Blueprints.Items.Armors; -using Kingmaker.Blueprints.Items.Components; -using Kingmaker.Blueprints.Items.Weapons; -using Kingmaker.Items; -using Kingmaker.Items.Parts; -using Kingmaker.UI.MVVM._PCView.Loot; -using Kingmaker.UI.MVVM._PCView.ServiceWindows.Inventory; -using Kingmaker.UI.MVVM._PCView.ServiceWindows.LocalMap.Markers; -using Kingmaker.UI.MVVM._PCView.Slots; -using Kingmaker.UI.MVVM._PCView.Tooltip.Bricks; -using Kingmaker.UI.MVVM._PCView.Vendor; -using Kingmaker.UI.MVVM._VM.ServiceWindows.Inventory; -using Kingmaker.UI.MVVM._VM.ServiceWindows.LocalMap.Utils; -using Kingmaker.UI.MVVM._VM.Slots; -using UniRx; -using Owlcat.Runtime.UI.MVVM; -using UnityEngine; -using UnityEngine.UI; -using System; -using Kingmaker.Items.Slots; -using Kingmaker.AI; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.UnitLogic.Alignments; -using ModKit; -using JetBrains.Annotations; -using Kingmaker.Utility; -using System.Linq; -using System.Collections.Generic; -using System.Reflection; -using ModKit.Utility; -using Kingmaker.Designers.EventConditionActionSystem.Evaluators; -using Kingmaker.UnitLogic; -using UnityEngine.PlayerLoop; -using Kingmaker.Visual.Animation.Events; -using Kingmaker.Visual.Animation; -using System.Security.Policy; -using Kingmaker.UnitLogic.Groups; -using Kingmaker.View; -using Kingmaker.Visual.Animation.Kingmaker; -using Kingmaker.Controllers.Optimization; -using Kingmaker.UnitLogic.Commands; -using static Owlcat.QA.Validation.BlueprintValidationHelper; -using static RootMotion.FinalIK.InteractionTrigger; -using TurnBased.Controllers; - -namespace ToyBox.BagOfPatches { -#if false - internal static class Braaainz { - public static Settings settings = Main.settings; - public static Player player = Game.Instance.Player; - - [HarmonyPatch(typeof(AiBrainController), nameof(AiBrainController.TickBrain))] - private static class AiBrainController_TickBrain_patch { - public static bool Prefix(AiBrainController __instance, UnitEntityData unit) { - Mod.Log($"{unit?.CharacterName.orange()} - Maybe BrainTick"); - return true; - } - } - [HarmonyPatch(typeof(AiBrainController), nameof(AiBrainController.SelectAction))] - private static class AiBrainController_SelectAction_patch { - public static bool Prefix(AiBrainController __instance, UnitEntityData unit, DecisionContext context, - [CanBeNull] AIDebugScope debugScope) { - Mod.Log($"{unit?.CharacterName.orange()} - SelectAction".cyan()); - return true; - } - } -#if true - [HarmonyPatch(typeof(AiBrainController), nameof(AiBrainController.FindBestAction))] - private static class AiBrainController_FindAction_patch { - private static string MaybeListToString(object o) { - if (o is List<TargetInfo> list) return $"<{string.Join(", ", list.Select(ti => ti.Unit.CharacterName))}>"; - return o.ToString(); - } - public static bool Prefix( - AiBrainController __instance, - UnitEntityData unit, - DecisionContext context, - out AiAction bestActionResult, - out UnitEntityData bestTargetResult, - out bool isAutoUseAbility - ) { - Mod.Log($" FindBestAction - {unit.CharacterName}".yellow()); - bestActionResult = null; - bestTargetResult = null; - isAutoUseAbility = false; - AiAction bestAction = bestActionResult = (AiAction)null; - UnitEntityData bestTarget = bestTargetResult = (UnitEntityData)null; - AiAction action = unit.IsPlayerFaction - ? unit.Brain.GetAvailableAutoUseAbility()?.DefaultAiAction - : null; - if (action != null) { - Mod.Log($" found AutoUseAction {action.DebugName}"); - using (AIDebugScope debugScope = AIDebugScope.Open((object)action)) - AiBrainController.CalculateActionScore(context, action, ref bestAction, ref bestTarget, debugScope); - } - isAutoUseAbility = bestAction != null && context.CurrentScore >= 0.1M; - if (!isAutoUseAbility) { - for (int index = 0; index < unit.Brain.CustomActions.Count; ++index) { - AiActionCustom customAction = unit.Brain.CustomActions[index]; - customAction.ValidateAndFix(); - using (AIDebugScope debugScope = AIDebugScope.Open((object)customAction)) { - if (customAction.IsValid) { - using (ProfileScope.New("One Action", (SimpleBlueprint)customAction.Blueprint)) { - if (!(context.BestScore > (Decimal)customAction.BaseScore)) - AiBrainController.CalculateActionScore(context, (AiAction)customAction, ref bestAction, ref bestTarget, debugScope); - } - } - } - } - if (bestAction != null) - Mod.Log($" found Custom Action {bestAction.DebugName} bestTarget: {bestTarget?.CharacterName}"); - } - if (!isAutoUseAbility && (bestAction == null || context.CurrentScore < 0.1M)) { - for (int index = 0; index < unit.Brain.AvailableActions.Count; ++index) { - AiAction availableAction = unit.Brain.AvailableActions[index]; - using (AIDebugScope debugScope = AIDebugScope.Open((object)availableAction)) { - using (ProfileScope.New("One Action", (SimpleBlueprint)availableAction.Blueprint)) { - Mod.Log($" checking {availableAction.DebugName.yellow()} - baseScore: {availableAction.BaseScore} best:{context.BestScore} context:{String.Join(",\n", context.ToDictionary().Select(p => $"{p.Key} : {MaybeListToString(p.Value)}"))}"); - if (!(context.BestScore > (Decimal)availableAction.BaseScore)) - AiBrainController.CalculateActionScore(context, availableAction, ref bestAction, ref bestTarget, debugScope); - } - } - } - if (bestAction != null) - Mod.Log($" {unit.CharacterName.orange()} found Brain.AvailableAction {bestAction?.DebugName.cyan()} bestTarget: {bestTarget?.CharacterName.yellow()}".green()); - } - bestActionResult = bestAction; - bestTargetResult = bestTarget; - return false; - } - } -#if false - [HarmonyPatch(typeof(ProfileScope), nameof(ProfileScope.New), new Type[] { typeof(string), typeof(UnityEngine.Object) })] - private static class ProfileScope_New_Patch { - public static void Postfix(ref IDisposable __result, string text, UnityEngine.Object ctx = null) { - if (text.Matches("Intersecting") - || text.Equals("some type name") - || text.Equals("BlueprintSpellsTable") - || text.Equals("Spellbook") - || text.Equals("UpdateEvents") - || text.Equals("Update dead bodies") - || text.Equals("Units") - || text.Equals("EntityBoundsHelper.FindEntitiesInRange") - || text.Equals("GetDataList") - || text.Equals("Update Revealer") - || text.Equals("Playing") - || text.Equals("Animation") - || text.Equals("Mixer") - || text.Equals("Scale") - || text.Equals("GetWeight") - || text.Equals("UpdateSkeleton") - || text.Equals("One Controller") - || text.Equals("EarlyOuts") - || text.Equals("GetParticles") - || text.Equals("UpdateMap") - || text.Equals("UpdateUnitEncumbrance") - || text.Equals("Revealers") - || text.Equals("GetSpeed") - || text.Equals("ActionBarVM") - || text.Equals("UO") - || text.Equals("VM visibility") - || text.Equals("View update") - || text.Equals("Update AkSoundEngineController") - || text.Equals("Update Objects") - || text.Equals("Dummy Listener") - || text.Equals("UpdateListenerPosition") - || text.Equals("FxAoeSpawner") - || text.Equals("ApplyResults") - || text.Equals("Groups") - || text.Equals("add") - || text.Equals("remove") - || text.Equals("FxAoeSpawner") - || text.Equals("SetParticles") - || text.Equals("UpdatePartyEncumbrance") - || text.Equals("Encumbrance.GetPartyCarryingCapacity") - || text.Equals("PartyCapacity") - || text.Equals("DetachedCapacity") - || text.Equals("EquipmentWeight") - || text.Equals("Additional") - || text.Equals("PrepareEntities") - || text.Equals("UpdateBorders") - || text.Equals("PathVisualizer Update") - || text.Equals("Mixer.NormalizeWeights") - || text.Equals("AnimationManager.UpdateAnimations") - || text.Equals("Animation.UpdateInternal") - || text.Equals("Mixer.NormalizeWeights") - || text.Equals("AnimationManager.UpdateActions") - || text.Equals("Cleanup Revealers") - || text.Equals("Collect Revealers") - || text.Equals("UpdateRendererRevealers") - || text.Equals("AnimationClipWrapperStateMachineBehaviour.OnStateUpdate") - || text.Equals("GetSizeScale") - || text.Equals("ActionBarVM OnUpdateHandler") - || text.Equals("Localized string") - || text.Equals("TransitioningIn") - || text.Equals("UpdateEntity") - || text.Equals("Update Object Position") - || text.Equals("Update Object Zone") - || text.Equals("ActionBarVM UpdateSelection") - || text.Equals("TransitioningIn") - || text.Equals("Tick Movement") - || text.Equals("ObstacleAnalyzer.GetNearestNode") - || text.Equals("Before Avoidance") - || text.Equals("Avoidance") - || text.Equals("Init") - || text.Equals("Navmesh Avoidance") - || text.Equals("Units Avoidance") - || text.Equals("UnitGroup.IsEnemy") - || text.Equals("UnitGroup.IsEnemy") - || text.Equals("UnitGroup.IsEnemy") - || text.Equals("UnitGroup.IsEnemy") - || text.Equals("Calc Direction") - || text.Equals("After avoidance") - || text.Equals("Physics move") - || text.Equals("Navmesh clamp") - || text.Equals("ObstacleAnalyzer.GetNearestNode") - || text.Equals("Physics") - || text.Equals("Combat") - || text.Equals("State") - || text.Equals("MicroIdle") - || text.Equals("UnitAnimationManager.Tick()") - || text.Equals("Tick Animator") - || text.Equals("Check Sleeping") - || text.Equals("Set Variables") - || text.Equals("Movement") - || text.Equals("Speed") - || text.Equals("TBM") - || text.Equals("UnitCommands.RemoveFinishedAndUpdateQueue") - || text.Equals("Tick one shape") - || text.Equals("CopyUnitsInside") - || text.Equals("EndIfNecessary") - || text.Equals("UpdatePosition") - || text.Equals("UpdateUnits") - || text.Equals("EntityBoundsHelper.FindUnitsInShape") - || text.Equals("UpdateUnit") - || text.Equals("IsUnitInside") - || text.Equals("SearchUnitInside") - || text.Equals("HandleTick") - || text.Equals("SetLifeState") - || text.Equals("Check Should Pause Cutscene") - || text.Equals("CombatController Change turn") - || text.Equals("Mounted crap") - || text.Equals("Vision Range") - || text.Equals("Collect SpottedBy") - || text.Equals("Should Be In Stealth") - || text.Matches("ShouldBeInStealth") - || text.Equals("Memory Cleanup") - || text.Equals("TickCommand") - || text.Equals("TickApproaching") - || text.Equals("TransitionOut") - || text.Matches("Animator") - || text.Equals("Finishing") - || text.Equals("HandleRound") - || text.Equals("Check Condition") - || text.Equals("Action") - || text.Equals("Cooldown") - || text.Equals("HP") - || text.Equals("HP Short") - || text.Equals("SpellHandle") - || text.Equals("Find animation") - ) - return; - Mod.Log($"ProfileScope: {text ?? "null scope"}".pink()); - } - } -#endif -#if false - [HarmonyPatch(typeof(ProfileScope), nameof(ProfileScope.New), new Type[] { typeof(string), typeof(SimpleBlueprint) })] - private static class ProfileScope_New_Patch2 { - public static void Postfix(ref IDisposable __result, string text, SimpleBlueprint _) { - Mod.Log($"ProfileScope: {text ?? "null scope"}".pink()); - } - } - [HarmonyPatch] - static class ProfileScope_New_Patch { - static IEnumerable<MethodInfo> TargetMethods() - => typeof(ProfileScope).GetMethods(nameof(ProfileScope.New)); //not sure if you need binding flags here - - static void Postfix(string text) - => Mod.Log($"ProfileScope: {text ?? "null scope"}".pink()); - } -#endif - -#endif - } -#endif -} \ No newline at end of file diff --git a/ToyBox/classes/MonkeyPatchin/EnhancedUI/InventoryStashVM.cs b/ToyBox/classes/MonkeyPatchin/EnhancedUI/InventoryStashVM.cs deleted file mode 100644 index 6fed16646..000000000 --- a/ToyBox/classes/MonkeyPatchin/EnhancedUI/InventoryStashVM.cs +++ /dev/null @@ -1,53 +0,0 @@ -using HarmonyLib; -using Kingmaker.Blueprints.Root; -using Kingmaker.UI.Common; -using Kingmaker.UI.MVVM._PCView.ServiceWindows.Inventory; -using Kingmaker.UI.MVVM._PCView.Slots; -using Kingmaker.UI.MVVM._VM.ServiceWindows.Inventory; -using Kingmaker.UI.MVVM._VM.Slots; -using Kingmaker.UI.Vendor; -using ModKit; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using TMPro; -using UniRx; -using static ToyBox.BlueprintExtensions; - -namespace ToyBox.Inventory { - // Handles both adding selected sorters to the sorter dropdowns and making sure that the dropdown is properly updates to match the selected sorter. - [HarmonyPatch(typeof(SlotsGroupVM<ItemSlotVM>))] - public static class SlotsGroupVM_ { - [HarmonyPatch(nameof(SlotsGroupVM<ItemSlotVM>.UpdateVisibleCollection), new Type[] {typeof(bool), typeof(bool)})] - [HarmonyPostfix] - public static void UpdateVisibleCollection(SlotsGroupVM<ItemSlotVM> __instance, bool force = false, bool forceSetIndex = false) { - // InGamePCView(Clone)/InGameStaticPartPCView/StaticCanvas/ServiceWindowsPCView/Background/Windows/InventoryPCView/Inventory/Stash/StashContainer/ - // GlobalMapPCView(Clone)/StaticCanvas/ServiceWindowsConfig/Background/Windows/InventoryPCView/Inventory/Stash/StashContainer/ - try { - var inventoryScreen = UIHelpers.InventoryScreen; - var inventoryView = inventoryScreen.GetComponent<InventoryStashPCView>(); - var stashHeader = inventoryScreen.Find("Inventory/Stash/StashContainer/StashHeader"); - var stashHeaderLabel = stashHeader.GetComponent<TextMeshProUGUI>(); - if (Main.Settings.toggleEnhancedInventory) { - var count = __instance.VisibleCollection.Sum(vm => vm.HasItem ? vm.ItemEntity.Count : 0); -// var distinctCount = __instance.VisibleCollection.Count(vm => vm.HasItem); - stashHeaderLabel.AddSuffix($" ({count} items)".size(25), '('); - // stashHeaderLabel.AddSuffix($" ({count}{(count != distinctCount ? $" ({distinctCount})" : "")} items)".size(25), '('); - } - // Cleanup modified text if enhanced inventory gets turned off - else if (stashHeaderLabel.text.IndexOf('(') != -1) - stashHeaderLabel.AddSuffix(null, '('); - } - catch { } - } - // Player side - // VendorPCView - InGamePCView(Clone)/InGameStaticPartPCView/StaticCanvas/NestedCanvas1/VendorPCView/ - // InGamePCView(Clone)/InGameStaticPartPCView/StaticCanvas/NestedCanvas1/VendorPCView/MainContent/PlayerStash/ - - // Vendor side - // InGamePCView(Clone)/InGameStaticPartPCView/StaticCanvas/NestedCanvas1/VendorPCView/MainContent/VendorBlock/VendorHeader - // InGamePCView(Clone)/InGameStaticPartPCView/StaticCanvas/NestedCanvas1/VendorPCView/MainContent/VendorBlock/PC_FilterBlock/FilterPCView - } -} \ No newline at end of file diff --git a/ToyBox/classes/MonkeyPatchin/EnhancedUI/ItemsFilter.cs b/ToyBox/classes/MonkeyPatchin/EnhancedUI/ItemsFilter.cs deleted file mode 100644 index a3b5a3b69..000000000 --- a/ToyBox/classes/MonkeyPatchin/EnhancedUI/ItemsFilter.cs +++ /dev/null @@ -1,268 +0,0 @@ -using HarmonyLib; -using Kingmaker.Blueprints; -using Kingmaker.Blueprints.Items; -using Kingmaker.Blueprints.Items.Components; -using Kingmaker.Blueprints.Items.Equipment; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.Items; -using Kingmaker.Items.Parts; -using Kingmaker.UI.Common; -using Kingmaker.UI.MVVM._PCView.Loot; -using Kingmaker.UI.MVVM._PCView.ServiceWindows.Inventory; -using Kingmaker.UI.MVVM._PCView.ServiceWindows.LocalMap.Markers; -using Kingmaker.UI.MVVM._PCView.Slots; -using Kingmaker.UI.MVVM._PCView.Tooltip.Bricks; -using Kingmaker.UI.MVVM._PCView.Vendor; -using Kingmaker.UI.MVVM._VM.ServiceWindows.Inventory; -using Kingmaker.UI.MVVM._VM.ServiceWindows.LocalMap.Utils; -using Kingmaker.UI.MVVM._VM.Slots; -using System; -using System.Collections.Generic; -using ModKit; -using static ToyBox.BlueprintExtensions; -using Kingmaker.Blueprints.Root; -using Kingmaker.Enums.Damage; -using Kingmaker.UI.MVVM._VM.Tooltip.Templates; -using Kingmaker.UI.Tooltip; -using System.Linq; -using ModKit.Utility; -using ToyBox.classes.MainUI.Inventory; - -namespace ToyBox.Inventory { - // Hook #1 out of #2 for filtering - this hook handled the custom filter categories. - [HarmonyPatch(typeof(ItemsFilter), nameof(ItemsFilter.ShouldShowItem), new Type[] { typeof(BlueprintItem), typeof(ItemsFilter.FilterType), typeof(ItemEntity) })] - public static class ItemsFilter_ShouldShowItem_ItemEntity { - // Here, we handle filtering any expanded categories that we have. - [HarmonyPrefix] - public static bool Prefix(BlueprintItem blueprintItem, - ItemsFilter.FilterType filter, - ItemEntity item, - ref bool __result) { - if (item == null) return true; - ExpandedFilterType expanded_filter = (ExpandedFilterType)filter; - //Mod.Log($"ItemsFilter_ShouldShowItem_ItemEntity - filter: {filter} expandedFilter: {expanded_filter}"); - - if (expanded_filter == ExpandedFilterType.QuickslotUtilities) { - __result = item.Blueprint is BlueprintItemEquipmentUsable blueprint && blueprint.Type != UsableItemType.Potion && blueprint.Type != UsableItemType.Scroll; - } - else if (expanded_filter == ExpandedFilterType.UnlearnedRecipes) { - CopyRecipe recipe = item.Blueprint.GetComponent<CopyRecipe>(); - __result = recipe != null && recipe.CanCopy(item, null); - } - else if (expanded_filter == ExpandedFilterType.UnreadDocuments) { - ItemPartShowInfoCallback cb = item.Get<ItemPartShowInfoCallback>(); - __result = cb != null && (!cb.m_Settings.Once || !cb.m_Triggered); - } - else if (expanded_filter == ExpandedFilterType.UsableWithoutUMD) { - UnitEntityData unit = WrathExtensions.GetCurrentCharacter(); - __result = item.Blueprint is BlueprintItemEquipmentUsable blueprint && (blueprint.Type == UsableItemType.Scroll || blueprint.Type == UsableItemType.Wand) && unit != null && !blueprint.IsUnitNeedUMDForUse(unit); - } - else if (expanded_filter == ExpandedFilterType.CurrentEquipped) { - UnitEntityData unit = SelectedCharacterObserver.Shared.SelectedUnit ?? WrathExtensions.GetCurrentCharacter(); - if (unit != null) { - if (item.Blueprint is BlueprintItemEquipment && !(item.Blueprint is BlueprintItemEquipmentUsable)) - __result = item.CanBeEquippedBy(unit); - } -#if false - __result = unit != null; - if (__result) { - bool weapon_match = item is ItemEntityWeapon weapon && ((unit.Body.PrimaryHand.HasWeapon && unit.Body.PrimaryHand.Weapon.Blueprint.Type == weapon.Blueprint.Type) || (unit.Body.SecondaryHand.HasWeapon && unit.Body.SecondaryHand.Weapon.Blueprint.Type == weapon.Blueprint.Type)); - - bool shield_match = item is ItemEntityShield shield && ((unit.Body.PrimaryHand.HasShield && unit.Body.PrimaryHand.Shield.Blueprint.Type == shield.Blueprint.Type) || (unit.Body.SecondaryHand.HasShield && unit.Body.SecondaryHand.Shield.Blueprint.Type == shield.Blueprint.Type)); - - bool armour_match = item is ItemEntityArmor armor && unit.Body.Armor.HasArmor && unit.Body.Armor.Armor.Blueprint.ProficiencyGroup == armor.Blueprint.ProficiencyGroup; - - __result = weapon_match || shield_match || armour_match; - } -#endif - } - else if (expanded_filter == ExpandedFilterType.NonZeroPW) { - __result = item.Blueprint.SellPrice > 0 && item.Blueprint.Weight > 0.0f; - } - else if (expanded_filter == ExpandedFilterType.UnlearnedScrolls) - { - var scroll = item.Blueprint.GetComponent<CopyScroll>(); - var unit = Kingmaker.Game.Instance.SelectionCharacter.CurrentSelectedCharacter; - var canCopy = scroll?.CanCopy(item, unit) ?? false; - __result = unit != null && canCopy; - } - else { - // Original call - proceed as normal. - return true; - } - - // This call to the blueprint version will skip original in prefix then apply the search bar logic in postfix. - __result = __result && ItemsFilter.ShouldShowItem(item.Blueprint, filter); - return false; - } - } - - // Hook #2 out of #2 for filtering - this hook handles filtering based on string search. - [HarmonyPatch(typeof(ItemsFilter), nameof(ItemsFilter.ShouldShowItem), new Type[] { typeof(BlueprintItem), typeof(ItemsFilter.FilterType), typeof(ItemEntity) })] - public static class ItemsFilter_ShouldShowItem_Blueprint { - public static string SearchContents = null; - - // Prefix: If we're filtering one of the expanded categories, we require more than the blueprint - we require the instance. - // If someone calls the function to check the blueprint directly, for expanded categories, we must simply allow everything. - [HarmonyPrefix] - public static bool Prefix(BlueprintItem blueprintItem, - ItemsFilter.FilterType filter, - ItemEntity item, - ref bool __result) { - __result = true; - return (int)filter < (int)ExpandedFilterType.QuickslotUtilities; - } - - // Postfix: We apply the string match, if any, to the resulting matches from the original call (or our prefix). - [HarmonyPostfix] - public static void Postfix(BlueprintItem blueprintItem, - ItemsFilter.FilterType filter, - ItemEntity item, - ref bool __result) { - if (__result && !string.IsNullOrWhiteSpace(SearchContents)) { - __result = false; - - if (Main.Settings.InventorySearchCriteria.HasFlag(InventorySearchCriteria.ItemName)) { - __result |= blueprintItem.Name.IndexOf(SearchContents, StringComparison.OrdinalIgnoreCase) >= 0; - } - - if (Main.Settings.InventorySearchCriteria.HasFlag(InventorySearchCriteria.ItemType)) { - __result |= blueprintItem.ItemType.ToString().IndexOf(SearchContents, StringComparison.OrdinalIgnoreCase) >= 0; - } - - if (Main.Settings.InventorySearchCriteria.HasFlag(InventorySearchCriteria.ItemSubtype)) { - __result |= blueprintItem.SubtypeName.IndexOf(SearchContents, StringComparison.OrdinalIgnoreCase) >= 0; - } - - if (Main.Settings.InventorySearchCriteria.HasFlag(InventorySearchCriteria.ItemDescription)) { - __result |= blueprintItem.Description.IndexOf(SearchContents, StringComparison.OrdinalIgnoreCase) >= 0; - } - } - } - } - - // Sorting - this hook handles custom sorting categories. - [HarmonyPatch(typeof(ItemsFilter))] - public static class ItemsFilter_ItemSorter { - private static int CompareByWeightValue(ItemEntity a, ItemEntity b, ItemsFilter.FilterType filter) { - float a_weight_value = a.Blueprint.Weight <= 0.0f ? float.PositiveInfinity : a.Blueprint.Cost / a.Blueprint.Weight; - float b_weight_value = b.Blueprint.Weight <= 0.0f ? float.PositiveInfinity : b.Blueprint.Cost / b.Blueprint.Weight; - return a_weight_value == b_weight_value ? ItemsFilter.CompareByTypeAndName(a, b, filter) : (a_weight_value > b_weight_value ? 1 : -1); - } - - [HarmonyPrefix] - [HarmonyPatch(nameof(ItemsFilter.CompareByTypeAndName))] - private static bool CompareByTypeAndName(ItemEntity a, ItemEntity b, ItemsFilter.FilterType filter, ref int __result) { - // First by main type - int a_b_comparison = a.Blueprint.ItemType.CompareTo(b.Blueprint.ItemType); - if (a_b_comparison != 0) { - __result = a_b_comparison; - return false; - } - - // Then by subtype - a_b_comparison = string.Compare(a.Blueprint.SubtypeName, b.Blueprint.SubtypeName, StringComparison.OrdinalIgnoreCase); - if (a_b_comparison != 0) { - __result = a_b_comparison; - return false; - } - - // Finally by name - __result = string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase); - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(nameof(ItemsFilter.ItemSorter))] - public static bool Prefix(ItemsFilter.SorterType type, List<ItemEntity> items, ItemsFilter.FilterType filter, ref List<ItemEntity> __result) { - ExpandedSorterType expanded_type = (ExpandedSorterType)type; - - if (expanded_type == ExpandedSorterType.WeightValueUp) { - items.Sort((ItemEntity a, ItemEntity b) => CompareByWeightValue(a, b, filter)); - } - else if (expanded_type == ExpandedSorterType.WeightValueDown) { - items.Sort((ItemEntity a, ItemEntity b) => CompareByWeightValue(a, b, filter)); - items.Reverse(); - } - else if (expanded_type == ExpandedSorterType.RarityUp) { - items.Sort((a, b) => RarityCompare(a, b, false, filter, ItemsFilter.CompareByPrice)); - } - else if (expanded_type == ExpandedSorterType.RarityDown) { - items.Sort((a,b) => RarityCompare(a, b, true, filter, ItemsFilter.CompareByPrice)); - } - else { - return true; - } - - __result = items; - return false; - } - - [HarmonyPatch(typeof(ItemsFilter), nameof(ItemsFilter.IsMatchSearchRequest))] - private static class ItemsFilter_IsMatchSearchRequest_Patch { - - public static bool Prefix(ref bool __result, ItemEntity item, string searchRequest) { - if (string.IsNullOrEmpty(searchRequest) || item == null) { - __result = true; - return false; - } - string[] separator = new string[1] { ", " }; - string[] strArray = searchRequest.Split(separator, StringSplitOptions.RemoveEmptyEntries); - if (strArray.Length > 1) { - __result = strArray.All<string>(searchWord => ItemsFilter.IsMatchSearchRequest(item, searchWord)); - return false; - } - if (item.Name.StringEntry(searchRequest, item)) { - __result = true; - return false; - } - foreach (ItemsFilter.FilterType filterType in Enum.GetValues(typeof(ItemsFilter.FilterType))) { - var itemFilter = LocalizedTexts.Instance.ItemsFilter; - if (itemFilter.GetText(filterType).Equals(searchRequest, StringComparison.InvariantCultureIgnoreCase)) { - __result = ItemsFilter.ShouldShowItem(item, filterType); - return false; - } - } - foreach (ItemsFilter.FilterType filterType in Enum.GetValues(typeof(ExpandedFilterType))) { - var entry = EnhancedInventory.FilterCategoryMap.Values.FirstOrDefault(e => e.index == (int)filterType); - if (entry.title.Matches(searchRequest)) { - __result = ItemsFilter.ShouldShowItem(item, filterType); - return false; - } - } - ItemTooltipData itemTooltipData; - if (!ItemsFilter.s_ItemTooltipDataSet.TryGetValue(item, out itemTooltipData)) { - itemTooltipData = UIUtilityItem.GetItemTooltipData(item, true); - ItemsFilter.s_ItemTooltipDataSet.Add(item, itemTooltipData); - } - foreach (KeyValuePair<TooltipElement, string> text in itemTooltipData.Texts) { - switch (text.Key) { - case TooltipElement.Name: - case TooltipElement.Count: - case TooltipElement.ItemType: - case TooltipElement.Price: - case TooltipElement.SellPrice: - case TooltipElement.Wielder: - case TooltipElement.WielderSlot: - case TooltipElement.Damage: - case TooltipElement.PhysicalDamage: - case TooltipElement.EquipDamage: - case TooltipElement.ArmorCheckPenaltyDetails: - case TooltipElement.ArcaneSpellFailureDetails: - case TooltipElement.Charges: - case TooltipElement.CasterLevel: - continue; - default: - if (text.Value.StringEntry(searchRequest, item)) { - __result = true; - return false; - } - continue; - } - } - __result = itemTooltipData.Energy.Any(e => UIUtilityTexts.GetTextByKey(e.Key).StringEntry(searchRequest, item)) || itemTooltipData.PhysicalDamage.Any(d => UIUtilityTexts.GetDamageFormText(d.Key).StringEntry(searchRequest, item)); - return false; - } - } - } -} \ No newline at end of file diff --git a/ToyBox/classes/MonkeyPatchin/EnhancedUI/ItemsFilterPCView.cs b/ToyBox/classes/MonkeyPatchin/EnhancedUI/ItemsFilterPCView.cs deleted file mode 100644 index 65579a42d..000000000 --- a/ToyBox/classes/MonkeyPatchin/EnhancedUI/ItemsFilterPCView.cs +++ /dev/null @@ -1,116 +0,0 @@ -using HarmonyLib; -using Kingmaker.Blueprints.Root; -using Kingmaker.UI.Common; -using Kingmaker.UI.MVVM._PCView.Slots; -using ModKit; -using Owlcat.Runtime.UI.Controls.Button; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using UniRx; -using static ToyBox.BlueprintExtensions; - -namespace ToyBox.Inventory { - // Handles both adding selected sorters to the sorter dropdowns and making sure that the dropdown is properly updates to match the selected sorter. - [HarmonyPatch(typeof(ItemsFilterPCView))] - public static class ItemsFilterPCView_ { - public static Settings Settings = Main.Settings; - - private static readonly MethodInfo[] _methodInfosToTranspile = new MethodInfo[] { - AccessTools.Method(typeof(ItemsFilterPCView_), nameof(SetDropdown)), - AccessTools.Method(typeof(ItemsFilterPCView_), nameof(SetSorter)), - AccessTools.Method(typeof(ItemsFilterPCView_), nameof(ObserveFilterChange)), - }; - - private static void SetDropdown(ItemsFilterPCView __instance, ItemsFilter.SorterType val) { - if (!KnownFilterViews.Contains(__instance)) - KnownFilterViews.Add(__instance); - if (Settings.toggleEnhancedInventory) { - if (!ItemsFilterSearchPCView_Initialize_Patch.KnownFilterViews.Contains(__instance.m_SearchView)) - ItemsFilterSearchPCView_Initialize_Patch.KnownFilterViews.Add(__instance.m_SearchView); - __instance.m_Sorter.value = EnhancedInventory.SorterMapper.From((int)val); - } - else - __instance.m_Sorter.value = (int)val; - } - - private static void SetSorter(ItemsFilterPCView instance, int val) { - if (Settings.toggleEnhancedInventory) - instance.ViewModel.SetCurrentSorter((ItemsFilter.SorterType)EnhancedInventory.SorterMapper.To(val)); - else - instance.ViewModel.SetCurrentSorter((ItemsFilter.SorterType)val); - } - - private static ItemsFilter.FilterType _last_filter; - - private static void ObserveFilterChange(ItemsFilterPCView instance, ItemsFilter.FilterType filter) { - if (_last_filter != filter) { - _last_filter = filter; - instance.ScrollToTop(); - } - } - - // In BindViewImplementation, there are two inline delegates; we replace both of those in order with our own. - [HarmonyTranspiler] - [HarmonyPatch(nameof(ItemsFilterPCView.BindViewImplementation))] - public static IEnumerable<CodeInstruction> BindViewImplementation(IEnumerable<CodeInstruction> instructions) { - List<CodeInstruction> il = instructions.ToList(); - - int ldftn_count = 0; - - for (int i = 0; i < il.Count && ldftn_count < _methodInfosToTranspile.Length; ++i) { - if (il[i].opcode == OpCodes.Ldftn) { - il[i].operand = _methodInfosToTranspile[ldftn_count++]; - } - } - - return il.AsEnumerable(); - } - private static readonly HashSet<ItemsFilterPCView> KnownFilterViews = new(); - public static void ReloadFilterViews() { - foreach (var filterView in KnownFilterViews) { - filterView.ReloadSorterOptions(); - } - } - private static void ReloadSorterOptions(this ItemsFilterPCView filterView) { - if (!Settings.toggleEnhancedInventory) return; - filterView.m_Sorter.ClearOptions(); - List<string> options = new List<string>(); - - foreach (var flag in EnumHelper.ValidSorterCategories) { - if (Settings.InventoryItemSorterOptions.HasFlag(flag) - && EnhancedInventory.SorterCategoryMap.TryGetValue(flag, out var entry) - ) { - (int index, string text) = entry; - if (text == null) { - //Mod.Log($"adding {flag} : {text}"); - text = LocalizedTexts.Instance.ItemsFilter.GetText((ItemsFilter.SorterType)index); - EnhancedInventory.SorterCategoryMap[flag] = (index, text); - } - //Mod.Log($"flag: {flag} - text: {text}"); - options.Add(text); - } - } - - filterView.m_Sorter.AddOptions(options); - } - - // Adds the sorters to the dropdown. - [HarmonyPostfix] - [HarmonyPatch(nameof(ItemsFilterPCView.Initialize), new Type[] { })] - public static void Initialize(ItemsFilterPCView __instance) { - if (!Settings.toggleEnhancedInventory) return; - if (!KnownFilterViews.Contains(__instance)) - KnownFilterViews.Add(__instance); - __instance.ReloadSorterOptions(); - } - - [HarmonyPrefix] - [HarmonyPatch(nameof(ItemsFilterPCView.Initialize), new Type[] { typeof(bool) })] - public static void Initialize_Prefix(ref bool needReset) { - needReset = false; - } - } -} \ No newline at end of file diff --git a/ToyBox/classes/MonkeyPatchin/EnhancedUI/ItemsFilterSearchPCView.cs b/ToyBox/classes/MonkeyPatchin/EnhancedUI/ItemsFilterSearchPCView.cs deleted file mode 100644 index c8f060d78..000000000 --- a/ToyBox/classes/MonkeyPatchin/EnhancedUI/ItemsFilterSearchPCView.cs +++ /dev/null @@ -1,69 +0,0 @@ -using HarmonyLib; -using Kingmaker.Blueprints.Items; -using Kingmaker.Blueprints.Root; -using Kingmaker.Items; -using Kingmaker.UI.Common; -using Kingmaker.UI.MVVM._PCView.Slots; -using ModKit; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using UniRx; -using UnityEngine; -using static ToyBox.BlueprintExtensions; - -namespace ToyBox.Inventory { - [HarmonyPatch(typeof(ItemsFilterSearchPCView))] - public static class ItemsFilterSearchPCView_Initialize_Patch { - public static Settings Settings = Main.Settings; - public static readonly HashSet<ItemsFilterSearchPCView> KnownFilterViews = new(); - public static void ReloadFilterViews() { - foreach (var filterView in KnownFilterViews) { - filterView.ReloadFilterOptions(); - } - } - private static void ReloadFilterOptions(this ItemsFilterSearchPCView filterView) { - if (!Settings.toggleEnhancedInventory) return; - filterView.m_DropdownValues.Clear(); - List<string> options = new List<string>(); - - foreach (var flag in EnumHelper.ValidFilterCategories) { - if (Main.Settings.SearchFilterCategories.HasFlag(flag) - && EnhancedInventory.FilterCategoryMap.TryGetValue(flag, out var entry) - ) { - (int index, string text) = entry; - if (text == null) { - //Mod.Log($"adding {flag} : {text}"); - text = LocalizedTexts.Instance.ItemsFilter.GetText((ItemsFilter.FilterType)index); - EnhancedInventory.FilterCategoryMap[flag] = (index, text); - } - //Mod.Log($"flag: {flag} - text: {text}"); - options.Add(text); - filterView.m_DropdownValues.Add(text); - } - } - filterView.SetupDropdown(); - } - [HarmonyPatch(nameof(ItemsFilterSearchPCView.Initialize), new Type[] { })] - [HarmonyPostfix] - public static void Initialize(ItemsFilterSearchPCView __instance) { - if (!Settings.toggleEnhancedInventory) return; - if (!KnownFilterViews.Contains(__instance)) - KnownFilterViews.Add(__instance); - __instance.ReloadFilterOptions(); - } - [HarmonyPatch(nameof(ItemsFilterSearchPCView.SetActive), new Type[] { typeof(bool)})] - [HarmonyPrefix] - public static bool SetActive(ItemsFilterSearchPCView __instance, bool value) { - if (!Settings.toggleEnhancedInventory || !Settings.toggleDontClearSearchWhenLoseFocus) return true; - __instance.gameObject.SetActive(value); - __instance.m_Dropdown.Hide(); - if (!value) - return false; - __instance.m_InputField.Select(); - return false; - } - } -} \ No newline at end of file diff --git a/ToyBox/classes/MonkeyPatchin/EnhancedUI/LocalMap.cs b/ToyBox/classes/MonkeyPatchin/EnhancedUI/LocalMap.cs deleted file mode 100644 index 4065994e5..000000000 --- a/ToyBox/classes/MonkeyPatchin/EnhancedUI/LocalMap.cs +++ /dev/null @@ -1,602 +0,0 @@ -using HarmonyLib; -using Kingmaker; -using Kingmaker.Blueprints.Area; -using Kingmaker.Controllers; -using Kingmaker.Controllers.Clicks.Handlers; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.UI._ConsoleUI.Overtips; -using Kingmaker.UI.MVVM._PCView.ServiceWindows.LocalMap; -using Kingmaker.UI.MVVM._PCView.ServiceWindows.LocalMap.Markers; -using Kingmaker.UI.MVVM._VM.ServiceWindows.LocalMap; -using Kingmaker.UI.MVVM._VM.ServiceWindows.LocalMap.Markers; -using Kingmaker.UI.MVVM._VM.ServiceWindows.LocalMap.Utils; -using Kingmaker.UnitLogic; -using Kingmaker.UnitLogic.FactLogic; -using Kingmaker.UnitLogic.Interaction; -using Kingmaker.UnitLogic.Parts; -using Kingmaker.Utility; -using Kingmaker.View.MapObjects; -using Kingmaker.Visual.LocalMap; -using ModKit; -using System; -using System.Collections.Generic; -using System.Linq; -using Kingmaker.View; -using TMPro; -using UniRx; -using UnityEngine; -using UnityEngine.EventSystems; -using UnityEngine.UI; -using static Kingmaker.UnitLogic.Interaction.SpawnerInteractionPart; - -namespace ToyBox.BagOfPatches { - internal static class LocalMapPatches { - public static Settings settings = Main.Settings; - public static Player player = Game.Instance.Player; - - public static float Zoom = 1.0f; - public static float Width = 0.0f; - public static Vector3 Position = Vector3.zero; - public static Vector3 FrameRotation = Vector3.zero; - - [HarmonyPatch(typeof(LocalMapVM))] - internal static class LocalMapVMPatch { - public static Vector3 prevLocalPos = new Vector2(); - - [HarmonyPatch(nameof(OnClick), new Type[] { typeof(Vector2), typeof(bool) })] - [HarmonyPrefix] - public static bool OnClick(LocalMapVM __instance, Vector2 localPos, bool state) { - if (false && !settings.toggleZoomableLocalMaps) return true; - var vector3 = LocalMapRenderer.Instance.ViewportToWorldPoint( - new Vector2( - localPos.x / (__instance.DrawResult.Value.ColorRT.width), - localPos.y / (__instance.DrawResult.Value.ColorRT.height) - ) - ); - - if (!LocalMapModel.IsInCurrentArea(vector3)) - vector3 = AreaService.Instance.CurrentAreaPart.Bounds.LocalMapBounds.ClosestPoint(vector3); - if (state) { - Game.Instance.CameraController.Follower.Release(); - Game.Instance.UI.GetCameraRig().ScrollTo(vector3); - } - else { - ClickGroundHandler.MoveSelectedUnitsToPoint(vector3); - } - return false; - } - [HarmonyPatch(nameof(LocalMapVM.SetMarkers))] - [HarmonyPrefix] - private static bool SetMarkers(LocalMapVM __instance) { - Mod.Debug($"LocalMapVM.SetMarkers"); - LocalMapModel.Markers.RemoveWhere(m => m.GetMarkerType() == LocalMapMarkType.Invalid); - foreach (var marker in LocalMapModel.Markers) - if (LocalMapModel.IsInCurrentArea(marker.GetPosition())) - __instance.MarkersVm.Add(new LocalMapCommonMarkerVM(marker)); - IEnumerable<UnitEntityData> first = Game.Instance.Player.PartyAndPets; - if (Game.Instance.Player.CapitalPartyMode) - first = first.Concat(Game.Instance.Player.RemoteCompanions.Where(u => !u.IsCustomCompanion())); - foreach (var unit in first) - if (unit.View != null && unit.View.enabled && !unit.IsHiddenBecauseDead && - LocalMapModel.IsInCurrentArea(unit.Position)) { - __instance.MarkersVm.Add(new LocalMapCharacterMarkerVM(unit)); - __instance.MarkersVm.Add(new LocalMapDestinationMarkerVM(unit)); - } - - foreach (var units in Game.Instance.Player.MainCharacter.Value.Memory.UnitsList) { - Mod.Debug($"Checking {units.Unit.CharacterName}"); - if (!units.Unit.IsPlayerFaction - && (units.Unit.IsVisibleForPlayer || units.Unit.InterestingnessCoefficent() > 0) - && !units.Unit.Descriptor.State.IsDead - && !units.Unit.State.Features.IsUntargetable.Value - && LocalMapModel.IsInCurrentArea(units.Unit.Position) - ) { - __instance.MarkersVm.Add(new LocalMapUnitMarkerVM(units)); - } - } - return false; - } - } - // Modifies Local Map View to zoom the map for easier reading - // InGamePCView(Clone)/InGameStaticPartPCView/StaticCanvas/ServiceWindowsPCView/Background/Windows/LocalMapPCView/ContentGroup/MapBlock - [HarmonyPatch(typeof(LocalMapBaseView))] - internal static class LocalMapBaseViewPatch { - private static float prevZoom = 0; - private static float width = 0; - // These are the transform paths for the different kinds of marks on the LocalMapView - private static readonly string[] MarksPaths = { "MarksPC", "MarksUnits", "MarksLoot", "MarksPoi", "MarksVIT" }; - - [HarmonyPatch(nameof(SetDrawResult), new Type[] { typeof(LocalMapRenderer.DrawResult) })] - [HarmonyPrefix] - public static bool SetDrawResult(LocalMapBaseView __instance, LocalMapRenderer.DrawResult dr) { - // This is the original owlcat code. This gets called when zoom changes to adjust the size of the FrameBlock, a widget that looks like a picture frame and depicts the users view into the world based on zoom and camera rotation - width = dr.ColorRT.width; - LocalMapPatches.Width = width; - var height = dr.ColorRT.height; - __instance.m_Image.rectTransform.sizeDelta = new Vector2(width, height); - var a = (dr.ScreenRect.z - dr.ScreenRect.x) * width; - var b = (dr.ScreenRect.w - dr.ScreenRect.y) * height; - var sizeDelta = new Vector2(Mathf.Max(a, b), Mathf.Min(a, b)); - __instance.m_FrameBlock.sizeDelta = sizeDelta; - __instance.m_FrameBlock.localPosition = new Vector2(dr.ScreenRect.x * width, dr.ScreenRect.y * height); - __instance.SetupBPRVisible(); - - // Now ToyBox wants to rock your world. We grab various transforms - var contentGroup = UIHelpers.LocalMapScreen.Find("ContentGroup"); // Overall map view including the compass - var mapBlock = UIHelpers.LocalMapScreen.Find("ContentGroup/MapBlock"); // Container for map, border, markers and the frame - var map = mapBlock.Find("Map"); // Just the map - var frameBlock = mapBlock.Find("Map/FrameBlock"); // Camera viewport projected onto the map - var frame = frameBlock.Find("Frame"); // intermediate container for the FrameBlock - if (contentGroup is RectTransform contentGroupRect - && mapBlock is RectTransform mapBlockRect - && map is RectTransform mapRect - && frameBlock is RectTransform frameBlockRect - && frame is RectTransform frameRect - ) { - if (settings.toggleZoomableLocalMaps) { - // Calculate a zoom factor based on info used previously to scale the Frame Block. In our new world we will center the Frame Block in middle of the ContentGroup and then pan the map behind it. TODO - make it rotate so that it matches exactly the view of the camera (Frame Block will always point up) - var worldWidth = (dr.WorldRect.z - dr.WorldRect.x); - var fovMultiplier = settings.AdjustedFovMultiplier; - var worldZoom = worldWidth /(fovMultiplier * 47f); - Zoom = width / (worldZoom * sizeDelta.x); - Mod.Log($"zoom: {Zoom} worldZoom: {worldZoom} sizeDelta: {sizeDelta} - screenRect:{dr.ScreenRect.z - dr.ScreenRect.x} worldRec:{dr.WorldRect.z - dr.WorldRect.x} proj:\n{dr.InverseViewProj}"); - // save off the frame rotation so we can fix the camera movement when the map is open - FrameRotation = frame.localEulerAngles; - var zoom = Zoom; - // LocalMapVM_Patch.offset = frameBlockRect.localPosition * LocalMapVM_Patch.zoom; - - // Now adjust the position of the mapBlock to keep the FrameBlock in a fixed position - Position = mapBlock.localPosition; - Position.x = -3 - frameBlockRect.localPosition.x * zoom - width / (2 * worldZoom); // ??? this is a weird correction (make better?) - Position.y = -22 - frameBlockRect.localPosition.y * zoom - width / (4 * worldZoom); // ??? this is a weird correction (make better?) - mapBlock.localPosition = Position; - // Now apply the zoom to MapBlock - var zoomVector = new Vector3(zoom, zoom, 1.0f); - mapBlock.localScale = zoomVector; - //frameBlockRect.localScale = new Vector3(1f,1f, 1.0f); - - // Fix the pivot to ensure we stay centered when we zoom - mapBlockRect.pivot = new Vector2(0.0f, 0.0f); - //Mod.Log($"zoom: {zoomVector}"); - //frameBlockRect.pivot = new Vector2(0.5f, 0.5f); - //mapRect.pivot = new Vector2(0.5f, 0.5f); - if (Math.Abs(zoom - prevZoom) > .001) { - // Now we don't need all the POI and other map markers to get really big when you zoom so we will shrink them to a reasonable size - var shrinkVector = new Vector3(1.5f / zoom, 1.5f / zoom, 1); - - foreach (var markPath in MarksPaths) { - var marks = map.Find(markPath).gameObject.getChildren(); - foreach (var mark in marks) { - if (!mark.transform.localScale.Equals(new Vector3(0, 0, 0))) { - mark.transform.localScale = shrinkVector; - } - var lootMarkerView = mark.GetComponent<LocalMapLootMarkerPCView>(); - lootMarkerView?.Hide(); - } - } - - // Finally we tweak the thickness of the Frame Block so it doesn't grow really small and thick. - if (frame.FindChild("Top")?.gameObject?.transform is Transform tt) tt.localScale = new Vector3(1, 1.5f / zoom, 1); - if (frame.FindChild("Bottom")?.gameObject?.transform is Transform tb) tb.localScale = new Vector3(1, 1.5f / zoom, 1); - if (frame.FindChild("Bottom/BottomEye")?.gameObject?.transform is Transform tbe) tbe.localScale = new Vector3(1.5f / zoom, 1f, 1); - if (frame.FindChild("Left")?.gameObject?.transform is Transform tl) tl.localScale = new Vector3(1.5f / zoom, 1, 1); - if (frame.FindChild("Right")?.gameObject?.transform is Transform tr) tr.localScale = new Vector3(1.5f / zoom, 1, 1); - } - } - else { - // TODO: Factor the above into a helper function and take zoom as a paremeter so we can call it to reset everything back to normal when we turn off Enhanced Map - var zoomVector = new Vector3(1, 1, 1.0f); - Zoom = 1.0f; - //LocalMapVM_Patch.offset = new Vector2(0.0f, 0.0f); - mapBlock.localScale = new Vector3(1, 1, 1); - } - } - return false; - } - #if true - enum MouseEventState { - Off, - Short, - Long, - } - - private static MouseEventState eventState = MouseEventState.Off; - private static long eventStartTime = 0; - private static Vector3 eventStartPosition = Vector3.zero; - - [HarmonyPatch(typeof(LocalMapPCView))] - public static class LocalMapPCViewPatch { - [HarmonyPatch(nameof(OnPointerClick))] - [HarmonyPrefix] - public static bool OnPointerClick(LocalMapPCView __instance, PointerEventData eventData) { - if (!settings.toggleZoomableLocalMaps) return true; - if (eventData.button == PointerEventData.InputButton.Middle) - return false; - Vector2 adjustedPoint = eventStartPosition; - if (eventState != MouseEventState.Short) { - Vector2 localPoint; - RectTransformUtility.ScreenPointToLocalPointInRectangle( - __instance.m_Image.rectTransform, - eventData.position, - Game.Instance.UI.UICamera, - out localPoint - ); - adjustedPoint = localPoint - + Vector2.Scale( - __instance.m_Image.rectTransform.sizeDelta, - __instance.m_Image.rectTransform.pivot - ); - } - Mod.Debug($"Click - localPoint: {adjustedPoint} vs pos: {Position } zoom:{Zoom}"); - __instance.ViewModel.OnClick(adjustedPoint, eventData.button == PointerEventData.InputButton.Left); - return false; - } - - [HarmonyPatch(nameof(Update))] - [HarmonyPrefix] - private static bool Update(LocalMapPCView __instance) { - if (!settings.toggleZoomableLocalMaps) return true; - if (!__instance.m_MouseDown) { - eventState = MouseEventState.Off; - return false; - } - Vector2 localPoint; - RectTransformUtility.ScreenPointToLocalPointInRectangle( - __instance.m_Image.rectTransform, - Input.mousePosition, - Game.Instance.UI.UICamera, out localPoint - ); - var adjustedPoint = localPoint + Vector2.Scale( - __instance.m_Image.rectTransform.sizeDelta * Zoom, - __instance.m_Image.rectTransform.pivot - ); - var time = DateTimeOffset.Now.ToUnixTimeMilliseconds(); - bool forwardEvent = false; - if (eventState == MouseEventState.Off) { - eventStartTime = time; - eventState = MouseEventState.Short; - // save off start position for Click can use it if we get Click during the Short phase - eventStartPosition = adjustedPoint; - forwardEvent = true; // send first event - } - if (eventState == MouseEventState.Short && time - eventStartTime > 100) { - eventState = MouseEventState.Long; - } - else if (eventState == MouseEventState.Long) { - forwardEvent = true; // once we are in long click then start sending updates - eventStartPosition = adjustedPoint; // update start position so that Click will have the right value after a long drag - } - if (forwardEvent) { - Mod.Debug($"Update - localPoint: {localPoint} -> {adjustedPoint} vs pos: {Position} zoom:{Zoom}"); - __instance.ViewModel.OnClick(adjustedPoint, true); - } - return false; - } - } - #endif - - // The compass (kind for drawing circles) is pretty but we want to hide it when the map zooms - [HarmonyPatch(nameof(SetupBPRVisible))] - [HarmonyPrefix] - public static bool SetupBPRVisible(LocalMapBaseView __instance) { - if (!settings.toggleZoomableLocalMaps) return true; - #if true - bool show = Zoom <= 1.0f && __instance.m_Image.rectTransform.rect.width < 975.0; - __instance.m_BPRImage.CrossFadeColor(show ? Color.white : Color.clear, 0.5f, true, true); - #else - __instance.m_BPRImage?.gameObject?.SetActive( - LocalMapVM_Patch.zoom <= 1.0f && - __instance.m_Image.rectTransform.rect.width < 975.0 - ); - #endif - return false; - } - } - - [HarmonyPatch(typeof(LocalMapMarkerPCView), nameof(LocalMapMarkerPCView.BindViewImplementation))] - private static class LocalMapMarkerPCView_BindViewImplementation_Patch { - [HarmonyPostfix] - public static void Postfix(LocalMapMarkerPCView __instance) { - if (__instance == null) - return; - //Mod.Debug($"LocalMapMarkerPCView.BindViewImplementation - {__instance.ViewModel.MarkerType} - {__instance.ViewModel.GetType().Name}"); - if (__instance.ViewModel.MarkerType == LocalMapMarkType.Loot) - __instance.AddDisposable(__instance.ViewModel.IsVisible.Subscribe(value => { (__instance as LocalMapLootMarkerPCView)?.Hide(); })); - if (settings.toggleShowInterestingNPCsOnLocalMap) { - if (__instance.ViewModel is LocalMapCommonMarkerVM markerVM - && markerVM.m_Marker is AddLocalMapMarker.Runtime marker) { - var unit = marker.Owner; - UpdateMarker(__instance, unit); - } - else if (__instance.ViewModel is LocalMapUnitMarkerVM unitMarkerVM) { - UpdateMarker(__instance, unitMarkerVM.m_Unit); - } - else if (__instance.ViewModel is LocalMapCharacterMarkerVM characterMarkerVM) { - UpdateMarker(__instance, characterMarkerVM.m_Unit); - } - } - } - - private static void UpdateMarker(LocalMapMarkerPCView markerView, UnitEntityData unit) { - var count = unit.InterestingnessCoefficent(); - //Mod.Debug($"{unit.CharacterName.orange()} -> unit interestingness: {count}"); - //var attentionMark = markerView.transform.Find("ToyBoxAttentionMark")?.gameObject; - //Mod.Debug($"attentionMark: {attentionMark}"); - var markImage = markerView.transform.FindChild("Mark").GetComponent<Image>(); - if (count >= 1) { - //Mod.Debug($"adding Mark to {unit.CharacterName.orange()}"); - var mark = markerView.transform; - markImage.color = new Color(1, 1f, 0); - } - else { -// attentionMark?.SetActive(false); - markImage.color = new Color(1, 1, 1); - } - } - } - - [HarmonyPatch(typeof(UnitOvertipView))] - private static class UnitOvertipViewPatch { - [HarmonyPatch(nameof(UnitOvertipView.BindViewImplementation))] - [HarmonyPostfix] - public static void BindViewImplementation(UnitOvertipView __instance) { - if (!settings.toggleShowInterestingNPCsOnLocalMap) return; - if (__instance.ViewModel is EntityOvertipVM entityOvertipVM) { - var interestingness = entityOvertipVM.Unit.InterestingnessCoefficent(); - var charName = __instance.transform.Find("OverUnit/NonCombatOvertip/CharacterName").GetComponent<TextMeshProUGUI>(); - if (interestingness >= 1) - charName.color = new Color(0.6898f, 0.3771f, 0.0184f); - else - charName.color = new Color(0.1098f, 0.098f, 0.0784f); - } - } - [HarmonyPatch(nameof(UnitOvertipView.UpdateInternal))] - [HarmonyPostfix] - public static void UpdateInternal(UnitOvertipView __instance, Vector3 canvasPosition) { - if (!settings.toggleShowInterestingNPCsOnLocalMap || __instance is null) return; - if (__instance.ViewModel is EntityOvertipVM entityOvertipVM) { - var interestingness = entityOvertipVM.Unit.InterestingnessCoefficent(); - var charName = __instance.transform.Find("OverUnit/NonCombatOvertip/CharacterName").GetComponent<TextMeshProUGUI>(); - if (interestingness >= 1) - charName.color = new Color(0.6898f, 0.3771f, 0.0184f); - else - charName.color = new Color(0.1098f, 0.098f, 0.0784f); - } - } - } - #if DEBUG - [HarmonyPatch(typeof(LocalMapMarkerPart))] - public static class LocalMapMarkerPartPatch { - [HarmonyPatch(nameof(LocalMapMarkerPart.OnTurnOn))] - [HarmonyPostfix] - public static void Markers() { - Mod.Error($"Marker Add"); - } - } - [HarmonyPatch(typeof(AddLocalMapMarker.Runtime))] - public static class AddLocalMapMarkerPatch { - [HarmonyPatch(nameof(AddLocalMapMarker.Runtime.OnTurnOn))] - [HarmonyPostfix] - public static void Markers() { - Mod.Error($"Marker Add"); - } - } - [HarmonyPatch(typeof(UnitEntityView))] - public static class UnitEntityViewPatch { - [HarmonyPatch(nameof(UnitEntityView.UpdateLootLocalMapMark))] - [HarmonyPostfix] - public static void Markers() { - //Mod.Error($"Marker Add"); - } - } - #endif - #if false - [HarmonyPatch(typeof(EntityVisibilityForPlayerController))] - public static class AddLocalMapMarkerRuntimePatch { - [HarmonyPatch(nameof(EntityVisibilityForPlayerController.IsVisible), new Type[] { typeof(UnitEntityData)})] - [HarmonyPrefix] - private static bool IsUnitVisible(UnitEntityData unit, ref bool __result) { - if (unit.GetUnitIterestingnessCoefficent() > 0) { - //__result = true; - return false; - } - return true; - } - [HarmonyPatch(nameof(EntityVisibilityForPlayerController.IsVisible), new Type[] { typeof(MapObjectEntityData)})] - [HarmonyPrefix] - private static bool IsMapObjectVisible(MapObjectEntityData mapObject, ref bool __result) { - //__result = true; - return false; - //Mod.Debug($"MapObject: {mapObject}"); - return true; - } - } - #endif - - -#if false - #if false - var mapBlockRotation = mapBlock.localEulerAngles; - var frameRotation = frame.localEulerAngles; - var frameBlockRotation = frameBlock.localEulerAngles; - var mbPos = mapBlock.position; - var center = new Vector3(width, width, 0); - mapBlock.ResetRotation(); - mapBlock.localPosition += center; - mapBlock.Rotate(mapBlock.forward, -frameRotation.z); - mapBlock.localPosition -= center; - //mapBlock.Rotate(new Vector3()); -// mapBlock.localEulerAngles = mapBlockRotation; - Mod.Log($"frameRotation: {mapBlockRotation}"); - #endif - - // some experimental code to implement map rotation - ) { - if (settings.toggleZoomableLocalMaps) { - //Mod.Log($"width: {width} sizeDelta.x: {sizeDelta.x} a:{a} dr.ScreenRect: {dr.ScreenRect}"); - LocalMapVM_Patch.zoom = width / (2 * sizeDelta.x); - LocalMapVM_Patch.offset = frameBlockRect.localPosition * LocalMapVM_Patch.zoom; - var pos = mapBlock.localPosition; - pos.x = -3 - frameBlockRect.localPosition.x * LocalMapVM_Patch.zoom - width / 4; - pos.y = -22 - frameBlockRect.localPosition.y * LocalMapVM_Patch.zoom - width / 4; - mapBlock.localPosition = pos; - var zoomVector = new Vector3(LocalMapVM_Patch.zoom, LocalMapVM_Patch.zoom, 1.0f); - mapBlock.localScale = zoomVector; - mapBlockRect.pivot = new Vector2(0.0f, 0.0f); -#if false - frameBlockRect.pivot = new Vector2(0.5f, 0.5f); - mapRect.pivot = new Vector2(0.5f, 0.5f); -#endif - //var frameQuat = frameRect.localRotation; - //frameRect.localRotation= new Quaternion(0, 0, 0, 0); - } - - } -#endif -#if false - var uiRoot = UIHelpers.UIRoot; - if (attentionMark == null) { - var attentionPrototype = uiRoot.Find("TransitionViewPCView/Alushinyrra/LegendBlock/Nexus_Legend/Attention"); - attentionMark = GameObject.Instantiate(attentionPrototype).gameObject; - attentionMark.name = "ToyBoxAttentionMark"; - attentionMark.AddTo(mark); - } - attentionMark.SetActive(true); -#endif -#if false - [HarmonyPatch(typeof(LocalMapRenderer))] - private static class LocalMapRenderer_Patch { - public static float prevZoom = 1.0f; - - [HarmonyPatch("Draw", new Type[] { typeof(Vector2) })] - [HarmonyPrefix] - public static bool Draw(LocalMapRenderer __instance, Vector2 size, ref DrawResult __result) { - var zoom = LocalMapVM_Patch.zoom; - //Mod.Log($"LocalMapRenderer_Patch - zoom: {zoom} size: {size}"); - if (!Application.isPlaying || __instance.m_CurrentArea == null) { - __result = new DrawResult { - Canceled = true - }; - return false; - } - if (Math.Abs(zoom - prevZoom) > 0.001) - __instance.IsAreaDirty = true; - if (!__instance.IsDirty()) { - __result = __instance.GenerateDrawResult(); - return false; - } - size *= 1; - prevZoom = zoom; - var localMapBounds = __instance.m_CurrentArea.Bounds.LocalMapBounds; - var num = Vector3.Distance(localMapBounds.min, localMapBounds.max); - __instance.m_Camera.transform.rotation = Quaternion.Euler(__instance.ViewAngle, - (bool)(SimpleBlueprint)__instance.m_CurrentArea ? __instance.m_CurrentArea.LocalMapRotation - 180f : -180f, - 0.0f); - var position = localMapBounds.center - __instance.m_Camera.transform.forward * num; - __instance.m_Camera.transform.position = position; - - if (__instance.lightInst == null) - __instance.lightInst = __instance.InstantiatePPLight(); - var vector2 = __instance.m_Camera.aspect > (double)(size.x / size.y) - ? new Vector2(size.x, size.x / __instance.m_Camera.aspect) - : new Vector2(size.y * __instance.m_Camera.aspect, size.y); - if (__instance.m_ColorRT == null || __instance.m_ColorRT.width != (int)vector2.x || __instance.m_ColorRT.height != (int)vector2.y) { - if (__instance.m_ColorRT != null) { - __instance.m_ColorRT.Release(); - UnityEngine.Object.Destroy(__instance.m_ColorRT); - } - - if (__instance.m_DepthRT != null) { - __instance.m_DepthRT.Release(); - UnityEngine.Object.Destroy(__instance.m_DepthRT); - } - - __instance.m_ColorRT = new RenderTexture((int)vector2.x, (int)vector2.y, 0, RenderTextureFormat.ARGB32); - __instance.m_ColorRT.name = "LocalMapColorTex"; - var active = RenderTexture.active; - RenderTexture.active = __instance.m_ColorRT; - GL.Clear(true, true, new Color(0.0f, 0.0f, 0.0f, 0.0f)); - RenderTexture.active = active; - __instance.m_DepthRT = new RenderTexture((int)vector2.x, (int)vector2.y, 0, RenderTextureFormat.RFloat); - __instance.m_DepthRT.name = "LocalMapDepthTex"; - __instance.m_DepthRT.filterMode = FilterMode.Point; - RenderTexture.active = __instance.m_DepthRT; - GL.Clear(true, true, new Color(0.0f, 0.0f, 0.0f, 0.0f)); - RenderTexture.active = active; - } - __instance.m_Camera.targetTexture = __instance.m_ColorRT; - __instance.m_AdditionalCameraData.DepthTexture = __instance.m_DepthRT; - __instance.m_Camera.cullingMatrix = CalculateProjMatrix(__instance.m_CurrentArea) * CalculateViewMatrix(__instance.m_CurrentArea); - using (ForcedCullingService.Instance.UncullEverything()) { - __instance.m_Camera.Render(); - } - - var drawResult = __instance.GenerateDrawResult(); - __instance.m_CachedArea = __instance.m_CurrentArea; - __instance.m_CachedAngle = __instance.ViewAngle; - if (!(null != __instance.lightInst)) { - __result = drawResult; - return false; - } - UnityEngine.Object.Destroy(__instance.lightInst); - __result = drawResult; - return false; - } - - [HarmonyPatch(nameof(UpdateCamera), new Type[] { })] - [HarmonyPrefix] - public static bool UpdateCamera(LocalMapRenderer __instance) { - __instance.m_Camera.enabled = false; - __instance.m_Camera.orthographic = true; - __instance.m_Camera.backgroundColor = new Color(0.0f, 0.0f, 0.0f, 1f); - __instance.m_Camera.clearFlags = CameraClearFlags.Color; - __instance.m_Camera.targetTexture = __instance.m_ColorRT; - __instance.m_Camera.cullingMask = 102784273; - __instance.m_AdditionalCameraData.RenderPostProcessing = false; - __instance.m_AdditionalCameraData.AllowDistortion = true; - __instance.m_AdditionalCameraData.AllowDecals = false; - __instance.m_AdditionalCameraData.AllowIndirectRendering = false; - __instance.m_AdditionalCameraData.AllowFog = false; - __instance.m_AdditionalCameraData.AllowLighting = true; - __instance.m_AdditionalCameraData.Dithering = false; - __instance.m_AdditionalCameraData.DepthTexture = __instance.m_DepthRT; - __instance.m_AdditionalCameraData.AllowVfxPreparation = false; - __instance.m_AdditionalCameraData.DisableAllFeatures(); - if (Game.GetCamera() == null) - return false; - if (Application.isPlaying) - __instance.m_CurrentArea = AreaService.Instance.CurrentAreaPart; - if (__instance.m_CurrentArea == null) - return false; - var localMapBounds = __instance.m_CurrentArea.Bounds.LocalMapBounds; - var num = Vector3.Distance(localMapBounds.min, localMapBounds.max); - __instance.m_Camera.transform.rotation = Quaternion.Euler(__instance.ViewAngle, - (bool)(SimpleBlueprint)__instance.m_CurrentArea ? __instance.m_CurrentArea.LocalMapRotation - 180f : -180f, - 0.0f); - __instance.m_Camera.transform.position = localMapBounds.center - __instance.m_Camera.transform.forward * num; - __instance.CreateAABBPoints(ref localMapBounds, __instance.m_SceneAABBPointsLightSpace); - var worldToLocalMatrix = __instance.m_Camera.transform.worldToLocalMatrix; - var rhs1 = Vector3.one * float.MaxValue; - var rhs2 = Vector3.one * float.MinValue; - for (var index = 0; index < __instance.m_SceneAABBPointsLightSpace.Length; ++index) { - __instance.m_SceneAABBPointsLightSpace[index] = - worldToLocalMatrix.MultiplyPoint(__instance.m_SceneAABBPointsLightSpace[index]); - rhs1 = Vector3.Min(__instance.m_SceneAABBPointsLightSpace[index], rhs1); - rhs2 = Vector3.Max(__instance.m_SceneAABBPointsLightSpace[index], rhs2); - } - - var bounds = new Bounds(); - bounds.min = rhs1; - bounds.max = rhs2; - __instance.m_Camera.transform.position = __instance.m_Camera.transform.TransformPoint(bounds.center - Vector3.forward * num); - __instance.m_Camera.farClipPlane = num * 2f; - __instance.m_Camera.orthographicSize = bounds.extents.y; - __instance.m_Camera.aspect = bounds.size.x / bounds.size.y; - return false; - } - } -#endif - - - } -} \ No newline at end of file diff --git a/ToyBox/classes/MonkeyPatchin/HighlightObjectToggle.cs b/ToyBox/classes/MonkeyPatchin/HighlightObjectToggle.cs deleted file mode 100644 index 91612baa2..000000000 --- a/ToyBox/classes/MonkeyPatchin/HighlightObjectToggle.cs +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright (c) 2018 fireundubh <fireundubh@gmail.com> -// This code is licensed under MIT license (see LICENSE for details) -// Copyright < 2021 > Narria (github user Cabarius) - License: MIT -using System; -using HarmonyLib; -using ModKit; -using Kingmaker; -using Kingmaker.Controllers.MapObjects; -using Kingmaker.EntitySystem.Entities; -using Kingmaker.PubSubSystem; -using Kingmaker.View; -using Kingmaker.View.MapObjects; -using Kingmaker.View.MapObjects.SriptZones; -using Kingmaker.View.MapObjects.Traps; -using Owlcat.Runtime.Visual.RenderPipeline.RendererFeatures.Highlighting; -using UnityEngine; - -namespace ToyBox.classes.MonkeyPatchin { - public class HighlightObjectToggle { - [HarmonyPatch(typeof(InteractionHighlightController), nameof(InteractionHighlightController.HighlightOn))] - private class InteractionHighlightController_Activate_Patch { - //static TimeSpan m_LastTickTime; - private static AccessTools.FieldRef<InteractionHighlightController, bool> m_IsHighlightingRef; - - //static FastGetter<InteractionHighlightController, bool> IsHighlightingGet; - //static FastSetter<InteractionHighlightController, bool> IsHighlightingSet; - private static bool Prepare() { - // Accessors.CreateFieldRef<KingdomEvent, int>("m_StartedOn"); - m_IsHighlightingRef = Accessors.CreateFieldRef<InteractionHighlightController, bool>("m_IsHighlighting"); - //IsHighlightingGet = Accessors.CreateFieldRe<InteractionHighlightController, bool>("IsHighlighting"); - //IsHighlightingSet = Accessors.CreateSetter<InteractionHighlightController, bool>("IsHighlighting"); - return true; - } - - private static bool Prefix(InteractionHighlightController __instance, bool ___m_Inactive) { - try { - if (!Main.Enabled) return true; - if (!Main.Settings.highlightObjectsToggle) return true; - //var isInCutScene = Game.Instance.State.Cutscenes.ToList().Count() > 0; - //if (isInCutScene) return true; - if (m_IsHighlightingRef(__instance) & !___m_Inactive) { - m_IsHighlightingRef(__instance) = false; - foreach (var mapObjectEntityData in Game.Instance.State.MapObjects) { - mapObjectEntityData.View.UpdateHighlight(); - } - foreach (var unitEntityData in Game.Instance.State.Units) { - unitEntityData.View.UpdateHighlight(false); - } - EventBus.RaiseEvent<IInteractionHighlightUIHandler>(delegate (IInteractionHighlightUIHandler h) { - h.HandleHighlightChange(false); - }); - return false; - } - } - catch (Exception ex) { - Mod.Error(ex); - } - return true; - } - } - [HarmonyPatch(typeof(InteractionHighlightController), nameof(InteractionHighlightController.HighlightOff))] - private class InteractionHighlightController_Deactivate_Patch { - private static bool Prefix(InteractionHighlightController __instance) { - try { - if (!Main.Enabled) return true; - if (Main.Settings.highlightObjectsToggle) { - return false; - } - } - catch (Exception ex) { - Mod.Error(ex); - } - return true; - } - } - } - - [HarmonyPatch(typeof(MapObjectView), nameof(MapObjectView.UpdateHighlight))] - internal class HighlightHiddenObjects { - private static readonly string ObjName = "ToyBox.HiddenHighlighter"; - private static readonly string DecalName = "ToyBox.DecalHiddenHighlighter"; - private static Color HighlightColor0 = new(1.0f, 0.0f, 1.0f, 0.8f); - private static Color HighlightColor1 = new(0.0f, 0.0f, 1.0f, 1.0f); - - private static void Postfix(MapObjectView __instance) { - var data = __instance.Data; - if(data == null) return; - - var pcc = __instance.GetComponent<PerceptionCheckComponent>(); - - if (!data.IsPerceptionCheckPassed && pcc != null) { - var is_highlighting = Game.Instance?.InteractionHighlightController?.IsHighlighting; - var should_highlight = (is_highlighting ?? false) && Main.Settings.highlightHiddenObjects; - - if (!Main.Settings.highlightHiddenObjectsInFog && data.IsInFogOfWar) { - should_highlight = false; - } - - if (should_highlight) { - HighlightOn(__instance); - } else { - HighlightOff(__instance); - } - } else { - HighlightDestroy(__instance); - } - } - - private static void HighlightCreate(MapObjectView view) { - if (view.transform.Find(ObjName)) return; - var obj = new GameObject(ObjName); - Main.Objects.Add(obj); - obj.transform.parent = view.transform; - var highlighter = obj.AddComponent<Highlighter>(); - - foreach (var polygon in view.transform.GetComponentsInChildren<ScriptZonePolygon>()) { - var mesh = polygon.DecalMeshObject; - if (mesh == null) continue; - - var renderer = mesh.GetComponent<MeshRenderer>(); - if (renderer == null) continue; - - var decal = UnityEngine.Object.Instantiate(renderer.gameObject, renderer.transform.parent); - decal.name = DecalName; - Main.Objects.Add(decal); - - var decal_renderer = decal.GetComponent<MeshRenderer>(); - decal_renderer.enabled = false; - decal_renderer.forceRenderingOff = true; - } - - foreach (var renderer in view.transform.GetComponentsInChildren<Renderer>()) { - highlighter.AddExtraRenderer(renderer); - } - } - - private static void HighlightDestroy(MapObjectView view) { - var decal = view?.transform?.Find(DecalName)?.gameObject; - if (decal != null) { - UnityEngine.Object.Destroy(decal); - } - - var obj = view?.transform?.Find(ObjName)?.gameObject; - if (obj != null) { - UnityEngine.Object.Destroy(obj); - } - } - - private static void HighlightOn(MapObjectView view) { - var obj = view.transform.Find(ObjName)?.gameObject; - if (obj == null) { - HighlightCreate(view); - obj = view.transform.Find(ObjName)?.gameObject; - } - - var highlighter = obj.GetComponent<Highlighter>(); - if (highlighter != null) { - highlighter.ConstantOnImmediate(HighlightColor0); - highlighter.FlashingOn(HighlightColor0, HighlightColor1, 1.0f); - - var decal = view?.transform?.Find(DecalName)?.gameObject; - if (decal == null) return; - var renderer = decal.GetComponent<MeshRenderer>(); - if (renderer == null) return; - renderer.enabled = true; - renderer.forceRenderingOff = true; - } - } - - private static void HighlightOff(MapObjectView view) { - var obj = view.transform.Find(ObjName)?.gameObject; - if (obj == null) return; - - var highlighter = obj.GetComponent<Highlighter>(); - if (highlighter != null) { - highlighter.ConstantOff(0.0f); - highlighter.FlashingOff(); - - var decal = view?.transform?.Find(DecalName)?.gameObject; - if (decal == null) return; - var renderer = decal.GetComponent<MeshRenderer>(); - if (renderer == null) return; - renderer.enabled = false; - renderer.forceRenderingOff = true; - } - } - } -} diff --git a/ToyBox/classes/MonkeyPatchin/MoveThroughOthers.cs b/ToyBox/classes/MonkeyPatchin/MoveThroughOthers.cs deleted file mode 100644 index 39ab9c52e..000000000 --- a/ToyBox/classes/MonkeyPatchin/MoveThroughOthers.cs +++ /dev/null @@ -1,42 +0,0 @@ -// This code is licensed under MIT license (see LICENSE for details) -// Based on work in https://github.com/hsinyuhcan/KingmakerTurnBasedMod by Hsinyu Chan -// Copyright < 2021 > Narria (github user Cabarius) - License: MIT -using HarmonyLib; -using Kingmaker.View; - -namespace ToyBox { - // TODO - do we really need this? Someone requested it but I don't observe any movement restrictions - public static class MoveThroughOthers { - // moving through ... feature - public static Settings settings => Main.Settings; - [HarmonyPatch(typeof(UnitMovementAgent), nameof(UnitMovementAgent.AvoidanceDisabled), MethodType.Getter)] - private static class UnitMovementAgent_AvoidanceDisabled_Patch { - [HarmonyPostfix] - private static void Postfix(UnitMovementAgent __instance, ref bool __result) { - if (UnitEntityDataUtils.CheckUnitEntityData(__instance.Unit?.EntityData, settings.allowMovementThroughSelection)) { - __result = true; - } - } - } - - // forbid moving through non selected entity type - [HarmonyPatch(typeof(UnitMovementAgent), nameof(UnitMovementAgent.IsSoftObstacle), typeof(UnitMovementAgent))] - private static class UnitMovementAgent_IsSoftObstacle_Patch { - [HarmonyPrefix] - private static bool Prefix(UnitMovementAgent __instance, ref bool __result) { - if (!UnitEntityDataUtils.CheckUnitEntityData(__instance.Unit?.EntityData, settings.allowMovementThroughSelection)) { - __result = !__instance.CombatMode; // this duplicates the logic in the original logic for IsSoftObstacle. If we are not in combat mode and it is not in our allow movement through category then it is a soft obstacle - return false; - } - return true; - } - } - - // modify collision radius - [HarmonyPatch(typeof(UnitMovementAgentBase), nameof(UnitMovementAgent.Corpulence), MethodType.Getter)] - private static class UnitMovementAgentBaset_get_Corpulence_Patch { - [HarmonyPostfix] - private static void Postfix(ref float __result) => __result *= settings.collisionRadiusMultiplier; - } - } -} \ No newline at end of file diff --git a/ToyBox/classes/MonkeyPatchin/Multiclass/Archetypes.cs b/ToyBox/classes/MonkeyPatchin/Multiclass/Archetypes.cs deleted file mode 100644 index c3e215090..000000000 --- a/ToyBox/classes/MonkeyPatchin/Multiclass/Archetypes.cs +++ /dev/null @@ -1,324 +0,0 @@ -// Based on MultipleArchetypes with kind permission by Vek17 (https://github.com/Vek17/WrathMods-MultipleArchetypes/) -using HarmonyLib; -using Kingmaker; -using Kingmaker.Blueprints.Classes; -using Kingmaker.Blueprints.Root; -using Kingmaker.UI.Common; -using Kingmaker.UI.MVVM._VM.CharGen.Phases.Class; -using Kingmaker.UI.MVVM._VM.CharGen.Phases.Class.Mechanic; -using Kingmaker.UI.MVVM._VM.Other.NestedSelectionGroup; -using Kingmaker.UI.MVVM._VM.ServiceWindows.CharacterInfo.Sections.LevelClassScores.Classes; -using Kingmaker.UI.MVVM._VM.ServiceWindows.CharacterInfo.Sections.Progression.ChupaChupses; -using Kingmaker.UI.MVVM._VM.ServiceWindows.CharacterInfo.Sections.Progression.Main; -using Kingmaker.UI.MVVM._VM.ServiceWindows.CharacterInfo.Sections.Progression.Spellbook; -using Kingmaker.UI.MVVM._VM.Tooltip.Templates; -using Kingmaker.UnitLogic; -using Kingmaker.UnitLogic.Class.LevelUp; -using Kingmaker.UnitLogic.Class.LevelUp.Actions; -using Kingmaker.Utility; -using ModKit; -using Owlcat.Runtime.UI.Tooltips; -using System; -using System.Collections.Generic; -using System.Linq; -using UniRx; - -namespace ToyBox.Multiclass { - public static class Archetypes { - public static Settings settings = Main.Settings; - public static Player player = Game.Instance.Player; - - public static string ArchetypesName(this ClassData cd) => string.Join("/", cd.Archetypes.Select(a => a.Name)); - - [HarmonyPatch(typeof(ClassData), nameof(ClassData.CalcSkillPoints))] - private static class ClassData_CalcSkillPoints_Patch { - static bool Prefix(ClassData __instance, ref int __result) { - if (!settings.toggleMultiArchetype) return true; - if (!__instance.Archetypes.Any()) { return true; } - __result = __instance.CharacterClass.SkillPoints + __instance.Archetypes.Select((BlueprintArchetype a) => a.AddSkillPoints).Max(); - return false; - } - } - [HarmonyPatch(typeof(TooltipTemplateClass), MethodType.Constructor, new Type[] { typeof(ClassData) })] - private static class TooltipTemplateClass_Constructor_Patch { - static void Postfix(ref TooltipTemplateClass __instance, ClassData classData) { - var name = classData.ArchetypesName(); - var description = string.Join("\n\n", classData.Archetypes.Select(a => a.Description)); - if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(name)) { - var NameSetter = AccessTools.Field(typeof(TooltipTemplateClass), "m_Name"); - var DescSetter = AccessTools.Field(typeof(TooltipTemplateClass), "m_Desc"); - NameSetter.SetValue(__instance, name); - DescSetter.SetValue(__instance, description); - } - } - } - [HarmonyPatch(typeof(CharInfoClassEntryVM), MethodType.Constructor, new Type[] { typeof(ClassData) })] - private static class CharInfoClassEntryVM_Constructor_Patch { - private static void Postfix(CharInfoClassEntryVM __instance, ClassData classData) { - var Name = classData.ArchetypesName(); - if (!string.IsNullOrEmpty(Name)) { - var ClassName = AccessTools.Field(typeof(CharInfoClassEntryVM), "<ClassName>k__BackingField"); - ClassName.SetValue(__instance, Name); - } - } - } - [HarmonyPatch(typeof(ClassProgressionVM), MethodType.Constructor, new Type[] { typeof(UnitDescriptor), typeof(ClassData) })] - private static class ClassProgressionVM_Constructor_Patch { - private static void Postfix(ClassProgressionVM __instance, UnitDescriptor unit, ClassData unitClass) { - if (!settings.toggleMultiArchetype) return; - var Name = string.Join("/", unitClass.Archetypes.Select(a => a.Name)); - if (!string.IsNullOrEmpty(Name)) { - __instance.Name = string.Join(" ", unitClass.CharacterClass.Name, $"({Name})"); - } - var castingArchetype = unitClass.Archetypes.Where(a => a.ReplaceSpellbook != null).FirstOrDefault(); - if (castingArchetype != null) { - __instance.AddDisposable(__instance.SpellbookProgressionVM = new SpellbookProgressionVM( - __instance.m_UnitClass, - castingArchetype, - __instance.m_Unit, - __instance.m_LevelProgressionVM)); - } - } - } - [HarmonyPatch(typeof(CharGenClassSelectorItemVM), MethodType.Constructor, new Type[] { - typeof(BlueprintCharacterClass), - typeof(BlueprintArchetype), - typeof(LevelUpController), - typeof(INestedListSource), - typeof(ReactiveProperty<CharGenClassSelectorItemVM>), - typeof(ReactiveProperty<TooltipBaseTemplate>), - typeof(bool), - typeof(bool), - typeof(bool), - })] - private static class CharGenClassSelectorItemVM_Constructor_Patch { - private static void Postfix(CharGenClassSelectorItemVM __instance, - BlueprintCharacterClass cls, - BlueprintArchetype archetype, - LevelUpController levelUpController, - INestedListSource source, - ReactiveProperty<CharGenClassSelectorItemVM> selectedArchetype, - ReactiveProperty<TooltipBaseTemplate> tooltipTemplate, - bool prerequisitesDone, - bool canSelect, - bool allowSwitchOff) { - if (__instance.HasClassLevel) { - var classData = levelUpController.Unit.Progression.GetClassData(cls); - if (!classData.Archetypes.Any()) return; - var name = classData.ArchetypesName(); - var DisplayName = AccessTools.Field(typeof(CharGenClassSelectorItemVM), "DisplayName"); - DisplayName.SetValue(__instance, $"{cls.Name} — {name}"); - } - } - } - [HarmonyPatch(typeof(CharGenClassSelectorItemVM), nameof(CharGenClassSelectorItemVM.GetArchetypesList), new Type[] { typeof(BlueprintCharacterClass) })] - private static class CharGenClassSelectorItemVM_GetArchetypesList_Patch { - public static List<NestedSelectionGroupEntityVM> archetypes; - private static void Postfix(CharGenClassSelectorItemVM __instance, List<NestedSelectionGroupEntityVM> __result) { - archetypes = __result; - } - } - [HarmonyPatch(typeof(NestedSelectionGroupEntityVM), nameof(NestedSelectionGroupEntityVM.SetSelected), new Type[] { typeof(bool) })] - private static class NestedSelectionGroupEntityVM_SetSelected_Patch { - private static bool Prefix(NestedSelectionGroupEntityVM __instance, ref bool state) { - if (!settings.toggleMultiArchetype) return true; - var VM = __instance as CharGenClassSelectorItemVM; - var controller = Game.Instance?.LevelUpController; - if (VM == null || controller == null) return true; - var progression = controller.Preview?.Progression; - var classData = controller?.Preview?.Progression?.GetClassData(controller.State.SelectedClass); - if (classData == null) { return true; } - if (controller.Unit.Progression.GetClassLevel(VM.Class) >= 1) { return true; } - var hasArchetype = classData.Archetypes.HasItem(VM.Archetype); - state |= hasArchetype; - if (!state) { - if (progression != null && VM.Archetype != null) { - VM.SetAvailableState(progression.CanAddArchetype(classData.CharacterClass, VM.Archetype) && VM.PrerequisitesDone); - } - } - return true; - } - } - [HarmonyPatch(typeof(NestedSelectionGroupEntityVM), nameof(NestedSelectionGroupEntityVM.SetSelectedFromView), new Type[] { typeof(bool) })] - private static class NestedSelectionGroupEntityVM_SetSelectedFromView_Patch { - private static bool Prefix(NestedSelectionGroupEntityVM __instance, bool state) { - if (!settings.toggleMultiArchetype) return true; - if (!state && !__instance.AllowSwitchOff) { - return false; - } - __instance.IsSelected.Value = state; - __instance.RefreshView.Execute(); - if (state) { - __instance.DoSelectMe(); - } - //__instance.SetSelected(state); - return false; - } - } - [HarmonyPatch(typeof(CharGenClassPhaseVM), nameof(CharGenClassPhaseVM.OnSelectorArchetypeChanged), new Type[] { typeof(BlueprintArchetype) })] - private static class CharGenClassPhaseVM_OnSelectorArchetypeChanged_Patch { - private static bool Prefix(CharGenClassPhaseVM __instance, BlueprintArchetype archetype) { - if (!settings.toggleMultiArchetype) return true; - __instance.UpdateTooltipTemplate(false); - if (__instance.LevelUpController.State.SelectedClass == null) { - return false; - } - var Progression = __instance.LevelUpController.Preview.Progression; - var classData = __instance.LevelUpController.Preview - .Progression.GetClassData(__instance.LevelUpController.State.SelectedClass); - - if (classData != null && (archetype == null || !Progression.CanAddArchetype(classData.CharacterClass, archetype))) { - classData.Archetypes.ForEach(delegate (BlueprintArchetype a) { - __instance.LevelUpController.RemoveArchetype(a); - }); - } - if (archetype != null) { - __instance.LevelUpController.RemoveArchetype(archetype); - if (!__instance.LevelUpController.AddArchetype(archetype)) { - MainThreadDispatcher.Post(delegate(object _) { - __instance.SelectedArchetypeVM.Value = null; - }, null); - } - } - __instance.UpdateClassInformation(); - return false; - } - } - [HarmonyPatch(typeof(ClassProgressionVM), MethodType.Constructor, new Type[] { - typeof(UnitDescriptor), - typeof(BlueprintCharacterClass), - typeof(BlueprintArchetype), - typeof(bool), - typeof(int), - })] - private static class ClassProgressionVM2_Constructor_Patch { - private static void Postfix(ClassProgressionVM __instance, BlueprintCharacterClass classBlueprint, int level, bool buildDifference) { - if (!settings.toggleMultiArchetype) return; - var data = __instance.ProgressionVms.Select(vm => vm.ProgressionData).OfType<AdvancedProgressionData>().First(); - __instance.ProgressionVms.Clear(); - var addArchetypes = Game.Instance.LevelUpController.LevelUpActions.OfType<AddArchetype>(); - foreach (var add in addArchetypes) { - data.AddArchetype(add.Archetype); - } - var newVM = new ProgressionVM(data, __instance.m_Unit, new int?(level), buildDifference); - __instance.ProgressionVms.Add(newVM); - __instance.AddProgressions(__instance.m_Unit.Progression.GetClassProgressions(__instance.m_UnitClass).EmptyIfNull<ProgressionData>()); - __instance.AddProgressionSources(newVM.ProgressionSourceFeatures); - var archetypeString = string.Join("/", addArchetypes.Select(a => a.Archetype.Name)); - if (!string.IsNullOrEmpty(archetypeString)) { - __instance.Name = string.Join(" ", classBlueprint.Name, $"({archetypeString})"); - } - var castingArchetype = addArchetypes.Select(a => a.Archetype).Where(a => a.ReplaceSpellbook != null).FirstOrDefault(); - if (castingArchetype != null) { - __instance.AddDisposable(__instance.SpellbookProgressionVM = new SpellbookProgressionVM( - __instance.m_UnitClass, - castingArchetype, - __instance.m_Unit, - __instance.m_LevelProgressionVM)); - } - } - } - [HarmonyPatch(typeof(ProgressionVM), nameof(ProgressionVM.SetClassArchetypeDifType), new Type[] { typeof(ProgressionVM.FeatureEntry) })] - private static class ProgressionVM_SetClassArchetypeDifType_Patch { - private static void Postfix(ProgressionVM __instance, ref ProgressionVM.FeatureEntry featureEntry) { - if (!settings.toggleMultiArchetype) return; - var featureEntry2 = featureEntry; - foreach (var archetype in __instance.ProgressionData.Archetypes) { - foreach (var removeFeature in archetype.RemoveFeatures.Where(entry => entry.Level == featureEntry2.Level)) { - if (removeFeature.Features.Any(f => f == featureEntry2.Feature)) { - featureEntry.DifType = ClassArchetypeDifType.Removed; - }; - } - foreach (var addFeature in archetype.AddFeatures.Where(entry => entry.Level == featureEntry2.Level)) { - if (addFeature.Features.Any(f => f == featureEntry2.Feature)) { - featureEntry.DifType = ClassArchetypeDifType.Added; - }; - } - } - } - } - //Details Tab in CharGen - [HarmonyPatch(typeof(CharGenClassCasterStatsVM), MethodType.Constructor, new Type[] { typeof(BlueprintCharacterClass), typeof(BlueprintArchetype) })] - private static class CharGenClassCasterStatsVM_MultiArchetype_Patch { - private static void Postfix(CharGenClassCasterStatsVM __instance, BlueprintCharacterClass valueClass, BlueprintArchetype valueArchetype) { - if (!settings.toggleMultiArchetype) return; - var controller = Game.Instance?.LevelUpController; - if (controller == null) return; - var classData = controller.Preview?.Progression?.GetClassData(valueClass); - if (classData == null) return; - __instance.CanCast.Value = classData.Spellbook != null; - if (classData.Spellbook == null) return; - var changeTypeArchetype = classData.Archetypes?.Where(a => a.ChangeCasterType).FirstOrDefault(); - __instance.MaxSpellsLevel.Value = classData.Spellbook.MaxSpellLevel.ToString(); - __instance.CasterAbilityScore.Value = LocalizedTexts.Instance.Stats.GetText(classData.Spellbook.CastingAttribute); - __instance.CasterMindType.Value = ((changeTypeArchetype == null) ? - (UIUtilityUnit.GetCasterMindType(valueClass) ?? "—") : (UIUtilityUnit.GetCasterMindType(changeTypeArchetype) ?? "—")); - __instance.SpellbookUseType.Value = UIUtilityUnit.GetCasterSpellbookUseType(classData.Spellbook); - } - } - //Details Tab in CharGen - [HarmonyPatch(typeof(CharGenClassMartialStatsVM), MethodType.Constructor, new Type[] { typeof(BlueprintCharacterClass), typeof(BlueprintArchetype), typeof(UnitDescriptor) })] - private static class CharGenClassMartialStatsVM_MultiArchetype_Patch { - private static void Postfix(CharGenClassMartialStatsVM __instance, BlueprintCharacterClass valueClass, BlueprintArchetype valueArchetype, UnitDescriptor unit) { - if (!settings.toggleMultiArchetype) return; - Mod.Debug("CharGenClassMartialStatsVM::Triggered"); - var controller = Game.Instance?.LevelUpController; - if (controller == null) return; - var classData = controller.Preview?.Progression?.GetClassData(valueClass); - if (classData == null) return; - Mod.Debug("Made it to override"); - __instance.Fortitude.Value = UIUtilityUnit.GetStatProgressionGrade(classData.FortitudeSave); - __instance.Will.Value = UIUtilityUnit.GetStatProgressionGrade(classData.WillSave); - __instance.Reflex.Value = UIUtilityUnit.GetStatProgressionGrade(classData.ReflexSave); - __instance.BAB.Value = UIUtilityUnit.GetStatProgressionGrade(classData.BaseAttackBonus); - } - } - //Details Tab in CharGen - [HarmonyPatch(typeof(CharGenClassSkillsVM), MethodType.Constructor, new Type[] { typeof(BlueprintCharacterClass), typeof(BlueprintArchetype) })] - private static class CharGenClassSkillsVM_MultiArchetype_Patch { - private static void Postfix(CharGenClassSkillsVM __instance, BlueprintCharacterClass valueClass, BlueprintArchetype valueArchetype) { - if (!settings.toggleMultiArchetype) return; - Mod.Debug("CharGenClassSkillsVM::Triggered"); - var controller = Game.Instance?.LevelUpController; - if (controller == null) return; - var classData = controller.Preview?.Progression?.GetClassData(valueClass); - if (classData == null) return; - Mod.Debug("Made it to override"); - var classSkills = classData.Archetypes.SelectMany(a => a.ClassSkills) - .Concat(classData.CharacterClass.ClassSkills).Distinct().ToArray(); - __instance.ClassSkills.Clear(); - foreach (var skill in classSkills) { - var charGenClassStatEntryVM = new CharGenClassStatEntryVM(skill); - __instance.AddDisposable(charGenClassStatEntryVM); - __instance.ClassSkills.Add(charGenClassStatEntryVM); - } - return; - } - } - //Details Tab in CharGen - [HarmonyPatch(typeof(CharGenClassPhaseVM), nameof(CharGenClassPhaseVM.UpdateClassInformation))] - private static class CharGenClassPhaseVM_UpdateClassInformation_MultiArchetype_Patch { - private static void Postfix(CharGenClassPhaseVM __instance) { - if (!settings.toggleMultiArchetype) return; - Mod.Debug("CharGenClassPhaseVM::UpdateClassInformation"); - var controller = Game.Instance?.LevelUpController; - if (controller == null) return; - var classData = controller.Preview?.Progression?.GetClassData(__instance.SelectedClassVM.Value?.Class); - if (classData == null) return; - Mod.Debug("Made it to override"); - var classSkills = classData.Archetypes.SelectMany(a => a.ClassSkills) - .Concat(classData.CharacterClass.ClassSkills).Distinct().ToArray(); - //this.SelectedClassVM.Value.Class.Name + " — " + this.SelectedArchetypeVM.Value.Archetype.Name; - var archetypeName = classData.ArchetypesName(); - if (!string.IsNullOrEmpty(archetypeName)) { - __instance.ClassDisplayName.Value = string.Join(" ", classData.CharacterClass.Name, $"({archetypeName})"); - } - var archetypeDrescription = string.Join("\n\n", classData.Archetypes.Select(a => a.Description)); - if (!string.IsNullOrEmpty(archetypeName)) { - __instance.ClassDescription.Value = archetypeDrescription; - } - return; - } - } - } -} \ No newline at end of file diff --git a/ToyBox/classes/MonkeyPatchin/Multiclass/LevelUP+Multiclass.cs b/ToyBox/classes/MonkeyPatchin/Multiclass/LevelUP+Multiclass.cs deleted file mode 100644 index cd5b7d619..000000000 --- a/ToyBox/classes/MonkeyPatchin/Multiclass/LevelUP+Multiclass.cs +++ /dev/null @@ -1,203 +0,0 @@ -// borrowed shamelessly and enhanced from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/26, which is under the MIT License - -using HarmonyLib; -using JetBrains.Annotations; -using Kingmaker; -using Kingmaker.Blueprints; -using Kingmaker.Blueprints.Classes; -using Kingmaker.Blueprints.Classes.Spells; -using Kingmaker.Designers.Mechanics.Facts; -//using Kingmaker.Controllers.GlobalMap; -using Kingmaker.EntitySystem.Entities; -//using Kingmaker.UI._ConsoleUI.Models; -//using Kingmaker.UI.RestCamp; -using Kingmaker.UnitLogic; -using Kingmaker.UnitLogic.Class.LevelUp; -using Kingmaker.UnitLogic.Class.LevelUp.Actions; -using Kingmaker.Utility; -using ModKit; -using System; -using System.Linq; -using ToyBox.classes.Infrastructure; -//using Kingmaker.UI._ConsoleUI.GroupChanger; -//using Kingmaker.UI.LevelUp.Phase; - -namespace ToyBox.Multiclass { - internal static class LevelUp { - public static Settings settings = Main.Settings; - public static Player player = Game.Instance.Player; - - [HarmonyPatch(typeof(LevelUpController), MethodType.Constructor, new Type[] { - typeof(UnitEntityData), - typeof(bool), - typeof(LevelUpState.CharBuildMode), - typeof(bool)})] - private static class LevelUpController_ctor_Patch { - [HarmonyPrefix, HarmonyPriority(Priority.First)] - private static bool Prefix(LevelUpController __instance) { - if (Main.Enabled) { - MultipleClasses.levelUpController = __instance; - } - return true; - } - } -#if true - /* public static void UpdateProgression( - [NotNull] LevelUpState state, - [NotNull] UnitDescriptor unit, - [NotNull] BlueprintProgression progression) - */ - [HarmonyPatch(typeof(LevelUpHelper), nameof(LevelUpHelper.UpdateProgression))] - [HarmonyPatch(new Type[] { typeof(LevelUpState), typeof(UnitDescriptor), typeof(BlueprintProgression) })] - private static class LevelUpHelper_UpdateProgression_Patch { - public static bool Prefix([NotNull] LevelUpState state, [NotNull] UnitDescriptor unit, [NotNull] BlueprintProgression progression) { - if (!settings.toggleMulticlass) return true; - var progressionData = unit.Progression.SureProgressionData(progression); - var level = progressionData.Level; - var nextLevel = progressionData.Blueprint.CalcLevel(unit); - progressionData.Level = nextLevel; - // TODO - this is from the mod but we need to figure out if max level 20 still makes sense with mythic levels - // int maxLevel = 20 // unit.Progression.CharacterLevel; - // if (nextLevel > maxLevel) - // nextLevel = maxLevel; - //Mod.Debug($"LevelUpHelper_UpdateProgression_Patch - {unit.CharacterName.orange()} - class: {state.SelectedClass} level: {level} nextLvl: {nextLevel}"); - if (level >= nextLevel || progression.ExclusiveProgression != null && state.SelectedClass != progression.ExclusiveProgression) - return false; - if (!progression.GiveFeaturesForPreviousLevels) - level = nextLevel - 1; - for (var lvl = level + 1; lvl <= nextLevel; ++lvl) { - // if (!AllowProceed(progression)) break; - var levelEntry = progressionData.GetLevelEntry(lvl); - //Mod.Debug($" LevelUpHelper_UpdateProgression_Patch - {string.Join(", ", levelEntry.Features.Select(f => f.name.yellow()))}"); - - LevelUpHelper.AddFeaturesFromProgression(state, unit, levelEntry.Features, (FeatureSource)progression, lvl); - } - return false; - } - private static bool AllowProceed(BlueprintProgression progression) { - // SpellSpecializationProgression << shouldn't be applied more than once per character level - if (!Main.Enabled || Main.multiclassMod == null) return false; - return Main.multiclassMod.UpdatedProgressions.Add(progression); - // TODO - what is the following and does it still matter? - // || progression.AssetGuid != "fe9220cdc16e5f444a84d85d5fa8e3d5"; - } - } -#endif - - // Do not proceed the spell selection if the caster level was not changed - [HarmonyPatch(typeof(ApplySpellbook), nameof(ApplySpellbook.Apply))] - [HarmonyPatch(new Type[] { typeof(LevelUpState), typeof(UnitDescriptor) })] - private static class ApplySpellbook_Apply_Patch { - public static bool Prefix(LevelUpState state, UnitDescriptor unit) { - if (!settings.toggleMulticlass) return true; - - if (state.SelectedClass == null) { - return false; - } - var component1 = state.SelectedClass.GetComponent<SkipLevelsForSpellProgression>(); - if (component1 != null && component1.Levels.Contains(state.NextClassLevel)) { - return false; - } - - var classData = unit.Progression.GetClassData(state.SelectedClass); - if (classData?.Spellbook == null) { - return false; - } - - var spellbook1 = unit.DemandSpellbook(classData.Spellbook); - if (state.SelectedClass.Spellbook && state.SelectedClass.Spellbook != classData.Spellbook) { - var spellbook2 = unit.Spellbooks.FirstOrDefault(s => s.Blueprint == state.SelectedClass.Spellbook); - if (spellbook2 != null) { - foreach (var allKnownSpell in spellbook2.GetAllKnownSpells()) { - spellbook1.AddKnown(allKnownSpell.SpellLevel, allKnownSpell.Blueprint); - } - - unit.DeleteSpellbook(state.SelectedClass.Spellbook); - } - } - var casterLevelAfter = CasterHelpers.GetRealCasterLevel(unit, spellbook1.Blueprint); // Calculates based on progression which includes class selected in level up screen - spellbook1.AddLevelFromClass(classData.CharacterClass); // This only adds one class at a time and will only ever increase by 1 or 2 - var isNewSpellCaster = (spellbook1.IsStandaloneMythic && casterLevelAfter == 2) || casterLevelAfter == 1; - var spellSelectionData = state.DemandSpellSelection(spellbook1.Blueprint, spellbook1.Blueprint.SpellList); - if (spellbook1.Blueprint.SpellsKnown != null) { - for (var index = 0; index <= 10; ++index) { - var spellsKnown = spellbook1.Blueprint.SpellsKnown; - var expectedCount = spellsKnown.GetCount(casterLevelAfter, index); - var actual = CasterHelpers.GetActualSpellsLearnedForClass(unit, spellbook1, index); - int learnabl = spellbook1.GetSpellsLearnableOfLevel(index).Count(); - int spelladd = Math.Max(0, Math.Min(expectedCount - actual, learnabl)); -#if DEBUG - Mod.Trace($"Spellbook {spellbook1.Blueprint.Name}: Granting {spelladd} spells of spell level:{index} based on expected={expectedCount}, actual={actual}, learnable={learnabl}"); -#endif - spellSelectionData.SetLevelSpells(index, spelladd); - } - } - var maxSpellLevel = spellbook1.MaxSpellLevel; - if (spellbook1.Blueprint.SpellsPerLevel > 0) { - if (isNewSpellCaster) { - spellSelectionData.SetExtraSpells(0, maxSpellLevel); - spellSelectionData.ExtraByStat = true; - spellSelectionData.UpdateMaxLevelSpells(unit); - } - else { - spellSelectionData.SetExtraSpells(spellbook1.Blueprint.SpellsPerLevel, maxSpellLevel); - } - } - foreach (var component2 in spellbook1.Blueprint.GetComponents<AddCustomSpells>()) { - ApplySpellbook.TryApplyCustomSpells(spellbook1, component2, state, unit); - } - - return false; - } - } - - // Fixed a vanilla PFK bug that caused dragon bloodline to be displayed in Magus' feats tree - [HarmonyPatch(typeof(ApplyClassMechanics), nameof(ApplyClassMechanics.ApplyProgressions))] - private static class ApplyClassMechanics_ApplyProgressions_Patch { - public static bool Prefix(LevelUpState state, UnitDescriptor unit) { - if (!settings.toggleMulticlass) return true; - //Mod.Debug($"ApplyClassMechanics_ApplyProgressions_Patch - {unit.CharacterName.orange()} - class: {state.SelectedClass} nextLevel: {state.NextClassLevel}"); - BlueprintCharacterClass blueprintCharacterClass = null; - if (unit.TryGetPartyMemberForLevelUpVersion(out var ch) - && ch.TryGetClass(state.SelectedClass, out var cl) - && state.NextClassLevel >= cl.Level - ) - blueprintCharacterClass = state.SelectedClass; - //var blueprintCharacterClass = state.NextClassLevel <= 1 ? state.SelectedClass : (BlueprintCharacterClass)null; - var features = unit.Progression.Features.Enumerable; - var progressions = features.Select(f => f.Blueprint).OfType<BlueprintProgression>().ToList(); // this ToList is important because it prevents mutation exceptions - foreach (var blueprintProgression in progressions) { - var p = blueprintProgression; - //Mod.Debug($" prog: {p.name.yellow()}"); - if (blueprintCharacterClass != null - // && p.Classes.Contains<BlueprintCharacterClass>(blueprintCharacterClass)) - && p.IsChildProgressionOf(unit, blueprintCharacterClass) // Mod Line replacing above - ) { - var feature = unit.Progression.Features.Enumerable.FirstItem(f => f.Blueprint == p); - feature?.SetSource(blueprintCharacterClass, state.NextClassLevel); - Mod.Debug($" feature: {feature.Name.cyan()} - levlel: {state.NextClassLevel}"); - //feature?.SetSource(blueprintCharacterClass, 1); - } - LevelUpHelper.UpdateProgression(state, unit, p); - } - return true; - } - } - [HarmonyPatch(typeof(UnitHelper), nameof(UnitHelper.CopyInternal))] - private static class UnitProgressionData_CopyFrom_Patch { - private static void Postfix(UnitEntityData unit, UnitEntityData __result) { - if (!settings.toggleMulticlass) return; - // When upgrading, this method will be used to copy a UnitEntityData, which involves copying UnitProgressionData - // By default, the CharacterLevel of the copied UnitProgressionData is equal to the sum of all non-mythical class levels - // If the character level is not equal to this default value, there will be problems (for example, when it is lower than the default value, you may not be able to upgrade until you reach level 20, because the sum of non-mythical class levels has exceeded 20 in advance) - // Fix this. - - var UnitProgressionData_CharacterLevel = AccessTools.Property(typeof(UnitProgressionData), nameof(UnitProgressionData.CharacterLevel)); - Mod.Trace($"UnitProgressionData_CopyFrom_Patch - {unit.CharacterName.orange()} - {UnitProgressionData_CharacterLevel}"); - - UnitProgressionData_CharacterLevel.SetValue(__result.Descriptor.Progression, unit.Descriptor.Progression.CharacterLevel); - __result.Descriptor.Progression.MythicLevel = unit.Descriptor.Progression.MythicLevel; - } - } - } -} diff --git a/ToyBox/classes/MonkeyPatchin/Multiclass/MulticlassMod.cs b/ToyBox/classes/MonkeyPatchin/Multiclass/MulticlassMod.cs deleted file mode 100644 index 0490d5cb6..000000000 --- a/ToyBox/classes/MonkeyPatchin/Multiclass/MulticlassMod.cs +++ /dev/null @@ -1,212 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Kingmaker; -using Kingmaker.Blueprints; -using Kingmaker.Blueprints.Classes; -using Kingmaker.Blueprints.Items.Components; -using Kingmaker.EntitySystem.Entities; -//using Kingmaker.UI.LevelUp.Phase; -using Kingmaker.UnitLogic; -using Kingmaker.UnitLogic.Class.LevelUp; -using Kingmaker.Utility; -using UnityEngine.SceneManagement; -using static ModKit.Utility.ReflectionCache; -using UnityModManager = UnityModManagerNet.UnityModManager; -using ModKit; -using Kingmaker.UnitLogic.ActivatableAbilities.Restrictions; -using Kingmaker.Kingdom.Settlements; -using Kingmaker.Blueprints.Classes.Prerequisites; -using Kingmaker.Kingdom.Settlements.BuildingComponents; - -namespace ToyBox.Multiclass { - public enum ProgressionPolicy { - PrimaryClass = 0, - Average = 1, - Largest = 2, - Sum = 3, - }; - public class MulticlassMod { - //public HashSet<Type> AbilityCasterCheckerTypes { get; } = - // new HashSet<Type>(Assembly.GetAssembly(typeof(IAbilityCasterChecker)).GetTypes() - // .Where(type => typeof(IAbilityCasterChecker).IsAssignableFrom(type) && !type.IsInterface)); - - public HashSet<Type> ActivatableAbilityRestrictionTypes { get; } = - new HashSet<Type>(Assembly.GetAssembly(typeof(ActivatableAbilityRestriction)).GetTypes() - .Where(type => type.IsSubclassOf(typeof(ActivatableAbilityRestriction)))); - - public HashSet<Type> BuildingRestrictionTypes { get; } = - new HashSet<Type>(Assembly.GetAssembly(typeof(BuildingRestriction)).GetTypes() - .Where(type => type.IsSubclassOf(typeof(BuildingRestriction))).Except(new[] { typeof(DLCRestriction) })); - - public HashSet<Type> EquipmentRestrictionTypes { get; } = - new HashSet<Type>(Assembly.GetAssembly(typeof(EquipmentRestriction)).GetTypes() - .Where(type => type.IsSubclassOf(typeof(EquipmentRestriction)))); - - public HashSet<Type> PrerequisiteTypes { get; } = - new HashSet<Type>(Assembly.GetAssembly(typeof(Prerequisite)).GetTypes() - .Where(type => type.IsSubclassOf(typeof(Prerequisite)))); - - public HashSet<BlueprintCharacterClass> AppliedMulticlassSet { get; internal set; } - = new HashSet<BlueprintCharacterClass>(); - - public HashSet<BlueprintProgression> UpdatedProgressions { get; internal set; } - = new HashSet<BlueprintProgression>(); - - public LevelUpController LevelUpController { get; internal set; } - - public HashSet<MethodBase> LockedPatchedMethods { get; internal set; } = new HashSet<MethodBase>(); - - public bool IsLevelLocked { get; internal set; } - - public Scene ActiveScene => SceneManager.GetActiveScene(); - - public BlueprintCharacterClass[] CharacterClasses => Game.Instance.BlueprintRoot.Progression.CharacterClasses.ToArray(); - - public BlueprintCharacterClass[] MythicClasses => Game.Instance.BlueprintRoot.Progression.CharacterMythics.ToArray(); - - public BlueprintCharacterClass[] AllClasses => this.CharacterClasses.Concat(this.MythicClasses).ToArray(); - - public BlueprintScriptableObject LibraryObject => typeof(ResourcesLibrary).GetFieldValue<BlueprintScriptableObject>("s_LoadedResources");//("s_LibraryObject"); - - public Player Player => Game.Instance.Player; - //public static bool IsCharGen() => !Main.IsInGame && Game.Instance.LevelUpController.State.Mode == CharBuildMode.CharGen; - } - - public static class MulticlassUtils { - public static Settings settings = Main.Settings; - public static Player player = Game.Instance.Player; - - public static bool IsCharGen(this LevelUpState state) { - var companionNames = Game.Instance?.Player?.AllCharacters.Where(c => !c.IsMainCharacter).Select(c => c.CharacterName).ToList(); - var isCompanion = companionNames?.Contains(state.Unit.CharacterName) ?? false; - if (isCompanion) return false; - return state.Mode == LevelUpState.CharBuildMode.CharGen || state.Unit.CharacterName == "Player Character" || state.IsFirstCharacterLevel; - } - - public static bool IsLevelUp(this LevelUpState state) => state.Mode == LevelUpState.CharBuildMode.LevelUp; - - public static bool IsPreGen(this LevelUpState state) => state.IsPregen; - public static bool IsClassGestalt(this UnitEntityData ch, BlueprintCharacterClass cl) { - if (ch.HashKey() == null) return false; - if (Main.Settings.perSave == null) return false; - var excludeSet = Main.Settings.perSave.excludeClassesFromCharLevelSets.GetValueOrDefault(ch.HashKey(), new HashSet<string>()); - return excludeSet.Contains(cl.AssetGuid.ToString()); - } - - public static void SetClassIsGestalt(this UnitEntityData ch, BlueprintCharacterClass cl, bool isGestalt) { - if (ch.HashKey() == null) return; - var classID = cl.AssetGuid.ToString(); - var excludeSet = Main.Settings.perSave.excludeClassesFromCharLevelSets.GetValueOrDefault(ch.HashKey(), new HashSet<string>()); - if (isGestalt) excludeSet.Add(classID); - else excludeSet.Remove(classID); - Mod.Trace($"Set - key: {classID} -> {isGestalt} excludeSet: ({string.Join(" ", excludeSet.ToArray())})"); - Main.Settings.perSave.excludeClassesFromCharLevelSets[ch.HashKey()] = excludeSet; - Settings.SavePerSaveSettings(); - } - - public static bool IsClassGestalt(this UnitDescriptor ch, BlueprintCharacterClass cl) { - if (ch.HashKey() == null) return false; - var excludeSet = Main.Settings.perSave.excludeClassesFromCharLevelSets.GetValueOrDefault(ch.HashKey(), new HashSet<string>()); - var result = excludeSet.Contains(cl.AssetGuid.ToString()); - return result; - } - - public static void SetClassIsGestalt(this UnitDescriptor ch, BlueprintCharacterClass cl, bool exclude) { - if (ch.HashKey() == null) return; - var classID = cl.AssetGuid.ToString(); - var excludeSet = Main.Settings.perSave.excludeClassesFromCharLevelSets.GetValueOrDefault(ch.HashKey(), new HashSet<string>()); - if (exclude) excludeSet.Add(classID); - else excludeSet.Remove(classID); - // Main.Log($"Set - key: {classID} -> {exclude} excludeSet: ({String.Join(" ", excludeSet.ToArray())})"); - Main.Settings.perSave.excludeClassesFromCharLevelSets[ch.HashKey()] = excludeSet; - } - public static bool IsClassGestalt(this UnitProgressionData progression, BlueprintCharacterClass cl) { - var chars = Game.Instance.Player.AllCharacters; - foreach (var ch in chars) { - //Mod.Debug($" {ch.Progression.Owner} vs { progression.Owner}"); - if (ch.Progression.Owner == progression.Owner) { - var result = ch.IsClassGestalt(cl); - //Mod.Debug($" found: {ch.HashKey()} - {cl.Name.orange()} - IsGestalt: {result.ToString().cyan()}"); - return result; - } - } - return false; - } - } - - public static partial class MultipleClasses { - - public static Settings settings = Main.Settings; - public static Player player = Game.Instance.Player; - public static LevelUpController levelUpController { get; internal set; } - - public static bool IsAvailable() => Main.Enabled && - settings.toggleMulticlass && - levelUpController.IsManualPlayerUnit(true); - - public static bool Enabled { - get => settings.toggleMulticlass; - set => settings.toggleMulticlass = value; - } - #region Utilities - - private static void ForEachAppliedMulticlass(LevelUpState state, UnitDescriptor unit, Action action) { - var options = MulticlassOptions.Get(state.IsCharGen() ? null : unit); - var selectedClass = state.SelectedClass; - StateReplacer stateReplacer = new(state); - Mod.Trace($"ForEachAppliedMulticlass\n hash key: {unit.HashKey()}"); - Mod.Trace($" mythic: {state.IsMythicClassSelected}"); - Mod.Trace($" options: {options}"); - foreach (var characterClass in Main.multiclassMod.AllClasses) { - if (characterClass != stateReplacer.SelectedClass - //&& characterClass.IsMythic == selectedClass.IsMythic - && options.Contains(characterClass)) { - var classes = unit?.Progression.Classes; - var match = classes.Find(c => c.CharacterClass == characterClass); - Mod.Trace($" {characterClass.GetDisplayName()} lvl: {match?.Level ?? -1}"); - if (state.IsMythicClassSelected == characterClass.IsMythic) { - stateReplacer.Replace(characterClass, unit.Progression.GetClassLevel(characterClass)); - action(); - } - } - } - stateReplacer.Restore(); - } - public static void UpdateLevelsForGestalt(this UnitProgressionData progression) { - progression.m_CharacterLevel = new int?(0); - progression.m_MythicLevel = new int?(0); - int? nullable; - // this logic is to work around a case where you may mark a character gestalt and then load an earlier save and have them get level 0 which breaks level up. Here we will detect that you have no main class and prevent your first class from being gestalt - var classToEnsureNonGestalt = progression.Classes.FirstOrDefault()?.CharacterClass ?? null; - foreach (var classData in progression.Classes) { - if (!progression.IsClassGestalt(classData.CharacterClass)) { - classToEnsureNonGestalt = classData.CharacterClass; - break; - } - } - foreach (var classData in progression.Classes) { - var cl = classData.CharacterClass; - var shouldSkip = progression.IsClassGestalt(cl) && cl != classToEnsureNonGestalt; - if (!shouldSkip) { - if (classData.CharacterClass.IsMythic) { - nullable = progression.m_MythicLevel; - var level = classData.Level; - progression.m_MythicLevel = nullable.HasValue ? new int?(nullable.GetValueOrDefault() + level) : new int?(); - } - else { - nullable = progression.m_CharacterLevel; - var level = classData.Level; - progression.m_CharacterLevel = nullable.HasValue ? new int?(nullable.GetValueOrDefault() + level) : new int?(); - } - } - } - } - public static void SyncAllGestaltState() { - var chars = Game.Instance?.Player.AllCharacters; - chars?.ForEach(ch => ch.Progression.UpdateLevelsForGestalt()); - } - #endregion - } -} diff --git a/ToyBox/classes/MonkeyPatchin/Multiclass/MultipleClasses.cs b/ToyBox/classes/MonkeyPatchin/Multiclass/MultipleClasses.cs deleted file mode 100644 index 9dd1425bb..000000000 --- a/ToyBox/classes/MonkeyPatchin/Multiclass/MultipleClasses.cs +++ /dev/null @@ -1,477 +0,0 @@ -// borrowed shamelessly and enhanced from Bag of Tricks https://www.nexusmods.com/pathfinderkingmaker/mods/26, which is under the MIT License -using HarmonyLib; -using Kingmaker; -using Kingmaker.Blueprints.Classes; -//using Kingmaker.Controllers.GlobalMap; -using Kingmaker.EntitySystem.Entities; -//using Kingmaker.UI._ConsoleUI.Models; -//using Kingmaker.UI.RestCamp; -using Kingmaker.UnitLogic; -using Kingmaker.UnitLogic.Class.LevelUp; -using Kingmaker.UnitLogic.Class.LevelUp.Actions; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -//using Kingmaker.UI._ConsoleUI.GroupChanger; -using UnityModManager = UnityModManagerNet.UnityModManager; -using ModKit.Utility; -using ModKit; -using Kingmaker.UI.MVVM._PCView.CharGen.Phases.Class; -using TMPro; -using UnityEngine; -using UnityEngine.UI; -using Kingmaker.UI; -using Kingmaker.UI.MVVM._VM.CharGen.Phases.Class; -using Kingmaker.PubSubSystem; -using Kingmaker.UI.MVVM._PCView.ServiceWindows.CharacterInfo.Sections.Progression.Main; -using Kingmaker.UI.MVVM._PCView.CharGen; -using Kingmaker.UI.MVVM._VM.CharGen.Phases.Mythic; -using Kingmaker.UI.MVVM._VM.Party; -using Kingmaker.Blueprints.Root; -using Kingmaker.Blueprints; -using Kingmaker.DLC; -using Kingmaker.UI.MVVM._VM.Other.NestedSelectionGroup; -using Kingmaker.Utility; -using Kingmaker.UI.MVVM._VM.ServiceWindows.MythicInfo; -using Kingmaker.EntitySystem.Entities; - -namespace ToyBox.Multiclass { - public static partial class MultipleClasses { - - #region Class Level & Archetype - [HarmonyPatch(typeof(LevelUpController), nameof(LevelUpController.UpdatePreview))] - private static class LevelUpController_UpdatePreview_Patch { - private static void Prefix(LevelUpController __instance) { - if (IsAvailable()) { - // This is the critical place that gets called once before we go through all the level computations for setting up the level up screen - //Mod.Debug("LevelUpController_UpdatePreview_Patch"); - //Main.multiclassMod.AppliedMulticlassSet.Clear(); - //Main.multiclassMod.UpdatedProgressions.Clear(); - } - } - } - [HarmonyPatch(typeof(SelectClass), nameof(SelectClass.Apply), new Type[] { typeof(LevelUpState), typeof(UnitDescriptor) })] - private static class SelectClass_Apply_Patch { - [HarmonyPostfix] - private static void Postfix(LevelUpState state, UnitDescriptor unit) { - if (!settings.toggleMulticlass) return; - if (!unit.IsPartyOrPet()) return; - //if (Mod.IsCharGen()) Main.Log($"stack: {System.Environment.StackTrace}"); - if (IsAvailable()) { - Main.multiclassMod.AppliedMulticlassSet.Clear(); - Main.multiclassMod.UpdatedProgressions.Clear(); - // Some companions have predefined levels so in some cases we get called iteratively for each level so we will make sure we only apply multiclass on the last level - if (unit.TryGetPartyMemberForLevelUpVersion(out var ch) - && ch.TryGetClass(state.SelectedClass, out var cl) - && unit != ch.Descriptor - && state.NextClassLevel <= cl.Level - ) { - Mod.Debug($"SelectClass_Apply_Patch, unit: {unit.CharacterName.orange()} isCH: {unit == ch.Descriptor}) - skip - lvl:{state.NextClassLevel} vs {cl.Level} ".green()); - return; - } - // get multi-class setting - var isPet = unit.Unit?.IsPet ?? false; - var useDefaultMulticlassOptions = state.IsCharGen() && !isPet; - var options = MulticlassOptions.Get(useDefaultMulticlassOptions ? null : unit); - Mod.Trace($"SelectClass_Apply_Patch, unit: {unit.CharacterName.orange()} useDefaultMulticlassOptions: {useDefaultMulticlassOptions} mode:{state.Mode} isCharGen: {state.IsCharGen()} is1stLvl: {state.IsFirstCharacterLevel} isPet: {isPet} isPlayerChar: {unit.CharacterName == "Player Character"} level: {state.NextClassLevel.ToString().yellow()}".cyan().bold()); - - if (options == null || options.Count == 0) - return; - Mod.Trace($" selected options: {options}".orange()); - //selectedMulticlassSet.ForEach(cl => Main.Log($" {cl}")); - - // applying classes - var selectedClass = state.SelectedClass; - StateReplacer stateReplacer = new(state); - foreach (var characterClass in Main.multiclassMod.AllClasses) { - if (characterClass.IsMythic != selectedClass.IsMythic) continue; - if (Main.multiclassMod.AppliedMulticlassSet.Contains(characterClass)) { - Mod.Warn($"SelectClass_Apply_Patch - duplicate application of multiclass detected: {characterClass.name.yellow()}"); - continue; - } - if (options.Contains(characterClass)) { - Mod.Trace($" checking {characterClass.HashKey()} {characterClass.GetDisplayName()} "); - } - if (characterClass != stateReplacer.SelectedClass - && characterClass.IsMythic == state.IsMythicClassSelected - && options.Contains(characterClass) - ) { - stateReplacer.Replace(null, 0); // TODO - figure out and document what this is doing - Mod.Trace($" {characterClass.Name} matches".cyan()); - //stateReplacer.Replace(characterClass, unit.Progression.GetClassLevel(characterClass)); - - if (new SelectClass(characterClass).Check(state, unit)) { - Mod.Trace($" - {nameof(SelectClass)}.{nameof(SelectClass.Apply)}*({characterClass}, {unit})".cyan()); - - unit.Progression.AddClassLevel_NotCharacterLevel(characterClass); - //state.NextClassLevel = unit.Progression.GetClassLevel(characterClass); - //state.SelectedClass = characterClass; - characterClass.RestrictPrerequisites(unit, state); - //EventBus.RaiseEvent<ILevelUpSelectClassHandler>(h => h.HandleSelectClass(unit, state)); - - Main.multiclassMod.AppliedMulticlassSet.Add(characterClass); - } - } - } - stateReplacer.Restore(); - - Mod.Trace($" checking archetypes for {unit.CharacterName}".cyan()); - // applying archetypes - ForEachAppliedMulticlass(state, unit, () => { - Mod.Trace($" {state.SelectedClass.HashKey()} SelectClass-ForEachApplied".cyan().bold()); - var selectedClass = state.SelectedClass; - var archetypeOptions = options.ArchetypeOptions(selectedClass); - foreach (var archetype in state.SelectedClass.Archetypes) { - // here is where we need to start supporting multiple archetypes of the same class - if (archetypeOptions.Contains(archetype)) { - Mod.Trace($" adding archetype: ${archetype.Name}".cyan().bold()); - AddArchetype addArchetype = new(state.SelectedClass, archetype); - unit.SetClassIsGestalt(addArchetype.CharacterClass, true); - if (addArchetype.Check(state, unit)) { - addArchetype.Apply(state, unit); - } - } - } - }); - } - } - } - - #endregion - - #region Skills & Features - - [HarmonyPatch(typeof(LevelUpController), nameof(LevelUpController.ApplyLevelup))] - private static class LevelUpController_ApplyLevelup_Patch { - private static void Prefix(LevelUpController __instance, UnitEntityData unit) { - if (!settings.toggleMulticlass) return; - - if (unit == __instance.Preview) { - //Mod.Trace($"Unit Preview = {unit.CharacterName}"); - //Mod.Trace("levelup action:"); - foreach (var action in __instance.LevelUpActions) { - Mod.Trace($"{action.GetType()}"); - } - } - } - } - [HarmonyPatch(typeof(ApplyClassMechanics), nameof(ApplyClassMechanics.Apply), new Type[] { typeof(LevelUpState), typeof(UnitDescriptor) })] - private static class ApplyClassMechanics_Apply_Patch { - [HarmonyPostfix] - private static void Postfix(ApplyClassMechanics __instance, LevelUpState state, UnitDescriptor unit) { - if (!settings.toggleMulticlass) return; - if (IsAvailable()) { - //Mod.Debug($"ApplyClassMechanics_Apply_Patch - unit: {unit} {unit.CharacterName} class:{state.SelectedClass}"); - if (state.SelectedClass != null) { - ForEachAppliedMulticlass(state, unit, () => { - unit.SetClassIsGestalt(state.SelectedClass, true); - //Main.Log($" - {nameof(ApplyClassMechanics)}.{nameof(ApplyClassMechanics.Apply)}*({state.SelectedClass}{state.SelectedClass.Archetypes}[{state.NextClassLevel}], {unit}) mythic: {state.IsMythicClassSelected} vs {state.SelectedClass.IsMythic}"); - - __instance.Apply_NoStatsAndHitPoints(state, unit); - }); - } - var allAppliedClasses = Main.multiclassMod.AppliedMulticlassSet.ToList(); - //Mod.Debug($"ApplyClassMechanics_Apply_Patch - {String.Join(" ", allAppliedClasses.Select(cl => cl.Name))}".orange()); - allAppliedClasses.Add(state.SelectedClass); - SavesBAB.ApplySaveBAB(unit, state, allAppliedClasses.ToArray()); - HPDice.ApplyHPDice(unit, state, allAppliedClasses.ToArray()); - } - } - } - - [HarmonyPatch(typeof(SelectFeature), nameof(SelectFeature.Apply), new Type[] { typeof(LevelUpState), typeof(UnitDescriptor) })] - private static class SelectFeature_Apply_Patch { - [HarmonyPrefix, HarmonyPriority(Priority.First)] - private static void Prefix(SelectFeature __instance, LevelUpState state, UnitDescriptor unit, ref StateReplacer __state) { - if (!settings.toggleMulticlass) return; - if (IsAvailable()) { - if (__instance.Item != null) { - var selectionState = - ReflectionCache.GetMethod<SelectFeature, Func<SelectFeature, LevelUpState, FeatureSelectionState>> - ("GetSelectionState")(__instance, state); - if (selectionState != null) { - var sourceClass = selectionState.SourceFeature?.GetSourceClass(unit); - if (sourceClass != null) { - __state = new StateReplacer(state); - __state.Replace(sourceClass, unit.Progression.GetClassLevel(sourceClass)); - } - } - } - } - } - - [HarmonyPostfix, HarmonyPriority(Priority.Last)] - private static void Postfix(SelectFeature __instance, ref StateReplacer __state) { - if (!settings.toggleMulticlass) return; - if (__state != null) { - __state.Restore(); - } - } - } - - [HarmonyPatch(typeof(SelectFeature), nameof(SelectFeature.Check), new Type[] { typeof(LevelUpState), typeof(UnitDescriptor) })] - private static class SelectFeature_Check_Patch { - [HarmonyPrefix, HarmonyPriority(Priority.First)] - private static void Prefix(SelectFeature __instance, LevelUpState state, UnitDescriptor unit, ref StateReplacer __state) { - if (!settings.toggleMulticlass) return; - if (IsAvailable()) { - if (__instance.Item != null) { - var selectionState = - ReflectionCache.GetMethod<SelectFeature, Func<SelectFeature, LevelUpState, FeatureSelectionState>> - ("GetSelectionState")(__instance, state); - if (selectionState != null) { - var sourceClass = selectionState.SourceFeature?.GetSourceClass(unit); - if (sourceClass != null) { - __state = new StateReplacer(state); - __state.Replace(sourceClass, unit.Progression.GetClassLevel(sourceClass)); - } - } - } - } - } - - [HarmonyPostfix, HarmonyPriority(Priority.Last)] - private static void Postfix(SelectFeature __instance, ref StateReplacer __state) { - if (!settings.toggleMulticlass) return; - if (__state != null) { - __state.Restore(); - } - } - } - - #endregion - - #region Spellbook - - [HarmonyPatch(typeof(ApplySpellbook), nameof(ApplySpellbook.Apply), new Type[] { typeof(LevelUpState), typeof(UnitDescriptor) })] - private static class ApplySpellbook_Apply_Patch { - [HarmonyPostfix] - private static void Postfix(MethodBase __originalMethod, ApplySpellbook __instance, LevelUpState state, UnitDescriptor unit) { - if (!settings.toggleMulticlass) return; - - if (IsAvailable() && !Main.multiclassMod.LockedPatchedMethods.Contains(__originalMethod)) { - Main.multiclassMod.LockedPatchedMethods.Add(__originalMethod); - ForEachAppliedMulticlass(state, unit, () => { - __instance.Apply(state, unit); - }); - Main.multiclassMod.LockedPatchedMethods.Remove(__originalMethod); - } - } - } - - #endregion - - #region UnitProgressionData - - [HarmonyPatch(typeof(UnitProgressionData), nameof(UnitProgressionData.SetupLevelsIfNecessary))] - private static class UnitProgressionData_SetupLevelsIfNecessary_Patch { - private static bool Prefix(UnitProgressionData __instance) { - if (__instance.m_CharacterLevel.HasValue && __instance.m_MythicLevel.HasValue) - return false; - __instance.UpdateLevelsForGestalt(); - return false; - } - } - [HarmonyPatch(typeof(UnitProgressionData), nameof(UnitProgressionData.LastMythicClass), MethodType.Getter)] - private static class UnitProgressionData_LastMythicClass_Patch { - static void Postfix(ref BlueprintCharacterClass __result, UnitProgressionData __instance) { - __result = __instance.m_ClassesOrder.LastItem<BlueprintCharacterClass>((Func<BlueprintCharacterClass, bool>)(cl => cl.IsMythic && !__instance.Owner.IsClassGestalt(cl))); - //Mod.Trace($"LastMythic = {__result}"); - } - } - - #endregion - - [HarmonyPatch(typeof(MythicInfoProgressionVM), nameof(MythicInfoProgressionVM.RefreshData))] - private static class MythicInfoProgressionVM_RefreshData_Patch { - static void Postfix(MythicInfoProgressionVM __instance) { - var lastMythic = __instance.m_ClassDatas.LastOrDefault<ClassData>((cd) => !__instance.Unit.Value.IsClassGestalt(cd.CharacterClass)); - //Mod.Trace($"MythicInfoProgressionVM - LastMythic = {lastMythic?.CharacterClass.Name}"); - __instance.MythicName = lastMythic?.CharacterClass.Name; - - } - } - - #region Commit - - [HarmonyPatch(typeof(LevelUpController), nameof(LevelUpController.Commit))] - private static class LevelUpController_Commit_Patch { - [HarmonyPostfix] - private static void Postfix(LevelUpController __instance) { - if (!settings.toggleMulticlass) return; - - if (IsAvailable()) { - var isCharGen = __instance.State.IsCharGen(); - var ch = __instance.Unit; - var options = MulticlassOptions.Get(isCharGen ? null : ch); - if (isCharGen - && __instance.Unit.IsCustomCompanion() - && options.Count > 0) { - //Mod.Trace($"LevelUpController_Commit_Patch - {ch} - {options}"); - MulticlassOptions.Set(ch, options); - } - } - } - } - - #endregion - - [HarmonyPatch(typeof(CharGenClassPhaseVM), nameof(CharGenClassPhaseVM.CreateClassListSelector))] - private static class CharGenClassPhaseVM_CreateClassListSelector_Patch { - private static bool Prefix(CharGenClassPhaseVM __instance) { - if (settings.toggleMulticlass || settings.toggleIgnoreClassRestrictions) { - var progression = Game.Instance.BlueprintRoot.Progression; - __instance.m_ClassesVMs = (__instance.LevelUpController.State.Mode == LevelUpState.CharBuildMode.Mythic - ? __instance.GetMythicClasses() - : (__instance.LevelUpController.Unit.IsPet - ? progression.PetClasses.Concat(progression.CharacterClasses.OrderBy(cl => cl.Name)) - : progression.CharacterClasses) - ) - .Where(cls => { - if (cls.IsDlcRestricted()) - return false; - return CharGenClassPhaseVM.MeetsPrerequisites(__instance.LevelUpController, cls) || !cls.HideIfRestricted; - }) - .Select(cls => new CharGenClassSelectorItemVM( - cls, - null, - __instance.LevelUpController, - __instance, - __instance.SelectedArchetypeVM, - __instance.ReactiveTooltipTemplate, - CharGenClassPhaseVM.IsClassAvailable(__instance.LevelUpController, cls), - true, false)) - .ToList<CharGenClassSelectorItemVM>(); - __instance.ClassSelector = new NestedSelectionGroupRadioVM<CharGenClassSelectorItemVM>((INestedListSource)__instance); - return false; - } - return true; - } - } - public static class MulticlassCheckBoxHelper { - public static void UpdateCheckbox(CharGenClassSelectorItemPCView instance) { - if (instance == null) return; - var multicheckbox = instance.transform?.Find("MulticlassCheckbox-ToyBox"); - if (multicheckbox == null) return; - var toggle = multicheckbox.GetComponent<ToggleWorkaround>(); - if (toggle == null) return; - var viewModel = instance.ViewModel; - if (viewModel == null) return; - var ch = Main.IsInGame ? viewModel.LevelUpController.Unit : null; - var cl = viewModel.Class; - var image = multicheckbox.Find("Background").GetComponent<Image>(); - var canSelect = MulticlassOptions.CanSelectClassAsMulticlass(ch, cl); - image.CrossFadeAlpha(canSelect ? 1.0f : 0f, 0, true); - var options = MulticlassOptions.Get(ch); - var shouldSelect = options.Contains(cl) && (!viewModel.IsArchetype || options.ArchetypeOptions(cl).Contains(viewModel.Archetype)); - - toggle.SetIsOnWithoutNotify(shouldSelect); - toggle.onValueChanged.RemoveAllListeners(); - toggle.onValueChanged.AddListener(v => MulticlassCheckBoxChanged(v, instance)); - } - public static void MulticlassCheckBoxChanged(bool value, CharGenClassSelectorItemPCView instance) { - if (instance == null) return; - var viewModel = instance.ViewModel; - if (viewModel == null) return; - var ch = Main.IsInGame ? viewModel.LevelUpController.Unit : null; - var cl = viewModel.Class; - if (!MulticlassOptions.CanSelectClassAsMulticlass(ch, cl)) return; - var options = MulticlassOptions.Get(ch); - var cd = ch?.Progression.GetClassData(cl); - var chArchetype = cd?.Archetypes.FirstOrDefault<BlueprintArchetype>(); - var archetypeOptions = options.ArchetypeOptions(cl); - if (value) - archetypeOptions = options.Add(cl); - else - options.Remove(cl); - if (options.Contains(cl)) { - if (viewModel.IsArchetype) { - if (value) - archetypeOptions.AddExclusive(viewModel.Archetype); - else - archetypeOptions.Remove(viewModel.Archetype); - } - else if (chArchetype != null) { - // this is the case where the user clicks on the class and the character already has an archetype in this class - if (value) - archetypeOptions.AddExclusive(chArchetype); - else - archetypeOptions.Remove(chArchetype); - } - options.SetArchetypeOptions(cl, archetypeOptions); - } - MulticlassOptions.Set(ch, options); - Mod.Debug($"ch: {ch?.CharacterName.ToString().orange() ?? "null"} class: {cl?.name ?? "null"} isArch: {viewModel.IsArchetype} arch: {viewModel.Archetype} chArchetype:{chArchetype} - options: {options}"); - var canvas = Game.Instance.UI.Canvas; - var transform = canvas != null ? canvas.transform : Game.Instance.UI.MainMenu.transform; - var charGemClassPhaseDetailedView = transform.Find("ChargenPCView/ContentWrapper/DetailedViewZone/PhaseClassDetaildPCView"); - var phaseClassDetailView = charGemClassPhaseDetailedView.GetComponent<Kingmaker.UI.MVVM._PCView.CharGen.Phases.Class.CharGenClassPhaseDetailedPCView>(); - var charGenClassPhaseVM = phaseClassDetailView.ViewModel; - var selectedClassVM = charGenClassPhaseVM.SelectedClassVM.Value; - charGenClassPhaseVM.OnSelectorClassChanged(viewModel); - if (viewModel.IsArchetype) { - charGenClassPhaseVM.OnSelectorArchetypeChanged(viewModel.Archetype); - } - else { - charGenClassPhaseVM.LevelUpController.RemoveArchetype(viewModel.Archetype); - charGenClassPhaseVM.UpdateClassInformation(); - } - charGenClassPhaseVM.OnSelectorClassChanged(selectedClassVM); - - } - } - [HarmonyPatch(typeof(CharGenClassSelectorItemPCView), nameof(CharGenClassSelectorItemPCView.BindViewImplementation))] - private static class CharGenClassSelectorItemPCView_BindViewImplementation_Patch { - private static void Postfix(CharGenClassSelectorItemPCView __instance) { - //Mod.Warn("CharGenClassSelectorItemPCView_CharGenClassSelectorItemPCView_Patch"); - var multicheckbox = __instance.transform.Find("MulticlassCheckbox-ToyBox"); - if (multicheckbox != null) { - if (!settings.toggleMulticlass) { - UnityEngine.Object.Destroy(multicheckbox.gameObject); - } - } - if (!settings.toggleMulticlass) return; - if (multicheckbox == null) { - var checkbox = Game.Instance.UI.FadeCanvas.transform.Find("TutorialView/BigWindow/Window/Content/Footer/Toggle"); - //var checkbox = Game.Instance.UI.Canvas.transform.Find("ServiceWindowsPCView/SpellbookView/SpellbookScreen/MainContainer/KnownSpells/Toggle"); - var sibling = __instance.transform.Find("CollapseButton"); - var textContainer = __instance.transform.Find("TextContainer"); - var textLayout = textContainer.GetComponent<LayoutElement>(); - textLayout.preferredWidth = 1; - var siblingIndex = sibling.transform.GetSiblingIndex(); - multicheckbox = UnityEngine.Object.Instantiate(checkbox, __instance.transform); - multicheckbox.transform.SetSiblingIndex(1); - multicheckbox.name = "MulticlassCheckbox-ToyBox"; - multicheckbox.GetComponentInChildren<TextMeshProUGUI>().text = ""; - multicheckbox.gameObject.SetActive(true); - MulticlassCheckBoxHelper.UpdateCheckbox(__instance); - PerSaveSettings.observers += (perSave) => MulticlassCheckBoxHelper.UpdateCheckbox(__instance); - } - else { - multicheckbox.gameObject.SetActive(true); - MulticlassCheckBoxHelper.UpdateCheckbox(__instance); - } - } - } - //[HarmonyPatch(typeof(CharGenClassSelectorItemPCView), nameof(CharGenClassSelectorItemPCView.RefreshView))] - //private static class CharGenClassSelectorItemPCView_RefreshView_Patch { - // private static void Postfix(CharGenClassSelectorItemPCView __instance) { - // if (!settings.toggleMulticlass) return; - // MulticlassCheckBoxHelper.UpdateCheckbox(__instance); - // } - //} - private static String class_selection_text_initial = null; - [HarmonyPatch(typeof(CharGenClassPhaseDetailedPCView), nameof(CharGenClassPhaseDetailedPCView.BindViewImplementation))] - private static class CharGenClassPhaseDetailedPCView_BindViewImplementation_Patch { - private static void Postfix(CharGenClassPhaseDetailedPCView __instance) { - var chooseClass = __instance.transform.Find("ClassSelecotrPlace/Selector/HeaderH2/Label"); - if (class_selection_text_initial == null) { - class_selection_text_initial = chooseClass.GetComponentInChildren<TextMeshProUGUI>().text; - } - chooseClass.GetComponentInChildren<TextMeshProUGUI>().text = settings.toggleMulticlass ? "Choose Class <size=67%>(Checkbox for multiclass)</size>" : class_selection_text_initial; - } - } - } -} - diff --git a/ToyBox/classes/MonkeyPatchin/Multiclass/StatProgression/HP.cs b/ToyBox/classes/MonkeyPatchin/Multiclass/StatProgression/HP.cs deleted file mode 100644 index e123ef120..000000000 --- a/ToyBox/classes/MonkeyPatchin/Multiclass/StatProgression/HP.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Kingmaker.Blueprints.Classes; -using Kingmaker.EntitySystem.Stats; -using Kingmaker.UnitLogic; -using Kingmaker.UnitLogic.Class.LevelUp; -using System.Linq; - -namespace ToyBox.Multiclass { - public static class HPDice { - public static void ApplyHPDice(UnitDescriptor unit, LevelUpState state, BlueprintCharacterClass[] appliedClasses) { - if (appliedClasses.Count() <= 0) return; - var newClassLvls = appliedClasses.Select(cl => unit.Progression.GetClassLevel(cl)).ToArray(); - var classCount = newClassLvls.Length; - var hitDies = appliedClasses.Select(cl => (int)cl.HitDie).ToArray(); - - var mainClassIndex = appliedClasses.ToList().FindIndex(ch => ch == state.SelectedClass); - //Logger.ModLoggerDebug($"mainClassIndex = {mainClassIndex}"); - var mainClassHPDie = hitDies[mainClassIndex]; - - var currentHPIncrease = hitDies[mainClassIndex]; - var newIncrease = currentHPIncrease; - switch (Main.Settings.multiclassHitPointPolicy) { - case ProgressionPolicy.Average: - newIncrease = hitDies.Sum() / classCount; - break; - case ProgressionPolicy.Largest: - newIncrease = hitDies.Max(); - break; - case ProgressionPolicy.Sum: - newIncrease = hitDies.Sum(); - break; - default: - break; ; - } - unit.Stats.GetStat(StatType.HitPoints).BaseValue += newIncrease - currentHPIncrease; - } - } -} diff --git a/ToyBox/classes/MonkeyPatchin/Multiclass/StatProgression/SavesBAB.cs b/ToyBox/classes/MonkeyPatchin/Multiclass/StatProgression/SavesBAB.cs deleted file mode 100644 index 2498429c2..000000000 --- a/ToyBox/classes/MonkeyPatchin/Multiclass/StatProgression/SavesBAB.cs +++ /dev/null @@ -1,93 +0,0 @@ -using Kingmaker.Blueprints.Classes; -using Kingmaker.EntitySystem.Stats; -using Kingmaker.UnitLogic; -using Kingmaker.UnitLogic.Class.LevelUp; -using System; -using System.Linq; - -namespace ToyBox.Multiclass { - //public enum MulticlassSaveBABType { - // SumOfAll = 0, - // UseMaxIncrement = 1, - // UseMaxValue = 2, - // OnlyPrimary = 3 - //}; - public static class SavesBAB { - public static void ApplySingleStat(UnitDescriptor unit, LevelUpState state, BlueprintCharacterClass[] appliedClasses, StatType stat, BlueprintStatProgression[] statProgs, ProgressionPolicy policy = ProgressionPolicy.Largest) { - if (appliedClasses.Count() <= 0) return; - //Mod.Debug($"stat: {stat} baseValue: {unit.Stats.GetStat(stat).BaseValue}"); - var newClassLvls = appliedClasses.Select(cd => unit.Progression.GetClassLevel(cd)).ToArray(); - var appliedClassCount = newClassLvls.Length; - var oldBonuses = new int[appliedClassCount]; - var newBonuses = new int[appliedClassCount]; - - for (var i = 0; i < appliedClassCount; i++) { - newBonuses[i] = statProgs[i].GetBonus(newClassLvls[i]); - oldBonuses[i] = statProgs[i].GetBonus(newClassLvls[i] - 1); - } - - var mainClassIndex = appliedClasses.ToList().FindIndex(cd => cd == state.SelectedClass); - //v($"mainClassIndex = {mainClassIndex}"); - var mainClassInc = newBonuses[mainClassIndex] - oldBonuses[mainClassIndex]; - var increase = 0; - - switch (policy) { - case ProgressionPolicy.Average: - if (appliedClassCount == 0) - break; - for (var i = 0; i < appliedClassCount; i++) increase += Math.Max(0, newBonuses[i] - oldBonuses[i]); - unit.Stats.GetStat(stat).BaseValue += increase / appliedClassCount - mainClassInc; - break; - case ProgressionPolicy.Largest: - int maxOldValue = 0, maxNewValue = 0; - for (var i = 0; i < appliedClassCount; i++) maxOldValue = Math.Max(maxOldValue, oldBonuses[i]); - for (var i = 0; i < appliedClassCount; i++) maxNewValue = Math.Max(maxNewValue, newBonuses[i]); - increase = maxNewValue - maxOldValue; - unit.Stats.GetStat(stat).BaseValue += increase - mainClassInc; - break; - case ProgressionPolicy.Sum: - for (var i = 0; i < appliedClassCount; i++) increase += Math.Max(0, newBonuses[i] - oldBonuses[i]); - unit.Stats.GetStat(stat).BaseValue += increase - mainClassInc; - break; - default: - break; ; - } - } - - public static void ApplySaveBAB(UnitDescriptor unit, LevelUpState state, BlueprintCharacterClass[] classes) { - ApplySingleStat( - unit, - state, - classes, - StatType.BaseAttackBonus, - classes.Select(a => a.BaseAttackBonus).ToArray(), - Main.Settings.multiclassBABPolicy - ); - ApplySingleStat( - unit, - state, - classes, - StatType.SaveFortitude, - classes.Select(a => a.FortitudeSave).ToArray(), - Main.Settings.multiclassSavingThrowPolicy - ); - ApplySingleStat( - unit, - state, - classes, - StatType.SaveReflex, - classes.Select(a => a.ReflexSave).ToArray(), - Main.Settings.multiclassSavingThrowPolicy - ); - ApplySingleStat( - unit, - state, - classes, - StatType.SaveWill, - classes.Select(a => a.WillSave).ToArray(), - Main.Settings.multiclassSavingThrowPolicy - ); - - } - } -} diff --git a/ToyBox/classes/MonkeyPatchin/Multiclass/StatProgression/SkillPoint.cs b/ToyBox/classes/MonkeyPatchin/Multiclass/StatProgression/SkillPoint.cs deleted file mode 100644 index 863000467..000000000 --- a/ToyBox/classes/MonkeyPatchin/Multiclass/StatProgression/SkillPoint.cs +++ /dev/null @@ -1,61 +0,0 @@ -using HarmonyLib; -using Kingmaker.UnitLogic; -using Kingmaker.UnitLogic.Class.LevelUp.Actions; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; - -namespace ToyBox.Multiclass { - public static class SkillPoint { - public static int CalcTotalSkillPointsNonMythic(this ClassData cd) => cd.CharacterClass.IsMythic ? 0 : cd.CalcSkillPoints() * cd.Level; - public static int? GetTotalSkillPoints(UnitDescriptor unit, int nextLevel) { - var intelligenceSkillPoints = LevelUpHelper.GetTotalIntelligenceSkillPoints(unit, nextLevel); - var classes = unit.Progression.Classes; - var split = classes.GroupBy(cd => unit.IsClassGestalt(cd.CharacterClass)); - var total = 0; - var gestaltCount = 0; - var baseTotal = 0; - var gestaltSumOrMax = 0; - foreach (var group in split) { - if (group.Key == false) baseTotal += group.ToList().Sum(cd => cd.CalcTotalSkillPointsNonMythic()); - else { - var gestaltClasses = group.ToList(); - gestaltCount = gestaltClasses.Count; - if (gestaltCount > 0) { - switch (Main.Settings.multiclassSkillPointPolicy) { - case ProgressionPolicy.Largest: - gestaltSumOrMax += gestaltClasses.Max(cl => cl.CalcTotalSkillPointsNonMythic()); - break; - case ProgressionPolicy.Average: - case ProgressionPolicy.Sum: - gestaltSumOrMax += gestaltClasses.Sum(cl => cl.CalcTotalSkillPointsNonMythic()); - break; - default: - break; - } - } - } - } - total = Main.Settings.multiclassSkillPointPolicy switch { - ProgressionPolicy.Largest => Mathf.Max(baseTotal, gestaltSumOrMax), - ProgressionPolicy.Average => (gestaltSumOrMax + baseTotal) / (gestaltCount + 1), - ProgressionPolicy.Sum => gestaltSumOrMax + baseTotal, - _ => baseTotal, - }; - return Mathf.Max(intelligenceSkillPoints + total, nextLevel); - } - - [HarmonyPatch(typeof(LevelUpHelper), nameof(LevelUpHelper.GetTotalSkillPoints))] - public static class LevelUpHelper_GetTotalSkillPoints_Patch { - public static bool Prefix(UnitDescriptor unit, int nextLevel, ref int __result) { - if (!MultipleClasses.IsAvailable()) return true; - var totalSkillPoints = GetTotalSkillPoints(unit, nextLevel); - if (totalSkillPoints.HasValue) { - __result = totalSkillPoints.Value; - return false; - } - else return true; - } - } - } -} diff --git a/ToyBox/classes/MonkeyPatchin/Multiclass/WrathExtensionsMulticlass.cs b/ToyBox/classes/MonkeyPatchin/Multiclass/WrathExtensionsMulticlass.cs deleted file mode 100644 index 3752bfc33..000000000 --- a/ToyBox/classes/MonkeyPatchin/Multiclass/WrathExtensionsMulticlass.cs +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright < 2021 > Narria (github user Cabarius) - License: MIT -using UnityModManagerNet; -using System; -using System.Linq; -using Kingmaker.Blueprints.Classes; -using Kingmaker.Blueprints.Classes.Spells; -using Kingmaker.Blueprints.Items.Weapons; -using Kingmaker.RuleSystem; -//using Kingmaker.UI.LevelUp.Phase; -using Kingmaker.UnitLogic; -using Kingmaker.UnitLogic.Class.LevelUp; -using Kingmaker.UnitLogic.Class.LevelUp.Actions; -using Kingmaker.Utility; -using ModKit.Utility; -using ModKit; - -namespace ToyBox.Multiclass { - public static class WrathExtensionsMulticlass { - public static void AddClassLevel_NotCharacterLevel(this UnitProgressionData instance, BlueprintCharacterClass characterClass) { - //instance.SureClassData(characterClass).Level++; - Mod.Debug($"AddClassLevel_NotCharLevel: class = {characterClass.name.cyan()} - lvl:{instance.GetClassLevel(characterClass)} - {string.Join(", ", instance.Features.Enumerable.Select(f => f.Name.orange()))}"); - ReflectionCache.GetMethod<UnitProgressionData, Func<UnitProgressionData, BlueprintCharacterClass, ClassData>> - ("SureClassData")(instance, characterClass).Level++; - //instance.CharacterLevel++; - instance.m_ClassesOrder.Add(characterClass); - instance.Classes.Sort(); - instance.Features.AddFeature(characterClass.Progression, null).SetSource(characterClass, 1); - instance.Owner.OnGainClassLevel(characterClass); - //int[] bonuses = BlueprintRoot.Instance.Progression.XPTable.Bonuses; - //int val = bonuses[Math.Min(bonuses.Length - 1, instance.CharacterLevel)]; - //instance.Experience = Math.Max(instance.Experience, val); - } - - public static void Apply_NoStatsAndHitPoints(this ApplyClassMechanics instance, LevelUpState state, UnitDescriptor unit) { - Mod.Trace($"Apply_NoStatsAndHitPoints: unit = {unit.CharacterName}, state.class = {(state.SelectedClass == null ? "NULL" : state.SelectedClass.Name)}"); - if (state.SelectedClass != null) { - var classData = unit.Progression.GetClassData(state.SelectedClass); - if (classData != null) { - //GetMethodDel<ApplyClassMechanics, Action<ApplyClassMechanics, LevelUpState, ClassData, UnitDescriptor>> - // ("ApplyBaseStats")(null, state, classData, unit); - //GetMethodDel<ApplyClassMechanics, Action<ApplyClassMechanics, LevelUpState, ClassData, UnitDescriptor>> - // ("ApplyHitPoints")(null, state, classData, unit); - ReflectionCache.GetMethod<ApplyClassMechanics, Action<ApplyClassMechanics, ClassData, UnitDescriptor>> - ("ApplyClassSkills")(null, classData, unit); - ReflectionCache.GetMethod<ApplyClassMechanics, Action<ApplyClassMechanics, LevelUpState, UnitDescriptor>> - ("ApplyProgressions")(null, state, unit); - } - } - } - - public static BlueprintCharacterClass GetOwnerClass(this BlueprintSpellbook spellbook, UnitDescriptor unit) => unit.Progression.Classes.FirstOrDefault(item => item.Spellbook == spellbook)?.CharacterClass; - - public static ClassData GetOwnerClassData(this BlueprintSpellbook spellbook, UnitDescriptor unit) => unit.Progression.Classes.FirstOrDefault(item => item.Spellbook == spellbook); - - public static BlueprintCharacterClass GetSourceClass(this BlueprintFeature feature, UnitDescriptor unit) => unit.Progression.Features.Enumerable.FirstOrDefault(item => item.Blueprint == feature)?.GetSourceClass(); - - public static bool IsChildProgressionOf(this BlueprintProgression progression, UnitDescriptor unit, BlueprintCharacterClass characterClass) { - var classData = unit.Progression.GetClassData(characterClass); - if (classData != null) { - /* - if (progression.Classes.Contains(characterClass) && - !progression.Archetypes.Intersect(characterClass.Archetypes).Any()) - return true; - if (progression.Archetypes.Intersect(unit.Progression.GetClassData(characterClass).Archetypes).Any()) - return true; - */ - if (progression.Classes.Contains(characterClass)) - return true; - } - return false; - } - - public static bool IsManualPlayerUnit(this LevelUpController controller, bool allowPet = false, bool allowAutoCommit = false, bool allowPregen = false) => - //Main.Log($"controller: {controller} AutoCommit: {controller.AutoCommit}"); - //Main.Log($" unit: {controller.Unit} isPlayerFaction: {controller.Unit.IsPlayerFaction} isPet: {controller.Unit.IsPet}"); - //Main.Log($" levelup state: {controller.State}"); - - controller != null - && controller.Unit.IsPlayerFaction - && (allowPet || !controller.Unit.IsPet - //&& (allowAutoCommit || !controller.AutoCommit) - //&& (allowPregen || !controller.State.IsPreGen() - ); - -#if false - public static void SetSpellbook(this CharBPhaseSpells instance, BlueprintCharacterClass characterClass) { - LevelUpState state = Game.Instance.LevelUpController.State; - BlueprintCharacterClass selectedClass = state.SelectedClass; - state.SelectedClass = characterClass; - ReflectionCache.GetMethod<CharBPhaseSpells, Action<CharBPhaseSpells>>("SetupSpellBookView")(instance); - state.SelectedClass = selectedClass; - } -#endif - //public static ClassData SureClassData(this UnitProgressionData progression, BlueprintCharacterClass characterClass) - //{ - // ClassData classData = progression.Classes.FirstOrDefault((ClassData cd) => cd.CharacterClass == characterClass); - // if (classData == null) - // { - // classData = new ClassData(characterClass); - // progression.Classes.Add(classData); - // } - // return classData; - //} - } -} \ No newline at end of file diff --git a/ToyBox/classes/MonkeyPatchin/PartyView.cs b/ToyBox/classes/MonkeyPatchin/PartyView.cs deleted file mode 100644 index 54eba61f0..000000000 --- a/ToyBox/classes/MonkeyPatchin/PartyView.cs +++ /dev/null @@ -1,433 +0,0 @@ -using HarmonyLib; -using Kingmaker; -using Kingmaker.Assets.Code.UI.Overtip; -using Kingmaker.UI.MVVM._VM.Party; -using System; -using UnityModManager = UnityModManagerNet.UnityModManager; -using Kingmaker.PubSubSystem; -using UniRx; -using Kingmaker.UI.MVVM._PCView.Party; -using Kingmaker.UI; -using Kingmaker.Blueprints.Root; -using Owlcat.Runtime.UI.Controls.Selectable; -using Owlcat.Runtime.UI.Controls.Other; -using Owlcat.Runtime.UI.Controls.Button; -using System.Collections.Generic; -using System.Linq; -using System.Reflection.Emit; -using System.Reflection; -using UnityEngine; -using ModKit; -using UnityEngine.UI; - -namespace ToyBox.BagOfPatches { - internal static class PartyView { - public static Settings settings => Main.Settings; - public static Player player = Game.Instance.Player; - -#if false - static int SlotsInParty = 8; - [HarmonyPatch(typeof(PartyVM), nameof(PartyVM.SetGroup))] - public static class PartyVM_SetGroup_Patch { - public static bool Prefix(ref PartyVM __instance) { - Main.Log($"PartyVM_SetGroup_Patch"); - //for (int index = 6;index < SlotsInParty; ++index) { - // Main.Log($"PartyVM_SetGroup_Patch - adding slot: {index}"); - // __instance.CharactersVM.Add(new PartyCharacterVM(new Action<bool>(__instance.NextPrev), index)); - //} - - __instance.m_ActualGroup = Game.Instance.SelectionCharacter.ActualGroup; - __instance.StartIndex = 0; - for (int index = 0;index < __instance.m_ActualGroup.Count;++index) { - if (__instance.FullCharactersVM.Count == index) { - __instance.FullCharactersVM.Add(new PartyCharacterVM(new Action<bool>(__instance.NextPrev), index)); - __instance.CharactersVM.Add(new PartyCharacterVM(new Action<bool>(__instance.NextPrev), index)); - Main.Log($"PartyVM_SetGroup_Patch - adding slot: {index}"); - } - __instance.FullCharactersVM[index].SetUnitData(__instance.m_ActualGroup[index]); - } - while (__instance.FullCharactersVM.Count > __instance.m_ActualGroup.Count) { - Main.Log($"PartyVM_SetGroup_Patch - removing slot: {__instance.FullCharactersVM.Count - 1}"); - - PartyCharacterVM partyCharacterVm = __instance.FullCharactersVM[__instance.FullCharactersVM.Count - 1]; - partyCharacterVm.Dispose(); - __instance.FullCharactersVM.Remove(partyCharacterVm); - } - return false; - } - } -#endif - -#if false - public static class PartyVM_Construtor_Patch { - public static void Postfix(PartyVM __instance) { - Game.Instance.UI.PartyBarkManager = (Kingmaker.Assets.Code.UI.Overtip.IBarkPlayer)__instance; - for (int index = 6; index < SlotsInParty; ++index) - __instance.CharactersVM.Add(new PartyCharacterVM(new Action<bool>(__instance.NextPrev), index)); - __instance.SetGroup(); - } - } - - [HarmonyPatch(typeof(PartyVM), MethodType.Constructor)] - public static class PartyVM_Construtor_Patch { - public static bool Prefix(PartyVM __instance) { - __instance.AddDisposable(EventBus.Subscribe((object)__instance)); - Game.Instance.UI.PartyBarkManager = (IBarkPlayer)__instance; - for (int index = 0;index < 6;++index) - __instance.CharactersVM.Add(new PartyCharacterVM(new Action<bool>(__instance.NextPrev), index)); - __instance.AddDisposable(Game.Instance.SelectionCharacter.SelectionCharacterUpdated.Subscribe<UniRx.Unit>((Action<UniRx.Unit>)(_ => __instance.SetGroup()))); - __instance.SetGroup(); - return false; - } - } - - [HarmonyPatch(typeof(PartyVM), nameof(PartyVM.StartIndex) , MethodType.Setter)] - public static class PartyVM_StartIndex_Patch { - public static bool Prefix(ref PartyVM __instance, int value) { - int max = __instance.m_ActualGroup.Count - SlotsInParty; - if (max < 0) - max = 0; - value = Mathf.Clamp(value, 0, max); - __instance.m_StartIndex = value; - __instance.PrevEnable.Value = value > 0; - __instance.NextEnable.Value = __instance.m_ActualGroup.Count > __instance.m_StartIndex + SlotsInParty; - for (int index1 = 0; index1 < __instance.CharactersVM.Count; ++index1) { - int index2 = __instance.m_StartIndex + index1; - __instance.CharactersVM[index1].SetUnitData(__instance.m_ActualGroup.Count > index2 ? __instance.m_ActualGroup[index2] : (UnitEntityData)null); - } - return true; - } - } - [HarmonyPatch(typeof(PartyVM), nameof(PartyVM.SetGroup))] - public static class PartyVM_SetGroup_Patch { - public static bool Prefix(ref PartyVM __instance) { - __instance.m_ActualGroup = Game.Instance.SelectionCharacter.ActualGroup; - __instance.StartIndex = 0; - for (int index = 0; index < __instance.m_ActualGroup.Count; ++index) { - if (__instance.FullCharactersVM.Count == index) { - __instance.FullCharactersVM.Add(new PartyCharacterVM(new Action<bool>(__instance.NextPrev), index)); - } - __instance.FullCharactersVM[index].SetUnitData(__instance.m_ActualGroup[index]); - } - while (__instance.FullCharactersVM.Count > __instance.m_ActualGroup.Count) { - PartyCharacterVM partyCharacterVm = __instance.FullCharactersVM[__instance.FullCharactersVM.Count - 1]; - partyCharacterVm.Dispose(); - __instance.FullCharactersVM.Remove(partyCharacterVm); - } - __instance.CharactersVM.Clear(); - foreach (var ch in __instance.FullCharactersVM) { - __instance.CharactersVM.Add(ch); - } - Main.Log($"after: Characters: {__instance.CharactersVM.Count()}"); - Main.Log($"after: FullCharacters: {__instance.FullCharactersVM.Count()}"); - return true; - } - } -#endif -#if false - [HarmonyPatch(typeof(PartyPCView))] - internal static class PartyPCView_Patches { - private static Sprite hudBackground8; - private static int leadingEdge; - private static float itemWidth; - private static float firstX; - private static float scale; - - [HarmonyPatch("Initialize"), HarmonyPrefix] - static void Initialize(PartyPCView __instance) { - if (PartyVM_Patches.SupportedSlots == 6) - return; - - try { - - - Mod.Log("INSTALLING BUBBLE GROUP PANEL"); - - if (hudBackground8 == null) { - hudBackground8 = AssetLoader.LoadInternal("sprites", "UI_HudBackgroundCharacter_8.png", new Vector2Int(1746, 298)); - } - - __instance.transform.Find("Background").GetComponent<Image>().sprite = hudBackground8; - - scale = 6 / (float)8; - itemWidth = 94.5f; - __instance.m_Shift = itemWidth; - - var currentViews = __instance.GetComponentsInChildren<PartyCharacterPCView>(true); - List<GameObject> toTweak = new(currentViews.Select(view => view.gameObject)); - firstX = toTweak[0].transform.localPosition.x - 9.5f; - - UpdateCharacterBindings(__instance); - - } - catch (Exception e) { - Mod.Error(e); - } - } - - [HarmonyPatch(nameof(PartyPCView.UpdateCharacterBindings)), HarmonyPostfix] - static void UpdateCharacterBindings(PartyPCView __instance) { - if (PartyVM_Patches.SupportedSlots == 6) return; - - var currentViews = __instance.GetComponentsInChildren<PartyCharacterPCView>(true); - List<GameObject> toTweak = new(currentViews.Select(view => view.gameObject)); - firstX = toTweak[0].transform.localPosition.x - 9.5f; - - for (int i = 0; i < toTweak.Count; i++) { - GameObject view = toTweak[i]; - - TweakPCView(__instance, i, view); - } - } - - private static void TweakPCView(PartyPCView __instance, int i, GameObject view) { - var viewRect = view.transform as RectTransform; - - if (viewRect.localScale.x <= (scale + 0.01f)) return; - - var pos = viewRect.localPosition; - pos.x = firstX + (i * itemWidth); - viewRect.localPosition = pos; - viewRect.localScale = new Vector3(scale, scale, 1); - - var portraitRect = view.transform.Find("Portrait") as RectTransform; - const float recaleFactor = 1.25f; - portraitRect.localScale = new Vector3(recaleFactor, recaleFactor, 1); - - var frameRect = view.transform.Find("Frame") as RectTransform; - frameRect.pivot = new Vector2(.5f, 1); - frameRect.anchoredPosition = new Vector2(0, 23); - frameRect.sizeDelta = new Vector2(0, 47); - - var healthBarRect = view.transform.Find("Health") as RectTransform; - healthBarRect.pivot = new Vector2(0, 1); - healthBarRect.anchoredPosition = new Vector2(0, -2); - healthBarRect.anchorMin = new Vector2(0, 1); - healthBarRect.anchorMax = new Vector2(0, 1); - healthBarRect.localScale = new Vector2(recaleFactor, recaleFactor); - - var hitpointRect = view.transform.Find("HitPoint") as RectTransform; - var hpPos = hitpointRect.anchoredPosition; - hpPos.y -= 20; - hitpointRect.anchoredPosition = hpPos; - - view.transform.Find("PartBuffView").gameObject.SetActive(false); - - (view.transform.Find("Frame/Selected/Mark") as RectTransform).anchoredPosition = new Vector2(0, 94); - - var buffRect = view.transform.Find("BuffMain") as RectTransform; - - buffRect.sizeDelta = new Vector2(-8, 24); - buffRect.pivot = new Vector2(0, 0); - buffRect.anchorMin = new Vector2(0, 1); - buffRect.anchorMax = new Vector2(1, 1); - buffRect.anchoredPosition = new Vector2(4, -4); - buffRect.Edit<GridLayoutGroupWorkaround>(g => { - g.constraint = GridLayoutGroup.Constraint.FixedRowCount; - g.padding.top = 2; - }); - buffRect.gameObject.AddComponent<Image>().color = new Color(.05f, .05f, .05f); - - var buffHover = buffRect.Find("BuffTriggerNotification").GetComponent<OwlcatSelectable>(); - - __instance.AddDisposable(buffHover.OnHoverAsObservable().Subscribe<bool>(selected => { - viewRect.SetAsLastSibling(); - })); - - buffRect.Find("BuffTriggerNotification/BuffAdditional/").localScale = new Vector2(1.25f, 1.25f); - } - } - - //[HarmonyPatch(typeof(UnitBuffPartPCView))] - //static class UnitBuffPartPCView_Patches { - - // private static HashSet<Guid> Dreamwork = new() { - // // Teamwork buffs - // Guid.Parse("44569e9e95364bf42b1071382a8a89da"), - // Guid.Parse("a6298b0f87fc7694086cd8eac9d6a2aa"), - // Guid.Parse("cc26546e4f73fe142b606b4759b4eb18"), - // Guid.Parse("e5079510480031146992dafde835c3b8"), - // Guid.Parse("3de0359d9480cb549ab6cf1eac51f9dc"), - // Guid.Parse("2f5768f642de59f40acd5211a627a237"), - // Guid.Parse("965ea9716b87f4b46a6a8f50523393bd"), - // Guid.Parse("693964e674883e74b8d0005dbf4a4e6b"), - // Guid.Parse("731a11dcc952e744f8a88768e07a0542"), - // Guid.Parse("953c3dbda63dcdb4aad6c54c1a4590d0"), - // Guid.Parse("9c179de4894c295499822714878f3590"), - // Guid.Parse("c7223802e54e8524c8b1e5c71df22f7b"), - - // // Toggles (power attack, rapid shot) - // Guid.Parse("0f310c1e709e15e4fa693db15a4baeb4"), - // Guid.Parse("f958ef62eea5050418fb92dfa944c631"), - // Guid.Parse("8af258b1dd322874ba6047b0c24660c7"), - // Guid.Parse("bf3b19ed9c919464aa2a741271718542"), - - // }; - - - // [HarmonyPatch("DrawBuffs"), HarmonyPostfix] - // static void DrawBuffs(UnitBuffPartPCView __instance) { - // try { - // if (PartyVM_Patches.SupportedSlots == 6) - // return; - - // if (__instance.ViewModel.Buffs.Count <= 6) - // return; - - // var main = __instance.m_MainContainer.transform; - // var overflow = __instance.m_AdditionalContainer.transform; - - // int[] badButShown = new int[5]; - // int[] goodButHidden = new int[5]; - // int nextShown = 0; - // int nextHidden = 0; - - // for (int i = 0; i < __instance.m_BuffList.Count; i++) { - // var buff = __instance.m_BuffList[i].ViewModel.Buff; - // if (nextShown < 5 && Dreamwork.Contains(buff.Blueprint.AssetGuid.m_Guid)) { - // badButShown[nextShown++] = i; - // } else if (nextHidden < 5 && __instance.m_BuffList[i].transform.parent == overflow) { - // goodButHidden[nextHidden++] = i; - // } - // } - - // if (nextHidden == 0 || nextShown == 0) - // return; - - // Vector3 overflowScale = __instance.m_BuffList[goodButHidden[0]].transform.localScale; - // Vector3 mainScale = __instance.m_BuffList[badButShown[0]].transform.localScale; - - - // while (nextHidden > 0 && nextShown > 0) { - // nextHidden--; - // nextShown--; - - // __instance.m_BuffList[badButShown[nextShown]].transform.SetParent(overflow); - // __instance.m_BuffList[badButShown[nextShown]].transform.localScale = overflowScale; - // __instance.m_BuffList[goodButHidden[nextHidden]].transform.SetParent(main); - // __instance.m_BuffList[goodButHidden[nextHidden]].transform.localScale = mainScale; - // } - - // while (main.childCount > 6) { - // var toSwap = main.transform.GetChild(6); - // toSwap.transform.SetParent(overflow); - // toSwap.localScale = overflowScale; - // } - // } catch (Exception ex) { - // Main.Error(ex, "buffling"); - // } - // } - //} - - //[HarmonyPatch(typeof(PartyVM))] - public static class PartyVM_Patches { - - public static int SupportedSlots = 6; - - private static int WantedSlots = 6; - - //[HarmonyTranspiler] - //[HarmonyPatch("set_StartIndex")] - static IEnumerable<CodeInstruction> UpdateStartValue(IEnumerable<CodeInstruction> instructions) { - return ConvertConstants(instructions, 8); - } - - //[HarmonyTranspiler] - //[HarmonyPatch(MethodType.Constructor)] - static IEnumerable<CodeInstruction> _ctor(IEnumerable<CodeInstruction> instructions) { - return ConvertConstants(instructions, 8); - } - - private static OpCode[] LdConstants = { - OpCodes.Ldc_I4_0, - OpCodes.Ldc_I4_1, - OpCodes.Ldc_I4_2, - OpCodes.Ldc_I4_3, - OpCodes.Ldc_I4_4, - OpCodes.Ldc_I4_5, - OpCodes.Ldc_I4_6, - OpCodes.Ldc_I4_7, - OpCodes.Ldc_I4_8, - }; - - private static IEnumerable<CodeInstruction> ConvertConstants(IEnumerable<CodeInstruction> instructions, int to) { - Func<CodeInstruction> makeReplacement; - if (to <= 8) - makeReplacement = () => new CodeInstruction(LdConstants[to]); - else - makeReplacement = () => new CodeInstruction(OpCodes.Ldc_I4_S, to); - - foreach (var ins in instructions) { - if (ins.opcode == OpCodes.Ldc_I4_6) - yield return makeReplacement(); - else - yield return ins; - } - } - - - private static readonly BubblePatch Patch_ctor = BubblePatch.FirstConstructor(typeof(PartyVM), typeof(PartyVM_Patches)); - private static readonly BubblePatch Patch_set_StartIndex = BubblePatch.Method(typeof(PartyVM), typeof(PartyVM_Patches), "UpdateStartValue"); - - public static void Repatch() { - if (settings.toggleExpandedPartyView) - WantedSlots = 8; - else - WantedSlots = 6; - - if (SupportedSlots == WantedSlots) - return; - - Patch_ctor.Revert(); - Patch_set_StartIndex.Revert(); - - if (WantedSlots == 6) - return; - - SupportedSlots = 8; - - Patch_ctor.Apply(); - Patch_set_StartIndex.Apply(); - } - } - - public class BubblePatch { - public MethodBase Original; - public HarmonyMethod Patch; - public bool IsPatched = false; - - public BubblePatch(MethodBase target, Type patcher, string name) { - Patch = new HarmonyMethod(patcher, name); - Original = target; - } - - public static BubblePatch FirstConstructor(Type target, Type patcher) { - return new BubblePatch(target.GetConstructors().First(), patcher, "_ctor"); - } - public static BubblePatch Setter(Type target, Type patcher, string propertyName) { - return new BubblePatch(target.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).SetMethod, patcher, $"set_{propertyName}"); - } - public static BubblePatch Getter(Type target, Type patcher, string propertyName) { - return new BubblePatch(target.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).GetMethod, patcher, $"get_{propertyName}"); - } - public static BubblePatch Method(Type target, Type patcher, string name) { - return new BubblePatch(target.GetMethod(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance), patcher, name); - } - - public void Revert() { - if (!IsPatched) - return; - - Main.HarmonyInstance.Unpatch(Original, Patch.method); - IsPatched = false; - } - public void Apply() { - if (IsPatched) - throw new Exception("Trying to apply a patch that is already applied"); - Main.HarmonyInstance.Patch(Original, null, null, Patch, null); - IsPatched = true; - } - } -#endif - } -} diff --git a/ToyBox/classes/MonkeyPatchin/PreviewManager.cs b/ToyBox/classes/MonkeyPatchin/PreviewManager.cs deleted file mode 100644 index ca7c5aea4..000000000 --- a/ToyBox/classes/MonkeyPatchin/PreviewManager.cs +++ /dev/null @@ -1,617 +0,0 @@ -using HarmonyLib; -using Kingmaker; -using Kingmaker.Blueprints; -using Kingmaker.Blueprints.Items; -using Kingmaker.Blueprints.Root; -using Kingmaker.Blueprints.Root.Strings; -using Kingmaker.Controllers.Dialog; -using Kingmaker.Designers.EventConditionActionSystem.Actions; -using Kingmaker.Designers.EventConditionActionSystem.Conditions; -using Kingmaker.DialogSystem; -using Kingmaker.DialogSystem.Blueprints; -using Kingmaker.ElementsSystem; -using Kingmaker.Enums; -using Kingmaker.Kingdom; -using Kingmaker.Kingdom.Actions; -using Kingmaker.Kingdom.Blueprints; -using Kingmaker.Kingdom.Tasks; -using Kingmaker.Kingdom.UI; -using Kingmaker.Localization; -using Kingmaker.PubSubSystem; -using Kingmaker.RandomEncounters; -using Kingmaker.Settings; -using Kingmaker.UI.Common; -using Kingmaker.UI.Dialog; -using Kingmaker.UI.GlobalMap; -using Kingmaker.UI.Kingdom; -using Kingmaker.UI.MVVM._PCView.Dialog.Dialog; -using Kingmaker.UI.MVVM._VM.Tooltip.Utils; -using Kingmaker.UI.Tooltip; -using Kingmaker.UnitLogic.Alignments; -using Kingmaker.Utility; -using ModKit; -using Owlcat.Runtime.UI.Tooltips; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using System.Xml.Linq; -using TMPro; -using UnityEngine; -using UnityEngine.Events; -using UnityEngine.UI; - -namespace ToyBox { - internal static class PreviewManager { - public static Settings settings = Main.Settings; - public static Player player = Game.Instance.Player; - private static GameDialogsSettings DialogSettings => SettingsRoot.Game.Dialogs; - - public static KingdomStats.Changes CalculateEventResult(KingdomEvent kingdomEvent, EventResult.MarginType margin, AlignmentMaskType alignment, LeaderType leaderType) { - var checkMargin = EventResult.MarginToInt(margin); - var result = new KingdomStats.Changes(); - var m_TriggerChange = Traverse.Create(kingdomEvent).Field("m_TriggerChange").GetValue<KingdomStats.Changes>(); - var m_SuccessCount = Traverse.Create(kingdomEvent).Field("m_SuccessCount").GetValue<int>(); - var blueprintKingdomEvent = kingdomEvent.EventBlueprint as BlueprintKingdomEvent; - if (blueprintKingdomEvent && blueprintKingdomEvent.UnapplyTriggerOnResolve && m_TriggerChange != null) { - result.Accumulate(m_TriggerChange.Opposite(), 1); - } - var resolutions = kingdomEvent.EventBlueprint.Solutions.GetResolutions(leaderType); - if (resolutions == null) resolutions = Array.Empty<EventResult>(); - foreach (var eventResult in resolutions) { - var validConditions = eventResult.Condition == null; // || eventResult.Condition.Check(kingdomEvent.GetKingdomEventData()); - if (eventResult.MatchesMargin(checkMargin) && (alignment & eventResult.LeaderAlignment) != AlignmentMaskType.None && validConditions) { - result.Accumulate(eventResult.StatChanges, 1); - m_SuccessCount += eventResult.SuccessCount; - } - } - if (checkMargin >= 0 && blueprintKingdomEvent != null) { - result.Accumulate((KingdomStats.Type)leaderType, Game.Instance.BlueprintRoot.Kingdom.StatIncreaseOnEvent); - } - var willBeFinished = true; - if (blueprintKingdomEvent != null && blueprintKingdomEvent.IsRecurrent) { - willBeFinished = m_SuccessCount >= blueprintKingdomEvent.Solutions.GetSuccessCount(leaderType); - } - if (willBeFinished) { - var eventFinalResults = kingdomEvent.EventBlueprint.GetComponent<EventFinalResults>(); - if (eventFinalResults != null && eventFinalResults.Results != null) { - foreach (var eventResult in eventFinalResults.Results) { - var validConditions = eventResult.Condition == null; // || eventResult.Condition.Check(kingdomEvent.EventBlueprint); - if (eventResult.MatchesMargin(checkMargin) && (alignment & eventResult.LeaderAlignment) != AlignmentMaskType.None && validConditions) { - result.Accumulate(eventResult.StatChanges, 1); - } - } - } - } - return result; - - } - - private static string FormatResult(KingdomEvent kingdomEvent, EventResult.MarginType margin, AlignmentMaskType alignment, LeaderType leaderType) { - var text = ""; - var statChanges = CalculateEventResult(kingdomEvent, margin, alignment, leaderType); - var statChangesText = statChanges.ToStringWithPrefix(" "); - text += string.Format("{0}:{1}", - margin, - statChangesText == "" ? " No Change" : statChangesText); - //TODO: Solution for presenting actions - text += "\n"; - return text; - } - - private static List<Tuple<BlueprintCueBase, int, GameAction[], AlignmentShift>> CollateAnswerData(BlueprintAnswer answer, out bool isRecursive) { - var cueResults = new List<Tuple<BlueprintCueBase, int, GameAction[], AlignmentShift>>(); - var toCheck = new Queue<Tuple<BlueprintCueBase, int>>(); - isRecursive = false; - var visited = new HashSet<BlueprintAnswerBase> { }; - visited.Add(answer); - if (answer.NextCue.Cues.Count > 0) { - toCheck.Enqueue(new Tuple<BlueprintCueBase, int>(answer.NextCue.Cues[0], 1)); - } - cueResults.Add(new Tuple<BlueprintCueBase, int, GameAction[], AlignmentShift>( - null, - 0, - answer.OnSelect.Actions, - answer.AlignmentShift - )); - while (toCheck.Count > 0) { - var item = toCheck.Dequeue(); - var cueBase = item.Item1; - var currentDepth = item.Item2; - if (currentDepth > 20) break; - if (cueBase is BlueprintCue cue) { - cueResults.Add(new Tuple<BlueprintCueBase, int, GameAction[], AlignmentShift>( - cue, - currentDepth, - cue.OnShow.Actions.Concat(cue.OnStop.Actions).ToArray(), - cue.AlignmentShift - )); - if (cue.Answers.Count > 0) { - var subAnswer = cue.Answers[0].Get(); - if (visited.Contains(subAnswer)) { - isRecursive = true; - break; - } - visited.Add(subAnswer); - } - if (cue.Continue.Cues.Count > 0) { - toCheck.Enqueue(new Tuple<BlueprintCueBase, int>(cue.Continue.Cues[0], currentDepth + 1)); - } - } - else if (cueBase is BlueprintBookPage page) { - cueResults.Add(new Tuple<BlueprintCueBase, int, GameAction[], AlignmentShift>( - page, - currentDepth, - page.OnShow.Actions, - null - )); - if (page.Answers.Count > 0) { - var subAnswer = page.Answers[0].Get(); - if (visited.Contains(subAnswer)) { - isRecursive = true; - break; - } - visited.Add(subAnswer); - if (page.Answers[0].Get() is BlueprintAnswersList) break; - } - if (page.Cues.Count > 0) { - foreach (var c in page.Cues) - if (c.Get().CanShow()) - toCheck.Enqueue(new Tuple<BlueprintCueBase, int>(c, currentDepth + 1)); - } - } - else if (cueBase is BlueprintCheck check) { - toCheck.Enqueue(new Tuple<BlueprintCueBase, int>(check.Success, currentDepth + 1)); - toCheck.Enqueue(new Tuple<BlueprintCueBase, int>(check.Fail, currentDepth + 1)); - } - else if (cueBase is BlueprintCueSequence sequence) { - foreach (var c in sequence.Cues) - if (c.Get().CanShow()) - toCheck.Enqueue(new Tuple<BlueprintCueBase, int>(c, currentDepth + 1)); - if (sequence.Exit != null) { - var exit = sequence.Exit; - if (exit.Answers.Count > 0) { - var subAnswer = exit.Answers[0]; - if (visited.Contains(subAnswer)) { - isRecursive = true; - break; - } - visited.Add(subAnswer); - if (exit.Continue.Cues.Count > 0) { - toCheck.Enqueue(new Tuple<BlueprintCueBase, int>(exit.Continue.Cues[0], currentDepth + 1)); - } - } - } - } - else { - break; - } - } - return cueResults; - } - - public static string GetFixedAnswerString(BlueprintAnswer answer, string bind, int index) { - var flag = Game.Instance.DialogController.Dialog.Type == DialogType.Book; - string checkFormat = (!flag) ? UIDialog.Instance.AnswerStringWithCheckFormat : UIDialog.Instance.AnswerStringWithCheckBeFormat; - var text = string.Empty; - if (DialogSettings.ShowSkillcheckDC) { - text = answer.SkillChecksDC.Aggregate(string.Empty, (string current, SkillCheckDC skillCheck) => current - + string.Format(checkFormat, UIUtility.PackKeys(new object[] { - TooltipType.SkillcheckDC, - skillCheck.StatType - }), LocalizedTexts.Instance.Stats.GetText(skillCheck.StatType), skillCheck.ValueDC)); - } - if (DialogSettings.ShowAlignmentRequirements && answer.AlignmentRequirement != AlignmentComponent.None) { - text = string.Format(UIDialog.Instance.AlignmentRequirementFormat, UIUtility.GetAlignmentRequirementText(answer.AlignmentRequirement)) + text; - } - if (answer.HasShowCheck) { - text = string.Format(UIDialog.Instance.AnswerShowCheckFormat, LocalizedTexts.Instance.Stats.GetText(answer.ShowCheck.Type), text); - } - if (DialogSettings.ShowAlignmentShiftsInAnswer && answer.AlignmentRequirement == AlignmentComponent.None && answer.AlignmentShift.Value > 0 && DialogSettings.ShowAlignmentShiftsInAnswer) { - text = string.Format(UIDialog.Instance.AligmentShiftedFormat, UIUtility.GetAlignmentShiftDirectionText(answer.AlignmentShift.Direction)) + text; - } - var stringByBinding = UIKeyboardTexts.Instance.GetStringByBinding(Game.Instance.Keyboard.GetBindingByName(bind)); - return string.Format(UIDialog.Instance.AnswerDialogueFormat, - (!stringByBinding.Empty()) ? stringByBinding : index.ToString(), - text + ((!text.Empty()) ? " " : string.Empty) + answer.DisplayText); - } - - [HarmonyPatch(typeof(UIConsts), nameof(UIConsts.GetAnswerString))] - private static class UIConsts_GetAnswerString_Patch { - private static void Postfix(ref string __result, BlueprintAnswer answer, string bind, int index) { - try { - if (!Main.Enabled) return; - if (Main.Settings.previewAlignmentRestrictedDialog && !answer.IsAlignmentRequirementSatisfied) { - __result = GetFixedAnswerString(answer, bind, index); - } - if (!Main.Settings.previewDialogResults) return; - var answerData = CollateAnswerData(answer, out var isRecursive); - if (isRecursive) { - __result += $" <size=75%>[Repeats]</size>"; - } - var results = new List<string>(); - foreach (var data in answerData) { - var cue = data.Item1; - var depth = data.Item2; - var actions = data.Item3; - var alignment = data.Item4; - var line = new List<string>(); - if (actions.Length > 0) { - line.AddRange(actions.SelectMany(action => PreviewUtilities.FormatActionAsList(action) - .Select(actionText => actionText == "" ? "EmptyAction" : actionText))); - } - if (alignment != null && alignment.Value > 0) { - line.Add($"AlignmentShift({alignment.Direction}, {alignment.Value}, {alignment.Description})"); - } - if (cue is BlueprintCheck check) { - line.Add($"Check({check.Type}, DC {check.DC}, hidden {check.Hidden})"); - } - if (line.Count > 0) results.Add($"{depth}: {line.Join()}"); - } - if (results.Count > 0) __result += $" \v<size=75%>[{results.Join()}]</size>"; - } - catch (Exception ex) { - Mod.Error(ex); - } - } - } - - [HarmonyPatch(typeof(DialogCurrentPart), nameof(DialogCurrentPart.Fill))] - private static class DialogCurrentPart_Fill_Patch { - private static void Postfix(DialogCurrentPart __instance) { - try { - if (!Main.Enabled) return; - if (!Main.Settings.previewDialogResults) return; - var cue = Game.Instance.DialogController.CurrentCue; - var actions = cue.OnShow.Actions.Concat(cue.OnStop.Actions).ToArray(); - var alignment = cue.AlignmentShift; - var text = ""; - if (actions.Length > 0) { - var result = PreviewUtilities.FormatActions(actions); - if (result == "") result = "EmptyAction"; - text += $" \n<size=75%>[{result}]</size>"; - } - if (alignment != null && alignment.Value > 0) { - text += $" \n<size=75%>[AlignmentShift {alignment.Direction} by {alignment.Value} - {alignment.Description}]"; - } - __instance.DialogPhrase.text += text; - } - catch (Exception ex) { - Mod.Error(ex); - } - } - } - - [HarmonyPatch(typeof(KingdomUIEventWindow), nameof(KingdomUIEventWindow.SetHeader))] - private static class KingdomUIEventWindow_SetHeader_Patch { - private static void Postfix(KingdomUIEventWindow __instance, KingdomEventUIView kingdomEventView) { - try { - if (!Main.Enabled) return; - if (!Main.Settings.previewEventResults) return; - if (kingdomEventView.Task == null || kingdomEventView.Task.Event == null) { - return; //Task is null on event results; - } - //Calculate results for current leader - var solutionText = Traverse.Create(__instance).Field("m_Description").GetValue<TextMeshProUGUI>(); - solutionText.text += "\n"; - var leader = kingdomEventView.Task.AssignedLeader; - if (leader == null) { - solutionText.text += "<size=75%>Select a leader to preview results</size>"; - return; - } - var blueprint = kingdomEventView.Blueprint; - var solutions = blueprint.Solutions; - var resolutions = solutions.GetResolutions(leader.Type); - solutionText.text += "<size=75%>"; -#if false - TODO - does this matter in WoTR? It seems like the ability to get alignment or name is gone from LeaderState - var leaderAlignmentMask = leader.LeaderSelection.Alignment.ToMask(); - bool isValid(EventResult result) => (leaderAlignmentMask & result.LeaderAlignment) != AlignmentMaskType.None; - var validResults = resolutions.Where(isValid); - solutionText.text += "Leader " + leader.LeaderSelection.CharacterName + " - Alignment " + leaderAlignmentMask + "\n"; - foreach (var eventResult in validResults) { - solutionText.text += FormatResult(kingdomEventView.Task.Event, eventResult.Margin, leaderAlignmentMask, leader.Type); - } -#endif - //Calculate best result - var bestResult = 0; - KingdomStats.Changes bestEventResult = null; - LeaderType bestLeader = 0; - AlignmentMaskType bestAlignment = 0; - foreach (var solution in solutions.Entries) { - if (!solution.CanBeSolved) continue; - foreach (var alignmentMask in solution.Resolutions.Select(s => s.LeaderAlignment).Distinct()) { - var eventResult = CalculateEventResult(kingdomEventView.Task.Event, EventResult.MarginType.GreatSuccess, alignmentMask, solution.Leader); - var sum = 0; - for (var i = 0; i < 10; i++) sum += eventResult[(KingdomStats.Type)i]; - if (sum > bestResult) { - bestResult = sum; - bestLeader = solution.Leader; - bestEventResult = eventResult; - bestAlignment = alignmentMask; - } - } - } - - if (bestEventResult != null) { - solutionText.text += "<size=50%>\n<size=75%>"; - solutionText.text += "Best Result: Leader " + bestLeader + " - Alignment " + bestAlignment + "\n"; -#if false - if (bestLeader == leader.Type && (leaderAlignmentMask & bestAlignment) != AlignmentMaskType.None) { - solutionText.text += "<color=#308014>"; - } - else { - solutionText.text += "<color=#808080>"; - } -#else - solutionText.text += "<color=#808080>"; -#endif - solutionText.text += FormatResult(kingdomEventView.Task.Event, EventResult.MarginType.GreatSuccess, bestAlignment, bestLeader); - solutionText.text += "</color>"; - } - solutionText.text += "</size>"; - } - - catch (Exception ex) { - Mod.Error(ex); - } - } - } - - [HarmonyPatch(typeof(GlobalMapRandomEncounterController), nameof(GlobalMapRandomEncounterController.OnRandomEncounterStarted))] - private static class GlobalMapRandomEncounterController_OnRandomEncounterStarted_Patch { - private static AccessTools.FieldRef<GlobalMapRandomEncounterController, TextMeshProUGUI> m_DescriptionRef; - - private static bool Prepare() { - m_DescriptionRef = Accessors.CreateFieldRef<GlobalMapRandomEncounterController, TextMeshProUGUI>("m_Description"); - return true; - } - - private static void Postfix(GlobalMapRandomEncounterController __instance, ref CombatRandomEncounterData encounter) { - try { - if (!Main.Enabled) return; - if (Main.Settings.previewRandomEncounters) { - var blueprint = encounter.Blueprint; - var text = $"\n<size=70%>Name: {blueprint.Name}\nType: {blueprint.Type}\nCR: {encounter.Blueprint.AvoidDC}</size>"; - m_DescriptionRef(__instance).text += text; - } - } - catch (Exception ex) { - Mod.Error(ex); - } - } - } - - [HarmonyPatch(typeof(KingdomUIEventWindowFooterSolution), nameof(KingdomUIEventWindowFooterSolution.Initialize))] - private static class KingdomUIEventWindowFooter_Initialize_Patch { - private static bool Prefix(KingdomUIEventWindowFooterSolution __instance, - EventSolution eventSolution, - UnityAction<bool> onToggle, - ToggleGroup toggleGroup, - bool isOn = false) { - - bool showResults = settings.previewDialogResults || settings.previewEventResults; - if (!showResults && !settings.toggleIgnoreEventSolutionRestrictions) return true; - bool showRestrictions = (settings.toggleIgnoreEventSolutionRestrictions || settings.previewDialogResults) && !settings.toggleHideEventSolutionRestrictionsPreview; - __instance.gameObject.SetActive(true); - __instance.EventSolution = eventSolution; - __instance.Toggle.group = toggleGroup; - __instance.Toggle.onValueChanged.RemoveAllListeners(); - __instance.Toggle.onValueChanged.AddListener(onToggle); - var extraText = ""; - var isAvail = eventSolution.IsAvail || settings.toggleIgnoreEventSolutionRestrictions; - var color = eventSolution.IsAvail ? "#005800><b>" : "#800000>"; - if (showResults || showRestrictions) { - if (eventSolution.m_AvailConditions.HasConditions && showRestrictions) { - if (settings.toggleIgnoreEventSolutionRestrictions && !eventSolution.IsAvail) - extraText += $"\n<color=#005800><b>[Overridden]:</b></color> "; - else - extraText += $"\n"; - extraText += $"<color={color}[{string.Join(", ", eventSolution.m_AvailConditions.Conditions.Select(c => c.GetCaption())).MergeSpaces(true)}]</b></color>"; - } - if (eventSolution.m_SuccessEffects.Actions.Length > 0 && showResults) - extraText += $"\n[{string.Join(", ", eventSolution.m_SuccessEffects.Actions.Select(c => c.GetCaption())).MergeSpaces(true)}]"; - } - if (isAvail) { - if (extraText.Length > 0) - __instance.m_TextLabel.text = $"{eventSolution.SolutionText}<size=75%>{extraText}</size>"; - else - __instance.m_TextLabel.text = (string)eventSolution.SolutionText; - } - else if (eventSolution.UnavailingBehaviour == UnavailingBehaviour.ShowPlaceholder) { - if (extraText.Length > 0) - __instance.m_TextLabel.text = $"{eventSolution.UnavailingPlaceholder}<size=75%>{extraText}</size>"; - else - __instance.m_TextLabel.text = (string)eventSolution.UnavailingPlaceholder; - } - else - __instance.gameObject.SetActive(false); - __instance.Toggle.interactable = isAvail; - __instance.Toggle.isOn = isOn; - __instance.m_Disposable?.Dispose(); - __instance.m_Disposable = __instance.m_TextLabel.SetLinkTooltip(); - return false; - } - } - public static string PreviewText(this EventSolution solution) { - var result = ""; - result += solution.SolutionText.ToString(); // add main solution string - if (solution.m_SuccessEffects.Actions.Length > 0 - && solution.m_SuccessEffects.Actions.FindOrDefault(act => act is AddItemToPlayer) is AddItemToPlayer addIntermediateItemAct) { - var item = addIntermediateItemAct.ItemToGive; - if (item != null) { - var pattern = @"<link=([^>]*)>"; - var matches = Regex.Matches(result, pattern); - if (matches.Cast<Match>().FirstOrDefault()? - .Groups.Cast<Group>().FirstOrDefault()? - .Captures.Cast<Capture>().FirstOrDefault()? - .Value is string link) { - result += $" {link}[{item.Name.RarityInGame(item.Rarity())}]</link>".sizePercent(75); - } - else - result += $" [{item.Name.RarityInGame(item.Rarity())}]".sizePercent(75); - } - } - return result; - } - [HarmonyPatch(typeof(KingdomUIEventWindow), nameof(KingdomUIEventWindow.SetDescription))] - private static class KingdomUIEventWindow_SetDescription_Patch { - private static bool Prefix(KingdomUIEventWindow __instance, KingdomEventUIView kingdomEventView) { - var blueprint = kingdomEventView.Blueprint; - __instance.m_Description.text = blueprint.LocalizedDescription; - __instance.m_Disposables.Add(__instance.m_Description.SetLinkTooltip(null, null, default)); - var isActive = kingdomEventView.IsCrusadeEvent && kingdomEventView.IsFinished; - __instance.m_ResultDescription.gameObject.SetActive(isActive); - if (isActive) { - var currentEventSolution = __instance.m_Footer.CurrentEventSolution; - if (currentEventSolution?.ResultText != null) { - var resultDescription = __instance.m_ResultDescription; - var currentEventSolution2 = __instance.m_Footer.CurrentEventSolution; - resultDescription.text = ((currentEventSolution2 != null) ? currentEventSolution2.ResultText : null); - __instance.m_Disposables.Add(__instance.m_ResultDescription.SetLinkTooltip(null, null, default)); - } - else - __instance.m_ResultDescription.text = string.Empty; - } - var blueprintKingdomProject = blueprint as BlueprintKingdomProject; - string mechanicalDescription = ((blueprintKingdomProject != null) ? blueprintKingdomProject.MechanicalDescription : null); - if (settings.previewDecreeResults) { - var eventResults = blueprintKingdomProject.Solutions.GetResolutions(blueprintKingdomProject.DefaultResolutionType); - if (eventResults != null) { - foreach (var result in eventResults) { - if (result.Actions != null && result.Actions.Actions.Length > 0) - mechanicalDescription += $"\n<size=67%><b>Results Preview </b>\n{string.Join("\n", result.Actions.Actions.Select(c => c.GetCaption())).MergeSpaces(true)}</size>"; - } - } - } - // RelicInfo code borrowed (with permission from Rathtr) from https://www.nexusmods.com/pathfinderwrathoftherighteous/mods/200 - var projectType = kingdomEventView.ProjectBlueprint.ProjectType; - if (settings.previewRelicResults - //&& projectType == KingdomProjectType.Relics - && blueprint.ElementsArray.Find(elem => elem is KingdomActionStartEvent) is KingdomActionStartEvent e - && e.Event is BlueprintCrusadeEvent crusadeEvent - ) { - // this relic event starts another event on completion - var allRelicProjects = // todo: cache this? - from ev in Game.Instance.BlueprintRoot.Kingdom.KingdomProjectEvents - where ev.Get().ProjectType == KingdomProjectType.Relics - select ev.Get(); - - BlueprintCrusadeEvent subEvent = null; - foreach (var relicProject in allRelicProjects) { - if (relicProject.TriggerCondition.HasConditions && - relicProject.TriggerCondition.Conditions.First() is FlagUnlocked unlockedFlag) { - var conditionFlag = unlockedFlag.ConditionFlag; - if (blueprint.ElementsArray.Exists(elem - => elem is KingdomActionStartEvent actE - && actE.Event.ElementsArray.Exists(elem2 - => elem2 is UnlockFlag unlockFlag - && unlockFlag.flag == conditionFlag - ) - ) - && relicProject.ElementsArray.Find(elem => elem is KingdomActionStartEvent) is KingdomActionStartEvent subActionEvent - && subActionEvent.Event is BlueprintCrusadeEvent subBp - && subBp.EventSolutions.Length >= 1 - && (subEvent = subBp) != null) { - break; - } - } - } - var solStrings = "\n\n"; - for (var i = 0; i < crusadeEvent.EventSolutions.Length; ++i) { - var sol = crusadeEvent.EventSolutions[i]; - if (sol.IsAvail) - solStrings += sol.PreviewText(); - if (subEvent != null) { - var finalSols = from subEventSol in subEvent.EventSolutions - where subEventSol.m_AvailConditions.HasConditions && subEventSol.m_AvailConditions.Conditions[0] is FlagUnlocked flagUnlockCond && sol.m_SuccessEffects.Actions.ToList().Find(act => act is UnlockFlag) is UnlockFlag intermediateFlagUnlocked && intermediateFlagUnlocked.flag == flagUnlockCond.ConditionFlag - select subEventSol; - if (finalSols.Count() > 1) - solStrings += " leads to\n".sizePercent(75); - foreach (var endSolution in finalSols) { - solStrings += $" {endSolution.PreviewText()}\n"; - } - } - else - solStrings += "\n"; - } - __instance.m_Description.text += solStrings.sizePercent(87); - } - __instance.m_Disposables.Add(__instance.m_Description.SetLinkTooltip()); - __instance.m_ResultDescription.gameObject.SetActive(isActive); - if (isActive && __instance.m_Footer.CurrentEventSolution?.ResultText != null) { - __instance.m_ResultDescription.text = (string)__instance.m_Footer.CurrentEventSolution?.ResultText; - __instance.m_Disposables.Add(__instance.m_ResultDescription.SetLinkTooltip()); - } - else - __instance.m_ResultDescription.text = string.Empty; - - __instance.m_MechanicalDescription.text = mechanicalDescription; - __instance.m_MechanicalDescription.gameObject.SetActive((blueprintKingdomProject?.MechanicalDescription) != null); - __instance.m_Disposables.Add(__instance.m_MechanicalDescription.SetLinkTooltip(null, null, default)); - - return false; - } - } - - [HarmonyPatch(typeof(KingdomUIEventWindow), nameof(KingdomUIEventWindow.OnClose))] - private static class KingdomUIEventWindow_OnClose_Patch { - private static void Prefix(KingdomUIEventWindow __instance) { - __instance.m_MechanicalDescription.text = null; - } - } - - [HarmonyPatch(typeof(DialogAnswerView), nameof(DialogAnswerView.SetAnswer))] - private static class DialogAnswerView_SetAnswer_Patch { - private static bool Prefix(DialogAnswerView __instance, BlueprintAnswer answer) { - if (!settings.previewDialogResults && !settings.toggleShowAnswersForEachConditionalResponse && !settings.toggleMakePreviousAnswersMoreClear) return true; - var type = Game.Instance.DialogController.Dialog.Type; - var str = string.Format("DialogChoice{0}", (object)__instance.ViewModel.Index); - var text = UIConsts.GetAnswerString(answer, str, __instance.ViewModel.Index); - var isAvail = answer.CanSelect(); - if (answer.NextCue.Cues.Count == 1) { - var cue = answer.NextCue.Cues.Dereference<BlueprintCueBase>().FirstOrDefault(); - var conditionText = $"{string.Join(", ", cue.Conditions.Conditions.Select(c => c.GetCaption()))}"; - // the following is a kludge for toggleShowAnswersForEachConditionalResponse to work around cases where there may be a next cue that doesn't get shown due it being already seen and the dialog being intended to fall through. We assume that any singleton conditional nextCue (CueSelection) was generated by this feature. We should look for edge cases to be sure. - isAvail = isAvail && (cue.CanShow() || !settings.toggleShowAnswersForEachConditionalResponse || conditionText.Length == 0); - var color = isAvail ? "#005800><b>" : "#800000>"; - if (conditionText.Length > 0) - text += $"<size=75%><color={color}[{conditionText.MergeSpaces(true)}]</color></size>"; - } - __instance.AnswerText.text = text; - __instance.ViewModel.Enable.Value = (answer.CanSelect() && isAvail); - var color32 = isAvail ? DialogAnswerView.Colors.NormalAnswer : DialogAnswerView.Colors.DisabledAnswer; - if (type == DialogType.Common && answer.IsAlreadySelected() && (Game.Instance.DialogController.NextCueWasShown(answer) || !Game.Instance.DialogController.NextCueHasNewAnswers(answer))) { - color32 = DialogAnswerView.Colors.SelectedAnswer; - __instance.AnswerText.alpha = 0.45f; - if (settings.toggleMakePreviousAnswersMoreClear) - __instance.AnswerText.text = text.sizePercent(83); - } - else - __instance.AnswerText.alpha = 1.0f; - __instance.AnswerText.color = (Color)color32; - __instance.AddDisposable(Game.Instance.Keyboard.Bind(str, new Action(__instance.Confirm))); - if (__instance.ViewModel.Index != 1 || type != DialogType.Interchapter && type != DialogType.Epilogue) - return false; - __instance.AddDisposable(Game.Instance.Keyboard.Bind("NextOrEnd", new Action(__instance.Confirm))); - return false; - } -#if false - private void SetAnswer(BlueprintAnswer answer) { - DialogType type = Game.Instance.DialogController.Dialog.Type; - string str = string.Format("DialogChoice{0}", (object)this.ViewModel.Index); - this.AnswerText.text = UIConsts.GetAnswerString(answer, str, this.ViewModel.Index); - Color32 color32 = answer.CanSelect() ? DialogAnswerView.Colors.NormalAnswer : DialogAnswerView.Colors.DisabledAnswer; - if (type == DialogType.Common && answer.IsAlreadySelected() && (Game.Instance.DialogController.NextCueWasShown(answer) || !Game.Instance.DialogController.NextCueHasNewAnswers(answer))) - color32 = DialogAnswerView.Colors.SelectedAnswer; - - this.AnswerText.color = (Color)color32; - this.AddDisposable(Game.Instance.Keyboard.Bind(str, new Action(this.Confirm))); - if (this.ViewModel.Index != 1 || type != DialogType.Interchapter && type != DialogType.Epilogue) - return; - this.AddDisposable(Game.Instance.Keyboard.Bind("NextOrEnd", new Action(this.Confirm))); - } -#endif - } - } -} diff --git a/ToyBox/classes/MonkeyPatchin/Vendor/BulkSellLogic.cs b/ToyBox/classes/MonkeyPatchin/Vendor/BulkSellLogic.cs deleted file mode 100644 index 86709c68e..000000000 --- a/ToyBox/classes/MonkeyPatchin/Vendor/BulkSellLogic.cs +++ /dev/null @@ -1,331 +0,0 @@ -using HarmonyLib; -using Kingmaker; -using Kingmaker.Blueprints.Items.Ecnchantments; -using Kingmaker.Blueprints.Items.Equipment; -using Kingmaker.Blueprints.Root; -using Kingmaker.Craft; -using Kingmaker.Designers.Mechanics.EquipmentEnchants; -using Kingmaker.Designers.Mechanics.Facts; -using Kingmaker.Designers; -using Kingmaker.Enums.Damage; -using Kingmaker.Enums; -using Kingmaker.Items; -using Kingmaker.UI.MVVM._VM.Vendor; -using System; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; -using Kingmaker.Blueprints; -using Kingmaker.Blueprints.Items.Weapons; -using Kingmaker.UnitLogic.FactLogic; -using Kingmaker.EntitySystem.Stats; -using ToyBox.classes.Models; - -namespace ToyBox { - - static class BulkSellLogic { - - struct CountArgs { - public int itemInventoryCount; - public int itemStackCount; - public BulkSellSettings settings; - } - - public static int canBulkSellCount(ItemEntity item) { - if (item == null || item.Blueprint.IsNotable) { - return 0; - } - - var args = new CountArgs { - itemInventoryCount = Game.Instance.Player.Inventory.Count(item.Blueprint), - itemStackCount = item.Count, - settings = Main.Settings.bulkSellSettings - }; - - var entityToSell = item switch { - ItemEntityWeapon weapon => ShouldSellHowManyOfThisWeapon(weapon, args), - ItemEntityArmor armor => ShouldSellHowManyOfThisArmor(armor, args), - ItemEntityShield shield => ShouldSellHowManyOfThisShield(shield, args), - ItemEntityUsable usable => ShouldSellHowManyOfThisConsumable(usable, args), - _ => 0 - }; - - if (entityToSell > 0) { - return entityToSell; - } - - var blueprintToSell = item.Blueprint switch { - BlueprintIngredient _ => ShouldSellHowManyOfThisIngredient(args), - BlueprintItemEquipmentBelt belt => ShouldSellHowManyOfThisBelt(belt, args), - BlueprintItemEquipmentHead hat => ShouldSellHowManyOfThisHat(hat, args), - BlueprintItemEquipmentRing ring => ShouldSellHowManyOfThisRing(ring, args), - BlueprintItemEquipmentShoulders cloak => ShouldSellHowManyOfThisCloak(cloak, args), - BlueprintItemEquipmentWrist bracer => ShouldSellHowManyOfThisBracer(bracer, args), - BlueprintItemEquipmentNeck necklace => ShouldSellHowManyOfThisNecklace(necklace, args), - _ => 0 - }; - - return blueprintToSell; - } - - private static int ShouldSellHowManyOfThisWeapon(ItemEntityWeapon weapon, CountArgs args) { - - bool shouldSellMasterwork = - Game.Instance.Player.UISettings.OptionsDictionary[VendorHelper.SaleOptions.MasterWork]; - - - if (!shouldSellMasterwork && weapon.Blueprint.IsMasterwork) - return 0; - - string defName = weapon.Blueprint.DefaultNonIdentifiedName ?? ""; - string preEncNames = weapon.Blueprint.GetEnchantmentPrefixes() ?? ""; - string postEncNames = weapon.Blueprint.GetEnchantmentSuffixes() ?? ""; - string materialName = weapon.Blueprint.DamageType.Physical.Material == 0 - ? "" - : LocalizedTexts.Instance.DamageMaterial.GetText(weapon.Blueprint.DamageType.Physical.Material) ?? ""; - - var hasUniqueName = !weapon.Name.Contains(defName) || !weapon.Name.Contains(preEncNames) || - !weapon.Name.Contains(postEncNames) || !weapon.Name.Contains(materialName); - - if (!args.settings.sellUniqueWeapons && hasUniqueName) { - return 0; - } - - - var nonPhysicalDamageMatchesSettings = weapon.Blueprint?.Enchantments?.All(e => { - var energy = e.GetComponent<WeaponEnergyDamageDice>(); - var reality = e.GetComponent<WeaponReality>(); - var alignment = e.GetComponent<WeaponAlignment>(); - var matchesEnergy = energy == null || args.settings.damageEnergy[energy.Element]; - var matchesReality = reality == null || args.settings.damageReality[reality.Reality]; - var matchesAlignment = alignment == null || args.settings.damageAlignment[alignment.Alignment]; - return matchesEnergy && matchesReality && matchesAlignment; - }) ?? true; - - var physicalDamage = Enum.GetValues(typeof(PhysicalDamageMaterial)) as IEnumerable<PhysicalDamageMaterial>; - - var physicalDamageMatchesSettings = physicalDamage.All(type => { - var weaponTypeNumeric = (uint)weapon.Blueprint.DamageType.Physical.Material; - var currentTypeNumeric = (uint)type; - //This may be "untyped" or just regular physical damage. Either way, we want to ignore values outside of the enum - //This line may never be executed, but it's there in the original mod logic, so I included it - if ((weaponTypeNumeric & currentTypeNumeric) == 0u) { - return true; - } - - return args.settings.damageMaterial[type]; - }); - - var enhancementBonus = GameHelper.GetWeaponEnhancementBonus(weapon.Blueprint); - - var allTypesMatch = nonPhysicalDamageMatchesSettings && physicalDamageMatchesSettings; - var enhancementBonusIsSellable = enhancementBonus <= args.settings.weaponEnchantLevel; - var stackSizeIsBiggerThanMinimum = args.itemInventoryCount > args.settings.weaponStackSize; - - if (allTypesMatch && enhancementBonusIsSellable && stackSizeIsBiggerThanMinimum) { - return Mathf.Min(args.itemInventoryCount - args.settings.weaponStackSize, args.itemStackCount); - } - - return 0; - } - - private static int ShouldSellHowManyOfThisArmor(ItemEntityArmor armor, CountArgs args) { - var hasUniqueName = !armor.Name.Contains("+"); - - if (!args.settings.sellUniqueArmors && hasUniqueName) { - return 0; - } - - var enhancementBonus = GameHelper.GetArmorEnhancementBonus(armor.Blueprint); - var enhancementBonusIsSellable = enhancementBonus <= (args.settings.armorEnchantLevel); - var stackSizeIsBiggerThanMinimum = args.itemInventoryCount > args.settings.armorStackSize; - - if (enhancementBonusIsSellable && stackSizeIsBiggerThanMinimum) { - return Mathf.Min(args.itemInventoryCount - args.settings.armorStackSize, args.itemStackCount); - } - - return 0; - } - - private static int ShouldSellHowManyOfThisShield(ItemEntityShield shield, CountArgs args) { - var hasUniqueName = !shield.Name.Contains("+"); - - if (!args.settings.sellUniqueShields && hasUniqueName) { - return 0; - } - - var enhancementBonus = GameHelper.GetArmorEnhancementBonus(shield.ArmorComponent.Blueprint); - var enhancementBonusIsSellable = enhancementBonus <= args.settings.shieldEnchantLevel; - var stackSizeIsBiggerThanMinimum = args.itemInventoryCount > args.settings.shieldStackSize; - if (enhancementBonusIsSellable && stackSizeIsBiggerThanMinimum) - return Mathf.Min(args.itemInventoryCount - Main.Settings.bulkSellSettings.shieldStackSize, args.itemStackCount); - - return 0; - } - - private static int ShouldSellHowManyOfThisConsumable(ItemEntityUsable usable, CountArgs args) { - var shouldSellScrolls = args.itemInventoryCount > args.settings.scrollStackSize && args.settings.sellScrolls; - var scrollsToSell = Mathf.Min(args.itemInventoryCount - Main.Settings.bulkSellSettings.scrollStackSize, args.itemStackCount); - var shouldSellPotions = args.itemInventoryCount > args.settings.potionStackSize && args.settings.sellPotions; - var potionsToSell = Mathf.Min(args.itemInventoryCount - Main.Settings.bulkSellSettings.potionStackSize, args.itemStackCount); - - return usable.Blueprint.Type switch { - UsableItemType.Scroll => shouldSellScrolls ? scrollsToSell : 0, - UsableItemType.Potion => shouldSellPotions ? potionsToSell : 0, - _ => 0 - }; - } - - private static int ShouldSellHowManyOfThisIngredient(CountArgs args) { - if (args.itemInventoryCount > args.settings.ingredientStackSize && args.settings.sellIngredients) - return Mathf.Min(args.itemInventoryCount - args.settings.ingredientStackSize, args.itemStackCount); - - return 0; - } - - private static int ShouldSellHowManyOfThisBelt(BlueprintItemEquipmentBelt belt, CountArgs args) { - if (belt.Enchantments.Count > 0 && belt.Enchantments.TrueForAll(e => - e.GetComponent<AddStatBonusEquipment>() is AddStatBonusEquipment sb && - isAttribute(sb.Stat) && sb.Descriptor == ModifierDescriptor.Enhancement && - args.settings.maxAttributeBonusForBelt >= sb.Value)) { - return Mathf.Min(args.itemInventoryCount - args.settings.beltStackSize, args.itemStackCount); - } - return 0; - } - - private static int ShouldSellHowManyOfThisHat(BlueprintItemEquipmentHead hat, CountArgs args) { - if (hat.Enchantments.Count == 1 && - hat.Enchantments.TrueForAll(e => - e.GetComponent<AddStatBonusEquipment>() is AddStatBonusEquipment sb && - isAttribute(sb.Stat) && sb.Descriptor == ModifierDescriptor.Enhancement && - args.settings.maxAttributeBonusForHead >= sb.Value)) { - return Mathf.Min(args.itemInventoryCount - args.settings.headStackSize, args.itemStackCount); - } - return 0; - } - - private static int ShouldSellHowManyOfThisRing(BlueprintItemEquipmentRing ring, CountArgs args) { - if (ring.Enchantments.Count == 1 && - ring.Enchantments.TrueForAll(e => - e.GetComponent<AddStatBonusEquipment>() is AddStatBonusEquipment sb && - sb.Descriptor == ModifierDescriptor.Deflection && isAC(sb.Stat) && - args.settings.maxACBonusForRings >= sb.Value)) { - return Mathf.Min(args.itemInventoryCount - args.settings.ringStackSize, args.itemStackCount); - } - return 0; - } - - private static int ShouldSellHowManyOfThisCloak(BlueprintItemEquipmentShoulders cloak, CountArgs args) { - if (cloak.Enchantments.Count == 1 && - cloak.Enchantments.TrueForAll(e => - e.GetComponent<AllSavesBonusEquipment>() is AllSavesBonusEquipment sb && - sb.Descriptor == ModifierDescriptor.Resistance && - args.settings.maxSaveBonusForCloaks >= sb.Value)) { - return Mathf.Min(args.itemInventoryCount - args.settings.cloakStackSize, args.itemStackCount); - } - return 0; - } - - private static int ShouldSellHowManyOfThisBracer(BlueprintItemEquipmentWrist bracer, CountArgs args) { - if (isBracersOfArmor(bracer, args.settings.maxACBonusForBracers) || isBracersOfLesserArchery(bracer, args.settings.maxACBonusForBracers)) { - return Mathf.Min(args.itemInventoryCount - args.settings.bracerStackSize, args.itemStackCount); - - } - return 0; - } - - private static int ShouldSellHowManyOfThisNecklace(BlueprintItemEquipmentNeck necklace, CountArgs args) { - if ((necklace.Enchantments.Count == 1 && - necklace.Enchantments.TrueForAll(e => - isNaturalArmorEnc(e, args.settings.maxACBonusForNeck) || - isMightyFistsEnc(e, args.settings.maxACBonusForNeck))) || - isAgileFistsEnc(necklace, args.settings.maxACBonusForNeck)) { - return Mathf.Min(args.itemInventoryCount - args.settings.neckStackSize, args.itemStackCount); - } - return 0; - } - - static bool isBracersOfLesserArchery(BlueprintItemEquipmentWrist wrist, int maxEncLevel) { - return wrist.Enchantments.Count == 1 && - (wrist.Enchantments.TrueForAll(e => - (e.GetComponent<AddUnitFeatureEquipment>() is AddUnitFeatureEquipment uf && - uf.Feature.ComponentsArray.Length == 2 && - uf.Feature.ComponentsArray.All(comp => - (comp is WeaponGroupAttackBonus ab && - ab.AttackBonus <= maxEncLevel && ab.WeaponGroup == WeaponFighterGroup.Bows) || - (comp is AddFacts facts))))); // probably not necessary to check the facts... - } - - static bool isBracersOfArmor(BlueprintItemEquipmentWrist wrist, int maxEncLevel) { - return wrist.Enchantments.Count == 2 && - (wrist.Enchantments.TrueForAll(e => - (e.GetComponent<AddStatBonusEquipment>() is AddStatBonusEquipment sb && - sb.Descriptor == ModifierDescriptor.Armor && isAC(sb.Stat) && maxEncLevel >= sb.Value) || - (e.GetComponent<AddUnitFeatureEquipment>() is AddUnitFeatureEquipment uf && uf.Feature.GetComponent<ACBonusAgainstWeaponType>() != null))); - } - - static bool isAgileFistsEnc(BlueprintItemEquipmentNeck bp, int maxEncLevel) { - return (bp.Enchantments.Count == 2 && - bp.Enchantments.TrueForAll(encBp => - (encBp.GetComponent<EquipmentWeaponTypeDamageStatReplacement>() is EquipmentWeaponTypeDamageStatReplacement enc && - enc.AllNaturalAndUnarmed && enc.Stat == StatType.Dexterity && enc.RequiresFinesse && - enc.Category == WeaponCategory.UnarmedStrike) || - (encBp.GetComponent<EquipmentWeaponTypeEnhancement>() is EquipmentWeaponTypeEnhancement enc2 && - enc2.AllNaturalAndUnarmed && enc2.Category == WeaponCategory.UnarmedStrike && maxEncLevel >= enc2.Enhancement))); - } - - static bool isNaturalArmorEnc(BlueprintItemEnchantment bp, int maxEncLevel) { - return bp.GetComponent<AddStatBonusEquipment>() is AddStatBonusEquipment enc && - enc.Descriptor == ModifierDescriptor.NaturalArmorEnhancement && maxEncLevel >= enc.Value; - } - - static bool isMightyFistsEnc(BlueprintItemEnchantment bp, int maxEncLevel) { - return bp.GetComponent<EquipmentWeaponTypeEnhancement>() is EquipmentWeaponTypeEnhancement enc && - enc.AllNaturalAndUnarmed && enc.Category == WeaponCategory.UnarmedStrike && - maxEncLevel >= enc.Enhancement; - } - - static bool isAttribute(StatType stat) { - return - stat == StatType.Strength || - stat == StatType.Dexterity || - stat == StatType.Constitution || - stat == StatType.Intelligence || - stat == StatType.Wisdom || - stat == StatType.Charisma; - } - - static bool isAC(StatType stat) { - return stat == StatType.AC; - } - } - - [HarmonyPatch(typeof(VendorVM), nameof(VendorVM.TryMassSale))] - internal class VendorVM_TryMassSalePatch { - static bool Prefix(ref bool __result) { - if (!Main.Settings.toggleCustomBulkSell) return true; - - var list = Game.Instance.Player.Inventory - .Select(item => new { - item, - vanillaMassSell = VendorHelper.IsAppropriateForMassSelling(item), - numberToSell = BulkSellLogic.canBulkSellCount(item) - }) - .Where(item => item.vanillaMassSell || item.numberToSell > 0) - .Select(item => (new { item.item, quantity = item.vanillaMassSell ? -1 : item.numberToSell })) - .ToList(); - - if (list.Count <= 0) { - __result = false; - } - - list.ForEach(item => { - Game.Instance.Vendor.AddForSell(item.item, item.quantity); - }); - - __result = true; - - return false; - } - } -} diff --git a/ToyBox/zip-hash-sign.ps1 b/ToyBox/zip-hash-sign.ps1 deleted file mode 100644 index 76005ab49..000000000 --- a/ToyBox/zip-hash-sign.ps1 +++ /dev/null @@ -1,7 +0,0 @@ -#New-Item -ItemType Directory -Force -Path Final - -$appPath = (get-item $pwd).parent.parent.FullName -$fileName = "0ToyBox0.zip" -$fullName = $pwd.Path + "\..\" + $fileName -Compress-Archive -Path '*' -DestinationPath $fullName -Force -#Compress-Archive -Path '*.dll', '*.json', '*.pdb' -DestinationPath $fullName -Force \ No newline at end of file diff --git a/ToyBox/zmisc/api.txt b/ToyBox/zmisc/api.txt deleted file mode 100644 index 2215b7749..000000000 --- a/ToyBox/zmisc/api.txt +++ /dev/null @@ -1 +0,0 @@ - dotnet nuget push .\bin\Debug\ModKit.1.0.8.nupkg --api-key <key> --source https://api.nuget.org/v3/index.json \ No newline at end of file diff --git a/art-promo/BagOfTricks.jpg b/art-promo/BagOfTricks.jpg new file mode 100644 index 000000000..86f66fef2 Binary files /dev/null and b/art-promo/BagOfTricks.jpg differ diff --git a/lib/Assembly-CSharp_public.dll b/art-promo/Sh0dan-Imperium.png similarity index 56% rename from lib/Assembly-CSharp_public.dll rename to art-promo/Sh0dan-Imperium.png index 48fffd64b..f42130ca3 100644 Binary files a/lib/Assembly-CSharp_public.dll and b/art-promo/Sh0dan-Imperium.png differ diff --git a/art-promo/Sh0dan.png b/art-promo/Sh0dan.png new file mode 100644 index 000000000..9113fc6ea Binary files /dev/null and b/art-promo/Sh0dan.png differ diff --git a/art-promo/ToyBox-banner.jpg b/art-promo/ToyBox-banner.jpg new file mode 100644 index 000000000..0dfa866a2 Binary files /dev/null and b/art-promo/ToyBox-banner.jpg differ diff --git a/art-promo/ToyBox-banner.pdn b/art-promo/ToyBox-banner.pdn new file mode 100644 index 000000000..5b5cf11d6 Binary files /dev/null and b/art-promo/ToyBox-banner.pdn differ diff --git a/art-promo/ToyBoxRT.jpg b/art-promo/ToyBoxRT.jpg new file mode 100644 index 000000000..de6569968 Binary files /dev/null and b/art-promo/ToyBoxRT.jpg differ diff --git a/art-promo/ToyBoxRT.png b/art-promo/ToyBoxRT.png new file mode 100644 index 000000000..11f6c1407 Binary files /dev/null and b/art-promo/ToyBoxRT.png differ diff --git a/art-promo/image.png b/art-promo/image.png new file mode 100644 index 000000000..33d8c1444 Binary files /dev/null and b/art-promo/image.png differ diff --git a/lib/Assembly-CSharp_public.hash b/lib/Assembly-CSharp_public.hash deleted file mode 100644 index 1396284d8..000000000 --- a/lib/Assembly-CSharp_public.hash +++ /dev/null @@ -1 +0,0 @@ -F3EE22550CEDA2464BA8EB30B8F367319332B9FA \ No newline at end of file diff --git a/lib/Owlcat.Runtime.UI_public.dll b/lib/Owlcat.Runtime.UI_public.dll deleted file mode 100644 index 950377c1c..000000000 Binary files a/lib/Owlcat.Runtime.UI_public.dll and /dev/null differ diff --git a/lib/Owlcat.Runtime.UI_public.hash b/lib/Owlcat.Runtime.UI_public.hash deleted file mode 100644 index f78651d6c..000000000 --- a/lib/Owlcat.Runtime.UI_public.hash +++ /dev/null @@ -1 +0,0 @@ -28F9798C53274B4869CD774BB67206F85460BFA4 \ No newline at end of file diff --git a/lib/UnityModManager_public.dll b/lib/UnityModManager_public.dll deleted file mode 100644 index 94795f9df..000000000 Binary files a/lib/UnityModManager_public.dll and /dev/null differ diff --git a/lib/UnityModManager_public.hash b/lib/UnityModManager_public.hash deleted file mode 100644 index b7d254957..000000000 --- a/lib/UnityModManager_public.hash +++ /dev/null @@ -1 +0,0 @@ -89B6456169589730C0203ED57319CD32AE787B02 \ No newline at end of file diff --git a/readme.md b/readme.md index c8c7f9f5d..c82cb68eb 100644 --- a/readme.md +++ b/readme.md @@ -1,16 +1,47 @@ -# About -Toy Box is a cute and playful mod with 400+ cheats, tweaks and quality of life improvements for Pathfinder: WoTR. +# ToyBox -It was created in the spirit of Bag of Tricks & Cheat Menu but with a little different focus, It offers a powerful and convenient way to edit: -- the party composition -- stats -- search and add to party members: - - Feats - - Features - - Items - - etc. +### Install & Setup (Rogue) -Download: [nexusmods.com](https://www.nexusmods.com/pathfinderwrathoftherighteous/mods/8) +1. Download the ToyBox mod file and unzip +1. If the folder is not already named 0ToyBox0 please rename it to that +1. Launch the game at least once. +1. **Please note that the game comes with its own built in Unity Mod Manager so you do not need to install another one** +1. Navigate to %userprofile%\AppData\LocalLow\Owlcat Games\Warhammer 40000 Rogue Trader\UnityModManager\ +1. An example path is C:\Users\PC\AppData\LocalLow\Owlcat Games\Warhammer 40000 Rogue Trader\UnityModManager\ +1. Copy 0ToyBox0 into the UnityModManagerFolder +1. Launch Rogue Trader and you may need to hit ctrl+F10 to see the mod manager window +1. Load a save or start a new game to get the most out of of the mod + +* Warning: ToyBox for Warhammer 40,000: Rogue Trader is a complex mod. Save early and often. +* Note: If you find non-functioning features, please report them. + +### Usage +Here is a summarized list of features. This list only includes a part of the features contained in the mod. + +- **Bag of Tricks**: + * Allow both male and female RTs to romance Heinrix and Cassia + * Allow Remote Companions to join into dialog + * Modify Faction Reputation, Navigator's Insight, Scrap and more + * Experience Multiplier + * Dice Roll Cheats/Options + * Enable Achievements with Mods installed +- **Level Up**: Make Respec start from Level 0 for you and Companions +- **Party Editor**: Modify your own conviction and/or stats or features or portraits or voices of party members or Respec Characters for free +- **Colonies**: Modify different Colony Resources +- **Search 'n Pick**: this lets you search through various available things (items, feats, abilities, unity and locations) and spawn/give/summon them + and manipulate your game state in an almost limitless set of ways. You can add/remove items, feats, abilities, + etc. You can spawn any unit. You can start/unstart/complete etudes, quests, etc. You can teleport to any area in the + game. It is almost unimaginable how much you can do in here so keep digging! +- **Etudes**: this is a new and exciting feature that allows you to see for the first time the structure and some basic + relationships of Etudes and other Elements that control the progression of your game story. Etudes are hierarchical in + structure and additionally contain a set of Elements that can both conditions to check and actions to execute when the + etude is started. As you browe you will notice there is a disclosure triangle next to the name which will show the + children of the Etude. Etudes that have Elements will offer a second disclosure triangle next to the status that will + show them to you. + WARNING: this tool can both miraculously fix your broken progression or it can break it even further. Save and back up + your save before using. Remember that "with great power comes great responsibility" +- **Quest Resolution**: this allows you to view your active quests and advance them as needed to work around bugs or + skip quests you don't want to do. Be warned this may break your game progression if used carelessly. # How to contribute - Make sure you have Visual Studio 22 (or current) installed and other tools you might want. See [WotR Modding Beginners Guide](https://github.com/WittleWolfie/OwlcatModdingWiki/wiki/Beginner-Guide) for more info @@ -29,12 +60,8 @@ Download: [nexusmods.com](https://www.nexusmods.com/pathfinderwrathoftherighteou # Development Setup 1. Install ToyBox mod into your game via Unity Mod Manager 1. Clone the git repo -1. Locate the install folder of Pathfinder Wrath of the Righteous -1. Go to System Properties > Environment Variables and add WrathPath with a value that looks like this: - `WrathPath` `C:\Program Files (x86)\Steam\steamapps\common\Pathfinder Second Adventure` -1. First time and when the game updates make sure you clean the solution to trigger the publicize step -1. build the solution debug and it will automatically build and install into the mod folder in the game -1. when you rebuild you can go to the mod and hit the reload button at the top to make it use the latest +1. Build the solution twice and restart Visual Studio +1. Now you can just change things and build the solution and it will automatically build and install into the mod folder in the game 1. **Important** If you are adding a feature or fixing a bug please add a release note entry to the *ReadMe.md*. This is how we tell our the world about your great work^_^ 1. Find the highest release name near the top. It will look something like `### Ver 1.3.7 (Coming Soon)`. 1. Usually it is 0.0.1 higher than the current release but please check on Nexus. If not please add a new Version entry at the top for the next release using proper markdown.