README as a small game website in Crookfinger's voice; INSTALL.md, BESTIARY.md, playable standalone file
- README: logo, badges (Rust, WebAssembly, Claude Code, MIT), intro addressed to the master, features with screenshots, the household, how to play, licences and credits - INSTALL.md: server, PWA, building, tests, savegame service and API, languages, how to refresh generated files - BESTIARY.md generated from the game's data (tools/make_bestiary.py) with exported sprites and icons (docs/img, test export_doc_images) - play/DungeonCraft.html: the whole game in one file to download and play offline Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No files matched your search
@@ -1,3 +1,5 @@
|
|||||||
target
|
target
|
||||||
.git
|
.git
|
||||||
server
|
server
|
||||||
|
docs
|
||||||
|
play
|
||||||
@@ -0,0 +1,468 @@
|
|||||||
|
# Crookfinger's Bestiary
|
||||||
|
|
||||||
|
<p align="center"><img src="docs/screenshots/bestiary.png" width="720" alt="The bestiary in the game"></p>
|
||||||
|
|
||||||
|
> *Ah, master, you want to know who lives in your dungeon? Sit down. Mind the bones. I have written everything down -
|
||||||
|
> lovingly about our own, and with a peg on my nose about the others.*
|
||||||
|
> *- Crookfinger, imp, advisor, keeper of the tea*
|
||||||
|
|
||||||
|
Every creature gains experience by fighting, training, studying and sparring. With every level it grows tougher
|
||||||
|
(+15% health, +12% attack per level), its badge changes (bronze chevrons, silver bars, golden stars) and its gear grows
|
||||||
|
(shoulder pieces, helmet, cape, golden armour with a crown and a halo). Its attacks look bigger, too.
|
||||||
|
|
||||||
|
- [Your creatures](#your-creatures)
|
||||||
|
- [The humans](#the-humans)
|
||||||
|
- [The neutrals](#the-neutrals)
|
||||||
|
- [Rooms of the dungeon](#rooms-of-the-dungeon)
|
||||||
|
- [Towers, traps and doors](#towers-traps-and-doors)
|
||||||
|
- [Spells](#spells)
|
||||||
|
|
||||||
|
## Your creatures
|
||||||
|
|
||||||
|
### Pit Imp
|
||||||
|
|
||||||
|
<img src="docs/img/units/worker_1.png" width="72" alt="Pit Imp, level 1"> <img src="docs/img/units/worker_5.png" width="72" alt="Pit Imp, level 5"> <img src="docs/img/units/worker_10.png" width="72" alt="Pit Imp, level 10">
|
||||||
|
|
||||||
|
*Level 1, 5 and 10 - the gear grows with the rank.*
|
||||||
|
|
||||||
|
| Health | Attack | Range | Speed | Wage |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 45 | 3 every 1.0 s (3/s) | close combat | 3.2 tiles/s | 0 gold |
|
||||||
|
|
||||||
|
**Comes for:** the dungeon itself; the summon spell brings more
|
||||||
|
**Loves:** Treasury
|
||||||
|
**From level 3:** Old hand - digs a quarter faster.
|
||||||
|
|
||||||
|
My little cousins. They dig, they claim, they carry gold, they repair towers and they never complain - well, they complain, but in a language nobody understands.
|
||||||
|
|
||||||
|
Up to five imps help each other with one task. Treat them well: without imps there is no dungeon.
|
||||||
|
|
||||||
|
### Kobold
|
||||||
|
|
||||||
|
<img src="docs/img/units/kobold_1.png" width="72" alt="Kobold, level 1"> <img src="docs/img/units/kobold_5.png" width="72" alt="Kobold, level 5"> <img src="docs/img/units/kobold_10.png" width="72" alt="Kobold, level 10">
|
||||||
|
|
||||||
|
*Level 1, 5 and 10 - the gear grows with the rank.*
|
||||||
|
|
||||||
|
| Health | Attack | Range | Speed | Wage |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 75 | 8 every 0.9 s (9/s) | close combat | 2.6 tiles/s | 40 gold |
|
||||||
|
|
||||||
|
**Comes for:** any lair: always the first through the portal
|
||||||
|
**Loves:** Training Hall
|
||||||
|
**Cannot stand:** Shadow Mage
|
||||||
|
**From level 5:** Dash - leaps at enemies a few tiles away with a heavy blow.
|
||||||
|
|
||||||
|
Small, scaly, sharp-toothed and endlessly enthusiastic. A kobold will fight anything, including things ten times its size, and lose with a happy face.
|
||||||
|
|
||||||
|
Cheap to keep, quick on its feet. I keep a jar of their shed scales on my shelf.
|
||||||
|
|
||||||
|
### Cave Spider
|
||||||
|
|
||||||
|
<img src="docs/img/units/spider_1.png" width="72" alt="Cave Spider, level 1"> <img src="docs/img/units/spider_5.png" width="72" alt="Cave Spider, level 5"> <img src="docs/img/units/spider_10.png" width="72" alt="Cave Spider, level 10">
|
||||||
|
|
||||||
|
*Level 1, 5 and 10 - the gear grows with the rank.*
|
||||||
|
|
||||||
|
| Health | Attack | Range | Speed | Wage |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 95 | 10 every 1.0 s (10/s) | close combat | 2.9 tiles/s | 60 gold |
|
||||||
|
|
||||||
|
**Comes for:** a mushroom garden of 6+ tiles
|
||||||
|
**Loves:** Mushroom Garden
|
||||||
|
**Cannot stand:** Giant Bat
|
||||||
|
**From level 4:** Web - its bites often leave enemies stuck and slow.
|
||||||
|
|
||||||
|
Eight legs, eight eyes and a heart of pure velvet. Cave spiders love the damp warmth of a mushroom garden and the crunchy sound of armour.
|
||||||
|
|
||||||
|
Quick and tough. Do not let the knights see them first: knights scream in a very high voice.
|
||||||
|
|
||||||
|
### Shadow Mage
|
||||||
|
|
||||||
|
<img src="docs/img/units/mage_1.png" width="72" alt="Shadow Mage, level 1"> <img src="docs/img/units/mage_5.png" width="72" alt="Shadow Mage, level 5"> <img src="docs/img/units/mage_10.png" width="72" alt="Shadow Mage, level 10">
|
||||||
|
|
||||||
|
*Level 1, 5 and 10 - the gear grows with the rank.*
|
||||||
|
|
||||||
|
| Health | Attack | Range | Speed | Wage |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 60 | 13 every 1.4 s (9/s) | 4.5 tiles | 2.0 tiles/s | 110 gold |
|
||||||
|
|
||||||
|
**Comes for:** a library of 6+ tiles
|
||||||
|
**Loves:** Library
|
||||||
|
**Cannot stand:** Kobold, Stone Ogre
|
||||||
|
**From level 6:** Shadow step - badly hurt in a fight, it vanishes back to its bed.
|
||||||
|
|
||||||
|
Shadow mages read everything, forget nothing and hurl dark bolts from a safe distance. They are polite, quiet and slightly terrifying.
|
||||||
|
|
||||||
|
Fragile, so keep them behind the ogres. They pay you back in burnt paladins.
|
||||||
|
|
||||||
|
### Stone Ogre
|
||||||
|
|
||||||
|
<img src="docs/img/units/ogre_1.png" width="72" alt="Stone Ogre, level 1"> <img src="docs/img/units/ogre_5.png" width="72" alt="Stone Ogre, level 5"> <img src="docs/img/units/ogre_10.png" width="72" alt="Stone Ogre, level 10">
|
||||||
|
|
||||||
|
*Level 1, 5 and 10 - the gear grows with the rank.*
|
||||||
|
|
||||||
|
| Health | Attack | Range | Speed | Wage |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 230 | 20 every 1.4 s (14/s) | close combat | 1.8 tiles/s | 150 gold |
|
||||||
|
|
||||||
|
**Comes for:** a training hall of 9+ tiles
|
||||||
|
**Loves:** Training Hall
|
||||||
|
**Cannot stand:** Shadow Mage, Cogling
|
||||||
|
**From level 4:** Stomp - shakes the ground: enemies around it are hurt and stunned.
|
||||||
|
|
||||||
|
A stone ogre is a wall that walks and hugs. Mostly it hugs enemies, and they do not get up again.
|
||||||
|
|
||||||
|
Slow, stubborn and wonderfully sturdy. Feed it well, pay it on time and it will stand in front of your core until the end of the world.
|
||||||
|
|
||||||
|
### Jailer
|
||||||
|
|
||||||
|
<img src="docs/img/units/jailer_1.png" width="72" alt="Jailer, level 1"> <img src="docs/img/units/jailer_5.png" width="72" alt="Jailer, level 5"> <img src="docs/img/units/jailer_10.png" width="72" alt="Jailer, level 10">
|
||||||
|
|
||||||
|
*Level 1, 5 and 10 - the gear grows with the rank.*
|
||||||
|
|
||||||
|
| Health | Attack | Range | Speed | Wage |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 260 | 16 every 1.3 s (12/s) | close combat | 1.6 tiles/s | 140 gold |
|
||||||
|
|
||||||
|
**Comes for:** a torture chamber of 6+ tiles
|
||||||
|
**Loves:** Torture Chamber
|
||||||
|
**Cannot stand:** Flame Priestess
|
||||||
|
**From level 7:** Shackle - its blows sometimes chain enemies in place.
|
||||||
|
|
||||||
|
The jailer pulls enemies close with his chain and has a gift for persuasion: prisoners he looks after change sides much more often.
|
||||||
|
|
||||||
|
Big, quiet, hums while he works. Children love him. Well, ours do.
|
||||||
|
|
||||||
|
### Flame Priestess
|
||||||
|
|
||||||
|
<img src="docs/img/units/priestess_1.png" width="72" alt="Flame Priestess, level 1"> <img src="docs/img/units/priestess_5.png" width="72" alt="Flame Priestess, level 5"> <img src="docs/img/units/priestess_10.png" width="72" alt="Flame Priestess, level 10">
|
||||||
|
|
||||||
|
*Level 1, 5 and 10 - the gear grows with the rank.*
|
||||||
|
|
||||||
|
| Health | Attack | Range | Speed | Wage |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 70 | 14 every 1.6 s (9/s) | 5.0 tiles | 2.0 tiles/s | 120 gold |
|
||||||
|
|
||||||
|
**Comes for:** a temple of 6+ tiles
|
||||||
|
**Loves:** Temple
|
||||||
|
**Cannot stand:** Jailer
|
||||||
|
**From level 5:** Healing pulse - heals all hurt creatures around her at once.
|
||||||
|
|
||||||
|
The flame priestess throws fireballs that splash onto everyone around the target, and she heals the soul core with her prayers.
|
||||||
|
|
||||||
|
She sleeps on glowing embers and smells of cinnamon. Do not ask her about the knights who burned her temple. Just let her work.
|
||||||
|
|
||||||
|
### Chainbreaker
|
||||||
|
|
||||||
|
<img src="docs/img/units/chainbreaker_1.png" width="72" alt="Chainbreaker, level 1"> <img src="docs/img/units/chainbreaker_5.png" width="72" alt="Chainbreaker, level 5"> <img src="docs/img/units/chainbreaker_10.png" width="72" alt="Chainbreaker, level 10">
|
||||||
|
|
||||||
|
*Level 1, 5 and 10 - the gear grows with the rank.*
|
||||||
|
|
||||||
|
| Health | Attack | Range | Speed | Wage |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 600 | 36 every 1.1 s (33/s) | close combat | 2.3 tiles/s | 400 gold |
|
||||||
|
|
||||||
|
**Comes for:** a big dungeon: 40+ room tiles, a Lv 5 veteran and 4000 gold
|
||||||
|
**Loves:** Training Hall
|
||||||
|
**From level 6:** Whirlwind - spins its axes through every enemy around it.
|
||||||
|
|
||||||
|
A legend. Twin axes, broken chains, and a temper that has flattened whole fortresses. Only one at a time, and only for a master who is worth it.
|
||||||
|
|
||||||
|
Every swing also hits a second enemy. He gets restless when the dungeon is small - give him space and enemies, and he is yours.
|
||||||
|
|
||||||
|
### Cogling
|
||||||
|
|
||||||
|
<img src="docs/img/units/cogling_1.png" width="72" alt="Cogling, level 1"> <img src="docs/img/units/cogling_5.png" width="72" alt="Cogling, level 5"> <img src="docs/img/units/cogling_10.png" width="72" alt="Cogling, level 10">
|
||||||
|
|
||||||
|
*Level 1, 5 and 10 - the gear grows with the rank.*
|
||||||
|
|
||||||
|
| Health | Attack | Range | Speed | Wage |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 90 | 9 every 0.9 s (10/s) | close combat | 2.4 tiles/s | 70 gold |
|
||||||
|
|
||||||
|
**Comes for:** a forge of 6+ tiles
|
||||||
|
**Loves:** Forge
|
||||||
|
**Cannot stand:** Stone Ogre
|
||||||
|
**From level 4:** Bomb - throws a home-made bomb that explodes among the enemies.
|
||||||
|
|
||||||
|
A cogling is soot, brass goggles and pure enthusiasm. It builds traps two and a half times as fast as anyone else and hums while it hammers.
|
||||||
|
|
||||||
|
It cannot fight much, but every corridor it touches becomes a terrible place to walk. I adore them.
|
||||||
|
|
||||||
|
### Revenant
|
||||||
|
|
||||||
|
<img src="docs/img/units/revenant_1.png" width="72" alt="Revenant, level 1"> <img src="docs/img/units/revenant_5.png" width="72" alt="Revenant, level 5"> <img src="docs/img/units/revenant_10.png" width="72" alt="Revenant, level 10">
|
||||||
|
|
||||||
|
*Level 1, 5 and 10 - the gear grows with the rank.*
|
||||||
|
|
||||||
|
| Health | Attack | Range | Speed | Wage |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 160 | 14 every 1.2 s (12/s) | close combat | 1.9 tiles/s | 30 gold |
|
||||||
|
|
||||||
|
**Comes for:** rises from the graveyard
|
||||||
|
**Loves:** Graveyard
|
||||||
|
**Cannot stand:** Flame Priestess
|
||||||
|
**From level 5:** Life drain - heals itself by a third of the damage it deals.
|
||||||
|
|
||||||
|
A revenant does not eat, does not sleep and does not complain - the perfect employee, if you can stand the smell.
|
||||||
|
|
||||||
|
It drinks the strength of those it strikes once it has some experience. The priestess and it do not get along, for obvious reasons.
|
||||||
|
|
||||||
|
### Giant Bat
|
||||||
|
|
||||||
|
<img src="docs/img/units/bat_1.png" width="72" alt="Giant Bat, level 1"> <img src="docs/img/units/bat_5.png" width="72" alt="Giant Bat, level 5"> <img src="docs/img/units/bat_10.png" width="72" alt="Giant Bat, level 10">
|
||||||
|
|
||||||
|
*Level 1, 5 and 10 - the gear grows with the rank.*
|
||||||
|
|
||||||
|
| Health | Attack | Range | Speed | Wage |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 55 | 6 every 0.7 s (9/s) | close combat | 3.6 tiles/s | 30 gold |
|
||||||
|
|
||||||
|
**Comes for:** an aerie of 4+ tiles
|
||||||
|
**Loves:** Aerie
|
||||||
|
**Cannot stand:** Cave Spider
|
||||||
|
**From level 5:** Shriek - a piercing cry that stuns enemies nearby.
|
||||||
|
|
||||||
|
Giant bats! Fast, cheeky, and they fly over water and lava. They patrol the edges of your realm and dive at lonely enemies.
|
||||||
|
|
||||||
|
Weak alone, a plague in swarms. They flee when badly hurt and come back once they have rested.
|
||||||
|
|
||||||
|
### Chain Gargoyle
|
||||||
|
|
||||||
|
<img src="docs/img/units/gargoyle_1.png" width="72" alt="Chain Gargoyle, level 1"> <img src="docs/img/units/gargoyle_5.png" width="72" alt="Chain Gargoyle, level 5"> <img src="docs/img/units/gargoyle_10.png" width="72" alt="Chain Gargoyle, level 10">
|
||||||
|
|
||||||
|
*Level 1, 5 and 10 - the gear grows with the rank.*
|
||||||
|
|
||||||
|
| Health | Attack | Range | Speed | Wage |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 260 | 15 every 1.2 s (12/s) | close combat | 2.2 tiles/s | 120 gold |
|
||||||
|
|
||||||
|
**Comes for:** an aerie of 6+ tiles and a torture chamber of 4+
|
||||||
|
**Loves:** Torture Chamber
|
||||||
|
**Cannot stand:** Arcane Owl
|
||||||
|
**From level 5:** Stone form - badly hurt, it turns to stone and takes half damage for a while.
|
||||||
|
|
||||||
|
A chain gargoyle is carved stone with bad manners and a soft spot for dungeons with a proper torture chamber.
|
||||||
|
|
||||||
|
Tough as a castle wall, flies over rivers and does not mind arrows at all. It sits very still in the aerie - that is how it relaxes.
|
||||||
|
|
||||||
|
### Arcane Owl
|
||||||
|
|
||||||
|
<img src="docs/img/units/owl_1.png" width="72" alt="Arcane Owl, level 1"> <img src="docs/img/units/owl_5.png" width="72" alt="Arcane Owl, level 5"> <img src="docs/img/units/owl_10.png" width="72" alt="Arcane Owl, level 10">
|
||||||
|
|
||||||
|
*Level 1, 5 and 10 - the gear grows with the rank.*
|
||||||
|
|
||||||
|
| Health | Attack | Range | Speed | Wage |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 70 | 12 every 1.3 s (9/s) | 4.5 tiles | 2.8 tiles/s | 100 gold |
|
||||||
|
|
||||||
|
**Comes for:** an aerie of 6+ tiles and a library of 6+
|
||||||
|
**Loves:** Library
|
||||||
|
**Cannot stand:** Chain Gargoyle
|
||||||
|
**From level 5:** Volley - every bolt is followed by a second one at another enemy.
|
||||||
|
|
||||||
|
The arcane owl has read every book in your library twice and now shoots glowing bolts at heroes from the air.
|
||||||
|
|
||||||
|
Clever, fragile and terribly judgemental. It hoots when you build crooked rooms.
|
||||||
|
|
||||||
|
### Ember Wyrm
|
||||||
|
|
||||||
|
<img src="docs/img/units/wyrm_1.png" width="72" alt="Ember Wyrm, level 1"> <img src="docs/img/units/wyrm_5.png" width="72" alt="Ember Wyrm, level 5"> <img src="docs/img/units/wyrm_10.png" width="72" alt="Ember Wyrm, level 10">
|
||||||
|
|
||||||
|
*Level 1, 5 and 10 - the gear grows with the rank.*
|
||||||
|
|
||||||
|
| Health | Attack | Range | Speed | Wage |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 650 | 30 every 1.4 s (21/s) | 3.0 tiles | 2.5 tiles/s | 380 gold |
|
||||||
|
|
||||||
|
**Comes for:** an aerie of 16+ tiles and a treasury of 16+ (only one)
|
||||||
|
**Loves:** Treasury
|
||||||
|
**Cannot stand:** Giant Bat
|
||||||
|
**From level 7:** Inferno - its fire breath spreads much wider.
|
||||||
|
|
||||||
|
An ember wyrm. In your dungeon. Breathing fire on knights. Master, I may cry.
|
||||||
|
|
||||||
|
Very rare, very strong, burns everything around its target. It wants a huge aerie and a rich treasury, and it will not share the sky with another wyrm.
|
||||||
|
|
||||||
|
## The humans
|
||||||
|
|
||||||
|
> *Hold your nose, master. Here come the heroes.*
|
||||||
|
|
||||||
|
### Knight
|
||||||
|
|
||||||
|
<img src="docs/img/units/knight_1.png" width="72" alt="Knight, level 1"> <img src="docs/img/units/knight_5.png" width="72" alt="Knight, level 5"> <img src="docs/img/units/knight_10.png" width="72" alt="Knight, level 10">
|
||||||
|
|
||||||
|
*Level 1, 5 and 10 - the gear grows with the rank.*
|
||||||
|
|
||||||
|
| Health | Attack | Range | Speed | Bounty |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 140 | 11 every 1.1 s (10/s) | close combat | 1.9 tiles/s | 120 gold |
|
||||||
|
|
||||||
|
The backbone of the Order: polished, punctual and utterly devoid of imagination. Knights march in circles, sing about the sun and smell of boot wax.
|
||||||
|
|
||||||
|
They hit hard and hold the line. Disgusting. Capture them if you can - some of them can be persuaded in the torture chamber.
|
||||||
|
|
||||||
|
### Archer
|
||||||
|
|
||||||
|
<img src="docs/img/units/archer_1.png" width="72" alt="Archer, level 1"> <img src="docs/img/units/archer_5.png" width="72" alt="Archer, level 5"> <img src="docs/img/units/archer_10.png" width="72" alt="Archer, level 10">
|
||||||
|
|
||||||
|
*Level 1, 5 and 10 - the gear grows with the rank.*
|
||||||
|
|
||||||
|
| Health | Attack | Range | Speed | Bounty |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 75 | 9 every 1.3 s (7/s) | 4.5 tiles | 2.1 tiles/s | 100 gold |
|
||||||
|
|
||||||
|
Archers stand far away, shoot from behind walls and call it bravery. They wear little green hats. Little green hats, master.
|
||||||
|
|
||||||
|
Weak up close. Send the bats, they hate it.
|
||||||
|
|
||||||
|
### Priest
|
||||||
|
|
||||||
|
<img src="docs/img/units/priest_1.png" width="72" alt="Priest, level 1"> <img src="docs/img/units/priest_5.png" width="72" alt="Priest, level 5"> <img src="docs/img/units/priest_10.png" width="72" alt="Priest, level 10">
|
||||||
|
|
||||||
|
*Level 1, 5 and 10 - the gear grows with the rank.*
|
||||||
|
|
||||||
|
| Health | Attack | Range | Speed | Bounty |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 80 | 5 every 1.5 s (3/s) | 3.5 tiles | 1.9 tiles/s | 120 gold |
|
||||||
|
|
||||||
|
Priests heal other heroes from afar and pray loudly enough to curdle milk. Every one of them has a sermon about us, and none of them is short.
|
||||||
|
|
||||||
|
Kill them first, or the fight never ends.
|
||||||
|
|
||||||
|
### Dwarf Miner
|
||||||
|
|
||||||
|
<img src="docs/img/units/dwarf_1.png" width="72" alt="Dwarf Miner, level 1"> <img src="docs/img/units/dwarf_5.png" width="72" alt="Dwarf Miner, level 5"> <img src="docs/img/units/dwarf_10.png" width="72" alt="Dwarf Miner, level 10">
|
||||||
|
|
||||||
|
*Level 1, 5 and 10 - the gear grows with the rank.*
|
||||||
|
|
||||||
|
| Health | Attack | Range | Speed | Bounty |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 110 | 9 every 1.0 s (9/s) | close combat | 2.0 tiles/s | 100 gold |
|
||||||
|
|
||||||
|
The dwarf miners of the Order dig their way through earth three times as fast as anyone else, straight towards your treasury. They have no respect for walls - your walls in particular.
|
||||||
|
|
||||||
|
Reinforce your walls. Steel walls make them weep.
|
||||||
|
|
||||||
|
### Fairy Spy
|
||||||
|
|
||||||
|
<img src="docs/img/units/fairy_1.png" width="72" alt="Fairy Spy, level 1"> <img src="docs/img/units/fairy_5.png" width="72" alt="Fairy Spy, level 5"> <img src="docs/img/units/fairy_10.png" width="72" alt="Fairy Spy, level 10">
|
||||||
|
|
||||||
|
*Level 1, 5 and 10 - the gear grows with the rank.*
|
||||||
|
|
||||||
|
| Health | Attack | Range | Speed | Bounty |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 70 | 5 every 1.2 s (4/s) | 3.5 tiles | 3.2 tiles/s | 150 gold |
|
||||||
|
|
||||||
|
The Order's little informer: a glowing gnat with wings, sent ahead to find your traps and tell everybody. Once it has seen a trap, the heroes smash it.
|
||||||
|
|
||||||
|
It also sprinkles healing dust on the heroes. Swat it first. Twice, to be sure.
|
||||||
|
|
||||||
|
### Battle Mage
|
||||||
|
|
||||||
|
<img src="docs/img/units/battlemage_1.png" width="72" alt="Battle Mage, level 1"> <img src="docs/img/units/battlemage_5.png" width="72" alt="Battle Mage, level 5"> <img src="docs/img/units/battlemage_10.png" width="72" alt="Battle Mage, level 10">
|
||||||
|
|
||||||
|
*Level 1, 5 and 10 - the gear grows with the rank.*
|
||||||
|
|
||||||
|
| Health | Attack | Range | Speed | Bounty |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 120 | 18 every 1.8 s (10/s) | 5.0 tiles | 1.8 tiles/s | 250 gold |
|
||||||
|
|
||||||
|
A mage in the Order's robes who thinks walls are a personal insult. Reinforced walls burst under his fireballs, and he never stops talking about it.
|
||||||
|
|
||||||
|
Fragile up close. Steel walls annoy him most - good.
|
||||||
|
|
||||||
|
### Giant
|
||||||
|
|
||||||
|
<img src="docs/img/units/giant_1.png" width="72" alt="Giant, level 1"> <img src="docs/img/units/giant_5.png" width="72" alt="Giant, level 5"> <img src="docs/img/units/giant_10.png" width="72" alt="Giant, level 10">
|
||||||
|
|
||||||
|
*Level 1, 5 and 10 - the gear grows with the rank.*
|
||||||
|
|
||||||
|
| Health | Attack | Range | Speed | Bounty |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 900 | 34 every 1.6 s (21/s) | close combat | 1.3 tiles/s | 600 gold |
|
||||||
|
|
||||||
|
A giant is what happens when a knight eats his vegetables for three hundred years. Slow, enormous, and it treats doors as a suggestion.
|
||||||
|
|
||||||
|
It wrecks traps simply by standing next to them. Revolting manners. Hit it with everything, preferably from afar.
|
||||||
|
|
||||||
|
### Paladin of Light
|
||||||
|
|
||||||
|
<img src="docs/img/units/paladin_1.png" width="72" alt="Paladin of Light, level 1"> <img src="docs/img/units/paladin_5.png" width="72" alt="Paladin of Light, level 5"> <img src="docs/img/units/paladin_10.png" width="72" alt="Paladin of Light, level 10">
|
||||||
|
|
||||||
|
*Level 1, 5 and 10 - the gear grows with the rank.*
|
||||||
|
|
||||||
|
| Health | Attack | Range | Speed | Bounty |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 700 | 26 every 1.2 s (22/s) | close combat | 1.7 tiles/s | 1000 gold |
|
||||||
|
|
||||||
|
Paladins of light: glowing, enormous and absolutely convinced of themselves. They are the Order's heroes and have the bounty to prove it.
|
||||||
|
|
||||||
|
Tough as an ogre, twice as smug. Bring everything you have, and maybe a bucket for the glare.
|
||||||
|
|
||||||
|
## The neutrals
|
||||||
|
|
||||||
|
### Cave dwarf
|
||||||
|
|
||||||
|
<img src="docs/img/units/dwarf_5.png" width="72" alt="cave dwarf">
|
||||||
|
|
||||||
|
Not all dwarves serve the Order. The cave dwarves keep to themselves in caves full of gold and gems and want nothing but to be left alone.
|
||||||
|
|
||||||
|
They fight whoever comes close and never leave their cave. The more your army grows, the tougher they get - they have heard of you. Some of their chests hold the plans of the rune forge tower.
|
||||||
|
|
||||||
|
## Rooms of the dungeon
|
||||||
|
|
||||||
|
| | Room | |
|
||||||
|
|---|---|---|
|
||||||
|
| <img src="docs/img/icons/treasury.png" width="40"> | **Treasury** | Stores gold: +500 capacity per tile. *Where the gold sleeps. The richer the treasury, the happier everyone is. Imps bring their loads to the nearest treasury, and payday is paid from here. | Each tile holds 500 gold; the core keeps a reserve when it is full.* |
|
||||||
|
| <img src="docs/img/icons/lair.png" width="40"> | **Lair** | One bed per tile. Determines how many creatures come. *Every creature wants its own bed, and only arrives when one is free. A big lair also brings new creatures faster.* |
|
||||||
|
| <img src="docs/img/icons/farm.png" width="40"> | **Mushroom Garden** | Food for creatures. With 6 tiles or more, cave spiders come. *Mushrooms! Juicy, pale, slightly glowing. Hungry creatures are grumpy creatures, and spiders love a big garden.* |
|
||||||
|
| <img src="docs/img/icons/training.png" width="40"> | **Training Hall** | Creatures gain levels (costs gold). With 9 tiles or more, stone ogres come. *Creatures train here and gain levels. Training is billed on payday. Ogres come for a big hall.* |
|
||||||
|
| <img src="docs/img/icons/library.png" width="40"> | **Library** | Creatures generate mana. With 6 tiles or more, shadow mages come. *Research, quiet reading and the occasional explosion. Mages and owls come for a library.* |
|
||||||
|
| <img src="docs/img/icons/torture.png" width="40"> | **Torture Chamber** | Holds one prisoner per 2 tiles. Defeated heroes are carried here and tortured until they die or defect. From 6 tiles on, Jailers come. *Captured heroes end up here. With time and a jailer, some of them change sides. Gargoyles feel at home with one.* |
|
||||||
|
| <img src="docs/img/icons/prison.png" width="40"> | **Prison** | Captured heroes sit in cells here - apart from the torture chamber. Without torture they do not change sides, and hunger wears them down. Pick a prisoner up with the hand to move it to the torture chamber. *Rows of cells. Heroes rot here quietly until somebody decides what to do with them.* |
|
||||||
|
| <img src="docs/img/icons/temple.png" width="40"> | **Temple** | Creatures pray here: more mana than studying, and it lifts their mood. From 6 tiles on, Flame Priestesses come. *Prayers heal the soul core and keep spirits high. The flame priestess comes for a temple.* |
|
||||||
|
| <img src="docs/img/icons/aerie.png" width="40"> | **Aerie** | Perches and nests for flying creatures. 4+ tiles attract giant bats; with a torture chamber chain gargoyles, with a library arcane owls; a big aerie (16+) and treasury (16+) may lure an ember wyrm. Flyers cross water and lava. *Perches and nests for flying creatures. Bats, gargoyles, owls and even the ember wyrm need room here.* |
|
||||||
|
| <img src="docs/img/icons/forge.png" width="40"> | **Forge** | Creatures manufacture traps and doors here - work, no gold. Finished pieces wait in crates until an imp installs them. Coglings come for a forge of 6+ tiles. *The clang of hammers, the smell of hot iron. Every trap and door is made here first - the more creatures work, the faster.* |
|
||||||
|
| <img src="docs/img/icons/graveyard.png" width="40"> | **Graveyard** | The fallen - heroes and creatures - are buried here. After a while some of them rise as revenants and serve you. *Moss, stones and fresh earth. Nobody stays dead for long down here.* |
|
||||||
|
| <img src="docs/img/icons/guardpost.png" width="40"> | **Guard Post** | Creatures hold the position here for a while and watch far: they attack every enemy that comes into view. *A brazier, a banner and a bored creature with sharp eyes.* |
|
||||||
|
| <img src="docs/img/icons/arena.png" width="40"> | **Arena** | Creatures spar in pairs: twice as much experience as training and free of charge, but they get bruised. *Sand, sweat and the occasional tooth. Experience comes fast here.* |
|
||||||
|
| <img src="docs/img/icons/altar.png" width="40"> | **Sacrifice Altar** | Drop a creature on the altar to sacrifice it. Certain pairs of sacrifices bring a rare creature, otherwise the dungeon is blessed: better mood and mana. *Black marble that drinks. Some say it listens to pairs. Ogre and spider, mage and bat, jailer and priestess, kobold and cogling...* |
|
||||||
|
|
||||||
|
## Towers, traps and doors
|
||||||
|
|
||||||
|
| | | |
|
||||||
|
|---|---|---|
|
||||||
|
| <img src="docs/img/icons/arrow.png" width="40"> | **Arrow Tower** | Cheap and fast. Arrows pierce up to two more heroes behind the target. Click your tower with this tool to upgrade it. *The arrow pierces up to two more enemies behind the target. Cheap and reliable.* |
|
||||||
|
| <img src="docs/img/icons/cannon.png" width="40"> | **Cannon Tower** | Slow, long range, splash damage that knocks heroes back. Cannot hit targets right next to it. *Splash damage and knockback - push the knights into the lava.* |
|
||||||
|
| <img src="docs/img/icons/magic.png" width="40"> | **Magic Tower** | Uses mana per shot. Never misses, slows the target and jumps to a second hero. *Never misses, slows the target and jumps on to a second enemy. Uses mana.* |
|
||||||
|
| <img src="docs/img/icons/rune.png" width="40"> | **Rune Forge Tower** | Plans from a dwarven chest: built for free, one tower per chest. Huge range, rune bolts jump on to three more enemies. Sell it and the base tower comes back once, in the next mission. *The masterpiece of the cave dwarves: rune bolts that jump on to three more enemies, enormous range and walls of granite. One free build per chest; sell it and it comes back once.* |
|
||||||
|
| <img src="docs/img/icons/spikes.png" width="40"> | **Spike Pit** | Spikes shoot up when an enemy steps on it. Strikes again and again. *Simple, honest, reusable. Knights hate it when the floor bites back.* |
|
||||||
|
| <img src="docs/img/icons/poison.png" width="40"> | **Poison Vent** | Releases a cloud of poison that hurts every enemy inside for a while. *A puff of green and a lot of coughing. Best in narrow corridors.* |
|
||||||
|
| <img src="docs/img/icons/fire.png" width="40"> | **Fire Jet** | A demon head spits a jet of fire at enemies up to three tiles away. *A demon head with a temper. Put it where the heroes queue.* |
|
||||||
|
| <img src="docs/img/icons/roller.png" width="40"> | **Stone Roller** | Sends a boulder rolling down the corridor: hits hard and knocks enemies aside. *A boulder and a long corridor - physics does the rest.* |
|
||||||
|
| <img src="docs/img/icons/alarm.png" width="40"> | **Alarm Bell** | Rings when enemies come near: your creatures nearby rush to fight them. *Not dangerous itself, but it calls everybody who is.* |
|
||||||
|
| <img src="docs/img/icons/mine.png" width="40"> | **Rune Mine** | Explodes once with great force when an enemy steps close. *One step too many and the corridor gets redecorated.* |
|
||||||
|
| <img src="docs/img/icons/frost.png" width="40"> | **Frost Glow** | An ice crystal: enemies around it are slowed and chilled, always. *Cold makes heroes slow, and slow heroes make easy targets.* |
|
||||||
|
| <img src="docs/img/icons/door_wood.png" width="40"> | **Wooden Door** | Your creatures walk through, enemies have to smash it. Click a door again to lock it. *Keeps the draught and the knights out - for a while.* |
|
||||||
|
| <img src="docs/img/icons/door_iron.png" width="40"> | **Iron Door** | A heavy door that takes a long time to break. Click a door again to lock it. *Heavy, riveted, stubborn. Bring a battering ram, heroes.* |
|
||||||
|
| <img src="docs/img/icons/door_magic.png" width="40"> | **Magic Door** | The strongest door: it hurls back whoever beats on it. Click a door again to lock it. *Strike it and it strikes back. Locked, it is the end of every corridor.* |
|
||||||
|
|
||||||
|
## Spells
|
||||||
|
|
||||||
|
| | Spell | Mana | |
|
||||||
|
|---|---|---|---|
|
||||||
|
| <img src="docs/img/icons/lightning.png" width="40"> | **Lightning** | 50 | 55 damage to heroes in the area. |
|
||||||
|
| <img src="docs/img/icons/heal.png" width="40"> | **Heal** | 60 | Heals your units in the area by 50%. |
|
||||||
|
| <img src="docs/img/icons/summon.png" width="40"> | **Summon Pit Imp** | 80+ | Creates a new Pit Imp on your own territory. |
|
||||||
|
| <img src="docs/img/icons/eye.png" width="40"> | **Seer's Eye** | 40 | Opens a magic eye: a wide area around the spot is revealed for 20 seconds. |
|
||||||
|
| <img src="docs/img/icons/cavein.png" width="40"> | **Cave-in** | 70 | The ceiling comes down: open floor turns back into earth (3x3), whoever stands there is hurt and pushed out. Blocks corridors. |
|
||||||
|
| <img src="docs/img/icons/haste.png" width="40"> | **Haste** | 30 | Your creatures around the spot move and act much faster for 30 seconds. |
|
||||||
|
| <img src="docs/img/icons/stoneskin.png" width="40"> | **Stoneskin** | 40 | Your creatures around the spot take only half damage for 30 seconds. |
|
||||||
|
| <img src="docs/img/icons/goldrain.png" width="40"> | **Gold Rain** | 100 | Turns mana into 250 gold for your treasury. |
|
||||||
|
| <img src="docs/img/icons/invisible.png" width="40"> | **Invisibility** | 50 | One creature becomes invisible for 25 seconds: enemies and towers ignore it until it attacks. |
|
||||||
|
| <img src="docs/img/icons/plague.png" width="40"> | **Plague** | 80 | A sickness that eats at the enemies around the spot and jumps from one to the next. |
|
||||||
|
| <img src="docs/img/icons/quake.png" width="40"> | **Quake** | 90 | The ground shakes: enemies around the spot are hurt and stunned for two seconds. Your creatures keep their footing. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*This page is written from the game's own data: `python3 tools/make_bestiary.py`.*
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
# Installing and running DungeonCraft
|
||||||
|
|
||||||
|
> *Technical matters, master. Crookfinger does not understand half of it, but the imps say it works.*
|
||||||
|
|
||||||
|
**Just want to play?** Download [`play/DungeonCraft.html`](play/DungeonCraft.html) and open it in a browser -
|
||||||
|
no server, no installation, no login. Savegames are downloaded as `.dcsave` files and loaded again with
|
||||||
|
*Load Game* (or dropped onto the game window).
|
||||||
|
|
||||||
|
Everything below is for running your own server (with user accounts and savegames on the server),
|
||||||
|
building the game yourself or working on it.
|
||||||
|
|
||||||
|
## Running a server
|
||||||
|
|
||||||
|
The easiest way is Docker; the image builds the WASM file and serves it with nginx. A second container runs the savegame service (see below):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose up -d --build
|
||||||
|
# open http://localhost:18093
|
||||||
|
```
|
||||||
|
|
||||||
|
### Install as an app (PWA)
|
||||||
|
|
||||||
|
DungeonCraft is a progressive web app: browsers offer to install it (Chrome/Edge: install icon in the address bar or "Install app" in the menu; Android: "Add to home screen"; iOS Safari: Share → "Add to Home Screen"). The installed game starts in full screen and also works offline (the savegame list on the server then falls back to savegame files).
|
||||||
|
|
||||||
|
Browsers only allow the installation over **HTTPS** (or on `localhost`), so a public installation needs a reverse proxy with a certificate in front of port 18093.
|
||||||
|
|
||||||
|
### Building without Docker
|
||||||
|
|
||||||
|
Requirements: Rust with the `wasm32-unknown-unknown` target.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
rustup target add wasm32-unknown-unknown
|
||||||
|
cargo build --release --target wasm32-unknown-unknown
|
||||||
|
cp target/wasm32-unknown-unknown/release/dungeoncraft.wasm www/
|
||||||
|
cp ~/.cargo/registry/src/*/macroquad-*/js/mq_js_bundle.js www/
|
||||||
|
python3 -m http.server 8080 -d www # open http://localhost:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing the game logic
|
||||||
|
|
||||||
|
The tests run natively: a headless simulation of whole games, a savegame round trip, rendering of all music modules and the audio engine:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo test --release
|
||||||
|
```
|
||||||
|
|
||||||
|
## Music
|
||||||
|
|
||||||
|
The soundtrack consists of FastTracker 2 modules in `assets/music/`, generated by `tools/compose_music.py` (`python3 tools/compose_music.py`). They can be opened and edited in MilkyTracker. The game plays them with its own XM/MOD replayer (`src/xm.rs`); sound effects are synthesised chip sounds (`src/sfx.rs`). Both are mixed in `src/audio.rs` and streamed to the browser by `www/dc_plugin.js`.
|
||||||
|
|
||||||
|
## Controls
|
||||||
|
|
||||||
|
| Input | Action |
|
||||||
|
|---|---|
|
||||||
|
| Esc | Pause menu (save, load, settings) |
|
||||||
|
| Left click / drag | Use the active tool (dig and rooms: drag a rectangle) |
|
||||||
|
| Right click | Drop a unit from your hand (prisoners anywhere on your ground; onto a free torture place = locked up) |
|
||||||
|
| Shift + left click (hand) | Backhand own units (15 s faster, costs a little HP and mood) / punch heroes on your ground (15 damage, 1 s stun) |
|
||||||
|
| Right drag, WASD, arrows, screen edge | Move the camera |
|
||||||
|
| Middle drag, Q/E, R/F | Rotate / tilt the camera |
|
||||||
|
| Mouse wheel | Zoom |
|
||||||
|
| 1–9, 0, X, V, K, J, B, C, T, U, Y, O, N | Select a tool or spell (more can be bound in the settings) |
|
||||||
|
| P / Space | Pause |
|
||||||
|
| M, +/- | Sound on/off, volume |
|
||||||
|
| G | Tile outlines on/off |
|
||||||
|
| H | Help |
|
||||||
|
|
||||||
|
All keys except Esc and the arrow keys can be changed in Settings → Keys.
|
||||||
|
|
||||||
|
The toolbar groups the tools into categories (Dig, Hand, Rooms, Towers, Traps, Spells, Orders) plus a menu button; categories with several tools open a row above the toolbar. The keys select tools directly.
|
||||||
|
|
||||||
|
### Touch (phones and tablets)
|
||||||
|
|
||||||
|
The touch interface switches on automatically on touch devices or after the first touch (Settings → Game → Touch interface: Auto / On / Off). It uses a larger interface and these gestures:
|
||||||
|
|
||||||
|
| Gesture | Action |
|
||||||
|
|---|---|
|
||||||
|
| Tap | Use the tool (hand: pick up a unit; spells, flag, towers: at that spot) |
|
||||||
|
| Drag | Dig, rooms and sell: mark a rectangle; other tools: move the map |
|
||||||
|
| Two fingers | Move the map, pinch to zoom, twist to rotate the camera |
|
||||||
|
| Long press | Information about the spot; hand: drop the units there |
|
||||||
|
| Double tap (hand) | Backhand own units / punch heroes on your ground |
|
||||||
|
|
||||||
|
The savegame name is entered with the browser's text dialog when the name field is tapped.
|
||||||
|
|
||||||
|
## Savegames
|
||||||
|
|
||||||
|
When the game is served together with the service (`server/`, started by `docker compose`), it starts with a **login**:
|
||||||
|
|
||||||
|
- The very first login creates the **admin account** with the entered name and password - do this right after installing.
|
||||||
|
- The admin creates further users (title screen -> *Account*); every user can change the password there and log out.
|
||||||
|
- Every user only sees the **own savegames** on the server: *Save Game* and *Load Game* open the list (save under a new name, overwrite, load, delete).
|
||||||
|
- *Download Standalone* (title screen) downloads `DungeonCraft.html`: the whole game in one file, without login and without server. Savegame files (*Download as file*) open there as well.
|
||||||
|
|
||||||
|
If the service cannot be reached (for example the standalone file, `www/` opened as local files or a plain web server), there is no login and the game uses savegame files: *Save Game* downloads a `.dcsave` file (JSON), *Load Game* opens the file picker; dropping the file onto the game window works too.
|
||||||
|
|
||||||
|
The game also autosaves into the browser every two minutes and when the tab is closed; *Continue* on the title screen resumes from there.
|
||||||
|
|
||||||
|
### Savegame service
|
||||||
|
|
||||||
|
A small Rust service (axum) in `server/` keeps the user accounts (Argon2 password hashes) and the savegames as files in the Docker volume `savegames` (one folder per user). nginx forwards `/api/` to it; the service itself is not published. The standalone file is served under `/standalone/` only for logged-in users (nginx `auth_request`).
|
||||||
|
|
||||||
|
| Request | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| `GET /api/health` | `ok` when the service runs (`ok setup` while there is no account yet) |
|
||||||
|
| `POST /api/login` | `{name, password}` -> `{token, name, admin}`; the first login creates the admin |
|
||||||
|
| `POST /api/logout`, `GET /api/me`, `POST /api/password` | session, own account, change password |
|
||||||
|
| `GET/POST /api/users`, `DELETE /api/users/{name}` | admin: list, create, delete users |
|
||||||
|
| `GET /api/saves` | own savegames (id, name, date, map size, wave, play time), newest first |
|
||||||
|
| `POST /api/saves?name=...` | store a new savegame (body: savegame JSON) |
|
||||||
|
| `PUT /api/saves/{id}?name=...` | overwrite a savegame |
|
||||||
|
| `GET /api/saves/{id}` | savegame JSON |
|
||||||
|
| `DELETE /api/saves/{id}` | delete a savegame |
|
||||||
|
|
||||||
|
A forgotten password is set again on the server with `docker compose stop saves && docker compose run --rm --no-deps saves dungeoncraft-saves set-password <name> '<password>' && docker compose up -d saves` (ends the user's sessions). To start over with no accounts at all, delete `users.json` and `sessions.json` in the `savegames` volume.
|
||||||
|
|
||||||
|
All requests except health and login need `Authorization: Bearer <token>`. Limits: 1 MB per savegame, 500 MB in total, 30 write requests and 20 login attempts per minute. Settings: `SAVE_DIR` (default `/data`), `LISTEN` (default `0.0.0.0:8080`).
|
||||||
|
|
||||||
|
## Adding a language
|
||||||
|
|
||||||
|
1. Copy `lang/en.lang` to `lang/<code>.lang` and translate the texts (placeholders `{0}`, `{1}` stay as they are).
|
||||||
|
2. Add the file to `SOURCES` in `src/i18n.rs`.
|
||||||
|
3. Note: the DINOBYTE font only covers the CP437 character set.
|
||||||
|
|
||||||
|
## Refreshing the generated files
|
||||||
|
|
||||||
|
| File | How |
|
||||||
|
|---|---|
|
||||||
|
| `play/DungeonCraft.html` | build the WASM file, then `python3 tools/make_standalone.py <www dir with dungeoncraft.wasm and mq_js_bundle.js> play/DungeonCraft.html` (the Docker image builds the same file under `/standalone/`) |
|
||||||
|
| `docs/img/units`, `docs/img/icons` | `cargo test --release export_doc_images -- --ignored` |
|
||||||
|
| `BESTIARY.md` | `python3 tools/make_bestiary.py` |
|
||||||
@@ -1,135 +1,169 @@
|
|||||||
# DungeonCraft
|
<p align="center">
|
||||||
|
<img src="docs/img/logo.png" width="128" alt="DungeonCraft - the soul core">
|
||||||
|
</p>
|
||||||
|
|
||||||
> **Made with the help of [Claude Code](https://claude.com/claude-code).** The code, graphics, texts and
|
<h1 align="center">DungeonCraft</h1>
|
||||||
> documentation of this game were written together with Claude Code, Anthropic's AI coding assistant.
|
|
||||||
|
|
||||||
An original dungeon building & defence game that runs in the browser (Rust + [macroquad](https://github.com/not-fl3/macroquad), compiled to WebAssembly).
|
<p align="center"><b>Dig. Build. Defend. Conquer.</b><br>
|
||||||
|
An evil little dungeon game for the browser - in glorious pixels, in 3D, for free.</p>
|
||||||
|
|
||||||
Dig out your underground realm, claim the portal to attract creatures, keep them fed, rested and paid, fend off endless hero waves that adapt to your army – and finally destroy the Hero Gate.
|
<p align="center">
|
||||||
|
<img src="https://img.shields.io/badge/Rust-macroquad-b7410e?logo=rust&logoColor=white" alt="Rust">
|
||||||
|
<img src="https://img.shields.io/badge/WebAssembly-runs%20in%20the%20browser-654ff0?logo=webassembly&logoColor=white" alt="WebAssembly">
|
||||||
|
<img src="https://img.shields.io/badge/made%20with-Claude%20Code-d97757?logo=anthropic&logoColor=white" alt="Made with Claude Code">
|
||||||
|
<img src="https://img.shields.io/badge/license-MIT-2ea44f" alt="MIT License">
|
||||||
|
<img src="https://img.shields.io/badge/languages-EN%20%7C%20DE-555" alt="English and German">
|
||||||
|
</p>
|
||||||
|
|
||||||
- 3D isometric view with a freely rotatable and tiltable camera
|
<p align="center">
|
||||||
- Procedural pixel art, irregular cave walls, flickering torch light
|
<a href="play/DungeonCraft.html"><b>▶ Play now - download one HTML file, open it, done</b></a><br>
|
||||||
- CP437 text-mode style interface with the DINOBYTE bitmap font
|
<a href="BESTIARY.md">Bestiary</a> · <a href="INSTALL.md">Run your own server</a> · <a href="DESIGN.md">Design notes</a>
|
||||||
- Map sizes from 64×64 to 256×256 tiles (one portal per 64×64 tiles, dwarf caves with treasure and rune forge plans)
|
</p>
|
||||||
- English and German (language files in `lang/`, easy to extend)
|
|
||||||
|
|
||||||
See [DESIGN.md](DESIGN.md) for all design decisions and [CREDITS.md](CREDITS.md) for third-party assets.
|
<p align="center">
|
||||||
|
<img src="docs/screenshots/title-battle.png" width="820" alt="The title screen with the endless bridge battle">
|
||||||
|
</p>
|
||||||
|
|
||||||
## Running
|
---
|
||||||
|
|
||||||
The easiest way is Docker; the image builds the WASM file and serves it with nginx. A second container runs the savegame service (see below):
|
## Wake up, master.
|
||||||
|
|
||||||
```sh
|
Yes, you. Don't look around, there's nobody else down here - just you, four imps and me, old **Crookfinger**.
|
||||||
docker compose up -d --build
|
|
||||||
# open http://localhost:18093
|
|
||||||
```
|
|
||||||
|
|
||||||
### Install as an app (PWA)
|
A hundred years ago the Order of the Lightwatch smashed your soul core into shards and carried them off like trophies.
|
||||||
|
They polished their boots, sang songs about the sun and forgot about you. **Big mistake.**
|
||||||
|
|
||||||
DungeonCraft is a progressive web app: browsers offer to install it (Chrome/Edge: install icon in the address bar or "Install app" in the menu; Android: "Add to home screen"; iOS Safari: Share → "Add to Home Screen"). The installed game starts in full screen and also works offline (the savegame list on the server then falls back to savegame files).
|
Your heart still beats. Hear that? *Lub-dub. Lub-dub.* That's the sound of a grudge.
|
||||||
|
|
||||||
Browsers only allow the installation over **HTTPS** (or on `localhost`), so a public installation needs a reverse proxy with a certificate in front of port 18093.
|
So pick up the pickaxe - well, point at the rock, the imps carry the pickaxes - and let's dig ourselves a realm.
|
||||||
|
Gold is in the walls, creatures are waiting behind the portal, and the knights... the knights are about to have
|
||||||
|
a very bad century.
|
||||||
|
|
||||||
### Building without Docker
|
<p align="center">
|
||||||
|
<img src="docs/screenshots/dungeon-heart.png" width="820" alt="The beating crystal heart, the demon gate and the dark towers">
|
||||||
|
<br><i>Your heart, master, held in chains on its obsidian throne. And the demon gate. I polished it myself.</i>
|
||||||
|
</p>
|
||||||
|
|
||||||
Requirements: Rust with the `wasm32-unknown-unknown` target.
|
## What awaits you down here
|
||||||
|
|
||||||
```sh
|
**🕳 Dig your realm.** Mark the rock and your imps dig, claim the ground, carry gold, gems and diamonds to the
|
||||||
rustup target add wasm32-unknown-unknown
|
treasury and hang torches on the walls. Up to five imps help each other with one job. Every tile you claim is *yours*.
|
||||||
cargo build --release --target wasm32-unknown-unknown
|
|
||||||
cp target/wasm32-unknown-unknown/release/dungeoncraft.wasm www/
|
|
||||||
cp ~/.cargo/registry/src/*/macroquad-*/js/mq_js_bundle.js www/
|
|
||||||
python3 -m http.server 8080 -d www # open http://localhost:8080
|
|
||||||
```
|
|
||||||
|
|
||||||
### Testing the game logic
|
**🏰 Build rooms that bring the right guests.** A lair and a mushroom garden open the portal. A library brings
|
||||||
|
shadow mages, a training hall stone ogres, a temple the flame priestess, an aerie giant bats, gargoyles, arcane
|
||||||
|
owls - and maybe, one day, an **ember wyrm**. Fourteen rooms in total, from the forge to the sacrifice altar.
|
||||||
|
|
||||||
The tests run natively: a headless simulation of whole games, a savegame round trip, rendering of all music modules and the audio engine:
|
**👹 Keep them happy, or they leave.** Every creature wants a bed, food and its wage on payday - and it walks to the
|
||||||
|
treasury to fetch it. Each has a favourite room, and some of them simply cannot stand each other.
|
||||||
|
Put a mage next to an ogre and watch the fur fly. (Nobody dies. Probably.)
|
||||||
|
|
||||||
```sh
|
**⚔ Train them into legends.** Ten ranks from Recruit to Warlord, with chevrons, bars and stars - and gear that grows
|
||||||
cargo test --release
|
with them, up to golden armour, a crown and a halo. With the level come abilities: the ogre learns to *stomp*, the mage
|
||||||
```
|
to *step through shadows*, the chainbreaker to spin like a *whirlwind*.
|
||||||
|
|
||||||
## Music
|
<p align="center">
|
||||||
|
<img src="docs/screenshots/bridge-battle.png" width="820" alt="A battle at a bridge over an underground river">
|
||||||
|
<br><i>The knights brought a bridge. How thoughtful. Now they're bringing it back, one piece at a time.</i>
|
||||||
|
</p>
|
||||||
|
|
||||||
The soundtrack consists of FastTracker 2 modules in `assets/music/`, generated by `tools/compose_music.py` (`python3 tools/compose_music.py`). They can be opened and edited in MilkyTracker. The game plays them with its own XM/MOD replayer (`src/xm.rs`); sound effects are synthesised chip sounds (`src/sfx.rs`). Both are mixed in `src/audio.rs` and streamed to the browser by `www/dc_plugin.js`.
|
**🔥 Defend with towers, traps and doors.** Arrow spires, hellmouth cannons and dark spires on the walls. In the
|
||||||
|
**forge** your creatures build spike pits, poison vents, fire jets, stone rollers, alarm bells, rune mines and
|
||||||
|
frost glows - and doors of wood, iron and magic. Lock them, and nobody gets through. Not even the giant. Well. Not for a while.
|
||||||
|
|
||||||
## Controls
|
**✨ Cast nasty spells.** Lightning, healing, seer's eye, cave-in, haste, stoneskin, gold rain, invisibility,
|
||||||
|
plague and quake. Your scholars **research** them in the library - one discovery after another.
|
||||||
|
|
||||||
| Input | Action |
|
**🌫 Explore the dark.** Everything beyond your walls is black until your creatures go there.
|
||||||
|
Somewhere out there: a cave of neutral **dwarves** guarding gold and gems - and sometimes a chest with the plans
|
||||||
|
of a dwarven **rune forge tower**. Islands in lava rivers. Mana springs. Creatures in chains, waiting to be freed.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="docs/screenshots/forge-and-traps.png" width="400" alt="The forge with coglings and every kind of trap and door">
|
||||||
|
<img src="docs/screenshots/dark-rooms.png" width="400" alt="Prison, graveyard, arena, altar and guard posts">
|
||||||
|
<br><i>Left: the forge and its little coglings. Right: prison, graveyard, arena, guard posts and the altar. Cosy.</i>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
**👑 Other masters want your realm, too.** Mossgrim the Mouldy (green, smells like a wet cellar) and Lady Nightwell
|
||||||
|
(blue, writes threatening letters on scented paper) dig their own dungeons in free play and in the campaign.
|
||||||
|
And the humans attack *everybody*. Let them bite each other first.
|
||||||
|
|
||||||
|
**📜 A campaign of thirty missions.** Take back the shards of your soul core - from the first little outpost by the
|
||||||
|
border brook to the citadel of Grand Master Valerian Brightshield. Rivers, fortresses, treasure islands, sealed
|
||||||
|
caves, rival masters, and me, telling you the story. With tea.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="docs/screenshots/dwarf-cave.png" width="400" alt="A dwarf cave with a rune chest">
|
||||||
|
<img src="docs/screenshots/fog-of-war.png" width="400" alt="The start of a game in the fog of war">
|
||||||
|
<br><i>A dwarf cave, master. Nice dwarves. Don't go too close. - And this is where every game begins: in the dark.</i>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Meet the household
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="docs/img/units/worker_5.png" width="56" alt="Pit imp">
|
||||||
|
<img src="docs/img/units/kobold_5.png" width="56" alt="Kobold">
|
||||||
|
<img src="docs/img/units/spider_5.png" width="56" alt="Cave spider">
|
||||||
|
<img src="docs/img/units/mage_5.png" width="56" alt="Shadow mage">
|
||||||
|
<img src="docs/img/units/ogre_5.png" width="56" alt="Stone ogre">
|
||||||
|
<img src="docs/img/units/jailer_5.png" width="56" alt="Jailer">
|
||||||
|
<img src="docs/img/units/priestess_5.png" width="56" alt="Flame priestess">
|
||||||
|
<img src="docs/img/units/cogling_5.png" width="56" alt="Cogling">
|
||||||
|
<img src="docs/img/units/revenant_5.png" width="56" alt="Revenant">
|
||||||
|
<img src="docs/img/units/bat_5.png" width="56" alt="Giant bat">
|
||||||
|
<img src="docs/img/units/gargoyle_5.png" width="56" alt="Chain gargoyle">
|
||||||
|
<img src="docs/img/units/owl_5.png" width="56" alt="Arcane owl">
|
||||||
|
<img src="docs/img/units/wyrm_5.png" width="56" alt="Ember wyrm">
|
||||||
|
<img src="docs/img/units/chainbreaker_5.png" width="56" alt="Chainbreaker">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
> *Fourteen kinds of wonderful creatures, master, and eight kinds of humans in shiny tin. I wrote everything down for
|
||||||
|
> you - who loves what, who hates whom, who learns what - in* **[Crookfinger's Bestiary](BESTIARY.md)**.
|
||||||
|
> *The same book is in the game, by the way - on the title screen and in the pause menu.*
|
||||||
|
|
||||||
|
## How to play
|
||||||
|
|
||||||
|
1. **Download [`play/DungeonCraft.html`](play/DungeonCraft.html)** and open it in your browser. That's all.
|
||||||
|
It runs offline, on desktop and on phones (touch controls switch on by themselves).
|
||||||
|
2. Or **run your own server** with Docker - with user accounts, savegames on the server and the game as an
|
||||||
|
installable app: see **[INSTALL.md](INSTALL.md)**.
|
||||||
|
|
||||||
|
**The basics, master:**
|
||||||
|
|
||||||
|
| | |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Esc | Pause menu (save, load, settings) |
|
| Left click / drag | use the tool - dig, build rooms (drag a rectangle), cast, place towers and traps |
|
||||||
| Left click / drag | Use the active tool (dig and rooms: drag a rectangle) |
|
| Right click | drop what your hand holds (creatures, prisoners - or a sacrifice on the altar) |
|
||||||
| Right click | Drop a unit from your hand (prisoners anywhere on your ground; onto a free torture place = locked up) |
|
| Shift + click | slap your own creatures (faster for a while) or punch a hero |
|
||||||
| Shift + left click (hand) | Backhand own units (15 s faster, costs a little HP and mood) / punch heroes on your ground (15 damage, 1 s stun) |
|
| Right drag, WASD, screen edge | move the camera · middle drag, Q/E, R/F: rotate and tilt · wheel: zoom |
|
||||||
| Right drag, WASD, arrows, screen edge | Move the camera |
|
| Esc | pause menu: save, load, settings, bestiary |
|
||||||
| Middle drag, Q/E, R/F | Rotate / tilt the camera |
|
|
||||||
| Mouse wheel | Zoom |
|
|
||||||
| 1–9, 0, X, V, K, J, B, C, T | Select a tool or spell |
|
|
||||||
| P / Space | Pause |
|
|
||||||
| M, +/- | Sound on/off, volume |
|
|
||||||
| G | Tile outlines on/off |
|
|
||||||
| H | Help |
|
|
||||||
|
|
||||||
All keys except Esc and the arrow keys can be changed in Settings → Keys.
|
On phones: tap to use a tool, drag to mark or move, two fingers to zoom and turn, long press for information.
|
||||||
|
|
||||||
The toolbar groups the tools into categories (Dig, Hand, Rooms, Towers, Spells, Orders) plus a menu button; categories with several tools open a row above the toolbar. The keys select tools directly.
|
## Under the hood
|
||||||
|
|
||||||
### Touch (phones and tablets)
|
- **Rust**, compiled to **WebAssembly**, with [macroquad](https://github.com/not-fl3/macroquad) - one small module, no engine, no plugins.
|
||||||
|
- A real 3D isometric world in pixel art: chunked meshes, flickering point lights, a glow pass, particles,
|
||||||
|
billboard sprites with eight viewing directions, every frame of it drawn by code - **no image files** except the font.
|
||||||
|
- Procedural chiptune soundtrack as FastTracker 2 modules with its own XM replayer and synthesised sound effects.
|
||||||
|
- A tiny Rust savegame server (axum, Argon2 passwords) - optional; without it the game uses savegame files.
|
||||||
|
- English and German, more languages are one text file away.
|
||||||
|
|
||||||
The touch interface switches on automatically on touch devices or after the first touch (Settings → Game → Touch interface: Auto / On / Off). It uses a larger interface and these gestures:
|
## Made with Claude Code
|
||||||
|
|
||||||
| Gesture | Action |
|
<p><img src="docs/img/claude-mark.png" width="72" alt="Claude Code mark" align="left"> DungeonCraft was developed with the help of
|
||||||
|---|---|
|
<a href="https://claude.com/claude-code"><b>Claude Code</b></a>, Anthropic's AI coding assistant - the code, the graphics,
|
||||||
| Tap | Use the tool (hand: pick up a unit; spells, flag, towers: at that spot) |
|
the music, the texts and this page. Claude, Claude Code and Anthropic are trademarks of Anthropic; the pixel mark only
|
||||||
| Drag | Dig, rooms and sell: mark a rectangle; other tools: move the map |
|
credits the tool.</p>
|
||||||
| Two fingers | Move the map, pinch to zoom, twist to rotate the camera |
|
<br clear="left">
|
||||||
| Long press | Information about the spot; hand: drop the units there |
|
|
||||||
| Double tap (hand) | Backhand own units / punch heroes on your ground |
|
|
||||||
|
|
||||||
The savegame name is entered with the browser's text dialog when the name field is tapped.
|
|
||||||
|
|
||||||
## Savegames
|
|
||||||
|
|
||||||
When the game is served together with the service (`server/`, started by `docker compose`), it starts with a **login**:
|
|
||||||
|
|
||||||
- The very first login creates the **admin account** with the entered name and password - do this right after installing.
|
|
||||||
- The admin creates further users (title screen -> *Account*); every user can change the password there and log out.
|
|
||||||
- Every user only sees the **own savegames** on the server: *Save Game* and *Load Game* open the list (save under a new name, overwrite, load, delete).
|
|
||||||
- *Download Standalone* (title screen) downloads `DungeonCraft.html`: the whole game in one file, without login and without server. Savegame files (*Download as file*) open there as well.
|
|
||||||
|
|
||||||
If the service cannot be reached (for example the standalone file, `www/` opened as local files or a plain web server), there is no login and the game uses savegame files: *Save Game* downloads a `.dcsave` file (JSON), *Load Game* opens the file picker; dropping the file onto the game window works too.
|
|
||||||
|
|
||||||
The game also autosaves into the browser every two minutes and when the tab is closed; *Continue* on the title screen resumes from there.
|
|
||||||
|
|
||||||
### Savegame service
|
|
||||||
|
|
||||||
A small Rust service (axum) in `server/` keeps the user accounts (Argon2 password hashes) and the savegames as files in the Docker volume `savegames` (one folder per user). nginx forwards `/api/` to it; the service itself is not published. The standalone file is served under `/standalone/` only for logged-in users (nginx `auth_request`).
|
|
||||||
|
|
||||||
| Request | Meaning |
|
|
||||||
|---|---|
|
|
||||||
| `GET /api/health` | `ok` when the service runs (`ok setup` while there is no account yet) |
|
|
||||||
| `POST /api/login` | `{name, password}` -> `{token, name, admin}`; the first login creates the admin |
|
|
||||||
| `POST /api/logout`, `GET /api/me`, `POST /api/password` | session, own account, change password |
|
|
||||||
| `GET/POST /api/users`, `DELETE /api/users/{name}` | admin: list, create, delete users |
|
|
||||||
| `GET /api/saves` | own savegames (id, name, date, map size, wave, play time), newest first |
|
|
||||||
| `POST /api/saves?name=...` | store a new savegame (body: savegame JSON) |
|
|
||||||
| `PUT /api/saves/{id}?name=...` | overwrite a savegame |
|
|
||||||
| `GET /api/saves/{id}` | savegame JSON |
|
|
||||||
| `DELETE /api/saves/{id}` | delete a savegame |
|
|
||||||
|
|
||||||
A forgotten password is set again on the server with `docker compose stop saves && docker compose run --rm --no-deps saves dungeoncraft-saves set-password <name> '<password>' && docker compose up -d saves` (ends the user's sessions). To start over with no accounts at all, delete `users.json` and `sessions.json` in the `savegames` volume.
|
|
||||||
|
|
||||||
All requests except health and login need `Authorization: Bearer <token>`. Limits: 1 MB per savegame, 500 MB in total, 30 write requests and 20 login attempts per minute. Settings: `SAVE_DIR` (default `/data`), `LISTEN` (default `0.0.0.0:8080`).
|
|
||||||
|
|
||||||
## Adding a language
|
|
||||||
|
|
||||||
1. Copy `lang/en.lang` to `lang/<code>.lang` and translate the texts (placeholders `{0}`, `{1}` stay as they are).
|
|
||||||
2. Add the file to `SOURCES` in `src/i18n.rs`.
|
|
||||||
3. Note: the DINOBYTE font only covers the CP437 character set.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
DungeonCraft is released under the [MIT License](LICENSE) – this covers the code, the generated music in `assets/music/` and the procedural graphics.
|
DungeonCraft is released under the **[MIT License](LICENSE)** - © 2026 Richard Zink. It covers the code, the generated
|
||||||
The DINOBYTE font (`assets/dbyte_1x.png`) by mby is licensed separately under CC BY 4.0, see [CREDITS.md](CREDITS.md).
|
music in `assets/music/` and the procedural graphics.
|
||||||
|
|
||||||
|
The **DINOBYTE** font (`assets/dbyte_1x.png`) by [mby](https://mby.itch.io/dinobyte) is licensed separately under
|
||||||
|
**CC BY 4.0**. Details in [CREDITS.md](CREDITS.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<p align="center"><i>"The underworld is yours, master. Now - tea?"</i><br>- Crookfinger</p>
|
||||||
|
After Width: | Height: | Size: 253 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 420 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 710 B |
|
After Width: | Height: | Size: 401 B |
|
After Width: | Height: | Size: 856 B |
|
After Width: | Height: | Size: 562 B |
|
After Width: | Height: | Size: 423 B |
|
After Width: | Height: | Size: 503 B |
|
After Width: | Height: | Size: 479 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 414 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 653 B |
|
After Width: | Height: | Size: 576 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 457 B |
|
After Width: | Height: | Size: 373 B |
|
After Width: | Height: | Size: 894 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 431 B |
|
After Width: | Height: | Size: 428 B |
|
After Width: | Height: | Size: 552 B |
|
After Width: | Height: | Size: 608 B |
|
After Width: | Height: | Size: 710 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 747 B |
|
After Width: | Height: | Size: 580 B |
|
After Width: | Height: | Size: 458 B |
|
After Width: | Height: | Size: 823 B |
|
After Width: | Height: | Size: 559 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 971 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 782 B |
|
After Width: | Height: | Size: 881 B |
|
After Width: | Height: | Size: 798 B |
|
After Width: | Height: | Size: 792 B |
|
After Width: | Height: | Size: 894 B |
|
After Width: | Height: | Size: 841 B |
|
After Width: | Height: | Size: 802 B |
|
After Width: | Height: | Size: 868 B |
|
After Width: | Height: | Size: 844 B |
|
After Width: | Height: | Size: 895 B |
|
After Width: | Height: | Size: 991 B |
|
After Width: | Height: | Size: 917 B |
|
After Width: | Height: | Size: 818 B |
|
After Width: | Height: | Size: 910 B |
|
After Width: | Height: | Size: 863 B |
|
After Width: | Height: | Size: 775 B |
|
After Width: | Height: | Size: 848 B |
|
After Width: | Height: | Size: 805 B |
|
After Width: | Height: | Size: 721 B |
|
After Width: | Height: | Size: 805 B |
|
After Width: | Height: | Size: 748 B |
|
After Width: | Height: | Size: 854 B |
|
After Width: | Height: | Size: 945 B |
|
After Width: | Height: | Size: 879 B |
|
After Width: | Height: | Size: 801 B |
|
After Width: | Height: | Size: 902 B |
|
After Width: | Height: | Size: 824 B |
|
After Width: | Height: | Size: 771 B |
|
After Width: | Height: | Size: 893 B |
|
After Width: | Height: | Size: 817 B |
|
After Width: | Height: | Size: 805 B |
|
After Width: | Height: | Size: 905 B |
|
After Width: | Height: | Size: 819 B |
|
After Width: | Height: | Size: 797 B |
|
After Width: | Height: | Size: 924 B |
|
After Width: | Height: | Size: 853 B |
|
After Width: | Height: | Size: 794 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 821 B |
|
After Width: | Height: | Size: 827 B |
|
After Width: | Height: | Size: 958 B |
|
After Width: | Height: | Size: 874 B |
|
After Width: | Height: | Size: 866 B |
|
After Width: | Height: | Size: 960 B |
|
After Width: | Height: | Size: 920 B |
|
After Width: | Height: | Size: 813 B |
|
After Width: | Height: | Size: 909 B |
|
After Width: | Height: | Size: 833 B |
|
After Width: | Height: | Size: 689 B |
|
After Width: | Height: | Size: 782 B |
|
After Width: | Height: | Size: 757 B |
|
After Width: | Height: | Size: 734 B |
|
After Width: | Height: | Size: 823 B |
|
After Width: | Height: | Size: 800 B |
|
After Width: | Height: | Size: 853 B |