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 ( not itemstack
:is_known() ) then
434 meta
:set_string("status", "Awaiting configuration by owner.")
435 meta
:set_string("message", "Unknown item specified.")
436 easyvend
.sound_error(sender
:get_player_name())
437 easyvend
.set_formspec(pos
, sender
)
439 elseif ( number == nil or number < 1 or number > maxnumber
) then
440 if maxnumber
> 1 then
441 meta
:set_string("message", string.format("Invalid item count; must be between 1 and %d!", maxnumber
))
443 meta
:set_string("message", "Invalid item count; must be exactly 1!")
445 meta
:set_int("number", oldnumber
)
446 easyvend
.sound_error(sender
:get_player_name())
447 easyvend
.set_formspec(pos
, sender
)
449 elseif ( cost
== nil or cost
< 1 or cost
> maxcost
) then
451 meta
:set_string("message", string.format("Invalid cost; must be between 1 and %d!", maxcost
))
453 meta
:set_string("message", "Invalid cost; must be exactly 1!")
455 meta
:set_int("cost", oldcost
)
456 easyvend
.sound_error(sender
:get_player_name())
457 easyvend
.set_formspec(pos
, sender
)
460 meta
:set_int("number", number)
461 meta
:set_int("cost", cost
)
462 itemname
=itemstack
:get_name()
463 meta
:set_string("itemname", itemname
)
464 meta
:set_int("configmode", 0)
466 if itemname
== easyvend
.currency
and number == cost
and cost
<= cost_stack_max
then
467 meta
:set_string("message", "Configuration successful. I am feeling funny.")
468 meta
:set_int("joketimer", joketimer_start
)
469 meta
:set_int("joke_id", easyvend
.assign_joke(buysell
))
471 meta
:set_string("message", "Configuration successful.")
474 local change
= easyvend
.machine_check(pos
, node
)
477 if (node
.name
== "easyvend:vendor_on" or node
.name
== "easyvend:depositor_on") then
478 easyvend
.sound_setup(pos
)
480 easyvend
.sound_disable(pos
)
485 easyvend
.make_infotext
= function(nodename
, owner
, cost
, number, itemstring
)
487 if itemstring
== nil or itemstring
== "" or number == 0 or cost
== 0 then
488 if easyvend
.buysell(nodename
) == "sell" then
489 d
= string.format("Inactive vending machine (owned by %s)", owner
)
491 d
= string.format("Inactive depositing machine (owned by %s)", owner
)
496 if minetest
.registered_items
[itemstring
] then
497 iname
= minetest
.registered_items
[itemstring
].description
499 iname
= string.format("Unknown Item (%s)", itemstring
)
501 if iname
== nil then iname
= itemstring
end
502 local printitem
, printcost
506 printitem
= string.format("%d×%s", number, iname
)
509 printcost
= easyvend
.currency_desc
511 printcost
= string.format("%d×%s", cost
, easyvend
.currency_desc
)
513 if nodename
== "easyvend:vendor_on" then
514 d
= string.format("Vending machine (owned by %s)\nSelling: %s\nPrice: %s", owner
, printitem
, printcost
)
515 elseif nodename
== "easyvend:vendor" then
516 d
= string.format("Inactive vending machine (owned by %s)\nSelling: %s\nPrice: %s", owner
, printitem
, printcost
)
517 elseif nodename
== "easyvend:depositor_on" then
518 d
= string.format("Depositing machine (owned by %s)\nBuying: %s\nPayment: %s", owner
, printitem
, printcost
)
519 elseif nodename
== "easyvend:depositor" then
520 d
= string.format("Inactive depositing machine (owned by %s)\nBuying: %s\nPayment: %s", owner
, printitem
, printcost
)
525 if minetest
.get_modpath("awards") then
526 awards
.register_achievement("easyvend_seller",{
527 title
= "First Sale",
528 description
= "Sell something with a vending machine.",
529 icon
= "easyvend_vendor_front_on.png^awards_level1.png",
531 local desc_powerseller
532 if easyvend
.currency
== "default:gold_ingot" then
533 desc_powerseller
= string.format("Earn %d gold ingots by selling goods with a single vending machine.", easyvend
.powerseller
)
535 desc_powerseller
= string.format("Earn %d currency items by selling goods with a single vending machine.", easyvend
.powerseller
)
537 awards
.register_achievement("easyvend_powerseller",{
538 title
= "Power Seller",
539 description
= desc_powerseller
,
540 icon
= "easyvend_vendor_front_on.png^awards_level2.png",
544 easyvend
.check_earnings
= function(buyername
, nodemeta
)
545 local owner
= nodemeta
:get_string("owner")
546 if buyername
~= owner
then
547 local cost
= nodemeta
:get_int("cost")
548 local itemname
= nodemeta
:get_string("itemname")
550 if minetest
.get_modpath("awards") and minetest
.get_player_by_name(owner
) ~= nil then
551 awards
.unlock(owner
, "easyvend_seller")
553 if itemname
~= easyvend
.currency
then
554 local newearnings
= nodemeta
:get_int("earnings") + cost
555 if newearnings
>= easyvend
.powerseller
and minetest
.get_modpath("awards") then
556 if minetest
.get_player_by_name(owner
) ~= nil then
557 awards
.unlock(owner
, "easyvend_powerseller")
560 nodemeta
:set_int("earnings", newearnings
)
565 easyvend
.on_receive_fields_buysell
= function(pos
, formname
, fields
, sender
)
566 local sendername
= sender
:get_player_name()
567 local meta
= minetest
.get_meta(pos
)
569 if not fields
.buysell
then
573 local node
= minetest
.get_node(pos
)
574 local number = meta
:get_int("number")
575 local cost
= meta
:get_int("cost")
576 local itemname
=meta
:get_string("itemname")
577 local item
=meta
:get_inventory():get_stack("item", 1)
578 local check_wear
= meta
:get_int("wear") == 0 and minetest
.registered_tools
[itemname
] ~= nil
579 local machine_owner
= meta
:get_string("owner")
581 local buysell
= easyvend
.buysell(node
.name
)
583 local number_stack_max
= item
:get_stack_max()
584 local maxnumber
= number_stack_max
* slots_max
586 if ( number == nil or number < 1 or number > maxnumber
) or
587 ( cost
== nil or cost
< 1 or cost
> maxcost
) or
588 ( itemname
== nil or itemname
=="") then
589 meta
:set_string("status", "Invalid item count or price!")
590 easyvend
.machine_disable(pos
, node
, sendername
)
594 local chest_pos_remove
, chest_error_remove
, chest_pos_add
, chest_error_add
595 if buysell
== "sell" then
596 chest_pos_remove
, chest_error_remove
= easyvend
.find_connected_chest(machine_owner
, pos
, itemname
, check_wear
, number, true)
597 chest_pos_add
, chest_error_add
= easyvend
.find_connected_chest(machine_owner
, pos
, easyvend
.currency
, check_wear
, cost
, false)
599 chest_pos_remove
, chest_error_remove
= easyvend
.find_connected_chest(machine_owner
, pos
, easyvend
.currency
, check_wear
, cost
, true)
600 chest_pos_add
, chest_error_add
= easyvend
.find_connected_chest(machine_owner
, pos
, itemname
, check_wear
, number, false)
603 if chest_pos_remove
~= nil and chest_pos_add
~= nil and sender
and sender
:is_player() then
604 local rchest
= minetest
.get_node(chest_pos_remove
)
605 local rchestdef
= registered_chests
[rchest
.name
]
606 local rchest_meta
= minetest
.get_meta(chest_pos_remove
)
607 local rchest_inv
= rchest_meta
:get_inventory()
608 local achest
= minetest
.get_node(chest_pos_add
)
609 local achestdef
= registered_chests
[achest
.name
]
610 local achest_meta
= minetest
.get_meta(chest_pos_add
)
611 local achest_inv
= achest_meta
:get_inventory()
613 local player_inv
= sender
:get_inventory()
615 local stack
= {name
=itemname
, count
=number, wear
=0, metadata
=""}
616 local price
= {name
=easyvend
.currency
, count
=cost
, wear
=0, metadata
=""}
617 local chest_has
, player_has
, chest_free
, player_free
, chest_out
, player_out
619 if buysell
== "sell" then
620 chest_has
, chest_out
= easyvend
.check_and_get_items(rchest_inv
, rchestdef
.inv_list
, stack
, check_wear
)
621 player_has
, player_out
= easyvend
.check_and_get_items(player_inv
, "main", price
, check_wear
)
622 chest_free
= achest_inv
:room_for_item(achestdef
.inv_list
, price
)
623 player_free
= player_inv
:room_for_item("main", stack
)
624 if chest_has
and player_has
and chest_free
and player_free
then
625 if cost
<= cost_stack_max
and number <= number_stack_max
then
626 easyvend
.machine_enable(pos
, node
)
627 player_inv
:remove_item("main", price
)
629 rchest_inv
:set_stack(rchestdef
.inv_list
, chest_out
[1].id
, "")
630 player_inv
:add_item("main", chest_out
[1].item
)
632 stack
= rchest_inv
:remove_item(rchestdef
.inv_list
, stack
)
633 player_inv
:add_item("main", stack
)
635 achest_inv
:add_item(achestdef
.inv_list
, price
)
636 if itemname
== easyvend
.currency
and number == cost
and cost
<= cost_stack_max
then
637 meta
:set_string("message", easyvend
.get_joke(buysell
, meta
:get_int("joke_id")))
638 meta
:set_int("joketimer", joketimer_start
)
640 meta
:set_string("message", "Item bought.")
642 easyvend
.check_earnings(sendername
, meta
)
643 easyvend
.sound_vend(pos
)
644 easyvend
.machine_check(pos
, node
)
646 -- Large item counts (multiple stacks)
647 local coststacks
= math
.modf(cost
/ cost_stack_max
)
648 local costremainder
= math
.fmod(cost
, cost_stack_max
)
649 local numberstacks
= math
.modf(number / number_stack_max
)
650 local numberremainder
= math
.fmod(number, number_stack_max
)
651 local numberfree
= numberstacks
652 local costfree
= coststacks
653 if numberremainder
> 0 then numberfree
= numberfree
+ 1 end
654 if costremainder
> 0 then costfree
= costfree
+ 1 end
655 if not player_free
and easyvend
.free_slots(player_inv
, "main") < numberfree
then
656 if numberfree
> 1 then
657 msg
= string.format("No room in your inventory (%d empty slots required)!", numberfree
)
659 msg
= "No room in your inventory!"
661 meta
:set_string("message", msg
)
662 elseif not chest_free
and easyvend
.free_slots(achest_inv
, achestdef
.inv_list
) < costfree
then
663 meta
:set_string("status", "No room in the machine’s storage!")
664 easyvend
.machine_disable(pos
, node
, sendername
)
666 -- Remember items for transfer
667 local cheststacks
= {}
668 easyvend
.machine_enable(pos
, node
)
669 for i
=1, coststacks
do
670 price
.count
= cost_stack_max
671 player_inv
:remove_item("main", price
)
673 if costremainder
> 0 then
674 price
.count
= costremainder
675 player_inv
:remove_item("main", price
)
678 for o
=1,#chest_out
do
679 rchest_inv
:set_stack(rchestdef
.inv_list
, chest_out
[o
].id
, "")
682 for i
=1, numberstacks
do
683 stack
.count
= number_stack_max
684 table.insert(cheststacks
, rchest_inv
:remove_item(rchestdef
.inv_list
, stack
))
687 if numberremainder
> 0 then
688 stack
.count
= numberremainder
689 table.insert(cheststacks
, rchest_inv
:remove_item(rchestdef
.inv_list
, stack
))
691 for i
=1, coststacks
do
692 price
.count
= cost_stack_max
693 achest_inv
:add_item(achestdef
.inv_list
, price
)
695 if costremainder
> 0 then
696 price
.count
= costremainder
697 achest_inv
:add_item(achestdef
.inv_list
, price
)
700 for o
=1,#chest_out
do
701 player_inv
:add_item("main", chest_out
[o
].item
)
704 for i
=1,#cheststacks
do
705 player_inv
:add_item("main", cheststacks
[i
])
708 meta
:set_string("message", "Item bought.")
709 easyvend
.check_earnings(sendername
, meta
)
710 easyvend
.sound_vend(pos
)
711 easyvend
.machine_check(pos
, node
)
714 elseif chest_has
and player_has
then
715 if not player_free
then
716 msg
= "No room in your inventory!"
717 meta
:set_string("message", msg
)
718 easyvend
.sound_error(sendername
)
719 elseif not chest_free
then
720 msg
= "No room in the machine’s storage!"
721 meta
:set_string("status", msg
)
722 easyvend
.machine_disable(pos
, node
, sendername
)
725 if not chest_has
then
726 msg
= "The vending machine has insufficient materials!"
727 meta
:set_string("status", msg
)
728 easyvend
.machine_disable(pos
, node
, sendername
)
729 elseif not player_has
then
730 msg
= "You can’t afford this item!"
731 meta
:set_string("message", msg
)
732 easyvend
.sound_error(sendername
)
736 chest_has
, chest_out
= easyvend
.check_and_get_items(rchest_inv
, rchestdef
.inv_list
, price
, check_wear
)
737 player_has
, player_out
= easyvend
.check_and_get_items(player_inv
, "main", stack
, check_wear
)
738 chest_free
= achest_inv
:room_for_item(achestdef
.inv_list
, stack
)
739 player_free
= player_inv
:room_for_item("main", price
)
740 if chest_has
and player_has
and chest_free
and player_free
then
741 if cost
<= cost_stack_max
and number <= number_stack_max
then
742 easyvend
.machine_enable(pos
, node
)
744 player_inv
:set_stack("main", player_out
[1].id
, "")
745 achest_inv
:add_item(achestdef
.inv_list
, player_out
[1].item
)
747 stack
= player_inv
:remove_item("main", stack
)
748 achest_inv
:add_item(achestdef
.inv_list
, stack
)
750 rchest_inv
:remove_item(rchestdef
.inv_list
, price
)
751 player_inv
:add_item("main", price
)
752 meta
:set_string("status", "Ready.")
753 if itemname
== easyvend
.currency
and number == cost
and cost
<= cost_stack_max
then
754 meta
:set_string("message", easyvend
.get_joke(buysell
, meta
:get_int("joke_id")))
755 meta
:set_int("joketimer", joketimer_start
)
757 meta
:set_string("message", "Item sold.")
759 easyvend
.sound_deposit(pos
)
760 easyvend
.machine_check(pos
, node
)
762 -- Large item counts (multiple stacks)
763 local coststacks
= math
.modf(cost
/ cost_stack_max
)
764 local costremainder
= math
.fmod(cost
, cost_stack_max
)
765 local numberstacks
= math
.modf(number / number_stack_max
)
766 local numberremainder
= math
.fmod(number, number_stack_max
)
767 local numberfree
= numberstacks
768 local costfree
= coststacks
769 if numberremainder
> 0 then numberfree
= numberfree
+ 1 end
770 if costremainder
> 0 then costfree
= costfree
+ 1 end
771 if not player_free
and easyvend
.free_slots(player_inv
, "main") < costfree
then
773 msg
= string.format("No room in your inventory (%d empty slots required)!", costfree
)
775 msg
= "No room in your inventory!"
777 meta
:set_string("message", msg
)
778 easyvend
.sound_error(sendername
)
779 elseif not chest_free
and easyvend
.free_slots(achest_inv
, achestdef
.inv_list
) < numberfree
then
780 meta
:set_string("status", "No room in the machine’s storage!")
781 easyvend
.machine_disable(pos
, node
, sendername
)
783 easyvend
.machine_enable(pos
, node
)
784 -- Remember removed items for transfer
785 local playerstacks
= {}
786 for i
=1, coststacks
do
787 price
.count
= cost_stack_max
788 rchest_inv
:remove_item(rchestdef
.inv_list
, price
)
790 if costremainder
> 0 then
791 price
.count
= costremainder
792 rchest_inv
:remove_item(rchestdef
.inv_list
, price
)
795 for o
=1,#player_out
do
796 player_inv
:set_stack("main", player_out
[o
].id
, "")
799 for i
=1, numberstacks
do
800 stack
.count
= number_stack_max
801 table.insert(playerstacks
, player_inv
:remove_item("main", stack
))
804 if numberremainder
> 0 then
805 stack
.count
= numberremainder
806 table.insert(playerstacks
, player_inv
:remove_item("main", stack
))
808 for i
=1, coststacks
do
809 price
.count
= cost_stack_max
810 player_inv
:add_item("main", price
)
812 if costremainder
> 0 then
813 price
.count
= costremainder
814 player_inv
:add_item("main", price
)
817 for o
=1,#player_out
do
818 achest_inv
:add_item(achestdef
.inv_list
, player_out
[o
].item
)
821 for i
=1,#playerstacks
do
822 achest_inv
:add_item(achestdef
.inv_list
, playerstacks
[i
])
825 meta
:set_string("message", "Item sold.")
826 easyvend
.sound_deposit(pos
)
827 easyvend
.machine_check(pos
, node
)
830 elseif chest_has
and player_has
then
831 if not player_free
then
832 msg
= "No room in your inventory!"
833 meta
:set_string("message", msg
)
834 easyvend
.sound_error(sendername
)
835 elseif not chest_free
then
836 msg
= "No room in the machine’s storage!"
837 meta
:set_string("status", msg
)
838 easyvend
.machine_disable(pos
, node
, sendername
)
841 if not player_has
then
842 msg
= "You have insufficient materials!"
843 meta
:set_string("message", msg
)
844 easyvend
.sound_error(sendername
)
845 elseif not chest_has
then
846 msg
= "The depositing machine is out of money!"
847 meta
:set_string("status", msg
)
848 easyvend
.machine_disable(pos
, node
, sendername
)
854 meta
:set_int("stock", 0)
855 if chest_error_remove
== "no_chest" and chest_error_add
== "no_chest" then
856 status
= "No storage; machine needs to be connected with a locked chest."
857 elseif chest_error_remove
== "not_owned" or chest_error_add
== "not_owned" then
858 status
= "Storage can’t be accessed because it is owned by a different person!"
859 elseif chest_error_remove
== "no_stock" then
860 if buysell
== "sell" then
861 status
= "The vending machine has insufficient materials!"
863 status
= "The depositing machine is out of money!"
865 elseif chest_error_add
== "no_space" then
866 status
= "No room in the machine’s storage!"
868 status
= "Unknown error!"
870 meta
:set_string("status", status
)
871 easyvend
.sound_error(sendername
)
874 easyvend
.set_formspec(pos
, sender
)
878 easyvend
.after_place_node
= function(pos
, placer
)
879 local node
= minetest
.get_node(pos
)
880 local meta
= minetest
.get_meta(pos
)
881 local inv
= meta
:get_inventory()
882 local player_name
= placer
:get_player_name()
883 inv
:set_size("item", 1)
884 inv
:set_size("gold", 1)
886 inv
:set_stack( "gold", 1, easyvend
.currency
)
889 if node
.name
== "easyvend:vendor" then
890 d
= string.format("Inactive vending machine (owned by %s)", player_name
)
891 meta
:set_int("wear", 1)
892 -- Total number of currency items earned for the machine's life time (excluding currency-currency trading)
893 meta
:set_int("earnings", 0)
894 elseif node
.name
== "easyvend:depositor" then
895 d
= string.format("Inactive depositing machine (owned by %s)", player_name
)
896 meta
:set_int("wear", 0)
898 meta
:set_string("infotext", d
)
899 meta
:set_string("status", "Awaiting configuration by owner.")
900 meta
:set_string("message", "Welcome! Please prepare the machine.")
901 meta
:set_int("number", 1)
902 meta
:set_int("cost", 1)
903 meta
:set_int("stock", -1)
904 meta
:set_int("configmode", 1)
905 meta
:set_int("joketimer", -1)
906 meta
:set_int("joke_id", 1)
907 meta
:set_string("itemname", "")
909 meta
:set_string("owner", player_name
or "")
911 easyvend
.set_formspec(pos
, placer
)
914 easyvend
.can_dig
= function(pos
, player
)
915 local meta
= minetest
.get_meta(pos
)
916 local name
= player
:get_player_name()
917 local owner
= meta
:get_string("owner")
918 -- Owner can always dig shop
919 if owner
== name
then
922 local chest_pos
= easyvend
.find_connected_chest(owner
, pos
)
923 local chest
, meta_chest
925 chest
= minetest
.get_node(chest_pos
)
926 meta_chest
= minetest
.get_meta(chest_pos
)
928 return true --if no chest, enyone can dig this shop
930 if registered_chests
[chest
.name
] then
931 if player
and player
:is_player() then
932 local owner_chest
= meta_chest
:get_string(registered_chests
[chest
.name
].meta_owner
)
933 if name
== owner_chest
then
934 return true --chest owner can also dig shop
939 return true --if no chest, enyone can dig this shop
943 easyvend
.on_receive_fields
= function(pos
, formname
, fields
, sender
)
944 local meta
= minetest
.get_meta(pos
)
945 local node
= minetest
.get_node(pos
)
946 local owner
= meta
:get_string("owner")
947 local sendername
= sender
:get_player_name()
950 if minetest
.get_modpath("doc") and minetest
.get_modpath("doc_items") then
951 if easyvend
.buysell(node
.name
) == "buy" then
952 doc
.show_entry(sendername
, "nodes", "easyvend:depositor", true)
954 doc
.show_entry(sendername
, "nodes", "easyvend:vendor", true)
957 elseif fields
.config
or fields
.save
or fields
.usermode
then
958 if sender
:get_player_name() == owner
then
959 easyvend
.on_receive_fields_config(pos
, formname
, fields
, sender
)
961 meta
:set_string("message", "Only the owner may change the configuration.")
962 easyvend
.sound_error(sendername
)
963 easyvend
.set_formspec(pos
, sender
)
966 elseif fields
.wear
~= nil then
967 if sender
:get_player_name() == owner
then
968 if fields
.wear
== "true" then
969 if easyvend
.buysell(node
.name
) == "buy" then
970 meta
:set_string("message", "Used tools are now accepted.")
972 meta
:set_string("message", "Used tools are now for sale.")
974 meta
:set_int("wear", 1)
975 elseif fields
.wear
== "false" then
976 if easyvend
.buysell(node
.name
) == "buy" then
977 meta
:set_string("message", "Used tools are now rejected.")
979 meta
:set_string("message", "Used tools won’t be sold anymore.")
981 meta
:set_int("wear", 0)
983 easyvend
.set_formspec(pos
, sender
)
986 meta
:set_string("message", "Only the owner may change the configuration.")
987 easyvend
.sound_error(sendername
)
988 easyvend
.set_formspec(pos
, sender
)
991 elseif fields
.buysell
then
992 easyvend
.on_receive_fields_buysell(pos
, formname
, fields
, sender
)
996 -- Jokes: Appear when machine exchanges currency for currency at equal rate
999 local jokes_vendor
= {
1000 "Thank you. You have made a vending machine very happy.",
1001 "Humans have a strange sense of humor.",
1002 "Let’s get this over with …",
1005 "Do you realize what you’ve just bought?",
1008 local jokes_depositor
= {
1009 "Thank you, the money started to smell inside.",
1010 "Money doesn’t grow on trees, you know?",
1012 "Well, that was an awkward exchange.",
1013 "Are you having fun?",
1014 "Is this really trading?",
1017 easyvend
.assign_joke
= function(buysell
)
1019 if buysell
== "sell" then
1020 jokes
= jokes_vendor
1021 elseif buysell
== "buy" then
1022 jokes
= jokes_depositor
1024 local r
= math
.random(1,#jokes
)
1028 easyvend
.get_joke
= function(buysell
, id
)
1030 if buysell
== nil or id
== nil then
1031 -- Fallback message (should never happen)
1032 return "Items exchanged."
1034 if buysell
== "sell" then
1035 joke
= jokes_vendor
[id
]
1036 if joke
== nil then joke
= jokes_vendor
[1] end
1037 elseif buysell
== "buy" then
1038 joke
= jokes_depositor
[id
]
1039 if joke
== nil then joke
= jokes_depositor
[1] end
1044 easyvend
.sound_error
= function(playername
)
1045 minetest
.sound_play("easyvend_error", {to_player
= playername
, gain
= 0.25})
1048 easyvend
.sound_setup
= function(pos
)
1049 minetest
.sound_play("easyvend_activate", {pos
= pos
, gain
= 0.5, max_hear_distance
= 12,})
1052 easyvend
.sound_disable
= function(pos
)
1053 minetest
.sound_play("easyvend_disable", {pos
= pos
, gain
= 0.9, max_hear_distance
= 12,})
1056 easyvend
.sound_vend
= function(pos
)
1057 minetest
.sound_play("easyvend_vend", {pos
= pos
, gain
= 0.4, max_hear_distance
= 5,})
1060 easyvend
.sound_deposit
= function(pos
)
1061 minetest
.sound_play("easyvend_deposit", {pos
= pos
, gain
= 0.4, max_hear_distance
= 5,})
1064 --[[ Tower building ]]
1066 easyvend
.is_traversable
= function(pos
)
1067 local node
= minetest
.get_node_or_nil(pos
)
1068 if (node
== nil) then
1071 return traversable_node_types
[node
.name
] == true
1074 easyvend
.neighboring_nodes
= function(pos
)
1076 {x
=pos
.x
, y
=pos
.y
-1, z
=pos
.z
},
1077 {x
=pos
.x
, y
=pos
.y
+1, z
=pos
.z
},
1081 if easyvend
.is_traversable(check
[i
]) then
1082 table.insert(trav
, check
[i
])
1088 easyvend
.find_connected_chest
= function(owner
, pos
, nodename
, check_wear
, amount
, removing
)
1089 local nodes
= easyvend
.neighboring_nodes(pos
)
1091 if (#nodes
< 1 or #nodes
> 2) then
1092 return nil, "no_chest"
1095 -- Find the stack direction
1099 if ( first
== nil ) then
1106 local chest_pos
, chest_internal
1108 if (first
~= nil and second
~= nil) then
1109 local dy
= (first
.y
- second
.y
)/2
1110 chest_pos
, chest_internal
= easyvend
.find_chest(owner
, pos
, dy
, nodename
, check_wear
, amount
, removing
)
1111 if ( chest_pos
== nil ) then
1112 chest_pos
, chest_internal
= easyvend
.find_chest(owner
, pos
, -dy
, nodename
, check_wear
, amount
, removing
, chest_internal
)
1115 local dy
= first
.y
- pos
.y
1116 chest_pos
, chest_internal
= easyvend
.find_chest(owner
, pos
, dy
, nodename
, check_wear
, amount
, removing
)
1119 if chest_internal
.chests
== 0 then
1120 return nil, "no_chest"
1121 elseif chest_internal
.chests
== chest_internal
.other_chests
then
1122 return nil, "not_owned"
1123 elseif removing
and chest_internal
.stock
< 1 then
1124 return nil, "no_stock"
1125 elseif not removing
and chest_internal
.space
< 1 then
1126 return nil, "no_space"
1127 elseif chest_pos
~= nil then
1130 return nil, "unknown"
1134 easyvend
.find_chest
= function(owner
, pos
, dy
, itemname
, check_wear
, amount
, removing
, internal
)
1135 pos
= {x
=pos
.x
, y
=pos
.y
+ dy
, z
=pos
.z
}
1137 if internal
== nil then
1140 internal
.other_chests
= 0
1145 local node
= minetest
.get_node_or_nil(pos
)
1146 if ( node
== nil ) then
1147 return nil, internal
1149 local chestdef
= registered_chests
[node
.name
]
1150 if (chestdef
~= nil) then
1151 internal
.chests
= internal
.chests
+ 1
1152 local meta
= minetest
.get_meta(pos
)
1153 if (owner
~= meta
:get_string(chestdef
.meta_owner
)) then
1154 internal
.other_chests
= internal
.other_chests
+ 1
1155 return nil, internal
1157 local inv
= meta
:get_inventory()
1158 if (inv
~= nil) then
1159 if (itemname
~= nil and minetest
.registered_items
[itemname
] and amount
~= nil and removing
~= nil and check_wear
~= nil) then
1160 local chest_has
, chest_free
1161 local stack
= {name
=itemname
, count
=amount
, wear
=0, metadata
=""}
1162 local stack_max
= minetest
.registered_items
[itemname
].stack_max
1164 local stacks
= math
.modf(amount
/ stack_max
)
1165 local stacksremainder
= math
.fmod(amount
, stack_max
)
1167 if stacksremainder
> 0 then free
= free
+ 1 end
1169 chest_has
= easyvend
.check_and_get_items(inv
, chestdef
.inv_list
, stack
, check_wear
)
1171 internal
.stock
= internal
.stock
+ 1
1173 chest_free
= inv
:room_for_item(chestdef
.inv_list
, stack
) and easyvend
.free_slots(inv
, chestdef
.inv_list
) >= free
1175 internal
.space
= internal
.space
+ 1
1178 if (removing
and internal
.stock
== 0) or (not removing
and internal
.space
== 0) then
1179 return easyvend
.find_chest(owner
, pos
, dy
, itemname
, check_wear
, amount
, removing
, internal
)
1181 return pos
, internal
1184 return nil, internal
1187 return nil, internal
1189 elseif (node
.name
~= "easyvend:vendor" and node
.name
~="easyvend:depositor" and node
.name
~="easyvend:vendor_on" and node
.name
~="easyvend:depositor_on") then
1190 return nil, internal
1193 return easyvend
.find_chest(owner
, pos
, dy
, itemname
, check_wear
, amount
, removing
, internal
)
1196 -- Pseudo-inventory handling
1197 easyvend
.allow_metadata_inventory_put
= function(pos
, listname
, index
, stack
, player
)
1198 if listname
=="item" then
1199 local meta
= minetest
.get_meta(pos
);
1200 local owner
= meta
:get_string("owner")
1201 local name
= player
:get_player_name()
1202 if name
== owner
then
1203 local inv
= meta
:get_inventory()
1205 inv
:set_stack( "item", 1, nil )
1207 inv
:set_stack( "item", 1, stack
:get_name() )
1208 meta
:set_string("itemname", stack
:get_name())
1209 easyvend
.set_formspec(pos
, player
)
1216 easyvend
.allow_metadata_inventory_take
= function(pos
, listname
, index
, stack
, player
)
1220 easyvend
.allow_metadata_inventory_move
= function(pos
, from_list
, from_index
, to_list
, to_index
, count
, player
)
1224 minetest
.register_abm({
1225 nodenames
= {"easyvend:vendor", "easyvend:vendor_on", "easyvend:depositor", "easyvend:depositor_on"},
1229 action
= function(pos
, node
, active_object_count
, active_object_count_wider
)
1230 easyvend
.machine_check(pos
, node
)
1234 -- Legacy support for vendor mod:
1235 -- Transform the world and items to use the easyvend nodes/items
1237 -- For safety reasons, only do this when player requested so
1238 if minetest
.setting_getbool("easyvend_convert_vendor") == true then
1239 -- Replace vendor nodes
1240 minetest
.register_lbm({
1241 name
= "easyvend:replace_vendor",
1242 nodenames
= { "vendor:vendor", "vendor:depositor" },
1243 run_at_every_load
= true,
1244 action
= function(pos
, node
)
1247 if node
.name
== "vendor:vendor" then
1248 newnodename
= "easyvend:vendor"
1249 elseif node
.name
== "vendor:depositor" then
1250 newnodename
= "easyvend:depositor"
1252 -- Remove axis rotation; only allow 4 facedirs
1253 local p2
= math
.fmod(node
.param2
, 4)
1254 minetest
.swap_node(pos
, { name
= newnodename
, param2
= p2
})
1256 -- Initialize metadata
1257 local meta
= minetest
.get_meta(pos
)
1258 if node
.name
== "vendor:vendor" then
1259 meta
:set_int("earnings", 0)
1261 meta
:set_int("stock", -1)
1262 meta
:set_int("joketimer", -1)
1263 meta
:set_int("joke_id", 1)
1264 local inv
= meta
:get_inventory()
1265 inv
:set_size("item", 1)
1266 inv
:set_size("gold", 1)
1267 inv
:set_stack("gold", 1, easyvend
.currency
)
1269 -- In vendor, all machines accepted worn tools
1270 meta
:set_int("wear", 1)
1273 local itemname
= meta
:get_string("itemname")
1274 if itemname
== "" or itemname
== nil then
1275 itemname
= meta
:get_string("itemtype")
1277 if itemname
~= "" and itemname
~= nil then
1278 inv
:set_stack("item", 1, itemname
)
1279 meta
:set_string("itemname", itemname
)
1282 -- Check for valid item, item count and price
1283 local configmode
= 1
1284 if itemname
~= "" and itemname
~= nil then
1285 local itemstack
= inv
:get_stack("item", 1)
1286 local number_stack_max
= itemstack
:get_stack_max()
1287 local maxnumber
= number_stack_max
* slots_max
1288 local cost
= meta
:get_int("cost")
1289 local number = meta
:get_int("number")
1290 if number >= 1 and number <= maxnumber
and cost
>= 1 and cost
<= maxcost
then
1291 -- Everything's OK, get out of config mode!
1296 -- Final initialization stuff
1297 meta
:set_int("configmode", configmode
)
1299 local owner
= meta
:get_string("owner")
1300 if easyvend
.buysell(newnodename
) == "sell" then
1301 meta
:set_string("infotext", string.format("Vending machine (owned by %s)", owner
))
1303 meta
:set_string("infotext", string.format("Depositing machine (owned by %s)", owner
))
1307 meta
:set_string("status", "Initializing …")
1308 meta
:set_string("message", "Upgrade successful.")
1309 easyvend
.machine_check(pos
, node
)