1. Please be advised of a few specific rules and guidelines for this section.

RELEASED Automatic Doors 3.9

Makes all doors open and close automatically!

  1. lornlynx

    lornlynx Cosmic Narwhal

  2. WSilvermane

    WSilvermane Scruffy Nerf-Herder

    Honestly, I doubt this is the mods fault. I'm fully updated and can't go to the outpost without mods either. :/

    It's Chucklefish's problem again, I think.
     
  3. lornlynx

    lornlynx Cosmic Narwhal

    very interesting
    Personally I could go to the outpost without issues, with about 3-4 mods installed. Maybe the amount of npc's there creates the issue. Good to know my mod's not at fault at least, lol.
     
  4. WSilvermane

    WSilvermane Scruffy Nerf-Herder

    Indeed, I even started fresh. Had to admin my way into doing anything past getting cores, since I can't go to the outpost; I can't do missions and get ship upgrades. You know?

    QUITE the problem. Though I can assure you, your mod works. But Starbound doesn't... It seems.
     
  5. lornlynx

    lornlynx Cosmic Narwhal

    Could you maybe post your logfile somewhere (you find it in the giraffe_storage folder inside your Starbound folder) after your game crashed? Might give a clue to the issue.
     
  6. Manjaro

    Manjaro Void-Bound Voyager

    currently the mod breaks the "Mysterious Party" quest boss trapdoor. The door will open automatically causing the player to fall through and not finish the boss fight. you still pass the quest but you end up not fully seeing it all through. tested 3 times. first time for my first run out of suprise, second time with the mod disabled and the third time because i truly didn't realize it the second time when the door didn't open.
     
    lornlynx likes this.
  7. lornlynx

    lornlynx Cosmic Narwhal

    Thank you for the report, I will make the door an exception!
     
  8. lornlynx

    lornlynx Cosmic Narwhal

    a short question, with the door you mean the trapdoor that looks like dirt, correct?
     
  9. Manjaro

    Manjaro Void-Bound Voyager

    Yes. The Dirt Trapdoor asset. That seems to be all i can make of it, as the other doors in the level work fine.
     
  10. lornlynx

    lornlynx Cosmic Narwhal

  11. lornlynx

    lornlynx Cosmic Narwhal

  12. Zoomah

    Zoomah Big Damn Hero

    Can you modify your code a little, so doors close automatically, even after Tenants opened it? Would be nice.

    For me i changed the code block around line 275 like that:

    Code:
        -- prevents door spasming
        if #npcIds > 0 and not isDoorHorizontal() then
          return
        elseif storage.state then
          --disable for NPC's, close when opened by player
          closeDoor()
          if self.playerOpened == true then
            closeDoor()
            self.playerOpened = false
          end
        end
      end
    
    works flawlessly and Tenants finally close doors when they go through ;)
     
  13. lornlynx

    lornlynx Cosmic Narwhal

    Guess documenting the code was worth sth after all ;)
     
  14. xRuneXero

    xRuneXero Void-Bound Voyager

    1.0 is hitting the 22nd July, and it's already on Unstable. I hope this mod gets updated, it's a must-have :)
     
  15. bk3k

    bk3k Oxygen Tank

    Does this not work on unstable/nightly?
     
  16. xRuneXero

    xRuneXero Void-Bound Voyager

    Not for me, it makes it so doors are completely ignored and you go through them.
     
  17. bk3k

    bk3k Oxygen Tank

    Fortunately this mod has open permissions. So in case it doesn't get updated, I've got you covered. I've already heavily modified it for use of my custom doors(part of not yet released update). Besides some additional functionality I wanted(and will probably later expand upon), I believe it should be more efficient because most things happen in init(only once) where as they did happen in update(over and over again). Granted I'm not an expert on LUA efficiency, but that seems like a better approach.

    I'll go ahead and make it nightly/1.0 ready too and post the code here so you can use it. Granted lornlynx was last active last month, so I don't think its fair to assume the mod abandoned. Perhaps he'll incorporate my modifications, and perhaps he won't. But I don't intend to publish essentially the same mod as his - especially for which his is the original basis. I suppose that would be called a fork, but an unnecessary one.


    Code:
    function init()
      self.closeCooldown = 0
      --self.inputNodeCount = config.getParameter("inputNodes", nil)
      --I was going to do something with this, but not currently doing so as I went another way.
      storage.wireControlled = false
      storage.wireOpened = false
      --if self.inputNodeCount == nil then self.inputNode = 0 end
      setDirection(storage.doorDirection or object.direction())
      storage.defaultLocked = config.getParameter("defaultlocked", false)
      if config.getParameter("openingAnimation", false) then
      storage.openingAnimation = "opening"  --my doors have a seperate opening animation cycle seperate from the "open" state
      else storage.openingAnimation = "open"
       end
       
      if storage.locked == nil then     --added check for additional parameter which differs in intent
                       --defaultLocked differs from locked in that the door will return to a locked state absent wire input
       if not (config.getParameter("defaultLocked") == nil) then
         storage.locked = config.getParameter("locked")
      else storage.locked = config.getParameter("locked", false)
       end
      end
    
      if storage.isOpen == nil then
      closeDoor()
      else
      animator.setAnimationState("doorState", storage.isOpen and "open" or "closing")
      end
      if storage.defaultLocked then
      object.setInteractive(false)
      else
      updateInteractive()
      end
      setupMaterialSpaces()
      updateCollisionAndWires()
      updateLight()
     
      --automatic door specific stuff below
      storage.boundVar = config.getParameter("detectBoundMode", "CollisionArea")
      noAutomatic()  --will set storage.noAuto if door should not be automatic
      if storage.noAuto == true then return end  --no need for the rest, save a few CPU cycles
      storage.isHorizontal = isDoorHorizontal()  
      self.playerOpened = false
      self.playerClosed = false
      setQuery()
     
      --this was in the nightly door.lua but I don't know in what cases it is used
      message.setHandler("openDoor", function() openDoor() end)
      message.setHandler("lockDoor", function() lockDoor() end)
     
    end
    
    
    function setQuery()  --new function of code moved from update() and called by init() so only done once
    
      local objectName = config.getParameter("objectName", "door")
      storage.doorPosition = object.position()
      --local storage.queryRadius = 3
      storage.queryRadius = 5 --I don't like the door opening directly in my face
     
      if objectName == "lunarbasedoor" then
      --storage.queryRadius = 4.5
      storage.queryRadius = 5.5
      storage.doorPosition[2] = storage.doorPosition[2] + 2.5
      storage.doorPosition[1] = storage.doorPosition[1]
      -- world.debugLine({storage.doorPosition[1], storage.doorPosition[2]}, {storage.doorPosition[1] + storage.queryRadius, storage.doorPosition[2] + storage.queryRadius}, "blue")
      -- changes door position slightly to make scanning position at height of half
      -- the door (or width if horizontal door)
      elseif storage.isHorizontal then
      storage.doorPosition[1] = storage.doorPosition[1] + 2.5
      storage.doorPosition[2] = storage.doorPosition[2]
      -- world.debugLine(getQueryArea(storage.doorPosition, storage.queryRadius)[1], getQueryArea(storage.doorPosition, storage.queryRadius)[2], "blue")
      else
      storage.doorPosition[2] = storage.doorPosition[2] + 2.5
      storage.doorPosition[1] = storage.doorPosition[1] + 0.5
      -- world.debugLine({storage.doorPosition[1], storage.doorPosition[2]}, {storage.doorPosition[1] + storage.queryRadius, storage.doorPosition[2] + storage.queryRadius}, "blue")
      end
     
      -- sb.loginfo("dt")
      -- world.debugPoint(storage.doorPosition, "green")
      -- world.debugPoint(object.position(), "red")
      -- world.debugText("pos: %s, %s", storage.doorPosition[1], storage.doorPosition[2], {object.position()[1], object.position()[2] + 1}, "black")
     
      -- checks for players around and saves
      -- them in array.
      self.queryArea = getQueryArea(object.position(), storage.queryRadius)
    end
    
    
    function onNodeConnectionChange(args)  --modified
      updateInteractive()
      if object.isInputNodeConnected(0) then
      storage.wireControlled = true
      --onInputNodeChange({ level = object.getInputNodeLevel(0) })
      else storage.wireControlled = false
      end
    end
    
    
    function onInputNodeChange(args)  --modified
    -- @tab args Map of:
    --  {
    --  node = <(int) index of the node that is changing>
    --  level = <new level of the node>
    --  }
      if storage.defaultLocked == true then
       secureControl(args) --pass the responsibility to a function more fitting rather than bloat this one
       return  --end function
       end
      if args.level then
      storage.wireOpened = true
      openDoor(storage.doorDirection)
       
      else
      storage.wireOpened = false
      closeDoor()
      end
    end
    
    
    function secureControl (args)
      if object.getinputNodeLevel(0) and object.getinputNodeLevel(1) then --this requires multiple inputs to open, else lock
      storage.locked = false
       openDoor(storage.doorDirection)
      else
       lockDoor()
       end
    --I may expand this later to demand wire inputs that conform to a pattern.  Such as having 8 input nodes,
    --and only opening when only the correct nodes are activated at once, perhaps in sequence!
    end
    
    
    function onInteraction(args)
      if storage.locked == true then
      animator.playSound("locked")
      else
      if not storage.isOpen then
      openDoor(args.source[1])
      if storage.isHorizontal then
      -- give the door a cooldown before closing again
      self.closeCooldown = 2  --increased cooldown
      else
      self.closeCooldown = 0
      end
      else
      closeDoor()
      self.playerClosed = true
      end
      end
    end
    
    
    function updateLight()
      if not storage.isOpen then
      object.setLightColor(config.getParameter("closedLight", {0,0,0,0}))
      else
      object.setLightColor(config.getParameter("openLight", {0,0,0,0}))
      end
    end
    
    
    function updateInteractive()
      if storage.defaultLocked then return end
      object.setInteractive(config.getParameter("interactive", true) and not object.isInputNodeConnected(0))
    end
    
    
    function updateCollisionAndWires()
      --object.setColliding(not storage.isOpen)
      --object.setAlloutputNodes(storage.isOpen)
      --new version from nightly
      --setupMaterialSpaces()  
      --not going to run that from here
      object.setMaterialSpaces(storage.isOpen and storage.openMaterialSpaces or storage.closedMaterialSpaces)
      object.setAllOutputNodes(storage.isOpen)
    end
    
    function setupMaterialSpaces()
      storage.closedMaterialSpaces = config.getParameter("closedMaterialSpaces")
      if not storage.closedMaterialSpaces then
      storage.closedMaterialSpaces = {}
      local metamaterial = "metamaterial:door"
      if object.isInputNodeConnected(0) then
      metamaterial = "metamaterial:lockedDoor"
      end
      for i, space in ipairs(object.spaces()) do
      table.insert(storage.closedMaterialSpaces, {space, metamaterial})
      end
      end
      storage.openMaterialSpaces = config.getParameter("openMaterialSpaces", {})
    end
    
    function setDirection(direction)
      storage.doorDirection = direction
      animator.setGlobalTag("doorDirection", direction < 0 and "Left" or "Right")
    end
    
    
    function hasCapability(capability)  --doesn't appear to be used ...unless called by scriptedobject?
      if object.isInputNodeConnected(0) or storage.locked then
      return false
      elseif capability == 'door' then
      return true
      elseif capability == 'closedDoor' then
      return not storage.isOpen
      elseif capability == 'openDoor' then
      return storage.isOpen
      else
      return false
      end
    end
    
    
    function doorOccupiesSpace(position)  --doesn't appear to be used
      local relative = {position[1] - object.position()[1], position[2] - object.position()[2]}
      for _, space in ipairs(object.spaces()) do
      if math.floor(relative[1]) == space[1] and math.floor(relative[2]) == space[2] then
      return true
      end
      end
      return false
    end
    
    
    function lockDoor() --added because oddly because there is no locking function even while an unlocking one
      if not storage.locked == true then
       if storage.isOpen == true then
        animator.setAnimationState("doorState", "locking")
        storage.isOpen = false
        animator.playSound("close")
        updateCollisionAndWires()
        updateLight()
        else --no need to close door, just change animation state
         animator.setAnimationState("doorState", "closing")
         end
       storage.locked = true
       end
    end
    
    
    function unlockDoor()
      if storage.locked then
      storage.locked = false
      updateInteractive()
      animator.setAnimationState("doorState", "closed")
      end
    end
    
    
    function closeDoor()
      -- only close door when cooldown is zero
      if (storage.isOpen ~= false) and (self.closeCooldown <= 0) then
      storage.isOpen = false
      animator.playSound("close")
      animator.setAnimationState("doorState", "closing")
      updateCollisionAndWires()
      updateLight()
      end
      -- world.debugText("Close!", object.position(), "red")
    end
    
    
    function openDoor(direction)
      if storage.locked == true then --probably this shouldn't get called if door is locked
       animator.playSound("locked")  --but just in case
       return end
      if not storage.isOpen then
      storage.isOpen = true
      --storage.locked = false -- make sure we don't get out of sync when wired
      setDirection((direction == nil or direction * object.direction() < 0) and -1 or 1)
      animator.playSound("open")
      animator.setAnimationState("doorState", storage.openingAnimation)
      updateCollisionAndWires()
      updateLight()
       
      self.playerClosed = false
      end
      -- world.debugText("Open!", object.position(), "red")
    end
    
    
    -- Checks if doors is horizontal, depending on different use of anchors.
    --
    -- @return BOOL value for confirmation
    --
    function isDoorHorizontal()
    --expanded upon to have a backup should there be no anchors, instead testing for itself in either horizontal direction.  
    --Alternatively I could have tested the locations of all the objects spaces, but this seems easier.
    --This will also now only run from init() to set a variable because it just seems silly to run this function more than once as the answer will never change
      local theAnswer = config.getParameter("horizontal_door")
      if (theAnswer ~= nil) and (type(theAnswer) == "boolean") then
      return theAnswer
       end
      --if this is set, no need to further check
       
      local anchors = config.getParameter("anchors", {"top", "bottom"})
      for _,anchor in ipairs(anchors) do
      if anchor == "left" or anchor == "right" then
      return true
      end
      end
       
      theAnswer = false --lets assume false for now
      local toTheRight = {}
      local toTheLeft = {}
      toTheRight[1] = object.position()[1] + 3
      toTheRight[2] = object.position()[2]
      toTheLeft[1] = object.position()[1] - 3
      toTheLeft[2] = object.position()[2]
      --I suppose that would fail if verticle door was 4 thick,
      --but I've never seen thicker than 3 (and that's a bit excessive already)
     
      local rightTable = world.objectQuery(toTheRight, 0)
      local leftTable = world.objectQuery(toTheLeft, 0)
     
      for k,v in pairs(rightTable) do
         table.insert( leftTable, v )
       end  --merged leftTable into RightTable
      local rtSize = #rightTable
     
      local i = 1
      while not (i > rtSize) do
       if rightTable[i] == entity.id() then
        theAnswer = true
        break
        end
       i = i + 1
       end --search all entities returned in this table, and check if the id matches this object.
         
      return theAnswer
    end
    
    
    -- Modifies query values for horizontal doors
    --
    -- If the door is horizontal, the position and radius are used as min & max
    -- positions which causes Query to use a rectangular scanning area.
    --
    -- @tab position Default door position
    -- @tab radius Wanted radius/sidelenght for scanning area
    --
    -- @return minPos Position for left bottom corner of scanning rectangle
    -- @return minPos Position for right top corner of scanning rectangle
    -- @return position Does get return unmodified if vertical door
    -- @return radius Does get return unmodified if vertical door
    --
    function getQueryArea(position, radius)
      if storage.isHorizontal then
      local minPos = {position[1] - radius, position[2] - radius}
      local maxPos = {position[1] + radius, position[2]} -- Don't query above, want players to walk on the door
      return {minPos, maxPos}
      else
      return {position, radius}
      end
    end
    
    
    function noAutomatic() --added
      --sets boolean variable to determine if automatic functionality will be disabled for this door
      local theAnswer = config.getParameter("noAutomaticDoors", false)  --default assumption is automatic doors
     
      if storage.defaultLocked == true then
      theAnswer = true
      lockDoor()
       end
     
      if objectName == "castlehiddentrapdoor" or objectName == "castlehiddendoor" or objectName == "templehiddentrapdoor" or
      objectName == "pilch_horizdoor" or objectName == "dirttrapdoor" then
      theAnswer = true
       end
    
      storage.noAuto = theAnswer
     
      if (config.getParameter("defaultOpen", false) == true) then openDoor() end
     
    end
    
    
    
    -- Main function, is running constantly with delta t time interval, functions esentially like an infinite while loop
    --
    function update(dt)
      if storage.noAuto == true then return end    --everything remaining is used to make doors automatic, and therefore should
                           --be skipped when automatic functionality is undesirable.
      if (storage.wireControlled == true) or (storage.wireOpened == true) or (storage.locked == true)then return end  
                 --no automatic when wired to input 1
                 --don't need automatic functionality when door opened from ANY wire input or locked
      local objectIdsOpen = world.playerQuery(self.queryArea[1], self.queryArea[2],
      {boundMode = storage.boundVar})
     
      if #objectIdsOpen > 0 and not self.playerClosed then
      -- query for player at door proximity
      --local playerPosition = world.objectPosition(objectIdsOpen[1])
       local playerPosition = world.entityPosition(objectIdsOpen[1])
      -- sb.loginfo("Player detected!")
       
      -- open door in direction depending on position of the player
      if not storage.isOpen and not storage.isHorizontal then
      openDoor(playerPosition[1] - storage.doorPosition[1])
      --self.playerOpened = true
      -- sb.loginfo("direction: %d", playerPosition[1] - object.position()[1])
      elseif not storage.isOpen then
      openDoor(playerPosition[2] - storage.doorPosition[2])
      -- sb.loginfo("direction: %d", playerPosition[1] - object.position()[1])
      end
       
      if not storage.isHorizontal then
      self.playerOpened = true
      end
      end
     
    
      if #objectIdsOpen == 0 then
      -- resetting toggle once player gets out of range
      if self.playerClosed then
      self.playerClosed = false
      end
       
      -- check for NPCs and players in bigger radius before closing door
      --local objectIdsClose = world.playerQuery(getQueryArea(storage.doorPosition, 2))
      local npcIds = world.npcQuery(storage.doorPosition, storage.queryRadius, {boundMode = storage.boundVar})
       
      -- prevents door spasming
      if #npcIds > 0 and not storage.isHorizontal then
      return
      elseif storage.isOpen then
      --disable for NPC's, close when opened by player
      if storage.isHorizontal then
      closeDoor()
      elseif self.playerOpened == true then
      closeDoor()
      self.playerOpened = false
      end
      end
      end
     
      -- lowers cooldown with each cycle
      if self.closeCooldown > 0 then
      self.closeCooldown = self.closeCooldown - dt
      end
    end
    


    What you would do is go into the mod
    objects/wired/door
    open door.lua in whatever text editor you use(I recommend Notepad++ but others love Sublime Text).
    Delete everything and copy/paste the code I just gave you.
    Save.
    Your doors should be automatic in nightly and should continue to work in 1.0 when it launches.
     
    Last edited: Jul 12, 2016
    lornlynx and xRuneXero like this.
  18. xRuneXero

    xRuneXero Void-Bound Voyager

    Thank you very much, works perfectly :)
     
    bk3k likes this.
  19. bk3k

    bk3k Oxygen Tank

    I've slightly improved upon what I posted before.

    Code:
    
    function init()
      self.closeCooldown = 0
      --self.inputNodeCount = config.getParameter("inputNodes", nil)
      --I was going to do something with this, but not currently doing so as I went another way.
      storage.wireControlled = false
      storage.wireOpened = false
      --if self.inputNodeCount == nil then self.inputNode = 0 end
      setDirection(storage.doorDirection or object.direction())
      storage.defaultLocked = config.getParameter("defaultlocked", false)
      if config.getParameter("openingAnimation", false) then
      storage.openingAnimation = "opening"  --my doors have a seperate opening animation cycle seperate from the "open" state
      else storage.openingAnimation = "open"
       end
      
      if storage.locked == nil then     --added check for additional parameter which differs in intent
                       --defaultLocked differs from locked in that the door will return to a locked state absent wire input
       if not (config.getParameter("defaultLocked") == nil) then
         storage.locked = config.getParameter("locked")
      else storage.locked = config.getParameter("locked", false)
       end
      end
    
      if storage.isOpen == nil then
      closeDoor()
      else
      animator.setAnimationState("doorState", storage.isOpen and "open" or "closing")
      end
      if storage.defaultLocked then
      object.setInteractive(false)
      else
      updateInteractive()
      end
      setupMaterialSpaces()
      updateCollisionAndWires()
      updateLight()
    
      --automatic door specific stuff below
      storage.boundVar = config.getParameter("detectBoundMode", "CollisionArea")
      noAutomatic()  --will set storage.noAuto if door should not be automatic
      if storage.noAuto == true then return end  --no need for the rest, save a few CPU cycles
      storage.isHorizontal = isDoorHorizontal() 
      self.playerOpened = false
      self.playerClosed = false
      setQuery()
    
      --this was in the nightly door.lua but I don't know in what cases it is used
      message.setHandler("openDoor", function() openDoor() end)
      message.setHandler("lockDoor", function() lockDoor() end)
    
    end
    
    
    function setQuery()  --new function of code moved from update() and called by init() so only done once
    
      local objectName = config.getParameter("objectName", "door")
      storage.doorPosition = object.position()
      --local storage.queryRadius = 3
      storage.queryRadius = 5 --I don't like the door opening directly in my face
    
      if objectName == "lunarbasedoor" then
      --storage.queryRadius = 4.5
      storage.queryRadius = 5.5
      storage.doorPosition[2] = storage.doorPosition[2] + 2.5
      storage.doorPosition[1] = storage.doorPosition[1]
      -- world.debugLine({storage.doorPosition[1], storage.doorPosition[2]}, {storage.doorPosition[1] + storage.queryRadius, storage.doorPosition[2] + storage.queryRadius}, "blue")
      -- changes door position slightly to make scanning position at height of half
      -- the door (or width if horizontal door)
      elseif storage.isHorizontal then
      storage.doorPosition[1] = storage.doorPosition[1] + 2.5
      storage.doorPosition[2] = storage.doorPosition[2]
      -- world.debugLine(getQueryArea(storage.doorPosition, storage.queryRadius)[1], getQueryArea(storage.doorPosition, storage.queryRadius)[2], "blue")
      else
      storage.doorPosition[2] = storage.doorPosition[2] + 2.5
      storage.doorPosition[1] = storage.doorPosition[1] + 0.5
      -- world.debugLine({storage.doorPosition[1], storage.doorPosition[2]}, {storage.doorPosition[1] + storage.queryRadius, storage.doorPosition[2] + storage.queryRadius}, "blue")
      end
    
      -- sb.loginfo("dt")
      -- world.debugPoint(storage.doorPosition, "green")
      -- world.debugPoint(object.position(), "red")
      -- world.debugText("pos: %s, %s", storage.doorPosition[1], storage.doorPosition[2], {object.position()[1], object.position()[2] + 1}, "black")
    
      -- checks for players around and saves
      -- them in array.
      self.queryArea = getQueryArea(object.position(), storage.queryRadius)
    end
    
    
    function onNodeConnectionChange(args)  --modified
      updateInteractive()
      if object.isInputNodeConnected(0) then
      storage.wireControlled = true
      --onInputNodeChange({ level = object.getInputNodeLevel(0) })
      else storage.wireControlled = false
      end
    end
    
    
    function onInputNodeChange(args)  --modified
    -- @tab args Map of:
    --  {
    --  node = <(int) index of the node that is changing>
    --  level = <new level of the node>
    --  }
      if storage.defaultLocked == true then
       secureControl(args) --pass the responsibility to a function more fitting rather than bloat this one
       return  --end function
       end
      if args.level then
      storage.wireOpened = true
      openDoor(storage.doorDirection)
      
      else
      storage.wireOpened = false
      closeDoor()
      end
    end
    
    
    function secureControl (args)
      if object.getinputNodeLevel(0) and object.getinputNodeLevel(1) then --this requires multiple inputs to open, else lock
      storage.locked = false
       openDoor(storage.doorDirection)
      else
       lockDoor()
       end
    --I may expand this later to demand wire inputs that conform to a pattern.  Such as having 8 input nodes,
    --and only opening when only the correct nodes are activated at once, perhaps in sequence!
    end
    
    
    function onInteraction(args)
      if storage.locked == true then
      animator.playSound("locked")
      else
      if not storage.isOpen then
      openDoor(args.source[1])
      if storage.isHorizontal then
      -- give the door a cooldown before closing again
      self.closeCooldown = 2  --increased cooldown
      else
      self.closeCooldown = 0
      end
      else
      closeDoor()
      self.playerClosed = true
      end
      end
    end
    
    
    function updateLight()
      if not storage.isOpen then
      object.setLightColor(config.getParameter("closedLight", {0,0,0,0}))
      else
      object.setLightColor(config.getParameter("openLight", {0,0,0,0}))
      end
    end
    
    
    function updateInteractive()
      if storage.defaultLocked then return end
      object.setInteractive(config.getParameter("interactive", true) and not object.isInputNodeConnected(0))
    end
    
    
    function updateCollisionAndWires()
      --object.setColliding(not storage.isOpen)
      --object.setAlloutputNodes(storage.isOpen)
      --new version from nightly
      --setupMaterialSpaces() 
      --not going to run that from here
      object.setMaterialSpaces(storage.isOpen and storage.openMaterialSpaces or storage.closedMaterialSpaces)
      object.setAllOutputNodes(storage.isOpen)
    end
    
    
    function setupMaterialSpaces()
      storage.closedMaterialSpaces = config.getParameter("closedMaterialSpaces")
      if not storage.closedMaterialSpaces then
      storage.closedMaterialSpaces = {}
      local metamaterial = "metamaterial:door"
      if object.isInputNodeConnected(0) then
      metamaterial = "metamaterial:lockedDoor"
      end
      for i, space in ipairs(object.spaces()) do
      table.insert(storage.closedMaterialSpaces, {space, metamaterial})
      end
      end
      storage.openMaterialSpaces = config.getParameter("openMaterialSpaces", {})
    end
    
    
    function setDirection(direction)
      storage.doorDirection = direction
      animator.setGlobalTag("doorDirection", direction < 0 and "Left" or "Right")
    end
    
    
    function hasCapability(capability)  --doesn't appear to be used ...unless called by scriptedobject?
      if object.isInputNodeConnected(0) or storage.locked then
      return false
      elseif capability == 'door' then
      return true
      elseif capability == 'closedDoor' then
      return not storage.isOpen
      elseif capability == 'openDoor' then
      return storage.isOpen
      else
      return false
      end
    end
    
    
    function doorOccupiesSpace(position)  --doesn't appear to be used
      local relative = {position[1] - object.position()[1], position[2] - object.position()[2]}
      for _, space in ipairs(object.spaces()) do
      if math.floor(relative[1]) == space[1] and math.floor(relative[2]) == space[2] then
      return true
      end
      end
      return false
    end
    
    
    function lockDoor() --added because oddly because there is no locking function even while an unlocking one
      if not storage.locked == true then
       if storage.isOpen == true then
        animator.setAnimationState("doorState", "locking")
        storage.isOpen = false
        animator.playSound("close")
        updateCollisionAndWires()
        updateLight()
        else --no need to close door, just change animation state
         animator.setAnimationState("doorState", "closing")
         end
       storage.locked = true
       end
    end
    
    
    function unlockDoor()
      if storage.locked then
      storage.locked = false
      updateInteractive()
      animator.setAnimationState("doorState", "closed")
      end
    end
    
    
    function closeDoor()
      -- only close door when cooldown is zero
      if (storage.isOpen ~= false) and (self.closeCooldown <= 0) then
      storage.isOpen = false
      animator.playSound("close")
      animator.setAnimationState("doorState", "closing")
      updateCollisionAndWires()
      updateLight()
      end
      -- world.debugText("Close!", object.position(), "red")
    end
    
    
    function openDoor(direction)
      if storage.locked == true then --probably this shouldn't get called if door is locked
       animator.playSound("locked")  --but just in case
       return end
      if not storage.isOpen then
      storage.isOpen = true
      --storage.locked = false -- make sure we don't get out of sync when wired
      setDirection((direction == nil or direction * object.direction() < 0) and -1 or 1)
      animator.playSound("open")
      animator.setAnimationState("doorState", storage.openingAnimation)
      updateCollisionAndWires()
      updateLight()
      
      self.playerClosed = false
      end
      -- world.debugText("Open!", object.position(), "red")
    end
    
    
    -- Checks if doors is horizontal, depending on different use of anchors.
    --
    -- @return BOOL value for confirmation
    --
    function isDoorHorizontal()
    --expanded upon to have a backup should there be no anchors, instead testing for itself in either horizontal direction. 
    --Alternatively I could have tested the locations of all the objects spaces, but this seems easier.
    --This will also now only run from init() to set a variable because it just seems silly to run this function more than once as the answer will never change
      local theAnswer = config.getParameter("horizontal_door")
      if (theAnswer ~= nil) and (type(theAnswer) == "boolean") then
      return theAnswer
       end
      --if this is set, no need to further check
      
      local anchors = config.getParameter("anchors", {"top", "bottom"})
      for _,anchor in ipairs(anchors) do
      if anchor == "left" or anchor == "right" then
      return true
      end
      end
      
      theAnswer = false --lets assume false for now
      local toTheRight = {}
      local toTheLeft = {}
      toTheRight[1] = object.position()[1] + 3
      toTheRight[2] = object.position()[2]
      toTheLeft[1] = object.position()[1] - 3
      toTheLeft[2] = object.position()[2]
      --I suppose that would fail if verticle door was 4 thick,
      --but I've never seen thicker than 3 (and that's a bit excessive already)
    
      local rightTable = world.objectQuery(toTheRight, 0)
      local leftTable = world.objectQuery(toTheLeft, 0)
    
      for k,v in pairs(rightTable) do
         table.insert( leftTable, v )
       end  --merged leftTable into RightTable
      local rtSize = #rightTable
    
      local i = 1
      while not (i > rtSize) do
       if rightTable[i] == entity.id() then
        theAnswer = true
        break
        end
       i = i + 1
       end --search all entities returned in this table, and check if the id matches this object.
        
      return theAnswer
    end
    
    
    -- Modifies query values for horizontal doors
    --
    -- If the door is horizontal, the position and radius are used as min & max
    -- positions which causes Query to use a rectangular scanning area.
    --
    -- @tab position Default door position
    -- @tab radius Wanted radius/sidelenght for scanning area
    --
    -- @return minPos Position for left bottom corner of scanning rectangle
    -- @return minPos Position for right top corner of scanning rectangle
    -- @return position Does get return unmodified if vertical door
    -- @return radius Does get return unmodified if vertical door
    --
    function getQueryArea(position, radius)
      if storage.isHorizontal then
      local minPos = {position[1] - radius, position[2] - radius}
      local maxPos = {position[1] + radius, position[2]} -- Don't query above, want players to walk on the door
      return {minPos, maxPos}
      else
      return {position, radius}
      end
    end
    
    
    function noAutomatic() --added
      --sets boolean variable to determine if automatic functionality will be disabled for this door
      local theAnswer = config.getParameter("noAutomaticDoors", false)  --default assumption is automatic doors
    
      if storage.defaultLocked == true then
      theAnswer = true
      lockDoor()
       end
    
      if objectName == "castlehiddentrapdoor" or objectName == "castlehiddendoor" or objectName == "templehiddentrapdoor" or
      objectName == "pilch_horizdoor" or objectName == "dirttrapdoor" then
      theAnswer = true
       end
    
      storage.noAuto = theAnswer
    
      if (config.getParameter("defaultOpen", false) == true) then openDoor() end
    
    end
    
    
    
    -- Main function, is running constantly with delta t time interval, functions esentially like an infinite while loop
    --
    function update(dt)
      if storage.noAuto == true then return end    --everything remaining is used to make doors automatic, and therefore should
                           --be skipped when automatic functionality is undesirable.
      if (storage.wireControlled == true) or (storage.wireOpened == true) or (storage.locked == true)then return end 
                 --no automatic when wired to input 1
                 --don't need automatic functionality when door opened from ANY wire input or locked
      --local objectIdsOpen = world.playerQuery(self.queryArea[1], self.queryArea[2],
      --{boundMode = storage.boundVar})
                  
      local objectIdsOpen = world.entityQuery(self.queryArea[1], self.queryArea[2], {
      withoutEntityId = entity.id(),
      includedTypes = { "vehicle", "player" },
      boundMode = storage.boundVar})
        
      --for k,v in pairs(vehicleIds) do
         --table.insert(objectIdsOpen , v )
       --end  --merged vehicleIds into objectIdsOpen
          
    
      if #objectIdsOpen > 0 and not self.playerClosed then
      -- query for player at door proximity
      --local playerPosition = world.objectPosition(objectIdsOpen[1])
       local playerPosition = world.entityPosition(objectIdsOpen[1])
      -- sb.loginfo("Player detected!")
      
      -- open door in direction depending on position of the player
      if not storage.isOpen and not storage.isHorizontal then
      openDoor(playerPosition[1] - storage.doorPosition[1])
      --self.playerOpened = true
      -- sb.loginfo("direction: %d", playerPosition[1] - object.position()[1])
      elseif not storage.isOpen then
      openDoor(playerPosition[2] - storage.doorPosition[2])
      -- sb.loginfo("direction: %d", playerPosition[1] - object.position()[1])
      end
      
      if not storage.isHorizontal then
      self.playerOpened = true
      end
      end
    
    
      if #objectIdsOpen == 0 then
      -- resetting toggle once player gets out of range
      if self.playerClosed then
      self.playerClosed = false
      end
      
      -- check for NPCs and players in bigger radius before closing door
      --local objectIdsClose = world.playerQuery(getQueryArea(storage.doorPosition, 2))
      local npcIds = world.npcQuery(storage.doorPosition, storage.queryRadius, {boundMode = storage.boundVar})
      
      -- prevents door spasming
      if #npcIds > 0 and not storage.isHorizontal then
      return
      elseif storage.isOpen then
      --disable for NPC's, close when opened by player
      if storage.isHorizontal then
      closeDoor()
      elseif self.playerOpened == true then
      closeDoor()
      self.playerOpened = false
      end
      end
      end
    
      -- lowers cooldown with each cycle
      if self.closeCooldown > 0 then
      self.closeCooldown = self.closeCooldown - dt
      end
    end
    


    Granted that's mostly useful with larger doors. Of course I have larger doors (for which I wanted a customized automatic doors script in the first place). They'll be in my next shipyard update. No reason you can't enjoy the functionality now even without.
     
    lornlynx likes this.
  20. lornlynx

    lornlynx Cosmic Narwhal

    Oh, wow, this is fantastic!
    I haven't planned doing any more modding until 1.0 officially is released, so I purposefully neglected the mod meanwhile. But it is great seeing that you took matters in your own hands and fixed the outdated stuff.
    Anyways, if you agree I can use your version for the update (with credit obv). Just know that I wish the code to stay open source, but should you not agree you can gladly release your own fork, I will update the mod myself in that case.
     
    bk3k likes this.

Share This Page