2017-06-13 13:45:42 +02:00
-- compass configuration interface - adjustable from other mods or minetest.conf settings
2020-02-01 22:53:29 -07:00
death_compass = { }
2017-04-11 00:18:11 +02:00
2020-02-01 22:53:29 -07:00
local S = minetest.get_translator ( " death_compass " )
-- how many seconds does the death compass work for? 0 for indefinite
local duration = tonumber ( minetest.settings : get ( " death_compass_duration " ) ) or 0
2020-02-02 15:05:40 -07:00
local automatic = minetest.settings : get_bool ( " death_compass_automatic " , false )
2017-04-11 22:49:36 +02:00
2020-02-01 22:53:29 -07:00
local range_to_inactivate = 5
2017-06-13 13:45:42 +02:00
2020-02-05 11:29:00 -07:00
local hud_position = {
x = tonumber ( minetest.settings : get ( " death_compass_hud_x " ) ) or 0.5 ,
y = tonumber ( minetest.settings : get ( " death_compass_hud_y " ) ) or 0.9 ,
}
local hud_color = tonumber ( " 0x " .. ( minetest.settings : get ( " death_compass_hud_color " ) or " FFFF00 " ) ) or 0xFFFF00
2020-02-02 19:49:02 -07:00
-- If round is true the return string will only have the two largest-scale values
local function clock_string ( seconds , round )
seconds = math.floor ( seconds )
local days = math.floor ( seconds / 86400 )
seconds = seconds - days * 86400
local hours = math.floor ( seconds / 3600 )
seconds = seconds - hours * 3600
local minutes = math.floor ( seconds / 60 )
seconds = seconds - minutes * 60
local ret = { }
if days == 1 then
table.insert ( ret , S ( " 1 day " ) )
elseif days > 1 then
table.insert ( ret , S ( " @1 days " , days ) )
end
if hours == 1 then
table.insert ( ret , S ( " 1 hour " ) )
elseif hours > 1 then
table.insert ( ret , S ( " @1 hours " , hours ) )
end
if minutes == 1 then
table.insert ( ret , S ( " 1 minute " ) )
elseif minutes > 1 then
table.insert ( ret , S ( " @1 minutes " , minutes ) )
end
if seconds == 1 then
table.insert ( ret , S ( " 1 second " ) )
elseif seconds > 1 then
table.insert ( ret , S ( " @1 seconds " , seconds ) )
end
if # ret == 0 then
return S ( " @1 seconds " , 0 )
end
if # ret == 1 then
return ret [ 1 ]
end
if round or # ret == 2 then
return S ( " @1 and @2 " , ret [ 1 ] , ret [ 2 ] )
end
return table.concat ( ret , S ( " , " ) )
end
local documentation = S ( " This does nothing in its current inert state. If you have this in your inventory when you die, however, it will follow you into your next life's inventory and point toward the location of your previous life's end. " )
2020-02-02 17:53:19 -07:00
local durationdesc
if duration > 0 then
2020-02-02 19:49:02 -07:00
durationdesc = S ( " The Death Compass' guidance will only last for @1 after death. " , clock_string ( duration , false ) )
2020-02-02 17:53:19 -07:00
else
2020-02-02 19:49:02 -07:00
durationdesc = S ( " The Death Compass will point toward your corpse until you find it. " )
2020-02-02 17:53:19 -07:00
end
2017-06-15 01:24:52 +02:00
-- set a position to the compass stack
2020-02-02 19:49:02 -07:00
local function set_target ( stack , pos , name )
2017-06-15 01:24:52 +02:00
local meta = stack : get_meta ( )
2020-02-01 22:53:29 -07:00
meta : set_string ( " target_pos " , minetest.pos_to_string ( pos ) )
meta : set_string ( " target_corpse " , name )
meta : set_int ( " time_of_death " , minetest.get_gametime ( ) )
2017-06-15 01:24:52 +02:00
end
2017-06-13 13:45:42 +02:00
-- Get compass target
2017-04-11 22:49:36 +02:00
local function get_destination ( player , stack )
2017-06-13 13:45:42 +02:00
local posstring = stack : get_meta ( ) : get_string ( " target_pos " )
if posstring ~= " " then
return minetest.string_to_pos ( posstring )
2017-04-11 00:18:11 +02:00
end
end
2020-02-01 22:53:29 -07:00
-- looped ticking sound if there's a duration on this
local player_ticking = { }
local function start_ticking ( player_name )
if not player_ticking [ player_name ] then
player_ticking [ player_name ] = minetest.sound_play ( " death_compass_tick_tock " ,
{ to_player = player_name , gain = 0.125 , loop = true } )
2018-02-17 09:08:36 +01:00
end
2020-02-01 22:53:29 -07:00
end
local function stop_ticking ( player_name )
local tick_tock_handle = player_ticking [ player_name ]
if tick_tock_handle then
minetest.sound_stop ( tick_tock_handle )
player_ticking [ player_name ] = nil
2018-02-17 09:08:36 +01:00
end
2018-02-14 20:11:14 +01:00
end
2020-02-05 11:29:00 -07:00
local player_huds = { }
local function hide_hud ( player , player_name )
local id = player_huds [ player_name ]
if id then
player : hud_remove ( id )
player_huds [ player_name ] = nil
end
end
local function update_hud ( player , player_name , compass )
local metadata = compass : get_meta ( )
local target_pos = minetest.string_to_pos ( metadata : get_string ( " target_pos " ) )
local player_pos = player : get_pos ( )
local distance = vector.distance ( player_pos , target_pos )
if not target_pos then
return
end
local time_of_death = metadata : get_int ( " time_of_death " )
local target_name = metadata : get_string ( " target_corpse " )
local description
if duration > 0 then
local remaining = time_of_death + duration - minetest.get_gametime ( )
if remaining < 0 then
return
end
description = S ( " @1m to @2's corpse, @3 remaining " , math.floor ( distance ) ,
target_name , clock_string ( remaining , true ) )
else
description = S ( " @1m to @2's corpse, died @3 ago " , math.floor ( distance ) ,
target_name , clock_string ( minetest.get_gametime ( ) - time_of_death , true ) )
end
local id = player_huds [ player_name ]
if not id then
id = player : hud_add ( {
hud_elem_type = " text " ,
position = hud_position ,
text = description ,
number = hud_color ,
scale = 20 ,
} )
player_huds [ player_name ] = id
else
player : hud_change ( id , " text " , description )
end
end
2020-02-01 22:53:29 -07:00
-- get right image number for players compass
2017-04-11 22:49:36 +02:00
local function get_compass_stack ( player , stack )
2017-06-13 13:45:42 +02:00
local target = get_destination ( player , stack )
2020-02-02 15:05:40 -07:00
local inactive_return
if automatic then
inactive_return = ItemStack ( " " )
else
inactive_return = ItemStack ( " death_compass:inactive " )
end
2020-02-01 22:53:29 -07:00
if not target then
2020-02-02 15:05:40 -07:00
return inactive_return
2020-02-01 22:53:29 -07:00
end
local pos = player : get_pos ( )
2020-02-05 11:29:00 -07:00
local distance = vector.distance ( pos , target )
2020-02-01 22:53:29 -07:00
local player_name = player : get_player_name ( )
2020-02-05 11:29:00 -07:00
if distance < range_to_inactivate then
2020-02-01 22:53:29 -07:00
stop_ticking ( player_name )
minetest.sound_play ( " death_compass_bone_crunch " , { to_player = player_name , gain = 1.0 } )
2020-02-02 15:05:40 -07:00
return inactive_return
2020-02-01 22:53:29 -07:00
end
2019-03-27 14:20:20 +01:00
local dir = player : get_look_horizontal ( )
2017-06-13 13:45:42 +02:00
local angle_north = math.deg ( math.atan2 ( target.x - pos.x , target.z - pos.z ) )
2017-04-11 00:18:11 +02:00
if angle_north < 0 then
angle_north = angle_north + 360
end
2019-03-27 14:20:20 +01:00
local angle_dir = math.deg ( dir )
local angle_relative = ( angle_north + angle_dir ) % 360
2019-09-15 11:57:23 +02:00
local compass_image = math.floor ( ( angle_relative / 22.5 ) + 0.5 ) % 16
2017-04-11 22:49:36 +02:00
2017-06-13 13:45:42 +02:00
-- create new stack with metadata copied
local metadata = stack : get_meta ( ) : to_table ( )
2020-02-01 22:53:29 -07:00
local meta_fields = metadata.fields
local time_of_death = tonumber ( meta_fields.time_of_death )
2020-02-05 11:29:00 -07:00
2020-02-01 22:53:29 -07:00
if duration > 0 then
local remaining = time_of_death + duration - minetest.get_gametime ( )
if remaining < 0 then
stop_ticking ( player_name )
minetest.sound_play ( " death_compass_bone_crunch " , { to_player = player_name , gain = 1.0 } )
2020-02-02 15:05:40 -07:00
return inactive_return
2020-02-01 22:53:29 -07:00
end
start_ticking ( player_name )
end
2020-02-05 11:29:00 -07:00
2020-02-01 22:53:29 -07:00
local newstack = ItemStack ( " death_compass:dir " .. compass_image )
2017-06-13 13:45:42 +02:00
if metadata then
newstack : get_meta ( ) : from_table ( metadata )
end
2017-05-07 23:53:27 +02:00
return newstack
2017-04-11 00:18:11 +02:00
end
2020-02-05 11:29:00 -07:00
-- update inventory and hud
2017-04-11 00:18:11 +02:00
minetest.register_globalstep ( function ( dtime )
2020-02-05 11:29:00 -07:00
for i , player in ipairs ( minetest.get_connected_players ( ) ) do
2020-02-01 22:53:29 -07:00
local player_name = player : get_player_name ( )
2020-02-05 11:29:00 -07:00
local compass_in_quickbar
local inv = player : get_inventory ( )
if inv then
for i , stack in ipairs ( inv : get_list ( " main " ) ) do
2017-06-13 13:45:42 +02:00
if i > 8 then
break
end
2020-02-01 22:53:29 -07:00
if string.sub ( stack : get_name ( ) , 0 , 17 ) == " death_compass:dir " then
2017-06-13 13:45:42 +02:00
player : get_inventory ( ) : set_stack ( " main " , i , get_compass_stack ( player , stack ) )
2020-02-05 11:29:00 -07:00
compass_in_quickbar = true
end
end
if compass_in_quickbar then
local wielded = player : get_wielded_item ( )
if string.sub ( wielded : get_name ( ) , 0 , 17 ) == " death_compass:dir " then
update_hud ( player , player_name , wielded )
else
hide_hud ( player , player_name )
2017-04-11 00:18:11 +02:00
end
end
end
2020-02-05 11:29:00 -07:00
if not compass_in_quickbar then
2020-02-01 22:53:29 -07:00
stop_ticking ( player_name )
2020-02-05 11:29:00 -07:00
hide_hud ( player , player_name )
2020-02-01 22:53:29 -07:00
end
2017-04-11 00:18:11 +02:00
end
end )
-- register items
2019-09-15 11:57:23 +02:00
for i = 0 , 15 do
2020-02-01 22:53:29 -07:00
local image = " death_compass_16_ " .. i .. " .png "
2020-02-02 17:53:19 -07:00
minetest.register_craftitem ( " death_compass:dir " .. i , {
2020-02-01 22:53:29 -07:00
description = S ( " Death Compass " ) ,
2017-04-11 00:18:11 +02:00
inventory_image = image ,
wield_image = image ,
2020-02-01 22:53:29 -07:00
stack_max = 1 ,
2020-02-02 19:49:02 -07:00
groups = { death_compass = 1 , not_in_creative_inventory = 1 } ,
2017-04-11 00:18:11 +02:00
} )
end
2020-02-02 15:05:40 -07:00
if not automatic then
2020-02-02 17:53:19 -07:00
local display_doc = function ( itemstack , user )
local player_name = user : get_player_name ( )
minetest.chat_send_player ( player_name , documentation .. " \n " .. durationdesc )
end
minetest.register_craftitem ( " death_compass:inactive " , {
description = S ( " Death Compass " ) ,
_doc_items_longdesc = documentation ,
_doc_items_usagehelp = durationdesc ,
2020-02-02 15:05:40 -07:00
inventory_image = " death_compass_inactive.png " ,
wield_image = " death_compass_inactive.png " ,
stack_max = 1 ,
2020-02-02 17:53:19 -07:00
on_place = display_doc ,
on_secondary_use = display_doc ,
2020-02-02 15:05:40 -07:00
} )
minetest.register_craft ( {
output = ' death_compass:inactive ' ,
recipe = {
{ ' ' , ' bones:bones ' , ' ' } ,
{ ' bones:bones ' , ' default:mese_crystal_fragment ' , ' bones:bones ' } ,
{ ' ' , ' bones:bones ' , ' ' }
}
} )
2020-02-02 19:49:02 -07:00
-- Allow a player to deliberately deactivate a death compass
minetest.register_craft ( {
output = ' death_compass:inactive ' ,
type = " shapeless " ,
recipe = {
' group:death_compass ' ,
}
} )
2020-02-02 15:05:40 -07:00
end
2020-02-01 22:53:29 -07:00
local player_death_location = { }
minetest.register_on_dieplayer ( function ( player , reason )
local player_name = player : get_player_name ( )
local inv = minetest.get_inventory ( { type = " player " , name = player : get_player_name ( ) } )
local list = inv : get_list ( " main " )
local count = 0
2020-02-02 15:05:40 -07:00
if automatic then
count = 1
else
for i , itemstack in pairs ( list ) do
2020-02-02 19:49:02 -07:00
if itemstack : get_name ( ) == " death_compass:inactive " then
2020-02-02 15:05:40 -07:00
count = count + itemstack : get_count ( )
list [ i ] = ItemStack ( " " )
end
2020-02-01 22:53:29 -07:00
end
end
if count > 0 then
inv : set_list ( " main " , list )
player_death_location [ player_name ] = { count = count , pos = player : get_pos ( ) }
end
end )
-- Called when a player dies
-- `reason`: a PlayerHPChangeReason table, see register_on_player_hpchange
2020-06-28 12:05:39 -06:00
-- Using the regular minetest.register_on_dieplayer causes the new callback to be inserted *after*
-- the on_dieplayer used by the bones mod, which means the bones mod clears the player inventory before
-- we get to this and we can't tell if there was a death compass in it.
-- We must therefore rearrange the callback table to move this mod's callback to the front
-- to ensure it always goes first.
local death_compass_dieplayer_callback = table.remove ( minetest.registered_on_dieplayers )
table.insert ( minetest.registered_on_dieplayers , 1 , death_compass_dieplayer_callback )
2020-02-01 22:53:29 -07:00
minetest.register_on_respawnplayer ( function ( player )
local player_name = player : get_player_name ( )
local compasses = player_death_location [ player_name ]
if compasses then
local inv = minetest.get_inventory ( { type = " player " , name = player_name } )
-- Remove any death compasses they might still have for some reason
local current = inv : get_list ( " main " )
for i , item in pairs ( current ) do
2020-02-02 19:49:02 -07:00
if item : get_name ( ) == " death_compass:inactive " then
2020-02-01 22:53:29 -07:00
current [ i ] = ItemStack ( " " )
end
end
inv : set_list ( " main " , current )
-- give them new compasses pointing to their place of death
for i = 1 , compasses.count do
local compass = ItemStack ( " death_compass:dir0 " )
set_target ( compass , compasses.pos , player_name )
inv : add_item ( " main " , compass )
end
end
return false
end )
-- * Called when player is to be respawned
-- * Called _before_ repositioning of player occurs
2020-02-16 20:11:23 -07:00
-- * return true in func to disable regular player placement
minetest.register_on_leaveplayer ( function ( player , timed_out )
hide_hud ( player , player : get_player_name ( ) )
end )