Modding Help UNSTABLE/NIGHTLY: Pet AI issue

Discussion in 'Starbound Modding' started by Campaigner, Feb 15, 2015.

  1. Campaigner

    Campaigner Giant Laser Beams

    My mod currently provides spawned pets that use the Cat's behaviors and whatnot. They're literal copy-pastes with different sprites and filenames, which worked on Stable. On Unstable/Nightly, there's an updated AI for the cat, so I went to test out what needed changing. Again, I made a complete copy/paste of the cat, and changed the names and directories. When I load the game and spawn the critter, it dies instantly and cannot be captured. The log file only has one error, and points to groundpet.lua. Here's the lua:

    Code:
    function init()
      self.pathing = {}
      self.pathing.stuckTimer = 0
      self.pathing.maxStuckTime = 2
    
      self.jumpCooldown = 0
      self.jumpMaxCooldown = 1
    
      self.movementParameters = mcontroller.baseParameters()
      self.jumpHoldTime = self.movementParameters.airJumpProfile.jumpHoldTime
      self.jumpSpeed = self.movementParameters.airJumpProfile.jumpSpeed
      self.runSpeed = self.movementParameters.runSpeed
    
      self.stuckPosition = mcontroller.position()
      self.stuckCount = 0
    
      self.scriptDelta = 5
    
      storage.petResources = storage.petResources or entity.configParameter("petResources")
      self.petResourceDeltas = entity.configParameter("petResourceDeltas")
      setPetResources(storage.petResources)
    
      self.state = stateMachine.create({
        "idleState",
        "wanderState"
      })
    
      local actionStates = {}
      local actions = stateMachine.scanScripts(entity.configParameter("scripts"), "(%a+Action)%.lua")
      for _, action in pairs(actions) do
        table.insert(actionStates, 1, action)
      end
      self.actionState = stateMachine.create(actionStates)
    
      self.followTarget = 0
    
      self.behaviorName = entity.configParameter("behavior")
      self.behavior = _ENV[self.behaviorName]
      storage.behaviorParams = storage.behaviorParams or entity.configParameter("behaviorParams")
    
      self.querySurroundingsCooldown = entity.configParameter("querySurroundingsCooldown", 3)
      self.querySurroundingsTimer = 1
      self.querySurroundingsRange = entity.configParameter("querySurroundingsRange", 50)
    
      self.debug = true
    
      if capturepod and capturepod.onInit then
        capturepod.onInit()
      end
      if self.behavior and self.behavior.init then
        self.behavior.init()
      end
    end
    
    
    function update(dt)
      if self.actionState.stateDesc() ~= "" or self.actionState.pickState() then
        self.actionState.update(dt)
      end
    
      --Some action states are triggered by resources
      if self.actionState.stateDesc() == "" and not self.state.update(dt) then
        self.state.pickState()
      end
    
      if self.querySurroundingsTimer <= 0 then
        querySurroundings()
        self.querySurroundingsTimer = self.querySurroundingsTimer + self.querySurroundingsCooldown
      end
    
      if self.debug then
        script.setUpdateDelta(1)
    
        if self.actionState.stateDesc() ~= "" then
          world.debugText(self.actionState.stateDesc(), mcontroller.position(), "blue")
        else
          world.debugText(self.state.stateDesc(), mcontroller.position(), "blue")
        end
        drawDebugResources()
      end
    
      tickResources(dt)
      decrementTimers(dt)
    
      if not self.moved then resetPathing() end
    end
    
    function damage(args)
      if capturepod ~= nil and capturepod.onDamage(args) then
        return
      end
    end
    
    function die()
      if capturepod ~= nil then
        capturepod.onDie()
      end
    end
    
    function querySurroundings()
      local nearEntities = world.entityQuery(mcontroller.position(), self.querySurroundingsRange, {includedTypes={"player", "itemDrop"}})
    
      --Queue up reactions
      for _,entityId in ipairs(nearEntities) do
        self.behavior.reactTo(entityId)
      end
    
      --Run actions
      self.behavior.run()
    
      return false
    end
    
    function emote(emoteType)
      entity.burstParticleEmitter("emote"..emoteType)
    end
    
    function itemFoodLiking(entityName)
      if not entityName or root.itemType(entityName) ~= "consumable" then
        return false
      end
    
      local foodLiking = storage.behaviorParams.food[entityName]
      if foodLiking == nil then
        return nil
      end
    
      --How much does it like the food
      if foodLiking == true or foodLiking == false then
        if foodLiking == true then
          foodLiking = math.random(50, 100)
        elseif foodLiking == false then
          foodLiking = math.random(0, 50)
        end
        storage.behaviorParams.food[entityName] = foodLiking
      end
    
      return foodLiking
    end
    
    function petResources()
      local resources = {}
      for resourceName, resourceValue in pairs(storage.petResources) do
        resources[resourceName] = status.resource(resourceName)
      end
      return resources
    end
    
    function setPetResources(resources)
      for resourceName, resourceValue in pairs(resources) do
        status.setResource(resourceName, resourceValue)
      end
    end
    
    function tickResources(dt)
      for resourceName, resourceDelta in pairs(self.petResourceDeltas) do
        status.modifyResource(resourceName, resourceDelta * dt)
      end
    end
    
    function drawDebugResources()
      local resources = storage.petResources
      local position = mcontroller.position()
    
      local y = 2
      for resourceName, resourceValue in pairs(storage.petResources) do
        --Border
        world.debugLine(vec2.add(position, {-2, y+0.125}), vec2.add(position, {-2, y + 0.75}), "black")
        world.debugLine(vec2.add(position, {-2, y + 0.75}), vec2.add(position, {2, y + 0.75}), "black")
        world.debugLine(vec2.add(position, {2, y + 0.75}), vec2.add(position, {2, y+0.125}), "black")
        world.debugLine(vec2.add(position, {2, y+0.125}), vec2.add(position, {-2, y+0.125}), "black")
    
        local width = 3.75 * status.resource(resourceName) / 100
        world.debugLine(vec2.add(position, {-1.875, y + 0.25}), vec2.add(position, {-1.875 + width, y + 0.25}), "green")
        world.debugLine(vec2.add(position, {-1.875, y + 0.375}), vec2.add(position, {-1.875 + width, y + 0.375}), "green")
        world.debugLine(vec2.add(position, {-1.875, y + 0.5}), vec2.add(position, {-1.875 + width, y + 0.5}), "green")
        world.debugLine(vec2.add(position, {-1.875, y + 0.625}), vec2.add(position, {-1.875 + width, y + 0.625}), "green")
    
        world.debugText(resourceName, vec2.add(position, {2.25, y - 0.125}), "blue")
        y = y + 1
      end
    end
    
    function decrementTimers(dt)
      self.querySurroundingsTimer = self.querySurroundingsTimer - dt
      self.jumpCooldown = self.jumpCooldown - dt
    end
    
    function setMovementState(running)
      if not mcontroller.onGround() then
        setJumpState()
      else
        if running then
          entity.setAnimationState("movement", "run")
        else
          entity.setAnimationState("movement", "walk")
        end
      end
    end
    
    function setIdleState()
      if not mcontroller.onGround() then
        setJumpState()
      else
        entity.setAnimationState("movement", "idle")
      end
    end
    
    function setJumpState()
      if mcontroller.yVelocity() > 0 then
        entity.setAnimationState("movement", "jumping")
      else
        entity.setAnimationState("movement", "falling")
      end
    end
    
    function boundingBox(force)
      if self.boundingBox and not force then return self.boundingBox end
    
      local collisionPoly = mcontroller.collisionPoly()
      local bounds = {0, 0, 0, 0}
    
      for _,point in pairs(collisionPoly) do
        if point[1] < bounds[1] then bounds[1] = point[1] end
        if point[2] < bounds[2] then bounds[2] = point[2] end
        if point[1] > bounds[3] then bounds[3] = point[1] end
        if point[2] > bounds[4] then bounds[4] = point[2] end
      end
      self.boundingBox = bounds
    
      return bounds
    end
    
    --------------------------------------------------------------------------------
    -- draw lines to display the specified rect {x1, y1, x2, y2} in the specified color, optionally offset by basePos
    function drawDebugRect(rect, color, basePos)
      if basePos then rect = translate(rect, basePos) end
      world.debugLine({rect[1], rect[2]}, {rect[1], rect[4]}, color)
      world.debugLine({rect[1], rect[2]}, {rect[3], rect[2]}, color)
      world.debugLine({rect[3], rect[4]}, {rect[1], rect[4]}, color)
      world.debugLine({rect[3], rect[4]}, {rect[3], rect[2]}, color)
    end
    
    --------------------------------------------------------------------------------
    -- draw lines and points to show the current path
    function drawDebugPath(goalDelta)
      local position = mcontroller.position()
    
      local step = 0
      local prevStep = position
      while true do
        local nextStep = entity.pathLookahead(step)
        if nextStep then
          world.debugLine(prevStep, vec2.add(position, nextStep), "blue")
          world.debugPoint(vec2.add(position, nextStep), "blue")
          prevStep = vec2.add(position, nextStep)
          step = step + 1
        else
          break
        end
      end
      if goalDelta then
        local goalPosition = vec2.add(goalDelta, position)
        world.debugLine(position, goalPosition, "green")
        world.debugPoint(goalPosition, "green")
      end
    end
    
    
    --------------------------------------------------------------------------------
    --MOVEMENT---------------------------------------------------------------------
    --------------------------------------------------------------------------------
    
    function approachPoint(dt, targetPosition, stopDistance, running)
      local toTarget = world.distance(targetPosition, mcontroller.position())
      local targetDistance = world.magnitude(targetPosition, mcontroller.position())
      local groundPosition = findGroundPosition(targetPosition, -20, 1, util.toDirection(-toTarget[1]))
    
      if groundPosition then
        self.approachPosition = groundPosition
      end
    
      if self.approachPosition and targetDistance > stopDistance then
        if moveTo(self.approachPosition, dt, {run = running}) then
          mcontroller.controlFace(self.pathing.deltaX or toTarget[1])
          setMovementState(running)
        else
          entity.setAnimationState("movement", "idle")
        end
    
        return false
      elseif targetDistance <= stopDistance then
        return true
      end
    end
    
    --------------------------------------------------------------------------------
    function moveX(direction, run)
      mcontroller.controlMove(direction, run)
      self.pathing.deltaX = direction
    end
    
    --------------------------------------------------------------------------------
    function move(direction, options)
      if options == nil then options = {} end
      if options.run == nil then options.run = false end
      direction = util.toDirection(direction)
    
      local position = mcontroller.position()
      local boundsEdge = 0
      local bounds = boundingBox()
      local tilePosition
      if direction > 0 then
        tilePosition = {math.ceil(position[1]), position[2]}
        boundsEdge = bounds[3]
      else
        tilePosition = {math.floor(position[1]), position[2]}
        boundsEdge = bounds[1]
      end
    
      --Stop at walls
      if world.lineCollision({position[1], position[2] + bounds[2] + 1.5}, { position[1] + boundsEdge + direction, position[2] + bounds[2] + 1.5}, true) then
        return false, "wall"
      end
    
      --Check if the position ahead is valid, including slopes
      local yDirs = {0, 1, -1}
      for _,yDir in ipairs(yDirs) do
        if findValidStandingPosition({tilePosition[1] + direction, tilePosition[2] + yDir}, direction) then
          moveX(direction, options.run)
          return true
        end
      end
    
      return false, "ledge"
    end
    
    --------------------------------------------------------------------------------
    -- Valid options:
    --  openDoorCallback: function that will be passed a door entity id and should
    --                    return true if the door can be opened
    --  run: whether npc should run
    function moveTo(targetPosition, dt, options)
      if options == nil then options = {} end
      if options.run == nil then options.run = false end
      self.moved = true
    
      targetPosition = {
        math.floor(targetPosition[1]) + 0.5,
        math.floor(targetPosition[2]) + 0.5
      }
    
      local toTargetPosition = world.distance(targetPosition, mcontroller.position())
      if math.abs(toTargetPosition[1]) < 2 and math.abs(toTargetPosition[2]) < 2 then
        moveX(toTargetPosition[1], options.run)
        return true
      end
    
      --Find new path if target position has changed
      local pathTargetPosition = self.pathing.targetPosition
      if pathTargetPosition == nil or
          targetPosition[1] ~= pathTargetPosition[1] or
          targetPosition[2] ~= pathTargetPosition[2] then
    
        local innerRadius = -1
        local outerRadius = 1
        if entity.findPath(targetPosition, innerRadius, outerRadius) then
          self.pathing.targetPosition = targetPosition
        else
          return false
        end
    
        self.pathing.delta = nil
      end
    
      if self.debug then
        drawDebugPath()
      end
    
      -- Store the path delta in case pathfinding doesn't succeed on the next try
      local pathDelta = entity.followPath()
      if pathDelta == nil then
        self.pathing.targetPosition = nil
      else
        self.pathing.delta = pathDelta
      end
    
      --New path and we can't move on it, try new path
      if self.pathing.delta == nil then
        self.pathing.targetPosition = nil
        return false
      end
    
      local position = mcontroller.position()
      local verticalPathUp = verticalPathLength("up")
      local verticalPathDown = verticalPathLength("down")
    
      --Keep jumping
      if (self.pathing.jumpHoldTimer ~= nil and verticalPathUp > 0) or self.pathing.goalJumpPosition then
        keepJumping(dt, options)
        return true
      end
      self.pathing.jumpHoldTimer = nil
    
      --Keep dropping
      if (self.pathing.downHoldTimer ~= nil and verticalPathDown > 0) or self.pathing.goalJumpPosition then
        keepDropping(dt, options)
        return true
      end
      self.pathing.downHoldTimer = nil
    
      script.setUpdateDelta(self.scriptDelta)
    
      local delta = entity.pathLookahead(0) or self.pathing.delta
    
      local nextPathPosition = vec2.add(position, delta)
      local goalPosition, forwardPosition, backwardPosition = findValidStandingPosition(nextPathPosition, util.toDirection(delta[1]))
    
      --Swimming
      if goalPosition and mcontroller.liquidMovement() and world.liquidAt(goalPosition) then
        local goalDelta = world.distance(goalPosition, position)
        if goalDelta[2] > 0 then
          mcontroller.controlJump()
          mcontroller.controlHoldJump()
        end
        moveX(goalDelta[1], options.run)
        return true
      end
    
      --If there is a ledge, don't use this position
      if goalPosition and not forwardPosition then
        goalPosition = nil
      end
    
      local nextDelta = entity.pathLookahead(1)
      --If very close to a path node, or between two path nodes, use the next
      if goalPosition and nextDelta and (delta[1] * nextDelta[1] < 0 or world.magnitude(delta) < 0.5) then
        goalPosition = nil
      end
    
      --If the next path node isn't valid (can't stand there), search the path for a valid one
      local step = 1
      local maxSteps = 30
      local deltaStep = 0
      local deltaDir = delta[1]
      local goalPathNode = entity.pathLookahead(step)
      while not goalPosition and step < maxSteps do
        goalPathNode = entity.pathLookahead(step)
        if goalPathNode then
          nextPathPosition = vec2.add(position, goalPathNode)
          goalPosition, forwardPosition, backwardPosition = findValidStandingPosition(nextPathPosition, util.toDirection(goalPathNode[1]))
    
          local nextDeltaDir = goalPathNode[1] - delta[1]
          if deltaStep == 0 or (deltaStep == step - 1 and nextDeltaDir * deltaDir >= 0) then
            if nextDeltaDir ~= 0 or deltaStep == 0 then
              deltaDir = nextDeltaDir
            end
            deltaStep = step
            delta = goalPathNode
          end
    
          step = step + 1
        else break end
      end
    
      --No valid position found, stop moving and find new path
      if not goalPosition then
        self.pathing.targetPosition = nil
        return false
      end
    
      if checkPathStuck(dt, goalPathNode) then
        return false
      else
        self.pathing.stuckTimer = self.pathing.stuckTimer + dt
      end
    
      if self.debug then
        world.debugLine(position, goalPosition, "green")
        world.debugPoint(goalPosition, "green")
        world.debugLine(position, vec2.add(position, delta), "yellow")
        world.debugPoint(vec2.add(position, delta), "yellow")
      end
    
      --Move on path
      local goalDelta = world.distance(goalPosition, position)
      local verticalMovementRatio = math.abs(goalDelta[2] / goalDelta[1])
      if goalDelta[1] == 0 then verticalMovementRatio = 10 end
      local maxVerticalRatio = 1.25 --Enough to not drop down or jump up stairs
    
      --Keep from dropping or jumping in stairs if we don't need to
      if verticalMovementRatio > maxVerticalRatio and forwardPosition then
        goalDelta = world.distance(forwardPosition, position)
        verticalMovementRatio = math.abs(goalDelta[2] / goalDelta[1])
      end
    
      --Path wants to take us steeply up, jump
      if ((verticalMovementRatio > maxVerticalRatio and goalDelta[2] > 0) or verticalPathUp > 1) then
        if (not closeToCeiling() or mcontroller.xVelocity() == 0) and delta[1] * goalDelta[1] > 0 and delta[1] * mcontroller.xVelocity() >= 0 then
          local jumpHold = math.max(verticalPathUp / self.jumpSpeed, distanceJumpTime(goalDelta))
          timedJump(math.max(jumpHold, 0.02), backwardPosition or forwardPosition, goalDelta[1])
        else
          moveX(delta[1], options.run)
          self.pathing.deltaX = util.toDirection(goalDelta[1])
        end
        return true
      end
    
      --Path is taking us steeply down, drop
      if ((verticalMovementRatio > maxVerticalRatio and goalDelta[2] < 0) or verticalPathDown > 1) and not onSolidGround() then
        local dropHold = timeToFall(math.max(-goalDelta[2], verticalPathDown))
        timedDrop(dropHold)
        return true
      end
    
      --Jump over gaps
      local deltaDir = util.toDirection(goalDelta[1])
      local nextStepPosition = {position[1] + deltaDir, position[2]}
      if deltaDir > 0 then
        nextStepPosition[1] = math.ceil(nextStepPosition[1])
      else
        nextStepPosition[1] = math.floor(nextStepPosition[1])
      end
      if (verticalMovementRatio < 0.5 or goalDelta[2] > 0) and math.abs(goalDelta[1]) > 1.5 then
        --Could be stairs, check diagonally
        if not findValidStandingPosition({nextStepPosition[1], nextStepPosition[2]}, deltaDir) and
          not findValidStandingPosition({nextStepPosition[1], nextStepPosition[2] - 1}, deltaDir) and
          not findValidStandingPosition({nextStepPosition[1], nextStepPosition[2] + 1}, deltaDir) then
          timedJump(distanceJumpTime(goalDelta), forwardPosition or backwardPosition, deltaDir)
          return true
        end
      end
    
      --Edge case - jump over small bumps that aren't slopes
      local bounds = boundingBox()
      local groundTestRegion = {
        position[1] - 0.95, position[2] + bounds[2] - 0.95,
        position[1] - 0.05, position[2] + bounds[2] - 0.05
      }
      local wallTestRegion = {
        position[1] - 0.95, position[2] + bounds[2] + 0.05,
        position[1] - 0.05, position[2] + bounds[2] + 0.95
      }
      if deltaDir > 0 then
        groundTestRegion[1] = groundTestRegion[1] + bounds[3]
        groundTestRegion[3] = groundTestRegion[3] + bounds[3]
        wallTestRegion[1] = groundTestRegion[1] + 1
        wallTestRegion[3] = groundTestRegion[3] + 1
      else
        groundTestRegion[1] = groundTestRegion[1] + bounds[1] + 1
        groundTestRegion[3] = groundTestRegion[3] + bounds[1] + 1
        wallTestRegion[1] = groundTestRegion[1] - 1
        wallTestRegion[3] = groundTestRegion[3] - 1
      end
      drawDebugRect(groundTestRegion, "blue")
      drawDebugRect(wallTestRegion, "blue")
      if verticalMovementRatio > 0.5 and not world.rectCollision(groundTestRegion, false) and world.rectCollision(wallTestRegion, false) then
        timedJump(0.02, {position[1] + deltaDir * 2, position[2] + 1})
        return true
      end
    
      moveX(goalDelta[1], options.run)
    
      return true
    end
    
    --PATHING--
    --------------------------------------------------------------------------------
    -- Get total length of a vertical path, step by step
    function verticalPathLength(yDirection)
      local direction = {up = 1, down = -1}
    
      local step = 1
      local firstDelta = entity.pathLookahead(0)
      local lastDelta = entity.pathLookahead(step)
      local length = 0
      while lastDelta do
        local deltaDiff = lastDelta[2] - firstDelta[2]
        if deltaDiff * direction[yDirection] > 0 and lastDelta[1] == firstDelta[1] then
          length = math.abs(deltaDiff)
          step = step + 1
          lastDelta = entity.pathLookahead(step)
        else
          break
        end
      end
    
      return length
    end
    
    --JUMPING AND DROPPING--
    --------------------------------------------------------------------------------
    --Jump and hold jump for holdTime, also approach goalPosition
    function timedJump(holdTime, goalPosition, facingDirection)
      if self.jumpCooldown > 0 then
        entity.setAnimationState("movement", "idle")
        self.stuckCount = 0
        return true
      end
    
      if not mcontroller.onGround() and not mcontroller.liquidMovement() then return nil end
      if holdTime == nil then holdTime = 0 end
      holdTime = math.min(holdTime, self.jumpHoldTime)
    
      if mcontroller.liquidMovement() then
        holdTime = holdTime + self.jumpHoldTime
      end
    
      mcontroller.controlJump()
      self.pathing.jumpHoldTimer = holdTime
      self.pathing.goalJumpPosition = goalPosition
      self.pathing.jumpFacingDirection = facingDirection
      script.setUpdateDelta(1)
      self.jumpCooldown = self.jumpMaxCooldown
    end
    
    --Keep holding jump for the duration of the timed jump
    function keepJumping(dt, options)
      if self.pathing.jumpHoldTimer ~= nil then
        mcontroller.controlHoldJump()
    
        self.pathing.jumpHoldTimer = self.pathing.jumpHoldTimer - dt
        if self.pathing.jumpHoldTimer <= 0 then
          self.pathing.jumpHoldTimer = nil
        end
      end
    
      if (mcontroller.onGround() or mcontroller.liquidMovement()) and not self.pathing.jumpHoldTimer then
        self.pathing.goalJumpPosition = nil
        self.pathing.jumpFacingDirection = nil
      end
    
      if self.pathing.goalJumpPosition then
        local goalDelta = world.distance(self.pathing.goalJumpPosition, mcontroller.position())
        moveX(goalDelta[1], options.run)
      end
    
      if self.pathing.jumpFacingDirection then
        mcontroller.controlFace(self.pathing.jumpFacingDirection)
        self.pathing.deltaX = self.pathing.jumpFacingDirection
      end
    end
    
    --Drop and hold down for holdTime, also approach goalPosition
    function timedDrop(holdTime, goalPosition)
      if holdTime == nil then holdTime = 0 end
      holdTime = math.min(holdTime, 0.5)
      mcontroller.controlDown()
      self.pathing.downHoldTimer = holdTime
      self.pathing.goalJumpPosition = goalPosition
      script.setUpdateDelta(1)
    end
    
    --Keep holding down
    function keepDropping(dt, options)
      if self.pathing.downHoldTimer ~= nil then
        mcontroller.controlDown()
    
        self.pathing.downHoldTimer = self.pathing.downHoldTimer - dt
        if self.pathing.downHoldTimer <= 0 then
          self.pathing.downHoldTimer = nil
        end
      end
    
      if self.pathing.goalJumpPosition then
        local goalDelta = world.distance(self.pathing.goalJumpPosition, mcontroller.position())
        moveX(goalDelta[1], options.run)
      end
    end
    
    --Returns the time the entity needs to hold jump to reach the specified distance
    --TODO: Make this not bad by using math
    function distanceJumpTime(distance)
      local position = mcontroller.position()
      local gravity = world.gravity(mcontroller.position())
    
      local fallDistanceTime = (math.abs(distance[1]) / 2) / self.runSpeed
      local fallDistance = (gravity / 2) * fallDistanceTime * fallDistanceTime
      fallDistance = fallDistance + distance[2]
    
      local holdTime = fallDistance / self.jumpSpeed
      return holdTime
    end
    
    --The time it would take to fall distance
    function timeToFall(distance)
      local gravity = world.gravity(mcontroller.position())
      return math.sqrt(2 * distance / gravity)
    end
    
    --POSITIONING--
    --------------------------------------------------------------------------------
    --Finds a position on top of a tile (rather than in between tiles) that the entity can stand on
    function findValidStandingPosition(nodePosition, direction)
      if forceDirection == nil then forceDirection = false end
    
      local forwardCollisionResolve = validStandingPosition(nodePosition, direction)
      local backwardCollisionResolve = validStandingPosition(nodePosition, -direction)
    
      if forwardCollisionResolve or backwardCollisionResolve then
        return nodePosition, forwardCollisionResolve, backwardCollisionResolve
      end
    end
    
    --Checks if the entity can stand in this position
    --Or if it can swim there
    function validStandingPosition(position, direction)
      direction = util.toDirection(direction)
      local collisionPoly = mcontroller.collisionPoly()
      local bounds = boundingBox()
    
      local groundRegion = {
        position[1] + math.min(direction * math.max(-bounds[1], 1), 0) + 0.05, position[2] + bounds[2] - 0.1,
        position[1] + math.max(direction * math.max(bounds[3], 1), 0) - 0.05, position[2] + bounds[2]
      }
      local collisionResolve = world.resolvePolyCollision(collisionPoly, {position[1] + direction/2, position[2]}, 0.2)
      if (world.rectCollision(groundRegion, false) or world.liquidAt(position)) and collisionResolve and
        (world.material({groundRegion[1], groundRegion[2]}, "foreground") or world.material({groundRegion[3], groundRegion[4]}, "foreground")) then
        if self.debug then
          drawDebugRect(groundRegion, "blue")
        end
        return collisionResolve
      end
    end
    
    --Find a valid ground position
    function findGroundPosition(position, minHeight, maxHeight, direction)
      local bounds = boundingBox()
      position[2] = math.floor(position[2] + bounds[2] % 1)
    
      if direction == nil then direction = 1 end
      --Check upward
      for y = 0, maxHeight do
        local validPosition, forwardPosition, backwardPosition = findValidStandingPosition({position[1], position[2] + y}, direction)
        if validPosition then
          return validPosition, forwardPosition, backwardPosition
        end
      end
      --Check downward
      for y = -1, minHeight, -1 do
        local validPosition, forwardPosition, backwardPosition = findValidStandingPosition({position[1], position[2] + y}, direction)
        if validPosition then
          return validPosition, forwardPosition, backwardPosition
        end
      end
    
      return false
    end
    
    --Check if entity is right under a solid block ceiling
    function closeToCeiling()
      local position = mcontroller.position()
      local bounds = boundingBox()
    
      local ceilingRegion = {
        position[1] + bounds[1], position[2] + 1,
        position[1] + bounds[3], position[2] + 6
      }
      return world.rectCollision(ceilingRegion, true)
    end
    
    --Check if entity is on solid ground (not platforms)
    function onSolidGround()
      local position = mcontroller.position()
      local bounds = boundingBox()
    
      local groundRegion = {
        position[1] + bounds[1] - 0.05, position[2] + bounds[2] - 0.95,
        position[1] + bounds[3] + 0.05, position[2] + bounds[2] + 0.05
      }
      return world.rectCollision(groundRegion, true)
    end
    
    function checkPathStuck(dt, goalNode)
      if self.pathing.stuckNode == nil then
        self.pathing.stuckNode = vec2.add(mcontroller.position(), goalNode or entity.pathLookahead(0))
      end
    
      --Search path for stuck check node
      local step = 0
      local checkNode = nil
      while step do
        nextPathNode = entity.pathLookahead(step)
        if nextPathNode then
          nextPathNode = vec2.add(mcontroller.position(), nextPathNode)
          if self.pathing.stuckNode[1] == nextPathNode[1] and self.pathing.stuckNode[2] == nextPathNode[2] then
            checkNode = nextPathNode
          end
          step = step + 1
        else
          step = nil
        end
      end
    
      if checkNode then
        --The node is still in the path meaning we haven't reached it yet. Maybe stuck
        if self.pathing.stuckTimer > self.pathing.maxStuckTime then
          return true
        end
        return false
      end
      --The node is not in the path, we've probably passed it
      self.pathing.stuckNode = nil
      self.pathing.stuckTimer = 0
    
      return false
    end
    
    function resetPathing()
      self.pathing.stuckNode = nil
      self.pathing.stuckTimer = 0
    end


    The error says;
    Code:
    [12:45:42.534] Error: Exception while invoking lua function 'init'. (LuaException) Error code 2, [string "/monsters/pets/groundPet.lua"]:150: bad argument #1 to 'pairs' (table expected, got nil)
      000000001896CB60
      000000001896C510 
    The cat works on unstable/nightly, so whatever is wrong doesn't affect the cat. What can I do to fix this?


    EDIT: Forgot to note, JSON validators all say this:

    Code:
    Parse error on line 1:
    functioninit()self.p
    ^
    Expecting '{', '['
    But adding those does nothing.[DOUBLEPOST=1424023512][/DOUBLEPOST]Quick update; I added a space between "function init()" and "self.pathing = {}", so that error ended. Now I get this error:

    Code:
    [13:01:16.940] Error: Exception while invoking lua function 'init'. (LuaException) Error code 2, [string "/monsters/pets/groundPet.lua"]:151: bad argument #1 to 'pairs' (table expected, got nil)
      0000000008E4CB60
      0000000008E4C510 
    Line 151 is as such;

    Code:
      for resourceName, resourceValue in pairs(resources) do
    Again, not sure what I should do to fix this.
     
    Last edited: Feb 15, 2015
  2. Olxinos

    Olxinos Scruffy Nerf-Herder

    Best Answer
    Lua isn't Json, don't use a json checker on lua code, it won't tell you anything worthwile (on any other file, it's a good reflex though).
    The error suggests that storage.petResources hasn't been properly set line 19, which means that "petResources" isn't defined in your pet .monstertype file.
    My groundpet.lua file however is different and doesn't need some "petResources" parameter (maybe because my version isn't totally up to date, i'll update it) so i can't help you much more for now.
     
  3. Campaigner

    Campaigner Giant Laser Beams

    I'm still wet behind the ears on coding, so I didn't know that lua didn't involve JSON. That helped a good deal in helping me find the problem.

    Looking into it, the cat's new monstertype file had some extra stuff that I didn't notice. All the issues led to the monstertype file, and now I have it fixed. Thank you for pointing me in the right direction!
     

Share This Page