tpad/init.lua.bak.4
2025-08-22 01:59:14 +02:00

931 lines
31 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: Hole die Pads des Besitzers, auf dessen Pad man steht.
local owner_pads = tpad._get_stored_pads(ownername)
for strpos, pad in pairs(owner_pads) do
-- KORREKTUR: Wenn der Betrachter der Besitzer ist, zeige auch seine privaten Pads an.
-- Ansonsten zeige nur die öffentlichen Pads des Besitzers.
local is_viewer_the_owner = (ownername == viewername)
if (pad.type == PUBLIC_PAD) or (is_viewer_the_owner and pad.type == PRIVATE_PAD) then
-- Füge nur Pads hinzu, die nicht global sind.
if pad.type ~= GLOBAL_PAD then
table.insert(result, tpad.decorate_pad_data(strpos, pad, ownername))
added_pads[strpos] = true
end
end
end
-- Schritt 2: Füge die eigenen Pads des Betrachters hinzu, falls er eine andere Person ist.
if ownername ~= viewername then
local viewer_pads = tpad._get_stored_pads(viewername)
for strpos, pad in pairs(viewer_pads) do
-- Der Betrachter sieht immer seine eigenen privaten und öffentlichen Pads.
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"
local current_pad_type_str
if current_pad_data.type == GLOBAL_PAD then
current_pad_type_str = "GLOBALE"
else
current_pad_type_str = "LOKALE"
end
local RED_ESCAPE = minetest.get_color_escape_sequence("#FF0000") or ""
title_text = RED_ESCAPE .. current_pad_type_str .. " " .. WHITE_ESCAPE .. "TPAD-Station " .. YELLOW_ESCAPE .. current_pad_name .. WHITE_ESCAPE .. ". Wähle ein Ziel:"
if network_type == "global" then
form.network_type = "global"
pad_list = submit.global_helper()
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()
-- KORREKTUR: Der 'ownername' wird nicht mehr überschrieben.
-- Er bleibt der des ursprünglichen Pads, was korrekt ist.
clean_form.page = 1
tpad.show_network_view(clean_form, "local")
end)
end)
else -- "local"
form.network_type = "local"
-- KORREKTUR: Übergebe den Spielernamen (string), nicht das Ergebnis eines Vergleichs (boolean).
pad_list = submit.local_helper(form.ownername, form.playername)
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)
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")
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
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)
break
end
end
end
listbox:onClick(function()
local index = listbox:getSelected()
update_fields_for_index(index)
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
if not minetest.get_player_privs(form.playername).tpad_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)
-- NEU: Logik für den Admin-Teleport-Button
if is_admin then
local admin_teleport_button = form.state:get("admin_teleport_button")
admin_teleport_button:setVisible(true)
admin_teleport_button:onClick(function()
if not selected_pad_data then
notify.warn(form.playername, "Bitte zuerst ein TPAD aus der Liste auswählen, um dorthin zu teleportieren.")
return
end
local dest_pad = selected_pad_data
-- Erstelle den Kontext für die Ansicht, die nach dem Teleport angezeigt werden soll
local new_context_after_teleport = {
playername = form.playername,
clicker = form.clicker,
ownername = dest_pad.owner,
clicked_pos = dest_pad.pos,
node = minetest.get_node(dest_pad.pos),
page = 1,
network_type = (dest_pad.type == GLOBAL_PAD) and "global" or "local"
}
tpad.do_teleport(form.clicker, dest_pad.pos, dest_pad.name, new_context_after_teleport)
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.25, 6.7, 6.25, 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.3, 2, 0, "save_button", "Speichern")
state:button(7, 7.1, 2, 0, "delete_button", "Löschen")
state:button(0.2, 8.4, 2, 0, "back_button", "Zurück")
-- NEU: Teleport-Button für Admins, standardmäßig unsichtbar
state:button(2.3, 8.4, 3, 0, "admin_teleport_button", "Teleport (Admin)"):setVisible(false)
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})