diff --git a/dev-assets/doodads/azulian/azulian-red.js b/dev-assets/doodads/azulian/azulian-red.js index 45f5881..f42d92d 100644 --- a/dev-assets/doodads/azulian/azulian-red.js +++ b/dev-assets/doodads/azulian/azulian-red.js @@ -7,6 +7,7 @@ function main() { Self.SetHitbox(0, 0, 32, 32) Self.SetMobile(true); + Self.SetInventory(true); Self.SetGravity(true); 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"]); @@ -16,7 +17,7 @@ function main() { var sampleRate = 5; var lastSampledX = 0; - setInterval(function() { + setInterval(function () { if (sampleTick % sampleRate === 0) { var curX = Self.Position().X; var delta = Math.abs(curX - lastSampledX); @@ -33,7 +34,7 @@ function main() { Self.SetVelocity(Vector(Vx, 0.0)); if (!Self.IsAnimating()) { - Self.PlayAnimation("walk-"+direction, null); + Self.PlayAnimation("walk-" + direction, null); } }, 100); } diff --git a/dev-assets/doodads/azulian/azulian.js b/dev-assets/doodads/azulian/azulian.js index a01881c..3aa7637 100644 --- a/dev-assets/doodads/azulian/azulian.js +++ b/dev-assets/doodads/azulian/azulian.js @@ -9,11 +9,12 @@ function main() { Self.SetMobile(true); Self.SetGravity(true); + Self.SetInventory(true); Self.SetHitbox(7, 4, 17, 28); Self.AddAnimation("walk-left", 100, ["blu-wl1", "blu-wl2", "blu-wl3", "blu-wl4"]); Self.AddAnimation("walk-right", 100, ["blu-wr1", "blu-wr2", "blu-wr3", "blu-wr4"]); - Events.OnKeypress(function(ev) { + Events.OnKeypress(function (ev) { Vx = 0; Vy = 0; diff --git a/dev-assets/doodads/bird/bird.js b/dev-assets/doodads/bird/bird.js index d7bb2a7..e6d38c8 100644 --- a/dev-assets/doodads/bird/bird.js +++ b/dev-assets/doodads/bird/bird.js @@ -1,4 +1,5 @@ -// Red bird mob. +// Bird + function main() { var speed = 4; var Vx = Vy = 0; @@ -13,11 +14,16 @@ function main() { Self.SetMobile(true); Self.SetGravity(false); - Self.SetHitbox(0, 10, 46, 32); + Self.SetHitbox(0, 0, 46, 32); Self.AddAnimation("fly-left", 100, ["left-1", "left-2"]); Self.AddAnimation("fly-right", 100, ["right-1", "right-2"]); - Events.OnCollide(function(e) { + // Player Character controls? + if (Self.IsPlayer()) { + return player(); + } + + Events.OnCollide(function (e) { if (e.Actor.IsMobile() && e.InHitbox) { return false; } @@ -29,7 +35,7 @@ function main() { var lastSampledX = 0; var lastSampledY = 0; - setInterval(function() { + setInterval(function () { if (sampleTick % sampleRate === 0) { var curX = Self.Position().X; var delta = Math.abs(curX - lastSampledX); @@ -46,7 +52,39 @@ function main() { Self.SetVelocity(Vector(Vx, 0.0)); if (!Self.IsAnimating()) { - Self.PlayAnimation("fly-"+direction, null); + Self.PlayAnimation("fly-" + direction, null); } }, 100); } + +// If under control of the player character. +function player() { + Self.SetInventory(true); + Events.OnKeypress(function (ev) { + Vx = 0; + Vy = 0; + + if (ev.Up) { + Vy = -playerSpeed; + } else if (ev.Down) { + Vy = playerSpeed; + } + + if (ev.Right) { + if (!Self.IsAnimating()) { + Self.PlayAnimation("fly-right", null); + } + Vx = playerSpeed; + } else if (ev.Left) { + if (!Self.IsAnimating()) { + Self.PlayAnimation("fly-left", null); + } + Vx = -playerSpeed; + } else { + Self.StopAnimation(); + animating = false; + } + + Self.SetVelocity(Vector(Vx, Vy)); + }) +} \ No newline at end of file diff --git a/dev-assets/doodads/boy/boy.js b/dev-assets/doodads/boy/boy.js index 362a6c0..02f877b 100644 --- a/dev-assets/doodads/boy/boy.js +++ b/dev-assets/doodads/boy/boy.js @@ -8,12 +8,13 @@ function main() { var animFrame = animStart; Self.SetMobile(true); + Self.SetInventory(true); Self.SetGravity(true); Self.SetHitbox(0, 0, 32, 52); Self.AddAnimation("walk-left", 200, ["stand-left", "walk-left-1", "walk-left-2", "walk-left-3", "walk-left-2", "walk-left-1"]); Self.AddAnimation("walk-right", 200, ["stand-right", "walk-right-1", "walk-right-2", "walk-right-3", "walk-right-2", "walk-right-1"]); - Events.OnKeypress(function(ev) { + Events.OnKeypress(function (ev) { Vx = 0; Vy = 0; diff --git a/dev-assets/doodads/build.sh b/dev-assets/doodads/build.sh index 5597a19..9c99564 100755 --- a/dev-assets/doodads/build.sh +++ b/dev-assets/doodads/build.sh @@ -12,6 +12,10 @@ boy() { cd boy/ make cd .. + + cd thief/ + make + cd .. } buttons() { diff --git a/dev-assets/doodads/doors/keys.js b/dev-assets/doodads/doors/keys.js index 593b871..469a158 100644 --- a/dev-assets/doodads/doors/keys.js +++ b/dev-assets/doodads/doors/keys.js @@ -2,11 +2,13 @@ function main() { var color = Self.GetTag("color"); var quantity = color === "small" ? 1 : 0; - Events.OnCollide(function(e) { + Events.OnCollide(function (e) { if (e.Settled) { - Sound.Play("item-get.wav") - e.Actor.AddItem(Self.Filename, quantity); - Self.Destroy(); + if (e.Actor.HasInventory()) { + Sound.Play("item-get.wav") + e.Actor.AddItem(Self.Filename, quantity); + Self.Destroy(); + } } }) } diff --git a/dev-assets/doodads/thief/Makefile b/dev-assets/doodads/thief/Makefile new file mode 100644 index 0000000..fc6b00c --- /dev/null +++ b/dev-assets/doodads/thief/Makefile @@ -0,0 +1,11 @@ +ALL: build + +.PHONY: build +build: + doodad convert -t "Thief" stand-right.png stand-left.png \ + thief.doodad + doodad install-script thief.js thief.doodad + + doodad edit-doodad --tag "category=creatures" thief.doodad + + cp *.doodad ../../../assets/doodads/ \ No newline at end of file diff --git a/dev-assets/doodads/thief/stand-left.png b/dev-assets/doodads/thief/stand-left.png new file mode 100644 index 0000000..9e58a98 Binary files /dev/null and b/dev-assets/doodads/thief/stand-left.png differ diff --git a/dev-assets/doodads/thief/stand-right.png b/dev-assets/doodads/thief/stand-right.png new file mode 100644 index 0000000..be7a8b5 Binary files /dev/null and b/dev-assets/doodads/thief/stand-right.png differ diff --git a/dev-assets/doodads/thief/thief.js b/dev-assets/doodads/thief/thief.js new file mode 100644 index 0000000..e7a86dc --- /dev/null +++ b/dev-assets/doodads/thief/thief.js @@ -0,0 +1,104 @@ +// Thief + +function main() { + Self.SetMobile(true); + Self.SetGravity(true); + Self.SetInventory(true); + Self.SetHitbox(0, 0, 32, 58); + Self.AddAnimation("walk-left", 200, ["stand-left"]); //, "walk-left-1", "walk-left-2", "walk-left-3", "walk-left-2", "walk-left-1"]); + Self.AddAnimation("walk-right", 200, ["stand-right"]); //, "walk-right-1", "walk-right-2", "walk-right-3", "walk-right-2", "walk-right-1"]); + + // Controlled by the player character? + if (Self.IsPlayer()) { + return playable(); + } + return ai(); +} + +// Enemy Doodad AI. +function ai() { + // Walks back and forth. + var Vx = Vy = 0.0, + playerSpeed = 4, + direction = "right", + lastSampledX = 0, + sampleTick = 0, + sampleRate = 2, + stolenItems = {}; // map item->qty + + setInterval(function () { + if (sampleTick % sampleRate === 0) { + var curX = Self.Position().X, + delta = Math.abs(curX - lastSampledX); + if (delta < 5) { + direction = direction === "right" ? "left" : "right"; + } + lastSampledX = curX; + } + sampleTick++; + + Vx = parseFloat(playerSpeed * (direction === "left" ? -1 : 1)); + Self.SetVelocity(Vector(Vx, Vy)); + + Self.StopAnimation(); + Self.PlayAnimation("walk-" + direction, null); + }, 100); + + // Steals your items. + Events.OnCollide(function (e) { + if (!e.Settled) { + return; + } + + // Steal inventory + var stolen = 0; + if (e.Actor.HasInventory()) { + var myInventory = Self.Inventory(), + theirInventory = e.Actor.Inventory(); + + for (var key in theirInventory) { + if (!theirInventory.hasOwnProperty(key)) { + continue; + } + + var value = theirInventory[key]; + if (value > 0 || myInventory[key] === undefined) { + e.Actor.RemoveItem(key, value); + Self.AddItem(key, value); + stolenItems[key] = value; + stolen += (value === 0 ? 1 : value); + } + } + + // Notify the player if it was them. + if (e.Actor.IsPlayer() && stolen > 0) { + Flash("Watch out for thieves! %d item%s stolen!", parseInt(stolen), stolen === 1 ? ' was' : 's were'); + } + } + }); +} + +// If under control of the player character. +function playable() { + Events.OnKeypress(function (ev) { + Vx = 0; + Vy = 0; + + if (ev.Right) { + if (!Self.IsAnimating()) { + Self.PlayAnimation("walk-right", null); + } + Vx = playerSpeed; + } else if (ev.Left) { + if (!Self.IsAnimating()) { + Self.PlayAnimation("walk-left", null); + } + Vx = -playerSpeed; + } else { + Self.StopAnimation(); + animating = false; + } + + // Self.SetVelocity(Point(Vx, Vy)); + }) +} \ No newline at end of file diff --git a/pkg/play_scene.go b/pkg/play_scene.go index 2870a38..9df0f04 100644 --- a/pkg/play_scene.go +++ b/pkg/play_scene.go @@ -538,7 +538,7 @@ func (s *PlayScene) movePlayer(ev *event.State) { ) // Antigravity: player can move anywhere with arrow keys. - if s.antigravity { + if s.antigravity || !s.Player.HasGravity() { velocity.X = 0 velocity.Y = 0 diff --git a/pkg/scripting/pubsub.go b/pkg/scripting/pubsub.go index ae2a2fd..8324698 100644 --- a/pkg/scripting/pubsub.go +++ b/pkg/scripting/pubsub.go @@ -19,8 +19,6 @@ This adds the global methods `Message.Subscribe(name, func)` and `Message.Publish(name, args)` to the JavaScript VM's scope. */ func RegisterPublishHooks(s *Supervisor, vm *VM) { - log.Error("RegisterPublishHooks called with %+v %+v", s, vm) - // Goroutine to watch the VM's inbound channel and invoke Subscribe handlers // for any matching messages received. go func() { diff --git a/pkg/uix/actor.go b/pkg/uix/actor.go index 1756b56..970080a 100644 --- a/pkg/uix/actor.go +++ b/pkg/uix/actor.go @@ -34,14 +34,15 @@ type Actor struct { flagUsing bool // flag that the (player) has pressed the Use key. // Actor runtime variables. - hasGravity bool - isMobile bool // Mobile character, such as the player or an enemy - noclip bool // Disable collision detection - hidden bool // invisible, via Hide() and Show() - frozen bool // Frozen, via Freeze() and Unfreeze() - hitbox render.Rect - inventory map[string]int // item inventory. doodad name -> quantity, 0 for key item. - data map[string]string // arbitrary key/value store. DEPRECATED ?? + hasGravity bool + hasInventory bool + isMobile bool // Mobile character, such as the player or an enemy + noclip bool // Disable collision detection + hidden bool // invisible, via Hide() and Show() + frozen bool // Frozen, via Freeze() and Unfreeze() + hitbox render.Rect + inventory map[string]int // item inventory. doodad name -> quantity, 0 for key item. + data map[string]string // arbitrary key/value store. DEPRECATED ?? // Movement data. position render.Point @@ -114,6 +115,11 @@ func (a *Actor) SetMobile(v bool) { a.isMobile = v } +// SetInventory configures whether the actor is capable of carrying items. +func (a *Actor) SetInventory(v bool) { + a.hasInventory = true +} + // IsMobile returns whether the actor is a mobile character. func (a *Actor) IsMobile() bool { return a.isMobile @@ -125,6 +131,16 @@ func (a *Actor) IsPlayer() bool { return a.Canvas.Name == "PLAYER" } +// HasInventory returns if the actor is capable of carrying items. +func (a *Actor) HasInventory() bool { + return a.hasInventory +} + +// HasGravity returns if gravity applies to the actor. +func (a *Actor) HasGravity() bool { + return a.hasGravity +} + // Size returns the size of the actor, from the underlying doodads.Drawing. func (a *Actor) Size() render.Rect { return a.Drawing.Size() diff --git a/pkg/uix/actor_collision.go b/pkg/uix/actor_collision.go index 940acf9..60eeaf8 100644 --- a/pkg/uix/actor_collision.go +++ b/pkg/uix/actor_collision.go @@ -2,7 +2,6 @@ package uix import ( "errors" - "sync" "time" "git.kirsle.net/apps/doodle/pkg/balance" @@ -36,11 +35,14 @@ func (w *Canvas) loopActorCollision() error { // Loop over all the actors in parallel, processing their movement and // checking collision data against the level geometry. - var wg sync.WaitGroup + // NOTE: parallelism wasn't good for race conditions like the Thief + // trying to take your inventory. + // var wg sync.WaitGroup for i, a := range w.actors { - wg.Add(1) - go func(i int, a *Actor) { - defer wg.Done() + // wg.Add(1) + //go + func(i int, a *Actor) { + // defer wg.Done() originalPositions[a.ID()] = a.Position() // Advance any animations for this actor. @@ -114,10 +116,10 @@ func (w *Canvas) loopActorCollision() error { // Store this actor's bounding box after they've moved. boxes[i] = collision.SizePlusHitbox(collision.GetBoundingRect(a), a.Hitbox()) }(i, a) - wg.Wait() + // wg.Wait() } - var collidingActors = map[string]string{} + var collidingActors = map[*Actor]*Actor{} for tuple := range collision.BetweenBoxes(boxes) { a, b := w.actors[tuple.A], w.actors[tuple.B] @@ -126,7 +128,7 @@ func (w *Canvas) loopActorCollision() error { continue } - collidingActors[a.ID()] = b.ID() + collidingActors[a] = b // log.Error("between boxes: %+v <%s> <%s>", tuple, a.ID(), b.ID()) @@ -293,9 +295,12 @@ func (w *Canvas) loopActorCollision() error { } // Check for lacks of collisions since last frame. - for sourceID, targetID := range w.collidingActors { - if _, ok := collidingActors[sourceID]; !ok { - w.scripting.To(sourceID).Events.RunLeave(targetID) + for sourceActor, targetActor := range w.collidingActors { + if _, ok := collidingActors[sourceActor]; !ok { + w.scripting.To(sourceActor.ID()).Events.RunLeave(&CollideEvent{ + Actor: targetActor, + Settled: true, + }) } } diff --git a/pkg/uix/canvas.go b/pkg/uix/canvas.go index 2926336..5736539 100644 --- a/pkg/uix/canvas.go +++ b/pkg/uix/canvas.go @@ -61,7 +61,7 @@ type Canvas struct { actors []*Actor // if this canvas CONTAINS actors (i.e., is a level) // Collision memory for the actors. - collidingActors map[string]string // mapping their IDs to each other + collidingActors map[*Actor]*Actor // mapping their IDs to each other // Doodad scripting engine supervisor. // NOTE: initialized and managed by the play_scene. diff --git a/pkg/uix/scripting.go b/pkg/uix/scripting.go index 0f4a012..2aa16ac 100644 --- a/pkg/uix/scripting.go +++ b/pkg/uix/scripting.go @@ -22,13 +22,21 @@ func (w *Canvas) MakeSelfAPI(actor *Actor) map[string]interface{} { "SetHitbox": actor.SetHitbox, "SetVelocity": actor.SetVelocity, "SetMobile": actor.SetMobile, + "SetInventory": actor.SetInventory, + "HasInventory": actor.HasInventory, "SetGravity": actor.SetGravity, "AddAnimation": actor.AddAnimation, "IsAnimating": actor.IsAnimating, + "IsPlayer": actor.IsPlayer, "PlayAnimation": actor.PlayAnimation, "StopAnimation": actor.StopAnimation, "ShowLayer": actor.ShowLayer, "ShowLayerNamed": actor.ShowLayerNamed, + "Inventory": actor.Inventory, + "AddItem": actor.AddItem, + "RemoveItem": actor.RemoveItem, + "HasItem": actor.HasItem, + "ClearInventory": actor.ClearInventory, "Destroy": actor.Destroy, "GetLinks": func() []map[string]interface{} { var result = []map[string]interface{}{}