Yukitty / CassetteBeasts-modutils

Mod Utils
https://modworkshop.net/mod/42080
MIT License
5 stars 1 forks source link

Error using class patcher #1

Open neopryne opened 8 months ago

neopryne commented 8 months ago

I'm trying to use the class patcher, and I see this error when I run the game. I have modutils and my mod installed, as well as the DLC. I'm trying to patch BestiaryMenu.gd, which is normally autoloaded. Is this a supported use case?

Godot Engine v3.5.1.stable.custom_build.bb8bd433c - https://godotengine.org
OpenGL ES 3.0 Renderer: NVIDIA GeForce RTX 3070 Ti/PCIe/SSE2
Async. shader compilation: OFF

CassetteBeasts (Godot 3.5.1 stable)
Type help to get more information about usage
Loaded user://mods/cat_modutils.pck
Loaded user://mods/compatibility_search.pck
ERROR: Can't call non-static function 'reload' in script.
   at: GDScript::call (modules\gdscript\gdscript.cpp:664) - Condition "!E->get()->is_static()" is true. Returned: Variant()
ERROR: unsupported format character
   at: GDScriptFunctions::call (modules\gdscript\gdscript_functions.cpp:775) - unsupported format character
Device 0 connected: XInput Gamepad __XINPUT_DEVICE__
CassetteBeasts version 1.5.0
neopryne commented 8 months ago

Also, I see that the class patcher isn't consistently applying my changes. It seems to not like large blocks of code with indents.

Or just indents in general. The lines

# PATCH: REPLACE LINES
                _add_list_button(species)
# PATCH: INTO
                if (MONSTER_SEARCH_species_is_compatible(species)):
                    _add_list_button(species)
# PATCH: STOP

gets turned into this

                if (MONSTER_SEARCH_species_is_compatible(species)):
                    if (MONSTER_SEARCH_species_is_compatible(species)):
                    _add_list_button(species)

which copies the first line for some reason, and I don't think is valid code because the second if doesn't have an indent.

It also skips over two ADD LINES HERE blocks containing three functions.

MONSTER_SEARCH_generate_move_array MONSTER_SEARCH_species_is_compatible MONSTER_SEARCH__on_FilterPromptButton_pressed

Original file with patch commands

extends "res://menus/BaseMenu.gd"

const BestiaryListButton = preload("BestiaryListButton.tscn")
const BestiaryListButtonFusion = preload("BestiaryListButtonFusion.tscn")

enum SortMode{SORT_BY_NUMBER, SORT_BY_NAME}
enum ListMode{LIST_MONSTERS, LIST_FUSIONS}

export (bool) var show_list:bool = true
export (Resource) var species:Resource setget set_species
export (SortMode) var sort_mode = SortMode.SORT_BY_NUMBER
export (ListMode) var list_mode = ListMode.LIST_MONSTERS
export (int) var initial_info_page:int = 0 setget set_initial_info_page

onready var species_info_margin = find_node("BestiarySpeciesInfoMargin")
onready var species_info = find_node("BestiarySpeciesInfo")
onready var button_list_container = find_node("ButtonListContainer")
onready var button_list = find_node("ButtonList")
onready var seen_label = find_node("SeenLabel")
onready var obtained_label = find_node("ObtainedLabel")
onready var obtained_percent_label = find_node("ObtainedPercentLabel")
onready var sort_prompt_button = find_node("SortPromptButton")
onready var list_mode_prompt_button = find_node("ListModePromptButton")

var seen_count:int = 0
var seen_max:int = 0
var obtained_count:int = 0
var obtained_max:int = 0
var current_button:BaseButton = null
# PATCH: ADD LINES HERE
var MONSTER_SEARCH_move_filter = ""
var MONSTER_SEARCH_move_array = Array()
var MONSTER_SEARCH_query_tag_array = Array()
# PATCH: STOP

func _ready():
    reload()
    set_initial_info_page(initial_info_page)

    if not show_list:
        $BackButtonPanel.back_text_override = "UI_BUTTON_CLOSE"

func reload():
    var focus_owner = get_focus_owner()
    var had_focus = focus_owner != null and (button_list == focus_owner or button_list.is_a_parent_of(focus_owner))

    seen_count = 0
    seen_max = 0
    obtained_count = 0
    obtained_max = 0

    current_button = null
    for button in button_list.get_children():
        button_list.remove_child(button)
        button.queue_free()
    button_list.initial_focus = NodePath()

    if list_mode == ListMode.LIST_FUSIONS:
        load_fusions()
    else :
        assert (list_mode == ListMode.LIST_MONSTERS)
        load_monsters()

    button_list.setup_focus()
    if had_focus:
        button_list.grab_focus()
    else :
        var initial_button = button_list.get_focus_delegate()
        if initial_button:
            _on_list_button_focus_entered(initial_button)

    seen_label.text = Loc.trf("UI_BESTIARY_SEEN_COUNT", {
        "num":seen_count, 
        "total":seen_max
    })
    obtained_percent_label.text = Loc.trf("UI_BESTIARY_COMPLETION_PERCENT", {
        "percent":Loc.format_float("%03.1f", float(obtained_count) / max(obtained_max, 1) * 100.0)
    })

    var sort_prompt_key = "UI_BESTIARY_SORT_BY_NUMBER" if sort_mode == SortMode.SORT_BY_NAME else "UI_BESTIARY_SORT_BY_NAME"
    sort_prompt_button.set_text(sort_prompt_key)
    if show_list:
        sort_prompt_button.set_visible_override(null)
    else :
        sort_prompt_button.set_visible_override(false)

    var list_mode_prompt_key = "UI_BESTIARY_LIST_MODE_VIEW_MONSTERS" if list_mode == ListMode.LIST_FUSIONS else "UI_BESTIARY_LIST_MODE_VIEW_FUSIONS"
    list_mode_prompt_button.set_text(list_mode_prompt_key)
    if can_toggle_list_mode():
        list_mode_prompt_button.set_visible_override(null)
    else :
        list_mode_prompt_button.set_visible_override(false)

func set_initial_info_page(value:int):
    initial_info_page = value
    if species_info:
        species_info.set_page(value)

func can_toggle_list_mode()->bool:
    if not show_list:
        return false
    return list_mode == ListMode.LIST_FUSIONS or SaveState.species_collection.seen_fusions_by_name.size() > 0

# PATCH: ADD LINES HERE
func MONSTER_SEARCH_generate_move_array():
    var MONSTER_SEARCH_move_name_array = MONSTER_SEARCH_move_filter.split(",", false, 0)
    #clear old values
    MONSTER_SEARCH_move_array = Array()
    MONSTER_SEARCH_query_tag_array = Array()
    for MONSTER_SEARCH_move_name in MONSTER_SEARCH_move_name_array:
        var MONSTER_SEARCH_cleaned_move_name = MONSTER_SEARCH_move_name.strip_edges()
        print(MONSTER_SEARCH_cleaned_move_name)
        #if it starts with a :, that's a :tag, not a move.
        if (MONSTER_SEARCH_cleaned_move_name.substr(0, 1) == ":"):
            MONSTER_SEARCH_query_tag_array.append(MONSTER_SEARCH_cleaned_move_name.substr(1))
        else:
            #find matching move data
            #I don't know how to get moves by name so brute force it
            for MONSTER_SEARCH_move in BattleMoves.all_valid:
                if (Strings.strip_diacritics(tr(MONSTER_SEARCH_move.name).to_lower()) == (MONSTER_SEARCH_cleaned_move_name)):
                    print(MONSTER_SEARCH_move.name)
                    MONSTER_SEARCH_move_array.append(MONSTER_SEARCH_move)
                    break

