475 lines
18 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_level_select")
local F = minetest.formspec_escape
local FS = function(...) return minetest.formspec_escape(S(...)) end
-- Directory separator
local DIR_DELIM = "/"
lzr_level_select = {}
local current_core_level_selection = nil
local current_custom_level_selection = nil
local current_custom_level_pack_selection = "__singleton"
local current_stats_level_pack_selection = "__core"
local custom_levels
local custom_level_packs = {}
local message_form = function(player, message)
return "formspec_version[7]size[8,3.5]"..
"textarea[0.5,0.5;7,1.8;;;"..F(message).."]"..
"button_exit[3,2.5;2,0.6;okay;"..FS("OK").."]"
end
local update_custom_level_packs = function(skip_core, level_pack_selection)
local packnames = lzr_levels.get_level_pack_names()
local mypacks = {}
custom_level_packs = {}
table.insert(custom_level_packs, "__singleton")
table.insert(mypacks, FS("Single levels"))
local dropdown_idx
local pi = 1 -- counted pack index
for p=1, #packnames do
local packname = packnames[p]
if (not skip_core) or packname ~= "__core" then
pi = pi + 1
if packname == level_pack_selection then
dropdown_idx = pi
end
table.insert(custom_level_packs, packname)
local pack = lzr_levels.get_level_pack(packname)
local title = pack.title or packname
table.insert(mypacks, minetest.formspec_escape(title))
end
end
if level_pack_selection == "__singleton" then
dropdown_idx = 1
end
if not dropdown_idx then
dropdown_idx = 1
end
return mypacks, dropdown_idx
end
lzr_level_select.open_dialog = function(player, level_set)
local caption
if level_set == "core" then
caption = S("Select level:")
elseif level_set == "custom" then
caption = S("Select level pack:")
else
minetest.log("error", "[lzr_level_select] open_dialog called with unknown level_set!")
return false
end
local form = "formspec_version[7]size[6,10]"..
"label[0.5,0.4;"..F(caption).."]"..
"button_exit[1.5,8.5;3,1;okay;"..FS("Start").."]"
local mypacks
local form_levellist
if level_set == "custom" then
local mypacks, dropdown_idx = update_custom_level_packs(true, current_custom_level_pack_selection)
local mypacks_str = table.concat(mypacks, ",")
form = form ..
"dropdown[0.5,0.7;5,0.6;levelpack;"..mypacks_str..";"..dropdown_idx..";true]"
form_levellist =
"label[0.5,1.65;"..FS("Select level:").."]"..
"tablecolumns[color;text]"..
"table[0.5,2;5,5.9;levellist;"
else
form_levellist = "tablecolumns[color;text]"..
"table[0.5,0.8;5,7.5;levellist;"
end
local list = {}
local entry_header = ""
local first_uncompleted_level = nil
if level_set == "core" then
-- Built-in level pack
local level_data = lzr_levels.get_level_pack("__core")
local completed_levels = lzr_levels.get_completed_levels(level_data)
for l=1, #level_data do
local filename = level_data[l].filename
local level_id = string.sub(filename, 1, -5)
if completed_levels[level_id] then
entry_header = "#00FF00"
else
if not first_uncompleted_level then
first_uncompleted_level = l
end
entry_header = ""
end
table.insert(list, entry_header..","..F(lzr_levels.get_level_name(l, level_data, true)))
end
if not first_uncompleted_level then
first_uncompleted_level = 1
end
current_core_level_selection = first_uncompleted_level
else
if current_custom_level_pack_selection == "__singleton" then
-- Pseudo-levelpack containing single unsorted levels (editor levels)
local list_levels = lzr_editor.get_custom_levels()
custom_levels = {}
for l=1, #list_levels do
local levelname = list_levels[l]
-- Hide the autosave level because it's not meant
-- for playing
if levelname ~= lzr_globals.AUTOSAVE_NAME then
table.insert(custom_levels, levelname)
local proper_name = lzr_editor.get_custom_level_name(levelname, true)
table.insert(list, entry_header..","..F(proper_name))
end
end
first_uncompleted_level = 1
current_custom_level_selection = 1
else
-- Custom level pack
local level_data = lzr_levels.get_level_pack(current_custom_level_pack_selection)
local completed_levels = lzr_levels.get_completed_levels(level_data)
for l=1, #level_data do
local filename = level_data[l].filename
local level_id = string.sub(filename, 1, -5)
if completed_levels[level_id] then
entry_header = "#00FF00"
else
if not first_uncompleted_level then
first_uncompleted_level = l
end
entry_header = ""
end
table.insert(list, entry_header..","..F(lzr_levels.get_level_name(l, level_data, true)))
end
if not first_uncompleted_level then
first_uncompleted_level = 1
end
current_custom_level_selection = first_uncompleted_level
end
end
if not first_uncompleted_level then
first_uncompleted_level = 1
end
local list_str = table.concat(list, ",")
form_levellist = form_levellist .. list_str .. ";"..first_uncompleted_level.."]"
local formname
if level_set == "core" then
if #list == 0 then
form = message_form(player, S("There are no levels."))
else
form = form .. form_levellist
end
formname = "lzr_level_select:levellist"
else
if #list == 0 then
local level_packs = lzr_levels.get_level_pack_names()
if #level_packs <= 1 then
form = message_form(player,
S("There are no level packs or custom single levels.").."\n"..
S("Install a level pack or use the level editor to build your own levels."))
elseif current_custom_level_pack_selection == "__singleton" then
form = form .. "textarea[0.5,1.7;5,5;;;"..
FS("There are no custom single levels.").."\n"..
FS("Install one from your friends or use the level editor to build your own levels.").."]n"
else
form = form .. "textarea[0.5,1.7;5,5;;;"..
FS("This level pack is empty.").."]"
end
else
form = form .. form_levellist
end
formname = "lzr_level_select:levellist_custom"
end
minetest.show_formspec(player:get_player_name(), formname, form)
end
local load_custom_level = function(level, player)
minetest.log("action", "[lzr_level_select] Player selects custom level: "..level)
local csv = minetest.get_worldpath().."/levels/"..level..".csv"
local levels_path = minetest.get_worldpath().."/levels"
local pname = player:get_player_name()
local custom_level_data, error_type, error_msg, error_detail = lzr_levels.analyze_levels(csv, levels_path)
if custom_level_data then
custom_level_data.is_singleton = true
-- Load level with metadata (preferred)
lzr_levels.start_level(1, custom_level_data)
elseif error_type == "load_error" then
-- Fallback when CSV file could not be read
custom_level_data = lzr_levels.create_fallback_level_data(level, levels_path)
if not custom_level_data then
local form = message_form(player, S("Level could not be loaded. No level CSV file was found, and the fallback mechanism failed as well."))
minetest.show_formspec(pname, "lzr_level_select:custom_load_error", form)
minetest.log("action", "[lzr_level_select] Player tried to load broken custom level: "..tostring(level)..". Missing CSV file, and the fallback, failed as well.")
else
custom_level_data.is_singleton = true
lzr_levels.start_level(1, custom_level_data)
minetest.log("action", "[lzr_level_select] No CSV file found for custom level: "..tostring(level)..". Using fallback.")
end
elseif error_type == "csv_error" then
local form = message_form(player, S("The level could not be loaded. Invalid syntax of level CSV file."))
minetest.show_formspec(pname, "lzr_level_select:custom_load_error", form)
minetest.log("action", "[lzr_level_select] Player tried to load broken custom level "..tostring(level)..". Invalid CSV syntax.")
elseif error_type == "bad_schematic" then
local error_append = ""
if error_detail then
local reason
if error_detail == "file_nonexistant" then
reason = S("The level schematic file chould not be found.")
elseif error_detail == "no_teleporter" then
reason = S("Theres no teleporter for the player to start on.")
elseif error_detail == "too_many_teleporters" then
reason = S("Theres more than one teleporter.")
elseif error_detail == "too_many_parrot_spawners" then
reason = S("Theres more than one Goldie the Parrot.")
elseif error_detail == "barriers" then
reason = S("Theres a barrier or barrier-like node in the level.")
elseif error_detail == "gold_block" then
reason = S("Theres a bare gold block in the level.")
elseif error_detail == "plant_on_ground" then
reason = S("Theres a rooted plant in the level.")
elseif error_detail == "schematic_load_error" then
reason = S("Error while loading schematic file.")
else
reason = error_detail
end
--~ Reason shown on level loading error
error_append = "\n\n" .. S("Reason: @1", reason)
end
local form = message_form(player, S("This level is unplayable.")..error_append)
minetest.show_formspec(pname, "lzr_level_select:custom_load_error", form)
minetest.log("action", "[lzr_level_select] Player tried to load broken custom level "..tostring(level)..". Bad schematic.")
else
local form = message_form(player, S("The level could not be loaded due to an unknown error."))
minetest.show_formspec(pname, "lzr_level_select:custom_load_error", form)
minetest.log("error", "[lzr_level_select] Unknown error while loading custom level: "..tostring(level)..". Aborting.")
end
end
lzr_level_select.open_stats_dialog = function(player)
local level_packs_names = lzr_levels.get_level_pack_names()
local mypacks, dropdown_idx = update_custom_level_packs(false, current_stats_level_pack_selection)
local mypacks_str = table.concat(mypacks, ",")
local form = "formspec_version[7]size[6,10]"..
"box[0,0;6,0.8;#00000080]"..
"label[0.5,0.4;"..FS("Level pack stats").."]"..
"label[0.5,1.3;"..FS("Select level pack:").."]"..
"dropdown[0.5,1.5;5,0.6;levelpack;"..mypacks_str..";"..dropdown_idx..";true]"..
"button_exit[1.5,8.5;3,1;okay;"..FS("Okay").."]"..
"box[0.5,2.3;5,5.9;#00000020]"..
"hypertext[0.5,2.3;5,5.9;info;"
local info
local color = "#00ff00"
local H = minetest.hypertext_escape
if current_stats_level_pack_selection == "__singleton" then
local count = #lzr_editor.get_custom_levels()
local path = "<WORLD PATH>"..DIR_DELIM.."levels"
info =
"<bigger>"..H(S("Single levels")).."</bigger>\n"..
"<big>"..H(S("Description")).."</big>\n"..
H(S("Single, unsorted levels that dont belong to any level pack.")).."\n"..
"<big>"..H(S("Stats")).."</big>\n"..
--~ @1 = number of levels
H(S("• Levels: @1", count)).."\n\n"..
"<big>"..H(S("File location")).."</big>\n"..
H(S("Single levels are stored in:")).."\n"..
"<mono>"..H(path).."</mono>"
else
local level_data = lzr_levels.get_level_pack(current_stats_level_pack_selection)
local treasures_total = lzr_levels.count_total_treasures(level_data)
local treasures_found = lzr_levels.count_total_collected_treasures(level_data)
local treasures_percent = string.format("%d", (treasures_found / treasures_total) * 100)
local levels_total = #level_data
local levels_completed = lzr_levels.count_completed_levels(level_data)
local levels_percent = string.format("%d", (levels_completed / levels_total) * 100)
local title = level_data.title or level_data.name
local pack_description = level_data.description or S("(no description provided)")
info =
"<bigger>"..H(title).."</bigger>\n"..
"<big>"..H(S("Description")).."</big>\n"..
H(pack_description).."\n"..
"<big>"..H(S("Stats")).."</big>\n"..
--~ @1 = total number of levels, @2 = number of completed levels, @3 percentage of level completion
H(S("• Levels: @1 (@2 completed, @3%)", levels_total, levels_completed, levels_percent)).."\n"..
--~ @1 = total number of gold blocks, @2 = number of found gold blocks, @3 percentage of found gold blocks
H(S("• Gold blocks: @1 (@2 found, @3%)", treasures_total, treasures_found, treasures_percent)).."\n\n"..
"<big>"..H(S("Mod")).."</big>\n"..
"<mono>"..H(level_data.mod_origin).."</mono>"
local privs = minetest.get_player_privs(player:get_player_name())
if minetest.settings:get_bool("lzr_debug", true) or privs.server or privs.debug then
info = info .."\n"..
--~ For the technical level pack identifier
"<big>"..H(S("Level pack ID")).."</big>\n"..
"<mono>"..H(level_data.name).."</mono>"
end
end
form = form .. F(info) .. "]"
minetest.show_formspec(player:get_player_name(), "lzr_level_select:stats", form)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname == "lzr_level_select:levellist" then
if lzr_gamestate.get_state() ~= lzr_gamestate.MENU then
return
end
local level_data = lzr_levels.get_level_pack("__core")
if fields.okay then
if current_core_level_selection then
lzr_levels.start_level(current_core_level_selection, level_data)
end
elseif fields.levellist then
local expl = minetest.explode_table_event(fields.levellist)
if expl.type == "CHG" then
current_core_level_selection = expl.row
elseif expl.type == "DCL" then
current_core_level_selection = expl.row
lzr_levels.start_level(current_core_level_selection, level_data)
minetest.close_formspec(player:get_player_name(), "lzr_level_select:levellist")
elseif expl.type == "INV" then
current_core_level_selection = nil
end
end
elseif formname == "lzr_level_select:levellist_custom" then
if lzr_gamestate.get_state() ~= lzr_gamestate.MENU then
return
end
if fields.okay then
if custom_levels and current_custom_level_selection then
if current_custom_level_pack_selection == "__singleton" then
local level = custom_levels[current_custom_level_selection]
if level then
load_custom_level(level, player)
end
else
local level_data = lzr_levels.get_level_pack(current_custom_level_pack_selection)
lzr_levels.start_level(current_custom_level_selection, level_data)
end
end
elseif fields.levellist then
local expl = minetest.explode_table_event(fields.levellist)
if expl.type == "CHG" then
current_custom_level_selection = expl.row
elseif expl.type == "DCL" then
current_custom_level_selection = expl.row
if current_custom_level_pack_selection == "__singleton" then
if custom_levels then
local level = custom_levels[current_custom_level_selection]
if level then
load_custom_level(level, player)
end
end
else
local level_data = lzr_levels.get_level_pack(current_custom_level_pack_selection)
lzr_levels.start_level(current_custom_level_selection, level_data)
end
minetest.close_formspec(player:get_player_name(), "lzr_level_select:levellist_custom")
elseif expl.type == "INV" then
current_custom_level_selection = nil
end
elseif fields.levelpack then
local idx = tonumber(fields.levelpack)
current_custom_level_pack_selection = custom_level_packs[idx]
lzr_level_select.open_dialog(player, "custom")
end
elseif formname == "lzr_level_select:stats" then
if fields.levelpack then
local idx = tonumber(fields.levelpack)
current_stats_level_pack_selection = custom_level_packs[idx]
if not fields.quit then
lzr_level_select.open_stats_dialog(player)
end
end
end
end)
minetest.register_chatcommand("level", {
privs = { server = true },
description = S("Go to level"),
params = S("[<level pack>] <level number>"),
func = function(name, param)
if lzr_gamestate.get_state() == lzr_gamestate.LEVEL_TEST then
return false, S("Not possible during the level solution test!")
end
if lzr_gamestate.is_loading() then
return false, S("Cant start a level while loading!")
end
-- Parse arguments. level argument may be relative (tilde notation)
local pack, level = string.match(param, "([a-zA-Z0-9_]+) (~?-?[0-9]*)")
local current_data = lzr_levels.get_current_level_data()
local current_pack
if current_data then
current_pack = current_data.name
end
-- Select level pack. Defaults to the current level pack
-- if playing one or __core if playing no level pack
-- (singleton levels don't count)
if not pack then
if current_pack then
pack = current_pack
else
pack = "__core"
end
level = string.match(param, "(~?-?[0-9]*)")
end
if not level then
return false
end
-- Handle relative level argument (e.g. "~1" to move to the next level)
local is_relative_level = string.sub(level, 1, 1) == "~"
if is_relative_level then
local current_level = lzr_levels.get_current_level()
-- Relative level argument is only allowed if playing in a level
-- and the chosen level pack is the same as the current one.
if not current_level then
return false, S("Level argument cant be relative when not playing in a level.")
end
if current_pack ~= pack then
return false, S("Level argument cant be relative when choosing a different level pack.")
end
level = minetest.parse_relative_number(level, current_level)
else
level = tonumber(level)
end
if not level then
return false
end
local level_data = lzr_levels.get_level_pack(pack)
if not level_data then
return false, S("Level pack “@1” doesnt exist.", pack)
end
if level < 1 or level > #level_data then
--~ @1, @2, and @3 are level numbers
return false, S("Level @1 does not exist in this level pack. Available levels range from @2 to @3.", level, 1, #level_data)
end
lzr_levels.start_level(level, level_data)
return true
end,
})
minetest.register_chatcommand("level_pack_stats", {
description = S("Display stats about the installed level packs"),
params = "",
func = function(name, param)
local player = minetest.get_player_by_name(name)
if not player then
return false, S("No player.")
end
lzr_level_select.open_stats_dialog(player)
return true
end,
})