1 -- TODO: Improve mod compability
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
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
31 if minetest
.registered_tools
[itemstring
] ~= nil then
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
38 table.insert(get_items
, {id
=i
, item
=stack
})
39 if count
>= minimum
then
40 return true, get_items
47 -- Normal Minetest check
48 return inventory
:contains_item(listname
, ItemStack(itemtable
))
53 if minetest
.get_modpath("default") ~= nil then
54 easyvend
.register_chest("default:chest_locked", "main", "owner")
57 easyvend
.free_slots
= function(inv
, listname
)
58 local size
= inv
:get_size(listname
)
61 local stack
= inv
:get_stack(listname
, i
)
62 if stack
:is_empty() then
69 easyvend
.buysell
= function(nodename
)
71 if ( nodename
== "easyvend:depositor" or nodename
== "easyvend:depositor_on" ) then
73 elseif ( nodename
== "easyvend:vendor" or nodename
== "easyvend:vendor_on" ) then
79 easyvend
.is_active
= function(nodename
)
80 if ( nodename
== "easyvend:depositor_on" or nodename
== "easyvend:vendor_on" ) then
82 elseif ( nodename
== "easyvend:depositor" or nodename
== "easyvend:vendor" ) then
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")
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"
109 buysellbuttontext
= "Buy"
110 elseif buysell
== "buy" then
111 numbertext
= "Requested item"
113 buysellbuttontext
= "Sell"
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
122 if node
.name
== "easyvend:vendor_on" or node
.name
== "easyvend:depositor_on" then
123 status_image
= "easyvend_status_on.png"
125 status_image
= "easyvend_status_off.png"
128 -- TODO: Expose number of items in stock
130 local formspec
= "size[8,7.3;]"
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;]" ..
150 if meta
:get_int("wear") == 1 then wear
= "true" end
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)"
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
).."]"
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)]"
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
189 if meta
:get_int("wear") == 0 then
190 if buysell
== "buy" then
191 weartext
= "Only intact tools are bought."
193 weartext
= "Only intact tools are sold."
196 if buysell
== "sell" then
197 weartext
= "Warning: Might sell worn tools."
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
})
216 elseif node
.name
== "easyvend:depositor_on" then
217 easyvend
.sound_disable(pos
)
218 minetest
.swap_node(pos
, {name
="easyvend:depositor", param2
= node
.param2
})
221 if playername
~= nil then
222 easyvend
.sound_error(playername
)
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
})
233 elseif node
.name
== "easyvend:depositor" then
234 easyvend
.sound_setup(pos
)
235 minetest
.swap_node(pos
, {name
="easyvend:depositor_on", param2
= node
.param2
})
242 easyvend
.machine_check
= function(pos
, node
)
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)
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
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
294 if buysell
== "sell" then
295 status
= "Invalid item count or price."
297 status
= "Invalid item count or payment."
302 status
= "Awaiting configuration by owner."
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!"
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!"
320 status
= "Unknown error!"
323 if meta
:get_int("configmode") == 1 then
325 status
= "Awaiting configuration by owner."
328 if itemname
== easyvend
.currency
and number == cost
and active
then
329 local jt
= meta
:get_int("joketimer")
334 if buysell
== "sell" then
335 meta
:set_string("message", "Item bought.")
337 meta
:set_string("message", "Item sold.")
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")
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
)
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
)
382 meta
:set_string("message", "Configuration mode activated; machine disabled.")
384 meta
:set_string("message", "Configuration mode activated.")
386 easyvend
.machine_check(pos
, node
)
390 if not fields
.save
then
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
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
415 cost
= n
* cost_stack_max
418 number = tonumber(number)
419 cost
= tonumber(cost
)
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
)
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
))
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
)
443 elseif ( cost
== nil or cost
< 1 or cost
> maxcost
) then
445 meta
:set_string("message", string.format("Invalid cost; must be between 1 and %d!", maxcost
))
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
)
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
))
465 meta
:set_string("message", "Configuration successful.")
468 local change
= easyvend
.machine_check(pos
, node
)
471 if (node
.name
== "easyvend:vendor_on" or node
.name
== "easyvend:depositor_on") then
472 easyvend
.sound_setup(pos
)
474 easyvend
.sound_disable(pos
)
479 easyvend
.make_infotext
= function(nodename
, owner
, cost
, number, itemstring
)
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
)
485 d
= string.format("Inactive depositing machine (owned by %s)", owner
)
490 if minetest
.registered_items
[itemstring
] then
491 iname
= minetest
.registered_items
[itemstring
].description
493 iname
= string.format("Unknown Item (%s)", itemstring
)
495 if iname
== nil then iname
= itemstring
end
496 local printitem
, printcost
500 printitem
= string.format("%d×%s", number, iname
)
503 printcost
= easyvend
.currency_desc
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
)
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
)
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")
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
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
)
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)
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
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
)
623 rchest_inv
:set_stack(rchestdef
.inv_list
, chest_out
[1].id
, "")
624 player_inv
:add_item("main", chest_out
[1].item
)
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
)
634 meta
:set_string("message", "Item bought.")
636 easyvend
.check_earnings(sendername
, meta
)
637 easyvend
.sound_vend(pos
)
638 easyvend
.machine_check(pos
, node
)
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
)
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
)
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
)
672 for o
=1,#chest_out
do
673 rchest_inv
:set_stack(rchestdef
.inv_list
, chest_out
[o
].id
, "")
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
)
694 for o
=1,#chest_out
do
695 player_inv
:add_item("main", chest_out
[o
].item
)
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
)
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
)
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
)
738 player_inv
:set_stack("main", player_out
[1].id
, "")
739 achest_inv
:add_item(achestdef
.inv_list
, player_out
[1].item
)
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
)
751 meta
:set_string("message", "Item sold.")
753 easyvend
.sound_deposit(pos
)
754 easyvend
.machine_check(pos
, node
)
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
767 msg
= string.format("No room in your inventory (%d empty slots required)!", costfree
)
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
)
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
)
789 for o
=1,#player_out
do
790 player_inv
:set_stack("main", player_out
[o
].id
, "")
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
)
811 for o
=1,#player_out
do
812 achest_inv
:add_item(achestdef
.inv_list
, player_out
[o
].item
)
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
)
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
)
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!"
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!"
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
)
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
916 local chest_pos
= easyvend
.find_connected_chest(owner
, pos
)
917 local chest
, meta_chest
919 chest
= minetest
.get_node(chest_pos
)
920 meta_chest
= minetest
.get_meta(chest_pos
)
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
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()
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)
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
)
955 meta
:set_string("message", "Only the owner may change the configuration.")
956 easyvend
.sound_error(sendername
)
957 easyvend
.set_formspec(pos
, sender
)
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.")
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.")
973 meta
:set_string("message", "Used tools won’t be sold anymore.")
975 meta
:set_int("wear", 0)
977 easyvend
.set_formspec(pos
, sender
)
980 meta
:set_string("message", "Only the owner may change the configuration.")
981 easyvend
.sound_error(sendername
)
982 easyvend
.set_formspec(pos
, sender
)
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
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 …",
999 "Do you realize what you’ve just bought?",
1002 local jokes_depositor
= {
1003 "Thank you, the money started to smell inside.",
1004 "Money doesn’t grow on trees, you know?",
1006 "Well, that was an awkward exchange.",
1007 "Are you having fun?",
1008 "Is this really trading?",
1011 easyvend
.assign_joke
= function(buysell
)
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
)
1022 easyvend
.get_joke
= function(buysell
, id
)
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
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
1065 return traversable_node_types
[node
.name
] == true
1068 easyvend
.neighboring_nodes
= function(pos
)
1070 {x
=pos
.x
, y
=pos
.y
-1, z
=pos
.z
},
1071 {x
=pos
.x
, y
=pos
.y
+1, z
=pos
.z
},
1075 if easyvend
.is_traversable(check
[i
]) then
1076 table.insert(trav
, check
[i
])
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
1093 if ( first
== nil ) then
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
)
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
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
1134 internal
.other_chests
= 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
)
1161 if stacksremainder
> 0 then free
= free
+ 1 end
1163 chest_has
= easyvend
.check_and_get_items(inv
, chestdef
.inv_list
, stack
, check_wear
)
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
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
)
1175 return pos
, internal
1178 return nil, internal
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()
1199 inv
:set_stack( "item", 1, nil )
1201 inv
:set_stack( "item", 1, stack
:get_name() )
1202 meta
:set_string("itemname", stack
:get_name())
1203 easyvend
.set_formspec(pos
, player
)
1210 easyvend
.allow_metadata_inventory_take
= function(pos
, listname
, index
, stack
, player
)
1214 easyvend
.allow_metadata_inventory_move
= function(pos
, from_list
, from_index
, to_list
, to_index
, count
, player
)
1218 minetest
.register_abm({
1219 nodenames
= {"easyvend:vendor", "easyvend:vendor_on", "easyvend:depositor", "easyvend:depositor_on"},
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
)
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)
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!
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
))
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
)