Avoid unsafe `not` before condition.
[insidethebox.git] / mods / doors / init.lua
blobd0ad910e2beaacd01d368a036afd87783cdeff9d
2 --[[
4 The MIT License (MIT)
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.
29 ]]--
31 local S = minetest.get_translator("doors")
33 -- our API object
34 doors = {}
36 -- private data
37 local _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
46 return {
47 pos = pos,
48 open = function(self, player)
49 if self:state() then
50 return false
51 end
52 return _doors.door_toggle(self.pos, nil, player)
53 end,
54 close = function(self, player)
55 if not self:state() then
56 return false
57 end
58 return _doors.door_toggle(self.pos, nil, player)
59 end,
60 toggle = function(self, player)
61 return _doors.door_toggle(self.pos, nil, player)
62 end,
63 state = function(self)
64 local state = minetest.get_meta(self.pos):get_int("state")
65 return state %2 == 1
66 end
68 elseif _doors.registered_trapdoors[node_name] then
69 -- A trapdoor
70 return {
71 pos = pos,
72 open = function(self, player)
73 if self:state() then
74 return false
75 end
76 return _doors.trapdoor_toggle(self.pos, nil, player)
77 end,
78 close = function(self, player)
79 if not self:state() then
80 return false
81 end
82 return _doors.trapdoor_toggle(self.pos, nil, player)
83 end,
84 toggle = function(self, player)
85 return _doors.trapdoor_toggle(self.pos, nil, player)
86 end,
87 state = function(self)
88 return minetest.get_node(self.pos).name:sub(-5) == "_open"
89 end
91 else
92 return nil
93 end
94 end
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",
104 paramtype = "light",
105 paramtype2 = "facedir",
106 sunlight_propagates = true,
107 -- has to be walkable for falling nodes to stop falling.
108 walkable = true,
109 pointable = false,
110 diggable = false,
111 buildable_to = false,
112 floodable = false,
113 unpushable = true,
114 drop = "",
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.
119 nodebox = {
120 type = "fixed",
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
124 collision_box = {
125 type = "fixed",
126 fixed = {-15/32, 13/32, -15/32, -13/32, 1/2, -13/32},
130 -- table used to aid door opening/closing
131 local transform = {
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")
165 if state == "" then
166 -- fix up lvm-placed right-hinged doors, default closed
167 if node.name:sub(-2) == "_b" then
168 state = 2
169 else
170 state = 0
172 else
173 state = tonumber(state)
176 -- until Lua-5.2 we have no bitwise operators :(
177 if state % 2 == 1 then
178 state = state - 1
179 else
180 state = state + 1
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})
187 else
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)
198 return true
202 local function on_place_node(place_to, newnode,
203 placer, oldnode, itemstack, pointed_thing)
204 -- Run script hook
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}
208 local newnode_copy =
209 {name = newnode.name, param1 = newnode.param1, param2 = newnode.param2}
210 local oldnode_copy =
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
231 then
232 return false
234 return true
235 elseif boxes.players_in_boxes[digger_name] then
236 return false
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)
251 local pos
253 if pointed_thing.type ~= "node" then
254 return itemstack
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
267 else
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
272 return itemstack
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
281 return itemstack
284 local dir = minetest.dir_to_facedir(placer:get_look_dir())
286 local ref = {
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},
293 local aside = {
294 x = pos.x + ref[dir + 1].x,
295 y = pos.y + ref[dir + 1].y,
296 z = pos.z + ref[dir + 1].z,
299 local state = 0
300 if minetest.get_item_group(minetest.get_node(aside).name, "door") == 1 then
301 state = state + 2
302 minetest.set_node(pos, {name = name .. "_b", param2 = dir})
303 minetest.set_node(above, {name = "doors:hidden", param2 = (dir + 3) % 4})
304 else
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)
319 return itemstack
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
333 def.groups.door = 1
334 def.groups.node = 1
335 def.unpushable = true
336 def.drop = name
337 def.door = {
338 name = name,
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)
345 return itemstack
347 else
348 def.groups.node = 2
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)
357 return false
359 def.on_trigger = function(pos)
360 local d = doors.get(pos)
361 if d then
362 d:open()
365 def.on_untrigger = function(pos)
366 local d = doors.get(pos)
367 if d then
368 d:close()
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
383 def.walkable = 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",
411 protected = true,
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",
418 ----trapdoor----
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})
430 else
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)
449 return itemstack
451 else
452 def.groups.node = 2
453 def.groups.unbreakable = 2
455 def.on_trigger = function(pos)
456 local d = doors.get(pos)
457 if d then
458 d:open()
461 def.on_untrigger = function(pos)
462 local d = doors.get(pos)
463 if d then
464 d:close()
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 = {
489 type = "fixed",
490 fixed = {-0.5, -0.5, -0.5, 0.5, -6/16, 0.5}
492 def_closed.selection_box = {
493 type = "fixed",
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 = {
502 type = "fixed",
503 fixed = {-0.5, -0.5, 6/16, 0.5, 0.5, 0.5}
505 def_opened.selection_box = {
506 type = "fixed",
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",
541 protected = true,
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,
549 ----fence gate----
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})
559 local fence = {
560 description = def.description,
561 drawtype = "mesh",
562 use_texture_alpha = "clip",
563 tiles = {def.texture},
564 paramtype = "light",
565 paramtype2 = "facedir",
566 sunlight_propagates = true,
567 is_ground_content = false,
568 drop = name .. "_closed",
569 connect_sides = {"left", "right"},
570 groups = def.groups,
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})
577 return itemstack
578 end,
579 selection_box = {
580 type = "fixed",
581 fixed = {-1/2, -1/2, -1/4, 1/2, 1/2, 1/4},
583 on_trigger = on_trigger,
584 on_untrigger = on_trigger,
585 sounds = def.sounds,
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 = {
595 type = "fixed",
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 = {
605 type = "fixed",
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",
617 groups = {node = 1},
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",
624 groups = {node = 1},
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",
631 groups = {node = 1},
632 sounds = sounds.wood,