Compare commits
247 Commits
Author | SHA1 | Date | |
---|---|---|---|
1f00af5741 | |||
90414609a9 | |||
57b757a378 | |||
f35bc48c05 | |||
c7a3c7a797 | |||
6be2f86b58 | |||
4851730ccf | |||
5654145fd8 | |||
b1d7c7a384 | |||
57c76de679 | |||
9dfd38ea5e | |||
35962540e1 | |||
e20c694d93 | |||
8595ad0eba | |||
866e5e7fd8 | |||
c4456ac51b | |||
21847f5e57 | |||
b8665c8b8d | |||
5b3121171e | |||
f2a20808ea | |||
9e90ea4c6c | |||
3cdd56424a | |||
33dc17bb19 | |||
a79601f983 | |||
a06787411d | |||
7eb7f6148c | |||
f4ef0f8d8f | |||
6def8f7625 | |||
85523d8311 | |||
8216e5863b | |||
6fc5900131 | |||
bb28b990e6 | |||
1a9706c09f | |||
9cce93f431 | |||
da83231559 | |||
481638bea6 | |||
282229ba80 | |||
ffb9068fb6 | |||
79996ccd34 | |||
cf1bc81f25 | |||
d397584323 | |||
82884c79ae | |||
856de848c9 | |||
03cd1d4ca0 | |||
0d8933513e | |||
1e37509421 | |||
31097881ff | |||
ddcad27485 | |||
a10a09a818 | |||
06dd30893c | |||
cbc8682406 | |||
56c03dda45 | |||
48d1f2c3b7 | |||
e330a7b6bb | |||
2dd6b5e34b | |||
8b5dab6d6f | |||
ecaa8c6cef | |||
701073cecc | |||
7d15651ff6 | |||
653184b8f8 | |||
cd103f06c7 | |||
73421d27f2 | |||
546b5705db | |||
6631d8d11c | |||
6404024d12 | |||
83f0a2fb49 | |||
ec0b5ba6ca | |||
3e16051724 | |||
53c72f18d1 | |||
d7f247e4cc | |||
a28644d253 | |||
46ab5c9de0 | |||
0a18cd4227 | |||
34c45095b5 | |||
434416d3a4 | |||
450c6b3bb2 | |||
ffc2c6f69b | |||
315c8a81a0 | |||
94d0da78e7 | |||
4efa8d00fc | |||
9b75f1b039 | |||
75fa0c7e56 | |||
fc736abd5f | |||
ad67e2b42b | |||
402b5efa7e | |||
302506eda9 | |||
93623e4e8a | |||
2d3f36379c | |||
9cdc7260bb | |||
c5353df211 | |||
d694fcc7c2 | |||
6b8c7a1efe | |||
db5760ee83 | |||
dbd79ad972 | |||
ba373553cb | |||
38a23f00b2 | |||
af6b8625d6 | |||
bf706efdc6 | |||
647124495b | |||
661c5f4365 | |||
ba4fbf55ef | |||
44122d4130 | |||
77297fd60d | |||
bc15155b68 | |||
0b2e04b336 | |||
962098d4e7 | |||
40cb9f15cb | |||
293ac668e7 | |||
1205dc2cd3 | |||
0fc046250e | |||
4de0126b19 | |||
626fd53a84 | |||
44aba8f1b4 | |||
3f7e384633 | |||
9201475060 | |||
1cc6eee5c8 | |||
5ca87c752f | |||
4d08bf1d85 | |||
d67c1cfcf1 | |||
05b97df846 | |||
9e4f34864d | |||
cbd7816fdf | |||
48e18da511 | |||
24c47d1e3f | |||
51e585b2f8 | |||
96314a852d | |||
3130d8ca94 | |||
a6297f6cb6 | |||
9a51ac39f9 | |||
672ee9641a | |||
690fdedb91 | |||
fa5f303dad | |||
3881457300 | |||
d16a8657aa | |||
37377cdcc1 | |||
6d3ffcd98c | |||
678326540b | |||
a75b7208ca | |||
ddf0074099 | |||
3a9cc83e78 | |||
0ec259b171 | |||
8ca411a0ae | |||
a112c19d76 | |||
1a8a5eb94b | |||
feea703d0c | |||
e80a3f0446 | |||
d6acee5a66 | |||
fb5a8a1ae8 | |||
0b0af70a62 | |||
a24c94a161 | |||
cc16a472af | |||
c2c91e45a9 | |||
489a43ea8c | |||
1f83300cec | |||
4469847c72 | |||
55efdd6eb5 | |||
97e179716c | |||
df3a1679b6 | |||
528e7b4807 | |||
0a1d86e1f5 | |||
21520e71e9 | |||
fd730483b0 | |||
6f5bd910c8 | |||
731d142dd6 | |||
0a8bce708e | |||
ecdfc46358 | |||
13ae66e1fa | |||
4d3e336ca1 | |||
449a30dc2c | |||
f446ed9130 | |||
7866f618da | |||
c499a15c71 | |||
7ea86b4ffc | |||
0fa1bf8a76 | |||
0cc1d17f4f | |||
1ac85c9297 | |||
43f8e3d9b2 | |||
0bf5045a53 | |||
405aaf509d | |||
d7a96d1770 | |||
0518df226c | |||
810ba193d9 | |||
49876c4fdf | |||
fbeeb207a8 | |||
3949934bc1 | |||
215ed5c847 | |||
2d1b926e4f | |||
d4e6d9babb | |||
2885b2c3d0 | |||
8603c43c58 | |||
3486050702 | |||
26b1ac88dd | |||
37f6177a17 | |||
ed492a4451 | |||
456863839e | |||
d1ef9d2932 | |||
1c7678c48e | |||
0af4dd40bc | |||
1105d9312a | |||
fa15a8bcf5 | |||
78f9d0dbfa | |||
99c93dc174 | |||
53123dff1d | |||
386e0b2b0c | |||
864156da53 | |||
d0cfa50625 | |||
dce32ea14b | |||
0449737607 | |||
d6f86487f5 | |||
e6b71f5512 | |||
c5e3fc297c | |||
7093b102e3 | |||
d9bca2152a | |||
eb24858830 | |||
e8388fafad | |||
bd90393cc3 | |||
8d3fc41e43 | |||
640e75ba4d | |||
ba29f407cc | |||
3d8eedce35 | |||
be47dc21c7 | |||
d470f7e647 | |||
d14eaf7df2 | |||
fcb5d27290 | |||
9b80d38c3e | |||
f5d814283c | |||
dc1f0721c2 | |||
0fedcf4fcb | |||
1f274e0ca6 | |||
76b7dfa4f8 | |||
837960c477 | |||
3892087932 | |||
2c1185cc9f | |||
11afc7b522 | |||
6912362899 | |||
6c5da42c91 | |||
c78cd38c1d | |||
580aaca2c5 | |||
9529980ee4 | |||
8eb3ab51d3 | |||
fade085695 | |||
6cd5f17e9b | |||
6e40d58010 | |||
24aef28a0d | |||
190d4be1b6 | |||
336a949ed0 | |||
bc02f2c685 |
2
.dockerignore
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
bin/*
|
||||||
|
dist/*
|
8
.gitignore
vendored
|
@ -1,19 +1,21 @@
|
||||||
pkg/bindata/bindata.go
|
pkg/bindata/bindata.go
|
||||||
|
deps/
|
||||||
fonts/
|
fonts/
|
||||||
maps/
|
maps/
|
||||||
bin/
|
bin/
|
||||||
dist/
|
dist/
|
||||||
rtp/
|
rtp/
|
||||||
dev-assets/guidebook/venv
|
guidebook/
|
||||||
dev-assets/guidebook/site/
|
docker-artifacts/
|
||||||
wasm/assets/
|
wasm/assets/
|
||||||
*.wasm
|
*.wasm
|
||||||
*.doodad
|
*.doodad
|
||||||
*.level
|
*.level
|
||||||
|
*.levelpack
|
||||||
|
*.AppImage
|
||||||
docker/ubuntu
|
docker/ubuntu
|
||||||
docker/debian
|
docker/debian
|
||||||
docker/fedora
|
docker/fedora
|
||||||
screenshot-*.png
|
screenshot-*.png
|
||||||
map-*.json
|
map-*.json
|
||||||
pkg/wallpaper/*.png
|
pkg/wallpaper/*.png
|
||||||
|
|
||||||
|
|
357
Building.md
|
@ -1,8 +1,192 @@
|
||||||
# Building Doodle
|
# Building Doodle
|
||||||
|
|
||||||
* [Linux](#linux)
|
- [Building Doodle](#building-doodle)
|
||||||
* [Windows Cross-Compile from Linux](#windows-cross-compile-from-linux)
|
- [Dockerfile](#dockerfile)
|
||||||
* [Mac OS](#mac os)
|
- [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
|
## Fonts
|
||||||
|
|
||||||
|
@ -21,17 +205,16 @@ mkdir fonts
|
||||||
cp /usr/share/fonts/dejavu/{DejaVuSans.ttf,DejaVuSans-Bold.ttf,DejaVuSansMono.ttf} 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
|
||||||
|
|
||||||
Makefile commands for Linux:
|
Makefile commands for Unix-likes:
|
||||||
|
|
||||||
* `make setup`: install Go dependencies and set up the build environment
|
* `make setup`: install Go dependencies and set up the build environment
|
||||||
* `make doodads`: build the default Doodads from sources in `dev-assets/`
|
* `make doodads`: build the default Doodads from sources in `deps/doodads/`
|
||||||
* `make bindata`: embed the default doodads, levels and other assets into the
|
|
||||||
Go program. `make bindata-dev` for lightweight dev versions that will read
|
|
||||||
from the filesystem at runtime instead.
|
|
||||||
* `make build`: build the Doodle and Doodad binaries to the `bin/` folder.
|
* `make build`: build the Doodle and Doodad binaries to the `bin/` folder.
|
||||||
* `make buildall`: runs all build steps: doodads, bindata, build.
|
* `make buildall`: runs all build steps: doodads, build.
|
||||||
* `make build-free`: build the shareware binaries to the `bin/` folder. See
|
* `make build-free`: build the shareware binaries to the `bin/` folder. See
|
||||||
Build Tags below.
|
Build Tags below.
|
||||||
* `make build-debug`: build a debug binary (not release-mode) to the `bin/`
|
* `make build-debug`: build a debug binary (not release-mode) to the `bin/`
|
||||||
|
@ -51,50 +234,27 @@ Makefile commands for Linux:
|
||||||
* `make docker.fedora`
|
* `make docker.fedora`
|
||||||
* `make clean`: clean all build artifacts
|
* `make clean`: clean all build artifacts
|
||||||
|
|
||||||
## Build Tags
|
# Dependencies
|
||||||
|
|
||||||
### shareware
|
The bootstrap.py script lists dependencies for Fedora, Debian and macOS.
|
||||||
|
Also here for clarity, hopefully not out-of-date:
|
||||||
> Files ending with `_free.go` are for the shareware release as opposed to
|
|
||||||
> `_paid.go` for the full version.
|
|
||||||
|
|
||||||
Builds the game in the free shareware release mode.
|
|
||||||
|
|
||||||
Run `make build-free` to build the shareware binary.
|
|
||||||
|
|
||||||
Shareware releases of the game have the following changes compared to the default
|
|
||||||
(release) mode:
|
|
||||||
|
|
||||||
* No access to the Doodad Editor scene in-game (soft toggle)
|
|
||||||
|
|
||||||
### developer
|
|
||||||
|
|
||||||
> Files ending with `_developer.go` are for the developer build as opposed to
|
|
||||||
> `_release.go` for the public version.
|
|
||||||
|
|
||||||
Developer builds support extra features over the standard release version:
|
|
||||||
|
|
||||||
* Ability to write the JSON file format for Levels and Doodads.
|
|
||||||
|
|
||||||
Run `make build-debug` to build a developer version of the program.
|
|
||||||
|
|
||||||
## Linux
|
|
||||||
|
|
||||||
Dependencies are Go, SDL2 and SDL2_ttf:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Fedora
|
# Fedora-likes
|
||||||
sudo dnf -y install golang SDL2-devel SDL2_ttf-devel SDL2_mixer-devel
|
sudo dnf install make golang SDL2-devel SDL2_ttf-devel \
|
||||||
|
SDL2_mixer-devel
|
||||||
|
|
||||||
# Ubuntu and Debian
|
# Debian and Ubuntu
|
||||||
sudo apt -y install golang libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-devel
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
## Mac OS
|
## Flatpak for Linux
|
||||||
|
|
||||||
```bash
|
The repo for this is at <https://git.kirsle.net/SketchyMaze/flatpak>.
|
||||||
brew install golang sdl2 sdl2_ttf pkg-config
|
|
||||||
```
|
|
||||||
|
|
||||||
## Windows Cross-Compile from Linux
|
## Windows Cross-Compile from Linux
|
||||||
|
|
||||||
|
@ -142,3 +302,108 @@ cp /usr/x86_64-w64-mingw32/bin/SDL*.dll bin/
|
||||||
|
|
||||||
SDL2_ttf requires libfreetype, you can get its DLL here:
|
SDL2_ttf requires libfreetype, you can get its DLL here:
|
||||||
https://github.com/ubawurinna/freetype-windows-binaries
|
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.
|
||||||
|
|
1177
Changes.md
121
Dockerfile
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
##
|
||||||
|
# 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/"]
|
636
LICENSE.md
Normal file
|
@ -0,0 +1,636 @@
|
||||||
|
# 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).
|
159
Makefile
|
@ -9,46 +9,42 @@ CURDIR=$(shell curdir)
|
||||||
LDFLAGS := -ldflags "-X main.Build=$(BUILD) -X main.BuildDate=$(BUILD_DATE)"
|
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"
|
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.
|
# `make setup` to set up a new environment, pull dependencies, etc.
|
||||||
.PHONY: setup
|
.PHONY: setup
|
||||||
setup: clean
|
setup: clean
|
||||||
go get -u git.kirsle.net/go/bindata/...
|
|
||||||
go get ./...
|
|
||||||
|
|
||||||
# `make build` to build the binary.
|
# `make build` to build the binary.
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build:
|
build:
|
||||||
gofmt -w .
|
go build $(LDFLAGS) $(BUILD_TAGS) -o bin/sketchymaze cmd/doodle/main.go
|
||||||
go build $(LDFLAGS) -i -o bin/doodle cmd/doodle/main.go
|
go build $(LDFLAGS) -tags=doodad -o bin/doodad cmd/doodad/main.go
|
||||||
go build $(LDFLAGS) -i -o bin/doodad cmd/doodad/main.go
|
|
||||||
|
|
||||||
# `make buildall` to run all build steps including doodads and bindata.
|
# `make buildall` to run all build steps including doodads.
|
||||||
.PHONY: buildall
|
.PHONY: buildall
|
||||||
buildall: doodads bindata build
|
buildall: doodads build
|
||||||
|
|
||||||
# `make build-free` to build the binary in free mode.
|
# `make build-free` to build the binary in free mode.
|
||||||
.PHONY: build-free
|
.PHONY: build-free
|
||||||
build-free:
|
build-free:
|
||||||
gofmt -w .
|
gofmt -w .
|
||||||
go build $(LDFLAGS) -tags="shareware" -i -o bin/doodle cmd/doodle/main.go
|
go build $(LDFLAGS) -o bin/sketchymaze cmd/doodle/main.go
|
||||||
go build $(LDFLAGS) -tags="shareware" -i -o bin/doodad cmd/doodad/main.go
|
go build $(LDFLAGS) -tags=doodad -o bin/doodad cmd/doodad/main.go
|
||||||
|
|
||||||
# `make build-debug` to build the binary in developer mode.
|
|
||||||
.PHONY: build-debug
|
|
||||||
build-debug:
|
|
||||||
gofmt -w .
|
|
||||||
go build $(LDFLAGS) -tags="developer" -i -o bin/doodle cmd/doodle/main.go
|
|
||||||
go build $(LDFLAGS) -tags="developer" -i -o bin/doodad cmd/doodad/main.go
|
|
||||||
|
|
||||||
# `make bindata` generates the embedded binary assets package.
|
# `make bindata` generates the embedded binary assets package.
|
||||||
.PHONY: bindata
|
.PHONY: bindata
|
||||||
bindata:
|
bindata:
|
||||||
go-bindata -pkg bindata -o pkg/bindata/bindata.go assets/...
|
echo "make bindata: deprecated in favor of Go 1.16 embed; nothing was done"
|
||||||
|
|
||||||
# `make bindata-dev` generates the debug version of bindata package.
|
# `make bindata-dev` generates the debug version of bindata package.
|
||||||
.PHONY: bindata-dev
|
.PHONY: bindata-dev
|
||||||
bindata-dev:
|
bindata-dev:
|
||||||
go-bindata -debug -pkg bindata -o pkg/bindata/bindata.go assets/...
|
echo "make bindata-dev: deprecated in favor of Go 1.16 embed; nothing was done"
|
||||||
|
|
||||||
# `make wasm` builds the WebAssembly port.
|
# `make wasm` builds the WebAssembly port.
|
||||||
.PHONY: wasm
|
.PHONY: wasm
|
||||||
|
@ -64,39 +60,100 @@ wasm-serve: wasm
|
||||||
# `make install` to install the Go binaries to your GOPATH.
|
# `make install` to install the Go binaries to your GOPATH.
|
||||||
.PHONY: install
|
.PHONY: install
|
||||||
install:
|
install:
|
||||||
go install git.kirsle.net/apps/doodle/cmd/...
|
go install git.kirsle.net/SketchyMaze/doodle/cmd/...
|
||||||
|
|
||||||
# `make doodads` to build the doodads from the dev-assets folder.
|
# `make doodads` to build the doodads from the deps/doodads folder.
|
||||||
.PHONY: doodads
|
.PHONY: doodads
|
||||||
doodads:
|
doodads:
|
||||||
cd dev-assets/doodads && ./build.sh
|
cd deps/doodads && ./build.sh > /dev/null
|
||||||
|
|
||||||
# `make mingw` to cross-compile a Windows binary with mingw.
|
# `make mingw` to cross-compile a Windows binary with mingw.
|
||||||
.PHONY: mingw
|
.PHONY: mingw
|
||||||
mingw: doodads bindata
|
mingw:
|
||||||
env CGO_ENABLED="1" CC="/usr/bin/x86_64-w64-mingw32-gcc" \
|
env CGO_ENABLED="1" CC="/usr/bin/x86_64-w64-mingw32-gcc" \
|
||||||
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
|
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
|
||||||
go build $(LDFLAGS_W) -i -o bin/doodle.exe cmd/doodle/main.go
|
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" \
|
env CGO_ENABLED="1" CC="/usr/bin/x86_64-w64-mingw32-gcc" \
|
||||||
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
|
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
|
||||||
go build $(LDFLAGS) -i -o bin/doodad.exe cmd/doodad/main.go
|
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.
|
# `make mingw-free` for Windows binary in free mode.
|
||||||
.PHONY: mingw-free
|
.PHONY: mingw-free
|
||||||
mingw-free: doodads bindata
|
mingw-free:
|
||||||
env CGO_ENABLED="1" CC="/usr/bin/x86_64-w64-mingw32-gcc" \
|
env CGO_ENABLED="1" CC="/usr/bin/x86_64-w64-mingw32-gcc" \
|
||||||
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
|
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
|
||||||
go build $(LDFLAGS_W) -tags="shareware" -i -o bin/doodle.exe cmd/doodle/main.go
|
go build $(LDFLAGS_W) -o bin/sketchymaze.exe cmd/doodle/main.go
|
||||||
env CGO_ENABLED="1" CC="/usr/bin/x86_64-w64-mingw32-gcc" \
|
env CGO_ENABLED="1" CC="/usr/bin/x86_64-w64-mingw32-gcc" \
|
||||||
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
|
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
|
||||||
go build $(LDFLAGS) -tags="shareware" -i -o bin/doodad.exe cmd/doodad/main.go
|
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
|
||||||
|
|
||||||
|
|
||||||
|
# `make run` to run it from source.
|
||||||
# `make run` to run it in debug mode.
|
|
||||||
.PHONY: run
|
.PHONY: run
|
||||||
run:
|
run:
|
||||||
go run cmd/doodle/main.go -debug
|
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
|
||||||
|
|
||||||
# `make guitest` to run it in guitest mode.
|
# `make guitest` to run it in guitest mode.
|
||||||
.PHONY: guitest
|
.PHONY: guitest
|
||||||
|
@ -110,39 +167,31 @@ test:
|
||||||
|
|
||||||
# `make dist` builds and tars up a release.
|
# `make dist` builds and tars up a release.
|
||||||
.PHONY: dist
|
.PHONY: dist
|
||||||
dist: doodads bindata build __dist-common
|
dist: doodads build __dist-common
|
||||||
|
|
||||||
|
# `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.
|
# `make dist-free` builds and tars up a release in shareware mode.
|
||||||
.PHONY: dist-free
|
.PHONY: dist-free
|
||||||
dist-free: doodads bindata build-free __dist-common
|
dist-free: doodads build-free __dist-common
|
||||||
|
|
||||||
# Common logic behind `make dist`
|
# Common logic behind `make dist`
|
||||||
.PHONY: __dist-common
|
.PHONY: __dist-common
|
||||||
__dist-common:
|
__dist-common:
|
||||||
mkdir -p dist/doodle-$(VERSION)
|
mkdir -p dist/sketchymaze-$(VERSION)
|
||||||
cp bin/* dist/doodle-$(VERSION)/
|
cp bin/* dist/sketchymaze-$(VERSION)/
|
||||||
cp -r README.md Changes.md "Open Source Licenses.md" rtp dist/doodle-$(VERSION)/
|
cp -r README.md Changes.md "Open Source Licenses.md" rtp dist/sketchymaze-$(VERSION)/
|
||||||
rm -rf dist/doodle-$(VERSION)/rtp/.git
|
if [[ -d ./guidebook ]]; then cp -r guidebook dist/sketchymaze-$(VERSION)/; fi
|
||||||
ln -sf doodle-$(VERSION) dist/doodle-latest
|
rm -rf dist/sketchymaze-$(VERSION)/rtp/.git
|
||||||
cd dist && tar -czvf doodle-$(VERSION).tar.gz doodle-$(VERSION)
|
ln -sf sketchymaze-$(VERSION) dist/sketchymaze-latest
|
||||||
cd dist && zip -r doodle-$(VERSION).zip doodle-$(VERSION)
|
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.
|
# `make clean` cleans everything up.
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
rm -rf bin dist docker/ubuntu docker/debian docker/fedora
|
rm -rf bin dist docker-artifacts
|
||||||
|
|
|
@ -47,28 +47,24 @@ Except as contained in this notice, the name of Tavmjong Bah shall not be used i
|
||||||
|
|
||||||
## Go Modules
|
## Go Modules
|
||||||
|
|
||||||
### github.com/robertkrimen/otto
|
### github.com/dop251/goja
|
||||||
|
|
||||||
```
|
```
|
||||||
|
Copyright (c) 2016 Dmitry Panov
|
||||||
|
|
||||||
Copyright (c) 2012 Robert Krimen
|
Copyright (c) 2012 Robert Krimen
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||||
in the Software without restriction, including without limitation the rights
|
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
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
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
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
|
### github.com/satori/go.uuid
|
||||||
|
@ -151,33 +147,3 @@ 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
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
```
|
```
|
||||||
|
|
||||||
### github.com/vmihailenco/msgpack
|
|
||||||
|
|
||||||
```
|
|
||||||
Copyright (c) 2013 The github.com/vmihailenco/msgpack 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.
|
|
||||||
|
|
||||||
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
|
|
||||||
OWNER 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.
|
|
||||||
```
|
|
||||||
|
|
214
README.md
|
@ -1,10 +1,10 @@
|
||||||
# Project: Doodle (Working Title)
|
# Sketchy Maze
|
||||||
|
|
||||||
> **Homepage:** https://www.kirsle.net/tagged/Doodle
|
> **Homepage:** https://www.sketchymaze.com
|
||||||
|
|
||||||
Doodle is a drawing-based maze game.
|
Sketchy Maze is a drawing-based maze game.
|
||||||
|
|
||||||
The theme of Doodle is centered around hand-drawn, side-scrolling platformer
|
The game is themed around hand-drawn, side-scrolling platformer
|
||||||
type mazes. You can draw your own levels using freehand and basic drawing tools,
|
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
|
color in some fire or water, and drag in pre-made "Doodads" like buttons, keys
|
||||||
and doors to add some interaction to your level.
|
and doors to add some interaction to your level.
|
||||||
|
@ -12,9 +12,12 @@ and doors to add some interaction to your level.
|
||||||
This is a _very_ early pre-release version of the game. Expect bugs and slowness
|
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.
|
but get a general gist of what the game is about.
|
||||||
|
|
||||||
This alpha release of the game comes with two example levels built-in for
|
This alpha release of the game comes with some example levels built-in for
|
||||||
playing or editing and a handful of built-in Doodads.
|
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
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
(Eventually), the high-level, user-facing features for the game are:
|
(Eventually), the high-level, user-facing features for the game are:
|
||||||
|
@ -46,6 +49,17 @@ game:
|
||||||
* If you receive a map with custom doodads, you can **install** the doodads
|
* 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.
|
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
|
# Keybindings
|
||||||
|
|
||||||
Global Keybindings:
|
Global Keybindings:
|
||||||
|
@ -94,55 +108,48 @@ Ctrl-Y
|
||||||
Redo
|
Redo
|
||||||
```
|
```
|
||||||
|
|
||||||
# Built-In Doodads
|
# Gamepad Controls
|
||||||
|
|
||||||
A brief introduction to the built-in doodads available so far:
|
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.
|
||||||
|
|
||||||
- **Characters**
|
Gamepad controls very depending on two modes the game can be in:
|
||||||
- Blue Azulian: this is used as the player character for now. If
|
|
||||||
dragged into a level, it doesn't do anything but is affected
|
## Mouse Mode
|
||||||
by gravity.
|
|
||||||
- Red Azulian: an example mobile mob for now. It walks back and
|
The Gamepad emulates a mouse cursor in this mode.
|
||||||
forth, changing directions only when it comes across an
|
|
||||||
obstacle and can't proceed any further.
|
* The left analog stick moves a cursor around the screen.
|
||||||
- **Doors and Keys**
|
* The right analog stick scrolls the level (title screen and editor)
|
||||||
- Colored Doors: these act like solid barriers until the player or
|
* A or X button simulates a Left-click
|
||||||
another doodad collects the matching colored key.
|
* B or Y button simulates a Right-click
|
||||||
- Colored Keys: these are collectable items that allow the player or
|
* L1 (left shoulder) emulates a Middle-click
|
||||||
another doodad to open the door of matching color. Note that
|
* L2 (left trigger) closes the top-most window in the editor mode
|
||||||
inventories are doodad-specific, so other mobs besides the
|
(like the Backspace key).
|
||||||
player can steal a key before the player gets it! (For now)
|
|
||||||
- Electric Door: this mechanical door stays closed and only
|
## Gameplay Mode
|
||||||
opens when it receives a power signal from a linked button.
|
|
||||||
- Trapdoor: this door allows one-way access, but once it's closed
|
When playing a level, the controls are as follows:
|
||||||
behind you, you can't pass through it from that side!
|
|
||||||
- **Buttons**
|
* The left analog stick and the D-Pad will move the player character.
|
||||||
- Button: while pressed by a player or other doodad, the button
|
* A or X button to "Use" objects such as Warp Doors.
|
||||||
emits a power signal to any doodad it is linked to. Link a button
|
* B or Y button to "Jump"
|
||||||
to an electric door, and the door will open while the button is
|
* R1 (right shoulder) toggles between Mouse Mode and Gameplay Mode.
|
||||||
pressed and close when the button is released.
|
|
||||||
- Sticky Button: this button will stay pressed once activated and
|
You can use the R1 button to access Mouse Mode to interact with the
|
||||||
will not release unless it receives a power signal from another
|
menus or click on the "Edit Level" button.
|
||||||
linked doodad. For example, one Button that links to a Sticky
|
|
||||||
Button will release the sticky button if pressed.
|
Note: characters with antigravity (such as the Bird) can move in all
|
||||||
- **Switches**
|
four directions but characters with gravity only move left and right
|
||||||
- Switch: when touched by the player or other doodad, the switch will
|
and have the dedicated "Jump" button. This differs from regular
|
||||||
toggle its state from "OFF" to "ON" or vice versa. Link it to an
|
keyboard controls where the Up arrow is to Jump.
|
||||||
Electric Door to open/close the door. Link switches _to each other_ as
|
|
||||||
well as to a door, and all switches will stay in sync with their ON/OFF
|
|
||||||
state when any switch is pressed.
|
|
||||||
- **Crumbly Floor**
|
|
||||||
- This rocky floor will break and fall away after being stepped on.
|
|
||||||
- **Two State Blocks**
|
|
||||||
- Blue and orange blocks that will toggle between solid and pass-thru
|
|
||||||
whenever the corresponding ON/OFF block is hit.
|
|
||||||
- **Start and Exit Flags**
|
|
||||||
- The "Go" flag lets you pick a spawn point for the player character.
|
|
||||||
- The "Exit" flag marks the level goal.
|
|
||||||
|
|
||||||
# Developer Console
|
# Developer Console
|
||||||
|
|
||||||
Press `Enter` at any time to open the 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.
|
||||||
|
|
||||||
Commands supported:
|
Commands supported:
|
||||||
|
|
||||||
|
@ -154,8 +161,8 @@ new
|
||||||
Show the "New Level" screen to start editing a new map.
|
Show the "New Level" screen to start editing a new map.
|
||||||
|
|
||||||
save [filename]
|
save [filename]
|
||||||
Save the current map in Edit Mode. The filename is required if the map has
|
Save the current map in Edit Mode. The filename is required
|
||||||
not been saved yet.
|
if the map has not been saved yet.
|
||||||
|
|
||||||
edit [filename]
|
edit [filename]
|
||||||
Open a map or doodad in Edit Mode.
|
Open a map or doodad in Edit Mode.
|
||||||
|
@ -166,14 +173,39 @@ play [filename]
|
||||||
echo <text>
|
echo <text>
|
||||||
Flash a message to the console.
|
Flash a message to the console.
|
||||||
|
|
||||||
|
alert <text>
|
||||||
|
Test an alert box modal with a custom message.
|
||||||
|
|
||||||
clear
|
clear
|
||||||
Clear the console output history.
|
Clear the console output history.
|
||||||
|
|
||||||
exit
|
exit
|
||||||
quit
|
quit
|
||||||
Close the developer console.
|
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.
|
||||||
|
|
||||||
## Cheat Codes
|
## Cheat Codes
|
||||||
|
|
||||||
The following cheats can be entered into the developer console.
|
The following cheats can be entered into the developer console.
|
||||||
|
@ -205,6 +237,84 @@ Experimental:
|
||||||
The player character must always remain on screen though so you can't
|
The player character must always remain on screen though so you can't
|
||||||
scroll too far away.
|
scroll too far away.
|
||||||
|
|
||||||
|
Unsupported shell commands (here be dragons):
|
||||||
|
|
||||||
|
* `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.
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
To enable certain debug features or customize some aspects of the game,
|
||||||
|
run it with environment variables like the following:
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
Supported variables include:
|
||||||
|
|
||||||
|
* `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).
|
||||||
|
|
||||||
# Author
|
# Author
|
||||||
|
|
||||||
Copyright (C) 2020 Noah Petherbridge. All rights reserved.
|
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.
|
||||||
|
|
||||||
|
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/>.
|
58
appimage.sh
Executable file
|
@ -0,0 +1,58 @@
|
||||||
|
#!/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)
|
70
assets/assets_embed.go
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
//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
|
||||||
|
}
|
32
assets/assets_omitted.go
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
//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
|
||||||
|
}
|
BIN
assets/icons/1024.png
Normal file
After Width: | Height: | Size: 89 KiB |
BIN
assets/icons/128.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
assets/icons/16.png
Normal file
After Width: | Height: | Size: 785 B |
BIN
assets/icons/256.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
assets/icons/32.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
assets/icons/512.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
assets/icons/64.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
assets/icons/96.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
assets/pattern/bars.png
Normal file
After Width: | Height: | Size: 989 B |
BIN
assets/pattern/bubbles.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
assets/pattern/circles.png
Normal file
After Width: | Height: | Size: 652 B |
BIN
assets/pattern/dashed.png
Normal file
After Width: | Height: | Size: 594 B |
BIN
assets/pattern/grid.png
Normal file
After Width: | Height: | Size: 594 B |
BIN
assets/pattern/ink.png
Normal file
After Width: | Height: | Size: 8.2 KiB |
BIN
assets/pattern/marker.png
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
assets/pattern/noise.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
assets/pattern/perlin-noise.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
185
assets/rivescript/begin.rive
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
! 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 " = "
|
||||||
|
! sub ' = '
|
||||||
|
! sub & = &
|
||||||
|
! sub < = <
|
||||||
|
! sub > = >
|
||||||
|
! 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
|
69
assets/rivescript/clients.rive
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
// 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>
|
294
assets/rivescript/eliza.rive
Normal file
|
@ -0,0 +1,294 @@
|
||||||
|
// 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?
|
58
assets/rivescript/myself.rive
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
// 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>.
|
19
assets/rivescript/sketchymaze.rive
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
// 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.
|
63
assets/scripts/generic-anvil.js
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
// 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));
|
||||||
|
});
|
||||||
|
}
|
38
assets/scripts/generic-fire.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
35
assets/scripts/generic-item.js
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
21
assets/scripts/generic-solid.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
// 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;
|
||||||
|
})
|
||||||
|
}
|
Before Width: | Height: | Size: 687 B After Width: | Height: | Size: 661 B |
BIN
assets/sprites/attr-fire.png
Normal file
After Width: | Height: | Size: 997 B |
BIN
assets/sprites/attr-semisolid.png
Normal file
After Width: | Height: | Size: 645 B |
BIN
assets/sprites/attr-slippery.png
Normal file
After Width: | Height: | Size: 690 B |
BIN
assets/sprites/attr-solid.png
Normal file
After Width: | Height: | Size: 792 B |
BIN
assets/sprites/attr-water.png
Normal file
After Width: | Height: | Size: 962 B |
Before Width: | Height: | Size: 717 B After Width: | Height: | Size: 662 B |
Before Width: | Height: | Size: 709 B After Width: | Height: | Size: 668 B |
BIN
assets/sprites/flood-cursor.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
assets/sprites/flood-tool.png
Normal file
After Width: | Height: | Size: 5.7 KiB |
BIN
assets/sprites/gear.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
assets/sprites/gold.png
Normal file
After Width: | Height: | Size: 619 B |
Before Width: | Height: | Size: 626 B After Width: | Height: | Size: 619 B |
Before Width: | Height: | Size: 679 B After Width: | Height: | Size: 665 B |
BIN
assets/sprites/padlock.png
Normal file
After Width: | Height: | Size: 652 B |
BIN
assets/sprites/pan-tool.png
Normal file
After Width: | Height: | Size: 662 B |
Before Width: | Height: | Size: 752 B After Width: | Height: | Size: 689 B |
BIN
assets/sprites/pencil.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
assets/sprites/pointer.png
Normal file
After Width: | Height: | Size: 729 B |
Before Width: | Height: | Size: 648 B After Width: | Height: | Size: 637 B |
BIN
assets/sprites/silver.png
Normal file
After Width: | Height: | Size: 620 B |
BIN
assets/sprites/text-tool.png
Normal file
After Width: | Height: | Size: 639 B |
BIN
assets/wallpapers/atmosphere.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
assets/wallpapers/blue-notebook.png
Normal file
After Width: | Height: | Size: 865 B |
BIN
assets/wallpapers/blue-parchment.png
Normal file
After Width: | Height: | Size: 75 KiB |
BIN
assets/wallpapers/dots-dark.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
assets/wallpapers/dots.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
assets/wallpapers/graph.png
Normal file
After Width: | Height: | Size: 7.5 KiB |
BIN
assets/wallpapers/green-parchment.png
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
assets/wallpapers/red-parchment.png
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
assets/wallpapers/white-parchment.png
Normal file
After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 693 B After Width: | Height: | Size: 1.3 KiB |
BIN
assets/wallpapers/yellow-parchment.png
Normal file
After Width: | Height: | Size: 84 KiB |
179
bootstrap.py
Executable file
|
@ -0,0 +1,179 @@
|
||||||
|
#!/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()
|
|
@ -1,21 +1,116 @@
|
||||||
# doodad.exe
|
# doodad.exe
|
||||||
|
|
||||||
The doodad tool is a command line interface for interacting with Levels and
|
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.
|
||||||
Doodad files, collectively referred to as "Doodle drawings" or just "drawings"
|
|
||||||
for short.
|
|
||||||
|
|
||||||
# Commands
|
- [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)
|
||||||
|
|
||||||
## doodad convert
|
# Quick Start
|
||||||
|
|
||||||
Convert between standard image files (bitmap or PNG) and Doodle drawings
|
## Creating a custom doodad
|
||||||
(levels or doodads).
|
|
||||||
|
|
||||||
This command can be used to "export" a Doodle drawing as a PNG (when run against
|
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:
|
||||||
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.
|
|
||||||
|
|
||||||
Example:
|
```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:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Export a full screenshot of your level
|
# Export a full screenshot of your level
|
||||||
|
@ -24,20 +119,149 @@ $ doodad convert mymap.level screenshot.png
|
||||||
# Create a new level based from a PNG image.
|
# Create a new level based from a PNG image.
|
||||||
$ doodad convert scanned-drawing.png new-level.level
|
$ 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
|
# Create a new doodad based from a BMP image, and in this image the chroma
|
||||||
# color (transparent) is #FF00FF instead of white as default.
|
# color (transparent) is #FF00FF instead of white as default.
|
||||||
$ doodad convert --key '#FF00FF' button.png button.doodad
|
$ doodad convert --key '#FF00FF' button.png button.doodad
|
||||||
```
|
```
|
||||||
|
|
||||||
Supported image types:
|
Supported image types to convert to or from:
|
||||||
|
|
||||||
* PNG (8-bit or 24-bit, with transparent pixels or chroma key)
|
* PNG (8-bit or 24-bit, with transparent pixels or chroma)
|
||||||
* BMP (bitmap image with chroma key)
|
* BMP (bitmap image with chroma key)
|
||||||
|
|
||||||
The chrome key defaults to white (`#FFFFFF`), so pixels of that color are
|
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.
|
||||||
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 drawing, the unique colors identified in the
|
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.
|
||||||
drawing are extracted into the palette. You will need to later edit the palette
|
|
||||||
to assign meaning to the colors.
|
## $ `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
|
||||||
|
```
|
|
@ -11,12 +11,13 @@ import (
|
||||||
|
|
||||||
"image/png"
|
"image/png"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/pkg/branding"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/branding"
|
||||||
"git.kirsle.net/apps/doodle/pkg/doodads"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
|
||||||
"git.kirsle.net/apps/doodle/pkg/level"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
|
||||||
"git.kirsle.net/apps/doodle/pkg/log"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
||||||
|
"git.kirsle.net/SketchyMaze/doodle/pkg/native"
|
||||||
"git.kirsle.net/go/render"
|
"git.kirsle.net/go/render"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli/v2"
|
||||||
"golang.org/x/image/bmp"
|
"golang.org/x/image/bmp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -32,8 +33,8 @@ func init() {
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "key",
|
Name: "key",
|
||||||
Usage: "chroma key color for transparency on input image files",
|
Usage: "chroma key color for transparency on input image files, e.g. #ffffff",
|
||||||
Value: "#ffffff",
|
Value: "",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "title",
|
Name: "title",
|
||||||
|
@ -48,7 +49,7 @@ func init() {
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
if c.NArg() < 2 {
|
if c.NArg() < 2 {
|
||||||
return cli.NewExitError(
|
return cli.Exit(
|
||||||
"Usage: doodad convert <input.png...> <output.doodad>\n"+
|
"Usage: doodad convert <input.png...> <output.doodad>\n"+
|
||||||
" Image file types: png, bmp\n"+
|
" Image file types: png, bmp\n"+
|
||||||
" Drawing file types: level, doodad",
|
" Drawing file types: level, doodad",
|
||||||
|
@ -57,13 +58,17 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the chroma key.
|
// Parse the chroma key.
|
||||||
chroma, err := render.HexColor(c.String("key"))
|
var chroma = render.Invisible
|
||||||
|
if key := c.String("key"); key != "" {
|
||||||
|
color, err := render.HexColor(c.String("key"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.NewExitError(
|
return cli.Exit(
|
||||||
"Chrome key not a valid color: "+err.Error(),
|
"Chrome key not a valid color: "+err.Error(),
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
chroma = color
|
||||||
|
}
|
||||||
|
|
||||||
args := c.Args().Slice()
|
args := c.Args().Slice()
|
||||||
var (
|
var (
|
||||||
|
@ -76,22 +81,22 @@ func init() {
|
||||||
if inputType == extPNG || inputType == extBMP {
|
if inputType == extPNG || inputType == extBMP {
|
||||||
if outputType == extLevel || outputType == extDoodad {
|
if outputType == extLevel || outputType == extDoodad {
|
||||||
if err := imageToDrawing(c, chroma, inputFiles, outputFile); err != nil {
|
if err := imageToDrawing(c, chroma, inputFiles, outputFile); err != nil {
|
||||||
return cli.NewExitError(err.Error(), 1)
|
return cli.Exit(err.Error(), 1)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return cli.NewExitError("Image inputs can only output to Doodle drawings", 1)
|
return cli.Exit("Image inputs can only output to Doodle drawings", 1)
|
||||||
} else if inputType == extLevel || inputType == extDoodad {
|
} else if inputType == extLevel || inputType == extDoodad {
|
||||||
if outputType == extPNG || outputType == extBMP {
|
if outputType == extPNG || outputType == extBMP {
|
||||||
if err := drawingToImage(c, chroma, inputFiles, outputFile); err != nil {
|
if err := drawingToImage(c, chroma, inputFiles, outputFile); err != nil {
|
||||||
return cli.NewExitError(err.Error(), 1)
|
return cli.Exit(err.Error(), 1)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return cli.NewExitError("Doodle drawing inputs can only output to image files", 1)
|
return cli.Exit("Doodle drawing inputs can only output to image files", 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cli.NewExitError("File types must be: png, bmp, level, doodad", 1)
|
return cli.Exit("File types must be: png, bmp, level, doodad", 1)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,20 +105,21 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
|
||||||
// Read the source images. Ensure they all have the same boundaries.
|
// Read the source images. Ensure they all have the same boundaries.
|
||||||
var (
|
var (
|
||||||
imageBounds image.Point
|
imageBounds image.Point
|
||||||
chunkSize int // the square shape for the Doodad chunk size
|
width int // dimensions of the incoming image
|
||||||
|
height int
|
||||||
images []image.Image
|
images []image.Image
|
||||||
)
|
)
|
||||||
|
|
||||||
for i, filename := range inputFiles {
|
for i, filename := range inputFiles {
|
||||||
reader, err := os.Open(filename)
|
reader, err := os.Open(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.NewExitError(err.Error(), 1)
|
return cli.Exit(err.Error(), 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
img, format, err := image.Decode(reader)
|
img, format, err := image.Decode(reader)
|
||||||
log.Info("Parsed image %d of %d. Format: %s", i+1, len(inputFiles), format)
|
log.Info("Parsed image %d of %d. Format: %s", i+1, len(inputFiles), format)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.NewExitError(err.Error(), 1)
|
return cli.Exit(err.Error(), 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the bounding box information of the source image.
|
// Get the bounding box information of the source image.
|
||||||
|
@ -125,18 +131,26 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
|
||||||
// Validate all images are the same size.
|
// Validate all images are the same size.
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
imageBounds = imageSize
|
imageBounds = imageSize
|
||||||
if imageSize.X > imageSize.Y {
|
width = imageSize.X
|
||||||
chunkSize = imageSize.X
|
height = imageSize.Y
|
||||||
} else {
|
|
||||||
chunkSize = imageSize.Y
|
|
||||||
}
|
|
||||||
} else if imageSize != imageBounds {
|
} else if imageSize != imageBounds {
|
||||||
return cli.NewExitError("your source images are not all the same dimensions", 1)
|
return cli.Exit("your source images are not all the same dimensions", 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
images = append(images, img)
|
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.
|
// Helper function to translate image filenames into layer names.
|
||||||
toLayerName := func(filename string) string {
|
toLayerName := func(filename string) string {
|
||||||
ext := filepath.Ext(filename)
|
ext := filepath.Ext(filename)
|
||||||
|
@ -146,18 +160,19 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
|
||||||
// Generate the output drawing file.
|
// Generate the output drawing file.
|
||||||
switch strings.ToLower(filepath.Ext(outputFile)) {
|
switch strings.ToLower(filepath.Ext(outputFile)) {
|
||||||
case extDoodad:
|
case extDoodad:
|
||||||
log.Info("Output is a Doodad file (chunk size %d): %s", chunkSize, outputFile)
|
doodad := doodads.New(width, height)
|
||||||
doodad := doodads.New(chunkSize)
|
|
||||||
doodad.GameVersion = branding.Version
|
doodad.GameVersion = branding.Version
|
||||||
doodad.Title = c.String("title")
|
doodad.Title = c.String("title")
|
||||||
if doodad.Title == "" {
|
if doodad.Title == "" {
|
||||||
doodad.Title = "Converted Doodad"
|
doodad.Title = "Converted Doodad"
|
||||||
}
|
}
|
||||||
doodad.Author = os.Getenv("USER")
|
doodad.Author = native.DefaultAuthor
|
||||||
|
|
||||||
// Write the first layer and gather its palette.
|
// Write the first layer and gather its palette.
|
||||||
log.Info("Converting first layer to drawing and getting the palette")
|
log.Info("Converting first layer to drawing and getting the palette")
|
||||||
palette, layer0 := imageToChunker(images[0], chroma, nil, chunkSize)
|
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)
|
||||||
doodad.Palette = palette
|
doodad.Palette = palette
|
||||||
doodad.Layers[0].Chunker = layer0
|
doodad.Layers[0].Chunker = layer0
|
||||||
doodad.Layers[0].Name = toLayerName(inputFiles[0])
|
doodad.Layers[0].Name = toLayerName(inputFiles[0])
|
||||||
|
@ -168,16 +183,13 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
|
||||||
img := images[i]
|
img := images[i]
|
||||||
log.Info("Converting extra layer %d", i)
|
log.Info("Converting extra layer %d", i)
|
||||||
_, chunker := imageToChunker(img, chroma, palette, chunkSize)
|
_, chunker := imageToChunker(img, chroma, palette, chunkSize)
|
||||||
doodad.Layers = append(doodad.Layers, doodads.Layer{
|
doodad.AddLayer(toLayerName(inputFiles[i]), chunker)
|
||||||
Name: toLayerName(inputFiles[i]),
|
|
||||||
Chunker: chunker,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := doodad.WriteJSON(outputFile)
|
err := doodad.WriteJSON(outputFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.NewExitError(err.Error(), 1)
|
return cli.Exit(err.Error(), 1)
|
||||||
}
|
}
|
||||||
case extLevel:
|
case extLevel:
|
||||||
log.Info("Output is a Level file: %s", outputFile)
|
log.Info("Output is a Level file: %s", outputFile)
|
||||||
|
@ -187,21 +199,24 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
|
||||||
|
|
||||||
lvl := level.New()
|
lvl := level.New()
|
||||||
lvl.GameVersion = branding.Version
|
lvl.GameVersion = branding.Version
|
||||||
|
lvl.MaxWidth = int64(width)
|
||||||
|
lvl.MaxHeight = int64(height)
|
||||||
|
lvl.PageType = level.Bounded
|
||||||
lvl.Title = c.String("title")
|
lvl.Title = c.String("title")
|
||||||
if lvl.Title == "" {
|
if lvl.Title == "" {
|
||||||
lvl.Title = "Converted Level"
|
lvl.Title = "Converted Level"
|
||||||
}
|
}
|
||||||
lvl.Author = os.Getenv("USER")
|
lvl.Author = native.DefaultAuthor
|
||||||
palette, chunker := imageToChunker(images[0], chroma, nil, lvl.Chunker.Size)
|
palette, chunker := imageToChunker(images[0], chroma, palette, lvl.Chunker.Size)
|
||||||
lvl.Palette = palette
|
lvl.Palette = palette
|
||||||
lvl.Chunker = chunker
|
lvl.Chunker = chunker
|
||||||
|
|
||||||
err := lvl.WriteJSON(outputFile)
|
err := lvl.WriteJSON(outputFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.NewExitError(err.Error(), 1)
|
return cli.Exit(err.Error(), 1)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return cli.NewExitError("invalid output file: not a Doodle drawing", 1)
|
return cli.Exit("invalid output file: not a Doodle drawing", 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -287,7 +302,8 @@ func drawingToImage(c *cli.Context, chroma render.Color, inputFiles []string, ou
|
||||||
//
|
//
|
||||||
// img: input image like a PNG
|
// img: input image like a PNG
|
||||||
// chroma: transparent color
|
// chroma: transparent color
|
||||||
func imageToChunker(img image.Image, chroma render.Color, palette *level.Palette, chunkSize int) (*level.Palette, *level.Chunker) {
|
// 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) {
|
||||||
var (
|
var (
|
||||||
chunker = level.NewChunker(chunkSize)
|
chunker = level.NewChunker(chunkSize)
|
||||||
bounds = img.Bounds()
|
bounds = img.Bounds()
|
||||||
|
@ -336,7 +352,10 @@ func imageToChunker(img image.Image, chroma render.Color, palette *level.Palette
|
||||||
sort.Strings(sortedColors)
|
sort.Strings(sortedColors)
|
||||||
for _, hex := range sortedColors {
|
for _, hex := range sortedColors {
|
||||||
if _, ok := newColors[hex]; ok {
|
if _, ok := newColors[hex]; ok {
|
||||||
palette.Swatches = append(palette.Swatches, uniqueColor[hex])
|
if err := palette.AddSwatch(uniqueColor[hex]); err != nil {
|
||||||
|
log.Error("Could not add more colors to the palette: %s", err)
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
palette.Inflate()
|
palette.Inflate()
|
||||||
|
|
|
@ -3,11 +3,13 @@ package commands
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/pkg/doodads"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
|
||||||
"git.kirsle.net/apps/doodle/pkg/log"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
||||||
"github.com/urfave/cli"
|
"git.kirsle.net/go/render"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EditDoodad allows writing doodad metadata.
|
// EditDoodad allows writing doodad metadata.
|
||||||
|
@ -32,11 +34,20 @@ func init() {
|
||||||
Name: "author",
|
Name: "author",
|
||||||
Usage: "set the doodad author",
|
Usage: "set the doodad author",
|
||||||
},
|
},
|
||||||
&cli.StringSliceFlag{
|
&cli.StringFlag{
|
||||||
|
Name: "hitbox",
|
||||||
|
Usage: "set the doodad hitbox (X,Y,W,H or W,H format)",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
Name: "tag",
|
Name: "tag",
|
||||||
Aliases: []string{"t"},
|
Aliases: []string{"t"},
|
||||||
Usage: "set a key/value tag on the doodad, in key=value format. Empty value deletes the tag.",
|
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{
|
&cli.BoolFlag{
|
||||||
Name: "hide",
|
Name: "hide",
|
||||||
Usage: "Hide the doodad from the palette",
|
Usage: "Hide the doodad from the palette",
|
||||||
|
@ -53,10 +64,14 @@ func init() {
|
||||||
Name: "unlock",
|
Name: "unlock",
|
||||||
Usage: "remove the write-lock on the level file",
|
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 {
|
Action: func(c *cli.Context) error {
|
||||||
if c.NArg() < 1 {
|
if c.NArg() < 1 {
|
||||||
return cli.NewExitError(
|
return cli.Exit(
|
||||||
"Usage: doodad edit-doodad <filename.doodad>",
|
"Usage: doodad edit-doodad <filename.doodad>",
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
|
@ -88,6 +103,11 @@ func editDoodad(c *cli.Context, filename string) error {
|
||||||
* Update level properties *
|
* Update level properties *
|
||||||
***************************/
|
***************************/
|
||||||
|
|
||||||
|
if c.Bool("touch") {
|
||||||
|
log.Info("Just touching and resaving the file")
|
||||||
|
modified = true
|
||||||
|
}
|
||||||
|
|
||||||
if c.String("title") != "" {
|
if c.String("title") != "" {
|
||||||
dd.Title = c.String("title")
|
dd.Title = c.String("title")
|
||||||
log.Info("Set title: %s", dd.Title)
|
log.Info("Set title: %s", dd.Title)
|
||||||
|
@ -100,13 +120,40 @@ func editDoodad(c *cli.Context, filename string) error {
|
||||||
modified = true
|
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.
|
// Tags.
|
||||||
tags := c.StringSlice("tag")
|
tag := c.String("tag")
|
||||||
if len(tags) > 0 {
|
if len(tag) > 0 {
|
||||||
for _, tag := range tags {
|
parts := strings.SplitN(tag, "=", 3)
|
||||||
parts := strings.SplitN(tag, "=", 2)
|
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
log.Error("--tag: must be in format `key=value`. Value may be blank to delete a tag. len=%d", len(parts))
|
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)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,6 +171,35 @@ func editDoodad(c *cli.Context, filename string) error {
|
||||||
|
|
||||||
modified = true
|
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") {
|
if c.Bool("hide") {
|
||||||
|
@ -152,7 +228,7 @@ func editDoodad(c *cli.Context, filename string) error {
|
||||||
|
|
||||||
if modified {
|
if modified {
|
||||||
if err := dd.WriteJSON(filename); err != nil {
|
if err := dd.WriteJSON(filename); err != nil {
|
||||||
return cli.NewExitError(fmt.Sprintf("Write error: %s", err), 1)
|
return cli.Exit(fmt.Sprintf("Write error: %s", err), 1)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Warn("Note: No changes made to level")
|
log.Warn("Note: No changes made to level")
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/pkg/level"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
|
||||||
"git.kirsle.net/apps/doodle/pkg/log"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
|
||||||
|
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
||||||
"git.kirsle.net/go/render"
|
"git.kirsle.net/go/render"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EditLevel allows writing level metadata.
|
// EditLevel allows writing level metadata.
|
||||||
|
@ -23,6 +25,11 @@ func init() {
|
||||||
Aliases: []string{"q"},
|
Aliases: []string{"q"},
|
||||||
Usage: "limit output (don't show doodad data at the end)",
|
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{
|
&cli.StringFlag{
|
||||||
Name: "title",
|
Name: "title",
|
||||||
Usage: "set the level title",
|
Usage: "set the level title",
|
||||||
|
@ -41,7 +48,11 @@ func init() {
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "max-size",
|
Name: "max-size",
|
||||||
Usage: "set the page max size (WxH format, like 2550x3300)",
|
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{
|
&cli.StringFlag{
|
||||||
Name: "wallpaper",
|
Name: "wallpaper",
|
||||||
|
@ -55,10 +66,18 @@ func init() {
|
||||||
Name: "unlock",
|
Name: "unlock",
|
||||||
Usage: "remove the write-lock on the level file",
|
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 {
|
Action: func(c *cli.Context) error {
|
||||||
if c.NArg() < 1 {
|
if c.NArg() < 1 {
|
||||||
return cli.NewExitError(
|
return cli.Exit(
|
||||||
"Usage: doodad edit-level <filename.level>",
|
"Usage: doodad edit-level <filename.level>",
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
|
@ -86,10 +105,20 @@ func editLevel(c *cli.Context, filename string) error {
|
||||||
|
|
||||||
log.Info("File: %s", filename)
|
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 *
|
* Update level properties *
|
||||||
***************************/
|
***************************/
|
||||||
|
|
||||||
|
if c.Bool("touch") {
|
||||||
|
log.Info("Just touching and resaving the file")
|
||||||
|
modified = true
|
||||||
|
}
|
||||||
|
|
||||||
if c.String("title") != "" {
|
if c.String("title") != "" {
|
||||||
lvl.Title = c.String("title")
|
lvl.Title = c.String("title")
|
||||||
log.Info("Set title: %s", lvl.Title)
|
log.Info("Set title: %s", lvl.Title)
|
||||||
|
@ -145,13 +174,36 @@ func editLevel(c *cli.Context, filename string) error {
|
||||||
modified = true
|
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 *
|
* Save level changes to disk *
|
||||||
******************************/
|
******************************/
|
||||||
|
|
||||||
if modified {
|
if modified {
|
||||||
if err := lvl.WriteFile(filename); err != nil {
|
if err := lvl.WriteFile(filename); err != nil {
|
||||||
return cli.NewExitError(fmt.Sprintf("Write error: %s", err), 1)
|
return cli.Exit(fmt.Sprintf("Write error: %s", err), 1)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Warn("Note: No changes made to level")
|
log.Warn("Note: No changes made to level")
|
||||||
|
@ -163,3 +215,51 @@ func editLevel(c *cli.Context, filename string) error {
|
||||||
|
|
||||||
return showLevel(c, filename)
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/pkg/doodads"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
|
||||||
"git.kirsle.net/apps/doodle/pkg/log"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InstallScript to add the script to a doodad file.
|
// InstallScript to add the script to a doodad file.
|
||||||
|
@ -26,7 +26,7 @@ func init() {
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
if c.NArg() != 2 {
|
if c.NArg() != 2 {
|
||||||
return cli.NewExitError(
|
return cli.Exit(
|
||||||
"Usage: doodad install-script <script.js> <filename.doodad>",
|
"Usage: doodad install-script <script.js> <filename.doodad>",
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
|
@ -41,12 +41,12 @@ func init() {
|
||||||
// Read the JavaScript source.
|
// Read the JavaScript source.
|
||||||
javascript, err := ioutil.ReadFile(scriptFile)
|
javascript, err := ioutil.ReadFile(scriptFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.NewExitError(err.Error(), 1)
|
return cli.Exit(err.Error(), 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
doodad, err := doodads.LoadJSON(doodadFile)
|
doodad, err := doodads.LoadJSON(doodadFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.NewExitError(
|
return cli.Exit(
|
||||||
fmt.Sprintf("Failed to read doodad file: %s", err),
|
fmt.Sprintf("Failed to read doodad file: %s", err),
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
|
|
343
cmd/doodad/commands/levelpack.go
Normal file
|
@ -0,0 +1,343 @@
|
||||||
|
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
|
||||||
|
}
|
115
cmd/doodad/commands/resave.go
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
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)
|
||||||
|
}
|
|
@ -1,15 +1,19 @@
|
||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/pkg/doodads"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
|
||||||
"git.kirsle.net/apps/doodle/pkg/enum"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/enum"
|
||||||
"git.kirsle.net/apps/doodle/pkg/level"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
|
||||||
"git.kirsle.net/apps/doodle/pkg/log"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/level/rle"
|
||||||
"github.com/urfave/cli"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Show information about a Level or Doodad file.
|
// Show information about a Level or Doodad file.
|
||||||
|
@ -33,15 +37,28 @@ func init() {
|
||||||
Name: "script",
|
Name: "script",
|
||||||
Usage: "print the script from a doodad file and exit",
|
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{
|
&cli.BoolFlag{
|
||||||
Name: "verbose",
|
Name: "verbose",
|
||||||
Aliases: []string{"v"},
|
Aliases: []string{"v"},
|
||||||
Usage: "print verbose output (all verbose flags enabled)",
|
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 {
|
Action: func(c *cli.Context) error {
|
||||||
if c.NArg() < 1 {
|
if c.NArg() < 1 {
|
||||||
return cli.NewExitError(
|
return cli.Exit(
|
||||||
"Usage: doodad show <.level .doodad ...>",
|
"Usage: doodad show <.level .doodad ...>",
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
|
@ -53,12 +70,12 @@ func init() {
|
||||||
case enum.LevelExt:
|
case enum.LevelExt:
|
||||||
if err := showLevel(c, filename); err != nil {
|
if err := showLevel(c, filename); err != nil {
|
||||||
log.Error(err.Error())
|
log.Error(err.Error())
|
||||||
return cli.NewExitError("Error", 1)
|
return cli.Exit("Error", 1)
|
||||||
}
|
}
|
||||||
case enum.DoodadExt:
|
case enum.DoodadExt:
|
||||||
if err := showDoodad(c, filename); err != nil {
|
if err := showDoodad(c, filename); err != nil {
|
||||||
log.Error(err.Error())
|
log.Error(err.Error())
|
||||||
return cli.NewExitError("Error", 1)
|
return cli.Exit("Error", 1)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
log.Error("File %s: not a level or doodad", filename)
|
log.Error("File %s: not a level or doodad", filename)
|
||||||
|
@ -76,17 +93,41 @@ func showLevel(c *cli.Context, filename string) error {
|
||||||
return err
|
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.Printf("===== Level: %s =====\n", filename)
|
||||||
|
|
||||||
fmt.Println("Headers:")
|
fmt.Println("Headers:")
|
||||||
|
fmt.Printf(" File format: %s\n", fileType)
|
||||||
fmt.Printf(" File version: %d\n", lvl.Version)
|
fmt.Printf(" File version: %d\n", lvl.Version)
|
||||||
fmt.Printf(" Game version: %s\n", lvl.GameVersion)
|
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(" Level title: %s\n", lvl.Title)
|
||||||
fmt.Printf(" Author: %s\n", lvl.Author)
|
fmt.Printf(" Author: %s\n", lvl.Author)
|
||||||
fmt.Printf(" Password: %s\n", lvl.Password)
|
fmt.Printf(" Password: %s\n", lvl.Password)
|
||||||
fmt.Printf(" Locked: %+v\n", lvl.Locked)
|
fmt.Printf(" Locked: %+v\n", lvl.Locked)
|
||||||
fmt.Println("")
|
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)
|
showPalette(lvl.Palette)
|
||||||
|
|
||||||
fmt.Println("Level Settings:")
|
fmt.Println("Level Settings:")
|
||||||
|
@ -95,6 +136,17 @@ func showLevel(c *cli.Context, filename string) error {
|
||||||
fmt.Printf(" Wallpaper: %s\n", lvl.Wallpaper)
|
fmt.Printf(" Wallpaper: %s\n", lvl.Wallpaper)
|
||||||
fmt.Println("")
|
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.
|
// Print the actor information.
|
||||||
fmt.Println("Actors:")
|
fmt.Println("Actors:")
|
||||||
fmt.Printf(" Level contains %d actors\n", len(lvl.Actors))
|
fmt.Printf(" Level contains %d actors\n", len(lvl.Actors))
|
||||||
|
@ -104,6 +156,19 @@ func showLevel(c *cli.Context, filename string) error {
|
||||||
fmt.Printf(" - Name: %s\n", actor.Filename)
|
fmt.Printf(" - Name: %s\n", actor.Filename)
|
||||||
fmt.Printf(" UUID: %s\n", id)
|
fmt.Printf(" UUID: %s\n", id)
|
||||||
fmt.Printf(" At: %s\n", actor.Point)
|
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") {
|
if c.Bool("links") {
|
||||||
for _, link := range actor.Links {
|
for _, link := range actor.Links {
|
||||||
if other, ok := lvl.Actors[link]; ok {
|
if other, ok := lvl.Actors[link]; ok {
|
||||||
|
@ -120,7 +185,7 @@ func showLevel(c *cli.Context, filename string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize chunk information.
|
// Serialize chunk information.
|
||||||
showChunker(c, lvl.Chunker)
|
showChunker(c, lvl.Chunker, 0)
|
||||||
|
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
return nil
|
return nil
|
||||||
|
@ -138,13 +203,22 @@ func showDoodad(c *cli.Context, filename string) error {
|
||||||
return nil
|
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.Printf("===== Doodad: %s =====\n", filename)
|
||||||
|
|
||||||
fmt.Println("Headers:")
|
fmt.Println("Headers:")
|
||||||
|
fmt.Printf(" File format: %s\n", fileType)
|
||||||
fmt.Printf(" File version: %d\n", dd.Version)
|
fmt.Printf(" File version: %d\n", dd.Version)
|
||||||
fmt.Printf(" Game version: %s\n", dd.GameVersion)
|
fmt.Printf(" Game version: %s\n", dd.GameVersion)
|
||||||
fmt.Printf(" Doodad title: %s\n", dd.Title)
|
fmt.Printf(" Doodad title: %s\n", dd.Title)
|
||||||
fmt.Printf(" Author: %s\n", dd.Author)
|
fmt.Printf(" Author: %s\n", dd.Author)
|
||||||
|
fmt.Printf(" Dimensions: %s\n", dd.Size)
|
||||||
|
fmt.Printf(" Hitbox: %s\n", dd.Hitbox)
|
||||||
fmt.Printf(" Locked: %+v\n", dd.Locked)
|
fmt.Printf(" Locked: %+v\n", dd.Locked)
|
||||||
fmt.Printf(" Hidden: %+v\n", dd.Hidden)
|
fmt.Printf(" Hidden: %+v\n", dd.Hidden)
|
||||||
fmt.Printf(" Script size: %d bytes\n", len(dd.Script))
|
fmt.Printf(" Script size: %d bytes\n", len(dd.Script))
|
||||||
|
@ -158,11 +232,26 @@ func showDoodad(c *cli.Context, filename string) error {
|
||||||
fmt.Println("")
|
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)
|
showPalette(dd.Palette)
|
||||||
|
|
||||||
for i, layer := range dd.Layers {
|
for i, layer := range dd.Layers {
|
||||||
fmt.Printf("Layer %d: %s\n", i, layer.Name)
|
fmt.Printf("Layer %d: %s\n", i, layer.Name)
|
||||||
showChunker(c, layer.Chunker)
|
showChunker(c, layer.Chunker, i)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
|
@ -179,13 +268,27 @@ func showPalette(pal *level.Palette) {
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
}
|
}
|
||||||
|
|
||||||
func showChunker(c *cli.Context, ch *level.Chunker) {
|
func showChunker(c *cli.Context, ch *level.Chunker, layer int) {
|
||||||
var worldSize = ch.WorldSize()
|
var (
|
||||||
var width = worldSize.W - worldSize.X
|
worldSize = ch.WorldSize()
|
||||||
var height = worldSize.H - worldSize.Y
|
chunkSize = int(ch.Size)
|
||||||
|
width = worldSize.W - worldSize.X
|
||||||
|
height = worldSize.H - worldSize.Y
|
||||||
|
|
||||||
|
// 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.Println("Chunks:")
|
||||||
fmt.Printf(" Pixels Per Chunk: %d^2\n", ch.Size)
|
fmt.Printf(" Pixels Per Chunk: %d^2\n", ch.Size)
|
||||||
fmt.Printf(" Number Generated: %d\n", len(ch.Chunks))
|
fmt.Printf(" Number Generated: %d\n", chunkCount)
|
||||||
fmt.Printf(" Coordinate Range: (%d,%d) ... (%d,%d)\n",
|
fmt.Printf(" Coordinate Range: (%d,%d) ... (%d,%d)\n",
|
||||||
worldSize.X,
|
worldSize.X,
|
||||||
worldSize.Y,
|
worldSize.Y,
|
||||||
|
@ -197,15 +300,53 @@ func showChunker(c *cli.Context, ch *level.Chunker) {
|
||||||
// Verbose chunk information.
|
// Verbose chunk information.
|
||||||
if c.Bool("chunks") || c.Bool("verbose") {
|
if c.Bool("chunks") || c.Bool("verbose") {
|
||||||
fmt.Println(" Chunk Details:")
|
fmt.Println(" Chunk Details:")
|
||||||
for point, chunk := range ch.Chunks {
|
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(" - Coord: %s\n", point)
|
||||||
fmt.Printf(" Type: %s\n", chunkTypeToName(chunk.Type))
|
fmt.Printf(" Type: %s\n", chunkTypeToName(chunk.Type))
|
||||||
fmt.Printf(" Range: (%d,%d) ... (%d,%d)\n",
|
fmt.Printf(" Range: (%d,%d) ... (%d,%d)\n",
|
||||||
int(point.X)*ch.Size,
|
int(point.X)*chunkSize,
|
||||||
int(point.Y)*ch.Size,
|
int(point.Y)*chunkSize,
|
||||||
(int(point.X)*ch.Size)+ch.Size,
|
(int(point.X)*chunkSize)+chunkSize,
|
||||||
(int(point.Y)*ch.Size)+ch.Size,
|
(int(point.Y)*chunkSize)+chunkSize,
|
||||||
)
|
)
|
||||||
|
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 {
|
} else {
|
||||||
fmt.Println(" Use -chunks or -verbose to serialize Chunks")
|
fmt.Println(" Use -chunks or -verbose to serialize Chunks")
|
||||||
|
@ -213,12 +354,12 @@ func showChunker(c *cli.Context, ch *level.Chunker) {
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
}
|
}
|
||||||
|
|
||||||
func chunkTypeToName(v int) string {
|
func chunkTypeToName(v uint64) string {
|
||||||
switch v {
|
switch v {
|
||||||
case level.MapType:
|
case level.MapType:
|
||||||
return "map"
|
return "map"
|
||||||
case level.GridType:
|
case level.RLEType:
|
||||||
return "grid"
|
return "rle map"
|
||||||
default:
|
default:
|
||||||
return fmt.Sprintf("type %d", v)
|
return fmt.Sprintf("type %d", v)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,15 +3,15 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/cmd/doodad/commands"
|
"git.kirsle.net/SketchyMaze/doodle/cmd/doodad/commands"
|
||||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/branding/builds"
|
||||||
"git.kirsle.net/apps/doodle/pkg/branding"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
||||||
"github.com/urfave/cli"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/plus/bootstrap"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Build variables.
|
// Build variables.
|
||||||
|
@ -27,19 +27,15 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
bootstrap.InitPlugins()
|
||||||
|
|
||||||
app := cli.NewApp()
|
app := cli.NewApp()
|
||||||
app.Name = "doodad"
|
app.Name = "doodad"
|
||||||
app.Usage = "command line interface for Doodle"
|
app.Usage = "command line interface for Doodle"
|
||||||
|
|
||||||
var freeLabel string
|
app.Version = fmt.Sprintf("%s build %s. Built on %s",
|
||||||
if balance.FreeVersion {
|
builds.Version,
|
||||||
freeLabel = " (shareware)"
|
|
||||||
}
|
|
||||||
|
|
||||||
app.Version = fmt.Sprintf("%s build %s%s. Built on %s",
|
|
||||||
branding.Version,
|
|
||||||
Build,
|
Build,
|
||||||
freeLabel,
|
|
||||||
BuildDate,
|
BuildDate,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -53,9 +49,11 @@ func main() {
|
||||||
app.Commands = []*cli.Command{
|
app.Commands = []*cli.Command{
|
||||||
commands.Convert,
|
commands.Convert,
|
||||||
commands.Show,
|
commands.Show,
|
||||||
|
commands.Resave,
|
||||||
commands.EditLevel,
|
commands.EditLevel,
|
||||||
commands.EditDoodad,
|
commands.EditDoodad,
|
||||||
commands.InstallScript,
|
commands.InstallScript,
|
||||||
|
commands.LevelPack,
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Sort(cli.FlagsByName(app.Flags))
|
sort.Sort(cli.FlagsByName(app.Flags))
|
||||||
|
@ -63,6 +61,7 @@ func main() {
|
||||||
|
|
||||||
err := app.Run(os.Args)
|
err := app.Run(os.Args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Error("Fatal: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
44
cmd/doodle-admin/command/key.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
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
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
73
cmd/doodle-admin/command/sign.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
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
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
92
cmd/doodle-admin/command/sign_level.go
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
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
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
59
cmd/doodle-admin/command/verify.go
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
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
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
73
cmd/doodle-admin/command/verify_level.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
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
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
61
cmd/doodle-admin/main.go
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,20 +4,35 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"runtime/pprof"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
doodle "git.kirsle.net/apps/doodle/pkg"
|
"git.kirsle.net/SketchyMaze/doodle/assets"
|
||||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
doodle "git.kirsle.net/SketchyMaze/doodle/pkg"
|
||||||
"git.kirsle.net/apps/doodle/pkg/bindata"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
|
||||||
"git.kirsle.net/apps/doodle/pkg/branding"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/branding"
|
||||||
"git.kirsle.net/apps/doodle/pkg/log"
|
"git.kirsle.net/SketchyMaze/doodle/pkg/branding/builds"
|
||||||
"git.kirsle.net/apps/doodle/pkg/sound"
|
"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"
|
"git.kirsle.net/go/render/sdl"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli/v2"
|
||||||
|
sdl2 "github.com/veandco/go-sdl2/sdl"
|
||||||
|
|
||||||
_ "image/png"
|
_ "image/png"
|
||||||
)
|
)
|
||||||
|
@ -41,19 +56,29 @@ func init() {
|
||||||
func main() {
|
func main() {
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
|
|
||||||
|
bootstrap.InitPlugins()
|
||||||
|
|
||||||
app := cli.NewApp()
|
app := cli.NewApp()
|
||||||
app.Name = "doodle"
|
app.Name = "doodle"
|
||||||
app.Usage = fmt.Sprintf("%s - %s", branding.AppName, branding.Summary)
|
app.Usage = fmt.Sprintf("%s - %s", branding.AppName, branding.Summary)
|
||||||
|
|
||||||
var freeLabel string
|
// Load user settings from disk ASAP.
|
||||||
if balance.FreeVersion {
|
if err := usercfg.Load(); err != nil {
|
||||||
freeLabel = " (shareware)"
|
log.Error("Error loading user settings (defaults will be used): %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Version = fmt.Sprintf("%s build %s%s. Built on %s",
|
// Set default user settings.
|
||||||
branding.Version,
|
if usercfg.Current.CrosshairColor == render.Invisible {
|
||||||
|
usercfg.Current.CrosshairColor = balance.DefaultCrosshairColor
|
||||||
|
usercfg.Save()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set GameController style.
|
||||||
|
gamepad.SetStyle(gamepad.Style(usercfg.Current.ControllerStyle))
|
||||||
|
|
||||||
|
app.Version = fmt.Sprintf("%s build %s. Built on %s",
|
||||||
|
builds.Version,
|
||||||
Build,
|
Build,
|
||||||
freeLabel,
|
|
||||||
BuildDate,
|
BuildDate,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -63,6 +88,24 @@ func main() {
|
||||||
Aliases: []string{"d"},
|
Aliases: []string{"d"},
|
||||||
Usage: "enable debug level logging",
|
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.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{
|
&cli.BoolFlag{
|
||||||
Name: "edit",
|
Name: "edit",
|
||||||
Aliases: []string{"e"},
|
Aliases: []string{"e"},
|
||||||
|
@ -73,25 +116,88 @@ func main() {
|
||||||
Aliases: []string{"w"},
|
Aliases: []string{"w"},
|
||||||
Usage: "set the window size (e.g. -w 1024x768) or special value: desktop, mobile, landscape, maximized",
|
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",
|
Name: "guitest",
|
||||||
Usage: "enter the GUI Test scene on startup",
|
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 {
|
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
|
var filename string
|
||||||
if c.NArg() > 0 {
|
if c.NArg() > 0 {
|
||||||
filename = c.Args().Get(0)
|
filename = c.Args().Get(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setting a custom resolution?
|
// Setting a custom resolution?
|
||||||
|
var maximize = true
|
||||||
if c.String("window") != "" {
|
if c.String("window") != "" {
|
||||||
if err := setResolution(c.String("window")); err != nil {
|
if err := setResolution(c.String("window")); err != nil {
|
||||||
panic(err)
|
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.
|
// SDL engine.
|
||||||
engine := sdl.New(
|
engine := sdl.New(
|
||||||
fmt.Sprintf("%s v%s", branding.AppName, branding.Version),
|
fmt.Sprintf("%s v%s", branding.AppName, branding.Version),
|
||||||
|
@ -99,10 +205,13 @@ func main() {
|
||||||
balance.Height,
|
balance.Height,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Activate game controller event support.
|
||||||
|
sdl2.GameControllerEventState(1)
|
||||||
|
|
||||||
// Load the SDL fonts in from bindata storage.
|
// Load the SDL fonts in from bindata storage.
|
||||||
if fonts, err := bindata.AssetDir("assets/fonts"); err == nil {
|
if fonts, err := assets.AssetDir("assets/fonts"); err == nil {
|
||||||
for _, file := range fonts {
|
for _, file := range fonts {
|
||||||
data, err := bindata.Asset("assets/fonts/" + file)
|
data, err := assets.Asset("assets/fonts/" + file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -118,8 +227,34 @@ func main() {
|
||||||
|
|
||||||
game := doodle.New(c.Bool("debug"), engine)
|
game := doodle.New(c.Bool("debug"), engine)
|
||||||
game.SetupEngine()
|
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") {
|
if c.Bool("guitest") {
|
||||||
game.Goto(&doodle.GUITestScene{})
|
game.Goto(&doodle.GUITestScene{})
|
||||||
|
} else if c.Bool("new") {
|
||||||
|
game.NewMap()
|
||||||
} else if filename != "" {
|
} else if filename != "" {
|
||||||
if c.Bool("edit") {
|
if c.Bool("edit") {
|
||||||
game.EditFile(filename)
|
game.EditFile(filename)
|
||||||
|
@ -134,6 +269,17 @@ func main() {
|
||||||
engine.Maximize()
|
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()
|
game.Run()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -147,6 +293,53 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 {
|
func setResolution(value string) error {
|
||||||
switch value {
|
switch value {
|
||||||
case "desktop", "maximized":
|
case "desktop", "maximized":
|
||||||
|
@ -154,6 +347,9 @@ func setResolution(value string) error {
|
||||||
case "mobile":
|
case "mobile":
|
||||||
balance.Width = 375
|
balance.Width = 375
|
||||||
balance.Height = 812
|
balance.Height = 812
|
||||||
|
if !usercfg.Current.Initialized {
|
||||||
|
usercfg.Current.HorizontalToolbars = true
|
||||||
|
}
|
||||||
case "landscape":
|
case "landscape":
|
||||||
balance.Width = 812
|
balance.Width = 812
|
||||||
balance.Height = 375
|
balance.Height = 375
|
||||||
|
@ -172,3 +368,18 @@ func setResolution(value string) error {
|
||||||
}
|
}
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|
7
debug-af.sh
Executable file
|
@ -0,0 +1,7 @@
|
||||||
|
#!/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
|
|
@ -1,40 +0,0 @@
|
||||||
function main() {
|
|
||||||
console.log("Azulian '%s' initialized!", Self.Title);
|
|
||||||
|
|
||||||
var playerSpeed = 4;
|
|
||||||
var gravity = 4;
|
|
||||||
var Vx = Vy = 0;
|
|
||||||
|
|
||||||
var direction = "right";
|
|
||||||
|
|
||||||
Self.SetMobile(true);
|
|
||||||
Self.SetGravity(true);
|
|
||||||
Self.AddAnimation("walk-left", 100, ["red-wl1", "red-wl2", "red-wl3", "red-wl4"]);
|
|
||||||
Self.AddAnimation("walk-right", 100, ["red-wr1", "red-wr2", "red-wr3", "red-wr4"]);
|
|
||||||
|
|
||||||
// Sample our X position every few frames and detect if we've hit a solid wall.
|
|
||||||
var sampleTick = 0;
|
|
||||||
var sampleRate = 5;
|
|
||||||
var lastSampledX = 0;
|
|
||||||
|
|
||||||
setInterval(function() {
|
|
||||||
if (sampleTick % sampleRate === 0) {
|
|
||||||
var curX = Self.Position().X;
|
|
||||||
var delta = Math.abs(curX - lastSampledX);
|
|
||||||
if (delta < 5) {
|
|
||||||
direction = direction === "right" ? "left" : "right";
|
|
||||||
}
|
|
||||||
lastSampledX = curX;
|
|
||||||
}
|
|
||||||
sampleTick++;
|
|
||||||
|
|
||||||
// TODO: Vector() requires floats, pain in the butt for JS,
|
|
||||||
// the JS API should be friendlier and custom...
|
|
||||||
var Vx = parseFloat(playerSpeed * (direction === "left" ? -1 : 1));
|
|
||||||
Self.SetVelocity(Vector(Vx, 0.0));
|
|
||||||
|
|
||||||
if (!Self.IsAnimating()) {
|
|
||||||
Self.PlayAnimation("walk-"+direction, null);
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
function main() {
|
|
||||||
var playerSpeed = 12;
|
|
||||||
var gravity = 4;
|
|
||||||
var Vx = Vy = 0;
|
|
||||||
|
|
||||||
var animating = false;
|
|
||||||
var animStart = animEnd = 0;
|
|
||||||
var animFrame = animStart;
|
|
||||||
|
|
||||||
Self.SetMobile(true);
|
|
||||||
Self.SetGravity(true);
|
|
||||||
Self.SetHitbox(7, 4, 17, 28);
|
|
||||||
Self.AddAnimation("walk-left", 100, ["blu-wl1", "blu-wl2", "blu-wl3", "blu-wl4"]);
|
|
||||||
Self.AddAnimation("walk-right", 100, ["blu-wr1", "blu-wr2", "blu-wr3", "blu-wr4"]);
|
|
||||||
|
|
||||||
Events.OnKeypress(function(ev) {
|
|
||||||
Vx = 0;
|
|
||||||
Vy = 0;
|
|
||||||
|
|
||||||
if (ev.Right) {
|
|
||||||
if (!Self.IsAnimating()) {
|
|
||||||
Self.PlayAnimation("walk-right", null);
|
|
||||||
}
|
|
||||||
Vx = playerSpeed;
|
|
||||||
} else if (ev.Left) {
|
|
||||||
if (!Self.IsAnimating()) {
|
|
||||||
Self.PlayAnimation("walk-left", null);
|
|
||||||
}
|
|
||||||
Vx = -playerSpeed;
|
|
||||||
} else {
|
|
||||||
Self.StopAnimation();
|
|
||||||
animating = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Self.SetVelocity(Point(Vx, Vy));
|
|
||||||
})
|
|
||||||
}
|
|
Before Width: | Height: | Size: 864 B |
Before Width: | Height: | Size: 829 B |
Before Width: | Height: | Size: 878 B |
Before Width: | Height: | Size: 910 B |
Before Width: | Height: | Size: 853 B |
Before Width: | Height: | Size: 833 B |
Before Width: | Height: | Size: 820 B |
Before Width: | Height: | Size: 893 B |
Before Width: | Height: | Size: 816 B |