Expand formlib & distinguish items by wear in market summary.

This commit is contained in:
Jesse McDonald 2017-06-03 16:40:32 -05:00
parent 2a5a726cc0
commit 808b3cbf04
5 changed files with 220 additions and 161 deletions

19
atm.lua
View File

@ -33,7 +33,7 @@ local function wire_fs(fs, p_name)
fs:size(4,5) fs:size(4,5)
if balance then if balance then
-- To prevent duplicates -- To detect duplicate/stale form submission
fs:field(-100, -100, 0,0, "trans_id", "", unique()) fs:field(-100, -100, 0,0, "trans_id", "", unique())
fs:label(0.50,0.325, "Balance: " .. balance) 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: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") fs("table[0,0.75;13.75,6.75;log_table;Time,Message")
for _, entry in ipairs(exchange:player_log(p_name)) do 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 end
fs("]") fs("]")
fs:button(6,7.5, 2,1, "logout", "Log Out") fs:button(6,7.5, 2,1, "logout", "Log Out")
@ -100,9 +99,6 @@ local function main_menu_fs(fs, p_name)
end end
local trans_ids = {}
local function show_atm_form(fs_fn, p_name, ...) local function show_atm_form(fs_fn, p_name, ...)
local fs = formlib.Builder() local fs = formlib.Builder()
fs_fn(fs, p_name, ...) fs_fn(fs, p_name, ...)
@ -110,15 +106,18 @@ local function show_atm_form(fs_fn, p_name, ...)
end end
local trans_ids = {}
minetest.register_on_player_receive_fields(function(player, formname, fields) minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= atm_form then return end if formname ~= atm_form then return end
if fields.quit then return true end if fields.quit then return true end
local p_name = player:get_player_name() 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 return true
end end

View File

@ -26,19 +26,15 @@ end
local function mk_inbox_list(fs, results, x, y, w, h) local function mk_inbox_list(fs, results, x, y, w, h)
fs("textlist[", x, ",", y, ";", w, ",", h, ";result_list;") fs:textlist(x,y, w,h, "result_list", function(add_row)
for i, row in ipairs(results) do
local sep = nil local wear_suffix = nil
for i, row in ipairs(results) do if row.Wear > 0 then
fs(sep) wear_suffix = " (" .. wear_string(row.Wear) .. ")"
fs:escape(row.Amount, " ", row.Item) end
if row.Wear > 0 then add_row(row.Amount, " ", row.Item, wear_suffix)
fs:escape(" (", wear_string(row.Wear), ")")
end end
sep = "," end)
end
fs("]")
end end

View File

@ -16,13 +16,13 @@ end)({})
local init_query = [=[ local init_query = [=[
BEGIN TRANSACTION; BEGIN TRANSACTION;
CREATE TABLE if not exists Credit CREATE TABLE IF NOT EXISTS Credit
( (
Owner TEXT PRIMARY KEY NOT NULL, Owner TEXT PRIMARY KEY NOT NULL,
Balance INTEGER NOT NULL Balance INTEGER NOT NULL
); );
CREATE TABLE if not exists Log CREATE TABLE IF NOT EXISTS Log
( (
Id INTEGER PRIMARY KEY AUTOINCREMENT, Id INTEGER PRIMARY KEY AUTOINCREMENT,
Recipient TEXT NOT NULL, Recipient TEXT NOT NULL,
@ -30,7 +30,7 @@ CREATE TABLE if not exists Log
Message TEXT NOT NULL Message TEXT NOT NULL
); );
CREATE TABLE if not exists Orders CREATE TABLE IF NOT EXISTS Orders
( (
Id INTEGER PRIMARY KEY AUTOINCREMENT, Id INTEGER PRIMARY KEY AUTOINCREMENT,
Poster TEXT NOT NULL, Poster TEXT NOT NULL,
@ -43,7 +43,7 @@ CREATE TABLE if not exists Orders
Rate INTEGER NOT NULL CHECK(Rate > 0) Rate INTEGER NOT NULL CHECK(Rate > 0)
); );
CREATE TABLE if not exists Inbox CREATE TABLE IF NOT EXISTS Inbox
( (
Id INTEGER PRIMARY KEY AUTOINCREMENT, Id INTEGER PRIMARY KEY AUTOINCREMENT,
Recipient TEXT NOT NULL, Recipient TEXT NOT NULL,
@ -52,44 +52,49 @@ CREATE TABLE if not exists Inbox
Amount INTEGER NOT NULL CHECK(Amount > 0) 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); 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); 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); ON Orders (Poster, Time);
CREATE INDEX if not exists index_inbox CREATE INDEX IF NOT EXISTS index_inbox
ON Inbox (Recipient, Item, Wear); ON Inbox (Recipient, Item, Wear);
CREATE VIEW if not exists distinct_items AS CREATE VIEW IF NOT EXISTS distinct_items AS
SELECT DISTINCT Item FROM Orders; SELECT DISTINCT Item, Wear FROM Orders;
CREATE VIEW if not exists market_summary AS CREATE VIEW IF NOT EXISTS market_summary AS
SELECT 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 WHERE Orders.Item = distinct_items.Item
AND Orders.Wear >= distinct_items.Wear
AND Orders.Type = "buy" 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 WHERE Orders.Item = distinct_items.Item
AND Orders.Wear >= distinct_items.Wear
AND Orders.Type = "buy" 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 WHERE Orders.Item = distinct_items.Item
AND Orders.Wear <= distinct_items.Wear
AND Orders.Type = "sell" 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 WHERE Orders.Item = distinct_items.Item
AND Orders.Wear <= distinct_items.Wear
AND Orders.Type = "sell" AND Orders.Type = "sell"
) ) AS Sell_Min
FROM distinct_items; FROM distinct_items;
@ -1241,14 +1246,8 @@ function ex_methods.market_summary(self)
local stmt = self.stmts.summary_stmt local stmt = self.stmts.summary_stmt
local res = {} local res = {}
for a in stmt:rows() do for a in stmt:nrows() do
table.insert(res, { table.insert(res, a)
item_name = a[1],
buy_volume = a[2],
buy_max = a[3],
sell_volume = a[4],
sell_min = a[5],
})
end end
stmt:reset() stmt:reset()

View File

@ -9,26 +9,40 @@ local function is_integer(x)
return math.floor(x) == x return math.floor(x) == x
end end
local function wear_string(wear)
if wear > 0 then
return "-" .. math.ceil(100 * wear / 65535) .. "%"
else
return "----"
end
end
local summary_fs = "" local summary_fs = ""
local function mk_summary_fs() local function mk_summary_fs()
local fs = formlib.Builder() local fs = formlib.Builder()
fs("tablecolumns[text;text;text;text;text;text]") fs:tablecolumns("text", "text", "text", "text", "text", "text", "text")
fs("table[0,0;11.75,9;summary_table;") fs:table(0,0, 11.75,9, "summary_table", function(add_row)
fs("Item,Description,Buy Vol,Buy Max,Sell Vol,Sell Min") add_row("Item",
"Description",
"Wear",
"Buy Vol",
"Buy Max",
"Sell Vol",
"Sell Min")
local all_items = minetest.registered_items local all_items = minetest.registered_items
for i, row in ipairs(exchange:market_summary()) do for i, row in ipairs(exchange:market_summary()) do
local def = all_items[row.item_name] or {} local def = all_items[row.Item] or {}
fs(",", formlib.escape(row.item_name)) add_row(row.Item,
fs(",", formlib.escape(def.description or "Unknown Item")) def.description or "Unknown Item",
fs(",", formlib.escape(row.buy_volume or 0)) wear_string(row.Wear),
fs(",", formlib.escape(row.buy_max or "N/A")) row.Buy_Volume or 0,
fs(",", formlib.escape(row.sell_volume or 0)) row.Buy_Max or "N/A",
fs(",", formlib.escape(row.sell_min or "N/A")) row.Sell_Volume or 0,
end row.Sell_Min or "N/A")
end
fs("]") end)
summary_fs = tostring(fs) summary_fs = tostring(fs)
end end
@ -58,10 +72,6 @@ for _,v in ipairs(wear_levels) do
wear_levels[tostring(v.text)] = v wear_levels[tostring(v.text)] = v
end end
local function wear_string(wear)
return "-" .. math.ceil(100 * wear / 65535) .. "%"
end
local main_state = {} local main_state = {}
-- ^ A per-player state for the main form. -- ^ A per-player state for the main form.
@ -108,30 +118,20 @@ end)
local main_form = "global_exchange:exchange_main" local main_form = "global_exchange:exchange_main"
local function table_from_results(fs, results, name, x, y, w, h, selected) local function table_from_results(fs, results, name, x, y, w, h, selected)
fs("tablecolumns[text;text;text;text;text;text;text]") fs:tablecolumns("text", "text", "text", "text", "text", "text", "text")
fs("table[", x, ",", y, ";", w, ",", h, ";") fs:table(x,y, w,h, name, function(add_row)
fs(formlib.escape(name), ";") add_row("Poster", "Type", "Item",
fs("Poster,Type,Item,Description,Wear,Amount,Rate") "Description",
"Wear", "Amount", "Rate")
local all_items = minetest.registered_items local all_items = minetest.registered_items
for i, row in ipairs(results) do
for i, row in ipairs(results) do local def = all_items[row.Item] or {}
local def = all_items[row.Item] or {} add_row(row.Poster, row.Type, row.Item,
fs(",", formlib.escape(row.Poster)) def.description or "Unknown Item",
fs(",", formlib.escape(row.Type)) wear_string(row.Wear), row.Amount, row.Rate)
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(",---")
end end
fs(",", formlib.escape(row.Amount)) end, math.max(0, tonumber(selected) or 0) + 1)
fs(",", formlib.escape(row.Rate))
end
local sel_num = math.max(0, tonumber(selected) or 0)
fs(";", sel_num + 1, "]")
end end
local function mk_main_market_fs(fs, p_name, state) 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 function mk_main_order_book_fs(fs, p_name, x, y, w, h, item_name)
local order_book = exchange:order_book("", item_name) local order_book = exchange:order_book("", item_name)
fs("tablecolumns[text;text;text;text]") fs:tablecolumns("text", "text", "text", "text")
fs("table[", x, ",", y, ";", w, ",", h, ";", "order_book;") fs:table(x,y, w,h, "order_book", function(add_row)
fs("Type,Rate,Wear,Amount") add_row("Type", "Rate", "Wear", "Amount")
for _,row in ipairs(order_book) do
for _,row in ipairs(order_book) do add_row(row.Type, row.Rate, wear_string(row.Wear), row.Amount)
fs(",", formlib.escape(row.Type))
fs(",", formlib.escape(row.Rate))
if row.Wear > 0 then
fs(",", formlib.escape(wear_string(row.Wear)))
else
fs(",---")
end end
fs(",", formlib.escape(row.Amount)) end, 1)
end
fs(";1]")
end end
local function mk_main_buy_fs(fs, p_name, state) 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) 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] local wear = wear_levels[state.buy_wear] or wear_levels[1]
fs("dropdown[9,1;3;buy_wear;") fs:dropdown(9,1, 3, "buy_wear", function(add_item)
local sep = nil for _,v in ipairs(wear_levels) do
for _,v in ipairs(wear_levels) do add_item(v.text)
if sep then fs(sep) end end
fs:escape(v.text) end, wear.index)
sep = ","
end
fs(";", wear.index, "]")
fs:field(9.35,2.40, 2.9,1, "buy_price", "Bid (ea.)", state.buy_price, false) 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:size(12,10)
fs:bgcolor("#606060", false) fs:bgcolor("#606060", false)
fs("tabheader[0,0.65;tab;") fs:tabheader(0,0.65, "tab", function(add_tab)
local sep = nil for _,tab in ipairs(main_tabs) do
for _,tab in ipairs(main_tabs) do add_tab(tab.text)
if sep then fs(sep) end end
fs:escape(tab.text) end, state.tab or 1, false, true)
sep = ","
end
fs(";", state.tab or 1, ";false;true]")
local bal = exchange:get_balance(p_name) local bal = exchange:get_balance(p_name)
fs:label(0,0.37, "Balance: " .. bal) fs:label(0,0.37, "Balance: " .. bal)

