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

View File

@ -17,9 +17,9 @@ To see the contents of a barrel, simply point at it and the item, count and
## Requirements
Optional dependencies:
* currency (if you want to use shop barrels)
* pipeworks
* digilines
* currency - my fork. Only needed if you want use shop barrels.
* pipeworks - my fork
* digilines - my fork
Optionally, if you want debug logging support, use:
* 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
more item/count pairs - examples: "default:cobble 4" or
"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
or back of the barrel.
listed will send that many items (or as many as it has, if it doesn't have
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".
Any barrel containing that item will response with a message on the
"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.
## 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: Code - LGPL, Textures - WTFPL

319
init.lua
View File

@ -31,6 +31,7 @@ function barrel.update_obj(pos, node)
local meta = minetest.get_meta(pos)
if meta:get_string("item") == "" then return end
local vec = barrel.dir2vec[node.param2]
if not vec then return end
local objpos = {x=pos.x + vec.x * 0.6,
y=pos.y + vec.y * 0.6,
z=pos.z + vec.z * 0.6}
@ -41,26 +42,77 @@ function barrel.update_obj(pos, node)
obj:get_luaentity().tex = {name}
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)
local nm = "nodemeta:"..pos.x..","..pos.y..","..pos.z
local price = meta:get_int("price")
local buyprice = meta:get_int("buyprice")
local saleunit = meta:get_int("saleunit")
local item = meta:get_string("item")
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]"
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..
"label[0,0;"..msg.."]"..
"list[current_player;payhere;0,0.5;3,2;]"..
"label[5,2.5;Take your goods]"..
"list["..nm..";paidfor;5,3;3,2;]"..
"list[current_player;main;0,5.5;8,4;]"..
"button[3,2;2,1;buy;Buy]"
"label[0,1;"..msg2.."]"..
"list["..nm..";exchange;0,2.5;6,2;]"..
"list[current_player;main;0,5.5;8,4;]"
if buybtn then
formspec = formspec .."button[6,2;2,1;buy;Buy]"
end
if sellbtn then
formspec = formspec .."button[6,3;2,1;sell;Sell]"
end
end
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
return formspec
end
@ -71,8 +123,9 @@ barrel.get_shop_owner_formspec = function(meta, pos)
"label[0,0;Money:]"..
"list["..nm..";money;0,0.5;3,2;]"..
"field[6,1;2,1;price;Price:;"..meta:get_int("price").."]"..
"field[6,2;2,1;saleunit;Unit:;"..meta:get_int("saleunit").."]"..
"button_exit[6,3;2,1;update;Update]"..
"field[6,2;2,1;buyprice;Buy Price:;"..meta:get_int("buyprice").."]"..
"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;]"
return formspec
end
@ -90,6 +143,7 @@ minetest.register_on_player_receive_fields(function(sender, formname, fields)
return
end
meta:set_int("price", fields.price)
meta:set_int("buyprice", fields.buyprice)
meta:set_int("saleunit", fields.saleunit)
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))
@ -106,106 +160,118 @@ minetest.register_on_player_receive_fields(function(sender, formname, fields)
barrel.get_shop_owner_formspec(meta, pos))
end
elseif fields.buy then
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
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!")
local status, msg = barrel.do_buy(pos)
if not status then
minetest.chat_send_player(name, msg)
return
end
-- Try and grab enough money...
local gotmoney = 0
local setstacks = {}
for i, stack in ipairs(playerinv:get_list("payhere")) 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
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!")
elseif fields.sell then
local status, msg = barrel.do_sell(pos)
if not status then
minetest.chat_send_player(name, msg)
return
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)
-- 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", {
visual="wielditem",
visual_size={x=0.2,y=0.2},
@ -240,11 +306,31 @@ end
function barrel.show_customer(player, pos, meta)
local playername = player:get_player_name()
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 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,
"Shop closed - contact "..owner ,true)
return
@ -349,9 +435,8 @@ function barrel.after_place_node_handler(pos, placer)
meta:set_int("price", 0)
meta:set_int("saleunit", 1)
local inv = meta:get_inventory()
inv:set_size("payhere", 3*2)
inv:set_size("paidfor", 3*2)
inv:set_size("money", 3*2)
inv:set_size("exchange", 6*2)
inv:set_size("money", 3*2)
end
if pipeworks then
pipeworks.scan_for_tube_objects(pos)
@ -509,11 +594,27 @@ function barrel.set_infotext(nodename, meta)
txt = "Empty shop"
else
local price = meta:get_int("price")
local buyprice = meta:get_int("buyprice")
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..")"
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
else