295 lines
9.1 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 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("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)
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()