nodecore-skyblock/mods/nc_stats/init.lua

266 lines
6.7 KiB
Lua

-- LUALOCALS < ---------------------------------------------------------
local math, minetest, nodecore, os, pairs, table, type, vector
= math, minetest, nodecore, os, pairs, table, type, vector
local math_random, os_date, table_remove
= math.random, os.date, table.remove
-- LUALOCALS > ---------------------------------------------------------
nodecore.amcoremod()
local modname = minetest.get_current_modname()
local modstore = minetest.get_mod_storage()
------------------------------------------------------------------------
-- DATABASE SETUP
local statsdb = {}
nodecore.statsdb = statsdb
local function load_check(s)
s = s and s ~= "" and minetest.deserialize(s)
return type(s) == "table" and s or {}
end
do
local s = load_check(modstore:get_string(modname))
statsdb[false] = s
s.firstseen = s.firstseen or os_date("!*t")
s.startup = (s.startup or 0) + 1
end
local function dbadd_nav(qty, dirty, db, root, key, ...)
local v = db[root]
if key then
if not v or type(v) ~= "table" then
v = {}
db[root] = v
end
if dirty then v.dirty = true end
return dbadd_nav(qty, nil, v, key, ...)
else
v = v and type(v) == "number" and v or 0
db[root] = v + qty
end
end
local function dbadd(qty, root, ...)
if qty == 0 then return end
return dbadd_nav(qty, true, statsdb, root, ...)
end
local function playeradd(qty, player, ...)
if not player then return end
local pname = (type(player) == "string") and player or player:get_player_name()
if not pname then return end
local data = statsdb[pname]
if not data then
if type(player) == "string" then player = minetest.get_player_by_name(player) end
if (not player) or (not player.is_player) or (not player:is_player()) then return end
data = load_check(player:get_meta():get_string(modname))
statsdb[pname] = data
end
dbadd(qty, pname, ...)
if not statsdb[pname].firstseen then
statsdb[pname].firstseen = os_date("!*t")
end
dbadd(qty, false, "players", ...)
end
nodecore.player_stat_add = playeradd
------------------------------------------------------------------------
-- PLAYER EVENTS
local function reghook(func, stat, pwhom, npos)
return func(function(...)
local t = {...}
local whom = t[pwhom]
local n = npos and t[npos].name or nil
return playeradd(1, whom, stat, n)
end)
end
reghook(minetest.register_on_punchnode, "punch", 3, 2)
reghook(minetest.register_on_dignode, "dig", 3, 2)
reghook(minetest.register_on_placenode, "place", 3, 2)
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)
local function unpackreason(reason)
if type(reason) ~= "table" then return reason or "?" end
if reason.from then return reason.from, reason.type or nil end
return reason.type or "?"
end
minetest.register_on_player_hpchange(function(whom, change, reason)
if change < 0 then
return playeradd(-change, whom, "hurt", unpackreason(reason))
else
return playeradd(change, whom, "heal", unpackreason(reason))
end
end)
minetest.register_on_cheat(function(player, reason)
playeradd(1, player, "cheat", unpackreason(reason))
end)
minetest.register_on_chat_message(function(name, msg)
dbadd(1, name, "chat", (msg:sub(1, 1) == "/") and "command" or "message")
end)
minetest.register_on_shutdown(function()
for _, player in pairs(minetest.get_connected_players()) do
playeradd(1, player, "shutdown")
end
end)
------------------------------------------------------------------------
-- PLAYER INVENTORY SCAN
local function invscan(dt, player)
local inv = player:get_inventory()
local t = {}
for i = 1, inv:get_size("main") do
local stack = inv:get_stack("main", i)
if not stack:is_empty() then
t[stack:get_name()] = true
end
end
for k in pairs(t) do
playeradd(dt, player, "inv", k)
end
end
------------------------------------------------------------------------
-- PLAYER MOVEMENT/IDLE HOOKS
local playdb = { }
local idlemin = 5
local function movement(dt, player)
if not player or not player:is_player() then return end
local pn = player:get_player_name()
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
if moved then
playeradd(vector.distance(pos,
{x = pd.last[1], y = pd.last[2], z = pd.last[3]}),
player, "distance")
end
end
pd.last = cur
local t = pd.t or 0
if moved then
pd.t = 0
if t >= idlemin then
playeradd(t, player, "idle")
return playeradd(dt, player, "move")
else
return playeradd(t + dt, player, "move")
end
else
if t >= idlemin then
return playeradd(dt, player, "idle")
else
pd.t = t + dt
if (t + dt) >= idlemin then
return playeradd(t + dt, player, "idle")
end
end
end
end
minetest.register_globalstep(function(dt)
for _, player in pairs(minetest.get_connected_players()) do
invscan(dt, player)
movement(dt, player)
end
end)
------------------------------------------------------------------------
-- DATABASE FLUSH CYCLE
local opq = {}
local function flushkey(k)
local v = statsdb[k]
if not v or not v.dirty then return end
v.dirty = nil
v.datafix = v.datafix or 0
if v.datafix < 1 then
v.datafix = 1
local q = k and v or v.players
for _, n in pairs({"hurt", "heal", "cheat"}) do
local old = q[n]
if old then
q[n] = {}
for xk, xv in pairs(old) do
if type(xv) == "number" then
dbadd_nav(xv, nil, q[n], unpackreason(xk))
else
q[n][xk] = xv
end
end
end
end
end
if k == false then
return modstore:set_string(modname, minetest.serialize(v))
end
local player = minetest.get_player_by_name(k)
if player then
return player:get_meta():set_string(modname, minetest.serialize(v))
end
end
local function flushop()
local k = table_remove(opq)
if k == nil then return end
minetest.after(0, flushop)
return flushkey(k)
end
local function flushenq()
minetest.after(20, flushenq)
if #opq > 0 then return end
for k in pairs(statsdb) do
opq[#opq + 1] = k
end
for i = 1, #opq do
local j = math_random(1, #opq)
opq[i], opq[j] = opq[j], opq[i]
end
minetest.after(0, flushop)
end
flushenq()
minetest.register_globalstep(function(dt)
dbadd(dt, false, "elapsed")
dbadd(1, false, "tick")
end)
minetest.register_on_leaveplayer(function(player)
return flushkey(player:get_player_name())
end)
minetest.register_on_shutdown(function()
dbadd(1, false, "shutdown")
flushkey(false)
for _, player in pairs(minetest.get_connected_players()) do
flushkey(player:get_player_name())
end
end)