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