270 lines
6.8 KiB
Lua
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
|
|
})
|