Start on new unified Hint API

- Move hint handling down to API layer
- Simplify stat data; old nc_stats counting can
  be moved out to a separate mod.  We only
  need whether the player has seen or not.
- Invert inventory tab responsibility.
- Merge witness system in from crafting.

TODO:
- Redistribute hint registration responsibility
  to individual mods.
- Test external mod compat.
- Retire old nc_stats and nc_guide systems.
- Add a way to reset hints.
This commit is contained in:
Aaron Suen 2020-09-04 16:27:44 -04:00
parent 5c73331ec2
commit edea123a1e
20 changed files with 239 additions and 120 deletions

View File

@ -124,9 +124,7 @@ local function craftcheck(recipe, pos, node, data, xx, xz, zx, zz)
end
if recipe.after then recipe.after(pos, data) end
if data.after then data.after(pos, data) end
if nodecore.player_stat_add then
nodecore.player_stat_add(1, data.crafter, "craft", recipe.label)
end
nodecore.player_discover(data.crafter, "craft:" .. recipe.label)
if recipe.witness then
local lut = {}
for _, v in pairs(recipe.nodes) do

View File

@ -5,7 +5,6 @@ local include, nodecore
nodecore.amcoremod()
include("witness")
include("register_craft")
include("craft_check")
include("item_place_node")

View File

