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 local number_stack_max
= itemstack
:get_stack_max()
401 local ss
= string.sub(number, #number, #number)
402 if ss
== "s" or ss
== "S" then
403 local n
= tonumber(string.sub(number, 1, #number-1))
404 if string.len(number) == 1 then n
= 1 end
406 number = n
* number_stack_max
409 ss
= string.sub(cost
, #cost
, #cost
)
410 if ss
== "s" or ss
== "S" then
411 local n
= tonumber(string.sub(cost
, 1, #cost
-1))
412 if string.len(cost
) == 1 then n
= 1 end
414 cost
= n
* cost_stack_max
417 number = tonumber(number)
418 cost
= tonumber(cost
)
422 local oldnumber
= meta
:get_int("number")
423 local oldcost
= meta
:get_int("cost")
424 local maxnumber
= number_stack_max
* slots_max
426 if ( itemstack
== nil or itemstack
:is_empty() ) then
427 meta
:set_string("status", "Awaiting configuration by owner.")
428 meta
:set_string("message", "No item specified.")
429 easyvend
.sound_error(sender
:get_player_name())
430 easyvend
.set_formspec(pos
, sender
)
432 elseif ( not itemstack
:is_known() ) then
433 meta
:set_string("status", "Awaiting configuration by owner.")
434 meta
:set_string("message", "Unknown item specified.")
435 easyvend
.sound_error(sender
:get_player_name())
436 easyvend
.set_formspec(pos
, sender
)
438 elseif ( number == nil or number < 1 or number > maxnumber
) then
439 if maxnumber
> 1 then
440 meta
:set_string("message", string.format("Invalid item count; must be between 1 and %d!", maxnumber
))
442 meta
:set_string("message", "Invalid item count; must be exactly 1!")
444 meta
:set_int("number", oldnumber
)
445 easyvend
.sound_error(sender
:get_player_name())
446 easyvend
.set_formspec(pos
, sender
)
448 elseif ( cost
== nil or cost
< 1 or cost
> maxcost
) then
450 meta
:set_string("message", string.format("Invalid cost; must be between 1 and %d!", maxcost
))
452 meta
:set_string("message", "Invalid cost; must be exactly 1!")
454 meta
:set_int("cost", oldcost
)
455 easyvend
.sound_error(sender
:get_player_name())
456 easyvend
.set_formspec(pos
, sender
)
459 meta
:set_int("number", number)
460 meta
:set_int("cost", cost
)
461 itemname
=itemstack
:get_name()
462 meta
:set_string("itemname", itemname
)
463 meta
:set_int("configmode", 0)
465 if itemname
== easyvend
.currency
and number == cost
and cost
<= cost_stack_max
then
466 meta
:set_string("message", "Configuration successful. I am feeling funny.")
467 meta
:set_int("joketimer", joketimer_start
)
468 meta
:set_int("joke_id", easyvend
.assign_joke(buysell
))
470 meta
:set_string("message", "Configuration successful.")
473 local change
= easyvend
.machine_check(pos
, node
)
476 if (node
.name
== "easyvend:vendor_on" or node
.name
== "easyvend:depositor_on") then
477 easyvend
.sound_setup(pos
)
479 easyvend
.sound_disable(pos
)
484 easyvend
.make_infotext
= function(nodename
, owner
, cost
, number, itemstring
)
486 if itemstring
== nil or itemstring
== "" or number == 0 or cost
== 0 then
487 if easyvend
.buysell(nodename
) == "sell" then
488 d
= string.format("Inactive vending machine (owned by %s)", owner
)
490 d
= string.format("Inactive depositing machine (owned by %s)", owner
)
495 if minetest
.registered_items
[itemstring
] then
496 iname
= minetest
.registered_items
[itemstring
].description
498 iname
= string.format("Unknown Item (%s)", itemstring
)
500 if iname
== nil then iname
= itemstring
end
501 local printitem
, printcost
505 printitem
= string.format("%d×%s", number, iname
)
508 printcost
= easyvend
.currency_desc
510 printcost
= string.format("%d×%s", cost
, easyvend
.currency_desc
)
512 if nodename
== "easyvend:vendor_on" then
513 d
= string.format("Vending machine (owned by %s)\nSelling: %s\nPrice: %s", owner
, printitem
, printcost
)
514 elseif nodename
== "easyvend:vendor" then
515 d
= string.format("Inactive vending machine (owned by %s)\nSelling: %s\nPrice: %s", owner
, printitem
, printcost
)
516 elseif nodename
== "easyvend:depositor_on" then
517 d
= string.format("Depositing machine (owned by %s)\nBuying: %s\nPayment: %s", owner
, printitem
, printcost
)
518 elseif nodename
== "easyvend:depositor" then
519 d
= string.format("Inactive depositing machine (owned by %s)\nBuying: %s\nPayment: %s", owner
, printitem
, printcost
)
524 if minetest
.get_modpath("awards") then
525 awards
.register_achievement("easyvend_seller",{
526 title
= "First Sale",
527 description
= "Sell something with a vending machine.",
528 icon
= "easyvend_vendor_front_on.png^awards_level1.png",
530 local desc_powerseller
531 if easyvend
.currency
== "default:gold_ingot" then
532 desc_powerseller
= string.format("Earn %d gold ingots by selling goods with a single vending machine.", easyvend
.powerseller
)
534 desc_powerseller
= string.format("Earn %d currency items by selling goods with a single vending machine.", easyvend
.powerseller
)
536 awards
.register_achievement("easyvend_powerseller",{
537 title
= "Power Seller",
538 description
= desc_powerseller
,
539 icon
= "easyvend_vendor_front_on.png^awards_level2.png",
543 easyvend
.check_earnings
= function(buyername
, nodemeta
)
544 local owner
= nodemeta
:get_string("owner")
545 if buyername
~= owner
then
546 local cost
= nodemeta
:get_int("cost")
547 local itemname
= nodemeta
:get_string("itemname")
549 if minetest
.get_modpath("awards") and minetest
.get_player_by_name(owner
) ~= nil then
550 awards
.unlock(owner
, "easyvend_seller")
552 if itemname
~= easyvend
.currency
then
553 local newearnings
= nodemeta
:get_int("earnings") + cost
554 if newearnings
>= easyvend
.powerseller
and minetest
.get_modpath("awards") then
555 if minetest
.get_player_by_name(owner
) ~= nil then
556 awards
.unlock(owner
, "easyvend_powerseller")
559 nodemeta
:set_int("earnings", newearnings
)
564 easyvend
.on_receive_fields_buysell
= function(pos
, formname
, fields
, sender
)
565 local sendername
= sender
:get_player_name()
566 local meta
= minetest
.get_meta(pos
)
568 if not fields
.buysell
then
572 local node
= minetest
.get_node(pos
)
573 local number = meta
:get_int("number")
574 local cost
= meta
:get_int("cost")
575 local itemname
=meta
:get_string("itemname")
576 local item
=meta
:get_inventory():get_stack("item", 1)
577 local check_wear
= meta
:get_int("wear") == 0 and minetest
.registered_tools
[itemname
] ~= nil
578 local machine_owner
= meta
:get_string("owner")
580 local buysell
= easyvend
.buysell(node
.name
)
582 local number_stack_max
= item
:get_stack_max()
583 local maxnumber
= number_stack_max
* slots_max
585 if ( number == nil or number < 1 or number > maxnumber
) or
586 ( cost
== nil or cost
< 1 or cost
> maxcost
) or
587 ( itemname
== nil or itemname
=="") then
588 meta
:set_string("status", "Invalid item count or price!")
589 easyvend
.machine_disable(pos
, node
, sendername
)
593 local chest_pos_remove
, chest_error_remove
, chest_pos_add
, chest_error_add
594 if buysell
== "sell" then
595 chest_pos_remove
, chest_error_remove
= easyvend
.find_connected_chest(machine_owner
, pos
, itemname
, check_wear
, number, true)
596 chest_pos_add
, chest_error_add
= easyvend
.find_connected_chest(machine_owner
, pos
, easyvend
.currency
, check_wear
, cost
, false)
598 chest_pos_remove
, chest_error_remove
= easyvend
.find_connected_chest(machine_owner
, pos
, easyvend
.currency
, check_wear
, cost
, true)
599 chest_pos_add
, chest_error_add
= easyvend
.find_connected_chest(machine_owner
, pos
, itemname
, check_wear
, number, false)
602 if chest_pos_remove
~= nil and chest_pos_add
~= nil and sender
and sender
:is_player() then
603 local rchest
= minetest
.get_node(chest_pos_remove
)
604 local rchestdef
= registered_chests
[rchest
.name
]
605 local rchest_meta
= minetest
.get_meta(chest_pos_remove
)
606 local rchest_inv
= rchest_meta
:get_inventory()
607 local achest
= minetest
.get_node(chest_pos_add
)
608 local achestdef
= registered_chests
[achest
.name
]
609 local achest_meta
= minetest
.get_meta(chest_pos_add
)
610 local achest_inv
= achest_meta
:get_inventory()
612 local player_inv
= sender
:get_inventory()
614 local stack
= {name
=itemname
, count
=number, wear
=0, metadata
=""}
615 local price
= {name
=easyvend
.currency
, count
=cost
, wear
=0, metadata
=""}
616 local chest_has
, player_has
, chest_free
, player_free
, chest_out
, player_out
618 if buysell
== "sell" then
619 chest_has
, chest_out
= easyvend
.check_and_get_items(rchest_inv
, rchestdef
.inv_list
, stack
, check_wear
)
620 player_has
, player_out
= easyvend
.check_and_get_items(player_inv
, "main", price
, check_wear
)
621 chest_free
= achest_inv
:room_for_item(achestdef
.inv_list
, price
)
622 player_free
= player_inv
:room_for_item("main", stack
)
623 if chest_has
and player_has
and chest_free
and player_free
then
624 if cost
<= cost_stack_max
and number <= number_stack_max
then
625 easyvend
.machine_enable(pos
, node
)
626 player_inv
:remove_item("main", price
)
628 rchest_inv
:set_stack(rchestdef
.inv_list
, chest_out
[1].id
, "")
629 player_inv
:add_item("main", chest_out
[1].item
)
631 stack
= rchest_inv
:remove_item(rchestdef
.inv_list
, stack
)
632 player_inv
:add_item("main", stack
)
634 achest_inv
:add_item(achestdef
.inv_list
, price
)
635 if itemname
== easyvend
.currency
and number == cost
and cost
<= cost_stack_max
then
636 meta
:set_string("message", easyvend
.get_joke(buysell
, meta
:get_int("joke_id")))
637 meta
:set_int("joketimer", joketimer_start
)
639 meta
:set_string("message", "Item bought.")
641 easyvend
.check_earnings(sendername
, meta
)
642 easyvend
.sound_vend(pos
)
643 easyvend
.machine_check(pos
, node
)
645 -- Large item counts (multiple stacks)
646 local coststacks
= math
.modf(cost
/ cost_stack_max
)
647 local costremainder
= math
.fmod(cost
, cost_stack_max
)
648 local numberstacks
= math
.modf(number / number_stack_max
)
649 local numberremainder
= math
.fmod(number, number_stack_max
)
650 local numberfree
= numberstacks
651 local costfree
= coststacks
652 if numberremainder
> 0 then numberfree
= numberfree
+ 1 end
653 if costremainder
> 0 then costfree
= costfree
+ 1 end
654 if not player_free
and easyvend
.free_slots(player_inv
, "main") < numberfree
then
655 if numberfree
> 1 then
656 msg
= string.format("No room in your inventory (%d empty slots required)!", numberfree
)
658 msg
= "No room in your inventory!"
660 meta
:set_string("message", msg
)
661 elseif not chest_free
and easyvend
.free_slots(achest_inv
, achestdef
.inv_list
) < costfree
then
662 meta
:set_string("status", "No room in the machine’s storage!")
663 easyvend
.machine_disable(pos
, node
, sendername
)
665 -- Remember items for transfer
666 local cheststacks
= {}
667 easyvend
.machine_enable(pos
, node
)
668 for i
=1, coststacks
do
669 price
.count
= cost_stack_max
670 player_inv
:remove_item("main", price
)
672 if costremainder
> 0 then
673 price
.count
= costremainder
674 player_inv
:remove_item("main", price
)
677 for o
=1,#chest_out
do
678 rchest_inv
:set_stack(rchestdef
.inv_list
, chest_out
[o
].id
, "")
681 for i
=1, numberstacks
do
682 stack
.count
= number_stack_max
683 table.insert(cheststacks
, rchest_inv
:remove_item(rchestdef
.inv_list
, stack
))
686 if numberremainder
> 0 then
687 stack
.count
= numberremainder
688 table.insert(cheststacks
, rchest_inv
:remove_item(rchestdef
.inv_list
, stack
))
690 for i
=1, coststacks
do
691 price
.count
= cost_stack_max
692 achest_inv
:add_item(achestdef
.inv_list
, price
)
694 if costremainder
> 0 then
695 price
.count
= costremainder
696 achest_inv
:add_item(achestdef
.inv_list
, price
)
699 for o
=1,#chest_out
do
700 player_inv
:add_item("main", chest_out
[o
].item
)
703 for i
=1,#cheststacks
do
704 player_inv
:add_item("main", cheststacks
[i
])
707 meta
:set_string("message", "Item bought.")
708 easyvend
.check_earnings(sendername
, meta
)
709 easyvend
.sound_vend(pos
)
710 easyvend
.machine_check(pos
, node
)
713 elseif chest_has
and player_has
then
714 if not player_free
then
715 msg
= "No room in your inventory!"
716 meta
:set_string("message", msg
)
717 easyvend
.sound_error(sendername
)
718 elseif not chest_free
then
719 msg
= "No room in the machine’s storage!"
720 meta
:set_string("status", msg
)
721 easyvend
.machine_disable(pos
, node
, sendername
)
724 if not chest_has
then
725 msg
= "The vending machine has insufficient materials!"
726 meta
:set_string("status", msg
)
727 easyvend
.machine_disable(pos
, node
, sendername
)
728 elseif not player_has
then
729 msg
= "You can’t afford this item!"
730 meta
:set_string("message", msg
)
731 easyvend
.sound_error(sendername
)
735 chest_has
, chest_out
= easyvend
.check_and_get_items(rchest_inv
, rchestdef
.inv_list
, price
, check_wear
)
736 player_has
, player_out
= easyvend
.check_and_get_items(player_inv
, "main", stack
, check_wear
)
737 chest_free
= achest_inv
:room_for_item(achestdef
.inv_list
, stack
)
738 player_free
= player_inv
:room_for_item("main", price
)
739 if chest_has
and player_has
and chest_free
and player_free
then
740 if cost
<= cost_stack_max
and number <= number_stack_max
then
741 easyvend
.machine_enable(pos
, node
)
743 player_inv
:set_stack("main", player_out
[1].id
, "")
744 achest_inv
:add_item(achestdef
.inv_list
, player_out
[1].item
)
746 stack
= player_inv
:remove_item("main", stack
)
747 achest_inv
:add_item(achestdef
.inv_list
, stack
)
749 rchest_inv
:remove_item(rchestdef
.inv_list
, price
)
750 player_inv
:add_item("main", price
)
751 meta
:set_string("status", "Ready.")
752 if itemname
== easyvend
.currency
and number == cost
and cost
<= cost_stack_max
then
753 meta
:set_string("message", easyvend
.get_joke(buysell
, meta
:get_int("joke_id")))
754 meta
:set_int("joketimer", joketimer_start
)
756 meta
:set_string("message", "Item sold.")
758 easyvend
.sound_deposit(pos
)
759 easyvend
.machine_check(pos
, node
)
761 -- Large item counts (multiple stacks)
762 local coststacks
= math
.modf(cost
/ cost_stack_max
)
763 local costremainder
= math
.fmod(cost
, cost_stack_max
)
764 local numberstacks
= math
.modf(number / number_stack_max
)
765 local numberremainder
= math
.fmod(number, number_stack_max
)
766 local numberfree
= numberstacks
767 local costfree
= coststacks
768 if numberremainder
> 0 then numberfree
= numberfree
+ 1 end
769 if costremainder
> 0 then costfree
= costfree
+ 1 end
770 if not player_free
and easyvend
.free_slots(player_inv
, "main") < costfree
then
772 msg
= string.format("No room in your inventory (%d empty slots required)!", costfree
)
774 msg
= "No room in your inventory!"
776 meta
:set_string("message", msg
)
777 easyvend
.sound_error(sendername
)
778 elseif not chest_free
and easyvend
.free_slots(achest_inv
, achestdef
.inv_list
) < numberfree
then
779 meta
:set_string("status", "No room in the machine’s storage!")
780 easyvend
.machine_disable(pos
, node
, sendername
)
782 easyvend
.machine_enable(pos
, node
)
783 -- Remember removed items for transfer
784 local playerstacks
= {}
785 for i
=1, coststacks
do
786 price
.count
= cost_stack_max
787 rchest_inv
:remove_item(rchestdef
.inv_list
, price
)
789 if costremainder
> 0 then
790 price
.count
= costremainder
791 rchest_inv
:remove_item(rchestdef
.inv_list
, price
)
794 for o
=1,#player_out
do
795 player_inv
:set_stack("main", player_out
[o
].id
, "")
798 for i
=1, numberstacks
do
799 stack
.count
= number_stack_max
800 table.insert(playerstacks
, player_inv
:remove_item("main", stack
))
803 if numberremainder
> 0 then
804 stack
.count
= numberremainder
805 table.insert(playerstacks
, player_inv
:remove_item("main", stack
))
807 for i
=1, coststacks
do
808 price
.count
= cost_stack_max
809 player_inv
:add_item("main", price
)
811 if costremainder
> 0 then
812 price
.count
= costremainder
813 player_inv
:add_item("main", price
)
816 for o
=1,#player_out
do
817 achest_inv
:add_item(achestdef
.inv_list
, player_out
[o
].item
)
820 for i
=1,#playerstacks
do
821 achest_inv
:add_item(achestdef
.inv_list
, playerstacks
[i
])
824 meta
:set_string("message", "Item sold.")
825 easyvend
.sound_deposit(pos
)
826 easyvend
.machine_check(pos
, node
)
829 elseif chest_has
and player_has
then
830 if not player_free
then
831 msg
= "No room in your inventory!"
832 meta
:set_string("message", msg
)
833 easyvend
.sound_error(sendername
)
834 elseif not chest_free
then
835 msg
= "No room in the machine’s storage!"
836 meta
:set_string("status", msg
)
837 easyvend
.machine_disable(pos
, node
, sendername
)
840 if not player_has
then
841 msg
= "You have insufficient materials!"
842 meta
:set_string("message", msg
)
843 easyvend
.sound_error(sendername
)
844 elseif not chest_has
then
845 msg
= "The depositing machine is out of money!"
846 meta
:set_string("status", msg
)
847 easyvend
.machine_disable(pos
, node
, sendername
)
853 meta
:set_int("stock", 0)
854 if chest_error_remove
== "no_chest" and chest_error_add
== "no_chest" then
855 status
= "No storage; machine needs to be connected with a locked chest."
856 elseif chest_error_remove
== "not_owned" or chest_error_add
== "not_owned" then
857 status
= "Storage can’t be accessed because it is owned by a different person!"
858 elseif chest_error_remove
== "no_stock" then
859 if buysell
== "sell" then
860 status
= "The vending machine has insufficient materials!"
862 status
= "The depositing machine is out of money!"
864 elseif chest_error_add
== "no_space" then
865 status
= "No room in the machine’s storage!"
867 status
= "Unknown error!"
869 meta
:set_string("status", status
)
870 easyvend
.sound_error(sendername
)
873 easyvend
.set_formspec(pos
, sender
)
877 easyvend
.after_place_node
= function(pos
, placer
)
878 local node
= minetest
.get_node(pos
)
879 local meta
= minetest
.get_meta(pos
)
880 local inv
= meta
:get_inventory()
881 local player_name
= placer
:get_player_name()
882 inv
:set_size("item", 1)
883 inv
:set_size("gold", 1)
885 inv
:set_stack( "gold", 1, easyvend
.currency
)
888 if node
.name
== "easyvend:vendor" then
889 d
= string.format("Inactive vending machine (owned by %s)", player_name
)
890 meta
:set_int("wear", 1)
891 -- Total number of currency items earned for the machine's life time (excluding currency-currency trading)
892 meta
:set_int("earnings", 0)
893 elseif node
.name
== "easyvend:depositor" then
894 d
= string.format("Inactive depositing machine (owned by %s)", player_name
)
895 meta
:set_int("wear", 0)
897 meta
:set_string("infotext", d
)
898 meta
:set_string("status", "Awaiting configuration by owner.")
899 meta
:set_string("message", "Welcome! Please prepare the machine.")
900 meta
:set_int("number", 1)
901 meta
:set_int("cost", 1)
902 meta
:set_int("stock", -1)
903 meta
:set_int("configmode", 1)
904 meta
:set_int("joketimer", -1)
905 meta
:set_int("joke_id", 1)
906 meta
:set_string("itemname", "")
908 meta
:set_string("owner", player_name
or "")
910 easyvend
.set_formspec(pos
, placer
)
913 easyvend
.can_dig
= function(pos
, player
)
914 local meta
= minetest
.get_meta(pos
)
915 local name
= player
:get_player_name()
916 local owner
= meta
:get_string("owner")
917 -- Owner can always dig shop
918 if owner
== name
then
921 local chest_pos
= easyvend
.find_connected_chest(owner
, pos
)
922 local chest
, meta_chest
924 chest
= minetest
.get_node(chest_pos
)
925 meta_chest
= minetest
.get_meta(chest_pos
)
927 return true --if no chest, enyone can dig this shop
929 if registered_chests
[chest
.name
] then
930 if player
and player
:is_player() then
931 local owner_chest
= meta_chest
:get_string(registered_chests
[chest
.name
].meta_owner
)
932 if name
== owner_chest
then
933 return true --chest owner can also dig shop
938 return true --if no chest, enyone can dig this shop
942 easyvend
.on_receive_fields
= function(pos
, formname
, fields
, sender
)
943 local meta
= minetest
.get_meta(pos
)
944 local node
= minetest
.get_node(pos
)
945 local owner
= meta
:get_string("owner")
946 local sendername
= sender
:get_player_name()
949 if minetest
.get_modpath("doc") and minetest
.get_modpath("doc_items") then
950 if easyvend
.buysell(node
.name
) == "buy" then
951 doc
.show_entry(sendername
, "nodes", "easyvend:depositor", true)
953 doc
.show_entry(sendername
, "nodes", "easyvend:vendor", true)
956 elseif fields
.config
or fields
.save
or fields
.usermode
then
957 if sender
:get_player_name() == owner
then
958 easyvend
.on_receive_fields_config(pos
, formname
, fields
, sender
)
960 meta
:set_string("message", "Only the owner may change the configuration.")
961 easyvend
.sound_error(sendername
)
962 easyvend
.set_formspec(pos
, sender
)
965 elseif fields
.wear
~= nil then
966 if sender
:get_player_name() == owner
then
967 if fields
.wear
== "true" then
968 if easyvend
.buysell(node
.name
) == "buy" then
969 meta
:set_string("message", "Used tools are now accepted.")
971 meta
:set_string("message", "Used tools are now for sale.")
973 meta
:set_int("wear", 1)
974 elseif fields
.wear
== "false" then
975 if easyvend
.buysell(node
.name
) == "buy" then
976 meta
:set_string("message", "Used tools are now rejected.")
978 meta
:set_string("message", "Used tools won’t be sold anymore.")
980 meta
:set_int("wear", 0)
982 easyvend
.set_formspec(pos
, sender
)
985 meta
:set_string("message", "Only the owner may change the configuration.")
986 easyvend
.sound_error(sendername
)
987 easyvend
.set_formspec(pos
, sender
)
990 elseif fields
.buysell
then
991 easyvend
.on_receive_fields_buysell(pos
, formname
, fields
, sender
)
995 -- Jokes: Appear when machine exchanges currency for currency at equal rate
998 local jokes_vendor
= {
999 "Thank you. You have made a vending machine very happy.",
1000 "Humans have a strange sense of humor.",
1001 "Let’s get this over with …",
1004 "Do you realize what you’ve just bought?",
1007 local jokes_depositor
= {
1008 "Thank you, the money started to smell inside.",
1009 "Money doesn’t grow on trees, you know?",
1011 "Well, that was an awkward exchange.",
1012 "Are you having fun?",
1013 "Is this really trading?",
1016 easyvend
.assign_joke
= function(buysell
)
1018 if buysell
== "sell" then
1019 jokes
= jokes_vendor
1020 elseif buysell
== "buy" then
1021 jokes
= jokes_depositor
1023 local r
= math
.random(1,#jokes
)
1027 easyvend
.get_joke
= function(buysell
, id
)
1029 if buysell
== nil or id
== nil then
1030 -- Fallback message (should never happen)
1031 return "Items exchanged."
1033 if buysell
== "sell" then
1034 joke
= jokes_vendor
[id
]
1035 if joke
== nil then joke
= jokes_vendor
[1] end
1036 elseif buysell
== "buy" then
1037 joke
= jokes_depositor
[id
]
1038 if joke
== nil then joke
= jokes_depositor
[1] end
1043 easyvend
.sound_error
= function(playername
)
1044 minetest
.sound_play("easyvend_error", {to_player
= playername
, gain
= 0.25})
1047 easyvend
.sound_setup
= function(pos
)
1048 minetest
.sound_play("easyvend_activate", {pos
= pos
, gain
= 0.5, max_hear_distance
= 12,})
1051 easyvend
.sound_disable
= function(pos
)
1052 minetest
.sound_play("easyvend_disable", {pos
= pos
, gain
= 0.9, max_hear_distance
= 12,})
1055 easyvend
.sound_vend
= function(pos
)
1056 minetest
.sound_play("easyvend_vend", {pos
= pos
, gain
= 0.4, max_hear_distance
= 5,})
1059 easyvend
.sound_deposit
= function(pos
)
1060 minetest
.sound_play("easyvend_deposit", {pos
= pos
, gain
= 0.4, max_hear_distance
= 5,})
1063 --[[ Tower building ]]
1065 easyvend
.is_traversable
= function(pos
)
1066 local node
= minetest
.get_node_or_nil(pos
)
1067 if (node
== nil) then
1070 return traversable_node_types
[node
.name
] == true
1073 easyvend
.neighboring_nodes
= function(pos
)
1075 {x
=pos
.x
, y
=pos
.y
-1, z
=pos
.z
},
1076 {x
=pos
.x
, y
=pos
.y
+1, z
=pos
.z
},
1080 if easyvend
.is_traversable(check
[i
]) then
1081 table.insert(trav
, check
[i
])
1087 easyvend
.find_connected_chest
= function(owner
, pos
, nodename
, check_wear
, amount
, removing
)
1088 local nodes
= easyvend
.neighboring_nodes(pos
)
1090 if (#nodes
< 1 or #nodes
> 2) then
1091 return nil, "no_chest"
1094 -- Find the stack direction
1098 if ( first
== nil ) then
1105 local chest_pos
, chest_internal
1107 if (first
~= nil and second
~= nil) then
1108 local dy
= (first
.y
- second
.y
)/2
1109 chest_pos
, chest_internal
= easyvend
.find_chest(owner
, pos
, dy
, nodename
, check_wear
, amount
, removing
)
1110 if ( chest_pos
== nil ) then
1111 chest_pos
, chest_internal
= easyvend
.find_chest(owner
, pos
, -dy
, nodename
, check_wear
, amount
, removing
, chest_internal
)
1114 local dy
= first
.y
- pos
.y
1115 chest_pos
, chest_internal
= easyvend
.find_chest(owner
, pos
, dy
, nodename
, check_wear
, amount
, removing
)
1118 if chest_internal
.chests
== 0 then
1119 return nil, "no_chest"
1120 elseif chest_internal
.chests
== chest_internal
.other_chests
then
1121 return nil, "not_owned"
1122 elseif removing
and chest_internal
.stock
< 1 then
1123 return nil, "no_stock"
1124 elseif not removing
and chest_internal
.space
< 1 then
1125 return nil, "no_space"
1126 elseif chest_pos
~= nil then
1129 return nil, "unknown"
1133 easyvend
.find_chest
= function(owner
, pos
, dy
, itemname
, check_wear
, amount
, removing
, internal
)
1134 pos
= {x
=pos
.x
, y
=pos
.y
+ dy
, z
=pos
.z
}
1136 if internal
== nil then
1139 internal
.other_chests
= 0
1144 local node
= minetest
.get_node_or_nil(pos
)
1145 if ( node
== nil ) then
1146 return nil, internal
1148 local chestdef
= registered_chests
[node
.name
]
1149 if (chestdef
~= nil) then
1150 internal
.chests
= internal
.chests
+ 1
1151 local meta
= minetest
.get_meta(pos
)
1152 if (owner
~= meta
:get_string(chestdef
.meta_owner
)) then
1153 internal
.other_chests
= internal
.other_chests
+ 1
1154 return nil, internal
1156 local inv
= meta
:get_inventory()
1157 if (inv
~= nil) then
1158 if (itemname
~= nil and minetest
.registered_items
[itemname
] and amount
~= nil and removing
~= nil and check_wear
~= nil) then
1159 local chest_has
, chest_free
1160 local stack
= {name
=itemname
, count
=amount
, wear
=0, metadata
=""}
1161 local stack_max
= minetest
.registered_items
[itemname
].stack_max
1163 local stacks
= math
.modf(amount
/ stack_max
)
1164 local stacksremainder
= math
.fmod(amount
, stack_max
)
1166 if stacksremainder
> 0 then free
= free
+ 1 end
1168 chest_has
= easyvend
.check_and_get_items(inv
, chestdef
.inv_list
, stack
, check_wear
)
1170 internal
.stock
= internal
.stock
+ 1
1172 chest_free
= inv
:room_for_item(chestdef
.inv_list
, stack
) and easyvend
.free_slots(inv
, chestdef
.inv_list
) >= free
1174 internal
.space
= internal
.space
+ 1
1177 if (removing
and internal
.stock
== 0) or (not removing
and internal
.space
== 0) then
1178 return easyvend
.find_chest(owner
, pos
, dy
, itemname
, check_wear
, amount
, removing
, internal
)
1180 return pos
, internal
1183 return nil, internal
1186 return nil, internal
1188 elseif (node
.name
~= "easyvend:vendor" and node
.name
~="easyvend:depositor" and node
.name
~="easyvend:vendor_on" and node
.name
~="easyvend:depositor_on") then
1189 return nil, internal
1192 return easyvend
.find_chest(owner
, pos
, dy
, itemname
, check_wear
, amount
, removing
, internal
)
1195 -- Pseudo-inventory handling
1196 easyvend
.allow_metadata_inventory_put
= function(pos
, listname
, index
, stack
, player
)
1197 if listname
=="item" then
1198 local meta
= minetest
.get_meta(pos
);
1199 local owner
= meta
:get_string("owner")
1200 local name
= player
:get_player_name()
1201 if name
== owner
then
1202 local inv
= meta
:get_inventory()
1204 inv
:set_stack( "item", 1, nil )
1206 inv
:set_stack( "item", 1, stack
:get_name() )
1207 meta
:set_string("itemname", stack
:get_name())
1208 easyvend
.set_formspec(pos
, player
)
1215 easyvend
.allow_metadata_inventory_take
= function(pos
, listname
, index
, stack
, player
)
1219 easyvend
.allow_metadata_inventory_move
= function(pos
, from_list
, from_index
, to_list
, to_index
, count
, player
)
1223 minetest
.register_abm({
1224 nodenames
= {"easyvend:vendor", "easyvend:vendor_on", "easyvend:depositor", "easyvend:depositor_on"},
1228 action
= function(pos
, node
, active_object_count
, active_object_count_wider
)
1229 easyvend
.machine_check(pos
, node
)
1233 -- Legacy support for vendor mod:
1234 -- Transform the world and items to use the easyvend nodes/items
1236 -- For safety reasons, only do this when player requested so
1237 if minetest
.setting_getbool("easyvend_convert_vendor") == true then
1238 -- Replace vendor nodes
1239 minetest
.register_lbm({
1240 name
= "easyvend:replace_vendor",
1241 nodenames
= { "vendor:vendor", "vendor:depositor" },
1242 run_at_every_load
= true,
1243 action
= function(pos
, node
)
1246 if node
.name
== "vendor:vendor" then
1247 newnodename
= "easyvend:vendor"
1248 elseif node
.name
== "vendor:depositor" then
1249 newnodename
= "easyvend:depositor"
1251 -- Remove axis rotation; only allow 4 facedirs
1252 local p2
= math
.fmod(node
.param2
, 4)
1253 minetest
.swap_node(pos
, { name
= newnodename
, param2
= p2
})
1255 -- Initialize metadata
1256 local meta
= minetest
.get_meta(pos
)
1257 if node
.name
== "vendor:vendor" then
1258 meta
:set_int("earnings", 0)
1260 meta
:set_int("stock", -1)
1261 meta
:set_int("joketimer", -1)
1262 meta
:set_int("joke_id", 1)
1263 local inv
= meta
:get_inventory()
1264 inv
:set_size("item", 1)
1265 inv
:set_size("gold", 1)
1266 inv
:set_stack("gold", 1, easyvend
.currency
)
1268 -- In vendor, all machines accepted worn tools
1269 meta
:set_int("wear", 1)
1272 local itemname
= meta
:get_string("itemname")
1273 if itemname
== "" or itemname
== nil then
1274 itemname
= meta
:get_string("itemtype")
1276 if itemname
~= "" and itemname
~= nil then
1277 inv
:set_stack("item", 1, itemname
)
1278 meta
:set_string("itemname", itemname
)
1281 -- Check for valid item, item count and price
1282 local configmode
= 1
1283 if itemname
~= "" and itemname
~= nil then
1284 local itemstack
= inv
:get_stack("item", 1)
1285 local number_stack_max
= itemstack
:get_stack_max()
1286 local maxnumber
= number_stack_max
* slots_max
1287 local cost
= meta
:get_int("cost")
1288 local number = meta
:get_int("number")
1289 if number >= 1 and number <= maxnumber
and cost
>= 1 and cost
<= maxcost
then
1290 -- Everything's OK, get out of config mode!
1295 -- Final initialization stuff
1296 meta
:set_int("configmode", configmode
)
1298 local owner
= meta
:get_string("owner")
1299 if easyvend
.buysell(newnodename
) == "sell" then
1300 meta
:set_string("infotext", string.format("Vending machine (owned by %s)", owner
))
1302 meta
:set_string("infotext", string.format("Depositing machine (owned by %s)", owner
))
1306 meta
:set_string("status", "Initializing …")
1307 meta
:set_string("message", "Upgrade successful.")
1308 easyvend
.machine_check(pos
, node
)