3 --Copyright (C) 2012 Bad_Command
6 --This library is free software; you can redistribute it and/or
7 --modify it under the terms of the GNU Lesser General Public
8 --License as published by the Free Software Foundation; either
9 --version 2.1 of the License, or (at your option) any later version.
11 --This program is distributed in the hope that it will be useful,
12 --but WITHOUT ANY WARRANTY; without even the implied warranty of
13 --MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 --GNU General Public License for more details.
16 --You should have received a copy of the GNU Lesser General Public
17 --License along with this library; if not, write to the Free Software
18 --Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 -- TODO: Improve mod compability
24 local traversable_node_types
= {
25 ["easyvend:vendor"] = true,
26 ["easyvend:depositor"] = true,
27 ["easyvend:vendor_on"] = true,
28 ["easyvend:depositor_on"] = true,
30 local registered_chests
= {}
31 local cost_stack_max
= minetest
.registered_items
[easyvend
.currency
].stack_max
32 local maxcost
= cost_stack_max
* slots_max
34 local joketimer_start
= 3
36 -- Allow for other mods to register custom chests
37 easyvend
.register_chest
= function(node_name
, inv_list
, meta_owner
)
38 registered_chests
[node_name
] = { inv_list
= inv_list
, meta_owner
= meta_owner
}
39 traversable_node_types
[node_name
] = true
42 -- Partly a wrapper around contains_item, but does special treatment if the item
43 -- is a tool. Basically checks whether the items exist in the supplied inventory
44 -- list. If check_wear is true, only counts items without wear.
45 easyvend
.check_and_get_items
= function(inventory
, listname
, itemtable
, check_wear
)
46 local itemstring
= itemtable
.name
47 local minimum
= itemtable
.count
48 if check_wear
== nil then check_wear
= false end
51 if minetest
.registered_tools
[itemstring
] ~= nil then
53 for i
=1,inventory
:get_size(listname
) do
54 local stack
= inventory
:get_stack(listname
, i
)
55 if stack
:get_name() == itemstring
then
56 if not check_wear
or stack
:get_wear() == 0 then
58 table.insert(get_items
, {id
=i
, item
=stack
})
59 if count
>= minimum
then
60 return true, get_items
67 -- Normal Minetest check
68 return inventory
:contains_item(listname
, ItemStack(itemtable
))
73 if minetest
.get_modpath("default") ~= nil then
74 easyvend
.register_chest("default:chest_locked", "main", "owner")
77 easyvend
.free_slots
= function(inv
, listname
)
78 local size
= inv
:get_size(listname
)
81 local stack
= inv
:get_stack(listname
, i
)
82 if stack
:is_empty() then
89 easyvend
.buysell
= function(nodename
)
91 if ( nodename
== "easyvend:depositor" or nodename
== "easyvend:depositor_on" ) then
93 elseif ( nodename
== "easyvend:vendor" or nodename
== "easyvend:vendor_on" ) then
99 easyvend
.is_active
= function(nodename
)
100 if ( nodename
== "easyvend:depositor_on" or nodename
== "easyvend:vendor_on" ) then
102 elseif ( nodename
== "easyvend:depositor" or nodename
== "easyvend:vendor" ) then
109 easyvend
.set_formspec
= function(pos
, player
)
110 local meta
= minetest
.get_meta(pos
)
111 local node
= minetest
.get_node(pos
)
113 local description
= minetest
.registered_nodes
[node
.name
].description
;
114 local number = meta
:get_int("number")
115 local cost
= meta
:get_int("cost")
116 local itemname
= meta
:get_string("itemname")
118 local configmode
= meta
:get_int("configmode") == 1
119 if minetest
.get_modpath("default") then
120 bg
= default
.gui_bg
.. default
.gui_bg_img
.. default
.gui_slots
123 local numbertext
, costtext
, buysellbuttontext
124 local itemcounttooltip
= "Item count (append “s” to multiply with maximum stack size)"
125 local buysell
= easyvend
.buysell(node
.name
)
126 if buysell
== "sell" then
127 numbertext
= "Offered item"
129 buysellbuttontext
= "Buy"
130 elseif buysell
== "buy" then
131 numbertext
= "Requested item"
133 buysellbuttontext
= "Sell"
137 local status
= meta
:get_string("status")
138 if status
== "" then status
= "Unknown." end
139 local message
= meta
:get_string("message")
140 if message
== "" then message
= "No message." end
142 if node
.name
== "easyvend:vendor_on" or node
.name
== "easyvend:depositor_on" then
143 status_image
= "easyvend_status_on.png"
145 status_image
= "easyvend_status_off.png"
148 -- TODO: Expose number of items in stock
150 local formspec
= "size[8,7.3;]"
152 .."label[3,-0.2;" .. minetest
.formspec_escape(description
) .. "]"
154 .."image[7.5,0.2;0.5,1;" .. status_image
.. "]"
155 .."textarea[2.8,0.2;5.1,2;;Status: " .. minetest
.formspec_escape(status
) .. ";]"
156 .."textarea[2.8,1.3;5.6,2;;Message: " .. minetest
.formspec_escape(message
) .. ";]"
158 .."label[0,-0.15;"..numbertext
.."]"
159 .."label[0,1.2;"..costtext
.."]"
160 .."list[current_player;main;0,3.5;8,4;]"
164 if meta
:get_int("wear") == 1 then wear
= "true" end
166 .."item_image_button[0,1.65;1,1;"..easyvend
.currency
..";easyvend.currency_image;]"
167 .."list[current_name;item;0,0.35;1,1;]"
168 .."listring[current_player;main]"
169 .."listring[current_name;item]"
170 .."field[1.3,0.65;1.5,1;number;;" .. number .. "]"
171 .."tooltip[number;"..itemcounttooltip
.."]"
172 .."field[1.3,1.95;1.5,1;cost;;" .. cost
.. "]"
173 .."tooltip[cost;"..itemcounttooltip
.."]"
174 .."button[6,2.8;2,0.5;save;Confirm]"
175 .."tooltip[save;Confirm configuration and activate machine (only for owner)]"
176 local weartext
, weartooltip
177 if buysell
== "buy" then
178 weartext
= "Buy worn tools"
179 weartooltip
= "If disabled, only tools in perfect condition will be bought from sellers (only settable by owner)"
181 weartext
= "Sell worn tools"
182 weartooltip
= "If disabled, only tools in perfect condition will be sold (only settable by owner)"
184 if minetest
.registered_tools
[itemname
] ~= nil then
185 formspec
= formspec
.."checkbox[2,2.4;wear;"..minetest
.formspec_escape(weartext
)..";"..wear
.."]"
186 .."tooltip[wear;"..minetest
.formspec_escape(weartooltip
).."]"
190 .."item_image_button[0,1.65;1,1;"..easyvend
.currency
..";easyvend.currency_image;]"
191 .."item_image_button[0,0.35;1,1;"..itemname
..";item_image;]"
192 .."label[1,1.85;×" .. cost
.. "]"
193 .."label[1,0.55;×" .. number .. "]"
194 .."button[6,2.8;2,0.5;config;Configure]"
195 if buysell
== "sell" then
196 formspec
= formspec
.. "tooltip[config;Configure offered items and price (only for owner)]"
198 formspec
= formspec
.. "tooltip[config;Configure requested items and payment (only for owner)]"
200 formspec
= formspec
.."button[0,2.8;2,0.5;buysell;"..buysellbuttontext
.."]"
201 if minetest
.registered_tools
[itemname
] ~= nil then
203 if meta
:get_int("wear") == 0 then
204 if buysell
== "buy" then
205 weartext
= "Only intact tools are bought."
207 weartext
= "Only intact tools are sold."
210 if buysell
== "sell" then
211 weartext
= "Warning: Might sell worn tools."
213 weartext
= "Worn tools are bought, too."
216 if weartext
~= nil then
217 formspec
= formspec
.."textarea[2.3,2.6;3,1;;"..minetest
.formspec_escape(weartext
)..";]"
222 meta
:set_string("formspec", formspec
)
225 easyvend
.machine_disable
= function(pos
, node
, playername
)
226 if node
.name
== "easyvend:vendor_on" then
227 easyvend
.sound_disable(pos
)
228 minetest
.swap_node(pos
, {name
="easyvend:vendor", param2
= node
.param2
})
230 elseif node
.name
== "easyvend:depositor_on" then
231 easyvend
.sound_disable(pos
)
232 minetest
.swap_node(pos
, {name
="easyvend:depositor", param2
= node
.param2
})
235 if playername
~= nil then
236 easyvend
.sound_error(playername
)
242 easyvend
.machine_enable
= function(pos
, node
)
243 if node
.name
== "easyvend:vendor" then
244 easyvend
.sound_setup(pos
)
245 minetest
.swap_node(pos
, {name
="easyvend:vendor_on", param2
= node
.param2
})
247 elseif node
.name
== "easyvend:depositor" then
248 easyvend
.sound_setup(pos
)
249 minetest
.swap_node(pos
, {name
="easyvend:depositor_on", param2
= node
.param2
})
256 easyvend
.machine_check
= function(pos
, node
)
258 local status
= "Ready."
260 local meta
= minetest
.get_meta(pos
)
262 local machine_owner
= meta
:get_string("owner")
263 local number = meta
:get_int("number")
264 local cost
= meta
:get_int("cost")
265 local itemname
= meta
:get_string("itemname")
266 local check_wear
= meta
:get_int("wear") == 0
267 local inv
= meta
:get_inventory()
268 local itemstack
= inv
:get_stack("item",1)
269 local buysell
= easyvend
.buysell(node
.name
)
271 local chest_pos
= easyvend
.find_connected_chest(machine_owner
, pos
, itemname
, number, buysell
== "sell")
272 local chest
, chestdef
, chest_meta
, chest_inv
273 if chest_pos
~= nil then
274 chest
= minetest
.get_node(chest_pos
)
275 chestdef
= registered_chests
[chest
.name
]
279 chest_meta
= minetest
.get_meta(chest_pos
)
280 chest_inv
= chest_meta
:get_inventory()
282 if ( chest_meta
:get_string(chestdef
.meta_owner
) == machine_owner
and chest_inv
~= nil ) then
284 local checkstack
, checkitem
285 if buysell
== "buy" then
286 checkitem
= easyvend
.currency
292 -- FIXME: Ignore tools with bad wear level
293 for i
=1,chest_inv
:get_size(chestdef
.inv_list
) do
294 checkstack
= chest_inv
:get_stack(chestdef
.inv_list
, i
)
295 if checkstack
:get_name() == checkitem
then
296 stock
= stock
+ checkstack
:get_count()
299 meta
:set_int("stock", stock
)
301 if not itemstack
:is_empty() then
302 local number_stack_max
= itemstack
:get_stack_max()
303 local maxnumber
= number_stack_max
* slots_max
304 if number >= 1 and number <= maxnumber
and cost
>= 1 and cost
<= maxcost
then
305 local stack
= {name
=itemname
, count
=number, wear
=0, metadata
=""}
306 local price
= {name
=easyvend
.currency
, count
=cost
, wear
=0, metadata
=""}
308 local chest_has
, chest_free
310 local coststacks
= math
.modf(cost
/ cost_stack_max
)
311 local costremainder
= math
.fmod(cost
, cost_stack_max
)
312 local numberstacks
= math
.modf(number / number_stack_max
)
313 local numberremainder
= math
.fmod(number, number_stack_max
)
314 local numberfree
= numberstacks
315 local costfree
= coststacks
316 if numberremainder
> 0 then numberfree
= numberfree
+ 1 end
317 if costremainder
> 0 then costfree
= costfree
+ 1 end
319 if buysell
== "sell" then
320 chest_has
= easyvend
.check_and_get_items(chest_inv
, chestdef
.inv_list
, stack
, check_wear
)
321 chest_free
= chest_inv
:room_for_item(chestdef
.inv_list
, price
)
322 if chest_has
and chest_free
then
323 if cost
<= cost_stack_max
and number <= number_stack_max
then
325 elseif easyvend
.free_slots(chest_inv
, chestdef
.inv_list
) < costfree
then
327 status
= "No room in the machine’s storage!"
329 elseif not chest_has
then
331 status
= "The vending machine has insufficient materials!"
332 elseif not chest_free
then
334 status
= "No room in the machine’s storage!"
336 elseif buysell
== "buy" then
337 chest_has
= easyvend
.check_and_get_items(chest_inv
, chestdef
.inv_list
, price
, check_wear
)
338 chest_free
= chest_inv
:room_for_item(chestdef
.inv_list
, stack
)
339 if chest_has
and chest_free
then
340 if cost
<= cost_stack_max
and number <= number_stack_max
then
342 elseif easyvend
.free_slots(chest_inv
, chestdef
.inv_list
) < numberfree
then
344 status
= "No room in the machine’s storage!"
346 elseif not chest_has
then
348 status
= "The depositing machine is out of money!"
349 elseif not chest_free
then
351 status
= "No room in the machine’s storage!"
356 if buysell
== "sell" then
357 status
= "Invalid item count or price."
359 status
= "Invalid item count or payment."
364 status
= "Awaiting configuration by owner."
367 meta
:set_int("stock", 0)
369 status
= "Storage can’t be accessed because it is owned by a different person!"
372 meta
:set_int("stock", 0)
374 status
= "No storage; machine needs a locked chest below it."
376 if meta
:get_int("configmode") == 1 then
378 status
= "Awaiting configuration by owner."
381 if itemname
== easyvend
.currency
and number == cost
and active
then
382 local jt
= meta
:get_int("joketimer")
387 if buysell
== "sell" then
388 meta
:set_string("message", "Item bought.")
390 meta
:set_string("message", "Item sold.")
394 meta
:set_int("joketimer", jt
)
396 meta
:set_string("status", status
)
398 meta
:set_string("infotext", easyvend
.make_infotext(node
.name
, machine_owner
, cost
, number, itemname
))
399 itemname
=itemstack
:get_name()
400 meta
:set_string("itemname", itemname
)
403 if node
.name
== "easyvend:vendor" or node
.name
== "easyvend:depositor" then
404 if active
then change
= easyvend
.machine_enable(pos
, node
) end
405 elseif node
.name
== "easyvend:vendor_on" or node
.name
== "easyvend:depositor_on" then
406 if not active
then change
= easyvend
.machine_disable(pos
, node
) end
408 easyvend
.set_formspec(pos
)
412 easyvend
.on_receive_fields_config
= function(pos
, formname
, fields
, sender
)
413 local node
= minetest
.get_node(pos
)
414 local meta
= minetest
.get_meta(pos
)
415 local inv_self
= meta
:get_inventory()
416 local itemstack
= inv_self
:get_stack("item",1)
417 local buysell
= easyvend
.buysell(node
.name
)
419 if fields
.config
then
420 meta
:set_int("configmode", 1)
421 local was_active
= easyvend
.is_active(node
.name
)
423 meta
:set_string("message", "Configuration mode activated; machine disabled.")
425 meta
:set_string("message", "Configuration mode activated.")
427 easyvend
.machine_check(pos
, node
)
431 if not fields
.save
then
435 local number = fields
.number
436 local cost
= fields
.cost
438 --[[ Convenience function:
439 When appending “s” or “S” to the number, it is multiplied
440 by the maximum stack size.
441 TODO: Expose this in user documentation ]]
442 local number_stack_max
= itemstack
:get_stack_max()
443 local ss
= string.sub(number, #number, #number)
444 if ss
== "s" or ss
== "S" then
445 local n
= tonumber(string.sub(number, 1, #number-1))
446 if string.len(number) == 1 then n
= 1 end
448 number = n
* number_stack_max
451 ss
= string.sub(cost
, #cost
, #cost
)
452 if ss
== "s" or ss
== "S" then
453 local n
= tonumber(string.sub(cost
, 1, #cost
-1))
454 if string.len(cost
) == 1 then n
= 1 end
456 cost
= n
* cost_stack_max
459 number = tonumber(number)
460 cost
= tonumber(cost
)
464 local oldnumber
= meta
:get_int("number")
465 local oldcost
= meta
:get_int("cost")
466 local maxnumber
= number_stack_max
* slots_max
468 if ( itemstack
== nil or itemstack
:is_empty() ) then
469 meta
:set_string("status", "Awaiting configuration by owner.")
470 meta
:set_string("message", "No item specified.")
471 easyvend
.sound_error(sender
:get_player_name())
472 easyvend
.set_formspec(pos
, sender
)
474 elseif ( number == nil or number < 1 or number > maxnumber
) then
475 if maxnumber
> 1 then
476 meta
:set_string("message", string.format("Invalid item count; must be between 1 and %d!", maxnumber
))
478 meta
:set_string("message", "Invalid item count; must be exactly 1!")
480 meta
:set_int("number", oldnumber
)
481 easyvend
.sound_error(sender
:get_player_name())
482 easyvend
.set_formspec(pos
, sender
)
484 elseif ( cost
== nil or cost
< 1 or cost
> maxcost
) then
486 meta
:set_string("message", string.format("Invalid cost; must be between 1 and %d!", maxcost
))
488 meta
:set_string("message", "Invalid cost; must be exactly 1!")
490 meta
:set_int("cost", oldcost
)
491 easyvend
.sound_error(sender
:get_player_name())
492 easyvend
.set_formspec(pos
, sender
)
495 meta
:set_int("number", number)
496 meta
:set_int("cost", cost
)
497 itemname
=itemstack
:get_name()
498 meta
:set_string("itemname", itemname
)
499 meta
:set_int("configmode", 0)
501 if itemname
== easyvend
.currency
and number == cost
and cost
<= cost_stack_max
then
502 meta
:set_string("message", "Configuration successful. I am feeling funny.")
503 meta
:set_int("joketimer", joketimer_start
)
504 meta
:set_int("joke_id", easyvend
.assign_joke(buysell
))
506 meta
:set_string("message", "Configuration successful.")
509 local change
= easyvend
.machine_check(pos
, node
)
512 if (node
.name
== "easyvend:vendor_on" or node
.name
== "easyvend:depositor_on") then
513 easyvend
.sound_setup(pos
)
515 easyvend
.sound_disable(pos
)
520 easyvend
.make_infotext
= function(nodename
, owner
, cost
, number, itemstring
)
522 if itemstring
== nil or itemstring
== "" or number == 0 or cost
== 0 then
523 if easyvend
.buysell(nodename
) == "sell" then
524 d
= string.format("Inactive vending machine (owned by %s)", owner
)
526 d
= string.format("Inactive depositing machine (owned by %s)", owner
)
530 local iname
= minetest
.registered_items
[itemstring
].description
531 if iname
== nil then iname
= itemstring
end
532 local printitem
, printcost
536 printitem
= string.format("%d×%s", number, iname
)
539 printcost
= easyvend
.currency_desc
541 printcost
= string.format("%d×%s", cost
, easyvend
.currency_desc
)
543 if nodename
== "easyvend:vendor_on" then
544 d
= string.format("Vending machine (owned by %s)\nSelling: %s\nPrice: %s", owner
, printitem
, printcost
)
545 elseif nodename
== "easyvend:vendor" then
546 d
= string.format("Inactive vending machine (owned by %s)\nSelling: %s\nPrice: %s", owner
, printitem
, printcost
)
547 elseif nodename
== "easyvend:depositor_on" then
548 d
= string.format("Depositing machine (owned by %s)\nBuying: %s\nPayment: %s", owner
, printitem
, printcost
)
549 elseif nodename
== "easyvend:depositor" then
550 d
= string.format("Inactive depositing machine (owned by %s)\nBuying: %s\nPayment: %s", owner
, printitem
, printcost
)
555 easyvend
.on_receive_fields_buysell
= function(pos
, formname
, fields
, sender
)
556 local sendername
= sender
:get_player_name()
557 local meta
= minetest
.get_meta(pos
)
559 if not fields
.buysell
then
563 local node
= minetest
.get_node(pos
)
564 local number = meta
:get_int("number")
565 local cost
= meta
:get_int("cost")
566 local itemname
=meta
:get_string("itemname")
567 local item
=meta
:get_inventory():get_stack("item", 1)
568 local check_wear
= meta
:get_int("wear") == 0 and minetest
.registered_tools
[itemname
] ~= nil
570 local buysell
= easyvend
.buysell(node
.name
)
572 local number_stack_max
= item
:get_stack_max()
573 local maxnumber
= number_stack_max
* slots_max
574 if ( number == nil or number < 1 or number > maxnumber
) or
575 ( cost
== nil or cost
< 1 or cost
> maxcost
) or
576 ( itemname
== nil or itemname
=="") then
577 meta
:set_string("status", "Invalid item count or price!")
578 easyvend
.machine_disable(pos
, node
, sendername
)
582 local chest_pos
= easyvend
.find_connected_chest(sendername
, pos
, itemname
, number, buysell
== "sell")
583 local chest
, chestdef
584 if chest_pos
~= nil then
585 chest
= minetest
.get_node(chest_pos
)
586 chestdef
= registered_chests
[chest
.name
]
588 if chestdef
and sender
and sender
:is_player() then
589 local chest_meta
= minetest
.get_meta(chest_pos
)
590 local chest_inv
= chest_meta
:get_inventory()
591 local player_inv
= sender
:get_inventory()
592 if ( chest_meta
:get_string(chestdef
.meta_owner
) == meta
:get_string("owner") and chest_inv
~= nil and player_inv
~= nil ) then
594 local stack
= {name
=itemname
, count
=number, wear
=0, metadata
=""}
595 local price
= {name
=easyvend
.currency
, count
=cost
, wear
=0, metadata
=""}
596 local chest_has
, player_has
, chest_free
, player_free
, chest_out
, player_out
598 if buysell
== "sell" then
599 chest_has
, chest_out
= easyvend
.check_and_get_items(chest_inv
, "main", stack
, check_wear
)
600 player_has
, player_out
= easyvend
.check_and_get_items(player_inv
, "main", price
, check_wear
)
601 chest_free
= chest_inv
:room_for_item("main", price
)
602 player_free
= player_inv
:room_for_item("main", stack
)
603 if chest_has
and player_has
and chest_free
and player_free
then
604 if cost
<= cost_stack_max
and number <= number_stack_max
then
605 easyvend
.machine_enable(pos
, node
)
606 player_inv
:remove_item("main", price
)
608 chest_inv
:set_stack("main", chest_out
[1].id
, "")
609 player_inv
:add_item("main", chest_out
[1].item
)
611 stack
= chest_inv
:remove_item("main", stack
)
612 player_inv
:add_item("main", stack
)
614 chest_inv
:add_item("main", price
)
615 if itemname
== easyvend
.currency
and number == cost
and cost
<= cost_stack_max
then
616 meta
:set_string("message", easyvend
.get_joke(buysell
, meta
:get_int("joke_id")))
617 meta
:set_int("joketimer", joketimer_start
)
619 meta
:set_string("message", "Item bought.")
621 easyvend
.sound_vend(pos
)
622 easyvend
.machine_check(pos
, node
)
624 -- Large item counts (multiple stacks)
625 local coststacks
= math
.modf(cost
/ cost_stack_max
)
626 local costremainder
= math
.fmod(cost
, cost_stack_max
)
627 local numberstacks
= math
.modf(number / number_stack_max
)
628 local numberremainder
= math
.fmod(number, number_stack_max
)
629 local numberfree
= numberstacks
630 local costfree
= coststacks
631 if numberremainder
> 0 then numberfree
= numberfree
+ 1 end
632 if costremainder
> 0 then costfree
= costfree
+ 1 end
633 if easyvend
.free_slots(player_inv
, "main") < numberfree
then
634 if numberfree
> 1 then
635 msg
= string.format("No room in your inventory (%d empty slots required)!", numberfree
)
637 msg
= "No room in your inventory!"
639 meta
:set_string("message", msg
)
640 elseif easyvend
.free_slots(chest_inv
, "main") < costfree
then
641 meta
:set_string("status", "No room in the machine’s storage!")
642 easyvend
.machine_disable(pos
, node
, sendername
)
644 -- Remember items for transfer
645 local cheststacks
= {}
646 easyvend
.machine_enable(pos
, node
)
647 for i
=1, coststacks
do
648 price
.count
= cost_stack_max
649 player_inv
:remove_item("main", price
)
651 if costremainder
> 0 then
652 price
.count
= costremainder
653 player_inv
:remove_item("main", price
)
656 for o
=1,#chest_out
do
657 chest_inv
:set_stack("main", chest_out
[o
].id
, "")
660 for i
=1, numberstacks
do
661 stack
.count
= number_stack_max
662 table.insert(cheststacks
, chest_inv
:remove_item("main", stack
))
665 if numberremainder
> 0 then
666 stack
.count
= numberremainder
667 table.insert(cheststacks
, chest_inv
:remove_item("main", stack
))
669 for i
=1, coststacks
do
670 price
.count
= cost_stack_max
671 chest_inv
:add_item("main", price
)
673 if costremainder
> 0 then
674 price
.count
= costremainder
675 chest_inv
:add_item("main", price
)
678 for o
=1,#chest_out
do
679 player_inv
:add_item("main", chest_out
[o
].item
)
682 for i
=1,#cheststacks
do
683 player_inv
:add_item("main", cheststacks
[i
])
686 meta
:set_string("message", "Item bought.")
687 easyvend
.sound_vend(pos
)
688 easyvend
.machine_check(pos
, node
)
691 elseif chest_has
and player_has
then
692 if not player_free
then
693 msg
= "No room in your inventory!"
694 meta
:set_string("message", msg
)
695 easyvend
.sound_error(sendername
)
696 elseif not chest_free
then
697 msg
= "No room in the machine’s storage!"
698 meta
:set_string("status", msg
)
699 easyvend
.machine_disable(pos
, node
, sendername
)
702 if not chest_has
then
703 msg
= "The vending machine has insufficient materials!"
704 meta
:set_string("status", msg
)
705 easyvend
.machine_disable(pos
, node
, sendername
)
706 elseif not player_has
then
707 msg
= "You can’t afford this item!"
708 meta
:set_string("message", msg
)
709 easyvend
.sound_error(sendername
)
713 chest_has
, chest_out
= easyvend
.check_and_get_items(chest_inv
, "main", price
, check_wear
)
714 player_has
, player_out
= easyvend
.check_and_get_items(player_inv
, "main", stack
, check_wear
)
715 chest_free
= chest_inv
:room_for_item("main", stack
)
716 player_free
= player_inv
:room_for_item("main", price
)
717 if chest_has
and player_has
and chest_free
and player_free
then
718 if cost
<= cost_stack_max
and number <= number_stack_max
then
719 easyvend
.machine_enable(pos
, node
)
721 player_inv
:set_stack("main", player_out
[1].id
, "")
722 chest_inv
:add_item("main", player_out
[1].item
)
724 stack
= player_inv
:remove_item("main", stack
)
725 chest_inv
:add_item("main", stack
)
727 chest_inv
:remove_item("main", price
)
728 player_inv
:add_item("main", price
)
729 meta
:set_string("status", "Ready.")
730 if itemname
== easyvend
.currency
and number == cost
and cost
<= cost_stack_max
then
731 meta
:set_string("message", easyvend
.get_joke(buysell
, meta
:get_int("joke_id")))
732 meta
:set_int("joketimer", joketimer_start
)
734 meta
:set_string("message", "Item sold.")
736 easyvend
.sound_deposit(pos
)
737 easyvend
.machine_check(pos
, node
)
739 -- Large item counts (multiple stacks)
740 local coststacks
= math
.modf(cost
/ cost_stack_max
)
741 local costremainder
= math
.fmod(cost
, cost_stack_max
)
742 local numberstacks
= math
.modf(number / number_stack_max
)
743 local numberremainder
= math
.fmod(number, number_stack_max
)
744 local numberfree
= numberstacks
745 local costfree
= coststacks
746 if numberremainder
> 0 then numberfree
= numberfree
+ 1 end
747 if costremainder
> 0 then costfree
= costfree
+ 1 end
748 if easyvend
.free_slots(player_inv
, "main") < costfree
then
750 msg
= string.format("No room in your inventory (%d empty slots required)!", costfree
)
752 msg
= "No room in your inventory!"
754 meta
:set_string("message", msg
)
755 easyvend
.sound_error(sendername
)
756 elseif easyvend
.free_slots(chest_inv
, "main") < numberfree
then
757 easyvend
.machine_disable(pos
, node
, sendername
)
759 easyvend
.machine_enable(pos
, node
)
760 -- Remember removed items for transfer
761 local playerstacks
= {}
762 for i
=1, coststacks
do
763 price
.count
= cost_stack_max
764 chest_inv
:remove_item("main", price
)
766 if costremainder
> 0 then
767 price
.count
= costremainder
768 chest_inv
:remove_item("main", price
)
771 for o
=1,#player_out
do
772 player_inv
:set_stack("main", player_out
[o
].id
, "")
775 for i
=1, numberstacks
do
776 stack
.count
= number_stack_max
777 table.insert(playerstacks
, player_inv
:remove_item("main", stack
))
780 if numberremainder
> 0 then
781 stack
.count
= numberremainder
782 table.insert(playerstacks
, player_inv
:remove_item("main", stack
))
784 for i
=1, coststacks
do
785 price
.count
= cost_stack_max
786 player_inv
:add_item("main", price
)
788 if costremainder
> 0 then
789 price
.count
= costremainder
790 player_inv
:add_item("main", price
)
793 for o
=1,#player_out
do
794 chest_inv
:add_item("main", player_out
[o
].item
)
797 for i
=1,#playerstacks
do
798 chest_inv
:add_item("main", playerstacks
[i
])
801 meta
:set_string("message", "Item sold.")
802 easyvend
.sound_deposit(pos
)
803 easyvend
.machine_check(pos
, node
)
806 elseif chest_has
and player_has
then
807 if not player_free
then
808 msg
= "No room in your inventory!"
809 meta
:set_string("message", msg
)
810 easyvend
.sound_error(sendername
)
811 elseif not chest_free
then
812 msg
= "No room in the machine’s storage!"
813 meta
:set_string("status", msg
)
814 easyvend
.machine_disable(pos
, node
, sendername
)
817 if not player_has
then
818 msg
= "You have insufficient materials!"
819 meta
:set_string("message", msg
)
820 easyvend
.sound_error(sendername
)
821 elseif not chest_has
then
822 msg
= "The depositing machine is out of money!"
823 meta
:set_string("status", msg
)
824 easyvend
.machine_disable(pos
, node
, sendername
)
829 meta
:set_string("status", "Storage can’t be accessed because it is owned by a different person!")
830 easyvend
.machine_disable(pos
, node
, sendername
)
833 if sender
and sender
:is_player() then
834 meta
:set_string("status", "No storage; machine needs a locked chest below it.")
835 easyvend
.machine_disable(pos
, node
, sendername
)
839 easyvend
.set_formspec(pos
, sender
)
843 easyvend
.after_place_node
= function(pos
, placer
)
844 local node
= minetest
.get_node(pos
)
845 local meta
= minetest
.get_meta(pos
)
846 local inv
= meta
:get_inventory()
847 local player_name
= placer
:get_player_name()
848 inv
:set_size("item", 1)
849 inv
:set_size("gold", 1)
851 inv
:set_stack( "gold", 1, easyvend
.currency
)
854 if node
.name
== "easyvend:vendor" then
855 d
= string.format("Inactive vending machine (owned by %s)", player_name
)
856 meta
:set_int("wear", 1)
857 elseif node
.name
== "easyvend:depositor" then
858 d
= string.format("Inactive depositing machine (owned by %s)", player_name
)
859 meta
:set_int("wear", 0)
861 meta
:set_string("infotext", d
)
862 meta
:set_string("status", "Awaiting configuration by owner.")
863 meta
:set_string("message", "Welcome! Please prepare the machine.")
864 meta
:set_int("number", 1)
865 meta
:set_int("cost", 1)
866 meta
:set_int("stock", -1)
867 meta
:set_int("configmode", 1)
868 meta
:set_int("joketimer", -1)
869 meta
:set_int("joke_id", 1)
870 meta
:set_string("itemname", "")
872 meta
:set_string("owner", player_name
or "")
874 easyvend
.set_formspec(pos
, placer
)
877 easyvend
.can_dig
= function(pos
, player
)
878 local meta
= minetest
.get_meta(pos
)
879 local name
= player
:get_player_name()
880 local owner
= meta
:get_string("owner")
881 -- Owner can always dig shop
882 if owner
== name
then
885 local chest_pos
= easyvend
.get_connected_chest(owner
, pos
)
886 local chest
, meta_chest
888 chest
= minetest
.get_node(chest_pos
)
889 meta_chest
= minetest
.get_meta(chest_pos
)
891 if registered_chests
[chest
.name
] then
892 if player
and player
:is_player() then
893 local owner_chest
= meta_chest
:get_string(registered_chests
[chest
.name
].meta_owner
)
894 if name
== owner_chest
then
895 return true --chest owner can also dig shop
900 return true --if no chest, enyone can dig this shop
904 easyvend
.on_receive_fields
= function(pos
, formname
, fields
, sender
)
905 local meta
= minetest
.get_meta(pos
)
906 local node
= minetest
.get_node(pos
)
907 local owner
= meta
:get_string("owner")
908 local sendername
= sender
:get_player_name(sender
)
910 if fields
.config
or fields
.save
or fields
.usermode
then
911 if sender
:get_player_name() == owner
then
912 easyvend
.on_receive_fields_config(pos
, formname
, fields
, sender
)
914 meta
:set_string("message", "Only the owner may change the configuration.")
915 easyvend
.sound_error(sendername
)
916 easyvend
.set_formspec(pos
, sender
)
919 elseif fields
.wear
~= nil then
920 if sender
:get_player_name() == owner
then
921 if fields
.wear
== "true" then
922 if easyvend
.buysell(node
.name
) == "buy" then
923 meta
:set_string("message", "Used tools are now accepted.")
925 meta
:set_string("message", "Used tools are now for sale.")
927 meta
:set_int("wear", 1)
928 elseif fields
.wear
== "false" then
929 if easyvend
.buysell(node
.name
) == "buy" then
930 meta
:set_string("message", "Used tools are now rejected.")
932 meta
:set_string("message", "Used tools won’t be sold anymore.")
934 meta
:set_int("wear", 0)
936 easyvend
.set_formspec(pos
, sender
)
939 meta
:set_string("message", "Only the owner may change the configuration.")
940 easyvend
.sound_error(sendername
)
941 easyvend
.set_formspec(pos
, sender
)
944 elseif fields
.buysell
then
945 easyvend
.on_receive_fields_buysell(pos
, formname
, fields
, sender
)
949 -- Jokes: Appear when machine exchanges currency for currency at equal rate
952 local jokes_vendor
= {
953 "Thank you. You have made a vending machine very happy.",
954 "Humans have a strange sense of humor.",
955 "Let’s get this over with …",
958 "Do you realize what you’ve just bought?",
961 local jokes_depositor
= {
962 "Thank you, the money started to smell inside.",
963 "Money doesn’t grow on trees, you know?",
965 "Well, that was an awkward exchange.",
966 "Are you having fun?",
967 "Is this really trading?",
970 easyvend
.assign_joke
= function(buysell
)
972 if buysell
== "sell" then
974 elseif buysell
== "buy" then
975 jokes
= jokes_depositor
977 local r
= math
.random(1,#jokes
)
981 easyvend
.get_joke
= function(buysell
, id
)
983 if buysell
== nil or id
== nil then
984 -- Fallback message (should never happen)
985 return "Items exchanged."
987 if buysell
== "sell" then
988 joke
= jokes_vendor
[id
]
989 if joke
== nil then joke
= jokes_vendor
[1] end
990 elseif buysell
== "buy" then
991 joke
= jokes_depositor
[id
]
992 if joke
== nil then joke
= jokes_depositor
[1] end
997 easyvend
.sound_error
= function(playername
)
998 minetest
.sound_play("easyvend_error", {to_player
= playername
, gain
= 0.25})
1001 easyvend
.sound_setup
= function(pos
)
1002 minetest
.sound_play("easyvend_activate", {pos
= pos
, gain
= 0.5, max_hear_distance
= 12,})
1005 easyvend
.sound_disable
= function(pos
)
1006 minetest
.sound_play("easyvend_disable", {pos
= pos
, gain
= 0.9, max_hear_distance
= 12,})
1009 easyvend
.sound_vend
= function(pos
)
1010 minetest
.sound_play("easyvend_vend", {pos
= pos
, gain
= 0.4, max_hear_distance
= 5,})
1013 easyvend
.sound_deposit
= function(pos
)
1014 minetest
.sound_play("easyvend_deposit", {pos
= pos
, gain
= 0.4, max_hear_distance
= 5,})
1017 --[[ Tower building ]]
1019 easyvend
.is_traversable
= function(pos
)
1020 local node
= minetest
.get_node_or_nil(pos
)
1021 if (node
== nil) then
1024 return traversable_node_types
[node
.name
] == true
1027 easyvend
.neighboring_nodes
= function(pos
)
1029 {x
=pos
.x
, y
=pos
.y
+1, z
=pos
.z
},
1030 {x
=pos
.x
, y
=pos
.y
-1, z
=pos
.z
},
1034 if easyvend
.is_traversable(check
[i
]) then
1035 trav
[#trav
+1] = check
[i
]
1041 easyvend
.find_connected_chest
= function(owner
, pos
, nodename
, amount
, removing
)
1042 local nodes
= easyvend
.neighboring_nodes(pos
)
1044 if (#nodes
< 1 or #nodes
> 2) then
1048 -- Find the stack direction
1052 if ( first
== nil ) then
1059 if (first
~= nil and second
~= nil) then
1060 local dx
= (first
.x
- second
.x
)/2
1061 local dy
= (first
.y
- second
.y
)/2
1062 local dz
= (first
.z
- second
.z
)/2
1063 -- make sure they are in a column/row
1064 if ( (dx
* dx
+ dy
* dy
+ dz
* dz
) ~= 1 ) then
1067 local chest_pos
= easyvend
.find_chest(owner
, pos
, dx
, dy
, dz
, nodename
, amount
, removing
)
1068 if ( chest_pos
== nil ) then
1069 chest_pos
= easyvend
.find_chest(owner
, pos
, -dx
, -dy
, -dz
, nodename
, amount
, removing
)
1073 local dx
= first
.x
- pos
.x
1074 local dy
= first
.y
- pos
.y
1075 local dz
= first
.z
- pos
.z
1076 return easyvend
.find_chest(owner
, pos
, dx
, dy
, dz
, nodename
, amount
, removing
)
1080 easyvend
.find_chest
= function(owner
, pos
, dx
, dy
, dz
, nodename
, amount
, removing
)
1081 pos
= {x
=pos
.x
+ dx
, y
=pos
.y
+ dy
, z
=pos
.z
+ dz
}
1083 local node
= minetest
.get_node_or_nil(pos
)
1084 if ( node
== nil ) then
1087 local chestdef
= registered_chests
[node
.name
]
1088 if (chestdef
~= nil) then
1089 local meta
= minetest
.get_meta(pos
)
1090 if (chestdef
~= nil and owner
~= meta
:get_string(chestdef
.meta_owner
)) then
1093 local inv
= meta
:get_inventory()
1094 if (inv
~= nil) then
1095 if (nodename
~= nil and amount
~= nil and removing
~= nil) then
1096 if (removing
and inv
:contains_item(chestdef
.inv_list
, nodename
.. " " .. amount
)) then
1098 elseif ((not removing
) and inv
:room_for_item(chestdef
.inv_list
, nodename
.. " " .. amount
)) then
1101 elseif (not inv
:is_empty(chestdef
.inv_list
)) then
1105 elseif (node
.name
~= "easyvend:vendor" and node
.name
~="easyvend:depositor" and node
.name
~="easyvend:vendor_on" and node
.name
~="easyvend:depositor_on") then
1109 return easyvend
.find_chest(owner
, pos
, dx
, dy
, dz
, nodename
, amount
, removing
)
1112 -- Pseudo-inventory handling
1113 easyvend
.allow_metadata_inventory_put
= function(pos
, listname
, index
, stack
, player
)
1114 if listname
=="item" then
1115 local meta
= minetest
.get_meta(pos
);
1116 local owner
= meta
:get_string("owner")
1117 local name
= player
:get_player_name()
1118 if name
== owner
then
1119 local inv
= meta
:get_inventory()
1121 inv
:set_stack( "item", 1, nil )
1123 inv
:set_stack( "item", 1, stack
:get_name() )
1124 meta
:set_string("itemname", stack
:get_name())
1125 easyvend
.set_formspec(pos
, player
)
1132 easyvend
.allow_metadata_inventory_take
= function(pos
, listname
, index
, stack
, player
)
1136 easyvend
.allow_metadata_inventory_move
= function(pos
, from_list
, from_index
, to_list
, to_index
, count
, player
)
1140 minetest
.register_abm({
1141 nodenames
= {"easyvend:vendor", "easyvend:vendor_on", "easyvend:depositor", "easyvend:depositor_on"},
1145 action
= function(pos
, node
, active_object_count
, active_object_count_wider
)
1146 easyvend
.machine_check(pos
, node
)
1150 -- Legacy support for vendor mod:
1151 -- Transform the world and items to use the easyvend nodes/items
1153 -- For safety reasons, only do this when player requested so
1154 if minetest
.setting_getbool("easyvend_convert_vendor") == true then
1155 -- Replace vendor nodes
1156 minetest
.register_lbm({
1157 name
= "easyvend:replace_vendor",
1158 nodenames
= { "vendor:vendor", "vendor:depositor" },
1159 run_at_every_load
= true,
1160 action
= function(pos
, node
)
1163 if node
.name
== "vendor:vendor" then
1164 newnodename
= "easyvend:vendor"
1165 elseif node
.name
== "vendor:depositor" then
1166 newnodename
= "easyvend:depositor"
1168 -- Remove axis rotation; only allow 4 facedirs
1169 local p2
= math
.fmod(node
.param2
, 4)
1170 minetest
.swap_node(pos
, { name
= newnodename
, param2
= p2
})
1172 -- Initialize metadata
1173 local meta
= minetest
.get_meta(pos
)
1174 meta
:set_int("stock", -1)
1175 meta
:set_int("joketimer", -1)
1176 meta
:set_int("joke_id", 1)
1177 local inv
= meta
:get_inventory()
1178 inv
:set_size("item", 1)
1179 inv
:set_size("gold", 1)
1180 inv
:set_stack("gold", 1, easyvend
.currency
)
1182 -- In vendor, all machines accepted worn tools
1183 meta
:set_int("wear", 1)
1186 local itemname
= meta
:get_string("itemname")
1187 if itemname
== "" or itemname
== nil then
1188 itemname
= meta
:get_string("itemtype")
1190 if itemname
~= "" and itemname
~= nil then
1191 inv
:set_stack("item", 1, itemname
)
1192 meta
:set_string("itemname", itemname
)
1195 -- Check for valid item, item count and price
1196 local configmode
= 1
1197 if itemname
~= "" and itemname
~= nil then
1198 local itemstack
= inv
:get_stack("item", 1)
1199 local number_stack_max
= itemstack
:get_stack_max()
1200 local maxnumber
= number_stack_max
* slots_max
1201 local cost
= meta
:get_int("cost")
1202 local number = meta
:get_int("number")
1203 if number >= 1 and number <= maxnumber
and cost
>= 1 and cost
<= maxcost
then
1204 -- Everything's OK, get out of config mode!
1209 -- Final initialization stuff
1210 meta
:set_int("configmode", configmode
)
1212 local owner
= meta
:get_string("owner")
1213 if easyvend
.buysell(newnodename
) == "sell" then
1214 meta
:set_string("infotext", string.format("Vending machine (owned by %s)", owner
))
1216 meta
:set_string("infotext", string.format("Depositing machine (owned by %s)", owner
))
1220 meta
:set_string("status", "Initializing …")
1221 meta
:set_string("message", "Upgrade successful.")
1222 easyvend
.machine_check(pos
, node
)