Compare commits
373 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 | |||
6241bfe415 | |||
71b3eafbe4 | |||
6d8aa387d7 | |||
47cca8c7c6 | |||
5f75168235 | |||
dabf88dff8 | |||
8964322f4e | |||
b0a2524f1a | |||
cb155c8750 | |||
eae7258ce1 | |||
de896c93e6 | |||
2c032f1df7 | |||
82d50f1c91 | |||
27896a9253 | |||
38614ee280 | |||
44788e8032 | |||
695ff4da42 | |||
d615619aba | |||
f0101ba048 | |||
2bd420ff54 | |||
08e65c32b5 | |||
c3d7348843 | |||
3cb99ad5f8 | |||
f8ca9a0921 | |||
7142c76b86 | |||
b4922edf5d | |||
0e3a30e633 | |||
a43e45fad0 | |||
7b3aec0fef | |||
cd31868a13 | |||
8965a7d86a | |||
c08a1bc13e | |||
b924ea9467 | |||
0437adfbf8 | |||
c1353d1c0f | |||
a060330450 | |||
d658359240 | |||
ea0b41a781 | |||
2adffd56c6 | |||
7355778a39 | |||
3634577f19 | |||
32db95ea85 | |||
65a811db0d | |||
6af60f1128 | |||
17b18b8c0a | |||
0c6c77a423 | |||
cc1e441232 | |||
7317615318 | |||
329adb76d9 | |||
0126c288b2 | |||
154fc6c9cb | |||
fd649b7ab1 | |||
6476a67faf | |||
cb02feff1d | |||
a504658055 | |||
dc2695cfc9 | |||
22440f436b | |||
12d34517e9 | |||
5a1ec156ca | |||
5d8c5510d8 | |||
c8620f871e | |||
0c22ecae5e | |||
3d08291bc5 | |||
54776ec9e1 | |||
35a89e5dbe | |||
b17ca34de2 | |||
c7cc40a339 | |||
3d199ca263 | |||
ba6892aa95 | |||
48fc40ade4 | |||
c5c85330de | |||
af67b20d9b | |||
8d855582ed | |||
9efe16582c | |||
4c2e8eca49 | |||
c7fee43f54 | |||
3d3561b8e6 | |||
7281fcbf91 | |||
4dd1bebc5f | |||
1150d6d3e9 | |||
99eab19c5b | |||
87416f9740 | |||
b06c52a705 | |||
567b3158f1 | |||
de79bde776 | |||
1523deeb9c | |||
a2e1bd1ccb | |||
61af068b80 | |||
d28745f89e | |||
a73dec9f31 | |||
ac490473b3 | |||
f76ba6fbb7 | |||
d042457365 | |||
af35703df0 | |||
693664db6c | |||
52a2545692 | |||
2a162a86dd | |||
fb8f4b1029 | |||
35d96b714d | |||
258b2eb285 | |||
81cb3bd617 | |||
c70add17e4 | |||
1e80304061 | |||
b33d93599a | |||
f8a83cbad9 | |||
241186209c | |||
5c08577214 | |||
8fc4f39da0 | |||
bd3dd41cea | |||
2b42a072a0 | |||
d0ff137b04 | |||
569e67bf9e | |||
92847fc3b7 | |||
bca848d534 | |||
b4a366baa9 | |||
0044b72943 | |||
20771fbe13 | |||
1c5a0842e4 | |||
97394f6cdb | |||
279a980106 | |||
5bf7d554f7 | |||
b67c4b67b2 | |||
f18dcf9c2c | |||
cfe26cb964 | |||
a7fd3aa1ca | |||
e25869644c |
2
.dockerignore
Normal file
|
@ -0,0 +1,2 @@
|
|||
bin/*
|
||||
dist/*
|
16
.gitignore
vendored
|
@ -1,5 +1,21 @@
|
|||
pkg/bindata/bindata.go
|
||||
deps/
|
||||
fonts/
|
||||
maps/
|
||||
bin/
|
||||
dist/
|
||||
rtp/
|
||||
guidebook/
|
||||
docker-artifacts/
|
||||
wasm/assets/
|
||||
*.wasm
|
||||
*.doodad
|
||||
*.level
|
||||
*.levelpack
|
||||
*.AppImage
|
||||
docker/ubuntu
|
||||
docker/debian
|
||||
docker/fedora
|
||||
screenshot-*.png
|
||||
map-*.json
|
||||
pkg/wallpaper/*.png
|
||||
|
|
409
Building.md
Normal file
|
@ -0,0 +1,409 @@
|
|||
# Building Doodle
|
||||
|
||||
- [Building Doodle](#building-doodle)
|
||||
- [Dockerfile](#dockerfile)
|
||||
- [Automated Release Scripts](#automated-release-scripts)
|
||||
- [Go Environment](#go-environment)
|
||||
- [Quickstart with bootstrap.py](#quickstart-with-bootstrappy)
|
||||
- [Detailed Instructions](#detailed-instructions)
|
||||
- [Fonts](#fonts)
|
||||
- [Makefile](#makefile)
|
||||
- [Dependencies](#dependencies)
|
||||
- [Flatpak for Linux](#flatpak-for-linux)
|
||||
- [Windows Cross-Compile from Linux](#windows-cross-compile-from-linux)
|
||||
- [Windows DLLs](#windows-dlls)
|
||||
- [Build on macOS from scratch](#build-on-macos-from-scratch)
|
||||
- [WebAssembly](#webassembly)
|
||||
- [Build Tags](#build-tags)
|
||||
- [doodad](#doodad)
|
||||
- [dpp](#dpp)
|
||||
|
||||
# Dockerfile
|
||||
|
||||
The Dockerfile in this git repo may be the quickest way to fully
|
||||
release the game for as many platforms as possible. Run it from a
|
||||
64-bit host Linux system and it will generate Linux and Windows
|
||||
releases for 64-bit and 32-bit Intel CPUs.
|
||||
|
||||
It depends on your git clone of doodle to be fully initialized
|
||||
(e.g., you have run the bootstrap.py script and a `make dist`
|
||||
would build a release for your current system, with doodads and
|
||||
runtime assets all in the right places).
|
||||
|
||||
Run `make docker` and the results will be in the
|
||||
`artifacts/release` folder in your current working directory.
|
||||
|
||||
**Fedora notes (SELinux):** if you run this from a Fedora host
|
||||
you will need to `sudo setenforce permissive` to allow the
|
||||
Dockerfile to mount the artifacts/release folder to export its
|
||||
results.
|
||||
|
||||
# Automated Release Scripts
|
||||
|
||||
Other Dockerfiles and scripts used to release the game:
|
||||
|
||||
* [SketchyMaze/docker](https://git.kirsle.net/SketchyMaze/docker) provides a Dockerfile
|
||||
that fully end-to-end releases the latest version of the game for Linux and Windows. 64bit and 32bit versions that freshly clone the
|
||||
game from git and output their respective CPU release artifacts:
|
||||
* Windows: .zip file
|
||||
* Linux: .tar.gz, .rpm, .deb
|
||||
* [flatpak](https://code.sketchymaze.com/game/flatpak) is a Flatpak manifest for
|
||||
Linux distributions.
|
||||
|
||||
The Docker container depends on all the git servers being up, but if you have
|
||||
the uber blob source code you can read the Dockerfile to see what it does.
|
||||
|
||||
# Go Environment
|
||||
|
||||
Part of the build scripts involve building and running the `doodad` command
|
||||
from this repo in order to generate the game's built-in doodads. For this to
|
||||
work smoothly from your Linux or macOS build environment, you may need to
|
||||
ensure that your `${GOPATH}/bin` directory is on your `$PATH` by, for example,
|
||||
configuring this in your bash/zsh profile:
|
||||
|
||||
```bash
|
||||
export GOPATH="${HOME}/go"
|
||||
export PATH="${PATH}:${GOPATH}/bin"
|
||||
```
|
||||
|
||||
For a complete example, see the "Build on macOS from scratch" section below.
|
||||
|
||||
# Quickstart with bootstrap.py
|
||||
|
||||
From any Unix-like system (Fedora, Ubuntu, macOS) the bootstrap.py script
|
||||
is intended to set this app up, from scratch, _fast._ The basic steps are:
|
||||
|
||||
```bash
|
||||
# Example from an Ubuntu 20.04 LTS fresh install running in a
|
||||
# libvirt-manager virtual machine on a Fedora host.
|
||||
|
||||
# 1. Ensure your SSH keys have git clone permission for git.kirsle.net.
|
||||
# For example just scp my keys from the host Fedora machine.
|
||||
$ scp -r kirsle@192.168.122.1:.ssh/id_rsa* ~/.ssh/
|
||||
|
||||
# 2. git clone the Project Doodle repository.
|
||||
$ git clone git@git.kirsle.net:apps/doodle
|
||||
$ cd ./doodle
|
||||
|
||||
# 3. Run the bootstrap script.
|
||||
$ python3 bootstrap.py
|
||||
```
|
||||
|
||||
The bootstrap script will take care of the rest:
|
||||
|
||||
* `apt install` all the dependencies (golang, SDL2, etc.)
|
||||
* `git clone` various other repositories into a "deps/" folder in doodle's
|
||||
directory. These are things like my Go render library `go/render` and
|
||||
`go/ui` as well as the doodle-rtp runtime package (sound effects, etc.)
|
||||
all of which are hosted on git.kirsle.net.
|
||||
* Build and install the `doodad` tool so it can generate the builtin
|
||||
doodads, and build and release a full distribution of the game.
|
||||
|
||||
It should work on Fedora-likes, Debian-likes and macOS all the same.
|
||||
It even runs on the Pine64 Pinephone (ARM64) with Mobian!
|
||||
|
||||
MacOS is expected to have [homebrew](https://brew.sh) installed.
|
||||
MP3 support issues? [See here](https://github.com/veandco/go-sdl2/issues/299#issuecomment-611681191).
|
||||
|
||||
**To do:** the most important repositories, like the game itself, are
|
||||
also mirrored on GitHub. Other supporting repos need mirroring too, or
|
||||
otherwise, full source tarballs (the result of bootstrap.py) will be
|
||||
built and archived somewhere safe for posterity in case git.kirsle.net
|
||||
ever goes away. The doodle mirror is at <https://github.com/SketchyMaze/doodle>
|
||||
(private repository) and the others are there too (go/render, go/ui, etc.)
|
||||
|
||||
# Detailed Instructions
|
||||
|
||||
For building the app the hard way, and in-depth instructions, read
|
||||
this section. You'll need the following git repositories:
|
||||
|
||||
* `git.kirsle.net/SketchyMaze/doodle` - the game engine.
|
||||
* `git.kirsle.net/SketchyMaze/assets` - where built-in level files are kept (optional)
|
||||
* `git.kirsle.net/SketchyMaze/vendor` - vendored libraries for Windows (SDL2.dll etc.)
|
||||
* `git.kirsle.net/SketchyMaze/rtp` - runtime package (sounds and music mostly)
|
||||
* `git.kirsle.net/SketchyMaze/doodads` - sources to compile the built-in doodads.
|
||||
|
||||
The [docker](https://git.kirsle.net/SketchyMaze/docker) repo will
|
||||
be more up-to-date than the instructions below, as that repo actually has
|
||||
runnable code in the Dockerfile!
|
||||
|
||||
```bash
|
||||
# Clone all the repos down to your project folder
|
||||
git clone https://git.kirsle.net/SketchyMaze/rtp rtp
|
||||
git clone https://git.kirsle.net/SketchyMaze/vendor vendor
|
||||
git clone https://git.kirsle.net/SketchyMaze/masters masters
|
||||
git clone https://git.kirsle.net/SketchyMaze/doodle doodle
|
||||
git clone https://git.kirsle.net/SketchyMaze/doodads doodle/deps/doodads
|
||||
|
||||
# Enter doodle/ project
|
||||
cd doodle/
|
||||
|
||||
# Copy fonts and levels in
|
||||
cp ../assets/levelpacks assets/levelpacks
|
||||
cp ../vendor/fonts assets/fonts
|
||||
mkdir rtp && cp -r ../rtp/* rtp/
|
||||
|
||||
# From the doodle repo:
|
||||
make setup # -or-
|
||||
go get ./... # install dependencies etc.
|
||||
|
||||
# The app should build now. Build and install the doodad tool.
|
||||
go install git.kirsle.net/SketchyMaze/doodle/cmd/doodad
|
||||
doodad --version
|
||||
# "doodad version 0.3.0-alpha build ..."
|
||||
|
||||
# Build and release the game into the dist/ folder.
|
||||
# This will: generate builtin doodads, bundle them with bindata,
|
||||
# and create a tarball in the dist/ folder.
|
||||
make dist
|
||||
|
||||
# Build a cross-compiled Windows target from Linux.
|
||||
# (you'd run before `make dist` to make an uber release)
|
||||
make mingw
|
||||
|
||||
# After make dist, `make release` will carve up Linux
|
||||
# and Windows (mingw) builds and zip them up nicely.
|
||||
make release
|
||||
```
|
||||
|
||||
`make build` produces a local binary in the bin/ folder and `make dist`
|
||||
will build an app for distribution in the dist/ folder.
|
||||
|
||||
The bootstrap.py script does all of the above up to `make dist` so if you need
|
||||
fully release the game by hand (e.g. on a macOS host) you can basically get away
|
||||
with:
|
||||
|
||||
1. Clone the doodle repo and cd into it
|
||||
2. Run `bootstrap.py` to fully set up your OS with dependencies and build a
|
||||
release quality version of the game with all latest assets (the script finishes
|
||||
with a `make dist`).
|
||||
3. Run `make release` to package the dist/ artifact into platform specific
|
||||
release artifacts (.rpm/.deb/.tar.gz bundles for Linux, .zip for Windows,
|
||||
.dmg if running on macOS) which output into the dist/release/ folder.
|
||||
|
||||
Before step 3 you may want to download the latest Guidebook to bundle with
|
||||
the game (optional). Grab and extract the tarball and run `make dist && make release`:
|
||||
|
||||
```bash
|
||||
wget -O - https://download.sketchymaze.com/guidebook.tar.gz | tar -xzvf -
|
||||
```
|
||||
|
||||
## Fonts
|
||||
|
||||
The `fonts/` folder is git-ignored. The app currently uses font files here
|
||||
named:
|
||||
|
||||
* `DejaVuSans.ttf` for sans-serif font.
|
||||
* `DejaVuSans-Bold.ttf` for bold sans-serif font.
|
||||
* `DejaVuSansMono.ttf` for monospace font.
|
||||
|
||||
These are the open source **DejaVu Sans [Mono]** fonts, so copy them in from
|
||||
your `/usr/share/fonts/dejavu` folder or provide alternative fonts.
|
||||
|
||||
```bash
|
||||
mkdir fonts
|
||||
cp /usr/share/fonts/dejavu/{DejaVuSans.ttf,DejaVuSans-Bold.ttf,DejaVuSansMono.ttf} fonts/
|
||||
```
|
||||
|
||||
The doodle-vendor repo has copies of these fonts.
|
||||
|
||||
## Makefile
|
||||
|
||||
Makefile commands for Unix-likes:
|
||||
|
||||
* `make setup`: install Go dependencies and set up the build environment
|
||||
* `make doodads`: build the default Doodads from sources in `deps/doodads/`
|
||||
* `make build`: build the Doodle and Doodad binaries to the `bin/` folder.
|
||||
* `make buildall`: runs all build steps: doodads, build.
|
||||
* `make build-free`: build the shareware binaries to the `bin/` folder. See
|
||||
Build Tags below.
|
||||
* `make build-debug`: build a debug binary (not release-mode) to the `bin/`
|
||||
folder. See Build Tags below.
|
||||
* `make wasm`: build the WebAssembly output
|
||||
* `make wasm-serve`: build the WASM output and then run the server.
|
||||
* `make run`: run a local dev build of Doodle in debug mode
|
||||
* `make guitest`: run a local dev build in the GUITest scene
|
||||
* `make test`: run the test suite
|
||||
* `make dist`: produce a zipped release tarball and zip file for your current
|
||||
environment and output into the `dist/` folder.
|
||||
* `make docker`: run all the Dockerfiles from the `docker/` folder to produce
|
||||
dist builds for Debian, Fedora and Ubuntu. You may also run these builds
|
||||
individually:
|
||||
* `make docker.ubuntu`
|
||||
* `make docker.debian`
|
||||
* `make docker.fedora`
|
||||
* `make clean`: clean all build artifacts
|
||||
|
||||
# Dependencies
|
||||
|
||||
The bootstrap.py script lists dependencies for Fedora, Debian and macOS.
|
||||
Also here for clarity, hopefully not out-of-date:
|
||||
|
||||
```bash
|
||||
# Fedora-likes
|
||||
sudo dnf install make golang SDL2-devel SDL2_ttf-devel \
|
||||
SDL2_mixer-devel
|
||||
|
||||
# Debian and Ubuntu
|
||||
sudo dnf install make golang libsdl2-dev libsdl2-ttf-dev \
|
||||
libsdl2-mixer-dev
|
||||
|
||||
# macOS via Homebrew (https://brew.sh)
|
||||
brew install golang sdl2 sdl2_ttf sdl2_mixer pkg-config
|
||||
```
|
||||
|
||||
## Flatpak for Linux
|
||||
|
||||
The repo for this is at <https://git.kirsle.net/SketchyMaze/flatpak>.
|
||||
|
||||
## Windows Cross-Compile from Linux
|
||||
|
||||
Install the Mingw C compiler:
|
||||
|
||||
```bash
|
||||
# Fedora
|
||||
sudo dnf -y install mingw64-gcc # for 64-bit
|
||||
sudo dnf -y install mingw32-gcc # for 32-bit
|
||||
|
||||
# Arch Linux
|
||||
pacman -S mingw-w64
|
||||
```
|
||||
|
||||
Download the SDL2 Mingw development libraries [here](https://libsdl.org/download-2.0.php)
|
||||
and SDL2_TTF from [here](https://www.libsdl.org/projects/).
|
||||
|
||||
Extract each and copy their library folder into the mingw path.
|
||||
|
||||
```bash
|
||||
# e.g. /usr/x86_64-w64-mingw32 is usually the correct path, verify on e.g.
|
||||
# Fedora with `rpm -ql mingw64-filesystem`
|
||||
tar -xzvf SDL2_ttf-devel-2.0.15-mingw.tar.gz
|
||||
cd SDL_ttf-2.0.15
|
||||
sudo cp -r x86_64-w64-mingw32 /usr
|
||||
```
|
||||
|
||||
Make and set permissions for Go to download the standard library for Windows:
|
||||
|
||||
```bash
|
||||
mkdir /usr/lib/golang/pkg/windows_amd64
|
||||
chown your_username /usr/lib/golang/pkg/windows_amd64
|
||||
```
|
||||
|
||||
And run `make mingw` to build the Windows binary.
|
||||
|
||||
### Windows DLLs
|
||||
|
||||
For the .exe to run it will need SDL2.dll and such.
|
||||
|
||||
```
|
||||
# SDL2.dll and SDL2_ttf.dll
|
||||
cp /usr/x86_64-w64-mingw32/bin/SDL*.dll bin/
|
||||
```
|
||||
|
||||
SDL2_ttf requires libfreetype, you can get its DLL here:
|
||||
https://github.com/ubawurinna/freetype-windows-binaries
|
||||
|
||||
## Build on macOS from scratch
|
||||
|
||||
Here are some detailed instructions how to build Sketchy Maze from a fresh
|
||||
install of macOS Ventura that assumes no previous software or configuration
|
||||
was applied to the system yet.
|
||||
|
||||
Install homebrew: https://brew.sh pay attention to the instructions at the end
|
||||
of the install to set up your zsh profile for homebrew to work correctly.
|
||||
|
||||
Clone the doodle repository:
|
||||
|
||||
```bash
|
||||
git clone https://git.kirsle.net/SketchyMaze/doodle
|
||||
cd doodle
|
||||
```
|
||||
|
||||
Note: on a fresh install, invoking the `git` command may cause macOS to install
|
||||
developer tools and Xcode. After installed, run the git clone again to finish
|
||||
cloning the repository.
|
||||
|
||||
Set your Go environment variables: edit your ~/.zprofile and ensure that $GOPATH
|
||||
is configured and that your $PATH includes $GOPATH/bin. **Note:** restart your
|
||||
terminal session or reload the config file (e.g. `. ~/.zprofile`) after making
|
||||
this change.
|
||||
|
||||
```bash
|
||||
# in your .zprofile, .bash_profile, .zshrc or similar shell config
|
||||
export GOPATH="${HOME}/go"
|
||||
export PATH="${PATH}:${GOPATH}/bin"
|
||||
```
|
||||
|
||||
Run the bootstrap script:
|
||||
|
||||
```bash
|
||||
python3 bootstrap.py
|
||||
```
|
||||
|
||||
Answer N (default) when asked to clone dependency repos over ssh. The bootstrap
|
||||
script will `brew install` any necessary dependencies (Go, SDL2, etc.) and clone
|
||||
support repos for the game (doodads, levelpacks, assets).
|
||||
|
||||
# WebAssembly
|
||||
|
||||
There is some **experimental** support for a WebAssembly build of Sketchy Maze
|
||||
since the very early days. Early on, the game "basically worked" but performance
|
||||
could be awful: playing levels was OK but clicking and dragging in the editor
|
||||
would cause your browser to freeze. Then for a time, the game wouldn't even get
|
||||
that far. Recently (December 2023), WASM performance seems much better but there
|
||||
are strange graphical glitches:
|
||||
|
||||
* On the title screen, the example levels in the background load OK and their
|
||||
doodads will wander around and performance seems OK.
|
||||
* But during Play Mode, only the menu bar draws but nothing else on the screen.
|
||||
* In the Level Editor, the entire screen is white BUT tooltips will appear and
|
||||
the menu bar can be clicked on (blindly) and the drop-down menus do appear.
|
||||
Some popups like the Palette Editor can be invoked and draw to varying degrees
|
||||
of success.
|
||||
|
||||
Some tips to get a WASM build to work:
|
||||
|
||||
* For fonts: symlink it so that ./wasm/fonts points to ./assets/fonts.
|
||||
* You may need an updated wasm_exec.js shim from Go. On Fedora,
|
||||
`dnf install golang-misc` and `cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .`
|
||||
from the wasm/ folder.
|
||||
* Run `make wasm` to build the WASM binary and `make wasm-serve` to run a simple
|
||||
Go web server to serve it from.
|
||||
|
||||
# Build Tags
|
||||
|
||||
Go build tags used by this game:
|
||||
|
||||
## doodad
|
||||
|
||||
This tag is used when building the `doodad` command-line tool.
|
||||
|
||||
It ensures that the embedded bindata assets (built-in doodads, etc.) do not
|
||||
need to be bundled into the doodad binary, but only the main game binary.
|
||||
|
||||
## dpp
|
||||
|
||||
The dpp tag stands for Doodle++ and is used for official commercial builds of
|
||||
the game. Doodle++ builds include additional code not found in the free & open
|
||||
source release of the game engine.
|
||||
|
||||
This build tag should be set automatically by the Makefile **if** the deps/
|
||||
folder has a git clone of the dpp project. The bootstrap.py script will clone
|
||||
the dpp repo **if** you use SSH to clone dependencies: so you will need SSH
|
||||
credentials to the upstream git server. It basically means that third-party
|
||||
users who download the open source release will not have the dpp dependency,
|
||||
and will not build dpp copies of the game.
|
||||
|
||||
If you _do_ have the dpp dependency, you can force build (and run) FOSS
|
||||
versions of the game via the Makefile commands `make build-free`,
|
||||
`make run-free` or `make dist-free` which are counterparts to the main make
|
||||
commands but which deliberately do not set the dpp build tag.
|
||||
|
||||
In source code, files ending with `_dpp.go` and `_foss.go` are conditionally
|
||||
compiled depending on this build tag.
|
||||
|
||||
How to tell whether your build of Sketchy Maze is Doodle++ include:
|
||||
|
||||
* The version string on the title screen.
|
||||
* FOSS builds (not dpp) will say "open source" in the version.
|
||||
* DPP builds may say "shareware" if unregistered or just the version.
|
1277
Changes.md
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/"]
|
44
Ideas.md
|
@ -76,10 +76,10 @@ The major milestones of the game are roughly:
|
|||
* Colors are not tied to behaviors. Each "Swatch" on the palette has its own
|
||||
color and a set of boolean flags for `solid`, `fire` and `water` behaviors.
|
||||
* [ ] User interface to edit (add/remove) swatches from the palette.
|
||||
* [ ] A Toolbox window with radio buttons to select between various drawing tools.
|
||||
* [x] A Toolbox window with radio buttons to select between various drawing tools.
|
||||
* [x] Pencil (the default) draws single pixels on the level.
|
||||
* [ ] Rectangle would draw a rectangular outline.
|
||||
* [ ] Line would draw a line from point to point.
|
||||
* [x] Rectangle would draw a rectangular outline.
|
||||
* [x] Line would draw a line from point to point.
|
||||
* [ ] A way to adjust brush properties:
|
||||
* [ ] Brush size, shape (round or square).
|
||||
* [ ] Tools to toggle "layers" of visibility into your level:
|
||||
|
@ -98,11 +98,11 @@ The major milestones of the game are roughly:
|
|||
|
||||
For creating Doodads in particular:
|
||||
|
||||
* [ ] Make a way to enter Edit Mode in either "Level Mode" or "Doodad Mode",
|
||||
* [x] Make a way to enter Edit Mode in either "Level Mode" or "Doodad Mode",
|
||||
i.e. by a "New Level" or "New Doodad" button.
|
||||
* [ ] Create a "frame manager" window to see and page between the frames of the
|
||||
drawing.
|
||||
* [ ] Ability to work on canvases with constrained size (including smaller than
|
||||
* [x] Ability to work on canvases with constrained size (including smaller than
|
||||
your window). This will use a Canvas widget in the UI toolkit as an abstraction
|
||||
layer. Small canvases will be useful for drawing doodads of a fixed size.
|
||||
|
||||
|
@ -222,11 +222,14 @@ Probably mostly DRM free. Will want some sort of account server early-on though.
|
|||
* The texture file will be a square (rectangular maybe ok) with four quadrants
|
||||
from which the textures will be extracted. For example if the overall image
|
||||
size was 100x100 pixels, it will be divided into the four 50x50 quadrants.
|
||||
1. `TL`: Top left corner is the top left edge of the "page" the level is on
|
||||
2. `TR`: Top right corner is the repeated "top of page" texture.
|
||||
3. `BL`: Bottom left corner is the repeated "left of page" texture.
|
||||
4. `BR`: Bottom right corner is the repeated background texture that extends
|
||||
1. `Corner`: Top left corner is the top left edge of the "page" the level is on
|
||||
2. `Top`: Top right corner is the repeated "top of page" texture.
|
||||
3. `Left`: Bottom left corner is the repeated "left of page" texture.
|
||||
4. `Repeat`: Bottom right corner is the repeated background texture that extends
|
||||
infinitely in all directions.
|
||||
* The Repeat texture is used all the time, and the other three are used when the
|
||||
level type has boundaries (on the top and left edges in particular) to draw
|
||||
decorative borders instead of the Repeat texture.
|
||||
* Levels will be able to choose a "page type" which controls how the wallpaper
|
||||
will be drawn and how the level boundaries may be constrained. There will be
|
||||
four options:
|
||||
|
@ -241,8 +244,9 @@ Probably mostly DRM free. Will want some sort of account server early-on though.
|
|||
wall.
|
||||
3. **Bounded:** The map has a fixed width and height and is bounded on all
|
||||
four edges.
|
||||
4. **Bounded, Mirrored Wallpaper:** same as Bounded but with a different
|
||||
wallpaper behavior.
|
||||
4. **Bordered:** same as Bounded but with a different wallpaper behavior.
|
||||
The bottom and right edges are covered with mirror images of the top and
|
||||
left edges.
|
||||
* The page types will have their own behaviors with how wallpapers are drawn:
|
||||
* **Unbounded:** only the `BR` texture from the wallpaper is used, repeated
|
||||
infinitely in the X and Y directions. The top-left, top, and left edge
|
||||
|
@ -264,6 +268,24 @@ Probably mostly DRM free. Will want some sort of account server early-on though.
|
|||
* The map author can also attach their own custom texture that will be included
|
||||
inside the map file.
|
||||
|
||||
### Default Wallpapers
|
||||
|
||||
**notebook**: standard ruled notebook paper with a red line alone the Left
|
||||
dge and a blank margin along the Top, with a Corner and the blue lines
|
||||
aking up the Repeat in all directions.
|
||||
|
||||
![notebook.png](../assets/wallpapers/notebook.png)
|
||||
|
||||
**graph**: graph paper made up of a grid of light grey or blue lines.
|
||||
|
||||
**dots**: graph paper made of dots at the intersections but not the lines in
|
||||
between.
|
||||
|
||||
**legal**: yellow lined notebook paper (legal pad).
|
||||
|
||||
**placemat**: a placemat texture with a wavy outline that emborders the map
|
||||
on all four sides. To be used with the Bordered level type.
|
||||
|
||||
# Text Console
|
||||
|
||||
* Create a rudimentary dev console for entering text commands in-game. It
|
||||
|
|
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).
|
179
Makefile
|
@ -1,34 +1,197 @@
|
|||
SHELL := /bin/bash
|
||||
|
||||
VERSION=$(shell grep -e 'Version' doodle.go | head -n 1 | cut -d '"' -f 2)
|
||||
VERSION=$(shell egrep -e 'Version\s+=' pkg/branding/branding.go | head -n 1 | cut -d '"' -f 2)
|
||||
BUILD=$(shell git describe --always)
|
||||
BUILD_DATE=$(shell date +"%Y-%m-%dT%H:%M:%S%z")
|
||||
CURDIR=$(shell curdir)
|
||||
|
||||
# Inject the build version (commit hash) into the executable.
|
||||
LDFLAGS := -ldflags "-X main.Build=$(BUILD)"
|
||||
LDFLAGS := -ldflags "-X main.Build=$(BUILD) -X main.BuildDate=$(BUILD_DATE)"
|
||||
LDFLAGS_W := -ldflags "-X main.Build=$(BUILD) -X main.BuildDate=$(BUILD_DATE) -H windowsgui"
|
||||
|
||||
# Doodle++ build tag for official builds of the game.
|
||||
BUILD_TAGS := -tags=""
|
||||
ifneq ("$(wildcard ./deps/dpp)", "")
|
||||
BUILD_TAGS = -tags="dpp"
|
||||
endif
|
||||
|
||||
# `make setup` to set up a new environment, pull dependencies, etc.
|
||||
.PHONY: setup
|
||||
setup: clean
|
||||
go get -u ./...
|
||||
|
||||
# `make build` to build the binary.
|
||||
.PHONY: build
|
||||
build:
|
||||
gofmt -w .
|
||||
go build $(LDFLAGS) -i -o bin/doodle cmd/doodle/main.go
|
||||
go build $(LDFLAGS) $(BUILD_TAGS) -o bin/sketchymaze cmd/doodle/main.go
|
||||
go build $(LDFLAGS) -tags=doodad -o bin/doodad cmd/doodad/main.go
|
||||
|
||||
# `make run` to run it in debug mode.
|
||||
# `make buildall` to run all build steps including doodads.
|
||||
.PHONY: buildall
|
||||
buildall: doodads build
|
||||
|
||||
# `make build-free` to build the binary in free mode.
|
||||
.PHONY: build-free
|
||||
build-free:
|
||||
gofmt -w .
|
||||
go build $(LDFLAGS) -o bin/sketchymaze cmd/doodle/main.go
|
||||
go build $(LDFLAGS) -tags=doodad -o bin/doodad cmd/doodad/main.go
|
||||
|
||||
# `make bindata` generates the embedded binary assets package.
|
||||
.PHONY: bindata
|
||||
bindata:
|
||||
echo "make bindata: deprecated in favor of Go 1.16 embed; nothing was done"
|
||||
|
||||
# `make bindata-dev` generates the debug version of bindata package.
|
||||
.PHONY: bindata-dev
|
||||
bindata-dev:
|
||||
echo "make bindata-dev: deprecated in favor of Go 1.16 embed; nothing was done"
|
||||
|
||||
# `make wasm` builds the WebAssembly port.
|
||||
.PHONY: wasm
|
||||
wasm:
|
||||
cd wasm && make
|
||||
|
||||
# `make wasm-serve` builds and launches the WebAssembly server.
|
||||
.PHONY: wasm-serve
|
||||
wasm-serve: wasm
|
||||
sh -c 'sleep 1; xdg-open http://localhost:8080/' &
|
||||
cd wasm && go run server.go
|
||||
|
||||
# `make install` to install the Go binaries to your GOPATH.
|
||||
.PHONY: install
|
||||
install:
|
||||
go install git.kirsle.net/SketchyMaze/doodle/cmd/...
|
||||
|
||||
# `make doodads` to build the doodads from the deps/doodads folder.
|
||||
.PHONY: doodads
|
||||
doodads:
|
||||
cd deps/doodads && ./build.sh > /dev/null
|
||||
|
||||
# `make mingw` to cross-compile a Windows binary with mingw.
|
||||
.PHONY: mingw
|
||||
mingw:
|
||||
env CGO_ENABLED="1" CC="/usr/bin/x86_64-w64-mingw32-gcc" \
|
||||
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
|
||||
go build $(LDFLAGS_W) $(BUILD_TAGS) -o bin/sketchymaze.exe cmd/doodle/main.go
|
||||
env CGO_ENABLED="1" CC="/usr/bin/x86_64-w64-mingw32-gcc" \
|
||||
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
|
||||
go build $(LDFLAGS) -tags=doodad -o bin/doodad.exe cmd/doodad/main.go
|
||||
|
||||
# `make mingw32` to cross-compile a Windows binary with mingw (32-bit).
|
||||
.PHONY: mingw32
|
||||
mingw32:
|
||||
env CGO_ENABLED="1" CC="/usr/bin/i686-w64-mingw32-gcc" \
|
||||
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
|
||||
go build $(LDFLAGS_W) $(BUILD_TAGS) -o bin/sketchymaze.exe cmd/doodle/main.go
|
||||
env CGO_ENABLED="1" CC="/usr/bin/i686-w64-mingw32-gcc" \
|
||||
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
|
||||
go build $(LDFLAGS) -tags=doodad -o bin/doodad.exe cmd/doodad/main.go
|
||||
|
||||
# `make mingw-free` for Windows binary in free mode.
|
||||
.PHONY: mingw-free
|
||||
mingw-free:
|
||||
env CGO_ENABLED="1" CC="/usr/bin/x86_64-w64-mingw32-gcc" \
|
||||
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
|
||||
go build $(LDFLAGS_W) -o bin/sketchymaze.exe cmd/doodle/main.go
|
||||
env CGO_ENABLED="1" CC="/usr/bin/x86_64-w64-mingw32-gcc" \
|
||||
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
|
||||
go build $(LDFLAGS) -tags=doodad -o bin/doodad.exe cmd/doodad/main.go
|
||||
|
||||
# `make release` runs the release.sh script, must be run
|
||||
# after `make dist`
|
||||
.PHONY: release
|
||||
release:
|
||||
./scripts/release.sh
|
||||
|
||||
# `make release32` runs release with ARCH_LABEL=32bit to product
|
||||
# artifacts targeting an i386 architecture (e.g. in rpm and deb packages
|
||||
# metadata about the release)
|
||||
.PHONY: release32
|
||||
release32:
|
||||
env ARCH_LABEL=32bit ./scripts/release.sh
|
||||
|
||||
# `make appimage` builds an AppImage, run it after `make dist`
|
||||
.PHONY: appimage
|
||||
appimage:
|
||||
./appimage.sh
|
||||
|
||||
# `make mingw-release` runs a FULL end-to-end release of Linux and Windows
|
||||
# binaries of the game, zipped and tagged and ready to go.
|
||||
.PHONY: mingw-release
|
||||
mingw-release: doodads build mingw __dist-common release
|
||||
|
||||
.PHONY: mingw32-release
|
||||
mingw32-release: doodads build mingw32 __dist-common release32
|
||||
|
||||
# `make from-docker64` is an internal command run by the Dockerfile to build the
|
||||
# game - assumes doodads and assets are in the right spot already.
|
||||
.PHONY: from-docker64
|
||||
.PHONY: from-docker32
|
||||
from-docker64: build mingw __dist-common
|
||||
ARCH=x86_64 make appimage
|
||||
make release
|
||||
from-docker32: build mingw32 __dist-common
|
||||
ARCH=i686 make appimage
|
||||
make release32
|
||||
|
||||
# `make osx` to cross-compile a Mac OS binary with osxcross.
|
||||
# .PHONY: osx
|
||||
# osx: doodads
|
||||
# CGO_ENABLED=1 CC=[path-to-osxcross]/target/bin/[arch]-apple-darwin[version]-clang GOOS=darwin GOARCH=[arch] go build -tags static -ldflags "-s -w" -a
|
||||
|
||||
|
||||
# `make run` to run it from source.
|
||||
.PHONY: 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.
|
||||
.PHONY: guitest
|
||||
guitest:
|
||||
go run cmd/doodle/main.go -debug -guitest
|
||||
|
||||
# `make test` to run unit tests.
|
||||
.PHONY: test
|
||||
test:
|
||||
go test ./...
|
||||
|
||||
# `make dist` builds and tars up a release.
|
||||
.PHONY: dist
|
||||
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.
|
||||
.PHONY: dist-free
|
||||
dist-free: doodads build-free __dist-common
|
||||
|
||||
# Common logic behind `make dist`
|
||||
.PHONY: __dist-common
|
||||
__dist-common:
|
||||
mkdir -p dist/sketchymaze-$(VERSION)
|
||||
cp bin/* dist/sketchymaze-$(VERSION)/
|
||||
cp -r README.md Changes.md "Open Source Licenses.md" rtp dist/sketchymaze-$(VERSION)/
|
||||
if [[ -d ./guidebook ]]; then cp -r guidebook dist/sketchymaze-$(VERSION)/; fi
|
||||
rm -rf dist/sketchymaze-$(VERSION)/rtp/.git
|
||||
ln -sf sketchymaze-$(VERSION) dist/sketchymaze-latest
|
||||
cd dist && tar -czvf sketchymaze-$(VERSION).tar.gz sketchymaze-$(VERSION)
|
||||
cd dist && zip -r sketchymaze-$(VERSION).zip sketchymaze-$(VERSION)
|
||||
|
||||
# `make clean` cleans everything up.
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf bin dist
|
||||
rm -rf bin dist docker-artifacts
|
||||
|
|
149
Open Source Licenses.md
Normal file
|
@ -0,0 +1,149 @@
|
|||
# Open Source Licenses
|
||||
|
||||
## DejaVu Fonts
|
||||
|
||||
_(from https://dejavu-fonts.github.io/License.html)_
|
||||
|
||||
Fonts are © Bitstream (see below). DejaVu changes are in public domain. Explanation of copyright is on [Gnome page on Bitstream Vera fonts](http://gnome.org/fonts/). Glyphs imported from [Arev](https://dejavu-fonts.github.io/Bitstream_Vera_Derivatives.html) fonts are © Tavmjung Bah (see below)
|
||||
|
||||
### Bitstream Vera Fonts Copyright
|
||||
|
||||
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces.
|
||||
|
||||
The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera".
|
||||
|
||||
This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names.
|
||||
|
||||
The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself.
|
||||
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
||||
Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org.
|
||||
|
||||
### Arev Fonts Copyright
|
||||
|
||||
[Original text](http://tavmjong.free.fr/FONTS/ArevCopyright.txt)
|
||||
|
||||
Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the modifications to the Bitstream Vera Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces.
|
||||
|
||||
The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Tavmjong Bah" or the word "Arev".
|
||||
|
||||
This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Tavmjong Bah Arev" names.
|
||||
|
||||
The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself.
|
||||
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
||||
Except as contained in this notice, the name of Tavmjong Bah shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from Tavmjong Bah. For further information, contact: tavmjong @ free . fr.
|
||||
|
||||
|
||||
## Go Modules
|
||||
|
||||
### github.com/dop251/goja
|
||||
|
||||
```
|
||||
Copyright (c) 2016 Dmitry Panov
|
||||
|
||||
Copyright (c) 2012 Robert Krimen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
||||
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
```
|
||||
|
||||
### github.com/satori/go.uuid
|
||||
|
||||
```
|
||||
Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
```
|
||||
|
||||
### github.com/urfave/cli
|
||||
|
||||
```
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Jeremy Saenz & Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
```
|
||||
|
||||
### github.com/veandco/go-sdl2
|
||||
|
||||
```
|
||||
Copyright (c) 2013, Go-SDL2 Authors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Go-SDL2 nor the names of its contributors may be
|
||||
used to endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
```
|
349
README.md
|
@ -1,6 +1,22 @@
|
|||
# Doodle
|
||||
# Sketchy Maze
|
||||
|
||||
Doodle is a drawing-based maze game written in Go.
|
||||
> **Homepage:** https://www.sketchymaze.com
|
||||
|
||||
Sketchy Maze is a drawing-based maze game.
|
||||
|
||||
The game is themed around hand-drawn, side-scrolling platformer
|
||||
type mazes. You can draw your own levels using freehand and basic drawing tools,
|
||||
color in some fire or water, and drag in pre-made "Doodads" like buttons, keys
|
||||
and doors to add some interaction to your level.
|
||||
|
||||
This is a _very_ early pre-release version of the game. Expect bugs and slowness
|
||||
but get a general gist of what the game is about.
|
||||
|
||||
This alpha release of the game comes with some example levels built-in for
|
||||
playing or editing and a handful of built-in Doodads.
|
||||
|
||||
See the **Guidebook** included with this game for good user-facing
|
||||
documentation or online at https://www.sketchymaze.com/guidebook
|
||||
|
||||
# Features
|
||||
|
||||
|
@ -20,6 +36,9 @@ Doodle is a drawing-based maze game written in Go.
|
|||
|
||||
## Mod-friendly
|
||||
|
||||
(Eventually) all these features will support custom content in the
|
||||
game:
|
||||
|
||||
* Users can create **Custom Doodads** to extend the game with a scripting
|
||||
language like JavaScript. The sprites and animations are edited in-game
|
||||
in Edit Mode, but the scripting is done in your text editor.
|
||||
|
@ -30,6 +49,17 @@ Doodle is a drawing-based maze game written in Go.
|
|||
* If you receive a map with custom doodads, you can **install** the doodads
|
||||
into your copy of the game and use them in your own maps.
|
||||
|
||||
# Developer Documentation
|
||||
|
||||
In case you are reading this from the game's open source repository, take a
|
||||
look in the `docs/` folder or the `*.md` files in the root of the repository.
|
||||
|
||||
Some to start with:
|
||||
|
||||
* [Building](Building.md) the game (tl;dr. run bootstrap.py)
|
||||
* [Tour of the Code](docs/Tour%20of%20the%20Code.md)
|
||||
* [Evolution of File Formats](docs/Evolution%20of%20File%20Formats.md)
|
||||
|
||||
# Keybindings
|
||||
|
||||
Global Keybindings:
|
||||
|
@ -40,8 +70,14 @@ Escape
|
|||
Exit the program otherwise.
|
||||
|
||||
Enter
|
||||
Open and close the developer console, and run commands while the console
|
||||
is open.
|
||||
Open and close the developer console, and run commands while the
|
||||
console is open.
|
||||
|
||||
F3
|
||||
Toggle the Debug Overlay.
|
||||
|
||||
F4
|
||||
Toggle debug collision hitboxes.
|
||||
```
|
||||
|
||||
In Play Mode:
|
||||
|
@ -49,167 +85,236 @@ In Play Mode:
|
|||
```
|
||||
Cursor Keys
|
||||
Move the player around.
|
||||
"E" Key
|
||||
Edit the map you're currently playing if you came from Edit Mode.
|
||||
```
|
||||
|
||||
In Edit Mode:
|
||||
|
||||
```
|
||||
F12
|
||||
Take a screenshot (generate a PNG based on level data)
|
||||
Cursor Keys
|
||||
Scroll the view of the map around.
|
||||
"P" Key
|
||||
Playtest the current map you're working on.
|
||||
"F" Key
|
||||
Switch to the Pencil (Freehand) Tool
|
||||
"L" Key
|
||||
Switch to the Line Tool
|
||||
"R" Key
|
||||
Switch to the Rectangle Tool
|
||||
Ctrl-Z
|
||||
Undo
|
||||
Ctrl-Y
|
||||
Redo
|
||||
```
|
||||
|
||||
## Developer Console
|
||||
# Gamepad Controls
|
||||
|
||||
Press `Enter` at any time to open the developer console.
|
||||
The game supports Xbox and Nintendo style game controllers. The button
|
||||
bindings are not yet customizable, except to choose between the
|
||||
"X Style" or "N Style" for A/B and X/Y button mappings.
|
||||
|
||||
Gamepad controls very depending on two modes the game can be in:
|
||||
|
||||
## Mouse Mode
|
||||
|
||||
The Gamepad emulates a mouse cursor in this mode.
|
||||
|
||||
* The left analog stick moves a cursor around the screen.
|
||||
* The right analog stick scrolls the level (title screen and editor)
|
||||
* A or X button simulates a Left-click
|
||||
* B or Y button simulates a Right-click
|
||||
* L1 (left shoulder) emulates a Middle-click
|
||||
* L2 (left trigger) closes the top-most window in the editor mode
|
||||
(like the Backspace key).
|
||||
|
||||
## Gameplay Mode
|
||||
|
||||
When playing a level, the controls are as follows:
|
||||
|
||||
* The left analog stick and the D-Pad will move the player character.
|
||||
* A or X button to "Use" objects such as Warp Doors.
|
||||
* B or Y button to "Jump"
|
||||
* R1 (right shoulder) toggles between Mouse Mode and Gameplay Mode.
|
||||
|
||||
You can use the R1 button to access Mouse Mode to interact with the
|
||||
menus or click on the "Edit Level" button.
|
||||
|
||||
Note: characters with antigravity (such as the Bird) can move in all
|
||||
four directions but characters with gravity only move left and right
|
||||
and have the dedicated "Jump" button. This differs from regular
|
||||
keyboard controls where the Up arrow is to Jump.
|
||||
|
||||
# Developer Console
|
||||
|
||||
Press `Enter` at any time to open the developer console. The console
|
||||
provides commands and advanced functionality, and is also where cheat
|
||||
codes can be entered.
|
||||
|
||||
Commands supported:
|
||||
|
||||
```
|
||||
close
|
||||
Exit to the game's title screen.
|
||||
|
||||
new
|
||||
Create a new map in Edit Mode.
|
||||
Show the "New Level" screen to start editing a new map.
|
||||
|
||||
save [filename.json]
|
||||
Save the current map in Edit Mode. The filename is required if the map has
|
||||
not been saved yet.
|
||||
save [filename]
|
||||
Save the current map in Edit Mode. The filename is required
|
||||
if the map has not been saved yet.
|
||||
|
||||
edit [filename.json]
|
||||
Open a map in Edit Mode.
|
||||
edit [filename]
|
||||
Open a map or doodad in Edit Mode.
|
||||
|
||||
play [filename.json]
|
||||
play [filename]
|
||||
Open a map in Play Mode.
|
||||
|
||||
echo <text>
|
||||
Flash a message to the console.
|
||||
|
||||
alert <text>
|
||||
Test an alert box modal with a custom message.
|
||||
|
||||
clear
|
||||
Clear the console output history.
|
||||
|
||||
exit
|
||||
quit
|
||||
Close the developer console.
|
||||
|
||||
boolProp <property> <true/false>
|
||||
Toggle certain boolean settings in the game. Most of these
|
||||
are debugging related. `boolProp list` shows the available
|
||||
props.
|
||||
|
||||
eval <expression>
|
||||
$ <expression>
|
||||
Execute a line of JavaScript code in the console. Several
|
||||
of the game's core data types are available here; `d` is
|
||||
the master game struct; d.Scene is the pointer to the
|
||||
current scene. d.Scene.UI.Canvas may point to the level edit
|
||||
canvas in Editor Mode. Object.keys() can enumerate public
|
||||
functions and variables.
|
||||
|
||||
repl
|
||||
Enters an interactive JavaScript shell, where the console
|
||||
stays open and pre-fills a $ prompt for subsequent commands.
|
||||
```
|
||||
|
||||
# Milestones
|
||||
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.
|
||||
|
||||
As a rough idea of the milestones needed for this game to work:
|
||||
## Cheat Codes
|
||||
|
||||
## SDL Paint Program
|
||||
The following cheats can be entered into the developer console.
|
||||
|
||||
* [x] Create a basic SDL window that you can click on to color pixels.
|
||||
* [x] Connect the pixels while the mouse is down to cover gaps.
|
||||
* [x] Implement a "screenshot" button that translates the canvas to a PNG
|
||||
image on disk.
|
||||
* `F12` key to take a screenshot of your drawing.
|
||||
* It reproduces a PNG image using its in-memory knowledge of the pixels you
|
||||
have drawn, *not* by reading the SDL canvas. This will be important for
|
||||
making the custom level format later.
|
||||
* The PNG I draw looks slightly different to what you see on the SDL canvas;
|
||||
maybe difference between `Renderer.DrawLine()` and my own algorithm or
|
||||
the anti-aliasing.
|
||||
* [x] Create a custom map file format (protobufs maybe) and "screenshot" the
|
||||
canvas into this custom file format.
|
||||
* [x] Make the program able to read this file format and reproduce the same
|
||||
pixels on the canvas.
|
||||
* [x] Abstract away SDL logic into a small corner so it can be replaced with
|
||||
OpenGL or something later on.
|
||||
* [x] Implement a command line shell in-game to ease development before a user
|
||||
interface is created.
|
||||
* [x] Add support for the shell to pop itself open and ask the user for
|
||||
input prompts.
|
||||
Play Mode:
|
||||
|
||||
## Alpha Platformer
|
||||
* `import antigravity`
|
||||
- This disables the effects of gravity for the player
|
||||
character. Arrow keys can freely move the player in any direction.
|
||||
* `ghost mode`
|
||||
- This disables collision detection for the player character
|
||||
so that you can pass through walls and solid doodads. Combine with
|
||||
antigravity or else you'll fall to the bottom of the map!
|
||||
* `give all keys`
|
||||
- Adds all four colored keys to the player's inventory.
|
||||
* `drop all items`
|
||||
- Clears the player's inventory of all items.
|
||||
|
||||
* [x] Inflate the pixel history from the map file into a full lookup grid
|
||||
of `(X,Y)` coordinates. This will be useful for collision detection.
|
||||
* [x] Create a dummy player character sprite, probably just a
|
||||
`render.Circle()`. In **Play Mode** run collision checks and gravity on
|
||||
the player sprite.
|
||||
* [x] Create the concept of the Doodad and make the player character
|
||||
implement one.
|
||||
* [x] Get basic movement and collision working. With a cleanup this can
|
||||
make a workable **ALPHA RELEASE**
|
||||
* [x] Ability to move laterally along the ground.
|
||||
* [x] Ability to walk up reasonable size slopes but be stopped when
|
||||
running against a steeper wall.
|
||||
* [x] Basic gravity
|
||||
Experimental:
|
||||
|
||||
## UI Overhaul
|
||||
* `unleash the beast`
|
||||
- Removes the 60 FPS frame rate lock, allowing the game to run as quickly
|
||||
as your hardware permits.
|
||||
* `don't edit and drive`
|
||||
- Allows editing the level _while_ you're playing it: you can click and drag
|
||||
new pixels with the freehand pencil tool.
|
||||
* `scroll scroll scroll your boat`
|
||||
- Enables Editor Mode scrolling (with the arrow keys) while playing a level.
|
||||
The player character must always remain on screen though so you can't
|
||||
scroll too far away.
|
||||
|
||||
* [x] Create a user interface toolkit which will be TREMENDOUSLY helpful
|
||||
for the rest of this program.
|
||||
* [x] Labels
|
||||
* [x] Buttons (text only is OK)
|
||||
* [x] Buttons wrap their Label and dynamically compute their size based
|
||||
on how wide the label will render, plus padding and border.
|
||||
* [x] Border colors and widths and paddings are all configurable.
|
||||
* [x] Buttons should interact with the cursor and be hoverable and
|
||||
clickable.
|
||||
* [x] UI Manager that will keep track of buttons to know when the mouse
|
||||
is interacting with them.
|
||||
* [x] Frames
|
||||
* Like Buttons, can have border (raised, sunken or solid), padding and
|
||||
background color.
|
||||
* [x] Should be able to size themselves dynamically based on child widgets.
|
||||
* [x] Windows (fixed, non-draggable is OK)
|
||||
* [x] Title bar with label
|
||||
* [x] Window body implements a Frame that contains child widgets.
|
||||
* [x] Window can resize itself dynamically based on the Frame.
|
||||
* [x] Create a "Main Menu" scene with buttons to enter a new Edit Mode,
|
||||
play an existing map from disk, etc.
|
||||
* [x] Add user interface Frames or Windows to the Edit Mode.
|
||||
* [x] A toolbar of buttons (New, Save, Open, Play) can be drawn at the top
|
||||
before the UI toolkit gains a proper MenuBar widget.
|
||||
* [x] Expand the Palette support in levels for solid vs. transparent, fire,
|
||||
etc. with UI toolbar to choose palettes.
|
||||
* [x] Add a "Canvas" widget that will hold level drawing data and abstract it
|
||||
out such that the Canvas can have a constrained width and height, position,
|
||||
and "scrollable" viewport area that determines rendered pixels. Will be VERY
|
||||
useful for Doodads and working on levels smaller than your window size (like
|
||||
doodads).
|
||||
Unsupported shell commands (here be dragons):
|
||||
|
||||
Lesser important UI features that can come at any later time:
|
||||
* `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.
|
||||
|
||||
* [ ] MenuBar widget with drop-down menu support.
|
||||
* [x] Checkbox and Radiobox widgets.
|
||||
* [ ] Text Entry widgets (in the meantime use the Developer Shell to prompt for
|
||||
text input questions)
|
||||
## Environment Variables
|
||||
|
||||
## Doodad Editor
|
||||
|
||||
* [ ] The Edit Mode should support creating drawings for Doodads.
|
||||
* [ ] It should know whether you're drawing a Map or a Doodad as some
|
||||
behaviors may need to be different between the two.
|
||||
* [ ] Compress the coordinates down toward `(0,0)` when saving a Doodad,
|
||||
by finding the toppest, leftest point and making that `(0,0)` and adjusting
|
||||
the rest accordingly. This will help trim down Doodads into the smallest
|
||||
possible space for easy collision detection.
|
||||
* [ ] Add a UX to edit multiple frames for a Doodad.
|
||||
* [ ] Edit Mode should be able to fully save the drawings and frames, and an
|
||||
external CLI tool can install the JavaScript into them.
|
||||
* [ ] Possible UX to toggle Doodad options, like its collision rules and
|
||||
whether the Doodad is continued to be "mobile" (i.e. doors and buttons won't
|
||||
move, but items and enemies may be able to; and non-mobile Doodads don't
|
||||
need to collision check against level geometry).
|
||||
* [ ] Edit Mode should have a Doodad Palette (Frame or Window) to drag
|
||||
Doodads into the map.
|
||||
* [ ] ???
|
||||
|
||||
# Building
|
||||
|
||||
Fedora dependencies:
|
||||
To enable certain debug features or customize some aspects of the game,
|
||||
run it with environment variables like the following:
|
||||
|
||||
```bash
|
||||
$ sudo dnf install SDL2-devel SDL2_ttf-devel
|
||||
# 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
|
||||
```
|
||||
|
||||
## Fonts
|
||||
Supported variables include:
|
||||
|
||||
The `fonts/` folder is git-ignored. The app currently uses font files here
|
||||
named:
|
||||
* `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).
|
||||
|
||||
* `DejaVuSans.ttf` for sans-serif font.
|
||||
* `DejaVuSans-Bold.ttf` for bold sans-serif font.
|
||||
* `DejaVuSansMono.ttf` for monospace font.
|
||||
# Author
|
||||
|
||||
These are the open source **DejaVu Sans [Mono]** fonts, so copy them in from
|
||||
your `/usr/share/fonts/dejavu` folder or provide alternative fonts.
|
||||
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/>.
|
104
TODO.md
Normal file
|
@ -0,0 +1,104 @@
|
|||
# TODO
|
||||
|
||||
## Alpha Launch Minimum Checklist
|
||||
|
||||
- [ ] Open Source Licenses
|
||||
- [x] Doodad Scripts: an "end level" function for a level goalpost.
|
||||
|
||||
**Blocker Bugs:**
|
||||
|
||||
- [ ] Sometimes the red Azulians don't interact with other doodads
|
||||
properly, but sometimes they do. (i.e. they phase thru doors, don't
|
||||
interact with buttons or keys).
|
||||
|
||||
**UI Cleanup:**
|
||||
|
||||
- Doodads Palette:
|
||||
- [x] Hide some doodads like the player character.
|
||||
- [x] Pagination or scrolling UI for long lists of doodads.
|
||||
|
||||
**Nice to haves:**
|
||||
|
||||
## Release Launch Checklist
|
||||
|
||||
**Features:**
|
||||
|
||||
- [ ] Single-player "campaign mode" of built-in levels.
|
||||
- campaign.json file format configuring the level order
|
||||
- [ ] Level Editor Improvements
|
||||
- [x] Undo/Redo Function
|
||||
- [x] Lines and Boxes
|
||||
- [ ] Eraser Tool
|
||||
- [ ] Brush size and/or shape
|
||||
- [ ] Doodad CLI Tool Features
|
||||
- [x] `doodad show` to display information about a level or doodad.
|
||||
- [ ] `doodad init` or some such to generate a default JS script.
|
||||
- [x] Options to toggle various states (hidden, hasInventory?)
|
||||
|
||||
**Shareware Version:**
|
||||
|
||||
- [x] Can't draw or edit doodads.
|
||||
- [ ] Can only create Bounded maps, not infinite ones.
|
||||
- [ ] Can play custom maps but only ones using built-in doodads.
|
||||
- [ ] Can not place custom doodads in maps.
|
||||
|
||||
**Built-in Doodads:**
|
||||
|
||||
- [x] Buttons
|
||||
- [x] Press Button
|
||||
- [x] Sticky Button
|
||||
- [x] Switches (4 varieties)
|
||||
- [x] Doors
|
||||
- [x] Locked Doors and Keys
|
||||
- [x] Electric Doors
|
||||
- [x] Trapdoors (all 4 directions)
|
||||
|
||||
## Doodad Ideas
|
||||
|
||||
In addition to those listed above:
|
||||
|
||||
- [ ] Crumbly floor: Tomb Raider inspired cracked stone floor that
|
||||
crumbles under the player a moment after being touched.
|
||||
- [ ] Firepit: decorative, painful
|
||||
- [ ] Gravity Boots: flip player's gravity upside down.
|
||||
- [ ] Warp Doors that lead to other linked maps.
|
||||
- For campaign levels only. If used in a normal player level, acts
|
||||
as a level goal and ends the level.
|
||||
- Doodads "Warp Door A" through "Warp Door D"
|
||||
- The campaign.json would link levels together.
|
||||
- [ ] Conveyor Belt
|
||||
|
||||
## New Ideas
|
||||
|
||||
- [ ] New Doodad struct fields:
|
||||
- [ ] `Hidden bool`: skip showing this doodad in the palette UI.
|
||||
- [ ] `HasInventory bool`: for player characters and maybe thieves. This way
|
||||
keys only get picked up by player characters and not "any doodad that
|
||||
touches them"
|
||||
- [ ] ``
|
||||
|
||||
## Path to Multiplayer
|
||||
|
||||
* Add a Player abstraction between events and player characters.
|
||||
* Keyboard keys would update PlayerOne's state with actions (move left, right, jump, etc)
|
||||
* Possible to have multiple local players (i.e. bound to different keyboard keys, bound to joypads, etc.)
|
||||
* A NetworkPlayer provides a Player's inputs from over a network.
|
||||
* Client/server negotiation, protocol
|
||||
* Client can request chunks from server for local rendering.
|
||||
* Players send inputs over network sockets.
|
||||
|
||||
## Far Off Ideas
|
||||
|
||||
### Level Styles (Top-down vs Side Scrolling)
|
||||
|
||||
It might be cool to support multiple "styles" of level, apart from the current
|
||||
2D platforming-with-gravity style.
|
||||
|
||||
For example a top-down perspective level would let the player freely walk in
|
||||
both axes and would probably have a full set of unique Doodads drawn in that
|
||||
perspective.
|
||||
|
||||
Migration path: a GameStyle enum would be added to Levels and Doodads as an
|
||||
integer type, default 0 is the current 2D platformer style, 1 for top-down,
|
||||
etc. -- so existing levels and doodads would default to 0 on upgrade and new
|
||||
levels/doodads would use the new value.
|
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
|
||||
}
|
16
assets/campaigns/tutorial.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"version": 1,
|
||||
"title": "Tutorial Levels",
|
||||
"author": "kirsle",
|
||||
"levels": [
|
||||
{
|
||||
"filename": "example1.level",
|
||||
},
|
||||
{
|
||||
"filename": "example2.level",
|
||||
},
|
||||
{
|
||||
"filename": "example3.level",
|
||||
}
|
||||
]
|
||||
}
|
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;
|
||||
})
|
||||
}
|
BIN
assets/sprites/actor-tool.png
Normal file
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 |
BIN
assets/sprites/ellipse-tool.png
Normal file
After Width: | Height: | Size: 662 B |
BIN
assets/sprites/eraser-tool.png
Normal file
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 |
BIN
assets/sprites/line-tool.png
Normal file
After Width: | Height: | Size: 619 B |
BIN
assets/sprites/link-tool.png
Normal file
After Width: | Height: | Size: 665 B |
BIN
assets/sprites/new-button.png
Normal file
After Width: | Height: | Size: 709 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 |
BIN
assets/sprites/pencil-tool.png
Normal file
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 |
BIN
assets/sprites/rect-tool.png
Normal file
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/blueprint.png
Normal file
After Width: | Height: | Size: 8.9 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/legal.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
assets/wallpapers/notebook.png
Normal file
After Width: | Height: | Size: 4.3 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 |
BIN
assets/wallpapers/white.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
assets/wallpapers/yellow-parchment.png
Normal file
After Width: | Height: | Size: 84 KiB |
|
@ -1,10 +0,0 @@
|
|||
package balance
|
||||
|
||||
// Numbers.
|
||||
var (
|
||||
// Speed to scroll a canvas with arrow keys in Edit Mode.
|
||||
CanvasScrollSpeed int32 = 8
|
||||
|
||||
// Default chunk size for canvases.
|
||||
ChunkSize = 1000
|
||||
)
|
|
@ -1,39 +0,0 @@
|
|||
package balance
|
||||
|
||||
import (
|
||||
"git.kirsle.net/apps/doodle/render"
|
||||
"git.kirsle.net/apps/doodle/ui"
|
||||
)
|
||||
|
||||
// Theme and appearance variables.
|
||||
var (
|
||||
// Window and panel styles.
|
||||
TitleConfig = ui.Config{
|
||||
Background: render.MustHexColor("#FF9900"),
|
||||
OutlineSize: 1,
|
||||
OutlineColor: render.Black,
|
||||
}
|
||||
TitleFont = render.Text{
|
||||
FontFilename: "./fonts/DejaVuSans-Bold.ttf",
|
||||
Size: 12,
|
||||
Padding: 4,
|
||||
Color: render.White,
|
||||
Stroke: render.Red,
|
||||
}
|
||||
WindowBackground = render.MustHexColor("#cdb689")
|
||||
WindowBorder = render.Grey
|
||||
|
||||
// Menu bar styles.
|
||||
MenuBackground = render.Black
|
||||
MenuFont = render.Text{
|
||||
Size: 12,
|
||||
PadX: 4,
|
||||
}
|
||||
|
||||
// StatusFont is the font for the status bar.
|
||||
StatusFont = render.Text{
|
||||
Size: 12,
|
||||
Padding: 4,
|
||||
Color: render.Black,
|
||||
}
|
||||
)
|
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()
|
267
cmd/doodad/README.md
Normal file
|
@ -0,0 +1,267 @@
|
|||
# doodad.exe
|
||||
|
||||
The `doodad` tool is a command line interface for interacting with Levels and Doodad files, collectively referred to as "Doodle drawings" or just "drawings" for short. It provides many useful features for custom content creators that are not available in the game's user interface.
|
||||
|
||||
- [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)
|
||||
|
||||
# Quick Start
|
||||
|
||||
## Creating a custom doodad
|
||||
|
||||
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:
|
||||
|
||||
```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
|
||||
# Export a full screenshot of your level
|
||||
$ doodad convert mymap.level screenshot.png
|
||||
|
||||
# Create a new level based from a PNG image.
|
||||
$ doodad convert scanned-drawing.png new-level.level
|
||||
|
||||
# Create a doodad from a series of PNG images as its layers.
|
||||
$ doodad convert -t "My Button" up.png down.png my-button.doodad
|
||||
|
||||
# Create a new doodad based from a BMP image, and in this image the chroma
|
||||
# color (transparent) is #FF00FF instead of white as default.
|
||||
$ doodad convert --key '#FF00FF' button.png button.doodad
|
||||
```
|
||||
|
||||
Supported image types to convert to or from:
|
||||
|
||||
* PNG (8-bit or 24-bit, with transparent pixels or chroma)
|
||||
* BMP (bitmap image with chroma key)
|
||||
|
||||
The chrome key defaults to white (`#FFFFFF`), so pixels of that color are treated as transparent and ignored. For PNG images, if a pixel is fully transparent (alpha channel 0%) it will also be skipped. So the easiest format to use are PNG images with transparent pixels.
|
||||
|
||||
When converting an image into a Level or Doodad, the unique colors identified in the drawing are extracted to create the **Palette**. You will need to later edit the palette to assign meaning to the colors (giving them names or level properties). In particular, when importing a Level you'd want to mark which colors are solid ground.
|
||||
|
||||
## $ `doodad show`: Get information about a level or doodad
|
||||
|
||||
The `doodad show` command can return metadata and debug the contents of a Level or Doodad file.
|
||||
|
||||
```
|
||||
% doodad show Example.level
|
||||
===== Level: Example.level =====
|
||||
Headers:
|
||||
File format: zipfile
|
||||
File version: 1
|
||||
Game version: 0.14.0
|
||||
Level UUID: d136fef3-1a3a-453c-b616-05aca8dd6840
|
||||
Level title: Lesson 1: Controls
|
||||
Author: Noah P
|
||||
Password:
|
||||
Locked: false
|
||||
|
||||
Game Rules:
|
||||
Difficulty: Normal (0)
|
||||
Survival: false
|
||||
|
||||
Palette:
|
||||
- Swatch name: solid
|
||||
Attributes: solid
|
||||
Color: #777777
|
||||
- Swatch name: decoration
|
||||
Attributes: none
|
||||
Color: #ff66ff
|
||||
- Swatch name: fire
|
||||
Attributes: fire,water
|
||||
Color: #ff0000
|
||||
- Swatch name: semisolid
|
||||
Attributes: semi-solid
|
||||
Color: #cccccc
|
||||
|
||||
Level Settings:
|
||||
Page type: Bounded
|
||||
Max size: 2550x3300
|
||||
Wallpaper: notebook.png
|
||||
|
||||
Attached Files:
|
||||
assets/screenshots/large.png: 63377 bytes
|
||||
assets/screenshots/medium.png: 25807 bytes
|
||||
assets/screenshots/small.png: 6837 bytes
|
||||
assets/screenshots/tiny.png: 7414 bytes
|
||||
|
||||
Actors:
|
||||
Level contains 16 actors
|
||||
Use -actors or -verbose to serialize Actors
|
||||
|
||||
Chunks:
|
||||
Pixels Per Chunk: 128^2
|
||||
Number Generated: 102
|
||||
Coordinate Range: (0,0) ... (1919,2047)
|
||||
World Dimensions: 1919x2047
|
||||
Use -chunks or -verbose to serialize Chunks
|
||||
```
|
||||
|
||||
Or for a doodad:
|
||||
|
||||
```
|
||||
% doodad show example.doodad
|
||||
===== Doodad: example.doodad =====
|
||||
Headers:
|
||||
File format: zipfile
|
||||
File version: 1
|
||||
Game version: 0.14.1
|
||||
Doodad title: Fire Region
|
||||
Author: Noah
|
||||
Dimensions: Rect<0,0,128,128>
|
||||
Hitbox: Rect<0,0,128,128>
|
||||
Locked: true
|
||||
Hidden: false
|
||||
Script size: 378 bytes
|
||||
|
||||
Tags:
|
||||
category: technical
|
||||
|
||||
Options:
|
||||
str name = fire
|
||||
|
||||
Palette:
|
||||
- Swatch name: Color<#8a2b2b+ff>
|
||||
Attributes: solid
|
||||
Color: #8a2b2b
|
||||
|
||||
Layer 0: fire-128
|
||||
Chunks:
|
||||
Pixels Per Chunk: 128^2
|
||||
Number Generated: 1
|
||||
Coordinate Range: (0,0) ... (127,127)
|
||||
World Dimensions: 127x127
|
||||
Use -chunks or -verbose to serialize Chunks
|
||||
```
|
||||
|
||||
## Editing Level or Doodad Properties
|
||||
|
||||
The `edit-doodad` and `edit-level` subcommands allow setting properties on your custom files programmatically.
|
||||
|
||||
Properties you can set for both file types include:
|
||||
|
||||
* Metadata like the Title and Author name
|
||||
* Lock your drawing from being edited in-game
|
||||
|
||||
For Doodads, you can set their Tags and Options, Hitbox, etc. - most of the useful settings are supported, as the game's built-in doodads use this program!
|
||||
|
||||
# Where to Find It
|
||||
|
||||
The `doodad` tool ships with the official releases of the game, and may be found in one of the following places:
|
||||
|
||||
* **Windows:** doodad.exe should be in the same place as sketchymaze.exe (e.g. in the ZIP file)
|
||||
* **Mac OS:** it is available inside the "Sketchy Maze.app" bundle, in the "Contents/MacOS" folder next to the `sketchymaze` binary.
|
||||
|
||||
Invoke it from a terminal like:
|
||||
|
||||
```bash
|
||||
alias doodad="/Applications/Sketchy Maze.app/Contents/MacOS/doodad"
|
||||
doodad -h
|
||||
```
|
||||
* **Linux:** the doodad binary should be in the same place as the sketchymaze program itself:
|
||||
* In the .tar.gz file
|
||||
* In /opt/sketchymaze if installed by an .rpm or .deb package
|
||||
* AppImage: `./SketchyMaze.AppImage doodad` will invoke the doodad command.
|
||||
* Flatpak: `flatpak run com.sketchymaze.Doodle doodad` will invoke the doodad command. Invoke it from a terminal like:
|
||||
|
||||
```bash
|
||||
alias doodad="flatpak run com.sketchymaze.Doodle doodad"
|
||||
doodad -h
|
||||
```
|
8
cmd/doodad/commands/consts.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
package commands
|
||||
|
||||
const (
|
||||
extLevel = ".level"
|
||||
extDoodad = ".doodad"
|
||||
extPNG = ".png"
|
||||
extBMP = ".bmp"
|
||||
)
|
364
cmd/doodad/commands/convert.go
Normal file
|
@ -0,0 +1,364 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"image/png"
|
||||
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/branding"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/native"
|
||||
"git.kirsle.net/go/render"
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/image/bmp"
|
||||
)
|
||||
|
||||
// Convert between image files (png or bitmap) and Doodle drawing files (levels
|
||||
// and doodads)
|
||||
var Convert *cli.Command
|
||||
|
||||
func init() {
|
||||
Convert = &cli.Command{
|
||||
Name: "convert",
|
||||
Usage: "convert between images and Doodle drawing files",
|
||||
ArgsUsage: "<input> <output>",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "key",
|
||||
Usage: "chroma key color for transparency on input image files, e.g. #ffffff",
|
||||
Value: "",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "title",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "set the title of the level or doodad being created",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "palette",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "use a palette JSON to define color swatch properties",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.NArg() < 2 {
|
||||
return cli.Exit(
|
||||
"Usage: doodad convert <input.png...> <output.doodad>\n"+
|
||||
" Image file types: png, bmp\n"+
|
||||
" Drawing file types: level, doodad",
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
||||
// Parse the chroma key.
|
||||
var chroma = render.Invisible
|
||||
if key := c.String("key"); key != "" {
|
||||
color, err := render.HexColor(c.String("key"))
|
||||
if err != nil {
|
||||
return cli.Exit(
|
||||
"Chrome key not a valid color: "+err.Error(),
|
||||
1,
|
||||
)
|
||||
}
|
||||
chroma = color
|
||||
}
|
||||
|
||||
args := c.Args().Slice()
|
||||
var (
|
||||
inputFiles = args[:len(args)-1]
|
||||
inputType = strings.ToLower(filepath.Ext(inputFiles[0]))
|
||||
outputFile = args[len(args)-1]
|
||||
outputType = strings.ToLower(filepath.Ext(outputFile))
|
||||
)
|
||||
|
||||
if inputType == extPNG || inputType == extBMP {
|
||||
if outputType == extLevel || outputType == extDoodad {
|
||||
if err := imageToDrawing(c, chroma, inputFiles, outputFile); err != nil {
|
||||
return cli.Exit(err.Error(), 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return cli.Exit("Image inputs can only output to Doodle drawings", 1)
|
||||
} else if inputType == extLevel || inputType == extDoodad {
|
||||
if outputType == extPNG || outputType == extBMP {
|
||||
if err := drawingToImage(c, chroma, inputFiles, outputFile); err != nil {
|
||||
return cli.Exit(err.Error(), 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return cli.Exit("Doodle drawing inputs can only output to image files", 1)
|
||||
}
|
||||
|
||||
return cli.Exit("File types must be: png, bmp, level, doodad", 1)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, outputFile string) error {
|
||||
// Read the source images. Ensure they all have the same boundaries.
|
||||
var (
|
||||
imageBounds image.Point
|
||||
width int // dimensions of the incoming image
|
||||
height int
|
||||
images []image.Image
|
||||
)
|
||||
|
||||
for i, filename := range inputFiles {
|
||||
reader, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return cli.Exit(err.Error(), 1)
|
||||
}
|
||||
|
||||
img, format, err := image.Decode(reader)
|
||||
log.Info("Parsed image %d of %d. Format: %s", i+1, len(inputFiles), format)
|
||||
if err != nil {
|
||||
return cli.Exit(err.Error(), 1)
|
||||
}
|
||||
|
||||
// Get the bounding box information of the source image.
|
||||
var (
|
||||
bounds = img.Bounds()
|
||||
imageSize = bounds.Size()
|
||||
)
|
||||
|
||||
// Validate all images are the same size.
|
||||
if i == 0 {
|
||||
imageBounds = imageSize
|
||||
width = imageSize.X
|
||||
height = imageSize.Y
|
||||
} else if imageSize != imageBounds {
|
||||
return cli.Exit("your source images are not all the same dimensions", 1)
|
||||
}
|
||||
|
||||
images = append(images, img)
|
||||
}
|
||||
|
||||
// Initialize the palette from a JSON file?
|
||||
var palette *level.Palette
|
||||
if paletteFile := c.String("palette"); paletteFile != "" {
|
||||
log.Info("Loading initial palette from file: %s", paletteFile)
|
||||
if p, err := level.LoadPaletteFromFile(paletteFile); err != nil {
|
||||
return err
|
||||
} else {
|
||||
palette = p
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to translate image filenames into layer names.
|
||||
toLayerName := func(filename string) string {
|
||||
ext := filepath.Ext(filename)
|
||||
return strings.TrimSuffix(filepath.Base(filename), ext)
|
||||
}
|
||||
|
||||
// Generate the output drawing file.
|
||||
switch strings.ToLower(filepath.Ext(outputFile)) {
|
||||
case extDoodad:
|
||||
doodad := doodads.New(width, height)
|
||||
doodad.GameVersion = branding.Version
|
||||
doodad.Title = c.String("title")
|
||||
if doodad.Title == "" {
|
||||
doodad.Title = "Converted Doodad"
|
||||
}
|
||||
doodad.Author = native.DefaultAuthor
|
||||
|
||||
// Write the first layer and gather its palette.
|
||||
log.Info("Converting first layer to drawing and getting the palette")
|
||||
var chunkSize = doodad.ChunkSize8()
|
||||
log.Info("Output is a Doodad file (%dx%d): %s", width, height, outputFile)
|
||||
palette, layer0 := imageToChunker(images[0], chroma, palette, chunkSize)
|
||||
doodad.Palette = palette
|
||||
doodad.Layers[0].Chunker = layer0
|
||||
doodad.Layers[0].Name = toLayerName(inputFiles[0])
|
||||
|
||||
// Write any additional layers.
|
||||
if len(images) > 1 {
|
||||
for i := 1; i < len(images); i++ {
|
||||
img := images[i]
|
||||
log.Info("Converting extra layer %d", i)
|
||||
_, chunker := imageToChunker(img, chroma, palette, chunkSize)
|
||||
doodad.AddLayer(toLayerName(inputFiles[i]), chunker)
|
||||
}
|
||||
}
|
||||
|
||||
err := doodad.WriteJSON(outputFile)
|
||||
if err != nil {
|
||||
return cli.Exit(err.Error(), 1)
|
||||
}
|
||||
case extLevel:
|
||||
log.Info("Output is a Level file: %s", outputFile)
|
||||
if len(images) > 1 {
|
||||
log.Warn("Notice: levels only support one layer so only your first image will be used")
|
||||
}
|
||||
|
||||
lvl := level.New()
|
||||
lvl.GameVersion = branding.Version
|
||||
lvl.MaxWidth = int64(width)
|
||||
lvl.MaxHeight = int64(height)
|
||||
lvl.PageType = level.Bounded
|
||||
lvl.Title = c.String("title")
|
||||
if lvl.Title == "" {
|
||||
lvl.Title = "Converted Level"
|
||||
}
|
||||
lvl.Author = native.DefaultAuthor
|
||||
palette, chunker := imageToChunker(images[0], chroma, palette, lvl.Chunker.Size)
|
||||
lvl.Palette = palette
|
||||
lvl.Chunker = chunker
|
||||
|
||||
err := lvl.WriteJSON(outputFile)
|
||||
if err != nil {
|
||||
return cli.Exit(err.Error(), 1)
|
||||
}
|
||||
default:
|
||||
return cli.Exit("invalid output file: not a Doodle drawing", 1)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func drawingToImage(c *cli.Context, chroma render.Color, inputFiles []string, outputFile string) error {
|
||||
var palette *level.Palette
|
||||
var chunker *level.Chunker
|
||||
inputFile := inputFiles[0]
|
||||
|
||||
switch strings.ToLower(filepath.Ext(inputFile)) {
|
||||
case extLevel:
|
||||
log.Info("Load Level: %s", inputFile)
|
||||
m, err := level.LoadJSON(inputFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("load level: %s", err.Error())
|
||||
}
|
||||
chunker = m.Chunker
|
||||
palette = m.Palette
|
||||
case extDoodad:
|
||||
log.Info("Load Doodad: %s", inputFile)
|
||||
d, err := doodads.LoadJSON(inputFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("load doodad: %s", err.Error())
|
||||
}
|
||||
chunker = d.Layers[0].Chunker // TODO: layers
|
||||
palette = d.Palette
|
||||
default:
|
||||
return fmt.Errorf("%s: not a level or doodad file", inputFile)
|
||||
}
|
||||
|
||||
_ = chunker
|
||||
_ = palette
|
||||
|
||||
// Create an image for the full world size.
|
||||
canvas := chunker.WorldSizePositive()
|
||||
img := image.NewRGBA(image.Rectangle{
|
||||
Min: image.Point{
|
||||
X: int(canvas.X),
|
||||
Y: int(canvas.Y),
|
||||
},
|
||||
Max: image.Point{
|
||||
X: int(canvas.W),
|
||||
Y: int(canvas.H),
|
||||
},
|
||||
})
|
||||
|
||||
// Blank out the pixels.
|
||||
for x := 0; x < img.Bounds().Max.X; x++ {
|
||||
for y := 0; y < img.Bounds().Max.Y; y++ {
|
||||
img.Set(x, y, render.White.ToColor())
|
||||
}
|
||||
}
|
||||
|
||||
// Transcode all pixels onto it.
|
||||
for px := range chunker.IterPixels() {
|
||||
img.Set(int(px.X), int(px.Y), px.Swatch.Color.ToColor())
|
||||
}
|
||||
|
||||
// Write the output file.
|
||||
switch strings.ToLower(filepath.Ext(outputFile)) {
|
||||
case ".png":
|
||||
fh, err := os.Create(outputFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fh.Close()
|
||||
return png.Encode(fh, img)
|
||||
case ".bmp":
|
||||
fh, err := os.Create(outputFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fh.Close()
|
||||
return bmp.Encode(fh, img)
|
||||
}
|
||||
|
||||
return errors.New("not valid output image type")
|
||||
}
|
||||
|
||||
// imageToChunker implements a generic transcoding of an image.Image to a Chunker
|
||||
// and returns the Palette, ready to plug into a Doodad or Level drawing.
|
||||
//
|
||||
// img: input image like a PNG
|
||||
// chroma: transparent color
|
||||
// palette: your palette so far (new distinct colors are added)
|
||||
func imageToChunker(img image.Image, chroma render.Color, palette *level.Palette, chunkSize uint8) (*level.Palette, *level.Chunker) {
|
||||
var (
|
||||
chunker = level.NewChunker(chunkSize)
|
||||
bounds = img.Bounds()
|
||||
)
|
||||
|
||||
if palette == nil {
|
||||
palette = level.NewPalette()
|
||||
}
|
||||
|
||||
// Cache a palette of unique colors as we go.
|
||||
var uniqueColor = map[string]*level.Swatch{}
|
||||
var newColors = map[string]*level.Swatch{} // new ones discovered this time
|
||||
for _, swatch := range palette.Swatches {
|
||||
uniqueColor[swatch.Color.String()] = swatch
|
||||
}
|
||||
|
||||
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
||||
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
||||
px := img.At(x, y)
|
||||
color := render.FromColor(px)
|
||||
if color == chroma || color.Transparent() { // invisible pixel
|
||||
continue
|
||||
}
|
||||
|
||||
// New color for the palette?
|
||||
swatch, ok := uniqueColor[color.String()]
|
||||
if !ok {
|
||||
log.Info("New color: %s", color)
|
||||
swatch = &level.Swatch{
|
||||
Name: color.String(),
|
||||
Color: color,
|
||||
}
|
||||
uniqueColor[color.String()] = swatch
|
||||
newColors[color.String()] = swatch
|
||||
}
|
||||
|
||||
chunker.Set(render.NewPoint(x, y), swatch)
|
||||
}
|
||||
}
|
||||
|
||||
// Order the palette.
|
||||
var sortedColors []string
|
||||
for k := range uniqueColor {
|
||||
sortedColors = append(sortedColors, k)
|
||||
}
|
||||
sort.Strings(sortedColors)
|
||||
for _, hex := range sortedColors {
|
||||
if _, ok := newColors[hex]; ok {
|
||||
if err := palette.AddSwatch(uniqueColor[hex]); err != nil {
|
||||
log.Error("Could not add more colors to the palette: %s", err)
|
||||
panic(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
palette.Inflate()
|
||||
|
||||
return palette, chunker
|
||||
}
|
242
cmd/doodad/commands/edit_doodad.go
Normal file
|
@ -0,0 +1,242 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
||||
"git.kirsle.net/go/render"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// EditDoodad allows writing doodad metadata.
|
||||
var EditDoodad *cli.Command
|
||||
|
||||
func init() {
|
||||
EditDoodad = &cli.Command{
|
||||
Name: "edit-doodad",
|
||||
Usage: "update metadata for a Doodad file",
|
||||
ArgsUsage: "<filename.doodad>",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "quiet",
|
||||
Aliases: []string{"q"},
|
||||
Usage: "limit output (don't show doodad data at the end)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "title",
|
||||
Usage: "set the doodad title",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "author",
|
||||
Usage: "set the doodad author",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "hitbox",
|
||||
Usage: "set the doodad hitbox (X,Y,W,H or W,H format)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "tag",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "set a key/value tag on the doodad, in key=value format. Empty value deletes the tag.",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "option",
|
||||
Aliases: []string{"o"},
|
||||
Usage: "set an option on the doodad, in key=type=default format, e.g. active=bool=true, speed=int=10, name=str. Value types are bool, str, int.",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "hide",
|
||||
Usage: "Hide the doodad from the palette",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "unhide",
|
||||
Usage: "Unhide the doodad from the palette",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "lock",
|
||||
Usage: "write-lock the level file",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "unlock",
|
||||
Usage: "remove the write-lock on the level file",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "touch",
|
||||
Usage: "simply load and re-save the doodad, to migrate it to a zipfile",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.NArg() < 1 {
|
||||
return cli.Exit(
|
||||
"Usage: doodad edit-doodad <filename.doodad>",
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
||||
var filenames = c.Args().Slice()
|
||||
for _, filename := range filenames {
|
||||
if err := editDoodad(c, filename); err != nil {
|
||||
log.Error("%s: %s", filename, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func editDoodad(c *cli.Context, filename string) error {
|
||||
var modified bool
|
||||
|
||||
dd, err := doodads.LoadJSON(filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to load %s: %s", filename, err)
|
||||
}
|
||||
|
||||
log.Info("Edit Doodad: %s", filename)
|
||||
|
||||
/***************************
|
||||
* Update level properties *
|
||||
***************************/
|
||||
|
||||
if c.Bool("touch") {
|
||||
log.Info("Just touching and resaving the file")
|
||||
modified = true
|
||||
}
|
||||
|
||||
if c.String("title") != "" {
|
||||
dd.Title = c.String("title")
|
||||
log.Info("Set title: %s", dd.Title)
|
||||
modified = true
|
||||
}
|
||||
|
||||
if c.String("author") != "" {
|
||||
dd.Author = c.String("author")
|
||||
log.Info("Set author: %s", dd.Author)
|
||||
modified = true
|
||||
}
|
||||
|
||||
if c.String("hitbox") != "" {
|
||||
// Setting a hitbox, parse it out.
|
||||
parts := strings.Split(c.String("hitbox"), ",")
|
||||
var ints []int
|
||||
for _, part := range parts {
|
||||
a, err := strconv.Atoi(strings.TrimSpace(part))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ints = append(ints, a)
|
||||
}
|
||||
|
||||
if len(ints) == 2 {
|
||||
dd.Hitbox = render.NewRect(ints[0], ints[1])
|
||||
modified = true
|
||||
} else if len(ints) == 4 {
|
||||
dd.Hitbox = render.Rect{
|
||||
X: ints[0],
|
||||
Y: ints[1],
|
||||
W: ints[2],
|
||||
H: ints[3],
|
||||
}
|
||||
modified = true
|
||||
} else {
|
||||
return cli.Exit("Hitbox should be in X,Y,W,H or just W,H format, 2 or 4 numbers.", 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Tags.
|
||||
tag := c.String("tag")
|
||||
if len(tag) > 0 {
|
||||
parts := strings.SplitN(tag, "=", 3)
|
||||
if len(parts) != 2 {
|
||||
log.Error("--tag: must be in format `key=value`. Value may be blank to delete a tag. len=%d tag=%s got=%+v", len(parts), tag, parts)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var (
|
||||
key = parts[0]
|
||||
value = parts[1]
|
||||
)
|
||||
if value == "" {
|
||||
log.Debug("Delete tag '%s'", key)
|
||||
delete(dd.Tags, key)
|
||||
} else {
|
||||
log.Debug("Set tag '%s' to '%s'", key, value)
|
||||
dd.Tags[key] = value
|
||||
}
|
||||
|
||||
modified = true
|
||||
}
|
||||
|
||||
// Options.
|
||||
opt := c.String("option")
|
||||
if len(opt) > 0 {
|
||||
parts := strings.SplitN(opt, "=", 3)
|
||||
if len(parts) < 2 {
|
||||
log.Error("--option: must be in format `name=type` or `name=type=value`")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var (
|
||||
name = parts[0]
|
||||
dataType = parts[1]
|
||||
value string
|
||||
)
|
||||
if len(parts) == 3 {
|
||||
value = parts[2]
|
||||
}
|
||||
|
||||
// Validate the data types.
|
||||
if dataType != "bool" && dataType != "str" && dataType != "int" {
|
||||
log.Error("--option: invalid type, should be a bool, str or int")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
value = dd.SetOption(name, dataType, value)
|
||||
log.Info("Set option %s (%s) = %s", name, dataType, value)
|
||||
|
||||
modified = true
|
||||
}
|
||||
|
||||
if c.Bool("hide") {
|
||||
dd.Hidden = true
|
||||
log.Info("Marked doodad Hidden")
|
||||
modified = true
|
||||
} else if c.Bool("unhide") {
|
||||
dd.Hidden = false
|
||||
log.Info("Doodad is no longer Hidden")
|
||||
modified = true
|
||||
}
|
||||
|
||||
if c.Bool("lock") {
|
||||
dd.Locked = true
|
||||
log.Info("Write lock enabled.")
|
||||
modified = true
|
||||
} else if c.Bool("unlock") {
|
||||
dd.Locked = false
|
||||
log.Info("Write lock disabled.")
|
||||
modified = true
|
||||
}
|
||||
|
||||
/******************************
|
||||
* Save level changes to disk *
|
||||
******************************/
|
||||
|
||||
if modified {
|
||||
if err := dd.WriteJSON(filename); err != nil {
|
||||
return cli.Exit(fmt.Sprintf("Write error: %s", err), 1)
|
||||
}
|
||||
} else {
|
||||
log.Warn("Note: No changes made to level")
|
||||
}
|
||||
|
||||
if c.Bool("quiet") {
|
||||
return nil
|
||||
}
|
||||
|
||||
return showDoodad(c, filename)
|
||||
}
|
265
cmd/doodad/commands/edit_level.go
Normal file
|
@ -0,0 +1,265 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
||||
"git.kirsle.net/go/render"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// EditLevel allows writing level metadata.
|
||||
var EditLevel *cli.Command
|
||||
|
||||
func init() {
|
||||
EditLevel = &cli.Command{
|
||||
Name: "edit-level",
|
||||
Usage: "update metadata for a Level file",
|
||||
ArgsUsage: "<filename.level>",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "quiet",
|
||||
Aliases: []string{"q"},
|
||||
Usage: "limit output (don't show doodad data at the end)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "output",
|
||||
Aliases: []string{"o"},
|
||||
Usage: "write to a different output file than the input (especially for --resize)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "title",
|
||||
Usage: "set the level title",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "author",
|
||||
Usage: "set the level author",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "password",
|
||||
Usage: "set the level password",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "type",
|
||||
Usage: "set the page type. One of: Unbounded, Bounded, NoNegativeSpace, Bordered",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "max-size",
|
||||
Usage: "set the bounded level page max size (WxH format, like 2550x3300)",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "resize",
|
||||
Usage: "change the chunk size, and re-encode the whole level into chunks of the new size",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "wallpaper",
|
||||
Usage: "set the wallpaper filename",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "lock",
|
||||
Usage: "write-lock the level file",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "unlock",
|
||||
Usage: "remove the write-lock on the level file",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "remove-actor",
|
||||
Usage: "Remove all instances of the actor from the level. Value is their filename or UUID.",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "touch",
|
||||
Usage: "simply load and re-save the level, to migrate it to a zipfile",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.NArg() < 1 {
|
||||
return cli.Exit(
|
||||
"Usage: doodad edit-level <filename.level>",
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
||||
var filenames = c.Args().Slice()
|
||||
for _, filename := range filenames {
|
||||
if err := editLevel(c, filename); err != nil {
|
||||
log.Error("%s: %s", filename, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func editLevel(c *cli.Context, filename string) error {
|
||||
var modified bool
|
||||
|
||||
lvl, err := level.LoadJSON(filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to load %s: %s", filename, err)
|
||||
}
|
||||
|
||||
log.Info("File: %s", filename)
|
||||
|
||||
// Migrating it to a different chunk size?
|
||||
if c.Int("resize") > 0 {
|
||||
return rechunkLevel(c, filename, lvl)
|
||||
}
|
||||
|
||||
/***************************
|
||||
* Update level properties *
|
||||
***************************/
|
||||
|
||||
if c.Bool("touch") {
|
||||
log.Info("Just touching and resaving the file")
|
||||
modified = true
|
||||
}
|
||||
|
||||
if c.String("title") != "" {
|
||||
lvl.Title = c.String("title")
|
||||
log.Info("Set title: %s", lvl.Title)
|
||||
modified = true
|
||||
}
|
||||
|
||||
if c.String("author") != "" {
|
||||
lvl.Author = c.String("author")
|
||||
log.Info("Set author: %s", lvl.Author)
|
||||
modified = true
|
||||
}
|
||||
|
||||
if c.String("password") != "" {
|
||||
lvl.Password = c.String("password")
|
||||
log.Info("Updated level password")
|
||||
modified = true
|
||||
}
|
||||
|
||||
if c.String("max-size") != "" {
|
||||
w, h, err := render.ParseResolution(c.String("max-size"))
|
||||
if err != nil {
|
||||
log.Error("-max-size: %s", err)
|
||||
} else {
|
||||
lvl.MaxWidth = int64(w)
|
||||
lvl.MaxHeight = int64(h)
|
||||
modified = true
|
||||
}
|
||||
}
|
||||
|
||||
if c.Bool("lock") {
|
||||
lvl.Locked = true
|
||||
log.Info("Write lock enabled.")
|
||||
modified = true
|
||||
} else if c.Bool("unlock") {
|
||||
lvl.Locked = false
|
||||
log.Info("Write lock disabled.")
|
||||
modified = true
|
||||
}
|
||||
|
||||
if c.String("type") != "" {
|
||||
if pageType, ok := level.PageTypeFromString(c.String("type")); ok {
|
||||
lvl.PageType = pageType
|
||||
log.Info("Page Type set to %s", pageType)
|
||||
modified = true
|
||||
} else {
|
||||
log.Error("Invalid -type value. Should be like Unbounded, Bounded, NoNegativeSpace, Bordered")
|
||||
}
|
||||
}
|
||||
|
||||
if c.String("wallpaper") != "" {
|
||||
lvl.Wallpaper = c.String("wallpaper")
|
||||
log.Info("Set wallpaper: %s", c.String("wallpaper"))
|
||||
modified = true
|
||||
}
|
||||
|
||||
if c.String("remove-actor") != "" {
|
||||
var (
|
||||
match = c.String("remove-actor")
|
||||
removeIDs = []string{}
|
||||
)
|
||||
|
||||
for id, actor := range lvl.Actors {
|
||||
if id == match || actor.Filename == match {
|
||||
removeIDs = append(removeIDs, id)
|
||||
}
|
||||
}
|
||||
|
||||
if len(removeIDs) > 0 {
|
||||
for _, id := range removeIDs {
|
||||
delete(lvl.Actors, id)
|
||||
}
|
||||
log.Info("Removed %d instances of actor %s from the level.", len(removeIDs), match)
|
||||
modified = true
|
||||
} else {
|
||||
log.Error("Did not find any actors like %s in the level.", match)
|
||||
}
|
||||
}
|
||||
|
||||
/******************************
|
||||
* Save level changes to disk *
|
||||
******************************/
|
||||
|
||||
if modified {
|
||||
if err := lvl.WriteFile(filename); err != nil {
|
||||
return cli.Exit(fmt.Sprintf("Write error: %s", err), 1)
|
||||
}
|
||||
} else {
|
||||
log.Warn("Note: No changes made to level")
|
||||
}
|
||||
|
||||
if c.Bool("quiet") {
|
||||
return nil
|
||||
}
|
||||
|
||||
return showLevel(c, filename)
|
||||
}
|
||||
|
||||
// doodad edit-level --resize CHUNK_SIZE
|
||||
//
|
||||
// Handles the deep operation of re-copying the old level into a new level
|
||||
// at the new chunk size.
|
||||
func rechunkLevel(c *cli.Context, filename string, lvl *level.Level) error {
|
||||
var chunkSize = balance.ChunkSize
|
||||
if v := c.Int("resize"); v != 0 {
|
||||
if v > 255 {
|
||||
return errors.New("chunk size must be a uint8 <= 255")
|
||||
}
|
||||
chunkSize = uint8(v)
|
||||
}
|
||||
log.Info("Resizing the level's chunk size.")
|
||||
log.Info("Current chunk size: %d", lvl.Chunker.Size)
|
||||
log.Info("Target chunk size: %d", chunkSize)
|
||||
|
||||
if output := c.String("output"); output != "" {
|
||||
filename = output
|
||||
log.Info("Output file will be: %s", filename)
|
||||
}
|
||||
|
||||
if chunkSize == lvl.Chunker.Size {
|
||||
return errors.New("the level already has the target chunk size")
|
||||
}
|
||||
|
||||
// Keep the level's current Chunker, and set a new one.
|
||||
var oldChunker = lvl.Chunker
|
||||
lvl.Chunker = level.NewChunker(chunkSize)
|
||||
|
||||
// Iterate all the Pixels of the old chunker.
|
||||
log.Info("Copying pixels from old chunker into new chunker (this may take a while)...")
|
||||
for pixel := range oldChunker.IterPixels() {
|
||||
lvl.Chunker.Set(
|
||||
pixel.Point(),
|
||||
pixel.Swatch,
|
||||
)
|
||||
}
|
||||
|
||||
log.Info("Writing new data to filename: %s", filename)
|
||||
if err := lvl.WriteFile(filename); err != nil {
|
||||
log.Error(err.Error())
|
||||
}
|
||||
|
||||
return showLevel(c, filename)
|
||||
|
||||
return nil
|
||||
}
|
61
cmd/doodad/commands/install_script.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// InstallScript to add the script to a doodad file.
|
||||
var InstallScript *cli.Command
|
||||
|
||||
func init() {
|
||||
InstallScript = &cli.Command{
|
||||
Name: "install-script",
|
||||
Usage: "install the JavaScript source to a doodad",
|
||||
ArgsUsage: "<index.js> <filename.doodad>",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "key",
|
||||
Usage: "chroma key color for transparency on input image files",
|
||||
Value: "#ffffff",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.NArg() != 2 {
|
||||
return cli.Exit(
|
||||
"Usage: doodad install-script <script.js> <filename.doodad>",
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
||||
var (
|
||||
args = c.Args()
|
||||
scriptFile = args.Get(0)
|
||||
doodadFile = args.Get(1)
|
||||
)
|
||||
|
||||
// Read the JavaScript source.
|
||||
javascript, err := ioutil.ReadFile(scriptFile)
|
||||
if err != nil {
|
||||
return cli.Exit(err.Error(), 1)
|
||||
}
|
||||
|
||||
doodad, err := doodads.LoadJSON(doodadFile)
|
||||
if err != nil {
|
||||
return cli.Exit(
|
||||
fmt.Sprintf("Failed to read doodad file: %s", err),
|
||||
1,
|
||||
)
|
||||
}
|
||||
doodad.Script = string(javascript)
|
||||
doodad.WriteJSON(doodadFile)
|
||||
log.Info("Installed script successfully")
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
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)
|
||||
}
|
366
cmd/doodad/commands/show.go
Normal file
|
@ -0,0 +1,366 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/enum"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/level/rle"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// Show information about a Level or Doodad file.
|
||||
var Show *cli.Command
|
||||
|
||||
func init() {
|
||||
Show = &cli.Command{
|
||||
Name: "show",
|
||||
Usage: "show information about a level or doodad file",
|
||||
ArgsUsage: "<.level or .doodad>",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "actors",
|
||||
Usage: "print verbose actor data in Level files",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "chunks",
|
||||
Usage: "print verbose data about all the pixel chunks in a file",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "script",
|
||||
Usage: "print the script from a doodad file and exit",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "attachment",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "print the contents of the attached filename to terminal",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "verbose",
|
||||
Aliases: []string{"v"},
|
||||
Usage: "print verbose output (all verbose flags enabled)",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "visualize-rle",
|
||||
Usage: "visually dump RLE encoded chunks to the terminal (VERY noisy for large drawings!)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "chunk",
|
||||
Usage: "specific chunk coordinate; when debugging chunks, only show this chunk (example: 2,-1)",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.NArg() < 1 {
|
||||
return cli.Exit(
|
||||
"Usage: doodad show <.level .doodad ...>",
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
||||
filenames := c.Args().Slice()
|
||||
for _, filename := range filenames {
|
||||
switch strings.ToLower(filepath.Ext(filename)) {
|
||||
case enum.LevelExt:
|
||||
if err := showLevel(c, filename); err != nil {
|
||||
log.Error(err.Error())
|
||||
return cli.Exit("Error", 1)
|
||||
}
|
||||
case enum.DoodadExt:
|
||||
if err := showDoodad(c, filename); err != nil {
|
||||
log.Error(err.Error())
|
||||
return cli.Exit("Error", 1)
|
||||
}
|
||||
default:
|
||||
log.Error("File %s: not a level or doodad", filename)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// showLevel shows data about a level file.
|
||||
func showLevel(c *cli.Context, filename string) error {
|
||||
lvl, err := level.LoadJSON(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Are we printing an attached file?
|
||||
if filename := c.String("attachment"); filename != "" {
|
||||
if data, err := lvl.GetFile(filename); err == nil {
|
||||
fmt.Print(string(data))
|
||||
return nil
|
||||
} else {
|
||||
fmt.Printf("Couldn't get attached file '%s': %s\n", filename, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Is it a new zipfile format?
|
||||
var fileType = "json or gzip"
|
||||
if lvl.Zipfile != nil {
|
||||
fileType = "zipfile"
|
||||
}
|
||||
|
||||
fmt.Printf("===== Level: %s =====\n", filename)
|
||||
|
||||
fmt.Println("Headers:")
|
||||
fmt.Printf(" File format: %s\n", fileType)
|
||||
fmt.Printf(" File version: %d\n", lvl.Version)
|
||||
fmt.Printf(" Game version: %s\n", lvl.GameVersion)
|
||||
fmt.Printf(" Level UUID: %s\n", lvl.UUID)
|
||||
fmt.Printf(" Level title: %s\n", lvl.Title)
|
||||
fmt.Printf(" Author: %s\n", lvl.Author)
|
||||
fmt.Printf(" Password: %s\n", lvl.Password)
|
||||
fmt.Printf(" Locked: %+v\n", lvl.Locked)
|
||||
fmt.Println("")
|
||||
|
||||
fmt.Println("Game Rules:")
|
||||
fmt.Printf(" Difficulty: %s (%d)\n", lvl.GameRule.Difficulty, lvl.GameRule.Difficulty)
|
||||
fmt.Printf(" Survival: %+v\n", lvl.GameRule.Survival)
|
||||
fmt.Println("")
|
||||
|
||||
showPalette(lvl.Palette)
|
||||
|
||||
fmt.Println("Level Settings:")
|
||||
fmt.Printf(" Page type: %s\n", lvl.PageType.String())
|
||||
fmt.Printf(" Max size: %dx%d\n", lvl.MaxWidth, lvl.MaxHeight)
|
||||
fmt.Printf(" Wallpaper: %s\n", lvl.Wallpaper)
|
||||
fmt.Println("")
|
||||
|
||||
fmt.Println("Attached Files:")
|
||||
if files := lvl.ListFiles(); len(files) > 0 {
|
||||
for _, v := range files {
|
||||
data, _ := lvl.GetFile(v)
|
||||
fmt.Printf(" %s: %d bytes\n", v, len(data))
|
||||
}
|
||||
fmt.Println("")
|
||||
} else {
|
||||
fmt.Printf(" None\n\n")
|
||||
}
|
||||
|
||||
// Print the actor information.
|
||||
fmt.Println("Actors:")
|
||||
fmt.Printf(" Level contains %d actors\n", len(lvl.Actors))
|
||||
if c.Bool("actors") || c.Bool("verbose") {
|
||||
fmt.Println(" List of Actors:")
|
||||
for id, actor := range lvl.Actors {
|
||||
fmt.Printf(" - Name: %s\n", actor.Filename)
|
||||
fmt.Printf(" UUID: %s\n", id)
|
||||
fmt.Printf(" At: %s\n", actor.Point)
|
||||
if len(actor.Options) > 0 {
|
||||
var ordered = []string{}
|
||||
for name := range actor.Options {
|
||||
ordered = append(ordered, name)
|
||||
}
|
||||
sort.Strings(ordered)
|
||||
|
||||
fmt.Println(" Options:")
|
||||
for _, name := range ordered {
|
||||
val := actor.Options[name]
|
||||
fmt.Printf(" %s %s = %v\n", val.Type, val.Name, val.Value)
|
||||
}
|
||||
}
|
||||
if c.Bool("links") {
|
||||
for _, link := range actor.Links {
|
||||
if other, ok := lvl.Actors[link]; ok {
|
||||
fmt.Printf(" Link: %s (%s)\n", link, other.Filename)
|
||||
} else {
|
||||
fmt.Printf(" Link: %s (**UNRESOLVED**)", link)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Println("")
|
||||
} else {
|
||||
fmt.Print(" Use -actors or -verbose to serialize Actors\n\n")
|
||||
}
|
||||
|
||||
// Serialize chunk information.
|
||||
showChunker(c, lvl.Chunker, 0)
|
||||
|
||||
fmt.Println("")
|
||||
return nil
|
||||
}
|
||||
|
||||
func showDoodad(c *cli.Context, filename string) error {
|
||||
dd, err := doodads.LoadJSON(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.Bool("script") {
|
||||
fmt.Printf("// %s.js\n", filename)
|
||||
fmt.Println(strings.TrimSpace(dd.Script))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Is it a new zipfile format?
|
||||
var fileType = "json or gzip"
|
||||
if dd.Zipfile != nil {
|
||||
fileType = "zipfile"
|
||||
}
|
||||
|
||||
fmt.Printf("===== Doodad: %s =====\n", filename)
|
||||
|
||||
fmt.Println("Headers:")
|
||||
fmt.Printf(" File format: %s\n", fileType)
|
||||
fmt.Printf(" File version: %d\n", dd.Version)
|
||||
fmt.Printf(" Game version: %s\n", dd.GameVersion)
|
||||
fmt.Printf(" Doodad title: %s\n", dd.Title)
|
||||
fmt.Printf(" Author: %s\n", dd.Author)
|
||||
fmt.Printf(" Dimensions: %s\n", dd.Size)
|
||||
fmt.Printf(" Hitbox: %s\n", dd.Hitbox)
|
||||
fmt.Printf(" Locked: %+v\n", dd.Locked)
|
||||
fmt.Printf(" Hidden: %+v\n", dd.Hidden)
|
||||
fmt.Printf(" Script size: %d bytes\n", len(dd.Script))
|
||||
fmt.Println("")
|
||||
|
||||
if len(dd.Tags) > 0 {
|
||||
fmt.Println("Tags:")
|
||||
for k, v := range dd.Tags {
|
||||
fmt.Printf(" %s: %s\n", k, v)
|
||||
}
|
||||
fmt.Println("")
|
||||
}
|
||||
|
||||
if len(dd.Options) > 0 {
|
||||
var ordered = []string{}
|
||||
for name := range dd.Options {
|
||||
ordered = append(ordered, name)
|
||||
}
|
||||
sort.Strings(ordered)
|
||||
|
||||
fmt.Println("Options:")
|
||||
for _, name := range ordered {
|
||||
opt := dd.Options[name]
|
||||
fmt.Printf(" %s %s = %v\n", opt.Type, opt.Name, opt.Default)
|
||||
}
|
||||
fmt.Println("")
|
||||
}
|
||||
|
||||
showPalette(dd.Palette)
|
||||
|
||||
for i, layer := range dd.Layers {
|
||||
fmt.Printf("Layer %d: %s\n", i, layer.Name)
|
||||
showChunker(c, layer.Chunker, i)
|
||||
}
|
||||
|
||||
fmt.Println("")
|
||||
return nil
|
||||
}
|
||||
|
||||
func showPalette(pal *level.Palette) {
|
||||
fmt.Println("Palette:")
|
||||
for _, sw := range pal.Swatches {
|
||||
fmt.Printf(" - Swatch name: %s\n", sw.Name)
|
||||
fmt.Printf(" Attributes: %s\n", sw.Attributes())
|
||||
fmt.Printf(" Color: %s\n", sw.Color.ToHex())
|
||||
}
|
||||
fmt.Println("")
|
||||
}
|
||||
|
||||
func showChunker(c *cli.Context, ch *level.Chunker, layer int) {
|
||||
var (
|
||||
worldSize = ch.WorldSize()
|
||||
chunkSize = int(ch.Size)
|
||||
width = worldSize.W - worldSize.X
|
||||
height = worldSize.H - worldSize.Y
|
||||
|
||||
// Chunk debugging CLI options.
|
||||
visualize = c.Bool("visualize-rle")
|
||||
specificChunk = c.String("chunk")
|
||||
)
|
||||
|
||||
// If it's a Zipfile, count its chunks.
|
||||
var chunkCount = len(ch.Chunks)
|
||||
if ch.Zipfile != nil {
|
||||
chunkCount = len(level.ChunksInZipfile(ch.Zipfile, layer))
|
||||
}
|
||||
|
||||
fmt.Println("Chunks:")
|
||||
fmt.Printf(" Pixels Per Chunk: %d^2\n", ch.Size)
|
||||
fmt.Printf(" Number Generated: %d\n", chunkCount)
|
||||
fmt.Printf(" Coordinate Range: (%d,%d) ... (%d,%d)\n",
|
||||
worldSize.X,
|
||||
worldSize.Y,
|
||||
worldSize.W,
|
||||
worldSize.H,
|
||||
)
|
||||
fmt.Printf(" World Dimensions: %dx%d\n", width, height)
|
||||
|
||||
// Verbose chunk information.
|
||||
if c.Bool("chunks") || c.Bool("verbose") {
|
||||
fmt.Println(" Chunk Details:")
|
||||
for point := range ch.IterChunks() {
|
||||
// Debugging specific chunk coordinate?
|
||||
if specificChunk != "" && point.String() != specificChunk {
|
||||
log.Warn("Skip chunk %s: not the specific chunk you're looking for", point)
|
||||
continue
|
||||
}
|
||||
|
||||
chunk, ok := ch.GetChunk(point)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf(" - Coord: %s\n", point)
|
||||
fmt.Printf(" Type: %s\n", chunkTypeToName(chunk.Type))
|
||||
fmt.Printf(" Range: (%d,%d) ... (%d,%d)\n",
|
||||
int(point.X)*chunkSize,
|
||||
int(point.Y)*chunkSize,
|
||||
(int(point.X)*chunkSize)+chunkSize,
|
||||
(int(point.Y)*chunkSize)+chunkSize,
|
||||
)
|
||||
fmt.Printf(" Usage: %f (%d len of %d)\n", chunk.Usage(), chunk.Len(), chunkSize*chunkSize)
|
||||
|
||||
// Visualize the RLE encoded chunks?
|
||||
if visualize && chunk.Type == level.RLEType {
|
||||
ext, bin, err := ch.RawChunkFromZipfile(point)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
continue
|
||||
} else if ext != ".bin" {
|
||||
log.Error("Unexpected filetype for RLE compressed chunk (expected .bin, got %s)", ext)
|
||||
continue
|
||||
}
|
||||
|
||||
// Read off the first byte (chunk type)
|
||||
var reader = bytes.NewBuffer(bin)
|
||||
binary.ReadUvarint(reader)
|
||||
bin = reader.Bytes()
|
||||
|
||||
grid, err := rle.NewGrid(chunkSize)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
grid.Decompress(bin)
|
||||
fmt.Println(grid.Visualize())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Println(" Use -chunks or -verbose to serialize Chunks")
|
||||
}
|
||||
fmt.Println("")
|
||||
}
|
||||
|
||||
func chunkTypeToName(v uint64) string {
|
||||
switch v {
|
||||
case level.MapType:
|
||||
return "map"
|
||||
case level.RLEType:
|
||||
return "rle map"
|
||||
default:
|
||||
return fmt.Sprintf("type %d", v)
|
||||
}
|
||||
}
|
67
cmd/doodad/main.go
Normal file
|
@ -0,0 +1,67 @@
|
|||
// doodad is the command line developer tool for Doodle.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"git.kirsle.net/SketchyMaze/doodle/cmd/doodad/commands"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/branding/builds"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/plus/bootstrap"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// Build variables.
|
||||
var (
|
||||
Build = "N/A"
|
||||
BuildDate string
|
||||
)
|
||||
|
||||
func init() {
|
||||
if BuildDate == "" {
|
||||
BuildDate = time.Now().Format(time.RFC3339)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
bootstrap.InitPlugins()
|
||||
|
||||
app := cli.NewApp()
|
||||
app.Name = "doodad"
|
||||
app.Usage = "command line interface for Doodle"
|
||||
|
||||
app.Version = fmt.Sprintf("%s build %s. Built on %s",
|
||||
builds.Version,
|
||||
Build,
|
||||
BuildDate,
|
||||
)
|
||||
|
||||
app.Flags = []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "debug, d",
|
||||
Usage: "enable debug level logging",
|
||||
},
|
||||
}
|
||||
|
||||
app.Commands = []*cli.Command{
|
||||
commands.Convert,
|
||||
commands.Show,
|
||||
commands.Resave,
|
||||
commands.EditLevel,
|
||||
commands.EditDoodad,
|
||||
commands.InstallScript,
|
||||
commands.LevelPack,
|
||||
}
|
||||
|
||||
sort.Sort(cli.FlagsByName(app.Flags))
|
||||
sort.Sort(cli.CommandsByName(app.Commands))
|
||||
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -1,52 +1,385 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.kirsle.net/apps/doodle"
|
||||
"git.kirsle.net/apps/doodle/render/sdl"
|
||||
"git.kirsle.net/SketchyMaze/doodle/assets"
|
||||
doodle "git.kirsle.net/SketchyMaze/doodle/pkg"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/branding"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/branding/builds"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/chatbot"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/gamepad"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/native"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/plus/bootstrap"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/plus/dpp"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/shmem"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/sound"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/sprites"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/usercfg"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/userdir"
|
||||
golog "git.kirsle.net/go/log"
|
||||
"git.kirsle.net/go/render"
|
||||
"git.kirsle.net/go/render/sdl"
|
||||
"github.com/urfave/cli/v2"
|
||||
sdl2 "github.com/veandco/go-sdl2/sdl"
|
||||
|
||||
_ "image/png"
|
||||
)
|
||||
|
||||
// Build number is the git commit hash.
|
||||
var Build string
|
||||
|
||||
// Command line args
|
||||
var (
|
||||
debug bool
|
||||
edit bool
|
||||
Build = "<dynamic>"
|
||||
BuildDate string
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&debug, "debug", false, "Debug mode")
|
||||
flag.BoolVar(&edit, "edit", false, "Edit the map given on the command line. Default is to play the map.")
|
||||
if BuildDate == "" {
|
||||
BuildDate = time.Now().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
// Use all the CPU cores for collision detection and other load balanced
|
||||
// goroutine work in the app.
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
}
|
||||
|
||||
func main() {
|
||||
runtime.LockOSThread()
|
||||
flag.Parse()
|
||||
|
||||
args := flag.Args()
|
||||
var filename string
|
||||
if len(args) > 0 {
|
||||
filename = args[0]
|
||||
bootstrap.InitPlugins()
|
||||
|
||||
app := cli.NewApp()
|
||||
app.Name = "doodle"
|
||||
app.Usage = fmt.Sprintf("%s - %s", branding.AppName, branding.Summary)
|
||||
|
||||
// Load user settings from disk ASAP.
|
||||
if err := usercfg.Load(); err != nil {
|
||||
log.Error("Error loading user settings (defaults will be used): %s", err)
|
||||
}
|
||||
|
||||
// SDL engine.
|
||||
engine := sdl.New(
|
||||
"Doodle v"+doodle.Version,
|
||||
800,
|
||||
600,
|
||||
// Set default user settings.
|
||||
if usercfg.Current.CrosshairColor == render.Invisible {
|
||||
usercfg.Current.CrosshairColor = balance.DefaultCrosshairColor
|
||||
usercfg.Save()
|
||||
}
|
||||
|
||||
// Set GameController style.
|
||||
gamepad.SetStyle(gamepad.Style(usercfg.Current.ControllerStyle))
|
||||
|
||||
app.Version = fmt.Sprintf("%s build %s. Built on %s",
|
||||
builds.Version,
|
||||
Build,
|
||||
BuildDate,
|
||||
)
|
||||
|
||||
app := doodle.New(debug, engine)
|
||||
app.SetupEngine()
|
||||
if filename != "" {
|
||||
if edit {
|
||||
app.EditLevel(filename)
|
||||
app.Flags = []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "debug",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "enable debug level logging",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "log",
|
||||
Aliases: []string{"o"},
|
||||
Usage: "path on disk to copy the game's standard output logs (default goes to your game profile directory)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "pprof",
|
||||
Usage: "record pprof metrics to a filename",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "chdir",
|
||||
Usage: "working directory for the game's runtime package",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "new",
|
||||
Aliases: []string{"n"},
|
||||
Usage: "open immediately to the level editor",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "edit",
|
||||
Aliases: []string{"e"},
|
||||
Usage: "edit the map given on the command line (instead of play it)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "window",
|
||||
Aliases: []string{"w"},
|
||||
Usage: "set the window size (e.g. -w 1024x768) or special value: desktop, mobile, landscape, maximized",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "touch",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "force TouchScreenMode to be on at all times, which hides the mouse cursor",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "guitest",
|
||||
Usage: "enter the GUI Test scene on startup",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "experimental",
|
||||
Usage: "enable experimental Feature Flags",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "offline",
|
||||
Usage: "offline mode, disables check for new updates",
|
||||
},
|
||||
}
|
||||
|
||||
app.Action = func(c *cli.Context) error {
|
||||
// Set the log level now if debugging is enabled.
|
||||
if c.Bool("debug") {
|
||||
log.Logger.Config.Level = golog.DebugLevel
|
||||
}
|
||||
|
||||
// Write the game's log to disk.
|
||||
if err := initLogFile(c.String("log")); err != nil {
|
||||
log.Error("Couldn't write logs to disk: %s", err)
|
||||
}
|
||||
|
||||
log.Info("Starting %s %s", app.Name, app.Version)
|
||||
|
||||
// Print registration information, + also this sets the DefaultAuthor field.
|
||||
if reg, err := dpp.Driver.GetRegistration(); err == nil {
|
||||
log.Info("Registered to %s", reg.Name)
|
||||
}
|
||||
|
||||
// --chdir into a different working directory? e.g. for Flatpak especially.
|
||||
if err := setWorkingDirectory(c); err != nil {
|
||||
log.Error("Couldn't set working directory: %s", err)
|
||||
}
|
||||
|
||||
// Recording pprof stats?
|
||||
if cpufile := c.String("pprof"); cpufile != "" {
|
||||
log.Info("Saving CPU profiling data to %s", cpufile)
|
||||
fh, err := os.Create(cpufile)
|
||||
if err != nil {
|
||||
log.Error("--pprof: can't create file: %s", err)
|
||||
return err
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
if err := pprof.StartCPUProfile(fh); err != nil {
|
||||
log.Error("pprof: %s", err)
|
||||
return err
|
||||
}
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
var filename string
|
||||
if c.NArg() > 0 {
|
||||
filename = c.Args().Get(0)
|
||||
}
|
||||
|
||||
// Setting a custom resolution?
|
||||
var maximize = true
|
||||
if c.String("window") != "" {
|
||||
if err := setResolution(c.String("window")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
maximize = false
|
||||
}
|
||||
|
||||
// Enable feature flags?
|
||||
if c.Bool("experimental") || usercfg.Current.EnableFeatures {
|
||||
balance.FeaturesOn()
|
||||
}
|
||||
|
||||
// Set other program flags.
|
||||
shmem.OfflineMode = c.Bool("offline")
|
||||
native.ForceTouchScreenModeAlwaysOn = c.Bool("touch")
|
||||
|
||||
// SDL engine.
|
||||
engine := sdl.New(
|
||||
fmt.Sprintf("%s v%s", branding.AppName, branding.Version),
|
||||
balance.Width,
|
||||
balance.Height,
|
||||
)
|
||||
|
||||
// Activate game controller event support.
|
||||
sdl2.GameControllerEventState(1)
|
||||
|
||||
// Load the SDL fonts in from bindata storage.
|
||||
if fonts, err := assets.AssetDir("assets/fonts"); err == nil {
|
||||
for _, file := range fonts {
|
||||
data, err := assets.Asset("assets/fonts/" + file)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
sdl.InstallFont(file, data)
|
||||
}
|
||||
} else {
|
||||
app.PlayLevel(filename)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Preload all sound effects.
|
||||
sound.PreloadAll()
|
||||
|
||||
game := doodle.New(c.Bool("debug"), engine)
|
||||
game.SetupEngine()
|
||||
|
||||
// Start with maximized window unless -w was given.
|
||||
if maximize {
|
||||
log.Info("Maximize window")
|
||||
engine.Maximize()
|
||||
}
|
||||
|
||||
// Reload usercfg - if their settings.json doesn't exist, we try and pick a
|
||||
// default "hide touch hints" based on touch device presence - which is only
|
||||
// known after SetupEngine.
|
||||
usercfg.Load()
|
||||
|
||||
// Hide the mouse cursor over the window, we draw our own sprite image for it.
|
||||
engine.ShowCursor(false)
|
||||
|
||||
// Set the app window icon.
|
||||
if engine, ok := game.Engine.(*sdl.Renderer); ok {
|
||||
if icon, err := sprites.LoadImage(game.Engine, balance.WindowIcon); err == nil {
|
||||
engine.SetWindowIcon(icon.Image)
|
||||
} else {
|
||||
log.Error("Couldn't load WindowIcon (%s): %s", balance.WindowIcon, err)
|
||||
}
|
||||
}
|
||||
|
||||
if c.Bool("guitest") {
|
||||
game.Goto(&doodle.GUITestScene{})
|
||||
} else if c.Bool("new") {
|
||||
game.NewMap()
|
||||
} else if filename != "" {
|
||||
if c.Bool("edit") {
|
||||
game.EditFile(filename)
|
||||
} else {
|
||||
game.PlayLevel(filename)
|
||||
}
|
||||
}
|
||||
|
||||
// Maximizing the window? with `-w maximized`
|
||||
if c.String("window") == "maximized" {
|
||||
log.Info("Maximize main window")
|
||||
engine.Maximize()
|
||||
}
|
||||
|
||||
// Log what Doodle thinks its working directory is, for debugging.
|
||||
pwd, _ := os.Getwd()
|
||||
log.Info("Program's working directory is: %s", pwd)
|
||||
|
||||
// Initialize the developer shell chatbot easter egg.
|
||||
chatbot.Setup()
|
||||
|
||||
// Log some basic environment details.
|
||||
w, h := engine.WindowSize()
|
||||
log.Info("Window size: %dx%d", w, h)
|
||||
|
||||
game.Run()
|
||||
return nil
|
||||
}
|
||||
|
||||
sort.Sort(cli.FlagsByName(app.Flags))
|
||||
sort.Sort(cli.CommandsByName(app.Commands))
|
||||
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
app.Run()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setResolution(value string) error {
|
||||
switch value {
|
||||
case "desktop", "maximized":
|
||||
return nil
|
||||
case "mobile":
|
||||
balance.Width = 375
|
||||
balance.Height = 812
|
||||
if !usercfg.Current.Initialized {
|
||||
usercfg.Current.HorizontalToolbars = true
|
||||
}
|
||||
case "landscape":
|
||||
balance.Width = 812
|
||||
balance.Height = 375
|
||||
default:
|
||||
var re = regexp.MustCompile(`^(\d+?)x(\d+?)$`)
|
||||
m := re.FindStringSubmatch(value)
|
||||
if len(m) == 0 {
|
||||
return errors.New("--window: must be of the form WIDTHxHEIGHT, i.e. " +
|
||||
"1024x768, or special keywords desktop, mobile, or landscape.")
|
||||
}
|
||||
|
||||
w, _ := strconv.Atoi(m[1])
|
||||
h, _ := strconv.Atoi(m[2])
|
||||
balance.Width = w
|
||||
balance.Height = h
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func initLogFile(filename string) error {
|
||||
// Default log file to disk goes to your profile directory.
|
||||
if filename == "" {
|
||||
filename = userdir.LogFile
|
||||
}
|
||||
|
||||
fh, err := golog.NewFileTee(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Logger.Config.Writer = fh
|
||||
return nil
|
||||
}
|
||||
|
|
197
commands.go
|
@ -1,197 +0,0 @@
|
|||
package doodle
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Command is a parsed shell command.
|
||||
type Command struct {
|
||||
Raw string // The complete raw command the user typed.
|
||||
Command string // The first word of their command.
|
||||
Args []string // The shell-args array of parameters.
|
||||
ArgsLiteral string // The args portion of the command literally.
|
||||
}
|
||||
|
||||
// Run the command.
|
||||
func (c Command) Run(d *Doodle) error {
|
||||
if len(c.Raw) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch c.Command {
|
||||
case "echo":
|
||||
d.Flash(c.ArgsLiteral)
|
||||
return nil
|
||||
case "new":
|
||||
return c.New(d)
|
||||
case "save":
|
||||
return c.Save(d)
|
||||
case "edit":
|
||||
return c.Edit(d)
|
||||
case "play":
|
||||
return c.Play(d)
|
||||
case "exit":
|
||||
case "quit":
|
||||
return c.Quit()
|
||||
case "help":
|
||||
return c.Help(d)
|
||||
case "reload":
|
||||
d.Goto(d.Scene)
|
||||
return nil
|
||||
case "guitest":
|
||||
d.Goto(&GUITestScene{})
|
||||
return nil
|
||||
case "eval":
|
||||
case "$":
|
||||
out, err := d.shell.js.Run(c.ArgsLiteral)
|
||||
d.Flash("%+v", out)
|
||||
return err
|
||||
case "boolProp":
|
||||
return c.BoolProp(d)
|
||||
default:
|
||||
return c.Default()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// New opens a new map in the editor mode.
|
||||
func (c Command) New(d *Doodle) error {
|
||||
d.Flash("Starting a new map")
|
||||
d.NewMap()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Help prints the help info.
|
||||
func (c Command) Help(d *Doodle) error {
|
||||
if len(c.Args) == 0 {
|
||||
d.Flash("Available commands: new save edit play quit echo clear help")
|
||||
d.Flash("Type `help` and then the command, like: `help edit`")
|
||||
return nil
|
||||
}
|
||||
|
||||
switch c.Args[0] {
|
||||
case "new":
|
||||
d.Flash("Usage: new")
|
||||
d.Flash("Create a new drawing in Edit Mode")
|
||||
case "save":
|
||||
d.Flash("Usage: save [filename.json]")
|
||||
d.Flash("Save the map to disk (in Edit Mode only)")
|
||||
case "edit":
|
||||
d.Flash("Usage: edit <filename.json>")
|
||||
d.Flash("Open a file on disk in Edit Mode")
|
||||
case "play":
|
||||
d.Flash("Usage: play <filename.json>")
|
||||
d.Flash("Open a map from disk in Play Mode")
|
||||
case "echo":
|
||||
d.Flash("Usage: echo <message>")
|
||||
d.Flash("Flash a message back to the console")
|
||||
case "quit":
|
||||
case "exit":
|
||||
d.Flash("Usage: quit")
|
||||
d.Flash("Closes the dev console")
|
||||
case "clear":
|
||||
d.Flash("Usage: clear")
|
||||
d.Flash("Clears the terminal output history")
|
||||
case "help":
|
||||
d.Flash("Usage: help <command>")
|
||||
default:
|
||||
d.Flash("Unknown help topic.")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save the current map to disk.
|
||||
func (c Command) Save(d *Doodle) error {
|
||||
if scene, ok := d.Scene.(*EditorScene); ok {
|
||||
filename := ""
|
||||
if len(c.Args) > 0 {
|
||||
filename = c.Args[0]
|
||||
} else if scene.filename != "" {
|
||||
filename = scene.filename
|
||||
} else {
|
||||
return errors.New("usage: save <filename.json>")
|
||||
}
|
||||
|
||||
d.shell.Write("Saving to file: " + filename)
|
||||
scene.SaveLevel(filename)
|
||||
} else {
|
||||
return errors.New("save: only available in Edit Mode")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Edit a map from disk.
|
||||
func (c Command) Edit(d *Doodle) error {
|
||||
if len(c.Args) == 0 {
|
||||
return errors.New("Usage: edit <file name>")
|
||||
}
|
||||
|
||||
filename := c.Args[0]
|
||||
d.shell.Write("Editing level: " + filename)
|
||||
d.EditLevel(filename)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Play a map.
|
||||
func (c Command) Play(d *Doodle) error {
|
||||
if len(c.Args) == 0 {
|
||||
return errors.New("Usage: play <file name>")
|
||||
}
|
||||
|
||||
filename := c.Args[0]
|
||||
d.shell.Write("Playing level: " + filename)
|
||||
d.PlayLevel(filename)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Quit the command line shell.
|
||||
func (c Command) Quit() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// BoolProp command sets available boolean variables.
|
||||
func (c Command) BoolProp(d *Doodle) error {
|
||||
if len(c.Args) != 2 {
|
||||
return errors.New("Usage: boolProp <name> <true or false>")
|
||||
}
|
||||
|
||||
var (
|
||||
name = c.Args[0]
|
||||
value = c.Args[1]
|
||||
truthy = value[0] == 't' || value[0] == 'T' || value[0] == '1'
|
||||
ok = true
|
||||
)
|
||||
|
||||
switch name {
|
||||
case "Debug":
|
||||
case "D":
|
||||
d.Debug = truthy
|
||||
case "DebugOverlay":
|
||||
case "DO":
|
||||
DebugOverlay = truthy
|
||||
case "DebugCollision":
|
||||
case "DC":
|
||||
DebugCollision = truthy
|
||||
default:
|
||||
ok = false
|
||||
}
|
||||
|
||||
if ok {
|
||||
d.Flash("Set boolProp %s=%s", name, strconv.FormatBool(truthy))
|
||||
} else {
|
||||
d.Flash("Unknown boolProp name %s", name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Default command.
|
||||
func (c Command) Default() error {
|
||||
return fmt.Errorf("%s: command not found. Try `help` for help",
|
||||
c.Command,
|
||||
)
|
||||
}
|
12
config.go
|
@ -1,12 +0,0 @@
|
|||
package doodle
|
||||
|
||||
import "git.kirsle.net/apps/doodle/render"
|
||||
|
||||
// Configuration constants.
|
||||
var (
|
||||
DebugTextPadding int32 = 8
|
||||
DebugTextSize = 24
|
||||
DebugTextColor = render.SkyBlue
|
||||
DebugTextStroke = render.Grey
|
||||
DebugTextShadow = render.Black
|
||||
)
|