2024-08-11 11:46:42 +02:00

774 lines
25 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

local S = minetest.get_translator("lzr_levels")
lzr_levels = {}
local WINDOW_HEIGHT = 3
local WINDOW_DIST = 3
-- Time the level title/complete message is shown (seconds)
local LEVEL_CAPTION_TIME = 3.0
local FINAL_LEVEL_CAPTION_TIME = 5.0
local current_level = nil
local core_level_data = {}
local current_level_data = nil
lzr_levels.LAST_LEVEL = 0
local level_size = vector.copy(lzr_globals.DEFAULT_LEVEL_SIZE)
lzr_levels.get_level_size = function()
return level_size
end
local set_level_size = function(new_size)
level_size = vector.copy(new_size)
minetest.log("verbose", "[lzr_levels] Level size set to: "..minetest.pos_to_string(new_size))
end
local get_max_treasures = function()
if current_level and current_level_data then
return current_level_data[current_level].treasures
end
end
-- Mod storage for game progress
local mod_storage = minetest.get_mod_storage()
local flat_index_to_pos = function(index, size)
local d = index-1
local x = d % size.x
local y = math.floor((d / size.x) % size.y)
local z = math.floor((d / (size.x*size.y)) % size.z)
return vector.new(x,y,z)
end
local analyze_level_schematic = function(filename, levels_path, level_data_entry)
local filepath = levels_path .. "/" ..filename
local schem = minetest.read_schematic(filepath, {write_yslice_prob="none"})
assert(schem, "Could not load level file: "..filename)
level_data_entry.contains_rotatable_block = false
level_data_entry.treasures = 0
level_data_entry.size = schem.size
local size = level_data_entry.size
local teleporters = 0
local parrot_spawners = 0
-- Find rotatable blocks, treasures, parrot spawners and find the start position
for d=1, #schem.data do
local nodename = schem.data[d].name
local is_rotatable = minetest.get_item_group(nodename, "rotatable") == 1
local treasure = minetest.get_item_group(nodename, "chest_closed") > 0 or minetest.get_item_group(nodename, "chest_open_treasure") > 0
if is_rotatable then
level_data_entry.contains_rotatable_block = true
end
if treasure then
level_data_entry.treasures = level_data_entry.treasures + 1
end
if nodename == "lzr_teleporter:teleporter_off" then
-- Player spawn pos on teleporter
teleporters = teleporters + 1
local start = flat_index_to_pos(d, size)
start = vector.add(start, vector.new(0, 0.5, 0))
level_data_entry.start_pos = start
end
if nodename == "lzr_parrot_npc:parrot_spawner" then
-- Parrot spawn pos
parrot_spawners = parrot_spawners + 1
local ppos = flat_index_to_pos(d, size)
level_data_entry.parrot_pos = ppos
end
end
-- Print warnings about level problems
if teleporters == 0 then
minetest.log("warning", "[lzr_levels] Level "..filename.." in "..levels_path.." doesn't have a teleporter!")
end
if teleporters > 1 then
minetest.log("warning", "[lzr_levels] Level "..filename.." in "..levels_path.." has more than one teleporter!")
end
if parrot_spawners > 1 then
minetest.log("warning", "[lzr_levels] Level "..filename.." in "..levels_path.." has more than one parrot spawner!")
end
end
-- Create a level_data table for a single level
-- with default settings. Used for levels where
-- no metadata is available.
lzr_levels.create_fallback_level_data = function(level, levels_path)
local local_level_data = {}
local filename = level .. ".mts"
local_level_data.levels_path = levels_path
local local_level = {
filename = filename,
name = "",
node_wall = lzr_globals.DEFAULT_WALL_NODE,
node_floor = lzr_globals.DEFAULT_FLOOR_NODE,
node_ceiling = lzr_globals.DEFAULT_CEILING_NODE,
node_window = lzr_globals.DEFAULT_WINDOW_NODE,
ambience = lzr_ambience.DEFAULT_AMBIENCE,
sky = lzr_globals.DEFAULT_SKY,
npc_texts = lzr_globals.DEFAULT_NPC_TEXTS,
weather = lzr_globals.DEFAULT_WEATHER,
}
analyze_level_schematic(filename, levels_path, local_level)
local_level_data[1] = local_level
return local_level_data
end
--[[ Read the level schematics to find out some metadata about them.
Returns a level_data table.
A CSV file (given by `level_list_path` is used for metadata.
Syntax of a single record in the CSV file:
<File name>,<Title>,<Border nodes>,<Ambience>,<Sky>,<NPC texts>,<Weather>
Border nodes is a list of nodenames for the level border, separated by the pipe symbol (“|”), in this order:
wall, window, floor, ceiling
wall is mandatory, the rest is optional (will default to the wall node)
Ambience is an ambience ID for the background noise (see lzr_ambience).
Sky and weather are IDs for sky and weather (see lzr_sky, lzr_weather).
NPC texts is a *single* text used by NPC like the information block.
(multiple NPC texts are not supported yet).
All entries up to ambience are mandatory, but sky, NPC texts and
Weather can be omitted.
Parameters:
* level_list_path: Path to CSV file file containing the level list
* levels_path: Path in which the levels are stored (.mts files)
Returns nil if CSV file coult not be read.
]]
lzr_levels.analyze_levels = function(level_list_path, levels_path)
local level_list_file = io.open(level_list_path, "r")
if not level_list_file then
return
end
local level_list_string = level_list_file:read("*a")
local level_list = lzr_csv.parse_csv(level_list_string)
level_list_file:close()
local local_level_data = {}
local_level_data.levels_path = levels_path
for ll=1, #level_list do
local level_list_row = level_list[ll]
local filename = level_list_row[1]
local lname = level_list_row[2]
local nodes = level_list_row[3]
local ambience = level_list_row[4]
local sky = level_list_row[5] or lzr_globals.DEFAULT_SKY
local npc_texts_raw = level_list_row[6]
local npc_texts
if npc_texts_raw then
npc_texts = { goldie = npc_texts_raw }
else
npc_texts = lzr_globals.DEFAULT_NPC_TEXTS
end
local weather = level_list_row[7] or lzr_globals.DEFAULT_WEATHER
local node_matches = string.split(nodes, "|")
local node_wall = node_matches[1]
local node_window = node_matches[2] or node_wall
local node_floor = node_matches[3] or node_wall
local node_ceiling = node_matches[4] or node_wall
table.insert(local_level_data, {filename=filename, name=lname, node_wall=node_wall, node_window=node_window, node_floor=node_floor, node_ceiling=node_ceiling, ambience=ambience, sky=sky, npc_texts=npc_texts, weather=weather})
end
-- Scan the level schematics for intersting blocks, treasures, etc.
-- and write it into local_level_data
for l=1, #local_level_data do
local filename = local_level_data[l].filename
analyze_level_schematic(filename, levels_path, local_level_data[l])
end
return local_level_data
end
-- Set the basic nodes of the room
local set_room_nodes = function(pos, size, nodes)
local psize = size
local posses_border = {}
local posses_window = {}
local posses_floor = {}
local posses_ceiling = {}
local size = vector.add(psize, {x=1,y=1,z=1})
set_level_size(psize)
for x=0,size.x do
for z=0,size.z do
for y=0,size.y do
local offset = {x=x-1, y=y-1, z=z-1}
if not ((x >= 1 and x < size.x) and
(y >= 1 and y < size.y) and
(z >= 1 and z < size.z)) then
if y == WINDOW_HEIGHT and ((x >= 1 and x < size.x and x % WINDOW_DIST == 0) or (z >= 1 and z < size.z and z % WINDOW_DIST == 0)) then
table.insert(posses_window, vector.add(pos, offset))
else
if y == 0 then
table.insert(posses_floor, vector.add(pos, offset))
elseif y == size.y then
table.insert(posses_ceiling, vector.add(pos, offset))
else
table.insert(posses_border, vector.add(pos, offset))
end
end
end
end
end
end
minetest.bulk_set_node(posses_floor, {name=nodes.node_floor})
minetest.bulk_set_node(posses_border, {name=nodes.node_wall})
minetest.bulk_set_node(posses_window, {name=nodes.node_window})
minetest.bulk_set_node(posses_ceiling, {name=nodes.node_ceiling})
end
local get_singleplayer = function()
return minetest.get_player_by_name("singleplayer")
end
local emerge_callback = function(blockpos, action, calls_remaining, param)
minetest.log("verbose", "[lzr_levels] emerge_callback() ...")
if action == minetest.EMERGE_ERRORED then
minetest.log("error", "[lzr_levels] Room emerging error.")
elseif action == minetest.EMERGE_CANCELLED then
minetest.log("error", "[lzr_levels] Room emerging cancelled.")
elseif calls_remaining == 0 and (action == minetest.EMERGE_GENERATED or action == minetest.EMERGE_FROM_DISK or action == minetest.EMERGE_FROM_MEMORY) then
if param.mode == "resize" then
local inner = {
x = math.min(param.old_size.x, param.size.x),
y = math.min(param.old_size.y, param.size.y),
z = math.min(param.old_size.z, param.size.z),
}
lzr_levels.clear_playfield(param.size, inner)
set_room_nodes(param.pos, param.size, param.nodes)
lzr_laser.full_laser_update(lzr_globals.PLAYFIELD_START, lzr_globals.PLAYFIELD_END)
minetest.log("action", "[lzr_levels] Room emerge resize callback done")
else
lzr_levels.clear_playfield(param.size)
set_room_nodes(param.pos, param.size, param.nodes)
local level_ok = false
if param.level then
level_ok = lzr_levels.build_level(param.level, param.level_data)
elseif param.schematic then
level_ok = lzr_levels.build_level_raw(param.schematic)
else
local player = get_singleplayer()
if player then
if param.spawn_pos then
player:set_pos(param.spawn_pos)
end
if param.yaw then
player:set_look_horizontal(param.yaw)
player:set_look_vertical(0)
end
end
minetest.log("action", "[lzr_levels] Empty room emerge callback done")
return
end
if not level_ok then
minetest.log("error", "[lzr_levels] Room emerge callback done with error")
else
local player = get_singleplayer()
if player then
if param.spawn_pos then
player:set_pos(param.spawn_pos)
end
if param.yaw then
player:set_look_horizontal(param.yaw)
player:set_look_vertical(0)
end
local gs = lzr_gamestate.get_state()
if gs == lzr_gamestate.LEVEL then
local found = lzr_laser.count_found_treasures(lzr_globals.PLAYFIELD_START, lzr_globals.PLAYFIELD_END)
lzr_gui.update_treasure_status(player, found, get_max_treasures())
if param.parrot_pos then
minetest.set_node(param.parrot_pos, {name="air"})
minetest.add_entity(vector.add(param.parrot_pos, lzr_globals.PARROT_SPAWN_OFFSET), "lzr_parrot_npc:parrot")
end
end
if param.level then
local lname = lzr_levels.get_level_name(param.level, param.level_data, true)
if lname ~= "" then
lzr_messages.show_message(player, lname, LEVEL_CAPTION_TIME)
end
minetest.sound_play({name = "lzr_levels_level_enter", gain = 1}, {to_player=player:get_player_name()}, true)
end
end
minetest.log("action", "[lzr_levels] Room emerge callback done")
end
end
end
end
local prepare_room = function(room_data)
minetest.emerge_area(room_data.pos, vector.add(room_data.pos, room_data.size), emerge_callback, room_data)
end
-- Resets the playfield with air or water blocks.
-- Also clears all objects.
-- * room_size: Size of the room (required for water check)
-- * protected_room_size: (optional): Nodes within a virtual
-- room of the given size will not be emptied.
-- If nil, this has no effect
function lzr_levels.clear_playfield(room_size, protected_room_size)
local posses_air = {}
local posses_water = {}
local size = lzr_globals.PLAYFIELD_SIZE
local prot_min, prot_max
if protected_room_size then
prot_min = vector.new(0,0,0)
prot_max = vector.add(prot_min, protected_room_size)
end
for z=0, size.z do
for y=0, size.y do
for x=0, size.x do
local pos = vector.new(x,y,z)
if not protected_room_size or not vector.in_area(pos, prot_min, prot_max) then
pos = vector.add(pos, lzr_globals.PLAYFIELD_START)
if pos.y <= lzr_globals.WATER_LEVEL and (x > room_size.x or y > room_size.y or z > room_size.z) then
table.insert(posses_water, pos)
else
table.insert(posses_air, pos)
end
end
end
end
end
minetest.bulk_set_node(posses_water, {name="lzr_core:ocean_water_source"})
minetest.bulk_set_node(posses_air, {name="air"})
-- Also clear objects
local objects = minetest.get_objects_in_area(
lzr_globals.PLAYFIELD_START,
vector.add(lzr_globals.PLAYFIELD_START, size))
for o=1, #objects do
local obj = objects[o]
if not obj:is_player() then
obj:remove()
end
end
end
-- room_data:
-- - pos: Room pos
-- - size: Room size vector
-- - spawn_pos: Relative player spawn position (optional)
-- - yaw: Initial player yaw (optional)
-- Either one of these (or none of them for empty room):
-- - level: level ID (for builtin level)
-- - schematic: Path to schematic
-- - nodes (optional): Table containing node names of level border nodes:
-- - node_floor, node_ceiling, node_wall, node_window
function lzr_levels.build_room(room_data)
if not room_data.nodes then
room_data.nodes = {
node_floor = lzr_globals.DEFAULT_FLOOR_NODE,
node_wall = lzr_globals.DEFAULT_WALL_NODE,
node_ceiling = lzr_globals.DEFAULT_CEILING_NODE,
node_window = lzr_globals.DEFAULT_WINDOW_NODE,
}
end
prepare_room(room_data)
end
-- Resize room while preserving the inner contents of the old room
-- as much as the new size permits.
function lzr_levels.resize_room(old_size, new_size, nodes)
prepare_room({mode="resize", pos=lzr_globals.LEVEL_POS, old_size=old_size, size=new_size, nodes=nodes})
end
function lzr_levels.prepare_and_build_level(level, level_data, spawn_pos, yaw)
if not level_data then
level_data = core_level_data
end
local bounding_nodes = {
node_floor = level_data[level].node_floor,
node_wall = level_data[level].node_wall,
node_ceiling = level_data[level].node_ceiling,
node_window = level_data[level].node_window,
}
lzr_levels.build_room({mode="build", pos=lzr_globals.LEVEL_POS, size=level_data[level].size, level=level, level_data=level_data, spawn_pos=spawn_pos, yaw=yaw, parrot_pos=level_data[level].parrot_pos, nodes=bounding_nodes})
end
function lzr_levels.prepare_and_build_custom_level(schematic, spawn_pos, yaw, bounding_nodes)
lzr_levels.build_room({mode="build", pos=lzr_globals.LEVEL_POS, size=schematic.size, schematic=schematic, spawn_pos=spawn_pos, yaw=yaw, nodes=bounding_nodes})
end
function lzr_levels.build_level_raw(schematic_specifier)
local schem = minetest.place_schematic(lzr_globals.LEVEL_POS, schematic_specifier, "0", {}, true, "")
if schem then
-- Propagate lasers
lzr_laser.full_laser_update(lzr_globals.PLAYFIELD_START, lzr_globals.PLAYFIELD_END)
else
minetest.log("error", "[lzr_levels] lzr_levels.build_level_raw failed to build level")
end
return schem
end
function lzr_levels.build_level(level, level_data)
if not level_data then
level_data = core_level_data
end
local filepath = level_data.levels_path .. "/" .. level_data[level].filename
local schematic_specifier
if level_data == core_level_data then
-- Will provide file name to place_schematic, causing Minetest
-- to cache it for better performance.
schematic_specifier = filepath
else
-- Custom levels must be read uncached because custom levels
-- may be edited in the level editor frequently.
-- Once a schematic was cached by Minetest, it is "locked"
-- in this state forever.
-- Reading a schematic uncached is done by first getting a
-- specifier with read_schematic and then passing it to
-- place_schematic.
schematic_specifier = minetest.read_schematic(filepath, {write_yslice_prob="none"})
end
local schem = lzr_levels.build_level_raw(schematic_specifier)
-- Check for insta-chest-unlock
local all_detectors = lzr_laser.check_all_detectors()
if all_detectors and lzr_gamestate.get_state() == lzr_gamestate.LEVEL then
lzr_laser.unlock_chests(lzr_globals.PLAYFIELD_START, lzr_globals.PLAYFIELD_END)
end
-- Check for insta-win (if the level has no treasure, the
-- player has found "all" treasure of this level)
local done = lzr_laser.check_level_won()
if done and lzr_gamestate.get_state() == lzr_gamestate.LEVEL then
minetest.after(3, function(param)
if lzr_gamestate.get_state() == lzr_gamestate.LEVEL and param.level == current_level and param.level_data == current_level_data then
lzr_levels.level_complete()
end
end, {level=level, level_data=level_data})
end
return schem
end
local function clear_inventory(player)
local inv = player:get_inventory()
for i=1,inv:get_size("main") do
inv:set_stack("main", i, "")
end
end
local function reset_inventory(player, needs_rotate)
clear_inventory(player)
if needs_rotate then
local inv = player:get_inventory()
inv:add_item("main", "screwdriver2:screwdriver")
end
end
local function get_start_pos(level, level_data)
if not level_data then
level_data = core_level_data
end
local start_pos -- player start position, relative to level
local size = level_data[level].size
if level_data[level].start_pos then
start_pos = level_data[level].start_pos
else
-- Fallback start pos
start_pos = vector.new(math.floor(size.x/2), -0.5, math.floor(size.z/2))
end
return start_pos
end
function lzr_levels.get_npc_texts()
if not current_level_data then
return nil
end
local level_data = current_level_data[current_level]
if not level_data then
return nil
end
local texts = level_data.npc_texts
-- Translate NPC texts in core level set
if current_level_data == core_level_data then
local translated_texts = {}
if texts then
for npc, text in pairs(texts) do
local tt = minetest.translate("_lzr_levels_npc_texts", text)
translated_texts[npc] = tt
end
end
return translated_texts
else
return level_data.npc_texts
end
end
function lzr_levels.get_current_spawn_pos()
if not current_level then
return nil
end
local start_pos = get_start_pos(current_level, current_level_data)
-- absolute spawn position
local spawn_pos = vector.add(lzr_globals.LEVEL_POS, start_pos)
return spawn_pos
end
function lzr_levels.start_level(level, level_data)
if not level_data then
level_data = core_level_data
end
current_level = level
current_level_data = level_data
local player = get_singleplayer()
local size = level_data[level].size
local start_pos = get_start_pos(level, level_data)
local spawn_pos = vector.add(lzr_globals.LEVEL_POS, start_pos)
local yaw = 0
if start_pos.z > size.z/2 then
yaw = yaw + math.pi
end
lzr_levels.prepare_and_build_level(level, level_data, spawn_pos, yaw)
local needs_rotate = level_data[current_level].contains_rotatable_block
reset_inventory(player, needs_rotate)
if lzr_gamestate.get_state() ~= lzr_gamestate.EDITOR then
lzr_gamestate.set_state(lzr_gamestate.LEVEL)
end
lzr_ambience.set_ambience(level_data[level].ambience)
lzr_sky.set_sky(level_data[level].sky)
lzr_weather.set_weather(level_data[level].weather)
minetest.log("action", "[lzr_levels] Starting level "..level)
end
function lzr_levels.clear_level_progress()
mod_storage:set_string("lzr_levels:levels", "")
minetest.log("action", "[lzr_levels] Level progress was cleared")
end
function lzr_levels.mark_level_as_complete(level, level_data)
if level_data ~= core_level_data then
return
end
local levels = minetest.deserialize(mod_storage:get_string("lzr_levels:levels"))
if not levels then
levels = {}
end
levels[level] = true
mod_storage:set_string("lzr_levels:levels", minetest.serialize(levels))
end
function lzr_levels.get_completed_levels()
local levels = minetest.deserialize(mod_storage:get_string("lzr_levels:levels"))
if not levels then
levels = {}
end
return levels
end
function lzr_levels.level_complete()
if lzr_gamestate.get_state() ~= lzr_gamestate.LEVEL then
return false
end
lzr_levels.mark_level_as_complete(current_level, current_level_data)
-- Trigger chest treasure particle animation
local open_chests = minetest.find_nodes_in_area(lzr_globals.PLAYFIELD_START, lzr_globals.PLAYFIELD_END, {"group:chest_open"})
for c=1, #open_chests do
local pos = open_chests[c]
local node = minetest.get_node(pos)
local def = minetest.registered_nodes[node.name]
if def._lzr_send_treasure then
def._lzr_send_treasure(pos, node)
end
end
if #open_chests > 0 then
lzr_laser.full_laser_update(lzr_globals.PLAYFIELD_START, lzr_globals.PLAYFIELD_END)
end
local player = get_singleplayer()
minetest.close_formspec(player:get_player_name(), "lzr_teleporter:level")
local has_treasure = current_level_data[current_level].treasures > 0
if has_treasure then
lzr_messages.show_message(player, S("Level complete!"), LEVEL_CAPTION_TIME)
else
-- Level had no treasures and thus was insta-won;
-- show special message
lzr_messages.show_message(player, S("There are no treasures here!"), LEVEL_CAPTION_TIME)
end
if current_level_data == core_level_data then
minetest.log("action", "[lzr_levels] Level "..current_level.." completed")
else
minetest.log("action", "[lzr_levels] Level completed")
end
-- Victory fanare
if has_treasure then
minetest.sound_play({name = "lzr_levels_level_complete", gain = 1}, {to_player=player:get_player_name()}, true)
end
lzr_gamestate.set_state(lzr_gamestate.LEVEL_COMPLETE)
-- Go to next level (only for core levels)
minetest.after(3, function(completed_level)
if lzr_gamestate.get_state() == lzr_gamestate.LEVEL_COMPLETE and current_level == completed_level then
lzr_levels.next_level()
end
end, current_level)
end
function lzr_levels.next_level()
if current_level_data ~= core_level_data then
lzr_levels.leave_level()
return
end
local player = get_singleplayer()
current_level = current_level + 1
if current_level > lzr_levels.LAST_LEVEL then
lzr_messages.show_message(player, S("Final level completed!"), FINAL_LEVEL_CAPTION_TIME)
lzr_levels.leave_level()
else
lzr_levels.start_level(current_level, current_level_data)
end
end
function lzr_levels.go_to_menu()
current_level = nil
current_level_data = nil
local player = get_singleplayer()
clear_inventory(player)
player:set_pos(vector.add(lzr_globals.MENU_SHIP_POS, lzr_globals.MENU_SHIP_PLAYER_SPAWN_OFFSET))
player:set_look_horizontal(0)
player:set_look_vertical(0)
lzr_gamestate.set_state(lzr_gamestate.MENU)
end
function lzr_levels.leave_level()
current_level = nil
current_level_data = nil
lzr_levels.go_to_menu()
end
function lzr_levels.get_current_level()
return current_level
end
function lzr_levels.get_current_level_data()
return current_level_data
end
-- Returns the name of the level with the given level number, translated
-- (translation only available for core levels).
-- Note that levels may have an empty name.
-- If with_fallback is true and the level's name is empty, it will return
-- "Untitled (<file name>)" (translated)
function lzr_levels.get_level_name(level, level_data, with_fallback)
if not level_data then
level_data = core_level_data
end
local name = level_data[level].name
if name and name ~= "" then
if level_data == core_level_data then
return minetest.translate("_lzr_levels_level_names", level_data[level].name)
else
return name
end
else
if with_fallback then
local fname = level_data[level].filename
fname = string.sub(fname, 1, -5)
return S("Untitled (@1)", fname)
else
return ""
end
end
end
function lzr_levels.restart_level()
local state = lzr_gamestate.get_state()
if state == lzr_gamestate.LEVEL then
lzr_levels.start_level(current_level, current_level_data)
return true
else
return false
end
end
-- To be called when a treasure has been found (only in game mode LEVEL!)
function lzr_levels.found_treasure()
local treasures = lzr_laser.count_found_treasures(lzr_globals.PLAYFIELD_START, lzr_globals.PLAYFIELD_END)
local player = get_singleplayer()
lzr_gui.update_treasure_status(player, treasures, get_max_treasures())
end
minetest.register_chatcommand("restart", {
privs = {},
params = "",
description = S("Restart current level"),
func = function(name, param)
local state = lzr_gamestate.get_state()
if state == lzr_gamestate.LEVEL then
lzr_levels.restart_level()
return true
elseif state == lzr_gamestate.LEVEL_COMPLETE then
return false, S("Cant restart level right now.")
else
return false, S("Not playing in a level!")
end
end,
})
minetest.register_chatcommand("leave", {
privs = {},
params = "",
description = S("Leave current level"),
func = function(name, param)
local state = lzr_gamestate.get_state()
if state == lzr_gamestate.LEVEL or state == lzr_gamestate.LEVEL_COMPLETE or state == lzr_gamestate.EDITOR then
lzr_levels.leave_level(current_level)
return true
else
return false, S("Not playing in a level!")
end
end,
})
minetest.register_chatcommand("reset_progress", {
privs = {},
params = "yes",
description = S("Reset level progress"),
func = function(name, param)
if param == "yes" then
lzr_levels.clear_level_progress()
return true, S("Level progress resetted.")
else
return false, S("To reset level progress, use “/reset_progress yes”")
end
end,
})
lzr_gamestate.register_on_enter_state(function(state)
if state == lzr_gamestate.LEVEL then
local player = minetest.get_player_by_name("singleplayer")
lzr_player.set_play_inventory(player)
lzr_gui.set_play_gui(player)
if current_level and current_level_data then
lzr_ambience.set_ambience(current_level_data[current_level].ambience)
lzr_sky.set_sky(current_level_data[current_level].sky)
lzr_weather.set_weather(current_level_data[current_level].weather)
end
lzr_laser.full_laser_update(lzr_globals.PLAYFIELD_START, lzr_globals.PLAYFIELD_END)
end
end)
local function analyze_core_levels()
core_level_data = lzr_levels.analyze_levels(
minetest.get_modpath("lzr_levels").."/data/level_data.csv",
minetest.get_modpath("lzr_levels").."/schematics"
)
assert(core_level_data, "Could not load level_data.csv")
lzr_levels.LAST_LEVEL = #core_level_data
end
analyze_core_levels()