--------------------------- -- SmartFS: Smart Formspecs -- License: CC0 or WTFPL -- by Rubenwardy --------------------------- local has_inventory_plus = core.get_modpath("inventory_plus") and true or false local has_unified_inventory = core.get_modpath("unified_inventory") and true or false smartfs = { _fdef = {}, _edef = {}, opened = {}, inv = {} } -- the smartfs() function function smartfs.__call(_, name) return smartfs._fdef[name] end -- Register forms and elements function smartfs.create(name, onload) if smartfs._fdef[name] then error("SmartFS - (Error) Form " .. name .. " already exists!") end if smartfs.loaded and not smartfs._loaded_override then error("SmartFS - (Error) Forms should be declared while the game loads.") end smartfs._fdef[name] = { _reg = onload, name = name, show = smartfs._show_ } return smartfs._fdef[name] end function smartfs.override_load_checks() smartfs._loaded_override = true end core.after(0, function() smartfs.loaded = true end) function smartfs.dynamic(name, player) if not smartfs._dynamic_warned then smartfs._dynamic_warned = true print("SmartFS - (Warning) On the fly forms are being used. May cause bad things to happen") end local state = smartfs._makeState_({ name = name }, player, nil, false) state.show = state._show_ smartfs.opened[player] = state return state end function smartfs.element(name, data) if smartfs._edef[name] then error("SmartFS - (Error) Element type " .. name .. " already exists!") end smartfs._edef[name] = data return smartfs._edef[name] end function smartfs.inventory_mod() if has_unified_inventory then return "unified_inventory" elseif has_inventory_plus then return "inventory_plus" else return nil end end function smartfs.add_to_inventory(form, icon, title) if has_unified_inventory then unified_inventory.register_button(form.name, { type = "image", image = icon, }) unified_inventory.register_page(form.name, { -- (player, formspec) get_formspec = function(player) local name = player:get_player_name() local opened = smartfs._show_(form, name, nil, true) return { formspec = opened:_getFS_(false) } end }) return true elseif has_inventory_plus then core.register_on_joinplayer(function(player) inventory_plus.register_button(player, form.name, title) end) core.register_on_player_receive_fields(function(player, formname, fields) if formname == "" and fields[form.name] then local name = player:get_player_name() local opened = smartfs._show_(form, name, nil, true) inventory_plus.set_inventory_formspec(player, opened:_getFS_(true)) end end) return true else return false end end function smartfs._makeState_(form, player, params, is_inv) return { _ele = {}, def = form, player = player, param = params or {}, is_inv = is_inv, get = function(self,name) return self._ele[name] end, close = function(self) self.closed = true end, size = function(self, w, h) self._size = { w = w, h = h } end, _getFS_ = function(self,size) local res = "" if self._size and size then res = "size[" .. self._size.w .. "," .. self._size.h .. "]" end for _, val in pairs(self._ele) do res = res .. val:build() end return res end, _show_ = function(self) if self.is_inv then if has_unified_inventory then unified_inventory.set_inventory_formspec( core.get_player_by_name(self.player), self.def.name) elseif has_inventory_plus then inventory_plus.set_inventory_formspec( core.get_player_by_name(self.player), self:_getFS_(true)) end else local res = self:_getFS_(true) core.show_formspec(player, form.name, res) end end, load = function(self, file_name) local file = io.open(file_name, "r") if file then local data = core.deserialize(file:read("*all")) file:close() if type(data) == "table" then if data.size then self._size = data.size end for _, val in pairs(data.ele) do self:element(val.type, val) end return true end end return false end, save = function(self, file_name) local res = { ele = {} } if self._size then res.size = self._size end for key, val in pairs(self._ele) do res.ele[key] = val.data end local file = io.open(file_name, "w") if file then file:write(core.serialize(res)) file:close() return true end return false end, setparam = function(self, key, value) if not key then return end self.param[key] = value return true end, getparam = function(self, key, default) if not key then return end return self.param[key] or default end, button = function(self, x, y, w, h, name, text, exitf) if exitf == nil then exitf = false end return self:element("button", { pos = { x = x, y = y }, size = { w = w, h = h }, name = name, value = text, closes = exitf }) end, label = function(self, x, y, name, text) return self:element("label", { pos = { x = x, y = y }, name = name, value = text }) end, toggle = function(self, x, y, w, h, name, list) return self:element("toggle", { pos = { x = x, y = y }, size = { w = w, h = h }, name = name, id = 1, list = list }) end, field = function(self, x, y, w, h, name, label) return self:element("field", { pos = { x = x, y = y }, size = { w = w, h = h }, name = name, value = "", label = label }) end, pwdfield = function(self, x, y, w, h, name, label) local res = self:element("field", { pos = { x = x, y = y }, size = { w = w, h = h }, name = name, value = "", label = label }) res:isPassword(true) return res end, textarea = function(self, x, y, w, h, name, label) local res = self:element("field", { pos = { x = x, y = y }, size = { w = w, h = h }, name = name, value = "", label = label }) res:isMultiline(true) return res end, image = function(self, x, y, w, h, name, img) return self:element("image", { pos = { x = x, y = y }, size = { w = w, h = h }, name = name, value = img }) end, checkbox = function(self, x, y, name, label, selected) return self:element("checkbox", { pos = { x = x, y = y }, name = name, value = selected, label = label }) end, listbox = function(self, x, y, w, h, name, selected, transparent) return self:element("list", { pos = { x = x, y = y }, size = { w = w, h = h }, name = name, selected = selected, transparent = transparent }) end, inventory = function(self, x, y, w, h, name) return self:element("inventory", { pos = { x = x, y = y }, size = { w = w, h = h }, name = name }) end, element = function(self, typen, data) local type = smartfs._edef[typen] if not type then error("Element type " .. typen .. " does not exist!") end if self._ele[data.name] then error("Element " .. data.name .. " already exists") end data.type = typen local ele = { name = data.name, root = self, data = data, remove = function(self2) self2.root._ele[self2.name] = nil end } for key, val in pairs(type) do ele[key] = val end self._ele[data.name] = ele return self._ele[data.name] end } end -- Show a formspec to a user function smartfs._show_(form, player, params, is_inv) local state = smartfs._makeState_(form, player, params, is_inv) state.show = state._show_ if form._reg(state) ~= false then if not is_inv then smartfs.opened[player] = state state:_show_() else smartfs.inv[player] = state end end return state end -- Receive fields from formspec local function _sfs_recieve_(state, name, fields) if fields.quit == "true" then if not state.is_inv then smartfs.opened[name] = nil end return true end for key, val in pairs(fields) do if state._ele[key] then state._ele[key].data.value = val end end for _, val in pairs(state._ele) do if val.submit then if val:submit(fields) == true then return true end end end if state.closed ~= true then state:_show_() else core.show_formspec(name, "", "size[5,1]label[0,0;Formspec closing not yet created!]") if not state.is_inv then smartfs.opened[name] = nil end end return true end core.register_on_player_receive_fields(function(player, formname, fields) local name = player:get_player_name() if smartfs.opened[name] and not smartfs.opened[name].is_inv then if smartfs.opened[name].def.name == formname then local state = smartfs.opened[name] return _sfs_recieve_(state, name, fields) else smartfs.opened[name] = nil end elseif smartfs.inv[name] and smartfs.inv[name].is_inv then local state = smartfs.inv[name] _sfs_recieve_(state, name, fields) end return false end) ----------------------------------------------------------------- ------------------------- ELEMENTS ---------------------------- ----------------------------------------------------------------- smartfs.element("button", { build = function(self) local common_prefix = self.data.pos.x .. "," .. self.data.pos.y .. ";" .. self.data.size.w .. "," ..self.data.size.h local common_sufix = ";" .. self.name .. ";" .. self.data.value .. "]" if self.data.img then return "image_button[" .. common_prefix .. ";" .. self.data.img .. common_sufix else if self.data.closes then return "button_exit[" .. common_prefix .. common_sufix else return "button[" .. common_prefix .. common_sufix end end end, -- (self, fields, state) submit = function(self, fields) if fields[self.name] and self._click then self:_click(self.root) end if self.data.closes then return true end end, setPosition = function(self, x, y) self.data.pos = { x = x, y = y } end, getPosition = function(self) return self.data.pos end, setSize = function(self, w, h) self.data.size = { w = w, h = h } end, getSize = function(self) return self.data.size end, onClick = function(self, func) self._click = func end, click = function(self, func) self._click = func end, setText = function(self, text) self.data.value = text end, getText = function(self) return self.data.value end, setImage = function(self, image) self.data.img = image end, getImage = function(self) return self.data.img end, setClose = function(self, bool) self.data.closes = bool end }) smartfs.element("toggle", { build = function(self) return "button[" .. self.data.pos.x .. "," .. self.data.pos.y .. ";" .. self.data.size.w .. "," .. self.data.size.h .. ";" .. self.name .. ";" .. self.data.list[self.data.id] .. "]" end, submit = function(self, fields) if fields[self.name] then self.data.id = self.data.id + 1 if self.data.id > #self.data.list then self.data.id = 1 end if self._tog then self:_tog(self.root) end end end, onToggle = function(self, func) self._tog = func end, setPosition = function(self, x, y) self.data.pos = { x = x, y = y } end, getPosition = function(self) return self.data.pos end, setSize = function(self, w, h) self.data.size = { w = w, h = h } end, getSize = function(self) return self.data.size end, setId = function(self, id) self.data.id = id end, getId = function(self) return self.data.id end, getText = function(self) return self.data.list[self.data.id] end }) smartfs.element("label", { build = function(self) return "label[" .. self.data.pos.x .. "," .. self.data.pos.y .. ";" .. self.data.value .. "]" end, setPosition = function(self, x, y) self.data.pos = { x = x, y = y } end, getPosition = function(self) return self.data.pos end, setText = function(self, text) self.data.value = text end, getText = function(self) return self.data.value end }) smartfs.element("field", { build = function(self) local common = self.data.pos.x .. "," .. self.data.pos.y .. ";" .. self.data.size.w .. "," .. self.data.size.h .. ";" .. self.name .. ";" .. self.data.label if self.data.ml then return "textarea[" .. common .. ";" .. self.data.value .. "]" elseif self.data.pwd then return "pwdfield[" .. common .. "]" else return "field[" .. common .. ";" .. self.data.value .. "]" end end, setPosition = function(self, x, y) self.data.pos = { x = x, y = y } end, getPosition = function(self) return self.data.pos end, setSize = function(self, w, h) self.data.size = { w = w, h = h } end, getSize = function(self) return self.data.size end, setText = function(self, text) self.data.value = text end, getText = function(self) return self.data.value end, isPassword = function(self, bool) self.data.pwd = bool end, isMultiline = function(self, bool) self.data.ml = bool end }) smartfs.element("image", { build = function(self) return "image[" .. self.data.pos.x .. "," .. self.data.pos.y .. ";" .. self.data.size.w .. "," .. self.data.size.h .. ";" .. self.data.value .. "]" end, setPosition = function(self, x, y) self.data.pos = { x = x, y = y } end, getPosition = function(self) return self.data.pos end, setSize = function(self, w, h) self.data.size = { w = w, h = h } end, getSize = function(self) return self.data.size end, setImage = function(self, text) self.data.value = text end, getImage = function(self) return self.data.value end }) smartfs.element("checkbox", { build = function(self) local out = "checkbox[" .. self.data.pos.x .. "," .. self.data.pos.y .. ";" .. self.name .. ";" .. self.data.label if self.data.value then return out .. ";true]" else return out .. ";false]" end end, setPosition = function(self, x, y) self.data.pos = { x = x, y = y } end, getPosition = function(self) return self.data.pos end, setSize = function(self, w, h) self.data.size = { w = w, h = h } end, getSize = function(self) return self.data.size end, setText = function(self, text) self.data.value = text end, getText = function(self) return self.data.value end }) smartfs.element("list", { build = function(self) if not self.data.items then self.data.items = {} end local listformspec = "textlist[" .. self.data.pos.x .. "," .. self.data.pos.y .. ";" .. self.data.size.w .. "," .. self.data.size.h .. ";" .. self.data.name .. ";" .. table.concat(self.data.items, ",") .. ";" .. tostring(self.data.selected or "") .. ";" .. tostring(self.data.transparent or "false") .. "]" return listformspec end, submit = function(self, fields) if fields[self.name] then local _type = string.sub(fields[self.data.name], 1, 3) local index = string.sub(fields[self.data.name], 5) if _type == "CHG" and self._click then self:_click(self.root, index) elseif _type == "DCL" and self._doubleClick then self:_doubleClick(self.root, index) end end end, onClick = function(self, func) self._click = func end, click = function(self, func) self._click = func end, onDoubleClick = function(self, func) self._doubleClick = func end, doubleclick = function(self, func) self._doubleClick = func end, setPosition = function(self, x, y) self.data.pos = { x = x, y = y } end, getPosition = function(self) return self.data.pos end, setSize = function(self, w, h) self.data.size = { w = w, h = h } end, getSize = function(self) return self.data.size end, addItem = function(self, item) if not self.data.items then self.data.items = {} end table.insert(self.data.items, item) end, removeItem = function(self, idx) if not self.data.items then self.data.items = {} end table.remove(self.data.items, idx) end, popItem = function(self) if not self.data.items then self.data.items = {} end local item = self.data.items[#self.data.items] table.remove(self.data.items) return item end }) smartfs.element("inventory", { build = function(self) return "list[" .. (self.data.location or "current_player") .. ";" .. self.name .. ";" .. self.data.pos.x .. "," .. self.data.pos.y .. ";" .. self.data.size.w .. "," .. self.data.size.h .. ";" .. (self.data.index or "") .. "]" end, setPosition = function(self, x, y) self.data.pos = { x = x, y = y } end, getPosition = function(self) return self.data.pos end, setSize = function(self, w, h) self.data.size = { w = w, h = h } end, getSize = function(self) return self.data.size end, -- available inventory locations -- "current_player": Player to whom the menu is shown -- "player:": Any player -- "nodemeta:,,": Any node metadata -- "detached:": A detached inventory -- "context" does not apply to smartfs, since there is no node-metadata as context available setLocation = function(self, location) self.data.location = location end, getLocation = function(self) return self.data.location or "current_player" end, usePosition = function(self, pos) self.data.location = string.format("nodemeta:%d,%d,%d", pos.x, pos.y, pos.z) end, usePlayer = function(self, name) self.data.location = "player:" .. name end, useDetached = function(self, name) self.data.location = "detached:" .. name end, setIndex = function(self, index) self.data.index = index end, getIndex = function(self) return self.data.index end }) smartfs.element("code", { build = function(self) if self._build then self:_build() end return self.data.code end, submit = function(self, fields) if self._sub then self:_sub(fields) end end, onSubmit = function(self, func) self._sub = func end, onBuild = function(self, func) self._build = func end, setCode = function(self, code) self.data.code = code end, getCode = function(self) return self.data.code end })