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{
&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()

View File

@ -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,

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
// 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);
}

View File

@ -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();

View File

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

View File

@ -234,6 +234,10 @@ var (
Label: "Dotted paper",
Value: "dots.png",
},
{
Label: "Dotted paper (dark)",
Value: "dots-dark.png",
},
{
Label: "Blueprint",
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.")
}
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)
}

View File

@ -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())

View File

@ -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{
{

View File

@ -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
}

View File

@ -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.

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.
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 {