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.
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)
29 local orientate_dispenser
= function(pos
, placer
)
30 -- Not placed by player
31 if not placer
then return end
34 local pitch
= placer
:get_look_vertical() * (180 / math
.pi
)
36 local node
= minetest
.get_node(pos
)
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
})
45 if minetest
.get_modpath("screwdriver") then
46 on_rotate
= screwdriver
.rotate_simple
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
)
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
)
65 meta
:from_table(meta2
:to_table())
67 _mcl_blast_resistance
= 17.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
}
85 local dropnode
= minetest
.get_node(droppos
)
86 local dropnodedef
= minetest
.registered_nodes
[dropnode
.name
]
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
})
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
106 --[===[ Dispense item ]===]
107 elseif iname
== "mcl_throwing:arrow" then
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)
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
)
122 inv
:set_stack("main", stack_id
, stack
)
124 elseif iname
== "mcl_fire:fire_charge" then
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
))
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
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
})
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
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.
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
)
187 minetest
.add_item(droppos
, dropitem
)
190 -- No liquid found: Drop empty bucket
191 minetest
.add_item(droppos
, dropitem
)
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"})
203 elseif iname
== "mcl_buckets:bucket_river_water" then
204 minetest
.set_node(droppos
, {name
= "mcl_cauldrons:cauldron_3r"})
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})
213 minetest
.set_node(droppos
, {name
= "mcl_core:water_source"})
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})
220 minetest
.set_node(droppos
, {name
= "mclx_core:river_water_source"})
223 elseif iname
== "mcl_buckets:bucket_lava" then
224 if dim
== "nether" then
225 minetest
.set_node(droppos
, {name
= "mcl_nether:nether_lava_source"})
227 minetest
.set_node(droppos
, {name
= "mcl_core:lava_source"})
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")
240 minetest
.add_item(droppos
, dropitem
)
244 elseif iname
== "mcl_dye:white" then
245 -- Apply bone meal, if possible
247 if dropnode
.name
== "air" then
248 pointed_thing
= { above
= droppos
, under
= { x
=droppos
.x
, y
=droppos
.y
-1, z
=droppos
.z
} }
250 pointed_thing
= { above
= pos
, under
= droppos
}
252 local success
= mcl_dye
.apply_bone_meal(pointed_thing
)
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
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
268 minetest
.add_item(droppos
, dropitem
)
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")
281 minetest
.add_item(droppos
, dropitem
)
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"
293 elseif igroups
.armor_torso
then
294 armor_type
= "armor_torso"
296 elseif igroups
.armor_legs
then
297 armor_type
= "armor_legs"
299 elseif igroups
.armor_feet
then
300 armor_type
= "armor_feet"
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
308 if dropnode
.name
== "3d_armor_stand:armor_stand" then
310 elseif dropnode_below
.name
== "3d_armor_stand:armor_stand" then
311 standpos
= droppos_below
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
)
323 inv
:set_stack("main", stack_id
, stack
)
324 armor_dispensed
= true
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
}
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
]
348 -- If player found, add armor
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
)
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
})
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
)
387 elseif igroups
.spawn_egg
then
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
)
395 inv
:set_stack("main", stack_id
, stack
)
398 -- TODO: Many other dispenser actions
401 minetest
.add_item(droppos
, dropitem
)
404 inv
:set_stack("main", stack_id
, stack
)
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
)
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
)
453 local down_def
= table.copy(dispenserdef
)
454 down_def
.description
= "Downwards-Facing Dispenser"
455 down_def
.after_place_node
= setup_dispenser
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
)
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"
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',
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
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
499 if bow
and bow
:get_wear() ~= 0 then
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")