master
Cédric Ronvel 2019-11-25 15:36:18 +01:00
commit 8067c2c819
12 changed files with 1428 additions and 0 deletions

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 cronvel
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.

74
README.md Normal file
View File

@ -0,0 +1,74 @@
## Respawn
-- Manage respawn points, interesting places, teleportation and death records
### Features
* Create/manage as many respawn points you want, to be used randomly (e.g. on player death or first connection)
* Create/manage global map place
* Each player can have its own personal places (stored and used separately from the global places)
* Each place and respawn retains not only the position, **but also a direction where to look**,
ideal for creating great spot on the map where you already look at the right thing once teleported
* Place have a short name (i.e. an ID, without space) and a full name (that can have space)
* Check close to which place you are
* Powerful teleport command
* teleport self or teleport other (with different privileges)
* teleport to a respawn point, a global place, an own place, a coordinate, your last death place, or next to a player
* Every death are logged, with the reason and the place it happened (if closed to a known global place)
* List any type of places
* List any player death
* Have a setting allowing player to respawn to their personal own place named "home" instead of using a regular respawn point
(disabled by default)
### Commands overview
For the complete syntax, see the in-game help.
* /list_respawns: List all respawn points.
* /reset_respawns: Reset respawn points. Require the "server" privilege.
* /set_respawn: Create a respawn point on your current player position. Require the "server" privilege.
* /list_places: List all (global) places.
* /reset_places: Reset (global) places, i.e. remove all places at once. Require the "server" and "place" privileges.
* /set_place: Create a (global) place on your current player position, also accept a full name for tasteful place names.
Require the "place" privilege.
* /remove_place: Remove one of the (global) place. Require the "place" privilege.
* /list_own_places: List all your personal own places.
* /reset_own_places: Reset all your personal own places, i.e. remove them all at once.
* /set_own_place: Create a personal own place on your current player position, also accept a full name for tasteful place names.
* /remove_own_place: Remove one of your personal own place.
* /reset_all_player_places: Remove all personal places of all players at once. Require the "server" and "place" privileges.
* /where: Tell you where you are, i.e. close to which place you are, if there is anyone close to you.
Search on both your own places list and the global places list. Require no privileges, but with the "locate" privilege
you can also see your coordinate and you can also locate any player.
* /teleport: teleport yourself to a respawn point, a global place, a personal own place, your last death place, a coordinate,
or a player. Require the "teleport" privilege.
* /teleport_other: teleport anyone to a respawn point, a global place, one of your personal own place (not their),
their last death place, a coordinate, close to you or any player. Require the "teleport" and "teleport_other" privileges.
* /list_deaths: list your or any player deaths with the cause and the place it occurs.
### Privileges
* teleport: Can use /teleport to self teleport to a registered respawn point, global place, own place, last death place.
Can teleport to xyz coordinates or close to another player in conjunction with the "locate" privilege.
* teleport_other: Can use /teleport_other to teleport any player to a registered respawn point, global place, own place (yours),
last death place (their), or close to self.
Can teleport to xyz coordinates or close to another player in conjunction with the "locate" privilege.
* place: Can use /set_place, /remove_place, /reset_places and /reset_all_player_places to manage global places.
* locate: Can use advanced /where command to locate other player and output coordinate, extend /teleport and /teleport_other
to support xyz coordinates and teleporting close to another player.
### Settings
* enable_home_respawn: if enabled (default: disabled), the player can respawn at their home instead of a regular respawn point.
Note: should not be confused with the *sethome* mod's home, the player should have typed the command `set_own_place home`
for this to work.

531
api.lua Normal file
View File

