diff --git a/mods/capitalism/company/permissions.lua b/mods/capitalism/company/permissions.lua index 38d0f8f..d222a88 100644 --- a/mods/capitalism/company/permissions.lua +++ b/mods/capitalism/company/permissions.lua @@ -9,4 +9,5 @@ company.permissions = { SHOP_CREATE = "Can create a shop on commercial areas", SHOP_ADMIN = "Can change the settings of a shop, including prices", SHOP_CHEST = "Can place, modify shop chests", + BUY_ITEMS = "Can buy from shops", } diff --git a/mods/capitalism/shop/api.lua b/mods/capitalism/shop/api.lua index 6a5ffcd..f8cc537 100644 --- a/mods/capitalism/shop/api.lua +++ b/mods/capitalism/shop/api.lua @@ -92,6 +92,107 @@ function shop.unassign_chest(s, pos, inv) chest.count = 0 end +function shop.can_buy(pos, pname, itemname, count, price) + assert(type(pos) == "table") + assert(type(pname) == "string") + assert(type(itemname) == "string" and minetest.registered_items[itemname]) + assert(type(count) == "number" and count >= 0) + assert(type(price) == "number" and price >= 0) + + local comp = company.get_active(pname) + if comp and not comp:check_perm(pname, "BUY_ITEMS", + { itemname = itemname, count = count, price = price }) then + return false, "Missing permission: BUY_ITEMS" + end + + local acc = banking.get_by_owner(comp and comp.name or pname) + if not acc then + return false, "You don't have a bank account" + end + + if acc.balance < price then + return false, "Insufficient funds" + end + + return true +end + +function shop.buy(pos, pname, item, count) + assert(type(pos) == "table") + assert(type(pname) == "string") + assert(type(item) == "table") + assert(type(count) == "number" and count >= 0) + + if count > item.stock then + return false, "Not enough stock" + end + + local price = count * item.price + + local suc, msg = shop.can_buy(pos, pname, item.name, count, price) + if not suc then + return false, msg + end + + local to_give = ItemStack({ name = item.name, count = count }) + local pinv = minetest.get_inventory({ type = "player", name = pname }) + if not pinv:room_for_item("main", to_give) then + return false, "Not enough room in inv" + end + + + local area = land.get_by_pos(pos) + assert(area.owner:sub(1, 2) == "c:") + local s = shop.get_by_area(area.id) + local comp = company.get_active(pname) + local account = banking.get_by_owner(comp and comp.name or pname) + local owner_account = banking.get_by_owner(area.owner) + assert(account) + assert(owner_account) + + -- Locate chest + local chests = s:get_chests_for_item(item.name, count, function(chest) + return minetest.get_node(chest.pos) ~= "ignore" + end) + + if not chests then + return false, "Map unloaded" + -- chests = s:get_chests_for_item(item.name, count) + -- + -- if not chests then + -- return false, "Error: unexpected out of stock. This should never happen." + -- end + end + + local took = 0 + for i=1, #chests do + local inv = minetest.get_inventory({ type = "node", pos = chests[i].pos }) + + local stack = inv:remove_item("main", { name = item.name, count = count - took }) + if not stack:is_empty() then + took = took + stack:get_count() + s:chest_remove_item(chests[i].pos, stack) + + if took == count then + break + end + end + end + + assert(took == count) + + if not banking.transfer(pname, account.owner, owner_account.owner, price, + "Purchase of item name=" .. item.name .. ", count=" .. count) then + return false, "Card payment error" + end + + pinv:add_item("main", to_give) + + shop.dirty = true + + return true +end + -- Minetest won't be available in tests if minetest then local storage = minetest.get_mod_storage() diff --git a/mods/capitalism/shop/gui.lua b/mods/capitalism/shop/gui.lua index aee6a0e..a8ef144 100644 --- a/mods/capitalism/shop/gui.lua +++ b/mods/capitalism/shop/gui.lua @@ -2,8 +2,7 @@ function shop.show_shop_form(pname, pos) if shop.can_admin(pname, pos) then shop.show_admin_form(pname, pos) else - minetest.chat_send_player(pname, "Shop checkout unimplemented") - -- shop.show_shop_checkout_form(playername, pos) + shop.show_customer_form(pname, pos) end end @@ -170,3 +169,136 @@ shop.show_chest_form = lib_quickfs.register("shop:chest", { end end, }) + + +shop.show_customer_form = lib_quickfs.register("shop:customer", { + get = function(context, player, pos) + local s = shop.get_by_pos(pos) + assert(s) + + local fs = { + "size[8,9.25]", + company.get_company_header(context.pname, 8, "balance"), + "label[4,1;", + minetest.formspec_escape(s.name), + "]", + "tablecolumns[color;text;text;text]", + "list[current_player;main;0,5.25;8,1;]", + "list[current_player;main;0,6.48;8,3;8]", + default.get_hotbar_bg(0, 5.25), + "table[0,1;5.8,4;list_items;", + "#999,Description,Stock,Price", + } + + -- Description Stock PricePI Sold + + local items_kv = s:get_items() + local items = {} + context.items = items + for _, item in pairs(items_kv) do + if item.price >= 0 then + local def = minetest.registered_items[item.name] or {} + local desc = def.description or item.name + + items[#items + 1] = item + + fs[#fs + 1] = ",," + fs[#fs + 1] = desc + fs[#fs + 1] = "," + fs[#fs + 1] = item.stock + fs[#fs + 1] = "," + fs[#fs + 1] = item.price + end + end + + if next(items) and not context.selected then + context.selected = 1 + end + + if context.selected then + if context.selected > #items then + context.selected = #items + end + + if context.selected and context.selected > 0 then + fs[#fs + 1] = ";" + fs[#fs + 1] = tostring(context.selected + 1) + end + end + + fs[#fs + 1] = "]" + + if context.selected and context.selected > 0 then + local item = items[context.selected] + local suc, msg = shop.can_buy(pos, context.pname, item.name, 0, 0) + if suc then + fs[#fs + 1] = "field[6.3,1.3;2,1;num;;" + fs[#fs + 1] = tostring(context.num or 1) + fs[#fs + 1] = "]" + fs[#fs + 1] = "button[6,2;2,1;buyn;Buy]" + else + fs[#fs + 1] = "box[6,1;1.8,0.8;#f00]" + fs[#fs + 1] = "box[6,2;1.8,0.8;#222]" + fs[#fs + 1] = "label[6,1;" + fs[#fs + 1] = minetest.formspec_escape(msg) + fs[#fs + 1] = "]" + end + + if context.error then + fs[#fs + 1] = "box[6,3;1.8,0.8;#f00]" + fs[#fs + 1] = "label[6,3;" + fs[#fs + 1] = minetest.formspec_escape(context.error) + fs[#fs + 1] = "]" + else + fs[#fs + 1] = "box[6,3;1.8,0.8;#222]" + end + fs[#fs + 1] = "box[6,4;1.8,0.8;#222]" + else + fs[#fs + 1] = "box[6,1;1.8,0.8;#222]" + fs[#fs + 1] = "box[6,2;1.8,0.8;#222]" + fs[#fs + 1] = "box[6,3;1.8,0.8;#222]" + fs[#fs + 1] = "box[6,4;1.8,0.8;#222]" + end + + return table.concat(fs, "") + end, + + on_receive_fields = function(context, player, fields, pos) + if fields.switch then + company.show_company_select_dialog(context.pname, function(player2) + shop.show_customer_form(player2:get_player_name(), unpack(context.args)) + end) + return + end + + if fields.list_items then + local evt = minetest.explode_table_event(fields.list_items) + context.selected = evt.row - 1 + return true + end + + if fields.num then + context.num = tonumber(fields.num) or 1 + end + + if fields.buyn then + local item = context.items[context.selected] + if item then + local count = tonumber(fields.num) + if not count then + context.error = "Not a number!" + return true + end + + local _, msg = shop.buy(pos, context.pname, item, count) + if msg then + context.error = msg + else + context.error = nil + end + return true + end + return true + end + end, +}) diff --git a/mods/capitalism/shop/shop.lua b/mods/capitalism/shop/shop.lua index 94fd6e4..92e9360 100644 --- a/mods/capitalism/shop/shop.lua +++ b/mods/capitalism/shop/shop.lua @@ -67,6 +67,21 @@ function Shop:get_chest(pos) return self.chests[posstr] end +function Shop:get_chests_for_item(name, count, filter) + local ret = {} + for _, chest in pairs(self.chests) do + if chest.itemname == name and chest.count > 0 and (not filter or filter(chest)) then + count = count - chest.count + ret[#ret + 1] = chest + if count <= 0 then + return ret + end + end + end + + return nil +end + function Shop:chest_poll(pos, inv) local chest = self:get_chest(pos) assert(chest)