#gotta figure out if this counts bootlegs or w/e.  Maybe in v2.
func MONSTER_SEARCH_species_is_compatible(MONSTER_SEARCH_monster_form:BaseForm):
    var MONSTER_SEARCH_is_compatible = true
    #must have all listed moves
    for MONSTER_SEARCH_move in MONSTER_SEARCH_move_array:
        var MONSTER_SEARCH_tag_array = MONSTER_SEARCH_move.tags
        #each move tag set must match a tag on the monster
        #the "any" tag is a special case, monsters don't have it, it's implicit.
        var MONSTER_SEARCH_tag_array_match = false
        for MONSTER_SEARCH_move_tag in MONSTER_SEARCH_tag_array:
            if (MONSTER_SEARCH_move_tag == "any"):
                MONSTER_SEARCH_tag_array_match = true
                break
            var MONSTER_SEARCH_tag_match = false
            for MONSTER_SEARCH_monster_tag in MONSTER_SEARCH_monster_form.move_tags:
                if (MONSTER_SEARCH_monster_tag == MONSTER_SEARCH_move_tag):
                    MONSTER_SEARCH_tag_match = true
            if (MONSTER_SEARCH_tag_match):
                MONSTER_SEARCH_tag_array_match = true
        if (!MONSTER_SEARCH_tag_array_match):
            MONSTER_SEARCH_is_compatible = false
    #must match all listed tags
    for MONSTER_SEARCH_query_tag in MONSTER_SEARCH_query_tag_array:
        #I don't know why you'd search for this but ok...
        if (MONSTER_SEARCH_query_tag == "any"):
            continue
        var MONSTER_SEARCH_tag_match = false
        for MONSTER_SEARCH_monster_tag in MONSTER_SEARCH_monster_form.move_tags:
            if (MONSTER_SEARCH_monster_tag == MONSTER_SEARCH_query_tag):
                MONSTER_SEARCH_tag_match = true
        if (!MONSTER_SEARCH_tag_match):
            MONSTER_SEARCH_is_compatible = false
    return MONSTER_SEARCH_is_compatible
# PATCH: STOP

func load_monsters():
    if sort_mode == SortMode.SORT_BY_NAME:
        for species in MonsterForms.by_name:
            assert (species != null)
            if SaveState.species_collection.has_seen_species(species):
# PATCH: REPLACE LINES
                _add_list_button(species)
# PATCH: INTO
                if (MONSTER_SEARCH_species_is_compatible(species)):
                    _add_list_button(species)
# PATCH: STOP
    else :
        assert (sort_mode == SortMode.SORT_BY_NUMBER)
        for species in MonsterForms.by_index:
            var index = species.bestiary_index
            if (species.bestiary_category == MonsterForm.BestiaryCategory.MONSTER and is_regular_index(index)) or SaveState.species_collection.has_seen_species(species):
# PATCH: REPLACE LINES
                _add_list_button(species)
# PATCH: INTO
                if (MONSTER_SEARCH_species_is_compatible(species)):
                    _add_list_button(species)
# PATCH: STOP

    obtained_max = MonsterForms.official_count

    obtained_label.text = Loc.trf("UI_BESTIARY_RECORDED_COUNT", {
        "num":obtained_count, 
        "total":obtained_max
    })

func _add_list_button(species:BaseForm):
    var button = BestiaryListButton.instance()
    button.species = species
    button_list.add_child(button)
    button.connect("focus_entered", self, "_on_list_button_focus_entered", [button])
    if species != null and button_list.initial_focus.is_empty():
        if _is_species_match(species, self.species):
            button_list.initial_focus = button_list.get_path_to(button)

    if species and SaveState.species_collection.has_obtained_species(species):
        obtained_count += 1
    if species and SaveState.species_collection.has_seen_species(species):
        seen_count += 1
    seen_max += 1

func _is_species_match(test:MonsterForm, current_species:BaseForm)->bool:
    if test == current_species:
        return true
    if current_species is FusionForm and current_species.base_form_1 == test:
        return true
    return false

func _add_list_button_fusion(species:Array):
    assert (species.size() == 2)
    if species.size() != 2:
        return 
    var button = BestiaryListButtonFusion.instance()
    button.species = species
    button_list.add_child(button)
    button.connect("focus_entered", self, "_on_list_button_focus_entered", [button])
    if species != null and button_list.initial_focus.is_empty():
        if _is_species_match_fusion(species, self.species):
            button_list.initial_focus = button_list.get_path_to(button)

    if species and SaveState.species_collection.has_formed_fusion(species[0], species[1]):
        obtained_count += 1
    if species and SaveState.species_collection.has_seen_fusion(species[0], species[1]):
        seen_count += 1
    seen_max += 1

func _is_species_match_fusion(test:Array, current_species:BaseForm)->bool:
    assert (test.size() == 2)
    if test.size() != 2:
        return false
    if current_species is FusionForm:
        return current_species.base_form_1 == test[0] and current_species.base_form_2 == test[1]
    else :
        assert (current_species is MonsterForm)
        return test[0] == current_species

func load_fusions():
    var species_list
    if sort_mode == SortMode.SORT_BY_NUMBER:
        species_list = SaveState.species_collection.seen_fusions_by_index
    else :
        species_list = SaveState.species_collection.seen_fusions_by_name

    for species in species_list:
        _add_list_button_fusion(species)

    seen_max = MonsterForms.official_count * MonsterForms.official_count
    obtained_max = seen_max

    obtained_label.text = Loc.trf("UI_BESTIARY_FORMED_COUNT", {
        "num":obtained_count, 
        "total":obtained_max
    })

func _on_list_button_focus_entered(button:BaseButton):
    current_button = button
    set_species(button.get_species_data())

func is_regular_index(index:int):
    return index > 0 and index <= MonsterForms.official_count

func set_species(value:Resource):
    species = value
    if species is FusionForm:
        list_mode = ListMode.LIST_FUSIONS
    else :
        list_mode = ListMode.LIST_MONSTERS
    if species_info:
        species_info.species = species

func get_show_hide_anim()->String:
    if show_list:
        return "show_with_list"
    else :
        return "show_without_list"

func grab_focus():
    if show_list:
        button_list.grab_focus()
    else :
        species_info.focus_mode = Control.FOCUS_CLICK
        species_info.grab_focus()

func _unhandled_input(event):
    if GlobalMessageDialog.message_dialog.visible or animation_player.is_playing() or not MenuHelper.is_in_top_menu(self):
        return 
    if event.is_action_pressed("ui_cancel"):
        cancel()
        accept_event()
    else :
        species_info._on_gui_input(event)
        if get_tree().is_input_handled():
            return 
        if not show_list and event.is_action_pressed("ui_accept"):
            cancel()
            accept_event()