@ -0,0 +1,531 @@
local S = respawn.S
-- Load from storage or config
respawn.load = function()
-- Respawn points
respawn.respawn_points = respawn.load_db( "respawn" )
if respawn.respawn_points == nil then
-- If not found, then try to default to some values
respawn.reset_respawns()
end
-- Server global named places/fine points of view
respawn.places = respawn.load_db( "places" ) or {}
-- Per player named places/fine points of view
respawn.player_places = respawn.load_db( "player_places" ) or {}
-- Per player death
respawn.player_deaths = respawn.load_db( "player_deaths" ) or {}
end
-- Reset respawn to default value
respawn.reset_respawns = function()
respawn.respawn_points = {}
respawn.save_db( "respawn" , respawn.respawn_points )
return true
end
-- data contains pos and look
respawn.set_respawn = function( spawn_id , data )
spawn_id = spawn_id or 1
respawn.respawn_points[ spawn_id ] = data
respawn.save_db( "respawn" , respawn.respawn_points )
return true
end
respawn.reset_places = function()
respawn.places = {}
respawn.save_db( "places" , respawn.places )
return true
end
respawn.set_place = function( place_name , data )
if not place_name or type( place_name ) ~= "string" or place_name == "" then return false end
respawn.places[ place_name ] = data
respawn.save_db( "places" , respawn.places )
return true
end
respawn.remove_place = function( place_name )
if not place_name or type( place_name ) ~= "string" or place_name == "" then return false end
respawn.places[ place_name ] = nil
respawn.save_db( "places" , respawn.places )
return true
end
-- Remove all players' places
respawn.reset_all_players_places = function()
respawn.player_places = {}
respawn.save_db( "player_places" , respawn.player_places )
return true
end
-- Reset personal places for one player only
respawn.reset_player_places = function( player )
if not player then return false end
local player_name = player:get_player_name()
if not player_name then return false end
respawn.player_places[ player_name ] = {}
respawn.save_db( "player_places" , respawn.player_places )
return true
end
respawn.set_player_place = function( player , place_name , data )
if not player then return false end
local player_name = player:get_player_name()
if not player_name then return false end
if not place_name or type( place_name ) ~= "string" or place_name == "" then place_name = "home" end
if not respawn.player_places[ player_name ] then respawn.player_places[ player_name ] = {} end
respawn.player_places[ player_name ][ place_name ] = data
respawn.save_db( "player_places" , respawn.player_places )
return true
end
respawn.remove_player_place = function( player , place_name )
if not player then return false end
local player_name = player:get_player_name()
if not player_name then return false end
if not place_name or type( place_name ) ~= "string" or place_name == "" then return false end
-- Nothing to do
if not respawn.player_places[ player_name ] then return true end
respawn.player_places[ player_name ][ place_name ] = nil
respawn.save_db( "player_places" , respawn.player_places )
return true
end
-- TODO: for instance it just counts how many there are
respawn.output_respawn_points = function( player )
if not player then return false end
local player_name = player:get_player_name()
if player_name == "" then return false end
minetest.chat_send_player( player_name , S("There are @1 respawn points." , #respawn.respawn_points) )
end
respawn.output_places = function( player )
if not player then return false end
local player_name = player:get_player_name()
if player_name == "" then return false end
local places_str = ""
local count = 0
for key , value in pairs( respawn.places ) do
if value.full_name then
places_str = places_str .. "\n " .. value.full_name .. " [" .. key .. "]"
else
places_str = places_str .. "\n [" .. key .. "]"
end
count = count + 1
end
if count == 0 then
minetest.chat_send_player( player_name , S("There is no place defined.") )
else
minetest.chat_send_player( player_name , S("Global places:@1" , places_str ) )
end
end
respawn.output_player_places = function( player )
if not player then return false end
local player_name = player:get_player_name()
if player_name == "" then return false end
if not respawn.player_places[ player_name ] then
minetest.chat_send_player( player_name , S("You have no own place defined.") )
return true
end
local places_str = ""
local count = 0
for key , value in pairs( respawn.player_places[ player_name ] ) do
if value.full_name then
places_str = places_str .. "\n " .. value.full_name .. " [" .. key .. "]"
else
places_str = places_str .. "\n [" .. key .. "]"
end
count = count + 1
end
if count == 0 then
minetest.chat_send_player( player_name , S("You have no own place defined.") )
else
minetest.chat_send_player( player_name , S("Your personal places:@1" , places_str ) )
end
end
respawn.teleport = function( player , point )
if not player or not point then return false end
player:set_pos( point.pos )
if point.look then
player:set_look_horizontal( point.look.h )
player:set_look_vertical( point.look.v )
end
return true
end
respawn.teleport_to_respawn = function( player , spawn_id )
spawn_id = spawn_id or math.random( #respawn.respawn_points )
local point = respawn.respawn_points[ spawn_id ]
if not point then point = respawn.respawn_points[ 1 ] end
return respawn.teleport( player , point )
end
respawn.teleport_to_place = function( player , place_name )
local point = respawn.places[ place_name ]
return respawn.teleport( player , point )
end
respawn.teleport_to_player_place = function( player , place_name )
if not player then return false end
local player_name = player:get_player_name()
if player_name == "" or not respawn.player_places[ player_name ] then return false end
local point = respawn.player_places[ player_name ][ place_name ]
return respawn.teleport( player , point )
end
respawn.teleport_to_other_player_place = function( player , other_player , place_name )
if not player or not other_player then return false end
local other_player_name = other_player:get_player_name()
if other_player_name == "" or not respawn.player_places[ other_player_name ] then return false end
local point = respawn.player_places[ other_player_name ][ place_name ]
return respawn.teleport( player , point )
end
respawn.teleport_to_other_player = function( player , other_player )
if not player or not other_player then return false end
local pos = other_player:get_pos()
-- Avoid to invade one's personal place ^^
pos.x = pos.x + math.random( -2 , 2 )
pos.y = pos.y + math.random( 0 , 1 )
pos.z = pos.z + math.random( -2 , 2 )
return respawn.teleport( player , { pos = pos } )
end
respawn.teleport_to_player_last_death_place = function( player )
if not player then return false end
local player_name = player:get_player_name()
if player_name == "" or not respawn.player_deaths[ player_name ] or #respawn.player_deaths[ player_name ] == 0 then return false end
local point = respawn.player_deaths[ player_name ][ #respawn.player_deaths[ player_name ] ] ;
return respawn.teleport( player , point )
end
respawn.teleport_delay = function( player , type , id , delay )
minetest.after( delay , function()
if type == "respawn" then
respawn.teleport_to_respawn( player , id )
elseif type == "place" then
respawn.teleport_to_place( player , id )
elseif type == "player_place" then
respawn.teleport_to_player_place( player , id )
elseif type == "last_death" then
respawn.teleport_to_player_last_death_place( player )
end
end )
end
-- a and b are position
function squared_distance( a , b )
return ( a.x - b.x ) * ( a.x - b.x ) + ( a.y - b.y ) * ( a.y - b.y ) + ( a.z - b.z ) * ( a.z - b.z )
end
respawn.closest_thing = function( list , pos , max_dist , max_squared_dist )
if not max_dist and not max_squared_dist then max_dist = 64000 end
if not max_squared_dist then max_squared_dist = max_dist * max_dist end
local closest_squared_dist = max_squared_dist
local closest_place
local closest_place_name
local squared_dist
for place_name , place in pairs( list ) do
squared_dist = squared_distance( pos , place.pos )
if squared_dist < closest_squared_dist then
closest_squared_dist = squared_dist
closest_place = place
closest_place_name = place_name
end
end
return closest_place_name , closest_place , closest_squared_dist
end
respawn.closest_respawn = function( pos , max_dist , max_squared_dist )
return respawn.closest_thing( respawn.respawn_points , pos , max_dist , max_squared_dist )
end
respawn.closest_place = function( pos , max_dist , max_squared_dist )
return respawn.closest_thing( respawn.places , pos , max_dist , max_squared_dist )
end
respawn.closest_player_place = function( player_name , pos , max_dist , max_squared_dist )
if respawn.player_places[ player_name ] then
return respawn.closest_thing( respawn.player_places[ player_name ] , pos , max_dist , max_squared_dist )
end
end
respawn.closest_place_or_player_place = function( player_name , pos , max_dist )
local place_name , place , square_dist , place_name2 , place2 , square_dist2
-- Use the chat player for player place, it makes more sense
place_name , place , square_dist = respawn.closest_player_place( player_name , pos , max_dist )
if place_name then
place_name2 , place2 , square_dist2 = respawn.closest_place( pos , nil , square_dist )
if place_name2 then
return place_name2 , place2 , square_dist2
else
return place_name , place , square_dist
end
else
return respawn.closest_place( pos , max_dist )
end
end
respawn.respawn = function( player )
-- We use a delay because returning true has no effect despite what the doc tells
-- so we teleport after the regular spawn
if minetest.settings:get_bool("enable_home_respawn") then
local player_name = player:get_player_name()
if player_name and respawn.player_places[ player_name ] and respawn.player_places[ player_name ].home then
respawn.teleport_delay( player , "player_place" , "home" , 0 )
return true
end
end
if #respawn.respawn_points > 0 then
respawn.teleport_delay( player , "respawn" , math.random( #respawn.respawn_points ) , 0 )
return true
end
-- If no respawn points defined, let the default behavior kick in... then add the actual default spawn to our list!
minetest.after( 0.5 , function()
local pos = player:get_pos()
-- Check if there is still no respawn point and if the player is still available
if #respawn.respawn_points > 0 or not pos then return end
respawn.set_respawn( 1 , {
pos = pos ,
look = { h = player:get_look_horizontal() , v = player:get_look_vertical() }
} )
end )
end
respawn.add_death_log = function( player , data )
if not player then return false end
local player_name = player:get_player_name()
if not player_name then return false end
if not respawn.player_deaths[ player_name ] then respawn.player_deaths[ player_name ] = {} end
table.insert( respawn.player_deaths[ player_name ] , data )
respawn.save_db( "player_deaths" , respawn.player_deaths )
return true
end
local function message_node_name( node_name )
node_name = node_name:gsub( "[^:]+:" , "" )
node_name = node_name:gsub( "_" , " " )
return node_name
end
local function message_biome_name( biome_name )
biome_name = biome_name:gsub( "[^:]+:" , "" )
biome_name = biome_name:gsub( "_" , " " )
return biome_name
end
respawn.death_message = function( player_name , data )
local place = S("at some unknown place")
if data.place and data.place ~= "" and type( data.place ) == "string" then
place = "near " .. message_biome_name( data.place )
elseif data.biome and data.biome ~= "" and type( data.biome ) == "string" then
place = "near " .. message_biome_name( data.biome )
end
if data.by_type == "player" then
if data.using and data.using ~= "" and type( data.using ) == "string" then
return S("@1 was killed by @2, using @3, @4." , player_name , data.by , message_node_name( data.using ) , place )
else
return S("@1 was killed by @2 @3." , player_name , data.by , place )
end
elseif data.by_type == "entity" then
-- For instance there is no difference between player and entity death messages
-- Also it's worth noting that we need to use message_node_name() because sometime we got an entity type as name (e.g. mobs_xxx:mob_type)
if data.using and data.using ~= "" and type( data.using ) == "string" then
return S("@1 was killed by @2, using @3, @4." , player_name , message_node_name( data.by ) , message_node_name( data.using ) , place )
else
return S("@1 was killed by @2 @3." , player_name , message_node_name( data.by ) , place )
end
elseif data.by_type == "fall" then
return S("@1 has fallen @2." , player_name , place )
elseif data.by_type == "drown" then
if data.by and data.by ~= "" and type( data.by ) == "string" then
return S("@1 has drown in @2, @3." , player_name , message_node_name( data.by ) , place )
else
return S("@1 has drown @2." , player_name , place )
end
elseif data.by_type == "node" then
if data.by and data.by ~= "" and type( data.by ) == "string" then
return S("@1 should not play with @2, @3." , player_name , message_node_name( data.by ) , place )
else
return S("@1 should not play with dangerous things @2." , player_name , place )
end
end
return S("@1 was killed @2." , player_name , place )
end
respawn.death = function( player , data )
if not player then return false end
local player_name = player:get_player_name()
if not player_name then return false end
local pos = player:get_pos()
data.pos = pos
local place_name , place = respawn.closest_place( pos , 80 )
if place then
data.place = place.full_name or place_name
end
local biome_data = minetest.get_biome_data( pos )
if biome_data then
data.biome = minetest.get_biome_name( biome_data.biome )
end
respawn.add_death_log( player , data )
minetest.chat_send_all( respawn.death_message( player_name , data ) )
return true
end
respawn.output_deaths = function( chat_player , player_name )
local chat_player_name = chat_player:get_player_name()
if chat_player_name == "" then return false end
if not player_name then player_name = chat_player_name end
if not respawn.player_deaths[ player_name ] then
minetest.chat_send_player( chat_player_name , S("@1 hasn't died already.", player_name ) )
return true
end
local deaths_str = ""
local count = 0
for key , value in pairs( respawn.player_deaths[ player_name ] ) do
deaths_str = deaths_str .. "\n " .. key .. ": " .. respawn.death_message( player_name , value )
count = count + 1
end
if count == 0 then
minetest.chat_send_player( chat_player_name , S("@1 hasn't died already.") )
else
minetest.chat_send_player( chat_player_name , S("@1 has died @2 times: @3" , player_name , count , deaths_str ) )
end
end

570
commands.lua Normal file
View File

@ -0,0 +1,570 @@
local S = respawn.S
minetest.register_privilege( "teleport", {
description = S("Can use /teleport to self teleport to a registered respawn point, global place, own place, last death place. Can teleport to xyz coordinates or close to another player in conjunction with the \"locate\" privilege.") ,
give_to_singleplayer = false
} )
minetest.register_privilege( "teleport_other", {
description = S("Can use /teleport_other to teleport any player to a registered respawn point, global place, own place (yours), last death place (their), or close to self. Can teleport to xyz coordinates or close to another player in conjunction with the \"locate\" privilege.") ,
give_to_singleplayer = false
} )
minetest.register_privilege( "place", {
description = S("Can use /set_place, /remove_place, /reset_places and /reset_all_player_places to manage global places.") ,
give_to_singleplayer = false
} )
minetest.register_privilege( "locate", {
description = S("Can use advanced /where command to locate other player and output coordinate, extend /teleport and /teleport_other to support xyz coordinates and teleporting close to another player.") ,
give_to_singleplayer = false
} )
-- Join an array of string
function join( tab , delimiter , first , last )
if not delimiter then delimiter = "" end
if not first then first = 1 end
if not last then last = #tab end
local str = tab[ first ] or ""
for i = first + 1, last , 1 do
str = str .. delimiter .. tab[ i ]
end
return str
end
minetest.register_chatcommand( "list_respawns", {
description = S("List all respawn points."),
func = function( player_name , param )
local player = minetest.get_player_by_name( player_name )
if not player then
return false, S("Player not found!")
end
return respawn.output_respawn_points( player )
end
} )
minetest.register_chatcommand( "reset_respawns", {
description = S("Reset respawn points."),
privs = { server = true },
func = function( player_name , param )
if respawn.reset_respawns() then
return true, S("Respawn points reset." )
end
return false, S("Something went wrong...")
end
} )
minetest.register_chatcommand( "set_respawn", {
description = S("Create a respawn point on your current player position. Without argument it set the first respawn point, with 'new' it appends a new respawn point."),
params = S("[<spawn number>|new]"),
privs = { server = true },
func = function( player_name , param )
local player = minetest.get_player_by_name( player_name )
if not player then
return false, S("Player not found!")
end
local parts = string.split( param , " " )
local spawn_id
if parts[1] == "new" then
spawn_id = #respawn.respawn_points + 1
else
spawn_id = tonumber( parts[1] ) or 1
end
if respawn.set_respawn( spawn_id , {
pos = player:get_pos() ,
look = { h = player:get_look_horizontal() , v = player:get_look_vertical() }
} ) then
return true, S("Respawn point @1 set!", spawn_id)
end
return false, S("Something went wrong...")
end
} )
minetest.register_chatcommand( "list_places", {
description = S("List all global places."),
func = function( player_name , param )
local player = minetest.get_player_by_name( player_name )
if not player then
return false, S("Player not found!")
end
return respawn.output_places( player )
end
} )
minetest.register_chatcommand( "reset_places", {
description = S("Reset all global places."),
privs = { server = true , place = true },
func = function( player_name , param )
if respawn.reset_places() then
return true, S("All global places removed." )
end
return false, S("Something went wrong...")
end
} )
minetest.register_chatcommand( "set_place", {
description = S("Create a new global named place on your current player position."),
params = S("<place name ID> [<place full name with spaces>]"),
privs = { place = true },
func = function( player_name , param )
local player = minetest.get_player_by_name( player_name )
if not player then
return false, S("Player not found!")
end
local parts = string.split( param , " " )
local place_name = parts[1]
local full_name = join( parts , " " , 2 )
if full_name == "" then full_name = nil end
if not place_name or place_name == "" then
return false, S("Missing place name!")
end
if respawn.set_place( place_name , {
pos = player:get_pos() ,
look = { h = player:get_look_horizontal() , v = player:get_look_vertical() } ,
full_name = full_name
} ) then
return true, S("Place \"@1\" set!", full_name or place_name )
end
return false, S("Something went wrong...")
end
} )
minetest.register_chatcommand( "remove_place", {
description = S("Remove a global place."),
params = S("<place name>"),
privs = { place = true },
func = function( player_name , param )
local parts = string.split( param , " " )
local place_name = parts[1]
if not place_name or place_name == "" then
return false, S("Missing place name!")
end
if respawn.remove_place( place_name ) then
return true, S("Place removed.")
end
return false, S("Something went wrong...")
end
} )
minetest.register_chatcommand( "list_own_places", {
description = S("List all personal places."),
func = function( player_name , param )
local player = minetest.get_player_by_name( player_name )
if not player then
return false, S("Player not found!")
end
return respawn.output_player_places( player )
end
} )
minetest.register_chatcommand( "reset_all_players_places", {
description = S("Reset all players' places."),
privs = { server = true , place = true },
func = function( player_name , param )
if respawn.reset_all_players_places() then
return true, S("All players' places removed." )
end
return false, S("Something went wrong...")
end
} )
minetest.register_chatcommand( "reset_own_places", {
description = S("Reset your personal places."),
func = function( player_name , param )
local player = minetest.get_player_by_name( player_name )
if not player then
return false, S("Player not found!")
end
if respawn.reset_player_places( player ) then
return true, S("All your personal places removed." )
end
return false, S("Something went wrong...")
end
} )
minetest.register_chatcommand( "set_own_place", {
description = S("Create a new personal named place on your current player position. Without argument, it set your home."),
params = S("<place name ID> [<place full name with spaces>]"),
func = function( player_name , param )
local player = minetest.get_player_by_name( player_name )
if not player then
return false, S("Player not found!")
end
local parts = string.split( param , " " )
local place_name = parts[1]
local full_name = join( parts , " " , 2 )
if full_name == "" then full_name = nil end
if not place_name or place_name == "" then
place_name = "home"
end
if respawn.set_player_place( player , place_name , {
pos = player:get_pos() ,
look = { h = player:get_look_horizontal() , v = player:get_look_vertical() } ,
full_name = full_name
} ) then
return true, S("Personal place \"@1\"set!", full_name or place_name )
end
return false, S("Something went wrong...")
end
} )
minetest.register_chatcommand( "remove_own_place", {
description = S("Remove a personal place."),
params = S("<place name>"),
func = function( player_name , param )
local player = minetest.get_player_by_name( player_name )
if not player then
return false, S("Player not found!")
end
local parts = string.split( param , " " )
local place_name = parts[1]
if not place_name or place_name == "" then
return false, S("Missing place!")
end
if respawn.remove_player_place( player , place_name ) then
return true, S("Personal place removed.")
end
return false, S("Something went wrong...")
end
} )
minetest.register_chatcommand( "where", {
description = S("Find the place where you are, if there is one close enough. If you have the \"locate\" privilege you can also see coordinate and other people location."),
params = S("[<player>]"),
func = function( chat_player_name , param )
local parts = string.split( param , " " )
local player_name = parts[1] or chat_player_name
local player = minetest.get_player_by_name( player_name )
if not player or not chat_player_name then
return false, S("Player not found!")
end
local has_locate = minetest.check_player_privs( chat_player_name, { locate = true } )
if not has_locate and player_name ~= chat_player_name then
return false, S("You can't locate other player (missing the \"locate\" privilege)!")
end
local pos = player:get_pos()
local max_dist = 80
-- Use the chat player for player place, it makes more sense
place_name , place = respawn.closest_place_or_player_place( chat_player_name , pos , max_dist )
if place_name then
if has_locate then
return true, S("@1 is near @2 (@3, @4, @5).", player_name, place.full_name or place_name, pos.x , pos.y, pos.z)
end
return true, S("@1 is near @2.", player_name, place.full_name or place_name)
end
if has_locate then
return true, S("@1 is at (@2, @3, @4).", player_name, pos.x , pos.y, pos.z)
end
return false, S("No place found near you.")
end
} )
minetest.register_chatcommand( "teleport", {
description = S("Teleport to a map respawn point, a place or more. First argument can be respawn/spawn, place/global, own_place/own/home, death (for last death place), xyz (for coordinates), player (teleport close to another player), if omitted it searches for own place first, then for global place. Last argument can be avoided: for respawn it would move to a random respawn point, for own place it would go to the home."),
params = S("<type> [<ID>] | xyz <x> <y> <z>"),
privs = { teleport = true },
func = function( player_name , param )
local player = minetest.get_player_by_name( player_name )
if not player then
return false, S("Player not found!")
end
local parts = string.split( param , " " )
local type = parts[1] or nil
if not type then
return false, S("Missing type!")
end
local id = parts[2] or nil
if type == "respawn" or type =="spawn" then
if respawn.teleport_to_respawn( player , tonumber( id ) , true ) then
if id then
return true, S("Teleported to the respawn n°@1.", id)
else
return true, S("Teleported to a random respawn point.")
end
end
elseif type == "place" or type == "global" then
if respawn.teleport_to_place( player , id ) then
return true, S("Teleported to @1.", respawn.places[ id ].full_name or id)
end
elseif type == "own" or type == "own_place" then
if respawn.teleport_to_player_place( player , id ) then
return true, S("Teleported to @1.", respawn.player_places[ player_name ][ id ].full_name or id)
end
elseif type == "death" then
if respawn.teleport_to_player_last_death_place( player ) then
return true, S("Teleported to last death place.")
end
elseif type == "xyz" then
if not minetest.check_player_privs( player_name, { locate = true } ) then
return false, S("You can't teleport to coordinate (missing the \"locate\" privilege)!")
end
if #parts < 4 then
return false, S("Missing x y z arguments")
end
local pos = {
x = tonumber( parts[2] ) ,
y = tonumber( parts[3] ) ,
z = tonumber( parts[4] )
}
if pos.x and pos.y and pos.z then
if respawn.teleport( player , { pos = pos } ) then
return true, S("Teleported to (@1, @2, @3).", pos.x , pos.y , pos.z)
end
end
elseif type == "player" then
if not minetest.check_player_privs( player_name, { locate = true } ) then
return false, S("You can't teleport to a player (missing the \"locate\" privilege)!")
end
if not id then
return false, S("Missing the other player name argument")
end
local other_player = minetest.get_player_by_name( id )
if not other_player then
return false, S("Player \"@1\" not found!", id)
end
if respawn.teleport_to_other_player( player , other_player ) then
return true, S("Teleported close to @1.", id)
end
elseif respawn.teleport_to_player_place( player , type ) then
return true, S("Teleported to @1.", respawn.player_places[ player_name ][ type ].full_name or type)
elseif respawn.teleport_to_place( player , type ) then
return true, S("Teleported to @1.", respawn.places[ type ].full_name or type)
end
return false, S("Respawn point or place not found!")
end
} )
-- Mostly a copy/paste of /teleport command
minetest.register_chatcommand( "teleport_other", {
description = S("Teleport another player to a map respawn point, a place or more. First argument is the player name, second argument can be respawn/spawn, place/global, death (for last death place), xyz (for coordinates), player (teleport close to another player), here (teleport close to you), if omitted it will search for global place. Last argument can be avoided: for respawn it would move to a random respawn point."),
params = S("<player> [<type>] [<ID>] | <player> xyz <x> <y> <z>"),
privs = { teleport = true , teleport_other = true },
func = function( performer_name , param )
local performer = minetest.get_player_by_name( performer_name )
local parts = string.split( param , " " )
local player_name = parts[1] or nil
local player = minetest.get_player_by_name( player_name )
if not player then
return false, S("Player not found!")
end
local type = parts[2] or nil
if not type then
return false, S("Missing type!")
end
local id = parts[3] or nil
if type == "respawn" or type =="spawn" then
if respawn.teleport_to_respawn( player , tonumber( id ) , true ) then
if id then
minetest.chat_send_all( S("@1 teleported @2 to the respawn n°@3.", performer_name , player_name , id) )
return true
else
minetest.chat_send_all( S("@1 teleported @2 to a random respawn point.", performer_name , player_name) )
return true
end
end
elseif type == "place" or type == "global" then
if respawn.teleport_to_place( player , id ) then
minetest.chat_send_all( S("@1 teleported @2 to @3.", performer_name , player_name , respawn.places[ id ].full_name or id) )
return true
end
elseif type == "own" or type == "own_place" then
if not performer then
return false, S("Player not found!")
end
if respawn.teleport_to_other_player_place( player , performer , id ) then
minetest.chat_send_all( S("@1 teleported @2 to @3.", performer_name , player_name , respawn.player_places[ performer_name ][ id ].full_name or id) )
return true
end
elseif type == "death" then
if respawn.teleport_to_player_last_death_place( player ) then
minetest.chat_send_all( S("@1 teleported @2 to the last death place.", performer_name , player_name ) )
return true
end
elseif type == "xyz" then
if not minetest.check_player_privs( performer_name, { locate = true } ) then
return false, S("You can't teleport other to coordinate (missing the \"locate\" privilege)!")
end
if #parts < 5 then
return false, S("Missing x y z arguments")
end
local pos = {
x = tonumber( parts[3] ) ,
y = tonumber( parts[4] ) ,
z = tonumber( parts[5] )
}
if pos.x and pos.y and pos.z then
if respawn.teleport( player , { pos = pos } ) then
minetest.chat_send_all( S("@1 teleported @2 to (@3, @4, @5).", performer_name , player_name , pos.x , pos.y , pos.z ) )
return true
end
end
elseif type == "player" then
if not minetest.check_player_privs( performer_name, { locate = true } ) then
return false, S("You can't teleport to a player (missing the \"locate\" privilege)!")
end
if not id then
return false, S("Missing the other player name argument")
end
local other_player = minetest.get_player_by_name( id )
if not other_player then
return false, S("Player \"@1\" not found!", id)
end
if respawn.teleport_to_other_player( player , other_player ) then
minetest.chat_send_all( S("@1 teleported @2 close to @3.", performer_name , player_name , id ) )
return true
end
elseif type == "here" then
if not performer then
return false, S("Player not found!")
end
if respawn.teleport_to_other_player( player , performer ) then
minetest.chat_send_all( S("@1 teleported @2 close to @3.", performer_name , player_name , performer_name ) )
return true
end
elseif respawn.teleport_to_other_player_place( player , performer , type ) then
minetest.chat_send_all( S("@1 teleported @2 to @3.", performer_name , player_name , respawn.player_places[ performer_name ][ type ].full_name or type) )
return true
elseif respawn.teleport_to_place( player , type ) then
minetest.chat_send_all( S("@1 teleported @2 to @3.", performer_name , player_name , respawn.places[ type ].full_name or type) )
return true
end
return false, S("Respawn point or place not found!")
end
} )
minetest.register_chatcommand( "list_deaths", {
description = S("List all deaths of a player. Without argument it applies to the current player."),
params = S("[<player name>]"),
func = function( chat_player_name , param )
local chat_player = minetest.get_player_by_name( chat_player_name )
if not chat_player then
return false, S("Player (chat) not found!")
end
local parts = string.split( param , " " )
local player_name = parts[1] or nil
return respawn.output_deaths( chat_player , player_name )
end
} )

1
depends.txt Normal file
View File

@ -0,0 +1 @@
default

158
init.lua Normal file
View File

@ -0,0 +1,158 @@
-- Global respawn namespace
respawn = {}
respawn.path = minetest.get_modpath( minetest.get_current_modname() )
respawn.S = minetest.get_translator( "respawn" )
-- Load files
dofile( respawn.path .. "/storage.lua" )
dofile( respawn.path .. "/api.lua" )
dofile( respawn.path .. "/commands.lua" )
respawn.load()
minetest.register_on_respawnplayer( function( player )
respawn.respawn( player )
-- returning true has no effect despite what the doc tells
return true
end )
minetest.register_on_newplayer( function( player )
respawn.respawn( player )
-- returning true has no effect despite what the doc tells
return true
end )
minetest.register_on_punchplayer( function( player, hitter, time_from_last_punch, tool_capabilities, dir, damage )
local hp = player:get_hp()
if hp <= 0 or hp - damage > 0 or not hitter then return end
if hitter:is_player() then
respawn.death( player , {
by_type = "player" ,
by = hitter:get_player_name() ,
using = hitter:get_wielded_item():get_name() ,
damage = damage
} )
else
local properties = hitter:get_properties()
local luaEntity = hitter:get_luaentity()
--[[ Debug to find some hidden names
minetest.chat_send_all( "----- debug hitter's properties -----" )
for k,v in pairs( properties ) do
minetest.chat_send_all( "" .. k .. ": " .. tostring( v ) )
end
minetest.chat_send_all( "----- debug hitter's nametag attributes -----" )
for k,v in pairs( hitter:get_nametag_attributes() ) do
minetest.chat_send_all( "" .. k .. ": " .. tostring( v ) )
end
minetest.chat_send_all( "----- debug hitter:get_luaentity() -----" )
for k,v in pairs( hitter:get_luaentity() ) do
minetest.chat_send_all( "" .. k .. ": " .. tostring( v ) )
end
minetest.chat_send_all( "----- debug hitter:get_luaentity()'s meta table -----" )
for k,v in pairs( getmetatable( hitter:get_luaentity() ) ) do
minetest.chat_send_all( "" .. k .. ": " .. tostring( v ) )
end
minetest.chat_send_all( "----- debug hitter:get_luaentity()'s meta table's __index -----" )
for k,v in pairs( getmetatable( hitter:get_luaentity() ).__index ) do
minetest.chat_send_all( "" .. k .. ": " .. tostring( v ) )
end
minetest.chat_send_all( "----- debug hitter.name -----" .. ( hitter.name or "(none)" ) )
minetest.chat_send_all( "----- debug hitter.nametag -----" .. ( hitter.nametag or "(none)" ) )
minetest.chat_send_all( "----- debug hitter.nametag2 -----" .. ( hitter.nametag2 or "(none)" ) )
minetest.chat_send_all( "----- debug hitter:get_luaentity().name -----" .. ( hitter:get_luaentity().name or "(none)" ) )
minetest.chat_send_all( "----- debug hitter:get_luaentity().nametag -----" .. ( hitter:get_luaentity().nametag or "(none)" ) )
minetest.chat_send_all( "----- debug hitter:get_luaentity().nametag2 -----" .. ( hitter:get_luaentity().nametag2 or "(none)" ) )
minetest.chat_send_all( "----- debug properties.name -----" .. ( properties.name or "(none)" ) )
minetest.chat_send_all( "----- debug properties.nametag -----" .. ( properties.nametag or "(none)" ) )
minetest.chat_send_all( "----- debug properties.nametag2 -----" .. ( properties.nametag2 or "(none)" ) )
--]]
local name
-- mobs_humans uses that, not sure how standard it is
if luaEntity.given_name and luaEntity.given_name ~= "" and type( luaEntity.given_name ) == "string" then name = luaEntity.given_name
-- aliveai uses that, not sure how standard it is
elseif luaEntity.botname and luaEntity.botname ~= "" and type( luaEntity.botname ) == "string" then name = luaEntity.botname
-- Never seen it set, but seems to me a good way to set an entity proper name
elseif properties.name and properties.name ~= "" and type( properties.name ) == "string" then name = properties.name
-- nametag2 is set by mobs_redo as a backup when changing nametag to display health, so it's reliable but rarely there
elseif properties.nametag2 and properties.nametag2 ~= "" and type( properties.nametag2 ) == "string" then name = properties.nametag2
-- nametag is not reliable, can be just the health display
--elseif properties.nametag and properties.nametag ~= "" and type( properties.nametag ) == "string" then name = properties.nametag
-- Usually, this is the generic name of the entity (its kind) rather than its proper name
else name = luaEntity.name
end
respawn.death( player , {
by_type = "entity" ,
by = name ,
using = hitter:get_wielded_item():get_name() ,
damage = damage
} )
end
end )
minetest.register_on_player_hpchange( function( player, hp_change, reason )
local hp = player:get_hp()
local by_type
local by
if hp <= 0 or hp + hp_change > 0 then return end
local pos = player:get_pos()
if reason.type == "fall" then
by_type = "fall"
elseif reason.type=="drown" then
by_type = "drown"
local eye_pos = vector.add( { x = 0, z = 0, y = player:get_properties().eye_height } , pos )
by = minetest.get_node( eye_pos ).name
elseif reason.type == "node_damage" then
-- from deathlist mod
by_type = "node"
local eye_pos = vector.add( { x = 0, z = 0, y = player:get_properties().eye_height } , pos )
local killing_node_head_name = minetest.get_node( eye_pos ).name
local killing_node_head = minetest.registered_nodes[ killing_node_head_name ]
local killing_node_feet_name = minetest.get_node( pos ).name
local killing_node_feet = minetest.registered_nodes[ killing_node_feet_name ]
by = killing_node_feet_name
if ( killing_node_head.node_damage or 0 ) > ( killing_node_feet.node_damage or 0 ) then
by = killing_node_head_name
end
elseif reason.type == "punch" then
-- do nothing, it should already be done by minetest.register_on_punchplayer()
--by_type = "punch"
return
elseif reason.type == "set_hp" then
-- maybe /killme
by_type = "unknown"
elseif reason.type == "respawn" then
-- Usually, we don't get there because respawn give hp rather than removing them
return
else
by_type = "unknown"
end
respawn.death( player , {
by_type = by_type ,
by = by ,
damage = - hp_change
} )
end )

1
locale/respawn.fr.tr Normal file
View File

@ -0,0 +1 @@
# textdomain: respawn

1
locale/template.txt Normal file
View File

@ -0,0 +1 @@
# textdomain: respawn

6
mod.conf Normal file
View File

@ -0,0 +1,6 @@
name = respawn
description = Manage respawn points, interesting places, teleportation and death records
release = 1
author = cronvel
title = Respawn
depends = default

BIN
screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

3
settingtypes.txt Normal file
View File

@ -0,0 +1,3 @@
# When enabled, players can respawn in their home place, if they defined one
# using the command: /set_own_place home
enable_home_respawn (Enable home respawn) bool false

62
storage.lua Normal file
View File

@ -0,0 +1,62 @@
-- Maybe those storage helpers should have their own mod?
local storage_base_path = minetest.get_worldpath() .. "/"
local storage_ext = "." .. minetest.get_current_modname() .. ".db"
storage_files = {}
respawn.load_db = function( key )
local file = storage_files[ key ]
if file then
file:seek( "set" , 0 )
else
local file_path = storage_base_path .. key .. storage_ext
file = io.open( file_path , "r+" )
if not file then
return nil
end
storage_files[ key ] = file
end
-- We only load the first line, after that line, there is garbage, see .save_db() comment.
local str = file:read( "*l" )
local value
if str and str ~= "" then
value = minetest.parse_json( str )
if value then
return value
end
end
return nil
end
respawn.save_db = function( key , value )
local file = storage_files[ key ]
if file then
file:seek( "set" , 0 )
else
local file_path = storage_base_path .. key .. storage_ext
file = io.open( file_path , "w+" )
if not file then
error( "Can't create file: " .. file_path )
end
storage_files[ key ] = file
end
-- We can't truncate files in Lua, and that sucks big time...
-- So to improve perf and not opening/closing/overwriting files everytime something needs to be written we use this trick:
-- we add a new line after writing JSON.
-- When loading, we only load the first line.
file:write( minetest.write_json( value ) , "\n" )
end