350 lines
No EOL
11 KiB
Lua
350 lines
No EOL
11 KiB
Lua
-- compass configuration interface - adjustable from other mods or minetest.conf settings
|
|
death_compass = {}
|
|
|
|
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
|
|
local automatic = minetest.settings:get_bool("death_compass_automatic", false)
|
|
|
|
local range_to_inactivate = 5
|
|
|
|
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
|
|
|
|
-- 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.")
|
|
local durationdesc
|
|
if duration > 0 then
|
|
durationdesc = S("The Death Compass' guidance will only last for @1 after death.", clock_string(duration, false))
|
|
else
|
|
durationdesc = S("The Death Compass will point toward your corpse until you find it.")
|
|
end
|
|
|
|
-- set a position to the compass stack
|
|
local function set_target(stack, pos, name)
|
|
local meta=stack:get_meta()
|
|
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())
|
|
end
|
|
|
|
-- Get compass target
|
|
local function get_destination(player, stack)
|
|
local posstring = stack:get_meta():get_string("target_pos")
|
|
if posstring ~= "" then
|
|
return minetest.string_to_pos(posstring)
|
|
end
|
|
end
|
|
|
|
-- 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})
|
|
end
|
|
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
|
|
end
|
|
end
|
|
|
|
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
|
|
|
|
-- get right image number for players compass
|
|
local function get_compass_stack(player, stack)
|
|
local target = get_destination(player, stack)
|
|
local inactive_return
|
|
if automatic then
|
|
inactive_return = ItemStack("")
|
|
else
|
|
inactive_return = ItemStack("death_compass:inactive")
|
|
end
|
|
|
|
if not target then
|
|
return inactive_return
|
|
end
|
|
local pos = player:get_pos()
|
|
local distance = vector.distance(pos, target)
|
|
local player_name = player:get_player_name()
|
|
|
|
if distance < range_to_inactivate then
|
|
stop_ticking(player_name)
|
|
minetest.sound_play("death_compass_bone_crunch", {to_player=player_name, gain = 1.0})
|
|
return inactive_return
|
|
end
|
|
|
|
local dir = player:get_look_horizontal()
|
|
local angle_north = math.deg(math.atan2(target.x - pos.x, target.z - pos.z))
|
|
if angle_north < 0 then
|
|
angle_north = angle_north + 360
|
|
end
|
|
local angle_dir = math.deg(dir)
|
|
local angle_relative = (angle_north + angle_dir) % 360
|
|
local compass_image = math.floor((angle_relative/22.5) + 0.5)%16
|
|
|
|
-- create new stack with metadata copied
|
|
local metadata = stack:get_meta():to_table()
|
|
local meta_fields = metadata.fields
|
|
local time_of_death = tonumber(meta_fields.time_of_death)
|
|
|
|
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})
|
|
return inactive_return
|
|
end
|
|
start_ticking(player_name)
|
|
end
|
|
|
|
local newstack = ItemStack("death_compass:dir"..compass_image)
|
|
if metadata then
|
|
newstack:get_meta():from_table(metadata)
|
|
end
|
|
return newstack
|
|
end
|
|
|
|
-- update inventory and hud
|
|
minetest.register_globalstep(function(dtime)
|
|
for i, player in ipairs(minetest.get_connected_players()) do
|
|
local player_name = player:get_player_name()
|
|
local compass_in_quickbar
|
|
local inv = player:get_inventory()
|
|
if inv then
|
|
for i, stack in ipairs(inv:get_list("main")) do
|
|
if i > 8 then
|
|
break
|
|
end
|
|
if string.sub(stack:get_name(), 0, 17) == "death_compass:dir" then
|
|
player:get_inventory():set_stack("main", i, get_compass_stack(player, stack))
|
|
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)
|
|
end
|
|
end
|
|
end
|
|
if not compass_in_quickbar then
|
|
stop_ticking(player_name)
|
|
hide_hud(player, player_name)
|
|
end
|
|
end
|
|
end)
|
|
|
|
-- register items
|
|
for i = 0, 15 do
|
|
local image = "death_compass_16_"..i..".png"
|
|
minetest.register_craftitem("death_compass:dir"..i, {
|
|
description = S("Death Compass"),
|
|
inventory_image = image,
|
|
wield_image = image,
|
|
stack_max = 1,
|
|
groups = {death_compass = 1, not_in_creative_inventory = 1},
|
|
})
|
|
end
|
|
|
|
if not automatic then
|
|
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,
|
|
inventory_image = "death_compass_inactive.png",
|
|
wield_image = "death_compass_inactive.png",
|
|
stack_max = 1,
|
|
on_place = display_doc,
|
|
on_secondary_use = display_doc,
|
|
})
|
|
|
|
minetest.register_craft({
|
|
output = 'death_compass:inactive',
|
|
recipe = {
|
|
{'', 'bones:bones', ''},
|
|
{'bones:bones', 'default:mese_crystal_fragment', 'bones:bones'},
|
|
{'', 'bones:bones', ''}
|
|
}
|
|
})
|
|
|
|
-- Allow a player to deliberately deactivate a death compass
|
|
minetest.register_craft({
|
|
output = 'death_compass:inactive',
|
|
type = "shapeless",
|
|
recipe = {
|
|
'group:death_compass',
|
|
}
|
|
})
|
|
|
|
end
|
|
|
|
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
|
|
if automatic then
|
|
count = 1
|
|
else
|
|
for i, itemstack in pairs(list) do
|
|
if itemstack:get_name() == "death_compass:inactive" then
|
|
count = count + itemstack:get_count()
|
|
list[i] = ItemStack("")
|
|
end
|
|
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
|
|
|
|
-- 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)
|
|
|
|
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
|
|
if item:get_name() == "death_compass:inactive" then
|
|
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
|
|
-- * 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) |