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
)
489 local iname
= minetest
.registered_items
[itemstring
].description
490 if iname
== nil then iname
= itemstring
end
491 local printitem
, printcost
495 printitem
= string.format("%d×%s", number, iname
)
498 printcost
= easyvend
.currency_desc
500 printcost
= string.format("%d×%s", cost
, easyvend
.currency_desc
)
502 if nodename
== "easyvend:vendor_on" then
503 d
= string.format("Vending machine (owned by %s)\nSelling: %s\nPrice: %s", owner
, printitem
, printcost
)
504 elseif nodename
== "easyvend:vendor" then
505 d
= string.format("Inactive vending machine (owned by %s)\nSelling: %s\nPrice: %s", owner
, printitem
, printcost
)
506 elseif nodename
== "easyvend:depositor_on" then
507 d
= string.format("Depositing machine (owned by %s)\nBuying: %s\nPayment: %s", owner
, printitem
, printcost
)
508 elseif nodename
== "easyvend:depositor" then
509 d
= string.format("Inactive depositing machine (owned by %s)\nBuying: %s\nPayment: %s", owner
, printitem
, printcost
)
514 if minetest
.get_modpath("awards") then
515 awards
.register_achievement("easyvend_seller",{
516 title
= "First Sale",
517 description
= "Sell something with a vending machine.",
518 icon
= "easyvend_vendor_front_on.png^awards_level1.png",
520 local desc_powerseller
521 if easyvend
.currency
== "default:gold_ingot" then
522 desc_powerseller
= string.format("Earn %d gold ingots by selling goods with a single vending machine.", easyvend
.powerseller
)
524 desc_powerseller
= string.format("Earn %d currency items by selling goods with a single vending machine.", easyvend
.powerseller
)
526 awards
.register_achievement("easyvend_powerseller",{
527 title
= "Power Seller",
528 description
= desc_powerseller
,
529 icon
= "easyvend_vendor_front_on.png^awards_level2.png",
533 easyvend
.check_earnings
= function(buyername
, nodemeta
)
534 local owner
= nodemeta
:get_string("owner")
535 if buyername
~= owner
then
536 local cost
= nodemeta
:get_int("cost")
537 local itemname
= nodemeta
:get_string("itemname")
539 if minetest
.get_modpath("awards") and minetest
.get_player_by_name(owner
) ~= nil then
540 awards
.unlock(owner
, "easyvend_seller")
542 if itemname
~= easyvend
.currency
then
543 local newearnings
= nodemeta
:get_int("earnings") + cost
544 if newearnings
>= easyvend
.powerseller
and minetest
.get_modpath("awards") then
545 if minetest
.get_player_by_name(owner
) ~= nil then
546 awards
.unlock(owner
, "easyvend_powerseller")
549 nodemeta
:set_int("earnings", newearnings
)
554 easyvend
.on_receive_fields_buysell
= function(pos
, formname
, fields
, sender
)
555 local sendername
= sender
:get_player_name()
556 local meta
= minetest
.get_meta(pos
)
558 if not fields
.buysell
then
562 local node
= minetest
.get_node(pos
)
563 local number = meta
:get_int("number")
564 local cost
= meta
:get_int("cost")
565 local itemname
=meta
:get_string("itemname")
566 local item
=meta
:get_inventory():get_stack("item", 1)
567 local check_wear
= meta
:get_int("wear") == 0 and minetest
.registered_tools
[itemname
] ~= nil
568 local machine_owner
= meta
:get_string("owner")
570 local buysell
= easyvend
.buysell(node
.name
)
572 local number_stack_max
= item
:get_stack_max()
573 local maxnumber
= number_stack_max
* slots_max
575 if ( number == nil or number < 1 or number > maxnumber
) or
576 ( cost
== nil or cost
< 1 or cost
> maxcost
) or
577 ( itemname
== nil or itemname
=="") then
578 meta
:set_string("status", "Invalid item count or price!")
579 easyvend
.machine_disable(pos
, node
, sendername
)
583 local chest_pos_remove
, chest_error_remove
, chest_pos_add
, chest_error_add
584 if buysell
== "sell" then
585 chest_pos_remove
, chest_error_remove
= easyvend
.find_connected_chest(machine_owner
, pos
, itemname
, check_wear
, number, true)
586 chest_pos_add
, chest_error_add
= easyvend
.find_connected_chest(machine_owner
, pos
, easyvend
.currency
, check_wear
, cost
, false)
588 chest_pos_remove
, chest_error_remove
= easyvend
.find_connected_chest(machine_owner
, pos
, easyvend
.currency
, check_wear
, cost
, true)
589 chest_pos_add
, chest_error_add
= easyvend
.find_connected_chest(machine_owner
, pos
, itemname
, check_wear
, number, false)
592 if chest_pos_remove
~= nil and chest_pos_add
~= nil and sender
and sender
:is_player() then
593 local rchest
= minetest
.get_node(chest_pos_remove
)
594 local rchestdef
= registered_chests
[rchest
.name
]
595 local rchest_meta
= minetest
.get_meta(chest_pos_remove
)
596 local rchest_inv
= rchest_meta
:get_inventory()
597 local achest
= minetest
.get_node(chest_pos_add
)
598 local achestdef
= registered_chests
[achest
.name
]
599 local achest_meta
= minetest
.get_meta(chest_pos_add
)
600 local achest_inv
= achest_meta
:get_inventory()
602 local player_inv
= sender
:get_inventory()
604 local stack
= {name
=itemname
, count
=number, wear
=0, metadata
=""}
605 local price
= {name
=easyvend
.currency
, count
=cost
, wear
=0, metadata
=""}
606 local chest_has
, player_has
, chest_free
, player_free
, chest_out
, player_out
608 if buysell
== "sell" then
609 chest_has
, chest_out
= easyvend
.check_and_get_items(rchest_inv
, rchestdef
.inv_list
, stack
, check_wear
)
610 player_has
, player_out
= easyvend
.check_and_get_items(player_inv
, "main", price
, check_wear
)
611 chest_free
= achest_inv
:room_for_item(achestdef
.inv_list
, price
)
612 player_free
= player_inv
:room_for_item("main", stack
)
613 if chest_has
and player_has
and chest_free
and player_free
then
614 if cost
<= cost_stack_max
and number <= number_stack_max
then
615 easyvend
.machine_enable(pos
, node
)
616 player_inv
:remove_item("main", price
)
618 rchest_inv
:set_stack(rchestdef
.inv_list
, chest_out
[1].id
, "")
619 player_inv
:add_item("main", chest_out
[1].item
)
621 stack
= rchest_inv
:remove_item(rchestdef
.inv_list
, stack
)
622 player_inv
:add_item("main", stack
)
624 achest_inv
:add_item(achestdef
.inv_list
, price
)
625 if itemname
== easyvend
.currency
and number == cost
and cost
<= cost_stack_max
then
626 meta
:set_string("message", easyvend
.get_joke(buysell
, meta
:get_int("joke_id")))
627 meta
:set_int("joketimer", joketimer_start
)
629 meta
:set_string("message", "Item bought.")
631 easyvend
.check_earnings(sendername
, meta
)
632 easyvend
.sound_vend(pos
)
633 easyvend
.machine_check(pos
, node
)
635 -- Large item counts (multiple stacks)
636 local coststacks
= math
.modf(cost
/ cost_stack_max
)
637 local costremainder
= math
.fmod(cost
, cost_stack_max
)
638 local numberstacks
= math
.modf(number / number_stack_max
)
639 local numberremainder
= math
.fmod(number, number_stack_max
)
640 local numberfree
= numberstacks
641 local costfree
= coststacks
642 if numberremainder
> 0 then numberfree
= numberfree
+ 1 end
643 if costremainder
> 0 then costfree
= costfree
+ 1 end
644 if not player_free
and easyvend
.free_slots(player_inv
, "main") < numberfree
then
645 if numberfree
> 1 then
646 msg
= string.format("No room in your inventory (%d empty slots required)!", numberfree
)
648 msg
= "No room in your inventory!"
650 meta
:set_string("message", msg
)
651 elseif not chest_free
and easyvend
.free_slots(achest_inv
, achestdef
.inv_list
) < costfree
then
652 meta
:set_string("status", "No room in the machine’s storage!")
653 easyvend
.machine_disable(pos
, node
, sendername
)
655 -- Remember items for transfer
656 local cheststacks
= {}
657 easyvend
.machine_enable(pos
, node
)
658 for i
=1, coststacks
do
659 price
.count
= cost_stack_max
660 player_inv
:remove_item("main", price
)
662 if costremainder
> 0 then
663 price
.count
= costremainder
664 player_inv
:remove_item("main", price
)
667 for o
=1,#chest_out
do
668 rchest_inv
:set_stack(rchestdef
.inv_list
, chest_out
[o
].id
, "")
671 for i
=1, numberstacks
do
672 stack
.count
= number_stack_max
673 table.insert(cheststacks
, rchest_inv
:remove_item(rchestdef
.inv_list
, stack
))
676 if numberremainder
> 0 then
677 stack
.count
= numberremainder
678 table.insert(cheststacks
, rchest_inv
:remove_item(rchestdef
.inv_list
, stack
))
680 for i
=1, coststacks
do
681 price
.count
= cost_stack_max
682 achest_inv
:add_item(achestdef
.inv_list
, price
)
684 if costremainder
> 0 then
685 price
.count
= costremainder
686 achest_inv
:add_item(achestdef
.inv_list
, price
)
689 for o
=1,#chest_out
do
690 player_inv
:add_item("main", chest_out
[o
].item
)
693 for i
=1,#cheststacks
do
694 player_inv
:add_item("main", cheststacks
[i
])
697 meta
:set_string("message", "Item bought.")
698 easyvend
.check_earnings(sendername
, meta
)
699 easyvend
.sound_vend(pos
)
700 easyvend
.machine_check(pos
, node
)
703 elseif chest_has
and player_has
then
704 if not player_free
then
705 msg
= "No room in your inventory!"
706 meta
:set_string("message", msg
)
707 easyvend
.sound_error(sendername
)
708 elseif not chest_free
then
709 msg
= "No room in the machine’s storage!"
710 meta
:set_string("status", msg
)
711 easyvend
.machine_disable(pos
, node
, sendername
)
714 if not chest_has
then
715 msg
= "The vending machine has insufficient materials!"
716 meta
:set_string("status", msg
)
717 easyvend
.machine_disable(pos
, node
, sendername
)
718 elseif not player_has
then
719 msg
= "You can’t afford this item!"
720 meta
:set_string("message", msg
)
721 easyvend
.sound_error(sendername
)
725 chest_has
, chest_out
= easyvend
.check_and_get_items(rchest_inv
, rchestdef
.inv_list
, price
, check_wear
)
726 player_has
, player_out
= easyvend
.check_and_get_items(player_inv
, "main", stack
, check_wear
)
727 chest_free
= achest_inv
:room_for_item(achestdef
.inv_list
, stack
)
728 player_free
= player_inv
:room_for_item("main", price
)
729 if chest_has
and player_has
and chest_free
and player_free
then
730 if cost
<= cost_stack_max
and number <= number_stack_max
then
731 easyvend
.machine_enable(pos
, node
)
733 player_inv
:set_stack("main", player_out
[1].id
, "")
734 achest_inv
:add_item(achestdef
.inv_list
, player_out
[1].item
)
736 stack
= player_inv
:remove_item("main", stack
)
737 achest_inv
:add_item(achestdef
.inv_list
, stack
)
739 rchest_inv
:remove_item(rchestdef
.inv_list
, price
)
740 player_inv
:add_item("main", price
)
741 meta
:set_string("status", "Ready.")
742 if itemname
== easyvend
.currency
and number == cost
and cost
<= cost_stack_max
then
743 meta
:set_string("message", easyvend
.get_joke(buysell
, meta
:get_int("joke_id")))
744 meta
:set_int("joketimer", joketimer_start
)
746 meta
:set_string("message", "Item sold.")
748 easyvend
.sound_deposit(pos
)
749 easyvend
.machine_check(pos
, node
)
751 -- Large item counts (multiple stacks)
752 local coststacks
= math
.modf(cost
/ cost_stack_max
)
753 local costremainder
= math
.fmod(cost
, cost_stack_max
)
754 local numberstacks
= math
.modf(number / number_stack_max
)
755 local numberremainder
= math
.fmod(number, number_stack_max
)
756 local numberfree
= numberstacks
757 local costfree
= coststacks
758 if numberremainder
> 0 then numberfree
= numberfree
+ 1 end
759 if costremainder
> 0 then costfree
= costfree
+ 1 end
760 if not player_free
and easyvend
.free_slots(player_inv
, "main") < costfree
then
762 msg
= string.format("No room in your inventory (%d empty slots required)!", costfree
)
764 msg
= "No room in your inventory!"
766 meta
:set_string("message", msg
)
767 easyvend
.sound_error(sendername
)
768 elseif not chest_free
and easyvend
.free_slots(achest_inv
, achestdef
.inv_list
) < numberfree
then
769 meta
:set_string("status", "No room in the machine’s storage!")
770 easyvend
.machine_disable(pos
, node
, sendername
)
772 easyvend
.machine_enable(pos
, node
)
773 -- Remember removed items for transfer
774 local playerstacks
= {}
775 for i
=1, coststacks
do
776 price
.count
= cost_stack_max
777 rchest_inv
:remove_item(rchestdef
.inv_list
, price
)
779 if costremainder
> 0 then
780 price
.count
= costremainder
781 rchest_inv
:remove_item(rchestdef
.inv_list
, price
)
784 for o
=1,#player_out
do
785 player_inv
:set_stack("main", player_out
[o
].id
, "")
788 for i
=1, numberstacks
do
789 stack
.count
= number_stack_max
790 table.insert(playerstacks
, player_inv
:remove_item("main", stack
))
793 if numberremainder
> 0 then
794 stack
.count
= numberremainder
795 table.insert(playerstacks
, player_inv
:remove_item("main", stack
))
797 for i
=1, coststacks
do
798 price
.count
= cost_stack_max
799 player_inv
:add_item("main", price
)
801 if costremainder
> 0 then
802 price
.count
= costremainder
803 player_inv
:add_item("main", price
)
806 for o
=1,#player_out
do
807 achest_inv
:add_item(achestdef
.inv_list
, player_out
[o
].item
)
810 for i
=1,#playerstacks
do
811 achest_inv
:add_item(achestdef
.inv_list
, playerstacks
[i
])
814 meta
:set_string("message", "Item sold.")
815 easyvend
.sound_deposit(pos
)
816 easyvend
.machine_check(pos
, node
)
819 elseif chest_has
and player_has
then
820 if not player_free
then
821 msg
= "No room in your inventory!"
822 meta
:set_string("message", msg
)
823 easyvend
.sound_error(sendername
)
824 elseif not chest_free
then
825 msg
= "No room in the machine’s storage!"
826 meta
:set_string("status", msg
)
827 easyvend
.machine_disable(pos
, node
, sendername
)
830 if not player_has
then
831 msg
= "You have insufficient materials!"
832 meta
:set_string("message", msg
)
833 easyvend
.sound_error(sendername
)
834 elseif not chest_has
then
835 msg
= "The depositing machine is out of money!"
836 meta
:set_string("status", msg
)
837 easyvend
.machine_disable(pos
, node
, sendername
)
843 meta
:set_int("stock", 0)
844 if chest_error_remove
== "no_chest" and chest_error_add
== "no_chest" then
845 status
= "No storage; machine needs to be connected with a locked chest."
846 elseif chest_error_remove
== "not_owned" or chest_error_add
== "not_owned" then
847 status
= "Storage can’t be accessed because it is owned by a different person!"
848 elseif chest_error_remove
== "no_stock" then
849 if buysell
== "sell" then
850 status
= "The vending machine has insufficient materials!"
852 status
= "The depositing machine is out of money!"
854 elseif chest_error_add
== "no_space" then
855 status
= "No room in the machine’s storage!"
857 status
= "Unknown error!"
859 meta
:set_string("status", status
)
860 easyvend
.sound_error(sendername
)
863 easyvend
.set_formspec(pos
, sender
)
867 easyvend
.after_place_node
= function(pos
, placer
)
868 local node
= minetest
.get_node(pos
)
869 local meta
= minetest
.get_meta(pos
)
870 local inv
= meta
:get_inventory()
871 local player_name
= placer
:get_player_name()
872 inv
:set_size("item", 1)
873 inv
:set_size("gold", 1)
875 inv
:set_stack( "gold", 1, easyvend
.currency
)
878 if node
.name
== "easyvend:vendor" then
879 d
= string.format("Inactive vending machine (owned by %s)", player_name
)
880 meta
:set_int("wear", 1)
881 -- Total number of currency items earned for the machine's life time (excluding currency-currency trading)
882 meta
:set_int("earnings", 0)
883 elseif node
.name
== "easyvend:depositor" then
884 d
= string.format("Inactive depositing machine (owned by %s)", player_name
)
885 meta
:set_int("wear", 0)
887 meta
:set_string("infotext", d
)
888 meta
:set_string("status", "Awaiting configuration by owner.")
889 meta
:set_string("message", "Welcome! Please prepare the machine.")
890 meta
:set_int("number", 1)
891 meta
:set_int("cost", 1)
892 meta
:set_int("stock", -1)
893 meta
:set_int("configmode", 1)
894 meta
:set_int("joketimer", -1)
895 meta
:set_int("joke_id", 1)
896 meta
:set_string("itemname", "")
898 meta
:set_string("owner", player_name
or "")
900 easyvend
.set_formspec(pos
, placer
)
903 easyvend
.can_dig
= function(pos
, player
)
904 local meta
= minetest
.get_meta(pos
)
905 local name
= player
:get_player_name()
906 local owner
= meta
:get_string("owner")
907 -- Owner can always dig shop
908 if owner
== name
then
911 local chest_pos
= easyvend
.find_connected_chest(owner
, pos
)
912 local chest
, meta_chest
914 chest
= minetest
.get_node(chest_pos
)
915 meta_chest
= minetest
.get_meta(chest_pos
)
917 return true --if no chest, enyone can dig this shop
919 if registered_chests
[chest
.name
] then
920 if player
and player
:is_player() then
921 local owner_chest
= meta_chest
:get_string(registered_chests
[chest
.name
].meta_owner
)
922 if name
== owner_chest
then
923 return true --chest owner can also dig shop
928 return true --if no chest, enyone can dig this shop
932 easyvend
.on_receive_fields
= function(pos
, formname
, fields
, sender
)
933 local meta
= minetest
.get_meta(pos
)
934 local node
= minetest
.get_node(pos
)
935 local owner
= meta
:get_string("owner")
936 local sendername
= sender
:get_player_name()
939 if minetest
.get_modpath("doc") and minetest
.get_modpath("doc_items") then
940 if easyvend
.buysell(node
.name
) == "buy" then
941 doc
.show_entry(sendername
, "nodes", "easyvend:depositor", true)
943 doc
.show_entry(sendername
, "nodes", "easyvend:vendor", true)
946 elseif fields
.config
or fields
.save
or fields
.usermode
then
947 if sender
:get_player_name() == owner
then
948 easyvend
.on_receive_fields_config(pos
, formname
, fields
, sender
)
950 meta
:set_string("message", "Only the owner may change the configuration.")
951 easyvend
.sound_error(sendername
)
952 easyvend
.set_formspec(pos
, sender
)
955 elseif fields
.wear
~= nil then
956 if sender
:get_player_name() == owner
then
957 if fields
.wear
== "true" then
958 if easyvend
.buysell(node
.name
) == "buy" then
959 meta
:set_string("message", "Used tools are now accepted.")
961 meta
:set_string("message", "Used tools are now for sale.")
963 meta
:set_int("wear", 1)
964 elseif fields
.wear
== "false" then
965 if easyvend
.buysell(node
.name
) == "buy" then
966 meta
:set_string("message", "Used tools are now rejected.")
968 meta
:set_string("message", "Used tools won’t be sold anymore.")
970 meta
:set_int("wear", 0)
972 easyvend
.set_formspec(pos
, sender
)
975 meta
:set_string("message", "Only the owner may change the configuration.")
976 easyvend
.sound_error(sendername
)
977 easyvend
.set_formspec(pos
, sender
)
980 elseif fields
.buysell
then
981 easyvend
.on_receive_fields_buysell(pos
, formname
, fields
, sender
)
985 -- Jokes: Appear when machine exchanges currency for currency at equal rate
988 local jokes_vendor
= {
989 "Thank you. You have made a vending machine very happy.",
990 "Humans have a strange sense of humor.",
991 "Let’s get this over with …",
994 "Do you realize what you’ve just bought?",
997 local jokes_depositor
= {
998 "Thank you, the money started to smell inside.",
999 "Money doesn’t grow on trees, you know?",
1001 "Well, that was an awkward exchange.",
1002 "Are you having fun?",
1003 "Is this really trading?",
1006 easyvend
.assign_joke
= function(buysell
)
1008 if buysell
== "sell" then
1009 jokes
= jokes_vendor
1010 elseif buysell
== "buy" then
1011 jokes
= jokes_depositor
1013 local r
= math
.random(1,#jokes
)
1017 easyvend
.get_joke
= function(buysell
, id
)
1019 if buysell
== nil or id
== nil then
1020 -- Fallback message (should never happen)
1021 return "Items exchanged."
1023 if buysell
== "sell" then
1024 joke
= jokes_vendor
[id
]
1025 if joke
== nil then joke
= jokes_vendor
[1] end
1026 elseif buysell
== "buy" then
1027 joke
= jokes_depositor
[id
]
1028 if joke
== nil then joke
= jokes_depositor
[1] end
1033 easyvend
.sound_error
= function(playername
)
1034 minetest
.sound_play("easyvend_error", {to_player
= playername
, gain
= 0.25})
1037 easyvend
.sound_setup
= function(pos
)
1038 minetest
.sound_play("easyvend_activate", {pos
= pos
, gain
= 0.5, max_hear_distance
= 12,})
1041 easyvend
.sound_disable
= function(pos
)
1042 minetest
.sound_play("easyvend_disable", {pos
= pos
, gain
= 0.9, max_hear_distance
= 12,})
1045 easyvend
.sound_vend
= function(pos
)
1046 minetest
.sound_play("easyvend_vend", {pos
= pos
, gain
= 0.4, max_hear_distance
= 5,})
1049 easyvend
.sound_deposit
= function(pos
)
1050 minetest
.sound_play("easyvend_deposit", {pos
= pos
, gain
= 0.4, max_hear_distance
= 5,})
1053 --[[ Tower building ]]
1055 easyvend
.is_traversable
= function(pos
)
1056 local node
= minetest
.get_node_or_nil(pos
)
1057 if (node
== nil) then
1060 return traversable_node_types
[node
.name
] == true
1063 easyvend
.neighboring_nodes
= function(pos
)
1065 {x
=pos
.x
, y
=pos
.y
-1, z
=pos
.z
},
1066 {x
=pos
.x
, y
=pos
.y
+1, z
=pos
.z
},
1070 if easyvend
.is_traversable(check
[i
]) then
1071 table.insert(trav
, check
[i
])
1077 easyvend
.find_connected_chest
= function(owner
, pos
, nodename
, check_wear
, amount
, removing
)
1078 local nodes
= easyvend
.neighboring_nodes(pos
)
1080 if (#nodes
< 1 or #nodes
> 2) then
1081 return nil, "no_chest"
1084 -- Find the stack direction
1088 if ( first
== nil ) then
1095 local chest_pos
, chest_internal
1097 if (first
~= nil and second
~= nil) then
1098 local dy
= (first
.y
- second
.y
)/2
1099 chest_pos
, chest_internal
= easyvend
.find_chest(owner
, pos
, dy
, nodename
, check_wear
, amount
, removing
)
1100 if ( chest_pos
== nil ) then
1101 chest_pos
, chest_internal
= easyvend
.find_chest(owner
, pos
, -dy
, nodename
, check_wear
, amount
, removing
, chest_internal
)
1104 local dy
= first
.y
- pos
.y
1105 chest_pos
, chest_internal
= easyvend
.find_chest(owner
, pos
, dy
, nodename
, check_wear
, amount
, removing
)
1108 if chest_internal
.chests
== 0 then
1109 return nil, "no_chest"
1110 elseif chest_internal
.chests
== chest_internal
.other_chests
then
1111 return nil, "not_owned"
1112 elseif removing
and chest_internal
.stock
< 1 then
1113 return nil, "no_stock"
1114 elseif not removing
and chest_internal
.space
< 1 then
1115 return nil, "no_space"
1116 elseif chest_pos
~= nil then
1119 return nil, "unknown"
1123 easyvend
.find_chest
= function(owner
, pos
, dy
, itemname
, check_wear
, amount
, removing
, internal
)
1124 pos
= {x
=pos
.x
, y
=pos
.y
+ dy
, z
=pos
.z
}
1126 if internal
== nil then
1129 internal
.other_chests
= 0
1134 local node
= minetest
.get_node_or_nil(pos
)
1135 if ( node
== nil ) then
1136 return nil, internal
1138 local chestdef
= registered_chests
[node
.name
]
1139 if (chestdef
~= nil) then
1140 internal
.chests
= internal
.chests
+ 1
1141 local meta
= minetest
.get_meta(pos
)
1142 if (owner
~= meta
:get_string(chestdef
.meta_owner
)) then
1143 internal
.other_chests
= internal
.other_chests
+ 1
1144 return nil, internal
1146 local inv
= meta
:get_inventory()
1147 if (inv
~= nil) then
1148 if (itemname
~= nil and amount
~= nil and removing
~= nil and check_wear
~= nil) then
1149 local chest_has
, chest_free
1150 local stack
= {name
=itemname
, count
=amount
, wear
=0, metadata
=""}
1151 local stack_max
= minetest
.registered_items
[itemname
].stack_max
1153 local stacks
= math
.modf(amount
/ stack_max
)
1154 local stacksremainder
= math
.fmod(amount
, stack_max
)
1156 if stacksremainder
> 0 then free
= free
+ 1 end
1158 chest_has
= easyvend
.check_and_get_items(inv
, chestdef
.inv_list
, stack
, check_wear
)
1160 internal
.stock
= internal
.stock
+ 1
1162 chest_free
= inv
:room_for_item(chestdef
.inv_list
, stack
) and easyvend
.free_slots(inv
, chestdef
.inv_list
) >= free
1164 internal
.space
= internal
.space
+ 1
1167 if (removing
and internal
.stock
== 0) or (not removing
and internal
.space
== 0) then
1168 return easyvend
.find_chest(owner
, pos
, dy
, itemname
, check_wear
, amount
, removing
, internal
)
1170 return pos
, internal
1174 elseif (node
.name
~= "easyvend:vendor" and node
.name
~="easyvend:depositor" and node
.name
~="easyvend:vendor_on" and node
.name
~="easyvend:depositor_on") then
1175 return nil, internal
1178 return easyvend
.find_chest(owner
, pos
, dy
, itemname
, check_wear
, amount
, removing
, internal
)
1181 -- Pseudo-inventory handling
1182 easyvend
.allow_metadata_inventory_put
= function(pos
, listname
, index
, stack
, player
)
1183 if listname
=="item" then
1184 local meta
= minetest
.get_meta(pos
);
1185 local owner
= meta
:get_string("owner")
1186 local name
= player
:get_player_name()
1187 if name
== owner
then
1188 local inv
= meta
:get_inventory()
1190 inv
:set_stack( "item", 1, nil )
1192 inv
:set_stack( "item", 1, stack
:get_name() )
1193 meta
:set_string("itemname", stack
:get_name())
1194 easyvend
.set_formspec(pos
, player
)
1201 easyvend
.allow_metadata_inventory_take
= function(pos
, listname
, index
, stack
, player
)
1205 easyvend
.allow_metadata_inventory_move
= function(pos
, from_list
, from_index
, to_list
, to_index
, count
, player
)
1209 minetest
.register_abm({
1210 nodenames
= {"easyvend:vendor", "easyvend:vendor_on", "easyvend:depositor", "easyvend:depositor_on"},
1214 action
= function(pos
, node
, active_object_count
, active_object_count_wider
)
1215 easyvend
.machine_check(pos
, node
)
1219 -- Legacy support for vendor mod:
1220 -- Transform the world and items to use the easyvend nodes/items
1222 -- For safety reasons, only do this when player requested so
1223 if minetest
.setting_getbool("easyvend_convert_vendor") == true then
1224 -- Replace vendor nodes
1225 minetest
.register_lbm({
1226 name
= "easyvend:replace_vendor",
1227 nodenames
= { "vendor:vendor", "vendor:depositor" },
1228 run_at_every_load
= true,
1229 action
= function(pos
, node
)
1232 if node
.name
== "vendor:vendor" then
1233 newnodename
= "easyvend:vendor"
1234 elseif node
.name
== "vendor:depositor" then
1235 newnodename
= "easyvend:depositor"
1237 -- Remove axis rotation; only allow 4 facedirs
1238 local p2
= math
.fmod(node
.param2
, 4)
1239 minetest
.swap_node(pos
, { name
= newnodename
, param2
= p2
})
1241 -- Initialize metadata
1242 local meta
= minetest
.get_meta(pos
)
1243 if node
.name
== "vendor:vendor" then
1244 meta
:set_int("earnings", 0)
1246 meta
:set_int("stock", -1)
1247 meta
:set_int("joketimer", -1)
1248 meta
:set_int("joke_id", 1)
1249 local inv
= meta
:get_inventory()
1250 inv
:set_size("item", 1)
1251 inv
:set_size("gold", 1)
1252 inv
:set_stack("gold", 1, easyvend
.currency
)
1254 -- In vendor, all machines accepted worn tools
1255 meta
:set_int("wear", 1)
1258 local itemname
= meta
:get_string("itemname")
1259 if itemname
== "" or itemname
== nil then
1260 itemname
= meta
:get_string("itemtype")
1262 if itemname
~= "" and itemname
~= nil then
1263 inv
:set_stack("item", 1, itemname
)
1264 meta
:set_string("itemname", itemname
)
1267 -- Check for valid item, item count and price
1268 local configmode
= 1
1269 if itemname
~= "" and itemname
~= nil then
1270 local itemstack
= inv
:get_stack("item", 1)
1271 local number_stack_max
= itemstack
:get_stack_max()
1272 local maxnumber
= number_stack_max
* slots_max
1273 local cost
= meta
:get_int("cost")
1274 local number = meta
:get_int("number")
1275 if number >= 1 and number <= maxnumber
and cost
>= 1 and cost
<= maxcost
then
1276 -- Everything's OK, get out of config mode!
1281 -- Final initialization stuff
1282 meta
:set_int("configmode", configmode
)
1284 local owner
= meta
:get_string("owner")
1285 if easyvend
.buysell(newnodename
) == "sell" then
1286 meta
:set_string("infotext", string.format("Vending machine (owned by %s)", owner
))
1288 meta
:set_string("infotext", string.format("Depositing machine (owned by %s)", owner
))
1292 meta
:set_string("status", "Initializing …")
1293 meta
:set_string("message", "Upgrade successful.")
1294 easyvend
.machine_check(pos
, node
)