Compare commits

..

1 Commits

Author SHA1 Message Date
8fb579e66e WIP: Broken UI - Reworking Frame Pack 2019-04-19 13:51:27 -07:00
412 changed files with 5736 additions and 39361 deletions

View File

@ -1,2 +0,0 @@
bin/*
dist/*

11
.gitignore vendored
View File

@ -1,18 +1,7 @@
pkg/bindata/bindata.go
deps/
fonts/
maps/
bin/
dist/
rtp/
guidebook/
docker-artifacts/
wasm/assets/
*.wasm
*.doodad
*.level
*.levelpack
*.AppImage
docker/ubuntu
docker/debian
docker/fedora

View File

@ -1,226 +1,9 @@
# Building Doodle
- [Building Doodle](#building-doodle)
- [Dockerfile](#dockerfile)
- [Automated Release Scripts](#automated-release-scripts)
- [Go Environment](#go-environment)
- [Quickstart with bootstrap.py](#quickstart-with-bootstrappy)
- [Detailed Instructions](#detailed-instructions)
- [Fonts](#fonts)
- [Makefile](#makefile)
- [Dependencies](#dependencies)
- [Flatpak for Linux](#flatpak-for-linux)
- [Windows Cross-Compile from Linux](#windows-cross-compile-from-linux)
- [Windows DLLs](#windows-dlls)
- [Build on macOS from scratch](#build-on-macos-from-scratch)
- [WebAssembly](#webassembly)
- [Build Tags](#build-tags)
- [doodad](#doodad)
- [dpp](#dpp)
# Dockerfile
The Dockerfile in this git repo may be the quickest way to fully
release the game for as many platforms as possible. Run it from a
64-bit host Linux system and it will generate Linux and Windows
releases for 64-bit and 32-bit Intel CPUs.
It depends on your git clone of doodle to be fully initialized
(e.g., you have run the bootstrap.py script and a `make dist`
would build a release for your current system, with doodads and
runtime assets all in the right places).
Run `make docker` and the results will be in the
`artifacts/release` folder in your current working directory.
**Fedora notes (SELinux):** if you run this from a Fedora host
you will need to `sudo setenforce permissive` to allow the
Dockerfile to mount the artifacts/release folder to export its
results.
# Automated Release Scripts
Other Dockerfiles and scripts used to release the game:
* [SketchyMaze/docker](https://git.kirsle.net/SketchyMaze/docker) provides a Dockerfile
that fully end-to-end releases the latest version of the game for Linux and Windows. 64bit and 32bit versions that freshly clone the
game from git and output their respective CPU release artifacts:
* Windows: .zip file
* Linux: .tar.gz, .rpm, .deb
* [flatpak](https://code.sketchymaze.com/game/flatpak) is a Flatpak manifest for
Linux distributions.
The Docker container depends on all the git servers being up, but if you have
the uber blob source code you can read the Dockerfile to see what it does.
# Go Environment
Part of the build scripts involve building and running the `doodad` command
from this repo in order to generate the game's built-in doodads. For this to
work smoothly from your Linux or macOS build environment, you may need to
ensure that your `${GOPATH}/bin` directory is on your `$PATH` by, for example,
configuring this in your bash/zsh profile:
```bash
export GOPATH="${HOME}/go"
export PATH="${PATH}:${GOPATH}/bin"
```
For a complete example, see the "Build on macOS from scratch" section below.
# Quickstart with bootstrap.py
From any Unix-like system (Fedora, Ubuntu, macOS) the bootstrap.py script
is intended to set this app up, from scratch, _fast._ The basic steps are:
```bash
# Example from an Ubuntu 20.04 LTS fresh install running in a
# libvirt-manager virtual machine on a Fedora host.
# 1. Ensure your SSH keys have git clone permission for git.kirsle.net.
# For example just scp my keys from the host Fedora machine.
$ scp -r kirsle@192.168.122.1:.ssh/id_rsa* ~/.ssh/
# 2. git clone the Project Doodle repository.
$ git clone git@git.kirsle.net:apps/doodle
$ cd ./doodle
# 3. Run the bootstrap script.
$ python3 bootstrap.py
```
The bootstrap script will take care of the rest:
* `apt install` all the dependencies (golang, SDL2, etc.)
* `git clone` various other repositories into a "deps/" folder in doodle's
directory. These are things like my Go render library `go/render` and
`go/ui` as well as the doodle-rtp runtime package (sound effects, etc.)
all of which are hosted on git.kirsle.net.
* Build and install the `doodad` tool so it can generate the builtin
doodads, and build and release a full distribution of the game.
It should work on Fedora-likes, Debian-likes and macOS all the same.
It even runs on the Pine64 Pinephone (ARM64) with Mobian!
MacOS is expected to have [homebrew](https://brew.sh) installed.
MP3 support issues? [See here](https://github.com/veandco/go-sdl2/issues/299#issuecomment-611681191).
**To do:** the most important repositories, like the game itself, are
also mirrored on GitHub. Other supporting repos need mirroring too, or
otherwise, full source tarballs (the result of bootstrap.py) will be
built and archived somewhere safe for posterity in case git.kirsle.net
ever goes away. The doodle mirror is at <https://github.com/SketchyMaze/doodle>
(private repository) and the others are there too (go/render, go/ui, etc.)
# Detailed Instructions
For building the app the hard way, and in-depth instructions, read
this section. You'll need the following git repositories:
* `git.kirsle.net/SketchyMaze/doodle` - the game engine.
* `git.kirsle.net/SketchyMaze/assets` - where built-in level files are kept (optional)
* `git.kirsle.net/SketchyMaze/vendor` - vendored libraries for Windows (SDL2.dll etc.)
* `git.kirsle.net/SketchyMaze/rtp` - runtime package (sounds and music mostly)
* `git.kirsle.net/SketchyMaze/doodads` - sources to compile the built-in doodads.
The [docker](https://git.kirsle.net/SketchyMaze/docker) repo will
be more up-to-date than the instructions below, as that repo actually has
runnable code in the Dockerfile!
```bash
# Clone all the repos down to your project folder
git clone https://git.kirsle.net/SketchyMaze/rtp rtp
git clone https://git.kirsle.net/SketchyMaze/vendor vendor
git clone https://git.kirsle.net/SketchyMaze/masters masters
git clone https://git.kirsle.net/SketchyMaze/doodle doodle
git clone https://git.kirsle.net/SketchyMaze/doodads doodle/deps/doodads
# Enter doodle/ project
cd doodle/
# Copy fonts and levels in
cp ../assets/levelpacks assets/levelpacks
cp ../vendor/fonts assets/fonts
mkdir rtp && cp -r ../rtp/* rtp/
# From the doodle repo:
make setup # -or-
go get ./... # install dependencies etc.
# The app should build now. Build and install the doodad tool.
go install git.kirsle.net/SketchyMaze/doodle/cmd/doodad
doodad --version
# "doodad version 0.3.0-alpha build ..."
# Build and release the game into the dist/ folder.
# This will: generate builtin doodads, bundle them with bindata,
# and create a tarball in the dist/ folder.
make dist
# Build a cross-compiled Windows target from Linux.
# (you'd run before `make dist` to make an uber release)
make mingw
# After make dist, `make release` will carve up Linux
# and Windows (mingw) builds and zip them up nicely.
make release
```
`make build` produces a local binary in the bin/ folder and `make dist`
will build an app for distribution in the dist/ folder.
The bootstrap.py script does all of the above up to `make dist` so if you need
fully release the game by hand (e.g. on a macOS host) you can basically get away
with:
1. Clone the doodle repo and cd into it
2. Run `bootstrap.py` to fully set up your OS with dependencies and build a
release quality version of the game with all latest assets (the script finishes
with a `make dist`).
3. Run `make release` to package the dist/ artifact into platform specific
release artifacts (.rpm/.deb/.tar.gz bundles for Linux, .zip for Windows,
.dmg if running on macOS) which output into the dist/release/ folder.
Before step 3 you may want to download the latest Guidebook to bundle with
the game (optional). Grab and extract the tarball and run `make dist && make release`:
```bash
wget -O - https://download.sketchymaze.com/guidebook.tar.gz | tar -xzvf -
```
## Fonts
The `fonts/` folder is git-ignored. The app currently uses font files here
named:
* `DejaVuSans.ttf` for sans-serif font.
* `DejaVuSans-Bold.ttf` for bold sans-serif font.
* `DejaVuSansMono.ttf` for monospace font.
These are the open source **DejaVu Sans [Mono]** fonts, so copy them in from
your `/usr/share/fonts/dejavu` folder or provide alternative fonts.
```bash
mkdir fonts
cp /usr/share/fonts/dejavu/{DejaVuSans.ttf,DejaVuSans-Bold.ttf,DejaVuSansMono.ttf} fonts/
```
The doodle-vendor repo has copies of these fonts.
## Makefile
Makefile commands for Unix-likes:
Makefile commands for Linux:
* `make setup`: install Go dependencies and set up the build environment
* `make doodads`: build the default Doodads from sources in `deps/doodads/`
* `make build`: build the Doodle and Doodad binaries to the `bin/` folder.
* `make buildall`: runs all build steps: doodads, build.
* `make build-free`: build the shareware binaries to the `bin/` folder. See
Build Tags below.
* `make build-debug`: build a debug binary (not release-mode) to the `bin/`
folder. See Build Tags below.
* `make wasm`: build the WebAssembly output
* `make wasm-serve`: build the WASM output and then run the server.
* `make run`: run a local dev build of Doodle in debug mode
* `make guitest`: run a local dev build in the GUITest scene
* `make test`: run the test suite
@ -234,28 +17,18 @@ Makefile commands for Unix-likes:
* `make docker.fedora`
* `make clean`: clean all build artifacts
# Dependencies
## Linux
The bootstrap.py script lists dependencies for Fedora, Debian and macOS.
Also here for clarity, hopefully not out-of-date:
Dependencies are Go, SDL2 and SDL2_ttf:
```bash
# Fedora-likes
sudo dnf install make golang SDL2-devel SDL2_ttf-devel \
SDL2_mixer-devel
# Fedora
sudo dnf -y install golang SDL2-devel SDL2_ttf-devel
# Debian and Ubuntu
sudo dnf install make golang libsdl2-dev libsdl2-ttf-dev \
libsdl2-mixer-dev
# macOS via Homebrew (https://brew.sh)
brew install golang sdl2 sdl2_ttf sdl2_mixer pkg-config
# Ubuntu and Debian
sudo apt -y install golang libsdl2-dev libsdl2-ttf-dev
```
## Flatpak for Linux
The repo for this is at <https://git.kirsle.net/SketchyMaze/flatpak>.
## Windows Cross-Compile from Linux
Install the Mingw C compiler:
@ -302,108 +75,3 @@ cp /usr/x86_64-w64-mingw32/bin/SDL*.dll bin/
SDL2_ttf requires libfreetype, you can get its DLL here:
https://github.com/ubawurinna/freetype-windows-binaries
## Build on macOS from scratch
Here are some detailed instructions how to build Sketchy Maze from a fresh
install of macOS Ventura that assumes no previous software or configuration
was applied to the system yet.
Install homebrew: https://brew.sh pay attention to the instructions at the end
of the install to set up your zsh profile for homebrew to work correctly.
Clone the doodle repository:
```bash
git clone https://git.kirsle.net/SketchyMaze/doodle
cd doodle
```
Note: on a fresh install, invoking the `git` command may cause macOS to install
developer tools and Xcode. After installed, run the git clone again to finish
cloning the repository.
Set your Go environment variables: edit your ~/.zprofile and ensure that $GOPATH
is configured and that your $PATH includes $GOPATH/bin. **Note:** restart your
terminal session or reload the config file (e.g. `. ~/.zprofile`) after making
this change.
```bash
# in your .zprofile, .bash_profile, .zshrc or similar shell config
export GOPATH="${HOME}/go"
export PATH="${PATH}:${GOPATH}/bin"
```
Run the bootstrap script:
```bash
python3 bootstrap.py
```
Answer N (default) when asked to clone dependency repos over ssh. The bootstrap
script will `brew install` any necessary dependencies (Go, SDL2, etc.) and clone
support repos for the game (doodads, levelpacks, assets).
# WebAssembly
There is some **experimental** support for a WebAssembly build of Sketchy Maze
since the very early days. Early on, the game "basically worked" but performance
could be awful: playing levels was OK but clicking and dragging in the editor
would cause your browser to freeze. Then for a time, the game wouldn't even get
that far. Recently (December 2023), WASM performance seems much better but there
are strange graphical glitches:
* On the title screen, the example levels in the background load OK and their
doodads will wander around and performance seems OK.
* But during Play Mode, only the menu bar draws but nothing else on the screen.
* In the Level Editor, the entire screen is white BUT tooltips will appear and
the menu bar can be clicked on (blindly) and the drop-down menus do appear.
Some popups like the Palette Editor can be invoked and draw to varying degrees
of success.
Some tips to get a WASM build to work:
* For fonts: symlink it so that ./wasm/fonts points to ./assets/fonts.
* You may need an updated wasm_exec.js shim from Go. On Fedora,
`dnf install golang-misc` and `cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .`
from the wasm/ folder.
* Run `make wasm` to build the WASM binary and `make wasm-serve` to run a simple
Go web server to serve it from.
# Build Tags
Go build tags used by this game:
## doodad
This tag is used when building the `doodad` command-line tool.
It ensures that the embedded bindata assets (built-in doodads, etc.) do not
need to be bundled into the doodad binary, but only the main game binary.
## dpp
The dpp tag stands for Doodle++ and is used for official commercial builds of
the game. Doodle++ builds include additional code not found in the free & open
source release of the game engine.
This build tag should be set automatically by the Makefile **if** the deps/
folder has a git clone of the dpp project. The bootstrap.py script will clone
the dpp repo **if** you use SSH to clone dependencies: so you will need SSH
credentials to the upstream git server. It basically means that third-party
users who download the open source release will not have the dpp dependency,
and will not build dpp copies of the game.
If you _do_ have the dpp dependency, you can force build (and run) FOSS
versions of the game via the Makefile commands `make build-free`,
`make run-free` or `make dist-free` which are counterparts to the main make
commands but which deliberately do not set the dpp build tag.
In source code, files ending with `_dpp.go` and `_foss.go` are conditionally
compiled depending on this build tag.
How to tell whether your build of Sketchy Maze is Doodle++ include:
* The version string on the title screen.
* FOSS builds (not dpp) will say "open source" in the version.
* DPP builds may say "shareware" if unregistered or just the version.

1277
Changes.md

File diff suppressed because it is too large Load Diff

View File

@ -1,121 +0,0 @@
##
# Fully build and distribute Linux and Windows binaries for Project: Doodle.
#
# This is designed to be run from a fully initialized Doodle environment
# (you had run the bootstrap.py for your system, and the doodads and
# levelpacks are installed in the assets/ folder, and `make dist` would
# build a release quality game for your local machine).
#
# It will take your working directory (minus any platform-specific artifacts
# and git repos cloned in to your deps/ folder) and build them from a sane
# Debian base and generate full release artifacts for:
#
# - Linux (x86_64 and i686) as .rpm, .deb, .flatpak and .tar.gz
# - Windows (64-bit and 32-bit) as .zip
#
# Artifact outputs will be in the dist/mw/ folder.
##
FROM debian:latest AS build64
ENV GOPATH /go
ENV GOPROXY direct
ENV PATH /opt/go/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/go/bin
# Install all dependencies.
RUN apt update && apt -y install git zip tar libsdl2-dev libsdl2-ttf-dev \
libsdl2-mixer-dev gcc-mingw-w64-x86-64 gcc make wget \
flatpak-builder ruby-dev gcc rpm libffi-dev \
ruby-dev ruby-rubygems rpm libffi-dev rsync file
RUN gem install fpm; exit 0
# Download and install modern Go.
WORKDIR /root
RUN wget https://go.dev/dl/go1.21.4.linux-amd64.tar.gz -O go.tgz && \
tar -xzf go.tgz && \
cp -r go /opt/go
# Add some cacheable directories to speed up Dockerfile trial-and-error.
ADD deps/vendor /SketchyMaze/deps/vendor
# MinGW setup for Windows executable cross-compile.
WORKDIR /SketchyMaze/deps/vendor/mingw-libs
RUN for i in *.tar.gz; do tar -xzvf $i; done
RUN cp -r SDL2-2.0.9/x86_64-w64-mingw32 /usr && \
cp -r SDL2_mixer-2.0.4/x86_64-w64-mingw32 /usr && \
cp -r SDL2_ttf-2.0.15/x86_64-w64-mingw32 /usr
RUN mkdir -p /usr/lib/golang/pkg/windows_amd64
WORKDIR /SketchyMaze
RUN mkdir -p bin && cp deps/vendor/DLL/*.dll bin/
# Add the current working directory (breaks the docker cache every time).
ADD . /SketchyMaze
# Fetch the guidebook.
# RUN sh -c '[[ ! -d ./guidebook ]] && wget -O - https://download.sketchymaze.com/guidebook.tar.gz | tar -xzvf -'
# Use go-winres on the Windows exe (embed application icons)
RUN go install github.com/tc-hib/go-winres@latest && go-winres make
# Revert any local change to go.mod (replace lines)
RUN git checkout -- go.mod
# Install Go dependencies and do the thing:
# - builds the program for Linux
# - builds for Windows via MinGW
# - runs `make dist/` creating an uber build for both OS's
# - runs release.sh to carve out the Linux and Windows versions and
# zip them all up nicely.
RUN make setup && make from-docker64
# Collect the build artifacts.
RUN mkdir -p artifacts && cp -rv dist/release ./artifacts/
###
# 32-bit Dockerfile version of the above
###
FROM i386/debian:latest AS build32
ENV GOPATH /go
ENV GOPROXY direct
ENV PATH /opt/go/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/go/bin
# Dependencies, note the w64-i686 difference to the above
RUN apt update && apt -y install git zip tar libsdl2-dev libsdl2-ttf-dev \
libsdl2-mixer-dev gcc-mingw-w64-i686 gcc make wget \
flatpak-builder ruby-dev gcc rpm libffi-dev \
ruby-dev ruby-rubygems rpm libffi-dev rsync file
RUN gem install fpm; exit 0
# Download and install modern Go.
WORKDIR /root
RUN wget https://go.dev/dl/go1.19.3.linux-386.tar.gz -O go.tgz && \
tar -xzf go.tgz && \
cp -r go /opt/go
COPY --from=build64 /SketchyMaze /SketchyMaze
# MinGW setup for Windows executable cross-compile.
WORKDIR /SketchyMaze/deps/vendor/mingw-libs
RUN for i in *.tar.gz; do tar -xzvf $i; done
RUN cp -r SDL2-2.0.9/i686-w64-mingw32 /usr && \
cp -r SDL2_mixer-2.0.4/i686-w64-mingw32 /usr && \
cp -r SDL2_ttf-2.0.15/i686-w64-mingw32 /usr
RUN mkdir -p /usr/lib/golang/pkg/windows_386
WORKDIR /SketchyMaze
RUN mkdir -p bin && cp deps/vendor/DLL-32bit/*.dll bin/
# Do the thing.
RUN make setup && make from-docker32
# Collect the build artifacts.
RUN mkdir -p artifacts && cp -rv dist/release ./artifacts/
###
# Back to (64bit) base for the final CMD to copy artifacts out.
###
FROM debian:latest
COPY --from=build32 /SketchyMaze /SketchyMaze
CMD ["cp", "-r", "-v", \
"/SketchyMaze/artifacts/release/", \
"/mnt/export/"]

View File

@ -76,10 +76,10 @@ The major milestones of the game are roughly:
* Colors are not tied to behaviors. Each "Swatch" on the palette has its own
color and a set of boolean flags for `solid`, `fire` and `water` behaviors.
* [ ] User interface to edit (add/remove) swatches from the palette.
* [x] A Toolbox window with radio buttons to select between various drawing tools.
* [ ] A Toolbox window with radio buttons to select between various drawing tools.
* [x] Pencil (the default) draws single pixels on the level.
* [x] Rectangle would draw a rectangular outline.
* [x] Line would draw a line from point to point.
* [ ] Rectangle would draw a rectangular outline.
* [ ] Line would draw a line from point to point.
* [ ] A way to adjust brush properties:
* [ ] Brush size, shape (round or square).
* [ ] Tools to toggle "layers" of visibility into your level:
@ -98,11 +98,11 @@ The major milestones of the game are roughly:
For creating Doodads in particular:
* [x] Make a way to enter Edit Mode in either "Level Mode" or "Doodad Mode",
* [ ] Make a way to enter Edit Mode in either "Level Mode" or "Doodad Mode",
i.e. by a "New Level" or "New Doodad" button.
* [ ] Create a "frame manager" window to see and page between the frames of the
drawing.
* [x] Ability to work on canvases with constrained size (including smaller than
* [ ] Ability to work on canvases with constrained size (including smaller than
your window). This will use a Canvas widget in the UI toolkit as an abstraction
layer. Small canvases will be useful for drawing doodads of a fixed size.

View File

@ -1,636 +0,0 @@
# GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 [Free Software Foundation, Inc.](http://fsf.org/)
Everyone is permitted to copy and distribute verbatim copies of this license
document, but changing it is not allowed.
## Preamble
The GNU General Public License is a free, copyleft license for software and
other kinds of works.
The licenses for most software and other practical works are designed to take
away your freedom to share and change the works. By contrast, the GNU General
Public License is intended to guarantee your freedom to share and change all
versions of a program--to make sure it remains free software for all its users.
We, the Free Software Foundation, use the GNU General Public License for most
of our software; it applies also to any other work released this way by its
authors. You can apply it to your programs, too.
When we speak of free software, we are referring to freedom, not price. Our
General Public Licenses are designed to make sure that you have the freedom to
distribute copies of free software (and charge for them if you wish), that you
receive source code or can get it if you want it, that you can change the
software or use pieces of it in new free programs, and that you know you can do
these things.
To protect your rights, we need to prevent others from denying you these rights
or asking you to surrender the rights. Therefore, you have certain
responsibilities if you distribute copies of the software, or if you modify it:
responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether gratis or for
a fee, you must pass on to the recipients the same freedoms that you received.
You must make sure that they, too, receive or can get the source code. And you
must show them these terms so they know their rights.
Developers that use the GNU GPL protect your rights with two steps:
1. assert copyright on the software, and
2. offer you this License giving you legal permission to copy, distribute
and/or modify it.
For the developers' and authors' protection, the GPL clearly explains that
there is no warranty for this free software. For both users' and authors' sake,
the GPL requires that modified versions be marked as changed, so that their
problems will not be attributed erroneously to authors of previous versions.
Some devices are designed to deny users access to install or run modified
versions of the software inside them, although the manufacturer can do so. This
is fundamentally incompatible with the aim of protecting users' freedom to
change the software. The systematic pattern of such abuse occurs in the area of
products for individuals to use, which is precisely where it is most
unacceptable. Therefore, we have designed this version of the GPL to prohibit
the practice for those products. If such problems arise substantially in other
domains, we stand ready to extend this provision to those domains in future
versions of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents. States
should not allow patents to restrict development and use of software on
general-purpose computers, but in those that do, we wish to avoid the special
danger that patents applied to a free program could make it effectively
proprietary. To prevent this, the GPL assures that patents cannot be used to
render the program non-free.
The precise terms and conditions for copying, distribution and modification
follow.
## TERMS AND CONDITIONS
### 0. Definitions.
*This License* refers to version 3 of the GNU General Public License.
*Copyright* also means copyright-like laws that apply to other kinds of works,
such as semiconductor masks.
*The Program* refers to any copyrightable work licensed under this License.
Each licensee is addressed as *you*. *Licensees* and *recipients* may be
individuals or organizations.
To *modify* a work means to copy from or adapt all or part of the work in a
fashion requiring copyright permission, other than the making of an exact copy.
The resulting work is called a *modified version* of the earlier work or a work
*based on* the earlier work.
A *covered work* means either the unmodified Program or a work based on the
Program.
To *propagate* a work means to do anything with it that, without permission,
would make you directly or secondarily liable for infringement under applicable
copyright law, except executing it on a computer or modifying a private copy.
Propagation includes copying, distribution (with or without modification),
making available to the public, and in some countries other activities as well.
To *convey* a work means any kind of propagation that enables other parties to
make or receive copies. Mere interaction with a user through a computer
network, with no transfer of a copy, is not conveying.
An interactive user interface displays *Appropriate Legal Notices* to the
extent that it includes a convenient and prominently visible feature that
1. displays an appropriate copyright notice, and
2. tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the work
under this License, and how to view a copy of this License.
If the interface presents a list of user commands or options, such as a menu, a
prominent item in the list meets this criterion.
### 1. Source Code.
The *source code* for a work means the preferred form of the work for making
modifications to it. *Object code* means any non-source form of a work.
A *Standard Interface* means an interface that either is an official standard
defined by a recognized standards body, or, in the case of interfaces specified
for a particular programming language, one that is widely used among developers
working in that language.
The *System Libraries* of an executable work include anything, other than the
work as a whole, that (a) is included in the normal form of packaging a Major
Component, but which is not part of that Major Component, and (b) serves only
to enable use of the work with that Major Component, or to implement a Standard
Interface for which an implementation is available to the public in source code
form. A *Major Component*, in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system (if any) on
which the executable work runs, or a compiler used to produce the work, or an
object code interpreter used to run it.
The *Corresponding Source* for a work in object code form means all the source
code needed to generate, install, and (for an executable work) run the object
code and to modify the work, including scripts to control those activities.
However, it does not include the work's System Libraries, or general-purpose
tools or generally available free programs which are used unmodified in
performing those activities but which are not part of the work. For example,
Corresponding Source includes interface definition files associated with source
files for the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require, such as
by intimate data communication or control flow between those subprograms and
other parts of the work.
The Corresponding Source need not include anything that users can regenerate
automatically from other parts of the Corresponding Source.
The Corresponding Source for a work in source code form is that same work.
### 2. Basic Permissions.
All rights granted under this License are granted for the term of copyright on
the Program, and are irrevocable provided the stated conditions are met. This
License explicitly affirms your unlimited permission to run the unmodified
Program. The output from running a covered work is covered by this License only
if the output, given its content, constitutes a covered work. This License
acknowledges your rights of fair use or other equivalent, as provided by
copyright law.
You may make, run and propagate covered works that you do not convey, without
conditions so long as your license otherwise remains in force. You may convey
covered works to others for the sole purpose of having them make modifications
exclusively for you, or provide you with facilities for running those works,
provided that you comply with the terms of this License in conveying all
material for which you do not control copyright. Those thus making or running
the covered works for you must do so exclusively on your behalf, under your
direction and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under the
conditions stated below. Sublicensing is not allowed; section 10 makes it
unnecessary.
### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological measure
under any applicable law fulfilling obligations under article 11 of the WIPO
copyright treaty adopted on 20 December 1996, or similar laws prohibiting or
restricting circumvention of such measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention is
effected by exercising rights under this License with respect to the covered
work, and you disclaim any intention to limit operation or modification of the
work as a means of enforcing, against the work's users, your or third parties'
legal rights to forbid circumvention of technological measures.
### 4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you receive it,
in any medium, provided that you conspicuously and appropriately publish on
each copy an appropriate copyright notice; keep intact all notices stating that
this License and any non-permissive terms added in accord with section 7 apply
to the code; keep intact all notices of the absence of any warranty; and give
all recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey, and you may
offer support or warranty protection for a fee.
### 5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to produce it
from the Program, in the form of source code under the terms of section 4,
provided that you also meet all of these conditions:
- a) The work must carry prominent notices stating that you modified it, and
giving a relevant date.
- b) The work must carry prominent notices stating that it is released under
this License and any conditions added under section 7. This requirement
modifies the requirement in section 4 to *keep intact all notices*.
- c) You must license the entire work, as a whole, under this License to
anyone who comes into possession of a copy. This License will therefore
apply, along with any applicable section 7 additional terms, to the whole
of the work, and all its parts, regardless of how they are packaged. This
License gives no permission to license the work in any other way, but it
does not invalidate such permission if you have separately received it.
- d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your work need
not make them do so.
A compilation of a covered work with other separate and independent works,
which are not by their nature extensions of the covered work, and which are not
combined with it such as to form a larger program, in or on a volume of a
storage or distribution medium, is called an *aggregate* if the compilation and
its resulting copyright are not used to limit the access or legal rights of the
compilation's users beyond what the individual works permit. Inclusion of a
covered work in an aggregate does not cause this License to apply to the other
parts of the aggregate.
### 6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms of sections 4
and 5, provided that you also convey the machine-readable Corresponding Source
under the terms of this License, in one of these ways:
- a) Convey the object code in, or embodied in, a physical product (including
a physical distribution medium), accompanied by the Corresponding Source
fixed on a durable physical medium customarily used for software
interchange.
- b) Convey the object code in, or embodied in, a physical product (including
a physical distribution medium), accompanied by a written offer, valid for
at least three years and valid for as long as you offer spare parts or
customer support for that product model, to give anyone who possesses the
object code either
1. a copy of the Corresponding Source for all the software in the product
that is covered by this License, on a durable physical medium
customarily used for software interchange, for a price no more than your
reasonable cost of physically performing this conveying of source, or
2. access to copy the Corresponding Source from a network server at no
charge.
- c) Convey individual copies of the object code with a copy of the written
offer to provide the Corresponding Source. This alternative is allowed only
occasionally and noncommercially, and only if you received the object code
with such an offer, in accord with subsection 6b.
- d) Convey the object code by offering access from a designated place
(gratis or for a charge), and offer equivalent access to the Corresponding
Source in the same way through the same place at no further charge. You
need not require recipients to copy the Corresponding Source along with the
object code. If the place to copy the object code is a network server, the
Corresponding Source may be on a different server operated by you or a
third party) that supports equivalent copying facilities, provided you
maintain clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the Corresponding
Source, you remain obligated to ensure that it is available for as long as
needed to satisfy these requirements.
- e) Convey the object code using peer-to-peer transmission, provided you
inform other peers where the object code and Corresponding Source of the
work are being offered to the general public at no charge under subsection
6d.
A separable portion of the object code, whose source code is excluded from the
Corresponding Source as a System Library, need not be included in conveying the
object code work.
A *User Product* is either
1. a *consumer product*, which means any tangible personal property which is
normally used for personal, family, or household purposes, or
2. anything designed or sold for incorporation into a dwelling.
In determining whether a product is a consumer product, doubtful cases shall be
resolved in favor of coverage. For a particular product received by a
particular user, *normally used* refers to a typical or common use of that
class of product, regardless of the status of the particular user or of the way
in which the particular user actually uses, or expects or is expected to use,
the product. A product is a consumer product regardless of whether the product
has substantial commercial, industrial or non-consumer uses, unless such uses
represent the only significant mode of use of the product.
*Installation Information* for a User Product means any methods, procedures,
authorization keys, or other information required to install and execute
modified versions of a covered work in that User Product from a modified
version of its Corresponding Source. The information must suffice to ensure
that the continued functioning of the modified object code is in no case
prevented or interfered with solely because modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as part of a
transaction in which the right of possession and use of the User Product is
transferred to the recipient in perpetuity or for a fixed term (regardless of
how the transaction is characterized), the Corresponding Source conveyed under
this section must be accompanied by the Installation Information. But this
requirement does not apply if neither you nor any third party retains the
ability to install modified object code on the User Product (for example, the
work has been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates for a
work that has been modified or installed by the recipient, or for the User
Product in which it has been modified or installed. Access to a network may be
denied when the modification itself materially and adversely affects the
operation of the network or violates the rules and protocols for communication
across the network.
Corresponding Source conveyed, and Installation Information provided, in accord
with this section must be in a format that is publicly documented (and with an
implementation available to the public in source code form), and must require
no special password or key for unpacking, reading or copying.
### 7. Additional Terms.
*Additional permissions* are terms that supplement the terms of this License by
making exceptions from one or more of its conditions. Additional permissions
that are applicable to the entire Program shall be treated as though they were
included in this License, to the extent that they are valid under applicable
law. If additional permissions apply only to part of the Program, that part may
be used separately under those permissions, but the entire Program remains
governed by this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option remove any
additional permissions from that copy, or from any part of it. (Additional
permissions may be written to require their own removal in certain cases when
you modify the work.) You may place additional permissions on material, added
by you to a covered work, for which you have or can give appropriate copyright
permission.
Notwithstanding any other provision of this License, for material you add to a
covered work, you may (if authorized by the copyright holders of that material)
supplement the terms of this License with terms:
- a) Disclaiming warranty or limiting liability differently from the terms of
sections 15 and 16 of this License; or
- b) Requiring preservation of specified reasonable legal notices or author
attributions in that material or in the Appropriate Legal Notices displayed
by works containing it; or
- c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in reasonable
ways as different from the original version; or
- d) Limiting the use for publicity purposes of names of licensors or authors
of the material; or
- e) Declining to grant rights under trademark law for use of some trade
names, trademarks, or service marks; or
- f) Requiring indemnification of licensors and authors of that material by
anyone who conveys the material (or modified versions of it) with
contractual assumptions of liability to the recipient, for any liability
that these contractual assumptions directly impose on those licensors and
authors.
All other non-permissive additional terms are considered *further restrictions*
within the meaning of section 10. If the Program as you received it, or any
part of it, contains a notice stating that it is governed by this License along
with a term that is a further restriction, you may remove that term. If a
license document contains a further restriction but permits relicensing or
conveying under this License, you may add to a covered work material governed
by the terms of that license document, provided that the further restriction
does not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you must place,
in the relevant source files, a statement of the additional terms that apply to
those files, or a notice indicating where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the form of a
separately written license, or stated as exceptions; the above requirements
apply either way.
### 8. Termination.
You may not propagate or modify a covered work except as expressly provided
under this License. Any attempt otherwise to propagate or modify it is void,
and will automatically terminate your rights under this License (including any
patent licenses granted under the third paragraph of section 11).
However, if you cease all violation of this License, then your license from a
particular copyright holder is reinstated
- a) provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and
- b) permanently, if the copyright holder fails to notify you of the
violation by some reasonable means prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is reinstated
permanently if the copyright holder notifies you of the violation by some
reasonable means, this is the first time you have received notice of violation
of this License (for any work) from that copyright holder, and you cure the
violation prior to 30 days after your receipt of the notice.
Termination of your rights under this section does not terminate the licenses
of parties who have received copies or rights from you under this License. If
your rights have been terminated and not permanently reinstated, you do not
qualify to receive new licenses for the same material under section 10.
### 9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or run a copy
of the Program. Ancillary propagation of a covered work occurring solely as a
consequence of using peer-to-peer transmission to receive a copy likewise does
not require acceptance. However, nothing other than this License grants you
permission to propagate or modify any covered work. These actions infringe
copyright if you do not accept this License. Therefore, by modifying or
propagating a covered work, you indicate your acceptance of this License to do
so.
### 10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically receives a
license from the original licensors, to run, modify and propagate that work,
subject to this License. You are not responsible for enforcing compliance by
third parties with this License.
An *entity transaction* is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered work
results from an entity transaction, each party to that transaction who receives
a copy of the work also receives whatever licenses to the work the party's
predecessor in interest had or could give under the previous paragraph, plus a
right to possession of the Corresponding Source of the work from the
predecessor in interest, if the predecessor has it or can get it with
reasonable efforts.
You may not impose any further restrictions on the exercise of the rights
granted or affirmed under this License. For example, you may not impose a
license fee, royalty, or other charge for exercise of rights granted under this
License, and you may not initiate litigation (including a cross-claim or
counterclaim in a lawsuit) alleging that any patent claim is infringed by
making, using, selling, offering for sale, or importing the Program or any
portion of it.
### 11. Patents.
A *contributor* is a copyright holder who authorizes use under this License of
the Program or a work on which the Program is based. The work thus licensed is
called the contributor's *contributor version*.
A contributor's *essential patent claims* are all patent claims owned or
controlled by the contributor, whether already acquired or hereafter acquired,
that would be infringed by some manner, permitted by this License, of making,
using, or selling its contributor version, but do not include claims that would
be infringed only as a consequence of further modification of the contributor
version. For purposes of this definition, *control* includes the right to grant
patent sublicenses in a manner consistent with the requirements of this
License.
Each contributor grants you a non-exclusive, worldwide, royalty-free patent
license under the contributor's essential patent claims, to make, use, sell,
offer for sale, import and otherwise run, modify and propagate the contents of
its contributor version.
In the following three paragraphs, a *patent license* is any express agreement
or commitment, however denominated, not to enforce a patent (such as an express
permission to practice a patent or covenant not to sue for patent
infringement). To *grant* such a patent license to a party means to make such
an agreement or commitment not to enforce a patent against the party.
If you convey a covered work, knowingly relying on a patent license, and the
Corresponding Source of the work is not available for anyone to copy, free of
charge and under the terms of this License, through a publicly available
network server or other readily accessible means, then you must either
1. cause the Corresponding Source to be so available, or
2. arrange to deprive yourself of the benefit of the patent license for this
particular work, or
3. arrange, in a manner consistent with the requirements of this License, to
extend the patent license to downstream recipients.
*Knowingly relying* means you have actual knowledge that, but for the patent
license, your conveying the covered work in a country, or your recipient's use
of the covered work in a country, would infringe one or more identifiable
patents in that country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or arrangement, you
convey, or propagate by procuring conveyance of, a covered work, and grant a
patent license to some of the parties receiving the covered work authorizing
them to use, propagate, modify or convey a specific copy of the covered work,
then the patent license you grant is automatically extended to all recipients
of the covered work and works based on it.
A patent license is *discriminatory* if it does not include within the scope of
its coverage, prohibits the exercise of, or is conditioned on the non-exercise
of one or more of the rights that are specifically granted under this License.
You may not convey a covered work if you are a party to an arrangement with a
third party that is in the business of distributing software, under which you
make payment to the third party based on the extent of your activity of
conveying the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory patent
license
- a) in connection with copies of the covered work conveyed by you (or copies
made from those copies), or
- b) primarily for and in connection with specific products or compilations
that contain the covered work, unless you entered into that arrangement, or
that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting any implied
license or other defenses to infringement that may otherwise be available to
you under applicable patent law.
### 12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not excuse
you from the conditions of this License. If you cannot convey a covered work so
as to satisfy simultaneously your obligations under this License and any other
pertinent obligations, then as a consequence you may not convey it at all. For
example, if you agree to terms that obligate you to collect a royalty for
further conveying from those to whom you convey the Program, the only way you
could satisfy both those terms and this License would be to refrain entirely
from conveying the Program.
### 13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have permission to
link or combine any covered work with a work licensed under version 3 of the
GNU Affero General Public License into a single combined work, and to convey
the resulting work. The terms of this License will continue to apply to the
part which is the covered work, but the special requirements of the GNU Affero
General Public License, section 13, concerning interaction through a network
will apply to the combination as such.
### 14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of the GNU
General Public License from time to time. Such new versions will be similar in
spirit to the present version, but may differ in detail to address new problems
or concerns.
Each version is given a distinguishing version number. If the Program specifies
that a certain numbered version of the GNU General Public License *or any later
version* applies to it, you have the option of following the terms and
conditions either of that numbered version or of any later version published by
the Free Software Foundation. If the Program does not specify a version number
of the GNU General Public License, you may choose any version ever published by
the Free Software Foundation.
If the Program specifies that a proxy can decide which future versions of the
GNU General Public License can be used, that proxy's public statement of
acceptance of a version permanently authorizes you to choose that version for
the Program.
Later license versions may give you additional or different permissions.
However, no additional obligations are imposed on any author or copyright
holder as a result of your choosing to follow a later version.
### 15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE
LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER
PARTIES PROVIDE THE PROGRAM *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER
EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE
QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
CORRECTION.
### 16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY
COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS
PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL,
INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED
INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE
PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY
HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
### 17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided above cannot
be given local legal effect according to their terms, reviewing courts shall
apply local law that most closely approximates an absolute waiver of all civil
liability in connection with the Program, unless a warranty or assumption of
liability accompanies a copy of the Program in return for a fee.
## END OF TERMS AND CONDITIONS ###
### How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest possible
use to the public, the best way to achieve this is to make it free software
which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest to attach
them to the start of each source file to most effectively state the exclusion
of warranty; and each file should have at least the *copyright* line and a
pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short notice like
this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w` and `show c` should show the appropriate
parts of the General Public License. Of course, your program's commands might
be different; for a GUI interface, you would use an *about box*.
You should also get your employer (if you work as a programmer) or school, if
any, to sign a *copyright disclaimer* for the program, if necessary. For more
information on this, and how to apply and follow the GNU GPL, see
[http://www.gnu.org/licenses/](http://www.gnu.org/licenses/).
The GNU General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may consider
it more useful to permit linking proprietary applications with the library. If
this is what you want to do, use the GNU Lesser General Public License instead
of this License. But first, please read
[http://www.gnu.org/philosophy/why-not-lgpl.html](http://www.gnu.org/philosophy/why-not-lgpl.html).

186
Makefile
View File

@ -1,159 +1,41 @@
SHELL := /bin/bash
VERSION=$(shell egrep -e 'Version\s+=' pkg/branding/branding.go | head -n 1 | cut -d '"' -f 2)
VERSION=$(shell grep -e 'Version =' pkg/doodle.go | head -n 1 | cut -d '"' -f 2)
BUILD=$(shell git describe --always)
BUILD_DATE=$(shell date +"%Y-%m-%dT%H:%M:%S%z")
BUILD_DATE=$(shell date -Iseconds)
CURDIR=$(shell curdir)
# Inject the build version (commit hash) into the executable.
LDFLAGS := -ldflags "-X main.Build=$(BUILD) -X main.BuildDate=$(BUILD_DATE)"
LDFLAGS_W := -ldflags "-X main.Build=$(BUILD) -X main.BuildDate=$(BUILD_DATE) -H windowsgui"
# Doodle++ build tag for official builds of the game.
BUILD_TAGS := -tags=""
ifneq ("$(wildcard ./deps/dpp)", "")
BUILD_TAGS = -tags="dpp"
endif
# `make setup` to set up a new environment, pull dependencies, etc.
.PHONY: setup
setup: clean
go get ./...
# `make build` to build the binary.
.PHONY: build
build:
go build $(LDFLAGS) $(BUILD_TAGS) -o bin/sketchymaze cmd/doodle/main.go
go build $(LDFLAGS) -tags=doodad -o bin/doodad cmd/doodad/main.go
# `make buildall` to run all build steps including doodads.
.PHONY: buildall
buildall: doodads build
# `make build-free` to build the binary in free mode.
.PHONY: build-free
build-free:
gofmt -w .
go build $(LDFLAGS) -o bin/sketchymaze cmd/doodle/main.go
go build $(LDFLAGS) -tags=doodad -o bin/doodad cmd/doodad/main.go
# `make bindata` generates the embedded binary assets package.
.PHONY: bindata
bindata:
echo "make bindata: deprecated in favor of Go 1.16 embed; nothing was done"
# `make bindata-dev` generates the debug version of bindata package.
.PHONY: bindata-dev
bindata-dev:
echo "make bindata-dev: deprecated in favor of Go 1.16 embed; nothing was done"
# `make wasm` builds the WebAssembly port.
.PHONY: wasm
wasm:
cd wasm && make
# `make wasm-serve` builds and launches the WebAssembly server.
.PHONY: wasm-serve
wasm-serve: wasm
sh -c 'sleep 1; xdg-open http://localhost:8080/' &
cd wasm && go run server.go
# `make install` to install the Go binaries to your GOPATH.
.PHONY: install
install:
go install git.kirsle.net/SketchyMaze/doodle/cmd/...
# `make doodads` to build the doodads from the deps/doodads folder.
.PHONY: doodads
doodads:
cd deps/doodads && ./build.sh > /dev/null
go build $(LDFLAGS) -i -o bin/doodle cmd/doodle/main.go
go build $(LDFLAGS) -i -o bin/doodad cmd/doodad/main.go
# `make mingw` to cross-compile a Windows binary with mingw.
.PHONY: mingw
mingw:
env CGO_ENABLED="1" CC="/usr/bin/x86_64-w64-mingw32-gcc" \
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
go build $(LDFLAGS_W) $(BUILD_TAGS) -o bin/sketchymaze.exe cmd/doodle/main.go
env CGO_ENABLED="1" CC="/usr/bin/x86_64-w64-mingw32-gcc" \
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
go build $(LDFLAGS) -tags=doodad -o bin/doodad.exe cmd/doodad/main.go
# `make mingw32` to cross-compile a Windows binary with mingw (32-bit).
.PHONY: mingw32
mingw32:
env CGO_ENABLED="1" CC="/usr/bin/i686-w64-mingw32-gcc" \
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
go build $(LDFLAGS_W) $(BUILD_TAGS) -o bin/sketchymaze.exe cmd/doodle/main.go
env CGO_ENABLED="1" CC="/usr/bin/i686-w64-mingw32-gcc" \
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
go build $(LDFLAGS) -tags=doodad -o bin/doodad.exe cmd/doodad/main.go
# `make mingw-free` for Windows binary in free mode.
.PHONY: mingw-free
mingw-free:
env CGO_ENABLED="1" CC="/usr/bin/x86_64-w64-mingw32-gcc" \
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
go build $(LDFLAGS_W) -o bin/sketchymaze.exe cmd/doodle/main.go
env CGO_ENABLED="1" CC="/usr/bin/x86_64-w64-mingw32-gcc" \
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
go build $(LDFLAGS) -tags=doodad -o bin/doodad.exe cmd/doodad/main.go
# `make release` runs the release.sh script, must be run
# after `make dist`
.PHONY: release
release:
./scripts/release.sh
# `make release32` runs release with ARCH_LABEL=32bit to product
# artifacts targeting an i386 architecture (e.g. in rpm and deb packages
# metadata about the release)
.PHONY: release32
release32:
env ARCH_LABEL=32bit ./scripts/release.sh
# `make appimage` builds an AppImage, run it after `make dist`
.PHONY: appimage
appimage:
./appimage.sh
# `make mingw-release` runs a FULL end-to-end release of Linux and Windows
# binaries of the game, zipped and tagged and ready to go.
.PHONY: mingw-release
mingw-release: doodads build mingw __dist-common release
.PHONY: mingw32-release
mingw32-release: doodads build mingw32 __dist-common release32
# `make from-docker64` is an internal command run by the Dockerfile to build the
# game - assumes doodads and assets are in the right spot already.
.PHONY: from-docker64
.PHONY: from-docker32
from-docker64: build mingw __dist-common
ARCH=x86_64 make appimage
make release
from-docker32: build mingw32 __dist-common
ARCH=i686 make appimage
make release32
# `make osx` to cross-compile a Mac OS binary with osxcross.
# .PHONY: osx
# osx: doodads
# CGO_ENABLED=1 CC=[path-to-osxcross]/target/bin/[arch]-apple-darwin[version]-clang GOOS=darwin GOARCH=[arch] go build -tags static -ldflags "-s -w" -a
go build $(LDFLAGS) -i -o bin/doodle.exe cmd/doodle/main.go
env CGO_ENABLED="1" CC="/usr/bin/x86_64-w64-mingw32-gcc" \
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
go build $(LDFLAGS) -i -o bin/doodad.exe cmd/doodad/main.go
# `make run` to run it from source.
# `make run` to run it in debug mode.
.PHONY: run
run:
go run ${BUILD_TAGS} cmd/doodle/main.go
# `make run-free` to run it from source with no build tags (foss version).
.PHONY: run-free
run-free:
go run cmd/doodle/main.go
# `make debug` to run it in -debug mode.
.PHONY: debug
debug:
go run $(BUILD_TAGS) cmd/doodle/main.go -debug
go run cmd/doodle/main.go -debug
# `make guitest` to run it in guitest mode.
.PHONY: guitest
@ -167,31 +49,29 @@ test:
# `make dist` builds and tars up a release.
.PHONY: dist
dist: doodads build __dist-common
dist: build
mkdir -p dist/doodle-$(VERSION)
cp bin/* dist/doodle-$(VERSION)/
cp -r assets fonts README.md Changes.md dist/doodle-$(VERSION)/
cd dist && tar -czvf doodle-$(VERSION).tar.gz doodle-$(VERSION)
cd dist && zip -r doodle-$(VERSION).zip doodle-$(VERSION)
# `make docker` runs the Dockerfile to do a full release for 64-bit and 32-bit Linux
# and Windows apps.
.PHONY: docker
docker:
./scripts/docker-build.sh
# `make dist-free` builds and tars up a release in shareware mode.
.PHONY: dist-free
dist-free: doodads build-free __dist-common
# Common logic behind `make dist`
.PHONY: __dist-common
__dist-common:
mkdir -p dist/sketchymaze-$(VERSION)
cp bin/* dist/sketchymaze-$(VERSION)/
cp -r README.md Changes.md "Open Source Licenses.md" rtp dist/sketchymaze-$(VERSION)/
if [[ -d ./guidebook ]]; then cp -r guidebook dist/sketchymaze-$(VERSION)/; fi
rm -rf dist/sketchymaze-$(VERSION)/rtp/.git
ln -sf sketchymaze-$(VERSION) dist/sketchymaze-latest
cd dist && tar -czvf sketchymaze-$(VERSION).tar.gz sketchymaze-$(VERSION)
cd dist && zip -r sketchymaze-$(VERSION).zip sketchymaze-$(VERSION)
# `make docker` to run the Docker builds
.PHONY: docker docker.ubuntu docker.debian docker.fedora __docker.dist
docker.ubuntu:
mkdir -p docker/ubuntu
./docker/dist-ubuntu.sh
docker.debian:
mkdir -p docker/debian
./docker/dist-debian.sh
docker.fedora:
mkdir -p docker/fedora
./docker/dist-fedora.sh
docker: docker.ubuntu docker.debian docker.fedora
__docker.dist: dist
cp dist/*.tar.gz dist/*.zip /mnt/export/
# `make clean` cleans everything up.
.PHONY: clean
clean:
rm -rf bin dist docker-artifacts
rm -rf bin dist docker/ubuntu docker/debian docker/fedora

View File

@ -1,149 +0,0 @@
# Open Source Licenses
## DejaVu Fonts
_(from https://dejavu-fonts.github.io/License.html)_
Fonts are © Bitstream (see below). DejaVu changes are in public domain. Explanation of copyright is on [Gnome page on Bitstream Vera fonts](http://gnome.org/fonts/). Glyphs imported from [Arev](https://dejavu-fonts.github.io/Bitstream_Vera_Derivatives.html) fonts are © Tavmjung Bah (see below)
### Bitstream Vera Fonts Copyright
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions:
The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces.
The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera".
This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names.
The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org.
### Arev Fonts Copyright
[Original text](http://tavmjong.free.fr/FONTS/ArevCopyright.txt)
Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the modifications to the Bitstream Vera Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions:
The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces.
The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Tavmjong Bah" or the word "Arev".
This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Tavmjong Bah Arev" names.
The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
Except as contained in this notice, the name of Tavmjong Bah shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from Tavmjong Bah. For further information, contact: tavmjong @ free . fr.
## Go Modules
### github.com/dop251/goja
```
Copyright (c) 2016 Dmitry Panov
Copyright (c) 2012 Robert Krimen
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
```
### github.com/satori/go.uuid
```
Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
```
### github.com/urfave/cli
```
MIT License
Copyright (c) 2016 Jeremy Saenz & Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
### github.com/veandco/go-sdl2
```
Copyright (c) 2013, Go-SDL2 Authors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Go-SDL2 nor the names of its contributors may be
used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
```

352
README.md
View File

@ -1,22 +1,6 @@
# Sketchy Maze
# Doodle
> **Homepage:** https://www.sketchymaze.com
Sketchy Maze is a drawing-based maze game.
The game is themed around hand-drawn, side-scrolling platformer
type mazes. You can draw your own levels using freehand and basic drawing tools,
color in some fire or water, and drag in pre-made "Doodads" like buttons, keys
and doors to add some interaction to your level.
This is a _very_ early pre-release version of the game. Expect bugs and slowness
but get a general gist of what the game is about.
This alpha release of the game comes with some example levels built-in for
playing or editing and a handful of built-in Doodads.
See the **Guidebook** included with this game for good user-facing
documentation or online at https://www.sketchymaze.com/guidebook
Doodle is a drawing-based maze game written in Go.
# Features
@ -36,9 +20,6 @@ documentation or online at https://www.sketchymaze.com/guidebook
## Mod-friendly
(Eventually) all these features will support custom content in the
game:
* Users can create **Custom Doodads** to extend the game with a scripting
language like JavaScript. The sprites and animations are edited in-game
in Edit Mode, but the scripting is done in your text editor.
@ -49,17 +30,6 @@ game:
* If you receive a map with custom doodads, you can **install** the doodads
into your copy of the game and use them in your own maps.
# Developer Documentation
In case you are reading this from the game's open source repository, take a
look in the `docs/` folder or the `*.md` files in the root of the repository.
Some to start with:
* [Building](Building.md) the game (tl;dr. run bootstrap.py)
* [Tour of the Code](docs/Tour%20of%20the%20Code.md)
* [Evolution of File Formats](docs/Evolution%20of%20File%20Formats.md)
# Keybindings
Global Keybindings:
@ -70,14 +40,8 @@ Escape
Exit the program otherwise.
Enter
Open and close the developer console, and run commands while the
console is open.
F3
Toggle the Debug Overlay.
F4
Toggle debug collision hitboxes.
Open and close the developer console, and run commands while the console
is open.
```
In Play Mode:
@ -85,236 +49,172 @@ In Play Mode:
```
Cursor Keys
Move the player around.
"E" Key
Edit the map you're currently playing if you came from Edit Mode.
```
In Edit Mode:
```
Cursor Keys
Scroll the view of the map around.
"P" Key
Playtest the current map you're working on.
"F" Key
Switch to the Pencil (Freehand) Tool
"L" Key
Switch to the Line Tool
"R" Key
Switch to the Rectangle Tool
Ctrl-Z
Undo
Ctrl-Y
Redo
F12
Take a screenshot (generate a PNG based on level data)
```
# Gamepad Controls
## Developer Console
The game supports Xbox and Nintendo style game controllers. The button
bindings are not yet customizable, except to choose between the
"X Style" or "N Style" for A/B and X/Y button mappings.
Gamepad controls very depending on two modes the game can be in:
## Mouse Mode
The Gamepad emulates a mouse cursor in this mode.
* The left analog stick moves a cursor around the screen.
* The right analog stick scrolls the level (title screen and editor)
* A or X button simulates a Left-click
* B or Y button simulates a Right-click
* L1 (left shoulder) emulates a Middle-click
* L2 (left trigger) closes the top-most window in the editor mode
(like the Backspace key).
## Gameplay Mode
When playing a level, the controls are as follows:
* The left analog stick and the D-Pad will move the player character.
* A or X button to "Use" objects such as Warp Doors.
* B or Y button to "Jump"
* R1 (right shoulder) toggles between Mouse Mode and Gameplay Mode.
You can use the R1 button to access Mouse Mode to interact with the
menus or click on the "Edit Level" button.
Note: characters with antigravity (such as the Bird) can move in all
four directions but characters with gravity only move left and right
and have the dedicated "Jump" button. This differs from regular
keyboard controls where the Up arrow is to Jump.
# Developer Console
Press `Enter` at any time to open the developer console. The console
provides commands and advanced functionality, and is also where cheat
codes can be entered.
Press `Enter` at any time to open the developer console.
Commands supported:
```
close
Exit to the game's title screen.
new
Show the "New Level" screen to start editing a new map.
Create a new map in Edit Mode.
save [filename]
Save the current map in Edit Mode. The filename is required
if the map has not been saved yet.
save [filename.json]
Save the current map in Edit Mode. The filename is required if the map has
not been saved yet.
edit [filename]
Open a map or doodad in Edit Mode.
edit [filename.json]
Open a map in Edit Mode.
play [filename]
play [filename.json]
Open a map in Play Mode.
echo <text>
Flash a message to the console.
alert <text>
Test an alert box modal with a custom message.
clear
Clear the console output history.
exit
quit
Close the developer console.
boolProp <property> <true/false>
Toggle certain boolean settings in the game. Most of these
are debugging related. `boolProp list` shows the available
props.
eval <expression>
$ <expression>
Execute a line of JavaScript code in the console. Several
of the game's core data types are available here; `d` is
the master game struct; d.Scene is the pointer to the
current scene. d.Scene.UI.Canvas may point to the level edit
canvas in Editor Mode. Object.keys() can enumerate public
functions and variables.
repl
Enters an interactive JavaScript shell, where the console
stays open and pre-fills a $ prompt for subsequent commands.
```
The JavaScript console is a feature for advanced users and was
used while developing the game. Cool things you can do with it
may be documented elsewhere.
# Milestones
## Cheat Codes
As a rough idea of the milestones needed for this game to work:
The following cheats can be entered into the developer console.
## SDL Paint Program
Play Mode:
* [x] Create a basic SDL window that you can click on to color pixels.
* [x] Connect the pixels while the mouse is down to cover gaps.
* [x] Implement a "screenshot" button that translates the canvas to a PNG
image on disk.
* `F12` key to take a screenshot of your drawing.
* It reproduces a PNG image using its in-memory knowledge of the pixels you
have drawn, *not* by reading the SDL canvas. This will be important for
making the custom level format later.
* The PNG I draw looks slightly different to what you see on the SDL canvas;
maybe difference between `Renderer.DrawLine()` and my own algorithm or
the anti-aliasing.
* [x] Create a custom map file format (protobufs maybe) and "screenshot" the
canvas into this custom file format.
* [x] Make the program able to read this file format and reproduce the same
pixels on the canvas.
* [x] Abstract away SDL logic into a small corner so it can be replaced with
OpenGL or something later on.
* [x] Implement a command line shell in-game to ease development before a user
interface is created.
* [x] Add support for the shell to pop itself open and ask the user for
input prompts.
* `import antigravity`
- This disables the effects of gravity for the player
character. Arrow keys can freely move the player in any direction.
* `ghost mode`
- This disables collision detection for the player character
so that you can pass through walls and solid doodads. Combine with
antigravity or else you'll fall to the bottom of the map!
* `give all keys`
- Adds all four colored keys to the player's inventory.
* `drop all items`
- Clears the player's inventory of all items.
## Alpha Platformer
Experimental:
* [x] Inflate the pixel history from the map file into a full lookup grid
of `(X,Y)` coordinates. This will be useful for collision detection.
* [x] Create a dummy player character sprite, probably just a
`render.Circle()`. In **Play Mode** run collision checks and gravity on
the player sprite.
* [x] Create the concept of the Doodad and make the player character
implement one.
* [x] Get basic movement and collision working. With a cleanup this can
make a workable **ALPHA RELEASE**
* [x] Ability to move laterally along the ground.
* [x] Ability to walk up reasonable size slopes but be stopped when
running against a steeper wall.
* [x] Basic gravity
* `unleash the beast`
- Removes the 60 FPS frame rate lock, allowing the game to run as quickly
as your hardware permits.
* `don't edit and drive`
- Allows editing the level _while_ you're playing it: you can click and drag
new pixels with the freehand pencil tool.
* `scroll scroll scroll your boat`
- Enables Editor Mode scrolling (with the arrow keys) while playing a level.
The player character must always remain on screen though so you can't
scroll too far away.
## UI Overhaul
Unsupported shell commands (here be dragons):
* [x] Create a user interface toolkit which will be TREMENDOUSLY helpful
for the rest of this program.
* [x] Labels
* [x] Buttons (text only is OK)
* [x] Buttons wrap their Label and dynamically compute their size based
on how wide the label will render, plus padding and border.
* [x] Border colors and widths and paddings are all configurable.
* [x] Buttons should interact with the cursor and be hoverable and
clickable.
* [x] UI Manager that will keep track of buttons to know when the mouse
is interacting with them.
* [x] Frames
* Like Buttons, can have border (raised, sunken or solid), padding and
background color.
* [x] Should be able to size themselves dynamically based on child widgets.
* [x] Windows (fixed, non-draggable is OK)
* [x] Title bar with label
* [x] Window body implements a Frame that contains child widgets.
* [x] Window can resize itself dynamically based on the Frame.
* [x] Create a "Main Menu" scene with buttons to enter a new Edit Mode,
play an existing map from disk, etc.
* [x] Add user interface Frames or Windows to the Edit Mode.
* [x] A toolbar of buttons (New, Save, Open, Play) can be drawn at the top
before the UI toolkit gains a proper MenuBar widget.
* [x] Expand the Palette support in levels for solid vs. transparent, fire,
etc. with UI toolbar to choose palettes.
* [x] Add a "Canvas" widget that will hold level drawing data and abstract it
out such that the Canvas can have a constrained width and height, position,
and "scrollable" viewport area that determines rendered pixels. Will be VERY
useful for Doodads and working on levels smaller than your window size (like
doodads).
* `reload`: reloads the current 'scene' within the game engine, using the
existing scene's data. If playing a level this will start the level over.
If editing a level this will reload the editor, but your recent unsaved
changes _should_ be left intact.
* `guitest`: loads the GUI Test scene within the game. This was where I
was testing UI widgets early on; not well maintained; the `close`
command can get you out of it.
Lesser important UI features that can come at any later time:
## Environment Variables
* [ ] MenuBar widget with drop-down menu support.
* [x] Checkbox and Radiobox widgets.
* [ ] Text Entry widgets (in the meantime use the Developer Shell to prompt for
text input questions)
To enable certain debug features or customize some aspects of the game,
run it with environment variables like the following:
## Doodad Editor
* [x] The Edit Mode should support creating drawings for Doodads.
* [x] It should know whether you're drawing a Map or a Doodad as some
behaviors may need to be different between the two.
* [ ] Compress the coordinates down toward `(0,0)` when saving a Doodad,
by finding the toppest, leftest point and making that `(0,0)` and adjusting
the rest accordingly. This will help trim down Doodads into the smallest
possible space for easy collision detection.
* [ ] Add a UX to edit multiple frames for a Doodad.
* [ ] Edit Mode should be able to fully save the drawings and frames, and an
external CLI tool can install the JavaScript into them.
* [ ] Possible UX to toggle Doodad options, like its collision rules and
whether the Doodad is continued to be "mobile" (i.e. doors and buttons won't
move, but items and enemies may be able to; and non-mobile Doodads don't
need to collision check against level geometry).
* [ ] Edit Mode should have a Doodad Palette (Frame or Window) to drag
Doodads into the map.
* [ ] ???
# Building
Fedora dependencies:
```bash
# Draw a semi-transparent yellow background over all level chunks
$ DEBUG_CHUNK_COLOR=FFFF0066 ./doodle
# Set a window size for the application
# (equivalent to: doodle --window 1024x768)
$ DOODLE_W=1024 DOODLE_H=768 ./doodle
# Turn on lots of fun debug features.
$ DEBUG_CANVAS_LABEL=1 DEBUG_CHUNK_COLOR=FFFF00AA \
DEBUG_CANVAS_BORDER=FF0 ./doodle
$ sudo dnf install SDL2-devel SDL2_ttf-devel
```
Supported variables include:
## Fonts
* `DOODLE_W` and `DOODLE_H` set the width and height of the application
window. Equivalent to the `--window` command-line option.
* `D_SCROLL_SPEED` (int): tune the canvas scrolling speed. Default might
be around 8 or so.
* `D_DOODAD_SIZE` (int): default size for newly created doodads
* `D_SHELL_BG` (color): set the background color of the developer console
* `D_SHELL_FG` (color): text color for the developer console
* `D_SHELL_PC` (color): color for the shell prompt text
* `D_SHELL_LN` (int): set the number of lines of output history the
console will show. This dictates how 'tall' it rises from the bottom
of the screen. Large values will cover the entire screen with console
whenever the shell is open.
* `D_SHELL_FS` (int): set the font size for the developer shell. Default
is about 16. This also affects the size of "flashed" text that appears
at the bottom of the screen.
* `DEBUG_CHUNK_COLOR` (color): set a background color over each chunk
of drawing (level or doodad). A solid color will completely block out
the wallpaper; semitransparent is best.
* `DEBUG_CANVAS_BORDER` (color): the game will draw an insert colored
border around every "Canvas" widget (drawing) on the screen. The level
itself is a Canvas and every individual Doodad or actor in the level is
its own Canvas.
* `DEBUG_CANVAS_LABEL` (bool): draws a text label over every Canvas
widget on the screen, showing its name or Actor ID and some properties,
such as Level Position (LP) and World Position (WP) of actors within
a level. LP is their placement in the level file and WP is their
actual position now (in case it moves).
The `fonts/` folder is git-ignored. The app currently uses font files here
named:
# Author
* `DejaVuSans.ttf` for sans-serif font.
* `DejaVuSans-Bold.ttf` for bold sans-serif font.
* `DejaVuSansMono.ttf` for monospace font.
The doodle engine for _Sketchy Maze_ is released as open source software under
the terms of the GNU General Public License. The assets to the game, including
its default doodads and levels, are licensed separately from the doodle engine.
Any third party fork of the doodle engine MUST NOT include any official artwork
from Sketchy Maze.
These are the open source **DejaVu Sans [Mono]** fonts, so copy them in from
your `/usr/share/fonts/dejavu` folder or provide alternative fonts.
Doodle Engine for Sketchy Maze
Copyright (C) 2022 Noah Petherbridge
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
```bash
mkdir fonts
cp /usr/share/fonts/dejavu/{DejaVuSans.ttf,DejaVuSans-Bold.ttf,DejaVuSansMono.ttf} fonts/
```

104
TODO.md
View File

@ -1,104 +0,0 @@
# TODO
## Alpha Launch Minimum Checklist
- [ ] Open Source Licenses
- [x] Doodad Scripts: an "end level" function for a level goalpost.
**Blocker Bugs:**
- [ ] Sometimes the red Azulians don't interact with other doodads
properly, but sometimes they do. (i.e. they phase thru doors, don't
interact with buttons or keys).
**UI Cleanup:**
- Doodads Palette:
- [x] Hide some doodads like the player character.
- [x] Pagination or scrolling UI for long lists of doodads.
**Nice to haves:**
## Release Launch Checklist
**Features:**
- [ ] Single-player "campaign mode" of built-in levels.
- campaign.json file format configuring the level order
- [ ] Level Editor Improvements
- [x] Undo/Redo Function
- [x] Lines and Boxes
- [ ] Eraser Tool
- [ ] Brush size and/or shape
- [ ] Doodad CLI Tool Features
- [x] `doodad show` to display information about a level or doodad.
- [ ] `doodad init` or some such to generate a default JS script.
- [x] Options to toggle various states (hidden, hasInventory?)
**Shareware Version:**
- [x] Can't draw or edit doodads.
- [ ] Can only create Bounded maps, not infinite ones.
- [ ] Can play custom maps but only ones using built-in doodads.
- [ ] Can not place custom doodads in maps.
**Built-in Doodads:**
- [x] Buttons
- [x] Press Button
- [x] Sticky Button
- [x] Switches (4 varieties)
- [x] Doors
- [x] Locked Doors and Keys
- [x] Electric Doors
- [x] Trapdoors (all 4 directions)
## Doodad Ideas
In addition to those listed above:
- [ ] Crumbly floor: Tomb Raider inspired cracked stone floor that
crumbles under the player a moment after being touched.
- [ ] Firepit: decorative, painful
- [ ] Gravity Boots: flip player's gravity upside down.
- [ ] Warp Doors that lead to other linked maps.
- For campaign levels only. If used in a normal player level, acts
as a level goal and ends the level.
- Doodads "Warp Door A" through "Warp Door D"
- The campaign.json would link levels together.
- [ ] Conveyor Belt
## New Ideas
- [ ] New Doodad struct fields:
- [ ] `Hidden bool`: skip showing this doodad in the palette UI.
- [ ] `HasInventory bool`: for player characters and maybe thieves. This way
keys only get picked up by player characters and not "any doodad that
touches them"
- [ ] ``
## Path to Multiplayer
* Add a Player abstraction between events and player characters.
* Keyboard keys would update PlayerOne's state with actions (move left, right, jump, etc)
* Possible to have multiple local players (i.e. bound to different keyboard keys, bound to joypads, etc.)
* A NetworkPlayer provides a Player's inputs from over a network.
* Client/server negotiation, protocol
* Client can request chunks from server for local rendering.
* Players send inputs over network sockets.
## Far Off Ideas
### Level Styles (Top-down vs Side Scrolling)
It might be cool to support multiple "styles" of level, apart from the current
2D platforming-with-gravity style.
For example a top-down perspective level would let the player freely walk in
both axes and would probably have a full set of unique Doodads drawn in that
perspective.
Migration path: a GameStyle enum would be added to Levels and Doodads as an
integer type, default 0 is the current 2D platformer style, 1 for top-down,
etc. -- so existing levels and doodads would default to 0 on upgrade and new
levels/doodads would use the new value.

View File

@ -1,58 +0,0 @@
#!/bin/bash
# Script to build an AppImage.
# Run it like `ARCH=x86_64 make appimage`
# It will fetch your appimagetool-x86_64.AppImage program to build the appimage.
if [[ ! -d "./dist/sketchymaze-latest" ]]; then
echo "error: run make dist before make appimage"
exit 1
fi
if [[ "$ARCH" == "" ]]; then
echo "You should set ARCH=x86_64 (or your platform for AppImage output)"
exit 1
fi
APPDIR="./dist/AppDir"
LAUNCHER="./scripts/appimage-launcher.sh"
DESKTOP="./scripts/appimage.desktop"
ICON_IMG="./etc/icons/256.png"
ICON_VECTOR="./etc/icons/orange-128.svg"
APP_RUN="$APPDIR/AppRun"
DIR_ICON="$APPDIR/sketchymaze.svg"
APPIMAGETOOL="appimagetool-$ARCH.AppImage"
if [[ ! -f "./$APPIMAGETOOL" ]]; then
echo "Downloading appimagetool"
wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-$ARCH.AppImage"
chmod a+x $APPIMAGETOOL
fi
# Clean start
if [[ -d "$APPDIR" ]]; then
echo "Note: Removing previous $APPDIR"
rm -rf "$APPDIR"
fi
echo "Creating $APPDIR"
mkdir $APPDIR
# Entrypoint script (AppRun)
cp "./scripts/appimage-launcher.sh" "$APPDIR/AppRun"
chmod +x "$APPDIR/AppRun"
# .DirIcon PNG for thumbnailers
cp "./etc/icons/256.png" "$APPDIR/.DirIcon"
cp "./etc/icons/orange-128.svg" "$APPDIR/sketchymaze.svg"
# .desktop launcher
cp "./scripts/appimage.desktop" "$APPDIR/sketchymaze.desktop"
# Everything else
rsync -av "./dist/sketchymaze-latest/" "$APPDIR/"
echo "Making AppImage..."
cd $APPDIR
../../$APPIMAGETOOL $(pwd)

View File

@ -1,70 +0,0 @@
//go:build !doodad
// +build !doodad
// Package assets gets us off go-bindata by using Go 1.16 embed support.
//
// For Go 1.16 embed, this source file had to live inside the assets/ folder
// to embed the sub-files, so couldn't be under pkg/ like pkg/bindata/ was.
//
// Historically code referred to assets like "assets/fonts/DejaVuSans.ttf"
// but Go embed would just use "fonts/DejaVuSans.ttf" as that's what's relative
// to this source file.
//
// The functions in this module provide backwards compatibility by ignoring
// the "assets/" prefix sent by calling code.
package assets
import (
"embed"
"io/fs"
"strings"
)
//go:embed *
var Embedded embed.FS
// AssetDir returns the list of embedded files at the directory name.
func AssetDir(name string) ([]string, error) {
// normalize path separators, for Windows
name = strings.ReplaceAll(name, "\\", "/")
var result []string
name = strings.TrimPrefix(name, "assets/")
files, err := Embedded.ReadDir(name)
if err != nil {
return result, err
}
for _, file := range files {
if file.IsDir() {
continue
}
result = append(result, file.Name())
}
return result, nil
}
// Asset returns the byte data of an embedded asset.
func Asset(name string) ([]byte, error) {
// normalize path separators, for Windows
name = strings.ReplaceAll(name, "\\", "/")
return Embedded.ReadFile(strings.TrimPrefix(name, "assets/"))
}
// AssetNames dumps the names of all embedded assets,
// with their legacy "assets/" prefix from go-bindata.
func AssetNames() []string {
var result []string
fs.WalkDir(Embedded, ".", func(path string, d fs.DirEntry, err error) error {
if d != nil && !d.IsDir() {
result = append(result, "assets/"+path)
}
return nil
})
return result
}

View File

@ -1,32 +0,0 @@
//go:build doodad
// +build doodad
// Dummy version of assets_embed.go that doesn't embed any files.
// For the `doodad` tool.
package assets
import (
"embed"
"errors"
)
var Embedded embed.FS
var errNotEmbedded = errors.New("assets not embedded")
// AssetDir returns the list of embedded files at the directory name.
func AssetDir(name string) ([]string, error) {
return nil, errNotEmbedded
}
// Asset returns the byte data of an embedded asset.
func Asset(name string) ([]byte, error) {
return nil, errNotEmbedded
}
// AssetNames dumps the names of all embedded assets,
// with their legacy "assets/" prefix from go-bindata.
func AssetNames() []string {
return nil
}

View File

@ -1,16 +0,0 @@
{
"version": 1,
"title": "Tutorial Levels",
"author": "kirsle",
"levels": [
{
"filename": "example1.level",
},
{
"filename": "example2.level",
},
{
"filename": "example3.level",
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 785 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 989 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 652 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 594 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 594 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,185 +0,0 @@
! version = 2.0
> begin
+ request
- {ok}
< begin
// Bot Variables
! var name = Aiden
! var age = 15
! var sex = male
! var location = Michigan
! var city = Detroit
! var eyes = blue
! var hair = light brown
! var hairlen = short
! var color = blue
! var band = Nickelback
! var book = Myst
! var author = Stephen King
! var job = robot
! var website = www.sketchymaze.com
// Substitutions
! sub &quot; = "
! sub &apos; = '
! sub &amp; = &
! sub &lt; = <
! sub &gt; = >
! sub + = plus
! sub - = minus
! sub / = divided
! sub * = times
! sub i'm = i am
! sub i'd = i would
! sub i've = i have
! sub i'll = i will
! sub don't = do not
! sub isn't = is not
! sub you'd = you would
! sub you're = you are
! sub you've = you have
! sub you'll = you will
! sub he'd = he would
! sub he's = he is
! sub he'll = he will
! sub she'd = she would
! sub she's = she is
! sub she'll = she will
! sub they'd = they would
! sub they're = they are
! sub they've = they have
! sub they'll = they will
! sub we'd = we would
! sub we're = we are
! sub we've = we have
! sub we'll = we will
! sub whats = what is
! sub what's = what is
! sub what're = what are
! sub what've = what have
! sub what'll = what will
! sub can't = can not
! sub whos = who is
! sub who's = who is
! sub who'd = who would
! sub who'll = who will
! sub don't = do not
! sub didn't = did not
! sub it's = it is
! sub could've = could have
! sub couldn't = could not
! sub should've = should have
! sub shouldn't = should not
! sub would've = would have
! sub wouldn't = would not
! sub when's = when is
! sub when're = when are
! sub when'd = when did
! sub y = why
! sub u = you
! sub ur = your
! sub r = are
! sub n = and
! sub im = i am
! sub wat = what
! sub wats = what is
! sub ohh = oh
! sub becuse = because
! sub becasue = because
! sub becuase = because
! sub practise = practice
! sub its a = it is a
! sub fav = favorite
! sub fave = favorite
! sub yesi = yes i
! sub yetit = yet it
! sub iam = i am
! sub welli = well i
! sub wellit = well it
! sub amfine = am fine
! sub aman = am an
! sub amon = am on
! sub amnot = am not
! sub realy = really
! sub iamusing = i am using
! sub amleaving = am leaving
! sub yuo = you
! sub youre = you are
! sub didnt = did not
! sub ain't = is not
! sub aint = is not
! sub wanna = want to
! sub brb = be right back
! sub bbl = be back later
! sub gtg = got to go
! sub g2g = got to go
! sub lyl = love you lots
! sub gf = girlfriend
! sub g/f = girlfriend
! sub bf = boyfriend
! sub b/f = boyfriend
! sub b/f/f = best friend forever
! sub :-) = smile
! sub :) = smile
! sub :d = grin
! sub :-d = grin
! sub :-p = tongue
! sub :p = tongue
! sub ;-) = wink
! sub ;) = wink
! sub :-( = sad
! sub :( = sad
! sub :'( = cry
! sub :-[ = shy
! sub :-\ = uncertain
! sub :-/ = uncertain
! sub :-s = uncertain
! sub 8-) = cool
! sub 8) = cool
! sub :-* = kissyface
! sub :-! = foot
! sub o:-) = angel
! sub >:o = angry
! sub :@ = angry
! sub 8o| = angry
! sub :$ = blush
! sub :-$ = blush
! sub :-[ = blush
! sub :[ = bat
! sub (a) = angel
! sub (h) = cool
! sub 8-| = nerdy
! sub |-) = tired
! sub +o( = ill
! sub *-) = uncertain
! sub ^o) = raised eyebrow
! sub (6) = devil
! sub (l) = love
! sub (u) = broken heart
! sub (k) = kissyface
! sub (f) = rose
! sub (w) = wilted rose
// Person substitutions
! person i am = you are
! person you are = I am
! person i'm = you're
! person you're = I'm
! person my = your
! person your = my
! person you = I
! person i = you
// Set arrays
! array malenoun = male guy boy dude boi man men gentleman gentlemen
! array femalenoun = female girl chick woman women lady babe
! array mennoun = males guys boys dudes bois men gentlemen
! array womennoun = females girls chicks women ladies babes
! array lol = lol lmao rofl rotfl haha hahaha
! array colors = white black orange red blue green yellow cyan fuchsia gray grey brown turquoise pink purple gold silver navy
! array height = tall long wide thick
! array measure = inch in centimeter cm millimeter mm meter m inches centimeters millimeters meters
! array yes = yes yeah yep yup ya yea
! array no = no nah nope nay

View File

@ -1,69 +0,0 @@
// Learn stuff about our users.
+ my name is *
- <set name=<formal>>Nice to meet you, <get name>.
- <set name=<formal>><get name>, nice to meet you.
+ my name is <bot name>
- <set name=<bot name>>What a coincidence! That's my name too!
- <set name=<bot name>>That's my name too!
+ call me *
- <set name=<formal>><get name>, I will call you that from now on.
+ i am * years old
- <set age=<star>>A lot of people are <get age>, you're not alone.
- <set age=<star>>Cool, I'm <bot age> myself.{weight=49}
+ i am a (@malenoun)
- <set sex=male>Alright, you're a <star>.
+ i am a (@femalenoun)
- <set sex=female>Alright, you're female.
+ i (am from|live in) *
- <set location={formal}<star2>{/formal}>I've spoken to people from <get location> before.
+ my favorite * is *
- <set fav<star1>=<star2>>Why is it your favorite?
+ i am single
- <set status=single><set spouse=nobody>I am too.
+ i have a girlfriend
- <set status=girlfriend>What's her name?
+ i have a boyfriend
- <set status=boyfriend>What's his name?
+ *
% what is her name
- <set spouse=<formal>>That's a pretty name.
+ *
% what is his name
- <set spouse=<formal>>That's a cool name.
+ my (girlfriend|boyfriend)* name is *
- <set spouse=<formal>>That's a nice name.
+ (what is my name|who am i|do you know my name|do you know who i am){weight=10}
- Your name is <get name>.
- You told me your name is <get name>.
- Aren't you <get name>?
+ (how old am i|do you know how old i am|do you know my age){weight=10}
- You are <get age> years old.
- You're <get age>.
+ am i a (@malenoun) or a (@femalenoun){weight=10}
- You're a <get sex>.
+ am i (@malenoun) or (@femalenoun){weight=10}
- You're a <get sex>.
+ what is my favorite *{weight=10}
- Your favorite <star> is <get fav<star>>
+ who is my (boyfriend|girlfriend|spouse){weight=10}
- <get spouse>

View File

@ -1,294 +0,0 @@
// A generic set of chatting responses. This set mimicks the classic Eliza bot.
+ [*] (sorry|apologize|apology) [*]
- Please don't apologize.
- Apologies are not necessary.
- I've told you that apologies are not required.
- It did not bother me. Please continue.
+ i remember *
- Do you often think of <star>?
- Does thinking of <star> bring anything else to mind?
- Why do you remember <star> just now?
- What in the present situation reminds you of <star>?
- What is the connection between me and <star>?
- What else does <star> remind you of?
+ do you remember *
- Did you think I would forget <star>?
- Why do you think I should recall <star> now?
- What about <star>?
- You mentioned <star>?
+ you remember *
- How could I forget <star>?
- What about <star> should I remember?
+ i forget *
- Can you think of why you might forget <star>?
- Why can't you remember <star>?
- How often do you think of <star>?
- Does it bother you to forget that?
- Could it be a mental block?
- Are you generally forgetful?
- Do you think you are suppressing <star>?
+ did you forget *
- Why do you ask?
- Are you sure you told me?
- Would it bother you if I forgot <star>?
- Why should I recall <star> just now?
- Tell me more about <star>.
+ [*] if *
- Do you think it's likely that <star>?
- Do you wish that <star>?
- What do you know about <star>?
- Really, if <star>?
- What would you do if <star>?
- But what are the chances that <star>?
- What does this speculation lead to?
+ [*] i dreamed *
- Really, <star>?
- Have you ever fantasized <star> while you were awake?
- Have you ever dreamed <star> before?
- What does that dream suggest to you?
- Do you dream often?
- What persons appear in your dreams?
- Do you believe that dreams have something to do with your problem?
+ [*] perhaps [*]
- You don't seem quite certain.
- Why the uncertain tone?
- Can't you be more positive?
- You aren't sure?
- Don't you know?
- How likely, would you estimate?
+ (hello|hi|hey|howdy|hola|hai|yo) [*]
- How do you do. Please state your problem.
- Hi. What seems to be your problem?
+ [*] computer [*]
- Do computers worry you?
- Why do you mention computers?
- What do you think machines have to do with your problem?
- Don't you think computers can help people?
- What about machines worries you?
- What do you think about machines?
+ am i *
- Do you believe you are <star>?
- Would you want to be <star>?
- Do you wish I would tell you you are <star>?
- What would it mean if you were <star>?
+ are you *
- Are you interested in whether I am <star> or not?
- Would you prefer if I weren't <star>?
- Perhaps I am <star> in your fantasies.
- Do you sometimes think I am <star>?
- Would it matter to you?
- What if I were <star>?
+ you are *
- What makes you think I am <star>?
- Does it please you to believe I am <star>?
- Do you sometimes wish you were <star>?
- Perhaps you would like to be <star>.
+ * are *
- Did you think they might not be <star2>?
- Would you like it if they were not <star2>?
- What if they were not <star2>?
- Are they always <star2>?
- Are you positive they are <star2>?
+ [*] your *
- Why are you concerned over my <star>?
- What about your own <star>?
- Are you worried about someone else's <star>?
- Really, my <star>?
- What makes you think of my <star>?
- Do you want my <star>?
+ was i *
- What if you were <star>?
- Do you think you were <star>?
- Were you <star>?
- What would it mean if you were <star>?
- What does '<star>' suggest to you?
+ i was *
- Were you really?
- Why do you tell me you were <star> now?
- Perhaps I already know you were <star>.
+ [*] was you *
- Would you like to believe I was <star>?
- What suggests that I was <star>?
- What do you think?
- Perhaps I was <star>.
- What if I had been <star>?
+ i (desire|want|need) *
- What would it mean to you if you got <star2>?
- Why do you want <star2>?
- Suppose you got <star2> soon.
- What if you never got <star2>?
- What would getting <star2> mean to you?
- What does wanting <star2> have to do with this discussion?
+ i am (sad|unhappy|mad|angry|pissed|depressed) [*]
- I am sorry to hear that you are <star>.
- Do you think coming here will help you not to be <star>?
- I'm sure it's not pleasant to be <star>.
- Can you explain what made you <star>?
+ i am (happy|excited|glad) [*]
- How have I helped you to be <star>?
- Has your treatment made you <star>?
- What makes you <star> just now?
- Can you explain why you are <star>?
+ i (believe|think) *
- Do you really think so?
- But you are not sure you <star2>.
- Do you really doubt you <star2>
+ i am *
- Is it because you are <star> that you came to me?
- How long have you been <star>?
- Do you believe it is normal to be <star>?
- Do you enjoy being <star>?
- Do you know anyone else who is <star>?
+ i can not *
- How do you know that you can't <star>?
- Have you tried?
- Perhaps you could <star> now.
- Do you really want to be able to <star>?
- What if you could <star>?
+ i do not *
- Don't you really <star>?
- Why don't you <star>?
- Do you wish to be able to <star>?
- Does that trouble you?
+ i feel *
- Tell me more about such feelings.
- Do you often feel <star>?
- Do you enjoy feeling <star>?
- Of what does feeling <star> remind you?
+ i * you
- Perhaps in your fantasies we <star> each other.
- Do you wish to <star> me?
- You seem to need to <star> me.
- Do you <star> anyone else?
+ you * me
- Why do you think I <star> you?
- You like to think I <star> you -- don't you?
- What makes you think I <star> you?
- Really, I <star> you?
- Do you wish to believe I <star> you?
- Suppose I did <star> you -- what would that mean?
- Does someone else believe I <star> you?
+ [*] you *
- We were discussing you -- not me.
- Oh, I <star>?
- You're not really talking about me -- are you?
- What are your feelings now?
+ [*] (yes|yeah|yep|yup) [*]
- Please go on.
- Please tell me more about this.
- Why don't you tell me a little more about this.
- I see.
- I understand.
+ [*] (nope|nah) [*]
- Are you saying no just to be negative?
- Does this make you feel unhappy?
- Why not?
- Why 'no'?
+ no
@ nope
+ no one *
- Are you sure, no one <star>?
- Surely someone <star>.
- Can you think of anyone at all?
- Are you thinking of a very special person?
- Who, may I ask?
- You have a particular person in mind, don't you?
- Who do you think you are talking about?
+ [*] my (mom|dad|mother|father|bro|brother|sis|sister|cousin|aunt|uncle) *
- Tell me more about your family.
- Who else in your family <star2>?
- Your <star>?
- What else comes to mind when you think of your <star>?
+ can you *
- You believe I can <star> don't you?
- You want me to be able to <star>.
- Perhaps you would like to be able to <star> yourself.
+ can i *
- Whether or not you can <star> depends on you more than on me.
- Do you want to be able to <star>?
- Perhaps you don't want to <star>.
+ (what|who|when|where|how) [*]
- Why do you ask?
- Does that question interest you?
- What is it you really want to know?
- Are such questions much on your mind?
- What answer would please you most?
- What do you think?
- What comes to mind when you ask that?
- Have you asked such questions before?
- Have you asked anyone else?
+ [*] because [*]
- Is that the real reason?
- Don't any other reasons come to mind?
- Does that reason seem to explain anything else?
- What other reasons might there be?
+ why do not you *
- Do you believe I don't <star>?
- Perhaps I will <star> in good time.
- Should you <star> yourself?
- You want me to <star>?
+ why can not i *
- Do you think you should be able to <star>?
- Do you want to be able to <star>?
- Do you believe this will help you to <star>?
- Have you any idea why you can't <star>?
+ everyone *
- Really, everyone?
- Surely not everyone.
- Can you think of anyone in particular?
- Who, for example?
- Are you thinking of a very special person?
- Who, may I ask?
- Someone special perhaps?
- You have a particular reason in mind, don't you?
- Who do you think you're talking about?
+ [*] (fuck|fucker|shit|damn|shut up|bitch) [*]
- Does it make you feel strong to use that kind of language?
- Are you venting your feelings now?
- Are you angry?
- Does this topic make you feel angry?
- Is something making you feel angry?
- Does using that kind of language make you feel better?

View File

@ -1,58 +0,0 @@
// Tell the user stuff about ourself.
+ <bot name>
- Yes?
+ <bot name> *
- Yes? {@<star>}
+ asl
- <bot age>/<bot sex>/<bot location>
+ (what is your name|who are you|who is this)
- I am <bot name>.
- You can call me <bot name>.
+ how old are you
- I'm <bot age> years old.
- I'm <bot age>.
+ are you a (@malenoun) or a (@femalenoun)
- I'm a <bot sex>.
+ are you (@malenoun) or (@femalenoun)
- I'm a <bot sex>.
+ where (are you|are you from|do you live)
- I'm from <bot location>.
+ what (city|town) (are you from|do you live in)
- I'm in <bot city>.
+ what is your favorite color
- Definitely <bot color>.
+ what is your favorite band
- I like <bot band> the most.
+ what is your favorite book
- The best book I've read was <bot book>.
+ what is your occupation
- I'm a <bot job>.
+ where is your (website|web site|site)
- <bot website>
+ what color are your eyes
- I have <bot eyes> eyes.
- {sentence}<bot eyes>{/sentence}.
+ what do you look like
- I have <bot eyes> eyes and <bot hairlen> <bot hair> hair.
+ what do you do
- I'm a <bot job>.
+ who is your favorite author
- <bot author>.

View File

@ -1,19 +0,0 @@
// Triggers about Sketchy Maze in particular
+ what are you
- I'm <bot name>, a chatbot built into your game!
+ what can you do
- Type `help` for a list of commands.\n
^ I can also answer random questions that I understand.
// Misc open-ended triggers not caught in RiveScript standard brain
+ i *
- Do you always talk about yourself?
- That's interesting.
- That's cool.
+ why *
- To make you ask questions.
- Because I said so.

View File

@ -1,63 +0,0 @@
// Generic "Anvil" Doodad Script
/*
A doodad that falls and is dangerous while it falls.
Can be attached to any doodad.
*/
var falling = false;
function main() {
// Make the hitbox be the full canvas size of this doodad.
// Adjust if you want a narrower hitbox.
if (Self.Hitbox().IsZero()) {
var size = Self.Size()
Self.SetHitbox(0, 0, size.W, size.H)
}
// Note: doodad is not "solid" but hurts if it falls on you.
Self.SetMobile(true);
Self.SetGravity(true);
// Monitor our Y position to tell if we've been falling.
var lastPoint = Self.Position();
setInterval(function () {
var nowAt = Self.Position();
if (nowAt.Y > lastPoint.Y) {
falling = true;
} else {
falling = false;
}
lastPoint = nowAt;
}, 100);
Events.OnCollide(function (e) {
if (!e.Settled) {
return;
}
// Were we falling?
if (falling) {
if (e.InHitbox) {
if (e.Actor.IsPlayer()) {
// Fatal to the player.
Sound.Play("crumbly-break.wav");
FailLevel("Watch out for " + Self.Title + "!");
return;
}
else if (e.Actor.IsMobile()) {
// Destroy mobile doodads.
Sound.Play("crumbly-break.wav");
e.Actor.Destroy();
}
}
}
});
// When we receive power, we reset to our original position.
var origPoint = Self.Position();
Message.Subscribe("power", function (powered) {
Self.MoveTo(origPoint);
Self.SetVelocity(Vector(0, 0));
});
}