func _on_SortPromptButton_pressed():
    sort_mode = (sort_mode + 1) % SortMode.size()
    reload()

# PATCH: ADD LINES HERE
func MONSTER_SEARCH__on_FilterPromptButton_pressed():
    var MONSTER_SEARCH_menu = preload("res://mods/compatibility_search/monster_search/MonsterSearchMenu.tscn").instance()
    MONSTER_SEARCH_menu.filter.name = MONSTER_SEARCH_move_filter
    MenuHelper.add_child(MONSTER_SEARCH_menu)
    var MONSTER_SEARCH_result = yield (MONSTER_SEARCH_menu.run_menu(), "completed")
    if MONSTER_SEARCH_result != null:
        MONSTER_SEARCH_move_filter = MONSTER_SEARCH_result.name
        MONSTER_SEARCH_generate_move_array()
        reload()
    MONSTER_SEARCH_menu.queue_free()
# PATCH: STOP

func _on_ListModePromptButton_pressed():
    if can_toggle_list_mode():
        list_mode = (list_mode + 1) % ListMode.size()
        reload()

func _on_habitat_map_requested(species, habitat_map, habitat_chunks):
    var prev_focus = get_focus_owner()
    Controls.set_disabled(self, true)
    yield (Co.wait_frames(2), "completed")
    prev_focus.release_focus()
    var feature_name = Loc.trf("UI_BESTIARY_INFO_LOCATION_MAP_FEATURE", {
        "species_name":species.name
    })
    yield (MenuHelper.show_map_menu(false, habitat_chunks, feature_name, habitat_map), "completed")
    Controls.set_disabled(self, false)
    if prev_focus:
        prev_focus.grab_focus()

func _on_ButtonListContainer_resized():
    if show_list:
        species_info_margin.add_constant_override("margin_left", button_list_container.rect_size.x - button_list_container.rect_min_size.x)

class_patch.finalcode

extends "res://menus/BaseMenu.gd"

const BestiaryListButton = preload("BestiaryListButton.tscn")
const BestiaryListButtonFusion = preload("BestiaryListButtonFusion.tscn")

enum SortMode{SORT_BY_NUMBER, SORT_BY_NAME}
enum ListMode{LIST_MONSTERS, LIST_FUSIONS}

export (bool) var show_list:bool = true
export (Resource) var species:Resource setget set_species
export (SortMode) var sort_mode = SortMode.SORT_BY_NUMBER
export (ListMode) var list_mode = ListMode.LIST_MONSTERS
export (int) var initial_info_page:int = 0 setget set_initial_info_page

onready var species_info_margin = find_node("BestiarySpeciesInfoMargin")
onready var species_info = find_node("BestiarySpeciesInfo")
onready var button_list_container = find_node("ButtonListContainer")
onready var button_list = find_node("ButtonList")
onready var seen_label = find_node("SeenLabel")
onready var obtained_label = find_node("ObtainedLabel")
onready var obtained_percent_label = find_node("ObtainedPercentLabel")
onready var sort_prompt_button = find_node("SortPromptButton")
onready var list_mode_prompt_button = find_node("ListModePromptButton")

var seen_count:int = 0
var seen_max:int = 0
var obtained_count:int = 0
var obtained_max:int = 0
var current_button:BaseButton = null
var MONSTER_SEARCH_move_filter = ""
var MONSTER_SEARCH_move_array = Array()
var MONSTER_SEARCH_query_tag_array = Array()

func _ready():
    reload()
    set_initial_info_page(initial_info_page)

    if not show_list:
        $BackButtonPanel.back_text_override = "UI_BUTTON_CLOSE"

func reload():
    var focus_owner = get_focus_owner()
    var had_focus = focus_owner != null and (button_list == focus_owner or button_list.is_a_parent_of(focus_owner))

    seen_count = 0
    seen_max = 0
    obtained_count = 0
    obtained_max = 0

    current_button = null
    for button in button_list.get_children():
        button_list.remove_child(button)
        button.queue_free()
    button_list.initial_focus = NodePath()

    if list_mode == ListMode.LIST_FUSIONS:
        load_fusions()
    else :
        assert (list_mode == ListMode.LIST_MONSTERS)
        load_monsters()

    button_list.setup_focus()
    if had_focus:
        button_list.grab_focus()
    else :
        var initial_button = button_list.get_focus_delegate()
        if initial_button:
            _on_list_button_focus_entered(initial_button)

    seen_label.text = Loc.trf("UI_BESTIARY_SEEN_COUNT", {
        "num":seen_count, 
        "total":seen_max
    })
    obtained_percent_label.text = Loc.trf("UI_BESTIARY_COMPLETION_PERCENT", {
        "percent":Loc.format_float("%03.1f", float(obtained_count) / max(obtained_max, 1) * 100.0)
    })

    var sort_prompt_key = "UI_BESTIARY_SORT_BY_NUMBER" if sort_mode == SortMode.SORT_BY_NAME else "UI_BESTIARY_SORT_BY_NAME"
    sort_prompt_button.set_text(sort_prompt_key)
    if show_list:
        sort_prompt_button.set_visible_override(null)
    else :
        sort_prompt_button.set_visible_override(false)

    var list_mode_prompt_key = "UI_BESTIARY_LIST_MODE_VIEW_MONSTERS" if list_mode == ListMode.LIST_FUSIONS else "UI_BESTIARY_LIST_MODE_VIEW_FUSIONS"
    list_mode_prompt_button.set_text(list_mode_prompt_key)
    if can_toggle_list_mode():
        list_mode_prompt_button.set_visible_override(null)
    else :
        list_mode_prompt_button.set_visible_override(false)

func set_initial_info_page(value:int):
    initial_info_page = value
    if species_info:
        species_info.set_page(value)

func can_toggle_list_mode()->bool:
    if not show_list:
        return false
    return list_mode == ListMode.LIST_FUSIONS or SaveState.species_collection.seen_fusions_by_name.size() > 0

func load_monsters():
    if sort_mode == SortMode.SORT_BY_NAME:
        for species in MonsterForms.by_name:
            assert (species != null)
            if SaveState.species_collection.has_seen_species(species):
                if (MONSTER_SEARCH_species_is_compatible(species)):
                    if (MONSTER_SEARCH_species_is_compatible(species)):
                    _add_list_button(species)
    else :
        assert (sort_mode == SortMode.SORT_BY_NUMBER)
        for species in MonsterForms.by_index:
            var index = species.bestiary_index
            if (species.bestiary_category == MonsterForm.BestiaryCategory.MONSTER and is_regular_index(index)) or SaveState.species_collection.has_seen_species(species):
                if (MONSTER_SEARCH_species_is_compatible(species)):
                    if (MONSTER_SEARCH_species_is_compatible(species)):
                    _add_list_button(species)

    obtained_max = MonsterForms.official_count

    obtained_label.text = Loc.trf("UI_BESTIARY_RECORDED_COUNT", {
        "num":obtained_count, 
        "total":obtained_max
    })

