FILES ----- player.config sfx.config [NEW] animations\teleport\playerteleport.animation [NEW] animations\teleport\playerwarpin.frames [NEW] animations\teleport\playerwarpin.png [NEW] animations\teleport\playerwarpin2.frames [NEW] animations\teleport\playerwarpin2.png [NEW] animations\teleport\playerwarpinbeam.frames [NEW] animations\teleport\playerwarpinbeam.png [NEW] animations\teleport\playerwarpinbeamgate.png [NEW] animations\teleport\playerwarpout.frames [NEW] animations\teleport\playerwarpout.png [NEW] animations\teleport\playerwarpout2.frames [NEW] animations\teleport\playerwarpout2.png [NEW] animations\teleport\playerwarpoutbeam.frames [NEW] animations\teleport\playerwarpoutbeam.png [NEW] animations\teleport\playerwarpoutbeamgate.png [NEW] animations\teleport\playerwarpoutgate.png monsters\boss\jellyboss\jellyboss.monstertype monsters\boss\skeyejelly\skeyejelly.monstertype monsters\ground\approachstate.lua monsters\ground\groundmonster.lua monsters\ground\hugebiped\hugebiped.monstertype monsters\ground\largebiped\largebiped.monstertype monsters\ground\largequadruped\largequadruped.monstertype monsters\ground\minibossbiped\minibossbiped.monstertype monsters\ground\minibossquadruped\minibossquadruped.monstertype monsters\ground\skills\chargeattack.lua monsters\ground\smallbiped\smallbiped.monstertype monsters\ground\smallquadruped\smallquadruped.monstertype monsters\pets\groundpet.lua monsters\pets\bunny\petbunny.monstertype monsters\pets\cat\petcat.monstertype monsters\pets\crasberry\crasberry.monstertype monsters\pets\orbis\petorbis.monstertype monsters\pets\piglett\piglett.monstertype monsters\pets\snake\petsnake.monstertype monsters\pets\snugget\snugget.monstertype monsters\pets\weasel\petweasel.monstertype monsters\unique\apexbrainmutant\apexbrainmutant.monstertype monsters\unique\apexmutant\apexmutant.monstertype monsters\unique\moontant\moontant.monstertype monsters\unique\pogolem\pogolem.monstertype monsters\unique\poptop\poptop.monstertype monsters\unique\serpentdroid\serpentdroid.monstertype monsters\unique\smallshroom\smallshroom.monstertype npcs\airshipcrew.npctype npcs\airshipguard.npctype npcs\airshipquartermaster.npctype npcs\apexslaver.npctype npcs\bandit.npctype npcs\blacksmith.npctype npcs\bunkerguard.npctype npcs\castleguard.npctype npcs\castlelord.npctype npcs\castleroyalguard.npctype npcs\chefmerchant.npctype npcs\clanleader.npctype npcs\cultist.npctype npcs\default.npctype npcs\doctormerchant.npctype npcs\eyeguard.npctype npcs\follower.npctype npcs\friendlyguard.npctype npcs\friendlypirateguard.npctype npcs\guard.npctype npcs\hellguard.npctype npcs\hellprisoner.npctype npcs\labguard.npctype npcs\labscientist.npctype npcs\main.lua npcs\merchant.npctype npcs\nakedvillager.npctype npcs\outlawsniper.npctype npcs\prisonberserker.npctype npcs\prisonbruiser.npctype npcs\prisongunman.npctype npcs\pyramidguard.npctype npcs\shroomguard.npctype npcs\shroommerchant.npctype npcs\stimmerchant.npctype npcs\templeguard.npctype npcs\testbandit.npctype npcs\testguard.npctype npcs\tombzombie.npctype npcs\toolmerchant.npctype npcs\towerascendant.npctype npcs\villageguard.npctype npcs\villager.npctype npcs\wanderingmerchant.npctype npcs\wildhunter.npctype npcs\wizardmerchant.npctype npcs\mission\florangladiator.npctype npcs\mission\floranpredator.npctype npcs\mission\floransniper.npctype npcs\mission\miner.npctype npcs\mission\minersurvivor.npctype npcs\mission\mutantminer.npctype npcs\mission\survivor.npctype npcs\mission\wildling.npctype npcs\outpost\outpostcivilian.npctype [NEW] scripts\pathing.lua DIFFS ----- player.config 561a562 > "teleportAnimator" : "/animations/teleport/playerteleport.animation", 563,566d563 < "teleportInBase" : "/humanoid/any/playerwarpin.png", < "teleportInBeam" : "/humanoid/any/playerwarpinbeam.png", < "teleportInFrames" : 16, < 568,574d564 < "teleportOutBase" : "/humanoid/any/playerwarpout.png", < "teleportOutBeam" : "/humanoid/any/playerwarpoutbeam.png", < "teleportOutFrames" : 12, < < "teleportHeight" : 43.0, < "teleportBeamHeight" : 8, < "teleportTallness" : 30, sfx.config 8,12c8 < "harvestToolVolume" : 1, < < "teleportNoiseUp" : "/sfx/tools/teleport_up.wav", < "teleportNoiseDown" : "/sfx/tools/teleport_down.wav", < "teleportVolume" : 1 --- > "harvestToolVolume" : 1 monsters\boss\jellyboss\jellyboss.monstertype 18a19 > "/scripts/pathing.lua", monsters\boss\skeyejelly\skeyejelly.monstertype 13a14 > "/scripts/pathing.lua", monsters\ground\approachstate.lua 35,38c35,40 < if option.valid and checkStuck() > 4 then < self.state.pickState({flee=true}) < return true < end --- > -- Commented out until pathing works - > -- TODO: Uncomment this when pathing works > -- if option.valid and checkStuck() > 4 then > -- self.state.pickState({flee=true}) > -- return true > -- end monsters\ground\groundmonster.lua 34,36d33 < self.isBlocked = false < self.willFall = false < 313c310 < drawDebugSkillOptions() --- > debugSkillOptions() 318c315,317 < if not self.moved then resetPathing() end --- > if hasTarget() then script.setUpdateDelta(1) end > > if not self.moved and not hasTarget() then script.setUpdateDelta(self.scriptDelta) end 325,326d323 < checkTerrain(delta[1]) < 373,380d369 < function moveX(direction, run) < checkTerrain(direction) < < mcontroller.controlMove(direction, run) < self.pathing.deltaX = direction < end < < -------------------------------------------------------------------------------- 438,475c427,430 < --TODO: this could probably be further optimized by creating a list of discrete points and using sensors... project for another time < function checkTerrain(direction) < --normalize to 1 or -1 < direction = direction > 0 and 1 or -1 < < local reverse = false < if direction ~= nil then < reverse = direction ~= mcontroller.facingDirection() < end < < local boundBox = mcontroller.boundBox() < < -- update self.isBlocked < local blockLine, topLine < if not reverse then < blockLine = {entity.toAbsolutePosition({boundBox[3] + 0.25, boundBox[4]}), entity.toAbsolutePosition({boundBox[3] + 0.25, boundBox[2] - 1.0})} < else < blockLine = {entity.toAbsolutePosition({-boundBox[3] - 0.25, boundBox[4]}), entity.toAbsolutePosition({-boundBox[3] - 0.25, boundBox[2] - 1.0})} < end < < local blockBlocks = world.collisionBlocksAlongLine(blockLine[1], blockLine[2]) < self.isBlocked = false < if #blockBlocks > 0 then < --check for basic blockage < local topOffset = blockBlocks[1][2] - blockLine[2][2] < if topOffset > 2.75 then < self.isBlocked = true < elseif topOffset > 0.25 then < --also check for that stupid little hook ledge thing < self.isBlocked = not world.pointTileCollision({blockBlocks[1][1] - direction, blockBlocks[1][2] - 1}) < < if not self.isBlocked then < --also check if blocks above prevent us from climbing < topLine = {entity.toAbsolutePosition({boundBox[1], boundBox[4] + 0.5}), entity.toAbsolutePosition({boundBox[3], boundBox[4] + 0.5})} < self.isBlocked = world.lineTileCollision(topLine[1], topLine[2]) < end < end < end --- > function isBlocked(direction) > local direction = direction or mcontroller.facingDirection() > local position = mcontroller.position() > position[1] = position[1] + direction 477,486c432,433 < -- world.debugLine(blockLine[1], blockLine[2], self.isBlocked and "red" or "blue") < -- if topLine then world.debugLine(topLine[1], topLine[2], self.isBlocked and "red" or "blue") end < -- if #blockBlocks > 0 then world.debugPoint({blockBlocks[1][1] - direction, blockBlocks[1][2] - 1}, self.isBlocked and "red" or "blue") end < < -- update self.willFall < local fallLine < if reverse then < fallLine = {entity.toAbsolutePosition({-0.5, boundBox[2] - 0.75}), entity.toAbsolutePosition({boundBox[3], boundBox[2] - 0.75})} < else < fallLine = {entity.toAbsolutePosition({0.5, boundBox[2] - 0.75}), entity.toAbsolutePosition({-boundBox[3], boundBox[2] - 0.75})} --- > if not world.resolvePolyCollision(mcontroller.collisionPoly(), position, 0.8) then > return true 488,498c435 < self.willFall = < world.lineTileCollision(fallLine[1], fallLine[2], false) == false and < world.lineTileCollision({fallLine[1][1], fallLine[1][2] - 1}, {fallLine[2][1], fallLine[2][2] - 1}, false) == false < < -- world.debugLine(fallLine[1], fallLine[2], self.willFall and "red" or "blue") < -- world.debugLine({fallLine[1][1], fallLine[1][2] - 1}, {fallLine[2][1], fallLine[2][2] - 1}, self.willFall and "red" or "blue") < end < < -------------------------------------------------------------------------------- < function isBlocked() < return self.isBlocked --- > return false 503c440,447 < return self.willFall --- > local direction = direction or mcontroller.facingDirection() > local position = mcontroller.position() > position[1] = position[1] + direction > > if not validStandingPosition(position) then > return true > end > return false 639,643c583 < local toApproachPoint = world.distance(approachPoint, mcontroller.position()) < local approachPoints = table.pack(findGroundPosition(approachPoint, math.floor(approachRect[2]), math.ceil(approachRect[4]), util.toDirection(toApproachPoint[1]))) < < --Rearrange to test backward, forward, middle < approachPoints = {approachPoints[3], approachPoints[2], approachPoints[1]} --- > local groundPoint = findGroundPosition(approachPoint, math.floor(approachRect[2]), math.ceil(approachRect[4])) 646,656c586,593 < for _,approachPoint in pairs(approachPoints) do < if approachPoint < and pointWithinRect(approachPoint, startRect) --approachPoint hasn't been shifted out of the startRect < and (params.requireLos == false or world.lineTileCollision(approachPoint, newTargetPosition) == false) --space is in LoS of target < and self.skillCooldownTimers[skillName] <= travelTime(world.distance(mcontroller.position(), approachPoint)[1]) + 0.4 --skill will be ready when we get there < then < self.skillOptions[#self.skillOptions].approachPoint = approachPoint < self.skillOptions[#self.skillOptions].valid = true < validOptionCount = validOptionCount + 1 < break < end --- > if groundPoint > and pointWithinRect(groundPoint, startRect) --approachPoint hasn't been shifted out of the startRect > and (params.requireLos == false or world.lineTileCollision(groundPoint, newTargetPosition) == false) --space is in LoS of target > and self.skillCooldownTimers[skillName] <= travelTime(world.distance(mcontroller.position(), groundPoint)[1]) + 0.4 --skill will be ready when we get there > then > self.skillOptions[#self.skillOptions].approachPoint = groundPoint > self.skillOptions[#self.skillOptions].valid = true > validOptionCount = validOptionCount + 1 721c658 < function drawDebugRect(rect, color, basePos) --- > function debugRect(rect, color, basePos) 730,754d666 < -- 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 < < -------------------------------------------------------------------------------- 756c668 < function drawDebugSkillOptions() --- > function debugSkillOptions() 759c671 < drawDebugRect(option.startRect, i == 1 and "#3333FF" or option.valid and "#AAFFBB" or "#FF3333") --- > debugRect(option.startRect, i == 1 and "#3333FF" or option.valid and "#AAFFBB" or "#FF3333") 838,1329c750 < 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 < < -------------------------------------------------------------------------------- < -- 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), position[2] + bounds[2] - 1, < position[1] + math.max(direction * math.max(bounds[3], 1), 0), position[2] + bounds[2] < } < local collisionResolve = world.resolvePolyCollision(collisionPoly, {position[1] + direction/2, position[2]}, 0.9) < if (world.rectTileCollision(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) < 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.rectTileCollision(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 --- > end \ No newline at end of file monsters\ground\hugebiped\hugebiped.monstertype 20a21 > "/scripts/pathing.lua", monsters\ground\largebiped\largebiped.monstertype 22a23 > "/scripts/pathing.lua", monsters\ground\largequadruped\largequadruped.monstertype 14a15 > "/scripts/pathing.lua", monsters\ground\minibossbiped\minibossbiped.monstertype 20a21 > "/scripts/pathing.lua", monsters\ground\minibossquadruped\minibossquadruped.monstertype 14a15 > "/scripts/pathing.lua", monsters\ground\skills\chargeattack.lua 7a8 > world.logInfo("Blocked: %s | Falling: %s", isBlocked(), willFall()) monsters\ground\smallbiped\smallbiped.monstertype 23a24 > "/scripts/pathing.lua", monsters\ground\smallquadruped\smallquadruped.monstertype 15a16 > "/scripts/pathing.lua", monsters\pets\groundpet.lua 76a77,83 > tickResources(dt) > decrementTimers(dt) > > updateAnchor() > > if not self.moved then script.setUpdateDelta(self.scriptDelta) end > 87,93d93 < < tickResources(dt) < decrementTimers(dt) < < updateAnchor() < < if not self.moved then resetPathing() end 309c309 < function drawDebugRect(rect, color, basePos) --- > function debugRect(rect, color, basePos) 318,343d317 < -- 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 < < < -------------------------------------------------------------------------------- 371,376d344 < function moveX(direction, run) < mcontroller.controlMove(direction, run) < self.pathing.deltaX = direction < end < < -------------------------------------------------------------------------------- 402c370 < if findValidStandingPosition({tilePosition[1] + direction, tilePosition[2] + yDir}, direction) then --- > if validStandingPosition({tilePosition[1] + direction, tilePosition[2] + yDir}) then 409,887c377 < 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 < self.pathing.stuck = true < return false < else < self.pathing.stuck = false < 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), position[2] + bounds[2] - 1, < position[1] + math.max(direction * math.max(bounds[3], 1), 0), position[2] + bounds[2] < } < local collisionResolve = world.resolvePolyCollision(collisionPoly, {position[1] + direction/2, position[2]}, 0.2) < if (world.rectTileCollision(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 --- > end \ No newline at end of file monsters\pets\bunny\petbunny.monstertype 17a18 > "/scripts/pathing.lua", 22d22 < "/monsters/pets/groundPet.lua", monsters\pets\cat\petcat.monstertype 17a18 > "/scripts/pathing.lua", 22d22 < "/monsters/pets/groundPet.lua", monsters\pets\crasberry\crasberry.monstertype 17a18 > "/scripts/pathing.lua", 22d22 < "/monsters/pets/groundPet.lua", monsters\pets\orbis\petorbis.monstertype 16a17 > "/scripts/pathing.lua", 21d21 < "/monsters/pets/groundPet.lua", monsters\pets\piglett\piglett.monstertype 17a18 > "/scripts/pathing.lua", 22d22 < "/monsters/pets/groundPet.lua", monsters\pets\snake\petsnake.monstertype 17a18 > "/scripts/pathing.lua", 22d22 < "/monsters/pets/groundPet.lua", monsters\pets\snugget\snugget.monstertype 17a18 > "/scripts/pathing.lua", 22d22 < "/monsters/pets/groundPet.lua", monsters\pets\weasel\petweasel.monstertype 17a18 > "/scripts/pathing.lua", 22d22 < "/monsters/pets/groundPet.lua", monsters\unique\apexbrainmutant\apexbrainmutant.monstertype 15a16 > "/scripts/pathing.lua", monsters\unique\apexmutant\apexmutant.monstertype 14a15 > "/scripts/pathing.lua", monsters\unique\moontant\moontant.monstertype 22a23 > "/scripts/pathing.lua", monsters\unique\pogolem\pogolem.monstertype 15a16 > "/scripts/pathing.lua", monsters\unique\poptop\poptop.monstertype 14a15 > "/scripts/pathing.lua", monsters\unique\serpentdroid\serpentdroid.monstertype 15a16 > "/scripts/pathing.lua", monsters\unique\smallshroom\smallshroom.monstertype 23a24 > "/scripts/pathing.lua", npcs\airshipcrew.npctype 12a13 > "/scripts/pathing.lua", npcs\airshipguard.npctype 10a11 > "/scripts/pathing.lua", npcs\airshipquartermaster.npctype 12a13 > "/scripts/pathing.lua", npcs\apexslaver.npctype 12a13 > "/scripts/pathing.lua", npcs\bandit.npctype 11a12 > "/scripts/pathing.lua", npcs\blacksmith.npctype 12a13 > "/scripts/pathing.lua", npcs\bunkerguard.npctype 12a13 > "/scripts/pathing.lua", npcs\castleguard.npctype 12a13 > "/scripts/pathing.lua", npcs\castlelord.npctype 12a13 > "/scripts/pathing.lua", npcs\castleroyalguard.npctype 10a11 > "/scripts/pathing.lua", npcs\chefmerchant.npctype 12a13 > "/scripts/pathing.lua", npcs\clanleader.npctype 12a13 > "/scripts/pathing.lua", npcs\cultist.npctype 11a12 > "/scripts/pathing.lua", npcs\default.npctype 7a8 > "/scripts/pathing.lua", npcs\doctormerchant.npctype 12a13 > "/scripts/pathing.lua", npcs\eyeguard.npctype 12a13 > "/scripts/pathing.lua", npcs\follower.npctype 11a12 > "/scripts/pathing.lua", npcs\friendlyguard.npctype 13a14 > "/scripts/pathing.lua", npcs\friendlypirateguard.npctype 13a14 > "/scripts/pathing.lua", npcs\guard.npctype 11a12 > "/scripts/pathing.lua", npcs\hellguard.npctype 12a13 > "/scripts/pathing.lua", npcs\hellprisoner.npctype 12a13 > "/scripts/pathing.lua", npcs\labguard.npctype 12a13 > "/scripts/pathing.lua", npcs\labscientist.npctype 12a13 > "/scripts/pathing.lua", npcs\main.lua 453,491d452 < function debugPathEdgeColor(edge) < local action = edge.action < if action == "Walk" then < return "blue" < elseif action == "Jump" then < return "green" < elseif action == "Drop" then < return "blue" < elseif action == "Swim" then < return "white" < elseif action == "Fly" then < return "magenta" < else < return "red" < end < end < < function debugPath(path) < local position = mcontroller.position() < < local step = 0 < local prevStep = position < while true do < local edge = path.path[path.currentEdgeIndex+step] < if edge then < local nextStep = edge.target.position < local color = debugPathEdgeColor(edge) < world.debugLine(prevStep, nextStep, color) < if edge.action == "Jump" then < world.debugText(edge.jumpVelocity[2], {nextStep[1], nextStep[2]-1}, "red") < end < world.debugPoint(nextStep, color) < prevStep = nextStep < step = step + 1 < else < break < end < end < end 536a498 > --MOVEMENT 538,561c500 < 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 < < -------------------------------------------------------------------------------- < function moveX(direction, run) < mcontroller.controlMove(direction, run) < self.pathing.deltaX = direction < end < < -------------------------------------------------------------------------------- --- > --Walk in one direction until a wall or ledge is encountered 586c525 < if findValidStandingPosition({position[1] + direction, position[2] + yDir}, direction) or forceWalk then --- > if validStandingPosition({position[1] + direction, position[2] + yDir}) or forceWalk then 593,923c532 < 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 < < local position = mcontroller.position() < targetPosition = { < math.floor(targetPosition[1]) + 0.5, < math.floor(targetPosition[2]) + 0.5 < } < < script.setUpdateDelta(4) < < --Find new path if target position has changed < local pathTargetPosition = self.pathing.targetPosition < if pathTargetPosition == nil or self.pathing.path == nil or < targetPosition[1] ~= pathTargetPosition[1] or < targetPosition[2] ~= pathTargetPosition[2] then < < if not findPath(targetPosition) then < world.debugText("findPath failed", {position[1], position[2]-2}, "red") < return false < end < end < < local path = updatePath() < if not path then < world.debugText("updatePath failed", {position[1], position[2]-2}, "red") < -- New path is found on the next call to moveTo < return true < end < if self.debug then debugPath(path) end < < if self.pathing.downHoldTimer ~= nil then < keepDropping(dt, options) < return true < end < < local currentEdge = path.currentEdge < local nextPathPosition = currentEdge.target.position < local action = currentEdge.action < < local delta = {nextPathPosition[1] - position[1], nextPathPosition[2] - position[2]} < < -- Open doors in the way < local closedDoorIds = world.entityLineQuery(position, { position[1] + util.clamp(delta[1], -2, 2), position[2] }, { includedTypes = {"object"}, callScript = "hasCapability", callScriptArgs = { "closedDoor" } }) < for _, closedDoorId in pairs(closedDoorIds) do < if options.openDoorCallback == nil or options.openDoorCallback(closedDoorId) then < world.callScriptedEntity(closedDoorId, "openDoor") < end < end < < if self.debug then < world.debugText(action, {position[1], position[2]-2}, "blue") < end < < if action == "Swim" then < if delta[2] > 0 then < mcontroller.controlJump() < mcontroller.controlHoldJump() < end < moveX(delta[1], options.run) < return true < < elseif action == "Jump" then < if not mcontroller.onGround() then < if not path.jumped then < -- If we haven't quite landed yet, wait until we do < mcontroller.setXVelocity(0) < return true < else < -- We've jumped and we're no longer on the ground. Move to the next node < mcontroller.setXVelocity(currentEdge.jumpVelocity[1]) < path.currentEdgeIndex = path.currentEdgeIndex + 1 < path.currentEdge = path.path[path.currentEdgeIndex+1] < path.jumped = nil < return true < end < end < -- Keep applying the Y jump velocity until we're no longer on the ground. < -- Things like groundSlideMovement mess with our velocity until we're < -- in the air (we do however still want groundSlideMovement to apply when < -- we're not jumping so disabling it altogether is not ideal). < -- X velocity is applied when we're clear of the ground. < jump({0, currentEdge.jumpVelocity[2]}) < path.jumped = true < return true < < elseif action == "Drop" then < timedDrop(timeToFall(-delta[2])) < path.currentEdgeIndex = path.currentEdgeIndex + 1 < path.currentEdge = path.path[path.currentEdgeIndex+1] < return true < < elseif action == "Arc" then < if mcontroller.onGround() then < -- Jump failed and we landed somehow. < -- Move a bit towards the next node in case we landed right on the corner < -- of a ledge, just short of the next node. < moveX(delta[1]) < -- If we landed too far from the node to get there then we'll shortly < -- recalculate the path again anyway. < return true < < else < -- setXVelocity in case that changes mid-jump (e.g. when jumping straight < -- up and then to the side). < if currentEdge.source.velocity ~= nil then < mcontroller.setXVelocity(currentEdge.source.velocity[1]) < elseif currentEdge.target.velocity ~= nil then < mcontroller.setXVelocity(currentEdge.target.velocity[1]) < end < script.setUpdateDelta(1) < return true < < end < < else < -- action is either "Walk" or "Fly" < -- TODO implement flying for flying NPCs (if there are any) < moveX(delta[1], options.run) < return true < end < end < < function resetPathExpiration() < self.pathing.expirationTime = world.time() + 1.0 + 2.0 * math.random() < end < < function findPath(targetPosition) < if self.pathing.expirationTime == nil then < resetPathExpiration() < end < if not mcontroller.onGround() and not world.liquidAt(mcontroller.position()) then < return self.pathing.path < end < if self.pathing.expirationTime < world.time() then < self.pathing.path = entity.findPath(targetPosition) < self.pathing.targetPosition = targetPosition < resetPathExpiration() < end < return self.pathing.path < end < < function updatePath() < if self.pathing.expirationTime < world.time() then < return findPath(self.pathing.targetPosition) < end < self.pathing.path = entity.updatePath(self.pathing.path) < return self.pathing.path < 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 with a given x and y velocity < function jump(velocity) < if not mcontroller.onGround() then return end < -- TODO update animation for jumping < mcontroller.setVelocity(velocity) < 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 < function distanceJumpTime(distance) < return timeToFall(distance[2]) < 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 < function validStandingPosition(position, direction) < direction = util.toDirection(direction) < local collisionPoly = mcontroller.collisionPoly() < local bounds = boundingBox() < < local groundRegion = { < position[1] + direction * bounds[1] + 0.05, position[2] + bounds[2] - 0.95, < position[1] + direction * bounds[3] - 0.05, position[2] + bounds[2] < } < local collisionResolve = world.resolvePolyCollision(collisionPoly, {position[1] + direction/2, position[2]}, 0.9) < if (world.rectTileCollision(groundRegion, false) or world.liquidAt(position)) and collisionResolve then < if self.debug then debugRect(groundRegion, "blue") end < return collisionResolve < end < end < < --Find a valid ground position < function findGroundPosition(position, minHeight, maxHeight) < --Check upward < for y = 0, maxHeight do < local validPosition = findValidStandingPosition({position[1], position[2] + y}, 1) < if validPosition then < return validPosition < end < end < --Check downward < for y = -1, minHeight, -1 do < local validPosition = findValidStandingPosition({position[1], position[2] + y}, 1) < if validPosition then < return validPosition < 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] + 0.1, position[2] + bounds[4], < position[1] + bounds[3] - 0.1, position[2] + bounds[4] + 0.5 < } < return world.rectTileCollision(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.rectTileCollision(groundRegion, true) < end --- > end \ No newline at end of file npcs\merchant.npctype 11a12 > "/scripts/pathing.lua", npcs\nakedvillager.npctype 10a11 > "/scripts/pathing.lua", npcs\outlawsniper.npctype 12a13 > "/scripts/pathing.lua", npcs\prisonberserker.npctype 12a13 > "/scripts/pathing.lua", npcs\prisonbruiser.npctype 12a13 > "/scripts/pathing.lua", npcs\prisongunman.npctype 12a13 > "/scripts/pathing.lua", npcs\pyramidguard.npctype 12a13 > "/scripts/pathing.lua", npcs\shroomguard.npctype 12a13 > "/scripts/pathing.lua", npcs\shroommerchant.npctype 11a12 > "/scripts/pathing.lua", npcs\stimmerchant.npctype 11a12 > "/scripts/pathing.lua", npcs\templeguard.npctype 12a13 > "/scripts/pathing.lua", npcs\testbandit.npctype 11a12 > "/scripts/pathing.lua", npcs\testguard.npctype 11a12 > "/scripts/pathing.lua", npcs\tombzombie.npctype 12a13 > "/scripts/pathing.lua", npcs\toolmerchant.npctype 11a12 > "/scripts/pathing.lua", npcs\towerascendant.npctype 10a11 > "/scripts/pathing.lua", npcs\villageguard.npctype 11a12 > "/scripts/pathing.lua", npcs\villager.npctype 10a11 > "/scripts/pathing.lua", npcs\wanderingmerchant.npctype 12a13 > "/scripts/pathing.lua", npcs\wildhunter.npctype 12a13 > "/scripts/pathing.lua", npcs\wizardmerchant.npctype 13a14 > "/scripts/pathing.lua", npcs\mission\florangladiator.npctype 11a12 > "/scripts/pathing.lua", npcs\mission\floranpredator.npctype 11a12 > "/scripts/pathing.lua", npcs\mission\floransniper.npctype 11a12 > "/scripts/pathing.lua", npcs\mission\miner.npctype 13a14 > "/scripts/pathing.lua", npcs\mission\minersurvivor.npctype 11a12 > "/scripts/pathing.lua", npcs\mission\mutantminer.npctype 11a12 > "/scripts/pathing.lua", npcs\mission\survivor.npctype 11a12 > "/scripts/pathing.lua", npcs\mission\wildling.npctype 11a12 > "/scripts/pathing.lua", npcs\outpost\outpostcivilian.npctype 10a11 > "/scripts/pathing.lua",