diff --git a/src/net/torvald/terrarum/gameactors/ActorMovingPlatform.kt b/src/net/torvald/terrarum/gameactors/ActorMovingPlatform.kt index 6f79573ab..120c5a27b 100644 --- a/src/net/torvald/terrarum/gameactors/ActorMovingPlatform.kt +++ b/src/net/torvald/terrarum/gameactors/ActorMovingPlatform.kt @@ -15,6 +15,8 @@ import org.dyn4j.geometry.Vector2 * * Subclasses must set [platformVelocity] before calling `super.updateImpl(delta)`. * + * TODO: in the future this must be generalised as a PhysContraption + * * Created by minjaesong on 2022-02-28. */ open class ActorMovingPlatform() : ActorWithBody() { @@ -23,6 +25,11 @@ open class ActorMovingPlatform() : ActorWithBody() { constructor(newTilewiseWidth: Int) : this() { this.tilewiseWidth = newTilewiseWidth + + physProp = PhysProperties.MOBILE_OBJECT() + collisionType = COLLISION_KINEMATIC + + setHitboxDimension(TILE_SIZE * newTilewiseWidth, TILE_SIZE, 0, 0) } /** Actors currently riding this platform, stored by ActorID for serialisation. */ @@ -34,8 +41,11 @@ open class ActorMovingPlatform() : ActorWithBody() { /** Actual displacement applied this tick (after clampHitbox). */ @Transient private val appliedVelocity = Vector2(0.0, 0.0) - /** Tolerance in pixels for "feet on top of platform" detection. */ - @Transient private val MOUNT_TOLERANCE_Y = 2.0 + /** Tolerance above platform top for "feet on top" detection (pixels). */ + @Transient private val MOUNT_TOLERANCE_ABOVE = (TILE_SIZE / 2).toDouble()//2.0 + + /** Tolerance below platform top — how far feet can sink before dismount (pixels). */ + @Transient private val MOUNT_TOLERANCE_BELOW = (TILE_SIZE / 2).toDouble() /** Minimum combined Y velocity to count as "jumping up" (prevents mount while jumping). */ @Transient private val JUMP_THRESHOLD_Y = -0.5 @@ -86,12 +96,25 @@ open class ActorMovingPlatform() : ActorWithBody() { // Compute actual displacement (clampHitbox may have wrapped coordinates) appliedVelocity.set(hitbox.startX - oldX, hitbox.startY - oldY) - // --- Mount detection and rider management --- + // --- Step 1: Move existing riders BEFORE mount detection --- + // This keeps riders aligned with the platform's new position so the + // mount check doesn't fail when the platform is moving fast. + for (riderId in actorsRiding.toList()) { + val rider = INGAME.getActorByID(riderId) as? ActorWithBody + if (rider != null) { + rider.hitbox.translate(appliedVelocity) + rider.hitbox.setPositionY(hitbox.startY - rider.hitbox.height) + if (rider.externalV.y > 0.0) { + rider.externalV.y = 0.0 + } + rider.walledBottom = true + } + } + + // --- Step 2: Mount detection (riders are now at correct positions) --- val ridersToRemove = ArrayList() - - // Build set of actors currently on top of this platform - val newRiders = ArrayList() + val currentRiders = ArrayList() // Check all active actors + actorNowPlaying val candidates = ArrayList() @@ -104,10 +127,15 @@ open class ActorMovingPlatform() : ActorWithBody() { for (actor in candidates) { val feetY = actor.hitbox.endY + val headY = actor.hitbox.startY val platTop = hitbox.startY - // Check vertical proximity: feet within tolerance of platform top - val verticallyAligned = Math.abs(feetY - platTop) <= MOUNT_TOLERANCE_Y + // Feet are near platform top: slightly above or sunk partway in + val feetNearPlatTop = feetY >= platTop - MOUNT_TOLERANCE_ABOVE && + feetY <= platTop + MOUNT_TOLERANCE_BELOW + + // Actor's head must be above platform top (prevents mounting from below) + val comingFromAbove = headY < platTop // Check horizontal overlap val horizontalOverlap = actor.hitbox.endX > hitbox.startX && actor.hitbox.startX < hitbox.endX @@ -116,18 +144,24 @@ open class ActorMovingPlatform() : ActorWithBody() { val combinedVelY = actor.externalV.y + (actor.controllerV?.y ?: 0.0) val notJumping = combinedVelY >= JUMP_THRESHOLD_Y - if (verticallyAligned && horizontalOverlap && notJumping) { + if (feetNearPlatTop && comingFromAbove && horizontalOverlap && notJumping) { if (!actorsRiding.contains(actor.referenceID)) { + // New rider — mount and snap mount(actor) + actor.hitbox.setPositionY(hitbox.startY - actor.hitbox.height) + if (actor.externalV.y > 0.0) { + actor.externalV.y = 0.0 + } + actor.walledBottom = true } - newRiders.add(actor) + currentRiders.add(actor) } } - // Dismount actors that are no longer on top - val newRiderIds = newRiders.map { it.referenceID }.toSet() + // --- Step 3: Dismount actors no longer on top --- + val currentRiderIds = currentRiders.map { it.referenceID }.toSet() for (riderId in actorsRiding.toList()) { - if (riderId !in newRiderIds) { + if (riderId !in currentRiderIds) { val rider = INGAME.getActorByID(riderId) if (rider is ActorWithBody) { dismount(rider) @@ -138,18 +172,6 @@ open class ActorMovingPlatform() : ActorWithBody() { } } ridersToRemove.forEach { actorsRiding.remove(it) } - - // Move riders and suppress their gravity - for (rider in newRiders) { - // Translate rider by platform's actual displacement - rider.hitbox.translate(appliedVelocity) - - // Snap rider's feet to platform top - rider.hitbox.setPositionY(hitbox.startY - rider.hitbox.height) - - // Suppress gravity for this tick - rider.walledBottom = true - } } /** diff --git a/src/net/torvald/terrarum/gameactors/ActorWithBody.kt b/src/net/torvald/terrarum/gameactors/ActorWithBody.kt index a434e77c6..ef2e841c7 100644 --- a/src/net/torvald/terrarum/gameactors/ActorWithBody.kt +++ b/src/net/torvald/terrarum/gameactors/ActorWithBody.kt @@ -582,7 +582,7 @@ open class ActorWithBody : Actor { // --> Apply more forces <-- // // Actors are subject to the gravity and the buoyancy if they are not levitating - if (!isNoSubjectToGrav) { + if (!isNoSubjectToGrav && platformsRiding.isEmpty()) { applyGravitation() applyBuoyancy() } @@ -603,7 +603,14 @@ open class ActorWithBody : Actor { * If and only if: * This body is NON-STATIC and the other body is STATIC */ - if (!isNoCollideWorld) { + if (platformsRiding.isNotEmpty()) { + // Riding a platform: skip displaceHitbox entirely. + // The CCD/collision solver doesn't know about platforms and + // will corrupt the rider's position (bounce, stair-step, etc.). + // Only apply horizontal movement; the platform owns Y. + hitbox.translate(vecSum.x, 0.0) + } + else if (!isNoCollideWorld) { val (collisionStatus, collisionDamage) = displaceHitbox(true) @@ -665,7 +672,7 @@ open class ActorWithBody : Actor { walledLeft = isWalled(hitbox, COLLIDING_LEFT) walledRight = isWalled(hitbox, COLLIDING_RIGHT) walledTop = isWalled(hitbox, COLLIDING_TOP) - walledBottom = isWalled(hitbox, COLLIDING_BOTTOM) + walledBottom = isWalled(hitbox, COLLIDING_BOTTOM) || platformsRiding.isNotEmpty() colliding = isColliding(hitbox) if (isNoCollideWorld) { diff --git a/src/net/torvald/terrarum/modulebasegame/console/SpawnMovingPlatform.kt b/src/net/torvald/terrarum/modulebasegame/console/SpawnMovingPlatform.kt index db447625f..1c7357355 100644 --- a/src/net/torvald/terrarum/modulebasegame/console/SpawnMovingPlatform.kt +++ b/src/net/torvald/terrarum/modulebasegame/console/SpawnMovingPlatform.kt @@ -2,6 +2,7 @@ package net.torvald.terrarum.modulebasegame.console import net.torvald.terrarum.INGAME import net.torvald.terrarum.Terrarum +import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZED import net.torvald.terrarum.console.ConsoleAlias import net.torvald.terrarum.console.ConsoleCommand import net.torvald.terrarum.console.Echo @@ -13,12 +14,12 @@ import net.torvald.terrarum.modulebasegame.gameactors.ActorTestPlatform @ConsoleAlias("spawnplatform") internal object SpawnMovingPlatform : ConsoleCommand { override fun execute(args: Array) { - val mouseX = Terrarum.mouseX - val mouseY = Terrarum.mouseY + val mouseX = Terrarum.mouseTileX * TILE_SIZED + val mouseY = Terrarum.mouseTileY * TILE_SIZED val platform = ActorTestPlatform() // setPosition places bottom-centre at the given point; offset Y so the platform is centred at cursor - platform.setPosition(mouseX, mouseY + platform.hitbox.height / 2.0) + platform.setPosition(mouseX, mouseY) INGAME.queueActorAddition(platform) diff --git a/src/net/torvald/terrarum/modulebasegame/gameactors/ActorTestPlatform.kt b/src/net/torvald/terrarum/modulebasegame/gameactors/ActorTestPlatform.kt index f3ec09f95..7a09108a0 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameactors/ActorTestPlatform.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameactors/ActorTestPlatform.kt @@ -18,7 +18,7 @@ import kotlin.math.sin class ActorTestPlatform : ActorMovingPlatform(8) { /** Movement pattern index (0-3). */ - private val pattern: Int = (0..3).random() + private val pattern: Int = 1//(0..3).random() /** Speed in pixels per tick (2.0 to 4.0). */ private val speed: Double = 2.0 + Math.random() * 2.0