func _add_list_button(species:BaseForm):
    var button = BestiaryListButton.instance()
    button.species = species
    button_list.add_child(button)
    button.connect("focus_entered", self, "_on_list_button_focus_entered", [button])
    if species != null and button_list.initial_focus.is_empty():
        if _is_species_match(species, self.species):
            button_list.initial_focus = button_list.get_path_to(button)

    if species and SaveState.species_collection.has_obtained_species(species):
        obtained_count += 1
    if species and SaveState.species_collection.has_seen_species(species):
        seen_count += 1
    seen_max += 1

func _is_species_match(test:MonsterForm, current_species:BaseForm)->bool:
    if test == current_species:
        return true
    if current_species is FusionForm and current_species.base_form_1 == test:
        return true
    return false

func _add_list_button_fusion(species:Array):
    assert (species.size() == 2)
    if species.size() != 2:
        return 
    var button = BestiaryListButtonFusion.instance()
    button.species = species
    button_list.add_child(button)
    button.connect("focus_entered", self, "_on_list_button_focus_entered", [button])
    if species != null and button_list.initial_focus.is_empty():
        if _is_species_match_fusion(species, self.species):
            button_list.initial_focus = button_list.get_path_to(button)

    if species and SaveState.species_collection.has_formed_fusion(species[0], species[1]):
        obtained_count += 1
    if species and SaveState.species_collection.has_seen_fusion(species[0], species[1]):
        seen_count += 1
    seen_max += 1

func _is_species_match_fusion(test:Array, current_species:BaseForm)->bool:
    assert (test.size() == 2)
    if test.size() != 2:
        return false
    if current_species is FusionForm:
        return current_species.base_form_1 == test[0] and current_species.base_form_2 == test[1]
    else :
        assert (current_species is MonsterForm)
        return test[0] == current_species

func load_fusions():
    var species_list
    if sort_mode == SortMode.SORT_BY_NUMBER:
        species_list = SaveState.species_collection.seen_fusions_by_index
    else :
        species_list = SaveState.species_collection.seen_fusions_by_name

    for species in species_list:
        _add_list_button_fusion(species)

    seen_max = MonsterForms.official_count * MonsterForms.official_count
    obtained_max = seen_max

    obtained_label.text = Loc.trf("UI_BESTIARY_FORMED_COUNT", {
        "num":obtained_count, 
        "total":obtained_max
    })

func _on_list_button_focus_entered(button:BaseButton):
    current_button = button
    set_species(button.get_species_data())

func is_regular_index(index:int):
    return index > 0 and index <= MonsterForms.official_count

func set_species(value:Resource):
    species = value
    if species is FusionForm:
        list_mode = ListMode.LIST_FUSIONS
    else :
        list_mode = ListMode.LIST_MONSTERS
    if species_info:
        species_info.species = species

func get_show_hide_anim()->String:
    if show_list:
        return "show_with_list"
    else :
        return "show_without_list"

func grab_focus():
    if show_list:
        button_list.grab_focus()
    else :
        species_info.focus_mode = Control.FOCUS_CLICK
        species_info.grab_focus()

func _unhandled_input(event):
    if GlobalMessageDialog.message_dialog.visible or animation_player.is_playing() or not MenuHelper.is_in_top_menu(self):
        return 
    if event.is_action_pressed("ui_cancel"):
        cancel()
        accept_event()
    else :
        species_info._on_gui_input(event)
        if get_tree().is_input_handled():
            return 
        if not show_list and event.is_action_pressed("ui_accept"):
            cancel()
            accept_event()

func _on_SortPromptButton_pressed():
    sort_mode = (sort_mode + 1) % SortMode.size()
    reload()

func _on_ListModePromptButton_pressed():
    if can_toggle_list_mode():
        list_mode = (list_mode + 1) % ListMode.size()
        reload()

func _on_habitat_map_requested(species, habitat_map, habitat_chunks):
    var prev_focus = get_focus_owner()
    Controls.set_disabled(self, true)
    yield (Co.wait_frames(2), "completed")
    prev_focus.release_focus()
    var feature_name = Loc.trf("UI_BESTIARY_INFO_LOCATION_MAP_FEATURE", {
        "species_name":species.name
    })
    yield (MenuHelper.show_map_menu(false, habitat_chunks, feature_name, habitat_map), "completed")
    Controls.set_disabled(self, false)
    if prev_focus:
        prev_focus.grab_focus()

func _on_ButtonListContainer_resized():
    if show_list:
        species_info_margin.add_constant_override("margin_left", button_list_container.rect_size.x - button_list_container.rect_min_size.x)
neopryne commented 8 months ago

After much wrangling, I was able to make the class patcher generate the code that I wanted it to, however, the way it gets there is definitely wrong. In particular, the REPLACE tag is very broken when replacing multiple things, or replacing things with blocks that include lines that were replaced.

example, this

# PATCH: REPLACE LINES
                _add_list_button(species)
# PATCH: INTO
                if (MONSTER_SEARCH_species_is_compatible(species)):
                    _add_list_button(species)#first comment to make patcher work
# PATCH: STOP
    else :
        assert (sort_mode == SortMode.SORT_BY_NUMBER)
        for species in MonsterForms.by_index:
            var index = species.bestiary_index
            if (species.bestiary_category == MonsterForm.BestiaryCategory.MONSTER and is_regular_index(index)) or SaveState.species_collection.has_seen_species(species):
# PATCH: REPLACE LINES
                _add_list_button(species)
# PATCH: INTO
                if (MONSTER_SEARCH_species_is_compatible(species)):
# PATCH: STOP
# PATCH: ADD LINES AFTER
                    _add_list_button(species)#second comment to make patcher work
# PATCH: STOP

becomes this

                if (MONSTER_SEARCH_species_is_compatible(species)):
                    _add_list_button(species)#first comment to make patcher work
    else :
        assert (sort_mode == SortMode.SORT_BY_NUMBER)
        for species in MonsterForms.by_index:
            var index = species.bestiary_index
            if (species.bestiary_category == MonsterForm.BestiaryCategory.MONSTER and is_regular_index(index)) or SaveState.species_collection.has_seen_species(species):
                if (MONSTER_SEARCH_species_is_compatible(species)):
                    _add_list_button(species)#first comment to make patcher work

This works for some reason, though it's defintely not stable. The second comment line is ignored, and for some reason the first comment line is applied twice, once to a block it doesn't belong to.

Entire file before/after

extends "res://menus/BaseMenu.gd"

const BestiaryListButton = preload("BestiaryListButton.tscn")
const BestiaryListButtonFusion = preload("BestiaryListButtonFusion.tscn")

enum SortMode{SORT_BY_NUMBER, SORT_BY_NAME}
enum ListMode{LIST_MONSTERS, LIST_FUSIONS}

export (bool) var show_list:bool = true
export (Resource) var species:Resource setget set_species
export (SortMode) var sort_mode = SortMode.SORT_BY_NUMBER
export (ListMode) var list_mode = ListMode.LIST_MONSTERS
export (int) var initial_info_page:int = 0 setget set_initial_info_page

