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:
parent
75fa0c7e56
commit
9b75f1b039
BIN
assets/wallpapers/dots-dark.png
Normal file
BIN
assets/wallpapers/dots-dark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
|
@ -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 (
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
|
@ -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();
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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{
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user