View File

@ -2,83 +2,163 @@ local formlib = {}
local builder_methods = {} local builder_methods = {}
function formlib.escape(x) 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 end
function builder_methods.escape(fs, ...) function builder_methods.escape(fs, ...)
for _,x in ipairs({...}) do for i=1,select("#", ...) do
if x then fs(formlib.escape(x)) end local x = select(i, ...)
if x ~= nil then table.insert(fs, formlib.escape(x)) end
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 end
function builder_methods.size(fs, w,h, fixed) function builder_methods.size(fs, w,h, fixed)
if fixed == false then if fixed == nil then
fs("size[", w, ",", h, ",false]") return fs:element("size", {w,h})
elseif fixed then
fs("size[", w, ",", h, ",true]")
else else
fs("size[", w, ",", h, "]") return fs:element("size", {w,h, formlib.bool(fixed)})
end end
end end
function builder_methods.bgcolor(fs, color, fullscreen) function builder_methods.bgcolor(fs, color, fullscreen)
if fullscreen == false then if fullscreen == nil then
fs("bgcolor[", formlib.escape(color), ";false]") return fs:element("bgcolor", {color})
elseif fullscreen then
fs("bgcolor[", formlib.escape(color), ";true]")
else else
fs("bgcolor[", formlib.escape(color), "]") return fs:element("bgcolor", {color}, {formlib.bool(fullscreen)})
end end
end end
function builder_methods.list(fs, x,y, w,h, inv_loc, inv_list, start_idx) 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), ";", return fs:element("list", {inv_loc}, {inv_list}, {x,y}, {w,h}, {start_idx})
x, ",", y, ";", w, ",", h, ";", formlib.escape(start_idx), "]")
end end
function builder_methods.button(fs, x,y, w,h, name, text) function builder_methods.button(fs, x,y, w,h, name, text)
fs("button[", x, ",", y, ";", w, ",", h, ";", return fs:element("button", {x,y}, {w,h}, {name}, {text})
formlib.escape(name), ";", formlib.escape(text), "]")
end end
function builder_methods.item_image_button(fs, x,y, w,h, name, item, text) function builder_methods.item_image_button(fs, x,y, w,h, name, item, text)
fs("item_image_button[", x, ",", y, ";", w, ",", h, ";", return fs:element("item_image_button", {x,y}, {w,h}, {item}, {name}, {text})
formlib.escape(item), ";", formlib.escape(name), ";",
formlib.escape(text), "]")
end end
function builder_methods.label(fs, x,y, text) function builder_methods.label(fs, x,y, text)
fs("label[", x, ",", y, ";", formlib.escape(text), "]") return fs:element("label", {x,y}, {text})
end end
function builder_methods.field(fs, x,y, w,h, name, label, default, close_on_enter) function builder_methods.field(fs, x,y, w,h, name, label, default, close_on_enter)
fs("field[", x, ",", y, ";", w, ",", h, ";", fs:element("field", {x,y}, {w,h}, {name}, {label}, {default})
formlib.escape(name), ";", formlib.escape(label), ";", if close_on_enter ~= nil then
formlib.escape(default), "]") fs:element("field_close_on_enter", {name}, {formlib.bool(close_on_enter)})
if close_on_enter == false then
fs("field_close_on_enter[", formlib.escape(name), ";false]")
end end
end return fs
function builder_methods.container(fs, x,y, sub_fn, ...)
fs("container[", x, ",", y, "]")
sub_fn(fs, ...)
fs("container_end[]")
end end
function builder_methods.box(fs, x,y, w,h, color) 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 end
local builder_meta = { local builder_meta = {
__metatable = "protected", __metatable = "protected",
__index = builder_methods, __index = builder_methods,
__call = function(fs, ...) __call = builder_methods.append,
for _,x in ipairs({...}) do
if x then table.insert(fs, tostring(x)) end
end
end,
__tostring = table.concat, __tostring = table.concat,
} }