onready var species_info_margin = find_node("BestiarySpeciesInfoMargin")
onready var species_info = find_node("BestiarySpeciesInfo")
onready var button_list_container = find_node("ButtonListContainer")
onready var button_list = find_node("ButtonList")
onready var seen_label = find_node("SeenLabel")
onready var obtained_label = find_node("ObtainedLabel")
onready var obtained_percent_label = find_node("ObtainedPercentLabel")
onready var sort_prompt_button = find_node("SortPromptButton")
onready var list_mode_prompt_button = find_node("ListModePromptButton")

var seen_count:int = 0
var seen_max:int = 0
var obtained_count:int = 0
var obtained_max:int = 0
var current_button:BaseButton = null
# PATCH: ADD LINES HERE
var MONSTER_SEARCH_move_filter = ""
var MONSTER_SEARCH_move_array = Array()
var MONSTER_SEARCH_query_tag_array = Array()
# PATCH: STOP

func _ready():
    reload()
    set_initial_info_page(initial_info_page)

    if not show_list:
        $BackButtonPanel.back_text_override = "UI_BUTTON_CLOSE"

func reload():
    var focus_owner = get_focus_owner()
    var had_focus = focus_owner != null and (button_list == focus_owner or button_list.is_a_parent_of(focus_owner))

    seen_count = 0
    seen_max = 0
    obtained_count = 0
    obtained_max = 0

    current_button = null
    for button in button_list.get_children():
        button_list.remove_child(button)
        button.queue_free()
    button_list.initial_focus = NodePath()

    if list_mode == ListMode.LIST_FUSIONS:
        load_fusions()
    else :
        assert (list_mode == ListMode.LIST_MONSTERS)
        load_monsters()

    button_list.setup_focus()
    if had_focus:
        button_list.grab_focus()
    else :
        var initial_button = button_list.get_focus_delegate()
        if initial_button:
            _on_list_button_focus_entered(initial_button)

    seen_label.text = Loc.trf("UI_BESTIARY_SEEN_COUNT", {
        "num":seen_count, 
        "total":seen_max
    })
    obtained_percent_label.text = Loc.trf("UI_BESTIARY_COMPLETION_PERCENT", {
        "percent":Loc.format_float("%03.1f", float(obtained_count) / max(obtained_max, 1) * 100.0)
    })

    var sort_prompt_key = "UI_BESTIARY_SORT_BY_NUMBER" if sort_mode == SortMode.SORT_BY_NAME else "UI_BESTIARY_SORT_BY_NAME"
    sort_prompt_button.set_text(sort_prompt_key)
    if show_list:
        sort_prompt_button.set_visible_override(null)
    else :
        sort_prompt_button.set_visible_override(false)

    var list_mode_prompt_key = "UI_BESTIARY_LIST_MODE_VIEW_MONSTERS" if list_mode == ListMode.LIST_FUSIONS else "UI_BESTIARY_LIST_MODE_VIEW_FUSIONS"
    list_mode_prompt_button.set_text(list_mode_prompt_key)
    if can_toggle_list_mode():
        list_mode_prompt_button.set_visible_override(null)
    else :
        list_mode_prompt_button.set_visible_override(false)

func set_initial_info_page(value:int):
    initial_info_page = value
    if species_info:
        species_info.set_page(value)

func can_toggle_list_mode()->bool:
    if not show_list:
        return false
    return list_mode == ListMode.LIST_FUSIONS or SaveState.species_collection.seen_fusions_by_name.size() > 0

# PATCH: ADD FUNC
func MONSTER_SEARCH_generate_move_array():
    var MONSTER_SEARCH_move_name_array = MONSTER_SEARCH_move_filter.split(",", false, 0)
    #clear old values
    MONSTER_SEARCH_move_array = Array()
    MONSTER_SEARCH_query_tag_array = Array()
    for MONSTER_SEARCH_move_name in MONSTER_SEARCH_move_name_array:
        var MONSTER_SEARCH_cleaned_move_name = MONSTER_SEARCH_move_name.strip_edges()
        print(MONSTER_SEARCH_cleaned_move_name)
        #if it starts with a :, that's a :tag, not a move.
        if (MONSTER_SEARCH_cleaned_move_name.substr(0, 1) == ":"):
            MONSTER_SEARCH_query_tag_array.append(MONSTER_SEARCH_cleaned_move_name.substr(1))
        else:
            #find matching move data
            #I don't know how to get moves by name so brute force it
            for MONSTER_SEARCH_move in BattleMoves.all_valid:
                if (Strings.strip_diacritics(tr(MONSTER_SEARCH_move.name).to_lower()) == (MONSTER_SEARCH_cleaned_move_name)):
                    print(MONSTER_SEARCH_move.name)
                    MONSTER_SEARCH_move_array.append(MONSTER_SEARCH_move)
                    break

# PATCH: STOP

#gotta figure out if this counts bootlegs or w/e.  Maybe in v2.
# PATCH: ADD FUNC
func MONSTER_SEARCH_species_is_compatible(MONSTER_SEARCH_monster_form:BaseForm):
    var MONSTER_SEARCH_is_compatible = true
    #must have all listed moves
    for MONSTER_SEARCH_move in MONSTER_SEARCH_move_array:
        var MONSTER_SEARCH_tag_array = MONSTER_SEARCH_move.tags
        #each move tag set must match a tag on the monster
        #the "any" tag is a special case, monsters don't have it, it's implicit.
        var MONSTER_SEARCH_tag_array_match = false
        for MONSTER_SEARCH_move_tag in MONSTER_SEARCH_tag_array:
            if (MONSTER_SEARCH_move_tag == "any"):
                MONSTER_SEARCH_tag_array_match = true
                break
            var MONSTER_SEARCH_tag_match = false
            for MONSTER_SEARCH_monster_tag in MONSTER_SEARCH_monster_form.move_tags:
                if (MONSTER_SEARCH_monster_tag == MONSTER_SEARCH_move_tag):
                    MONSTER_SEARCH_tag_match = true
            if (MONSTER_SEARCH_tag_match):
                MONSTER_SEARCH_tag_array_match = true
        if (!MONSTER_SEARCH_tag_array_match):
            MONSTER_SEARCH_is_compatible = false
    #must match all listed tags
    for MONSTER_SEARCH_query_tag in MONSTER_SEARCH_query_tag_array:
        #I don't know why you'd search for this but ok...
        if (MONSTER_SEARCH_query_tag == "any"):
            continue
        var MONSTER_SEARCH_tag_match = false
        for MONSTER_SEARCH_monster_tag in MONSTER_SEARCH_monster_form.move_tags:
            if (MONSTER_SEARCH_monster_tag == MONSTER_SEARCH_query_tag):
                MONSTER_SEARCH_tag_match = true
        if (!MONSTER_SEARCH_tag_match):
            MONSTER_SEARCH_is_compatible = false
    return MONSTER_SEARCH_is_compatible

# PATCH: STOP

func load_monsters():
    if sort_mode == SortMode.SORT_BY_NAME:
        for species in MonsterForms.by_name:
            assert (species != null)
            if SaveState.species_collection.has_seen_species(species):
# PATCH: REPLACE LINES
                _add_list_button(species)
