Add buying support, and API

This commit is contained in:
Ciaran Gultnieks 2015-05-04 07:15:48 +01:00
parent 3492d921b4
commit 77a8d86dd9
2 changed files with 221 additions and 114 deletions

@ -17,9 +17,9 @@ To see the contents of a barrel, simply point at it and the item, count and
## Requirements ## Requirements
Optional dependencies: Optional dependencies:
* currency (if you want to use shop barrels) * currency - my fork. Only needed if you want use shop barrels.
* pipeworks * pipeworks - my fork
* digilines * digilines - my fork
Optionally, if you want debug logging support, use: Optionally, if you want debug logging support, use:
* moddebug - https://gitlab.com/CiaranG/moddebug * moddebug - https://gitlab.com/CiaranG/moddebug
@ -73,13 +73,19 @@ The message must be a table, with "action" and "item" fields. Action is one of:
* "send" - instruct the barrel to send items. The "item" field should be one or * "send" - instruct the barrel to send items. The "item" field should be one or
more item/count pairs - examples: "default:cobble 4" or more item/count pairs - examples: "default:cobble 4" or
"default:stone 1 default:dirt 1". Any barrel that contains one of the items "default:stone 1 default:dirt 1". Any barrel that contains one of the items
listed will send that many items down the pipeworks connection to the bottom listed will send that many items (or as many as it has, if it doesn't have
or back of the barrel. enough) down the pipeworks connection to the bottom of the barrel.
* "count" - the "item" field shoud be a single item name, e.g. "default:cobble". * "count" - the "item" field shoud be a single item name, e.g. "default:cobble".
Any barrel containing that item will response with a message on the Any barrel containing that item will response with a message on the
"barrelcount" channel, with the message being a table containing two fields, "barrelcount" channel, with the message being a table containing two fields,
"item" (the item name) and "count" (the number of that item in the barrel. "item" (the item name) and "count" (the number of that item in the barrel.
## API
There are some API functions to allow use of barrels by other modules. A working
example of this can be found in the people mod, where NPC entities can, for
example, use shop barrels to buy and sell goods in the same way as a player.
## License ## License
License: Code - LGPL, Textures - WTFPL License: Code - LGPL, Textures - WTFPL

319
init.lua

@ -31,6 +31,7 @@ function barrel.update_obj(pos, node)
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
if meta:get_string("item") == "" then return end if meta:get_string("item") == "" then return end
local vec = barrel.dir2vec[node.param2] local vec = barrel.dir2vec[node.param2]
if not vec then return end
local objpos = {x=pos.x + vec.x * 0.6, local objpos = {x=pos.x + vec.x * 0.6,
y=pos.y + vec.y * 0.6, y=pos.y + vec.y * 0.6,
z=pos.z + vec.z * 0.6} z=pos.z + vec.z * 0.6}
@ -41,26 +42,77 @@ function barrel.update_obj(pos, node)
obj:get_luaentity().tex = {name} obj:get_luaentity().tex = {name}
end end
-- Get the total amount of money the shop (represented by the given node
-- meta) has.
barrel.total_money = function(meta)
local total = 0
for i, stack in ipairs(meta:get_inventory():get_list("money")) do
local denom = 0
local nn = stack:get_name()
if nn == "currency:minegeld_10" then
denom = 10
elseif nn == "currency:minegeld_5" then
denom = 5
elseif nn == "currency:minegeld" then
denom = 1
end
if denom > 0 then
total = total + denom * stack:get_count()
end
end
return total
end
barrel.get_shop_customer_formspec = function(meta, pos, playername) barrel.get_shop_customer_formspec = function(meta, pos, playername)
local nm = "nodemeta:"..pos.x..","..pos.y..","..pos.z local nm = "nodemeta:"..pos.x..","..pos.y..","..pos.z
local price = meta:get_int("price") local price = meta:get_int("price")
local buyprice = meta:get_int("buyprice")
local saleunit = meta:get_int("saleunit") local saleunit = meta:get_int("saleunit")
local item = meta:get_string("item") local item = meta:get_string("item")
local formspec = "size[8,9.5]" local formspec = "size[8,9.5]"
if item == "" or price == 0 or saleunit == 0 then if item == "" or (price == 0 and buyprice ==0) or saleunit == 0 then
formspec = formspec .. "label[0,0;Shop Closed]" formspec = formspec .. "label[0,0;Shop Closed]"
else else
msg = "Put money here - $"..price.." for "..saleunit.." "..item local desc = minetest.registered_items[item].description
local msg, msg2
local buybtn = false
if price ~= 0 then
if meta:get_int("item_amount") >= saleunit then
msg = "Put money here to buy "..saleunit.." "..desc.." for $"..price
buybtn = true
else
msg = "Sorry - we ran out of "..desc
end
else
msg = ""
end
local sellbtn = false
if buyprice ~= 0 then
if barrel.total_money(meta) >= buyprice then
msg2 = "Put "..saleunit.." "..desc.." here to get $"..buyprice
sellbtn = true
else
msg2 = "Sorry, we can't afford to buy "..desc.." today"
end
else
msg2 = ""
end
formspec = formspec.. formspec = formspec..
"label[0,0;"..msg.."]".. "label[0,0;"..msg.."]"..
"list[current_player;payhere;0,0.5;3,2;]".. "label[0,1;"..msg2.."]"..
"label[5,2.5;Take your goods]".. "list["..nm..";exchange;0,2.5;6,2;]"..
"list["..nm..";paidfor;5,3;3,2;]".. "list[current_player;main;0,5.5;8,4;]"
"list[current_player;main;0,5.5;8,4;]".. if buybtn then
"button[3,2;2,1;buy;Buy]" formspec = formspec .."button[6,2;2,1;buy;Buy]"
end
if sellbtn then
formspec = formspec .."button[6,3;2,1;sell;Sell]"
end
end end
if playername == meta:get_string("owner") then if playername == meta:get_string("owner") then
formspec = formspec.."button[3,3;2,1;manage;Manage]" formspec = formspec.."button[6,4;2,1;manage;Manage]"
end end
return formspec return formspec
end end
@ -71,8 +123,9 @@ barrel.get_shop_owner_formspec = function(meta, pos)
"label[0,0;Money:]".. "label[0,0;Money:]"..
"list["..nm..";money;0,0.5;3,2;]".. "list["..nm..";money;0,0.5;3,2;]"..
"field[6,1;2,1;price;Price:;"..meta:get_int("price").."]".. "field[6,1;2,1;price;Price:;"..meta:get_int("price").."]"..
"field[6,2;2,1;saleunit;Unit:;"..meta:get_int("saleunit").."]".. "field[6,2;2,1;buyprice;Buy Price:;"..meta:get_int("buyprice").."]"..
"button_exit[6,3;2,1;update;Update]".. "field[6,3;2,1;saleunit;Unit:;"..meta:get_int("saleunit").."]"..
"button_exit[5.5,4;2,1;update;Update]"..
"list[current_player;main;0,5.5;8,4;]" "list[current_player;main;0,5.5;8,4;]"
return formspec return formspec
end end
@ -90,6 +143,7 @@ minetest.register_on_player_receive_fields(function(sender, formname, fields)
return return
end end
meta:set_int("price", fields.price) meta:set_int("price", fields.price)
meta:set_int("buyprice", fields.buyprice)
meta:set_int("saleunit", fields.saleunit) meta:set_int("saleunit", fields.saleunit)
barrel.set_infotext(minetest.get_node(pos).name, meta) barrel.set_infotext(minetest.get_node(pos).name, meta)
dbg.v1(name.." set price "..fields.price..", unit "..fields.saleunit.." at "..minetest.pos_to_string(pos)) dbg.v1(name.." set price "..fields.price..", unit "..fields.saleunit.." at "..minetest.pos_to_string(pos))
@ -106,106 +160,118 @@ minetest.register_on_player_receive_fields(function(sender, formname, fields)
barrel.get_shop_owner_formspec(meta, pos)) barrel.get_shop_owner_formspec(meta, pos))
end end
elseif fields.buy then elseif fields.buy then
local amount = meta:get_int("item_amount") local status, msg = barrel.do_buy(pos)
local saleunit = meta:get_int("saleunit") if not status then
local price = meta:get_int("price") minetest.chat_send_player(name, msg)
if price == 0 or saleunit == 0 or amount < saleunit then
minetest.chat_send_player(name, "Out of stock!")
return
end
local shopinv = meta:get_inventory()
local playerinv = sender:get_inventory()
if playerinv:is_empty("payhere") then
minetest.chat_send_player(name, "Put your money in first!")
return return
end end
-- Try and grab enough money... elseif fields.sell then
local gotmoney = 0 local status, msg = barrel.do_sell(pos)
local setstacks = {} if not status then
for i, stack in ipairs(playerinv:get_list("payhere")) do minetest.chat_send_player(name, msg)
local denom = 0
local nn = stack:get_name()
if nn == "currency:minegeld_10" then
denom = 10
elseif nn == "currency:minegeld_5" then
denom = 5
elseif nn == "currency:minegeld" then
denom = 1
end
if denom > 0 then
local left = stack:get_count()
while left > 0 and gotmoney < price do
left = left - 1
gotmoney = gotmoney + denom
end
if left > 0 then
setstacks[i] = ItemStack(nn.." "..left)
else
setstacks[i] = ItemStack(nil)
end
else
setstacks[i] = stack
end
if gotmoney >= price then break end
end
if gotmoney < price then
minetest.chat_send_player(name, "Not enough money!")
return return
end end
-- Remove money
for i, stack in ipairs(setstacks) do
playerinv:set_stack("payhere", i, stack)
end
-- Hand over the goods and return change
-- The notes are added back to the "playhere" area lowest
-- denomination first. Highest first means if you pay $10 for
-- a $1 item, you get 5+4*1 back in change. If you then buy again,
-- it uses the 5, and now you have 8*1. Reversing the order means
-- it use the 1 for the second purchase.
shopinv:add_item("paidfor", ItemStack(meta:get_string("item").." "..saleunit))
meta:set_int("item_amount", amount - saleunit)
local change = gotmoney - price
local putmoney = function(inv, listname, amount)
local add10 = 0
local add5 = 0
local add1 = 0
while amount >= 10 do
add10 = add10 + 1
amount = amount - 10
end
if amount >= 5 then
add5 = add5 + 1
amount = amount - 5
end
if amount > 0 then
add1 = add1 + amount
end
if add1 > 0 then
inv:add_item(listname, ItemStack("currency:minegeld "..add1))
end
if add5 > 0 then
inv:add_item(listname, ItemStack("currency:minegeld_5 "..add5))
end
if add10 > 0 then
inv:add_item(listname, ItemStack("currency:minegeld_10 "..add10))
end
end
putmoney(playerinv, "payhere", change)
putmoney(shopinv, "money", price)
barrel.set_infotext(minetest.get_node(pos).name, meta)
dbg.v1(name.." bought "..saleunit.." of "..meta:get_string("item")..
" for ".. price.." at "..minetest.pos_to_string(pos))
end end
end end
end) end)
-- API function
-- Get shopinfo structure describing the shop at the given position.
-- Returns nil if it isn't a valid shop.
barrel.get_shopinfo = function(pos)
local meta = minetest.get_meta(pos)
local item = meta:get_string("item")
if not item then return nil end
local price = meta:get_int("price")
local buyprice = meta:get_int("buyprice")
local saleunit = meta:get_int("saleunit")
local stock = meta:get_int("item_amount")
return {pos = vector.new(pos),
meta = meta,
item=item,
saleunit=saleunit,
buyprice=buyprice,
price=price,
stock=stock}
end
-- API function
-- Do a 'sell' action. Goods must have been placed in the shop's "exchange"
-- inventory. Returns true if the sell action succeeded (i.e. some goods were
-- removed and money added), or false, message if it didn't happen for any
-- reason.
barrel.do_sell = function(pos)
local meta = minetest.get_meta(pos)
local amount = meta:get_int("item_amount")
local saleunit = meta:get_int("saleunit")
local price = meta:get_int("buyprice")
if price == 0 or saleunit == 0 or barrel.total_money(meta) < price then
return false, "Sale can't be done!"
end
local shopinv = meta:get_inventory()
if shopinv:is_empty("exchange") then
return false, "Put your goods in first!"
end
local toremove = ItemStack(meta:get_string("item").." "..saleunit)
if not shopinv:contains_item("exchange", toremove) then
return false, "Not enough!"
end
shopinv:remove_item("exchange", toremove)
meta:set_int("item_amount", amount + saleunit)
currency.put_to_inv(shopinv, "exchange", price)
currency.remove_from_inv(shopinv, "money", price)
barrel.set_infotext(minetest.get_node(pos).name, meta)
dbg.v1("Shop bought "..saleunit.." of "..meta:get_string("item")..
" for ".. price.." at "..minetest.pos_to_string(pos))
return true
end
-- API function
-- Do a 'buy' action. Money must have been placed in the shop's "exchange"
-- inventory. Returns true if the buy action succeeded (i.e. some money was
-- removed and goods added), or false, message if it didn't happen for any
-- reason.
barrel.do_buy = function(pos)
local meta = minetest.get_meta(pos)
local item = meta:get_string("item")
if not item then return nil end
local amount = meta:get_int("item_amount")
local saleunit = meta:get_int("saleunit")
local price = meta:get_int("price")
if price == 0 or saleunit == 0 or amount < saleunit then
return false, "Out of stock!"
end
local shopinv = meta:get_inventory()
if shopinv:is_empty("exchange") then
return false, "Put your money in first!"
end
local gotmoney = currency.remove_from_inv(shopinv, "exchange", price)
if gotmoney == 0 then
return false, "Not enough money!"
end
-- Hand over the goods and return change
shopinv:add_item("exchange", ItemStack(meta:get_string("item").." "..saleunit))
meta:set_int("item_amount", amount - saleunit)
local change = gotmoney - price
currency.put_to_inv(shopinv, "exchange", change)
currency.put_to_inv(shopinv, "money", price)
barrel.set_infotext(minetest.get_node(pos).name, meta)
dbg.v1("Shop sold "..saleunit.." of "..meta:get_string("item")..
" for ".. price.." at "..minetest.pos_to_string(pos))
return true
end
minetest.register_entity("barrel:obj", { minetest.register_entity("barrel:obj", {
visual="wielditem", visual="wielditem",
visual_size={x=0.2,y=0.2}, visual_size={x=0.2,y=0.2},
@ -240,11 +306,31 @@ end
function barrel.show_customer(player, pos, meta) function barrel.show_customer(player, pos, meta)
local playername = player:get_player_name() local playername = player:get_player_name()
local owner = meta:get_string("owner") local owner = meta:get_string("owner")
player:get_inventory():set_size("payhere", 3*2)
player:get_inventory():set_size("paidfor", 3*2) -- Make sure the node's inventory has the 'exchange' list. We do
-- it here for upgrade purposes.
local inv = meta:get_inventory()
local got_ex = false
local got_mo = false
for _, ln in ipairs(inv:get_lists()) do
if ln == "exchange" then
got_ex = true
elseif ln == "money" then
got_mo = true
end
end
if not got_ex then
inv:set_size("exchange", 6*2)
end
if not got_mo then
inv:set_size("money", 3*2)
end
if playername ~= owner then if playername ~= owner then
if meta:get_int("price") == 0 or meta:get_int("saleunit") == 0 or
meta:get_int("item_amount") < meta:get_int("saleunit") then if (meta:get_int("price") == 0 and meta:get_int("buyprice") == 0)
or meta:get_int("saleunit") == 0 or meta:get_int("item") == ""
then
minetest.chat_send_player(playername, minetest.chat_send_player(playername,
"Shop closed - contact "..owner ,true) "Shop closed - contact "..owner ,true)
return return
@ -349,9 +435,8 @@ function barrel.after_place_node_handler(pos, placer)
meta:set_int("price", 0) meta:set_int("price", 0)
meta:set_int("saleunit", 1) meta:set_int("saleunit", 1)
local inv = meta:get_inventory() local inv = meta:get_inventory()
inv:set_size("payhere", 3*2) inv:set_size("exchange", 6*2)
inv:set_size("paidfor", 3*2) inv:set_size("money", 3*2)
inv:set_size("money", 3*2)
end end
if pipeworks then if pipeworks then
pipeworks.scan_for_tube_objects(pos) pipeworks.scan_for_tube_objects(pos)
@ -509,11 +594,27 @@ function barrel.set_infotext(nodename, meta)
txt = "Empty shop" txt = "Empty shop"
else else
local price = meta:get_int("price") local price = meta:get_int("price")
local buyprice = meta:get_int("buyprice")
local saleunit = meta:get_int("saleunit") local saleunit = meta:get_int("saleunit")
if price == 0 or saleunit == 0 or amount < saleunit then
if (price == 0 and buyprice == 0) or saleunit == 0 then
txt = "Closed shop (stock:"..amount..")" txt = "Closed shop (stock:"..amount..")"
else else
txt = "Selling "..saleunit.." "..item.." for $"..price.." (stock:"..amount..")" local desc = minetest.registered_items[item].description
if price > 0 and buyprice > 0 then
txt = "Buying and selling "
elseif price > 0 then
txt = "Selling "
else
txt = "Buying "
end
if saleunit > 1 then
txt = txt .. saleunit.." "
end
txt = txt .. desc
if price > 0 then
txt = txt .. " (stock:"..amount..")"
end
end end
end end
else else