Implement basic towers
[minetest_easyvend.git] / easyvend.lua
blob6e81ffa0b59aa2e229e5d3e0a3cfcb44aa4a5a55
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 traversable_node_types = {
25 ["easyvend:vendor"] = true,
26 ["easyvend:depositor"] = true,
27 ["easyvend:vendor_on"] = true,
28 ["easyvend:depositor_on"] = true,
30 local registered_chests = {}
31 local cost_stack_max = minetest.registered_items[easyvend.currency].stack_max
32 local maxcost = cost_stack_max * slots_max
34 local joketimer_start = 3
36 -- Allow for other mods to register custom chests
37 easyvend.register_chest = function(node_name, inv_list, meta_owner)
38 registered_chests[node_name] = { inv_list = inv_list, meta_owner = meta_owner }
39 traversable_node_types[node_name] = true
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.is_active = function(nodename)
100 if ( nodename == "easyvend:depositor_on" or nodename == "easyvend:vendor_on" ) then
101 return true
102 elseif ( nodename == "easyvend:depositor" or nodename == "easyvend:vendor" ) then
103 return false
104 else
105 return nil
109 easyvend.set_formspec = function(pos, player)
110 local meta = minetest.get_meta(pos)
111 local node = minetest.get_node(pos)
113 local description = minetest.registered_nodes[node.name].description;
114 local number = meta:get_int("number")
115 local cost = meta:get_int("cost")
116 local itemname = meta:get_string("itemname")
117 local bg = ""
118 local configmode = meta:get_int("configmode") == 1
119 if minetest.get_modpath("default") then
120 bg = default.gui_bg .. default.gui_bg_img .. default.gui_slots
123 local numbertext, costtext, buysellbuttontext
124 local itemcounttooltip = "Item count (append “s” to multiply with maximum stack size)"
125 local buysell = easyvend.buysell(node.name)
126 if buysell == "sell" then
127 numbertext = "Offered item"
128 costtext = "Price"
129 buysellbuttontext = "Buy"
130 elseif buysell == "buy" then
131 numbertext = "Requested item"
132 costtext = "Payment"
133 buysellbuttontext = "Sell"
134 else
135 return
137 local status = meta:get_string("status")
138 if status == "" then status = "Unknown." end
139 local message = meta:get_string("message")
140 if message == "" then message = "No message." end
141 local status_image
142 if node.name == "easyvend:vendor_on" or node.name == "easyvend:depositor_on" then
143 status_image = "easyvend_status_on.png"
144 else
145 status_image = "easyvend_status_off.png"
148 -- TODO: Expose number of items in stock
150 local formspec = "size[8,7.3;]"
151 .. bg
152 .."label[3,-0.2;" .. minetest.formspec_escape(description) .. "]"
154 .."image[7.5,0.2;0.5,1;" .. status_image .. "]"
155 .."textarea[2.8,0.2;5.1,2;;Status: " .. minetest.formspec_escape(status) .. ";]"
156 .."textarea[2.8,1.3;5.6,2;;Message: " .. minetest.formspec_escape(message) .. ";]"
158 .."label[0,-0.15;"..numbertext.."]"
159 .."label[0,1.2;"..costtext.."]"
160 .."list[current_player;main;0,3.5;8,4;]"
162 if configmode then
163 local wear = "false"
164 if meta:get_int("wear") == 1 then wear = "true" end
165 formspec = formspec
166 .."item_image_button[0,1.65;1,1;"..easyvend.currency..";easyvend.currency_image;]"
167 .."list[current_name;item;0,0.35;1,1;]"
168 .."listring[current_player;main]"
169 .."listring[current_name;item]"
170 .."field[1.3,0.65;1.5,1;number;;" .. number .. "]"
171 .."tooltip[number;"..itemcounttooltip.."]"
172 .."field[1.3,1.95;1.5,1;cost;;" .. cost .. "]"
173 .."tooltip[cost;"..itemcounttooltip.."]"
174 .."button[6,2.8;2,0.5;save;Confirm]"
175 .."tooltip[save;Confirm configuration and activate machine (only for owner)]"
176 local weartext, weartooltip
177 if buysell == "buy" then
178 weartext = "Buy worn tools"
179 weartooltip = "If disabled, only tools in perfect condition will be bought from sellers (only settable by owner)"
180 else
181 weartext = "Sell worn tools"
182 weartooltip = "If disabled, only tools in perfect condition will be sold (only settable by owner)"
184 if minetest.registered_tools[itemname] ~= nil then
185 formspec = formspec .."checkbox[2,2.4;wear;"..minetest.formspec_escape(weartext)..";"..wear.."]"
186 .."tooltip[wear;"..minetest.formspec_escape(weartooltip).."]"
188 else
189 formspec = formspec
190 .."item_image_button[0,1.65;1,1;"..easyvend.currency..";easyvend.currency_image;]"
191 .."item_image_button[0,0.35;1,1;"..itemname..";item_image;]"
192 .."label[1,1.85;×" .. cost .. "]"
193 .."label[1,0.55;×" .. number .. "]"
194 .."button[6,2.8;2,0.5;config;Configure]"
195 if buysell == "sell" then
196 formspec = formspec .. "tooltip[config;Configure offered items and price (only for owner)]"
197 else
198 formspec = formspec .. "tooltip[config;Configure requested items and payment (only for owner)]"
200 formspec = formspec .."button[0,2.8;2,0.5;buysell;"..buysellbuttontext.."]"
201 if minetest.registered_tools[itemname] ~= nil then
202 local weartext
203 if meta:get_int("wear") == 0 then
204 if buysell == "buy" then
205 weartext = "Only intact tools are bought."
206 else
207 weartext = "Only intact tools are sold."
209 else
210 if buysell == "sell" then
211 weartext = "Warning: Might sell worn tools."
212 else
213 weartext = "Worn tools are bought, too."
216 if weartext ~= nil then
217 formspec = formspec .."textarea[2.3,2.6;3,1;;"..minetest.formspec_escape(weartext)..";]"
222 meta:set_string("formspec", formspec)
225 easyvend.machine_disable = function(pos, node, playername)
226 if node.name == "easyvend:vendor_on" then
227 easyvend.sound_disable(pos)
228 minetest.swap_node(pos, {name="easyvend:vendor", param2 = node.param2})
229 return true
230 elseif node.name == "easyvend:depositor_on" then
231 easyvend.sound_disable(pos)
232 minetest.swap_node(pos, {name="easyvend:depositor", param2 = node.param2})
233 return true
234 else
235 if playername ~= nil then
236 easyvend.sound_error(playername)
238 return false
242 easyvend.machine_enable = function(pos, node)
243 if node.name == "easyvend:vendor" then
244 easyvend.sound_setup(pos)
245 minetest.swap_node(pos, {name="easyvend:vendor_on", param2 = node.param2})
246 return true
247 elseif node.name == "easyvend:depositor" then
248 easyvend.sound_setup(pos)
249 minetest.swap_node(pos, {name="easyvend:depositor_on", param2 = node.param2})
250 return true
251 else
252 return false
256 easyvend.machine_check = function(pos, node)
257 local active = true
258 local status = "Ready."
260 local meta = minetest.get_meta(pos)
262 local machine_owner = meta:get_string("owner")
263 local number = meta:get_int("number")
264 local cost = meta:get_int("cost")
265 local itemname = meta:get_string("itemname")
266 local check_wear = meta:get_int("wear") == 0
267 local inv = meta:get_inventory()
268 local itemstack = inv:get_stack("item",1)
269 local buysell = easyvend.buysell(node.name)
271 local chest_pos = easyvend.find_connected_chest(machine_owner, pos, itemname, number, buysell == "sell")
272 local chest, chestdef, chest_meta, chest_inv
273 if chest_pos ~= nil then
274 chest = minetest.get_node(chest_pos)
275 chestdef = registered_chests[chest.name]
278 if chestdef then
279 chest_meta = minetest.get_meta(chest_pos)
280 chest_inv = chest_meta:get_inventory()
282 if ( chest_meta:get_string(chestdef.meta_owner) == machine_owner and chest_inv ~= nil ) then
284 local checkstack, checkitem
285 if buysell == "buy" then
286 checkitem = easyvend.currency
287 else
288 checkitem = itemname
290 local stock = 0
291 -- Count stock
292 -- FIXME: Ignore tools with bad wear level
293 for i=1,chest_inv:get_size(chestdef.inv_list) do
294 checkstack = chest_inv:get_stack(chestdef.inv_list, i)
295 if checkstack:get_name() == checkitem then
296 stock = stock + checkstack:get_count()
299 meta:set_int("stock", stock)
301 if not itemstack:is_empty() then
302 local number_stack_max = itemstack:get_stack_max()
303 local maxnumber = number_stack_max * slots_max
304 if number >= 1 and number <= maxnumber and cost >= 1 and cost <= maxcost then
305 local stack = {name=itemname, count=number, wear=0, metadata=""}
306 local price = {name=easyvend.currency, count=cost, wear=0, metadata=""}
308 local chest_has, chest_free
310 local coststacks = math.modf(cost / cost_stack_max)
311 local costremainder = math.fmod(cost, cost_stack_max)
312 local numberstacks = math.modf(number / number_stack_max)
313 local numberremainder = math.fmod(number, number_stack_max)
314 local numberfree = numberstacks
315 local costfree = coststacks
316 if numberremainder > 0 then numberfree = numberfree + 1 end
317 if costremainder > 0 then costfree = costfree + 1 end
319 if buysell == "sell" then
320 chest_has = easyvend.check_and_get_items(chest_inv, chestdef.inv_list, stack, check_wear)
321 chest_free = chest_inv:room_for_item(chestdef.inv_list, price)
322 if chest_has and chest_free then
323 if cost <= cost_stack_max and number <= number_stack_max then
324 active = true
325 elseif easyvend.free_slots(chest_inv, chestdef.inv_list) < costfree then
326 active = false
327 status = "No room in the machine’s storage!"
329 elseif not chest_has then
330 active = false
331 status = "The vending machine has insufficient materials!"
332 elseif not chest_free then
333 active = false
334 status = "No room in the machine’s storage!"
336 elseif buysell == "buy" then
337 chest_has = easyvend.check_and_get_items(chest_inv, chestdef.inv_list, price, check_wear)
338 chest_free = chest_inv:room_for_item(chestdef.inv_list, stack)
339 if chest_has and chest_free then
340 if cost <= cost_stack_max and number <= number_stack_max then
341 active = true
342 elseif easyvend.free_slots(chest_inv, chestdef.inv_list) < numberfree then
343 active = false
344 status = "No room in the machine’s storage!"
346 elseif not chest_has then
347 active = false
348 status = "The depositing machine is out of money!"
349 elseif not chest_free then
350 active = false
351 status = "No room in the machine’s storage!"
354 else
355 active = false
356 if buysell == "sell" then
357 status = "Invalid item count or price."
358 else
359 status = "Invalid item count or payment."
362 else
363 active = false
364 status = "Awaiting configuration by owner."
366 else
367 meta:set_int("stock", 0)
368 active = false
369 status = "Storage can’t be accessed because it is owned by a different person!"
371 else
372 meta:set_int("stock", 0)
373 active = false
374 status = "No storage; machine needs a locked chest below it."
376 if meta:get_int("configmode") == 1 then
377 active = false
378 status = "Awaiting configuration by owner."
381 if itemname == easyvend.currency and number == cost and active then
382 local jt = meta:get_int("joketimer")
383 if jt > 0 then
384 jt = jt - 1
386 if jt == 0 then
387 if buysell == "sell" then
388 meta:set_string("message", "Item bought.")
389 else
390 meta:set_string("message", "Item sold.")
392 jt = -1
394 meta:set_int("joketimer", jt)
396 meta:set_string("status", status)
398 meta:set_string("infotext", easyvend.make_infotext(node.name, machine_owner, cost, number, itemname))
399 itemname=itemstack:get_name()
400 meta:set_string("itemname", itemname)
402 local change
403 if node.name == "easyvend:vendor" or node.name == "easyvend:depositor" then
404 if active then change = easyvend.machine_enable(pos, node) end
405 elseif node.name == "easyvend:vendor_on" or node.name == "easyvend:depositor_on" then
406 if not active then change = easyvend.machine_disable(pos, node) end
408 easyvend.set_formspec(pos)
409 return change
412 easyvend.on_receive_fields_config = function(pos, formname, fields, sender)
413 local node = minetest.get_node(pos)
414 local meta = minetest.get_meta(pos)
415 local inv_self = meta:get_inventory()
416 local itemstack = inv_self:get_stack("item",1)
417 local buysell = easyvend.buysell(node.name)
419 if fields.config then
420 meta:set_int("configmode", 1)
421 local was_active = easyvend.is_active(node.name)
422 if was_active then
423 meta:set_string("message", "Configuration mode activated; machine disabled.")
424 else
425 meta:set_string("message", "Configuration mode activated.")
427 easyvend.machine_check(pos, node)
428 return
431 if not fields.save then
432 return
435 local number = fields.number
436 local cost = fields.cost
438 --[[ Convenience function:
439 When appending “s” or “S” to the number, it is multiplied
440 by the maximum stack size.
441 TODO: Expose this in user documentation ]]
442 local number_stack_max = itemstack:get_stack_max()
443 local ss = string.sub(number, #number, #number)
444 if ss == "s" or ss == "S" then
445 local n = tonumber(string.sub(number, 1, #number-1))
446 if string.len(number) == 1 then n = 1 end
447 if n ~= nil then
448 number = n * number_stack_max
451 ss = string.sub(cost, #cost, #cost)
452 if ss == "s" or ss == "S" then
453 local n = tonumber(string.sub(cost, 1, #cost-1))
454 if string.len(cost) == 1 then n = 1 end
455 if n ~= nil then
456 cost = n * cost_stack_max
459 number = tonumber(number)
460 cost = tonumber(cost)
462 local itemname=""
464 local oldnumber = meta:get_int("number")
465 local oldcost = meta:get_int("cost")
466 local maxnumber = number_stack_max * slots_max
468 if ( itemstack == nil or itemstack:is_empty() ) then
469 meta:set_string("status", "Awaiting configuration by owner.")
470 meta:set_string("message", "No item specified.")
471 easyvend.sound_error(sender:get_player_name())
472 easyvend.set_formspec(pos, sender)
473 return
474 elseif ( number == nil or number < 1 or number > maxnumber ) then
475 if maxnumber > 1 then
476 meta:set_string("message", string.format("Invalid item count; must be between 1 and %d!", maxnumber))
477 else
478 meta:set_string("message", "Invalid item count; must be exactly 1!")
480 meta:set_int("number", oldnumber)
481 easyvend.sound_error(sender:get_player_name())
482 easyvend.set_formspec(pos, sender)
483 return
484 elseif ( cost == nil or cost < 1 or cost > maxcost ) then
485 if maxcost > 1 then
486 meta:set_string("message", string.format("Invalid cost; must be between 1 and %d!", maxcost))
487 else
488 meta:set_string("message", "Invalid cost; must be exactly 1!")
490 meta:set_int("cost", oldcost)
491 easyvend.sound_error(sender:get_player_name())
492 easyvend.set_formspec(pos, sender)
493 return
495 meta:set_int("number", number)
496 meta:set_int("cost", cost)
497 itemname=itemstack:get_name()
498 meta:set_string("itemname", itemname)
499 meta:set_int("configmode", 0)
501 if itemname == easyvend.currency and number == cost and cost <= cost_stack_max then
502 meta:set_string("message", "Configuration successful. I am feeling funny.")
503 meta:set_int("joketimer", joketimer_start)
504 meta:set_int("joke_id", easyvend.assign_joke(buysell))
505 else
506 meta:set_string("message", "Configuration successful.")
509 local change = easyvend.machine_check(pos, node)
511 if not change then
512 if (node.name == "easyvend:vendor_on" or node.name == "easyvend:depositor_on") then
513 easyvend.sound_setup(pos)
514 else
515 easyvend.sound_disable(pos)
520 easyvend.make_infotext = function(nodename, owner, cost, number, itemstring)
521 local d = ""
522 if itemstring == nil or itemstring == "" or number == 0 or cost == 0 then
523 if easyvend.buysell(nodename) == "sell" then
524 d = string.format("Inactive vending machine (owned by %s)", owner)
525 else
526 d = string.format("Inactive depositing machine (owned by %s)", owner)
528 return d
530 local iname = minetest.registered_items[itemstring].description
531 if iname == nil then iname = itemstring end
532 local printitem, printcost
533 if number == 1 then
534 printitem = iname
535 else
536 printitem = string.format("%d×%s", number, iname)
538 if cost == 1 then
539 printcost = easyvend.currency_desc
540 else
541 printcost = string.format("%d×%s", cost, easyvend.currency_desc)
543 if nodename == "easyvend:vendor_on" then
544 d = string.format("Vending machine (owned by %s)\nSelling: %s\nPrice: %s", owner, printitem, printcost)
545 elseif nodename == "easyvend:vendor" then
546 d = string.format("Inactive vending machine (owned by %s)\nSelling: %s\nPrice: %s", owner, printitem, printcost)
547 elseif nodename == "easyvend:depositor_on" then
548 d = string.format("Depositing machine (owned by %s)\nBuying: %s\nPayment: %s", owner, printitem, printcost)
549 elseif nodename == "easyvend:depositor" then
550 d = string.format("Inactive depositing machine (owned by %s)\nBuying: %s\nPayment: %s", owner, printitem, printcost)
552 return d
555 easyvend.on_receive_fields_buysell = function(pos, formname, fields, sender)
556 local sendername = sender:get_player_name()
557 local meta = minetest.get_meta(pos)
559 if not fields.buysell then
560 return
563 local node = minetest.get_node(pos)
564 local number = meta:get_int("number")
565 local cost = meta:get_int("cost")
566 local itemname=meta:get_string("itemname")
567 local item=meta:get_inventory():get_stack("item", 1)
568 local check_wear = meta:get_int("wear") == 0 and minetest.registered_tools[itemname] ~= nil
570 local buysell = easyvend.buysell(node.name)
572 local number_stack_max = item:get_stack_max()
573 local maxnumber = number_stack_max * slots_max
574 if ( number == nil or number < 1 or number > maxnumber ) or
575 ( cost == nil or cost < 1 or cost > maxcost ) or
576 ( itemname == nil or itemname=="") then
577 meta:set_string("status", "Invalid item count or price!")
578 easyvend.machine_disable(pos, node, sendername)
579 return
582 local chest_pos = easyvend.find_connected_chest(sendername, pos, itemname, number, buysell == "sell")
583 local chest, chestdef
584 if chest_pos ~= nil then
585 chest = minetest.get_node(chest_pos)
586 chestdef = registered_chests[chest.name]
588 if chestdef and sender and sender:is_player() then
589 local chest_meta = minetest.get_meta(chest_pos)
590 local chest_inv = chest_meta:get_inventory()
591 local player_inv = sender:get_inventory()
592 if ( chest_meta:get_string(chestdef.meta_owner) == meta:get_string("owner") and chest_inv ~= nil and player_inv ~= nil ) then
594 local stack = {name=itemname, count=number, wear=0, metadata=""}
595 local price = {name=easyvend.currency, count=cost, wear=0, metadata=""}
596 local chest_has, player_has, chest_free, player_free, chest_out, player_out
597 local msg = ""
598 if buysell == "sell" then
599 chest_has, chest_out = easyvend.check_and_get_items(chest_inv, "main", stack, check_wear)
600 player_has, player_out = easyvend.check_and_get_items(player_inv, "main", price, check_wear)
601 chest_free = chest_inv:room_for_item("main", price)
602 player_free = player_inv:room_for_item("main", stack)
603 if chest_has and player_has and chest_free and player_free then
604 if cost <= cost_stack_max and number <= number_stack_max then
605 easyvend.machine_enable(pos, node)
606 player_inv:remove_item("main", price)
607 if check_wear then
608 chest_inv:set_stack("main", chest_out[1].id, "")
609 player_inv:add_item("main", chest_out[1].item)
610 else
611 stack = chest_inv:remove_item("main", stack)
612 player_inv:add_item("main", stack)
614 chest_inv:add_item("main", price)
615 if itemname == easyvend.currency and number == cost and cost <= cost_stack_max then
616 meta:set_string("message", easyvend.get_joke(buysell, meta:get_int("joke_id")))
617 meta:set_int("joketimer", joketimer_start)
618 else
619 meta:set_string("message", "Item bought.")
621 easyvend.sound_vend(pos)
622 easyvend.machine_check(pos, node)
623 else
624 -- Large item counts (multiple stacks)
625 local coststacks = math.modf(cost / cost_stack_max)
626 local costremainder = math.fmod(cost, cost_stack_max)
627 local numberstacks = math.modf(number / number_stack_max)
628 local numberremainder = math.fmod(number, number_stack_max)
629 local numberfree = numberstacks
630 local costfree = coststacks
631 if numberremainder > 0 then numberfree = numberfree + 1 end
632 if costremainder > 0 then costfree = costfree + 1 end
633 if easyvend.free_slots(player_inv, "main") < numberfree then
634 if numberfree > 1 then
635 msg = string.format("No room in your inventory (%d empty slots required)!", numberfree)
636 else
637 msg = "No room in your inventory!"
639 meta:set_string("message", msg)
640 elseif easyvend.free_slots(chest_inv, "main") < costfree then
641 meta:set_string("status", "No room in the machine’s storage!")
642 easyvend.machine_disable(pos, node, sendername)
643 else
644 -- Remember items for transfer
645 local cheststacks = {}
646 easyvend.machine_enable(pos, node)
647 for i=1, coststacks do
648 price.count = cost_stack_max
649 player_inv:remove_item("main", price)
651 if costremainder > 0 then
652 price.count = costremainder
653 player_inv:remove_item("main", price)
655 if check_wear then
656 for o=1,#chest_out do
657 chest_inv:set_stack("main", chest_out[o].id, "")
659 else
660 for i=1, numberstacks do
661 stack.count = number_stack_max
662 table.insert(cheststacks, chest_inv:remove_item("main", stack))
665 if numberremainder > 0 then
666 stack.count = numberremainder
667 table.insert(cheststacks, chest_inv:remove_item("main", stack))
669 for i=1, coststacks do
670 price.count = cost_stack_max
671 chest_inv:add_item("main", price)
673 if costremainder > 0 then
674 price.count = costremainder
675 chest_inv:add_item("main", price)
677 if check_wear then
678 for o=1,#chest_out do
679 player_inv:add_item("main", chest_out[o].item)
681 else
682 for i=1,#cheststacks do
683 player_inv:add_item("main", cheststacks[i])
686 meta:set_string("message", "Item bought.")
687 easyvend.sound_vend(pos)
688 easyvend.machine_check(pos, node)
691 elseif chest_has and player_has then
692 if not player_free then
693 msg = "No room in your inventory!"
694 meta:set_string("message", msg)
695 easyvend.sound_error(sendername)
696 elseif not chest_free then
697 msg = "No room in the machine’s storage!"
698 meta:set_string("status", msg)
699 easyvend.machine_disable(pos, node, sendername)
701 else
702 if not chest_has then
703 msg = "The vending machine has insufficient materials!"
704 meta:set_string("status", msg)
705 easyvend.machine_disable(pos, node, sendername)
706 elseif not player_has then
707 msg = "You can’t afford this item!"
708 meta:set_string("message", msg)
709 easyvend.sound_error(sendername)
712 else
713 chest_has, chest_out = easyvend.check_and_get_items(chest_inv, "main", price, check_wear)
714 player_has, player_out = easyvend.check_and_get_items(player_inv, "main", stack, check_wear)
715 chest_free = chest_inv:room_for_item("main", stack)
716 player_free = player_inv:room_for_item("main", price)
717 if chest_has and player_has and chest_free and player_free then
718 if cost <= cost_stack_max and number <= number_stack_max then
719 easyvend.machine_enable(pos, node)
720 if check_wear then
721 player_inv:set_stack("main", player_out[1].id, "")
722 chest_inv:add_item("main", player_out[1].item)
723 else
724 stack = player_inv:remove_item("main", stack)
725 chest_inv:add_item("main", stack)
727 chest_inv:remove_item("main", price)
728 player_inv:add_item("main", price)
729 meta:set_string("status", "Ready.")
730 if itemname == easyvend.currency and number == cost and cost <= cost_stack_max then
731 meta:set_string("message", easyvend.get_joke(buysell, meta:get_int("joke_id")))
732 meta:set_int("joketimer", joketimer_start)
733 else
734 meta:set_string("message", "Item sold.")
736 easyvend.sound_deposit(pos)
737 easyvend.machine_check(pos, node)
738 else
739 -- Large item counts (multiple stacks)
740 local coststacks = math.modf(cost / cost_stack_max)
741 local costremainder = math.fmod(cost, cost_stack_max)
742 local numberstacks = math.modf(number / number_stack_max)
743 local numberremainder = math.fmod(number, number_stack_max)
744 local numberfree = numberstacks
745 local costfree = coststacks
746 if numberremainder > 0 then numberfree = numberfree + 1 end
747 if costremainder > 0 then costfree = costfree + 1 end
748 if easyvend.free_slots(player_inv, "main") < costfree then
749 if costfree > 1 then
750 msg = string.format("No room in your inventory (%d empty slots required)!", costfree)
751 else
752 msg = "No room in your inventory!"
754 meta:set_string("message", msg)
755 easyvend.sound_error(sendername)
756 elseif easyvend.free_slots(chest_inv, "main") < numberfree then
757 easyvend.machine_disable(pos, node, sendername)
758 else
759 easyvend.machine_enable(pos, node)
760 -- Remember removed items for transfer
761 local playerstacks = {}
762 for i=1, coststacks do
763 price.count = cost_stack_max
764 chest_inv:remove_item("main", price)
766 if costremainder > 0 then
767 price.count = costremainder
768 chest_inv:remove_item("main", price)
770 if check_wear then
771 for o=1,#player_out do
772 player_inv:set_stack("main", player_out[o].id, "")
774 else
775 for i=1, numberstacks do
776 stack.count = number_stack_max
777 table.insert(playerstacks, player_inv:remove_item("main", stack))
780 if numberremainder > 0 then
781 stack.count = numberremainder
782 table.insert(playerstacks, player_inv:remove_item("main", stack))
784 for i=1, coststacks do
785 price.count = cost_stack_max
786 player_inv:add_item("main", price)
788 if costremainder > 0 then
789 price.count = costremainder
790 player_inv:add_item("main", price)
792 if check_wear then
793 for o=1,#player_out do
794 chest_inv:add_item("main", player_out[o].item)
796 else
797 for i=1,#playerstacks do
798 chest_inv:add_item("main", playerstacks[i])
801 meta:set_string("message", "Item sold.")
802 easyvend.sound_deposit(pos)
803 easyvend.machine_check(pos, node)
806 elseif chest_has and player_has then
807 if not player_free then
808 msg = "No room in your inventory!"
809 meta:set_string("message", msg)
810 easyvend.sound_error(sendername)
811 elseif not chest_free then
812 msg = "No room in the machine’s storage!"
813 meta:set_string("status", msg)
814 easyvend.machine_disable(pos, node, sendername)
816 else
817 if not player_has then
818 msg = "You have insufficient materials!"
819 meta:set_string("message", msg)
820 easyvend.sound_error(sendername)
821 elseif not chest_has then
822 msg = "The depositing machine is out of money!"
823 meta:set_string("status", msg)
824 easyvend.machine_disable(pos, node, sendername)
828 else
829 meta:set_string("status", "Storage can’t be accessed because it is owned by a different person!")
830 easyvend.machine_disable(pos, node, sendername)
832 else
833 if sender and sender:is_player() then
834 meta:set_string("status", "No storage; machine needs a locked chest below it.")
835 easyvend.machine_disable(pos, node, sendername)
839 easyvend.set_formspec(pos, sender)
843 easyvend.after_place_node = function(pos, placer)
844 local node = minetest.get_node(pos)
845 local meta = minetest.get_meta(pos)
846 local inv = meta:get_inventory()
847 local player_name = placer:get_player_name()
848 inv:set_size("item", 1)
849 inv:set_size("gold", 1)
851 inv:set_stack( "gold", 1, easyvend.currency )
853 local d = ""
854 if node.name == "easyvend:vendor" then
855 d = string.format("Inactive vending machine (owned by %s)", player_name)
856 meta:set_int("wear", 1)
857 elseif node.name == "easyvend:depositor" then
858 d = string.format("Inactive depositing machine (owned by %s)", player_name)
859 meta:set_int("wear", 0)
861 meta:set_string("infotext", d)
862 meta:set_string("status", "Awaiting configuration by owner.")
863 meta:set_string("message", "Welcome! Please prepare the machine.")
864 meta:set_int("number", 1)
865 meta:set_int("cost", 1)
866 meta:set_int("stock", -1)
867 meta:set_int("configmode", 1)
868 meta:set_int("joketimer", -1)
869 meta:set_int("joke_id", 1)
870 meta:set_string("itemname", "")
872 meta:set_string("owner", player_name or "")
874 easyvend.set_formspec(pos, placer)
877 easyvend.can_dig = function(pos, player)
878 local meta = minetest.get_meta(pos)
879 local name = player:get_player_name()
880 local owner = meta:get_string("owner")
881 -- Owner can always dig shop
882 if owner == name then
883 return true
885 local chest_pos = easyvend.get_connected_chest(owner, pos)
886 local chest, meta_chest
887 if chest_pos then
888 chest = minetest.get_node(chest_pos)
889 meta_chest = minetest.get_meta(chest_pos)
891 if registered_chests[chest.name] then
892 if player and player:is_player() then
893 local owner_chest = meta_chest:get_string(registered_chests[chest.name].meta_owner)
894 if name == owner_chest then
895 return true --chest owner can also dig shop
898 return false
899 else
900 return true --if no chest, enyone can dig this shop
904 easyvend.on_receive_fields = function(pos, formname, fields, sender)
905 local meta = minetest.get_meta(pos)
906 local node = minetest.get_node(pos)
907 local owner = meta:get_string("owner")
908 local sendername = sender:get_player_name(sender)
910 if fields.config or fields.save or fields.usermode then
911 if sender:get_player_name() == owner then
912 easyvend.on_receive_fields_config(pos, formname, fields, sender)
913 else
914 meta:set_string("message", "Only the owner may change the configuration.")
915 easyvend.sound_error(sendername)
916 easyvend.set_formspec(pos, sender)
917 return
919 elseif fields.wear ~= nil then
920 if sender:get_player_name() == owner then
921 if fields.wear == "true" then
922 if easyvend.buysell(node.name) == "buy" then
923 meta:set_string("message", "Used tools are now accepted.")
924 else
925 meta:set_string("message", "Used tools are now for sale.")
927 meta:set_int("wear", 1)
928 elseif fields.wear == "false" then
929 if easyvend.buysell(node.name) == "buy" then
930 meta:set_string("message", "Used tools are now rejected.")
931 else
932 meta:set_string("message", "Used tools won’t be sold anymore.")
934 meta:set_int("wear", 0)
936 easyvend.set_formspec(pos, sender)
937 return
938 else
939 meta:set_string("message", "Only the owner may change the configuration.")
940 easyvend.sound_error(sendername)
941 easyvend.set_formspec(pos, sender)
942 return
944 elseif fields.buysell then
945 easyvend.on_receive_fields_buysell(pos, formname, fields, sender)
949 -- Jokes: Appear when machine exchanges currency for currency at equal rate
951 -- Vendor
952 local jokes_vendor = {
953 "Thank you. You have made a vending machine very happy.",
954 "Humans have a strange sense of humor.",
955 "Let’s get this over with …",
956 "Item “bought”.",
957 "Tit for tat.",
958 "Do you realize what you’ve just bought?",
960 -- Depositor
961 local jokes_depositor = {
962 "Thank you, the money started to smell inside.",
963 "Money doesn’t grow on trees, you know?",
964 "Sanity sold.",
965 "Well, that was an awkward exchange.",
966 "Are you having fun?",
967 "Is this really trading?",
970 easyvend.assign_joke = function(buysell)
971 local jokes
972 if buysell == "sell" then
973 jokes = jokes_vendor
974 elseif buysell == "buy" then
975 jokes = jokes_depositor
977 local r = math.random(1,#jokes)
978 return r
981 easyvend.get_joke = function(buysell, id)
982 local joke
983 if buysell == nil or id == nil then
984 -- Fallback message (should never happen)
985 return "Items exchanged."
987 if buysell == "sell" then
988 joke = jokes_vendor[id]
989 if joke == nil then joke = jokes_vendor[1] end
990 elseif buysell == "buy" then
991 joke = jokes_depositor[id]
992 if joke == nil then joke = jokes_depositor[1] end
994 return joke
997 easyvend.sound_error = function(playername)
998 minetest.sound_play("easyvend_error", {to_player = playername, gain = 0.25})
1001 easyvend.sound_setup = function(pos)
1002 minetest.sound_play("easyvend_activate", {pos = pos, gain = 0.5, max_hear_distance = 12,})
1005 easyvend.sound_disable = function(pos)
1006 minetest.sound_play("easyvend_disable", {pos = pos, gain = 0.9, max_hear_distance = 12,})
1009 easyvend.sound_vend = function(pos)
1010 minetest.sound_play("easyvend_vend", {pos = pos, gain = 0.4, max_hear_distance = 5,})
1013 easyvend.sound_deposit = function(pos)
1014 minetest.sound_play("easyvend_deposit", {pos = pos, gain = 0.4, max_hear_distance = 5,})
1017 --[[ Tower building ]]
1019 easyvend.is_traversable = function(pos)
1020 local node = minetest.get_node_or_nil(pos)
1021 if (node == nil) then
1022 return false
1024 return traversable_node_types[node.name] == true
1027 easyvend.neighboring_nodes = function(pos)
1028 local check = {
1029 {x=pos.x, y=pos.y+1, z=pos.z},
1030 {x=pos.x, y=pos.y-1, z=pos.z},
1032 local trav = {}
1033 for i=1,#check do
1034 if easyvend.is_traversable(check[i]) then
1035 trav[#trav+1] = check[i]
1038 return trav
1041 easyvend.find_connected_chest = function(owner, pos, nodename, amount, removing)
1042 local nodes = easyvend.neighboring_nodes(pos)
1044 if (#nodes < 1 or #nodes > 2) then
1045 return nil
1048 -- Find the stack direction
1049 local first = nil
1050 local second = nil
1051 for i=1,#nodes do
1052 if ( first == nil ) then
1053 first = nodes[i]
1054 else
1055 second = nodes[i]
1059 if (first ~= nil and second ~= nil) then
1060 local dx = (first.x - second.x)/2
1061 local dy = (first.y - second.y)/2
1062 local dz = (first.z - second.z)/2
1063 -- make sure they are in a column/row
1064 if ( (dx * dx + dy * dy + dz * dz) ~= 1 ) then
1065 return nil
1067 local chest_pos = easyvend.find_chest(owner, pos, dx, dy, dz, nodename, amount, removing)
1068 if ( chest_pos == nil ) then
1069 chest_pos = easyvend.find_chest(owner, pos, -dx, -dy, -dz, nodename, amount, removing)
1071 return chest_pos
1072 else
1073 local dx = first.x - pos.x
1074 local dy = first.y - pos.y
1075 local dz = first.z - pos.z
1076 return easyvend.find_chest(owner, pos, dx, dy, dz, nodename, amount, removing)
1080 easyvend.find_chest = function(owner, pos, dx, dy, dz, nodename, amount, removing)
1081 pos = {x=pos.x + dx, y=pos.y + dy, z=pos.z + dz}
1083 local node = minetest.get_node_or_nil(pos)
1084 if ( node == nil ) then
1085 return nil
1087 local chestdef = registered_chests[node.name]
1088 if (chestdef ~= nil) then
1089 local meta = minetest.get_meta(pos)
1090 if (chestdef ~= nil and owner ~= meta:get_string(chestdef.meta_owner)) then
1091 return nil
1093 local inv = meta:get_inventory()
1094 if (inv ~= nil) then
1095 if (nodename ~= nil and amount ~= nil and removing ~= nil) then
1096 if (removing and inv:contains_item(chestdef.inv_list, nodename .. " " .. amount)) then
1097 return pos
1098 elseif ((not removing) and inv:room_for_item(chestdef.inv_list, nodename .. " " .. amount)) then
1099 return pos
1101 elseif (not inv:is_empty(chestdef.inv_list)) then
1102 return pos
1105 elseif (node.name ~= "easyvend:vendor" and node.name~="easyvend:depositor" and node.name~="easyvend:vendor_on" and node.name~="easyvend:depositor_on") then
1106 return nil
1109 return easyvend.find_chest(owner, pos, dx, dy, dz, nodename, amount, removing)
1112 -- Pseudo-inventory handling
1113 easyvend.allow_metadata_inventory_put = function(pos, listname, index, stack, player)
1114 if listname=="item" then
1115 local meta = minetest.get_meta(pos);
1116 local owner = meta:get_string("owner")
1117 local name = player:get_player_name()
1118 if name == owner then
1119 local inv = meta:get_inventory()
1120 if stack==nil then
1121 inv:set_stack( "item", 1, nil )
1122 else
1123 inv:set_stack( "item", 1, stack:get_name() )
1124 meta:set_string("itemname", stack:get_name())
1125 easyvend.set_formspec(pos, player)
1129 return 0
1132 easyvend.allow_metadata_inventory_take = function(pos, listname, index, stack, player)
1133 return 0
1136 easyvend.allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
1137 return 0
1140 minetest.register_abm({
1141 nodenames = {"easyvend:vendor", "easyvend:vendor_on", "easyvend:depositor", "easyvend:depositor_on"},
1142 interval = 5,
1143 chance = 1,
1144 catch_up = false,
1145 action = function(pos, node, active_object_count, active_object_count_wider)
1146 easyvend.machine_check(pos, node)
1150 -- Legacy support for vendor mod:
1151 -- Transform the world and items to use the easyvend nodes/items
1153 -- For safety reasons, only do this when player requested so
1154 if minetest.setting_getbool("easyvend_convert_vendor") == true then
1155 -- Replace vendor nodes
1156 minetest.register_lbm({
1157 name = "easyvend:replace_vendor",
1158 nodenames = { "vendor:vendor", "vendor:depositor" },
1159 run_at_every_load = true,
1160 action = function(pos, node)
1161 -- Replace node
1162 local newnodename
1163 if node.name == "vendor:vendor" then
1164 newnodename = "easyvend:vendor"
1165 elseif node.name == "vendor:depositor" then
1166 newnodename = "easyvend:depositor"
1168 -- Remove axis rotation; only allow 4 facedirs
1169 local p2 = math.fmod(node.param2, 4)
1170 minetest.swap_node(pos, { name = newnodename, param2 = p2 })
1172 -- Initialize metadata
1173 local meta = minetest.get_meta(pos)
1174 meta:set_int("stock", -1)
1175 meta:set_int("joketimer", -1)
1176 meta:set_int("joke_id", 1)
1177 local inv = meta:get_inventory()
1178 inv:set_size("item", 1)
1179 inv:set_size("gold", 1)
1180 inv:set_stack("gold", 1, easyvend.currency)
1182 -- In vendor, all machines accepted worn tools
1183 meta:set_int("wear", 1)
1185 -- Set item
1186 local itemname = meta:get_string("itemname")
1187 if itemname == "" or itemname == nil then
1188 itemname = meta:get_string("itemtype")
1190 if itemname ~= "" and itemname ~= nil then
1191 inv:set_stack("item", 1, itemname)
1192 meta:set_string("itemname", itemname)
1195 -- Check for valid item, item count and price
1196 local configmode = 1
1197 if itemname ~= "" and itemname ~= nil then
1198 local itemstack = inv:get_stack("item", 1)
1199 local number_stack_max = itemstack:get_stack_max()
1200 local maxnumber = number_stack_max * slots_max
1201 local cost = meta:get_int("cost")
1202 local number = meta:get_int("number")
1203 if number >= 1 and number <= maxnumber and cost >= 1 and cost <= maxcost then
1204 -- Everything's OK, get out of config mode!
1205 configmode = 0
1209 -- Final initialization stuff
1210 meta:set_int("configmode", configmode)
1212 local owner = meta:get_string("owner")
1213 if easyvend.buysell(newnodename) == "sell" then
1214 meta:set_string("infotext", string.format("Vending machine (owned by %s)", owner))
1215 else
1216 meta:set_string("infotext", string.format("Depositing machine (owned by %s)", owner))
1220 meta:set_string("status", "Initializing …")
1221 meta:set_string("message", "Upgrade successful.")
1222 easyvend.machine_check(pos, node)
1223 end,