# PATCH: INTO
                if (MONSTER_SEARCH_species_is_compatible(species)):
                    _add_list_button(species)#first comment to make patcher work
# PATCH: STOP
    else :
        assert (sort_mode == SortMode.SORT_BY_NUMBER)
        for species in MonsterForms.by_index:
            var index = species.bestiary_index
            if (species.bestiary_category == MonsterForm.BestiaryCategory.MONSTER and is_regular_index(index)) or SaveState.species_collection.has_seen_species(species):
# PATCH: REPLACE LINES
                _add_list_button(species)
# PATCH: INTO
                if (MONSTER_SEARCH_species_is_compatible(species)):
# PATCH: STOP
# PATCH: ADD LINES AFTER
                    _add_list_button(species)#second comment to make patcher work
# PATCH: STOP

    obtained_max = MonsterForms.official_count

    obtained_label.text = Loc.trf("UI_BESTIARY_RECORDED_COUNT", {
        "num":obtained_count, 
        "total":obtained_max
    })

func _add_list_button(species:BaseForm):
    var button = BestiaryListButton.instance()
    button.species = species
    button_list.add_child(button)
    button.connect("focus_entered", self, "_on_list_button_focus_entered", [button])
    if species != null and button_list.initial_focus.is_empty():
        if _is_species_match(species, self.species):
            button_list.initial_focus = button_list.get_path_to(button)

    if species and SaveState.species_collection.has_obtained_species(species):
        obtained_count += 1
    if species and SaveState.species_collection.has_seen_species(species):
        seen_count += 1
    seen_max += 1

func _is_species_match(test:MonsterForm, current_species:BaseForm)->bool:
    if test == current_species:
        return true
    if current_species is FusionForm and current_species.base_form_1 == test:
        return true
    return false

func _add_list_button_fusion(species:Array):
    assert (species.size() == 2)
    if species.size() != 2:
        return 
    var button = BestiaryListButtonFusion.instance()
    button.species = species
    button_list.add_child(button)
    button.connect("focus_entered", self, "_on_list_button_focus_entered", [button])
    if species != null and button_list.initial_focus.is_empty():
        if _is_species_match_fusion(species, self.species):
            button_list.initial_focus = button_list.get_path_to(button)

    if species and SaveState.species_collection.has_formed_fusion(species[0], species[1]):
        obtained_count += 1
    if species and SaveState.species_collection.has_seen_fusion(species[0], species[1]):
        seen_count += 1
    seen_max += 1

func _is_species_match_fusion(test:Array, current_species:BaseForm)->bool:
    assert (test.size() == 2)
    if test.size() != 2:
        return false
    if current_species is FusionForm:
        return current_species.base_form_1 == test[0] and current_species.base_form_2 == test[1]
    else :
        assert (current_species is MonsterForm)
        return test[0] == current_species

func load_fusions():
    var species_list
    if sort_mode == SortMode.SORT_BY_NUMBER:
        species_list = SaveState.species_collection.seen_fusions_by_index
    else :
        species_list = SaveState.species_collection.seen_fusions_by_name

    for species in species_list:
        _add_list_button_fusion(species)

    seen_max = MonsterForms.official_count * MonsterForms.official_count
    obtained_max = seen_max

    obtained_label.text = Loc.trf("UI_BESTIARY_FORMED_COUNT", {
        "num":obtained_count, 
        "total":obtained_max
    })

func _on_list_button_focus_entered(button:BaseButton):
    current_button = button
    set_species(button.get_species_data())

func is_regular_index(index:int):
    return index > 0 and index <= MonsterForms.official_count

func set_species(value:Resource):
    species = value
    if species is FusionForm:
        list_mode = ListMode.LIST_FUSIONS
    else :
        list_mode = ListMode.LIST_MONSTERS
    if species_info:
        species_info.species = species

func get_show_hide_anim()->String:
    if show_list:
        return "show_with_list"
    else :
        return "show_without_list"

func grab_focus():
    if show_list:
        button_list.grab_focus()
    else :
        species_info.focus_mode = Control.FOCUS_CLICK
        species_info.grab_focus()

func _unhandled_input(event):
    if GlobalMessageDialog.message_dialog.visible or animation_player.is_playing() or not MenuHelper.is_in_top_menu(self):
        return 
    if event.is_action_pressed("ui_cancel"):
        cancel()
        accept_event()
    else :
        species_info._on_gui_input(event)
        if get_tree().is_input_handled():
            return 
        if not show_list and event.is_action_pressed("ui_accept"):
            cancel()
            accept_event()

func _on_SortPromptButton_pressed():
    sort_mode = (sort_mode + 1) % SortMode.size()
    reload()

# PATCH: ADD FUNC
func MONSTER_SEARCH__on_FilterPromptButton_pressed():
    var MONSTER_SEARCH_menu = preload("res://mods/compatibility_search/monster_search/MonsterSearchMenu.tscn").instance()
    MONSTER_SEARCH_menu.filter.name = MONSTER_SEARCH_move_filter
    MenuHelper.add_child(MONSTER_SEARCH_menu)
    var MONSTER_SEARCH_result = yield (MONSTER_SEARCH_menu.run_menu(), "completed")
    if MONSTER_SEARCH_result != null:
        MONSTER_SEARCH_move_filter = MONSTER_SEARCH_result.name
        MONSTER_SEARCH_generate_move_array()
        reload()
    MONSTER_SEARCH_menu.queue_free()
# PATCH: STOP

func _on_ListModePromptButton_pressed():
    if can_toggle_list_mode():
        list_mode = (list_mode + 1) % ListMode.size()
        reload()

func _on_habitat_map_requested(species, habitat_map, habitat_chunks):
    var prev_focus = get_focus_owner()
    Controls.set_disabled(self, true)
    yield (Co.wait_frames(2), "completed")
    prev_focus.release_focus()
    var feature_name = Loc.trf("UI_BESTIARY_INFO_LOCATION_MAP_FEATURE", {
        "species_name":species.name
    })
    yield (MenuHelper.show_map_menu(false, habitat_chunks, feature_name, habitat_map), "completed")
    Controls.set_disabled(self, false)
    if prev_focus:
        prev_focus.grab_focus()

func _on_ButtonListContainer_resized():
    if show_list:
        species_info_margin.add_constant_override("margin_left", button_list_container.rect_size.x - button_list_container.rect_min_size.x)

After

extends "res://menus/BaseMenu.gd"

const BestiaryListButton = preload("BestiaryListButton.tscn")
const BestiaryListButtonFusion = preload("BestiaryListButtonFusion.tscn")

enum SortMode{SORT_BY_NUMBER, SORT_BY_NAME}
enum ListMode{LIST_MONSTERS, LIST_FUSIONS}

export (bool) var show_list:bool = true
export (Resource) var species:Resource setget set_species
export (SortMode) var sort_mode = SortMode.SORT_BY_NUMBER
export (ListMode) var list_mode = ListMode.LIST_MONSTERS
export (int) var initial_info_page:int = 0 setget set_initial_info_page

