diff --git a/assets/wallpapers/dots-dark.png b/assets/wallpapers/dots-dark.png new file mode 100644 index 0000000..2f670ae Binary files /dev/null and b/assets/wallpapers/dots-dark.png differ diff --git a/cmd/doodad/commands/convert.go b/cmd/doodad/commands/convert.go index 634b1b0..d9386de 100644 --- a/cmd/doodad/commands/convert.go +++ b/cmd/doodad/commands/convert.go @@ -32,8 +32,8 @@ func init() { Flags: []cli.Flag{ &cli.StringFlag{ Name: "key", - Usage: "chroma key color for transparency on input image files", - Value: "#ffffff", + Usage: "chroma key color for transparency on input image files, e.g. #ffffff", + Value: "", }, &cli.StringFlag{ Name: "title", @@ -57,12 +57,16 @@ func init() { } // Parse the chroma key. - chroma, err := render.HexColor(c.String("key")) - if err != nil { - return cli.Exit( - "Chrome key not a valid color: "+err.Error(), - 1, - ) + var chroma = render.Invisible + if key := c.String("key"); key != "" { + color, err := render.HexColor(c.String("key")) + if err != nil { + return cli.Exit( + "Chrome key not a valid color: "+err.Error(), + 1, + ) + } + chroma = color } args := c.Args().Slice() diff --git a/dev-assets/doodads/box/box.js b/dev-assets/doodads/box/box.js index b78c83d..0ca950a 100644 --- a/dev-assets/doodads/box/box.js +++ b/dev-assets/doodads/box/box.js @@ -22,7 +22,6 @@ function main() { // Be sure to position them snug on top. // TODO: this might be a nice general solution in the // collision detector... - console.log("new box code"); e.Actor.MoveTo(Point( e.Actor.Position().X, Self.Position().Y - e.Actor.Hitbox().Y - e.Actor.Hitbox().H - 2, diff --git a/dev-assets/doodads/gems/totem.js b/dev-assets/doodads/gems/totem.js index b2e494f..3598f50 100644 --- a/dev-assets/doodads/gems/totem.js +++ b/dev-assets/doodads/gems/totem.js @@ -35,8 +35,6 @@ function main() { } } - console.log("Totem %s is linked to %d neighbors", Self.ID(), Object.keys(totems).length); - // Shimmer animation is just like the gemstones: first 4 frames // are the filled socket sprites. Self.AddAnimation("shimmer", 100, [0, 1, 2, 3, 0]); @@ -81,14 +79,10 @@ function tryPower() { return; } - console.log("Totem %s (%s) tries power", Self.ID(), Self.Filename); - // Can't if any of our linked totems aren't activated. try { for (let totemId of Object.keys(totems)) { - console.log("Totem %s (%s) sees linked totem %s", Self.ID(), Self.Filename, totemId); if (totems[totemId] === false) { - console.log("Can't, a linked totem not active!"); return; } } @@ -98,11 +92,9 @@ function tryPower() { // Can't if we aren't powered. if (activated === false) { - console.log("Can't, we are not active!"); return; } // Emit power! - console.log("POWER!"); Message.Publish("power", true); } \ No newline at end of file diff --git a/dev-assets/doodads/snake/snake.js b/dev-assets/doodads/snake/snake.js index e8b2aaf..952f742 100644 --- a/dev-assets/doodads/snake/snake.js +++ b/dev-assets/doodads/snake/snake.js @@ -66,15 +66,12 @@ function main() { } if (delta < watchRadius) { - console.log("Player is nearby snake! %d", delta); nearby = true; } // If we are idle and the player is jumping nearby... if (state == states.idle && nearby && Self.Grounded()) { if (playerPoint.Y - point.Y+(size.H/2) < 20) { - console.warn("Player is jumping near us!") - // Enter attack state. if (time.Since(jumpCooldownStart) > 500 * time.Millisecond) { state = states.attacking; @@ -88,7 +85,6 @@ function main() { // If we are attacking and gravity has claimed us back. if (state === states.attacking && Self.Grounded()) { - console.log("Landed again after jump!"); state = states.idle; jumpCooldownStart = time.Now(); Self.StopAnimation(); diff --git a/pkg/balance/cheats.go b/pkg/balance/cheats.go index 59f9d2d..c87ee81 100644 --- a/pkg/balance/cheats.go +++ b/pkg/balance/cheats.go @@ -30,6 +30,7 @@ var ( CheatGodMode = "god mode" CheatDebugLoadScreen = "test load screen" CheatUnlockLevels = "master key" + CheatSkipLevel = "warp whistle" ) // Global cheat boolean states. diff --git a/pkg/balance/theme.go b/pkg/balance/theme.go index 3c9e280..cffefb2 100644 --- a/pkg/balance/theme.go +++ b/pkg/balance/theme.go @@ -234,6 +234,10 @@ var ( Label: "Dotted paper", Value: "dots.png", }, + { + Label: "Dotted paper (dark)", + Value: "dots-dark.png", + }, { Label: "Blueprint", Value: "blueprint.png", diff --git a/pkg/cheats.go b/pkg/cheats.go index b133107..ccd2a2c 100644 --- a/pkg/cheats.go +++ b/pkg/cheats.go @@ -142,11 +142,6 @@ func (c Command) cheatCommand(d *Doodle) bool { d.FlashError("Use this cheat in Play Mode to clear your inventory.") } - case balance.CheatPlayAsBird: - balance.PlayerCharacterDoodad = "bird-red.doodad" - setPlayerCharacter = true - d.Flash("Set default player character to Bird (red)") - case balance.CheatGodMode: if isPlay { d.Flash("God mode toggled") @@ -181,6 +176,18 @@ func (c Command) cheatCommand(d *Doodle) bool { d.Flash("All locked Story Mode levels are again locked.") } + case balance.CheatSkipLevel: + if isPlay { + playScene.SetCheated() + playScene.ShowEndLevelModal( + true, + "Level Completed", + "Great job, you cheated and 'won' the level!", + ) + } else { + d.Flash("Use this cheat in Play Mode to instantly win the level.") + } + default: // See if it was an endorsed actor cheat. if filename, ok := balance.CheatActors[strings.ToLower(c.Raw)]; ok { @@ -195,6 +202,7 @@ func (c Command) cheatCommand(d *Doodle) bool { // If we're setting the player character and in Play Mode, do it. if setPlayerCharacter && isPlay { + playScene.SetCheated() playScene.SetPlayerCharacter(balance.PlayerCharacterDoodad) } diff --git a/pkg/level/chunk.go b/pkg/level/chunk.go index 63d3b38..368de07 100644 --- a/pkg/level/chunk.go +++ b/pkg/level/chunk.go @@ -194,6 +194,11 @@ func (c *Chunk) ToBitmap(mask render.Color) image.Image { for px := range c.Iter() { var color = px.Swatch.Color + // Don't draw perfectly white pixels, SDL2 will make them invisible! + if color == render.White { + color.Blue-- + } + // If the swatch has a pattern, mesh it in. if px.Swatch.Pattern != "" { color = pattern.SampleColor(px.Swatch.Pattern, color, px.Point()) diff --git a/pkg/level/palette_defaults.go b/pkg/level/palette_defaults.go index fba575e..f913501 100644 --- a/pkg/level/palette_defaults.go +++ b/pkg/level/palette_defaults.go @@ -10,6 +10,7 @@ var ( "Default", "Colored Pencil", "Blueprint", + "Neon Bright", } DefaultPalettes = map[string]*Palette{ @@ -98,6 +99,43 @@ var ( }, }, + "Neon Bright": { + Swatches: []*Swatch{ + { + Name: "ground", + Color: render.MustHexColor("#FFE"), + Solid: true, + Pattern: "noise.png", + }, + { + Name: "grass green", + Color: render.Green, + Solid: true, + Pattern: "noise.png", + }, + { + Name: "fire", + Color: render.MustHexColor("#F90"), + Pattern: "marker.png", + }, + { + Name: "electricity", + Color: render.Yellow, + Pattern: "perlin.png", + }, + { + Name: "water", + Color: render.MustHexColor("#09F"), + Pattern: "ink.png", + }, + { + Name: "hint", + Color: render.Magenta, + Pattern: "marker.png", + }, + }, + }, + "Blueprint": { Swatches: []*Swatch{ { diff --git a/pkg/main_scene.go b/pkg/main_scene.go index d01e1be..bebaa97 100644 --- a/pkg/main_scene.go +++ b/pkg/main_scene.go @@ -588,12 +588,47 @@ func (s *MainScene) LoopLazyScroll() { } } } else { + var ( + // Bounded and bordered levels will naturally hit + // an edge and stop scrolling + bounceY = currentScroll.Y == lastScrollValue.Y + bounceX = currentScroll.X == lastScrollValue.X + worldsize = s.canvas.Chunker().WorldSize() + viewport = s.canvas.Viewport() + ) + + // In case of unbounded levels, set limits ourself. + if !bounceX { + if viewport.X < worldsize.X || viewport.X > worldsize.W { + bounceX = true + + // Set the trajectory the right direction immediately. + if viewport.X < worldsize.X { + s.lazyScrollTrajectory.X = 1 + } else { + s.lazyScrollTrajectory.X = -1 + } + } + } + if !bounceY { + if viewport.Y < worldsize.Y || viewport.Y > worldsize.H { + bounceY = true + + // Set the trajectory the right direction immediately. + if viewport.Y < worldsize.Y { + s.lazyScrollTrajectory.Y = 1 + } else { + s.lazyScrollTrajectory.Y = -1 + } + } + } + // Lazy bounce algorithm. - if currentScroll.Y == lastScrollValue.Y { + if bounceY { log.Debug("LoopLazyScroll: Hit a floor/ceiling") s.lazyScrollTrajectory.Y = -s.lazyScrollTrajectory.Y } - if currentScroll.X == lastScrollValue.X { + if bounceX { log.Debug("LoopLazyScroll: Hit the side of the map!") s.lazyScrollTrajectory.X = -s.lazyScrollTrajectory.X } diff --git a/pkg/play_scene.go b/pkg/play_scene.go index 3424c9a..58ff74b 100644 --- a/pkg/play_scene.go +++ b/pkg/play_scene.go @@ -207,8 +207,6 @@ func (s *PlayScene) setupAsync(d *Doodle) error { // Initialize the drawing canvas. s.drawing = uix.NewCanvas(balance.ChunkSize, false) s.drawing.Name = "play-canvas" - s.drawing.MoveTo(render.Origin) - s.drawing.Resize(render.NewRect(d.width, d.height)) s.drawing.Compute(d.Engine) // Handler when an actor touches water or fire. @@ -294,9 +292,46 @@ func (s *PlayScene) setupAsync(d *Doodle) error { s.perfectRun = true s.running = true + // // Cap the canvas size in case the user has an ultra HD monitor that's bigger + // // than a bounded level's limits. + // s.screen.Compute(d.Engine) + // s.PlaceResizeCanvas() + return nil } +// PlaceResizeCanvas updates the Canvas size and placement on the screen, +// e.g. if an ultra HD monitor plays a Bounded level where the entirety of a +// level bounds is on-screen, the drawing should be cut there and the +// canvas centered. +func (s *PlayScene) PlaceResizeCanvas() { + var ( + width = s.d.width + height = s.d.height + menubar = 0 + ) + + if s.menubar != nil { + menubar = s.menubar.Size().H + height -= menubar + } + + if s.Level != nil && s.Level.PageType >= level.Bounded { + if s.Level.MaxWidth < int64(width) { + width = int(s.Level.MaxWidth) + } + if s.Level.MaxHeight < int64(height) { + height = int(s.Level.MaxHeight) + } + } + + s.drawing.Resize(render.NewRect(width, height)) + s.drawing.MoveTo(render.Point{ + X: (s.d.width / 2) - (width / 2), + Y: menubar, + }) +} + // SetPlayerCharacter changes the doodad used for the player, by destroying the // current player character and making it from scratch. func (s *PlayScene) SetPlayerCharacter(filename string) { @@ -682,8 +717,8 @@ func (s *PlayScene) Loop(d *Doodle, ev *event.State) error { s.supervisor.Loop(ev) // Has the window been resized? - if ev.WindowResized { - s.drawing.Resize(render.NewRect(d.width, d.height)) + if ev.WindowResized || s.drawing.Point().IsZero() { + s.PlaceResizeCanvas() s.screen.Resize(render.NewRect(d.width, d.height)) return nil } @@ -732,9 +767,9 @@ func (s *PlayScene) Draw(d *Doodle) error { } // Clear the canvas and fill it with white. - d.Engine.Clear(render.White) + d.Engine.Clear(balance.WindowBackground) - // Draw the level. + // Draw the canvas widget. s.drawing.Present(d.Engine, s.drawing.Point()) // Draw out bounding boxes. diff --git a/pkg/uix/canvas_editable.go b/pkg/uix/canvas_editable.go index 19c95d7..fb12fef 100644 --- a/pkg/uix/canvas_editable.go +++ b/pkg/uix/canvas_editable.go @@ -458,8 +458,6 @@ func (w *Canvas) loopEditable(ev *event.State) error { // See if any of the actors are below the mouse cursor. var WP = w.WorldIndexAt(cursor) - // log.Debug("ActorTool, cursor=%s WP=%s zoom=%d P=%s", cursor, WP, w.Zoom, ui.AbsolutePosition(w)) - var deleteActors = []*Actor{} for _, actor := range w.actors {