5 Copyright (C) 2012-2016 PilzAdam
6 Copyright (C) 2014-2016 BlockMen
7 Copyright (C) 2015-2016 sofar (sofar@foo-projects.org)
8 Copyright (C) 2012-2016 Various Minetest developers and contributors
10 Permission is hereby granted, free of charge, to any person obtaining
11 a copy of this software and associated documentation files (the
12 "Software"), to deal in the Software without restriction, including
13 without limitation the rights to use, copy, modify, merge, publish,
14 distribute, sublicense, and/or sell copies of the Software, and to
15 permit persons to whom the Software is furnished to do so, subject
16 to the following conditions:
18 The above copyright notice and this permission notice shall be included
19 in all copies or substantial portions of the Software.
21 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
22 KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
23 WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 local S
= minetest
.get_translator("doors")
38 _doors
.registered_doors
= {}
39 _doors
.registered_trapdoors
= {}
41 -- returns an object to a door object or nil
42 function doors
.get(pos
)
43 local node_name
= minetest
.get_node(pos
).name
44 if _doors
.registered_doors
[node_name
] then
45 -- A normal upright door
48 open
= function(self
, player
)
52 return _doors
.door_toggle(self
.pos
, nil, player
)
54 close
= function(self
, player
)
55 if not self
:state() then
58 return _doors
.door_toggle(self
.pos
, nil, player
)
60 toggle
= function(self
, player
)
61 return _doors
.door_toggle(self
.pos
, nil, player
)
63 state
= function(self
)
64 local state
= minetest
.get_meta(self
.pos
):get_int("state")
68 elseif _doors
.registered_trapdoors
[node_name
] then
72 open
= function(self
, player
)
76 return _doors
.trapdoor_toggle(self
.pos
, nil, player
)
78 close
= function(self
, player
)
79 if not self
:state() then
82 return _doors
.trapdoor_toggle(self
.pos
, nil, player
)
84 toggle
= function(self
, player
)
85 return _doors
.trapdoor_toggle(self
.pos
, nil, player
)
87 state
= function(self
)
88 return minetest
.get_node(self
.pos
).name
:sub(-5) == "_open"
96 -- this hidden node is placed on top of the bottom, and prevents
97 -- nodes from being placed in the top half of the door.
98 minetest
.register_node("doors:hidden", {
99 description
= S("Hidden door segment"),
100 -- can't use airlike otherwise falling nodes will turn to entities
101 -- and will be forever stuck until door is removed.
102 drawtype
= "nodebox",
103 use_texture_alpha
= "clip",
105 paramtype2
= "facedir",
106 sunlight_propagates
= true,
107 -- has to be walkable for falling nodes to stop falling.
111 buildable_to
= false,
115 groups
= {not_in_creative_inventory
= 1},
116 on_blast
= function() end,
117 tiles
= {"itb_blank.png"},
118 -- 1px transparent block inside door hinge near node top.
121 fixed
= {-15/32, 13/32, -15/32, -13/32, 1/2, -13/32},
123 -- collision_box needed otherise selection box would be full node size
126 fixed
= {-15/32, 13/32, -15/32, -13/32, 1/2, -13/32},
130 -- table used to aid door opening/closing
133 {v
= "_a", param2
= 3},
134 {v
= "_a", param2
= 0},
135 {v
= "_a", param2
= 1},
136 {v
= "_a", param2
= 2},
139 {v
= "_b", param2
= 1},
140 {v
= "_b", param2
= 2},
141 {v
= "_b", param2
= 3},
142 {v
= "_b", param2
= 0},
145 {v
= "_b", param2
= 1},
146 {v
= "_b", param2
= 2},
147 {v
= "_b", param2
= 3},
148 {v
= "_b", param2
= 0},
151 {v
= "_a", param2
= 3},
152 {v
= "_a", param2
= 0},
153 {v
= "_a", param2
= 1},
154 {v
= "_a", param2
= 2},
158 function _doors
.door_toggle(pos
, node
, clicker
)
159 local meta
= minetest
.get_meta(pos
)
160 node
= node
or minetest
.get_node(pos
)
161 local def
= minetest
.registered_nodes
[node
.name
]
162 local name
= def
.door
.name
164 local state
= meta
:get_string("state")
166 -- fix up lvm-placed right-hinged doors, default closed
167 if node
.name
:sub(-2) == "_b" then
173 state
= tonumber(state
)
176 -- until Lua-5.2 we have no bitwise operators :(
177 if state
% 2 == 1 then
183 local dir
= node
.param2
184 if state
% 2 == 0 then
185 minetest
.sound_play(def
.door
.sounds
[1],
186 {pos
= pos
, gain
= 0.3, max_hear_distance
= 32})
188 minetest
.sound_play(def
.door
.sounds
[2],
189 {pos
= pos
, gain
= 0.3, max_hear_distance
= 32})
192 minetest
.swap_node(pos
, {
193 name
= name
.. transform
[state
+ 1][dir
+1].v
,
194 param2
= transform
[state
+ 1][dir
+1].param2
196 meta
:set_int("state", state
)
202 local function on_place_node(place_to
, newnode
,
203 placer
, oldnode
, itemstack
, pointed_thing
)
205 for _
, callback
in ipairs(minetest
.registered_on_placenodes
) do
206 -- Deepcopy pos, node and pointed_thing because callback can modify them
207 local place_to_copy
= {x
= place_to
.x
, y
= place_to
.y
, z
= place_to
.z
}
209 {name
= newnode
.name
, param1
= newnode
.param1
, param2
= newnode
.param2
}
211 {name
= oldnode
.name
, param1
= oldnode
.param1
, param2
= oldnode
.param2
}
212 local pointed_thing_copy
= {
213 type = pointed_thing
.type,
214 above
= vector
.new(pointed_thing
.above
),
215 under
= vector
.new(pointed_thing
.under
),
216 ref
= pointed_thing
.ref
,
218 callback(place_to_copy
, newnode_copy
, placer
,
219 oldnode_copy
, itemstack
, pointed_thing_copy
)
223 local function can_dig_door(pos
, digger
)
224 local digger_name
= digger
and digger
:get_player_name()
225 if boxes
.players_editing_boxes
[digger_name
] then
226 -- don't allow breaking entrance/exit doors
227 local box
= boxes
.players_editing_boxes
[digger_name
]
228 if pos
.x
<= box
.minp
.x
or pos
.x
>= box
.maxp
.x
or
229 pos
.y
<= box
.minp
.y
or pos
.y
>= box
.maxp
.y
or
230 pos
.z
<= box
.minp
.z
or pos
.z
>= box
.maxp
.z
235 elseif boxes
.players_in_boxes
[digger_name
] then
238 return minetest
.check_player_privs(digger_name
, "server")
241 function doors
.register(name
, def
)
242 if not name
:find(":") then
243 name
= "doors:" .. name
246 minetest
.register_craftitem(":" .. name
, {
247 description
= def
.description
,
248 inventory_image
= def
.inventory_image
,
250 on_place
= function(itemstack
, placer
, pointed_thing
)
253 if pointed_thing
.type ~= "node" then
257 local node
= minetest
.get_node(pointed_thing
.under
)
258 local pdef
= minetest
.registered_nodes
[node
.name
]
259 if pdef
and pdef
.on_rightclick
and
260 not placer
:get_player_control().sneak
then
261 return pdef
.on_rightclick(pointed_thing
.under
,
262 node
, placer
, itemstack
, pointed_thing
)
265 if pdef
and pdef
.buildable_to
then
266 pos
= pointed_thing
.under
268 pos
= pointed_thing
.above
269 node
= minetest
.get_node(pos
)
270 pdef
= minetest
.registered_nodes
[node
.name
]
271 if not pdef
or not pdef
.buildable_to
then
276 local above
= {x
= pos
.x
, y
= pos
.y
+ 1, z
= pos
.z
}
277 local top_node
= minetest
.get_node_or_nil(above
)
278 local topdef
= top_node
and minetest
.registered_nodes
[top_node
.name
]
280 if not topdef
or not topdef
.buildable_to
then
284 local dir
= minetest
.dir_to_facedir(placer
:get_look_dir())
287 {x
= -1, y
= 0, z
= 0},
288 {x
= 0, y
= 0, z
= 1},
289 {x
= 1, y
= 0, z
= 0},
290 {x
= 0, y
= 0, z
= -1},
294 x
= pos
.x
+ ref
[dir
+ 1].x
,
295 y
= pos
.y
+ ref
[dir
+ 1].y
,
296 z
= pos
.z
+ ref
[dir
+ 1].z
,
300 if minetest
.get_item_group(minetest
.get_node(aside
).name
, "door") == 1 then
302 minetest
.set_node(pos
, {name
= name
.. "_b", param2
= dir
})
303 minetest
.set_node(above
, {name
= "doors:hidden", param2
= (dir
+ 3) % 4})
305 minetest
.set_node(pos
, {name
= name
.. "_a", param2
= dir
})
306 minetest
.set_node(above
, {name
= "doors:hidden", param2
= dir
})
309 local meta
= minetest
.get_meta(pos
)
310 meta
:set_int("state", state
)
312 if def
.sounds
and def
.sounds
.place
then
313 minetest
.sound_play(def
.sounds
.place
,
314 {pos
= pos
, gain
= 1.0, max_hear_distance
= 32})
316 on_place_node(pos
, minetest
.get_node(pos
),
317 placer
, node
, itemstack
, pointed_thing
)
322 def
.inventory_image
= nil
324 if not def
.sound_open
then
325 def
.sound_open
= "doors_door_open"
328 if not def
.sound_close
then
329 def
.sound_close
= "doors_door_close"
332 def
.groups
.not_in_creative_inventory
= 1
335 def
.unpushable
= true
339 sounds
= { def
.sound_close
, def
.sound_open
},
342 if not def
.protected
then
343 def
.on_rightclick
= function(pos
, node
, clicker
, itemstack
, pointed_thing
)
344 _doors
.door_toggle(pos
, node
, clicker
)
349 def
.groups
.unbreakable
= 2
351 def
.after_dig_node
= function(pos
, node
, meta
, digger
)
352 mech
.after_dig(pos
, node
, meta
, digger
)
353 minetest
.remove_node({x
= pos
.x
, y
= pos
.y
+ 1, z
= pos
.z
})
354 minetest
.check_for_falling({x
= pos
.x
, y
= pos
.y
+ 1, z
= pos
.z
})
356 def
.on_rotate
= function(pos
, node
, user
, mode
, new_param2
)
359 def
.on_trigger
= function(pos
)
360 local d
= doors
.get(pos
)
365 def
.on_untrigger
= function(pos
)
366 local d
= doors
.get(pos
)
372 def
.can_dig
= can_dig_door
374 def
.on_destruct
= function(pos
)
375 minetest
.remove_node({x
= pos
.x
, y
= pos
.y
+ 1, z
= pos
.z
})
378 def
.drawtype
= "mesh"
379 def
.use_texture_alpha
= "clip"
380 def
.paramtype
= "light"
381 def
.paramtype2
= "facedir"
382 def
.sunlight_propagates
= true
384 def
.is_ground_content
= false
385 def
.buildable_to
= false
386 def
.selection_box
= {type = "fixed", fixed
= {-1/2,-1/2,-1/2,1/2,3/2,-6/16}}
387 def
.collision_box
= {type = "fixed", fixed
= {-1/2,-1/2,-1/2,1/2,3/2,-6/16}}
389 def
.mesh
= "door_a.obj"
390 minetest
.register_node(":" .. name
.. "_a", def
)
392 def
.mesh
= "door_b.obj"
393 minetest
.register_node(":" .. name
.. "_b", def
)
395 _doors
.registered_doors
[name
.. "_a"] = true
396 _doors
.registered_doors
[name
.. "_b"] = true
399 doors
.register("door_wood", {
400 tiles
= {{ name
= "itb_doors_door_wood.png", backface_culling
= true }},
401 description
= S("Wooden door"),
402 inventory_image
= "itb_doors_item_wood.png",
403 groups
= {unbreakable
= 1, node
= 1, door
= 1},
404 sounds
= sounds
.wood
,
407 doors
.register("door_steel", {
408 tiles
= {{name
= "itb_doors_door_steel.png", backface_culling
= true}},
409 description
= S("Steel door"),
410 inventory_image
= "itb_doors_item_steel.png",
412 groups
= {unbreakable
= 2, node
= 2, door
= 1},
413 sounds
= sounds
.metal
,
414 sound_open
= "doors_steel_door_open",
415 sound_close
= "doors_steel_door_close",
420 function _doors
.trapdoor_toggle(pos
, node
, clicker
)
421 node
= node
or minetest
.get_node(pos
)
423 local def
= minetest
.registered_nodes
[node
.name
]
425 if string.sub(node
.name
, -5) == "_open" then
426 minetest
.sound_play(def
.sound_close
,
427 {pos
= pos
, gain
= 0.3, max_hear_distance
= 32})
428 minetest
.swap_node(pos
, {name
= string.sub(node
.name
, 1,
429 string.len(node
.name
) - 5), param1
= node
.param1
, param2
= node
.param2
})
431 minetest
.sound_play(def
.sound_open
,
432 {pos
= pos
, gain
= 0.3, max_hear_distance
= 32})
433 minetest
.swap_node(pos
, {name
= node
.name
.. "_open",
434 param1
= node
.param1
, param2
= node
.param2
})
438 function doors
.register_trapdoor(name
, def
)
439 if not name
:find(":") then
440 name
= "doors:" .. name
443 local name_closed
= name
444 local name_opened
= name
.."_open"
446 if not def
.protected
then
447 def
.on_rightclick
= function(pos
, node
, clicker
, itemstack
, pointed_thing
)
448 _doors
.trapdoor_toggle(pos
, node
, clicker
)
453 def
.groups
.unbreakable
= 2
455 def
.on_trigger
= function(pos
)
456 local d
= doors
.get(pos
)
461 def
.on_untrigger
= function(pos
)
462 local d
= doors
.get(pos
)
468 -- Common trapdoor configuration
469 def
.drawtype
= "nodebox"
470 def
.use_texture_alpha
= "clip"
471 def
.paramtype
= "light"
472 def
.paramtype2
= "facedir"
473 def
.is_ground_content
= false
474 def
.after_dig_node
= mech
.after_dig
475 def
.can_dig
= can_dig_door
477 if not def
.sound_open
then
478 def
.sound_open
= "doors_door_open"
481 if not def
.sound_close
then
482 def
.sound_close
= "doors_door_close"
485 local def_opened
= table.copy(def
)
486 local def_closed
= table.copy(def
)
488 def_closed
.node_box
= {
490 fixed
= {-0.5, -0.5, -0.5, 0.5, -6/16, 0.5}
492 def_closed
.selection_box
= {
494 fixed
= {-0.5, -0.5, -0.5, 0.5, -6/16, 0.5}
496 def_closed
.tiles
= {def
.tile_front
,
497 def
.tile_front
.. '^[transformFY',
498 def
.tile_side
, def
.tile_side
,
499 def
.tile_side
, def
.tile_side
}
501 def_opened
.node_box
= {
503 fixed
= {-0.5, -0.5, 6/16, 0.5, 0.5, 0.5}
505 def_opened
.selection_box
= {
507 fixed
= {-0.5, -0.5, 6/16, 0.5, 0.5, 0.5}
509 def_opened
.tiles
= {def
.tile_side
, def
.tile_side
,
510 def
.tile_side
.. '^[transform3',
511 def
.tile_side
.. '^[transform1',
512 def
.tile_front
.. '^[transform46',
513 def
.tile_front
.. '^[transform6'}
515 def_opened
.drop
= name_closed
516 def_opened
.groups
.not_in_creative_inventory
= 1
518 minetest
.register_node(name_opened
, def_opened
)
519 minetest
.register_node(name_closed
, def_closed
)
521 _doors
.registered_trapdoors
[name_opened
] = true
522 _doors
.registered_trapdoors
[name_closed
] = true
525 doors
.register_trapdoor("doors:trapdoor", {
526 description
= S("Wooden trapdoor"),
527 inventory_image
= "itb_doors_trapdoor.png",
528 wield_image
= "itb_doors_trapdoor.png",
529 tile_front
= "itb_doors_trapdoor.png",
530 tile_side
= "itb_doors_trapdoor_side.png",
531 groups
= {unbreakable
= 1, node
= 1, door
= 1},
532 sounds
= sounds
.wood
,
535 doors
.register_trapdoor("doors:trapdoor_steel", {
536 description
= S("Steel trapdoor"),
537 inventory_image
= "itb_doors_trapdoor_steel.png",
538 wield_image
= "itb_doors_trapdoor_steel.png",
539 tile_front
= "itb_doors_trapdoor_steel.png",
540 tile_side
= "itb_doors_trapdoor_steel_side.png",
542 sound_open
= "doors_steel_door_open",
543 sound_close
= "doors_steel_door_close",
544 groups
= {unbreakable
= 2, node
= 2, door
= 1},
545 sounds
= sounds
.metal
,
551 function doors
.register_fencegate(name
, def
)
552 local on_trigger
= function(pos
)
553 local node
= minetest
.get_node(pos
)
554 local node_def
= minetest
.registered_nodes
[node
.name
]
555 minetest
.swap_node(pos
, {name
= node_def
.gate
, param2
= node
.param2
})
556 minetest
.sound_play(node_def
.sound
, {pos
= pos
, gain
= 0.3,
557 max_hear_distance
= 32})
560 description
= def
.description
,
562 use_texture_alpha
= "clip",
563 tiles
= {def
.texture
},
565 paramtype2
= "facedir",
566 sunlight_propagates
= true,
567 is_ground_content
= false,
568 drop
= name
.. "_closed",
569 connect_sides
= {"left", "right"},
571 after_dig_node
= mech
.after_dig
,
572 on_rightclick
= function(pos
, node
, clicker
, itemstack
, pointed_thing
)
573 local node_def
= minetest
.registered_nodes
[node
.name
]
574 minetest
.swap_node(pos
, {name
= node_def
.gate
, param2
= node
.param2
})
575 minetest
.sound_play(node_def
.sound
, {pos
= pos
, gain
= 0.3,
576 max_hear_distance
= 32})
581 fixed
= {-1/2, -1/2, -1/4, 1/2, 1/2, 1/4},
583 on_trigger
= on_trigger
,
584 on_untrigger
= on_trigger
,
588 fence
.groups
.fence
= 1
590 local fence_closed
= table.copy(fence
)
591 fence_closed
.mesh
= "doors_fencegate_closed.obj"
592 fence_closed
.gate
= name
.. "_open"
593 fence_closed
.sound
= "doors_fencegate_open"
594 fence_closed
.collision_box
= {
596 fixed
= {-1/2, -1/2, -1/4, 1/2, 1, 1/4},
599 local fence_open
= table.copy(fence
)
600 fence_open
.mesh
= "doors_fencegate_open.obj"
601 fence_open
.gate
= name
.. "_closed"
602 fence_open
.sound
= "doors_fencegate_close"
603 fence_open
.groups
.not_in_creative_inventory
= 1
604 fence_open
.collision_box
= {
606 fixed
= {{-1/2, -1/2, -1/4, -3/8, 1, 1/4},
607 {-1/2, -3/8, -1/2, -3/8, 1, 0}},
610 minetest
.register_node(":" .. name
.. "_closed", fence_closed
)
611 minetest
.register_node(":" .. name
.. "_open", fence_open
)
614 doors
.register_fencegate("doors:gate_wood_medium", {
615 description
= S("Medium wood fence gate"),
616 texture
= "blocks_tiles.png^[sheet:8x8:2,3",
618 sounds
= sounds
.wood
,
621 doors
.register_fencegate("doors:gate_wood_dark", {
622 description
= S("Dark wood fence gate"),
623 texture
= "blocks_tiles.png^[sheet:8x8:0,3",
625 sounds
= sounds
.wood
,
628 doors
.register_fencegate("doors:gate_wood_light", {
629 description
= S("Light wood fence gate"),
630 texture
= "blocks_tiles.png^[sheet:8x8:1,3",
632 sounds
= sounds
.wood
,