2019-05-29 04:43:30 +00:00
|
|
|
package uix
|
|
|
|
|
|
|
|
import (
|
2019-07-05 22:02:22 +00:00
|
|
|
"errors"
|
2019-05-29 04:43:30 +00:00
|
|
|
"time"
|
|
|
|
|
2022-09-24 22:17:25 +00:00
|
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
|
|
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/collision"
|
|
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
|
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/physics"
|
|
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/scripting"
|
2019-12-28 03:16:34 +00:00
|
|
|
"git.kirsle.net/go/render"
|
2022-01-17 04:09:27 +00:00
|
|
|
"github.com/dop251/goja"
|
2019-05-29 04:43:30 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// loopActorCollision is the Loop function that checks if pairs of
|
|
|
|
// actors are colliding with each other, and handles their scripting
|
|
|
|
// responses to such collisions.
|
|
|
|
func (w *Canvas) loopActorCollision() error {
|
2019-07-05 22:02:22 +00:00
|
|
|
if w.scripting == nil {
|
|
|
|
return errors.New("Canvas.loopActorCollision: scripting engine not attached to Canvas")
|
|
|
|
}
|
|
|
|
|
2019-05-29 04:43:30 +00:00
|
|
|
var (
|
|
|
|
// Current time of this tick so we can advance animations.
|
|
|
|
now = time.Now()
|
|
|
|
|
|
|
|
// As we iterate over all actors below to process their movement, track
|
|
|
|
// their bounding rectangles so we can later see if any pair of actors
|
|
|
|
// intersect each other. Also, in case of actor scripts protesting a
|
|
|
|
// collision later, store each actor's original position before the move.
|
|
|
|
boxes = make([]render.Rect, len(w.actors))
|
|
|
|
originalPositions = map[string]render.Point{}
|
|
|
|
)
|
|
|
|
|
|
|
|
// Loop over all the actors in parallel, processing their movement and
|
|
|
|
// checking collision data against the level geometry.
|
Thief and Inventory APIs
This commit adds the Thief character with starter graphics
(no animations).
The Thief walks back and forth and will steal items from other
doodads, including the player. For singleton items that have no
quantity, like the Colored Keys, the Thief will only steal one
if he does not already have it. Quantitied items like the
Small Key are always stolen.
Flexibility in the playable character is introduced: Boy,
Azulian, Bird, and Thief all respond to playable controls.
There is not currently a method to enable these apart from
modifying balance.PlayerCharacterDoodad at compile time.
New and Changed Doodads
* Thief: new doodad that walks back and forth and will steal
items from other characters inventory.
* Bird: has no inventory and cannot pick up items, unless player
controlled. Its hitbox has also been fixed so it collides with
floors correctly - not something normally seen in the Bird.
* Boy: opts in to have inventory.
* Keys (all): only gives themselves to actors having inventories.
JavaScript API - New functions available
* Self.IsPlayer() - returns if the current actor IS the player.
* Self.SetInventory(bool) - doodads must opt-in to having an
inventory. Keys should only give themselves to doodads having
an inventory.
* Self.HasInventory() bool
* Self.AddItem(filename, qty)
* Self.RemoveItem(filename, qty)
* Self.HasItem(filename)
* Self.Inventory() - returns map[string]int
* Self.ClearInventory()
* Self.OnLeave(func(e)) now receives a CollideEvent as parameter
instead of the useless actor ID. Notably, e.Actor is the
leaving actor and e.Settled is always true.
Other Changes
* Play Mode: if playing as a character which doesn't obey gravity,
such as the bird, antigravity controls are enabled by default.
If you `import antigravity` you can turn gravity back on.
* Doodad collision scripts are no longer run in parallel
goroutines. It made the Thief's job difficult trying to steal
items in many threads simultaneously!
2021-08-10 05:42:22 +00:00
|
|
|
// NOTE: parallelism wasn't good for race conditions like the Thief
|
|
|
|
// trying to take your inventory.
|
|
|
|
// var wg sync.WaitGroup
|
2019-05-29 04:43:30 +00:00
|
|
|
for i, a := range w.actors {
|
2022-01-19 05:24:36 +00:00
|
|
|
if a.IsFrozen() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
Thief and Inventory APIs
This commit adds the Thief character with starter graphics
(no animations).
The Thief walks back and forth and will steal items from other
doodads, including the player. For singleton items that have no
quantity, like the Colored Keys, the Thief will only steal one
if he does not already have it. Quantitied items like the
Small Key are always stolen.
Flexibility in the playable character is introduced: Boy,
Azulian, Bird, and Thief all respond to playable controls.
There is not currently a method to enable these apart from
modifying balance.PlayerCharacterDoodad at compile time.
New and Changed Doodads
* Thief: new doodad that walks back and forth and will steal
items from other characters inventory.
* Bird: has no inventory and cannot pick up items, unless player
controlled. Its hitbox has also been fixed so it collides with
floors correctly - not something normally seen in the Bird.
* Boy: opts in to have inventory.
* Keys (all): only gives themselves to actors having inventories.
JavaScript API - New functions available
* Self.IsPlayer() - returns if the current actor IS the player.
* Self.SetInventory(bool) - doodads must opt-in to having an
inventory. Keys should only give themselves to doodads having
an inventory.
* Self.HasInventory() bool
* Self.AddItem(filename, qty)
* Self.RemoveItem(filename, qty)
* Self.HasItem(filename)
* Self.Inventory() - returns map[string]int
* Self.ClearInventory()
* Self.OnLeave(func(e)) now receives a CollideEvent as parameter
instead of the useless actor ID. Notably, e.Actor is the
leaving actor and e.Settled is always true.
Other Changes
* Play Mode: if playing as a character which doesn't obey gravity,
such as the bird, antigravity controls are enabled by default.
If you `import antigravity` you can turn gravity back on.
* Doodad collision scripts are no longer run in parallel
goroutines. It made the Thief's job difficult trying to steal
items in many threads simultaneously!
2021-08-10 05:42:22 +00:00
|
|
|
// wg.Add(1)
|
|
|
|
//go
|
|
|
|
func(i int, a *Actor) {
|
|
|
|
// defer wg.Done()
|
2019-05-29 04:43:30 +00:00
|
|
|
originalPositions[a.ID()] = a.Position()
|
|
|
|
|
|
|
|
// Advance any animations for this actor.
|
|
|
|
if a.activeAnimation != nil && a.activeAnimation.nextFrameAt.Before(now) {
|
|
|
|
if done := a.TickAnimation(a.activeAnimation); done {
|
2019-12-31 02:13:28 +00:00
|
|
|
// Animation has finished, get the callback function.
|
|
|
|
callback := a.animationCallback
|
2019-05-29 04:43:30 +00:00
|
|
|
|
2019-12-31 02:13:28 +00:00
|
|
|
// Clean up the animation state, in case the callback wants
|
|
|
|
// to immediately play another animation.
|
2019-05-29 04:43:30 +00:00
|
|
|
a.StopAnimation()
|
2019-12-31 02:13:28 +00:00
|
|
|
|
|
|
|
// Call the callback function.
|
2022-01-17 04:09:27 +00:00
|
|
|
if function, ok := goja.AssertFunction(callback); ok {
|
|
|
|
function(goja.Undefined())
|
2019-12-31 02:13:28 +00:00
|
|
|
}
|
|
|
|
|
2019-05-29 04:43:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the actor's velocity to see if it's moving this tick.
|
|
|
|
v := a.Velocity()
|
2020-04-05 04:00:32 +00:00
|
|
|
|
|
|
|
// Apply gravity to the actor's velocity.
|
|
|
|
if a.hasGravity && !a.Grounded() { //v.Y >= 0 {
|
|
|
|
if !a.Grounded() {
|
2022-05-06 04:35:32 +00:00
|
|
|
var gravity = balance.Gravity
|
|
|
|
if a.IsWet() {
|
|
|
|
gravity = balance.SwimGravity
|
|
|
|
}
|
2020-04-05 04:00:32 +00:00
|
|
|
v.Y = physics.Lerp(
|
2022-05-06 04:35:32 +00:00
|
|
|
v.Y, // current speed
|
|
|
|
gravity, // target max gravity falling downwards
|
2021-06-03 05:18:25 +00:00
|
|
|
balance.GravityAcceleration,
|
2020-04-05 04:00:32 +00:00
|
|
|
)
|
|
|
|
} else {
|
|
|
|
v.Y = 0
|
|
|
|
}
|
|
|
|
a.SetVelocity(v)
|
|
|
|
// v.Y += balance.Gravity
|
2019-05-29 04:43:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// If not moving, grab the bounding box right now.
|
2020-04-05 04:00:32 +00:00
|
|
|
if v.IsZero() {
|
|
|
|
boxes[i] = collision.GetBoundingRect(a)
|
2019-05-29 04:43:30 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a delta point from their current location to where they
|
|
|
|
// want to move to this tick.
|
2020-04-05 04:00:32 +00:00
|
|
|
delta := physics.VectorFromPoint(a.Position())
|
2019-05-29 04:43:30 +00:00
|
|
|
delta.Add(v)
|
|
|
|
|
|
|
|
// Check collision with level geometry.
|
2020-04-05 04:00:32 +00:00
|
|
|
chkPoint := delta.ToPoint()
|
2022-05-06 04:35:32 +00:00
|
|
|
info, _ := collision.CollidesWithGrid(a, w.chunks, chkPoint)
|
|
|
|
|
|
|
|
// Inform the caller about the collision state every tick
|
|
|
|
if w.OnLevelCollision != nil {
|
|
|
|
w.OnLevelCollision(a, info)
|
2019-05-29 04:43:30 +00:00
|
|
|
}
|
2020-01-03 04:23:27 +00:00
|
|
|
|
|
|
|
// Move us back where the collision check put us
|
|
|
|
if !a.noclip {
|
2020-04-05 04:00:32 +00:00
|
|
|
delta = physics.VectorFromPoint(info.MoveTo)
|
2020-01-03 04:23:27 +00:00
|
|
|
}
|
2019-05-29 04:43:30 +00:00
|
|
|
|
|
|
|
// Move the actor's World Position to the new location.
|
2020-04-05 04:00:32 +00:00
|
|
|
a.MoveTo(delta.ToPoint())
|
2019-05-29 04:43:30 +00:00
|
|
|
|
|
|
|
// Keep the actor from leaving the world borders of bounded maps.
|
|
|
|
w.loopContainActorsInsideLevel(a)
|
|
|
|
|
|
|
|
// Store this actor's bounding box after they've moved.
|
2021-06-03 03:41:53 +00:00
|
|
|
boxes[i] = collision.SizePlusHitbox(collision.GetBoundingRect(a), a.Hitbox())
|
2019-05-29 04:43:30 +00:00
|
|
|
}(i, a)
|
Thief and Inventory APIs
This commit adds the Thief character with starter graphics
(no animations).
The Thief walks back and forth and will steal items from other
doodads, including the player. For singleton items that have no
quantity, like the Colored Keys, the Thief will only steal one
if he does not already have it. Quantitied items like the
Small Key are always stolen.
Flexibility in the playable character is introduced: Boy,
Azulian, Bird, and Thief all respond to playable controls.
There is not currently a method to enable these apart from
modifying balance.PlayerCharacterDoodad at compile time.
New and Changed Doodads
* Thief: new doodad that walks back and forth and will steal
items from other characters inventory.
* Bird: has no inventory and cannot pick up items, unless player
controlled. Its hitbox has also been fixed so it collides with
floors correctly - not something normally seen in the Bird.
* Boy: opts in to have inventory.
* Keys (all): only gives themselves to actors having inventories.
JavaScript API - New functions available
* Self.IsPlayer() - returns if the current actor IS the player.
* Self.SetInventory(bool) - doodads must opt-in to having an
inventory. Keys should only give themselves to doodads having
an inventory.
* Self.HasInventory() bool
* Self.AddItem(filename, qty)
* Self.RemoveItem(filename, qty)
* Self.HasItem(filename)
* Self.Inventory() - returns map[string]int
* Self.ClearInventory()
* Self.OnLeave(func(e)) now receives a CollideEvent as parameter
instead of the useless actor ID. Notably, e.Actor is the
leaving actor and e.Settled is always true.
Other Changes
* Play Mode: if playing as a character which doesn't obey gravity,
such as the bird, antigravity controls are enabled by default.
If you `import antigravity` you can turn gravity back on.
* Doodad collision scripts are no longer run in parallel
goroutines. It made the Thief's job difficult trying to steal
items in many threads simultaneously!
2021-08-10 05:42:22 +00:00
|
|
|
// wg.Wait()
|
2019-05-29 04:43:30 +00:00
|
|
|
}
|
|
|
|
|
Thief and Inventory APIs
This commit adds the Thief character with starter graphics
(no animations).
The Thief walks back and forth and will steal items from other
doodads, including the player. For singleton items that have no
quantity, like the Colored Keys, the Thief will only steal one
if he does not already have it. Quantitied items like the
Small Key are always stolen.
Flexibility in the playable character is introduced: Boy,
Azulian, Bird, and Thief all respond to playable controls.
There is not currently a method to enable these apart from
modifying balance.PlayerCharacterDoodad at compile time.
New and Changed Doodads
* Thief: new doodad that walks back and forth and will steal
items from other characters inventory.
* Bird: has no inventory and cannot pick up items, unless player
controlled. Its hitbox has also been fixed so it collides with
floors correctly - not something normally seen in the Bird.
* Boy: opts in to have inventory.
* Keys (all): only gives themselves to actors having inventories.
JavaScript API - New functions available
* Self.IsPlayer() - returns if the current actor IS the player.
* Self.SetInventory(bool) - doodads must opt-in to having an
inventory. Keys should only give themselves to doodads having
an inventory.
* Self.HasInventory() bool
* Self.AddItem(filename, qty)
* Self.RemoveItem(filename, qty)
* Self.HasItem(filename)
* Self.Inventory() - returns map[string]int
* Self.ClearInventory()
* Self.OnLeave(func(e)) now receives a CollideEvent as parameter
instead of the useless actor ID. Notably, e.Actor is the
leaving actor and e.Settled is always true.
Other Changes
* Play Mode: if playing as a character which doesn't obey gravity,
such as the bird, antigravity controls are enabled by default.
If you `import antigravity` you can turn gravity back on.
* Doodad collision scripts are no longer run in parallel
goroutines. It made the Thief's job difficult trying to steal
items in many threads simultaneously!
2021-08-10 05:42:22 +00:00
|
|
|
var collidingActors = map[*Actor]*Actor{}
|
2019-05-29 04:43:30 +00:00
|
|
|
for tuple := range collision.BetweenBoxes(boxes) {
|
|
|
|
a, b := w.actors[tuple.A], w.actors[tuple.B]
|
2020-01-03 01:58:22 +00:00
|
|
|
|
|
|
|
// If neither actor is mobile, don't run collision handlers.
|
|
|
|
if !(a.IsMobile() || b.IsMobile()) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
Thief and Inventory APIs
This commit adds the Thief character with starter graphics
(no animations).
The Thief walks back and forth and will steal items from other
doodads, including the player. For singleton items that have no
quantity, like the Colored Keys, the Thief will only steal one
if he does not already have it. Quantitied items like the
Small Key are always stolen.
Flexibility in the playable character is introduced: Boy,
Azulian, Bird, and Thief all respond to playable controls.
There is not currently a method to enable these apart from
modifying balance.PlayerCharacterDoodad at compile time.
New and Changed Doodads
* Thief: new doodad that walks back and forth and will steal
items from other characters inventory.
* Bird: has no inventory and cannot pick up items, unless player
controlled. Its hitbox has also been fixed so it collides with
floors correctly - not something normally seen in the Bird.
* Boy: opts in to have inventory.
* Keys (all): only gives themselves to actors having inventories.
JavaScript API - New functions available
* Self.IsPlayer() - returns if the current actor IS the player.
* Self.SetInventory(bool) - doodads must opt-in to having an
inventory. Keys should only give themselves to doodads having
an inventory.
* Self.HasInventory() bool
* Self.AddItem(filename, qty)
* Self.RemoveItem(filename, qty)
* Self.HasItem(filename)
* Self.Inventory() - returns map[string]int
* Self.ClearInventory()
* Self.OnLeave(func(e)) now receives a CollideEvent as parameter
instead of the useless actor ID. Notably, e.Actor is the
leaving actor and e.Settled is always true.
Other Changes
* Play Mode: if playing as a character which doesn't obey gravity,
such as the bird, antigravity controls are enabled by default.
If you `import antigravity` you can turn gravity back on.
* Doodad collision scripts are no longer run in parallel
goroutines. It made the Thief's job difficult trying to steal
items in many threads simultaneously!
2021-08-10 05:42:22 +00:00
|
|
|
collidingActors[a] = b
|
2019-05-29 04:43:30 +00:00
|
|
|
|
2020-04-05 04:00:32 +00:00
|
|
|
// log.Error("between boxes: %+v <%s> <%s>", tuple, a.ID(), b.ID())
|
|
|
|
|
Improve Collision Detection: More Active w/ Actors
* Improve the collision detection algorithm so that Actor OnCollide
scripts get called more often WHILE an actor is moving, to prevent a
fast-moving actor from zipping right through the "solid" hitbox and
not giving the subject actor time to protest the movement.
* It's implemented by adding a `Settled` boolean to the OnCollide event
object. When the game is testing out movement, Settled=false to give
the actor a chance to say "I'm solid!" and have the moving party be
stopped early.
* After all this is done, for any pair of actors still with overlapping
hitboxes, OnCollide is called one last time with Settled=true. This is
when the actor should run its actions (like publishing messages to
other actors, changing state as in a trapdoor, etc.)
* The new collision detection algorithm works as follows:
* Stage 1 is the same as before, all mobile actors are moved and
tested against level geometry. They record their Original and New
position during this phase.
* Stage 2 is where we re-run that movement but ping actors being
intersected each step of the way. We trace the steps between
Original and New position, test OnCollide handler, and if it returns
false we move the mobile actor to the Last Good Position along the
trace.
* Stage 3 we run the final OnCollide(Settled=true) to let actors run
actions they wanted to for their collide handler, WITHOUT spamming
those actions during Stage 2.
* This should now allow for tweaking of gravity speed and player speed
without breaking all actor collision checking.
2019-07-17 04:07:38 +00:00
|
|
|
// Call the OnCollide handler for A informing them of B's intersection.
|
2019-05-29 04:43:30 +00:00
|
|
|
if w.scripting != nil {
|
Improve Collision Detection: More Active w/ Actors
* Improve the collision detection algorithm so that Actor OnCollide
scripts get called more often WHILE an actor is moving, to prevent a
fast-moving actor from zipping right through the "solid" hitbox and
not giving the subject actor time to protest the movement.
* It's implemented by adding a `Settled` boolean to the OnCollide event
object. When the game is testing out movement, Settled=false to give
the actor a chance to say "I'm solid!" and have the moving party be
stopped early.
* After all this is done, for any pair of actors still with overlapping
hitboxes, OnCollide is called one last time with Settled=true. This is
when the actor should run its actions (like publishing messages to
other actors, changing state as in a trapdoor, etc.)
* The new collision detection algorithm works as follows:
* Stage 1 is the same as before, all mobile actors are moved and
tested against level geometry. They record their Original and New
position during this phase.
* Stage 2 is where we re-run that movement but ping actors being
intersected each step of the way. We trace the steps between
Original and New position, test OnCollide handler, and if it returns
false we move the mobile actor to the Last Good Position along the
trace.
* Stage 3 we run the final OnCollide(Settled=true) to let actors run
actions they wanted to for their collide handler, WITHOUT spamming
those actions during Stage 2.
* This should now allow for tweaking of gravity speed and player speed
without breaking all actor collision checking.
2019-07-17 04:07:38 +00:00
|
|
|
var (
|
2021-06-03 03:41:53 +00:00
|
|
|
rect = collision.SizePlusHitbox(collision.GetBoundingRect(b), b.Hitbox())
|
2019-12-31 02:13:28 +00:00
|
|
|
lastGoodBox = render.Rect{
|
|
|
|
X: originalPositions[b.ID()].X,
|
|
|
|
Y: originalPositions[b.ID()].Y,
|
|
|
|
W: boxes[tuple.B].W,
|
|
|
|
H: boxes[tuple.B].H,
|
|
|
|
}
|
Improve Collision Detection: More Active w/ Actors
* Improve the collision detection algorithm so that Actor OnCollide
scripts get called more often WHILE an actor is moving, to prevent a
fast-moving actor from zipping right through the "solid" hitbox and
not giving the subject actor time to protest the movement.
* It's implemented by adding a `Settled` boolean to the OnCollide event
object. When the game is testing out movement, Settled=false to give
the actor a chance to say "I'm solid!" and have the moving party be
stopped early.
* After all this is done, for any pair of actors still with overlapping
hitboxes, OnCollide is called one last time with Settled=true. This is
when the actor should run its actions (like publishing messages to
other actors, changing state as in a trapdoor, etc.)
* The new collision detection algorithm works as follows:
* Stage 1 is the same as before, all mobile actors are moved and
tested against level geometry. They record their Original and New
position during this phase.
* Stage 2 is where we re-run that movement but ping actors being
intersected each step of the way. We trace the steps between
Original and New position, test OnCollide handler, and if it returns
false we move the mobile actor to the Last Good Position along the
trace.
* Stage 3 we run the final OnCollide(Settled=true) to let actors run
actions they wanted to for their collide handler, WITHOUT spamming
those actions during Stage 2.
* This should now allow for tweaking of gravity speed and player speed
without breaking all actor collision checking.
2019-07-17 04:07:38 +00:00
|
|
|
)
|
|
|
|
|
2020-01-03 06:05:49 +00:00
|
|
|
// 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
|
|
|
|
|
Improve Collision Detection: More Active w/ Actors
* Improve the collision detection algorithm so that Actor OnCollide
scripts get called more often WHILE an actor is moving, to prevent a
fast-moving actor from zipping right through the "solid" hitbox and
not giving the subject actor time to protest the movement.
* It's implemented by adding a `Settled` boolean to the OnCollide event
object. When the game is testing out movement, Settled=false to give
the actor a chance to say "I'm solid!" and have the moving party be
stopped early.
* After all this is done, for any pair of actors still with overlapping
hitboxes, OnCollide is called one last time with Settled=true. This is
when the actor should run its actions (like publishing messages to
other actors, changing state as in a trapdoor, etc.)
* The new collision detection algorithm works as follows:
* Stage 1 is the same as before, all mobile actors are moved and
tested against level geometry. They record their Original and New
position during this phase.
* Stage 2 is where we re-run that movement but ping actors being
intersected each step of the way. We trace the steps between
Original and New position, test OnCollide handler, and if it returns
false we move the mobile actor to the Last Good Position along the
trace.
* Stage 3 we run the final OnCollide(Settled=true) to let actors run
actions they wanted to for their collide handler, WITHOUT spamming
those actions during Stage 2.
* This should now allow for tweaking of gravity speed and player speed
without breaking all actor collision checking.
2019-07-17 04:07:38 +00:00
|
|
|
// 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.
|
|
|
|
|
2019-12-31 02:13:28 +00:00
|
|
|
// Special case for when a mobile actor lands ON TOP OF a solid
|
|
|
|
// actor. We want to stop their Y movement downwards, but allow
|
|
|
|
// horizontal movement on the X axis.
|
|
|
|
// Touching the solid actor from the side is already fine.
|
|
|
|
var onTop = false
|
|
|
|
|
2020-01-03 06:05:49 +00:00
|
|
|
var (
|
|
|
|
lockX int
|
|
|
|
lockY int
|
|
|
|
)
|
|
|
|
|
Improve Collision Detection: More Active w/ Actors
* Improve the collision detection algorithm so that Actor OnCollide
scripts get called more often WHILE an actor is moving, to prevent a
fast-moving actor from zipping right through the "solid" hitbox and
not giving the subject actor time to protest the movement.
* It's implemented by adding a `Settled` boolean to the OnCollide event
object. When the game is testing out movement, Settled=false to give
the actor a chance to say "I'm solid!" and have the moving party be
stopped early.
* After all this is done, for any pair of actors still with overlapping
hitboxes, OnCollide is called one last time with Settled=true. This is
when the actor should run its actions (like publishing messages to
other actors, changing state as in a trapdoor, etc.)
* The new collision detection algorithm works as follows:
* Stage 1 is the same as before, all mobile actors are moved and
tested against level geometry. They record their Original and New
position during this phase.
* Stage 2 is where we re-run that movement but ping actors being
intersected each step of the way. We trace the steps between
Original and New position, test OnCollide handler, and if it returns
false we move the mobile actor to the Last Good Position along the
trace.
* Stage 3 we run the final OnCollide(Settled=true) to let actors run
actions they wanted to for their collide handler, WITHOUT spamming
those actions during Stage 2.
* This should now allow for tweaking of gravity speed and player speed
without breaking all actor collision checking.
2019-07-17 04:07:38 +00:00
|
|
|
for point := range render.IterLine(
|
|
|
|
origPoint,
|
|
|
|
b.Position(),
|
|
|
|
) {
|
2020-01-03 06:05:49 +00:00
|
|
|
point := point
|
Improve Collision Detection: More Active w/ Actors
* Improve the collision detection algorithm so that Actor OnCollide
scripts get called more often WHILE an actor is moving, to prevent a
fast-moving actor from zipping right through the "solid" hitbox and
not giving the subject actor time to protest the movement.
* It's implemented by adding a `Settled` boolean to the OnCollide event
object. When the game is testing out movement, Settled=false to give
the actor a chance to say "I'm solid!" and have the moving party be
stopped early.
* After all this is done, for any pair of actors still with overlapping
hitboxes, OnCollide is called one last time with Settled=true. This is
when the actor should run its actions (like publishing messages to
other actors, changing state as in a trapdoor, etc.)
* The new collision detection algorithm works as follows:
* Stage 1 is the same as before, all mobile actors are moved and
tested against level geometry. They record their Original and New
position during this phase.
* Stage 2 is where we re-run that movement but ping actors being
intersected each step of the way. We trace the steps between
Original and New position, test OnCollide handler, and if it returns
false we move the mobile actor to the Last Good Position along the
trace.
* Stage 3 we run the final OnCollide(Settled=true) to let actors run
actions they wanted to for their collide handler, WITHOUT spamming
those actions during Stage 2.
* This should now allow for tweaking of gravity speed and player speed
without breaking all actor collision checking.
2019-07-17 04:07:38 +00:00
|
|
|
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 {
|
2019-12-31 02:13:28 +00:00
|
|
|
// Are they on top?
|
2021-06-03 03:41:53 +00:00
|
|
|
aHitbox := collision.SizePlusHitbox(collision.GetBoundingRect(a), a.Hitbox())
|
2020-01-03 06:05:49 +00:00
|
|
|
if render.AbsInt(test.Y+test.H-aHitbox.Y) == 0 {
|
2021-08-09 04:54:37 +00:00
|
|
|
// log.Error("ActorCollision: onTop=true at Y=%s", test.Y)
|
2019-12-31 02:13:28 +00:00
|
|
|
onTop = true
|
2020-01-03 06:05:49 +00:00
|
|
|
onTopY = test.Y
|
2019-12-31 02:13:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// What direction were we moving?
|
|
|
|
if test.Y != lastGoodBox.Y {
|
2020-01-03 06:05:49 +00:00
|
|
|
if lockY == 0 {
|
|
|
|
lockY = lastGoodBox.Y
|
|
|
|
}
|
|
|
|
if onTop {
|
2021-08-09 04:54:37 +00:00
|
|
|
// log.Error("ActorCollision: setGrounded(true)", test.Y)
|
2020-01-03 06:05:49 +00:00
|
|
|
b.SetGrounded(true)
|
|
|
|
}
|
2019-12-31 02:13:28 +00:00
|
|
|
}
|
|
|
|
if test.X != lastGoodBox.X {
|
|
|
|
if !onTop {
|
2020-01-03 06:05:49 +00:00
|
|
|
lockX = lastGoodBox.X
|
2019-12-31 02:13:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-03 06:05:49 +00:00
|
|
|
// Move them back to the last good box.
|
2019-12-31 02:13:28 +00:00
|
|
|
lastGoodBox = test
|
2020-01-03 06:05:49 +00:00
|
|
|
if lockX != 0 {
|
|
|
|
lastGoodBox.X = lockX
|
2019-12-31 02:13:28 +00:00
|
|
|
}
|
Improve Collision Detection: More Active w/ Actors
* Improve the collision detection algorithm so that Actor OnCollide
scripts get called more often WHILE an actor is moving, to prevent a
fast-moving actor from zipping right through the "solid" hitbox and
not giving the subject actor time to protest the movement.
* It's implemented by adding a `Settled` boolean to the OnCollide event
object. When the game is testing out movement, Settled=false to give
the actor a chance to say "I'm solid!" and have the moving party be
stopped early.
* After all this is done, for any pair of actors still with overlapping
hitboxes, OnCollide is called one last time with Settled=true. This is
when the actor should run its actions (like publishing messages to
other actors, changing state as in a trapdoor, etc.)
* The new collision detection algorithm works as follows:
* Stage 1 is the same as before, all mobile actors are moved and
tested against level geometry. They record their Original and New
position during this phase.
* Stage 2 is where we re-run that movement but ping actors being
intersected each step of the way. We trace the steps between
Original and New position, test OnCollide handler, and if it returns
false we move the mobile actor to the Last Good Position along the
trace.
* Stage 3 we run the final OnCollide(Settled=true) to let actors run
actions they wanted to for their collide handler, WITHOUT spamming
those actions during Stage 2.
* This should now allow for tweaking of gravity speed and player speed
without breaking all actor collision checking.
2019-07-17 04:07:38 +00:00
|
|
|
} else {
|
2022-01-19 05:24:36 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Error("RunCollide on %s (%s) errored: %s", a.ID(), a.Actor.Filename, err)
|
|
|
|
}
|
2020-01-03 06:05:49 +00:00
|
|
|
// Move them back to the last good box.
|
Improve Collision Detection: More Active w/ Actors
* Improve the collision detection algorithm so that Actor OnCollide
scripts get called more often WHILE an actor is moving, to prevent a
fast-moving actor from zipping right through the "solid" hitbox and
not giving the subject actor time to protest the movement.
* It's implemented by adding a `Settled` boolean to the OnCollide event
object. When the game is testing out movement, Settled=false to give
the actor a chance to say "I'm solid!" and have the moving party be
stopped early.
* After all this is done, for any pair of actors still with overlapping
hitboxes, OnCollide is called one last time with Settled=true. This is
when the actor should run its actions (like publishing messages to
other actors, changing state as in a trapdoor, etc.)
* The new collision detection algorithm works as follows:
* Stage 1 is the same as before, all mobile actors are moved and
tested against level geometry. They record their Original and New
position during this phase.
* Stage 2 is where we re-run that movement but ping actors being
intersected each step of the way. We trace the steps between
Original and New position, test OnCollide handler, and if it returns
false we move the mobile actor to the Last Good Position along the
trace.
* Stage 3 we run the final OnCollide(Settled=true) to let actors run
actions they wanted to for their collide handler, WITHOUT spamming
those actions during Stage 2.
* This should now allow for tweaking of gravity speed and player speed
without breaking all actor collision checking.
2019-07-17 04:07:38 +00:00
|
|
|
lastGoodBox = test
|
2019-05-29 04:43:30 +00:00
|
|
|
}
|
2020-01-03 06:05:49 +00:00
|
|
|
} else {
|
|
|
|
// No collision between boxes, increment the lastGoodBox
|
|
|
|
lastGoodBox = test
|
2019-05-29 04:43:30 +00:00
|
|
|
}
|
Improve Collision Detection: More Active w/ Actors
* Improve the collision detection algorithm so that Actor OnCollide
scripts get called more often WHILE an actor is moving, to prevent a
fast-moving actor from zipping right through the "solid" hitbox and
not giving the subject actor time to protest the movement.
* It's implemented by adding a `Settled` boolean to the OnCollide event
object. When the game is testing out movement, Settled=false to give
the actor a chance to say "I'm solid!" and have the moving party be
stopped early.
* After all this is done, for any pair of actors still with overlapping
hitboxes, OnCollide is called one last time with Settled=true. This is
when the actor should run its actions (like publishing messages to
other actors, changing state as in a trapdoor, etc.)
* The new collision detection algorithm works as follows:
* Stage 1 is the same as before, all mobile actors are moved and
tested against level geometry. They record their Original and New
position during this phase.
* Stage 2 is where we re-run that movement but ping actors being
intersected each step of the way. We trace the steps between
Original and New position, test OnCollide handler, and if it returns
false we move the mobile actor to the Last Good Position along the
trace.
* Stage 3 we run the final OnCollide(Settled=true) to let actors run
actions they wanted to for their collide handler, WITHOUT spamming
those actions during Stage 2.
* This should now allow for tweaking of gravity speed and player speed
without breaking all actor collision checking.
2019-07-17 04:07:38 +00:00
|
|
|
}
|
|
|
|
|
2020-01-03 06:05:49 +00:00
|
|
|
// Did we lock their X or Y coordinate from moving further?
|
|
|
|
if lockY != 0 {
|
|
|
|
lastGoodBox.Y = lockY
|
|
|
|
}
|
|
|
|
if lockX != 0 {
|
|
|
|
lastGoodBox.X = lockX
|
|
|
|
}
|
|
|
|
|
2020-01-03 04:23:27 +00:00
|
|
|
if !b.noclip {
|
|
|
|
b.MoveTo(lastGoodBox.Point())
|
|
|
|
}
|
Improve Collision Detection: More Active w/ Actors
* Improve the collision detection algorithm so that Actor OnCollide
scripts get called more often WHILE an actor is moving, to prevent a
fast-moving actor from zipping right through the "solid" hitbox and
not giving the subject actor time to protest the movement.
* It's implemented by adding a `Settled` boolean to the OnCollide event
object. When the game is testing out movement, Settled=false to give
the actor a chance to say "I'm solid!" and have the moving party be
stopped early.
* After all this is done, for any pair of actors still with overlapping
hitboxes, OnCollide is called one last time with Settled=true. This is
when the actor should run its actions (like publishing messages to
other actors, changing state as in a trapdoor, etc.)
* The new collision detection algorithm works as follows:
* Stage 1 is the same as before, all mobile actors are moved and
tested against level geometry. They record their Original and New
position during this phase.
* Stage 2 is where we re-run that movement but ping actors being
intersected each step of the way. We trace the steps between
Original and New position, test OnCollide handler, and if it returns
false we move the mobile actor to the Last Good Position along the
trace.
* Stage 3 we run the final OnCollide(Settled=true) to let actors run
actions they wanted to for their collide handler, WITHOUT spamming
those actions during Stage 2.
* This should now allow for tweaking of gravity speed and player speed
without breaking all actor collision checking.
2019-07-17 04:07:38 +00:00
|
|
|
} else {
|
|
|
|
log.Error(
|
|
|
|
"ERROR: Actors %s and %s overlap and the script returned false,"+
|
|
|
|
"but I didn't store %s original position earlier??",
|
2020-04-05 04:00:32 +00:00
|
|
|
a.Doodad().Title, b.Doodad().Title, b.Doodad().Title,
|
Improve Collision Detection: More Active w/ Actors
* Improve the collision detection algorithm so that Actor OnCollide
scripts get called more often WHILE an actor is moving, to prevent a
fast-moving actor from zipping right through the "solid" hitbox and
not giving the subject actor time to protest the movement.
* It's implemented by adding a `Settled` boolean to the OnCollide event
object. When the game is testing out movement, Settled=false to give
the actor a chance to say "I'm solid!" and have the moving party be
stopped early.
* After all this is done, for any pair of actors still with overlapping
hitboxes, OnCollide is called one last time with Settled=true. This is
when the actor should run its actions (like publishing messages to
other actors, changing state as in a trapdoor, etc.)
* The new collision detection algorithm works as follows:
* Stage 1 is the same as before, all mobile actors are moved and
tested against level geometry. They record their Original and New
position during this phase.
* Stage 2 is where we re-run that movement but ping actors being
intersected each step of the way. We trace the steps between
Original and New position, test OnCollide handler, and if it returns
false we move the mobile actor to the Last Good Position along the
trace.
* Stage 3 we run the final OnCollide(Settled=true) to let actors run
actions they wanted to for their collide handler, WITHOUT spamming
those actions during Stage 2.
* This should now allow for tweaking of gravity speed and player speed
without breaking all actor collision checking.
2019-07-17 04:07:38 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2020-01-03 06:05:49 +00:00
|
|
|
if onTopY != 0 && lastGoodBox.Y-onTopY <= 1 {
|
|
|
|
lastGoodBox.Y = onTopY
|
|
|
|
}
|
|
|
|
|
Improve Collision Detection: More Active w/ Actors
* Improve the collision detection algorithm so that Actor OnCollide
scripts get called more often WHILE an actor is moving, to prevent a
fast-moving actor from zipping right through the "solid" hitbox and
not giving the subject actor time to protest the movement.
* It's implemented by adding a `Settled` boolean to the OnCollide event
object. When the game is testing out movement, Settled=false to give
the actor a chance to say "I'm solid!" and have the moving party be
stopped early.
* After all this is done, for any pair of actors still with overlapping
hitboxes, OnCollide is called one last time with Settled=true. This is
when the actor should run its actions (like publishing messages to
other actors, changing state as in a trapdoor, etc.)
* The new collision detection algorithm works as follows:
* Stage 1 is the same as before, all mobile actors are moved and
tested against level geometry. They record their Original and New
position during this phase.
* Stage 2 is where we re-run that movement but ping actors being
intersected each step of the way. We trace the steps between
Original and New position, test OnCollide handler, and if it returns
false we move the mobile actor to the Last Good Position along the
trace.
* Stage 3 we run the final OnCollide(Settled=true) to let actors run
actions they wanted to for their collide handler, WITHOUT spamming
those actions during Stage 2.
* This should now allow for tweaking of gravity speed and player speed
without breaking all actor collision checking.
2019-07-17 04:07:38 +00:00
|
|
|
// 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 {
|
2020-01-03 01:58:22 +00:00
|
|
|
log.Error("VM(%s).RunCollide: %s", a.ID(), err.Error())
|
2019-05-29 04:43:30 +00:00
|
|
|
}
|
2021-01-03 23:19:21 +00:00
|
|
|
|
|
|
|
// If the (player) is pressing the Use key, call the colliding
|
|
|
|
// actor's OnUse event.
|
|
|
|
if b.flagUsing {
|
|
|
|
if err := w.scripting.To(a.ID()).Events.RunUse(&UseEvent{
|
|
|
|
Actor: b,
|
|
|
|
}); err != nil {
|
|
|
|
log.Error("VM(%s).RunUse: %s", a.ID(), err.Error())
|
|
|
|
}
|
|
|
|
}
|
2019-05-29 04:43:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for lacks of collisions since last frame.
|
Thief and Inventory APIs
This commit adds the Thief character with starter graphics
(no animations).
The Thief walks back and forth and will steal items from other
doodads, including the player. For singleton items that have no
quantity, like the Colored Keys, the Thief will only steal one
if he does not already have it. Quantitied items like the
Small Key are always stolen.
Flexibility in the playable character is introduced: Boy,
Azulian, Bird, and Thief all respond to playable controls.
There is not currently a method to enable these apart from
modifying balance.PlayerCharacterDoodad at compile time.
New and Changed Doodads
* Thief: new doodad that walks back and forth and will steal
items from other characters inventory.
* Bird: has no inventory and cannot pick up items, unless player
controlled. Its hitbox has also been fixed so it collides with
floors correctly - not something normally seen in the Bird.
* Boy: opts in to have inventory.
* Keys (all): only gives themselves to actors having inventories.
JavaScript API - New functions available
* Self.IsPlayer() - returns if the current actor IS the player.
* Self.SetInventory(bool) - doodads must opt-in to having an
inventory. Keys should only give themselves to doodads having
an inventory.
* Self.HasInventory() bool
* Self.AddItem(filename, qty)
* Self.RemoveItem(filename, qty)
* Self.HasItem(filename)
* Self.Inventory() - returns map[string]int
* Self.ClearInventory()
* Self.OnLeave(func(e)) now receives a CollideEvent as parameter
instead of the useless actor ID. Notably, e.Actor is the
leaving actor and e.Settled is always true.
Other Changes
* Play Mode: if playing as a character which doesn't obey gravity,
such as the bird, antigravity controls are enabled by default.
If you `import antigravity` you can turn gravity back on.
* Doodad collision scripts are no longer run in parallel
goroutines. It made the Thief's job difficult trying to steal
items in many threads simultaneously!
2021-08-10 05:42:22 +00:00
|
|
|
for sourceActor, targetActor := range w.collidingActors {
|
|
|
|
if _, ok := collidingActors[sourceActor]; !ok {
|
|
|
|
w.scripting.To(sourceActor.ID()).Events.RunLeave(&CollideEvent{
|
|
|
|
Actor: targetActor,
|
|
|
|
Settled: true,
|
|
|
|
})
|
2019-05-29 04:43:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Store this frame's colliding actors for next frame.
|
|
|
|
w.collidingActors = collidingActors
|
|
|
|
return nil
|
|
|
|
}
|