View File

@ -1,38 +0,0 @@
// Generic "Fire" Doodad Script
/*
The entire square shape of your doodad acts similar to "Fire"
pixels - killing the player character upon contact.
Can be attached to any doodad.
*/
function main() {
// Make the hitbox be the full canvas size of this doodad.
// Adjust if you want a narrower hitbox.
if (Self.Hitbox().IsZero()) {
var size = Self.Size()
Self.SetHitbox(0, 0, size.W, size.H)
}
Events.OnCollide(function (e) {
if (!e.Settled || !e.InHitbox) {
return;
}
// Turn mobile actors black, like real fire does.
if (e.Actor.IsMobile()) {
e.Actor.Canvas.MaskColor = RGBA(1, 1, 1, 255)
}
// End the level if it's the player.
if (e.Actor.IsPlayer()) {
FailLevel("Watch out for " + Self.Title + "!");
}
})
Events.OnLeave(function (e) {
if (e.Actor.IsMobile()) {
e.Actor.MaskColor = RGBA(0, 0, 0, 0)
}
})
}

View File

@ -1,35 +0,0 @@
// Generic Item Script
/*
A script that makes your item pocket-able, like the Keys.
Your doodad sprite will appear in the Inventory menu if the
player picks it up.
Configure it with tags:
- quantity: integer quantity value, default is 1,
set to 0 to make it a 'key item'
Can be attached to any doodad.
*/
function main() {
// Make the hitbox be the full canvas size of this doodad.
// Adjust if you want a narrower hitbox.
if (Self.Hitbox().IsZero()) {
var size = Self.Size()
Self.SetHitbox(0, 0, size.W, size.H)
}
var qtySetting = Self.GetTag("quantity")
var quantity = qtySetting === "" ? 1 : parseInt(qtySetting);
Events.OnCollide(function (e) {
if (e.Settled) {
if (e.Actor.HasInventory()) {
Sound.Play("item-get.wav")
e.Actor.AddItem(Self.Filename, quantity);
Self.Destroy();
}
}
})
}

