Spit and polish

* New built-in wallpaper: "Dotted paper (dark)" is a dark-themed wallpaper.
* New built-in palette: "Neon Bright" with bright colors for dark levels.
* New cheat: "warp whistle" to automatically win the level.
* In case the user has a VERY LARGE screen resolution bigger than the full
  bounds of a Bounded level, the Play Scene will cap the size and center
  the level canvas onto the window. This is preferable to being able to see
  beyond the level's boundaries and hitting an invisible wall in-game.
* Make the titlescreen Lazy Scroll work on unbounded levels. It can't bounce
  off scroll boundaries but it will reverse course if it reaches the level's
  furthest limits.
* Bugfix: characters' white eyes were transparent in-game. Multiple culprits
  from the `doodad convert` tool defaulting the chroma key to white, to the
  SDL2 textures considering white to be transparent. For the latter, the game
  offsets the color by -1 blue.
This commit is contained in:
Noah 2022-05-03 21:15:39 -07:00
parent 75fa0c7e56
commit 9b75f1b039
13 changed files with 151 additions and 36 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -32,8 +32,8 @@ func init() {
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "key", Name: "key",
Usage: "chroma key color for transparency on input image files", Usage: "chroma key color for transparency on input image files, e.g. #ffffff",
Value: "#ffffff", Value: "",
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "title", Name: "title",
@ -57,13 +57,17 @@ func init() {
} }
// Parse the chroma key. // Parse the chroma key.
chroma, err := render.HexColor(c.String("key")) var chroma = render.Invisible
if key := c.String("key"); key != "" {
color, err := render.HexColor(c.String("key"))
if err != nil { if err != nil {
return cli.Exit( return cli.Exit(
"Chrome key not a valid color: "+err.Error(), "Chrome key not a valid color: "+err.Error(),
1, 1,
) )
} }
chroma = color
}
args := c.Args().Slice() args := c.Args().Slice()
var ( var (

View File

@ -22,7 +22,6 @@ function main() {
// Be sure to position them snug on top. // Be sure to position them snug on top.
// TODO: this might be a nice general solution in the // TODO: this might be a nice general solution in the
// collision detector... // collision detector...
console.log("new box code");
e.Actor.MoveTo(Point( e.Actor.MoveTo(Point(
e.Actor.Position().X, e.Actor.Position().X,
Self.Position().Y - e.Actor.Hitbox().Y - e.Actor.Hitbox().H - 2, Self.Position().Y - e.Actor.Hitbox().Y - e.Actor.Hitbox().H - 2,

View File

@ -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 // Shimmer animation is just like the gemstones: first 4 frames
// are the filled socket sprites. // are the filled socket sprites.
Self.AddAnimation("shimmer", 100, [0, 1, 2, 3, 0]); Self.AddAnimation("shimmer", 100, [0, 1, 2, 3, 0]);
@ -81,14 +79,10 @@ function tryPower() {
return; return;
} }
console.log("Totem %s (%s) tries power", Self.ID(), Self.Filename);
// Can't if any of our linked totems aren't activated. // Can't if any of our linked totems aren't activated.
try { try {
for (let totemId of Object.keys(totems)) { 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) { if (totems[totemId] === false) {
console.log("Can't, a linked totem not active!");
return; return;
} }
} }
@ -98,11 +92,9 @@ function tryPower() {
// Can't if we aren't powered. // Can't if we aren't powered.
if (activated === false) { if (activated === false) {
console.log("Can't, we are not active!");
return; return;
} }
// Emit power! // Emit power!
console.log("POWER!");
Message.Publish("power", true); Message.Publish("power", true);
} }

View File

@ -66,15 +66,12 @@ function main() {
} }
if (delta < watchRadius) { if (delta < watchRadius) {
console.log("Player is nearby snake! %d", delta);
nearby = true; nearby = true;
} }
// If we are idle and the player is jumping nearby... // If we are idle and the player is jumping nearby...
if (state == states.idle && nearby && Self.Grounded()) { if (state == states.idle && nearby && Self.Grounded()) {
if (playerPoint.Y - point.Y+(size.H/2) < 20) { if (playerPoint.Y - point.Y+(size.H/2) < 20) {
console.warn("Player is jumping near us!")
// Enter attack state. // Enter attack state.
if (time.Since(jumpCooldownStart) > 500 * time.Millisecond) { if (time.Since(jumpCooldownStart) > 500 * time.Millisecond) {
state = states.attacking; state = states.attacking;
@ -88,7 +85,6 @@ function main() {
// If we are attacking and gravity has claimed us back. // If we are attacking and gravity has claimed us back.
if (state === states.attacking && Self.Grounded()) { if (state === states.attacking && Self.Grounded()) {
console.log("Landed again after jump!");
state = states.idle; state = states.idle;
jumpCooldownStart = time.Now(); jumpCooldownStart = time.Now();
Self.StopAnimation(); Self.StopAnimation();

View File

@ -30,6 +30,7 @@ var (
CheatGodMode = "god mode" CheatGodMode = "god mode"
CheatDebugLoadScreen = "test load screen" CheatDebugLoadScreen = "test load screen"
CheatUnlockLevels = "master key" CheatUnlockLevels = "master key"
CheatSkipLevel = "warp whistle"
) )
// Global cheat boolean states. // Global cheat boolean states.

View File

@ -234,6 +234,10 @@ var (
Label: "Dotted paper", Label: "Dotted paper",
Value: "dots.png", Value: "dots.png",
}, },
{
Label: "Dotted paper (dark)",
Value: "dots-dark.png",
},
{ {
Label: "Blueprint", Label: "Blueprint",
Value: "blueprint.png", Value: "blueprint.png",

View File

@ -142,11 +142,6 @@ func (c Command) cheatCommand(d *Doodle) bool {
d.FlashError("Use this cheat in Play Mode to clear your inventory.") 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: case balance.CheatGodMode:
if isPlay { if isPlay {
d.Flash("God mode toggled") 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.") 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: default:
// See if it was an endorsed actor cheat. // See if it was an endorsed actor cheat.
if filename, ok := balance.CheatActors[strings.ToLower(c.Raw)]; ok { 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 we're setting the player character and in Play Mode, do it.
if setPlayerCharacter && isPlay { if setPlayerCharacter && isPlay {
playScene.SetCheated()
playScene.SetPlayerCharacter(balance.PlayerCharacterDoodad) playScene.SetPlayerCharacter(balance.PlayerCharacterDoodad)
} }

View File

@ -194,6 +194,11 @@ func (c *Chunk) ToBitmap(mask render.Color) image.Image {
for px := range c.Iter() { for px := range c.Iter() {
var color = px.Swatch.Color 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 the swatch has a pattern, mesh it in.
if px.Swatch.Pattern != "" { if px.Swatch.Pattern != "" {
color = pattern.SampleColor(px.Swatch.Pattern, color, px.Point()) color = pattern.SampleColor(px.Swatch.Pattern, color, px.Point())

View File

@ -10,6 +10,7 @@ var (
"Default", "Default",
"Colored Pencil", "Colored Pencil",
"Blueprint", "Blueprint",
"Neon Bright",
} }
DefaultPalettes = map[string]*Palette{ 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": { "Blueprint": {
Swatches: []*Swatch{ Swatches: []*Swatch{
{ {

View File

@ -588,12 +588,47 @@ func (s *MainScene) LoopLazyScroll() {
} }
} }
} else { } 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. // Lazy bounce algorithm.
if currentScroll.Y == lastScrollValue.Y { if bounceY {
log.Debug("LoopLazyScroll: Hit a floor/ceiling") log.Debug("LoopLazyScroll: Hit a floor/ceiling")
s.lazyScrollTrajectory.Y = -s.lazyScrollTrajectory.Y s.lazyScrollTrajectory.Y = -s.lazyScrollTrajectory.Y
} }
if currentScroll.X == lastScrollValue.X { if bounceX {
log.Debug("LoopLazyScroll: Hit the side of the map!") log.Debug("LoopLazyScroll: Hit the side of the map!")
s.lazyScrollTrajectory.X = -s.lazyScrollTrajectory.X s.lazyScrollTrajectory.X = -s.lazyScrollTrajectory.X
} }

View File

@ -207,8 +207,6 @@ func (s *PlayScene) setupAsync(d *Doodle) error {
// Initialize the drawing canvas. // Initialize the drawing canvas.
s.drawing = uix.NewCanvas(balance.ChunkSize, false) s.drawing = uix.NewCanvas(balance.ChunkSize, false)
s.drawing.Name = "play-canvas" s.drawing.Name = "play-canvas"
s.drawing.MoveTo(render.Origin)
s.drawing.Resize(render.NewRect(d.width, d.height))
s.drawing.Compute(d.Engine) s.drawing.Compute(d.Engine)
// Handler when an actor touches water or fire. // Handler when an actor touches water or fire.
@ -294,9 +292,46 @@ func (s *PlayScene) setupAsync(d *Doodle) error {
s.perfectRun = true s.perfectRun = true
s.running = 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 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 // SetPlayerCharacter changes the doodad used for the player, by destroying the
// current player character and making it from scratch. // current player character and making it from scratch.
func (s *PlayScene) SetPlayerCharacter(filename string) { func (s *PlayScene) SetPlayerCharacter(filename string) {
@ -682,8 +717,8 @@ func (s *PlayScene) Loop(d *Doodle, ev *event.State) error {
s.supervisor.Loop(ev) s.supervisor.Loop(ev)
// Has the window been resized? // Has the window been resized?
if ev.WindowResized { if ev.WindowResized || s.drawing.Point().IsZero() {
s.drawing.Resize(render.NewRect(d.width, d.height)) s.PlaceResizeCanvas()
s.screen.Resize(render.NewRect(d.width, d.height)) s.screen.Resize(render.NewRect(d.width, d.height))
return nil return nil
} }
@ -732,9 +767,9 @@ func (s *PlayScene) Draw(d *Doodle) error {
} }
// Clear the canvas and fill it with white. // 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()) s.drawing.Present(d.Engine, s.drawing.Point())
// Draw out bounding boxes. // Draw out bounding boxes.

View File

@ -458,8 +458,6 @@ func (w *Canvas) loopEditable(ev *event.State) error {
// See if any of the actors are below the mouse cursor. // See if any of the actors are below the mouse cursor.
var WP = w.WorldIndexAt(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{} var deleteActors = []*Actor{}
for _, actor := range w.actors { for _, actor := range w.actors {