Module:Buffer and GameDataMgr: Difference between pages

From ZeldaMods (Breath of the Wild)
(Difference between pages)
Jump to navigation Jump to search
imported>Leoetlino
m (1 revision imported)
 
imported>Leoetlino
 
Line 1: Line 1:
--[[=============================
{{Subsystem infobox|name=GameDataMgr|is_name_official=1|description=Manages game data flags|init_addr_switch150=0000007100DCE964}}
This Module was written by Alexander Zhikun He, also known as, User:Codehydro on the English Wikipedia
{{stub}}


All methods were developed independently and any resemblance to other string buffer libraries would be coincidental.
GameDataMgr holds the date of all game data flags and provides functions to get, set and reset them. It is also responsible for keeping track of "shop sold out" flags and resetting them regularly.
Furthermore, many methods will not work when compiled by standard Lua libraries as they depend on behaviors unique to
the MediaMiki Scribunto mod, which, for example, has a getmetatable() method that always returns nil on non-tables.
https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual


Source code comments may be thin at some points because they are intended to be supplemented by the documentation page:
Flag data is loaded from [[bgdata]] files and stored in [[TriggerParam]] objects. Transferring data from and to save files is done by interacting with the [[SaveSystem]] and [[SaveMgr]] subsystems.
https://en.wikipedia.org/wiki/Module:Buffer/doc


Licensed under Creative Commons Attribution-ShareAlike 3.0 Unported License
In debug versions, GameDataMgr is able to sync save data from and to a computer.
https://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License


https://en.wikipedia.org/wiki/Module:Buffer
== TriggerParams ==
https://en.wikipedia.org/wiki/User:Codehydro
There are at least two sets of TriggerParams at all times and up to four (param1, param, ???, GimmickResetBuffer). The exact purpose of each TriggerParam is currently unknown.
=============================--]]
local function Valid(v)--type validation
if v and v~=true then--reject nil/boolean; faster than 2 type() comparisons
local str = tostring(v)--functions not filtered since unlikely passed by accident (Scribunto does not have userdata/thread types)
if str~=v and str=='table' then return rawget(v, 1) and table.concat(v) end--tostring(string-type) returns same ref; same refs compare faster than type()
if str~='' then return str end--numbers are coerced to string per table.concat op; appending in string form saves ops on repeat concat
end
end
local noOp, MBpairs = function()end do local iMap, vMap, oMap, pIter, pOther, pFast, Next--Map
local function init()--init = noOp after first run
function Next(t) return next, t end--slightly faster to do this than to use select()
function pIter(t, k) k = (iMap[t] or MBpairs(t, true) and iMap[t])[not k and 1 or vMap[t][k]] return k, t[k] end--don't use rawget; accepting unmapped tables does not measurably affect performance.
function pOther(t, k) k = (oMap[t] or MBpairs(t, true) and oMap[t])[nil==k and 1 or vMap[t][k]] return k, t[k] end--comparison to nil because false is a valid key
function pFast(t, k) k = not k and 1 or k < (vMap[t] or #t) and k + 1 or nil return k, t[k] end--mapless iterator; almost as fast as native ipairs; slight performance penalty when length not cached
  --k and k < (vMap[t] or #t) and k + 1 or not k and 1 or nil return k, t[k] end--mapless iterator; almost as fast as native ipairs; slight performance penalty when length not cached
local mk = {__mode = 'k'}--use mode 'k'; found that mode 'kv' sometimes garbage collects maps mid-loop (may not error because iterators auto re-map, but that's expensive)
init, iMap, vMap, oMap = noOp, setmetatable({}, mk), setmetatable({}, mk), setmetatable({}, mk)--iMap is numeric keys, oMap is non-numeric keys, and vMap points to next key
end
function MBpairs(t, ...)--pairs always iterates in order
local iter, ex = ...
iter = iter==init()--nil
if iter and not oMap[t] and ex==nil and rawget(t, 1)~=nil and next(t, #t)==nil then--while possible to miss keys, more thorough check would negate the benefit of pFast
vMap[t] = #t return pFast, t, nil
elseif ... or not vMap[t] or select('#', ...)~=1 then
local ti, tn, to, n = {}, {}, {}, #t--reduces table lookups
iMap[t], vMap[t], oMap[t] = ti, tn, to
for k = 1, n do ti[k], tn[k] = k, k + 1 end--stage one avoids number type checking op in stage two for most numeric keys
for k in (ex or Next)(t) do
if not tn[k] then table.insert(tonumber(k)~=k and to or ti, k) end
end
if #ti~=n then
table.sort(ti)
for k = 1, #ti do tn[ti[k]] = k + 1 end--somewhat wasteful, but trying to avoid overwriting can be even more expensive
end
for k = 1, #to do tn[to[k]] = k + 1 end
end
return iter and pIter or oMap[t] and pOther or noOp, t--noOp for mapless
end
end
local parent, rawkey, spec do--new scope for variables not reused outside (reduces number of var names that need to checked outside of scope)
local mkv = {__mode='kv', __call=function(t,k,v)t[k]=v return k end}--shared meta for Buffer parent property, raw mode, and specialized functions
parent, rawkey, spec = setmetatable({}, mkv), setmetatable({}, mkv), setmetatable({}, mkv)--shared meta less memory
end


local MB, MBi, MBmix, buffHTML, gfuncs, noCache, Element do--minimize number of locals per scope to reduce time spent sifting through irrelevant variable names
After all GameData flag info has been loaded into this->triggerParam from the GameData archive ([[gamedata.sarc]]), another TriggerParam instance called param1 (this->triggerParam1) is constructed and data is copied from this->triggerParam. The copy function also builds the lists of flags that need to be reset (with a non-zero ResetType) and of boolean flags with (initialValue >> 1) != 0.
local _stream do local stream--keep stream near top of scope
local function init(f)--init = noOp after first run
local function each(self, ...)
for k = 1, select('#', ...) do
k = Valid(select(k, ...))--slightly faster than table.insert(self, (Valid(select(k, ...))))
if k then table.insert(self, k) end
end
return self
end
init, stream, _stream = noOp, {
__call = function(t, v) v = v and Valid(v) return v and table.insert(t, v) or t end,--last_concat cleared before entering stream mode
__index = function(t, i) return i=='each' and each or MB.__index(t, i) and setmetatable(t, MB)[i] end,--no table look up minimizes resources to retrieve the only stream function
__tostring = function(t) return setmetatable(t, MB)() end
} for k, v in next, MB do stream[k] = stream[k] or v end
setmetatable(stream, getmetatable(MB))
end
function _stream(self, ...) self.last_concat = init() return setmetatable(self, stream):each(...) end
end
local function isMBfunc(Buffer, s, ...)--helper for :getParent()-like methods (including getBuffer which does not return a parent)
return s and (select('#', ...)==0 and--eventually should figure out to make this work for :getHTML which is very similar
(not rawkey[s] and tostring(s):match'^_.*' and MB.__index(Buffer, s) and MB.__index(Buffer, s)(Buffer) or MBmix(Buffer, s))--unprefixed function names append as a string
or assert(MB.__index(Buffer, s), ('" %s " does not match any available Module:Buffer function'):format(s))(Buffer, ...)--getParent is a one-way trip so one-time assert not expensive
) or Buffer
end
local function MBselect(n, ...)--helper for :_out and :_str
local n, seps = n - 1, {select(2, ...)}
if type(seps[n])=='table' then
if buffHTML and rawget(seps[n], buffHTML) then return ... end
setmetatable(seps, {__index = setmetatable(seps[n], {__index = function(t) return rawget(t, 1) end})})[n] = nil
end
return ..., seps
end
local _inHTML do local lastBuffer, lastHTML
local function init(...)--init replaced and new version called on return
local create, mwFunc = mw.html.create do
local mwHTMLmeta = getmetatable(create())
buffHTML, mwFunc, _inHTML = setmetatable(mw.clone(mwHTMLmeta), getmetatable(MB)), mwHTMLmeta.__index--buffHTML declared near top of module; remove _inHTML from outer scope
function init(nodes, ...)
local name, args, tag = select(... and type(...)=='table' and 1 or 2, nil, ...)
tag = create(Valid(name), args)
if nodes then table.insert(nodes, tag.parent and tag or rawset(tag, 'parent', parent[nodes])) end
if args then
local a, b = args.selfClosing, args.parent
args.selfClosing, args.parent = nil
if next(args) then Element._add(parent(tag.nodes, tag), args) end
args.selfClosing, args.parent = a, b--in case args is reused
end
return tag
end
for k, v in next, {[mw] = mwHTMLmeta,
__call = function(h, v) return MBmix(spec[h.nodes] and h.nodes or spec(setmetatable(parent(h.nodes, h), MB), Element), v) end,
__concat = false,--false means take from MB
__eq = false
} do buffHTML[k] = v or MB[k] end
end
local nonSelf, BHi = {tag=true,done=true,allDone=true}, buffHTML.__index do local g
g = {__index = function(t, i)
if gfuncs and gfuncs[i] then g.__index, gfuncs = gfuncs return g.__index[i] end
end}
setmetatable(nonSelf, g)
setmetatable(BHi, g)
end
for k in next, nonSelf do--any HTML objects returned by these funcs will be granted Module:Buffer enhancements
local func = mwFunc[k]
BHi[k] = function(t, ...) local HTML = func(t, ...) return parent[HTML] and HTML or setmetatable(parent(HTML, t), buffHTML) end
end
do local function joinNode(HTML, sep)
local nodes, join = HTML.nodes
if noCache and rawkey[sep] or Valid(sep) then join, HTML.nodes = tostring(rawset(HTML, 'nodes', {MB.__call(nodes, sep)})), nodes end
return join or tostring(HTML)
end
for k, v in next, {
getParent = function(HTML, ...) lastHTML = HTML return MBi.getParent(HTML:allDone(), ...) end,--return to Buffer that created the HTML tree
getBuffer = function(HTML, ...) lastHTML = HTML return isMBfunc(lastBuffer, ...) end,--return to last used
killParent = function(HTML, ...) MBi.killParent(HTML:allDone(), ...) return HTML end,
_out = function(HTML, ...)
if ...==0 then MBi._out(HTML.nodes, ...) return HTML end
lastHTML, HTML = HTML, HTML:allDone()
local n, ops, seps = select('#', ...)
if n > 1 then
local ops, seps = MBselect(n, ...)
return parent[HTML]:_in(joinNode(HTML, rawget(seps, 0))):_out(ops, rawset(seps, buffHTML, true))
end
return parent[HTML]:_(joinNode(HTML, ...))
end,
_str = function(HTML, ...)--does not set lastHTML
if ...==0 then return joinNode(HTML, select(2, ...)) end--passing 0 strings without calling allDone()
local HTML, n = HTML:allDone(), select('#', ...)
if n > 1 then
local ops, seps = MBselect(n, ...)
return parent[HTML]:_in(joinNode(HTML, rawget(seps, 1))):_str(ops, rawset(seps, buffHTML, true))
end
return joinNode(HTML, ...)
end,
_parent = function(HTML, ...) table.insert(HTML.nodes, parent[HTML:allDone()]:_str(...)) return HTML end
} do BHi[k] = v end
end
do local htmlArg, skip, outFuncs = {parent=true,selfClosing=true,tagName=true}, {}
do local out local function func(nodes, ...) return out(parent[nodes], ...) end
outFuncs = setmetatable({
tag = function(nodes, ...) return parent(setmetatable(init(nodes, ...), buffHTML), parent[nodes]) end,
done = function(b, ops)
b = parent[b]
while b.parent and ops~=0 do b, ops = b.parent, ops and ops - 1 or 0 end
return b
end
}, {__index = function(nodes, i)
if rawget(BHi, i) then out = BHi[i] return func end--rawget to exclude globals
end})
end
Element = {
_add = function(nodes, t)
for k, v in MBpairs(t), t, skip[t] do (v~=true and MBmix or noOp)(nodes, v) end
local HTML = parent[nodes] for k, v in MBpairs(t, false) do
if htmlArg[k] then HTML[k] = v
elseif v and v~=true then
if nonSelf[k] then
if k=='tag' then
if type(v)=='table' then
skip[v], k = 1, rawset(create(Valid(v[1])), 'parent', HTML)
Element._add(spec(parent(k.nodes, k, table.insert(nodes, k)), Element), v)
if k.selfClosing then k.nodes = nil else spec[k.nodes], parent[k.nodes] = nil end--free memory/reduce clutter; parent ref will auto-unset when k.nodes is nil
if not k.tagName then k.styles, k.attributes = nil end
else table.insert(nodes, create(v)) end
elseif mwFunc[k] then
if k=='done' and tonumber(v)~=v and v[1] and tonumber(v[1])==v[1] then skip[v] = 1 end
MBmix(outFuncs[k](nodes, skip[v] and v[1]).nodes, v)
elseif v[1] or v[2] then
k = MBi[k](nodes, unpack(v, 1, rawset(skip, v, k=='_B' and 1 or 2)[v]))
Element._add(getmetatable(k) and rawget(k, 'nodes') or k, v)--if k is not a table, then v should not contain any extra keys or this may error.
else MBi[k](nodes, v) end--k probably == '_G' or '_R'
elseif mwFunc[k] then
if type(v)~='table' or rawget(v, 'nodes') then mwFunc[k](HTML, v)
else
local css = k=='css'
for x, y in MBpairs(v, true) do (y and y~=true and mwFunc[k] or noOp)(HTML, css and x:gsub('_', '-') or x, y) end--iterate non-numbers first
for _, y in MBpairs(v, nil) do (y and y~=true and mwFunc[k] or noOp)(HTML, y) end--don't bother with gsub since text must be quoted anyhow
end
elseif rawget(Element, k) or rawget(MBi, k) then
if tonumber(v)==v or v[1]==nil or getmetatable(v) then (Element[k] or MBi[k])(nodes, v)--v is probably string-able object, or a table to be handled by :_all
else (Element[k] or MBi[k])(nodes, unpack(v, 1, table.maxn(v))) end--v is definately a table
else mwFunc.css(HTML, k:gsub('_', '-', 1), tostring(v)) end--oddly enough, :_add clocked its fastest runtime after adding auto-gsub as a feature
skip[v] = nil
end
end
return nodes
end
}
local tempMeta = {mode='v', copy={styles=true,attributes=true}}
function tempMeta.__index(t, i) return tempMeta.copy[i] and rawset(t, i, MBi._cc(false, 0, t.orig[i]))[i] or t.orig[i] end
rawkey[setmetatable(Element, {__index = outFuncs, __concat=function(Element, v) return setmetatable({nodes=spec({}, Element),orig=parent[v]}, tempMeta) end})] = math.huge
end
function MBi:getHTML(...)
lastBuffer = self
if ... then
if select('#', ...)==1 then return not rawkey[s] and tostring(...):match'^_' and BHi[...] and BHi[...](lastHTML) or lastHTML(...)
else return assert(BHi[...], ('" %s " does not match any mw.html or Buffer-mw.html function'):format(tostring(...)))(lastHTML, select(2, ...)) end
end
return lastHTML
end
function MBi:_html(...) return MBi._(self, lastHTML, select(spec[self]==Element and select('#', ...)==0 and 1 or 2, true, ...)) end
return init(...)
end
function _inHTML(self, ...)
local HTML = init(nil, ...)
if HTML.selfClosing and spec[self]==Element then self.last_concat = table.insert(self, HTML) return self end
lastBuffer, lastHTML = self, setmetatable(parent(HTML, self), buffHTML)--set after 'args' table processed by :_add
return HTML
end
end
local _var, unbuild do local prev, rebuild
local function init(...)--init replaced before return
local function pick(b, v) return b and table.insert(b, v) or v end
local function c(a, num) return rawset(a.a or a, 0, a[0] and a[0] + a.c or num and a[1] or a[1]:byte())[0] end
local same, build, alt = {__tostring = function(a, b) return a.a[0] and pick(b, a.a.string and string.char(a.a[0]) or a.a.table and a.a[1][a.a[0]] or a.a[0]) end}, {
__index = {c = 1},
__tostring = function(t) return t:_build() end,
table = function(a, b) local i = next(a[1], a[0]) or a[0]==#a[1] and next(a[1]) return pick(b, rawset(a.a or a, 0, i)[1][i]) end,--change rate (a.c) ignored since users control the table's contents
number = function(a, b) return pick(b, c(a, true)) end,
string = function(a, b) return pick(b, string.char(c(a))) end
}, {__index = function(a, i) return a.a[i] end, __tostring = function(a, b) return (rawget(a, 0) and a[0]==tostring(a[0]) and rawset(a, 0, a[0]:byte()) or a).a._build(a, b) end}
local function shift(t, c)
t[0] = t[0] and t[0] + c or t:_build() and t[0] - t.c + c
if t.table then t[0] = (t[0] - 1) % #t[1] + 1 end
end
function rebuild(...)
local v, c = ...
if v or select('#', ...)==0 then
if v and not c then return prev end
local meta, c = select(v and 1 or 3, alt, c, same, 0)
return setmetatable({a = prev, _build = meta.__tostring, c = c}, meta)
elseif v==nil then--no-op
elseif c then shift(prev, c)--v == false
else prev:_build() end
end
init, noCache = function(v, c) prev = setmetatable({v, c = c, _build = build[type(v)] or v, [type(v)] = true, alt = {}}, build) return prev end, true
return init(...)
end
function unbuild(sep)
for k, v in MBpairs(sep, nil) do
k = getmetatable(v) if k and (k==build or k==alt) then shift(v.a or v, -v.c) end
end
end
function _var(self, ...)
local obj if ... and ...~=true then obj = init(...)
elseif prev then
if ...~=false then obj = rebuild(...)
else rebuild(...) end
end
return obj and MBi._(self, obj, nil, true) or self
end
end
local lib; MBi = setmetatable({stream = _stream,
_inHTML = _inHTML,
_var = _var,
_ = function(self, v, ...)
local at, raw = select(select('#', ...)==1 and ...==true and 1 or 2, nil, ...)
if raw then rawkey[self] = math.huge else v = Valid(v) end
if v or raw then
if at or rawkey[self] then raw = #self end--if length increases by more than one after table.insert, then set rawkey[self] = math.huge; rawkey[self] may be equal to a previous 'at'
at, self.last_concat = at and (tonumber(at)~=at and raw + at or at)
table.insert(self, select(at and 1 or 2, at, v))
if at and at < 0 or raw and #self - raw > 1 then rawkey[self] = math.huge elseif at and #self==raw then rawkey[self] = rawkey[self] and math.max(rawkey[self], at) or at end
end--above line looks bizarre because one table.insert op may make length jump from 0 to 8: local wtf={[2]=2,[4]=4,[8]=8}mw.log(#wtf,table.insert(wtf,1),#wtf)
return self
end,
_nil = function(self, at, ...)
if ...~=true and ...~=false then--faster than type(...) ~= 'boolean'
if not at or at=='0' then
self[#self] = ... if ... then rawkey[self] = math.huge end
else
local n, v = tonumber(at), ...
if n~=at then
if n then n = #self + at
elseif at~=true and select('#', ...)==0 then v, n = at, #self end
end
if n then
if v==nil and n > 0 then table.remove(self, n)
else self[math.floor(n)], rawkey[self] = v, math.huge end--floor position for consistency with Table library
end
end
self.last_concat = nil
end
return self
end,
_all = function(self, t, valKey)
for k, v in MBpairs(t) do MBmix(self, v, valKey) end
for k, v in valKey and MBpairs(t, false) or noOp, t do
if tonumber(v) then MBi._(self, k, v)--self not always a buffer
elseif rawget(MBi, k) and v and v~=true then
if v[1]==nil or getmetatable(v) then MBi[k](self, v)
else MBi[k](self, unpack(v, 1, table.maxn(v))) end
end
end
return self
end,
_str = function(t, ...)
local n = select('#', ...)
if n > 1 then
local k, ops, seps, r = 2, MBselect(n, ...)
r = MB(t(seps[1]))
while parent[t] and ops > 1 and r:_(parent[t](seps[k]), 1) do t, k, ops = parent[t], k + 1, ops - 1 end
return table.concat(r, seps[k] or nil)
end
return MB.__call(t, ...)
end,
_in = function (self, ...) return parent(MB(...), self) end,
_out = function(t, ...)
if ...==0 then return parent(t, parent[t], MBi._cc(t, t, MB.__call(t, (select(2, ...))), getmetatable(t))) end--love how :_cc needed nothing new to implement this *self pat on back*
local n = select('#', ...)
if n > 1 then
local k, ops, seps = 1, MBselect(n, ...)
while parent[t] and ops > 0 do t, k, ops = parent[t]:_(t(seps[k])), k + 1, ops - 1 end
elseif parent[t] then return parent[t]:_(t(...)) end
return t
end,
_cc = function(self, clear, copy, meta)
if clear then
if rawequal(clear, copy) then return self, spec[MBi._cc] and setmetatable(spec[MBi._cc], MB)--rawequal to avoid re-string via __eq in case both are different Buffer objects
elseif copy==true then copy = self end
if clear~=0 then
assert(type(clear)=='table', debug.traceback('Buffer:_cc can only "clear" tables. Did you forget to call with a colon?', 2))--errors can be hard to trace without this
for k in self and next or noOp, clear do rawset(clear, k, nil) end
else return MBi._cc(false, {unpack(copy)}, copy) end--copy length w/o empty strings; recursion to avoid self = false causing garbage collection (non-weak child may exist)
if self==false or copy and type(copy)=='table' then--self==false means copy is a table (saves a type op for recursive calls)
meta = meta or getmetatable(copy)
if self and #copy > 1 then--preserves length with empty strings; developed from studying http://www.lua.org/source/5.1/ltable.c.html
local n, null, i, e = #copy, {}, math.ldexp(2, select(2, math.frexp(#copy)) - 2)
e, spec[MBi._cc], parent[null] = i - 1, null, clear
for k = 1, e do table.insert(clear, false) end
while i<=n do table.insert(clear, i, '') i, null[i] = i + math.ldexp(2, select(2, math.frexp(n - i)) - 2), '' end
for k = 1, e do rawset(clear, k, nil) end
end
for k, v in next, copy do rawset(clear, k, type(v)=='table' and MBi._cc(false, 0, v) or v) end
elseif copy then rawset(clear, 1, (Valid(copy))) end
rawkey[setmetatable(clear, meta)], parent[clear] = rawkey[copy], parent[copy]
end
return self and rawset(self, 'last_concat', nil) or clear
end,
_parent = function(self, ...) return parent[self] and MBi._(self, parent[self]:_str(...)) or self end,
getParent = function(self, ...) return isMBfunc(parent[self] or parent[parent(self, setmetatable({}, MB))], ...) end,
killParent = function(self, ...) return parent[self] and isMBfunc(parent[self], ...) and parent(self) or self end,
_build = function(self, t) table.insert(t, self()) end,--for compatibility with mw.html:node()
last_concat = false--prevent library check
}, {__index = function(t, i)--import string, mw.text, and mw.ustring libraries on an as-needed basis
local func = string[i] or mw.text[i] or mw.ustring[i] or type(i)=='string' and mw.ustring[i:match'^u(.+)'] if func then
lib = lib or function (s, f, ...)
if parent[s] and next(s)==nil then return s:_((f(tostring(parent[Element and (spec[s]==Element and s:allDone() or spec[parent[s]]==Element and parent[s]) or s]), ...))) end
return f(tostring(s), ...)--not using ternary/logical operators here to allow multiple return values
end
return rawset(t, i, i:match'^u?gsub' and function(self, p, r, ...)return lib(self, func, p, r or '', ...)end--Why are ugsub/gsub special? because empty strings are against my religion!
or function(self, ...)return lib(self, func, ...)end)[i]
end
end})
end


function MBmix(t, v, ...) return v and ((type(v)~='table' or getmetatable(v)) and MBi._(t, v) or (select('#', ...)==0 and spec[t] and spec[t]._add or MBi._all)(t, v, ...)) or t end--:_all always passes two args
== Flags ==


local _G, new_G = _G--localize _G for console testing (console _G ~= module _G)
== GameDataMgr::Flags ==
return setmetatable({__index = function(t, i) return spec[t] and spec[t][i] or MBi[i] end,
Stored at GameDataMgr+0xC18 on Switch 1.5.0. Most flags are still poorly understood.
__call = function(t, ...)
 
local rawsep, sep, i, j, raw = noCache and rawkey[...] and ..., ...
Flags are checked in the main update function (GameDataMgr::calc). Some flags are used to request actions such as resetting all flags to their initial value; these are automatically unset after GameDataMgr has processed the request.
if i or j or rawsep or Valid(sep) then
 
raw, sep, i, j = rawkey[spec[t]] or rawkey[t], rawsep or Valid(sep), i and (i~=tonumber(i) and i + #t or i), j and (j~=tonumber(j) and j + #t or j)
<source lang="c++">
if rawsep or raw and (raw>=(j or #t) or i < 1) then
enum GameDataMgr::Flags
raw, i, j = {}, i and math.floor(i), j and math.floor(j)--floor for consistency with table.concat(t, sep, i, j), which ignores decimals
{
raw.lc, t.last_concat = t.last_concat--temporarily unset last_concat to prevent disqualification from mapless iteration
  GameDataMgr::Flags_1 = 0x1,
for k, v in MBpairs(t) do
  GameDataMgr::Flags_2 = 0x2,
if raw[1] or not i or k>=i then if j and k > j then break end
  /// Causes GameDataMgr to call TriggerParam::resetToInitialValues(this->triggerParam1),
if raw.s then raw.s = table.insert(raw, tostring(sep)) end--if sep contains v and v is a Buffer-variable, sep must be strung before v
  /// clear some values of this->triggerParam and invoke reset callbacks
k = Valid(v) if k then
  GameDataMgr::Flags_NeedResetAllToInitial = 0x4,
raw.s = rawsep or sep and raw[1] and table.insert(raw, sep)
  /// Causes GameDataMgr to call TriggerParam::reset(this->triggerParam1) (see below)
table.insert(raw, k)
  GameDataMgr::Flags_NeedReset = 0x8,
end
  /// [Debug only] Creates a TriggerParam instance called "GimmickResetBuffer" (if needed)
end
  /// and copies data from param1.
end
  /// A pointer to the instance is stored in GameDataMgr.
if rawsep and not raw.s then raw[#raw] = unbuild(sep) end--unbuild rawsep if final index in t was invalid
  GameDataMgr::Flags_NeedGimmickReset = 0x10,
t.last_concat = raw.lc return table.concat(raw)
  /// If this->gimmickResetBufferParam is non-null, copy it to triggerParam and to triggerParam1 (args: 1, 0, 0).
end
  GameDataMgr::Flags_NeedCopyGimmickParam = 0x20,
return table.concat(t, sep, i and math.max(i, 1), j and math.min(j, #t))
  /// If this->gimmickResetBufferParam is non-null, copy it to triggerParam and to triggerParam1 (args: 1, 1, 0).
end
  GameDataMgr::Flags_40 = 0x40,
return MB.__tostring(t)
  GameDataMgr::Flags_80 = 0x80,
end,
  GameDataMgr::Flags_100 = 0x100,
__tostring = function(t)
  GameDataMgr::Flags_200 = 0x200,
if t.last_concat then return t.last_concat end
  GameDataMgr::Flags_400 = 0x400,
local r = rawkey[spec[t]] or rawkey[t]
  /// Set from flag setter functions. The name is based on the fact that IsChangedByDebug gets set at the same time.
r = table.concat(r and r>=#t and MBi._all({}, t) or t)
  GameDataMgr::Flags_IsChangedByDebugMaybe = 0x800,
return (noCache or rawset(t, 'last_concat', r)) and r
  GameDataMgr::Flags_1000 = 0x1000,
end,
  GameDataMgr::Flags_2000 = 0x2000,
__concat = function(a, b)
  GameDataMgr::Flags_4000 = 0x4000,
if buffHTML then
  GameDataMgr::Flags_ChangeOnlyOnceMode = 0x8000,
for k = 1, 2 do local v = select(k, a, b)--faster than for k, v in pairs{a, b} do
  GameDataMgr::Flags_10000 = 0x10000,
if v and spec[v] and spec[v]==Element then
  GameDataMgr::Flags_20000 = 0x20000,
if parent[v].selfClosing then
  /// If set, GameDataMgr::calc will skip copying param1 to param (args: 0, 0, 0)
if rawequal(a, b) then return (not noCache or parent[v].tagName) and v:_str(0):rep(2) or v:_str(0)..v:_str(0) end--rawequal avoids premature tostring of Buffer:_var objects;
  GameDataMgr::Flags_DisableParam1ToParamSync = 0x40000,
b, a = select(k, b, parent[v], a)
  GameDataMgr::Flags_IsRestartFromGameOverMaybe = 0x80000,
else local temp = Element .. v --helper method; returns a mirror of parent[v]
  GameDataMgr::Flags_DoNotResetToInitialFromRadarMgr = 0x100000,
MBmix(MBmix(parent(temp.nodes, temp), a), k==1 and spec[b]==Element and parent[b] or b)
};
return buffHTML.__tostring(setmetatable(temp, {__index=parent[v], __mode='v'}))--switch from tempMeta to avoid MBi._cc op of styles/attributes
</source>
end
 
end
== Reset flags ==
end
Stored at GameDataMgr+0xC1C on Switch 1.5.0.
end
{|class="wikitable"
return table.concat(MBmix(MBmix({}, a), b))
! Flag !! Description
end,
|-
__pairs = MBpairs,
| 1 || ?
__ipairs = MBpairs,
|-
__eq = function(a, b) return tostring(a)==tostring(b) end--avoid a==b in this module; use rawequal(a,b) when they may be different Buffers (premature tostring waste ops and is bad for Buffer:_var)
| 2 || Corresponds to reset type 1.
}, {__tostring = function()return''end,
|-
__call = function(self, ...) MB = MB or self
| 4 || Corresponds to reset type 2.
if new_G then if ... and _G and ...==_G then new_G = ... end
|-
elseif ... and (...==_G or type(...)=='table' and (...)._G==...) then
| 8 || Corresponds to reset type 3.
local Nil, mG = {}, (...):getmetatable() or (...):setmetatable{}:getmetatable()
|-
new_G, _G, gfuncs = ..., ..., {--gfuncs stored for Buffer:_inHTML; new_G is a is a Module:Buffer local declared just before the final return statement.
| 0x10 || Corresponds to reset type 4.
_G = function(self, i, ...)
|}
local X, save = rawget(new_G, i), select('#', ...)==0 and self or ...
 
if i and i~=true and not (X and save and rawequal(X, save)) and rawset(new_G, i, save) and (X~=nil or save==nil and new_G[i]~=nil) then--rawequal in case X is another buffer
== Reset process ==
local mG = getmetatable(new_G) or {__call=mG.__call}
All game data flags for which <code>(1 << resetType) & resetFlags</code> is true are reset to their initial values.
if mG.__index then pcall(rawset, mG.__index, i, X)
 
else mG.__index = setmetatable(new_G, mG) and {[i] = X} end
TriggerParam::reset processes up to 1024 flags at a time. The function returns the number of remaining flags to reset and flag 0x08 (GameDataMgr::Flags_NeedReset) is only cleared when all flags have been reset.
end
 
return self, ...--avoiding __eq with rawequal(self,save) is overkill since buffers can self-save without being passed as save
=== bool and s32 flags with reset type 3 ===
end,
bool and s32 flags with reset type 3, and with a flag name hash that is listed in ShopAreaInfo (in [[ShopGameDataInfo.byml]]) receive special treatment.
_R = function(self, i, v, m)
 
if i~='new_G' then if i and i~=true then rawset(new_G, i , v) end
If:
elseif not v or v==true or v._G~=_G then new_G = setmetatable(v~=true and v or {}, {__call = mG.__call, __index = v~=true and m~=true and (m or new_G) or nil})
* the player is in none of the shop areas that are associated with the flag (or in the Dealer's area for area-less flags); or if the player is not on MainField
else new_G, (not m and (m~=nil or v==new_G) and Nil or getmetatable(v)).__index = v, m~=true and (m or new_G) or nil end--setting Nil.__index is noOp
* '''and''', for items with the <code>Arrow</code> tag: if the player has fewer than {itemSaleRevivalCount} arrows in their inventory
return self
Then:
end,
* The flag is reset to its initial value.
_2 = function(self, ...)
* For bool flags: All associated sold out flags are reset to their initial values.
if new_G[...]~=nil then return new_G[...] end--higher priority so Buffer:_G('new_G', ...) can prevent an overwrite
 
if ...=='new_G' then return rawset((select('#', ...)~=1 and MBi._R(new_G, ...) or new_G), '_G', _G) end
[[Category: Internals]]
return select(select('#', ...)==1 and 1 or 2, self:_G(...))--return only one value; 'return select(2, self:_G(...)) or self' doesn't work for returning nil
[[Category: Subsystems (BotW)]]
end,
_B = function(self, v) return v or v==nil and Nil end
} for k, v in next, gfuncs do MBi[k] = v end
setmetatable(Nil,{__concat=MB.__concat,__newindex=noOp,__call=noOp,__tostring=noOp,__metatable=MB,__index=setmetatable({_B=MBi._B,_=function()return Nil end,last_concat=''},
{__index=function(t,i)return (MBi[i] or i and not tonumber(i)) and t._ or nil end})})
function mG.__call(G, k, ...) return (k._G or G.type(k)=='table') and (G.select('#', ...)~=1 and G.rawset(k, ...) or G:rawset(..., k) and k) or G:rawset(k, (...)) and ... end
end
local new = setmetatable({}, self)
if ... and (...)==new_G then return select(2, ...) and MBmix(new:_G((select(2, ...))), select(3, ...)) or new end
return ... and MBi._(new, ...) or new
end,
__index = function(t, i)
MB = MB or t return MBi[i] and function(...) return MBi[i](setmetatable({}, t), select(...==t and 2 or 1,...)) end
end
})

Revision as of 09:47, 31 December 2018

GameDataMgr
Subsystem
Official name Yes
Description Manages game data flags
Init function Switch 1.5.0: 0000007100DCE964
Wii U 1.5.0: ???
Debug only No


GameDataMgr holds the date of all game data flags and provides functions to get, set and reset them. It is also responsible for keeping track of "shop sold out" flags and resetting them regularly.

Flag data is loaded from bgdata files and stored in TriggerParam objects. Transferring data from and to save files is done by interacting with the SaveSystem and SaveMgr subsystems.

In debug versions, GameDataMgr is able to sync save data from and to a computer.

TriggerParams

There are at least two sets of TriggerParams at all times and up to four (param1, param, ???, GimmickResetBuffer). The exact purpose of each TriggerParam is currently unknown.

After all GameData flag info has been loaded into this->triggerParam from the GameData archive (gamedata.sarc), another TriggerParam instance called param1 (this->triggerParam1) is constructed and data is copied from this->triggerParam. The copy function also builds the lists of flags that need to be reset (with a non-zero ResetType) and of boolean flags with (initialValue >> 1) != 0.

Flags

GameDataMgr::Flags

Stored at GameDataMgr+0xC18 on Switch 1.5.0. Most flags are still poorly understood.

Flags are checked in the main update function (GameDataMgr::calc). Some flags are used to request actions such as resetting all flags to their initial value; these are automatically unset after GameDataMgr has processed the request.

enum GameDataMgr::Flags
{
  GameDataMgr::Flags_1 = 0x1,
  GameDataMgr::Flags_2 = 0x2,
  /// Causes GameDataMgr to call TriggerParam::resetToInitialValues(this->triggerParam1),
  /// clear some values of this->triggerParam and invoke reset callbacks
  GameDataMgr::Flags_NeedResetAllToInitial = 0x4,
  /// Causes GameDataMgr to call TriggerParam::reset(this->triggerParam1) (see below)
  GameDataMgr::Flags_NeedReset = 0x8,
  /// [Debug only] Creates a TriggerParam instance called "GimmickResetBuffer" (if needed)
  /// and copies data from param1.
  /// A pointer to the instance is stored in GameDataMgr.
  GameDataMgr::Flags_NeedGimmickReset = 0x10,
  /// If this->gimmickResetBufferParam is non-null, copy it to triggerParam and to triggerParam1 (args: 1, 0, 0).
  GameDataMgr::Flags_NeedCopyGimmickParam = 0x20,
  /// If this->gimmickResetBufferParam is non-null, copy it to triggerParam and to triggerParam1 (args: 1, 1, 0).
  GameDataMgr::Flags_40 = 0x40,
  GameDataMgr::Flags_80 = 0x80,
  GameDataMgr::Flags_100 = 0x100,
  GameDataMgr::Flags_200 = 0x200,
  GameDataMgr::Flags_400 = 0x400,
  /// Set from flag setter functions. The name is based on the fact that IsChangedByDebug gets set at the same time.
  GameDataMgr::Flags_IsChangedByDebugMaybe = 0x800,
  GameDataMgr::Flags_1000 = 0x1000,
  GameDataMgr::Flags_2000 = 0x2000,
  GameDataMgr::Flags_4000 = 0x4000,
  GameDataMgr::Flags_ChangeOnlyOnceMode = 0x8000,
  GameDataMgr::Flags_10000 = 0x10000,
  GameDataMgr::Flags_20000 = 0x20000,
  /// If set, GameDataMgr::calc will skip copying param1 to param (args: 0, 0, 0)
  GameDataMgr::Flags_DisableParam1ToParamSync = 0x40000,
  GameDataMgr::Flags_IsRestartFromGameOverMaybe = 0x80000,
  GameDataMgr::Flags_DoNotResetToInitialFromRadarMgr = 0x100000,
};

Reset flags

Stored at GameDataMgr+0xC1C on Switch 1.5.0.

Flag Description
1 ?
2 Corresponds to reset type 1.
4 Corresponds to reset type 2.
8 Corresponds to reset type 3.
0x10 Corresponds to reset type 4.

Reset process

All game data flags for which (1 << resetType) & resetFlags is true are reset to their initial values.

TriggerParam::reset processes up to 1024 flags at a time. The function returns the number of remaining flags to reset and flag 0x08 (GameDataMgr::Flags_NeedReset) is only cleared when all flags have been reset.

bool and s32 flags with reset type 3

bool and s32 flags with reset type 3, and with a flag name hash that is listed in ShopAreaInfo (in ShopGameDataInfo.byml) receive special treatment.

If:

  • the player is in none of the shop areas that are associated with the flag (or in the Dealer's area for area-less flags); or if the player is not on MainField
  • and, for items with the Arrow tag: if the player has fewer than {itemSaleRevivalCount} arrows in their inventory

Then:

  • The flag is reset to its initial value.
  • For bool flags: All associated sold out flags are reset to their initial values.