onready var species_info_margin = find_node("BestiarySpeciesInfoMargin")
onready var species_info = find_node("BestiarySpeciesInfo")
onready var button_list_container = find_node("ButtonListContainer")
onready var button_list = find_node("ButtonList")
onready var seen_label = find_node("SeenLabel")
onready var obtained_label = find_node("ObtainedLabel")
onready var obtained_percent_label = find_node("ObtainedPercentLabel")
onready var sort_prompt_button = find_node("SortPromptButton")
onready var list_mode_prompt_button = find_node("ListModePromptButton")

var seen_count:int = 0
var seen_max:int = 0
var obtained_count:int = 0
var obtained_max:int = 0
var current_button:BaseButton = null
var MONSTER_SEARCH_move_filter = ""
var MONSTER_SEARCH_move_array = Array()
var MONSTER_SEARCH_query_tag_array = Array()

func _ready():
    reload()
    set_initial_info_page(initial_info_page)

    if not show_list:
        $BackButtonPanel.back_text_override = "UI_BUTTON_CLOSE"

func reload():
    var focus_owner = get_focus_owner()
    var had_focus = focus_owner != null and (button_list == focus_owner or button_list.is_a_parent_of(focus_owner))

    seen_count = 0
    seen_max = 0
    obtained_count = 0
    obtained_max = 0

    current_button = null
    for button in button_list.get_children():
        button_list.remove_child(button)
        button.queue_free()
    button_list.initial_focus = NodePath()

    if list_mode == ListMode.LIST_FUSIONS:
        load_fusions()
    else :
        assert (list_mode == ListMode.LIST_MONSTERS)
        load_monsters()

    button_list.setup_focus()
    if had_focus:
        button_list.grab_focus()
    else :
        var initial_button = button_list.get_focus_delegate()
        if initial_button:
            _on_list_button_focus_entered(initial_button)

    seen_label.text = Loc.trf("UI_BESTIARY_SEEN_COUNT", {
        "num":seen_count, 
        "total":seen_max
    })
    obtained_percent_label.text = Loc.trf("UI_BESTIARY_COMPLETION_PERCENT", {
        "percent":Loc.format_float("%03.1f", float(obtained_count) / max(obtained_max, 1) * 100.0)
    })

    var sort_prompt_key = "UI_BESTIARY_SORT_BY_NUMBER" if sort_mode == SortMode.SORT_BY_NAME else "UI_BESTIARY_SORT_BY_NAME"
    sort_prompt_button.set_text(sort_prompt_key)
    if show_list:
        sort_prompt_button.set_visible_override(null)
    else :
        sort_prompt_button.set_visible_override(false)

    var list_mode_prompt_key = "UI_BESTIARY_LIST_MODE_VIEW_MONSTERS" if list_mode == ListMode.LIST_FUSIONS else "UI_BESTIARY_LIST_MODE_VIEW_FUSIONS"
    list_mode_prompt_button.set_text(list_mode_prompt_key)
    if can_toggle_list_mode():
        list_mode_prompt_button.set_visible_override(null)
    else :
        list_mode_prompt_button.set_visible_override(false)

func set_initial_info_page(value:int):
    initial_info_page = value
    if species_info:
        species_info.set_page(value)

func can_toggle_list_mode()->bool:
    if not show_list:
        return false
    return list_mode == ListMode.LIST_FUSIONS or SaveState.species_collection.seen_fusions_by_name.size() > 0

func load_monsters():
    if sort_mode == SortMode.SORT_BY_NAME:
        for species in MonsterForms.by_name:
            assert (species != null)
            if SaveState.species_collection.has_seen_species(species):
                if (MONSTER_SEARCH_species_is_compatible(species)):
                    _add_list_button(species)#first comment to make patcher work
    else :
        assert (sort_mode == SortMode.SORT_BY_NUMBER)
        for species in MonsterForms.by_index:
            var index = species.bestiary_index
            if (species.bestiary_category == MonsterForm.BestiaryCategory.MONSTER and is_regular_index(index)) or SaveState.species_collection.has_seen_species(species):
                if (MONSTER_SEARCH_species_is_compatible(species)):
                    _add_list_button(species)#first comment to make patcher work

    obtained_max = MonsterForms.official_count

    obtained_label.text = Loc.trf("UI_BESTIARY_RECORDED_COUNT", {
        "num":obtained_count, 
        "total":obtained_max
    })

                    _add_list_button(species)#second comment to make patcher work
func _add_list_button(species:BaseForm):
    var button = BestiaryListButton.instance()
    button.species = species
    button_list.add_child(button)
    button.connect("focus_entered", self, "_on_list_button_focus_entered", [button])
    if species != null and button_list.initial_focus.is_empty():
        if _is_species_match(species, self.species):
            button_list.initial_focus = button_list.get_path_to(button)

    if species and SaveState.species_collection.has_obtained_species(species):
        obtained_count += 1
    if species and SaveState.species_collection.has_seen_species(species):
        seen_count += 1
    seen_max += 1

func _is_species_match(test:MonsterForm, current_species:BaseForm)->bool:
    if test == current_species:
        return true
    if current_species is FusionForm and current_species.base_form_1 == test:
        return true
    return false

func _add_list_button_fusion(species:Array):
    assert (species.size() == 2)
    if species.size() != 2:
        return 
    var button = BestiaryListButtonFusion.instance()
    button.species = species
    button_list.add_child(button)
    button.connect("focus_entered", self, "_on_list_button_focus_entered", [button])
    if species != null and button_list.initial_focus.is_empty():
        if _is_species_match_fusion(species, self.species):
            button_list.initial_focus = button_list.get_path_to(button)

    if species and SaveState.species_collection.has_formed_fusion(species[0], species[1]):
        obtained_count += 1
    if species and SaveState.species_collection.has_seen_fusion(species[0], species[1]):
        seen_count += 1
    seen_max += 1

func _is_species_match_fusion(test:Array, current_species:BaseForm)->bool:
    assert (test.size() == 2)
    if test.size() != 2:
        return false
    if current_species is FusionForm:
        return current_species.base_form_1 == test[0] and current_species.base_form_2 == test[1]
    else :
        assert (current_species is MonsterForm)
        return test[0] == current_species

func load_fusions():
    var species_list
    if sort_mode == SortMode.SORT_BY_NUMBER:
        species_list = SaveState.species_collection.seen_fusions_by_index
    else :
        species_list = SaveState.species_collection.seen_fusions_by_name

    for species in species_list:
        _add_list_button_fusion(species)

    seen_max = MonsterForms.official_count * MonsterForms.official_count
    obtained_max = seen_max

    obtained_label.text = Loc.trf("UI_BESTIARY_FORMED_COUNT", {
        "num":obtained_count, 
        "total":obtained_max
    })

func _on_list_button_focus_entered(button:BaseButton):
    current_button = button
    set_species(button.get_species_data())

func is_regular_index(index:int):
    return index > 0 and index <= MonsterForms.official_count

func set_species(value:Resource):
    species = value
    if species is FusionForm:
        list_mode = ListMode.LIST_FUSIONS
    else :
        list_mode = ListMode.LIST_MONSTERS
    if species_info:
        species_info.species = species

func get_show_hide_anim()->String:
    if show_list:
        return "show_with_list"
    else :
        return "show_without_list"

