213 lines
6.9 KiB
Python
213 lines
6.9 KiB
Python
import math
|
|
|
|
from ..constants import tileSize
|
|
from .geometry import Aabb
|
|
|
|
slopeStick = 6.0
|
|
groundProbe = 2.0
|
|
|
|
|
|
def bodyAabb(actor):
|
|
return Aabb(actor.x, actor.y, actor.w, actor.h)
|
|
|
|
|
|
def cellRange(box):
|
|
leftCol = int(math.floor(box.left / tileSize))
|
|
rightCol = int(math.floor((box.right - 1e-6) / tileSize))
|
|
topRow = int(math.floor(box.top / tileSize))
|
|
bottomRow = int(math.floor((box.bottom - 1e-6) / tileSize))
|
|
return leftCol, rightCol, topRow, bottomRow
|
|
|
|
|
|
def solidTilesOverlapping(terrain, box):
|
|
leftCol, rightCol, topRow, bottomRow = cellRange(box)
|
|
hits = []
|
|
for row in range(topRow, bottomRow + 1):
|
|
for col in range(leftCol, rightCol + 1):
|
|
if terrain.solidAt(col, row):
|
|
hits.append(terrain.rectFor(col, row))
|
|
return hits
|
|
|
|
|
|
def oneWayTilesOverlapping(terrain, box):
|
|
leftCol, rightCol, topRow, bottomRow = cellRange(box)
|
|
hits = []
|
|
for row in range(topRow, bottomRow + 1):
|
|
for col in range(leftCol, rightCol + 1):
|
|
if terrain.oneWayAt(col, row):
|
|
hits.append(terrain.rectFor(col, row))
|
|
return hits
|
|
|
|
|
|
def slopeSurfaceY(col, row, sampleX, kind):
|
|
tileLeft = col * tileSize
|
|
tileTop = row * tileSize
|
|
localX = sampleX - tileLeft
|
|
if localX < 0.0:
|
|
localX = 0.0
|
|
elif localX > tileSize:
|
|
localX = float(tileSize)
|
|
if kind == "ascendRight":
|
|
return tileTop + (tileSize - localX)
|
|
return tileTop + localX
|
|
|
|
|
|
def moveHorizontal(actor, terrain, platforms):
|
|
actor.x += actor.vx
|
|
box = bodyAabb(actor)
|
|
actor.onWallLeft = False
|
|
actor.onWallRight = False
|
|
for tileBox in solidTilesOverlapping(terrain, box):
|
|
if box.intersects(tileBox):
|
|
if actor.vx > 0:
|
|
actor.x = tileBox.left - actor.w
|
|
actor.onWallRight = True
|
|
elif actor.vx < 0:
|
|
actor.x = tileBox.right
|
|
actor.onWallLeft = True
|
|
actor.vx = 0.0
|
|
box = bodyAabb(actor)
|
|
for platform in platforms:
|
|
if platform.oneWay:
|
|
continue
|
|
platformBox = platform.aabb
|
|
if box.intersects(platformBox):
|
|
if actor.vx > 0:
|
|
actor.x = platformBox.left - actor.w
|
|
actor.onWallRight = True
|
|
elif actor.vx < 0:
|
|
actor.x = platformBox.right
|
|
actor.onWallLeft = True
|
|
actor.vx = 0.0
|
|
box = bodyAabb(actor)
|
|
|
|
|
|
def moveVertical(actor, terrain, platforms, dropThrough=False):
|
|
prevBottom = actor.y + actor.h
|
|
actor.y += actor.vy
|
|
box = bodyAabb(actor)
|
|
actor.onGround = False
|
|
actor.onCeiling = False
|
|
actor.groundPlatform = None
|
|
|
|
for tileBox in solidTilesOverlapping(terrain, box):
|
|
if box.intersects(tileBox):
|
|
if actor.vy > 0:
|
|
actor.y = tileBox.top - actor.h
|
|
actor.onGround = True
|
|
elif actor.vy < 0:
|
|
actor.y = tileBox.bottom
|
|
actor.onCeiling = True
|
|
actor.vy = 0.0
|
|
box = bodyAabb(actor)
|
|
|
|
if actor.vy >= 0 and not dropThrough:
|
|
for tileBox in oneWayTilesOverlapping(terrain, box):
|
|
top = tileBox.top
|
|
if prevBottom <= top + 1.0 and box.bottom >= top and box.right > tileBox.left and box.left < tileBox.right:
|
|
actor.y = top - actor.h
|
|
actor.vy = 0.0
|
|
actor.onGround = True
|
|
box = bodyAabb(actor)
|
|
|
|
for platform in platforms:
|
|
platformBox = platform.aabb
|
|
if platform.oneWay:
|
|
if actor.vy >= 0 and not dropThrough:
|
|
top = platformBox.top
|
|
horizontal = box.right > platformBox.left and box.left < platformBox.right
|
|
if prevBottom <= top + 1.0 and box.bottom >= top and horizontal:
|
|
actor.y = top - actor.h
|
|
actor.vy = 0.0
|
|
actor.onGround = True
|
|
actor.groundPlatform = platform
|
|
box = bodyAabb(actor)
|
|
else:
|
|
if box.intersects(platformBox):
|
|
if actor.vy > 0:
|
|
actor.y = platformBox.top - actor.h
|
|
actor.onGround = True
|
|
actor.groundPlatform = platform
|
|
elif actor.vy < 0:
|
|
actor.y = platformBox.bottom
|
|
actor.onCeiling = True
|
|
actor.vy = 0.0
|
|
box = bodyAabb(actor)
|
|
|
|
|
|
def resolveSlope(actor, terrain, wasOnGround):
|
|
box = bodyAabb(actor)
|
|
sampleX = box.centerX
|
|
col = int(math.floor(sampleX / tileSize))
|
|
topRow = int(math.floor((box.top - tileSize) / tileSize))
|
|
bottomRow = int(math.floor((box.bottom + slopeStick) / tileSize))
|
|
for row in range(topRow, bottomRow + 1):
|
|
kind = terrain.slopeAt(col, row)
|
|
if not kind:
|
|
continue
|
|
surfaceY = slopeSurfaceY(col, row, sampleX, kind)
|
|
if surfaceY <= box.top:
|
|
continue
|
|
gap = surfaceY - box.bottom
|
|
penetrating = gap <= 0.0 and gap >= -float(tileSize) - slopeStick
|
|
clinging = wasOnGround and 0.0 < gap <= slopeStick
|
|
if actor.vy >= 0.0 and (penetrating or clinging):
|
|
actor.y = surfaceY - actor.h
|
|
actor.vy = 0.0
|
|
actor.onGround = True
|
|
return True
|
|
return False
|
|
|
|
|
|
def touchingSolidSide(actor, terrain, platforms, direction):
|
|
probe = Aabb(
|
|
actor.x + (actor.w if direction > 0 else -groundProbe),
|
|
actor.y + 2.0,
|
|
groundProbe,
|
|
actor.h - 4.0,
|
|
)
|
|
for tileBox in solidTilesOverlapping(terrain, probe):
|
|
if probe.intersects(tileBox):
|
|
return True
|
|
for platform in platforms:
|
|
if platform.oneWay:
|
|
continue
|
|
if probe.intersects(platform.aabb):
|
|
return True
|
|
return False
|
|
|
|
|
|
def hasGroundBelow(actor, terrain, platforms):
|
|
probe = Aabb(actor.x + 1.0, actor.y + actor.h, actor.w - 2.0, groundProbe)
|
|
for tileBox in solidTilesOverlapping(terrain, probe):
|
|
if probe.intersects(tileBox):
|
|
return True
|
|
for tileBox in oneWayTilesOverlapping(terrain, probe):
|
|
if probe.intersects(tileBox):
|
|
return True
|
|
for platform in platforms:
|
|
if probe.intersects(platform.aabb):
|
|
return True
|
|
return False
|
|
|
|
|
|
def overlapsLadder(actor, terrain):
|
|
box = bodyAabb(actor)
|
|
leftCol, rightCol, topRow, bottomRow = cellRange(box)
|
|
for row in range(topRow, bottomRow + 1):
|
|
for col in range(leftCol, rightCol + 1):
|
|
if terrain.ladderAt(col, row):
|
|
return True
|
|
return False
|
|
|
|
|
|
def hazardOverlap(actor, terrain):
|
|
box = bodyAabb(actor).inflated(-1.0, -1.0)
|
|
leftCol, rightCol, topRow, bottomRow = cellRange(box)
|
|
worstDamage = 0
|
|
for row in range(topRow, bottomRow + 1):
|
|
for col in range(leftCol, rightCol + 1):
|
|
if terrain.hazardAt(col, row):
|
|
worstDamage = max(worstDamage, terrain.hazardDamageAt(col, row))
|
|
return worstDamage
|