dragonfire-mods-waspsteran/CSMdeathMarkers/init.lua

276 lines
6.1 KiB
Lua

-- minetest CSM deathMarkers --
-- 2021 by Luke aka SwissalpS --
-- based on https://gitlab.com/PeterNerlich/death_markers --
local UPDATE_INTERVAL = 3
local WAYPOINT_SATURATION = 1
local WAYPOINT_EXPIRES_SECONDS = 42 * 60
local iDeaths = 0
local tDeaths = {}
local sStoreID = ''
local tColourCache = {}
local bSkipClearOnVisit = false
local oStore = core.get_mod_storage()
-- by PeterNerlich
local function interpolate(a, b, t) return t * (b - a) + a end
-- by PeterNerlich
local function currentColour(fT)
-- clamp t between 0 and 1, in steps of 0.01
local iT = math.max(0, math.min(1, math.floor(fT * 100 + 0.5) / 100))
if nil == tColourCache[iT] then
-- helper variables
local iT2 = iT^2 -- t squared
local iInvT = 1 - iT -- inverse t
local iInvT2 = iInvT^2 -- inverse t squared
local iRed = iT2
local iGreen = 2 * iT * iInvT
local iBlue = iInvT2
local iAverage = math.sqrt(iRed^2 + iGreen^2 + iBlue^2)
iRed = interpolate(iAverage, iRed, WAYPOINT_SATURATION)
iGreen = interpolate(iAverage, iGreen, WAYPOINT_SATURATION)
iBlue = interpolate(iAverage, iBlue, WAYPOINT_SATURATION)
local iWhiteStart = iInvT2^2
local iDimming = 1 - iT^8
iRed = (iRed * (1 - iWhiteStart) + iWhiteStart) * iDimming
iGreen = (iGreen * (1 - iWhiteStart) + iWhiteStart) * iDimming
iBlue = (iBlue * (1 - iWhiteStart) + iWhiteStart) * iDimming
-- we have 255 steps per subpixel
local iBase = 0xFF
-- clamp values, discard fractions
iRed = math.floor(iBase * math.max(0, math.min(1, iRed)))
iGreen = math.floor(iBase * math.max(0, math.min(1, iGreen)))
iBlue = math.floor(iBase * math.max(0, math.min(1, iBlue)))
-- pack it into one number representing the RGB values
tColourCache[iT] = iRed * 2^16 + iGreen * 2^8 + iBlue
end
return tColourCache[iT]
end -- currentColour
local function pos2string(tPos)
return tostring(math.floor(tPos.x)) .. ' | '
.. tostring(math.floor(tPos.y)) .. ' | '
.. tostring(math.floor(tPos.z))
end -- pos2string
local function clearAll()
local oPlayer = core.localplayer
for sPos, tMarker in pairs(tDeaths) do
oPlayer:hud_remove(tMarker.id)
tDeaths[sPos] = nil
end -- loop waypoints
end -- clearAll
local function onFormInput(sFormName, _)
if 'bultin:death' ~= sFormName then return end
bSkipClearOnVisit = false
end -- onFormInput
local function makeWaypoint(oPlayer, sPos, tPos)
return oPlayer:hud_add({
hud_elem_type = 'waypoint',
name = 'Bone #' .. tostring(iDeaths) .. ' ' .. sPos,
text = 'm',
precision = 3,
number = 0xFF0000,
world_pos = tPos,
offset = { x = 0, y = 0},
alignment = {x = 1, y = -1},
})
end -- makeWaypoint
local function onSave()
-- save shutdown time
oStore:set_int(sStoreID .. 'shutdown', os.time())
-- save death-count
oStore:set_int(sStoreID .. 'deathCount', iDeaths)
-- save table of waypoints
oStore:set_string(sStoreID .. 'deaths', core.serialize(tDeaths))
--print('[deathMarkers saved marker DB]')
end -- onSave
local function onDeath()
iDeaths = iDeaths + 1
local oPlayer = core.localplayer
-- get player's position
local tPos = oPlayer:get_pos()
-- adjust position
tPos.x = math.floor(tPos.x + 0.5)
tPos.y = math.floor(tPos.y + 0.5)
tPos.z = math.floor(tPos.z + 0.5)
local sPos = pos2string(tPos)
-- make waypoint and add to table
tDeaths[sPos] = {
pos = tPos,
ts = os.time(),
id = makeWaypoint(oPlayer, sPos, tPos),
}
-- mark player as dead
bSkipClearOnVisit = true
onSave()
end -- onDeath
local function onPunch(tPos, tNode)
if 'bones:bones' ~= tNode.name then return false end
local sPos = pos2string(tPos)
if tDeaths[sPos] then
-- player punched bones -> clear the waypoint
core.localplayer:hud_remove(tDeaths[sPos].id)
tDeaths[sPos] = nil
-- and save (in case our game crashes)
onSave()
end
return false
end -- onPunch
local function onUpdate()
core.after(UPDATE_INTERVAL, onUpdate)
local oPlayer = core.localplayer
local iNow = os.time()
for sPos, tMarker in pairs(tDeaths) do
local fT = (iNow - tMarker.ts) / WAYPOINT_EXPIRES_SECONDS
if 1 < fT then
-- waypoint has expired -> remove it
oPlayer:hud_remove(tMarker.id)
tDeaths[sPos] = nil
else
-- adjust colour
oPlayer:hud_change(tMarker.id, 'number', currentColour(fT))
end
end -- loop waypoints
-- check if player has visited bones and clear marker
-- skip clearing marker as player may still be dead at bones
if bSkipClearOnVisit then return end
local tPos = oPlayer:get_pos()
local sPos = pos2string(tPos)
if tDeaths[sPos] then
-- player is at bones -> clear the waypoint
oPlayer:hud_remove(tDeaths[sPos].id)
tDeaths[sPos] = nil
end
end -- onUpdate
local function onInit()
local oPlayer = core.localplayer
if not oPlayer then
-- onInit was called to early, try again later
core.after(1, onInit)
return
end
local oSI = core.get_server_info()
sStoreID = oPlayer:get_name()
-- in singleplayer mode the port changes
if 'singleplayer' ~= sStoreID then
sStoreID = sStoreID .. '-' .. oSI.ip .. ':' .. oSI.port
end
-- read death-count
iDeaths = oStore:get_int(sStoreID .. 'deathCount')
-- get table of saved markers
tDeaths = core.deserialize(oStore:get_string(sStoreID .. 'deaths')) or {}
-- how long between sessions
local iDiff = os.time() - oStore:get_int(sStoreID .. 'shutdown')
for sPos, tMarker in pairs(tDeaths) do
-- add inactive time passed between sessions to each marker
tMarker.ts = tMarker.ts + iDiff
-- re-create the waypoint
tMarker.id = makeWaypoint(oPlayer, sPos, tMarker.pos)
end -- loop waypoints
core.after(UPDATE_INTERVAL, onUpdate)
print('[deathMarkers initialized]')
end -- onInit
-- hook in to core shutdown callback to save markers and shutdown time
core.register_on_shutdown(onSave)
-- hook in to formspec signals to catch when 'you died' formspec is closed
core.register_on_formspec_input(onFormInput)
-- hook in to death event
core.register_on_death(onDeath)
-- hook in to check if our bone is being dug
core.register_on_punchnode(onPunch)
-- add chatcommand to clear all waypoints
core.register_chatcommand('cadw', {
description = 'Clears all death waypoints.',
func = clearAll,
params = '<none>',
})
-- init delayed so core.localplayer exists
core.after(1, onInit)