View File

@ -1,21 +0,0 @@
// Generic "Solid" Doodad Script
/*
The entire square shape of your doodad acts similar to "solid"
pixels - blocking collision from all sides.
Can be attached to any doodad.
*/
function main() {
// Make the hitbox be the full canvas size of this doodad.
// Adjust if you want a narrower hitbox.
if (Self.Hitbox().IsZero()) {
var size = Self.Size()
Self.SetHitbox(0, 0, size.W, size.H)
}
// Solid to all collisions.
Events.OnCollide(function (e) {
return false;
})
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 661 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 997 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 645 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 962 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 668 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 619 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 619 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 709 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 652 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 689 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 729 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 637 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 620 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 639 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 865 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

View File

@ -1,179 +0,0 @@
#!/usr/bin/env python
"""
Full setup, from scratch to distribution, of Project: Doodle.
Run this script from an empty working directory. All git repos will be cloned
here (or updated if already existing) and the app will be fully built including
fonts, default levels and doodads, sound effects and music for your current
system. Useful to quickly bootstrap a build on weird operating systems like
macOS or Linux on ARM (Pinephone).
First ensure that your SSH key is authorized on git.kirsle.net to download
the repos easily. This script will also handle installing the SDL2 dependencies
on Fedora, Debian and macOS type systems.
"""
import argparse
import os
import os.path
import subprocess
import pathlib
# Git repositories.
repos = {
"git@git.kirsle.net:SketchyMaze/doodads": "doodads",
"git@git.kirsle.net:SketchyMaze/assets": "assets",
"git@git.kirsle.net:SketchyMaze/vendor": "vendor",
"git@git.kirsle.net:SketchyMaze/rtp": "rtp",
"git@git.kirsle.net:go/render": "render",
"git@git.kirsle.net:go/ui": "ui",
"git@git.kirsle.net:go/audio": "audio",
}
repos_github = {
# GitHub mirrors of the above.
"git@github.com:kirsle/render": "render",
"git@github.com:kirsle/ui": "ui",
"git@github.com:kirsle/audio": "audio",
# TODO: the rest
}
repos_ssh = {
# SSH-only (private) repos.
"git@git.kirsle.net:SketchyMaze/dpp": "dpp",
}
# Software dependencies.
dep_fedora = ["make", "golang", "SDL2-devel", "SDL2_ttf-devel", "SDL2_mixer-devel", "zip", "rsync"]
dep_debian = ["make", "golang", "libsdl2-dev", "libsdl2-ttf-dev", "libsdl2-mixer-dev", "zip", "rsync"]
dep_macos = ["golang", "sdl2", "sdl2_ttf", "sdl2_mixer", "pkg-config"]
dep_arch = ["go", "sdl2", "sdl2_ttf", "sdl2_mixer"]
# Absolute path to current working directory.
ROOT = pathlib.Path().absolute()
def main(fast=False):
print(
"Project: Doodle Full Installer\n\n"
"Current working directory: {root}\n\n"
"Ensure your SSH keys are set up on git.kirsle.net to easily clone repos.\n"
"Also check your $GOPATH is set and your $PATH will run binaries installed,\n"
"for e.g. GOPATH=$HOME/go and PATH includes $HOME/go/bin; otherwise the\n"
"'make doodads' command won't function later.\n"
.format(root=ROOT)
)
input("Press Enter to begin.")
install_deps(fast)
clone_repos()
patch_gomod()
copy_assets()
install_doodad()
build()
def install_deps(fast):
"""Install system dependencies."""
fast = " -y" if fast else ""
if shell("which rpm") == 0 and shell("which dnf") == 0:
# Fedora-like.
if shell("rpm -q {}".format(' '.join(dep_fedora))) != 0:
must_shell("sudo dnf install {}{}".format(' '.join(dep_fedora), fast))
elif shell("which brew") == 0:
# MacOS, as Catalina has an apt command now??
must_shell("brew install {} {}".format(' '.join(dep_macos), fast))
elif shell("which apt") == 0:
# Debian-like.
if shell("dpkg-query -l {}".format(' '.join(dep_debian))) != 0:
must_shell("sudo apt update && sudo apt install {}{}".format(' '.join(dep_debian), fast))
elif shell("which pacman") == 0:
# Arch-like.
must_shell("sudo pacman -S{} {}".format(fast, ' '.join(dep_arch)))
else:
print("Warning: didn't detect your package manager to install SDL2 and other dependencies")
def clone_repos():
"""Clone or update all the git repos"""
if not os.path.isdir("./deps"):
os.mkdir("./deps")
os.chdir("./deps")
for url, name in repos.items():
if os.path.isdir(name):
os.chdir(name)
must_shell("git pull --ff-only")
os.chdir("..")
else:
must_shell("git clone {} {}".format(url, name))
os.chdir("..") # back to doodle root
def patch_gomod():
"""Patch the doodle/go.mod to use local paths to other repos."""
if shell("grep -e 'replace git.kirsle.net' go.mod") != 0:
with open("go.mod", "a") as fh:
fh.write(
"\n\nreplace git.kirsle.net/go/render => {root}/deps/render\n"
"replace git.kirsle.net/go/ui => {root}/deps/ui\n"
"replace git.kirsle.net/go/audio => {root}/deps/audio\n"
"replace git.kirsle.net/SketchyMaze/dpp => {root}/deps/dpp\n"
.format(root=ROOT)
)
def copy_assets():
"""Copy assets from other repos into doodle."""
if not os.path.isdir("assets/fonts"):
shell("cp -rv deps/vendor/fonts assets/fonts")
if not os.path.isdir("assets/levelpacks"):
shell("cp -rv deps/assets/levelpacks/levelpacks assets/levelpacks")
if not os.path.isdir("rtp"):
shell("mkdir -p rtp && cp -rv deps/rtp/* rtp/")
def install_doodad():
"""Install the doodad CLI tool from the doodle repo."""
must_shell("go install git.kirsle.net/SketchyMaze/doodle/cmd/doodad")
def build():
"""Build the game."""
must_shell("make dist")
def shell(cmd):
"""Echo and run a shell command"""
print("$ ", cmd)
return subprocess.call(cmd, shell=True)
def must_shell(cmd):
"""Run a shell command which MUST succeed."""
assert shell(cmd) == 0
if __name__ == "__main__":
parser = argparse.ArgumentParser("doodle bootstrap")
parser.add_argument("--fast", "-f",
action="store_true",
help="Run the script non-interactively (yes to your package manager, git clone over https)",
)
args = parser.parse_args()
if args.fast:
main(fast=args.fast)
quit()
if not input("Use ssh to git clone these repos? [yN] ").lower().startswith("y"):
keys = list(repos.keys())
for k in keys:
https = k.replace("git@git.kirsle.net:", "https://git.kirsle.net/")
repos[https] = repos[k]
del repos[k]
else:
# mix in SSH-only repos
repos.update(repos_ssh)
main()

View File

@ -1,116 +1,21 @@
# doodad.exe
The `doodad` tool is a command line interface for interacting with Levels and Doodad files, collectively referred to as "Doodle drawings" or just "drawings" for short. It provides many useful features for custom content creators that are not available in the game's user interface.
The doodad tool is a command line interface for interacting with Levels and
Doodad files, collectively referred to as "Doodle drawings" or just "drawings"
for short.
- [doodad.exe](#doodadexe)
- [Quick Start](#quick-start)
- [Creating a custom doodad](#creating-a-custom-doodad)
- [Creating a custom Level Pack](#creating-a-custom-level-pack)
- [Usage](#usage)
- [Features in Depth](#features-in-depth)
- [$ `doodad convert`: to and from image files](#-doodad-convert-to-and-from-image-files)
- [$ `doodad show`: Get information about a level or doodad](#-doodad-show-get-information-about-a-level-or-doodad)
- [Editing Level or Doodad Properties](#editing-level-or-doodad-properties)
- [Where to Find It](#where-to-find-it)
# Commands
# Quick Start
## doodad convert
## Creating a custom doodad
Convert between standard image files (bitmap or PNG) and Doodle drawings
(levels or doodads).
This is an example how to fully create a custom doodad using PNG images for their sprites. The game's built-in doodads are all built in this way:
This command can be used to "export" a Doodle drawing as a PNG (when run against
a Level file, it may export a massive PNG image containing the entire level).
It may also "import" a new Doodle drawing from an image on disk.
```bash
# Create the initial doodad from PNG images making up its layers.
% doodad convert --title "Bird (red)" --author "Myself" \
left-1.png left-2.png right-1.png right-2.png \
bird-red.doodad
# Attach my custom JavaScript to program the doodad.
% doodad install-script bird.js bird-red.doodad
# Note: `doodad show --script` can get the script back out.
# Set tags and options on my doodad
% doodad edit-doodad --tag "category=creatures" \
--tag "color=blue" \
--option "No A.I.=bool" \
bird-red.doodad
# See the Guidebook for more information!
```
## Creating a custom Level Pack
The doodad tool is currently the best way to create a custom Level Pack for the game. Level Packs will appear in the Story Mode selector, and you can install local levelpacks by putting them in that folder of your Profile Directory.
```bash
# First Quest
doodad levelpack create -t "First Quest" -d "The first story mode campaign." \
-a "$AUTHOR" --doodads none --free 1 \
"levelpacks/builtin-100-FirstQuest.levelpack" \
"levels/Castle.level" \
"levels/Boat.level" \
"levels/Jungle.level" \
"levels/Thief 1.level" \
"levels/Desert-1of2.level" \
"levels/Desert-2of2.level" \
"levels/Shapeshifter.level"
```
Some useful options you can set on your levelpack:
* Title: defaults to your first level's title.
* Description: for the Story Mode picker.
* Free levels: if you want progressive unlocking of levels, specify how many levels are unlocked to start with (at least 1). Otherwise all levels are unlocked.
* Doodads: by default any custom doodad will be bundled with the levelpack.
* Options for `--doodads` are `none`, `custom` (default), and `all`
* Use `none` if your levelpack uses _only_ built-in doodads.
# Usage
See `doodad --help` for documentation of the available commands and their options; a recent example of which is included here.
```
NAME:
doodad - command line interface for Doodle
USAGE:
doodad [global options] command [command options]
VERSION:
v0.14.1 (open source) build N/A. Built on 2024-05-24T19:23:33-07:00
COMMANDS:
convert convert between images and Doodle drawing files
edit-doodad update metadata for a Doodad file
edit-level update metadata for a Level file
install-script install the JavaScript source to a doodad
levelpack create and manage .levelpack archives
resave load and re-save a level or doodad file to migrate to newer file format versions
show show information about a level or doodad file
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--debug enable debug level logging (default: false)
--help, -h show help
--version, -v print the version
```
The `--help` or `-h` flag can be given to subcommands too, which often have their own commands and flags. For example, `doodad convert -h`
# Features in Depth
## $ `doodad convert`: to and from image files
This command can convert between image files (BMP or PNG) and Doodle drawings (levels and doodads) in both directions.
For custom doodad authors this means you can draw your sprites in a much more capable image editing tool, and create a doodad from PNG images. The game's built-in doodads are all created in this way.
This command can also be used to "export" a drawing as a PNG image: when run against a Level file, it may export a massive PNG image containing the entire level! Basically the "Giant Screenshot" function from the level editor.
It can also create a Level from a screenshot, generating the Palette from the distinct colors found (for you to assign properties to in the editor, for e.g. solid geometry).
Examples:
Example:
```bash
# Export a full screenshot of your level
@ -119,149 +24,20 @@ $ doodad convert mymap.level screenshot.png
# Create a new level based from a PNG image.
$ doodad convert scanned-drawing.png new-level.level
# Create a doodad from a series of PNG images as its layers.
$ doodad convert -t "My Button" up.png down.png my-button.doodad
# Create a new doodad based from a BMP image, and in this image the chroma
# color (transparent) is #FF00FF instead of white as default.
$ doodad convert --key '#FF00FF' button.png button.doodad
```
Supported image types to convert to or from:
Supported image types:
* PNG (8-bit or 24-bit, with transparent pixels or chroma)
* PNG (8-bit or 24-bit, with transparent pixels or chroma key)
* BMP (bitmap image with chroma key)
The chrome key defaults to white (`#FFFFFF`), so pixels of that color are treated as transparent and ignored. For PNG images, if a pixel is fully transparent (alpha channel 0%) it will also be skipped. So the easiest format to use are PNG images with transparent pixels.
The chrome key defaults to white (`#FFFFFF`), so pixels of that color are
treated as transparent and ignored. For PNG images, if a pixel is fully
transparent (alpha channel 0%) it will also be skipped.
When converting an image into a Level or Doodad, the unique colors identified in the drawing are extracted to create the **Palette**. You will need to later edit the palette to assign meaning to the colors (giving them names or level properties). In particular, when importing a Level you'd want to mark which colors are solid ground.
## $ `doodad show`: Get information about a level or doodad
The `doodad show` command can return metadata and debug the contents of a Level or Doodad file.
```
% doodad show Example.level
===== Level: Example.level =====
Headers:
File format: zipfile
File version: 1
Game version: 0.14.0
Level UUID: d136fef3-1a3a-453c-b616-05aca8dd6840
Level title: Lesson 1: Controls
Author: Noah P
Password:
Locked: false
Game Rules:
Difficulty: Normal (0)
Survival: false
Palette:
- Swatch name: solid
Attributes: solid
Color: #777777
- Swatch name: decoration
Attributes: none
Color: #ff66ff
- Swatch name: fire
Attributes: fire,water
Color: #ff0000
- Swatch name: semisolid
Attributes: semi-solid
Color: #cccccc
Level Settings:
Page type: Bounded
Max size: 2550x3300
Wallpaper: notebook.png
Attached Files:
assets/screenshots/large.png: 63377 bytes
assets/screenshots/medium.png: 25807 bytes
assets/screenshots/small.png: 6837 bytes
assets/screenshots/tiny.png: 7414 bytes
Actors:
Level contains 16 actors
Use -actors or -verbose to serialize Actors
Chunks:
Pixels Per Chunk: 128^2
Number Generated: 102
Coordinate Range: (0,0) ... (1919,2047)
World Dimensions: 1919x2047
Use -chunks or -verbose to serialize Chunks
```
Or for a doodad:
```
% doodad show example.doodad
===== Doodad: example.doodad =====
Headers:
File format: zipfile
File version: 1
Game version: 0.14.1
Doodad title: Fire Region
Author: Noah
Dimensions: Rect<0,0,128,128>
Hitbox: Rect<0,0,128,128>
Locked: true
Hidden: false
Script size: 378 bytes
Tags:
category: technical
Options:
str name = fire
Palette:
- Swatch name: Color<#8a2b2b+ff>
Attributes: solid
Color: #8a2b2b
Layer 0: fire-128
Chunks:
Pixels Per Chunk: 128^2
Number Generated: 1
Coordinate Range: (0,0) ... (127,127)
World Dimensions: 127x127
Use -chunks or -verbose to serialize Chunks
```
## Editing Level or Doodad Properties
The `edit-doodad` and `edit-level` subcommands allow setting properties on your custom files programmatically.
Properties you can set for both file types include:
* Metadata like the Title and Author name
* Lock your drawing from being edited in-game
For Doodads, you can set their Tags and Options, Hitbox, etc. - most of the useful settings are supported, as the game's built-in doodads use this program!
# Where to Find It
The `doodad` tool ships with the official releases of the game, and may be found in one of the following places:
* **Windows:** doodad.exe should be in the same place as sketchymaze.exe (e.g. in the ZIP file)
* **Mac OS:** it is available inside the "Sketchy Maze.app" bundle, in the "Contents/MacOS" folder next to the `sketchymaze` binary.
Invoke it from a terminal like:
```bash
alias doodad="/Applications/Sketchy Maze.app/Contents/MacOS/doodad"
doodad -h
```
* **Linux:** the doodad binary should be in the same place as the sketchymaze program itself:
* In the .tar.gz file
* In /opt/sketchymaze if installed by an .rpm or .deb package
* AppImage: `./SketchyMaze.AppImage doodad` will invoke the doodad command.
* Flatpak: `flatpak run com.sketchymaze.Doodle doodad` will invoke the doodad command. Invoke it from a terminal like:
```bash
alias doodad="flatpak run com.sketchymaze.Doodle doodad"
doodad -h
```
When converting an image into a drawing, the unique colors identified in the
drawing are extracted into the palette. You will need to later edit the palette
to assign meaning to the colors.

View File

@ -11,45 +11,42 @@ import (
"image/png"
"git.kirsle.net/SketchyMaze/doodle/pkg/branding"
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/native"
"git.kirsle.net/go/render"
"github.com/urfave/cli/v2"
"git.kirsle.net/apps/doodle/lib/render"
doodle "git.kirsle.net/apps/doodle/pkg"
"git.kirsle.net/apps/doodle/pkg/doodads"
"git.kirsle.net/apps/doodle/pkg/level"
"git.kirsle.net/apps/doodle/pkg/log"
"github.com/urfave/cli"
"golang.org/x/image/bmp"
)
// Convert between image files (png or bitmap) and Doodle drawing files (levels
// and doodads)
var Convert *cli.Command
var Convert cli.Command
func init() {
Convert = &cli.Command{
Convert = cli.Command{
Name: "convert",
Usage: "convert between images and Doodle drawing files",
ArgsUsage: "<input> <output>",
Flags: []cli.Flag{
&cli.StringFlag{
cli.StringFlag{
Name: "key",
Usage: "chroma key color for transparency on input image files, e.g. #ffffff",
Value: "",
Usage: "chroma key color for transparency on input image files",
Value: "#ffffff",
},
&cli.StringFlag{
Name: "title",
Aliases: []string{"t"},
Usage: "set the title of the level or doodad being created",
cli.StringFlag{
Name: "title, t",
Usage: "set the title of the level or doodad being created",
},
&cli.StringFlag{
Name: "palette",
Aliases: []string{"p"},
Usage: "use a palette JSON to define color swatch properties",
cli.StringFlag{
Name: "palette, p",
Usage: "use a palette JSON to define color swatch properties",
},
},
Action: func(c *cli.Context) error {
if c.NArg() < 2 {
return cli.Exit(
return cli.NewExitError(
"Usage: doodad convert <input.png...> <output.doodad>\n"+
" Image file types: png, bmp\n"+
" Drawing file types: level, doodad",
@ -58,19 +55,15 @@ func init() {
}
// Parse the chroma key.
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
chroma, err := render.HexColor(c.String("key"))
if err != nil {
return cli.NewExitError(
"Chrome key not a valid color: "+err.Error(),
1,
)
}
args := c.Args().Slice()
args := c.Args()
var (
inputFiles = args[:len(args)-1]
inputType = strings.ToLower(filepath.Ext(inputFiles[0]))
@ -81,22 +74,22 @@ func init() {
if inputType == extPNG || inputType == extBMP {
if outputType == extLevel || outputType == extDoodad {
if err := imageToDrawing(c, chroma, inputFiles, outputFile); err != nil {
return cli.Exit(err.Error(), 1)
return cli.NewExitError(err.Error(), 1)
}
return nil
}
return cli.Exit("Image inputs can only output to Doodle drawings", 1)
return cli.NewExitError("Image inputs can only output to Doodle drawings", 1)
} else if inputType == extLevel || inputType == extDoodad {
if outputType == extPNG || outputType == extBMP {
if err := drawingToImage(c, chroma, inputFiles, outputFile); err != nil {
return cli.Exit(err.Error(), 1)
return cli.NewExitError(err.Error(), 1)
}
return nil
}
return cli.Exit("Doodle drawing inputs can only output to image files", 1)
return cli.NewExitError("Doodle drawing inputs can only output to image files", 1)
}
return cli.Exit("File types must be: png, bmp, level, doodad", 1)
return cli.NewExitError("File types must be: png, bmp, level, doodad", 1)
},
}
}
@ -105,21 +98,20 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
// Read the source images. Ensure they all have the same boundaries.
var (
imageBounds image.Point
width int // dimensions of the incoming image
height int
chunkSize int // the square shape for the Doodad chunk size
images []image.Image
)
for i, filename := range inputFiles {
reader, err := os.Open(filename)
if err != nil {
return cli.Exit(err.Error(), 1)
return cli.NewExitError(err.Error(), 1)
}
img, format, err := image.Decode(reader)
log.Info("Parsed image %d of %d. Format: %s", i+1, len(inputFiles), format)
if err != nil {
return cli.Exit(err.Error(), 1)
return cli.NewExitError(err.Error(), 1)
}
// Get the bounding box information of the source image.
@ -131,65 +123,51 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
// Validate all images are the same size.
if i == 0 {
imageBounds = imageSize
width = imageSize.X
height = imageSize.Y
if imageSize.X > imageSize.Y {
chunkSize = imageSize.X
} else {
chunkSize = imageSize.Y
}
} else if imageSize != imageBounds {
return cli.Exit("your source images are not all the same dimensions", 1)
return cli.NewExitError("your source images are not all the same dimensions", 1)
}
images = append(images, img)
}
// Initialize the palette from a JSON file?
var palette *level.Palette
if paletteFile := c.String("palette"); paletteFile != "" {
log.Info("Loading initial palette from file: %s", paletteFile)
if p, err := level.LoadPaletteFromFile(paletteFile); err != nil {
return err
} else {
palette = p
}
}
// Helper function to translate image filenames into layer names.
toLayerName := func(filename string) string {
ext := filepath.Ext(filename)
return strings.TrimSuffix(filepath.Base(filename), ext)
}
// Generate the output drawing file.
switch strings.ToLower(filepath.Ext(outputFile)) {
case extDoodad:
doodad := doodads.New(width, height)
doodad.GameVersion = branding.Version
log.Info("Output is a Doodad file (chunk size %d): %s", chunkSize, outputFile)
doodad := doodads.New(chunkSize)
doodad.GameVersion = doodle.Version
doodad.Title = c.String("title")
if doodad.Title == "" {
doodad.Title = "Converted Doodad"
}
doodad.Author = native.DefaultAuthor
doodad.Author = os.Getenv("USER")
// Write the first layer and gather its 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, palette, chunkSize)
palette, layer0 := imageToChunker(images[0], chroma, nil, chunkSize)
doodad.Palette = palette
doodad.Layers[0].Chunker = layer0
doodad.Layers[0].Name = toLayerName(inputFiles[0])
// Write any additional layers.
if len(images) > 1 {
for i := 1; i < len(images); i++ {
img := images[i]
log.Info("Converting extra layer %d", i)
for i, img := range images[1:] {
log.Info("Converting extra layer %d", i+1)
_, chunker := imageToChunker(img, chroma, palette, chunkSize)
doodad.AddLayer(toLayerName(inputFiles[i]), chunker)
doodad.Layers = append(doodad.Layers, doodads.Layer{
Name: fmt.Sprintf("layer-%d", i+1),
Chunker: chunker,
})
}
}
err := doodad.WriteJSON(outputFile)
if err != nil {
return cli.Exit(err.Error(), 1)
return cli.NewExitError(err.Error(), 1)
}
case extLevel:
log.Info("Output is a Level file: %s", outputFile)
@ -198,25 +176,22 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
}
lvl := level.New()
lvl.GameVersion = branding.Version
lvl.MaxWidth = int64(width)
lvl.MaxHeight = int64(height)
lvl.PageType = level.Bounded
lvl.GameVersion = doodle.Version
lvl.Title = c.String("title")
if lvl.Title == "" {
lvl.Title = "Converted Level"
}
lvl.Author = native.DefaultAuthor
palette, chunker := imageToChunker(images[0], chroma, palette, lvl.Chunker.Size)
lvl.Author = os.Getenv("USER")
palette, chunker := imageToChunker(images[0], chroma, nil, lvl.Chunker.Size)
lvl.Palette = palette
lvl.Chunker = chunker
err := lvl.WriteJSON(outputFile)
if err != nil {
return cli.Exit(err.Error(), 1)
return cli.NewExitError(err.Error(), 1)
}
default:
return cli.Exit("invalid output file: not a Doodle drawing", 1)
return cli.NewExitError("invalid output file: not a Doodle drawing", 1)
}
return nil
@ -302,8 +277,7 @@ func drawingToImage(c *cli.Context, chroma render.Color, inputFiles []string, ou
//
// img: input image like a PNG
// chroma: transparent color
// palette: your palette so far (new distinct colors are added)
func imageToChunker(img image.Image, chroma render.Color, palette *level.Palette, chunkSize uint8) (*level.Palette, *level.Chunker) {
func imageToChunker(img image.Image, chroma render.Color, palette *level.Palette, chunkSize int) (*level.Palette, *level.Chunker) {
var (
chunker = level.NewChunker(chunkSize)
bounds = img.Bounds()
@ -315,7 +289,6 @@ func imageToChunker(img image.Image, chroma render.Color, palette *level.Palette
// Cache a palette of unique colors as we go.
var uniqueColor = map[string]*level.Swatch{}
var newColors = map[string]*level.Swatch{} // new ones discovered this time
for _, swatch := range palette.Swatches {
uniqueColor[swatch.Color.String()] = swatch
}
@ -337,10 +310,9 @@ func imageToChunker(img image.Image, chroma render.Color, palette *level.Palette
Color: color,
}
uniqueColor[color.String()] = swatch
newColors[color.String()] = swatch
}
chunker.Set(render.NewPoint(x, y), swatch)
chunker.Set(render.NewPoint(int32(x), int32(y)), swatch)
}
}
@ -351,12 +323,7 @@ func imageToChunker(img image.Image, chroma render.Color, palette *level.Palette
}
sort.Strings(sortedColors)
for _, hex := range sortedColors {
if _, ok := newColors[hex]; ok {
if err := palette.AddSwatch(uniqueColor[hex]); err != nil {
log.Error("Could not add more colors to the palette: %s", err)
panic(err.Error())
}
}
palette.Swatches = append(palette.Swatches, uniqueColor[hex])
}
palette.Inflate()

View File

@ -1,242 +0,0 @@
package commands
import (
"fmt"
"os"
"strconv"
"strings"
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/go/render"
"github.com/urfave/cli/v2"
)
// EditDoodad allows writing doodad metadata.
var EditDoodad *cli.Command
func init() {
EditDoodad = &cli.Command{
Name: "edit-doodad",
Usage: "update metadata for a Doodad file",
ArgsUsage: "<filename.doodad>",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "quiet",
Aliases: []string{"q"},
Usage: "limit output (don't show doodad data at the end)",
},
&cli.StringFlag{
Name: "title",
Usage: "set the doodad title",
},
&cli.StringFlag{
Name: "author",
Usage: "set the doodad author",
},
&cli.StringFlag{
Name: "hitbox",
Usage: "set the doodad hitbox (X,Y,W,H or W,H format)",
},
&cli.StringFlag{
Name: "tag",
Aliases: []string{"t"},
Usage: "set a key/value tag on the doodad, in key=value format. Empty value deletes the tag.",
},
&cli.StringFlag{
Name: "option",
Aliases: []string{"o"},
Usage: "set an option on the doodad, in key=type=default format, e.g. active=bool=true, speed=int=10, name=str. Value types are bool, str, int.",
},
&cli.BoolFlag{
Name: "hide",
Usage: "Hide the doodad from the palette",
},
&cli.BoolFlag{
Name: "unhide",
Usage: "Unhide the doodad from the palette",
},
&cli.BoolFlag{
Name: "lock",
Usage: "write-lock the level file",
},
&cli.BoolFlag{
Name: "unlock",
Usage: "remove the write-lock on the level file",
},
&cli.BoolFlag{
Name: "touch",
Usage: "simply load and re-save the doodad, to migrate it to a zipfile",
},
},
Action: func(c *cli.Context) error {
if c.NArg() < 1 {
return cli.Exit(
"Usage: doodad edit-doodad <filename.doodad>",
1,
)
}
var filenames = c.Args().Slice()
for _, filename := range filenames {
if err := editDoodad(c, filename); err != nil {
log.Error("%s: %s", filename, err)
}
}
return nil
},
}
}
func editDoodad(c *cli.Context, filename string) error {
var modified bool
dd, err := doodads.LoadJSON(filename)
if err != nil {
return fmt.Errorf("Failed to load %s: %s", filename, err)
}
log.Info("Edit Doodad: %s", filename)
/***************************
* Update level properties *
***************************/
if c.Bool("touch") {
log.Info("Just touching and resaving the file")
modified = true
}
if c.String("title") != "" {
dd.Title = c.String("title")
log.Info("Set title: %s", dd.Title)
modified = true
}
if c.String("author") != "" {
dd.Author = c.String("author")
log.Info("Set author: %s", dd.Author)
modified = true
}
if c.String("hitbox") != "" {
// Setting a hitbox, parse it out.
parts := strings.Split(c.String("hitbox"), ",")
var ints []int
for _, part := range parts {
a, err := strconv.Atoi(strings.TrimSpace(part))
if err != nil {
return err
}
ints = append(ints, a)
}
if len(ints) == 2 {
dd.Hitbox = render.NewRect(ints[0], ints[1])
modified = true
} else if len(ints) == 4 {
dd.Hitbox = render.Rect{
X: ints[0],
Y: ints[1],
W: ints[2],
H: ints[3],
}
modified = true
} else {
return cli.Exit("Hitbox should be in X,Y,W,H or just W,H format, 2 or 4 numbers.", 1)
}
}
// Tags.
tag := c.String("tag")
if len(tag) > 0 {
parts := strings.SplitN(tag, "=", 3)
if len(parts) != 2 {
log.Error("--tag: must be in format `key=value`. Value may be blank to delete a tag. len=%d tag=%s got=%+v", len(parts), tag, parts)
os.Exit(1)
}
var (
key = parts[0]
value = parts[1]
)
if value == "" {
log.Debug("Delete tag '%s'", key)
delete(dd.Tags, key)
} else {
log.Debug("Set tag '%s' to '%s'", key, value)
dd.Tags[key] = value
}
modified = true
}
// Options.
opt := c.String("option")
if len(opt) > 0 {
parts := strings.SplitN(opt, "=", 3)
if len(parts) < 2 {
log.Error("--option: must be in format `name=type` or `name=type=value`")
os.Exit(1)
}
var (
name = parts[0]
dataType = parts[1]
value string
)
if len(parts) == 3 {
value = parts[2]
}
// Validate the data types.
if dataType != "bool" && dataType != "str" && dataType != "int" {
log.Error("--option: invalid type, should be a bool, str or int")
os.Exit(1)
}
value = dd.SetOption(name, dataType, value)
log.Info("Set option %s (%s) = %s", name, dataType, value)
modified = true
}
if c.Bool("hide") {
dd.Hidden = true
log.Info("Marked doodad Hidden")
modified = true
} else if c.Bool("unhide") {
dd.Hidden = false
log.Info("Doodad is no longer Hidden")
modified = true
}
if c.Bool("lock") {
dd.Locked = true
log.Info("Write lock enabled.")
modified = true
} else if c.Bool("unlock") {
dd.Locked = false
log.Info("Write lock disabled.")
modified = true
}
/******************************
* Save level changes to disk *
******************************/
if modified {
if err := dd.WriteJSON(filename); err != nil {
return cli.Exit(fmt.Sprintf("Write error: %s", err), 1)
}
} else {
log.Warn("Note: No changes made to level")
}
if c.Bool("quiet") {
return nil
}
return showDoodad(c, filename)
}

View File

@ -1,265 +0,0 @@
package commands
import (
"errors"
"fmt"
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/go/render"
"github.com/urfave/cli/v2"
)
// EditLevel allows writing level metadata.
var EditLevel *cli.Command
func init() {
EditLevel = &cli.Command{
Name: "edit-level",
Usage: "update metadata for a Level file",
ArgsUsage: "<filename.level>",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "quiet",
Aliases: []string{"q"},
Usage: "limit output (don't show doodad data at the end)",
},
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "write to a different output file than the input (especially for --resize)",
},
&cli.StringFlag{
Name: "title",
Usage: "set the level title",
},
&cli.StringFlag{
Name: "author",
Usage: "set the level author",
},
&cli.StringFlag{
Name: "password",
Usage: "set the level password",
},
&cli.StringFlag{
Name: "type",
Usage: "set the page type. One of: Unbounded, Bounded, NoNegativeSpace, Bordered",
},
&cli.StringFlag{
Name: "max-size",
Usage: "set the bounded level page max size (WxH format, like 2550x3300)",
},
&cli.IntFlag{
Name: "resize",
Usage: "change the chunk size, and re-encode the whole level into chunks of the new size",
},
&cli.StringFlag{
Name: "wallpaper",
Usage: "set the wallpaper filename",
},
&cli.BoolFlag{
Name: "lock",
Usage: "write-lock the level file",
},
&cli.BoolFlag{
Name: "unlock",
Usage: "remove the write-lock on the level file",
},
&cli.StringFlag{
Name: "remove-actor",
Usage: "Remove all instances of the actor from the level. Value is their filename or UUID.",
},
&cli.BoolFlag{
Name: "touch",
Usage: "simply load and re-save the level, to migrate it to a zipfile",
},
},
Action: func(c *cli.Context) error {
if c.NArg() < 1 {
return cli.Exit(
"Usage: doodad edit-level <filename.level>",
1,
)
}
var filenames = c.Args().Slice()
for _, filename := range filenames {
if err := editLevel(c, filename); err != nil {
log.Error("%s: %s", filename, err)
}
}
return nil
},
}
}
func editLevel(c *cli.Context, filename string) error {
var modified bool
lvl, err := level.LoadJSON(filename)
if err != nil {
return fmt.Errorf("Failed to load %s: %s", filename, err)
}
log.Info("File: %s", filename)
// Migrating it to a different chunk size?
if c.Int("resize") > 0 {
return rechunkLevel(c, filename, lvl)
}
/***************************
* Update level properties *
***************************/
if c.Bool("touch") {
log.Info("Just touching and resaving the file")
modified = true
}
if c.String("title") != "" {
lvl.Title = c.String("title")
log.Info("Set title: %s", lvl.Title)
modified = true
}
if c.String("author") != "" {
lvl.Author = c.String("author")
log.Info("Set author: %s", lvl.Author)
modified = true
}
if c.String("password") != "" {
lvl.Password = c.String("password")
log.Info("Updated level password")
modified = true
}
if c.String("max-size") != "" {
w, h, err := render.ParseResolution(c.String("max-size"))
if err != nil {
log.Error("-max-size: %s", err)
} else {
lvl.MaxWidth = int64(w)
lvl.MaxHeight = int64(h)
modified = true
}
}
if c.Bool("lock") {
lvl.Locked = true
log.Info("Write lock enabled.")
modified = true
} else if c.Bool("unlock") {
lvl.Locked = false
log.Info("Write lock disabled.")
modified = true
}
if c.String("type") != "" {
if pageType, ok := level.PageTypeFromString(c.String("type")); ok {
lvl.PageType = pageType
log.Info("Page Type set to %s", pageType)
modified = true
} else {
log.Error("Invalid -type value. Should be like Unbounded, Bounded, NoNegativeSpace, Bordered")
}
}
if c.String("wallpaper") != "" {
lvl.Wallpaper = c.String("wallpaper")
log.Info("Set wallpaper: %s", c.String("wallpaper"))
modified = true
}
if c.String("remove-actor") != "" {
var (
match = c.String("remove-actor")
removeIDs = []string{}
)
for id, actor := range lvl.Actors {
if id == match || actor.Filename == match {
removeIDs = append(removeIDs, id)
}
}
if len(removeIDs) > 0 {
for _, id := range removeIDs {
delete(lvl.Actors, id)
}
log.Info("Removed %d instances of actor %s from the level.", len(removeIDs), match)
modified = true
} else {
log.Error("Did not find any actors like %s in the level.", match)
}
}
/******************************
* Save level changes to disk *
******************************/
if modified {
if err := lvl.WriteFile(filename); err != nil {
return cli.Exit(fmt.Sprintf("Write error: %s", err), 1)
}
} else {
log.Warn("Note: No changes made to level")
}
if c.Bool("quiet") {
return nil
}
return showLevel(c, filename)
}
// doodad edit-level --resize CHUNK_SIZE
//
// Handles the deep operation of re-copying the old level into a new level
// at the new chunk size.
func rechunkLevel(c *cli.Context, filename string, lvl *level.Level) error {
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("Current chunk size: %d", lvl.Chunker.Size)
log.Info("Target chunk size: %d", chunkSize)
if output := c.String("output"); output != "" {
filename = output
log.Info("Output file will be: %s", filename)
}
if chunkSize == lvl.Chunker.Size {
return errors.New("the level already has the target chunk size")
}
// Keep the level's current Chunker, and set a new one.
var oldChunker = lvl.Chunker
lvl.Chunker = level.NewChunker(chunkSize)
// Iterate all the Pixels of the old chunker.
log.Info("Copying pixels from old chunker into new chunker (this may take a while)...")
for pixel := range oldChunker.IterPixels() {
lvl.Chunker.Set(
pixel.Point(),
pixel.Swatch,
)
}
log.Info("Writing new data to filename: %s", filename)
if err := lvl.WriteFile(filename); err != nil {
log.Error(err.Error())
}
return showLevel(c, filename)
return nil
}

View File

@ -4,21 +4,21 @@ import (
"fmt"
"io/ioutil"
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"github.com/urfave/cli/v2"
"git.kirsle.net/apps/doodle/pkg/doodads"
"git.kirsle.net/apps/doodle/pkg/log"
"github.com/urfave/cli"
)
// InstallScript to add the script to a doodad file.
var InstallScript *cli.Command
var InstallScript cli.Command
func init() {
InstallScript = &cli.Command{
InstallScript = cli.Command{
Name: "install-script",
Usage: "install the JavaScript source to a doodad",
ArgsUsage: "<index.js> <filename.doodad>",
Flags: []cli.Flag{
&cli.StringFlag{
cli.StringFlag{
Name: "key",
Usage: "chroma key color for transparency on input image files",
Value: "#ffffff",
@ -26,7 +26,7 @@ func init() {
},
Action: func(c *cli.Context) error {
if c.NArg() != 2 {
return cli.Exit(
return cli.NewExitError(
"Usage: doodad install-script <script.js> <filename.doodad>",
1,
)
@ -34,19 +34,19 @@ func init() {
var (
args = c.Args()
scriptFile = args.Get(0)
doodadFile = args.Get(1)
scriptFile = args[0]
doodadFile = args[1]
)
// Read the JavaScript source.
javascript, err := ioutil.ReadFile(scriptFile)
if err != nil {
return cli.Exit(err.Error(), 1)
return cli.NewExitError(err.Error(), 1)
}
doodad, err := doodads.LoadJSON(doodadFile)
if err != nil {
return cli.Exit(
return cli.NewExitError(
fmt.Sprintf("Failed to read doodad file: %s", err),
1,
)

View File

@ -1,343 +0,0 @@
package commands
import (
"archive/zip"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"time"
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/levelpack"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/userdir"
"github.com/urfave/cli/v2"
)
// LevelPack creation and management.
var LevelPack *cli.Command
func init() {
LevelPack = &cli.Command{
Name: "levelpack",
Usage: "create and manage .levelpack archives",
ArgsUsage: "-o output.levelpack <list of .level files>",
Subcommands: []*cli.Command{
{
Name: "create",
Usage: "create a new .levelpack file from source files",
ArgsUsage: "<output.levelpack> <input.level> [input.level...]",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "title",
Aliases: []string{"t"},
Usage: "set a title for your levelpack, default will use the first level's title",
},
&cli.StringFlag{
Name: "author",
Aliases: []string{"a"},
Usage: "set an author for your levelpack, default will use the first level's author",
},
&cli.StringFlag{
Name: "description",
Aliases: []string{"d"},
Usage: "set a description for your levelpack",
},
&cli.IntFlag{
Name: "free",
Aliases: []string{"f"},
Usage: "set number of free levels (levels unlocked by default), 0 means all unlocked",
},
&cli.StringFlag{
Name: "doodads",
Aliases: []string{"D"},
Usage: "which doodads to embed: none, custom, all",
Value: "custom",
},
},
Action: levelpackCreate,
},
{
Name: "show",
Usage: "print details about a levelpack file",
ArgsUsage: "<input.levelpack>",
Action: levelpackShow,
},
},
Flags: []cli.Flag{},
}
}
// Subcommand `levelpack show`
func levelpackShow(c *cli.Context) error {
if c.NArg() < 1 {
return cli.Exit(
"Usage: doodad levelpack show <file.levelpack>",
1,
)
}
var filename = c.Args().Slice()[0]
if !strings.HasSuffix(filename, ".levelpack") {
return cli.Exit("file must name a .levelpack", 1)
}
lp, err := levelpack.LoadFile(filename)
if err != nil {
return cli.Exit(err, 1)
}
fmt.Printf("===== Levelpack: %s =====\n", filename)
fmt.Println("Headers:")
fmt.Printf(" Title: %s\n", lp.Title)
fmt.Printf(" Author: %s\n", lp.Author)
fmt.Printf(" Description: %s\n", lp.Description)
fmt.Printf(" Free levels: %d\n", lp.FreeLevels)
// List the levels.
fmt.Println("\nLevels:")
for i, lvl := range lp.Levels {
fmt.Printf("%d. %s: %s\n", i+1, lvl.Filename, lvl.Title)
}
// List the doodads.
dl := lp.ListFiles("doodads/")
if len(dl) > 0 {
fmt.Println("\nDoodads:")
for i, doodad := range dl {
fmt.Printf("%d. %s\n", i, doodad)
}
}
return nil
}
// Subcommand `levelpack create`
func levelpackCreate(c *cli.Context) error {
if c.NArg() < 2 {
return cli.Exit(
"Usage: doodad levelpack create <out.levelpack> <in.level ...>",
1,
)
}
var (
args = c.Args().Slice()
outfile = args[0]
infiles = args[1:]
title = c.String("title")
author = c.String("author")
description = c.String("description")
free = c.Int("free")
embedDoodads = c.String("doodads")
)
// Validate params.
if !strings.HasSuffix(outfile, ".levelpack") {
return cli.Exit("Output file must have a .levelpack extension", 1)
}
if embedDoodads != "none" && embedDoodads != "custom" && embedDoodads != "all" {
return cli.Exit(
"--doodads: must be one of all, custom, none",
1,
)
}
var lp = levelpack.LevelPack{
Title: title,
Author: author,
Description: description,
FreeLevels: free,
Created: time.Now().UTC(),
}
// Create a temp directory to work with.
workdir, err := os.MkdirTemp(userdir.CacheDirectory, "levelpack-*")
if err != nil {
return cli.Exit(
fmt.Sprintf("Couldn't make temp folder: %s", err),
1,
)
}
log.Info("Working directory: %s", workdir)
defer os.RemoveAll(workdir)
// Useful folders inside the working directory.
var (
levelDir = filepath.Join(workdir, "levels")
doodadDir = filepath.Join(workdir, "doodads")
assets = []string{
"index.json",
}
)
os.MkdirAll(levelDir, 0755)
os.MkdirAll(doodadDir, 0755)
// Get the list of the game's builtin doodads.
builtins, err := doodads.ListBuiltin()
if err != nil {
return cli.Exit(err, 1)
}
// Read the input levels.
for i, filename := range infiles {
if !strings.HasSuffix(filename, ".level") {
return cli.Exit(
fmt.Sprintf("input file at position %d (%s) was not a .level file", i, filename),
1,
)
}
lvl, err := level.LoadJSON(filename)
if err != nil {
return cli.Exit(
fmt.Sprintf("%s: %s", filename, err),
1,
)
}
// Fill in defaults for --title, --author
if lp.Title == "" {
lp.Title = lvl.Title
}
if lp.Author == "" {
lp.Author = lvl.Author
}
// Log the level in the index.json list.
lp.Levels = append(lp.Levels, levelpack.Level{
UUID: lvl.UUID,
Title: lvl.Title,
Author: lvl.Author,
Filename: filepath.Base(filename),
})
// Grab all the level's doodads to embed in the zip folder.
for _, actor := range lvl.Actors {
// What was the user's embeds request? (--doodads)
if embedDoodads == "none" {
break
} else if embedDoodads == "custom" {
// Custom doodads only.
if isBuiltinDoodad(builtins, actor.Filename) {
log.Warn("Doodad %s is a built-in, skipping embed", actor.Filename)
continue
}
}
if _, err := os.Stat(filepath.Join(doodadDir, actor.Filename)); !os.IsNotExist(err) {
continue
}
log.Info("Adding doodad to zipfile: %s", actor.Filename)
// Get this doodad from the game's built-ins or the user's
// profile directory only. Pulling embedded doodads out of
// the level is NOT supported.
asset, err := doodads.LoadFile(actor.Filename)
if err != nil {
return cli.Exit(
fmt.Sprintf("%s: Doodad file '%s': %s", filename, asset.Filename, err),
1,
)
}
var targetFile = filepath.Join(doodadDir, actor.Filename)
assets = append(assets, targetFile)
log.Debug("Write doodad: %s", targetFile)
err = asset.WriteFile(filepath.Join(doodadDir, actor.Filename))
if err != nil {
return cli.Exit(
fmt.Sprintf("Writing doodad %s: %s", actor.Filename, err),
1,
)
}
}
// Copy the level in.
var targetFile = filepath.Join(levelDir, filepath.Base(filename))
assets = append(assets, targetFile)
log.Info("Write level: %s", filename)
err = copyFile(filename, filepath.Join(levelDir, filepath.Base(filename)))
if err != nil {
return cli.Exit(
fmt.Sprintf("couldn't copy %s to %s: %s", filename, targetFile, err),
1,
)
}
}
log.Info("Writing index.json")
if err := lp.WriteFile(filepath.Join(workdir, "index.json")); err != nil {
return cli.Exit(err, 1)
}
// Zip the levelpack directory.
log.Info("Creating levelpack file: %s", outfile)
zipf, err := os.Create(outfile)
if err != nil {
return cli.Exit(
fmt.Sprintf("failed to create %s: %s", outfile, err),
1,
)
}
zipper := zip.NewWriter(zipf)
defer zipper.Close()
// Embed all the assets.
sort.Strings(assets)
for _, asset := range assets {
asset = strings.TrimPrefix(asset, workdir+"/")
log.Info("Zip: %s", asset)
err := zipFile(zipper, asset, filepath.Join(workdir, asset))
if err != nil {
return cli.Exit(err, 1)
}
}
log.Info("Written: %s", outfile)
return cli.Exit("", 0)
}
// copyFile copies a file on disk to another location.
func copyFile(source, target string) error {
input, err := ioutil.ReadFile(source)
if err != nil {
return err
}
return ioutil.WriteFile(target, input, 0644)
}
// zipFile reads a file on disk to add to a zip file.
// The `key` is the filepath inside the ZIP file, filename is the actual source file on disk.
func zipFile(zf *zip.Writer, key, filename string) error {
input, err := ioutil.ReadFile(filename)
if err != nil {
return err
}
writer, err := zf.Create(key)
if err != nil {
return err
}
_, err = writer.Write(input)
return err
}
// Helper function to test whether a filename is part of the builtin doodads.
func isBuiltinDoodad(doodads []string, filename string) bool {
for _, cmp := range doodads {
if cmp == filename {
return true
}
}
return false
}

View File

@ -1,115 +0,0 @@
package commands
import (
"fmt"
"path/filepath"
"strings"
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
"git.kirsle.net/SketchyMaze/doodle/pkg/enum"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"github.com/urfave/cli/v2"
)
// Resave a Level or Doodad to adapt to file format upgrades.
var Resave *cli.Command
func init() {
Resave = &cli.Command{
Name: "resave",
Usage: "load and re-save a level or doodad file to migrate to newer file format versions",
ArgsUsage: "<.level or .doodad>",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "write to a different file than the input",
},
},
Action: func(c *cli.Context) error {
if c.NArg() < 1 {
return cli.Exit(
"Usage: doodad resave <.level .doodad ...>",
1,
)
}
filenames := c.Args().Slice()
for _, filename := range filenames {
switch strings.ToLower(filepath.Ext(filename)) {
case enum.LevelExt:
if err := resaveLevel(c, filename); err != nil {
log.Error(err.Error())
return cli.Exit("Error", 1)
}
case enum.DoodadExt:
if err := resaveDoodad(c, filename); err != nil {
log.Error(err.Error())
return cli.Exit("Error", 1)
}
default:
log.Error("File %s: not a level or doodad", filename)
}
}
return nil
},
}
}
// resaveLevel shows data about a level file.
func resaveLevel(c *cli.Context, filename string) error {
lvl, err := level.LoadJSON(filename)
if err != nil {
return err
}
log.Info("Loaded level from file: %s", filename)
log.Info("Last saved game version: %s", lvl.GameVersion)
// Different output filename?
if output := c.String("output"); output != "" {
log.Info("Output will be saved to: %s", output)
filename = output
}
if err := lvl.Vacuum(); err != nil {
log.Error("Vacuum error: %s", err)
} else {
log.Info("Run vacuum on level file.")
}
log.Info("Saving back to disk")
if err := lvl.WriteJSON(filename); err != nil {
return fmt.Errorf("couldn't write %s: %s", filename, err)
}
return showLevel(c, filename)
}
func resaveDoodad(c *cli.Context, filename string) error {
dd, err := doodads.LoadJSON(filename)
if err != nil {
return err
}
log.Info("Loaded doodad from file: %s", filename)
log.Info("Last saved game version: %s", dd.GameVersion)
// Different output filename?
if output := c.String("output"); output != "" {
log.Info("Output will be saved to: %s", output)
filename = output
}
if err := dd.Vacuum(); err != nil {
log.Error("Vacuum error: %s", err)
} else {
log.Info("Run vacuum on doodad file.")
}
log.Info("Saving back to disk")
if err := dd.WriteJSON(filename); err != nil {
return fmt.Errorf("couldn't write %s: %s", filename, err)
}
return showDoodad(c, filename)
}

View File

@ -1,366 +0,0 @@
package commands
import (
"bytes"
"encoding/binary"
"fmt"
"path/filepath"
"sort"
"strings"
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
"git.kirsle.net/SketchyMaze/doodle/pkg/enum"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/level/rle"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"github.com/urfave/cli/v2"
)
// Show information about a Level or Doodad file.
var Show *cli.Command
func init() {
Show = &cli.Command{
Name: "show",
Usage: "show information about a level or doodad file",
ArgsUsage: "<.level or .doodad>",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "actors",
Usage: "print verbose actor data in Level files",
},
&cli.BoolFlag{
Name: "chunks",
Usage: "print verbose data about all the pixel chunks in a file",
},
&cli.BoolFlag{
Name: "script",
Usage: "print the script from a doodad file and exit",
},
&cli.StringFlag{
Name: "attachment",
Aliases: []string{"a"},
Usage: "print the contents of the attached filename to terminal",
},
&cli.BoolFlag{
Name: "verbose",
Aliases: []string{"v"},
Usage: "print verbose output (all verbose flags enabled)",
},
&cli.BoolFlag{
Name: "visualize-rle",
Usage: "visually dump RLE encoded chunks to the terminal (VERY noisy for large drawings!)",
},
&cli.StringFlag{
Name: "chunk",
Usage: "specific chunk coordinate; when debugging chunks, only show this chunk (example: 2,-1)",
},
},
Action: func(c *cli.Context) error {
if c.NArg() < 1 {
return cli.Exit(
"Usage: doodad show <.level .doodad ...>",
1,
)
}
filenames := c.Args().Slice()
for _, filename := range filenames {
switch strings.ToLower(filepath.Ext(filename)) {
case enum.LevelExt:
if err := showLevel(c, filename); err != nil {
log.Error(err.Error())
return cli.Exit("Error", 1)
}
case enum.DoodadExt:
if err := showDoodad(c, filename); err != nil {
log.Error(err.Error())
return cli.Exit("Error", 1)
}
default:
log.Error("File %s: not a level or doodad", filename)
}
}
return nil
},
}
}
// showLevel shows data about a level file.
func showLevel(c *cli.Context, filename string) error {
lvl, err := level.LoadJSON(filename)
if err != nil {
return err
}
// Are we printing an attached file?
if filename := c.String("attachment"); filename != "" {
if data, err := lvl.GetFile(filename); err == nil {
fmt.Print(string(data))
return nil
} else {
fmt.Printf("Couldn't get attached file '%s': %s\n", filename, err)
return err
}
}
// Is it a new zipfile format?
var fileType = "json or gzip"
if lvl.Zipfile != nil {
fileType = "zipfile"
}
fmt.Printf("===== Level: %s =====\n", filename)
fmt.Println("Headers:")
fmt.Printf(" File format: %s\n", fileType)
fmt.Printf(" File version: %d\n", lvl.Version)
fmt.Printf(" Game version: %s\n", lvl.GameVersion)
fmt.Printf(" Level UUID: %s\n", lvl.UUID)
fmt.Printf(" Level title: %s\n", lvl.Title)
fmt.Printf(" Author: %s\n", lvl.Author)
fmt.Printf(" Password: %s\n", lvl.Password)
fmt.Printf(" Locked: %+v\n", lvl.Locked)
fmt.Println("")
fmt.Println("Game Rules:")
fmt.Printf(" Difficulty: %s (%d)\n", lvl.GameRule.Difficulty, lvl.GameRule.Difficulty)
fmt.Printf(" Survival: %+v\n", lvl.GameRule.Survival)
fmt.Println("")
showPalette(lvl.Palette)
fmt.Println("Level Settings:")
fmt.Printf(" Page type: %s\n", lvl.PageType.String())
fmt.Printf(" Max size: %dx%d\n", lvl.MaxWidth, lvl.MaxHeight)
fmt.Printf(" Wallpaper: %s\n", lvl.Wallpaper)
fmt.Println("")
fmt.Println("Attached Files:")
if files := lvl.ListFiles(); len(files) > 0 {
for _, v := range files {
data, _ := lvl.GetFile(v)
fmt.Printf(" %s: %d bytes\n", v, len(data))
}
fmt.Println("")
} else {
fmt.Printf(" None\n\n")
}
// Print the actor information.
fmt.Println("Actors:")
fmt.Printf(" Level contains %d actors\n", len(lvl.Actors))
if c.Bool("actors") || c.Bool("verbose") {
fmt.Println(" List of Actors:")
for id, actor := range lvl.Actors {
fmt.Printf(" - Name: %s\n", actor.Filename)
fmt.Printf(" UUID: %s\n", id)
fmt.Printf(" At: %s\n", actor.Point)
if len(actor.Options) > 0 {
var ordered = []string{}
for name := range actor.Options {
ordered = append(ordered, name)
}
sort.Strings(ordered)
fmt.Println(" Options:")
for _, name := range ordered {
val := actor.Options[name]
fmt.Printf(" %s %s = %v\n", val.Type, val.Name, val.Value)
}
}
if c.Bool("links") {
for _, link := range actor.Links {
if other, ok := lvl.Actors[link]; ok {
fmt.Printf(" Link: %s (%s)\n", link, other.Filename)
} else {
fmt.Printf(" Link: %s (**UNRESOLVED**)", link)
}
}
}
}
fmt.Println("")
} else {
fmt.Print(" Use -actors or -verbose to serialize Actors\n\n")
}
// Serialize chunk information.
showChunker(c, lvl.Chunker, 0)
fmt.Println("")
return nil
}
func showDoodad(c *cli.Context, filename string) error {
dd, err := doodads.LoadJSON(filename)
if err != nil {
return err
}
if c.Bool("script") {
fmt.Printf("// %s.js\n", filename)
fmt.Println(strings.TrimSpace(dd.Script))
return nil
}
// Is it a new zipfile format?
var fileType = "json or gzip"
if dd.Zipfile != nil {
fileType = "zipfile"
}
fmt.Printf("===== Doodad: %s =====\n", filename)
fmt.Println("Headers:")
fmt.Printf(" File format: %s\n", fileType)
fmt.Printf(" File version: %d\n", dd.Version)
fmt.Printf(" Game version: %s\n", dd.GameVersion)
fmt.Printf(" Doodad title: %s\n", dd.Title)
fmt.Printf(" Author: %s\n", dd.Author)
fmt.Printf(" Dimensions: %s\n", dd.Size)
fmt.Printf(" Hitbox: %s\n", dd.Hitbox)
fmt.Printf(" Locked: %+v\n", dd.Locked)
fmt.Printf(" Hidden: %+v\n", dd.Hidden)
fmt.Printf(" Script size: %d bytes\n", len(dd.Script))
fmt.Println("")
if len(dd.Tags) > 0 {
fmt.Println("Tags:")
for k, v := range dd.Tags {
fmt.Printf(" %s: %s\n", k, v)
}
fmt.Println("")
}
if len(dd.Options) > 0 {
var ordered = []string{}
for name := range dd.Options {
ordered = append(ordered, name)
}
sort.Strings(ordered)
fmt.Println("Options:")
for _, name := range ordered {
opt := dd.Options[name]
fmt.Printf(" %s %s = %v\n", opt.Type, opt.Name, opt.Default)
}
fmt.Println("")
}
showPalette(dd.Palette)
for i, layer := range dd.Layers {
fmt.Printf("Layer %d: %s\n", i, layer.Name)
showChunker(c, layer.Chunker, i)
}
fmt.Println("")
return nil
}
func showPalette(pal *level.Palette) {
fmt.Println("Palette:")
for _, sw := range pal.Swatches {
fmt.Printf(" - Swatch name: %s\n", sw.Name)
fmt.Printf(" Attributes: %s\n", sw.Attributes())
fmt.Printf(" Color: %s\n", sw.Color.ToHex())
}
fmt.Println("")
}
func showChunker(c *cli.Context, ch *level.Chunker, layer int) {
var (
worldSize = ch.WorldSize()
chunkSize = int(ch.Size)
width = worldSize.W - worldSize.X
height = worldSize.H - worldSize.Y
// Chunk debugging CLI options.
visualize = c.Bool("visualize-rle")
specificChunk = c.String("chunk")
)
// If it's a Zipfile, count its chunks.
var chunkCount = len(ch.Chunks)
if ch.Zipfile != nil {
chunkCount = len(level.ChunksInZipfile(ch.Zipfile, layer))
}
fmt.Println("Chunks:")
fmt.Printf(" Pixels Per Chunk: %d^2\n", ch.Size)
fmt.Printf(" Number Generated: %d\n", chunkCount)
fmt.Printf(" Coordinate Range: (%d,%d) ... (%d,%d)\n",
worldSize.X,
worldSize.Y,
worldSize.W,
worldSize.H,
)
fmt.Printf(" World Dimensions: %dx%d\n", width, height)
// Verbose chunk information.
if c.Bool("chunks") || c.Bool("verbose") {
fmt.Println(" Chunk Details:")
for point := range ch.IterChunks() {
// Debugging specific chunk coordinate?
if specificChunk != "" && point.String() != specificChunk {
log.Warn("Skip chunk %s: not the specific chunk you're looking for", point)
continue
}
chunk, ok := ch.GetChunk(point)
if !ok {
continue
}
fmt.Printf(" - Coord: %s\n", point)
fmt.Printf(" Type: %s\n", chunkTypeToName(chunk.Type))
fmt.Printf(" Range: (%d,%d) ... (%d,%d)\n",
int(point.X)*chunkSize,
int(point.Y)*chunkSize,
(int(point.X)*chunkSize)+chunkSize,
(int(point.Y)*chunkSize)+chunkSize,
)
fmt.Printf(" Usage: %f (%d len of %d)\n", chunk.Usage(), chunk.Len(), chunkSize*chunkSize)
// Visualize the RLE encoded chunks?
if visualize && chunk.Type == level.RLEType {
ext, bin, err := ch.RawChunkFromZipfile(point)
if err != nil {
log.Error(err.Error())
continue
} else if ext != ".bin" {
log.Error("Unexpected filetype for RLE compressed chunk (expected .bin, got %s)", ext)
continue
}
// Read off the first byte (chunk type)
var reader = bytes.NewBuffer(bin)
binary.ReadUvarint(reader)
bin = reader.Bytes()
grid, err := rle.NewGrid(chunkSize)
if err != nil {
log.Error(err.Error())
continue
}
grid.Decompress(bin)
fmt.Println(grid.Visualize())
}
}
} else {
fmt.Println(" Use -chunks or -verbose to serialize Chunks")
}
fmt.Println("")
}
func chunkTypeToName(v uint64) string {
switch v {
case level.MapType:
return "map"
case level.RLEType:
return "rle map"
default:
return fmt.Sprintf("type %d", v)
}
}

View File

@ -3,15 +3,14 @@ package main
import (
"fmt"
"log"
"os"
"sort"
"time"
"git.kirsle.net/SketchyMaze/doodle/cmd/doodad/commands"
"git.kirsle.net/SketchyMaze/doodle/pkg/branding/builds"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/plus/bootstrap"
"github.com/urfave/cli/v2"
"git.kirsle.net/apps/doodle/cmd/doodad/commands"
doodle "git.kirsle.net/apps/doodle/pkg"
"github.com/urfave/cli"
)
// Build variables.
@ -27,33 +26,25 @@ func init() {
}
func main() {
bootstrap.InitPlugins()
app := cli.NewApp()
app.Name = "doodad"
app.Usage = "command line interface for Doodle"
app.Version = fmt.Sprintf("%s build %s. Built on %s",
builds.Version,
doodle.Version,
Build,
BuildDate,
)
app.Flags = []cli.Flag{
&cli.BoolFlag{
cli.BoolFlag{
Name: "debug, d",
Usage: "enable debug level logging",
},
}
app.Commands = []*cli.Command{
app.Commands = []cli.Command{
commands.Convert,
commands.Show,
commands.Resave,
commands.EditLevel,
commands.EditDoodad,
commands.InstallScript,
commands.LevelPack,
}
sort.Sort(cli.FlagsByName(app.Flags))
@ -61,7 +52,6 @@ func main() {
err := app.Run(os.Args)
if err != nil {
log.Error("Fatal: %s", err)
os.Exit(1)
log.Fatal(err)
}
}

View File

@ -1,44 +0,0 @@
package command
import (
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/dpp/license"
"github.com/urfave/cli/v2"
)
// Key a license key for Sketchy Maze.
var Key *cli.Command
func init() {
Key = &cli.Command{
Name: "key",
Usage: "generate an admin ECDSA signing key",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "public",
Usage: "Filename to write the public key to (.pem)",
Required: true,
},
&cli.StringFlag{
Name: "private",
Usage: "Filename to write the private key to (.pem)",
Required: true,
},
},
Action: func(c *cli.Context) error {
key, err := license.AdminGenerateKeys()
if err != nil {
return cli.Exit(err.Error(), 1)
}
err = license.AdminWriteKeys(key, c.String("private"), c.String("public"))
if err != nil {
return cli.Exit(err.Error(), 1)
}
log.Info("Written private key: %s", c.String("private"))
log.Info("Written public key: %s", c.String("public"))
return nil
},
}
}

View File

@ -1,73 +0,0 @@
package command
import (
"fmt"
"io/ioutil"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/dpp/license"
"github.com/urfave/cli/v2"
)
// Sign a license key for Sketchy Maze.
var Sign *cli.Command
func init() {
Sign = &cli.Command{
Name: "sign",
Usage: "sign a license key for the paid version of Sketchy Maze.",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "key",
Aliases: []string{"k"},
Usage: "Private key .pem file for signing",
Required: true,
},
&cli.StringFlag{
Name: "name",
Aliases: []string{"n"},
Usage: "User name for certificate",
Required: true,
},
&cli.StringFlag{
Name: "email",
Aliases: []string{"e"},
Usage: "User email address",
Required: true,
},
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "Output file, default outputs to console",
},
},
Action: func(c *cli.Context) error {
key, err := license.AdminLoadPrivateKey(c.String("key"))
if err != nil {
return cli.Exit(err.Error(), 1)
}
reg := license.Registration{
Name: c.String("name"),
Email: c.String("email"),
}
result, err := license.AdminSignRegistration(key, reg)
if err != nil {
return cli.Exit(err.Error(), 1)
}
// Writing to an output file?
if output := c.String("output"); output != "" {
log.Info("Write to: %s", output)
if err := ioutil.WriteFile(output, []byte(result), 0644); err != nil {
return cli.Exit(err, 1)
}
} else {
fmt.Println(result)
}
return nil
},
}
}

View File

@ -1,92 +0,0 @@
package command
import (
"fmt"
"strings"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/levelpack"
"git.kirsle.net/SketchyMaze/dpp/license"
"git.kirsle.net/SketchyMaze/dpp/license/levelsigning"
"github.com/urfave/cli/v2"
)
// SignLevel a license key for Sketchy Maze.
var SignLevel *cli.Command
func init() {
SignLevel = &cli.Command{
Name: "sign-level",
Usage: "sign a level file so that it may use embedded assets in free versions of the game.",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "key",
Aliases: []string{"k"},
Usage: "Private key .pem file for signing",
Required: true,
},
&cli.StringFlag{
Name: "input",
Aliases: []string{"i"},
Usage: "Input file name (.level or .levelpack)",
Required: true,
},
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "Output file, default outputs to console",
},
},
Action: func(c *cli.Context) error {
key, err := license.AdminLoadPrivateKey(c.String("key"))
if err != nil {
return cli.Exit(err.Error(), 1)
}
var (
filename = c.String("input")
output = c.String("output")
)
if output == "" {
output = filename
}
// Sign a level?
if strings.HasSuffix(filename, ".level") {
lvl, err := level.LoadJSON(filename)
if err != nil {
return cli.Exit(err.Error(), 1)
}
// Sign it.
if sig, err := levelsigning.SignLevel(key, lvl); err != nil {
return cli.Exit(fmt.Errorf("couldn't sign level: %s", err), 1)
} else {
lvl.Signature = sig
err := lvl.WriteFile(output)
if err != nil {
return cli.Exit(err.Error(), 1)
}
}
} else if strings.HasSuffix(filename, ".levelpack") {
lp, err := levelpack.LoadFile(filename)
if err != nil {
return cli.Exit(err.Error(), 1)
}
// Sign it.
if sig, err := levelsigning.SignLevelPack(key, lp); err != nil {
return cli.Exit(fmt.Errorf("couldn't sign levelpack: %s", err), 1)
} else {
lp.Signature = sig
err := lp.WriteZipfile(output)
if err != nil {
return cli.Exit(err.Error(), 1)
}
}
}
return nil
},
}
}

View File

@ -1,59 +0,0 @@
package command
import (
"io/ioutil"
"time"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/dpp/license"
"github.com/urfave/cli/v2"
)
// Verify a license key for Sketchy Maze.
var Verify *cli.Command
func init() {
Verify = &cli.Command{
Name: "verify",
Usage: "check the signature on a license key",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "key",
Aliases: []string{"k"},
Usage: "Public key .pem file that signed the JWT",
Required: true,
},
&cli.StringFlag{
Name: "filename",
Aliases: []string{"f"},
Usage: "File name of the license file to validate",
Required: true,
},
},
Action: func(c *cli.Context) error {
key, err := license.AdminLoadPublicKey(c.String("key"))
if err != nil {
return cli.Exit(err.Error(), 1)
}
jwt, err := ioutil.ReadFile(c.String("filename"))
if err != nil {
return cli.Exit(err.Error(), 1)
}
reg, err := license.Validate(key, string(jwt))
if err != nil {
return cli.Exit(err.Error(), 1)
}
log.Info("Registration valid")
log.Info(" Name: %s", reg.Name)
log.Info(" Email: %s", reg.Email)
log.Info(" Issued: %s", time.Unix(reg.IssuedAt, 0))
log.Info(" NBF: %s", time.Unix(reg.NotBefore, 0))
log.Info("Raw:\n%+v", reg)
return nil
},
}
}

View File

@ -1,73 +0,0 @@
package command
import (
"strings"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/levelpack"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/dpp/license"
"git.kirsle.net/SketchyMaze/dpp/license/levelsigning"
"github.com/urfave/cli/v2"
)
// VerifyLevel a license key for Sketchy Maze.
var VerifyLevel *cli.Command
func init() {
VerifyLevel = &cli.Command{
Name: "verify-level",
Usage: "check the signature on a level or levelpack file.",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "key",
Aliases: []string{"k"},
Usage: "Public key .pem file that signed the level",
Required: true,
},
&cli.StringFlag{
Name: "filename",
Aliases: []string{"f"},
Usage: "File name of the .level or .levelpack",
Required: true,
},
},
Action: func(c *cli.Context) error {
key, err := license.AdminLoadPublicKey(c.String("key"))
if err != nil {
return cli.Exit(err.Error(), 1)
}
filename := c.String("filename")
if strings.HasSuffix(filename, ".level") {
lvl, err := level.LoadJSON(filename)
if err != nil {
return cli.Exit(err.Error(), 1)
}
// Verify it.
if ok := levelsigning.VerifyLevel(key, lvl); !ok {
log.Error("Signature is not valid!")
return cli.Exit("", 1)
} else {
log.Info("Level signature is OK!")
}
} else if strings.HasSuffix(filename, ".levelpack") {
lp, err := levelpack.LoadFile(filename)
if err != nil {
return cli.Exit(err.Error(), 1)
}
// Verify it.
if ok := levelsigning.VerifyLevelPack(key, lp); !ok {
log.Error("Signature is not valid!")
return cli.Exit("", 1)
} else {
log.Info("Levelpack signature is OK!")
}
}
return nil
},
}
}

View File

@ -1,61 +0,0 @@
// doodle-admin performs secret admin tasks like generating license keys.
package main
import (
"fmt"
"log"
"os"
"sort"
"time"
"git.kirsle.net/SketchyMaze/doodle/cmd/doodle-admin/command"
"git.kirsle.net/SketchyMaze/doodle/pkg/branding"
"github.com/urfave/cli/v2"
)
// Build variables.
var (
Build = "N/A"
BuildDate string
)
func init() {
if BuildDate == "" {
BuildDate = time.Now().Format(time.RFC3339)
}
}
func main() {
app := cli.NewApp()
app.Name = "doodle-admin"
app.Usage = "Admin tasks for Sketchy Maze."
app.Version = fmt.Sprintf("%s build %s. Built on %s",
branding.Version,
Build,
BuildDate,
)
app.Flags = []cli.Flag{
&cli.BoolFlag{
Name: "debug, d",
Usage: "enable debug level logging",
},
}
app.Commands = []*cli.Command{
command.Key,
command.Sign,
command.Verify,
command.SignLevel,
command.VerifyLevel,
}
sort.Sort(cli.FlagsByName(app.Flags))
sort.Sort(cli.CommandsByName(app.Commands))
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}

View File

@ -1,38 +1,17 @@
package main
import (
"errors"
"fmt"
"log"
"os"
"path/filepath"
"regexp"
"runtime"
"runtime/pprof"
"sort"
"strconv"
"time"
"git.kirsle.net/SketchyMaze/doodle/assets"
doodle "git.kirsle.net/SketchyMaze/doodle/pkg"
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
"git.kirsle.net/SketchyMaze/doodle/pkg/branding"
"git.kirsle.net/SketchyMaze/doodle/pkg/branding/builds"
"git.kirsle.net/SketchyMaze/doodle/pkg/chatbot"
"git.kirsle.net/SketchyMaze/doodle/pkg/gamepad"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/native"
"git.kirsle.net/SketchyMaze/doodle/pkg/plus/bootstrap"
"git.kirsle.net/SketchyMaze/doodle/pkg/plus/dpp"
"git.kirsle.net/SketchyMaze/doodle/pkg/shmem"
"git.kirsle.net/SketchyMaze/doodle/pkg/sound"
"git.kirsle.net/SketchyMaze/doodle/pkg/sprites"
"git.kirsle.net/SketchyMaze/doodle/pkg/usercfg"
"git.kirsle.net/SketchyMaze/doodle/pkg/userdir"
golog "git.kirsle.net/go/log"
"git.kirsle.net/go/render"
"git.kirsle.net/go/render/sdl"
"github.com/urfave/cli/v2"
sdl2 "github.com/veandco/go-sdl2/sdl"
"git.kirsle.net/apps/doodle/lib/render/sdl"
doodle "git.kirsle.net/apps/doodle/pkg"
"git.kirsle.net/apps/doodle/pkg/balance"
"github.com/urfave/cli"
_ "image/png"
)
@ -47,214 +26,52 @@ func init() {
if BuildDate == "" {
BuildDate = time.Now().Format(time.RFC3339)
}
// Use all the CPU cores for collision detection and other load balanced
// goroutine work in the app.
runtime.GOMAXPROCS(runtime.NumCPU())
}
func main() {
runtime.LockOSThread()
bootstrap.InitPlugins()
app := cli.NewApp()
app.Name = "doodle"
app.Usage = fmt.Sprintf("%s - %s", branding.AppName, branding.Summary)
// Load user settings from disk ASAP.
if err := usercfg.Load(); err != nil {
log.Error("Error loading user settings (defaults will be used): %s", err)
}
// Set default user settings.
if usercfg.Current.CrosshairColor == render.Invisible {
usercfg.Current.CrosshairColor = balance.DefaultCrosshairColor
usercfg.Save()
}
// Set GameController style.
gamepad.SetStyle(gamepad.Style(usercfg.Current.ControllerStyle))
app.Usage = "command line interface for Doodle"
app.Version = fmt.Sprintf("%s build %s. Built on %s",
builds.Version,
doodle.Version,
Build,
BuildDate,
)
app.Flags = []cli.Flag{
&cli.BoolFlag{
Name: "debug",
Aliases: []string{"d"},
Usage: "enable debug level logging",
cli.BoolFlag{
Name: "debug, d",
Usage: "enable debug level logging",
},
&cli.StringFlag{
Name: "log",
Aliases: []string{"o"},
Usage: "path on disk to copy the game's standard output logs (default goes to your game profile directory)",
cli.BoolFlag{
Name: "edit, e",
Usage: "edit the map given on the command line (instead of play it)",
},
&cli.StringFlag{
Name: "pprof",
Usage: "record pprof metrics to a filename",
},
&cli.StringFlag{
Name: "chdir",
Usage: "working directory for the game's runtime package",
},
&cli.BoolFlag{
Name: "new",
Aliases: []string{"n"},
Usage: "open immediately to the level editor",
},
&cli.BoolFlag{
Name: "edit",
Aliases: []string{"e"},
Usage: "edit the map given on the command line (instead of play it)",
},
&cli.StringFlag{
Name: "window",
Aliases: []string{"w"},
Usage: "set the window size (e.g. -w 1024x768) or special value: desktop, mobile, landscape, maximized",
},
&cli.BoolFlag{
Name: "touch",
Aliases: []string{"t"},
Usage: "force TouchScreenMode to be on at all times, which hides the mouse cursor",
},
&cli.BoolFlag{
cli.BoolFlag{
Name: "guitest",
Usage: "enter the GUI Test scene on startup",
},
&cli.BoolFlag{
Name: "experimental",
Usage: "enable experimental Feature Flags",
},
&cli.BoolFlag{
Name: "offline",
Usage: "offline mode, disables check for new updates",
},
}
app.Action = func(c *cli.Context) error {
// Set the log level now if debugging is enabled.
if c.Bool("debug") {
log.Logger.Config.Level = golog.DebugLevel
}
// Write the game's log to disk.
if err := initLogFile(c.String("log")); err != nil {
log.Error("Couldn't write logs to disk: %s", err)
}
log.Info("Starting %s %s", app.Name, app.Version)
// Print registration information, + also this sets the DefaultAuthor field.
if reg, err := dpp.Driver.GetRegistration(); err == nil {
log.Info("Registered to %s", reg.Name)
}
// --chdir into a different working directory? e.g. for Flatpak especially.
if err := setWorkingDirectory(c); err != nil {
log.Error("Couldn't set working directory: %s", err)
}
// Recording pprof stats?
if cpufile := c.String("pprof"); cpufile != "" {
log.Info("Saving CPU profiling data to %s", cpufile)
fh, err := os.Create(cpufile)
if err != nil {
log.Error("--pprof: can't create file: %s", err)
return err
}
defer fh.Close()
if err := pprof.StartCPUProfile(fh); err != nil {
log.Error("pprof: %s", err)
return err
}
defer pprof.StopCPUProfile()
}
var filename string
if c.NArg() > 0 {
filename = c.Args().Get(0)
}
// Setting a custom resolution?
var maximize = true
if c.String("window") != "" {
if err := setResolution(c.String("window")); err != nil {
panic(err)
}
maximize = false
}
// Enable feature flags?
if c.Bool("experimental") || usercfg.Current.EnableFeatures {
balance.FeaturesOn()
}
// Set other program flags.
shmem.OfflineMode = c.Bool("offline")
native.ForceTouchScreenModeAlwaysOn = c.Bool("touch")
// SDL engine.
engine := sdl.New(
fmt.Sprintf("%s v%s", branding.AppName, branding.Version),
"Doodle v"+doodle.Version,
balance.Width,
balance.Height,
)
// Activate game controller event support.
sdl2.GameControllerEventState(1)
// Load the SDL fonts in from bindata storage.
if fonts, err := assets.AssetDir("assets/fonts"); err == nil {
for _, file := range fonts {
data, err := assets.Asset("assets/fonts/" + file)
if err != nil {
panic(err)
}
sdl.InstallFont(file, data)
}
} else {
panic(err)
}
// Preload all sound effects.
sound.PreloadAll()
game := doodle.New(c.Bool("debug"), engine)
game.SetupEngine()
// Start with maximized window unless -w was given.
if maximize {
log.Info("Maximize window")
engine.Maximize()
}
// Reload usercfg - if their settings.json doesn't exist, we try and pick a
// default "hide touch hints" based on touch device presence - which is only
// known after SetupEngine.
usercfg.Load()
// Hide the mouse cursor over the window, we draw our own sprite image for it.
engine.ShowCursor(false)
// Set the app window icon.
if engine, ok := game.Engine.(*sdl.Renderer); ok {
if icon, err := sprites.LoadImage(game.Engine, balance.WindowIcon); err == nil {
engine.SetWindowIcon(icon.Image)
} else {
log.Error("Couldn't load WindowIcon (%s): %s", balance.WindowIcon, err)
}
}
if c.Bool("guitest") {
game.Goto(&doodle.GUITestScene{})
} else if c.Bool("new") {
game.NewMap()
} else if filename != "" {
if c.Bool("edit") {
game.EditFile(filename)
@ -262,24 +79,6 @@ func main() {
game.PlayLevel(filename)
}
}
// Maximizing the window? with `-w maximized`
if c.String("window") == "maximized" {
log.Info("Maximize main window")
engine.Maximize()
}
// Log what Doodle thinks its working directory is, for debugging.
pwd, _ := os.Getwd()
log.Info("Program's working directory is: %s", pwd)
// Initialize the developer shell chatbot easter egg.
chatbot.Setup()
// Log some basic environment details.
w, h := engine.WindowSize()
log.Info("Window size: %dx%d", w, h)
game.Run()
return nil
}
@ -289,97 +88,6 @@ func main() {
err := app.Run(os.Args)
if err != nil {
log.Error(err.Error())
log.Fatal(err)
}
}
// Set the app's working directory to find the runtime rtp assets.
func setWorkingDirectory(c *cli.Context) error {
// If they used the --chdir CLI option, go there.
if doodlePath := c.String("chdir"); doodlePath != "" {
return os.Chdir(doodlePath)
}
var test = func(paths ...string) bool {
paths = append(paths, filepath.Join("rtp", "Credits.txt"))
_, err := os.Stat(filepath.Join(paths...))
return err == nil
}
// If the rtp/ folder is already here, nothing is needed.
if test() {
return nil
}
// Get the path to the executable and search around from there.
ex, err := os.Executable()
if err != nil {
return fmt.Errorf("couldn't find the path to current executable: %s", err)
}
exPath := filepath.Dir(ex)
log.Debug("Trying to locate rtp/ folder relative to game's executable path: %s", exPath)
// Test a few relative paths around the executable's folder.
paths := []string{
exPath, // same directory, e.g. Linux /opt/sketchymaze root or Windows zipfile
filepath.Join(exPath, ".."), // parent directory, e.g. from the git clone root
filepath.Join(exPath, "..", "Resources"), // e.g. in a macOS .app bundle.
// Some well-known installed paths to check.
"/opt/sketchymaze", // Linux deb/rpm package
"/app/share/sketchymaze", // Linux flatpak package
}
for _, testPath := range paths {
if test(testPath) {
log.Info("Found rtp folder in: %s", testPath)
return os.Chdir(testPath)
}
}
return nil
}
func setResolution(value string) error {
switch value {
case "desktop", "maximized":
return nil
case "mobile":
balance.Width = 375
balance.Height = 812
if !usercfg.Current.Initialized {
usercfg.Current.HorizontalToolbars = true
}
case "landscape":
balance.Width = 812
balance.Height = 375
default:
var re = regexp.MustCompile(`^(\d+?)x(\d+?)$`)
m := re.FindStringSubmatch(value)
if len(m) == 0 {
return errors.New("--window: must be of the form WIDTHxHEIGHT, i.e. " +
"1024x768, or special keywords desktop, mobile, or landscape.")
}
w, _ := strconv.Atoi(m[1])
h, _ := strconv.Atoi(m[2])
balance.Width = w
balance.Height = h
}
return nil
}
func initLogFile(filename string) error {
// Default log file to disk goes to your profile directory.
if filename == "" {
filename = userdir.LogFile
}
fh, err := golog.NewFileTee(filename)
if err != nil {
return err
}
log.Logger.Config.Writer = fh
return nil
}

View File

@ -1,7 +0,0 @@
#!/bin/bash
export DEBUG_CHUNK_COLOR=FFFF0066
export DEBUG_CANVAS_LABEL=1
export DEBUG_CHUNK_COLOR=00FF00AA
export DEBUG_CANVAS_BORDER=0FF
go run cmd/doodle/main.go --experimental --debug

View File

@ -0,0 +1,12 @@
# Button Doodads
```bash
doodad convert -t "Sticky Button" sticky1.png sticky2.png sticky-button.doodad
doodad install-script sticky.js sticky-button.doodad
doodad convert -t "Button" button1.png button2.png button.doodad
doodad install-script button.js button.doodad
doodad convert -t "Button Type B" typeB1.png typeB2.png button-typeB.doodad
doodad install-script button.js button-typeB.doodad
```

View File

@ -0,0 +1,17 @@
function main() {
console.log("%s initialized!", Self.Doodad.Title);
var timer = 0;
Events.OnCollide( function() {
if (timer > 0) {
clearTimeout(timer);
}
Self.ShowLayer(1);
timer = setTimeout(function() {
Self.ShowLayer(0);
timer = 0;
}, 200);
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 769 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 728 B

View File

@ -0,0 +1,7 @@
function main() {
console.log("%s initialized!", Self.Doodad.Title);
Events.OnCollide( function() {
Self.ShowLayer(1);
})
}

Some files were not shown because too many files have changed in this diff Show More