295 lines
9.1 KiB
Lua
295 lines
9.1 KiB
Lua
local S = minetest.get_translator("lzr_levels")
|
||
|
||
lzr_levels = {}
|
||
|
||
local ROOM_NODE = "lzr_core:wood"
|
||
local WINDOW_NODE = "lzr_decor:woodframed_glass"
|
||
local WINDOW_HEIGHT = 3
|
||
local WINDOW_DIST = 3
|
||
|
||
local current_level = nil
|
||
|
||
local level_data = {}
|
||
lzr_levels.LAST_LEVEL = 0
|
||
|
||
-- Mod storage for game progress
|
||
local mod_storage = minetest.get_mod_storage()
|
||
|
||
-- Read the level schematics to find out some metadata about them
|
||
-- and count the number of levels
|
||
local analyze_levels = function()
|
||
local level_list_path = minetest.get_modpath("lzr_levels").."/data/level_data.csv"
|
||
local level_list_file = io.open(level_list_path, "r")
|
||
assert(level_list_file, "Could not load level_data.csv")
|
||
|
||
for line in level_list_file:lines() do
|
||
local matches = string.split(line, ",")
|
||
assert(matches ~= nil, "Malformed level_data.csv")
|
||
local filename = matches[1]
|
||
table.insert(level_data, {filename=filename})
|
||
end
|
||
lzr_levels.LAST_LEVEL = #level_data
|
||
|
||
-- Mark levels that contain at least 1 rotatable block
|
||
for l=1, #level_data do
|
||
local filename = level_data[l].filename
|
||
local filepath = minetest.get_modpath("lzr_levels").."/schematics/"..filename
|
||
local schem = minetest.read_schematic(filepath, {write_yslice_prob="none"})
|
||
assert(schem, "Could not load level file: "..filename)
|
||
level_data[l].contains_rotatable_block = false
|
||
level_data[l].size = schem.size
|
||
for d=1, #schem.data do
|
||
local nodename = schem.data[d].name
|
||
local is_rotatable = minetest.get_item_group(nodename, "rotatable") == 1
|
||
if is_rotatable then
|
||
level_data[l].contains_rotatable_block = true
|
||
break
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
local build_room = function(param)
|
||
local pos = param.pos
|
||
local psize = param.size
|
||
local posses_border = {}
|
||
local posses_window = {}
|
||
local posses_air = {}
|
||
local size = vector.add(psize, {x=1,y=1,z=1})
|
||
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 (x >= 1 and x < size.x) and
|
||
(y >= 1 and y < size.y) and
|
||
(z >= 1 and z < size.z) then
|
||
table.insert(posses_air, vector.add(pos, offset))
|
||
elseif 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
|
||
table.insert(posses_border, vector.add(pos, offset))
|
||
end
|
||
end
|
||
end
|
||
end
|
||
minetest.bulk_set_node(posses_border, {name=ROOM_NODE})
|
||
minetest.bulk_set_node(posses_window, {name=WINDOW_NODE})
|
||
minetest.bulk_set_node(posses_air, {name="air"})
|
||
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
|
||
build_room(param)
|
||
local level_ok = lzr_levels.build_level(param.level)
|
||
if not level_ok then
|
||
minetest.log("error", "[lzr_levels] Room emerge callback done with error")
|
||
else
|
||
minetest.log("action", "[lzr_levels] Room emerge callback done")
|
||
end
|
||
end
|
||
end
|
||
|
||
local prepare_room = function(pos, size, level)
|
||
minetest.emerge_area(pos, vector.add(pos, size), emerge_callback, {pos=pos, size=size, level=level})
|
||
end
|
||
|
||
function lzr_levels.build_room(pos, size, level)
|
||
prepare_room(pos, size, level)
|
||
end
|
||
|
||
function lzr_levels.prepare_and_build_level(level)
|
||
lzr_levels.build_room(lzr_globals.LEVEL_POS, {x=10, y=6, z=10}, level)
|
||
end
|
||
|
||
function lzr_levels.build_level(level)
|
||
local filepath = minetest.get_modpath("lzr_levels").."/schematics/"..level_data[level].filename
|
||
local schem = minetest.place_schematic(lzr_globals.LEVEL_POS, filepath, "0", {}, true, "")
|
||
if schem then
|
||
-- Propagate lasers and check for insta-win
|
||
lzr_laser.full_laser_update(lzr_globals.PLAYFIELD_START, lzr_globals.PLAYFIELD_END)
|
||
local done = lzr_laser.check_level_won()
|
||
if done then
|
||
lzr_levels.level_complete()
|
||
end
|
||
else
|
||
minetest.log("error", "[lzr_levels] lzr_levels.build_level failed to build level")
|
||
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 get_singleplayer = function()
|
||
return minetest.get_player_by_name("singleplayer")
|
||
end
|
||
|
||
function lzr_levels.start_level(level)
|
||
current_level = level
|
||
local player = get_singleplayer()
|
||
local start_pos = vector.add(lzr_globals.LEVEL_POS, {x=4,y=-0.5,z=4})
|
||
player:set_pos(start_pos)
|
||
player:set_look_horizontal(0)
|
||
lzr_levels.prepare_and_build_level(level)
|
||
local needs_rotate = level_data[current_level].contains_rotatable_block
|
||
reset_inventory(player, needs_rotate)
|
||
lzr_messages.show_message(player, S("Level @1", level), 3)
|
||
if lzr_gamestate.get_state() ~= lzr_gamestate.EDITOR then
|
||
lzr_gamestate.set_state(lzr_gamestate.LEVEL)
|
||
end
|
||
minetest.sound_play({name = "lzr_levels_level_enter", gain = 1}, {to_player=player:get_player_name()}, true)
|
||
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)
|
||
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_COMPLETE then
|
||
return false
|
||
end
|
||
lzr_levels.mark_level_as_complete(current_level)
|
||
local player = get_singleplayer()
|
||
lzr_messages.show_message(player, S("Level @1 complete!", current_level), 3)
|
||
minetest.log("action", "[lzr_levels] Level "..current_level.." completed")
|
||
minetest.sound_play({name = "lzr_levels_level_complete", gain = 1}, {to_player=player:get_player_name()}, true)
|
||
lzr_gamestate.set_state(lzr_gamestate.LEVEL_COMPLETE)
|
||
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()
|
||
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!"), 5)
|
||
else
|
||
lzr_levels.start_level(current_level)
|
||
end
|
||
end
|
||
|
||
function lzr_levels.leave_level()
|
||
local player = get_singleplayer()
|
||
current_level = nil
|
||
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)
|
||
lzr_gamestate.set_state(lzr_gamestate.MENU)
|
||
end
|
||
|
||
minetest.register_chatcommand("level", {
|
||
privs = { server = true },
|
||
description = S("Go to level"),
|
||
params = S("<level>"),
|
||
func = function(name, param)
|
||
local level = tonumber(param)
|
||
if not level then
|
||
return false
|
||
end
|
||
if level < 1 or level > lzr_levels.LAST_LEVEL then
|
||
return false, S("Invalid level!")
|
||
end
|
||
lzr_levels.start_level(level)
|
||
return true
|
||
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 or state == lzr_gamestate.EDITOR then
|
||
lzr_levels.start_level(current_level)
|
||
return true
|
||
elseif state == lzr_gamestate.LEVEL_COMPLETE then
|
||
return false, S("Can’t 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)
|
||
if lzr_gamestate.get_state() == lzr_gamestate.LEVEL or lzr_gamestate.get_state() == lzr_gamestate.LEVEL_COMPLETE 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)
|
||
lzr_laser.full_laser_update(lzr_globals.PLAYFIELD_START, lzr_globals.PLAYFIELD_END)
|
||
end
|
||
end)
|
||
|
||
|
||
analyze_levels()
|