2022-10-07 07:25:14 -04:00

270 lines
6.8 KiB
Lua

-- LUALOCALS < ---------------------------------------------------------
local ipairs, minetest, next, pairs, string, table, tonumber, type
= ipairs, minetest, next, pairs, string, table, tonumber, type
local string_format, table_concat, table_sort
= string.format, table.concat, table.sort
-- LUALOCALS > ---------------------------------------------------------
local modname = minetest.get_current_modname()
local modstore = minetest.get_mod_storage()
------------------------------------------------------------------------
-- CONFIG
local default_mult = {
dig = 1,
place = 1,
move = 0.1,
}
local priv_cache = {}
local function applylevels(pname, xp)
local cached = priv_cache[pname]
if not cached then
cached = {}
priv_cache[pname] = cached
end
local addprivs = {}
local dirty
local xplevels = minetest.settings:get(modname .. "_privs") or ""
for _, spec in pairs(xplevels:split(";")) do
local parts = spec:split("=")
local min, privs = tonumber(parts[1]), parts[2]
if min and xp >= min then
for p in pairs(minetest.string_to_privs(privs)) do
if not cached[p] then
dirty = true
addprivs[p] = true
end
end
end
end
if not dirty then return end
dirty = nil
local callbacks = {}
local privs = minetest.get_player_privs(pname)
for k in pairs(addprivs) do
cached[k] = true
if not privs[k] then
dirty = true
privs[k] = true
callbacks[k] = true
end
end
if not dirty then return end
minetest.set_player_privs(pname, privs)
for priv in pairs(callbacks) do
minetest.run_priv_callbacks(pname, priv, pname, "grant")
end
if next(callbacks) then
minetest.chat_send_player(pname,
(minetest.settings:get(modname .. "_earned")
or "earned privilege(s): ")
.. minetest.privs_to_string(callbacks, ', '))
end
end
------------------------------------------------------------------------
-- IN-MEMORY DATABASE AND UTILITY
local cache = {}
local function getxp(pname)
local s = cache[pname]
if s then return s end
s = modstore:get_float(pname) or 0
cache[pname] = s
return s
end
local function savexp(pname, n)
cache[pname] = n
modstore:set_int(pname, n)
return applylevels(pname, n)
end
local function addxp(pname, qty, stat)
if qty == 0 then return end
local mult = tonumber(minetest.settings:get(modname .. "_xp_" .. stat))
or default_mult[stat] or 0
if mult == 0 then return end
return savexp(pname, getxp(pname) + qty * mult)
end
local function getpn(whom)
if not whom then return end
local pn = whom.get_player_name
if not pn then return end
pn = pn(whom)
if not pn or not pn:find("%S") then return end
return pn
end
------------------------------------------------------------------------
-- PLAYER ACTIVITY EVENT HOOKS
local function reghook(func, stat, pwhom)
return func(function(...)
local t = {...}
local whom = t[pwhom]
local pn = getpn(whom)
if not pn then return end
return addxp(pn, 1, stat)
end)
end
reghook(minetest.register_on_dignode, "dig", 3)
reghook(minetest.register_on_placenode, "place", 3)
reghook(minetest.register_on_dieplayer, "die", 1)
reghook(minetest.register_on_respawnplayer, "spawn", 1)
reghook(minetest.register_on_joinplayer, "join", 1)
reghook(minetest.register_on_leaveplayer, "leave", 1)
reghook(minetest.register_on_craft, "craft", 2)
minetest.register_on_player_hpchange(function(whom, change)
local pn = getpn(whom)
if not pn then return end
if change < 0 then
return addxp(pn, -change, "hurt")
else
return addxp(pn, change, "heal")
end
end)
local olddrop = minetest.item_drop
function minetest.item_drop(item, whom, pos, ...)
local pn = getpn(whom)
if pn then addxp(pn, 1, "drop") end
return olddrop(item, whom, pos, ...)
end
------------------------------------------------------------------------
-- PLAYER MOVEMENT/IDLE HOOKS
local playdb = {}
local idlemin = 5
local function procstep(dt, player)
local pn = getpn(player)
if not pn then return end
local pd = playdb[pn]
if not pd then
pd = {}
playdb[pn] = pd
end
local pos = player:get_pos()
local dir = player:get_look_dir()
local cur = {pos.x, pos.y, pos.z, dir.x, dir.y, dir.z}
local moved
if pd.last then
for i = 1, 6 do
moved = moved or pd.last[i] ~= cur[i]
end
end
pd.last = cur
local t = pd.t or 0
if moved then
pd.t = 0
if t >= idlemin then
addxp(pn, t, "idle")
return addxp(pn, dt, "move")
else
return addxp(pn, t + dt, "move")
end
else
if t >= idlemin then
return addxp(pn, dt, "idle")
else
pd.t = t + dt
if (t + dt) >= idlemin then
return addxp(pn, t + dt, "idle")
end
end
end
end
minetest.register_globalstep(function(dt)
for _, player in pairs(minetest.get_connected_players()) do
procstep(dt, player)
end
end)
------------------------------------------------------------------------
-- PUBLIC API
minetest["get_" .. modname] = minetest["get_" .. modname] or function(player)
if not player then return end
if type(player) == "string" then return getxp(player) end
if not player.get_player_name then return end
local pname = player:get_player_name()
return pname and getxp(pname) or nil
end
------------------------------------------------------------------------
-- QUERY COMMAND
local stpriv = minetest.settings:get(modname .. "_hide") or "stealth"
local function isstealth(p) return minetest.check_player_privs(p, stpriv) end
local prefixes = {"", "k", "M", "G", "T", "P", "E", "Z", "Y"}
local function numdisp(n)
if not n then return "?" end
local idx = 1
while n >= 1000 do
idx = idx + 1
n = n / 1000
end
if n >= 100 then return string_format("%d%s", n, prefixes[idx]) end
if n >= 10 then return string_format("%.1f%s", n, prefixes[idx]) end
return string_format("%.2f%s", n, prefixes[idx])
end
minetest.register_chatcommand(modname, {
description = "Read player(s) xp levels",
params = "[player]",
func = function(_, param)
local list = param and param ~= "" and {param}
if not list then
list = {}
for _, p in ipairs(minetest.get_connected_players()) do
if not isstealth(p) then
list[#list + 1] = p:get_player_name()
end
end
end
local xp = {}
for _, p in ipairs(list) do
xp[p] = numdisp(getxp(p) or 0)
end
table_sort(list, function(a, b) return xp[a] > xp[b] end)
for i = 1, #list do
list[i] = list[i] .. "=" .. xp[list[i]]
end
return true, table_concat(list, ", ")
end
})
------------------------------------------------------------------------
-- CLEANUP COMMAND
local function cleanup()
priv_cache = {}
for pname, xp in pairs(modstore:to_table().fields) do
if not minetest.player_exists(pname) then
modstore:set_string(pname, "")
else
applylevels(pname, tonumber(xp) or 0)
end
end
return true, modname .. " synced"
end
minetest.after(0, cleanup)
minetest.register_chatcommand(modname .. "_sync", {
description = modname .. " cleanup/sync",
privs = {server = true},
func = cleanup
})