aboutsummaryrefslogtreecommitdiff
path: root/src/room_editor.lua
diff options
context:
space:
mode:
authoriamcheeseman <[email protected]>2026-03-13 15:25:03 -0400
committeriamcheeseman <[email protected]>2026-03-13 15:25:03 -0400
commit332d00e92bf620b6dd11dc10096ead7a5badd7f4 (patch)
treed0e557afe65949d0d7de2f7aa97a8327f1537360 /src/room_editor.lua
parentb47490aea2f63d881a2cc69a326b03abae1a46c0 (diff)
Add basic entity editor
Diffstat (limited to 'src/room_editor.lua')
-rw-r--r--src/room_editor.lua283
1 files changed, 283 insertions, 0 deletions
diff --git a/src/room_editor.lua b/src/room_editor.lua
new file mode 100644
index 0000000..01d5c90
--- /dev/null
+++ b/src/room_editor.lua
@@ -0,0 +1,283 @@
+register_input("Next_Tileset", {{"key", "v"}})
+register_input("Prev_Tileset", {{"key", "c"}})
+register_input("Tile_Brush", {{"key", "t"}})
+register_input("Entity_Brush", {{"key", "e"}})
+register_input("Delete", {{"key", "delete"}, {"key", "backspace"}})
+
+local basic_entity_properties = {
+ {"flip_h", false},
+ {"flip_v", false},
+}
+
+local entity_definitions = {}
+
+function define_entity(name, tex_name, properties, spawner)
+ table.insert(entity_definitions, name)
+ entity_definitions[name] = {
+ spawner = spawner,
+ tex_name = tex_name,
+ properties = properties,
+ idx = #entity_definitions,
+ }
+end
+
+register_comp("Room_Editor", function(ent)
+ ent.room_editor = {
+ -- Valid values are Tile, Entity
+ -- TODO:
+ -- * Prop (place stuff on top of tiles automatically)
+ brush = "Tile",
+ tile = 1,
+ entity = "Muntik",
+ entity_snap = true,
+ selected_ent = -1,
+ placed_entities = {},
+ }
+end)
+
+register_comp("Editor_Entity", function(ent, room_editor, ent_type, x, y)
+ ent.x = x
+ ent.y = y
+ local entity_def = entity_definitions[ent_type]
+ local properties = {}
+ for _, property in ipairs(basic_entity_properties) do
+ local name = property[1]
+ local default = property[2]
+ properties[name] = default
+ end
+ for _, property in ipairs(entity_def.properties) do
+ local name = property[1]
+ local default = property[2]
+ properties[name] = default
+ end
+ ent.editor_dat = {
+ type = ent_type,
+ room_editor = room_editor,
+ properties = properties,
+ }
+end)
+
+local function get_selected_entity(room_editor)
+ return room_editor.placed_entities[room_editor.selected_ent]
+end
+
+local function new_editor_entity(room_editor, ent_type, x, y)
+ local ent = new_entity()
+ add_comp(ent, "Editor_Entity", room_editor, ent_type, x, y)
+ add_comp(ent, "Sprite", entity_definitions[ent_type].tex_name, {})
+ return ent
+end
+
+function draw_editor_entity_sys(ent)
+ local editor_dat = ent.editor_dat
+ local selected_ent = get_selected_entity(editor_dat.room_editor)
+
+ if selected_ent == ent then
+ -- show selected mhmm
+ lg.rectangle("line", ent.x, ent.y, ent.sprite.width, ent.sprite.height)
+ end
+end
+
+local property_ui = {
+ ["number"] = function(name, editor_dat, property)
+ im.layout({0.5, 1})
+ im.text(name)
+ im.text(tostring(snap(editor_dat.properties[name], 0.01)))
+ im.layout()
+ editor_dat.properties[name] = im.slider(
+ editor_dat.properties[name],
+ property.min or 0,
+ property.max or 1,
+ property.step or 0.1
+ )
+ end,
+ ["boolean"] = function(name, editor_dat, property)
+ im.layout({0.25, 0.5, 1})
+ im.text(name)
+ im.text(tostring(editor_dat.properties[name]))
+ if im.button("Toggle") then
+ editor_dat.properties[name] = not editor_dat.properties[name]
+ end
+ end,
+}
+
+local function property_list(ent, properties)
+ local editor_dat = ent.editor_dat
+ for _, property in ipairs(properties) do
+ local name = property[1]
+ local prop_type = type(property[2])
+ property_ui[prop_type](name, editor_dat, property)
+ im.layout()
+ end
+end
+
+function editor_entity_ui_sys(ent)
+ local editor_dat = ent.editor_dat
+ if editor_dat.room_editor.brush ~= "Entity" then
+ return
+ end
+
+ local selected_ent = get_selected_entity(editor_dat.room_editor)
+
+ if selected_ent == ent then
+ local entity_def = entity_definitions[editor_dat.type]
+ im.begin_window("Entity Properties", 190, 5, 180, 340, {})
+ im.text("Base Properties")
+ im.separator()
+ property_list(ent, basic_entity_properties)
+ im.separator()
+ im.text("Type Properties")
+ im.separator()
+ property_list(ent, entity_def.properties)
+ im.end_window()
+ end
+end
+
+local function get_snapped_to_grid(room_editor, x, y)
+ if room_editor.entity_snap then
+ return snap(x, TILESIZE), snap(y, TILESIZE)
+ else
+ -- Still snap to the pixel cause yes
+ return round(x), round(y)
+ end
+end
+
+local brushes = {
+ ["Tile"] = function(room_editor)
+ if is_input_pressed("Left_Click") then
+ local scn = get_current_scene()
+ assert(scn, "no scene set.")
+
+ local mx, my = get_mouse_pos()
+ local tx, ty = to_tile_coords(mx, my)
+ set_tile(scn.tilemap, tx, ty, room_editor.tile)
+ end
+ if is_input_pressed("Right_Click") then
+ local scn = get_current_scene()
+ assert(scn, "no scene set.")
+
+ local mx, my = get_mouse_pos()
+ local tx, ty = to_tile_coords(mx, my)
+ remove_tile(scn.tilemap, tx, ty)
+ end
+ end,
+ ["Entity"] = function(room_editor)
+ local mx, my = get_mouse_pos()
+ if is_input_just_pressed("Left_Click") then
+ -- first check if we're selecting an entity
+ local selected = false
+ for i, ent in ipairs(room_editor.placed_entities) do
+ if point_in_rect(
+ mx, my,
+ ent.x, ent.y,
+ ent.sprite.width, ent.sprite.height
+ ) then
+ room_editor.selected_ent = i
+ selected = true
+ break
+ end
+ end
+
+ -- Place one frfr
+ if not selected then
+ mx, my = get_snapped_to_grid(room_editor, mx, my)
+ local ent = new_editor_entity(room_editor, room_editor.entity, mx, my)
+ table.insert(room_editor.placed_entities, ent)
+ room_editor.selected_ent = #room_editor.placed_entities
+ end
+ end
+ end
+}
+
+function tile_place_sys(ent)
+ local room_editor = ent.room_editor
+
+ if not im.has_focus() then
+ brushes[room_editor.brush](room_editor)
+ end
+
+ if is_input_just_pressed("Next_Tileset") then
+ room_editor.tile = math.min(room_editor.tile + 1, get_tileset_count())
+ end
+ if is_input_just_pressed("Prev_Tileset") then
+ room_editor.tile = math.max(room_editor.tile - 1, 1)
+ end
+
+ if is_input_just_pressed("Tile_Brush") then
+ room_editor.brush = "Tile"
+ end
+ if is_input_just_pressed("Entity_Brush") then
+ room_editor.brush = "Entity"
+ end
+end
+
+function room_editor_ui_sys(ent)
+ local room_editor = ent.room_editor
+
+ im.begin_window("Room Editor", 5, 5, 180, 180, {})
+ im.text("Brushes")
+ im.layout({0.1, 1})
+ im.text("t")
+ if im.button("Tile") then
+ room_editor.brush = "Tile"
+ end
+ im.text("e")
+ if im.button("Entity") then
+ room_editor.brush = "Entity"
+ end
+ im.layout()
+ im.end_window()
+
+ if room_editor.brush == "Tile" then
+ im.begin_window("Tile Editor", 5, 190, 180, 340, {})
+ im.text("Tile: " .. tostring(room_editor.tile))
+ im.separator()
+ im.layout({0.1, 0.6, 1})
+
+ for tileset_id=1, get_tileset_count() do
+ local text = " "
+ if tileset_id == room_editor.tile then
+ text = "*"
+ elseif tileset_id == room_editor.tile - 1 then
+ text = "c"
+ elseif tileset_id == room_editor.tile + 1 then
+ text = "v"
+ end
+ im.text(text)
+ if im.button("select") then
+ room_editor.tile = tileset_id
+ end
+ im.image(TILE_TEX, get_tileset_quad(tileset_id))
+ end
+
+ im.layout()
+ im.end_window()
+ elseif room_editor.brush == "Entity" then
+ im.begin_window("Entity Editor", 5, 190, 180, 340, {})
+ im.layout({0.2, 1})
+
+ im.text(room_editor.entity_snap and "On" or "Off")
+ if im.button("Position Snapping") then
+ room_editor.entity_snap = not room_editor.entity_snap
+ end
+
+ im.layout({0.1, 0.6, 1})
+
+ for _, entity_name in ipairs(entity_definitions) do
+ local entity_def = entity_definitions[entity_name]
+
+ local text = " "
+ if entity_name == room_editor.entity then
+ text = "*"
+ end
+ im.text(text)
+ if im.button("select") then
+ room_editor.entity = entity_name
+ end
+ im.image(get_tex(entity_def.tex_name))
+ end
+
+ im.layout()
+ im.end_window()
+ end
+end