warps/init.lua
Montandalar b2d71bf105
Fix errors under 5.x and allow selecting warps from a dropdown (#5)
- Loading would previously fail if there was both no data in mod_storage "warps" and no warps.txt
- A wrong variable used while adding the warp queue was causing crashes when attempting to warp
- Don't even attempt to teleport to a warp spot that doesn't exit
- Allow selecting warps from a dropdown for ease of use, but still allow text input as warps might be created later than the warp crystals are created (negotiable, invalid input could be dropped and only the combobox allowed if others object).
2021-02-16 22:07:09 +01:00

338 lines
9.1 KiB
Lua

--[[
Copyright (C) 2015 - Auke Kok <sofar@foo-projects.org>
"warps" is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1
of the license, or (at your option) any later version.
--]]
local warps = {}
local warps_queue = {}
local queue_state = 0
local warps_freeze = 5
-- t = time in usec
-- p = player obj
-- w = warp name
local S = minetest.get_mod_storage()
assert(S, "mod_storage is required")
-- import warps or load
local function firstload()
local store = S:get("warps")
local worldpath = minetest.get_worldpath()
if store then
warps = minetest.deserialize(store)
return
end
local fh,err = io.open(worldpath .. "/warps.txt", "r")
if err then
-- If it doesn't exist, we've never used this mod before.
if not err:find("No such file or directory") then
minetest.log("error", "[warps] Error trying to load warps.txt: " .. err)
end
return
end
while true do
local line = fh:read()
if line == nil then
break
end
local paramlist = string.split(line, " ")
local w = {
name = paramlist[1],
x = tonumber(paramlist[2]),
y = tonumber(paramlist[3]),
z = tonumber(paramlist[4]),
yaw = tonumber(paramlist[5]),
pitch = tonumber(paramlist[6])
}
table.insert(warps, w)
end
fh:close()
minetest.log("action", "[warps] converted warps to mod_storage. Please delete 'warps.txt'.")
S:set_string("warps", minetest.serialize(warps))
end
local function lookup_warp(name)
for i = 1,table.getn(warps) do
if warps[i].name == name then
return warps[i]
end
end
end
local function round_digits(n, digits)
digits = digits or 0
local multi = math.pow(10, digits)
n = n * multi
if n > 0 then
return math.floor(n + 0.5) / multi
else
return math.ceil(n - 0.5) / multi
end
end
local warp = function(player, dest)
local warp = lookup_warp(dest)
if not warp then
minetest.chat_send_player(player:get_player_name(), "Unknown warp \"" .. dest .. "\"")
return
end
local pos = vector.new(warp)
pos.y = pos.y + 0.5
player:setpos(pos)
player:set_look_horizontal(warp.yaw)
player:set_look_vertical(warp.pitch)
minetest.chat_send_player(player:get_player_name(), "Warped to \"" .. dest .. "\"")
minetest.log("action", player:get_player_name() .. " warped to \"" .. dest .. "\"")
minetest.sound_play("warps_plop", {pos = pos})
end
local function do_warp_queue()
if table.getn(warps_queue) == 0 then
queue_state = 0
return
end
local t = minetest.get_us_time()
for i = table.getn(warps_queue),1,-1 do
local e = warps_queue[i]
if e.p:getpos() then
if vector.equals(e.p:getpos(), e.pos) then
if t > e.t then
warp(e.p, e.w)
table.remove(warps_queue, i)
end
else
minetest.sound_stop(e.sh)
minetest.chat_send_player(e.p:get_player_name(),
"You have to stand still for " .. warps_freeze .. " seconds!")
table.remove(warps_queue, i)
end
end
end
if table.getn(warps_queue) == 0 then
queue_state = 0
return
end
minetest.after(1, do_warp_queue)
end
local warp_queue_add = function(player, dest)
table.insert(warps_queue, {
t = minetest.get_us_time() + (warps_freeze * 1000000),
pos = player:getpos(),
p = player,
w = dest,
sh = minetest.sound_play("warps_woosh", { pos = player:getpos() })
})
minetest.chat_send_player(player:get_player_name(), "Don't move for " .. warps_freeze .. " seconds!")
if queue_state == 0 then
queue_state = 1
minetest.after(1, do_warp_queue)
end
-- attempt to emerge the target area before the player gets there
local pos = vector.new(lookup_warp(dest))
minetest.get_voxel_manip():read_from_map(pos, pos)
if not minetest.get_node_or_nil(pos) then
minetest.emerge_area(vector.subtract(pos, 80), vector.add(pos, 80))
end
-- force mapblock send to player, if supported
if player.send_mapblock then
player:send_mapblock(vector.divide(pos, 16))
end
end
minetest.register_privilege("warp_admin", {
description = "Allows modification of warp points",
give_to_singleplayer = true,
default = false
})
minetest.register_privilege("warp_user", {
description = "Allows use of warp points",
give_to_singleplayer = true,
default = true
})
minetest.register_chatcommand("setwarp", {
params = "name",
description = "Set a warp location to the players location",
privs = { warp_admin = true },
func = function(name, param)
param = param:gsub("%W", "")
if param == "" then
return false, "Cannot set warp: Name missing."
end
local h = "Created"
for i = 1,table.getn(warps) do
if warps[i].name == param then
table.remove(warps, i)
h = "Changed"
break
end
end
local player = minetest.get_player_by_name(name)
local pos = vector.round(player:getpos())
table.insert(warps, {
name = param,
x = pos.x,
y = pos.y,
z = pos.z,
yaw = round_digits(player:get_look_horizontal(), 3),
pitch = round_digits(player:get_look_vertical(), 3)
})
S:set_string("warps", minetest.serialize(warps))
minetest.log("action", name .. " " .. h .. " warp \"" .. param .. "\": " ..
pos.x .. ", " .. pos.y .. ", " .. pos.z)
return true, h .. " warp \"" .. param .. "\""
end,
})
minetest.register_chatcommand("delwarp", {
params = "name",
description = "Set a warp location to the players location",
privs = { warp_admin = true },
func = function(name, param)
for i = 1,table.getn(warps) do
if warps[i].name == param then
table.remove(warps, i)
minetest.log("action", name .. " removed warp \"" .. param .. "\"")
return true, "Removed warp \"" .. param .. "\""
end
end
return false, "Unknown warp location \"" .. param .. "\""
end,
})
minetest.register_chatcommand("listwarps", {
params = "name",
description = "List known warp locations",
privs = { warp_user = true },
func = function(name, param)
local s = "List of known warp locations:\n"
for i = 1,table.getn(warps) do
s = s .. "- " .. warps[i].name .. "\n"
end
return true, s
end
})
minetest.register_chatcommand("warp", {
params = "name",
description = "Warp to a warp location",
privs = { warp_user = true },
func = function(name, param)
local player = minetest.get_player_by_name(name)
if not minetest.check_player_privs(player, {warp_admin = true}) then
warp_queue_add(player, param)
else
warp(player, param)
end
end
})
local function prepare_dropdown(x,y,w,h,curr_dest)
local dd = string.format("dropdown[%f,%f;%f,%f;ddwarp;", x, y, w, h)
local sel = 0
for idx, warp in ipairs(warps) do
local warpname = warp.name
dd = dd .. minetest.formspec_escape(warpname) .. ","
if curr_dest == warpname then
sel = idx
end
end
dd = dd .. ";"..tostring(sel).."]"
return dd
end
local function prepare_formspec(dest)
local custdest = ""
if not lookup_warp(dest) then
custdest = dest
end
return "size[4.5,3]label[0.7,0;Warp destination]"
.."field[1,2.2;3,0.2;destination;Future destination;"
..minetest.formspec_escape(custdest).."]"
.."button_exit[0.7,2.7;3,0.5;proceed;Proceed]"
..prepare_dropdown(0.7,0.4,3,1, dest)
end
minetest.register_node("warps:warpstone", {
visual = "mesh",
mesh = "warps_warpstone.obj",
description = "A Warp Stone",
tiles = { "warps_warpstone.png" },
drawtype = "mesh",
sunlight_propagates = true,
walkable = false,
paramtype = "light",
groups = { choppy=3 },
light_source = 8,
diggable = false,
selection_box = {
type = "fixed",
fixed = {-0.25, -0.5, -0.25, 0.25, 0.5, 0.25}
},
on_construct = function(pos)
local meta = minetest.get_meta(pos)
meta:set_string("formspec", prepare_formspec(""))
meta:set_string("infotext", "Uninitialized Warp Stone")
end,
on_receive_fields = function(pos, formname, fields, sender)
if not minetest.check_player_privs(sender:get_player_name(), {warp_admin = true}) then
minetest.chat_send_player(sender:get_player_name(), "You do not have permission to modify warp stones")
return false
end
if not (fields.destination and fields.quit) then
return
end
local dest
if fields.destination == "" and fields.ddwarp then
dest = fields.ddwarp
else
dest = fields.destination
end
local meta = minetest.get_meta(pos)
meta:set_string("formspec", prepare_formspec(dest))
meta:set_string("infotext", "Warp stone to " .. dest)
meta:set_string("warps_destination", dest)
minetest.log("action", sender:get_player_name() .. " changed warp stone at "
.. minetest.pos_to_string(pos) .. " to \"" .. dest .. "\"")
end,
on_punch = function(pos, node, puncher, pointed_thingo)
if puncher:get_player_control().sneak and
minetest.check_player_privs(puncher:get_player_name(), {warp_admin = true}) then
minetest.remove_node(pos)
minetest.chat_send_player(puncher:get_player_name(), "Warp stone removed!")
return
end
local meta = minetest.get_meta(pos)
local destination = meta:get_string("warps_destination")
if destination == "" or lookup_warp(destination) == nil then
minetest.chat_send_player(puncher:get_player_name(),
"Unknown warp location for this warp stone, cannot warp!")
return false
end
minetest.log("action", string.format("Going to warp player %s to waypoint %s",
puncher:get_player_name(), destination
))
warp_queue_add(puncher, destination)
end,
})
firstload()