Legacy support: Use itemtype metadata field
[minetest_easyvend.git] / easyvend.lua
blob94e01231aa53616640f3b114896ebb74f289a278
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
286 local number_stack_max = itemstack:get_stack_max()
287 local maxnumber = number_stack_max * slots_max
288 if number >= 1 and number <= maxnumber and cost >= 1 and cost <= maxcost then
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 if buysell == "sell" then
341 status = "Invalid item count or price."
342 else
343 status = "Invalid item count or payment."
346 else
347 active = false
348 status = "Awaiting configuration by owner."
350 else
351 meta:set_int("stock", 0)
352 active = false
353 status = "Storage can’t be accessed because it is owned by a different person!"
355 else
356 meta:set_int("stock", 0)
357 active = false
358 status = "No storage; machine needs a locked chest below it."
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 local change
387 if node.name == "easyvend:vendor" or node.name == "easyvend:depositor" then
388 if active then change = easyvend.machine_enable(pos, node) end
389 elseif node.name == "easyvend:vendor_on" or node.name == "easyvend:depositor_on" then
390 if not active then change = easyvend.machine_disable(pos, node) end
392 easyvend.set_formspec(pos)
393 return change
396 easyvend.on_receive_fields_config = function(pos, formname, fields, sender)
397 local node = minetest.get_node(pos)
398 local meta = minetest.get_meta(pos)
399 local inv_self = meta:get_inventory()
400 local itemstack = inv_self:get_stack("item",1)
401 local buysell = easyvend.buysell(node.name)
403 if fields.config then
404 meta:set_int("configmode", 1)
405 local was_active = easyvend.is_active(node.name)
406 if was_active then
407 meta:set_string("message", "Configuration mode activated; machine disabled.")
408 else
409 meta:set_string("message", "Configuration mode activated.")
411 easyvend.machine_check(pos, node)
412 return
415 if not fields.save then
416 return
419 local number = fields.number
420 local cost = fields.cost
422 --[[ Convenience function:
423 When appending “s” or “S” to the number, it is multiplied
424 by the maximum stack size.
425 TODO: Expose this in user documentation ]]
426 local number_stack_max = itemstack:get_stack_max()
427 local ss = string.sub(number, #number, #number)
428 if ss == "s" or ss == "S" then
429 local n = tonumber(string.sub(number, 1, #number-1))
430 if string.len(number) == 1 then n = 1 end
431 if n ~= nil then
432 number = n * number_stack_max
435 ss = string.sub(cost, #cost, #cost)
436 if ss == "s" or ss == "S" then
437 local n = tonumber(string.sub(cost, 1, #cost-1))
438 if string.len(cost) == 1 then n = 1 end
439 if n ~= nil then
440 cost = n * cost_stack_max
443 number = tonumber(number)
444 cost = tonumber(cost)
446 local itemname=""
448 local oldnumber = meta:get_int("number")
449 local oldcost = meta:get_int("cost")
450 local maxnumber = number_stack_max * slots_max
452 if ( itemstack == nil or itemstack:is_empty() ) then
453 meta:set_string("status", "Awaiting configuration by owner.")
454 meta:set_string("message", "No item specified.")
455 easyvend.sound_error(sender:get_player_name())
456 easyvend.set_formspec(pos, sender)
457 return
458 elseif ( number == nil or number < 1 or number > maxnumber ) then
459 if maxnumber > 1 then
460 meta:set_string("message", string.format("Invalid item count; must be between 1 and %d!", maxnumber))
461 else
462 meta:set_string("message", "Invalid item count; must be exactly 1!")
464 meta:set_int("number", oldnumber)
465 easyvend.sound_error(sender:get_player_name())
466 easyvend.set_formspec(pos, sender)
467 return
468 elseif ( cost == nil or cost < 1 or cost > maxcost ) then
469 if maxcost > 1 then
470 meta:set_string("message", string.format("Invalid cost; must be between 1 and %d!", maxcost))
471 else
472 meta:set_string("message", "Invalid cost; must be exactly 1!")
474 meta:set_int("cost", oldcost)
475 easyvend.sound_error(sender:get_player_name())
476 easyvend.set_formspec(pos, sender)
477 return
479 meta:set_int("number", number)
480 meta:set_int("cost", cost)
481 itemname=itemstack:get_name()
482 meta:set_string("itemname", itemname)
483 meta:set_int("configmode", 0)
485 if itemname == easyvend.currency and number == cost and cost <= cost_stack_max then
486 meta:set_string("message", "Configuration successful. I am feeling funny.")
487 meta:set_int("joketimer", joketimer_start)
488 meta:set_int("joke_id", easyvend.assign_joke(buysell))
489 else
490 meta:set_string("message", "Configuration successful.")
493 local change = easyvend.machine_check(pos, node)
495 if not change then
496 if (node.name == "easyvend:vendor_on" or node.name == "easyvend:depositor_on") then
497 easyvend.sound_setup(pos)
498 else
499 easyvend.sound_disable(pos)
504 easyvend.make_infotext = function(nodename, owner, cost, number, itemstring)
505 local d = ""
506 if itemstring == nil or itemstring == "" or number == 0 or cost == 0 then
507 if easyvend.buysell(nodename) == "sell" then
508 d = string.format("Inactive vending machine (owned by %s)", owner)
509 else
510 d = string.format("Inactive depositing machine (owned by %s)", owner)
512 return d
514 local iname = minetest.registered_items[itemstring].description
515 if iname == nil then iname = itemstring end
516 local printitem, printcost
517 if number == 1 then
518 printitem = iname
519 else
520 printitem = string.format("%d×%s", number, iname)
522 if cost == 1 then
523 printcost = easyvend.currency_desc
524 else
525 printcost = string.format("%d×%s", cost, easyvend.currency_desc)
527 if nodename == "easyvend:vendor_on" then
528 d = string.format("Vending machine (owned by %s)\nSelling: %s\nPrice: %s", owner, printitem, printcost)
529 elseif nodename == "easyvend:vendor" then
530 d = string.format("Inactive vending machine (owned by %s)\nSelling: %s\nPrice: %s", owner, printitem, printcost)
531 elseif nodename == "easyvend:depositor_on" then
532 d = string.format("Depositing machine (owned by %s)\nBuying: %s\nPayment: %s", owner, printitem, printcost)
533 elseif nodename == "easyvend:depositor" then
534 d = string.format("Inactive depositing machine (owned by %s)\nBuying: %s\nPayment: %s", owner, printitem, printcost)
536 return d
539 easyvend.on_receive_fields_buysell = function(pos, formname, fields, sender)
540 local sendername = sender:get_player_name()
541 local meta = minetest.get_meta(pos)
543 if not fields.buysell then
544 return
547 local node = minetest.get_node(pos)
548 local number = meta:get_int("number")
549 local cost = meta:get_int("cost")
550 local itemname=meta:get_string("itemname")
551 local item=meta:get_inventory():get_stack("item", 1)
552 local check_wear = meta:get_int("wear") == 0 and minetest.registered_tools[itemname] ~= nil
554 local buysell = easyvend.buysell(node.name)
556 local number_stack_max = item:get_stack_max()
557 local maxnumber = number_stack_max * slots_max
558 if ( number == nil or number < 1 or number > maxnumber ) or
559 ( cost == nil or cost < 1 or cost > maxcost ) or
560 ( itemname == nil or itemname=="") then
561 meta:set_string("status", "Invalid item count or price!")
562 easyvend.machine_disable(pos, node, sendername)
563 return
567 local chest = minetest.get_node({x=pos.x,y=pos.y-1,z=pos.z})
568 local chestdef = registered_chests[chest.name]
569 if chestdef and sender and sender:is_player() then
570 local chest_meta = minetest.get_meta({x=pos.x,y=pos.y-1,z=pos.z})
571 local chest_inv = chest_meta:get_inventory()
572 local player_inv = sender:get_inventory()
573 if ( chest_meta:get_string(chestdef.meta_owner) == meta:get_string("owner") and chest_inv ~= nil and player_inv ~= nil ) then
575 local stack = {name=itemname, count=number, wear=0, metadata=""}
576 local price = {name=easyvend.currency, count=cost, wear=0, metadata=""}
577 local chest_has, player_has, chest_free, player_free, chest_out, player_out
578 local msg = ""
579 if buysell == "sell" then
580 chest_has, chest_out = easyvend.check_and_get_items(chest_inv, "main", stack, check_wear)
581 player_has, player_out = easyvend.check_and_get_items(player_inv, "main", price, check_wear)
582 chest_free = chest_inv:room_for_item("main", price)
583 player_free = player_inv:room_for_item("main", stack)
584 if chest_has and player_has and chest_free and player_free then
585 if cost <= cost_stack_max and number <= number_stack_max then
586 easyvend.machine_enable(pos, node)
587 player_inv:remove_item("main", price)
588 if check_wear then
589 chest_inv:set_stack("main", chest_out[1].id, "")
590 player_inv:add_item("main", chest_out[1].item)
591 else
592 stack = chest_inv:remove_item("main", stack)
593 player_inv:add_item("main", stack)
595 chest_inv:add_item("main", price)
596 if itemname == easyvend.currency and number == cost and cost <= cost_stack_max then
597 meta:set_string("message", easyvend.get_joke(buysell, meta:get_int("joke_id")))
598 meta:set_int("joketimer", joketimer_start)
599 else
600 meta:set_string("message", "Item bought.")
602 easyvend.sound_vend(pos)
603 easyvend.machine_check(pos, node)
604 else
605 -- Large item counts (multiple stacks)
606 local coststacks = math.modf(cost / cost_stack_max)
607 local costremainder = math.fmod(cost, cost_stack_max)
608 local numberstacks = math.modf(number / number_stack_max)
609 local numberremainder = math.fmod(number, number_stack_max)
610 local numberfree = numberstacks
611 local costfree = coststacks
612 if numberremainder > 0 then numberfree = numberfree + 1 end
613 if costremainder > 0 then costfree = costfree + 1 end
614 if easyvend.free_slots(player_inv, "main") < numberfree then
615 if numberfree > 1 then
616 msg = string.format("No room in your inventory (%d empty slots required)!", numberfree)
617 else
618 msg = "No room in your inventory!"
620 meta:set_string("message", msg)
621 elseif easyvend.free_slots(chest_inv, "main") < costfree then
622 meta:set_string("status", "No room in the machine’s storage!")
623 easyvend.machine_disable(pos, node, sendername)
624 else
625 -- Remember items for transfer
626 local cheststacks = {}
627 easyvend.machine_enable(pos, node)
628 for i=1, coststacks do
629 price.count = cost_stack_max
630 player_inv:remove_item("main", price)
632 if costremainder > 0 then
633 price.count = costremainder
634 player_inv:remove_item("main", price)
636 if check_wear then
637 for o=1,#chest_out do
638 chest_inv:set_stack("main", chest_out[o].id, "")
640 else
641 for i=1, numberstacks do
642 stack.count = number_stack_max
643 table.insert(cheststacks, chest_inv:remove_item("main", stack))
646 if numberremainder > 0 then
647 stack.count = numberremainder
648 table.insert(cheststacks, chest_inv:remove_item("main", stack))
650 for i=1, coststacks do
651 price.count = cost_stack_max
652 chest_inv:add_item("main", price)
654 if costremainder > 0 then
655 price.count = costremainder
656 chest_inv:add_item("main", price)
658 if check_wear then
659 for o=1,#chest_out do
660 player_inv:add_item("main", chest_out[o].item)
662 else
663 for i=1,#cheststacks do
664 player_inv:add_item("main", cheststacks[i])
667 meta:set_string("message", "Item bought.")
668 easyvend.sound_vend(pos)
669 easyvend.machine_check(pos, node)
672 elseif chest_has and player_has then
673 if not player_free then
674 msg = "No room in your inventory!"
675 meta:set_string("message", msg)
676 easyvend.sound_error(sendername)
677 elseif not chest_free then
678 msg = "No room in the machine’s storage!"
679 meta:set_string("status", msg)
680 easyvend.machine_disable(pos, node, sendername)
682 else
683 if not chest_has then
684 msg = "The vending machine has insufficient materials!"
685 meta:set_string("status", msg)
686 easyvend.machine_disable(pos, node, sendername)
687 elseif not player_has then
688 msg = "You can’t afford this item!"
689 meta:set_string("message", msg)
690 easyvend.sound_error(sendername)
693 else
694 chest_has, chest_out = easyvend.check_and_get_items(chest_inv, "main", price, check_wear)
695 player_has, player_out = easyvend.check_and_get_items(player_inv, "main", stack, check_wear)
696 chest_free = chest_inv:room_for_item("main", stack)
697 player_free = player_inv:room_for_item("main", price)
698 if chest_has and player_has and chest_free and player_free then
699 if cost <= cost_stack_max and number <= number_stack_max then
700 easyvend.machine_enable(pos, node)
701 if check_wear then
702 player_inv:set_stack("main", player_out[1].id, "")
703 chest_inv:add_item("main", player_out[1].item)
704 else
705 stack = player_inv:remove_item("main", stack)
706 chest_inv:add_item("main", stack)
708 chest_inv:remove_item("main", price)
709 player_inv:add_item("main", price)
710 meta:set_string("status", "Ready.")
711 if itemname == easyvend.currency and number == cost and cost <= cost_stack_max then
712 meta:set_string("message", easyvend.get_joke(buysell, meta:get_int("joke_id")))
713 meta:set_int("joketimer", joketimer_start)
714 else
715 meta:set_string("message", "Item sold.")
717 easyvend.sound_deposit(pos)
718 easyvend.machine_check(pos, node)
719 else
720 -- Large item counts (multiple stacks)
721 local coststacks = math.modf(cost / cost_stack_max)
722 local costremainder = math.fmod(cost, cost_stack_max)
723 local numberstacks = math.modf(number / number_stack_max)
724 local numberremainder = math.fmod(number, number_stack_max)
725 local numberfree = numberstacks
726 local costfree = coststacks
727 if numberremainder > 0 then numberfree = numberfree + 1 end
728 if costremainder > 0 then costfree = costfree + 1 end
729 if easyvend.free_slots(player_inv, "main") < costfree then
730 if costfree > 1 then
731 msg = string.format("No room in your inventory (%d empty slots required)!", costfree)
732 else
733 msg = "No room in your inventory!"
735 meta:set_string("message", msg)
736 easyvend.sound_error(sendername)
737 elseif easyvend.free_slots(chest_inv, "main") < numberfree then
738 easyvend.machine_disable(pos, node, sendername)
739 else
740 easyvend.machine_enable(pos, node)
741 -- Remember removed items for transfer
742 local playerstacks = {}
743 for i=1, coststacks do
744 price.count = cost_stack_max
745 chest_inv:remove_item("main", price)
747 if costremainder > 0 then
748 price.count = costremainder
749 chest_inv:remove_item("main", price)
751 if check_wear then
752 for o=1,#player_out do
753 player_inv:set_stack("main", player_out[o].id, "")
755 else
756 for i=1, numberstacks do
757 stack.count = number_stack_max
758 table.insert(playerstacks, player_inv:remove_item("main", stack))
761 if numberremainder > 0 then
762 stack.count = numberremainder
763 table.insert(playerstacks, player_inv:remove_item("main", stack))
765 for i=1, coststacks do
766 price.count = cost_stack_max
767 player_inv:add_item("main", price)
769 if costremainder > 0 then
770 price.count = costremainder
771 player_inv:add_item("main", price)
773 if check_wear then
774 for o=1,#player_out do
775 chest_inv:add_item("main", player_out[o].item)
777 else
778 for i=1,#playerstacks do
779 chest_inv:add_item("main", playerstacks[i])
782 meta:set_string("message", "Item sold.")
783 easyvend.sound_deposit(pos)
784 easyvend.machine_check(pos, node)
787 elseif chest_has and player_has then
788 if not player_free then
789 msg = "No room in your inventory!"
790 meta:set_string("message", msg)
791 easyvend.sound_error(sendername)
792 elseif not chest_free then
793 msg = "No room in the machine’s storage!"
794 meta:set_string("status", msg)
795 easyvend.machine_disable(pos, node, sendername)
797 else
798 if not player_has then
799 msg = "You have insufficient materials!"
800 meta:set_string("message", msg)
801 easyvend.sound_error(sendername)
802 elseif not chest_has then
803 msg = "The depositing machine is out of money!"
804 meta:set_string("status", msg)
805 easyvend.machine_disable(pos, node, sendername)
809 else
810 meta:set_string("status", "Storage can’t be accessed because it is owned by a different person!")
811 easyvend.machine_disable(pos, node, sendername)
813 else
814 if sender and sender:is_player() then
815 meta:set_string("status", "No storage; machine needs a locked chest below it.")
816 easyvend.machine_disable(pos, node, sendername)
820 easyvend.set_formspec(pos, sender)
824 easyvend.after_place_node = function(pos, placer)
825 local node = minetest.get_node(pos)
826 local meta = minetest.get_meta(pos)
827 local inv = meta:get_inventory()
828 local player_name = placer:get_player_name()
829 inv:set_size("item", 1)
830 inv:set_size("gold", 1)
832 inv:set_stack( "gold", 1, easyvend.currency )
834 local d = ""
835 if node.name == "easyvend:vendor" then
836 d = string.format("Inactive vending machine (owned by %s)", player_name)
837 meta:set_int("wear", 1)
838 elseif node.name == "easyvend:depositor" then
839 d = string.format("Inactive depositing machine (owned by %s)", player_name)
840 meta:set_int("wear", 0)
842 meta:set_string("infotext", d)
843 local chest = minetest.get_node({x=pos.x,y=pos.y-1,z=pos.z})
844 meta:set_string("status", "Awaiting configuration by owner.")
845 meta:set_string("message", "Welcome! Please prepare the machine.")
846 meta:set_int("number", 1)
847 meta:set_int("cost", 1)
848 meta:set_int("stock", -1)
849 meta:set_int("configmode", 1)
850 meta:set_int("joketimer", -1)
851 meta:set_int("joke_id", 1)
852 meta:set_string("itemname", "")
854 meta:set_string("owner", player_name or "")
856 easyvend.set_formspec(pos, placer)
859 easyvend.can_dig = function(pos, player)
860 local meta = minetest.get_meta(pos)
861 local name = player:get_player_name()
862 -- Owner can always dig shop
863 if meta:get_string("owner") == name then
864 return true
866 local chest = minetest.get_node({x=pos.x,y=pos.y-1,z=pos.z})
867 local meta_chest = minetest.get_meta({x=pos.x,y=pos.y-1,z=pos.z});
868 if registered_chests[chest.name] then
869 if player and player:is_player() then
870 local owner_chest = meta_chest:get_string(registered_chests[chest.name].meta_owner)
871 if name == owner_chest then
872 return true --chest owner can also dig shop
875 return false
876 else
877 return true --if no chest, enyone can dig this shop
881 easyvend.on_receive_fields = function(pos, formname, fields, sender)
882 local meta = minetest.get_meta(pos)
883 local node = minetest.get_node(pos)
884 local owner = meta:get_string("owner")
885 local sendername = sender:get_player_name(sender)
887 if fields.config or fields.save or fields.usermode then
888 if sender:get_player_name() == owner then
889 easyvend.on_receive_fields_config(pos, formname, fields, sender)
890 else
891 meta:set_string("message", "Only the owner may change the configuration.")
892 easyvend.sound_error(sendername)
893 easyvend.set_formspec(pos, sender)
894 return
896 elseif fields.wear ~= nil then
897 if sender:get_player_name() == owner then
898 if fields.wear == "true" then
899 if easyvend.buysell(node.name) == "buy" then
900 meta:set_string("message", "Used tools are now accepted.")
901 else
902 meta:set_string("message", "Used tools are now for sale.")
904 meta:set_int("wear", 1)
905 elseif fields.wear == "false" then
906 if easyvend.buysell(node.name) == "buy" then
907 meta:set_string("message", "Used tools are now rejected.")
908 else
909 meta:set_string("message", "Used tools won’t be sold anymore.")
911 meta:set_int("wear", 0)
913 easyvend.set_formspec(pos, sender)
914 return
915 else
916 meta:set_string("message", "Only the owner may change the configuration.")
917 easyvend.sound_error(sendername)
918 easyvend.set_formspec(pos, sender)
919 return
921 elseif fields.buysell then
922 easyvend.on_receive_fields_buysell(pos, formname, fields, sender)
926 -- Jokes: Appear when machine exchanges currency for currency at equal rate
928 -- Vendor
929 local jokes_vendor = {
930 "Thank you. You have made a vending machine very happy.",
931 "Humans have a strange sense of humor.",
932 "Let’s get this over with …",
933 "Item “bought”.",
934 "Tit for tat.",
935 "Do you realize what you’ve just bought?",
937 -- Depositor
938 local jokes_depositor = {
939 "Thank you, the money started to smell inside.",
940 "Money doesn’t grow on trees, you know?",
941 "Sanity sold.",
942 "Well, that was an awkward exchange.",
943 "Are you having fun?",
944 "Is this really trading?",
947 easyvend.assign_joke = function(buysell)
948 local jokes
949 if buysell == "sell" then
950 jokes = jokes_vendor
951 elseif buysell == "buy" then
952 jokes = jokes_depositor
954 local r = math.random(1,#jokes)
955 return r
958 easyvend.get_joke = function(buysell, id)
959 local joke
960 if buysell == nil or id == nil then
961 -- Fallback message (should never happen)
962 return "Items exchanged."
964 if buysell == "sell" then
965 joke = jokes_vendor[id]
966 if joke == nil then joke = jokes_vendor[1] end
967 elseif buysell == "buy" then
968 joke = jokes_depositor[id]
969 if joke == nil then joke = jokes_depositor[1] end
971 return joke
974 easyvend.sound_error = function(playername)
975 minetest.sound_play("easyvend_error", {to_player = playername, gain = 0.25})
978 easyvend.sound_setup = function(pos)
979 minetest.sound_play("easyvend_activate", {pos = pos, gain = 0.5, max_hear_distance = 12,})
982 easyvend.sound_disable = function(pos)
983 minetest.sound_play("easyvend_disable", {pos = pos, gain = 0.9, max_hear_distance = 12,})
986 easyvend.sound_vend = function(pos)
987 minetest.sound_play("easyvend_vend", {pos = pos, gain = 0.4, max_hear_distance = 5,})
990 easyvend.sound_deposit = function(pos)
991 minetest.sound_play("easyvend_deposit", {pos = pos, gain = 0.4, max_hear_distance = 5,})
994 easyvend.allow_metadata_inventory_put = function(pos, listname, index, stack, player)
995 if listname=="item" then
996 local meta = minetest.get_meta(pos);
997 local owner = meta:get_string("owner")
998 local name = player:get_player_name()
999 if name == owner then
1000 local inv = meta:get_inventory()
1001 if stack==nil then
1002 inv:set_stack( "item", 1, nil )
1003 else
1004 inv:set_stack( "item", 1, stack:get_name() )
1005 meta:set_string("itemname", stack:get_name())
1006 easyvend.set_formspec(pos, player)
1010 return 0
1013 easyvend.allow_metadata_inventory_take = function(pos, listname, index, stack, player)
1014 return 0
1017 easyvend.allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
1018 return 0
1021 minetest.register_abm({
1022 nodenames = {"easyvend:vendor", "easyvend:vendor_on", "easyvend:depositor", "easyvend:depositor_on"},
1023 interval = 5,
1024 chance = 1,
1025 catch_up = false,
1026 action = function(pos, node, active_object_count, active_object_count_wider)
1027 easyvend.machine_check(pos, node)
1031 -- Legacy support for vendor mod:
1032 -- Transform the world and items to use the easyvend nodes/items
1034 -- For safety reasons, only do this when player requested so
1035 if minetest.setting_getbool("easyvend_convert_vendor") == true then
1036 -- Replace vendor nodes
1037 minetest.register_lbm({
1038 name = "easyvend:replace_vendor",
1039 nodenames = { "vendor:vendor", "vendor:depositor" },
1040 run_at_every_load = true,
1041 action = function(pos, node)
1042 -- Replace node
1043 local newnodename
1044 if node.name == "vendor:vendor" then
1045 newnodename = "easyvend:vendor"
1046 elseif node.name == "vendor:depositor" then
1047 newnodename = "easyvend:depositor"
1049 minetest.swap_node(pos, { name = newnodename, param2 = node.param2 })
1051 -- Initialize metadata
1052 local meta = minetest.get_meta(pos)
1053 meta:set_int("stock", -1)
1054 meta:set_int("joketimer", -1)
1055 meta:set_int("joke_id", 1)
1056 local inv = meta:get_inventory()
1057 inv:set_size("item", 1)
1058 inv:set_size("gold", 1)
1059 inv:set_stack("gold", 1, easyvend.currency)
1061 -- In vendor, all machines accepted worn tools
1062 meta:set_int("wear", 1)
1064 -- Set item
1065 local itemname = meta:get_string("itemname")
1066 if itemname == "" or itemname == nil then
1067 itemname = meta:get_string("itemtype")
1069 local configmode = 1
1070 if itemname == "" or itemname == nil then
1071 -- If no itemname set, try to scan first item in chest below
1072 local chest = minetest.get_node({x=pos.x,y=pos.y-1,z=pos.z})
1073 if chest.name == "default:chest_locked" then
1074 local chest_meta = minetest.get_meta({x=pos.x,y=pos.y-1,z=pos.z})
1075 local chest_inv = chest_meta:get_inventory()
1076 if ( chest_meta:get_string("owner") == machine_owner and chest_inv ~= nil ) then
1078 for i=1,chest_inv:get_size("main") do
1079 checkstack = chest_inv:get_stack("main", i)
1080 if not checkstack:is_empty() then
1081 itemname = checkstack:get_name()
1082 break
1087 if itemname ~= "" and itemname ~= nil then
1088 inv:set_stack("item", 1, itemname)
1089 meta:set_string("itemname", itemname)
1092 -- Check for valid item, item count and price
1093 if itemname ~= "" and itemname ~= nil then
1094 local itemstack = inv:get_stack("item", 1)
1095 local number_stack_max = itemstack:get_stack_max()
1096 local maxnumber = number_stack_max * slots_max
1097 local cost = meta:get_int("cost")
1098 local number = meta:get_int("number")
1099 if number >= 1 and number <= maxnumber and cost >= 1 and cost <= maxcost then
1100 configmode = 0
1104 -- Final initialization stuff
1105 meta:set_int("configmode", configmode)
1107 local owner = meta:get_string("owner")
1108 if easyvend.buysell(newnodename) == "sell" then
1109 meta:set_string("infotext", string.format("Vending machine (owned by %s)", owner))
1110 else
1111 meta:set_string("infotext", string.format("Depositing machine (owned by %s)", owner))
1115 meta:set_string("status", "Initializing …")
1116 meta:set_string("message", "Upgrade successful.")
1117 easyvend.machine_check(pos, node)
1118 end,