Clarify “Access denied.” message
[minetest_easyvend.git] / easyvend.lua
blobf83e6f90c1c5dcc7d1e90544928d4e74329c20c0
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 -- Set item which is used as payment for vending and depositing machines
25 local currency = minetest.setting_get("easyvend_currency")
26 if currency == nil or minetest.registered_items[currency] == nil then
27 currency = "default:gold_ingot"
28 end
30 local currency_desc = minetest.registered_items[currency].description
31 local registered_chests = {}
32 local cost_stack_max = ItemStack(currency):get_stack_max()
33 local maxcost = cost_stack_max * slots_max
35 local joketimer_start = 3
37 -- Allow for other mods to register custom chests
38 easyvend.register_chest = function(node_name, inv_list, meta_owner)
39 registered_chests[node_name] = { inv_list = inv_list, meta_owner = meta_owner }
40 end
42 -- Partly a wrapper around contains_item, but does special treatment if the item
43 -- is a tool. Basically checks whether the items exist in the supplied inventory
44 -- list. If check_wear is true, only counts items without wear.
45 easyvend.check_and_get_items = function(inventory, listname, itemtable, check_wear)
46 local itemstring = itemtable.name
47 local minimum = itemtable.count
48 if check_wear == nil then check_wear = false end
49 local get_items = {}
50 -- Tool workaround
51 if minetest.registered_tools[itemstring] ~= nil then
52 local count = 0
53 for i=1,inventory:get_size(listname) do
54 local stack = inventory:get_stack(listname, i)
55 if stack:get_name() == itemstring then
56 if not check_wear or stack:get_wear() == 0 then
57 count = count + 1
58 table.insert(get_items, {id=i, item=stack})
59 if count >= minimum then
60 return true, get_items
61 end
62 end
63 end
64 end
65 return false
66 else
67 -- Normal Minetest check
68 return inventory:contains_item(listname, ItemStack(itemtable))
69 end
70 end
73 if minetest.get_modpath("default") ~= nil then
74 easyvend.register_chest("default:chest_locked", "main", "owner")
75 end
77 easyvend.free_slots= function(inv, listname)
78 local size = inv:get_size(listname)
79 local free = 0
80 for i=1,size do
81 local stack = inv:get_stack(listname, i)
82 if stack:is_empty() then
83 free = free + 1
84 end
85 end
86 return free
87 end
89 easyvend.buysell = function(nodename)
90 local buysell = nil
91 if ( nodename == "easyvend:depositor" or nodename == "easyvend:depositor_on" ) then
92 buysell = "buy"
93 elseif ( nodename == "easyvend:vendor" or nodename == "easyvend:vendor_on" ) then
94 buysell = "sell"
95 end
96 return buysell
97 end
99 easyvend.set_formspec = function(pos, player)
100 local meta = minetest.get_meta(pos)
101 local node = minetest.get_node(pos)
103 local description = minetest.registered_nodes[node.name].description;
104 local number = meta:get_int("number")
105 local cost = meta:get_int("cost")
106 local bg = ""
107 local configmode = meta:get_int("configmode") == 1
108 if minetest.get_modpath("default") then
109 bg = default.gui_bg .. default.gui_bg_img .. default.gui_slots
112 local numbertext, costtext, buysellbuttontext
113 local itemcounttooltip = "Item count (append “s” to multiply with maximum stack size)"
114 local buysell = easyvend.buysell(node.name)
115 if buysell == "sell" then
116 numbertext = "Offered item"
117 costtext = "Price"
118 buysellbuttontext = "Buy"
119 elseif buysell == "buy" then
120 numbertext = "Requested item"
121 costtext = "Payment"
122 buysellbuttontext = "Sell"
123 else
124 return
126 local status = meta:get_string("status")
127 if status == "" then status = "Unknown." end
128 local message = meta:get_string("message")
129 if message == "" then message = "No message." end
130 local status_image
131 if node.name == "easyvend:vendor_on" or node.name == "easyvend:depositor_on" then
132 status_image = "easyvend_status_on.png"
133 else
134 status_image = "easyvend_status_off.png"
137 local formspec = "size[8,7.3;]"
138 .. bg
139 .."label[3,-0.2;" .. minetest.formspec_escape(description) .. "]"
141 .."image[7.5,0.2;0.5,1;" .. status_image .. "]"
142 .."textarea[2.8,0.2;5.1,2;;Status: " .. minetest.formspec_escape(status) .. ";]"
143 .."textarea[2.8,1.3;5.6,2;;Message: " .. minetest.formspec_escape(message) .. ";]"
145 .."label[0,-0.15;"..numbertext.."]"
146 .."label[0,1.2;"..costtext.."]"
147 .."list[current_player;main;0,3.5;8,4;]"
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;"..currency..";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 = "Accept 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 formspec = formspec .."checkbox[2,2.4;wear;"..minetest.formspec_escape(weartext)..";"..wear.."]"
171 .."tooltip[wear;"..minetest.formspec_escape(weartooltip).."]"
172 else
173 local itemname = meta:get_string("itemname")
174 formspec = formspec
175 .."item_image_button[0,1.65;1,1;"..currency..";currency_image;]"
176 .."item_image_button[0,0.35;1,1;"..itemname..";item_image;]"
177 .."label[1,1.85;×" .. cost .. "]"
178 .."label[1,0.55;×" .. number .. "]"
179 .."button[6,2.8;2,0.5;config;Configure]"
180 if buysell == "sell" then
181 formspec = formspec .. "tooltip[config;Configure offered items and price (only for owner)]"
182 else
183 formspec = formspec .. "tooltip[config;Configure requested items and payment (only for owner)]"
185 formspec = formspec .."button[0,2.8;2,0.5;buysell;"..buysellbuttontext.."]"
186 if minetest.registered_tools[itemname] ~= nil then
187 local weartext
188 if meta:get_int("wear") == 0 then
189 if buysell == "buy" then
190 weartext = "Only intact tools are bought."
191 else
192 weartext = "Only intact tools are sold."
194 elseif buysell == "sell" then
195 weartext = "Warning: Might sell worn tools."
197 if weartext ~= nil then
198 formspec = formspec .."textarea[2.3,2.6;3,1;;"..minetest.formspec_escape(weartext)..";]"
203 meta:set_string("formspec", formspec)
206 easyvend.machine_disable = function(pos, node, playername)
207 if node.name == "easyvend:vendor_on" then
208 easyvend.sound_disable(pos)
209 minetest.swap_node(pos, {name="easyvend:vendor", param2 = node.param2})
210 return true
211 elseif node.name == "easyvend:depositor_on" then
212 easyvend.sound_disable(pos)
213 minetest.swap_node(pos, {name="easyvend:depositor", param2 = node.param2})
214 return true
215 else
216 if playername ~= nil then
217 easyvend.sound_error(playername)
219 return false
223 easyvend.machine_enable = function(pos, node)
224 if node.name == "easyvend:vendor" then
225 easyvend.sound_setup(pos)
226 minetest.swap_node(pos, {name="easyvend:vendor_on", param2 = node.param2})
227 return true
228 elseif node.name == "easyvend:depositor" then
229 easyvend.sound_setup(pos)
230 minetest.swap_node(pos, {name="easyvend:depositor_on", param2 = node.param2})
231 return true
232 else
233 return false
237 easyvend.machine_check = function(pos, node)
238 local active = true
239 local status = "Ready."
241 local chest = minetest.get_node({x=pos.x,y=pos.y-1,z=pos.z})
242 local meta = minetest.get_meta(pos)
243 local number = meta:get_int("number")
244 local cost = meta:get_int("cost")
245 local inv = meta:get_inventory()
246 local itemstack = inv:get_stack("item",1)
247 local itemname=meta:get_string("itemname")
248 local machine_owner = meta:get_string("owner")
249 local check_wear = meta:get_int("wear") == 0
250 local chestdef = registered_chests[chest.name]
252 if chestdef then
253 local chest_meta = minetest.get_meta({x=pos.x,y=pos.y-1,z=pos.z})
254 local chest_inv = chest_meta:get_inventory()
256 if ( chest_meta:get_string(chestdef.meta_owner) == machine_owner and chest_inv ~= nil ) then
257 local buysell = easyvend.buysell(node.name)
259 if not itemstack:is_empty() then
261 local number_stack_max = itemstack:get_stack_max()
263 local stack = {name=itemname, count=number, wear=0, metadata=""}
264 local price = {name=currency, count=cost, wear=0, metadata=""}
266 local chest_has, chest_free
268 local coststacks = math.modf(cost / cost_stack_max)
269 local costremainder = math.fmod(cost, cost_stack_max)
270 local numberstacks = math.modf(number / number_stack_max)
271 local numberremainder = math.fmod(number, number_stack_max)
272 local numberfree = numberstacks
273 local costfree = coststacks
274 if numberremainder > 0 then numberfree = numberfree + 1 end
275 if costremainder > 0 then costfree = costfree + 1 end
277 if buysell == "sell" then
278 chest_has = easyvend.check_and_get_items(chest_inv, chestdef.inv_list, stack, check_wear)
279 chest_free = chest_inv:room_for_item(chestdef.inv_list, price)
280 if chest_has and chest_free then
281 if cost <= cost_stack_max and number <= number_stack_max then
282 active = true
283 elseif easyvend.free_slots(chest_inv, chestdef.inv_list) < costfree then
284 active = false
285 status = "No room in the machine’s storage!"
287 elseif not chest_has then
288 active = false
289 status = "The vending machine has insufficient materials!"
290 elseif not chest_free then
291 active = false
292 status = "No room in the machine’s storage!"
294 elseif buysell == "buy" then
295 chest_has = easyvend.check_and_get_items(chest_inv, chestdef.inv_list, price, check_wear)
296 chest_free = chest_inv:room_for_item(chestdef.inv_list, stack)
297 if chest_has and chest_free then
298 if cost <= cost_stack_max and number <= number_stack_max then
299 active = true
300 elseif easyvend.free_slots(chest_inv, chestdef.inv_list) < numberfree then
301 active = false
302 status = "No room in the machine’s storage!"
304 elseif not chest_has then
305 active = false
306 status = "The depositing machine is out of money!"
307 elseif not chest_free then
308 active = false
309 status = "No room in the machine’s storage!"
312 else
313 active = false
314 status = "Awaiting configuration by owner."
316 else
317 active = false
318 status = "Storage can’t be accessed because it is owned by a different person!"
320 else
321 active = false
322 status = "No storage; machine needs a locked chest below it."
324 if meta:get_int("configmode") == 1 then
325 active = false
326 status = "Awaiting configuration by owner."
329 if itemname == currency and number == cost and active then
330 local jt = meta:get_int("joketimer")
331 if jt > 0 then
332 jt = jt - 1
334 if jt == 0 then
335 meta:set_string("message", "Ready.")
336 jt = -1
338 meta:set_int("joketimer", jt)
340 meta:set_string("status", status)
342 if not itemstack:is_empty() then
343 if itemname == nil or itemname == "" then
344 itemname = itemstack:get_name()
346 meta:set_string("infotext", easyvend.make_infotext(node.name, machine_owner, cost, number, itemname))
348 itemname=itemstack:get_name()
349 meta:set_string("itemname", itemname)
351 local change
352 if node.name == "easyvend:vendor" or node.name == "easyvend:depositor" then
353 if active then change = easyvend.machine_enable(pos, node) end
354 elseif node.name == "easyvend:vendor_on" or node.name == "easyvend:depositor_on" then
355 if not active then change = easyvend.machine_disable(pos, node) end
357 easyvend.set_formspec(pos)
358 return change
361 easyvend.on_receive_fields_config = function(pos, formname, fields, sender)
362 local node = minetest.get_node(pos)
363 local meta = minetest.get_meta(pos)
364 local inv_self = meta:get_inventory()
365 local itemstack = inv_self:get_stack("item",1)
366 local buysell = easyvend.buysell(node.name)
368 if fields.config then
369 meta:set_int("configmode", 1)
370 easyvend.machine_check(pos, node)
371 return
374 if not fields.save then
375 return
378 local number = fields.number
379 local cost = fields.cost
381 --[[ Convenience function:
382 When appending “s” or “S” to the number, it is multiplied
383 by the maximum stack size.
384 TODO: Expose this in user documentation ]]
385 local number_stack_max = itemstack:get_stack_max()
386 local ss = string.sub(number, #number, #number)
387 if ss == "s" or ss == "S" then
388 local n = tonumber(string.sub(number, 1, #number-1))
389 if string.len(number) == 1 then n = 1 end
390 if n ~= nil then
391 number = n * number_stack_max
394 ss = string.sub(cost, #cost, #cost)
395 if ss == "s" or ss == "S" then
396 local n = tonumber(string.sub(cost, 1, #cost-1))
397 if string.len(cost) == 1 then n = 1 end
398 if n ~= nil then
399 cost = n * cost_stack_max
402 number = tonumber(number)
403 cost = tonumber(cost)
405 local itemname=""
407 local oldnumber = meta:get_int("number")
408 local oldcost = meta:get_int("cost")
409 local maxnumber = number_stack_max * slots_max
411 if ( itemstack == nil or itemstack:is_empty() ) then
412 meta:set_string("status", "Awaiting configuration by owner.")
413 meta:set_string("message", "You must specify an item.")
414 easyvend.sound_error(sender:get_player_name())
415 easyvend.set_formspec(pos, sender)
416 return
417 elseif ( number == nil or number < 1 or number > maxnumber ) then
418 if maxnumber > 1 then
419 meta:set_string("message", string.format("Invalid item count; must be between 1 and %d!", maxnumber))
420 else
421 meta:set_string("message", "Invalid item count; must be exactly 1!")
423 meta:set_int("number", oldnumber)
424 easyvend.sound_error(sender:get_player_name())
425 easyvend.set_formspec(pos, sender)
426 return
427 elseif ( cost == nil or cost < 1 or cost > maxcost ) then
428 if maxcost > 1 then
429 meta:set_string("message", string.format("Invalid cost; must be between 1 and %d!", maxcost))
430 else
431 meta:set_string("message", "Invalid cost; must be exactly 1!")
433 meta:set_int("cost", oldcost)
434 easyvend.sound_error(sender:get_player_name())
435 easyvend.set_formspec(pos, sender)
436 return
438 meta:set_int("number", number)
439 meta:set_int("cost", cost)
440 itemname=itemstack:get_name()
441 meta:set_string("itemname", itemname)
442 meta:set_int("configmode", 0)
444 local change = easyvend.machine_check(pos, node)
446 if itemname == currency and number == cost and cost <= cost_stack_max then
447 meta:set_string("message", "Configuration successful. I am feeling funny.")
448 meta:set_int("joketimer", joketimer_start)
449 meta:set_int("joke_id", easyvend.assign_joke(buysell))
450 else
451 meta:set_string("message", "Configuration successful.")
454 if not change then
455 if (node.name == "easyvend:vendor_on" or node.name == "easyvend:depositor_on") then
456 easyvend.sound_setup(pos)
457 meta:set_string("status", "Ready.")
458 else
459 easyvend.sound_disable(pos)
462 easyvend.machine_check(pos, node)
465 easyvend.make_infotext = function(nodename, owner, cost, number, itemstring)
466 local iname = minetest.registered_items[itemstring].description
467 if iname == nil then iname = itemstring end
468 local d = ""
469 local printitem, printcost
470 if number == 1 then
471 printitem = iname
472 else
473 printitem = string.format("%d×%s", number, iname)
475 if cost == 1 then
476 printcost = currency_desc
477 else
478 printcost = string.format("%d×%s", cost, currency_desc)
480 if nodename == "easyvend:vendor" or nodename == "easyvend:vendor_on" then
481 d = string.format("Vending machine (owned by %s)\nSelling: %s\nPrice: %s", owner, printitem, printcost)
482 elseif nodename == "easyvend:depositor" or nodename == "easyvend:depositor_on" then
483 d = string.format("Depositing machine (owned by %s)\nBuying: %s\nPayment: %s", owner, printitem, printcost)
485 return d
488 easyvend.on_receive_fields_buysell = function(pos, formname, fields, sender)
489 local sendername = sender:get_player_name()
490 local meta = minetest.get_meta(pos)
492 if not fields.buysell then
493 return
496 local node = minetest.get_node(pos)
497 local number = meta:get_int("number")
498 local cost = meta:get_int("cost")
499 local itemname=meta:get_string("itemname")
500 local item=meta:get_inventory():get_stack("item", 1)
501 local check_wear = meta:get_int("wear") == 0 and minetest.registered_tools[itemname] ~= nil
503 local buysell = easyvend.buysell(node.name)
505 local number_stack_max = item:get_stack_max()
506 local maxnumber = number_stack_max * slots_max
507 if ( number == nil or number < 1 or number > maxnumber ) or
508 ( cost == nil or cost < 1 or cost > maxcost ) or
509 ( itemname == nil or itemname=="") then
510 meta:set_string("status", "Invalid item count or price!")
511 easyvend.machine_disable(pos, node, sendername)
512 return
516 local chest = minetest.get_node({x=pos.x,y=pos.y-1,z=pos.z})
517 local chestdef = registered_chests[chest.name]
518 if chestdef and sender and sender:is_player() then
519 local chest_meta = minetest.get_meta({x=pos.x,y=pos.y-1,z=pos.z})
520 local chest_inv = chest_meta:get_inventory()
521 local player_inv = sender:get_inventory()
522 if ( chest_meta:get_string(chestdef.meta_owner) == meta:get_string("owner") and chest_inv ~= nil and player_inv ~= nil ) then
524 local stack = {name=itemname, count=number, wear=0, metadata=""}
525 local price = {name=currency, count=cost, wear=0, metadata=""}
526 local chest_has, player_has, chest_free, player_free, chest_out, player_out
527 local msg = ""
528 if buysell == "sell" then
529 chest_has, chest_out = easyvend.check_and_get_items(chest_inv, "main", stack, check_wear)
530 player_has, player_out = easyvend.check_and_get_items(player_inv, "main", price, check_wear)
531 chest_free = chest_inv:room_for_item("main", price)
532 player_free = player_inv:room_for_item("main", stack)
533 if chest_has and player_has and chest_free and player_free then
534 if cost <= cost_stack_max and number <= number_stack_max then
535 easyvend.machine_enable(pos, node)
536 player_inv:remove_item("main", price)
537 if check_wear then
538 chest_inv:set_stack("main", chest_out[1].id, "")
539 player_inv:add_item("main", chest_out[1].item)
540 else
541 stack = chest_inv:remove_item("main", stack)
542 player_inv:add_item("main", stack)
544 chest_inv:add_item("main", price)
545 meta:set_string("status", "Ready.")
546 if itemname == currency and number == cost and cost <= cost_stack_max then
547 meta:set_string("message", easyvend.get_joke(buysell, meta:get_int("joke_id")))
548 meta:set_int("joketimer", joketimer_start)
549 else
550 meta:set_string("message", "Item bought.")
552 easyvend.sound_vend(pos)
553 else
554 -- Large item counts (multiple stacks)
555 local coststacks = math.modf(cost / cost_stack_max)
556 local costremainder = math.fmod(cost, cost_stack_max)
557 local numberstacks = math.modf(number / number_stack_max)
558 local numberremainder = math.fmod(number, number_stack_max)
559 local numberfree = numberstacks
560 local costfree = coststacks
561 if numberremainder > 0 then numberfree = numberfree + 1 end
562 if costremainder > 0 then costfree = costfree + 1 end
563 if easyvend.free_slots(player_inv, "main") < numberfree then
564 if numberfree > 1 then
565 msg = string.format("No room in your inventory (%d empty slots required)!", numberfree)
566 else
567 msg = "No room in your inventory!"
569 meta:set_string("message", msg)
570 elseif easyvend.free_slots(chest_inv, "main") < costfree then
571 meta:set_string("status", "No room in the machine’s storage!")
572 easyvend.machine_disable(pos, node, sendername)
573 else
574 -- Remember items for transfer
575 local cheststacks = {}
576 easyvend.machine_enable(pos, node)
577 for i=1, coststacks do
578 price.count = cost_stack_max
579 player_inv:remove_item("main", price)
581 if costremainder > 0 then
582 price.count = costremainder
583 player_inv:remove_item("main", price)
585 if check_wear then
586 for o=1,#chest_out do
587 chest_inv:set_stack("main", chest_out[o].id, "")
589 else
590 for i=1, numberstacks do
591 stack.count = number_stack_max
592 table.insert(cheststacks, chest_inv:remove_item("main", stack))
595 if numberremainder > 0 then
596 stack.count = numberremainder
597 table.insert(cheststacks, chest_inv:remove_item("main", stack))
599 for i=1, coststacks do
600 price.count = cost_stack_max
601 chest_inv:add_item("main", price)
603 if costremainder > 0 then
604 price.count = costremainder
605 chest_inv:add_item("main", price)
607 if check_wear then
608 for o=1,#chest_out do
609 player_inv:add_item("main", chest_out[o].item)
611 else
612 for i=1,#cheststacks do
613 player_inv:add_item("main", cheststacks[i])
616 meta:set_string("message", "Item bought.")
617 easyvend.sound_vend(pos)
620 elseif chest_has and player_has then
621 if not player_free then
622 msg = "No room in your inventory!"
623 meta:set_string("message", msg)
624 easyvend.sound_error(sendername)
625 elseif not chest_free then
626 msg = "No room in the machine’s storage!"
627 meta:set_string("status", msg)
628 easyvend.machine_disable(pos, node, sendername)
630 else
631 if not chest_has then
632 msg = "The vending machine has insufficient materials!"
633 meta:set_string("status", msg)
634 easyvend.machine_disable(pos, node, sendername)
635 elseif not player_has then
636 msg = "You can’t afford this item!"
637 meta:set_string("message", msg)
638 easyvend.sound_error(sendername)
641 else
642 chest_has, chest_out = easyvend.check_and_get_items(chest_inv, "main", price, check_wear)
643 player_has, player_out = easyvend.check_and_get_items(player_inv, "main", stack, check_wear)
644 chest_free = chest_inv:room_for_item("main", stack)
645 player_free = player_inv:room_for_item("main", price)
646 if chest_has and player_has and chest_free and player_free then
647 if cost <= cost_stack_max and number <= number_stack_max then
648 easyvend.machine_enable(pos, node)
649 if check_wear then
650 player_inv:set_stack("main", player_out[1].id, "")
651 chest_inv:add_item("main", player_out[1].item)
652 else
653 stack = player_inv:remove_item("main", stack)
654 chest_inv:add_item("main", stack)
656 chest_inv:remove_item("main", price)
657 player_inv:add_item("main", price)
658 meta:set_string("status", "Ready.")
659 if itemname == currency and number == cost and cost <= cost_stack_max then
660 meta:set_string("message", easyvend.get_joke(buysell, meta:get_int("joke_id")))
661 meta:set_int("joketimer", joketimer_start)
662 else
663 meta:set_string("message", "Item sold.")
665 easyvend.sound_deposit(pos)
666 else
667 -- Large item counts (multiple stacks)
668 local coststacks = math.modf(cost / cost_stack_max)
669 local costremainder = math.fmod(cost, cost_stack_max)
670 local numberstacks = math.modf(number / number_stack_max)
671 local numberremainder = math.fmod(number, number_stack_max)
672 local numberfree = numberstacks
673 local costfree = coststacks
674 if numberremainder > 0 then numberfree = numberfree + 1 end
675 if costremainder > 0 then costfree = costfree + 1 end
676 if easyvend.free_slots(player_inv, "main") < costfree then
677 if costfree > 1 then
678 msg = string.format("No room in your inventory (%d empty slots required)!", costfree)
679 else
680 msg = "No room in your inventory!"
682 meta:set_string("status", msg)
683 easyvend.sound_error(sendername)
684 elseif easyvend.free_slots(chest_inv, "main") < numberfree then
685 easyvend.machine_disable(pos, node, sendername)
686 else
687 easyvend.machine_enable(pos, node)
688 -- Remember removed items for transfer
689 local playerstacks = {}
690 for i=1, coststacks do
691 price.count = cost_stack_max
692 chest_inv:remove_item("main", price)
694 if costremainder > 0 then
695 price.count = costremainder
696 chest_inv:remove_item("main", price)
698 if check_wear then
699 for o=1,#player_out do
700 player_inv:set_stack("main", player_out[o].id, "")
702 else
703 for i=1, numberstacks do
704 stack.count = number_stack_max
705 table.insert(playerstacks, player_inv:remove_item("main", stack))
708 if numberremainder > 0 then
709 stack.count = numberremainder
710 table.insert(playerstacks, player_inv:remove_item("main", stack))
712 for i=1, coststacks do
713 price.count = cost_stack_max
714 player_inv:add_item("main", price)
716 if costremainder > 0 then
717 price.count = costremainder
718 player_inv:add_item("main", price)
720 if check_wear then
721 for o=1,#player_out do
722 chest_inv:add_item("main", player_out[o].item)
724 else
725 for i=1,#playerstacks do
726 chest_inv:add_item("main", playerstacks[i])
729 meta:set_string("message", "Item sold.")
730 easyvend.sound_deposit(pos)
733 elseif chest_has and player_has then
734 if not player_free then
735 msg = "No room in your inventory!"
736 meta:set_string("message", msg)
737 easyvend.sound_error(sendername)
738 elseif not chest_free then
739 msg = "No room in the machine’s storage!"
740 meta:set_string("status", msg)
741 easyvend.machine_disable(pos, node, sendername)
743 else
744 if not player_has then
745 msg = "You have insufficient materials!"
746 meta:set_string("message", msg)
747 easyvend.sound_error(sendername)
748 elseif not chest_has then
749 msg = "The depositing machine is out of money!"
750 meta:set_string("status", msg)
751 easyvend.machine_disable(pos, node, sendername)
755 else
756 meta:set_string("status", "Storage can’t be accessed because it is owned by a different person!")
757 easyvend.machine_disable(pos, node, sendername)
759 else
760 if sender and sender:is_player() then
761 meta:set_string("status", "No storage; machine needs a locked chest below it.")
762 easyvend.machine_disable(pos, node, sendername)
766 easyvend.set_formspec(pos, sender)
770 easyvend.after_place_node = function(pos, placer)
771 local node = minetest.get_node(pos)
772 local meta = minetest.get_meta(pos)
773 local inv = meta:get_inventory()
774 local player_name = placer:get_player_name()
775 inv:set_size("item", 1)
776 inv:set_size("gold", 1)
778 inv:set_stack( "gold", 1, currency )
780 local d = ""
781 if node.name == "easyvend:vendor" then
782 d = string.format("New vending machine (owned by %s)", player_name)
783 meta:set_int("wear", 1)
784 elseif node.name == "easyvend:depositor" then
785 d = string.format("New depositing machine (owned by %s)", player_name)
786 meta:set_int("wear", 0)
788 meta:set_string("infotext", d)
789 local chest = minetest.get_node({x=pos.x,y=pos.y-1,z=pos.z})
790 if registered_chests[chest.name] then
791 meta:set_string("status", "Awaiting configuration by owner.")
792 else
793 meta:set_string("status", "No storage; machine needs a locked chest below it.")
795 meta:set_string("message", "Welcome! Please prepare the machine.")
796 meta:set_int("number", 1)
797 meta:set_int("cost", 1)
798 meta:set_int("configmode", 1)
799 meta:set_int("joketimer", -1)
800 meta:set_int("joke_id", 1)
801 meta:set_string("itemname", "")
803 meta:set_string("owner", player_name or "")
805 easyvend.set_formspec(pos, placer)
808 easyvend.can_dig = function(pos, player)
809 local meta = minetest.get_meta(pos)
810 local name = player:get_player_name()
811 -- Owner can always dig shop
812 if meta:get_string("owner") == name then
813 return true
815 local chest = minetest.get_node({x=pos.x,y=pos.y-1,z=pos.z})
816 local meta_chest = minetest.get_meta({x=pos.x,y=pos.y-1,z=pos.z});
817 if registered_chests[chest.name] then
818 if player and player:is_player() then
819 local owner_chest = meta_chest:get_string(registered_chests[chest.name].meta_owner)
820 if name == owner_chest then
821 return true --chest owner can also dig shop
824 return false
825 else
826 return true --if no chest, enyone can dig this shop
830 easyvend.on_receive_fields = function(pos, formname, fields, sender)
831 local meta = minetest.get_meta(pos)
832 local node = minetest.get_node(pos)
833 local owner = meta:get_string("owner")
834 local sendername = sender:get_player_name(sender)
836 if fields.config or fields.save or fields.usermode then
837 if sender:get_player_name() == owner then
838 easyvend.on_receive_fields_config(pos, formname, fields, sender)
839 else
840 meta:set_string("message", "Only owner may change the configuration.")
841 easyvend.sound_error(sendername)
842 easyvend.set_formspec(pos, sender)
843 return
845 elseif fields.wear ~= nil then
846 if sender:get_player_name() == owner then
847 if fields.wear == "true" then
848 if easyvend.buysell(node.name) == "buy" then
849 meta:set_string("message", "Used tools are now accepted.")
850 else
851 meta:set_string("message", "Used tools are now for sale.")
853 meta:set_int("wear", 1)
854 elseif fields.wear == "false" then
855 if easyvend.buysell(node.name) == "buy" then
856 meta:set_string("message", "Used tools are now rejected.")
857 else
858 meta:set_string("message", "Used tools won’t be sold anymore.")
860 meta:set_int("wear", 0)
862 easyvend.set_formspec(pos, sender)
863 return
864 else
865 meta:set_string("message", "Only owner may change the configuration.")
866 easyvend.sound_error(sendername)
867 easyvend.set_formspec(pos, sender)
868 return
870 elseif fields.buysell then
871 easyvend.on_receive_fields_buysell(pos, formname, fields, sender)
875 -- Jokes: Appear when machine exchanges currency for currency at equal rate
877 -- Vendor
878 local jokes_vendor = {
879 "Thank you. You have made a vending machine very happy.",
880 "Humans have a strange sense of humor.",
881 "Let’s get this over with …",
882 "Item “bought”.",
883 "Tit for tat.",
884 "Do you realize what you’ve just bought?",
886 -- Depositor
887 local jokes_depositor = {
888 "Thank you, the money started to smell inside.",
889 "Money doesn’t grow on trees, you know?",
890 "Sanity sold.",
891 "Well, that was an awkward exchange.",
892 "Are you having fun?",
893 "Is this really trading?",
896 easyvend.assign_joke = function(buysell)
897 local jokes
898 if buysell == "sell" then
899 jokes = jokes_vendor
900 elseif buysell == "buy" then
901 jokes = jokes_depositor
903 local r = math.random(1,#jokes)
904 return r
907 easyvend.get_joke = function(buysell, id)
908 if buysell == nil or id == nil then
909 -- Fallback message (should never happen)
910 return "Items exchanged."
912 if buysell == "sell" then
913 joke = jokes_vendor[id]
914 if joke == nil then joke = jokes_vendor[1] end
915 elseif buysell == "buy" then
916 joke = jokes_depositor[id]
917 if joke == nil then joke = jokes_depositor[1] end
919 return joke
922 easyvend.sound_error = function(playername)
923 minetest.sound_play("easyvend_error", {to_player = playername, gain = 0.25})
926 easyvend.sound_setup = function(pos)
927 minetest.sound_play("easyvend_activate", {pos = pos, gain = 0.5, max_hear_distance = 12,})
930 easyvend.sound_disable = function(pos)
931 minetest.sound_play("easyvend_disable", {pos = pos, gain = 0.9, max_hear_distance = 12,})
934 easyvend.sound_vend = function(pos)
935 minetest.sound_play("easyvend_vend", {pos = pos, gain = 0.4, max_hear_distance = 5,})
938 easyvend.sound_deposit = function(pos)
939 minetest.sound_play("easyvend_deposit", {pos = pos, gain = 0.4, max_hear_distance = 5,})
942 easyvend.allow_metadata_inventory_put = function(pos, listname, index, stack, player)
943 if listname=="item" then
944 local meta = minetest.get_meta(pos);
945 local owner = meta:get_string("owner")
946 local name = player:get_player_name()
947 if name == owner then
948 local inv = meta:get_inventory()
949 if stack==nil then
950 inv:set_stack( "item", 1, nil )
951 else
952 inv:set_stack( "item", 1, stack:get_name() )
956 return 0
959 easyvend.allow_metadata_inventory_take = function(pos, listname, index, stack, player)
960 return 0
963 easyvend.allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
964 return 0
967 minetest.register_abm({
968 nodenames = {"easyvend:vendor", "easyvend:vendor_on", "easyvend:depositor", "easyvend:depositor_on"},
969 interval = 5,
970 chance = 1,
971 catch_up = false,
972 action = function(pos, node, active_object_count, active_object_count_wider)
973 easyvend.machine_check(pos, node)