init
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 entuland
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
89
README.md
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
# tpad
|
||||||
|
A teleporter-pads mod for Minetest
|
||||||
|
|
||||||
|
Developed and tested on Minetest 0.4.16 - try in other versions at your own risk :)
|
||||||
|
|
||||||
|
WIP mod forum discussion: https://forum.minetest.net/viewtopic.php?f=9&t=20081
|
||||||
|
|
||||||
|
**Table of Contents**
|
||||||
|
- [Recipe](#recipe)
|
||||||
|
- [Features](#features)
|
||||||
|
- [Appearance](#appearance)
|
||||||
|
- [Pad types](#pad-types)
|
||||||
|
- [Pad interaction](#pad-interaction)
|
||||||
|
- [Closest pad waypoint](#closest-pad-waypoint)
|
||||||
|
- [Pad admin](#pad-admin)
|
||||||
|
- [Screenshots](#screenshots)
|
||||||
|
|
||||||
|
## Recipe
|
||||||
|
The recipe can be customized altering the file `custom.recipes.lua`, created in the mod's folder on first run and never overwritten.
|
||||||
|
|
||||||
|
W = any wood planks
|
||||||
|
B = bronze ingot
|
||||||
|
|
||||||
|
WBW
|
||||||
|
BWB
|
||||||
|
WBW
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
With these pads players can build their own Local Network and collaborate to build a Global Network shared among all players.
|
||||||
|
|
||||||
|
Pads are sorted by name in the lists, the Global Network list groups them by owner name first.
|
||||||
|
|
||||||
|
## Appearance
|
||||||
|
|
||||||
|
This is how a pad looks like when placed against a wall or on the floor (they can be placed under the ceiling as well):
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Pad types
|
||||||
|
|
||||||
|
Each pad can be set as one of these three types:
|
||||||
|
- `Private` (default): only accessible to its owner or by an admin
|
||||||
|
- `Public`: accessible to anyone from any Public pad of the owner's Local Network
|
||||||
|
- `Global`: accessible to anyone from any Public pad
|
||||||
|
|
||||||
|
A pad can be edited and destroyed only by its owner or by an admin.
|
||||||
|
|
||||||
|
## Pad interaction
|
||||||
|
|
||||||
|
- place a pad down, it will be immediately active and set as "Private"
|
||||||
|
- right click a pad edit its name/type and to access the Networks
|
||||||
|
- select a pad from a list and hit "Teleport", or doubleclick on the list item
|
||||||
|
- delete any remote pad by selecting it on the Local Network list and clicking "Delete"
|
||||||
|
|
||||||
|
## Closest pad waypoint
|
||||||
|
|
||||||
|
Issue `/tpad` on the chat to get a waypoint to the closest of your pads.
|
||||||
|
Issue `/tpad off` to remove the waypoint from the HUD - it will also be removed when you teleport from any pad.
|
||||||
|
|
||||||
|
## Pad admin
|
||||||
|
|
||||||
|
A `tpad_admin` privilege is available, players with such privilege can access, alter and destroy any pad, they can set the max number of total / global pads a player can create and they can also place any amount of pads regardless of those limits.
|
||||||
|
|
||||||
|
Limits can be edited by admins directly in the admin interface (reachable from the "Global Network" dialog of any pad); limits get stored on a per-world basis in the file `/mod_storage/tpad.custom.conf`. By default a player can place up to 100 pads, and of these, only 4 can appear in the Global Network.
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
A public pad's interface seen by a visitor:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The same interface seen by its owner or by an admin (notice the highlighted "private" pad, which was hidden in the previous interface):
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The Global Network interface seen by a non-admin:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Same as above, but the admins can see an "Admin" button:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Admin settings interface:
|
||||||
|
|
||||||
|

|
||||||
12
custom.recipes.lua
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
-- only alter this file if it's named "custom.recipes.lua"
|
||||||
|
-- alter the recipes as you please and delete / comment out
|
||||||
|
-- the recipes you don't want to be available in the game
|
||||||
|
-- the original versions are in "default/recipes.lua"
|
||||||
|
|
||||||
|
return {
|
||||||
|
["tpad:tpad"] = {
|
||||||
|
{'group:wood', 'default:bronze_ingot', 'group:wood'},
|
||||||
|
{'default:bronze_ingot', 'group:wood', 'default:bronze_ingot'},
|
||||||
|
{'group:wood', 'default:bronze_ingot', 'group:wood'},
|
||||||
|
},
|
||||||
|
}
|
||||||
2
default/README.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
please do not edit any file in this folder,
|
||||||
|
corresponding custom.* files get created in the main mod's folder for you to customize
|
||||||
12
default/recipes.lua
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
-- only alter this file if it's named "custom.recipes.lua"
|
||||||
|
-- alter the recipes as you please and delete / comment out
|
||||||
|
-- the recipes you don't want to be available in the game
|
||||||
|
-- the original versions are in "default/recipes.lua"
|
||||||
|
|
||||||
|
return {
|
||||||
|
["tpad:tpad"] = {
|
||||||
|
{'group:wood', 'default:bronze_ingot', 'group:wood'},
|
||||||
|
{'default:bronze_ingot', 'group:wood', 'default:bronze_ingot'},
|
||||||
|
{'group:wood', 'default:bronze_ingot', 'group:wood'},
|
||||||
|
},
|
||||||
|
}
|
||||||
938
init.lua
Normal file
|
|
@ -0,0 +1,938 @@
|
||||||
|
-- ========================================================================
|
||||||
|
-- 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
|
||||||
|
-- Der 'viewername' ist hier nicht bekannt, also wird 'nil' übergeben, was die Funktion korrekt behandelt.
|
||||||
|
table.insert(result, tpad.decorate_pad_data(strpos, pad, ownername, nil))
|
||||||
|
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 = {}
|
||||||
|
|
||||||
|
local owner_pads = tpad._get_stored_pads(ownername)
|
||||||
|
for strpos, pad in pairs(owner_pads) do
|
||||||
|
local is_viewer_the_owner = (ownername == viewername)
|
||||||
|
if (pad.type == PUBLIC_PAD) or (is_viewer_the_owner and pad.type == PRIVATE_PAD) then
|
||||||
|
if pad.type ~= GLOBAL_PAD then
|
||||||
|
table.insert(result, tpad.decorate_pad_data(strpos, pad, ownername, viewername))
|
||||||
|
added_pads[strpos] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if ownername ~= viewername then
|
||||||
|
local viewer_pads = tpad._get_stored_pads(viewername)
|
||||||
|
for strpos, pad in pairs(viewer_pads) do
|
||||||
|
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, 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, playername))
|
||||||
|
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, 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, playername)
|
||||||
|
|
||||||
|
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 für private Pads
|
||||||
|
if pad.type == PRIVATE_PAD and pad.owner ~= playername and not minetest.get_player_privs(playername).tpad_admin then
|
||||||
|
local RED_ESCAPE = minetest.get_color_escape_sequence("#FF0000") or ""
|
||||||
|
notify.warn(playername, YELLOW_ESCAPE .. "PRIVATE" .. WHITE_ESCAPE .. " TPAD Station von " .. pad.owner .. ". " .. RED_ESCAPE .. "Zugriff verweigert.")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Erstelle das Kontext-Objekt für die Formulare
|
||||||
|
local form = {
|
||||||
|
playername = playername,
|
||||||
|
clicker = clicker,
|
||||||
|
ownername = pad.owner,
|
||||||
|
clicked_pos = clicked_pos,
|
||||||
|
node = node,
|
||||||
|
page = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Wenn das Pad neu ist (keinen Namen hat), direkt zur Verwaltung
|
||||||
|
if pad.name == "" then
|
||||||
|
form.preselect_pos = clicked_pos
|
||||||
|
submit.management(form)
|
||||||
|
else
|
||||||
|
-- Ansonsten zeige die entsprechende Netzwerk-Ansicht
|
||||||
|
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, viewername)
|
||||||
|
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: Erstellt den Anzeigenamen für das lokale Netzwerk
|
||||||
|
local is_own_pad = (viewername and ownername == viewername)
|
||||||
|
if is_own_pad then
|
||||||
|
if pad.type == PRIVATE_PAD then
|
||||||
|
pad.local_fullname = pad.name .. " (meins, privat)"
|
||||||
|
elseif pad.type == PUBLIC_PAD then
|
||||||
|
pad.local_fullname = pad.name .. " (meins)"
|
||||||
|
else
|
||||||
|
-- Fallback, falls es weitere Typen gäbe
|
||||||
|
pad.local_fullname = pad.name
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- Bisherige Logik für fremde Pads
|
||||||
|
if pad.type == PRIVATE_PAD then
|
||||||
|
pad.local_fullname = pad.name .. " (privat)"
|
||||||
|
else
|
||||||
|
pad.local_fullname = pad.name
|
||||||
|
end
|
||||||
|
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})
|
||||||
869
init.lua.bak.1
Normal file
|
|
@ -0,0 +1,869 @@
|
||||||
|
-- ========================================================================
|
||||||
|
-- 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})
|
||||||
901
init.lua.bak.2
Normal file
|
|
@ -0,0 +1,901 @@
|
||||||
|
-- ========================================================================
|
||||||
|
-- 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})
|
||||||
925
init.lua.bak.3
Normal file
|
|
@ -0,0 +1,925 @@
|
||||||
|
-- ========================================================================
|
||||||
|
-- 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"
|
||||||
|
|
||||||
|
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()
|
||||||
|
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"
|
||||||
|
-- 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})
|
||||||
931
init.lua.bak.4
Normal file
|
|
@ -0,0 +1,931 @@
|
||||||
|
-- ========================================================================
|
||||||
|
-- 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})
|
||||||
938
init.lua.bak.5
Normal file
|
|
@ -0,0 +1,938 @@
|
||||||
|
-- ========================================================================
|
||||||
|
-- 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
|
||||||
|
-- Der 'viewername' ist hier nicht bekannt, also wird 'nil' übergeben, was die Funktion korrekt behandelt.
|
||||||
|
table.insert(result, tpad.decorate_pad_data(strpos, pad, ownername, nil))
|
||||||
|
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 = {}
|
||||||
|
|
||||||
|
local owner_pads = tpad._get_stored_pads(ownername)
|
||||||
|
for strpos, pad in pairs(owner_pads) do
|
||||||
|
local is_viewer_the_owner = (ownername == viewername)
|
||||||
|
if (pad.type == PUBLIC_PAD) or (is_viewer_the_owner and pad.type == PRIVATE_PAD) then
|
||||||
|
if pad.type ~= GLOBAL_PAD then
|
||||||
|
table.insert(result, tpad.decorate_pad_data(strpos, pad, ownername, viewername))
|
||||||
|
added_pads[strpos] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if ownername ~= viewername then
|
||||||
|
local viewer_pads = tpad._get_stored_pads(viewername)
|
||||||
|
for strpos, pad in pairs(viewer_pads) do
|
||||||
|
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, 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, playername))
|
||||||
|
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, 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, viewername)
|
||||||
|
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: Erstellt den Anzeigenamen für das lokale Netzwerk
|
||||||
|
local is_own_pad = (viewername and ownername == viewername)
|
||||||
|
if is_own_pad then
|
||||||
|
if pad.type == PRIVATE_PAD then
|
||||||
|
pad.local_fullname = pad.name .. " (meins, privat)"
|
||||||
|
elseif pad.type == PUBLIC_PAD then
|
||||||
|
pad.local_fullname = pad.name .. " (meins)"
|
||||||
|
else
|
||||||
|
-- Fallback, falls es weitere Typen gäbe
|
||||||
|
pad.local_fullname = pad.name
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- Bisherige Logik für fremde Pads
|
||||||
|
if pad.type == PRIVATE_PAD then
|
||||||
|
pad.local_fullname = pad.name .. " (privat)"
|
||||||
|
else
|
||||||
|
pad.local_fullname = pad.name
|
||||||
|
end
|
||||||
|
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})
|
||||||
1440
lib/smartfs.lua
Normal file
5
mod.conf
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
name = tpad
|
||||||
|
release = 28854
|
||||||
|
author = entuland
|
||||||
|
description = A simple but powerful pad to create teleporting networks
|
||||||
|
title = Teleporter Pads
|
||||||
60
models/tpad-mesh.obj
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
# Blender v2.79 (sub 0) OBJ File: 'tpad.blend'
|
||||||
|
# www.blender.org
|
||||||
|
mtllib tpad-mesh.mtl
|
||||||
|
o Cube
|
||||||
|
v -0.500000 -0.500000 -0.500000
|
||||||
|
v 0.500000 -0.500000 -0.500000
|
||||||
|
v 0.500000 -0.500000 0.500000
|
||||||
|
v -0.500000 -0.500000 0.500000
|
||||||
|
v -0.429289 -0.300000 -0.429289
|
||||||
|
v 0.429289 -0.300000 -0.429289
|
||||||
|
v 0.429289 -0.300000 0.429289
|
||||||
|
v -0.429289 -0.300000 0.429289
|
||||||
|
v -0.500000 -0.380000 -0.500000
|
||||||
|
v 0.500000 -0.380000 -0.499999
|
||||||
|
v -0.500000 -0.380000 0.500000
|
||||||
|
v 0.500000 -0.380000 0.500000
|
||||||
|
vt 0.078125 0.421875
|
||||||
|
vt 0.078125 0.000000
|
||||||
|
vt 0.484375 0.000000
|
||||||
|
vt 0.484375 0.421875
|
||||||
|
vt 0.093750 0.515625
|
||||||
|
vt 0.468750 0.515625
|
||||||
|
vt 0.468750 0.890625
|
||||||
|
vt 0.093750 0.890625
|
||||||
|
vt -0.000000 0.484375
|
||||||
|
vt 0.046875 0.484375
|
||||||
|
vt 0.046875 0.921875
|
||||||
|
vt -0.000000 0.921875
|
||||||
|
vt 0.078125 0.984375
|
||||||
|
vt 0.078125 0.937500
|
||||||
|
vt 0.484375 0.937500
|
||||||
|
vt 0.484375 0.984375
|
||||||
|
vt 0.562500 0.921875
|
||||||
|
vt 0.515625 0.921875
|
||||||
|
vt 0.515625 0.484375
|
||||||
|
vt 0.562500 0.484375
|
||||||
|
vt 0.078125 0.468750
|
||||||
|
vt 0.484375 0.468750
|
||||||
|
vn 0.0000 -1.0000 0.0000
|
||||||
|
vn 0.0000 1.0000 -0.0000
|
||||||
|
vn 0.0000 0.0000 -1.0000
|
||||||
|
vn 1.0000 -0.0000 0.0000
|
||||||
|
vn -0.0000 -0.0000 1.0000
|
||||||
|
vn -1.0000 0.0000 -0.0000
|
||||||
|
vn 0.0000 0.6623 -0.7493
|
||||||
|
vn -0.7493 0.6623 -0.0000
|
||||||
|
vn 0.7493 0.6623 0.0000
|
||||||
|
vn -0.0000 0.6623 0.7493
|
||||||
|
usemtl Material
|
||||||
|
s off
|
||||||
|
f 1/1/1 2/2/1 3/3/1 4/4/1
|
||||||
|
f 5/5/2 8/6/2 7/7/2 6/8/2
|
||||||
|
f 1/9/3 9/10/3 10/11/3 2/12/3
|
||||||
|
f 2/13/4 10/14/4 12/15/4 3/16/4
|
||||||
|
f 3/17/5 12/18/5 11/19/5 4/20/5
|
||||||
|
f 9/21/6 1/1/6 4/4/6 11/22/6
|
||||||
|
f 5/5/7 6/8/7 10/11/7 9/10/7
|
||||||
|
f 8/6/8 5/5/8 9/21/8 11/22/8
|
||||||
|
f 6/8/9 7/7/9 12/15/9 10/14/9
|
||||||
|
f 7/7/10 8/6/10 11/19/10 12/18/10
|
||||||
79
notify.lua
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
local mod_name = minetest.get_current_modname()
|
||||||
|
local huds = {}
|
||||||
|
local hud_timeout_seconds = 3
|
||||||
|
|
||||||
|
-- defaults
|
||||||
|
local position = { x = 0.1, y = 0.9}
|
||||||
|
local alignment = { x = 1, y = -1}
|
||||||
|
local normal_color = 0xFFFFFF
|
||||||
|
local warning_color = 0xFFFF00
|
||||||
|
local error_color = 0xDD0000
|
||||||
|
local direction = 0
|
||||||
|
|
||||||
|
local notify = {}
|
||||||
|
notify.__index = notify
|
||||||
|
setmetatable(notify, notify)
|
||||||
|
|
||||||
|
local function hud_remove(player, playername)
|
||||||
|
local hud = huds[playername]
|
||||||
|
if not hud then return end
|
||||||
|
if os.time() < hud_timeout_seconds + hud.time then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
player:hud_remove(hud.id)
|
||||||
|
huds[playername] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function hud_create(player, message, params)
|
||||||
|
local playername = player:get_player_name()
|
||||||
|
local def = type(params) == "table" and params or {}
|
||||||
|
def.position = def.position or position
|
||||||
|
def.alignment = def.alignment or alignment
|
||||||
|
def.number = def.number or def.color or normal_color
|
||||||
|
def.color = nil
|
||||||
|
def.position = def.position or position
|
||||||
|
def.direction = def.direction or direction
|
||||||
|
def.text = message or def.text
|
||||||
|
def.hud_elem_type = def.hud_elem_type or "text"
|
||||||
|
def.name = mod_name .. "_feedback"
|
||||||
|
local id = player:hud_add(def)
|
||||||
|
huds[playername] = {
|
||||||
|
id = id,
|
||||||
|
time = os.time(),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
notify.warn = function(player, message)
|
||||||
|
notify(player, message, {color = warning_color })
|
||||||
|
end
|
||||||
|
|
||||||
|
notify.warning = notify.warn
|
||||||
|
|
||||||
|
notify.err = function(player, message)
|
||||||
|
notify(player, message, {color = error_color })
|
||||||
|
end
|
||||||
|
|
||||||
|
notify.error = notify.err
|
||||||
|
|
||||||
|
notify.__call = function(self, player, message, params)
|
||||||
|
local playername
|
||||||
|
if type(player) == "string" then
|
||||||
|
playername = player
|
||||||
|
player = minetest.get_player_by_name(playername)
|
||||||
|
elseif player and player.get_player_name then
|
||||||
|
playername = player:get_player_name()
|
||||||
|
else
|
||||||
|
return
|
||||||
|
end
|
||||||
|
message = "[" .. mod_name .. "] " .. message
|
||||||
|
local hud = huds[playername]
|
||||||
|
if hud then
|
||||||
|
player:hud_remove(hud.id)
|
||||||
|
end
|
||||||
|
hud_create(player, message, params)
|
||||||
|
minetest.after(hud_timeout_seconds, function()
|
||||||
|
hud_remove(player, playername)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
return notify
|
||||||
BIN
screenshots/admin-settings.png
Normal file
|
After Width: | Height: | Size: 300 KiB |
BIN
screenshots/crafting.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
screenshots/global-network-admin.png
Normal file
|
After Width: | Height: | Size: 288 KiB |
BIN
screenshots/global-network.png
Normal file
|
After Width: | Height: | Size: 237 KiB |
BIN
screenshots/local-network-visitor.png
Normal file
|
After Width: | Height: | Size: 259 KiB |
BIN
screenshots/local-network.png
Normal file
|
After Width: | Height: | Size: 268 KiB |
BIN
screenshots/pads.png
Normal file
|
After Width: | Height: | Size: 338 KiB |
BIN
sounds/tpad_teleport.ogg
Normal file
113
storage.lua
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
|
||||||
|
local storage = minetest.get_mod_storage()
|
||||||
|
|
||||||
|
function tpad._get_all_pads()
|
||||||
|
local storage_table = storage:to_table()
|
||||||
|
local allpads = {}
|
||||||
|
for key, value in pairs(storage_table.fields) do
|
||||||
|
local parts = key:split(":")
|
||||||
|
if parts[1] == "pads" then
|
||||||
|
local pads = minetest.deserialize(value)
|
||||||
|
if type(pads) == "table" then
|
||||||
|
allpads[parts[2]] = pads
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return allpads
|
||||||
|
end
|
||||||
|
|
||||||
|
function tpad._get_stored_pads(ownername)
|
||||||
|
local serial_pads = storage:get_string("pads:" .. ownername)
|
||||||
|
if serial_pads == nil or serial_pads == "" then return {} end
|
||||||
|
return minetest.deserialize(serial_pads)
|
||||||
|
end
|
||||||
|
|
||||||
|
function tpad._set_stored_pads(ownername, pads)
|
||||||
|
if ownername == nil or ownername == "" then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
storage:set_string("pads:" .. ownername, minetest.serialize(pads))
|
||||||
|
end
|
||||||
|
|
||||||
|
function tpad.set_max_total_pads(max)
|
||||||
|
if not max then max = 0 end
|
||||||
|
storage:set_string("max_total_pads_per_player", max)
|
||||||
|
end
|
||||||
|
|
||||||
|
function tpad.get_max_total_pads()
|
||||||
|
local max = tonumber(storage:get_string("max_total_pads_per_player"))
|
||||||
|
if not max then
|
||||||
|
tpad.set_max_total_pads(100)
|
||||||
|
return 100
|
||||||
|
end
|
||||||
|
return max
|
||||||
|
end
|
||||||
|
|
||||||
|
function tpad.set_max_global_pads(max)
|
||||||
|
if not max then max = 0 end
|
||||||
|
storage:set_string("max_global_pads_per_player", max)
|
||||||
|
end
|
||||||
|
|
||||||
|
function tpad.get_max_global_pads()
|
||||||
|
local max = tonumber(storage:get_string("max_global_pads_per_player"))
|
||||||
|
if not max then
|
||||||
|
tpad.set_max_global_pads(4)
|
||||||
|
return 4
|
||||||
|
end
|
||||||
|
return max
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _convert_legacy_settings()
|
||||||
|
local legacy_settings_file = minetest.get_worldpath() .. "/mod_storage/" .. tpad.mod_name .. ".custom.conf"
|
||||||
|
local file = io.open(legacy_settings_file, "r")
|
||||||
|
if file then
|
||||||
|
file:close()
|
||||||
|
local settings = Settings(legacy_settings_file)
|
||||||
|
local max_global = tonumber(settings:get("max_global_pads_per_player"))
|
||||||
|
if max_global then
|
||||||
|
tpad.set_max_global_pads(max_global)
|
||||||
|
end
|
||||||
|
local max_total = tonumber(settings:get("max_total_pads_per_player"))
|
||||||
|
if max_total then
|
||||||
|
tpad.set_max_total_pads(max_total)
|
||||||
|
end
|
||||||
|
os.remove(legacy_settings_file)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
_convert_legacy_settings()
|
||||||
|
tpad.get_max_total_pads()
|
||||||
|
tpad.get_max_global_pads()
|
||||||
|
|
||||||
|
local function _convert_storage_1_1()
|
||||||
|
local storage_table = storage:to_table()
|
||||||
|
for field, value in pairs(storage_table.fields) do
|
||||||
|
local parts = field:split(":")
|
||||||
|
if parts[1] == "pads" then
|
||||||
|
local pads = minetest.deserialize(value)
|
||||||
|
for key, name in pairs(pads) do
|
||||||
|
pads[key] = { name = name }
|
||||||
|
end
|
||||||
|
storage_table.fields[field] = minetest.serialize(pads)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
storage:from_table(storage_table)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _storage_version_check()
|
||||||
|
local storage_version = storage:get_string("_version")
|
||||||
|
local storage_path = minetest.get_worldpath() .. "/mod_storage/"
|
||||||
|
if storage_version == "1.1" then
|
||||||
|
local file = io.open(storage_path .. tpad.mod_name, "r")
|
||||||
|
if file then
|
||||||
|
file:close()
|
||||||
|
tpad._copy_file(storage_path .. tpad.mod_name, storage_path .. tpad.mod_name .. ".1.1.backup")
|
||||||
|
end
|
||||||
|
_convert_storage_1_1()
|
||||||
|
elseif storage_version ~= "" and storage_version ~= tpad.version then
|
||||||
|
error("Mod storage version not supported, aborting to prevent data corruption")
|
||||||
|
end
|
||||||
|
storage:set_string("_version", tpad.version)
|
||||||
|
end
|
||||||
|
|
||||||
|
_storage_version_check()
|
||||||
BIN
textures/tpad-texture.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
textures/tpad_bg_transparent.png
Normal file
|
After Width: | Height: | Size: 141 B |
BIN
textures/tpad_bg_white.png
Normal file
|
After Width: | Height: | Size: 142 B |
BIN
textures/tpad_particle.png
Normal file
|
After Width: | Height: | Size: 498 B |