From 233099c4dffe4e0b8575839a5f5694701f9fbd9f Mon Sep 17 00:00:00 2001 From: Thanatos Date: Thu, 10 Aug 2023 17:52:59 +0200 Subject: [PATCH 1/4] Add SR to view the scenario files --- dread_editor/bmsad_editor.py | 17 +- dread_editor/file_browser.py | 27 +- dread_editor/level_data_common.py | 32 ++ .../{level_data.py => level_data_dread.py} | 35 +- dread_editor/level_data_sr.py | 386 ++++++++++++++++++ dread_editor/main_loop.py | 38 +- dread_editor/type_render.py | 29 +- 7 files changed, 495 insertions(+), 69 deletions(-) create mode 100644 dread_editor/level_data_common.py rename dread_editor/{level_data.py => level_data_dread.py} (94%) create mode 100644 dread_editor/level_data_sr.py diff --git a/dread_editor/bmsad_editor.py b/dread_editor/bmsad_editor.py index 4c0f06f..44ecbec 100644 --- a/dread_editor/bmsad_editor.py +++ b/dread_editor/bmsad_editor.py @@ -1,7 +1,7 @@ import construct import imgui -from mercury_engine_data_structures import type_lib -from mercury_engine_data_structures.file_tree_editor import FileTreeEditor +from mercury_engine_data_structures.type_lib import TypeLib +from mercury_engine_data_structures.file_tree_editor import FileTreeEditor, Game from mercury_engine_data_structures.formats import Bmsad from mercury_engine_data_structures.formats.bmsad import find_charclass_for_type @@ -9,13 +9,14 @@ from dread_editor.file_editor import FileEditor from dread_editor.type_render import TypeTreeRender -bmsad_tree_render = TypeTreeRender() - +# TODO: Implement it for SR if we support it class BmsadEditor(FileEditor): def __init__(self, bmsad: Bmsad): self.bmsad = bmsad - self.string_vector = type_lib.get_type("base::global::CRntVector") + self.type_lib = TypeLib(Game.DREAD) + self.bmsad_tree_render = TypeTreeRender(self.type_lib) + self.string_vector = self.type_lib.get_type("base::global::CRntVector") def is_modified(self): return False @@ -48,7 +49,7 @@ def draw(self, current_scale: float): imgui.next_column() imgui.next_column() if node_open: - changed, new_field = bmsad_tree_render.render_value_of_type(prop.sub_actors, self.string_vector, + changed, new_field = self.bmsad_tree_render.render_value_of_type(prop.sub_actors, self.string_vector, "sub_actors") if changed: prop.sub_actors = new_field @@ -66,9 +67,9 @@ def draw(self, current_scale: float): # Fields if component.fields is not None and imgui_util.tree_node_with_column( f"Fields ##{component_key}_fields", imgui.TREE_NODE_DEFAULT_OPEN): - changed, new_field = bmsad_tree_render.render_value_of_type( + changed, new_field = self.bmsad_tree_render.render_value_of_type( component.fields.fields, - type_lib.get_type(find_charclass_for_type(component.type)), + self.type_lib.get_type(find_charclass_for_type(component.type)), f"{component_key}" ) if changed: diff --git a/dread_editor/file_browser.py b/dread_editor/file_browser.py index d9b9139..ae0e532 100644 --- a/dread_editor/file_browser.py +++ b/dread_editor/file_browser.py @@ -1,41 +1,50 @@ import typing import imgui -from mercury_engine_data_structures import type_lib -from mercury_engine_data_structures.file_tree_editor import FileTreeEditor +from mercury_engine_data_structures.type_lib import TypeLib +from mercury_engine_data_structures.file_tree_editor import FileTreeEditor, Game from mercury_engine_data_structures.formats import Bmsad from dread_editor.bmsad_editor import BmsadEditor from dread_editor.file_editor import FileEditor, GenericEditor from dread_editor.type_render import TypeTreeRender -tree_render = TypeTreeRender() - def create_editor_reader(type_name: str): - type_data = type_lib.get_type(type_name) + type_lib = TypeLib(Game.DREAD) + type_data = type_lib.get_type( type_name) return lambda path, tree_editor: GenericEditor( tree_editor.get_parsed_asset(path), - tree_render, + TypeTreeRender(type_lib), type_data, ) -file_types = { +file_types_dread = { ".bmsad": lambda path, tree_editor: BmsadEditor(tree_editor.get_parsed_asset(path, type_hint=Bmsad)), ".bmmap": create_editor_reader('CMinimapData'), ".bmscu": create_editor_reader('CCutSceneDef'), ".brsa": create_editor_reader("gameeditor::CGameModelRoot"), } +file_types_sr = { + ".bmsad": lambda path, tree_editor: BmsadEditor(tree_editor.get_parsed_asset(path, type_hint=Bmsad)), +} + +file_types = { + Game.SAMUS_RETURNS: file_types_sr, + Game.DREAD: file_types_dread +} + class FileBrowser: _is_open: bool = False filter: str = "" - def __init__(self, tree_editor: FileTreeEditor): + def __init__(self, tree_editor: FileTreeEditor, game: Game): self.tree_editor = tree_editor self.all_files_tree = {} + self.game = game for asset_name in sorted(tree_editor.all_asset_names()): name_tree = asset_name.split("/") @@ -96,7 +105,7 @@ def draw_tree(parent_path: str, body: dict[str, typing.Any]): full_path.parent.mkdir(parents=True, exist_ok=True) full_path.write_bytes(self.tree_editor.get_raw_asset(full_name)) - for extension, build in file_types.items(): + for extension, build in file_types.get(self.game).items(): if name.endswith(extension): if imgui.button("Open"): open_editors[full_name] = build(full_name, self.tree_editor) diff --git a/dread_editor/level_data_common.py b/dread_editor/level_data_common.py new file mode 100644 index 0000000..2b89553 --- /dev/null +++ b/dread_editor/level_data_common.py @@ -0,0 +1,32 @@ +import typing +import imgui + +from dread_editor.type_render import SpecificTypeRender +from dread_editor import imgui_util +from mercury_engine_data_structures.type_lib import BaseType + +class LevelData: + def open_actor_link(self, link: str): + raise NotImplementedError("Not implemented") + + +class GameLinkRender(SpecificTypeRender): + def __init__(self, level_data: LevelData): + self.level_data = level_data + + def uses_one_column(self, type_data: BaseType): + return True + + def create_default(self, type_data: BaseType): + return "" + + def render_value(self, value: typing.Any, type_data: BaseType, path: str): + if isinstance(value, str) and value.startswith("Root"): + if imgui.button(value): + self.level_data.open_actor_link(value) + imgui_util.set_hovered_tooltip(value) + else: + imgui.text(str(value)) + return False, None + + diff --git a/dread_editor/level_data.py b/dread_editor/level_data_dread.py similarity index 94% rename from dread_editor/level_data.py rename to dread_editor/level_data_dread.py index b82cf53..13e6797 100644 --- a/dread_editor/level_data.py +++ b/dread_editor/level_data_dread.py @@ -6,14 +6,15 @@ import typing import imgui -from mercury_engine_data_structures import type_lib -from mercury_engine_data_structures.file_tree_editor import FileTreeEditor +from mercury_engine_data_structures.type_lib import TypeLib +from mercury_engine_data_structures.file_tree_editor import FileTreeEditor, Game from mercury_engine_data_structures.formats import Brsa, Brfld, Bmscc from mercury_engine_data_structures.formats.dread_types import CActor from mercury_engine_data_structures.type_lib import BaseType from dread_editor import imgui_util from dread_editor.actor_filter import ActorFilter +from dread_editor.level_data_common import GameLinkRender, LevelData from dread_editor.preferences import global_preferences, save_preferences from dread_editor.type_render import TypeTreeRender, SpecificTypeRender @@ -31,7 +32,7 @@ def get_subareas(pkg_editor: FileTreeEditor, brfld_path: str) -> set[str]: return cams -class LevelData: +class LevelDataDread(LevelData): def __init__(self, file_name: str, brfld: Brfld, bmscc: Bmscc, valid_cameras: dict[str, bool], display_borders: dict[str, float]): @@ -48,8 +49,9 @@ def __init__(self, file_name: str, brfld: Brfld, bmscc: Bmscc, valid_cameras: di self.highlighted_actors_in_canvas = [] self.actor_filter = ActorFilter() self.copy_actor_name = "" + self.type_lib = TypeLib(Game.DREAD) - self.tree_render = TypeTreeRender() + self.tree_render = TypeTreeRender(self.type_lib) for k in ["CGameLink", "CGameLink"]: self.tree_render.specific_renders[k] = GameLinkRender(self) @@ -326,7 +328,7 @@ def draw_visible_actors(self, current_scale: float): actor = self.brfld.actors_for_layer(layer_name)[actor_name] imgui.columns(2, "actor details") self.tree_render.render_value_of_type( - actor, type_lib.get_type(actor["@type"]), + actor, type_lib.get_type(Game.DREAD, actor["@type"]), f"{self.file_name}.{layer_name}.{actor_name}", ) imgui.columns(1, "actor details") @@ -356,28 +358,9 @@ def apply_changes_to(self, pkg_editor: FileTreeEditor): # pkg_editor.ensure_present(pkg_name, bmsad) -class GameLinkRender(SpecificTypeRender): - def __init__(self, level_data: LevelData): - self.level_data = level_data - - def uses_one_column(self, type_data: BaseType): - return True - - def create_default(self, type_data: BaseType): - return "" - - def render_value(self, value: typing.Any, type_data: BaseType, path: str): - if isinstance(value, str) and value.startswith("Root"): - if imgui.button(value): - self.level_data.open_actor_link(value) - imgui_util.set_hovered_tooltip(value) - else: - imgui.text(str(value)) - return False, None - class InnerValueRender(SpecificTypeRender): - def __init__(self, level_data: LevelData): + def __init__(self, level_data: LevelDataDread): self.level_data = level_data self.cache = {} @@ -393,7 +376,7 @@ def render_value(self, value: bytes, type_data: BaseType, path: str): result = value changed, new_actor = self.level_data.tree_render.render_value_of_type( - self.cache[path], type_lib.get_type("CActor"), + self.cache[path], self.level_data.type_lib.get_type("CActor"), f"{path}.actor", ) if changed: diff --git a/dread_editor/level_data_sr.py b/dread_editor/level_data_sr.py new file mode 100644 index 0000000..ecd0a1e --- /dev/null +++ b/dread_editor/level_data_sr.py @@ -0,0 +1,386 @@ +import colorsys +import copy +import hashlib +import os +import struct +import typing + +import imgui +from mercury_engine_data_structures.file_tree_editor import FileTreeEditor, Game +from mercury_engine_data_structures.formats import Bmscc, Bmsld +from mercury_engine_data_structures.formats.bmsld import ProperActor +from mercury_engine_data_structures.type_lib import BaseType +from mercury_engine_data_structures.type_lib import TypeLib + +from dread_editor import imgui_util +from dread_editor.actor_filter import ActorFilter +from dread_editor.level_data_common import GameLinkRender, LevelData +from dread_editor.preferences import global_preferences, save_preferences +from dread_editor.type_render import TypeTreeRender, SpecificTypeRender + + +def get_subareas(pkg_editor: FileTreeEditor, bmsld_path: str) -> set[str]: + cams: set[str] = set() + + bmscc = typing.cast(Bmscc, pkg_editor.get_parsed_asset(bmsld_path.replace(".bmsld", ".bmscc"))) + + for subarea in bmscc.raw.layers[0].entries: + cams.add(subarea.name) + + return cams + + +class LevelDataSR(LevelData): + def __init__(self, file_name: str, bmsld: Bmsld, bmscc: Bmscc, valid_cameras: dict[str, bool], + display_borders: dict[str, float]): + + self.preferences = global_preferences.get(file_name, {}) + global_preferences[file_name] = self.preferences + self.preferences["layers"] = self.preferences.get("layers", {}) + + self.file_name = file_name + self.bmsld = bmsld + self.bmscc = bmscc + self.visible_actors = {} + self.valid_cameras = valid_cameras + self.display_borders = display_borders + self.highlighted_actors_in_canvas = [] + self.actor_filter = ActorFilter() + self.copy_actor_name = "" + self.type_lib = TypeLib(Game.SAMUS_RETURNS) + + self.tree_render = TypeTreeRender(self.type_lib) + for k in ["CGameLink", "CGameLink"]: + self.tree_render.specific_renders[k] = GameLinkRender(self) + + self.tree_render.specific_renders["base::global::CRntFile"] = InnerValueRenderSR(self) + + for layer_index in range(len(bmsld.raw.actors)): + if layer_index not in self.visible_layers: + self.visible_layers[str(layer_index)] = True + + @classmethod + def open_file(cls, pkg_editor: FileTreeEditor, file_name: str): + bmsld = typing.cast(Bmsld, pkg_editor.get_parsed_asset(file_name)) + + valid_cameras = { + key: True + for key in sorted(get_subareas(pkg_editor, file_name)) + } + bmscc = typing.cast(Bmscc, pkg_editor.get_parsed_asset(file_name.replace(".bmsld", ".bmscc"))) + + display_borders: dict[str, float] = {"left": 0, "right": 0, "top": 0, "bottom": 0} + for entry in bmscc.raw.layers[0].entries: + x1, y1, x2, y2 = entry.data.total_boundings + if abs(x1) > 59999 or abs(y1) > 59999 or abs(x2) > 59999 or abs(y2) > 59999: + if entry.name in valid_cameras: + valid_cameras.pop(entry.name) + continue + display_borders["left"] = min(display_borders["left"], x1) + display_borders["bottom"] = min(display_borders["bottom"], y1) + display_borders["right"] = max(display_borders["right"], x2) + display_borders["top"] = max(display_borders["top"], y2) + + return cls(file_name, bmsld, bmscc, valid_cameras, display_borders) + + @property + def visible_layers(self) -> dict[str, bool]: + return self.preferences["layers"] + + @property + def render_scale(self): + return self.preferences.get("render_scale", 500.0) + + @render_scale.setter + def render_scale(self, value): + self.preferences["render_scale"] = value + save_preferences() + + def open_actor_link(self, link: str): + if (actor := self.bmsld.follow_link(link)) is not None: + layer_name = link.split(":")[4] + print(actor) + self.visible_actors[(layer_name, actor.sName)] = True + + def render_actor_context_menu(self, layer_name: str, actor): + if self.copy_actor_name is None: + self.copy_actor_name = f"{actor.sName}_Copy" + + if imgui.button("Duplicate Actor"): + new_actor = copy.deepcopy(actor) + new_actor.sName = self.copy_actor_name + self.add_new_actor(layer_name, new_actor) + imgui.close_current_popup() + self.copy_actor_name = None + + imgui.same_line() + self.copy_actor_name = imgui.input_text( + "New actor name", + self.copy_actor_name or "", + 500 + )[1] + + imgui.text("Position:") + imgui.same_line() + changed_x, x = imgui.slider_float("##actor-context-position-x", actor.vPos[0], + self.display_borders["left"], self.display_borders["right"]) + imgui.same_line() + changed_y, y = imgui.slider_float("##actor-context-position-y", actor.vPos[1], + self.display_borders["top"], self.display_borders["bottom"]) + if changed_x or changed_y: + actor.vPos = (x, y, actor.vPos[2]) + + def add_new_actor(self, layer_name: str, actor): + if actor is not None: + self.bmsld.actors_for_layer(layer_name)[actor.sName] = actor + self.visible_actors[(layer_name, actor.sName)] = True + + def render_window(self, current_scale): + imgui.set_next_window_size(900 * current_scale, 300 * current_scale, imgui.FIRST_USE_EVER) + expanded, opened = imgui.begin(os.path.basename(self.file_name), True) + + if not opened: + imgui.end() + return False + + def color_for_layer(name: str): + d = hashlib.blake2b(name.encode("utf-8"), digest_size=4).digest() + d = struct.unpack("=L", d)[0] / 0xFFFFFFFF + r, g, b = colorsys.hsv_to_rgb(d, 1, 1) + return [r, g, b, 1] + + highlighted_actors_in_list = set() + + with imgui_util.with_group(): + imgui.text("Actor Layers") + with imgui_util.with_child("##ActorLayers", 300 * current_scale, 0, + imgui.WINDOW_ALWAYS_VERTICAL_SCROLLBAR): + self.actor_filter.draw(current_scale) + imgui.columns(2, "actor layers") + imgui.set_column_width(-1, 20 * current_scale) + for layer_index in range(len(self.bmsld.raw.actors)): + layer_name = str(layer_index) + changed, self.visible_layers[layer_name] = imgui.checkbox(f"##{layer_name}_visible", + self.visible_layers[layer_name]) + if changed: + save_preferences() + + imgui.next_column() + if imgui_util.colored_tree_node(layer_name, color_for_layer(layer_name)): + actors = self.bmsld.raw.actors[int(layer_name)] + + for actor_name, actor in actors.items(): + key = (layer_name, actor_name) + if not self.actor_filter.passes(actor): + continue + + def do_item(): + self.visible_actors[key] = imgui.checkbox( + f"{actor_name} ##{layer_name}_{actor_name}", self.visible_actors.get(key) + )[1] + + if (layer_name, actor) in self.highlighted_actors_in_canvas: + with imgui.colored(imgui.COLOR_TEXT, 1, 1, 0.2): + do_item() + else: + do_item() + + if imgui.is_item_hovered(): + highlighted_actors_in_list.add(key) + + if imgui.begin_popup_context_item(): + self.render_actor_context_menu(layer_name, actor) + imgui.end_popup() + + imgui.tree_pop() + imgui.next_column() + imgui.columns(1, "actor layers") + + imgui.same_line() + + with imgui_util.with_group(): + imgui.text("Camera Groups") + with imgui_util.with_child("##CameraSections", 200 * current_scale, 0, + imgui.WINDOW_ALWAYS_VERTICAL_SCROLLBAR): + highlighted_section = None + for key in self.valid_cameras.keys(): + self.valid_cameras[key] = imgui.checkbox(key, self.valid_cameras[key])[1] + if imgui.is_item_hovered(): + highlighted_section = key + + imgui.same_line() + + with imgui_util.with_child("##Canvas", 0, 0): + changed, new_scale = imgui.slider_float("Scale", self.render_scale, 100, 1000) + if changed: + self.render_scale = new_scale + + self.display_borders["left"], self.display_borders["right"] = imgui.slider_float2( + "Left and right borders", + self.display_borders["left"], + self.display_borders["right"], + -59999, + 59999, + )[1] + self.display_borders["top"], self.display_borders["bottom"] = imgui.slider_float2( + "Top and bottom borders", + self.display_borders["top"], + self.display_borders["bottom"], + 59999, + -59999, + )[1] + + imgui.separator() + + mouse = imgui.get_mouse_pos() + canvas_po = imgui.get_cursor_screen_pos() + actual_scale = self.render_scale * current_scale + draw_list = imgui.get_window_draw_list() + + def lerp_x(x): + lx = (x - self.display_borders["left"]) / ( + self.display_borders["right"] - self.display_borders["left"]) + return lx * actual_scale + canvas_po.x + + def lerp_y(y): + ly = (y - self.display_borders["top"]) / ( + self.display_borders["bottom"] - self.display_borders["top"]) + return ly * actual_scale + canvas_po.y + + for entry in self.bmscc.raw.layers[0].entries: + if not self.valid_cameras.get(entry.name): + continue + + raw_vertices = [ + (lerp_x(v.x), lerp_y(v.y)) + for v in entry.data.polys[0].points + ] + if highlighted_section == entry.name: + draw_list.add_polyline(raw_vertices, imgui.get_color_u32_rgba(0.2, 0.8, 1, 1.0), + closed=True, + thickness=5) + else: + draw_list.add_polyline(raw_vertices, imgui.get_color_u32_rgba(0.2, 0.2, 1, 0.8), + closed=True, + thickness=3) + + self.highlighted_actors_in_canvas = [] + + for layer_index in range(len(self.bmsld.raw.actors)): + layer_name = str(layer_index) + if not self.visible_layers[layer_name]: + continue + + color = imgui.get_color_u32_rgba(*color_for_layer(layer_name)) + for actor_name in self.bmsld.raw.actors[layer_index]: + actor = self.bmsld.raw.actors[layer_index][actor_name] + if "x" not in actor: + # TODO: vPos might be a required field. Re-visit after editor fields + continue + + final_x = lerp_x(actor.x) + final_y = lerp_y(actor.y) + if (layer_name, actor_name) in highlighted_actors_in_list: + draw_list.add_circle_filled(final_x, final_y, 15, imgui.get_color_u32_rgba(1, 1, 1, 1)) + else: + draw_list.add_circle_filled(final_x, final_y, 5, color) + + if (mouse.x - final_x) ** 2 + (mouse.y - final_y) ** 2 < 5 * 5: + self.highlighted_actors_in_canvas.append((layer_name, actor_name)) + + if self.highlighted_actors_in_canvas and imgui.is_window_hovered(): + imgui.begin_tooltip() + for layer_name, actor_name in self.highlighted_actors_in_canvas: + imgui.text(f"{layer_name} - {actor_name}") + if imgui.is_mouse_double_clicked(0): + self.visible_actors[(layer_name, actor_name)] = True + imgui.end_tooltip() + + if len(self.highlighted_actors_in_canvas) == 1: + layer_name, actor_name = self.highlighted_actors_in_canvas[0] + if imgui.is_mouse_released(1): + print("OPEN THE POPUP!", f"canvas_actor_context_{layer_name}_{actor_name}") + imgui.open_popup(f"canvas_actor_context_{layer_name}_{actor_name}") + + # for sub_areas in self.bmsld.raw.sub_areas: + # layer_name = sub_areas.name + # for actor in list(self.bmsld.actors_for_layer(layer_name).values()): + # if imgui.begin_popup(f"canvas_actor_context_{layer_name}_{actor.sName}", + # imgui.WINDOW_ALWAYS_AUTO_RESIZE | imgui.WINDOW_NO_TITLE_BAR | + # imgui.WINDOW_NO_SAVED_SETTINGS): + # self.render_actor_context_menu(layer_name, actor) + # imgui.end_popup() + + imgui.end() + return True + + def draw_visible_actors(self, current_scale: float): + for (layer_name, actor_name), active in list(self.visible_actors.items()): + if not active: + continue + + imgui.set_next_window_size(300 * current_scale, 200 * current_scale, imgui.FIRST_USE_EVER) + path = f"{self.file_name}_{layer_name}_{actor_name}" + active = imgui.begin(f"Actor: {layer_name} - {actor_name} ##{path}", active)[1] + if not active: + self.visible_actors[(layer_name, actor_name)] = False + imgui.end() + continue + + actor = self.bmsld.raw.actors[int(layer_name)][actor_name] + imgui.columns(2, "actor details") + self.tree_render.render_value_of_type( + actor, self.type_lib.get_type("ProperActor"), + f"{self.file_name}.{layer_name}.{actor_name}", + ) + imgui.columns(1, "actor details") + + imgui.separator() + imgui.text("Actor Groups") + + with imgui_util.with_child("##ActorGroups", 0, 300 * current_scale, + imgui.WINDOW_ALWAYS_VERTICAL_SCROLLBAR): + for group_name in sorted(self.bmsld.all_actor_groups()): + changed, present = imgui.checkbox(f"{group_name} ##actor_group.{group_name}", + self.bmsld.is_actor_in_group(group_name, actor_name, layer_name)) + # if changed: + # if present: + # self.bmsld.add_actor_to_group(group_name, actor_name, layer_name) + # else: + # self.bmsld.remove_actor_from_group(group_name, actor_name, layer_name) + + imgui.end() + + def apply_changes_to(self, pkg_editor: FileTreeEditor): + pkg_editor.replace_asset(self.file_name, self.bmsld.build()) + # for actor in self.bmsld.all_actors(): + # bmsad = actor.oActorDefLink.removeprefix("actordef:") + # + # for pkg_name in pkg_editor.find_pkgs(self.file_name): + # pkg_editor.ensure_present(pkg_name, bmsad) + +class InnerValueRenderSR(SpecificTypeRender): + def __init__(self, level_data: LevelDataSR): + self.level_data = level_data + self.cache = {} + + def uses_one_column(self, type_data: BaseType): + return False + + def create_default(self, type_data: BaseType): + return b"" + + def render_value(self, value: bytes, type_data: BaseType, path: str): + if path not in self.cache: + self.cache[path] = ProperActor.parse(value) + + result = value + changed, new_actor = self.level_data.tree_render.render_value_of_type( + self.cache[path], self.level_data.type_lib.get_type("ProperActor"), + f"{path}.actor", + ) + if changed: + self.cache[path] = new_actor + result = ProperActor.build(new_actor) + + return changed, result \ No newline at end of file diff --git a/dread_editor/main_loop.py b/dread_editor/main_loop.py index 8fb638b..3471779 100644 --- a/dread_editor/main_loop.py +++ b/dread_editor/main_loop.py @@ -15,7 +15,9 @@ from dread_editor import type_render, imgui_util from dread_editor.file_browser import FileBrowser from dread_editor.file_editor import FileEditor -from dread_editor.level_data import LevelData +from dread_editor.level_data_common import LevelData +from dread_editor.level_data_dread import LevelDataDread +from dread_editor.level_data_sr import LevelDataSR from dread_editor.preferences import global_preferences, load_preferences, save_preferences from dread_editor.type_render import SpecificTypeRender, TypeTreeRender @@ -169,18 +171,21 @@ def loop(): file_browser: Optional[FileBrowser] = None pkg_editor: Optional[FileTreeEditor] = None current_error_message = None + current_game = None pending_load_last_romfs = True - possible_brfld = [] + # brfld (dread) or bmsld (samus returns) + possible_level_files = [] def load_romfs(path: Path): - nonlocal pkg_editor, possible_brfld, file_browser - pkg_editor = FileTreeEditor(path, Game.DREAD) - possible_brfld = [ + nonlocal pkg_editor, possible_level_files, file_browser + pkg_editor = FileTreeEditor(path, current_game) + level_file_name = "bmsld" if current_game == Game.SAMUS_RETURNS else "brfld" + possible_level_files = [ asset_name for asset_name in pkg_editor.all_asset_names() - if asset_name.endswith("brfld") + if asset_name.endswith(level_file_name) ] - possible_brfld.sort() + possible_level_files.sort() all_bmsad = [ asset_name for asset_name in pkg_editor.all_asset_names() @@ -191,9 +196,10 @@ def load_romfs(path: Path): all_bmsad_actordefs.clear() all_bmsad_actordefs.extend(f"actordef:{asset_name}" for asset_name in all_bmsad) - file_browser = FileBrowser(pkg_editor) + file_browser = FileBrowser(pkg_editor, current_game) global_preferences["last_romfs"] = str(path) + global_preferences["last_game"] = current_game.value save_preferences() while not glfw.window_should_close(window): @@ -217,6 +223,12 @@ def load_romfs(path: Path): if imgui.menu_item("Select extracted Metroid Dread root")[0]: f = prompt_file(directory=True) if f: + current_game = Game.DREAD + load_romfs(Path(f)) + if imgui.menu_item("Select extracted Samus Returns root")[0]: + f = prompt_file(directory=True) + if f: + current_game = Game.SAMUS_RETURNS load_romfs(Path(f)) imgui.text_disabled(f'* Current root: {global_preferences.get("last_romfs")}') @@ -242,14 +254,14 @@ def load_romfs(path: Path): imgui.end_menu() - if imgui.begin_menu("Select level file", len(possible_brfld) > 0): + if imgui.begin_menu("Select level file", len(possible_level_files) > 0): current_file_name = None if current_level_data is not None: current_file_name = current_level_data.file_name - for name in possible_brfld: + for name in possible_level_files: if imgui.menu_item(name, "", name == current_file_name)[0]: - current_level_data = LevelData.open_file(pkg_editor, name) + current_level_data = LevelDataSR.open_file(pkg_editor, name) if current_game == Game.SAMUS_RETURNS else LevelDataDread.open_file(pkg_editor, name) add_custom_type_renders(current_level_data.tree_render) imgui.end_menu() @@ -276,13 +288,15 @@ def load_romfs(path: Path): impl.render(imgui.get_draw_data()) glfw.swap_buffers(window) - if pending_load_last_romfs and global_preferences.get("last_romfs") is not None: + if pending_load_last_romfs and global_preferences.get("last_romfs") and global_preferences.get("last_game") is not None: pending_load_last_romfs = False try: + current_game = Game(global_preferences["last_game"]) load_romfs(Path(global_preferences["last_romfs"])) except Exception as e: logging.exception(f"Unable to re-open last romfs: {e}") global_preferences["last_romfs"] = None + global_preferences["last_game"] = None save_preferences() impl.shutdown() diff --git a/dread_editor/type_render.py b/dread_editor/type_render.py index c597c2a..5f6a4df 100644 --- a/dread_editor/type_render.py +++ b/dread_editor/type_render.py @@ -2,12 +2,12 @@ import typing import imgui -from mercury_engine_data_structures import type_lib from mercury_engine_data_structures.type_lib import ( TypeKind, VectorType, DictionaryType, PointerType, EnumType, StructType, PrimitiveType, PrimitiveKind, - TypedefType, FlagsetType, BaseType + TypedefType, FlagsetType, BaseType, TypeLib ) +from mercury_engine_data_structures.file_tree_editor import Game from dread_editor import imgui_util @@ -29,7 +29,7 @@ def render_int(value, path: str): def render_string(value, path: str): - return imgui.input_text(f"##{path}", value, 500) + return imgui.input_text(f"##{path}", str(value), 500) def render_float_vector(value, path: str): @@ -88,10 +88,11 @@ def render_value(self, value: typing.Any, type_data: BaseType, path: str): class TypeTreeRender: specific_renders: dict[str, SpecificTypeRender] - def __init__(self): + def __init__(self, type_lib: TypeLib): self._debug_once = set() self.memory = {} self.specific_renders = {} + self.type_lib = type_lib def print_once(self, path, msg): if path not in self._debug_once: @@ -204,7 +205,7 @@ def new_item_prompt(): return imgui.button("New Item"), value.append return self._render_container_of_type( - value, type_lib.get_type(type_data.value_type), path, + value, self.type_lib.get_type(type_data.value_type), path, imgui.TREE_NODE_DEFAULT_OPEN if len(value) < 50 else 0, lambda v: enumerate(v), lambda k: f"Item {k}", @@ -213,7 +214,7 @@ def new_item_prompt(): ) def render_dict_of_type(self, value: dict, type_data: DictionaryType, path: str): - key_type = type_lib.get_type(type_data.key_type) + key_type = self.type_lib.get_type(type_data.key_type) if isinstance(key_type, PrimitiveType) and key_type.primitive_kind == PrimitiveKind.STRING: def new_item_prompt(): @@ -226,7 +227,7 @@ def item_add(new_item): return imgui.button("New Item"), item_add return self._render_container_of_type( - value, type_lib.get_type(type_data.value_type), path, + value, self.type_lib.get_type(type_data.value_type), path, 0, lambda v: v.items(), lambda k: k, @@ -240,7 +241,7 @@ def item_add(new_item): return False, value def render_ptr_of_type(self, value, type_data: PointerType, path: str): - all_options = sorted(type_lib.get_all_children_for(type_data.target)) + all_options = sorted(self.type_lib.get_all_children_for(type_data.target)) all_options.insert(0, "None") value_type_name: str @@ -264,7 +265,7 @@ def create_default(type_name: str): if value_type_name == "None": return None else: - return self.create_default_of_type(type_lib.get_type(value_type_name)) + return self.create_default_of_type(self.type_lib.get_type(value_type_name)) # TODO: this check doesn't make sense if self.type_uses_one_column(type_data): @@ -284,7 +285,7 @@ def create_default(type_name: str): if value_type_name == "None": imgui.text("None") else: - value_type_data = type_lib.get_type(value_type_name) + value_type_data = self.type_lib.get_type(value_type_name) if not self.type_uses_one_column(value_type_data): imgui.text(f"Expected type {value_type_name} to use one column") else: @@ -304,7 +305,7 @@ def create_default(type_name: str): value = create_default(value_type_name) if value_type_name != "None": - value_type_data = type_lib.get_type(value_type_name) + value_type_data = self.type_lib.get_type(value_type_name) value_changed, value = self.render_value_of_type(value, value_type_data, f"{path}.Deref") changed = changed or value_changed @@ -318,7 +319,7 @@ def render_enum_of_type(self, value: enum.IntEnum, type_data: EnumType, path: st return False, value def render_flagset_of_type(self, value: dict, type_data: FlagsetType, path: str) -> tuple[bool, typing.Any]: - enum_data = type_lib.get_type(type_data.enum) + enum_data = self.type_lib.get_type(type_data.enum) assert isinstance(enum_data, EnumType) changed, selected = imgui_util.combo_flagset(path, value, enum_data.enum_class()) @@ -331,12 +332,12 @@ def render_struct_of_type(self, value, type_data: StructType, path: str) -> tupl modified = False if type_data.parent is not None: - parent = type_lib.get_type(type_data.parent) + parent = self.type_lib.get_type(type_data.parent) assert isinstance(parent, StructType) modified, value = self.render_struct_of_type(value, parent, path) for field_name, field_type in type_data.fields.items(): - field_type_data = type_lib.get_type(field_type) + field_type_data = self.type_lib.get_type(field_type) field_path = f"{path}.{field_name}" tooltip = f"Field of class {type_data.name} of type {field_type_data.name}." From 513b409ef71997b7a1e535c8e0ceb9f0c40f9d62 Mon Sep 17 00:00:00 2001 From: Thanatos Date: Fri, 11 Aug 2023 07:26:49 +0200 Subject: [PATCH 2/4] Update for meds changes --- dread_editor/bmsad_editor.py | 6 +++--- dread_editor/file_browser.py | 4 ++-- dread_editor/level_data_dread.py | 6 +++--- dread_editor/level_data_sr.py | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/dread_editor/bmsad_editor.py b/dread_editor/bmsad_editor.py index 44ecbec..9d342ef 100644 --- a/dread_editor/bmsad_editor.py +++ b/dread_editor/bmsad_editor.py @@ -1,7 +1,7 @@ import construct import imgui -from mercury_engine_data_structures.type_lib import TypeLib -from mercury_engine_data_structures.file_tree_editor import FileTreeEditor, Game +from mercury_engine_data_structures.type_lib_instances import get_type_lib_dread +from mercury_engine_data_structures.file_tree_editor import FileTreeEditor from mercury_engine_data_structures.formats import Bmsad from mercury_engine_data_structures.formats.bmsad import find_charclass_for_type @@ -14,7 +14,7 @@ class BmsadEditor(FileEditor): def __init__(self, bmsad: Bmsad): self.bmsad = bmsad - self.type_lib = TypeLib(Game.DREAD) + self.type_lib = get_type_lib_dread() self.bmsad_tree_render = TypeTreeRender(self.type_lib) self.string_vector = self.type_lib.get_type("base::global::CRntVector") diff --git a/dread_editor/file_browser.py b/dread_editor/file_browser.py index ae0e532..811939c 100644 --- a/dread_editor/file_browser.py +++ b/dread_editor/file_browser.py @@ -1,7 +1,7 @@ import typing import imgui -from mercury_engine_data_structures.type_lib import TypeLib +from mercury_engine_data_structures.type_lib_instances import get_type_lib_dread from mercury_engine_data_structures.file_tree_editor import FileTreeEditor, Game from mercury_engine_data_structures.formats import Bmsad @@ -11,7 +11,7 @@ def create_editor_reader(type_name: str): - type_lib = TypeLib(Game.DREAD) + type_lib = get_type_lib_dread() type_data = type_lib.get_type( type_name) return lambda path, tree_editor: GenericEditor( tree_editor.get_parsed_asset(path), diff --git a/dread_editor/level_data_dread.py b/dread_editor/level_data_dread.py index 13e6797..21219a0 100644 --- a/dread_editor/level_data_dread.py +++ b/dread_editor/level_data_dread.py @@ -6,7 +6,7 @@ import typing import imgui -from mercury_engine_data_structures.type_lib import TypeLib +from mercury_engine_data_structures.type_lib_instances import get_type_lib_dread from mercury_engine_data_structures.file_tree_editor import FileTreeEditor, Game from mercury_engine_data_structures.formats import Brsa, Brfld, Bmscc from mercury_engine_data_structures.formats.dread_types import CActor @@ -49,7 +49,7 @@ def __init__(self, file_name: str, brfld: Brfld, bmscc: Bmscc, valid_cameras: di self.highlighted_actors_in_canvas = [] self.actor_filter = ActorFilter() self.copy_actor_name = "" - self.type_lib = TypeLib(Game.DREAD) + self.type_lib = get_type_lib_dread() self.tree_render = TypeTreeRender(self.type_lib) for k in ["CGameLink", "CGameLink"]: @@ -328,7 +328,7 @@ def draw_visible_actors(self, current_scale: float): actor = self.brfld.actors_for_layer(layer_name)[actor_name] imgui.columns(2, "actor details") self.tree_render.render_value_of_type( - actor, type_lib.get_type(Game.DREAD, actor["@type"]), + actor, self.type_lib.get_type(actor["@type"]), f"{self.file_name}.{layer_name}.{actor_name}", ) imgui.columns(1, "actor details") diff --git a/dread_editor/level_data_sr.py b/dread_editor/level_data_sr.py index ecd0a1e..fc2b077 100644 --- a/dread_editor/level_data_sr.py +++ b/dread_editor/level_data_sr.py @@ -10,7 +10,7 @@ from mercury_engine_data_structures.formats import Bmscc, Bmsld from mercury_engine_data_structures.formats.bmsld import ProperActor from mercury_engine_data_structures.type_lib import BaseType -from mercury_engine_data_structures.type_lib import TypeLib +from mercury_engine_data_structures.type_lib_instances import get_type_lib_samus_returns from dread_editor import imgui_util from dread_editor.actor_filter import ActorFilter @@ -47,7 +47,7 @@ def __init__(self, file_name: str, bmsld: Bmsld, bmscc: Bmscc, valid_cameras: di self.highlighted_actors_in_canvas = [] self.actor_filter = ActorFilter() self.copy_actor_name = "" - self.type_lib = TypeLib(Game.SAMUS_RETURNS) + self.type_lib = get_type_lib_samus_returns() self.tree_render = TypeTreeRender(self.type_lib) for k in ["CGameLink", "CGameLink"]: From f33017bf4af38aa3cf847f5f850a868668a82704 Mon Sep 17 00:00:00 2001 From: Thanatos Date: Sun, 27 Aug 2023 16:39:32 +0200 Subject: [PATCH 3/4] Update for new MEDS and new imgui --- dread_editor/bmsad_editor.py | 2 +- dread_editor/file_browser.py | 2 +- dread_editor/level_data_dread.py | 6 +++--- dread_editor/level_data_sr.py | 7 +++---- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/dread_editor/bmsad_editor.py b/dread_editor/bmsad_editor.py index 9d342ef..4296faa 100644 --- a/dread_editor/bmsad_editor.py +++ b/dread_editor/bmsad_editor.py @@ -1,6 +1,6 @@ import construct import imgui -from mercury_engine_data_structures.type_lib_instances import get_type_lib_dread +from mercury_engine_data_structures.type_lib import get_type_lib_dread from mercury_engine_data_structures.file_tree_editor import FileTreeEditor from mercury_engine_data_structures.formats import Bmsad from mercury_engine_data_structures.formats.bmsad import find_charclass_for_type diff --git a/dread_editor/file_browser.py b/dread_editor/file_browser.py index 811939c..7e3556a 100644 --- a/dread_editor/file_browser.py +++ b/dread_editor/file_browser.py @@ -1,7 +1,7 @@ import typing import imgui -from mercury_engine_data_structures.type_lib_instances import get_type_lib_dread +from mercury_engine_data_structures.type_lib import get_type_lib_dread from mercury_engine_data_structures.file_tree_editor import FileTreeEditor, Game from mercury_engine_data_structures.formats import Bmsad diff --git a/dread_editor/level_data_dread.py b/dread_editor/level_data_dread.py index 21219a0..d7313e8 100644 --- a/dread_editor/level_data_dread.py +++ b/dread_editor/level_data_dread.py @@ -6,7 +6,7 @@ import typing import imgui -from mercury_engine_data_structures.type_lib_instances import get_type_lib_dread +from mercury_engine_data_structures.type_lib import get_type_lib_dread from mercury_engine_data_structures.file_tree_editor import FileTreeEditor, Game from mercury_engine_data_structures.formats import Brsa, Brfld, Bmscc from mercury_engine_data_structures.formats.dread_types import CActor @@ -258,11 +258,11 @@ def lerp_y(y): ] if highlighted_section == entry.name: draw_list.add_polyline(raw_vertices, imgui.get_color_u32_rgba(0.2, 0.8, 1, 1.0), - closed=True, + flags=imgui.DRAW_CLOSED, thickness=5) else: draw_list.add_polyline(raw_vertices, imgui.get_color_u32_rgba(0.2, 0.2, 1, 0.8), - closed=True, + flags=imgui.DRAW_CLOSED, thickness=3) self.highlighted_actors_in_canvas = [] diff --git a/dread_editor/level_data_sr.py b/dread_editor/level_data_sr.py index fc2b077..7abe338 100644 --- a/dread_editor/level_data_sr.py +++ b/dread_editor/level_data_sr.py @@ -9,8 +9,7 @@ from mercury_engine_data_structures.file_tree_editor import FileTreeEditor, Game from mercury_engine_data_structures.formats import Bmscc, Bmsld from mercury_engine_data_structures.formats.bmsld import ProperActor -from mercury_engine_data_structures.type_lib import BaseType -from mercury_engine_data_structures.type_lib_instances import get_type_lib_samus_returns +from mercury_engine_data_structures.type_lib import BaseType, get_type_lib_samus_returns from dread_editor import imgui_util from dread_editor.actor_filter import ActorFilter @@ -257,11 +256,11 @@ def lerp_y(y): ] if highlighted_section == entry.name: draw_list.add_polyline(raw_vertices, imgui.get_color_u32_rgba(0.2, 0.8, 1, 1.0), - closed=True, + flags=imgui.DRAW_CLOSED, thickness=5) else: draw_list.add_polyline(raw_vertices, imgui.get_color_u32_rgba(0.2, 0.2, 1, 0.8), - closed=True, + flags=imgui.DRAW_CLOSED, thickness=3) self.highlighted_actors_in_canvas = [] From a197e67c1fd459c25850ba190cfbdcd34460ca9a Mon Sep 17 00:00:00 2001 From: Thanatos Date: Fri, 15 Sep 2023 17:53:57 +0200 Subject: [PATCH 4/4] Update dependencies Fix some parts of SR editor --- dread_editor/level_data_sr.py | 29 +++++++++++++++-------------- dread_editor/main_loop.py | 4 +++- requirements.txt | 8 ++++---- setup.cfg | 4 ++-- 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/dread_editor/level_data_sr.py b/dread_editor/level_data_sr.py index 7abe338..a0a1684 100644 --- a/dread_editor/level_data_sr.py +++ b/dread_editor/level_data_sr.py @@ -101,14 +101,13 @@ def open_actor_link(self, link: str): print(actor) self.visible_actors[(layer_name, actor.sName)] = True - def render_actor_context_menu(self, layer_name: str, actor): + def render_actor_context_menu(self, layer_index: int, actor): if self.copy_actor_name is None: self.copy_actor_name = f"{actor.sName}_Copy" if imgui.button("Duplicate Actor"): new_actor = copy.deepcopy(actor) - new_actor.sName = self.copy_actor_name - self.add_new_actor(layer_name, new_actor) + self.add_new_actor(layer_index, new_actor, self.copy_actor_name) imgui.close_current_popup() self.copy_actor_name = None @@ -121,18 +120,20 @@ def render_actor_context_menu(self, layer_name: str, actor): imgui.text("Position:") imgui.same_line() - changed_x, x = imgui.slider_float("##actor-context-position-x", actor.vPos[0], + changed_x, x = imgui.slider_float("##actor-context-position-x", actor.x, self.display_borders["left"], self.display_borders["right"]) imgui.same_line() - changed_y, y = imgui.slider_float("##actor-context-position-y", actor.vPos[1], + changed_y, y = imgui.slider_float("##actor-context-position-y", actor.y, self.display_borders["top"], self.display_borders["bottom"]) - if changed_x or changed_y: - actor.vPos = (x, y, actor.vPos[2]) + if changed_x: + actor.x = x + if changed_y: + actor.y = y - def add_new_actor(self, layer_name: str, actor): + def add_new_actor(self, layer_index: int, actor, actor_name: str): if actor is not None: - self.bmsld.actors_for_layer(layer_name)[actor.sName] = actor - self.visible_actors[(layer_name, actor.sName)] = True + self.bmsld.raw.actors[layer_index][actor_name] = actor + self.visible_actors[(str(layer_index), actor_name)] = True def render_window(self, current_scale): imgui.set_next_window_size(900 * current_scale, 300 * current_scale, imgui.FIRST_USE_EVER) @@ -168,7 +169,7 @@ def color_for_layer(name: str): if imgui_util.colored_tree_node(layer_name, color_for_layer(layer_name)): actors = self.bmsld.raw.actors[int(layer_name)] - for actor_name, actor in actors.items(): + for actor_name, actor in sorted(actors.items(), key=lambda it: it[0]): key = (layer_name, actor_name) if not self.actor_filter.passes(actor): continue @@ -188,7 +189,7 @@ def do_item(): highlighted_actors_in_list.add(key) if imgui.begin_popup_context_item(): - self.render_actor_context_menu(layer_name, actor) + self.render_actor_context_menu(layer_index, actor) imgui.end_popup() imgui.tree_pop() @@ -339,9 +340,9 @@ def draw_visible_actors(self, current_scale: float): with imgui_util.with_child("##ActorGroups", 0, 300 * current_scale, imgui.WINDOW_ALWAYS_VERTICAL_SCROLLBAR): - for group_name in sorted(self.bmsld.all_actor_groups()): + for group_name, group in sorted(self.bmsld.all_actor_groups()): changed, present = imgui.checkbox(f"{group_name} ##actor_group.{group_name}", - self.bmsld.is_actor_in_group(group_name, actor_name, layer_name)) + self.bmsld.is_actor_in_group(group_name, actor_name)) # if changed: # if present: # self.bmsld.add_actor_to_group(group_name, actor_name, layer_name) diff --git a/dread_editor/main_loop.py b/dread_editor/main_loop.py index 3471779..76adb5e 100644 --- a/dread_editor/main_loop.py +++ b/dread_editor/main_loop.py @@ -223,11 +223,13 @@ def load_romfs(path: Path): if imgui.menu_item("Select extracted Metroid Dread root")[0]: f = prompt_file(directory=True) if f: + current_level_data = None current_game = Game.DREAD load_romfs(Path(f)) if imgui.menu_item("Select extracted Samus Returns root")[0]: f = prompt_file(directory=True) if f: + current_level_data = None current_game = Game.SAMUS_RETURNS load_romfs(Path(f)) @@ -250,7 +252,7 @@ def load_romfs(path: Path): f = prompt_file(directory=True) if f: - pkg_editor.save_modifications(Path(f), OutputFormat.PKG) + pkg_editor.save_modifications(Path(f), OutputFormat.PKG, finalize_editor=False) imgui.end_menu() diff --git a/requirements.txt b/requirements.txt index 9b9d218..72dcfe7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ construct==2.10.68 -glfw==2.5.9 -imgui==1.4.1 -mercury-engine-data-structures==0.22.0 -PyOpenGL==3.1.6 +glfw==2.6.2 +imgui==2.0.0 +mercury-engine-data-structures==0.24.0 +PyOpenGL==3.1.7 diff --git a/setup.cfg b/setup.cfg index 3e318d6..e77fe4b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,12 +18,12 @@ classifiers = [options] packages = find: install_requires = - mercury-engine-data-structures>=0.16.0 + mercury-engine-data-structures>=0.24.0 imgui[glfw] include_package_data = True zip_safe = False -python_requires = >=3.9 +python_requires = >=3.11 [options.packages.find] exclude =