mirror of
https://gitlab.com/xCrystal/pokecrystal-board.git
synced 2024-09-09 09:51:34 -07:00
Tileset creation tool (#39)
This commit is contained in:
parent
8ecdfab1ac
commit
56bed4503d
280
tools/board/tileset.py
Executable file
280
tools/board/tileset.py
Executable file
@ -0,0 +1,280 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import messagebox
|
||||||
|
|
||||||
|
# Matrix of default metatile space values
|
||||||
|
default_values = [
|
||||||
|
['BLUE', 'BLUE', 'BLUE', 'BLUE', 'BLUE', 'BLUE', 'BLUE', 'BLUE'],
|
||||||
|
['BLUE', 'BLUE', 'BLUE', 'BLUE', 'BLUE', 'BLUE', 'BLUE', 'BLUE'],
|
||||||
|
['BLUE', 'BLUE', 'BLUE', 'BLUE', 'BLUE', 'BLUE', 'BLUE', 'BLUE'],
|
||||||
|
['POKEMON', 'POKEMON', 'POKEMON', 'POKEMON', 'POKEMON', 'POKEMON', 'POKEMON', 'POKEMON'],
|
||||||
|
['POKEMON', 'POKEMON', 'POKEMON', 'POKEMON', 'POKEMON', 'POKEMON', 'POKEMON', 'POKEMON'],
|
||||||
|
['POKEMON', 'POKEMON', 'POKEMON', 'POKEMON', 'POKEMON', 'POKEMON', 'POKEMON', 'POKEMON'],
|
||||||
|
['RED', 'RED', 'RED', 'RED', 'RED', 'RED', 'RED', 'RED'],
|
||||||
|
['RED', 'RED', 'RED', 'RED', 'RED', 'RED', 'RED', 'RED'],
|
||||||
|
['END', 'GREEN', 'ITEM', 'MINIGAME', 'GREEN', 'GREEN', 'ITEM', 'MINIGAME'],
|
||||||
|
['END', 'GREEN', 'ITEM', 'MINIGAME', 'GREEN', 'GREEN', 'ITEM', 'MINIGAME'],
|
||||||
|
['END', 'GREEN', 'ITEM', 'MINIGAME', 'GREEN', 'GREEN', 'ITEM', 'MINIGAME'],
|
||||||
|
['END', 'GREEN', 'ITEM', 'MINIGAME', 'GREEN', 'GREEN', 'ITEM', 'MINIGAME']
|
||||||
|
]
|
||||||
|
|
||||||
|
fixed_spaces = ['BLUE', 'RED', 'GREEN', 'ITEM', 'POKEMON', 'MINIGAME', 'END', 'GREY']
|
||||||
|
|
||||||
|
def create_palette_map(new_tileset_name, source_tileset_name, variable_spaces_id):
|
||||||
|
# Generate the filename for the source tileset
|
||||||
|
source_filename = source_tileset_name + "_palette_map.asm"
|
||||||
|
|
||||||
|
# Check if the source tileset file exists
|
||||||
|
source_filepath = os.path.join("gfx", "tilesets", source_filename)
|
||||||
|
if not os.path.exists(source_filepath):
|
||||||
|
messagebox.showerror("Error", f"Source tileset file '{source_filename}' not found.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Read the first 24 lines of the source tileset file
|
||||||
|
with open(source_filepath, "r") as source_file:
|
||||||
|
first_24_lines = "".join(source_file.readlines()[:24])
|
||||||
|
|
||||||
|
# Read the first 4 lines from the fixed spaces palette map file
|
||||||
|
fixed_spaces_filepath = os.path.join("gfx", "tilesets", "spaces", "fixed_spaces_palette_map.asm")
|
||||||
|
with open(fixed_spaces_filepath, "r") as fixed_spaces_file:
|
||||||
|
fixed_spaces_lines = "".join(fixed_spaces_file.readlines()[:4])
|
||||||
|
|
||||||
|
# Read the first 4 lines from the variable spaces palette map file
|
||||||
|
variable_spaces_filename = f"variable_spaces_{variable_spaces_id}_palette_map.asm"
|
||||||
|
variable_spaces_filepath = os.path.join("gfx", "tilesets", "spaces", variable_spaces_filename)
|
||||||
|
if not os.path.exists(variable_spaces_filepath):
|
||||||
|
messagebox.showerror("Error", f"Variable spaces palette map file '{variable_spaces_filename}' not found.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
with open(variable_spaces_filepath, "r") as variable_spaces_file:
|
||||||
|
variable_spaces_lines = "".join(variable_spaces_file.readlines()[:4])
|
||||||
|
|
||||||
|
# Generate the filename for the new tileset
|
||||||
|
new_tileset_filename = new_tileset_name + "_palette_map.asm"
|
||||||
|
new_tileset_filepath = os.path.join("gfx", "tilesets", new_tileset_filename)
|
||||||
|
|
||||||
|
# Write the lines to the new tileset file
|
||||||
|
with open(new_tileset_filepath, "w") as new_tileset_file:
|
||||||
|
new_tileset_file.write(first_24_lines)
|
||||||
|
new_tileset_file.write(fixed_spaces_lines)
|
||||||
|
new_tileset_file.write(variable_spaces_lines)
|
||||||
|
|
||||||
|
return new_tileset_filename
|
||||||
|
|
||||||
|
def create_2bpp(new_tileset_name, source_tileset_name, variable_spaces_id):
|
||||||
|
# Generate the filename for the source binary file
|
||||||
|
source_binary_filename = source_tileset_name + ".2bpp"
|
||||||
|
|
||||||
|
# Check if the source binary file exists
|
||||||
|
source_binary_filepath = os.path.join("gfx", "tilesets", source_binary_filename)
|
||||||
|
if not os.path.exists(source_binary_filepath):
|
||||||
|
messagebox.showerror("Error", f"Source binary file '{source_binary_filename}' not found.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Calculate the number of bytes to copy (128*96*2/8)
|
||||||
|
num_bytes = 128 * 96 * 2 // 8
|
||||||
|
|
||||||
|
# Read the specified number of bytes from the source binary file
|
||||||
|
with open(source_binary_filepath, "rb") as source_binary_file:
|
||||||
|
binary_data = source_binary_file.read(num_bytes)
|
||||||
|
|
||||||
|
# Read the first (16*96*2/8) bytes from the fixed spaces binary file
|
||||||
|
fixed_spaces_filename = "fixed_spaces.2bpp"
|
||||||
|
fixed_spaces_filepath = os.path.join("gfx", "tilesets", "spaces", fixed_spaces_filename)
|
||||||
|
with open(fixed_spaces_filepath, "rb") as fixed_spaces_file:
|
||||||
|
fixed_spaces_data = fixed_spaces_file.read(num_bytes // 8 * 2)
|
||||||
|
|
||||||
|
# Read the first (16*96*2/8) bytes from the variable spaces binary file
|
||||||
|
variable_spaces_filename = f"variable_spaces_{variable_spaces_id}.2bpp"
|
||||||
|
variable_spaces_filepath = os.path.join("gfx", "tilesets", "spaces", variable_spaces_filename)
|
||||||
|
if not os.path.exists(variable_spaces_filepath):
|
||||||
|
messagebox.showerror("Error", f"Variable spaces binary file '{variable_spaces_filename}' not found.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
with open(variable_spaces_filepath, "rb") as variable_spaces_file:
|
||||||
|
variable_spaces_data = variable_spaces_file.read(num_bytes // 8 * 2)
|
||||||
|
|
||||||
|
# Generate the filename for the new binary file
|
||||||
|
new_binary_filename = new_tileset_name + ".2bpp"
|
||||||
|
new_binary_filepath = os.path.join("gfx", "tilesets", new_binary_filename)
|
||||||
|
|
||||||
|
# Write the binary data to the new binary file
|
||||||
|
with open(new_binary_filepath, "wb") as new_binary_file:
|
||||||
|
new_binary_file.write(binary_data)
|
||||||
|
new_binary_file.write(fixed_spaces_data)
|
||||||
|
new_binary_file.write(variable_spaces_data)
|
||||||
|
|
||||||
|
return new_binary_filename
|
||||||
|
|
||||||
|
def create_metatiles(new_tileset_name, source_tileset_name, collision_values):
|
||||||
|
# Generate the filename for the source metatiles binary file
|
||||||
|
source_metatiles_filename = source_tileset_name + "_metatiles.bin"
|
||||||
|
|
||||||
|
# Check if the source metatiles binary file exists
|
||||||
|
source_metatiles_filepath = os.path.join("data", "tilesets", source_metatiles_filename)
|
||||||
|
if not os.path.exists(source_metatiles_filepath):
|
||||||
|
messagebox.showerror("Error", f"Source metatiles binary file '{source_metatiles_filename}' not found.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Read (128*16) bytes from the source metatiles binary file
|
||||||
|
num_bytes = 128 * 16
|
||||||
|
with open(source_metatiles_filepath, "rb") as source_metatiles_file:
|
||||||
|
metatiles_data = source_metatiles_file.read(num_bytes)
|
||||||
|
|
||||||
|
# Generate the filename for the new metatiles binary file
|
||||||
|
new_metatiles_filename = new_tileset_name + "_metatiles.bin"
|
||||||
|
new_metatiles_filepath = os.path.join("data", "tilesets", new_metatiles_filename)
|
||||||
|
|
||||||
|
# Write the metatiles data to the new binary file
|
||||||
|
with open(new_metatiles_filepath, "wb") as new_metatiles_file:
|
||||||
|
new_metatiles_file.write(metatiles_data)
|
||||||
|
|
||||||
|
# Write 96 chunks of 16 bytes
|
||||||
|
for value in collision_values:
|
||||||
|
# Find the index of the value in fixed_spaces
|
||||||
|
index = fixed_spaces.index(value)
|
||||||
|
# Write the corresponding 16 bytes
|
||||||
|
for i in range(16):
|
||||||
|
if i == 0 or i == 1:
|
||||||
|
byte_value = index * 2 + 0xc0 + i
|
||||||
|
elif i == 4 or i == 5:
|
||||||
|
byte_value = index * 2 + 0xd0 + (i - 4)
|
||||||
|
else:
|
||||||
|
byte_value = 0x01
|
||||||
|
new_metatiles_file.write(bytearray([byte_value]))
|
||||||
|
|
||||||
|
# Write 32*16 bytes corresponding to 'GREY' space
|
||||||
|
index = fixed_spaces.index('GREY')
|
||||||
|
for i in range(32):
|
||||||
|
new_metatiles_file.write(bytearray([index * 2 + 0xc0, index * 2 + 0xc1, 0x01, 0x01, index * 2 + 0xd0, index * 2 + 0xd1, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01]))
|
||||||
|
|
||||||
|
return new_metatiles_filename
|
||||||
|
|
||||||
|
def create_collision(new_tileset_name, source_tileset_name, collision_values):
|
||||||
|
# Generate the filename for the source collision text file
|
||||||
|
source_collision_filename = source_tileset_name + "_collision.asm"
|
||||||
|
|
||||||
|
# Check if the source collision text file exists
|
||||||
|
source_collision_filepath = os.path.join("data", "tilesets", source_collision_filename)
|
||||||
|
if not os.path.exists(source_collision_filepath):
|
||||||
|
messagebox.showerror("Error", f"Source collision text file '{source_collision_filename}' not found.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Read 128 lines from the source collision text file
|
||||||
|
with open(source_collision_filepath, "r") as source_collision_file:
|
||||||
|
collision_lines = source_collision_file.readlines()[:128]
|
||||||
|
|
||||||
|
# Generate the filename for the new collision text file
|
||||||
|
new_collision_filename = new_tileset_name + "_collision.asm"
|
||||||
|
new_collision_filepath = os.path.join("data", "tilesets", new_collision_filename)
|
||||||
|
|
||||||
|
# Write the collision lines to the new text file
|
||||||
|
with open(new_collision_filepath, "w") as new_collision_file:
|
||||||
|
new_collision_file.writelines(collision_lines)
|
||||||
|
|
||||||
|
# Append additional lines based on collision_values
|
||||||
|
for index, value in enumerate(collision_values, start=0x80):
|
||||||
|
new_collision_file.write(f'\ttilecoll {value}_SPACE, FLOOR, FLOOR, FLOOR ; {format(index, "02x")}\n')
|
||||||
|
|
||||||
|
# Add fixed 32 lines at the end
|
||||||
|
for index in range(0xe0, 0x100):
|
||||||
|
new_collision_file.write(f'\ttilecoll GREY_SPACE, FLOOR, FLOOR, FLOOR ; {format(index, "02x")}\n')
|
||||||
|
|
||||||
|
return new_collision_filename
|
||||||
|
|
||||||
|
def create_tileset():
|
||||||
|
# Get the values from the entry fields
|
||||||
|
new_tileset_name = new_tileset_entry.get()
|
||||||
|
source_tileset_name = source_tileset_entry.get()
|
||||||
|
variable_spaces_id = variable_spaces_entry.get()
|
||||||
|
|
||||||
|
# Read the values from the dropdown menus into a 12x8 array
|
||||||
|
collision_values = []
|
||||||
|
for i in range(12):
|
||||||
|
row_values = []
|
||||||
|
for j in range(8):
|
||||||
|
row_values.append(collision_dropdowns[i][j].get())
|
||||||
|
collision_values.append(row_values)
|
||||||
|
# Flatten the resulting array
|
||||||
|
collision_values = [value for sublist in collision_values for value in sublist]
|
||||||
|
|
||||||
|
# Create the palette map file
|
||||||
|
palette_map_filename = create_palette_map(new_tileset_name, source_tileset_name, variable_spaces_id)
|
||||||
|
if palette_map_filename is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Copy the binary data
|
||||||
|
binary_filename = create_2bpp(new_tileset_name, source_tileset_name, variable_spaces_id)
|
||||||
|
if binary_filename is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Copy the metatiles data
|
||||||
|
metatiles_filename = create_metatiles(new_tileset_name, source_tileset_name, collision_values)
|
||||||
|
if metatiles_filename is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Copy the collision data
|
||||||
|
collision_filename = create_collision(new_tileset_name, source_tileset_name, collision_values)
|
||||||
|
if collision_filename is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
message = (
|
||||||
|
f"Tileset '{palette_map_filename}' created successfully.\n"
|
||||||
|
f"2bpp file '{binary_filename}' created successfully.\n"
|
||||||
|
f"Metatiles file '{metatiles_filename}' created successfully.\n"
|
||||||
|
f"Collision file '{collision_filename}' created successfully."
|
||||||
|
)
|
||||||
|
messagebox.showinfo("Success", message)
|
||||||
|
|
||||||
|
|
||||||
|
# Create the main window
|
||||||
|
root = tk.Tk()
|
||||||
|
root.title("Create Tileset")
|
||||||
|
|
||||||
|
# Create labels and entry fields
|
||||||
|
tk.Label(root, text="New Tileset Name:").grid(row=0, column=0, padx=10, pady=5)
|
||||||
|
new_tileset_entry = tk.Entry(root)
|
||||||
|
new_tileset_entry.grid(row=0, column=1, padx=10, pady=5)
|
||||||
|
|
||||||
|
tk.Label(root, text="Source Tileset Name:").grid(row=1, column=0, padx=10, pady=5)
|
||||||
|
source_tileset_entry = tk.Entry(root)
|
||||||
|
source_tileset_entry.grid(row=1, column=1, padx=10, pady=5)
|
||||||
|
|
||||||
|
tk.Label(root, text="Variable Spaces ID:").grid(row=2, column=0, padx=10, pady=5)
|
||||||
|
variable_spaces_entry = tk.Entry(root)
|
||||||
|
variable_spaces_entry.grid(row=2, column=1, padx=10, pady=5)
|
||||||
|
|
||||||
|
# Create dropdown menus for collision constants
|
||||||
|
collision_constants = []
|
||||||
|
with open("constants/collision_constants.asm", "r") as collision_constants_file:
|
||||||
|
for line in collision_constants_file:
|
||||||
|
match = re.search(r'const COLL_([A-Z]*)_SPACE', line)
|
||||||
|
if match:
|
||||||
|
collision_constants.append(match.group(1))
|
||||||
|
|
||||||
|
# Define a matrix to store the dropdown menus
|
||||||
|
collision_dropdowns = []
|
||||||
|
|
||||||
|
# Create dropdown menus for each row
|
||||||
|
for i in range(12):
|
||||||
|
label_text = f"0x{format(i * 8 + 0x80, '02x')}"
|
||||||
|
tk.Label(root, text=label_text).grid(row=i, column=2, padx=10, pady=5)
|
||||||
|
row_dropdowns = []
|
||||||
|
for j in range(8):
|
||||||
|
default_value = default_values[i][j]
|
||||||
|
if default_value not in collision_constants:
|
||||||
|
default_value = collision_constants[0] # Use the first collision constant if the default value is not found
|
||||||
|
variable = tk.StringVar(root)
|
||||||
|
variable.set(default_value) # set default value
|
||||||
|
dropdown = tk.OptionMenu(root, variable, *collision_constants)
|
||||||
|
dropdown.grid(row=i, column=j + 3, padx=10, pady=5)
|
||||||
|
row_dropdowns.append(variable)
|
||||||
|
collision_dropdowns.append(row_dropdowns)
|
||||||
|
|
||||||
|
# Create "Create" button
|
||||||
|
create_button = tk.Button(root, text="Create", command=create_tileset)
|
||||||
|
create_button.grid(row=15, column=0, columnspan=10, padx=10, pady=10)
|
||||||
|
|
||||||
|
# Run the GUI
|
||||||
|
root.mainloop()
|
Loading…
Reference in New Issue
Block a user