@ -20,7 +20,7 @@ nodecore.register_on_joinplayer("join hint setup", function(player)
msgcache[pname] = {}
end)
nodecore.register_on_player_discover(function(player)
nodecore.register_on_discover(function(player)
local pname = player:get_player_name()
local dc = donecache[pname]
if not dc then return end

View File

@ -0,0 +1,11 @@
-- LUALOCALS < ---------------------------------------------------------
local minetest, nodecore, string
= minetest, nodecore, string
local string_lower
= string.lower
-- LUALOCALS > ---------------------------------------------------------
function nodecore.hints_disabled()
return minetest.settings:get_bool(
string_lower(nodecore.product) .. "_disable_hints")
end

View File

@ -0,0 +1,112 @@
-- LUALOCALS < ---------------------------------------------------------
local minetest, nodecore, pairs, string, type
= minetest, nodecore, pairs, string, type
local string_format
= string.format
-- LUALOCALS > ---------------------------------------------------------
local modname = minetest.get_current_modname()
nodecore.register_on_discover,
nodecore.registered_on_discovers
= nodecore.mkreg()
local cache = {}
local function loaddb(p)
if nodecore.hints_disabled() then return end
if not p then return end
local player
local pname
if type(p) == "string" then
player, pname = minetest.get_player_by_name(p), p
else
player, pname = p, p:get_player_name()
end
if not (player and pname) then return end
local db = cache[pname]
if not db then
local s = player:get_meta():get_string(modname) or ""
db = s and minetest.deserialize(s) or {}
cache[pname] = db
end
return db, player, pname
end
nodecore.get_player_discovered = loaddb
local function discover(p, k)
local db, player, pname = loaddb(p)
if not db then return end
db[k] = true
player:get_meta():set_string(modname, minetest.serialize(db))
minetest.log("action", string_format("player %q discovered %q", pname, k))
for _, cb in pairs(nodecore.registered_on_discovers) do
cb(player, k, pname, db)
end
end
nodecore.player_discover = discover
------------------------------------------------------------------------
-- PLAYER EVENTS
local function reghook(func, stat, pwhom, npos)
return func("stat hook", function(...)
local t = {...}
local whom = t[pwhom]
local n = npos and t[npos].name or nil
if n then stat = stat .. ":" .. n end
return discover(whom, stat)
end)
end
reghook(nodecore.register_on_punchnode, "punch", 3, 2)
reghook(nodecore.register_on_dignode, "dig", 3, 2)
reghook(nodecore.register_on_placenode, "place", 3, 2)
reghook(nodecore.register_on_dieplayer, "die", 1)
reghook(nodecore.register_on_respawnplayer, "spawn", 1)
reghook(nodecore.register_on_joinplayer, "join", 1)
local function unpackreason(reason)
if type(reason) ~= "table" then return reason or "?" end
if reason.nc_type then return "nc", reason.nc_type end
if reason.from then return reason.from, reason.type or nil end
return reason.type or "?"
end
nodecore.register_on_player_hpchange("hurt/heal stats", function(whom, change, reason)
if change < 0 then
return discover(whom, "hurt:" .. unpackreason(reason))
else
return discover(whom, "heal:" .. unpackreason(reason))
end
end)
nodecore.register_on_cheat("cheat stats", function(player, reason)
discover(player, "cheat: " .. unpackreason(reason))
end)
nodecore.register_on_chat_message("chat message stats", function(name, msg)
discover(name, "chat:" .. (msg:sub(1, 1) == "/") and "command" or "message")
end)
------------------------------------------------------------------------
-- PLAYER INVENTORY SCAN
nodecore.register_playerstep({
label = "inv",
action = function(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
discover(player, "inv", k)
end
end
})

View File

@ -0,0 +1,13 @@
-- LUALOCALS < ---------------------------------------------------------
local include, nodecore
= include, nodecore
-- LUALOCALS > ---------------------------------------------------------
nodecore.amcoremod()
include("disable")
include("discover")
include("witness")
include("register")
include("state")
include("alerts")

View File

@ -0,0 +1 @@
depends = nc_api_active, nc_api_hud

View File

@ -0,0 +1,42 @@
-- LUALOCALS < ---------------------------------------------------------
local nodecore, type
= nodecore, type
-- LUALOCALS > ---------------------------------------------------------
nodecore.hints = {}
local function conv(spec)
if not spec then
return function() return true end
end
if type(spec) == "function" then return spec end
if type(spec) == "table" then
local f = spec[1]
if f == true then
return function(db)
for i = 2, #spec do
if db[spec[i]] then return true end
end
end
end
return function(db)
for i = 1, #spec do
if not db[spec[i]] then return end
end
return true
end
end
return function(db) return db[spec] end
end
function nodecore.register_hint(text, goal, reqs)
local hints = nodecore.hints
local t = nodecore.translate(text)
local h = {
text = t,
goal = conv(goal),
reqs = conv(reqs)
}
hints[#hints + 1] = h
return h
end

View File

@ -0,0 +1,46 @@
-- LUALOCALS < ---------------------------------------------------------
local ipairs, minetest, nodecore, pairs, string
= ipairs, minetest, nodecore, pairs, string
local string_gsub, string_match
= string.gsub, string.match
-- LUALOCALS > ---------------------------------------------------------
function nodecore.hint_state(pspec)
local rawdb, player, pname = nodecore.get_player_discovered(pspec)
if not rawdb then return {}, {} end
local db = {}
for k in pairs(rawdb) do
db[k] = true
while string_match(k, ":") do
k = string_gsub(k, "^[^:]*:", "")
db[k] = true
end
end
for k, v in pairs(minetest.registered_items) do
if db[k] then
if v.tool_capabilities and v.tool_capabilities.groupcaps then
for gn, gv in pairs(v.tool_capabilities.groupcaps) do
for gt in pairs(gv.times or {}) do
db["toolcap:" .. gn .. ":" .. gt] = true
end
end
end
for gn, gv in pairs(v.groups or {}) do
db["group:" .. gn] = gv
end
end
end
local done = {}
local found = {}
for _, hint in ipairs(nodecore.hints) do
if hint.goal(db, pname, player) then
done[#done + 1] = hint
elseif hint.reqs(db, pname, player) then
found[#found + 1] = hint
end
end
return found, done
end

View File

@ -35,11 +35,10 @@ local function playercheck(player, pos, maxdist, check)
end
function nodecore.witness(pos, label, maxdist, check)
if not nodecore.player_stat_add then return end
for _, player in pairs(minetest.get_connected_players()) do
if playercheck(player, pos, maxdist or 32, check) then
for _, l in pairs(type(label) == "table" and label or {label}) do
nodecore.player_stat_add(1, player, "witness", l)
nodecore.player_discover(player, "witness:" .. l)
end
end
end

View File

@ -54,8 +54,8 @@ function nodecore.register_door(basemod, basenode, desc, pin, lv)
local dir = vector.subtract(pointed.above, pointed.under)
if vector.equals(dir, fd.t) or vector.equals(dir, fd.b) then
node.name = doorname
nodecore.player_stat_add(1, clicker, "craft",
"door pin " .. basenode:lower())
nodecore.player_discover(clicker, "craft:door pin "
.. basenode:lower())
nodecore.set_loud(pos, node)
stack:take_item(1)
return stack

View File

@ -1,96 +0,0 @@
-- LUALOCALS < ---------------------------------------------------------
local ipairs, minetest, nodecore, pairs, string, type
= ipairs, minetest, nodecore, pairs, string, type
local string_lower
= string.lower
-- LUALOCALS > ---------------------------------------------------------
nodecore.hints = {}
function nodecore.hints_disabled()
return minetest.settings:get_bool(
string_lower(nodecore.product) .. "_disable_hints")
end
local function conv(spec)
if not spec then
return function() return true end
end
if type(spec) == "function" then return spec end
if type(spec) == "table" then
local f = spec[1]
if f == true then
return function(db)
for i = 2, #spec do
if db[spec[i]] then return true end
end
end
end
return function(db)
for i = 1, #spec do
if not db[spec[i]] then return end
end
return true
end
end
return function(db) return db[spec] end
end
function nodecore.addhint(text, goal, reqs)
local hints = nodecore.hints
local t = nodecore.translate(text)
local h = {
text = t,
goal = conv(goal),
reqs = conv(reqs)
}
hints[#hints + 1] = h
return h
end
function nodecore.hint_state(player)
local pname
if type(player) == "string" then
pname, player = player, minetest.get_player_by_name(player)
else
pname = player:get_player_name()
end
if type(player) == "string" then player = minetest.get_player_by_name(pname) end
local rawdb = nodecore.statsdb[pname] or {}
local db = {}
for _, r in ipairs({"inv", "punch", "dig", "place", "craft", "witness"}) do
for k, v in pairs(rawdb[r] or {}) do
db[k] = v
db[r .. ":" .. k] = v
end
end
for k, v in pairs(minetest.registered_items) do
if db[k] then
if v.tool_capabilities and v.tool_capabilities.groupcaps then
for gn, gv in pairs(v.tool_capabilities.groupcaps) do
for gt in pairs(gv.times or {}) do
db["toolcap:" .. gn .. ":" .. gt] = true
end
end
end
for gn, gv in pairs(v.groups or {}) do
db["group:" .. gn] = gv
end
end
end
local done = {}
local found = {}
for _, hint in ipairs(nodecore.hints) do
if hint.goal(db, pname, player) then
done[#done + 1] = hint
elseif hint.reqs(db, pname, player) then
found[#found + 1] = hint
end
end
return found, done
end

View File

@ -3,7 +3,7 @@ local nodecore
= nodecore
-- LUALOCALS > ---------------------------------------------------------
local addhint = nodecore.addhint
local addhint = nodecore.register_hint
------------------------------------------------------------------------
-- SCALING

View File

@ -5,7 +5,4 @@ local include, nodecore
nodecore.amcoremod()
include("api")
include("hints")
include("invtab")
include("alerts")

View File

@ -1 +1 @@
depends = nc_api_all, nc_fire, nc_player_gui, nc_stats
depends = nc_api_all

View File

@ -38,7 +38,7 @@ local function gethint(player)
while #found > 5 do
table_remove(found, math_random(1, #found))
end
while #found < 5 do
while #found < 5 and #done > 0 do
local j = math_random(1, #done)
found[#found + 1] = done[j]
table_remove(done, j)

View File

@ -8,3 +8,4 @@ nodecore.amcoremod()
include("api")
include("about")
include("guide")
include("hints")

View File

@ -73,10 +73,8 @@ hand.on_place = function(stack, player, pointed, ...)
stack, player, pointed, node, ...) then return end
if nodecore.scaling_apply(pointed, player) then
if nodecore.player_stat_add then
nodecore.player_stat_add(1, player, "craft",
"scaling dy=" .. (pointed.under.y - pointed.above.y))
end
nodecore.player_discover(player, "craft:scaling dy="
.. (pointed.under.y - pointed.above.y))
return nodecore.scaling_particles(pointed.above, {
time = 0.1,
amount = 40,

View File

@ -28,8 +28,8 @@ nodecore.extend_item(chip, function(copy, orig)
nodecore.sound_play(stoned.name, stoned)
itemstack:set_count(itemstack:get_count() - 1)
if placer then
nodecore.player_stat_add(1, placer, "craft",
"assemble " .. v.to)
nodecore.player_discover(placer,
"craft:assemble " .. v.to)
end
return itemstack
end

View File

@ -33,9 +33,7 @@ minetest.register_node(modname .. ":eggcorn", {
nodecore.set_loud(pos, {name = epname, param2 = 16})
if nodecore.player_stat_add then
nodecore.player_stat_add(1, whom, "craft", "eggcorn planting")
end
nodecore.player_discover(whom, "craft:eggcorn planting")
nodecore.log("action", (whom and whom:get_player_name() or "unknown")
.. " planted an eggcorn at " .. minetest.pos_to_string(pos))