mirror of
https://github.com/JHDev2006/Super-Mario-Bros.-Remastered-Public.git
synced 2025-10-22 15:38:14 +00:00
308 lines
10 KiB
GDScript
308 lines
10 KiB
GDScript
class_name AssetEditor
|
|
extends AssetRipper
|
|
|
|
const CUR_SPRITE_TEXT: String = "Current Sprite:\n%s"
|
|
|
|
var list_index: int = 0
|
|
var sprite_list: Array
|
|
var cached_sprites: Dictionary[String, Dictionary]
|
|
|
|
var tile_atlas: Texture
|
|
var rom_provided: bool
|
|
|
|
var source_path: String
|
|
var tile_index: int = 0
|
|
var columns: int = 4
|
|
var sheet_size := Vector2i(16, 16)
|
|
|
|
var palette_base: String = "Tile"
|
|
var palettes: Dictionary
|
|
var tiles: Dictionary
|
|
|
|
@onready var rom_required: Label = %RomRequired
|
|
@onready var file_dialog: FileDialog = %FileDialog
|
|
|
|
@onready var image_preview: TextureRect = %ImagePreview
|
|
@onready var green_preview: TextureRect = %GreenPreview
|
|
@onready var tiles_preview: TextureRect = %TilesPreview
|
|
@onready var cur_sprite: Label = %CurSprite
|
|
|
|
@onready var preview: Sprite2D = %Preview
|
|
@onready var buttons: HBoxContainer = %Buttons
|
|
@onready var palette_override: LineEdit = %PaletteOverride
|
|
|
|
@onready var scroll_container: ScrollContainer = %ScrollContainer
|
|
@onready var json_container: VBoxContainer = %JSONContainer
|
|
@onready var json_edit: TextEdit = %JSONEdit
|
|
|
|
|
|
func _ready() -> void:
|
|
Global.get_node("GameHUD").hide()
|
|
if Global.rom_path != "":
|
|
on_file_selected(Global.rom_path)
|
|
|
|
func _exit_tree() -> void:
|
|
Global.get_node("GameHUD").show()
|
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
|
if not rom_provided:
|
|
return
|
|
|
|
if event is InputEventMouseMotion:
|
|
var is_snapped = not Input.is_action_pressed("editor_cam_fast")
|
|
var mouse_pos: Vector2 = event.position + Vector2(
|
|
scroll_container.scroll_horizontal,
|
|
scroll_container.scroll_vertical
|
|
) + Vector2(-4, -8)
|
|
preview.position = Vector2i(mouse_pos).snappedi(8 if is_snapped else 1)
|
|
|
|
preview.visible = true
|
|
if preview.position.x + 8 > sheet_size.x:
|
|
preview.visible = false
|
|
if preview.position.y + 8 > sheet_size.y:
|
|
preview.visible = false
|
|
|
|
if not preview.is_visible_in_tree(): return
|
|
|
|
var direction: int = 0
|
|
if event.is_action_pressed("editor_cam_left"): direction = -1
|
|
if event.is_action_pressed("editor_cam_right"): direction = 1
|
|
if event.is_action_pressed("pick_tile"):
|
|
if tiles.has(preview.position):
|
|
tile_index = tiles[preview.position].index
|
|
preview.flip_h = tiles[preview.position].flip_h
|
|
preview.flip_v = tiles[preview.position].flip_v
|
|
if Input.is_action_pressed("editor_select"):
|
|
if tiles[preview.position].has("palette"):
|
|
palette_override.text = tiles[preview.position]["palette"]
|
|
else:
|
|
palette_override.text = ""
|
|
preview.region_rect = Rect2i(index_to_coords(tile_index, 32) * 8, Vector2i(8, 8))
|
|
if direction != 0:
|
|
var multiply = 1 if not Input.is_action_pressed("editor_cam_fast") else 8
|
|
tile_index = wrapi(tile_index + direction * multiply, 0, 512)
|
|
preview.region_rect = Rect2i(index_to_coords(tile_index, 32) * 8, Vector2i(8, 8))
|
|
|
|
if event.is_action_pressed("jump_0"): preview.flip_h = not preview.flip_h
|
|
if event.is_action_pressed("run_0"): preview.flip_v = not preview.flip_v
|
|
|
|
var left_click: bool = event.is_action_pressed("mb_left")
|
|
var right_click: bool = event.is_action_pressed("mb_right")
|
|
if left_click or right_click:
|
|
if left_click:
|
|
tiles[preview.position] = {
|
|
"index": tile_index,
|
|
"flip_h": preview.flip_h,
|
|
"flip_v": preview.flip_v
|
|
}
|
|
if not palette_override.text.is_empty():
|
|
tiles[preview.position]["palette"] = palette_override.text
|
|
else:
|
|
tiles.erase(preview.position)
|
|
|
|
update_edited_image()
|
|
|
|
func _process(_delta: float) -> void:
|
|
if Input.is_action_pressed("editor_select") == false:
|
|
return
|
|
var left_click: bool = Input.is_action_pressed("mb_left")
|
|
var right_click: bool = Input.is_action_pressed("mb_right")
|
|
if left_click or right_click:
|
|
if left_click:
|
|
tiles[preview.position] = {
|
|
"index": tile_index,
|
|
"flip_h": preview.flip_h,
|
|
"flip_v": preview.flip_v
|
|
}
|
|
if not palette_override.text.is_empty():
|
|
tiles[preview.position]["palette"] = palette_override.text
|
|
else:
|
|
tiles.erase(preview.position)
|
|
|
|
update_edited_image()
|
|
|
|
func update_edited_image() -> void:
|
|
var tiles_images: Array[Image] = get_tile_images(image_preview.texture.get_image().get_size(), tiles, palettes)
|
|
tiles_preview.texture = ImageTexture.create_from_image(tiles_images[0])
|
|
green_preview.texture = ImageTexture.create_from_image(tiles_images[1])
|
|
|
|
func get_tile_images(
|
|
img_size: Vector2i, tile_list: Dictionary, palette_lists: Dictionary
|
|
) -> Array[Image]:
|
|
var image := Image.create(img_size.x, img_size.y, false, Image.FORMAT_RGBA8)
|
|
var green_image := Image.create(img_size.x, img_size.y, false, Image.FORMAT_RGBA8)
|
|
|
|
for palette_name in palette_lists.keys():
|
|
var cur_column: int = 0
|
|
var offset := Vector2.ZERO
|
|
|
|
var pal_json: String = FileAccess.get_file_as_string(
|
|
PALETTES_FOLDER % [DEFAULT_PALETTE_GROUP, palette_name])
|
|
var pal_dict: Dictionary = JSON.parse_string(pal_json).palettes
|
|
|
|
for palette_id: String in palette_lists[palette_name]:
|
|
var palette: Array = pal_dict.get(palette_id, PREVIEW_PALETTE)
|
|
|
|
for tile_pos: Vector2 in tile_list:
|
|
var tile_dict: Dictionary = tile_list[tile_pos]
|
|
var tile_palette: String = tile_dict.get("palette", palette_base)
|
|
if tile_palette == palette_name:
|
|
var destination: Vector2 = tile_pos + offset
|
|
if destination.x < img_size.x and destination.y < img_size.y:
|
|
draw_green(green_image, destination)
|
|
draw_tile(
|
|
false,
|
|
image,
|
|
tile_dict.get("index", 0),
|
|
destination,
|
|
palette,
|
|
tile_dict.get("flip_h", false),
|
|
tile_dict.get("flip_v", false)
|
|
)
|
|
cur_column += 1
|
|
if cur_column >= columns:
|
|
cur_column = 0
|
|
offset.x = 0
|
|
offset.y += sheet_size.y
|
|
else:
|
|
offset.x += sheet_size.x
|
|
return [image, green_image]
|
|
|
|
func on_file_selected(path: String) -> void:
|
|
rom = FileAccess.get_file_as_bytes(path)
|
|
prg_rom_size = rom[4] * 16384
|
|
chr_rom = rom.slice(16 + prg_rom_size)
|
|
|
|
rom_provided = true
|
|
rom_required.hide()
|
|
buttons.show()
|
|
|
|
# setup sprite atlas for placing tiles
|
|
var atlas := Image.create(256, 256, false, Image.FORMAT_RGBA8)
|
|
for index in range(512):
|
|
var pos: Vector2i = index_to_coords(index, 32) * 8
|
|
draw_tile(false, atlas, index, pos, PREVIEW_PALETTE)
|
|
preview.texture = ImageTexture.create_from_image(atlas)
|
|
#
|
|
|
|
var list_json: String = FileAccess.get_file_as_string(SPRITE_LIST_PATH)
|
|
var list_dict: Dictionary = JSON.parse_string(list_json)
|
|
sprite_list = list_dict.get("sprites", [])
|
|
|
|
cycle_list(0)
|
|
|
|
func load_sprite(sprite_dict: Dictionary) -> void:
|
|
source_path = sprite_dict.source_path
|
|
|
|
if source_path.begins_with("res://"):
|
|
image_preview.texture = load(source_path)
|
|
else:
|
|
var image := Image.load_from_file(source_path)
|
|
image_preview.texture = ImageTexture.create_from_image(image)
|
|
cur_sprite.text = CUR_SPRITE_TEXT % source_path.get_file()
|
|
|
|
columns = str_to_var(sprite_dict.get("columns", "4"))
|
|
sheet_size = str_to_var(sprite_dict.get("sheet_size", "Vector2i(16, 16)"))
|
|
palette_base = sprite_dict.get("palette_base", "Tile")
|
|
|
|
var palettes_var: Variant = str_to_var(sprite_dict.get("palettes", var_to_str(get_default_palettes())))
|
|
if typeof(palettes_var) == TYPE_ARRAY:
|
|
palettes = {}
|
|
palettes[palette_base] = palettes_var
|
|
elif typeof(palettes_var) == TYPE_DICTIONARY:
|
|
palettes = palettes_var
|
|
|
|
tiles = str_to_var(sprite_dict.get("tiles", "{}"))
|
|
|
|
update_palettes()
|
|
update_edited_image()
|
|
|
|
func save_sprite() -> void:
|
|
var sprite_dict: Dictionary = get_as_dict()
|
|
var destination_path: String = png_path_to_json(sprite_dict.source_path)
|
|
|
|
var json_string: String = JSON.stringify(sprite_dict)
|
|
DirAccess.make_dir_recursive_absolute(destination_path.get_base_dir())
|
|
var file: FileAccess = FileAccess.open(destination_path, FileAccess.WRITE)
|
|
file.store_line(json_string)
|
|
file.close()
|
|
|
|
# save green over original image
|
|
var base_image: Image = image_preview.texture.get_image()
|
|
var green_image: Image = green_preview.texture.get_image()
|
|
for y in range(green_image.get_size().y):
|
|
for x in range(green_image.get_size().x):
|
|
var found_color: Color = green_image.get_pixel(x, y)
|
|
if found_color.a > 0:
|
|
base_image.set_pixel(x, y, found_color)
|
|
base_image.save_png(sprite_dict.source_path)
|
|
|
|
func cycle_list(add_index: int) -> void:
|
|
if add_index != 0:
|
|
cached_sprites[sprite_list[list_index]] = get_as_dict()
|
|
list_index = wrapi(list_index + add_index, 0, sprite_list.size())
|
|
|
|
if sprite_list[list_index] in cached_sprites:
|
|
load_sprite(cached_sprites[sprite_list[list_index]])
|
|
else:
|
|
var json_path: String = sprite_list[list_index].replace(
|
|
"res://Assets/Sprites/", "res://Resources/AssetRipper/Sprites/"
|
|
).replace(".png", ".json")
|
|
if FileAccess.file_exists(json_path):
|
|
var json_string: String = FileAccess.get_file_as_string(json_path)
|
|
load_sprite(JSON.parse_string(json_string))
|
|
else:
|
|
load_sprite({"source_path": sprite_list[list_index]})
|
|
|
|
func get_as_dict() -> Dictionary:
|
|
return {
|
|
"source_path": source_path,
|
|
"columns": var_to_str(columns),
|
|
"sheet_size": var_to_str(sheet_size),
|
|
"palette_base": palette_base,
|
|
"palettes": var_to_str(palettes),
|
|
"tiles": var_to_str(tiles)
|
|
}
|
|
|
|
func get_default_palettes() -> Dictionary:
|
|
var pal_json: String = FileAccess.get_file_as_string(
|
|
PALETTES_FOLDER % [DEFAULT_PALETTE_GROUP, palette_base])
|
|
var pal_dict: Dictionary = JSON.parse_string(pal_json)
|
|
var default_palettes: Array = pal_dict.get("palettes", []).keys()
|
|
return {palette_base: default_palettes}
|
|
|
|
func update_palettes(load_textedit: bool = false) -> void:
|
|
if load_textedit:
|
|
var dict: Dictionary = JSON.parse_string(json_edit.text)
|
|
columns = dict.get("columns", 1)
|
|
var size_array: Array = dict.get("sheet_size", [16, 16])
|
|
sheet_size = Vector2i(size_array[0], size_array[1])
|
|
palette_base = dict.get("palette_base", "Tile")
|
|
var pal_var: Variant = dict.get("palettes", get_default_palettes())
|
|
if typeof(pal_var) == TYPE_ARRAY:
|
|
palettes = {}
|
|
palettes[palette_base] = pal_var
|
|
elif typeof(pal_var) == TYPE_DICTIONARY:
|
|
palettes = pal_var
|
|
update_edited_image()
|
|
|
|
json_edit.text = JSON.stringify(
|
|
{
|
|
"columns": columns,
|
|
"sheet_size": [sheet_size.x, sheet_size.y],
|
|
"palette_base": palette_base,
|
|
"palettes": palettes
|
|
}, "\t", false)
|
|
|
|
func toggle_palettes_view(toggled_on: bool) -> void:
|
|
json_container.visible = toggled_on
|
|
scroll_container.visible = not toggled_on
|
|
|
|
func draw_green(
|
|
image: Image,
|
|
pos: Vector2i
|
|
) -> void:
|
|
for y in range(8):
|
|
for x in range(8):
|
|
image.set_pixelv(Vector2i(x, y) + pos, Color.GREEN)
|