diff --git a/dev-assets/doodads/azulian/azulian-red.js b/dev-assets/doodads/azulian/azulian-red.js index 4ff17bc..a138408 100644 --- a/dev-assets/doodads/azulian/azulian-red.js +++ b/dev-assets/doodads/azulian/azulian-red.js @@ -11,24 +11,16 @@ function main() { Self.AddAnimation("walk-left", 100, ["red-wl1", "red-wl2", "red-wl3", "red-wl4"]); Self.AddAnimation("walk-right", 100, ["red-wr1", "red-wr2", "red-wr3", "red-wr4"]); - // var nextTurn = time.Add(time.Now(), 2500); - // Sample our X position every few frames and detect if we've hit a solid wall. var sampleTick = 0; var sampleRate = 5; var lastSampledX = 0; setInterval(function() { - // if (time.Now().After(nextTurn)) { - // direction = direction === "right" ? "left" : "right"; - // nextTurn = time.Add(time.Now(), 2500); - // } - if (sampleTick % sampleRate === 0) { var curX = Self.Position().X; var delta = Math.abs(curX - lastSampledX); if (delta < 5) { - log.Error("flip red azulian"); direction = direction === "right" ? "left" : "right"; } lastSampledX = curX; diff --git a/dev-assets/doodads/buttons/button.js b/dev-assets/doodads/buttons/button.js index 5ae0a17..7e48193 100644 --- a/dev-assets/doodads/buttons/button.js +++ b/dev-assets/doodads/buttons/button.js @@ -4,6 +4,10 @@ function main() { var timer = 0; Events.OnCollide(function(e) { + if (!e.Settled) { + return; + } + // Verify they've touched the button. if (e.Overlap.Y + e.Overlap.H < 24) { return; diff --git a/dev-assets/doodads/buttons/sticky.js b/dev-assets/doodads/buttons/sticky.js index bd29aee..ed4113d 100644 --- a/dev-assets/doodads/buttons/sticky.js +++ b/dev-assets/doodads/buttons/sticky.js @@ -13,6 +13,10 @@ function main() { }) Events.OnCollide(function(e) { + if (!e.Settled) { + return; + } + if (pressed) { return; } diff --git a/dev-assets/doodads/doors/electric-door.js b/dev-assets/doodads/doors/electric-door.js index 7ecdbdb..d424ba3 100644 --- a/dev-assets/doodads/doors/electric-door.js +++ b/dev-assets/doodads/doors/electric-door.js @@ -37,11 +37,4 @@ function main() { } } }); - Events.OnLeave(function() { - // if (opened) { - // Self.PlayAnimation("close", function() { - // opened = false; - // }); - // } - }) } diff --git a/dev-assets/doodads/doors/locked-door.js b/dev-assets/doodads/doors/locked-door.js index 28f6d70..ce91829 100644 --- a/dev-assets/doodads/doors/locked-door.js +++ b/dev-assets/doodads/doors/locked-door.js @@ -2,8 +2,6 @@ function main() { Self.AddAnimation("open", 0, [1]); var unlocked = false; - // Self.Canvas.SetBackground(RGBA(0, 255, 255, 100)); - // Map our door names to key names. var KeyMap = { "Blue Door": "Blue Key", @@ -12,8 +10,6 @@ function main() { "Yellow Door": "Yellow Key" } - // log.Warn("%s loaded!", Self.Doodad.Title); - // console.log("%s Setting hitbox", Self.Doodad.Title); Self.SetHitbox(16, 0, 32, 64); Events.OnCollide(function(e) { @@ -28,11 +24,10 @@ function main() { return false; } - unlocked = true; - Self.PlayAnimation("open", null); + if (e.Settled) { + unlocked = true; + Self.PlayAnimation("open", null); + } } }); - // Events.OnLeave(function(e) { - // console.log("%s has stopped touching %s", e, Self.Doodad.Title) - // }) } diff --git a/dev-assets/doodads/switches/switch.js b/dev-assets/doodads/switches/switch.js index 6a273ee..489aedf 100644 --- a/dev-assets/doodads/switches/switch.js +++ b/dev-assets/doodads/switches/switch.js @@ -14,6 +14,10 @@ function main() { }); Events.OnCollide(function(e) { + if (!e.Settled) { + return; + } + if (collide === false) { state = !state; Message.Publish("power", state); diff --git a/dev-assets/doodads/trapdoors/trapdoor.js b/dev-assets/doodads/trapdoors/trapdoor.js index 7c3571b..374c781 100644 --- a/dev-assets/doodads/trapdoors/trapdoor.js +++ b/dev-assets/doodads/trapdoors/trapdoor.js @@ -18,11 +18,11 @@ function main() { if (direction === "left") { Self.SetHitbox(48, 0, doodadSize, doodadSize); } else if (direction === "right") { - Self.SetHitbox(0, 0, thickness+4, doodadSize); + Self.SetHitbox(0, 0, thickness, doodadSize); } else if (direction === "up") { Self.SetHitbox(0, doodadSize - thickness, doodadSize, doodadSize); } else { // Down, default. - Self.SetHitbox(0, 0, 72, 6); + Self.SetHitbox(0, 0, doodadSize, thickness); } var animationSpeed = 100; @@ -46,18 +46,43 @@ function main() { // Is the actor colliding our solid part? if (e.InHitbox) { // Are they touching our opening side? - if (direction === "left" && (e.Overlap.X+e.Overlap.W) < (doodadSize-thickness)) { - return false; - } else if (direction === "right" && e.Overlap.X > 0) { - return false; - } else if (direction === "up" && (e.Overlap.Y+e.Overlap.H) < doodadSize) { - return false; - } else if (direction === "down" && e.Overlap.Y > 0) { - return false; - } else { - opened = true; - Self.PlayAnimation("open", null); + if (direction === "left") { + if (doodadSize - e.Overlap.X < thickness) { + // Touching the right edge, open the door. + opened = true; + Self.PlayAnimation("open", null); + return; + } + if (e.Overlap.W === doodadSize - thickness) { + return false; + } + } else if (direction === "right") { + if (e.Overlap.X > 0) { + return false; + } else if (e.Settled) { + opened = true; + Self.PlayAnimation("open", null); + } + } else if (direction === "up") { + if (doodadSize - e.Overlap.Y < thickness) { + // Touching the bottom edge, open the door. + opened = true; + Self.PlayAnimation("open", null); + return; + } + if (e.Overlap.H === doodadSize - thickness) { + return false; + } + } else if (direction === "down") { + if (e.Overlap.Y > 0) { + return false; + } else if (e.Settled) { + opened = true; + Self.PlayAnimation("open", null); + } } + + return true; } }); diff --git a/pkg/balance/boolprops.go b/pkg/balance/boolprops.go index 0d53665..a67f492 100644 --- a/pkg/balance/boolprops.go +++ b/pkg/balance/boolprops.go @@ -17,6 +17,9 @@ var ( // Pretty-print JSON files when writing. JSONIndent bool + + // Temporary debug flag. + TempDebug bool ) // Human friendly names for the boolProps. Not necessarily the long descriptive @@ -25,6 +28,7 @@ var props = map[string]*bool{ "showAllDoodads": &ShowHiddenDoodads, "writeLockOverride": &WriteLockOverride, "prettyJSON": &JSONIndent, + "tempDebug": &TempDebug, // WARNING: SLOW! "disableChunkTextureCache": &DisableChunkTextureCache, diff --git a/pkg/collision/actors_test.go b/pkg/collision/actors_test.go index fcac887..11c2800 100644 --- a/pkg/collision/actors_test.go +++ b/pkg/collision/actors_test.go @@ -101,11 +101,19 @@ func TestActorCollision(t *testing.T) { case 0: assert(i, overlap, 0, 1) case 1: - assert(i, overlap, 3, 4) + assert(i, overlap, 1, 0) case 2: - assert(i, overlap, 5, 6) + assert(i, overlap, 3, 4) case 3: + assert(i, overlap, 4, 3) + case 4: + assert(i, overlap, 5, 6) + case 5: assert(i, overlap, 5, 7) + case 6: + assert(i, overlap, 6, 5) + case 7: + assert(i, overlap, 7, 5) default: t.Errorf("got unexpected collision result, index %d, tuple (%d,%d)", i, a, b, diff --git a/pkg/collision/collide_actors.go b/pkg/collision/collide_actors.go index 78e0725..378d81a 100644 --- a/pkg/collision/collide_actors.go +++ b/pkg/collision/collide_actors.go @@ -31,8 +31,10 @@ func BetweenBoxes(boxes []render.Rect) chan BoxCollision { go func() { // Outer loop: test each box for intersection with the others. for i, box := range boxes { - for j := i + 1; j < len(boxes); j++ { - other := boxes[j] + for j, other := range boxes { + if i == j { + continue + } collision, err := CompareBoxes(box, other) if err == nil { collision.A = i diff --git a/pkg/scripting/pubsub.go b/pkg/scripting/pubsub.go index 5876ad9..8cabf4d 100644 --- a/pkg/scripting/pubsub.go +++ b/pkg/scripting/pubsub.go @@ -35,7 +35,6 @@ func RegisterPublishHooks(vm *VM) { // Register the Message.Subscribe and Message.Publish functions. vm.vm.Set("Message", map[string]interface{}{ "Subscribe": func(name string, callback otto.Value) { - log.Error("SUBSCRIBE: %s", name) if !callback.IsFunction() { log.Error("SUBSCRIBE(%s): callback is not a function", name) return diff --git a/pkg/scripting/vm.go b/pkg/scripting/vm.go index 6dd4095..498e887 100644 --- a/pkg/scripting/vm.go +++ b/pkg/scripting/vm.go @@ -73,6 +73,9 @@ func (vm *VM) RegisterLevelHooks() error { "Point": render.NewPoint, "Self": vm.Self, // i.e., the uix.Actor object "Events": vm.Events, + "GetTick": func() uint64 { + return shmem.Tick + }, "TypeOf": reflect.TypeOf, "time": map[string]interface{}{ diff --git a/pkg/uix/actor_collision.go b/pkg/uix/actor_collision.go index 0ef63f8..b7c96e0 100644 --- a/pkg/uix/actor_collision.go +++ b/pkg/uix/actor_collision.go @@ -100,50 +100,90 @@ func (w *Canvas) loopActorCollision() error { a, b := w.actors[tuple.A], w.actors[tuple.B] collidingActors[a.ID()] = b.ID() - // Call the OnCollide handler. + // Call the OnCollide handler for A informing them of B's intersection. if w.scripting != nil { - // Tell actor A about the collision with B. - if err := w.scripting.To(a.ID()).Events.RunCollide(&CollideEvent{ - Actor: b, - Overlap: tuple.Overlap, - InHitbox: tuple.Overlap.Intersects(a.Hitbox()), - }); err != nil { - if err == scripting.ErrReturnFalse { - if origPoint, ok := originalPositions[b.ID()]; ok { - // Trace a vector back from the actor's current position - // to where they originated from and find the earliest - // point where they are not violating the hitbox. - var ( - rect = doodads.GetBoundingRect(b) - hitbox = a.Hitbox() - ) - for point := range render.IterLine( - b.Position(), - origPoint, - ) { - test := render.Rect{ - X: point.X, - Y: point.Y, - W: rect.W, - H: rect.H, - } - info, err := collision.CompareBoxes( - boxes[tuple.A], - test, - ) - if err != nil || !info.Overlap.Intersects(hitbox) { - b.MoveTo(point) - break - } - } - } else { - log.Error( - "ERROR: Actors %s and %s overlap and the script returned false,"+ - "but I didn't store %s original position earlier??", - a.Doodad.Title, b.Doodad.Title, b.Doodad.Title, - ) + var ( + rect = doodads.GetBoundingRect(b) + lastGoodBox = boxes[tuple.B] // worst case scenario we get blocked right away + ) + + // 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 + // OnCollide handler for each step, with Settled=false. A should + // only return false if it protests the movement, but not trigger + // any actions (such as emit messages to linked doodads) until + // Settled=true. + if origPoint, ok := originalPositions[b.ID()]; ok { + // Trace a vector back from the actor's current position + // to where they originated from. If A protests B's position at + // ANY time, we mark didProtest=true and continue backscanning + // B's movement. The next time A does NOT protest, that is to be + // B's new position. + + var firstPoint = true + for point := range render.IterLine( + origPoint, + b.Position(), + ) { + test := render.Rect{ + X: point.X, + Y: point.Y, + W: rect.W, + H: rect.H, } + + 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. + err := w.scripting.To(a.ID()).Events.RunCollide(&CollideEvent{ + Actor: b, + Overlap: info.Overlap, + InHitbox: info.Overlap.Intersects(a.Hitbox()), + Settled: false, + }) + + // Did A protest? + if err == scripting.ErrReturnFalse { + break + } else { + lastGoodBox = test + } + } + + firstPoint = false + } + + // Were we stopped before we even began? + if firstPoint { + // TODO: undo the effect of gravity this tick. Use case: + // the player lands on top of a solid door, and their + // movement is blocked the first step by the door. Originally + // he'd continue falling, so I had to move him up to stop it, + // turns out moving up by the -gravity is exactly the distance + // to go. Don't know why. + b.MoveBy(render.NewPoint(0, int32(-balance.Gravity))) } else { + b.MoveTo(lastGoodBox.Point()) + } + } else { + log.Error( + "ERROR: Actors %s and %s overlap and the script returned false,"+ + "but I didn't store %s original position earlier??", + a.Doodad.Title, b.Doodad.Title, b.Doodad.Title, + ) + } + + // 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. + if info, err := collision.CompareBoxes(boxes[tuple.A], lastGoodBox); err == nil { + if err := w.scripting.To(a.ID()).Events.RunCollide(&CollideEvent{ + Actor: b, + Overlap: info.Overlap, + InHitbox: info.Overlap.Intersects(a.Hitbox()), + Settled: true, + }); err != nil && err != scripting.ErrReturnFalse { log.Error(err.Error()) } } diff --git a/pkg/uix/actor_events.go b/pkg/uix/actor_events.go index e050ec4..e72d9be 100644 --- a/pkg/uix/actor_events.go +++ b/pkg/uix/actor_events.go @@ -7,4 +7,5 @@ type CollideEvent struct { Actor *Actor Overlap render.Rect InHitbox bool // If the two elected hitboxes are overlapping + Settled bool // Movement phase finished, actor script can fire actions }