Fix error message when using unknown item
[minetest_easyvend.git] / easyvend.lua
blob199fd60eb0ddccb3e73c0af787f4b5c29bde9ce1
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 -- Allow for other mods to register custom chests
17 easyvend.register_chest = function(node_name, inv_list, meta_owner)
18 registered_chests[node_name] = { inv_list = inv_list, meta_owner = meta_owner }
19 traversable_node_types[node_name] = true
20 end
22 -- Partly a wrapper around contains_item, but does special treatment if the item
23 -- is a tool. Basically checks whether the items exist in the supplied inventory
24 -- list. If check_wear is true, only counts items without wear.
25 easyvend.check_and_get_items = function(inventory, listname, itemtable, check_wear)
26 local itemstring = itemtable.name
27 local minimum = itemtable.count
28 if check_wear == nil then check_wear = false end
29 local get_items = {}
30 -- Tool workaround
31 if minetest.registered_tools[itemstring] ~= nil then
32 local count = 0
33 for i=1,inventory:get_size(listname) do
34 local stack = inventory:get_stack(listname, i)
35 if stack:get_name() == itemstring then
36 if not check_wear or stack:get_wear() == 0 then
37 count = count + 1
38 table.insert(get_items, {id=i, item=stack})
39 if count >= minimum then
40 return true, get_items
41 end
42 end
43 end
44 end
45 return false
46 else
47 -- Normal Minetest check
48 return inventory:contains_item(listname, ItemStack(itemtable))
49 end
50 end
53 if minetest.get_modpath("default") ~= nil then
54 easyvend.register_chest("default:chest_locked", "main", "owner")
55 end
57 easyvend.free_slots= function(inv, listname)
58 local size = inv:get_size(listname)
59 local free = 0
60 for i=1,size do
61 local stack = inv:get_stack(listname, i)
62 if stack:is_empty() then
63 free = free + 1
64 end
65 end
66 return free
67 end
69 easyvend.buysell = function(nodename)
70 local buysell = nil
71 if ( nodename == "easyvend:depositor" or nodename == "easyvend:depositor_on" ) then
72 buysell = "buy"
73 elseif ( nodename == "easyvend:vendor" or nodename == "easyvend:vendor_on" ) then
74 buysell = "sell"
75 end
76 return buysell
77 end
79 easyvend.is_active = function(nodename)
80 if ( nodename == "easyvend:depositor_on" or nodename == "easyvend:vendor_on" ) then
81 return true
82 elseif ( nodename == "easyvend:depositor" or nodename == "easyvend:vendor" ) then
83 return false
84 else
85 return nil
86 end
87 end
89 easyvend.set_formspec = function(pos, player)
90 local meta = minetest.get_meta(pos)
91 local node = minetest.get_node(pos)
93 local description = minetest.registered_nodes[node.name].description;
94 local number = meta:get_int("number")
95 local cost = meta:get_int("cost")
96 local itemname = meta:get_string("itemname")
97 local bg = ""
98 local configmode = meta:get_int("configmode") == 1
99 if minetest.get_modpath("default") then
100 bg = default.gui_bg .. default.gui_bg_img .. default.gui_slots
103 local numbertext, costtext, buysellbuttontext
104 local itemcounttooltip = "Item count (append “s” to multiply with maximum stack size)"
105 local buysell = easyvend.buysell(node.name)
106 if buysell == "sell" then
107 numbertext = "Offered item"
108 costtext = "Price"
109 buysellbuttontext = "Buy"
110 elseif buysell == "buy" then
111 numbertext = "Requested item"
112 costtext = "Payment"
113 buysellbuttontext = "Sell"
114 else
115 return
117 local status = meta:get_string("status")
118 if status == "" then status = "Unknown." end
119 local message = meta:get_string("message")
120 if message == "" then message = "No message." end
121 local status_image
122 if node.name == "easyvend:vendor_on" or node.name == "easyvend:depositor_on" then
123 status_image = "easyvend_status_on.png"
124 else
125 status_image = "easyvend_status_off.png"
128 -- TODO: Expose number of items in stock
130 local formspec = "size[8,7.3;]"
131 .. bg
132 .."label[3,-0.2;" .. minetest.formspec_escape(description) .. "]"
134 .."image[7.5,0.2;0.5,1;" .. status_image .. "]"
135 .."textarea[2.8,0.2;5.1,2;;Status: " .. minetest.formspec_escape(status) .. ";]"
136 .."textarea[2.8,1.3;5.6,2;;Message: " .. minetest.formspec_escape(message) .. ";]"
138 .."label[0,-0.15;"..numbertext.."]"
139 .."label[0,1.2;"..costtext.."]"
140 .."list[current_player;main;0,3.5;8,4;]"
141 if minetest.get_modpath("doc") and minetest.get_modpath("doc_items") then
142 if (doc.VERSION.MAJOR >= 1) or (doc.VERSION.MAJOR == 0 and doc.VERSION.MINOR >= 8) then
143 formspec = formspec .. "image_button[7.25,2;0.75,0.75;doc_button_icon_lores.png;doc;]" ..
144 "tooltip[doc;Help]"
148 if configmode then
149 local wear = "false"
150 if meta:get_int("wear") == 1 then wear = "true" end
151 formspec = formspec
152 .."item_image_button[0,1.65;1,1;"..easyvend.currency..";easyvend.currency_image;]"
153 .."list[current_name;item;0,0.35;1,1;]"
154 .."listring[current_player;main]"
155 .."listring[current_name;item]"
156 .."field[1.3,0.65;1.5,1;number;;" .. number .. "]"
157 .."tooltip[number;"..itemcounttooltip.."]"
158 .."field[1.3,1.95;1.5,1;cost;;" .. cost .. "]"
159 .."tooltip[cost;"..itemcounttooltip.."]"
160 .."button[6,2.8;2,0.5;save;Confirm]"
161 .."tooltip[save;Confirm configuration and activate machine (only for owner)]"
162 local weartext, weartooltip
163 if buysell == "buy" then
164 weartext = "Buy worn tools"
165 weartooltip = "If disabled, only tools in perfect condition will be bought from sellers (only settable by owner)"
166 else
167 weartext = "Sell worn tools"
168 weartooltip = "If disabled, only tools in perfect condition will be sold (only settable by owner)"
170 if minetest.registered_tools[itemname] ~= nil then
171 formspec = formspec .."checkbox[2,2.4;wear;"..minetest.formspec_escape(weartext)..";"..wear.."]"
172 .."tooltip[wear;"..minetest.formspec_escape(weartooltip).."]"
174 else
175 formspec = formspec
176 .."item_image_button[0,1.65;1,1;"..easyvend.currency..";easyvend.currency_image;]"
177 .."item_image_button[0,0.35;1,1;"..itemname..";item_image;]"
178 .."label[1,1.85;×" .. cost .. "]"
179 .."label[1,0.55;×" .. number .. "]"
180 .."button[6,2.8;2,0.5;config;Configure]"
181 if buysell == "sell" then
182 formspec = formspec .. "tooltip[config;Configure offered items and price (only for owner)]"
183 else
184 formspec = formspec .. "tooltip[config;Configure requested items and payment (only for owner)]"
186 formspec = formspec .."button[0,2.8;2,0.5;buysell;"..buysellbuttontext.."]"
187 if minetest.registered_tools[itemname] ~= nil then
188 local weartext
189 if meta:get_int("wear") == 0 then
190 if buysell == "buy" then
191 weartext = "Only intact tools are bought."
192 else
193 weartext = "Only intact tools are sold."
195 else
196 if buysell == "sell" then
197 weartext = "Warning: Might sell worn tools."
198 else
199 weartext = "Worn tools are bought, too."
202 if weartext ~= nil then
203 formspec = formspec .."textarea[2.3,2.6;3,1;;"..minetest.formspec_escape(weartext)..";]"
208 meta:set_string("formspec", formspec)
211 easyvend.machine_disable = function(pos, node, playername)
212 if node.name == "easyvend:vendor_on" then
213 easyvend.sound_disable(pos)
214 minetest.swap_node(pos, {name="easyvend:vendor", param2 = node.param2})
215 return true
216 elseif node.name == "easyvend:depositor_on" then
217 easyvend.sound_disable(pos)
218 minetest.swap_node(pos, {name="easyvend:depositor", param2 = node.param2})
219 return true
220 else
221 if playername ~= nil then
222 easyvend.sound_error(playername)
224 return false
228 easyvend.machine_enable = function(pos, node)
229 if node.name == "easyvend:vendor" then
230 easyvend.sound_setup(pos)
231 minetest.swap_node(pos, {name="easyvend:vendor_on", param2 = node.param2})
232 return true
233 elseif node.name == "easyvend:depositor" then
234 easyvend.sound_setup(pos)
235 minetest.swap_node(pos, {name="easyvend:depositor_on", param2 = node.param2})
236 return true
237 else
238 return false
242 easyvend.machine_check = function(pos, node)
243 local active = true
244 local status = "Ready."
246 local meta = minetest.get_meta(pos)
248 local machine_owner = meta:get_string("owner")
249 local number = meta:get_int("number")
250 local cost = meta:get_int("cost")
251 local itemname = meta:get_string("itemname")
252 local check_wear = meta:get_int("wear") == 0
253 local inv = meta:get_inventory()
254 local itemstack = inv:get_stack("item",1)
255 local buysell = easyvend.buysell(node.name)
257 local chest_pos_remove, chest_error_remove, chest_pos_add, chest_error_add
258 if buysell == "sell" then
259 chest_pos_remove, chest_error_remove = easyvend.find_connected_chest(machine_owner, pos, itemname, check_wear, number, true)
260 chest_pos_add, chest_error_add = easyvend.find_connected_chest(machine_owner, pos, easyvend.currency, check_wear, cost, false)
261 else
262 chest_pos_remove, chest_error_remove = easyvend.find_connected_chest(machine_owner, pos, easyvend.currency, check_wear, cost, true)
263 chest_pos_add, chest_error_add = easyvend.find_connected_chest(machine_owner, pos, itemname, check_wear, number, false)
265 if chest_pos_remove and chest_pos_add then
266 local rchest, rchestdef, rchest_meta, rchest_inv
267 rchest = minetest.get_node(chest_pos_remove)
268 rchestdef = registered_chests[rchest.name]
269 rchest_meta = minetest.get_meta(chest_pos_remove)
270 rchest_inv = rchest_meta:get_inventory()
272 local checkstack, checkitem
273 if buysell == "buy" then
274 checkitem = easyvend.currency
275 else
276 checkitem = itemname
278 local stock = 0
279 -- Count stock
280 -- FIXME: Ignore tools with bad wear level
281 for i=1,rchest_inv:get_size(rchestdef.inv_list) do
282 checkstack = rchest_inv:get_stack(rchestdef.inv_list, i)
283 if checkstack:get_name() == checkitem then
284 stock = stock + checkstack:get_count()
287 meta:set_int("stock", stock)
289 if not itemstack:is_empty() then
290 local number_stack_max = itemstack:get_stack_max()
291 local maxnumber = number_stack_max * slots_max
292 if not(number >= 1 and number <= maxnumber and cost >= 1 and cost <= maxcost) then
293 active = false
294 if buysell == "sell" then
295 status = "Invalid item count or price."
296 else
297 status = "Invalid item count or payment."
300 else
301 active = false
302 status = "Awaiting configuration by owner."
304 else
305 active = false
306 meta:set_int("stock", 0)
307 if chest_error_remove == "no_chest" and chest_error_add == "no_chest" then
308 status = "No storage; machine needs to be connected with a locked chest."
309 elseif chest_error_remove == "not_owned" or chest_error_add == "not_owned" then
310 status = "Storage can’t be accessed because it is owned by a different person!"
311 elseif chest_error_remove == "no_stock" then
312 if buysell == "sell" then
313 status = "The vending machine has insufficient materials!"
314 else
315 status = "The depositing machine is out of money!"
317 elseif chest_error_add == "no_space" then
318 status = "No room in the machine’s storage!"
319 else
320 status = "Unknown error!"
323 if meta:get_int("configmode") == 1 then
324 active = false
325 status = "Awaiting configuration by owner."
328 if itemname == easyvend.currency and number == cost and active then
329 local jt = meta:get_int("joketimer")
330 if jt > 0 then
331 jt = jt - 1
333 if jt == 0 then
334 if buysell == "sell" then
335 meta:set_string("message", "Item bought.")
336 else
337 meta:set_string("message", "Item sold.")
339 jt = -1
341 meta:set_int("joketimer", jt)
343 meta:set_string("status", status)
345 meta:set_string("infotext", easyvend.make_infotext(node.name, machine_owner, cost, number, itemname))
346 itemname=itemstack:get_name()
347 meta:set_string("itemname", itemname)
349 if minetest.get_modpath("awards") and buysell == "sell" then
350 if minetest.get_player_by_name(machine_owner) then
351 local earnings = meta:get_int("earnings")
352 if earnings >= 1 then
353 awards.unlock(machine_owner, "easyvend_seller")
355 if earnings >= easyvend.powerseller then
356 awards.unlock(machine_owner, "easyvend_powerseller")
361 local change
362 if node.name == "easyvend:vendor" or node.name == "easyvend:depositor" then
363 if active then change = easyvend.machine_enable(pos, node) end
364 elseif node.name == "easyvend:vendor_on" or node.name == "easyvend:depositor_on" then
365 if not active then change = easyvend.machine_disable(pos, node) end
367 easyvend.set_formspec(pos)
368 return change
371 easyvend.on_receive_fields_config = function(pos, formname, fields, sender)
372 local node = minetest.get_node(pos)
373 local meta = minetest.get_meta(pos)
374 local inv_self = meta:get_inventory()
375 local itemstack = inv_self:get_stack("item",1)
376 local buysell = easyvend.buysell(node.name)
378 if fields.config then
379 meta:set_int("configmode", 1)
380 local was_active = easyvend.is_active(node.name)
381 if was_active then
382 meta:set_string("message", "Configuration mode activated; machine disabled.")
383 else
384 meta:set_string("message", "Configuration mode activated.")
386 easyvend.machine_check(pos, node)
387 return
390 if not fields.save then
391 return
394 local number = fields.number
395 local cost = fields.cost
397 --[[ Convenience function:
398 When appending “s” or “S” to the number, it is multiplied
399 by the maximum stack size.
400 TODO: Expose this in user documentation ]]
401 local number_stack_max = itemstack:get_stack_max()
402 local ss = string.sub(number, #number, #number)
403 if ss == "s" or ss == "S" then
404 local n = tonumber(string.sub(number, 1, #number-1))
405 if string.len(number) == 1 then n = 1 end
406 if n ~= nil then
407 number = n * number_stack_max
410 ss = string.sub(cost, #cost, #cost)
411 if ss == "s" or ss == "S" then
412 local n = tonumber(string.sub(cost, 1, #cost-1))
413 if string.len(cost) == 1 then n = 1 end
414 if n ~= nil then
415 cost = n * cost_stack_max
418 number = tonumber(number)
419 cost = tonumber(cost)
421 local itemname=""
423 local oldnumber = meta:get_int("number")
424 local oldcost = meta:get_int("cost")
425 local maxnumber = number_stack_max * slots_max
427 if ( itemstack == nil or itemstack:is_empty() ) then
428 meta:set_string("status", "Awaiting configuration by owner.")
429 meta:set_string("message", "No item specified.")
430 easyvend.sound_error(sender:get_player_name())
431 easyvend.set_formspec(pos, sender)
432 return
433 elseif ( not itemstack:is_known() ) then
434 meta:set_string("status", "Awaiting configuration by owner.")
435 meta:set_string("message", "Unknown item specified.")
436 easyvend.sound_error(sender:get_player_name())
437 easyvend.set_formspec(pos, sender)
438 return
439 elseif ( number == nil or number < 1 or number > maxnumber ) then
440 if maxnumber > 1 then
441 meta:set_string("message", string.format("Invalid item count; must be between 1 and %d!", maxnumber))
442 else
443 meta:set_string("message", "Invalid item count; must be exactly 1!")
445 meta:set_int("number", oldnumber)
446 easyvend.sound_error(sender:get_player_name())
447 easyvend.set_formspec(pos, sender)
448 return
449 elseif ( cost == nil or cost < 1 or cost > maxcost ) then
450 if maxcost > 1 then
451 meta:set_string("message", string.format("Invalid cost; must be between 1 and %d!", maxcost))
452 else
453 meta:set_string("message", "Invalid cost; must be exactly 1!")
455 meta:set_int("cost", oldcost)
456 easyvend.sound_error(sender:get_player_name())
457 easyvend.set_formspec(pos, sender)
458 return
460 meta:set_int("number", number)
461 meta:set_int("cost", cost)
462 itemname=itemstack:get_name()
463 meta:set_string("itemname", itemname)
464 meta:set_int("configmode", 0)
466 if itemname == easyvend.currency and number == cost and cost <= cost_stack_max then
467 meta:set_string("message", "Configuration successful. I am feeling funny.")
468 meta:set_int("joketimer", joketimer_start)
469 meta:set_int("joke_id", easyvend.assign_joke(buysell))
470 else
471 meta:set_string("message", "Configuration successful.")
474 local change = easyvend.machine_check(pos, node)
476 if not change then
477 if (node.name == "easyvend:vendor_on" or node.name == "easyvend:depositor_on") then
478 easyvend.sound_setup(pos)
479 else
480 easyvend.sound_disable(pos)
485 easyvend.make_infotext = function(nodename, owner, cost, number, itemstring)
486 local d = ""
487 if itemstring == nil or itemstring == "" or number == 0 or cost == 0 then
488 if easyvend.buysell(nodename) == "sell" then
489 d = string.format("Inactive vending machine (owned by %s)", owner)
490 else
491 d = string.format("Inactive depositing machine (owned by %s)", owner)
493 return d
495 local iname
496 if minetest.registered_items[itemstring] then
497 iname = minetest.registered_items[itemstring].description
498 else
499 iname = string.format("Unknown Item (%s)", itemstring)
501 if iname == nil then iname = itemstring end
502 local printitem, printcost
503 if number == 1 then
504 printitem = iname
505 else
506 printitem = string.format("%d×%s", number, iname)
508 if cost == 1 then
509 printcost = easyvend.currency_desc
510 else
511 printcost = string.format("%d×%s", cost, easyvend.currency_desc)
513 if nodename == "easyvend:vendor_on" then
514 d = string.format("Vending machine (owned by %s)\nSelling: %s\nPrice: %s", owner, printitem, printcost)
515 elseif nodename == "easyvend:vendor" then
516 d = string.format("Inactive vending machine (owned by %s)\nSelling: %s\nPrice: %s", owner, printitem, printcost)
517 elseif nodename == "easyvend:depositor_on" then
518 d = string.format("Depositing machine (owned by %s)\nBuying: %s\nPayment: %s", owner, printitem, printcost)
519 elseif nodename == "easyvend:depositor" then
520 d = string.format("Inactive depositing machine (owned by %s)\nBuying: %s\nPayment: %s", owner, printitem, printcost)
522 return d
525 if minetest.get_modpath("awards") then
526 awards.register_achievement("easyvend_seller",{
527 title = "First Sale",
528 description = "Sell something with a vending machine.",
529 icon = "easyvend_vendor_front_on.png^awards_level1.png",
531 local desc_powerseller
532 if easyvend.currency == "default:gold_ingot" then
533 desc_powerseller = string.format("Earn %d gold ingots by selling goods with a single vending machine.", easyvend.powerseller)
534 else
535 desc_powerseller = string.format("Earn %d currency items by selling goods with a single vending machine.", easyvend.powerseller)
537 awards.register_achievement("easyvend_powerseller",{
538 title = "Power Seller",
539 description = desc_powerseller,
540 icon = "easyvend_vendor_front_on.png^awards_level2.png",
544 easyvend.check_earnings = function(buyername, nodemeta)
545 local owner = nodemeta:get_string("owner")
546 if buyername ~= owner then
547 local cost = nodemeta:get_int("cost")
548 local itemname = nodemeta:get_string("itemname")
549 -- First sell
550 if minetest.get_modpath("awards") and minetest.get_player_by_name(owner) ~= nil then
551 awards.unlock(owner, "easyvend_seller")
553 if itemname ~= easyvend.currency then
554 local newearnings = nodemeta:get_int("earnings") + cost
555 if newearnings >= easyvend.powerseller and minetest.get_modpath("awards") then
556 if minetest.get_player_by_name(owner) ~= nil then
557 awards.unlock(owner, "easyvend_powerseller")
560 nodemeta:set_int("earnings", newearnings)
565 easyvend.on_receive_fields_buysell = function(pos, formname, fields, sender)
566 local sendername = sender:get_player_name()
567 local meta = minetest.get_meta(pos)
569 if not fields.buysell then
570 return
573 local node = minetest.get_node(pos)
574 local number = meta:get_int("number")
575 local cost = meta:get_int("cost")
576 local itemname=meta:get_string("itemname")
577 local item=meta:get_inventory():get_stack("item", 1)
578 local check_wear = meta:get_int("wear") == 0 and minetest.registered_tools[itemname] ~= nil
579 local machine_owner = meta:get_string("owner")
581 local buysell = easyvend.buysell(node.name)
583 local number_stack_max = item:get_stack_max()
584 local maxnumber = number_stack_max * slots_max
586 if ( number == nil or number < 1 or number > maxnumber ) or
587 ( cost == nil or cost < 1 or cost > maxcost ) or
588 ( itemname == nil or itemname=="") then
589 meta:set_string("status", "Invalid item count or price!")
590 easyvend.machine_disable(pos, node, sendername)
591 return
594 local chest_pos_remove, chest_error_remove, chest_pos_add, chest_error_add
595 if buysell == "sell" then
596 chest_pos_remove, chest_error_remove = easyvend.find_connected_chest(machine_owner, pos, itemname, check_wear, number, true)
597 chest_pos_add, chest_error_add = easyvend.find_connected_chest(machine_owner, pos, easyvend.currency, check_wear, cost, false)
598 else
599 chest_pos_remove, chest_error_remove = easyvend.find_connected_chest(machine_owner, pos, easyvend.currency, check_wear, cost, true)
600 chest_pos_add, chest_error_add = easyvend.find_connected_chest(machine_owner, pos, itemname, check_wear, number, false)
603 if chest_pos_remove ~= nil and chest_pos_add ~= nil and sender and sender:is_player() then
604 local rchest = minetest.get_node(chest_pos_remove)
605 local rchestdef = registered_chests[rchest.name]
606 local rchest_meta = minetest.get_meta(chest_pos_remove)
607 local rchest_inv = rchest_meta:get_inventory()
608 local achest = minetest.get_node(chest_pos_add)
609 local achestdef = registered_chests[achest.name]
610 local achest_meta = minetest.get_meta(chest_pos_add)
611 local achest_inv = achest_meta:get_inventory()
613 local player_inv = sender:get_inventory()
615 local stack = {name=itemname, count=number, wear=0, metadata=""}
616 local price = {name=easyvend.currency, count=cost, wear=0, metadata=""}
617 local chest_has, player_has, chest_free, player_free, chest_out, player_out
618 local msg = ""
619 if buysell == "sell" then
620 chest_has, chest_out = easyvend.check_and_get_items(rchest_inv, rchestdef.inv_list, stack, check_wear)
621 player_has, player_out = easyvend.check_and_get_items(player_inv, "main", price, check_wear)
622 chest_free = achest_inv:room_for_item(achestdef.inv_list, price)
623 player_free = player_inv:room_for_item("main", stack)
624 if chest_has and player_has and chest_free and player_free then
625 if cost <= cost_stack_max and number <= number_stack_max then
626 easyvend.machine_enable(pos, node)
627 player_inv:remove_item("main", price)
628 if check_wear then
629 rchest_inv:set_stack(rchestdef.inv_list, chest_out[1].id, "")
630 player_inv:add_item("main", chest_out[1].item)
631 else
632 stack = rchest_inv:remove_item(rchestdef.inv_list, stack)
633 player_inv:add_item("main", stack)
635 achest_inv:add_item(achestdef.inv_list, price)
636 if itemname == easyvend.currency and number == cost and cost <= cost_stack_max then
637 meta:set_string("message", easyvend.get_joke(buysell, meta:get_int("joke_id")))
638 meta:set_int("joketimer", joketimer_start)
639 else
640 meta:set_string("message", "Item bought.")
642 easyvend.check_earnings(sendername, meta)
643 easyvend.sound_vend(pos)
644 easyvend.machine_check(pos, node)
645 else
646 -- Large item counts (multiple stacks)
647 local coststacks = math.modf(cost / cost_stack_max)
648 local costremainder = math.fmod(cost, cost_stack_max)
649 local numberstacks = math.modf(number / number_stack_max)
650 local numberremainder = math.fmod(number, number_stack_max)
651 local numberfree = numberstacks
652 local costfree = coststacks
653 if numberremainder > 0 then numberfree = numberfree + 1 end
654 if costremainder > 0 then costfree = costfree + 1 end
655 if not player_free and easyvend.free_slots(player_inv, "main") < numberfree then
656 if numberfree > 1 then
657 msg = string.format("No room in your inventory (%d empty slots required)!", numberfree)
658 else
659 msg = "No room in your inventory!"
661 meta:set_string("message", msg)
662 elseif not chest_free and easyvend.free_slots(achest_inv, achestdef.inv_list) < costfree then
663 meta:set_string("status", "No room in the machine’s storage!")
664 easyvend.machine_disable(pos, node, sendername)
665 else
666 -- Remember items for transfer
667 local cheststacks = {}
668 easyvend.machine_enable(pos, node)
669 for i=1, coststacks do
670 price.count = cost_stack_max
671 player_inv:remove_item("main", price)
673 if costremainder > 0 then
674 price.count = costremainder
675 player_inv:remove_item("main", price)
677 if check_wear then
678 for o=1,#chest_out do
679 rchest_inv:set_stack(rchestdef.inv_list, chest_out[o].id, "")
681 else
682 for i=1, numberstacks do
683 stack.count = number_stack_max
684 table.insert(cheststacks, rchest_inv:remove_item(rchestdef.inv_list, stack))
687 if numberremainder > 0 then
688 stack.count = numberremainder
689 table.insert(cheststacks, rchest_inv:remove_item(rchestdef.inv_list, stack))
691 for i=1, coststacks do
692 price.count = cost_stack_max
693 achest_inv:add_item(achestdef.inv_list, price)
695 if costremainder > 0 then
696 price.count = costremainder
697 achest_inv:add_item(achestdef.inv_list, price)
699 if check_wear then
700 for o=1,#chest_out do
701 player_inv:add_item("main", chest_out[o].item)
703 else
704 for i=1,#cheststacks do
705 player_inv:add_item("main", cheststacks[i])
708 meta:set_string("message", "Item bought.")
709 easyvend.check_earnings(sendername, meta)
710 easyvend.sound_vend(pos)
711 easyvend.machine_check(pos, node)
714 elseif chest_has and player_has then
715 if not player_free then
716 msg = "No room in your inventory!"
717 meta:set_string("message", msg)
718 easyvend.sound_error(sendername)
719 elseif not chest_free then
720 msg = "No room in the machine’s storage!"
721 meta:set_string("status", msg)
722 easyvend.machine_disable(pos, node, sendername)
724 else
725 if not chest_has then
726 msg = "The vending machine has insufficient materials!"
727 meta:set_string("status", msg)
728 easyvend.machine_disable(pos, node, sendername)
729 elseif not player_has then
730 msg = "You can’t afford this item!"
731 meta:set_string("message", msg)
732 easyvend.sound_error(sendername)
735 else
736 chest_has, chest_out = easyvend.check_and_get_items(rchest_inv, rchestdef.inv_list, price, check_wear)
737 player_has, player_out = easyvend.check_and_get_items(player_inv, "main", stack, check_wear)
738 chest_free = achest_inv:room_for_item(achestdef.inv_list, stack)
739 player_free = player_inv:room_for_item("main", price)
740 if chest_has and player_has and chest_free and player_free then
741 if cost <= cost_stack_max and number <= number_stack_max then
742 easyvend.machine_enable(pos, node)
743 if check_wear then
744 player_inv:set_stack("main", player_out[1].id, "")
745 achest_inv:add_item(achestdef.inv_list, player_out[1].item)
746 else
747 stack = player_inv:remove_item("main", stack)
748 achest_inv:add_item(achestdef.inv_list, stack)
750 rchest_inv:remove_item(rchestdef.inv_list, price)
751 player_inv:add_item("main", price)
752 meta:set_string("status", "Ready.")
753 if itemname == easyvend.currency and number == cost and cost <= cost_stack_max then
754 meta:set_string("message", easyvend.get_joke(buysell, meta:get_int("joke_id")))
755 meta:set_int("joketimer", joketimer_start)
756 else
757 meta:set_string("message", "Item sold.")
759 easyvend.sound_deposit(pos)
760 easyvend.machine_check(pos, node)
761 else
762 -- Large item counts (multiple stacks)
763 local coststacks = math.modf(cost / cost_stack_max)
764 local costremainder = math.fmod(cost, cost_stack_max)
765 local numberstacks = math.modf(number / number_stack_max)
766 local numberremainder = math.fmod(number, number_stack_max)
767 local numberfree = numberstacks
768 local costfree = coststacks
769 if numberremainder > 0 then numberfree = numberfree + 1 end
770 if costremainder > 0 then costfree = costfree + 1 end
771 if not player_free and easyvend.free_slots(player_inv, "main") < costfree then
772 if costfree > 1 then
773 msg = string.format("No room in your inventory (%d empty slots required)!", costfree)
774 else
775 msg = "No room in your inventory!"
777 meta:set_string("message", msg)
778 easyvend.sound_error(sendername)
779 elseif not chest_free and easyvend.free_slots(achest_inv, achestdef.inv_list) < numberfree then
780 meta:set_string("status", "No room in the machine’s storage!")
781 easyvend.machine_disable(pos, node, sendername)
782 else
783 easyvend.machine_enable(pos, node)
784 -- Remember removed items for transfer
785 local playerstacks = {}
786 for i=1, coststacks do
787 price.count = cost_stack_max
788 rchest_inv:remove_item(rchestdef.inv_list, price)
790 if costremainder > 0 then
791 price.count = costremainder
792 rchest_inv:remove_item(rchestdef.inv_list, price)
794 if check_wear then
795 for o=1,#player_out do
796 player_inv:set_stack("main", player_out[o].id, "")
798 else
799 for i=1, numberstacks do
800 stack.count = number_stack_max
801 table.insert(playerstacks, player_inv:remove_item("main", stack))
804 if numberremainder > 0 then
805 stack.count = numberremainder
806 table.insert(playerstacks, player_inv:remove_item("main", stack))
808 for i=1, coststacks do
809 price.count = cost_stack_max
810 player_inv:add_item("main", price)
812 if costremainder > 0 then
813 price.count = costremainder
814 player_inv:add_item("main", price)
816 if check_wear then
817 for o=1,#player_out do
818 achest_inv:add_item(achestdef.inv_list, player_out[o].item)
820 else
821 for i=1,#playerstacks do
822 achest_inv:add_item(achestdef.inv_list, playerstacks[i])
825 meta:set_string("message", "Item sold.")
826 easyvend.sound_deposit(pos)
827 easyvend.machine_check(pos, node)
830 elseif chest_has and player_has then
831 if not player_free then
832 msg = "No room in your inventory!"
833 meta:set_string("message", msg)
834 easyvend.sound_error(sendername)
835 elseif not chest_free then
836 msg = "No room in the machine’s storage!"
837 meta:set_string("status", msg)
838 easyvend.machine_disable(pos, node, sendername)
840 else
841 if not player_has then
842 msg = "You have insufficient materials!"
843 meta:set_string("message", msg)
844 easyvend.sound_error(sendername)
845 elseif not chest_has then
846 msg = "The depositing machine is out of money!"
847 meta:set_string("status", msg)
848 easyvend.machine_disable(pos, node, sendername)
852 else
853 local status
854 meta:set_int("stock", 0)
855 if chest_error_remove == "no_chest" and chest_error_add == "no_chest" then
856 status = "No storage; machine needs to be connected with a locked chest."
857 elseif chest_error_remove == "not_owned" or chest_error_add == "not_owned" then
858 status = "Storage can’t be accessed because it is owned by a different person!"
859 elseif chest_error_remove == "no_stock" then
860 if buysell == "sell" then
861 status = "The vending machine has insufficient materials!"
862 else
863 status = "The depositing machine is out of money!"
865 elseif chest_error_add == "no_space" then
866 status = "No room in the machine’s storage!"
867 else
868 status = "Unknown error!"
870 meta:set_string("status", status)
871 easyvend.sound_error(sendername)
874 easyvend.set_formspec(pos, sender)
878 easyvend.after_place_node = function(pos, placer)
879 local node = minetest.get_node(pos)
880 local meta = minetest.get_meta(pos)
881 local inv = meta:get_inventory()
882 local player_name = placer:get_player_name()
883 inv:set_size("item", 1)
884 inv:set_size("gold", 1)
886 inv:set_stack( "gold", 1, easyvend.currency )
888 local d = ""
889 if node.name == "easyvend:vendor" then
890 d = string.format("Inactive vending machine (owned by %s)", player_name)
891 meta:set_int("wear", 1)
892 -- Total number of currency items earned for the machine's life time (excluding currency-currency trading)
893 meta:set_int("earnings", 0)
894 elseif node.name == "easyvend:depositor" then
895 d = string.format("Inactive depositing machine (owned by %s)", player_name)
896 meta:set_int("wear", 0)
898 meta:set_string("infotext", d)
899 meta:set_string("status", "Awaiting configuration by owner.")
900 meta:set_string("message", "Welcome! Please prepare the machine.")
901 meta:set_int("number", 1)
902 meta:set_int("cost", 1)
903 meta:set_int("stock", -1)
904 meta:set_int("configmode", 1)
905 meta:set_int("joketimer", -1)
906 meta:set_int("joke_id", 1)
907 meta:set_string("itemname", "")
909 meta:set_string("owner", player_name or "")
911 easyvend.set_formspec(pos, placer)
914 easyvend.can_dig = function(pos, player)
915 local meta = minetest.get_meta(pos)
916 local name = player:get_player_name()
917 local owner = meta:get_string("owner")
918 -- Owner can always dig shop
919 if owner == name then
920 return true
922 local chest_pos = easyvend.find_connected_chest(owner, pos)
923 local chest, meta_chest
924 if chest_pos then
925 chest = minetest.get_node(chest_pos)
926 meta_chest = minetest.get_meta(chest_pos)
927 else
928 return true --if no chest, enyone can dig this shop
930 if registered_chests[chest.name] then
931 if player and player:is_player() then
932 local owner_chest = meta_chest:get_string(registered_chests[chest.name].meta_owner)
933 if name == owner_chest then
934 return true --chest owner can also dig shop
937 return false
938 else
939 return true --if no chest, enyone can dig this shop
943 easyvend.on_receive_fields = function(pos, formname, fields, sender)
944 local meta = minetest.get_meta(pos)
945 local node = minetest.get_node(pos)
946 local owner = meta:get_string("owner")
947 local sendername = sender:get_player_name()
949 if fields.doc then
950 if minetest.get_modpath("doc") and minetest.get_modpath("doc_items") then
951 if easyvend.buysell(node.name) == "buy" then
952 doc.show_entry(sendername, "nodes", "easyvend:depositor", true)
953 else
954 doc.show_entry(sendername, "nodes", "easyvend:vendor", true)
957 elseif fields.config or fields.save or fields.usermode then
958 if sender:get_player_name() == owner then
959 easyvend.on_receive_fields_config(pos, formname, fields, sender)
960 else
961 meta:set_string("message", "Only the owner may change the configuration.")
962 easyvend.sound_error(sendername)
963 easyvend.set_formspec(pos, sender)
964 return
966 elseif fields.wear ~= nil then
967 if sender:get_player_name() == owner then
968 if fields.wear == "true" then
969 if easyvend.buysell(node.name) == "buy" then
970 meta:set_string("message", "Used tools are now accepted.")
971 else
972 meta:set_string("message", "Used tools are now for sale.")
974 meta:set_int("wear", 1)
975 elseif fields.wear == "false" then
976 if easyvend.buysell(node.name) == "buy" then
977 meta:set_string("message", "Used tools are now rejected.")
978 else
979 meta:set_string("message", "Used tools won’t be sold anymore.")
981 meta:set_int("wear", 0)
983 easyvend.set_formspec(pos, sender)
984 return
985 else
986 meta:set_string("message", "Only the owner may change the configuration.")
987 easyvend.sound_error(sendername)
988 easyvend.set_formspec(pos, sender)
989 return
991 elseif fields.buysell then
992 easyvend.on_receive_fields_buysell(pos, formname, fields, sender)
996 -- Jokes: Appear when machine exchanges currency for currency at equal rate
998 -- Vendor
999 local jokes_vendor = {
1000 "Thank you. You have made a vending machine very happy.",
1001 "Humans have a strange sense of humor.",
1002 "Let’s get this over with …",
1003 "Item “bought”.",
1004 "Tit for tat.",
1005 "Do you realize what you’ve just bought?",
1007 -- Depositor
1008 local jokes_depositor = {
1009 "Thank you, the money started to smell inside.",
1010 "Money doesn’t grow on trees, you know?",
1011 "Sanity sold.",
1012 "Well, that was an awkward exchange.",
1013 "Are you having fun?",
1014 "Is this really trading?",
1017 easyvend.assign_joke = function(buysell)
1018 local jokes
1019 if buysell == "sell" then
1020 jokes = jokes_vendor
1021 elseif buysell == "buy" then
1022 jokes = jokes_depositor
1024 local r = math.random(1,#jokes)
1025 return r
1028 easyvend.get_joke = function(buysell, id)
1029 local joke
1030 if buysell == nil or id == nil then
1031 -- Fallback message (should never happen)
1032 return "Items exchanged."
1034 if buysell == "sell" then
1035 joke = jokes_vendor[id]
1036 if joke == nil then joke = jokes_vendor[1] end
1037 elseif buysell == "buy" then
1038 joke = jokes_depositor[id]
1039 if joke == nil then joke = jokes_depositor[1] end
1041 return joke
1044 easyvend.sound_error = function(playername)
1045 minetest.sound_play("easyvend_error", {to_player = playername, gain = 0.25})
1048 easyvend.sound_setup = function(pos)
1049 minetest.sound_play("easyvend_activate", {pos = pos, gain = 0.5, max_hear_distance = 12,})
1052 easyvend.sound_disable = function(pos)
1053 minetest.sound_play("easyvend_disable", {pos = pos, gain = 0.9, max_hear_distance = 12,})
1056 easyvend.sound_vend = function(pos)
1057 minetest.sound_play("easyvend_vend", {pos = pos, gain = 0.4, max_hear_distance = 5,})
1060 easyvend.sound_deposit = function(pos)
1061 minetest.sound_play("easyvend_deposit", {pos = pos, gain = 0.4, max_hear_distance = 5,})
1064 --[[ Tower building ]]
1066 easyvend.is_traversable = function(pos)
1067 local node = minetest.get_node_or_nil(pos)
1068 if (node == nil) then
1069 return false
1071 return traversable_node_types[node.name] == true
1074 easyvend.neighboring_nodes = function(pos)
1075 local check = {
1076 {x=pos.x, y=pos.y-1, z=pos.z},
1077 {x=pos.x, y=pos.y+1, z=pos.z},
1079 local trav = {}
1080 for i=1,#check do
1081 if easyvend.is_traversable(check[i]) then
1082 table.insert(trav, check[i])
1085 return trav
1088 easyvend.find_connected_chest = function(owner, pos, nodename, check_wear, amount, removing)
1089 local nodes = easyvend.neighboring_nodes(pos)
1091 if (#nodes < 1 or #nodes > 2) then
1092 return nil, "no_chest"
1095 -- Find the stack direction
1096 local first = nil
1097 local second = nil
1098 for i=1,#nodes do
1099 if ( first == nil ) then
1100 first = nodes[i]
1101 else
1102 second = nodes[i]
1106 local chest_pos, chest_internal
1108 if (first ~= nil and second ~= nil) then
1109 local dy = (first.y - second.y)/2
1110 chest_pos, chest_internal = easyvend.find_chest(owner, pos, dy, nodename, check_wear, amount, removing)
1111 if ( chest_pos == nil ) then
1112 chest_pos, chest_internal = easyvend.find_chest(owner, pos, -dy, nodename, check_wear, amount, removing, chest_internal)
1114 else
1115 local dy = first.y - pos.y
1116 chest_pos, chest_internal = easyvend.find_chest(owner, pos, dy, nodename, check_wear, amount, removing)
1119 if chest_internal.chests == 0 then
1120 return nil, "no_chest"
1121 elseif chest_internal.chests == chest_internal.other_chests then
1122 return nil, "not_owned"
1123 elseif removing and chest_internal.stock < 1 then
1124 return nil, "no_stock"
1125 elseif not removing and chest_internal.space < 1 then
1126 return nil, "no_space"
1127 elseif chest_pos ~= nil then
1128 return chest_pos
1129 else
1130 return nil, "unknown"
1134 easyvend.find_chest = function(owner, pos, dy, itemname, check_wear, amount, removing, internal)
1135 pos = {x=pos.x, y=pos.y + dy, z=pos.z}
1137 if internal == nil then
1138 internal = {}
1139 internal.chests = 0
1140 internal.other_chests = 0
1141 internal.stock = 0
1142 internal.space = 0
1145 local node = minetest.get_node_or_nil(pos)
1146 if ( node == nil ) then
1147 return nil, internal
1149 local chestdef = registered_chests[node.name]
1150 if (chestdef ~= nil) then
1151 internal.chests = internal.chests + 1
1152 local meta = minetest.get_meta(pos)
1153 if (owner ~= meta:get_string(chestdef.meta_owner)) then
1154 internal.other_chests = internal.other_chests + 1
1155 return nil, internal
1157 local inv = meta:get_inventory()
1158 if (inv ~= nil) then
1159 if (itemname ~= nil and minetest.registered_items[itemname] and amount ~= nil and removing ~= nil and check_wear ~= nil) then
1160 local chest_has, chest_free
1161 local stack = {name=itemname, count=amount, wear=0, metadata=""}
1162 local stack_max = minetest.registered_items[itemname].stack_max
1164 local stacks = math.modf(amount / stack_max)
1165 local stacksremainder = math.fmod(amount, stack_max)
1166 local free = stacks
1167 if stacksremainder > 0 then free = free + 1 end
1169 chest_has = easyvend.check_and_get_items(inv, chestdef.inv_list, stack, check_wear)
1170 if chest_has then
1171 internal.stock = internal.stock + 1
1173 chest_free = inv:room_for_item(chestdef.inv_list, stack) and easyvend.free_slots(inv, chestdef.inv_list) >= free
1174 if chest_free then
1175 internal.space = internal.space + 1
1178 if (removing and internal.stock == 0) or (not removing and internal.space == 0) then
1179 return easyvend.find_chest(owner, pos, dy, itemname, check_wear, amount, removing, internal)
1180 else
1181 return pos, internal
1183 else
1184 return nil, internal
1186 else
1187 return nil, internal
1189 elseif (node.name ~= "easyvend:vendor" and node.name~="easyvend:depositor" and node.name~="easyvend:vendor_on" and node.name~="easyvend:depositor_on") then
1190 return nil, internal
1193 return easyvend.find_chest(owner, pos, dy, itemname, check_wear, amount, removing, internal)
1196 -- Pseudo-inventory handling
1197 easyvend.allow_metadata_inventory_put = function(pos, listname, index, stack, player)
1198 if listname=="item" then
1199 local meta = minetest.get_meta(pos);
1200 local owner = meta:get_string("owner")
1201 local name = player:get_player_name()
1202 if name == owner then
1203 local inv = meta:get_inventory()
1204 if stack==nil then
1205 inv:set_stack( "item", 1, nil )
1206 else
1207 inv:set_stack( "item", 1, stack:get_name() )
1208 meta:set_string("itemname", stack:get_name())
1209 easyvend.set_formspec(pos, player)
1213 return 0
1216 easyvend.allow_metadata_inventory_take = function(pos, listname, index, stack, player)
1217 return 0
1220 easyvend.allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
1221 return 0
1224 minetest.register_abm({
1225 nodenames = {"easyvend:vendor", "easyvend:vendor_on", "easyvend:depositor", "easyvend:depositor_on"},
1226 interval = 5,
1227 chance = 1,
1228 catch_up = false,
1229 action = function(pos, node, active_object_count, active_object_count_wider)
1230 easyvend.machine_check(pos, node)
1234 -- Legacy support for vendor mod:
1235 -- Transform the world and items to use the easyvend nodes/items
1237 -- For safety reasons, only do this when player requested so
1238 if minetest.setting_getbool("easyvend_convert_vendor") == true then
1239 -- Replace vendor nodes
1240 minetest.register_lbm({
1241 name = "easyvend:replace_vendor",
1242 nodenames = { "vendor:vendor", "vendor:depositor" },
1243 run_at_every_load = true,
1244 action = function(pos, node)
1245 -- Replace node
1246 local newnodename
1247 if node.name == "vendor:vendor" then
1248 newnodename = "easyvend:vendor"
1249 elseif node.name == "vendor:depositor" then
1250 newnodename = "easyvend:depositor"
1252 -- Remove axis rotation; only allow 4 facedirs
1253 local p2 = math.fmod(node.param2, 4)
1254 minetest.swap_node(pos, { name = newnodename, param2 = p2 })
1256 -- Initialize metadata
1257 local meta = minetest.get_meta(pos)
1258 if node.name == "vendor:vendor" then
1259 meta:set_int("earnings", 0)
1261 meta:set_int("stock", -1)
1262 meta:set_int("joketimer", -1)
1263 meta:set_int("joke_id", 1)
1264 local inv = meta:get_inventory()
1265 inv:set_size("item", 1)
1266 inv:set_size("gold", 1)
1267 inv:set_stack("gold", 1, easyvend.currency)
1269 -- In vendor, all machines accepted worn tools
1270 meta:set_int("wear", 1)
1272 -- Set item
1273 local itemname = meta:get_string("itemname")
1274 if itemname == "" or itemname == nil then
1275 itemname = meta:get_string("itemtype")
1277 if itemname ~= "" and itemname ~= nil then
1278 inv:set_stack("item", 1, itemname)
1279 meta:set_string("itemname", itemname)
1282 -- Check for valid item, item count and price
1283 local configmode = 1
1284 if itemname ~= "" and itemname ~= nil then
1285 local itemstack = inv:get_stack("item", 1)
1286 local number_stack_max = itemstack:get_stack_max()
1287 local maxnumber = number_stack_max * slots_max
1288 local cost = meta:get_int("cost")
1289 local number = meta:get_int("number")
1290 if number >= 1 and number <= maxnumber and cost >= 1 and cost <= maxcost then
1291 -- Everything's OK, get out of config mode!
1292 configmode = 0
1296 -- Final initialization stuff
1297 meta:set_int("configmode", configmode)
1299 local owner = meta:get_string("owner")
1300 if easyvend.buysell(newnodename) == "sell" then
1301 meta:set_string("infotext", string.format("Vending machine (owned by %s)", owner))
1302 else
1303 meta:set_string("infotext", string.format("Depositing machine (owned by %s)", owner))
1307 meta:set_string("status", "Initializing …")
1308 meta:set_string("message", "Upgrade successful.")
1309 easyvend.machine_check(pos, node)
1310 end,