790 lines
26 KiB
Lua
790 lines
26 KiB
Lua
local S = minetest.get_translator("lzr_editor")
|
|
local FS = function(...) return minetest.formspec_escape(S(...)) end
|
|
local NS = function(s) return end
|
|
|
|
local F = minetest.formspec_escape
|
|
|
|
-- Text color the special autosave level will be highlighted
|
|
-- in the save/load dialog
|
|
local AUTOSAVE_HIGHLIGHT = "#FF8060"
|
|
|
|
-- List of items to give to player on editor entry
|
|
local INITIAL_EDITOR_ITEMS = {
|
|
-- hotbar slots
|
|
"lzr_tools:ultra_pickaxe",
|
|
"screwdriver2:screwdriver",
|
|
"lzr_teleporter:teleporter_off",
|
|
"lzr_laser:emitter_on",
|
|
"lzr_laser:detector",
|
|
"lzr_laser:mirror",
|
|
"lzr_laser:crate",
|
|
"lzr_treasure:chest_wood_locked",
|
|
-- rest of inventory
|
|
"lzr_tools:ultra_bucket",
|
|
"lzr_laser:emit_toggler",
|
|
"lzr_laser:barricade",
|
|
"lzr_laser:emitter_takable_on",
|
|
"lzr_laser:detector_takable",
|
|
"lzr_laser:mirror_takable",
|
|
"lzr_laser:crate_takable",
|
|
"lzr_treasure:chest_wood_unlocked",
|
|
}
|
|
|
|
lzr_editor = {}
|
|
lzr_editor.first_time = true
|
|
|
|
-- The level state holds the metadata for
|
|
-- the level that is being currently edited.
|
|
-- INITIAL_LEVEL_STATE is the default state
|
|
-- and the state when the editor starts.
|
|
local INITIAL_LEVEL_STATE = {
|
|
name = "",
|
|
file_name = "",
|
|
size = lzr_globals.DEFAULT_LEVEL_SIZE,
|
|
wall = lzr_globals.DEFAULT_WALL_NODE,
|
|
window = lzr_globals.DEFAULT_WINDOW_NODE,
|
|
ceiling = lzr_globals.DEFAULT_CEILING_NODE,
|
|
floor = lzr_globals.DEFAULT_FLOOR_NODE,
|
|
ambient = lzr_ambience.DEFAULT_AMBIENCE,
|
|
sky = lzr_globals.DEFAULT_SKY,
|
|
npc_texts = lzr_globals.DEFAULT_NPC_TEXTS,
|
|
weather = lzr_globals.DEFAULT_WEATHER,
|
|
}
|
|
local level_state = table.copy(INITIAL_LEVEL_STATE)
|
|
|
|
-- Used to store the current contents for the
|
|
-- textlist contents of the custom level file list.
|
|
-- (this assumes singleplayer)
|
|
local level_file_textlist_state = {}
|
|
|
|
-- Give useful starter items to player
|
|
local function give_initial_items(player)
|
|
local inv = player:get_inventory()
|
|
-- Clear inventory first
|
|
inv:set_list("main", {})
|
|
-- Add itmes
|
|
local errored = false
|
|
for i=1, #INITIAL_EDITOR_ITEMS do
|
|
local item = INITIAL_EDITOR_ITEMS[i]
|
|
local leftover = inv:add_item("main", item)
|
|
if not leftover:is_empty() then
|
|
minetest.log("error", "[lzr_editor] Could not give initial item "..item.." (leftover="..leftover:to_string().."). "..
|
|
"get_size('main')="..inv:get_size("main").."; get_list('main')="..dump(inv:get_list("main")))
|
|
errored = true
|
|
end
|
|
end
|
|
if errored then
|
|
minetest.log("error", "[lzr_editor] Could not give one or more initial items")
|
|
end
|
|
end
|
|
|
|
-- Returns true if pos is within the current bounds
|
|
-- of the actively edited level.
|
|
function lzr_editor.is_in_level_bounds(pos)
|
|
local offset = table.copy(level_state.size)
|
|
offset = vector.offset(offset, -1, -1, -1)
|
|
return vector.in_area(pos, lzr_globals.LEVEL_POS, vector.add(lzr_globals.LEVEL_POS, offset))
|
|
end
|
|
|
|
-- Enter editor state (if not already in it).
|
|
-- Returns true if state changed, false if already in editor.
|
|
function lzr_editor.enter_editor(player)
|
|
local state = lzr_gamestate.get_state()
|
|
if state == lzr_gamestate.EDITOR then
|
|
return false
|
|
else
|
|
lzr_levels.clear_playfield(level_state.size)
|
|
|
|
level_state = table.copy(INITIAL_LEVEL_STATE)
|
|
local pos = lzr_globals.LEVEL_POS
|
|
local size = level_state.size
|
|
|
|
lzr_levels.build_room({pos=pos, size=size, spawn_pos=pos, yaw=0})
|
|
lzr_gui.show_level_bounds(player, pos, size)
|
|
lzr_gamestate.set_state(lzr_gamestate.EDITOR)
|
|
return true
|
|
end
|
|
end
|
|
|
|
local check_for_slash = function(str)
|
|
if string.match(str, "[/\\]") then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
local save_level = function(level_name, is_autosave)
|
|
if lzr_gamestate.get_state() ~= lzr_gamestate.EDITOR then
|
|
return false
|
|
end
|
|
if check_for_slash(level_name) then
|
|
return false
|
|
end
|
|
minetest.mkdir(minetest.get_worldpath().."/levels")
|
|
|
|
local filename = minetest.get_worldpath().."/levels/"..level_name..".mts"
|
|
local size = vector.subtract(level_state.size, vector.new(1, 1, 1))
|
|
|
|
-- TODO: Don't save the lasers
|
|
local ok = minetest.create_schematic(lzr_globals.LEVEL_POS, vector.add(lzr_globals.LEVEL_POS, size), {}, filename, {})
|
|
|
|
local csv_filename = minetest.get_worldpath().."/levels/"..level_name..".csv"
|
|
local npc_texts_csv = ""
|
|
if level_state.npc_texts and level_state.npc_texts.goldie then
|
|
npc_texts_csv = level_state.npc_texts.goldie or ""
|
|
end
|
|
local csv_contents = lzr_csv.write_csv({{
|
|
level_name..".mts",
|
|
level_state.name,
|
|
level_state.wall .. "|" .. level_state.window .. "|" .. level_state.floor .. "|" .. level_state.ceiling,
|
|
level_state.ambient,
|
|
level_state.sky,
|
|
npc_texts_csv,
|
|
level_state.weather,
|
|
}})
|
|
local ok_csv = minetest.safe_file_write(csv_filename, csv_contents)
|
|
|
|
if ok and ok_csv then
|
|
minetest.log("action", "[lzr_editor] Level written to "..filename.." and "..csv_filename)
|
|
if not is_autosave then
|
|
level_state.file_name = level_name
|
|
end
|
|
return true, filename, csv_filename
|
|
elseif ok then
|
|
minetest.log("error", "[lzr_editor] Level written to "..filename..", but failed to write CSV!")
|
|
return false, filename, csv_filename
|
|
else
|
|
minetest.log("error", "[lzr_editor] Failed to write level to "..filename)
|
|
return false, filename, csv_filename
|
|
end
|
|
end
|
|
|
|
-- Exit editor state (if not already outside of editor).
|
|
-- Returns true if state changed, false if already out of editor.
|
|
local function exit_editor(player)
|
|
level_state.file_name = ""
|
|
local state = lzr_gamestate.get_state()
|
|
if state ~= lzr_gamestate.EDITOR then
|
|
return false
|
|
else
|
|
lzr_levels.go_to_menu()
|
|
return true
|
|
end
|
|
end
|
|
|
|
minetest.register_chatcommand("editor_save", {
|
|
description = S("Save current level"),
|
|
params = S("<level name>"),
|
|
func = function(name, param)
|
|
if lzr_gamestate.get_state() ~= lzr_gamestate.EDITOR then
|
|
return false, S("Not in editor mode!")
|
|
end
|
|
if param == "" then
|
|
return false, S("No level name provided.")
|
|
end
|
|
local level_name = param
|
|
if check_for_slash(level_name) then
|
|
return false, S("Level name must not contain slash or backslash!")
|
|
end
|
|
local ok, filename, filename2 = save_level(level_name)
|
|
if ok and filename and filename2 then
|
|
return true, S("Level saved to @1 and @2.", filename, filename2)
|
|
elseif of and filename then
|
|
return true, S("Level saved to @1, but could not write metadata to @2.", filename, filename2)
|
|
else
|
|
return false, S("Error writing level file!")
|
|
end
|
|
end,
|
|
})
|
|
|
|
-- Returns true if the given file exists, false otherwise.
|
|
-- * path: Path to file (without file name)
|
|
-- * filename: File name of file (without path)
|
|
local file_exists = function(path, filename)
|
|
local levels = minetest.get_dir_list(path, false)
|
|
for l=1, #levels do
|
|
if levels[l] == filename then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local load_level = function(level_name, player)
|
|
if lzr_gamestate.get_state() ~= lzr_gamestate.EDITOR then
|
|
return false
|
|
end
|
|
|
|
if check_for_slash(level_name) then
|
|
return false
|
|
end
|
|
local filename = level_name..".mts"
|
|
local ok = file_exists(minetest.get_worldpath().."/levels", filename)
|
|
if not ok then
|
|
return false
|
|
end
|
|
local schem = minetest.read_schematic(minetest.get_worldpath().."/levels/"..filename, {write_yslice_prob="none"})
|
|
if schem then
|
|
local csv_file = io.open(minetest.get_worldpath().."/levels/"..level_name..".csv", "r")
|
|
if csv_file then
|
|
local csv_string = csv_file:read("*a")
|
|
csv_file:close()
|
|
local csv_parsed = lzr_csv.parse_csv(csv_string)
|
|
if csv_parsed and #csv_parsed >= 1 then
|
|
level_state.name = csv_parsed[1][2]
|
|
local bounds = csv_parsed[1][3]
|
|
local exploded_bounds = string.split(bounds, "|")
|
|
if exploded_bounds then
|
|
level_state.wall = exploded_bounds[1]
|
|
level_state.window = exploded_bounds[2]
|
|
level_state.floor = exploded_bounds[3]
|
|
level_state.ceiling = exploded_bounds[4]
|
|
end
|
|
level_state.ambient = csv_parsed[1][4]
|
|
level_state.sky = csv_parsed[1][5] or lzr_globals.DEFAULT_SKY
|
|
level_state.npc_texts = csv_parsed[1][6]
|
|
if level_state.npc_texts then
|
|
level_state.npc_texts = { goldie = level_state.npc_texts }
|
|
else
|
|
level_state.npc_texts = lzr_globals.DEFAULT_NPC_TEXTS
|
|
end
|
|
level_state.weather = csv_parsed[1][7] or lzr_globals.DEFAULT_WEATHER
|
|
else
|
|
minetest.log("error", "[lzr_editor] Error parsing CSV file for "..level_name)
|
|
end
|
|
else
|
|
minetest.log("error", "[lzr_editor] No CSV file found for "..level_name)
|
|
end
|
|
|
|
level_state.size = table.copy(schem.size)
|
|
level_state.name = level_state.name or ""
|
|
level_state.wall = level_state.wall or lzr_globals.DEFAULT_WALL_NODE
|
|
level_state.window = level_state.window or lzr_globals.DEFAULT_WINDOW_NODE
|
|
level_state.ceiling = level_state.ceiling or lzr_globals.DEFAULT_CEILING_NODE
|
|
level_state.floor = level_state.floor or lzr_globals.DEFAULT_FLOOR_NODE
|
|
level_state.ambient = level_state.ambient or lzr_ambience.DEFAULT_AMBIENCE
|
|
level_state.sky = level_state.sky or lzr_globals.DEFAULT_SKY
|
|
level_state.npc_texts = level_state.npc_texts or lzr_globals.DEFAULT_NPC_TEXTS
|
|
level_state.weather = level_state.weather or lzr_globals.DEFAULT_WEATHER
|
|
|
|
local bounding_nodes = {
|
|
node_wall = level_state.wall,
|
|
node_window = level_state.window,
|
|
node_ceiling = level_state.ceiling,
|
|
node_floor = level_state.floor,
|
|
}
|
|
lzr_levels.prepare_and_build_custom_level(schem, nil, nil, bounding_nodes)
|
|
lzr_gui.show_level_bounds(player, lzr_globals.LEVEL_POS, schem.size)
|
|
|
|
if lzr_ambience.ambience_exists(level_state.ambient) then
|
|
lzr_ambience.set_ambience(level_state.ambient)
|
|
else
|
|
level_state.ambient = "none"
|
|
lzr_ambience.set_ambience("none")
|
|
end
|
|
if lzr_sky.sky_exists(level_state.sky) then
|
|
lzr_sky.set_sky(level_state.sky)
|
|
else
|
|
lzr_sky.set_sky(lzr_globals.DEFAULT_SKY)
|
|
end
|
|
if lzr_weather.weather_exists(level_state.weather) then
|
|
lzr_weather.set_weather(level_state.weather)
|
|
else
|
|
lzr_weather.set_weather(lzr_globals.DEFAULT_WEATHER)
|
|
end
|
|
|
|
level_state.file_name = level_name
|
|
minetest.log("action", "[lzr_editor] Level loaded from "..filename)
|
|
return true
|
|
else
|
|
minetest.log("error", "[lzr_editor] Failed to read level from "..filename)
|
|
return false
|
|
end
|
|
end
|
|
|
|
|
|
minetest.register_chatcommand("editor_load", {
|
|
description = S("Load level"),
|
|
params = S("<level name>"),
|
|
func = function(name, param)
|
|
local player = minetest.get_player_by_name(name)
|
|
if lzr_gamestate.get_state() ~= lzr_gamestate.EDITOR then
|
|
return false, S("Not in editor mode!")
|
|
end
|
|
if param == "" then
|
|
return false, S("No level name provided.")
|
|
end
|
|
local level_name = param
|
|
if check_for_slash(level_name) then
|
|
return false, S("Level name must not contain slash or backslash!")
|
|
end
|
|
local ok = file_exists(minetest.get_worldpath().."/levels", level_name..".mts")
|
|
if not ok then
|
|
return false, S("Level file does not exist!")
|
|
end
|
|
|
|
local ok = load_level(level_name, player)
|
|
if ok then
|
|
return true, S("Level loaded.")
|
|
else
|
|
return false, S("Error reading level file!")
|
|
end
|
|
end,
|
|
})
|
|
|
|
minetest.register_chatcommand("editor", {
|
|
description = S("Start or exit level editor"),
|
|
params = S("[ enter | exit ]"),
|
|
func = function(name, param)
|
|
local player = minetest.get_player_by_name(name)
|
|
if param == "" or param == "enter" then
|
|
local ok = lzr_editor.enter_editor(player)
|
|
if ok then
|
|
return true
|
|
else
|
|
return false, S("Already in level editor!")
|
|
end
|
|
elseif param == "exit" then
|
|
local ok = exit_editor(player)
|
|
if ok then
|
|
return true
|
|
else
|
|
return false, S("Not in level editor!")
|
|
end
|
|
end
|
|
return false
|
|
end,
|
|
})
|
|
|
|
-- Unlimited node placement in editor mode
|
|
minetest.register_on_placenode(function(pos, newnode, placer, oldnode, itemstack)
|
|
if placer and placer:is_player() then
|
|
return lzr_gamestate.get_state() == lzr_gamestate.EDITOR
|
|
end
|
|
end)
|
|
|
|
-- Don't pick node up if the item is already in the inventory
|
|
local old_handle_node_drops = minetest.handle_node_drops
|
|
function minetest.handle_node_drops(pos, drops, digger)
|
|
if not digger or not digger:is_player() or
|
|
lzr_gamestate.get_state() ~= lzr_gamestate.EDITOR then
|
|
return old_handle_node_drops(pos, drops, digger)
|
|
end
|
|
local inv = digger:get_inventory()
|
|
if inv then
|
|
for _, item in ipairs(drops) do
|
|
if not inv:contains_item("main", item, true) then
|
|
inv:add_item("main", item)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
lzr_gamestate.register_on_enter_state(function(state)
|
|
if state == lzr_gamestate.EDITOR then
|
|
local player = minetest.get_player_by_name("singleplayer")
|
|
local message = S("Welcome to the Level Editor!").."\n"..
|
|
S("See LEVEL_EDITOR.md for instructions.")
|
|
|
|
lzr_player.set_editor_inventory(player)
|
|
lzr_gui.set_editor_gui(player)
|
|
lzr_ambience.set_ambience(lzr_ambience.DEFAULT_AMBIENCE)
|
|
lzr_sky.set_sky(lzr_globals.DEFAULT_SKY)
|
|
lzr_weather.set_weather(lzr_globals.DEFAULT_WEATHER)
|
|
|
|
give_initial_items(player)
|
|
|
|
lzr_privs.grant_edit_privs(player)
|
|
|
|
if lzr_editor.first_time then
|
|
minetest.chat_send_player("singleplayer", message)
|
|
lzr_editor.first_time = false
|
|
end
|
|
end
|
|
end)
|
|
|
|
lzr_gamestate.register_on_exit_state(function(state)
|
|
if state == lzr_gamestate.EDITOR then
|
|
minetest.log("action", "[lzr_editor] Autosaving level on editor exit")
|
|
save_level(lzr_globals.AUTOSAVE_NAME, true)
|
|
local player = minetest.get_player_by_name("singleplayer")
|
|
if player then
|
|
lzr_privs.revoke_edit_privs(player)
|
|
end
|
|
end
|
|
end)
|
|
|
|
local show_settings_dialog = function(player)
|
|
-- Shorthand function to get the current dropdown index of
|
|
-- a multiple-choice item (ambience, sky, weather) as well
|
|
-- as a string to insert in the dropdown[] formspec element
|
|
local get_current_thing = function(thing_type, thing_getter)
|
|
local thing_list = ""
|
|
local current_thing = 1
|
|
local things = thing_getter()
|
|
for t=1, #things do
|
|
-- Construct string for dropdown[]
|
|
thing_list = thing_list .. F(things[t])
|
|
-- Current thing found!
|
|
if things[t] == level_state[thing_type] then
|
|
current_thing = t
|
|
end
|
|
-- Append comma except at end
|
|
if t < #things then
|
|
thing_list = thing_list .. ","
|
|
end
|
|
end
|
|
return thing_list, current_thing
|
|
end
|
|
|
|
local ambient_list, current_ambient = get_current_thing("ambient", lzr_ambience.get_ambiences)
|
|
local sky_list, current_sky = get_current_thing("sky", lzr_sky.get_skies)
|
|
local weather_list, current_weather = get_current_thing("weather", lzr_weather.get_weathers)
|
|
|
|
-- TODO: Use this string when we have a parrot model
|
|
local goldie_speech = NS("Goldie speech")
|
|
|
|
local form = "formspec_version[4]"..
|
|
"size[9,11.7]"..
|
|
"box[0,0;9,0.8;#00000080]"..
|
|
"label[0.4,0.4;"..FS("Level settings").."]"..
|
|
"field[0.5,1.3;8,0.6;level_name;"..FS("Name")..";"..F(level_state.name).."]"..
|
|
"label[0.5,2.3;Size]"..
|
|
"field[1.6,2.3;1,0.6;level_size_x;"..FS("X")..";"..F(tostring(level_state.size.x)).."]"..
|
|
"field[2.62,2.3;1,0.6;level_size_y;"..FS("Y")..";"..F(tostring(level_state.size.y)).."]"..
|
|
"field[3.63,2.3;1,0.6;level_size_z;"..FS("Z")..";"..F(tostring(level_state.size.z)).."]"..
|
|
"field[0.5,3.3;8,0.6;level_wall;"..FS("Wall node")..";"..F(level_state.wall).."]"..
|
|
"field[0.5,4.3;8,0.6;level_window;"..FS("Window node")..";"..F(level_state.window).."]"..
|
|
"field[0.5,5.3;8,0.6;level_floor;"..FS("Floor node")..";"..F(level_state.floor).."]"..
|
|
"field[0.5,6.3;8,0.6;level_ceiling;"..FS("Ceiling node")..";"..F(level_state.ceiling).."]"..
|
|
"field[0.5,7.3;8,0.6;level_npc_goldie;"..FS("Information block text")..";"..F(level_state.npc_texts.goldie).."]"..
|
|
|
|
"label[0.5,8.1;"..FS("Ambience").."]"..
|
|
"dropdown[0.5,8.3;3.5,0.6;level_ambient;"..ambient_list..";"..current_ambient..";false]"..
|
|
|
|
"label[0.5,9.2;"..FS("Sky").."]"..
|
|
"dropdown[0.5,9.4;3.5,0.6;level_sky;"..sky_list..";"..current_sky..";false]"..
|
|
|
|
"label[5,9.2;"..FS("Weather").."]"..
|
|
"dropdown[5,9.4;3.5,0.6;level_weather;"..weather_list..";"..current_weather..";false]"..
|
|
|
|
"button_exit[0.5,10.5;3.5,0.7;level_ok;"..FS("OK").."]"..
|
|
"button_exit[5,10.5;3.5,0.7;level_cancel;"..FS("Cancel").."]"..
|
|
"field_close_on_enter[level_ok;true]"
|
|
minetest.show_formspec(player:get_player_name(), "lzr_editor:level_settings", form)
|
|
end
|
|
|
|
local show_save_load_dialog = function(player, save, level_name)
|
|
if not level_name then
|
|
level_name = ""
|
|
end
|
|
local txt_caption
|
|
local txt_action
|
|
local action
|
|
if save then
|
|
txt_caption = S("Save level as …")
|
|
txt_action = S("Save")
|
|
action = "save"
|
|
else
|
|
txt_caption = S("Load level …")
|
|
txt_action = S("Load")
|
|
action = "load"
|
|
end
|
|
|
|
local levels_files = minetest.get_dir_list(minetest.get_worldpath().."/levels", false)
|
|
local levels = {}
|
|
for l=1, #levels_files do
|
|
if string.lower(string.sub(levels_files[l], -4, -1)) == ".mts" then
|
|
local name = string.sub(levels_files[l], 1, -5)
|
|
table.insert(levels, name)
|
|
end
|
|
end
|
|
table.sort(levels)
|
|
level_file_textlist_state = levels
|
|
local levels_str = ""
|
|
for l=1, #levels do
|
|
local entry = levels[l]
|
|
if entry == lzr_globals.AUTOSAVE_NAME then
|
|
entry = AUTOSAVE_HIGHLIGHT .. entry
|
|
end
|
|
levels_str = levels_str .. entry
|
|
if l < #levels then
|
|
levels_str = levels_str .. ","
|
|
end
|
|
end
|
|
|
|
local level_list_elem = ""
|
|
if #levels > 0 then
|
|
level_list_elem = "label[0.5,1.5;"..FS("File list:").."]" ..
|
|
"textlist[0.5,2;8,4;level_list;"..levels_str.."]"
|
|
end
|
|
|
|
local form = "formspec_version[4]"..
|
|
"size[9,9]"..
|
|
"box[0,0;9,0.8;#00000080]"..
|
|
"label[0.4,0.4;"..F(txt_caption).."]"..
|
|
level_list_elem..
|
|
"field[0.5,6.6;7,0.6;file_name;"..FS("File name")..";"..minetest.formspec_escape(level_name).."]"..
|
|
"label[7.7,6.9;.mts]"..
|
|
"button_exit[0.5,7.7;3.5,0.7;"..action..";"..F(txt_action).."]"..
|
|
"button_exit[5,7.7;3.5,0.7;cancel;"..FS("Cancel").."]"..
|
|
"field_close_on_enter["..action..";true]"
|
|
|
|
local formname
|
|
if save then
|
|
formname = "lzr_editor:level_save"
|
|
else
|
|
formname = "lzr_editor:level_load"
|
|
end
|
|
minetest.show_formspec(player:get_player_name(), formname, form)
|
|
end
|
|
|
|
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
|
local pname = player:get_player_name()
|
|
if formname == "lzr_editor:level_settings" then
|
|
if fields.level_cancel or (not fields.level_ok and not fields.key_enter) then
|
|
return
|
|
end
|
|
local invalid = false
|
|
if not fields.level_name or not tonumber(fields.level_size_x) or not tonumber(fields.level_size_y) or not tonumber(fields.level_size_z)
|
|
or not fields.level_floor or not fields.level_ceiling or not fields.level_window or not fields.level_wall
|
|
or not fields.level_ambient or not fields.level_sky or not fields.level_npc_goldie or not fields.level_weather then
|
|
return
|
|
end
|
|
local old_level_size = table.copy(level_state.size)
|
|
level_state.name = fields.level_name
|
|
level_state.size.x = math.floor(tonumber(fields.level_size_x))
|
|
level_state.size.y = math.floor(tonumber(fields.level_size_y))
|
|
level_state.size.z = math.floor(tonumber(fields.level_size_z))
|
|
for _, axis in pairs({"x","y","z"}) do
|
|
if level_state.size[axis] > lzr_globals.PLAYFIELD_SIZE[axis]-1 then
|
|
level_state.size[axis] = lzr_globals.PLAYFIELD_SIZE[axis]-1
|
|
end
|
|
if level_state.size[axis] < lzr_globals.PLAYFIELD_SIZE_MIN[axis]-1 then
|
|
level_state.size[axis] = lzr_globals.PLAYFIELD_SIZE_MIN[axis]-1
|
|
end
|
|
end
|
|
local old_floor = level_state.floor
|
|
local old_window = level_state.window
|
|
local old_ceiling = level_state.ceiling
|
|
local old_wall = level_state.wall
|
|
level_state.floor = fields.level_floor
|
|
level_state.window = fields.level_window
|
|
level_state.ceiling = fields.level_ceiling
|
|
level_state.wall = fields.level_wall
|
|
level_state.ambient = fields.level_ambient
|
|
level_state.sky = fields.level_sky
|
|
level_state.npc_texts = {}
|
|
level_state.npc_texts.goldie = fields.level_npc_goldie
|
|
level_state.weather = fields.level_weather
|
|
|
|
local rebuild_room = false
|
|
local nodes = {}
|
|
nodes.node_floor = level_state.floor
|
|
nodes.node_window = level_state.window
|
|
nodes.node_ceiling = level_state.ceiling
|
|
nodes.node_wall = level_state.wall
|
|
if old_floor ~= nodes.floor or old_window ~= nodes.window or old_ceiling ~= nodes.ceiling or old_wall ~= nodes.wall then
|
|
for k,v in pairs(nodes) do
|
|
if not minetest.registered_nodes[v] then
|
|
nodes[k] = lzr_globals.DEFAULT_WALL_NODE
|
|
level_state[string.sub(k, 6)] = lzr_globals.DEFAULT_WALL_NODE
|
|
end
|
|
end
|
|
rebuild_room = true
|
|
end
|
|
if not vector.equals(level_state.size, old_level_size) then
|
|
rebuild_room = true
|
|
end
|
|
|
|
if rebuild_room then
|
|
minetest.log("action", "[lzr_editor] Autosaving level on level rebuild")
|
|
save_level(lzr_globals.AUTOSAVE_NAME, true)
|
|
|
|
lzr_levels.resize_room(old_level_size, level_state.size, nodes)
|
|
rebuild_room = true
|
|
lzr_gui.show_level_bounds(player, lzr_globals.LEVEL_POS, level_state.size)
|
|
end
|
|
|
|
if lzr_ambience.ambience_exists(level_state.ambient) then
|
|
lzr_ambience.set_ambience(level_state.ambient)
|
|
else
|
|
lzr_ambience.set_ambience("none")
|
|
end
|
|
if lzr_sky.sky_exists(level_state.sky) then
|
|
lzr_sky.set_sky(level_state.sky)
|
|
else
|
|
lzr_sky.set_sky(lzr_globals.DEFAULT_SKY)
|
|
end
|
|
if lzr_weather.weather_exists(level_state.weather) then
|
|
lzr_weather.set_weather(level_state.weather)
|
|
else
|
|
lzr_weather.set_weather(lzr_globals.DEFAULT_WEATHER)
|
|
end
|
|
|
|
return
|
|
elseif formname == "lzr_editor:level_save" or formname == "lzr_editor:level_load" then
|
|
if fields.level_list then
|
|
local evnt = minetest.explode_textlist_event(fields.level_list)
|
|
if evnt.type == "CHG" or evnt.type == "DBL" then
|
|
local file = level_file_textlist_state[evnt.index]
|
|
if file then
|
|
if formname == "lzr_editor:level_save" then
|
|
show_save_load_dialog(player, true, file)
|
|
else
|
|
show_save_load_dialog(player, false, file)
|
|
end
|
|
return
|
|
end
|
|
end
|
|
elseif (fields.load or (formname == "lzr_editor:level_load" and fields.key_enter)) and fields.file_name then
|
|
if fields.file_name == "" then
|
|
minetest.chat_send_player(pname, S("No level name provided."))
|
|
return
|
|
end
|
|
if check_for_slash(fields.file_name) then
|
|
minetest.chat_send_player(pname, S("File name must not contain slash or backslash!"))
|
|
return false
|
|
end
|
|
local exists = file_exists(minetest.get_worldpath().."/levels", fields.file_name..".mts")
|
|
if not exists then
|
|
minetest.chat_send_player(pname, S("Level file does not exist!"))
|
|
return
|
|
end
|
|
local ok, filename = load_level(fields.file_name, player)
|
|
if ok then
|
|
minetest.chat_send_player(pname, S("Level loaded."))
|
|
else
|
|
minetest.chat_send_player(pname, S("Error reading level file!"))
|
|
end
|
|
elseif (fields.save or (formname == "lzr_editor:level_save" and fields.key_enter)) and fields.file_name then
|
|
if fields.file_name == "" then
|
|
minetest.chat_send_player(pname, S("No level name provided."))
|
|
return
|
|
end
|
|
if check_for_slash(fields.file_name) then
|
|
minetest.chat_send_player(pname, S("File name must not contain slash or backslash!"))
|
|
return false
|
|
end
|
|
local ok, filename, filename2 = save_level(fields.file_name)
|
|
if ok and filename and filename2 then
|
|
minetest.chat_send_player(pname, S("Level saved to @1 and @2.", filename, filename2))
|
|
elseif ok and filename then
|
|
minetest.chat_send_player(pname, S("Level saved to @1, but could not write metadata to @2.", filename, filename2))
|
|
else
|
|
minetest.chat_send_player(pname, S("Error writing level file!"))
|
|
end
|
|
end
|
|
return
|
|
end
|
|
|
|
if fields.__lzr_level_editor_exit then
|
|
exit_editor()
|
|
return
|
|
elseif fields.__lzr_level_editor_settings then
|
|
show_settings_dialog(player)
|
|
return
|
|
elseif fields.__lzr_level_editor_save then
|
|
show_save_load_dialog(player, true, level_state.file_name)
|
|
return
|
|
elseif fields.__lzr_level_editor_load then
|
|
show_save_load_dialog(player, false, level_state.file_name)
|
|
return
|
|
elseif fields.__lzr_level_editor_get_item then
|
|
lzr_getitem.show_formspec(pname)
|
|
return
|
|
end
|
|
end)
|
|
|
|
-- Returns list of all custom level file names, sorted.
|
|
-- The file name suffix is omitted.
|
|
lzr_editor.get_custom_levels = function()
|
|
local levels_files = minetest.get_dir_list(minetest.get_worldpath().."/levels", false)
|
|
local levels = {}
|
|
for l=1, #levels_files do
|
|
if string.lower(string.sub(levels_files[l], -4, -1)) == ".mts" then
|
|
local name = string.sub(levels_files[l], 1, -5)
|
|
table.insert(levels, name)
|
|
end
|
|
end
|
|
table.sort(levels)
|
|
return levels
|
|
end
|
|
|
|
-- Returns proper level name of a custom level.
|
|
-- The proper level name is read from a matching CSV file.
|
|
-- If no such file is found, the empty string is returned.
|
|
-- * level_name: Level file name without suffix
|
|
-- * with_fallback: If true, will return "Untitled (<file name>)"
|
|
-- instead of the empty string if the level name is empty or undefined
|
|
lzr_editor.get_custom_level_name = function(level_name, with_fallback)
|
|
local csv_file = io.open(minetest.get_worldpath().."/levels/"..level_name..".csv", "r")
|
|
local pname
|
|
if csv_file then
|
|
local csv_string = csv_file:read("*a")
|
|
csv_file:close()
|
|
local csv_parsed = lzr_csv.parse_csv(csv_string)
|
|
if csv_parsed and #csv_parsed >= 1 then
|
|
pname = csv_parsed[1][2]
|
|
end
|
|
end
|
|
if not pname then
|
|
pname = ""
|
|
end
|
|
if pname == "" and with_fallback then
|
|
return S("Untitled (@1)", level_name)
|
|
end
|
|
return pname
|
|
end
|
|
|
|
-- Expose invisible blocks in editor mode
|
|
local invis_display_timer = 0
|
|
local INVIS_DISPLAY_UPDATE_TIME = 1
|
|
local INVIS_DISPLAY_RANGE = 8
|
|
minetest.register_globalstep(function(dtime)
|
|
local state = lzr_gamestate.get_state()
|
|
if state ~= lzr_gamestate.EDITOR then
|
|
return
|
|
end
|
|
invis_display_timer = invis_display_timer + dtime
|
|
if invis_display_timer < INVIS_DISPLAY_UPDATE_TIME then
|
|
return
|
|
end
|
|
invis_display_timer = 0
|
|
|
|
local player = minetest.get_player_by_name("singleplayer")
|
|
if not player then
|
|
return
|
|
end
|
|
local pos = vector.round(player:get_pos())
|
|
local r = INVIS_DISPLAY_RANGE
|
|
local vm = minetest.get_voxel_manip()
|
|
local emin, emax = vm:read_from_map(vector.offset(pos, -r, -r, -r), vector.offset(pos, r, r, r))
|
|
local area = VoxelArea:new({MinEdge = emin, MaxEdge = emax})
|
|
local data = vm:get_data()
|
|
for x=pos.x-r, pos.x+r do
|
|
for y=pos.y-r, pos.y+r do
|
|
for z=pos.z-r, pos.z+r do
|
|
local vi = area:index(x,y,z)
|
|
local nodename = minetest.get_name_from_content_id(data[vi])
|
|
local texture
|
|
if nodename == "lzr_core:barrier" then
|
|
texture = "lzr_core_barrier.png"
|
|
elseif nodename == "lzr_core:rain_membrane" then
|
|
texture = "lzr_core_rain_membrane.png"
|
|
end
|
|
if texture then
|
|
minetest.add_particle({
|
|
pos = {x=x, y=y, z=z},
|
|
texture = texture,
|
|
glow = minetest.LIGHT_MAX,
|
|
size = 8,
|
|
expirationtime = 1.1,
|
|
})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end)
|