@tool extends BaseViewer # Displays all enemies from directory structure or database # Shows icon sprite, name, and 2D/3D badge signal enemy_selected(enemy_resource_path: String) signal duplicate_enemy_requested(enemy_data: Dictionary) signal enemy_deleted(enemy_name: String, enemy_path: String) signal enemy_duplication_started(enemy_name: String, is_3d: bool) var _enemies_dir := "res://Resources/Enemies/" const SETTING_SHOW_2D = "weapon_creator/enemy_filter_show_2d" const SETTING_SHOW_3D = "weapon_creator/enemy_filter_show_3d" func _supports_2d_3d_filter() -> bool: return true func _get_filter_setting_2d_key() -> String: return SETTING_SHOW_2D func _get_filter_setting_3d_key() -> String: return SETTING_SHOW_3D func _build_header_buttons(header_hbox: HBoxContainer) -> void: var create_button = Button.new() create_button.text = "Create Enemy" create_button.pressed.connect(_on_create_enemy_pressed) header_hbox.add_child(create_button) func refresh() -> void: _clear_grid() if not DirAccess.dir_exists_absolute(_enemies_dir): _add_error_label("Enemies directory not found: " + _enemies_dir) return var show_2d = _should_show_2d() var show_3d = _should_show_3d() var enemy_count = 0 # Load 2D enemies if show_2d: enemy_count += _load_enemies_from_directory(_enemies_dir, false) # Load 3D enemies if show_3d: enemy_count += _load_enemies_from_directory(_enemies_dir, true) if enemy_count == 0: _add_info_label("No enemies found") func _load_enemies_from_directory(dir_path: String, is_3d: bool) -> int: var dir = DirAccess.open(dir_path) if dir == null: return 0 var count = 0 dir.list_dir_begin() var file_name = dir.get_next() while file_name != "": if not dir.current_is_dir() and (file_name.ends_with(".tres") or file_name.ends_with(".res")): var enemy_path = dir_path + file_name # Check if this matches the 2D/3D filter based on filename var file_is_3d = "_3D" in file_name if file_is_3d != is_3d: file_name = dir.get_next() continue var enemy_resource = load(enemy_path) if enemy_resource and enemy_resource.get_script(): var script_path = enemy_resource.get_script().resource_path if script_path and "EnemyResource.cs" in script_path: _create_enemy_card(enemy_resource, enemy_path, is_3d) count += 1 file_name = dir.get_next() dir.list_dir_end() return count func _create_enemy_card(enemy_resource: Resource, enemy_path: String, is_3d: bool) -> void: var card = PanelContainer.new() card.custom_minimum_size = Vector2(120, 140) card.tooltip_text = enemy_path _grid_container.add_child(card) var vbox = VBoxContainer.new() vbox.add_theme_constant_override("separation", 4) card.add_child(vbox) var preview_container = CenterContainer.new() preview_container.custom_minimum_size = Vector2(0, 64) vbox.add_child(preview_container) var icon_sprite = enemy_resource.get("IconSprite") if icon_sprite: var texture_rect = TextureRect.new() texture_rect.texture = icon_sprite texture_rect.custom_minimum_size = Vector2(64, 64) texture_rect.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL texture_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED preview_container.add_child(texture_rect) else: var placeholder = Label.new() placeholder.text = "No Icon" placeholder.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER preview_container.add_child(placeholder) var enemy_name = enemy_resource.get("EnemyName") var name_label = Label.new() name_label.text = str(enemy_name) if enemy_name else "Unnamed" name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER name_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART vbox.add_child(name_label) var mode_badge = Label.new() mode_badge.text = "3D" if is_3d else "2D" mode_badge.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER mode_badge.add_theme_color_override("font_color", Color.CYAN if is_3d else Color.LIGHT_CORAL) vbox.add_child(mode_badge) var button_hbox = HBoxContainer.new() button_hbox.alignment = BoxContainer.ALIGNMENT_CENTER button_hbox.add_theme_constant_override("separation", 4) vbox.add_child(button_hbox) var view_button = Button.new() view_button.text = "View" view_button.pressed.connect(_on_view_enemy_pressed.bind(enemy_path)) button_hbox.add_child(view_button) var duplicate_button = Button.new() duplicate_button.text = "Duplicate" duplicate_button.pressed.connect(_on_duplicate_enemy_pressed.bind(enemy_resource, enemy_path, is_3d)) button_hbox.add_child(duplicate_button) var delete_button = Button.new() delete_button.text = "Delete" delete_button.pressed.connect(_on_delete_enemy_pressed.bind(enemy_resource, enemy_path)) button_hbox.add_child(delete_button) func _on_view_enemy_pressed(enemy_path: String) -> void: if _editor_interface: _editor_interface.edit_resource(load(enemy_path)) enemy_selected.emit(enemy_path) func _on_duplicate_enemy_pressed(enemy_resource: Resource, enemy_path: String, is_3d: bool) -> void: var enemy_name = enemy_resource.get("EnemyName") enemy_duplication_started.emit(str(enemy_name), is_3d) var enemy_data = { "enemy_name": str(enemy_resource.get("EnemyName")), "enemy_key": str(enemy_resource.get("EnemyKey")) + "_COPY", "prefab_path": str(enemy_resource.get("PrefabPath")), "is_3d": is_3d, "max_health": float(enemy_resource.get("MaxHealth")), "movement_speed": float(enemy_resource.get("MovementSpeed")), "motivation_reward": float(enemy_resource.get("MotivationReward")), "weapon_resource": enemy_resource.get("Weapon"), "player_detection_range": float(enemy_resource.get("PlayerDetectionRange")), "view_range": float(enemy_resource.get("ViewRange")), "alarm_react_range": float(enemy_resource.get("AlarmReactRange")), "player_disengage_range": float(enemy_resource.get("PlayerDisengageRange")), "strafe_speed": float(enemy_resource.get("StrafeSpeed")), "max_strafe_distance": float(enemy_resource.get("MaxStrafeDistance")), "min_strafe_distance": float(enemy_resource.get("MinStrafeDistance")), "response_time": float(enemy_resource.get("ResponseTime")), "predict_player": bool(enemy_resource.get("PredictPlayer")), "icon_sprite": enemy_resource.get("IconSprite") } var dialog_script = load("res://addons/weapon_creator/EnemyCreatorDialog.gd") var dialog = Window.new() dialog.set_script(dialog_script) dialog.setup(_editor_interface, enemy_data) add_child(dialog) dialog.popup_centered() dialog.enemy_data_confirmed.connect(func(data): duplicate_enemy_requested.emit(data) ) func _on_delete_enemy_pressed(enemy_resource: Resource, enemy_path: String) -> void: var enemy_name = enemy_resource.get("EnemyName") var confirm_dialog = ConfirmationDialog.new() confirm_dialog.dialog_text = "Are you sure you want to delete enemy '" + str(enemy_name) + "'?\nThis cannot be undone." confirm_dialog.title = "Confirm Deletion" add_child(confirm_dialog) confirm_dialog.popup_centered() confirm_dialog.confirmed.connect(func(): _delete_enemy_file(enemy_path) enemy_deleted.emit(str(enemy_name), enemy_path) refresh() confirm_dialog.queue_free() ) confirm_dialog.canceled.connect(func(): confirm_dialog.queue_free() ) func _delete_enemy_file(enemy_path: String) -> void: var err = DirAccess.remove_absolute(enemy_path) if err != OK: push_error("Failed to delete enemy file: " + enemy_path) else: if _editor_interface: _editor_interface.get_resource_filesystem().scan() func _on_create_enemy_pressed() -> void: var dialog_script = load("res://addons/weapon_creator/EnemyCreatorDialog.gd") var dialog = Window.new() dialog.set_script(dialog_script) dialog.setup(_editor_interface) add_child(dialog) dialog.popup_centered() dialog.enemy_data_confirmed.connect(func(enemy_data): duplicate_enemy_requested.emit(enemy_data) )