Chunker size to uint8 and Rectangular Doodads #84
26
Changes.md
26
Changes.md
|
@ -2,6 +2,22 @@
|
||||||
|
|
||||||
## v0.13.2 (TBD)
|
## v0.13.2 (TBD)
|
||||||
|
|
||||||
|
Some new features:
|
||||||
|
|
||||||
|
* **Doodads can be non-square!** You can now set a rectangular canvas size
|
||||||
|
for your doodads. Many of the game's built-in doodads that used to be
|
||||||
|
off-center before (doors, creatures) because their sprites were not squares
|
||||||
|
now have correct rectangular shapes.
|
||||||
|
* A **Cheats Menu** has been added which enables you to enter many of the
|
||||||
|
game's cheat codes by clicking on buttons instead. Enable it through the
|
||||||
|
"Experimental" tab of the Settings, and the cheats menu can be opened from
|
||||||
|
the Help menu bar during gameplay.
|
||||||
|
|
||||||
|
Other miscellaneous changes:
|
||||||
|
|
||||||
|
* The default Author name on your new drawings will prefer to use your
|
||||||
|
license registration name (if the game is registered) before falling back
|
||||||
|
on your operating system's $USER name like before.
|
||||||
* In the level editor, you can now use the Pan Tool to access the actor
|
* In the level editor, you can now use the Pan Tool to access the actor
|
||||||
properties of doodads you've dropped into your level. Similar to the
|
properties of doodads you've dropped into your level. Similar to the
|
||||||
Actor Tool, when you mouse-over an actor on your level it will highlight
|
Actor Tool, when you mouse-over an actor on your level it will highlight
|
||||||
|
@ -12,6 +28,16 @@
|
||||||
on your level as might happen with the Actor Tool!
|
on your level as might happen with the Actor Tool!
|
||||||
* Start distributing AppImage releases for GNU/Linux (64-bit and 32-bit)
|
* Start distributing AppImage releases for GNU/Linux (64-bit and 32-bit)
|
||||||
|
|
||||||
|
Some technical changes:
|
||||||
|
|
||||||
|
* Chunk sizes in levels/doodads is now a uint8 type, meaning the maximum
|
||||||
|
chunk size is 255x255 pixels. The game's default has always been 128x128
|
||||||
|
but now there is a limit. This takes a step towards optimizing the game's
|
||||||
|
file formats: large world coordinates (64-bit) are mapped to a chunk
|
||||||
|
coordinate, and if each chunk only needs to worry about the 255 pixels
|
||||||
|
in its territory, space can be saved in memory without chunks needing to
|
||||||
|
theoretically support 64-bit sizes of pixels!
|
||||||
|
|
||||||
## v0.13.1 (Oct 10 2022)
|
## v0.13.1 (Oct 10 2022)
|
||||||
|
|
||||||
This release brings a handful of minor new features to the game.
|
This release brings a handful of minor new features to the game.
|
||||||
|
|
13
bootstrap.py
13
bootstrap.py
|
@ -70,23 +70,22 @@ def main(fast=False):
|
||||||
|
|
||||||
def install_deps(fast):
|
def install_deps(fast):
|
||||||
"""Install system dependencies."""
|
"""Install system dependencies."""
|
||||||
if fast:
|
fast = " -y" if fast else ""
|
||||||
fast = " -y"
|
|
||||||
|
|
||||||
if shell("which rpm") == 0 and shell("which dnf") == 0:
|
if shell("which rpm") == 0 and shell("which dnf") == 0:
|
||||||
# Fedora-like.
|
# Fedora-like.
|
||||||
if shell("rpm -q {}".format(' '.join(dep_fedora))) != 0:
|
if shell("rpm -q {}".format(' '.join(dep_fedora))) != 0:
|
||||||
must_shell("sudo dnf install {}{}".format(' '.join(dep_fedora)), fast)
|
must_shell("sudo dnf install {}{}".format(' '.join(dep_fedora), fast))
|
||||||
elif shell("which brew") == 0:
|
elif shell("which brew") == 0:
|
||||||
# MacOS, as Catalina has an apt command now??
|
# MacOS, as Catalina has an apt command now??
|
||||||
must_shell("brew install {} {}".format(' '.join(dep_macos)), fast)
|
must_shell("brew install {} {}".format(' '.join(dep_macos), fast))
|
||||||
elif shell("which apt") == 0:
|
elif shell("which apt") == 0:
|
||||||
# Debian-like.
|
# Debian-like.
|
||||||
if shell("dpkg-query -l {}".format(' '.join(dep_debian))) != 0:
|
if shell("dpkg-query -l {}".format(' '.join(dep_debian))) != 0:
|
||||||
must_shell("sudo apt update && sudo apt install {}{}".format(' '.join(dep_debian)), fast)
|
must_shell("sudo apt update && sudo apt install {}{}".format(' '.join(dep_debian), fast))
|
||||||
elif shell("which pacman") == 0:
|
elif shell("which pacman") == 0:
|
||||||
# Arch-like.
|
# Arch-like.
|
||||||
must_shell("sudo pacman -S{} {}{}".format(fast, ' '.join(dep_arch)))
|
must_shell("sudo pacman -S{} {}".format(fast, ' '.join(dep_arch)))
|
||||||
else:
|
else:
|
||||||
print("Warning: didn't detect your package manager to install SDL2 and other dependencies")
|
print("Warning: didn't detect your package manager to install SDL2 and other dependencies")
|
||||||
|
|
||||||
|
@ -152,7 +151,7 @@ def must_shell(cmd):
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser("doodle bootstrap")
|
parser = argparse.ArgumentParser("doodle bootstrap")
|
||||||
parser.add_argument("fast", "f",
|
parser.add_argument("--fast", "-f",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Run the script non-interactively (yes to your package manager, git clone over https)",
|
help="Run the script non-interactively (yes to your package manager, git clone over https)",
|
||||||
)
|
)
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
|
||||||
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
|
||||||
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
||||||
|
"git.kirsle.net/SketchyMaze/doodle/pkg/native"
|
||||||
"git.kirsle.net/go/render"
|
"git.kirsle.net/go/render"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"golang.org/x/image/bmp"
|
"golang.org/x/image/bmp"
|
||||||
|
@ -104,7 +105,8 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
|
||||||
// Read the source images. Ensure they all have the same boundaries.
|
// Read the source images. Ensure they all have the same boundaries.
|
||||||
var (
|
var (
|
||||||
imageBounds image.Point
|
imageBounds image.Point
|
||||||
chunkSize int // the square shape for the Doodad chunk size
|
width int // dimensions of the incoming image
|
||||||
|
height int
|
||||||
images []image.Image
|
images []image.Image
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -129,11 +131,8 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
|
||||||
// Validate all images are the same size.
|
// Validate all images are the same size.
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
imageBounds = imageSize
|
imageBounds = imageSize
|
||||||
if imageSize.X > imageSize.Y {
|
width = imageSize.X
|
||||||
chunkSize = imageSize.X
|
height = imageSize.Y
|
||||||
} else {
|
|
||||||
chunkSize = imageSize.Y
|
|
||||||
}
|
|
||||||
} else if imageSize != imageBounds {
|
} else if imageSize != imageBounds {
|
||||||
return cli.Exit("your source images are not all the same dimensions", 1)
|
return cli.Exit("your source images are not all the same dimensions", 1)
|
||||||
}
|
}
|
||||||
|
@ -150,17 +149,18 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
|
||||||
// Generate the output drawing file.
|
// Generate the output drawing file.
|
||||||
switch strings.ToLower(filepath.Ext(outputFile)) {
|
switch strings.ToLower(filepath.Ext(outputFile)) {
|
||||||
case extDoodad:
|
case extDoodad:
|
||||||
log.Info("Output is a Doodad file (chunk size %d): %s", chunkSize, outputFile)
|
doodad := doodads.New(width, height)
|
||||||
doodad := doodads.New(chunkSize)
|
|
||||||
doodad.GameVersion = branding.Version
|
doodad.GameVersion = branding.Version
|
||||||
doodad.Title = c.String("title")
|
doodad.Title = c.String("title")
|
||||||
if doodad.Title == "" {
|
if doodad.Title == "" {
|
||||||
doodad.Title = "Converted Doodad"
|
doodad.Title = "Converted Doodad"
|
||||||
}
|
}
|
||||||
doodad.Author = os.Getenv("USER")
|
doodad.Author = native.DefaultAuthor()
|
||||||
|
|
||||||
// Write the first layer and gather its palette.
|
// Write the first layer and gather its palette.
|
||||||
log.Info("Converting first layer to drawing and getting the palette")
|
log.Info("Converting first layer to drawing and getting the palette")
|
||||||
|
var chunkSize = doodad.ChunkSize8()
|
||||||
|
log.Info("Output is a Doodad file (%dx%d): %s", width, height, outputFile)
|
||||||
palette, layer0 := imageToChunker(images[0], chroma, nil, chunkSize)
|
palette, layer0 := imageToChunker(images[0], chroma, nil, chunkSize)
|
||||||
doodad.Palette = palette
|
doodad.Palette = palette
|
||||||
doodad.Layers[0].Chunker = layer0
|
doodad.Layers[0].Chunker = layer0
|
||||||
|
@ -188,11 +188,14 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
|
||||||
|
|
||||||
lvl := level.New()
|
lvl := level.New()
|
||||||
lvl.GameVersion = branding.Version
|
lvl.GameVersion = branding.Version
|
||||||
|
lvl.MaxWidth = int64(width)
|
||||||
|
lvl.MaxHeight = int64(height)
|
||||||
|
lvl.PageType = level.Bounded
|
||||||
lvl.Title = c.String("title")
|
lvl.Title = c.String("title")
|
||||||
if lvl.Title == "" {
|
if lvl.Title == "" {
|
||||||
lvl.Title = "Converted Level"
|
lvl.Title = "Converted Level"
|
||||||
}
|
}
|
||||||
lvl.Author = os.Getenv("USER")
|
lvl.Author = native.DefaultAuthor()
|
||||||
palette, chunker := imageToChunker(images[0], chroma, nil, lvl.Chunker.Size)
|
palette, chunker := imageToChunker(images[0], chroma, nil, lvl.Chunker.Size)
|
||||||
lvl.Palette = palette
|
lvl.Palette = palette
|
||||||
lvl.Chunker = chunker
|
lvl.Chunker = chunker
|
||||||
|
@ -288,7 +291,7 @@ func drawingToImage(c *cli.Context, chroma render.Color, inputFiles []string, ou
|
||||||
//
|
//
|
||||||
// img: input image like a PNG
|
// img: input image like a PNG
|
||||||
// chroma: transparent color
|
// chroma: transparent color
|
||||||
func imageToChunker(img image.Image, chroma render.Color, palette *level.Palette, chunkSize int) (*level.Palette, *level.Chunker) {
|
func imageToChunker(img image.Image, chroma render.Color, palette *level.Palette, chunkSize uint8) (*level.Palette, *level.Chunker) {
|
||||||
var (
|
var (
|
||||||
chunker = level.NewChunker(chunkSize)
|
chunker = level.NewChunker(chunkSize)
|
||||||
bounds = img.Bounds()
|
bounds = img.Bounds()
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
|
||||||
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
|
||||||
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
||||||
"git.kirsle.net/go/render"
|
"git.kirsle.net/go/render"
|
||||||
|
@ -220,7 +221,13 @@ func editLevel(c *cli.Context, filename string) error {
|
||||||
// Handles the deep operation of re-copying the old level into a new level
|
// Handles the deep operation of re-copying the old level into a new level
|
||||||
// at the new chunk size.
|
// at the new chunk size.
|
||||||
func rechunkLevel(c *cli.Context, filename string, lvl *level.Level) error {
|
func rechunkLevel(c *cli.Context, filename string, lvl *level.Level) error {
|
||||||
var chunkSize = c.Int("resize")
|
var chunkSize = balance.ChunkSize
|
||||||
|
if v := c.Int("resize"); v != 0 {
|
||||||
|
if v > 255 {
|
||||||
|
return errors.New("chunk size must be a uint8 <= 255")
|
||||||
|
}
|
||||||
|
chunkSize = uint8(v)
|
||||||
|
}
|
||||||
log.Info("Resizing the level's chunk size.")
|
log.Info("Resizing the level's chunk size.")
|
||||||
log.Info("Current chunk size: %d", lvl.Chunker.Size)
|
log.Info("Current chunk size: %d", lvl.Chunker.Size)
|
||||||
log.Info("Target chunk size: %d", chunkSize)
|
log.Info("Target chunk size: %d", chunkSize)
|
||||||
|
|
|
@ -205,6 +205,7 @@ func showDoodad(c *cli.Context, filename string) error {
|
||||||
fmt.Printf(" Game version: %s\n", dd.GameVersion)
|
fmt.Printf(" Game version: %s\n", dd.GameVersion)
|
||||||
fmt.Printf(" Doodad title: %s\n", dd.Title)
|
fmt.Printf(" Doodad title: %s\n", dd.Title)
|
||||||
fmt.Printf(" Author: %s\n", dd.Author)
|
fmt.Printf(" Author: %s\n", dd.Author)
|
||||||
|
fmt.Printf(" Dimensions: %s\n", dd.Size)
|
||||||
fmt.Printf(" Hitbox: %s\n", dd.Hitbox)
|
fmt.Printf(" Hitbox: %s\n", dd.Hitbox)
|
||||||
fmt.Printf(" Locked: %+v\n", dd.Locked)
|
fmt.Printf(" Locked: %+v\n", dd.Locked)
|
||||||
fmt.Printf(" Hidden: %+v\n", dd.Hidden)
|
fmt.Printf(" Hidden: %+v\n", dd.Hidden)
|
||||||
|
@ -256,9 +257,12 @@ func showPalette(pal *level.Palette) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func showChunker(c *cli.Context, ch *level.Chunker) {
|
func showChunker(c *cli.Context, ch *level.Chunker) {
|
||||||
var worldSize = ch.WorldSize()
|
var (
|
||||||
var width = worldSize.W - worldSize.X
|
worldSize = ch.WorldSize()
|
||||||
var height = worldSize.H - worldSize.Y
|
chunkSize = int(ch.Size)
|
||||||
|
width = worldSize.W - worldSize.X
|
||||||
|
height = worldSize.H - worldSize.Y
|
||||||
|
)
|
||||||
fmt.Println("Chunks:")
|
fmt.Println("Chunks:")
|
||||||
fmt.Printf(" Pixels Per Chunk: %d^2\n", ch.Size)
|
fmt.Printf(" Pixels Per Chunk: %d^2\n", ch.Size)
|
||||||
fmt.Printf(" Number Generated: %d\n", len(ch.Chunks))
|
fmt.Printf(" Number Generated: %d\n", len(ch.Chunks))
|
||||||
|
@ -277,10 +281,10 @@ func showChunker(c *cli.Context, ch *level.Chunker) {
|
||||||
fmt.Printf(" - Coord: %s\n", point)
|
fmt.Printf(" - Coord: %s\n", point)
|
||||||
fmt.Printf(" Type: %s\n", chunkTypeToName(chunk.Type))
|
fmt.Printf(" Type: %s\n", chunkTypeToName(chunk.Type))
|
||||||
fmt.Printf(" Range: (%d,%d) ... (%d,%d)\n",
|
fmt.Printf(" Range: (%d,%d) ... (%d,%d)\n",
|
||||||
int(point.X)*ch.Size,
|
int(point.X)*chunkSize,
|
||||||
int(point.Y)*ch.Size,
|
int(point.Y)*chunkSize,
|
||||||
(int(point.X)*ch.Size)+ch.Size,
|
(int(point.X)*chunkSize)+chunkSize,
|
||||||
(int(point.Y)*ch.Size)+ch.Size,
|
(int(point.Y)*chunkSize)+chunkSize,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -43,7 +43,7 @@ var (
|
||||||
|
|
||||||
// Actor replacement cheats
|
// Actor replacement cheats
|
||||||
var CheatActors = map[string]string{
|
var CheatActors = map[string]string{
|
||||||
"pinocchio": "boy",
|
"pinocchio": PlayerCharacterDoodad,
|
||||||
"the cell": "azu-blu",
|
"the cell": "azu-blu",
|
||||||
"super azulian": "azu-red",
|
"super azulian": "azu-red",
|
||||||
"hyper azulian": "azu-white",
|
"hyper azulian": "azu-white",
|
||||||
|
|
|
@ -55,7 +55,7 @@ var (
|
||||||
FollowPlayerFirstTicks uint64 = 60
|
FollowPlayerFirstTicks uint64 = 60
|
||||||
|
|
||||||
// Default chunk size for canvases.
|
// Default chunk size for canvases.
|
||||||
ChunkSize = 128
|
ChunkSize uint8 = 128
|
||||||
|
|
||||||
// Default size for a new Doodad.
|
// Default size for a new Doodad.
|
||||||
DoodadSize = 100
|
DoodadSize = 100
|
||||||
|
|
|
@ -36,10 +36,15 @@ func (d *Doodle) MakeCheatsWindow(supervisor *ui.Supervisor) *ui.Window {
|
||||||
return d.Scene.Name()
|
return d.Scene.Name()
|
||||||
},
|
},
|
||||||
RunCommand: func(command string) {
|
RunCommand: func(command string) {
|
||||||
|
// If we are in Play Mode, every command out of here is cheating.
|
||||||
|
if playScene, ok := d.Scene.(*PlayScene); ok {
|
||||||
|
playScene.SetCheated()
|
||||||
|
}
|
||||||
d.shell.Execute(command)
|
d.shell.Execute(command)
|
||||||
},
|
},
|
||||||
OnSetPlayerCharacter: func(doodad string) {
|
OnSetPlayerCharacter: func(doodad string) {
|
||||||
if scene, ok := d.Scene.(*PlayScene); ok {
|
if scene, ok := d.Scene.(*PlayScene); ok {
|
||||||
|
scene.SetCheated()
|
||||||
scene.SetPlayerCharacter(doodad)
|
scene.SetPlayerCharacter(doodad)
|
||||||
} else {
|
} else {
|
||||||
shmem.FlashError("This only works during Play Mode.")
|
shmem.FlashError("This only works during Play Mode.")
|
||||||
|
|
|
@ -14,6 +14,7 @@ type Doodad struct {
|
||||||
Filename string `json:"-"` // used internally, not saved in json
|
Filename string `json:"-"` // used internally, not saved in json
|
||||||
Hidden bool `json:"hidden,omitempty"`
|
Hidden bool `json:"hidden,omitempty"`
|
||||||
Palette *level.Palette `json:"palette"`
|
Palette *level.Palette `json:"palette"`
|
||||||
|
Size render.Rect `json:"size"` // doodad dimensions
|
||||||
Script string `json:"script"`
|
Script string `json:"script"`
|
||||||
Hitbox render.Rect `json:"hitbox"`
|
Hitbox render.Rect `json:"hitbox"`
|
||||||
Layers []Layer `json:"layers"`
|
Layers []Layer `json:"layers"`
|
||||||
|
@ -30,10 +31,47 @@ type Layer struct {
|
||||||
Chunker *level.Chunker `json:"chunks"`
|
Chunker *level.Chunker `json:"chunks"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Doodad.
|
/*
|
||||||
func New(size int) *Doodad {
|
New creates a new Doodad.
|
||||||
|
|
||||||
|
You can give it one or two values for dimensions:
|
||||||
|
|
||||||
|
- New(size int) creates a square doodad (classic)
|
||||||
|
- New(width, height int) lets you have a different width x height.
|
||||||
|
*/
|
||||||
|
func New(dimensions ...int) *Doodad {
|
||||||
|
var (
|
||||||
|
// Defaults
|
||||||
|
size int
|
||||||
|
chunkSize uint8
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
)
|
||||||
|
|
||||||
|
switch len(dimensions) {
|
||||||
|
case 1:
|
||||||
|
width, height = dimensions[0], dimensions[0]
|
||||||
|
case 2:
|
||||||
|
width, height = dimensions[0], dimensions[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the desired chunkSize to be the largest dimension.
|
||||||
|
if width > height {
|
||||||
|
size = width
|
||||||
|
} else {
|
||||||
|
size = height
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no size at all, fall back on the default.
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
size = balance.DoodadSize
|
size = int(balance.ChunkSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pick an optimal chunk size - if our doodad size
|
||||||
|
// is under 256 use only one chunk per layer by matching
|
||||||
|
// that size.
|
||||||
|
if size <= 255 {
|
||||||
|
chunkSize = uint8(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Doodad{
|
return &Doodad{
|
||||||
|
@ -41,11 +79,12 @@ func New(size int) *Doodad {
|
||||||
Version: 1,
|
Version: 1,
|
||||||
},
|
},
|
||||||
Palette: level.DefaultPalette(),
|
Palette: level.DefaultPalette(),
|
||||||
Hitbox: render.NewRect(size, size),
|
Hitbox: render.NewRect(width, height),
|
||||||
|
Size: render.NewRect(width, height),
|
||||||
Layers: []Layer{
|
Layers: []Layer{
|
||||||
{
|
{
|
||||||
Name: "main",
|
Name: "main",
|
||||||
Chunker: level.NewChunker(size),
|
Chunker: level.NewChunker(chunkSize),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Tags: map[string]string{},
|
Tags: map[string]string{},
|
||||||
|
@ -59,7 +98,7 @@ func New(size int) *Doodad {
|
||||||
// is optional - pass nil and a new blank chunker is created.
|
// is optional - pass nil and a new blank chunker is created.
|
||||||
func (d *Doodad) AddLayer(name string, chunker *level.Chunker) Layer {
|
func (d *Doodad) AddLayer(name string, chunker *level.Chunker) Layer {
|
||||||
if chunker == nil {
|
if chunker == nil {
|
||||||
chunker = level.NewChunker(d.ChunkSize())
|
chunker = level.NewChunker(d.ChunkSize8())
|
||||||
}
|
}
|
||||||
|
|
||||||
layer := Layer{
|
layer := Layer{
|
||||||
|
@ -107,12 +146,17 @@ func (d *Doodad) Tag(name string) string {
|
||||||
|
|
||||||
// ChunkSize returns the chunk size of the Doodad's first layer.
|
// ChunkSize returns the chunk size of the Doodad's first layer.
|
||||||
func (d *Doodad) ChunkSize() int {
|
func (d *Doodad) ChunkSize() int {
|
||||||
|
return int(d.Layers[0].Chunker.Size)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChunkSize8 returns the chunk size of the Doodad's first layer as its actual uint8 value.
|
||||||
|
func (d *Doodad) ChunkSize8() uint8 {
|
||||||
return d.Layers[0].Chunker.Size
|
return d.Layers[0].Chunker.Size
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rect returns a rect of the ChunkSize for scaling a Canvas widget.
|
// Rect returns a rect of the ChunkSize for scaling a Canvas widget.
|
||||||
func (d *Doodad) Rect() render.Rect {
|
func (d *Doodad) Rect() render.Rect {
|
||||||
var size = d.ChunkSize()
|
var size = int(d.ChunkSize())
|
||||||
return render.Rect{
|
return render.Rect{
|
||||||
W: size,
|
W: size,
|
||||||
H: size,
|
H: size,
|
||||||
|
|
|
@ -6,8 +6,8 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
|
|
||||||
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
||||||
|
"git.kirsle.net/go/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ToZipfile serializes the doodad into zipfile format.
|
// ToZipfile serializes the doodad into zipfile format.
|
||||||
|
@ -63,7 +63,7 @@ func (d *Doodad) ToZipfile() ([]byte, error) {
|
||||||
// FromZipfile reads a doodad from zipfile format.
|
// FromZipfile reads a doodad from zipfile format.
|
||||||
func FromZipfile(data []byte) (*Doodad, error) {
|
func FromZipfile(data []byte) (*Doodad, error) {
|
||||||
var (
|
var (
|
||||||
doodad = New(balance.DoodadSize)
|
doodad = New(0)
|
||||||
err = doodad.populateFromZipfile(data)
|
err = doodad.populateFromZipfile(data)
|
||||||
)
|
)
|
||||||
return doodad, err
|
return doodad, err
|
||||||
|
@ -109,6 +109,13 @@ func (d *Doodad) populateFromZipfile(data []byte) error {
|
||||||
// Re-inflate data after saving a new zipfile.
|
// Re-inflate data after saving a new zipfile.
|
||||||
d.Inflate()
|
d.Inflate()
|
||||||
|
|
||||||
|
// If we are a legacy doodad and don't have a Size (width x height),
|
||||||
|
// set it from the chunk size.
|
||||||
|
if d.Size.IsZero() {
|
||||||
|
var size = d.ChunkSize()
|
||||||
|
d.Size = render.NewRect(size, size)
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ package doodle
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -291,32 +290,16 @@ func (d *Doodle) NewMap() {
|
||||||
|
|
||||||
// NewDoodad loads a new Doodad in Edit Mode.
|
// NewDoodad loads a new Doodad in Edit Mode.
|
||||||
// If size is zero, it prompts the user to select a size or accept the default size.
|
// If size is zero, it prompts the user to select a size or accept the default size.
|
||||||
func (d *Doodle) NewDoodad(size int) {
|
func (d *Doodle) NewDoodad(width, height int) {
|
||||||
if size == 0 {
|
if width+height == 0 {
|
||||||
d.Prompt(fmt.Sprintf("Doodad size or %d>", balance.DoodadSize), func(answer string) {
|
width = balance.DoodadSize
|
||||||
size := balance.DoodadSize
|
height = width
|
||||||
if answer != "" {
|
|
||||||
i, err := strconv.Atoi(answer)
|
|
||||||
if err != nil {
|
|
||||||
d.FlashError("Error: Doodad size must be a number.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
size = i
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recurse with the proper answer.
|
|
||||||
if size <= 0 {
|
|
||||||
d.FlashError("Error: Doodad size must be a positive number.")
|
|
||||||
}
|
|
||||||
d.NewDoodad(size)
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Starting a new doodad")
|
log.Info("Starting a new doodad")
|
||||||
scene := &EditorScene{
|
scene := &EditorScene{
|
||||||
DrawingType: enum.DoodadDrawing,
|
DrawingType: enum.DoodadDrawing,
|
||||||
DoodadSize: size,
|
DoodadSize: render.NewRect(width, height),
|
||||||
}
|
}
|
||||||
d.Goto(scene)
|
d.Goto(scene)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ package doodle
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -19,6 +18,7 @@ import (
|
||||||
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
||||||
"git.kirsle.net/SketchyMaze/doodle/pkg/modal"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/modal"
|
||||||
"git.kirsle.net/SketchyMaze/doodle/pkg/modal/loadscreen"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/modal/loadscreen"
|
||||||
|
"git.kirsle.net/SketchyMaze/doodle/pkg/native"
|
||||||
"git.kirsle.net/SketchyMaze/doodle/pkg/usercfg"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/usercfg"
|
||||||
"git.kirsle.net/SketchyMaze/doodle/pkg/userdir"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/userdir"
|
||||||
"git.kirsle.net/SketchyMaze/doodle/pkg/windows"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/windows"
|
||||||
|
@ -32,7 +32,7 @@ type EditorScene struct {
|
||||||
DrawingType enum.DrawingType
|
DrawingType enum.DrawingType
|
||||||
OpenFile bool
|
OpenFile bool
|
||||||
Filename string
|
Filename string
|
||||||
DoodadSize int
|
DoodadSize render.Rect
|
||||||
RememberScrollPosition render.Point // Play mode remembers it for us
|
RememberScrollPosition render.Point // Play mode remembers it for us
|
||||||
|
|
||||||
UI *EditorUI
|
UI *EditorUI
|
||||||
|
@ -195,7 +195,7 @@ func (s *EditorScene) setupAsync(d *Doodle) error {
|
||||||
// No Doodad?
|
// No Doodad?
|
||||||
if s.Doodad == nil {
|
if s.Doodad == nil {
|
||||||
log.Debug("EditorScene.Setup: initializing a new Doodad")
|
log.Debug("EditorScene.Setup: initializing a new Doodad")
|
||||||
s.Doodad = doodads.New(s.DoodadSize)
|
s.Doodad = doodads.New(s.DoodadSize.W, s.DoodadSize.H)
|
||||||
s.UI.Canvas.LoadDoodad(s.Doodad)
|
s.UI.Canvas.LoadDoodad(s.Doodad)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,7 +206,7 @@ func (s *EditorScene) setupAsync(d *Doodle) error {
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: move inside the UI. Just an approximate position for now.
|
// TODO: move inside the UI. Just an approximate position for now.
|
||||||
s.UI.Canvas.Resize(render.NewRect(s.DoodadSize, s.DoodadSize))
|
s.UI.Canvas.Resize(s.DoodadSize)
|
||||||
s.UI.Canvas.ScrollTo(render.Origin)
|
s.UI.Canvas.ScrollTo(render.Origin)
|
||||||
s.UI.Canvas.Scrollable = false
|
s.UI.Canvas.Scrollable = false
|
||||||
s.UI.Workspace.Compute(d.Engine)
|
s.UI.Workspace.Compute(d.Engine)
|
||||||
|
@ -512,7 +512,7 @@ func (s *EditorScene) SaveLevel(filename string) error {
|
||||||
m.Title = "Alpha"
|
m.Title = "Alpha"
|
||||||
}
|
}
|
||||||
if m.Author == "" {
|
if m.Author == "" {
|
||||||
m.Author = os.Getenv("USER")
|
m.Author = native.DefaultAuthor()
|
||||||
}
|
}
|
||||||
|
|
||||||
m.Palette = s.UI.Canvas.Palette
|
m.Palette = s.UI.Canvas.Palette
|
||||||
|
@ -574,7 +574,7 @@ func (s *EditorScene) LoadDoodad(filename string) error {
|
||||||
|
|
||||||
s.DrawingType = enum.DoodadDrawing
|
s.DrawingType = enum.DoodadDrawing
|
||||||
s.Doodad = doodad
|
s.Doodad = doodad
|
||||||
s.DoodadSize = doodad.Layers[0].Chunker.Size
|
s.DoodadSize = doodad.Size
|
||||||
s.UI.Canvas.LoadDoodad(s.Doodad)
|
s.UI.Canvas.LoadDoodad(s.Doodad)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -595,7 +595,7 @@ func (s *EditorScene) SaveDoodad(filename string) error {
|
||||||
d.Title = "Untitled Doodad"
|
d.Title = "Untitled Doodad"
|
||||||
}
|
}
|
||||||
if d.Author == "" {
|
if d.Author == "" {
|
||||||
d.Author = os.Getenv("USER")
|
d.Author = native.DefaultAuthor()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: is this copying necessary?
|
// TODO: is this copying necessary?
|
||||||
|
|
|
@ -46,7 +46,7 @@ func (u *EditorUI) startDragActor(doodad *doodads.Doodad, actor *level.Actor) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size and scale this doodad according to the zoom level.
|
// Size and scale this doodad according to the zoom level.
|
||||||
size := doodad.Rect()
|
size := doodad.Size
|
||||||
size.W = u.Canvas.ZoomMultiply(size.W)
|
size.W = u.Canvas.ZoomMultiply(size.W)
|
||||||
size.H = u.Canvas.ZoomMultiply(size.H)
|
size.H = u.Canvas.ZoomMultiply(size.H)
|
||||||
|
|
||||||
|
|
|
@ -52,12 +52,7 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar {
|
||||||
// File menu
|
// File menu
|
||||||
fileMenu := menu.AddMenu("File")
|
fileMenu := menu.AddMenu("File")
|
||||||
fileMenu.AddItemAccel("New level", "Ctrl-N", u.Scene.MenuNewLevel)
|
fileMenu.AddItemAccel("New level", "Ctrl-N", u.Scene.MenuNewLevel)
|
||||||
fileMenu.AddItem("New doodad", func() {
|
fileMenu.AddItem("New doodad", u.Scene.MenuNewDoodad)
|
||||||
u.Scene.ConfirmUnload(func() {
|
|
||||||
// New doodad size with prompt.
|
|
||||||
d.NewDoodad(0)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
fileMenu.AddItemAccel("Save", "Ctrl-S", u.Scene.MenuSave(false))
|
fileMenu.AddItemAccel("Save", "Ctrl-S", u.Scene.MenuSave(false))
|
||||||
fileMenu.AddItemAccel("Save as...", "Shift-Ctrl-S", func() {
|
fileMenu.AddItemAccel("Save as...", "Shift-Ctrl-S", func() {
|
||||||
d.Prompt("Save as filename>", func(answer string) {
|
d.Prompt("Save as filename>", func(answer string) {
|
||||||
|
@ -285,6 +280,13 @@ func (s *EditorScene) MenuNewLevel() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *EditorScene) MenuNewDoodad() {
|
||||||
|
s.ConfirmUnload(func() {
|
||||||
|
// New doodad size with prompt.
|
||||||
|
s.d.GotoNewDoodadMenu()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// File->Open, or Ctrl-O
|
// File->Open, or Ctrl-O
|
||||||
func (s *EditorScene) MenuOpen() {
|
func (s *EditorScene) MenuOpen() {
|
||||||
s.ConfirmUnload(func() {
|
s.ConfirmUnload(func() {
|
||||||
|
|
|
@ -83,11 +83,11 @@ func (u *EditorUI) setupPaletteFrame(window *ui.Window) *ui.Frame {
|
||||||
if u.Canvas != nil && u.Canvas.Palette != nil {
|
if u.Canvas != nil && u.Canvas.Palette != nil {
|
||||||
for i, swatch := range u.Canvas.Palette.Swatches {
|
for i, swatch := range u.Canvas.Palette.Swatches {
|
||||||
swatch := swatch
|
swatch := swatch
|
||||||
var width = buttonSize
|
var width = uint8(buttonSize) // TODO: dangerous - buttonSize must be small
|
||||||
|
|
||||||
// Drawing buttons in two-column mode? (default right-side palette layout)
|
// Drawing buttons in two-column mode? (default right-side palette layout)
|
||||||
if twoColumn {
|
if twoColumn {
|
||||||
width = buttonSize / 2
|
width /= 2
|
||||||
if row == nil || i%2 == 0 {
|
if row == nil || i%2 == 0 {
|
||||||
row = ui.NewFrame(fmt.Sprintf("Swatch(%s) Button Frame", swatch.Name))
|
row = ui.NewFrame(fmt.Sprintf("Swatch(%s) Button Frame", swatch.Name))
|
||||||
frame.Pack(row, packConfig)
|
frame.Pack(row, packConfig)
|
||||||
|
@ -101,7 +101,8 @@ func (u *EditorUI) setupPaletteFrame(window *ui.Window) *ui.Frame {
|
||||||
var (
|
var (
|
||||||
colorbox = uix.NewCanvas(width, false)
|
colorbox = uix.NewCanvas(width, false)
|
||||||
chunker = level.NewChunker(width)
|
chunker = level.NewChunker(width)
|
||||||
size = render.NewRect(width, width)
|
iw = int(width)
|
||||||
|
size = render.NewRect(iw, iw)
|
||||||
)
|
)
|
||||||
chunker.SetRect(size, swatch)
|
chunker.SetRect(size, swatch)
|
||||||
colorbox.Resize(size)
|
colorbox.Resize(size)
|
||||||
|
|
|
@ -27,7 +27,7 @@ type Chunk struct {
|
||||||
|
|
||||||
// Values told to it from higher up, not stored in JSON.
|
// Values told to it from higher up, not stored in JSON.
|
||||||
Point render.Point
|
Point render.Point
|
||||||
Size int
|
Size uint8
|
||||||
|
|
||||||
// Texture cache properties so we don't redraw pixel-by-pixel every frame.
|
// Texture cache properties so we don't redraw pixel-by-pixel every frame.
|
||||||
uuid uuid.UUID
|
uuid uuid.UUID
|
||||||
|
@ -157,14 +157,17 @@ func (c *Chunk) generateTexture(mask render.Color) (render.Texturer, error) {
|
||||||
// want a cached bitmap image that only generates itself once, and
|
// want a cached bitmap image that only generates itself once, and
|
||||||
// again when marked dirty.
|
// again when marked dirty.
|
||||||
func (c *Chunk) ToBitmap(mask render.Color) image.Image {
|
func (c *Chunk) ToBitmap(mask render.Color) image.Image {
|
||||||
canvas := c.SizePositive()
|
var (
|
||||||
imgSize := image.Rectangle{
|
size = int(c.Size)
|
||||||
Min: image.Point{},
|
canvas = c.SizePositive()
|
||||||
Max: image.Point{
|
imgSize = image.Rectangle{
|
||||||
X: c.Size,
|
Min: image.Point{},
|
||||||
Y: c.Size,
|
Max: image.Point{
|
||||||
},
|
X: size,
|
||||||
}
|
Y: size,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if imgSize.Max.X == 0 {
|
if imgSize.Max.X == 0 {
|
||||||
imgSize.Max.X = int(canvas.W)
|
imgSize.Max.X = int(canvas.W)
|
||||||
|
@ -186,8 +189,8 @@ func (c *Chunk) ToBitmap(mask render.Color) image.Image {
|
||||||
// Pixel coordinate offset to map the Chunk World Position to the
|
// Pixel coordinate offset to map the Chunk World Position to the
|
||||||
// smaller image boundaries.
|
// smaller image boundaries.
|
||||||
pointOffset := render.Point{
|
pointOffset := render.Point{
|
||||||
X: c.Point.X * c.Size,
|
X: c.Point.X * size,
|
||||||
Y: c.Point.Y * c.Size,
|
Y: c.Point.Y * size,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blot all the pixels onto it.
|
// Blot all the pixels onto it.
|
||||||
|
|
|
@ -20,8 +20,8 @@ type Chunker struct {
|
||||||
// Layer is optional for the caller, levels use only 0 and
|
// Layer is optional for the caller, levels use only 0 and
|
||||||
// doodads use them for frames. When chunks are exported to
|
// doodads use them for frames. When chunks are exported to
|
||||||
// zipfile the Layer keeps them from overlapping.
|
// zipfile the Layer keeps them from overlapping.
|
||||||
Layer int `json:"-"` // internal use only
|
Layer int `json:"-"` // internal use only
|
||||||
Size int `json:"size"`
|
Size uint8 `json:"size"`
|
||||||
|
|
||||||
// A Zipfile reference for new-style levels and doodads which
|
// A Zipfile reference for new-style levels and doodads which
|
||||||
// keep their chunks in external parts of a zip file.
|
// keep their chunks in external parts of a zip file.
|
||||||
|
@ -51,7 +51,7 @@ type Chunker struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewChunker creates a new chunk manager with a given chunk size.
|
// NewChunker creates a new chunk manager with a given chunk size.
|
||||||
func NewChunker(size int) *Chunker {
|
func NewChunker(size uint8) *Chunker {
|
||||||
return &Chunker{
|
return &Chunker{
|
||||||
Size: size,
|
Size: size,
|
||||||
Chunks: ChunkMap{},
|
Chunks: ChunkMap{},
|
||||||
|
@ -186,10 +186,13 @@ func (c *Chunker) IterCachedChunks() <-chan *Chunk {
|
||||||
func (c *Chunker) IterViewportChunks(viewport render.Rect) <-chan render.Point {
|
func (c *Chunker) IterViewportChunks(viewport render.Rect) <-chan render.Point {
|
||||||
pipe := make(chan render.Point)
|
pipe := make(chan render.Point)
|
||||||
go func() {
|
go func() {
|
||||||
sent := make(map[render.Point]interface{})
|
var (
|
||||||
|
sent = make(map[render.Point]interface{})
|
||||||
|
size = int(c.Size)
|
||||||
|
)
|
||||||
|
|
||||||
for x := viewport.X; x < viewport.W; x += (c.Size / 4) {
|
for x := viewport.X; x < viewport.W; x += (size / 4) {
|
||||||
for y := viewport.Y; y < viewport.H; y += (c.Size / 4) {
|
for y := viewport.Y; y < viewport.H; y += (size / 4) {
|
||||||
|
|
||||||
// Constrain this chunksize step to a point within the bounds
|
// Constrain this chunksize step to a point within the bounds
|
||||||
// of the viewport. This can yield partial chunks on the edges
|
// of the viewport. This can yield partial chunks on the edges
|
||||||
|
@ -243,12 +246,16 @@ func (c *Chunker) IterPixels() <-chan Pixel {
|
||||||
// manage: the lowest pixels from the lowest chunks to the highest pixels of
|
// manage: the lowest pixels from the lowest chunks to the highest pixels of
|
||||||
// the highest chunks.
|
// the highest chunks.
|
||||||
func (c *Chunker) WorldSize() render.Rect {
|
func (c *Chunker) WorldSize() render.Rect {
|
||||||
chunkLowest, chunkHighest := c.Bounds()
|
var (
|
||||||
|
size = int(c.Size)
|
||||||
|
chunkLowest, chunkHighest = c.Bounds()
|
||||||
|
)
|
||||||
|
|
||||||
return render.Rect{
|
return render.Rect{
|
||||||
X: chunkLowest.X * c.Size,
|
X: chunkLowest.X * size,
|
||||||
Y: chunkLowest.Y * c.Size,
|
Y: chunkLowest.Y * size,
|
||||||
W: (chunkHighest.X * c.Size) + (c.Size - 1),
|
W: (chunkHighest.X * size) + (size - 1),
|
||||||
H: (chunkHighest.Y * c.Size) + (c.Size - 1),
|
H: (chunkHighest.Y * size) + (size - 1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ func GiantScreenshot(lvl *level.Level) (image.Image, error) {
|
||||||
// How big will our image be?
|
// How big will our image be?
|
||||||
var (
|
var (
|
||||||
size = lvl.Chunker.WorldSizePositive()
|
size = lvl.Chunker.WorldSizePositive()
|
||||||
chunkSize = lvl.Chunker.Size
|
chunkSize = int(lvl.Chunker.Size)
|
||||||
chunkLow, chunkHigh = lvl.Chunker.Bounds()
|
chunkLow, chunkHigh = lvl.Chunker.Bounds()
|
||||||
worldSize = render.Rect{
|
worldSize = render.Rect{
|
||||||
X: chunkLow.X,
|
X: chunkLow.X,
|
||||||
|
|
|
@ -4,12 +4,12 @@ import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
|
|
||||||
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
|
||||||
"git.kirsle.net/SketchyMaze/doodle/pkg/drawtool"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/drawtool"
|
||||||
"git.kirsle.net/SketchyMaze/doodle/pkg/enum"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/enum"
|
||||||
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
||||||
|
"git.kirsle.net/SketchyMaze/doodle/pkg/native"
|
||||||
"git.kirsle.net/go/render"
|
"git.kirsle.net/go/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ func New() *Level {
|
||||||
Base: Base{
|
Base: Base{
|
||||||
Version: 1,
|
Version: 1,
|
||||||
Title: "Untitled",
|
Title: "Untitled",
|
||||||
Author: os.Getenv("USER"),
|
Author: native.DefaultAuthor(),
|
||||||
Files: NewFileSystem(),
|
Files: NewFileSystem(),
|
||||||
},
|
},
|
||||||
Chunker: NewChunker(balance.ChunkSize),
|
Chunker: NewChunker(balance.ChunkSize),
|
||||||
|
|
|
@ -24,6 +24,7 @@ on the MainScene or elsewhere as wanted.
|
||||||
type MenuScene struct {
|
type MenuScene struct {
|
||||||
// Configuration.
|
// Configuration.
|
||||||
StartupMenu string
|
StartupMenu string
|
||||||
|
NewDoodad bool
|
||||||
|
|
||||||
Supervisor *ui.Supervisor
|
Supervisor *ui.Supervisor
|
||||||
|
|
||||||
|
@ -60,6 +61,17 @@ func (d *Doodle) GotoNewMenu() {
|
||||||
d.Goto(scene)
|
d.Goto(scene)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GotoNewDoodadMenu loads the MenuScene and shows the "New" window,
|
||||||
|
// but selected on the Doodad tab by default.
|
||||||
|
func (d *Doodle) GotoNewDoodadMenu() {
|
||||||
|
log.Info("Loading the MenuScene to the New window")
|
||||||
|
scene := &MenuScene{
|
||||||
|
StartupMenu: "new",
|
||||||
|
NewDoodad: true,
|
||||||
|
}
|
||||||
|
d.Goto(scene)
|
||||||
|
}
|
||||||
|
|
||||||
// GotoLoadMenu loads the MenuScene and shows the "Load" window.
|
// GotoLoadMenu loads the MenuScene and shows the "Load" window.
|
||||||
func (d *Doodle) GotoLoadMenu() {
|
func (d *Doodle) GotoLoadMenu() {
|
||||||
log.Info("Loading the MenuScene to the Load window for Edit Mode")
|
log.Info("Loading the MenuScene to the Load window for Edit Mode")
|
||||||
|
@ -152,6 +164,7 @@ func (s *MenuScene) setupNewWindow(d *Doodle) error {
|
||||||
window := windows.NewAddEditLevel(windows.AddEditLevel{
|
window := windows.NewAddEditLevel(windows.AddEditLevel{
|
||||||
Supervisor: s.Supervisor,
|
Supervisor: s.Supervisor,
|
||||||
Engine: d.Engine,
|
Engine: d.Engine,
|
||||||
|
NewDoodad: s.NewDoodad,
|
||||||
OnChangePageTypeAndWallpaper: func(pageType level.PageType, wallpaper string) {
|
OnChangePageTypeAndWallpaper: func(pageType level.PageType, wallpaper string) {
|
||||||
log.Info("OnChangePageTypeAndWallpaper called: %+v, %+v", pageType, wallpaper)
|
log.Info("OnChangePageTypeAndWallpaper called: %+v, %+v", pageType, wallpaper)
|
||||||
s.canvas.Destroy() // clean up old textures
|
s.canvas.Destroy() // clean up old textures
|
||||||
|
@ -163,8 +176,8 @@ func (s *MenuScene) setupNewWindow(d *Doodle) error {
|
||||||
Level: lvl,
|
Level: lvl,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
OnCreateNewDoodad: func(size int) {
|
OnCreateNewDoodad: func(width, height int) {
|
||||||
d.NewDoodad(size)
|
d.NewDoodad(width, height)
|
||||||
},
|
},
|
||||||
OnCancel: func() {
|
OnCancel: func() {
|
||||||
d.Goto(&MainScene{})
|
d.Goto(&MainScene{})
|
||||||
|
|
29
pkg/native/username.go
Normal file
29
pkg/native/username.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package native
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.kirsle.net/SketchyMaze/doodle/pkg/license"
|
||||||
|
)
|
||||||
|
|
||||||
|
var USER string = os.Getenv("USER")
|
||||||
|
|
||||||
|
/*
|
||||||
|
DefaultAuthor will return the local user's name to be the default Author
|
||||||
|
for levels and doodads they create.
|
||||||
|
|
||||||
|
If they have registered the game, use the name from their license JWT token.
|
||||||
|
|
||||||
|
Otherwise fall back to their native operating system user.
|
||||||
|
*/
|
||||||
|
func DefaultAuthor() string {
|
||||||
|
// Are we registered?
|
||||||
|
if license.IsRegistered() {
|
||||||
|
if reg, err := license.GetRegistration(); err == nil {
|
||||||
|
return reg.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return OS username
|
||||||
|
return os.Getenv("USER")
|
||||||
|
}
|
|
@ -69,7 +69,7 @@ func (s *PlayScene) computeInventory() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas := uix.NewCanvas(doodad.ChunkSize(), false)
|
canvas := uix.NewCanvas(doodad.ChunkSize8(), false)
|
||||||
canvas.SetBackground(render.RGBA(1, 0, 0, 0))
|
canvas.SetBackground(render.RGBA(1, 0, 0, 0))
|
||||||
canvas.LoadDoodad(doodad)
|
canvas.LoadDoodad(doodad)
|
||||||
canvas.Resize(render.NewRect(
|
canvas.Resize(render.NewRect(
|
||||||
|
|
|
@ -348,6 +348,11 @@ func (s *PlayScene) PlaceResizeCanvas() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Canvas returns the main level canvas - useful to call from the debug console as `d.Scene.Canvas()`
|
||||||
|
func (s *PlayScene) Canvas() *uix.Canvas {
|
||||||
|
return s.drawing
|
||||||
|
}
|
||||||
|
|
||||||
// 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) {
|
||||||
|
@ -471,10 +476,10 @@ func (s *PlayScene) installPlayerDoodad(filename string, spawn render.Point, cen
|
||||||
// Center the player within the box of the doodad, for the Start Flag especially.
|
// Center the player within the box of the doodad, for the Start Flag especially.
|
||||||
if !centerIn.IsZero() {
|
if !centerIn.IsZero() {
|
||||||
spawn = render.NewPoint(
|
spawn = render.NewPoint(
|
||||||
spawn.X+(centerIn.W/2)-(player.Layers[0].Chunker.Size/2),
|
spawn.X+(centerIn.W/2)-(player.ChunkSize()/2),
|
||||||
|
|
||||||
// Y: the bottom of the flag, 4 pixels from the floor.
|
// Y: the bottom of the flag, 4 pixels from the floor.
|
||||||
spawn.Y+centerIn.H-4-(player.Layers[0].Chunker.Size),
|
spawn.Y+centerIn.H-4-(player.ChunkSize()),
|
||||||
)
|
)
|
||||||
} else if spawn.IsZero() && !s.SpawnPoint.IsZero() {
|
} else if spawn.IsZero() && !s.SpawnPoint.IsZero() {
|
||||||
spawn = s.SpawnPoint
|
spawn = s.SpawnPoint
|
||||||
|
@ -638,6 +643,11 @@ func (s *PlayScene) GetCheated() bool {
|
||||||
return s.cheated
|
return s.cheated
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPerfect gives read-only access to the perfectRun flag.
|
||||||
|
func (s *PlayScene) GetPerfect() bool {
|
||||||
|
return s.perfectRun
|
||||||
|
}
|
||||||
|
|
||||||
// ShowEndLevelModal centralizes the EndLevel modal config.
|
// ShowEndLevelModal centralizes the EndLevel modal config.
|
||||||
// This is the common handler function between easy methods such as
|
// This is the common handler function between easy methods such as
|
||||||
// BeatLevel, FailLevel, and DieByFire.
|
// BeatLevel, FailLevel, and DieByFire.
|
||||||
|
@ -828,6 +838,13 @@ func (s *PlayScene) Draw(d *Doodle) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bug: sometimes (especially after cheating) if you restart a level
|
||||||
|
// properly, cheated=false perfectRun=true but the perfectRunIcon
|
||||||
|
// would not be showing.
|
||||||
|
if !s.cheated && s.perfectRun && s.timerPerfectImage.Hidden() {
|
||||||
|
s.timerPerfectImage.Show()
|
||||||
|
}
|
||||||
|
|
||||||
// Draw the UI screen and any widgets that attached to it.
|
// Draw the UI screen and any widgets that attached to it.
|
||||||
s.screen.Compute(d.Engine)
|
s.screen.Compute(d.Engine)
|
||||||
s.screen.Present(d.Engine, render.Origin)
|
s.screen.Present(d.Engine, render.Origin)
|
||||||
|
|
|
@ -19,11 +19,11 @@ import (
|
||||||
// Actor is an object that marries together the three things that make a
|
// Actor is an object that marries together the three things that make a
|
||||||
// Doodad instance "tick" while inside a Canvas:
|
// Doodad instance "tick" while inside a Canvas:
|
||||||
//
|
//
|
||||||
// - uix.Actor is a doodads.Drawing so it fulfills doodads.Actor to be a
|
// - uix.Actor is a doodads.Drawing so it fulfills doodads.Actor to be a
|
||||||
// dynamic object during gameplay.
|
// dynamic object during gameplay.
|
||||||
// - It has a pointer to the level.Actor indicating its static level data
|
// - It has a pointer to the level.Actor indicating its static level data
|
||||||
// as defined in the map: its spawn coordinate and configuration.
|
// as defined in the map: its spawn coordinate and configuration.
|
||||||
// - A uix.Canvas that can present the actor's graphics to the screen.
|
// - A uix.Canvas that can present the actor's graphics to the screen.
|
||||||
type Actor struct {
|
type Actor struct {
|
||||||
Drawing *doodads.Drawing
|
Drawing *doodads.Drawing
|
||||||
Actor *level.Actor
|
Actor *level.Actor
|
||||||
|
@ -67,8 +67,8 @@ func NewActor(id string, levelActor *level.Actor, doodad *doodads.Doodad) *Actor
|
||||||
id = uuid.Must(uuid.NewUUID()).String()
|
id = uuid.Must(uuid.NewUUID()).String()
|
||||||
}
|
}
|
||||||
|
|
||||||
size := doodad.Layers[0].Chunker.Size
|
size := doodad.ChunkSize()
|
||||||
can := NewCanvas(int(size), false)
|
can := NewCanvas(uint8(size), false)
|
||||||
can.Name = id
|
can.Name = id
|
||||||
|
|
||||||
// TODO: if the Background is render.Invisible it gets defaulted to
|
// TODO: if the Background is render.Invisible it gets defaulted to
|
||||||
|
@ -76,7 +76,7 @@ func NewActor(id string, levelActor *level.Actor, doodad *doodads.Doodad) *Actor
|
||||||
can.SetBackground(render.RGBA(0, 0, 1, 0))
|
can.SetBackground(render.RGBA(0, 0, 1, 0))
|
||||||
|
|
||||||
can.LoadDoodad(doodad)
|
can.LoadDoodad(doodad)
|
||||||
can.Resize(render.NewRect(size, size))
|
can.Resize(doodad.Size)
|
||||||
|
|
||||||
actor := &Actor{
|
actor := &Actor{
|
||||||
Drawing: doodads.NewDrawing(id, doodad),
|
Drawing: doodads.NewDrawing(id, doodad),
|
||||||
|
@ -164,7 +164,7 @@ func (a *Actor) SetWet(v bool) {
|
||||||
|
|
||||||
// Size returns the size of the actor, from the underlying doodads.Drawing.
|
// Size returns the size of the actor, from the underlying doodads.Drawing.
|
||||||
func (a *Actor) Size() render.Rect {
|
func (a *Actor) Size() render.Rect {
|
||||||
return a.Drawing.Size()
|
return a.Drawing.Doodad.Size
|
||||||
}
|
}
|
||||||
|
|
||||||
// Velocity returns the actor's current velocity vector.
|
// Velocity returns the actor's current velocity vector.
|
||||||
|
|
|
@ -36,6 +36,11 @@ type Canvas struct {
|
||||||
Scrollable bool // Cursor keys will scroll the viewport of this canvas.
|
Scrollable bool // Cursor keys will scroll the viewport of this canvas.
|
||||||
Zoom int // Zoom level on the canvas.
|
Zoom int // Zoom level on the canvas.
|
||||||
|
|
||||||
|
// Set this if your Canvas is a small fixed size (e.g. in doodad dropper),
|
||||||
|
// so that doodads will crop their texture (if chunk size larger than your
|
||||||
|
// Canvas) as to not overflow the canvas bounds. Not needed for Level canvases.
|
||||||
|
CroppedSize bool
|
||||||
|
|
||||||
// Toogle for doodad canvases in the Level Editor to show their buttons.
|
// Toogle for doodad canvases in the Level Editor to show their buttons.
|
||||||
ShowDoodadButtons bool
|
ShowDoodadButtons bool
|
||||||
doodadButtonFrame ui.Widget // lazy init
|
doodadButtonFrame ui.Widget // lazy init
|
||||||
|
@ -132,15 +137,17 @@ type Canvas struct {
|
||||||
|
|
||||||
// NewCanvas initializes a Canvas widget.
|
// NewCanvas initializes a Canvas widget.
|
||||||
//
|
//
|
||||||
|
// size is the Chunker size (uint8)
|
||||||
|
//
|
||||||
// If editable is true, Scrollable is also set to true, which means the arrow
|
// If editable is true, Scrollable is also set to true, which means the arrow
|
||||||
// keys will scroll the canvas viewport which is desirable in Edit Mode.
|
// keys will scroll the canvas viewport which is desirable in Edit Mode.
|
||||||
func NewCanvas(size int, editable bool) *Canvas {
|
func NewCanvas(size uint8, editable bool) *Canvas {
|
||||||
w := &Canvas{
|
w := &Canvas{
|
||||||
Editable: editable,
|
Editable: editable,
|
||||||
Scrollable: editable,
|
Scrollable: editable,
|
||||||
Palette: level.NewPalette(),
|
Palette: level.NewPalette(),
|
||||||
BrushSize: 1,
|
BrushSize: 1,
|
||||||
chunks: level.NewChunker(size),
|
chunks: level.NewChunker(uint8(size)),
|
||||||
actors: make([]*Actor, 0),
|
actors: make([]*Actor, 0),
|
||||||
wallpaper: &Wallpaper{},
|
wallpaper: &Wallpaper{},
|
||||||
|
|
||||||
|
@ -372,7 +379,7 @@ func (w *Canvas) ViewportRelative() render.Rect {
|
||||||
// levels under control.
|
// levels under control.
|
||||||
func (w *Canvas) LoadingViewport() render.Rect {
|
func (w *Canvas) LoadingViewport() render.Rect {
|
||||||
var (
|
var (
|
||||||
chunkSize int
|
chunkSize uint8
|
||||||
vp = w.Viewport()
|
vp = w.Viewport()
|
||||||
margin = balance.LoadingViewportMarginChunks
|
margin = balance.LoadingViewportMarginChunks
|
||||||
)
|
)
|
||||||
|
@ -381,17 +388,18 @@ func (w *Canvas) LoadingViewport() render.Rect {
|
||||||
if w.level != nil {
|
if w.level != nil {
|
||||||
chunkSize = w.level.Chunker.Size
|
chunkSize = w.level.Chunker.Size
|
||||||
} else if w.doodad != nil {
|
} else if w.doodad != nil {
|
||||||
chunkSize = w.doodad.ChunkSize()
|
chunkSize = w.doodad.ChunkSize8()
|
||||||
} else {
|
} else {
|
||||||
chunkSize = balance.ChunkSize
|
chunkSize = balance.ChunkSize
|
||||||
log.Error("Canvas.LoadingViewport: no drawing to get chunk size from, default to %d", chunkSize)
|
log.Error("Canvas.LoadingViewport: no drawing to get chunk size from, default to %d", chunkSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var size = int(chunkSize)
|
||||||
return render.Rect{
|
return render.Rect{
|
||||||
X: vp.X - chunkSize*margin.X,
|
X: vp.X - size*margin.X,
|
||||||
Y: vp.Y - chunkSize*margin.Y,
|
Y: vp.Y - size*margin.Y,
|
||||||
W: vp.W + chunkSize*margin.X,
|
W: vp.W + size*margin.X,
|
||||||
H: vp.H + chunkSize*margin.Y,
|
H: vp.H + size*margin.Y,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -220,7 +220,7 @@ func (w *Canvas) drawActors(e render.Engine, p render.Point) {
|
||||||
// Hitting the left edge. Cap the X coord and shrink the width.
|
// Hitting the left edge. Cap the X coord and shrink the width.
|
||||||
delta := p.X - drawAt.X // positive number
|
delta := p.X - drawAt.X // positive number
|
||||||
drawAt.X = p.X
|
drawAt.X = p.X
|
||||||
// scrollTo.X -= delta / 2 // TODO
|
scrollTo.X -= delta
|
||||||
resizeTo.W -= delta
|
resizeTo.W -= delta
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,6 +232,7 @@ func (w *Canvas) drawActors(e render.Engine, p render.Point) {
|
||||||
// Hitting the top edge. Cap the Y coord and shrink the height.
|
// Hitting the top edge. Cap the Y coord and shrink the height.
|
||||||
delta := p.Y - drawAt.Y
|
delta := p.Y - drawAt.Y
|
||||||
drawAt.Y = p.Y
|
drawAt.Y = p.Y
|
||||||
|
scrollTo.Y -= delta
|
||||||
resizeTo.H -= delta
|
resizeTo.H -= delta
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
26
pkg/uix/canvas_debug.go
Normal file
26
pkg/uix/canvas_debug.go
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package uix
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// Some debugging functions for the Canvas reachable via dev console in-game.
|
||||||
|
|
||||||
|
// GetCanvasesByActorName searches a (level) canvas's installed actors and returns any of
|
||||||
|
// them having this Title or Filename, with filename being more precise.
|
||||||
|
func (c *Canvas) GetCanvasesByActorName(filename string) []*Canvas {
|
||||||
|
var (
|
||||||
|
byFilename = []*Canvas{}
|
||||||
|
byTitle = []*Canvas{}
|
||||||
|
lower = strings.ToLower(filename)
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, a := range c.actors {
|
||||||
|
var doodad = a.Doodad()
|
||||||
|
if doodad.Filename == filename {
|
||||||
|
byFilename = append(byFilename, a.Canvas)
|
||||||
|
} else if strings.ToLower(doodad.Title) == lower {
|
||||||
|
byTitle = append(byTitle, a.Canvas)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(byFilename, byTitle...)
|
||||||
|
}
|
|
@ -114,16 +114,21 @@ func (w *Canvas) Present(e render.Engine, p render.Point) {
|
||||||
// into which it will render, cap the source width and height.
|
// into which it will render, cap the source width and height.
|
||||||
// This is especially useful for Doodad buttons because the drawing
|
// This is especially useful for Doodad buttons because the drawing
|
||||||
// is bigger than the button.
|
// is bigger than the button.
|
||||||
if src.W > S.W {
|
if w.CroppedSize {
|
||||||
src.W = S.W
|
// NOTE: this is a concern mainly for the Doodad Dropper so that
|
||||||
}
|
// the doodads won't overflow the button size they appear in.
|
||||||
if src.H > S.H {
|
if src.W > S.W {
|
||||||
src.H = S.H
|
src.W = S.W
|
||||||
|
}
|
||||||
|
if src.H > S.H {
|
||||||
|
src.H = S.H
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var size = int(chunk.Size)
|
||||||
dst := render.Rect{
|
dst := render.Rect{
|
||||||
X: p.X + w.Scroll.X + w.BoxThickness(1) + w.ZoomMultiply(coord.X*chunk.Size),
|
X: p.X + w.Scroll.X + w.BoxThickness(1) + w.ZoomMultiply(coord.X*size),
|
||||||
Y: p.Y + w.Scroll.Y + w.BoxThickness(1) + w.ZoomMultiply(coord.Y*chunk.Size),
|
Y: p.Y + w.Scroll.Y + w.BoxThickness(1) + w.ZoomMultiply(coord.Y*size),
|
||||||
|
|
||||||
// src.W and src.H will be AT MOST the full width and height of
|
// src.W and src.H will be AT MOST the full width and height of
|
||||||
// a Canvas widget. Subtract the scroll offset to keep it bounded
|
// a Canvas widget. Subtract the scroll offset to keep it bounded
|
||||||
|
@ -134,62 +139,68 @@ func (w *Canvas) Present(e render.Engine, p render.Point) {
|
||||||
|
|
||||||
// TODO: all this shit is in TrimBox(), make it DRY
|
// TODO: all this shit is in TrimBox(), make it DRY
|
||||||
|
|
||||||
// If the destination width will cause it to overflow the widget
|
// wtf? don't need all this code anymore??
|
||||||
// box, trim off the right edge of the destination rect.
|
_ = ParentPosition
|
||||||
//
|
/*
|
||||||
// Keep in mind we're dealing with chunks here, and a chunk is
|
|
||||||
// a small part of the image. Example:
|
|
||||||
// - Canvas is 800x600 (S.W=800 S.H=600)
|
|
||||||
// - Chunk wants to render at 790,0 width 100,100 or whatever
|
|
||||||
// dst={790, 0, 100, 100}
|
|
||||||
// - Chunk box would exceed 800px width (X=790 + W=100 == 890)
|
|
||||||
// - Find the delta how much it exceeds as negative (800 - 890 == -90)
|
|
||||||
// - Lower the Source and Dest rects by that delta size so they
|
|
||||||
// stay proportional and don't scale or anything dumb.
|
|
||||||
if dst.X+src.W > p.X+S.W+w.BoxThickness(1) {
|
|
||||||
// NOTE: delta is a negative number,
|
|
||||||
// so it will subtract from the width.
|
|
||||||
delta := (p.X + S.W - w.BoxThickness(1)) - (dst.W + dst.X)
|
|
||||||
src.W += delta
|
|
||||||
dst.W += delta
|
|
||||||
}
|
|
||||||
if dst.Y+src.H > p.Y+S.H+w.BoxThickness(1) {
|
|
||||||
// NOTE: delta is a negative number
|
|
||||||
delta := (p.Y + S.H - w.BoxThickness(1)) - (dst.H + dst.Y)
|
|
||||||
src.H += delta
|
|
||||||
dst.H += delta
|
|
||||||
}
|
|
||||||
|
|
||||||
// The same for the top left edge, so the drawings don't overlap
|
// If the destination width will cause it to overflow the widget
|
||||||
// menu bars or left side toolbars.
|
// box, trim off the right edge of the destination rect.
|
||||||
// - Canvas was placed 80px from the left of the screen.
|
//
|
||||||
// Canvas.MoveTo(80, 0)
|
// Keep in mind we're dealing with chunks here, and a chunk is
|
||||||
// - A texture wants to draw at 60, 0 which would cause it to
|
// a small part of the image. Example:
|
||||||
// overlap 20 pixels into the left toolbar. It needs to be cropped.
|
// - Canvas is 800x600 (S.W=800 S.H=600)
|
||||||
// - The delta is: p.X=80 - dst.X=60 == 20
|
// - Chunk wants to render at 790,0 width 100,100 or whatever
|
||||||
// - Set destination X to p.X to constrain it there: 20
|
// dst={790, 0, 100, 100}
|
||||||
// - Subtract the delta from destination W so we don't scale it.
|
// - Chunk box would exceed 800px width (X=790 + W=100 == 890)
|
||||||
// - Add 20 to X of the source: the left edge of source is not visible
|
// - Find the delta how much it exceeds as negative (800 - 890 == -90)
|
||||||
//
|
// - Lower the Source and Dest rects by that delta size so they
|
||||||
// Note: the +w.BoxThickness works around a bug if the Actor Canvas has
|
// stay proportional and don't scale or anything dumb.
|
||||||
// a border on it (e.g. in the Actor/Link Tool mouse-over or debug setting)
|
if dst.X+src.W > p.X+S.W+w.BoxThickness(1) {
|
||||||
if dst.X == ParentPosition.X+w.BoxThickness(1) {
|
// NOTE: delta is a negative number,
|
||||||
// NOTE: delta is a positive number,
|
// so it will subtract from the width.
|
||||||
// so it will add to the destination coordinates.
|
delta := (p.X + S.W - w.BoxThickness(1)) - (dst.W + dst.X)
|
||||||
delta := texSizeOrig.W - src.W
|
src.W += delta
|
||||||
dst.X = p.X + w.BoxThickness(1)
|
dst.W += delta
|
||||||
src.X += delta
|
}
|
||||||
}
|
if dst.Y+src.H > p.Y+S.H+w.BoxThickness(1) {
|
||||||
if dst.Y == ParentPosition.Y+w.BoxThickness(1) {
|
// NOTE: delta is a negative number
|
||||||
delta := texSizeOrig.H - src.H
|
delta := (p.Y + S.H - w.BoxThickness(1)) - (dst.H + dst.Y)
|
||||||
dst.Y = p.Y + w.BoxThickness(1)
|
src.H += delta
|
||||||
src.Y += delta
|
dst.H += delta
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trim the destination width so it doesn't overlap the Canvas border.
|
// The same for the top left edge, so the drawings don't overlap
|
||||||
if dst.W >= S.W-w.BoxThickness(1) {
|
// menu bars or left side toolbars.
|
||||||
dst.W = S.W - w.BoxThickness(1)
|
// - Canvas was placed 80px from the left of the screen.
|
||||||
}
|
// Canvas.MoveTo(80, 0)
|
||||||
|
// - A texture wants to draw at 60, 0 which would cause it to
|
||||||
|
// overlap 20 pixels into the left toolbar. It needs to be cropped.
|
||||||
|
// - The delta is: p.X=80 - dst.X=60 == 20
|
||||||
|
// - Set destination X to p.X to constrain it there: 20
|
||||||
|
// - Subtract the delta from destination W so we don't scale it.
|
||||||
|
// - Add 20 to X of the source: the left edge of source is not visible
|
||||||
|
//
|
||||||
|
// Note: the +w.BoxThickness works around a bug if the Actor Canvas has
|
||||||
|
// a border on it (e.g. in the Actor/Link Tool mouse-over or debug setting)
|
||||||
|
if dst.X == ParentPosition.X+w.BoxThickness(1) {
|
||||||
|
// NOTE: delta is a positive number,
|
||||||
|
// so it will add to the destination coordinates.
|
||||||
|
delta := texSizeOrig.W - src.W
|
||||||
|
dst.X = p.X + w.BoxThickness(1)
|
||||||
|
src.X += delta
|
||||||
|
}
|
||||||
|
if dst.Y == ParentPosition.Y+w.BoxThickness(1) {
|
||||||
|
delta := texSizeOrig.H - src.H
|
||||||
|
dst.Y = p.Y + w.BoxThickness(1)
|
||||||
|
src.Y += delta
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim the destination width so it doesn't overlap the Canvas border.
|
||||||
|
if dst.W >= S.W-w.BoxThickness(1) {
|
||||||
|
dst.W = S.W - w.BoxThickness(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
// When zooming OUT, make sure the source rect is at least the
|
// When zooming OUT, make sure the source rect is at least the
|
||||||
// full size of the chunk texture; otherwise the ZoomMultiplies
|
// full size of the chunk texture; otherwise the ZoomMultiplies
|
||||||
|
|
|
@ -398,6 +398,14 @@ func (form Form) Create(into *ui.Frame, fields []Field) {
|
||||||
if row.OnSelect != nil {
|
if row.OnSelect != nil {
|
||||||
row.OnSelect(selection.Value)
|
row.OnSelect(selection.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update bound variables.
|
||||||
|
if v, ok := selection.Value.(int); ok && row.IntVariable != nil {
|
||||||
|
*row.IntVariable = v
|
||||||
|
}
|
||||||
|
if v, ok := selection.Value.(string); ok && row.TextVariable != nil {
|
||||||
|
*row.TextVariable = v
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
|
@ -26,10 +26,13 @@ type AddEditLevel struct {
|
||||||
// Editing settings for an existing level?
|
// Editing settings for an existing level?
|
||||||
EditLevel *level.Level
|
EditLevel *level.Level
|
||||||
|
|
||||||
|
// Show the "New Doodad" tab by default?
|
||||||
|
NewDoodad bool
|
||||||
|
|
||||||
// Callback functions.
|
// Callback functions.
|
||||||
OnChangePageTypeAndWallpaper func(pageType level.PageType, wallpaper string)
|
OnChangePageTypeAndWallpaper func(pageType level.PageType, wallpaper string)
|
||||||
OnCreateNewLevel func(*level.Level)
|
OnCreateNewLevel func(*level.Level)
|
||||||
OnCreateNewDoodad func(size int)
|
OnCreateNewDoodad func(width, height int)
|
||||||
OnReload func()
|
OnReload func()
|
||||||
OnCancel func()
|
OnCancel func()
|
||||||
}
|
}
|
||||||
|
@ -74,6 +77,11 @@ func NewAddEditLevel(config AddEditLevel) *ui.Window {
|
||||||
|
|
||||||
tabframe.Supervise(config.Supervisor)
|
tabframe.Supervise(config.Supervisor)
|
||||||
|
|
||||||
|
// Show the doodad tab?
|
||||||
|
if config.NewDoodad {
|
||||||
|
tabframe.SetTab("doodad")
|
||||||
|
}
|
||||||
|
|
||||||
window.Hide()
|
window.Hide()
|
||||||
return window
|
return window
|
||||||
}
|
}
|
||||||
|
@ -389,7 +397,8 @@ func (config AddEditLevel) setupLevelFrame(tf *ui.TabFrame) {
|
||||||
func (config AddEditLevel) setupDoodadFrame(tf *ui.TabFrame) {
|
func (config AddEditLevel) setupDoodadFrame(tf *ui.TabFrame) {
|
||||||
// Default options.
|
// Default options.
|
||||||
var (
|
var (
|
||||||
doodadSize = 64
|
doodadWidth = 64
|
||||||
|
doodadHeight = doodadWidth
|
||||||
)
|
)
|
||||||
|
|
||||||
frame := tf.AddTab("doodad", ui.NewLabel(ui.Label{
|
frame := tf.AddTab("doodad", ui.NewLabel(ui.Label{
|
||||||
|
@ -401,110 +410,89 @@ func (config AddEditLevel) setupDoodadFrame(tf *ui.TabFrame) {
|
||||||
* Frame for selecting Page Type
|
* Frame for selecting Page Type
|
||||||
******************/
|
******************/
|
||||||
|
|
||||||
typeFrame := ui.NewFrame("Doodad Options Frame")
|
var sizeOptions = []magicform.Option{
|
||||||
frame.Pack(typeFrame, ui.Pack{
|
{Label: "32", Value: 32},
|
||||||
Side: ui.N,
|
{Label: "64", Value: 64},
|
||||||
FillX: true,
|
{Label: "96", Value: 96},
|
||||||
})
|
{Label: "128", Value: 128},
|
||||||
|
{Label: "200", Value: 200},
|
||||||
label1 := ui.NewLabel(ui.Label{
|
{Label: "256", Value: 256},
|
||||||
Text: "Doodad sprite size (square):",
|
{Label: "Custom...", Value: 0},
|
||||||
Font: balance.LabelFont,
|
|
||||||
})
|
|
||||||
typeFrame.Pack(label1, ui.Pack{
|
|
||||||
Side: ui.W,
|
|
||||||
})
|
|
||||||
|
|
||||||
// A selectbox to suggest some sizes or let the user enter a custom.
|
|
||||||
sizeBtn := ui.NewSelectBox("Size Select", ui.Label{
|
|
||||||
Font: ui.MenuFont,
|
|
||||||
})
|
|
||||||
typeFrame.Pack(sizeBtn, ui.Pack{
|
|
||||||
Side: ui.W,
|
|
||||||
Expand: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, row := range []struct {
|
|
||||||
Name string
|
|
||||||
Value int
|
|
||||||
}{
|
|
||||||
{"32", 32},
|
|
||||||
{"64", 64},
|
|
||||||
{"96", 96},
|
|
||||||
{"128", 128},
|
|
||||||
{"200", 200},
|
|
||||||
{"256", 256},
|
|
||||||
{"Custom...", 0},
|
|
||||||
} {
|
|
||||||
row := row
|
|
||||||
sizeBtn.AddItem(row.Name, row.Value, func() {})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sizeBtn.SetValue(doodadSize)
|
form := magicform.Form{
|
||||||
sizeBtn.Handle(ui.Change, func(ed ui.EventData) error {
|
Supervisor: config.Supervisor,
|
||||||
if selection, ok := sizeBtn.GetValue(); ok {
|
Engine: config.Engine,
|
||||||
if size, ok := selection.Value.(int); ok {
|
Vertical: true,
|
||||||
if size == 0 {
|
LabelWidth: 90,
|
||||||
shmem.Prompt("Enter a custom size for the doodad width and height: ", func(answer string) {
|
}
|
||||||
|
form.Create(frame, []magicform.Field{
|
||||||
|
{
|
||||||
|
Label: "Width:",
|
||||||
|
Font: balance.LabelFont,
|
||||||
|
Type: magicform.Selectbox,
|
||||||
|
IntVariable: &doodadWidth,
|
||||||
|
Options: sizeOptions,
|
||||||
|
OnSelect: func(v interface{}) {
|
||||||
|
if v.(int) == 0 {
|
||||||
|
shmem.Prompt("Enter a custom size for the doodad width: ", func(answer string) {
|
||||||
if a, err := strconv.Atoi(answer); err == nil && a > 0 {
|
if a, err := strconv.Atoi(answer); err == nil && a > 0 {
|
||||||
doodadSize = a
|
doodadWidth = a
|
||||||
} else {
|
} else {
|
||||||
shmem.FlashError("Doodad size should be a number greater than zero.")
|
shmem.FlashError("Doodad size should be a number greater than zero.")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
doodadSize = size
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
return nil
|
{
|
||||||
|
Label: "Height:",
|
||||||
|
Font: balance.LabelFont,
|
||||||
|
Type: magicform.Selectbox,
|
||||||
|
IntVariable: &doodadHeight,
|
||||||
|
Options: sizeOptions,
|
||||||
|
OnSelect: func(v interface{}) {
|
||||||
|
if v.(int) == 0 {
|
||||||
|
shmem.Prompt("Enter a custom size for the doodad height: ", func(answer string) {
|
||||||
|
if a, err := strconv.Atoi(answer); err == nil && a > 0 {
|
||||||
|
doodadHeight = a
|
||||||
|
} else {
|
||||||
|
shmem.FlashError("Doodad size should be a number greater than zero.")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Buttons: []magicform.Field{
|
||||||
|
{
|
||||||
|
Label: "Continue",
|
||||||
|
Font: balance.UIFont,
|
||||||
|
ButtonStyle: &balance.ButtonPrimary,
|
||||||
|
OnClick: func() {
|
||||||
|
if config.OnCreateNewDoodad != nil {
|
||||||
|
config.OnCreateNewDoodad(doodadWidth, doodadHeight)
|
||||||
|
} else {
|
||||||
|
shmem.FlashError("OnCreateNewDoodad not attached")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Label: "Cancel",
|
||||||
|
Font: balance.UIFont,
|
||||||
|
ButtonStyle: &balance.ButtonPrimary,
|
||||||
|
OnClick: func() {
|
||||||
|
if config.OnCancel != nil {
|
||||||
|
config.OnCancel()
|
||||||
|
} else {
|
||||||
|
shmem.FlashError("OnCancel not attached")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
sizeBtn.Supervise(config.Supervisor)
|
|
||||||
config.Supervisor.Add(sizeBtn)
|
|
||||||
|
|
||||||
/******************
|
|
||||||
* Confirm/cancel buttons.
|
|
||||||
******************/
|
|
||||||
|
|
||||||
bottomFrame := ui.NewFrame("Button Frame")
|
|
||||||
frame.Pack(bottomFrame, ui.Pack{
|
|
||||||
Side: ui.N,
|
|
||||||
FillX: true,
|
|
||||||
PadY: 8,
|
|
||||||
})
|
|
||||||
|
|
||||||
var buttons = []struct {
|
|
||||||
Label string
|
|
||||||
F func(ui.EventData) error
|
|
||||||
}{
|
|
||||||
{"Continue", func(ed ui.EventData) error {
|
|
||||||
if config.OnCreateNewDoodad != nil {
|
|
||||||
config.OnCreateNewDoodad(doodadSize)
|
|
||||||
} else {
|
|
||||||
shmem.FlashError("OnCreateNewDoodad not attached")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}},
|
|
||||||
|
|
||||||
{"Cancel", func(ed ui.EventData) error {
|
|
||||||
config.OnCancel()
|
|
||||||
return nil
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
for _, t := range buttons {
|
|
||||||
btn := ui.NewButton(t.Label, ui.NewLabel(ui.Label{
|
|
||||||
Text: t.Label,
|
|
||||||
Font: balance.MenuFont,
|
|
||||||
}))
|
|
||||||
btn.Handle(ui.Click, t.F)
|
|
||||||
config.Supervisor.Add(btn)
|
|
||||||
bottomFrame.Pack(btn, ui.Pack{
|
|
||||||
Side: ui.W,
|
|
||||||
PadX: 4,
|
|
||||||
PadY: 8,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates the Game Rules frame for existing level (set difficulty, etc.)
|
// Creates the Game Rules frame for existing level (set difficulty, etc.)
|
||||||
|
|
|
@ -253,7 +253,7 @@ func makeDoodadTab(config DoodadDropper, frame *ui.Frame, size render.Rect, cate
|
||||||
lastColumn = 0
|
lastColumn = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
can := uix.NewCanvas(int(buttonSize), true)
|
can := uix.NewCanvas(uint8(buttonSize), true) // TODO: dangerous - buttonSize must be small
|
||||||
can.Name = doodad.Title
|
can.Name = doodad.Title
|
||||||
can.SetBackground(balance.DoodadButtonBackground)
|
can.SetBackground(balance.DoodadButtonBackground)
|
||||||
can.LoadDoodad(doodad)
|
can.LoadDoodad(doodad)
|
||||||
|
@ -262,6 +262,7 @@ func makeDoodadTab(config DoodadDropper, frame *ui.Frame, size render.Rect, cate
|
||||||
canvases = append(canvases, can)
|
canvases = append(canvases, can)
|
||||||
|
|
||||||
btn := ui.NewButton(doodad.Title, can)
|
btn := ui.NewButton(doodad.Title, can)
|
||||||
|
can.CroppedSize = true
|
||||||
btn.Resize(render.NewRect(
|
btn.Resize(render.NewRect(
|
||||||
buttonSize-2, // TODO: without the -2 the button border
|
buttonSize-2, // TODO: without the -2 the button border
|
||||||
buttonSize-2, // rests on top of the window border
|
buttonSize-2, // rests on top of the window border
|
||||||
|
|
Loading…
Reference in New Issue
Block a user