901 lines
30 KiB
Groff
901 lines
30 KiB
Groff
-- ========================================================================
|
|
-- TPAD MOD v1.2 (Final, Reworked Delete Logic)
|
|
-- ========================================================================
|
|
|
|
tpad = {}
|
|
tpad.version = "1.2" -- As requested, version is not incremented
|
|
tpad.mod_name = minetest.get_current_modname()
|
|
tpad.texture = "tpad-texture.png"
|
|
tpad.mesh = "tpad-mesh.obj"
|
|
tpad.nodename = "tpad:tpad"
|
|
tpad.mod_path = minetest.get_modpath(tpad.mod_name)
|
|
tpad.sound_teleport = tpad.mod_name .. "_teleport"
|
|
tpad.particle_texture = tpad.mod_name .. "_particle.png"
|
|
|
|
-- Temporäre Variable zur sicheren Datenübergabe an den Bestätigungsdialog
|
|
tpad.pending_deletion = {}
|
|
|
|
-- ========================================================================
|
|
-- Constants
|
|
-- ========================================================================
|
|
|
|
local PRIVATE_PAD_STRING = "Privat (nur Besitzer)"
|
|
local PUBLIC_PAD_STRING = "Lokal (nur eigenes Netzwerk)"
|
|
local GLOBAL_PAD_STRING = "Global (beliebiges Netzwerk)"
|
|
|
|
local PRIVATE_PAD = 1
|
|
local PUBLIC_PAD = 2
|
|
local GLOBAL_PAD = 4
|
|
|
|
local RED_ESCAPE = minetest.get_color_escape_sequence("#FF0000")
|
|
local YELLOW_ESCAPE = minetest.get_color_escape_sequence("#FFFF00")
|
|
local CYAN_ESCAPE = minetest.get_color_escape_sequence("#00FFFF")
|
|
local WHITE_ESCAPE = minetest.get_color_escape_sequence("#FFFFFF")
|
|
local OWNER_ESCAPE_COLOR = CYAN_ESCAPE
|
|
|
|
local padtype_flag_to_string = {
|
|
[PRIVATE_PAD] = PRIVATE_PAD_STRING,
|
|
[PUBLIC_PAD] = PUBLIC_PAD_STRING,
|
|
[GLOBAL_PAD] = GLOBAL_PAD_STRING,
|
|
}
|
|
local padtype_string_to_flag = {
|
|
[PRIVATE_PAD_STRING] = PRIVATE_PAD,
|
|
[PUBLIC_PAD_STRING] = PUBLIC_PAD,
|
|
[GLOBAL_PAD_STRING] = GLOBAL_PAD,
|
|
}
|
|
local short_padtype_string = {
|
|
[PRIVATE_PAD] = "private",
|
|
[PUBLIC_PAD] = "public",
|
|
[GLOBAL_PAD] = "global",
|
|
}
|
|
|
|
-- ========================================================================
|
|
-- Dependencies and Libs
|
|
-- ========================================================================
|
|
|
|
local smartfs = dofile(tpad.mod_path .. "/lib/smartfs.lua")
|
|
local notify = dofile(tpad.mod_path .. "/notify.lua")
|
|
|
|
local waypoint_hud_ids = {}
|
|
|
|
minetest.register_privilege("tpad_admin", {
|
|
description = "Can edit and destroy any tpad",
|
|
give_to_singleplayer = true,
|
|
})
|
|
|
|
-- ========================================================================
|
|
-- Original Helper Functions
|
|
-- ========================================================================
|
|
|
|
local function copy_file(source, dest)
|
|
local src_file = io.open(source, "rb")
|
|
if not src_file then return false, "copy_file() unable to open source for reading" end
|
|
local src_data = src_file:read("*all")
|
|
src_file:close()
|
|
local dest_file = io.open(dest, "wb")
|
|
if not dest_file then return false, "copy_file() unable to open dest for writing" end
|
|
dest_file:write(src_data)
|
|
dest_file:close()
|
|
return true, "files copied successfully"
|
|
end
|
|
|
|
local function custom_or_default(modname, path, filename)
|
|
local default_filename = "default/" .. filename
|
|
local full_filename = path .. "/custom." .. filename
|
|
local full_default_filename = path .. "/" .. default_filename
|
|
local file_exists_at_path = io.open(path .. "/" .. filename, "r")
|
|
if file_exists_at_path then
|
|
file_exists_at_path:close()
|
|
os.rename(path .. "/" .. filename, full_filename)
|
|
end
|
|
local file = io.open(full_filename, "rb")
|
|
if not file then
|
|
minetest.debug("[" .. modname .. "] Copying " .. default_filename .. " to " .. filename .. " (path: " .. path .. ")")
|
|
local success, err = copy_file(full_default_filename, full_filename)
|
|
if not success then
|
|
minetest.debug("[" .. modname .. "] " .. err)
|
|
return false
|
|
end
|
|
file = io.open(full_filename, "rb")
|
|
if not file then
|
|
minetest.debug("[" .. modname .. "] Unable to load " .. filename .. " file from path " .. path)
|
|
return false
|
|
end
|
|
end
|
|
file:close()
|
|
return full_filename
|
|
end
|
|
|
|
dofile(tpad.mod_path .. "/storage.lua")
|
|
|
|
-- ========================================================================
|
|
-- Load Custom Recipe
|
|
-- ========================================================================
|
|
|
|
local recipes_filename = custom_or_default(tpad.mod_name, tpad.mod_path, "recipes.lua")
|
|
if recipes_filename then
|
|
local recipes = dofile(recipes_filename)
|
|
if type(recipes) == "table" and recipes[tpad.nodename] then
|
|
minetest.register_craft({
|
|
output = tpad.nodename,
|
|
recipe = recipes[tpad.nodename],
|
|
})
|
|
end
|
|
end
|
|
|
|
-- ========================================================================
|
|
-- Chat Command
|
|
-- ========================================================================
|
|
|
|
function tpad.command(playername, param)
|
|
tpad.hud_off(playername)
|
|
if(param == "off") then return end
|
|
local player = minetest.get_player_by_name(playername)
|
|
local pads = tpad._get_stored_pads(playername)
|
|
local shortest_distance = nil
|
|
local closest_pad = nil
|
|
local playerpos = player:getpos()
|
|
for strpos, pad in pairs(pads) do
|
|
local pos = minetest.string_to_pos(strpos)
|
|
local distance = vector.distance(pos, playerpos)
|
|
if not shortest_distance or distance < shortest_distance then
|
|
closest_pad = {
|
|
pos = pos,
|
|
name = pad.name .. " " .. strpos,
|
|
}
|
|
shortest_distance = distance
|
|
end
|
|
end
|
|
if closest_pad then
|
|
waypoint_hud_ids[playername] = player:hud_add({
|
|
hud_elem_type = "waypoint",
|
|
name = closest_pad.name,
|
|
world_pos = closest_pad.pos,
|
|
number = 0xFF0000,
|
|
})
|
|
notify(playername, "Waypoint to " .. closest_pad.name .. " displayed")
|
|
end
|
|
end
|
|
|
|
function tpad.hud_off(playername)
|
|
local player = minetest.get_player_by_name(playername)
|
|
local hud_id = waypoint_hud_ids[playername]
|
|
if hud_id then
|
|
player:hud_remove(hud_id)
|
|
end
|
|
end
|
|
|
|
-- ========================================================================
|
|
-- Teleport Logic
|
|
-- ========================================================================
|
|
|
|
function tpad.do_teleport(player, destination_pos, destination_name, form_context)
|
|
local playername = player:get_player_name()
|
|
|
|
minetest.after(0.1, function()
|
|
if not player or not player:is_player() then return end
|
|
local start_pos = player:getpos()
|
|
minetest.sound_play(tpad.sound_teleport, {pos = start_pos, max_hear_distance = 10, gain = 1.0})
|
|
minetest.add_particlespawner({
|
|
amount = 60, time = 0.5,
|
|
minpos = vector.add(start_pos, -0.5), maxpos = vector.add(start_pos, 0.5),
|
|
minvel = {x=-1, y=0, z=-1}, maxvel = {x=1, y=2, z=1},
|
|
minacc = {x=0, y=0, z=0}, maxacc = {x=0, y=0, z=0},
|
|
minexptime = 0.5, maxexptime = 2,
|
|
minsize = 1, maxsize = 3,
|
|
texture = tpad.particle_texture,
|
|
})
|
|
|
|
player:move_to(destination_pos)
|
|
|
|
minetest.sound_play(tpad.sound_teleport, {pos = destination_pos, max_hear_distance = 10, gain = 1.0})
|
|
minetest.add_particlespawner({
|
|
amount = 60, time = 0.5,
|
|
minpos = vector.add(destination_pos, {x = -0.5, y = 0, z = -0.5}),
|
|
maxpos = vector.add(destination_pos, {x = 0.5, y = 1, z = 0.5}),
|
|
minvel = {x=-1, y=1, z=-1}, maxvel = {x=1, y=2, z=1},
|
|
minacc = {x=0, y=0, z=0}, maxacc = {x=0, y=0, z=0},
|
|
minexptime = 0.5, maxexptime = 2,
|
|
minsize = 1, maxsize = 3,
|
|
texture = tpad.particle_texture,
|
|
})
|
|
tpad.hud_off(playername)
|
|
|
|
-- NEU: Nach Ankunft am Ziel die Ansicht mit neuem Kontext aktualisieren
|
|
minetest.after(0.2, function()
|
|
if not player or not player:is_player() then return end
|
|
|
|
local dest_pad_data = tpad.get_pad_data(destination_pos)
|
|
if not dest_pad_data then return end
|
|
|
|
local new_context = {
|
|
playername = playername,
|
|
clicker = player,
|
|
ownername = dest_pad_data.owner,
|
|
clicked_pos = destination_pos,
|
|
node = minetest.get_node(destination_pos),
|
|
page = 1,
|
|
-- Behalte den Netzwerk-Typ (lokal/global) bei
|
|
network_type = form_context.network_type
|
|
}
|
|
tpad.show_network_view(new_context, new_context.network_type)
|
|
end)
|
|
end)
|
|
end
|
|
|
|
-- ========================================================================
|
|
-- Node Placement and Data Helpers
|
|
-- ========================================================================
|
|
|
|
function tpad.after_place_node(pos, placer, itemstack)
|
|
local playername = placer:get_player_name()
|
|
if tpad.max_total_pads_reached(placer) then
|
|
notify.warn(playername, "Du kannst keine weiteren TPADs erstellen. Limit erreicht.")
|
|
minetest.remove_node(pos)
|
|
minetest.add_item(placer:get_pos(), itemstack:get_name())
|
|
return
|
|
end
|
|
|
|
local meta = minetest.get_meta(pos)
|
|
meta:set_string("owner", playername)
|
|
meta:set_string("infotext", "TPAD Station von " .. playername)
|
|
tpad.set_pad_data(pos, "", PRIVATE_PAD_STRING)
|
|
end
|
|
|
|
local submit = {}
|
|
|
|
function tpad.max_total_pads_reached(placer)
|
|
local placername = placer:get_player_name()
|
|
if minetest.get_player_privs(placername).tpad_admin then
|
|
return false
|
|
end
|
|
local pads = tpad._get_stored_pads(placername)
|
|
local count = 0
|
|
for _ in pairs(pads) do
|
|
count = count + 1
|
|
end
|
|
return count >= tpad.get_max_total_pads()
|
|
end
|
|
|
|
function tpad.max_global_pads_reached(playername)
|
|
if minetest.get_player_privs(playername).tpad_admin then
|
|
return false
|
|
end
|
|
local pads = tpad._get_stored_pads(playername)
|
|
local count = 0
|
|
for _, pad in pairs(pads) do
|
|
if pad.type == GLOBAL_PAD then
|
|
count = count + 1
|
|
end
|
|
end
|
|
return count >= tpad.get_max_global_pads()
|
|
end
|
|
|
|
-- ========================================================================
|
|
-- GUI DATA HELPERS
|
|
-- ========================================================================
|
|
|
|
function submit.global_helper()
|
|
local allpads = tpad._get_all_pads()
|
|
local result = {}
|
|
for ownername, pads in pairs(allpads) do
|
|
for strpos, pad in pairs(pads) do
|
|
if pad.type == GLOBAL_PAD then
|
|
table.insert(result, tpad.decorate_pad_data(strpos, pad, ownername))
|
|
end
|
|
end
|
|
end
|
|
table.sort(result, function(a, b) return a.global_fullname:lower() < b.global_fullname:lower() end)
|
|
return result
|
|
end
|
|
|
|
function submit.local_helper(ownername, viewername)
|
|
local result = {}
|
|
local added_pads = {} -- Verhindert, dass Pads doppelt hinzugefügt werden
|
|
|
|
-- Schritt 1: Füge die öffentlichen Pads des Besitzers des angeklickten TPADs hinzu
|
|
local owner_pads = tpad._get_stored_pads(ownername)
|
|
for strpos, pad in pairs(owner_pads) do
|
|
-- Vom Besitzer werden nur die öffentlichen (public) Pads angezeigt
|
|
if pad.type == PUBLIC_PAD then
|
|
table.insert(result, tpad.decorate_pad_data(strpos, pad, ownername))
|
|
added_pads[strpos] = true
|
|
end
|
|
end
|
|
|
|
-- Schritt 2: Füge die eigenen Pads hinzu, falls der Betrachter eine andere Person ist
|
|
if ownername ~= viewername then
|
|
local viewer_pads = tpad._get_stored_pads(viewername)
|
|
for strpos, pad in pairs(viewer_pads) do
|
|
-- Vom Betrachter werden seine privaten und öffentlichen Pads angezeigt
|
|
if (pad.type == PUBLIC_PAD or pad.type == PRIVATE_PAD) and not added_pads[strpos] then
|
|
table.insert(result, tpad.decorate_pad_data(strpos, pad, viewername))
|
|
added_pads[strpos] = true
|
|
end
|
|
end
|
|
end
|
|
|
|
table.sort(result, function(a, b) return a.local_fullname:lower() < b.local_fullname:lower() end)
|
|
return result
|
|
end
|
|
|
|
function submit.management_helper(playername)
|
|
local is_admin = minetest.get_player_privs(playername).tpad_admin
|
|
local result = {}
|
|
|
|
if is_admin then
|
|
local allpads = tpad._get_all_pads()
|
|
for ownername, pads in pairs(allpads) do
|
|
for strpos, pad in pairs(pads) do
|
|
table.insert(result, tpad.decorate_pad_data(strpos, pad, ownername))
|
|
end
|
|
end
|
|
else
|
|
local pads = tpad._get_stored_pads(playername)
|
|
for strpos, pad in pairs(pads) do
|
|
table.insert(result, tpad.decorate_pad_data(strpos, pad, playername))
|
|
end
|
|
end
|
|
|
|
table.sort(result, function(a, b) return a.local_fullname:lower() < b.local_fullname:lower() end)
|
|
return result
|
|
end
|
|
|
|
-- ========================================================================
|
|
-- GUI LOGIC (REFACTORED)
|
|
-- ========================================================================
|
|
|
|
function tpad.show_network_view(form, network_type)
|
|
local is_admin = minetest.get_player_privs(form.playername).tpad_admin
|
|
local form_key = is_admin and "network_view_admin" or "network_view"
|
|
form.state = tpad.forms[form_key]:show(form.playername)
|
|
form.formname = "tpad.forms." .. form_key
|
|
|
|
local pad_list
|
|
local title_text
|
|
|
|
local function create_clean_context()
|
|
return {
|
|
playername = form.playername,
|
|
clicker = form.clicker,
|
|
ownername = form.ownername,
|
|
clicked_pos = form.clicked_pos,
|
|
node = form.node,
|
|
page = form.page or 1
|
|
}
|
|
end
|
|
|
|
local current_pad_data = tpad.get_pad_data(form.clicked_pos)
|
|
local current_pad_name = current_pad_data.name or "Unnamed"
|
|
|
|
if network_type == "global" then
|
|
form.network_type = "global"
|
|
pad_list = submit.global_helper()
|
|
title_text = RED_ESCAPE .. "GLOBALE " .. WHITE_ESCAPE .. "TPAD-Station " .. YELLOW_ESCAPE .. current_pad_name .. WHITE_ESCAPE .. ". Wähle ein Ziel:"
|
|
form.state:get("toggle_network_button"):setText("Lokales Netzwerk")
|
|
form.state:get("toggle_network_button"):onClick(function()
|
|
minetest.after(0, function()
|
|
local clean_form = create_clean_context()
|
|
clean_form.ownername = clean_form.playername
|
|
clean_form.page = 1
|
|
tpad.show_network_view(clean_form, "local")
|
|
end)
|
|
end)
|
|
else -- "local"
|
|
form.network_type = "local"
|
|
pad_list = submit.local_helper(form.ownername, form.playername)
|
|
title_text = RED_ESCAPE .. "LOKALE " .. WHITE_ESCAPE .. "TPAD-Station " .. YELLOW_ESCAPE .. current_pad_name .. WHITE_ESCAPE .. ". Wähle ein Ziel:"
|
|
form.state:get("toggle_network_button"):setText("Globales Netzwerk")
|
|
form.state:get("toggle_network_button"):onClick(function()
|
|
minetest.after(0, function()
|
|
local clean_form = create_clean_context()
|
|
clean_form.page = 1
|
|
tpad.show_network_view(clean_form, "global")
|
|
end)
|
|
end)
|
|
end
|
|
|
|
form.state:get("title_label"):setText(title_text)
|
|
form.state:get("management_button"):onClick(function()
|
|
minetest.after(0, function()
|
|
local clean_form = create_clean_context()
|
|
clean_form.preselect_pos = clean_form.clicked_pos
|
|
submit.management(clean_form)
|
|
end)
|
|
end)
|
|
|
|
if is_admin then
|
|
form.state:get("admin_button"):onClick(function()
|
|
minetest.after(0, function() submit.admin_settings(form) end)
|
|
end)
|
|
end
|
|
|
|
local max_rows, num_columns = 10, 3
|
|
local buttons_per_page = max_rows * num_columns
|
|
|
|
local destination_pads = {}
|
|
for _, pad in ipairs(pad_list) do
|
|
if not vector.equals(pad.pos, form.clicked_pos) then
|
|
table.insert(destination_pads, pad)
|
|
end
|
|
end
|
|
|
|
local total_pages = math.ceil(#destination_pads / buttons_per_page)
|
|
if total_pages == 0 then total_pages = 1 end
|
|
|
|
local current_page = form.page or 1
|
|
if current_page > total_pages then current_page = total_pages end
|
|
if current_page < 1 then current_page = 1 end
|
|
form.page = current_page
|
|
|
|
for i = 1, buttons_per_page do
|
|
local index = ((current_page - 1) * buttons_per_page) + i
|
|
local pad_data = destination_pads[index]
|
|
local button_name = "tpad_btn_" .. i
|
|
local button = form.state:get(button_name)
|
|
|
|
if button then
|
|
if pad_data then
|
|
local display_name = (network_type == "global") and pad_data.global_fullname or pad_data.local_fullname
|
|
button:setText(display_name)
|
|
button:setVisible(true)
|
|
button:onClick(function()
|
|
tpad.do_teleport(form.clicker, pad_data.pos, display_name, form)
|
|
end)
|
|
else
|
|
button:setVisible(false)
|
|
end
|
|
end
|
|
end
|
|
|
|
if total_pages > 1 then
|
|
form.state:get("page_label"):setText("Seite " .. current_page .. " von " .. total_pages)
|
|
local prev_button, next_button = form.state:get("prev_button"), form.state:get("next_button")
|
|
prev_button:setVisible(current_page > 1)
|
|
next_button:setVisible(current_page < total_pages)
|
|
|
|
-- KORREKTUR: Paginierung verwendet jetzt ebenfalls einen sauberen Kontext
|
|
prev_button:onClick(function()
|
|
minetest.after(0, function()
|
|
local clean_form = create_clean_context()
|
|
clean_form.page = current_page - 1
|
|
tpad.show_network_view(clean_form, network_type)
|
|
end)
|
|
end)
|
|
next_button:onClick(function()
|
|
minetest.after(0, function()
|
|
local clean_form = create_clean_context()
|
|
clean_form.page = current_page + 1
|
|
tpad.show_network_view(clean_form, network_type)
|
|
end)
|
|
end)
|
|
else
|
|
form.state:get("page_label"):setText("")
|
|
form.state:get("prev_button"):setVisible(false)
|
|
form.state:get("next_button"):setVisible(false)
|
|
end
|
|
end
|
|
|
|
function submit.management(form)
|
|
form.formname = "tpad_management"
|
|
form.state = tpad.forms.management:show(form.playername)
|
|
local pad_list = submit.management_helper(form.playername)
|
|
local listbox = form.state:get("pads_listbox")
|
|
local is_admin = minetest.get_player_privs(form.playername).tpad_admin
|
|
local selected_pad_data = nil
|
|
|
|
listbox:clearItems()
|
|
for _, pad in ipairs(pad_list) do
|
|
local label = pad.name .. " (" .. short_padtype_string[pad.type] .. ")"
|
|
if is_admin then
|
|
label = label .. " [" .. pad.owner .. "]"
|
|
end
|
|
listbox:addItem(label)
|
|
end
|
|
|
|
local padname_field = form.state:get("padname_field")
|
|
local padtype_dropdown = form.state:get("padtype_dropdown")
|
|
|
|
-- NEU: Funktion zur Aktualisierung der Felder, um Code-Dopplung zu vermeiden
|
|
local function update_fields_for_index(index)
|
|
if not index or index <= 0 then return end
|
|
selected_pad_data = pad_list[index]
|
|
if selected_pad_data then
|
|
padname_field:setText(selected_pad_data.name)
|
|
padtype_dropdown:setSelectedItem(padtype_flag_to_string[selected_pad_data.type])
|
|
end
|
|
end
|
|
|
|
-- NEU: Prüfen, ob ein Pad vorausgewählt werden soll
|
|
if form.preselect_pos then
|
|
for i, pad in ipairs(pad_list) do
|
|
if vector.equals(pad.pos, form.preselect_pos) then
|
|
listbox:setSelected(i)
|
|
update_fields_for_index(i) -- Felder für den vorausgewählten Eintrag aktualisieren
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
listbox:onClick(function()
|
|
local index = listbox:getSelected()
|
|
update_fields_for_index(index) -- Felder bei Klick aktualisieren
|
|
end)
|
|
|
|
form.state:get("save_button"):onClick(function()
|
|
if not selected_pad_data then
|
|
notify.warn(form.playername, "Bitte wähle zuerst ein TPAD aus der Liste aus.")
|
|
return
|
|
end
|
|
|
|
local new_name = padname_field:getText()
|
|
local new_type_str
|
|
local value_from_dropdown = padtype_dropdown:getSelectedItem()
|
|
if value_from_dropdown then
|
|
local index = tonumber(value_from_dropdown)
|
|
if index then
|
|
new_type_str = padtype_dropdown:getItem(index)
|
|
else
|
|
new_type_str = value_from_dropdown
|
|
end
|
|
end
|
|
|
|
if not new_type_str or new_type_str == "" then
|
|
notify.err(form.playername, "Konnte TPAD-Typ nicht lesen. Bitte erneut versuchen.")
|
|
return
|
|
end
|
|
|
|
-- NEU: Prüfe zuerst, ob der ausführende Spieler Admin-Rechte hat.
|
|
local is_acting_admin = minetest.get_player_privs(form.playername).tpad_admin
|
|
|
|
-- Wende die Limits nur an, wenn der ausführende Spieler KEIN Admin ist.
|
|
if not is_acting_admin then
|
|
if new_type_str == GLOBAL_PAD_STRING and tpad.max_global_pads_reached(selected_pad_data.owner) then
|
|
notify.warn(form.playername, "Der Besitzer des TPADs hat das Limit für globale TPADs erreicht.")
|
|
return
|
|
end
|
|
end
|
|
|
|
tpad.set_pad_data(selected_pad_data.pos, new_name, new_type_str)
|
|
|
|
local meta = minetest.get_meta(selected_pad_data.pos)
|
|
if new_name and new_name ~= "" then
|
|
meta:set_string("infotext", "TPAD Station " .. new_name)
|
|
else
|
|
meta:set_string("infotext", "Unbenannte TPAD Station")
|
|
end
|
|
|
|
notify(form.playername, "TPAD '" .. new_name .. "' gespeichert.")
|
|
minetest.after(0, function()
|
|
form.preselect_pos = nil
|
|
submit.management(form)
|
|
end)
|
|
end)
|
|
|
|
form.state:get("delete_button"):onClick(function()
|
|
if not selected_pad_data then
|
|
notify.warn(form.playername, "Bitte wähle zuerst ein TPAD aus der Liste aus.")
|
|
return
|
|
end
|
|
|
|
if vector.equals(selected_pad_data.pos, form.clicked_pos) then
|
|
notify.warn(form.playername, "Du kannst das TPAD, an dem du stehst, nicht über dieses Menü löschen.")
|
|
return
|
|
end
|
|
|
|
tpad.pending_deletion[form.playername] = {
|
|
pad_data = selected_pad_data,
|
|
original_form_context = form,
|
|
}
|
|
|
|
minetest.close_formspec(form.playername, form.formname)
|
|
minetest.after(0.1, function()
|
|
tpad.forms.confirm_pad_deletion:show(form.playername)
|
|
end)
|
|
end)
|
|
|
|
form.state:get("back_button"):onClick(function()
|
|
minetest.after(0, function()
|
|
local clean_form_context = {
|
|
playername = form.playername,
|
|
clicker = form.clicker,
|
|
ownername = form.ownername,
|
|
clicked_pos = form.clicked_pos,
|
|
node = form.node,
|
|
page = 1
|
|
}
|
|
tpad.show_network_view(clean_form_context, form.network_type)
|
|
end)
|
|
end)
|
|
end
|
|
|
|
function submit.admin_settings(form)
|
|
form.state = tpad.forms.admin:show(form.playername)
|
|
form.formname = "tpad_admin_settings"
|
|
local max_total_field, max_global_field = form.state:get("max_total_field"), form.state:get("max_global_field")
|
|
max_total_field:setText(tpad.get_max_total_pads())
|
|
max_global_field:setText(tpad.get_max_global_pads())
|
|
|
|
form.state:get("save_button"):onClick(function()
|
|
tpad.set_max_total_pads(tonumber(max_total_field:getText()))
|
|
tpad.set_max_global_pads(tonumber(max_global_field:getText()))
|
|
minetest.close_formspec(form.playername, form.formname)
|
|
end)
|
|
end
|
|
|
|
-- ========================================================================
|
|
-- Main Node Callbacks
|
|
-- ========================================================================
|
|
|
|
function tpad.on_rightclick(clicked_pos, node, clicker)
|
|
local playername = clicker:get_player_name()
|
|
local pad = tpad.get_pad_data(clicked_pos)
|
|
if not pad or not pad.owner then
|
|
notify.err(playername, "Fehler! Fehlende oder korrupte TPAD-Daten. Bitte neu platzieren.")
|
|
return
|
|
end
|
|
|
|
-- Zugriffsschutz-Prüfung zuerst
|
|
if pad.type == PRIVATE_PAD and pad.owner ~= playername and not minetest.get_player_privs(playername).tpad_admin then
|
|
notify.warn(playername, "Dieses TPAD ist privat.")
|
|
return
|
|
end
|
|
|
|
-- Erstelle das Kontext-Objekt für alle Fälle
|
|
local form = {
|
|
playername = playername,
|
|
clicker = clicker,
|
|
ownername = pad.owner,
|
|
clicked_pos = clicked_pos,
|
|
node = node,
|
|
page = 1,
|
|
}
|
|
|
|
-- NEU: Prüfe, ob das Pad neu ist (d.h. keinen Namen hat)
|
|
if pad.name == "" then
|
|
-- Setze einen Parameter zur Vorauswahl für das Verwaltungs-Menü
|
|
form.preselect_pos = clicked_pos
|
|
-- Rufe direkt die Verwaltung auf
|
|
submit.management(form)
|
|
else
|
|
-- BESTEHENDE LOGIK: Wenn das Pad bereits konfiguriert ist
|
|
if pad.type == GLOBAL_PAD then
|
|
tpad.show_network_view(form, "global")
|
|
else
|
|
tpad.show_network_view(form, "local")
|
|
end
|
|
end
|
|
end
|
|
|
|
function tpad.can_dig(pos, player)
|
|
local meta = minetest.get_meta(pos)
|
|
local ownername = meta:get_string("owner")
|
|
local playername = player:get_player_name()
|
|
if ownername == "" or ownername == playername or minetest.get_player_privs(playername).tpad_admin then
|
|
return true
|
|
end
|
|
notify.warn(playername, "Dieses TPAD gehört dir nicht.")
|
|
return false
|
|
end
|
|
|
|
function tpad.on_destruct(pos)
|
|
local meta = minetest.get_meta(pos)
|
|
local ownername = meta:get_string("owner")
|
|
if ownername and ownername ~= "" then
|
|
tpad.del_pad(ownername, pos)
|
|
end
|
|
end
|
|
|
|
-- ========================================================================
|
|
-- FORMS (Rebuilt)
|
|
-- ========================================================================
|
|
|
|
tpad.forms = {}
|
|
|
|
local function create_network_view_form(state)
|
|
state:size(16, 11)
|
|
state:label(0.5, 0.2, "title_label", "")
|
|
local bottom_y = 10.4
|
|
state:button(0.5, bottom_y, 3.0, 0, "toggle_network_button", "")
|
|
state:button(3.6, bottom_y, 2.3, 0, "management_button", "Verwaltung")
|
|
local close_button = state:button(14.0, bottom_y, 1.5, 0, "close_button", "Schließen")
|
|
close_button:setClose(true)
|
|
state:button(6.0, bottom_y, 1.0, 0, "prev_button", "[<<]")
|
|
state:label(7.1, bottom_y, "page_label", "")
|
|
state:button(9.5, bottom_y, 1.0, 0, "next_button", "[>>]")
|
|
|
|
local max_rows, num_columns = 10, 3
|
|
local start_x, start_y = 0.5, 1.0
|
|
local button_width, button_height = 4.8, 0.8
|
|
local column_width = 5.0
|
|
for i = 1, max_rows * num_columns do
|
|
local column = math.floor((i - 1) / max_rows)
|
|
local row = (i - 1) % max_rows
|
|
local current_x = start_x + (column * column_width)
|
|
local current_y = start_y + (row * button_height)
|
|
state:button(current_x, current_y, button_width, 0, "tpad_btn_" .. i, ""):setVisible(false)
|
|
end
|
|
end
|
|
|
|
tpad.forms.teleport_success = smartfs.create("tpad.forms.teleport_success", function(state)
|
|
state:size(8, 2)
|
|
local destination_name = state.param.destination_name or "???"
|
|
state:label(0.5, 0.5, "success_label", "Teleport erfolgreich: " .. YELLOW_ESCAPE .. destination_name)
|
|
|
|
-- Dieser Button hat setClose(true), was das Fenster zuverlässig schließt.
|
|
local close_button = state:button(3, 1.2, 2, 0, "close_button", "Schließen")
|
|
close_button:setClose(true)
|
|
end)
|
|
|
|
tpad.forms.network_view = smartfs.create("tpad.forms.network_view", create_network_view_form)
|
|
|
|
tpad.forms.network_view_admin = smartfs.create("tpad.forms.network_view_admin", function(state)
|
|
create_network_view_form(state)
|
|
state:button(12.4, 10.4, 1.5, 0, "admin_button", "Admin")
|
|
end)
|
|
|
|
tpad.forms.management = smartfs.create("tpad.forms.management", function(state)
|
|
state:size(12, 9)
|
|
state:label(0.2, 0.2, "management_title", "TPAD Verwaltung")
|
|
state:listbox(0.2, 0.6, 11.6, 5, "pads_listbox", {})
|
|
state:field(0.5, 6.6, 6, 0, "padname_field", "Name", "")
|
|
|
|
local padtype_dropdown = state:dropdown(0.5, 6.8, 6, 0, "padtype_dropdown")
|
|
padtype_dropdown:addItem(PRIVATE_PAD_STRING)
|
|
padtype_dropdown:addItem(PUBLIC_PAD_STRING)
|
|
padtype_dropdown:addItem(GLOBAL_PAD_STRING)
|
|
|
|
state:button(7, 6.2, 2, 0, "save_button", "Speichern")
|
|
state:button(7, 7.0, 2, 0, "delete_button", "Löschen")
|
|
state:button(0.2, 8.4, 2, 0, "back_button", "Zurück")
|
|
local close_button = state:button(9.8, 8.4, 2, 0, "close_button", "Schließen")
|
|
close_button:setClose(true)
|
|
end)
|
|
|
|
-- This form now defines its OWN behavior, making it independent and robust.
|
|
tpad.forms.confirm_pad_deletion = smartfs.create("tpad.forms.confirm_pad_deletion", function(state)
|
|
state:size(8, 2.5)
|
|
state:label(0, 0, "intro_label", "Willst du das TPAD wirklich löschen?")
|
|
state:label(0, 0.5, "padname_label", "")
|
|
state:label(0, 1, "outro_label", "(es lässt sich nicht wiederherstellen)")
|
|
|
|
local confirm_button = state:button(0, 2.2, 2, 0, "confirm_button", "Ja, löschen")
|
|
local deny_button = state:button(6, 2.2, 2, 0, "deny_button", "Nein, abbrechen")
|
|
|
|
local playername = state.location.player
|
|
local pending_data = tpad.pending_deletion[playername]
|
|
|
|
if not pending_data then
|
|
state:close()
|
|
return
|
|
end
|
|
|
|
local pad_to_delete = pending_data.pad_data
|
|
local original_form_context = pending_data.original_form_context
|
|
|
|
local pad_display_name = pad_to_delete.name .. " (" .. short_padtype_string[pad_to_delete.type] .. ")"
|
|
state:get("padname_label"):setText(YELLOW_ESCAPE .. pad_display_name)
|
|
|
|
-- Diese Funktion erzwingt einen sauberen Neuaufbau der Verwaltungs-Ansicht
|
|
local function return_to_management_with_fresh_state()
|
|
tpad.pending_deletion[playername] = nil -- Temporäre Daten löschen
|
|
minetest.after(0, function()
|
|
-- Erstelle einen sauberen Kontext, anstatt den alten wiederzuverwenden
|
|
local fresh_context = {
|
|
playername = original_form_context.playername,
|
|
clicker = original_form_context.clicker,
|
|
ownername = original_form_context.ownername,
|
|
clicked_pos = original_form_context.clicked_pos,
|
|
node = minetest.get_node(original_form_context.clicked_pos), -- Node neu holen, falls sich was geändert hat
|
|
network_type = original_form_context.network_type,
|
|
page = 1
|
|
}
|
|
submit.management(fresh_context)
|
|
end)
|
|
end
|
|
|
|
confirm_button:onClick(function()
|
|
tpad.del_pad(pad_to_delete.owner, pad_to_delete.pos)
|
|
minetest.remove_node(pad_to_delete.pos)
|
|
notify(playername, "TPAD '" .. pad_to_delete.name .. "' gelöscht.")
|
|
return_to_management_with_fresh_state()
|
|
end)
|
|
|
|
deny_button:onClick(return_to_management_with_fresh_state)
|
|
end)
|
|
|
|
tpad.forms.admin = smartfs.create("tpad.forms.admin", function(state)
|
|
state:size(8, 8)
|
|
state:label(0.2, 0.2, "admin_label", "TPAD Einstellungen")
|
|
state:field(0.5, 2, 6, 0, "max_total_field", "Max. Gesamtzahl an TPADs (pro Spieler)")
|
|
state:field(0.5, 3.5, 6, 0, "max_global_field", "Max. globale TPADs (pro Spieler)")
|
|
state:button(6.5, 0.7, 1.5, 0, "save_button", "Speichern")
|
|
local close_button = state:button(6.5, 7, 1.5, 0, "close_button", "Schließen")
|
|
close_button:setClose(true)
|
|
end)
|
|
|
|
-- ========================================================================
|
|
-- Data Helper Functions
|
|
-- ========================================================================
|
|
|
|
function tpad.decorate_pad_data(pos, pad, ownername)
|
|
pad = table.copy(pad)
|
|
if type(pos) == "string" then
|
|
pad.strpos = pos
|
|
pad.pos = minetest.string_to_pos(pos)
|
|
else
|
|
pad.pos = pos
|
|
pad.strpos = minetest.pos_to_string(pos)
|
|
end
|
|
pad.owner = ownername
|
|
pad.name = pad.name or ""
|
|
pad.type = pad.type or PUBLIC_PAD
|
|
|
|
-- NEUE LOGIK: Füge den Suffix nur bei privaten Pads hinzu.
|
|
if pad.type == PRIVATE_PAD then
|
|
pad.local_fullname = pad.name .. " (privat)"
|
|
else
|
|
-- Bei Public Pads wird kein Suffix mehr angehängt.
|
|
pad.local_fullname = pad.name
|
|
end
|
|
|
|
pad.global_fullname = pad.name
|
|
return pad
|
|
end
|
|
|
|
function tpad.get_pad_data(pos)
|
|
local meta = minetest.get_meta(pos)
|
|
local ownername = meta:get_string("owner")
|
|
if not ownername or ownername == "" then return end
|
|
local pads = tpad._get_stored_pads(ownername)
|
|
local strpos = minetest.pos_to_string(pos)
|
|
local pad = pads[strpos]
|
|
if not pad then return end
|
|
return tpad.decorate_pad_data(pos, pad, ownername)
|
|
end
|
|
|
|
function tpad.set_pad_data(pos, padname, padtype_str)
|
|
local meta = minetest.get_meta(pos)
|
|
local ownername = meta:get_string("owner")
|
|
local pads = tpad._get_stored_pads(ownername)
|
|
local strpos = minetest.pos_to_string(pos)
|
|
local pad = pads[strpos] or {}
|
|
pad.name = padname
|
|
pad.type = padtype_string_to_flag[padtype_str]
|
|
pads[strpos] = pad
|
|
tpad._set_stored_pads(ownername, pads)
|
|
end
|
|
|
|
function tpad.del_pad(ownername, pos)
|
|
local pads = tpad._get_stored_pads(ownername)
|
|
pads[minetest.pos_to_string(pos)] = nil
|
|
tpad._set_stored_pads(ownername, pads)
|
|
end
|
|
|
|
-- ========================================================================
|
|
-- Register Node and Bind Callbacks
|
|
-- ========================================================================
|
|
|
|
local collision_box = {
|
|
type = "fixed",
|
|
fixed = { -0.5, -0.5, -0.5, 0.5, -0.3, 0.5 },
|
|
}
|
|
|
|
minetest.register_node(tpad.nodename, {
|
|
drawtype = "mesh",
|
|
tiles = { tpad.texture },
|
|
mesh = tpad.mesh,
|
|
paramtype = "light",
|
|
paramtype2 = "facedir",
|
|
on_place = minetest.rotate_and_place,
|
|
after_place_node = tpad.after_place_node,
|
|
collision_box = collision_box,
|
|
selection_box = collision_box,
|
|
description = "Teleporter Pad",
|
|
groups = {choppy = 2, dig_immediate = 2},
|
|
on_rightclick = tpad.on_rightclick,
|
|
can_dig = tpad.can_dig,
|
|
on_destruct = tpad.on_destruct,
|
|
})
|
|
|
|
minetest.register_chatcommand("tpad", {func = tpad.command})
|