Fix variable warnings
[minetest_easyvend.git] / easyvend.lua
blobb01acb299d7baaf8c9496345cd225b552da74685
1 ---
2 --vendor
3 --Copyright (C) 2012 Bad_Command
4 --Rewrited by Andrej
5 --
6 --This library is free software; you can redistribute it and/or
7 --modify it under the terms of the GNU Lesser General Public
8 --License as published by the Free Software Foundation; either
9 --version 2.1 of the License, or (at your option) any later version.
11 --This program is distributed in the hope that it will be useful,
12 --but WITHOUT ANY WARRANTY; without even the implied warranty of
13 --MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 --GNU General Public License for more details.
16 --You should have received a copy of the GNU Lesser General Public
17 --License along with this library; if not, write to the Free Software
18 --Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 ---
21 -- TODO: Improve mod compability
22 local slots_max = 31
24 local registered_chests = {}
25 local cost_stack_max = minetest.registered_items[easyvend.currency].stack_max
26 local maxcost = cost_stack_max * slots_max
28 local joketimer_start = 3
30 -- Allow for other mods to register custom chests
31 easyvend.register_chest = function(node_name, inv_list, meta_owner)
32 registered_chests[node_name] = { inv_list = inv_list, meta_owner = meta_owner }
33 end
35 -- Partly a wrapper around contains_item, but does special treatment if the item
36 -- is a tool. Basically checks whether the items exist in the supplied inventory
37 -- list. If check_wear is true, only counts items without wear.
38 easyvend.check_and_get_items = function(inventory, listname, itemtable, check_wear)
39 local itemstring = itemtable.name
40 local minimum = itemtable.count
41 if check_wear == nil then check_wear = false end
42 local get_items = {}
43 -- Tool workaround
44 if minetest.registered_tools[itemstring] ~= nil then
45 local count = 0
46 for i=1,inventory:get_size(listname) do
47 local stack = inventory:get_stack(listname, i)
48 if stack:get_name() == itemstring then
49 if not check_wear or stack:get_wear() == 0 then
50 count = count + 1
51 table.insert(get_items, {id=i, item=stack})
52 if count >= minimum then
53 return true, get_items
54 end
55 end
56 end
57 end
58 return false
59 else
60 -- Normal Minetest check
61 return inventory:contains_item(listname, ItemStack(itemtable))
62 end
63 end
66 if minetest.get_modpath("default") ~= nil then
67 easyvend.register_chest("default:chest_locked", "main", "owner")
68 end
70 easyvend.free_slots= function(inv, listname)
71 local size = inv:get_size(listname)
72 local free = 0
73 for i=1,size do
74 local stack = inv:get_stack(listname, i)
75 if stack:is_empty() then
76 free = free + 1
77 end
78 end
79 return free
80 end
82 easyvend.buysell = function(nodename)
83 local buysell = nil
84 if ( nodename == "easyvend:depositor" or nodename == "easyvend:depositor_on" ) then
85 buysell = "buy"
86 elseif ( nodename == "easyvend:vendor" or nodename == "easyvend:vendor_on" ) then
87 buysell = "sell"
88 end
89 return buysell
90 end
92 easyvend.is_active = function(nodename)
93 if ( nodename == "easyvend:depositor_on" or nodename == "easyvend:vendor_on" ) then
94 return true
95 elseif ( nodename == "easyvend:depositor" or nodename == "easyvend:vendor" ) then
96 return false
97 else
98 return nil
99 end
102 easyvend.set_formspec = function(pos, player)
103 local meta = minetest.get_meta(pos)
104 local node = minetest.get_node(pos)
106 local description = minetest.registered_nodes[node.name].description;
107 local number = meta:get_int("number")
108 local cost = meta:get_int("cost")
109 local itemname = meta:get_string("itemname")
110 local bg = ""
111 local configmode = meta:get_int("configmode") == 1
112 if minetest.get_modpath("default") then
113 bg = default.gui_bg .. default.gui_bg_img .. default.gui_slots
116 local numbertext, costtext, buysellbuttontext
117 local itemcounttooltip = "Item count (append “s” to multiply with maximum stack size)"
118 local buysell = easyvend.buysell(node.name)
119 if buysell == "sell" then
120 numbertext = "Offered item"
121 costtext = "Price"
122 buysellbuttontext = "Buy"
123 elseif buysell == "buy" then
124 numbertext = "Requested item"
125 costtext = "Payment"
126 buysellbuttontext = "Sell"
127 else
128 return
130 local status = meta:get_string("status")
131 if status == "" then status = "Unknown." end
132 local message = meta:get_string("message")
133 if message == "" then message = "No message." end
134 local status_image
135 if node.name == "easyvend:vendor_on" or node.name == "easyvend:depositor_on" then
136 status_image = "easyvend_status_on.png"
137 else
138 status_image = "easyvend_status_off.png"
141 -- TODO: Expose number of items in stock
143 local formspec = "size[8,7.3;]"
144 .. bg
145 .."label[3,-0.2;" .. minetest.formspec_escape(description) .. "]"
147 .."image[7.5,0.2;0.5,1;" .. status_image .. "]"
148 .."textarea[2.8,0.2;5.1,2;;Status: " .. minetest.formspec_escape(status) .. ";]"
149 .."textarea[2.8,1.3;5.6,2;;Message: " .. minetest.formspec_escape(message) .. ";]"
151 .."label[0,-0.15;"..numbertext.."]"
152 .."label[0,1.2;"..costtext.."]"
153 .."list[current_player;main;0,3.5;8,4;]"
155 if configmode then
156 local wear = "false"
157 if meta:get_int("wear") == 1 then wear = "true" end
158 formspec = formspec
159 .."item_image_button[0,1.65;1,1;"..easyvend.currency..";easyvend.currency_image;]"
160 .."list[current_name;item;0,0.35;1,1;]"
161 .."listring[current_player;main]"
162 .."listring[current_name;item]"
163 .."field[1.3,0.65;1.5,1;number;;" .. number .. "]"
164 .."tooltip[number;"..itemcounttooltip.."]"
165 .."field[1.3,1.95;1.5,1;cost;;" .. cost .. "]"
166 .."tooltip[cost;"..itemcounttooltip.."]"
167 .."button[6,2.8;2,0.5;save;Confirm]"
168 .."tooltip[save;Confirm configuration and activate machine (only for owner)]"
169 local weartext, weartooltip
170 if buysell == "buy" then
171 weartext = "Accept worn tools"
172 weartooltip = "If disabled, only tools in perfect condition will be bought from sellers (only settable by owner)"
173 else
174 weartext = "Sell worn tools"
175 weartooltip = "If disabled, only tools in perfect condition will be sold (only settable by owner)"
177 if minetest.registered_tools[itemname] ~= nil then
178 formspec = formspec .."checkbox[2,2.4;wear;"..minetest.formspec_escape(weartext)..";"..wear.."]"
179 .."tooltip[wear;"..minetest.formspec_escape(weartooltip).."]"
181 else
182 formspec = formspec
183 .."item_image_button[0,1.65;1,1;"..easyvend.currency..";easyvend.currency_image;]"
184 .."item_image_button[0,0.35;1,1;"..itemname..";item_image;]"
185 .."label[1,1.85;×" .. cost .. "]"
186 .."label[1,0.55;×" .. number .. "]"
187 .."button[6,2.8;2,0.5;config;Configure]"
188 if buysell == "sell" then
189 formspec = formspec .. "tooltip[config;Configure offered items and price (only for owner)]"
190 else
191 formspec = formspec .. "tooltip[config;Configure requested items and payment (only for owner)]"
193 formspec = formspec .."button[0,2.8;2,0.5;buysell;"..buysellbuttontext.."]"
194 if minetest.registered_tools[itemname] ~= nil then
195 local weartext
196 if meta:get_int("wear") == 0 then
197 if buysell == "buy" then
198 weartext = "Only intact tools are bought."
199 else
200 weartext = "Only intact tools are sold."
202 elseif buysell == "sell" then
203 weartext = "Warning: Might sell worn tools."
205 if weartext ~= nil then
206 formspec = formspec .."textarea[2.3,2.6;3,1;;"..minetest.formspec_escape(weartext)..";]"
211 meta:set_string("formspec", formspec)
214 easyvend.machine_disable = function(pos, node, playername)
215 if node.name == "easyvend:vendor_on" then
216 easyvend.sound_disable(pos)
217 minetest.swap_node(pos, {name="easyvend:vendor", param2 = node.param2})
218 return true
219 elseif node.name == "easyvend:depositor_on" then
220 easyvend.sound_disable(pos)
221 minetest.swap_node(pos, {name="easyvend:depositor", param2 = node.param2})
222 return true
223 else
224 if playername ~= nil then
225 easyvend.sound_error(playername)
227 return false
231 easyvend.machine_enable = function(pos, node)
232 if node.name == "easyvend:vendor" then
233 easyvend.sound_setup(pos)
234 minetest.swap_node(pos, {name="easyvend:vendor_on", param2 = node.param2})
235 return true
236 elseif node.name == "easyvend:depositor" then
237 easyvend.sound_setup(pos)
238 minetest.swap_node(pos, {name="easyvend:depositor_on", param2 = node.param2})
239 return true
240 else
241 return false
245 easyvend.machine_check = function(pos, node)
246 local active = true
247 local status = "Ready."
249 local chest = minetest.get_node({x=pos.x,y=pos.y-1,z=pos.z})
250 local meta = minetest.get_meta(pos)
251 local number = meta:get_int("number")
252 local cost = meta:get_int("cost")
253 local inv = meta:get_inventory()
254 local itemstack = inv:get_stack("item",1)
255 local itemname=meta:get_string("itemname")
256 local machine_owner = meta:get_string("owner")
257 local check_wear = meta:get_int("wear") == 0
258 local chestdef = registered_chests[chest.name]
259 local chest_meta, chest_inv
261 if chestdef then
262 chest_meta = minetest.get_meta({x=pos.x,y=pos.y-1,z=pos.z})
263 chest_inv = chest_meta:get_inventory()
265 if ( chest_meta:get_string(chestdef.meta_owner) == machine_owner and chest_inv ~= nil ) then
266 local buysell = easyvend.buysell(node.name)
268 local checkstack, checkitem
269 if buysell == "buy" then
270 checkitem = easyvend.currency
271 else
272 checkitem = itemname
274 local stock = 0
275 -- Count stock
276 -- FIXME: Ignore tools with bad wear level
277 for i=1,chest_inv:get_size(chestdef.inv_list) do
278 checkstack = chest_inv:get_stack(chestdef.inv_list, i)
279 if checkstack:get_name() == checkitem then
280 stock = stock + checkstack:get_count()
283 meta:set_int("stock", stock)
285 if not itemstack:is_empty() then
287 local number_stack_max = itemstack:get_stack_max()
289 local stack = {name=itemname, count=number, wear=0, metadata=""}
290 local price = {name=easyvend.currency, count=cost, wear=0, metadata=""}
292 local chest_has, chest_free
294 local coststacks = math.modf(cost / cost_stack_max)
295 local costremainder = math.fmod(cost, cost_stack_max)
296 local numberstacks = math.modf(number / number_stack_max)
297 local numberremainder = math.fmod(number, number_stack_max)
298 local numberfree = numberstacks
299 local costfree = coststacks
300 if numberremainder > 0 then numberfree = numberfree + 1 end
301 if costremainder > 0 then costfree = costfree + 1 end
303 if buysell == "sell" then
304 chest_has = easyvend.check_and_get_items(chest_inv, chestdef.inv_list, stack, check_wear)
305 chest_free = chest_inv:room_for_item(chestdef.inv_list, price)
306 if chest_has and chest_free then
307 if cost <= cost_stack_max and number <= number_stack_max then
308 active = true
309 elseif easyvend.free_slots(chest_inv, chestdef.inv_list) < costfree then
310 active = false
311 status = "No room in the machine’s storage!"
313 elseif not chest_has then
314 active = false
315 status = "The vending machine has insufficient materials!"
316 elseif not chest_free then
317 active = false
318 status = "No room in the machine’s storage!"
320 elseif buysell == "buy" then
321 chest_has = easyvend.check_and_get_items(chest_inv, chestdef.inv_list, price, check_wear)
322 chest_free = chest_inv:room_for_item(chestdef.inv_list, stack)
323 if chest_has and chest_free then
324 if cost <= cost_stack_max and number <= number_stack_max then
325 active = true
326 elseif easyvend.free_slots(chest_inv, chestdef.inv_list) < numberfree then
327 active = false
328 status = "No room in the machine’s storage!"
330 elseif not chest_has then
331 active = false
332 status = "The depositing machine is out of money!"
333 elseif not chest_free then
334 active = false
335 status = "No room in the machine’s storage!"
338 else
339 active = false
340 status = "Awaiting configuration by owner."
342 else
343 meta:set_int("stock", 0)
344 active = false
345 status = "Storage can’t be accessed because it is owned by a different person!"
347 else
348 meta:set_int("stock", 0)
349 active = false
350 status = "No storage; machine needs a locked chest below it."
352 if meta:get_int("configmode") == 1 then
353 active = false
354 status = "Awaiting configuration by owner."
357 if itemname == easyvend.currency and number == cost and active then
358 local jt = meta:get_int("joketimer")
359 if jt > 0 then
360 jt = jt - 1
362 if jt == 0 then
363 if buysell == "sell" then
364 meta:set_string("message", "Item bought.")
365 else
366 meta:set_string("message", "Item sold.")
368 jt = -1
370 meta:set_int("joketimer", jt)
372 meta:set_string("status", status)
374 if not itemstack:is_empty() then
375 if itemname == nil or itemname == "" then
376 itemname = itemstack:get_name()
378 meta:set_string("infotext", easyvend.make_infotext(node.name, machine_owner, cost, number, itemname))
380 itemname=itemstack:get_name()
381 meta:set_string("itemname", itemname)
383 local change
384 if node.name == "easyvend:vendor" or node.name == "easyvend:depositor" then
385 if active then change = easyvend.machine_enable(pos, node) end
386 elseif node.name == "easyvend:vendor_on" or node.name == "easyvend:depositor_on" then
387 if not active then change = easyvend.machine_disable(pos, node) end
389 easyvend.set_formspec(pos)
390 return change
393 easyvend.on_receive_fields_config = function(pos, formname, fields, sender)
394 local node = minetest.get_node(pos)
395 local meta = minetest.get_meta(pos)
396 local inv_self = meta:get_inventory()
397 local itemstack = inv_self:get_stack("item",1)
398 local buysell = easyvend.buysell(node.name)
400 if fields.config then
401 meta:set_int("configmode", 1)
402 local was_active = easyvend.is_active(node.name)
403 if was_active then
404 meta:set_string("message", "Configuration mode activated; machine disabled.")
405 else
406 meta:set_string("message", "Configuration mode activated.")
408 easyvend.machine_check(pos, node)
409 return
412 if not fields.save then
413 return
416 local number = fields.number
417 local cost = fields.cost
419 --[[ Convenience function:
420 When appending “s” or “S” to the number, it is multiplied
421 by the maximum stack size.
422 TODO: Expose this in user documentation ]]
423 local number_stack_max = itemstack:get_stack_max()
424 local ss = string.sub(number, #number, #number)
425 if ss == "s" or ss == "S" then
426 local n = tonumber(string.sub(number, 1, #number-1))
427 if string.len(number) == 1 then n = 1 end
428 if n ~= nil then
429 number = n * number_stack_max
432 ss = string.sub(cost, #cost, #cost)
433 if ss == "s" or ss == "S" then
434 local n = tonumber(string.sub(cost, 1, #cost-1))
435 if string.len(cost) == 1 then n = 1 end
436 if n ~= nil then
437 cost = n * cost_stack_max
440 number = tonumber(number)
441 cost = tonumber(cost)
443 local itemname=""
445 local oldnumber = meta:get_int("number")
446 local oldcost = meta:get_int("cost")
447 local maxnumber = number_stack_max * slots_max
449 if ( itemstack == nil or itemstack:is_empty() ) then
450 meta:set_string("status", "Awaiting configuration by owner.")
451 meta:set_string("message", "No item specified.")
452 easyvend.sound_error(sender:get_player_name())
453 easyvend.set_formspec(pos, sender)
454 return
455 elseif ( number == nil or number < 1 or number > maxnumber ) then
456 if maxnumber > 1 then
457 meta:set_string("message", string.format("Invalid item count; must be between 1 and %d!", maxnumber))
458 else
459 meta:set_string("message", "Invalid item count; must be exactly 1!")
461 meta:set_int("number", oldnumber)
462 easyvend.sound_error(sender:get_player_name())
463 easyvend.set_formspec(pos, sender)
464 return
465 elseif ( cost == nil or cost < 1 or cost > maxcost ) then
466 if maxcost > 1 then
467 meta:set_string("message", string.format("Invalid cost; must be between 1 and %d!", maxcost))
468 else
469 meta:set_string("message", "Invalid cost; must be exactly 1!")
471 meta:set_int("cost", oldcost)
472 easyvend.sound_error(sender:get_player_name())
473 easyvend.set_formspec(pos, sender)
474 return
476 meta:set_int("number", number)
477 meta:set_int("cost", cost)
478 itemname=itemstack:get_name()
479 meta:set_string("itemname", itemname)
480 meta:set_int("configmode", 0)
482 if itemname == easyvend.currency and number == cost and cost <= cost_stack_max then
483 meta:set_string("message", "Configuration successful. I am feeling funny.")
484 meta:set_int("joketimer", joketimer_start)
485 meta:set_int("joke_id", easyvend.assign_joke(buysell))
486 else
487 meta:set_string("message", "Configuration successful.")
490 local change = easyvend.machine_check(pos, node)
492 if not change then
493 if (node.name == "easyvend:vendor_on" or node.name == "easyvend:depositor_on") then
494 easyvend.sound_setup(pos)
495 else
496 easyvend.sound_disable(pos)
501 easyvend.make_infotext = function(nodename, owner, cost, number, itemstring)
502 local iname = minetest.registered_items[itemstring].description
503 if iname == nil then iname = itemstring end
504 local d = ""
505 local printitem, printcost
506 if number == 1 then
507 printitem = iname
508 else
509 printitem = string.format("%d×%s", number, iname)
511 if cost == 1 then
512 printcost = easyvend.currency_desc
513 else
514 printcost = string.format("%d×%s", cost, easyvend.currency_desc)
516 if nodename == "easyvend:vendor_on" then
517 d = string.format("Vending machine (owned by %s)\nSelling: %s\nPrice: %s", owner, printitem, printcost)
518 elseif nodename == "easyvend:vendor" then
519 d = string.format("Inactive vending machine (owned by %s)\nSelling: %s\nPrice: %s", owner, printitem, printcost)
520 elseif nodename == "easyvend:depositor_on" then
521 d = string.format("Depositing machine (owned by %s)\nBuying: %s\nPayment: %s", owner, printitem, printcost)
522 elseif nodename == "easyvend:depositor" then
523 d = string.format("Inactive depositing machine (owned by %s)\nBuying: %s\nPayment: %s", owner, printitem, printcost)
525 return d
528 easyvend.on_receive_fields_buysell = function(pos, formname, fields, sender)
529 local sendername = sender:get_player_name()
530 local meta = minetest.get_meta(pos)
532 if not fields.buysell then
533 return
536 local node = minetest.get_node(pos)
537 local number = meta:get_int("number")
538 local cost = meta:get_int("cost")
539 local itemname=meta:get_string("itemname")
540 local item=meta:get_inventory():get_stack("item", 1)
541 local check_wear = meta:get_int("wear") == 0 and minetest.registered_tools[itemname] ~= nil
543 local buysell = easyvend.buysell(node.name)
545 local number_stack_max = item:get_stack_max()
546 local maxnumber = number_stack_max * slots_max
547 if ( number == nil or number < 1 or number > maxnumber ) or
548 ( cost == nil or cost < 1 or cost > maxcost ) or
549 ( itemname == nil or itemname=="") then
550 meta:set_string("status", "Invalid item count or price!")
551 easyvend.machine_disable(pos, node, sendername)
552 return
556 local chest = minetest.get_node({x=pos.x,y=pos.y-1,z=pos.z})
557 local chestdef = registered_chests[chest.name]
558 if chestdef and sender and sender:is_player() then
559 local chest_meta = minetest.get_meta({x=pos.x,y=pos.y-1,z=pos.z})
560 local chest_inv = chest_meta:get_inventory()
561 local player_inv = sender:get_inventory()
562 if ( chest_meta:get_string(chestdef.meta_owner) == meta:get_string("owner") and chest_inv ~= nil and player_inv ~= nil ) then
564 local stack = {name=itemname, count=number, wear=0, metadata=""}
565 local price = {name=easyvend.currency, count=cost, wear=0, metadata=""}
566 local chest_has, player_has, chest_free, player_free, chest_out, player_out
567 local msg = ""
568 if buysell == "sell" then
569 chest_has, chest_out = easyvend.check_and_get_items(chest_inv, "main", stack, check_wear)
570 player_has, player_out = easyvend.check_and_get_items(player_inv, "main", price, check_wear)
571 chest_free = chest_inv:room_for_item("main", price)
572 player_free = player_inv:room_for_item("main", stack)
573 if chest_has and player_has and chest_free and player_free then
574 if cost <= cost_stack_max and number <= number_stack_max then
575 easyvend.machine_enable(pos, node)
576 player_inv:remove_item("main", price)
577 if check_wear then
578 chest_inv:set_stack("main", chest_out[1].id, "")
579 player_inv:add_item("main", chest_out[1].item)
580 else
581 stack = chest_inv:remove_item("main", stack)
582 player_inv:add_item("main", stack)
584 chest_inv:add_item("main", price)
585 if itemname == easyvend.currency and number == cost and cost <= cost_stack_max then
586 meta:set_string("message", easyvend.get_joke(buysell, meta:get_int("joke_id")))
587 meta:set_int("joketimer", joketimer_start)
588 else
589 meta:set_string("message", "Item bought.")
591 easyvend.sound_vend(pos)
592 easyvend.machine_check(pos, node)
593 else
594 -- Large item counts (multiple stacks)
595 local coststacks = math.modf(cost / cost_stack_max)
596 local costremainder = math.fmod(cost, cost_stack_max)
597 local numberstacks = math.modf(number / number_stack_max)
598 local numberremainder = math.fmod(number, number_stack_max)
599 local numberfree = numberstacks
600 local costfree = coststacks
601 if numberremainder > 0 then numberfree = numberfree + 1 end
602 if costremainder > 0 then costfree = costfree + 1 end
603 if easyvend.free_slots(player_inv, "main") < numberfree then
604 if numberfree > 1 then
605 msg = string.format("No room in your inventory (%d empty slots required)!", numberfree)
606 else
607 msg = "No room in your inventory!"
609 meta:set_string("message", msg)
610 elseif easyvend.free_slots(chest_inv, "main") < costfree then
611 meta:set_string("status", "No room in the machine’s storage!")
612 easyvend.machine_disable(pos, node, sendername)
613 else
614 -- Remember items for transfer
615 local cheststacks = {}
616 easyvend.machine_enable(pos, node)
617 for i=1, coststacks do
618 price.count = cost_stack_max
619 player_inv:remove_item("main", price)
621 if costremainder > 0 then
622 price.count = costremainder
623 player_inv:remove_item("main", price)
625 if check_wear then
626 for o=1,#chest_out do
627 chest_inv:set_stack("main", chest_out[o].id, "")
629 else
630 for i=1, numberstacks do
631 stack.count = number_stack_max
632 table.insert(cheststacks, chest_inv:remove_item("main", stack))
635 if numberremainder > 0 then
636 stack.count = numberremainder
637 table.insert(cheststacks, chest_inv:remove_item("main", stack))
639 for i=1, coststacks do
640 price.count = cost_stack_max
641 chest_inv:add_item("main", price)
643 if costremainder > 0 then
644 price.count = costremainder
645 chest_inv:add_item("main", price)
647 if check_wear then
648 for o=1,#chest_out do
649 player_inv:add_item("main", chest_out[o].item)
651 else
652 for i=1,#cheststacks do
653 player_inv:add_item("main", cheststacks[i])
656 meta:set_string("message", "Item bought.")
657 easyvend.sound_vend(pos)
658 easyvend.machine_check(pos, node)
661 elseif chest_has and player_has then
662 if not player_free then
663 msg = "No room in your inventory!"
664 meta:set_string("message", msg)
665 easyvend.sound_error(sendername)
666 elseif not chest_free then
667 msg = "No room in the machine’s storage!"
668 meta:set_string("status", msg)
669 easyvend.machine_disable(pos, node, sendername)
671 else
672 if not chest_has then
673 msg = "The vending machine has insufficient materials!"
674 meta:set_string("status", msg)
675 easyvend.machine_disable(pos, node, sendername)
676 elseif not player_has then
677 msg = "You can’t afford this item!"
678 meta:set_string("message", msg)
679 easyvend.sound_error(sendername)
682 else
683 chest_has, chest_out = easyvend.check_and_get_items(chest_inv, "main", price, check_wear)
684 player_has, player_out = easyvend.check_and_get_items(player_inv, "main", stack, check_wear)
685 chest_free = chest_inv:room_for_item("main", stack)
686 player_free = player_inv:room_for_item("main", price)
687 if chest_has and player_has and chest_free and player_free then
688 if cost <= cost_stack_max and number <= number_stack_max then
689 easyvend.machine_enable(pos, node)
690 if check_wear then
691 player_inv:set_stack("main", player_out[1].id, "")
692 chest_inv:add_item("main", player_out[1].item)
693 else
694 stack = player_inv:remove_item("main", stack)
695 chest_inv:add_item("main", stack)
697 chest_inv:remove_item("main", price)
698 player_inv:add_item("main", price)
699 meta:set_string("status", "Ready.")
700 if itemname == easyvend.currency and number == cost and cost <= cost_stack_max then
701 meta:set_string("message", easyvend.get_joke(buysell, meta:get_int("joke_id")))
702 meta:set_int("joketimer", joketimer_start)
703 else
704 meta:set_string("message", "Item sold.")
706 easyvend.sound_deposit(pos)
707 easyvend.machine_check(pos, node)
708 else
709 -- Large item counts (multiple stacks)
710 local coststacks = math.modf(cost / cost_stack_max)
711 local costremainder = math.fmod(cost, cost_stack_max)
712 local numberstacks = math.modf(number / number_stack_max)
713 local numberremainder = math.fmod(number, number_stack_max)
714 local numberfree = numberstacks
715 local costfree = coststacks
716 if numberremainder > 0 then numberfree = numberfree + 1 end
717 if costremainder > 0 then costfree = costfree + 1 end
718 if easyvend.free_slots(player_inv, "main") < costfree then
719 if costfree > 1 then
720 msg = string.format("No room in your inventory (%d empty slots required)!", costfree)
721 else
722 msg = "No room in your inventory!"
724 meta:set_string("message", msg)
725 easyvend.sound_error(sendername)
726 elseif easyvend.free_slots(chest_inv, "main") < numberfree then
727 easyvend.machine_disable(pos, node, sendername)
728 else
729 easyvend.machine_enable(pos, node)
730 -- Remember removed items for transfer
731 local playerstacks = {}
732 for i=1, coststacks do
733 price.count = cost_stack_max
734 chest_inv:remove_item("main", price)
736 if costremainder > 0 then
737 price.count = costremainder
738 chest_inv:remove_item("main", price)
740 if check_wear then
741 for o=1,#player_out do
742 player_inv:set_stack("main", player_out[o].id, "")
744 else
745 for i=1, numberstacks do
746 stack.count = number_stack_max
747 table.insert(playerstacks, player_inv:remove_item("main", stack))
750 if numberremainder > 0 then
751 stack.count = numberremainder
752 table.insert(playerstacks, player_inv:remove_item("main", stack))
754 for i=1, coststacks do
755 price.count = cost_stack_max
756 player_inv:add_item("main", price)
758 if costremainder > 0 then
759 price.count = costremainder
760 player_inv:add_item("main", price)
762 if check_wear then
763 for o=1,#player_out do
764 chest_inv:add_item("main", player_out[o].item)
766 else
767 for i=1,#playerstacks do
768 chest_inv:add_item("main", playerstacks[i])
771 meta:set_string("message", "Item sold.")
772 easyvend.sound_deposit(pos)
773 easyvend.machine_check(pos, node)
776 elseif chest_has and player_has then
777 if not player_free then
778 msg = "No room in your inventory!"
779 meta:set_string("message", msg)
780 easyvend.sound_error(sendername)
781 elseif not chest_free then
782 msg = "No room in the machine’s storage!"
783 meta:set_string("status", msg)
784 easyvend.machine_disable(pos, node, sendername)
786 else
787 if not player_has then
788 msg = "You have insufficient materials!"
789 meta:set_string("message", msg)
790 easyvend.sound_error(sendername)
791 elseif not chest_has then
792 msg = "The depositing machine is out of money!"
793 meta:set_string("status", msg)
794 easyvend.machine_disable(pos, node, sendername)
798 else
799 meta:set_string("status", "Storage can’t be accessed because it is owned by a different person!")
800 easyvend.machine_disable(pos, node, sendername)
802 else
803 if sender and sender:is_player() then
804 meta:set_string("status", "No storage; machine needs a locked chest below it.")
805 easyvend.machine_disable(pos, node, sendername)
809 easyvend.set_formspec(pos, sender)
813 easyvend.after_place_node = function(pos, placer)
814 local node = minetest.get_node(pos)
815 local meta = minetest.get_meta(pos)
816 local inv = meta:get_inventory()
817 local player_name = placer:get_player_name()
818 inv:set_size("item", 1)
819 inv:set_size("gold", 1)
821 inv:set_stack( "gold", 1, easyvend.currency )
823 local d = ""
824 if node.name == "easyvend:vendor" then
825 d = string.format("Inactive vending machine (owned by %s)", player_name)
826 meta:set_int("wear", 1)
827 elseif node.name == "easyvend:depositor" then
828 d = string.format("Inactive depositing machine (owned by %s)", player_name)
829 meta:set_int("wear", 0)
831 meta:set_string("infotext", d)
832 local chest = minetest.get_node({x=pos.x,y=pos.y-1,z=pos.z})
833 meta:set_string("status", "Awaiting configuration by owner.")
834 meta:set_string("message", "Welcome! Please prepare the machine.")
835 meta:set_int("number", 1)
836 meta:set_int("cost", 1)
837 meta:set_int("stock", -1)
838 meta:set_int("configmode", 1)
839 meta:set_int("joketimer", -1)
840 meta:set_int("joke_id", 1)
841 meta:set_string("itemname", "")
843 meta:set_string("owner", player_name or "")
845 easyvend.set_formspec(pos, placer)
848 easyvend.can_dig = function(pos, player)
849 local meta = minetest.get_meta(pos)
850 local name = player:get_player_name()
851 -- Owner can always dig shop
852 if meta:get_string("owner") == name then
853 return true
855 local chest = minetest.get_node({x=pos.x,y=pos.y-1,z=pos.z})
856 local meta_chest = minetest.get_meta({x=pos.x,y=pos.y-1,z=pos.z});
857 if registered_chests[chest.name] then
858 if player and player:is_player() then
859 local owner_chest = meta_chest:get_string(registered_chests[chest.name].meta_owner)
860 if name == owner_chest then
861 return true --chest owner can also dig shop
864 return false
865 else
866 return true --if no chest, enyone can dig this shop
870 easyvend.on_receive_fields = function(pos, formname, fields, sender)
871 local meta = minetest.get_meta(pos)
872 local node = minetest.get_node(pos)
873 local owner = meta:get_string("owner")
874 local sendername = sender:get_player_name(sender)
876 if fields.config or fields.save or fields.usermode then
877 if sender:get_player_name() == owner then
878 easyvend.on_receive_fields_config(pos, formname, fields, sender)
879 else
880 meta:set_string("message", "Only the owner may change the configuration.")
881 easyvend.sound_error(sendername)
882 easyvend.set_formspec(pos, sender)
883 return
885 elseif fields.wear ~= nil then
886 if sender:get_player_name() == owner then
887 if fields.wear == "true" then
888 if easyvend.buysell(node.name) == "buy" then
889 meta:set_string("message", "Used tools are now accepted.")
890 else
891 meta:set_string("message", "Used tools are now for sale.")
893 meta:set_int("wear", 1)
894 elseif fields.wear == "false" then
895 if easyvend.buysell(node.name) == "buy" then
896 meta:set_string("message", "Used tools are now rejected.")
897 else
898 meta:set_string("message", "Used tools won’t be sold anymore.")
900 meta:set_int("wear", 0)
902 easyvend.set_formspec(pos, sender)
903 return
904 else
905 meta:set_string("message", "Only the owner may change the configuration.")
906 easyvend.sound_error(sendername)
907 easyvend.set_formspec(pos, sender)
908 return
910 elseif fields.buysell then
911 easyvend.on_receive_fields_buysell(pos, formname, fields, sender)
915 -- Jokes: Appear when machine exchanges currency for currency at equal rate
917 -- Vendor
918 local jokes_vendor = {
919 "Thank you. You have made a vending machine very happy.",
920 "Humans have a strange sense of humor.",
921 "Let’s get this over with …",
922 "Item “bought”.",
923 "Tit for tat.",
924 "Do you realize what you’ve just bought?",
926 -- Depositor
927 local jokes_depositor = {
928 "Thank you, the money started to smell inside.",
929 "Money doesn’t grow on trees, you know?",
930 "Sanity sold.",
931 "Well, that was an awkward exchange.",
932 "Are you having fun?",
933 "Is this really trading?",
936 easyvend.assign_joke = function(buysell)
937 local jokes
938 if buysell == "sell" then
939 jokes = jokes_vendor
940 elseif buysell == "buy" then
941 jokes = jokes_depositor
943 local r = math.random(1,#jokes)
944 return r
947 easyvend.get_joke = function(buysell, id)
948 local joke
949 if buysell == nil or id == nil then
950 -- Fallback message (should never happen)
951 return "Items exchanged."
953 if buysell == "sell" then
954 joke = jokes_vendor[id]
955 if joke == nil then joke = jokes_vendor[1] end
956 elseif buysell == "buy" then
957 joke = jokes_depositor[id]
958 if joke == nil then joke = jokes_depositor[1] end
960 return joke
963 easyvend.sound_error = function(playername)
964 minetest.sound_play("easyvend_error", {to_player = playername, gain = 0.25})
967 easyvend.sound_setup = function(pos)
968 minetest.sound_play("easyvend_activate", {pos = pos, gain = 0.5, max_hear_distance = 12,})
971 easyvend.sound_disable = function(pos)
972 minetest.sound_play("easyvend_disable", {pos = pos, gain = 0.9, max_hear_distance = 12,})
975 easyvend.sound_vend = function(pos)
976 minetest.sound_play("easyvend_vend", {pos = pos, gain = 0.4, max_hear_distance = 5,})
979 easyvend.sound_deposit = function(pos)
980 minetest.sound_play("easyvend_deposit", {pos = pos, gain = 0.4, max_hear_distance = 5,})
983 easyvend.allow_metadata_inventory_put = function(pos, listname, index, stack, player)
984 if listname=="item" then
985 local meta = minetest.get_meta(pos);
986 local owner = meta:get_string("owner")
987 local name = player:get_player_name()
988 if name == owner then
989 local inv = meta:get_inventory()
990 if stack==nil then
991 inv:set_stack( "item", 1, nil )
992 else
993 inv:set_stack( "item", 1, stack:get_name() )
994 meta:set_string("itemname", stack:get_name())
995 easyvend.set_formspec(pos, player)
999 return 0
1002 easyvend.allow_metadata_inventory_take = function(pos, listname, index, stack, player)
1003 return 0
1006 easyvend.allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
1007 return 0
1010 minetest.register_abm({
1011 nodenames = {"easyvend:vendor", "easyvend:vendor_on", "easyvend:depositor", "easyvend:depositor_on"},
1012 interval = 5,
1013 chance = 1,
1014 catch_up = false,
1015 action = function(pos, node, active_object_count, active_object_count_wider)
1016 easyvend.machine_check(pos, node)