func grab_focus():
    if show_list:
        button_list.grab_focus()
    else :
        species_info.focus_mode = Control.FOCUS_CLICK
        species_info.grab_focus()

func _unhandled_input(event):
    if GlobalMessageDialog.message_dialog.visible or animation_player.is_playing() or not MenuHelper.is_in_top_menu(self):
        return 
    if event.is_action_pressed("ui_cancel"):
        cancel()
        accept_event()
    else :
        species_info._on_gui_input(event)
        if get_tree().is_input_handled():
            return 
        if not show_list and event.is_action_pressed("ui_accept"):
            cancel()
            accept_event()

func _on_SortPromptButton_pressed():
    sort_mode = (sort_mode + 1) % SortMode.size()
    reload()

func _on_ListModePromptButton_pressed():
    if can_toggle_list_mode():
        list_mode = (list_mode + 1) % ListMode.size()
        reload()

func _on_habitat_map_requested(species, habitat_map, habitat_chunks):
    var prev_focus = get_focus_owner()
    Controls.set_disabled(self, true)
    yield (Co.wait_frames(2), "completed")
    prev_focus.release_focus()
    var feature_name = Loc.trf("UI_BESTIARY_INFO_LOCATION_MAP_FEATURE", {
        "species_name":species.name
    })
    yield (MenuHelper.show_map_menu(false, habitat_chunks, feature_name, habitat_map), "completed")
    Controls.set_disabled(self, false)
    if prev_focus:
        prev_focus.grab_focus()

func _on_ButtonListContainer_resized():
    if show_list:
        species_info_margin.add_constant_override("margin_left", button_list_container.rect_size.x - button_list_container.rect_min_size.x)

func MONSTER_SEARCH_generate_move_array():
    var MONSTER_SEARCH_move_name_array = MONSTER_SEARCH_move_filter.split(",", false, 0)
    #clear old values
    MONSTER_SEARCH_move_array = Array()
    MONSTER_SEARCH_query_tag_array = Array()
    for MONSTER_SEARCH_move_name in MONSTER_SEARCH_move_name_array:
        var MONSTER_SEARCH_cleaned_move_name = MONSTER_SEARCH_move_name.strip_edges()
        print(MONSTER_SEARCH_cleaned_move_name)
        #if it starts with a :, that's a :tag, not a move.
        if (MONSTER_SEARCH_cleaned_move_name.substr(0, 1) == ":"):
            MONSTER_SEARCH_query_tag_array.append(MONSTER_SEARCH_cleaned_move_name.substr(1))
        else:
            #find matching move data
            #I don't know how to get moves by name so brute force it
            for MONSTER_SEARCH_move in BattleMoves.all_valid:
                if (Strings.strip_diacritics(tr(MONSTER_SEARCH_move.name).to_lower()) == (MONSTER_SEARCH_cleaned_move_name)):
                    print(MONSTER_SEARCH_move.name)
                    MONSTER_SEARCH_move_array.append(MONSTER_SEARCH_move)
                    break

func MONSTER_SEARCH_species_is_compatible(MONSTER_SEARCH_monster_form:BaseForm):
    var MONSTER_SEARCH_is_compatible = true
    #must have all listed moves
    for MONSTER_SEARCH_move in MONSTER_SEARCH_move_array:
        var MONSTER_SEARCH_tag_array = MONSTER_SEARCH_move.tags
        #each move tag set must match a tag on the monster
        #the "any" tag is a special case, monsters don't have it, it's implicit.
        var MONSTER_SEARCH_tag_array_match = false
        for MONSTER_SEARCH_move_tag in MONSTER_SEARCH_tag_array:
            if (MONSTER_SEARCH_move_tag == "any"):
                MONSTER_SEARCH_tag_array_match = true
                break
            var MONSTER_SEARCH_tag_match = false
            for MONSTER_SEARCH_monster_tag in MONSTER_SEARCH_monster_form.move_tags:
                if (MONSTER_SEARCH_monster_tag == MONSTER_SEARCH_move_tag):
                    MONSTER_SEARCH_tag_match = true
            if (MONSTER_SEARCH_tag_match):
                MONSTER_SEARCH_tag_array_match = true
        if (!MONSTER_SEARCH_tag_array_match):
            MONSTER_SEARCH_is_compatible = false
    #must match all listed tags
    for MONSTER_SEARCH_query_tag in MONSTER_SEARCH_query_tag_array:
        #I don't know why you'd search for this but ok...
        if (MONSTER_SEARCH_query_tag == "any"):
            continue
        var MONSTER_SEARCH_tag_match = false
        for MONSTER_SEARCH_monster_tag in MONSTER_SEARCH_monster_form.move_tags:
            if (MONSTER_SEARCH_monster_tag == MONSTER_SEARCH_query_tag):
                MONSTER_SEARCH_tag_match = true
        if (!MONSTER_SEARCH_tag_match):
            MONSTER_SEARCH_is_compatible = false
    return MONSTER_SEARCH_is_compatible

func MONSTER_SEARCH__on_FilterPromptButton_pressed():
    var MONSTER_SEARCH_menu = preload("res://mods/compatibility_search/monster_search/MonsterSearchMenu.tscn").instance()
    MONSTER_SEARCH_menu.filter.name = MONSTER_SEARCH_move_filter
    MenuHelper.add_child(MONSTER_SEARCH_menu)
    var MONSTER_SEARCH_result = yield (MONSTER_SEARCH_menu.run_menu(), "completed")
    if MONSTER_SEARCH_result != null:
        MONSTER_SEARCH_move_filter = MONSTER_SEARCH_result.name
        MONSTER_SEARCH_generate_move_array()
        reload()
    MONSTER_SEARCH_menu.queue_free()

All this still doesn't solve the original issue I was having where the patcher fails to apply the patch to the script due to a static compatibility error.

neopryne commented 8 months ago

Re the original issue, NCrafters gave me a workaround.

Sorry for the late reply, finally got time to test this out myself. I get the same error when trying it out on BestiaryMenu, as far as I can tell I guess it's because this isn't a class script. The same test with MonsterTape doesn't have this reload issue. (Tbh, I'm not sure why because I've used reload on non-class scripts too but I don't have time to dig too far into this right now).

For now here's the steps to using the take_over_path method of doing this: 1) Duplicate the BeastiaryMenu.tscn and move it to your mod folder 2) Open this duplicate scene and change the script to be your modded script (minus any tags you used for modutils, these should be direct changes) 3) change your mod.gd script (The ContentInfo script) to match the following:

extends ContentInfo

var bestiarymenu = preload("res://mods/testmod1/BestiartyMenu.tscn")

func _init(): bestiarymenu.take_over_path("res://menus/bestiary/BestiaryMenu.tscn")

Explanation for the script above: We preload the modded version of the BestiaryMenu that is using your modded script, then in the _init() function we use take_over_path to have it replace the original BestiaryMenu. The reason you're doing this with the scene instead of the direct script is because in editor it will work fine to do this with direct scripts, but in the actual build the BestiaryMenu won't acknowledge you've replaced anything. There's other ways to patch the script code yourself, but I think it might be better to have you take the easier option first while you get comfortable modding. Then if you want later you can update to use a different method.