Fix dispenser inventory not getting set if up/down
[MineClone/MineClone2.git] / mods / ITEMS / REDSTONE / mcl_dispensers / init.lua
blobba461fc2425f4606515ef7f59b81a9bcc5ad0ae8
1 --[[ This mod registers 3 nodes:
2 - One node for the horizontal-facing dispensers (mcl_dispensers:dispenser)
3 - One node for the upwards-facing dispensers (mcl_dispenser:dispenser_up)
4 - One node for the downwards-facing dispensers (mcl_dispenser:dispenser_down)
6 3 node definitions are needed because of the way the textures are defined.
7 All node definitions share a lot of code, so this is the reason why there
8 are so many weird tables below.
9 ]]
11 -- For after_place_node
12 local setup_dispenser = function(pos)
13 -- Set formspec and inventory
14 local form = "size[9,8.75]"..
15 "background[-0.19,-0.25;9.41,9.49;crafting_inventory_9_slots.png]"..
16 mcl_vars.inventory_header..
17 "image[3,-0.2;5,0.75;mcl_dispensers_fnt_dispenser.png]"..
18 "list[current_player;main;0,4.5;9,3;9]"..
19 "list[current_player;main;0,7.74;9,1;]"..
20 "list[current_name;main;3,0.5;3,3;]"..
21 "listring[current_name;main]"..
22 "listring[current_player;main]"
23 local meta = minetest.get_meta(pos)
24 meta:set_string("formspec", form)
25 local inv = meta:get_inventory()
26 inv:set_size("main", 9)
27 end
29 local orientate_dispenser = function(pos, placer)
30 -- Not placed by player
31 if not placer then return end
33 -- Pitch in degrees
34 local pitch = placer:get_look_vertical() * (180 / math.pi)
36 local node = minetest.get_node(pos)
37 if pitch > 55 then
38 minetest.swap_node(pos, {name="mcl_dispensers:dispenser_up", param2 = node.param2})
39 elseif pitch < -55 then
40 minetest.swap_node(pos, {name="mcl_dispensers:dispenser_down", param2 = node.param2})
41 end
42 end
44 local on_rotate
45 if minetest.get_modpath("screwdriver") then
46 on_rotate = screwdriver.rotate_simple
47 end
49 -- Shared core definition table
50 local dispenserdef = {
51 is_ground_content = false,
52 sounds = mcl_sounds.node_sound_stone_defaults(),
53 after_dig_node = function(pos, oldnode, oldmetadata, digger)
54 local meta = minetest.get_meta(pos)
55 local meta2 = meta
56 meta:from_table(oldmetadata)
57 local inv = meta:get_inventory()
58 for i=1, inv:get_size("main") do
59 local stack = inv:get_stack("main", i)
60 if not stack:is_empty() then
61 local p = {x=pos.x+math.random(0, 10)/10-0.5, y=pos.y, z=pos.z+math.random(0, 10)/10-0.5}
62 minetest.add_item(p, stack)
63 end
64 end
65 meta:from_table(meta2:to_table())
66 end,
67 _mcl_blast_resistance = 17.5,
68 _mcl_hardness = 3.5,
69 mesecons = {effector = {
70 -- Dispense random item when triggered
71 action_on = function (pos, node)
72 local meta = minetest.get_meta(pos)
73 local inv = meta:get_inventory()
74 local droppos, dropdir
75 if node.name == "mcl_dispensers:dispenser" then
76 dropdir = vector.multiply(minetest.facedir_to_dir(node.param2), -1)
77 droppos = vector.add(pos, dropdir)
78 elseif node.name == "mcl_dispensers:dispenser_up" then
79 dropdir = {x=0, y=1, z=0}
80 droppos = {x=pos.x, y=pos.y+1, z=pos.z}
81 elseif node.name == "mcl_dispensers:dispenser_down" then
82 dropdir = {x=0, y=-1, z=0}
83 droppos = {x=pos.x, y=pos.y-1, z=pos.z}
84 end
85 local dropnode = minetest.get_node(droppos)
86 local dropnodedef = minetest.registered_nodes[dropnode.name]
87 local stacks = {}
88 for i=1,inv:get_size("main") do
89 local stack = inv:get_stack("main", i)
90 if not stack:is_empty() then
91 table.insert(stacks, {stack = stack, stackpos = i})
92 end
93 end
94 if #stacks >= 1 then
95 local r = math.random(1, #stacks)
96 local stack = stacks[r].stack
97 local dropitem = ItemStack(stack:get_name())
98 local stack_id = stacks[r].stackpos
99 local iname = stack:get_name()
100 local igroups = minetest.registered_items[iname].groups
102 -- Do not dispense into solid nodes. Exception: Water bucket into cauldron
103 if dropnodedef.walkable and not (minetest.get_item_group(dropnode.name, "cauldron") ~= 0 and (iname == "mcl_buckets:bucket_water" or iname == "mcl_buckets:bucket_river_water")) then
104 -- no-op
106 --[===[ Dispense item ]===]
107 elseif iname == "mcl_throwing:arrow" then
108 -- Shoot arrow
109 local shootpos = vector.add(pos, vector.multiply(dropdir, 0.51))
110 local yaw = math.atan2(dropdir.z, dropdir.x) - math.pi/2
111 mcl_throwing.shoot_arrow(iname, shootpos, dropdir, yaw, nil, 19, 3)
113 stack:take_item()
114 inv:set_stack("main", stack_id, stack)
116 elseif iname == "mcl_throwing:egg" or iname == "mcl_throwing:snowball" then
117 -- Throw egg or snowball
118 local shootpos = vector.add(pos, vector.multiply(dropdir, 0.51))
119 mcl_throwing.throw(iname, shootpos, dropdir)
121 stack:take_item()
122 inv:set_stack("main", stack_id, stack)
124 elseif iname == "mcl_fire:fire_charge" then
125 -- Throw fire charge
126 local shootpos = vector.add(pos, vector.multiply(dropdir, 0.51))
127 local fireball = minetest.add_entity(shootpos, "mobs_mc:blaze_fireball")
128 local ent = fireball:get_luaentity()
129 ent._shot_from_dispenser = true
130 local v = ent.velocity or 1
131 fireball:setvelocity(vector.multiply(dropdir, v))
132 ent.switch = 1
134 stack:take_item()
135 inv:set_stack("main", stack_id, stack)
137 elseif iname == "mcl_fire:flint_and_steel" then
138 -- Ignite air or fire
139 if dropnode.name == "air" then
140 minetest.add_node(droppos, {name="mcl_fire:fire"})
141 if not minetest.settings:get_bool("creative_mode") then
142 stack:add_wear(65535/65) -- 65 uses
144 elseif dropnode.name == "mcl_tnt:tnt" then
145 tnt.ignite(droppos)
146 if not minetest.settings:get_bool("creative_mode") then
147 stack:add_wear(65535/65) -- 65 uses
151 inv:set_stack("main", stack_id, stack)
152 elseif iname == "mcl_tnt:tnt" then
153 -- Place and ignite TNT
154 if dropnodedef.buildable_to then
155 minetest.set_node(droppos, {name = iname})
156 tnt.ignite(droppos)
158 stack:take_item()
159 inv:set_stack("main", stack_id, stack)
161 elseif iname == "mcl_buckets:bucket_empty" then
162 -- Fill empty bucket with liquid or drop bucket if no liquid
163 local collect_liquid = false
164 local bucket_id
165 if dropnode.name == "mcl_core:water_source" then
166 collect_liquid = true
167 bucket_id = "mcl_buckets:bucket_water"
168 elseif dropnode.name == "mcl_core:lava_source" or dropnode.name == "mcl_nether:nether_lava_source" then
169 collect_liquid = true
170 bucket_id = "mcl_buckets:bucket_lava"
171 elseif dropnode.name == "mclx_core:river_water_source" then
172 collect_liquid = true
173 bucket_id = "mcl_buckets:bucket_river_water"
175 if collect_liquid then
176 minetest.set_node(droppos, {name="air"})
178 -- Fill bucket with liquid and put it back into inventory
179 -- if there's still space. If not, drop it.
180 stack:take_item()
181 inv:set_stack("main", stack_id, stack)
183 local new_bucket = ItemStack(bucket_id)
184 if inv:room_for_item("main", new_bucket) then
185 inv:add_item("main", new_bucket)
186 else
187 minetest.add_item(droppos, dropitem)
189 else
190 -- No liquid found: Drop empty bucket
191 minetest.add_item(droppos, dropitem)
193 stack:take_item()
194 inv:set_stack("main", stack_id, stack)
196 elseif iname == "mcl_buckets:bucket_water" or iname == "mcl_buckets:bucket_river_water" or iname == "mcl_buckets:bucket_lava" then
197 local do_empty = false
198 -- Place water/lava source
199 if minetest.get_item_group(dropnode.name, "cauldron") ~= 0 then
200 if iname == "mcl_buckets:bucket_water" then
201 minetest.set_node(droppos, {name = "mcl_cauldrons:cauldron_3"})
202 do_empty = true
203 elseif iname == "mcl_buckets:bucket_river_water" then
204 minetest.set_node(droppos, {name = "mcl_cauldrons:cauldron_3r"})
205 do_empty = true
207 elseif dropnodedef.buildable_to then
208 local dim = mcl_worlds.pos_to_dimension(droppos)
209 if iname == "mcl_buckets:bucket_water" then
210 if dim == "nether" then
211 minetest.sound_play("fire_extinguish_flame", {pos = droppos, gain = 0.25, max_hear_distance = 16})
212 else
213 minetest.set_node(droppos, {name = "mcl_core:water_source"})
215 do_empty = true
216 elseif iname == "mcl_buckets:bucket_river_water" then
217 if dim == "nether" then
218 minetest.sound_play("fire_extinguish_flame", {pos = droppos, gain = 0.25, max_hear_distance = 16})
219 else
220 minetest.set_node(droppos, {name = "mclx_core:river_water_source"})
222 do_empty = true
223 elseif iname == "mcl_buckets:bucket_lava" then
224 if dim == "nether" then
225 minetest.set_node(droppos, {name = "mcl_nether:nether_lava_source"})
226 else
227 minetest.set_node(droppos, {name = "mcl_core:lava_source"})
229 do_empty = true
233 if do_empty then
234 stack:take_item()
235 inv:set_stack("main", stack_id, stack)
237 if inv:room_for_item("main", "mcl_buckets:bucket_empty") then
238 inv:add_item("main", "mcl_buckets:bucket_empty")
239 else
240 minetest.add_item(droppos, dropitem)
244 elseif iname == "mcl_dye:white" then
245 -- Apply bone meal, if possible
246 local pointed_thing
247 if dropnode.name == "air" then
248 pointed_thing = { above = droppos, under = { x=droppos.x, y=droppos.y-1, z=droppos.z } }
249 else
250 pointed_thing = { above = pos, under = droppos }
252 local success = mcl_dye.apply_bone_meal(pointed_thing)
253 if success then
254 stack:take_item()
255 inv:set_stack("main", stack_id, stack)
258 elseif minetest.get_item_group(iname, "minecart") == 1 then
259 -- Place minecart as entity on rail
260 local placed
261 if dropnodedef.groups.rail then
262 -- FIXME: This places minecarts even if the spot is already occupied
263 local pointed_thing = { under = droppos, above = { x=droppos.x, y=droppos.y+1, z=droppos.z } }
264 placed = mcl_minecarts.place_minecart(stack, pointed_thing)
266 if placed == nil then
267 -- Drop item
268 minetest.add_item(droppos, dropitem)
271 stack:take_item()
272 inv:set_stack("main", stack_id, stack)
274 elseif igroups.boat then
275 local below = {x=droppos.x, y=droppos.y-1, z=droppos.z}
276 local belownode = minetest.get_node(below)
277 -- Place boat as entity on or in water
278 if dropnodedef.groups.water or (dropnode.name == "air" and minetest.registered_nodes[belownode.name].groups.water) then
279 minetest.add_entity(droppos, "mcl_boats:boat")
280 else
281 minetest.add_item(droppos, dropitem)
284 stack:take_item()
285 inv:set_stack("main", stack_id, stack)
287 elseif igroups.armor_head or igroups.armor_torso or igroups.armor_legs or igroups.armor_feet then
288 local armor_type, armor_slot
289 local armor_dispensed = false
290 if igroups.armor_head then
291 armor_type = "armor_head"
292 armor_slot = 2
293 elseif igroups.armor_torso then
294 armor_type = "armor_torso"
295 armor_slot = 3
296 elseif igroups.armor_legs then
297 armor_type = "armor_legs"
298 armor_slot = 4
299 elseif igroups.armor_feet then
300 armor_type = "armor_feet"
301 armor_slot = 5
304 local droppos_below = {x=droppos.x, y=droppos.y-1, z=droppos.z}
305 local dropnode_below = minetest.get_node(droppos_below)
306 -- Put armor on player or armor stand
307 local standpos
308 if dropnode.name == "3d_armor_stand:armor_stand" then
309 standpos = droppos
310 elseif dropnode_below.name == "3d_armor_stand:armor_stand" then
311 standpos = droppos_below
313 if standpos then
314 local dropmeta = minetest.get_meta(standpos)
315 local dropinv = dropmeta:get_inventory()
316 if dropinv:room_for_item(armor_type, dropitem) then
317 dropinv:add_item(armor_type, dropitem)
318 --[[ FIXME: For some reason, this function is not called after calling add_item,
319 so we call it manually to update the armor stand entity.
320 This may need investigation and the following line may be a small hack. ]]
321 minetest.registered_nodes["3d_armor_stand:armor_stand"].on_metadata_inventory_put(standpos)
322 stack:take_item()
323 inv:set_stack("main", stack_id, stack)
324 armor_dispensed = true
326 else
327 -- Put armor on nearby player
328 -- First search for player in front of dispenser (check 2 nodes)
329 local objs1 = minetest.get_objects_inside_radius(droppos, 1)
330 local objs2 = minetest.get_objects_inside_radius(droppos_below, 1)
331 local objs_table = {objs1, objs2}
332 local player
333 for oi=1, #objs_table do
334 local objs_inner = objs_table[oi]
335 for o=1, #objs_inner do
336 --[[ First player in list is the lucky one. The other player get nothing :-(
337 If multiple players are close to the dispenser, it can be a bit
338 -- unpredictable on who gets the armor. ]]
339 if objs_inner[o]:is_player() then
340 player = objs_inner[o]
341 break
344 if player then
345 break
348 -- If player found, add armor
349 if player then
350 local ainv = minetest.get_inventory({type="detached", name=player:get_player_name().."_armor"})
351 local pinv = player:get_inventory()
352 if ainv:get_stack("armor", armor_slot):is_empty() and pinv:get_stack("armor", armor_slot):is_empty() then
353 ainv:set_stack("armor", armor_slot, dropitem)
354 pinv:set_stack("armor", armor_slot, dropitem)
355 armor:set_player_armor(player)
356 armor:update_inventory(player)
358 stack:take_item()
359 inv:set_stack("main", stack_id, stack)
360 armor_dispensed = true
364 -- Place head or pumpkin as node, if equipping it as armor has failed
365 if not armor_dispensed then
366 if igroups.head or iname == "mcl_farming:pumpkin_face" then
367 if dropnodedef.buildable_to then
368 minetest.set_node(droppos, {name = iname, param2 = node.param2})
369 stack:take_item()
370 inv:set_stack("main", stack_id, stack)
376 elseif igroups.shulker_box then
377 -- Place shulker box as node
378 if dropnodedef.buildable_to then
379 minetest.set_node(droppos, {name = iname, param2 = node.param2})
380 local imeta = stack:get_metadata()
381 local iinv_main = minetest.deserialize(imeta)
382 local ninv = minetest.get_inventory({type="node", pos=droppos})
383 ninv:set_list("main", iinv_main)
384 stack:take_item()
387 elseif igroups.spawn_egg then
388 -- Place spawn egg
389 if not dropnodedef.walkable then
390 pointed_thing = { above = droppos, under = { x=droppos.x, y=droppos.y-1, z=droppos.z } }
392 minetest.registered_items[iname].on_place(ItemStack(iname), nil, pointed_thing)
394 stack:take_item()
395 inv:set_stack("main", stack_id, stack)
398 -- TODO: Many other dispenser actions
399 else
400 -- Drop item
401 minetest.add_item(droppos, dropitem)
403 stack:take_item()
404 inv:set_stack("main", stack_id, stack)
407 end,
408 rules = mesecon.rules.alldirs,
410 on_rotate = on_rotate,
413 -- Horizontal dispenser
415 local horizontal_def = table.copy(dispenserdef)
416 horizontal_def.description = "Dispenser"
417 horizontal_def._doc_items_longdesc = "A dispenser is a block which acts as a redstone component which, when powered with redstone power, dispenses an item. It has a container with 9 inventory slots."
418 horizontal_def._doc_items_usagehelp = [[Place the dispenser in one of 6 possible directions. The “hole” is where items will fly out of the dispenser. Rightclick the dispenser to access its inventory. Insert the items you wish to dispense. Supply the dispenser with redstone energy once to dispense a single random item.
420 The dispenser will do different things, depending on the dispensed item:
422 • Arrows: Are launched
423 • Eggs and snowballs: Are thrown
424 • Fire charges: Are fired in a straight line
425 • Armor: Will be equipped to players and armor stands
426 • Boats: Are placed on water or are dropped
427 • Minecart: Are placed on rails or are dropped
428 • Bone meal: Is applied on the block it is facint
429 • Empty buckets: Are used to collect a liquid source
430 • Filled buckets: Are used to place a liquid source
431 • Heads, pumpkins: Equipped to players and armor stands, or placed as a block
432 • Shulker boxes: Are placed as a block
433 • TNT: Is placed and ignited
434 • Flint and steel: Is used to ignite a fire in air and to ignite TNT
435 • Spawn eggs: Will summon the mob they contain
436 • Other items: Are simply dropped]]
438 horizontal_def.after_place_node = function(pos, placer, itemstack, pointed_thing)
439 setup_dispenser(pos)
440 orientate_dispenser(pos, placer)
442 horizontal_def.tiles = {
443 "default_furnace_top.png", "default_furnace_bottom.png",
444 "default_furnace_side.png", "default_furnace_side.png",
445 "default_furnace_side.png", "mcl_dispensers_dispenser_front_horizontal.png"
447 horizontal_def.paramtype2 = "facedir"
448 horizontal_def.groups = {pickaxey=1, container=2, material_stone=1}
450 minetest.register_node("mcl_dispensers:dispenser", horizontal_def)
452 -- Down dispenser
453 local down_def = table.copy(dispenserdef)
454 down_def.description = "Downwards-Facing Dispenser"
455 down_def.after_place_node = setup_dispenser
456 down_def.tiles = {
457 "default_furnace_top.png", "mcl_dispensers_dispenser_front_vertical.png",
458 "default_furnace_side.png", "default_furnace_side.png",
459 "default_furnace_side.png", "default_furnace_side.png"
461 down_def.groups = {pickaxey=1, container=2,not_in_creative_inventory=1, material_stone=1}
462 down_def._doc_items_create_entry = false
463 down_def.drop = "mcl_dispensers:dispenser"
464 minetest.register_node("mcl_dispensers:dispenser_down", down_def)
466 -- Up dispenser
467 -- The up dispenser is almost identical to the down dispenser , it only differs in textures
468 local up_def = table.copy(down_def)
469 up_def.description = "Upwards-Facing Dispenser"
470 up_def.tiles = {
471 "mcl_dispensers_dispenser_front_vertical.png", "default_furnace_bottom.png",
472 "default_furnace_side.png", "default_furnace_side.png",
473 "default_furnace_side.png", "default_furnace_side.png"
475 minetest.register_node("mcl_dispensers:dispenser_up", up_def)
478 minetest.register_craft({
479 output = 'mcl_dispensers:dispenser',
480 recipe = {
481 {"mcl_core:cobble", "mcl_core:cobble", "mcl_core:cobble",},
482 {"mcl_core:cobble", "mcl_throwing:bow", "mcl_core:cobble",},
483 {"mcl_core:cobble", "mesecons:redstone", "mcl_core:cobble",},
487 -- Only allow crafting if the bow is intact
488 local check_craft = function(itemstack, player, old_craft_grid, craft_inv)
489 if itemstack:get_name() == "mcl_dispensers:dispenser" then
490 local bow, id
491 for i=1, craft_inv:get_size("craft") do
492 local item = craft_inv:get_stack("craft", i)
493 if item:get_name() == "mcl_throwing:bow" then
494 bow = item
495 id = i
496 break
499 if bow and bow:get_wear() ~= 0 then
500 return ""
503 return nil
506 minetest.register_on_craft(check_craft)
507 minetest.register_craft_predict(check_craft)
509 -- Add entry aliases for the Help
510 if minetest.get_modpath("doc") then
511 doc.add_entry_alias("nodes", "mcl_dispensers:dispenser", "nodes", "mcl_dispensers:dispenser_down")
512 doc.add_entry_alias("nodes", "mcl_dispensers:dispenser", "nodes", "mcl_dispensers:dispenser_up")