From 808b3cbf0461d454b5fb44915243e5f682e27669 Mon Sep 17 00:00:00 2001 From: Jesse McDonald Date: Sat, 3 Jun 2017 16:40:32 -0500 Subject: [PATCH] Expand formlib & distinguish items by wear in market summary. --- atm.lua | 19 +++--- digital_mailbox.lua | 20 +++--- exchange.lua | 55 ++++++++------- exchange_machine.lua | 129 ++++++++++++++++------------------- formlib.lua | 158 ++++++++++++++++++++++++++++++++----------- 5 files changed, 220 insertions(+), 161 deletions(-) diff --git a/atm.lua b/atm.lua index 79fd8e7..bdde4db 100644 --- a/atm.lua +++ b/atm.lua @@ -33,7 +33,7 @@ local function wire_fs(fs, p_name) fs:size(4,5) if balance then - -- To prevent duplicates + -- To detect duplicate/stale form submission fs:field(-100, -100, 0,0, "trans_id", "", unique()) fs:label(0.50,0.325, "Balance: " .. balance) @@ -79,13 +79,12 @@ local function log_fs(fs, p_name) fs:label(0,0, "Transaction Log") - fs("tablecolumns[text;text]") + fs:element("tablecolumns", "text", "text") + fs("table[0,0.75;13.75,6.75;log_table;Time,Message") - for _, entry in ipairs(exchange:player_log(p_name)) do - fs(",", formlib.escape(entry.Time), ",", formlib.escape(entry.Message)) + fs(","):escape_list(entry.Time, entry.Message) end - fs("]") fs:button(6,7.5, 2,1, "logout", "Log Out") @@ -100,9 +99,6 @@ local function main_menu_fs(fs, p_name) end -local trans_ids = {} - - local function show_atm_form(fs_fn, p_name, ...) local fs = formlib.Builder() fs_fn(fs, p_name, ...) @@ -110,15 +106,18 @@ local function show_atm_form(fs_fn, p_name, ...) end +local trans_ids = {} + minetest.register_on_player_receive_fields(function(player, formname, fields) if formname ~= atm_form then return end if fields.quit then return true end local p_name = player:get_player_name() - local this_id = fields.trans_id + local this_id = tonumber(fields.trans_id) - if this_id and this_id == trans_ids[p_name] then + if this_id and trans_ids[p_name] and this_id <= trans_ids[p_name] then + -- Ignore duplicate/stale form submittal return true end diff --git a/digital_mailbox.lua b/digital_mailbox.lua index f333245..000f9a8 100644 --- a/digital_mailbox.lua +++ b/digital_mailbox.lua @@ -26,19 +26,15 @@ end local function mk_inbox_list(fs, results, x, y, w, h) - fs("textlist[", x, ",", y, ";", w, ",", h, ";result_list;") - - local sep = nil - for i, row in ipairs(results) do - fs(sep) - fs:escape(row.Amount, " ", row.Item) - if row.Wear > 0 then - fs:escape(" (", wear_string(row.Wear), ")") + fs:textlist(x,y, w,h, "result_list", function(add_row) + for i, row in ipairs(results) do + local wear_suffix = nil + if row.Wear > 0 then + wear_suffix = " (" .. wear_string(row.Wear) .. ")" + end + add_row(row.Amount, " ", row.Item, wear_suffix) end - sep = "," - end - - fs("]") + end) end diff --git a/exchange.lua b/exchange.lua index e01249d..1c2f569 100644 --- a/exchange.lua +++ b/exchange.lua @@ -16,13 +16,13 @@ end)({}) local init_query = [=[ BEGIN TRANSACTION; -CREATE TABLE if not exists Credit +CREATE TABLE IF NOT EXISTS Credit ( Owner TEXT PRIMARY KEY NOT NULL, Balance INTEGER NOT NULL ); -CREATE TABLE if not exists Log +CREATE TABLE IF NOT EXISTS Log ( Id INTEGER PRIMARY KEY AUTOINCREMENT, Recipient TEXT NOT NULL, @@ -30,7 +30,7 @@ CREATE TABLE if not exists Log Message TEXT NOT NULL ); -CREATE TABLE if not exists Orders +CREATE TABLE IF NOT EXISTS Orders ( Id INTEGER PRIMARY KEY AUTOINCREMENT, Poster TEXT NOT NULL, @@ -43,7 +43,7 @@ CREATE TABLE if not exists Orders Rate INTEGER NOT NULL CHECK(Rate > 0) ); -CREATE TABLE if not exists Inbox +CREATE TABLE IF NOT EXISTS Inbox ( Id INTEGER PRIMARY KEY AUTOINCREMENT, Recipient TEXT NOT NULL, @@ -52,44 +52,49 @@ CREATE TABLE if not exists Inbox Amount INTEGER NOT NULL CHECK(Amount > 0) ); -CREATE INDEX if not exists index_log +CREATE INDEX IF NOT EXISTS index_log ON Log (Recipient, Time); -CREATE INDEX if not exists index_orders +CREATE INDEX IF NOT EXISTS index_orders ON Orders (Exchange, Type, Item, Rate, Wear, Time); -CREATE INDEX if not exists index_own_orders +CREATE INDEX IF NOT EXISTS index_own_orders ON Orders (Poster, Time); -CREATE INDEX if not exists index_inbox +CREATE INDEX IF NOT EXISTS index_inbox ON Inbox (Recipient, Item, Wear); -CREATE VIEW if not exists distinct_items AS -SELECT DISTINCT Item FROM Orders; +CREATE VIEW IF NOT EXISTS distinct_items AS +SELECT DISTINCT Item, Wear FROM Orders; -CREATE VIEW if not exists market_summary AS +CREATE VIEW IF NOT EXISTS market_summary AS SELECT - distinct_items.Item, + distinct_items.Item AS Item, + distinct_items.Wear AS Wear, ( - SELECT sum(Orders.Amount) FROM Orders + SELECT SUM(Orders.Amount) FROM Orders WHERE Orders.Item = distinct_items.Item + AND Orders.Wear >= distinct_items.Wear AND Orders.Type = "buy" - ), + ) AS Buy_Volume, ( - SELECT max(Orders.Rate) FROM Orders + SELECT MAX(Orders.Rate) FROM Orders WHERE Orders.Item = distinct_items.Item + AND Orders.Wear >= distinct_items.Wear AND Orders.Type = "buy" - ), + ) AS Buy_Max, ( - SELECT sum(Orders.Amount) FROM Orders + SELECT SUM(Orders.Amount) FROM Orders WHERE Orders.Item = distinct_items.Item + AND Orders.Wear <= distinct_items.Wear AND Orders.Type = "sell" - ), + ) AS Sell_Volume, ( - SELECT min(Orders.Rate) FROM Orders + SELECT MIN(Orders.Rate) FROM Orders WHERE Orders.Item = distinct_items.Item + AND Orders.Wear <= distinct_items.Wear AND Orders.Type = "sell" - ) + ) AS Sell_Min FROM distinct_items; @@ -1241,14 +1246,8 @@ function ex_methods.market_summary(self) local stmt = self.stmts.summary_stmt local res = {} - for a in stmt:rows() do - table.insert(res, { - item_name = a[1], - buy_volume = a[2], - buy_max = a[3], - sell_volume = a[4], - sell_min = a[5], - }) + for a in stmt:nrows() do + table.insert(res, a) end stmt:reset() diff --git a/exchange_machine.lua b/exchange_machine.lua index 0e75cea..e928c7d 100644 --- a/exchange_machine.lua +++ b/exchange_machine.lua @@ -9,26 +9,40 @@ local function is_integer(x) return math.floor(x) == x end +local function wear_string(wear) + if wear > 0 then + return "-" .. math.ceil(100 * wear / 65535) .. "%" + else + return "----" + end +end + local summary_fs = "" local function mk_summary_fs() local fs = formlib.Builder() - fs("tablecolumns[text;text;text;text;text;text]") - fs("table[0,0;11.75,9;summary_table;") - fs("Item,Description,Buy Vol,Buy Max,Sell Vol,Sell Min") + fs:tablecolumns("text", "text", "text", "text", "text", "text", "text") + fs:table(0,0, 11.75,9, "summary_table", function(add_row) + add_row("Item", + "Description", + "Wear", + "Buy Vol", + "Buy Max", + "Sell Vol", + "Sell Min") - local all_items = minetest.registered_items - for i, row in ipairs(exchange:market_summary()) do - local def = all_items[row.item_name] or {} - fs(",", formlib.escape(row.item_name)) - fs(",", formlib.escape(def.description or "Unknown Item")) - fs(",", formlib.escape(row.buy_volume or 0)) - fs(",", formlib.escape(row.buy_max or "N/A")) - fs(",", formlib.escape(row.sell_volume or 0)) - fs(",", formlib.escape(row.sell_min or "N/A")) - end - - fs("]") + local all_items = minetest.registered_items + for i, row in ipairs(exchange:market_summary()) do + local def = all_items[row.Item] or {} + add_row(row.Item, + def.description or "Unknown Item", + wear_string(row.Wear), + row.Buy_Volume or 0, + row.Buy_Max or "N/A", + row.Sell_Volume or 0, + row.Sell_Min or "N/A") + end + end) summary_fs = tostring(fs) end @@ -58,10 +72,6 @@ for _,v in ipairs(wear_levels) do wear_levels[tostring(v.text)] = v end -local function wear_string(wear) - return "-" .. math.ceil(100 * wear / 65535) .. "%" -end - local main_state = {} -- ^ A per-player state for the main form. @@ -108,30 +118,20 @@ end) local main_form = "global_exchange:exchange_main" local function table_from_results(fs, results, name, x, y, w, h, selected) - fs("tablecolumns[text;text;text;text;text;text;text]") - fs("table[", x, ",", y, ";", w, ",", h, ";") - fs(formlib.escape(name), ";") - fs("Poster,Type,Item,Description,Wear,Amount,Rate") + fs:tablecolumns("text", "text", "text", "text", "text", "text", "text") + fs:table(x,y, w,h, name, function(add_row) + add_row("Poster", "Type", "Item", + "Description", + "Wear", "Amount", "Rate") - local all_items = minetest.registered_items - - for i, row in ipairs(results) do - local def = all_items[row.Item] or {} - fs(",", formlib.escape(row.Poster)) - fs(",", formlib.escape(row.Type)) - fs(",", formlib.escape(row.Item)) - fs(",", formlib.escape(def.description or "Unknown Item")) - if row.Wear > 0 then - fs(",", formlib.escape("-" .. math.ceil(100 * row.Wear / 65535) .. "%")) - else - fs(",---") + local all_items = minetest.registered_items + for i, row in ipairs(results) do + local def = all_items[row.Item] or {} + add_row(row.Poster, row.Type, row.Item, + def.description or "Unknown Item", + wear_string(row.Wear), row.Amount, row.Rate) end - fs(",", formlib.escape(row.Amount)) - fs(",", formlib.escape(row.Rate)) - end - - local sel_num = math.max(0, tonumber(selected) or 0) - fs(";", sel_num + 1, "]") + end, math.max(0, tonumber(selected) or 0) + 1) end local function mk_main_market_fs(fs, p_name, state) @@ -141,22 +141,13 @@ end local function mk_main_order_book_fs(fs, p_name, x, y, w, h, item_name) local order_book = exchange:order_book("", item_name) - fs("tablecolumns[text;text;text;text]") - fs("table[", x, ",", y, ";", w, ",", h, ";", "order_book;") - fs("Type,Rate,Wear,Amount") - - for _,row in ipairs(order_book) do - fs(",", formlib.escape(row.Type)) - fs(",", formlib.escape(row.Rate)) - if row.Wear > 0 then - fs(",", formlib.escape(wear_string(row.Wear))) - else - fs(",---") + fs:tablecolumns("text", "text", "text", "text") + fs:table(x,y, w,h, "order_book", function(add_row) + add_row("Type", "Rate", "Wear", "Amount") + for _,row in ipairs(order_book) do + add_row(row.Type, row.Rate, wear_string(row.Wear), row.Amount) end - fs(",", formlib.escape(row.Amount)) - end - - fs(";1]") + end, 1) end local function mk_main_buy_fs(fs, p_name, state) @@ -167,14 +158,11 @@ local function mk_main_buy_fs(fs, p_name, state) fs:field(10.25,0.40, 2,1, "buy_amount", "Quantity", state.buy_amount, false) local wear = wear_levels[state.buy_wear] or wear_levels[1] - fs("dropdown[9,1;3;buy_wear;") - local sep = nil - for _,v in ipairs(wear_levels) do - if sep then fs(sep) end - fs:escape(v.text) - sep = "," - end - fs(";", wear.index, "]") + fs:dropdown(9,1, 3, "buy_wear", function(add_item) + for _,v in ipairs(wear_levels) do + add_item(v.text) + end + end, wear.index) fs:field(9.35,2.40, 2.9,1, "buy_price", "Bid (ea.)", state.buy_price, false) @@ -241,14 +229,11 @@ local function mk_main_fs(fs, p_name, err_str, success) fs:size(12,10) fs:bgcolor("#606060", false) - fs("tabheader[0,0.65;tab;") - local sep = nil - for _,tab in ipairs(main_tabs) do - if sep then fs(sep) end - fs:escape(tab.text) - sep = "," - end - fs(";", state.tab or 1, ";false;true]") + fs:tabheader(0,0.65, "tab", function(add_tab) + for _,tab in ipairs(main_tabs) do + add_tab(tab.text) + end + end, state.tab or 1, false, true) local bal = exchange:get_balance(p_name) fs:label(0,0.37, "Balance: " .. bal) diff --git a/formlib.lua b/formlib.lua index cb318c0..87a3310 100644 --- a/formlib.lua +++ b/formlib.lua @@ -2,83 +2,163 @@ local formlib = {} local builder_methods = {} function formlib.escape(x) - return minetest.formspec_escape(tostring(x or "")) + if x == nil then return "" end + return minetest.formspec_escape(tostring(x)) +end + +function formlib.bool(x) + -- nil and false are returned as-is, everything else maps to true + return x and true +end + +function builder_methods.append(fs, ...) + for i=1,select("#", ...) do + local x = select(i, ...) + if x ~= nil then table.insert(fs, tostring(x)) end + end + return fs end function builder_methods.escape(fs, ...) - for _,x in ipairs({...}) do - if x then fs(formlib.escape(x)) end + for i=1,select("#", ...) do + local x = select(i, ...) + if x ~= nil then table.insert(fs, formlib.escape(x)) end end + return fs +end + +function builder_methods.escape_list(fs, ...) + for i=1,select("#", ...) do + local x = select(i, ...) + if i > 1 then fs(",") end + fs:escape(x) + end + return fs +end + +function builder_methods.escape_groups(fs, ...) + for i=1,select("#", ...) do + local group = select(i, ...) + if i > 1 then fs(";") end + if type(group) == "table" then + fs:escape_list(unpack(group)) + else + fs:escape(group) + end + end + return fs +end + +function builder_methods.element(fs, name, ...) + return fs(name, "["):escape_groups(...):append("]") end function builder_methods.size(fs, w,h, fixed) - if fixed == false then - fs("size[", w, ",", h, ",false]") - elseif fixed then - fs("size[", w, ",", h, ",true]") + if fixed == nil then + return fs:element("size", {w,h}) else - fs("size[", w, ",", h, "]") + return fs:element("size", {w,h, formlib.bool(fixed)}) end end function builder_methods.bgcolor(fs, color, fullscreen) - if fullscreen == false then - fs("bgcolor[", formlib.escape(color), ";false]") - elseif fullscreen then - fs("bgcolor[", formlib.escape(color), ";true]") + if fullscreen == nil then + return fs:element("bgcolor", {color}) else - fs("bgcolor[", formlib.escape(color), "]") + return fs:element("bgcolor", {color}, {formlib.bool(fullscreen)}) end end function builder_methods.list(fs, x,y, w,h, inv_loc, inv_list, start_idx) - fs("list[", formlib.escape(inv_loc), ";", formlib.escape(inv_list), ";", - x, ",", y, ";", w, ",", h, ";", formlib.escape(start_idx), "]") + return fs:element("list", {inv_loc}, {inv_list}, {x,y}, {w,h}, {start_idx}) end function builder_methods.button(fs, x,y, w,h, name, text) - fs("button[", x, ",", y, ";", w, ",", h, ";", - formlib.escape(name), ";", formlib.escape(text), "]") + return fs:element("button", {x,y}, {w,h}, {name}, {text}) end function builder_methods.item_image_button(fs, x,y, w,h, name, item, text) - fs("item_image_button[", x, ",", y, ";", w, ",", h, ";", - formlib.escape(item), ";", formlib.escape(name), ";", - formlib.escape(text), "]") + return fs:element("item_image_button", {x,y}, {w,h}, {item}, {name}, {text}) end function builder_methods.label(fs, x,y, text) - fs("label[", x, ",", y, ";", formlib.escape(text), "]") + return fs:element("label", {x,y}, {text}) end function builder_methods.field(fs, x,y, w,h, name, label, default, close_on_enter) - fs("field[", x, ",", y, ";", w, ",", h, ";", - formlib.escape(name), ";", formlib.escape(label), ";", - formlib.escape(default), "]") - - if close_on_enter == false then - fs("field_close_on_enter[", formlib.escape(name), ";false]") + fs:element("field", {x,y}, {w,h}, {name}, {label}, {default}) + if close_on_enter ~= nil then + fs:element("field_close_on_enter", {name}, {formlib.bool(close_on_enter)}) end -end - -function builder_methods.container(fs, x,y, sub_fn, ...) - fs("container[", x, ",", y, "]") - sub_fn(fs, ...) - fs("container_end[]") + return fs end function builder_methods.box(fs, x,y, w,h, color) - fs("box[", x, ",", y, ";", w, ",", h, ";", formlib.escape(color), "]") + return fs:element("box", {x,y}, {w,h}, {color}) +end + +function builder_methods.dropdown(fs, x,y, w, name, body_fn, selected_idx) + fs("dropdown["):escape_groups({x,y}, {w}, {name}):append(";") + local first = true + local results = { body_fn(function(...) + if first then first = false else fs(",") end + fs:escape(...) + end) } + if selected_idx ~= nil then fs(";"):escape(selected_idx) end + return fs("]"), unpack(results) +end + +function builder_methods.tabheader(fs, x,y, name, body_fn, current_tab, transparent, draw_border) + fs("tabheader["):escape_groups({x,y}, {name}):append(";") + local first = true + local results = { body_fn(function(...) + if first then first = false else fs(",") end + fs:escape(...) + end) } + fs(";"):escape_groups({current_tab}, {formlib.bool(transparent)}, {formlib.bool(draw_border)}) + return fs("]"), unpack(results) +end + +function builder_methods.textlist(fs, x,y, w,h, name, body_fn, selected_idx, transparent) + fs("textlist["):escape_groups({x,y}, {w,h}, {name}):append(";") + local first = true + local results = { body_fn(function(...) + if first then first = false else fs(",") end + fs:escape(...) + end) } + fs(";"):escape_groups({selected_idx}, {formlib.bool(transparent)}) + return fs("]"), unpack(results) +end + +function builder_methods.tableoptions(fs, ...) + return fs:element("tableoptions", ...) +end + +function builder_methods.tablecolumns(fs, ...) + return fs:element("tablecolumns", ...) +end + +function builder_methods.table(fs, x,y, w,h, name, body_fn, selected_idx) + fs("table["):escape_groups({x,y}, {w,h}, {name}):append(";") + local first = true + local results = { body_fn(function(...) + if first then first = false else fs(",") end + fs:escape_list(...) + end) } + if selected_idx ~= nil then fs(";"):escape(selected_idx) end + return fs("]"), unpack(results) +end + +function builder_methods.container(fs, x,y, sub_fn, ...) + fs:element("container", {x,y}) + local results = { sub_fn(fs, ...) } + return fs("container_end[]"), unpack(results) end local builder_meta = { __metatable = "protected", __index = builder_methods, - __call = function(fs, ...) - for _,x in ipairs({...}) do - if x then table.insert(fs, tostring(x)) end - end - end, + __call = builder_methods.append, __tostring = table.concat, }