Update select_item handling
[minetest_easyvend.git] / easyvend.lua
bloba26848bfdf8409bad7359ee5280176e2fceb1199
1 -- TODO: Improve mod compability
2 local slots_max = 31
4 local traversable_node_types = {
5 ["easyvend:vendor"] = true,
6 ["easyvend:depositor"] = true,
7 ["easyvend:vendor_on"] = true,
8 ["easyvend:depositor_on"] = true,
10 local registered_chests = {}
11 local cost_stack_max = minetest.registered_items[easyvend.currency].stack_max
12 local maxcost = cost_stack_max * slots_max
14 local joketimer_start = 3
16 local active_item_selection = {}
18 -- Allow for other mods to register custom chests
19 easyvend.register_chest = function(node_name, inv_list, meta_owner)
20 registered_chests[node_name] = { inv_list = inv_list, meta_owner = meta_owner }
21 traversable_node_types[node_name] = true
22 end
24 if minetest.get_modpath("select_item") then
25 -- When player selects item via "select item" dialog, switch the
26 -- machine's selected item and update the formspec.
27 select_item.register_on_select_item(function(playername, dialogname, itemstring)
28 if dialogname == "easyvend:trade_item" then
29 local player = minetest.get_player_by_name(playername)
30 if not player then
31 return
32 end
33 local pos = active_item_selection[playername]
34 if pos then
35 local node = minetest.get_node(pos)
36 if not easyvend.is_machine(node.name) then
37 return
38 end
39 local meta = minetest.get_meta(pos)
40 local owner = meta:get_string("owner")
41 if playername == owner then
42 local inv = meta:get_inventory()
43 local stack = ItemStack(itemstring)
44 if stack == nil then
45 inv:set_stack( "item", 1, nil )
46 else
47 inv:set_stack( "item", 1, stack)
48 meta:set_string("itemname", itemstring)
49 easyvend.set_formspec(pos, player)
50 end
51 end
52 end
53 active_item_selection[playername] = nil
54 end
55 end)
56 end
58 -- Partly a wrapper around contains_item, but does special treatment if the item
59 -- is a tool. Basically checks whether the items exist in the supplied inventory
60 -- list. If check_wear is true, only counts items without wear.
61 easyvend.check_and_get_items = function(inventory, listname, itemtable, check_wear)
62 local itemstring = itemtable.name
63 local minimum = itemtable.count
64 if check_wear == nil then check_wear = false end
65 local get_items = {}
66 -- Tool workaround
67 if minetest.registered_tools[itemstring] ~= nil then
68 local count = 0
69 for i=1,inventory:get_size(listname) do
70 local stack = inventory:get_stack(listname, i)
71 if stack:get_name() == itemstring then
72 if not check_wear or stack:get_wear() == 0 then
73 count = count + 1
74 table.insert(get_items, {id=i, item=stack})
75 if count >= minimum then
76 return true, get_items
77 end
78 end
79 end
80 end
81 return false
82 else
83 -- Normal Minetest check
84 return inventory:contains_item(listname, ItemStack(itemtable))
85 end
86 end
89 if minetest.get_modpath("default") ~= nil then
90 easyvend.register_chest("default:chest_locked", "main", "owner")
91 end
93 easyvend.free_slots= function(inv, listname)
94 local size = inv:get_size(listname)
95 local free = 0
96 for i=1,size do
97 local stack = inv:get_stack(listname, i)
98 if stack:is_empty() then
99 free = free + 1
102 return free
105 easyvend.buysell = function(nodename)
106 local buysell = nil
107 if ( nodename == "easyvend:depositor" or nodename == "easyvend:depositor_on" ) then
108 buysell = "buy"
109 elseif ( nodename == "easyvend:vendor" or nodename == "easyvend:vendor_on" ) then
110 buysell = "sell"
112 return buysell
115 easyvend.is_machine = function(nodename)
116 return ( nodename == "easyvend:depositor_on" or nodename == "easyvend:vendor_on" or nodename == "easyvend:depositor" or nodename == "easyvend:vendor" )
119 easyvend.is_active = function(nodename)
120 if ( nodename == "easyvend:depositor_on" or nodename == "easyvend:vendor_on" ) then
121 return true
122 elseif ( nodename == "easyvend:depositor" or nodename == "easyvend:vendor" ) then
123 return false
124 else
125 return nil
129 easyvend.set_formspec = function(pos, player)
130 local meta = minetest.get_meta(pos)
131 local node = minetest.get_node(pos)
133 local description = minetest.registered_nodes[node.name].description;
134 local number = meta:get_int("number")
135 local cost = meta:get_int("cost")
136 local itemname = meta:get_string("itemname")
137 local bg = ""
138 local configmode = meta:get_int("configmode") == 1
139 if minetest.get_modpath("default") then
140 bg = default.gui_bg .. default.gui_bg_img .. default.gui_slots
143 local numbertext, costtext, buysellbuttontext
144 local itemcounttooltip = "Item count (append ā€œsā€ to multiply with maximum stack size)"
145 local buysell = easyvend.buysell(node.name)
146 if buysell == "sell" then
147 numbertext = "Offered item"
148 costtext = "Price"
149 buysellbuttontext = "Buy"
150 elseif buysell == "buy" then
151 numbertext = "Requested item"
152 costtext = "Payment"
153 buysellbuttontext = "Sell"
154 else
155 return
157 local status = meta:get_string("status")
158 if status == "" then status = "Unknown." end
159 local message = meta:get_string("message")
160 if message == "" then message = "No message." end
161 local status_image
162 if node.name == "easyvend:vendor_on" or node.name == "easyvend:depositor_on" then
163 status_image = "easyvend_status_on.png"
164 else
165 status_image = "easyvend_status_off.png"
168 -- TODO: Expose number of items in stock
170 local formspec = "size[8,7.3;]"
171 .. bg
172 .."label[3,-0.2;" .. minetest.formspec_escape(description) .. "]"
174 .."image[7.5,0.2;0.5,1;" .. status_image .. "]"
175 .."textarea[2.8,0.2;5.1,2;;Status: " .. minetest.formspec_escape(status) .. ";]"
176 .."textarea[2.8,1.3;5.6,2;;Message: " .. minetest.formspec_escape(message) .. ";]"
178 .."label[0,-0.15;"..numbertext.."]"
179 .."label[0,1.2;"..costtext.."]"
180 .."list[current_player;main;0,3.5;8,4;]"
182 if configmode then
183 local wear = "false"
184 if meta:get_int("wear") == 1 then wear = "true" end
185 formspec = formspec
186 .."item_image_button[0,1.65;1,1;"..easyvend.currency..";easyvend.currency_image;]"
187 .."list[current_name;item;0,0.35;1,1;]"
188 .."listring[current_player;main]"
189 .."listring[current_name;item]"
190 .."field[1.3,0.65;1.5,1;number;;" .. number .. "]"
191 .."tooltip[number;"..itemcounttooltip.."]"
192 .."field[1.3,1.95;1.5,1;cost;;" .. cost .. "]"
193 .."tooltip[cost;"..itemcounttooltip.."]"
194 .."button[6,2.8;2,0.5;save;Confirm]"
195 .."tooltip[save;Confirm configuration and activate machine (only for owner)]"
196 if minetest.get_modpath("select_item") then
197 formspec = formspec .. "button[0,2.8;2,0.5;select_item;Select item]"
199 local weartext, weartooltip
200 if buysell == "buy" then
201 weartext = "Buy worn tools"
202 weartooltip = "If disabled, only tools in perfect condition will be bought from sellers (only settable by owner)"
203 else
204 weartext = "Sell worn tools"
205 weartooltip = "If disabled, only tools in perfect condition will be sold (only settable by owner)"
207 if minetest.registered_tools[itemname] ~= nil then
208 formspec = formspec .."checkbox[2,2.4;wear;"..minetest.formspec_escape(weartext)..";"..wear.."]"
209 .."tooltip[wear;"..minetest.formspec_escape(weartooltip).."]"
211 else
212 formspec = formspec
213 .."item_image_button[0,1.65;1,1;"..easyvend.currency..";easyvend.currency_image;]"
214 .."item_image_button[0,0.35;1,1;"..itemname..";item_image;]"
215 .."label[1,1.85;Ɨ" .. cost .. "]"
216 .."label[1,0.55;Ɨ" .. number .. "]"
217 .."button[6,2.8;2,0.5;config;Configure]"
218 if buysell == "sell" then
219 formspec = formspec .. "tooltip[config;Configure offered items and price (only for owner)]"
220 else
221 formspec = formspec .. "tooltip[config;Configure requested items and payment (only for owner)]"
223 formspec = formspec .."button[0,2.8;2,0.5;buysell;"..buysellbuttontext.."]"
224 if minetest.registered_tools[itemname] ~= nil then
225 local weartext
226 if meta:get_int("wear") == 0 then
227 if buysell == "buy" then
228 weartext = "Only intact tools are bought."
229 else
230 weartext = "Only intact tools are sold."
232 else
233 if buysell == "sell" then
234 weartext = "Note: Might sell worn tools."
235 else
236 weartext = "Accepts worn tools."
239 if weartext ~= nil then
240 formspec = formspec .."textarea[2.3,2.6;3,1;;"..minetest.formspec_escape(weartext)..";]"
245 meta:set_string("formspec", formspec)
248 easyvend.machine_disable = function(pos, node, playername)
249 if node.name == "easyvend:vendor_on" then
250 easyvend.sound_disable(pos)
251 minetest.swap_node(pos, {name="easyvend:vendor", param2 = node.param2})
252 return true
253 elseif node.name == "easyvend:depositor_on" then
254 easyvend.sound_disable(pos)
255 minetest.swap_node(pos, {name="easyvend:depositor", param2 = node.param2})
256 return true
257 else
258 if playername ~= nil then
259 easyvend.sound_error(playername)
261 return false
265 easyvend.machine_enable = function(pos, node)
266 if node.name == "easyvend:vendor" then
267 easyvend.sound_setup(pos)
268 minetest.swap_node(pos, {name="easyvend:vendor_on", param2 = node.param2})
269 return true
270 elseif node.name == "easyvend:depositor" then
271 easyvend.sound_setup(pos)
272 minetest.swap_node(pos, {name="easyvend:depositor_on", param2 = node.param2})
273 return true
274 else
275 return false
279 easyvend.machine_check = function(pos, node)
280 local active = true
281 local status = "Ready."
283 local meta = minetest.get_meta(pos)
285 local machine_owner = meta:get_string("owner")
286 local number = meta:get_int("number")
287 local cost = meta:get_int("cost")
288 local itemname = meta:get_string("itemname")
289 local check_wear = meta:get_int("wear") == 0
290 local inv = meta:get_inventory()
291 local itemstack = inv:get_stack("item",1)
292 local buysell = easyvend.buysell(node.name)
294 local chest_pos_remove, chest_error_remove, chest_pos_add, chest_error_add
295 if buysell == "sell" then
296 chest_pos_remove, chest_error_remove = easyvend.find_connected_chest(machine_owner, pos, itemname, check_wear, number, true)
297 chest_pos_add, chest_error_add = easyvend.find_connected_chest(machine_owner, pos, easyvend.currency, check_wear, cost, false)
298 else
299 chest_pos_remove, chest_error_remove = easyvend.find_connected_chest(machine_owner, pos, easyvend.currency, check_wear, cost, true)
300 chest_pos_add, chest_error_add = easyvend.find_connected_chest(machine_owner, pos, itemname, check_wear, number, false)
302 if chest_pos_remove and chest_pos_add then
303 local rchest, rchestdef, rchest_meta, rchest_inv
304 rchest = minetest.get_node(chest_pos_remove)
305 rchestdef = registered_chests[rchest.name]
306 rchest_meta = minetest.get_meta(chest_pos_remove)
307 rchest_inv = rchest_meta:get_inventory()
309 local checkstack, checkitem
310 if buysell == "buy" then
311 checkitem = easyvend.currency
312 else
313 checkitem = itemname
315 local stock = 0
316 -- Count stock
317 -- FIXME: Ignore tools with bad wear level
318 for i=1,rchest_inv:get_size(rchestdef.inv_list) do
319 checkstack = rchest_inv:get_stack(rchestdef.inv_list, i)
320 if checkstack:get_name() == checkitem then
321 stock = stock + checkstack:get_count()
324 meta:set_int("stock", stock)
326 if not itemstack:is_empty() then
327 local number_stack_max = itemstack:get_stack_max()
328 local maxnumber = number_stack_max * slots_max
329 if not(number >= 1 and number <= maxnumber and cost >= 1 and cost <= maxcost) then
330 active = false
331 if buysell == "sell" then
332 status = "Invalid item count or price."
333 else
334 status = "Invalid item count or payment."
337 else
338 active = false
339 status = "Awaiting configuration by owner."
341 else
342 active = false
343 meta:set_int("stock", 0)
344 if chest_error_remove == "no_chest" and chest_error_add == "no_chest" then
345 status = "No storage; machine needs to be connected with a locked chest."
346 elseif chest_error_remove == "not_owned" or chest_error_add == "not_owned" then
347 status = "Storage canā€™t be accessed because it is owned by a different person!"
348 elseif chest_error_remove == "no_stock" then
349 if buysell == "sell" then
350 status = "The vending machine has insufficient materials!"
351 else
352 status = "The depositing machine is out of money!"
354 elseif chest_error_add == "no_space" then
355 status = "No room in the machineā€™s storage!"
356 else
357 status = "Unknown error!"
360 if meta:get_int("configmode") == 1 then
361 active = false
362 status = "Awaiting configuration by owner."
365 if itemname == easyvend.currency and number == cost and active then
366 local jt = meta:get_int("joketimer")
367 if jt > 0 then
368 jt = jt - 1
370 if jt == 0 then
371 if buysell == "sell" then
372 meta:set_string("message", "Item bought.")
373 else
374 meta:set_string("message", "Item sold.")
376 jt = -1
378 meta:set_int("joketimer", jt)
380 meta:set_string("status", status)
382 meta:set_string("infotext", easyvend.make_infotext(node.name, machine_owner, cost, number, itemname))
383 itemname=itemstack:get_name()
384 meta:set_string("itemname", itemname)
386 if minetest.get_modpath("awards") and buysell == "sell" then
387 if minetest.get_player_by_name(machine_owner) then
388 local earnings = meta:get_int("earnings")
389 if earnings >= 1 then
390 awards.unlock(machine_owner, "easyvend_seller")
392 if earnings >= easyvend.powerseller then
393 awards.unlock(machine_owner, "easyvend_powerseller")
398 local change
399 if node.name == "easyvend:vendor" or node.name == "easyvend:depositor" then
400 if active then change = easyvend.machine_enable(pos, node) end
401 elseif node.name == "easyvend:vendor_on" or node.name == "easyvend:depositor_on" then
402 if not active then change = easyvend.machine_disable(pos, node) end
404 easyvend.set_formspec(pos)
405 return change
408 easyvend.on_receive_fields_config = function(pos, formname, fields, sender)
409 local node = minetest.get_node(pos)
410 local meta = minetest.get_meta(pos)
411 local inv_self = meta:get_inventory()
412 local itemstack = inv_self:get_stack("item",1)
413 local buysell = easyvend.buysell(node.name)
415 if fields.config then
416 meta:set_int("configmode", 1)
417 local was_active = easyvend.is_active(node.name)
418 if was_active then
419 meta:set_string("message", "Configuration mode activated; machine disabled.")
420 else
421 meta:set_string("message", "Configuration mode activated.")
423 easyvend.machine_check(pos, node)
424 return
427 if not fields.save then
428 return
431 local number = fields.number
432 local cost = fields.cost
434 --[[ Convenience function:
435 When appending ā€œsā€ or ā€œSā€ to the number, it is multiplied
436 by the maximum stack size. ]]
437 local number_stack_max = itemstack:get_stack_max()
438 local ss = string.sub(number, #number, #number)
439 if ss == "s" or ss == "S" then
440 local n = tonumber(string.sub(number, 1, #number-1))
441 if string.len(number) == 1 then n = 1 end
442 if n ~= nil then
443 number = n * number_stack_max
446 ss = string.sub(cost, #cost, #cost)
447 if ss == "s" or ss == "S" then
448 local n = tonumber(string.sub(cost, 1, #cost-1))
449 if string.len(cost) == 1 then n = 1 end
450 if n ~= nil then
451 cost = n * cost_stack_max
454 number = tonumber(number)
455 cost = tonumber(cost)
457 local itemname=""
459 local oldnumber = meta:get_int("number")
460 local oldcost = meta:get_int("cost")
461 local maxnumber = number_stack_max * slots_max
463 if ( itemstack == nil or itemstack:is_empty() ) then
464 meta:set_string("status", "Awaiting configuration by owner.")
465 meta:set_string("message", "No item specified.")
466 easyvend.sound_error(sender:get_player_name())
467 easyvend.set_formspec(pos, sender)
468 return
469 elseif ( not itemstack:is_known() ) then
470 meta:set_string("status", "Awaiting configuration by owner.")
471 meta:set_string("message", "Unknown item specified.")
472 easyvend.sound_error(sender:get_player_name())
473 easyvend.set_formspec(pos, sender)
474 return
475 elseif ( number == nil or number < 1 or number > maxnumber ) then
476 if maxnumber > 1 then
477 meta:set_string("message", string.format("Invalid item count; must be between 1 and %d!", maxnumber))
478 else
479 meta:set_string("message", "Invalid item count; must be exactly 1!")
481 meta:set_int("number", oldnumber)
482 easyvend.sound_error(sender:get_player_name())
483 easyvend.set_formspec(pos, sender)
484 return
485 elseif ( cost == nil or cost < 1 or cost > maxcost ) then
486 if maxcost > 1 then
487 meta:set_string("message", string.format("Invalid cost; must be between 1 and %d!", maxcost))
488 else
489 meta:set_string("message", "Invalid cost; must be exactly 1!")
491 meta:set_int("cost", oldcost)
492 easyvend.sound_error(sender:get_player_name())
493 easyvend.set_formspec(pos, sender)
494 return
496 meta:set_int("number", number)
497 meta:set_int("cost", cost)
498 itemname=itemstack:get_name()
499 meta:set_string("itemname", itemname)
500 meta:set_int("configmode", 0)
502 if itemname == easyvend.currency and number == cost and cost <= cost_stack_max then
503 meta:set_string("message", "Configuration successful. I am feeling funny.")
504 meta:set_int("joketimer", joketimer_start)
505 meta:set_int("joke_id", easyvend.assign_joke(buysell))
506 else
507 meta:set_string("message", "Configuration successful.")
510 local change = easyvend.machine_check(pos, node)
512 if not change then
513 if (node.name == "easyvend:vendor_on" or node.name == "easyvend:depositor_on") then
514 easyvend.sound_setup(pos)
515 else
516 easyvend.sound_disable(pos)
521 easyvend.make_infotext = function(nodename, owner, cost, number, itemstring)
522 local d = ""
523 if itemstring == nil or itemstring == "" or number == 0 or cost == 0 then
524 if easyvend.buysell(nodename) == "sell" then
525 d = string.format("Inactive vending machine (owned by %s)", owner)
526 else
527 d = string.format("Inactive depositing machine (owned by %s)", owner)
529 return d
531 local iname
532 if minetest.registered_items[itemstring] then
533 iname = minetest.registered_items[itemstring].description
534 else
535 iname = string.format("Unknown Item (%s)", itemstring)
537 if iname == nil then iname = itemstring end
538 local printitem, printcost
539 if number == 1 then
540 printitem = iname
541 else
542 printitem = string.format("%dƗ%s", number, iname)
544 if cost == 1 then
545 printcost = easyvend.currency_desc
546 else
547 printcost = string.format("%dƗ%s", cost, easyvend.currency_desc)
549 if nodename == "easyvend:vendor_on" then
550 d = string.format("Vending machine (owned by %s)\nSelling: %s\nPrice: %s", owner, printitem, printcost)
551 elseif nodename == "easyvend:vendor" then
552 d = string.format("Inactive vending machine (owned by %s)\nSelling: %s\nPrice: %s", owner, printitem, printcost)
553 elseif nodename == "easyvend:depositor_on" then
554 d = string.format("Depositing machine (owned by %s)\nBuying: %s\nPayment: %s", owner, printitem, printcost)
555 elseif nodename == "easyvend:depositor" then
556 d = string.format("Inactive depositing machine (owned by %s)\nBuying: %s\nPayment: %s", owner, printitem, printcost)
558 return d
561 if minetest.get_modpath("awards") then
562 awards.register_achievement("easyvend_seller",{
563 title = "First Sale",
564 description = "Sell something with a vending machine.",
565 icon = "easyvend_vendor_front_on.png^awards_level1.png",
567 local desc_powerseller
568 if easyvend.currency == "default:gold_ingot" then
569 desc_powerseller = string.format("Earn %d gold ingots by selling goods with a single vending machine.", easyvend.powerseller)
570 else
571 desc_powerseller = string.format("Earn %d currency items by selling goods with a single vending machine.", easyvend.powerseller)
573 awards.register_achievement("easyvend_powerseller",{
574 title = "Power Seller",
575 description = desc_powerseller,
576 icon = "easyvend_vendor_front_on.png^awards_level2.png",
580 easyvend.check_earnings = function(buyername, nodemeta)
581 local owner = nodemeta:get_string("owner")
582 if buyername ~= owner then
583 local cost = nodemeta:get_int("cost")
584 local itemname = nodemeta:get_string("itemname")
585 -- First sell
586 if minetest.get_modpath("awards") and minetest.get_player_by_name(owner) ~= nil then
587 awards.unlock(owner, "easyvend_seller")
589 if itemname ~= easyvend.currency then
590 local newearnings = nodemeta:get_int("earnings") + cost
591 if newearnings >= easyvend.powerseller and minetest.get_modpath("awards") then
592 if minetest.get_player_by_name(owner) ~= nil then
593 awards.unlock(owner, "easyvend_powerseller")
596 nodemeta:set_int("earnings", newearnings)
601 easyvend.on_receive_fields_buysell = function(pos, formname, fields, sender)
602 local sendername = sender:get_player_name()
603 local meta = minetest.get_meta(pos)
605 if not fields.buysell then
606 return
609 local node = minetest.get_node(pos)
610 local number = meta:get_int("number")
611 local cost = meta:get_int("cost")
612 local itemname=meta:get_string("itemname")
613 local item=meta:get_inventory():get_stack("item", 1)
614 local check_wear = meta:get_int("wear") == 0 and minetest.registered_tools[itemname] ~= nil
615 local machine_owner = meta:get_string("owner")
617 local buysell = easyvend.buysell(node.name)
619 local number_stack_max = item:get_stack_max()
620 local maxnumber = number_stack_max * slots_max
622 if ( number == nil or number < 1 or number > maxnumber ) or
623 ( cost == nil or cost < 1 or cost > maxcost ) or
624 ( itemname == nil or itemname=="") then
625 meta:set_string("status", "Invalid item count or price!")
626 easyvend.machine_disable(pos, node, sendername)
627 return
630 local chest_pos_remove, chest_error_remove, chest_pos_add, chest_error_add
631 if buysell == "sell" then
632 chest_pos_remove, chest_error_remove = easyvend.find_connected_chest(machine_owner, pos, itemname, check_wear, number, true)
633 chest_pos_add, chest_error_add = easyvend.find_connected_chest(machine_owner, pos, easyvend.currency, check_wear, cost, false)
634 else
635 chest_pos_remove, chest_error_remove = easyvend.find_connected_chest(machine_owner, pos, easyvend.currency, check_wear, cost, true)
636 chest_pos_add, chest_error_add = easyvend.find_connected_chest(machine_owner, pos, itemname, check_wear, number, false)
639 if chest_pos_remove ~= nil and chest_pos_add ~= nil and sender and sender:is_player() then
640 local rchest = minetest.get_node(chest_pos_remove)
641 local rchestdef = registered_chests[rchest.name]
642 local rchest_meta = minetest.get_meta(chest_pos_remove)
643 local rchest_inv = rchest_meta:get_inventory()
644 local achest = minetest.get_node(chest_pos_add)
645 local achestdef = registered_chests[achest.name]
646 local achest_meta = minetest.get_meta(chest_pos_add)
647 local achest_inv = achest_meta:get_inventory()
649 local player_inv = sender:get_inventory()
651 local stack = {name=itemname, count=number, wear=0, metadata=""}
652 local price = {name=easyvend.currency, count=cost, wear=0, metadata=""}
653 local chest_has, player_has, chest_free, player_free, chest_out, player_out
654 local msg = ""
655 if buysell == "sell" then
656 chest_has, chest_out = easyvend.check_and_get_items(rchest_inv, rchestdef.inv_list, stack, check_wear)
657 player_has, player_out = easyvend.check_and_get_items(player_inv, "main", price, check_wear)
658 chest_free = achest_inv:room_for_item(achestdef.inv_list, price)
659 player_free = player_inv:room_for_item("main", stack)
660 if chest_has and player_has and chest_free and player_free then
661 if cost <= cost_stack_max and number <= number_stack_max then
662 easyvend.machine_enable(pos, node)
663 player_inv:remove_item("main", price)
664 if check_wear then
665 rchest_inv:set_stack(rchestdef.inv_list, chest_out[1].id, "")
666 player_inv:add_item("main", chest_out[1].item)
667 else
668 stack = rchest_inv:remove_item(rchestdef.inv_list, stack)
669 player_inv:add_item("main", stack)
671 achest_inv:add_item(achestdef.inv_list, price)
672 if itemname == easyvend.currency and number == cost and cost <= cost_stack_max then
673 meta:set_string("message", easyvend.get_joke(buysell, meta:get_int("joke_id")))
674 meta:set_int("joketimer", joketimer_start)
675 else
676 meta:set_string("message", "Item bought.")
678 easyvend.check_earnings(sendername, meta)
679 easyvend.sound_vend(pos)
680 easyvend.machine_check(pos, node)
681 else
682 -- Large item counts (multiple stacks)
683 local coststacks = math.modf(cost / cost_stack_max)
684 local costremainder = math.fmod(cost, cost_stack_max)
685 local numberstacks = math.modf(number / number_stack_max)
686 local numberremainder = math.fmod(number, number_stack_max)
687 local numberfree = numberstacks
688 local costfree = coststacks
689 if numberremainder > 0 then numberfree = numberfree + 1 end
690 if costremainder > 0 then costfree = costfree + 1 end
691 if not player_free and easyvend.free_slots(player_inv, "main") < numberfree then
692 if numberfree > 1 then
693 msg = string.format("No room in your inventory (%d empty slots required)!", numberfree)
694 else
695 msg = "No room in your inventory!"
697 meta:set_string("message", msg)
698 elseif not chest_free and easyvend.free_slots(achest_inv, achestdef.inv_list) < costfree then
699 meta:set_string("status", "No room in the machineā€™s storage!")
700 easyvend.machine_disable(pos, node, sendername)
701 else
702 -- Remember items for transfer
703 local cheststacks = {}
704 easyvend.machine_enable(pos, node)
705 for i=1, coststacks do
706 price.count = cost_stack_max
707 player_inv:remove_item("main", price)
709 if costremainder > 0 then
710 price.count = costremainder
711 player_inv:remove_item("main", price)
713 if check_wear then
714 for o=1,#chest_out do
715 rchest_inv:set_stack(rchestdef.inv_list, chest_out[o].id, "")
717 else
718 for i=1, numberstacks do
719 stack.count = number_stack_max
720 table.insert(cheststacks, rchest_inv:remove_item(rchestdef.inv_list, stack))
723 if numberremainder > 0 then
724 stack.count = numberremainder
725 table.insert(cheststacks, rchest_inv:remove_item(rchestdef.inv_list, stack))
727 for i=1, coststacks do
728 price.count = cost_stack_max
729 achest_inv:add_item(achestdef.inv_list, price)
731 if costremainder > 0 then
732 price.count = costremainder
733 achest_inv:add_item(achestdef.inv_list, price)
735 if check_wear then
736 for o=1,#chest_out do
737 player_inv:add_item("main", chest_out[o].item)
739 else
740 for i=1,#cheststacks do
741 player_inv:add_item("main", cheststacks[i])
744 meta:set_string("message", "Item bought.")
745 easyvend.check_earnings(sendername, meta)
746 easyvend.sound_vend(pos)
747 easyvend.machine_check(pos, node)
750 elseif chest_has and player_has then
751 if not player_free then
752 msg = "No room in your inventory!"
753 meta:set_string("message", msg)
754 easyvend.sound_error(sendername)
755 elseif not chest_free then
756 msg = "No room in the machineā€™s storage!"
757 meta:set_string("status", msg)
758 easyvend.machine_disable(pos, node, sendername)
760 else
761 if not chest_has then
762 msg = "The vending machine has insufficient materials!"
763 meta:set_string("status", msg)
764 easyvend.machine_disable(pos, node, sendername)
765 elseif not player_has then
766 msg = "You canā€™t afford this item!"
767 meta:set_string("message", msg)
768 easyvend.sound_error(sendername)
771 else
772 chest_has, chest_out = easyvend.check_and_get_items(rchest_inv, rchestdef.inv_list, price, check_wear)
773 player_has, player_out = easyvend.check_and_get_items(player_inv, "main", stack, check_wear)
774 chest_free = achest_inv:room_for_item(achestdef.inv_list, stack)
775 player_free = player_inv:room_for_item("main", price)
776 if chest_has and player_has and chest_free and player_free then
777 if cost <= cost_stack_max and number <= number_stack_max then
778 easyvend.machine_enable(pos, node)
779 if check_wear then
780 player_inv:set_stack("main", player_out[1].id, "")
781 achest_inv:add_item(achestdef.inv_list, player_out[1].item)
782 else
783 stack = player_inv:remove_item("main", stack)
784 achest_inv:add_item(achestdef.inv_list, stack)
786 rchest_inv:remove_item(rchestdef.inv_list, price)
787 player_inv:add_item("main", price)
788 meta:set_string("status", "Ready.")
789 if itemname == easyvend.currency and number == cost and cost <= cost_stack_max then
790 meta:set_string("message", easyvend.get_joke(buysell, meta:get_int("joke_id")))
791 meta:set_int("joketimer", joketimer_start)
792 else
793 meta:set_string("message", "Item sold.")
795 easyvend.sound_deposit(pos)
796 easyvend.machine_check(pos, node)
797 else
798 -- Large item counts (multiple stacks)
799 local coststacks = math.modf(cost / cost_stack_max)
800 local costremainder = math.fmod(cost, cost_stack_max)
801 local numberstacks = math.modf(number / number_stack_max)
802 local numberremainder = math.fmod(number, number_stack_max)
803 local numberfree = numberstacks
804 local costfree = coststacks
805 if numberremainder > 0 then numberfree = numberfree + 1 end
806 if costremainder > 0 then costfree = costfree + 1 end
807 if not player_free and easyvend.free_slots(player_inv, "main") < costfree then
808 if costfree > 1 then
809 msg = string.format("No room in your inventory (%d empty slots required)!", costfree)
810 else
811 msg = "No room in your inventory!"
813 meta:set_string("message", msg)
814 easyvend.sound_error(sendername)
815 elseif not chest_free and easyvend.free_slots(achest_inv, achestdef.inv_list) < numberfree then
816 meta:set_string("status", "No room in the machineā€™s storage!")
817 easyvend.machine_disable(pos, node, sendername)
818 else
819 easyvend.machine_enable(pos, node)
820 -- Remember removed items for transfer
821 local playerstacks = {}
822 for i=1, coststacks do
823 price.count = cost_stack_max
824 rchest_inv:remove_item(rchestdef.inv_list, price)
826 if costremainder > 0 then
827 price.count = costremainder
828 rchest_inv:remove_item(rchestdef.inv_list, price)
830 if check_wear then
831 for o=1,#player_out do
832 player_inv:set_stack("main", player_out[o].id, "")
834 else
835 for i=1, numberstacks do
836 stack.count = number_stack_max
837 table.insert(playerstacks, player_inv:remove_item("main", stack))
840 if numberremainder > 0 then
841 stack.count = numberremainder
842 table.insert(playerstacks, player_inv:remove_item("main", stack))
844 for i=1, coststacks do
845 price.count = cost_stack_max
846 player_inv:add_item("main", price)
848 if costremainder > 0 then
849 price.count = costremainder
850 player_inv:add_item("main", price)
852 if check_wear then
853 for o=1,#player_out do
854 achest_inv:add_item(achestdef.inv_list, player_out[o].item)
856 else
857 for i=1,#playerstacks do
858 achest_inv:add_item(achestdef.inv_list, playerstacks[i])
861 meta:set_string("message", "Item sold.")
862 easyvend.sound_deposit(pos)
863 easyvend.machine_check(pos, node)
866 elseif chest_has and player_has then
867 if not player_free then
868 msg = "No room in your inventory!"
869 meta:set_string("message", msg)
870 easyvend.sound_error(sendername)
871 elseif not chest_free then
872 msg = "No room in the machineā€™s storage!"
873 meta:set_string("status", msg)
874 easyvend.machine_disable(pos, node, sendername)
876 else
877 if not player_has then
878 msg = "You have insufficient materials!"
879 meta:set_string("message", msg)
880 easyvend.sound_error(sendername)
881 elseif not chest_has then
882 msg = "The depositing machine is out of money!"
883 meta:set_string("status", msg)
884 easyvend.machine_disable(pos, node, sendername)
888 else
889 local status
890 meta:set_int("stock", 0)
891 if chest_error_remove == "no_chest" and chest_error_add == "no_chest" then
892 status = "No storage; machine needs to be connected with a locked chest."
893 elseif chest_error_remove == "not_owned" or chest_error_add == "not_owned" then
894 status = "Storage canā€™t be accessed because it is owned by a different person!"
895 elseif chest_error_remove == "no_stock" then
896 if buysell == "sell" then
897 status = "The vending machine has insufficient materials!"
898 else
899 status = "The depositing machine is out of money!"
901 elseif chest_error_add == "no_space" then
902 status = "No room in the machineā€™s storage!"
903 else
904 status = "Unknown error!"
906 meta:set_string("status", status)
907 easyvend.sound_error(sendername)
910 easyvend.set_formspec(pos, sender)
914 easyvend.after_place_node = function(pos, placer)
915 local node = minetest.get_node(pos)
916 local meta = minetest.get_meta(pos)
917 local inv = meta:get_inventory()
918 local player_name = placer:get_player_name()
919 inv:set_size("item", 1)
920 inv:set_size("gold", 1)
922 inv:set_stack( "gold", 1, easyvend.currency )
924 local d = ""
925 if node.name == "easyvend:vendor" then
926 d = string.format("Inactive vending machine (owned by %s)", player_name)
927 meta:set_int("wear", 1)
928 -- Total number of currency items earned for the machine's life time (excluding currency-currency trading)
929 meta:set_int("earnings", 0)
930 elseif node.name == "easyvend:depositor" then
931 d = string.format("Inactive depositing machine (owned by %s)", player_name)
932 meta:set_int("wear", 0)
934 meta:set_string("infotext", d)
935 meta:set_string("status", "Awaiting configuration by owner.")
936 meta:set_string("message", "Please select an item and amount, then confirm.")
937 meta:set_int("number", 1)
938 meta:set_int("cost", 1)
939 meta:set_int("stock", -1)
940 meta:set_int("configmode", 1)
941 meta:set_int("joketimer", -1)
942 meta:set_int("joke_id", 1)
943 meta:set_string("itemname", "")
945 meta:set_string("owner", player_name or "")
947 easyvend.set_formspec(pos, placer)
950 easyvend.can_dig = function(pos, player)
951 local meta = minetest.get_meta(pos)
952 local name = player:get_player_name()
953 local owner = meta:get_string("owner")
954 -- Owner can always dig shop
955 if owner == name then
956 return true
958 local chest_pos = easyvend.find_connected_chest(owner, pos)
959 local chest, meta_chest
960 if chest_pos then
961 chest = minetest.get_node(chest_pos)
962 meta_chest = minetest.get_meta(chest_pos)
963 else
964 return true --if no chest, enyone can dig this shop
966 if registered_chests[chest.name] then
967 if player and player:is_player() then
968 local owner_chest = meta_chest:get_string(registered_chests[chest.name].meta_owner)
969 if name == owner_chest then
970 return true --chest owner can also dig shop
973 return false
974 else
975 return true --if no chest, enyone can dig this shop
979 easyvend.on_receive_fields = function(pos, formname, fields, sender)
980 local meta = minetest.get_meta(pos)
981 local node = minetest.get_node(pos)
982 local owner = meta:get_string("owner")
983 local sendername = sender:get_player_name()
985 if fields.doc then
986 if minetest.get_modpath("doc") and minetest.get_modpath("doc_items") then
987 if easyvend.buysell(node.name) == "buy" then
988 doc.show_entry(sendername, "nodes", "easyvend:depositor", true)
989 else
990 doc.show_entry(sendername, "nodes", "easyvend:vendor", true)
993 elseif fields.config or fields.save or fields.usermode then
994 if sendername == owner then
995 easyvend.on_receive_fields_config(pos, formname, fields, sender)
996 else
997 meta:set_string("message", "Only the owner may change the configuration.")
998 easyvend.sound_error(sendername)
999 easyvend.set_formspec(pos, sender)
1000 return
1002 elseif fields.select_item then
1003 if minetest.get_modpath("select_item") then
1004 if sendername == owner then
1005 active_item_selection[sendername] = pos
1006 select_item.show_dialog(sendername, "easyvend:trade_item", select_item.filters.creative)
1007 else
1008 meta:set_string("message", "Only the owner may change the configuration.")
1009 easyvend.sound_error(sendername)
1010 easyvend.set_formspec(pos, sender)
1011 return
1014 elseif fields.wear ~= nil then
1015 if sender:get_player_name() == owner then
1016 if fields.wear == "true" then
1017 if easyvend.buysell(node.name) == "buy" then
1018 meta:set_string("message", "Used tools are now accepted.")
1019 else
1020 meta:set_string("message", "Used tools are now for sale.")
1022 meta:set_int("wear", 1)
1023 elseif fields.wear == "false" then
1024 if easyvend.buysell(node.name) == "buy" then
1025 meta:set_string("message", "Used tools are now rejected.")
1026 else
1027 meta:set_string("message", "Used tools wonā€™t be sold anymore.")
1029 meta:set_int("wear", 0)
1031 easyvend.set_formspec(pos, sender)
1032 return
1033 else
1034 meta:set_string("message", "Only the owner may change the configuration.")
1035 easyvend.sound_error(sendername)
1036 easyvend.set_formspec(pos, sender)
1037 return
1039 elseif fields.buysell then
1040 easyvend.on_receive_fields_buysell(pos, formname, fields, sender)
1044 -- Jokes: Appear when machine exchanges currency for currency at equal rate
1046 -- Vendor
1047 local jokes_vendor = {
1048 "Thank you. You have made a vending machine very happy.",
1049 "Humans have a strange sense of humor.",
1050 "Letā€™s get this over with ā€¦",
1051 "Item ā€œboughtā€.",
1052 "Tit for tat.",
1053 "Do you realize what youā€™ve just bought?",
1055 -- Depositor
1056 local jokes_depositor = {
1057 "Thank you, the money started to smell inside.",
1058 "Money doesnā€™t grow on trees, you know?",
1059 "Sanity sold.",
1060 "Well, that was an awkward exchange.",
1061 "Are you having fun?",
1062 "Is this really trading?",
1065 easyvend.assign_joke = function(buysell)
1066 local jokes
1067 if buysell == "sell" then
1068 jokes = jokes_vendor
1069 elseif buysell == "buy" then
1070 jokes = jokes_depositor
1072 local r = math.random(1,#jokes)
1073 return r
1076 easyvend.get_joke = function(buysell, id)
1077 local joke
1078 if buysell == nil or id == nil then
1079 -- Fallback message (should never happen)
1080 return "Items exchanged."
1082 if buysell == "sell" then
1083 joke = jokes_vendor[id]
1084 if joke == nil then joke = jokes_vendor[1] end
1085 elseif buysell == "buy" then
1086 joke = jokes_depositor[id]
1087 if joke == nil then joke = jokes_depositor[1] end
1089 return joke
1092 easyvend.sound_error = function(playername)
1093 minetest.sound_play("easyvend_error", {to_player = playername, gain = 0.25})
1096 easyvend.sound_setup = function(pos)
1097 minetest.sound_play("easyvend_activate", {pos = pos, gain = 0.5, max_hear_distance = 12,})
1100 easyvend.sound_disable = function(pos)
1101 minetest.sound_play("easyvend_disable", {pos = pos, gain = 0.9, max_hear_distance = 12,})
1104 easyvend.sound_vend = function(pos)
1105 minetest.sound_play("easyvend_vend", {pos = pos, gain = 0.4, max_hear_distance = 5,})
1108 easyvend.sound_deposit = function(pos)
1109 minetest.sound_play("easyvend_deposit", {pos = pos, gain = 0.4, max_hear_distance = 5,})
1112 --[[ Tower building ]]
1114 easyvend.is_traversable = function(pos)
1115 local node = minetest.get_node_or_nil(pos)
1116 if (node == nil) then
1117 return false
1119 return traversable_node_types[node.name] == true
1122 easyvend.neighboring_nodes = function(pos)
1123 local check = {
1124 {x=pos.x, y=pos.y-1, z=pos.z},
1125 {x=pos.x, y=pos.y+1, z=pos.z},
1127 local trav = {}
1128 for i=1,#check do
1129 if easyvend.is_traversable(check[i]) then
1130 table.insert(trav, check[i])
1133 return trav
1136 easyvend.find_connected_chest = function(owner, pos, nodename, check_wear, amount, removing)
1137 local nodes = easyvend.neighboring_nodes(pos)
1139 if (#nodes < 1 or #nodes > 2) then
1140 return nil, "no_chest"
1143 -- Find the stack direction
1144 local first = nil
1145 local second = nil
1146 for i=1,#nodes do
1147 if ( first == nil ) then
1148 first = nodes[i]
1149 else
1150 second = nodes[i]
1154 local chest_pos, chest_internal
1156 if (first ~= nil and second ~= nil) then
1157 local dy = (first.y - second.y)/2
1158 chest_pos, chest_internal = easyvend.find_chest(owner, pos, dy, nodename, check_wear, amount, removing)
1159 if ( chest_pos == nil ) then
1160 chest_pos, chest_internal = easyvend.find_chest(owner, pos, -dy, nodename, check_wear, amount, removing, chest_internal)
1162 else
1163 local dy = first.y - pos.y
1164 chest_pos, chest_internal = easyvend.find_chest(owner, pos, dy, nodename, check_wear, amount, removing)
1167 if chest_internal.chests == 0 then
1168 return nil, "no_chest"
1169 elseif chest_internal.chests == chest_internal.other_chests then
1170 return nil, "not_owned"
1171 elseif removing and chest_internal.stock < 1 then
1172 return nil, "no_stock"
1173 elseif not removing and chest_internal.space < 1 then
1174 return nil, "no_space"
1175 elseif chest_pos ~= nil then
1176 return chest_pos
1177 else
1178 return nil, "unknown"
1182 easyvend.find_chest = function(owner, pos, dy, itemname, check_wear, amount, removing, internal)
1183 pos = {x=pos.x, y=pos.y + dy, z=pos.z}
1185 if internal == nil then
1186 internal = {}
1187 internal.chests = 0
1188 internal.other_chests = 0
1189 internal.stock = 0
1190 internal.space = 0
1193 local node = minetest.get_node_or_nil(pos)
1194 if ( node == nil ) then
1195 return nil, internal
1197 local chestdef = registered_chests[node.name]
1198 if (chestdef ~= nil) then
1199 internal.chests = internal.chests + 1
1200 local meta = minetest.get_meta(pos)
1201 if (owner ~= meta:get_string(chestdef.meta_owner)) then
1202 internal.other_chests = internal.other_chests + 1
1203 return nil, internal
1205 local inv = meta:get_inventory()
1206 if (inv ~= nil) then
1207 if (itemname ~= nil and minetest.registered_items[itemname] and amount ~= nil and removing ~= nil and check_wear ~= nil) then
1208 local chest_has, chest_free
1209 local stack = {name=itemname, count=amount, wear=0, metadata=""}
1210 local stack_max = minetest.registered_items[itemname].stack_max
1212 local stacks = math.modf(amount / stack_max)
1213 local stacksremainder = math.fmod(amount, stack_max)
1214 local free = stacks
1215 if stacksremainder > 0 then free = free + 1 end
1217 chest_has = easyvend.check_and_get_items(inv, chestdef.inv_list, stack, check_wear)
1218 if chest_has then
1219 internal.stock = internal.stock + 1
1221 chest_free = inv:room_for_item(chestdef.inv_list, stack) and easyvend.free_slots(inv, chestdef.inv_list) >= free
1222 if chest_free then
1223 internal.space = internal.space + 1
1226 if (removing and internal.stock == 0) or (not removing and internal.space == 0) then
1227 return easyvend.find_chest(owner, pos, dy, itemname, check_wear, amount, removing, internal)
1228 else
1229 return pos, internal
1231 else
1232 return nil, internal
1234 else
1235 return nil, internal
1237 elseif not easyvend.is_machine(node.name) then
1238 return nil, internal
1241 return easyvend.find_chest(owner, pos, dy, itemname, check_wear, amount, removing, internal)
1244 -- Pseudo-inventory handling
1245 easyvend.allow_metadata_inventory_put = function(pos, listname, index, stack, player)
1246 if listname=="item" then
1247 local meta = minetest.get_meta(pos);
1248 local owner = meta:get_string("owner")
1249 local name = player:get_player_name()
1250 if name == owner then
1251 local inv = meta:get_inventory()
1252 if stack == nil then
1253 inv:set_stack( "item", 1, nil )
1254 else
1255 inv:set_stack( "item", 1, stack:get_name() )
1256 meta:set_string("itemname", stack:get_name())
1257 easyvend.set_formspec(pos, player)
1261 return 0
1264 easyvend.allow_metadata_inventory_take = function(pos, listname, index, stack, player)
1265 return 0
1268 easyvend.allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
1269 return 0
1272 minetest.register_abm({
1273 nodenames = {"easyvend:vendor", "easyvend:vendor_on", "easyvend:depositor", "easyvend:depositor_on"},
1274 interval = 5,
1275 chance = 1,
1276 catch_up = false,
1277 action = function(pos, node, active_object_count, active_object_count_wider)
1278 easyvend.machine_check(pos, node)
1282 -- Legacy support for vendor mod:
1283 -- Transform the world and items to use the easyvend nodes/items
1285 -- For safety reasons, only do this when player requested so
1286 if minetest.setting_getbool("easyvend_convert_vendor") == true then
1287 -- Replace vendor nodes
1288 minetest.register_lbm({
1289 name = "easyvend:replace_vendor",
1290 nodenames = { "vendor:vendor", "vendor:depositor" },
1291 run_at_every_load = true,
1292 action = function(pos, node)
1293 -- Replace node
1294 local newnodename
1295 if node.name == "vendor:vendor" then
1296 newnodename = "easyvend:vendor"
1297 elseif node.name == "vendor:depositor" then
1298 newnodename = "easyvend:depositor"
1300 -- Remove axis rotation; only allow 4 facedirs
1301 local p2 = math.fmod(node.param2, 4)
1302 minetest.swap_node(pos, { name = newnodename, param2 = p2 })
1304 -- Initialize metadata
1305 local meta = minetest.get_meta(pos)
1306 if node.name == "vendor:vendor" then
1307 meta:set_int("earnings", 0)
1309 meta:set_int("stock", -1)
1310 meta:set_int("joketimer", -1)
1311 meta:set_int("joke_id", 1)
1312 local inv = meta:get_inventory()
1313 inv:set_size("item", 1)
1314 inv:set_size("gold", 1)
1315 inv:set_stack("gold", 1, easyvend.currency)
1317 -- In vendor, all machines accepted worn tools
1318 meta:set_int("wear", 1)
1320 -- Set item
1321 local itemname = meta:get_string("itemname")
1322 if itemname == "" or itemname == nil then
1323 itemname = meta:get_string("itemtype")
1325 if itemname ~= "" and itemname ~= nil then
1326 inv:set_stack("item", 1, itemname)
1327 meta:set_string("itemname", itemname)
1330 -- Check for valid item, item count and price
1331 local configmode = 1
1332 if itemname ~= "" and itemname ~= nil then
1333 local itemstack = inv:get_stack("item", 1)
1334 local number_stack_max = itemstack:get_stack_max()
1335 local maxnumber = number_stack_max * slots_max
1336 local cost = meta:get_int("cost")
1337 local number = meta:get_int("number")
1338 if number >= 1 and number <= maxnumber and cost >= 1 and cost <= maxcost then
1339 -- Everything's OK, get out of config mode!
1340 configmode = 0
1344 -- Final initialization stuff
1345 meta:set_int("configmode", configmode)
1347 local owner = meta:get_string("owner")
1348 if easyvend.buysell(newnodename) == "sell" then
1349 meta:set_string("infotext", string.format("Vending machine (owned by %s)", owner))
1350 else
1351 meta:set_string("infotext", string.format("Depositing machine (owned by %s)", owner))
1355 meta:set_string("status", "Initializing ā€¦")
1356 meta:set_string("message", "Upgrade successful.")
1357 easyvend.machine_check(pos, node)
1358 end,