diff --git a/cmd/doodad/commands/convert.go b/cmd/doodad/commands/convert.go index a1c188e..978bcb4 100644 --- a/cmd/doodad/commands/convert.go +++ b/cmd/doodad/commands/convert.go @@ -11,11 +11,11 @@ import ( "image/png" - "git.kirsle.net/go/render" "git.kirsle.net/apps/doodle/pkg/branding" "git.kirsle.net/apps/doodle/pkg/doodads" "git.kirsle.net/apps/doodle/pkg/level" "git.kirsle.net/apps/doodle/pkg/log" + "git.kirsle.net/go/render" "github.com/urfave/cli" "golang.org/x/image/bmp" ) @@ -322,7 +322,7 @@ func imageToChunker(img image.Image, chroma render.Color, palette *level.Palette newColors[color.String()] = swatch } - chunker.Set(render.NewPoint(int32(x), int32(y)), swatch) + chunker.Set(render.NewPoint(x, y), swatch) } } diff --git a/cmd/doodad/commands/edit_level.go b/cmd/doodad/commands/edit_level.go index 0ea138b..7e219c3 100644 --- a/cmd/doodad/commands/edit_level.go +++ b/cmd/doodad/commands/edit_level.go @@ -3,9 +3,9 @@ package commands import ( "fmt" - "git.kirsle.net/go/render" "git.kirsle.net/apps/doodle/pkg/level" "git.kirsle.net/apps/doodle/pkg/log" + "git.kirsle.net/go/render" "github.com/urfave/cli" ) diff --git a/cmd/doodle/main.go b/cmd/doodle/main.go index 5949223..f6e459f 100644 --- a/cmd/doodle/main.go +++ b/cmd/doodle/main.go @@ -8,11 +8,11 @@ import ( "sort" "time" - "git.kirsle.net/go/render/sdl" doodle "git.kirsle.net/apps/doodle/pkg" "git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/bindata" "git.kirsle.net/apps/doodle/pkg/branding" + "git.kirsle.net/go/render/sdl" "github.com/urfave/cli" _ "image/png" diff --git a/dev-assets/doodads/azulian/azulian-red.js b/dev-assets/doodads/azulian/azulian-red.js index a138408..f631e40 100644 --- a/dev-assets/doodads/azulian/azulian-red.js +++ b/dev-assets/doodads/azulian/azulian-red.js @@ -7,6 +7,7 @@ function main() { var direction = "right"; + Self.SetMobile(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"]); diff --git a/dev-assets/doodads/azulian/azulian.js b/dev-assets/doodads/azulian/azulian.js index 1599b48..a01881c 100644 --- a/dev-assets/doodads/azulian/azulian.js +++ b/dev-assets/doodads/azulian/azulian.js @@ -7,6 +7,7 @@ function main() { var animStart = animEnd = 0; var animFrame = animStart; + Self.SetMobile(true); Self.SetGravity(true); Self.SetHitbox(7, 4, 17, 28); Self.AddAnimation("walk-left", 100, ["blu-wl1", "blu-wl2", "blu-wl3", "blu-wl4"]); diff --git a/dev-assets/doodads/build.sh b/dev-assets/doodads/build.sh index c50d5dd..5a72c6a 100755 --- a/dev-assets/doodads/build.sh +++ b/dev-assets/doodads/build.sh @@ -115,6 +115,33 @@ objects() { doodad convert -t "Exit Flag" exit-flag.png exit-flag.doodad doodad install-script exit-flag.js exit-flag.doodad + doodad convert -t "Start Flag" start-flag.png start-flag.doodad + + cp *.doodad ../../../assets/doodads/ + + cd ../crumbly-floor + + doodad convert -t "Crumbly Floor" floor.png shake1.png shake2.png \ + fall1.png fall2.png fall3.png fall4.png fallen.png \ + crumbly-floor.doodad + doodad install-script crumbly-floor.js crumbly-floor.doodad + cp *.doodad ../../../assets/doodads/ + + cd .. +} + +onoff() { + cd on-off/ + + doodad convert -t "State Button" blue-button.png orange-button.png state-button.doodad + doodad install-script state-button.js state-button.doodad + + doodad convert -t "State Block (Blue)" blue-on.png blue-off.png state-block-blue.doodad + doodad install-script state-block-blue.js state-block-blue.doodad + + doodad convert -t "State Block (Orange)" orange-off.png orange-on.png state-block-orange.doodad + doodad install-script state-block-orange.js state-block-orange.doodad + cp *.doodad ../../../assets/doodads/ cd .. @@ -126,5 +153,6 @@ doors trapdoors azulians objects +onoff doodad edit-doodad -quiet -lock -author "Noah" ../../assets/doodads/*.doodad doodad edit-doodad -hide ../../assets/doodads/azu-blu.doodad diff --git a/dev-assets/doodads/crumbly-floor/crumbly-floor.js b/dev-assets/doodads/crumbly-floor/crumbly-floor.js new file mode 100644 index 0000000..155be86 --- /dev/null +++ b/dev-assets/doodads/crumbly-floor/crumbly-floor.js @@ -0,0 +1,60 @@ +// Crumbly Floor. +function main() { + Self.SetHitbox(0, 0, 65, 7); + + Self.AddAnimation("shake", 100, ["shake1", "shake2", "floor", "shake1", "shake2", "floor"]); + Self.AddAnimation("fall", 100, ["fall1", "fall2", "fall3", "fall4"]); + + // Recover time for the floor to respawn. + var recover = 5000; + + // States of the floor. + var stateSolid = 0; + var stateShaking = 1; + var stateFalling = 2; + var stateFallen = 3; + var state = stateSolid; + + // Started the animation? + 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) { + return; + } + + // Floor is solid until it begins to fall. + if (e.InHitbox && (state === stateSolid || state === stateShaking)) { + // Only activate when touched from the top. + if (e.Overlap.Y > 0) { + return false; + } + + // Begin the animation sequence if we're in the solid state. + if (state === stateSolid) { + state = stateShaking; + Self.PlayAnimation("shake", function() { + state = stateFalling; + Self.PlayAnimation("fall", function() { + state = stateFallen; + Self.ShowLayerNamed("fallen"); + + // Recover after a while. + setTimeout(function() { + Self.ShowLayer(0); + state = stateSolid; + }, recover); + }); + }) + } + + return false; + } + }); +} diff --git a/dev-assets/doodads/crumbly-floor/fall1.png b/dev-assets/doodads/crumbly-floor/fall1.png new file mode 100644 index 0000000..bc9710c Binary files /dev/null and b/dev-assets/doodads/crumbly-floor/fall1.png differ diff --git a/dev-assets/doodads/crumbly-floor/fall2.png b/dev-assets/doodads/crumbly-floor/fall2.png new file mode 100644 index 0000000..1524b6d Binary files /dev/null and b/dev-assets/doodads/crumbly-floor/fall2.png differ diff --git a/dev-assets/doodads/crumbly-floor/fall3.png b/dev-assets/doodads/crumbly-floor/fall3.png new file mode 100644 index 0000000..5b05d93 Binary files /dev/null and b/dev-assets/doodads/crumbly-floor/fall3.png differ diff --git a/dev-assets/doodads/crumbly-floor/fall4.png b/dev-assets/doodads/crumbly-floor/fall4.png new file mode 100644 index 0000000..97e8ee0 Binary files /dev/null and b/dev-assets/doodads/crumbly-floor/fall4.png differ diff --git a/dev-assets/doodads/crumbly-floor/fallen.png b/dev-assets/doodads/crumbly-floor/fallen.png new file mode 100644 index 0000000..dd5e57f Binary files /dev/null and b/dev-assets/doodads/crumbly-floor/fallen.png differ diff --git a/dev-assets/doodads/crumbly-floor/floor.png b/dev-assets/doodads/crumbly-floor/floor.png new file mode 100644 index 0000000..f89f16e Binary files /dev/null and b/dev-assets/doodads/crumbly-floor/floor.png differ diff --git a/dev-assets/doodads/crumbly-floor/shake1.png b/dev-assets/doodads/crumbly-floor/shake1.png new file mode 100644 index 0000000..4881adf Binary files /dev/null and b/dev-assets/doodads/crumbly-floor/shake1.png differ diff --git a/dev-assets/doodads/crumbly-floor/shake2.png b/dev-assets/doodads/crumbly-floor/shake2.png new file mode 100644 index 0000000..42e770d Binary files /dev/null and b/dev-assets/doodads/crumbly-floor/shake2.png differ diff --git a/dev-assets/doodads/doors/electric-door.js b/dev-assets/doodads/doors/electric-door.js index d424ba3..9aa75af 100644 --- a/dev-assets/doodads/doors/electric-door.js +++ b/dev-assets/doodads/doors/electric-door.js @@ -23,8 +23,8 @@ function main() { }); } else { animating = true; - opened = false; Self.PlayAnimation("close", function() { + opened = false; animating = false; }) } diff --git a/dev-assets/doodads/objects/start-flag.png b/dev-assets/doodads/objects/start-flag.png new file mode 100644 index 0000000..125550e Binary files /dev/null and b/dev-assets/doodads/objects/start-flag.png differ diff --git a/dev-assets/doodads/on-off/blue-button.png b/dev-assets/doodads/on-off/blue-button.png new file mode 100644 index 0000000..184627a Binary files /dev/null and b/dev-assets/doodads/on-off/blue-button.png differ diff --git a/dev-assets/doodads/on-off/blue-off.png b/dev-assets/doodads/on-off/blue-off.png new file mode 100644 index 0000000..6275e2b Binary files /dev/null and b/dev-assets/doodads/on-off/blue-off.png differ diff --git a/dev-assets/doodads/on-off/blue-on.png b/dev-assets/doodads/on-off/blue-on.png new file mode 100644 index 0000000..e200231 Binary files /dev/null and b/dev-assets/doodads/on-off/blue-on.png differ diff --git a/dev-assets/doodads/on-off/orange-button.png b/dev-assets/doodads/on-off/orange-button.png new file mode 100644 index 0000000..ec5ef16 Binary files /dev/null and b/dev-assets/doodads/on-off/orange-button.png differ diff --git a/dev-assets/doodads/on-off/orange-off.png b/dev-assets/doodads/on-off/orange-off.png new file mode 100644 index 0000000..1ce8f1e Binary files /dev/null and b/dev-assets/doodads/on-off/orange-off.png differ diff --git a/dev-assets/doodads/on-off/orange-on.png b/dev-assets/doodads/on-off/orange-on.png new file mode 100644 index 0000000..c56e09e Binary files /dev/null and b/dev-assets/doodads/on-off/orange-on.png differ diff --git a/dev-assets/doodads/on-off/state-block-blue.js b/dev-assets/doodads/on-off/state-block-blue.js new file mode 100644 index 0000000..0b287e3 --- /dev/null +++ b/dev-assets/doodads/on-off/state-block-blue.js @@ -0,0 +1,28 @@ +// Blue State Block +function main() { + Self.SetHitbox(0, 0, 33, 33); + + // Blue block is ON by default. + var state = true; + + Message.Subscribe("broadcast:state-change", function(newState) { + state = !newState; + console.warn("BLUE BLOCK Received state=%+v, set mine to %+v", newState, state); + + // Layer 0: ON + // Layer 1: OFF + if (state) { + Self.ShowLayer(0); + } else { + Self.ShowLayer(1); + } + }); + + Events.OnCollide(function(e) { + if (e.Actor.IsMobile() && e.InHitbox) { + if (state) { + return false; + } + } + }); +} diff --git a/dev-assets/doodads/on-off/state-block-orange.js b/dev-assets/doodads/on-off/state-block-orange.js new file mode 100644 index 0000000..634d5e8 --- /dev/null +++ b/dev-assets/doodads/on-off/state-block-orange.js @@ -0,0 +1,28 @@ +// Orange State Block +function main() { + Self.SetHitbox(0, 0, 33, 33); + + // Orange block is OFF by default. + var state = false; + + Message.Subscribe("broadcast:state-change", function(newState) { + state = newState; + console.warn("ORANGE BLOCK Received state=%+v, set mine to %+v", newState, state); + + // Layer 0: OFF + // Layer 1: ON + if (state) { + Self.ShowLayer(1); + } else { + Self.ShowLayer(0); + } + }); + + Events.OnCollide(function(e) { + if (e.Actor.IsMobile() && e.InHitbox) { + if (state) { + return false; + } + } + }); +} diff --git a/dev-assets/doodads/on-off/state-button.js b/dev-assets/doodads/on-off/state-button.js new file mode 100644 index 0000000..51a2af3 --- /dev/null +++ b/dev-assets/doodads/on-off/state-button.js @@ -0,0 +1,47 @@ +// State Block Control Button +function main() { + console.log("%s initialized!", Self.Doodad.Title); + Self.SetHitbox(0, 0, 33, 33); + + // When the button is activated, don't keep toggling state until we're not + // being touched again. + var colliding = false; + + // Button is "OFF" by default. + var state = false; + + Events.OnCollide(function(e) { + if (colliding) { + return false; + } + + // Only trigger for mobile characters. + if (e.Actor.IsMobile()) { + console.log("Mobile actor %s touched the on/off button!", e.Actor.Actor.Filename); + + // Only activate if touched from the bottom or sides. + if (e.Overlap.Y === 0) { + console.log("... but touched the top!"); + return false; + } + + colliding = true; + console.log(" -> emit state change"); + state = !state; + Message.Broadcast("broadcast:state-change", state); + + if (state) { + Self.ShowLayer(1); + } else { + Self.ShowLayer(0); + } + } + + // Always a solid button. + return false; + }); + + Events.OnLeave(function(e) { + colliding = false; + }) +} diff --git a/docs/Shell.md b/docs/Shell.md index 1e92b2f..334eab4 100644 --- a/docs/Shell.md +++ b/docs/Shell.md @@ -6,6 +6,9 @@ * `don't edit and drive` - enable editing while playing a level. * `scroll scroll scroll your boat` - enable scrolling the level with arrow keys while playing a level. +* `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. ## Bool Props diff --git a/pkg/collision/actors_test.go b/pkg/collision/actors_test.go index d7e71f2..1493786 100644 --- a/pkg/collision/actors_test.go +++ b/pkg/collision/actors_test.go @@ -3,8 +3,8 @@ package collision_test import ( "testing" - "git.kirsle.net/go/render" "git.kirsle.net/apps/doodle/pkg/collision" + "git.kirsle.net/go/render" ) func TestActorCollision(t *testing.T) { diff --git a/pkg/commands.go b/pkg/commands.go index 4a134cd..57242a2 100644 --- a/pkg/commands.go +++ b/pkg/commands.go @@ -49,6 +49,20 @@ func (c Command) Run(d *Doodle) error { d.Flash("Use this cheat in Play Mode to make the level scrollable.") } return nil + } else if c.Raw == "import antigravity" { + if playScene, ok := d.Scene.(*PlayScene); ok { + playScene.antigravity = !playScene.antigravity + playScene.Player.SetGravity(!playScene.antigravity) + + if playScene.antigravity { + d.Flash("Gravity disabled for player character.") + } else { + d.Flash("Gravity restored for player character.") + } + } else { + d.Flash("Use this cheat in Play Mode to disable gravity for the player character.") + } + return nil } switch c.Command { diff --git a/pkg/doodads/actor.go b/pkg/doodads/actor.go index d970db2..8e8891e 100644 --- a/pkg/doodads/actor.go +++ b/pkg/doodads/actor.go @@ -25,8 +25,11 @@ type Actor interface { MoveTo(render.Point) // Set current Position to {X,Y}. } -// GetBoundingRect computes the full pairs of points for the collision box -// around a doodad actor. +// GetBoundingRect computes the full pairs of points for the bounding box of +// the actor. +// +// The X,Y coordinates are the position in the level of the actor, +// The W,H are the size of the actor's drawn box. func GetBoundingRect(d Actor) render.Rect { var ( P = d.Position() @@ -40,6 +43,27 @@ func GetBoundingRect(d Actor) render.Rect { } } +// GetBoundingRectHitbox returns the bounding rect of the Actor taking into +// account their self-declared collision hitbox. +// +// The rect returned has the X,Y coordinate set to the actor's position, plus +// the X,Y of their hitbox, if any. +// +// The W,H of the rect is the W,H of their declared hitbox. +// +// If the actor has NOT declared its hitbox, this function returns exactly the +// same way as GetBoundingRect() does. +func GetBoundingRectHitbox(d Actor, hitbox render.Rect) render.Rect { + rect := GetBoundingRect(d) + if !hitbox.IsZero() { + rect.X += hitbox.X + rect.Y += hitbox.Y + rect.W = hitbox.W + rect.H = hitbox.H + } + return rect +} + // GetBoundingRectWithHitbox is like GetBoundingRect but adjusts it for the // relative hitbox of the actor. // func GetBoundingRectWithHitbox(d Actor, hitbox render.Rect) render.Rect { diff --git a/pkg/doodads/drawing.go b/pkg/doodads/drawing.go index 43515c2..e576926 100644 --- a/pkg/doodads/drawing.go +++ b/pkg/doodads/drawing.go @@ -76,21 +76,6 @@ func (d *Drawing) SetGrounded(v bool) { d.grounded = v } -// // SetHitbox sets the actor's elected hitbox. -// func (d *Drawing) SetHitbox(x, y, w, h int) { -// d.hitbox = render.Rect{ -// X: int32(x), -// Y: int32(y), -// W: int32(w), -// H: int32(h), -// } -// } -// -// // Hitbox returns the actor's elected hitbox. -// func (d *Drawing) Hitbox() render.Rect { -// return d.hitbox -// } - // MoveBy a relative value. func (d *Drawing) MoveBy(by render.Point) { d.point.Add(by) diff --git a/pkg/editor_ui.go b/pkg/editor_ui.go index 640a80e..7fcfc35 100644 --- a/pkg/editor_ui.go +++ b/pkg/editor_ui.go @@ -513,7 +513,7 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.Frame { u.Supervisor.Add(w) frame.Pack(w, ui.Pack{ Side: ui.W, - PadX: 1, + PadX: 1, }) } @@ -547,7 +547,7 @@ func (u *EditorUI) SetupStatusBar(d *Doodle) *ui.Frame { label.Compute(d.Engine) frame.Pack(label, ui.Pack{ Side: ui.W, - PadX: 1, + PadX: 1, }) if labelHeight == 0 { diff --git a/pkg/editor_ui_doodad.go b/pkg/editor_ui_doodad.go index fa80908..6b4046b 100644 --- a/pkg/editor_ui_doodad.go +++ b/pkg/editor_ui_doodad.go @@ -111,8 +111,8 @@ func (u *EditorUI) setupDoodadFrame(e render.Engine, window *ui.Window) (*ui.Fra u.doodadPager = pager frame.Pack(pager, ui.Pack{ Side: ui.N, - Fill: true, - PadY: 5, + Fill: true, + PadY: 5, }) doodadsAvailable, err := doodads.ListDoodads() @@ -161,7 +161,7 @@ func (u *EditorUI) setupDoodadFrame(e render.Engine, window *ui.Window) (*ui.Fra btnRows = append(btnRows, row) frame.Pack(row, ui.Pack{ Side: ui.N, - Fill: true, + Fill: true, }) } diff --git a/pkg/editor_ui_palette.go b/pkg/editor_ui_palette.go index 3f29080..5fb0a87 100644 --- a/pkg/editor_ui_palette.go +++ b/pkg/editor_ui_palette.go @@ -3,10 +3,10 @@ package doodle import ( "fmt" - "git.kirsle.net/go/render" - "git.kirsle.net/go/ui" "git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/log" + "git.kirsle.net/go/render" + "git.kirsle.net/go/ui" ) // SetupPalette sets up the palette panel. @@ -33,7 +33,7 @@ func (u *EditorUI) SetupPalette(d *Doodle) *ui.Window { u.DoodadTab.Hide() window.Pack(u.DoodadTab, ui.Pack{ Side: ui.N, - Fill: true, + Fill: true, }) } @@ -41,7 +41,7 @@ func (u *EditorUI) SetupPalette(d *Doodle) *ui.Window { u.PaletteTab = u.setupPaletteFrame(window) window.Pack(u.PaletteTab, ui.Pack{ Side: ui.N, - Fill: true, + Fill: true, }) return window @@ -106,8 +106,8 @@ func (u *EditorUI) setupPaletteFrame(window *ui.Window) *ui.Frame { frame.Pack(btn, ui.Pack{ Side: ui.N, - Fill: true, - PadY: 4, + Fill: true, + PadY: 4, }) } } diff --git a/pkg/editor_ui_toolbar.go b/pkg/editor_ui_toolbar.go index 7402ae7..de279f2 100644 --- a/pkg/editor_ui_toolbar.go +++ b/pkg/editor_ui_toolbar.go @@ -147,14 +147,14 @@ func (u *EditorUI) SetupToolbar(d *Doodle) *ui.Frame { btnFrame.Pack(btn, ui.Pack{ Side: ui.N, - PadY: 2, + PadY: 2, }) } // Spacer frame. frame.Pack(ui.NewFrame("spacer"), ui.Pack{ Side: ui.N, - PadY: 8, + PadY: 8, }) // "Brush Size" label @@ -171,7 +171,7 @@ func (u *EditorUI) SetupToolbar(d *Doodle) *ui.Frame { sizeFrame := ui.NewFrame("Brush Size Frame") frame.Pack(sizeFrame, ui.Pack{ Side: ui.N, - PadY: 0, + PadY: 0, }) sizeLabel := ui.NewLabel(ui.Label{ @@ -184,15 +184,15 @@ func (u *EditorUI) SetupToolbar(d *Doodle) *ui.Frame { Background: render.Grey, }) sizeFrame.Pack(sizeLabel, ui.Pack{ - Side: ui.N, - FillX: true, - PadY: 2, + Side: ui.N, + FillX: true, + PadY: 2, }) sizeBtnFrame := ui.NewFrame("Size Increment Button Frame") sizeFrame.Pack(sizeBtnFrame, ui.Pack{ - Side: ui.N, - FillX: true, + Side: ui.N, + FillX: true, }) var incButtons = []struct { diff --git a/pkg/guitest_scene.go b/pkg/guitest_scene.go index da07136..ff1827d 100644 --- a/pkg/guitest_scene.go +++ b/pkg/guitest_scene.go @@ -54,7 +54,7 @@ func (s *GUITestScene) Setup(d *Doodle) error { }) window.Pack(titleBar, ui.Pack{ Side: ui.N, - Fill: true, + Fill: true, }) // Window Body @@ -63,7 +63,7 @@ func (s *GUITestScene) Setup(d *Doodle) error { Background: render.Yellow, }) window.Pack(body, ui.Pack{ - Side: ui.N, + Side: ui.N, Expand: true, }) s.body = body @@ -77,8 +77,8 @@ func (s *GUITestScene) Setup(d *Doodle) error { Width: 100, }) body.Pack(leftFrame, ui.Pack{ - Side: ui.W, - FillY: true, + Side: ui.W, + FillY: true, }) // Some left frame buttons. @@ -92,9 +92,9 @@ func (s *GUITestScene) Setup(d *Doodle) error { }) s.Supervisor.Add(btn) leftFrame.Pack(btn, ui.Pack{ - Side: ui.N, - FillX: true, - PadY: 2, + Side: ui.N, + FillX: true, + PadY: 2, }) } @@ -105,7 +105,7 @@ func (s *GUITestScene) Setup(d *Doodle) error { BorderSize: 0, }) body.Pack(frame, ui.Pack{ - Side: ui.W, + Side: ui.W, Expand: true, Fill: true, }) @@ -120,7 +120,7 @@ func (s *GUITestScene) Setup(d *Doodle) error { }) body.Pack(rightFrame, ui.Pack{ Side: ui.W, - Fill: true, + Fill: true, }) // A grid of buttons. @@ -142,7 +142,7 @@ func (s *GUITestScene) Setup(d *Doodle) error { d.Flash("%s clicked", btn) }) rowFrame.Pack(btn, ui.Pack{ - Side: ui.W, + Side: ui.W, Expand: true, FillX: true, }) @@ -151,7 +151,7 @@ func (s *GUITestScene) Setup(d *Doodle) error { } rightFrame.Pack(rowFrame, ui.Pack{ Side: ui.N, - Fill: true, + Fill: true, }) } @@ -163,7 +163,7 @@ func (s *GUITestScene) Setup(d *Doodle) error { Color: render.Black, }, }), ui.Pack{ - Side: ui.NW, + Side: ui.NW, Padding: 2, }) @@ -175,7 +175,7 @@ func (s *GUITestScene) Setup(d *Doodle) error { }), ) frame.Pack(cb, ui.Pack{ - Side: ui.NW, + Side: ui.NW, Padding: 4, }) cb.Supervise(s.Supervisor) @@ -187,7 +187,7 @@ func (s *GUITestScene) Setup(d *Doodle) error { Color: render.Red, }, }), ui.Pack{ - Side: ui.SE, + Side: ui.SE, Padding: 8, }) frame.Pack(ui.NewLabel(ui.Label{ @@ -197,7 +197,7 @@ func (s *GUITestScene) Setup(d *Doodle) error { Color: render.Blue, }, }), ui.Pack{ - Side: ui.SE, + Side: ui.SE, Padding: 8, }) @@ -233,11 +233,11 @@ func (s *GUITestScene) Setup(d *Doodle) error { var align = ui.W btnFrame.Pack(button1, ui.Pack{ - Side: align, + Side: align, Padding: 20, }) btnFrame.Pack(button2, ui.Pack{ - Side: align, + Side: align, Padding: 20, }) diff --git a/pkg/level/chunk_test.go b/pkg/level/chunk_test.go index c08b0c2..ab7b147 100644 --- a/pkg/level/chunk_test.go +++ b/pkg/level/chunk_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "git.kirsle.net/go/render" "git.kirsle.net/apps/doodle/pkg/level" + "git.kirsle.net/go/render" ) // Test the high level Chunker. diff --git a/pkg/main_scene.go b/pkg/main_scene.go index 9ba096e..607c4b8 100644 --- a/pkg/main_scene.go +++ b/pkg/main_scene.go @@ -68,7 +68,7 @@ func (s *MainScene) Setup(d *Doodle) error { s.Supervisor.Add(btn) frame.Pack(btn, ui.Pack{ Side: ui.N, - PadY: 8, + PadY: 8, // Fill: true, FillX: true, }) diff --git a/pkg/play_scene.go b/pkg/play_scene.go index 76c220c..bd29807 100644 --- a/pkg/play_scene.go +++ b/pkg/play_scene.go @@ -50,7 +50,8 @@ type PlayScene struct { // Player character Player *uix.Actor - playerJumpCounter int // limit jump length + antigravity bool // Cheat: disable player gravity + playerJumpCounter int // limit jump length } // Name of the scene. @@ -154,21 +155,7 @@ func (s *PlayScene) Setup(d *Doodle) error { } // Load in the player character. - player, err := doodads.LoadFile("azu-blu.doodad") - if err != nil { - log.Error("PlayScene.Setup: failed to load player doodad: %s", err) - player = doodads.NewDummy(32) - } - - s.Player = uix.NewActor("PLAYER", &level.Actor{}, player) - s.Player.MoveTo(render.NewPoint(128, 128)) - s.drawing.AddActor(s.Player) - s.drawing.FollowActor = s.Player.ID() - - // Set up the player character's script in the VM. - if err := s.scripting.AddLevelScript(s.Player.ID()); err != nil { - log.Error("PlayScene.Setup: scripting.InstallActor(player) failed: %s", err) - } + s.setupPlayer() // Run all the actor scripts' main() functions. if err := s.drawing.InstallScripts(); err != nil { @@ -186,6 +173,60 @@ func (s *PlayScene) Setup(d *Doodle) error { return nil } +// setupPlayer creates and configures the Player Character in the level. +func (s *PlayScene) setupPlayer() { + // Load in the player character. + player, err := doodads.LoadFile("azu-blu.doodad") + if err != nil { + log.Error("PlayScene.Setup: failed to load player doodad: %s", err) + player = doodads.NewDummy(32) + } + + // Find the spawn point of the player. Search the level for the + // "start-flag.doodad" + var ( + spawn render.Point + flagCount int + ) + for actorID, actor := range s.Level.Actors { + if actor.Filename == "start-flag.doodad" { + if flagCount > 1 { + break + } + + // TODO: start-flag.doodad is 86x86 pixels but we can't tell that + // from right here. + size := render.NewRect(86, 86) + log.Info("Found start-flag.doodad at %s (ID %s)", actor.Point, actorID) + spawn = render.NewPoint( + // X: centered inside the flag. + actor.Point.X+(size.W/2)-(player.Layers[0].Chunker.Size/2), + + // Y: the bottom of the flag, 4 pixels from the floor. + actor.Point.Y+size.H-4-(player.Layers[0].Chunker.Size), + ) + flagCount++ + } + } + + // Surface warnings around the spawn flag. + if flagCount == 0 { + s.d.Flash("Warning: this level contained no Start Flag.") + } else if flagCount > 1 { + s.d.Flash("Warning: this level contains multiple Start Flags. Player spawn point is ambiguous.") + } + + s.Player = uix.NewActor("PLAYER", &level.Actor{}, player) + s.Player.MoveTo(spawn) + s.drawing.AddActor(s.Player) + s.drawing.FollowActor = s.Player.ID() + + // Set up the player character's script in the VM. + if err := s.scripting.AddLevelScript(s.Player.ID()); err != nil { + log.Error("PlayScene.Setup: scripting.InstallActor(player) failed: %s", err) + } +} + // SetupAlertbox configures the alert box UI. func (s *PlayScene) SetupAlertbox() { window := ui.NewWindow("Level Completed") @@ -393,31 +434,24 @@ func (s *PlayScene) movePlayer(ev *event.State) { if ev.Right { velocity.X = playerSpeed } - if ev.Up && (s.Player.Grounded() || s.playerJumpCounter >= 0) { + if ev.Up && (s.Player.Grounded() || s.playerJumpCounter >= 0 || s.antigravity) { velocity.Y = -playerSpeed if s.Player.Grounded() { s.playerJumpCounter = 20 } } + if ev.Down && s.antigravity { + velocity.Y = playerSpeed + } if !s.Player.Grounded() { s.playerJumpCounter-- } - // // Apply gravity if not grounded. - // if !s.Player.Grounded() { - // // Gravity has to pipe through the collision checker, too, so it - // // can't give us a cheated downward boost. - // velocity.Y += gravity - // } - s.Player.SetVelocity(velocity) - // TODO: invoke the player OnKeypress for animation testing - // if velocity != render.Origin { s.scripting.To(s.Player.ID()).Events.RunKeypress(ev) - // } } // Drawing returns the private world drawing, for debugging with the console. diff --git a/pkg/scene.go b/pkg/scene.go index 4353a65..45cb452 100644 --- a/pkg/scene.go +++ b/pkg/scene.go @@ -1,8 +1,8 @@ package doodle import ( - "git.kirsle.net/go/render/event" "git.kirsle.net/apps/doodle/pkg/log" + "git.kirsle.net/go/render/event" ) // Scene is an abstraction for a game mode in Doodle. The app points to one diff --git a/pkg/scripting/pubsub.go b/pkg/scripting/pubsub.go index 8cabf4d..407f22e 100644 --- a/pkg/scripting/pubsub.go +++ b/pkg/scripting/pubsub.go @@ -18,7 +18,7 @@ RegisterPublishHooks adds the pub/sub hooks to a JavaScript VM. This adds the global methods `Message.Subscribe(name, func)` and `Message.Publish(name, args)` to the JavaScript VM's scope. */ -func RegisterPublishHooks(vm *VM) { +func RegisterPublishHooks(s *Supervisor, vm *VM) { // Goroutine to watch the VM's inbound channel and invoke Subscribe handlers // for any matching messages received. go func() { @@ -47,7 +47,6 @@ func RegisterPublishHooks(vm *VM) { }, "Publish": func(name string, v ...interface{}) { - log.Error("PUBLISH: %s %+v", name, v) for _, channel := range vm.Outbound { channel <- Message{ Name: name, @@ -55,5 +54,15 @@ func RegisterPublishHooks(vm *VM) { } } }, + + "Broadcast": func(name string, v ...interface{}) { + // Send the message to all actor VMs. + for _, vm := range s.scripts { + vm.Inbound <- Message{ + Name: name, + Args: v, + } + } + }, }) } diff --git a/pkg/scripting/scripting.go b/pkg/scripting/scripting.go index 3bad626..bb91697 100644 --- a/pkg/scripting/scripting.go +++ b/pkg/scripting/scripting.go @@ -77,7 +77,7 @@ func (s *Supervisor) AddLevelScript(id string) error { } s.scripts[id] = NewVM(id) - RegisterPublishHooks(s.scripts[id]) + RegisterPublishHooks(s, s.scripts[id]) RegisterEventHooks(s, s.scripts[id]) if err := s.scripts[id].RegisterLevelHooks(); err != nil { return err diff --git a/pkg/scripting/vm.go b/pkg/scripting/vm.go index 57cf67f..21f78f5 100644 --- a/pkg/scripting/vm.go +++ b/pkg/scripting/vm.go @@ -5,9 +5,9 @@ import ( "reflect" "time" - "git.kirsle.net/go/render" "git.kirsle.net/apps/doodle/pkg/log" "git.kirsle.net/apps/doodle/pkg/shmem" + "git.kirsle.net/go/render" "github.com/robertkrimen/otto" ) diff --git a/pkg/sprites/sprites.go b/pkg/sprites/sprites.go index f8c70ad..cbf40b9 100644 --- a/pkg/sprites/sprites.go +++ b/pkg/sprites/sprites.go @@ -8,11 +8,11 @@ import ( "os" "runtime" - "git.kirsle.net/go/render" - "git.kirsle.net/go/ui" "git.kirsle.net/apps/doodle/pkg/bindata" "git.kirsle.net/apps/doodle/pkg/log" "git.kirsle.net/apps/doodle/pkg/wasm" + "git.kirsle.net/go/render" + "git.kirsle.net/go/ui" ) // LoadImage loads a sprite as a ui.Image object. It checks Doodle's embedded diff --git a/pkg/uix/actor.go b/pkg/uix/actor.go index 23d56d4..28ddc2a 100644 --- a/pkg/uix/actor.go +++ b/pkg/uix/actor.go @@ -6,6 +6,7 @@ import ( "git.kirsle.net/apps/doodle/pkg/doodads" "git.kirsle.net/apps/doodle/pkg/level" + "git.kirsle.net/apps/doodle/pkg/log" "git.kirsle.net/go/render" "github.com/google/uuid" "github.com/robertkrimen/otto" @@ -29,6 +30,7 @@ type Actor struct { // Actor runtime variables. hasGravity bool + isMobile bool // Mobile character, such as the player or an enemy hitbox render.Rect data map[string]string @@ -75,6 +77,18 @@ func (a *Actor) SetGravity(v bool) { a.hasGravity = v } +// SetMobile configures whether the actor is a mobile character (i.e. is the +// player or a mobile enemy). Mobile characters can set off certain traps when +// touched but non-mobile actors don't set each other off if touching. +func (a *Actor) SetMobile(v bool) { + a.isMobile = v +} + +// IsMobile returns whether the actor is a mobile character. +func (a *Actor) IsMobile() bool { + return a.isMobile +} + // GetBoundingRect gets the bounding box of the actor's doodad. func (a *Actor) GetBoundingRect() render.Rect { return doodads.GetBoundingRect(a) @@ -133,6 +147,21 @@ func (a *Actor) ShowLayer(index int) error { return nil } +// ShowLayerNamed sets the actor's ActiveLayer to the one named. +func (a *Actor) ShowLayerNamed(name string) error { + // Find the layer. + for i, layer := range a.Doodad.Layers { + if layer.Name == name { + return a.ShowLayer(i) + } + } + log.Warn("Actor(%s) ShowLayerNamed(%s): layer not found", + a.Actor.Filename, + name, + ) + return fmt.Errorf("the layer named %s was not found", name) +} + // Destroy deletes the actor from the running level. func (a *Actor) Destroy() { a.flagDestroy = true diff --git a/pkg/uix/actor_collision.go b/pkg/uix/actor_collision.go index 31c38d1..a2fda38 100644 --- a/pkg/uix/actor_collision.go +++ b/pkg/uix/actor_collision.go @@ -46,13 +46,18 @@ func (w *Canvas) loopActorCollision() error { // Advance any animations for this actor. if a.activeAnimation != nil && a.activeAnimation.nextFrameAt.Before(now) { if done := a.TickAnimation(a.activeAnimation); done { - // Animation has finished, run the callback script. - if a.animationCallback.IsFunction() { - a.animationCallback.Call(otto.NullValue()) + // Animation has finished, get the callback function. + callback := a.animationCallback + + // Clean up the animation state, in case the callback wants + // to immediately play another animation. + a.StopAnimation() + + // Call the callback function. + if callback.IsFunction() { + callback.Call(otto.NullValue()) } - // Clean up the animation state. - a.StopAnimation() } } @@ -104,7 +109,13 @@ func (w *Canvas) loopActorCollision() error { if w.scripting != nil { var ( rect = doodads.GetBoundingRect(b) - lastGoodBox = boxes[tuple.B] // worst case scenario we get blocked right away + lastGoodBox = render.Rect{ + X: originalPositions[b.ID()].X, + Y: originalPositions[b.ID()].Y, + W: boxes[tuple.B].W, + H: boxes[tuple.B].H, + } + // lastGoodBox = originalPositions[b.ID()] // 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 @@ -121,7 +132,12 @@ func (w *Canvas) loopActorCollision() error { // B's movement. The next time A does NOT protest, that is to be // B's new position. - var firstPoint = true + // 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 + for point := range render.IterLine( origPoint, b.Position(), @@ -133,6 +149,11 @@ 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. @@ -145,27 +166,41 @@ func (w *Canvas) loopActorCollision() error { // Did A protest? if err == scripting.ErrReturnFalse { - break + // Are they on top? + if render.AbsInt(lastGoodBox.Y+lastGoodBox.H-boxes[tuple.A].Y) <= 2 { + onTop = true + } + + // What direction were we moving? + if test.Y != lastGoodBox.Y { + lockY = true + b.SetGrounded(true) + } + if test.X != lastGoodBox.X { + if !onTop { + lockX = true + } + } + + // Move them back to the last good box, locking the + // axis they were moving from being able to enter + // this box. + tmp := lastGoodBox + lastGoodBox = test + if lockY { + lastGoodBox.Y = tmp.Y + } + if lockX { + lastGoodBox.X = tmp.X + 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, -balance.Gravity)) - } else { - b.MoveTo(lastGoodBox.Point()) - } + b.MoveTo(lastGoodBox.Point()) } else { log.Error( "ERROR: Actors %s and %s overlap and the script returned false,"+ diff --git a/pkg/uix/canvas_actors.go b/pkg/uix/canvas_actors.go index 351984e..6ff0ec7 100644 --- a/pkg/uix/canvas_actors.go +++ b/pkg/uix/canvas_actors.go @@ -4,11 +4,11 @@ import ( "errors" "fmt" - "git.kirsle.net/go/render" "git.kirsle.net/apps/doodle/pkg/doodads" "git.kirsle.net/apps/doodle/pkg/level" "git.kirsle.net/apps/doodle/pkg/log" "git.kirsle.net/apps/doodle/pkg/scripting" + "git.kirsle.net/go/render" ) // InstallActors adds external Actors to the canvas to be superimposed on top diff --git a/pkg/uix/canvas_editable.go b/pkg/uix/canvas_editable.go index b345137..bb12858 100644 --- a/pkg/uix/canvas_editable.go +++ b/pkg/uix/canvas_editable.go @@ -303,7 +303,7 @@ func (w *Canvas) loopEditable(ev *event.State) error { w.OnDragStart(actor.Actor) } break - } else if ev.Button2 { + } else if ev.Button3 { // Right click to delete an actor. deleteActors = append(deleteActors, actor.Actor) } diff --git a/pkg/uix/canvas_strokes.go b/pkg/uix/canvas_strokes.go index 70937ad..aedc6c5 100644 --- a/pkg/uix/canvas_strokes.go +++ b/pkg/uix/canvas_strokes.go @@ -1,13 +1,13 @@ package uix import ( - "git.kirsle.net/go/render" - "git.kirsle.net/go/ui" "git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/drawtool" "git.kirsle.net/apps/doodle/pkg/level" "git.kirsle.net/apps/doodle/pkg/log" "git.kirsle.net/apps/doodle/pkg/shmem" + "git.kirsle.net/go/render" + "git.kirsle.net/go/ui" ) // canvas_strokes.go: functions related to drawtool.Stroke and the Canvas. diff --git a/pkg/wallpaper/texture.go b/pkg/wallpaper/texture.go index 9102135..32706a4 100644 --- a/pkg/wallpaper/texture.go +++ b/pkg/wallpaper/texture.go @@ -6,9 +6,9 @@ import ( "errors" "image" - "git.kirsle.net/go/render" "git.kirsle.net/apps/doodle/pkg/shmem" "git.kirsle.net/apps/doodle/pkg/userdir" + "git.kirsle.net/go/render" ) // CornerTexture returns the Texture. diff --git a/pkg/wallpaper/wallpaper.go b/pkg/wallpaper/wallpaper.go index 3f89d46..5f00bd7 100644 --- a/pkg/wallpaper/wallpaper.go +++ b/pkg/wallpaper/wallpaper.go @@ -9,8 +9,8 @@ import ( "runtime" "strings" - "git.kirsle.net/go/render" "git.kirsle.net/apps/doodle/pkg/bindata" + "git.kirsle.net/go/render" ) // Wallpaper is a repeatable background image to go behind levels. diff --git a/wasm/main_wasm.go b/wasm/main_wasm.go index 93eb136..2406e6c 100644 --- a/wasm/main_wasm.go +++ b/wasm/main_wasm.go @@ -7,12 +7,12 @@ import ( "syscall/js" - "git.kirsle.net/go/render" - "git.kirsle.net/go/render/canvas" doodle "git.kirsle.net/apps/doodle/pkg" "git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/branding" "git.kirsle.net/apps/doodle/pkg/log" + "git.kirsle.net/go/render" + "git.kirsle.net/go/render/canvas" ) func main() {