diff --git a/dev-assets/doodads/crumbly-floor/crumbly-floor.js b/dev-assets/doodads/crumbly-floor/crumbly-floor.js index 155be86..509101b 100644 --- a/dev-assets/doodads/crumbly-floor/crumbly-floor.js +++ b/dev-assets/doodads/crumbly-floor/crumbly-floor.js @@ -19,10 +19,6 @@ function main() { var startedAnimation = false; Events.OnCollide(function(e) { - // Only trigger for mobile characters. - if (!e.Actor.IsMobile()) { - return; - } // If the floor is falling, the player passes right thru. if (state === stateFalling || state === stateFallen) { @@ -36,6 +32,11 @@ function main() { return false; } + // If movement is not settled, be solid. + if (!e.Settled) { + return false; + } + // Begin the animation sequence if we're in the solid state. if (state === stateSolid) { state = stateShaking; diff --git a/docs/Shell.md b/docs/Shell.md index 936c5c3..62d274c 100644 --- a/docs/Shell.md +++ b/docs/Shell.md @@ -9,6 +9,8 @@ * `import antigravity` - during Play Mode, disables gravity for the player character and allows free movement in all directions with the arrow keys. Enter the cheat again to restore gravity to normal. + * Note: under antigravity, hold down the Shift key to lower the player + speed to only one pixel per tick. * `ghost mode` - during Play Mode, toggles noclip for the player character. ## Bool Props diff --git a/pkg/play_scene.go b/pkg/play_scene.go index 7326b90..a25a9dd 100644 --- a/pkg/play_scene.go +++ b/pkg/play_scene.go @@ -427,6 +427,12 @@ func (s *PlayScene) Draw(d *Doodle) error { func (s *PlayScene) movePlayer(ev *event.State) { var playerSpeed = balance.PlayerMaxVelocity + // If antigravity enabled and the Shift key is pressed down, move the + // player by only one pixel per tick. + if s.antigravity && ev.Shift { + playerSpeed = 1 + } + var velocity render.Point if ev.Left { diff --git a/pkg/uix/actor_collision.go b/pkg/uix/actor_collision.go index 78555c5..0f557aa 100644 --- a/pkg/uix/actor_collision.go +++ b/pkg/uix/actor_collision.go @@ -127,6 +127,19 @@ func (w *Canvas) loopActorCollision() error { } ) + // HACK: below, when we determine the moving actor is "onTop" of + // the doodad's solid hitbox, we lockY their movement so they don't + // fall down further; but sometimes there's an off-by-one error if + // the actor fell a distance before landing, and so the final + // Settled collision check doesn't fire (i.e. if they fell onto a + // Crumbly Floor which should begin shaking when walked on). + // + // When we decide they're onTop, record the Y position, and then + // use it for collision-check purposes but DON'T physically move + // the character by it (moving the character may clip them thru + // other solid hitboxes like the upside-down trapdoor) + var onTopY int + // Firstly we want to make sure B isn't able to clip through A's // solid hitbox if A protests the movement. Trace a vector from // B's original position to their current one and ping A's @@ -147,10 +160,16 @@ func (w *Canvas) loopActorCollision() error { // Touching the solid actor from the side is already fine. var onTop = false + var ( + lockX int + lockY int + ) + for point := range render.IterLine( origPoint, b.Position(), ) { + point := point test := render.Rect{ X: point.X, Y: point.Y, @@ -158,11 +177,6 @@ func (w *Canvas) loopActorCollision() error { H: rect.H, } - var ( - lockX bool - lockY bool - ) - if info, err := collision.CompareBoxes(boxes[tuple.A], test); err == nil { // B is overlapping A's box, call its OnCollide handler // with Settled=false and see if it protests the overlap. @@ -176,39 +190,50 @@ func (w *Canvas) loopActorCollision() error { // Did A protest? if err == scripting.ErrReturnFalse { // Are they on top? - if render.AbsInt(lastGoodBox.Y+lastGoodBox.H-boxes[tuple.A].Y) <= 2 { + aHitbox := doodads.GetBoundingRectHitbox(a, a.Hitbox()) + if render.AbsInt(test.Y+test.H-aHitbox.Y) == 0 { onTop = true + onTopY = test.Y } // What direction were we moving? if test.Y != lastGoodBox.Y { - lockY = true - b.SetGrounded(true) + if lockY == 0 { + lockY = lastGoodBox.Y + } + if onTop { + b.SetGrounded(true) + } } if test.X != lastGoodBox.X { if !onTop { - lockX = true + lockX = lastGoodBox.X } } - // Move them back to the last good box, locking the - // axis they were moving from being able to enter - // this box. - tmp := lastGoodBox + // Move them back to the last good box. lastGoodBox = test - if lockY { - lastGoodBox.Y = tmp.Y - } - if lockX { - lastGoodBox.X = tmp.X - break + if lockX != 0 { + lastGoodBox.X = lockX } } else { + // Move them back to the last good box. lastGoodBox = test } + } else { + // No collision between boxes, increment the lastGoodBox + lastGoodBox = test } } + // Did we lock their X or Y coordinate from moving further? + if lockY != 0 { + lastGoodBox.Y = lockY + } + if lockX != 0 { + lastGoodBox.X = lockX + } + if !b.noclip { b.MoveTo(lastGoodBox.Point()) } @@ -220,6 +245,10 @@ func (w *Canvas) loopActorCollision() error { ) } + if onTopY != 0 && lastGoodBox.Y-onTopY <= 1 { + lastGoodBox.Y = onTopY + } + // Movement has been settled. Check if B's point is still invading // A's box and call its OnCollide handler one last time in // Settled=true mode so it can run its actions.