diff --git a/HOWTO_nuafan.txt b/HOWTO_nuafan.txt new file mode 100644 index 0000000..2bc3c29 --- /dev/null +++ b/HOWTO_nuafan.txt @@ -0,0 +1,192 @@ +Nuafan adapts nodes from other mods to add nua functionality. + +Using nuafan is all about telling it which nodes to adapt from other mods, and what changes to make. + +This is acheived using filter files, which are essentially just lists. + + +-------------------------- +Contents: + Filter files + Mod filtering + Node filtering + Basic syntax + + Optional + Globbing syntax + Precedence + Exclusion patterns + + Editing tips + + Footnote +-------------------------- + + + + +====== Filter files ====== + +The README.md for nua outlines the code that nuafan produces. + + + +** Mod Filtering ** + +"depends.txt" is the main filter, consisting of one mod name per line. + "nua" is nuafan's only real dependency, and must not be removed. + Otherwise, all and only those mods to be adapted should be listed. +With no other filters, nuafan will add event signalling to all nodes from those mods. + +Optional dependencies ("name?") will be used if loaded, and are recommended so you don't have to edit it when you disable mods. +Nuafan will not adapt any nodes from mods not listed here. + + + +** Node Filtering ** + +The remaining filters all deal with individual node types. +They may include unused node specifications without problems, so there's no need to edit it every time you change mods. + +"f_xconstruct.txt", "f_xdestruct.txt" + These prevent listed nodes from signalling an event on construction or destruction, respectively. + By default they contain "@safemode:" which is a directive that avoids editing any callbacks that have already been defined. This is because nuafan cannot yet cooperate with existing definitions without hurting their original functionality. + No editing is required unless you wish to exclude additional nodes, or disable safemode and do everything manually. + Manual entries in "f_xconstruct.txt" can have "X" as a second field to also block definitions in "f_callbacks." + +"f_callbacks.txt" + This defines callbacks for nodes, allowing them to respond to alerts as well as cause them. It has the same syntax as the other "f_" files, plus an additional function name on each line. Function names given here shall be called with two arguments, (receiverpos,eventpos) to receive update alerts. + Mods that supply only functions need not be in "depends.txt," as long as those you need are loaded by the game. In fact, if the mod has a nua-compatible callback function, its nodes are probably already nua aware anyway. + + + +====== Basic syntax ====== + +All filters have one entry per line; a node descriptor, and in "callbacks" a function name. +Different parts of an entry are separated by space(s), and no spaces are allowed within each part. +Space or tab indented lines are comments, and ignored by nuafan. + + ----------------------------------- + +default:cobble mymod.monsterize + + ----------------------------------- + +Duplicate node descriptors replace earlier entries. In "callbacks," this redefines the function, or removes it if none is given. + +Everything can be done explicitly with the basic syntax above. The following simply reduces the file size a bit. + + + + +====== Optional ====== + +Nuafan does not, and will not, support general regular expressions, but some conveniences may prove indispensable with larger variety of nodes. + + + +Note: Safemode can be enabled selectively, but it's very unlikely you won't want it on all mods. + +@safemode: applies to all mods +@safemode:mod1,mod2 applies only to named mods + +Because safemode handles essential filtering automatically, and you would likely want update events to work for as many nodes as possible, usually only "f_callbacks" would be worth editing. + + + +** Globbing syntax ** + +Multiple node names can be referenced on one line: + +m:a,b,c expands to separate entries m:a m:b m:c (INclusion) +m:-a,b,c all names from m except m:a m:b m:c (EXclusion) +m: all names from m +* all names from all mods + + +Related specifiers can also be referenced within a single specifier pattern: +The "*" can only occur once, at one end, where a "_" might be + +a_b*, a*, *a_b, *b all match a_b + +for example: + +"stone*" matches: + stone + stone_with_iron +but not: + stonebrick + + +REMEMBER: Each line shares any additional fields, such as the "X" in f_xconstruct, or function name in f_callbacks. + +default:-*flowing,furnace*,wood oddmod.allbut_flowoodfurn + + + +** Precedence ** + +1) Test classes are ordered from most to least restrictive. + m:a > m:-a > m: > * + +2) Inclusion tests prioritise by first '*'-pattern to match, under the ordering: + plain; on the right, leftwards; on the left, rightwards. +e.g. a_b_c > a_b_c* > a_b* > a* > *a_b_c > *b_c > *c + +3) Exclusion tests prioritise by entry order. + +4) If duplicate node identifiers occur later in the list, they will replace any definitions of the earlier entry, but in the list position of the original. With the exception of "X," which is protected against overwriting. +Comparison occurs after +- inclusion lists are expanded to separate entries +- exclusion lists are put in canonical form, e.g. + m:-b,a = m:-b,a,b = m:-a,b,a = m:-a,a,b --> m:-a,b +Note that '*' is treated literally, without interpretation. + +Tip: Use precedence to make exceptions, e.g. + +* mymod.everyone_does_it +yourmod:exception + wins the match and returns no function. + + + + +** Exclusion patterns ** (extra) + +Precedence ensures that they only apply to the leftovers, after inclusion patterns take what they want. + +Patterns with more EXclusions and therefore fewer INclusions should have higher priority. +Usually, you should list wider exclusion patterns earlier, so their inclusions are not blocked. + +e.g. + +m:-a n.f <-- m:b and m:the_rest +m:-a,b n.g useless + +but, + +m:-a,b n.g <-- m:the_rest +m:-a n.f <-- m:b + +but also, + +m:-a,b n.g gets redefined in place +m:-a n.f <-- m:b +m:-b,a n.h <-- m:the_rest + + + + +====== Editing tips ====== + +Make your filter files easy to read and understand so you know what to expect. + +Group entries by mod. +Use blank lines if it helps. +Remember, if you need them, comments are on separate lines. +It's usually easier and clearer when temporarily disabling entries to indent them in place rather than move or remove them. + +When using globs: +List entries from high to low precedence. +"mod: " finishes a mod group, "* " finishes the list, if used. + diff --git a/README.md b/README.md index bd7bf9f..e806e43 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,40 @@ -# nuafan -NUA For Aganostic Nodes adds nua functionality to nodes from nua-ignorant mods +Minetest mod: nuafan + +==== NUA For Agnostic Nodes ==== + +Nuafan adds nua functionality to nodes from nua-ignorant mods. + +Specifically, on_construct and after_destruct node update alerts, + and user-defined callbacks allowing them to receive and respond + to alerts as well. + +A demo is enabled by default so you can play around with it in-game before deciding whether to use it seriously. +It reports nearby updates via chat. + +requires: nua +https://github.com/Aftermoth/nua + + +Limitations: + +1) Some node types already use on_construct or after_destruct to provide special functionality. +Nuafan cannot yet cooperate with existing definitions without breaking them, and so avoids altering them by default. + +2) Some node types repeatedly and ceaselessly destruct/construct without changing type. +Nuafan cannot yet distinguish between real and fake changes, so its best option is not to signal events from those nodes at all, although they may still receive them. +Additionally, large numbers of those nodes spam the event queue quite heavily however they are handled, and could impact performance, especially if extra tests are required. + +3) Map-generated nodes and trees do not receive extra functionality upon creation, although nodes in (2) and some plants aquire it without player intervention. This is generally desirable, although it can be difficult to remember which nodes of the same type are aware and which are not, e.g. between generated and user-placed stone. + + +---- + +Copyright (C) 2016 Aftermoth, Zolan Davis + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published +by the Free Software Foundation; either version 2.1 of the License, +or (at your option) version 3 of the License. + +http://www.gnu.org/licenses/lgpl-2.1.html + diff --git a/depends.txt b/depends.txt new file mode 100644 index 0000000..a93ce1d --- /dev/null +++ b/depends.txt @@ -0,0 +1,13 @@ +nua + +bones? +default? +doors? +farming? +fire? +flowers? +stairs? +tnt? +vessels? +wool? +xpanes? diff --git a/f_callbacks.txt b/f_callbacks.txt new file mode 100644 index 0000000..91f37f3 --- /dev/null +++ b/f_callbacks.txt @@ -0,0 +1,5 @@ +* nuafan.demo + + nuafan.demo allows blocks to report alerts they receive to nearby players via chat. + You can use this to test your filters. + diff --git a/f_xconstruct.txt b/f_xconstruct.txt new file mode 100644 index 0000000..2b1d694 --- /dev/null +++ b/f_xconstruct.txt @@ -0,0 +1,6 @@ +@safemode: + + No-change destruct/construct spammers. +default:dirt +default:dirt_with_grass +default:desert_sand diff --git a/f_xdestruct.txt b/f_xdestruct.txt new file mode 100644 index 0000000..2b1d694 --- /dev/null +++ b/f_xdestruct.txt @@ -0,0 +1,6 @@ +@safemode: + + No-change destruct/construct spammers. +default:dirt +default:dirt_with_grass +default:desert_sand diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..9c228da --- /dev/null +++ b/init.lua @@ -0,0 +1,101 @@ +--[[ + +Copyright (C) 2016 Aftermoth, Zolan Davis + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published +by the Free Software Foundation; either version 2.1 of the License, +or (at your option) version 3 of the License. + +http://www.gnu.org/licenses/lgpl-2.1.html + +--]] + +nuafan = {} + +nuafan.demo = function(ip,ep) + local players, near, pp = {}, 8 + for _,player in ipairs(minetest.get_connected_players()) do + pp = player:getpos() + if math.abs(pp.x-ip.x) + math.abs(pp.y-ip.y) + math.abs(pp.z-ip.z) < near then + table.insert(players,player) + end + end + if players[1] then + local m, p = minetest, '('..ep.x..','..ep.y..','..ep.z..')' + local i, e = m.get_node_or_nil(ip).name, m.get_node_or_nil(ep).name + local j, f = m.registered_nodes[i].description, (e=="air" and "Nothing") or m.registered_nodes[e].description + j, f =(j=="" and '['..i..']') or j, (f=="" and '['..e..']') or f + for _,pp in ipairs(players) do + m.chat_send_player(pp:get_player_name(),j..' sees '..f..' at '..p) + end + end +end + +-- Local -- + + +local this = minetest.get_current_modname() +local here = minetest.get_modpath(this)..'/' +local lookup, mlist, xclist, xdlist, cblist = dofile(here..'mkfilters.lua') + +local function shallow(t) + local d = {} + for a,b in pairs(t) do + d[a] = b + end + return d +end + +local function upgrade() + local mod,c,d,s + for nn,dfn in pairs(minetest.registered_nodes) do + + mod,c = string.match(nn,'^([^:]+):([^:]+)$') + if mod and mlist[mod] then + d = nil + + if not (lookup(xdlist,'@safemode:'..mod) and type(dfn.after_destruct) == "function") then + s = lookup(xdlist,nn) + if not s then + d=shallow(dfn) + d.after_destruct = function (pos, old) + nua.event(pos) + end + end + end + + if not (lookup(xclist,'@safemode:'..mod) and type(dfn.on_construct) == "function") then + s = lookup(xclist,nn) + if s~="X" then + local s2 = lookup(cblist,nn) + if not s or (s2 and s2~="") then + if not d then d=shallow(dfn) end + if s2 and s2~="" then + if s then + d.on_construct = function (pos) + minetest.get_meta(pos):set_string('on_nbr_update',s2) + end + else + d.on_construct = function (pos) + minetest.get_meta(pos):set_string('on_nbr_update',s2) + nua.event(pos) + end + end + else + d.on_construct = function (pos) + nua.event(pos) + end + end + end + end + end + + if d then + minetest.register_node(':'..nn,d) + end + end + end +end + +upgrade() diff --git a/mkfilters.lua b/mkfilters.lua new file mode 100644 index 0000000..9a281db --- /dev/null +++ b/mkfilters.lua @@ -0,0 +1,154 @@ +--[[ + +Copyright (C) 2016 Aftermoth, Zolan Davis + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published +by the Free Software Foundation; either version 2.1 of the License, +or (at your option) version 3 of the License. + +http://www.gnu.org/licenses/lgpl-2.1.html + +--]] + +local function lookup(t,n) +--[[ + returns + nil if invalid + false if not found + string if found +]] + local m , a = string.match(n,'^([^:]+:)([^:]+)$') + if m then +-- pre-generate possible * matches + local ps = {} + table.insert(ps,a) + local s,r = a,"" + while s and s~="" do + table.insert(ps,s..'*') + s,r = string.match(s, '^(.*)(_[^_]*'..r..')$') + end + s,r = a,"" + while s and s~="" do + table.insert(ps,'*'..s) + r,s = string.match(a,'^('..r..'[^_]*_)(.*)$') + end +-- inclusion -- '*' order + for _,v in ipairs(ps) do + if t[m..v] then + return t[m..v] + end + end +-- exclusion -- entry order + if t[m..'-'] then + local ok + for _,g in ipairs(t[m..'-']) do + ok = true + for _,v in ipairs(ps) do + if g[1][v] then + ok = false + break + end + end + if ok then + return g[2] + end + end + end +-- any in mod + if t[m] then + return t[m] + end +-- any + if t['*'] then + return t['*'] + end +-- no match + return false + end +-- invalid + return nil +end + + +local here = minetest.get_modpath(minetest.get_current_modname())..'/' + +local mkmlist = function () + local list = {} + local s + for ln in io.lines(here.."depends.txt") do + s=string.match(ln,'([^%s:?]+)') + if s then list[s] = 1 end + end + return list +end +local mlist = mkmlist() + +local function split(s,pfx,val) + local t={} + for v in string.gmatch(s,'([^,]+)') do t[pfx..v] = val end + return t +end +local function parse(s,s2) + local t,h = {}, {} + local m,e,a,c = string.match(s,'^([^:]+:?(-?))([^,]*(,?).*)$') + if e ~= '' then + t = split(a,'',1) + for n,_ in pairs(t) do + table.insert(h,n) + end + table.sort(h) + return { [0] = m, [1] = { [0]=table.concat(h,","), [1]=t, [2]=s2 }} + elseif c ~= '' then + t = split(a,m,s2) + return t + elseif s ~= '0' then + return { [s] = s2 } + end + return {} +end + +local function mkflist(src) + src=here..src + local list = {} + local s1,s2,p,h + local fh = io.open(src) + if fh then + io.close(fh) + for ln in io.lines(src) do + s1,s2=string.match(ln,'^([^%s]+)%s*([^%s]*)') + if s1 then + if s1 == "*" or string.sub(s1,1,1) == "@" or mlist[string.match(s1,'^([^:]+)') or ':'] then -- mod ok + p = parse(s1,s2) + if p[0] then + if not list[p[0] ] then + list[p[0] ] = {} + h=true + else + h=p[1][0] + for _,t in ipairs(list[p[0] ]) do + if h == t[0] then + t[2]=p[1][2] + h=false + break + end + end + end + if h then + table.insert(list[p[0] ],p[1]) + end + else + for k,v in pairs(p) do + if not (list[k] and list[k]=='X') then + list[k] = v + end + end + end + end + end + end + end + return list +end + +return lookup, mlist, mkflist('f_xconstruct.txt'), mkflist('f_xdestruct.txt'), mkflist('f_callbacks.txt')