-- ======================================================================== -- 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 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) local pads = tpad._get_stored_pads(ownername) local result = {} 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 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 = "Du bist hier: " .. YELLOW_ESCAPE .. current_pad_name .. WHITE_ESCAPE .. ". Wähle ein globales 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) title_text = "Du bist hier: " .. YELLOW_ESCAPE .. current_pad_name .. WHITE_ESCAPE .. ". Wähle ein lokales 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 if new_type_str == GLOBAL_PAD_STRING and tpad.max_global_pads_reached(selected_pad_data.owner) then notify.warn(form.playername, "Besitzer kann keine TPADs (mehr) zum globalen Netzwerk hinzufügen.") return end tpad.set_pad_data(selected_pad_data.pos, new_name, new_type_str) notify(form.playername, "TPAD '" .. new_name .. "' gespeichert.") minetest.after(0, function() -- WICHTIG: preselect_pos nach dem Speichern entfernen, damit es nicht "hängen bleibt" 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})